fix(coding-agent): aggregate debug threads without assuming a threadless root

Review feedback: classifying every root-with-a-live-child as a threadless
launcher dropped the root from thread aggregation, so a custom TCP adapter
whose root process owns threads and also issues startDebugging would have its
threads omitted. Drop the launcher heuristic: threads now fans out across all
live sessions in the tree and merges results. A genuinely threadless launcher
simply returns no threads (or an error we skip), so no topology guess is made.

Fixes #6663
This commit is contained in:
roboomp
2026-07-26 03:48:08 +00:00
parent 74c279c60b
commit 04f61e2125
2 changed files with 31 additions and 28 deletions
@@ -223,7 +223,9 @@ describe("DAP multi-session debugging", () => {
expect(active?.parentSessionId).toBeDefined();
expect(threads.threads).toEqual([{ id: 7, name: "target.js" }]);
expect(child.requests.filter(request => request.command === "threads")).toHaveLength(1);
expect(root.requests.filter(request => request.command === "threads")).toHaveLength(0);
// The root is queried too (no topology guess); here it just echoes the
// same thread, which dedupes away.
expect(root.requests.filter(request => request.command === "threads")).toHaveLength(1);
await manager.terminate(undefined, 100);
});
@@ -262,12 +264,18 @@ describe("DAP multi-session debugging", () => {
});
it("keeps focus on the stopped script child when a worker attaches later", async () => {
const root = new FakeDapClient({
name: "script.mts",
type: "pwa-node",
__pendingTargetId: "main",
program: "/tmp/script.mts",
});
const root = new FakeDapClient(
{
name: "script.mts",
type: "pwa-node",
__pendingTargetId: "main",
program: "/tmp/script.mts",
},
"launch",
true,
// Threadless launcher: it answers `threads` with an empty list.
{ threads: [] },
);
// The script child stops on entry (thread 1), then a worker session
// attaches afterwards via a late reverse `startDebugging`.
const main = new FakeDapClient(undefined, "launch", true, {
@@ -308,14 +316,15 @@ describe("DAP multi-session debugging", () => {
// `threads` must surface every live thread across the tree, not just one.
const threads = await manager.threads(undefined, 1_000);
expect(threads.threads).toHaveLength(2);
expect(threads.threads).toEqual(
expect.arrayContaining([
{ id: 1, name: "script.mts" },
{ id: 1, name: "[worker 1]" },
]),
);
// The threadless launcher is never queried while real children are live.
expect(root.requests.filter(request => request.command === "threads")).toHaveLength(0);
// The launcher is still queried, but being threadless it contributes none.
expect(root.requests.filter(request => request.command === "threads")).toHaveLength(1);
await manager.terminate(undefined, 1_000);
});