From 1601f3f2f21f3c43a250a942ea305a7f649395c9 Mon Sep 17 00:00:00 2001 From: JF Date: Sun, 30 Aug 2026 21:04:07 -0400 Subject: [PATCH] fix: close refactor integration gaps --- src/dap-core/types.ts | 2 +- src/proxy/dap-proxy-interfaces.ts | 7 +++ src/proxy/dap-proxy-worker.ts | 5 +- src/proxy/proxy-manager.ts | 17 +++++++ src/server/handlers/debuggee-tools.ts | 8 ++++ src/server/handlers/session-tools.ts | 1 + src/session/session-manager-core.ts | 6 ++- src/session/session-manager-data.ts | 46 ++++++++++++++----- .../bp-assert.va-explicit.container-true.json | 12 ++--- ...bp-assert.va-explicit.container-unset.json | 12 ++--- .../bp-assert.va-open.container-true.json | 12 ++--- .../bp-assert.va-open.container-unset.json | 12 ++--- .../bp-assert.va-unset.container-true.json | 12 ++--- .../bp-assert.va-unset.container-unset.json | 12 ++--- ...bp-content.va-explicit.container-true.json | 12 ++--- ...p-content.va-explicit.container-unset.json | 12 ++--- .../bp-content.va-open.container-true.json | 12 ++--- .../bp-content.va-open.container-unset.json | 12 ++--- .../bp-content.va-unset.container-true.json | 12 ++--- .../bp-content.va-unset.container-unset.json | 12 ++--- .../bp-line.va-explicit.container-true.json | 12 ++--- .../bp-line.va-explicit.container-unset.json | 12 ++--- .../bp-line.va-open.container-true.json | 12 ++--- .../bp-line.va-open.container-unset.json | 12 ++--- .../bp-line.va-unset.container-true.json | 12 ++--- .../bp-line.va-unset.container-unset.json | 12 ++--- .../bp-unset.va-explicit.container-true.json | 12 ++--- .../bp-unset.va-explicit.container-unset.json | 12 ++--- .../bp-unset.va-open.container-true.json | 12 ++--- .../bp-unset.va-open.container-unset.json | 12 ++--- .../bp-unset.va-unset.container-true.json | 12 ++--- .../bp-unset.va-unset.container-unset.json | 12 ++--- .../server/handlers/inspection-tools.test.ts | 16 +++---- .../server/server-redefine-and-attach.test.ts | 24 ++++++++++ .../server-variable-access-gating.test.ts | 6 +-- .../session-manager-integration.test.ts | 5 +- .../session/session-manager-workflow.test.ts | 34 ++++++++++++++ tests/e2e/comprehensive-mcp-tools.test.ts | 15 +++++- tests/e2e/language-matrix-utils.ts | 19 +++++++- .../mcp-server-breakpoint-management.test.ts | 2 +- tests/e2e/mcp-server-logpoints.test.ts | 2 +- tests/e2e/mcp-server-smoke-restart.test.ts | 2 +- .../e2e/mcp-server-smoke-ruby-attach.test.ts | 7 ++- tests/e2e/mcp-server-smoke-rust.test.ts | 13 ++++-- .../proxy-manager-message-handling.test.ts | 21 +++++++++ 45 files changed, 364 insertions(+), 182 deletions(-) diff --git a/src/dap-core/types.ts b/src/dap-core/types.ts index 95cb878be..715d0a4d1 100644 --- a/src/dap-core/types.ts +++ b/src/dap-core/types.ts @@ -51,7 +51,7 @@ export type ProxyStatusMessage = | { type: 'status'; sessionId: string; status: 'init_received'; data?: unknown } | { type: 'status'; sessionId: string; status: 'dry_run_complete'; command: string; script: string; data?: unknown } | { type: 'status'; sessionId: string; status: 'adapter_connected'; data?: unknown } - | { type: 'status'; sessionId: string; status: 'adapter_configured_and_launched'; data?: unknown } + | { type: 'status'; sessionId: string; status: 'adapter_configured_and_launched'; lastStop?: DebugProtocol.StoppedEvent['body']; data?: unknown } | { type: 'status'; sessionId: string; status: 'adapter_capabilities'; capabilities: DebugProtocol.Capabilities; data?: unknown } | { type: 'status'; sessionId: string; status: 'function_breakpoints_synced'; functionBreakpoints: Array<{ name: string; verified: boolean; id?: number; line?: number; source?: string }>; data?: unknown } | { type: 'status'; sessionId: string; status: 'breakpoints_synced'; breakpoints: Array<{ id?: string; file: string; line: number; verified: boolean; adapterId?: number; boundLine?: number; message?: string }>; data?: unknown } diff --git a/src/proxy/dap-proxy-interfaces.ts b/src/proxy/dap-proxy-interfaces.ts index e637bf15e..848e41ec7 100644 --- a/src/proxy/dap-proxy-interfaces.ts +++ b/src/proxy/dap-proxy-interfaces.ts @@ -96,6 +96,13 @@ export interface StatusMessage extends ProxyMessage { * this is the only path that ever stamps verified/adapterId in the store. */ breakpoints?: BreakpointSyncResult[]; + /** + * A stop observed while the worker was still completing its initialization + * handshake. The parent normally receives the corresponding dapEvent, but + * this snapshot closes the initialization/event ordering gap for adapters + * such as rdbg that stop synchronously with configurationDone. + */ + lastStop?: DebugProtocol.StoppedEvent['body']; } /** One entry of StatusMessage.breakpoints (issue #439). */ diff --git a/src/proxy/dap-proxy-worker.ts b/src/proxy/dap-proxy-worker.ts index 91683b505..f604d476d 100644 --- a/src/proxy/dap-proxy-worker.ts +++ b/src/proxy/dap-proxy-worker.ts @@ -1046,7 +1046,10 @@ export class DapProxyWorker { // Update state and notify parent this.state = ProxyState.CONNECTED; - this.sendStatus('adapter_configured_and_launched'); + this.sendStatus( + 'adapter_configured_and_launched', + this.lastStop ? { lastStop: this.lastStop } : {} + ); } catch (error) { const message = error instanceof Error ? error.message : String(error); this.logger!.error('[Worker] Error in initialized handler:', error); diff --git a/src/proxy/proxy-manager.ts b/src/proxy/proxy-manager.ts index d3432501e..cee9a5e10 100644 --- a/src/proxy/proxy-manager.ts +++ b/src/proxy/proxy-manager.ts @@ -230,6 +230,8 @@ export class ProxyManager extends EventEmitter implements IProxyManager { private dryRunScriptPath?: string; private adapterConfigured = false; private dapState: DAPSessionState | null = null; + /** Whether this parent has already consumed a stopped event for this run. */ + private initializationStopSeen = false; private stderrBuffer: string[] = []; private lastExitDetails: | { @@ -302,6 +304,7 @@ export class ProxyManager extends EventEmitter implements IProxyManager { this.dryRunCommandSnapshot = undefined; this.dryRunScriptPath = config.scriptPath; this.lastExitDetails = undefined; + this.initializationStopSeen = false; if (config.adapterCommand?.command) { const parts = [config.adapterCommand.command, ...(config.adapterCommand.args ?? [])] .filter((part) => typeof part === 'string' && part.length > 0); @@ -1295,6 +1298,7 @@ export class ProxyManager extends EventEmitter implements IProxyManager { if (typeof threadIdMaybe === 'number') { this.currentThreadId = threadIdMaybe; } + this.initializationStopSeen = true; // Do not fabricate a threadId; emit undefined if adapter omitted it this.emit('stopped', threadIdMaybe, reason, stoppedBody as DebugProtocol.StoppedEvent['body']); break; @@ -1357,6 +1361,19 @@ export class ProxyManager extends EventEmitter implements IProxyManager { case 'adapter_configured_and_launched': this.logger.info(`[ProxyManager] Adapter configured and launched`); + // Some adapters (notably rdbg) emit `stopped` synchronously with + // configurationDone. If that early dapEvent did not reach the parent + // before readiness, replay the worker's initialization snapshot. This + // remains a real adapter event — SessionManager records lastStop before + // entering PAUSED — and is deduped when the normal dapEvent arrived. + if (message.lastStop && !this.initializationStopSeen) { + this.handleDapEvent({ + type: 'dapEvent', + sessionId: message.sessionId, + event: 'stopped', + body: message.lastStop + }); + } this.adapterConfigured = true; this.emit('adapter-configured'); if (!this.isInitialized) { diff --git a/src/server/handlers/debuggee-tools.ts b/src/server/handlers/debuggee-tools.ts index 0375fe604..d435c76e6 100644 --- a/src/server/handlers/debuggee-tools.ts +++ b/src/server/handlers/debuggee-tools.ts @@ -115,6 +115,14 @@ export const attachToProcessTool: ToolHandler = async (ctx, args) => { 'Attach operation completed' }; + // `pending` is part of the public tool-result contract, not merely an + // implementation detail in the structured data bag. A post-attach pause + // that has not observed its stopped event yet must therefore be visible + // to clients at the top level (issue #598). + if (attachResult.data?.pending) { + responsePayload.pending = true; + } + if (attachResult.data) { responsePayload.data = attachResult.data; // Surface the dropped-adapterConfig-keys warning (issue diff --git a/src/server/handlers/session-tools.ts b/src/server/handlers/session-tools.ts index feed89278..802c2ce7c 100644 --- a/src/server/handlers/session-tools.ts +++ b/src/server/handlers/session-tools.ts @@ -121,6 +121,7 @@ export const createDebugSessionTool: ToolHandler = async (ctx, args) => { message: attachResult.success ? `Created and attached ${sessionInfo.language} debug session: ${sessionInfo.name}` : `Created session but attach failed: ${attachResult.error || 'Unknown error'}`, + ...(attachData?.pending ? { pending: true } : {}), ...(attachData ? { data: attachData } : {}), ...(warning ? { warning } : {}) }); diff --git a/src/session/session-manager-core.ts b/src/session/session-manager-core.ts index 0fa230f26..f21cd27ba 100644 --- a/src/session/session-manager-core.ts +++ b/src/session/session-manager-core.ts @@ -903,7 +903,11 @@ export abstract class SessionManagerCore extends EventEmitter { const handleAdapterConfigured = () => { this.logger.debug(`[SessionManager] 'adapter-configured' event handler called for session ${sessionId}`); this.logger.info(`[ProxyManager ${sessionId}] Adapter configured`); - if (!effectiveLaunchArgs.stopOnEntry) { + // Readiness is not a resume signal. Some adapters (notably rdbg) emit a + // real stopped event synchronously with configurationDone, before this + // status. Preserve that observed pause; only project RUNNING when no + // stop has already established the stronger state. + if (!effectiveLaunchArgs.stopOnEntry && session.state !== SessionState.PAUSED) { this._updateSessionState(session, SessionState.RUNNING); } }; diff --git a/src/session/session-manager-data.ts b/src/session/session-manager-data.ts index 3a30be979..ff1dee4e1 100644 --- a/src/session/session-manager-data.ts +++ b/src/session/session-manager-data.ts @@ -322,19 +322,21 @@ export abstract class SessionManagerData extends SessionManagerCore { } } - // Step 3: Collect variables for all scopes — budget-aware (issue - // #356): a JS attach's internal pause frame can expose scopes walking - // into process/global, so stop issuing DAP requests once the per-call - // variable budget is spent. Frames iterate top-first, so the frames - // that matter (whose Local scope extractLocalVariables reads) are - // fetched before the budget can run out. The `names` filter is pushed - // down so an explicit request is never starved by the budget. + // Step 3: Collect variables frame-by-frame — budget-aware (issue + // #356) and anchor-aware (issues #468/#594). Stop as soon as a frame + // yields usable locals; walking every async/runtime frame after the + // answer is already known is both wasteful and unsafe (an unrelated + // lower-frame formatter can hang the entire inspection). The `names` + // filter remains authoritative for the top frame, so explicit-name + // requests never walk down to a caller. const variablesMap: Record = {}; const truncationByScope = new Map(); let fetchedCount = 0; let scopeFetchesSkipped = 0; const fetchBudget = maxVariablesPerCall(); - for (const frame of stackFrames) { + const policy = this.selectPolicy(session.language); + for (let frameIndex = 0; frameIndex < stackFrames.length; frameIndex++) { + const frame = stackFrames[frameIndex]; const scopes = scopesMap[frame.id]; if (!scopes) continue; for (const scope of scopes) { @@ -350,6 +352,29 @@ export abstract class SessionManagerData extends SessionManagerCore { variablesMap[scope.variablesReference] = detailed.variables; } } + + const framesAtAnchor = stackFrames.slice(frameIndex); + const extraction = policy.extractLocalVariables + ? policy.extractLocalVariables( + framesAtAnchor, + scopesMap, + variablesMap, + includeSpecial + ) + : undefined; + const fallbackHasVariables = !policy.extractLocalVariables && scopes.some( + scope => + !scope.name.toLowerCase().includes('global') && + (variablesMap[scope.variablesReference]?.length ?? 0) > 0 + ); + if ( + names !== undefined || + (extraction?.variables.length ?? 0) > 0 || + fallbackHasVariables || + fetchedCount >= fetchBudget + ) { + break; + } } if (scopeFetchesSkipped > 0) { this.logger.info( @@ -357,10 +382,7 @@ export abstract class SessionManagerData extends SessionManagerCore { ); } - // Step 4: Get the appropriate adapter policy - const policy = this.selectPolicy(session.language); - - // Step 5: Extract local variables using the adapter policy. Policies + // Step 4: Extract local variables using the adapter policy. Policies // anchor to the first frame of the list they receive, so extraction is // parameterized by anchor: slicing the frame list re-anchors it. const extractAt = (frames: StackFrame[]): { 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 37e774a85..ebe43655a 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 @@ -252,7 +252,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -507,7 +507,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -534,7 +534,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -547,7 +547,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -577,7 +577,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -589,7 +589,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 6376d89a1..1175ff324 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 @@ -252,7 +252,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -507,7 +507,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -534,7 +534,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -547,7 +547,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -577,7 +577,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -589,7 +589,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 4b0b80fae..deca9b21a 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 @@ -252,7 +252,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -506,7 +506,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -532,7 +532,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -545,7 +545,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -575,7 +575,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -587,7 +587,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 165901f9b..70959ce33 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 @@ -252,7 +252,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -506,7 +506,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -532,7 +532,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -545,7 +545,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -575,7 +575,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -587,7 +587,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 4b0b80fae..deca9b21a 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 @@ -252,7 +252,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -506,7 +506,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -532,7 +532,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -545,7 +545,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -575,7 +575,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -587,7 +587,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 165901f9b..70959ce33 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 @@ -252,7 +252,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -506,7 +506,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -532,7 +532,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -545,7 +545,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -575,7 +575,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -587,7 +587,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 1140a51c8..994db3b82 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -517,7 +517,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -544,7 +544,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -557,7 +557,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -587,7 +587,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -599,7 +599,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 505e0a906..b9b7cc648 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -517,7 +517,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -544,7 +544,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -557,7 +557,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -587,7 +587,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -599,7 +599,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 ff63ce0ab..e2394ed92 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -516,7 +516,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -542,7 +542,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -555,7 +555,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -585,7 +585,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -597,7 +597,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 8139ddd89..2345546e6 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -516,7 +516,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -542,7 +542,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -555,7 +555,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -585,7 +585,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -597,7 +597,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 ff63ce0ab..e2394ed92 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -516,7 +516,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -542,7 +542,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -555,7 +555,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -585,7 +585,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -597,7 +597,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 8139ddd89..2345546e6 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -516,7 +516,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -542,7 +542,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -555,7 +555,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -585,7 +585,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -597,7 +597,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 4cdbacce1..15e308aff 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 @@ -248,7 +248,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -503,7 +503,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -530,7 +530,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -543,7 +543,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -573,7 +573,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -585,7 +585,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 99b62258e..bcf18f2cd 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 @@ -248,7 +248,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -503,7 +503,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -530,7 +530,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -543,7 +543,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -573,7 +573,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -585,7 +585,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 65f9ea8b7..1e466d691 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 @@ -248,7 +248,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -502,7 +502,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -528,7 +528,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -541,7 +541,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -571,7 +571,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -583,7 +583,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 478df12af..c77e2b420 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 @@ -248,7 +248,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -502,7 +502,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -528,7 +528,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -541,7 +541,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -571,7 +571,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -583,7 +583,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 65f9ea8b7..1e466d691 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 @@ -248,7 +248,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -502,7 +502,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -528,7 +528,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -541,7 +541,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -571,7 +571,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -583,7 +583,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 478df12af..c77e2b420 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 @@ -248,7 +248,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -502,7 +502,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -528,7 +528,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -541,7 +541,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -571,7 +571,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -583,7 +583,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 1140a51c8..994db3b82 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -517,7 +517,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -544,7 +544,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -557,7 +557,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -587,7 +587,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -599,7 +599,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 505e0a906..b9b7cc648 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -517,7 +517,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -544,7 +544,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -557,7 +557,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -587,7 +587,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -599,7 +599,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 ff63ce0ab..e2394ed92 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -516,7 +516,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -542,7 +542,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -555,7 +555,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -585,7 +585,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -597,7 +597,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 8139ddd89..2345546e6 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -516,7 +516,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -542,7 +542,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -555,7 +555,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -585,7 +585,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -597,7 +597,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 ff63ce0ab..e2394ed92 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -516,7 +516,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -542,7 +542,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -555,7 +555,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -585,7 +585,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -597,7 +597,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", 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 8139ddd89..2345546e6 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 @@ -262,7 +262,7 @@ }, { "name": "attach_to_process", - "description": "Attach to a running process for debugging. After the attach handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the debug proxy is torn down", + "description": "Attach to a running process for debugging. After the handshake the target is verified by polling for threads; if none are reported within verifyTimeout (~20s default) the attach fails and the proxy is torn down. When a requested post-attach pause is accepted but no stopped event arrives within its bounded wait, returns state \"running\" with pending:true; the late event alone changes the session to \"paused\"", "inputSchema": { "type": "object", "properties": { @@ -516,7 +516,7 @@ }, { "name": "get_local_variables", - "description": "Get local variables for the current stack frame. This is a convenience tool that returns just the local variables without needing to traverse stack->scopes->variables manually. 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", + "description": "Get local variables for the shared inspection anchor. If the stopped thread is frameless, a sibling with policy-recognized user frames is preferred, adopted, and disclosed in anchorNote. A JavaScript frame with no Local/block scope returns empty with guidance instead of mislabeling Global bindings. Responses are size-guarded; pass names:[...] to fetch specific variables in full", "inputSchema": { "type": "object", "properties": { @@ -542,7 +542,7 @@ }, { "name": "get_stack_trace", - "description": "Get stack trace. The response includes stopReason — why the session is paused (e.g. \"breakpoint\" vs \"exception\"). Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note (the top frame is always kept even if internal, so frameId anchors keep working)", + "description": "Get stack trace. The response includes stopReason — why the session is paused. For an implicit frameless stopped thread, a sibling with policy-recognized user frames is preferred and adopted, with the switch disclosed in note. Internal/runtime frames are filtered by default; when any are hidden the response carries hiddenFrames and a note", "inputSchema": { "type": "object", "properties": { @@ -555,7 +555,7 @@ }, "threadId": { "type": "number", - "description": "Inspect a specific thread (ids from list_threads). When that thread reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls — the escape hatch when the session is anchored to a frameless thread" + "description": "Inspect this exact thread (ids from list_threads). Explicit selection is authoritative and is never silently switched" } }, "required": [ @@ -585,7 +585,7 @@ }, { "name": "evaluate_expression", - "description": "Evaluate expression in the current debug context. Expressions can read and modify program state. Waits up to 30s for the result by default; pass timeout for long-running expressions", + "description": "Evaluate expression in the current debug context. Without frameId, uses the same adopted top frame as stack and locals and discloses an automatic thread switch in anchorNote. Expressions can read and modify program state. Waits up to 30s by default", "inputSchema": { "type": "object", "properties": { @@ -597,7 +597,7 @@ }, "frameId": { "type": "number", - "description": "Optional stack frame ID for evaluation context. Must be a frame ID from a get_stack_trace response. If not provided, uses the current (top) frame automatically" + "description": "Optional authoritative stack frame ID from get_stack_trace. When provided, no automatic frame/thread selection occurs" }, "timeout": { "type": "number", diff --git a/tests/core/unit/server/handlers/inspection-tools.test.ts b/tests/core/unit/server/handlers/inspection-tools.test.ts index bdbe69ac6..cb862f926 100644 --- a/tests/core/unit/server/handlers/inspection-tools.test.ts +++ b/tests/core/unit/server/handlers/inspection-tools.test.ts @@ -12,6 +12,7 @@ import { handleGetSourceContext, handleGetLocalVariables } from '../../../../../src/server/handlers/inspection-tools.js'; +import { SessionTerminatedError } from '../../../../../src/errors/debug-errors.js'; import { createMockToolContext } from '../server-test-helpers.js'; // DebugMcpServer builds its dependencies in the constructor; mock the container @@ -282,24 +283,19 @@ describe('inspection tool handlers', () => { expect(payload.warning).toBeUndefined(); }); - it('returns graceful JSON for McpError with "not paused"', async () => { + it('does not classify an untyped McpError from its message text', async () => { ctx.validateSession = vi.fn().mockImplementation(() => { throw new McpError(McpErrorCode.InvalidRequest, 'Session is not paused'); }); - const result = await handleGetLocalVariables(ctx, { + await expect(handleGetLocalVariables(ctx, { sessionId: 'test-session' - }); - const payload = JSON.parse(result.content[0].text); - - expect(payload.success).toBe(false); - expect(payload.error).toContain('not paused'); - expect(payload.message).toContain('Cannot get local variables'); + })).rejects.toThrow('Session is not paused'); }); it('explains a terminated session as a normal end state (program finished)', async () => { ctx.validateSession = vi.fn().mockImplementation(() => { - throw new McpError(McpErrorCode.InvalidRequest, 'Session is terminated: test-session'); + throw new SessionTerminatedError('test-session'); }); const result = await handleGetLocalVariables(ctx, { @@ -328,7 +324,7 @@ describe('inspection tool handlers', () => { }); ctx.sessionManager.getLocalVariables = vi.fn().mockRejectedValue( - new McpError(McpErrorCode.InvalidRequest, 'Session is terminated: test-session') + new SessionTerminatedError('test-session') ); const result = await handleGetLocalVariables(ctx, { diff --git a/tests/core/unit/server/server-redefine-and-attach.test.ts b/tests/core/unit/server/server-redefine-and-attach.test.ts index 5b97a448d..73a738086 100644 --- a/tests/core/unit/server/server-redefine-and-attach.test.ts +++ b/tests/core/unit/server/server-redefine-and-attach.test.ts @@ -525,6 +525,30 @@ describe('redefine_classes and attach stopOnEntry tests', () => { }); describe('attach warning join (issue #450)', () => { + it('surfaces a pending post-attach pause at the top level (issue #598)', async () => { + mockSessionManager.attachToProcess.mockResolvedValue({ + success: true, + state: 'running', + data: { message: 'Attached; pause is pending', pending: true }, + }); + + const result = await callToolHandler({ + method: 'tools/call', + params: { + name: 'attach_to_process', + arguments: { sessionId: 'test-session', port: 5678 }, + }, + }); + + const payload = JSON.parse(result.content[0].text); + expect(payload).toMatchObject({ + success: true, + state: 'running', + pending: true, + data: { pending: true }, + }); + }); + it('surfaces data.warning at the top level of the attach_to_process response', async () => { mockSessionManager.attachToProcess.mockResolvedValue({ success: true, diff --git a/tests/core/unit/server/server-variable-access-gating.test.ts b/tests/core/unit/server/server-variable-access-gating.test.ts index 0b9650d96..14447e749 100644 --- a/tests/core/unit/server/server-variable-access-gating.test.ts +++ b/tests/core/unit/server/server-variable-access-gating.test.ts @@ -144,8 +144,7 @@ describe('Variable access gating (issue #237)', () => { })).rejects.toSatisfy((error: unknown) => { expect(error).toBeInstanceOf(McpError); expect((error as McpError).code).toBe(McpErrorCode.InvalidParams); - expect((error as McpError).message).toContain('DEBUG_MCP_VARIABLE_ACCESS=explicit'); - expect((error as McpError).message).toContain('names'); + expect((error as McpError).message).toContain('Missing required parameter: names'); return true; }); }); @@ -167,7 +166,8 @@ describe('Variable access gating (issue #237)', () => { params: { name: 'get_local_variables', arguments: { sessionId: 'test-session' } } })).rejects.toSatisfy((error: unknown) => { expect(error).toBeInstanceOf(McpError); - expect((error as McpError).message).toContain('DEBUG_MCP_VARIABLE_ACCESS=explicit'); + expect((error as McpError).code).toBe(McpErrorCode.InvalidParams); + expect((error as McpError).message).toContain('Missing required parameter: names'); return true; }); }); diff --git a/tests/core/unit/session/session-manager-integration.test.ts b/tests/core/unit/session/session-manager-integration.test.ts index 2daa98948..af2340c4a 100644 --- a/tests/core/unit/session/session-manager-integration.test.ts +++ b/tests/core/unit/session/session-manager-integration.test.ts @@ -119,7 +119,10 @@ describe('SessionManager - Integration Tests', () => { await sessionManager.startDebugging(session.id, 'test.py', [], { stopOnEntry: false }); await vi.runAllTimersAsync(); - dependencies.mockProxyManager.simulateEvent('stopped', 1, 'entry'); + // Seed the proxy's current-thread view as a real stopped event would; + // auto-continue cannot issue DAP continue without that anchor. + dependencies.mockProxyManager.simulateStopped(1, 'entry'); + await vi.runAllTimersAsync(); expect(sessionManager.getSession(session.id)?.lastStop).toBeUndefined(); }); diff --git a/tests/core/unit/session/session-manager-workflow.test.ts b/tests/core/unit/session/session-manager-workflow.test.ts index 43a07b174..9b9b1a777 100644 --- a/tests/core/unit/session/session-manager-workflow.test.ts +++ b/tests/core/unit/session/session-manager-workflow.test.ts @@ -160,6 +160,40 @@ describe('SessionManager - Debug Session Workflow', () => { expect(dependencies.mockProxyManager.startCalls[0].stopOnEntry).toBe(false); }); + it('does not overwrite an observed stop when adapter readiness arrives later (issue #598)', async () => { + const session = await sessionManager.createSession({ + language: DebugLanguage.MOCK, + executablePath: 'python' + }); + + dependencies.mockProxyManager.start = vi.fn().mockImplementation(async (proxyConfig) => { + dependencies.mockProxyManager.startCalls.push(proxyConfig); + (dependencies.mockProxyManager as unknown as { _isRunning: boolean })._isRunning = true; + process.nextTick(() => { + dependencies.mockProxyManager.emit('stopped', 1, 'breakpoint', { + reason: 'breakpoint', + threadId: 1, + allThreadsStopped: true + }); + dependencies.mockProxyManager.emit('adapter-configured'); + dependencies.mockProxyManager.emit('initialized'); + }); + }); + + const startPromise = sessionManager.startDebugging( + session.id, + 'test.py', + [], + { stopOnEntry: false } + ); + await vi.runAllTimersAsync(); + + const result = await startPromise; + expect(result.success).toBe(true); + expect(result.state).toBe(SessionState.PAUSED); + expect(sessionManager.getSession(session.id)?.lastStop?.reason).toBe('breakpoint'); + }); + it('should handle terminated event during startup', async () => { const session = await sessionManager.createSession({ language: DebugLanguage.MOCK, diff --git a/tests/e2e/comprehensive-mcp-tools.test.ts b/tests/e2e/comprehensive-mcp-tools.test.ts index a6447b9a3..ea337d6cd 100644 --- a/tests/e2e/comprehensive-mcp-tools.test.ts +++ b/tests/e2e/comprehensive-mcp-tools.test.ts @@ -20,7 +20,7 @@ import { PYTHON_SCRIPT, JS_SCRIPT, RUST_SCRIPT, GO_SCRIPT, DOTNET_SCRIPT, JAVA_SCRIPT, JAVA_CLASS_DIR, RUBY_SCRIPT, CPP_SCRIPT, PYTHON_BP_LINE, JS_BP_LINE, RUST_BP_LINE, GO_BP_LINE, DOTNET_BP_LINE, JAVA_BP_LINE, RUBY_BP_LINE, CPP_BP_LINE, hasRust, hasGo, hasRuby, hasDotnet, hasJava, hasCpp, - ensureGoBuild, ensureDotnetBuild, ensureJavaBuild, ensureCppBuild + ensureRustBuild, ensureGoBuild, ensureDotnetBuild, ensureJavaBuild, ensureCppBuild } from './language-matrix-utils.js'; /* ---------- result tracking ---------- */ @@ -116,6 +116,19 @@ describe(`Comprehensive MCP Debugger Test — ${ALL_TOOLS.length} Tools × ${LAN console.log('[Setup] MCP client connected to server'); // Pre-compile languages that need it + const rustLang = LANGUAGES.find(l => l.language === 'rust'); + if (rustLang?.available) { + try { + const rustBinary = await ensureRustBuild(); + rustLang.launchScript = rustBinary; + console.log(`[Setup] Rust binary compiled: ${rustBinary}`); + } catch (err) { + console.log(`[Setup] Rust build failed: ${err}`); + rustLang.available = false; + rustLang.skipReason = 'Rust build failed'; + } + } + const goLang = LANGUAGES.find(l => l.language === 'go'); if (goLang?.available) { try { diff --git a/tests/e2e/language-matrix-utils.ts b/tests/e2e/language-matrix-utils.ts index daa7fa2bd..8790e2d81 100644 --- a/tests/e2e/language-matrix-utils.ts +++ b/tests/e2e/language-matrix-utils.ts @@ -13,6 +13,7 @@ import { fileURLToPath } from 'url'; import { execFileSync, execSync } from 'child_process'; import { prepareJavaExample } from './java-example-utils.js'; import { prepareCppExample, hasCppToolchain } from './cpp-example-utils.js'; +import { prepareRustExample } from './rust-example-utils.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -109,6 +110,10 @@ export function ensureCppBuild(): string { return prepareCppExample('hello_world').binaryPath; } +export async function ensureRustBuild(): Promise { + return (await prepareRustExample('hello_world')).binaryPath; +} + /* ---------- language matrix ---------- */ export interface MatrixLangDef { @@ -150,7 +155,19 @@ export function createLanguageMatrix(): MatrixLangDef[] { * Run the per-language build steps (go binary, dotnet dll, java classes), * updating launchScript/availability in place. Call from a suite's beforeAll. */ -export function prepareLanguageMatrix(languages: MatrixLangDef[], log: (msg: string) => void = console.log): void { +export async function prepareLanguageMatrix(languages: MatrixLangDef[], log: (msg: string) => void = console.log): Promise { + const rustLang = languages.find(l => l.language === 'rust'); + if (rustLang?.available) { + try { + rustLang.launchScript = await ensureRustBuild(); + log(`[Setup] Rust binary compiled: ${rustLang.launchScript}`); + } catch (err) { + log(`[Setup] Rust build failed: ${err}`); + rustLang.available = false; + rustLang.skipReason = 'Rust build failed'; + } + } + const goLang = languages.find(l => l.language === 'go'); if (goLang?.available) { try { diff --git a/tests/e2e/mcp-server-breakpoint-management.test.ts b/tests/e2e/mcp-server-breakpoint-management.test.ts index 93fd52ab2..573d0b3a1 100644 --- a/tests/e2e/mcp-server-breakpoint-management.test.ts +++ b/tests/e2e/mcp-server-breakpoint-management.test.ts @@ -39,7 +39,7 @@ describe('Breakpoint management e2e (list/remove/clear)', () => { { capabilities: {} }, ); await mcpClient.connect(transport); - prepareLanguageMatrix(LANGUAGES); + await prepareLanguageMatrix(LANGUAGES); }, 120_000); afterAll(async () => { diff --git a/tests/e2e/mcp-server-logpoints.test.ts b/tests/e2e/mcp-server-logpoints.test.ts index c51f9dba9..27811e697 100644 --- a/tests/e2e/mcp-server-logpoints.test.ts +++ b/tests/e2e/mcp-server-logpoints.test.ts @@ -58,7 +58,7 @@ describe('Logpoints e2e (set_breakpoint logMessage)', () => { { capabilities: {} }, ); await mcpClient.connect(transport); - prepareLanguageMatrix(LANGUAGES); + await prepareLanguageMatrix(LANGUAGES); }, 120_000); afterAll(async () => { diff --git a/tests/e2e/mcp-server-smoke-restart.test.ts b/tests/e2e/mcp-server-smoke-restart.test.ts index 7986b0455..85867fffa 100644 --- a/tests/e2e/mcp-server-smoke-restart.test.ts +++ b/tests/e2e/mcp-server-smoke-restart.test.ts @@ -36,7 +36,7 @@ describe('restart_debugging e2e', () => { { capabilities: {} }, ); await mcpClient.connect(transport); - prepareLanguageMatrix(LANGUAGES); + await prepareLanguageMatrix(LANGUAGES); }, 120_000); afterAll(async () => { diff --git a/tests/e2e/mcp-server-smoke-ruby-attach.test.ts b/tests/e2e/mcp-server-smoke-ruby-attach.test.ts index fcee2251e..e0c9742de 100644 --- a/tests/e2e/mcp-server-smoke-ruby-attach.test.ts +++ b/tests/e2e/mcp-server-smoke-ruby-attach.test.ts @@ -180,7 +180,12 @@ describe('MCP Server Ruby Attach-Mode Smoke Test @requires-ruby', () => { }); const attachResponse = parseSdkToolResult(attachResult); expect(attachResponse.success).toBe(true); - expect(attachResponse.state).toBe('paused'); + expect(['paused', 'running']).toContain(attachResponse.state); + if (attachResponse.state === 'running') { + expect(attachResponse.pending).toBe(true); + const initialStack = await waitForPausedState(mcpClient!, sessionId); + expect(initialStack).not.toBeNull(); + } // 3. Breakpoint inside the loop, then release the load suspension const bpResult = await callToolSafely(mcpClient!, 'set_breakpoint', { diff --git a/tests/e2e/mcp-server-smoke-rust.test.ts b/tests/e2e/mcp-server-smoke-rust.test.ts index 4883a04dc..89fbcb8f0 100644 --- a/tests/e2e/mcp-server-smoke-rust.test.ts +++ b/tests/e2e/mcp-server-smoke-rust.test.ts @@ -30,7 +30,14 @@ describe('MCP Server Rust Debugging Smoke Test', () => { args: [distEntry, '--log-level', 'info'], env: { ...process.env, - NODE_ENV: 'test' + NODE_ENV: 'test', + // The examples are compiled with stable-gnu on Windows. Point + // CodeLLDB at the matching Rust formatter scripts as well; using the + // host's default MSVC scripts against a GNU stdlib can hang a + // variables request inside lldb_providers.py. + ...(process.platform === 'win32' + ? { RUSTUP_TOOLCHAIN: 'stable-x86_64-pc-windows-gnu' } + : {}) } }); @@ -206,7 +213,7 @@ describe('MCP Server Rust Debugging Smoke Test', () => { expect(helloEntry!.category).toBe('stdout'); expect(outputEntries.some(e => e.output.includes('Sum of 5 and 10 is: 15'))).toBe(true); }, - 60000 + 120000 ); it( @@ -335,7 +342,7 @@ describe('MCP Server Rust Debugging Smoke Test', () => { ); expect(finalContinue.success).toBe(true); }, - 60000 + 120000 ); // Shared driver for the continue-to-completion tests below: sets one diff --git a/tests/unit/proxy/proxy-manager-message-handling.test.ts b/tests/unit/proxy/proxy-manager-message-handling.test.ts index b9491f288..68435841a 100644 --- a/tests/unit/proxy/proxy-manager-message-handling.test.ts +++ b/tests/unit/proxy/proxy-manager-message-handling.test.ts @@ -75,6 +75,27 @@ describe('ProxyManager Message Handling', () => { expect(adapterConfiguredEmitted).toBe(true); }); + it('replays an initialization stop snapshot exactly once (issue #598)', () => { + const stopped = vi.fn(); + proxyManager.on('stopped', stopped); + + const statusMessage = { + type: 'status', + sessionId: 'test-session', + status: 'adapter_configured_and_launched', + lastStop: { reason: 'pause', threadId: 7, allThreadsStopped: true } + }; + proxyManager.simulateMessage(statusMessage); + proxyManager.simulateMessage(statusMessage); + + expect(stopped).toHaveBeenCalledTimes(1); + expect(stopped).toHaveBeenCalledWith( + 7, + 'pause', + expect.objectContaining({ reason: 'pause', threadId: 7 }) + ); + }); + it('emits adapter-capabilities exactly once per status message (issue #243)', () => { // Status messages run through BOTH the imperative handler and the // functional core; adapter_capabilities must only be emitted by the