@loops-adk/core 0.1.1 → 0.3.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.
- package/README.md +132 -15
- package/assets/logo.png +0 -0
- package/dist/{agent-sdk-RF5VJZAT.js → agent-sdk-4QJDWM7N.js} +3 -3
- package/dist/{agent-sdk-RF5VJZAT.js.map → agent-sdk-4QJDWM7N.js.map} +1 -1
- package/dist/api.d.ts +119 -3
- package/dist/api.js +26 -10
- package/dist/api.js.map +1 -1
- package/dist/{chunk-6BDWTFOS.js → chunk-3PMVII43.js} +784 -37
- package/dist/chunk-3PMVII43.js.map +1 -0
- package/dist/{chunk-XC46B4FD.js → chunk-MA6NDQMO.js} +2 -2
- package/dist/chunk-MA6NDQMO.js.map +1 -0
- package/dist/{claude-cli-U7WEVAOL.js → claude-cli-75AOQUKG.js} +3 -3
- package/dist/{claude-cli-U7WEVAOL.js.map → claude-cli-75AOQUKG.js.map} +1 -1
- package/dist/{codex-6I5UZ2HM.js → codex-LYZF52WL.js} +25 -13
- package/dist/codex-LYZF52WL.js.map +1 -0
- package/dist/env/command.d.ts +1 -1
- package/dist/env/docker.d.ts +1 -1
- package/dist/env/sst.d.ts +1 -1
- package/dist/index.js +155 -14
- package/dist/index.js.map +1 -1
- package/dist/{types-B4wGVpqo.d.ts → types-CpB03Jj4.d.ts} +255 -38
- package/package.json +11 -1
- package/skills/author-loop/SKILL.md +14 -5
- package/skills/design-agent-team/SKILL.md +108 -0
- package/skills/supervise-loop-run/SKILL.md +64 -0
- package/dist/chunk-6BDWTFOS.js.map +0 -1
- package/dist/chunk-XC46B4FD.js.map +0 -1
- package/dist/codex-6I5UZ2HM.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/engines/engine.ts"],"names":[],"mappings":";AAyBO,IAAM,cAAA,GAAiB,CAAC,MAAM;AA6D9B,SAAS,SAAS,GAAA,EAA2C;AAClE,EAAA,OACE,OAAO,GAAA,KAAQ,QAAA,IACf,QAAQ,IAAA,IACR,OAAQ,IAAe,GAAA,KAAQ,UAAA;AAEnC","file":"chunk-MA6NDQMO.js","sourcesContent":["/**\n * The pluggable execution backend. A `Step` asks an `Engine` to run one agent\n * turn with a *fresh context* and stream events back. Each call is independent —\n * that is what gives every loop iteration its clean slate.\n */\n\n/**\n * Built-in, registry-resolvable adapter names. The union is open (`& {}` trick)\n * so callers can name and register their own engines — the core never assumes a\n * fixed provider set. (`mock` is constructed directly in tests/examples, not\n * registered by name, so it is intentionally not listed here.)\n */\nexport type EngineName =\n | 'agent-sdk'\n | 'claude-cli'\n | 'codex'\n | 'anthropic-api'\n | (string & {});\n\nexport interface Usage {\n inputTokens: number;\n outputTokens: number;\n}\n\n/** Tools an agent uses to spawn sub-agents / fan out. A `leaf` request disallows these. */\nexport const SUBAGENT_TOOLS = ['Task'];\n\nexport interface AgentRequest {\n prompt: string;\n system?: string;\n model?: string;\n maxTokens?: number;\n /** Tool allowlist, where the backend supports tools (SDK / CLI). */\n allowedTools?: string[];\n cwd?: string;\n timeoutMs?: number;\n /**\n * Forbid this agent from spawning sub-agents (fanning out). A leaf agent is told to\n * disallow the sub-agent tool (`SUBAGENT_TOOLS`), so a branch of the graph bottoms out\n * here instead of expanding into an uncontrolled swarm — control over where work stops.\n * Authoritative over `allowedTools` (a disallow wins). Engines with no sub-agent tool\n * (anthropic-api, codex, mock) ignore it.\n */\n leaf?: boolean;\n}\n\nexport interface AgentResult {\n /** Final assistant text (concatenated across blocks). */\n text: string;\n usage: Usage;\n model: string;\n stopReason?: string;\n /** Backend-native final payload, for escape-hatch inspection. */\n raw?: unknown;\n}\n\n/** Streamed during a run. The runtime re-tags these as `LoopEvent`s. */\nexport type EngineStreamEvent =\n | { type: 'text'; delta: string }\n | { type: 'thinking'; delta: string }\n | { type: 'tool'; name: string; phase: 'use' | 'result' }\n | { type: 'usage'; usage: Usage; model: string };\n\nexport type EngineEventSink = (event: EngineStreamEvent) => void;\n\nexport interface Engine {\n readonly name: EngineName;\n /**\n * Run one fresh agent turn. Contract for the `usage` stream event: emit it\n * **exactly once, at the end** of the turn — stats sums every `usage` event,\n * so a backend that emits incremental usage mid-stream would inflate totals.\n */\n run(\n req: AgentRequest,\n onEvent: EngineEventSink,\n signal: AbortSignal,\n ): Promise<AgentResult>;\n}\n\n/**\n * Anywhere an engine can be selected, accept either a registered name or a\n * ready-made `Engine`. The latter is the \"bring your own provider/framework\"\n * escape hatch — the runtime treats every backend through this one interface.\n */\nexport type EngineRef = EngineName | Engine;\n\nexport function isEngine(ref: EngineRef | undefined): ref is Engine {\n return (\n typeof ref === 'object' &&\n ref !== null &&\n typeof (ref as Engine).run === 'function'\n );\n}\n\n/**\n * How a tool-using engine treats permission prompts. Mirrors the Claude Code\n * values. `bypassPermissions` lets a headless worker read/write/run without\n * prompting — required for an unattended agent that must touch the filesystem or\n * shell, and to be set deliberately.\n */\nexport type PermissionMode =\n | 'default'\n | 'acceptEdits'\n | 'bypassPermissions'\n | 'plan'\n | 'dontAsk'\n | 'auto';\n\n/** Per-run options that the registry uses to construct engines. */\nexport interface EngineOptions {\n /** Default model when a request/step does not name one. */\n defaultModel?: string;\n apiKey?: string;\n /** For CLI-backed engines: path to the binary. */\n cliBinary?: string;\n /** Extra args appended to CLI-backed engine invocations. */\n cliArgs?: string[];\n /**\n * Permission mode for tool-using engines. Unset = the engine/CLI default\n * where applicable; the Codex adapter stays read-only unless explicitly set\n * to `bypassPermissions`.\n */\n permissionMode?: PermissionMode;\n}\n"]}
|
|
@@ -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-
|
|
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-
|
|
124
|
-
//# sourceMappingURL=claude-cli-
|
|
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
|
|
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-
|
|
60
|
-
//# sourceMappingURL=codex-
|
|
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"]}
|
package/dist/env/command.d.ts
CHANGED
package/dist/env/docker.d.ts
CHANGED
package/dist/env/sst.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { jobMeta, renderPlan, listRuns, runsHome, readRunStatus, runEventsPath, formatEvent, run, exitCodeFor, loop, runSemanticRecordsPath, agentJob, agentCheck, bodyPassed, gateJob } from './chunk-3PMVII43.js';
|
|
3
3
|
import './chunk-JFTXJ7I2.js';
|
|
4
|
-
import './chunk-
|
|
4
|
+
import './chunk-MA6NDQMO.js';
|
|
5
5
|
import './chunk-Y2SD7GBL.js';
|
|
6
6
|
import './chunk-I3STY7U6.js';
|
|
7
7
|
import fs from 'fs';
|
|
@@ -190,6 +190,12 @@ function plainReporter() {
|
|
|
190
190
|
`${indent(event.path)} ${pc.dim(`tool ${event.phase}: ${event.name}`)}`
|
|
191
191
|
);
|
|
192
192
|
return;
|
|
193
|
+
case "loop:stall":
|
|
194
|
+
endStream();
|
|
195
|
+
console.log(
|
|
196
|
+
`${indent(event.path)} ${pc.red("\u23F9 stalled")}: no progress across iterations ${event.report.iterations.join(", ")} ${pc.dim(`(${event.report.reason})`)}`
|
|
197
|
+
);
|
|
198
|
+
return;
|
|
193
199
|
case "limit:wait":
|
|
194
200
|
endStream();
|
|
195
201
|
console.log(
|
|
@@ -288,7 +294,8 @@ var FlagSpec = z.object({
|
|
|
288
294
|
review: z.string().optional(),
|
|
289
295
|
reviewThreshold: z.number().min(0).max(1).default(0.85),
|
|
290
296
|
interval: z.number().int().nonnegative().optional(),
|
|
291
|
-
maxTokens: z.number().int().positive().optional()
|
|
297
|
+
maxTokens: z.number().int().positive().optional(),
|
|
298
|
+
stallAfter: z.number().int().positive().optional()
|
|
292
299
|
});
|
|
293
300
|
function parseDuration(value) {
|
|
294
301
|
if (/^\d+$/.test(value)) return Number(value);
|
|
@@ -340,6 +347,7 @@ Your previous attempt was REJECTED in review: ${ctx.lastReview.summary ?? ctx.la
|
|
|
340
347
|
until,
|
|
341
348
|
review,
|
|
342
349
|
max: spec.max,
|
|
350
|
+
noProgress: spec.stallAfter,
|
|
343
351
|
delayMs: spec.interval
|
|
344
352
|
});
|
|
345
353
|
}
|
|
@@ -403,7 +411,8 @@ function buildFromFlags(flags) {
|
|
|
403
411
|
review: flags.review,
|
|
404
412
|
reviewThreshold: num(flags.reviewThreshold),
|
|
405
413
|
interval: flags.interval != null ? parseDuration(flags.interval) : void 0,
|
|
406
|
-
maxTokens: num(flags.maxTokens)
|
|
414
|
+
maxTokens: num(flags.maxTokens),
|
|
415
|
+
stallAfter: num(flags.stallAfter)
|
|
407
416
|
});
|
|
408
417
|
} catch (e) {
|
|
409
418
|
if (e instanceof z.ZodError) {
|
|
@@ -550,6 +559,58 @@ function relAge(ms2) {
|
|
|
550
559
|
if (h < 48) return `${h}h`;
|
|
551
560
|
return `${Math.round(h / 24)}d`;
|
|
552
561
|
}
|
|
562
|
+
function readSemanticRecords(runId) {
|
|
563
|
+
const path2 = runSemanticRecordsPath(runId);
|
|
564
|
+
if (!fs.existsSync(path2)) return void 0;
|
|
565
|
+
const raw = fs.readFileSync(path2, "utf8").trim();
|
|
566
|
+
if (!raw) return [];
|
|
567
|
+
const records = [];
|
|
568
|
+
for (const line of raw.split("\n")) {
|
|
569
|
+
try {
|
|
570
|
+
records.push(JSON.parse(line));
|
|
571
|
+
} catch {
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return records;
|
|
575
|
+
}
|
|
576
|
+
function parsePositiveIntFlag(value, flag) {
|
|
577
|
+
const parsed = Number(value);
|
|
578
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
579
|
+
throw new Error(`${flag} must be a positive integer, got "${value}"`);
|
|
580
|
+
}
|
|
581
|
+
return parsed;
|
|
582
|
+
}
|
|
583
|
+
function parseSinceFlag(value) {
|
|
584
|
+
const trimmed = value.trim();
|
|
585
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed);
|
|
586
|
+
const parsed = Date.parse(trimmed);
|
|
587
|
+
if (!Number.isFinite(parsed)) {
|
|
588
|
+
throw new Error(`--since must be epoch ms or an ISO timestamp, got "${value}"`);
|
|
589
|
+
}
|
|
590
|
+
return parsed;
|
|
591
|
+
}
|
|
592
|
+
function normalizeRecordPath(value) {
|
|
593
|
+
return value.split(/[\/›>]+/).map((part) => part.trim()).filter(Boolean).join("/");
|
|
594
|
+
}
|
|
595
|
+
function matchesRecordPath(record, prefix) {
|
|
596
|
+
const path2 = record.path.join("/");
|
|
597
|
+
return path2 === prefix || path2.startsWith(`${prefix}/`);
|
|
598
|
+
}
|
|
599
|
+
function formatSemanticRecord(record) {
|
|
600
|
+
const at = record.path.length ? `${record.path.join(" \u203A ")} ` : "";
|
|
601
|
+
switch (record.kind) {
|
|
602
|
+
case "dispatch":
|
|
603
|
+
return `${at}dispatch ${record.unit}${record.label ? ` ${record.label}` : ""}${record.node ? ` ${record.node}` : ""}`;
|
|
604
|
+
case "completion":
|
|
605
|
+
return `${at}completion ${record.unit}${record.label ? ` ${record.label}` : ""}: ${record.outcome.status}${record.outcome.summary ? ` \u2014 ${record.outcome.summary}` : ""}`;
|
|
606
|
+
case "surfacing":
|
|
607
|
+
return `${at}surfacing ${record.source} ${record.decision}${record.severity ? ` [${record.severity}]` : ""}: ${record.reason}`;
|
|
608
|
+
case "revision-emitted":
|
|
609
|
+
return `${at}revision emitted ${record.sourceEvent}${record.revision.target ? ` -> ${record.revision.target}` : ""}: ${record.revision.reason}`;
|
|
610
|
+
case "revision-routed":
|
|
611
|
+
return `${at}revision routed ${record.sourceEvent} ${record.decision}${record.revision.target ? ` -> ${record.revision.target}` : ""}: ${record.revision.reason}`;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
553
614
|
async function main(argv = process.argv) {
|
|
554
615
|
const program = new Command();
|
|
555
616
|
program.name("loops").description(
|
|
@@ -563,7 +624,7 @@ async function main(argv = process.argv) {
|
|
|
563
624
|
"read the worker prompt from a file (no-file mode)"
|
|
564
625
|
).option(
|
|
565
626
|
"-e, --engine <name>",
|
|
566
|
-
"default engine: agent-sdk | claude-cli | anthropic-api"
|
|
627
|
+
"default engine: codex | agent-sdk | claude-cli | anthropic-api"
|
|
567
628
|
).option("--default-model <id>", "fallback model id for engines").option("--worker-model <id>", "model for the worker job").option(
|
|
568
629
|
"--validator-model <id>",
|
|
569
630
|
"small model for agent-validated conditions"
|
|
@@ -574,15 +635,18 @@ async function main(argv = process.argv) {
|
|
|
574
635
|
"--review-threshold <0..1>",
|
|
575
636
|
"confidence threshold for --review",
|
|
576
637
|
"0.85"
|
|
577
|
-
).option("-i, --interval <dur>", "delay between iterations (e.g. 30s, 5m)").option("--max-tokens <n>", "max output tokens per agent turn").option(
|
|
638
|
+
).option("-i, --interval <dur>", "delay between iterations (e.g. 30s, 5m)").option("--max-tokens <n>", "max output tokens per agent turn").option(
|
|
639
|
+
"--stall-after <n>",
|
|
640
|
+
"end exhausted after n consecutive iterations with no observable progress"
|
|
641
|
+
).option("--api-key <key>", "Anthropic API key (anthropic-api engine)").option(
|
|
578
642
|
"--cli-binary <path>",
|
|
579
|
-
"path to
|
|
643
|
+
"path to a CLI engine binary"
|
|
580
644
|
).option(
|
|
581
645
|
"--permission-mode <mode>",
|
|
582
|
-
"tool permission mode for
|
|
646
|
+
"tool permission mode for CLI/SDK engines (default | acceptEdits | bypassPermissions | plan | dontAsk | auto)"
|
|
583
647
|
).option(
|
|
584
648
|
"--engine-arg <arg>",
|
|
585
|
-
"extra arg forwarded to
|
|
649
|
+
"extra arg forwarded to CLI-backed engines (repeatable)",
|
|
586
650
|
(v, acc) => acc.concat(v),
|
|
587
651
|
[]
|
|
588
652
|
).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 +669,17 @@ async function main(argv = process.argv) {
|
|
|
605
669
|
);
|
|
606
670
|
program.command("validate").argument("<file>", "a loop-definition file to check").description(
|
|
607
671
|
"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) => {
|
|
672
|
+
).option("--json", "emit JSON with the loaded job shape").action(async (file, flags) => {
|
|
609
673
|
const { job } = await loadJob(file);
|
|
610
|
-
const
|
|
674
|
+
const shape = jobMeta(job);
|
|
675
|
+
if (flags.json) {
|
|
676
|
+
process.stdout.write(
|
|
677
|
+
`${JSON.stringify({ file, ok: true, executed: false, shape }, null, 2)}
|
|
678
|
+
`
|
|
679
|
+
);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const plan = renderPlan(shape);
|
|
611
683
|
process.stdout.write(
|
|
612
684
|
`\u2713 ${file} loads (not executed)
|
|
613
685
|
${plan.map((l) => ` ${l}`).join("\n")}
|
|
@@ -616,10 +688,14 @@ ${plan.map((l) => ` ${l}`).join("\n")}
|
|
|
616
688
|
});
|
|
617
689
|
program.command("describe").argument("<file>", "a loop-definition file").description(
|
|
618
690
|
"print a loop's shape (its gate, body, and dag nodes) without running it"
|
|
619
|
-
).action(async (file) => {
|
|
691
|
+
).option("--json", "emit the job shape as JSON").action(async (file, flags) => {
|
|
620
692
|
const { job } = await loadJob(file);
|
|
621
|
-
|
|
622
|
-
|
|
693
|
+
const shape = jobMeta(job);
|
|
694
|
+
process.stdout.write(
|
|
695
|
+
flags.json ? `${JSON.stringify(shape, null, 2)}
|
|
696
|
+
` : `${renderPlan(shape).join("\n")}
|
|
697
|
+
`
|
|
698
|
+
);
|
|
623
699
|
});
|
|
624
700
|
program.command("list").alias("ls").description("list supervised runs (start one with `loops run --supervise`)").action(() => {
|
|
625
701
|
const runs = listRuns();
|
|
@@ -717,6 +793,71 @@ ${renderPlan(r.shape).map((l) => ` ${l}`).join("\n")}
|
|
|
717
793
|
}
|
|
718
794
|
process.removeListener("SIGINT", onSig);
|
|
719
795
|
});
|
|
796
|
+
program.command("records").argument("<runId>", "a run id from `loops list`").description("show a supervised run's semantic records").option(
|
|
797
|
+
"--kind <kind>",
|
|
798
|
+
"filter by record kind: dispatch | completion | surfacing | revision-emitted | revision-routed | revision"
|
|
799
|
+
).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) => {
|
|
800
|
+
const records = readSemanticRecords(runId);
|
|
801
|
+
if (!records) {
|
|
802
|
+
process.stderr.write(`no semantic records for run "${runId}" in ${runsHome()}
|
|
803
|
+
`);
|
|
804
|
+
process.exitCode = 1;
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const validKinds = [
|
|
808
|
+
"dispatch",
|
|
809
|
+
"completion",
|
|
810
|
+
"surfacing",
|
|
811
|
+
"revision-emitted",
|
|
812
|
+
"revision-routed",
|
|
813
|
+
"revision"
|
|
814
|
+
];
|
|
815
|
+
if (flags.kind && !validKinds.includes(flags.kind)) {
|
|
816
|
+
process.stderr.write(
|
|
817
|
+
`--kind must be one of ${validKinds.join(" | ")}, got "${flags.kind}"
|
|
818
|
+
`
|
|
819
|
+
);
|
|
820
|
+
process.exitCode = 1;
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
let pathPrefix;
|
|
824
|
+
if (flags.path != null) {
|
|
825
|
+
pathPrefix = normalizeRecordPath(flags.path);
|
|
826
|
+
if (!pathPrefix) {
|
|
827
|
+
process.stderr.write("--path must contain at least one path segment\n");
|
|
828
|
+
process.exitCode = 1;
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
let since;
|
|
833
|
+
let last2;
|
|
834
|
+
try {
|
|
835
|
+
if (flags.since != null) since = parseSinceFlag(flags.since);
|
|
836
|
+
if (flags.last != null) last2 = parsePositiveIntFlag(flags.last, "--last");
|
|
837
|
+
} catch (e) {
|
|
838
|
+
process.stderr.write(`${e instanceof Error ? e.message : String(e)}
|
|
839
|
+
`);
|
|
840
|
+
process.exitCode = 1;
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
let filtered = flags.kind ? flags.kind === "revision" ? records.filter(
|
|
844
|
+
(r) => r.kind === "revision-emitted" || r.kind === "revision-routed"
|
|
845
|
+
) : records.filter((r) => r.kind === flags.kind) : records;
|
|
846
|
+
if (pathPrefix) filtered = filtered.filter((r) => matchesRecordPath(r, pathPrefix));
|
|
847
|
+
if (since != null) filtered = filtered.filter((r) => r.ts >= since);
|
|
848
|
+
if (last2 != null) filtered = filtered.slice(-last2);
|
|
849
|
+
if (flags.json) {
|
|
850
|
+
for (const record of filtered) {
|
|
851
|
+
process.stdout.write(`${JSON.stringify(record)}
|
|
852
|
+
`);
|
|
853
|
+
}
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
for (const record of filtered) {
|
|
857
|
+
process.stdout.write(`${formatSemanticRecord(record)}
|
|
858
|
+
`);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
720
861
|
await program.parseAsync(argv);
|
|
721
862
|
}
|
|
722
863
|
|