@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.
Files changed (64) hide show
  1. package/README.md +49 -104
  2. package/dist/package-version.d.ts +1 -1
  3. package/dist/package-version.js +1 -1
  4. package/dist/prompts.d.ts +1 -1
  5. package/dist/resources.d.ts +1 -1
  6. package/dist/resources.js +2 -2
  7. package/dist/server-validation.d.ts +1 -0
  8. package/dist/server-validation.js +8 -0
  9. package/dist/server.js +87 -9
  10. package/dist/tools/doctor.d.ts +39 -1
  11. package/dist/tools/doctor.js +68 -9
  12. package/dist/tools/eval.js +3 -2
  13. package/dist/tools/get-run.d.ts +3 -0
  14. package/dist/tools/get-run.js +3 -1
  15. package/dist/tools/get-verification-results.d.ts +3 -0
  16. package/dist/tools/get-verification-results.js +3 -1
  17. package/dist/tools/plan.js +4 -2
  18. package/dist/tools/pr-tools.js +2 -1
  19. package/dist/tools/preflight.d.ts +41 -1
  20. package/dist/tools/preflight.js +74 -19
  21. package/dist/tools/run-dossier.d.ts +3 -0
  22. package/dist/tools/run-dossier.js +5 -2
  23. package/dist/tools/run-loop.d.ts +7 -2
  24. package/dist/tools/run-loop.js +67 -35
  25. package/dist/tools/run-store.js +67 -15
  26. package/dist/tools/tool-errors.js +1 -1
  27. package/dist/tools/tool-support.d.ts +8 -3
  28. package/dist/tools/tool-support.js +61 -18
  29. package/dist/tools/workflow-governance.d.ts +19 -3
  30. package/dist/tools/workflow-governance.js +107 -55
  31. package/dist/vendor/adapters/claude-cli.d.ts +45 -3
  32. package/dist/vendor/adapters/claude-cli.js +465 -45
  33. package/dist/vendor/adapters/cli-bridge.d.ts +46 -0
  34. package/dist/vendor/adapters/cli-bridge.js +147 -38
  35. package/dist/vendor/adapters/codex-launcher.d.ts +76 -0
  36. package/dist/vendor/adapters/codex-launcher.js +538 -0
  37. package/dist/vendor/adapters/index.d.ts +3 -2
  38. package/dist/vendor/adapters/index.js +3 -2
  39. package/dist/vendor/adapters/openai-compatible.d.ts +19 -4
  40. package/dist/vendor/adapters/openai-compatible.js +50 -19
  41. package/dist/vendor/adapters/runtime-support.d.ts +3 -0
  42. package/dist/vendor/adapters/runtime-support.js +9 -1
  43. package/dist/vendor/adapters/stub-direct-provider.js +3 -0
  44. package/dist/vendor/adapters/verifier-only.d.ts +2 -0
  45. package/dist/vendor/adapters/verifier-only.js +11 -4
  46. package/dist/vendor/contracts/index.d.ts +39 -0
  47. package/dist/vendor/contracts/index.js +2 -0
  48. package/dist/vendor/core/context-integrity.js +28 -3
  49. package/dist/vendor/core/grounding.d.ts +1 -0
  50. package/dist/vendor/core/grounding.js +6 -2
  51. package/dist/vendor/core/index.d.ts +24 -3
  52. package/dist/vendor/core/index.js +113 -21
  53. package/dist/vendor/core/leash.js +85 -8
  54. package/dist/vendor/core/persistence/index.d.ts +2 -0
  55. package/dist/vendor/core/persistence/index.js +1 -0
  56. package/dist/vendor/core/persistence/integrity.d.ts +38 -0
  57. package/dist/vendor/core/persistence/integrity.js +248 -0
  58. package/dist/vendor/core/persistence/store.d.ts +7 -0
  59. package/dist/vendor/core/persistence/store.js +25 -1
  60. package/dist/vendor/core/policy.d.ts +9 -0
  61. package/dist/workflow-state.d.ts +9 -0
  62. package/dist/workflow-state.js +46 -3
  63. package/package.json +2 -2
  64. package/server.json +2 -2
