Settle in-flight run when the session is replaced or disposed

Claude-Session: https://claude.ai/code/session_01JtuZTCMW9TyogEmBgGf8JT
This commit is contained in:
EugeneTes
2026-09-06 16:46:46 +02:00
parent 1e5b1b6f3e
commit 4b7348a704
2 changed files with 31 additions and 0 deletions

View File

@@ -184,6 +184,25 @@ describe("AgentHost", () => {
expect(events.filter((e) => e.type === "run_settled")).toHaveLength(1);
});
it("settles an in-flight run as aborted when the session is replaced", async () => {
const { session, host, events } = await setup();
let release!: () => void;
session.promptImpl = async () => {
session.emit({ type: "agent_start" });
await new Promise<void>((r) => (release = r));
};
const sending = host.send({ text: "x" });
await new Promise((r) => setTimeout(r, 0));
expect(events).toEqual([{ type: "run_started" }]);
await host.newSession();
expect(session.calls).toContain("abort");
expect(events.at(-1)).toEqual({ type: "run_settled", finalText: "", aborted: true });
release();
await sending;
session.emit({ type: "agent_settled" });
expect(events.filter((e) => e.type === "run_settled" || e.type === "run_error")).toHaveLength(1);
});
it("emits run_error when prompt rejects", async () => {
const { session, host, events } = await setup();
session.promptImpl = async () => {

View File

@@ -71,10 +71,14 @@ export class AgentHost {
}
async newSession(): Promise<void> {
if (this.busy) {
await this.session?.abort().catch(() => undefined);
}
this.attach(await this.backend.newSession());
}
async dispose(): Promise<void> {
this.settleActiveRun();
this.unsubscribe?.();
this.unsubscribe = undefined;
await this.backend.dispose();
@@ -142,12 +146,20 @@ export class AgentHost {
}
private attach(session: SessionLike): void {
this.settleActiveRun();
this.unsubscribe?.();
this.session = session;
this.run = { texts: [], terminalEmitted: true, retrying: false };
this.unsubscribe = session.subscribe((event) => this.onSessionEvent(event));
}
/** Closes out an in-flight run as an aborted settle before the session is swapped or torn down. */
private settleActiveRun(): void {
if (!this.run.terminalEmitted) {
this.finishRun({ type: "run_settled", finalText: "", aborted: true });
}
}
private onSessionEvent(event: SessionEvent): void {
switch (event.type) {
case "agent_start":