@martinloop/mcp 0.8.0 → 0.8.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.
@@ -1 +1 @@
1
- export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.0";
1
+ export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.1";
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by build — do not edit by hand.
2
2
  // Source of truth: packages/mcp/package.json#version
3
- export const MARTIN_MCP_PACKAGE_VERSION = "0.8.0";
3
+ export const MARTIN_MCP_PACKAGE_VERSION = "0.8.1";
@@ -63,6 +63,11 @@ export interface AgentCliAdapterOptions {
63
63
  streamingTokenCap?: boolean;
64
64
  /** Provider detail-token fields are already included in input/output token totals. */
65
65
  streamingUsageDetailsIncludedInTotals?: boolean;
66
+ /** Estimated floor used by the host to reject impossible token caps before spawning. */
67
+ budgetPreflight?: {
68
+ minimumViableTokens: number;
69
+ basis: string;
70
+ };
66
71
  /** Test-only override for subprocess spawning. */
67
72
  spawnImpl?: SpawnLike;
68
73
  }
@@ -675,6 +675,7 @@ export function createAgentCliAdapter(options) {
675
675
  transport: "cli",
676
676
  agentExecutionIntent,
677
677
  providerExecutionTimeoutMs: timeoutMs,
678
+ ...(options.budgetPreflight ? { budgetPreflight: options.budgetPreflight } : {}),
678
679
  capabilities: createAdapterCapabilities({
679
680
  preflight: true,
680
681
  usageSettlement: supportsUsageSettlement,
@@ -1,6 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { type AgentExecutionIntent } from "../contracts/index.js";
3
- export type CodexFlagScope = "global" | "exec";
3
+ export type CodexFlagScope = "global" | "exec" | "sandbox";
4
4
  export type CodexPromptTransport = "stdin-dash" | "argv";
5
5
  export type CodexWriteStrategy = "sandbox+approval" | "automation";
6
6
  export interface CodexCapabilityFlag {
@@ -26,6 +26,8 @@ export interface CodexCapabilityProfile {
26
26
  supportsExec: boolean;
27
27
  probeSucceeded: boolean;
28
28
  probeError?: string;
29
+ config?: CodexCapabilityFlag;
30
+ sandboxConfig?: CodexCapabilityFlag;
29
31
  model?: CodexCapabilityFlag;
30
32
  cwd?: CodexCapabilityFlag;
31
33
  sandbox?: CodexSandboxCapability;
@@ -44,6 +46,7 @@ export interface CodexCapabilityProfile {
44
46
  export interface CodexExecArgsOptions {
45
47
  command?: string;
46
48
  workingDirectory: string;
49
+ platform?: NodeJS.Platform;
47
50
  sandbox?: "read-only" | "workspace-write" | "danger-full-access";
48
51
  model?: string;
49
52
  extraArgs?: string[];
@@ -134,8 +134,18 @@ export function probeCodexCapabilities(binaryPath, options = {}) {
134
134
  spawnSyncImpl,
135
135
  injected
136
136
  });
137
+ const sandboxProbe = platform === "win32"
138
+ ? runHelpProbe({
139
+ binaryPath,
140
+ args: ["sandbox", "--help"],
141
+ platform,
142
+ spawnSyncImpl,
143
+ injected
144
+ })
145
+ : { text: "", status: null };
137
146
  const globalHelp = globalProbe.text;
138
147
  const execHelp = execProbe.text;
148
+ const sandboxCommandHelp = sandboxProbe.text;
139
149
  const supportsExec = execProbe.status === 0 &&
140
150
  !/(unknown command|unrecognized subcommand|unexpected argument ['"]?exec)/iu.test(execHelp);
141
151
  const sandboxFlag = locateFlag(globalHelp, execHelp, ["--sandbox", "-s"]);
@@ -160,7 +170,8 @@ export function probeCodexCapabilities(binaryPath, options = {}) {
160
170
  ? `${flagContext(globalHelp, colorFlag.flag)}\n${flagContext(execHelp, colorFlag.flag)}`
161
171
  : "";
162
172
  const promptTransports = parsePromptTransports(execHelp);
163
- const probeError = globalProbe.error ?? execProbe.error;
173
+ const probeError = globalProbe.error ?? execProbe.error ?? sandboxProbe.error;
174
+ const sandboxConfigFlag = ["--config", "-c"].find((flag) => flagPattern(flag).test(sandboxCommandHelp));
164
175
  const profile = {
165
176
  binaryPath,
166
177
  supportsExec,
@@ -169,6 +180,12 @@ export function probeCodexCapabilities(binaryPath, options = {}) {
169
180
  ...(locateFlag(globalHelp, execHelp, ["--model", "-m"])
170
181
  ? { model: locateFlag(globalHelp, execHelp, ["--model", "-m"]) }
171
182
  : {}),
183
+ ...(locateFlag(globalHelp, execHelp, ["--config", "-c"])
184
+ ? { config: locateFlag(globalHelp, execHelp, ["--config", "-c"]) }
185
+ : {}),
186
+ ...(sandboxConfigFlag
187
+ ? { sandboxConfig: { flag: sandboxConfigFlag, scope: "sandbox" } }
188
+ : {}),
172
189
  ...(locateFlag(globalHelp, execHelp, ["--cd", "--cwd", "--working-dir", "-C"])
173
190
  ? { cwd: locateFlag(globalHelp, execHelp, ["--cd", "--cwd", "--working-dir", "-C"]) }
174
191
  : {}),
@@ -242,18 +259,33 @@ export function buildCodexExecArgs(options) {
242
259
  if (options.mode !== "probe" && !resolution) {
243
260
  throw new Error(`Resolved Codex binary ${profile.binaryPath} has no negotiated governed-autonomous execution resolution.`);
244
261
  }
245
- const permissionFlags = [profile.sandbox?.flag, profile.automation?.flag, profile.approvalPolicy?.flag]
262
+ const controlledFlags = [
263
+ profile.sandbox?.flag,
264
+ profile.automation?.flag,
265
+ profile.approvalPolicy?.flag,
266
+ profile.config?.flag
267
+ ]
246
268
  .filter((value) => Boolean(value));
247
- if ((options.extraArgs ?? []).some((arg) => arg === "danger-full-access" || permissionFlags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)))) {
248
- throw new Error("Permission and sandbox controls cannot be supplied through extraArgs.");
269
+ if ((options.extraArgs ?? []).some((arg) => {
270
+ const isReservedConfig = arg === "--config" ||
271
+ arg.startsWith("--config=") ||
272
+ arg === "-c" ||
273
+ (arg.startsWith("-c") && arg.length > 2);
274
+ return arg === "danger-full-access" || isReservedConfig || controlledFlags.some((flag) => arg === flag || arg.startsWith(`${flag}=`));
275
+ })) {
276
+ throw new Error("Permission, sandbox, and configuration controls cannot be supplied through extraArgs.");
249
277
  }
250
278
  const globalArgs = [];
251
279
  const execArgs = [];
280
+ const platform = options.platform ?? process.platform;
252
281
  const requestedSandbox = options.sandbox ?? "workspace-write";
253
282
  const strategy = resolution?.strategy ?? options.writeStrategy;
254
283
  const promptTransport = options.promptTransport ?? profile.promptTransport;
255
284
  if (profile.userConfigIsolation)
256
285
  pushCapabilityArg(globalArgs, execArgs, profile.userConfigIsolation);
286
+ if (platform === "win32" && profile.userConfigIsolation && profile.config) {
287
+ pushCapabilityArg(globalArgs, execArgs, profile.config, 'windows.sandbox="elevated"');
288
+ }
257
289
  if (profile.cwd)
258
290
  pushCapabilityArg(globalArgs, execArgs, profile.cwd, options.workingDirectory);
259
291
  if (requestedSandbox === "workspace-write") {
@@ -1,5 +1,12 @@
1
1
  import { type CodexCliAdapterOptions as LegacyCodexCliAdapterOptions } from "./claude-cli.js";
2
2
  import { type CodexAutonomyResolution, type CodexCapabilityProfile } from "./codex-launcher.js";
3
+ /**
4
+ * Conservative admission floor for one Codex CLI turn. Codex reports token
5
+ * usage in turn.completed, after the first turn has already consumed context,
6
+ * so smaller explicit caps cannot be enforced before spend.
7
+ */
8
+ export declare const CODEX_MINIMUM_VIABLE_TOKEN_BUDGET = 128000;
9
+ export declare const CODEX_TOKEN_BUDGET_PREFLIGHT_BASIS = "codex_first_turn_usage_reports_after_completion";
3
10
  export interface CodexCliAdapterOptions extends Omit<LegacyCodexCliAdapterOptions, "command"> {
4
11
  /** Exact resolved executable selected by the Codex launch probe. */
5
12
  command?: string;
@@ -1,5 +1,12 @@
1
1
  import { createAgentCliAdapter } from "./claude-cli.js";
2
2
  import { buildCodexExecArgs, buildCodexStdin, isCodexAutonomyResolutionVerifiedByLaunchProbe, probeCodexCapabilities } from "./codex-launcher.js";
3
+ /**
4
+ * Conservative admission floor for one Codex CLI turn. Codex reports token
5
+ * usage in turn.completed, after the first turn has already consumed context,
6
+ * so smaller explicit caps cannot be enforced before spend.
7
+ */
8
+ export const CODEX_MINIMUM_VIABLE_TOKEN_BUDGET = 128_000;
9
+ export const CODEX_TOKEN_BUDGET_PREFLIGHT_BASIS = "codex_first_turn_usage_reports_after_completion";
3
10
  /**
4
11
  * Capability-driven Codex CLI adapter.
5
12
  *
@@ -44,6 +51,10 @@ export function createCodexCliAdapter(options = {}) {
44
51
  streamingUsageCap: true,
45
52
  streamingTokenCap: true,
46
53
  streamingUsageDetailsIncludedInTotals: true,
54
+ budgetPreflight: {
55
+ minimumViableTokens: CODEX_MINIMUM_VIABLE_TOKEN_BUDGET,
56
+ basis: CODEX_TOKEN_BUDGET_PREFLIGHT_BASIS
57
+ },
47
58
  spawnImpl: options.spawnImpl,
48
59
  argsBuilder: (prompt) => buildCodexExecArgs({
49
60
  command: selectedBinary,
@@ -319,8 +319,14 @@ function buildLocalSandboxProbeArgs(input) {
319
319
  " if (!error || !['EACCES', 'EPERM', 'EROFS'].includes(error.code)) throw error;",
320
320
  "}"
321
321
  ].join("\n");
322
+ const windowsSandboxConfig = input.platform === "win32" &&
323
+ input.capabilityProfile.userConfigIsolation &&
324
+ input.capabilityProfile.sandboxConfig
325
+ ? [input.capabilityProfile.sandboxConfig.flag, 'windows.sandbox="elevated"']
326
+ : [];
322
327
  return [
323
328
  "sandbox",
329
+ ...windowsSandboxConfig,
324
330
  "--permission-profile",
325
331
  ":workspace",
326
332
  "-C",
@@ -442,6 +448,21 @@ export function probeCodexLaunch(input) {
442
448
  });
443
449
  continue;
444
450
  }
451
+ if (platform === "win32" && profile.userConfigIsolation && (!profile.config || !profile.sandboxConfig)) {
452
+ const summary = "Resolved Codex binary does not advertise configuration override support for both exec and sandbox; cannot prove isolated native Windows workspace writes.";
453
+ lastFailure = { candidate, profile, args: [], summary };
454
+ candidateResults.push({
455
+ path: candidate.path,
456
+ installKind: candidate.diagnosis.installKind,
457
+ invocationMode: candidate.diagnosis.invocationMode,
458
+ nativeInstallValid: true,
459
+ sandboxCompatible: false,
460
+ launchReady: false,
461
+ summary,
462
+ capabilityProfile: profile
463
+ });
464
+ continue;
465
+ }
445
466
  const transport = profile.promptTransports?.[0] ?? profile.promptTransport;
446
467
  const resolution = resolveCodexAutonomyCandidates(profile)[0];
447
468
  let candidateSummary = "No advertised Codex invocation strategy supports governed workspace writes.";
@@ -470,7 +491,9 @@ export function probeCodexLaunch(input) {
470
491
  const args = buildLocalSandboxProbeArgs({
471
492
  workingDirectory: input.workingDirectory,
472
493
  markerPath,
473
- outsideMarkerPath
494
+ outsideMarkerPath,
495
+ platform,
496
+ capabilityProfile: profile
474
497
  });
475
498
  const plan = input.spawnSyncImpl
476
499
  ? buildInjectedSpawnPlan(candidate.path, args, platform)
@@ -1,6 +1,6 @@
1
1
  export { createDirectProviderAdapter, type DirectProviderAdapterOptions } from "./direct-provider.js";
2
2
  export { createAgentCliAdapter, createClaudeCliAdapter, createGeminiCliAdapter, type AgentCliAdapterOptions, type ClaudeCliAdapterOptions, type GeminiCliAdapterOptions, type CliArgsBuilder } from "./claude-cli.js";
3
- export { createCodexCliAdapter, type CodexCliAdapterOptions } from "./codex-cli.js";
3
+ export { createCodexCliAdapter, CODEX_MINIMUM_VIABLE_TOKEN_BUDGET, CODEX_TOKEN_BUDGET_PREFLIGHT_BASIS, type CodexCliAdapterOptions } from "./codex-cli.js";
4
4
  export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig, type OpenAiCompatibleAdapterOptions } from "./openai-compatible.js";
5
5
  export { createSwarmVerifierExecutor, createVerifierOnlyAdapter, type SwarmVerifierExecutorOptions, type VerifierOnlyAdapterOptions } from "./verifier-only.js";
6
6
  /** @internal Test-only compatibility export. Public package builders strip this surface. */
@@ -1,6 +1,6 @@
1
1
  export { createDirectProviderAdapter } from "./direct-provider.js";
2
2
  export { createAgentCliAdapter, createClaudeCliAdapter, createGeminiCliAdapter } from "./claude-cli.js";
3
- export { createCodexCliAdapter } from "./codex-cli.js";
3
+ export { createCodexCliAdapter, CODEX_MINIMUM_VIABLE_TOKEN_BUDGET, CODEX_TOKEN_BUDGET_PREFLIGHT_BASIS } from "./codex-cli.js";
4
4
  export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig } from "./openai-compatible.js";
5
5
  export { createSwarmVerifierExecutor, createVerifierOnlyAdapter } from "./verifier-only.js";
6
6
  /** @internal Test-only compatibility export. Public package builders strip this surface. */
@@ -368,6 +368,10 @@ export interface BudgetPreflightEstimate {
368
368
  estimatedPromptTokens: number;
369
369
  estimatedToolOverheadTokens: number;
370
370
  estimatedOutputTokensMax: number;
371
+ /** Provider-specific minimum viable first-turn envelope, never actual usage. */
372
+ estimatedMinimumViableTokens: number;
373
+ /** Largest of the generic attempt estimate and provider minimum. */
374
+ estimatedTotalTokens: number;
371
375
  estimatedVerifierCostUsd: number;
372
376
  estimatedAttemptCostUsd: number;
373
377
  provenance: CostProvenance;
@@ -177,6 +177,11 @@ export interface MartinAdapter {
177
177
  providerId: string;
178
178
  model?: string;
179
179
  transport?: "cli" | "http" | "routed_http";
180
+ budgetPreflight?: {
181
+ /** Estimated token floor required before the adapter can report first-turn usage. */
182
+ minimumViableTokens?: number;
183
+ basis?: string;
184
+ };
180
185
  capabilities?: {
181
186
  preflight?: boolean;
182
187
  usageSettlement?: boolean;
@@ -600,6 +600,8 @@ export async function runMartin(input) {
600
600
  promptCharCount: distilled.focus.length + loop.task.objective.length * 3,
601
601
  attemptCount: loop.attempts.length,
602
602
  remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
603
+ remainingTokens: distilled.constraints.remainingTokens,
604
+ minimumViableTokens: executingAdapter.metadata.budgetPreflight?.minimumViableTokens,
603
605
  perAttemptCapUsd: loop.budget.maxUsd * 0.25
604
606
  });
605
607
  if (!budgetPreflight.allowed) {
@@ -103,6 +103,9 @@ export interface BudgetPreflightInput {
103
103
  promptCharCount: number;
104
104
  attemptCount: number;
105
105
  remainingBudgetUsd: number;
106
+ remainingTokens?: number;
107
+ /** Adapter-declared estimated floor required to admit a credible first turn. */
108
+ minimumViableTokens?: number;
106
109
  perAttemptCapUsd?: number;
107
110
  pricePerMTokenUsd?: number;
108
111
  }
@@ -365,6 +365,8 @@ export function evaluateBudgetPreflight(input) {
365
365
  const estimatedPromptTokens = Math.ceil(rawPromptTokens * 1.2);
366
366
  const estimatedToolOverheadTokens = 800 + input.attemptCount * 200;
367
367
  const estimatedOutputTokensMax = 4_000;
368
+ const estimatedMinimumViableTokens = Math.max(0, input.minimumViableTokens ?? 0);
369
+ const estimatedTotalTokens = Math.max(estimatedPromptTokens + estimatedToolOverheadTokens + estimatedOutputTokensMax, estimatedMinimumViableTokens);
368
370
  const estimatedVerifierCostUsd = 0.01;
369
371
  const estimatedAttemptCostUsd = roundUsd(((estimatedPromptTokens + estimatedToolOverheadTokens + estimatedOutputTokensMax) /
370
372
  1_000_000) *
@@ -374,10 +376,21 @@ export function evaluateBudgetPreflight(input) {
374
376
  estimatedPromptTokens,
375
377
  estimatedToolOverheadTokens,
376
378
  estimatedOutputTokensMax,
379
+ estimatedMinimumViableTokens,
380
+ estimatedTotalTokens,
377
381
  estimatedVerifierCostUsd,
378
382
  estimatedAttemptCostUsd,
379
383
  provenance
380
384
  };
385
+ if (input.remainingTokens !== undefined
386
+ && estimatedMinimumViableTokens > 0
387
+ && estimatedTotalTokens > input.remainingTokens) {
388
+ return {
389
+ allowed: false,
390
+ reason: `Preflight: ${String(estimatedTotalTokens)} estimated tokens exceed ${String(input.remainingTokens)} remaining tokens.`,
391
+ estimate
392
+ };
393
+ }
381
394
  if (estimatedAttemptCostUsd > input.remainingBudgetUsd) {
382
395
  return {
383
396
  allowed: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@martinloop/mcp",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "mcpName": "io.github.Keesan12/martin-loop",
5
5
  "private": false,
6
6
  "type": "module",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/Keesan12/martin-loop",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.8.0",
10
+ "version": "0.8.1",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@martinloop/mcp",
15
- "version": "0.8.0",
15
+ "version": "0.8.1",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }