@martinloop/mcp 0.7.1 → 0.8.1

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 (77) hide show
  1. package/dist/package-version.d.ts +1 -1
  2. package/dist/package-version.js +1 -1
  3. package/dist/vendor/adapters/claude-cli.d.ts +16 -1
  4. package/dist/vendor/adapters/claude-cli.js +286 -71
  5. package/dist/vendor/adapters/cli-bridge.d.ts +12 -3
  6. package/dist/vendor/adapters/cli-bridge.js +129 -149
  7. package/dist/vendor/adapters/codex-capabilities.d.ts +4 -1
  8. package/dist/vendor/adapters/codex-capabilities.js +36 -4
  9. package/dist/vendor/adapters/codex-cli.d.ts +10 -3
  10. package/dist/vendor/adapters/codex-cli.js +21 -24
  11. package/dist/vendor/adapters/codex-host.js +150 -132
  12. package/dist/vendor/adapters/index.d.ts +3 -2
  13. package/dist/vendor/adapters/index.js +3 -2
  14. package/dist/vendor/adapters/openai-compatible.js +88 -6
  15. package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
  16. package/dist/vendor/adapters/process-supervisor.js +541 -0
  17. package/dist/vendor/adapters/verifier-only.d.ts +10 -1
  18. package/dist/vendor/adapters/verifier-only.js +43 -1
  19. package/dist/vendor/contracts/external-outcome.d.ts +102 -0
  20. package/dist/vendor/contracts/external-outcome.js +226 -0
  21. package/dist/vendor/contracts/index.d.ts +12 -1
  22. package/dist/vendor/contracts/index.js +2 -0
  23. package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
  24. package/dist/vendor/contracts/swarm-hosted.js +629 -0
  25. package/dist/vendor/contracts/swarm.d.ts +449 -0
  26. package/dist/vendor/contracts/swarm.js +550 -0
  27. package/dist/vendor/core/external-outcome.d.ts +34 -0
  28. package/dist/vendor/core/external-outcome.js +515 -0
  29. package/dist/vendor/core/grounding.js +1 -1
  30. package/dist/vendor/core/index.d.ts +52 -1
  31. package/dist/vendor/core/index.js +55 -3
  32. package/dist/vendor/core/leash.d.ts +1 -0
  33. package/dist/vendor/core/leash.js +13 -0
  34. package/dist/vendor/core/persistence/integrity.d.ts +4 -0
  35. package/dist/vendor/core/persistence/integrity.js +19 -6
  36. package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
  37. package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
  38. package/dist/vendor/core/policy.d.ts +3 -0
  39. package/dist/vendor/core/policy.js +13 -0
  40. package/dist/vendor/core/swarm/candidates.d.ts +78 -0
  41. package/dist/vendor/core/swarm/candidates.js +810 -0
  42. package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
  43. package/dist/vendor/core/swarm/evidence-projection.js +128 -0
  44. package/dist/vendor/core/swarm/evidence.d.ts +40 -0
  45. package/dist/vendor/core/swarm/evidence.js +575 -0
  46. package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
  47. package/dist/vendor/core/swarm/hosted-export.js +362 -0
  48. package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
  49. package/dist/vendor/core/swarm/hosted-projection.js +258 -0
  50. package/dist/vendor/core/swarm/index.d.ts +110 -0
  51. package/dist/vendor/core/swarm/index.js +1060 -0
  52. package/dist/vendor/core/swarm/integration.d.ts +76 -0
  53. package/dist/vendor/core/swarm/integration.js +615 -0
  54. package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
  55. package/dist/vendor/core/swarm/lifecycle.js +252 -0
  56. package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
  57. package/dist/vendor/core/swarm/live-runtime.js +1482 -0
  58. package/dist/vendor/core/swarm/live-store.d.ts +99 -0
  59. package/dist/vendor/core/swarm/live-store.js +860 -0
  60. package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
  61. package/dist/vendor/core/swarm/operational-reader.js +96 -0
  62. package/dist/vendor/core/swarm/operations.d.ts +29 -0
  63. package/dist/vendor/core/swarm/operations.js +128 -0
  64. package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
  65. package/dist/vendor/core/swarm/parent-receipt.js +489 -0
  66. package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
  67. package/dist/vendor/core/swarm/receipt-projection.js +120 -0
  68. package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
  69. package/dist/vendor/core/swarm/scheduler.js +555 -0
  70. package/dist/vendor/core/swarm/verification.d.ts +70 -0
  71. package/dist/vendor/core/swarm/verification.js +295 -0
  72. package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
  73. package/dist/vendor/core/swarm/workspaces.js +488 -0
  74. package/dist/vendor/core/verified-handoff.d.ts +4 -0
  75. package/dist/vendor/core/verified-handoff.js +4 -0
  76. package/package.json +1 -1
  77. package/server.json +2 -2
