@martinloop/mcp 0.5.3 → 0.5.6

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 (65) hide show
  1. package/README.md +175 -175
  2. package/dist/arcade/capabilities.d.ts +36 -0
  3. package/dist/arcade/capabilities.js +37 -0
  4. package/dist/arcade/resource.d.ts +31 -0
  5. package/dist/arcade/resource.js +52 -0
  6. package/dist/arcade/status.d.ts +23 -0
  7. package/dist/arcade/status.js +25 -0
  8. package/dist/package-version.d.ts +1 -1
  9. package/dist/package-version.js +3 -3
  10. package/dist/resources.js +147 -147
  11. package/dist/server-validation.js +2 -0
  12. package/dist/server.js +79 -35
  13. package/dist/tools/run-loop.d.ts +2 -2
  14. package/dist/tools/run-loop.js +26 -6
  15. package/dist/tools/tool-response.d.ts +9 -1
  16. package/dist/tools/tool-response.js +7 -5
  17. package/dist/tools/tool-support.js +62 -271
  18. package/dist/vendor/adapters/claude-cli.d.ts +9 -0
  19. package/dist/vendor/adapters/claude-cli.js +21 -3
  20. package/dist/vendor/adapters/cli-bridge.js +7 -1
  21. package/dist/vendor/adapters/codex-capabilities.d.ts +12 -3
  22. package/dist/vendor/adapters/codex-capabilities.js +46 -18
  23. package/dist/vendor/adapters/codex-cli.d.ts +2 -1
  24. package/dist/vendor/adapters/codex-cli.js +12 -2
  25. package/dist/vendor/adapters/codex-host.d.ts +4 -1
  26. package/dist/vendor/adapters/codex-host.js +34 -16
  27. package/dist/vendor/adapters/codex-launcher.d.ts +1 -1
  28. package/dist/vendor/adapters/codex-launcher.js +1 -1
  29. package/dist/vendor/adapters/index.d.ts +3 -1
  30. package/dist/vendor/adapters/index.js +2 -0
  31. package/dist/vendor/adapters/openai-compatible.js +12 -9
  32. package/dist/vendor/adapters/stub-agent-cli.d.ts +9 -0
  33. package/dist/vendor/adapters/stub-agent-cli.js +43 -0
  34. package/dist/vendor/adapters/stub-direct-provider.d.ts +8 -0
  35. package/dist/vendor/adapters/stub-direct-provider.js +16 -0
  36. package/dist/vendor/contracts/agent-execution.d.ts +9 -0
  37. package/dist/vendor/contracts/agent-execution.js +14 -0
  38. package/dist/vendor/contracts/index.d.ts +9 -0
  39. package/dist/vendor/contracts/index.js +1 -0
  40. package/dist/vendor/core/grounding.js +42 -7
  41. package/dist/vendor/core/index.d.ts +5 -2
  42. package/dist/vendor/core/index.js +20 -66
  43. package/dist/vendor/core/policy.d.ts +1 -0
  44. package/dist/vendor/core/policy.js +8 -0
  45. package/dist/vendor/core/rollback.d.ts +3 -0
  46. package/dist/vendor/core/rollback.js +33 -20
  47. package/dist/vendor/core/verified-handoff.js +25 -6
  48. package/dist/vendor/presentation/governed-plan.d.ts +33 -0
  49. package/dist/vendor/presentation/governed-plan.js +146 -0
  50. package/dist/vendor/presentation/index.d.ts +7 -0
  51. package/dist/vendor/presentation/index.js +7 -0
  52. package/dist/vendor/presentation/motion.d.ts +12 -0
  53. package/dist/vendor/presentation/motion.js +123 -0
  54. package/dist/vendor/presentation/status.d.ts +8 -0
  55. package/dist/vendor/presentation/status.js +11 -0
  56. package/dist/vendor/presentation/table.d.ts +11 -0
  57. package/dist/vendor/presentation/table.js +47 -0
  58. package/dist/vendor/presentation/text.d.ts +6 -0
  59. package/dist/vendor/presentation/text.js +30 -0
  60. package/dist/vendor/presentation/theme.d.ts +31 -0
  61. package/dist/vendor/presentation/theme.js +82 -0
  62. package/dist/vendor/presentation/verified-handoff.d.ts +15 -0
  63. package/dist/vendor/presentation/verified-handoff.js +148 -0
  64. package/package.json +81 -80
  65. package/server.json +21 -21
@@ -12,6 +12,7 @@
12
12
  * MCP tools and integration tests use the same factories.
13
13
  */
14
14
  import type { MartinAdapter, MartinAdapterRequest } from "../core/index.js";
15
+ import { type AgentExecutionIntent } from "../core/index.js";
15
16
  import { type SpawnLike } from "./cli-bridge.js";
16
17
  /**
17
18
  * Given a prompt string, returns the full argv array to pass to spawn().
@@ -33,6 +34,8 @@ export interface AgentCliAdapterOptions {
33
34
  workingDirectory?: string;
34
35
  /** Timeout for the agent subprocess in ms. Defaults to 300_000 (5 min). */
35
36
  timeoutMs?: number;
37
+ agentExecutionIntent?: AgentExecutionIntent;
38
+ providerExecutionTimeoutMs?: number;
36
39
  /** Timeout per verification command in ms. Defaults to 120_000 (2 min). */
37
40
  verifyTimeoutMs?: number;
38
41
  /** Human-readable label shown in loop records. */
