@martinloop/mcp 0.8.9 → 0.8.10
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/process-supervisor.d.ts +12 -0
- package/dist/vendor/adapters/process-supervisor.js +40 -2
- package/dist/vendor/adapters/verifier-only.js +4 -0
- package/dist/vendor/core/index.d.ts +2 -0
- package/dist/vendor/core/index.js +1 -0
- package/dist/vendor/core/policy.js +11 -1
- package/dist/vendor/core/verified-handoff.js +62 -2
- package/package.json +3 -7
- package/server.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.
|
|
1
|
+
export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.10";
|
package/dist/package-version.js
CHANGED
|
@@ -64,4 +64,16 @@ export declare function spawnSupervisedProcessForTest(command: string, args: rea
|
|
|
64
64
|
* @internal - deterministic pure classification for process-supervisor tests
|
|
65
65
|
*/
|
|
66
66
|
export declare function classifyPosixProcessGroupSnapshot(output: string, ownedGroupPid: number): "closed" | "alive" | "absent" | "invalid";
|
|
67
|
+
type WindowsProcessTreeRow = {
|
|
68
|
+
ProcessId: number;
|
|
69
|
+
ParentProcessId: number;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Classify descendants that remain visible after a failed termination attempt.
|
|
73
|
+
* Two point-in-time snapshots cannot prove closure across the interval because
|
|
74
|
+
* a descendant may be created and reparented between them. Absence is therefore
|
|
75
|
+
* indeterminate and must retain the original termination failure.
|
|
76
|
+
* @internal - deterministic pure classification for process-supervisor tests
|
|
77
|
+
*/
|
|
78
|
+
export declare function classifyWindowsDescendantPostcondition(initialRows: WindowsProcessTreeRow[], postTerminationRows: WindowsProcessTreeRow[], rootPid: number): "closed" | "alive" | "invalid";
|
|
67
79
|
export {};
|
|
@@ -456,6 +456,39 @@ function isErrno(error, code) {
|
|
|
456
456
|
return typeof error === "object" && error !== null && "code" in error
|
|
457
457
|
&& String(error.code) === code;
|
|
458
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Classify descendants that remain visible after a failed termination attempt.
|
|
461
|
+
* Two point-in-time snapshots cannot prove closure across the interval because
|
|
462
|
+
* a descendant may be created and reparented between them. Absence is therefore
|
|
463
|
+
* indeterminate and must retain the original termination failure.
|
|
464
|
+
* @internal - deterministic pure classification for process-supervisor tests
|
|
465
|
+
*/
|
|
466
|
+
export function classifyWindowsDescendantPostcondition(initialRows, postTerminationRows, rootPid) {
|
|
467
|
+
if (!isOwnedPid(rootPid))
|
|
468
|
+
return "invalid";
|
|
469
|
+
const allRows = [...initialRows, ...postTerminationRows];
|
|
470
|
+
if (allRows.some((row) => !isOwnedPid(row.ProcessId) || !Number.isSafeInteger(row.ParentProcessId))) {
|
|
471
|
+
return "invalid";
|
|
472
|
+
}
|
|
473
|
+
const ownedLineage = new Set([rootPid]);
|
|
474
|
+
const expandLineage = (rows) => {
|
|
475
|
+
let changed = true;
|
|
476
|
+
while (changed) {
|
|
477
|
+
changed = false;
|
|
478
|
+
for (const row of rows) {
|
|
479
|
+
if (ownedLineage.has(row.ParentProcessId) && !ownedLineage.has(row.ProcessId)) {
|
|
480
|
+
ownedLineage.add(row.ProcessId);
|
|
481
|
+
changed = true;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
expandLineage(initialRows);
|
|
487
|
+
expandLineage(postTerminationRows);
|
|
488
|
+
return postTerminationRows.some((row) => ownedLineage.has(row.ProcessId))
|
|
489
|
+
? "alive"
|
|
490
|
+
: "invalid";
|
|
491
|
+
}
|
|
459
492
|
async function terminateWindowsDescendantsAfterRootExit(rootPid, graceMs, waitImpl) {
|
|
460
493
|
let rows;
|
|
461
494
|
try {
|
|
@@ -471,11 +504,16 @@ async function terminateWindowsDescendantsAfterRootExit(rootPid, graceMs, waitIm
|
|
|
471
504
|
childrenByParent.set(row.ParentProcessId, children);
|
|
472
505
|
}
|
|
473
506
|
const directChildren = childrenByParent.get(rootPid) ?? [];
|
|
507
|
+
let taskkillFailure;
|
|
474
508
|
for (const childPid of directChildren) {
|
|
475
509
|
const failure = await runTaskkill(childPid, spawnProcess, graceMs, waitImpl);
|
|
476
|
-
|
|
477
|
-
return failure;
|
|
510
|
+
taskkillFailure ??= failure;
|
|
478
511
|
}
|
|
512
|
+
// Once taskkill has failed, a later process-table snapshot cannot prove the
|
|
513
|
+
// complete descendant set closed. Return the bounded failure immediately;
|
|
514
|
+
// starting another PowerShell query here could otherwise stall cleanup.
|
|
515
|
+
if (taskkillFailure)
|
|
516
|
+
return taskkillFailure;
|
|
479
517
|
return { state: "closed" };
|
|
480
518
|
}
|
|
481
519
|
function readWindowsProcessTree() {
|
|
@@ -49,6 +49,10 @@ export function createVerifierOnlyAdapter(options = {}) {
|
|
|
49
49
|
providerId: "verifier",
|
|
50
50
|
model: "verify-only",
|
|
51
51
|
transport: "cli",
|
|
52
|
+
budgetPreflight: {
|
|
53
|
+
pricePerMTokenUsd: 0,
|
|
54
|
+
basis: "Verifier-only execution launches no model provider."
|
|
55
|
+
},
|
|
52
56
|
capabilities: createAdapterCapabilities({
|
|
53
57
|
usageSettlement: true,
|
|
54
58
|
diffArtifacts: true,
|
|
@@ -206,6 +206,8 @@ export interface MartinAdapter {
|
|
|
206
206
|
budgetPreflight?: {
|
|
207
207
|
/** Estimated token floor required before the adapter can report first-turn usage. */
|
|
208
208
|
minimumViableTokens?: number;
|
|
209
|
+
/** Adapter-specific provider price used by the admission estimate. */
|
|
210
|
+
pricePerMTokenUsd?: number;
|
|
209
211
|
basis?: string;
|
|
210
212
|
};
|
|
211
213
|
capabilities?: {
|
|
@@ -880,6 +880,7 @@ export async function runMartin(input) {
|
|
|
880
880
|
remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
|
|
881
881
|
remainingTokens: distilled.constraints.remainingTokens,
|
|
882
882
|
minimumViableTokens: executingAdapter.metadata.budgetPreflight?.minimumViableTokens,
|
|
883
|
+
pricePerMTokenUsd: executingAdapter.metadata.budgetPreflight?.pricePerMTokenUsd,
|
|
883
884
|
// D2: pass remaining iterations so the preflight can compute a budget-aware cap
|
|
884
885
|
// rather than using the old rigid maxUsd * 0.25 that blocked viable attempts.
|
|
885
886
|
remainingIterations: distilled.constraints.remainingIterations
|
|
@@ -385,7 +385,10 @@ function roundUsd(value) {
|
|
|
385
385
|
return Math.round(value * 100) / 100;
|
|
386
386
|
}
|
|
387
387
|
export function evaluateBudgetPreflight(input) {
|
|
388
|
-
const
|
|
388
|
+
const declaredPrice = input.pricePerMTokenUsd;
|
|
389
|
+
const validDeclaredPrice = declaredPrice === undefined
|
|
390
|
+
|| (Number.isFinite(declaredPrice) && declaredPrice >= 0);
|
|
391
|
+
const pricePerMToken = validDeclaredPrice ? (declaredPrice ?? 3.0) : 3.0;
|
|
389
392
|
const rawPromptTokens = Math.ceil(input.promptCharCount / 4);
|
|
390
393
|
const estimatedPromptTokens = Math.ceil(rawPromptTokens * 1.2);
|
|
391
394
|
const estimatedToolOverheadTokens = 800 + input.attemptCount * 200;
|
|
@@ -407,6 +410,13 @@ export function evaluateBudgetPreflight(input) {
|
|
|
407
410
|
estimatedAttemptCostUsd,
|
|
408
411
|
provenance
|
|
409
412
|
};
|
|
413
|
+
if (!validDeclaredPrice) {
|
|
414
|
+
return {
|
|
415
|
+
allowed: false,
|
|
416
|
+
reason: "Preflight: invalid adapter price; pricePerMTokenUsd must be finite and non-negative.",
|
|
417
|
+
estimate
|
|
418
|
+
};
|
|
419
|
+
}
|
|
410
420
|
if (input.remainingTokens !== undefined
|
|
411
421
|
&& estimatedMinimumViableTokens > 0
|
|
412
422
|
&& estimatedTotalTokens > input.remainingTokens) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: MartinLoop contributors
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { posix, win32 } from "node:path";
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
5
6
|
// Mapping: granular TestIntegrityStatus → public TestIntegrityVerdict
|
|
6
7
|
//
|
|
@@ -88,6 +89,65 @@ export function deriveVerifiedHandoffLifecycleTruth(loop) {
|
|
|
88
89
|
evidenceAvailable: true,
|
|
89
90
|
};
|
|
90
91
|
}
|
|
92
|
+
function sameOptionalVerifierFilesystemPath(actual, expected) {
|
|
93
|
+
if (actual === undefined || expected === undefined) {
|
|
94
|
+
return actual === expected;
|
|
95
|
+
}
|
|
96
|
+
return sameVerifierFilesystemPath(actual, expected);
|
|
97
|
+
}
|
|
98
|
+
function classifyVerifierFilesystemPath(value) {
|
|
99
|
+
// Persisted Windows bindings are expected to be drive-absolute or UNC.
|
|
100
|
+
// Do not use win32.isAbsolute() here because it also classifies POSIX-style
|
|
101
|
+
// root paths such as "/repo" as Windows root-relative paths.
|
|
102
|
+
if (/^[A-Za-z]:[\\/]/u.test(value) || /^\\\\/u.test(value) || /^\/\//u.test(value)) {
|
|
103
|
+
return "windows";
|
|
104
|
+
}
|
|
105
|
+
if (value.startsWith("/")) {
|
|
106
|
+
return "posix";
|
|
107
|
+
}
|
|
108
|
+
return "relative";
|
|
109
|
+
}
|
|
110
|
+
function stripTrailingVerifierSeparators(value, kind) {
|
|
111
|
+
if (kind === "windows") {
|
|
112
|
+
// Preserve drive roots (C:\\) and UNC share roots while removing harmless
|
|
113
|
+
// trailing separators from ordinary persisted paths.
|
|
114
|
+
if (/^[a-z]:\\$/iu.test(value) || /^\\\\[^\\]+\\[^\\]+\\?$/u.test(value)) {
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
return value.replace(/[\\/]+$/u, "");
|
|
118
|
+
}
|
|
119
|
+
if (kind === "posix") {
|
|
120
|
+
return value === "/" ? value : value.replace(/\/+$/u, "");
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
function normalizeVerifierFilesystemPath(value) {
|
|
125
|
+
const kind = classifyVerifierFilesystemPath(value);
|
|
126
|
+
if (kind === "windows") {
|
|
127
|
+
return {
|
|
128
|
+
kind,
|
|
129
|
+
value: stripTrailingVerifierSeparators(win32.normalize(value), kind).toLowerCase(),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (kind === "posix") {
|
|
133
|
+
return {
|
|
134
|
+
kind,
|
|
135
|
+
value: stripTrailingVerifierSeparators(posix.normalize(value), kind),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// Relative verifier bindings are legacy/non-canonical. Normalize only their
|
|
139
|
+
// separators/dot segments and keep comparison case-sensitive/fail-closed.
|
|
140
|
+
return {
|
|
141
|
+
kind,
|
|
142
|
+
value: stripTrailingVerifierSeparators(posix.normalize(value.replace(/\\/gu, "/")), kind),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function sameVerifierFilesystemPath(actual, expected) {
|
|
146
|
+
const normalizedActual = normalizeVerifierFilesystemPath(actual);
|
|
147
|
+
const normalizedExpected = normalizeVerifierFilesystemPath(expected);
|
|
148
|
+
return (normalizedActual.kind === normalizedExpected.kind &&
|
|
149
|
+
normalizedActual.value === normalizedExpected.value);
|
|
150
|
+
}
|
|
91
151
|
export function verifierActuallyPassed(evidence, expected) {
|
|
92
152
|
if (!evidence?.passed || !evidence.binding || !evidence.steps) {
|
|
93
153
|
return false;
|
|
@@ -98,8 +158,8 @@ export function verifierActuallyPassed(evidence, expected) {
|
|
|
98
158
|
if (evidence.binding.runId !== expected.runId ||
|
|
99
159
|
evidence.binding.workspaceId !== expected.workspaceId ||
|
|
100
160
|
(evidence.binding.attemptId ?? undefined) !== (expected.attemptId ?? undefined) ||
|
|
101
|
-
evidence.binding.cwd
|
|
102
|
-
(evidence.binding.runsRoot
|
|
161
|
+
!sameVerifierFilesystemPath(evidence.binding.cwd, expected.cwd) ||
|
|
162
|
+
!sameOptionalVerifierFilesystemPath(evidence.binding.runsRoot, expected.runsRoot) ||
|
|
103
163
|
(evidence.binding.executionProfile ?? undefined) !== (expected.executionProfile ?? undefined) ||
|
|
104
164
|
JSON.stringify(evidence.binding.allowedNetworkDomains ?? []) !== JSON.stringify(expected.allowedNetworkDomains ?? []) ||
|
|
105
165
|
JSON.stringify(evidence.binding.commands) !== JSON.stringify(expected.commands)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martinloop/mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.10",
|
|
4
4
|
"mcpName": "io.github.Keesan12/martin-loop",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -74,13 +74,9 @@
|
|
|
74
74
|
"mcpb:smoke": "node ./mcpb/smoke-mcpb.mjs"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
77
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
78
78
|
"@open-policy-agent/opa-wasm": "^1.10.0",
|
|
79
|
-
"
|
|
80
|
-
"@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
|
|
81
|
-
"@opentelemetry/resources": "^2.6.1",
|
|
82
|
-
"@opentelemetry/sdk-logs": "^0.214.0",
|
|
83
|
-
"ts-morph": "^21.0.0"
|
|
79
|
+
"zod": "^4.6.7"
|
|
84
80
|
},
|
|
85
81
|
"devDependencies": {
|
|
86
82
|
"@anthropic-ai/mcpb": "2.1.2",
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/Keesan12/martin-loop",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.8.
|
|
10
|
+
"version": "0.8.10",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@martinloop/mcp",
|
|
15
|
-
"version": "0.8.
|
|
15
|
+
"version": "0.8.10",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|