@hy-sde-org/dsh-tool-debug 0.1.1-rc.2

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/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hy-sde
4
+
5
+ This repository contains a port of the oh-my-pi coding-agent DAP debugging stack
6
+ (dap client, session manager, adapter configuration, and the debug tool). The
7
+ ported portions are distributed under the same MIT license with their original
8
+ copyright held by their respective authors:
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # @hy-sde-org/dsh-tool-debug
2
+
3
+ The `debug` tool plugin for DeepSeek Harness, built on the standalone DAP
4
+ seam from the sibling package [`@hy-sde-org/dsh-dap`](../dap/): one tool, 28
5
+ operations — launch/attach, source/function/instruction/data breakpoints,
6
+ continue/pause/step, threads/stackTrace/scopes/variables/evaluate,
7
+ disassembly, memory access, modules, loaded sources, custom requests,
8
+ captured output, and termination.
9
+
10
+ ```bash
11
+ dsh plugin --profile web add @hy-sde-org/dsh-tool-debug
12
+ ```
13
+
14
+ The npm package installs a zero-effect bundle patch (nothing on the official
15
+ harness collides with the `debug` name, so there is nothing to disable or
16
+ shadow); the tool itself is mounted by adding the provided
17
+ [agent preset row](./examples/agent-preset) to a user preset. Installing the
18
+ bundle alone never breaks boot. After mounting the preset, agents get the
19
+ `debug` tool and can launch/attach real debuggers (debugpy, lldb-dap, gdb,
20
+ dlv, js-debug, ...) through the mounted DAP provider.
21
+
22
+ ## Tool surface
23
+
24
+ - `name`: `tool-debug`, `inject`: `['tools', 'dap', 'systemPrompt']`
25
+ - Config: `maxResultChars` (16000), `requestTimeoutSec` (30), `timeoutMs`
26
+ (120000, enforced by `dsh-tool-call-timeout-policy`).
27
+ - Requires a session workspace cwd (`exec.agent.session.header.cwd`) and is
28
+ not concurrency-safe: debug sessions are exclusive.
29
+
30
+ ## Behavior notes
31
+
32
+ - One active session: an active session must be terminated (or has
33
+ terminated/exited) before another launch/attach.
34
+ - Paths resolve against the session workspace; `cwd` overrides per call.
35
+ - Per-request timeout aborts via a combined `AbortSignal` (call + timeout).
36
+ - Adapter selection errors name the missing adapter and the install command
37
+ (`pip install debugpy`, `apt install gdb`, ...).
38
+ - Breakpoint mutations are serialized per session, and propagated to every
39
+ live session when an adapter reports a session tree.
40
+
41
+ ## Example preset
42
+
43
+ `examples/agent-preset/` contains a ready-to-copy user preset
44
+ (`agent.cordis.yml` + `preset.yml`) that mounts the debug group (the `dap`
45
+ service in its own isolated realm so each agent owns its adapter processes)
46
+ plus the `tool-debug` row beside it. Copy the directory to
47
+ `~/.dsh/.agent-presets/<id>/`, select the preset in the Web UI preset picker
48
+ (or `dsh agent` CLI), and agents in that preset can call `debug`.
49
+
50
+ ## Testing
51
+
52
+ `pnpm --filter @hy-sde-org/dsh-tool-debug test` (vitest) covers argument
53
+ parsing, rendering, and an end-to-end launch/breakpoint/step/evaluate flow
54
+ through a scripted DAP adapter.
@@ -0,0 +1,15 @@
1
+ # @hy-sde-org/dsh-tool-debug — install surface for the `debug` tool.
2
+ #
3
+ # DeepSeek Harness has no stock `debug` tool: unlike the edit stack (which
4
+ # shadows the built-in `str_replace_editor`/`edit`), nothing on the official
5
+ # releases collides with the `debug` name. The bundle therefore stays out of
6
+ # the way and inserts no rows and disables nothing: installing this package
7
+ # alone never changes a profile's behavior and never breaks boot.
8
+ #
9
+ # The tool mounts at the AGENT plane — one row in a user agent preset — where
10
+ # its scoped `debug` becomes available alongside the deployment's other tools.
11
+ # See the README "Example preset" section and `examples/agent-preset/` in this
12
+ # package for the ready-to-copy preset (it mounts the `dap` service in a
13
+ # per-session realm plus the `tool-debug` row beside it).
14
+ #
15
+ # Nothing to patch on stock releases.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Model-facing `debug` tool over `ctx.dap`. One tool, 28 operations: launch/
3
+ * attach, source/function/instruction/data breakpoints, continue/pause/step,
4
+ * threads/stackTrace/scopes/variables/evaluate, disassembly, memory access,
5
+ * modules, captured output, termination, and session listing. It resolves
6
+ * paths against the session workspace, requires an active session for stateful
7
+ * operations, converts request timeouts into abort signals, and renders every
8
+ * result through `render.ts`. It runtime-injects `tools`, `dap`, and
9
+ * `systemPrompt`, and imports no provider.
10
+ *
11
+ * Namespace plugin (named exports, no default export).
12
+ * @module @hy-sde-org/dsh-tool-debug
13
+ */
14
+ import type { Context } from '@deepseek-ai/cordis';
15
+ import z from '@deepseek-ai/schemastery';
16
+ import type { DebugToolArgs } from './types.ts';
17
+ /** Cordis plugin name for loader diagnostics. */
18
+ export declare const name = "tool-debug";
19
+ /** Services required by this plugin. */
20
+ export declare const inject: string[];
21
+ /** Default per-request timeout (seconds) when the model omits `timeout`. */
22
+ export declare const DEFAULT_DEBUG_REQUEST_TIMEOUT_SEC = 30;
23
+ /** Default whole-tool-call timeout budget (ms) for `dsh-tool-call-timeout-policy`. */
24
+ export declare const DEFAULT_DEBUG_TOOL_TIMEOUT_MS = 120000;
25
+ /** The stable system-prompt guidance positioning the debug tool. */
26
+ export declare const DEBUG_PROMPT_TEXT = "Use debug to attach a real debugger (gdb/lldb-dap/debugpy/dlv/...) to a process: launch or attach, set breakpoints, then continue/step, inspect threads/stack/scopes/variables, evaluate expressions, read memory, and terminate. Debug sessions are exclusive \u2014 terminate a session before launching another. Breakpoints must be set before continuing after a stop.";
27
+ /** Plugin configuration: result caps and timeout budgets. */
28
+ export interface Config {
29
+ /** Largest complete rendered result in characters (default 16000). */
30
+ maxResultChars?: number;
31
+ /** Default per-request timeout in seconds (default 30). */
32
+ requestTimeoutSec?: number;
33
+ /** Whole-tool-call timeout budget in ms (default 120000). */
34
+ timeoutMs?: number;
35
+ }
36
+ export declare const Config: z<Config>;
37
+ /**
38
+ * Register the `debug` tool and its system-prompt guidance.
39
+ * @param ctx - the plugin context (must inject `tools`, `dap`, `systemPrompt`).
40
+ * @param config - the resolved plugin configuration.
41
+ */
42
+ export declare function apply(ctx: Context, config: Config): void;
43
+ /**
44
+ * Validate and normalize raw model arguments into `DebugToolArgs`.
45
+ * @param args - the raw model arguments.
46
+ * @returns the normalized debug tool arguments.
47
+ */
48
+ export declare function parseDebugArgs(args: unknown): DebugToolArgs;
49
+ /** Adapter-unavailable hints keyed by well-known adapter ids. */
50
+ declare const ADAPTER_UNAVAILABLE_MESSAGES: Readonly<Record<string, string>>;
51
+ export { ADAPTER_UNAVAILABLE_MESSAGES };
52
+ export type { DebugToolArgs } from './types.ts';
53
+ export { DEBUG_ACTIONS, DEBUG_OUTPUT_SCHEMA, formatBreakpoints, formatCustomResponse, formatDataBreakpointInfo, formatDataBreakpoints, formatDisassembly, formatEvaluation, formatFunctionBreakpoints, formatInstructionBreakpoints, formatLoadedSources, formatMemoryRead, formatModules, formatScopes, formatSessionSnapshot, formatSessions, formatStackFrames, formatThreads, formatVariables, makeDebugOutput, presentDebugCall, summarizeDebugCall, } from './render.ts';
54
+ export type { GenericCallView } from '@deepseek-ai/dsh-tools';
55
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAkCxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG/C,iDAAiD;AACjD,eAAO,MAAM,IAAI,eAAe,CAAA;AAEhC,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAAmC,CAAA;AAEtD,4EAA4E;AAC5E,eAAO,MAAM,iCAAiC,KAAK,CAAA;AAEnD,sFAAsF;AACtF,eAAO,MAAM,6BAA6B,SAAU,CAAA;AAEpD,oEAAoE;AACpE,eAAO,MAAM,iBAAiB,gXAC4U,CAAA;AAE1W,6DAA6D;AAC7D,MAAM,WAAW,MAAM;IACrB,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAID,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAI3B,CAAA;AAIF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAgFxD;AAKD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,CAS3D;AAED,iEAAiE;AACjE,QAAA,MAAM,4BAA4B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMlE,CAAA;AA2ZD,OAAO,EAAE,4BAA4B,EAAE,CAAA;AACvC,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EAChB,yBAAyB,EACzB,4BAA4B,EAC5B,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,473 @@
1
+ /**
2
+ * Model-facing `debug` tool over `ctx.dap`. One tool, 28 operations: launch/
3
+ * attach, source/function/instruction/data breakpoints, continue/pause/step,
4
+ * threads/stackTrace/scopes/variables/evaluate, disassembly, memory access,
5
+ * modules, captured output, termination, and session listing. It resolves
6
+ * paths against the session workspace, requires an active session for stateful
7
+ * operations, converts request timeouts into abort signals, and renders every
8
+ * result through `render.ts`. It runtime-injects `tools`, `dap`, and
9
+ * `systemPrompt`, and imports no provider.
10
+ *
11
+ * Namespace plugin (named exports, no default export).
12
+ * @module @hy-sde-org/dsh-tool-debug
13
+ */
14
+ import { defineTool } from '@deepseek-ai/dsh-tools';
15
+ import z from '@deepseek-ai/schemastery';
16
+ import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout';
17
+ import { formatBreakpoints, formatCustomResponse, formatDataBreakpointInfo, formatDataBreakpoints, formatDisassembly, formatEvaluation, formatFunctionBreakpoints, formatInstructionBreakpoints, formatLoadedSources, formatMemoryRead, formatModules, formatScopes, formatSessionSnapshot, formatSessions, formatStackFrames, formatThreads, formatVariables, makeDebugOutput, presentDebugCall, projectSession, DEBUG_OUTPUT_SCHEMA, DEBUG_ACTIONS, } from "./render.js";
18
+ import { resolveToCwd, resolveCallCwd, sessionCwd } from "./session.js";
19
+ import { isDebugAction } from "./types.js";
20
+ /** Cordis plugin name for loader diagnostics. */
21
+ export const name = 'tool-debug';
22
+ /** Services required by this plugin. */
23
+ export const inject = ['tools', 'dap', 'systemPrompt'];
24
+ /** Default per-request timeout (seconds) when the model omits `timeout`. */
25
+ export const DEFAULT_DEBUG_REQUEST_TIMEOUT_SEC = 30;
26
+ /** Default whole-tool-call timeout budget (ms) for `dsh-tool-call-timeout-policy`. */
27
+ export const DEFAULT_DEBUG_TOOL_TIMEOUT_MS = 120_000;
28
+ /** The stable system-prompt guidance positioning the debug tool. */
29
+ export const DEBUG_PROMPT_TEXT = 'Use debug to attach a real debugger (gdb/lldb-dap/debugpy/dlv/...) to a process: launch or attach, set breakpoints, then continue/step, inspect threads/stack/scopes/variables, evaluate expressions, read memory, and terminate. Debug sessions are exclusive — terminate a session before launching another. Breakpoints must be set before continuing after a stop.';
30
+ const DEFAULT_MAX_RESULT_CHARS = 16_000;
31
+ export const Config = z.object({
32
+ maxResultChars: z.number().default(DEFAULT_MAX_RESULT_CHARS),
33
+ requestTimeoutSec: z.number().default(DEFAULT_DEBUG_REQUEST_TIMEOUT_SEC),
34
+ timeoutMs: z.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_DEBUG_TOOL_TIMEOUT_MS),
35
+ });
36
+ /**
37
+ * Register the `debug` tool and its system-prompt guidance.
38
+ * @param ctx - the plugin context (must inject `tools`, `dap`, `systemPrompt`).
39
+ * @param config - the resolved plugin configuration.
40
+ */
41
+ export function apply(ctx, config) {
42
+ const resolved = config;
43
+ assertPositiveInteger('maxResultChars', resolved.maxResultChars);
44
+ assertPositiveInteger('requestTimeoutSec', resolved.requestTimeoutSec);
45
+ assertTimer('timeoutMs', resolved.timeoutMs);
46
+ ctx.systemPrompt.section({ name: 'tool:debug', order: 128, text: DEBUG_PROMPT_TEXT });
47
+ ctx.tools.register(defineTool({
48
+ name: 'debug',
49
+ description: 'Attach a real debugger to a running or launched process through the Debug Adapter Protocol (DAP). 28 operations: launch, attach, set/remove_breakpoint (source or function), set/remove_instruction_breakpoint, data_breakpoint_info, set/remove_data_breakpoint, continue, step_over, step_in, step_out, pause, evaluate, stack_trace, threads, scopes, variables, disassemble, read_memory, write_memory, modules, loaded_sources, custom_request, output, terminate, sessions. One active session at a time — terminate before launching another.',
50
+ parameters: {
51
+ action: {
52
+ type: 'string',
53
+ required: true,
54
+ enum: [...DEBUG_ACTIONS],
55
+ description: 'The debug operation to perform.',
56
+ },
57
+ program: { type: 'string', description: 'Debug target path; Delve accepts Go package directories.' },
58
+ args: { type: 'array', items: { type: 'string' }, description: 'Program arguments for launch.' },
59
+ adapter: { type: 'string', description: 'Configured adapter id (gdb, lldb-dap, debugpy, dlv, ... or a dap.json entry).' },
60
+ cwd: { type: 'string', description: 'Call working directory; defaults to the session workspace.' },
61
+ file: { type: 'string', description: 'Source file (breakpoint operations).' },
62
+ line: { type: 'number', description: 'Source line (breakpoint operations).' },
63
+ function: { type: 'string', description: 'Function name (breakpoint operations).' },
64
+ name: { type: 'string', description: 'Variable or data name (data_breakpoint_info).' },
65
+ condition: { type: 'string', description: 'Breakpoint condition expression.' },
66
+ hit_condition: { type: 'string', description: 'Breakpoint hit count condition.' },
67
+ expression: { type: 'string', description: 'Expression to evaluate.' },
68
+ context: { type: 'string', enum: ['watch', 'repl', 'hover', 'variables', 'clipboard'], description: 'Evaluate context (default repl).' },
69
+ frame_id: { type: 'number', description: 'Stack frame id (scopes/evaluate).' },
70
+ scope_id: { type: 'number', description: 'Scope variables reference (variables).' },
71
+ variable_ref: { type: 'number', description: 'Variable reference (variables).' },
72
+ pid: { type: 'number', description: 'Process id for attach.' },
73
+ port: { type: 'number', description: 'Remote attach port.' },
74
+ host: { type: 'string', description: 'Remote attach host (default localhost).' },
75
+ levels: { type: 'number', description: 'Max stack frames for stack_trace.' },
76
+ memory_reference: { type: 'string', description: 'Memory reference or address.' },
77
+ instruction_reference: { type: 'string', description: 'Instruction reference for set_instruction_breakpoint.' },
78
+ instruction_count: { type: 'number', description: 'Instructions to disassemble.' },
79
+ instruction_offset: { type: 'number' },
80
+ count: { type: 'number', description: 'Bytes to read for read_memory.' },
81
+ data: { type: 'string', description: 'Base64 memory payload for write_memory.' },
82
+ data_id: { type: 'string', description: 'Data breakpoint id.' },
83
+ access_type: { type: 'string', enum: ['read', 'write', 'readWrite'], description: 'Data breakpoint access type.' },
84
+ command: { type: 'string', description: 'Custom DAP request command.' },
85
+ arguments: { type: 'object', additionalProperties: true, description: 'Custom request arguments.' },
86
+ offset: { type: 'number', description: 'Byte offset for memory operations.' },
87
+ resolve_symbols: { type: 'boolean' },
88
+ allow_partial: { type: 'boolean' },
89
+ start_module: { type: 'number' },
90
+ module_count: { type: 'number' },
91
+ timeout: { type: 'number', description: 'Per-request timeout in seconds (default 30).' },
92
+ },
93
+ output: {
94
+ schema: DEBUG_OUTPUT_SCHEMA,
95
+ render: (_args, value) => {
96
+ const output = value;
97
+ return [{ type: 'text', text: output.message ?? '(no output)' }];
98
+ },
99
+ },
100
+ timeoutMs: resolved.timeoutMs,
101
+ async execute(args, exec) {
102
+ const input = parseDebugArgs(args);
103
+ const workspace = sessionCwd(exec);
104
+ if (workspace === undefined) {
105
+ throw new Error('the debug tool requires a session workspace cwd');
106
+ }
107
+ const requestTimeoutMs = (input.timeout ?? resolved.requestTimeoutSec) * 1000;
108
+ const timeoutSignal = AbortSignal.timeout(requestTimeoutMs);
109
+ const combinedSignal = exec.signal
110
+ ? AbortSignal.any([exec.signal, timeoutSignal])
111
+ : timeoutSignal;
112
+ const dap = ctx.dap;
113
+ return dispatchDebugAction(dap, input, workspace, combinedSignal, requestTimeoutMs, resolved.maxResultChars);
114
+ },
115
+ presentCall: presentDebugCall,
116
+ }));
117
+ }
118
+ /**
119
+ * Validate and normalize raw model arguments into `DebugToolArgs`.
120
+ * @param args - the raw model arguments.
121
+ * @returns the normalized debug tool arguments.
122
+ */
123
+ export function parseDebugArgs(args) {
124
+ if (args === null || typeof args !== 'object') {
125
+ throw new Error('debug arguments must be an object');
126
+ }
127
+ const raw = args;
128
+ if (!isDebugAction(raw.action)) {
129
+ throw new Error(`action must be one of ${DEBUG_ACTIONS.join(', ')}`);
130
+ }
131
+ return { ...raw, action: raw.action };
132
+ }
133
+ /** Adapter-unavailable hints keyed by well-known adapter ids. */
134
+ const ADAPTER_UNAVAILABLE_MESSAGES = {
135
+ debugpy: "adapter 'debugpy' is not available: install with 'pip install debugpy'",
136
+ dlv: "adapter 'dlv' is not available: install with 'go install github.com/go-delve/delve/cmd/dlv@latest'",
137
+ rdbg: "adapter 'rdbg' is not available: install with 'gem install debug'",
138
+ 'js-debug-adapter': "adapter 'js-debug-adapter' is not available: download it from https://github.com/microsoft/vscode-js-debug",
139
+ };
140
+ async function requireAdapter(adapter, name, cwd, dap) {
141
+ if (adapter !== null)
142
+ return adapter;
143
+ const available = await dap.listAdapters(cwd);
144
+ const names = available.length > 0 ? available.join(', ') : 'none';
145
+ if (name) {
146
+ const hint = ADAPTER_UNAVAILABLE_MESSAGES[name];
147
+ throw new Error(hint ?? `adapter '${name}' is not available. Installed adapters: ${names}`);
148
+ }
149
+ throw new Error(`No debugger adapter available. Installed adapters: ${names}`);
150
+ }
151
+ function requireSession(dap) {
152
+ const snapshot = dap.getActiveSession();
153
+ if (!snapshot) {
154
+ throw new Error('No active debug session. Launch or attach first.');
155
+ }
156
+ return snapshot;
157
+ }
158
+ function requireCapability(dap, capability, description) {
159
+ requireSession(dap);
160
+ if (dap.getCapabilities()?.[capability] !== true) {
161
+ throw new Error(`Current adapter does not support ${description}`);
162
+ }
163
+ }
164
+ function resolveDisassemblyReference(dap, memoryReference) {
165
+ if (memoryReference)
166
+ return memoryReference;
167
+ const snapshot = requireSession(dap);
168
+ if (snapshot.instructionPointerReference) {
169
+ return snapshot.instructionPointerReference;
170
+ }
171
+ throw new Error('disassemble requires memory_reference unless the current stop location has an instruction pointer reference');
172
+ }
173
+ /** Validate breakpoint references used by instruction/data breakpoint ops. */
174
+ function requireBreakpointInput(kind, input) {
175
+ if (kind === 'instruction') {
176
+ const reference = input.instruction_reference;
177
+ if (!reference) {
178
+ throw new Error('instruction_reference is required for instruction breakpoint operations');
179
+ }
180
+ return reference;
181
+ }
182
+ const dataId = input.data_id;
183
+ if (!dataId) {
184
+ throw new Error('data_id is required for data breakpoint operations');
185
+ }
186
+ return dataId;
187
+ }
188
+ /** Project a session summary into the canonical plain session record. */
189
+ function acc(snapshot) {
190
+ return { session: projectSession(snapshot) };
191
+ }
192
+ async function dispatchDebugAction(dap, input, workspace, signal, requestTimeoutMs, maxResultChars) {
193
+ switch (input.action) {
194
+ case 'launch': {
195
+ if (!input.program)
196
+ throw new Error('program is required for launch');
197
+ const cwd = resolveCallCwd(input, workspace);
198
+ const program = resolveToCwd(input.program, cwd);
199
+ const programKind = await dap.classifyProgram(program, cwd);
200
+ const selection = await dap.selectLaunchAdapter(program, cwd, {
201
+ ...(input.adapter ? { adapter: input.adapter } : {}),
202
+ programKind,
203
+ signal,
204
+ });
205
+ if (selection.kind === 'unavailable') {
206
+ const hint = ADAPTER_UNAVAILABLE_MESSAGES[selection.adapterName];
207
+ throw new Error(hint ??
208
+ `adapter '${selection.adapterName}' is not available: configured command '${selection.command}' did not resolve. Check the DAP adapter config for this workspace.`);
209
+ }
210
+ if (selection.kind === 'none') {
211
+ const available = await dap.listAdapters(cwd);
212
+ throw new Error(`No debugger adapter available. Installed adapters: ${available.length > 0 ? available.join(', ') : 'none'}`);
213
+ }
214
+ const { adapter } = selection;
215
+ if (programKind === 'directory' && !adapter.acceptsDirectoryProgram) {
216
+ throw new Error(`launch program resolves to a directory: ${program}. Pass an executable file path or choose an adapter that supports package directories.`);
217
+ }
218
+ const extraLaunchArguments = dap.resolveLaunchOverrides(adapter, program, programKind);
219
+ const snapshot = await dap.launch({ adapter, program, ...(input.args ? { args: input.args } : {}), cwd, extraLaunchArguments }, signal, requestTimeoutMs);
220
+ const text = formatSessionSnapshot(snapshot).join('\n');
221
+ return makeDebugOutput('launch', text, acc(snapshot), maxResultChars);
222
+ }
223
+ case 'attach': {
224
+ if (input.pid === undefined && input.port === undefined) {
225
+ throw new Error('attach requires pid or port');
226
+ }
227
+ const cwd = resolveCallCwd(input, workspace);
228
+ const adapter = await requireAdapter(await dap.selectAttachAdapter(cwd, {
229
+ ...(input.adapter ? { adapter: input.adapter } : {}),
230
+ ...(input.port !== undefined ? { port: input.port } : {}),
231
+ signal,
232
+ }), input.adapter, cwd, dap);
233
+ const snapshot = await dap.attach({
234
+ adapter,
235
+ cwd,
236
+ ...(input.pid !== undefined ? { pid: input.pid } : {}),
237
+ ...(input.port !== undefined ? { port: input.port } : {}),
238
+ ...(input.host ? { host: input.host } : {}),
239
+ }, signal, requestTimeoutMs);
240
+ const text = formatSessionSnapshot(snapshot).join('\n');
241
+ return makeDebugOutput('attach', text, acc(snapshot), maxResultChars);
242
+ }
243
+ case 'set_breakpoint': {
244
+ if (input.function) {
245
+ const response = await dap.setFunctionBreakpoint(input.function, input.condition, signal, requestTimeoutMs);
246
+ const text = formatFunctionBreakpoints(response.breakpoints);
247
+ return makeDebugOutput('set_breakpoint', text, acc(response.snapshot), maxResultChars);
248
+ }
249
+ if (!input.file || input.line === undefined) {
250
+ throw new Error('set_breakpoint requires file+line or function');
251
+ }
252
+ const file = resolveToCwd(input.file, workspace);
253
+ const response = await dap.setBreakpoint(file, input.line, input.condition, signal, requestTimeoutMs);
254
+ const text = formatBreakpoints(file, response.breakpoints);
255
+ return makeDebugOutput('set_breakpoint', text, acc(response.snapshot), maxResultChars);
256
+ }
257
+ case 'remove_breakpoint': {
258
+ if (input.function) {
259
+ const response = await dap.removeFunctionBreakpoint(input.function, signal, requestTimeoutMs);
260
+ const text = formatFunctionBreakpoints(response.breakpoints);
261
+ return makeDebugOutput('remove_breakpoint', text, acc(response.snapshot), maxResultChars);
262
+ }
263
+ if (!input.file || input.line === undefined) {
264
+ throw new Error('remove_breakpoint requires file+line or function');
265
+ }
266
+ const file = resolveToCwd(input.file, workspace);
267
+ const response = await dap.removeBreakpoint(file, input.line, signal, requestTimeoutMs);
268
+ const text = formatBreakpoints(file, response.breakpoints);
269
+ return makeDebugOutput('remove_breakpoint', text, acc(response.snapshot), maxResultChars);
270
+ }
271
+ case 'set_instruction_breakpoint': {
272
+ requireCapability(dap, 'supportsInstructionBreakpoints', 'instruction breakpoints');
273
+ const instructionReference = requireBreakpointInput('instruction', input);
274
+ const response = await dap.setInstructionBreakpoint(instructionReference, input.offset, input.condition, input.hit_condition, signal, requestTimeoutMs);
275
+ const text = formatInstructionBreakpoints(response.breakpoints);
276
+ return makeDebugOutput('set_instruction_breakpoint', text, acc(response.snapshot), maxResultChars);
277
+ }
278
+ case 'remove_instruction_breakpoint': {
279
+ requireCapability(dap, 'supportsInstructionBreakpoints', 'instruction breakpoints');
280
+ const instructionReference = requireBreakpointInput('instruction', input);
281
+ const response = await dap.removeInstructionBreakpoint(instructionReference, input.offset, signal, requestTimeoutMs);
282
+ const text = formatInstructionBreakpoints(response.breakpoints);
283
+ return makeDebugOutput('remove_instruction_breakpoint', text, acc(response.snapshot), maxResultChars);
284
+ }
285
+ case 'data_breakpoint_info': {
286
+ requireCapability(dap, 'supportsDataBreakpoints', 'data breakpoints');
287
+ if (!input.name)
288
+ throw new Error('name is required for data_breakpoint_info');
289
+ const response = await dap.dataBreakpointInfo(input.name, input.variable_ref ?? input.scope_id, input.frame_id, signal, requestTimeoutMs);
290
+ const text = formatDataBreakpointInfo(response.info);
291
+ return makeDebugOutput('data_breakpoint_info', text, acc(response.snapshot), maxResultChars);
292
+ }
293
+ case 'set_data_breakpoint': {
294
+ requireCapability(dap, 'supportsDataBreakpoints', 'data breakpoints');
295
+ const dataId = requireBreakpointInput('data-id', input);
296
+ const response = await dap.setDataBreakpoint(dataId, input.access_type, input.condition, input.hit_condition, signal, requestTimeoutMs);
297
+ const text = formatDataBreakpoints(response.breakpoints);
298
+ return makeDebugOutput('set_data_breakpoint', text, acc(response.snapshot), maxResultChars);
299
+ }
300
+ case 'remove_data_breakpoint': {
301
+ requireCapability(dap, 'supportsDataBreakpoints', 'data breakpoints');
302
+ const dataId = requireBreakpointInput('data-id', input);
303
+ const response = await dap.removeDataBreakpoint(dataId, signal, requestTimeoutMs);
304
+ const text = formatDataBreakpoints(response.breakpoints);
305
+ return makeDebugOutput('remove_data_breakpoint', text, acc(response.snapshot), maxResultChars);
306
+ }
307
+ case 'continue':
308
+ case 'step_over':
309
+ case 'step_in':
310
+ case 'step_out': {
311
+ // Call through the service object (never a detached reference, which
312
+ // would lose `this` on the prototype-backed delegating service).
313
+ const outcome = input.action === 'continue'
314
+ ? await dap.continue(signal, requestTimeoutMs)
315
+ : input.action === 'step_over'
316
+ ? await dap.stepOver(signal, requestTimeoutMs)
317
+ : input.action === 'step_in'
318
+ ? await dap.stepIn(signal, requestTimeoutMs)
319
+ : await dap.stepOut(signal, requestTimeoutMs);
320
+ const verb = input.action === 'continue'
321
+ ? 'Continue'
322
+ : input.action === 'step_over'
323
+ ? 'Step over'
324
+ : input.action === 'step_in'
325
+ ? 'Step in'
326
+ : 'Step out';
327
+ const lines = formatSessionSnapshot(outcome.snapshot);
328
+ if (outcome.timedOut) {
329
+ lines.push(`Program is still running after ${Math.round(requestTimeoutMs / 1000)}s. Use pause to interrupt and inspect state.`);
330
+ }
331
+ else if (outcome.state === 'stopped') {
332
+ const location = outcome.snapshot.source?.path && outcome.snapshot.line !== undefined
333
+ ? `${outcome.snapshot.source.path}:${outcome.snapshot.line}`
334
+ : 'unknown location';
335
+ lines.push(`${verb} stopped at ${location}.`);
336
+ }
337
+ else if (outcome.state === 'terminated') {
338
+ lines.push(`Program terminated${outcome.snapshot.exitCode !== undefined ? ` with exit code ${outcome.snapshot.exitCode}` : ''}.`);
339
+ }
340
+ else {
341
+ lines.push('Program is running.');
342
+ }
343
+ const text = lines.join('\n');
344
+ return makeDebugOutput(input.action, text, { ...acc(outcome.snapshot), state: outcome.state, timedOut: outcome.timedOut }, maxResultChars);
345
+ }
346
+ case 'pause': {
347
+ const snapshot = await dap.pause(signal, requestTimeoutMs);
348
+ const text = [...formatSessionSnapshot(snapshot), 'Program paused.'].join('\n');
349
+ return makeDebugOutput('pause', text, acc(snapshot), maxResultChars);
350
+ }
351
+ case 'evaluate': {
352
+ if (!input.expression)
353
+ throw new Error('expression is required for evaluate');
354
+ const evaluationContext = input.context ?? 'repl';
355
+ const response = await dap.evaluate(input.expression, evaluationContext, input.frame_id, signal, requestTimeoutMs);
356
+ const text = formatEvaluation(response.evaluation ?? { result: '', variablesReference: 0 });
357
+ return makeDebugOutput('evaluate', text, acc(response.snapshot), maxResultChars);
358
+ }
359
+ case 'stack_trace': {
360
+ const response = await dap.stackTrace(input.levels, signal, requestTimeoutMs);
361
+ const text = formatStackFrames(response.stackFrames);
362
+ return makeDebugOutput('stack_trace', text, acc(response.snapshot), maxResultChars);
363
+ }
364
+ case 'threads': {
365
+ const response = await dap.threads(signal, requestTimeoutMs);
366
+ const text = formatThreads(response.threads);
367
+ return makeDebugOutput('threads', text, acc(response.snapshot), maxResultChars);
368
+ }
369
+ case 'scopes': {
370
+ const response = await dap.scopes(input.frame_id, signal, requestTimeoutMs);
371
+ const text = formatScopes(response.scopes);
372
+ return makeDebugOutput('scopes', text, acc(response.snapshot), maxResultChars);
373
+ }
374
+ case 'variables': {
375
+ const variableReference = input.variable_ref ?? input.scope_id;
376
+ if (variableReference === undefined) {
377
+ throw new Error('variables requires variable_ref or scope_id');
378
+ }
379
+ const response = await dap.variables(variableReference, signal, requestTimeoutMs);
380
+ const text = formatVariables(response.variables);
381
+ return makeDebugOutput('variables', text, acc(response.snapshot), maxResultChars);
382
+ }
383
+ case 'disassemble': {
384
+ requireCapability(dap, 'supportsDisassembleRequest', 'disassembly');
385
+ if (input.instruction_count === undefined)
386
+ throw new Error('instruction_count is required for disassemble');
387
+ const response = await dap.disassemble(resolveDisassemblyReference(dap, input.memory_reference), input.instruction_count, input.offset, input.instruction_offset, input.resolve_symbols, signal, requestTimeoutMs);
388
+ const text = formatDisassembly(response.instructions);
389
+ return makeDebugOutput('disassemble', text, acc(response.snapshot), maxResultChars);
390
+ }
391
+ case 'read_memory': {
392
+ requireCapability(dap, 'supportsReadMemoryRequest', 'memory reads');
393
+ if (!input.memory_reference)
394
+ throw new Error('memory_reference is required for read_memory');
395
+ if (input.count === undefined)
396
+ throw new Error('count is required for read_memory');
397
+ const response = await dap.readMemory(input.memory_reference, input.count, input.offset, signal, requestTimeoutMs);
398
+ const text = formatMemoryRead(response.address, response.data, response.unreadableBytes);
399
+ return makeDebugOutput('read_memory', text, acc(response.snapshot), maxResultChars);
400
+ }
401
+ case 'write_memory': {
402
+ requireCapability(dap, 'supportsWriteMemoryRequest', 'memory writes');
403
+ if (!input.memory_reference)
404
+ throw new Error('memory_reference is required for write_memory');
405
+ if (!input.data)
406
+ throw new Error('data is required for write_memory');
407
+ const response = await dap.writeMemory(input.memory_reference, input.data, input.offset, input.allow_partial, signal, requestTimeoutMs);
408
+ const text = [
409
+ 'Memory write completed.',
410
+ ...(response.bytesWritten !== undefined ? [`Bytes written: ${response.bytesWritten}`] : []),
411
+ ...(response.offset !== undefined ? [`Offset: ${response.offset}`] : []),
412
+ ].join('\n');
413
+ return makeDebugOutput('write_memory', text, acc(response.snapshot), maxResultChars);
414
+ }
415
+ case 'modules': {
416
+ requireCapability(dap, 'supportsModulesRequest', 'module introspection');
417
+ const response = await dap.modules(input.start_module, input.module_count, signal, requestTimeoutMs);
418
+ const text = formatModules(response.modules);
419
+ return makeDebugOutput('modules', text, acc(response.snapshot), maxResultChars);
420
+ }
421
+ case 'loaded_sources': {
422
+ requireCapability(dap, 'supportsLoadedSourcesRequest', 'loaded sources');
423
+ const response = await dap.loadedSources(signal, requestTimeoutMs);
424
+ const text = formatLoadedSources(response.sources);
425
+ return makeDebugOutput('loaded_sources', text, acc(response.snapshot), maxResultChars);
426
+ }
427
+ case 'custom_request': {
428
+ if (!input.command)
429
+ throw new Error('command is required for custom_request');
430
+ const response = await dap.customRequest(input.command, input.arguments, signal, requestTimeoutMs);
431
+ const text = formatCustomResponse(input.command, response.body);
432
+ return makeDebugOutput('custom_request', text, acc(response.snapshot), maxResultChars);
433
+ }
434
+ case 'output': {
435
+ const response = dap.getOutput();
436
+ const text = response.output.length > 0 ? response.output : '(no output captured)';
437
+ return makeDebugOutput('output', text, { ...acc(response.snapshot), output: response.output }, maxResultChars);
438
+ }
439
+ case 'terminate': {
440
+ const snapshot = await dap.terminate(signal, requestTimeoutMs);
441
+ if (!snapshot) {
442
+ return makeDebugOutput('terminate', 'No debug session to terminate.', {}, maxResultChars);
443
+ }
444
+ const text = [...formatSessionSnapshot(snapshot), 'Debug session terminated.'].join('\n');
445
+ return makeDebugOutput('terminate', text, acc(snapshot), maxResultChars);
446
+ }
447
+ case 'sessions': {
448
+ const sessions = dap.listSessions();
449
+ const text = formatSessions(sessions);
450
+ return makeDebugOutput('sessions', text, { sessions: sessions.map(projectSession) }, maxResultChars);
451
+ }
452
+ default: {
453
+ // exhaustive over the union; unreachable after parseDebugArgs validation
454
+ throw new Error(`Unsupported debug action: ${String(input.action)}`);
455
+ }
456
+ }
457
+ }
458
+ /** Reject a non-positive-integer config value at load, so misconfiguration fails loud. */
459
+ function assertPositiveInteger(name, value) {
460
+ if (!Number.isInteger(value) || value < 1) {
461
+ throw new Error(`tool-debug: ${name} must be a positive integer`);
462
+ }
463
+ }
464
+ /** Reject a timer value Node would clamp instead of scheduling as configured. */
465
+ function assertTimer(name, value) {
466
+ if (!Number.isInteger(value) || value < 1 || value > MAX_TIMER_DELAY_MS) {
467
+ throw new Error(`tool-debug: ${name} must be a positive integer no greater than ${MAX_TIMER_DELAY_MS}`);
468
+ }
469
+ }
470
+ // Internal helper kept exported for tests and derived packages.
471
+ export { ADAPTER_UNAVAILABLE_MESSAGES };
472
+ export { DEBUG_ACTIONS, DEBUG_OUTPUT_SCHEMA, formatBreakpoints, formatCustomResponse, formatDataBreakpointInfo, formatDataBreakpoints, formatDisassembly, formatEvaluation, formatFunctionBreakpoints, formatInstructionBreakpoints, formatLoadedSources, formatMemoryRead, formatModules, formatScopes, formatSessionSnapshot, formatSessions, formatStackFrames, formatThreads, formatVariables, makeDebugOutput, presentDebugCall, summarizeDebugCall, } from "./render.js";
473
+ //# sourceMappingURL=index.js.map