@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
@@ -1,6 +1,8 @@
1
1
  export { createDirectProviderAdapter, type DirectProviderAdapterOptions } from "./direct-provider.js";
2
- export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, type AgentCliAdapterOptions, type ClaudeCliAdapterOptions, type CodexCliAdapterOptions, type GeminiCliAdapterOptions, type CliArgsBuilder } from "./claude-cli.js";
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
4
  export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig, type OpenAiCompatibleAdapterOptions } from "./openai-compatible.js";
4
5
  export { createVerifierOnlyAdapter, type VerifierOnlyAdapterOptions } from "./verifier-only.js";
5
- export { detectCodexHostPlatform, diagnoseCodexHost, probeCodexLaunch, resolveCliCommandAvailability, type CliCommandAvailability, type CodexHostDiagnosis, type CodexHostPlatform, type CodexLaunchProbeResult, 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 { 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";
6
8
  export { createSpawnPlan, type SpawnLike, type SpawnPlan, type SubprocessResult, type VerificationOutcome } from "./cli-bridge.js";
@@ -1,6 +1,8 @@
1
1
  export { createDirectProviderAdapter } from "./direct-provider.js";
2
- export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter } from "./claude-cli.js";
2
+ export { createAgentCliAdapter, createClaudeCliAdapter, createGeminiCliAdapter } from "./claude-cli.js";
3
+ export { createCodexCliAdapter } from "./codex-cli.js";
3
4
  export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig } from "./openai-compatible.js";
4
5
  export { createVerifierOnlyAdapter } from "./verifier-only.js";
5
- export { detectCodexHostPlatform, diagnoseCodexHost, probeCodexLaunch, resolveCliCommandAvailability, checkCodexSandboxPreflight, probeFilesystemWriteCapability } from "./codex-launcher.js";
6
+ /** @internal Test-only compatibility export. Public package builders strip this surface. */
7
+ export { buildCodexExecArgs, buildCodexStdin, clearCodexCapabilityCacheForTests, detectCodexHostPlatform, diagnoseCodexHost, probeCodexCapabilities, probeCodexLaunch, resolveCliCommandAvailability, checkCodexSandboxPreflight, probeFilesystemWriteCapability } from "./codex-launcher.js";
6
8
  export { createSpawnPlan } from "./cli-bridge.js";
@@ -0,0 +1,9 @@
1
+ export declare const DEFAULT_AGENT_EXECUTION_INTENT: "governed-autonomous";
2
+ export type AgentExecutionIntent = typeof DEFAULT_AGENT_EXECUTION_INTENT;
3
+ /** Provider-neutral safety boundary every governed-autonomous adapter must honor. */
4
+ export declare const GOVERNED_AUTONOMOUS_BOUNDARY: {
5
+ readonly interaction: "non-interactive";
6
+ readonly writeScope: "workspace-only";
7
+ };
8
+ export declare const DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS = 300000;
9
+ export declare function normalizeProviderExecutionTimeoutMs(value: number | undefined): number;
@@ -0,0 +1,14 @@
1
+ export const DEFAULT_AGENT_EXECUTION_INTENT = "governed-autonomous";
2
+ /** Provider-neutral safety boundary every governed-autonomous adapter must honor. */
3
+ export const GOVERNED_AUTONOMOUS_BOUNDARY = {
4
+ interaction: "non-interactive",
5
+ writeScope: "workspace-only"
6
+ };
7
+ export const DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS = 300_000;
8
+ export function normalizeProviderExecutionTimeoutMs(value) {
9
+ const resolved = value ?? DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS;
10
+ if (!Number.isFinite(resolved) || resolved <= 0) {
11
+ throw new Error("providerExecutionTimeoutMs must be a positive finite number.");
12
+ }
13
+ return resolved;
14
+ }
@@ -8,6 +8,9 @@
8
8
  * portfolio snapshots and routing economics.
9
9
  */
10
10
  import type { TerminationEnvelopeV1 } from "./exits.js";
11
+ import type { AgentExecutionIntent } from "./agent-execution.js";
12
+ export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "./agent-execution.js";
13
+ export type { AgentExecutionIntent } from "./agent-execution.js";
11
14
  export type LoopStatus = "queued" | "running" | "verifying" | "completed" | "failed" | "exited";
