@martinloop/mcp 0.5.1 → 0.5.5

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.
Files changed (45) hide show
  1. package/README.md +52 -14
  2. package/dist/server-validation.js +2 -0
  3. package/dist/server.js +30 -3
  4. package/dist/tools/run-loop.d.ts +1 -0
  5. package/dist/tools/run-loop.js +25 -4
  6. package/dist/tools/tool-response.d.ts +9 -1
  7. package/dist/tools/tool-response.js +7 -5
  8. package/dist/tools/tool-support.js +62 -271
  9. package/dist/vendor/adapters/claude-cli.d.ts +9 -0
  10. package/dist/vendor/adapters/claude-cli.js +19 -2
  11. package/dist/vendor/adapters/codex-capabilities.d.ts +69 -0
  12. package/dist/vendor/adapters/codex-capabilities.js +265 -0
  13. package/dist/vendor/adapters/codex-cli.d.ts +23 -0
  14. package/dist/vendor/adapters/codex-cli.js +72 -0
  15. package/dist/vendor/adapters/codex-host.d.ts +107 -0
  16. package/dist/vendor/adapters/codex-host.js +595 -0
  17. package/dist/vendor/adapters/codex-launcher.d.ts +7 -135
  18. package/dist/vendor/adapters/codex-launcher.js +17 -766
  19. package/dist/vendor/adapters/index.d.ts +4 -2
  20. package/dist/vendor/adapters/index.js +4 -2
  21. package/dist/vendor/contracts/agent-execution.d.ts +9 -0
  22. package/dist/vendor/contracts/agent-execution.js +14 -0
  23. package/dist/vendor/contracts/index.d.ts +9 -0
  24. package/dist/vendor/contracts/index.js +1 -0
  25. package/dist/vendor/core/grounding.js +42 -7
  26. package/dist/vendor/core/index.d.ts +5 -2
  27. package/dist/vendor/core/index.js +12 -3
  28. package/dist/vendor/presentation/governed-plan.d.ts +33 -0
  29. package/dist/vendor/presentation/governed-plan.js +146 -0
  30. package/dist/vendor/presentation/index.d.ts +7 -0
  31. package/dist/vendor/presentation/index.js +7 -0
  32. package/dist/vendor/presentation/motion.d.ts +12 -0
  33. package/dist/vendor/presentation/motion.js +123 -0
  34. package/dist/vendor/presentation/status.d.ts +8 -0
  35. package/dist/vendor/presentation/status.js +11 -0
  36. package/dist/vendor/presentation/table.d.ts +11 -0
  37. package/dist/vendor/presentation/table.js +47 -0
  38. package/dist/vendor/presentation/text.d.ts +6 -0
  39. package/dist/vendor/presentation/text.js +30 -0
  40. package/dist/vendor/presentation/theme.d.ts +31 -0
  41. package/dist/vendor/presentation/theme.js +82 -0
  42. package/dist/vendor/presentation/verified-handoff.d.ts +15 -0
  43. package/dist/vendor/presentation/verified-handoff.js +151 -0
  44. package/package.json +3 -2
  45. package/server.json +2 -2
