diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 948b44080..5c674773d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixed -- Fixed `AsyncJobManager.instance()` being cleared while the owning top-level session was still live, which broke the `task` async path with "Async execution is enabled but no async job manager is available" until process restart. Any in-process secondary top-level `createAgentSession()` call (e.g. the Agent Control Center's create flow in `agent-dashboard.ts`) constructed a fresh `AsyncJobManager`, overwrote the singleton, and then cleared it on its own dispose. Secondary sessions now leave the live singleton untouched, and their dispose-time cleanup is scoped so it can no longer cancel the primary session's running bash/task jobs. `bash` / `task` / `job` tools and session job snapshots now resolve the manager through session-scoped async manager wiring rather than `AsyncJobManager.instance()`, so a secondary in-process top-level session cannot accidentally register background work on the owning session's manager or report the owning session's jobs; subagents still inherit the parent's manager via their scoped async manager ([#1923](https://github.com/can1357/oh-my-pi/issues/1923)). +- Fixed `AsyncJobManager.instance()` being cleared while the owning top-level session was still live, which broke the `task` async path with "Async execution is enabled but no async job manager is available" until process restart. Any in-process secondary top-level `createAgentSession()` call (e.g. the Agent Control Center's create flow in `agent-dashboard.ts`) constructed a fresh `AsyncJobManager`, overwrote the singleton, and then cleared it on its own dispose. Secondary sessions now leave the live singleton untouched, and their dispose-time cleanup is scoped so it can no longer cancel the primary session's running bash/task jobs. `bash` / `task` / `job` tools and session job snapshots now resolve the manager through session-scoped async manager wiring rather than `AsyncJobManager.instance()`, so a secondary in-process top-level session cannot accidentally register background work on the owning session's manager or report the owning session's jobs; subagents still inherit the parent's manager via their scoped async manager. Startup failures after a top-level session installs its manager now clear and dispose that manager before the next session decides whether it can create its own ([#1923](https://github.com/can1357/oh-my-pi/issues/1923)). ## [15.9.1] - 2026-06-04 diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index 0f5d73e9b..7a6e69802 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -2276,6 +2276,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} await session.dispose(); } else { if (hasRegistered) agentRegistry.unregister(resolvedAgentId); + if (asyncJobManager) { + if (AsyncJobManager.instance() === asyncJobManager) { + AsyncJobManager.setInstance(undefined); + } + await asyncJobManager.dispose({ timeoutMs: 3_000 }); + } await disposeKernelSessionsByOwner(evalKernelOwnerId); } } catch (cleanupError) { diff --git a/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts b/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts index 420a7be1e..0d4a8029d 100644 --- a/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts +++ b/packages/coding-agent/test/sdk-async-job-manager-singleton.test.ts @@ -133,4 +133,40 @@ describe("AsyncJobManager singleton across concurrent top-level sessions", () => await primary.dispose(); } }); + + it("clears a manager installed before a top-level session startup failure takes ownership", async () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `pi-sdk-async-startup-failure-${Snowflake.next()}-`)); + tempDirs.push(tempDir); + const cwd = path.join(tempDir, `project-${Snowflake.next()}`); + const agentDir = path.join(tempDir, "agent"); + fs.mkdirSync(cwd, { recursive: true }); + + await expect( + createAgentSession({ + cwd, + agentDir, + settings: Settings.isolated({ "bash.autoBackground.enabled": true }), + disableExtensionDiscovery: true, + skills: [], + contextFiles: [], + promptTemplates: [], + slashCommands: [], + enableMCP: false, + enableLsp: false, + systemPrompt: () => { + throw new Error("forced startup failure"); + }, + }), + ).rejects.toThrow("forced startup failure"); + + expect(AsyncJobManager.instance()).toBeUndefined(); + + const replacement = await spawnTopLevelSession(); + try { + expect(AsyncJobManager.instance()).toBeDefined(); + expect(replacement.getAsyncJobSnapshot()).not.toBeNull(); + } finally { + await replacement.dispose(); + } + }); });