diff --git a/sdk/packages/core/src/runtime/orchestration/session-runtime-orchestrator.test.ts b/sdk/packages/core/src/runtime/orchestration/session-runtime-orchestrator.test.ts index 194b4f53d1..314aa3d3c6 100644 --- a/sdk/packages/core/src/runtime/orchestration/session-runtime-orchestrator.test.ts +++ b/sdk/packages/core/src/runtime/orchestration/session-runtime-orchestrator.test.ts @@ -779,6 +779,59 @@ describe("SessionRuntime.abort", () => { expect(abortCalls).toEqual(["user cancelled"]); }); + it("observes an abort rejection before the caller awaits the run", async () => { + let rejectRun: ((error: Error) => void) | undefined; + const runGate = new Promise((_resolve, reject) => { + rejectRun = reject; + }); + let markRunStarted: (() => void) | undefined; + const runStarted = new Promise((resolve) => { + markRunStarted = resolve; + }); + const abortCalls: unknown[] = []; + const runtime = { + async run() { + markRunStarted?.(); + return await runGate; + }, + async continue() { + markRunStarted?.(); + return await runGate; + }, + abort(reason?: unknown) { + abortCalls.push(reason); + rejectRun?.(new Error(String(reason ?? "aborted"))); + }, + subscribe() { + return () => {}; + }, + snapshot() { + return makeSnapshot(); + }, + } as unknown as AgentRuntime; + const unhandledReasons: unknown[] = []; + const onUnhandledRejection = (reason: unknown): void => { + unhandledReasons.push(reason); + }; + + process.prependListener("unhandledRejection", onUnhandledRejection); + try { + const session = new SessionRuntime(makeAgentConfig(), { + createAgentRuntimeImpl: () => runtime, + }); + const runPromise = session.run("slow"); + await runStarted; + session.abort("user cancelled"); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(unhandledReasons).toEqual([]); + await expect(runPromise).rejects.toThrow("user cancelled"); + expect(abortCalls).toEqual(["user cancelled"]); + } finally { + process.off("unhandledRejection", onUnhandledRejection); + } + }); + it("is a no-op when no run is active", () => { const { deps } = withFakeRuntime(); const session = new SessionRuntime(makeAgentConfig(), deps); diff --git a/sdk/packages/core/src/runtime/orchestration/session-runtime-orchestrator.ts b/sdk/packages/core/src/runtime/orchestration/session-runtime-orchestrator.ts index e47b25681d..90b6a186b3 100644 --- a/sdk/packages/core/src/runtime/orchestration/session-runtime-orchestrator.ts +++ b/sdk/packages/core/src/runtime/orchestration/session-runtime-orchestrator.ts @@ -194,7 +194,7 @@ export class SessionRuntime { private abortReason: string | undefined; /** Reference to the current run's `AgentRuntime` so `abort` can forward. */ private activeRuntime: AgentRuntime | null = null; - /** Promise for the current run so shutdown can await an aborted run's drain. */ + /** Promise returned from the current run so shutdown can await its drain. */ private activeRunPromise: Promise | null = null; /** Per-run `Agent → AgentEvent` adapter; `reset()` each run. */ private readonly eventAdapter = new RuntimeEventAdapter(); @@ -541,7 +541,7 @@ export class SessionRuntime { // Run / continue // ------------------------------------------------------------------- - async run( + run( userMessage: string, userImages?: string[], userFiles?: string[], @@ -556,7 +556,7 @@ export class SessionRuntime { }); } - async continue( + continue( userMessage?: string, userImages?: string[], userFiles?: string[], @@ -573,21 +573,20 @@ export class SessionRuntime { // Private implementation // ------------------------------------------------------------------- - private async executeRun(input: { + private executeRun(input: { userMessage?: string; userImages?: string[]; userFiles?: string[]; isContinue: boolean; }): Promise { - const runPromise = this.executeRunInternal(input); - this.activeRunPromise = runPromise; - try { - return await runPromise; - } finally { - if (this.activeRunPromise === runPromise) { + let activePromise!: Promise; + activePromise = this.executeRunInternal(input).finally(() => { + if (this.activeRunPromise === activePromise) { this.activeRunPromise = null; } - } + }); + this.activeRunPromise = activePromise; + return activePromise; } private async executeRunInternal(input: {