fix(coding-agent): add replacement-session callbacks closes #2860

This commit is contained in:
Mario Zechner
2026-04-22 12:13:54 +02:00
parent a900d25119
commit 1cc303d053
11 changed files with 579 additions and 67 deletions

View File

@@ -2,7 +2,7 @@ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
import { basename, join, resolve } from "node:path";
import type { AgentSession } from "./agent-session.js";
import type { AgentSessionRuntimeDiagnostic, AgentSessionServices } from "./agent-session-services.js";
import type { SessionShutdownEvent, SessionStartEvent } from "./extensions/index.js";
import type { ReplacedSessionContext, SessionShutdownEvent, SessionStartEvent } from "./extensions/index.js";
import { emitSessionShutdownEvent } from "./extensions/runner.js";
import type { CreateAgentSessionResult } from "./sdk.js";
import { assertSessionCwdExists } from "./session-cwd.js";
@@ -65,6 +65,8 @@ function extractUserMessageText(content: string | Array<{ type: string; text?: s
* caller. The caller is responsible for user-facing error handling.
*/
export class AgentSessionRuntime {
private rebindSession?: (session: AgentSession) => Promise<void>;
constructor(
private _session: AgentSession,
private _services: AgentSessionServices,
@@ -93,6 +95,10 @@ export class AgentSessionRuntime {
return this._modelFallbackMessage;
}
setRebindSession(rebindSession?: (session: AgentSession) => Promise<void>): void {
this.rebindSession = rebindSession;
}
private async emitBeforeSwitch(
reason: "new" | "resume",
targetSessionFile?: string,
@@ -143,14 +149,26 @@ export class AgentSessionRuntime {
this._modelFallbackMessage = result.modelFallbackMessage;
}
async switchSession(sessionPath: string, cwdOverride?: string): Promise<{ cancelled: boolean }> {
private async finishSessionReplacement(withSession?: (ctx: ReplacedSessionContext) => Promise<void>): Promise<void> {
if (this.rebindSession) {
await this.rebindSession(this.session);
}
if (withSession) {
await withSession(this.session.createReplacedSessionContext());
}
}
async switchSession(
sessionPath: string,
options?: { cwdOverride?: string; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },
): Promise<{ cancelled: boolean }> {
const beforeResult = await this.emitBeforeSwitch("resume", sessionPath);
if (beforeResult.cancelled) {
return beforeResult;
}
const previousSessionFile = this.session.sessionFile;
const sessionManager = SessionManager.open(sessionPath, undefined, cwdOverride);
const sessionManager = SessionManager.open(sessionPath, undefined, options?.cwdOverride);
assertSessionCwdExists(sessionManager, this.cwd);
await this.teardownCurrent("resume", sessionManager.getSessionFile());
this.apply(
@@ -161,12 +179,14 @@ export class AgentSessionRuntime {
sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
}),
);
await this.finishSessionReplacement(options?.withSession);
return { cancelled: false };
}
async newSession(options?: {
parentSession?: string;
setup?: (sessionManager: SessionManager) => Promise<void>;
withSession?: (ctx: ReplacedSessionContext) => Promise<void>;
}): Promise<{ cancelled: boolean }> {
const beforeResult = await this.emitBeforeSwitch("new");
if (beforeResult.cancelled) {
@@ -193,12 +213,13 @@ export class AgentSessionRuntime {
await options.setup(this.session.sessionManager);
this.session.agent.state.messages = this.session.sessionManager.buildSessionContext().messages;
}
await this.finishSessionReplacement(options?.withSession);
return { cancelled: false };
}
async fork(
entryId: string,
options?: { position?: "before" | "at" },
options?: { position?: "before" | "at"; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },
): Promise<{ cancelled: boolean; selectedText?: string }> {
const position = options?.position ?? "before";
const beforeResult = await this.emitBeforeFork(entryId, { position });
@@ -242,6 +263,7 @@ export class AgentSessionRuntime {
sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
}),
);
await this.finishSessionReplacement(options?.withSession);
return { cancelled: false, selectedText };
}
@@ -260,6 +282,7 @@ export class AgentSessionRuntime {
sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
}),
);
await this.finishSessionReplacement(options?.withSession);
return { cancelled: false, selectedText };
}
@@ -278,6 +301,7 @@ export class AgentSessionRuntime {
sessionStartEvent: { type: "session_start", reason: "fork", previousSessionFile },
}),
);
await this.finishSessionReplacement(options?.withSession);
return { cancelled: false, selectedText };
}
@@ -321,6 +345,7 @@ export class AgentSessionRuntime {
sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile },
}),
);
await this.finishSessionReplacement();
return { cancelled: false };
}