@eyeauras/insight-cli 0.0.0-stage → 0.1.0

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.
Files changed (164) hide show
  1. package/.cli-package-build.json +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -2
  4. package/SKILL.md +55 -0
  5. package/dist/src/administration-results.d.ts +1 -0
  6. package/dist/src/administration-results.js +84 -0
  7. package/dist/src/administration.d.ts +14 -0
  8. package/dist/src/administration.js +120 -0
  9. package/dist/src/archive-contract.d.ts +6 -0
  10. package/dist/src/archive-contract.js +108 -0
  11. package/dist/src/archive.d.ts +12 -0
  12. package/dist/src/archive.js +116 -0
  13. package/dist/src/arguments.d.ts +14 -0
  14. package/dist/src/arguments.js +62 -0
  15. package/dist/src/artifacts.d.ts +17 -0
  16. package/dist/src/artifacts.js +115 -0
  17. package/dist/src/bin.d.ts +2 -0
  18. package/dist/src/bin.js +4 -0
  19. package/dist/src/cli.d.ts +10 -0
  20. package/dist/src/cli.js +387 -0
  21. package/dist/src/comment-effects.d.ts +3 -0
  22. package/dist/src/comment-effects.js +55 -0
  23. package/dist/src/contexts.d.ts +115 -0
  24. package/dist/src/contexts.js +1068 -0
  25. package/dist/src/coordinator.d.ts +41 -0
  26. package/dist/src/coordinator.js +160 -0
  27. package/dist/src/cross-results.d.ts +14 -0
  28. package/dist/src/cross-results.js +71 -0
  29. package/dist/src/cross.d.ts +3 -0
  30. package/dist/src/cross.js +43 -0
  31. package/dist/src/data-effects.d.ts +3 -0
  32. package/dist/src/data-effects.js +103 -0
  33. package/dist/src/debugger-results.d.ts +4 -0
  34. package/dist/src/debugger-results.js +151 -0
  35. package/dist/src/debugger-supervisor.d.ts +1 -0
  36. package/dist/src/debugger-supervisor.js +136 -0
  37. package/dist/src/debugger.d.ts +21 -0
  38. package/dist/src/debugger.js +267 -0
  39. package/dist/src/documentation-effects.d.ts +5 -0
  40. package/dist/src/documentation-effects.js +56 -0
  41. package/dist/src/extensions.d.ts +64 -0
  42. package/dist/src/extensions.js +289 -0
  43. package/dist/src/function-effects.d.ts +4 -0
  44. package/dist/src/function-effects.js +112 -0
  45. package/dist/src/ghidra.d.ts +50 -0
  46. package/dist/src/ghidra.js +468 -0
  47. package/dist/src/index.d.ts +2 -0
  48. package/dist/src/index.js +1 -0
  49. package/dist/src/initial-model.d.ts +6 -0
  50. package/dist/src/initial-model.js +85 -0
  51. package/dist/src/json.d.ts +2 -0
  52. package/dist/src/json.js +8 -0
  53. package/dist/src/lifecycle.d.ts +49 -0
  54. package/dist/src/lifecycle.js +148 -0
  55. package/dist/src/memory-inputs.d.ts +8 -0
  56. package/dist/src/memory-inputs.js +56 -0
  57. package/dist/src/merge.d.ts +29 -0
  58. package/dist/src/merge.js +66 -0
  59. package/dist/src/model.d.ts +74 -0
  60. package/dist/src/model.js +67 -0
  61. package/dist/src/mutation-effects.d.ts +4 -0
  62. package/dist/src/mutation-effects.js +321 -0
  63. package/dist/src/mutation-inputs.d.ts +2 -0
  64. package/dist/src/mutation-inputs.js +71 -0
  65. package/dist/src/mutations.d.ts +4 -0
  66. package/dist/src/mutations.js +53 -0
  67. package/dist/src/native-inputs.d.ts +28 -0
  68. package/dist/src/native-inputs.js +171 -0
  69. package/dist/src/native-queries.d.ts +2 -0
  70. package/dist/src/native-queries.js +29 -0
  71. package/dist/src/projects.d.ts +31 -0
  72. package/dist/src/projects.js +301 -0
  73. package/dist/src/prototype-cli.d.ts +8 -0
  74. package/dist/src/prototype-cli.js +155 -0
  75. package/dist/src/queries.d.ts +5 -0
  76. package/dist/src/queries.js +84 -0
  77. package/dist/src/query-policy.d.ts +6 -0
  78. package/dist/src/query-policy.js +72 -0
  79. package/dist/src/query-results.d.ts +2 -0
  80. package/dist/src/query-results.js +224 -0
  81. package/dist/src/research.d.ts +184 -0
  82. package/dist/src/research.js +808 -0
  83. package/dist/src/saved-materials.d.ts +12 -0
  84. package/dist/src/saved-materials.js +196 -0
  85. package/dist/src/scripts.d.ts +19 -0
  86. package/dist/src/scripts.js +79 -0
  87. package/dist/src/sdk-contracts.d.ts +13 -0
  88. package/dist/src/sdk-contracts.js +37 -0
  89. package/dist/src/sdk-files.d.ts +29 -0
  90. package/dist/src/sdk-files.js +249 -0
  91. package/dist/src/sdk.d.ts +4 -0
  92. package/dist/src/sdk.js +125 -0
  93. package/dist/src/server.d.ts +44 -0
  94. package/dist/src/server.js +455 -0
  95. package/dist/src/store.d.ts +33 -0
  96. package/dist/src/store.js +178 -0
  97. package/dist/src/sync-policy.d.ts +22 -0
  98. package/dist/src/sync-policy.js +123 -0
  99. package/dist/src/transport.d.ts +23 -0
  100. package/dist/src/transport.js +135 -0
  101. package/dist/src/ui-results.d.ts +2 -0
  102. package/dist/src/ui-results.js +80 -0
  103. package/dist/src/ui.d.ts +17 -0
  104. package/dist/src/ui.js +88 -0
  105. package/dist/src/utility.d.ts +14 -0
  106. package/dist/src/utility.js +43 -0
  107. package/dist/src/version-tracking.d.ts +57 -0
  108. package/dist/src/version-tracking.js +200 -0
  109. package/dist/src/workspace.d.ts +199 -0
  110. package/dist/src/workspace.js +564 -0
  111. package/package.json +45 -4
  112. package/references/InsightRepositoryProbe.java +55 -0
  113. package/references/administration.md +33 -0
  114. package/references/archive.md +32 -0
  115. package/references/artifacts.md +47 -0
  116. package/references/debugger.md +40 -0
  117. package/references/extensions-and-sync.md +124 -0
  118. package/references/mutations.json +1424 -0
  119. package/references/native/InsightApplyDocumentation.java +48 -0
  120. package/references/native/InsightArchivePayloads.java +36 -0
  121. package/references/native/InsightBlockTransfer.java +57 -0
  122. package/references/native/InsightEmulation.java +93 -0
  123. package/references/native/InsightExtensions.java +36 -0
  124. package/references/native/InsightFunctionDatabases.java +98 -0
  125. package/references/native/InsightGuiMain.java +32 -0
  126. package/references/native/InsightHeadlessMain.java +10 -0
  127. package/references/native/InsightInitialImport.java +42 -0
  128. package/references/native/InsightInitialJournal.java +48 -0
  129. package/references/native/InsightInitialPublication.java +142 -0
  130. package/references/native/InsightKeytool.java +16 -0
  131. package/references/native/InsightNativeReceipt.java +31 -0
  132. package/references/native/InsightPlugins.java +34 -0
  133. package/references/native/InsightProjectFiles.java +92 -0
  134. package/references/native/InsightPublicationRecovery.java +63 -0
  135. package/references/native/InsightPythonWheels.java +31 -0
  136. package/references/native/InsightResearchQueries.java +188 -0
  137. package/references/native/InsightSdkFiles.java +142 -0
  138. package/references/native/InsightSdkScript.java +31 -0
  139. package/references/native/InsightSdkTools.java +338 -0
  140. package/references/native/InsightSdkTypes.java +132 -0
  141. package/references/native/InsightServerAdministration.java +82 -0
  142. package/references/native/InsightServerInit.java +62 -0
  143. package/references/native/InsightServerMain.java +43 -0
  144. package/references/native/InsightServerProbe.java +29 -0
  145. package/references/native/InsightSnapshotFields.java +68 -0
  146. package/references/native/InsightSnapshotInspect.java +189 -0
  147. package/references/native/InsightSnapshotPublisher.java +123 -0
  148. package/references/native/InsightStartDebugger.java +28 -0
  149. package/references/native/InsightStopDebugger.java +20 -0
  150. package/references/native/InsightTraceInspect.java +24 -0
  151. package/references/native/InsightTraceState.java +130 -0
  152. package/references/native/InsightTraceTools.java +114 -0
  153. package/references/native/InsightVersionTracking.java +91 -0
  154. package/references/native-workflows.md +112 -0
  155. package/references/queries.json +1761 -0
  156. package/references/query-semantics.md +96 -0
  157. package/references/recovery.md +53 -0
  158. package/references/research.md +101 -0
  159. package/references/setup.md +47 -0
  160. package/runtime/GhidraMCP-7.0.0.jar +0 -0
  161. package/runtime/GhidraMCP-LICENSE +202 -0
  162. package/runtime/GhidraMCP-NOTICE +10 -0
  163. package/runtime/provenance.json +11 -0
  164. package/skills/insight-re/SKILL.md +108 -0
