@martinloop/mcp 0.3.0 → 0.3.2
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 +5 -4
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/server-validation.js +2 -2
- package/dist/server.js +72 -10
- package/dist/tools/doctor.d.ts +27 -0
- package/dist/tools/doctor.js +39 -11
- package/dist/tools/get-run.d.ts +2 -1
- package/dist/tools/get-run.js +1 -0
- package/dist/tools/get-verification-results.d.ts +2 -1
- package/dist/tools/get-verification-results.js +1 -0
- package/dist/tools/plan.js +4 -2
- package/dist/tools/preflight.d.ts +27 -0
- package/dist/tools/preflight.js +44 -20
- package/dist/tools/run-dossier.d.ts +2 -1
- package/dist/tools/run-dossier.js +1 -0
- package/dist/tools/run-loop.d.ts +5 -1
- package/dist/tools/run-loop.js +20 -8
- package/dist/tools/run-store.js +67 -15
- package/dist/tools/tool-support.d.ts +2 -0
- package/dist/tools/tool-support.js +49 -13
- 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 +20 -3
- package/dist/vendor/adapters/claude-cli.js +193 -33
- package/dist/vendor/adapters/cli-bridge.d.ts +45 -0
- package/dist/vendor/adapters/cli-bridge.js +107 -39
- package/dist/vendor/adapters/codex-launcher.d.ts +32 -0
- package/dist/vendor/adapters/codex-launcher.js +409 -118
- package/dist/vendor/adapters/openai-compatible.js +8 -2
- package/dist/vendor/adapters/runtime-support.js +1 -0
- 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 +9 -3
- package/dist/vendor/contracts/index.d.ts +2 -1
- package/dist/vendor/contracts/index.js +14 -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 +1 -0
- package/dist/vendor/core/index.js +25 -6
- package/dist/vendor/core/leash.js +90 -8
- package/dist/vendor/core/persistence/integrity.d.ts +1 -1
- package/dist/vendor/core/persistence/integrity.js +15 -6
- package/dist/workflow-state.d.ts +9 -0
- package/dist/workflow-state.js +44 -3
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -5,11 +5,21 @@ export interface CliCommandAvailability {
|
|
|
5
5
|
locator: string;
|
|
6
6
|
detail: string;
|
|
7
7
|
resolvedPath?: string;
|
|
8
|
+
candidatePaths?: string[];
|
|
8
9
|
}
|
|
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";
|
|
10
13
|
export interface CodexHostDiagnosis {
|
|
11
14
|
hostPlatform: CodexHostPlatform;
|
|
12
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;
|
|
13
23
|
warnings: string[];
|
|
14
24
|
remediation?: string;
|
|
15
25
|
}
|
|
@@ -23,10 +33,32 @@ export interface CodexLaunchProbeResult {
|
|
|
23
33
|
exitCode?: number;
|
|
24
34
|
stdout?: string;
|
|
25
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";
|
|
26
44
|
}
|
|
27
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[];
|
|
28
59
|
export declare function resolveCliCommandAvailability(command: string, options?: {
|
|
29
60
|
platform?: NodeJS.Platform;
|
|
61
|
+
env?: NodeJS.ProcessEnv;
|
|
30
62
|
spawnSyncImpl?: SpawnSyncLike;
|
|
31
63
|
}): CliCommandAvailability;
|
|
32
64
|
export declare function detectCodexHostPlatform(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): CodexHostPlatform;
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { dirname, extname, resolve } from "node:path";
|
|
2
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { dirname, extname, join, resolve } from "node:path";
|
|
4
|
+
const codexLaunchProbeCache = new Map();
|
|
5
|
+
const CODEX_LAUNCH_PROBE_PROMPT = [
|
|
6
|
+
"You are validating MartinLoop Codex host readiness.",
|
|
7
|
+
"Do not edit files.",
|
|
8
|
+
"Use the shell command executor exactly once to run: git status --short -- .",
|
|
9
|
+
"Do not use MCP tools, Node REPL, or any fallback tool if shell execution fails.",
|
|
10
|
+
"If the shell command succeeds, reply with READY only.",
|
|
11
|
+
"If it fails, reply with the exact failure in one sentence."
|
|
12
|
+
].join("\n");
|
|
13
|
+
function buildProbeCacheKey(input) {
|
|
14
|
+
return JSON.stringify({
|
|
15
|
+
workingDirectory: resolve(input.workingDirectory),
|
|
16
|
+
platform: input.platform,
|
|
17
|
+
candidatePaths: input.candidatePaths
|
|
18
|
+
});
|
|
19
|
+
}
|
|
4
20
|
function isInsideGitRepository(workingDirectory) {
|
|
5
21
|
let current = resolve(workingDirectory);
|
|
6
22
|
while (true) {
|
|
@@ -14,50 +30,90 @@ function isInsideGitRepository(workingDirectory) {
|
|
|
14
30
|
current = parent;
|
|
15
31
|
}
|
|
16
32
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return 1;
|
|
33
|
-
case ".ps1":
|
|
34
|
-
return 2;
|
|
35
|
-
case ".exe":
|
|
36
|
-
return 3;
|
|
37
|
-
case "":
|
|
38
|
-
return 4;
|
|
39
|
-
default:
|
|
40
|
-
return 5;
|
|
41
|
-
}
|
|
33
|
+
function normalizeCandidates(lines) {
|
|
34
|
+
return [...new Set(lines.map((line) => line.trim()).filter(Boolean))];
|
|
35
|
+
}
|
|
36
|
+
function readLocatorCandidates(command, platform, spawnSyncImpl) {
|
|
37
|
+
const locator = platform === "win32" ? "where.exe" : "which";
|
|
38
|
+
const result = spawnSyncImpl(locator, [command], {
|
|
39
|
+
encoding: "utf8",
|
|
40
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
locator,
|
|
44
|
+
candidates: result.status === 0
|
|
45
|
+
? normalizeCandidates((result.stdout ?? "").split(/\r?\n/u))
|
|
46
|
+
: [],
|
|
47
|
+
foundOnPath: result.status === 0
|
|
42
48
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
}
|
|
50
|
+
function discoverWindowsDesktopCodexCandidates(env) {
|
|
51
|
+
const localAppData = env["LOCALAPPDATA"];
|
|
52
|
+
if (!localAppData) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const baseDirectory = join(localAppData, "OpenAI", "Codex", "bin");
|
|
56
|
+
if (!existsSync(baseDirectory)) {
|
|
57
|
+
return [];
|
|
46
58
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
const candidates = [];
|
|
60
|
+
const directCandidate = join(baseDirectory, "codex.exe");
|
|
61
|
+
if (existsSync(directCandidate)) {
|
|
62
|
+
candidates.push({ path: directCandidate, mtimeMs: statSync(directCandidate).mtimeMs });
|
|
63
|
+
}
|
|
64
|
+
for (const entry of readdirSync(baseDirectory, { withFileTypes: true })) {
|
|
65
|
+
if (!entry.isDirectory()) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const candidate = join(baseDirectory, entry.name, "codex.exe");
|
|
69
|
+
if (!existsSync(candidate)) {
|
|
70
|
+
continue;
|
|
54
71
|
}
|
|
72
|
+
candidates.push({ path: candidate, mtimeMs: statSync(candidate).mtimeMs });
|
|
73
|
+
}
|
|
74
|
+
candidates.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
75
|
+
return normalizeCandidates(candidates.map((candidate) => candidate.path));
|
|
76
|
+
}
|
|
77
|
+
function codexProbePreference(diagnosis) {
|
|
78
|
+
if (diagnosis.installKind === "native" && diagnosis.invocationMode === "direct") {
|
|
79
|
+
return 0;
|
|
55
80
|
}
|
|
56
|
-
|
|
81
|
+
if (diagnosis.installKind === "native") {
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
if (diagnosis.installKind === "windows_shim") {
|
|
85
|
+
return 2;
|
|
86
|
+
}
|
|
87
|
+
return 3;
|
|
88
|
+
}
|
|
89
|
+
function buildProbeCandidates(input) {
|
|
90
|
+
const pathCandidates = normalizeCandidates(input.availability.candidatePaths ?? [input.availability.resolvedPath ?? input.availability.command]);
|
|
91
|
+
const desktopCandidates = input.platform === "win32" && input.includeDesktopCandidates
|
|
92
|
+
? discoverWindowsDesktopCodexCandidates(input.env).filter((candidatePath) => !pathCandidates.includes(candidatePath))
|
|
93
|
+
: [];
|
|
94
|
+
return [...pathCandidates, ...desktopCandidates]
|
|
95
|
+
.map((path, discoveryIndex) => {
|
|
96
|
+
const diagnosis = diagnoseCodexHost({
|
|
97
|
+
...input.availability,
|
|
98
|
+
resolvedPath: path
|
|
99
|
+
}, {
|
|
100
|
+
env: input.env,
|
|
101
|
+
platform: input.platform
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
path,
|
|
105
|
+
diagnosis,
|
|
106
|
+
preference: input.platform === "win32" ? codexProbePreference(diagnosis) : 0,
|
|
107
|
+
discoveryIndex
|
|
108
|
+
};
|
|
109
|
+
})
|
|
110
|
+
.sort((left, right) => left.preference === right.preference
|
|
111
|
+
? left.discoveryIndex - right.discoveryIndex
|
|
112
|
+
: left.preference - right.preference);
|
|
57
113
|
}
|
|
58
114
|
function buildProbeCommand(command, args, platform) {
|
|
59
115
|
if (platform !== "win32") {
|
|
60
|
-
return { command, args };
|
|
116
|
+
return { command, args, invocationMode: "direct" };
|
|
61
117
|
}
|
|
62
118
|
const extension = extname(command).toLowerCase();
|
|
63
119
|
switch (extension) {
|
|
@@ -65,40 +121,149 @@ function buildProbeCommand(command, args, platform) {
|
|
|
65
121
|
case ".bat":
|
|
66
122
|
return {
|
|
67
123
|
command: process.env.ComSpec || "cmd.exe",
|
|
68
|
-
args: ["/d", "/c", command, ...args]
|
|
124
|
+
args: ["/d", "/c", command, ...args],
|
|
125
|
+
invocationMode: "cmd_shell"
|
|
69
126
|
};
|
|
70
127
|
case ".ps1":
|
|
71
128
|
return {
|
|
72
129
|
command: "powershell.exe",
|
|
73
|
-
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command, ...args]
|
|
130
|
+
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command, ...args],
|
|
131
|
+
invocationMode: "powershell"
|
|
74
132
|
};
|
|
75
133
|
default:
|
|
76
|
-
return { command, args };
|
|
134
|
+
return { command, args, invocationMode: "direct" };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function detectInstallKind(resolvedPath, hostPlatform) {
|
|
138
|
+
if (!resolvedPath) {
|
|
139
|
+
return "missing";
|
|
140
|
+
}
|
|
141
|
+
const normalizedPath = resolvedPath.replace(/\\/gu, "/").toLowerCase();
|
|
142
|
+
const looksWindowsShim = normalizedPath.endsWith(".cmd") ||
|
|
143
|
+
normalizedPath.endsWith(".bat") ||
|
|
144
|
+
normalizedPath.endsWith(".ps1") ||
|
|
145
|
+
normalizedPath.includes("/appdata/roaming/npm/");
|
|
146
|
+
const looksMountedWindowsPath = normalizedPath.startsWith("/mnt/c/");
|
|
147
|
+
if ((hostPlatform === "linux" || hostPlatform === "wsl") && looksMountedWindowsPath) {
|
|
148
|
+
return "windows_mounted_path";
|
|
77
149
|
}
|
|
150
|
+
if (looksWindowsShim) {
|
|
151
|
+
return "windows_shim";
|
|
152
|
+
}
|
|
153
|
+
return "native";
|
|
154
|
+
}
|
|
155
|
+
function detectInvocationMode(resolvedPath, platform) {
|
|
156
|
+
if (platform !== "win32" || !resolvedPath) {
|
|
157
|
+
return "direct";
|
|
158
|
+
}
|
|
159
|
+
const extension = extname(resolvedPath).toLowerCase();
|
|
160
|
+
if (extension === ".ps1") {
|
|
161
|
+
return "powershell";
|
|
162
|
+
}
|
|
163
|
+
if (extension === ".cmd" || extension === ".bat") {
|
|
164
|
+
return "cmd_shell";
|
|
165
|
+
}
|
|
166
|
+
return "direct";
|
|
167
|
+
}
|
|
168
|
+
function classifyProbeFailure(stderr, stdout, diagnosis) {
|
|
169
|
+
const combined = `${stderr}\n${stdout}`;
|
|
170
|
+
if (/@openai\/codex-linux-x64/iu.test(combined) ||
|
|
171
|
+
/cannot find module ['"]@openai\/codex-linux-x64['"]/iu.test(combined)) {
|
|
172
|
+
const warnings = [...diagnosis.warnings];
|
|
173
|
+
const missingDependencyWarning = "Codex is missing the native Linux package '@openai/codex-linux-x64' required for this host.";
|
|
174
|
+
if (!warnings.includes(missingDependencyWarning)) {
|
|
175
|
+
warnings.push(missingDependencyWarning);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
summary: "Codex native dependency '@openai/codex-linux-x64' is missing for this Linux/WSL environment.",
|
|
179
|
+
diagnosis: {
|
|
180
|
+
...diagnosis,
|
|
181
|
+
nativeInstallValid: false,
|
|
182
|
+
sandboxCompatible: false,
|
|
183
|
+
warnings,
|
|
184
|
+
nativeDependencyStatus: "missing",
|
|
185
|
+
nativeDependencyPackage: "@openai/codex-linux-x64",
|
|
186
|
+
remediation: "Reinstall Codex natively inside this Linux/WSL environment so the '@openai/codex-linux-x64' package is present before running governed Codex work."
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (/CreateProcessAsUserW failed:\s*5/iu.test(combined) ||
|
|
191
|
+
/windows sandbox: runner error: CreateProcessAsUserW failed:\s*5/iu.test(combined) ||
|
|
192
|
+
/spawn setup refresh/iu.test(combined)) {
|
|
193
|
+
const warnings = [...diagnosis.warnings];
|
|
194
|
+
const sandboxFailureWarning = "Codex workspace-write sandbox could not launch subprocesses on this Windows host.";
|
|
195
|
+
if (!warnings.includes(sandboxFailureWarning)) {
|
|
196
|
+
warnings.push(sandboxFailureWarning);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
summary: "Codex workspace-write sandbox could not launch subprocesses on this Windows host.",
|
|
200
|
+
diagnosis: {
|
|
201
|
+
...diagnosis,
|
|
202
|
+
sandboxCompatible: false,
|
|
203
|
+
warnings,
|
|
204
|
+
remediation: "Update or reinstall Codex on this Windows host until `codex exec --sandbox workspace-write` can launch a simple shell command before running governed Codex work."
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {};
|
|
209
|
+
}
|
|
210
|
+
export function buildCodexExecArgs(options) {
|
|
211
|
+
const sandbox = options.sandbox ?? "workspace-write";
|
|
212
|
+
const modelArgs = options.model ? ["--model", options.model] : [];
|
|
213
|
+
const extraArgs = options.extraArgs ?? [];
|
|
214
|
+
return [
|
|
215
|
+
"exec",
|
|
216
|
+
"--cd",
|
|
217
|
+
options.workingDirectory,
|
|
218
|
+
"--sandbox",
|
|
219
|
+
sandbox,
|
|
220
|
+
"--json",
|
|
221
|
+
"--color",
|
|
222
|
+
"never",
|
|
223
|
+
...modelArgs,
|
|
224
|
+
...extraArgs,
|
|
225
|
+
"-"
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
function parseCodexProbeEvents(stdout) {
|
|
229
|
+
return stdout
|
|
230
|
+
.split(/\r?\n/u)
|
|
231
|
+
.map((line) => line.trim())
|
|
232
|
+
.filter(Boolean)
|
|
233
|
+
.map((line) => {
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(line);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
.filter((event) => event !== undefined);
|
|
242
|
+
}
|
|
243
|
+
function hasSuccessfulCommandExecution(events) {
|
|
244
|
+
return events.some((event) => event.type === "item.completed" &&
|
|
245
|
+
event.item?.type === "command_execution" &&
|
|
246
|
+
event.item?.status === "completed" &&
|
|
247
|
+
event.item?.exit_code === 0);
|
|
78
248
|
}
|
|
79
249
|
export function resolveCliCommandAvailability(command, options = {}) {
|
|
80
250
|
const platform = options.platform ?? process.platform;
|
|
81
251
|
const spawnSyncImpl = options.spawnSyncImpl ?? spawnSync;
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
86
|
-
});
|
|
87
|
-
const resolvedPath = result.status === 0
|
|
88
|
-
? selectResolvedPath((result.stdout ?? "").split(/\r?\n/u), platform)
|
|
89
|
-
: undefined;
|
|
90
|
-
return result.status === 0
|
|
252
|
+
const discovery = readLocatorCandidates(command, platform, spawnSyncImpl);
|
|
253
|
+
const resolvedPath = discovery.candidates[0];
|
|
254
|
+
return discovery.candidates.length > 0
|
|
91
255
|
? {
|
|
92
256
|
command,
|
|
93
257
|
available: true,
|
|
94
|
-
locator,
|
|
258
|
+
locator: discovery.locator,
|
|
95
259
|
detail: `${command} is available on PATH.`,
|
|
96
|
-
...(resolvedPath ? { resolvedPath } : {})
|
|
260
|
+
...(resolvedPath ? { resolvedPath } : {}),
|
|
261
|
+
candidatePaths: discovery.candidates
|
|
97
262
|
}
|
|
98
263
|
: {
|
|
99
264
|
command,
|
|
100
265
|
available: false,
|
|
101
|
-
locator,
|
|
266
|
+
locator: discovery.locator,
|
|
102
267
|
detail: `${command} is not available on PATH.`
|
|
103
268
|
};
|
|
104
269
|
}
|
|
@@ -116,26 +281,34 @@ export function detectCodexHostPlatform(env = process.env, platform = process.pl
|
|
|
116
281
|
}
|
|
117
282
|
export function diagnoseCodexHost(availability, options = {}) {
|
|
118
283
|
const hostPlatform = detectCodexHostPlatform(options.env ?? process.env, options.platform ?? process.platform);
|
|
284
|
+
const resolvedPath = availability.resolvedPath;
|
|
285
|
+
const installKind = detectInstallKind(resolvedPath, hostPlatform);
|
|
286
|
+
const invocationMode = detectInvocationMode(resolvedPath, options.platform ?? process.platform);
|
|
119
287
|
const warnings = [];
|
|
120
288
|
if (!availability.available) {
|
|
121
289
|
return {
|
|
122
290
|
hostPlatform,
|
|
123
291
|
nativeInstallValid: false,
|
|
292
|
+
installKind,
|
|
293
|
+
invocationMode,
|
|
294
|
+
sandboxMode: "workspace-write",
|
|
295
|
+
sandboxCompatible: false,
|
|
296
|
+
...(resolvedPath ? { resolvedPath } : {}),
|
|
124
297
|
warnings,
|
|
125
298
|
remediation: "Install or expose the Codex CLI on PATH before running governed Codex work."
|
|
126
299
|
};
|
|
127
300
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
resolvedPath.endsWith(".bat") ||
|
|
131
|
-
resolvedPath.endsWith(".ps1") ||
|
|
132
|
-
resolvedPath.includes("/appdata/roaming/npm/");
|
|
133
|
-
const looksMountedWindowsPath = resolvedPath.startsWith("/mnt/c/");
|
|
134
|
-
if ((hostPlatform === "linux" || hostPlatform === "wsl") && (looksWindowsShim || looksMountedWindowsPath)) {
|
|
301
|
+
if ((hostPlatform === "linux" || hostPlatform === "wsl") &&
|
|
302
|
+
(installKind === "windows_shim" || installKind === "windows_mounted_path")) {
|
|
135
303
|
warnings.push("Codex resolves to a Windows-hosted install from a Linux/WSL environment.");
|
|
136
304
|
return {
|
|
137
305
|
hostPlatform,
|
|
138
306
|
nativeInstallValid: false,
|
|
307
|
+
installKind,
|
|
308
|
+
invocationMode,
|
|
309
|
+
sandboxMode: "workspace-write",
|
|
310
|
+
sandboxCompatible: false,
|
|
311
|
+
...(resolvedPath ? { resolvedPath } : {}),
|
|
139
312
|
warnings,
|
|
140
313
|
remediation: "Install Codex natively inside this Linux/WSL environment instead of relying on a Windows PATH shim."
|
|
141
314
|
};
|
|
@@ -143,6 +316,14 @@ export function diagnoseCodexHost(availability, options = {}) {
|
|
|
143
316
|
return {
|
|
144
317
|
hostPlatform,
|
|
145
318
|
nativeInstallValid: true,
|
|
319
|
+
installKind,
|
|
320
|
+
invocationMode,
|
|
321
|
+
sandboxMode: "workspace-write",
|
|
322
|
+
sandboxCompatible: true,
|
|
323
|
+
...(resolvedPath ? { resolvedPath } : {}),
|
|
324
|
+
...(hostPlatform === "linux" || hostPlatform === "wsl"
|
|
325
|
+
? { nativeDependencyStatus: "unknown" }
|
|
326
|
+
: {}),
|
|
146
327
|
warnings
|
|
147
328
|
};
|
|
148
329
|
}
|
|
@@ -156,17 +337,10 @@ export function probeCodexLaunch(input) {
|
|
|
156
337
|
env: input.env,
|
|
157
338
|
platform: input.platform
|
|
158
339
|
});
|
|
159
|
-
const args =
|
|
160
|
-
|
|
161
|
-
"
|
|
162
|
-
|
|
163
|
-
"--sandbox",
|
|
164
|
-
"workspace-write",
|
|
165
|
-
"--json",
|
|
166
|
-
"--color",
|
|
167
|
-
"never",
|
|
168
|
-
"--help"
|
|
169
|
-
];
|
|
340
|
+
const args = buildCodexExecArgs({
|
|
341
|
+
workingDirectory: input.workingDirectory,
|
|
342
|
+
mode: "probe"
|
|
343
|
+
});
|
|
170
344
|
if (!availability.available) {
|
|
171
345
|
return {
|
|
172
346
|
ok: false,
|
|
@@ -177,16 +351,6 @@ export function probeCodexLaunch(input) {
|
|
|
177
351
|
args
|
|
178
352
|
};
|
|
179
353
|
}
|
|
180
|
-
if (!diagnosis.nativeInstallValid) {
|
|
181
|
-
return {
|
|
182
|
-
ok: false,
|
|
183
|
-
summary: diagnosis.remediation ?? "Codex host installation is not valid for this environment.",
|
|
184
|
-
availability,
|
|
185
|
-
diagnosis,
|
|
186
|
-
command: availability.resolvedPath ?? availability.command,
|
|
187
|
-
args
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
354
|
if (!isInsideGitRepository(input.workingDirectory)) {
|
|
191
355
|
return {
|
|
192
356
|
ok: false,
|
|
@@ -199,49 +363,176 @@ export function probeCodexLaunch(input) {
|
|
|
199
363
|
}
|
|
200
364
|
const spawnSyncImpl = input.spawnSyncImpl ?? spawnSync;
|
|
201
365
|
const platform = input.platform ?? process.platform;
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
366
|
+
const env = input.env ?? process.env;
|
|
367
|
+
const probeCandidates = buildProbeCandidates({
|
|
368
|
+
availability,
|
|
369
|
+
env,
|
|
370
|
+
platform,
|
|
371
|
+
includeDesktopCandidates: input.spawnSyncImpl === undefined
|
|
207
372
|
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
373
|
+
const candidatePaths = probeCandidates.map((candidate) => candidate.path);
|
|
374
|
+
const cacheKey = buildProbeCacheKey({
|
|
375
|
+
workingDirectory: input.workingDirectory,
|
|
376
|
+
platform,
|
|
377
|
+
candidatePaths
|
|
378
|
+
});
|
|
379
|
+
if (input.spawnSyncImpl === undefined) {
|
|
380
|
+
const cached = codexLaunchProbeCache.get(cacheKey);
|
|
381
|
+
if (cached) {
|
|
382
|
+
return cached;
|
|
383
|
+
}
|
|
219
384
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
385
|
+
const candidateResults = [];
|
|
386
|
+
const probeCandidatePath = (candidate) => {
|
|
387
|
+
const candidateDiagnosis = candidate.diagnosis;
|
|
388
|
+
if (!candidateDiagnosis.nativeInstallValid) {
|
|
389
|
+
candidateResults.push({
|
|
390
|
+
path: candidate.path,
|
|
391
|
+
diagnosis: candidateDiagnosis,
|
|
392
|
+
ok: false,
|
|
393
|
+
summary: candidateDiagnosis.remediation ?? "Codex host installation is not valid for this environment."
|
|
394
|
+
});
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
const spawnPlan = buildProbeCommand(candidate.path, args, platform);
|
|
398
|
+
const result = spawnSyncImpl(spawnPlan.command, spawnPlan.args, {
|
|
399
|
+
cwd: input.workingDirectory,
|
|
400
|
+
encoding: "utf8",
|
|
401
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
402
|
+
input: CODEX_LAUNCH_PROBE_PROMPT
|
|
403
|
+
});
|
|
404
|
+
const probedDiagnosis = {
|
|
405
|
+
...candidateDiagnosis,
|
|
406
|
+
invocationMode: spawnPlan.invocationMode
|
|
407
|
+
};
|
|
408
|
+
if (result.error) {
|
|
409
|
+
const classifiedFailure = classifyProbeFailure(result.stderr ?? "", result.stdout ?? "", probedDiagnosis);
|
|
410
|
+
candidateResults.push({
|
|
411
|
+
path: candidate.path,
|
|
412
|
+
diagnosis: classifiedFailure.diagnosis ?? probedDiagnosis,
|
|
413
|
+
ok: false,
|
|
414
|
+
summary: classifiedFailure.summary ?? `Codex launch probe failed: ${result.error.message}`,
|
|
415
|
+
stderr: result.stderr ?? "",
|
|
416
|
+
stdout: result.stdout ?? ""
|
|
417
|
+
});
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
if (result.status !== 0) {
|
|
421
|
+
const stderr = (result.stderr ?? "").trim();
|
|
422
|
+
const classifiedFailure = classifyProbeFailure(result.stderr ?? "", result.stdout ?? "", probedDiagnosis);
|
|
423
|
+
candidateResults.push({
|
|
424
|
+
path: candidate.path,
|
|
425
|
+
diagnosis: classifiedFailure.diagnosis ?? probedDiagnosis,
|
|
426
|
+
ok: false,
|
|
427
|
+
summary: classifiedFailure.summary ??
|
|
428
|
+
(stderr.length > 0 ? `Codex launch probe failed: ${stderr}` : "Codex launch probe exited non-zero."),
|
|
429
|
+
exitCode: result.status ?? undefined,
|
|
430
|
+
stderr: result.stderr ?? "",
|
|
431
|
+
stdout: result.stdout ?? ""
|
|
432
|
+
});
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
const events = parseCodexProbeEvents(result.stdout ?? "");
|
|
436
|
+
if (!hasSuccessfulCommandExecution(events)) {
|
|
437
|
+
const classifiedFailure = classifyProbeFailure(result.stderr ?? "", result.stdout ?? "", probedDiagnosis);
|
|
438
|
+
candidateResults.push({
|
|
439
|
+
path: candidate.path,
|
|
440
|
+
diagnosis: classifiedFailure.diagnosis ?? probedDiagnosis,
|
|
441
|
+
ok: false,
|
|
442
|
+
summary: classifiedFailure.summary ?? "Codex launch probe did not complete a shell command successfully.",
|
|
443
|
+
exitCode: result.status ?? undefined,
|
|
444
|
+
stderr: result.stderr ?? "",
|
|
445
|
+
stdout: result.stdout ?? ""
|
|
446
|
+
});
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
candidateResults.push({
|
|
450
|
+
path: candidate.path,
|
|
451
|
+
diagnosis: probedDiagnosis,
|
|
452
|
+
ok: true,
|
|
453
|
+
summary: "Codex exec prompt-and-shell probe passed for the current MartinLoop invocation shape.",
|
|
231
454
|
exitCode: result.status ?? undefined,
|
|
232
455
|
stderr: result.stderr ?? "",
|
|
233
456
|
stdout: result.stdout ?? ""
|
|
457
|
+
});
|
|
458
|
+
return true;
|
|
459
|
+
};
|
|
460
|
+
for (const candidate of probeCandidates) {
|
|
461
|
+
if (probeCandidatePath(candidate)) {
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const successfulCandidates = candidateResults.filter((candidate) => candidate.ok);
|
|
466
|
+
const selectedCandidate = successfulCandidates[0];
|
|
467
|
+
const candidateProbeResults = candidateResults.map((candidate) => ({
|
|
468
|
+
path: candidate.path,
|
|
469
|
+
installKind: candidate.diagnosis.installKind,
|
|
470
|
+
invocationMode: candidate.diagnosis.invocationMode,
|
|
471
|
+
nativeInstallValid: candidate.diagnosis.nativeInstallValid,
|
|
472
|
+
sandboxCompatible: candidate.diagnosis.sandboxCompatible,
|
|
473
|
+
launchReady: candidate.ok,
|
|
474
|
+
summary: candidate.summary,
|
|
475
|
+
...(candidate.diagnosis.remediation ? { remediation: candidate.diagnosis.remediation } : {}),
|
|
476
|
+
...(candidate.diagnosis.nativeDependencyStatus
|
|
477
|
+
? { nativeDependencyStatus: candidate.diagnosis.nativeDependencyStatus }
|
|
478
|
+
: {}),
|
|
479
|
+
...(candidate.diagnosis.nativeDependencyPackage
|
|
480
|
+
? { nativeDependencyPackage: candidate.diagnosis.nativeDependencyPackage }
|
|
481
|
+
: {})
|
|
482
|
+
}));
|
|
483
|
+
if (selectedCandidate) {
|
|
484
|
+
const successResult = {
|
|
485
|
+
ok: true,
|
|
486
|
+
summary: selectedCandidate.summary,
|
|
487
|
+
availability: {
|
|
488
|
+
...availability,
|
|
489
|
+
resolvedPath: selectedCandidate.path,
|
|
490
|
+
candidatePaths
|
|
491
|
+
},
|
|
492
|
+
diagnosis: {
|
|
493
|
+
...selectedCandidate.diagnosis,
|
|
494
|
+
resolvedPath: selectedCandidate.path
|
|
495
|
+
},
|
|
496
|
+
command: selectedCandidate.path,
|
|
497
|
+
args,
|
|
498
|
+
exitCode: selectedCandidate.exitCode,
|
|
499
|
+
stderr: selectedCandidate.stderr,
|
|
500
|
+
stdout: selectedCandidate.stdout,
|
|
501
|
+
candidateProbeResults
|
|
234
502
|
};
|
|
503
|
+
if (input.spawnSyncImpl === undefined) {
|
|
504
|
+
codexLaunchProbeCache.set(cacheKey, successResult);
|
|
505
|
+
}
|
|
506
|
+
return successResult;
|
|
235
507
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
508
|
+
const bestFailure = platform === "win32"
|
|
509
|
+
? candidateResults.find((candidate) => candidate.diagnosis.nativeInstallValid && candidate.diagnosis.installKind === "native") ??
|
|
510
|
+
candidateResults.find((candidate) => candidate.diagnosis.nativeInstallValid) ??
|
|
511
|
+
candidateResults[0]
|
|
512
|
+
: candidateResults[0];
|
|
513
|
+
const failureResult = {
|
|
514
|
+
ok: false,
|
|
515
|
+
summary: bestFailure?.summary ?? diagnosis.remediation ?? "Codex launch probe failed.",
|
|
516
|
+
availability: {
|
|
517
|
+
...availability,
|
|
518
|
+
...(bestFailure ? { resolvedPath: bestFailure.path } : {}),
|
|
519
|
+
...(candidatePaths.length ? { candidatePaths } : {})
|
|
520
|
+
},
|
|
521
|
+
diagnosis: bestFailure
|
|
522
|
+
? {
|
|
523
|
+
...bestFailure.diagnosis,
|
|
524
|
+
resolvedPath: bestFailure.path
|
|
525
|
+
}
|
|
526
|
+
: diagnosis,
|
|
527
|
+
command: bestFailure?.path ?? availability.resolvedPath ?? availability.command,
|
|
242
528
|
args,
|
|
243
|
-
exitCode:
|
|
244
|
-
stderr:
|
|
245
|
-
stdout:
|
|
529
|
+
exitCode: bestFailure?.exitCode,
|
|
530
|
+
stderr: bestFailure?.stderr,
|
|
531
|
+
stdout: bestFailure?.stdout,
|
|
532
|
+
candidateProbeResults
|
|
246
533
|
};
|
|
534
|
+
if (input.spawnSyncImpl === undefined) {
|
|
535
|
+
codexLaunchProbeCache.set(cacheKey, failureResult);
|
|
536
|
+
}
|
|
537
|
+
return failureResult;
|
|
247
538
|
}
|