cool-workflow 0.1.97 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +11 -2
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/end-to-end-golden-path/app.json +1 -1
  7. package/apps/pr-review-fix-ci/app.json +1 -1
  8. package/apps/release-cut/app.json +1 -1
  9. package/apps/research-synthesis/app.json +1 -1
  10. package/dist/cli/dispatch.js +236 -0
  11. package/dist/cli/entry.js +120 -0
  12. package/dist/cli/io.js +21 -4
  13. package/dist/cli/parseargv.js +157 -0
  14. package/dist/cli.js +6 -33
  15. package/dist/core/capability-table.js +3534 -0
  16. package/dist/core/format/help.js +314 -0
  17. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  18. package/dist/core/hash.js +137 -0
  19. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  20. package/dist/core/multi-agent/collaboration.js +481 -0
  21. package/dist/core/multi-agent/coordinator.js +515 -0
  22. package/dist/core/multi-agent/eval-replay.js +306 -0
  23. package/dist/core/multi-agent/runtime.js +929 -0
  24. package/dist/core/multi-agent/topology.js +298 -0
  25. package/dist/core/multi-agent/trust-policy.js +197 -0
  26. package/dist/core/pipeline/commit-gate.js +320 -0
  27. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  28. package/dist/core/pipeline/dispatch.js +103 -0
  29. package/dist/core/pipeline/drive-decide.js +227 -0
  30. package/dist/core/pipeline/error-feedback.js +161 -0
  31. package/dist/core/pipeline/loop-expansion.js +124 -0
  32. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  33. package/dist/core/pipeline/runner.js +230 -0
  34. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  35. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  36. package/dist/core/state/node-projection.js +95 -0
  37. package/dist/core/state/node-snapshot.js +230 -0
  38. package/dist/core/state/run-paths.js +93 -0
  39. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  40. package/dist/core/state/schema.js +50 -0
  41. package/dist/core/state/state-explosion/digest.js +243 -0
  42. package/dist/core/state/state-explosion/graph.js +527 -0
  43. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  44. package/dist/core/state/state-explosion/report.js +187 -0
  45. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  46. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  47. package/dist/core/state/types.js +19 -0
  48. package/dist/{validation.js → core/state/validation.js} +64 -131
  49. package/dist/core/trust/evidence-grounding.js +134 -0
  50. package/dist/core/trust/ledger.js +199 -0
  51. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  52. package/dist/core/trust/telemetry-ledger.js +127 -0
  53. package/dist/core/types.js +20 -0
  54. package/dist/core/version.js +16 -0
  55. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  56. package/dist/mcp/dispatch.js +104 -0
  57. package/dist/mcp/server.js +144 -0
  58. package/dist/mcp-server.js +6 -84
  59. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  60. package/dist/shell/app-run-cli.js +88 -0
  61. package/dist/shell/audit-cli.js +259 -0
  62. package/dist/shell/audit-provenance.js +83 -0
  63. package/dist/shell/candidate-scoring-io.js +459 -0
  64. package/dist/shell/collaboration-io.js +264 -0
  65. package/dist/shell/commit-summary.js +104 -0
  66. package/dist/shell/commit.js +290 -0
  67. package/dist/shell/coordinator-io.js +476 -0
  68. package/dist/shell/demo-cli.js +19 -0
  69. package/dist/shell/dispatch.js +162 -0
  70. package/dist/{doctor.js → shell/doctor.js} +123 -56
  71. package/dist/shell/drive.js +873 -0
  72. package/dist/shell/error-feedback-io.js +305 -0
  73. package/dist/shell/eval-io.js +473 -0
  74. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  75. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  76. package/dist/shell/exec-backend-cli.js +88 -0
  77. package/dist/shell/execution-backend/agent.js +475 -0
  78. package/dist/shell/execution-backend/ci.js +15 -0
  79. package/dist/shell/execution-backend/container.js +69 -0
  80. package/dist/shell/execution-backend/envelopes.js +55 -0
  81. package/dist/shell/execution-backend/local.js +113 -0
  82. package/dist/shell/execution-backend/probes.js +175 -0
  83. package/dist/shell/execution-backend/registry.js +402 -0
  84. package/dist/shell/execution-backend/remote.js +128 -0
  85. package/dist/shell/execution-backend/types.js +11 -0
  86. package/dist/shell/feedback-cli.js +81 -0
  87. package/dist/shell/feedback-operations.js +48 -0
  88. package/dist/shell/fs-atomic.js +276 -0
  89. package/dist/shell/harness.js +98 -0
  90. package/dist/shell/ledger-cli.js +212 -0
  91. package/dist/shell/ledger-io.js +169 -0
  92. package/dist/shell/man-cli.js +89 -0
  93. package/dist/shell/metrics-cli.js +98 -0
  94. package/dist/shell/multi-agent-cli.js +1002 -0
  95. package/dist/shell/multi-agent-host.js +563 -0
  96. package/dist/shell/multi-agent-io.js +387 -0
  97. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  98. package/dist/shell/node-store.js +124 -0
  99. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  100. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  101. package/dist/{observability.js → shell/observability.js} +159 -332
  102. package/dist/{onramp.js → shell/onramp.js} +11 -0
  103. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  104. package/dist/shell/operator-ux.js +431 -0
  105. package/dist/shell/orchestrator.js +231 -0
  106. package/dist/shell/pipeline-cli.js +667 -0
  107. package/dist/shell/pipeline.js +217 -0
  108. package/dist/shell/reclamation-io.js +1366 -0
  109. package/dist/shell/registry-cli.js +329 -0
  110. package/dist/{remote-source.js → shell/remote-source.js} +12 -5
  111. package/dist/shell/report-cli.js +101 -0
  112. package/dist/shell/report-view-cli.js +117 -0
  113. package/dist/{orchestrator → shell}/report.js +289 -282
  114. package/dist/shell/reporter.js +62 -0
  115. package/dist/shell/run-export-cli.js +106 -0
  116. package/dist/shell/run-export.js +680 -0
  117. package/dist/shell/run-registry-io.js +1014 -0
  118. package/dist/shell/run-store.js +164 -0
  119. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  120. package/dist/{scheduler.js → shell/scheduler-io.js} +248 -48
  121. package/dist/shell/scheduling-io.js +311 -0
  122. package/dist/shell/state-cli.js +181 -0
  123. package/dist/shell/state-explosion-cli.js +197 -0
  124. package/dist/shell/telemetry-cli.js +85 -0
  125. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  126. package/dist/shell/telemetry-ledger-io.js +132 -0
  127. package/dist/{term.js → shell/term.js} +36 -46
  128. package/dist/shell/topology-io.js +361 -0
  129. package/dist/shell/trust-audit.js +471 -0
  130. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  131. package/dist/shell/verifier.js +48 -0
  132. package/dist/shell/workbench-host.js +250 -0
  133. package/dist/shell/workbench-text.js +18 -0
  134. package/dist/shell/workbench.js +175 -0
  135. package/dist/shell/worker-cli.js +124 -0
  136. package/dist/shell/worker-isolation.js +852 -0
  137. package/dist/shell/workflow-app-loader.js +650 -0
  138. package/docs/agent-delegation-drive.7.md +4 -0
  139. package/docs/cli-mcp-parity.7.md +284 -211
  140. package/docs/contract-migration-tooling.7.md +4 -0
  141. package/docs/control-plane-scheduling.7.md +4 -0
  142. package/docs/cross-agent-ledger.7.md +217 -0
  143. package/docs/designs/handoff-ledger.md +145 -0
  144. package/docs/durable-state-and-locking.7.md +4 -0
  145. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  146. package/docs/execution-backends.7.md +4 -0
  147. package/docs/handoff-setup.md +120 -0
  148. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  149. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  150. package/docs/multi-agent-operator-ux.7.md +4 -0
  151. package/docs/node-snapshot-diff-replay.7.md +4 -0
  152. package/docs/observability-cost-accounting.7.md +4 -0
  153. package/docs/project-index.md +143 -71
  154. package/docs/real-execution-backends.7.md +4 -0
  155. package/docs/release-and-migration.7.md +4 -0
  156. package/docs/release-tooling.7.md +4 -0
  157. package/docs/run-registry-control-plane.7.md +4 -0
  158. package/docs/run-retention-reclamation.7.md +25 -0
  159. package/docs/state-explosion-management.7.md +4 -0
  160. package/docs/team-collaboration.7.md +4 -0
  161. package/docs/web-desktop-workbench.7.md +4 -0
  162. package/manifest/plugin.manifest.json +1 -1
  163. package/manifest/source-context-profiles.json +9 -13
  164. package/package.json +1 -1
  165. package/scripts/agents/codex-agent.js +34 -4
  166. package/scripts/agents/cw-attest-wrap.js +2 -2
  167. package/scripts/bump-version.js +4 -3
  168. package/scripts/canonical-apps.js +4 -4
  169. package/scripts/children/batch-delegate-child.js +30 -10
  170. package/scripts/dogfood-architecture-review.js +2 -3
  171. package/scripts/dogfood-release.js +3 -3
  172. package/scripts/gen-parity-doc.js +15 -2
  173. package/scripts/golden-path.js +4 -4
  174. package/scripts/onramp-check.js +1 -1
  175. package/scripts/parity-check.js +38 -21
  176. package/scripts/release-flow.js +9 -3
  177. package/scripts/sync-project-index.js +51 -27
  178. package/scripts/validate-run-state-schema.js +2 -2
  179. package/scripts/version-sync-check.js +30 -30
  180. package/dist/candidate-scoring.js +0 -729
  181. package/dist/capability-core.js +0 -1186
  182. package/dist/capability-registry.js +0 -879
  183. package/dist/cli/command-surface.js +0 -490
  184. package/dist/cli/format.js +0 -56
  185. package/dist/cli/handlers/audit.js +0 -82
  186. package/dist/cli/handlers/blackboard.js +0 -81
  187. package/dist/cli/handlers/candidate.js +0 -40
  188. package/dist/cli/handlers/clones.js +0 -34
  189. package/dist/cli/handlers/collaboration.js +0 -61
  190. package/dist/cli/handlers/eval.js +0 -40
  191. package/dist/cli/handlers/maintenance.js +0 -107
  192. package/dist/cli/handlers/multi-agent.js +0 -165
  193. package/dist/cli/handlers/node.js +0 -41
  194. package/dist/cli/handlers/operational.js +0 -155
  195. package/dist/cli/handlers/operator.js +0 -146
  196. package/dist/cli/handlers/registry.js +0 -68
  197. package/dist/cli/handlers/run.js +0 -153
  198. package/dist/cli/handlers/scheduling.js +0 -132
  199. package/dist/cli/handlers/workbench.js +0 -41
  200. package/dist/cli/handlers/worker.js +0 -45
  201. package/dist/cli/run-summary.js +0 -45
  202. package/dist/clones.js +0 -162
  203. package/dist/collaboration.js +0 -726
  204. package/dist/commit.js +0 -592
  205. package/dist/compare.js +0 -18
  206. package/dist/coordinator/classify.js +0 -45
  207. package/dist/coordinator/paths.js +0 -42
  208. package/dist/coordinator/util.js +0 -126
  209. package/dist/coordinator.js +0 -990
  210. package/dist/daemon.js +0 -44
  211. package/dist/dispatch.js +0 -250
  212. package/dist/drive.js +0 -827
  213. package/dist/error-feedback.js +0 -419
  214. package/dist/evidence-grounding.js +0 -184
  215. package/dist/execution-backend/agent.js +0 -294
  216. package/dist/execution-backend/probes.js +0 -112
  217. package/dist/execution-backend/util.js +0 -47
  218. package/dist/execution-backend.js +0 -961
  219. package/dist/gates.js +0 -48
  220. package/dist/harness.js +0 -61
  221. package/dist/loop-expansion.js +0 -60
  222. package/dist/mcp/tool-call.js +0 -434
  223. package/dist/mcp/tool-definitions.js +0 -1040
  224. package/dist/mcp-surface.js +0 -30
  225. package/dist/multi-agent/graph.js +0 -84
  226. package/dist/multi-agent/helpers.js +0 -141
  227. package/dist/multi-agent/ids.js +0 -20
  228. package/dist/multi-agent/paths.js +0 -22
  229. package/dist/multi-agent-eval/normalize.js +0 -51
  230. package/dist/multi-agent-eval.js +0 -678
  231. package/dist/multi-agent-host.js +0 -777
  232. package/dist/multi-agent.js +0 -984
  233. package/dist/node-projection.js +0 -59
  234. package/dist/node-snapshot.js +0 -260
  235. package/dist/operator-ux.js +0 -631
  236. package/dist/orchestrator/app-operations.js +0 -211
  237. package/dist/orchestrator/audit-operations.js +0 -182
  238. package/dist/orchestrator/candidate-operations.js +0 -117
  239. package/dist/orchestrator/cli-options.js +0 -294
  240. package/dist/orchestrator/collaboration-operations.js +0 -86
  241. package/dist/orchestrator/feedback-operations.js +0 -81
  242. package/dist/orchestrator/host-operations.js +0 -78
  243. package/dist/orchestrator/lifecycle-operations.js +0 -626
  244. package/dist/orchestrator/migration-operations.js +0 -44
  245. package/dist/orchestrator/multi-agent-operations.js +0 -362
  246. package/dist/orchestrator/topology-operations.js +0 -84
  247. package/dist/orchestrator.js +0 -917
  248. package/dist/pipeline-runner.js +0 -285
  249. package/dist/reclamation/hash.js +0 -72
  250. package/dist/reclamation.js +0 -812
  251. package/dist/reporter.js +0 -67
  252. package/dist/run-export.js +0 -784
  253. package/dist/run-registry/derive.js +0 -175
  254. package/dist/run-registry/format.js +0 -124
  255. package/dist/run-registry/gc.js +0 -251
  256. package/dist/run-registry/policy.js +0 -16
  257. package/dist/run-registry/queue.js +0 -115
  258. package/dist/run-registry.js +0 -850
  259. package/dist/run-state-schema.js +0 -68
  260. package/dist/scheduling.js +0 -184
  261. package/dist/state-explosion.js +0 -1014
  262. package/dist/state.js +0 -367
  263. package/dist/telemetry-ledger.js +0 -196
  264. package/dist/topology.js +0 -565
  265. package/dist/triggers.js +0 -184
  266. package/dist/trust-audit.js +0 -644
  267. package/dist/types/blackboard.js +0 -2
  268. package/dist/types/candidate.js +0 -2
  269. package/dist/types/collaboration.js +0 -2
  270. package/dist/types/core.js +0 -2
  271. package/dist/types/drive.js +0 -10
  272. package/dist/types/error-feedback.js +0 -2
  273. package/dist/types/evidence-reasoning.js +0 -2
  274. package/dist/types/execution-backend.js +0 -2
  275. package/dist/types/multi-agent.js +0 -2
  276. package/dist/types/observability.js +0 -2
  277. package/dist/types/pipeline.js +0 -2
  278. package/dist/types/reclamation.js +0 -8
  279. package/dist/types/report-bundle.js +0 -6
  280. package/dist/types/result.js +0 -2
  281. package/dist/types/run-registry.js +0 -2
  282. package/dist/types/run.js +0 -2
  283. package/dist/types/sandbox.js +0 -2
  284. package/dist/types/schedule.js +0 -2
  285. package/dist/types/state-node.js +0 -2
  286. package/dist/types/topology.js +0 -2
  287. package/dist/types/trust.js +0 -2
  288. package/dist/types/workbench.js +0 -2
  289. package/dist/types/worker.js +0 -2
  290. package/dist/types/workflow-app.js +0 -2
  291. package/dist/types.js +0 -44
  292. package/dist/util/fingerprint.js +0 -19
  293. package/dist/util/fingerprint.test.js +0 -27
  294. package/dist/verifier.js +0 -78
  295. package/dist/version.js +0 -8
  296. package/dist/workbench-host.js +0 -182
  297. package/dist/workbench.js +0 -192
  298. package/dist/worker-accept/acceptance.js +0 -114
  299. package/dist/worker-accept/blackboard-fanout.js +0 -80
  300. package/dist/worker-accept/blackboard-linkage.js +0 -19
  301. package/dist/worker-accept/context.js +0 -2
  302. package/dist/worker-accept/telemetry-ledger.js +0 -126
  303. package/dist/worker-accept/validation.js +0 -77
  304. package/dist/worker-accept/verifier-completion.js +0 -73
  305. package/dist/worker-isolation/helpers.js +0 -51
  306. package/dist/worker-isolation/paths.js +0 -46
  307. package/dist/worker-isolation.js +0 -656
  308. package/dist/workflow-api.js +0 -131
  309. /package/dist/{types → core/types}/boundary.js +0 -0
