跳到內容

Session Lifecycle 與 Resume

Session lifetime 是產品決策。Pedelec 預設 page-scoped lifecycle,但也能讓 Core session 保留,之後重新 resume。

const session = await pedelec.createSession({
provider: "codex",
autoEndOnDisconnect: true,
});

true 是預設值。Extension 追蹤附著於 session 的 SDK routes;最後一條 route disconnect 後會請 Core end thread。

常見 disconnect 原因:

  • 關閉 tab;
  • page reload;
  • navigation 銷毀 JavaScript page;
  • Extension reload/update;
  • browser 或 Extension process interruption。

Session 不應超出目前 visible application context 時使用 page-scoped。

const session = await pedelec.createSession({
provider: "codex",
autoEndOnDisconnect: false,
});
const record = {
sessionId: session.sessionId,
provider: session.provider,
effortLevel: session.effortLevel,
};
localStorage.setItem("pedelec-session", JSON.stringify(record));

false 代表 SDK route 消失時不自動 end Core session,application 之後可用 saved ID resume。

Browser-side 的 PedelecSession handle 綁定於建立它的 SDK route。若該 runtime port disconnect,即使 parent Pedelec client 已 reconnect,這個 handle 仍會變成不可使用。請明確呼叫 resumeSession(sessionId),讓新 handle subscribe 到仍存活的 Core session;SDK 不會靜默搬移舊 session operation。明確 end() 的 handle 則不同:只要 transport 尚未 detached,同一個 object 可以呼叫 session.resume() 重新啟用已結束的 Core thread。

只有在已有 retention 與 cleanup policy 時使用。永遠不 resume 或 end 的 session 可能在 Desktop state 存活得比 UI 暗示更久。

const session = await pedelec.resumeSession(savedSessionId);

resumeSession()

  1. 要求 non-empty session ID;
  2. 目前 origin 未核准時觸發 approval;
  3. 將目前 SDK route subscribe 到既有 Core thread;
  4. reconciliation 回傳的 authoritative lifecycle snapshot,包括 status、active/last operation identity,以及可能存在的 pending tool request;Ended thread 仍會保持 ended,這個 method 不會隱式呼叫 Core resume_thread
  5. 回傳已同步的 PedelecSession handle。

若 Core 已知 normalized token usage,同一個 resume snapshot 也會 hydrate session.usage.totalTokens。缺少 usage field 代表目前尚未知道受支援的 total;對既有 handle 不會因此重設 usage。

空 ID 會收到 INVALID_INPUT

Core 不再認得 ID 時會回傳 THREAD_NOT_FOUND,應移除 stale persistence 或建立另一個 session。Session 屬於其他 browser origin 時會 reject THREAD_ACCESS_DENIED。SDK 不會靜默建立 replacement session。

const session = await pedelec.createSession({
provider: "codex",
autoEndOnDisconnect: false,
});
await session.sendText("第一個任務");
await session.end();
await session.resume();
console.log(session.getStatus()); // "idle"
await session.sendText("繼續");

PedelecSession.resume() 是同一個、尚未 transport-detached 的 browser handle 所執行的明確 Ended → Idle Core lifecycle transition。它保留 thread ID、handlers、usage、provider/effort metadata、prepared 狀態與 provider session identity。它不會啟動或接觸 provider runtime;下一個 sendText() 或其他正常 operation 才會 lazy resume provider work。

只有 Core 仍知道 thread,且記錄的 workspace_path 仍存在並且是 directory 時才能 reactivation。Desktop-managed 與 application-managed workspace 都支援。Workspace 缺失或無法開啟時 reject WORKSPACE_OPEN_FAILED;Desktop/Core restart 後若 Core 不再知道 thread,會 reject THREAD_NOT_FOUND,不能只靠 workspace contents 重建 thread。

Active handle 的 resume() 是 idempotent,同一個 ended handle 的 concurrent calls 會共用一個 request。Transport-detached handle 會 reject;請改用 pedelec.resumeSession(sessionId),讓新的 handle attach 到仍存活的 Core thread。即使該 Core thread 是 ended,detached handle 也不能用 session.resume() 復活。

JavaScript function 無法跨 reload 保存。使用 Pedelec.resumeSession() 後重新 attach:

const session = await pedelec.resumeSession(record.sessionId);
const offChat = session.onChat(handleCompletedChat);
const offChatDelta = session.onChatDelta(handleChatDelta);
const offStatus = session.onStatus(handleStatus);
const offError = session.onError(handleError);
const offEnded = session.onEnded(handleEnded);
const offTool = session.onTool("update_counter", handleUpdateCounter);