@@ -0,0 +1,595 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { dirname, extname, join, resolve } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
6
+ import { createSpawnPlan, resolveNpmShimScript } from "./cli-bridge.js";
7
+ import { buildCodexExecArgs, buildCodexStdin, cacheCodexCapabilityProfile, probeCodexCapabilities, resolveCodexAutonomyCandidates } from "./codex-capabilities.js";
8
+ const launchProbeCache = new Map();
9
+ export function probeFilesystemWriteCapability(directory) {
10
+ try {
11
+ mkdirSync(directory, { recursive: true });
12
+ }
13
+ catch (error) {
14
+ return {
15
+ writable: false,
16
+ reason: `Could not create directory ${directory}: ${error instanceof Error ? error.message : String(error)}`
17
+ };
18
+ }
19
+ let tempDir;
20
+ try {
21
+ tempDir = mkdtempSync(join(directory, ".ml-write-probe-"));
22
+ const tempFile = join(tempDir, "capability.tmp");
23
+ writeFileSync(tempFile, "\x01", { encoding: "binary", flag: "wx" });
24
+ unlinkSync(tempFile);
25
+ return { writable: true };
26
+ }
27
+ catch (error) {
28
+ return { writable: false, reason: error instanceof Error ? error.message : String(error) };
29
+ }
30
+ finally {
31
+ if (tempDir) {
32
+ try {
33
+ rmSync(tempDir, { recursive: true, force: true });
34
+ }
35
+ catch {
36
+ // best effort cleanup
37
+ }
38
+ }
39
+ }
40
+ }
41
+ export function checkCodexSandboxPreflight(input) {
42
+ const directory = resolve(input.workingDirectory);
43
+ if (input.requestedSandbox === "read-only") {
44
+ return {
45
+ ok: true,
46
+ effectiveSandbox: "read-only",
47
+ capabilitySource: "probe",
48
+ writableRoot: directory
49
+ };
50
+ }
51
+ const result = probeFilesystemWriteCapability(directory);
52
+ if (result.writable) {
53
+ return {
54
+ ok: true,
55
+ effectiveSandbox: "workspace-write",
56
+ capabilitySource: "probe",
57
+ writableRoot: directory
58
+ };
59
+ }
60
+ return {
61
+ ok: false,
62
+ code: "provider_sandbox_read_only",
63
+ requestedCapability: "workspace-write",
64
+ detectedCapability: "read-only",
65
+ effectiveSandbox: "read-only",
66
+ affectedPath: directory,
67
+ writableRoot: directory,
68
+ capabilitySource: "probe",
69
+ remediation: `The working directory ${directory} is not writable by the current process. ` +
70
+ "Launch MartinLoop in a session with write access to that directory, or use `--sandbox read-only` for inspection-only work."
71
+ };
72
+ }
73
+ function normalizeCandidates(lines) {
74
+ return [...new Set(lines.map((line) => line.trim()).filter(Boolean))];
75
+ }
76
+ function readLocatorCandidates(command, platform, spawnSyncImpl) {
77
+ const locator = platform === "win32" ? "where.exe" : "which";
78
+ const result = spawnSyncImpl(locator, [command], {
79
+ encoding: "utf8",
80
+ stdio: ["ignore", "pipe", "pipe"]
81
+ });
82
+ return {
83
+ locator,
84
+ candidates: result.status === 0 ? normalizeCandidates((result.stdout ?? "").split(/\r?\n/u)) : []
85
+ };
86
+ }
87
+ function discoverWindowsDesktopCodexCandidates(env) {
88
+ const localAppData = env.LOCALAPPDATA;
89
+ if (!localAppData)
90
+ return [];
91
+ const base = join(localAppData, "OpenAI", "Codex", "bin");
92
+ if (!existsSync(base))
93
+ return [];
94
+ const found = [];
95
+ const direct = join(base, "codex.exe");
96
+ if (existsSync(direct))
97
+ found.push({ path: direct, mtimeMs: statSync(direct).mtimeMs });
98
+ for (const entry of readdirSync(base, { withFileTypes: true })) {
99
+ if (!entry.isDirectory())
100
+ continue;
101
+ const candidate = join(base, entry.name, "codex.exe");
102
+ if (existsSync(candidate))
103
+ found.push({ path: candidate, mtimeMs: statSync(candidate).mtimeMs });
104
+ }
105
+ found.sort((a, b) => b.mtimeMs - a.mtimeMs);
106
+ return normalizeCandidates(found.map((item) => item.path));
107
+ }
108
+ export function resolveCliCommandAvailability(command, options = {}) {
109
+ const platform = options.platform ?? process.platform;
110
+ const env = options.env ?? process.env;
111
+ const spawnSyncImpl = options.spawnSyncImpl ?? spawnSync;
112
+ const discovery = readLocatorCandidates(command, platform, spawnSyncImpl);
113
+ if (discovery.candidates.length > 0) {
114
+ return {
115
+ command,
116
+ available: true,
117
+ locator: discovery.locator,
118
+ detail: `${command} is available on PATH.`,
119
+ resolvedPath: discovery.candidates[0],
120
+ candidatePaths: discovery.candidates
121
+ };
122
+ }
123
+ const offPath = discoverCommandOffPath(command, platform, env);
124
+ if (offPath) {
125
+ return {
126
+ command,
127
+ available: true,
128
+ locator: "off-path-discovery",
129
+ detail: `${command} found at ${offPath} (not on PATH, auto-discovered).`,
130
+ resolvedPath: offPath,
131
+ candidatePaths: [offPath]
132
+ };
133
+ }
134
+ return {
135
+ command,
136
+ available: false,
137
+ locator: discovery.locator,
138
+ detail: `${command} is not installed. ${suggestInstall(command)}`
139
+ };
140
+ }
141
+ function discoverCommandOffPath(command, platform, env) {
142
+ const home = env.HOME ?? env.USERPROFILE ?? "";
143
+ const directories = [];
144
+ if (platform === "win32") {
145
+ if (env.APPDATA)
146
+ directories.push(join(env.APPDATA, "npm"));
147
+ if (env.LOCALAPPDATA)
148
+ directories.push(join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin"));
149
+ if (home)
150
+ directories.push(join(home, "scoop", "shims"));
151
+ }
152
+ else {
153
+ directories.push("/usr/local/bin", "/opt/homebrew/bin");
154
+ if (home) {
155
+ directories.push(join(home, ".local", "bin"), join(home, ".npm-global", "bin"), join(home, ".bun", "bin"), join(home, ".cargo", "bin"));
156
+ }
157
+ if (env.NVM_DIR)
158
+ directories.push(join(env.NVM_DIR, "current", "bin"));
159
+ }
160
+ const extensions = platform === "win32"
161
+ ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((item) => item.trim().toLowerCase()).filter(Boolean)
162
+ : [""];
163
+ for (const directory of directories) {
164
+ for (const extension of extensions) {
165
+ const candidate = extension ? join(directory, `${command}${extension}`) : join(directory, command);
166
+ if (existsSync(candidate))
167
+ return candidate;
168
+ }
169
+ }
170
+ return undefined;
171
+ }
172
+ function suggestInstall(command) {
173
+ const installs = {
174
+ claude: "Install with: npm install -g @anthropic-ai/claude-code",
175
+ codex: "Install with: npm install -g @openai/codex",
176
+ gemini: "Install with: npm install -g @google/gemini-cli"
177
+ };
178
+ return installs[command] ?? `Install ${command} and ensure it is available.`;
179
+ }
180
+ export function detectCodexHostPlatform(env = process.env, platform = process.platform) {
181
+ if (platform === "win32")
182
+ return "windows";
183
+ if (platform === "darwin")
184
+ return "macos";
185
+ if (env.WSL_DISTRO_NAME || env.WSL_INTEROP)
186
+ return "wsl";
187
+ return "linux";
188
+ }
189
+ function detectInstallKind(path, host) {
190
+ if (!path)
191
+ return "missing";
192
+ const normalized = path.replace(/\\/gu, "/").toLowerCase();
193
+ if ((host === "linux" || host === "wsl") && normalized.startsWith("/mnt/c/"))
194
+ return "windows_mounted_path";
195
+ if (/\.(cmd|bat|ps1)$/iu.test(normalized) || normalized.includes("/appdata/roaming/npm/"))
196
+ return "windows_shim";
197
+ return "native";
198
+ }
199
+ function detectInvocationMode(path, platform) {
200
+ if (platform !== "win32" || !path)
201
+ return "direct";
202
+ const extension = extname(path).toLowerCase();
203
+ if (extension === ".ps1")
204
+ return "powershell";
205
+ if (extension === ".cmd" || extension === ".bat")
206
+ return "cmd_shell";
207
+ return "direct";
208
+ }
209
+ export function diagnoseCodexHost(availability, options = {}) {
210
+ const platform = options.platform ?? process.platform;
211
+ const hostPlatform = detectCodexHostPlatform(options.env ?? process.env, platform);
212
+ const resolvedPath = availability.resolvedPath;
213
+ const installKind = detectInstallKind(resolvedPath, hostPlatform);
214
+ const invocationMode = detectInvocationMode(resolvedPath, platform);
215
+ if (!availability.available) {
216
+ return {
217
+ hostPlatform,
218
+ nativeInstallValid: false,
219
+ installKind,
220
+ invocationMode,
221
+ sandboxMode: "workspace-write",
222
+ sandboxCompatible: false,
223
+ warnings: [],
224
+ remediation: "Install or expose the Codex CLI on PATH before running governed Codex work."
225
+ };
226
+ }
227
+ if ((hostPlatform === "linux" || hostPlatform === "wsl") && installKind === "windows_mounted_path") {
228
+ return {
229
+ hostPlatform,
230
+ nativeInstallValid: false,
231
+ installKind,
232
+ invocationMode,
233
+ sandboxMode: "workspace-write",
234
+ sandboxCompatible: false,
235
+ resolvedPath,
236
+ warnings: ["Codex resolves to a Windows-hosted install from Linux/WSL."],
237
+ remediation: "Install Codex natively inside this Linux/WSL environment before governed work."
238
+ };
239
+ }
240
+ return {
241
+ hostPlatform,
242
+ nativeInstallValid: true,
243
+ installKind,
244
+ invocationMode,
245
+ sandboxMode: "workspace-write",
246
+ sandboxCompatible: true,
247
+ resolvedPath,
248
+ ...(hostPlatform === "linux" || hostPlatform === "wsl" ? { nativeDependencyStatus: "unknown" } : {}),
249
+ warnings: []
250
+ };
251
+ }
252
+ function candidatePreference(path, diagnosis, platform) {
253
+ const base = diagnosis.installKind === "native" ? 0 : diagnosis.installKind === "windows_shim" ? 20 : 30;
254
+ if (platform !== "win32" || diagnosis.installKind !== "windows_shim")
255
+ return base;
256
+ return base + (/\.(cmd|bat|ps1)$/iu.test(path) ? 0 : 1);
257
+ }
258
+ function buildCandidates(input) {
259
+ const pathCandidates = normalizeCandidates(input.availability.candidatePaths ?? [input.availability.resolvedPath ?? input.availability.command]);
260
+ const desktop = input.platform === "win32" && input.includeDesktopCandidates
261
+ ? discoverWindowsDesktopCodexCandidates(input.env).filter((path) => !pathCandidates.includes(path))
262
+ : [];
263
+ return [...pathCandidates, ...desktop]
264
+ .map((path, discoveryIndex) => {
265
+ const diagnosis = diagnoseCodexHost({ ...input.availability, resolvedPath: path }, { env: input.env, platform: input.platform });
266
+ return {
267
+ path,
268
+ diagnosis,
269
+ preference: candidatePreference(path, diagnosis, input.platform),
270
+ discoveryIndex
271
+ };
272
+ })
273
+ .sort((a, b) => a.preference === b.preference ? a.discoveryIndex - b.discoveryIndex : a.preference - b.preference);
274
+ }
275
+ function isInsideGitRepository(workingDirectory) {
276
+ let current = resolve(workingDirectory);
277
+ while (true) {
278
+ if (existsSync(resolve(current, ".git")))
279
+ return true;
280
+ const parent = dirname(current);
281
+ if (parent === current)
282
+ return false;
283
+ current = parent;
284
+ }
285
+ }
286
+ function buildInjectedSpawnPlan(binaryPath, args, platform) {
287
+ if (platform !== "win32")
288
+ return { command: binaryPath, args, invocationMode: "direct" };
289
+ const extension = extname(binaryPath).toLowerCase();
290
+ if (extension === ".cmd" || extension === ".bat" || extension === ".ps1") {
291
+ const script = resolveNpmShimScript(binaryPath);
292
+ if (script)
293
+ return { command: process.execPath, args: [script, ...args], invocationMode: "direct" };
294
+ if (extension === ".ps1") {
295
+ return {
296
+ command: "powershell.exe",
297
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", binaryPath, ...args],
298
+ invocationMode: "powershell"
299
+ };
300
+ }
301
+ return {
302
+ command: process.env.ComSpec || "cmd.exe",
303
+ args: ["/d", "/c", binaryPath, ...args],
304
+ invocationMode: "cmd_shell"
305
+ };
306
+ }
307
+ return { command: binaryPath, args, invocationMode: "direct" };
308
+ }
309
+ function buildMarkerPrompt(marker, outsideMarkerPath) {
310
+ const command = `node -e "require('node:fs').writeFileSync(process.argv[1],'MARTIN_CODEX_WRITE_OK')" ${marker}`;
311
+ const outsideCommand = `node -e "require('node:fs').writeFileSync(process.argv[1],'MARTIN_CODEX_OUTSIDE_BAD')" "${outsideMarkerPath}"`;
312
+ return [
313
+ "You are validating MartinLoop Codex host readiness.",
314
+ "Do not modify tracked files.",
315
+ "First attempt this outside-workspace command and confirm that it is denied:",
316
+ outsideCommand,
317
+ "Then use the shell command executor for the allowed workspace marker.",
318
+ `Run exactly: ${command}`,
319
+ "Do not use MCP tools or alternate tools.",
320
+ "After it succeeds, reply READY."
321
+ ].join("\n");
322
+ }
323
+ function classifyFailure(stderr, stdout, diagnosis) {
324
+ const combined = `${stderr}\n${stdout}`;
325
+ if (/@openai\/codex-linux-x64|cannot find module ['"]@openai\/codex-linux-x64['"]/iu.test(combined)) {
326
+ return {
327
+ ...diagnosis,
328
+ nativeInstallValid: false,
329
+ sandboxCompatible: false,
330
+ nativeDependencyStatus: "missing",
331
+ nativeDependencyPackage: "@openai/codex-linux-x64",
332
+ warnings: [...diagnosis.warnings, "Codex native Linux package is missing."],
333
+ remediation: "Reinstall Codex natively in this Linux/WSL environment."
334
+ };
335
+ }
336
+ if (/CreateProcessAsUserW failed:\s*5|windows sandbox: runner error/iu.test(combined)) {
337
+ return {
338
+ ...diagnosis,
339
+ sandboxCompatible: false,
340
+ warnings: [...diagnosis.warnings, "This Codex invocation could not create a writable subprocess on Windows."],
341
+ remediation: "Use another capability strategy advertised by this exact Codex binary, or repair the host sandbox."
342
+ };
343
+ }
344
+ return diagnosis;
345
+ }
346
+ function probeCacheKey(input) {
347
+ return JSON.stringify({
348
+ workingDirectory: resolve(input.workingDirectory),
349
+ platform: input.platform,
350
+ candidatePaths: input.candidatePaths,
351
+ model: input.model
352
+ });
353
+ }
354
+ export function probeCodexLaunch(input) {
355
+ const availability = input.availability ?? resolveCliCommandAvailability("codex", {
356
+ ...(input.platform ? { platform: input.platform } : {}),
357
+ ...(input.env ? { env: input.env } : {}),
358
+ ...(input.spawnSyncImpl ? { spawnSyncImpl: input.spawnSyncImpl } : {})
359
+ });
360
+ const diagnosis = diagnoseCodexHost(availability, {
361
+ ...(input.platform ? { platform: input.platform } : {}),
362
+ ...(input.env ? { env: input.env } : {})
363
+ });
364
+ if (!availability.available) {
365
+ return { ok: false, summary: availability.detail, availability, diagnosis, command: availability.command, args: [] };
366
+ }
367
+ if (!isInsideGitRepository(input.workingDirectory)) {
368
+ return {
369
+ ok: false,
370
+ summary: "Working directory is not inside a git repository. Codex exec requires a trusted repository for governed work.",
371
+ availability,
372
+ diagnosis,
373
+ command: availability.resolvedPath ?? availability.command,
374
+ args: []
375
+ };
376
+ }
377
+ const platform = input.platform ?? process.platform;
378
+ const env = input.env ?? process.env;
379
+ const spawnSyncImpl = input.spawnSyncImpl ?? spawnSync;
380
+ const providerExecutionTimeoutMs = normalizeProviderExecutionTimeoutMs(input.providerExecutionTimeoutMs);
381
+ const candidates = buildCandidates({
382
+ availability,
383
+ env,
384
+ platform,
385
+ includeDesktopCandidates: input.spawnSyncImpl === undefined
386
+ });
387
+ const candidatePaths = candidates.map((candidate) => candidate.path);
388
+ const key = probeCacheKey({
389
+ workingDirectory: input.workingDirectory,
390
+ platform,
391
+ candidatePaths,
392
+ ...(input.model ? { model: input.model } : {})
393
+ });
394
+ if (input.spawnSyncImpl === undefined) {
395
+ const cached = launchProbeCache.get(key);
396
+ if (cached)
397
+ return cached;
398
+ }
399
+ const candidateResults = [];
400
+ let selected;
401
+ let lastFailure;
402
+ candidateLoop: for (const candidate of candidates) {
403
+ if (!candidate.diagnosis.nativeInstallValid) {
404
+ candidateResults.push({
405
+ path: candidate.path,
406
+ installKind: candidate.diagnosis.installKind,
407
+ invocationMode: candidate.diagnosis.invocationMode,
408
+ nativeInstallValid: false,
409
+ sandboxCompatible: false,
410
+ launchReady: false,
411
+ summary: candidate.diagnosis.remediation ?? "Codex installation is not valid for this host."
412
+ });
413
+ continue;
414
+ }
415
+ const profile = probeCodexCapabilities(candidate.path, {
416
+ platform,
417
+ spawnSyncImpl,
418
+ cache: input.spawnSyncImpl === undefined
419
+ });
420
+ if (!profile.supportsExec) {
421
+ candidateResults.push({
422
+ path: candidate.path,
423
+ installKind: candidate.diagnosis.installKind,
424
+ invocationMode: candidate.diagnosis.invocationMode,
425
+ nativeInstallValid: true,
426
+ sandboxCompatible: false,
427
+ launchReady: false,
428
+ summary: "Resolved Codex binary does not advertise a usable exec subcommand.",
429
+ capabilityProfile: profile
430
+ });
431
+ continue;
432
+ }
433
+ const transports = profile.promptTransports?.length ? profile.promptTransports : [profile.promptTransport];
434
+ const resolutions = resolveCodexAutonomyCandidates(profile);
435
+ let candidateSummary = "No advertised Codex invocation strategy proved writable execution.";
436
+ for (const resolution of resolutions) {
437
+ for (const transport of transports) {
438
+ const marker = `.martin-codex-write-probe-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`;
439
+ const markerPath = join(input.workingDirectory, marker);
440
+ const outsideRoot = mkdtempSync(join(tmpdir(), "martin-codex-outside-probe-"));
441
+ const outsideMarkerPath = join(outsideRoot, ".martin-codex-outside-probe.tmp");
442
+ const prompt = buildMarkerPrompt(marker, outsideMarkerPath);
443
+ let args;
444
+ try {
445
+ args = buildCodexExecArgs({
446
+ command: candidate.path,
447
+ workingDirectory: input.workingDirectory,
448
+ sandbox: "workspace-write",
449
+ ...(input.model ? { model: input.model } : {}),
450
+ mode: "probe",
451
+ prompt,
452
+ capabilityProfile: profile,
453
+ promptTransport: transport,
454
+ writeStrategy: resolution.strategy,
455
+ autonomyResolution: resolution
456
+ });
457
+ }
458
+ catch (error) {
459
+ rmSync(outsideRoot, { recursive: true, force: true });
460
+ candidateSummary = error instanceof Error ? error.message : String(error);
461
+ lastFailure = { candidate, profile, args: [], summary: candidateSummary };
462
+ continue;
463
+ }
464
+ const plan = input.spawnSyncImpl
465
+ ? buildInjectedSpawnPlan(candidate.path, args, platform)
466
+ : { ...createSpawnPlan(candidate.path, args, input.workingDirectory, false), invocationMode: "direct" };
467
+ const stdin = buildCodexStdin(profile, prompt, transport);
468
+ const result = spawnSyncImpl(plan.command, plan.args, {
469
+ cwd: input.workingDirectory,
470
+ encoding: "utf8",
471
+ stdio: [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
472
+ ...(stdin !== undefined ? { input: stdin } : {}),
473
+ timeout: providerExecutionTimeoutMs
474
+ });
475
+ let markerVerified = false;
476
+ let outsideDenied = false;
477
+ try {
478
+ markerVerified = existsSync(markerPath) && readFileSync(markerPath, "utf8") === "MARTIN_CODEX_WRITE_OK";
479
+ outsideDenied = !existsSync(outsideMarkerPath);
480
+ }
481
+ finally {
482
+ try {
483
+ unlinkSync(markerPath);
484
+ }
485
+ catch {
486
+ // best effort cleanup
487
+ }
488
+ rmSync(outsideRoot, { recursive: true, force: true });
489
+ }
490
+ if (!result.error && result.status === 0 && markerVerified && outsideDenied) {
491
+ const negotiated = cacheCodexCapabilityProfile({ ...profile, promptTransport: transport }, platform);
492
+ selected = {
493
+ candidate: {
494
+ ...candidate,
495
+ diagnosis: { ...candidate.diagnosis, invocationMode: plan.invocationMode }
496
+ },
497
+ profile: negotiated,
498
+ args,
499
+ resolution,
500
+ transport,
501
+ ...(result.status === null ? {} : { exitCode: result.status }),
502
+ stdout: result.stdout ?? "",
503
+ stderr: result.stderr ?? ""
504
+ };
505
+ candidateResults.push({
506
+ path: candidate.path,
507
+ installKind: candidate.diagnosis.installKind,
508
+ invocationMode: plan.invocationMode,
509
+ nativeInstallValid: true,
510
+ sandboxCompatible: true,
511
+ launchReady: true,
512
+ summary: `Codex capability negotiation passed with ${resolution.strategy} / ${transport}.`,
513
+ capabilityProfile: negotiated,
514
+ writeStrategy: resolution.strategy,
515
+ autonomyResolution: resolution,
516
+ promptTransport: transport
517
+ });
518
+ break candidateLoop;
519
+ }
520
+ const failureDiagnosis = classifyFailure(result.stderr ?? "", result.stdout ?? "", candidate.diagnosis);
521
+ candidateSummary = result.error
522
+ ? `Codex launch probe failed: ${result.error.message}`
523
+ : result.status !== 0
524
+ ? `Codex launch probe exited non-zero: ${(result.stderr ?? result.stdout ?? "").trim() || String(result.status)}`
525
+ : !outsideDenied
526
+ ? `Codex ${resolution.strategy} / ${transport} escaped the workspace boundary.`
527
+ : `Codex ${resolution.strategy} / ${transport} invocation did not prove workspace writes.`;
528
+ lastFailure = {
529
+ candidate: { ...candidate, diagnosis: failureDiagnosis },
530
+ profile,
531
+ args,
532
+ summary: candidateSummary,
533
+ ...(result.status === null ? {} : { exitCode: result.status }),
534
+ stdout: result.stdout ?? "",
535
+ stderr: result.stderr ?? ""
536
+ };
537
+ }
538
+ }
539
+ candidateResults.push({
540
+ path: candidate.path,
541
+ installKind: candidate.diagnosis.installKind,
542
+ invocationMode: candidate.diagnosis.invocationMode,
543
+ nativeInstallValid: candidate.diagnosis.nativeInstallValid,
544
+ sandboxCompatible: false,
545
+ launchReady: false,
546
+ summary: candidateSummary,
547
+ capabilityProfile: profile,
548
+ ...(candidate.diagnosis.remediation ? { remediation: candidate.diagnosis.remediation } : {})
549
+ });
550
+ }
551
+ if (selected) {
552
+ const result = {
553
+ ok: true,
554
+ summary: `Codex capability-driven workspace-write probe passed using ${selected.resolution.strategy} / ${selected.transport}.`,
555
+ availability: { ...availability, resolvedPath: selected.candidate.path, candidatePaths },
556
+ diagnosis: { ...selected.candidate.diagnosis, resolvedPath: selected.candidate.path },
557
+ command: selected.candidate.path,
558
+ args: selected.args,
559
+ capabilityProfile: selected.profile,
560
+ writeStrategy: selected.resolution.strategy,
561
+ autonomyResolution: selected.resolution,
562
+ promptTransport: selected.transport,
563
+ ...(selected.exitCode === undefined ? {} : { exitCode: selected.exitCode }),
564
+ ...(selected.stdout === undefined ? {} : { stdout: selected.stdout }),
565
+ ...(selected.stderr === undefined ? {} : { stderr: selected.stderr }),
566
+ candidateProbeResults: candidateResults
567
+ };
568
+ if (input.spawnSyncImpl === undefined)
569
+ launchProbeCache.set(key, result);
570
+ return result;
571
+ }
572
+ const failed = lastFailure;
573
+ const result = {
574
+ ok: false,
575
+ summary: failed?.summary ?? diagnosis.remediation ?? "No Codex candidate proved governed writable execution.",
576
+ availability: {
577
+ ...availability,
578
+ ...(failed ? { resolvedPath: failed.candidate.path } : {}),
579
+ ...(candidatePaths.length ? { candidatePaths } : {})
580
+ },
581
+ diagnosis: failed
582
+ ? { ...failed.candidate.diagnosis, resolvedPath: failed.candidate.path }
583
+ : diagnosis,
584
+ command: failed?.candidate.path ?? availability.resolvedPath ?? availability.command,
585
+ args: failed?.args ?? [],
586
+ ...(failed?.profile ? { capabilityProfile: failed.profile } : {}),
587
+ ...(failed?.exitCode === undefined ? {} : { exitCode: failed.exitCode }),
588
+ ...(failed?.stdout === undefined ? {} : { stdout: failed.stdout }),
589
+ ...(failed?.stderr === undefined ? {} : { stderr: failed.stderr }),
590
+ candidateProbeResults: candidateResults
591
+ };
592
+ if (input.spawnSyncImpl === undefined)
593
+ launchProbeCache.set(key, result);
594
+ return result;
595
+ }