@@ -1,1186 +0,0 @@
1
- "use strict";
2
- // Shared capability core: the SINGLE source of truth for composite capabilities
3
- // whose payload is assembled from more than one orchestrator call.
4
- //
5
- // BSD discipline (mechanism vs policy): these functions are MECHANISM. They live
6
- // here — never in cli.ts or mcp-server.ts — so the CLI and the MCP surface are
7
- // two renderings of ONE data source. Any capability that composes multiple
8
- // runner calls (plan summary, app run, sandbox choice, commit envelope) MUST be
9
- // expressed here and called identically by both surfaces. A composite that lives
10
- // in only one surface is exactly the cross-surface drift v0.1.27 forbids.
11
- //
12
- // Capability metadata (which entry is on which surface, tool names, jsonMode) is
13
- // declared in ONE place — the BUILTIN_CAPABILITIES table in capability-registry.ts,
14
- // the single source of truth both surfaces and the parity gate read. New
15
- // capabilities add a row there. (A v0.1.46 "self-register at load time" mechanism
16
- // was removed: the registry snapshot was taken before those registrations ran, so
17
- // they were silently dead duplicates of the table — see capability-registry.ts.)
18
- //
19
- // See docs/cli-mcp-parity.7.md and src/capability-registry.ts.
20
- var __importDefault = (this && this.__importDefault) || function (mod) {
21
- return (mod && mod.__esModule) ? mod : { "default": mod };
22
- };
23
- Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.gcClones = exports.listClones = exports.QUICKSTART_DEFAULT_APP = void 0;
25
- exports.planSummary = planSummary;
26
- exports.appRun = appRun;
27
- exports.sandboxChoose = sandboxChoose;
28
- exports.commitEnvelope = commitEnvelope;
29
- exports.compactOperatorStatus = compactOperatorStatus;
30
- exports.runRegistryFor = runRegistryFor;
31
- exports.runRegistryRefresh = runRegistryRefresh;
32
- exports.runRegistryShow = runRegistryShow;
33
- exports.runSearch = runSearch;
34
- exports.runList = runList;
35
- exports.runShow = runShow;
36
- exports.runResume = runResume;
37
- exports.runArchive = runArchive;
38
- exports.runRerun = runRerun;
39
- exports.runExportArchive = runExportArchive;
40
- exports.runImportArchive = runImportArchive;
41
- exports.runInspectArchive = runInspectArchive;
42
- exports.runVerifyImport = runVerifyImport;
43
- exports.runRestoreArchive = runRestoreArchive;
44
- exports.reportBundle = reportBundle;
45
- exports.runVerifyReportBundle = runVerifyReportBundle;
46
- exports.queueAdd = queueAdd;
47
- exports.queueList = queueList;
48
- exports.queueDrain = queueDrain;
49
- exports.queueShow = queueShow;
50
- exports.schedPlan = schedPlan;
51
- exports.schedLease = schedLease;
52
- exports.schedRelease = schedRelease;
53
- exports.schedComplete = schedComplete;
54
- exports.schedReclaim = schedReclaim;
55
- exports.schedReset = schedReset;
56
- exports.schedPolicyShow = schedPolicyShow;
57
- exports.schedPolicySet = schedPolicySet;
58
- exports.runDrivePreview = runDrivePreview;
59
- exports.runDrive = runDrive;
60
- exports.collectRunFindings = collectRunFindings;
61
- exports.quickstart = quickstart;
62
- exports.backendAgentConfigShow = backendAgentConfigShow;
63
- exports.backendAgentConfigSet = backendAgentConfigSet;
64
- exports.gcPlan = gcPlan;
65
- exports.gcRun = gcRun;
66
- exports.gcVerify = gcVerify;
67
- exports.runHistory = runHistory;
68
- exports.metricsSummary = metricsSummary;
69
- exports.sandboxProfileIdFrom = sandboxProfileIdFrom;
70
- exports.withoutRuntimeKeys = withoutRuntimeKeys;
71
- exports.optionalString = optionalString;
72
- exports.isRecord = isRecord;
73
- exports.telemetryVerify = telemetryVerify;
74
- exports.auditVerify = auditVerify;
75
- exports.demoTamper = demoTamper;
76
- exports.demoBundle = demoBundle;
77
- const drive_1 = require("./drive");
78
- const agent_config_1 = require("./agent-config");
79
- const run_registry_1 = require("./run-registry");
80
- const observability_1 = require("./observability");
81
- const telemetry_ledger_1 = require("./telemetry-ledger");
82
- const telemetry_attestation_1 = require("./telemetry-attestation");
83
- const trust_audit_1 = require("./trust-audit");
84
- const remote_source_1 = require("./remote-source");
85
- const telemetry_demo_1 = require("./telemetry-demo");
86
- const state_1 = require("./state");
87
- const run_export_1 = require("./run-export");
88
- const result_normalize_1 = require("./result-normalize");
89
- const node_fs_1 = __importDefault(require("node:fs"));
90
- const node_path_1 = __importDefault(require("node:path"));
91
- const scheduling_1 = require("./scheduling");
92
- // ---- canonical plan payload -----------------------------------------------
93
- // Both `cw plan` (default + --json) and `cw_plan` resolve to this exact object.
94
- function planSummary(runner, workflowId, options) {
95
- const run = runner.plan(workflowId, options);
96
- return {
97
- runId: run.id,
98
- workflowId: run.workflow.id,
99
- statePath: run.paths.state,
100
- reportPath: run.paths.report,
101
- pendingTasks: run.tasks.filter((task) => task.status === "pending").length
102
- };
103
- }
104
- // ---- canonical app-run payload --------------------------------------------
105
- // Both `cw app run` and `cw_app_run` resolve to this exact object. Structured
106
- // app inputs + optional sandbox resolution, then a compact operator status.
107
- function appRun(runner, args) {
108
- const appId = String(args.appId || args.workflowId || "");
109
- const inputs = isRecord(args.inputs) ? args.inputs : {};
110
- const planOptions = { ...inputs, ...withoutRuntimeKeys(args) };
111
- const sandboxProfileId = sandboxProfileIdFrom(args);
112
- const resolvedSandbox = sandboxProfileId ? runner.showSandboxProfile(sandboxProfileId, args) : undefined;
113
- const run = runner.plan(appId, planOptions);
114
- const status = runner.operatorStatus(run.id);
115
- return {
116
- runId: run.id,
117
- workflowId: run.workflow.id,
118
- appId: run.workflow.app?.id || appId,
119
- appVersion: run.workflow.app?.version,
120
- statePath: run.paths.state,
121
- reportPath: run.paths.report,
122
- pendingTasks: run.tasks.filter((task) => task.status === "pending").length,
123
- operatorStatus: compactOperatorStatus(status),
124
- nextActions: status.nextActions,
125
- sandboxProfileId,
126
- sandboxProfile: resolvedSandbox
127
- };
128
- }
129
- // ---- canonical sandbox choice payload -------------------------------------
130
- // Both `cw sandbox choose|resolve` and `cw_sandbox_choose|cw_sandbox_resolve`
131
- // resolve to this exact object.
132
- function sandboxChoose(runner, args) {
133
- const profileId = sandboxProfileIdFrom(args) || "readonly";
134
- const profile = runner.showSandboxProfile(profileId, args);
135
- return {
136
- profileId,
137
- sandboxProfileId: profile.id,
138
- valid: true,
139
- profile
140
- };
141
- }
142
- // ---- canonical commit envelope --------------------------------------------
143
- // `cw_commit` resolves to this operator-facing envelope. The CLI `commit`
144
- // command intentionally emits the raw StateCommitResult for scripting; both
145
- // derive from the single core entry runner.commit (declared, not drift — see
146
- // the capability registry's `commit` descriptor).
147
- function commitEnvelope(runner, runId, args) {
148
- const result = runner.commit(runId, args);
149
- const commit = result.commit;
150
- const status = runner.operatorStatus(runId);
151
- return {
152
- runId,
153
- commitId: commit.id,
154
- verifierGated: commit.verifierGated,
155
- checkpoint: commit.checkpoint,
156
- verifierNodeId: commit.verifierNodeId,
157
- candidateId: commit.candidateId,
158
- selectionId: commit.selectionId,
159
- evidenceCount: (commit.evidence || []).length,
160
- snapshotPath: commit.snapshotPath,
161
- nextActions: status.nextActions,
162
- commit
163
- };
164
- }
165
- function compactOperatorStatus(status) {
166
- return {
167
- runId: status.runId,
168
- workflowId: status.workflowId,
169
- appId: status.appId,
170
- appVersion: status.appVersion,
171
- loopStage: status.loopStage,
172
- activePhase: status.activePhase,
173
- blocked: status.blocked,
174
- blockedReasons: status.blockedReasons,
175
- pendingTasks: status.tasks.pending.length,
176
- runningTasks: status.tasks.running.length,
177
- completedTasks: status.tasks.completed.length,
178
- nextActions: status.nextActions
179
- };
180
- }
181
- // ---- run registry / control plane (v0.1.28) -------------------------------
182
- // MECHANISM, ONE SOURCE: the CLI and MCP surfaces both route through these
183
- // functions so `cw <cmd> --json` is byte-identical to `cw_<tool>`. Each accepts
184
- // the raw CLI options OR the raw MCP arguments and normalizes them identically,
185
- // then calls the single RunRegistry method. The registry is constructed from the
186
- // same resolved cwd on both surfaces (CLI: --cwd|process.cwd(); MCP passes a
187
- // resolved cwd and scopes the runner), so repo/home roots line up.
188
- function runRegistryFor(args, planner) {
189
- return new run_registry_1.RunRegistry(String(args.cwd || process.cwd()), planner);
190
- }
191
- function scopeOf(args, fallback) {
192
- if (args.scope === "repo")
193
- return "repo";
194
- if (args.scope === "home")
195
- return "home";
196
- return fallback;
197
- }
198
- function lifecycleOf(value) {
199
- return (0, run_registry_1.isRunLifecycleState)(value) ? value : undefined;
200
- }
201
- function flag(value) {
202
- if (value === undefined)
203
- return undefined;
204
- if (value === false || value === "false" || value === "no" || value === "0")
205
- return false;
206
- return Boolean(value);
207
- }
208
- // F7: explicit invocation cwd — no more process.chdir bracket.
209
- // The runner resolves a run from process.cwd() by default; to operate WITH a run's
210
- // repo as base (run --drive --repo X from anywhere; cross-directory quickstart; run
211
- // import/export against a target dir) we now pass that base EXPLICITLY —
212
- // runner.withBaseDir(dir).loadRun(...) for run resolution (see
213
- // CoolWorkflowRunner.withBaseDir), and invocationCwd(args) to anchor relative path
214
- // args. Nothing mutates the global process.cwd(), so concurrent in-process callers
215
- // can no longer corrupt each other's working directory (the former reentrancy hazard).
216
- function invocationCwd(args) {
217
- return node_path_1.default.resolve(optionalString(args.cwd) || process.cwd());
218
- }
219
- function runRegistryRefresh(reg, args) {
220
- return reg.refresh({ scope: scopeOf(args, "repo") });
221
- }
222
- function runRegistryShow(reg, args) {
223
- return reg.show({ scope: scopeOf(args, "repo") });
224
- }
225
- function runSearch(reg, args) {
226
- return reg.search({
227
- scope: scopeOf(args, "home"),
228
- text: optionalString(args.text || args.q || args.query),
229
- app: optionalString(args.app || args.appId),
230
- status: lifecycleOf(args.status),
231
- repo: optionalString(args.repo),
232
- since: optionalString(args.since),
233
- until: optionalString(args.until),
234
- includeArchived: flag(args.includeArchived ?? args["include-archived"]),
235
- limit: args.limit === undefined ? undefined : Number(args.limit),
236
- offset: args.offset === undefined ? undefined : Number(args.offset)
237
- });
238
- }
239
- function runList(reg, args) {
240
- return reg.list({
241
- scope: scopeOf(args, "home"),
242
- includeArchived: flag(args.includeArchived ?? args["include-archived"]),
243
- limit: args.limit === undefined ? undefined : Number(args.limit),
244
- offset: args.offset === undefined ? undefined : Number(args.offset)
245
- });
246
- }
247
- function runShow(reg, runId, args) {
248
- return reg.showRun(runId, { scope: scopeOf(args, "home") });
249
- }
250
- function runResume(reg, runner, runId, args) {
251
- const base = reg.resume(runId, {
252
- scope: scopeOf(args, "home"),
253
- limit: args.limit === undefined ? undefined : Number(args.limit)
254
- });
255
- // Default (no --drive/--once): read-only, byte-identical to before.
256
- if (!isTrue(args.drive) && !isTrue(args.once))
257
- return base;
258
- // Opt-in continuation: hand the resolved run to the EXISTING agent-delegation
259
- // drive loop (re-plans nothing; picks up pending/running tasks from durable
260
- // state). An unconfigured agent surfaces drive.status="blocked" (fail-closed).
261
- const drive = runDrive(runner, { ...args, runId: base.runId, repo: base.repo, once: isTrue(args.once) });
262
- return { ...base, drive };
263
- }
264
- function runArchive(reg, runId, args) {
265
- if (runId) {
266
- return reg.archive(runId, {
267
- scope: scopeOf(args, "home"),
268
- reason: optionalString(args.reason),
269
- unarchive: flag(args.unarchive)
270
- });
271
- }
272
- const days = Number(args.olderThanDays ?? args["older-than-days"]);
273
- const states = parseLifecycleList(args.state ?? args.status);
274
- return reg.archiveByPolicy({
275
- schemaVersion: 1,
276
- archiveOlderThanDays: Number.isFinite(days) ? days : 0,
277
- archiveStates: states.length ? states : ["completed", "failed"],
278
- defaultQueuePriority: 100
279
- }, { scope: scopeOf(args, "home") });
280
- }
281
- function runRerun(reg, runId, args) {
282
- return reg.rerun(runId, { scope: scopeOf(args, "home"), reason: optionalString(args.reason) });
283
- }
284
- function runExportArchive(runner, runId, args) {
285
- const base = invocationCwd(args);
286
- const output = optionalString(args.output || args.path || args.archive) || `${runId}.cwrun.json`;
287
- // Optionally seal in the operator's PUBLIC trust key so the bundle re-verifies
288
- // offline. Default falls back to the same env the verify gate reads, so a single
289
- // configured key both attests at record-time and travels with the export.
290
- const trustPublicKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey || args.pubkey) || process.env.CW_AGENT_ATTEST_PUBKEY;
291
- const resolvedOutput = node_path_1.default.resolve(base, output);
292
- const sysDirs = /^\/(etc|bin|sbin|usr|Library|System|Applications|boot|dev|proc|sys|root|var\/log|var\/run)\//;
293
- if (sysDirs.test(resolvedOutput)) {
294
- throw new Error(`Refusing to write archive to a system directory: ${output}`);
295
- }
296
- return (0, run_export_1.exportRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId), resolvedOutput, { trustPublicKey });
297
- }
298
- function runImportArchive(runner, args) {
299
- const base = invocationCwd(args);
300
- const archive = optionalString(args.archive || args.path || args.file);
301
- if (!archive)
302
- throw new Error("run import requires an archive path (positional, --archive, --path, or --file)");
303
- const target = node_path_1.default.resolve(base, optionalString(args.target || args.repo || args.cwd) || base);
304
- const imported = (0, run_export_1.importRun)(node_path_1.default.resolve(base, archive), target);
305
- const registry = new run_registry_1.RunRegistry(target, runner.withBaseDir(target));
306
- const registryReport = registry.refresh({ scope: "repo" });
307
- return { ...imported, registry: registryReport };
308
- }
309
- // Read-only: inspect a portable archive's integrity WITHOUT importing it. Routes
310
- // both surfaces through one shared core entry. The runner is unused (no registry
311
- // touch — inspection writes nothing) but kept for dispatch-signature symmetry.
312
- function runInspectArchive(_runner, args) {
313
- const base = invocationCwd(args);
314
- const archive = optionalString(args.archive || args.path || args.file);
315
- if (!archive)
316
- throw new Error("run inspect-archive requires an archive path (positional, --archive, --path, or --file)");
317
- return (0, run_export_1.inspectArchive)(node_path_1.default.resolve(base, archive));
318
- }
319
- function runVerifyImport(runner, runId, args) {
320
- return (0, run_export_1.verifyImportedRun)(runner.withBaseDir(optionalString(args.cwd)).loadRun(runId));
321
- }
322
- // Fail-closed atomic restore of a portable run archive. `run import` runs a
323
- // verification (importRun returns it as ImportResult.verification) but does NOT
324
- // fail on it — it exits 0 even when the telemetry-ledger or trust-audit hash chain
325
- // does not verify, so a chain-tampered run imports with a fabricated success.
326
- // `run restore` closes that gap in ONE step: it integrity-INSPECTS the archive
327
- // FIRST (writing nothing), refuses a bad bundle before any import, then IMPORTS
328
- // and REUSES importRun's verification verdict — reporting ok:true ONLY when verify
329
- // passes. Pure composition of the existing functions (inspectArchive + importRun,
330
- // whose own verification we reuse); no new crypto or IO logic, and no second
331
- // re-hash. The CLI/MCP surfaces map ok:false to exit 1.
332
- function runRestoreArchive(runner, args) {
333
- const base = invocationCwd(args);
334
- const archive = optionalString(args.archive || args.path || args.file);
335
- if (!archive)
336
- throw new Error("run restore requires an archive path (positional, --archive, --path, or --file)");
337
- const resolvedArchive = node_path_1.default.resolve(base, archive);
338
- const target = node_path_1.default.resolve(base, optionalString(args.target || args.repo || args.cwd) || base);
339
- // (1) Integrity-inspect FIRST — read-only, writes nothing. A bad bundle is
340
- // refused here, before any import touches the target tree (no partial restore).
341
- const inspect = (0, run_export_1.inspectArchive)(resolvedArchive);
342
- if (!inspect.ok) {
343
- return { schemaVersion: 1, ok: false, target, inspect, imported: null, verify: null, registry: null };
344
- }
345
- // (2) Intact: import + refresh the target registry (mirrors runImportArchive).
346
- const imported = (0, run_export_1.importRun)(resolvedArchive, target);
347
- const registry = new run_registry_1.RunRegistry(target, runner.withBaseDir(target));
348
- const registryReport = registry.refresh({ scope: "repo" });
349
- // (3) REUSE the verification importRun already ran — it re-proves the same
350
- // restored files PLUS the telemetry-ledger and trust-audit hash chains, but
351
- // returns the verdict WITHOUT throwing. We do NOT re-run verifyImportedRun (that
352
- // would re-hash every file a second time for no new information). The gap restore
353
- // closes is exactly that `run import` ships this verdict and exits 0 even when it
354
- // is false — restore fails-closed on it below (CLI maps ok:false → exit 1).
355
- const verify = imported.verification;
356
- return {
357
- schemaVersion: 1,
358
- // inspect.ok is guaranteed true here — the corrupt-archive case early-returned
359
- // above — so the verdict is purely verify.ok (the telemetry/trust-audit chain).
360
- ok: verify.ok,
361
- target,
362
- inspect,
363
- imported: { ...imported, registry: registryReport },
364
- verify,
365
- registry: registryReport
366
- };
367
- }
368
- // Produce-and-prove: export a run to a portable bundle sealed with the operator's
369
- // trust key (defaulting to CW_AGENT_ATTEST_PUBKEY, same as `run export`), then
370
- // IMMEDIATELY verify the artifact offline the way a recipient will. The producer
371
- // learns now — fail-closed — whether the bundle a client will check is actually
372
- // verifiable (e.g. an unconfigured attest key yields an unverifiable bundle). Pure
373
- // composition of runExportArchive + verifyReportBundle; spawns nothing, writes only
374
- // the archive (and, with --extract-report, the human report) that `run export` would.
375
- function reportBundle(runner, runId, args) {
376
- const exported = runExportArchive(runner, runId, args);
377
- const base = invocationCwd(args);
378
- const extractReportTo = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
379
- const verification = (0, run_export_1.verifyReportBundle)(exported.path, {
380
- pubkey: optionalString(args.pubkey || args.pubKey || args.publicKey),
381
- extractReportTo: extractReportTo ? node_path_1.default.resolve(base, extractReportTo) : undefined,
382
- strictSignatures: Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs),
383
- requireSignatures: Boolean(args["require-signatures"] || args.requireSignatures || args.requireSigs)
384
- });
385
- return {
386
- schemaVersion: 1,
387
- runId,
388
- archivePath: exported.path,
389
- trustKeyEmbedded: exported.trustKeyEmbedded,
390
- reportExtractedTo: verification.reportExtractedTo,
391
- verification,
392
- ok: verification.ok
393
- };
394
- }
395
- // Read-only: verify a portable run bundle OFFLINE and self-contained (archive bytes
396
- // + telemetry chain + trust-audit chain + embedded-key signatures). The runner is
397
- // unused — verification restores into its own throwaway tmpdir and writes nothing to
398
- // any registry — but kept for dispatch-signature symmetry with the other run verbs.
399
- function runVerifyReportBundle(_runner, args) {
400
- const base = invocationCwd(args);
401
- const archive = optionalString(args.archive || args.path || args.file || args.bundle);
402
- if (!archive)
403
- throw new Error("report verify-bundle requires a bundle path (positional, --archive, --path, --file, or --bundle)");
404
- const extractReportTo = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
405
- return (0, run_export_1.verifyReportBundle)(node_path_1.default.resolve(base, archive), {
406
- pubkey: optionalString(args.pubkey || args.pubKey || args.publicKey),
407
- extractReportTo: extractReportTo ? node_path_1.default.resolve(base, extractReportTo) : undefined,
408
- strictSignatures: Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs),
409
- requireSignatures: Boolean(args["require-signatures"] || args.requireSignatures || args.requireSigs)
410
- });
411
- }
412
- function queueAdd(reg, args) {
413
- return reg.queueAdd({
414
- runId: optionalString(args.runId),
415
- appId: optionalString(args.appId || args.app),
416
- workflowId: optionalString(args.workflowId || args.workflow),
417
- repo: optionalString(args.repo),
418
- priority: args.priority === undefined ? undefined : Number(args.priority),
419
- note: optionalString(args.note),
420
- id: optionalString(args.id)
421
- });
422
- }
423
- function queueList(reg, args) {
424
- const status = optionalString(args.status);
425
- return reg.queueList({
426
- status: status,
427
- repo: optionalString(args.repo)
428
- });
429
- }
430
- function queueDrain(reg, args) {
431
- return reg.queueDrain({
432
- limit: args.limit === undefined ? undefined : Number(args.limit),
433
- repo: optionalString(args.repo)
434
- });
435
- }
436
- function queueShow(reg, id) {
437
- return reg.queueShow(id);
438
- }
439
- // ---- control-plane scheduling (v0.1.37) -----------------------------------
440
- function loadSchedulingPolicy(reg) {
441
- const file = reg.schedulingPolicyPath();
442
- // Absent policy => conservative DEFAULT (an unconfigured backend, which §4
443
- // permits). But a PRESENT-but-corrupt policy must fail closed: silently
444
- // substituting defaults would schedule under settings the operator never
445
- // chose while their broken file sits on disk. Let readJson's throw surface it.
446
- if (node_fs_1.default.existsSync(file)) {
447
- return { policy: (0, scheduling_1.normalizeSchedulingPolicy)((0, state_1.readJson)(file)), source: "file" };
448
- }
449
- return { policy: scheduling_1.DEFAULT_SCHEDULING_POLICY, source: "default" };
450
- }
451
- function schedNow(args) {
452
- return optionalString(args.now) || new Date().toISOString();
453
- }
454
- function isTrue(value) {
455
- return value === true || value === "true" || value === "1";
456
- }
457
- function schedPlan(reg, args) {
458
- return (0, scheduling_1.planSchedule)(reg.loadQueueEntries(), loadSchedulingPolicy(reg).policy, schedNow(args));
459
- }
460
- function schedLease(reg, args) {
461
- const now = schedNow(args);
462
- const policy = loadSchedulingPolicy(reg).policy;
463
- const limit = args.limit === undefined ? undefined : Number(args.limit);
464
- const { entries, leases } = (0, scheduling_1.applyLease)(reg.loadQueueEntries(), policy, now, limit);
465
- reg.saveQueueEntries(entries);
466
- return { schemaVersion: 1, now, granted: leases.length, leases };
467
- }
468
- function schedRelease(reg, args) {
469
- const now = schedNow(args);
470
- const failed = isTrue(args.failed);
471
- const { entries, matched } = (0, scheduling_1.leaseRelease)(reg.loadQueueEntries(), String(args.leaseId || ""), loadSchedulingPolicy(reg).policy, now, {
472
- failed,
473
- reason: optionalString(args.reason)
474
- });
475
- if (!matched)
476
- throw new Error(`No active lease to release: ${args.leaseId}`);
477
- reg.saveQueueEntries(entries);
478
- return { schemaVersion: 1, released: String(args.leaseId || ""), failed };
479
- }
480
- function schedComplete(reg, args) {
481
- const { entries, matched } = (0, scheduling_1.leaseComplete)(reg.loadQueueEntries(), String(args.leaseId || ""), schedNow(args));
482
- if (!matched)
483
- throw new Error(`No active lease to complete: ${args.leaseId}`);
484
- reg.saveQueueEntries(entries);
485
- return { schemaVersion: 1, completed: String(args.leaseId || "") };
486
- }
487
- function schedReclaim(reg, args) {
488
- const now = schedNow(args);
489
- const { entries, reclaimed } = (0, scheduling_1.reclaimExpired)(reg.loadQueueEntries(), loadSchedulingPolicy(reg).policy, now);
490
- reg.saveQueueEntries(entries);
491
- return { schemaVersion: 1, now, reclaimed };
492
- }
493
- function schedReset(reg, args) {
494
- const { entries, matched } = (0, scheduling_1.resetEntry)(reg.loadQueueEntries(), String(args.id || ""));
495
- if (!matched)
496
- throw new Error(`No parked entry to reset: ${args.id}`);
497
- reg.saveQueueEntries(entries);
498
- return { schemaVersion: 1, reset: String(args.id || "") };
499
- }
500
- function schedPolicyShow(reg) {
501
- const { policy, source } = loadSchedulingPolicy(reg);
502
- return { schemaVersion: 1, policy, source };
503
- }
504
- function schedPolicySet(reg, args) {
505
- const current = loadSchedulingPolicy(reg).policy;
506
- const patch = {};
507
- for (const key of ["maxConcurrent", "maxAttempts", "leaseTtlMs", "backoffBaseMs", "backoffFactor", "backoffCapMs"]) {
508
- if (args[key] === undefined)
509
- continue;
510
- // Fail closed on a non-numeric flag instead of letting normalizeSchedulingPolicy
511
- // silently substitute the DEFAULT (which would report source:"file" + exit 0,
512
- // so the operator believes they set a value they didn't). Matches the
513
- // Number.isFinite guard the sibling reclaimPolicyFrom already uses.
514
- const value = Number(args[key]);
515
- if (!Number.isFinite(value)) {
516
- throw new Error(`Invalid --${key} "${String(args[key])}": expected a number (e.g. --${key} 4)`);
517
- }
518
- patch[key] = value;
519
- }
520
- const policy = (0, scheduling_1.normalizeSchedulingPolicy)({ ...current, ...patch });
521
- (0, state_1.writeJson)(reg.schedulingPolicyPath(), policy);
522
- return { schemaVersion: 1, policy, source: "file" };
523
- }
524
- // ---- agent delegation drive (v0.1.38) -------------------------------------
525
- // MECHANISM, ONE SOURCE: both surfaces route drive/preview/config through these.
526
- // The read-only preview + config-show payloads are deterministic (counts from
527
- // state, host-stable config path) with NO now-derived numeric field, so
528
- // `cw <cmd> --json` is byte-identical to `cw_<tool>`.
529
- /** Read-only, deterministic preview of a run's NEXT drive step. */
530
- function runDrivePreview(runner, args) {
531
- return (0, drive_1.drivePreview)(runner, String(args.runId || ""), args);
532
- }
533
- const DRIVE_RUNTIME_KEYS = [
534
- "once",
535
- "now",
536
- "preview",
537
- "step",
538
- "drive",
539
- "json",
540
- "format",
541
- "run",
542
- "runId",
543
- "cwd",
544
- "agentCommand",
545
- "agent-command",
546
- "agentArgs",
547
- "agent-args",
548
- "agentEndpoint",
549
- "agent-endpoint",
550
- "agentModel",
551
- "agent-model",
552
- "agentTimeoutMs",
553
- "agent-timeout-ms",
554
- "resume",
555
- "incremental",
556
- // Remote-source flags (v0.1.91): materialized into a local checkout in the capability
557
- // layer, never passed to plan as inputs (the resolved sourceUrl/sourceCommit ARE inputs).
558
- "link",
559
- "ref",
560
- "branch",
561
- "refresh"
562
- ];
563
- function planInputsFor(args) {
564
- const copy = withoutRuntimeKeys(args);
565
- for (const key of DRIVE_RUNTIME_KEYS)
566
- delete copy[key];
567
- return copy;
568
- }
569
- /** Mutating drive step/run. Plans a fresh run for an app id, or continues a run id.
570
- * The agent hop goes ONLY through the agent backend; this composes existing verbs.
571
- *
572
- * The run lives under its repo's `.cw/` and every runner verb resolves a run from
573
- * the process cwd, so the drive operates WITH the run's repo as cwd (exactly how
574
- * the golden path runs each verb from the repo dir) — then restores cwd. This lets
575
- * `cw run <app> --drive --repo X` work when invoked from anywhere. */
576
- function runDrive(runner, args) {
577
- let runId = optionalString(args.runId || args.run);
578
- let repoCwd = optionalString(args.repo);
579
- if (!runId) {
580
- const appId = String(args.appId || args.workflowId || args.app || "");
581
- if (!appId)
582
- throw new Error("run --drive requires an app id (or --run <run-id> to continue)");
583
- const run = runner.plan(appId, planInputsFor(args));
584
- runId = run.id;
585
- repoCwd = run.cwd;
586
- }
587
- // The runner resolves the run from its baseDir, so the drive must run WITH the
588
- // run's repo as base. Pass it explicitly via withBaseDir (F7 — no process.chdir).
589
- const target = repoCwd && node_fs_1.default.existsSync(repoCwd) ? repoCwd : undefined;
590
- const driveRunId = runId;
591
- return (0, drive_1.drive)(runner.withBaseDir(target), driveRunId, {
592
- once: isTrue(args.once),
593
- now: optionalString(args.now),
594
- incremental: isTrue(args.incremental),
595
- args
596
- });
597
- }
598
- /** The app the one-command quickstart plans when none is named. */
599
- exports.QUICKSTART_DEFAULT_APP = "architecture-review";
600
- /** ONE-COMMAND quickstart (v0.1.38+): plan(app) -> run --drive -> report in a single
601
- * invocation, so a newcomer gets a cited risk report from one command on a target
602
- * repo. This is a THIN UX wrapper — NOT a new engine: it composes the EXISTING
603
- * `runDrive` core (which already plans the run, then delegates every worker to the
604
- * configured agent backend and commits) and then writes the report. It introduces
605
- * no second executor, queue, or scheduler, and imports no model SDK.
606
- *
607
- * RED LINE (DIRECTION.md): worker execution still DELEGATES to the operator's own
608
- * agent backend (claude -p / codex exec / HTTP endpoint). With no agent configured
609
- * the drive fails closed (status=blocked) and we never fabricate a completion. */
610
- /** Collect a COMPACT findings list for the end-of-run summary by re-parsing each completed
611
- * worker's `result.md` `cw:result` block (the schema source of truth — `normalizeResultEnvelope`).
612
- * Stderr/human-side ONLY: this NEVER touches the byte-exact stdout payload, so it cannot perturb
613
- * `--json` or the `cw:result` fence. `baseDir` anchors to the run's OWN repo (quickstart may run
614
- * cross-directory). Best-effort: a missing/garbled result.md or an unloadable run is skipped, not
615
- * fatal — the full prose still lives in report.md + each worker transcript. */
616
- function collectRunFindings(runner, runId, baseDir) {
617
- const rows = [];
618
- try {
619
- const run = runner.withBaseDir(baseDir).loadRun(runId);
620
- for (const task of run.tasks) {
621
- if (task.status !== "completed" || !task.resultPath || !node_fs_1.default.existsSync(task.resultPath))
622
- continue;
623
- try {
624
- const envelope = (0, result_normalize_1.normalizeResultEnvelope)(node_fs_1.default.readFileSync(task.resultPath, "utf8"));
625
- for (const f of envelope.findings) {
626
- rows.push({ id: f.id, severity: f.severity || "none", classification: f.classification || "unknown" });
627
- }
628
- }
629
- catch {
630
- /* skip a garbled result.md — the transcript still holds the full record */
631
- }
632
- }
633
- }
634
- catch {
635
- /* run not loadable on this host — the summary degrades to no findings table */
636
- }
637
- return rows;
638
- }
639
- function quickstart(runner, args) {
640
- const appId = String(args.appId || args.app || args.workflowId || exports.QUICKSTART_DEFAULT_APP);
641
- // Remote source (v0.1.91): a `--link <url>` — or a URL passed to `--repo`/`-dir` — is
642
- // materialized to a LOCAL checkout in the capability layer (below). Cloning is
643
- // non-deterministic network I/O and must never enter the replay-deterministic core, so we
644
- // rewrite `args.repo` to the local path here; everything downstream is a normal local run.
645
- const linkArg = optionalString(args.link);
646
- const repoArgRaw = optionalString(args.repo);
647
- const remoteCandidate = linkArg || (repoArgRaw && (0, remote_source_1.isRemoteUrl)(repoArgRaw) ? repoArgRaw : undefined);
648
- // Run anywhere (like brew): default the repo-under-review to the caller's cwd when no
649
- // --repo/--cwd/--link is given. A remote candidate is materialized below, so it must NOT
650
- // fall through to the cwd default here.
651
- if (!remoteCandidate && !optionalString(args.repo) && !optionalString(args.cwd)) {
652
- args.repo = invocationCwd(args);
653
- }
654
- const agentConfigured = Boolean((0, agent_config_1.resolveAgentConfig)(args).command || (0, agent_config_1.resolveAgentConfig)(args).endpoint);
655
- if (isTrue(args.check)) {
656
- return remoteCandidate
657
- ? remoteQuickstartCheck(runner, appId, args, agentConfigured, remoteCandidate)
658
- : quickstartCheck(runner, appId, args, agentConfigured);
659
- }
660
- // Materialize the remote NOW — after `--check` (which never fetches) and before any
661
- // plan/preview/drive — so the orchestrator only ever sees the local checkout. Fails closed:
662
- // a bad URL / blocked scheme / network / auth failure throws here, before any run is planned.
663
- let remoteSource;
664
- if (remoteCandidate) {
665
- remoteSource = (0, remote_source_1.materializeRemote)(remoteCandidate, {
666
- ref: optionalString(args.ref || args.branch),
667
- refresh: isTrue(args.refresh)
668
- });
669
- args.repo = remoteSource.localPath;
670
- // Record the origin as plan INPUTS so it rides into run.inputs → the report header.
671
- args.sourceUrl = remoteSource.url;
672
- args.sourceCommit = remoteSource.commit;
673
- if (remoteSource.ref)
674
- args.sourceRef = remoteSource.ref;
675
- }
676
- // `--resume`: a discoverability flag over the existing continuation. With no
677
- // `--run`, advance exactly ONE step (reuse the `--once` path) and print a
678
- // copy-pasteable continue line; with `--run <id>`, continue that run to
679
- // completion (the default drive). It adds no new execution path.
680
- const resume = isTrue(args.resume);
681
- const resumeRunId = resume ? optionalString(args.runId || args.run) : undefined;
682
- // `--preview`: read-only, deterministic next-step projection (no spawn, no commit).
683
- // Plan a fresh run (the read-only first verb) then project the next drive step.
684
- if (isTrue(args.preview)) {
685
- let runId = optionalString(args.runId || args.run);
686
- let repoCwd = optionalString(args.repo);
687
- if (!runId) {
688
- const run = runner.plan(appId, planInputsFor(args));
689
- runId = run.id;
690
- repoCwd = run.cwd;
691
- }
692
- const previewRunId = runId;
693
- const target = repoCwd && node_fs_1.default.existsSync(repoCwd) ? repoCwd : undefined;
694
- return (0, drive_1.drivePreview)(runner.withBaseDir(target), previewRunId, args);
695
- }
696
- // Drive end-to-end (or one `--once` step). runDrive plans the run, delegates each
697
- // worker to the agent backend, and commits — we add only the report write + a
698
- // single assembled payload. No orchestration is duplicated here.
699
- // `--resume` with no run id advances a single step so a newcomer WITNESSES the
700
- // stop-then-resume; with a run id it continues to completion. Non-resume paths
701
- // are untouched (byte-identical default).
702
- const result = runDrive(runner, { ...args, appId, ...(resume && !resumeRunId ? { once: true } : {}) });
703
- // Always (re)write the report so the one command yields a report.md on disk, even
704
- // when the drive blocked/parked (a partial report is still useful triage).
705
- //
706
- // runDrive restored cwd, so the runs root would resolve against the CALLER's cwd
707
- // here — orphaning the run when quickstart is invoked cross-directory (cwd =
708
- // plugin dir, --repo elsewhere: the README's headline command). The run's
709
- // statePath (<repo>/.cw/runs/<id>/state.json) is authoritative however the run
710
- // was planned or continued; resolve to ITS repo BEFORE any run read, reentrant-safe.
711
- const runRepoCwd = node_path_1.default.resolve(node_path_1.default.dirname(result.statePath), "..", "..", "..");
712
- const reportTarget = node_fs_1.default.existsSync(runRepoCwd) ? runRepoCwd : undefined;
713
- const reportPath = runner.withBaseDir(reportTarget).report(result.runId).path;
714
- // Tamper-evident provenance: bind the remote origin (url@sha) into the run's hash-chained
715
- // trust-audit log so `cw audit verify` re-proves where the code came from. metadata is
716
- // auto-scrubbed of credentials by recordTrustAuditEvent → scrubMetadata. Best-effort: the
717
- // origin is already in run.inputs/report/the result, so a recording hiccup never fails the
718
- // review (additive trust evidence, not a gate).
719
- if (remoteSource) {
720
- try {
721
- const provRun = runner.withBaseDir(reportTarget).loadRun(result.runId);
722
- (0, trust_audit_1.recordTrustAuditEvent)(provRun, {
723
- kind: remoteSource.kind === "archive" ? "source.download" : "source.clone",
724
- decision: "recorded",
725
- source: "operator-recorded",
726
- metadata: { url: remoteSource.url, commit: remoteSource.commit, ref: remoteSource.ref || null, kind: remoteSource.kind, depth: 1 }
727
- });
728
- }
729
- catch {
730
- /* provenance is additive; never fail a completed review over an audit-log hiccup */
731
- }
732
- }
733
- // --bundle: after a COMPLETE drive, seal the run into a portable, self-verified
734
- // bundle so the one command yields a client-verifiable artifact. Pure composition
735
- // of reportBundle() (export sealed + offline self-verify); spawns nothing. Gated on
736
- // completion: a partial or blocked run is NEVER sealed (you must not ship an
737
- // uncommitted artifact).
738
- //
739
- // Run-state resolution MUST anchor to the run's OWN repo (reportTarget): the README
740
- // headline runs quickstart cross-directory (caller cwd != --repo), so a caller-cwd
741
- // loadRun would not find the run. But operator-supplied OUTPUT paths
742
- // (--output/--extract-report) and the default archive name resolve against the
743
- // CALLER's cwd — so artifacts land where the operator ran the command (and `&&
744
- // send out.md` works) and never pollute the analyzed repo's working tree, matching
745
- // standalone `report bundle`. Pre-resolving to absolute makes path.resolve(base, …)
746
- // inside reportBundle a no-op, so the run-repo cwd cannot reclaim them.
747
- let bundle;
748
- const wantsBundle = flag(args.bundle) === true;
749
- if (wantsBundle && result.status === "complete") {
750
- const callerBase = invocationCwd(args);
751
- const outArg = optionalString(args.output || args.path || args.archive);
752
- const extractArg = optionalString(args["extract-report"] || args.extractReport || args.extractReportTo);
753
- bundle = reportBundle(runner, result.runId, {
754
- ...args,
755
- cwd: reportTarget,
756
- output: node_path_1.default.resolve(callerBase, outArg || `${result.runId}.cwrun.json`),
757
- ...(extractArg ? { "extract-report": node_path_1.default.resolve(callerBase, extractArg) } : {})
758
- });
759
- }
760
- let hint;
761
- if (!agentConfigured) {
762
- hint =
763
- "agent backend not configured — set CW_AGENT_COMMAND (e.g. \"claude -p\") or pass --agent-command, then re-run. The one command DELEGATES worker execution to YOUR agent; it never executes a model itself.";
764
- }
765
- else if (result.status === "parked") {
766
- hint = `a worker parked past its retry budget — inspect: cw run show ${result.runId}`;
767
- }
768
- else if (result.status === "blocked") {
769
- hint = `the drive is blocked — inspect: cw run drive ${result.runId}`;
770
- }
771
- else if (result.status === "in-progress") {
772
- hint = resume
773
- ? `one step advanced — continue: cw quickstart ${appId} --run ${result.runId} --resume${wantsBundle ? " --bundle" : ""}`
774
- : `one step advanced (--once) — continue: cw quickstart ${appId} --run ${result.runId} --once`;
775
- }
776
- // --bundle on a run that didn't complete is a NO-OP, not silence: tell the operator
777
- // why nothing was sealed (Rule of Silence permits a human-facing hint).
778
- if (wantsBundle && result.status !== "complete") {
779
- hint = `${hint ? `${hint} ` : ""}--bundle skipped: the run did not complete (status=${result.status}); no bundle was sealed.`;
780
- }
781
- return {
782
- schemaVersion: 1,
783
- appId,
784
- runId: result.runId,
785
- workflowId: result.workflowId,
786
- status: result.status,
787
- plannedWorkers: result.plannedWorkers,
788
- completedWorkers: result.completedWorkers,
789
- parkedWorkers: result.parkedWorkers,
790
- commitId: result.commitId,
791
- reportPath,
792
- statePath: result.statePath,
793
- agentConfigured,
794
- steps: result.steps,
795
- hint,
796
- // Stamp resumedFrom ONLY when we continued an explicit run. Conditional spread
797
- // keeps the key absent on the default/fresh path (own-property absent + omitted
798
- // by JSON.stringify), so default output is byte-identical.
799
- ...(resumeRunId ? { resumedFrom: resumeRunId } : {}),
800
- // Same conditional-spread discipline: `bundle` is present only when --bundle ran
801
- // on a completed drive, so the default (no --bundle) payload is byte-identical.
802
- ...(bundle ? { bundle } : {}),
803
- // `remote` is present only when the review targeted a --link/URL source, so a local-repo
804
- // run stays byte-identical. Carries the sanitized origin + resolved commit for provenance.
805
- ...(remoteSource
806
- ? { remote: { url: remoteSource.url, commit: remoteSource.commit, kind: remoteSource.kind, cached: remoteSource.cached, ...(remoteSource.ref ? { ref: remoteSource.ref } : {}) } }
807
- : {})
808
- };
809
- }
810
- function quickstartCheck(runner, appId, args, agentConfigured) {
811
- const base = invocationCwd(args);
812
- const repoArg = optionalString(args.repo) || base;
813
- const repo = node_path_1.default.resolve(base, repoArg);
814
- const checks = [];
815
- try {
816
- runner.showApp(appId);
817
- checks.push({ name: "app", status: "ok", detail: `Workflow app ${appId} is available.` });
818
- }
819
- catch (error) {
820
- checks.push({
821
- name: "app",
822
- status: "blocked",
823
- detail: `Workflow app ${appId} is not available.`,
824
- fix: "Run `cw app list` and choose one of the listed app ids."
825
- });
826
- }
827
- let repoReadable = false;
828
- let repoStateWritable = false;
829
- try {
830
- const stat = node_fs_1.default.statSync(repo);
831
- repoReadable = stat.isDirectory();
832
- if (!repoReadable)
833
- throw new Error("not a directory");
834
- node_fs_1.default.accessSync(repo, node_fs_1.default.constants.R_OK);
835
- checks.push({ name: "repo", status: "ok", detail: `Repository path is readable (${repo}).` });
836
- }
837
- catch (error) {
838
- checks.push({
839
- name: "repo",
840
- status: "blocked",
841
- detail: `Repository path is not readable (${repo}).`,
842
- fix: "Pass --repo PATH for a readable repository directory."
843
- });
844
- }
845
- try {
846
- const cwDir = node_path_1.default.join(repo, ".cw");
847
- node_fs_1.default.accessSync(node_fs_1.default.existsSync(cwDir) ? cwDir : repo, node_fs_1.default.constants.W_OK);
848
- repoStateWritable = repoReadable;
849
- checks.push({ name: "repo-state", status: "ok", detail: "Run state location is writable." });
850
- }
851
- catch (error) {
852
- checks.push({
853
- name: "repo-state",
854
- status: "blocked",
855
- detail: "Run state location is not writable.",
856
- fix: "Use a writable repo, fix directory permissions, or pass --repo to a writable checkout."
857
- });
858
- }
859
- if (optionalString(args.question)) {
860
- checks.push({ name: "question", status: "ok", detail: "Question is set." });
861
- }
862
- else {
863
- checks.push({
864
- name: "question",
865
- status: "blocked",
866
- detail: "Question is missing.",
867
- fix: "Pass --question TEXT."
868
- });
869
- }
870
- if (agentConfigured) {
871
- checks.push({ name: "agent", status: "ok", detail: "Agent backend is configured." });
872
- }
873
- else {
874
- checks.push({
875
- name: "agent",
876
- status: "blocked",
877
- detail: "No agent backend is configured.",
878
- fix: "Pass --agent-command \"claude -p\", set $CW_AGENT_COMMAND, or use --agent-command builtin:claude."
879
- });
880
- }
881
- if (flag(args.bundle) === true) {
882
- const trustKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey || args.pubkey) || process.env.CW_AGENT_ATTEST_PUBKEY;
883
- if (trustKey) {
884
- checks.push({ name: "bundle-trust-key", status: "ok", detail: "Bundle trust public key is configured." });
885
- }
886
- else if (Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs)) {
887
- checks.push({
888
- name: "bundle-trust-key",
889
- status: "blocked",
890
- detail: "Strict signature verification needs a public trust key.",
891
- fix: "Pass --with-trust-key PATH or set $CW_AGENT_ATTEST_PUBKEY."
892
- });
893
- }
894
- else {
895
- checks.push({
896
- name: "bundle-trust-key",
897
- status: "warn",
898
- detail: "No public trust key is configured; unsigned or unkeyed bundles may verify with reduced signature proof.",
899
- fix: "Pass --with-trust-key PATH to embed the public key."
900
- });
901
- }
902
- }
903
- const ok = checks.every((check) => check.status !== "blocked") && repoStateWritable;
904
- return {
905
- schemaVersion: 1,
906
- mode: "check",
907
- ok,
908
- appId,
909
- repo,
910
- checks,
911
- nextCommand: quickstartNextCommand(appId, repo, args)
912
- };
913
- }
914
- /** Preflight for a `--link`/URL review: validate the URL SHAPE + tooling WITHOUT fetching
915
- * (a clone is heavy and side-effecting; --check stays read-only). Mirrors quickstartCheck's
916
- * app/question/agent sub-checks but swaps the local-repo readability checks for link+tooling.
917
- * `repo` carries the sanitized URL so the result reports what would be reviewed. */
918
- function remoteQuickstartCheck(runner, appId, args, agentConfigured, candidate) {
919
- const validation = (0, remote_source_1.validateRemoteUrl)(candidate);
920
- const checks = [];
921
- try {
922
- runner.showApp(appId);
923
- checks.push({ name: "app", status: "ok", detail: `Workflow app ${appId} is available.` });
924
- }
925
- catch (error) {
926
- checks.push({ name: "app", status: "blocked", detail: `Workflow app ${appId} is not available.`, fix: "Run `cw app list` and choose one of the listed app ids." });
927
- }
928
- if (validation.ok) {
929
- checks.push({ name: "link", status: "ok", detail: `Remote source is a valid ${validation.kind} URL (${validation.url}).` });
930
- }
931
- else {
932
- checks.push({ name: "link", status: "blocked", detail: `Remote source is not usable: ${validation.reason}.`, fix: "Pass a git URL (https/ssh/git/file or git@host:repo)." });
933
- }
934
- if ((0, remote_source_1.gitAvailable)()) {
935
- checks.push({ name: "tooling", status: "ok", detail: "git is available to clone the remote." });
936
- }
937
- else {
938
- checks.push({ name: "tooling", status: "blocked", detail: "git was not found on PATH.", fix: "Install git, then re-run." });
939
- }
940
- if (optionalString(args.question)) {
941
- checks.push({ name: "question", status: "ok", detail: "Question is set." });
942
- }
943
- else {
944
- checks.push({ name: "question", status: "blocked", detail: "Question is missing.", fix: "Pass --question TEXT." });
945
- }
946
- if (agentConfigured) {
947
- checks.push({ name: "agent", status: "ok", detail: "Agent backend is configured." });
948
- }
949
- else {
950
- checks.push({ name: "agent", status: "blocked", detail: "No agent backend is configured.", fix: "Pass --agent-command \"claude -p\", set $CW_AGENT_COMMAND, or use --agent-command builtin:claude." });
951
- }
952
- const ok = checks.every((check) => check.status !== "blocked");
953
- return {
954
- schemaVersion: 1,
955
- mode: "check",
956
- ok,
957
- appId,
958
- repo: validation.url,
959
- checks,
960
- nextCommand: `cw quickstart ${shellWord(appId)} --link ${shellWord(validation.url)}${optionalString(args.question) ? ` --question ${shellWord(String(args.question))}` : ""}`
961
- };
962
- }
963
- function quickstartNextCommand(appId, repo, args) {
964
- const parts = ["cw", "quickstart", shellWord(appId), "--repo", shellWord(repo)];
965
- const question = optionalString(args.question);
966
- if (question)
967
- parts.push("--question", shellWord(question));
968
- const command = optionalString(args.agentCommand || args["agent-command"]);
969
- if (command)
970
- parts.push("--agent-command", shellWord(command));
971
- if (flag(args.bundle) === true)
972
- parts.push("--bundle");
973
- const trustKey = optionalString(args["with-trust-key"] || args.withTrustKey || args.trustKey);
974
- if (trustKey)
975
- parts.push("--with-trust-key", shellWord(trustKey));
976
- if (Boolean(args["strict-signatures"] || args.strictSignatures || args.strictSigs))
977
- parts.push("--strict-signatures");
978
- return parts.join(" ");
979
- }
980
- function shellWord(value) {
981
- if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value))
982
- return value;
983
- return `'${value.replace(/'/g, "'\\''")}'`;
984
- }
985
- /** Read-only, deterministic projection of the effective agent config (secret-stripped). */
986
- function backendAgentConfigShow(args) {
987
- return (0, agent_config_1.agentConfigShow)(args);
988
- }
989
- /** Persist the durable agent config (secret-stripped) and return the new state. */
990
- function backendAgentConfigSet(args) {
991
- (0, agent_config_1.setAgentConfigFile)(args);
992
- return (0, agent_config_1.agentConfigShow)(args);
993
- }
994
- // ---- run retention & provable reclamation (v0.1.39) -----------------------
995
- // MECHANISM, ONE SOURCE: both surfaces route gc plan/run/verify through these.
996
- // `gc plan`/`gc verify` are read-only + deterministic (only `generatedAt` is
997
- // now-derived ISO, allowed by the parity rule); `gc run` is the disk-freeing tier.
998
- function reclaimPolicyFrom(args) {
999
- const policy = {};
1000
- const days = Number(args.reclaimAfterArchiveDays ?? args["reclaim-after-archive-days"] ?? args.olderThanDays ?? args["older-than-days"]);
1001
- if (Number.isFinite(days))
1002
- policy.reclaimAfterArchiveDays = days;
1003
- const keepScratch = flag(args.keepScratch ?? args["keep-scratch"]);
1004
- if (keepScratch !== undefined)
1005
- policy.keepScratch = keepScratch;
1006
- const keepSnapshots = flag(args.keepSnapshots ?? args["keep-snapshots"]);
1007
- if (keepSnapshots !== undefined)
1008
- policy.keepSnapshots = keepSnapshots;
1009
- const maxRuns = Number(args.maxReclaimRuns ?? args["max-reclaim-runs"]);
1010
- if (Number.isFinite(maxRuns))
1011
- policy.maxReclaimRuns = maxRuns;
1012
- const maxBytes = Number(args.maxReclaimBytes ?? args["max-reclaim-bytes"]);
1013
- if (Number.isFinite(maxBytes))
1014
- policy.maxReclaimBytes = maxBytes;
1015
- const states = parseLifecycleList(args.state ?? args.status);
1016
- if (states.length)
1017
- policy.reclaimStates = states;
1018
- return policy;
1019
- }
1020
- function gcPlan(reg, runId, args) {
1021
- return reg.gcPlan({ scope: scopeOf(args, "home"), runId: runId || optionalString(args.runId), policy: reclaimPolicyFrom(args), now: optionalString(args.now) });
1022
- }
1023
- function gcRun(reg, runId, args) {
1024
- return reg.gcRun({
1025
- scope: scopeOf(args, "home"),
1026
- runId: runId || optionalString(args.runId),
1027
- policy: reclaimPolicyFrom(args),
1028
- now: optionalString(args.now),
1029
- actor: optionalString(args.actor),
1030
- limit: args.limit === undefined ? undefined : Number(args.limit)
1031
- });
1032
- }
1033
- function gcVerify(reg, runId, args) {
1034
- return reg.gcVerify(runId, { scope: scopeOf(args, "home") });
1035
- }
1036
- // Remote-source clone cache (v0.1.91): list/reclaim the `~/.local/state/cool-workflow/clones`
1037
- // checkouts that `--link`/URL reviews populate. Pure filesystem work; both CLI and MCP route
1038
- // here so `cw clones …` and `cw_clones_…` are byte-identical.
1039
- var clones_1 = require("./clones");
1040
- Object.defineProperty(exports, "listClones", { enumerable: true, get: function () { return clones_1.listClones; } });
1041
- Object.defineProperty(exports, "gcClones", { enumerable: true, get: function () { return clones_1.gcClones; } });
1042
- function runHistory(reg, args) {
1043
- return reg.history({
1044
- scope: scopeOf(args, "home"),
1045
- app: optionalString(args.app || args.appId),
1046
- status: lifecycleOf(args.status),
1047
- limit: args.limit === undefined ? undefined : Number(args.limit),
1048
- offset: args.offset === undefined ? undefined : Number(args.offset)
1049
- });
1050
- }
1051
- // ---- observability + cost accounting (v0.1.31) ----------------------------
1052
- // MECHANISM, ONE SOURCE: both `cw metrics summary --json` and `cw_metrics_summary`
1053
- // route through this function. It enumerates the v0.1.28 registry (derived live
1054
- // from source), loads each run's durable state, and DERIVES the cross-repo
1055
- // rollup. Runs whose source is unreadable are counted in `unreadableRuns` (fail
1056
- // closed), never silently dropped. Pricing is POLICY via `--pricing`; `now` is
1057
- // injectable via `args.now` for eval/replay determinism.
1058
- function metricsSummary(reg, runner, args) {
1059
- const scope = scopeOf(args, "repo");
1060
- const report = reg.show({ scope });
1061
- const policy = (0, observability_1.loadCostPolicy)(args, runner.pluginRoot);
1062
- const now = optionalString(args.now) || new Date().toISOString();
1063
- const inputs = [];
1064
- let unreadableRuns = 0;
1065
- for (const record of report.index.records) {
1066
- try {
1067
- const loaded = (0, state_1.loadRunStateFile)(record.statePath, { dryRun: true });
1068
- if (loaded.report.status === "unsupported") {
1069
- unreadableRuns++;
1070
- continue;
1071
- }
1072
- inputs.push({
1073
- run: loaded.run,
1074
- repo: record.repo,
1075
- persistedFingerprint: (0, observability_1.loadPersistedMetricsFingerprint)(loaded.run)
1076
- });
1077
- }
1078
- catch {
1079
- unreadableRuns++;
1080
- }
1081
- }
1082
- return (0, observability_1.deriveMetricsSummary)(inputs, { now, scope, policy, unreadableRuns });
1083
- }
1084
- function parseLifecycleList(value) {
1085
- const raw = Array.isArray(value) ? value : value === undefined ? [] : [value];
1086
- const out = [];
1087
- for (const item of raw) {
1088
- if ((0, run_registry_1.isRunLifecycleState)(item))
1089
- out.push(item);
1090
- }
1091
- return out;
1092
- }
1093
- // ---- shared argument helpers ----------------------------------------------
1094
- function sandboxProfileIdFrom(args) {
1095
- return optionalString(args.sandbox || args.sandboxProfile || args.sandboxProfileId || args.profileId);
1096
- }
1097
- function withoutRuntimeKeys(args) {
1098
- const copy = { ...args };
1099
- for (const key of ["appId", "workflowId", "inputs", "sandbox", "sandboxProfile", "sandboxProfileId", "profileId"]) {
1100
- delete copy[key];
1101
- }
1102
- return copy;
1103
- }
1104
- function optionalString(value) {
1105
- if (value === undefined || value === null || value === "")
1106
- return undefined;
1107
- return String(value);
1108
- }
1109
- function isRecord(value) {
1110
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
1111
- }
1112
- // ---- telemetry attestation: read-only ledger verification (Track 1) --------
1113
- // Re-prove a run's telemetry chain offline: prevHash linkage + independent per-
1114
- // record hash recompute (never trusts the stored hash). The auditable claim made
1115
- // inspectable on demand — anyone can run this; a forged/edited record fails it.
1116
- function telemetryVerify(runner, args) {
1117
- const runId = optionalString(args.runId || args.run);
1118
- if (!runId)
1119
- throw new Error("telemetry verify requires a run id (cw telemetry verify <run-id>)");
1120
- const run = runner.loadRun(runId);
1121
- const v = (0, telemetry_ledger_1.verifyTelemetryLedger)(run);
1122
- // Opt-in independent signature re-verification. verifyTelemetryLedger re-proves
1123
- // the chain (so the stored attestation verdicts were not edited); supplying the
1124
- // trust public key (--pubkey / CW_AGENT_ATTEST_PUBKEY) additionally RE-RUNS the
1125
- // ed25519 check over each `attested` record's stored raw usage rather than
1126
- // trusting that verdict, so a forged signature can no longer ride a green chain.
1127
- const trustPublicKeyInput = optionalString(args.pubkey || args.pubKey || args.publicKey) || process.env.CW_AGENT_ATTEST_PUBKEY;
1128
- const trustPublicKey = (0, telemetry_attestation_1.resolveTrustPublicKey)(trustPublicKeyInput);
1129
- const keyChecks = trustPublicKeyInput && !trustPublicKey
1130
- ? [{ name: "signature-key", pass: false, code: "telemetry-pubkey-unreadable" }]
1131
- : [];
1132
- const sig = (0, telemetry_attestation_1.verifyTelemetrySignatures)(v.records, trustPublicKey);
1133
- const failedChecks = [...v.checks.filter((c) => !c.pass), ...keyChecks, ...sig.checks.filter((c) => !c.pass)];
1134
- return {
1135
- schemaVersion: 1,
1136
- runId: run.id,
1137
- present: v.present,
1138
- // Chain integrity AND (when a key was supplied) every attested signature must
1139
- // re-verify. With no key, sig.failed is 0 → unchanged chain-only behavior.
1140
- verified: v.verified && keyChecks.length === 0 && sig.failed === 0,
1141
- records: v.records.length,
1142
- attested: v.attested,
1143
- unattested: v.unattested,
1144
- absent: v.absent,
1145
- signatureKeyProvided: sig.keyProvided,
1146
- signaturesChecked: sig.checked,
1147
- signaturesReverified: sig.reverified,
1148
- signaturesFailed: sig.failed,
1149
- failedChecks: failedChecks.map((c) => ({ name: c.name, code: c.code }))
1150
- };
1151
- }
1152
- // audit.verify — fail-closed re-prove of a run's trust-audit hash chain. The peer
1153
- // of telemetry.verify for the sandbox/policy/commit-gate decision log: recomputes
1154
- // every event hash from genesis, checks chain linkage, and catches the
1155
- // unchained-event forgery. Exposed as a verb (not just embedded in `audit summary`,
1156
- // which always exits 0) so `cw audit verify <run> && deploy` can gate on the exit
1157
- // code. POLA: a run with no audit log is present:false / verified:true / exit 0.
1158
- function auditVerify(runner, args) {
1159
- const runId = optionalString(args.runId || args.run);
1160
- if (!runId)
1161
- throw new Error("audit verify requires a run id (cw audit verify <run-id>)");
1162
- const run = runner.loadRun(runId);
1163
- const v = (0, trust_audit_1.verifyTrustAudit)(run);
1164
- return {
1165
- schemaVersion: 1,
1166
- runId: run.id,
1167
- present: v.present,
1168
- verified: v.verified,
1169
- eventCount: v.eventCount,
1170
- chained: v.chained,
1171
- unchained: v.unchained,
1172
- corruptLines: v.corruptLines,
1173
- failedChecks: v.checks.filter((c) => !c.pass).map((c) => ({ name: c.name, code: c.code }))
1174
- };
1175
- }
1176
- // ---- demo: tamper-evidence (the one-command proof) -------------------------
1177
- // Hermetic, deterministic-shape: builds a real ed25519-signed telemetry ledger,
1178
- // then forges it three ways and shows all three tamper-evidence layers (ledger,
1179
- // signature, result) catch it. CLI-only
1180
- // (a human-facing demonstration; the underlying verify is the telemetry.verify verb).
1181
- function demoTamper(_runner, _args = {}) {
1182
- return (0, telemetry_demo_1.runTamperDemo)();
1183
- }
1184
- function demoBundle(_runner, _args = {}) {
1185
- return (0, telemetry_demo_1.runBundleDemo)();
1186
- }