@loops-adk/core 0.1.1 → 0.2.0

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,6 +1,6 @@
1
1
  import { redactSecrets } from './chunk-JFTXJ7I2.js';
2
2
  import { newAccumulator, mapMessage } from './chunk-CXEPZHSR.js';
3
- import { SUBAGENT_TOOLS } from './chunk-XC46B4FD.js';
3
+ import { SUBAGENT_TOOLS } from './chunk-MA6NDQMO.js';
4
4
  import { LoopError } from './chunk-I3STY7U6.js';
5
5
  import { execa } from 'execa';
6
6
 
@@ -120,5 +120,5 @@ ${stdout}`);
120
120
  };
121
121
 
122
122
  export { ClaudeCliEngine, buildClaudeArgs, classifyCliLimit };
123
- //# sourceMappingURL=claude-cli-U7WEVAOL.js.map
124
- //# sourceMappingURL=claude-cli-U7WEVAOL.js.map
123
+ //# sourceMappingURL=claude-cli-75AOQUKG.js.map
124
+ //# sourceMappingURL=claude-cli-75AOQUKG.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/engines/claude-cli.ts"],"names":[],"mappings":"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","file":"claude-cli-U7WEVAOL.js","sourcesContent":["/**\n * Engine adapter: the `claude` CLI as a subprocess. A fresh process per call =\n * a fresh context. Robust spawning + abort + timeout via `execa`; output is the\n * same stream-json schema the Agent SDK emits, so we reuse `mapMessage`.\n */\n\nimport { execa } from 'execa';\nimport {\n SUBAGENT_TOOLS,\n type AgentRequest,\n type AgentResult,\n type Engine,\n type EngineEventSink,\n type EngineOptions,\n} from './engine.ts';\nimport { mapMessage, newAccumulator } from './message-map.ts';\nimport { LoopError } from '../core/errors.ts';\nimport { redactSecrets } from '../core/redact.ts';\n\n/**\n * Classify a failed `claude` subprocess into a provider-limit `LoopError`, or\n * return `undefined` to fall through to the generic ENGINE/TIMEOUT mapping. The\n * CLI has no structured limit channel on a hard failure, so we read its\n * (already-redacted) output text:\n * - a usage/quota limit (\"usage limit reached\", \"out of credits\") → QUOTA.\n * A reset time, when the message states one (epoch seconds or an absolute\n * time the CLI prints), makes it auto-waitable; otherwise QUOTA has no\n * reset and the loop policy checkpoints-and-pauses.\n * - a plain \"rate limit\" → RATE_LIMIT (resets on its own).\n * Order matters: usage/quota is checked first so a usage message that also\n * contains the words \"rate limit\" is not mis-tagged as a transient throttle.\n *\n * Exported for unit testing without spawning a subprocess (mirrors\n * `buildClaudeArgs`).\n */\nexport function classifyCliLimit(text: string): LoopError | undefined {\n const lower = text.toLowerCase();\n const isUsage =\n /usage limit|out of credits|insufficient credits|quota|billing/.test(lower);\n const isRate = /rate limit|rate-limit|too many requests|429/.test(lower);\n if (!isUsage && !isRate) return undefined;\n\n const resetAt = parseResetAt(text);\n if (isUsage) {\n return new LoopError({\n code: 'QUOTA',\n phase: 'engine',\n message: `claude usage limit: ${text}`,\n resetAt,\n });\n }\n return new LoopError({\n code: 'RATE_LIMIT',\n phase: 'engine',\n message: `claude rate limited: ${text}`,\n resetAt,\n });\n}\n\n/**\n * Pull a reset time (epoch ms) out of CLI limit text. The CLI states a reset as\n * an epoch-seconds value (e.g. `resets at 1700000000`); convert to ms. Returns\n * `undefined` when no reset is stated — a quota with no parseable reset is not\n * auto-waitable.\n */\nfunction parseResetAt(text: string): number | undefined {\n const m = /(?:reset|resets|retry|available)\\D{0,20}(\\d{10,13})/i.exec(text);\n if (!m) return undefined;\n const n = Number(m[1]);\n if (!Number.isFinite(n)) return undefined;\n // 10-digit values are epoch seconds; 13-digit are already ms.\n return m[1]!.length <= 10 ? n * 1000 : n;\n}\n\n/**\n * Build the `claude` argv for one run. Extracted (and exported) so the flag\n * wiring — model, system prompt, tool allowlist, permission mode, the `--`\n * argument-smuggling guard — is unit-testable without spawning a process.\n */\nexport function buildClaudeArgs(\n req: AgentRequest,\n opts: EngineOptions,\n): string[] {\n const model = req.model ?? opts.defaultModel;\n const args = ['-p', '--output-format', 'stream-json', '--verbose'];\n if (model) args.push('--model', model);\n if (req.system) args.push('--append-system-prompt', req.system);\n if (req.allowedTools?.length)\n args.push('--allowedTools', req.allowedTools.join(','));\n // A leaf agent may not spawn sub-agents — disallow the spawn tool (wins over any allowlist).\n if (req.leaf) args.push('--disallowedTools', SUBAGENT_TOOLS.join(','));\n if (opts.permissionMode) args.push('--permission-mode', opts.permissionMode);\n if (opts.cliArgs?.length) args.push(...opts.cliArgs);\n // `--` ends option parsing so a prompt starting with `-` can't be\n // mis-interpreted by `claude` as a flag (argument smuggling).\n args.push('--', req.prompt);\n return args;\n}\n\nexport class ClaudeCliEngine implements Engine {\n readonly name = 'claude-cli';\n constructor(private readonly opts: EngineOptions = {}) {}\n\n async run(\n req: AgentRequest,\n onEvent: EngineEventSink,\n signal: AbortSignal,\n ): Promise<AgentResult> {\n const bin = this.opts.cliBinary ?? 'claude';\n const model = req.model ?? this.opts.defaultModel;\n const args = buildClaudeArgs(req, this.opts);\n\n const acc = newAccumulator(model ?? 'claude-cli');\n // Buffered (default) so `stderr` is a string for error messages; we still\n // attach a `data` listener to stream stdout line-by-line as it arrives.\n const sub = execa(bin, args, {\n cwd: req.cwd,\n cancelSignal: signal,\n // The prompt is passed as an argument, not piped — don't let `claude -p`\n // stall waiting on stdin.\n stdin: 'ignore',\n // If the child ignores the SIGTERM from an abort/timeout, escalate to\n // SIGKILL so a wedged subprocess can't make Ctrl-C hang.\n forceKillAfterDelay: 5000,\n reject: false,\n timeout: req.timeoutMs,\n stripFinalNewline: false,\n });\n\n let buffer = '';\n const flush = (line: string) => {\n const trimmed = line.trim();\n if (!trimmed) return;\n try {\n mapMessage(JSON.parse(trimmed), acc, onEvent);\n } catch {\n /* ignore non-JSON banner lines */\n }\n };\n sub.stdout?.setEncoding('utf8');\n sub.stdout?.on('data', (chunk: string) => {\n buffer += chunk;\n let idx: number;\n while ((idx = buffer.indexOf('\\n')) >= 0) {\n flush(buffer.slice(0, idx));\n buffer = buffer.slice(idx + 1);\n }\n });\n\n const result = await sub;\n if (buffer) flush(buffer);\n\n if (signal.aborted)\n throw new LoopError({\n code: 'ABORTED',\n phase: 'engine',\n message: 'claude-cli run aborted',\n });\n if (result.failed) {\n // The child's stderr is outside our control and may echo credentials on\n // an auth failure — redact before it lands in events/logs/the summary.\n const stderr =\n typeof result.stderr === 'string'\n ? redactSecrets(result.stderr.slice(0, 400))\n : '';\n // A rate/usage limit can land on either stream; check both (redacted)\n // before falling through to the generic exit-code error.\n if (!result.timedOut) {\n const stdout =\n typeof result.stdout === 'string'\n ? redactSecrets(result.stdout.slice(0, 400))\n : '';\n const limit = classifyCliLimit(`${stderr}\\n${stdout}`);\n if (limit) throw limit;\n }\n throw new LoopError({\n code: result.timedOut ? 'TIMEOUT' : 'ENGINE',\n phase: 'engine',\n message: `claude exited ${result.exitCode ?? '?'}${stderr ? `: ${stderr}` : ''}`,\n });\n }\n\n onEvent({ type: 'usage', usage: acc.usage, model: acc.model });\n return {\n text: acc.text,\n usage: acc.usage,\n model: acc.model,\n stopReason: acc.stopReason,\n };\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/engines/claude-cli.ts"],"names":[],"mappings":"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","file":"claude-cli-75AOQUKG.js","sourcesContent":["/**\n * Engine adapter: the `claude` CLI as a subprocess. A fresh process per call =\n * a fresh context. Robust spawning + abort + timeout via `execa`; output is the\n * same stream-json schema the Agent SDK emits, so we reuse `mapMessage`.\n */\n\nimport { execa } from 'execa';\nimport {\n SUBAGENT_TOOLS,\n type AgentRequest,\n type AgentResult,\n type Engine,\n type EngineEventSink,\n type EngineOptions,\n} from './engine.ts';\nimport { mapMessage, newAccumulator } from './message-map.ts';\nimport { LoopError } from '../core/errors.ts';\nimport { redactSecrets } from '../core/redact.ts';\n\n/**\n * Classify a failed `claude` subprocess into a provider-limit `LoopError`, or\n * return `undefined` to fall through to the generic ENGINE/TIMEOUT mapping. The\n * CLI has no structured limit channel on a hard failure, so we read its\n * (already-redacted) output text:\n * - a usage/quota limit (\"usage limit reached\", \"out of credits\") → QUOTA.\n * A reset time, when the message states one (epoch seconds or an absolute\n * time the CLI prints), makes it auto-waitable; otherwise QUOTA has no\n * reset and the loop policy checkpoints-and-pauses.\n * - a plain \"rate limit\" → RATE_LIMIT (resets on its own).\n * Order matters: usage/quota is checked first so a usage message that also\n * contains the words \"rate limit\" is not mis-tagged as a transient throttle.\n *\n * Exported for unit testing without spawning a subprocess (mirrors\n * `buildClaudeArgs`).\n */\nexport function classifyCliLimit(text: string): LoopError | undefined {\n const lower = text.toLowerCase();\n const isUsage =\n /usage limit|out of credits|insufficient credits|quota|billing/.test(lower);\n const isRate = /rate limit|rate-limit|too many requests|429/.test(lower);\n if (!isUsage && !isRate) return undefined;\n\n const resetAt = parseResetAt(text);\n if (isUsage) {\n return new LoopError({\n code: 'QUOTA',\n phase: 'engine',\n message: `claude usage limit: ${text}`,\n resetAt,\n });\n }\n return new LoopError({\n code: 'RATE_LIMIT',\n phase: 'engine',\n message: `claude rate limited: ${text}`,\n resetAt,\n });\n}\n\n/**\n * Pull a reset time (epoch ms) out of CLI limit text. The CLI states a reset as\n * an epoch-seconds value (e.g. `resets at 1700000000`); convert to ms. Returns\n * `undefined` when no reset is stated — a quota with no parseable reset is not\n * auto-waitable.\n */\nfunction parseResetAt(text: string): number | undefined {\n const m = /(?:reset|resets|retry|available)\\D{0,20}(\\d{10,13})/i.exec(text);\n if (!m) return undefined;\n const n = Number(m[1]);\n if (!Number.isFinite(n)) return undefined;\n // 10-digit values are epoch seconds; 13-digit are already ms.\n return m[1]!.length <= 10 ? n * 1000 : n;\n}\n\n/**\n * Build the `claude` argv for one run. Extracted (and exported) so the flag\n * wiring — model, system prompt, tool allowlist, permission mode, the `--`\n * argument-smuggling guard — is unit-testable without spawning a process.\n */\nexport function buildClaudeArgs(\n req: AgentRequest,\n opts: EngineOptions,\n): string[] {\n const model = req.model ?? opts.defaultModel;\n const args = ['-p', '--output-format', 'stream-json', '--verbose'];\n if (model) args.push('--model', model);\n if (req.system) args.push('--append-system-prompt', req.system);\n if (req.allowedTools?.length)\n args.push('--allowedTools', req.allowedTools.join(','));\n // A leaf agent may not spawn sub-agents — disallow the spawn tool (wins over any allowlist).\n if (req.leaf) args.push('--disallowedTools', SUBAGENT_TOOLS.join(','));\n if (opts.permissionMode) args.push('--permission-mode', opts.permissionMode);\n if (opts.cliArgs?.length) args.push(...opts.cliArgs);\n // `--` ends option parsing so a prompt starting with `-` can't be\n // mis-interpreted by `claude` as a flag (argument smuggling).\n args.push('--', req.prompt);\n return args;\n}\n\nexport class ClaudeCliEngine implements Engine {\n readonly name = 'claude-cli';\n constructor(private readonly opts: EngineOptions = {}) {}\n\n async run(\n req: AgentRequest,\n onEvent: EngineEventSink,\n signal: AbortSignal,\n ): Promise<AgentResult> {\n const bin = this.opts.cliBinary ?? 'claude';\n const model = req.model ?? this.opts.defaultModel;\n const args = buildClaudeArgs(req, this.opts);\n\n const acc = newAccumulator(model ?? 'claude-cli');\n // Buffered (default) so `stderr` is a string for error messages; we still\n // attach a `data` listener to stream stdout line-by-line as it arrives.\n const sub = execa(bin, args, {\n cwd: req.cwd,\n cancelSignal: signal,\n // The prompt is passed as an argument, not piped — don't let `claude -p`\n // stall waiting on stdin.\n stdin: 'ignore',\n // If the child ignores the SIGTERM from an abort/timeout, escalate to\n // SIGKILL so a wedged subprocess can't make Ctrl-C hang.\n forceKillAfterDelay: 5000,\n reject: false,\n timeout: req.timeoutMs,\n stripFinalNewline: false,\n });\n\n let buffer = '';\n const flush = (line: string) => {\n const trimmed = line.trim();\n if (!trimmed) return;\n try {\n mapMessage(JSON.parse(trimmed), acc, onEvent);\n } catch {\n /* ignore non-JSON banner lines */\n }\n };\n sub.stdout?.setEncoding('utf8');\n sub.stdout?.on('data', (chunk: string) => {\n buffer += chunk;\n let idx: number;\n while ((idx = buffer.indexOf('\\n')) >= 0) {\n flush(buffer.slice(0, idx));\n buffer = buffer.slice(idx + 1);\n }\n });\n\n const result = await sub;\n if (buffer) flush(buffer);\n\n if (signal.aborted)\n throw new LoopError({\n code: 'ABORTED',\n phase: 'engine',\n message: 'claude-cli run aborted',\n });\n if (result.failed) {\n // The child's stderr is outside our control and may echo credentials on\n // an auth failure — redact before it lands in events/logs/the summary.\n const stderr =\n typeof result.stderr === 'string'\n ? redactSecrets(result.stderr.slice(0, 400))\n : '';\n // A rate/usage limit can land on either stream; check both (redacted)\n // before falling through to the generic exit-code error.\n if (!result.timedOut) {\n const stdout =\n typeof result.stdout === 'string'\n ? redactSecrets(result.stdout.slice(0, 400))\n : '';\n const limit = classifyCliLimit(`${stderr}\\n${stdout}`);\n if (limit) throw limit;\n }\n throw new LoopError({\n code: result.timedOut ? 'TIMEOUT' : 'ENGINE',\n phase: 'engine',\n message: `claude exited ${result.exitCode ?? '?'}${stderr ? `: ${stderr}` : ''}`,\n });\n }\n\n onEvent({ type: 'usage', usage: acc.usage, model: acc.model });\n return {\n text: acc.text,\n usage: acc.usage,\n model: acc.model,\n stopReason: acc.stopReason,\n };\n }\n}\n"]}
@@ -1,9 +1,29 @@
1
+ import { redactSecrets } from './chunk-JFTXJ7I2.js';
1
2
  import { LoopError } from './chunk-I3STY7U6.js';
2
3
  import { mkdtempSync, readFileSync, rmSync } from 'fs';
3
4
  import { tmpdir } from 'os';
4
5
  import { join } from 'path';
5
6
  import { execa } from 'execa';
6
7
 
8
+ function buildCodexArgs(req, opts, outFile) {
9
+ const model = req.model ?? opts.defaultModel;
10
+ const prompt = req.system ? `${req.system}
11
+
12
+ ---
13
+
14
+ ${req.prompt}` : req.prompt;
15
+ const args = ["exec", "--ephemeral", "--skip-git-repo-check", "--color", "never"];
16
+ if (opts.permissionMode === "bypassPermissions") {
17
+ args.push("--dangerously-bypass-approvals-and-sandbox");
18
+ } else {
19
+ args.push("-s", "read-only");
20
+ }
21
+ if (req.cwd) args.push("-C", req.cwd);
22
+ if (model) args.push("-m", model);
23
+ if (opts.cliArgs?.length) args.push(...opts.cliArgs);
24
+ args.push("-o", outFile, prompt);
25
+ return args;
26
+ }
7
27
  var CodexEngine = class {
8
28
  constructor(opts = {}) {
9
29
  this.opts = opts;
@@ -14,15 +34,7 @@ var CodexEngine = class {
14
34
  const model = req.model ?? this.opts.defaultModel;
15
35
  const dir = mkdtempSync(join(tmpdir(), "loops-codex-"));
16
36
  const outFile = join(dir, "last.txt");
17
- const prompt = req.system ? `${req.system}
18
-
19
- ---
20
-
21
- ${req.prompt}` : req.prompt;
22
- const args = ["exec", "--ephemeral", "-s", "read-only", "--skip-git-repo-check"];
23
- if (req.cwd) args.push("-C", req.cwd);
24
- if (model) args.push("-m", model);
25
- args.push("-o", outFile, prompt);
37
+ const args = buildCodexArgs(req, this.opts, outFile);
26
38
  try {
27
39
  const sub = await execa(this.opts.cliBinary ?? "codex", args, {
28
40
  stdin: "ignore",
@@ -43,7 +55,7 @@ ${req.prompt}` : req.prompt;
43
55
  throw new LoopError({
44
56
  code: sub.timedOut ? "TIMEOUT" : "ENGINE",
45
57
  phase: "engine",
46
- message: `codex exited ${sub.exitCode ?? "?"}${typeof sub.stderr === "string" ? `: ${sub.stderr.slice(0, 300)}` : ""}`
58
+ message: `codex exited ${sub.exitCode ?? "?"}${typeof sub.stderr === "string" ? `: ${redactSecrets(sub.stderr.slice(0, 300))}` : ""}`
47
59
  });