@@ -0,0 +1,538 @@
1
+ import { spawnSync } from "node:child_process";
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
+ }
20
+ function isInsideGitRepository(workingDirectory) {
21
+ let current = resolve(workingDirectory);
22
+ while (true) {
23
+ if (existsSync(resolve(current, ".git"))) {
24
+ return true;
25
+ }
26
+ const parent = dirname(current);
27
+ if (parent === current) {
28
+ return false;
29
+ }
30
+ current = parent;
31
+ }
32
+ }
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
48
+ };
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 [];
58
+ }
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;
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;
80
+ }
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);
113
+ }
114
+ function buildProbeCommand(command, args, platform) {
115
+ if (platform !== "win32") {
116
+ return { command, args, invocationMode: "direct" };
117
+ }
118
+ const extension = extname(command).toLowerCase();
119
+ switch (extension) {
120
+ case ".cmd":
121
+ case ".bat":
122
+ return {
123
+ command: process.env.ComSpec || "cmd.exe",
124
+ args: ["/d", "/c", command, ...args],
125
+ invocationMode: "cmd_shell"
126
+ };
127
+ case ".ps1":
128
+ return {
129
+ command: "powershell.exe",
130
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command, ...args],
131
+ invocationMode: "powershell"
132
+ };
133
+ default:
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";
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);
248
+ }
249
+ export function resolveCliCommandAvailability(command, options = {}) {
250
+ const platform = options.platform ?? process.platform;
251
+ const spawnSyncImpl = options.spawnSyncImpl ?? spawnSync;
252
+ const discovery = readLocatorCandidates(command, platform, spawnSyncImpl);
253
+ const resolvedPath = discovery.candidates[0];
254
+ return discovery.candidates.length > 0
255
+ ? {
256
+ command,
257
+ available: true,
258
+ locator: discovery.locator,
259
+ detail: `${command} is available on PATH.`,
260
+ ...(resolvedPath ? { resolvedPath } : {}),
261
+ candidatePaths: discovery.candidates
262
+ }
263
+ : {
264
+ command,
265
+ available: false,
266
+ locator: discovery.locator,
267
+ detail: `${command} is not available on PATH.`
268
+ };
269
+ }
270
+ export function detectCodexHostPlatform(env = process.env, platform = process.platform) {
271
+ if (platform === "win32") {
272
+ return "windows";
273
+ }
274
+ if (platform === "darwin") {
275
+ return "macos";
276
+ }
277
+ if (env["WSL_DISTRO_NAME"] || env["WSL_INTEROP"]) {
278
+ return "wsl";
279
+ }
280
+ return "linux";
281
+ }
282
+ export function diagnoseCodexHost(availability, options = {}) {
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);
287
+ const warnings = [];
288
+ if (!availability.available) {
289
+ return {
290
+ hostPlatform,
291
+ nativeInstallValid: false,
292
+ installKind,
293
+ invocationMode,
294
+ sandboxMode: "workspace-write",
295
+ sandboxCompatible: false,
296
+ ...(resolvedPath ? { resolvedPath } : {}),
297
+ warnings,
298
+ remediation: "Install or expose the Codex CLI on PATH before running governed Codex work."
299
+ };
300
+ }
301
+ if ((hostPlatform === "linux" || hostPlatform === "wsl") &&
302
+ (installKind === "windows_shim" || installKind === "windows_mounted_path")) {
303
+ warnings.push("Codex resolves to a Windows-hosted install from a Linux/WSL environment.");
304
+ return {
305
+ hostPlatform,
306
+ nativeInstallValid: false,
307
+ installKind,
308
+ invocationMode,
309
+ sandboxMode: "workspace-write",
310
+ sandboxCompatible: false,
311
+ ...(resolvedPath ? { resolvedPath } : {}),
312
+ warnings,
313
+ remediation: "Install Codex natively inside this Linux/WSL environment instead of relying on a Windows PATH shim."
314
+ };
315
+ }
316
+ return {
317
+ hostPlatform,
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
+ : {}),
327
+ warnings
328
+ };
329
+ }
330
+ export function probeCodexLaunch(input) {
331
+ const availability = input.availability ??
332
+ resolveCliCommandAvailability("codex", {
333
+ platform: input.platform,
334
+ spawnSyncImpl: input.spawnSyncImpl
335
+ });
336
+ const diagnosis = diagnoseCodexHost(availability, {
337
+ env: input.env,
338
+ platform: input.platform
339
+ });
340
+ const args = buildCodexExecArgs({
341
+ workingDirectory: input.workingDirectory,
342
+ mode: "probe"
343
+ });
344
+ if (!availability.available) {
345
+ return {
346
+ ok: false,
347
+ summary: availability.detail,
348
+ availability,
349
+ diagnosis,
350
+ command: availability.command,
351
+ args
352
+ };
353
+ }
354
+ if (!isInsideGitRepository(input.workingDirectory)) {
355
+ return {
356
+ ok: false,
357
+ summary: "Working directory is not inside a git repository. Codex exec requires a trusted repo unless --skip-git-repo-check is explicitly enabled.",
358
+ availability,
359
+ diagnosis,
360
+ command: availability.resolvedPath ?? availability.command,
361
+ args
362
+ };
363
+ }
364
+ const spawnSyncImpl = input.spawnSyncImpl ?? spawnSync;
365
+ const platform = input.platform ?? process.platform;
366
+ const env = input.env ?? process.env;
367
+ const probeCandidates = buildProbeCandidates({
368
+ availability,
369
+ env,
370
+ platform,
371
+ includeDesktopCandidates: input.spawnSyncImpl === undefined
372
+ });
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
+ }
384
+ }
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.",
454
+ exitCode: result.status ?? undefined,
455
+ stderr: result.stderr ?? "",
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
502
+ };
503
+ if (input.spawnSyncImpl === undefined) {
504
+ codexLaunchProbeCache.set(cacheKey, successResult);
505
+ }
506
+ return successResult;
507
+ }
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,
528
+ args,
529
+ exitCode: bestFailure?.exitCode,
530
+ stderr: bestFailure?.stderr,
531
+ stdout: bestFailure?.stdout,
532
+ candidateProbeResults
533
+ };
534
+ if (input.spawnSyncImpl === undefined) {
535
+ codexLaunchProbeCache.set(cacheKey, failureResult);
536
+ }
537
+ return failureResult;
538
+ }
@@ -1,7 +1,8 @@
1
1
  export { createDirectProviderAdapter, type DirectProviderAdapterOptions } from "./direct-provider.js";
