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:
@@ -1359,10 +1359,12 @@ export class DapSessionManager {
|
||||
client.onEvent("exited", body => {
|
||||
session.exitCode = (body as DapExitedEventBody | undefined)?.exitCode;
|
||||
session.status = "terminated";
|
||||
this.#reactivateAfterTermination(session);
|
||||
this.#resolveTreeOutcome(session);
|
||||
});
|
||||
client.onEvent("terminated", () => {
|
||||
session.status = "terminated";
|
||||
this.#reactivateAfterTermination(session);
|
||||
this.#resolveTreeOutcome(session);
|
||||
});
|
||||
this.#sessions.set(session.id, session);
|
||||
@@ -1379,6 +1381,7 @@ export class DapSessionManager {
|
||||
void client.proc.exited.finally(() => {
|
||||
clearInterval(heartbeat);
|
||||
session.status = "terminated";
|
||||
this.#reactivateAfterTermination(session);
|
||||
this.#resolveTreeOutcome(session);
|
||||
});
|
||||
return session;
|
||||
@@ -1735,6 +1738,20 @@ export class DapSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Point the active session at a live tree member when the active one terminates. */
|
||||
#reactivateAfterTermination(session: DapSession): void {
|
||||
if (this.#activeSessionId !== session.id) return;
|
||||
const live = this.#getTreeSessions(session).filter(
|
||||
candidate => candidate.status !== "terminated" && candidate.client.isAlive(),
|
||||
);
|
||||
if (live.length === 0) return;
|
||||
const replacement =
|
||||
live.find(candidate => candidate.status === "stopped") ??
|
||||
live.find(candidate => candidate.parentSessionId !== undefined) ??
|
||||
live[0];
|
||||
this.#activeSessionId = replacement.id;
|
||||
}
|
||||
|
||||
#resolveTreeOutcome(session: DapSession): void {
|
||||
const rootId = this.#getRootSession(session).id;
|
||||
for (const waiter of [...this.#treeOutcomeWaiters]) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user