@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.
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/vendor/adapters/claude-cli.d.ts +16 -1
- package/dist/vendor/adapters/claude-cli.js +286 -71
- package/dist/vendor/adapters/cli-bridge.d.ts +12 -3
- package/dist/vendor/adapters/cli-bridge.js +129 -149
- package/dist/vendor/adapters/codex-capabilities.d.ts +4 -1
- package/dist/vendor/adapters/codex-capabilities.js +36 -4
- package/dist/vendor/adapters/codex-cli.d.ts +10 -3
- package/dist/vendor/adapters/codex-cli.js +21 -24
- package/dist/vendor/adapters/codex-host.js +150 -132
- package/dist/vendor/adapters/index.d.ts +3 -2
- package/dist/vendor/adapters/index.js +3 -2
- package/dist/vendor/adapters/openai-compatible.js +88 -6
- package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
- package/dist/vendor/adapters/process-supervisor.js +541 -0
- package/dist/vendor/adapters/verifier-only.d.ts +10 -1
- package/dist/vendor/adapters/verifier-only.js +43 -1
- package/dist/vendor/contracts/external-outcome.d.ts +102 -0
- package/dist/vendor/contracts/external-outcome.js +226 -0
- package/dist/vendor/contracts/index.d.ts +12 -1
- package/dist/vendor/contracts/index.js +2 -0
- package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
- package/dist/vendor/contracts/swarm-hosted.js +629 -0
- package/dist/vendor/contracts/swarm.d.ts +449 -0
- package/dist/vendor/contracts/swarm.js +550 -0
- package/dist/vendor/core/external-outcome.d.ts +34 -0
- package/dist/vendor/core/external-outcome.js +515 -0
- package/dist/vendor/core/grounding.js +1 -1
- package/dist/vendor/core/index.d.ts +52 -1
- package/dist/vendor/core/index.js +55 -3
- package/dist/vendor/core/leash.d.ts +1 -0
- package/dist/vendor/core/leash.js +13 -0
- package/dist/vendor/core/persistence/integrity.d.ts +4 -0
- package/dist/vendor/core/persistence/integrity.js +19 -6
- package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
- package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
- package/dist/vendor/core/policy.d.ts +3 -0
- package/dist/vendor/core/policy.js +13 -0
- package/dist/vendor/core/swarm/candidates.d.ts +78 -0
- package/dist/vendor/core/swarm/candidates.js +810 -0
- package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
- package/dist/vendor/core/swarm/evidence-projection.js +128 -0
- package/dist/vendor/core/swarm/evidence.d.ts +40 -0
- package/dist/vendor/core/swarm/evidence.js +575 -0
- package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
- package/dist/vendor/core/swarm/hosted-export.js +362 -0
- package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
- package/dist/vendor/core/swarm/hosted-projection.js +258 -0
- package/dist/vendor/core/swarm/index.d.ts +110 -0
- package/dist/vendor/core/swarm/index.js +1060 -0
- package/dist/vendor/core/swarm/integration.d.ts +76 -0
- package/dist/vendor/core/swarm/integration.js +615 -0
- package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
- package/dist/vendor/core/swarm/lifecycle.js +252 -0
- package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
- package/dist/vendor/core/swarm/live-runtime.js +1482 -0
- package/dist/vendor/core/swarm/live-store.d.ts +99 -0
- package/dist/vendor/core/swarm/live-store.js +860 -0
- package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
- package/dist/vendor/core/swarm/operational-reader.js +96 -0
- package/dist/vendor/core/swarm/operations.d.ts +29 -0
- package/dist/vendor/core/swarm/operations.js +128 -0
- package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
- package/dist/vendor/core/swarm/parent-receipt.js +489 -0
- package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
- package/dist/vendor/core/swarm/receipt-projection.js +120 -0
- package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
- package/dist/vendor/core/swarm/scheduler.js +555 -0
- package/dist/vendor/core/swarm/verification.d.ts +70 -0
- package/dist/vendor/core/swarm/verification.js +295 -0
- package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
- package/dist/vendor/core/swarm/workspaces.js +488 -0
- package/dist/vendor/core/verified-handoff.d.ts +4 -0
- package/dist/vendor/core/verified-handoff.js +4 -0
- package/package.json +1 -1
- 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
|
-
|
|
28
|
-
//
|
|
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
|
-
|
|
31
|
-
"claude-
|
|
32
|
-
"claude-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
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-
|
|
43
|
-
{ canonicalModelId: "claude-
|
|
44
|
-
{ canonicalModelId: "claude-
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
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 = (
|
|
201
|
-
(
|
|
202
|
-
|
|
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
|
|
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 = (
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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 = (
|
|
359
|
-
?
|
|
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.
|
|
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 =
|
|
370
|
-
|
|
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:
|
|
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 &&
|
|
586
|
-
|
|
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
|
-
//
|
|
825
|
-
|
|
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
|
-
|
|
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"]
|
|
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
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
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
|
-
|
|
1045
|
-
|
|
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(
|
|
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 (
|
|
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 {
|
|
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"
|
|
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[]>;
|