@martinloop/mcp 0.6.3 → 0.6.4
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/tools/tool-support.d.ts +3 -0
- package/dist/tools/tool-support.js +15 -1
- package/dist/vendor/adapters/codex-capabilities.js +15 -10
- package/dist/vendor/adapters/codex-host.js +5 -2
- package/dist/vendor/core/index.js +17 -26
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const MARTIN_MCP_PACKAGE_VERSION = "0.6.
|
|
1
|
+
export declare const MARTIN_MCP_PACKAGE_VERSION = "0.6.4";
|
package/dist/package-version.js
CHANGED
|
@@ -37,6 +37,9 @@ export interface LoopPreview {
|
|
|
37
37
|
remainingTokens?: number;
|
|
38
38
|
lastAttempt?: AttemptSummary;
|
|
39
39
|
routingEconomics?: Record<string, unknown>;
|
|
40
|
+
/** Present when an attempt is executing but not yet in the completed attempts list.
|
|
41
|
+
* Indicates the backend is actively running — callers must NOT start a concurrent recovery run. */
|
|
42
|
+
activeAttemptId?: string;
|
|
40
43
|
}
|
|
41
44
|
export interface AttemptArtifactFiles {
|
|
42
45
|
directory: string;
|
|
@@ -141,6 +141,19 @@ export function buildLoopPreview(loop) {
|
|
|
141
141
|
attemptsUsed: loop.attempts.length
|
|
142
142
|
});
|
|
143
143
|
const lastAttempt = loop.attempts.at(-1);
|
|
144
|
+
// Detect an in-flight attempt: an attempt.started event whose attemptId has
|
|
145
|
+
// no matching entry in loop.attempts (completed attempts only). This is the
|
|
146
|
+
// signal callers need to distinguish "backend actively executing" from
|
|
147
|
+
// "nothing has started yet" after a client transport timeout.
|
|
148
|
+
const completedAttemptIds = new Set(loop.attempts.map((a) => a.attemptId).filter(Boolean));
|
|
149
|
+
const activeAttemptEvent = (loop.events ?? [])
|
|
150
|
+
.filter((e) => e.type === "attempt.started")
|
|
151
|
+
.at(-1);
|
|
152
|
+
const activeAttemptId = activeAttemptEvent !== undefined &&
|
|
153
|
+
typeof activeAttemptEvent.payload["attemptId"] === "string" &&
|
|
154
|
+
!completedAttemptIds.has(activeAttemptEvent.payload["attemptId"])
|
|
155
|
+
? activeAttemptEvent.payload["attemptId"]
|
|
156
|
+
: undefined;
|
|
144
157
|
return {
|
|
145
158
|
loopId: loop.loopId,
|
|
146
159
|
title: loop.task?.title ?? loop.loopId,
|
|
@@ -161,7 +174,8 @@ export function buildLoopPreview(loop) {
|
|
|
161
174
|
? { remainingTokens: costState.remainingTokens }
|
|
162
175
|
: {}),
|
|
163
176
|
...(lastAttempt ? { lastAttempt: buildAttemptSummary(lastAttempt) } : {}),
|
|
164
|
-
...(loop.routingEconomics ? { routingEconomics: buildRoutingEconomicsSummary(loop.routingEconomics) } : {})
|
|
177
|
+
...(loop.routingEconomics ? { routingEconomics: buildRoutingEconomicsSummary(loop.routingEconomics) } : {}),
|
|
178
|
+
...(activeAttemptId !== undefined ? { activeAttemptId } : {})
|
|
165
179
|
};
|
|
166
180
|
}
|
|
167
181
|
function buildRoutingEconomicsSummary(economics) {
|
|
@@ -33,24 +33,28 @@ function flagContext(help, flag) {
|
|
|
33
33
|
const index = lines.findIndex((line) => line.includes(flag));
|
|
34
34
|
if (index < 0)
|
|
35
35
|
return "";
|
|
36
|
-
|
|
36
|
+
let end = index + 1;
|
|
37
|
+
while (end < lines.length && end < index + 24) {
|
|
38
|
+
if (/^\s*(?:-\w,\s*)?--[a-z][\w-]*/iu.test(lines[end] ?? ""))
|
|
39
|
+
break;
|
|
40
|
+
end += 1;
|
|
41
|
+
}
|
|
42
|
+
return lines.slice(Math.max(0, index - 1), end).join("\n");
|
|
37
43
|
}
|
|
38
44
|
function parseAdvertisedValues(help, flag) {
|
|
39
45
|
const flagIndex = help.indexOf(flag);
|
|
40
46
|
if (flagIndex < 0)
|
|
41
47
|
return [];
|
|
42
48
|
const tail = help.slice(flagIndex);
|
|
43
|
-
const nextFlagOffset = tail.slice(flag.length).search(/\n\s
|
|
49
|
+
const nextFlagOffset = tail.slice(flag.length).search(/\n\s*(?:-\w,\s*)?--[a-z][\w-]*/iu);
|
|
44
50
|
const scoped = nextFlagOffset < 0
|
|
45
51
|
? tail
|
|
46
52
|
: tail.slice(0, flag.length + nextFlagOffset);
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
return [];
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.map((value) => value.trim().replace(/[\].]$/u, ""))
|
|
53
|
-
.filter(Boolean);
|
|
53
|
+
const inline = scoped.match(/possible values:[ \t]*([^\]\r\n]+)/iu)?.[1];
|
|
54
|
+
if (inline?.trim()) {
|
|
55
|
+
return inline.split(",").map((value) => value.trim().replace(/[\].]$/u, "")).filter(Boolean);
|
|
56
|
+
}
|
|
57
|
+
return [...scoped.matchAll(/^\s*-\s+([a-z][a-z0-9-]*):/gimu)].map((match) => match[1]).filter(Boolean);
|
|
54
58
|
}
|
|
55
59
|
function buildInjectedSpawnPlan(binaryPath, args, platform) {
|
|
56
60
|
if (platform !== "win32")
|
|
@@ -214,7 +218,8 @@ export function resolveCodexAutonomyCandidates(profile, intent = DEFAULT_AGENT_E
|
|
|
214
218
|
binaryPath: profile.binaryPath,
|
|
215
219
|
intent,
|
|
216
220
|
strategy: "sandbox+approval",
|
|
217
|
-
sandboxValue: "workspace-write"
|
|
221
|
+
sandboxValue: "workspace-write",
|
|
222
|
+
...(profile.approvalPolicy?.values?.includes("never") ? { approvalValue: "never" } : {})
|
|
218
223
|
});
|
|
219
224
|
}
|
|
220
225
|
// Keep provider automation as a compatibility fallback. It is never trusted
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, extname, join, resolve } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { userInfo } from "node:os";
|
|
5
5
|
import { normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
|
|
6
6
|
import { createSpawnPlan, resolveNpmShimScript } from "./cli-bridge.js";
|
|
7
7
|
import { buildCodexExecArgs, buildCodexStdin, cacheCodexCapabilityProfile, probeCodexCapabilities, resolveCodexAutonomyCandidates } from "./codex-capabilities.js";
|
|
@@ -437,7 +437,10 @@ export function probeCodexLaunch(input) {
|
|
|
437
437
|
for (const transport of transports) {
|
|
438
438
|
const marker = `.martin-codex-write-probe-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`;
|
|
439
439
|
const markerPath = join(input.workingDirectory, marker);
|
|
440
|
-
|
|
440
|
+
// Codex workspace-write intentionally permits the host temp directory,
|
|
441
|
+
// and the repo can be nested inside a broader writable host workspace.
|
|
442
|
+
// Probe from the user-home boundary so the denial canary is outside both.
|
|
443
|
+
const outsideRoot = mkdtempSync(join(userInfo().homedir, ".martin-codex-outside-probe-"));
|
|
441
444
|
const outsideMarkerPath = join(outsideRoot, ".martin-codex-outside-probe.tmp");
|
|
442
445
|
const prompt = buildMarkerPrompt(marker, outsideMarkerPath);
|
|
443
446
|
let args;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs, appendLoopEvent, createLoopRecord } from "../contracts/index.js";
|
|
3
2
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } from "./policy.js";
|
|
4
3
|
import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
|
|
@@ -776,6 +775,12 @@ export async function runMartin(input) {
|
|
|
776
775
|
capturedAt: attemptStartedAt
|
|
777
776
|
})
|
|
778
777
|
: undefined;
|
|
778
|
+
// Persist attempt.started state before blocking on provider execution.
|
|
779
|
+
// If the MCP client transport times out (default 300 s), a status query against
|
|
780
|
+
// the stable run ID must see this attempt as active rather than observing
|
|
781
|
+
// attempts=0 with no in-progress state — which falsely implies nothing happened
|
|
782
|
+
// and causes callers to start a concurrent recovery run before the backend finishes.
|
|
783
|
+
await persistLoopRecordIfSupported(input.store, loop);
|
|
779
784
|
let result;
|
|
780
785
|
try {
|
|
781
786
|
result = await executingAdapter.execute({
|
|
@@ -1136,7 +1141,7 @@ export async function runMartin(input) {
|
|
|
1136
1141
|
// VERIFY: Run grounding scan on patch diff if available
|
|
1137
1142
|
// Uses the task's repoRoot to build/load the grounding index, then scans any diff
|
|
1138
1143
|
let groundingScanResult;
|
|
1139
|
-
const patchDiff = buildPatchDiff(result, changedFiles
|
|
1144
|
+
const patchDiff = buildPatchDiff(result, changedFiles);
|
|
1140
1145
|
if (patchDiff && input.task.repoRoot) {
|
|
1141
1146
|
try {
|
|
1142
1147
|
const groundingIndex = await loadOrBuildRepoGroundingIndex(input.task.repoRoot);
|
|
@@ -1569,34 +1574,20 @@ function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
|
|
|
1569
1574
|
}
|
|
1570
1575
|
return listAttemptChangedFilesSinceBoundary({ repoRoot, boundary: rollbackBoundary });
|
|
1571
1576
|
}
|
|
1572
|
-
function buildPatchDiff(result, changedFiles
|
|
1577
|
+
function buildPatchDiff(result, changedFiles) {
|
|
1573
1578
|
// Use structured diff stats to build a minimal diff header if no raw diff is available
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
return
|
|
1577
|
-
.map((file) =>
|
|
1579
|
+
if (result.execution?.changedFiles?.length) {
|
|
1580
|
+
// Build a synthetic diff header from changed file list
|
|
1581
|
+
return result.execution.changedFiles
|
|
1582
|
+
.map((file) => `--- a/${file}\n+++ b/${file}\n@@ -0,0 +1 @@\n+`)
|
|
1578
1583
|
.join("\n");
|
|
1579
1584
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
const isNewWorkspaceFile = repoRoot ? isUntrackedWorkspaceFile(repoRoot, normalizedFile) : false;
|
|
1585
|
-
const before = isNewWorkspaceFile ? "/dev/null" : `a/${normalizedFile}`;
|
|
1586
|
-
return `--- ${before}\n+++ b/${normalizedFile}\n@@ -0,0 +1 @@\n+`;
|
|
1587
|
-
}
|
|
1588
|
-
function isUntrackedWorkspaceFile(repoRoot, file) {
|
|
1589
|
-
const result = spawnSync("git", ["ls-files", "--others", "--exclude-standard", "--", file], {
|
|
1590
|
-
cwd: repoRoot,
|
|
1591
|
-
encoding: "utf8"
|
|
1592
|
-
});
|
|
1593
|
-
if (result.status !== 0 || typeof result.stdout !== "string") {
|
|
1594
|
-
return false;
|
|
1585
|
+
if (changedFiles.length > 0) {
|
|
1586
|
+
return changedFiles
|
|
1587
|
+
.map((file) => `--- a/${file}\n+++ b/${file}\n@@ -0,0 +1 @@\n+`)
|
|
1588
|
+
.join("\n");
|
|
1595
1589
|
}
|
|
1596
|
-
return
|
|
1597
|
-
.split(/\r?\n/u)
|
|
1598
|
-
.map((line) => line.trim().replace(/\\/gu, "/"))
|
|
1599
|
-
.includes(file);
|
|
1590
|
+
return undefined;
|
|
1600
1591
|
}
|
|
1601
1592
|
function createBudgetSettlement(input) {
|
|
1602
1593
|
const totalActualUsd = getUsageUsd(input.usage);
|
package/package.json
CHANGED
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.6.
|
|
10
|
+
"version": "0.6.4",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@martinloop/mcp",
|
|
15
|
-
"version": "0.6.
|
|
15
|
+
"version": "0.6.4",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|