@@ -60,6 +63,8 @@ export interface AgentCliAdapterOptions {
60
63
  export interface ClaudeCliAdapterOptions {
61
64
  workingDirectory?: string;
62
65
  timeoutMs?: number;
66
+ agentExecutionIntent?: AgentExecutionIntent;
67
+ providerExecutionTimeoutMs?: number;
63
68
  verifyTimeoutMs?: number;
64
69
  label?: string;
65
70
  /** Override the model passed via --model flag. */
@@ -73,6 +78,8 @@ export interface CodexCliAdapterOptions {
73
78
  command?: string;
74
79
  workingDirectory?: string;
75
80
  timeoutMs?: number;
81
+ agentExecutionIntent?: AgentExecutionIntent;
82
+ providerExecutionTimeoutMs?: number;
76
83
  verifyTimeoutMs?: number;
77
84
  label?: string;
78
85
  /** Override the model passed via --model flag. */
@@ -94,6 +101,8 @@ export interface CodexCliAdapterOptions {
94
101
  export interface GeminiCliAdapterOptions {
95
102
  workingDirectory?: string;
96
103
  timeoutMs?: number;
104
+ agentExecutionIntent?: AgentExecutionIntent;
105
+ providerExecutionTimeoutMs?: number;
97
106
  verifyTimeoutMs?: number;
98
107
  label?: string;
99
108
  /** Explicit model override passed via --model. Omitted to preserve Gemini Auto. */
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * MCP tools and integration tests use the same factories.
13
13
  */
14
+ import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs } from "../core/index.js";
14
15
  import { readGitChangedFiles, readGitExecutionArtifacts, resolveGitRepositoryRoot, runSubprocess, runVerification } from "./cli-bridge.js";
15
16
  import { buildCodexExecArgs } from "./codex-launcher.js";
16
17
  import { createAdapterCapabilities, normalizeStructuredErrors, normalizeUsage } from "./runtime-support.js";
@@ -518,7 +519,8 @@ function inferStructuralClassHint(agentOutput, verificationSummary, exitCode, ob
518
519
  // ---------------------------------------------------------------------------
519
520
  export function createAgentCliAdapter(options) {
520
521
  const workingDirectory = options.workingDirectory ?? process.cwd();
521
- const timeoutMs = options.timeoutMs ?? 300_000;
522
+ const agentExecutionIntent = options.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT;
523
+ const timeoutMs = normalizeProviderExecutionTimeoutMs(options.providerExecutionTimeoutMs ?? options.timeoutMs);
522
524
  const verifyTimeoutMs = options.verifyTimeoutMs ?? 120_000;
523
525
  const adapterId = `agent-cli:${options.adapterIdSuffix ?? options.command}`;
524
526
  const supportsJsonOutput = options.supportsJsonOutput === true;
@@ -531,6 +533,8 @@ export function createAgentCliAdapter(options) {
531
533
  providerId: options.command,
532
534
  ...(options.model ? { model: options.model } : {}),
533
535
  transport: "cli",
536
+ agentExecutionIntent,
537
+ providerExecutionTimeoutMs: timeoutMs,
534
538
  capabilities: createAdapterCapabilities({
535
539
  preflight: true,
536
540
  usageSettlement: supportsUsageSettlement,
@@ -571,6 +575,7 @@ export function createAgentCliAdapter(options) {
571
575
  }
572
576
  const args = options.argsBuilder(prompt, request);
573
577
  const stdinData = options.stdinBuilder?.(prompt);
578
+ const executionTimeoutMs = normalizeProviderExecutionTimeoutMs(request.context.providerExecutionTimeoutMs ?? timeoutMs);
574
579
  // Live cumulative-cost circuit breaker: a single attempt should never be
575
580
  // allowed to spend more than the loop has left. `--output-format json`
576
581
  // only reports usage once the process exits, so for `stream-json` we
@@ -582,7 +587,7 @@ export function createAgentCliAdapter(options) {
582
587
  : undefined;
583
588
  const agentResult = await runSubprocess(options.command, args, {
584
589
  cwd: workingDirectory,
585
- timeoutMs,
590
+ timeoutMs: executionTimeoutMs,
586
591
  spawnImpl: options.spawnImpl,
587
592
  ...(stdinData === undefined ? {} : { stdinData }),
588
593
  ...(streamingUsage ? { onStdoutChunk: streamingUsage.onChunk } : {}),
@@ -744,6 +749,7 @@ export function createAgentCliAdapter(options) {
744
749
  workspaceId: request.workspaceId,
745
750
  cwd: workingDirectory,
746
751
  });
752
+ const verificationConfigured = verification.binding.commands.length > 0;
747
753
  // Check for zero-diff (agent ran but made no file changes)
748
754
  const postRunChangedFiles = gitRepoRoot
749
755
  ? await readGitChangedFiles(gitRepoRoot, 5_000, options.spawnImpl)
@@ -786,7 +792,7 @@ export function createAgentCliAdapter(options) {
786
792
  }
787
793
  // Derive structural classHint from evidence, not keyword scanning
788
794
  const structuralHint = inferStructuralClassHint(agentText, verification.summary, agentResult.exitCode, request.context.objective);
789
- if (verification.passed) {
795
+ if (verification.passed || !verificationConfigured) {
790
796
  return {
791
797
  status: "completed",
792
798
  summary,
@@ -901,6 +907,8 @@ export function createClaudeCliAdapter(options = {}) {
901
907
  label: options.label ?? "Claude CLI adapter",
902
908
  workingDirectory: options.workingDirectory,
903
909
  timeoutMs: options.timeoutMs,
910
+ agentExecutionIntent: options.agentExecutionIntent,
911
+ providerExecutionTimeoutMs: options.providerExecutionTimeoutMs,
904
912
  verifyTimeoutMs: options.verifyTimeoutMs,
905
913
  supportsJsonOutput: true,
906
914
  streamingUsageCap: true,
@@ -952,6 +960,8 @@ export function createCodexCliAdapter(options = {}) {
952
960
  label: options.label ?? "Codex CLI adapter",
953
961
  workingDirectory,
954
962
  timeoutMs: options.timeoutMs,
963
+ agentExecutionIntent: options.agentExecutionIntent,
964
+ providerExecutionTimeoutMs: options.providerExecutionTimeoutMs,
955
965
  verifyTimeoutMs: options.verifyTimeoutMs,
956
966
  supportsJsonOutput: false,
957
967
  spawnImpl: options.spawnImpl,
@@ -979,7 +989,13 @@ export function createCodexCliAdapter(options = {}) {
979
989
  */
980
990
  export function createGeminiCliAdapter(options = {}) {
981
991
  const approvalMode = options.approvalMode ?? "yolo";
992
+ if (approvalMode !== "yolo") {
993
+ throw new Error("governed-autonomous Gemini execution requires yolo approval mode; interactive downgrade rejected.");
994
+ }
982
995
  const extraArgs = options.extraArgs ?? [];
996
+ if (extraArgs.some((arg) => arg === "--approval-mode" || arg.startsWith("--approval-mode="))) {
997
+ throw new Error("Gemini approval mode cannot be overridden through extraArgs.");
998
+ }
983
999
  return createAgentCliAdapter({
984
1000
  command: "gemini",
985
1001
  adapterIdSuffix: "gemini",
@@ -987,6 +1003,8 @@ export function createGeminiCliAdapter(options = {}) {
987
1003
  label: options.label ?? "Gemini CLI adapter",
988
1004
  workingDirectory: options.workingDirectory,
989
1005
  timeoutMs: options.timeoutMs,
1006
+ agentExecutionIntent: options.agentExecutionIntent,
1007
+ providerExecutionTimeoutMs: options.providerExecutionTimeoutMs,
990
1008
  verifyTimeoutMs: options.verifyTimeoutMs,
991
1009
  supportsJsonOutput: false,
992
1010
  spawnImpl: options.spawnImpl,
@@ -154,7 +154,13 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
154
154
  commands: steps.map((step) => step.command),
155
155
  };
156
156
  if (steps.length === 0) {
157
- return { passed: true, summary: "No verification commands specified.", steps: [], binding: executionBinding };
157
+ return {
158
+ passed: false,
159
+ summary: "No verification commands specified; execution is not VERIFIED.",
160
+ steps: [],
161
+ warnings: ["Execution completed without verifier evidence."],
162
+ binding: executionBinding,
163
+ };
158
164
  }
159
165
  const failedSteps = [];
160
166
  const stepOutcomes = [];
@@ -1,7 +1,8 @@
1
1
  import { spawnSync } from "node:child_process";
2
+ import { type AgentExecutionIntent } from "../contracts/index.js";
2
3
  export type CodexFlagScope = "global" | "exec";
3
4
  export type CodexPromptTransport = "stdin-dash" | "argv";
4
- export type CodexWriteStrategy = "sandbox+approval" | "sandbox" | "automation" | "approval" | "default";
5
+ export type CodexWriteStrategy = "sandbox+approval" | "automation";
5
6
  export interface CodexCapabilityFlag {
6
7
  flag: string;
7
8
  scope: CodexFlagScope;
@@ -11,7 +12,14 @@ export interface CodexSandboxCapability extends CodexCapabilityFlag {
11
12
  }
12
13
  export interface CodexApprovalCapability extends CodexCapabilityFlag {
13
14
  semantics: "approval-policy" | "automation-mode";
14
- value?: string;
15
+ values?: string[];
16
+ }
17
+ export interface CodexAutonomyResolution {
18
+ binaryPath: string;
19
+ intent: AgentExecutionIntent;
20
+ strategy: "automation" | "sandbox+approval";
21
+ sandboxValue?: "workspace-write";
22
+ approvalValue?: string;
15
23
  }
16
24
  export interface CodexCapabilityProfile {
17
25
  binaryPath: string;
@@ -32,7 +40,6 @@ export interface CodexCapabilityProfile {
32
40
  userConfigIsolation?: CodexCapabilityFlag;
33
41
  promptTransport: CodexPromptTransport;
34
42
  promptTransports?: CodexPromptTransport[];
35
- selectedWriteStrategy?: CodexWriteStrategy;
36
43
  }
37
44
  export interface CodexExecArgsOptions {
38
45
  command?: string;
@@ -45,6 +52,7 @@ export interface CodexExecArgsOptions {
45
52
  capabilityProfile?: CodexCapabilityProfile;
46
53
  promptTransport?: CodexPromptTransport;
47
54
  writeStrategy?: CodexWriteStrategy;
55
+ autonomyResolution?: CodexAutonomyResolution;
48
56
  }
49
57
  type SpawnSyncLike = typeof spawnSync;
50
58
  export declare function clearCodexCapabilityCacheForTests(): void;
@@ -55,6 +63,7 @@ export declare function probeCodexCapabilities(binaryPath: string, options?: {
55
63
  cache?: boolean;
56
64
  }): CodexCapabilityProfile;
57
65
  export declare function codexWriteStrategies(profile: CodexCapabilityProfile): CodexWriteStrategy[];
66
+ export declare function resolveCodexAutonomyCandidates(profile: CodexCapabilityProfile, intent?: AgentExecutionIntent): CodexAutonomyResolution[];
58
67
  export declare function buildCodexExecArgs(options: CodexExecArgsOptions): string[];
59
68
  export declare function buildCodexStdin(profile: CodexCapabilityProfile, prompt: string, transport?: CodexPromptTransport): string | undefined;
60
69
  export {};
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { extname } from "node:path";
3
3
  import { createSpawnPlan, resolveNpmShimScript } from "./cli-bridge.js";
4
+ import { DEFAULT_AGENT_EXECUTION_INTENT } from "../contracts/index.js";
4
5
  const capabilityCache = new Map();
5
6
  function escapeRegex(value) {
6
7
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
@@ -26,6 +27,23 @@ function flagContext(help, flag) {
26
27
  return "";
27
28
  return lines.slice(Math.max(0, index - 1), Math.min(lines.length, index + 3)).join("\n");
28
29
  }
30
+ function parseAdvertisedValues(help, flag) {
31
+ const flagIndex = help.indexOf(flag);
32
+ if (flagIndex < 0)
33
+ return [];
34
+ const tail = help.slice(flagIndex);
35
+ const nextFlagOffset = tail.slice(flag.length).search(/\n\s*-/u);
36
+ const scoped = nextFlagOffset < 0
37
+ ? tail
38
+ : tail.slice(0, flag.length + nextFlagOffset);
39
+ const match = scoped.match(/possible values:\s*([^\]\n]+)/iu);
40
+ if (!match?.[1])
41
+ return [];
42
+ return match[1]
43
+ .split(",")
44
+ .map((value) => value.trim().replace(/[\].]$/u, ""))
45
+ .filter(Boolean);
46
+ }
29
47
  function buildInjectedSpawnPlan(binaryPath, args, platform) {
30
48
  if (platform !== "win32")
31
49
  return { command: binaryPath, args };
@@ -121,8 +139,9 @@ export function probeCodexCapabilities(binaryPath, options = {}) {
121
139
  const approvalHelp = approvalFlag
122
140
  ? `${flagContext(globalHelp, approvalFlag.flag)}\n${flagContext(execHelp, approvalFlag.flag)}`
123
141
  : "";
124
- const approvalPolicy = approvalFlag && /\bnever\b/iu.test(approvalHelp)
125
- ? { ...approvalFlag, semantics: "approval-policy", value: "never" }
142
+ const approvalValues = approvalFlag ? parseAdvertisedValues(approvalHelp, approvalFlag.flag) : [];
143
+ const approvalPolicy = approvalFlag
144
+ ? { ...approvalFlag, semantics: "approval-policy", values: approvalValues }
126
145
  : undefined;
127
146
  const colorFlag = locateFlag(globalHelp, execHelp, ["--color"]);
128
147
  const colorHelp = colorFlag
@@ -174,42 +193,51 @@ function pushCapabilityArg(globalArgs, execArgs, capability, ...values) {
174
193
  (capability.scope === "global" ? globalArgs : execArgs).push(capability.flag, ...values);
175
194
  }
176
195
  export function codexWriteStrategies(profile) {
177
- const strategies = [];
178
- const hasWorkspaceWrite = profile.sandbox?.values.includes("workspace-write") === true;
179
- if (hasWorkspaceWrite && profile.approvalPolicy?.value === "never")
180
- strategies.push("sandbox+approval");
181
- if (hasWorkspaceWrite)
182
- strategies.push("sandbox");
183
- if (profile.automation)
184
- strategies.push("automation");
185
- if (profile.approvalPolicy?.value === "never")
186
- strategies.push("approval");
187
- strategies.push("default");
188
- return [...new Set(strategies)];
196
+ return resolveCodexAutonomyCandidates(profile).map((candidate) => candidate.strategy);
197
+ }
198
+ export function resolveCodexAutonomyCandidates(profile, intent = DEFAULT_AGENT_EXECUTION_INTENT) {
199
+ const candidates = [];
200
+ if (profile.automation) {
201
+ candidates.push({
202
+ binaryPath: profile.binaryPath,
203
+ intent,
204
+ strategy: "automation"
205
+ });
206
+ }
207
+ return candidates;
189
208
  }
190
209
  export function buildCodexExecArgs(options) {
191
210
  const profile = options.capabilityProfile ?? probeCodexCapabilities(options.command ?? "codex");
192
211
  if (!profile.supportsExec) {
193
212
  throw new Error(`Resolved Codex binary ${profile.binaryPath} does not advertise a usable exec subcommand.`);
194
213
  }
214
+ const resolution = options.autonomyResolution;
215
+ if (options.mode !== "probe" && !resolution) {
216
+ throw new Error(`Resolved Codex binary ${profile.binaryPath} has no negotiated governed-autonomous execution resolution.`);
217
+ }
218
+ const permissionFlags = [profile.sandbox?.flag, profile.automation?.flag, profile.approvalPolicy?.flag]
219
+ .filter((value) => Boolean(value));
220
+ if ((options.extraArgs ?? []).some((arg) => arg === "danger-full-access" || permissionFlags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)))) {
221
+ throw new Error("Permission and sandbox controls cannot be supplied through extraArgs.");
222
+ }
195
223
  const globalArgs = [];
196
224
  const execArgs = [];
197
225
  const requestedSandbox = options.sandbox ?? "workspace-write";
198
- const strategy = options.writeStrategy ?? profile.selectedWriteStrategy ?? codexWriteStrategies(profile)[0] ?? "default";
226
+ const strategy = resolution?.strategy ?? options.writeStrategy;
199
227
  const promptTransport = options.promptTransport ?? profile.promptTransport;
200
228
  if (profile.userConfigIsolation)
201
229
  pushCapabilityArg(globalArgs, execArgs, profile.userConfigIsolation);
202
230
  if (profile.cwd)
203
231
  pushCapabilityArg(globalArgs, execArgs, profile.cwd, options.workingDirectory);
204
232
  if (requestedSandbox === "workspace-write") {
205
- if ((strategy === "sandbox" || strategy === "sandbox+approval") && profile.sandbox?.values.includes("workspace-write")) {
233
+ if (strategy === "sandbox+approval" && profile.sandbox?.values.includes("workspace-write")) {
206
234
  pushCapabilityArg(globalArgs, execArgs, profile.sandbox, "workspace-write");
207
235
  }
208
236
  if (strategy === "automation" && profile.automation) {
209
237
  pushCapabilityArg(globalArgs, execArgs, profile.automation);
210
238
  }
211
- if ((strategy === "approval" || strategy === "sandbox+approval") && profile.approvalPolicy?.value) {
212
- pushCapabilityArg(globalArgs, execArgs, profile.approvalPolicy, profile.approvalPolicy.value);
239
+ if (strategy === "sandbox+approval" && profile.approvalPolicy && resolution?.approvalValue) {
240
+ pushCapabilityArg(globalArgs, execArgs, profile.approvalPolicy, resolution.approvalValue);
213
241
  }
214
242
  }
215
243
  else {
@@ -1,5 +1,5 @@
1
1
  import { type CodexCliAdapterOptions as LegacyCodexCliAdapterOptions } from "./claude-cli.js";
2
- import { type CodexCapabilityProfile } from "./codex-launcher.js";
2
+ import { type CodexAutonomyResolution, type CodexCapabilityProfile } from "./codex-launcher.js";
3
3
  export interface CodexCliAdapterOptions extends Omit<LegacyCodexCliAdapterOptions, "command"> {
4
4
  /** Exact resolved executable selected by the Codex launch probe. */
5
5
  command?: string;
@@ -9,6 +9,7 @@ export interface CodexCliAdapterOptions extends Omit<LegacyCodexCliAdapterOption
9
9
  * for the process lifetime. Exposed for deterministic integrations/tests.
10
10
  */
11
11
  capabilityProfile?: CodexCapabilityProfile;
12
+ autonomyResolution?: CodexAutonomyResolution;
12
13
  }
13
14
  /**
14
15
  * Capability-driven Codex CLI adapter.
@@ -24,8 +24,15 @@ function createCodexSpawnRouter(input) {
24
24
  */
25
25
  export function createCodexCliAdapter(options = {}) {
26
26
  const workingDirectory = options.workingDirectory ?? process.cwd();
27
- const selectedBinary = options.command ?? "codex";
27
+ const selectedBinary = options.command ?? options.capabilityProfile?.binaryPath ?? "codex";
28
28
  const capabilityProfile = options.capabilityProfile ?? probeCodexCapabilities(selectedBinary);
29
+ const autonomyResolution = options.autonomyResolution;
30
+ if (capabilityProfile.binaryPath !== selectedBinary) {
31
+ throw new Error("Codex exact binary capability profile mismatch.");
32
+ }
33
+ if (autonomyResolution && autonomyResolution.binaryPath !== selectedBinary) {
34
+ throw new Error("Codex exact binary autonomy resolution mismatch.");
35
+ }
29
36
  const sandbox = options.sandbox ?? "workspace-write";
30
37
  const extraArgs = options.extraArgs ?? [];
31
38
  const launchModel = options.model;
@@ -44,6 +51,8 @@ export function createCodexCliAdapter(options = {}) {
44
51
  label: options.label ?? "Codex CLI adapter",
45
52
  workingDirectory,
46
53
  timeoutMs: options.timeoutMs,
54
+ agentExecutionIntent: options.agentExecutionIntent,
55
+ providerExecutionTimeoutMs: options.providerExecutionTimeoutMs,
47
56
  verifyTimeoutMs: options.verifyTimeoutMs,
48
57
  supportsJsonOutput: false,
49
58
  spawnImpl,
@@ -55,7 +64,8 @@ export function createCodexCliAdapter(options = {}) {
55
64
  extraArgs,
56
65
  mode: "prompt",
57
66
  prompt,
58
- capabilityProfile
67
+ capabilityProfile,
68
+ ...(autonomyResolution ? { autonomyResolution } : {})
59
69
  }),
60
70
  stdinBuilder: (prompt) => buildCodexStdin(capabilityProfile, prompt)
61
71
  });
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { type CodexCapabilityProfile, type CodexPromptTransport, type CodexWriteStrategy } from "./codex-capabilities.js";
2
+ import { type CodexAutonomyResolution, type CodexCapabilityProfile, type CodexPromptTransport, type CodexWriteStrategy } from "./codex-capabilities.js";
3
3
  export interface CliCommandAvailability {
4
4
  command: string;
5
5
  available: boolean;
@@ -37,6 +37,7 @@ export interface CodexProbeCandidateResult {
37
37
  nativeDependencyPackage?: string;
38
38
  capabilityProfile?: CodexCapabilityProfile;
39
39
  writeStrategy?: CodexWriteStrategy;
40
+ autonomyResolution?: CodexAutonomyResolution;
40
41
  promptTransport?: CodexPromptTransport;
41
42
  }
42
43
  export interface CodexLaunchProbeResult {
@@ -48,6 +49,7 @@ export interface CodexLaunchProbeResult {
48
49
  args: string[];
49
50
  capabilityProfile?: CodexCapabilityProfile;
50
51
  writeStrategy?: CodexWriteStrategy;
52
+ autonomyResolution?: CodexAutonomyResolution;
51
53
  promptTransport?: CodexPromptTransport;
52
54
  exitCode?: number;
53
55
  stdout?: string;
@@ -100,5 +102,6 @@ export declare function probeCodexLaunch(input: {
100
102
  platform?: NodeJS.Platform;
101
103
  spawnSyncImpl?: SpawnSyncLike;
102
104
  model?: string;
105
+ providerExecutionTimeoutMs?: number;
103
106
  }): CodexLaunchProbeResult;
104
107
  export {};
@@ -1,8 +1,10 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { dirname, extname, join, resolve } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
4
6
  import { createSpawnPlan, resolveNpmShimScript } from "./cli-bridge.js";
5
- import { buildCodexExecArgs, buildCodexStdin, cacheCodexCapabilityProfile, codexWriteStrategies, probeCodexCapabilities } from "./codex-capabilities.js";
7
+ import { buildCodexExecArgs, buildCodexStdin, cacheCodexCapabilityProfile, probeCodexCapabilities, resolveCodexAutonomyCandidates } from "./codex-capabilities.js";
6
8
  const launchProbeCache = new Map();
7
9
  export function probeFilesystemWriteCapability(directory) {
8
10
  try {
@@ -304,12 +306,15 @@ function buildInjectedSpawnPlan(binaryPath, args, platform) {
304
306
  }
305
307
  return { command: binaryPath, args, invocationMode: "direct" };
306
308
  }
307
- function buildMarkerPrompt(marker) {
309
+ function buildMarkerPrompt(marker, outsideMarkerPath) {
308
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}"`;
309
312
  return [
310
313
  "You are validating MartinLoop Codex host readiness.",
311
314
  "Do not modify tracked files.",
312
- "Use the shell command executor exactly once.",
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.",
313
318
  `Run exactly: ${command}`,
314
319
  "Do not use MCP tools or alternate tools.",
315
320
  "After it succeeds, reply READY."
@@ -372,6 +377,7 @@ export function probeCodexLaunch(input) {
372
377
  const platform = input.platform ?? process.platform;
373
378
  const env = input.env ?? process.env;
374
379
  const spawnSyncImpl = input.spawnSyncImpl ?? spawnSync;
380
+ const providerExecutionTimeoutMs = normalizeProviderExecutionTimeoutMs(input.providerExecutionTimeoutMs);
375
381
  const candidates = buildCandidates({
376
382
  availability,
377
383
  env,
@@ -425,13 +431,15 @@ export function probeCodexLaunch(input) {
425
431
  continue;
426
432
  }
427
433
  const transports = profile.promptTransports?.length ? profile.promptTransports : [profile.promptTransport];
428
- const strategies = codexWriteStrategies(profile);
434
+ const resolutions = resolveCodexAutonomyCandidates(profile);
429
435
  let candidateSummary = "No advertised Codex invocation strategy proved writable execution.";
430
- for (const strategy of strategies) {
436
+ for (const resolution of resolutions) {
431
437
  for (const transport of transports) {
432
438
  const marker = `.martin-codex-write-probe-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`;
433
439
  const markerPath = join(input.workingDirectory, marker);
434
- const prompt = buildMarkerPrompt(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);
435
443
  let args;
436
444
  try {
437
445
  args = buildCodexExecArgs({
@@ -443,10 +451,12 @@ export function probeCodexLaunch(input) {
443
451
  prompt,
444
452
  capabilityProfile: profile,
445
453
  promptTransport: transport,
446
- writeStrategy: strategy
454
+ writeStrategy: resolution.strategy,
455
+ autonomyResolution: resolution
447
456
  });
448
457
  }
449
458
  catch (error) {
459
+ rmSync(outsideRoot, { recursive: true, force: true });
450
460
  candidateSummary = error instanceof Error ? error.message : String(error);
451
461
  lastFailure = { candidate, profile, args: [], summary: candidateSummary };
452
462
  continue;
@@ -459,11 +469,14 @@ export function probeCodexLaunch(input) {
459
469
  cwd: input.workingDirectory,
460
470
  encoding: "utf8",
461
471
  stdio: [stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
462
- ...(stdin !== undefined ? { input: stdin } : {})
472
+ ...(stdin !== undefined ? { input: stdin } : {}),
473
+ timeout: providerExecutionTimeoutMs
463
474
  });
464
475
  let markerVerified = false;
476
+ let outsideDenied = false;
465
477
  try {
466
478
  markerVerified = existsSync(markerPath) && readFileSync(markerPath, "utf8") === "MARTIN_CODEX_WRITE_OK";
479
+ outsideDenied = !existsSync(outsideMarkerPath);
467
480
  }
468
481
  finally {
469
482
  try {
@@ -472,9 +485,10 @@ export function probeCodexLaunch(input) {
472
485
  catch {
473
486
  // best effort cleanup
474
487
  }
488
+ rmSync(outsideRoot, { recursive: true, force: true });
475
489
  }
476
- if (!result.error && result.status === 0 && markerVerified) {
477
- const negotiated = cacheCodexCapabilityProfile({ ...profile, selectedWriteStrategy: strategy, promptTransport: transport }, platform);
490
+ if (!result.error && result.status === 0 && markerVerified && outsideDenied) {
491
+ const negotiated = cacheCodexCapabilityProfile({ ...profile, promptTransport: transport }, platform);
478
492
  selected = {
479
493
  candidate: {
480
494
  ...candidate,
@@ -482,7 +496,7 @@ export function probeCodexLaunch(input) {
482
496
  },
483
497
  profile: negotiated,
484
498
  args,
485
- strategy,
499
+ resolution,
486
500
  transport,
487
501
  ...(result.status === null ? {} : { exitCode: result.status }),
488
502
  stdout: result.stdout ?? "",
@@ -495,9 +509,10 @@ export function probeCodexLaunch(input) {
495
509
  nativeInstallValid: true,
496
510
  sandboxCompatible: true,
497
511
  launchReady: true,
498
- summary: `Codex capability negotiation passed with ${strategy} / ${transport}.`,
512
+ summary: `Codex capability negotiation passed with ${resolution.strategy} / ${transport}.`,
499
513
  capabilityProfile: negotiated,
500
- writeStrategy: strategy,
514
+ writeStrategy: resolution.strategy,
515
+ autonomyResolution: resolution,
501
516
  promptTransport: transport
502
517
  });
503
518
  break candidateLoop;
@@ -507,7 +522,9 @@ export function probeCodexLaunch(input) {
507
522
  ? `Codex launch probe failed: ${result.error.message}`
508
523
  : result.status !== 0
509
524
  ? `Codex launch probe exited non-zero: ${(result.stderr ?? result.stdout ?? "").trim() || String(result.status)}`
510
- : `Codex ${strategy} / ${transport} invocation did not prove workspace writes.`;
525
+ : !outsideDenied
526
+ ? `Codex ${resolution.strategy} / ${transport} escaped the workspace boundary.`
527
+ : `Codex ${resolution.strategy} / ${transport} invocation did not prove workspace writes.`;
511
528
  lastFailure = {
512
529
  candidate: { ...candidate, diagnosis: failureDiagnosis },
513
530
  profile,
@@ -534,13 +551,14 @@ export function probeCodexLaunch(input) {
534
551
  if (selected) {
535
552
  const result = {
536
553
  ok: true,
537
- summary: `Codex capability-driven workspace-write probe passed using ${selected.strategy} / ${selected.transport}.`,
554
+ summary: `Codex capability-driven workspace-write probe passed using ${selected.resolution.strategy} / ${selected.transport}.`,
538
555
  availability: { ...availability, resolvedPath: selected.candidate.path, candidatePaths },
539
556
  diagnosis: { ...selected.candidate.diagnosis, resolvedPath: selected.candidate.path },
540
557
  command: selected.candidate.path,
541
558
  args: selected.args,
542
559
  capabilityProfile: selected.profile,
543
- writeStrategy: selected.strategy,
560
+ writeStrategy: selected.resolution.strategy,
561
+ autonomyResolution: selected.resolution,
544
562
  promptTransport: selected.transport,
545
563
  ...(selected.exitCode === undefined ? {} : { exitCode: selected.exitCode }),
546
564
  ...(selected.stdout === undefined ? {} : { stdout: selected.stdout }),
@@ -6,5 +6,5 @@ import { type CodexExecArgsOptions } from "./codex-capabilities.js";
6
6
  * builder without restoring any hard-coded flag assumption.
7
7
  */
8
8
  export declare function buildCodexExecArgs(options: CodexExecArgsOptions): string[];
9
- export { buildCodexStdin, cacheCodexCapabilityProfile, clearCodexCapabilityCacheForTests, codexWriteStrategies, probeCodexCapabilities, type CodexApprovalCapability, type CodexCapabilityFlag, type CodexCapabilityProfile, type CodexExecArgsOptions, type CodexFlagScope, type CodexPromptTransport, type CodexSandboxCapability, type CodexWriteStrategy } from "./codex-capabilities.js";
9
+ export { buildCodexStdin, cacheCodexCapabilityProfile, clearCodexCapabilityCacheForTests, codexWriteStrategies, probeCodexCapabilities, resolveCodexAutonomyCandidates, type CodexAutonomyResolution, type CodexApprovalCapability, type CodexCapabilityFlag, type CodexCapabilityProfile, type CodexExecArgsOptions, type CodexFlagScope, type CodexPromptTransport, type CodexSandboxCapability, type CodexWriteStrategy } from "./codex-capabilities.js";
10
10
  export { checkCodexSandboxPreflight, detectCodexHostPlatform, diagnoseCodexHost, probeCodexLaunch, probeFilesystemWriteCapability, resolveCliCommandAvailability, type CliCommandAvailability, type CodexHostDiagnosis, type CodexHostPlatform, type CodexInstallKind, type CodexInvocationMode, type CodexLaunchProbeResult, type CodexProbeCandidateResult, type CodexSandboxPreflightOk, type CodexSandboxPreflightOutcome, type CodexSandboxPreflightReadOnly } from "./codex-host.js";
@@ -18,5 +18,5 @@ export function buildCodexExecArgs(options) {
18
18
  capabilityProfile: normalizedProfile
19
19
  });
20
20
  }
21
- export { buildCodexStdin, cacheCodexCapabilityProfile, clearCodexCapabilityCacheForTests, codexWriteStrategies, probeCodexCapabilities } from "./codex-capabilities.js";
21
+ export { buildCodexStdin, cacheCodexCapabilityProfile, clearCodexCapabilityCacheForTests, codexWriteStrategies, probeCodexCapabilities, resolveCodexAutonomyCandidates } from "./codex-capabilities.js";
22
22
  export { checkCodexSandboxPreflight, detectCodexHostPlatform, diagnoseCodexHost, probeCodexLaunch, probeFilesystemWriteCapability, resolveCliCommandAvailability } from "./codex-host.js";
@@ -3,5 +3,7 @@ export { createAgentCliAdapter, createClaudeCliAdapter, createGeminiCliAdapter,
3
3
  export { createCodexCliAdapter, type CodexCliAdapterOptions } from "./codex-cli.js";
4
4
  export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig, type OpenAiCompatibleAdapterOptions } from "./openai-compatible.js";
5
5
  export { createVerifierOnlyAdapter, type VerifierOnlyAdapterOptions } from "./verifier-only.js";
6
- export { buildCodexExecArgs, buildCodexStdin, clearCodexCapabilityCacheForTests, detectCodexHostPlatform, diagnoseCodexHost, probeCodexCapabilities, probeCodexLaunch, resolveCliCommandAvailability, type CliCommandAvailability, type CodexApprovalCapability, type CodexCapabilityFlag, type CodexCapabilityProfile, type CodexFlagScope, type CodexHostDiagnosis, type CodexHostPlatform, type CodexLaunchProbeResult, type CodexPromptTransport, type CodexSandboxCapability, checkCodexSandboxPreflight, probeFilesystemWriteCapability, type CodexSandboxPreflightOk, type CodexSandboxPreflightOutcome, type CodexSandboxPreflightReadOnly } from "./codex-launcher.js";
6
+ /** @internal Test-only compatibility export. Public package builders strip this surface. */
7
+ export { createStubAgentCliAdapter, type StubAgentCliAdapterOptions, createStubDirectProviderAdapter, type StubDirectProviderAdapterOptions } from "./stub-agent-cli.js";
8
+ export { buildCodexExecArgs, buildCodexStdin, clearCodexCapabilityCacheForTests, detectCodexHostPlatform, diagnoseCodexHost, probeCodexCapabilities, probeCodexLaunch, resolveCliCommandAvailability, type CliCommandAvailability, type CodexApprovalCapability, type CodexAutonomyResolution, type CodexCapabilityFlag, type CodexCapabilityProfile, type CodexFlagScope, type CodexHostDiagnosis, type CodexHostPlatform, type CodexLaunchProbeResult, type CodexPromptTransport, type CodexSandboxCapability, checkCodexSandboxPreflight, probeFilesystemWriteCapability, type CodexSandboxPreflightOk, type CodexSandboxPreflightOutcome, type CodexSandboxPreflightReadOnly } from "./codex-launcher.js";
7
9
  export { createSpawnPlan, type SpawnLike, type SpawnPlan, type SubprocessResult, type VerificationOutcome } from "./cli-bridge.js";
@@ -3,5 +3,7 @@ export { createAgentCliAdapter, createClaudeCliAdapter, createGeminiCliAdapter }
3
3
  export { createCodexCliAdapter } from "./codex-cli.js";
4
4
  export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig } from "./openai-compatible.js";
5
5
  export { createVerifierOnlyAdapter } from "./verifier-only.js";
6
+ /** @internal Test-only compatibility export. Public package builders strip this surface. */
7
+ export { createStubAgentCliAdapter, createStubDirectProviderAdapter } from "./stub-agent-cli.js";
6
8
  export { buildCodexExecArgs, buildCodexStdin, clearCodexCapabilityCacheForTests, detectCodexHostPlatform, diagnoseCodexHost, probeCodexCapabilities, probeCodexLaunch, resolveCliCommandAvailability, checkCodexSandboxPreflight, probeFilesystemWriteCapability } from "./codex-launcher.js";
7
9
  export { createSpawnPlan } from "./cli-bridge.js";
@@ -103,12 +103,12 @@ function normalizeOpenAiCompatibleUsage(input) {
103
103
  // ---------------------------------------------------------------------------
104
104
  // Prompt builder
105
105
  // ---------------------------------------------------------------------------
106
- const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
107
- Follow these rules exactly:
108
- - Read the task description carefully and implement only what is asked.
109
- - Do not add features, refactors, or improvements beyond the stated task.
110
- - If the task asks you to write or modify code, output the complete file content with changes applied.
111
- - Be precise, minimal, and test-backed in all changes.
106
+ const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
107
+ Follow these rules exactly:
108
+ - Read the task description carefully and implement only what is asked.
109
+ - Do not add features, refactors, or improvements beyond the stated task.
110
+ - If the task asks you to write or modify code, output the complete file content with changes applied.
111
+ - Be precise, minimal, and test-backed in all changes.
112
112
  - State what you changed and why at the end of your response.`;
113
113
  export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
114
114
  export function resolveOpenAiCompatibleRuntimeConfig(env = process.env) {
@@ -347,11 +347,14 @@ export function createOpenAiCompatibleAdapter(options) {
347
347
  ? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
348
348
  : []
349
349
  };
350
+ const verificationConfigured = verification.binding.commands.length > 0;
350
351
  return {
351
- status: verification.passed ? "completed" : "failed",
352
+ status: verification.passed || !verificationConfigured ? "completed" : "failed",
352
353
  summary: verification.passed
353
354
  ? `${model} completed the task. Verifier passed.`
354
- : `${model} completed but verifier failed: ${verification.summary}`,
355
+ : !verificationConfigured
356
+ ? `${model} completed the task without verifier evidence; outcome is not VERIFIED.`
357
+ : `${model} completed but verifier failed: ${verification.summary}`,
355
358
  usage: normalizeOpenAiCompatibleUsage({
356
359
  model,
357
360
  tokensIn,
@@ -362,7 +365,7 @@ export function createOpenAiCompatibleAdapter(options) {
362
365
  }),
363
366
  verification,
364
367
  execution,
365
- ...(verification.passed ? {} : {
368
+ ...(verification.passed || !verificationConfigured ? {} : {
366
369
  failure: { message: verification.summary }
367
370
  })
368
371
  };
@@ -0,0 +1,9 @@
1
+ import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../core/index.js";
2
+ export interface StubAgentCliAdapterOptions {
3
+ command: string[];
4
+ profile?: string;
5
+ label?: string;
6
+ responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
7
+ }
8
+ export declare function createStubAgentCliAdapter(options: StubAgentCliAdapterOptions): MartinAdapter;
9
+ export { createStubDirectProviderAdapter, type StubDirectProviderAdapterOptions } from "./stub-direct-provider.js";