2
2
  export { createStubDirectProviderAdapter, type StubDirectProviderAdapterOptions } from "./stub-direct-provider.js";
3
3
  export { createStubAgentCliAdapter, type StubAgentCliAdapterOptions } from "./stub-agent-cli.js";
4
- export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter, type AgentCliAdapterOptions, type ClaudeCliAdapterOptions, type CodexCliAdapterOptions, type CliArgsBuilder } from "./claude-cli.js";
4
+ export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, type AgentCliAdapterOptions, type ClaudeCliAdapterOptions, type CodexCliAdapterOptions, type GeminiCliAdapterOptions, type CliArgsBuilder } from "./claude-cli.js";
5
5
  export { createVerifierOnlyAdapter, type VerifierOnlyAdapterOptions } from "./verifier-only.js";
6
- export { createOpenAiCompatibleAdapter, type OpenAiCompatibleAdapterOptions } from "./openai-compatible.js";
6
+ export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig, type OpenAiCompatibleAdapterOptions } from "./openai-compatible.js";
7
+ export { detectCodexHostPlatform, diagnoseCodexHost, probeCodexLaunch, resolveCliCommandAvailability, type CliCommandAvailability, type CodexHostDiagnosis, type CodexHostPlatform, type CodexLaunchProbeResult } from "./codex-launcher.js";
7
8
  export { createSpawnPlan, type SpawnLike, type SpawnPlan, type SubprocessResult, type VerificationOutcome } from "./cli-bridge.js";
