@i-scope/mcp-server 0.4.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/CHANGELOG.md +147 -0
- package/LICENSE +21 -0
- package/README.md +373 -0
- package/dist/src/abi-check.d.ts +19 -0
- package/dist/src/abi-check.js +66 -0
- package/dist/src/bridge-driver.d.ts +90 -0
- package/dist/src/bridge-driver.js +290 -0
- package/dist/src/dap-client.d.ts +80 -0
- package/dist/src/dap-client.js +296 -0
- package/dist/src/dap-driver.d.ts +162 -0
- package/dist/src/dap-driver.js +703 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +175 -0
- package/dist/src/state.d.ts +86 -0
- package/dist/src/state.js +15 -0
- package/dist/src/tools/abi-check.d.ts +3 -0
- package/dist/src/tools/abi-check.js +64 -0
- package/dist/src/tools/breakpoints.d.ts +3 -0
- package/dist/src/tools/breakpoints.js +56 -0
- package/dist/src/tools/execution.d.ts +3 -0
- package/dist/src/tools/execution.js +75 -0
- package/dist/src/tools/helpers.d.ts +27 -0
- package/dist/src/tools/helpers.js +134 -0
- package/dist/src/tools/inspection.d.ts +3 -0
- package/dist/src/tools/inspection.js +141 -0
- package/dist/src/tools/lifecycle.d.ts +3 -0
- package/dist/src/tools/lifecycle.js +103 -0
- package/dist/src/tools/preflight.d.ts +3 -0
- package/dist/src/tools/preflight.js +95 -0
- package/dist/src/tools/registry.d.ts +15 -0
- package/dist/src/tools/registry.js +19 -0
- package/dist/src/tools/snapshot.d.ts +3 -0
- package/dist/src/tools/snapshot.js +117 -0
- package/dist/src/tools/source-maps.d.ts +3 -0
- package/dist/src/tools/source-maps.js +232 -0
- package/dist/src/tools/sync.d.ts +3 -0
- package/dist/src/tools/sync.js +80 -0
- package/dist/src/tools/ui-modal.d.ts +3 -0
- package/dist/src/tools/ui-modal.js +182 -0
- package/package.json +73 -0
- package/src/abi-check.ts +97 -0
- package/src/bridge-driver.ts +328 -0
- package/src/dap-client.ts +336 -0
- package/src/dap-driver.ts +810 -0
- package/src/index.ts +155 -0
- package/src/state.ts +115 -0
- package/src/tools/abi-check.ts +66 -0
- package/src/tools/breakpoints.ts +59 -0
- package/src/tools/execution.ts +105 -0
- package/src/tools/helpers.ts +142 -0
- package/src/tools/inspection.ts +173 -0
- package/src/tools/lifecycle.ts +129 -0
- package/src/tools/preflight.ts +95 -0
- package/src/tools/registry.ts +34 -0
- package/src/tools/snapshot.ts +132 -0
- package/src/tools/source-maps.ts +222 -0
- package/src/tools/sync.ts +90 -0
- package/src/tools/ui-modal.ts +201 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Minimal stand-alone Debug Adapter Protocol client.
|
|
3
|
+
//
|
|
4
|
+
// Why we don't reuse @vscode/debugadapter-testsupport:
|
|
5
|
+
// That package is Microsoft's own test harness — fine for the
|
|
6
|
+
// adapter author, BUT (a) the name screams "test only" and scares
|
|
7
|
+
// anyone reading our published package.json, and (b) it's a
|
|
8
|
+
// *testsupport* artefact: Microsoft do not promise API stability
|
|
9
|
+
// across minor versions. A breaking change in a 1.69.x → 1.70.x
|
|
10
|
+
// bump would silently break every npm install of our MCP server.
|
|
11
|
+
//
|
|
12
|
+
// The wire is just JSON-over-stdio with a `Content-Length: N\r\n
|
|
13
|
+
// \r\n<body>` framing — too small to justify a runtime dependency
|
|
14
|
+
// for a public package. Hence: ~250 LOC of our own, tailored to
|
|
15
|
+
// the surface DapDriver actually needs.
|
|
16
|
+
//
|
|
17
|
+
// Surface (intentionally narrower than DebugClient's):
|
|
18
|
+
// - `start()` / `stop()` lifecycle.
|
|
19
|
+
// - `request<T>(command, args?, timeoutMs?)` → resolves with
|
|
20
|
+
// `response.body` (or rejects with the response.message).
|
|
21
|
+
// - `waitForEvent<T>(name, timeoutMs?)` → resolves with the next
|
|
22
|
+
// event of that name.
|
|
23
|
+
// - EventEmitter semantics for unsolicited events (`output`,
|
|
24
|
+
// `stopped`, `continued`, `terminated`, `exited`, ...) — callers
|
|
25
|
+
// attach `.on(name, handler)` like they would on any Node
|
|
26
|
+
// EventEmitter.
|
|
27
|
+
//
|
|
28
|
+
// Out of scope (versus DebugClient):
|
|
29
|
+
// - Convenience wrappers like `initializeRequest(args)` —
|
|
30
|
+
// `dc.request('initialize', args)` is enough.
|
|
31
|
+
// - Reverse requests (the server's `request` direction, e.g.
|
|
32
|
+
// `runInTerminal`). The iScope adapter never issues those.
|
|
33
|
+
// - `hitBreakpoint()` / `assertStoppedLocation()` test helpers.
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.DapClient = void 0;
|
|
36
|
+
const node_child_process_1 = require("node:child_process");
|
|
37
|
+
const node_events_1 = require("node:events");
|
|
38
|
+
const NO_LOG = (_) => { };
|
|
39
|
+
/**
|
|
40
|
+
* Stand-alone DAP client. Inherits from EventEmitter for event
|
|
41
|
+
* subscription. Type-safe `request<T>` and `waitForEvent<T>` give
|
|
42
|
+
* callers the response body / event payload directly.
|
|
43
|
+
*/
|
|
44
|
+
class DapClient extends node_events_1.EventEmitter {
|
|
45
|
+
runtime;
|
|
46
|
+
executable;
|
|
47
|
+
args;
|
|
48
|
+
env;
|
|
49
|
+
cwd;
|
|
50
|
+
inheritStderr;
|
|
51
|
+
log;
|
|
52
|
+
child = null;
|
|
53
|
+
childExited = false;
|
|
54
|
+
/** Monotonic outgoing message id. DAP spec: `seq` is per-sender
|
|
55
|
+
* and increases by one per message; the OTHER side has its own
|
|
56
|
+
* `seq` axis we ignore. */
|
|
57
|
+
nextSeq = 1;
|
|
58
|
+
/** Accumulating stdout buffer. We pull complete frames out of
|
|
59
|
+
* it as they arrive. Type widened to ArrayBufferLike to match
|
|
60
|
+
* what `Buffer.concat` and `Buffer#subarray` return in
|
|
61
|
+
* @types/node 22+. */
|
|
62
|
+
rxBuffer = Buffer.alloc(0);
|
|
63
|
+
/** Once we see `Content-Length: N\r\n\r\n` we remember N here
|
|
64
|
+
* until we've consumed exactly N bytes of body. -1 = "still
|
|
65
|
+
* parsing the header". */
|
|
66
|
+
rxContentLength = -1;
|
|
67
|
+
/** seq → outstanding request. We never await a response by
|
|
68
|
+
* `command` alone — two simultaneous `stackTrace`s would
|
|
69
|
+
* collide. `seq` is the only safe key. */
|
|
70
|
+
pending = new Map();
|
|
71
|
+
defaultTimeoutMs;
|
|
72
|
+
constructor(opts) {
|
|
73
|
+
super();
|
|
74
|
+
this.runtime = opts.runtime ?? 'node';
|
|
75
|
+
this.executable = opts.executable;
|
|
76
|
+
this.args = opts.args ?? [];
|
|
77
|
+
this.env = opts.env ?? process.env;
|
|
78
|
+
this.cwd = opts.cwd;
|
|
79
|
+
this.inheritStderr = opts.inheritStderr ?? true;
|
|
80
|
+
this.log = opts.log ?? NO_LOG;
|
|
81
|
+
this.defaultTimeoutMs = opts.defaultTimeoutMs ?? 30_000;
|
|
82
|
+
}
|
|
83
|
+
/** Spawn the adapter child. Returns once `spawn()` has been
|
|
84
|
+
* called — the child may still be initialising. Callers
|
|
85
|
+
* immediately issue `request('initialize', ...)` and the DAP
|
|
86
|
+
* request/response framing handles the rest. */
|
|
87
|
+
async start() {
|
|
88
|
+
if (this.child) {
|
|
89
|
+
throw new Error('DapClient.start: already started');
|
|
90
|
+
}
|
|
91
|
+
const argv = [this.executable, ...this.args];
|
|
92
|
+
const child = (0, node_child_process_1.spawn)(this.runtime, argv, {
|
|
93
|
+
env: this.env,
|
|
94
|
+
cwd: this.cwd,
|
|
95
|
+
stdio: ['pipe', 'pipe', this.inheritStderr ? 'inherit' : 'pipe'],
|
|
96
|
+
windowsHide: true,
|
|
97
|
+
});
|
|
98
|
+
this.child = child;
|
|
99
|
+
this.childExited = false;
|
|
100
|
+
// Stdout is the DAP wire. Stderr (if piped) is diagnostic
|
|
101
|
+
// chatter — we forward it via `log` so it doesn't poison the
|
|
102
|
+
// wire by accident.
|
|
103
|
+
const stdout = child.stdout;
|
|
104
|
+
const stdin = child.stdin;
|
|
105
|
+
if (!stdout || !stdin) {
|
|
106
|
+
throw new Error('DapClient.start: child has no stdio pipes');
|
|
107
|
+
}
|
|
108
|
+
stdout.on('data', (chunk) => this.onStdoutChunk(chunk));
|
|
109
|
+
stdout.on('error', (e) => this.log(`[dap-client] stdout error: ${e.message}`));
|
|
110
|
+
stdin.on('error', (e) => this.log(`[dap-client] stdin error: ${e.message}`));
|
|
111
|
+
if (!this.inheritStderr && child.stderr) {
|
|
112
|
+
child.stderr.on('data', (chunk) => {
|
|
113
|
+
this.log(`[dap-child stderr] ${chunk.toString('utf8').replace(/\n$/, '')}`);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
child.on('exit', (code, signal) => {
|
|
117
|
+
this.childExited = true;
|
|
118
|
+
this.log(`[dap-client] child exited code=${code} signal=${signal ?? '-'}`);
|
|
119
|
+
// Fail any in-flight requests. Without this they would
|
|
120
|
+
// hang on the timeout — bad UX when the child crashed
|
|
121
|
+
// 200 ms ago.
|
|
122
|
+
const reason = `child process exited (code=${code} signal=${signal ?? '-'})`;
|
|
123
|
+
for (const p of this.pending.values()) {
|
|
124
|
+
clearTimeout(p.timer);
|
|
125
|
+
p.reject(new Error(`DAP request '${p.command}' failed: ${reason}`));
|
|
126
|
+
}
|
|
127
|
+
this.pending.clear();
|
|
128
|
+
this.emit('exited', { code, signal });
|
|
129
|
+
});
|
|
130
|
+
child.on('error', (err) => {
|
|
131
|
+
this.log(`[dap-client] spawn error: ${err.message}`);
|
|
132
|
+
this.emit('error', err);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** Send a request and resolve with `response.body`. Rejects on
|
|
136
|
+
* - timeout (after `timeoutMs ?? this.defaultTimeoutMs`),
|
|
137
|
+
* - protocol-level failure (`response.success === false`),
|
|
138
|
+
* - child process exit (the exit handler nukes pending). */
|
|
139
|
+
request(command, args, timeoutMs) {
|
|
140
|
+
if (!this.child || this.childExited || !this.child.stdin) {
|
|
141
|
+
return Promise.reject(new Error(`DAP request '${command}': no live child`));
|
|
142
|
+
}
|
|
143
|
+
const seq = this.nextSeq++;
|
|
144
|
+
const message = { seq, type: 'request', command, arguments: args };
|
|
145
|
+
const json = JSON.stringify(message);
|
|
146
|
+
const header = `Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n`;
|
|
147
|
+
try {
|
|
148
|
+
this.child.stdin.write(header + json, 'utf8');
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
152
|
+
return Promise.reject(new Error(`DAP request '${command}' write failed: ${msg}`));
|
|
153
|
+
}
|
|
154
|
+
const wait = timeoutMs ?? this.defaultTimeoutMs;
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
const timer = setTimeout(() => {
|
|
157
|
+
this.pending.delete(seq);
|
|
158
|
+
reject(new Error(`DAP request '${command}' timed out after ${wait}ms`));
|
|
159
|
+
}, wait);
|
|
160
|
+
this.pending.set(seq, {
|
|
161
|
+
resolve: (body) => resolve(body),
|
|
162
|
+
reject,
|
|
163
|
+
timer,
|
|
164
|
+
command,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/** Resolve with the next event of the given name. Honours a
|
|
169
|
+
* timeout to avoid hanging on tests that never fire the event
|
|
170
|
+
* they expect. */
|
|
171
|
+
waitForEvent(name, timeoutMs) {
|
|
172
|
+
const wait = timeoutMs ?? this.defaultTimeoutMs;
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
const handler = (ev) => {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
resolve(ev);
|
|
177
|
+
};
|
|
178
|
+
const timer = setTimeout(() => {
|
|
179
|
+
this.off(name, handler);
|
|
180
|
+
reject(new Error(`DAP event '${name}' did not arrive within ${wait}ms`));
|
|
181
|
+
}, wait);
|
|
182
|
+
this.once(name, handler);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/** Tear the child down. Graceful path: close stdin → child sees
|
|
186
|
+
* EOF → exits → we resolve. Fallback path: after `gracefulMs`,
|
|
187
|
+
* SIGTERM; after another second, SIGKILL. Either way the
|
|
188
|
+
* Promise always resolves; we never throw from cleanup. */
|
|
189
|
+
async stop(gracefulMs = 2_000) {
|
|
190
|
+
const child = this.child;
|
|
191
|
+
if (!child)
|
|
192
|
+
return;
|
|
193
|
+
if (this.childExited) {
|
|
194
|
+
this.child = null;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
child.stdin?.end();
|
|
199
|
+
}
|
|
200
|
+
catch { /* already closed */ }
|
|
201
|
+
await new Promise((resolve) => {
|
|
202
|
+
const onExit = () => {
|
|
203
|
+
clearTimeout(termTimer);
|
|
204
|
+
clearTimeout(killTimer);
|
|
205
|
+
resolve();
|
|
206
|
+
};
|
|
207
|
+
const termTimer = setTimeout(() => {
|
|
208
|
+
try {
|
|
209
|
+
child.kill('SIGTERM');
|
|
210
|
+
}
|
|
211
|
+
catch { /* ignore */ }
|
|
212
|
+
}, gracefulMs);
|
|
213
|
+
const killTimer = setTimeout(() => {
|
|
214
|
+
try {
|
|
215
|
+
child.kill('SIGKILL');
|
|
216
|
+
}
|
|
217
|
+
catch { /* ignore */ }
|
|
218
|
+
// Whether or not SIGKILL got through, give Node one
|
|
219
|
+
// more tick to fire 'exit'; otherwise resolve anyway.
|
|
220
|
+
setTimeout(resolve, 100);
|
|
221
|
+
}, gracefulMs + 1_000);
|
|
222
|
+
child.once('exit', onExit);
|
|
223
|
+
});
|
|
224
|
+
this.child = null;
|
|
225
|
+
}
|
|
226
|
+
// ---- internals --------------------------------------------------
|
|
227
|
+
onStdoutChunk(chunk) {
|
|
228
|
+
this.rxBuffer = this.rxBuffer.length === 0
|
|
229
|
+
? chunk
|
|
230
|
+
: Buffer.concat([this.rxBuffer, chunk]);
|
|
231
|
+
// Drain as many complete frames as fit in the buffer right
|
|
232
|
+
// now. One stdout chunk can carry many frames (e.g. a flood
|
|
233
|
+
// of output events after launch); equally one frame can
|
|
234
|
+
// arrive in several chunks (when the adapter writes a big
|
|
235
|
+
// variables[] body).
|
|
236
|
+
while (true) {
|
|
237
|
+
if (this.rxContentLength < 0) {
|
|
238
|
+
const headerEnd = this.rxBuffer.indexOf('\r\n\r\n');
|
|
239
|
+
if (headerEnd < 0)
|
|
240
|
+
break;
|
|
241
|
+
const headers = this.rxBuffer.subarray(0, headerEnd).toString('ascii');
|
|
242
|
+
let length = -1;
|
|
243
|
+
for (const line of headers.split('\r\n')) {
|
|
244
|
+
const m = /^Content-Length:\s*(\d+)$/i.exec(line);
|
|
245
|
+
if (m)
|
|
246
|
+
length = parseInt(m[1], 10);
|
|
247
|
+
}
|
|
248
|
+
if (length < 0) {
|
|
249
|
+
this.log(`[dap-client] missing Content-Length in headers; dropping ${headerEnd + 4} bytes and resyncing`);
|
|
250
|
+
this.rxBuffer = this.rxBuffer.subarray(headerEnd + 4);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
this.rxContentLength = length;
|
|
254
|
+
this.rxBuffer = this.rxBuffer.subarray(headerEnd + 4);
|
|
255
|
+
}
|
|
256
|
+
if (this.rxBuffer.length < this.rxContentLength)
|
|
257
|
+
break;
|
|
258
|
+
const body = this.rxBuffer.subarray(0, this.rxContentLength).toString('utf8');
|
|
259
|
+
this.rxBuffer = this.rxBuffer.subarray(this.rxContentLength);
|
|
260
|
+
this.rxContentLength = -1;
|
|
261
|
+
try {
|
|
262
|
+
this.dispatch(JSON.parse(body));
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
266
|
+
this.log(`[dap-client] failed to parse incoming message (${body.length}B): ${msg}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
dispatch(msg) {
|
|
271
|
+
if (msg.type === 'response') {
|
|
272
|
+
const seq = msg.request_seq ?? -1;
|
|
273
|
+
const p = this.pending.get(seq);
|
|
274
|
+
if (!p)
|
|
275
|
+
return;
|
|
276
|
+
this.pending.delete(seq);
|
|
277
|
+
clearTimeout(p.timer);
|
|
278
|
+
if (msg.success === false) {
|
|
279
|
+
p.reject(new Error(msg.message || `DAP request '${p.command}' failed`));
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
p.resolve(msg.body);
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (msg.type === 'event' && typeof msg.event === 'string') {
|
|
287
|
+
this.emit(msg.event, msg);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// type='request' from the server (e.g. runInTerminal) is the
|
|
291
|
+
// only other DAP message kind. The iScope adapter never sends
|
|
292
|
+
// those; ignore.
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
exports.DapClient = DapClient;
|
|
296
|
+
//# sourceMappingURL=dap-client.js.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { DebugProtocol } from '@vscode/debugprotocol';
|
|
2
|
+
import { DapClient } from './dap-client.js';
|
|
3
|
+
import type { FrameInfo, OutputEntry, SessionState, StoppedInfo, ExecutionTransition, TerminationInfo } from './state.js';
|
|
4
|
+
export interface DapDriverOptions {
|
|
5
|
+
/** Optional bundled-root override forwarded to the spawned DAP
|
|
6
|
+
* server as `ISCOPE_EXTENSION_PATH`. Useful only when the caller
|
|
7
|
+
* ships its own copy of `iScopeBridge.exe` and wants it to win
|
|
8
|
+
* over the npm-installed `@i-scope/iscope-bridge`. Leave undefined
|
|
9
|
+
* for the standard "everything via npm" deployment — the
|
|
10
|
+
* bridge-client will resolve the binary from the installed
|
|
11
|
+
* `@i-scope/iscope-bridge` package automatically. */
|
|
12
|
+
extensionPath?: string;
|
|
13
|
+
/** Default per-DAP-request timeout. Defaults to 30 000 ms. */
|
|
14
|
+
defaultTimeoutMs?: number;
|
|
15
|
+
/** Maximum buffered Host.ReportOut + console entries before the
|
|
16
|
+
* oldest get dropped. Defaults to 2000. */
|
|
17
|
+
outputBufferSize?: number;
|
|
18
|
+
/** Optional log function for MCP-server-side diagnostics. Writes
|
|
19
|
+
* go to stderr by default (stdout is reserved for the MCP wire).
|
|
20
|
+
* Use this if you want to redirect to a file. */
|
|
21
|
+
log?: (line: string) => void;
|
|
22
|
+
}
|
|
23
|
+
export interface LaunchToolArgs {
|
|
24
|
+
program: string;
|
|
25
|
+
mwfFile?: string;
|
|
26
|
+
autoLaunch?: boolean;
|
|
27
|
+
openFileBeforeRun?: boolean | null;
|
|
28
|
+
stopOnEntry?: boolean | 'auto';
|
|
29
|
+
oscilloscopePath?: string;
|
|
30
|
+
helperPath?: string;
|
|
31
|
+
outFiles?: string[];
|
|
32
|
+
/** Override per-request timeout for this `launch` (covers the
|
|
33
|
+
* initial spawn + initialize round-trip). */
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
}
|
|
36
|
+
/** Promise-chain mutex. Each `runExclusive(fn)` waits for the prior
|
|
37
|
+
* one to settle, then runs `fn`. No re-entrancy — if a tool handler
|
|
38
|
+
* re-enters runExclusive from inside its own callback it WILL
|
|
39
|
+
* deadlock. (Tools are designed to never do that.)
|
|
40
|
+
*
|
|
41
|
+
* Exported for unit testing. The MCP server itself doesn't need to
|
|
42
|
+
* re-export it.
|
|
43
|
+
*/
|
|
44
|
+
export declare class Mutex {
|
|
45
|
+
private tail;
|
|
46
|
+
runExclusive<T>(fn: () => Promise<T>): Promise<T>;
|
|
47
|
+
}
|
|
48
|
+
export declare class DapDriver {
|
|
49
|
+
private readonly extensionPath;
|
|
50
|
+
private readonly debugServerJs;
|
|
51
|
+
private readonly defaultTimeoutMs;
|
|
52
|
+
private readonly outputBufferSize;
|
|
53
|
+
private readonly log;
|
|
54
|
+
private client;
|
|
55
|
+
private state;
|
|
56
|
+
/** Source-map manager used to fill `generatedSource`/`generatedLine`
|
|
57
|
+
* on mapped frames (and to surface unmapped-fallback warnings).
|
|
58
|
+
* Lazily created on first launch and reset on disconnect so a
|
|
59
|
+
* fresh session always parses the current `.ajs.map` from disk. */
|
|
60
|
+
private sourceMaps;
|
|
61
|
+
/** Absolute path to the generated `.ajs` for the current launch.
|
|
62
|
+
* Used by `classifyFrame` to ask the manager for reverse mappings
|
|
63
|
+
* without re-resolving the sibling each call. Empty when launch
|
|
64
|
+
* was against a `.ajs` (then `generatedAjs === program`). */
|
|
65
|
+
private generatedAjs;
|
|
66
|
+
/** Once-per-launch dedup for unmapped-fallback warning messages.
|
|
67
|
+
* Without it the same warning would land in the output buffer on
|
|
68
|
+
* every stack_trace call. Key: `<source>:<line>:<col>`. */
|
|
69
|
+
private readonly unmappedWarned;
|
|
70
|
+
/** Args from the most recent launch. Used to classify
|
|
71
|
+
* `sourceOrigin` on frames returned by stackTrace. */
|
|
72
|
+
private launchArgs;
|
|
73
|
+
/** Lower-case basename extension of `launchArgs.program`, e.g.
|
|
74
|
+
* `.ts` or `.ajs`. Cached so frame-mapping doesn't repeat
|
|
75
|
+
* `path.extname()` per frame. */
|
|
76
|
+
private programIsTs;
|
|
77
|
+
/** Circular output buffer; oldest entries dropped first when full. */
|
|
78
|
+
private readonly outputBuffer;
|
|
79
|
+
private outputNextId;
|
|
80
|
+
/** Last `stopped` snapshot — refreshed on every StoppedEvent.
|
|
81
|
+
* Used by `current_state` tool and by step/continue tools that
|
|
82
|
+
* want to read frames straight from cache before calling
|
|
83
|
+
* stack_trace explicitly. */
|
|
84
|
+
private lastStopped;
|
|
85
|
+
/** Termination metadata captured when DAP emits `terminated`. */
|
|
86
|
+
private terminationInfo;
|
|
87
|
+
/** Pending step/continue/wait_for_paused awaiters. Resolved on
|
|
88
|
+
* the next state transition. */
|
|
89
|
+
private pendingWaits;
|
|
90
|
+
private readonly mutex;
|
|
91
|
+
constructor(opts: DapDriverOptions);
|
|
92
|
+
getState(): SessionState;
|
|
93
|
+
getStoppedSnapshot(): StoppedInfo | null;
|
|
94
|
+
getTerminationInfo(): TerminationInfo | null;
|
|
95
|
+
/** Was the most recent launch against a `.ts` file? Tool layer
|
|
96
|
+
* uses this to bake `sourceOrigin` into frames without
|
|
97
|
+
* re-inspecting `launchArgs`. */
|
|
98
|
+
isTsLaunch(): boolean;
|
|
99
|
+
/** Snapshot of buffered Output entries with id > cursor.
|
|
100
|
+
* AI passes `nextCursor` from the previous read to get the tail.
|
|
101
|
+
* Lock-free; never mutates state. */
|
|
102
|
+
readOutput(cursor: number, limit?: number): {
|
|
103
|
+
entries: OutputEntry[];
|
|
104
|
+
nextCursor: number;
|
|
105
|
+
};
|
|
106
|
+
launch(args: LaunchToolArgs): Promise<ExecutionTransition>;
|
|
107
|
+
disconnect(): Promise<void>;
|
|
108
|
+
setBreakpoints(source: string, lines: number[]): Promise<DebugProtocol.SetBreakpointsResponse['body']>;
|
|
109
|
+
/** continue / step_* / wait_for_paused all funnel through this
|
|
110
|
+
* helper. `dapCall` returns the DAP response Promise; we await
|
|
111
|
+
* the resulting state transition (stopped / terminated / timeout)
|
|
112
|
+
* and return it.
|
|
113
|
+
*
|
|
114
|
+
* `dapCall` may be null for `wait_for_paused` (no request to
|
|
115
|
+
* send — just observe). */
|
|
116
|
+
resumeAndWait(dapCall: ((dc: DapClient) => Promise<unknown>) | null, timeoutMs?: number): Promise<ExecutionTransition>;
|
|
117
|
+
stackTrace(): Promise<DebugProtocol.StackTraceResponse['body']>;
|
|
118
|
+
scopes(frameId: number): Promise<DebugProtocol.ScopesResponse['body']>;
|
|
119
|
+
variables(variablesReference: number): Promise<DebugProtocol.VariablesResponse['body']>;
|
|
120
|
+
evaluate(expression: string, frameId: number | undefined, context: 'watch' | 'hover' | 'repl'): Promise<DebugProtocol.EvaluateResponse['body']>;
|
|
121
|
+
/** Convert a DAP StackFrame into our richer FrameInfo with
|
|
122
|
+
* sourceOrigin annotation AND fully-populated generatedSource/
|
|
123
|
+
* generatedLine/generatedColumn fields.
|
|
124
|
+
*
|
|
125
|
+
* Algorithm:
|
|
126
|
+
* - launch with `.ajs` → frame is always `generated`.
|
|
127
|
+
* generatedSource = frame.source.
|
|
128
|
+
* - launch with `.ts` and frame.source ends in `.ts` →
|
|
129
|
+
* `mapped`. Reverse-look the TS coords up in our SourceMap
|
|
130
|
+
* Manager to get the corresponding `.ajs` coords (so the AI
|
|
131
|
+
* can cross-reference engine offsets even when reading TS).
|
|
132
|
+
* If the reverse lookup fails we still classify as mapped
|
|
133
|
+
* but leave generatedSource/Line undefined.
|
|
134
|
+
* - launch with `.ts` and frame.source ends in `.ajs` →
|
|
135
|
+
* `unmapped-fallback` (adapter could not translate this
|
|
136
|
+
* specific line; AI sees raw engine position). We also
|
|
137
|
+
* buffer ONE console-category OutputEntry per unique
|
|
138
|
+
* <source:line:col> tuple so `debug_read_output` surfaces
|
|
139
|
+
* the gap as a visible warning. */
|
|
140
|
+
classifyFrame(f: DebugProtocol.StackFrame): Promise<FrameInfo>;
|
|
141
|
+
/** Buffer a single console-category warning per unique
|
|
142
|
+
* <source:line:col>. AI sees it via `debug_read_output` and can
|
|
143
|
+
* decide whether to ignore or to ask the user to rebuild with
|
|
144
|
+
* better source-map coverage. */
|
|
145
|
+
private warnUnmappedFallback;
|
|
146
|
+
private wireClientEvents;
|
|
147
|
+
private waitForTransition;
|
|
148
|
+
private resolvePendingWaits;
|
|
149
|
+
private failPendingWaits;
|
|
150
|
+
private removePending;
|
|
151
|
+
private snapshotTransition;
|
|
152
|
+
private requireClient;
|
|
153
|
+
private resetSessionState;
|
|
154
|
+
/** Resolve and preload `.ajs.map` candidates for this launch.
|
|
155
|
+
* Stores the resolved generated `.ajs` path in `generatedAjs` so
|
|
156
|
+
* `classifyFrame` can run reverse-mapping lookups without re-
|
|
157
|
+
* guessing the sibling each time. */
|
|
158
|
+
private initSourceMaps;
|
|
159
|
+
private basename;
|
|
160
|
+
private errMsg;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=dap-driver.d.ts.map
|