From 83fb60d6a005481405f0fd42aff8c8f13d3c2f6b Mon Sep 17 00:00:00 2001 From: JF Date: Fri, 6 Nov 2026 08:57:50 -0500 Subject: [PATCH 1/8] =?UTF-8?q?feat(javascript):=20autoAttachChildProcesse?= =?UTF-8?q?s=20defaults=20to=20true=20on=20launch=20and=20attach=20?= =?UTF-8?q?=E2=80=94=20every=20Node=20child=20is=20a=20debug=20session=20(?= =?UTF-8?q?#1082)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The session tree's M2 second slice: with the mechanism of #1094 in place (every process js-debug reports after the program's own is adopted as a child session, or released by a throwaway attach beyond the cap), the guard that defaulted the key off — #501's park, which could only happen while a report could be dropped — has nothing left to guard. The launch transform, the attach transform and the policy's self-contained attach guard now default `autoAttachChildProcesses` to `true`; an explicit `false` (dapLaunchArgs on launch, adapterConfig on attach) keeps the children undebugged as before. The `attach_to_process` schema text says what the key does now (a child debug session per fork, the opt-out), the `wait_for_stop` text and the skill text name JavaScript beside Python as on by default, and the 24 tool-list snapshots move with the two strings. The unit pins of the old default are inverted, with a launch twin. Measured before this flip, on the self-debug suite (an mcp-debugger attached to another): the nested server's forked proxy worker is adopted as a child session and still acknowledges its init inside the window. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_015BDrn6AsHQ4rmTXFHzQkn8 --- .../src/javascript-debug-adapter.ts | 23 +++++++++++-------- ...javascript-debug-adapter.transform.test.ts | 23 +++++++++++++++---- .../src/interfaces/adapter-policy-js.ts | 18 +++++++-------- skills/debugging/SKILL.md | 2 +- skills/debugging/references/javascript.md | 2 +- src/server/tool-schemas.ts | 4 ++-- src/skill-content.ts | 2 +- .../bp-assert.va-explicit.container-true.json | 4 ++-- ...bp-assert.va-explicit.container-unset.json | 4 ++-- .../bp-assert.va-open.container-true.json | 4 ++-- .../bp-assert.va-open.container-unset.json | 4 ++-- .../bp-assert.va-unset.container-true.json | 4 ++-- .../bp-assert.va-unset.container-unset.json | 4 ++-- ...bp-content.va-explicit.container-true.json | 4 ++-- ...p-content.va-explicit.container-unset.json | 4 ++-- .../bp-content.va-open.container-true.json | 4 ++-- .../bp-content.va-open.container-unset.json | 4 ++-- .../bp-content.va-unset.container-true.json | 4 ++-- .../bp-content.va-unset.container-unset.json | 4 ++-- .../bp-line.va-explicit.container-true.json | 4 ++-- .../bp-line.va-explicit.container-unset.json | 4 ++-- .../bp-line.va-open.container-true.json | 4 ++-- .../bp-line.va-open.container-unset.json | 4 ++-- .../bp-line.va-unset.container-true.json | 4 ++-- .../bp-line.va-unset.container-unset.json | 4 ++-- .../bp-unset.va-explicit.container-true.json | 4 ++-- .../bp-unset.va-explicit.container-unset.json | 4 ++-- .../bp-unset.va-open.container-true.json | 4 ++-- .../bp-unset.va-open.container-unset.json | 4 ++-- .../bp-unset.va-unset.container-true.json | 4 ++-- .../bp-unset.va-unset.container-unset.json | 4 ++-- tests/unit/shared/adapter-policy-js.test.ts | 12 ++++++---- 32 files changed, 102 insertions(+), 80 deletions(-) diff --git a/packages/adapter-javascript/src/javascript-debug-adapter.ts b/packages/adapter-javascript/src/javascript-debug-adapter.ts index 4175a3a0c..618839be8 100644 --- a/packages/adapter-javascript/src/javascript-debug-adapter.ts +++ b/packages/adapter-javascript/src/javascript-debug-adapter.ts @@ -628,10 +628,13 @@ export class JavascriptDebugAdapter extends EventEmitter implements IDebugAdapte skipFiles, console: 'internalConsole', outputCapture: 'std', - // Off by default (every fork() would park under waitForDebugger, #501); - // an explicit caller value wins, as it does on attach + // On by default (the session tree, issue #1082): every Node process the + // program starts is reported by js-debug and adopted as a child debug + // session, or released by a throwaway attach beyond the cap, so the + // park of #501 cannot happen. An explicit caller value wins, as it + // does on attach: `false` keeps the children undebugged. autoAttachChildProcesses: - typeof u.autoAttachChildProcesses === 'boolean' ? u.autoAttachChildProcesses : false, + typeof u.autoAttachChildProcesses === 'boolean' ? u.autoAttachChildProcesses : true, env: mergedEnv }; @@ -991,15 +994,17 @@ export class JavascriptDebugAdapter extends EventEmitter implements IDebugAdapte name: 'Attach to Node.js process', host: attachHost, port: attachPort, - // js-debug's pwa-node attach defaults this to true, injecting its - // NODE_OPTIONS bootloader into the inspected process; every fork() then - // parks under waitForDebugger and only one child can be adopted (#501). - // Default it off like launch mode does; an explicit caller value wins - // (never silently override a supported key — cf. #499). + // On by default, like launch (the session tree, issue #1082): js-debug + // injects its NODE_OPTIONS bootloader into the inspected process, and + // every process it forks after the attach is reported and adopted as a + // child debug session (released by a throwaway attach beyond the cap), + // so the park of #501 cannot happen. An explicit caller value wins + // (never silently override a supported key — cf. #499): `false` keeps + // the children undebugged. autoAttachChildProcesses: typeof rest.autoAttachChildProcesses === 'boolean' ? rest.autoAttachChildProcesses - : false, + : true, // js-debug's smart-stepper turns a user pause that lands on a // blackboxed/unmapped frame into an auto-step — and on a mostly-idle // server every frame the pause can land on is a node-internal one, so diff --git a/packages/adapter-javascript/tests/unit/javascript-debug-adapter.transform.test.ts b/packages/adapter-javascript/tests/unit/javascript-debug-adapter.transform.test.ts index e85eaad0a..4a35cc343 100644 --- a/packages/adapter-javascript/tests/unit/javascript-debug-adapter.transform.test.ts +++ b/packages/adapter-javascript/tests/unit/javascript-debug-adapter.transform.test.ts @@ -687,16 +687,17 @@ describe('JavascriptDebugAdapter.transformLaunchConfig', () => { expect(cfg.continueOnAttach).toBe(true); }); - it('defaults autoAttachChildProcesses to false on attach (issue #501)', async () => { + it('defaults autoAttachChildProcesses to true on attach (issues #501, #1082)', async () => { const adapter = new JavascriptDebugAdapter(deps); const cfg = await adapter.transformAttachConfig({ request: 'attach', port: 9229 } as any) as Record; - // js-debug's pwa-node attach defaults this to true, which parks every - // fork() of the inspected process in waitForDebugger - expect(cfg.autoAttachChildProcesses).toBe(false); + // Every fork the target starts after the attach is reported by js-debug + // and adopted as a child session (the session tree, issue #1082), so the + // #501 park that made the attach default this off cannot happen. + expect(cfg.autoAttachChildProcesses).toBe(true); }); it('respects a caller-supplied autoAttachChildProcesses (issue #501)', async () => { @@ -882,6 +883,20 @@ describe('JavascriptDebugAdapter.transformLaunchConfig passthrough (issue #703)' expect(cfg.__attachMode).toBeUndefined(); }); + it('defaults autoAttachChildProcesses to true on launch and keeps a caller false (issue #1082)', async () => { + const adapter = new JavascriptDebugAdapter(depsWithFileSystem([])); + const defaulted = await adapter.transformLaunchConfig({ + program: path.resolve('/proj/app.js') + } as any) as Record; + expect(defaulted.autoAttachChildProcesses).toBe(true); + + const optedOut = await adapter.transformLaunchConfig({ + program: path.resolve('/proj/app.js'), + autoAttachChildProcesses: false + } as any) as Record; + expect(optedOut.autoAttachChildProcesses).toBe(false); + }); + it('pins the launch shape mcp-debugger owns, lets autoAttachChildProcesses follow the caller, and says so', async () => { const d = depsWithFileSystem([]); const adapter = new JavascriptDebugAdapter(d); diff --git a/packages/shared/src/interfaces/adapter-policy-js.ts b/packages/shared/src/interfaces/adapter-policy-js.ts index 501e56a8b..3dbc8d6ed 100644 --- a/packages/shared/src/interfaces/adapter-policy-js.ts +++ b/packages/shared/src/interfaces/adapter-policy-js.ts @@ -928,19 +928,19 @@ export const JsDebugAdapterPolicy = { if (typeof stopOnEntryValue === 'boolean') { attachArgs.stopOnEntry = stopOnEntryValue; } - // js-debug's pwa-node attach defaults autoAttachChildProcesses to true, - // which bootloads every fork() of the inspected process into - // waitForDebugger; with single-child adoption those forks wedge (#501). - // The MCP path already defaults this off in transformAttachConfig; this - // guard makes the policy self-contained for embedders that bypass the - // adapter transform. A caller-supplied boolean is respected, sourced - // like stopOnEntry above: launchConfig (via callerAttachExtras), then - // dapLaunchArgs. + // autoAttachChildProcesses is on by default (the session tree, issue + // #1082): every process the inspected one forks after the attach is + // reported and adopted as a child session, or released by a throwaway + // attach, so the wedge of #501 cannot happen. The MCP path defaults it + // in transformAttachConfig; this guard makes the policy self-contained + // for embedders that bypass the adapter transform. A caller-supplied + // boolean is respected, sourced like stopOnEntry above: launchConfig + // (via callerAttachExtras), then dapLaunchArgs. if (typeof attachArgs.autoAttachChildProcesses !== 'boolean') { attachArgs.autoAttachChildProcesses = typeof a.autoAttachChildProcesses === 'boolean' ? (a.autoAttachChildProcesses as boolean) - : false; + : true; } // Same self-containment for resolveSourceMapLocations (issue #655): // js-debug's attach defaults collapse it to null without a workspace diff --git a/skills/debugging/SKILL.md b/skills/debugging/SKILL.md index 88d2daa33..48d3c4b9d 100644 --- a/skills/debugging/SKILL.md +++ b/skills/debugging/SKILL.md @@ -45,7 +45,7 @@ Rules that prevent 90% of failed sessions: - **Absolute paths only** for `file` and `scriptPath` (relative paths are rejected in host mode). - **Use real frame IDs.** Take `id` from `get_stack_trace` frames; it is adapter-assigned and is not 0-indexed. - **Expand variable containers.** If a variable entry carries a `variablesReference`, call `get_variables` again with that reference to see children (Python's "special variables", object fields, array elements). -- **Respect session state.** Stepping, evaluation, and variable reads require `PAUSED`. After `continue_execution` the session is `RUNNING`; after a step or breakpoint hit it returns to `PAUSED` with a persisted stop reason telling you why it stopped (`breakpoint`, `step`, `entry`, `exception`, ...). `continue_execution` returns at once — call `wait_for_stop {sessionId}` to block until the program stops again or ends; it answers with the stop (`lastStop`, `location`) or the exit code. A session whose program spawned child sessions (Python, on by default; `dapLaunchArgs: { subProcess: false }` opts a launch out) is waited on as a tree: the answer's `sessionId` names the session that stopped — use it to inspect — and `children` lists the rest with their states. +- **Respect session state.** Stepping, evaluation, and variable reads require `PAUSED`. After `continue_execution` the session is `RUNNING`; after a step or breakpoint hit it returns to `PAUSED` with a persisted stop reason telling you why it stopped (`breakpoint`, `step`, `entry`, `exception`, ...). `continue_execution` returns at once — call `wait_for_stop {sessionId}` to block until the program stops again or ends; it answers with the stop (`lastStop`, `location`) or the exit code. A session whose program spawned child sessions (Python and JavaScript, on by default; `dapLaunchArgs: { subProcess: false }` or `{ autoAttachChildProcesses: false }` opts a launch out) is waited on as a tree: the answer's `sessionId` names the session that stopped — use it to inspect — and `children` lists the rest with their states. - **A `start_debugging` that answers `running` with `pending: true` is not stuck.** The launch holds its answer only briefly — about a second — for the program's first stop, so a breakpoint reached as the program starts is answered in the one call, and anything later is collected with `wait_for_stop {sessionId}`, which blocks until the program stops or ends. Nothing was cancelled: breakpoints stay armed for as long as the program runs, and the message says what is armed. For a server, this is the workflow: set the breakpoint in the handler, `start_debugging` (answers `pending`), send the request, `wait_for_stop`. The same goes for any step, pause or attach that answers `pending: true`. `wait_for_stop` takes a `timeout` in ms (default 30000); when it runs out it answers `pending: true` again — just call it again. Keep each call under your MCP client's own request cap (45 s is safe under the common 60 s cap): a longer wait is several calls, not one long `timeout`, or the client times out first. - **Breakpoints may verify late.** Some adapters (debugpy, JDI) report breakpoints unverified until the module/class loads; that is normal, not an error. - **`` placeholders are masking, not program state.** Credential-shaped values and values of sensitive variable names (`password`, `api_key`, ...) are masked by default in variable/evaluate/output results; a `redaction` field reports what was hidden. The real value is intact in the debuggee — don't "fix" it, and don't retry the read. The user can disable masking by restarting the server with `DEBUG_MCP_NO_REDACT=1`. diff --git a/skills/debugging/references/javascript.md b/skills/debugging/references/javascript.md index 422c5d1b3..ae50be51a 100644 --- a/skills/debugging/references/javascript.md +++ b/skills/debugging/references/javascript.md @@ -79,7 +79,7 @@ mcp-debugger signals the process (`SIGUSR1` / `process._debugProcess`), which op - **Entry pause auto-continues.** With `stopOnEntry: false` (the default) the debugger automatically continues past entry breakpoints, so execution runs straight to your first breakpoint. A breakpoint reached as the program starts is in the `start_debugging` answer; one reached later — a route handler, a timer — leaves the launch answering `pending: true`, and `wait_for_stop` collects it. Set `dapLaunchArgs: { "stopOnEntry": true }` only when you want control at the first line — and read `lastStop.text` when you get it: for a file whose first declaration is a function the program calls later, js-debug's entry breakpoint resolves *inside that function*, so the "entry" stop is its first call (top-level code before it has already run) and every later call stops as "entry" again. The text says so; a line breakpoint on the first statement is the reliable way to stop there. - **Stack filtering hides Node internals.** `get_stack_trace` returns user frames only by default; pass `includeInternals: true` if you genuinely need Node.js internal frames. If execution initially stops inside internals, just `continue_execution`. - **TypeScript auto-detection.** Point `scriptPath` at the `.ts` file when `tsx`/`ts-node` is available. If neither is installed you get a warning (not an error) — fall back to the compiled `.js` with source maps. -- **Child processes are not auto-attached.** `autoAttachChildProcesses` defaults to `false`; pass it as `true` in `dapLaunchArgs` to debug `spawn`-ed Node children. +- **Child processes are debugged by default.** Every Node process the program forks or spawns becomes a child debug session of its own (`parentSessionId`, the parent's breakpoints, reported by the parent's `wait_for_stop` with its `sessionId`); pass `autoAttachChildProcesses: false` in `dapLaunchArgs` (or `adapterConfig` on attach) to keep the children undebugged. A child's output is on the launch session's buffer (`get_output` answers `outputOn`); closing a child releases it to run on. - **Output capture works** (`outputCapture: 'std'`): stdout/stderr appear as `get_output` entries; entries without a category default to `console`. - **Frame IDs are adapter-assigned.** Use the `id` field from `get_stack_trace` frames for `get_scopes` — not the array index, not 0. Locals scopes are named `Local`/`Block`, and values come back as strings. - Logpoints work: `set_breakpoint` with `logMessage: "x={x}"` logs interpolated values to `get_output` (category `console`) without pausing. mcp-debugger compiles them itself and delivers them over the inspector, so they survive a program that replaced `console.log` (an MCP stdio server, for one), a `condition` gates the log instead of pausing, strings print raw and objects through `util.inspect`, and a malformed `{expression}` is refused at `set_breakpoint` with the syntax error. A logpoint set before launch adds a hidden entry stop that is auto-continued; nothing to wait for. diff --git a/src/server/tool-schemas.ts b/src/server/tool-schemas.ts index eaed22609..3043b4b81 100644 --- a/src/server/tool-schemas.ts +++ b/src/server/tool-schemas.ts @@ -200,7 +200,7 @@ export function buildToolDefinitions(options: BuildToolDefinitionsOptions): Tool stopOnEntry: { type: 'boolean', description: 'Pause the target after attaching. Default true when omitted (thread verification, then a pause request; if the stop lands after the response you get pending:true and the target still freezes on its next dispatch). Pass false to attach without stopping — required when attaching to a live service' }, justMyCode: { type: 'boolean', description: 'Only debug user code (skip library code)' }, breakOnExceptions: { type: 'string', enum: ['uncaught', 'all', 'none'], description: 'Break when exceptions are thrown: "uncaught" pauses at uncaught exceptions at the crash site; "all" also pauses on caught/raised exceptions (language-dependent). Default "none" — attach sessions never apply a language default (unlike launch)' }, - adapterConfig: { type: 'object', description: 'Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like "settings set target.exec-search-paths /proc//root"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)', additionalProperties: true } + adapterConfig: { type: 'object', description: 'Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like "settings set target.exec-search-paths /proc//root"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)', additionalProperties: true } }, required: ['sessionId'] } @@ -221,7 +221,7 @@ export function buildToolDefinitions(options: BuildToolDefinitionsOptions): Tool { name: 'step_into', description: 'Step into the current call. Waits briefly for the program to stop; if the step is still executing after ~5s, returns success with state "running" and pending:true — the session becomes "paused" when the step completes (call wait_for_stop to block until then)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'step_out', description: 'Step out of the current function. Waits briefly for the program to stop; if the step is still executing after ~5s (e.g. the rest of the function is long-running), returns success with state "running" and pending:true — the session becomes "paused" when the step completes (call wait_for_stop to block until then)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'continue_execution', description: 'Continue execution. Returns immediately after the adapter acknowledges; does not wait for the next stop. Call wait_for_stop to block until the program stops again or ends — it reports why (lastStop, e.g. "breakpoint" vs "exception") and where; list_debug_sessions and get_stack_trace show the same without waiting. Use get_output to read the program\'s stdout/stderr', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, - { name: 'wait_for_stop', description: 'Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state "paused" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state "running" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent\'s wait', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, timeout: { type: 'number', description: 'Max time (ms) to wait for the stop (default: 30000, max: 600000). Not a limit on the program — it keeps running under the debugger either way. Note: your MCP client may enforce its own overall request timeout; keep this below it and call again on pending:true' } }, required: ['sessionId'] } }, + { name: 'wait_for_stop', description: 'Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state "paused" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state "running" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent\'s wait', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, timeout: { type: 'number', description: 'Max time (ms) to wait for the stop (default: 30000, max: 600000). Not a limit on the program — it keeps running under the debugger either way. Note: your MCP client may enforce its own overall request timeout; keep this below it and call again on pending:true' } }, required: ['sessionId'] } }, { name: 'pause_execution', description: 'Pause a running program. Waits briefly for the stop; if the program cannot stop within ~5s (e.g. blocked in native code, or an idle server waiting for input), returns success with pending:true and the session reports "paused" the next time the program runs code (call wait_for_stop to block until then). An observed stop answers with stopReason, the first visible frame as location (with its source context), and note when the stop was reported on a thread with no program frame — a sibling thread in program code is shown instead, or, when none has one, the note says no program frame is active. Fails with an actionable error if the session has no debuggable target to pause (e.g. a js attach whose target session was never adopted or has ended)', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, threadId: { type: 'number', description: 'Thread ID to pause. If omitted or 0, pauses all threads.' } }, required: ['sessionId'] } }, { name: 'list_threads', description: 'List all threads in the debugged process', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' } }, required: ['sessionId'] } }, { name: 'get_variables', description: 'Get variables (scope is variablesReference: number). Responses are size-guarded: oversized values are cut (truncated:true) and very large scopes return a capped list with a truncation notice — pass names:[...] to fetch specific variables in full', inputSchema: { type: 'object', properties: { sessionId: { type: 'string' }, scope: { type: 'number', description: "The variablesReference number from a StackFrame or Variable" }, names: namesProp }, required: getVariablesRequired } }, diff --git a/src/skill-content.ts b/src/skill-content.ts index 384bca598..1fc5f1b93 100644 --- a/src/skill-content.ts +++ b/src/skill-content.ts @@ -46,7 +46,7 @@ Failing test (Python): start_debugging {sessionId, scriptPath: ABSOLUTE test fil Key rules: - Every paused answer (start/restart/wait_for_stop/pause/attach and the steps) already carries snapshot {exception?, frames, locals | changedVariables} — the exception message, top frames and the top frame's locals, redacted and capped. A repeated stop in the same frame (stepping, a continue+wait loop over one breakpoint) carries changedVariables {name: {before, after}} instead of locals — an empty {} means the line changed nothing visible. Read it before calling get_stack_trace/get_local_variables; call those (and get_scopes -> get_variables with a variablesReference) only to expand structures, see more frames, or fetch the full list (snapshot.truncation says when there is more). Entry stops carry no snapshot. - Stepping/evaluation/variable reads require the session to be PAUSED; the stop reason on each pause tells you why it stopped. -- continue_execution returns at once, and a launch, step, pause or attach may answer pending:true when its stop has not arrived yet. Nothing is cancelled — breakpoints stay armed. Call wait_for_stop {sessionId} to block until the session pauses or ends (it reports the stop, or the exit code); a wait that runs out answers pending:true again, so call it again. A session whose program spawned child sessions (Python, on by default; dapLaunchArgs subProcess: false opts a launch out) is waited on as a tree: the answer's sessionId names the session that stopped — use it to inspect — and children lists the rest with their states. +- continue_execution returns at once, and a launch, step, pause or attach may answer pending:true when its stop has not arrived yet. Nothing is cancelled — breakpoints stay armed. Call wait_for_stop {sessionId} to block until the session pauses or ends (it reports the stop, or the exit code); a wait that runs out answers pending:true again, so call it again. A session whose program spawned child sessions (Python and JavaScript, on by default; dapLaunchArgs subProcess: false or autoAttachChildProcesses: false opts a launch out) is waited on as a tree: the answer's sessionId names the session that stopped — use it to inspect — and children lists the rest with their states. - If a variable entry has a variablesReference, call get_variables with it to expand children. - Breakpoints may report unverified until the module/class loads — that is normal. - list_breakpoints shows every breakpoint with its verified state; remove_breakpoint (by id or file+line) and clear_breakpoints take effect immediately, even mid-run — use them to move a bisection window without restarting.${statementRule}${expectedContentRule} diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-explicit.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-explicit.container-true.json index 584d09192..d5526a05a 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-explicit.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-explicit.container-true.json @@ -356,7 +356,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -492,7 +492,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-explicit.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-explicit.container-unset.json index ff743d0a6..bb8acb28f 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-explicit.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-explicit.container-unset.json @@ -356,7 +356,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -492,7 +492,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-open.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-open.container-true.json index b9ce0c78f..5ea5edbf7 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-open.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-open.container-true.json @@ -356,7 +356,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -492,7 +492,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-open.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-open.container-unset.json index 5b46f0422..6ef02ff56 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-open.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-open.container-unset.json @@ -356,7 +356,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -492,7 +492,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-unset.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-unset.container-true.json index b9ce0c78f..5ea5edbf7 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-unset.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-unset.container-true.json @@ -356,7 +356,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -492,7 +492,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-unset.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-unset.container-unset.json index 5b46f0422..6ef02ff56 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-unset.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-assert.va-unset.container-unset.json @@ -356,7 +356,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -492,7 +492,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-explicit.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-explicit.container-true.json index 3aa52b9be..69235b4fc 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-explicit.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-explicit.container-true.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-explicit.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-explicit.container-unset.json index f1188accd..38c697289 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-explicit.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-explicit.container-unset.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-open.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-open.container-true.json index ea79194f0..90fdc7866 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-open.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-open.container-true.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-open.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-open.container-unset.json index 053aff18e..32c2c660b 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-open.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-open.container-unset.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-unset.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-unset.container-true.json index ea79194f0..90fdc7866 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-unset.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-unset.container-true.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-unset.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-unset.container-unset.json index 053aff18e..32c2c660b 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-unset.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-content.va-unset.container-unset.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-explicit.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-explicit.container-true.json index d30fc0005..4b9bb5b94 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-explicit.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-explicit.container-true.json @@ -352,7 +352,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -488,7 +488,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-explicit.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-explicit.container-unset.json index 9b9712819..f6a32bbf7 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-explicit.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-explicit.container-unset.json @@ -352,7 +352,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -488,7 +488,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-open.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-open.container-true.json index 913eb517b..effc3965d 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-open.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-open.container-true.json @@ -352,7 +352,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -488,7 +488,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-open.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-open.container-unset.json index e61ca6ac3..95a993933 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-open.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-open.container-unset.json @@ -352,7 +352,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -488,7 +488,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-unset.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-unset.container-true.json index 913eb517b..effc3965d 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-unset.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-unset.container-true.json @@ -352,7 +352,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -488,7 +488,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-unset.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-unset.container-unset.json index e61ca6ac3..95a993933 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-unset.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-line.va-unset.container-unset.json @@ -352,7 +352,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -488,7 +488,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-explicit.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-explicit.container-true.json index 3aa52b9be..69235b4fc 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-explicit.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-explicit.container-true.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-explicit.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-explicit.container-unset.json index f1188accd..38c697289 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-explicit.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-explicit.container-unset.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-open.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-open.container-true.json index ea79194f0..90fdc7866 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-open.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-open.container-true.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-open.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-open.container-unset.json index 053aff18e..32c2c660b 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-open.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-open.container-unset.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-unset.container-true.json b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-unset.container-true.json index ea79194f0..90fdc7866 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-unset.container-true.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-unset.container-true.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-unset.container-unset.json b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-unset.container-unset.json index 053aff18e..32c2c660b 100644 --- a/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-unset.container-unset.json +++ b/tests/core/unit/server/__snapshots__/tool-list/bp-unset.va-unset.container-unset.json @@ -366,7 +366,7 @@ }, "adapterConfig": { "type": "object", - "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to false on attach — child processes the target forks run undebugged (set it true to auto-attach children; only one child can be adopted at a time, further children are resumed undebugged)", + "description": "Adapter-specific attach configuration merged into the attach config before the adapter transforms it (e.g. C/C++/LLDB: program — the binary path for symbol resolution when /proc//maps paths are not openable, as in a kubectl-debug ephemeral container — or initCommands like \"settings set target.exec-search-paths /proc//root\"). Reserved keys request/__attachMode are ignored; set stopOnEntry via the top-level parameter. Keys the adapter does not recognize are still forwarded to the debugger and named in the response warning — a near-miss of a supported key gets a did-you-mean suggestion. js-debug pins its attach orchestration keys (address/port/continueOnAttach/attachExistingChildren) over caller values; its autoAttachChildProcesses defaults to true on attach — each Node process the target forks after the attach becomes a child debug session (listed with parentSessionId and reported by the parent session through wait_for_stop; adapterConfig: { autoAttachChildProcesses: false } keeps the children undebugged)", "additionalProperties": true } }, @@ -502,7 +502,7 @@ }, { "name": "wait_for_stop", - "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", + "description": "Block until the session next pauses or ends, then say why and where. Call it after continue_execution (which does not wait) and whenever a tool answers pending:true, instead of polling list_debug_sessions. Answers at once when the session is already paused (state \"paused\" with lastStop — the same record list_debug_sessions shows — and the top frame as location) or already over (the terminal state, with exitCode when the debuggee reported one); note says when the answer is anchored on a thread other than the one the stop was reported on, or when no frame of the shown stack is program code. If neither happens within the timeout it answers state \"running\" with pending:true: nothing was cancelled, breakpoints and exception filters stay armed — call it again to keep waiting, or pause_execution to interrupt. A session whose program spawned child sessions (Python and JavaScript, on by default) is waited on as a tree: sessionId names the session the answer is about — the session itself when it is paused, else the first paused descendant, else the session itself when it has ended — and children lists every descendant with its state; an ended child alone never completes the parent's wait", "inputSchema": { "type": "object", "properties": { diff --git a/tests/unit/shared/adapter-policy-js.test.ts b/tests/unit/shared/adapter-policy-js.test.ts index 41fb3e9cb..6894a244f 100644 --- a/tests/unit/shared/adapter-policy-js.test.ts +++ b/tests/unit/shared/adapter-policy-js.test.ts @@ -651,7 +651,7 @@ describe('JsDebugAdapterPolicy', () => { expect(sendDapRequest.mock.calls.some(([cmd]) => cmd === 'launch')).toBe(false); }); - it('defaults autoAttachChildProcesses to false in attach args, keeping a caller value (issue #501)', async () => { + it('defaults autoAttachChildProcesses to true in attach args, keeping a caller false (issues #501, #1082)', async () => { const runAttachHandshake = async (dapLaunchArgs: Record) => { vi.useFakeTimers(); try { @@ -687,13 +687,15 @@ describe('JsDebugAdapterPolicy', () => { const defaulted = await runAttachHandshake({ request: 'attach', attachSimplePort: 9229, type: 'pwa-node' }); - expect(defaulted.autoAttachChildProcesses).toBe(false); + // The session tree (issue #1082): forks are adopted, so the #501 guard's + // `false` is gone; an explicit false still keeps the children undebugged. + expect(defaulted.autoAttachChildProcesses).toBe(true); - const optedIn = await runAttachHandshake({ + const optedOut = await runAttachHandshake({ request: 'attach', attachSimplePort: 9229, type: 'pwa-node', - autoAttachChildProcesses: true + autoAttachChildProcesses: false }); - expect(optedIn.autoAttachChildProcesses).toBe(true); + expect(optedOut.autoAttachChildProcesses).toBe(false); // Issue #655: the same self-containment for resolveSourceMapLocations. expect(defaulted.resolveSourceMapLocations).toEqual(['**', '!**/node_modules/**']); From 0398131446a9c510435aefcc1db00550cf17d401 Mon Sep 17 00:00:00 2001 From: JF Date: Fri, 6 Nov 2026 08:57:51 -0500 Subject: [PATCH 2/8] test(javascript): the attach, cascade and cluster suites of the session tree, and the attach smoke expecting adoption (#1082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `javascript-subprocess-attach.test.ts` (real js-debug over `node --inspect`, runs in CI on both OSes): a fork started after the attach stops on the breakpoint set before it and the root ends when the target does; detach releases the paused fork and keeps both rows; close releases it and removes them; `adapterConfig: { autoAttachChildProcesses: false }` keeps the fork undebugged; and #501's wedge guard on `fork_attach_target.js`, whose forks now become child sessions and keep completing their IPC handshake. Measured here as #1094's review said: under an attach root a child's console output is on its own connection — its `get_output` has `child start` and no `outputOn`. `javascript-subprocess-cascade.test.ts` (no case names the key: the default is what it proves): row 1 — a fork that ends while the parent is paused leaves the parent paused with the fork listed `stopped`; closing one of two forks releases that fork alone (`released: true`, js-debug cannot end a target it adopted) and the survivor's stops keep arriving; a cluster primary's four `cluster.fork()` workers are four child sessions that each stop on the breakpoint in the worker branch. The fixture `attach_parent.js` forks `child.js` after one line on stdin, since js-debug's bootloader reaches only processes started after the attach. The e2e attach smoke's default round now expects child rows and flowing handshakes; its opted-in round became the opt-out round. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_015BDrn6AsHQ4rmTXFHzQkn8 --- .../javascript-subprocess-attach.test.ts | 289 ++++++++++++++++++ .../javascript-subprocess-cascade.test.ts | 206 +++++++++++++ .../javascript-subprocess-tree.test.ts | 3 +- ...mcp-server-smoke-javascript-attach.test.ts | 54 ++-- .../subprocess-tree/attach_parent.js | 27 ++ 5 files changed, 558 insertions(+), 21 deletions(-) create mode 100644 tests/adapters/javascript/integration/javascript-subprocess-attach.test.ts create mode 100644 tests/adapters/javascript/integration/javascript-subprocess-cascade.test.ts create mode 100644 tests/fixtures/javascript/subprocess-tree/attach_parent.js diff --git a/tests/adapters/javascript/integration/javascript-subprocess-attach.test.ts b/tests/adapters/javascript/integration/javascript-subprocess-attach.test.ts new file mode 100644 index 000000000..d467640f4 --- /dev/null +++ b/tests/adapters/javascript/integration/javascript-subprocess-attach.test.ts @@ -0,0 +1,289 @@ +/** + * The session tree over a by-port attach with real js-debug (issue #1082): a + * target started with `node --inspect` that forks a Node child after the + * attach. `autoAttachChildProcesses` is on by default for attach as for + * launch, so no case names the key except the one that opts out: js-debug + * injects its bootloader into the attached process and reports every process + * it forks from then on, and the server adopts each as a child session. + * + * Measured during #1094's review (js-debug v1.140.0): under an attach root the + * children's console output arrives on their own connections — a child's + * `get_output` carries its own lines and no `outputOn` pointer (an attach does + * not own the process pipes the way a launch does). The cascades on an attach + * root release its targets: detach keeps the rows, close removes them, and the + * target runs to its end either way. The last case is #501's wedge guard on + * the fork target the e2e attach smoke uses, here so CI runs it. + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import path from 'node:path'; +import net from 'node:net'; +import { spawn, type ChildProcess } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { startStdioClient, type StdioClientHandle, type ToolAnswer } from '../../../test-utils/helpers/mcp-stdio-client.js'; + +const root = fileURLToPath(new URL('../../../../', import.meta.url)); +const treeDir = path.join(root, 'tests/fixtures/javascript/subprocess-tree'); +const attachParent = path.join(treeDir, 'attach_parent.js'); +const childFixture = path.join(treeDir, 'child.js'); +const forkTarget = path.join(root, 'examples/javascript/fork_attach_target.js'); +/** `total += i; // BREAKPOINT` in child.js. */ +const CHILD_BREAKPOINT_LINE = 14; + +interface Row { id: string; name: string; state: string; parentSessionId?: string } +interface ChildSummary { sessionId: string; name: string; state: string; parentSessionId: string } +interface Stop extends ToolAnswer { + sessionId?: string; + pending?: boolean; + exitCode?: number; + location?: { file: string; line: number }; + lastStop?: { line?: number }; + children?: ChildSummary[]; + childrenClosed?: number; + childrenReleased?: number; + released?: boolean; + warning?: string; +} +interface Closed extends ToolAnswer { childrenClosed?: number; processEnded?: boolean; released?: boolean } +interface Output extends ToolAnswer { entries: Array<{ output: string }>; outputOn?: string; note?: string } +interface Stack extends ToolAnswer { stackFrames: Array<{ id: number; file?: string; line?: number }> } + +interface Target { + proc: ChildProcess; + port: number; + output: () => string; + exited: Promise; + /** Tell attach_parent.js to fork its child (one line on stdin). */ + go: () => void; +} + +let server: StdioClientHandle; +let targets: Target[] = []; +const call = (name: string, args: Record = {}): Promise => server.call(name, args); +const rows = async (): Promise => (await call('list_debug_sessions')).sessions; +const outputOf = (sessionId: string): Promise => call('get_output', { sessionId, limit: 1000 }); +const outputText = async (sessionId: string): Promise => (await outputOf(sessionId)).entries.map((e) => e.output).join(''); +const lineOf = (stop: Stop): number | undefined => stop.location?.line ?? stop.lastStop?.line; +const fileOf = (stop: Stop): string => stop.location?.file ?? ''; + +async function pollUntil(read: () => Promise, accept: (value: T) => boolean, timeoutMs: number, intervalMs = 250): Promise { + const deadline = Date.now() + timeoutMs; + let last = await read(); + while (!accept(last) && Date.now() < deadline) { + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + last = await read(); + } + return last; +} + +async function getFreePort(): Promise { + return new Promise((resolve, reject) => { + const probe = net.createServer(); + probe.listen(0, '127.0.0.1', () => { + const address = probe.address(); + const port = typeof address === 'object' && address ? address.port : 0; + probe.close(() => (port ? resolve(port) : reject(new Error('no free port')))); + }); + probe.on('error', reject); + }); +} + +/** Start a script under `node --inspect` and wait until its inspector listens. */ +async function startTarget(script: string): Promise { + const port = await getFreePort(); + const proc = spawn(process.execPath, [`--inspect=127.0.0.1:${port}`, script], { + cwd: treeDir, + windowsHide: true, + stdio: ['pipe', 'pipe', 'pipe'] + }); + let buffer = ''; + // close also waits for the output pipes, so the exit assertions see the final lines. + const exited = new Promise((resolve) => proc.on('close', (code) => resolve(code))); + const target: Target = { proc, port, output: () => buffer, exited, go: () => proc.stdin!.write('go\n') }; + // Register before readiness so afterEach also cleans up a failed startup. + targets.push(target); + await new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`node --inspect not listening: ${buffer}`)), 30000); + const onData = (chunk: Buffer) => { + buffer += chunk.toString(); + if (buffer.includes('Debugger listening on')) { clearTimeout(timer); resolve(); } + }; + proc.stdout!.on('data', onData); + proc.stderr!.on('data', onData); + proc.on('error', (error) => { clearTimeout(timer); reject(error); }); + proc.on('exit', (code) => { clearTimeout(timer); reject(new Error(`target exited ${code} before listening: ${buffer}`)); }); + }); + return target; +} + +/** The target's exit, with its own pipes' last lines: the proof that nothing killed it. */ +async function targetRanToItsEnd(target: Target): Promise { + let timer: ReturnType | undefined; + try { + const exit = await Promise.race([target.exited, new Promise((resolve) => { + timer = setTimeout(() => resolve('still running'), 20000); + })]); + expect(exit, target.output()).toBe(0); + expect(target.output()).toContain('child done 3'); + expect(target.output()).toContain('parent child exit 0'); + } finally { + clearTimeout(timer); + } +} + +/** Create the root, set the child's breakpoint before the attach, attach without stopping. */ +async function attachRoot(name: string, target: Target, extra: Record = {}): Promise<{ rootId: string; attached: Stop }> { + const rootId = await server.createSession('javascript', name); + const bp = await call('set_breakpoint', { file: childFixture, line: CHILD_BREAKPOINT_LINE }); + expect(bp.success, JSON.stringify(bp)).toBe(true); + const attached = await call('attach_to_process', { host: '127.0.0.1', port: target.port, stopOnEntry: false, verifyTimeout: 20000, ...extra }); + expect(attached.success, JSON.stringify(attached)).toBe(true); + expect(attached.state, JSON.stringify(attached)).toBe('running'); + return { rootId, attached }; +} + +/** Wait on `sessionId` for a stop that must be a paused child of the tree, and hand back its id. */ +async function waitForChildStop(sessionId: string, file: RegExp, line: number): Promise { + const stop = await call('wait_for_stop', { sessionId, timeout: 30000 }); + expect(stop.state, JSON.stringify(stop)).toBe('paused'); + expect(stop.sessionId, JSON.stringify(stop)).toBeDefined(); + expect(stop.sessionId).not.toBe(sessionId); + expect(fileOf(stop), JSON.stringify(stop)).toMatch(file); + expect(lineOf(stop)).toBe(line); + return stop as Stop & { sessionId: string }; +} + +beforeEach(async () => { + server = await startStdioClient({ name: 'js-subprocess-attach', callTimeoutMs: 60_000 }); +}); + +afterEach(async () => { + await server?.close(); + for (const target of targets) { + if (target.proc.exitCode === null) target.proc.kill(); + } + targets = []; +}, 30_000); + +describe('JavaScript session tree over a by-port attach (issue #1082)', () => { + it('a fork started after the attach stops on the breakpoint set before it, keeps its own output, and the root ends when the target does', async () => { + const target = await startTarget(attachParent); + const { rootId, attached } = await attachRoot('js-attach-tree-designed-flow', target); + // Nothing was passed for the key, so nothing can be warned about; the default is the server's. + expect(String(attached.warning ?? '')).not.toContain('autoAttachChildProcesses'); + target.go(); + + let childId: string | undefined; + for (let iteration = 0; iteration < 3; iteration += 1) { + const stop = await waitForChildStop(rootId, /child\.js$/, CHILD_BREAKPOINT_LINE); + childId ??= stop.sessionId; + expect(stop.sessionId).toBe(childId); + expect(stop.children, JSON.stringify(stop)).toEqual([ + { sessionId: childId, name: expect.stringMatching(/child\.js \[\d+\]$/), state: 'paused', parentSessionId: rootId } + ]); + if (iteration === 0) { + const stack = await call('get_stack_trace', { sessionId: childId }); + expect(stack.success, JSON.stringify(stack)).toBe(true); + expect(stack.stackFrames[0]?.line).toBe(CHILD_BREAKPOINT_LINE); + } + const resumed = await call('continue_execution', { sessionId: childId }); + expect(resumed.success, JSON.stringify(resumed)).toBe(true); + } + + // Under an attach root the child's console lines arrive on its own + // connection (measured): its own buffer has them and there is no pointer. + const childOutput = await pollUntil(() => outputOf(childId!), (answer) => answer.entries.some((e) => e.output.includes('child start')), 10000); + expect(childOutput.entries.map((e) => e.output).join(''), JSON.stringify(childOutput)).toContain('child start'); + expect(childOutput.outputOn).toBeUndefined(); + + // An attach root ends with `terminated` and no exit code, like its child. + const ended = await call('wait_for_stop', { sessionId: rootId, timeout: 30000 }); + expect(ended.state, JSON.stringify(ended)).toBe('stopped'); + expect(ended.sessionId).toBe(rootId); + expect(ended.exitCode).toBeUndefined(); + expect(ended.children).toEqual([expect.objectContaining({ sessionId: childId, state: 'stopped', parentSessionId: rootId })]); + await targetRanToItsEnd(target); + }, 120_000); + + it('detaching the attach root releases the paused fork first and keeps both rows stopped; the target runs on to its end', async () => { + const target = await startTarget(attachParent); + const { rootId } = await attachRoot('js-attach-tree-detach', target); + target.go(); + const { sessionId: childId } = await waitForChildStop(rootId, /child\.js$/, CHILD_BREAKPOINT_LINE); + + const detached = await call('detach_from_process', { sessionId: rootId }); + expect(detached.success, JSON.stringify(detached)).toBe(true); + expect(detached.childrenReleased, JSON.stringify(detached)).toBe(1); + expect(detached.childrenClosed).toBeUndefined(); + + // Detach keeps the rows: the child is stopped and says it was released. + const listing = await rows(); + expect(listing.find((row) => row.id === rootId)?.state).toBe('stopped'); + expect(listing.find((row) => row.id === childId)?.state).toBe('stopped'); + const childEnd = await call('wait_for_stop', { sessionId: childId, timeout: 5000 }); + expect(childEnd.state, JSON.stringify(childEnd)).toBe('stopped'); + expect(childEnd.released, JSON.stringify(childEnd)).toBe(true); + + // Nothing was killed: the released fork finished its ticks and the parent exited with its code. + await targetRanToItsEnd(target); + }, 120_000); + + it('closing the attach root releases the paused fork and removes both rows; the target runs on to its end', async () => { + const target = await startTarget(attachParent); + const { rootId } = await attachRoot('js-attach-tree-close', target); + target.go(); + const { sessionId: childId } = await waitForChildStop(rootId, /child\.js$/, CHILD_BREAKPOINT_LINE); + + const closed = await call('close_debug_session', { sessionId: rootId }); + expect(closed.success, JSON.stringify(closed)).toBe(true); + expect(closed.released, JSON.stringify(closed)).toBe(true); + expect(closed.childrenClosed, JSON.stringify(closed)).toBe(1); + expect(closed.processEnded).toBeUndefined(); + server.sessionId = undefined; + + const ids = (await rows()).map((row) => row.id); + expect(ids).not.toContain(rootId); + expect(ids).not.toContain(childId); + await targetRanToItsEnd(target); + }, 120_000); + + it('an attach with autoAttachChildProcesses: false keeps the fork undebugged: no child row, the target runs to its end', async () => { + const target = await startTarget(attachParent); + const { rootId, attached } = await attachRoot('js-attach-tree-opt-out', target, { adapterConfig: { autoAttachChildProcesses: false } }); + // The key is a supported attach key: opting out must not trip the unknown-key warning. + expect(String(attached.warning ?? '')).not.toContain('autoAttachChildProcesses'); + target.go(); + + const ended = await call('wait_for_stop', { sessionId: rootId, timeout: 30000 }); + expect(ended.state, JSON.stringify(ended)).toBe('stopped'); + expect(ended.sessionId).toBe(rootId); + expect(ended.children).toBeUndefined(); + expect((await rows()).some((row) => row.parentSessionId === rootId)).toBe(false); + await targetRanToItsEnd(target); + }, 120_000); + + /** + * #501's wedge guard, in CI: the fork target forks a child every 2 s that + * announces itself over IPC and exits; a child parked by the bootloader and + * never resumed is logged `child-wedged`. With adoption on by default every + * fork is a child session that completes its handshake. + */ + it('forks of a forking attach target become child sessions and keep completing their handshake (issue #501)', async () => { + const target = await startTarget(forkTarget); + const rootId = await server.createSession('javascript', 'js-attach-tree-fork-target'); + const attached = await call('attach_to_process', { host: '127.0.0.1', port: target.port, stopOnEntry: false, verifyTimeout: 20000 }); + expect(attached.success, JSON.stringify(attached)).toBe(true); + + const handshakes = (): number => (target.output().match(/child-handshake /g) ?? []).length; + const baseline = handshakes(); + const listing = await pollUntil(rows, (list) => list.some((row) => row.parentSessionId === rootId), 30000); + expect(listing.some((row) => row.parentSessionId === rootId), JSON.stringify(listing)).toBe(true); + await pollUntil(async () => handshakes(), (count) => count > baseline, 45000, 500); + expect(handshakes(), target.output()).toBeGreaterThan(baseline); + expect(target.output()).not.toContain('child-wedged'); + + const detached = await call('detach_from_process', { sessionId: rootId, terminateProcess: false }); + expect(detached.success, JSON.stringify(detached)).toBe(true); + await new Promise((resolve) => setTimeout(resolve, 500)); + expect(target.proc.exitCode, 'detach must leave the target alive').toBeNull(); + }, 120_000); +}); diff --git a/tests/adapters/javascript/integration/javascript-subprocess-cascade.test.ts b/tests/adapters/javascript/integration/javascript-subprocess-cascade.test.ts new file mode 100644 index 000000000..419f5f0e4 --- /dev/null +++ b/tests/adapters/javascript/integration/javascript-subprocess-cascade.test.ts @@ -0,0 +1,206 @@ +/** + * The session tree's lifecycle rows with real js-debug (issue #1082, + * docs/architecture/session-tree.md "Lifecycle: the cascade contract"): a + * fork that ends while its parent is paused, two siblings of which one is + * closed (released, for js-debug), and a cluster primary's four workers. + * `autoAttachChildProcesses` is on by default, so no case names the key: the + * launches here are the proof of the default. + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { startStdioClient, type StdioClientHandle, type ToolAnswer } from '../../../test-utils/helpers/mcp-stdio-client.js'; + +const root = fileURLToPath(new URL('../../../../', import.meta.url)); +const treeDir = path.join(root, 'tests/fixtures/javascript/subprocess-tree'); +const parentFixture = path.join(treeDir, 'parent.js'); +const parentTwoFixture = path.join(treeDir, 'parent_two.js'); +const clusterFixture = path.join(treeDir, 'cluster_parent.js'); +const childFixture = path.join(treeDir, 'child.js'); +/** The `// BREAKPOINT` lines of the fixtures. */ +const PARENT_BREAKPOINT_LINE = 20; +const CHILD_BREAKPOINT_LINE = 14; +const CLUSTER_BREAKPOINT_LINE = 29; +const WORKERS = 4; + +interface Row { id: string; name: string; state: string; parentSessionId?: string } +interface ChildSummary { sessionId: string; name: string; state: string; parentSessionId: string } +interface Stop extends ToolAnswer { + sessionId?: string; + pending?: boolean; + exitCode?: number; + location?: { file: string; line: number }; + lastStop?: { line?: number }; + children?: ChildSummary[]; + childrenClosed?: number; + childrenReleased?: number; +} +interface Closed extends ToolAnswer { childrenClosed?: number; processEnded?: boolean; released?: boolean } +interface Output extends ToolAnswer { entries: Array<{ output: string }> } +interface Stack extends ToolAnswer { stackFrames: Array<{ id: number; file?: string; line?: number }> } + +let server: StdioClientHandle; +const call = (name: string, args: Record = {}): Promise => server.call(name, args); +const rows = async (): Promise => (await call('list_debug_sessions')).sessions; +const outputText = async (sessionId: string): Promise => + (await call('get_output', { sessionId, limit: 1000 })).entries.map((e) => e.output).join(''); +const lineOf = (stop: Stop): number | undefined => stop.location?.line ?? stop.lastStop?.line; +const fileOf = (stop: Stop): string => stop.location?.file ?? ''; + +async function pollUntil(read: () => Promise, accept: (value: T) => boolean, timeoutMs: number, intervalMs = 250): Promise { + const deadline = Date.now() + timeoutMs; + let last = await read(); + while (!accept(last) && Date.now() < deadline) { + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + last = await read(); + } + return last; +} + +/** Launch without naming autoAttachChildProcesses: the default is what these cases prove. */ +async function launch(scriptPath: string): Promise { + const started = await call('start_debugging', { scriptPath, breakOnExceptions: 'none', dapLaunchArgs: { stopOnEntry: false } }); + expect(started.success, JSON.stringify(started)).toBe(true); + return started; +} + +async function setBreakpoint(file: string, line: number): Promise { + const bp = await call('set_breakpoint', { file, line }); + expect(bp.success, JSON.stringify(bp)).toBe(true); +} + +/** Wait on `sessionId` for a stop that must be a paused child of the tree, and hand back its id. */ +async function waitForChildStop(sessionId: string, file: RegExp, line: number): Promise { + const stop = await call('wait_for_stop', { sessionId, timeout: 30000 }); + expect(stop.state, JSON.stringify(stop)).toBe('paused'); + expect(stop.sessionId, JSON.stringify(stop)).toBeDefined(); + expect(stop.sessionId).not.toBe(sessionId); + expect(fileOf(stop), JSON.stringify(stop)).toMatch(file); + expect(lineOf(stop)).toBe(line); + return stop as Stop & { sessionId: string }; +} + +beforeEach(async () => { + server = await startStdioClient({ name: 'js-subprocess-cascade', callTimeoutMs: 60_000 }); +}); + +afterEach(async () => { + await server?.close(); +}, 30_000); + +describe('JavaScript session-tree lifecycle rows (issue #1082)', () => { + it('a fork that ends while the parent is paused leaves the parent paused, the fork ended in children (row 1)', async () => { + const rootId = await server.createSession('javascript', 'js-cascade-row-1'); + await setBreakpoint(parentFixture, PARENT_BREAKPOINT_LINE); + const started = await launch(parentFixture); + const paused = started.state === 'paused' ? started : await call('wait_for_stop', { sessionId: rootId, timeout: 30000 }); + expect(paused.state, JSON.stringify(paused)).toBe('paused'); + expect(fileOf(paused)).toMatch(/parent\.js$/); + expect(lineOf(paused)).toBe(PARENT_BREAKPOINT_LINE); + + // No breakpoint of its own: the adopted fork runs to its end while the parent stays paused. + const listing = await pollUntil(rows, (list) => list.some((row) => row.parentSessionId === rootId && row.state === 'stopped'), 30000); + const childRow = listing.find((row) => row.parentSessionId === rootId); + expect(childRow, JSON.stringify(listing)).toBeDefined(); + expect(childRow!.state).toBe('stopped'); + expect(listing.find((row) => row.id === rootId)?.state).toBe('paused'); + + // Rule 1: the paused parent is the answer, with the ended fork listed. + const stop = await call('wait_for_stop', { sessionId: rootId, timeout: 1000 }); + expect(stop.state, JSON.stringify(stop)).toBe('paused'); + expect(stop.sessionId).toBe(rootId); + expect(stop.children).toEqual([ + { sessionId: childRow!.id, name: expect.stringMatching(/child\.js \[\d+\]$/), state: 'stopped', parentSessionId: rootId } + ]); + + // After the parent resumes, it sees the exit code and ends normally. + expect((await call('continue_execution', { sessionId: rootId })).success).toBe(true); + const ended = await call('wait_for_stop', { sessionId: rootId, timeout: 30000 }); + expect(ended.state, JSON.stringify(ended)).toBe('stopped'); + expect(ended.sessionId).toBe(rootId); + expect(ended.exitCode).toBe(0); + const output = await pollUntil(() => outputText(rootId), (text) => text.includes('parent child exit 0'), 5000); + expect(output).toContain('child done 3'); + }, 120_000); + + it('closing one of two forks releases that fork alone: its sibling and the parent go on', async () => { + const rootId = await server.createSession('javascript', 'js-cascade-siblings'); + await setBreakpoint(childFixture, CHILD_BREAKPOINT_LINE); + await launch(parentTwoFixture); + + const first = await waitForChildStop(rootId, /child\.js$/, CHILD_BREAKPOINT_LINE); + expect(first.sessionId).toBeDefined(); + const listing = await pollUntil( + rows, + (list) => list.filter((row) => row.parentSessionId === rootId && row.state === 'paused').length === 2, + 30000 + ); + const children = listing.filter((row) => row.parentSessionId === rootId); + expect(children, JSON.stringify(listing)).toHaveLength(2); + const [closedChild, survivor] = children; + + // js-debug cannot end a target it adopted: the close releases the fork, which runs on undebugged. + const closed = await call('close_debug_session', { sessionId: closedChild.id }); + expect(closed.success, JSON.stringify(closed)).toBe(true); + expect(closed.released, JSON.stringify(closed)).toBe(true); + expect(closed.processEnded).toBeUndefined(); + expect(closed.childrenClosed).toBeUndefined(); + + const after = await rows(); + expect(after.map((row) => row.id)).not.toContain(closedChild.id); + expect(after.find((row) => row.id === survivor.id)?.state).toBe('paused'); + expect(after.find((row) => row.id === rootId)?.state).toBe('running'); + const stack = await call('get_stack_trace', { sessionId: survivor.id }); + expect(stack.success, JSON.stringify(stack)).toBe(true); + expect(stack.stackFrames[0]?.line).toBe(CHILD_BREAKPOINT_LINE); + + // The survivor runs its remaining two ticks under the debugger; the parent then ends. + for (let iteration = 1; iteration < 3; iteration += 1) { + expect((await call('continue_execution', { sessionId: survivor.id })).success).toBe(true); + const stop = await call('wait_for_stop', { sessionId: rootId, timeout: 30000 }); + expect(stop.state, JSON.stringify(stop)).toBe('paused'); + expect(stop.sessionId).toBe(survivor.id); + expect(stop.children).toEqual([expect.objectContaining({ sessionId: survivor.id, state: 'paused' })]); + } + expect((await call('continue_execution', { sessionId: survivor.id })).success).toBe(true); + const ended = await call('wait_for_stop', { sessionId: rootId, timeout: 30000 }); + expect(ended.state, JSON.stringify(ended)).toBe('stopped'); + expect(ended.sessionId).toBe(rootId); + expect(ended.exitCode).toBe(0); + expect(ended.children).toEqual([expect.objectContaining({ sessionId: survivor.id, state: 'stopped' })]); + const output = await pollUntil(() => outputText(rootId), (text) => text.includes('parent done 0'), 5000); + expect(output).toContain('parent done 0'); + }, 120_000); + + it("a cluster primary's workers each become a child session and stop on the breakpoint in the worker branch", async () => { + const rootId = await server.createSession('javascript', 'js-cascade-cluster'); + await setBreakpoint(clusterFixture, CLUSTER_BREAKPOINT_LINE); + await launch(clusterFixture); + + // One tick per worker: four stops, each a different child, in the scheduler's order. + const workers = new Set(); + for (let stops = 0; stops < WORKERS; stops += 1) { + const stop = await waitForChildStop(rootId, /cluster_parent\.js$/, CLUSTER_BREAKPOINT_LINE); + workers.add(stop.sessionId); + expect(stop.children).toEqual(expect.arrayContaining([ + { sessionId: stop.sessionId, name: expect.stringMatching(/cluster_parent\.js \[\d+\]$/), state: 'paused', parentSessionId: rootId } + ])); + expect((await call('continue_execution', { sessionId: stop.sessionId })).success).toBe(true); + } + expect(workers.size).toBe(WORKERS); + + const ended = await call('wait_for_stop', { sessionId: rootId, timeout: 30000 }); + expect(ended.state, JSON.stringify(ended)).toBe('stopped'); + expect(ended.sessionId).toBe(rootId); + expect(ended.exitCode).toBe(0); + expect(ended.children).toHaveLength(WORKERS); + for (const child of ended.children ?? []) { + expect(child).toMatchObject({ state: 'stopped', parentSessionId: rootId }); + expect(workers.has(child.sessionId)).toBe(true); + } + const output = await pollUntil(() => outputText(rootId), (text) => text.includes('primary done'), 5000); + for (let id = 1; id <= WORKERS; id += 1) { + expect(output).toContain(`worker ${id} done`); + } + }, 120_000); +}); diff --git a/tests/adapters/javascript/integration/javascript-subprocess-tree.test.ts b/tests/adapters/javascript/integration/javascript-subprocess-tree.test.ts index b9e95546c..a33403808 100644 --- a/tests/adapters/javascript/integration/javascript-subprocess-tree.test.ts +++ b/tests/adapters/javascript/integration/javascript-subprocess-tree.test.ts @@ -8,7 +8,8 @@ * moment it is held, release is a throwaway attach, the tree's output lands * on the launch session only, and a child's close releases it. * - * Every case names the key explicitly: the default flips in M2's second PR. + * Every case names the key explicitly for the record; it is on by default + * since M2's second PR, which the attach and cascade suites rely on. */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import path from 'node:path'; diff --git a/tests/e2e/mcp-server-smoke-javascript-attach.test.ts b/tests/e2e/mcp-server-smoke-javascript-attach.test.ts index 3097e47ad..9051ba6a1 100644 --- a/tests/e2e/mcp-server-smoke-javascript-attach.test.ts +++ b/tests/e2e/mcp-server-smoke-javascript-attach.test.ts @@ -17,11 +17,12 @@ * js-debug child adoption path must not force an entry stop), and detach * must leave it alive. * - * Tests 5/6 — forking targets (issue #501): attaching must not wedge child - * processes the target fork()s. By default the auto-attach bootloader is off, - * so forks run untouched; with autoAttachChildProcesses:true each fork parks - * under waitForDebugger and js-debug requests adoption — the single-child - * limitation means it must be released to run undebugged, not dropped. + * Tests 5/6 — forking targets (issues #501, #1082): attaching must not wedge + * child processes the target fork()s. By default the auto-attach bootloader + * is on and every fork js-debug reports becomes a child debug session (the + * session tree) that runs on under the debugger; with + * autoAttachChildProcesses:false the bootloader stays out and forks run + * untouched. Either way the fork's IPC handshake must complete. */ import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'; @@ -706,18 +707,29 @@ describe('MCP Server JavaScript Attach-Mode Smoke Tests', () => { return false; } - it('forked children keep completing their IPC handshake while attached (issue #501)', async () => { + it('forked children become child sessions and keep completing their IPC handshake while attached (issues #501, #1082)', async () => { const target = await spawnTarget(FORK_TARGET_SCRIPT); targetProcess = target.proc; const attachResponse = await createSessionAndAttach(target.port, { stopOnEntry: false }); expect(attachResponse.success, `attach failed: ${JSON.stringify(attachResponse)}`).toBe(true); - // Forks started AFTER the attach must complete their fork→IPC→ack round - // trip. Pre-fix, js-debug's auto-attach bootloader (attach default: on) - // parked every fork in waitForDebugger and only one target could ever be - // adopted — each fork wedged until the fixture's 5s diagnostic timeout. + // Forks started AFTER the attach are reported by js-debug's bootloader + // (on by default since the session tree, issue #1082) and adopted as + // child sessions of this one; each still completes its fork→IPC→ack + // round trip under the debugger. Pre-#501 the single-child adoption + // dropped every report and each fork wedged until the fixture's 5s + // diagnostic timeout. const baseline = countHandshakes(target); + const childRowsDeadline = Date.now() + 30000; + let childRows: Array<{ parentSessionId?: string }> = []; + while (Date.now() < childRowsDeadline && childRows.length === 0) { + const listing = await callToolSafely(mcpClient!, 'list_debug_sessions', {}); + childRows = ((listing.sessions as Array<{ parentSessionId?: string }> | undefined) ?? []) + .filter(row => row.parentSessionId === sessionId); + if (childRows.length === 0) await new Promise(r => setTimeout(r, 500)); + } + expect(childRows.length, 'no fork became a child session within 30s of attach (issue #1082)').toBeGreaterThan(0); const progressed = await waitForHandshakeProgress(target, baseline, 45000); expect( progressed, @@ -738,17 +750,17 @@ describe('MCP Server JavaScript Attach-Mode Smoke Tests', () => { expect(targetProcess!.exitCode, 'detach must leave the target process alive').toBeNull(); }, 120000); - it('releases forks it cannot adopt when autoAttachChildProcesses is opted in (issue #501)', async () => { + it('leaves forks untouched when autoAttachChildProcesses is opted out (issues #501, #1082)', async () => { const target = await spawnTarget(FORK_TARGET_SCRIPT); targetProcess = target.proc; const attachResponse = await createSessionAndAttach(target.port, { stopOnEntry: false, - adapterConfig: { autoAttachChildProcesses: true } + adapterConfig: { autoAttachChildProcesses: false } }); expect(attachResponse.success, `attach failed: ${JSON.stringify(attachResponse)}`).toBe(true); - // The key is declared in supportedAttachKeys — opting in must not trip + // The key is declared in supportedAttachKeys — opting out must not trip // the unrecognized-adapterConfig-key warning (issue #466 mechanism) const warning = String(attachResponse.warning ?? ''); expect( @@ -756,20 +768,22 @@ describe('MCP Server JavaScript Attach-Mode Smoke Tests', () => { `autoAttachChildProcesses is a supported attach key but the attach warned about it: ${warning}` ).not.toContain('autoAttachChildProcesses'); - // With the bootloader ON, every fork parks under waitForDebugger and - // js-debug requests adoption for it. The single-child limitation means it - // cannot be adopted — it must be RELEASED to run undebugged (attach with - // its __pendingTargetId, then detach), so handshakes keep completing. + // With the bootloader OFF no fork is reported: they run untouched, no + // child session appears, and handshakes keep completing. const baseline = countHandshakes(target); const progressed = await waitForHandshakeProgress(target, baseline, 45000); expect( progressed, `no fork completed its IPC handshake within 45s of attach with ` + - `autoAttachChildProcesses:true — unadoptable forks are not being released ` + - `(issue #501); target stdout:\n${target.stdout()}` + `autoAttachChildProcesses:false (issue #501); target stdout:\n${target.stdout()}` ).toBe(true); + expect(target.stdout(), 'a fork wedged with the bootloader opted out (issue #501)').not.toContain('child-wedged'); + const listing = await callToolSafely(mcpClient!, 'list_debug_sessions', {}); + const childRows = ((listing.sessions as Array<{ parentSessionId?: string }> | undefined) ?? []) + .filter(row => row.parentSessionId === sessionId); + expect(childRows, 'an opted-out attach must adopt no fork').toHaveLength(0); - // Debugging the parent must remain intact after releases happened + // Debugging the parent must remain intact with forks running untouched const threadsResult = await callToolSafely(mcpClient!, 'list_threads', { sessionId: sessionId! }); const threads = (threadsResult.threads as unknown[] | undefined) ?? []; expect( diff --git a/tests/fixtures/javascript/subprocess-tree/attach_parent.js b/tests/fixtures/javascript/subprocess-tree/attach_parent.js new file mode 100644 index 000000000..672f7ef37 --- /dev/null +++ b/tests/fixtures/javascript/subprocess-tree/attach_parent.js @@ -0,0 +1,27 @@ +/** + * The attach-mode parent of the session tree's attach fixture (issue #1082). + * + * Run as `node --inspect=127.0.0.1: attach_parent.js`: the inspector is + * up before the script runs (`Debugger listening on` on stderr), so a client + * can attach at once. js-debug injects its auto-attach bootloader into this + * process at attach time, so only a child started AFTER the attach is reported; + * this script therefore waits for one line on stdin before it forks child.js + * the way parent.js does, waits for the child and exits with its code, so a + * tree that was released or closed can be seen running to its end on the + * harness's own pipes. + */ +import { fork } from 'node:child_process'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const here = dirname(fileURLToPath(import.meta.url)); +console.log('ATTACH_PARENT_READY'); +process.stdin.setEncoding('utf8'); +process.stdin.once('data', () => { + console.log('parent go'); + const child = fork(join(here, 'child.js'), [], { stdio: 'inherit', windowsHide: true }); + child.on('exit', (code) => { + console.log(`parent child exit ${code}`); + process.exit(code ?? 1); + }); +}); From 70e58d0c9bb3fab44e59c43bf2b53acc49db18bc Mon Sep 17 00:00:00 2001 From: JF Date: Fri, 6 Nov 2026 08:57:52 -0500 Subject: [PATCH 3/8] =?UTF-8?q?docs(session-tree):=20M2=20complete=20?= =?UTF-8?q?=E2=80=94=20JavaScript=20child=20processes=20debugged=20by=20de?= =?UTF-8?q?fault,=20the=20opt-outs,=20outputOn,=20a=20js=20child's=20close?= =?UTF-8?q?=20as=20a=20release=20(#1082)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The design doc's status says M2 shipped in #1094 and this PR, and its M2 paragraph records what PR 2 measured (an attach root's children keep their own output; cluster workers are reported like any fork). The JavaScript README's child-process section states the default and the three opt-outs. The tool reference names JavaScript beside Python wherever the tree is described (`parentSessionId` with js-debug's `