@@ -1,7 +1,8 @@
1
1
  export { createDirectProviderAdapter } from "./direct-provider.js";
2
2
  export { createStubDirectProviderAdapter } from "./stub-direct-provider.js";
3
3
  export { createStubAgentCliAdapter } from "./stub-agent-cli.js";
4
- export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter } from "./claude-cli.js";
4
+ export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter } from "./claude-cli.js";
5
5
  export { createVerifierOnlyAdapter } from "./verifier-only.js";
6
- export { createOpenAiCompatibleAdapter } from "./openai-compatible.js";
6
+ export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig } from "./openai-compatible.js";
7
+ export { detectCodexHostPlatform, diagnoseCodexHost, probeCodexLaunch, resolveCliCommandAvailability } from "./codex-launcher.js";
7
8
  export { createSpawnPlan } from "./cli-bridge.js";
@@ -12,24 +12,30 @@
12
12
  * Llama 3.x, Mistral 7B, Phi-4, Gemma 3, any GGUF model.
13
13
  *
14
14
  * Usage:
15
+ * # Defaults to OpenAI's hosted endpoint when MARTIN_OPENAI_BASE_URL is unset.
16
+ * MARTIN_OPENAI_API_KEY=sk-...
17
+ * MARTIN_OPENAI_MODEL=gpt-4.1-mini
18
+ * martin-loop run "fix the bug" --engine openai
19
+ *
20
+ * # Or route to a third-party / self-hosted OpenAI-compatible endpoint:
15
21
  * MARTIN_OPENAI_BASE_URL=https://openrouter.ai/api
16
22
  * MARTIN_OPENAI_API_KEY=sk-or-...
17
23
  * MARTIN_OPENAI_MODEL=deepseek/deepseek-chat
18
- * martin run "fix the bug" --engine openai
24
+ * martin-loop run "fix the bug" --engine openai
19
25
  *
20
26
  * Or for Ollama:
21
27
  * MARTIN_OPENAI_BASE_URL=http://localhost:11434
22
28
  * MARTIN_OPENAI_MODEL=llama3.3
23
- * martin run "fix the bug" --engine openai
29
+ * martin-loop run "fix the bug" --engine openai
24
30
  */
25
31
  import type { MartinAdapter } from "../core/index.js";
26
32
  export interface OpenAiCompatibleAdapterOptions {
27
33
  /** Base URL of the OpenAI-compatible API. No trailing slash. */
28
- baseUrl: string;
34
+ baseUrl?: string;
29
35
  /** API key. Empty string for local (Ollama/LM Studio) endpoints. */
30
36
  apiKey?: string;
31
37
  /** Model identifier passed as-is to the API (e.g. "deepseek/deepseek-chat"). */
32
- model: string;
38
+ model?: string;
33
39
  /**
34
40
  * System prompt prepended before the MartinLoop task prompt.
35
41
  * Default instructs the model to act as a focused coding assistant.
@@ -44,4 +50,13 @@ export interface OpenAiCompatibleAdapterOptions {
44
50
  /** Optional fetch override for testing. */
45
51
  fetchImpl?: typeof fetch;
46
52
  }
53
+ export declare const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
54
+ export declare const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
55
+ export declare function resolveOpenAiCompatibleRuntimeConfig(env?: NodeJS.ProcessEnv): {
56
+ baseUrl: string;
57
+ model: string;
58
+ apiKey: string;
59
+ apiKeyConfigured: boolean;
60
+ authPosture: "api_key" | "anonymous_or_local";
61
+ };
47
62
  export declare function createOpenAiCompatibleAdapter(options: OpenAiCompatibleAdapterOptions): MartinAdapter;