12
15
  export type LoopLifecycleState = "created" | "running" | "verifying" | "completed" | "budget_exit" | "diminishing_returns" | "stuck_exit" | "human_escalation" | "wall_clock" | "error_threshold" | "external_event";
13
16
  export declare const FAILURE_CLASSES: readonly ["logic_error", "hallucination", "syntax_error", "type_error", "test_regression", "scope_creep", "no_progress", "repo_grounding_failure", "verification_failure", "environment_mismatch", "budget_pressure", "safety_leash_blocked", "sandbox_write_blocked"];
@@ -27,6 +30,10 @@ export interface LoopTask {
27
30
  executionProfile?: ExecutionProfile;
28
31
  allowedNetworkDomains?: string[];
29
32
  approvalPolicy?: ApprovalPolicy;
33
+ /** Provider-neutral execution posture. Defaults to governed autonomous execution. */
34
+ agentExecutionIntent?: AgentExecutionIntent;
35
+ /** Hard wall-clock limit for one provider coding process. */
36
+ providerExecutionTimeoutMs?: number;
30
37
  /** Glob patterns for files the agent is allowed to modify. Empty = no restriction. */
31
38
  allowedPaths?: string[];
32
39
  /** Glob patterns for files the agent must never modify. */
@@ -103,6 +110,8 @@ export interface ReceiptScope {
103
110
  writableRoot?: string;
104
111
  /** How the effective sandbox was determined. */
105
112
  capabilitySource?: "probe" | "configured" | "unknown";
113
+ agentExecutionIntent?: AgentExecutionIntent;
114
+ providerExecutionTimeoutMs?: number;
106
115
  /** Enforced demo changes (DEMO.md-only enforcement output). */
107
116
  demoChangedFiles?: string[];
108
117
  }
@@ -7,6 +7,7 @@
7
7
  * creating and managing loop records, handling events, validating batches, and tracking
8
8
  * portfolio snapshots and routing economics.
9
9
  */
10
+ export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "./agent-execution.js";
10
11
  export const FAILURE_CLASSES = [
11
12
  "logic_error",
12
13
  "hallucination",
@@ -196,7 +196,14 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
196
196
  const indexedPaths = new Set(index.files.map((file) => file.path));
197
197
  const indexedSymbols = new Set(index.files.flatMap((file) => file.symbols.map((symbol) => symbol.toLowerCase())));
198
198
  const referencedFiles = new Set();
199
+ const addedFiles = new Set();
199
200
  const diffFilePattern = /^(?:---|\+\+\+)\s+[ab]\/(.+)$/gm;
201
+ const addedFilePattern = /^---\s+\/dev\/null\r?\n\+\+\+\s+b\/(.+)$/gm;
202
+ for (const match of diff.matchAll(addedFilePattern)) {
203
+ const filePath = match[1]?.trim();
204
+ if (filePath)
205
+ addedFiles.add(filePath);
206
+ }
200
207
  for (const match of diff.matchAll(diffFilePattern)) {
201
208
  const filePath = match[1]?.trim();
202
209
  if (filePath && filePath !== "/dev/null") {
@@ -204,7 +211,7 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
204
211
  }
205
212
  }
206
213
  for (const filePath of referencedFiles) {
207
- if (indexedPaths.has(filePath)) {
214
+ if (indexedPaths.has(filePath) || addedFiles.has(filePath)) {
208
215
  resolvedFiles.push(filePath);
209
216
  }
210
217
  else {
@@ -227,7 +234,7 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
227
234
  }
228
235
  const addedLinePattern = /^\+(?!\+\+)(.+)$/gm;
229
236
  const importPattern = /(?:import|require)\s*(?:.*\s+from\s+)?['"]([^'"]+)['"]/g;
230
- const identifierPattern = /\b([A-Za-z_][A-Za-z0-9_]{2,})\b/g;
237
+ const callableIdentifierPattern = /(?<!\.)\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
231
238
  const symbolKeywords = new Set([
232
239
  "const",
233
240
  "let",
@@ -244,10 +251,33 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
244
251
  "true",
245
252
  "false",
246
253
  "null",
247
- "undefined"
254
+ "undefined",
255
+ "if",
256
+ "for",
257
+ "while",
258
+ "switch",
259
+ "catch",
260
+ "typeof"
248
261
  ]);
249
- for (const lineMatch of diff.matchAll(addedLinePattern)) {
250
- const line = lineMatch[1] ?? "";
262
+ const addedLines = [...diff.matchAll(addedLinePattern)].map((match) => match[1] ?? "");
263
+ const declaredSymbols = new Set();
264
+ const declarationPattern = /\b(?:function|class|interface|type|enum|const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)/g;
265
+ for (const line of addedLines) {
266
+ for (const declarationMatch of line.matchAll(declarationPattern)) {
267
+ const symbol = declarationMatch[1];
268
+ if (symbol)
269
+ declaredSymbols.add(symbol.toLowerCase());
270
+ }
271
+ if (/^\s*import\b/u.test(line)) {
272
+ const importClause = line.match(/^\s*import\s+(.+?)\s+from\s+['"]/u)?.[1] ?? "";
273
+ for (const importSymbol of importClause.matchAll(/\b(?:as\s+)?([A-Za-z_][A-Za-z0-9_]*)\b/g)) {
274
+ const symbol = importSymbol[1];
275
+ if (symbol && symbol !== "as")
276
+ declaredSymbols.add(symbol.toLowerCase());
277
+ }
278
+ }
279
+ }
280
+ for (const line of addedLines) {
251
281
  for (const importMatch of line.matchAll(importPattern)) {
252
282
  const reference = importMatch[1] ?? "";
253
283
  if (!reference.startsWith(".") && !reference.startsWith("/")) {
@@ -259,6 +289,9 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
259
289
  const hasMatch = index.files.some((file) => {
260
290
  const withoutExt = file.path.replace(/\.(js|ts|tsx|jsx)$/, "");
261
291
  return withoutExt.endsWith(normalized) || withoutExt.includes(normalized);
292
+ }) || [...addedFiles].some((filePath) => {
293
+ const withoutExt = filePath.replace(/\.(js|ts|tsx|jsx)$/, "");
294
+ return withoutExt.endsWith(normalized) || withoutExt.includes(normalized);
262
295
  });
263
296
  if (!hasMatch) {
264
297
  violations.push({
@@ -268,13 +301,15 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
268
301
  });
269
302
  }
270
303
  }
271
- for (const symbolMatch of line.matchAll(identifierPattern)) {
304
+ for (const symbolMatch of line.matchAll(callableIdentifierPattern)) {
272
305
  const symbol = symbolMatch[1];
273
306
  if (!symbol)
274
307
  continue;
275
308
  if (symbolKeywords.has(symbol))
276
309
  continue;
277
- if (symbol.toUpperCase() === symbol)
310
+ if (/^[A-Z]/u.test(symbol))
311
+ continue;
312
+ if (declaredSymbols.has(symbol.toLowerCase()))
278
313
  continue;
279
314
  if (indexedSymbols.has(symbol.toLowerCase()))
280
315
  continue;
@@ -1,4 +1,4 @@
1
- import { type ApprovalPolicy, type ContextHandoffReceipt, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
1
+ import { type ApprovalPolicy, type AgentExecutionIntent, type ContextHandoffReceipt, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
2
2
  import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, type ExitDecision } from "./policy.js";
3
3
  import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
4
4
  import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
@@ -7,7 +7,8 @@ import { type RunStore } from "./persistence/index.js";
7
7
  import { type ExitPolicyOverrides } from "./exits.js";
8
8
  import { type ExitSignalSource } from "./exit-signal.js";
9
9
  import { type VerifierExecutionBinding } from "./verified-handoff.js";
10
- export type { ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, MutationMode, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase, CallStage, AgentRole, FirstDelta, RoutingEconomics } from "../contracts/index.js";
10
+ export type { AgentExecutionIntent, ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, MutationMode, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase, CallStage, AgentRole, FirstDelta, RoutingEconomics } from "../contracts/index.js";
11
+ export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
11
12
  export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
12
13
  export type { BudgetPreflightDecision, BudgetPreflightInput, CostGovernorState, EvidenceVectorInput, EvaluatedPatchDecision, ExitDecision, FailureAssessment, PatchDecisionInput, RecoveryDecision, RecoveryRecipe } from "./policy.js";
13
14
  export type { ResolvedExecutionProfile, SafetyLeashDecision, SafetyViolation } from "./leash.js";
@@ -69,6 +70,8 @@ export interface MartinAdapterRequest {
69
70
  executionProfile?: ExecutionProfile;
70
71
  allowedNetworkDomains?: string[];
71
72
  approvalPolicy?: ApprovalPolicy;
73
+ agentExecutionIntent?: AgentExecutionIntent;
74
+ providerExecutionTimeoutMs?: number;
72
75
  focus: string;
73
76
  remainingBudgetUsd: number;
74
77
  remainingIterations: number;
@@ -1,6 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { isAbsolute, relative, resolve } from "node:path";
3
- import { appendLoopEvent, createLoopRecord } from "../contracts/index.js";
3
+ import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs, appendLoopEvent, createLoopRecord } from "../contracts/index.js";
4
4
  import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } from "./policy.js";
5
5
  import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
6
6
  import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
@@ -15,6 +15,7 @@ import { runContextIntegrityPrecheck } from "./context-integrity.js";
15
15
  import { decideCircuitBreak } from "./trajectory.js";
16
16
  import { verifyContextHandoff, decideContextCircuitBreak } from "./context-handoff.js";
17
17
  import { verifierActuallyPassed, } from "./verified-handoff.js";
18
+ export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
18
19
  export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
19
20
  export { buildContextGraphSnapshot, queryContextGraph } from "./context-graph.js";
20
21
  export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, verifyIdentityToken } from "./identity.js";
@@ -160,12 +161,16 @@ export function distillContext(loop, options = {}) {
160
161
  export async function runMartin(input) {
161
162
  const now = input.now ?? (() => new Date().toISOString());
162
163
  const idFactory = input.idFactory;
164
+ const providerExecutionTimeoutMs = normalizeProviderExecutionTimeoutMs(input.task.providerExecutionTimeoutMs);
165
+ const agentExecutionIntent = input.task.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT;
163
166
  let loop = createLoopRecord({
164
167
  workspaceId: input.workspaceId,
165
168
  projectId: input.projectId,
166
- task: input.task,
169
+ task: { ...input.task, agentExecutionIntent, providerExecutionTimeoutMs },
167
170
  budget: input.budget,
168
- ...(input.receiptScope ? { receiptScope: input.receiptScope } : {}),
171
+ ...(input.receiptScope
172
+ ? { receiptScope: { ...input.receiptScope, agentExecutionIntent, providerExecutionTimeoutMs } }
173
+ : {}),
169
174
  ...(input.teamId ? { teamId: input.teamId } : {}),
170
175
  ...(input.metadata ? { metadata: input.metadata } : {})
171
176
  }, { now: now(), idFactory });
@@ -655,6 +660,8 @@ export async function runMartin(input) {
655
660
  ? { allowedNetworkDomains: loop.task.allowedNetworkDomains }
656
661
  : {}),
657
662
  ...(loop.task.approvalPolicy ? { approvalPolicy: loop.task.approvalPolicy } : {}),
663
+ agentExecutionIntent: loop.task.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT,
664
+ providerExecutionTimeoutMs: normalizeProviderExecutionTimeoutMs(loop.task.providerExecutionTimeoutMs),
658
665
  focus: distilled.focus,
659
666
  remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
660
667
  remainingIterations: distilled.constraints.remainingIterations,
@@ -745,6 +752,8 @@ export async function runMartin(input) {
745
752
  ? { allowedNetworkDomains: loop.task.allowedNetworkDomains }
746
753
  : {}),
747
754
  ...(loop.task.approvalPolicy ? { approvalPolicy: loop.task.approvalPolicy } : {}),
755
+ agentExecutionIntent: loop.task.agentExecutionIntent ?? DEFAULT_AGENT_EXECUTION_INTENT,
756
+ providerExecutionTimeoutMs: normalizeProviderExecutionTimeoutMs(loop.task.providerExecutionTimeoutMs),
748
757
  focus: distilled.focus,
749
758
  remainingBudgetUsd: distilled.constraints.remainingBudgetUsd,
750
759
  remainingIterations: distilled.constraints.remainingIterations,
@@ -0,0 +1,33 @@
1
+ import type { LoopBudget, ReceiptScope } from "../contracts/index.js";
2
+ import { type RenderEnvironment } from "./theme.js";
3
+ export type PlanStageState = "READY" | "BLOCKED" | "NEXT" | "CONDITIONAL";
4
+ export interface GovernedPlanStage {
5
+ label: string;
6
+ state: PlanStageState;
7
+ gate: string;
8
+ purpose: string;
9
+ }
10
+ export interface GovernedRunPlanView {
11
+ ready: boolean;
12
+ task: string;
13
+ engine: string;
14
+ mode: "live" | "proof";
15
+ budget: LoopBudget;
16
+ verifier: string[];
17
+ receiptScope: ReceiptScope;
18
+ policyProfile: string;
19
+ blockingIssues: string[];
20
+ warnings: string[];
21
+ stages: GovernedPlanStage[];
22
+ }
23
+ export declare function buildGovernedPlanStages(input: Pick<GovernedRunPlanView, "ready" | "engine" | "verifier" | "blockingIssues">): GovernedPlanStage[];
24
+ export declare function renderGovernedRunPlan(input: GovernedRunPlanView, options?: {
25
+ width?: number;
26
+ environment?: RenderEnvironment;
27
+ }): string;
28
+ /**
29
+ * Renders a GovernedRunPlanView as plain Markdown (no ANSI).
30
+ */
31
+ export declare function renderGovernedRunPlanMarkdown(input: GovernedRunPlanView, options?: {
32
+ includeStatus?: boolean;
33
+ }): string;
@@ -0,0 +1,146 @@
1
+ import { renderTable } from "./table.js";
2
+ import { horizontalRule, terminalWidth, truncateVisible } from "./text.js";
3
+ import { bold, paint, } from "./theme.js";
4
+ function stageTone(state) {
5
+ switch (state) {
6
+ case "READY":
7
+ return "success";
8
+ case "BLOCKED":
9
+ return "danger";
10
+ case "NEXT":
11
+ return "info";
12
+ case "CONDITIONAL":
13
+ return "warning";
14
+ }
15
+ }
16
+ export function buildGovernedPlanStages(input) {
17
+ const blocked = !input.ready || input.blockingIssues.length > 0;
18
+ return [
19
+ {
20
+ label: "Contract & scope",
21
+ state: blocked ? "BLOCKED" : "READY",
22
+ gate: "request + paths",
23
+ purpose: "Bind the task to its governed workspace",
24
+ },
25
+ {
26
+ label: "Budget & policy",
27
+ state: blocked ? "BLOCKED" : "READY",
28
+ gate: "hard cap + policy",
29
+ purpose: "Arm execution limits before work starts",
30
+ },
31
+ {
32
+ label: "Agent execution",
33
+ state: blocked ? "BLOCKED" : "NEXT",
34
+ gate: input.engine,
35
+ purpose: "Run the selected coding agent",
36
+ },
37
+ {
38
+ label: "Verification",
39
+ state: input.verifier.length > 0 && !blocked ? "NEXT" : "BLOCKED",
40
+ gate: input.verifier.length > 0 ? input.verifier.join(", ") : "no verifier",
41
+ purpose: "Run configured completion checks",
42
+ },
43
+ {
44
+ label: "Recovery",
45
+ state: "CONDITIONAL",
46
+ gate: "stop / fail",
47
+ purpose: "Preserve rollback and recovery evidence",
48
+ },
49
+ {
50
+ label: "Proof",
51
+ state: blocked ? "BLOCKED" : "NEXT",
52
+ gate: "receipt",
53
+ purpose: "Produce the final governed record",
54
+ },
55
+ ];
56
+ }
57
+ function bounded(line, width) {
58
+ return truncateVisible(line, width);
59
+ }
60
+ export function renderGovernedRunPlan(input, options = {}) {
61
+ const environment = options.environment ?? {};
62
+ const width = terminalWidth(options.width);
63
+ const rows = input.stages.map((stage) => ({
64
+ ...stage,
65
+ renderedState: paint(stage.state, stageTone(stage.state), environment),
66
+ }));
67
+ const lines = [
68
+ horizontalRule(width, "━"),
69
+ bounded(bold("MARTINLOOP", environment) +
70
+ " ▸ " +
71
+ paint("GOVERNED RUN PLAN", input.ready ? "success" : "danger", environment), width),
72
+ horizontalRule(width, "━"),
73
+ "",
74
+ bounded(paint("Task".padEnd(18), "info", environment) + input.task, width),
75
+ bounded(paint("Engine".padEnd(18), "info", environment) + input.engine, width),
76
+ bounded(paint("Mode".padEnd(18), "info", environment) + input.mode, width),
77
+ "",
78
+ renderTable(rows, [
79
+ { header: "STAGE", value: (row) => row.label, minWidth: 18, maxWidth: 24 },
80
+ {
81
+ header: "STATUS",
82
+ value: (row) => row.renderedState,
83
+ minWidth: 12,
84
+ maxWidth: 14,
85
+ },
86
+ { header: "GATE", value: (row) => row.gate, minWidth: 18, maxWidth: 36 },
87
+ { header: "PURPOSE", value: (row) => row.purpose, minWidth: 24 },
88
+ ], width),
89
+ "",
90
+ bounded(bold("Budget cap", environment) +
91
+ " " +
92
+ paint("$" + input.budget.maxUsd.toFixed(2), "success", environment), width),
93
+ bounded(bold("Soft limit", environment) +
94
+ " $" +
95
+ input.budget.softLimitUsd.toFixed(2), width),
96
+ bounded(bold("Attempts", environment) + " " + input.budget.maxIterations, width),
97
+ bounded(bold("Verifier", environment) +
98
+ " " +
99
+ (input.verifier.join(", ") || "NOT CONFIGURED"), width),
100
+ bounded(bold("Policy", environment) + " " + input.policyProfile, width),
101
+ ];
102
+ if (input.blockingIssues.length > 0) {
103
+ lines.push("", bold("BLOCKING", environment), ...input.blockingIssues.map((issue) => bounded(" " + paint("✗", "danger", environment) + " " + issue, width)));
104
+ }
105
+ if (input.warnings.length > 0) {
106
+ lines.push("", bold("WARNINGS", environment), ...input.warnings.map((warning) => bounded(" " + paint("!", "warning", environment) + " " + warning, width)));
107
+ }
108
+ lines.push("", input.ready
109
+ ? paint("✓ READY TO EXECUTE", "success", environment)
110
+ : paint("✗ PREFLIGHT BLOCKED", "danger", environment), horizontalRule(width, "━"));
111
+ return lines.map((line) => bounded(line, width)).join("\n");
112
+ }
113
+ /**
114
+ * Renders a GovernedRunPlanView as plain Markdown (no ANSI).
115
+ */
116
+ export function renderGovernedRunPlanMarkdown(input, options = {}) {
117
+ void options;
118
+ const lines = [
119
+ `## MartinLoop Governed Run Plan — ${input.ready ? "READY" : "BLOCKED"}`,
120
+ "",
121
+ "| Field | Value |",
122
+ "|-------|-------|",
123
+ `| Task | ${input.task} |`,
124
+ `| Engine | ${input.engine} |`,
125
+ `| Mode | ${input.mode} |`,
126
+ `| Budget cap | $${input.budget.maxUsd.toFixed(2)} |`,
127
+ `| Soft limit | $${input.budget.softLimitUsd.toFixed(2)} |`,
128
+ `| Max attempts | ${input.budget.maxIterations} |`,
129
+ `| Verifier | ${input.verifier.join(", ") || "NOT CONFIGURED"} |`,
130
+ `| Policy | ${input.policyProfile} |`,
131
+ "",
132
+ "### Stages",
133
+ "",
134
+ "| Stage | Status | Gate | Purpose |",
135
+ "|-------|--------|------|---------|",
136
+ ...input.stages.map((s) => `| ${s.label} | ${s.state} | ${s.gate} | ${s.purpose} |`),
137
+ ];
138
+ if (input.blockingIssues.length > 0) {
139
+ lines.push("", "### Blocking Issues", "", ...input.blockingIssues.map((issue) => `- ✗ ${issue}`));
140
+ }
141
+ if (input.warnings.length > 0) {
142
+ lines.push("", "### Warnings", "", ...input.warnings.map((w) => `- ${w}`));
143
+ }
144
+ lines.push("", input.ready ? "**✓ READY TO EXECUTE**" : "**✗ PREFLIGHT BLOCKED**");
145
+ return lines.join("\n");
146
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./governed-plan.js";
2
+ export * from "./motion.js";
3
+ export * from "./status.js";
4
+ export * from "./table.js";
5
+ export * from "./text.js";
6
+ export * from "./theme.js";
7
+ export * from "./verified-handoff.js";
@@ -0,0 +1,7 @@
1
+ export * from "./governed-plan.js";
2
+ export * from "./motion.js";
3
+ export * from "./status.js";
4
+ export * from "./table.js";
5
+ export * from "./text.js";
6
+ export * from "./theme.js";
7
+ export * from "./verified-handoff.js";
@@ -0,0 +1,12 @@
1
+ import { type RenderEnvironment } from "./theme.js";
2
+ export interface MotionEnvironment extends RenderEnvironment {
3
+ readonly outputMode?: "human" | "json" | "quiet";
4
+ readonly stdinIsTty?: boolean;
5
+ readonly stdoutIsTty?: boolean;
6
+ readonly ci?: boolean;
7
+ readonly noMotion?: boolean;
8
+ }
9
+ export declare function canAnimate(env?: MotionEnvironment): boolean;
10
+ export declare function animateBannerShine(input: string, env?: MotionEnvironment): Promise<void>;
11
+ export declare function withSpinner<T>(label: string, task: () => Promise<T>, env?: MotionEnvironment): Promise<T>;
12
+ export declare function writeHumanOutputWithShine(text: string, env?: MotionEnvironment): Promise<void>;
@@ -0,0 +1,123 @@
1
+ import { setTimeout as sleep } from "node:timers/promises";
2
+ import { paint, terminalAnsi } from "./theme.js";
3
+ import { stripAnsi } from "./text.js";
4
+ function envFlag(name) {
5
+ const value = process.env[name];
6
+ return value === "1" || value === "true" || value === "yes";
7
+ }
8
+ export function canAnimate(env = {}) {
9
+ const mode = env.outputMode ?? "human";
10
+ if (mode !== "human") {
11
+ return false;
12
+ }
13
+ const noColor = env.noColor ?? Object.prototype.hasOwnProperty.call(process.env, "NO_COLOR");
14
+ if (noColor) {
15
+ return false;
16
+ }
17
+ const stdoutTty = env.stdoutIsTty ?? process.stdout.isTTY === true;
18
+ if (!stdoutTty) {
19
+ return false;
20
+ }
21
+ const ci = env.ci ?? Boolean(process.env.CI);
22
+ if (ci) {
23
+ return false;
24
+ }
25
+ const noMotion = env.noMotion ??
26
+ (envFlag("MARTIN_NO_MOTION") || envFlag("MARTIN_REDUCED_MOTION"));
27
+ if (noMotion) {
28
+ return false;
29
+ }
30
+ const term = env.term ?? process.env.TERM;
31
+ return term !== "dumb";
32
+ }
33
+ function shineCharacter(character, distance, env) {
34
+ if (character === " ") {
35
+ return character;
36
+ }
37
+ if (distance === 0) {
38
+ return paint(character, "plain", { ...env, color: "always" });
39
+ }
40
+ if (distance === 1) {
41
+ return paint(character, "info", { ...env, color: "always" });
42
+ }
43
+ return paint(character, "brand", { ...env, color: "always" });
44
+ }
45
+ export async function animateBannerShine(input, env = {}) {
46
+ if (!canAnimate(env)) {
47
+ return;
48
+ }
49
+ const target = [...stripAnsi(input)].slice(0, 100);
50
+ if (target.length === 0) {
51
+ return;
52
+ }
53
+ try {
54
+ process.stdout.write(terminalAnsi.hideCursor);
55
+ for (let center = -2; center < target.length + 4; center += 1) {
56
+ const frame = target
57
+ .map((character, index) => shineCharacter(character, Math.abs(index - center), env))
58
+ .join("");
59
+ process.stdout.write(terminalAnsi.cursorHome +
60
+ terminalAnsi.eraseLine +
61
+ frame +
62
+ terminalAnsi.reset);
63
+ await sleep(18);
64
+ }
65
+ }
66
+ finally {
67
+ process.stdout.write(terminalAnsi.cursorHome +
68
+ terminalAnsi.eraseLine +
69
+ terminalAnsi.reset +
70
+ terminalAnsi.showCursor);
71
+ }
72
+ }
73
+ const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
74
+ export async function withSpinner(label, task, env = {}) {
75
+ if (!canAnimate(env)) {
76
+ return task();
77
+ }
78
+ let frame = 0;
79
+ process.stdout.write(terminalAnsi.hideCursor);
80
+ const timer = setInterval(() => {
81
+ const glyph = spinnerFrames[frame % spinnerFrames.length] ?? "•";
82
+ frame += 1;
83
+ process.stdout.write(terminalAnsi.cursorHome +
84
+ terminalAnsi.eraseLine +
85
+ paint(glyph, "brand", { ...env, color: "always" }) +
86
+ " " +
87
+ label);
88
+ }, 70);
89
+ try {
90
+ return await task();
91
+ }
92
+ finally {
93
+ clearInterval(timer);
94
+ process.stdout.write(terminalAnsi.cursorHome +
95
+ terminalAnsi.eraseLine +
96
+ terminalAnsi.reset +
97
+ terminalAnsi.showCursor);
98
+ }
99
+ }
100
+ export async function writeHumanOutputWithShine(text, env = {}) {
101
+ if (!text) {
102
+ return;
103
+ }
104
+ if (!canAnimate(env)) {
105
+ process.stdout.write(text + "\n");
106
+ return;
107
+ }
108
+ const lines = text.split("\n");
109
+ const headingIndex = lines.findIndex((line) => stripAnsi(line).toUpperCase().includes("MARTINLOOP"));
110
+ if (headingIndex < 0) {
111
+ process.stdout.write(text + "\n");
112
+ return;
113
+ }
114
+ if (headingIndex > 0) {
115
+ process.stdout.write(lines.slice(0, headingIndex).join("\n") + "\n");
116
+ }
117
+ const heading = lines[headingIndex] ?? "";
118
+ await animateBannerShine(heading, env);
119
+ process.stdout.write(heading + "\n");
120
+ if (headingIndex < lines.length - 1) {
121
+ process.stdout.write(lines.slice(headingIndex + 1).join("\n") + "\n");
122
+ }
123
+ }
@@ -0,0 +1,8 @@
1
+ import type { MartinTone } from "./theme.js";
2
+ export type ProofStatus = "VERIFIED" | "NEEDS_REVIEW" | "STOPPED" | "FAILED" | "RUNNING" | "PENDING";
3
+ export interface StatusPresentation {
4
+ readonly glyph: string;
5
+ readonly label: string;
6
+ readonly tone: MartinTone;
7
+ }
8
+ export declare function statusPresentation(status: ProofStatus): StatusPresentation;
@@ -0,0 +1,11 @@
1
+ const presentations = {
2
+ VERIFIED: { glyph: "✓", label: "VERIFIED", tone: "success" },
3
+ NEEDS_REVIEW: { glyph: "!", label: "NEEDS REVIEW", tone: "warning" },
4
+ STOPPED: { glyph: "■", label: "STOPPED", tone: "danger" },
5
+ FAILED: { glyph: "×", label: "FAILED", tone: "danger" },
6
+ RUNNING: { glyph: "◆", label: "RUNNING", tone: "info" },
7
+ PENDING: { glyph: "○", label: "PENDING", tone: "muted" },
8
+ };
9
+ export function statusPresentation(status) {
10
+ return presentations[status];
11
+ }
@@ -0,0 +1,11 @@
1
+ export interface TableColumn<Row> {
2
+ readonly header: string;
3
+ readonly value: (row: Row) => string;
4
+ readonly minWidth?: number;
5
+ readonly maxWidth?: number;
6
+ }
7
+ export interface RenderTableOptions {
8
+ readonly separator?: string;
9
+ readonly width?: number;
10
+ }
11
+ export declare function renderTable<Row>(rows: readonly Row[], columns: readonly TableColumn<Row>[], options?: RenderTableOptions | number): string;