セッション switch/fork/import
AgentSessionRuntime は AgentSession の外側のラッパーで、「セッション差し替え」を管轄する。実行中のプロセス内で AgentSession インスタンスは何度も差し替えられ得る:/new で新規セッション、/resume で旧セッションに切替、/fork で特定メッセージから分岐、/import で外部 jsonl を取り込み。毎回の差し替えで旧 session を解体し、新サービスを作り、拡張を再バインドし、イベント購読を復元する。このファイルはその差し替えフローを入れる容器だ。
責務
- 現在 session の保持:
_sessionと_servicesフィールドが現在生きているインスタンスを格納し、session/services/cwdgetter で晒す。packages/coding-agent/src/core/agent-session-runtime.ts:67-97参照。 - new / switch / fork / import:4 つの差し替えメソッド。まず before イベント (拡張が cancel 可能) を送り、次に
teardownCurrent、その後にapplyで新 runtime を当てる。packages/coding-agent/src/core/agent-session-runtime.ts:175-198、packages/coding-agent/src/core/agent-session-runtime.ts:200-232、packages/coding-agent/src/core/agent-session-runtime.ts:234-320、packages/coding-agent/src/core/agent-session-runtime.ts:329-364参照。 - イベントフック:
session_before_switch/session_before_fork/session_shutdownの 3 種の拡張イベント。拡張が差し替えを cancel できる。packages/coding-agent/src/core/agent-session-runtime.ts:115-147参照。 - rebind コールバック:host (InteractiveMode か rpc-mode) は
setRebindSessionで、session 差し替え後に呼ばれるコールバックを登録する。これで拡張 UI を再バインドし、イベントを再購読する。packages/coding-agent/src/core/agent-session-runtime.ts:99-113、packages/coding-agent/src/core/agent-session-runtime.ts:166-173参照。 - ファクトリ再利用:
createRuntimeクロージャはcreateAgentSessionRuntime時に渡し、以降の差し替えすべてで同じファクトリを再利用し、cwd/agentDir/拡張パスが一致するようにする。packages/coding-agent/src/core/agent-session-runtime.ts:382-400参照。
設計動機
なぜ直接 this.session = new AgentSession(...) しないのか? 差し替えは 3 つのことを含むからだ:旧 session は shutdown イベントを送って拡張にリソース解放させないといけない。新 session は同じパスからサービス (settings、auth、resource loader) を読み込まないといけない。UI 層はイベントを再購読し、拡張コマンドコンテキストを再バインドしないといけない。この 3 つの順序が肝で——拡張が shutdown を受け取ってから dispose しないと、拡張が持つ参照がダangling pointer になる。teardownCurrent → apply → finishSessionReplacement の三段式はこの順序の硬保証だ。
createRuntime ファクトリの再利用も語る価値がある:差し替えごとに cwd、agentDir、拡張パスを再解析するのは重すぎるし、パラメータがドリフトする。クロージャが CLI で一度解析した設定を捕捉し、以降の差し替えはすべて同じものを使う。これで何度 /resume しても拡張パスが突然無効になったりしない。
主要ファイル
packages/coding-agent/src/core/agent-session-runtime.ts:67-97—class AgentSessionRuntimeフィールドと getter。packages/coding-agent/src/core/agent-session-runtime.ts:149-164—teardownCurrentとapply。差し替えのコア 2 ステップ。packages/coding-agent/src/core/agent-session-runtime.ts:166-173—finishSessionReplacement。host の rebind コールバックを発火。packages/coding-agent/src/core/agent-session-runtime.ts:175-198—switchSession。既存 jsonl を resume。packages/coding-agent/src/core/agent-session-runtime.ts:200-232—newSession。parentSessionで分岐ツリーを形成できる。packages/coding-agent/src/core/agent-session-runtime.ts:234-320—fork。positionbefore/atの 2 つの意味。packages/coding-agent/src/core/agent-session-runtime.ts:329-364—importFromJsonl。外部 jsonl を sessionDir にコピーしてから switch。packages/coding-agent/src/core/agent-session-runtime.ts:382-400—createAgentSessionRuntime。初期 runtime ファクトリ入り口。
差し替え三段式:
// packages/coding-agent/src/core/agent-session-runtime.ts:149-164
private async teardownCurrent(reason: SessionShutdownEvent["reason"], targetSessionFile?: string): Promise<void> {
await emitSessionShutdownEvent(this.session.extensionRunner, {
type: "session_shutdown",
reason,
targetSessionFile,
});
this.beforeSessionInvalidate?.();
this.session.dispose();
}
private apply(result: CreateAgentSessionRuntimeResult): void {
this._session = result.session;
this._services = result.services;
this._diagnostics = result.diagnostics;
this._modelFallbackMessage = result.modelFallbackMessage;
}fork は position: "at" の時、選択されたエントリをそのまま分岐点とし、before の時は親エントリを取り、ユーザーメッセージのテキストを抽出してエディタに戻す:
// packages/coding-agent/src/core/agent-session-runtime.ts:251-258
if (position === "at") {
targetLeafId = selectedEntry.id;
} else {
if (selectedEntry.type !== "message" || selectedEntry.message.role !== "user") {
throw new Error("Invalid entry ID for forking");
}
targetLeafId = selectedEntry.parentId;
selectedText = extractUserMessageText(selectedEntry.message.content);
}データフロー
session 差し替えの統一フロー:
境界と失敗
- fork で非ユーザーメッセージ選択:
position: "before"の時はユーザーメッセージからしか分岐できず、違反ならInvalid entry ID for forkingを投げる。packages/coding-agent/src/core/agent-session-runtime.ts:254-256参照。 - fork 失敗のロールバック:
sourceManager.createBranchedSessionが null を返すとFailed to create forked sessionを投げるが、この時旧 session は既に dispose 済みで、呼び出し側はこの「中間態」エラーを処理する必要がある。packages/coding-agent/src/core/agent-session-runtime.ts:286-288参照。 - import ファイル不在:
existsSyncチェック後にSessionImportFileNotFoundErrorを投げる。黙って失敗しない。packages/coding-agent/src/core/agent-session-runtime.ts:330-333参照。 - import の cwd 欠落:
assertSessionCwdExistsが取り込む jsonl の cwd にまだアクセスできるか検証する。interactive モードは prompt でユーザーに再選させる。packages/coding-agent/src/core/agent-session-runtime.ts:351-352参照。 - dispose 順序:
quitreason も同じくteardownCurrentフローを通り、プロセス退出時に拡張が shutdown イベントを受け取ることを保証する。
小ねた
AgentSessionRuntime はセッション差し替えを統一三段式に抽象化する:teardown → apply → rebind。4 つの入り口 (new/switch/fork/import) が同じ teardown とファクトリクロージャを共用し、拡張は before イベントで cancel できる。組み立てファクトリ内部の詳細は createAgentSession 装配、差し替え対象の session 自身は AgentSession オーケストレーション層 参照。