martin-loop
Advanced tools
@@ -14,3 +14,3 @@ /** | ||
| */ | ||
| import type { MartinAdapter, MartinAdapterRequest } from "../core/index.js"; | ||
| import type { MartinAdapter } from "../core/index.js"; | ||
| import { type SpawnLike } from "./cli-bridge.js"; | ||
@@ -22,3 +22,3 @@ /** | ||
| */ | ||
| export type CliArgsBuilder = (prompt: string, request: MartinAdapterRequest) => string[]; | ||
| export type CliArgsBuilder = (prompt: string) => string[]; | ||
| export type CliStdinBuilder = (prompt: string) => string | undefined; | ||
@@ -25,0 +25,0 @@ export interface AgentCliAdapterOptions { |
@@ -242,3 +242,3 @@ /** | ||
| } | ||
| function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate) { | ||
| function createStreamingUsageInspector(capUsd, modelLabel) { | ||
| const pricing = (modelLabel ? MODEL_PRICING[modelLabel] : undefined) ?? | ||
@@ -249,5 +249,3 @@ { inputPer1K: BLENDED_INPUT_COST_PER_1K, outputPer1K: BLENDED_OUTPUT_COST_PER_1K }; | ||
| // next check fires (proven live: $1.50 cap → $28.42 actual). | ||
| const largeContext = promptTokenEstimate > 10_000; | ||
| const effectiveCapRatio = largeContext ? 0.7 : 0.8; | ||
| const effectiveCapUsd = capUsd * effectiveCapRatio; | ||
| const effectiveCapUsd = capUsd * 0.8; | ||
| // Token-count ceiling fallback: if no usage events are ever parsed (e.g. | ||
@@ -276,3 +274,3 @@ // Claude changes its stream-json event format), use raw byte volume as a | ||
| terminate(`Streaming usage cap exceeded after ${String(turns)} turn(s): cumulative cost ~$${cumulativeUsd.toFixed(4)} ` + | ||
| `surpassed the per-attempt cap $${capUsd.toFixed(4)} (${String(Math.round(effectiveCapRatio * 100))}% threshold: $${effectiveCapUsd.toFixed(4)}). ` + | ||
| `surpassed the per-attempt cap $${capUsd.toFixed(4)} (80% threshold: $${effectiveCapUsd.toFixed(4)}). ` + | ||
| `Subprocess terminated to bound runaway overspend.`); | ||
@@ -308,14 +306,2 @@ } | ||
| } | ||
| const turnUsd = (turnTokensIn / 1000) * pricing.inputPer1K + (turnTokensOut / 1000) * pricing.outputPer1K; | ||
| const remainingBudgetBeforeTurn = Math.max(capUsd - cumulativeUsd, 0); | ||
| if (capUsd > 0 && remainingBudgetBeforeTurn > 0 && turnUsd > remainingBudgetBeforeTurn * 0.5) { | ||
| cumulativeUsd += turnUsd; | ||
| tokensIn += turnTokensIn; | ||
| tokensOut += turnTokensOut; | ||
| turns += 1; | ||
| usageEventSeen = true; | ||
| terminate(`Single turn spend ~$${turnUsd.toFixed(4)} consumed more than 50% of the remaining per-attempt budget ` + | ||
| `($${remainingBudgetBeforeTurn.toFixed(4)} before the turn). Subprocess terminated to prevent a one-turn overshoot.`); | ||
| return; | ||
| } | ||
| tokensIn += turnTokensIn; | ||
@@ -325,3 +311,3 @@ tokensOut += turnTokensOut; | ||
| usageEventSeen = true; | ||
| cumulativeUsd += turnUsd; | ||
| cumulativeUsd += (turnTokensIn / 1000) * pricing.inputPer1K + (turnTokensOut / 1000) * pricing.outputPer1K; | ||
| checkBudgetExceeded(terminate); | ||
@@ -485,3 +471,3 @@ }; | ||
| } | ||
| const args = options.argsBuilder(prompt, request); | ||
| const args = options.argsBuilder(prompt); | ||
| const stdinData = options.stdinBuilder?.(prompt); | ||
@@ -495,3 +481,3 @@ // Live cumulative-cost circuit breaker: a single attempt should never be | ||
| const streamingUsage = options.streamingUsageCap && request.context.remainingBudgetUsd > 0 | ||
| ? createStreamingUsageInspector(request.context.remainingBudgetUsd, options.model ?? options.command, estimatedUsage.tokensIn) | ||
| ? createStreamingUsageInspector(request.context.remainingBudgetUsd, options.model ?? options.command) | ||
| : undefined; | ||
@@ -814,3 +800,3 @@ const agentResult = await runSubprocess(options.command, args, { | ||
| spawnImpl: options.spawnImpl, | ||
| argsBuilder: (_prompt, request) => [ | ||
| argsBuilder: (_prompt) => [ | ||
| "--output-format", | ||
@@ -826,3 +812,2 @@ "stream-json", | ||
| "--strict-mcp-config", | ||
| ...(request.context.remainingTokens > 0 ? ["--max-tokens", String(request.context.remainingTokens)] : []), | ||
| ...modelArgs, | ||
@@ -829,0 +814,0 @@ ...extraArgs |
@@ -138,2 +138,4 @@ import { type MartinAdapter } from "../core/index.js"; | ||
| fileScope: string[]; | ||
| cwd?: string; | ||
| runsDir?: string; | ||
| }; | ||
@@ -140,0 +142,0 @@ type GateCommand = { |
| { | ||
| "name": "@martin/cli", | ||
| "version": "0.3.18", | ||
| "version": "0.3.19", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "description": "Martin Loop CLI — budget-aware coding loops with failure classification and verified exits.", |
| import { readFile, readdir, stat } from "node:fs/promises"; | ||
| import { join, resolve } from "node:path"; | ||
| import { diagnoseCodexHost, resolveCliCommandAvailability } from "../adapters/index.js"; | ||
| import { probeCodexLaunch, resolveCliCommandAvailability } from "../adapters/index.js"; | ||
| import { resolveRunsRoot } from "../core/index.js"; | ||
@@ -400,6 +400,8 @@ const DEFAULT_BLOCKED_PATHS = [ | ||
| const availability = resolveCliCommandAvailability("codex"); | ||
| const diagnosis = diagnoseCodexHost(availability); | ||
| const summary = availability.available | ||
| ? "Codex CLI detected. Run martin preflight for a live launch check before governed execution." | ||
| : availability.detail; | ||
| const probe = mode === "live" | ||
| ? probeCodexLaunch({ | ||
| workingDirectory: receiptScope.workingDirectory, | ||
| availability | ||
| }) | ||
| : undefined; | ||
| return { | ||
@@ -413,10 +415,19 @@ mode, | ||
| ...(availability.candidatePaths?.length ? { candidatePaths: availability.candidatePaths } : {}), | ||
| hostPlatform: diagnosis.hostPlatform, | ||
| installKind: diagnosis.installKind, | ||
| nativeInstallValid: diagnosis.nativeInstallValid, | ||
| invocationMode: diagnosis.invocationMode, | ||
| sandboxMode: diagnosis.sandboxMode, | ||
| sandboxCompatible: diagnosis.sandboxCompatible, | ||
| summary, | ||
| ...(diagnosis.remediation ? { remediation: diagnosis.remediation } : {}) | ||
| ...(probe | ||
| ? { | ||
| selectedPath: probe.command, | ||
| hostPlatform: probe.diagnosis.hostPlatform, | ||
| installKind: probe.diagnosis.installKind, | ||
| nativeInstallValid: probe.diagnosis.nativeInstallValid, | ||
| invocationMode: probe.diagnosis.invocationMode, | ||
| sandboxMode: probe.diagnosis.sandboxMode, | ||
| sandboxCompatible: probe.diagnosis.sandboxCompatible, | ||
| launchReady: probe.ok, | ||
| summary: probe.summary, | ||
| ...(probe.diagnosis.remediation ? { remediation: probe.diagnosis.remediation } : {}), | ||
| ...(probe.candidateProbeResults?.length | ||
| ? { candidateProbeResults: probe.candidateProbeResults } | ||
| : {}) | ||
| } | ||
| : {}) | ||
| } | ||
@@ -423,0 +434,0 @@ }; |
+14
-2
| { | ||
| "name": "martin-loop", | ||
| "private": false, | ||
| "version": "0.3.18", | ||
| "version": "0.3.19", | ||
| "type": "module", | ||
@@ -115,3 +115,15 @@ "description": "Open-source command center for governed AI coding agents with built-in onboarding, hard gates, MCP, and shareable run receipts.", | ||
| "benchmarks" | ||
| ] | ||
| ], | ||
| "pnpm": { | ||
| "overrides": { | ||
| "@hono/node-server": "^1.19.13", | ||
| "ajv": "^8.20.0", | ||
| "fast-uri": "^3.1.2", | ||
| "hono": "^4.12.21", | ||
| "ip-address": "^10.1.1", | ||
| "postcss": "^8.5.15", | ||
| "qs": "^6.15.2", | ||
| "vite": "^7.3.2" | ||
| } | ||
| } | ||
| } |
+13
-19
@@ -89,3 +89,3 @@ # MartinLoop | ||
| Release notes for the current root package: [MartinLoop 0.3.18](./docs/release/OSS-0.3.18-RELEASE-NOTES.md). | ||
| Release notes for the current root package: [MartinLoop 0.3.19](./docs/release/OSS-0.3.19-RELEASE-NOTES.md). | ||
@@ -131,13 +131,13 @@ ## Visual Proof | ||
| ```sh | ||
| npx -y martin-loop@0.3.18 --version | ||
| npx -y martin-loop@0.3.18 start | ||
| npx -y martin-loop@0.3.18 demo | ||
| npx -y martin-loop@0.3.19 --version | ||
| npx -y martin-loop@0.3.19 start | ||
| npx -y martin-loop@0.3.19 demo | ||
| cd martin-loop-demo | ||
| npm install | ||
| npx -y martin-loop@0.3.18 run "Summarize the demo workspace and prove tests still pass" --verify "npm test" --budget-usd 2 --max-iterations 1 --json | ||
| npx -y martin-loop@0.3.18 dossier --latest --json | ||
| npx -y martin-loop@0.3.18 share --latest --json | ||
| npx -y martin-loop@0.3.19 run "Summarize the demo workspace and prove tests still pass" --verify "npm test" --budget-usd 2 --max-iterations 1 --json | ||
| npx -y martin-loop@0.3.19 dossier --latest --json | ||
| npx -y martin-loop@0.3.19 share --latest --json | ||
| ``` | ||
| For deterministic installs, pin the package line (`martin-loop@0.3.18`) or use `martin-loop@latest`. Plain `npx martin-loop` can resolve a stale local cache on some machines. | ||
| For deterministic installs, pin the package line (`martin-loop@0.3.19`) or use `martin-loop@latest`. Plain `npx martin-loop` can resolve a stale local cache on some machines. | ||
@@ -173,5 +173,5 @@ Expected share bundle outputs: | ||
| Public governed runs use one canonical taxonomy: the 12 runtime `FailureClass` values from `@martin/contracts`. | ||
| Public governed runs use one canonical taxonomy: the 13 runtime `FailureClass` values from `@martin/contracts`. | ||
| See the canonical table: [Failure Taxonomy (13 Runtime Classes)](./docs/oss/FAILURE-TAXONOMY.md). | ||
| See the canonical table: [Failure Taxonomy (13 Runtime Classes)](./docs/oss/FAILURE-TAXONOMY-13.md). | ||
@@ -183,3 +183,3 @@ ## What It Does | ||
| - Policy checks block unsafe verifier commands, risky path changes, and secret-like task inputs before execution. | ||
| - Failure classification uses canonical runtime classes for triage and reporting. See [Failure Taxonomy (13 Runtime Classes)](./docs/oss/FAILURE-TAXONOMY.md). | ||
| - Failure classification uses canonical runtime classes for triage and reporting. See [Failure Taxonomy (13 Runtime Classes)](./docs/oss/FAILURE-TAXONOMY-13.md). | ||
| - Run receipts capture stop reason, verifier evidence, budget posture, integrity state, and the next safe action. | ||
@@ -299,8 +299,2 @@ - `martin share --latest` turns the latest governed run into a local share bundle with a redacted JSON receipt, Markdown recap, and proof-card SVG. | ||
| If you need a local HTTP endpoint for a bridge or proxy, the standalone package can also run over HTTP: | ||
| ```sh | ||
| npx -y @martinloop/mcp --http --port 3033 | ||
| ``` | ||
| Generate host config from the root CLI: | ||
@@ -315,3 +309,3 @@ | ||
| The root `martin-loop` package and the standalone `@martinloop/mcp` package move on separate version lines. The root package line here is `0.3.18`; the current standalone MCP package is `0.3.6`. | ||
| The root `martin-loop` package and the standalone `@martinloop/mcp` package move on separate version lines. The current root package line here is `0.3.19`; the current standalone MCP package is `0.3.6`. | ||
@@ -375,3 +369,3 @@ The public MCP release train labels are: | ||
| - [Agent Failure Atlas](./docs/agent-failure-atlas.md) | ||
| - [Failure Taxonomy (13 Runtime Classes)](./docs/oss/FAILURE-TAXONOMY.md) | ||
| - [Failure Taxonomy (13 Runtime Classes)](./docs/oss/FAILURE-TAXONOMY-13.md) | ||
| - [PRE-028-PUBLIC-SURFACE-DIFF.md](./docs/oss/PRE-028-PUBLIC-SURFACE-DIFF.md) | ||
@@ -378,0 +372,0 @@ - [Claude Code walkthrough](./docs/getting-started/claude-code.md) |
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
20062
0875246
0437
-1.35%