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,978 @@
1
+ #!/usr/bin/env node
2
+ import { chmod, lstat, mkdir, mkdtemp, readFile, readdir, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { createAgentRuntime } from "../index.js";
6
+ import { diagnosticCodeFromEvent, envelopeReplayEvents, envelopeStreamEvent, } from "../core/event-contract.js";
7
+ import { redactText } from "../core/redaction.js";
8
+ import { runParserFixtureCases } from "../smoke/parser-samples.js";
9
+ import { defaultAdapters } from "../adapters/registry.js";
10
+ import { atomicWriteJsonFile, exportDiagnosticsBundle, getStoredGoal, getStoredRun, inspectStoreDirectory, inspectStoreLock, inspectStoreRepair, inspectStoreRepairDryRun, listStoredGoals, listStoredRuns, replayStoredGoalEvents, replayStoredRunEvents, } from "../storage/store-inspection.js";
11
+ const DEFAULT_OBSERVED_TEXT_TAIL_BYTES = 2_048;
12
+ const MAX_CWD_SCAN_ENTRIES = 1_000;
13
+ const MAX_CWD_MUTATION_SAMPLE = 20;
14
+ const SKIPPED_CWD_SCAN_DIRS = new Set([".git", "node_modules", "dist", ".agent-runtime"]);
15
+ const CLI_ERROR_SCHEMA_VERSION = "agent-runtime.cliError.v1";
16
+ async function main() {
17
+ const parsed = parseArgs(process.argv.slice(2));
18
+ if (!parsed.command || parsed.command === "help" || parsed.flags.has("help")) {
19
+ printHelp();
20
+ return;
21
+ }
22
+ if (parsed.command === "store-health") {
23
+ const storageDir = requiredStringFlag(parsed, "storage-dir", "store-health requires --storage-dir <dir>");
24
+ output(parsed, inspectStoreDirectory(path.resolve(storageDir)));
25
+ return;
26
+ }
27
+ if (parsed.command === "store-lock") {
28
+ const storageDir = requiredStringFlag(parsed, "storage-dir", "store-lock requires --storage-dir <dir>");
29
+ output(parsed, inspectStoreLock(path.resolve(storageDir)));
30
+ return;
31
+ }
32
+ if (parsed.command === "store-repair") {
33
+ const storageDir = requiredStringFlag(parsed, "storage-dir", "store-repair requires --storage-dir <dir>");
34
+ if (parsed.flags.has("apply") && parsed.flags.has("dry-run"))
35
+ throw new Error("store-repair accepts either --dry-run or --apply, not both");
36
+ output(parsed, parsed.flags.has("apply")
37
+ ? inspectStoreRepair(path.resolve(storageDir), { apply: true })
38
+ : inspectStoreRepairDryRun(path.resolve(storageDir)));
39
+ return;
40
+ }
41
+ if (parsed.command === "diagnostics") {
42
+ await runDiagnosticsCommand(parsed);
43
+ return;
44
+ }
45
+ if (isReadOnlyStoreCommand(parsed.command)) {
46
+ runReadOnlyStoreCommand(parsed);
47
+ return;
48
+ }
49
+ if (parsed.command === "smoke") {
50
+ await runSmoke(parsed);
51
+ return;
52
+ }
53
+ if (parsed.command === "conformance") {
54
+ await runConformance(parsed);
55
+ return;
56
+ }
57
+ const runtime = createAgentRuntime(runtimeOptionsFromFlags(parsed));
58
+ try {
59
+ if (parsed.command === "agents") {
60
+ const agents = await runtime.detect({ includeUnavailable: true });
61
+ output(parsed, agents);
62
+ return;
63
+ }
64
+ if (parsed.command === "doctor") {
65
+ const agents = await runtime.detect({ includeUnavailable: true });
66
+ output(parsed, {
67
+ ok: agents.some((agent) => agent.available),
68
+ agents,
69
+ });
70
+ return;
71
+ }
72
+ if (parsed.command === "run") {
73
+ const prompt = await promptFromFlags(parsed);
74
+ const handle = await runtime.run({
75
+ agentId: stringFlag(parsed, "agent") ?? "codex",
76
+ cwd: path.resolve(stringFlag(parsed, "cwd") ?? "."),
77
+ prompt,
78
+ model: stringFlag(parsed, "model"),
79
+ permissionPolicy: permissionFlag(parsed),
80
+ timeoutMs: numberFlag(parsed, "timeout-ms"),
81
+ });
82
+ await streamRun(parsed, handle.events, { kind: "run", id: handle.runId }, "run_summary", () => runtime.getRun(handle.runId));
83
+ return;
84
+ }
85
+ if (parsed.command === "goal") {
86
+ const prompt = await promptFromFlags(parsed);
87
+ const handle = await runtime.createGoal({
88
+ defaultAgentId: stringFlag(parsed, "agent") ?? "codex",
89
+ cwd: path.resolve(stringFlag(parsed, "cwd") ?? "."),
90
+ objective: prompt,
91
+ permissionPolicy: permissionFlag(parsed),
92
+ timeoutMs: numberFlag(parsed, "timeout-ms"),
93
+ maxConcurrentTasks: numberFlag(parsed, "max-concurrent-tasks"),
94
+ retryPolicy: retryPolicyFromFlags(parsed),
95
+ });
96
+ await streamRun(parsed, handle.events, { kind: "goal", id: handle.goalId }, "goal_summary", () => runtime.getGoal(handle.goalId));
97
+ return;
98
+ }
99
+ throw new Error(`Unknown command: ${parsed.command}`);
100
+ }
101
+ finally {
102
+ await runtime.shutdown("CLI command complete");
103
+ }
104
+ }
105
+ function isReadOnlyStoreCommand(command) {
106
+ return command === "runs"
107
+ || command === "run-status"
108
+ || command === "replay-run"
109
+ || command === "run-events"
110
+ || command === "goals"
111
+ || command === "goal-status"
112
+ || command === "replay-goal"
113
+ || command === "goal-events";
114
+ }
115
+ function runReadOnlyStoreCommand(parsed) {
116
+ const storageDirFlag = stringFlag(parsed, "storage-dir");
117
+ const storageDir = storageDirFlag ? path.resolve(storageDirFlag) : undefined;
118
+ if (parsed.command === "runs") {
119
+ output(parsed, storageDir ? listStoredRuns(storageDir, { status: runStatusFlag(parsed) }) : []);
120
+ return;
121
+ }
122
+ if (parsed.command === "run-status") {
123
+ const runId = parsed.positional[0];
124
+ if (!runId)
125
+ throw new Error("run-status requires a runId");
126
+ output(parsed, storageDir ? getStoredRun(storageDir, runId) : null);
127
+ return;
128
+ }
129
+ if (parsed.command === "replay-run" || parsed.command === "run-events") {
130
+ const runId = parsed.positional[0];
131
+ if (!runId)
132
+ throw new Error(`${parsed.command} requires a runId`);
133
+ outputReplay(parsed, storageDir ? replayStoredRunEvents(storageDir, runId, numberFlag(parsed, "after")) : []);
134
+ return;
135
+ }
136
+ if (parsed.command === "goals") {
137
+ output(parsed, storageDir ? listStoredGoals(storageDir, { status: goalStatusFlag(parsed) }) : []);
138
+ return;
139
+ }
140
+ if (parsed.command === "goal-status") {
141
+ const goalId = parsed.positional[0];
142
+ if (!goalId)
143
+ throw new Error("goal-status requires a goalId");
144
+ output(parsed, storageDir ? getStoredGoal(storageDir, goalId) : null);
145
+ return;
146
+ }
147
+ if (parsed.command === "replay-goal" || parsed.command === "goal-events") {
148
+ const goalId = parsed.positional[0];
149
+ if (!goalId)
150
+ throw new Error(`${parsed.command} requires a goalId`);
151
+ outputReplay(parsed, storageDir ? replayStoredGoalEvents(storageDir, goalId, numberFlag(parsed, "after")) : []);
152
+ }
153
+ }
154
+ async function runDiagnosticsCommand(parsed) {
155
+ const kind = parsed.positional[0];
156
+ const id = parsed.positional[1];
157
+ if (kind !== "run" && kind !== "goal")
158
+ throw new Error("diagnostics requires run or goal");
159
+ if (!id)
160
+ throw new Error(`diagnostics ${kind} requires an id`);
161
+ const storageDir = path.resolve(requiredStringFlag(parsed, "storage-dir", "diagnostics requires --storage-dir <dir>"));
162
+ const bundle = kind === "run"
163
+ ? exportDiagnosticsBundle({ kind, runId: id }, storageDir)
164
+ : exportDiagnosticsBundle({ kind, goalId: id }, storageDir);
165
+ const out = stringFlag(parsed, "out");
166
+ if (out) {
167
+ const outFile = path.resolve(out);
168
+ await mkdir(path.dirname(outFile), { recursive: true });
169
+ atomicWriteJsonFile(outFile, bundle);
170
+ }
171
+ output(parsed, bundle);
172
+ }
173
+ async function runSmoke(parsed) {
174
+ const mode = stringFlag(parsed, "mode") ?? "detection";
175
+ const agent = stringFlag(parsed, "agent") ?? "all";
176
+ if (mode === "detection") {
177
+ const runtime = createAgentRuntime(runtimeOptionsFromFlags(parsed));
178
+ try {
179
+ const agents = await runtime.detect({ includeUnavailable: true });
180
+ output(parsed, {
181
+ ok: agents.some((item) => item.available),
182
+ mode,
183
+ agents: agent === "all" ? agents : agents.filter((item) => item.id === agent),
184
+ });
185
+ }
186
+ finally {
187
+ await runtime.shutdown("CLI command complete");
188
+ }
189
+ return;
190
+ }
191
+ if (mode === "fixtures") {
192
+ const fixtures = runParserFixtureCases(agent);
193
+ output(parsed, {
194
+ ok: fixtures.length > 0 && fixtures.every((fixture) => fixture.ok),
195
+ mode,
196
+ fixtures,
197
+ });
198
+ return;
199
+ }
200
+ if (mode === "real") {
201
+ if (agent === "all")
202
+ throw new Error("smoke --mode real requires --agent <id>");
203
+ const allowRealRun = parsed.flags.has("allow-real-run");
204
+ const runtime = createAgentRuntime(runtimeOptionsFromFlags(parsed));
205
+ try {
206
+ const detected = (await runtime.detect({ includeUnavailable: true })).find((item) => item.id === agent);
207
+ const adapter = runtime.getAdapter(agent);
208
+ const preflight = realSmokePreflight(agent, detected, adapter, allowRealRun);
209
+ if (preflight) {
210
+ output(parsed, preflight);
211
+ return;
212
+ }
213
+ const cwdFlag = stringFlag(parsed, "cwd");
214
+ const cwd = path.resolve(cwdFlag ?? await mkdtemp(path.join(os.tmpdir(), "agent-runtime-real-smoke-")));
215
+ const expectation = await realSmokeExpectation(parsed, agent);
216
+ const beforeCwd = await snapshotCwd(cwd);
217
+ const handle = await runtime.run({
218
+ agentId: agent,
219
+ cwd,
220
+ prompt: expectation.prompt,
221
+ permissionPolicy: "read-only",
222
+ timeoutMs: numberFlag(parsed, "timeout-ms") ?? 30_000,
223
+ });
224
+ await streamRealSmoke(parsed, agent, detected, cwd, expectation, beforeCwd, handle.events, { kind: "run", id: handle.runId }, () => runtime.getRun(handle.runId));
225
+ }
226
+ finally {
227
+ await runtime.shutdown("CLI command complete");
228
+ }
229
+ return;
230
+ }
231
+ throw new Error("--mode must be one of: detection, fixtures, real");
232
+ }
233
+ async function runConformance(parsed) {
234
+ const mode = (enumFlag(parsed, "mode", ["fixtures", "fake", "real"]) ?? "fixtures");
235
+ const requestedAgent = stringFlag(parsed, "agent") ?? "all";
236
+ const agents = selectedAgents(requestedAgent);
237
+ const allowRealRun = parsed.flags.has("allow-real-run");
238
+ if (mode === "fixtures") {
239
+ const summaries = agents.map((agentId) => fixtureConformanceSummary(agentId));
240
+ output(parsed, {
241
+ schemaVersion: "agent-runtime.conformance.v1",
242
+ ok: summaries.every((summary) => summary.runClassification === "success"),
243
+ mode,
244
+ agents: summaries,
245
+ });
246
+ return;
247
+ }
248
+ const fake = mode === "fake" ? await setupFakeConformanceEnv() : undefined;
249
+ const runMode = mode;
250
+ const runtime = createAgentRuntime(runtimeOptionsFromFlags(parsed, fake ? { env: fake.env, searchPath: [fake.binDir] } : undefined));
251
+ try {
252
+ const detected = await runtime.detect({ includeUnavailable: true, timeoutMs: mode === "fake" ? 10_000 : undefined });
253
+ const summaries = [];
254
+ for (const agentId of agents) {
255
+ const detection = detected.find((item) => item.id === agentId);
256
+ summaries.push(await runAgentConformance(parsed, runtime, agentId, detection, runMode, allowRealRun));
257
+ }
258
+ output(parsed, {
259
+ schemaVersion: "agent-runtime.conformance.v1",
260
+ ok: conformanceOk(mode, requestedAgent, summaries, allowRealRun),
261
+ mode,
262
+ agents: summaries,
263
+ });
264
+ }
265
+ finally {
266
+ await runtime.shutdown("CLI command complete");
267
+ }
268
+ }
269
+ function conformanceOk(mode, requestedAgent, summaries, allowRealRun) {
270
+ if (mode !== "real")
271
+ return summaries.every((summary) => summary.runClassification === "success");
272
+ if (!allowRealRun)
273
+ return summaries.length > 0 && summaries.every((summary) => summary.failureReason === null);
274
+ if (requestedAgent !== "all")
275
+ return summaries.every((summary) => summary.runClassification === "success");
276
+ const successCount = summaries.filter((summary) => summary.runClassification === "success").length;
277
+ const failedRuns = summaries.filter((summary) => summary.skippedReason === null && summary.runClassification !== "success");
278
+ return successCount > 0 && failedRuns.length === 0;
279
+ }
280
+ function selectedAgents(agent) {
281
+ const all = ["codex", "claude", "opencode"];
282
+ if (agent === "all")
283
+ return all;
284
+ if (agent === "codex" || agent === "claude" || agent === "opencode")
285
+ return [agent];
286
+ throw new Error("--agent must be one of: codex, claude, opencode, all");
287
+ }
288
+ function fixtureConformanceSummary(agent) {
289
+ const fixtures = runParserFixtureCases(agent);
290
+ const ok = fixtures.length > 0 && fixtures.every((fixture) => fixture.ok);
291
+ const adapter = defaultAdapters().find((item) => item.id === agent) ?? null;
292
+ return {
293
+ adapter: agent,
294
+ version: null,
295
+ resolvedExecutable: null,
296
+ auth: "not_checked",
297
+ modelsSource: "fixtures",
298
+ capabilities: adapterCapabilities(adapter),
299
+ argvProfile: adapterArgvProfile(adapter),
300
+ promptTransport: adapterPromptTransport(adapter),
301
+ parserMode: adapter?.compatibility?.streamFormat ?? null,
302
+ runClassification: ok ? "success" : "failed",
303
+ expectedTextMatched: null,
304
+ observedTextTail: null,
305
+ cwdMutationChecked: false,
306
+ cwdMutated: null,
307
+ diagnosticsCount: fixtures.filter((fixture) => !fixture.ok).length,
308
+ diagnostics: fixtures
309
+ .filter((fixture) => !fixture.ok)
310
+ .map((fixture) => ({
311
+ code: "AGENT_STREAM_PARSE_FAILED",
312
+ message: `${agent} parser fixture failed: ${fixture.name}`,
313
+ actionableHints: ["Inspect parser fixture expectations before updating the adapter stream profile."],
314
+ })),
315
+ skippedReason: null,
316
+ failureReason: ok ? null : "failed",
317
+ };
318
+ }
319
+ async function runAgentConformance(parsed, runtime, agent, detected, mode, allowRealRun) {
320
+ const adapter = runtime.getAdapter(agent);
321
+ const preflight = conformancePreflight(agent, detected, adapter, mode, allowRealRun);
322
+ if (preflight)
323
+ return preflight;
324
+ const cwdFlag = stringFlag(parsed, "cwd");
325
+ const cwd = path.resolve(cwdFlag ?? await mkdtemp(path.join(os.tmpdir(), `agent-runtime-${mode}-conformance-`)));
326
+ const expectation = await realSmokeExpectation(parsed, agent);
327
+ const beforeCwd = await snapshotCwd(cwd);
328
+ const handle = await runtime.run({
329
+ agentId: agent,
330
+ cwd,
331
+ prompt: expectation.prompt,
332
+ permissionPolicy: "read-only",
333
+ timeoutMs: numberFlag(parsed, "timeout-ms") ?? 30_000,
334
+ });
335
+ const evidence = {
336
+ observedText: "",
337
+ textDeltaCount: 0,
338
+ expectedText: expectation.expectedText,
339
+ expectedTextRequired: expectation.expectedTextRequired,
340
+ };
341
+ for await (const event of handle.events)
342
+ collectRealSmokeEvidence(evidence, event);
343
+ const run = await runtime.getRun(handle.runId);
344
+ const mutation = await cwdMutationEvidence(cwd, beforeCwd);
345
+ const expectedTextMatched = evidence.expectedText ? evidence.observedText.includes(evidence.expectedText) : null;
346
+ const classification = classifyRealSmokeRun(run, {
347
+ hasObservedText: evidence.observedText.trim().length > 0,
348
+ expectedTextMatched,
349
+ expectedTextRequired: evidence.expectedTextRequired,
350
+ cwdMutated: mutation.cwdMutated,
351
+ });
352
+ const failureReason = classification === "success" ? null : classification;
353
+ return {
354
+ adapter: agent,
355
+ version: detected?.version ?? null,
356
+ resolvedExecutable: detected?.path ?? null,
357
+ auth: detected?.authStatus ?? "unknown",
358
+ modelsSource: detected?.modelsSource ?? "none",
359
+ capabilities: detected?.capabilities ?? adapterCapabilities(adapter),
360
+ argvProfile: adapterArgvProfile(adapter),
361
+ promptTransport: adapterPromptTransport(adapter),
362
+ parserMode: adapter?.compatibility?.streamFormat ?? null,
363
+ runClassification: classification,
364
+ expectedTextMatched,
365
+ observedTextTail: tailText(evidence.observedText, DEFAULT_OBSERVED_TEXT_TAIL_BYTES),
366
+ cwdMutationChecked: mutation.cwdMutationChecked,
367
+ cwdMutated: mutation.cwdMutated,
368
+ diagnosticsCount: (detected?.diagnostics.length ?? 0) + (run?.diagnostics.length ?? 0),
369
+ diagnostics: compactDiagnostics([...(detected?.diagnostics ?? []), ...(run?.diagnostics ?? [])]),
370
+ skippedReason: null,
371
+ failureReason,
372
+ };
373
+ }
374
+ function conformancePreflight(agent, detected, adapter, mode, allowRealRun) {
375
+ if (!detected) {
376
+ return skippedConformance(agent, "unavailable_executable", "no_detection_result", 1, undefined, adapter);
377
+ }
378
+ if (!detected.available) {
379
+ return skippedConformance(agent, "unavailable_executable", "unavailable_executable", detected.diagnostics.length, detected, adapter);
380
+ }
381
+ if (detected.authStatus === "missing" || detected.authStatus === "expired") {
382
+ return skippedConformance(agent, "auth_missing", "auth_missing", detected.diagnostics.length, detected, adapter);
383
+ }
384
+ if (detected.diagnostics.some((item) => item.code === "unsupported_flag")) {
385
+ return skippedConformance(agent, "unsupported_flag", "unsupported_flag", detected.diagnostics.length, detected, adapter);
386
+ }
387
+ if (detected.diagnostics.some((item) => item.code === "needs_verification")) {
388
+ return skippedConformance(agent, "needs_verification", "needs_verification", detected.diagnostics.length, detected, adapter);
389
+ }
390
+ if (mode === "real" && !allowRealRun) {
391
+ return skippedConformance(agent, "real_run_skipped", "real_run_not_allowed", detected.diagnostics.length, detected, adapter);
392
+ }
393
+ return null;
394
+ }
395
+ function skippedConformance(agent, classification, reason, diagnosticsCount, detected, adapter) {
396
+ return {
397
+ adapter: agent,
398
+ version: detected?.version ?? null,
399
+ resolvedExecutable: detected?.path ?? null,
400
+ auth: detected?.authStatus ?? "unknown",
401
+ modelsSource: detected?.modelsSource ?? "none",
402
+ capabilities: detected?.capabilities ?? adapterCapabilities(adapter ?? null),
403
+ argvProfile: adapterArgvProfile(adapter ?? null),
404
+ promptTransport: adapterPromptTransport(adapter ?? null),
405
+ parserMode: adapter?.compatibility?.streamFormat ?? null,
406
+ runClassification: classification,
407
+ expectedTextMatched: null,
408
+ observedTextTail: null,
409
+ cwdMutationChecked: false,
410
+ cwdMutated: null,
411
+ diagnosticsCount,
412
+ diagnostics: compactDiagnostics(detected?.diagnostics ?? (diagnosticsCount > 0 ? [{
413
+ code: "not_installed",
414
+ message: `No adapter detection result for ${agent}`,
415
+ actionableHints: ["Verify the requested adapter id and executable configuration."],
416
+ }] : [])),
417
+ skippedReason: reason,
418
+ failureReason: null,
419
+ };
420
+ }
421
+ function adapterCapabilities(adapter) {
422
+ if (!adapter)
423
+ return null;
424
+ return {
425
+ streaming: adapter.capabilities?.streaming ?? true,
426
+ tools: adapter.capabilities?.tools ?? false,
427
+ models: adapter.capabilities?.models ?? Boolean(adapter.listModels),
428
+ authProbe: Boolean(adapter.authProbe),
429
+ prompt: [adapter.promptTransport.kind],
430
+ };
431
+ }
432
+ function adapterArgvProfile(adapter) {
433
+ if (!adapter?.compatibility)
434
+ return null;
435
+ return {
436
+ defaultArgs: adapter.compatibility.defaultArgs,
437
+ knownFlags: adapter.compatibility.knownFlags.map((flag) => ({
438
+ flag: flag.flag,
439
+ mapsTo: flag.mapsTo,
440
+ status: flag.needsVerification ? "needs_verification" : "known",
441
+ notes: flag.notes,
442
+ })),
443
+ needsVerification: adapter.compatibility.needsVerification ?? [],
444
+ };
445
+ }
446
+ function adapterPromptTransport(adapter) {
447
+ if (!adapter)
448
+ return null;
449
+ if (adapter.compatibility?.promptTransport)
450
+ return adapter.compatibility.promptTransport;
451
+ const format = adapter.promptTransport.kind === "stdin" && adapter.promptTransport.inputFormat ? `:${adapter.promptTransport.inputFormat}` : "";
452
+ return `${adapter.promptTransport.kind}${format}`;
453
+ }
454
+ function compactDiagnostics(diagnostics) {
455
+ return diagnostics.map((item) => ({
456
+ code: item.code,
457
+ message: item.message,
458
+ probe: item.probe,
459
+ actionableHints: item.actionableHints,
460
+ }));
461
+ }
462
+ async function setupFakeConformanceEnv() {
463
+ const binDir = await mkdtemp(path.join(os.tmpdir(), "agent-runtime-conformance-bin-"));
464
+ await writeFakeExecutable(binDir, "codex", `
465
+ const args = process.argv.slice(2);
466
+ if (args[0] === "--version") { console.log("codex-cli fake-conformance"); process.exit(0); }
467
+ if (args[0] === "debug" && args[1] === "models") { console.log(JSON.stringify({ models: [{ slug: "gpt-fake", display_name: "GPT Fake" }] })); process.exit(0); }
468
+ process.stdin.resume();
469
+ process.stdin.on("end", () => {
470
+ console.log(JSON.stringify({ type: "thread.started" }));
471
+ console.log(JSON.stringify({ type: "item.completed", item: { type: "agent_message", text: "agent-runtime codex smoke ok" } }));
472
+ });
473
+ `);
474
+ await writeFakeExecutable(binDir, "claude", `
475
+ const args = process.argv.slice(2);
476
+ if (args[0] === "--version") { console.log("Claude Code fake-conformance"); process.exit(0); }
477
+ if (args[0] === "-p" && args[1] === "--help") { console.log("--include-partial-messages\\n--add-dir"); process.exit(0); }
478
+ if (args[0] === "auth" && args[1] === "status") { console.log(JSON.stringify({ loggedIn: true, authMethod: "fake", apiProvider: "anthropic-compatible" })); process.exit(0); }
479
+ process.stdin.resume();
480
+ process.stdin.on("end", () => {
481
+ console.log(JSON.stringify({ type: "system" }));
482
+ console.log(JSON.stringify({ type: "assistant", message: { content: [{ type: "text", text: "agent-runtime claude smoke ok" }] } }));
483
+ });
484
+ `);
485
+ await writeFakeExecutable(binDir, "opencode", `
486
+ const args = process.argv.slice(2);
487
+ if (args[0] === "--version") { console.log("opencode fake-conformance"); process.exit(0); }
488
+ if (args[0] === "models") { console.log("openai/gpt-fake"); process.exit(0); }
489
+ process.stdin.resume();
490
+ process.stdin.on("end", () => {
491
+ console.log(JSON.stringify({ type: "step_start" }));
492
+ console.log(JSON.stringify({ type: "text", part: { text: "agent-runtime opencode smoke ok" } }));
493
+ });
494
+ `);
495
+ return {
496
+ binDir,
497
+ env: {
498
+ ...process.env,
499
+ PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ""}`,
500
+ CODEX_BIN: path.join(binDir, "codex"),
501
+ CLAUDE_BIN: path.join(binDir, "claude"),
502
+ OPENCODE_BIN: path.join(binDir, "opencode"),
503
+ },
504
+ };
505
+ }
506
+ async function writeFakeExecutable(binDir, name, body) {
507
+ const file = path.join(binDir, name);
508
+ await writeFile(file, `#!${process.execPath}\n${body}`, "utf8");
509
+ await chmod(file, 0o755);
510
+ }
511
+ async function realSmokeExpectation(parsed, agent) {
512
+ const promptFromUser = await optionalPromptFromFlags(parsed);
513
+ const expectText = stringFlag(parsed, "expect-text");
514
+ if (expectText !== undefined) {
515
+ return {
516
+ prompt: promptFromUser ?? promptForExpectedText(expectText),
517
+ expectedText: expectText,
518
+ expectedTextRequired: true,
519
+ };
520
+ }
521
+ if (promptFromUser !== undefined)
522
+ return { prompt: promptFromUser, expectedTextRequired: true };
523
+ const prompt = defaultRealSmokePrompt(agent);
524
+ return { prompt, expectedText: defaultRealSmokeExpectedText(agent), expectedTextRequired: true };
525
+ }
526
+ function defaultRealSmokePrompt(agent) {
527
+ return promptForExpectedText(defaultRealSmokeExpectedText(agent));
528
+ }
529
+ function defaultRealSmokeExpectedText(agent) {
530
+ return `agent-runtime ${agent} smoke ok`;
531
+ }
532
+ function promptForExpectedText(expectedText) {
533
+ return `Reply exactly: ${expectedText}. Do not edit files.`;
534
+ }
535
+ function realSmokePreflight(agent, detected, adapter, allowRealRun) {
536
+ if (!detected) {
537
+ return skippedRealSmoke(agent, "unavailable_executable", "no_detection_result", 1, undefined, adapter);
538
+ }
539
+ if (!detected.available) {
540
+ return skippedRealSmoke(agent, "unavailable_executable", "unavailable_executable", detected.diagnostics.length, detected, adapter);
541
+ }
542
+ if (detected.authStatus === "missing" || detected.authStatus === "expired") {
543
+ return skippedRealSmoke(agent, "auth_missing", "auth_missing", detected.diagnostics.length, detected, adapter);
544
+ }
545
+ if (detected.diagnostics.some((item) => item.code === "unsupported_flag")) {
546
+ return skippedRealSmoke(agent, "unsupported_flag", "unsupported_flag", detected.diagnostics.length, detected, adapter);
547
+ }
548
+ if (detected.diagnostics.some((item) => item.code === "needs_verification")) {
549
+ return skippedRealSmoke(agent, "needs_verification", "needs_verification", detected.diagnostics.length, detected, adapter);
550
+ }
551
+ if (!allowRealRun) {
552
+ return skippedRealSmoke(agent, "real_run_skipped", "real_run_not_allowed", detected.diagnostics.length, detected, adapter);
553
+ }
554
+ return null;
555
+ }
556
+ function skippedRealSmoke(agent, classification, reason, diagnosticsCount, detected, adapter) {
557
+ const diagnostics = detected?.diagnostics ?? (diagnosticsCount > 0 ? [{
558
+ code: "not_installed",
559
+ message: `No adapter detection result for ${agent}`,
560
+ actionableHints: ["Verify the requested adapter id and executable configuration."],
561
+ }] : []);
562
+ return {
563
+ schemaVersion: "agent-runtime.realSmoke.v1",
564
+ type: "real_smoke_summary",
565
+ ok: false,
566
+ mode: "real",
567
+ adapter: agent,
568
+ version: detected?.version ?? null,
569
+ auth: detected?.authStatus ?? "unknown",
570
+ modelsSource: detected?.modelsSource ?? "none",
571
+ runClassification: classification,
572
+ expectedTextRequired: true,
573
+ expectedTextMatched: null,
574
+ observedTextDeltaCount: 0,
575
+ observedTextTail: null,
576
+ cwdMutationChecked: false,
577
+ cwdMutated: null,
578
+ diagnosticsCount,
579
+ diagnostics: compactDiagnostics(diagnostics),
580
+ skippedReason: reason,
581
+ failureReason: null,
582
+ };
583
+ }
584
+ function parseArgs(argv) {
585
+ const [command = "help", ...rest] = argv;
586
+ const flags = new Map();
587
+ const positional = [];
588
+ for (let index = 0; index < rest.length; index += 1) {
589
+ const arg = rest[index];
590
+ if (!arg.startsWith("--")) {
591
+ positional.push(arg);
592
+ continue;
593
+ }
594
+ const key = arg.slice(2);
595
+ const next = rest[index + 1];
596
+ if (!next || next.startsWith("--")) {
597
+ flags.set(key, true);
598
+ }
599
+ else {
600
+ flags.set(key, next);
601
+ index += 1;
602
+ }
603
+ }
604
+ return { command, positional, flags };
605
+ }
606
+ async function promptFromFlags(parsed) {
607
+ const prompt = await optionalPromptFromFlags(parsed);
608
+ if (prompt !== undefined)
609
+ return prompt;
610
+ throw new Error("--prompt or --prompt-file is required");
611
+ }
612
+ async function optionalPromptFromFlags(parsed) {
613
+ const prompt = stringFlag(parsed, "prompt");
614
+ if (prompt)
615
+ return prompt;
616
+ const promptFile = stringFlag(parsed, "prompt-file");
617
+ if (promptFile)
618
+ return readFile(path.resolve(promptFile), "utf8");
619
+ return undefined;
620
+ }
621
+ async function streamRun(parsed, events, scope, summaryType, loadSummary) {
622
+ if (parsed.flags.get("stream") === "jsonl") {
623
+ let sequence = 1;
624
+ let diagnosticCode;
625
+ for await (const event of events) {
626
+ diagnosticCode = diagnosticCodeFromEvent(event) ?? diagnosticCode;
627
+ process.stdout.write(`${JSON.stringify(redactForCli(envelopeStreamEvent(event, scope, sequence, diagnosticCode)))}\n`);
628
+ sequence += 1;
629
+ }
630
+ if (parsed.flags.has("diagnostics")) {
631
+ process.stdout.write(`${JSON.stringify(redactForCli({ type: summaryType, summary: await loadSummary() }))}\n`);
632
+ }
633
+ return;
634
+ }
635
+ let last = null;
636
+ for await (const event of events) {
637
+ last = event;
638
+ if (parsed.flags.has("json"))
639
+ continue;
640
+ if (typeof event === "object" && event && "type" in event) {
641
+ const typed = event;
642
+ if (typed.type === "text_delta" && typed.text)
643
+ process.stdout.write(typed.text);
644
+ else if (typed.type.endsWith("finished"))
645
+ process.stdout.write(`\n${typed.type}: ${typed.result ?? "done"}\n`);
646
+ else if (typed.type === "error" && typed.message)
647
+ process.stderr.write(`${redactText(typed.message)}\n`);
648
+ }
649
+ }
650
+ if (parsed.flags.has("json"))
651
+ output(parsed, (await loadSummary()) ?? last ?? {});
652
+ }
653
+ async function streamRealSmoke(parsed, agent, detected, cwd, expectation, beforeCwd, events, scope, loadRun) {
654
+ const evidence = {
655
+ observedText: "",
656
+ textDeltaCount: 0,
657
+ expectedText: expectation.expectedText,
658
+ expectedTextRequired: expectation.expectedTextRequired,
659
+ };
660
+ if (parsed.flags.get("stream") === "jsonl") {
661
+ let sequence = 1;
662
+ let diagnosticCode;
663
+ for await (const event of events) {
664
+ collectRealSmokeEvidence(evidence, event);
665
+ diagnosticCode = diagnosticCodeFromEvent(event) ?? diagnosticCode;
666
+ process.stdout.write(`${JSON.stringify(redactForCli(envelopeStreamEvent(event, scope, sequence, diagnosticCode)))}\n`);
667
+ sequence += 1;
668
+ }
669
+ if (parsed.flags.has("diagnostics")) {
670
+ const run = await loadRun();
671
+ const mutation = await cwdMutationEvidence(cwd, beforeCwd);
672
+ process.stdout.write(`${JSON.stringify(redactForCli(realSmokeSummary(agent, detected, run, evidence, mutation)))}\n`);
673
+ }
674
+ return;
675
+ }
676
+ let last = null;
677
+ for await (const event of events) {
678
+ last = event;
679
+ collectRealSmokeEvidence(evidence, event);
680
+ if (parsed.flags.has("json"))
681
+ continue;
682
+ if (typeof event === "object" && event && "type" in event) {
683
+ const typed = event;
684
+ if (typed.type === "text_delta" && typed.text)
685
+ process.stdout.write(typed.text);
686
+ else if (typed.type.endsWith("finished"))
687
+ process.stdout.write(`\n${typed.type}: ${typed.result ?? "done"}\n`);
688
+ else if (typed.type === "error" && typed.message)
689
+ process.stderr.write(`${redactText(typed.message)}\n`);
690
+ }
691
+ }
692
+ const run = await loadRun();
693
+ const mutation = await cwdMutationEvidence(cwd, beforeCwd);
694
+ output(parsed, realSmokeSummary(agent, detected, run ?? last, evidence, mutation));
695
+ }
696
+ function collectRealSmokeEvidence(evidence, event) {
697
+ if (!event || typeof event !== "object" || !("type" in event))
698
+ return;
699
+ const typed = event;
700
+ if (typed.type !== "text_delta" || typeof typed.text !== "string")
701
+ return;
702
+ evidence.observedText += typed.text;
703
+ evidence.textDeltaCount += 1;
704
+ }
705
+ function realSmokeSummary(agent, detected, run, evidence, mutation) {
706
+ const expectedTextMatched = evidence.expectedText ? evidence.observedText.includes(evidence.expectedText) : null;
707
+ const hasObservedText = evidence.observedText.trim().length > 0;
708
+ const classification = classifyRealSmokeRun(run, {
709
+ hasObservedText,
710
+ expectedTextMatched,
711
+ expectedTextRequired: evidence.expectedTextRequired,
712
+ cwdMutated: mutation.cwdMutated,
713
+ });
714
+ const diagnostics = run?.diagnostics ?? [];
715
+ const failureReason = classification === "success" ? null : classification;
716
+ return {
717
+ schemaVersion: "agent-runtime.realSmoke.v1",
718
+ type: "real_smoke_summary",
719
+ ok: classification === "success",
720
+ mode: "real",
721
+ adapter: agent,
722
+ version: detected?.version ?? null,
723
+ auth: detected?.authStatus ?? "unknown",
724
+ modelsSource: detected?.modelsSource ?? "none",
725
+ runClassification: classification,
726
+ expectedTextRequired: evidence.expectedTextRequired,
727
+ expectedTextMatched,
728
+ observedTextDeltaCount: evidence.textDeltaCount,
729
+ observedTextTail: tailText(evidence.observedText, DEFAULT_OBSERVED_TEXT_TAIL_BYTES),
730
+ cwdMutationChecked: mutation.cwdMutationChecked,
731
+ cwdMutated: mutation.cwdMutated,
732
+ cwdMutationCount: mutation.cwdMutationCount,
733
+ cwdMutationSample: mutation.cwdMutationSample,
734
+ cwdMutationLimitReached: mutation.cwdMutationLimitReached,
735
+ cwdMutationError: mutation.cwdMutationError,
736
+ diagnosticsCount: diagnostics.length,
737
+ diagnostics: compactDiagnostics(diagnostics),
738
+ skippedReason: null,
739
+ failureReason,
740
+ };
741
+ }
742
+ function classifyRealSmokeRun(run, evidence) {
743
+ if (!run)
744
+ return "failed";
745
+ if (run.status === "succeeded") {
746
+ if (evidence?.cwdMutated)
747
+ return "cwd_mutated";
748
+ if (!evidence?.hasObservedText)
749
+ return "unexpected_output";
750
+ if (evidence.expectedTextRequired && !evidence.expectedTextMatched)
751
+ return "unexpected_output";
752
+ return "success";
753
+ }
754
+ if (run.errorCode === "AGENT_TIMEOUT")
755
+ return "timeout";
756
+ if (run.errorCode === "AGENT_UNAVAILABLE" || run.errorCode === "AGENT_NOT_EXECUTABLE")
757
+ return "unavailable_executable";
758
+ if (run.diagnostics.some((diagnostic) => diagnostic.code === "unsupported_flag"))
759
+ return "unsupported_flag";
760
+ if (run.diagnostics.some((diagnostic) => diagnostic.code === "needs_verification"))
761
+ return "needs_verification";
762
+ if (run.diagnostics.some((diagnostic) => diagnostic.code === "auth_missing" || diagnostic.code === "AGENT_AUTH_REQUIRED"))
763
+ return "auth_missing";
764
+ return "failed";
765
+ }
766
+ async function snapshotCwd(cwd) {
767
+ const entries = new Map();
768
+ try {
769
+ let entryCount = 0;
770
+ let limitReached = false;
771
+ async function visit(dir) {
772
+ if (limitReached)
773
+ return;
774
+ const dirents = await readdir(dir, { withFileTypes: true });
775
+ for (const dirent of dirents) {
776
+ if (limitReached)
777
+ return;
778
+ if (dirent.isDirectory() && SKIPPED_CWD_SCAN_DIRS.has(dirent.name))
779
+ continue;
780
+ const absolute = path.join(dir, dirent.name);
781
+ const relative = path.relative(cwd, absolute);
782
+ entryCount += 1;
783
+ if (entryCount > MAX_CWD_SCAN_ENTRIES) {
784
+ limitReached = true;
785
+ return;
786
+ }
787
+ const stats = await lstat(absolute);
788
+ entries.set(relative, `${entryKind(stats)}:${stats.size}:${Math.trunc(stats.mtimeMs)}`);
789
+ if (dirent.isDirectory())
790
+ await visit(absolute);
791
+ }
792
+ }
793
+ await visit(cwd);
794
+ return { checked: true, entries, entryCount: Math.min(entryCount, MAX_CWD_SCAN_ENTRIES), limitReached };
795
+ }
796
+ catch (error) {
797
+ return {
798
+ checked: true,
799
+ entries,
800
+ entryCount: entries.size,
801
+ limitReached: false,
802
+ error: error instanceof Error ? error.message : String(error),
803
+ };
804
+ }
805
+ }
806
+ async function cwdMutationEvidence(cwd, before) {
807
+ const after = await snapshotCwd(cwd);
808
+ if (!before.checked || !after.checked) {
809
+ return {
810
+ cwdMutationChecked: false,
811
+ cwdMutated: false,
812
+ cwdMutationCount: 0,
813
+ cwdMutationSample: [],
814
+ };
815
+ }
816
+ const mutations = [];
817
+ for (const [entryPath, signature] of after.entries) {
818
+ const beforeSignature = before.entries.get(entryPath);
819
+ if (beforeSignature === undefined)
820
+ mutations.push({ path: sanitizeRelativePath(entryPath), action: "created" });
821
+ else if (beforeSignature !== signature)
822
+ mutations.push({ path: sanitizeRelativePath(entryPath), action: "updated" });
823
+ }
824
+ for (const entryPath of before.entries.keys()) {
825
+ if (!after.entries.has(entryPath))
826
+ mutations.push({ path: sanitizeRelativePath(entryPath), action: "deleted" });
827
+ }
828
+ mutations.sort((left, right) => left.path.localeCompare(right.path) || left.action.localeCompare(right.action));
829
+ const error = before.error ?? after.error;
830
+ return {
831
+ cwdMutationChecked: true,
832
+ cwdMutated: mutations.length > 0,
833
+ cwdMutationCount: mutations.length,
834
+ cwdMutationSample: mutations.slice(0, MAX_CWD_MUTATION_SAMPLE),
835
+ cwdMutationLimitReached: before.limitReached || after.limitReached || undefined,
836
+ cwdMutationError: error ? redactText(error) : undefined,
837
+ };
838
+ }
839
+ function entryKind(stats) {
840
+ if (stats.isDirectory())
841
+ return "dir";
842
+ if (stats.isFile())
843
+ return "file";
844
+ if (stats.isSymbolicLink())
845
+ return "symlink";
846
+ return "other";
847
+ }
848
+ function sanitizeRelativePath(value) {
849
+ return redactText(value.split(path.sep).join("/"));
850
+ }
851
+ function tailText(value, max) {
852
+ return value.length > max ? value.slice(value.length - max) : value;
853
+ }
854
+ function output(parsed, value) {
855
+ const safeValue = redactForCli(value);
856
+ if (parsed.flags.has("json")) {
857
+ process.stdout.write(`${JSON.stringify(safeValue, null, 2)}\n`);
858
+ return;
859
+ }
860
+ process.stdout.write(`${JSON.stringify(safeValue, null, 2)}\n`);
861
+ }
862
+ function outputReplay(parsed, events) {
863
+ if (parsed.flags.has("jsonl")) {
864
+ for (const event of envelopeReplayEvents(events))
865
+ process.stdout.write(`${JSON.stringify(redactForCli(event))}\n`);
866
+ return;
867
+ }
868
+ output(parsed, events);
869
+ }
870
+ function redactForCli(value) {
871
+ if (typeof value === "string")
872
+ return redactText(value);
873
+ if (Array.isArray(value))
874
+ return value.map((item) => redactForCli(item));
875
+ if (!value || typeof value !== "object")
876
+ return value;
877
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactForCli(item)]));
878
+ }
879
+ function stringFlag(parsed, key) {
880
+ const value = parsed.flags.get(key);
881
+ return typeof value === "string" ? value : undefined;
882
+ }
883
+ function requiredStringFlag(parsed, key, message) {
884
+ const value = stringFlag(parsed, key);
885
+ if (!value)
886
+ throw new Error(message);
887
+ return value;
888
+ }
889
+ function numberFlag(parsed, key) {
890
+ const value = stringFlag(parsed, key);
891
+ if (!value)
892
+ return undefined;
893
+ const parsedValue = Number(value);
894
+ if (!Number.isFinite(parsedValue))
895
+ throw new Error(`--${key} must be a number`);
896
+ return parsedValue;
897
+ }
898
+ function permissionFlag(parsed) {
899
+ return stringFlag(parsed, "permission");
900
+ }
901
+ function retryPolicyFromFlags(parsed) {
902
+ const maxAttempts = numberFlag(parsed, "max-attempts");
903
+ const retryableErrorCodes = csvFlag(parsed, "retryable-error-codes");
904
+ const backoffMs = numberFlag(parsed, "retry-backoff-ms");
905
+ if (maxAttempts === undefined && retryableErrorCodes === undefined && backoffMs === undefined)
906
+ return undefined;
907
+ return { maxAttempts, retryableErrorCodes, backoffMs };
908
+ }
909
+ function runtimeOptionsFromFlags(parsed, override) {
910
+ const durability = enumFlag(parsed, "storage-durability", ["relaxed", "fsync"]);
911
+ return {
912
+ env: override?.env,
913
+ searchPath: override?.searchPath,
914
+ storageDir: stringFlag(parsed, "storage-dir"),
915
+ storage: durability ? { durability } : undefined,
916
+ };
917
+ }
918
+ function csvFlag(parsed, key) {
919
+ const value = stringFlag(parsed, key);
920
+ if (!value)
921
+ return undefined;
922
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
923
+ }
924
+ function runStatusFlag(parsed) {
925
+ return enumFlag(parsed, "status", ["active", "queued", "running", "succeeded", "failed", "canceled"]);
926
+ }
927
+ function goalStatusFlag(parsed) {
928
+ return enumFlag(parsed, "status", ["active", "planning", "running", "succeeded", "failed", "canceled"]);
929
+ }
930
+ function enumFlag(parsed, key, allowed) {
931
+ const value = stringFlag(parsed, key);
932
+ if (!value)
933
+ return undefined;
934
+ if (!allowed.includes(value))
935
+ throw new Error(`--${key} must be one of: ${allowed.join(", ")}`);
936
+ return value;
937
+ }
938
+ function printHelp() {
939
+ process.stdout.write(`agent-runtime agents [--json] [--storage-dir <dir>]
940
+ agent-runtime doctor [--json] [--storage-dir <dir>]
941
+ agent-runtime smoke [--mode detection|fixtures|real] [--agent all|codex|claude|opencode] [--allow-real-run] [--cwd <dir>] [--prompt <text>] [--prompt-file <file>] [--expect-text <text>] [--timeout-ms <ms>] [--json] [--stream jsonl] [--diagnostics] [--storage-dir <dir>]
942
+ agent-runtime conformance [--mode fixtures|fake|real] [--agent all|codex|claude|opencode] [--allow-real-run] [--cwd <dir>] [--prompt <text>] [--prompt-file <file>] [--expect-text <text>] [--timeout-ms <ms>] [--json] [--storage-dir <dir>]
943
+ agent-runtime run --agent <id> --cwd <dir> (--prompt "..." | --prompt-file <file>) [--model <id>] [--permission <policy>] [--timeout-ms <ms>] [--json] [--stream jsonl] [--diagnostics] [--storage-dir <dir>]
944
+ agent-runtime goal --agent <id> --cwd <dir> (--prompt "..." | --prompt-file <file>) [--permission <policy>] [--timeout-ms <ms>] [--max-concurrent-tasks <n>] [--max-attempts <n>] [--retryable-error-codes <codes>] [--retry-backoff-ms <ms>] [--json] [--stream jsonl] [--diagnostics] [--storage-dir <dir>]
945
+ agent-runtime runs [--storage-dir <dir>] [--status active|queued|running|succeeded|failed|canceled] [--json]
946
+ agent-runtime run-status <runId> [--storage-dir <dir>] [--json]
947
+ agent-runtime replay-run <runId> [--storage-dir <dir>] [--after <eventId>] [--jsonl]
948
+ agent-runtime goals [--storage-dir <dir>] [--status active|planning|running|succeeded|failed|canceled] [--json]
949
+ agent-runtime goal-status <goalId> [--storage-dir <dir>] [--json]
950
+ agent-runtime replay-goal <goalId> [--storage-dir <dir>] [--after <eventId>] [--jsonl]
951
+ agent-runtime store-health --storage-dir <dir> [--json]
952
+ agent-runtime store-lock --storage-dir <dir> [--json]
953
+ agent-runtime store-repair --storage-dir <dir> [--dry-run | --apply] [--json]
954
+ agent-runtime diagnostics run <runId> --storage-dir <dir> [--json] [--out <file>]
955
+ agent-runtime diagnostics goal <goalId> --storage-dir <dir> [--json] [--out <file>]
956
+
957
+ Storage durability:
958
+ --storage-durability relaxed|fsync
959
+ `);
960
+ }
961
+ main().catch((error) => {
962
+ const message = redactText(error instanceof Error ? error.message : String(error));
963
+ if (process.argv.slice(2).includes("--json")) {
964
+ process.stdout.write(`${JSON.stringify({
965
+ schemaVersion: CLI_ERROR_SCHEMA_VERSION,
966
+ ok: false,
967
+ error: {
968
+ code: "CLI_USAGE_ERROR",
969
+ message,
970
+ },
971
+ })}\n`);
972
+ }
973
+ else {
974
+ process.stderr.write(`${message}\n`);
975
+ }
976
+ process.exitCode = 1;
977
+ });
978
+ //# sourceMappingURL=main.js.map