@@ -0,0 +1,541 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ const ownedProcesses = new WeakMap();
3
+ export async function spawnSupervisedProcess(command, args, options) {
4
+ return spawnSupervisedProcessInternal(command, args, options);
5
+ }
6
+ /**
7
+ * Internal deterministic OS seam used only by this package's supervisor tests.
8
+ * It is intentionally absent from the adapters package root so production
9
+ * callers cannot exchange a caller-supplied PID for operating-system kill
10
+ * authority.
11
+ * @internal
12
+ */
13
+ export async function spawnSupervisedProcessForTest(command, args, options, seams) {
14
+ if (!hasCompleteTestTerminationSeams(seams)) {
15
+ return {
16
+ exitCode: 1,
17
+ stdout: "",
18
+ stderr: "Test supervisor refused incomplete injected termination seams.",
19
+ timedOut: false,
20
+ completed: false,
21
+ crashed: false,
22
+ outputCapped: false,
23
+ launched: false,
24
+ cleanup: {
25
+ state: "failed",
26
+ code: "missing_test_termination_seam",
27
+ message: "Injected Windows requires taskkill+wait; injected POSIX requires group-kill+wait."
28
+ }
29
+ };
30
+ }
31
+ return spawnSupervisedProcessInternal(command, args, options, seams);
32
+ }
33
+ async function spawnSupervisedProcessInternal(command, args, options, testSeams) {
34
+ if (options.signal?.aborted) {
35
+ return {
36
+ exitCode: 1,
37
+ stdout: "",
38
+ stderr: "Subprocess launch aborted before spawn.",
39
+ timedOut: false,
40
+ completed: false,
41
+ crashed: false,
42
+ outputCapped: false,
43
+ terminationReason: "aborted",
44
+ launched: false,
45
+ cleanup: { state: "not_required" }
46
+ };
47
+ }
48
+ const platform = testSeams?.platform ?? process.platform;
49
+ const spawnImpl = options.spawnImpl ?? spawn;
50
+ const stdinMode = options.stdinData !== undefined ? "pipe" : "ignore";
51
+ let proc;
52
+ try {
53
+ proc = spawnImpl(command, args, {
54
+ cwd: options.cwd,
55
+ stdio: [stdinMode, "pipe", "pipe"],
56
+ env: options.env ?? process.env,
57
+ windowsHide: true,
58
+ detached: platform !== "win32"
59
+ });
60
+ }
61
+ catch (error) {
62
+ return {
63
+ exitCode: 1,
64
+ stdout: "",
65
+ stderr: error instanceof Error ? error.message : String(error),
66
+ timedOut: false,
67
+ completed: false,
68
+ crashed: true,
69
+ outputCapped: false,
70
+ launched: false,
71
+ cleanup: { state: "not_required" }
72
+ };
73
+ }
74
+ const ownedPid = proc.pid;
75
+ if (isOwnedPid(ownedPid)) {
76
+ ownedProcesses.set(proc, ownedPid);
77
+ }
78
+ return new Promise((resolve) => {
79
+ let timedOut = false;
80
+ let outputCapped = false;
81
+ let terminationReason;
82
+ let settled = false;
83
+ let exited = false;
84
+ let closed = false;
85
+ let terminationRequested = false;
86
+ let outputBytes = 0;
87
+ let cleanup = { state: "not_required" };
88
+ let closeCode;
89
+ let naturalClosureProofPending = false;
90
+ let terminationProofPending = false;
91
+ const stdoutChunks = [];
92
+ const stderrChunks = [];
93
+ let resolveClosed;
94
+ const closedPromise = new Promise((resolveClose) => { resolveClosed = resolveClose; });
95
+ const resolveOnce = (result) => {
96
+ if (settled)
97
+ return;
98
+ settled = true;
99
+ cleanupListeners();
100
+ resolve({
101
+ ...result,
102
+ timedOut,
103
+ outputCapped,
104
+ ...(terminationReason ? { terminationReason } : {}),
105
+ cleanup
106
+ });
107
+ };
108
+ const resolveCleanupFailure = (failure) => {
109
+ cleanup = failure;
110
+ resolveOnce({
111
+ exitCode: 1,
112
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
113
+ stderr: Buffer.concat(stderrChunks).toString("utf8"),
114
+ launched: true,
115
+ completed: false,
116
+ crashed: false
117
+ });
118
+ };
119
+ const requestTermination = (reason, exposeReason = false) => {
120
+ if (terminationRequested || settled || closed)
121
+ return;
122
+ terminationRequested = true;
123
+ terminationProofPending = true;
124
+ if (exposeReason)
125
+ terminationReason = reason;
126
+ void terminateOwnedProcessTree(proc, closedPromise, {
127
+ platform,
128
+ graceMs: options.terminationGraceMs ?? 1_000,
129
+ taskkillSpawnImpl: testSeams?.taskkillSpawnImpl ?? spawnProcess,
130
+ killProcessGroup: testSeams?.killProcessGroup,
131
+ wait: testSeams?.wait ?? wait,
132
+ authority: testSeams !== undefined || options.spawnImpl === undefined
133
+ ? "owned_os_process"
134
+ : "injected_child_only"
135
+ }).then((result) => {
136
+ terminationProofPending = false;
137
+ if (result.state === "failed") {
138
+ resolveCleanupFailure(result);
139
+ }
140
+ else
141
+ cleanup = { state: "closed" };
142
+ finalizeClose();
143
+ });
144
+ };
145
+ const trackOutput = (chunks, chunk) => {
146
+ if (outputCapped || timedOut || terminationRequested)
147
+ return;
148
+ chunks.push(chunk);
149
+ outputBytes += chunk.byteLength;
150
+ if (options.maxOutputBytes !== undefined && outputBytes > options.maxOutputBytes) {
151
+ outputCapped = true;
152
+ requestTermination("output_capped");
153
+ }
154
+ };
155
+ const terminateEarly = (reason) => {
156
+ if (terminationRequested || timedOut || outputCapped)
157
+ return;
158
+ requestTermination(reason, true);
159
+ };
160
+ const onAbort = () => requestTermination("aborted");
161
+ proc.stdout?.on("data", (chunk) => {
162
+ if (outputCapped || timedOut || terminationRequested)
163
+ return;
164
+ trackOutput(stdoutChunks, chunk);
165
+ if (options.onStdoutChunk) {
166
+ try {
167
+ options.onStdoutChunk(chunk, terminateEarly);
168
+ }
169
+ catch (error) {
170
+ terminateEarly(`stdout inspector error: ${error instanceof Error ? error.message : String(error)}`);
171
+ }
172
+ }
173
+ });
174
+ proc.stderr?.on("data", (chunk) => {
175
+ if (outputCapped || timedOut || terminationRequested)
176
+ return;
177
+ trackOutput(stderrChunks, chunk);
178
+ });
179
+ proc.stdin?.on("error", (error) => {
180
+ if (error.code !== "EPIPE")
181
+ stderrChunks.push(Buffer.from(`${error.message}\n`, "utf8"));
182
+ });
183
+ const timer = setTimeout(() => {
184
+ if (settled || exited || closed || typeof proc.exitCode === "number")
185
+ return;
186
+ timedOut = true;
187
+ requestTermination("timeout");
188
+ }, options.timeoutMs);
189
+ function cleanupListeners() {
190
+ clearTimeout(timer);
191
+ if (options.signal !== undefined)
192
+ options.signal.removeEventListener("abort", onAbort);
193
+ ownedProcesses.delete(proc);
194
+ }
195
+ const finalizeClose = () => {
196
+ if (closeCode === undefined || naturalClosureProofPending || terminationProofPending || settled)
197
+ return;
198
+ const completed = closeCode !== null && !timedOut && !outputCapped && !terminationRequested;
199
+ resolveOnce({
200
+ exitCode: closeCode ?? 1,
201
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
202
+ stderr: Buffer.concat(stderrChunks).toString("utf8"),
203
+ launched: true,
204
+ completed,
205
+ crashed: !completed && !timedOut && !outputCapped && !terminationRequested
206
+ });
207
+ };
208
+ proc.on("exit", () => {
209
+ exited = true;
210
+ if (options.requireTreeClosureOnSuccess && !terminationRequested && !naturalClosureProofPending) {
211
+ naturalClosureProofPending = true;
212
+ void terminateOwnedProcessTree(proc, closedPromise, {
213
+ platform,
214
+ graceMs: options.terminationGraceMs ?? 1_000,
215
+ taskkillSpawnImpl: testSeams?.taskkillSpawnImpl ?? spawnProcess,
216
+ killProcessGroup: testSeams?.killProcessGroup,
217
+ wait: testSeams?.wait ?? wait,
218
+ authority: testSeams !== undefined || options.spawnImpl === undefined
219
+ ? "owned_os_process"
220
+ : "injected_child_only",
221
+ naturalExit: true
222
+ }).then((result) => {
223
+ cleanup = result;
224
+ naturalClosureProofPending = false;
225
+ finalizeClose();
226
+ });
227
+ }
228
+ });
229
+ proc.on("error", (error) => {
230
+ resolveOnce({
231
+ exitCode: 1,
232
+ stdout: "",
233
+ stderr: error.message,
234
+ launched: false,
235
+ completed: false,
236
+ crashed: true
237
+ });
238
+ });
239
+ proc.on("close", (code) => {
240
+ closed = true;
241
+ closeCode = code;
242
+ resolveClosed();
243
+ if (terminationRequested)
244
+ cleanup = { state: "closed" };
245
+ else if (!options.requireTreeClosureOnSuccess)
246
+ cleanup = { state: "not_required" };
247
+ finalizeClose();
248
+ });
249
+ if (options.stdinData !== undefined && proc.stdin) {
250
+ try {
251
+ proc.stdin.end(options.stdinData, "utf8");
252
+ }
253
+ catch (error) {
254
+ const stdinError = error;
255
+ if (stdinError.code !== "EPIPE") {
256
+ resolveOnce({
257
+ exitCode: 1,
258
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
259
+ stderr: stdinError.message,
260
+ launched: false,
261
+ completed: false,
262
+ crashed: true
263
+ });
264
+ }
265
+ }
266
+ }
267
+ if (options.signal !== undefined) {
268
+ options.signal.addEventListener("abort", onAbort, { once: true });
269
+ // AbortSignal dispatch is synchronous, but an injected spawn can abort
270
+ // between the pre-spawn check and listener registration. Rechecking
271
+ // after every close/error/timer listener is installed closes that race.
272
+ if (options.signal.aborted)
273
+ onAbort();
274
+ }
275
+ });
276
+ }
277
+ async function terminateOwnedProcessTree(proc, closedPromise, options) {
278
+ if (options.authority === "injected_child_only") {
279
+ if (options.naturalExit) {
280
+ return {
281
+ state: "failed",
282
+ code: "missing_test_termination_seam",
283
+ message: "Injected process completion cannot prove descendant-tree closure."
284
+ };
285
+ }
286
+ return terminateInjectedChild(proc, closedPromise, options.graceMs, options.wait);
287
+ }
288
+ const pid = ownedProcesses.get(proc);
289
+ if (!isOwnedPid(pid)) {
290
+ return {
291
+ state: "failed",
292
+ code: "invalid_owned_pid",
293
+ message: "Cannot terminate a process tree without one registered positive owned PID."
294
+ };
295
+ }
296
+ if (options.platform === "win32") {
297
+ if (options.naturalExit) {
298
+ return terminateWindowsDescendantsAfterRootExit(pid, options.graceMs, options.wait);
299
+ }
300
+ const taskkillResult = await runTaskkill(pid, options.taskkillSpawnImpl, Math.max(options.graceMs, 1_000), options.wait);
301
+ if (taskkillResult !== undefined)
302
+ return taskkillResult;
303
+ return waitForOwnedClose(closedPromise, options.graceMs, options.wait);
304
+ }
305
+ const killGroup = options.killProcessGroup ?? ((ownedGroupPid, signal) => process.kill(ownedGroupPid, signal));
306
+ if (options.naturalExit) {
307
+ return terminatePosixGroupAfterRootExit(pid, killGroup, options.graceMs, options.wait);
308
+ }
309
+ try {
310
+ killGroup(-pid, "SIGTERM");
311
+ }
312
+ catch (error) {
313
+ return cleanupFailure("termination_failed", error);
314
+ }
315
+ if (await closesWithin(closedPromise, options.graceMs, options.wait))
316
+ return { state: "closed" };
317
+ try {
318
+ killGroup(-pid, "SIGKILL");
319
+ }
320
+ catch (error) {
321
+ return cleanupFailure("termination_failed", error);
322
+ }
323
+ return waitForOwnedClose(closedPromise, options.graceMs, options.wait);
324
+ }
325
+ async function terminatePosixGroupAfterRootExit(pid, killGroup, graceMs, waitImpl) {
326
+ const target = -pid;
327
+ const initial = probePosixProcessGroup(target, killGroup);
328
+ if (initial === "closed")
329
+ return { state: "closed" };
330
+ if (initial instanceof Error)
331
+ return cleanupFailure("termination_failed", initial);
332
+ try {
333
+ killGroup(target, "SIGTERM");
334
+ }
335
+ catch (error) {
336
+ if (isErrno(error, "ESRCH"))
337
+ return { state: "closed" };
338
+ return cleanupFailure("termination_failed", error);
339
+ }
340
+ if (await waitForPosixGroupExit(target, killGroup, graceMs, waitImpl))
341
+ return { state: "closed" };
342
+ try {
343
+ killGroup(target, "SIGKILL");
344
+ }
345
+ catch (error) {
346
+ if (isErrno(error, "ESRCH"))
347
+ return { state: "closed" };
348
+ return cleanupFailure("termination_failed", error);
349
+ }
350
+ if (await waitForPosixGroupExit(target, killGroup, graceMs, waitImpl))
351
+ return { state: "closed" };
352
+ return {
353
+ state: "failed",
354
+ code: "tree_close_timeout",
355
+ message: "Owned POSIX process group remained live after SIGTERM and SIGKILL."
356
+ };
357
+ }
358
+ async function waitForPosixGroupExit(target, killGroup, durationMs, waitImpl) {
359
+ const deadline = Date.now() + Math.max(1, durationMs);
360
+ while (Date.now() < deadline) {
361
+ const probe = probePosixProcessGroup(target, killGroup);
362
+ if (probe === "closed")
363
+ return true;
364
+ if (probe instanceof Error)
365
+ return false;
366
+ await waitImpl(Math.min(25, Math.max(1, deadline - Date.now())));
367
+ }
368
+ return probePosixProcessGroup(target, killGroup) === "closed";
369
+ }
370
+ function probePosixProcessGroup(target, killGroup) {
371
+ try {
372
+ killGroup(target, 0);
373
+ return "alive";
374
+ }
375
+ catch (error) {
376
+ if (isErrno(error, "ESRCH"))
377
+ return "closed";
378
+ return error instanceof Error ? error : new Error(String(error));
379
+ }
380
+ }
381
+ function isErrno(error, code) {
382
+ return typeof error === "object" && error !== null && "code" in error
383
+ && String(error.code) === code;
384
+ }
385
+ async function terminateWindowsDescendantsAfterRootExit(rootPid, graceMs, waitImpl) {
386
+ let rows;
387
+ try {
388
+ rows = await readWindowsProcessTree();
389
+ }
390
+ catch (error) {
391
+ return cleanupFailure("termination_failed", error);
392
+ }
393
+ const childrenByParent = new Map();
394
+ for (const row of rows) {
395
+ const children = childrenByParent.get(row.ParentProcessId) ?? [];
396
+ children.push(row.ProcessId);
397
+ childrenByParent.set(row.ParentProcessId, children);
398
+ }
399
+ const directChildren = childrenByParent.get(rootPid) ?? [];
400
+ for (const childPid of directChildren) {
401
+ const failure = await runTaskkill(childPid, spawnProcess, graceMs, waitImpl);
402
+ if (failure)
403
+ return failure;
404
+ }
405
+ return { state: "closed" };
406
+ }
407
+ function readWindowsProcessTree() {
408
+ const script = [
409
+ "Get-CimInstance Win32_Process",
410
+ "Select-Object ProcessId,ParentProcessId",
411
+ "ConvertTo-Json -Compress"
412
+ ].join(" | ");
413
+ return new Promise((resolvePromise, rejectPromise) => {
414
+ execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { windowsHide: true, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }, (error, stdout) => {
415
+ if (error) {
416
+ rejectPromise(error);
417
+ return;
418
+ }
419
+ try {
420
+ const parsed = JSON.parse(stdout || "[]");
421
+ const values = Array.isArray(parsed) ? parsed : [parsed];
422
+ resolvePromise(values.flatMap((value) => {
423
+ if (!value || typeof value !== "object")
424
+ return [];
425
+ const processId = Number(value.ProcessId);
426
+ const parentProcessId = Number(value.ParentProcessId);
427
+ return isOwnedPid(processId) && Number.isSafeInteger(parentProcessId)
428
+ ? [{ ProcessId: processId, ParentProcessId: parentProcessId }]
429
+ : [];
430
+ }));
431
+ }
432
+ catch (parseError) {
433
+ rejectPromise(parseError);
434
+ }
435
+ });
436
+ });
437
+ }
438
+ async function runTaskkill(pid, spawnImpl, graceMs, waitImpl) {
439
+ let taskkill;
440
+ try {
441
+ taskkill = spawnImpl("taskkill.exe", ["/PID", String(pid), "/T", "/F"], {
442
+ stdio: "ignore",
443
+ windowsHide: true
444
+ });
445
+ }
446
+ catch (error) {
447
+ return cleanupFailure("termination_failed", error);
448
+ }
449
+ const taskkillClose = new Promise((resolve) => {
450
+ let settled = false;
451
+ const finish = (result) => {
452
+ if (settled)
453
+ return;
454
+ settled = true;
455
+ resolve(result);
456
+ };
457
+ taskkill.once("error", (error) => finish(cleanupFailure("termination_failed", error)));
458
+ taskkill.once("close", (code) => {
459
+ if (code === 0)
460
+ finish();
461
+ else
462
+ finish({
463
+ state: "failed",
464
+ code: "termination_failed",
465
+ message: `taskkill.exe exited with code ${String(code)}`
466
+ });
467
+ });
468
+ });
469
+ const result = await Promise.race([
470
+ taskkillClose.then((value) => ({ state: "closed", value })),
471
+ waitImpl(graceMs).then(() => ({ state: "timeout" }))
472
+ ]);
473
+ if (result.state === "closed")
474
+ return result.value;
475
+ try {
476
+ taskkill.kill("SIGKILL");
477
+ }
478
+ catch {
479
+ // The bounded failure below remains authoritative if the taskkill helper
480
+ // itself cannot be interrupted.
481
+ }
482
+ return {
483
+ state: "failed",
484
+ code: "termination_failed",
485
+ message: "taskkill.exe did not exit before the bounded cleanup deadline."
486
+ };
487
+ }
488
+ async function terminateInjectedChild(proc, closedPromise, graceMs, waitImpl) {
489
+ try {
490
+ if (!proc.kill("SIGTERM")) {
491
+ return {
492
+ state: "failed",
493
+ code: "termination_failed",
494
+ message: "Injected child rejected its own termination request."
495
+ };
496
+ }
497
+ }
498
+ catch (error) {
499
+ return cleanupFailure("termination_failed", error);
500
+ }
501
+ return waitForOwnedClose(closedPromise, graceMs, waitImpl);
502
+ }
503
+ async function waitForOwnedClose(closedPromise, graceMs, waitImpl) {
504
+ if (await closesWithin(closedPromise, graceMs, waitImpl))
505
+ return { state: "closed" };
506
+ return {
507
+ state: "failed",
508
+ code: "tree_close_timeout",
509
+ message: "Owned process tree did not report close before the bounded cleanup deadline."
510
+ };
511
+ }
512
+ async function closesWithin(closedPromise, durationMs, waitImpl) {
513
+ return Promise.race([
514
+ closedPromise.then(() => true),
515
+ waitImpl(durationMs).then(() => false)
516
+ ]);
517
+ }
518
+ function cleanupFailure(code, error) {
519
+ return {
520
+ state: "failed",
521
+ code,
522
+ message: error instanceof Error ? error.message : String(error)
523
+ };
524
+ }
525
+ function isOwnedPid(pid) {
526
+ return Number.isSafeInteger(pid) && (pid ?? 0) > 0;
527
+ }
528
+ function hasCompleteTestTerminationSeams(value) {
529
+ if (typeof value !== "object" || value === null)
530
+ return false;
531
+ const seams = value;
532
+ if (typeof seams.wait !== "function")
533
+ return false;
534
+ if (seams.platform === "win32")
535
+ return typeof seams.taskkillSpawnImpl === "function";
536
+ return typeof seams.platform === "string" && typeof seams.killProcessGroup === "function";
537
+ }
538
+ function wait(durationMs) {
539
+ return new Promise((resolve) => setTimeout(resolve, durationMs));
540
+ }
541
+ const spawnProcess = (command, args, options) => spawn(command, [...(args ?? [])], options ?? {});
@@ -1,4 +1,4 @@
1
- import type { MartinAdapter } from "../core/index.js";
1
+ import type { MartinAdapter, RunParentSwarmPipelineInput } from "../core/index.js";
2
2
  import { type SpawnLike } from "./cli-bridge.js";