原始 createSession({ skills }) 的 inline handlers 不會在新 page 自動重建。Core session 仍有 tool manifest,但 resumed browser 必須替可能被 agent 呼叫的 tools 註冊 handlers。 同一個 handle 使用 session.resume() 時,原有 handlers(包括 inline handlers)會保留。

Generic fallback 可協助 migration,但不應取代每個 tool 的 deliberate validation。

Resume bridge 會回傳 sessionId 與內部 lifecycle snapshot,但不是完整 Core session record。因此 provider metadata 仍可能不可用,但 lifecycle status 會是 authoritative:

session.provider === "";
session.effortLevel === undefined;

Reload 後 UI 需要 provider 或 effort level 時,請和 session ID 一起保存,或從 application-owned persistence 載入。不要假設 resumeSession() 會 rehydrate 所有 metadata;但 resumeSession() resolve 後可依 getStatus() 取得 Core lifecycle state。同一個 handle 使用 session.resume() 時,已知的 metadata 會保留。

session.sessionCreatedAt 是 browser-side handle construction time,不是原始 Core creation time。

SSR hydration、React Strict Mode development 或 duplicated effect 可能重複呼叫 resumeSession()。請用 application state guard:

let resumePromise: Promise<PedelecSession> | null = null;
function resumeOnce(id: string) {
resumePromise ??= pedelec.resumeSession(id);
return resumePromise;
}

切換 ID 時再明確清除或替換 guard。

await session.end();

成功 end() 會:

  • 請 Core end session;
  • 將 SDK handle mark ended
  • 針對這次進入 ended 的 transition emit 一次 onEnded()
  • 從 client unregister session;
  • 未來 sendText()prepare() 在同一個 handle 成功 resume() 前收到 SESSION_ENDED

end() 不會立即刪除 session workspace;lifecycle 取決於建立 session 時的設定:

  • 未指定 workspace.path:使用 Desktop 管理的 temporary storage。Desktop App 正常退出時移除 managed workspace,異常終止的 leftovers 會在下次啟動嘗試清除。關閉主視窗只會隱藏 App,不會執行這個 cleanup。
  • 指定 absolute workspace.path:使用 application 管理的 workspace。Pedelec 在 end()、App 正常退出或 stale-workspace cleanup 時都不會刪除它,多個 active session 也可以共用。

即使 cleanup 尚未執行,ended handle 也不能透過 uploadAsset()listAssets()readAsset() 存取 asset。Shared workspace 的 filesystem conflict 不由 Pedelec 協調,explicit path 也不得與 managed workspace root overlap。檔案被鎖定時,managed cleanup 可能要到後續啟動才能完成。

同一個已 ended handle 再呼叫 end() 會直接 resolve,不送第二次 request。

session.resume() 是另外的明確 reactivation operation;它要求原 handle 的 transport 仍然 attached,也不會從 workspace contents 重建 thread。

End request 失敗時,end() emit SDK error 並 reject。因 Core state 不確定,failed call 不會先 local mark ended。

async function disposeSession() {
const current = session;
session = null;
try {
if (current && current.getStatus() !== "ended") {
await current.end();
}
} finally {
for (const dispose of handlerDisposers) dispose();
handlerDisposers = [];
localStorage.removeItem("pedelec-session");
}
}

使用 autoEndOnDisconnect: false 時,只有在產品確定 session 不再可恢復或不再需要時,才移除 persisted ID。

Extension 可以將一個 session route 給多條 SDK connections。Automatic end enabled 時,要等最後一條 route 消失才會 end,這也帶來協調問題:

  • session 同時只允許一個 active turn;
  • 兩個 tab 可能 race 並收到 SESSION_BUSY
  • tool handler 可能存在於多個 page context;
  • application state 可能在 tabs 間分歧。

除非刻意支援 cross-tab collaboration,建議保存 ownership token 或避免多個 active controller。

Native Host 或單一 thread subscription disconnect 時,Extension 會恢復 subscription 並重新 reconciliation snapshot,不會自動重送可能語意不明的 sendText()prepare() 或 tool-result mutation。請保留 operation ID 與 promise error 作為 diagnostics,讓恢復後的 authoritative state 決定下一個操作。

需求 建議設定
只屬於單一 page 的 temporary assistant autoEndOnDisconnect: true
Full reload 後 resume false + persist sessionId
Multi-page workflow false + explicit ownership/cleanup
敏感且短暫的 context 優先 true 並 explicit end()
Long-running task false,加上清楚 status 與 recovery UX

下一區:前端 Tools