agent-cli-runtime 0.1.0-alpha.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.
Files changed (151) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/CONTRIBUTING.md +60 -0
  3. package/LICENSE +202 -0
  4. package/README.md +573 -0
  5. package/README.zh-CN.md +571 -0
  6. package/SECURITY.md +35 -0
  7. package/dist/adapters/adapter-types.d.ts +138 -0
  8. package/dist/adapters/adapter-types.js +2 -0
  9. package/dist/adapters/adapter-types.js.map +1 -0
  10. package/dist/adapters/claude.d.ts +2 -0
  11. package/dist/adapters/claude.js +97 -0
  12. package/dist/adapters/claude.js.map +1 -0
  13. package/dist/adapters/codex.d.ts +3 -0
  14. package/dist/adapters/codex.js +120 -0
  15. package/dist/adapters/codex.js.map +1 -0
  16. package/dist/adapters/opencode.d.ts +4 -0
  17. package/dist/adapters/opencode.js +111 -0
  18. package/dist/adapters/opencode.js.map +1 -0
  19. package/dist/adapters/registry.d.ts +9 -0
  20. package/dist/adapters/registry.js +23 -0
  21. package/dist/adapters/registry.js.map +1 -0
  22. package/dist/cli/main.d.ts +2 -0
  23. package/dist/cli/main.js +978 -0
  24. package/dist/cli/main.js.map +1 -0
  25. package/dist/core/async-queue.d.ts +10 -0
  26. package/dist/core/async-queue.js +49 -0
  27. package/dist/core/async-queue.js.map +1 -0
  28. package/dist/core/diagnostics.d.ts +20 -0
  29. package/dist/core/diagnostics.js +4 -0
  30. package/dist/core/diagnostics.js.map +1 -0
  31. package/dist/core/event-contract.d.ts +32 -0
  32. package/dist/core/event-contract.js +128 -0
  33. package/dist/core/event-contract.js.map +1 -0
  34. package/dist/core/events.d.ts +147 -0
  35. package/dist/core/events.js +4 -0
  36. package/dist/core/events.js.map +1 -0
  37. package/dist/core/ids.d.ts +1 -0
  38. package/dist/core/ids.js +5 -0
  39. package/dist/core/ids.js.map +1 -0
  40. package/dist/core/redaction.d.ts +4 -0
  41. package/dist/core/redaction.js +51 -0
  42. package/dist/core/redaction.js.map +1 -0
  43. package/dist/core/runtime.d.ts +41 -0
  44. package/dist/core/runtime.js +83 -0
  45. package/dist/core/runtime.js.map +1 -0
  46. package/dist/core/schema-contract.d.ts +55 -0
  47. package/dist/core/schema-contract.js +143 -0
  48. package/dist/core/schema-contract.js.map +1 -0
  49. package/dist/detection/detect.d.ts +14 -0
  50. package/dist/detection/detect.js +293 -0
  51. package/dist/detection/detect.js.map +1 -0
  52. package/dist/detection/env.d.ts +2 -0
  53. package/dist/detection/env.js +15 -0
  54. package/dist/detection/env.js.map +1 -0
  55. package/dist/detection/executable-resolution.d.ts +12 -0
  56. package/dist/detection/executable-resolution.js +50 -0
  57. package/dist/detection/executable-resolution.js.map +1 -0
  58. package/dist/detection/invocation.d.ts +9 -0
  59. package/dist/detection/invocation.js +22 -0
  60. package/dist/detection/invocation.js.map +1 -0
  61. package/dist/goals/goal-scheduler.d.ts +31 -0
  62. package/dist/goals/goal-scheduler.js +518 -0
  63. package/dist/goals/goal-scheduler.js.map +1 -0
  64. package/dist/goals/goal-store.d.ts +37 -0
  65. package/dist/goals/goal-store.js +300 -0
  66. package/dist/goals/goal-store.js.map +1 -0
  67. package/dist/goals/goal-types.d.ts +103 -0
  68. package/dist/goals/goal-types.js +2 -0
  69. package/dist/goals/goal-types.js.map +1 -0
  70. package/dist/goals/planner-prompts.d.ts +3 -0
  71. package/dist/goals/planner-prompts.js +26 -0
  72. package/dist/goals/planner-prompts.js.map +1 -0
  73. package/dist/goals/task-graph.d.ts +9 -0
  74. package/dist/goals/task-graph.js +229 -0
  75. package/dist/goals/task-graph.js.map +1 -0
  76. package/dist/goals/validation-runner.d.ts +7 -0
  77. package/dist/goals/validation-runner.js +63 -0
  78. package/dist/goals/validation-runner.js.map +1 -0
  79. package/dist/index.d.ts +11 -0
  80. package/dist/index.js +2 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/parsers/claude-stream-json.d.ts +11 -0
  83. package/dist/parsers/claude-stream-json.js +102 -0
  84. package/dist/parsers/claude-stream-json.js.map +1 -0
  85. package/dist/parsers/codex-json.d.ts +8 -0
  86. package/dist/parsers/codex-json.js +107 -0
  87. package/dist/parsers/codex-json.js.map +1 -0
  88. package/dist/parsers/line-buffer.d.ts +7 -0
  89. package/dist/parsers/line-buffer.js +28 -0
  90. package/dist/parsers/line-buffer.js.map +1 -0
  91. package/dist/parsers/opencode-json.d.ts +8 -0
  92. package/dist/parsers/opencode-json.js +72 -0
  93. package/dist/parsers/opencode-json.js.map +1 -0
  94. package/dist/parsers/plain-lines.d.ts +6 -0
  95. package/dist/parsers/plain-lines.js +9 -0
  96. package/dist/parsers/plain-lines.js.map +1 -0
  97. package/dist/public-types.d.ts +143 -0
  98. package/dist/public-types.js +2 -0
  99. package/dist/public-types.js.map +1 -0
  100. package/dist/runs/process-runner.d.ts +35 -0
  101. package/dist/runs/process-runner.js +97 -0
  102. package/dist/runs/process-runner.js.map +1 -0
  103. package/dist/runs/prompt-transport.d.ts +10 -0
  104. package/dist/runs/prompt-transport.js +43 -0
  105. package/dist/runs/prompt-transport.js.map +1 -0
  106. package/dist/runs/run-result.d.ts +9 -0
  107. package/dist/runs/run-result.js +22 -0
  108. package/dist/runs/run-result.js.map +1 -0
  109. package/dist/runs/run-scheduler.d.ts +25 -0
  110. package/dist/runs/run-scheduler.js +552 -0
  111. package/dist/runs/run-scheduler.js.map +1 -0
  112. package/dist/runs/run-store.d.ts +42 -0
  113. package/dist/runs/run-store.js +297 -0
  114. package/dist/runs/run-store.js.map +1 -0
  115. package/dist/runs/run-types.d.ts +59 -0
  116. package/dist/runs/run-types.js +2 -0
  117. package/dist/runs/run-types.js.map +1 -0
  118. package/dist/smoke/parser-samples.d.ts +17 -0
  119. package/dist/smoke/parser-samples.js +186 -0
  120. package/dist/smoke/parser-samples.js.map +1 -0
  121. package/dist/storage/file-storage.d.ts +35 -0
  122. package/dist/storage/file-storage.js +271 -0
  123. package/dist/storage/file-storage.js.map +1 -0
  124. package/dist/storage/jsonl-store.d.ts +9 -0
  125. package/dist/storage/jsonl-store.js +138 -0
  126. package/dist/storage/jsonl-store.js.map +1 -0
  127. package/dist/storage/manifest-validation.d.ts +11 -0
  128. package/dist/storage/manifest-validation.js +102 -0
  129. package/dist/storage/manifest-validation.js.map +1 -0
  130. package/dist/storage/storage-lease.d.ts +40 -0
  131. package/dist/storage/storage-lease.js +223 -0
  132. package/dist/storage/storage-lease.js.map +1 -0
  133. package/dist/storage/storage-types.d.ts +55 -0
  134. package/dist/storage/storage-types.js +2 -0
  135. package/dist/storage/storage-types.js.map +1 -0
  136. package/dist/storage/store-inspection.d.ts +28 -0
  137. package/dist/storage/store-inspection.js +941 -0
  138. package/dist/storage/store-inspection.js.map +1 -0
  139. package/docs/api-schema-contract.md +92 -0
  140. package/docs/compatibility.md +832 -0
  141. package/docs/daemon-ready-contract.md +283 -0
  142. package/docs/production-readiness.md +281 -0
  143. package/docs/release-checklist.md +257 -0
  144. package/docs/release-publish-runbook.md +201 -0
  145. package/docs/release-report.md +517 -0
  146. package/docs/ssot.md +1257 -0
  147. package/examples/cli-dogfood.md +113 -0
  148. package/examples/library-goal.js +94 -0
  149. package/examples/library-run.js +84 -0
  150. package/package.json +79 -0
  151. package/scripts/dogfood.mjs +243 -0