3
3
  export interface VerifierOnlyAdapterOptions {
4
4
  workingDirectory?: string;
@@ -6,4 +6,13 @@ export interface VerifierOnlyAdapterOptions {
6
6
  label?: string;
7
7
  spawnImpl?: SpawnLike;
8
8
  }
9
+ export interface SwarmVerifierExecutorOptions {
10
+ verifyTimeoutMs?: number;
11
+ spawnImpl?: SpawnLike;
12
+ now?: () => string;
13
+ }
14
+ type ParentSwarmVerifierExecutor = RunParentSwarmPipelineInput["verifierExecutor"];
15
+ /** Adapter-side executor for Core's injected, full-binding parent verifier boundary. */
16
+ export declare function createSwarmVerifierExecutor(options?: SwarmVerifierExecutorOptions): ParentSwarmVerifierExecutor;
9
17
  export declare function createVerifierOnlyAdapter(options?: VerifierOnlyAdapterOptions): MartinAdapter;
18
+ export {};
@@ -1,5 +1,43 @@
1
1
  import { readGitChangedFiles, runVerification } from "./cli-bridge.js";
2
2
  import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
3
+ /** Adapter-side executor for Core's injected, full-binding parent verifier boundary. */
4
+ export function createSwarmVerifierExecutor(options = {}) {
5
+ const verifyTimeoutMs = options.verifyTimeoutMs ?? 120_000;
6
+ const now = options.now ?? (() => new Date().toISOString());
7
+ return {
8
+ async execute(request) {
9
+ const startedAt = now();
10
+ const verification = await runVerification(request.commands.map((step) => step.command), request.cwd, verifyTimeoutMs, request.commands.map((step) => ({ ...step })), options.spawnImpl, {
11
+ runId: request.swarmId,
12
+ workspaceId: request.workspaceId,
13
+ cwd: request.cwd
14
+ }, request.signal);
15
+ const completedAt = now();
16
+ return {
17
+ passed: verification.passed,
18
+ processCloseState: verification.processCloseState,
19
+ binding: {
20
+ swarmId: request.swarmId,
21
+ workspaceId: request.workspaceId,
22
+ cwd: request.cwd,
23
+ parentPolicyVersion: request.parentPolicyVersion,
24
+ baselineCommit: request.baselineCommit,
25
+ integratedTreeHash: request.integratedTreeHash,
26
+ commands: request.commands.map((step) => step.command)
27
+ },
28
+ subprocessResults: verification.steps.map((step) => ({
29
+ command: step.command,
30
+ launched: step.launched,
31
+ completed: step.completed,
32
+ timedOut: step.timedOut,
33
+ exitCode: step.exitCode ?? null,
34
+ startedAt,
35
+ ...(step.completed ? { completedAt } : {})
36
+ }))
37
+ };
38
+ }
39
+ };
40
+ }
3
41
  export function createVerifierOnlyAdapter(options = {}) {
4
42
  const workingDirectory = options.workingDirectory ?? process.cwd();
5
43
  const verifyTimeoutMs = options.verifyTimeoutMs ?? 120_000;
@@ -26,8 +64,12 @@ export function createVerifierOnlyAdapter(options = {}) {
26
64
  const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl, {
27
65
  runId: request.loopId,
28
66
  workspaceId: request.workspaceId,
67
+ attemptId: request.attemptId,
29
68
  cwd: workingDirectory,
30
- });
69
+ ...(request.context.runsRoot ? { runsRoot: request.context.runsRoot } : {}),
70
+ ...(request.context.executionProfile ? { executionProfile: request.context.executionProfile } : {}),
71
+ ...(request.context.allowedNetworkDomains?.length ? { allowedNetworkDomains: [...request.context.allowedNetworkDomains] } : {}),
72
+ }, request.signal);
31
73
  const changedFiles = shouldTrackVerifierWrites
32
74
  ? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
33
75
  : [];