@martinloop/mcp 0.5.0 → 0.5.3
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/README.md +52 -14
- package/dist/resources.js +15 -3
- package/dist/server.js +40 -8
- package/dist/tools/doctor.js +5 -4
- package/dist/tools/get-status.d.ts +2 -0
- package/dist/tools/get-status.js +1 -0
- package/dist/tools/preflight.js +1 -1
- package/dist/tools/run-loop.d.ts +6 -1
- package/dist/tools/run-loop.js +22 -7
- package/dist/tools/tool-errors.js +1 -1
- package/dist/tools/tool-response.js +9 -2
- package/dist/tools/tool-support.d.ts +3 -2
- package/dist/tools/tool-support.js +5 -3
- package/dist/vendor/adapters/claude-cli.d.ts +2 -2
- package/dist/vendor/adapters/claude-cli.js +59 -34
- package/dist/vendor/adapters/codex-capabilities.d.ts +60 -0
- package/dist/vendor/adapters/codex-capabilities.js +237 -0
- package/dist/vendor/adapters/codex-cli.d.ts +22 -0
- package/dist/vendor/adapters/codex-cli.js +62 -0
- package/dist/vendor/adapters/codex-host.d.ts +104 -0
- package/dist/vendor/adapters/codex-host.js +577 -0
- package/dist/vendor/adapters/codex-launcher.d.ts +7 -135
- package/dist/vendor/adapters/codex-launcher.js +17 -766
- package/dist/vendor/adapters/index.d.ts +4 -3
- package/dist/vendor/adapters/index.js +4 -3
- package/dist/vendor/adapters/openai-compatible.d.ts +2 -3
- package/dist/vendor/adapters/openai-compatible.js +33 -7
- package/dist/vendor/adapters/verifier-only.d.ts +9 -0
- package/dist/vendor/adapters/verifier-only.js +68 -0
- package/dist/vendor/contracts/index.d.ts +16 -6
- package/dist/vendor/contracts/index.js +1 -1
- package/dist/vendor/contracts/verified-handoff.d.ts +6 -0
- package/dist/vendor/contracts/verified-handoff.js +5 -0
- package/dist/vendor/core/grounding.d.ts +2 -1
- package/dist/vendor/core/grounding.js +40 -5
- package/dist/vendor/core/index.d.ts +4 -6
- package/dist/vendor/core/index.js +25 -19
- package/dist/vendor/core/persistence/runs-reader.d.ts +2 -0
- package/dist/vendor/core/policy.js +6 -6
- package/dist/vendor/core/red-blue/red-phase.d.ts +2 -1
- package/dist/vendor/core/red-blue/red-phase.js +3 -3
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +2 -4
- package/dist/vendor/core/red-blue/risk-tiers.js +1 -3
- package/dist/vendor/core/routing.d.ts +0 -42
- package/dist/vendor/core/routing.js +4 -154
- package/dist/vendor/core/verified-handoff.d.ts +6 -1
- package/dist/vendor/core/verified-handoff.js +28 -0
- package/package.json +6 -2
- package/server.json +2 -2
- package/dist/vendor/adapters/stub-direct-provider.d.ts +0 -8
- package/dist/vendor/adapters/stub-direct-provider.js +0 -12
package/README.md
CHANGED
|
@@ -6,11 +6,31 @@ But did it actually work? Did it pass your tests? How much did it spend? What fi
|
|
|
6
6
|
|
|
7
7
|
You don't know. And that's the problem.
|
|
8
8
|
|
|
9
|
+
## One system around the coding agent
|
|
10
|
+
|
|
11
|
+
MartinLoop is the execution-control system around coding agents. The coding agent still performs the software work. MartinLoop connects the run from Definition of Done through preflight, control, verification, recovery evidence, receipts, and post-run analysis.
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
DEFINE -> PREFLIGHT -> CONTROL -> VERIFY -> RECOVER -> PROVE -> ANALYZE
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The product-level flow is **Definition of Done -> Controlled Run -> Verified Handoff**.
|
|
18
|
+
|
|
19
|
+
Through MCP, compatible hosts can use the same MartinLoop lifecycle without giving up the host's own model selection. MartinLoop does not inject a hidden fallback model.
|
|
20
|
+
|
|
21
|
+
For agent-readable product context see [`../../llms.txt`](../../llms.txt), [`../../llms-full.txt`](../../llms-full.txt), and [`../../docs/for-agents.md`](../../docs/for-agents.md).
|
|
22
|
+
|
|
9
23
|
## What This Does
|
|
10
24
|
|
|
11
|
-
MartinLoop
|
|
25
|
+
MartinLoop governs AI coding-agent execution. You define what the run needs to accomplish, set execution boundaries such as budget and verifier checks, and let the configured coding agent do the work inside that contract.
|
|
26
|
+
|
|
27
|
+
When it's done, you get a receipt — not a vague summary, but a structured record: dollars spent when available, attempts made, verification results, files changed, recovery state, and what the available evidence established.
|
|
28
|
+
|
|
29
|
+
The final governed outcome is one of:
|
|
12
30
|
|
|
13
|
-
|
|
31
|
+
- `VERIFIED` when the configured evidence supports the Definition of Done
|
|
32
|
+
- `STOPPED` when a configured hard boundary ends the run
|
|
33
|
+
- `NEEDS REVIEW` when completion cannot be established from the available evidence
|
|
14
34
|
|
|
15
35
|
**One line to connect it:**
|
|
16
36
|
|
|
@@ -18,7 +38,7 @@ When it's done, you get a receipt — not a vague summary, but a structured reco
|
|
|
18
38
|
claude mcp add martin-loop -- npx -y @martinloop/mcp
|
|
19
39
|
```
|
|
20
40
|
|
|
21
|
-
That's it. Your
|
|
41
|
+
That's it. Your host can now use MartinLoop's governed run and evidence surfaces.
|
|
22
42
|
|
|
23
43
|
## Real Numbers From Real Runs
|
|
24
44
|
|
|
@@ -37,27 +57,27 @@ These aren't hypotheticals. The $28 overshoot happened in production testing. We
|
|
|
37
57
|
|
|
38
58
|
### Claude Code
|
|
39
59
|
```sh
|
|
40
|
-
claude mcp add martin-loop -- npx -y @martinloop/mcp
|
|
60
|
+
claude mcp add martin-loop -- npx -y @martinloop/mcp@0.5.3
|
|
41
61
|
```
|
|
42
62
|
|
|
43
63
|
Windows:
|
|
44
64
|
```sh
|
|
45
|
-
claude mcp add --transport stdio --scope user martin-loop -- cmd /c npx -y @martinloop/mcp
|
|
65
|
+
claude mcp add --transport stdio --scope user martin-loop -- cmd /c npx -y @martinloop/mcp@0.5.3
|
|
46
66
|
```
|
|
47
67
|
|
|
48
68
|
### Codex
|
|
49
69
|
```sh
|
|
50
|
-
codex mcp add martin-loop -- npx -y @martinloop/mcp
|
|
70
|
+
codex mcp add martin-loop -- npx -y @martinloop/mcp@0.5.3
|
|
51
71
|
```
|
|
52
72
|
|
|
53
73
|
### Gemini CLI
|
|
54
74
|
```sh
|
|
55
|
-
gemini mcp add martin-loop -- npx -y @martinloop/mcp
|
|
75
|
+
gemini mcp add martin-loop -- npx -y @martinloop/mcp@0.5.3
|
|
56
76
|
```
|
|
57
77
|
|
|
58
78
|
### Any MCP Host
|
|
59
79
|
```sh
|
|
60
|
-
npx -y @martinloop/mcp
|
|
80
|
+
npx -y @martinloop/mcp@0.5.3
|
|
61
81
|
```
|
|
62
82
|
|
|
63
83
|
## How a Governed Run Works
|
|
@@ -69,14 +89,16 @@ You: "Fix the auth bug. Budget $3. Verify: npm test"
|
|
|
69
89
|
martin_plan → scopes the task, sets constraints
|
|
70
90
|
martin_preflight → validates before any spend
|
|
71
91
|
martin_run → agent works inside budget + verifier gates
|
|
72
|
-
martin_dossier → receipt:
|
|
92
|
+
martin_dossier → receipt: spend, attempts, verifier evidence, final outcome
|
|
73
93
|
```
|
|
74
94
|
|
|
75
|
-
Every attempt
|
|
95
|
+
Every attempt can be checked by your configured verifier. Budget and policy controls stay attached to the run. If the agent drifts off-task, the scope contract can block the change. If a hard spend boundary is reached, the run stops according to policy instead of silently retrying forever.
|
|
76
96
|
|
|
77
97
|
## What Your Agent Gets
|
|
78
98
|
|
|
79
|
-
###
|
|
99
|
+
### MCP Tools
|
|
100
|
+
|
|
101
|
+
The exact tool set is discoverable from the running MCP server with `tools/list`; do not rely on a hard-coded count in documentation.
|
|
80
102
|
|
|
81
103
|
**Run the loop:**
|
|
82
104
|
`martin_doctor` `martin_plan` `martin_preflight` `martin_run` `martin_pause` `martin_continue` `martin_cancel`
|
|
@@ -87,7 +109,9 @@ Every attempt runs your verifier. Every dollar is tracked. If the agent drifts o
|
|
|
87
109
|
**Ship the work:**
|
|
88
110
|
`martin_pr_summary` `martin_create_pr` `martin_review_pr`
|
|
89
111
|
|
|
90
|
-
###
|
|
112
|
+
### Read-Only Resources
|
|
113
|
+
|
|
114
|
+
The exact resource set is discoverable from the running MCP server; this documentation names the primary public resources without treating a count as a release invariant.
|
|
91
115
|
|
|
92
116
|
Your agent can pull context without side effects:
|
|
93
117
|
|
|
@@ -106,20 +130,33 @@ npx martin-loop mcp print-config --host claude --profile github-review # + PR w
|
|
|
106
130
|
|
|
107
131
|
## What Happens When Things Go Wrong
|
|
108
132
|
|
|
109
|
-
MartinLoop
|
|
133
|
+
MartinLoop does not round failure into success. It preserves the evidence and follows the configured recovery path while budget and policy still allow useful work.
|
|
110
134
|
|
|
111
135
|
| Failure | Old behavior | Now |
|
|
112
136
|
|---|---|---|
|
|
113
137
|
| CLI not on PATH | Error message, dead stop | Searches npm global, homebrew, nvm, scoop — uses what it finds |
|
|
114
|
-
| Verifier says "command not found" | Generic "verification failed" |
|
|
138
|
+
| Verifier says "command not found" | Generic "verification failed" | Carries specific failure context into the next allowed attempt |
|
|
115
139
|
| `git restore` fails mid-rollback | Throws, leaves dirty state | Retries once, falls back to `git checkout`, cleans up |
|
|
116
140
|
| Invalid `--profile` flag | Crashes | Warns, falls back to `minimal`, keeps running |
|
|
117
141
|
|
|
142
|
+
## Codex compatibility in 0.5.3
|
|
143
|
+
|
|
144
|
+
MartinLoop no longer assumes one Codex host or one fixed Codex flag set. The CLI resolves the exact Codex executable, reads its advertised capabilities, negotiates a supported write strategy, proves that strategy before reporting launch readiness, and reuses the same contract for the governed run.
|
|
145
|
+
|
|
146
|
+
That means MCP hosts can point MartinLoop at different Codex installations without requiring a documentation-specific approval or sandbox flag to exist everywhere.
|
|
147
|
+
|
|
148
|
+
## Presentation and evidence
|
|
149
|
+
|
|
150
|
+
MCP responses are Markdown-first for humans while retaining structured content for agents and automation. The presentation layer does not rewrite governed truth.
|
|
151
|
+
|
|
152
|
+
MartinLoop Arcade is a terminal-only experience. It does not run inside MCP and cannot influence MCP evidence, budgets, verification, or final outcomes.
|
|
153
|
+
|
|
118
154
|
## Who Uses This
|
|
119
155
|
|
|
120
156
|
- **Engineers running overnight agent loops** — you need a kill switch that actually works, and a receipt in the morning
|
|
121
157
|
- **Teams with shared API budgets** — you need per-task spend caps, not org-wide prayer
|
|
122
158
|
- **Anyone reviewing agent-generated PRs** — the dossier shows what the agent tried, what passed, and what didn't
|
|
159
|
+
- **Teams tired of stitching together point tools** — you want one execution-control lifecycle around coding-agent work from preflight through post-run analysis
|
|
123
160
|
|
|
124
161
|
## Requirements
|
|
125
162
|
|
|
@@ -129,6 +166,7 @@ MartinLoop doesn't just report failures — it tries to fix them:
|
|
|
129
166
|
## Links
|
|
130
167
|
|
|
131
168
|
- [martin-loop](https://www.npmjs.com/package/martin-loop) — the standalone CLI
|
|
169
|
+
- [MartinLoop for AI Agents](../../docs/for-agents.md)
|
|
132
170
|
- [GitHub](https://github.com/Keesan12/martin-loop) — source and issues
|
|
133
171
|
- [martinloop.com](https://martinloop.com)
|
|
134
172
|
|
package/dist/resources.js
CHANGED
|
@@ -37,6 +37,16 @@ export const MARTIN_STATIC_RESOURCE_URIS = {
|
|
|
37
37
|
modeStatus: "martin://agent/mode-status",
|
|
38
38
|
memorySummary: "martin://agent/memory-summary"
|
|
39
39
|
};
|
|
40
|
+
function formatRecordedCost(usd, provenance) {
|
|
41
|
+
if (provenance === "unavailable")
|
|
42
|
+
return "unavailable";
|
|
43
|
+
if (provenance === "actual")
|
|
44
|
+
return `$${usd.toFixed(2)} provider-settled actual`;
|
|
45
|
+
if (provenance === "calculated") {
|
|
46
|
+
return `$${usd.toFixed(2)} calculated from observed usage`;
|
|
47
|
+
}
|
|
48
|
+
return `$${usd.toFixed(2)} estimated`;
|
|
49
|
+
}
|
|
40
50
|
export const MARTIN_RESOURCE_TEMPLATES = [
|
|
41
51
|
{
|
|
42
52
|
name: "martin_run_record",
|
|
@@ -376,7 +386,7 @@ async function loadLatestRunForCompactResource(runsRoot) {
|
|
|
376
386
|
empty: true,
|
|
377
387
|
warnings: [
|
|
378
388
|
"No Martin run records were found yet.",
|
|
379
|
-
"Run `npx martin-loop demo`, then
|
|
389
|
+
"Run `npx martin-loop demo`, then use `--proof --verify <command>` for explicitly non-governed verification-only evidence."
|
|
380
390
|
]
|
|
381
391
|
};
|
|
382
392
|
}
|
|
@@ -404,6 +414,7 @@ async function buildLatestSummaryResource(runsRoot) {
|
|
|
404
414
|
budget: loop.budget,
|
|
405
415
|
cost: {
|
|
406
416
|
actualUsd: loop.cost.actualUsd,
|
|
417
|
+
provenance: loop.cost.provenance ?? "unavailable",
|
|
407
418
|
avoidedUsdEstimate: loop.cost.avoidedUsd ?? 0,
|
|
408
419
|
tokensIn: loop.cost.tokensIn,
|
|
409
420
|
tokensOut: loop.cost.tokensOut,
|
|
@@ -439,6 +450,7 @@ async function buildLatestBudgetStatusResource(runsRoot) {
|
|
|
439
450
|
budget: loop.budget,
|
|
440
451
|
cost: {
|
|
441
452
|
actualUsd: loop.cost.actualUsd,
|
|
453
|
+
provenance: loop.cost.provenance ?? "unavailable",
|
|
442
454
|
avoidedUsdEstimate: loop.cost.avoidedUsd ?? 0,
|
|
443
455
|
tokensIn: loop.cost.tokensIn,
|
|
444
456
|
tokensOut: loop.cost.tokensOut,
|
|
@@ -575,7 +587,7 @@ async function buildLatestProofCardResource(runsRoot) {
|
|
|
575
587
|
`Run: \`${loop.loopId}\``,
|
|
576
588
|
`Status: ${loop.status} / ${loop.lifecycleState}`,
|
|
577
589
|
`Attempts: ${loop.attempts.length}`,
|
|
578
|
-
`Spend:
|
|
590
|
+
`Spend: ${formatRecordedCost(loop.cost.actualUsd, loop.cost.provenance ?? "unavailable")}, $${(loop.cost.avoidedUsd ?? 0).toFixed(2)} avoided estimate`,
|
|
579
591
|
`Tokens: ${loop.cost.tokensIn} in / ${loop.cost.tokensOut} out`,
|
|
580
592
|
`Verifier: ${verifierStatus}`,
|
|
581
593
|
"",
|
|
@@ -599,7 +611,7 @@ function compactEmptyState(kind, runsRoot, warnings) {
|
|
|
599
611
|
status: "empty",
|
|
600
612
|
runsRoot,
|
|
601
613
|
summary: "No Martin run records are available yet.",
|
|
602
|
-
nextStep: "Run `npx martin-loop doctor`, create the demo workspace
|
|
614
|
+
nextStep: "Run `npx martin-loop doctor`, create the demo workspace, then use `--proof --verify <command>` only for non-governed verification evidence.",
|
|
603
615
|
warnings
|
|
604
616
|
};
|
|
605
617
|
}
|
package/dist/server.js
CHANGED
|
@@ -55,6 +55,15 @@ const stringArraySchema = {
|
|
|
55
55
|
type: "array",
|
|
56
56
|
items: { type: "string" }
|
|
57
57
|
};
|
|
58
|
+
function describeCostProvenance(provenance) {
|
|
59
|
+
if (provenance === "actual")
|
|
60
|
+
return "provider-settled actual";
|
|
61
|
+
if (provenance === "calculated")
|
|
62
|
+
return "calculated from observed usage";
|
|
63
|
+
if (provenance === "estimated")
|
|
64
|
+
return "estimated";
|
|
65
|
+
return "unavailable";
|
|
66
|
+
}
|
|
58
67
|
const loopPreviewSchema = {
|
|
59
68
|
type: "object",
|
|
60
69
|
additionalProperties: true,
|
|
@@ -68,6 +77,10 @@ const loopPreviewSchema = {
|
|
|
68
77
|
updatedAt: { type: "string" },
|
|
69
78
|
attempts: { type: "integer" },
|
|
70
79
|
costUsd: { type: "number" },
|
|
80
|
+
costProvenance: {
|
|
81
|
+
type: "string",
|
|
82
|
+
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
83
|
+
},
|
|
71
84
|
avoidedUsd: { type: "number" },
|
|
72
85
|
pressure: { type: "string" },
|
|
73
86
|
shouldStop: { type: "boolean" },
|
|
@@ -87,6 +100,7 @@ const loopPreviewSchema = {
|
|
|
87
100
|
"lifecycleState",
|
|
88
101
|
"attempts",
|
|
89
102
|
"costUsd",
|
|
103
|
+
"costProvenance",
|
|
90
104
|
"avoidedUsd",
|
|
91
105
|
"pressure",
|
|
92
106
|
"shouldStop",
|
|
@@ -113,9 +127,13 @@ const costSchema = {
|
|
|
113
127
|
actualUsd: { type: "number" },
|
|
114
128
|
avoidedUsd: { type: "number" },
|
|
115
129
|
tokensIn: { type: "integer" },
|
|
116
|
-
tokensOut: { type: "integer" }
|
|
130
|
+
tokensOut: { type: "integer" },
|
|
131
|
+
provenance: {
|
|
132
|
+
type: "string",
|
|
133
|
+
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
134
|
+
}
|
|
117
135
|
},
|
|
118
|
-
required: ["actualUsd", "avoidedUsd", "tokensIn", "tokensOut"]
|
|
136
|
+
required: ["actualUsd", "avoidedUsd", "tokensIn", "tokensOut", "provenance"]
|
|
119
137
|
};
|
|
120
138
|
const verificationSchema = {
|
|
121
139
|
type: "object",
|
|
@@ -248,6 +266,10 @@ const runOutputSchema = {
|
|
|
248
266
|
reason: { type: "string" },
|
|
249
267
|
attempts: { type: "integer" },
|
|
250
268
|
costUsd: { type: "number" },
|
|
269
|
+
costProvenance: {
|
|
270
|
+
type: "string",
|
|
271
|
+
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
272
|
+
},
|
|
251
273
|
verificationPassed: { type: "boolean" },
|
|
252
274
|
loopId: { type: "string" },
|
|
253
275
|
pressure: { type: "string" },
|
|
@@ -279,6 +301,7 @@ const runOutputSchema = {
|
|
|
279
301
|
"reason",
|
|
280
302
|
"attempts",
|
|
281
303
|
"costUsd",
|
|
304
|
+
"costProvenance",
|
|
282
305
|
"verificationPassed",
|
|
283
306
|
"loopId",
|
|
284
307
|
"pressure",
|
|
@@ -346,6 +369,10 @@ const statusOutputSchema = {
|
|
|
346
369
|
lifecycleState: { type: "string" },
|
|
347
370
|
attempts: { type: "integer" },
|
|
348
371
|
costUsd: { type: "number" },
|
|
372
|
+
costProvenance: {
|
|
373
|
+
type: "string",
|
|
374
|
+
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
375
|
+
},
|
|
349
376
|
avoidedUsd: { type: "number" },
|
|
350
377
|
pressure: { type: "string" },
|
|
351
378
|
shouldStop: { type: "boolean" },
|
|
@@ -369,6 +396,7 @@ const statusOutputSchema = {
|
|
|
369
396
|
"lifecycleState",
|
|
370
397
|
"attempts",
|
|
371
398
|
"costUsd",
|
|
399
|
+
"costProvenance",
|
|
372
400
|
"avoidedUsd",
|
|
373
401
|
"pressure",
|
|
374
402
|
"shouldStop",
|
|
@@ -1503,17 +1531,22 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1503
1531
|
});
|
|
1504
1532
|
}
|
|
1505
1533
|
const output = await runLoopTool(input);
|
|
1506
|
-
return createToolSuccessResult(output,
|
|
1534
|
+
return createToolSuccessResult(output, [
|
|
1535
|
+
`Run ${output.loopId} is ${output.status}/${output.lifecycleState}`,
|
|
1536
|
+
`after ${output.attempts} attempt(s); recorded cost ${output.costUsd.toFixed(2)} USD (${describeCostProvenance(output.costProvenance)}).`,
|
|
1537
|
+
`Execution mode: ${output.executionMode}; governance claim eligible:`,
|
|
1538
|
+
`${output.governanceClaimEligible ? "yes" : "no"}.`
|
|
1539
|
+
].join(" "));
|
|
1507
1540
|
}
|
|
1508
1541
|
if (name === "martin_inspect") {
|
|
1509
1542
|
const input = validateToolInput("martin_inspect", args);
|
|
1510
1543
|
const output = await inspectLoopTool(input);
|
|
1511
|
-
return createToolSuccessResult(output, `Inspected ${output.loopCount} run(s) from ${output.source}; total
|
|
1544
|
+
return createToolSuccessResult(output, `Inspected ${output.loopCount} run(s) from ${output.source}; total recorded cost ${output.portfolio.totalActualUsd.toFixed(2)} USD.`);
|
|
1512
1545
|
}
|
|
1513
1546
|
if (name === "martin_status") {
|
|
1514
1547
|
const input = validateToolInput("martin_status", args);
|
|
1515
1548
|
const output = await getStatusTool(input);
|
|
1516
|
-
return createToolSuccessResult(output, `Loop ${output.loopId} is ${output.status}/${output.lifecycleState}; pressure is ${output.pressure} with ${output.remainingBudgetUsd.toFixed(2)} USD remaining.`);
|
|
1549
|
+
return createToolSuccessResult(output, `Loop ${output.loopId} is ${output.status}/${output.lifecycleState}; recorded cost is ${output.costUsd.toFixed(2)} USD (${describeCostProvenance(output.costProvenance)}), and pressure is ${output.pressure} with ${output.remainingBudgetUsd.toFixed(2)} USD remaining.`);
|
|
1517
1550
|
}
|
|
1518
1551
|
if (name === "martin_doctor") {
|
|
1519
1552
|
const input = validateToolInput("martin_doctor", args);
|
|
@@ -1671,8 +1704,7 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1671
1704
|
compressed: route.compressed,
|
|
1672
1705
|
...(route.compressionSummary ? { compressionSummary: route.compressionSummary } : {}),
|
|
1673
1706
|
recommendedBudgetUsd,
|
|
1674
|
-
|
|
1675
|
-
estimatedSavingVsSonnetUsd: route.estimatedSavingVsSonnetUsd
|
|
1707
|
+
modelAuthority: "agent_or_provider_default"
|
|
1676
1708
|
};
|
|
1677
1709
|
await recordMcpWorkflowStep({
|
|
1678
1710
|
runsRoot,
|
|
@@ -1687,7 +1719,7 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1687
1719
|
runsRoot
|
|
1688
1720
|
}
|
|
1689
1721
|
}).catch(() => { });
|
|
1690
|
-
return createToolSuccessResult(output, `Estimate: ${route.selectedMode} route
|
|
1722
|
+
return createToolSuccessResult(output, `Estimate: ${route.selectedMode} route, ~$${route.expectedCostUsd.toFixed(2)} expected cost, ${route.expectedPreworkBurnPct}% pre-work burn. Model authority remains with the agent/provider. Recommended budget: $${recommendedBudgetUsd.toFixed(2)}.`);
|
|
1691
1723
|
}
|
|
1692
1724
|
return createToolErrorResult(toToolFailure(new Error(`Unknown tool: ${name}`)));
|
|
1693
1725
|
}
|
package/dist/tools/doctor.js
CHANGED
|
@@ -115,10 +115,11 @@ export async function martinDoctorTool(input) {
|
|
|
115
115
|
...(gemini.resolvedPath ? { resolvedPath: gemini.resolvedPath } : {})
|
|
116
116
|
},
|
|
117
117
|
openai: {
|
|
118
|
-
available: Boolean(process.env.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
available: Boolean(process.env.MARTIN_OPENAI_MODEL &&
|
|
119
|
+
(process.env.MARTIN_OPENAI_API_KEY || process.env.MARTIN_OPENAI_BASE_URL)),
|
|
120
|
+
detail: process.env.MARTIN_OPENAI_MODEL
|
|
121
|
+
? `OpenAI-compatible model configured: ${process.env.MARTIN_OPENAI_MODEL}.`
|
|
122
|
+
: "MODEL_CONFIGURATION_REQUIRED: set MARTIN_OPENAI_MODEL and the endpoint/auth required by your provider."
|
|
122
123
|
}
|
|
123
124
|
},
|
|
124
125
|
...(input.engine ? { requestedEngine: input.engine } : {}),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CostProvenance } from "../vendor/contracts/index.js";
|
|
1
2
|
import { type LoopPreview } from "./tool-support.js";
|
|
2
3
|
export interface GetStatusInput {
|
|
3
4
|
/** JSON-serialized LoopRecord. */
|
|
@@ -18,6 +19,7 @@ export interface GetStatusOutput {
|
|
|
18
19
|
lifecycleState: string;
|
|
19
20
|
attempts: number;
|
|
20
21
|
costUsd: number;
|
|
22
|
+
costProvenance: CostProvenance;
|
|
21
23
|
avoidedUsd: number;
|
|
22
24
|
pressure: string;
|
|
23
25
|
shouldStop: boolean;
|
package/dist/tools/get-status.js
CHANGED
|
@@ -31,6 +31,7 @@ export async function getStatusTool(input) {
|
|
|
31
31
|
lifecycleState: loop.lifecycleState,
|
|
32
32
|
attempts: loop.attempts.length,
|
|
33
33
|
costUsd: loop.cost.actualUsd,
|
|
34
|
+
costProvenance: loop.cost.provenance ?? "unavailable",
|
|
34
35
|
avoidedUsd: loop.cost.avoidedUsd ?? 0,
|
|
35
36
|
pressure: costState.pressure,
|
|
36
37
|
shouldStop: costState.shouldStop,
|
package/dist/tools/preflight.js
CHANGED
|
@@ -36,7 +36,7 @@ export async function martinPreflightTool(input) {
|
|
|
36
36
|
maxTokens: input.maxTokens
|
|
37
37
|
});
|
|
38
38
|
if (!executionMode.liveMode) {
|
|
39
|
-
warnings.push("
|
|
39
|
+
warnings.push("Verification-only mode is active; it cannot emit governed VERIFIED or prove live CLI readiness.");
|
|
40
40
|
}
|
|
41
41
|
else if (!engineAvailability.available) {
|
|
42
42
|
warnings.push(`Requested engine '${engine}' is not available on PATH.`);
|
package/dist/tools/run-loop.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type SpawnLike } from "../vendor/adapters/index.js";
|
|
1
2
|
import { type RunStore } from "../vendor/core/index.js";
|
|
2
|
-
import type { LoopBudget, ReceiptScope } from "../vendor/contracts/index.js";
|
|
3
|
+
import type { CostProvenance, ExecutionMode, LoopBudget, ReceiptScope } from "../vendor/contracts/index.js";
|
|
3
4
|
import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, type MartinEngine } from "./tool-support.js";
|
|
4
5
|
export interface RunLoopInput {
|
|
5
6
|
objective: string;
|
|
@@ -22,7 +23,10 @@ export interface RunLoopOutput {
|
|
|
22
23
|
reason: string;
|
|
23
24
|
attempts: number;
|
|
24
25
|
costUsd: number;
|
|
26
|
+
costProvenance: CostProvenance;
|
|
25
27
|
verificationPassed: boolean;
|
|
28
|
+
executionMode: ExecutionMode;
|
|
29
|
+
governanceClaimEligible: boolean;
|
|
26
30
|
loopId: string;
|
|
27
31
|
pressure: string;
|
|
28
32
|
shouldStop: boolean;
|
|
@@ -43,5 +47,6 @@ export interface RunLoopOutput {
|
|
|
43
47
|
artifacts: ReturnType<typeof buildArtifactSummary>;
|
|
44
48
|
};
|
|
45
49
|
}
|
|
50
|
+
export declare function __setProofModeVerifierSpawnImplForTests(spawnImpl?: SpawnLike): void;
|
|
46
51
|
export declare function __setRunStoreOverrideForTests(store?: RunStore): void;
|
|
47
52
|
export declare function runLoopTool(input: RunLoopInput): Promise<RunLoopOutput>;
|
package/dist/tools/run-loop.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter,
|
|
1
|
+
import { createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, createVerifierOnlyAdapter, probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
|
|
2
2
|
import { createFileRunStore, evaluateCostGovernor, resolveRunsRoot, runMartin } from "../vendor/core/index.js";
|
|
3
3
|
import { normalizeSafePathPatterns, resolveSafeRepoRoot } from "../server-validation.js";
|
|
4
4
|
import { MartinToolError } from "./tool-errors.js";
|
|
5
5
|
import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, buildRunRecordPaths, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
|
|
6
6
|
import { normalizeLoopBudget } from "./workflow-governance.js";
|
|
7
|
+
let proofModeVerifierSpawnImpl;
|
|
7
8
|
let runStoreOverrideForTests;
|
|
9
|
+
export function __setProofModeVerifierSpawnImplForTests(spawnImpl) {
|
|
10
|
+
proofModeVerifierSpawnImpl = spawnImpl;
|
|
11
|
+
}
|
|
8
12
|
export function __setRunStoreOverrideForTests(store) {
|
|
9
13
|
runStoreOverrideForTests = store;
|
|
10
14
|
}
|
|
@@ -15,6 +19,9 @@ export async function runLoopTool(input) {
|
|
|
15
19
|
const allowedPaths = normalizeSafePathPatterns(input.allowedPaths, "allowedPaths");
|
|
16
20
|
const deniedPaths = normalizeSafePathPatterns(input.deniedPaths, "deniedPaths");
|
|
17
21
|
const executionMode = resolveExecutionMode();
|
|
22
|
+
const evidenceExecutionMode = executionMode.liveMode
|
|
23
|
+
? "governed"
|
|
24
|
+
: "verification_only";
|
|
18
25
|
const workspaceRoot = resolveSafeRepoRoot();
|
|
19
26
|
const runsRoot = resolveRunsRoot(process.env);
|
|
20
27
|
const receiptScope = {
|
|
@@ -30,7 +37,7 @@ export async function runLoopTool(input) {
|
|
|
30
37
|
if (!engineAvailability.available) {
|
|
31
38
|
throw new MartinToolError("engine_unavailable", `Engine '${engine}' is not available on PATH.`, {
|
|
32
39
|
category: "environment",
|
|
33
|
-
suggestion: "Install the requested CLI
|
|
40
|
+
suggestion: "Install the requested CLI. MARTIN_LIVE=false is verification-only and cannot emit governed VERIFIED.",
|
|
34
41
|
retryable: false
|
|
35
42
|
});
|
|
36
43
|
}
|
|
@@ -52,17 +59,18 @@ export async function runLoopTool(input) {
|
|
|
52
59
|
if (!engineAvailability.available) {
|
|
53
60
|
throw new MartinToolError("engine_unavailable", `Engine '${engine}' is not available on PATH.`, {
|
|
54
61
|
category: "environment",
|
|
55
|
-
suggestion: "Install the requested CLI
|
|
62
|
+
suggestion: "Install the requested CLI. MARTIN_LIVE=false is verification-only and cannot emit governed VERIFIED.",
|
|
56
63
|
retryable: false
|
|
57
64
|
});
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
const adapter = !executionMode.liveMode
|
|
62
|
-
?
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
? createVerifierOnlyAdapter({
|
|
70
|
+
workingDirectory,
|
|
71
|
+
label: "Verification-only adapter (MARTIN_LIVE=false)",
|
|
72
|
+
...(input.verifyTimeoutMs !== undefined ? { verifyTimeoutMs: input.verifyTimeoutMs } : {}),
|
|
73
|
+
...(proofModeVerifierSpawnImpl ? { spawnImpl: proofModeVerifierSpawnImpl } : {})
|
|
66
74
|
})
|
|
67
75
|
: engine === "codex"
|
|
68
76
|
? createCodexCliAdapter({
|
|
@@ -110,6 +118,10 @@ export async function runLoopTool(input) {
|
|
|
110
118
|
...(deniedPaths ? { deniedPaths } : {})
|
|
111
119
|
},
|
|
112
120
|
budget,
|
|
121
|
+
metadata: {
|
|
122
|
+
executionMode: evidenceExecutionMode,
|
|
123
|
+
governanceClaimEligible: evidenceExecutionMode === "governed" ? "true" : "false"
|
|
124
|
+
},
|
|
113
125
|
adapter
|
|
114
126
|
});
|
|
115
127
|
const lastAttempt = result.loop.attempts.at(-1);
|
|
@@ -133,7 +145,10 @@ export async function runLoopTool(input) {
|
|
|
133
145
|
reason: result.decision.reason,
|
|
134
146
|
attempts: result.loop.attempts.length,
|
|
135
147
|
costUsd: result.loop.cost.actualUsd,
|
|
148
|
+
costProvenance: result.loop.cost.provenance ?? "unavailable",
|
|
136
149
|
verificationPassed,
|
|
150
|
+
executionMode: evidenceExecutionMode,
|
|
151
|
+
governanceClaimEligible: evidenceExecutionMode === "governed",
|
|
137
152
|
loopId: result.loop.loopId,
|
|
138
153
|
pressure: costState.pressure,
|
|
139
154
|
shouldStop: costState.shouldStop,
|
|
@@ -92,7 +92,7 @@ export function toToolFailure(error) {
|
|
|
92
92
|
code: "engine_unavailable",
|
|
93
93
|
category: "environment",
|
|
94
94
|
message,
|
|
95
|
-
suggestion: "Install the requested CLI
|
|
95
|
+
suggestion: "Install the requested CLI. MARTIN_LIVE=false is verification-only and cannot emit governed VERIFIED.",
|
|
96
96
|
retryable: false
|
|
97
97
|
};
|
|
98
98
|
}
|
|
@@ -4,13 +4,20 @@ function createJsonBlock(output) {
|
|
|
4
4
|
text: JSON.stringify(output, null, 2)
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
|
+
function resolveHumanArtifact(output, summary) {
|
|
8
|
+
const rendered = "rendered" in output ? output.rendered : undefined;
|
|
9
|
+
return typeof rendered === "string" && rendered.trim().length > 0 ? rendered : summary;
|
|
10
|
+
}
|
|
7
11
|
export function createToolSuccessResult(output, summary) {
|
|
8
12
|
return {
|
|
9
|
-
content: [
|
|
13
|
+
content: [
|
|
14
|
+
{ type: "text", text: resolveHumanArtifact(output, summary) },
|
|
15
|
+
createJsonBlock(output)
|
|
16
|
+
],
|
|
10
17
|
structuredContent: output,
|
|
11
18
|
_meta: {
|
|
12
19
|
"martinloop/summary": summary,
|
|
13
|
-
"martinloop/contentVersion": "2026-
|
|
20
|
+
"martinloop/contentVersion": "2026-08-18"
|
|
14
21
|
}
|
|
15
22
|
};
|
|
16
23
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LoopArtifact, LoopBudget, LoopCost, LoopEvent, LoopTask, ReceiptIntegritySummary, ReceiptScope, TerminationEnvelopeV1 } from "../vendor/contracts/index.js";
|
|
1
|
+
import type { CostProvenance, LoopArtifact, LoopBudget, LoopCost, LoopEvent, LoopTask, ReceiptIntegritySummary, ReceiptScope, TerminationEnvelopeV1 } from "../vendor/contracts/index.js";
|
|
2
2
|
import { type LedgerEvent, type LoopAttemptRecord, type LoopRunRecord } from "../vendor/core/index.js";
|
|
3
3
|
export declare const MARTIN_ENGINE_VALUES: readonly ["claude", "codex", "gemini", "openai"];
|
|
4
4
|
export type MartinEngine = (typeof MARTIN_ENGINE_VALUES)[number];
|
|
@@ -27,6 +27,7 @@ export interface LoopPreview {
|
|
|
27
27
|
updatedAt?: string;
|
|
28
28
|
attempts: number;
|
|
29
29
|
costUsd: number;
|
|
30
|
+
costProvenance: CostProvenance;
|
|
30
31
|
avoidedUsd: number;
|
|
31
32
|
pressure: string;
|
|
32
33
|
shouldStop: boolean;
|
|
@@ -128,7 +129,7 @@ export declare function inspectRunsRoot(runsRoot?: string): Promise<RunStoreInsp
|
|
|
128
129
|
export declare function buildRunRecordPaths(runsRoot: string, loopId: string): CanonicalRunPaths;
|
|
129
130
|
export declare function buildAttemptArtifactDirectory(runsRoot: string, loopId: string, attemptIndex: number): string;
|
|
130
131
|
export declare function buildAttemptArtifactsReference(runsRoot: string, loopId: string, attemptIndex: number): Promise<AttemptArtifactFiles>;
|
|
131
|
-
export declare function buildCostSnapshot(cost: Pick<LoopCost, "actualUsd" | "tokensIn" | "tokensOut"> & {
|
|
132
|
+
export declare function buildCostSnapshot(cost: Pick<LoopCost, "actualUsd" | "tokensIn" | "tokensOut" | "provenance"> & {
|
|
132
133
|
avoidedUsd?: number;
|
|
133
134
|
}): LoopCost;
|
|
134
135
|
export declare function buildBudgetSnapshot(budget: LoopBudget): LoopBudget;
|
|
@@ -15,7 +15,7 @@ export function resolveExecutionMode() {
|
|
|
15
15
|
mode: liveMode ? "live" : "proof",
|
|
16
16
|
detail: liveMode
|
|
17
17
|
? "Live CLI execution is enabled."
|
|
18
|
-
: "
|
|
18
|
+
: "Verification-only mode is active because MARTIN_LIVE=false; governed VERIFIED is unavailable."
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
export function detectCliAvailability(command) {
|
|
@@ -175,7 +175,7 @@ function suggestInstallCommand(command) {
|
|
|
175
175
|
export function getEngineAvailability(engine) {
|
|
176
176
|
return detectCliAvailability(engine);
|
|
177
177
|
}
|
|
178
|
-
export function createSkippedCliAvailability(command, detail = "
|
|
178
|
+
export function createSkippedCliAvailability(command, detail = "Verification-only mode skipped live CLI availability detection; governed VERIFIED is unavailable.") {
|
|
179
179
|
return {
|
|
180
180
|
command,
|
|
181
181
|
available: false,
|
|
@@ -208,6 +208,7 @@ export function buildLoopPreview(loop) {
|
|
|
208
208
|
...(loop.updatedAt ? { updatedAt: loop.updatedAt } : {}),
|
|
209
209
|
attempts: loop.attempts.length,
|
|
210
210
|
costUsd: loop.cost.actualUsd,
|
|
211
|
+
costProvenance: loop.cost.provenance ?? "unavailable",
|
|
211
212
|
avoidedUsd: loop.cost.avoidedUsd ?? 0,
|
|
212
213
|
pressure: costState.pressure,
|
|
213
214
|
shouldStop: costState.shouldStop,
|
|
@@ -411,7 +412,8 @@ export function buildCostSnapshot(cost) {
|
|
|
411
412
|
actualUsd: cost.actualUsd,
|
|
412
413
|
avoidedUsd: cost.avoidedUsd ?? 0,
|
|
413
414
|
tokensIn: cost.tokensIn,
|
|
414
|
-
tokensOut: cost.tokensOut
|
|
415
|
+
tokensOut: cost.tokensOut,
|
|
416
|
+
provenance: cost.provenance ?? "unavailable"
|
|
415
417
|
};
|
|
416
418
|
}
|
|
417
419
|
export function buildBudgetSnapshot(budget) {
|
|
@@ -96,7 +96,7 @@ export interface GeminiCliAdapterOptions {
|
|
|
96
96
|
timeoutMs?: number;
|
|
97
97
|
verifyTimeoutMs?: number;
|
|
98
98
|
label?: string;
|
|
99
|
-
/**
|
|
99
|
+
/** Explicit model override passed via --model. Omitted to preserve Gemini Auto. */
|
|
100
100
|
model?: string;
|
|
101
101
|
/** Approval mode for headless Gemini runs. Defaults to yolo for autonomous execution. */
|
|
102
102
|
approvalMode?: "default" | "auto_edit" | "yolo" | "plan";
|
|
@@ -135,7 +135,7 @@ export declare function createClaudeCliAdapter(options?: ClaudeCliAdapterOptions
|
|
|
135
135
|
*/
|
|
136
136
|
export declare function createCodexCliAdapter(options?: CodexCliAdapterOptions): MartinAdapter;
|
|
137
137
|
/**
|
|
138
|
-
* Spawns `gemini --model <model> --prompt "" --approval-mode <mode> --output-format json [...]`.
|
|
138
|
+
* Spawns `gemini [--model <explicit-model>] --prompt "" --approval-mode <mode> --output-format json [...]`.
|
|
139
139
|
*
|
|
140
140
|
* The prompt is delivered via stdin while forcing headless mode with `--prompt ""`,
|
|
141
141
|
* which keeps large MartinLoop prompts off the command line on Windows.
|