dap-mcp-server 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/SKILL.md +4 -0
- package/build/adapters/builtin.js +25 -0
- package/build/adapters/builtin.js.map +1 -1
- package/build/adapters/recipes.d.ts +46 -0
- package/build/adapters/recipes.js +128 -0
- package/build/adapters/recipes.js.map +1 -0
- package/build/dap/types.d.ts +20 -0
- package/build/diagnostics/doctor.d.ts +6 -0
- package/build/diagnostics/doctor.js +87 -28
- package/build/diagnostics/doctor.js.map +1 -1
- package/build/server.js +161 -112
- package/build/server.js.map +1 -1
- package/build/session/inspect.d.ts +7 -1
- package/build/session/inspect.js +13 -16
- package/build/session/inspect.js.map +1 -1
- package/build/session/run.d.ts +2 -0
- package/build/session/run.js +11 -14
- package/build/session/run.js.map +1 -1
- package/build/session/session.d.ts +22 -2
- package/build/session/session.js +95 -4
- package/build/session/session.js.map +1 -1
- package/build/session/state-format.d.ts +32 -0
- package/build/session/state-format.js +62 -0
- package/build/session/state-format.js.map +1 -0
- package/integrations/claude/dap-debugging/skills/dap-debugging/SKILL.md +2 -0
- package/package.json +1 -1
package/build/server.js
CHANGED
|
@@ -31,7 +31,9 @@ import { DebugSession } from "./session/session.js";
|
|
|
31
31
|
import { runDebugInspect } from "./session/inspect.js";
|
|
32
32
|
import { runDebugRun } from "./session/run.js";
|
|
33
33
|
import { inspectAdapterAvailability } from "./adapters/detect.js";
|
|
34
|
-
import { resolveProjectAdapter, runDebugDoctor } from "./diagnostics/doctor.js";
|
|
34
|
+
import { conciseDoctorResult, resolveProjectAdapter, runDebugDoctor, } from "./diagnostics/doctor.js";
|
|
35
|
+
import { buildLaunchArgs, defaultCargoProgram } from "./adapters/recipes.js";
|
|
36
|
+
import { COMPACT_STACK_DEPTH, renderState } from "./session/state-format.js";
|
|
35
37
|
function readPackageVersion() {
|
|
36
38
|
try {
|
|
37
39
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -62,6 +64,8 @@ export function createServer() {
|
|
|
62
64
|
"Prefer debug_at for a one-call breakpoint inspection: it selects the adapter, launches, waits, evaluates expressions, and cleans up.",
|
|
63
65
|
"Use debug_run when output or termination is enough. Use debug_launch only for interactive stepping or multiple inspection points.",
|
|
64
66
|
"For interactive sessions: debug_launch -> get_state/evaluate -> step -> debug_disconnect. A launch with breakpoints waits by default and reports stopped, terminated, or timeout explicitly.",
|
|
67
|
+
"step answers compactly (frame-local scopes, short stack) and states carry only output not reported before; use get_state for the full picture and get_output for the whole buffer.",
|
|
68
|
+
"To debug an already-running or suspended process, pass breakpoints to debug_attach so they bind before it resumes.",
|
|
65
69
|
"Omit sessionId when exactly one session is active. Adapter-specific DAP fields belong in launchConfig. Tool errors use isError=true.",
|
|
66
70
|
].join("\n"),
|
|
67
71
|
});
|
|
@@ -88,6 +92,7 @@ export function createServer() {
|
|
|
88
92
|
program: z.string(),
|
|
89
93
|
transport: z.enum(["stdio", "tcp"]),
|
|
90
94
|
})),
|
|
95
|
+
otherAdapters: z.array(z.string()).optional(),
|
|
91
96
|
nextSteps: z.array(z.string()),
|
|
92
97
|
});
|
|
93
98
|
const breakpointResultSchema = z.object({
|
|
@@ -100,6 +105,14 @@ export function createServer() {
|
|
|
100
105
|
adapter: z.string(),
|
|
101
106
|
hit: z.boolean(),
|
|
102
107
|
reason: z.string().optional(),
|
|
108
|
+
hint: z.string().optional(),
|
|
109
|
+
exception: z
|
|
110
|
+
.object({
|
|
111
|
+
id: z.string().optional(),
|
|
112
|
+
description: z.string().optional(),
|
|
113
|
+
breakMode: z.string().optional(),
|
|
114
|
+
})
|
|
115
|
+
.optional(),
|
|
103
116
|
location: z
|
|
104
117
|
.object({
|
|
105
118
|
file: z.string(),
|
|
@@ -117,9 +130,58 @@ export function createServer() {
|
|
|
117
130
|
.optional(),
|
|
118
131
|
output: z.array(z.string()).optional(),
|
|
119
132
|
status: z.string().optional(),
|
|
133
|
+
exitCode: z.number().optional(),
|
|
120
134
|
timedOut: z.boolean().optional(),
|
|
121
135
|
breakpoint: breakpointResultSchema.optional(),
|
|
122
136
|
});
|
|
137
|
+
const breakpointInputSchema = z.object({
|
|
138
|
+
file: z.string().describe("Source file path"),
|
|
139
|
+
line: z.number().describe("Line number"),
|
|
140
|
+
condition: z.string().optional(),
|
|
141
|
+
hitCondition: z.string().optional(),
|
|
142
|
+
logMessage: z.string().optional(),
|
|
143
|
+
});
|
|
144
|
+
const detailSchema = z.enum(["compact", "full"]);
|
|
145
|
+
/** Set breakpoints grouped by file; must run between `initialized` and configurationDone. */
|
|
146
|
+
async function applyBreakpoints(session, breakpoints = []) {
|
|
147
|
+
const byFile = new Map();
|
|
148
|
+
for (const bp of breakpoints) {
|
|
149
|
+
const existing = byFile.get(bp.file) ?? [];
|
|
150
|
+
existing.push(bp);
|
|
151
|
+
byFile.set(bp.file, existing);
|
|
152
|
+
}
|
|
153
|
+
const results = [];
|
|
154
|
+
for (const [file, bps] of byFile) {
|
|
155
|
+
const fileResults = await session.setBreakpoints(file, bps.map(({ line, condition, hitCondition, logMessage }) => ({
|
|
156
|
+
line,
|
|
157
|
+
condition,
|
|
158
|
+
hitCondition,
|
|
159
|
+
logMessage,
|
|
160
|
+
})));
|
|
161
|
+
results.push({ file, breakpoints: fileResults });
|
|
162
|
+
}
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
/** What to report once the program is no longer running under our control. */
|
|
166
|
+
async function terminatedReport(session) {
|
|
167
|
+
return {
|
|
168
|
+
status: "terminated",
|
|
169
|
+
exitCode: await session.waitForExitCode(),
|
|
170
|
+
...session.unreportedOutput(true),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Wait for the first stop after launch/attach/restart and describe the outcome. */
|
|
174
|
+
async function awaitFirstStop(session, timeout) {
|
|
175
|
+
const startedAt = Date.now();
|
|
176
|
+
const outcome = await session.waitForStopOrTermination(timeout);
|
|
177
|
+
if (outcome.status === "stopped") {
|
|
178
|
+
const state = await session.collectState(undefined, 10, 1, { consumeOutput: true });
|
|
179
|
+
return state;
|
|
180
|
+
}
|
|
181
|
+
if (outcome.status === "terminated")
|
|
182
|
+
return terminatedReport(session);
|
|
183
|
+
return { status: session.state, timedOut: true, elapsedMs: Date.now() - startedAt };
|
|
184
|
+
}
|
|
123
185
|
// ────────────────────────────────────────────────────────────────────────────
|
|
124
186
|
// High-level LLM entry points
|
|
125
187
|
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -129,6 +191,10 @@ export function createServer() {
|
|
|
129
191
|
inputSchema: z.object({
|
|
130
192
|
cwd: z.string().optional().describe("Project directory (default: server working directory)"),
|
|
131
193
|
adapter: z.string().optional().describe("Adapter to validate instead of auto-detecting one"),
|
|
194
|
+
verbose: z
|
|
195
|
+
.boolean()
|
|
196
|
+
.optional()
|
|
197
|
+
.describe("Include full details for every known adapter, not just the selected one"),
|
|
132
198
|
}),
|
|
133
199
|
outputSchema: doctorOutputSchema,
|
|
134
200
|
annotations: {
|
|
@@ -195,11 +261,19 @@ export function createServer() {
|
|
|
195
261
|
});
|
|
196
262
|
const adapter = resolved.result.recommendedAdapter;
|
|
197
263
|
if (!adapter || !resolved.config || !resolved.result.ready) {
|
|
264
|
+
const { recommendedAdapter, detected, adapters, otherAdapters, nextSteps } = conciseDoctorResult(resolved.result);
|
|
265
|
+
const summary = {
|
|
266
|
+
recommendedAdapter,
|
|
267
|
+
languages: detected.languages,
|
|
268
|
+
reason: adapters[0]?.reason,
|
|
269
|
+
nextSteps,
|
|
270
|
+
otherAdapters,
|
|
271
|
+
};
|
|
198
272
|
return {
|
|
199
273
|
content: [
|
|
200
274
|
{
|
|
201
275
|
type: "text",
|
|
202
|
-
text: `Debugger is not ready
|
|
276
|
+
text: `Debugger is not ready: ${JSON.stringify(summary)}\nRun debug_doctor with verbose=true for full adapter details.`,
|
|
203
277
|
},
|
|
204
278
|
],
|
|
205
279
|
isError: true,
|
|
@@ -207,18 +281,22 @@ export function createServer() {
|
|
|
207
281
|
}
|
|
208
282
|
const hasLaunchTarget = options.launchConfig !== undefined &&
|
|
209
283
|
["program", "module", "code"].some((key) => options.launchConfig?.[key] !== undefined);
|
|
210
|
-
|
|
284
|
+
const compiledProgram = !options.program && !hasLaunchTarget && adapter === "cppdbg"
|
|
285
|
+
? await defaultCargoProgram(cwd)
|
|
286
|
+
: undefined;
|
|
287
|
+
if (!options.program && !hasLaunchTarget && adapter === "cppdbg" && !compiledProgram) {
|
|
211
288
|
return {
|
|
212
289
|
content: [
|
|
213
290
|
{
|
|
214
291
|
type: "text",
|
|
215
|
-
text: "Error: C/C
|
|
292
|
+
text: "Error: C/C++/Rust debugging requires the compiled executable in program or launchConfig.program. For a Cargo project, run `cargo build` first and program defaults to target/debug/<package name>.",
|
|
216
293
|
},
|
|
217
294
|
],
|
|
218
295
|
isError: true,
|
|
219
296
|
};
|
|
220
297
|
}
|
|
221
298
|
const program = options.program ??
|
|
299
|
+
compiledProgram ??
|
|
222
300
|
(hasLaunchTarget ? undefined : adapter === "go" ? cwd : sourceFile);
|
|
223
301
|
const result = await runDebugInspect(registry, sessions, {
|
|
224
302
|
adapter,
|
|
@@ -364,13 +442,7 @@ export function createServer() {
|
|
|
364
442
|
.optional()
|
|
365
443
|
.describe("Stop on program entry. Defaults to false when breakpoints are provided (run to first breakpoint), true otherwise."),
|
|
366
444
|
breakpoints: z
|
|
367
|
-
.array(
|
|
368
|
-
file: z.string().describe("Source file path"),
|
|
369
|
-
line: z.number().describe("Line number"),
|
|
370
|
-
condition: z.string().optional(),
|
|
371
|
-
hitCondition: z.string().optional(),
|
|
372
|
-
logMessage: z.string().optional(),
|
|
373
|
-
}))
|
|
445
|
+
.array(breakpointInputSchema)
|
|
374
446
|
.optional()
|
|
375
447
|
.describe("Breakpoints to set before launching"),
|
|
376
448
|
exceptionBreakpoints: z
|
|
@@ -421,27 +493,15 @@ export function createServer() {
|
|
|
421
493
|
sessions.add(session);
|
|
422
494
|
// Initialize (waits for the initialized event internally)
|
|
423
495
|
await session.initialize();
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
496
|
+
const launchArgs = await buildLaunchArgs(config, {
|
|
497
|
+
program,
|
|
498
|
+
args,
|
|
499
|
+
cwd,
|
|
500
|
+
env,
|
|
501
|
+
launchConfig,
|
|
428
502
|
stopOnEntry: shouldStop,
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
launchArgs.program = program;
|
|
432
|
-
if (args !== undefined)
|
|
433
|
-
launchArgs.args = args;
|
|
434
|
-
if (cwd !== undefined)
|
|
435
|
-
launchArgs.cwd = cwd;
|
|
436
|
-
if (env !== undefined)
|
|
437
|
-
launchArgs.env = env;
|
|
438
|
-
// Adapter-specific arg normalization. delve's `cwd` is the debuggee's
|
|
439
|
-
// working directory; the *build* (go build) runs from delve's own
|
|
440
|
-
// process cwd, controlled by `dlvCwd`. Mirror cwd → dlvCwd so a
|
|
441
|
-
// user-supplied cwd is found by `go build` looking for go.mod.
|
|
442
|
-
if (config.type === "go" && cwd !== undefined && launchArgs.dlvCwd === undefined) {
|
|
443
|
-
launchArgs.dlvCwd = cwd;
|
|
444
|
-
}
|
|
503
|
+
sourceFiles: breakpoints?.map((bp) => bp.file),
|
|
504
|
+
});
|
|
445
505
|
// Send launch first (don't await — real adapters like debugpy block
|
|
446
506
|
// the launch response until configurationDone arrives)
|
|
447
507
|
const launchPromise = session.launch(launchArgs);
|
|
@@ -454,25 +514,7 @@ export function createServer() {
|
|
|
454
514
|
// initialize response; others (debugpy) only emit it after receiving
|
|
455
515
|
// launch — that's why we fire launch first and wait for initialized here.
|
|
456
516
|
await session.waitForInitialized(10000);
|
|
457
|
-
|
|
458
|
-
const breakpointResults = [];
|
|
459
|
-
if (breakpoints && breakpoints.length > 0) {
|
|
460
|
-
const byFile = new Map();
|
|
461
|
-
for (const bp of breakpoints) {
|
|
462
|
-
const existing = byFile.get(bp.file) ?? [];
|
|
463
|
-
existing.push(bp);
|
|
464
|
-
byFile.set(bp.file, existing);
|
|
465
|
-
}
|
|
466
|
-
for (const [file, bps] of byFile) {
|
|
467
|
-
const results = await session.setBreakpoints(file, bps.map((b) => ({
|
|
468
|
-
line: b.line,
|
|
469
|
-
condition: b.condition,
|
|
470
|
-
hitCondition: b.hitCondition,
|
|
471
|
-
logMessage: b.logMessage,
|
|
472
|
-
})));
|
|
473
|
-
breakpointResults.push({ file, breakpoints: results });
|
|
474
|
-
}
|
|
475
|
-
}
|
|
517
|
+
const breakpointResults = await applyBreakpoints(session, breakpoints);
|
|
476
518
|
// Set exception breakpoints
|
|
477
519
|
if (exceptionBreakpoints && exceptionBreakpoints.length > 0) {
|
|
478
520
|
await session.setExceptionBreakpoints(exceptionBreakpoints);
|
|
@@ -482,27 +524,9 @@ export function createServer() {
|
|
|
482
524
|
// Now await the launch response
|
|
483
525
|
await launchPromise;
|
|
484
526
|
const shouldWait = waitForStop ?? (shouldStop || expectsConfiguredStop);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const outcome = await session.waitForStopOrTermination(timeout ?? 30000);
|
|
489
|
-
if (outcome.status === "stopped") {
|
|
490
|
-
stateData = (await session.collectState());
|
|
491
|
-
}
|
|
492
|
-
else if (outcome.status === "terminated") {
|
|
493
|
-
const { output } = session.getOutput();
|
|
494
|
-
stateData = {
|
|
495
|
-
status: "terminated",
|
|
496
|
-
output: output.map((entry) => entry.text),
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
else {
|
|
500
|
-
stateData = { status: session.state, timedOut: true, elapsedMs: Date.now() - startedAt };
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
else {
|
|
504
|
-
stateData = { status: session.state };
|
|
505
|
-
}
|
|
527
|
+
const stateData = shouldWait
|
|
528
|
+
? await awaitFirstStop(session, timeout ?? 30000)
|
|
529
|
+
: { status: session.state };
|
|
506
530
|
return {
|
|
507
531
|
content: [
|
|
508
532
|
{
|
|
@@ -526,12 +550,25 @@ export function createServer() {
|
|
|
526
550
|
// ────────────────────────────────────────────────────────────────────────────
|
|
527
551
|
// 4. debug_attach
|
|
528
552
|
// ────────────────────────────────────────────────────────────────────────────
|
|
529
|
-
server.tool("debug_attach", "Attach a debug adapter to an already-running process.", {
|
|
553
|
+
server.tool("debug_attach", "Attach a debug adapter to an already-running process. Pass breakpoints here rather than afterwards: they bind before the debuggee resumes, which is the only reliable way to stop a process started suspended (e.g. JDWP suspend=y). With breakpoints or exception breakpoints, waits and reports stopped, terminated, or timeout.", {
|
|
530
554
|
adapter: z.string().describe("Adapter type to use"),
|
|
531
555
|
port: z.number().optional().describe("Debug port to attach to"),
|
|
532
556
|
host: z.string().optional().describe("Host to attach to (default: localhost)"),
|
|
533
557
|
processId: z.number().optional().describe("PID of process to attach to"),
|
|
534
558
|
attachConfig: z.record(z.string(), z.unknown()).optional().describe("Arbitrary attach configuration properties"),
|
|
559
|
+
breakpoints: z
|
|
560
|
+
.array(breakpointInputSchema)
|
|
561
|
+
.optional()
|
|
562
|
+
.describe("Breakpoints to bind before the debuggee resumes"),
|
|
563
|
+
exceptionBreakpoints: z
|
|
564
|
+
.array(z.string())
|
|
565
|
+
.optional()
|
|
566
|
+
.describe("Exception filter IDs (e.g. ['uncaughtExceptions'])"),
|
|
567
|
+
waitForStop: z
|
|
568
|
+
.boolean()
|
|
569
|
+
.optional()
|
|
570
|
+
.describe("Wait for stopped/terminated outcome; defaults to true when breakpoints or exception breakpoints are given"),
|
|
571
|
+
timeout: z.number().positive().optional().describe("Wait timeout in milliseconds (default: 30000)"),
|
|
535
572
|
adapterConfig: z
|
|
536
573
|
.object({
|
|
537
574
|
type: z.string(),
|
|
@@ -545,7 +582,9 @@ export function createServer() {
|
|
|
545
582
|
})
|
|
546
583
|
.optional()
|
|
547
584
|
.describe("Override adapter config inline"),
|
|
548
|
-
}, async ({ adapter, port, host, processId, attachConfig, adapterConfig }) => {
|
|
585
|
+
}, async ({ adapter, port, host, processId, attachConfig, breakpoints, exceptionBreakpoints, waitForStop, timeout, adapterConfig, }) => {
|
|
586
|
+
const expectsConfiguredStop = (breakpoints !== undefined && breakpoints.length > 0) ||
|
|
587
|
+
(exceptionBreakpoints !== undefined && exceptionBreakpoints.length > 0);
|
|
549
588
|
let session;
|
|
550
589
|
try {
|
|
551
590
|
await registryReady;
|
|
@@ -582,13 +621,24 @@ export function createServer() {
|
|
|
582
621
|
// will still throw if attach fails.
|
|
583
622
|
attachPromise.catch(() => { });
|
|
584
623
|
await session.waitForInitialized(10000);
|
|
624
|
+
const breakpointResults = await applyBreakpoints(session, breakpoints);
|
|
625
|
+
if (exceptionBreakpoints && exceptionBreakpoints.length > 0) {
|
|
626
|
+
await session.setExceptionBreakpoints(exceptionBreakpoints);
|
|
627
|
+
}
|
|
585
628
|
await session.configurationDone();
|
|
586
629
|
await attachPromise;
|
|
630
|
+
const stateData = waitForStop ?? expectsConfiguredStop
|
|
631
|
+
? await awaitFirstStop(session, timeout ?? 30000)
|
|
632
|
+
: { status: session.state };
|
|
587
633
|
return {
|
|
588
634
|
content: [
|
|
589
635
|
{
|
|
590
636
|
type: "text",
|
|
591
|
-
text: JSON.stringify({
|
|
637
|
+
text: JSON.stringify({
|
|
638
|
+
sessionId: id,
|
|
639
|
+
breakpoints: breakpointResults.length > 0 ? breakpointResults : undefined,
|
|
640
|
+
...stateData,
|
|
641
|
+
}, null, 2),
|
|
592
642
|
},
|
|
593
643
|
],
|
|
594
644
|
};
|
|
@@ -669,6 +719,13 @@ export function createServer() {
|
|
|
669
719
|
threadId: z.number().optional().describe("Thread ID (uses stopped thread if omitted)"),
|
|
670
720
|
depth: z.number().optional().describe("Stack trace depth (default: 10)"),
|
|
671
721
|
variableDepth: z.number().optional().describe("Variable expansion depth (default: 1)"),
|
|
722
|
+
scopes: z
|
|
723
|
+
.array(z.string())
|
|
724
|
+
.optional()
|
|
725
|
+
.describe("Scope names to fetch, e.g. ['Registers']. Default: every scope except CPU registers; skipped ones are listed in omittedScopes"),
|
|
726
|
+
detail: detailSchema
|
|
727
|
+
.optional()
|
|
728
|
+
.describe("'full' (default) or 'compact' (frame-local scopes only, slimmer JSON)"),
|
|
672
729
|
timeout: z
|
|
673
730
|
.number()
|
|
674
731
|
.optional()
|
|
@@ -678,25 +735,24 @@ export function createServer() {
|
|
|
678
735
|
destructiveHint: false,
|
|
679
736
|
idempotentHint: true,
|
|
680
737
|
openWorldHint: false,
|
|
681
|
-
}, async ({ sessionId, threadId, depth, variableDepth, timeout }) => {
|
|
738
|
+
}, async ({ sessionId, threadId, depth, variableDepth, scopes, detail, timeout }) => {
|
|
682
739
|
try {
|
|
683
740
|
const session = sessions.resolve(sessionId);
|
|
684
741
|
const waitMs = timeout ?? 0;
|
|
742
|
+
const stateDetail = detail ?? "full";
|
|
743
|
+
if (waitMs > 0 && session.state === "running") {
|
|
744
|
+
await session.waitForStopOrTermination(waitMs);
|
|
745
|
+
}
|
|
685
746
|
if (session.state === "stopped") {
|
|
686
|
-
|
|
747
|
+
// Read-only: output stays unreported so a later step still carries it.
|
|
748
|
+
const state = await session.collectState(threadId, depth ?? 10, variableDepth ?? 1, {
|
|
749
|
+
scopes,
|
|
750
|
+
localsOnly: stateDetail === "compact",
|
|
751
|
+
});
|
|
687
752
|
return {
|
|
688
|
-
content: [{ type: "text", text:
|
|
753
|
+
content: [{ type: "text", text: renderState(state, stateDetail) }],
|
|
689
754
|
};
|
|
690
755
|
}
|
|
691
|
-
if (waitMs > 0 && session.state === "running") {
|
|
692
|
-
const outcome = await session.waitForStopOrTermination(waitMs);
|
|
693
|
-
if (outcome.status === "stopped") {
|
|
694
|
-
const state = await session.collectState(threadId, depth ?? 10, variableDepth ?? 1);
|
|
695
|
-
return {
|
|
696
|
-
content: [{ type: "text", text: JSON.stringify(state, null, 2) }],
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
756
|
const { output } = session.getOutput();
|
|
701
757
|
return {
|
|
702
758
|
content: [
|
|
@@ -763,20 +819,29 @@ export function createServer() {
|
|
|
763
819
|
.enum(["continue", "next", "stepIn", "stepOut", "pause"])
|
|
764
820
|
.describe("Execution control action"),
|
|
765
821
|
threadId: z.number().optional().describe("Thread ID (uses stopped thread if omitted)"),
|
|
822
|
+
detail: detailSchema
|
|
823
|
+
.optional()
|
|
824
|
+
.describe("'compact' (default: frame-local scopes, short stack, slimmer JSON) or 'full' (same shape as get_state)"),
|
|
766
825
|
timeout: z
|
|
767
826
|
.number()
|
|
768
827
|
.optional()
|
|
769
828
|
.describe("Milliseconds to wait for the program to stop after the action (default: 30000)"),
|
|
770
|
-
}, async ({ sessionId, action, threadId, timeout }) => {
|
|
829
|
+
}, async ({ sessionId, action, threadId, detail, timeout }) => {
|
|
771
830
|
try {
|
|
772
831
|
const session = sessions.resolve(sessionId);
|
|
773
832
|
const waitMs = timeout ?? 30000;
|
|
774
833
|
const startTime = Date.now();
|
|
834
|
+
const stateDetail = detail ?? "compact";
|
|
835
|
+
const collectSteppedState = () => session.collectState(threadId, stateDetail === "compact" ? COMPACT_STACK_DEPTH : 10, 1, {
|
|
836
|
+
localsOnly: stateDetail === "compact",
|
|
837
|
+
consumeOutput: true,
|
|
838
|
+
});
|
|
775
839
|
// If already stopped and asking to pause, just return current state
|
|
776
840
|
if (action === "pause" && session.state === "stopped") {
|
|
777
|
-
const state = await session.collectState(threadId);
|
|
778
841
|
return {
|
|
779
|
-
content: [
|
|
842
|
+
content: [
|
|
843
|
+
{ type: "text", text: renderState(await collectSteppedState(), stateDetail) },
|
|
844
|
+
],
|
|
780
845
|
};
|
|
781
846
|
}
|
|
782
847
|
switch (action) {
|
|
@@ -800,13 +865,9 @@ export function createServer() {
|
|
|
800
865
|
if (outcome.status !== "stopped") {
|
|
801
866
|
const elapsed = Date.now() - startTime;
|
|
802
867
|
if (outcome.status === "terminated") {
|
|
803
|
-
const { output } = session.getOutput();
|
|
804
868
|
return {
|
|
805
869
|
content: [
|
|
806
|
-
{
|
|
807
|
-
type: "text",
|
|
808
|
-
text: JSON.stringify({ status: "terminated", output: output.map((e) => e.text) }, null, 2),
|
|
809
|
-
},
|
|
870
|
+
{ type: "text", text: JSON.stringify(await terminatedReport(session)) },
|
|
810
871
|
],
|
|
811
872
|
};
|
|
812
873
|
}
|
|
@@ -819,9 +880,10 @@ export function createServer() {
|
|
|
819
880
|
],
|
|
820
881
|
};
|
|
821
882
|
}
|
|
822
|
-
const state = await session.collectState(threadId);
|
|
823
883
|
return {
|
|
824
|
-
content: [
|
|
884
|
+
content: [
|
|
885
|
+
{ type: "text", text: renderState(await collectSteppedState(), stateDetail) },
|
|
886
|
+
],
|
|
825
887
|
};
|
|
826
888
|
}
|
|
827
889
|
catch (err) {
|
|
@@ -954,22 +1016,9 @@ export function createServer() {
|
|
|
954
1016
|
await launchPromise;
|
|
955
1017
|
const stopOnEntry = launchConfig.stopOnEntry !== false;
|
|
956
1018
|
const shouldWait = stopOnEntry || savedBreakpoints.size > 0;
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
if (outcome.status === "stopped") {
|
|
961
|
-
stateData = (await newSession.collectState());
|
|
962
|
-
}
|
|
963
|
-
else {
|
|
964
|
-
stateData = {
|
|
965
|
-
status: outcome.status === "timeout" ? newSession.state : "terminated",
|
|
966
|
-
timedOut: outcome.status === "timeout",
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
else {
|
|
971
|
-
stateData = { status: newSession.state };
|
|
972
|
-
}
|
|
1019
|
+
const stateData = shouldWait
|
|
1020
|
+
? await awaitFirstStop(newSession, 30000)
|
|
1021
|
+
: { status: newSession.state };
|
|
973
1022
|
return {
|
|
974
1023
|
content: [
|
|
975
1024
|
{
|