48
60
  const usage = { inputTokens: 0, outputTokens: 0 };
49
61
  if (text) onEvent({ type: "text", delta: text });
@@ -55,6 +67,6 @@ ${req.prompt}` : req.prompt;
55
67
  }
56
68
  };
57
69
 
58
- export { CodexEngine };
59
- //# sourceMappingURL=codex-6I5UZ2HM.js.map
60
- //# sourceMappingURL=codex-6I5UZ2HM.js.map
70
+ export { CodexEngine, buildCodexArgs };
71
+ //# sourceMappingURL=codex-LYZF52WL.js.map
72
+ //# sourceMappingURL=codex-LYZF52WL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engines/codex.ts"],"names":[],"mappings":"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","file":"codex-LYZF52WL.js","sourcesContent":["/**\n * Engine adapter: the `codex` CLI (GPT-5) as a non-interactive subprocess. The\n * point is a genuinely DIFFERENT model behind the same `Engine` seam — point any\n * reviewer at `engine: 'codex'` for a second-model adversarial signal, with no\n * bespoke integration. Read-only by default: a report-only reviewer never edits,\n * so the sandbox forbids writes and the run cannot touch the workspace.\n *\n * `codex exec` is non-interactive but blocks on an open stdin, so stdin is always\n * ignored; the final assistant message is captured via `-o <file>` rather than\n * scraped from the event stream.\n */\nimport { mkdtempSync, readFileSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport type {\n AgentRequest,\n AgentResult,\n Engine,\n EngineEventSink,\n EngineOptions,\n} from './engine.ts';\nimport { LoopError } from '../core/errors.ts';\nimport { redactSecrets } from '../core/redact.ts';\n\nexport function buildCodexArgs(\n req: AgentRequest,\n opts: EngineOptions,\n outFile: string,\n): string[] {\n const model = req.model ?? opts.defaultModel;\n const prompt = req.system ? `${req.system}\\n\\n---\\n\\n${req.prompt}` : req.prompt;\n const args = ['exec', '--ephemeral', '--skip-git-repo-check', '--color', 'never'];\n\n if (opts.permissionMode === 'bypassPermissions') {\n args.push('--dangerously-bypass-approvals-and-sandbox');\n } else {\n args.push('-s', 'read-only');\n }\n\n if (req.cwd) args.push('-C', req.cwd);\n if (model) args.push('-m', model);\n if (opts.cliArgs?.length) args.push(...opts.cliArgs);\n args.push('-o', outFile, prompt);\n return args;\n}\n\nexport class CodexEngine implements Engine {\n readonly name = 'codex';\n constructor(private readonly opts: EngineOptions = {}) {}\n\n async run(\n req: AgentRequest,\n onEvent: EngineEventSink,\n signal: AbortSignal,\n ): Promise<AgentResult> {\n const model = req.model ?? this.opts.defaultModel;\n const dir = mkdtempSync(join(tmpdir(), 'loops-codex-'));\n const outFile = join(dir, 'last.txt');\n const args = buildCodexArgs(req, this.opts, outFile);\n\n try {\n const sub = await execa(this.opts.cliBinary ?? 'codex', args, {\n stdin: 'ignore', // codex exec stalls on an open stdin\n cancelSignal: signal,\n forceKillAfterDelay: 5000,\n reject: false,\n timeout: req.timeoutMs,\n });\n if (signal.aborted)\n throw new LoopError({ code: 'ABORTED', phase: 'engine', message: 'codex run aborted' });\n\n let text = '';\n try {\n text = readFileSync(outFile, 'utf8').trim();\n } catch {\n /* no final message written */\n }\n if (!text && sub.failed)\n throw new LoopError({\n code: sub.timedOut ? 'TIMEOUT' : 'ENGINE',\n phase: 'engine',\n message: `codex exited ${sub.exitCode ?? '?'}${\n typeof sub.stderr === 'string' ? `: ${redactSecrets(sub.stderr.slice(0, 300))}` : ''\n }`,\n });\n\n // codex bills a separate (GPT-5) account, so its tokens are out-of-band for\n // the loops token budget — report zero rather than conflate providers.\n const usage = { inputTokens: 0, outputTokens: 0 };\n if (text) onEvent({ type: 'text', delta: text });\n onEvent({ type: 'usage', usage, model: model ?? 'codex' });\n return { text, usage, model: model ?? 'codex', stopReason: 'end_turn' };\n } finally {\n rmSync(dir, { recursive: true, force: true });\n }\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { W as Workspace, E as Environment } from '../types-B4wGVpqo.js';
1
+ import { W as Workspace, E as Environment } from '../types-Cv_3ymr9.js';
2
2
 
3
3
  /**
4
4
  * `commandEnvironment` — a generic, CLI-driven Environment. Every IaC tool
@@ -1,4 +1,4 @@
1
- import { W as Workspace, E as Environment } from '../types-B4wGVpqo.js';
1
+ import { W as Workspace, E as Environment } from '../types-Cv_3ymr9.js';
2
2
 
3
3
  /**
4
4
  * `dockerEnvironment` — a local environment via Docker Compose, a thin preset
package/dist/env/sst.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { W as Workspace, E as Environment } from '../types-B4wGVpqo.js';
1
+ import { W as Workspace, E as Environment } from '../types-Cv_3ymr9.js';
2
2
  import { Cmd } from './command.js';
3
3
 
4
4
  /**
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { renderPlan, jobMeta, listRuns, runsHome, readRunStatus, runEventsPath, formatEvent, run, exitCodeFor, loop, agentJob, agentCheck, bodyPassed, gateJob } from './chunk-6BDWTFOS.js';
2
+ import { jobMeta, renderPlan, listRuns, runsHome, readRunStatus, runEventsPath, formatEvent, run, exitCodeFor, loop, runSemanticRecordsPath, agentJob, agentCheck, bodyPassed, gateJob } from './chunk-WM5QVHM2.js';
3
3
  import './chunk-JFTXJ7I2.js';
4
- import './chunk-XC46B4FD.js';
4
+ import './chunk-MA6NDQMO.js';
5
5
  import './chunk-Y2SD7GBL.js';
6
6
  import './chunk-I3STY7U6.js';
7
7
  import fs from 'fs';
@@ -550,6 +550,58 @@ function relAge(ms2) {
550
550
  if (h < 48) return `${h}h`;
551
551
  return `${Math.round(h / 24)}d`;
552
552
  }
553
+ function readSemanticRecords(runId) {
554
+ const path2 = runSemanticRecordsPath(runId);
555
+ if (!fs.existsSync(path2)) return void 0;
556
+ const raw = fs.readFileSync(path2, "utf8").trim();
557
+ if (!raw) return [];
558
+ const records = [];
559
+ for (const line of raw.split("\n")) {
560
+ try {
561
+ records.push(JSON.parse(line));
562
+ } catch {
563
+ }
564
+ }
565
+ return records;
566
+ }
567
+ function parsePositiveIntFlag(value, flag) {
568
+ const parsed = Number(value);
569
+ if (!Number.isInteger(parsed) || parsed <= 0) {
570
+ throw new Error(`${flag} must be a positive integer, got "${value}"`);
571
+ }
572
+ return parsed;
573
+ }
574
+ function parseSinceFlag(value) {
575
+ const trimmed = value.trim();
576
+ if (/^\d+$/.test(trimmed)) return Number(trimmed);
577
+ const parsed = Date.parse(trimmed);
578
+ if (!Number.isFinite(parsed)) {
579
+ throw new Error(`--since must be epoch ms or an ISO timestamp, got "${value}"`);
580
+ }
581
+ return parsed;
582
+ }
583
+ function normalizeRecordPath(value) {
584
+ return value.split(/[\/›>]+/).map((part) => part.trim()).filter(Boolean).join("/");
585
+ }
586
+ function matchesRecordPath(record, prefix) {
587
+ const path2 = record.path.join("/");
588
+ return path2 === prefix || path2.startsWith(`${prefix}/`);
589
+ }
590
+ function formatSemanticRecord(record) {
591
+ const at = record.path.length ? `${record.path.join(" \u203A ")} ` : "";
592
+ switch (record.kind) {
593
+ case "dispatch":
594
+ return `${at}dispatch ${record.unit}${record.label ? ` ${record.label}` : ""}${record.node ? ` ${record.node}` : ""}`;
595
+ case "completion":
596
+ return `${at}completion ${record.unit}${record.label ? ` ${record.label}` : ""}: ${record.outcome.status}${record.outcome.summary ? ` \u2014 ${record.outcome.summary}` : ""}`;
597
+ case "surfacing":
598
+ return `${at}surfacing ${record.source} ${record.decision}${record.severity ? ` [${record.severity}]` : ""}: ${record.reason}`;
599
+ case "revision-emitted":
600
+ return `${at}revision emitted ${record.sourceEvent}${record.revision.target ? ` -> ${record.revision.target}` : ""}: ${record.revision.reason}`;
601
+ case "revision-routed":
602
+ return `${at}revision routed ${record.sourceEvent} ${record.decision}${record.revision.target ? ` -> ${record.revision.target}` : ""}: ${record.revision.reason}`;
603
+ }
604
+ }
553
605
  async function main(argv = process.argv) {
554
606
  const program = new Command();
555
607
  program.name("loops").description(
@@ -563,7 +615,7 @@ async function main(argv = process.argv) {
563
615
  "read the worker prompt from a file (no-file mode)"
564
616
  ).option(
565
617
  "-e, --engine <name>",
566
- "default engine: agent-sdk | claude-cli | anthropic-api"
618
+ "default engine: codex | agent-sdk | claude-cli | anthropic-api"
567
619
  ).option("--default-model <id>", "fallback model id for engines").option("--worker-model <id>", "model for the worker job").option(
568
620
  "--validator-model <id>",
569
621
  "small model for agent-validated conditions"
@@ -576,13 +628,13 @@ async function main(argv = process.argv) {
576
628
  "0.85"
577
629
  ).option("-i, --interval <dur>", "delay between iterations (e.g. 30s, 5m)").option("--max-tokens <n>", "max output tokens per agent turn").option("--api-key <key>", "Anthropic API key (anthropic-api engine)").option(
578
630
  "--cli-binary <path>",
579
- "path to the claude binary (claude-cli engine)"
631
+ "path to a CLI engine binary"
580
632
  ).option(
581
633
  "--permission-mode <mode>",
582
- "tool permission mode for claude-cli/agent-sdk (default | acceptEdits | bypassPermissions | plan | dontAsk | auto)"
634
+ "tool permission mode for CLI/SDK engines (default | acceptEdits | bypassPermissions | plan | dontAsk | auto)"
583
635
  ).option(
584
636
  "--engine-arg <arg>",
585
- "extra arg forwarded to the claude-cli engine (repeatable)",
637
+ "extra arg forwarded to CLI-backed engines (repeatable)",
586
638
  (v, acc) => acc.concat(v),
587
639
  []
588
640
  ).option("--state <json>", "seed the shared run state (JSON)").option("--budget <tokens>", "cap total tokens (input+output) for the run").option("--record <path>", "append a JSONL run record to this path").option(
@@ -605,9 +657,17 @@ async function main(argv = process.argv) {
605
657
  );
606
658
  program.command("validate").argument("<file>", "a loop-definition file to check").description(
607
659
  "load a .loop.ts and print its shape without running it: the cheap, no-model pre-flight an agent runs before `loops run`"
608
- ).action(async (file) => {
660
+ ).option("--json", "emit JSON with the loaded job shape").action(async (file, flags) => {
609
661
  const { job } = await loadJob(file);
610
- const plan = renderPlan(jobMeta(job));
662
+ const shape = jobMeta(job);
663
+ if (flags.json) {
664
+ process.stdout.write(
665
+ `${JSON.stringify({ file, ok: true, executed: false, shape }, null, 2)}
666
+ `
667
+ );
668
+ return;
669
+ }
670
+ const plan = renderPlan(shape);
611
671
  process.stdout.write(
612
672
  `\u2713 ${file} loads (not executed)
