fix(coding-agent): reactivate a live debug session when the active child terminates

When the active js-debug child exits or terminates while other tree
sessions remain alive, #activeSessionId pointed at the dead child and
every subsequent tool action failed. Reassign to a live tree session
(preferring stopped, then non-root) on exited/terminated/proc-exit.
Regression test proves threads route to the surviving session.
This commit is contained in:
can1357
2026-07-18 19:37:07 +02:00
parent ab77aeb245
commit bf94f05a8e
2 changed files with 54 additions and 0 deletions
@@ -123,6 +123,10 @@ class FakeDapClient {
for (const handler of this.#events.get(event) ?? []) void handler(body, message);
}
emit(event: string, body: unknown): void {
this.#emit(event, body);
}
async #emitReverse(command: string, args: unknown): Promise<void> {
const handler = this.#reverseHandlers.get(command);
if (!handler) throw new Error(`Missing reverse handler for ${command}`);
@@ -209,4 +213,37 @@ describe("DAP multi-session debugging", () => {
await manager.terminate(undefined, 100);
});
it("reactivates a live session when the active child terminates", async () => {
const root = new FakeDapClient({
name: "target.js",
type: "pwa-node",
__pendingTargetId: "child",
});
const child = new FakeDapClient();
spyOn(DapClient, "spawn").mockResolvedValue(root as unknown as DapClient);
spyOn(DapClient, "connect").mockResolvedValue(child as unknown as DapClient);
const manager = new DapSessionManager();
const launched = await manager.launch(
{ adapter: TEST_ADAPTER, program: "/tmp/target.js", cwd: "/tmp" },
undefined,
1_000,
);
expect(launched.parentSessionId).toBeDefined();
child.emit("terminated", {});
await child.dispose();
const active = manager.getActiveSession();
expect(active).not.toBeNull();
expect(active?.id).not.toBe(launched.id);
expect(active?.status).not.toBe("terminated");
const threads = await manager.threads(undefined, 100);
expect(threads.threads).toEqual([{ id: 7, name: "target.js" }]);
expect(root.requests.filter(request => request.command === "threads")).toHaveLength(1);
await manager.terminate(undefined, 100);
});
});