@martinloop/mcp 0.3.9 → 0.5.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/README.md +82 -141
- package/dist/discovery-metadata.d.ts +1 -1
- package/dist/discovery-metadata.js +1 -1
- package/dist/resources.d.ts +1 -2
- package/dist/resources.js +62 -98
- package/dist/server-validation.d.ts +1 -1
- package/dist/server-validation.js +16 -11
- package/dist/server.d.ts +23 -0
- package/dist/server.js +192 -15
- 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.d.ts +2 -0
- package/dist/tools/preflight.js +43 -3
- package/dist/tools/run-dossier.d.ts +3 -1
- package/dist/tools/run-dossier.js +40 -2
- package/dist/tools/run-loop.d.ts +5 -3
- package/dist/tools/run-loop.js +15 -9
- package/dist/tools/tool-errors.js +1 -1
- package/dist/tools/tool-response.js +9 -2
- package/dist/tools/tool-support.d.ts +4 -2
- package/dist/tools/tool-support.js +6 -15
- package/dist/vendor/adapters/claude-cli.d.ts +2 -2
- package/dist/vendor/adapters/claude-cli.js +97 -69
- package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
- package/dist/vendor/adapters/cli-bridge.js +54 -13
- package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
- package/dist/vendor/adapters/codex-launcher.js +118 -19
- package/dist/vendor/adapters/index.d.ts +2 -4
- package/dist/vendor/adapters/index.js +2 -4
- package/dist/vendor/adapters/openai-compatible.d.ts +2 -3
- package/dist/vendor/adapters/openai-compatible.js +80 -15
- package/dist/vendor/adapters/verifier-only.js +9 -4
- package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
- package/dist/vendor/contracts/context-handoff.d.ts +100 -0
- package/dist/vendor/contracts/context-handoff.js +11 -0
- package/dist/vendor/contracts/context-manifest.d.ts +195 -0
- package/dist/vendor/contracts/context-manifest.js +17 -0
- package/dist/vendor/contracts/context-shadow.d.ts +75 -0
- package/dist/vendor/contracts/context-shadow.js +8 -0
- package/dist/vendor/contracts/delivery.d.ts +47 -0
- package/dist/vendor/contracts/delivery.js +18 -0
- package/dist/vendor/contracts/exits.d.ts +126 -0
- package/dist/vendor/contracts/exits.js +32 -0
- package/dist/vendor/contracts/index.d.ts +55 -6
- package/dist/vendor/contracts/index.js +24 -0
- package/dist/vendor/contracts/mission.d.ts +102 -0
- package/dist/vendor/contracts/mission.js +64 -0
- package/dist/vendor/contracts/operator.d.ts +3 -1
- package/dist/vendor/contracts/operator.js +2 -1
- package/dist/vendor/contracts/verified-handoff.d.ts +106 -0
- package/dist/vendor/contracts/verified-handoff.js +40 -0
- package/dist/vendor/core/context-chain-gate.d.ts +89 -0
- package/dist/vendor/core/context-chain-gate.js +173 -0
- package/dist/vendor/core/context-compiler.d.ts +53 -0
- package/dist/vendor/core/context-compiler.js +261 -0
- package/dist/vendor/core/context-handoff.d.ts +42 -0
- package/dist/vendor/core/context-handoff.js +167 -0
- package/dist/vendor/core/context-shadow.d.ts +39 -0
- package/dist/vendor/core/context-shadow.js +200 -0
- package/dist/vendor/core/delivery/index.d.ts +6 -0
- package/dist/vendor/core/delivery/index.js +4 -0
- package/dist/vendor/core/delivery/message-client.d.ts +18 -0
- package/dist/vendor/core/delivery/message-client.js +38 -0
- package/dist/vendor/core/delivery/message-ledger.d.ts +35 -0
- package/dist/vendor/core/delivery/message-ledger.js +97 -0
- package/dist/vendor/core/delivery/message-schema.d.ts +16 -0
- package/dist/vendor/core/delivery/message-schema.js +111 -0
- package/dist/vendor/core/delivery/update-check.d.ts +19 -0
- package/dist/vendor/core/delivery/update-check.js +107 -0
- package/dist/vendor/core/exit-signal.d.ts +88 -0
- package/dist/vendor/core/exit-signal.js +339 -0
- package/dist/vendor/core/exits.d.ts +41 -0
- package/dist/vendor/core/exits.js +258 -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 +47 -6
- package/dist/vendor/core/index.js +1102 -670
- package/dist/vendor/core/mission/aggregate.d.ts +27 -0
- package/dist/vendor/core/mission/aggregate.js +42 -0
- package/dist/vendor/core/mission/index.d.ts +2 -0
- package/dist/vendor/core/mission/index.js +1 -0
- package/dist/vendor/core/persistence/compiler.d.ts +8 -0
- package/dist/vendor/core/persistence/compiler.js +156 -0
- package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
- package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
- package/dist/vendor/core/persistence/index.d.ts +4 -0
- package/dist/vendor/core/persistence/index.js +2 -0
- package/dist/vendor/core/persistence/ledger.d.ts +1 -1
- package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
- package/dist/vendor/core/persistence/mission-store.js +281 -0
- package/dist/vendor/core/persistence/runs-reader.d.ts +2 -0
- package/dist/vendor/core/policy.d.ts +2 -0
- package/dist/vendor/core/policy.js +11 -8
- 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/rollback.js +15 -1
- package/dist/vendor/core/routing.d.ts +0 -42
- package/dist/vendor/core/routing.js +4 -154
- package/dist/vendor/core/savings.d.ts +13 -0
- package/dist/vendor/core/savings.js +22 -0
- package/dist/vendor/core/termination-store.d.ts +15 -0
- package/dist/vendor/core/termination-store.js +44 -0
- package/dist/vendor/core/verified-handoff.d.ts +72 -0
- package/dist/vendor/core/verified-handoff.js +238 -0
- package/package.json +4 -10
- package/server.json +3 -3
- package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
- package/dist/vendor/adapters/stub-agent-cli.js +0 -40
- package/dist/vendor/adapters/stub-direct-provider.d.ts +0 -8
- package/dist/vendor/adapters/stub-direct-provider.js +0 -12
package/dist/server.js
CHANGED
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
* Manual start:
|
|
17
17
|
* node dist/server.js
|
|
18
18
|
*/
|
|
19
|
+
import { createServer as createHttpServer } from "node:http";
|
|
19
20
|
import { fileURLToPath } from "node:url";
|
|
20
21
|
import { realpathSync } from "node:fs";
|
|
21
22
|
import path from "node:path";
|
|
22
23
|
import { resolveRunsRoot } from "./vendor/core/index.js";
|
|
23
24
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
24
25
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
25
27
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
26
28
|
import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
|
|
27
29
|
import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
|
|
@@ -53,6 +55,15 @@ const stringArraySchema = {
|
|
|
53
55
|
type: "array",
|
|
54
56
|
items: { type: "string" }
|
|
55
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
|
+
}
|
|
56
67
|
const loopPreviewSchema = {
|
|
57
68
|
type: "object",
|
|
58
69
|
additionalProperties: true,
|
|
@@ -66,6 +77,10 @@ const loopPreviewSchema = {
|
|
|
66
77
|
updatedAt: { type: "string" },
|
|
67
78
|
attempts: { type: "integer" },
|
|
68
79
|
costUsd: { type: "number" },
|
|
80
|
+
costProvenance: {
|
|
81
|
+
type: "string",
|
|
82
|
+
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
83
|
+
},
|
|
69
84
|
avoidedUsd: { type: "number" },
|
|
70
85
|
pressure: { type: "string" },
|
|
71
86
|
shouldStop: { type: "boolean" },
|
|
@@ -85,6 +100,7 @@ const loopPreviewSchema = {
|
|
|
85
100
|
"lifecycleState",
|
|
86
101
|
"attempts",
|
|
87
102
|
"costUsd",
|
|
103
|
+
"costProvenance",
|
|
88
104
|
"avoidedUsd",
|
|
89
105
|
"pressure",
|
|
90
106
|
"shouldStop",
|
|
@@ -111,9 +127,13 @@ const costSchema = {
|
|
|
111
127
|
actualUsd: { type: "number" },
|
|
112
128
|
avoidedUsd: { type: "number" },
|
|
113
129
|
tokensIn: { type: "integer" },
|
|
114
|
-
tokensOut: { type: "integer" }
|
|
130
|
+
tokensOut: { type: "integer" },
|
|
131
|
+
provenance: {
|
|
132
|
+
type: "string",
|
|
133
|
+
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
134
|
+
}
|
|
115
135
|
},
|
|
116
|
-
required: ["actualUsd", "avoidedUsd", "tokensIn", "tokensOut"]
|
|
136
|
+
required: ["actualUsd", "avoidedUsd", "tokensIn", "tokensOut", "provenance"]
|
|
117
137
|
};
|
|
118
138
|
const verificationSchema = {
|
|
119
139
|
type: "object",
|
|
@@ -246,6 +266,10 @@ const runOutputSchema = {
|
|
|
246
266
|
reason: { type: "string" },
|
|
247
267
|
attempts: { type: "integer" },
|
|
248
268
|
costUsd: { type: "number" },
|
|
269
|
+
costProvenance: {
|
|
270
|
+
type: "string",
|
|
271
|
+
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
272
|
+
},
|
|
249
273
|
verificationPassed: { type: "boolean" },
|
|
250
274
|
loopId: { type: "string" },
|
|
251
275
|
pressure: { type: "string" },
|
|
@@ -277,6 +301,7 @@ const runOutputSchema = {
|
|
|
277
301
|
"reason",
|
|
278
302
|
"attempts",
|
|
279
303
|
"costUsd",
|
|
304
|
+
"costProvenance",
|
|
280
305
|
"verificationPassed",
|
|
281
306
|
"loopId",
|
|
282
307
|
"pressure",
|
|
@@ -344,6 +369,10 @@ const statusOutputSchema = {
|
|
|
344
369
|
lifecycleState: { type: "string" },
|
|
345
370
|
attempts: { type: "integer" },
|
|
346
371
|
costUsd: { type: "number" },
|
|
372
|
+
costProvenance: {
|
|
373
|
+
type: "string",
|
|
374
|
+
enum: ["actual", "calculated", "estimated", "unavailable"]
|
|
375
|
+
},
|
|
347
376
|
avoidedUsd: { type: "number" },
|
|
348
377
|
pressure: { type: "string" },
|
|
349
378
|
shouldStop: { type: "boolean" },
|
|
@@ -367,6 +396,7 @@ const statusOutputSchema = {
|
|
|
367
396
|
"lifecycleState",
|
|
368
397
|
"attempts",
|
|
369
398
|
"costUsd",
|
|
399
|
+
"costProvenance",
|
|
370
400
|
"avoidedUsd",
|
|
371
401
|
"pressure",
|
|
372
402
|
"shouldStop",
|
|
@@ -1401,6 +1431,10 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1401
1431
|
type: "array",
|
|
1402
1432
|
items: { type: "string" },
|
|
1403
1433
|
description: "Optional file paths to scope the estimate."
|
|
1434
|
+
},
|
|
1435
|
+
workingDirectory: {
|
|
1436
|
+
type: "string",
|
|
1437
|
+
description: "Optional workspace path for recording the estimate receipt against the same task root you plan to run."
|
|
1404
1438
|
}
|
|
1405
1439
|
},
|
|
1406
1440
|
required: ["objective"]
|
|
@@ -1497,17 +1531,22 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1497
1531
|
});
|
|
1498
1532
|
}
|
|
1499
1533
|
const output = await runLoopTool(input);
|
|
1500
|
-
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(" "));
|
|
1501
1540
|
}
|
|
1502
1541
|
if (name === "martin_inspect") {
|
|
1503
1542
|
const input = validateToolInput("martin_inspect", args);
|
|
1504
1543
|
const output = await inspectLoopTool(input);
|
|
1505
|
-
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.`);
|
|
1506
1545
|
}
|
|
1507
1546
|
if (name === "martin_status") {
|
|
1508
1547
|
const input = validateToolInput("martin_status", args);
|
|
1509
1548
|
const output = await getStatusTool(input);
|
|
1510
|
-
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.`);
|
|
1511
1550
|
}
|
|
1512
1551
|
if (name === "martin_doctor") {
|
|
1513
1552
|
const input = validateToolInput("martin_doctor", args);
|
|
@@ -1518,7 +1557,7 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1518
1557
|
workingDirectory: output.environment.workingDirectory,
|
|
1519
1558
|
engine: input.engine,
|
|
1520
1559
|
receiptScope: output.receiptScope
|
|
1521
|
-
});
|
|
1560
|
+
}).catch(() => { });
|
|
1522
1561
|
return createToolSuccessResult(output, output.summary);
|
|
1523
1562
|
}
|
|
1524
1563
|
if (name === "martin_plan") {
|
|
@@ -1535,7 +1574,7 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1535
1574
|
repoRoot: output.workingDirectory,
|
|
1536
1575
|
runsRoot: resolveRunsRoot(process.env)
|
|
1537
1576
|
}
|
|
1538
|
-
});
|
|
1577
|
+
}).catch(() => { });
|
|
1539
1578
|
return createToolSuccessResult(output, `Plan ready for ${output.objective} with ${output.risk.level} risk and ${output.approvalRecommendation.replace(/_/gu, " ")} approval.`);
|
|
1540
1579
|
}
|
|
1541
1580
|
if (name === "martin_preflight") {
|
|
@@ -1553,7 +1592,7 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1553
1592
|
allowedPaths: output.normalized.allowedPaths,
|
|
1554
1593
|
deniedPaths: output.normalized.deniedPaths,
|
|
1555
1594
|
budget: output.normalized.budget
|
|
1556
|
-
});
|
|
1595
|
+
}).catch(() => { });
|
|
1557
1596
|
}
|
|
1558
1597
|
return createToolSuccessResult(output, output.summary);
|
|
1559
1598
|
}
|
|
@@ -1640,8 +1679,8 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1640
1679
|
const engine = input.engine ?? "claude";
|
|
1641
1680
|
const budgetUsd = input.budgetUsd ?? 5;
|
|
1642
1681
|
const fileScope = input.fileScope ?? [];
|
|
1643
|
-
const workingDirectory = resolveSafeRepoRoot();
|
|
1644
1682
|
const runsRoot = resolveRunsRoot(process.env);
|
|
1683
|
+
const workingDirectory = input.workingDirectory ?? resolveSafeRepoRoot();
|
|
1645
1684
|
const route = classifyRoute({
|
|
1646
1685
|
objective,
|
|
1647
1686
|
verificationPlan: [],
|
|
@@ -1665,17 +1704,22 @@ export function createMartinMcpServer(serverInfo) {
|
|
|
1665
1704
|
compressed: route.compressed,
|
|
1666
1705
|
...(route.compressionSummary ? { compressionSummary: route.compressionSummary } : {}),
|
|
1667
1706
|
recommendedBudgetUsd,
|
|
1668
|
-
|
|
1669
|
-
estimatedSavingVsSonnetUsd: route.estimatedSavingVsSonnetUsd
|
|
1707
|
+
modelAuthority: "agent_or_provider_default"
|
|
1670
1708
|
};
|
|
1671
1709
|
await recordMcpWorkflowStep({
|
|
1672
1710
|
runsRoot,
|
|
1673
1711
|
step: "estimate",
|
|
1674
1712
|
workingDirectory,
|
|
1675
1713
|
objective,
|
|
1676
|
-
engine
|
|
1677
|
-
|
|
1678
|
-
|
|
1714
|
+
engine,
|
|
1715
|
+
receiptScope: {
|
|
1716
|
+
invocationRoot: resolveSafeRepoRoot(),
|
|
1717
|
+
workingDirectory,
|
|
1718
|
+
repoRoot: workingDirectory,
|
|
1719
|
+
runsRoot
|
|
1720
|
+
}
|
|
1721
|
+
}).catch(() => { });
|
|
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)}.`);
|
|
1679
1723
|
}
|
|
1680
1724
|
return createToolErrorResult(toToolFailure(new Error(`Unknown tool: ${name}`)));
|
|
1681
1725
|
}
|
|
@@ -1698,6 +1742,114 @@ export async function connectMartinMcpStdioServer() {
|
|
|
1698
1742
|
await server.connect(transport);
|
|
1699
1743
|
return server;
|
|
1700
1744
|
}
|
|
1745
|
+
export function parseMartinMcpServerArgs(argv) {
|
|
1746
|
+
let transport = "stdio";
|
|
1747
|
+
let host = "127.0.0.1";
|
|
1748
|
+
let port = 3033;
|
|
1749
|
+
let path = "/mcp";
|
|
1750
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1751
|
+
const token = argv[index];
|
|
1752
|
+
if (token === "--http") {
|
|
1753
|
+
transport = "http";
|
|
1754
|
+
continue;
|
|
1755
|
+
}
|
|
1756
|
+
if (token === "--port") {
|
|
1757
|
+
const next = argv[index + 1];
|
|
1758
|
+
const parsed = Number(next);
|
|
1759
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
|
|
1760
|
+
throw new Error(`Invalid --port value '${next ?? ""}'.`);
|
|
1761
|
+
}
|
|
1762
|
+
port = parsed;
|
|
1763
|
+
index += 1;
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
if (token === "--host") {
|
|
1767
|
+
const next = argv[index + 1];
|
|
1768
|
+
if (!next) {
|
|
1769
|
+
throw new Error("Missing value for --host.");
|
|
1770
|
+
}
|
|
1771
|
+
host = next;
|
|
1772
|
+
index += 1;
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
if (token === "--path") {
|
|
1776
|
+
const next = argv[index + 1];
|
|
1777
|
+
if (!next || !next.startsWith("/")) {
|
|
1778
|
+
throw new Error("MCP HTTP path must start with '/'.");
|
|
1779
|
+
}
|
|
1780
|
+
path = next;
|
|
1781
|
+
index += 1;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return transport === "http"
|
|
1785
|
+
? { transport, host, port, path }
|
|
1786
|
+
: { transport };
|
|
1787
|
+
}
|
|
1788
|
+
export async function connectMartinMcpHttpServer(options = {}) {
|
|
1789
|
+
const host = options.host ?? "127.0.0.1";
|
|
1790
|
+
const port = options.port ?? 3033;
|
|
1791
|
+
const path = options.path ?? "/mcp";
|
|
1792
|
+
const mcpServer = createMartinMcpServer();
|
|
1793
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1794
|
+
sessionIdGenerator: undefined
|
|
1795
|
+
});
|
|
1796
|
+
await mcpServer.connect(transport);
|
|
1797
|
+
const httpServer = createHttpServer(async (req, res) => {
|
|
1798
|
+
try {
|
|
1799
|
+
const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? `${host}:${port}`}`);
|
|
1800
|
+
if (requestUrl.pathname !== path) {
|
|
1801
|
+
res.statusCode = 404;
|
|
1802
|
+
res.setHeader("content-type", "application/json");
|
|
1803
|
+
res.end(JSON.stringify({ error: "not_found", path: requestUrl.pathname }));
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
if (req.method === "POST") {
|
|
1807
|
+
const parsedBody = await readJsonBody(req, res);
|
|
1808
|
+
if (res.writableEnded) {
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
await transport.handleRequest(req, res, parsedBody);
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (req.method === "GET" || req.method === "DELETE") {
|
|
1815
|
+
await transport.handleRequest(req, res);
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
res.statusCode = 405;
|
|
1819
|
+
res.setHeader("allow", "GET, POST, DELETE");
|
|
1820
|
+
res.end();
|
|
1821
|
+
}
|
|
1822
|
+
catch (error) {
|
|
1823
|
+
if (!res.headersSent) {
|
|
1824
|
+
res.statusCode = 500;
|
|
1825
|
+
res.setHeader("content-type", "application/json");
|
|
1826
|
+
res.end(JSON.stringify({ error: "internal_error", message: error.message }));
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
await new Promise((resolve, reject) => {
|
|
1831
|
+
httpServer.once("error", reject);
|
|
1832
|
+
httpServer.listen(port, host, () => {
|
|
1833
|
+
httpServer.off("error", reject);
|
|
1834
|
+
resolve();
|
|
1835
|
+
});
|
|
1836
|
+
});
|
|
1837
|
+
const address = httpServer.address();
|
|
1838
|
+
const resolvedPort = typeof address === "object" && address ? address.port : port;
|
|
1839
|
+
return {
|
|
1840
|
+
server: httpServer,
|
|
1841
|
+
host,
|
|
1842
|
+
port: resolvedPort,
|
|
1843
|
+
path,
|
|
1844
|
+
endpoint: `http://${host}:${resolvedPort}${path}`,
|
|
1845
|
+
close: async () => {
|
|
1846
|
+
await transport.close();
|
|
1847
|
+
await new Promise((resolve) => {
|
|
1848
|
+
httpServer.close(() => resolve());
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1701
1853
|
export function isDirectExecutionEntry(entryPath, moduleUrl = import.meta.url) {
|
|
1702
1854
|
if (typeof entryPath !== "string" || entryPath.length === 0) {
|
|
1703
1855
|
return false;
|
|
@@ -1718,5 +1870,30 @@ function realPathOrResolved(filePath) {
|
|
|
1718
1870
|
}
|
|
1719
1871
|
}
|
|
1720
1872
|
if (isDirectExecution()) {
|
|
1721
|
-
|
|
1873
|
+
const startup = parseMartinMcpServerArgs(process.argv.slice(2));
|
|
1874
|
+
if (startup.transport === "http") {
|
|
1875
|
+
await connectMartinMcpHttpServer(startup);
|
|
1876
|
+
}
|
|
1877
|
+
else {
|
|
1878
|
+
await connectMartinMcpStdioServer();
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
async function readJsonBody(req, res) {
|
|
1882
|
+
const chunks = [];
|
|
1883
|
+
for await (const chunk of req) {
|
|
1884
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1885
|
+
}
|
|
1886
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
1887
|
+
if (!raw) {
|
|
1888
|
+
return undefined;
|
|
1889
|
+
}
|
|
1890
|
+
try {
|
|
1891
|
+
return JSON.parse(raw);
|
|
1892
|
+
}
|
|
1893
|
+
catch {
|
|
1894
|
+
res.statusCode = 400;
|
|
1895
|
+
res.setHeader("content-type", "application/json");
|
|
1896
|
+
res.end(JSON.stringify({ error: "invalid_json" }));
|
|
1897
|
+
return undefined;
|
|
1898
|
+
}
|
|
1722
1899
|
}
|
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,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CodexHostPlatform } from "../vendor/adapters/index.js";
|
|
2
|
+
import type { UpdateAvailableField } from "../vendor/contracts/index.js";
|
|
2
3
|
import { type MartinEngine } from "./tool-support.js";
|
|
3
4
|
import { buildPolicyPackDefinition, type MartinPlanProposal, type MartinPolicyPack, type MartinRiskAssessment, type MartinRunContract } from "./workflow-governance.js";
|
|
4
5
|
export interface MartinPreflightInput {
|
|
@@ -108,5 +109,6 @@ export interface MartinPreflightOutput {
|
|
|
108
109
|
risk: MartinRiskAssessment;
|
|
109
110
|
runContract: MartinRunContract;
|
|
110
111
|
plan: MartinPlanProposal;
|
|
112
|
+
updateAvailable?: UpdateAvailableField;
|
|
111
113
|
}
|
|
112
114
|
export declare function martinPreflightTool(input: MartinPreflightInput): Promise<MartinPreflightOutput>;
|
package/dist/tools/preflight.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
|
|
2
|
-
import { resolveRunsRoot } from "../vendor/core/index.js";
|
|
2
|
+
import { fetchSelectedMessage, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, recordShown, resolveDefaultLedgerPath, resolveRunsRoot, saveDeliveryRecord, } from "../vendor/core/index.js";
|
|
3
|
+
import { MARTIN_MCP_PACKAGE_VERSION } from "../package-version.js";
|
|
3
4
|
import { resolveSafeRepoRoot } from "../server-validation.js";
|
|
4
5
|
import { createSkippedCliAvailability, formatUsd, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
|
|
5
6
|
import { buildPlanProposal, normalizeLoopBudget, buildRunContract, buildPolicyPackDefinition, inspectRepoSignals } from "./workflow-governance.js";
|
|
6
7
|
export async function martinPreflightTool(input) {
|
|
8
|
+
// Fire delivery fetch early so it races with the rest of preflight work.
|
|
9
|
+
const mcpVersion = getMcpInstalledVersion() ?? MARTIN_MCP_PACKAGE_VERSION;
|
|
10
|
+
const deliveryFetchPromise = fetchSelectedMessage({ clientVersion: mcpVersion, clientKind: "mcp", trigger: "version_check" }, { timeoutMs: 3_000 }).catch(() => null);
|
|
7
11
|
const executionMode = resolveExecutionMode();
|
|
8
12
|
const workspaceRoot = resolveSafeRepoRoot();
|
|
9
13
|
const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
|
|
@@ -32,7 +36,7 @@ export async function martinPreflightTool(input) {
|
|
|
32
36
|
maxTokens: input.maxTokens
|
|
33
37
|
});
|
|
34
38
|
if (!executionMode.liveMode) {
|
|
35
|
-
warnings.push("
|
|
39
|
+
warnings.push("Verification-only mode is active; it cannot emit governed VERIFIED or prove live CLI readiness.");
|
|
36
40
|
}
|
|
37
41
|
else if (!engineAvailability.available) {
|
|
38
42
|
warnings.push(`Requested engine '${engine}' is not available on PATH.`);
|
|
@@ -140,6 +144,42 @@ export async function martinPreflightTool(input) {
|
|
|
140
144
|
policy,
|
|
141
145
|
risk: runContract.risk,
|
|
142
146
|
runContract,
|
|
143
|
-
plan
|
|
147
|
+
plan,
|
|
148
|
+
...await resolvePreflightUpdateAvailable(deliveryFetchPromise, mcpVersion)
|
|
144
149
|
};
|
|
145
150
|
}
|
|
151
|
+
async function resolvePreflightUpdateAvailable(fetchPromise, currentVersion) {
|
|
152
|
+
try {
|
|
153
|
+
const message = await fetchPromise;
|
|
154
|
+
if (!message)
|
|
155
|
+
return {};
|
|
156
|
+
if (message.action.type !== "upgrade_mcp")
|
|
157
|
+
return {};
|
|
158
|
+
const targetVersion = message.action.targetVersion;
|
|
159
|
+
if (!targetVersion)
|
|
160
|
+
return {};
|
|
161
|
+
if (!isNewerVersion(currentVersion, targetVersion))
|
|
162
|
+
return {};
|
|
163
|
+
const ledgerPath = resolveDefaultLedgerPath();
|
|
164
|
+
const record = loadDeliveryRecord(ledgerPath);
|
|
165
|
+
const nowMs = Date.now();
|
|
166
|
+
if (!isCooldownExpired(record, nowMs))
|
|
167
|
+
return {};
|
|
168
|
+
if (isDismissed(record, message.id))
|
|
169
|
+
return {};
|
|
170
|
+
try {
|
|
171
|
+
saveDeliveryRecord(ledgerPath, recordShown(record, message, nowMs));
|
|
172
|
+
}
|
|
173
|
+
catch { /* ledger write failure must not surface */ }
|
|
174
|
+
return {
|
|
175
|
+
updateAvailable: {
|
|
176
|
+
targetVersion,
|
|
177
|
+
kind: "mcp",
|
|
178
|
+
message: message.body
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -2,7 +2,7 @@ import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildEven
|
|
|
2
2
|
import { readRunControlState } from "./run-controls.js";
|
|
3
3
|
import { martinEvalTool } from "./eval.js";
|
|
4
4
|
import { assessRunRisk } from "./workflow-governance.js";
|
|
5
|
-
import type { ReceiptIntegritySummary, ReceiptScope } from "../vendor/contracts/index.js";
|
|
5
|
+
import type { ReceiptIntegritySummary, ReceiptScope, TerminationEnvelopeV1, VerifiedHandoffV1 } from "../vendor/contracts/index.js";
|
|
6
6
|
export interface MartinRunDossierInput {
|
|
7
7
|
file?: string;
|
|
8
8
|
loopId?: string;
|
|
@@ -45,6 +45,8 @@ export interface MartinRunDossierOutput {
|
|
|
45
45
|
};
|
|
46
46
|
evaluation: Awaited<ReturnType<typeof martinEvalTool>>;
|
|
47
47
|
control: Awaited<ReturnType<typeof readRunControlState>>;
|
|
48
|
+
terminationEnvelope?: TerminationEnvelopeV1;
|
|
49
|
+
verifiedHandoff: VerifiedHandoffV1;
|
|
48
50
|
format: "json" | "md" | "github-pr";
|
|
49
51
|
rendered?: string;
|
|
50
52
|
inspection: {
|
|
@@ -4,6 +4,7 @@ import { loadDetailedLoopRecord, readAttemptArtifactFiles, readLedgerEvents } fr
|
|
|
4
4
|
import { readRunControlState } from "./run-controls.js";
|
|
5
5
|
import { martinEvalTool } from "./eval.js";
|
|
6
6
|
import { assessRunRisk, inspectRepoSignals } from "./workflow-governance.js";
|
|
7
|
+
import { buildVerifiedHandoff } from "../vendor/core/index.js";
|
|
7
8
|
export async function martinRunDossierTool(input) {
|
|
8
9
|
const detail = await loadDetailedLoopRecord(input);
|
|
9
10
|
const ledgerEvents = await readLedgerEvents(detail);
|
|
@@ -54,6 +55,7 @@ export async function martinRunDossierTool(input) {
|
|
|
54
55
|
cost: buildCostSnapshot(detail.loop.cost),
|
|
55
56
|
receiptIntegrity: resolveReceiptIntegrity(detail.loop),
|
|
56
57
|
...(detail.loop.receiptScope ? { receiptScope: detail.loop.receiptScope } : {}),
|
|
58
|
+
...(detail.loop.terminationEnvelope ? { terminationEnvelope: detail.loop.terminationEnvelope } : {}),
|
|
57
59
|
attempts,
|
|
58
60
|
verification,
|
|
59
61
|
artifacts: buildArtifactSummary(detail.loop),
|
|
@@ -72,7 +74,37 @@ export async function martinRunDossierTool(input) {
|
|
|
72
74
|
...(detail.canonicalLoopRecordPath ? { canonicalLoopRecordPath: detail.canonicalLoopRecordPath } : {}),
|
|
73
75
|
...(detail.ledgerPath ? { ledgerPath: detail.ledgerPath } : {})
|
|
74
76
|
},
|
|
75
|
-
warnings: [...detail.warnings, ...verification.warnings]
|
|
77
|
+
warnings: [...detail.warnings, ...verification.warnings],
|
|
78
|
+
verifiedHandoff: buildVerifiedHandoff({
|
|
79
|
+
loop: detail.loop,
|
|
80
|
+
receiptIntegrity: resolveReceiptIntegrity(detail.loop),
|
|
81
|
+
verification: {
|
|
82
|
+
status: verification.status,
|
|
83
|
+
summary: verification.summary ?? "No verification summary recorded.",
|
|
84
|
+
steps: [],
|
|
85
|
+
warnings: verification.warnings,
|
|
86
|
+
},
|
|
87
|
+
scope: {
|
|
88
|
+
status: detail.loop.task?.allowedPaths?.length ||
|
|
89
|
+
detail.loop.task?.deniedPaths?.length
|
|
90
|
+
? "WITHIN_SCOPE"
|
|
91
|
+
: "NOT_EVALUATED",
|
|
92
|
+
allowedPaths: detail.loop.task?.allowedPaths ?? [],
|
|
93
|
+
deniedPaths: detail.loop.task?.deniedPaths ?? [],
|
|
94
|
+
changedFiles: [],
|
|
95
|
+
violations: [],
|
|
96
|
+
},
|
|
97
|
+
testIntegrity: {
|
|
98
|
+
status: "NOT_EVALUATED",
|
|
99
|
+
verdict: "NOT_EVALUATED",
|
|
100
|
+
protectedPaths: [],
|
|
101
|
+
changedProtectedPaths: [],
|
|
102
|
+
findings: [],
|
|
103
|
+
summary: "Automatic test-integrity evidence was not recorded for this run.",
|
|
104
|
+
},
|
|
105
|
+
unresolvedWork: verification.status === "passed" ? [] : [verification.summary ?? "Verification did not pass."],
|
|
106
|
+
nextAction: review.nextAction,
|
|
107
|
+
}),
|
|
76
108
|
};
|
|
77
109
|
if (output.format !== "json") {
|
|
78
110
|
output.rendered = renderDossier(output);
|
|
@@ -81,7 +113,13 @@ export async function martinRunDossierTool(input) {
|
|
|
81
113
|
}
|
|
82
114
|
function renderDossier(output) {
|
|
83
115
|
const lines = [
|
|
84
|
-
output.format === "github-pr" ? "## MartinLoop
|
|
116
|
+
output.format === "github-pr" ? "## MartinLoop Verified Handoff" : "# MartinLoop Verified Handoff",
|
|
117
|
+
"",
|
|
118
|
+
`Outcome: ${output.verifiedHandoff.outcome}`,
|
|
119
|
+
`Definition of Done: ${output.verifiedHandoff.definitionOfDone.acceptanceCriteria.length} acceptance criterion/criteria`,
|
|
120
|
+
`Verification: ${output.verifiedHandoff.verification.status}`,
|
|
121
|
+
`Scope: ${output.verifiedHandoff.scope.status}`,
|
|
122
|
+
`Test Integrity: ${output.verifiedHandoff.testIntegrity.verdict}`,
|
|
85
123
|
"",
|
|
86
124
|
`Objective: ${output.loop.objective}`,
|
|
87
125
|
`Run: ${output.loop.loopId}`,
|
package/dist/tools/run-loop.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SpawnLike } from "../vendor/adapters/index.js";
|
|
2
|
-
import { type RunStore
|
|
3
|
-
import type { LoopBudget, ReceiptScope } from "../vendor/contracts/index.js";
|
|
2
|
+
import { type RunStore } from "../vendor/core/index.js";
|
|
3
|
+
import type { CostProvenance, ExecutionMode, LoopBudget, ReceiptScope } from "../vendor/contracts/index.js";
|
|
4
4
|
import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, type MartinEngine } from "./tool-support.js";
|
|
5
5
|
export interface RunLoopInput {
|
|
6
6
|
objective: string;
|
|
@@ -23,7 +23,10 @@ export interface RunLoopOutput {
|
|
|
23
23
|
reason: string;
|
|
24
24
|
attempts: number;
|
|
25
25
|
costUsd: number;
|
|
26
|
+
costProvenance: CostProvenance;
|
|
26
27
|
verificationPassed: boolean;
|
|
28
|
+
executionMode: ExecutionMode;
|
|
29
|
+
governanceClaimEligible: boolean;
|
|
27
30
|
loopId: string;
|
|
28
31
|
pressure: string;
|
|
29
32
|
shouldStop: boolean;
|
|
@@ -46,5 +49,4 @@ export interface RunLoopOutput {
|
|
|
46
49
|
}
|
|
47
50
|
export declare function __setProofModeVerifierSpawnImplForTests(spawnImpl?: SpawnLike): void;
|
|
48
51
|
export declare function __setRunStoreOverrideForTests(store?: RunStore): void;
|
|
49
|
-
export declare function __setRunMartinImplForTests(impl?: (input: RunMartinInput) => Promise<RunMartinResult>): void;
|
|
50
52
|
export declare function runLoopTool(input: RunLoopInput): Promise<RunLoopOutput>;
|