@@ -0,0 +1,1068 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { randomUUID, randomBytes } from 'node:crypto';
3
+ import { mkdir, open, readFile, readdir, rename, link, realpath, copyFile, chmod, writeFile } from 'node:fs/promises';
4
+ import { constants } from 'node:fs';
5
+ import { join, delimiter, sep, resolve } from 'node:path';
6
+ import { createServer } from 'node:net';
7
+ import { setTimeout as delay } from 'node:timers/promises';
8
+ import { nativeHelpers, nativeInputs, nativeJvmDirectories, nativeExtensionArguments } from './native-inputs.js';
9
+ import { GhidraServer } from './server.js';
10
+ import { object, fail, text, version, stable } from './model.js';
11
+ import { HttpBridge } from './transport.js';
12
+ import { scriptInventory, scriptPath } from './scripts.js';
13
+ import { logicalPrograms } from './cross-results.js';
14
+ import { uiResult } from './ui-results.js';
15
+ import { execFile } from 'node:child_process';
16
+ import { promisify } from 'node:util';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { debuggerProgramMetadata } from './debugger-results.js';
19
+ import { nativeQueries } from './query-policy.js';
20
+ import { nativeQuery } from './native-queries.js';
21
+ import { sdkWrites } from './sdk-contracts.js';
22
+ import { initialIntent, initializeModel } from './initial-model.js';
23
+ const alive = (pid) => {
24
+ try {
25
+ process.kill(pid, 0);
26
+ return true;
27
+ }
28
+ catch (error) {
29
+ if (error.code === 'ESRCH')
30
+ return false;
31
+ throw error;
32
+ }
33
+ };
34
+ const programPath = (target) => '/' + target + '.bin';
35
+ async function durable(path, value) {
36
+ const temporary = path + '.new-' + randomUUID(), handle = await open(temporary, 'wx', 0o600);
37
+ try {
38
+ await handle.writeFile(stable(value));
39
+ await handle.sync();
40
+ }
41
+ finally {
42
+ await handle.close();
43
+ }
44
+ await rename(temporary, path);
45
+ }
46
+ async function port() {
47
+ const listener = createServer();
48
+ await new Promise((ok, bad) => { listener.once('error', bad); listener.listen(0, '127.0.0.1', ok); });
49
+ const address = listener.address();
50
+ if (!address || typeof address === 'string')
51
+ fail('launch', 'Owned context port allocation failed.');
52
+ await new Promise(ok => listener.close(() => ok()));
53
+ return address.port;
54
+ }
55
+ /** Isolated native contexts. This layer has no agent-facing instance/worker selector. */
56
+ export class GhidraContexts {
57
+ workspace;
58
+ inputs;
59
+ c;
60
+ running;
61
+ modelTasks;
62
+ ownedServer;
63
+ capacity;
64
+ constructor(workspace, inputs, c, running = new Map(), modelTasks = new Map(), ownedServer = new GhidraServer(workspace.directory, inputs, c), capacity = { slots: new Set() }) {
65
+ this.workspace = workspace;
66
+ this.inputs = inputs;
67
+ this.c = c;
68
+ this.running = running;
69
+ this.modelTasks = modelTasks;
70
+ this.ownedServer = ownedServer;
71
+ this.capacity = capacity;
72
+ }
73
+ fork(c) { return new GhidraContexts(this.workspace, this.inputs, c, this.running, this.modelTasks, this.ownedServer.fork(c), this.capacity); }
74
+ directory() { return join(this.workspace.directory, 'ghidra-native'); }
75
+ server() { return this.ownedServer; }
76
+ async administration(operation, parameters) { return this.server().administration(operation, parameters); }
77
+ async sdkFile(operation, parameters) { return this.server().sdkFile(operation, parameters); }
78
+ async prepareRoot(kind, target, context) {
79
+ const root = join(this.directory(), kind, context ?? target, randomUUID());
80
+ await mkdir(root, { recursive: true, mode: 0o700 });
81
+ return root;
82
+ }
83
+ async launch(root, target, kind, program, mode, context, credentials = {}) {
84
+ if ((await nativeInputs(this.c.profile.values, this.workspace.directory)).fingerprint !== this.inputs.fingerprint)
85
+ fail('binding', 'Native runtime inputs changed; stop the owner before using a different build.');
86
+ const endpointPort = await port(), tokenSecret = 'context-' + randomUUID(), token = randomBytes(32).toString('hex');
87
+ await this.c.secrets.set(tokenSecret, token);
88
+ const classes = await nativeHelpers(this.workspace.directory, this.inputs);
89
+ let debuggerEnvironment = {};
90
+ if (mode === 'gui') {
91
+ const python = join(root, 'debug-python-' + randomUUID());
92
+ await mkdir(python, { mode: 0o700 });
93
+ const prepared = await promisify(execFile)(this.inputs.java, ['-cp', classes, 'InsightPythonWheels', python, ...this.inputs.debuggerWheels], { timeout: 30000, maxBuffer: 65536 });
94
+ if (object(JSON.parse(prepared.stdout)).state !== 'prepared')
95
+ fail('configuration', 'Owned debugger Python dependencies could not be prepared.');
96
+ debuggerEnvironment = { INSIGHT_DEBUGGER_PYTHON: python, PYTHONPATH: python, PYTHONNOUSERSITE: '1', PYTHONDONTWRITEBYTECODE: '1', PYTHONPYCACHEPREFIX: join(python, 'isolated-cache') };
97
+ if (process.platform === 'darwin') {
98
+ const bin = join(root, 'debug-bin-' + randomUUID());
99
+ await mkdir(bin, { mode: 0o700 });
100
+ const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'", launch = join(root, 'debugger-run', 'current.json');
101
+ await writeFile(join(bin, 'lldb'), '#!/bin/sh\n' +
102
+ 'directory=' + quote(root) + '\nif [ -f ' + quote(launch) + ' ]; then\n directory=$(/usr/bin/plutil -extract cwd raw -o - ' + quote(launch) + ') || exit 1\nfi\n' +
103
+ 'cd "$directory" || exit 1\nexec ' + quote(process.execPath) + ' ' + quote(fileURLToPath(new URL('./debugger-supervisor.js', import.meta.url))) + ' ' + quote(root) + ' "$@"\n', { mode: 0o500 });
104
+ debuggerEnvironment.PATH = bin + delimiter + (this.c.environment.PATH ?? process.env.PATH ?? '/usr/bin:/bin');
105
+ }
106
+ }
107
+ const main = mode === 'gui' ? 'InsightGuiMain' : 'InsightHeadlessMain';
108
+ const args = mode === 'gui' ? [root, 'view', String(endpointPort)] : ['--bind', '127.0.0.1', '--port', String(endpointPort)];
109
+ const log = await open(join(root, 'runtime.log'), 'a', 0o600);
110
+ const child = spawn(this.inputs.java, ['-Xmx768m', '-Djava.awt.headless=' + String(mode !== 'gui'), '-Dghidra.home=' + this.inputs.ghidraHome,
111
+ ...nativeJvmDirectories(root), ...nativeExtensionArguments(this.inputs), '-cp', classes + delimiter + this.inputs.classpath, main, ...args], { cwd: root, detached: true, windowsHide: true, stdio: ['ignore', log.fd, log.fd],
112
+ env: { PATH: this.c.environment.PATH ?? process.env.PATH, HOME: root, TMPDIR: root, XDG_RUNTIME_DIR: root,
113
+ GHIDRA_MCP_AUTH_TOKEN: token, GHIDRA_MCP_FILE_ROOT: root, GHIDRA_MCP_SHARED_PROJECT_DIR: root,
114
+ GHIDRA_MCP_ALLOW_SCRIPTS: '1', ...debuggerEnvironment, ...credentials } });
115
+ child.on('error', () => { });
116
+ child.unref();
117
+ await log.close();
118
+ if (!child.pid)
119
+ fail('launch', 'Owned Ghidra context could not be launched.');
120
+ const binding = { pid: child.pid, endpoint: `http://127.0.0.1:${endpointPort}`, root, target: target.id,
121
+ ...(context ? { context } : {}), kind, mode, program, tokenSecret, runtimeFingerprint: this.inputs.fingerprint };
122
+ await durable(join(root, 'process.json'), binding);
123
+ const record = { binding, http: new HttpBridge(binding.endpoint, this.c.fetch, this.c.signal, token), child };
124
+ this.running.set(root, record);
125
+ const deadline = Date.now() + 90000;
126
+ try {
127
+ while (Date.now() < deadline && child.exitCode === null && child.signalCode === null && !this.c.signal.aborted) {
128
+ try {
129
+ const info = await record.http.instance();
130
+ if (info.pid !== binding.pid || info.tcp_port !== endpointPort)
131
+ fail('binding', 'Context endpoint belongs to a different native process.');
132
+ return record;
133
+ }
134
+ catch {
135
+ await delay(100);
136
+ }
137
+ }
138
+ fail('launch', 'Owned Ghidra context did not become ready; its stage is retained.');
139
+ }
140
+ catch (error) {
141
+ if (child.exitCode === null && child.signalCode === null)
142
+ child.kill('SIGTERM');
143
+ for (let i = 0; i < 100 && child.exitCode === null && child.signalCode === null; i++)
144
+ await delay(50);
145
+ this.running.delete(root);
146
+ throw error;
147
+ }
148
+ }
149
+ async verified(binding, target, verifySource = true) {
150
+ const root = resolve(binding.root);
151
+ if (!root.startsWith(resolve(this.directory()) + sep) || await realpath(root) !== root || binding.target !== target.id || binding.runtimeFingerprint !== this.inputs.fingerprint)
152
+ fail('binding', 'Native context receipt does not match its profile, target or runtime.');
153
+ if (stable(JSON.parse(await readFile(join(root, 'process.json'), 'utf8'))) !== stable(binding))
154
+ fail('binding', 'Native context receipt changed.');
155
+ const token = await this.c.secrets.require(binding.tokenSecret);
156
+ const http = new HttpBridge(binding.endpoint, this.c.fetch, this.c.signal, token);
157
+ const info = await http.instance();
158
+ if (info.pid !== binding.pid || info.tcp_port !== Number(new URL(binding.endpoint).port) ||
159
+ !String(info.project_path ?? '').startsWith(root + sep))
160
+ fail('binding', 'Live context PID, endpoint or project no longer matches its receipt.');
161
+ const record = { ...this.running.get(root), binding, http };
162
+ this.running.set(root, record);
163
+ if (verifySource)
164
+ await this.source(record, target);
165
+ return record;
166
+ }
167
+ async source(record, target) {
168
+ const result = object(await record.http.call('/get_program_options', { program: record.binding.program, group: 'Program Information' }));
169
+ if (!Array.isArray(result.options) || object(result.options.find(value => object(value).name === 'Executable SHA256') ?? {}).value !== target.binarySha256)
170
+ fail('binding', 'Opened native program does not match the target input bytes.');
171
+ return object(await record.http.call('/get_metadata', { program: record.binding.program }));
172
+ }
173
+ async stopRecord(record, ready = true, releaseCapacity = true) {
174
+ const { binding, child } = record;
175
+ const childFinished = child && child.pid === binding.pid && (child.exitCode !== null || child.signalCode !== null);
176
+ if (!childFinished && alive(binding.pid)) {
177
+ if (ready)
178
+ await this.verified(binding, this.workspace.target(binding.target), false);
179
+ else if (!child || child.pid !== binding.pid)
180
+ fail('binding', 'Unready process may only be stopped through its original child handle.');
181
+ if (ready && binding.mode === 'gui') {
182
+ const token = await this.c.secrets.require(binding.tokenSecret), http = new HttpBridge(binding.endpoint, this.c.fetch, new AbortController().signal, token, 60000);
183
+ await this.debuggerHelperRecord({ ...record, http }, 'Stop', 'INSIGHT_DEBUGGER_STOPPED');
184
+ }
185
+ process.kill(binding.pid, 'SIGTERM');
186
+ for (let i = 0; i < 100 && alive(binding.pid); i++)
187
+ await delay(50);
188
+ if (alive(binding.pid))
189
+ fail('busy', 'Owned context did not stop; its stage is retained.');
190
+ }
191
+ if (binding.mode === 'gui')
192
+ await this.debuggerReleased(binding);
193
+ await durable(join(binding.root, 'stopped.json'), { pid: binding.pid, tokenSecret: binding.tokenSecret });
194
+ if (releaseCapacity && binding.context)
195
+ this.capacity.slots.delete(binding.context);
196
+ this.running.delete(binding.root);
197
+ }
198
+ async debuggerReleased(binding) {
199
+ const root = resolve(binding.root);
200
+ if (!root.startsWith(resolve(this.directory()) + sep) || await realpath(root) !== root ||
201
+ stable(JSON.parse(await readFile(join(root, 'process.json'), 'utf8'))) !== stable(binding))
202
+ fail('binding', 'Debugger lifetime binding moved outside its owned context.');
203
+ const directory = join(binding.root, 'debugger-supervisors'), deadline = Date.now() + 15000;
204
+ for (;;) {
205
+ let names;
206
+ try {
207
+ names = await readdir(directory);
208
+ }
209
+ catch (error) {
210
+ if (error.code === 'ENOENT')
211
+ return;
212
+ throw error;
213
+ }
214
+ let pending = false;
215
+ for (const name of names.filter(name => /^[a-f0-9-]{36}\.json$/.test(name))) {
216
+ const path = join(directory, name);
217
+ if (await realpath(path) !== resolve(path))
218
+ fail('binding', 'Debugger lifetime receipt moved.');
219
+ const row = object(JSON.parse(await readFile(path, 'utf8')));
220
+ if (row.schema !== 1 || row.id + '.json' !== name || row.root !== binding.root || row.context !== binding.context || row.target !== binding.target)
221
+ fail('binding', 'Debugger lifetime receipt changed.');
222
+ if (row.guiPid !== binding.pid || row.ownerToken !== binding.tokenSecret)
223
+ continue;
224
+ if (row.state === 'closed' && row.inferiorTerminated === true)
225
+ continue;
226
+ if (row.state === 'failed')
227
+ fail('blocked', 'Owned debugger termination remains unverified; its receipt and trace are retained.');
228
+ pending = true;
229
+ }
230
+ if (!pending)
231
+ return;
232
+ if (Date.now() >= deadline)
233
+ fail('busy', 'Owned debugger cleanup is still pending; its context is retained.');
234
+ await delay(100);
235
+ }
236
+ }
237
+ async capture(record) {
238
+ const result = object(await record.http.call('/save_program', { program: record.binding.program }));
239
+ if (result.success !== true)
240
+ fail('save', 'Private program save was not acknowledged.');
241
+ const output = join(record.binding.root, 'exports');
242
+ await mkdir(output, { recursive: true, mode: 0o700 });
243
+ const name = 'saved-' + randomUUID() + '.gzf';
244
+ const exported = object(await record.http.call('/export_program', { program_name: record.binding.program, output_dir: output, output_name: name }));
245
+ if (exported.success !== true)
246
+ fail('save', 'Saved private snapshot export was not acknowledged.');
247
+ return this.workspace.captureSnapshot(join(output, name));
248
+ }
249
+ async shared(target, binding) {
250
+ const root = await this.prepareRoot('shared', target.id);
251
+ const server = this.server();
252
+ const record = await this.launch(root, target, 'shared', programPath(target.id), 'headless', undefined, await server.credentials(binding, 'reader'));
253
+ try {
254
+ await record.http.call('/server/connect');
255
+ await record.http.call('/open_project', { path: binding.origin + '/' + binding.repository });
256
+ const opened = object(await record.http.call('/open_program', { path: record.binding.program }));
257
+ if (opened.read_only !== true || opened.path !== record.binding.program)
258
+ fail('authorization', 'Shared baseline must open through verified read-only authority.');
259
+ await this.verified(record.binding, target);
260
+ return record;
261
+ }
262
+ catch (error) {
263
+ await this.stopRecord(record, false);
264
+ throw error;
265
+ }
266
+ }
267
+ async retireLegacyBootstrap(target) {
268
+ const parent = join(this.directory(), 'bootstrap', target.id);
269
+ let entries;
270
+ try {
271
+ entries = await readdir(parent, { withFileTypes: true });
272
+ }
273
+ catch (error) {
274
+ if (error.code === 'ENOENT')
275
+ return;
276
+ throw error;
277
+ }
278
+ for (const entry of entries) {
279
+ if (!entry.isDirectory())
280
+ continue;
281
+ const root = join(parent, entry.name);
282
+ if (await realpath(root) !== resolve(root))
283
+ fail('binding', 'Legacy initial-import stage moved outside owned storage.');
284
+ const binding = JSON.parse(await readFile(join(root, 'process.json'), 'utf8'));
285
+ if (binding.root !== root || binding.target !== target.id || binding.kind !== 'bootstrap' || binding.program !== programPath(target.id))
286
+ fail('binding', 'Legacy bootstrap receipt changed.');
287
+ let stopped = false;
288
+ try {
289
+ const receipt = object(JSON.parse(await readFile(join(root, 'stopped.json'), 'utf8')));
290
+ stopped = receipt.pid === binding.pid && receipt.tokenSecret === binding.tokenSecret;
291
+ }
292
+ catch (error) {
293
+ if (error.code !== 'ENOENT')
294
+ throw error;
295
+ }
296
+ if (stopped)
297
+ continue;
298
+ if (alive(binding.pid))
299
+ await this.stopRecord(await this.verified(binding, target, false));
300
+ }
301
+ }
302
+ async initialModel(target) {
303
+ const model = this.workspace.model(target.id);
304
+ if (model.state === 'ready')
305
+ return;
306
+ const server = this.server(), binding = await server.ensure();
307
+ if (!await initialIntent(this.workspace, target.id)) {
308
+ // Old builds imported into an unversioned local shared-project stage. First stop
309
+ // its authenticated worker; a failed probe is never treated as authoritative absence.
310
+ if (model.state === 'unknown')
311
+ await this.retireLegacyBootstrap(target);
312
+ const status = await server.programStatus(binding, programPath(target.id));
313
+ if (status.state !== 'absent')
314
+ fail('unknown', 'Canonical initial target already exists or is reserved; retain its original stage for independent reconciliation.');
315
+ }
316
+ await initializeModel(this.workspace, target, binding, server, this.inputs.fingerprint);
317
+ }
318
+ async model(target) {
319
+ let task = this.modelTasks.get(target.id);
320
+ if (!task) {
321
+ task = this.initialModel(target).finally(() => { if (this.modelTasks.get(target.id) === task)
322
+ this.modelTasks.delete(target.id); });
323
+ this.modelTasks.set(target.id, task);
324
+ }
325
+ await task;
326
+ return this.workspace.model(target.id);
327
+ }
328
+ async privateCopy(context, snapshot) {
329
+ if (!this.capacity.inventory)
330
+ this.capacity.inventory = this.inventory();
331
+ await this.capacity.inventory;
332
+ const configured = this.c.profile.values.maxContexts, max = configured === undefined ? 4 : Number(configured);
333
+ if (!Number.isSafeInteger(max) || max < 1 || max > 16)
334
+ fail('configuration', 'maxContexts must be an integer from 1 to 16.');
335
+ if (!this.capacity.slots.has(context.id) && this.capacity.slots.size >= max)
336
+ fail('limit', 'Owned context capacity is occupied; close a saved context before opening another.');
337
+ this.capacity.slots.add(context.id);
338
+ let record;
339
+ try {
340
+ const target = this.workspace.target(context.target), root = await this.prepareRoot('context', target.id, context.id);
341
+ const file = join(root, 'saved.gzf');
342
+ await link(await this.workspace.artifacts.verify(snapshot), file);
343
+ record = await this.launch(root, target, 'context', '/program', 'headless', context.id);
344
+ const project = object(await record.http.call('/create_project', { parentDir: root, name: 'view' }));
345
+ if (project.success !== true)
346
+ fail('launch', 'Private context project creation was not acknowledged.');
347
+ const imported = object(await record.http.call('/import_program', { gzf_path: file, target_folder: '/', target_name: 'program', overwrite: false }));
348
+ if (imported.success !== true)
349
+ fail('launch', 'Private context snapshot import was not acknowledged.');
350
+ const opened = object(await record.http.call('/open_program', { path: record.binding.program }));
351
+ if (opened.path !== record.binding.program || opened.read_only === true)
352
+ fail('binding', 'Private context did not open its writable saved view.');
353
+ await this.verified(record.binding, target);
354
+ record.binding.baseRevision = context.baseRevision ?? this.workspace.model(context.target).sharedRevision;
355
+ record.binding.viewEpoch = context.viewEpoch ?? 0;
356
+ await durable(join(root, 'process.json'), record.binding);
357
+ await durable(join(this.directory(), 'context', context.id, 'current.json'), record.binding);
358
+ return record;
359
+ }
360
+ catch (error) {
361
+ if (record)
362
+ await this.stopRecord(record, false);
363
+ else
364
+ this.capacity.slots.delete(context.id);
365
+ throw error;
366
+ }
367
+ }
368
+ async open(scope, request) {
369
+ const context = this.workspace.context(scope), target = this.workspace.target(scope.target);
370
+ if (this.workspace.unresolved(scope).some(intent => intent.id !== request))
371
+ fail('blocked', 'This context retains an unresolved operation; reconcile its recorded outcome before continuing.');
372
+ let binding;
373
+ try {
374
+ binding = JSON.parse(await readFile(join(this.directory(), 'context', context.id, 'current.json'), 'utf8'));
375
+ }
376
+ catch (error) {
377
+ if (error.code !== 'ENOENT')
378
+ throw error;
379
+ }
380
+ let stopped = false;
381
+ if (binding) {
382
+ try {
383
+ const receipt = object(JSON.parse(await readFile(join(binding.root, 'stopped.json'), 'utf8')));
384
+ stopped = receipt.pid === binding.pid && receipt.tokenSecret === binding.tokenSecret;
385
+ }
386
+ catch (error) {
387
+ if (error.code !== 'ENOENT')
388
+ throw error;
389
+ }
390
+ }
391
+ if (binding && !stopped && alive(binding.pid) && ((binding.viewEpoch ?? 0) !== (context.viewEpoch ?? 0) ||
392
+ binding.baseRevision !== context.baseRevision && context.baseRevision !== undefined)) {
393
+ await this.stopRecord(await this.verified(binding, target), true, false);
394
+ stopped = true;
395
+ }
396
+ if (binding && !stopped && alive(binding.pid)) {
397
+ if (binding.context !== context.id)
398
+ fail('binding', 'Native process belongs to a different working context.');
399
+ await this.verified(binding, target);
400
+ return context;
401
+ }
402
+ if (binding?.mode === 'gui')
403
+ await this.debuggerReleased(binding);
404
+ let model = context.workingSnapshot ? this.workspace.model(target.id) : await this.model(target);
405
+ if (!context.workingSnapshot) {
406
+ // A new session begins at the actual server revision, including external publications.
407
+ // Existing contexts keep their saved private view until an explicit refresh.
408
+ const shared = await this.latest(target.id);
409
+ model = this.workspace.observeShared(target.id, shared.revision, shared.snapshot);
410
+ }
411
+ const snapshot = context.workingSnapshot ?? model.sharedSnapshot;
412
+ if (!snapshot || !model.sharedRevision)
413
+ fail('binding', 'Canonical target has no saved base.');
414
+ await this.privateCopy(context, snapshot);
415
+ if (context.workingSnapshot)
416
+ return this.workspace.saveContext(scope, context.generation, { state: 'ready' });
417
+ return this.workspace.saveContext(scope, context.generation, { state: 'ready', baseRevision: model.sharedRevision,
418
+ baseSnapshot: snapshot, workingSnapshot: snapshot });
419
+ }
420
+ async selected(scope) {
421
+ const context = this.workspace.context(scope);
422
+ const binding = JSON.parse(await readFile(join(this.directory(), 'context', context.id, 'current.json'), 'utf8'));
423
+ if (binding.context !== context.id)
424
+ fail('binding', 'Context process binding changed.');
425
+ return this.verified(binding, this.workspace.target(scope.target));
426
+ }
427
+ async call(scope, operation, parameters = {}) {
428
+ if (Object.hasOwn(parameters, 'program'))
429
+ fail('invalid', 'Program routing is owned by the session and target selectors.');
430
+ if (operation === '/set_variables' && typeof parameters.variables !== 'string')
431
+ parameters = { ...parameters, variables: JSON.stringify(parameters.variables) };
432
+ if (operation === '/create_function_signature' && typeof parameters.parameters !== 'string')
433
+ parameters = { ...parameters, parameters: JSON.stringify(parameters.parameters) };
434
+ const record = await this.selected(scope);
435
+ if (operation.startsWith('/sdk/')) {
436
+ if (operation === '/sdk/type-archive-import') {
437
+ const root = join(record.binding.root, 'sdk-archives', randomUUID());
438
+ await mkdir(root, { recursive: true, mode: 0o700 });
439
+ const path = join(root, 'input.gdt');
440
+ await link(await this.workspace.artifacts.verify(parameters.archive), path);
441
+ parameters = { ...parameters, input_path: path };
442
+ }
443
+ return nativeQuery(record.http, record.binding.program, operation, parameters, sdkWrites.has(operation));
444
+ }
445
+ if (nativeQueries.has(operation))
446
+ return nativeQuery(record.http, record.binding.program, operation, parameters);
447
+ if (operation === '/set_function_prototype' && parameters.calling_convention === 'default') {
448
+ const validation = object(await nativeQuery(record.http, record.binding.program, '/validate_function_prototype', {
449
+ function: parameters.function_address, prototype: parameters.prototype, calling_convention: 'default'
450
+ }));
451
+ if (validation.valid !== true)
452
+ fail('invalid', 'Prototype or default convention could not be resolved by Ghidra.');
453
+ parameters = { ...parameters, calling_convention: object(validation.signature).calling_convention };
454
+ }
455
+ if (operation === '/apply_function_documentation') {
456
+ const name = 'InsightApplyDocumentation_' + randomUUID().replaceAll('-', '');
457
+ const source = await readFile(new URL('../../references/native/InsightApplyDocumentation.java', import.meta.url), 'utf8');
458
+ return record.http.call('/run_script_inline', { program: record.binding.program,
459
+ code: source.replace('class InsightApplyDocumentation ', `class ${name} `),
460
+ args: Buffer.from(JSON.stringify(parameters.json_body), 'utf8').toString('base64') });
461
+ }
462
+ if (operation === '/configure_analyzer' && (this.inputs.extensionRoots?.length ?? 0) > 0) {
463
+ // A packed program need not persist disabled/default analyzer option registrations.
464
+ // Register this JVM's actual applicable extension analyzers before the pinned handler's
465
+ // Options.contains check. The surrounding program request already owns its journal.
466
+ const name = 'InsightAnalyzerOptions_' + randomUUID().replaceAll('-', '');
467
+ const code = `import ghidra.app.script.GhidraScript;
468
+ public class ${name} extends GhidraScript {public void run(){ghidra.app.plugin.core.analysis.AutoAnalysisManager.getAnalysisManager(currentProgram).initializeOptions();println("INSIGHT_ANALYZER_OPTIONS_READY");}}`;
469
+ const initialized = object(await record.http.call('/run_script_inline', { program: record.binding.program, code }));
470
+ if (initialized.success !== true || typeof initialized.console_output !== 'string' || !initialized.console_output.includes('INSIGHT_ANALYZER_OPTIONS_READY'))
471
+ fail('partial', 'Installed analyzer option registration could not be established.');
472
+ }
473
+ if (operation === '/run_ghidra_script') {
474
+ const script = String(parameters.script_name), path = await scriptPath(this.inputs.ghidraHome, record.binding.root, script, this.inputs.extensionRoots);
475
+ const value = object(await record.http.call(operation, { ...parameters, script_name: path, program: record.binding.program }));
476
+ delete value.script_path;
477
+ value.script_name = script;
478
+ return value;
479
+ }
480
+ if (operation === '/clone_data_type') {
481
+ // The pinned handler calls clone(manager), which can return the source itself.
482
+ // copy(manager) preserves that source while creating an independent type.
483
+ const name = 'InsightCopyType_' + randomUUID().replaceAll('-', '');
484
+ const code = `import ghidra.app.script.GhidraScript;
485
+ import ghidra.program.model.data.*;
486
+ public class ${name} extends GhidraScript {
487
+ public void run() throws Exception {
488
+ String[] args=getScriptArgs();
489
+ String original=new String(java.util.Base64.getDecoder().decode(args[0]),java.nio.charset.StandardCharsets.UTF_8);
490
+ String desired=new String(java.util.Base64.getDecoder().decode(args[1]),java.nio.charset.StandardCharsets.UTF_8);
491
+ DataTypeManager manager=currentProgram.getDataTypeManager();DataType source=null;
492
+ var types=manager.getAllDataTypes();while(types.hasNext()){DataType value=types.next();if(value.getName().equals(original)||value.getPathName().equals(original)){if(source!=null)throw new IllegalArgumentException("Ambiguous source type");source=value;}}
493
+ if(source==null)throw new IllegalArgumentException("Source type not found");
494
+ int transaction=currentProgram.startTransaction("Copy data type");boolean committed=false;
495
+ try {DataType copied=source.copy(manager);if(copied==source)throw new IllegalStateException("Copy reused its source");copied.setName(desired);
496
+ manager.addDataType(copied,DataTypeConflictHandler.REPLACE_HANDLER);committed=true;}
497
+ finally {currentProgram.endTransaction(transaction,committed);}
498
+ println("insight-copy-type:completed");
499
+ }
500
+ }`;
501
+ return record.http.call('/run_script_inline', { program: record.binding.program, code,
502
+ args: [parameters.source_type, parameters.new_name].map(value => Buffer.from(text(value, 512), 'utf8').toString('base64')).join(' ') });
503
+ }
504
+ const descriptor = await record.http.descriptor(operation);
505
+ return record.http.call(operation, { ...parameters, ...(descriptor.params.some(p => p.name === 'program') ? { program: record.binding.program } : {}) });
506
+ }
507
+ async saved(scope) { return this.capture(await this.selected(scope)); }
508
+ async scripts(scope, filter, limit, offset) {
509
+ const record = await this.selected(scope);
510
+ return scriptInventory(this.inputs.ghidraHome, record.binding.root, filter, limit, offset, this.inputs.extensionRoots);
511
+ }
512
+ async inspect(snapshot, parameters) {
513
+ const root = join(this.directory(), 'inspection', randomUUID());
514
+ await mkdir(root, { recursive: true, mode: 0o700 });
515
+ if (parameters.kind === 'sdk' && parameters.operation === 'type-archive-import') {
516
+ const p = object(parameters.parameters), path = join(root, 'input.gdt');
517
+ await link(await this.workspace.artifacts.verify(p.archive), path);
518
+ parameters = { ...parameters, parameters: { ...p, input_path: path } };
519
+ }
520
+ const request = join(root, 'request.json');
521
+ await durable(request, parameters);
522
+ const result = await this.server().inspect(await this.workspace.artifacts.verify(snapshot), request);
523
+ return object(result.value);
524
+ }
525
+ async removePeer(record, path) {
526
+ // A cancelled invocation must still release its verified owned temporary program.
527
+ const cleanup = this.fork({ ...this.c, signal: new AbortController().signal });
528
+ const checked = await cleanup.verified(record.binding, this.workspace.target(record.binding.target));
529
+ const closed = object(await checked.http.call('/close_program', { name: path, save: false }));
530
+ if (closed.success !== true)
531
+ fail('binding', 'Owned cross-target snapshot could not be closed.');
532
+ const removed = object(await checked.http.call('/delete_file', { filePath: path }));
533
+ if (removed.success !== true)
534
+ fail('binding', 'Owned cross-target snapshot could not be removed.');
535
+ }
536
+ async reconcilePeers(record) {
537
+ const parent = join(record.binding.root, 'cross-peer');
538
+ let entries;
539
+ try {
540
+ entries = await readdir(parent, { withFileTypes: true });
541
+ }
542
+ catch (error) {
543
+ if (error.code === 'ENOENT')
544
+ return;
545
+ throw error;
546
+ }
547
+ for (const entry of entries) {
548
+ if (!entry.isDirectory() || !/^__insight_peer_[a-f0-9]{32}$/.test(entry.name))
549
+ fail('binding', 'Owned cross-target stage inventory changed.');
550
+ const folder = join(parent, entry.name);
551
+ if (await realpath(folder) !== folder)
552
+ fail('binding', 'Cross-target stage left its owned directory.');
553
+ const receipt = object(JSON.parse(await readFile(join(folder, 'receipt.json'), 'utf8'))), path = '/' + entry.name;
554
+ if (receipt.path !== path || receipt.target !== record.binding.target || receipt.context !== record.binding.context)
555
+ fail('binding', 'Cross-target cleanup receipt belongs to another context.');
556
+ if (receipt.state === 'closed')
557
+ continue;
558
+ if (receipt.state !== 'preparing')
559
+ fail('binding', 'Unknown cross-target cleanup stage.');
560
+ await this.removePeer(record, path);
561
+ await durable(join(folder, 'receipt.json'), { ...receipt, state: 'closed' });
562
+ }
563
+ }
564
+ async withPeer(record, snapshot, action, reconcile = true) {
565
+ if (reconcile)
566
+ await this.reconcilePeers(record);
567
+ const name = '__insight_peer_' + randomUUID().replaceAll('-', ''), path = '/' + name, folder = join(record.binding.root, 'cross-peer', name);
568
+ await mkdir(folder, { recursive: true, mode: 0o700 });
569
+ const file = join(folder, 'input.gzf');
570
+ await link(await this.workspace.artifacts.verify(snapshot), file);
571
+ await durable(join(folder, 'receipt.json'), { path, target: record.binding.target, context: record.binding.context, state: 'preparing' });
572
+ try {
573
+ const imported = object(await record.http.call('/import_program', { gzf_path: file, target_folder: '/', target_name: name, overwrite: false }));
574
+ if (imported.success !== true)
575
+ fail('binding', 'Cross-target snapshot import was not acknowledged.');
576
+ const opened = object(await record.http.call('/open_program', { path }));
577
+ if (opened.path !== path)
578
+ fail('binding', 'Cross-target snapshot opened another program.');
579
+ return await action(path);
580
+ }
581
+ finally {
582
+ await this.removePeer(record, path);
583
+ await durable(join(folder, 'receipt.json'), { path, target: record.binding.target, context: record.binding.context, state: 'closed' });
584
+ }
585
+ }
586
+ async pair(selected, other, snapshot, operation, parameters) {
587
+ const record = await this.selected(selected);
588
+ return this.withPeer(record, snapshot, async (peer) => {
589
+ if (operation === '/sdk/changes')
590
+ return nativeQuery(record.http, record.binding.program, operation, { ...parameters, peer_program: peer });
591
+ let routing;
592
+ if (operation === '/diff_functions')
593
+ routing = { program_a: record.binding.program, program_b: peer };
594
+ else if (operation === '/compare_programs_documentation')
595
+ routing = { program: record.binding.program };
596
+ else if (['/bulk_fuzzy_match', '/find_similar_functions_fuzzy'].includes(operation))
597
+ routing = { source_program: record.binding.program, target_program: peer };
598
+ else
599
+ fail('invalid', 'Unsupported cross-target comparison.');
600
+ const value = object(await record.http.call(operation, { ...parameters, ...routing }));
601
+ if (operation === '/compare_programs_documentation') {
602
+ if (!Array.isArray(value.programs))
603
+ fail('partial', 'Comparison omitted its programs.');
604
+ value.programs = value.programs.map(raw => {
605
+ const row = object(raw), who = row.path === record.binding.program ? selected : row.path === peer ? other : undefined;
606
+ if (!who)
607
+ fail('binding', 'Comparison included an undeclared context.');
608
+ const { name, path, is_current, ...counts } = row;
609
+ return { ...who, label: this.workspace.target(who.target).name, ...counts };
610
+ });
611
+ }
612
+ return logicalPrograms(value, new Map([[record.binding.program, selected], ['program', selected], [peer, other], [peer.slice(1), other]]));
613
+ });
614
+ }
615
+ async transfer(selected, source, dryRun) {
616
+ const record = await this.selected(selected);
617
+ return this.withPeer(record, source, peer => record.http.call('/merge_program_documentation', { source: peer, target: record.binding.program, dry_run: dryRun }));
618
+ }
619
+ async transferCheck(selected, source, candidate) {
620
+ const record = await this.selected(selected);
621
+ return this.withPeer(record, source, from => this.withPeer(record, candidate, to => record.http.call('/merge_program_documentation', { source: from, target: to, dry_run: true }), false));
622
+ }
623
+ async mode(scope, mode) {
624
+ const record = await this.selected(scope);
625
+ if (record.binding.mode === mode)
626
+ return;
627
+ const context = this.workspace.context(scope), snapshot = await this.capture(record);
628
+ const debuggerState = record.binding.mode === 'gui' && context.debugger?.traces.length ? await this.debuggerSaved(scope) : context.debugger;
629
+ const complexChanges = await this.debuggerMetadataChanges(context, snapshot);
630
+ this.workspace.saveContext(scope, context.generation, { workingSnapshot: snapshot, complexChanges, ...(debuggerState ? { debugger: { ...debuggerState, connected: false, traces: debuggerState.traces.map(trace => ({ ...trace, connected: false })) } } : {}) });
631
+ await this.stopRecord(record, true, false);
632
+ if (mode === 'headless') {
633
+ await this.privateCopy(this.workspace.context(scope), snapshot);
634
+ return;
635
+ }
636
+ const gui = await this.launch(record.binding.root, this.workspace.target(scope.target), 'context', record.binding.program, 'gui', context.id);
637
+ gui.binding.baseRevision = record.binding.baseRevision;
638
+ gui.binding.viewEpoch = record.binding.viewEpoch;
639
+ await durable(join(gui.binding.root, 'process.json'), gui.binding);
640
+ await gui.http.call('/open_program', { path: gui.binding.program });
641
+ await this.verified(gui.binding, this.workspace.target(scope.target));
642
+ await durable(join(this.directory(), 'context', context.id, 'current.json'), gui.binding);
643
+ }
644
+ async project(operation, parameters) { return this.server().project(operation, parameters); }
645
+ async ui(scope, operation, p) {
646
+ const record = await this.selected(scope);
647
+ await this.reconcilePeers(record);
648
+ if (operation === 'plugins_list' || operation === 'plugins_set') {
649
+ if (record.binding.mode !== 'gui')
650
+ fail('configuration', 'Managed plugins require the owned GUI context.');
651
+ const base = 'InsightPlugins', name = base + '_' + randomUUID().replaceAll('-', ''), source = await readFile(new URL('../../references/native/' + base + '.java', import.meta.url), 'utf8');
652
+ const result = object(await record.http.call('/run_script_inline', { program: record.binding.program, code: source.replace('class ' + base + ' ', `class ${name} `), args: Buffer.from(JSON.stringify({ ...p, operation: operation === 'plugins_list' ? 'list' : 'set' })).toString('base64') }));
653
+ const lines = typeof result.console_output === 'string' ? result.console_output.split(/\r?\n/).filter(line => line.includes('INSIGHT_PLUGINS:')) : [];
654
+ if (result.success !== true || lines.length !== 1)
655
+ fail('partial', 'Managed plugin effect was not established.');
656
+ return object(JSON.parse(lines[0].slice(lines[0].indexOf('INSIGHT_PLUGINS:') + 16)));
657
+ }
658
+ const paths = { cursor: '/get_ui_cursor', tools: '/tool/running_tools', goto: '/tool/goto_address', prompt_policy: '/prompt_policy' }, path = paths[operation];
659
+ if (!path)
660
+ fail('invalid', 'Unsupported UI operation.');
661
+ const target = this.workspace.target(scope.target), metadata = await this.source(record, target), name = text(metadata.program_name);
662
+ const value = await record.http.call(path, operation === 'cursor' ? { type: 'all' } : p);
663
+ return uiResult(operation, p, value, scope, record.binding.program, target.name, name);
664
+ }
665
+ async archivePayloads(scope, p) {
666
+ const record = await this.selected(scope), base = 'InsightArchivePayloads', name = base + '_' + randomUUID().replaceAll('-', '');
667
+ const source = await readFile(new URL('../../references/native/' + base + '.java', import.meta.url), 'utf8');
668
+ const result = object(await record.http.call('/run_script_inline', { program: record.binding.program, code: source.replace('class ' + base + ' ', `class ${name} `), args: Buffer.from(JSON.stringify(p)).toString('base64') }));
669
+ if (result.success !== true || typeof result.console_output !== 'string')
670
+ fail('partial', 'Archive payload preparation failed.');
671
+ const lines = result.console_output.split(/\r?\n/).filter(line => line.includes('INSIGHT_ARCHIVE_PAYLOADS '));
672
+ if (lines.length !== 1)
673
+ fail('partial', 'Archive payload preparation omitted its exact result.');
674
+ return object(JSON.parse(lines[0].slice(lines[0].indexOf('INSIGHT_ARCHIVE_PAYLOADS ') + 'INSIGHT_ARCHIVE_PAYLOADS '.length)));
675
+ }
676
+ async debuggerStart(scope) {
677
+ await this.debuggerHelper(scope, 'Start', 'INSIGHT_DEBUGGER_READY');
678
+ const saved = this.workspace.context(scope).debugger;
679
+ if (saved?.traces.length) {
680
+ const record = await this.selected(scope), marker = join(record.binding.root, 'traces-restored.json');
681
+ try {
682
+ const value = object(JSON.parse(await readFile(marker, 'utf8')));
683
+ if (value.pid === record.binding.pid && value.tokenSecret === record.binding.tokenSecret)
684
+ return;
685
+ }
686
+ catch (error) {
687
+ if (error.code !== 'ENOENT')
688
+ throw error;
689
+ }
690
+ const traces = await Promise.all(saved.traces.map(async (trace) => ({ id: trace.id, input: await this.workspace.artifacts.verify(trace.snapshot), snap: trace.snap, ...(trace.thread ? { thread: trace.thread } : {}) })));
691
+ await this.traceHelper(scope, { operation: 'restore', traces, ...(saved.active ? { active: saved.active } : {}) });
692
+ await durable(marker, { pid: record.binding.pid, tokenSecret: record.binding.tokenSecret });
693
+ }
694
+ }
695
+ async debuggerStop(scope) {
696
+ return this.debuggerHelper(scope, 'Stop', 'INSIGHT_DEBUGGER_STOPPED');
697
+ }
698
+ async debuggerHelper(scope, action, marker) {
699
+ const record = await this.selected(scope);
700
+ if (record.binding.mode !== 'gui')
701
+ fail('configuration', 'Debugger requires the owned GUI context.');
702
+ return this.debuggerHelperRecord(record, action, marker);
703
+ }
704
+ async debuggerHelperRecord(record, action, marker) {
705
+ const base = 'Insight' + action + 'Debugger', name = base + '_' + randomUUID().replaceAll('-', ''), source = await readFile(new URL('../../references/native/' + base + '.java', import.meta.url), 'utf8');
706
+ const result = object(await record.http.call('/run_script_inline', { program: record.binding.program, code: source.replace('class ' + base + ' ', `class ${name} `) }));
707
+ if (result.success !== true || typeof result.console_output !== 'string' || !result.console_output.includes(marker))
708
+ fail('partial', 'Owned debugger lifecycle effect was not established.');
709
+ }
710
+ async debuggerCall(scope, operation, p = {}) {
711
+ const allowed = ['launch', 'launch_offers', 'status', 'traces', 'resume', 'interrupt', 'step', 'set_breakpoint', 'remove_breakpoint', 'list_breakpoints', 'registers', 'read_memory', 'stack_trace', 'modules', 'static_to_dynamic', 'dynamic_to_static', 'write_memory', 'write_register', 'timeline', 'objects', 'object_values', 'schedule'];
712
+ if (!allowed.includes(operation) || Object.hasOwn(p, 'program'))
713
+ fail('invalid', 'Unsupported scoped debugger operation.');
714
+ const record = await this.selected(scope);
715
+ if (['write_memory', 'write_register', 'timeline', 'objects', 'object_values', 'schedule'].includes(operation))
716
+ return this.traceHelper(scope, { ...p, operation });
717
+ if (operation === 'list_breakpoints')
718
+ return (await this.traceHelper(scope, { operation: 'breakpoints' })).items;
719
+ if (['resume', 'interrupt', 'step', 'set_breakpoint', 'remove_breakpoint'].includes(operation))
720
+ await this.traceHelper(scope, { operation: 'focus' });
721
+ if (operation === 'read_memory')
722
+ await this.traceHelper(scope, { operation: 'read_memory', address: p.address, size: p.size });
723
+ const token = await this.c.secrets.require(record.binding.tokenSecret), timeout = operation === 'launch' ? (Number(p.timeout_seconds ?? 60) + 90) * 1000 : 45000;
724
+ const raw = await new HttpBridge(record.binding.endpoint, this.c.fetch, this.c.signal, token, timeout).call('/debugger/' + operation, { ...p, ...(['launch', 'launch_offers', 'static_to_dynamic'].includes(operation) ? { program: record.binding.program } : {}) });
725
+ const label = this.workspace.target(scope.target).name;
726
+ if (operation === 'modules' && Array.isArray(raw))
727
+ return raw.map(value => { const row = object(value), { path, ...rest } = row; return { ...rest, ...(typeof row.name === 'string' && (row.name.includes(record.binding.root) || ['image', 'image.exe'].includes(row.name)) ? { name: label } : {}) }; });
728
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
729
+ const value = object(raw), { program, executable_path, target_description, ...rest } = value;
730
+ if (operation === 'launch') {
731
+ if (value.status !== 'launched' || executable_path !== p.executable_path || typeof value.trace_name !== 'string')
732
+ fail('binding', 'Debugger launch did not verify its staged executable.');
733
+ await this.traceHelper(scope, { operation: 'bind_new', name: value.trace_name });
734
+ }
735
+ if (program !== undefined) {
736
+ const metadata = await this.source(record, this.workspace.target(scope.target));
737
+ if (program !== metadata.program_name && program !== record.binding.program)
738
+ fail('binding', 'Debugger selected another static program.');
739
+ }
740
+ if (value.trace_name !== undefined)
741
+ rest.trace_name = label;
742
+ if (typeof value.module === 'string' && (value.module.includes(record.binding.root) || ['image', 'image.exe'].includes(value.module)))
743
+ rest.module = label;
744
+ return rest;
745
+ }
746
+ return raw;
747
+ }
748
+ async traceHelper(scope, p) {
749
+ const record = await this.selected(scope), context = this.workspace.context(scope), name = 'InsightTraceState_' + randomUUID().replaceAll('-', '');
750
+ const source = await readFile(new URL('../../references/native/InsightTraceState.java', import.meta.url), 'utf8');
751
+ const args = Buffer.from(JSON.stringify({ ...p, target: scope.target, context: context.id, owned_root: record.binding.root })).toString('base64');
752
+ const result = object(await record.http.call('/run_script_inline', { program: record.binding.program, code: source.replace('class InsightTraceState ', `class ${name} `), args }));
753
+ const output = result.console_output;
754
+ if (result.success !== true || typeof output !== 'string')
755
+ fail('partial', 'Trace state action did not complete.');
756
+ const lines = output.split(/\r?\n/).filter(line => line.includes('INSIGHT_TRACE_STATE '));
757
+ if (lines.length !== 1)
758
+ fail('partial', 'Trace action omitted its exact result marker.');
759
+ return object(JSON.parse(lines[0].slice(lines[0].indexOf('INSIGHT_TRACE_STATE ') + 'INSIGHT_TRACE_STATE '.length)));
760
+ }
761
+ async debuggerSaved(scope) {
762
+ const record = await this.selected(scope), context = this.workspace.context(scope);
763
+ let value, output = '';
764
+ for (let attempt = 0; attempt < 12; attempt++) {
765
+ output = join(record.binding.root, 'trace-exports', randomUUID());
766
+ await mkdir(output, { recursive: true, mode: 0o700 });
767
+ try {
768
+ value = await this.traceHelper(scope, { operation: 'checkpoint', output });
769
+ break;
770
+ }
771
+ catch (error) {
772
+ if (attempt === 11 || !String(error.message).includes('Unable to lock due to active transaction'))
773
+ throw error;
774
+ await delay(150);
775
+ }
776
+ }
777
+ if (!value || !Array.isArray(value.traces) || value.traces.length > 64)
778
+ fail('partial', 'Trace checkpoint inventory is malformed.');
779
+ const traces = [];
780
+ for (const raw of value.traces) {
781
+ const row = object(raw), traceId = text(row.id);
782
+ if (!/^trace_[a-f0-9-]{36}$/.test(traceId))
783
+ fail('binding', 'Trace checkpoint identity changed.');
784
+ const snapshot = await this.workspace.captureSnapshot(join(output, traceId + '.gzf')), observed = await this.server().traceInspect(await this.workspace.artifacts.verify(snapshot));
785
+ if (observed.id !== traceId || observed.target !== scope.target || observed.context !== context.id)
786
+ fail('binding', 'Packed trace belongs to another tool context.');
787
+ if (typeof row.snap !== 'string' || !/^\d+$/.test(row.snap) || !Number.isSafeInteger(observed.modules) || !Number.isSafeInteger(observed.threads))
788
+ fail('partial', 'Packed trace metadata is incomplete.');
789
+ if (typeof row.connected !== 'boolean')
790
+ fail('partial', 'Trace connection state is absent.');
791
+ if (row.thread !== undefined && (typeof row.thread !== 'string' || !/^\d+$/.test(row.thread)))
792
+ fail('partial', 'Trace checkpoint thread identity is invalid.');
793
+ traces.push({ id: traceId, name: text(observed.name), snapshot, snap: row.snap, modules: Number(observed.modules), threads: Number(observed.threads), connected: row.connected, ...(row.thread ? { thread: String(row.thread) } : {}) });
794
+ }
795
+ const active = value.active === undefined ? undefined : text(value.active);
796
+ if (active && !traces.some(trace => trace.id === active))
797
+ fail('binding', 'Active trace is absent from its checkpoint.');
798
+ return { traces, ...(active ? { active } : {}), connected: traces.some(trace => trace.connected) };
799
+ }
800
+ async debuggerSelect(scope, trace) { await this.traceHelper(scope, { operation: 'select', id: trace }); }
801
+ async debuggerLaunch(scope, p, request) {
802
+ if (!/^q_[a-f0-9-]{36}$/.test(request))
803
+ fail('binding', 'Invalid internal debugger launch identity.');
804
+ const record = await this.selected(scope);
805
+ await this.debuggerReleased(record.binding);
806
+ const target = this.workspace.target(scope.target), root = join(record.binding.root, 'debugger-run', request);
807
+ await mkdir(root, { recursive: true, mode: 0o700 });
808
+ const executable = join(root, process.platform === 'win32' ? 'image.exe' : 'image');
809
+ await copyFile(await this.workspace.artifacts.verify(target.input), executable, constants.COPYFILE_EXCL);
810
+ await chmod(executable, 0o500);
811
+ const copied = await this.workspace.artifacts.capture(executable, 'input');
812
+ if (copied.id !== target.input.id)
813
+ fail('binding', 'Staged debug executable differs from the immutable target.');
814
+ const cwd = p.cwd === undefined ? root : text(p.cwd, 4096);
815
+ if (/[\r\n\0]/.test(cwd))
816
+ fail('invalid', 'Debugger working directory must be a single filesystem path.');
817
+ await durable(join(record.binding.root, 'debugger-run', 'current.json'), { request, target: scope.target, context: record.binding.context, executable, cwd });
818
+ return this.debuggerCall(scope, 'launch', { ...p, executable_path: executable, cwd,
819
+ ...(this.c.profile.values.debuggerPython ? { python_executable: text(this.c.profile.values.debuggerPython, 4096) } : {}),
820
+ ...(this.c.profile.values.windbgDir ? { windbg_dir: text(this.c.profile.values.windbgDir, 4096) } : {}) });
821
+ }
822
+ async projectOutcome(requestId, expected) { return this.server().projectOutcome(requestId, expected); }
823
+ async debuggerMetadataChanges(context, snapshot) {
824
+ if (!context.debugger?.traces.length || !context.workingSnapshot)
825
+ return context.complexChanges;
826
+ return await debuggerProgramMetadata(this, context.workingSnapshot) !== await debuggerProgramMetadata(this, snapshot) ? [...context.complexChanges, 'debugger-metadata-' + randomUUID()] : context.complexChanges;
827
+ }
828
+ async close(scope) {
829
+ const context = this.workspace.context(scope), record = await this.selected(scope), snapshot = await this.capture(record);
830
+ const debuggerState = record.binding.mode === 'gui' && context.debugger?.traces.length ? await this.debuggerSaved(scope) : context.debugger;
831
+ const complexChanges = await this.debuggerMetadataChanges(context, snapshot);
832
+ this.workspace.saveContext(scope, context.generation, { state: 'stopped', workingSnapshot: snapshot, complexChanges, ...(debuggerState ? { debugger: { ...debuggerState, connected: false, traces: debuggerState.traces.map(trace => ({ ...trace, connected: false })) } } : {}) });
833
+ await this.stopRecord(record);
834
+ }
835
+ async closed(scope) {
836
+ const context = this.workspace.context(scope), path = join(this.directory(), 'context', context.id, 'current.json');
837
+ const binding = JSON.parse(await readFile(path, 'utf8')), root = resolve(binding.root);
838
+ if (!root.startsWith(resolve(this.directory()) + sep) || await realpath(root) !== root || binding.context !== context.id || binding.target !== scope.target ||
839
+ stable(JSON.parse(await readFile(join(root, 'process.json'), 'utf8'))) !== stable(binding))
840
+ fail('binding', 'Closed context receipt changed.');
841
+ try {
842
+ const stopped = object(JSON.parse(await readFile(join(root, 'stopped.json'), 'utf8')));
843
+ if (stopped.pid === binding.pid && stopped.tokenSecret === binding.tokenSecret)
844
+ return true;
845
+ // A GUI transition reuses the saved project root. Its earlier headless stop is not
846
+ // evidence about this process; verify the current PID/binding independently below.
847
+ }
848
+ catch (error) {
849
+ if (error.code !== 'ENOENT')
850
+ throw error;
851
+ }
852
+ if (!alive(binding.pid))
853
+ return true;
854
+ await this.verified(binding, this.workspace.target(scope.target));
855
+ return false;
856
+ }
857
+ async latest(targetId) {
858
+ const target = this.workspace.target(targetId), server = this.server(), binding = await server.ensure();
859
+ const before = await server.programStatus(binding, programPath(targetId));
860
+ if (before.checkinActive !== false)
861
+ fail('blocked', 'Shared model has an active check-in.');
862
+ const expected = version(before.version), record = await this.shared(target, binding);
863
+ try {
864
+ const opened = await server.programStatus(binding, programPath(targetId));
865
+ if (opened.version !== expected)
866
+ fail('binding', 'Shared model changed while opening its saved view.');
867
+ const output = join(record.binding.root, 'exports');
868
+ await mkdir(output, { recursive: true, mode: 0o700 });
869
+ const exported = object(await record.http.call('/export_program', { program_name: record.binding.program, output_dir: output, output_name: 'shared.gzf' }));
870
+ if (exported.success !== true)
871
+ fail('save', 'Shared snapshot export was not acknowledged.');
872
+ const snapshot = await this.workspace.captureSnapshot(join(output, 'shared.gzf'));
873
+ const after = await server.programStatus(binding, programPath(targetId));
874
+ if (after.version !== expected)
875
+ fail('binding', 'Shared model changed before its snapshot was verified.');
876
+ return { revision: expected, snapshot };
877
+ }
878
+ finally {
879
+ await this.stopRecord(record);
880
+ }
881
+ }
882
+ async fields(snapshot, changes) {
883
+ const directory = join(this.directory(), 'inspection', randomUUID());
884
+ await mkdir(directory, { recursive: true, mode: 0o700 });
885
+ const selectors = join(directory, 'fields.json');
886
+ await durable(selectors, changes.map(change => ({ address: change.address, field: change.field })));
887
+ const result = await this.server().fields(await this.workspace.artifacts.verify(snapshot), selectors), values = object(result.fields);
888
+ for (const change of changes) {
889
+ const item = object(values[change.address + '@' + change.field]);
890
+ if (item.value !== null && typeof item.value !== 'string' || typeof item.exists !== 'boolean' || typeof item.dependency !== 'string' || !/^[a-f0-9]{64}$/.test(item.dependency))
891
+ fail('partial', 'Saved field inspection returned an incomplete value or dependency.');
892
+ }
893
+ return values;
894
+ }
895
+ async applyField(record, change) {
896
+ const result = object(await record.http.call(change.field === 'function.name' ? '/rename_function' : '/set_comment', change.field === 'function.name' ?
897
+ { program: record.binding.program, old_name: change.address, new_name: change.local, strict_mode: 'off' } :
898
+ { program: record.binding.program, address: change.address, type: change.field.slice(8), comment: change.local ?? '' }));
899
+ if (result.status !== 'success' && result.success !== true)
900
+ fail('partial', 'Candidate edit was not acknowledged.');
901
+ if (change.field === 'function.name') {
902
+ const read = object(await record.http.call('/get_functions', { program: record.binding.program, functions: change.address, fields: 'entry_point', include_call_context: false }));
903
+ if (object(object(read.functions)[change.address]).name !== change.local)
904
+ fail('partial', 'Candidate name readback differs.');
905
+ }
906
+ else {
907
+ const read = object(await record.http.call('/get_comment', { program: record.binding.program, address: change.address }));
908
+ if ((read[change.field.slice(8)] || null) !== change.local)
909
+ fail('partial', 'Candidate comment readback differs.');
910
+ }
911
+ }
912
+ async candidate(targetId, snapshot, changes) {
913
+ const target = this.workspace.target(targetId), root = await this.prepareRoot('candidate', targetId);
914
+ await link(await this.workspace.artifacts.verify(snapshot), join(root, 'saved.gzf'));
915
+ const record = await this.launch(root, target, 'candidate', '/program', 'headless');
916
+ try {
917
+ await record.http.call('/create_project', { parentDir: root, name: 'view' });
918
+ await record.http.call('/import_program', { gzf_path: join(root, 'saved.gzf'), target_folder: '/', target_name: 'program', overwrite: false });
919
+ await record.http.call('/open_program', { path: record.binding.program });
920
+ await this.verified(record.binding, target);
921
+ for (const change of changes)
922
+ await this.applyField(record, change);
923
+ return await this.capture(record);
924
+ }
925
+ finally {
926
+ await this.stopRecord(record, false);
927
+ }
928
+ }
929
+ async publish(publication) {
930
+ const server = this.server(), binding = await server.ensure(), target = this.workspace.target(publication.target);
931
+ const directory = join(this.directory(), 'publications', publication.id);
932
+ await mkdir(directory, { recursive: true, mode: 0o700 });
933
+ const result = await server.publish(binding, programPath(target.id), await this.workspace.artifacts.verify(publication.snapshot), publication.expectedRevision, publication.id, join(directory, 'native-intent.json'), target.binarySha256);
934
+ if (result.state === 'conflict' && result.applied === 0)
935
+ return { state: 'conflict', applied: 0 };
936
+ if (result.state !== 'published' || result.version !== publication.expectedRevision + 1 || result.snapshotBuffersVerified !== true || result.ownedCheckoutReleased !== true)
937
+ fail('unknown', 'Native publication did not establish its version and complete saved buffers.');
938
+ const verified = await this.recover(publication);
939
+ if (verified.state !== 'verified')
940
+ fail('unknown', 'Independent exact-version publication verification remains unresolved.');
941
+ return { state: 'published', revision: verified.revision };
942
+ }
943
+ async recover(publication) {
944
+ const server = this.server(), binding = await server.ensure(), target = this.workspace.target(publication.target);
945
+ const journal = join(this.directory(), 'publications', publication.id, 'native-intent.json');
946
+ const result = await server.recoverPublication(binding, programPath(target.id), await this.workspace.artifacts.verify(publication.snapshot), publication.expectedRevision, publication.id, journal, target.binarySha256);
947
+ if (result.state === 'verified' && result.version === publication.expectedRevision + 1 && result.snapshotBuffersVerified === true && result.ownedCheckoutReleased === true)
948
+ return { state: 'verified', revision: version(result.version) };
949
+ return { state: 'unknown' };
950
+ }
951
+ async recoverModel(targetId) {
952
+ if (this.workspace.activePublication(targetId))
953
+ return false;
954
+ const model = this.workspace.model(targetId);
955
+ if (model.state === 'ready')
956
+ return true;
957
+ if (model.state === 'opening')
958
+ return true;
959
+ const target = this.workspace.target(targetId), server = this.server(), binding = await server.ensure();
960
+ if (await initialIntent(this.workspace, targetId)) {
961
+ await this.model(target);
962
+ return this.workspace.model(targetId).state === 'ready';
963
+ }
964
+ await this.retireLegacyBootstrap(target);
965
+ let status;
966
+ try {
967
+ status = await server.programStatus(binding, programPath(targetId));
968
+ }
969
+ catch {
970
+ return false;
971
+ }
972
+ if (status.state === 'absent') {
973
+ await this.model(target);
974
+ return this.workspace.model(targetId).state === 'ready';
975
+ }
976
+ if (status.version !== 1 || status.checkinActive !== false || !Array.isArray(status.versions) || status.versions.length !== 1 ||
977
+ object(status.versions[0]).comment !== 'insight target ' + targetId)
978
+ return false;
979
+ const record = await this.shared(target, binding);
980
+ try {
981
+ const metadata = await this.source(record, target);
982
+ if (model.settings.language && metadata.language !== model.settings.language || model.settings.compilerSpec && metadata.compiler !== model.settings.compilerSpec ||
983
+ model.settings.imageBase && String(metadata.base_address).replace(/^0+(?=.)/, '') !== model.settings.imageBase)
984
+ return false;
985
+ const output = join(record.binding.root, 'exports');
986
+ await mkdir(output, { recursive: true, mode: 0o700 });
987
+ const exported = object(await record.http.call('/export_program', { program_name: record.binding.program, output_dir: output, output_name: 'recovered.gzf' }));
988
+ if (exported.success !== true)
989
+ return false;
990
+ const snapshot = await this.workspace.captureSnapshot(join(output, 'recovered.gzf'));
991
+ const after = await server.programStatus(binding, programPath(targetId));
992
+ if (after.version !== 1 || after.checkinActive !== false)
993
+ return false;
994
+ this.workspace.establishModel(targetId, 1, snapshot);
995
+ return true;
996
+ }
997
+ finally {
998
+ await this.stopRecord(record);
999
+ }
1000
+ }
1001
+ async dispose() {
1002
+ // Factory has cancelled invocation signals before resource drain. Cleanup still needs
1003
+ // bounded authenticated reads to establish ownership of previously orphaned children.
1004
+ const cleanup = this.fork({ ...this.c, signal: new AbortController().signal });
1005
+ let failures = 0;
1006
+ try {
1007
+ await cleanup.inventory();
1008
+ }
1009
+ catch {
1010
+ failures++;
1011
+ }
1012
+ for (const record of [...this.running.values()]) {
1013
+ try {
1014
+ // Original child handles also permit cleanup of a failed, unready owned stage.
1015
+ await cleanup.stopRecord(record, !record.child);
1016
+ }
1017
+ catch {
1018
+ failures++;
1019
+ }
1020
+ }
1021
+ if (failures)
1022
+ fail('busy', 'Some owned native stages could not be stopped; their receipts and saved work are retained.');
1023
+ }
1024
+ async inventory() {
1025
+ const directory = join(this.directory(), 'context');
1026
+ let entries;
1027
+ try {
1028
+ entries = await readdir(directory, { withFileTypes: true });
1029
+ }
1030
+ catch (error) {
1031
+ if (error.code === 'ENOENT')
1032
+ return;
1033
+ throw error;
1034
+ }
1035
+ for (const entry of entries) {
1036
+ if (!entry.isDirectory())
1037
+ continue;
1038
+ let binding;
1039
+ try {
1040
+ binding = JSON.parse(await readFile(join(directory, entry.name, 'current.json'), 'utf8'));
1041
+ }
1042
+ catch (error) {
1043
+ if (error.code === 'ENOENT')
1044
+ continue;
1045
+ throw error;
1046
+ }
1047
+ if (binding.context !== entry.name || binding.kind !== 'context')
1048
+ fail('binding', 'Persisted context inventory has an inconsistent receipt.');
1049
+ let stopped = false;
1050
+ try {
1051
+ const receipt = object(JSON.parse(await readFile(join(binding.root, 'stopped.json'), 'utf8')));
1052
+ stopped = receipt.pid === binding.pid && receipt.tokenSecret === binding.tokenSecret;
1053
+ }
1054
+ catch (error) {
1055
+ if (error.code !== 'ENOENT')
1056
+ throw error;
1057
+ }
1058
+ if (!stopped && alive(binding.pid)) {
1059
+ // A replacement owner must also release contexts it has not read yet.
1060
+ // PID existence alone grants no authority: authenticate the complete receipt.
1061
+ await this.verified(binding, this.workspace.target(binding.target), false);
1062
+ this.capacity.slots.add(entry.name);
1063
+ }
1064
+ else if (binding.mode === 'gui')
1065
+ await this.debuggerReleased(binding);
1066
+ }
1067
+ }
1068
+ }