@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,90 @@
|
|
|
1
|
+
import { type AbiPolicy, type PreflightCheckResponse, type UIModalClickRequest, type UIModalClickResponse, type UIModalDismissRequest, type UIModalDismissResponse, type UIModalFillRequest, type UIModalFillResponse, type UIModalListResponse } from '@i-scope/iscope-bridge-client';
|
|
2
|
+
import { type AbiCheckResult } from './abi-check.js';
|
|
3
|
+
export type { AbiCheckResult } from './abi-check.js';
|
|
4
|
+
export interface BridgeDriverOptions {
|
|
5
|
+
/** Explicit override; otherwise resolveHelperPath() runs. */
|
|
6
|
+
helperPath?: string;
|
|
7
|
+
/** Optional bundledRoot forwarded to resolveHelperPath. */
|
|
8
|
+
bundledRoot?: string;
|
|
9
|
+
/** Per-RPC timeout. Defaults to 10 000 ms. */
|
|
10
|
+
requestTimeoutMs?: number;
|
|
11
|
+
/** Diagnostic line sink (typically stderr writer). */
|
|
12
|
+
log?: (line: string) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Lightweight wrapper around `IScopeBridge` that exposes the
|
|
16
|
+
* UI-modal-control + pre-flight surface to MCP tools. Spawns its own
|
|
17
|
+
* helper child on demand; safe to coexist with an active DAP session
|
|
18
|
+
* that also holds an iScopeBridge.exe.
|
|
19
|
+
*/
|
|
20
|
+
export declare class BridgeDriver {
|
|
21
|
+
private readonly opts;
|
|
22
|
+
private bridge;
|
|
23
|
+
private spawned;
|
|
24
|
+
private initialized;
|
|
25
|
+
/**
|
|
26
|
+
* Single mutex protecting any in-flight transition (spawn or
|
|
27
|
+
* initialize). Non-null while a transition is running. Concurrent
|
|
28
|
+
* callers await the same promise instead of starting a second
|
|
29
|
+
* transition, which would race against the first (e.g. two spawn
|
|
30
|
+
* calls would leak a second helper child; a spawn racing an init
|
|
31
|
+
* could initialize a stale handle). `disconnect()` also awaits
|
|
32
|
+
* this so we never tear the helper down mid-transition.
|
|
33
|
+
*/
|
|
34
|
+
private lifecyclePromise;
|
|
35
|
+
constructor(opts?: BridgeDriverOptions);
|
|
36
|
+
/**
|
|
37
|
+
* Ensure the helper child is spawned + its JSON-RPC loop alive.
|
|
38
|
+
* Does NOT call `initialize()`. Idempotent. Use this for the
|
|
39
|
+
* `preflight/check` path which is registry-only and must work on
|
|
40
|
+
* machines where Oscilloscope is not installed or not running.
|
|
41
|
+
*/
|
|
42
|
+
private ensureSpawned;
|
|
43
|
+
/**
|
|
44
|
+
* Ensure the helper child is spawned AND `initialize()`d (i.e.
|
|
45
|
+
* connected to Oscilloscope's COM server). Idempotent. Calls
|
|
46
|
+
* `ensureSpawned()` internally so it works from a cold start AND
|
|
47
|
+
* from a warm state where `preflightCheck()` already spawned the
|
|
48
|
+
* helper.
|
|
49
|
+
*/
|
|
50
|
+
private ensureReady;
|
|
51
|
+
/**
|
|
52
|
+
* Block until any in-flight `lifecyclePromise` resolves or rejects.
|
|
53
|
+
* Loops because a fresh transition may start while we await the
|
|
54
|
+
* current one (e.g. parallel callers chained through the mutex).
|
|
55
|
+
* Swallows rejections — the caller re-reads the boolean state and
|
|
56
|
+
* decides whether to retry. */
|
|
57
|
+
private awaitLifecycleSettled;
|
|
58
|
+
/** Install `promise` as the current `lifecyclePromise`, await it,
|
|
59
|
+
* and clear the slot on completion. The caller has already
|
|
60
|
+
* awaited the previous transition via `awaitLifecycleSettled()`,
|
|
61
|
+
* so installing here is race-free. */
|
|
62
|
+
private runTransition;
|
|
63
|
+
/** Construct an IScopeBridge with our standard wiring. Does NOT
|
|
64
|
+
* start the helper child — the caller decides between
|
|
65
|
+
* `start()` only (preflight path) and `start() + initialize()`
|
|
66
|
+
* (full ready path). */
|
|
67
|
+
private spawnHelper;
|
|
68
|
+
private log;
|
|
69
|
+
uiModalList(): Promise<UIModalListResponse>;
|
|
70
|
+
uiModalClick(args: UIModalClickRequest): Promise<UIModalClickResponse>;
|
|
71
|
+
uiModalFill(args: UIModalFillRequest): Promise<UIModalFillResponse>;
|
|
72
|
+
uiModalDismiss(args: UIModalDismissRequest): Promise<UIModalDismissResponse>;
|
|
73
|
+
preflightCheck(): Promise<PreflightCheckResponse>;
|
|
74
|
+
/** Wire ABI probe via `protocol/version` — no `initialize()`, no COM. */
|
|
75
|
+
abiCheck(policy?: AbiPolicy): Promise<AbiCheckResult>;
|
|
76
|
+
openFile(_path: string): Promise<void>;
|
|
77
|
+
getStatus(): Promise<{
|
|
78
|
+
status: number;
|
|
79
|
+
name: string;
|
|
80
|
+
}>;
|
|
81
|
+
runScript(_args: {
|
|
82
|
+
file: string;
|
|
83
|
+
mwfFile?: string;
|
|
84
|
+
}): Promise<void>;
|
|
85
|
+
/** Tear down the helper child. Idempotent. Resets state to `idle`.
|
|
86
|
+
* Waits for any in-flight transition (spawn or initialize) to
|
|
87
|
+
* settle first so we never `forceStop` a helper mid-handshake. */
|
|
88
|
+
disconnect(): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=bridge-driver.d.ts.map
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// BridgeDriver — direct (non-DAP) RPC access to iScopeBridge.exe.
|
|
3
|
+
//
|
|
4
|
+
// Live home of:
|
|
5
|
+
// - ui_modal_list / click / fill / dismiss (MCP UI plan)
|
|
6
|
+
// - system_preflight_check (Phase 1, MDM detection)
|
|
7
|
+
// - system_abi_check (wire ABI via protocol/version)
|
|
8
|
+
//
|
|
9
|
+
// Reserved for Phase 2/3 expansion:
|
|
10
|
+
// - control_open_file (BridgeDriver.openFile)
|
|
11
|
+
// - control_get_status (BridgeDriver.getStatus)
|
|
12
|
+
// - control_run_script (BridgeDriver.executeFile without debug)
|
|
13
|
+
// - control_close_application
|
|
14
|
+
// - data_* (TBD — needs helper-cpp extensions)
|
|
15
|
+
//
|
|
16
|
+
// Why a separate class from DapDriver:
|
|
17
|
+
// DapDriver OWNS a `debug-server.js` child which in turn owns its
|
|
18
|
+
// own iScopeBridge.exe (via `extension/.../com/bridge.ts`). For
|
|
19
|
+
// non-debug operations we don't want to pay the cost of a full DAP
|
|
20
|
+
// session — a direct helper child is plenty. The two drivers may
|
|
21
|
+
// coexist: COM lets multiple clients attach to Oscilloscope, so an
|
|
22
|
+
// AI agent can simultaneously hold a DAP session AND poll modal
|
|
23
|
+
// state via `ui_modal_list`.
|
|
24
|
+
//
|
|
25
|
+
// Lifecycle (two-stage state machine):
|
|
26
|
+
//
|
|
27
|
+
// ensureSpawned ensureReady
|
|
28
|
+
// ────────────► ────────────►
|
|
29
|
+
// 'idle' ─────────────► 'spawned' ──────────► 'ready'
|
|
30
|
+
// ▲ │ │
|
|
31
|
+
// │ disconnect() │ disconnect() │
|
|
32
|
+
// └──────────────────────┴─────────────────────┘
|
|
33
|
+
//
|
|
34
|
+
// - `ensureSpawned()` — guarantees the helper child is running and its
|
|
35
|
+
// stdio JSON-RPC loop is alive. Required for the `preflight/check`
|
|
36
|
+
// method which is registry-only and does NOT need a live COM
|
|
37
|
+
// connection (the MCP `system_preflight_check` tool runs even on a
|
|
38
|
+
// box where Oscilloscope is not installed).
|
|
39
|
+
// - `ensureReady()` — additionally guarantees `bridge.initialize()`
|
|
40
|
+
// has succeeded, i.e. the helper is connected to Oscilloscope's COM
|
|
41
|
+
// server. Required for every ui_modal_* method. Internally calls
|
|
42
|
+
// `ensureSpawned()` first so it works from a cold start AND from a
|
|
43
|
+
// warm state where preflight already spawned the helper.
|
|
44
|
+
// - `disconnect()` — full teardown. Resets state to `idle`.
|
|
45
|
+
//
|
|
46
|
+
// Both methods are idempotent and serialise concurrent callers via
|
|
47
|
+
// a single `lifecyclePromise` mutex — two parallel `ensureReady()`
|
|
48
|
+
// calls (one from a `ui_modal_list` tool, one from `ui_modal_click`
|
|
49
|
+
// chained right behind it) share the same spawn AND the same
|
|
50
|
+
// initialize. Failed transitions reset the relevant boolean so the
|
|
51
|
+
// next caller retries from the appropriate stage.
|
|
52
|
+
//
|
|
53
|
+
// `attachPolicy='attachOnly'` by default — we never auto-launch
|
|
54
|
+
// Oscilloscope just because the agent asked to list modals; if
|
|
55
|
+
// osc isn't running, `initialize()` returns connected=false and the
|
|
56
|
+
// calling tool surfaces a friendly "Oscilloscope is not running"
|
|
57
|
+
// error to the AI.
|
|
58
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
59
|
+
exports.BridgeDriver = void 0;
|
|
60
|
+
const iscope_bridge_client_1 = require("@i-scope/iscope-bridge-client");
|
|
61
|
+
const abi_check_js_1 = require("./abi-check.js");
|
|
62
|
+
/**
|
|
63
|
+
* Lightweight wrapper around `IScopeBridge` that exposes the
|
|
64
|
+
* UI-modal-control + pre-flight surface to MCP tools. Spawns its own
|
|
65
|
+
* helper child on demand; safe to coexist with an active DAP session
|
|
66
|
+
* that also holds an iScopeBridge.exe.
|
|
67
|
+
*/
|
|
68
|
+
class BridgeDriver {
|
|
69
|
+
opts;
|
|
70
|
+
// ---- State-machine fields ------------------------------------------------
|
|
71
|
+
//
|
|
72
|
+
// Two booleans rather than a single discriminated enum because the
|
|
73
|
+
// transitions are linear (idle → spawned → ready) and we frequently
|
|
74
|
+
// need to ask "are we at-or-past stage X?" — booleans answer that
|
|
75
|
+
// in one read. Invariant: `initialized` implies `spawned`, which
|
|
76
|
+
// implies `bridge !== null`.
|
|
77
|
+
bridge = null;
|
|
78
|
+
spawned = false;
|
|
79
|
+
initialized = false;
|
|
80
|
+
/**
|
|
81
|
+
* Single mutex protecting any in-flight transition (spawn or
|
|
82
|
+
* initialize). Non-null while a transition is running. Concurrent
|
|
83
|
+
* callers await the same promise instead of starting a second
|
|
84
|
+
* transition, which would race against the first (e.g. two spawn
|
|
85
|
+
* calls would leak a second helper child; a spawn racing an init
|
|
86
|
+
* could initialize a stale handle). `disconnect()` also awaits
|
|
87
|
+
* this so we never tear the helper down mid-transition.
|
|
88
|
+
*/
|
|
89
|
+
lifecyclePromise = null;
|
|
90
|
+
constructor(opts = {}) {
|
|
91
|
+
this.opts = opts;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Ensure the helper child is spawned + its JSON-RPC loop alive.
|
|
95
|
+
* Does NOT call `initialize()`. Idempotent. Use this for the
|
|
96
|
+
* `preflight/check` path which is registry-only and must work on
|
|
97
|
+
* machines where Oscilloscope is not installed or not running.
|
|
98
|
+
*/
|
|
99
|
+
async ensureSpawned() {
|
|
100
|
+
// Wait until any in-flight transition (spawn or init) settles
|
|
101
|
+
// so our state read below is consistent.
|
|
102
|
+
await this.awaitLifecycleSettled();
|
|
103
|
+
if (this.spawned && this.bridge)
|
|
104
|
+
return this.bridge;
|
|
105
|
+
// Serialise the spawn behind the mutex so a parallel caller
|
|
106
|
+
// does not start a SECOND helper child. Re-check inside the
|
|
107
|
+
// critical section in case another caller spawned while we
|
|
108
|
+
// were queued.
|
|
109
|
+
const transition = (async () => {
|
|
110
|
+
if (this.spawned && this.bridge)
|
|
111
|
+
return;
|
|
112
|
+
const br = this.spawnHelper();
|
|
113
|
+
br.start();
|
|
114
|
+
this.bridge = br;
|
|
115
|
+
this.spawned = true;
|
|
116
|
+
})();
|
|
117
|
+
await this.runTransition(transition);
|
|
118
|
+
if (!this.bridge)
|
|
119
|
+
throw new Error('BridgeDriver spawn failed');
|
|
120
|
+
return this.bridge;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Ensure the helper child is spawned AND `initialize()`d (i.e.
|
|
124
|
+
* connected to Oscilloscope's COM server). Idempotent. Calls
|
|
125
|
+
* `ensureSpawned()` internally so it works from a cold start AND
|
|
126
|
+
* from a warm state where `preflightCheck()` already spawned the
|
|
127
|
+
* helper.
|
|
128
|
+
*/
|
|
129
|
+
async ensureReady() {
|
|
130
|
+
const br = await this.ensureSpawned();
|
|
131
|
+
await this.awaitLifecycleSettled();
|
|
132
|
+
if (this.initialized && this.bridge)
|
|
133
|
+
return this.bridge;
|
|
134
|
+
// Serialise the initialize call behind the mutex. Failure
|
|
135
|
+
// leaves `spawned=true, initialized=false` so the next caller
|
|
136
|
+
// can retry the initialize step without re-spawning.
|
|
137
|
+
const transition = (async () => {
|
|
138
|
+
if (this.initialized && this.bridge)
|
|
139
|
+
return;
|
|
140
|
+
if (!this.spawned || !this.bridge) {
|
|
141
|
+
// disconnect() ran between ensureSpawned and the mutex
|
|
142
|
+
// becoming available. Surface a clear error so the
|
|
143
|
+
// caller knows to retry from scratch.
|
|
144
|
+
throw new Error('BridgeDriver: helper not spawned (lost race with disconnect)');
|
|
145
|
+
}
|
|
146
|
+
// autoLaunch=false: we never auto-spawn Oscilloscope from a
|
|
147
|
+
// UI tool. If osc is not running, `initialize` will throw
|
|
148
|
+
// (or return connected=false depending on helper config)
|
|
149
|
+
// and the calling tool surfaces a friendly "Oscilloscope
|
|
150
|
+
// is not running" error to the AI.
|
|
151
|
+
await this.bridge.initialize({ autoLaunch: false });
|
|
152
|
+
this.initialized = true;
|
|
153
|
+
})();
|
|
154
|
+
await this.runTransition(transition);
|
|
155
|
+
if (!this.bridge)
|
|
156
|
+
throw new Error('BridgeDriver init failed');
|
|
157
|
+
return br;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Block until any in-flight `lifecyclePromise` resolves or rejects.
|
|
161
|
+
* Loops because a fresh transition may start while we await the
|
|
162
|
+
* current one (e.g. parallel callers chained through the mutex).
|
|
163
|
+
* Swallows rejections — the caller re-reads the boolean state and
|
|
164
|
+
* decides whether to retry. */
|
|
165
|
+
async awaitLifecycleSettled() {
|
|
166
|
+
while (this.lifecyclePromise) {
|
|
167
|
+
const p = this.lifecyclePromise;
|
|
168
|
+
try {
|
|
169
|
+
await p;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
/* let the caller re-inspect spawned/initialized */
|
|
173
|
+
}
|
|
174
|
+
if (this.lifecyclePromise === p)
|
|
175
|
+
this.lifecyclePromise = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Install `promise` as the current `lifecyclePromise`, await it,
|
|
179
|
+
* and clear the slot on completion. The caller has already
|
|
180
|
+
* awaited the previous transition via `awaitLifecycleSettled()`,
|
|
181
|
+
* so installing here is race-free. */
|
|
182
|
+
async runTransition(promise) {
|
|
183
|
+
// Guard form — never store a rejected promise in the slot.
|
|
184
|
+
// Without this, a concurrent `await this.lifecyclePromise`
|
|
185
|
+
// would synchronously re-throw the same rejection.
|
|
186
|
+
const guard = promise.then(() => { }, () => { });
|
|
187
|
+
this.lifecyclePromise = guard;
|
|
188
|
+
try {
|
|
189
|
+
await promise;
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
if (this.lifecyclePromise === guard)
|
|
193
|
+
this.lifecyclePromise = null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Construct an IScopeBridge with our standard wiring. Does NOT
|
|
197
|
+
* start the helper child — the caller decides between
|
|
198
|
+
* `start()` only (preflight path) and `start() + initialize()`
|
|
199
|
+
* (full ready path). */
|
|
200
|
+
spawnHelper() {
|
|
201
|
+
const helperPath = this.opts.helperPath
|
|
202
|
+
?? (0, iscope_bridge_client_1.resolveHelperPath)({ bundledRoot: this.opts.bundledRoot });
|
|
203
|
+
this.log(`[BridgeDriver] spawning helper: ${helperPath}`);
|
|
204
|
+
return new iscope_bridge_client_1.IScopeBridge({
|
|
205
|
+
helperPath,
|
|
206
|
+
requestTimeoutMs: this.opts.requestTimeoutMs ?? 10_000,
|
|
207
|
+
onStderr: (chunk) => this.log(`[helper-stderr] ${chunk.trimEnd()}`),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
log(line) {
|
|
211
|
+
if (this.opts.log)
|
|
212
|
+
this.opts.log(line);
|
|
213
|
+
}
|
|
214
|
+
// ---- UI modal control ----------------------------------------------------
|
|
215
|
+
async uiModalList() {
|
|
216
|
+
const br = await this.ensureReady();
|
|
217
|
+
return br.uiModalList();
|
|
218
|
+
}
|
|
219
|
+
async uiModalClick(args) {
|
|
220
|
+
const br = await this.ensureReady();
|
|
221
|
+
return br.uiModalClick(args);
|
|
222
|
+
}
|
|
223
|
+
async uiModalFill(args) {
|
|
224
|
+
const br = await this.ensureReady();
|
|
225
|
+
return br.uiModalFill(args);
|
|
226
|
+
}
|
|
227
|
+
async uiModalDismiss(args) {
|
|
228
|
+
const br = await this.ensureReady();
|
|
229
|
+
return br.uiModalDismiss(args);
|
|
230
|
+
}
|
|
231
|
+
// ---- Pre-flight check (Phase 1, MDM detection) -------------------------
|
|
232
|
+
// Uses `ensureSpawned` rather than `ensureReady` because the C++
|
|
233
|
+
// `preflight/check` handler is registry-only and must work on a
|
|
234
|
+
// machine where Oscilloscope is not installed or not running.
|
|
235
|
+
// The state-machine contract guarantees that any subsequent
|
|
236
|
+
// `ui_modal_*` call from the same MCP server session will then
|
|
237
|
+
// promote the helper from `spawned` to `ready` via `ensureReady`,
|
|
238
|
+
// running `initialize()` exactly once — without this two-stage
|
|
239
|
+
// design, `preflightCheck` would set `bridge !== null` and a
|
|
240
|
+
// single-flag `ensureReady` would short-circuit, leaving the
|
|
241
|
+
// helper un-initialised when the first ui_modal_* call arrives
|
|
242
|
+
// (B1 regression, fixed 2026-05-19; see test 23-preflight-then-ui).
|
|
243
|
+
async preflightCheck() {
|
|
244
|
+
const br = await this.ensureSpawned();
|
|
245
|
+
return br.preflightCheck();
|
|
246
|
+
}
|
|
247
|
+
/** Wire ABI probe via `protocol/version` — no `initialize()`, no COM. */
|
|
248
|
+
async abiCheck(policy) {
|
|
249
|
+
const br = await this.ensureSpawned();
|
|
250
|
+
const version = await br.protocolVersion();
|
|
251
|
+
return (0, abi_check_js_1.evaluateAbiCheck)(version, policy ?? 'strict');
|
|
252
|
+
}
|
|
253
|
+
// ---- Phase 2/3 placeholders ---------------------------------------------
|
|
254
|
+
async openFile(_path) {
|
|
255
|
+
throw new Error('BridgeDriver.openFile not yet implemented (Phase 2)');
|
|
256
|
+
}
|
|
257
|
+
async getStatus() {
|
|
258
|
+
throw new Error('BridgeDriver.getStatus not yet implemented (Phase 2)');
|
|
259
|
+
}
|
|
260
|
+
async runScript(_args) {
|
|
261
|
+
throw new Error('BridgeDriver.runScript not yet implemented (Phase 2)');
|
|
262
|
+
}
|
|
263
|
+
// ---- Lifecycle -----------------------------------------------------------
|
|
264
|
+
/** Tear down the helper child. Idempotent. Resets state to `idle`.
|
|
265
|
+
* Waits for any in-flight transition (spawn or initialize) to
|
|
266
|
+
* settle first so we never `forceStop` a helper mid-handshake. */
|
|
267
|
+
async disconnect() {
|
|
268
|
+
await this.awaitLifecycleSettled();
|
|
269
|
+
const br = this.bridge;
|
|
270
|
+
this.bridge = null;
|
|
271
|
+
this.spawned = false;
|
|
272
|
+
this.initialized = false;
|
|
273
|
+
if (!br)
|
|
274
|
+
return;
|
|
275
|
+
try {
|
|
276
|
+
await br.shutdown({ closePolicy: 'ifWeLaunched' });
|
|
277
|
+
}
|
|
278
|
+
catch (e) {
|
|
279
|
+
this.log(`[BridgeDriver] shutdown error: ${e instanceof Error ? e.message : String(e)}`);
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
br.forceStop();
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
/* helper might already be dead */
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
exports.BridgeDriver = BridgeDriver;
|
|
290
|
+
//# sourceMappingURL=bridge-driver.js.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { DebugProtocol } from '@vscode/debugprotocol';
|
|
3
|
+
export interface DapClientOptions {
|
|
4
|
+
/** Runtime to spawn (typically 'node'). Defaults to 'node'. */
|
|
5
|
+
runtime?: string;
|
|
6
|
+
/** Absolute path to the .js / .cjs adapter entry point. */
|
|
7
|
+
executable: string;
|
|
8
|
+
/** Extra argv for the runtime, AFTER `executable`. Rarely used —
|
|
9
|
+
* DAP adapters typically take no argv. */
|
|
10
|
+
args?: string[];
|
|
11
|
+
/** Environment for the child. Defaults to `process.env`. */
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
/** Working directory for the child. */
|
|
14
|
+
cwd?: string;
|
|
15
|
+
/** Per-request default timeout. Defaults to 30 000 ms. */
|
|
16
|
+
defaultTimeoutMs?: number;
|
|
17
|
+
/** Inherit the child's stderr into the parent's stderr. true is
|
|
18
|
+
* the right default for production (we surface adapter logs).
|
|
19
|
+
* Set false in unit tests if the log noise bothers you. */
|
|
20
|
+
inheritStderr?: boolean;
|
|
21
|
+
/** Diagnostic logger. Defaults to no-op. */
|
|
22
|
+
log?: (line: string) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Stand-alone DAP client. Inherits from EventEmitter for event
|
|
26
|
+
* subscription. Type-safe `request<T>` and `waitForEvent<T>` give
|
|
27
|
+
* callers the response body / event payload directly.
|
|
28
|
+
*/
|
|
29
|
+
export declare class DapClient extends EventEmitter {
|
|
30
|
+
private readonly runtime;
|
|
31
|
+
private readonly executable;
|
|
32
|
+
private readonly args;
|
|
33
|
+
private readonly env;
|
|
34
|
+
private readonly cwd;
|
|
35
|
+
private readonly inheritStderr;
|
|
36
|
+
private readonly log;
|
|
37
|
+
private child;
|
|
38
|
+
private childExited;
|
|
39
|
+
/** Monotonic outgoing message id. DAP spec: `seq` is per-sender
|
|
40
|
+
* and increases by one per message; the OTHER side has its own
|
|
41
|
+
* `seq` axis we ignore. */
|
|
42
|
+
private nextSeq;
|
|
43
|
+
/** Accumulating stdout buffer. We pull complete frames out of
|
|
44
|
+
* it as they arrive. Type widened to ArrayBufferLike to match
|
|
45
|
+
* what `Buffer.concat` and `Buffer#subarray` return in
|
|
46
|
+
* @types/node 22+. */
|
|
47
|
+
private rxBuffer;
|
|
48
|
+
/** Once we see `Content-Length: N\r\n\r\n` we remember N here
|
|
49
|
+
* until we've consumed exactly N bytes of body. -1 = "still
|
|
50
|
+
* parsing the header". */
|
|
51
|
+
private rxContentLength;
|
|
52
|
+
/** seq → outstanding request. We never await a response by
|
|
53
|
+
* `command` alone — two simultaneous `stackTrace`s would
|
|
54
|
+
* collide. `seq` is the only safe key. */
|
|
55
|
+
private readonly pending;
|
|
56
|
+
defaultTimeoutMs: number;
|
|
57
|
+
constructor(opts: DapClientOptions);
|
|
58
|
+
/** Spawn the adapter child. Returns once `spawn()` has been
|
|
59
|
+
* called — the child may still be initialising. Callers
|
|
60
|
+
* immediately issue `request('initialize', ...)` and the DAP
|
|
61
|
+
* request/response framing handles the rest. */
|
|
62
|
+
start(): Promise<void>;
|
|
63
|
+
/** Send a request and resolve with `response.body`. Rejects on
|
|
64
|
+
* - timeout (after `timeoutMs ?? this.defaultTimeoutMs`),
|
|
65
|
+
* - protocol-level failure (`response.success === false`),
|
|
66
|
+
* - child process exit (the exit handler nukes pending). */
|
|
67
|
+
request<T = unknown>(command: string, args?: unknown, timeoutMs?: number): Promise<T>;
|
|
68
|
+
/** Resolve with the next event of the given name. Honours a
|
|
69
|
+
* timeout to avoid hanging on tests that never fire the event
|
|
70
|
+
* they expect. */
|
|
71
|
+
waitForEvent<T extends DebugProtocol.Event = DebugProtocol.Event>(name: string, timeoutMs?: number): Promise<T>;
|
|
72
|
+
/** Tear the child down. Graceful path: close stdin → child sees
|
|
73
|
+
* EOF → exits → we resolve. Fallback path: after `gracefulMs`,
|
|
74
|
+
* SIGTERM; after another second, SIGKILL. Either way the
|
|
75
|
+
* Promise always resolves; we never throw from cleanup. */
|
|
76
|
+
stop(gracefulMs?: number): Promise<void>;
|
|
77
|
+
private onStdoutChunk;
|
|
78
|
+
private dispatch;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=dap-client.d.ts.map
|