@@ -0,0 +1,113 @@
1
+ # CLI Dogfood
2
+
3
+ These commands are safe defaults for a local checkout. They do not launch an authenticated real agent run unless `--allow-real-run` is explicitly present.
4
+
5
+ ## Local Checkout
6
+
7
+ ```bash
8
+ npm ci
9
+ npm run build
10
+ npm run dogfood
11
+ ```
12
+
13
+ `npm run dogfood` rebuilds the package, runs offline fixtures and fake conformance, performs real local detection/profile conformance without real runs, checks `agents` and `doctor`, performs an npm pack dry-run, installs the packed tarball into a temporary project, and verifies the installed package import plus CLI fixtures/fake paths.
14
+
15
+ ## Fixtures And Fake Paths
16
+
17
+ ```bash
18
+ node ./dist/cli/main.js conformance --mode fixtures --json
19
+ node ./dist/cli/main.js conformance --mode fake --json
20
+ node ./dist/cli/main.js smoke --mode fixtures --json
21
+ node ./dist/cli/main.js agents --json
22
+ node ./dist/cli/main.js doctor --json
23
+ ```
24
+
25
+ Fixtures exercise parser contracts offline. Fake conformance creates temporary fake CLIs and runs the real adapter argv/stdin/parser path without real accounts, provider tokens, or network agent runs.
26
+
27
+ ## Real Local Profile Conformance
28
+
29
+ ```bash
30
+ node ./dist/cli/main.js conformance --mode real --agent all --json
31
+ ```
32
+
33
+ This performs real executable/version/auth/model/profile certification. It does not launch a real agent run. Runnable adapters report `runClassification: "real_run_skipped"` and `skippedReason: "real_run_not_allowed"`.
34
+
35
+ ## Optional Real Run
36
+
37
+ Run this only on a machine where the selected CLI is installed, authorized, and safe to use. Without `--cwd`, the command uses an isolated temporary cwd and asks for read-only behavior.
38
+
39
+ ```bash
40
+ node ./dist/cli/main.js conformance \
41
+ --mode real \
42
+ --agent codex \
43
+ --allow-real-run \
44
+ --json \
45
+ --timeout-ms 30000
46
+ ```
47
+
48
+ Equivalent detailed smoke:
49
+
50
+ ```bash
51
+ node ./dist/cli/main.js smoke \
52
+ --mode real \
53
+ --agent codex \
54
+ --allow-real-run \
55
+ --json \
56
+ --diagnostics \
57
+ --timeout-ms 30000
58
+ ```
59
+
60
+ ## Run And Goal
61
+
62
+ Use a disposable directory for manual mutation tests:
63
+
64
+ ```bash
65
+ tmp="$(mktemp -d /tmp/agent-runtime-run-XXXXXX)"
66
+ node ./dist/cli/main.js run \
67
+ --agent codex \
68
+ --cwd "$tmp" \
69
+ --permission workspace-write \
70
+ --prompt "Create smoke.txt containing exactly: agent-runtime smoke ok" \
71
+ --stream jsonl \
72
+ --diagnostics
73
+ ```
74
+
75
+ ```bash
76
+ tmp="$(mktemp -d /tmp/agent-runtime-goal-XXXXXX)"
77
+ node ./dist/cli/main.js goal \
78
+ --agent codex \
79
+ --cwd "$tmp" \
80
+ --permission workspace-write \
81
+ --prompt "Create one file named goal-smoke.txt containing exactly: agent-runtime goal smoke ok" \
82
+ --stream jsonl \
83
+ --diagnostics
84
+ ```
85
+
86
+ ## Diagnostics And Store Health
87
+
88
+ ```bash
89
+ store="$(mktemp -d /tmp/agent-runtime-store-XXXXXX)"
90
+ node ./dist/cli/main.js store-health --storage-dir "$store" --json
91
+ node ./dist/cli/main.js store-lock --storage-dir "$store" --json
92
+ node ./dist/cli/main.js store-repair --storage-dir "$store" --dry-run --json
93
+ ```
94
+
95
+ After a stored run or goal exists:
96
+
97
+ ```bash
98
+ node ./dist/cli/main.js runs --storage-dir "$store" --json
99
+ node ./dist/cli/main.js goals --storage-dir "$store" --json
100
+ node ./dist/cli/main.js replay-run run_123 --storage-dir "$store" --after 10 --jsonl
101
+ node ./dist/cli/main.js replay-goal goal_123 --storage-dir "$store" --after 10 --jsonl
102
+ node ./dist/cli/main.js diagnostics run run_123 --storage-dir "$store" --json
103
+ node ./dist/cli/main.js diagnostics goal goal_123 --storage-dir "$store" --json --out diagnostics-goal_123.json
104
+ ```
105
+
106
+ ## Package Boundary
107
+
108
+ ```bash
109
+ npm pack --dry-run
110
+ npm pack --json --ignore-scripts --pack-destination "$(mktemp -d /tmp/agent-runtime-pack-XXXXXX)"
111
+ ```
112
+
113
+ Before publishing, confirm the packed file list includes `dist/`, README files, `LICENSE`, `docs/`, and `examples/`, and excludes `.reference/`, `tests/`, fixture files, raw CLI output, real private paths, and secrets.
@@ -0,0 +1,94 @@
1
+ import { chmod, mkdir, mkdtemp, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { createAgentRuntime } from "agent-cli-runtime";
5
+
6
+ async function writeFakeCodex(binDir) {
7
+ const bin = path.join(binDir, "codex");
8
+ await writeFile(bin, `#!${process.execPath}
9
+ const args = process.argv.slice(2);
10
+ if (args[0] === "--version") {
11
+ console.log("codex-cli example-fake");
12
+ process.exit(0);
13
+ }
14
+ if (args[0] === "debug" && args[1] === "models") {
15
+ console.log(JSON.stringify({ models: [{ slug: "gpt-example", display_name: "GPT Example" }] }));
16
+ process.exit(0);
17
+ }
18
+ let input = "";
19
+ process.stdin.setEncoding("utf8");
20
+ process.stdin.on("data", (chunk) => input += chunk);
21
+ process.stdin.on("end", () => {
22
+ console.log(JSON.stringify({ type: "thread.started" }));
23
+ if (input.includes("Return strict JSON only")) {
24
+ const nodeCommand = JSON.stringify(process.execPath) + ' -e "process.exit(0)"';
25
+ const graph = {
26
+ tasks: [
27
+ {
28
+ id: "T001",
29
+ title: "Run fake task",
30
+ objective: "Complete the local fake task without editing files.",
31
+ dependencies: [],
32
+ allowedFiles: [],
33
+ validationCommands: [nodeCommand],
34
+ agentId: "codex"
35
+ }
36
+ ]
37
+ };
38
+ console.log(JSON.stringify({ type: "item.completed", item: { type: "agent_message", text: JSON.stringify(graph) } }));
39
+ } else {
40
+ console.log(JSON.stringify({ type: "item.completed", item: { type: "agent_message", text: "fake task completed" } }));
41
+ }
42
+ console.log(JSON.stringify({ type: "turn.completed", usage: { input_tokens: 20, output_tokens: 10 } }));
43
+ });
44
+ `, "utf8");
45
+ await chmod(bin, 0o755);
46
+ return bin;
47
+ }
48
+
49
+ const root = await mkdtemp(path.join(os.tmpdir(), "agent-runtime-example-goal-"));
50
+ const binDir = path.join(root, "bin");
51
+ const cwd = path.join(root, "work");
52
+ const storageDir = path.join(root, "store");
53
+ await Promise.all([
54
+ mkdir(binDir, { recursive: true }),
55
+ mkdir(cwd, { recursive: true }),
56
+ ]);
57
+ const codexBin = await writeFakeCodex(binDir);
58
+
59
+ const runtime = createAgentRuntime({
60
+ storageDir,
61
+ env: {
62
+ ...process.env,
63
+ PATH: binDir,
64
+ CODEX_BIN: codexBin,
65
+ },
66
+ searchPath: [binDir],
67
+ });
68
+
69
+ try {
70
+ const goal = await runtime.createGoal({
71
+ cwd,
72
+ objective: "Dogfood the goal scheduler with one fake task.",
73
+ defaultAgentId: "codex",
74
+ permissionPolicy: "read-only",
75
+ timeoutMs: 10_000,
76
+ });
77
+
78
+ const eventTypes = [];
79
+ for await (const event of goal.events) eventTypes.push(event.type);
80
+
81
+ const record = await runtime.getGoal(goal.goalId);
82
+ const replay = await runtime.replayGoalEvents(goal.goalId);
83
+ const diagnostics = await runtime.exportDiagnostics({ kind: "goal", goalId: goal.goalId });
84
+
85
+ console.log(JSON.stringify({
86
+ goal: { id: record?.id, status: record?.status, result: record?.result },
87
+ tasks: record?.tasks.map((task) => ({ id: task.id, status: task.status, attempts: task.evidence.attempts?.length ?? 0 })),
88
+ eventTypes,
89
+ replayEvents: replay.length,
90
+ diagnosticsSchema: diagnostics.schemaVersion,
91
+ }, null, 2));
92
+ } finally {
93
+ await runtime.shutdown("example complete");
94
+ }
@@ -0,0 +1,84 @@
1
+ import { chmod, mkdir, mkdtemp, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { createAgentRuntime } from "agent-cli-runtime";
5
+
6
+ async function writeFakeCodex(binDir) {
7
+ const bin = path.join(binDir, "codex");
8
+ await writeFile(bin, `#!${process.execPath}
9
+ const args = process.argv.slice(2);
10
+ if (args[0] === "--version") {
11
+ console.log("codex-cli example-fake");
12
+ process.exit(0);
13
+ }
14
+ if (args[0] === "debug" && args[1] === "models") {
15
+ console.log(JSON.stringify({ models: [{ slug: "gpt-example", display_name: "GPT Example" }] }));
16
+ process.exit(0);
17
+ }
18
+ let input = "";
19
+ process.stdin.setEncoding("utf8");
20
+ process.stdin.on("data", (chunk) => input += chunk);
21
+ process.stdin.on("end", () => {
22
+ console.log(JSON.stringify({ type: "thread.started" }));
23
+ console.log(JSON.stringify({
24
+ type: "item.completed",
25
+ item: { type: "agent_message", text: "example run ok: " + input.trim().slice(0, 80) }
26
+ }));
27
+ console.log(JSON.stringify({ type: "turn.completed", usage: { input_tokens: 12, output_tokens: 8 } }));
28
+ });
29
+ `, "utf8");
30
+ await chmod(bin, 0o755);
31
+ return bin;
32
+ }
33
+
34
+ const root = await mkdtemp(path.join(os.tmpdir(), "agent-runtime-example-run-"));
35
+ const binDir = path.join(root, "bin");
36
+ const cwd = path.join(root, "work");
37
+ const storageDir = path.join(root, "store");
38
+ await Promise.all([
39
+ mkdir(binDir, { recursive: true }),
40
+ mkdir(cwd, { recursive: true }),
41
+ ]);
42
+ const codexBin = await writeFakeCodex(binDir);
43
+
44
+ const runtime = createAgentRuntime({
45
+ storageDir,
46
+ env: {
47
+ ...process.env,
48
+ PATH: binDir,
49
+ CODEX_BIN: codexBin,
50
+ },
51
+ searchPath: [binDir],
52
+ });
53
+
54
+ try {
55
+ const agents = await runtime.detect({ includeUnavailable: true });
56
+ const run = await runtime.run({
57
+ agentId: "codex",
58
+ cwd,
59
+ prompt: "Say hello from the library-run example.",
60
+ permissionPolicy: "read-only",
61
+ timeoutMs: 10_000,
62
+ });
63
+
64
+ let observedText = "";
65
+ for await (const event of run.events) {
66
+ if (event.type === "text_delta") observedText += event.text;
67
+ }
68
+
69
+ const record = await runtime.getRun(run.runId);
70
+ const replay = await runtime.replayRunEvents(run.runId);
71
+ const diagnostics = await runtime.exportDiagnostics({ kind: "run", runId: run.runId });
72
+ const health = await runtime.inspectStore();
73
+
74
+ console.log(JSON.stringify({
75
+ detected: agents.map((agent) => ({ id: agent.id, available: agent.available, version: agent.version })),
76
+ run: { id: record?.id, status: record?.status, result: record?.result },
77
+ observedText,
78
+ replayEvents: replay.length,
79
+ diagnosticsSchema: diagnostics.schemaVersion,
80
+ storeOk: health.ok,
81
+ }, null, 2));
82
+ } finally {
83
+ await runtime.shutdown("example complete");
84
+ }
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "agent-cli-runtime",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Local-first TypeScript runtime for scheduling Codex CLI, Claude Code, OpenCode, and compatible coding-agent CLIs.",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/iiwish/agent-cli-runtime.git"
10
+ },
11
+ "homepage": "https://github.com/iiwish/agent-cli-runtime#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/iiwish/agent-cli-runtime/issues"
14
+ },
15
+ "bin": {
16
+ "agent-runtime": "dist/cli/main.js"
17
+ },
18
+ "publishConfig": {
19
+ "tag": "alpha"
20
+ },
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "CHANGELOG.md",
32
+ "CONTRIBUTING.md",
33
+ "SECURITY.md",
34
+ "README.md",
35
+ "README.zh-CN.md",
36
+ "LICENSE",
37
+ "examples",
38
+ "docs/release-checklist.md",
39
+ "docs/release-report.md",
40
+ "docs/release-publish-runbook.md",
41
+ "docs/api-schema-contract.md",
42
+ "docs/daemon-ready-contract.md",
43
+ "docs/ssot.md",
44
+ "docs/compatibility.md",
45
+ "docs/production-readiness.md",
46
+ "scripts/dogfood.mjs"
47
+ ],
48
+ "scripts": {
49
+ "build": "tsc -p tsconfig.json",
50
+ "ci": "npm run typecheck && npm run lint && npm test && npm run build && npm audit --omit=dev && npm run package:check && npm pack --dry-run",
51
+ "daemon:verify": "node ./scripts/verify-daemon-ready.mjs",
52
+ "dogfood": "node ./scripts/dogfood.mjs",
53
+ "package:check": "node ./scripts/check-package-boundary.mjs",
54
+ "prepublish:check": "npm run typecheck && npm run lint && npm test && npm run build && npm run daemon:verify && npm run runtime:safety && npm run dogfood && npm audit --omit=dev && npm run package:check && npm pack --dry-run",
55
+ "prepack": "npm run build",
56
+ "release:candidate": "node ./scripts/create-release-candidate.mjs",
57
+ "release:verify": "node ./scripts/verify-release-artifacts.mjs",
58
+ "runtime:safety": "node ./scripts/verify-runtime-safety.mjs",
59
+ "typecheck": "tsc -p tsconfig.json --noEmit",
60
+ "lint": "tsc -p tsconfig.json --noEmit",
61
+ "test": "npm run build && vitest run --reporter=verbose --no-file-parallelism --testTimeout 30000"
62
+ },
63
+ "keywords": [
64
+ "agent",
65
+ "cli",
66
+ "codex",
67
+ "claude",
68
+ "opencode",
69
+ "runtime"
70
+ ],
71
+ "engines": {
72
+ "node": ">=20"
73
+ },
74
+ "devDependencies": {
75
+ "@types/node": "^24.0.0",
76
+ "typescript": "^5.8.0",
77
+ "vitest": "^3.2.0"
78
+ }
79
+ }
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+ import { chmodSync, mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+
7
+ const repoRoot = process.cwd();
8
+ const node = process.execPath;
9
+
10
+ function run(command, args, options = {}) {
11
+ console.log(`\n$ ${[command, ...args].join(" ")}`);
12
+ const result = spawnSync(command, args, {
13
+ cwd: options.cwd ?? repoRoot,
14
+ env: options.env ?? process.env,
15
+ stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit",
16
+ encoding: "utf8",
17
+ });
18
+ if (result.status !== 0) {
19
+ if (options.capture) {
20
+ process.stdout.write(result.stdout ?? "");
21
+ process.stderr.write(result.stderr ?? "");
22
+ }
23
+ throw new Error(`command failed: ${command} ${args.join(" ")}`);
24
+ }
25
+ return result.stdout ?? "";
26
+ }
27
+
28
+ function writeNodeBin(dir, name, body) {
29
+ const file = path.join(dir, name);
30
+ writeFileSync(file, `#!${node}\n${body}`, "utf8");
31
+ chmodSync(file, 0o755);
32
+ return file;
33
+ }
34
+
35
+ function createFakeBins(parent) {
36
+ const binDir = path.join(parent, "fake-bin");
37
+ mkdirSync(binDir, { recursive: true });
38
+ const codex = writeNodeBin(binDir, "codex", `
39
+ const args = process.argv.slice(2);
40
+ if (args[0] === "--version") { console.log("codex-cli dogfood-fake"); process.exit(0); }
41
+ if (args[0] === "debug" && args[1] === "models") { console.log(JSON.stringify({ models: [{ slug: "gpt-dogfood", display_name: "GPT Dogfood" }] })); process.exit(0); }
42
+ process.stdin.resume();
43
+ process.stdin.on("end", () => {
44
+ console.log(JSON.stringify({ type: "thread.started" }));
45
+ console.log(JSON.stringify({ type: "item.completed", item: { type: "agent_message", text: "agent-runtime codex smoke ok" } }));
46
+ });
47
+ `);
48
+ const claude = writeNodeBin(binDir, "claude", `
49
+ const args = process.argv.slice(2);
50
+ if (args[0] === "--version") { console.log("Claude Code dogfood-fake"); process.exit(0); }
51
+ if (args[0] === "-p" && args[1] === "--help") { console.log("--include-partial-messages\\n--add-dir"); process.exit(0); }
52
+ if (args[0] === "auth" && args[1] === "status") { console.log(JSON.stringify({ loggedIn: true })); process.exit(0); }
53
+ process.stdin.resume();
54
+ process.stdin.on("end", () => {
55
+ console.log(JSON.stringify({ type: "system" }));
56
+ console.log(JSON.stringify({ type: "assistant", message: { content: [{ type: "text", text: "agent-runtime claude smoke ok" }] } }));
57
+ });
58
+ `);
59
+ const opencode = writeNodeBin(binDir, "opencode", `
60
+ const args = process.argv.slice(2);
61
+ if (args[0] === "--version") { console.log("opencode dogfood-fake"); process.exit(0); }
62
+ if (args[0] === "models") { console.log("openai/gpt-dogfood"); process.exit(0); }
63
+ process.stdin.resume();
64
+ process.stdin.on("end", () => {
65
+ console.log(JSON.stringify({ type: "step_start" }));
66
+ console.log(JSON.stringify({ type: "text", part: { text: "agent-runtime opencode smoke ok" } }));
67
+ });
68
+ `);
69
+ writeNodeBin(binDir, "opencode-cli", `
70
+ const args = process.argv.slice(2);
71
+ if (args[0] === "--version") { console.log("opencode dogfood-fake"); process.exit(0); }
72
+ if (args[0] === "models") { console.log("openai/gpt-dogfood"); process.exit(0); }
73
+ process.stdin.resume();
74
+ process.stdin.on("end", () => {
75
+ console.log(JSON.stringify({ type: "step_start" }));
76
+ console.log(JSON.stringify({ type: "text", part: { text: "agent-runtime opencode smoke ok" } }));
77
+ });
78
+ `);
79
+ return {
80
+ PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ""}`,
81
+ CODEX_BIN: codex,
82
+ CLAUDE_BIN: claude,
83
+ OPENCODE_BIN: opencode,
84
+ };
85
+ }
86
+
87
+ function runCli(args, options = {}) {
88
+ return run(node, ["./dist/cli/main.js", ...args], options);
89
+ }
90
+
91
+ function installSmoke() {
92
+ const tmp = mkdtempSync(path.join(tmpdir(), "agent-runtime-dogfood-"));
93
+ const packText = run("npm", ["pack", "--json", "--ignore-scripts", "--pack-destination", tmp], { capture: true });
94
+ const [packInfo] = JSON.parse(packText);
95
+ const tarball = path.join(tmp, packInfo.filename);
96
+ run("npm", ["init", "-y"], { cwd: tmp, capture: true });
97
+ run("npm", ["install", tarball, "--no-save", "--ignore-scripts", "--no-audit", "--no-fund"], { cwd: tmp });
98
+
99
+ const fakeEnv = { ...process.env, ...createFakeBins(tmp) };
100
+ const consumerAgent = writeNodeBin(path.join(tmp, "fake-bin"), "consumer-agent", `
101
+ const args = process.argv.slice(2);
102
+ if (args[0] === "--version") {
103
+ console.log("consumer-agent dogfood-fake");
104
+ process.exit(0);
105
+ }
106
+ let input = "";
107
+ process.stdin.setEncoding("utf8");
108
+ process.stdin.on("data", chunk => input += chunk);
109
+ process.stdin.on("end", () => {
110
+ if (input.includes("Return strict JSON")) {
111
+ console.log(JSON.stringify({ tasks: [
112
+ { id: "T001", title: "Consumer task", objective: "consumer task run", dependencies: [], validationCommands: ["node -e \\"process.exit(0)\\""] }
113
+ ] }));
114
+ return;
115
+ }
116
+ console.log("consumer fake run ok");
117
+ });
118
+ `);
119
+ fakeEnv.PATH = `${path.dirname(consumerAgent)}${path.delimiter}${fakeEnv.PATH ?? ""}`;
120
+ const installedCli = path.join(tmp, "node_modules", ".bin", "agent-runtime");
121
+ run(node, ["-e", "const m = await import('agent-cli-runtime'); if (typeof m.createAgentRuntime !== 'function') process.exit(1); console.log(typeof m.createAgentRuntime);"], { cwd: tmp });
122
+ writeFileSync(path.join(tmp, "tsconfig.json"), JSON.stringify({
123
+ compilerOptions: {
124
+ target: "ES2022",
125
+ module: "NodeNext",
126
+ moduleResolution: "NodeNext",
127
+ strict: true,
128
+ skipLibCheck: false,
129
+ lib: ["ES2022", "DOM"],
130
+ noEmit: true,
131
+ },
132
+ include: ["consumer.ts"],
133
+ }, null, 2), "utf8");
134
+ writeFileSync(path.join(tmp, "consumer.ts"), `
135
+ import {
136
+ createAgentRuntime,
137
+ type AgentAdapterDef,
138
+ type AgentEvent,
139
+ type CreateGoalRequest,
140
+ type DiagnosticsBundle,
141
+ type ReplayEvent,
142
+ type RunRequest,
143
+ type RuntimeOptions,
144
+ type StoreHealth,
145
+ } from "agent-cli-runtime";
146
+
147
+ const adapter: AgentAdapterDef = {
148
+ id: "consumer-fake",
149
+ displayName: "Consumer Fake",
150
+ bin: "consumer-agent",
151
+ versionArgs: ["--version"],
152
+ fallbackModels: [{ id: "default", label: "Default" }],
153
+ buildArgs: () => [],
154
+ promptTransport: { kind: "stdin", inputFormat: "text" },
155
+ stream: {
156
+ create: () => ({
157
+ parse: (chunk: string) => [{ type: "text_delta", text: chunk }],
158
+ flush: () => [],
159
+ }),
160
+ },
161
+ };
162
+
163
+ const runRequest: RunRequest = { agentId: "consumer-fake", cwd: ".", prompt: "consumer run" };
164
+ const goalRequest: CreateGoalRequest = { defaultAgentId: "consumer-fake", cwd: ".", objective: "consumer goal" };
165
+ const options: RuntimeOptions = { adapters: [adapter], searchPath: ["."], storageDir: "./consumer-store" };
166
+ const runtime = createAgentRuntime(options);
167
+ const health: Promise<StoreHealth> = runtime.inspectStore();
168
+ const diagnostics: Promise<DiagnosticsBundle> = runtime.exportDiagnostics({ kind: "run", runId: "run_missing" });
169
+ const replay: ReplayEvent<AgentEvent>[] = [];
170
+
171
+ void runRequest;
172
+ void goalRequest;
173
+ void health;
174
+ void diagnostics;
175
+ void replay;
176
+ void runtime.shutdown();
177
+ `, "utf8");
178
+ writeFileSync(path.join(tmp, "consumer.mjs"), `
179
+ import { createAgentRuntime } from "agent-cli-runtime";
180
+
181
+ const fakeBin = ${JSON.stringify(path.join(tmp, "fake-bin"))};
182
+ const cwd = process.cwd();
183
+ const runtime = createAgentRuntime({
184
+ adapters: [{
185
+ id: "consumer-fake",
186
+ displayName: "Consumer Fake",
187
+ bin: "consumer-agent",
188
+ versionArgs: ["--version"],
189
+ fallbackModels: [{ id: "default", label: "Default" }],
190
+ buildArgs: () => [],
191
+ promptTransport: { kind: "stdin", inputFormat: "text" },
192
+ stream: {
193
+ create: () => ({
194
+ parse: (chunk) => chunk.split(/\\r?\\n/u).filter(Boolean).map((line) => ({ type: "text_delta", text: line + "\\n" })),
195
+ flush: () => [],
196
+ }),
197
+ },
198
+ }],
199
+ searchPath: [fakeBin],
200
+ storageDir: cwd + "/consumer-store",
201
+ });
202
+
203
+ const runHandle = await runtime.run({ agentId: "consumer-fake", cwd, prompt: "consumer run" });
204
+ for await (const _event of runHandle.events) {}
205
+ const run = await runtime.getRun(runHandle.runId);
206
+ const runReplay = await runtime.replayRunEvents(runHandle.runId);
207
+ const runDiagnostics = await runtime.exportDiagnostics({ kind: "run", runId: runHandle.runId });
208
+
209
+ const goalHandle = await runtime.createGoal({ defaultAgentId: "consumer-fake", cwd, objective: "consumer goal" });
210
+ for await (const _event of goalHandle.events) {}
211
+ const goal = await runtime.getGoal(goalHandle.goalId);
212
+ const goalReplay = await runtime.replayGoalEvents(goalHandle.goalId);
213
+ const goalDiagnostics = await runtime.exportDiagnostics({ kind: "goal", goalId: goalHandle.goalId });
214
+ const health = await runtime.inspectStore();
215
+ await runtime.shutdown("consumer smoke complete");
216
+
217
+ if (run?.status !== "succeeded" || goal?.status !== "succeeded") process.exit(1);
218
+ if (runReplay.length === 0 || goalReplay.length === 0) process.exit(1);
219
+ if (runDiagnostics.schemaVersion !== "agent-runtime.diagnostics.v1") process.exit(1);
220
+ if (goalDiagnostics.schemaVersion !== "agent-runtime.diagnostics.v1") process.exit(1);
221
+ if (!health.ok) process.exit(1);
222
+ console.log(JSON.stringify({ run: run.status, goal: goal.status, runReplay: runReplay.length, goalReplay: goalReplay.length }));
223
+ `, "utf8");
224
+ run(node, [path.join(repoRoot, "node_modules", "typescript", "bin", "tsc"), "--noEmit"], { cwd: tmp });
225
+ run(node, ["consumer.mjs"], { cwd: tmp, env: fakeEnv });
226
+ run(node, [installedCli, "agents", "--json"], { cwd: tmp, env: fakeEnv });
227
+ run(node, [installedCli, "doctor", "--json"], { cwd: tmp, env: fakeEnv });
228
+ run(node, [installedCli, "smoke", "--mode", "fixtures", "--json"], { cwd: tmp, env: fakeEnv });
229
+ run(node, [installedCli, "conformance", "--mode", "fixtures", "--json"], { cwd: tmp, env: fakeEnv });
230
+ run(node, [installedCli, "conformance", "--mode", "fake", "--json"], { cwd: tmp, env: fakeEnv });
231
+ }
232
+
233
+ run("npm", ["run", "build"]);
234
+ runCli(["conformance", "--mode", "fixtures", "--json"]);
235
+ runCli(["conformance", "--mode", "fake", "--json"]);
236
+ runCli(["conformance", "--mode", "real", "--agent", "all", "--json"]);
237
+ runCli(["smoke", "--mode", "fixtures", "--json"]);
238
+ runCli(["agents", "--json"]);
239
+ runCli(["doctor", "--json"]);
240
+ run(node, ["examples/library-run.js"]);
241
+ run(node, ["examples/library-goal.js"]);
242
+ run("npm", ["pack", "--dry-run"]);
243
+ installSmoke();