Skip to content

Commit ff3677b

Browse files
committed
Make variable_filter required. Debug sessions always end now by default singleShot versus inspect modes
1 parent acf1b53 commit ff3677b

26 files changed

+1137
-545
lines changed

.vscode/settings.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
1010
"typescript.tsc.autoDetect": "off",
1111
// Prettier formatting settings
12-
"editor.defaultFormatter": "esbenp.prettier-vscode",
12+
"editor.defaultFormatter": "vscode.typescript-language-features",
1313
"editor.formatOnSave": true,
1414
"editor.codeActionsOnSave": {},
1515
// Ensure Prettier is used for all supported file types
@@ -41,5 +41,6 @@
4141
"cmd": "npm run update"
4242
}
4343
]
44-
}
44+
},
45+
"editor.formatOnPaste": true
4546
}

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ All notable changes to the "copilot-debugger" extension will be documented in th
44

55
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
66

7-
## [Unreleased]
7+
## 2025-12-29
8+
9+
**Changed:** Start Debugger tool sessions now default to **singleShot** behavior: when the debugger is paused at a breakpoint, the tool will terminate the debug session before returning. This is a safe-by-default guardrail to prevent out-of-band actions against a paused debuggee.
10+
11+
- To keep the session alive for follow-up inspection (variables/expressions/resume), set `mode: "inspect"` in the Start Debugger tool input.
12+
- Note: This behavior applies to sessions started via the tool flow; it does not change normal manual VS Code debugging behavior.
813

914
## [0.0.21] - 2025-11-21
1015

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ Description
9494
Type : `integer`
9595
Default : `50`
9696

97+
#### `copilot-debugger.maxOutputChars`
98+
Description : Maximum number of characters returned by Copilot debugger tools (tool output is truncated with a suffix when exceeded).
99+
Type : `integer`
100+
Default : `8192`
101+
97102
#### `copilot-debugger.consoleLogLevel`
98103
Description : Controls how verbosely logs are mirrored to the developer console (Output panel always receives every log; this only gates console.* mirroring). Changes take effect immediately without reloading.
99104
Type : `string`
@@ -129,6 +134,10 @@ Example settings snippet:
129134

130135
You may supply a `serverReady` object when starting the debugger to run an automated action (shell command, HTTP request, or VS Code command) once the target is "ready".
131136

137+
> **Important (don’t use `curl` with breakpoints):** If any breakpoint `onHit` is `break`, **do not** use `serverReady.action.type = "shellCommand"` with `curl`/`wget`/etc to call an endpoint that can hit that breakpoint. The request will often **hang** because the debuggee is paused and cannot finish responding.
138+
>
139+
> Prefer `serverReady.action.type = "httpRequest"` when you intend to **trigger a breakpoint** and capture state.
140+
132141
Structure (legacy union still accepted; new flat shape preferred):
133142

