@martinloop/mcp 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/vendor/adapters/claude-cli.d.ts +11 -1
- package/dist/vendor/adapters/claude-cli.js +285 -71
- package/dist/vendor/adapters/cli-bridge.d.ts +12 -3
- package/dist/vendor/adapters/cli-bridge.js +129 -149
- package/dist/vendor/adapters/codex-cli.d.ts +3 -3
- package/dist/vendor/adapters/codex-cli.js +10 -24
- package/dist/vendor/adapters/codex-host.js +127 -132
- package/dist/vendor/adapters/index.d.ts +2 -1
- package/dist/vendor/adapters/index.js +2 -1
- package/dist/vendor/adapters/openai-compatible.js +88 -6
- package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
- package/dist/vendor/adapters/process-supervisor.js +541 -0
- package/dist/vendor/adapters/verifier-only.d.ts +10 -1
- package/dist/vendor/adapters/verifier-only.js +43 -1
- package/dist/vendor/contracts/external-outcome.d.ts +102 -0
- package/dist/vendor/contracts/external-outcome.js +226 -0
- package/dist/vendor/contracts/index.d.ts +8 -1
- package/dist/vendor/contracts/index.js +2 -0
- package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
- package/dist/vendor/contracts/swarm-hosted.js +629 -0
- package/dist/vendor/contracts/swarm.d.ts +449 -0
- package/dist/vendor/contracts/swarm.js +550 -0
- package/dist/vendor/core/external-outcome.d.ts +34 -0
- package/dist/vendor/core/external-outcome.js +515 -0
- package/dist/vendor/core/grounding.js +1 -1
- package/dist/vendor/core/index.d.ts +47 -1
- package/dist/vendor/core/index.js +53 -3
- package/dist/vendor/core/leash.d.ts +1 -0
- package/dist/vendor/core/leash.js +13 -0
- package/dist/vendor/core/persistence/integrity.d.ts +4 -0
- package/dist/vendor/core/persistence/integrity.js +19 -6
- package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
- package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
- package/dist/vendor/core/swarm/candidates.d.ts +78 -0
- package/dist/vendor/core/swarm/candidates.js +810 -0
- package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
- package/dist/vendor/core/swarm/evidence-projection.js +128 -0
- package/dist/vendor/core/swarm/evidence.d.ts +40 -0
- package/dist/vendor/core/swarm/evidence.js +575 -0
- package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
- package/dist/vendor/core/swarm/hosted-export.js +362 -0
- package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
- package/dist/vendor/core/swarm/hosted-projection.js +258 -0
- package/dist/vendor/core/swarm/index.d.ts +110 -0
- package/dist/vendor/core/swarm/index.js +1060 -0
- package/dist/vendor/core/swarm/integration.d.ts +76 -0
- package/dist/vendor/core/swarm/integration.js +615 -0
- package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
- package/dist/vendor/core/swarm/lifecycle.js +252 -0
- package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
- package/dist/vendor/core/swarm/live-runtime.js +1482 -0
- package/dist/vendor/core/swarm/live-store.d.ts +99 -0
- package/dist/vendor/core/swarm/live-store.js +860 -0
- package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
- package/dist/vendor/core/swarm/operational-reader.js +96 -0
- package/dist/vendor/core/swarm/operations.d.ts +29 -0
- package/dist/vendor/core/swarm/operations.js +128 -0
- package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
- package/dist/vendor/core/swarm/parent-receipt.js +489 -0
- package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
- package/dist/vendor/core/swarm/receipt-projection.js +120 -0
- package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
- package/dist/vendor/core/swarm/scheduler.js +555 -0
- package/dist/vendor/core/swarm/verification.d.ts +70 -0
- package/dist/vendor/core/swarm/verification.js +295 -0
- package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
- package/dist/vendor/core/swarm/workspaces.js +488 -0
- package/dist/vendor/core/verified-handoff.d.ts +4 -0
- package/dist/vendor/core/verified-handoff.js +4 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ChildProcess, SpawnOptions } from "node:child_process";
|
|
2
2
|
import { diffStatsFromNumstat } from "./runtime-support.js";
|
|
3
3
|
import type { VerifierExecutionBinding } from "../core/index.js";
|
|
4
|
+
import type { ExternalOutcomeEvidenceReference } from "../contracts/index.js";
|
|
5
|
+
import { type ProcessTreeCleanupResult } from "./process-supervisor.js";
|
|
4
6
|
export type SpawnLike = (command: string, args?: readonly string[], options?: SpawnOptions) => ChildProcess;
|
|
5
7
|
export interface SubprocessResult {
|
|
6
8
|
exitCode: number;
|
|
@@ -27,9 +29,11 @@ export interface SubprocessResult {
|
|
|
27
29
|
*/
|
|
28
30
|
terminationReason?: string;
|
|
29
31
|
launched: boolean;
|
|
32
|
+
cleanup: ProcessTreeCleanupResult;
|
|
30
33
|
}
|
|
31
34
|
export interface VerificationOutcome {
|
|
32
35
|
passed: boolean;
|
|
36
|
+
processCloseState: "closed" | "not_required" | "failed";
|
|
33
37
|
summary: string;
|
|
34
38
|
steps: VerificationStepOutcome[];
|
|
35
39
|
warnings?: string[];
|
|
@@ -44,10 +48,12 @@ export interface VerificationStepOutcome {
|
|
|
44
48
|
timedOut: boolean;
|
|
45
49
|
fastFail: boolean;
|
|
46
50
|
detail?: string;
|
|
51
|
+
evidence?: ExternalOutcomeEvidenceReference;
|
|
47
52
|
}
|
|
48
53
|
export declare function runSubprocess(command: string, args: string[], options: {
|
|
49
54
|
cwd: string;
|
|
50
55
|
timeoutMs: number;
|
|
56
|
+
env?: NodeJS.ProcessEnv;
|
|
51
57
|
spawnImpl?: SpawnLike;
|
|
52
58
|
stdinData?: string;
|
|
53
59
|
/**
|
|
@@ -68,14 +74,17 @@ export declare function runSubprocess(command: string, args: string[], options:
|
|
|
68
74
|
onStdoutChunk?: (chunk: Buffer, terminate: (reason: string) => void) => void;
|
|
69
75
|
/** Optional abort signal — kills the subprocess when aborted. */
|
|
70
76
|
signal?: AbortSignal;
|
|
77
|
+
/** Require an owned process-tree sweep before successful completion. */
|
|
78
|
+
requireTreeClosureOnSuccess?: boolean;
|
|
71
79
|
}): Promise<SubprocessResult>;
|
|
72
80
|
export declare function runVerification(commands: string[], cwd: string, timeoutMs: number, verificationStack?: Array<{
|
|
73
81
|
command: string;
|
|
74
82
|
type: string;
|
|
75
83
|
fastFail?: boolean;
|
|
76
|
-
}>, spawnImpl?: SpawnLike, binding?: Omit<VerifierExecutionBinding, "commands"
|
|
77
|
-
export declare function readGitExecutionArtifacts(repoRoot: string, timeoutMs: number, spawnImpl?: SpawnLike): Promise<{
|
|
84
|
+
}>, spawnImpl?: SpawnLike, binding?: Omit<VerifierExecutionBinding, "commands">, signal?: AbortSignal): Promise<VerificationOutcome>;
|
|
85
|
+
export declare function readGitExecutionArtifacts(repoRoot: string, timeoutMs: number, spawnImpl?: SpawnLike, requestedChangedFiles?: readonly string[]): Promise<{
|
|
78
86
|
changedFiles?: string[];
|
|
87
|
+
patch?: string;
|
|
79
88
|
diffStats?: ReturnType<typeof diffStatsFromNumstat>;
|
|
80
89
|
}>;
|
|
81
90
|
export declare function readGitChangedFiles(repoRoot: string, timeoutMs: number, spawnImpl?: SpawnLike): Promise<string[]>;
|
|
@@ -1,146 +1,23 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { basename, delimiter, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
3
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
3
|
import { diffStatsFromNumstat } from "./runtime-support.js";
|
|
4
|
+
import { spawnSupervisedProcess } from "./process-supervisor.js";
|
|
5
5
|
const gitRepositoryRootCache = new Map();
|
|
6
6
|
export async function runSubprocess(command, args, options) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (settled) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
settled = true;
|
|
22
|
-
resolve({ ...result, timedOut, outputCapped, ...(terminationReason ? { terminationReason } : {}) });
|
|
23
|
-
};
|
|
24
|
-
let proc;
|
|
25
|
-
try {
|
|
26
|
-
const spawnPlan = createSpawnPlan(command, args, options.cwd, options.spawnImpl !== undefined);
|
|
27
|
-
proc = (options.spawnImpl ?? spawn)(spawnPlan.command, spawnPlan.args, {
|
|
28
|
-
cwd: options.cwd,
|
|
29
|
-
stdio: [stdinMode, "pipe", "pipe"],
|
|
30
|
-
env: process.env
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
-
resolveOnce({ exitCode: 1, stdout: "", stderr: message, launched: false, completed: false, crashed: true });
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const trackOutput = (chunks, chunk) => {
|
|
39
|
-
if (outputCapped || timedOut || terminationReason) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
chunks.push(chunk);
|
|
43
|
-
outputBytes += chunk.byteLength;
|
|
44
|
-
if (options.maxOutputBytes !== undefined && outputBytes > options.maxOutputBytes) {
|
|
45
|
-
outputCapped = true;
|
|
46
|
-
proc.kill("SIGTERM");
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
const terminateEarly = (reason) => {
|
|
50
|
-
if (terminationReason || timedOut || outputCapped) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
terminationReason = reason;
|
|
54
|
-
proc.kill("SIGTERM");
|
|
55
|
-
};
|
|
56
|
-
// Honour the harness abort signal — kill the subprocess immediately
|
|
57
|
-
if (options.signal !== undefined) {
|
|
58
|
-
const sig = options.signal;
|
|
59
|
-
if (sig.aborted) {
|
|
60
|
-
proc.kill("SIGTERM");
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
const onAbort = () => { proc.kill("SIGTERM"); };
|
|
64
|
-
sig.addEventListener("abort", onAbort, { once: true });
|
|
65
|
-
proc.on("close", () => { sig.removeEventListener("abort", onAbort); });
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
proc.stdout?.on("data", (chunk) => {
|
|
69
|
-
if (outputCapped || timedOut || terminationReason) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
trackOutput(stdoutChunks, chunk);
|
|
73
|
-
if (options.onStdoutChunk) {
|
|
74
|
-
try {
|
|
75
|
-
options.onStdoutChunk(chunk, terminateEarly);
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
terminateEarly(`stdout inspector error: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
proc.stderr?.on("data", (chunk) => {
|
|
83
|
-
if (outputCapped || timedOut || terminationReason) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
trackOutput(stderrChunks, chunk);
|
|
87
|
-
});
|
|
88
|
-
proc.stdin?.on("error", (error) => {
|
|
89
|
-
// Some CLIs exit before consuming stdin in tests and on fast-fail paths.
|
|
90
|
-
// Treat the closed pipe as a handled subprocess lifecycle condition.
|
|
91
|
-
if (error.code === "EPIPE") {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
stderrChunks.push(Buffer.from(`${error.message}\n`, "utf8"));
|
|
95
|
-
});
|
|
96
|
-
const timer = setTimeout(() => {
|
|
97
|
-
if (settled || exited || proc.exitCode !== null) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
timedOut = true;
|
|
101
|
-
proc.kill("SIGTERM");
|
|
102
|
-
}, options.timeoutMs);
|
|
103
|
-
proc.on("exit", () => {
|
|
104
|
-
exited = true;
|
|
105
|
-
});
|
|
106
|
-
proc.on("error", (error) => {
|
|
107
|
-
clearTimeout(timer);
|
|
108
|
-
resolveOnce({ exitCode: 1, stdout: "", stderr: error.message, launched: false, completed: false, crashed: true });
|
|
109
|
-
});
|
|
110
|
-
proc.on("close", (code) => {
|
|
111
|
-
clearTimeout(timer);
|
|
112
|
-
const completed = code !== null && !timedOut && !outputCapped && !terminationReason;
|
|
113
|
-
resolveOnce({
|
|
114
|
-
exitCode: code ?? 1,
|
|
115
|
-
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
116
|
-
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
117
|
-
launched: true,
|
|
118
|
-
completed,
|
|
119
|
-
crashed: !completed && !timedOut && !outputCapped && !terminationReason,
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
if (options.stdinData !== undefined && proc.stdin) {
|
|
123
|
-
try {
|
|
124
|
-
proc.stdin.end(options.stdinData, "utf8");
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
const stdinError = error;
|
|
128
|
-
if (stdinError.code !== "EPIPE") {
|
|
129
|
-
clearTimeout(timer);
|
|
130
|
-
resolveOnce({
|
|
131
|
-
exitCode: 1,
|
|
132
|
-
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
133
|
-
stderr: stdinError.message,
|
|
134
|
-
launched: false,
|
|
135
|
-
completed: false,
|
|
136
|
-
crashed: true,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
7
|
+
const spawnPlan = createSpawnPlan(command, args, options.cwd, options.spawnImpl !== undefined);
|
|
8
|
+
return spawnSupervisedProcess(spawnPlan.command, spawnPlan.args, {
|
|
9
|
+
cwd: options.cwd,
|
|
10
|
+
timeoutMs: options.timeoutMs,
|
|
11
|
+
...(options.env ? { env: options.env } : {}),
|
|
12
|
+
...(options.spawnImpl ? { spawnImpl: options.spawnImpl } : {}),
|
|
13
|
+
...(options.stdinData !== undefined ? { stdinData: options.stdinData } : {}),
|
|
14
|
+
...(options.maxOutputBytes !== undefined ? { maxOutputBytes: options.maxOutputBytes } : {}),
|
|
15
|
+
...(options.onStdoutChunk ? { onStdoutChunk: options.onStdoutChunk } : {}),
|
|
16
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
17
|
+
...(options.requireTreeClosureOnSuccess ? { requireTreeClosureOnSuccess: true } : {})
|
|
141
18
|
});
|
|
142
19
|
}
|
|
143
|
-
export async function runVerification(commands, cwd, timeoutMs, verificationStack, spawnImpl, binding) {
|
|
20
|
+
export async function runVerification(commands, cwd, timeoutMs, verificationStack, spawnImpl, binding, signal) {
|
|
144
21
|
const steps = verificationStack && verificationStack.length > 0
|
|
145
22
|
? verificationStack.map((step) => ({
|
|
146
23
|
command: step.command,
|
|
@@ -150,12 +27,29 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
150
27
|
const executionBinding = {
|
|
151
28
|
runId: binding?.runId ?? "unbound",
|
|
152
29
|
workspaceId: binding?.workspaceId ?? "unbound",
|
|
30
|
+
...(binding?.attemptId ? { attemptId: binding.attemptId } : {}),
|
|
153
31
|
cwd: binding?.cwd ?? cwd,
|
|
32
|
+
...(binding?.runsRoot ? { runsRoot: binding.runsRoot } : {}),
|
|
33
|
+
...(binding?.executionProfile ? { executionProfile: binding.executionProfile } : {}),
|
|
34
|
+
...(binding?.allowedNetworkDomains?.length ? { allowedNetworkDomains: [...binding.allowedNetworkDomains] } : {}),
|
|
154
35
|
commands: steps.map((step) => step.command),
|
|
155
36
|
};
|
|
37
|
+
const verifierEnv = {
|
|
38
|
+
...process.env,
|
|
39
|
+
MARTIN_RUN_ID: executionBinding.runId,
|
|
40
|
+
MARTIN_WORKSPACE_ID: executionBinding.workspaceId,
|
|
41
|
+
...(executionBinding.attemptId ? { MARTIN_ATTEMPT_ID: executionBinding.attemptId } : {}),
|
|
42
|
+
MARTIN_VERIFIER_CWD: executionBinding.cwd,
|
|
43
|
+
...(executionBinding.runsRoot ? { MARTIN_RUNS_DIR: executionBinding.runsRoot } : {}),
|
|
44
|
+
...(executionBinding.executionProfile ? { MARTIN_EXECUTION_PROFILE: executionBinding.executionProfile } : {}),
|
|
45
|
+
...(executionBinding.allowedNetworkDomains?.length
|
|
46
|
+
? { MARTIN_ALLOWED_NETWORK_DOMAINS: JSON.stringify(executionBinding.allowedNetworkDomains) }
|
|
47
|
+
: {}),
|
|
48
|
+
};
|
|
156
49
|
if (steps.length === 0) {
|
|
157
50
|
return {
|
|
158
51
|
passed: false,
|
|
52
|
+
processCloseState: "not_required",
|
|
159
53
|
summary: "No verification commands specified; execution is not VERIFIED.",
|
|
160
54
|
steps: [],
|
|
161
55
|
warnings: ["Execution completed without verifier evidence."],
|
|
@@ -165,6 +59,7 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
165
59
|
const failedSteps = [];
|
|
166
60
|
const stepOutcomes = [];
|
|
167
61
|
const warnings = [];
|
|
62
|
+
let processCloseState = "not_required";
|
|
168
63
|
for (const step of steps) {
|
|
169
64
|
let bin;
|
|
170
65
|
let args;
|
|
@@ -193,8 +88,17 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
193
88
|
if (!bin) {
|
|
194
89
|
continue;
|
|
195
90
|
}
|
|
196
|
-
const result = await runSubprocess(bin, args, {
|
|
91
|
+
const result = await runSubprocess(bin, args, {
|
|
92
|
+
cwd,
|
|
93
|
+
timeoutMs,
|
|
94
|
+
env: verifierEnv,
|
|
95
|
+
spawnImpl,
|
|
96
|
+
...(signal ? { signal } : {}),
|
|
97
|
+
requireTreeClosureOnSuccess: true
|
|
98
|
+
});
|
|
197
99
|
const detail = truncate(result.stderr.trim() || result.stdout.trim(), 500);
|
|
100
|
+
processCloseState = combineProcessCloseState(processCloseState, result.cleanup.state);
|
|
101
|
+
const evidence = parseExternalOutcomeEvidenceReference(result.stdout);
|
|
198
102
|
stepOutcomes.push({
|
|
199
103
|
command: step.command,
|
|
200
104
|
launched: result.launched,
|
|
@@ -203,11 +107,23 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
203
107
|
exitCode: result.exitCode,
|
|
204
108
|
timedOut: result.timedOut,
|
|
205
109
|
fastFail: step.fastFail,
|
|
206
|
-
...(detail ? { detail } : {})
|
|
110
|
+
...(detail ? { detail } : {}),
|
|
111
|
+
...(evidence ? { evidence } : {})
|
|
207
112
|
});
|
|
113
|
+
if (result.cleanup.state === "failed") {
|
|
114
|
+
return {
|
|
115
|
+
passed: false,
|
|
116
|
+
processCloseState,
|
|
117
|
+
summary: `Verifier process-tree closure failed: ${step.command}`,
|
|
118
|
+
steps: stepOutcomes,
|
|
119
|
+
binding: executionBinding,
|
|
120
|
+
warnings: [result.cleanup.message]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
208
123
|
if (result.timedOut) {
|
|
209
124
|
return {
|
|
210
125
|
passed: false,
|
|
126
|
+
processCloseState,
|
|
211
127
|
summary: `Verification timed out: ${step.command}`,
|
|
212
128
|
steps: stepOutcomes,
|
|
213
129
|
binding: executionBinding,
|
|
@@ -220,7 +136,7 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
220
136
|
warnings.push(`Verifier never launched: ${step.command}`);
|
|
221
137
|
}
|
|
222
138
|
if (step.fastFail) {
|
|
223
|
-
return { passed: false, summary, steps: stepOutcomes, binding: executionBinding, ...(warnings.length ? { warnings } : {}) };
|
|
139
|
+
return { passed: false, processCloseState, summary, steps: stepOutcomes, binding: executionBinding, ...(warnings.length ? { warnings } : {}) };
|
|
224
140
|
}
|
|
225
141
|
failedSteps.push(step.command);
|
|
226
142
|
}
|
|
@@ -228,6 +144,7 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
228
144
|
if (failedSteps.length > 0) {
|
|
229
145
|
return {
|
|
230
146
|
passed: false,
|
|
147
|
+
processCloseState,
|
|
231
148
|
summary: `Failed steps: ${failedSteps.join(", ")}`,
|
|
232
149
|
steps: stepOutcomes,
|
|
233
150
|
binding: executionBinding,
|
|
@@ -236,30 +153,93 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
236
153
|
}
|
|
237
154
|
return {
|
|
238
155
|
passed: true,
|
|
156
|
+
processCloseState,
|
|
239
157
|
summary: `All ${String(steps.length)} verification step(s) passed.`,
|
|
240
158
|
steps: stepOutcomes,
|
|
241
159
|
binding: executionBinding,
|
|
242
160
|
...(warnings.length ? { warnings } : {})
|
|
243
161
|
};
|
|
244
162
|
}
|
|
245
|
-
|
|
163
|
+
function parseExternalOutcomeEvidenceReference(stdout) {
|
|
164
|
+
const trimmed = stdout.trim();
|
|
165
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}"))
|
|
166
|
+
return undefined;
|
|
167
|
+
try {
|
|
168
|
+
const parsed = JSON.parse(trimmed);
|
|
169
|
+
const evidence = parsed.evidence;
|
|
170
|
+
if (evidence?.kind !== "external_outcome"
|
|
171
|
+
|| typeof evidence.contractId !== "string"
|
|
172
|
+
|| typeof evidence.path !== "string"
|
|
173
|
+
|| typeof evidence.sha256 !== "string"
|
|
174
|
+
|| !/^[a-f0-9]{64}$/u.test(evidence.sha256))
|
|
175
|
+
return undefined;
|
|
176
|
+
return {
|
|
177
|
+
kind: "external_outcome",
|
|
178
|
+
contractId: evidence.contractId,
|
|
179
|
+
path: evidence.path,
|
|
180
|
+
sha256: evidence.sha256,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function combineProcessCloseState(current, next) {
|
|
188
|
+
if (current === "failed" || next === "failed")
|
|
189
|
+
return "failed";
|
|
190
|
+
if (current === "closed" || next === "closed")
|
|
191
|
+
return "closed";
|
|
192
|
+
return "not_required";
|
|
193
|
+
}
|
|
194
|
+
export async function readGitExecutionArtifacts(repoRoot, timeoutMs, spawnImpl, requestedChangedFiles) {
|
|
246
195
|
if (!resolveGitRepositoryRoot(repoRoot)) {
|
|
247
196
|
return {};
|
|
248
197
|
}
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
198
|
+
const observedChangedFiles = requestedChangedFiles
|
|
199
|
+
? [...requestedChangedFiles]
|
|
200
|
+
: await readGitChangedFiles(repoRoot, timeoutMs, spawnImpl);
|
|
201
|
+
const changedFiles = observedChangedFiles.filter(isSafeRepoRelativeGitPath);
|
|
202
|
+
const patchParts = [];
|
|
203
|
+
const numstatParts = [];
|
|
204
|
+
const nullDevice = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
205
|
+
for (const file of changedFiles) {
|
|
206
|
+
const trackedPatch = await runSubprocess("git", ["diff", "--binary", "--no-ext-diff", "HEAD", "--", file], { cwd: repoRoot, timeoutMs, spawnImpl });
|
|
207
|
+
const trackedNumstat = await runSubprocess("git", ["diff", "--numstat", "HEAD", "--", file], { cwd: repoRoot, timeoutMs, spawnImpl });
|
|
208
|
+
if (trackedPatch.exitCode === 0 && trackedPatch.stdout.length > 0) {
|
|
209
|
+
patchParts.push(trackedPatch.stdout);
|
|
210
|
+
if (trackedNumstat.exitCode === 0 && trackedNumstat.stdout.length > 0) {
|
|
211
|
+
numstatParts.push(trackedNumstat.stdout);
|
|
212
|
+
}
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
// Git does not include untracked files in `git diff HEAD`. Diff each such
|
|
216
|
+
// path against the null tree so the parent runtime receives its content,
|
|
217
|
+
// not merely a synthetic filename-only patch.
|
|
218
|
+
const untrackedPatch = await runSubprocess("git", ["diff", "--binary", "--no-ext-diff", "--no-index", "--", nullDevice, file], { cwd: repoRoot, timeoutMs, spawnImpl });
|
|
219
|
+
if ((untrackedPatch.exitCode === 0 || untrackedPatch.exitCode === 1) && untrackedPatch.stdout.length > 0) {
|
|
220
|
+
patchParts.push(untrackedPatch.stdout);
|
|
221
|
+
}
|
|
222
|
+
const untrackedNumstat = await runSubprocess("git", ["diff", "--numstat", "--no-index", "--", nullDevice, file], { cwd: repoRoot, timeoutMs, spawnImpl });
|
|
223
|
+
if ((untrackedNumstat.exitCode === 0 || untrackedNumstat.exitCode === 1) && untrackedNumstat.stdout.length > 0) {
|
|
224
|
+
numstatParts.push(untrackedNumstat.stdout);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const patch = patchParts.join("\n");
|
|
228
|
+
const numstat = numstatParts.join("\n");
|
|
229
|
+
const diffStats = numstat.length > 0 ? diffStatsFromNumstat(numstat) : undefined;
|
|
258
230
|
return {
|
|
259
231
|
...(changedFiles.length > 0 ? { changedFiles } : {}),
|
|
232
|
+
...(patch.length > 0 ? { patch } : {}),
|
|
260
233
|
...(diffStats ? { diffStats } : {})
|
|
261
234
|
};
|
|
262
235
|
}
|
|
236
|
+
function isSafeRepoRelativeGitPath(file) {
|
|
237
|
+
if (file.length === 0 || file.includes("\u0000") || isAbsolute(file)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
const segments = file.replace(/\\/gu, "/").split("/");
|
|
241
|
+
return segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
242
|
+
}
|
|
263
243
|
export async function readGitChangedFiles(repoRoot, timeoutMs, spawnImpl) {
|
|
264
244
|
if (!resolveGitRepositoryRoot(repoRoot)) {
|
|
265
245
|
return [];
|
|
@@ -16,8 +16,8 @@ export interface CodexCliAdapterOptions extends Omit<LegacyCodexCliAdapterOption
|
|
|
16
16
|
*
|
|
17
17
|
* Provider identity remains `codex` even when doctor/preflight selected an
|
|
18
18
|
* absolute native binary or npm shim. The selected binary's cached capability
|
|
19
|
-
* profile builds both the real argv and stdin transport
|
|
20
|
-
* sends only the Codex subprocess to that exact
|
|
21
|
-
* subprocesses retain
|
|
19
|
+
* profile builds both the real argv and stdin transport. The generic adapter's
|
|
20
|
+
* execution-command boundary sends only the Codex subprocess to that exact
|
|
21
|
+
* executable, while Git/verifier subprocesses retain normal OS-owned spawning.
|
|
22
22
|
*/
|
|
23
23
|
export declare function createCodexCliAdapter(options?: CodexCliAdapterOptions): import("../core/index.js").MartinAdapter;
|
|
@@ -1,26 +1,13 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { createAgentCliAdapter } from "./claude-cli.js";
|
|
3
2
|
import { buildCodexExecArgs, buildCodexStdin, isCodexAutonomyResolutionVerifiedByLaunchProbe, probeCodexCapabilities } from "./codex-launcher.js";
|
|
4
|
-
import { createSpawnPlan } from "./cli-bridge.js";
|
|
5
|
-
function createCodexSpawnRouter(input) {
|
|
6
|
-
if (input.injectedSpawn) {
|
|
7
|
-
return (command, args = [], options) => input.injectedSpawn?.(command === "codex" ? input.selectedBinary : command, args, options);
|
|
8
|
-
}
|
|
9
|
-
return (command, args = [], options) => {
|
|
10
|
-
const executable = command === "codex" ? input.selectedBinary : command;
|
|
11
|
-
const cwd = typeof options?.cwd === "string" ? options.cwd : input.workingDirectory;
|
|
12
|
-
const plan = createSpawnPlan(executable, [...args], cwd, false);
|
|
13
|
-
return spawn(plan.command, plan.args, options ?? {});
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
3
|
/**
|
|
17
4
|
* Capability-driven Codex CLI adapter.
|
|
18
5
|
*
|
|
19
6
|
* Provider identity remains `codex` even when doctor/preflight selected an
|
|
20
7
|
* absolute native binary or npm shim. The selected binary's cached capability
|
|
21
|
-
* profile builds both the real argv and stdin transport
|
|
22
|
-
* sends only the Codex subprocess to that exact
|
|
23
|
-
* subprocesses retain
|
|
8
|
+
* profile builds both the real argv and stdin transport. The generic adapter's
|
|
9
|
+
* execution-command boundary sends only the Codex subprocess to that exact
|
|
10
|
+
* executable, while Git/verifier subprocesses retain normal OS-owned spawning.
|
|
24
11
|
*/
|
|
25
12
|
export function createCodexCliAdapter(options = {}) {
|
|
26
13
|
const workingDirectory = options.workingDirectory ?? process.cwd();
|
|
@@ -39,16 +26,12 @@ export function createCodexCliAdapter(options = {}) {
|
|
|
39
26
|
const sandbox = options.sandbox ?? "workspace-write";
|
|
40
27
|
const extraArgs = options.extraArgs ?? [];
|
|
41
28
|
const launchModel = options.model;
|
|
42
|
-
const spawnImpl = createCodexSpawnRouter({
|
|
43
|
-
selectedBinary,
|
|
44
|
-
workingDirectory,
|
|
45
|
-
...(options.spawnImpl ? { injectedSpawn: options.spawnImpl } : {})
|
|
46
|
-
});
|
|
47
29
|
return createAgentCliAdapter({
|
|
48
30
|
// Keep semantic provider identity stable for usage parsing, pricing,
|
|
49
|
-
// diagnostics, and adapter metadata.
|
|
50
|
-
//
|
|
31
|
+
// diagnostics, and adapter metadata. executionCommand selects the exact
|
|
32
|
+
// binary without disguising production OS spawning as a test injection.
|
|
51
33
|
command: "codex",
|
|
34
|
+
executionCommand: selectedBinary,
|
|
52
35
|
adapterIdSuffix: "codex",
|
|
53
36
|
model: options.model,
|
|
54
37
|
label: options.label ?? "Codex CLI adapter",
|
|
@@ -58,7 +41,10 @@ export function createCodexCliAdapter(options = {}) {
|
|
|
58
41
|
providerExecutionTimeoutMs: options.providerExecutionTimeoutMs,
|
|
59
42
|
verifyTimeoutMs: options.verifyTimeoutMs,
|
|
60
43
|
supportsJsonOutput: false,
|
|
61
|
-
|
|
44
|
+
streamingUsageCap: true,
|
|
45
|
+
streamingTokenCap: true,
|
|
46
|
+
streamingUsageDetailsIncludedInTotals: true,
|
|
47
|
+
spawnImpl: options.spawnImpl,
|
|
62
48
|
argsBuilder: (prompt) => buildCodexExecArgs({
|
|
63
49
|
command: selectedBinary,
|
|
64
50
|
workingDirectory,
|