613
673
  ${plan.map((l) => ` ${l}`).join("\n")}
@@ -616,10 +676,14 @@ ${plan.map((l) => ` ${l}`).join("\n")}
616
676
  });
617
677
  program.command("describe").argument("<file>", "a loop-definition file").description(
618
678
  "print a loop's shape (its gate, body, and dag nodes) without running it"
619
- ).action(async (file) => {
679
+ ).option("--json", "emit the job shape as JSON").action(async (file, flags) => {
620
680
  const { job } = await loadJob(file);
621
- process.stdout.write(`${renderPlan(jobMeta(job)).join("\n")}
622
- `);
681
+ const shape = jobMeta(job);
682
+ process.stdout.write(
683
+ flags.json ? `${JSON.stringify(shape, null, 2)}
684
+ ` : `${renderPlan(shape).join("\n")}
685
+ `
686
+ );
623
687
  });
624
688
  program.command("list").alias("ls").description("list supervised runs (start one with `loops run --supervise`)").action(() => {
625
689
  const runs = listRuns();
@@ -717,6 +781,71 @@ ${renderPlan(r.shape).map((l) => ` ${l}`).join("\n")}
717
781
  }
718
782
  process.removeListener("SIGINT", onSig);
719
783
  });
784
+ program.command("records").argument("<runId>", "a run id from `loops list`").description("show a supervised run's semantic records").option(
785
+ "--kind <kind>",
786
+ "filter by record kind: dispatch | completion | surfacing | revision-emitted | revision-routed | revision"
787
+ ).option("--path <path>", "filter by slash-separated record path prefix").option("--since <time>", "show records at or after an epoch ms or ISO timestamp").option("--last <n>", "show only the last n matching records").option("--json", "emit matching semantic records as JSONL").action((runId, flags) => {
788
+ const records = readSemanticRecords(runId);
789
+ if (!records) {
790
+ process.stderr.write(`no semantic records for run "${runId}" in ${runsHome()}
791
+ `);
792
+ process.exitCode = 1;
793
+ return;
794
+ }
795
+ const validKinds = [
796
+ "dispatch",
797
+ "completion",
798
+ "surfacing",
799
+ "revision-emitted",
800
+ "revision-routed",
801
+ "revision"
802
+ ];
803
+ if (flags.kind && !validKinds.includes(flags.kind)) {
804
+ process.stderr.write(
805
+ `--kind must be one of ${validKinds.join(" | ")}, got "${flags.kind}"
806
+ `
807
+ );
808
+ process.exitCode = 1;
809
+ return;
810
+ }
811
+ let pathPrefix;
812
+ if (flags.path != null) {
813
+ pathPrefix = normalizeRecordPath(flags.path);
814
+ if (!pathPrefix) {
815
+ process.stderr.write("--path must contain at least one path segment\n");
816
+ process.exitCode = 1;
817
+ return;
818
+ }
819
+ }
820
+ let since;
821
+ let last2;
822
+ try {
823
+ if (flags.since != null) since = parseSinceFlag(flags.since);
824
+ if (flags.last != null) last2 = parsePositiveIntFlag(flags.last, "--last");
825
+ } catch (e) {
826
+ process.stderr.write(`${e instanceof Error ? e.message : String(e)}
827
+ `);
828
+ process.exitCode = 1;
829
+ return;
830
+ }
831
+ let filtered = flags.kind ? flags.kind === "revision" ? records.filter(
832
+ (r) => r.kind === "revision-emitted" || r.kind === "revision-routed"
833
+ ) : records.filter((r) => r.kind === flags.kind) : records;
834
+ if (pathPrefix) filtered = filtered.filter((r) => matchesRecordPath(r, pathPrefix));
835
+ if (since != null) filtered = filtered.filter((r) => r.ts >= since);
836
+ if (last2 != null) filtered = filtered.slice(-last2);
837
+ if (flags.json) {
838
+ for (const record of filtered) {
839
+ process.stdout.write(`${JSON.stringify(record)}
840
+ `);
841
+ }
842
+ return;
843
+ }
844
+ for (const record of filtered) {
845
+ process.stdout.write(`${formatSemanticRecord(record)}
846
+ `);
847
+ }
848
+ });
720
849
  await program.parseAsync(argv);
721
850
  }
722
851