134143
```ts
@@ -177,6 +186,18 @@ Actions:
177186
- `httpRequest` – Issues a fetch; dispatched fire‑and‑forget so a slow service cannot block debugger continuation.
178187
- `vscodeCommand` – Invokes a VS Code command (e.g. telemetry-free internal toggles or extension commands) with optional `args`.
179188
189+
**Blocking behavior:**
190+
191+
- `shellCommand` runs in a terminal and can block forever if it launches an HTTP client (like `curl`) while your debug target is paused at a `break` breakpoint.
192+
- `httpRequest` is executed by the tool (with timeout handling) and is the recommended way to trigger a request that is expected to hit a breakpoint.
193+
194+
**Recommended patterns:**
195+
196+
- **To hit a breakpoint (and capture state):** use `serverReady.action.type = "httpRequest"`.
197+
- **To run a smoke command after you’ve resumed:** use `shellCommand` **after** you resume (e.g., via a separate task/command), not as `serverReady`.
198+
199+
**Common failure mode:** configuring `serverReady` with `curl http://localhost/...` while also setting a `break` breakpoint on the request handler causes the `curl` command to hang and the debug session to appear stuck.
200+
180201
Examples:
181202
182203
```jsonc
@@ -190,13 +211,13 @@ Examples:
190211
````
191212

192213
```jsonc
193-
// Breakpoint-triggered shell command
214+
// Breakpoint-triggered shell command (use for non-HTTP actions)
194215
{
195216
"serverReady": {
196217
"trigger": { "path": "src/server.ts", "line": 27 },
197218
"action": {
198219
"type": "shellCommand",
199-
"shellCommand": "curl http://localhost:3000/health"
220+
"shellCommand": "echo serverReady action executed"
200221
}
201222
}
202223
}

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ export default antfu(
234234
"out/**",
235235
"**/*.yml",
236236
"test-workspace/**",
237+
"src/generated-meta.ts",
237238
],
238239
}
239240
);

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@
9494
"description": "Maximum number of output lines (stderr/stdout) to buffer per debug session for runtime error reporting.",
9595
"scope": "resource"
9696
},
97+
"copilot-debugger.maxOutputChars": {
98+
"type": "integer",
99+
"default": 8192,
100+
"description": "Maximum number of characters returned by Copilot debugger tools (tool output is truncated with a suffix when exceeded).",
101+
"scope": "resource"
102+
},
97103
"copilot-debugger.consoleLogLevel": {
98104
"type": "string",
99105
"enum": [
@@ -122,8 +128,8 @@
122128
"displayName": "Start Debugger",
123129
"toolReferenceName": "startDebugSessionWithBreakpoints",
124130
"canBeReferencedInPrompt": true,
125-
"userDescription": "Start a debug session, set breakpoints, and wait until a breakpoint is hit. Supports onHit behaviors: break (pause & return), captureAndContinue (collect variables & interpolated log messages then resume automatically), stopDebugging (terminate after hit). Supports serverReady.action types: httpRequest | shellCommand | vscodeCommand (auto-run after readiness trigger). Exact numeric hitCount pauses on that occurrence.",
126-
"modelDescription": "Start a debug session using the configured default launch configuration or an explicitly provided configurationName. Auto-selects sole launch config when none specified and no default setting is present. Each breakpoint can specify an onHit: break (pause), captureAndContinue (non-blocking gather of variables & interpolation of {var} placeholders in logMessage then continue), or stopDebugging (terminate immediately after hit). Supports serverReady with trigger {path+line | pattern | omitted attach} and action.type httpRequest | shellCommand | vscodeCommand. Conditional expressions and exact numeric hitCount only. variableFilter required only for captureAndContinue; omit to capture all locals up to captureMaxVariables.",
131+
"userDescription": "Start a debug session, set breakpoints, and wait until a breakpoint is hit. Supports onHit behaviors: break (pause), captureAndContinue (collect variables then auto-resume), stopDebugging (terminate after hit). Default mode is singleShot: the tool terminates the session before returning so the model cannot accidentally run external curl/browser probes against a paused debuggee. If you need an interactive paused workflow, set mode=inspect.",
132+
"modelDescription": "Start a debug session using the configured default launch configuration or an explicitly provided configurationName. Auto-selects sole launch config when none specified and no default setting is present. Each breakpoint can specify an onHit: break (pause), captureAndContinue (collect variables & interpolate {var} placeholders then continue automatically), or stopDebugging (terminate immediately after hit). IMPORTANT protocol: if the tool returns while paused (inspect mode), you must NOT run external HTTP requests (curl/wget/browser/fetch) against the debuggee; only use debugger operations (evaluateExpression/getVariables/expandVariable/resume/stop) until resumed. Supports serverReady with trigger {path+line | pattern | omitted attach} and action.type httpRequest | shellCommand | vscodeCommand. httpRequest is the recommended canonical way to trigger a request intended to hit breakpoints because it is executed by the tool with timeout handling. shellCommand runs in a terminal and can block if it performs a request that hits a break breakpoint. Conditional expressions and exact numeric hitCount only. variableFilter required only for captureAndContinue; omit to capture all locals up to captureMaxVariables.",
127133
"inputSchema": {
128134
"type": "object",
129135
"properties": {
@@ -135,6 +141,14 @@
135141
"type": "string",
136142
"description": "Name of launch configuration to start. If omitted uses copilot-debugger.defaultLaunchConfiguration or auto-selects sole configuration if exactly one exists."
137143
},
144+
"mode": {
145+
"type": "string",
146+
"enum": [
147+
"singleShot",
148+
"inspect"
149+
],
150+
"description": "Tool mode. singleShot (default): terminate the session before returning. inspect: allow returning while paused for iterative inspection/resume workflows."
151+
},
138152
"breakpointConfig": {
139153
"type": "object",
140154
"properties": {
@@ -157,8 +171,7 @@
157171
"items": {
158172
"type": "string"
159173
},
160-
"minItems": 1,
161-
"description": "List of exact variable names to include when reporting variables for this breakpoint or for interpolation in captureAndContinue onHit. Required only if onHit=captureAndContinue and you want a subset; when omitted for captureAndContinue all locals (up to captureMaxVariables) are auto-collected. Ignored for break/stopDebugging. Case-sensitive."
174+
"description": "Required list of exact variable names to include when reporting variables for this breakpoint or for interpolation in captureAndContinue onHit. Use an empty array to opt into auto-capture (up to captureMaxVariables). Ignored for break/stopDebugging. Case-sensitive."
162175
},
163176
"onHit": {
164177
"type": "string",
@@ -189,7 +202,8 @@
189202
"required": [
190203
"path",
191204
"line",
192-
"onHit"
205+
"onHit",
206+
"variableFilter"
193207
]
194208
},
195209
"description": "Breakpoints to add before starting session."
@@ -198,7 +212,7 @@
198212
},
199213
"serverReady": {
200214
"type": "object",
201-
"description": "Optional server readiness automation. New flat schema: trigger {path+line | pattern | omitted attach}; action.type in httpRequest | shellCommand | vscodeCommand. Backward compatible with legacy shape.",
215+
"description": "Optional server readiness automation (trigger + action). Canonical pattern for driving a request into a breakpoint is action.type=httpRequest (tool-executed with timeout handling). shellCommand is allowed (including curl), but it can block if it performs a request that hits a break breakpoint. NOTE: the most common failure mode is external curl AFTER the tool returned while paused (inspect mode).",
202216
"properties": {
203217
"trigger": {
204218
"type": "object",
@@ -220,7 +234,7 @@
220234
},
221235
"action": {
222236
"type": "object",
223-
"description": "Action executed when ready. Flat schema with discriminator 'type'.",
237+
"description": "Action executed when ready. Flat schema with discriminator 'type'. Blocking behavior: shellCommand runs in a terminal and may block if it performs a request that hits a break breakpoint; httpRequest is executed by the tool with timeout handling and is recommended for triggering breakpoints.",
224238
"properties": {
225239
"type": {
226240
"type": "string",
@@ -229,7 +243,7 @@
229243
"shellCommand",
230244
"vscodeCommand"
231245
],
232-
"description": "Action type discriminator."
246+
"description": "Action type discriminator. Prefer httpRequest to trigger requests intended to hit breakpoints. shellCommand is allowed (including curl), but may block if it triggers a break breakpoint."
233247
},
234248
"url": {
235249
"type": "string",
@@ -252,7 +266,7 @@
252266
},
253267
"shellCommand": {
254268
"type": "string",
255-
"description": "Shell command executed in a new terminal (shellCommand)."
269+
"description": "Shell command executed in a new terminal (shellCommand). Allowed to run curl/wget/etc, but may block if it triggers a break breakpoint."
256270
},
257271
"command": {
258272
"type": "string",
@@ -284,6 +298,7 @@
284298
{
285299
"workspaceFolder": "/abs/path/project",
286300
"configurationName": "Run test.js",
301+
"mode": "singleShot",
287302
"breakpointConfig": {
288303
"breakpoints": [
289304
{
@@ -309,6 +324,7 @@
309324
},
310325
{
311326
"workspaceFolder": "/abs/path/project",
327+
"mode": "inspect",
312328
"breakpointConfig": {
313329
"breakpoints": [
314330
{
@@ -320,7 +336,7 @@
320336
"serverReady": {
321337
"action": {
322338
"type": "shellCommand",
323-
"shellCommand": "curl http://localhost:3000/health"
339+
"shellCommand": "curl -sS http://localhost:3000/health || exit 1"
324340
}
325341
}
326342
}
@@ -364,7 +380,6 @@
364380
"items": {
365381
"type": "string"
366382
},
367-
"minItems": 1,
368383
"description": "Variable names to include in reporting or interpolation for capture action."
369384
},
370385
"action": {
@@ -391,7 +406,8 @@
391406
},
392407
"required": [
393408
"path",
394-
"line"
409+
"line",
410+
"variableFilter"
395411
]
396412
}
397413
}
@@ -503,6 +519,7 @@
503519
"@anthropic-ai/sdk": "^0.27.3",
504520
"@reactive-vscode/reactivity": "^0.4.1",
505521
"@vscode/prompt-tsx": "^0.4.0-alpha.5",
522+
"markdown-table": "^3.0.4",
506523
"mocha": "^10.7.3",
507524
"reactive-vscode": "^0.4.1",
508525
"strip-ansi": "^7.1.2"

src/BreakpointDefinition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
export interface BreakpointDefinition {
88
path: string;
99
line: number;
10-
variableFilter?: string[]; // Optional: when action=capture and omitted we auto-capture all locals (bounded by captureMaxVariables setting)
10+
variableFilter: string[];
1111
onHit?: 'break' | 'stopDebugging' | 'captureAndContinue'; // captureAndContinue returns data then continues (non-blocking)
1212
condition?: string; // Expression evaluated at breakpoint; stop only if true
1313
hitCount?: number; // Exact numeric hit count (3 means pause on 3rd hit)

src/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const configDefaults = {
2020
5) as number,
2121
maxOutputLines: (Meta.configs.copilotDebuggerMaxOutputLines.default ??
2222
50) as number,
23+
maxOutputChars: (Meta.configs.copilotDebuggerMaxOutputChars.default ??
24+
8192) as number,
2325
consoleLogLevel: (Meta.configs.copilotDebuggerConsoleLogLevel.default ??
2426
'info') as ConsoleLogLevel,
2527
enableTraceLogging: (Meta.configs.copilotDebuggerEnableTraceLogging.default ??
@@ -32,6 +34,7 @@ const configDefaults = {
3234
serverReadyDefaultActionType: ServerReadyActionType;
3335
maxBuildErrors: number;
3436
maxOutputLines: number;
37+
maxOutputChars: number;
3538
consoleLogLevel: ConsoleLogLevel;
3639
enableTraceLogging: boolean;
3740
};

src/debugUtils.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { DebugProtocol } from '@vscode/debugprotocol';
22
import type * as vscode from 'vscode';
33
import { LanguageModelTextPart, LanguageModelToolResult } from 'vscode';
4+
import { truncateToolOutputText } from './outputTruncation';
45

56
// Re-export DAP types for convenience
67
export type Thread = DebugProtocol.Thread;
@@ -153,12 +154,16 @@ export class DAPHelpers {
153154
}
154155

155156
static createSuccessResult(message: string): LanguageModelToolResult {
156-
const textPart = new LanguageModelTextPart(message);
157+
const textPart = new LanguageModelTextPart(
158+
truncateToolOutputText(message).text
159+
);
157160
return new LanguageModelToolResult([textPart]);
158161
}
159162

160163
static createErrorResult(message: string): LanguageModelToolResult {
161-
const textPart = new LanguageModelTextPart(`Error: ${message}`);
164+
const textPart = new LanguageModelTextPart(
165+
truncateToolOutputText(`Error: ${message}`).text
166+
);
162167
return new LanguageModelToolResult([textPart]);
163168
}
164169
}

0 commit comments

Comments
 (0)