@martinloop/mcp 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/vendor/adapters/claude-cli.d.ts +11 -1
- package/dist/vendor/adapters/claude-cli.js +285 -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-cli.d.ts +3 -3
- package/dist/vendor/adapters/codex-cli.js +10 -24
- package/dist/vendor/adapters/codex-host.js +127 -132
- package/dist/vendor/adapters/index.d.ts +2 -1
- package/dist/vendor/adapters/index.js +2 -1
- 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 +8 -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 +47 -1
- package/dist/vendor/core/index.js +53 -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/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
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MARTIN_MCP_PACKAGE_VERSION = "0.
|
|
1
|
+
export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.0";
|
package/dist/package-version.js
CHANGED
|
@@ -22,8 +22,10 @@ import { type SpawnLike } from "./cli-bridge.js";
|
|
|
22
22
|
export type CliArgsBuilder = (prompt: string, request: MartinAdapterRequest) => string[];
|
|
23
23
|
export type CliStdinBuilder = (prompt: string) => string | undefined;
|
|
24
24
|
export interface AgentCliAdapterOptions {
|
|
25
|
-
/**
|
|
25
|
+
/** Stable provider identity (e.g. "claude", "codex"). */
|
|
26
26
|
command: string;
|
|
27
|
+
/** Exact executable to spawn when it differs from the provider identity. */
|
|
28
|
+
executionCommand?: string;
|
|
27
29
|
/** Converts a prompt string into the argv array passed to spawn(). */
|
|
28
30
|
argsBuilder: CliArgsBuilder;
|
|
29
31
|
/** Optional stdin payload for CLIs that accept prompt input via stdin or `-`. */
|
|
@@ -57,6 +59,10 @@ export interface AgentCliAdapterOptions {
|
|
|
57
59
|
* rather than only learning about an overspend after the process exits.
|
|
58
60
|
*/
|
|
59
61
|
streamingUsageCap?: boolean;
|
|
62
|
+
/** Terminate after streamed provider usage reports request.context.remainingTokens was exceeded. */
|
|
63
|
+
streamingTokenCap?: boolean;
|
|
64
|
+
/** Provider detail-token fields are already included in input/output token totals. */
|
|
65
|
+
streamingUsageDetailsIncludedInTotals?: boolean;
|
|
60
66
|
/** Test-only override for subprocess spawning. */
|
|
61
67
|
spawnImpl?: SpawnLike;
|
|
62
68
|
}
|
|
@@ -69,6 +75,8 @@ export interface ClaudeCliAdapterOptions {
|
|
|
69
75
|
label?: string;
|
|
70
76
|
/** Override the model passed via --model flag. */
|
|
71
77
|
model?: string;
|
|
78
|
+
/** Enforce Claude Code's non-mutating plan permission mode. */
|
|
79
|
+
readOnly?: boolean;
|
|
72
80
|
/** Extra args appended after core args (before prompt). */
|
|
73
81
|
extraArgs?: string[];
|
|
74
82
|
spawnImpl?: SpawnLike;
|
|
@@ -107,6 +115,8 @@ export interface GeminiCliAdapterOptions {
|
|
|
107
115
|
label?: string;
|
|
108
116
|
/** Explicit model override passed via --model. Omitted to preserve Gemini Auto. */
|
|
109
117
|
model?: string;
|
|
118
|
+
/** Enforce Gemini's non-mutating plan approval mode. */
|
|
119
|
+
readOnly?: boolean;
|
|
110
120
|
/** Approval mode for headless Gemini runs. Defaults to yolo for autonomous execution. */
|
|
111
121
|
approvalMode?: "default" | "auto_edit" | "yolo" | "plan";
|
|
112
122
|
/** Enable Gemini sandbox mode when the host is configured for it. Disabled by default. */
|
|
@@ -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) => {
|
|
@@ -582,10 +722,12 @@ export function createAgentCliAdapter(options) {
|
|
|
582
722
|
// watch per-turn usage events as they arrive and kill the subprocess the
|
|
583
723
|
// instant projected spend crosses what remains — bounding the worst case
|
|
584
724
|
// to roughly one turn's overshoot rather than the entire runaway session.
|
|
585
|
-
const streamingUsage = options.streamingUsageCap &&
|
|
586
|
-
|
|
725
|
+
const streamingUsage = options.streamingUsageCap &&
|
|
726
|
+
(request.context.remainingBudgetUsd > 0 ||
|
|
727
|
+
(options.streamingTokenCap && (request.context.remainingTokens ?? 0) > 0))
|
|
728
|
+
? createStreamingUsageInspector(request.context.remainingBudgetUsd, options.model ?? options.command, estimatedUsage.tokensIn, options.streamingTokenCap ? request.context.remainingTokens : undefined, options.streamingUsageDetailsIncludedInTotals, request.observedUsageGovernor)
|
|
587
729
|
: undefined;
|
|
588
|
-
const agentResult = await runSubprocess(options.command, args, {
|
|
730
|
+
const agentResult = await runSubprocess(options.executionCommand ?? options.command, args, {
|
|
589
731
|
cwd: workingDirectory,
|
|
590
732
|
timeoutMs: executionTimeoutMs,
|
|
591
733
|
spawnImpl: options.spawnImpl,
|
|
@@ -747,8 +889,12 @@ export function createAgentCliAdapter(options) {
|
|
|
747
889
|
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, verificationStack, options.spawnImpl, {
|
|
748
890
|
runId: request.loopId,
|
|
749
891
|
workspaceId: request.workspaceId,
|
|
892
|
+
attemptId: request.attemptId,
|
|
750
893
|
cwd: workingDirectory,
|
|
751
|
-
|
|
894
|
+
...(request.context.runsRoot ? { runsRoot: request.context.runsRoot } : {}),
|
|
895
|
+
...(request.context.executionProfile ? { executionProfile: request.context.executionProfile } : {}),
|
|
896
|
+
...(request.context.allowedNetworkDomains?.length ? { allowedNetworkDomains: [...request.context.allowedNetworkDomains] } : {}),
|
|
897
|
+
}, request.signal);
|
|
752
898
|
const verificationConfigured = verification.binding.commands.length > 0;
|
|
753
899
|
// Check for zero-diff (agent ran but made no file changes)
|
|
754
900
|
const postRunChangedFiles = gitRepoRoot
|
|
@@ -759,11 +905,12 @@ export function createAgentCliAdapter(options) {
|
|
|
759
905
|
// Extract structured errors from stderr/stdout for better failure context
|
|
760
906
|
const structuredErrors = normalizeStructuredErrors(extractStructuredErrors(agentResult.stderr, agentResult.stdout));
|
|
761
907
|
const rawExecutionArtifacts = gitRepoRoot
|
|
762
|
-
? await readGitExecutionArtifacts(gitRepoRoot, 5000, options.spawnImpl)
|
|
908
|
+
? await readGitExecutionArtifacts(gitRepoRoot, 5000, options.spawnImpl, agentChangedFiles)
|
|
763
909
|
: undefined;
|
|
764
910
|
const executionArtifacts = rawExecutionArtifacts
|
|
765
911
|
? {
|
|
766
912
|
...(agentChangedFiles.length > 0 ? { changedFiles: agentChangedFiles } : {}),
|
|
913
|
+
...(rawExecutionArtifacts.patch ? { patch: rawExecutionArtifacts.patch } : {}),
|
|
767
914
|
...(baselineChangedFiles.size === 0 && rawExecutionArtifacts.diffStats
|
|
768
915
|
? { diffStats: rawExecutionArtifacts.diffStats }
|
|
769
916
|
: {})
|
|
@@ -821,8 +968,11 @@ export function createAgentCliAdapter(options) {
|
|
|
821
968
|
const scopeBlock = scopeViolations.length > 0
|
|
822
969
|
? `\n Scope violations: ${scopeViolations.join(", ")}`
|
|
823
970
|
: "";
|
|
824
|
-
//
|
|
825
|
-
|
|
971
|
+
// PROGRESS.md is legacy adapter-local retry state. Never create it in a
|
|
972
|
+
// scope-constrained workspace: doing so manufactures an undeclared
|
|
973
|
+
// product change and causes the Core leash to hide the provider's real
|
|
974
|
+
// failure behind surface_write_not_allowed.
|
|
975
|
+
if (repoRoot && (request.context.allowedPaths?.length ?? 0) === 0) {
|
|
826
976
|
try {
|
|
827
977
|
const { writeFile, readFile, appendFile: appendFs } = await import("node:fs/promises");
|
|
828
978
|
const progressPath = `${repoRoot}/PROGRESS.md`;
|
|
@@ -882,6 +1032,33 @@ export function createAgentCliAdapter(options) {
|
|
|
882
1032
|
// ---------------------------------------------------------------------------
|
|
883
1033
|
// Pre-configured: Claude CLI
|
|
884
1034
|
// ---------------------------------------------------------------------------
|
|
1035
|
+
const CLAUDE_PERMISSION_CONTROL_ARGS = new Set([
|
|
1036
|
+
"--allow-dangerously-skip-permissions",
|
|
1037
|
+
"--allowed-tools",
|
|
1038
|
+
"--allowedTools",
|
|
1039
|
+
"--dangerously-skip-permissions",
|
|
1040
|
+
"--permission-mode"
|
|
1041
|
+
]);
|
|
1042
|
+
function assertClaudePermissionControlsAreGoverned(extraArgs) {
|
|
1043
|
+
const overridesPermissionControl = extraArgs.some((arg) => {
|
|
1044
|
+
const [flag] = arg.split("=", 1);
|
|
1045
|
+
return flag !== undefined && CLAUDE_PERMISSION_CONTROL_ARGS.has(flag);
|
|
1046
|
+
});
|
|
1047
|
+
if (overridesPermissionControl) {
|
|
1048
|
+
throw new Error("Claude permission controls cannot be overridden via extraArgs.");
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function hostOwnsVerification(request) {
|
|
1052
|
+
return request.context.verificationExecutionOwner === "host_only";
|
|
1053
|
+
}
|
|
1054
|
+
function buildClaudeVerifierAllowedTools(request) {
|
|
1055
|
+
if (hostOwnsVerification(request))
|
|
1056
|
+
return [];
|
|
1057
|
+
const commands = [...new Set(request.context.verificationPlan.filter((command) => command.trim().length > 0))];
|
|
1058
|
+
return commands.length > 0
|
|
1059
|
+
? ["--allowedTools", ...commands.map((command) => `Bash(${command})`)]
|
|
1060
|
+
: [];
|
|
1061
|
+
}
|
|
885
1062
|
/**
|
|
886
1063
|
* Spawns `claude --output-format stream-json --verbose --print "<prompt>" [extraArgs]`.
|
|
887
1064
|
*
|
|
@@ -900,6 +1077,7 @@ export function createAgentCliAdapter(options) {
|
|
|
900
1077
|
export function createClaudeCliAdapter(options = {}) {
|
|
901
1078
|
const modelArgs = options.model ? ["--model", options.model] : [];
|
|
902
1079
|
const extraArgs = options.extraArgs ?? [];
|
|
1080
|
+
assertClaudePermissionControlsAreGoverned(extraArgs);
|
|
903
1081
|
return createAgentCliAdapter({
|
|
904
1082
|
command: "claude",
|
|
905
1083
|
adapterIdSuffix: "claude",
|
|
@@ -912,20 +1090,26 @@ export function createClaudeCliAdapter(options = {}) {
|
|
|
912
1090
|
verifyTimeoutMs: options.verifyTimeoutMs,
|
|
913
1091
|
supportsJsonOutput: true,
|
|
914
1092
|
streamingUsageCap: true,
|
|
1093
|
+
streamingTokenCap: true,
|
|
915
1094
|
spawnImpl: options.spawnImpl,
|
|
916
1095
|
argsBuilder: (_prompt, request) => [
|
|
917
1096
|
"--output-format",
|
|
918
1097
|
"stream-json",
|
|
919
1098
|
"--verbose",
|
|
920
1099
|
"--print",
|
|
921
|
-
|
|
1100
|
+
...(options.readOnly
|
|
1101
|
+
? ["--permission-mode", "plan"]
|
|
1102
|
+
: ["--permission-mode", "acceptEdits"]),
|
|
1103
|
+
...buildClaudeVerifierAllowedTools(request),
|
|
922
1104
|
// Subprocess isolation strategy:
|
|
923
1105
|
// --bare: skips hooks (prevents SessionEnd/hook failures causing non-zero exits),
|
|
924
1106
|
// MCP server loading, LSP, CLAUDE.md discovery, and background prefetches.
|
|
925
1107
|
// Requires ANTHROPIC_API_KEY (OAuth/keychain auth not available in bare mode).
|
|
926
1108
|
// --strict-mcp-config: fallback when ANTHROPIC_API_KEY is not set — still
|
|
927
1109
|
// prevents parent MCP servers from being inherited by the subprocess.
|
|
928
|
-
...(process.env["ANTHROPIC_API_KEY"]
|
|
1110
|
+
...(process.env["ANTHROPIC_API_KEY"]
|
|
1111
|
+
? ["--bare"]
|
|
1112
|
+
: ["--strict-mcp-config", "--setting-sources", "project", "--disable-slash-commands"]),
|
|
929
1113
|
// NOTE: --max-tokens does not exist in the claude CLI. Token cap enforcement
|
|
930
1114
|
// is handled at the MartinLoop layer via streamingUsageCap, not via subprocess flags.
|
|
931
1115
|
...modelArgs,
|
|
@@ -988,10 +1172,13 @@ export function createCodexCliAdapter(options = {}) {
|
|
|
988
1172
|
* npm install -g @google/gemini-cli
|
|
989
1173
|
*/
|
|
990
1174
|
export function createGeminiCliAdapter(options = {}) {
|
|
991
|
-
const approvalMode = options.approvalMode ?? "yolo";
|
|
992
|
-
if (approvalMode !== "yolo") {
|
|
1175
|
+
const approvalMode = options.readOnly ? "plan" : options.approvalMode ?? "yolo";
|
|
1176
|
+
if (!options.readOnly && approvalMode !== "yolo") {
|
|
993
1177
|
throw new Error("governed-autonomous Gemini execution requires yolo approval mode; interactive downgrade rejected.");
|
|
994
1178
|
}
|
|
1179
|
+
if (options.readOnly && options.approvalMode !== undefined && options.approvalMode !== "plan") {
|
|
1180
|
+
throw new Error("read-only Gemini execution requires plan approval mode.");
|
|
1181
|
+
}
|
|
995
1182
|
const extraArgs = options.extraArgs ?? [];
|
|
996
1183
|
if (extraArgs.some((arg) => arg === "--approval-mode" || arg.startsWith("--approval-mode="))) {
|
|
997
1184
|
throw new Error("Gemini approval mode cannot be overridden through extraArgs.");
|
|
@@ -1034,15 +1221,32 @@ export function createGeminiCliAdapter(options = {}) {
|
|
|
1034
1221
|
function buildPrompt(request) {
|
|
1035
1222
|
const lines = [];
|
|
1036
1223
|
const mutationMode = request.context.mutationMode ?? "edit";
|
|
1224
|
+
const verificationIsHostOwned = hostOwnsVerification(request);
|
|
1037
1225
|
lines.push("You are running in autonomous agentic mode.");
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1226
|
+
if (mutationMode === "read_only") {
|
|
1227
|
+
lines.push("READ-ONLY EXECUTION. Do not create, edit, delete, rename, or move files.");
|
|
1228
|
+
lines.push("Inspect and verify only; report evidence without changing the workspace.");
|
|
1229
|
+
}
|
|
1230
|
+
else {
|
|
1231
|
+
lines.push("MAKE ALL REQUIRED FILE EDITS NOW. Do not ask for confirmation. Do not ask clarifying questions.");
|
|
1232
|
+
lines.push("Do not explain what you found without also making the changes. Edit the files and complete the task.");
|
|
1233
|
+
}
|
|
1043
1234
|
lines.push("");
|
|
1044
|
-
|
|
1045
|
-
|
|
1235
|
+
if ((request.context.allowedPaths?.length ?? 0) === 0) {
|
|
1236
|
+
lines.push("If PROGRESS.md exists in your working directory, read it first for context from prior attempts.");
|
|
1237
|
+
lines.push("If it does not exist, proceed with the objective below.");
|
|
1238
|
+
lines.push("");
|
|
1239
|
+
}
|
|
1240
|
+
lines.push(mutationMode === "read_only"
|
|
1241
|
+
? "Complete the following inspection task without making file changes."
|
|
1242
|
+
: "Complete the following coding task. Make all necessary file changes.");
|
|
1243
|
+
if (verificationIsHostOwned) {
|
|
1244
|
+
lines.push("MartinLoop owns verification and runs it after provider completion.");
|
|
1245
|
+
lines.push("Do not execute these verifier commands yourself.");
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
lines.push("When you are done, the verification commands listed below must pass.");
|
|
1249
|
+
}
|
|
1046
1250
|
lines.push("");
|
|
1047
1251
|
lines.push("OBJECTIVE:");
|
|
1048
1252
|
lines.push(sanitizeForPrompt(request.context.objective));
|
|
@@ -1068,7 +1272,9 @@ function buildPrompt(request) {
|
|
|
1068
1272
|
lines.push("");
|
|
1069
1273
|
}
|
|
1070
1274
|
if (request.context.verificationPlan.length > 0) {
|
|
1071
|
-
lines.push(
|
|
1275
|
+
lines.push(verificationIsHostOwned
|
|
1276
|
+
? "HOST-OWNED VERIFICATION (success conditions only; do not execute):"
|
|
1277
|
+
: "VERIFICATION (all commands must exit with code 0):");
|
|
1072
1278
|
for (const cmd of request.context.verificationPlan) {
|
|
1073
1279
|
lines.push(` ${cmd}`);
|
|
1074
1280
|
}
|
|
@@ -1116,6 +1322,9 @@ function buildPrompt(request) {
|
|
|
1116
1322
|
lines.push("");
|
|
1117
1323
|
}
|
|
1118
1324
|
lines.push(`FOCUS: ${sanitizeForPrompt(request.context.focus)}`);
|
|
1325
|
+
if (verificationIsHostOwned && request.context.verificationPlan.length > 0) {
|
|
1326
|
+
lines.push("FINAL BOUNDARY: stop after the scoped work and report it; MartinLoop will execute verification.");
|
|
1327
|
+
}
|
|
1119
1328
|
return lines.join("\n");
|
|
1120
1329
|
}
|
|
1121
1330
|
// ---------------------------------------------------------------------------
|
|
@@ -1159,7 +1368,12 @@ function estimatePromptCost(promptText, model, providerCommand) {
|
|
|
1159
1368
|
if (!pricing) {
|
|
1160
1369
|
return undefined;
|
|
1161
1370
|
}
|
|
1162
|
-
return (
|
|
1371
|
+
return calculateUsageCost({
|
|
1372
|
+
inputTokens,
|
|
1373
|
+
cachedInputTokens: 0,
|
|
1374
|
+
cacheCreationInputTokens: 0,
|
|
1375
|
+
outputTokens
|
|
1376
|
+
}, pricing, inputTokens);
|
|
1163
1377
|
}
|
|
1164
1378
|
function estimateUsage(promptText, model, providerCommand) {
|
|
1165
1379
|
const inputTokens = Math.ceil(promptText.length / 3.5);
|