@martinloop/mcp 0.2.7 → 0.3.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 +49 -104
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/prompts.d.ts +1 -1
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +2 -2
- package/dist/server-validation.d.ts +1 -0
- package/dist/server-validation.js +8 -0
- package/dist/server.js +87 -9
- package/dist/tools/doctor.d.ts +39 -1
- package/dist/tools/doctor.js +68 -9
- package/dist/tools/eval.js +3 -2
- package/dist/tools/get-run.d.ts +3 -0
- package/dist/tools/get-run.js +3 -1
- package/dist/tools/get-verification-results.d.ts +3 -0
- package/dist/tools/get-verification-results.js +3 -1
- package/dist/tools/plan.js +4 -2
- package/dist/tools/pr-tools.js +2 -1
- package/dist/tools/preflight.d.ts +41 -1
- package/dist/tools/preflight.js +74 -19
- package/dist/tools/run-dossier.d.ts +3 -0
- package/dist/tools/run-dossier.js +5 -2
- package/dist/tools/run-loop.d.ts +7 -2
- package/dist/tools/run-loop.js +67 -35
- package/dist/tools/run-store.js +67 -15
- package/dist/tools/tool-errors.js +1 -1
- package/dist/tools/tool-support.d.ts +8 -3
- package/dist/tools/tool-support.js +61 -18
- package/dist/tools/workflow-governance.d.ts +19 -3
- package/dist/tools/workflow-governance.js +107 -55
- package/dist/vendor/adapters/claude-cli.d.ts +45 -3
- package/dist/vendor/adapters/claude-cli.js +465 -45
- package/dist/vendor/adapters/cli-bridge.d.ts +46 -0
- package/dist/vendor/adapters/cli-bridge.js +147 -38
- package/dist/vendor/adapters/codex-launcher.d.ts +76 -0
- package/dist/vendor/adapters/codex-launcher.js +538 -0
- package/dist/vendor/adapters/index.d.ts +3 -2
- package/dist/vendor/adapters/index.js +3 -2
- package/dist/vendor/adapters/openai-compatible.d.ts +19 -4
- package/dist/vendor/adapters/openai-compatible.js +50 -19
- package/dist/vendor/adapters/runtime-support.d.ts +3 -0
- package/dist/vendor/adapters/runtime-support.js +9 -1
- package/dist/vendor/adapters/stub-direct-provider.js +3 -0
- package/dist/vendor/adapters/verifier-only.d.ts +2 -0
- package/dist/vendor/adapters/verifier-only.js +11 -4
- package/dist/vendor/contracts/index.d.ts +39 -0
- package/dist/vendor/contracts/index.js +2 -0
- package/dist/vendor/core/context-integrity.js +28 -3
- package/dist/vendor/core/grounding.d.ts +1 -0
- package/dist/vendor/core/grounding.js +6 -2
- package/dist/vendor/core/index.d.ts +24 -3
- package/dist/vendor/core/index.js +113 -21
- package/dist/vendor/core/leash.js +85 -8
- package/dist/vendor/core/persistence/index.d.ts +2 -0
- package/dist/vendor/core/persistence/index.js +1 -0
- package/dist/vendor/core/persistence/integrity.d.ts +38 -0
- package/dist/vendor/core/persistence/integrity.js +248 -0
- package/dist/vendor/core/persistence/store.d.ts +7 -0
- package/dist/vendor/core/persistence/store.js +25 -1
- package/dist/vendor/core/policy.d.ts +9 -0
- package/dist/workflow-state.d.ts +9 -0
- package/dist/workflow-state.js +46 -3
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -6,16 +6,60 @@ export interface SubprocessResult {
|
|
|
6
6
|
stdout: string;
|
|
7
7
|
stderr: string;
|
|
8
8
|
timedOut: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* True when the subprocess was terminated early because its combined
|
|
11
|
+
* stdout+stderr exceeded `maxOutputBytes` — a circuit breaker against
|
|
12
|
+
* runaway agent sessions that would otherwise burn far more cost/tokens
|
|
13
|
+
* than the loop budget allows before MartinLoop can observe the final
|
|
14
|
+
* (post-hoc) usage report. See `claude-cli.ts` execute() for how this
|
|
15
|
+
* cap is derived from the remaining loop budget.
|
|
16
|
+
*/
|
|
17
|
+
outputCapped: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Set to the inspector's reason string when an `onStdoutChunk` callback
|
|
20
|
+
* requested early termination (e.g. a streaming usage/cost circuit breaker
|
|
21
|
+
* that detected the agent is on track to blow through its budget). Distinct
|
|
22
|
+
* from `outputCapped`, which fires on raw byte volume rather than parsed
|
|
23
|
+
* semantic content.
|
|
24
|
+
*/
|
|
25
|
+
terminationReason?: string;
|
|
26
|
+
launched: boolean;
|
|
9
27
|
}
|
|
10
28
|
export interface VerificationOutcome {
|
|
11
29
|
passed: boolean;
|
|
12
30
|
summary: string;
|
|
31
|
+
steps: VerificationStepOutcome[];
|
|
32
|
+
warnings?: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface VerificationStepOutcome {
|
|
35
|
+
command: string;
|
|
36
|
+
launched: boolean;
|
|
37
|
+
exitCode?: number;
|
|
38
|
+
timedOut: boolean;
|
|
39
|
+
fastFail: boolean;
|
|
40
|
+
detail?: string;
|
|
13
41
|
}
|
|
14
42
|
export declare function runSubprocess(command: string, args: string[], options: {
|
|
15
43
|
cwd: string;
|
|
16
44
|
timeoutMs: number;
|
|
17
45
|
spawnImpl?: SpawnLike;
|
|
18
46
|
stdinData?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Optional circuit breaker: terminate the subprocess once combined
|
|
49
|
+
* stdout+stderr bytes exceed this threshold, instead of waiting for
|
|
50
|
+
* natural completion. Used to bound runaway agent-CLI cost/token spend
|
|
51
|
+
* that can't otherwise be observed until the process exits.
|
|
52
|
+
*/
|
|
53
|
+
maxOutputBytes?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Optional semantic inspector invoked with each raw stdout chunk. Used to
|
|
56
|
+
* parse streaming structured output (e.g. Claude's `stream-json` usage
|
|
57
|
+
* events) and request early termination via the supplied `terminate`
|
|
58
|
+
* callback once a semantic threshold (such as cumulative cost) is
|
|
59
|
+
* crossed — well before the subprocess would exit naturally and report
|
|
60
|
+
* a runaway final usage figure.
|
|
61
|
+
*/
|
|
62
|
+
onStdoutChunk?: (chunk: Buffer, terminate: (reason: string) => void) => void;
|
|
19
63
|
}): Promise<SubprocessResult>;
|
|
20
64
|
export declare function runVerification(commands: string[], cwd: string, timeoutMs: number, verificationStack?: Array<{
|
|
21
65
|
command: string;
|
|
@@ -26,6 +70,8 @@ export declare function readGitExecutionArtifacts(repoRoot: string, timeoutMs: n
|
|
|
26
70
|
changedFiles?: string[];
|
|
27
71
|
diffStats?: ReturnType<typeof diffStatsFromNumstat>;
|
|
28
72
|
}>;
|
|
73
|
+
export declare function readGitChangedFiles(repoRoot: string, timeoutMs: number, spawnImpl?: SpawnLike): Promise<string[]>;
|
|
74
|
+
export declare function resolveGitRepositoryRoot(workingDirectory: string): string | undefined;
|
|
29
75
|
export interface SpawnPlan {
|
|
30
76
|
command: string;
|
|
31
77
|
args: string[];
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { delimiter, extname, isAbsolute, join, resolve } from "node:path";
|
|
2
|
+
import { delimiter, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { diffStatsFromNumstat } from "./runtime-support.js";
|
|
5
|
+
const gitRepositoryRootCache = new Map();
|
|
5
6
|
export async function runSubprocess(command, args, options) {
|
|
6
7
|
return new Promise((resolve) => {
|
|
7
8
|
let timedOut = false;
|
|
9
|
+
let outputCapped = false;
|
|
10
|
+
let terminationReason;
|
|
8
11
|
let settled = false;
|
|
12
|
+
let outputBytes = 0;
|
|
9
13
|
const stdoutChunks = [];
|
|
10
14
|
const stderrChunks = [];
|
|
11
15
|
const stdinMode = options.stdinData !== undefined ? "pipe" : "ignore";
|
|
@@ -14,7 +18,7 @@ export async function runSubprocess(command, args, options) {
|
|
|
14
18
|
return;
|
|
15
19
|
}
|
|
16
20
|
settled = true;
|
|
17
|
-
resolve(result);
|
|
21
|
+
resolve({ ...result, timedOut, outputCapped, ...(terminationReason ? { terminationReason } : {}) });
|
|
18
22
|
};
|
|
19
23
|
let proc;
|
|
20
24
|
try {
|
|
@@ -27,19 +31,33 @@ export async function runSubprocess(command, args, options) {
|
|
|
27
31
|
}
|
|
28
32
|
catch (error) {
|
|
29
33
|
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
-
resolveOnce({
|
|
31
|
-
exitCode: 1,
|
|
32
|
-
stdout: "",
|
|
33
|
-
stderr: message,
|
|
34
|
-
timedOut: false
|
|
35
|
-
});
|
|
34
|
+
resolveOnce({ exitCode: 1, stdout: "", stderr: message, launched: false });
|
|
36
35
|
return;
|
|
37
36
|
}
|
|
37
|
+
const trackOutput = (chunks, chunk) => {
|
|
38
|
+
chunks.push(chunk);
|
|
39
|
+
outputBytes += chunk.byteLength;
|
|
40
|
+
if (options.maxOutputBytes !== undefined &&
|
|
41
|
+
!outputCapped &&
|
|
42
|
+
!timedOut &&
|
|
43
|
+
outputBytes > options.maxOutputBytes) {
|
|
44
|
+
outputCapped = true;
|
|
45
|
+
proc.kill("SIGTERM");
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const terminateEarly = (reason) => {
|
|
49
|
+
if (terminationReason || timedOut || outputCapped) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
terminationReason = reason;
|
|
53
|
+
proc.kill("SIGTERM");
|
|
54
|
+
};
|
|
38
55
|
proc.stdout?.on("data", (chunk) => {
|
|
39
|
-
stdoutChunks
|
|
56
|
+
trackOutput(stdoutChunks, chunk);
|
|
57
|
+
options.onStdoutChunk?.(chunk, terminateEarly);
|
|
40
58
|
});
|
|
41
59
|
proc.stderr?.on("data", (chunk) => {
|
|
42
|
-
stderrChunks
|
|
60
|
+
trackOutput(stderrChunks, chunk);
|
|
43
61
|
});
|
|
44
62
|
proc.stdin?.on("error", (error) => {
|
|
45
63
|
// Some CLIs exit before consuming stdin in tests and on fast-fail paths.
|
|
@@ -55,12 +73,7 @@ export async function runSubprocess(command, args, options) {
|
|
|
55
73
|
}, options.timeoutMs);
|
|
56
74
|
proc.on("error", (error) => {
|
|
57
75
|
clearTimeout(timer);
|
|
58
|
-
resolveOnce({
|
|
59
|
-
exitCode: 1,
|
|
60
|
-
stdout: "",
|
|
61
|
-
stderr: error.message,
|
|
62
|
-
timedOut: false
|
|
63
|
-
});
|
|
76
|
+
resolveOnce({ exitCode: 1, stdout: "", stderr: error.message, launched: false });
|
|
64
77
|
});
|
|
65
78
|
proc.on("close", (code) => {
|
|
66
79
|
clearTimeout(timer);
|
|
@@ -68,7 +81,7 @@ export async function runSubprocess(command, args, options) {
|
|
|
68
81
|
exitCode: code ?? 1,
|
|
69
82
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
70
83
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
71
|
-
|
|
84
|
+
launched: true
|
|
72
85
|
});
|
|
73
86
|
});
|
|
74
87
|
if (options.stdinData !== undefined && proc.stdin) {
|
|
@@ -83,7 +96,7 @@ export async function runSubprocess(command, args, options) {
|
|
|
83
96
|
exitCode: 1,
|
|
84
97
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
85
98
|
stderr: stdinError.message,
|
|
86
|
-
|
|
99
|
+
launched: false
|
|
87
100
|
});
|
|
88
101
|
}
|
|
89
102
|
}
|
|
@@ -98,9 +111,11 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
98
111
|
}))
|
|
99
112
|
: commands.map((command) => ({ command, fastFail: true }));
|
|
100
113
|
if (steps.length === 0) {
|
|
101
|
-
return { passed: true, summary: "No verification commands specified." };
|
|
114
|
+
return { passed: true, summary: "No verification commands specified.", steps: [] };
|
|
102
115
|
}
|
|
103
116
|
const failedSteps = [];
|
|
117
|
+
const stepOutcomes = [];
|
|
118
|
+
const warnings = [];
|
|
104
119
|
for (const step of steps) {
|
|
105
120
|
const parts = splitCommand(step.command);
|
|
106
121
|
const [bin, ...args] = parts;
|
|
@@ -108,24 +123,53 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
108
123
|
continue;
|
|
109
124
|
}
|
|
110
125
|
const result = await runSubprocess(bin, args, { cwd, timeoutMs, spawnImpl });
|
|
126
|
+
const detail = truncate(result.stderr.trim() || result.stdout.trim(), 500);
|
|
127
|
+
stepOutcomes.push({
|
|
128
|
+
command: step.command,
|
|
129
|
+
launched: result.launched,
|
|
130
|
+
exitCode: result.exitCode,
|
|
131
|
+
timedOut: result.timedOut,
|
|
132
|
+
fastFail: step.fastFail,
|
|
133
|
+
...(detail ? { detail } : {})
|
|
134
|
+
});
|
|
111
135
|
if (result.timedOut) {
|
|
112
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
passed: false,
|
|
138
|
+
summary: `Verification timed out: ${step.command}`,
|
|
139
|
+
steps: stepOutcomes,
|
|
140
|
+
...(warnings.length ? { warnings } : {})
|
|
141
|
+
};
|
|
113
142
|
}
|
|
114
143
|
if (result.exitCode !== 0) {
|
|
115
|
-
const detail = truncate(result.stderr.trim() || result.stdout.trim(), 500);
|
|
116
144
|
const summary = `Verification failed: ${step.command}\n${detail}`;
|
|
145
|
+
if (!result.launched) {
|
|
146
|
+
warnings.push(`Verifier never launched: ${step.command}`);
|
|
147
|
+
}
|
|
117
148
|
if (step.fastFail) {
|
|
118
|
-
return { passed: false, summary };
|
|
149
|
+
return { passed: false, summary, steps: stepOutcomes, ...(warnings.length ? { warnings } : {}) };
|
|
119
150
|
}
|
|
120
151
|
failedSteps.push(step.command);
|
|
121
152
|
}
|
|
122
153
|
}
|
|
123
154
|
if (failedSteps.length > 0) {
|
|
124
|
-
return {
|
|
155
|
+
return {
|
|
156
|
+
passed: false,
|
|
157
|
+
summary: `Failed steps: ${failedSteps.join(", ")}`,
|
|
158
|
+
steps: stepOutcomes,
|
|
159
|
+
...(warnings.length ? { warnings } : {})
|
|
160
|
+
};
|
|
125
161
|
}
|
|
126
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
passed: true,
|
|
164
|
+
summary: `All ${String(steps.length)} verification step(s) passed.`,
|
|
165
|
+
steps: stepOutcomes,
|
|
166
|
+
...(warnings.length ? { warnings } : {})
|
|
167
|
+
};
|
|
127
168
|
}
|
|
128
169
|
export async function readGitExecutionArtifacts(repoRoot, timeoutMs, spawnImpl) {
|
|
170
|
+
if (!resolveGitRepositoryRoot(repoRoot)) {
|
|
171
|
+
return {};
|
|
172
|
+
}
|
|
129
173
|
const changedFilesResult = await runSubprocess("git", ["diff", "--name-only", "HEAD"], { cwd: repoRoot, timeoutMs, spawnImpl });
|
|
130
174
|
const numstatResult = await runSubprocess("git", ["diff", "--numstat", "HEAD"], { cwd: repoRoot, timeoutMs, spawnImpl });
|
|
131
175
|
const changedFiles = changedFilesResult.exitCode === 0
|
|
@@ -140,6 +184,49 @@ export async function readGitExecutionArtifacts(repoRoot, timeoutMs, spawnImpl)
|
|
|
140
184
|
...(diffStats ? { diffStats } : {})
|
|
141
185
|
};
|
|
142
186
|
}
|
|
187
|
+
export async function readGitChangedFiles(repoRoot, timeoutMs, spawnImpl) {
|
|
188
|
+
if (!resolveGitRepositoryRoot(repoRoot)) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
const statusResult = await runSubprocess("git", ["status", "-z", "--porcelain=v1", "--untracked-files=all", "--ignore-submodules=all", "--", "."], { cwd: repoRoot, timeoutMs, spawnImpl });
|
|
192
|
+
if (statusResult.exitCode !== 0) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
return parsePorcelainEntries(statusResult.stdout).filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
196
|
+
}
|
|
197
|
+
export function resolveGitRepositoryRoot(workingDirectory) {
|
|
198
|
+
const resolvedWorkingDirectory = resolve(workingDirectory);
|
|
199
|
+
const cached = gitRepositoryRootCache.get(resolvedWorkingDirectory);
|
|
200
|
+
if (cached !== undefined) {
|
|
201
|
+
return cached ?? undefined;
|
|
202
|
+
}
|
|
203
|
+
const visited = [];
|
|
204
|
+
let current = resolvedWorkingDirectory;
|
|
205
|
+
while (true) {
|
|
206
|
+
visited.push(current);
|
|
207
|
+
const currentCached = gitRepositoryRootCache.get(current);
|
|
208
|
+
if (currentCached !== undefined) {
|
|
209
|
+
for (const candidate of visited) {
|
|
210
|
+
gitRepositoryRootCache.set(candidate, currentCached);
|
|
211
|
+
}
|
|
212
|
+
return currentCached ?? undefined;
|
|
213
|
+
}
|
|
214
|
+
if (existsSync(resolve(current, ".git"))) {
|
|
215
|
+
for (const candidate of visited) {
|
|
216
|
+
gitRepositoryRootCache.set(candidate, current);
|
|
217
|
+
}
|
|
218
|
+
return current;
|
|
219
|
+
}
|
|
220
|
+
const parent = dirname(current);
|
|
221
|
+
if (parent === current) {
|
|
222
|
+
for (const candidate of visited) {
|
|
223
|
+
gitRepositoryRootCache.set(candidate, null);
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
current = parent;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
143
230
|
export function createSpawnPlan(command, args, cwd, preserveRawForInjectedSpawn) {
|
|
144
231
|
if (preserveRawForInjectedSpawn || process.platform !== "win32") {
|
|
145
232
|
return { command, args };
|
|
@@ -150,17 +237,22 @@ export function createSpawnPlan(command, args, cwd, preserveRawForInjectedSpawn)
|
|
|
150
237
|
// Windows can resolve the command itself — this covers cases like `pnpm` where the npm global
|
|
151
238
|
// bin directory is present in the shell PATH but not yet visible to this Node.js process.
|
|
152
239
|
if (resolvedOrUndefined === undefined) {
|
|
153
|
-
const cmdStr = [quoteWindowsCmdArg(command), ...args.map(quoteWindowsCmdArg)].join(" ");
|
|
154
240
|
return {
|
|
155
241
|
command: process.env.ComSpec || "cmd.exe",
|
|
156
|
-
args: ["/d", "/
|
|
242
|
+
args: ["/d", "/c", command, ...args]
|
|
157
243
|
};
|
|
158
244
|
}
|
|
159
245
|
const extension = extname(resolvedOrUndefined).toLowerCase();
|
|
160
246
|
if (extension === ".cmd" || extension === ".bat") {
|
|
161
247
|
return {
|
|
162
248
|
command: process.env.ComSpec || "cmd.exe",
|
|
163
|
-
args: ["/d", "/
|
|
249
|
+
args: ["/d", "/c", resolvedOrUndefined, ...args]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
if (extension === ".ps1") {
|
|
253
|
+
return {
|
|
254
|
+
command: "powershell.exe",
|
|
255
|
+
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", resolvedOrUndefined, ...args]
|
|
164
256
|
};
|
|
165
257
|
}
|
|
166
258
|
return { command: resolvedOrUndefined, args };
|
|
@@ -186,11 +278,38 @@ function expandWindowsCommandCandidates(command) {
|
|
|
186
278
|
return [command];
|
|
187
279
|
}
|
|
188
280
|
const pathExt = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
|
|
189
|
-
|
|
281
|
+
const fromPathExt = pathExt
|
|
190
282
|
.split(";")
|
|
191
283
|
.map((extension) => extension.trim())
|
|
192
284
|
.filter(Boolean)
|
|
193
285
|
.map((extension) => `${command}${extension.toLowerCase()}`);
|
|
286
|
+
const candidates = [...fromPathExt, `${command}.ps1`];
|
|
287
|
+
return Array.from(new Set(candidates));
|
|
288
|
+
}
|
|
289
|
+
function parsePorcelainEntries(stdout) {
|
|
290
|
+
const entries = stdout.split("\u0000").filter((entry) => entry.length > 0);
|
|
291
|
+
const changedFiles = [];
|
|
292
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
293
|
+
const entry = entries[index];
|
|
294
|
+
if (entry === undefined || entry.length < 4) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const status = entry.slice(0, 2);
|
|
298
|
+
const payload = entry.slice(3);
|
|
299
|
+
if (!payload) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (status.includes("R") || status.includes("C")) {
|
|
303
|
+
const renamedPath = entries[index + 1];
|
|
304
|
+
if (renamedPath && renamedPath.length > 0) {
|
|
305
|
+
changedFiles.push(renamedPath);
|
|
306
|
+
index += 1;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
changedFiles.push(payload);
|
|
311
|
+
}
|
|
312
|
+
return changedFiles;
|
|
194
313
|
}
|
|
195
314
|
function windowsPathDirectories() {
|
|
196
315
|
const rawPath = process.env.Path ?? process.env.PATH ?? "";
|
|
@@ -199,16 +318,6 @@ function windowsPathDirectories() {
|
|
|
199
318
|
.map((entry) => entry.trim().replace(/^"|"$/g, ""))
|
|
200
319
|
.filter(Boolean);
|
|
201
320
|
}
|
|
202
|
-
function quoteWindowsCmdArg(value) {
|
|
203
|
-
const normalized = value.replace(/\r?\n/gu, " ");
|
|
204
|
-
const escaped = normalized
|
|
205
|
-
.replace(/\^/gu, "^^")
|
|
206
|
-
.replace(/"/gu, '^"')
|
|
207
|
-
.replace(/%/gu, "%%")
|
|
208
|
-
.replace(/!/gu, "^^!")
|
|
209
|
-
.replace(/[&|<>()]/gu, (match) => `^${match}`);
|
|
210
|
-
return `"${escaped}"`;
|
|
211
|
-
}
|
|
212
321
|
export function splitCommand(command) {
|
|
213
322
|
const tokens = [];
|
|
214
323
|
let current = "";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
export interface CliCommandAvailability {
|
|
3
|
+
command: string;
|
|
4
|
+
available: boolean;
|
|
5
|
+
locator: string;
|
|
6
|
+
detail: string;
|
|
7
|
+
resolvedPath?: string;
|
|
8
|
+
candidatePaths?: string[];
|
|
9
|
+
}
|
|
10
|
+
export type CodexHostPlatform = "windows" | "linux" | "wsl" | "macos";
|
|
11
|
+
export type CodexInstallKind = "missing" | "native" | "windows_shim" | "windows_mounted_path";
|
|
12
|
+
export type CodexInvocationMode = "direct" | "cmd_shell" | "powershell";
|
|
13
|
+
export interface CodexHostDiagnosis {
|
|
14
|
+
hostPlatform: CodexHostPlatform;
|
|
15
|
+
nativeInstallValid: boolean;
|
|
16
|
+
installKind: CodexInstallKind;
|
|
17
|
+
invocationMode: CodexInvocationMode;
|
|
18
|
+
sandboxMode: "workspace-write";
|
|
19
|
+
sandboxCompatible: boolean;
|
|
20
|
+
resolvedPath?: string;
|
|
21
|
+
nativeDependencyStatus?: "unknown" | "missing";
|
|
22
|
+
nativeDependencyPackage?: string;
|
|
23
|
+
warnings: string[];
|
|
24
|
+
remediation?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface CodexLaunchProbeResult {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
summary: string;
|
|
29
|
+
availability: CliCommandAvailability;
|
|
30
|
+
diagnosis: CodexHostDiagnosis;
|
|
31
|
+
command: string;
|
|
32
|
+
args: string[];
|
|
33
|
+
exitCode?: number;
|
|
34
|
+
stdout?: string;
|
|
35
|
+
stderr?: string;
|
|
36
|
+
candidateProbeResults?: CodexProbeCandidateResult[];
|
|
37
|
+
}
|
|
38
|
+
export interface CodexExecArgsOptions {
|
|
39
|
+
workingDirectory: string;
|
|
40
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access";
|
|
41
|
+
model?: string;
|
|
42
|
+
extraArgs?: string[];
|
|
43
|
+
mode?: "prompt" | "probe";
|
|
44
|
+
}
|
|
45
|
+
type SpawnSyncLike = typeof spawnSync;
|
|
46
|
+
export interface CodexProbeCandidateResult {
|
|
47
|
+
path: string;
|
|
48
|
+
installKind: CodexInstallKind;
|
|
49
|
+
invocationMode: CodexInvocationMode;
|
|
50
|
+
nativeInstallValid: boolean;
|
|
51
|
+
sandboxCompatible: boolean;
|
|
52
|
+
launchReady: boolean;
|
|
53
|
+
summary: string;
|
|
54
|
+
remediation?: string;
|
|
55
|
+
nativeDependencyStatus?: "unknown" | "missing";
|
|
56
|
+
nativeDependencyPackage?: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function buildCodexExecArgs(options: CodexExecArgsOptions): string[];
|
|
59
|
+
export declare function resolveCliCommandAvailability(command: string, options?: {
|
|
60
|
+
platform?: NodeJS.Platform;
|
|
61
|
+
env?: NodeJS.ProcessEnv;
|
|
62
|
+
spawnSyncImpl?: SpawnSyncLike;
|
|
63
|
+
}): CliCommandAvailability;
|
|
64
|
+
export declare function detectCodexHostPlatform(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): CodexHostPlatform;
|
|
65
|
+
export declare function diagnoseCodexHost(availability: CliCommandAvailability, options?: {
|
|
66
|
+
env?: NodeJS.ProcessEnv;
|
|
67
|
+
platform?: NodeJS.Platform;
|
|
68
|
+
}): CodexHostDiagnosis;
|
|
69
|
+
export declare function probeCodexLaunch(input: {
|
|
70
|
+
workingDirectory: string;
|
|
71
|
+
availability?: CliCommandAvailability;
|
|
72
|
+
env?: NodeJS.ProcessEnv;
|
|
73
|
+
platform?: NodeJS.Platform;
|
|
74
|
+
spawnSyncImpl?: SpawnSyncLike;
|
|
75
|
+
}): CodexLaunchProbeResult;
|
|
76
|
+
export {};
|