@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
@@ -24,24 +24,76 @@ import { createAdapterCapabilities, normalizeStructuredErrors, normalizeUsage }
24
24
  // ---------------------------------------------------------------------------
25
25
  const BLENDED_INPUT_COST_PER_1K = 0.003; // $/1K input tokens
26
26
  const BLENDED_OUTPUT_COST_PER_1K = 0.012; // $/1K output tokens
27
- // USD per 1K tokens. Claude cache creation uses the documented default
28
- // five-minute rate; cache reads are priced independently from fresh input.
27
+ const PRICING_SNAPSHOT_2026_10_04 = "official-provider-pricing@2026-10-04";
28
+ // USD per 1K tokens. Rates are standard/on-demand text-token prices from
29
+ // official provider pricing pages as of 2026-10-04. Long-context tiers are
30
+ // selected from the estimated/observed input-token count where providers
31
+ // publish a threshold.
29
32
  const MODEL_PRICING = {
30
- "claude-opus-4-6": { inputPer1K: 0.005, cachedInputPer1K: 0.0005, cacheCreationInputPer1K: 0.00625, outputPer1K: 0.025 },
31
- "claude-sonnet-4-6": { inputPer1K: 0.003, cachedInputPer1K: 0.0003, cacheCreationInputPer1K: 0.00375, outputPer1K: 0.015 },
32
- "claude-haiku-4-5": { inputPer1K: 0.001, cachedInputPer1K: 0.0001, cacheCreationInputPer1K: 0.00125, outputPer1K: 0.005 },
33
- // OpenAI coding models
34
- "codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
35
- "gpt-5-codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
36
- "gpt-5.1-codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
37
- "gpt-5.1-codex-max": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.01 },
33
+ // Anthropic current + supported legacy models.
34
+ "claude-fable-5-1": { inputPer1K: 0.010, cachedInputPer1K: 0.00025, cacheCreationInputPer1K: 0.0125, outputPer1K: 0.050, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
35
+ "claude-opus-5-5": { inputPer1K: 0.004, cachedInputPer1K: 0.0002, cacheCreationInputPer1K: 0.005, outputPer1K: 0.020, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
36
+ "claude-sonnet-5-5": { inputPer1K: 0.002, cachedInputPer1K: 0.0002, cacheCreationInputPer1K: 0.0025, outputPer1K: 0.010, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
37
+ "claude-opus-4-6": { inputPer1K: 0.005, cachedInputPer1K: 0.0005, cacheCreationInputPer1K: 0.00625, outputPer1K: 0.025, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
38
+ "claude-sonnet-4-6": { inputPer1K: 0.003, cachedInputPer1K: 0.0003, cacheCreationInputPer1K: 0.00375, outputPer1K: 0.015, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
39
+ "claude-haiku-4-5": { inputPer1K: 0.001, cachedInputPer1K: 0.0001, cacheCreationInputPer1K: 0.00125, outputPer1K: 0.005, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
40
+ // OpenAI / Codex. GPT-6-family long-context pricing applies above 272K input tokens.
41
+ "gpt-6-astra": {
42
+ inputPer1K: 0.010, cachedInputPer1K: 0.001, cacheCreationInputPer1K: 0.0125, outputPer1K: 0.050,
43
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
44
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.020, cachedInputPer1K: 0.002, cacheCreationInputPer1K: 0.025, outputPer1K: 0.075 }
45
+ },
46
+ "gpt-6.1-sol": {
47
+ inputPer1K: 0.002, cachedInputPer1K: 0.0001, cacheCreationInputPer1K: 0.0025, outputPer1K: 0.010,
48
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
49
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.004, cachedInputPer1K: 0.0002, cacheCreationInputPer1K: 0.005, outputPer1K: 0.015 }
50
+ },
51
+ "gpt-6-sol": {
52
+ inputPer1K: 0.002, cachedInputPer1K: 0.0002, cacheCreationInputPer1K: 0.0025, outputPer1K: 0.010,
53
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
54
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.004, cachedInputPer1K: 0.0004, cacheCreationInputPer1K: 0.005, outputPer1K: 0.015 }
55
+ },
56
+ "gpt-6-luna": {
57
+ inputPer1K: 0.0001, cachedInputPer1K: 0.00001, cacheCreationInputPer1K: 0.000125, outputPer1K: 0.0005,
58
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
59
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.0002, cachedInputPer1K: 0.00002, cacheCreationInputPer1K: 0.00025, outputPer1K: 0.00075 }
60
+ },
61
+ "gpt-5.6-sol": {
62
+ inputPer1K: 0.004, cachedInputPer1K: 0.0004, cacheCreationInputPer1K: 0.005, outputPer1K: 0.020,
63
+ pricingVersion: PRICING_SNAPSHOT_2026_10_04,
64
+ longContext: { thresholdInputTokens: 272_000, inputPer1K: 0.008, cachedInputPer1K: 0.0008, cacheCreationInputPer1K: 0.010, outputPer1K: 0.030 }
65
+ },
66
+ "gpt-5.3-codex": { inputPer1K: 0.00175, cachedInputPer1K: 0.000175, outputPer1K: 0.014, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
67
+ "codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010 },
68
+ "gpt-5-codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010 },
69
+ "gpt-5.1-codex": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010 },
70
+ "gpt-5.1-codex-max": { inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010 },
38
71
  "gpt-5.2-codex": { inputPer1K: 0.00175, cachedInputPer1K: 0.000175, outputPer1K: 0.014 },
39
- "codex-mini-latest": { inputPer1K: 0.0015, cachedInputPer1K: 0.000375, outputPer1K: 0.006 }
72
+ "codex-mini-latest": { inputPer1K: 0.0015, cachedInputPer1K: 0.000375, outputPer1K: 0.006 },
73
+ // Google Gemini current + common supported models.
74
+ "gemini-3.8-flash": { inputPer1K: 0.00075, cachedInputPer1K: 0.000075, outputPer1K: 0.00375, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
75
+ "gemini-3.7-flash": { inputPer1K: 0.00075, cachedInputPer1K: 0.000075, outputPer1K: 0.00375, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
76
+ "gemini-3.5-flash-lite": { inputPer1K: 0.0003, cachedInputPer1K: 0.00003, outputPer1K: 0.0025, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
77
+ "gemini-3.1-flash-lite": { inputPer1K: 0.00025, cachedInputPer1K: 0.000025, outputPer1K: 0.0015, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
78
+ "gemini-3.1-pro-preview": {
79
+ inputPer1K: 0.002, cachedInputPer1K: 0.0002, outputPer1K: 0.012, pricingVersion: PRICING_SNAPSHOT_2026_10_04,
80
+ longContext: { thresholdInputTokens: 200_000, inputPer1K: 0.004, cachedInputPer1K: 0.0004, outputPer1K: 0.018 }
81
+ },
82
+ "gemini-3-flash-preview": { inputPer1K: 0.0005, cachedInputPer1K: 0.00005, outputPer1K: 0.003, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
83
+ "gemini-2.5-pro": {
84
+ inputPer1K: 0.00125, cachedInputPer1K: 0.000125, outputPer1K: 0.010, pricingVersion: PRICING_SNAPSHOT_2026_10_04,
85
+ longContext: { thresholdInputTokens: 200_000, inputPer1K: 0.0025, cachedInputPer1K: 0.00025, outputPer1K: 0.015 }
86
+ },
87
+ "gemini-2.5-flash": { inputPer1K: 0.0003, cachedInputPer1K: 0.00003, outputPer1K: 0.0025, pricingVersion: PRICING_SNAPSHOT_2026_10_04 },
88
+ "gemini-2.5-flash-lite": { inputPer1K: 0.0001, cachedInputPer1K: 0.00001, outputPer1K: 0.0004, pricingVersion: PRICING_SNAPSHOT_2026_10_04 }
40
89
  };
41
90
  const CLAUDE_MODEL_ALIASES = [
42
- { canonicalModelId: "claude-opus-4-6", aliases: [/^claude-opus-4-6-\d{8}$/u, /^claude-opus$/u] },
43
- { canonicalModelId: "claude-sonnet-4-6", aliases: [/^claude-sonnet-4-6-\d{8}$/u, /^claude-sonnet$/u] },
44
- { canonicalModelId: "claude-haiku-4-5", aliases: [/^claude-haiku-4-5-\d{8}$/u, /^claude-haiku$/u] }
91
+ { canonicalModelId: "claude-fable-5-1", aliases: [/^claude-fable-5-1-\d{8}$/u, /^claude-fable$/u, /^fable$/u] },
92
+ { canonicalModelId: "claude-opus-5-5", aliases: [/^claude-opus-5-5-\d{8}$/u, /^claude-opus$/u, /^opus$/u] },
93
+ { canonicalModelId: "claude-sonnet-5-5", aliases: [/^claude-sonnet-5-5-\d{8}$/u, /^claude-sonnet$/u, /^sonnet$/u] },
94
+ { canonicalModelId: "claude-opus-4-6", aliases: [/^claude-opus-4-6-\d{8}$/u] },
95
+ { canonicalModelId: "claude-sonnet-4-6", aliases: [/^claude-sonnet-4-6-\d{8}$/u] },
96
+ { canonicalModelId: "claude-haiku-4-5", aliases: [/^claude-haiku-4-5-\d{8}$/u, /^claude-haiku$/u, /^haiku$/u] }
45
97
  ];
46
98
  function resolveModelPricing(modelLabel) {
47
99
  const normalized = modelLabel?.trim().toLowerCase();
@@ -63,13 +115,28 @@ function resolveModelPricing(modelLabel) {
63
115
  }
64
116
  return { status: "unknown" };
65
117
  }
66
- function calculateUsageCost(usage, pricing) {
67
- return ((usage.inputTokens / 1000) * pricing.inputPer1K +
68
- (usage.cachedInputTokens / 1000) * (pricing.cachedInputPer1K ?? pricing.inputPer1K) +
69
- (usage.cacheCreationInputTokens / 1000) * (pricing.cacheCreationInputPer1K ?? pricing.inputPer1K) +
70
- (usage.outputTokens / 1000) * pricing.outputPer1K);
118
+ function effectivePricingForInput(pricing, inputTokens) {
119
+ const long = pricing.longContext;
120
+ if (!long || inputTokens <= long.thresholdInputTokens) {
121
+ return pricing;
122
+ }
123
+ return {
124
+ inputPer1K: long.inputPer1K,
125
+ cachedInputPer1K: long.cachedInputPer1K,
126
+ cacheCreationInputPer1K: long.cacheCreationInputPer1K,
127
+ outputPer1K: long.outputPer1K,
128
+ pricingVersion: pricing.pricingVersion
129
+ };
130
+ }
131
+ function calculateUsageCost(usage, pricing, contextInputTokens = usage.inputTokens + usage.cachedInputTokens + usage.cacheCreationInputTokens) {
132
+ const effective = effectivePricingForInput(pricing, contextInputTokens);
133
+ return ((usage.inputTokens / 1000) * effective.inputPer1K +
134
+ (usage.cachedInputTokens / 1000) * (effective.cachedInputPer1K ?? effective.inputPer1K) +
135
+ (usage.cacheCreationInputTokens / 1000) * (effective.cacheCreationInputPer1K ?? effective.inputPer1K) +
136
+ (usage.outputTokens / 1000) * effective.outputPer1K);
71
137
  }
72
138
  const EMBEDDED_PRICING_VERSION = "embedded-v1";
139
+ // Source snapshot: https://developers.openai.com/api/docs/models/gpt-6.1-sol
73
140
  function extractClaudeObservedModel(stdout) {
74
141
  for (const line of stdout.split(/\r?\n/u)) {
75
142
  try {
@@ -135,7 +202,9 @@ function extractUsage(parsed, modelLabel, modelSource) {
135
202
  : pricingResolution.pricing
136
203
  ? "static_catalog"
137
204
  : "none",
138
- ...(pricingResolution.pricing ? { pricingVersion: EMBEDDED_PRICING_VERSION } : {}),
205
+ ...(pricingResolution.pricing
206
+ ? { pricingVersion: pricingResolution.pricing.pricingVersion ?? EMBEDDED_PRICING_VERSION }
207
+ : {}),
139
208
  rawUsageAvailable: true,
140
209
  settledAt: new Date().toISOString()
141
210
  }
@@ -192,14 +261,20 @@ function extractCodexJsonlResult(stdout, modelLabel) {
192
261
  const cachedInputTokens = latestTurnCompleted.usage.cached_input_tokens ?? 0;
193
262
  const outputTokens = latestTurnCompleted.usage.output_tokens ?? 0;
194
263
  const reasoningOutputTokens = latestTurnCompleted.usage.reasoning_output_tokens ?? 0;
195
- const tokensIn = promptTokens + cachedInputTokens;
196
- const tokensOut = outputTokens + reasoningOutputTokens;
197
- const exactPricing = modelLabel ? MODEL_PRICING[modelLabel] : undefined;
264
+ // Codex exposes cached/reasoning token detail fields as subsets of the
265
+ // corresponding input/output totals. Do not add those details twice.
266
+ const tokensIn = promptTokens;
267
+ const tokensOut = outputTokens;
268
+ const pricingResolution = resolveModelPricing(modelLabel);
269
+ const exactPricing = pricingResolution.pricing;
198
270
  const pricing = exactPricing ?? MODEL_PRICING["codex"] ??
199
271
  { inputPer1K: BLENDED_INPUT_COST_PER_1K, outputPer1K: BLENDED_OUTPUT_COST_PER_1K };
200
- const actualUsd = (promptTokens / 1000) * pricing.inputPer1K +
201
- (cachedInputTokens / 1000) * (pricing.cachedInputPer1K ?? pricing.inputPer1K) +
202
- (tokensOut / 1000) * pricing.outputPer1K;
272
+ const actualUsd = calculateUsageCost({
273
+ inputTokens: Math.max(promptTokens - cachedInputTokens, 0),
274
+ cachedInputTokens,
275
+ cacheCreationInputTokens: 0,
276
+ outputTokens
277
+ }, pricing, promptTokens);
203
278
  return {
204
279
  summary,
205
280
  usage: normalizeUsage({
@@ -221,7 +296,7 @@ function extractCodexJsonlResult(stdout, modelLabel) {
221
296
  billingMode: "unknown",
222
297
  modelSource: modelLabel ? "explicit_override" : "agent_default",
223
298
  pricingSource: exactPricing ? "static_catalog" : "blended_fallback",
224
- pricingVersion: EMBEDDED_PRICING_VERSION,
299
+ pricingVersion: exactPricing?.pricingVersion ?? EMBEDDED_PRICING_VERSION,
225
300
  rawUsageAvailable: true,
226
301
  settledAt: new Date().toISOString()
227
302
  }
@@ -270,11 +345,16 @@ function extractGeminiJsonResult(stdout, modelLabel) {
270
345
  }
271
346
  const tokensIn = promptTokens + cachedInputTokens;
272
347
  const tokensOut = outputTokens + reasoningOutputTokens;
273
- const pricing = (modelLabel ? MODEL_PRICING[modelLabel] : undefined) ??
348
+ const pricingResolution = resolveModelPricing(modelLabel);
349
+ const exactPricing = pricingResolution.pricing;
350
+ const pricing = exactPricing ??
274
351
  { inputPer1K: BLENDED_INPUT_COST_PER_1K, outputPer1K: BLENDED_OUTPUT_COST_PER_1K };
275
- const actualUsd = (promptTokens / 1000) * pricing.inputPer1K +
276
- (cachedInputTokens / 1000) * (pricing.cachedInputPer1K ?? pricing.inputPer1K) +
277
- (tokensOut / 1000) * pricing.outputPer1K;
352
+ const actualUsd = calculateUsageCost({
353
+ inputTokens: promptTokens,
354
+ cachedInputTokens,
355
+ cacheCreationInputTokens: 0,
356
+ outputTokens: tokensOut
357
+ }, pricing, promptTokens + cachedInputTokens);
278
358
  return {
279
359
  summary,
280
360
  usage: normalizeUsage({
@@ -295,15 +375,15 @@ function extractGeminiJsonResult(stdout, modelLabel) {
295
375
  reasoningOutputTokens,
296
376
  billingMode: "unknown",
297
377
  modelSource: modelLabel ? "explicit_override" : "agent_default",
298
- pricingSource: "blended_fallback",
299
- pricingVersion: EMBEDDED_PRICING_VERSION,
378
+ pricingSource: exactPricing ? "static_catalog" : "blended_fallback",
379
+ pricingVersion: exactPricing?.pricingVersion ?? EMBEDDED_PRICING_VERSION,
300
380
  rawUsageAvailable: true,
301
381
  settledAt: new Date().toISOString()
302
382
  }
303
383
  })
304
384
  };
305
385
  }
306
- function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate) {
386
+ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate, capTokens, detailTokensIncludedInTotals = false, observedUsageGovernor) {
307
387
  let pricingResolution = resolveModelPricing(modelLabel);
308
388
  // Safety margin: terminate at 80% of cap to bound one-turn overshoot.
309
389
  // Without this, a single expensive turn can blow past the cap before the
@@ -333,54 +413,101 @@ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate)
333
413
  let usageEventSeen = false;
334
414
  let firstChunkAt;
335
415
  let finalResult;
336
- const checkBudgetExceeded = (terminate) => {
416
+ let finalObservationPublished = false;
417
+ const observedEventIdentities = new Set();
418
+ const checkBudgetExceeded = (terminate, final = false) => {
419
+ const cumulativeTokens = tokensIn + tokensOut;
420
+ if (observedUsageGovernor) {
421
+ if (final && finalObservationPublished)
422
+ return;
423
+ if (final)
424
+ finalObservationPublished = true;
425
+ try {
426
+ const decision = observedUsageGovernor({ cumulativeUsd, cumulativeTokens, turns, final });
427
+ if (decision.action === "terminate") {
428
+ terminate(`Parent observed-usage governor terminated the subprocess: ${decision.reason}.`);
429
+ }
430
+ }
431
+ catch (error) {
432
+ terminate(`Parent observed-usage governor failed closed: ${error instanceof Error ? error.message : String(error)}.`);
433
+ }
434
+ return;
435
+ }
436
+ if (capTokens !== undefined && capTokens > 0 && cumulativeTokens > capTokens) {
437
+ terminate(`Streaming token lease exceeded after ${String(turns)} turn(s): observed ${String(cumulativeTokens)} tokens ` +
438
+ `surpassed the per-attempt token cap ${String(capTokens)}. Subprocess terminated to prevent additional token usage.`);
439
+ return;
440
+ }
337
441
  if (capUsd > 0 && cumulativeUsd > effectiveCapUsd) {
338
442
  terminate(`Streaming usage cap exceeded after ${String(turns)} turn(s): cumulative cost ~$${cumulativeUsd.toFixed(4)} ` +
339
443
  `surpassed the per-attempt cap $${capUsd.toFixed(4)} (${String(Math.round(effectiveCapRatio * 100))}% threshold: $${effectiveCapUsd.toFixed(4)}). ` +
340
444
  `Subprocess terminated to bound runaway overspend.`);
341
445
  }
342
446
  };
343
- const extractUsageFromEvent = (event, terminate) => {
447
+ const extractUsageFromEvent = (event, terminate, final = false) => {
448
+ const nestedMessage = event.message && typeof event.message === "object"
449
+ ? event.message
450
+ : undefined;
451
+ const providerEventId = typeof event.id === "string"
452
+ ? event.id
453
+ : typeof nestedMessage?.id === "string" ? nestedMessage.id : undefined;
454
+ if (providerEventId) {
455
+ const identity = `${String(event.type ?? "event")}:${providerEventId}`;
456
+ if (observedEventIdentities.has(identity))
457
+ return;
458
+ observedEventIdentities.add(identity);
459
+ }
344
460
  if (event.type === "system" && event.subtype === "init" && typeof event.model === "string") {
345
461
  pricingResolution = resolveModelPricing(event.model);
346
462
  }
347
463
  // Check for authoritative total_cost_usd on ANY event — if Claude reports
348
464
  // cost exceeding cap, terminate immediately regardless of event type.
349
- if (typeof event.total_cost_usd === "number" && event.total_cost_usd > 0) {
350
- cumulativeUsd = event.total_cost_usd;
351
- checkBudgetExceeded(terminate);
352
- return;
353
- }
465
+ const authoritativeCost = typeof event.total_cost_usd === "number" && event.total_cost_usd >= 0;
466
+ if (authoritativeCost)
467
+ cumulativeUsd = Math.max(cumulativeUsd, event.total_cost_usd);
354
468
  // Extract usage from any event shape that carries it:
355
469
  // - { type: "assistant", message: { usage: { ... } } } (original format)
356
470
  // - { usage: { input_tokens, output_tokens, ... } } (top-level usage)
357
471
  // - { message: { usage: { ... } } } (nested without type check)
358
- const usage = (event.message && typeof event.message === "object" && "usage" in event.message
359
- ? event.message.usage
472
+ const usage = (nestedMessage && "usage" in nestedMessage
473
+ ? nestedMessage.usage
360
474
  : undefined) ??
361
475
  (event.usage && typeof event.usage === "object" ? event.usage : undefined);
362
476
  if (!usage || typeof usage !== "object") {
477
+ if (authoritativeCost || final)
478
+ checkBudgetExceeded(terminate, final);
363
479
  return;
364
480
  }
365
481
  const usageRecord = usage;
366
482
  const turnInputTokens = usageRecord.input_tokens ?? usageRecord.inputTokens ?? 0;
367
- const turnCachedInputTokens = usageRecord.cache_read_input_tokens ?? usageRecord.cacheReadInputTokens ?? 0;
483
+ const turnCachedInputTokens = usageRecord.cached_input_tokens ?? usageRecord.cachedInputTokens ??
484
+ usageRecord.cache_read_input_tokens ?? usageRecord.cacheReadInputTokens ?? 0;
368
485
  const turnCacheCreationInputTokens = usageRecord.cache_creation_input_tokens ?? usageRecord.cacheCreationInputTokens ?? 0;
369
- const turnTokensIn = turnInputTokens + turnCachedInputTokens + turnCacheCreationInputTokens;
370
- const turnTokensOut = usageRecord.output_tokens ?? usageRecord.outputTokens ?? 0;
486
+ const turnTokensIn = detailTokensIncludedInTotals
487
+ ? turnInputTokens
488
+ : turnInputTokens + turnCachedInputTokens + turnCacheCreationInputTokens;
489
+ const turnOutputTokens = usageRecord.output_tokens ?? usageRecord.outputTokens ?? 0;
490
+ const turnReasoningOutputTokens = usageRecord.reasoning_output_tokens ?? usageRecord.reasoningOutputTokens ?? 0;
491
+ const turnTokensOut = detailTokensIncludedInTotals
492
+ ? turnOutputTokens
493
+ : turnOutputTokens + turnReasoningOutputTokens;
371
494
  if (turnTokensIn === 0 && turnTokensOut === 0) {
495
+ if (authoritativeCost || final)
496
+ checkBudgetExceeded(terminate, final);
372
497
  return;
373
498
  }
374
499
  const turnUsd = pricingResolution.pricing
375
500
  ? calculateUsageCost({
376
- inputTokens: turnInputTokens,
501
+ inputTokens: detailTokensIncludedInTotals
502
+ ? Math.max(turnInputTokens - turnCachedInputTokens - turnCacheCreationInputTokens, 0)
503
+ : turnInputTokens,
377
504
  cachedInputTokens: turnCachedInputTokens,
378
505
  cacheCreationInputTokens: turnCacheCreationInputTokens,
379
506
  outputTokens: turnTokensOut
380
507
  }, pricingResolution.pricing)
381
508
  : undefined;
382
509
  const remainingBudgetBeforeTurn = Math.max(capUsd - cumulativeUsd, 0);
383
- if (turnUsd !== undefined &&
510
+ if (!observedUsageGovernor && !final && !authoritativeCost && turnUsd !== undefined &&
384
511
  capUsd > 0 &&
385
512
  remainingBudgetBeforeTurn > 0 &&
386
513
  turnUsd > remainingBudgetBeforeTurn * 0.5) {
@@ -397,10 +524,10 @@ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate)
397
524
  tokensOut += turnTokensOut;
398
525
  turns += 1;
399
526
  usageEventSeen = true;
400
- if (turnUsd !== undefined) {
527
+ if (!authoritativeCost && turnUsd !== undefined) {
401
528
  cumulativeUsd += turnUsd;
402
529
  }
403
- checkBudgetExceeded(terminate);
530
+ checkBudgetExceeded(terminate, final);
404
531
  };
405
532
  const ingestLine = (line, terminate) => {
406
533
  const trimmed = line.trim();
@@ -414,10 +541,23 @@ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate)
414
541
  catch {
415
542
  return;
416
543
  }
417
- extractUsageFromEvent(event, terminate);
418
544
  if (event.type === "result") {
419
545
  finalResult = event;
546
+ // Claude's final result usage is an aggregate repeat of usage already
547
+ // emitted on assistant events. Count it only when no incremental usage
548
+ // was observed, otherwise token leases are falsely doubled.
549
+ if (!usageEventSeen) {
550
+ extractUsageFromEvent(event, terminate, true);
551
+ }
552
+ else {
553
+ if (typeof event.total_cost_usd === "number" && event.total_cost_usd >= 0) {
554
+ cumulativeUsd = Math.max(cumulativeUsd, event.total_cost_usd);
555
+ }
556
+ checkBudgetExceeded(terminate, true);
557
+ }
558
+ return;
420
559
  }
560
+ extractUsageFromEvent(event, terminate);
421
561
  };
422
562
  return {
423
563
  onChunk: (chunk, terminate) => {
@@ -535,6 +675,7 @@ export function createAgentCliAdapter(options) {
535
675
  transport: "cli",
536
676
  agentExecutionIntent,
537
677
  providerExecutionTimeoutMs: timeoutMs,
678
+ ...(options.budgetPreflight ? { budgetPreflight: options.budgetPreflight } : {}),
538
679
  capabilities: createAdapterCapabilities({
539
680
  preflight: true,
540
681
  usageSettlement: supportsUsageSettlement,
@@ -582,10 +723,12 @@ export function createAgentCliAdapter(options) {
582
723
  // watch per-turn usage events as they arrive and kill the subprocess the
583
724
  // instant projected spend crosses what remains — bounding the worst case
584
725
  // to roughly one turn's overshoot rather than the entire runaway session.
585
- const streamingUsage = options.streamingUsageCap && request.context.remainingBudgetUsd > 0
586
- ? createStreamingUsageInspector(request.context.remainingBudgetUsd, options.model ?? options.command, estimatedUsage.tokensIn)
726
+ const streamingUsage = options.streamingUsageCap &&
727
+ (request.context.remainingBudgetUsd > 0 ||
728
+ (options.streamingTokenCap && (request.context.remainingTokens ?? 0) > 0))
729
+ ? createStreamingUsageInspector(request.context.remainingBudgetUsd, options.model ?? options.command, estimatedUsage.tokensIn, options.streamingTokenCap ? request.context.remainingTokens : undefined, options.streamingUsageDetailsIncludedInTotals, request.observedUsageGovernor)
587
730
  : undefined;
588
- const agentResult = await runSubprocess(options.command, args, {
731
+ const agentResult = await runSubprocess(options.executionCommand ?? options.command, args, {
589
732
  cwd: workingDirectory,
590
733
  timeoutMs: executionTimeoutMs,
591
734
  spawnImpl: options.spawnImpl,
@@ -747,8 +890,12 @@ export function createAgentCliAdapter(options) {
747
890
  const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, verificationStack, options.spawnImpl, {
748
891
  runId: request.loopId,
749
892
  workspaceId: request.workspaceId,
893
+ attemptId: request.attemptId,
750
894
  cwd: workingDirectory,
751
- });
895
+ ...(request.context.runsRoot ? { runsRoot: request.context.runsRoot } : {}),
896
+ ...(request.context.executionProfile ? { executionProfile: request.context.executionProfile } : {}),
897
+ ...(request.context.allowedNetworkDomains?.length ? { allowedNetworkDomains: [...request.context.allowedNetworkDomains] } : {}),
898
+ }, request.signal);
752
899
  const verificationConfigured = verification.binding.commands.length > 0;
753
900
  // Check for zero-diff (agent ran but made no file changes)
754
901
  const postRunChangedFiles = gitRepoRoot
@@ -759,11 +906,12 @@ export function createAgentCliAdapter(options) {
759
906
  // Extract structured errors from stderr/stdout for better failure context
760
907
  const structuredErrors = normalizeStructuredErrors(extractStructuredErrors(agentResult.stderr, agentResult.stdout));
761
908
  const rawExecutionArtifacts = gitRepoRoot
762
- ? await readGitExecutionArtifacts(gitRepoRoot, 5000, options.spawnImpl)
909
+ ? await readGitExecutionArtifacts(gitRepoRoot, 5000, options.spawnImpl, agentChangedFiles)
763
910
  : undefined;
764
911
  const executionArtifacts = rawExecutionArtifacts
765
912
  ? {
766
913
  ...(agentChangedFiles.length > 0 ? { changedFiles: agentChangedFiles } : {}),
914
+ ...(rawExecutionArtifacts.patch ? { patch: rawExecutionArtifacts.patch } : {}),
767
915
  ...(baselineChangedFiles.size === 0 && rawExecutionArtifacts.diffStats
768
916
  ? { diffStats: rawExecutionArtifacts.diffStats }
769
917
  : {})
@@ -821,8 +969,11 @@ export function createAgentCliAdapter(options) {
821
969
  const scopeBlock = scopeViolations.length > 0
822
970
  ? `\n Scope violations: ${scopeViolations.join(", ")}`
823
971
  : "";
824
- // Write PROGRESS.md to help the next attempt re-anchor on the original objective
825
- if (repoRoot) {
972
+ // PROGRESS.md is legacy adapter-local retry state. Never create it in a
973
+ // scope-constrained workspace: doing so manufactures an undeclared
974
+ // product change and causes the Core leash to hide the provider's real
975
+ // failure behind surface_write_not_allowed.
976
+ if (repoRoot && (request.context.allowedPaths?.length ?? 0) === 0) {
826
977
  try {
827
978
  const { writeFile, readFile, appendFile: appendFs } = await import("node:fs/promises");
828
979
  const progressPath = `${repoRoot}/PROGRESS.md`;
@@ -882,6 +1033,33 @@ export function createAgentCliAdapter(options) {
882
1033
  // ---------------------------------------------------------------------------
883
1034
  // Pre-configured: Claude CLI
884
1035
  // ---------------------------------------------------------------------------
1036
+ const CLAUDE_PERMISSION_CONTROL_ARGS = new Set([
1037
+ "--allow-dangerously-skip-permissions",
1038
+ "--allowed-tools",
1039
+ "--allowedTools",
1040
+ "--dangerously-skip-permissions",
1041
+ "--permission-mode"
1042
+ ]);
1043
+ function assertClaudePermissionControlsAreGoverned(extraArgs) {
1044
+ const overridesPermissionControl = extraArgs.some((arg) => {
1045
+ const [flag] = arg.split("=", 1);
1046
+ return flag !== undefined && CLAUDE_PERMISSION_CONTROL_ARGS.has(flag);
1047
+ });
1048
+ if (overridesPermissionControl) {
1049
+ throw new Error("Claude permission controls cannot be overridden via extraArgs.");
1050
+ }
1051
+ }
1052
+ function hostOwnsVerification(request) {
1053
+ return request.context.verificationExecutionOwner === "host_only";
1054
+ }
1055
+ function buildClaudeVerifierAllowedTools(request) {
1056
+ if (hostOwnsVerification(request))
1057
+ return [];
1058
+ const commands = [...new Set(request.context.verificationPlan.filter((command) => command.trim().length > 0))];
1059
+ return commands.length > 0
1060
+ ? ["--allowedTools", ...commands.map((command) => `Bash(${command})`)]
1061
+ : [];
1062
+ }
885
1063
  /**
886
1064
  * Spawns `claude --output-format stream-json --verbose --print "<prompt>" [extraArgs]`.
887
1065
  *
@@ -900,6 +1078,7 @@ export function createAgentCliAdapter(options) {
900
1078
  export function createClaudeCliAdapter(options = {}) {
901
1079
  const modelArgs = options.model ? ["--model", options.model] : [];
902
1080
  const extraArgs = options.extraArgs ?? [];
1081
+ assertClaudePermissionControlsAreGoverned(extraArgs);
903
1082
  return createAgentCliAdapter({
904
1083
  command: "claude",
905
1084
  adapterIdSuffix: "claude",
@@ -912,20 +1091,26 @@ export function createClaudeCliAdapter(options = {}) {
912
1091
  verifyTimeoutMs: options.verifyTimeoutMs,
913
1092
  supportsJsonOutput: true,
914
1093
  streamingUsageCap: true,
1094
+ streamingTokenCap: true,
915
1095
  spawnImpl: options.spawnImpl,
916
1096
  argsBuilder: (_prompt, request) => [
917
1097
  "--output-format",
918
1098
  "stream-json",
919
1099
  "--verbose",
920
1100
  "--print",
921
- "--dangerously-skip-permissions",
1101
+ ...(options.readOnly
1102
+ ? ["--permission-mode", "plan"]
1103
+ : ["--permission-mode", "acceptEdits"]),
1104
+ ...buildClaudeVerifierAllowedTools(request),
922
1105
  // Subprocess isolation strategy:
923
1106
  // --bare: skips hooks (prevents SessionEnd/hook failures causing non-zero exits),
924
1107
  // MCP server loading, LSP, CLAUDE.md discovery, and background prefetches.
925
1108
  // Requires ANTHROPIC_API_KEY (OAuth/keychain auth not available in bare mode).
926
1109
  // --strict-mcp-config: fallback when ANTHROPIC_API_KEY is not set — still
927
1110
  // prevents parent MCP servers from being inherited by the subprocess.
928
- ...(process.env["ANTHROPIC_API_KEY"] ? ["--bare"] : ["--strict-mcp-config"]),
1111
+ ...(process.env["ANTHROPIC_API_KEY"]
1112
+ ? ["--bare"]
1113
+ : ["--strict-mcp-config", "--setting-sources", "project", "--disable-slash-commands"]),
929
1114
  // NOTE: --max-tokens does not exist in the claude CLI. Token cap enforcement
930
1115
  // is handled at the MartinLoop layer via streamingUsageCap, not via subprocess flags.
931
1116
  ...modelArgs,
@@ -988,10 +1173,13 @@ export function createCodexCliAdapter(options = {}) {
988
1173
  * npm install -g @google/gemini-cli
989
1174
  */
990
1175
  export function createGeminiCliAdapter(options = {}) {
991
- const approvalMode = options.approvalMode ?? "yolo";
992
- if (approvalMode !== "yolo") {
1176
+ const approvalMode = options.readOnly ? "plan" : options.approvalMode ?? "yolo";
1177
+ if (!options.readOnly && approvalMode !== "yolo") {
993
1178
  throw new Error("governed-autonomous Gemini execution requires yolo approval mode; interactive downgrade rejected.");
994
1179
  }
1180
+ if (options.readOnly && options.approvalMode !== undefined && options.approvalMode !== "plan") {
1181
+ throw new Error("read-only Gemini execution requires plan approval mode.");
1182
+ }
995
1183
  const extraArgs = options.extraArgs ?? [];
996
1184
  if (extraArgs.some((arg) => arg === "--approval-mode" || arg.startsWith("--approval-mode="))) {
997
1185
  throw new Error("Gemini approval mode cannot be overridden through extraArgs.");
@@ -1034,15 +1222,32 @@ export function createGeminiCliAdapter(options = {}) {
1034
1222
  function buildPrompt(request) {
1035
1223
  const lines = [];
1036
1224
  const mutationMode = request.context.mutationMode ?? "edit";
1225
+ const verificationIsHostOwned = hostOwnsVerification(request);
1037
1226
  lines.push("You are running in autonomous agentic mode.");
1038
- lines.push("MAKE ALL REQUIRED FILE EDITS NOW. Do not ask for confirmation. Do not ask clarifying questions.");
1039
- lines.push("Do not explain what you found without also making the changes. Edit the files and complete the task.");
1040
- lines.push("");
1041
- lines.push("If PROGRESS.md exists in your working directory, read it first for context from prior attempts.");
1042
- lines.push("If it does not exist, proceed with the objective below.");
1227
+ if (mutationMode === "read_only") {
1228
+ lines.push("READ-ONLY EXECUTION. Do not create, edit, delete, rename, or move files.");
1229
+ lines.push("Inspect and verify only; report evidence without changing the workspace.");
1230
+ }
1231
+ else {
1232
+ lines.push("MAKE ALL REQUIRED FILE EDITS NOW. Do not ask for confirmation. Do not ask clarifying questions.");
1233
+ lines.push("Do not explain what you found without also making the changes. Edit the files and complete the task.");
1234
+ }
1043
1235
  lines.push("");
1044
- lines.push("Complete the following coding task. Make all necessary file changes.");
1045
- lines.push("When you are done, the verification commands listed below must pass.");
1236
+ if ((request.context.allowedPaths?.length ?? 0) === 0) {
1237
+ lines.push("If PROGRESS.md exists in your working directory, read it first for context from prior attempts.");
1238
+ lines.push("If it does not exist, proceed with the objective below.");
1239
+ lines.push("");
1240
+ }
1241
+ lines.push(mutationMode === "read_only"
1242
+ ? "Complete the following inspection task without making file changes."
1243
+ : "Complete the following coding task. Make all necessary file changes.");
1244
+ if (verificationIsHostOwned) {
1245
+ lines.push("MartinLoop owns verification and runs it after provider completion.");
1246
+ lines.push("Do not execute these verifier commands yourself.");
1247
+ }
1248
+ else {
1249
+ lines.push("When you are done, the verification commands listed below must pass.");
1250
+ }
1046
1251
  lines.push("");
1047
1252
  lines.push("OBJECTIVE:");
1048
1253
  lines.push(sanitizeForPrompt(request.context.objective));
@@ -1068,7 +1273,9 @@ function buildPrompt(request) {
1068
1273
  lines.push("");
1069
1274
  }
1070
1275
  if (request.context.verificationPlan.length > 0) {
1071
- lines.push("VERIFICATION (all commands must exit with code 0):");
1276
+ lines.push(verificationIsHostOwned
1277
+ ? "HOST-OWNED VERIFICATION (success conditions only; do not execute):"
1278
+ : "VERIFICATION (all commands must exit with code 0):");
1072
1279
  for (const cmd of request.context.verificationPlan) {
1073
1280
  lines.push(` ${cmd}`);
1074
1281
  }
@@ -1116,6 +1323,9 @@ function buildPrompt(request) {
1116
1323
  lines.push("");
1117
1324
  }
1118
1325
  lines.push(`FOCUS: ${sanitizeForPrompt(request.context.focus)}`);
1326
+ if (verificationIsHostOwned && request.context.verificationPlan.length > 0) {
1327
+ lines.push("FINAL BOUNDARY: stop after the scoped work and report it; MartinLoop will execute verification.");
1328
+ }
1119
1329
  return lines.join("\n");
1120
1330
  }
1121
1331
  // ---------------------------------------------------------------------------
@@ -1159,7 +1369,12 @@ function estimatePromptCost(promptText, model, providerCommand) {
1159
1369
  if (!pricing) {
1160
1370
  return undefined;
1161
1371
  }
1162
- return (inputTokens / 1000) * pricing.inputPer1K + (outputTokens / 1000) * pricing.outputPer1K;
1372
+ return calculateUsageCost({
1373
+ inputTokens,
1374
+ cachedInputTokens: 0,
1375
+ cacheCreationInputTokens: 0,
1376
+ outputTokens
1377
+ }, pricing, inputTokens);
1163
1378
  }
1164
1379
  function estimateUsage(promptText, model, providerCommand) {
1165
1380
  const inputTokens = Math.ceil(promptText.length / 3.5);
@@ -1,6 +1,8 @@
1
- import { type ChildProcess, type SpawnOptions } from "node:child_process";
1
+ import type { ChildProcess, SpawnOptions } from "node:child_process";
2
2
  import { diffStatsFromNumstat } from "./runtime-support.js";
3
3
  import type { VerifierExecutionBinding } from "../core/index.js";
4
+ import type { ExternalOutcomeEvidenceReference } from "../contracts/index.js";
5
+ import { type ProcessTreeCleanupResult } from "./process-supervisor.js";
4
6
  export type SpawnLike = (command: string, args?: readonly string[], options?: SpawnOptions) => ChildProcess;
5
7
  export interface SubprocessResult {
6
8
  exitCode: number;
@@ -27,9 +29,11 @@ export interface SubprocessResult {
27
29
  */
28
30
  terminationReason?: string;
29
31
  launched: boolean;
32
+ cleanup: ProcessTreeCleanupResult;
30
33
  }
31
34
  export interface VerificationOutcome {
32
35
  passed: boolean;
36
+ processCloseState: "closed" | "not_required" | "failed";
33
37
  summary: string;
34
38
  steps: VerificationStepOutcome[];
35
39
  warnings?: string[];
@@ -44,10 +48,12 @@ export interface VerificationStepOutcome {
44
48
  timedOut: boolean;
45
49
  fastFail: boolean;
46
50
  detail?: string;
51
+ evidence?: ExternalOutcomeEvidenceReference;
47
52
  }
48
53
  export declare function runSubprocess(command: string, args: string[], options: {
49
54
  cwd: string;
50
55
  timeoutMs: number;
56
+ env?: NodeJS.ProcessEnv;
51
57
  spawnImpl?: SpawnLike;
52
58
  stdinData?: string;
53
59
  /**
@@ -68,14 +74,17 @@ export declare function runSubprocess(command: string, args: string[], options:
68
74
  onStdoutChunk?: (chunk: Buffer, terminate: (reason: string) => void) => void;
69
75
  /** Optional abort signal — kills the subprocess when aborted. */
70
76
  signal?: AbortSignal;
77
+ /** Require an owned process-tree sweep before successful completion. */
78
+ requireTreeClosureOnSuccess?: boolean;
71
79
  }): Promise<SubprocessResult>;
72
80
  export declare function runVerification(commands: string[], cwd: string, timeoutMs: number, verificationStack?: Array<{
73
81
  command: string;
74
82
  type: string;
75
83
  fastFail?: boolean;
76
- }>, spawnImpl?: SpawnLike, binding?: Omit<VerifierExecutionBinding, "commands">): Promise<VerificationOutcome>;
77
- export declare function readGitExecutionArtifacts(repoRoot: string, timeoutMs: number, spawnImpl?: SpawnLike): Promise<{
84
+ }>, spawnImpl?: SpawnLike, binding?: Omit<VerifierExecutionBinding, "commands">, signal?: AbortSignal): Promise<VerificationOutcome>;
85
+ export declare function readGitExecutionArtifacts(repoRoot: string, timeoutMs: number, spawnImpl?: SpawnLike, requestedChangedFiles?: readonly string[]): Promise<{
78
86
  changedFiles?: string[];
87
+ patch?: string;
79
88
  diffStats?: ReturnType<typeof diffStatsFromNumstat>;
80
89
  }>;
81
90
  export declare function readGitChangedFiles(repoRoot: string, timeoutMs: number, spawnImpl?: SpawnLike): Promise<string[]>;