cool-workflow 0.1.98 → 0.2.1

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 (315) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +18 -7
  4. package/apps/architecture-review/app.json +2 -2
  5. package/apps/architecture-review/workflow.js +12 -12
  6. package/apps/architecture-review-fast/app.json +1 -1
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/cli/dispatch.js +236 -0
  12. package/dist/cli/entry.js +120 -0
  13. package/dist/cli/io.js +21 -4
  14. package/dist/cli/parseargv.js +157 -0
  15. package/dist/cli.js +6 -33
  16. package/dist/core/capability-table.js +3518 -0
  17. package/dist/core/format/help.js +314 -0
  18. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  19. package/dist/core/hash.js +137 -0
  20. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  21. package/dist/core/multi-agent/collaboration.js +481 -0
  22. package/dist/core/multi-agent/coordinator.js +515 -0
  23. package/dist/core/multi-agent/eval-replay.js +306 -0
  24. package/dist/core/multi-agent/runtime.js +929 -0
  25. package/dist/core/multi-agent/topology.js +298 -0
  26. package/dist/core/multi-agent/trust-policy.js +197 -0
  27. package/dist/core/pipeline/commit-gate.js +320 -0
  28. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  29. package/dist/core/pipeline/dispatch.js +103 -0
  30. package/dist/core/pipeline/drive-decide.js +227 -0
  31. package/dist/core/pipeline/error-feedback.js +161 -0
  32. package/dist/core/pipeline/loop-expansion.js +124 -0
  33. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  34. package/dist/core/pipeline/runner.js +230 -0
  35. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  36. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  37. package/dist/core/state/node-projection.js +95 -0
  38. package/dist/core/state/node-snapshot.js +230 -0
  39. package/dist/core/state/run-paths.js +93 -0
  40. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  41. package/dist/core/state/schema.js +50 -0
  42. package/dist/core/state/state-explosion/digest.js +243 -0
  43. package/dist/core/state/state-explosion/graph.js +527 -0
  44. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  45. package/dist/core/state/state-explosion/report.js +187 -0
  46. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  47. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  48. package/dist/core/state/types.js +19 -0
  49. package/dist/{validation.js → core/state/validation.js} +64 -131
  50. package/dist/core/trust/evidence-grounding.js +134 -0
  51. package/dist/core/trust/ledger.js +199 -0
  52. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  53. package/dist/core/trust/telemetry-ledger.js +127 -0
  54. package/dist/core/types.js +20 -0
  55. package/dist/core/version.js +16 -0
  56. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  57. package/dist/mcp/dispatch.js +104 -0
  58. package/dist/mcp/server.js +144 -0
  59. package/dist/mcp-server.js +6 -84
  60. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  61. package/dist/shell/app-run-cli.js +88 -0
  62. package/dist/shell/audit-cli.js +259 -0
  63. package/dist/shell/audit-provenance.js +83 -0
  64. package/dist/shell/candidate-scoring-io.js +459 -0
  65. package/dist/shell/collaboration-io.js +264 -0
  66. package/dist/shell/commit-summary.js +104 -0
  67. package/dist/shell/commit.js +290 -0
  68. package/dist/shell/coordinator-io.js +476 -0
  69. package/dist/shell/demo-cli.js +19 -0
  70. package/dist/shell/dispatch.js +162 -0
  71. package/dist/shell/doctor.js +292 -0
  72. package/dist/shell/drive.js +885 -0
  73. package/dist/shell/error-feedback-io.js +305 -0
  74. package/dist/shell/eval-io.js +473 -0
  75. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  76. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  77. package/dist/shell/exec-backend-cli.js +88 -0
  78. package/dist/shell/execution-backend/agent.js +507 -0
  79. package/dist/shell/execution-backend/ci.js +15 -0
  80. package/dist/shell/execution-backend/container.js +69 -0
  81. package/dist/shell/execution-backend/envelopes.js +55 -0
  82. package/dist/shell/execution-backend/local.js +113 -0
  83. package/dist/shell/execution-backend/probes.js +175 -0
  84. package/dist/shell/execution-backend/registry.js +402 -0
  85. package/dist/shell/execution-backend/remote.js +128 -0
  86. package/dist/shell/execution-backend/types.js +11 -0
  87. package/dist/shell/feedback-cli.js +81 -0
  88. package/dist/shell/feedback-operations.js +48 -0
  89. package/dist/shell/fs-atomic.js +276 -0
  90. package/dist/shell/harness.js +98 -0
  91. package/dist/shell/ledger-cli.js +212 -0
  92. package/dist/shell/ledger-io.js +169 -0
  93. package/dist/shell/man-cli.js +89 -0
  94. package/dist/shell/metrics-cli.js +100 -0
  95. package/dist/shell/multi-agent-cli.js +1002 -0
  96. package/dist/shell/multi-agent-host.js +563 -0
  97. package/dist/shell/multi-agent-io.js +387 -0
  98. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  99. package/dist/shell/node-store.js +124 -0
  100. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  101. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  102. package/dist/{observability.js → shell/observability.js} +159 -332
  103. package/dist/{onramp.js → shell/onramp.js} +11 -0
  104. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  105. package/dist/shell/operator-ux.js +431 -0
  106. package/dist/shell/orchestrator.js +231 -0
  107. package/dist/shell/pipeline-cli.js +667 -0
  108. package/dist/shell/pipeline.js +217 -0
  109. package/dist/shell/reclamation-io.js +1366 -0
  110. package/dist/shell/registry-cli.js +344 -0
  111. package/dist/{remote-source.js → shell/remote-source.js} +2 -2
  112. package/dist/shell/report-cli.js +101 -0
  113. package/dist/shell/report-view-cli.js +117 -0
  114. package/dist/{orchestrator → shell}/report.js +289 -282
  115. package/dist/shell/reporter.js +62 -0
  116. package/dist/shell/run-export-cli.js +106 -0
  117. package/dist/shell/run-export.js +680 -0
  118. package/dist/shell/run-registry-io.js +1014 -0
  119. package/dist/shell/run-store.js +164 -0
  120. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  121. package/dist/{scheduler.js → shell/scheduler-io.js} +257 -48
  122. package/dist/shell/scheduling-io.js +321 -0
  123. package/dist/shell/state-cli.js +181 -0
  124. package/dist/shell/state-explosion-cli.js +197 -0
  125. package/dist/shell/telemetry-cli.js +85 -0
  126. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  127. package/dist/shell/telemetry-ledger-io.js +132 -0
  128. package/dist/{term.js → shell/term.js} +36 -46
  129. package/dist/shell/topology-io.js +361 -0
  130. package/dist/shell/trust-audit.js +472 -0
  131. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  132. package/dist/shell/verifier.js +48 -0
  133. package/dist/shell/workbench-host.js +258 -0
  134. package/dist/shell/workbench-text.js +18 -0
  135. package/dist/shell/workbench.js +170 -0
  136. package/dist/shell/worker-cli.js +124 -0
  137. package/dist/shell/worker-isolation.js +852 -0
  138. package/dist/shell/workflow-app-loader.js +650 -0
  139. package/docs/agent-delegation-drive.7.md +4 -0
  140. package/docs/cli-mcp-parity.7.md +282 -219
  141. package/docs/contract-migration-tooling.7.md +4 -0
  142. package/docs/control-plane-scheduling.7.md +4 -0
  143. package/docs/durable-state-and-locking.7.md +4 -0
  144. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  145. package/docs/execution-backends.7.md +4 -0
  146. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  147. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  148. package/docs/multi-agent-operator-ux.7.md +4 -0
  149. package/docs/node-snapshot-diff-replay.7.md +4 -0
  150. package/docs/observability-cost-accounting.7.md +6 -1
  151. package/docs/project-index.md +137 -71
  152. package/docs/real-execution-backends.7.md +4 -0
  153. package/docs/release-and-migration.7.md +4 -0
  154. package/docs/release-tooling.7.md +4 -0
  155. package/docs/remote-source-review.7.md +4 -4
  156. package/docs/report-verifiable-bundle.7.md +1 -1
  157. package/docs/run-registry-control-plane.7.md +4 -0
  158. package/docs/run-retention-reclamation.7.md +25 -0
  159. package/docs/security-trust-hardening.7.md +3 -1
  160. package/docs/state-explosion-management.7.md +4 -0
  161. package/docs/team-collaboration.7.md +4 -0
  162. package/docs/web-desktop-workbench.7.md +15 -1
  163. package/manifest/plugin.manifest.json +1 -1
  164. package/manifest/source-context-profiles.json +9 -13
  165. package/package.json +1 -1
  166. package/scripts/agents/agent-adapter-core.js +22 -2
  167. package/scripts/agents/claude-p-agent.js +8 -3
  168. package/scripts/agents/cw-attest-wrap.js +2 -2
  169. package/scripts/agents/gemini-agent.js +5 -1
  170. package/scripts/agents/opencode-agent.js +5 -1
  171. package/scripts/bump-version.js +4 -3
  172. package/scripts/canonical-apps.js +4 -4
  173. package/scripts/dogfood-architecture-review.js +2 -3
  174. package/scripts/dogfood-release.js +3 -3
  175. package/scripts/gen-parity-doc.js +15 -2
  176. package/scripts/golden-path.js +4 -4
  177. package/scripts/onramp-check.js +1 -1
  178. package/scripts/parity-check.js +38 -21
  179. package/scripts/release-flow.js +2 -2
  180. package/scripts/sync-project-index.js +51 -27
  181. package/scripts/validate-run-state-schema.js +2 -2
  182. package/scripts/version-sync-check.js +30 -30
  183. package/dist/candidate-scoring.js +0 -729
  184. package/dist/capability-core.js +0 -1189
  185. package/dist/capability-registry.js +0 -885
  186. package/dist/cli/command-surface.js +0 -494
  187. package/dist/cli/format.js +0 -56
  188. package/dist/cli/handlers/audit.js +0 -82
  189. package/dist/cli/handlers/blackboard.js +0 -81
  190. package/dist/cli/handlers/candidate.js +0 -40
  191. package/dist/cli/handlers/clones.js +0 -34
  192. package/dist/cli/handlers/collaboration.js +0 -61
  193. package/dist/cli/handlers/eval.js +0 -40
  194. package/dist/cli/handlers/ledger.js +0 -169
  195. package/dist/cli/handlers/maintenance.js +0 -107
  196. package/dist/cli/handlers/multi-agent.js +0 -165
  197. package/dist/cli/handlers/node.js +0 -41
  198. package/dist/cli/handlers/operational.js +0 -155
  199. package/dist/cli/handlers/operator.js +0 -146
  200. package/dist/cli/handlers/registry.js +0 -68
  201. package/dist/cli/handlers/run.js +0 -153
  202. package/dist/cli/handlers/scheduling.js +0 -132
  203. package/dist/cli/handlers/workbench.js +0 -41
  204. package/dist/cli/handlers/worker.js +0 -45
  205. package/dist/cli/run-summary.js +0 -45
  206. package/dist/clones.js +0 -162
  207. package/dist/collaboration.js +0 -726
  208. package/dist/commit.js +0 -592
  209. package/dist/compare.js +0 -18
  210. package/dist/coordinator/classify.js +0 -45
  211. package/dist/coordinator/paths.js +0 -42
  212. package/dist/coordinator/util.js +0 -126
  213. package/dist/coordinator.js +0 -990
  214. package/dist/daemon.js +0 -44
  215. package/dist/dispatch.js +0 -250
  216. package/dist/doctor.js +0 -212
  217. package/dist/drive.js +0 -864
  218. package/dist/error-feedback.js +0 -419
  219. package/dist/evidence-grounding.js +0 -184
  220. package/dist/execution-backend/agent.js +0 -354
  221. package/dist/execution-backend/probes.js +0 -112
  222. package/dist/execution-backend/util.js +0 -47
  223. package/dist/execution-backend.js +0 -961
  224. package/dist/gates.js +0 -48
  225. package/dist/harness.js +0 -61
  226. package/dist/ledger.js +0 -313
  227. package/dist/loop-expansion.js +0 -60
  228. package/dist/mcp/tool-call.js +0 -470
  229. package/dist/mcp/tool-definitions.js +0 -1066
  230. package/dist/mcp-surface.js +0 -30
  231. package/dist/multi-agent/graph.js +0 -84
  232. package/dist/multi-agent/helpers.js +0 -141
  233. package/dist/multi-agent/ids.js +0 -20
  234. package/dist/multi-agent/paths.js +0 -22
  235. package/dist/multi-agent-eval/normalize.js +0 -51
  236. package/dist/multi-agent-eval.js +0 -678
  237. package/dist/multi-agent-host.js +0 -777
  238. package/dist/multi-agent.js +0 -984
  239. package/dist/node-projection.js +0 -59
  240. package/dist/node-snapshot.js +0 -260
  241. package/dist/operator-ux.js +0 -631
  242. package/dist/orchestrator/app-operations.js +0 -211
  243. package/dist/orchestrator/audit-operations.js +0 -182
  244. package/dist/orchestrator/candidate-operations.js +0 -117
  245. package/dist/orchestrator/cli-options.js +0 -294
  246. package/dist/orchestrator/collaboration-operations.js +0 -86
  247. package/dist/orchestrator/feedback-operations.js +0 -81
  248. package/dist/orchestrator/host-operations.js +0 -78
  249. package/dist/orchestrator/lifecycle-operations.js +0 -650
  250. package/dist/orchestrator/migration-operations.js +0 -44
  251. package/dist/orchestrator/multi-agent-operations.js +0 -362
  252. package/dist/orchestrator/topology-operations.js +0 -84
  253. package/dist/orchestrator.js +0 -925
  254. package/dist/pipeline-runner.js +0 -285
  255. package/dist/reclamation/hash.js +0 -72
  256. package/dist/reclamation.js +0 -812
  257. package/dist/reporter.js +0 -67
  258. package/dist/run-export.js +0 -815
  259. package/dist/run-registry/derive.js +0 -175
  260. package/dist/run-registry/format.js +0 -124
  261. package/dist/run-registry/gc.js +0 -251
  262. package/dist/run-registry/policy.js +0 -16
  263. package/dist/run-registry/queue.js +0 -115
  264. package/dist/run-registry.js +0 -850
  265. package/dist/run-state-schema.js +0 -68
  266. package/dist/scheduling.js +0 -184
  267. package/dist/state-explosion.js +0 -1014
  268. package/dist/state.js +0 -367
  269. package/dist/telemetry-ledger.js +0 -196
  270. package/dist/topology.js +0 -565
  271. package/dist/triggers.js +0 -184
  272. package/dist/trust-audit.js +0 -644
  273. package/dist/types/blackboard.js +0 -2
  274. package/dist/types/candidate.js +0 -2
  275. package/dist/types/collaboration.js +0 -2
  276. package/dist/types/core.js +0 -2
  277. package/dist/types/drive.js +0 -10
  278. package/dist/types/error-feedback.js +0 -2
  279. package/dist/types/evidence-reasoning.js +0 -2
  280. package/dist/types/execution-backend.js +0 -2
  281. package/dist/types/multi-agent.js +0 -2
  282. package/dist/types/observability.js +0 -2
  283. package/dist/types/pipeline.js +0 -2
  284. package/dist/types/reclamation.js +0 -8
  285. package/dist/types/report-bundle.js +0 -6
  286. package/dist/types/result.js +0 -2
  287. package/dist/types/run-registry.js +0 -2
  288. package/dist/types/run.js +0 -2
  289. package/dist/types/sandbox.js +0 -2
  290. package/dist/types/schedule.js +0 -2
  291. package/dist/types/state-node.js +0 -2
  292. package/dist/types/topology.js +0 -2
  293. package/dist/types/trust.js +0 -2
  294. package/dist/types/workbench.js +0 -2
  295. package/dist/types/worker.js +0 -2
  296. package/dist/types/workflow-app.js +0 -2
  297. package/dist/types.js +0 -44
  298. package/dist/util/fingerprint.js +0 -19
  299. package/dist/util/fingerprint.test.js +0 -27
  300. package/dist/verifier.js +0 -78
  301. package/dist/version.js +0 -8
  302. package/dist/workbench-host.js +0 -192
  303. package/dist/workbench.js +0 -192
  304. package/dist/worker-accept/acceptance.js +0 -114
  305. package/dist/worker-accept/blackboard-fanout.js +0 -80
  306. package/dist/worker-accept/blackboard-linkage.js +0 -19
  307. package/dist/worker-accept/context.js +0 -2
  308. package/dist/worker-accept/telemetry-ledger.js +0 -126
  309. package/dist/worker-accept/validation.js +0 -77
  310. package/dist/worker-accept/verifier-completion.js +0 -73
  311. package/dist/worker-isolation/helpers.js +0 -51
  312. package/dist/worker-isolation/paths.js +0 -46
  313. package/dist/worker-isolation.js +0 -656
  314. package/dist/workflow-api.js +0 -131
  315. /package/dist/{types → core/types}/boundary.js +0 -0
@@ -1,329 +1,191 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
2
+ // core/workflow-apps/app-schema.ts workflow-app manifest + workflow DSL
3
+ // types, a pure builder API (`workflow`, `phase`, `parallel`, `loop`,
4
+ // `agent`, `artifact`, `subWorkflow`, `input`), and full manifest
5
+ // validation (`validateWorkflowApp`).
6
+ //
7
+ // MILESTONE 12 (docs/rebuild/PLAN.md build order; workflow-apps.md). Milestone 6+7
8
+ // built the minimal real subset needed to load ONE app and drive it
9
+ // end to end (see git history / the old header note this replaces).
10
+ // This milestone adds, on top of that, WITHOUT changing any existing
11
+ // exported function's signature or behavior:
12
+ // - `loop()` — sugar over `phase()` that sets a bounded dynamic loop
13
+ // spec, byte-exact to src/workflow-api.ts.
14
+ // - `subWorkflow()` — sugar over `agent()` that adds a `subWorkflow`
15
+ // field, byte-exact to src/workflow-api.ts.
16
+ // - `validateWorkflowApp`/`assertValidWorkflowApp`/
17
+ // `validateWorkflowDefinition` — the full WorkflowAppValidationIssue
18
+ // taxonomy `cw app validate` needs, ported byte-for-byte from
19
+ // src/workflow-app-framework.ts.
20
+ // - `WorkflowAppValidationError` — thrown by a fail-closed manifest
21
+ // load (see shell/workflow-app-loader.ts's discovery functions).
22
+ //
23
+ // Pure factory + validation functions (this file, no `fs`/`child_process`/
24
+ // `process.env`); the actual `require()`/fs read of an app's manifest +
25
+ // entrypoint, and `cw app list/show/validate/init/package` discovery,
26
+ // live in shell/ (see shell/workflow-app-loader.ts), per the core/shell
27
+ // split.
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.WorkflowAppValidationError = exports.workflow = exports.slugify = exports.phase = exports.input = exports.createWorkflowApi = exports.artifact = exports.agent = void 0;
7
- exports.defineWorkflowApp = defineWorkflowApp;
29
+ exports.WorkflowAppValidationError = void 0;
30
+ exports.slugify = slugify;
31
+ exports.workflow = workflow;
32
+ exports.phase = phase;
33
+ exports.parallel = parallel;
34
+ exports.loop = loop;
35
+ exports.agent = agent;
36
+ exports.subWorkflow = subWorkflow;
37
+ exports.artifact = artifact;
38
+ exports.input = input;
39
+ exports.createWorkflowApi = createWorkflowApi;
40
+ exports.validateWorkflowDefinition = validateWorkflowDefinition;
8
41
  exports.validateWorkflowApp = validateWorkflowApp;
9
42
  exports.assertValidWorkflowApp = assertValidWorkflowApp;
10
- exports.validateWorkflowDefinition = validateWorkflowDefinition;
11
- exports.loadWorkflowAppFromEntrypoint = loadWorkflowAppFromEntrypoint;
12
- exports.loadWorkflowAppFromManifest = loadWorkflowAppFromManifest;
13
- exports.summarizeWorkflowApp = summarizeWorkflowApp;
43
+ exports.isWorkflowAppCompatible = isWorkflowAppCompatible;
44
+ exports.validationIssuesFromError = validationIssuesFromError;
14
45
  exports.workflowAppRunMetadata = workflowAppRunMetadata;
15
- exports.createLegacyWorkflowApp = createLegacyWorkflowApp;
16
- exports.renderWorkflowAppTemplate = renderWorkflowAppTemplate;
17
- exports.renderWorkflowAppManifestTemplate = renderWorkflowAppManifestTemplate;
18
- exports.renderWorkflowAppEntrypointTemplate = renderWorkflowAppEntrypointTemplate;
19
- const node_fs_1 = __importDefault(require("node:fs"));
20
- const node_path_1 = __importDefault(require("node:path"));
21
- const workflow_api_1 = require("./workflow-api");
22
- Object.defineProperty(exports, "agent", { enumerable: true, get: function () { return workflow_api_1.agent; } });
23
- Object.defineProperty(exports, "artifact", { enumerable: true, get: function () { return workflow_api_1.artifact; } });
24
- Object.defineProperty(exports, "createWorkflowApi", { enumerable: true, get: function () { return workflow_api_1.createWorkflowApi; } });
25
- Object.defineProperty(exports, "input", { enumerable: true, get: function () { return workflow_api_1.input; } });
26
- Object.defineProperty(exports, "phase", { enumerable: true, get: function () { return workflow_api_1.phase; } });
27
- Object.defineProperty(exports, "slugify", { enumerable: true, get: function () { return workflow_api_1.slugify; } });
28
- Object.defineProperty(exports, "workflow", { enumerable: true, get: function () { return workflow_api_1.workflow; } });
29
- const sandbox_profile_1 = require("./sandbox-profile");
30
- const version_1 = require("./version");
46
+ function slugify(value) {
47
+ return String(value)
48
+ .trim()
49
+ .toLowerCase()
50
+ .replace(/[^a-z0-9]+/g, "-")
51
+ .replace(/^-|-$/g, "")
52
+ .replace(/-{2,}/g, "-");
53
+ }
54
+ function workflow(definition) {
55
+ if (!definition.id)
56
+ throw new Error("workflow.id is required");
57
+ if (!definition.title)
58
+ throw new Error("workflow.title is required");
59
+ if (!Array.isArray(definition.phases))
60
+ throw new Error("workflow.phases must be an array");
61
+ return {
62
+ limits: { maxAgents: 20, maxConcurrentAgents: 4, ...(definition.limits || {}) },
63
+ inputs: [],
64
+ summary: "",
65
+ ...definition,
66
+ };
67
+ }
68
+ function phase(name, tasks, options = {}) {
69
+ if (!name)
70
+ throw new Error("phase name is required");
71
+ if (!Array.isArray(tasks))
72
+ throw new Error(`phase ${name} tasks must be an array`);
73
+ return { id: slugify(name), name, status: "pending", tasks, ...options };
74
+ }
75
+ function parallel(name, tasks, options = {}) {
76
+ return phase(name, tasks, { mode: "parallel", ...options });
77
+ }
78
+ /** A BOUNDED DYNAMIC LOOP phase: `tasks` are a per-round template. After each
79
+ * round completes, the registered `until` predicate decides whether to run
80
+ * another round (a fresh appended phase with the same tasks, round-suffixed
81
+ * ids) or stop; capped at `maxRounds`. Sugar over phase() that sets `loop`;
82
+ * plain phases are unaffected. Byte-exact to src/workflow-api.ts's loop(). */
83
+ function loop(name, tasks, spec, options = {}) {
84
+ if (!spec || typeof spec.maxRounds !== "number" || spec.maxRounds < 1) {
85
+ throw new Error(`loop ${name} requires a positive integer maxRounds`);
86
+ }
87
+ const until = spec.until;
88
+ const valid = until &&
89
+ ((until.kind === "predicate" && Boolean(until.ref)) ||
90
+ (until.kind === "budget-target" && typeof until.target === "number" && until.target > 0));
91
+ if (!valid) {
92
+ throw new Error(`loop ${name} requires until: { kind: "predicate", ref } or { kind: "budget-target", target }`);
93
+ }
94
+ return phase(name, tasks, { loop: { maxRounds: Math.floor(spec.maxRounds), until }, ...options });
95
+ }
96
+ function task(kind, id, prompt, options) {
97
+ if (!id)
98
+ throw new Error(`${kind} task id is required`);
99
+ if (!prompt)
100
+ throw new Error(`${kind} task ${id} prompt is required`);
101
+ return {
102
+ id,
103
+ kind,
104
+ prompt,
105
+ status: "pending",
106
+ sandboxProfileId: typeof options.sandboxProfileId === "string" ? options.sandboxProfileId : undefined,
107
+ ...options,
108
+ };
109
+ }
110
+ function agent(id, prompt, options = {}) {
111
+ return task("agent", id, prompt, options);
112
+ }
113
+ /** A task fulfilled by an inline SUB-WORKFLOW: instead of spawning an agent,
114
+ * the drive plans + drives the child `appId` and binds its report back as
115
+ * this task's result. The prompt is recorded for provenance but is not sent
116
+ * to an agent. Byte-exact to src/workflow-api.ts's subWorkflow(). */
117
+ function subWorkflow(id, appId, options = {}) {
118
+ if (!appId)
119
+ throw new Error(`subWorkflow task ${id} requires an appId`);
120
+ const { inputs, bindResult, prompt, ...rest } = options;
121
+ return task("agent", id, prompt || `Delegate to sub-workflow app: ${appId}`, {
122
+ ...rest,
123
+ subWorkflow: { appId, ...(inputs ? { inputs } : {}), ...(bindResult ? { bindResult } : {}) },
124
+ });
125
+ }
126
+ function artifact(id, prompt, options = {}) {
127
+ return task("artifact", id, prompt, options);
128
+ }
129
+ function input(name, options = {}) {
130
+ if (!name)
131
+ throw new Error("input name is required");
132
+ return { name, ...options };
133
+ }
134
+ function createWorkflowApi() {
135
+ return { workflow, phase, parallel, loop, agent, artifact, subWorkflow, input };
136
+ }
31
137
  class WorkflowAppValidationError extends Error {
32
138
  issues;
33
139
  constructor(message, issues) {
34
- super(`${message}: ${issues.map((issue) => issue.message).join("; ")}`);
140
+ super(`${message}: ${issues.map((one) => one.message).join("; ")}`);
35
141
  this.name = "WorkflowAppValidationError";
36
142
  this.issues = issues;
37
143
  }
38
144
  }
39
145
  exports.WorkflowAppValidationError = WorkflowAppValidationError;
40
- function defineWorkflowApp(definition) {
41
- assertValidWorkflowApp(definition, { appPath: "inline" });
42
- return definition;
43
- }
44
- function validateWorkflowApp(candidate, options = {}) {
45
- const issues = [];
46
- const appPath = options.appPath;
47
- if (!isRecord(candidate)) {
48
- return {
49
- valid: false,
50
- appPath,
51
- issues: [issue("workflow-app-invalid", "Workflow app must be an object", appPath)]
52
- };
53
- }
54
- const app = candidate;
55
- if (app.schemaVersion !== version_1.WORKFLOW_APP_SCHEMA_VERSION) {
56
- issues.push(issue("workflow-app-schema-version", `Workflow app schemaVersion must be ${version_1.WORKFLOW_APP_SCHEMA_VERSION}`, joinPath(appPath, "schemaVersion")));
57
- }
58
- validateAppId(app.id, issues, joinPath(appPath, "id"));
59
- if (!isNonEmptyString(app.title)) {
60
- issues.push(issue("workflow-app-title", "Workflow app title is required", joinPath(appPath, "title")));
61
- }
62
- if (!isSemver(app.version)) {
63
- issues.push(issue("workflow-app-version", "Workflow app version must be a semver string such as 0.1.0", joinPath(appPath, "version")));
64
- }
65
- validateAuthor(app.author, issues, joinPath(appPath, "author"));
66
- validateInputDefinitions(app.inputs, issues, joinPath(appPath, "inputs"), { optional: true });
67
- validateSandboxProfileReferences(app.sandboxProfiles, issues, joinPath(appPath, "sandboxProfiles"), { optional: true });
68
- validateCompatibility(app.compatibility, issues, joinPath(appPath, "compatibility"));
69
- const workflowValue = options.loadedWorkflow || app.workflow;
70
- if (!workflowValue) {
71
- issues.push(issue("workflow-app-workflow", "Workflow app workflow is required", joinPath(appPath, "workflow")));
72
- }
73
- else if (isWorkflowEntrypoint(workflowValue)) {
74
- validateEntrypoint(workflowValue, issues, joinPath(appPath, "workflow"));
75
- }
76
- else {
77
- validateWorkflowDefinition(workflowValue, issues, joinPath(appPath, "workflow"), {
78
- appId: isNonEmptyString(app.id) ? app.id : undefined,
79
- appTitle: isNonEmptyString(app.title) ? app.title : undefined,
80
- appSandboxProfiles: app.sandboxProfiles
81
- });
82
- if (Array.isArray(app.inputs) && isWorkflowDefinition(workflowValue)) {
83
- validateMatchingInputs(app.inputs, workflowValue.inputs || [], issues, joinPath(appPath, "inputs"));
84
- }
85
- }
86
- return {
87
- valid: issues.length === 0,
88
- appId: isNonEmptyString(app.id) ? app.id : undefined,
89
- appPath,
90
- issues
91
- };
92
- }
93
- function assertValidWorkflowApp(candidate, options = {}) {
94
- const result = validateWorkflowApp(candidate, options);
95
- if (!result.valid) {
96
- throw new WorkflowAppValidationError("Invalid workflow app", result.issues);
97
- }
146
+ function issue(code, message, pathName) {
147
+ return { code, message, path: pathName };
98
148
  }
99
- function validateWorkflowDefinition(candidate, issues = [], basePath = "workflow", options = {}) {
100
- if (!isRecord(candidate)) {
101
- issues.push(issue("workflow-invalid", "Workflow definition must be an object", basePath));
102
- return issues;
103
- }
104
- const workflowDefinition = candidate;
105
- validateWorkflowId(workflowDefinition.id, issues, joinPath(basePath, "id"));
106
- if (!isNonEmptyString(workflowDefinition.title)) {
107
- issues.push(issue("workflow-title", "Workflow title is required", joinPath(basePath, "title")));
108
- }
109
- if (options.appId && workflowDefinition.id !== options.appId) {
110
- issues.push(issue("workflow-app-id-mismatch", `Workflow id must match app id ${options.appId}`, joinPath(basePath, "id")));
111
- }
112
- if (options.appTitle && workflowDefinition.title !== options.appTitle) {
113
- issues.push(issue("workflow-app-title-mismatch", `Workflow title must match app title ${options.appTitle}`, joinPath(basePath, "title")));
114
- }
115
- validateLimits(workflowDefinition.limits, issues, joinPath(basePath, "limits"));
116
- validateInputDefinitions(workflowDefinition.inputs, issues, joinPath(basePath, "inputs"), { optional: false });
117
- validateSandboxProfileReferences(workflowDefinition.sandboxProfiles, issues, joinPath(basePath, "sandboxProfiles"), { optional: true });
118
- validatePhases(workflowDefinition.phases, workflowDefinition.limits, issues, joinPath(basePath, "phases"), {
119
- appSandboxProfiles: options.appSandboxProfiles || workflowDefinition.sandboxProfiles
120
- });
121
- return issues;
149
+ function joinPath(basePath, segment) {
150
+ if (!basePath)
151
+ return segment;
152
+ return `${basePath}.${segment}`;
122
153
  }
123
- function loadWorkflowAppFromEntrypoint(file, options = {}) {
124
- const entrypointPath = node_path_1.default.resolve(file);
125
- const materialized = materializeModuleExport(entrypointPath, options.exportName);
126
- const loaded = extractWorkflowAppExport(materialized, entrypointPath);
127
- const source = {
128
- kind: options.sourceKind || "workflow-file",
129
- path: entrypointPath,
130
- entrypointPath
131
- };
132
- if (loaded.app) {
133
- const app = {
134
- ...loaded.app,
135
- workflow: loaded.workflow
136
- };
137
- assertValidWorkflowApp(app, { appPath: entrypointPath });
138
- return { app, source, legacy: false };
139
- }
140
- const app = createLegacyWorkflowApp(loaded.workflow, source);
141
- assertValidWorkflowApp(app, { appPath: entrypointPath });
142
- return { app, source, legacy: true };
143
- }
144
- function loadWorkflowAppFromManifest(manifestPath) {
145
- const absoluteManifestPath = node_path_1.default.resolve(manifestPath);
146
- if (!node_fs_1.default.existsSync(absoluteManifestPath)) {
147
- throw new WorkflowAppValidationError("Invalid workflow app manifest", [
148
- issue("workflow-app-manifest-not-found", `Workflow app manifest does not exist: ${absoluteManifestPath}`, absoluteManifestPath)
149
- ]);
150
- }
151
- let manifest;
152
- try {
153
- manifest = JSON.parse(node_fs_1.default.readFileSync(absoluteManifestPath, "utf8"));
154
- }
155
- catch (error) {
156
- throw new WorkflowAppValidationError("Invalid workflow app manifest", [
157
- issue("workflow-app-manifest-json", `Workflow app manifest is not valid JSON: ${messageOf(error)}`, absoluteManifestPath)
158
- ]);
159
- }
160
- const manifestValidation = validateWorkflowApp(manifest, { appPath: absoluteManifestPath });
161
- if (!manifestValidation.valid) {
162
- throw new WorkflowAppValidationError("Invalid workflow app manifest", manifestValidation.issues);
163
- }
164
- if (!isWorkflowEntrypoint(manifest.workflow)) {
165
- throw new WorkflowAppValidationError("Invalid workflow app manifest", [
166
- issue("workflow-app-entrypoint", "Manifest workflow must be an entrypoint object", joinPath(absoluteManifestPath, "workflow"))
167
- ]);
168
- }
169
- const entrypointPath = node_path_1.default.resolve(node_path_1.default.dirname(absoluteManifestPath), manifest.workflow.entrypoint);
170
- const materialized = materializeModuleExport(entrypointPath, manifest.workflow.exportName);
171
- const loaded = extractWorkflowAppExport(materialized, entrypointPath);
172
- const workflowDefinition = loaded.workflow;
173
- const source = {
174
- kind: node_path_1.default.basename(absoluteManifestPath) === "app.json" ? "app-directory" : "app-manifest",
175
- path: node_path_1.default.dirname(absoluteManifestPath),
176
- manifestPath: absoluteManifestPath,
177
- entrypointPath
178
- };
179
- if (loaded.app) {
180
- validateEntrypointAppMatchesManifest(loaded.app, manifest, absoluteManifestPath);
181
- }
182
- const app = {
183
- ...manifest,
184
- workflow: workflowDefinition
185
- };
186
- assertValidWorkflowApp(app, {
187
- appPath: absoluteManifestPath,
188
- loadedWorkflow: workflowDefinition
189
- });
190
- return { app, source, legacy: false };
191
- }
192
- function summarizeWorkflowApp(record) {
193
- const workflowDefinition = record.app.workflow;
194
- const sandboxProfiles = record.app.sandboxProfiles || workflowDefinition.sandboxProfiles || [];
195
- const summary = {
196
- id: record.app.id,
197
- title: record.app.title,
198
- summary: record.app.summary || workflowDefinition.summary || "",
199
- version: record.app.version,
200
- author: record.app.author,
201
- file: record.source.entrypointPath || record.source.manifestPath || record.source.path,
202
- sourceKind: record.source.kind,
203
- legacy: record.legacy,
204
- compatible: isAppCompatible(record.app),
205
- inputs: record.app.inputs || workflowDefinition.inputs || [],
206
- sandboxProfiles,
207
- phases: workflowDefinition.phases.map((phaseDefinition) => ({
208
- id: phaseDefinition.id,
209
- name: phaseDefinition.name,
210
- taskCount: phaseDefinition.tasks.length
211
- })),
212
- taskCount: workflowDefinition.phases.reduce((total, phaseDefinition) => total + phaseDefinition.tasks.length, 0)
213
- };
214
- return summary;
154
+ function isRecord(value) {
155
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
215
156
  }
216
- function workflowAppRunMetadata(record) {
217
- return {
218
- schemaVersion: version_1.WORKFLOW_APP_SCHEMA_VERSION,
219
- id: record.app.id,
220
- title: record.app.title,
221
- summary: record.app.summary || record.app.workflow.summary || "",
222
- version: record.app.version,
223
- author: record.app.author,
224
- compatibility: record.app.compatibility,
225
- sandboxProfiles: record.app.sandboxProfiles || record.app.workflow.sandboxProfiles,
226
- source: record.source,
227
- metadata: record.app.metadata
228
- };
157
+ function isNonEmptyString(value) {
158
+ return typeof value === "string" && value.trim().length > 0;
229
159
  }
230
- function createLegacyWorkflowApp(workflowDefinition, source) {
231
- return {
232
- schemaVersion: version_1.WORKFLOW_APP_SCHEMA_VERSION,
233
- id: workflowDefinition.id,
234
- title: workflowDefinition.title,
235
- summary: workflowDefinition.summary || "",
236
- version: "0.0.0",
237
- workflow: workflowDefinition,
238
- inputs: workflowDefinition.inputs || [],
239
- sandboxProfiles: workflowDefinition.sandboxProfiles || collectWorkflowSandboxProfiles(workflowDefinition),
240
- compatibility: {
241
- maxVersion: version_1.CURRENT_COOL_WORKFLOW_VERSION,
242
- notes: "Compatibility wrapper for legacy .workflow.js factory files."
243
- },
244
- metadata: {
245
- legacyWorkflow: true,
246
- sourcePath: source.path
247
- }
248
- };
160
+ function isSemver(value) {
161
+ return typeof value === "string" && /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value);
249
162
  }
250
- function renderWorkflowAppTemplate(id, title) {
251
- return `const { defineWorkflowApp, workflow, phase, agent, artifact, input } = require("../dist/workflow-app-framework");\n\nconst inputs = [\n input("question", { required: true, description: "Question or task this workflow should answer." })\n];\n\nmodule.exports = defineWorkflowApp({\n schemaVersion: 1,\n id: ${JSON.stringify(id)},\n title: ${JSON.stringify(title)},\n summary: "Describe what this workflow app does.",\n version: "0.1.0",\n author: "COOLWHITE LLC",\n inputs,\n sandboxProfiles: ["readonly"],\n compatibility: {\n minVersion: "0.1.9"\n },\n workflow: workflow({\n id: ${JSON.stringify(id)},\n title: ${JSON.stringify(title)},\n summary: "Describe what this workflow app does.",\n limits: {\n maxAgents: 8,\n maxConcurrentAgents: 4\n },\n inputs,\n sandboxProfiles: ["readonly"],\n phases: [\n phase("Map", [\n agent("map:context", "Map the task context, constraints, and evidence needed for {{question}}.", { sandboxProfileId: "readonly" })\n ]),\n phase("Assess", [\n agent("assess:risks", "Assess risks, tradeoffs, and unknowns for {{question}}.", { sandboxProfileId: "readonly" })\n ]),\n phase("Synthesize", [\n artifact("synthesis:report", "Synthesize the final answer for {{question}}.", { requiresEvidence: true, sandboxProfileId: "readonly" })\n ])\n ]\n })\n});\n`;
163
+ function semverParts(value) {
164
+ const [major, minor, patch] = value
165
+ .split(/[+-]/)[0]
166
+ .split(".")
167
+ .map((part) => Number(part));
168
+ return [major || 0, minor || 0, patch || 0];
252
169
  }
253
- function renderWorkflowAppManifestTemplate(id, title) {
254
- return `${JSON.stringify({
255
- schemaVersion: version_1.WORKFLOW_APP_SCHEMA_VERSION,
256
- id,
257
- title,
258
- summary: "Describe what this workflow app does.",
259
- version: "0.1.0",
260
- author: "COOLWHITE LLC",
261
- inputs: [
262
- {
263
- name: "question",
264
- type: "string",
265
- required: true,
266
- description: "Question or task this workflow should answer."
267
- }
268
- ],
269
- sandboxProfiles: ["readonly"],
270
- compatibility: {
271
- minVersion: "0.1.9"
272
- },
273
- workflow: {
274
- entrypoint: "workflow.js"
275
- }
276
- }, null, 2)}\n`;
277
- }
278
- function renderWorkflowAppEntrypointTemplate(id, title) {
279
- return `module.exports = ({ workflow, phase, agent, artifact, input }) => {\n const inputs = [\n input("question", { type: "string", required: true, description: "Question or task this workflow should answer." })\n ];\n\n return workflow({\n id: ${JSON.stringify(id)},\n title: ${JSON.stringify(title)},\n summary: "Describe what this workflow app does.",\n limits: {\n maxAgents: 8,\n maxConcurrentAgents: 4\n },\n inputs,\n sandboxProfiles: ["readonly"],\n phases: [\n phase("Map", [\n agent("map:context", "Map the task context, constraints, and evidence needed for {{question}}.", { sandboxProfileId: "readonly" })\n ]),\n phase("Assess", [\n agent("assess:risks", "Assess risks, tradeoffs, and unknowns for {{question}}.", { sandboxProfileId: "readonly" })\n ]),\n phase("Synthesize", [\n artifact("synthesis:report", "Synthesize the final answer for {{question}}.", { requiresEvidence: true, sandboxProfileId: "readonly" })\n ])\n ]\n });\n};\n`;
280
- }
281
- function materializeModuleExport(file, exportName) {
282
- if (!node_fs_1.default.existsSync(file)) {
283
- throw new WorkflowAppValidationError("Invalid workflow app entrypoint", [
284
- issue("workflow-app-entrypoint-not-found", `Workflow app entrypoint does not exist: ${file}`, file)
285
- ]);
286
- }
287
- // Workflow apps are plain runtime JavaScript, not TypeScript.
288
- // eslint-disable-next-line @typescript-eslint/no-var-requires
289
- const rawExport = require(file);
290
- const selectedExport = exportName && isRecord(rawExport) ? rawExport[exportName] : rawExport;
291
- if (exportName && selectedExport === undefined) {
292
- throw new WorkflowAppValidationError("Invalid workflow app entrypoint", [
293
- issue("workflow-app-entrypoint-export", `Workflow app entrypoint does not export ${exportName}`, file)
294
- ]);
295
- }
296
- if (typeof selectedExport === "function") {
297
- return selectedExport({
298
- ...(0, workflow_api_1.createWorkflowApi)(),
299
- defineWorkflowApp
300
- });
170
+ function compareSemver(left, right) {
171
+ const leftParts = semverParts(left);
172
+ const rightParts = semverParts(right);
173
+ for (let index = 0; index < 3; index += 1) {
174
+ if (leftParts[index] !== rightParts[index])
175
+ return leftParts[index] < rightParts[index] ? -1 : 1;
301
176
  }
302
- return selectedExport;
177
+ return 0;
303
178
  }
304
- function extractWorkflowAppExport(value, file) {
305
- if (isWorkflowAppDefinition(value) && isWorkflowDefinition(value.workflow)) {
306
- return {
307
- app: value,
308
- workflow: value.workflow
309
- };
310
- }
311
- if (isWorkflowDefinition(value)) {
312
- return { workflow: value };
313
- }
314
- throw new WorkflowAppValidationError("Invalid workflow app entrypoint", [
315
- issue("workflow-app-entrypoint-export", "Workflow app entrypoint must export a workflow definition, workflow app, or factory", file)
316
- ]);
179
+ function isWorkflowDefinition(value) {
180
+ return (isRecord(value) &&
181
+ isNonEmptyString(value.id) &&
182
+ isNonEmptyString(value.title) &&
183
+ Array.isArray(value.phases) &&
184
+ isRecord(value.limits) &&
185
+ Array.isArray(value.inputs));
317
186
  }
318
- function validateEntrypointAppMatchesManifest(entrypointApp, manifest, manifestPath) {
319
- const issues = [];
320
- for (const key of ["schemaVersion", "id", "title", "version"]) {
321
- if (entrypointApp[key] !== manifest[key]) {
322
- issues.push(issue("workflow-app-manifest-mismatch", `Entrypoint app ${key} must match manifest ${key}`, joinPath(manifestPath, key)));
323
- }
324
- }
325
- if (issues.length)
326
- throw new WorkflowAppValidationError("Invalid workflow app manifest", issues);
187
+ function isWorkflowEntrypoint(value) {
188
+ return isRecord(value) && "entrypoint" in value && !("phases" in value);
327
189
  }
328
190
  function validateAppId(value, issues, pathName) {
329
191
  if (!isNonEmptyString(value)) {
@@ -409,7 +271,7 @@ function validateMatchingInputs(appInputs, workflowInputs, issues, pathName) {
409
271
  issues.push(issue("workflow-app-inputs-mismatch", "Workflow app inputs must match workflow.inputs when both are present", pathName));
410
272
  }
411
273
  }
412
- function validateSandboxProfileReferences(profiles, issues, pathName, options = { optional: false }) {
274
+ function validateSandboxProfileReferences(profiles, issues, pathName, bundledIds, options = { optional: false }) {
413
275
  if (profiles === undefined && options.optional)
414
276
  return;
415
277
  if (!Array.isArray(profiles)) {
@@ -417,7 +279,6 @@ function validateSandboxProfileReferences(profiles, issues, pathName, options =
417
279
  return;
418
280
  }
419
281
  const seen = new Set();
420
- const bundled = (0, sandbox_profile_1.bundledSandboxProfileIds)();
421
282
  for (const [index, value] of profiles.entries()) {
422
283
  const profilePath = joinPath(pathName, String(index));
423
284
  if (!isNonEmptyString(value)) {
@@ -428,20 +289,20 @@ function validateSandboxProfileReferences(profiles, issues, pathName, options =
428
289
  issues.push(issue("workflow-sandbox-profile-duplicate", `Duplicate sandbox profile reference: ${value}`, profilePath));
429
290
  }
430
291
  seen.add(value);
431
- if (!(0, sandbox_profile_1.isBundledSandboxProfileId)(value)) {
432
- issues.push(issue("workflow-sandbox-profile-unknown", `Unknown sandbox profile ${value}; bundled profiles: ${bundled.join(", ")}`, profilePath));
292
+ if (!bundledIds.includes(value)) {
293
+ issues.push(issue("workflow-sandbox-profile-unknown", `Unknown sandbox profile ${value}; bundled profiles: ${bundledIds.join(", ")}`, profilePath));
433
294
  }
434
295
  }
435
296
  }
436
- function validateCompatibility(value, issues, pathName) {
297
+ function validateCompatibility(value, issues, pathName, currentVersion) {
437
298
  if (value === undefined)
438
299
  return;
439
300
  if (!isRecord(value)) {
440
301
  issues.push(issue("workflow-app-compatibility", "Workflow app compatibility must be an object", pathName));
441
302
  return;
442
303
  }
443
- if (value.workflowSchemaVersion !== undefined && value.workflowSchemaVersion !== version_1.WORKFLOW_APP_SCHEMA_VERSION) {
444
- issues.push(issue("workflow-app-compatibility", `Workflow schema version must be ${version_1.WORKFLOW_APP_SCHEMA_VERSION}`, joinPath(pathName, "workflowSchemaVersion")));
304
+ if (value.workflowSchemaVersion !== undefined && value.workflowSchemaVersion !== 1) {
305
+ issues.push(issue("workflow-app-compatibility", "Workflow schema version must be 1", joinPath(pathName, "workflowSchemaVersion")));
445
306
  }
446
307
  for (const key of ["coolWorkflow", "node", "notes"]) {
447
308
  if (value[key] !== undefined && !isNonEmptyString(value[key])) {
@@ -452,16 +313,16 @@ function validateCompatibility(value, issues, pathName) {
452
313
  if (!isSemver(value.minVersion)) {
453
314
  issues.push(issue("workflow-app-compatibility", "Compatibility minVersion must be semver", joinPath(pathName, "minVersion")));
454
315
  }
455
- else if (compareSemver(version_1.CURRENT_COOL_WORKFLOW_VERSION, value.minVersion) < 0) {
456
- issues.push(issue("workflow-app-incompatible", `Workflow app requires Cool Workflow >= ${value.minVersion}; current is ${version_1.CURRENT_COOL_WORKFLOW_VERSION}`, joinPath(pathName, "minVersion")));
316
+ else if (compareSemver(currentVersion, value.minVersion) < 0) {
317
+ issues.push(issue("workflow-app-incompatible", `Workflow app requires Cool Workflow >= ${value.minVersion}; current is ${currentVersion}`, joinPath(pathName, "minVersion")));
457
318
  }
458
319
  }
459
320
  if (value.maxVersion !== undefined) {
460
321
  if (!isSemver(value.maxVersion)) {
461
322
  issues.push(issue("workflow-app-compatibility", "Compatibility maxVersion must be semver", joinPath(pathName, "maxVersion")));
462
323
  }
463
- else if (compareSemver(version_1.CURRENT_COOL_WORKFLOW_VERSION, value.maxVersion) > 0) {
464
- issues.push(issue("workflow-app-incompatible", `Workflow app supports Cool Workflow <= ${value.maxVersion}; current is ${version_1.CURRENT_COOL_WORKFLOW_VERSION}`, joinPath(pathName, "maxVersion")));
324
+ else if (compareSemver(currentVersion, value.maxVersion) > 0) {
325
+ issues.push(issue("workflow-app-incompatible", `Workflow app supports Cool Workflow <= ${value.maxVersion}; current is ${currentVersion}`, joinPath(pathName, "maxVersion")));
465
326
  }
466
327
  }
467
328
  }
@@ -469,7 +330,7 @@ function validateEntrypoint(value, issues, pathName) {
469
330
  if (!isNonEmptyString(value.entrypoint)) {
470
331
  issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.entrypoint is required", joinPath(pathName, "entrypoint")));
471
332
  }
472
- if (value.entrypoint && node_path_1.default.isAbsolute(value.entrypoint)) {
333
+ if (value.entrypoint && /^([a-zA-Z]:)?[/\\]/.test(value.entrypoint)) {
473
334
  issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.entrypoint must be relative", joinPath(pathName, "entrypoint")));
474
335
  }
475
336
  if (value.entrypoint && value.entrypoint.split(/[\\/]/).includes("..")) {
@@ -479,26 +340,41 @@ function validateEntrypoint(value, issues, pathName) {
479
340
  issues.push(issue("workflow-app-entrypoint", "Workflow app workflow.exportName must be a string", joinPath(pathName, "exportName")));
480
341
  }
481
342
  }
482
- function validatePhases(phases, limits, issues, pathName, options = {}) {
483
- if (!Array.isArray(phases) || !phases.length) {
484
- issues.push(issue("workflow-phases", "Workflow phases must be a non-empty array", pathName));
343
+ function validateTask(taskDefinition, issues, pathName, seenTaskIds, options) {
344
+ if (!isRecord(taskDefinition)) {
345
+ issues.push(issue("workflow-task", "Workflow task must be an object", pathName));
485
346
  return;
486
347
  }
487
- const seenPhaseIds = new Set();
488
- const seenTaskIds = new Set();
489
- let taskCount = 0;
490
- for (const [phaseIndex, phaseDefinition] of phases.entries()) {
491
- const phasePath = joinPath(pathName, String(phaseIndex));
492
- validatePhase(phaseDefinition, issues, phasePath, seenPhaseIds);
493
- if (!isRecord(phaseDefinition) || !Array.isArray(phaseDefinition.tasks))
494
- continue;
495
- for (const [taskIndex, taskDefinition] of phaseDefinition.tasks.entries()) {
496
- taskCount += 1;
497
- validateTask(taskDefinition, issues, joinPath(joinPath(phasePath, "tasks"), String(taskIndex)), seenTaskIds, options);
498
- }
348
+ const taskValue = taskDefinition;
349
+ if (!isNonEmptyString(taskValue.id) || !/^[A-Za-z0-9][A-Za-z0-9_.:/-]*$/.test(taskValue.id)) {
350
+ issues.push(issue("workflow-task-id", `Workflow task id is malformed: ${String(taskValue.id || "")}`, joinPath(pathName, "id")));
499
351
  }
500
- if (isRecord(limits) && Number.isInteger(Number(limits.maxAgents)) && taskCount > Number(limits.maxAgents)) {
501
- issues.push(issue("workflow-limits", `Workflow defines ${taskCount} tasks but limits.maxAgents is ${String(limits.maxAgents)}`, joinPath(pathName, "limits.maxAgents")));
352
+ else if (seenTaskIds.has(taskValue.id)) {
353
+ issues.push(issue("workflow-task-duplicate", `Duplicate workflow task id: ${taskValue.id}`, joinPath(pathName, "id")));
354
+ }
355
+ else {
356
+ seenTaskIds.add(taskValue.id);
357
+ }
358
+ if (!["agent", "artifact"].includes(String(taskValue.kind))) {
359
+ issues.push(issue("workflow-task-kind", `Workflow task ${String(taskValue.id || "")} kind must be agent or artifact`, joinPath(pathName, "kind")));
360
+ }
361
+ if (!isNonEmptyString(taskValue.prompt)) {
362
+ issues.push(issue("workflow-task-prompt", `Workflow task ${String(taskValue.id || "")} prompt is required`, joinPath(pathName, "prompt")));
363
+ }
364
+ if (taskValue.requiresEvidence !== undefined && typeof taskValue.requiresEvidence !== "boolean") {
365
+ issues.push(issue("workflow-task-evidence", `Workflow task ${String(taskValue.id || "")} requiresEvidence must be boolean`, joinPath(pathName, "requiresEvidence")));
366
+ }
367
+ const sandboxProfileId = taskValue.sandboxProfileId;
368
+ if (sandboxProfileId !== undefined) {
369
+ if (!isNonEmptyString(sandboxProfileId)) {
370
+ issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandboxProfileId must be a string`, joinPath(pathName, "sandboxProfileId")));
371
+ }
372
+ else if (!options.bundledIds.includes(sandboxProfileId)) {
373
+ issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} references unknown sandbox profile ${sandboxProfileId}`, joinPath(pathName, "sandboxProfileId")));
374
+ }
375
+ else if (options.appSandboxProfiles && !options.appSandboxProfiles.includes(sandboxProfileId)) {
376
+ issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandbox profile ${sandboxProfileId} must be listed in app sandboxProfiles`, joinPath(pathName, "sandboxProfileId")));
377
+ }
502
378
  }
503
379
  }
504
380
  function validatePhase(phaseDefinition, issues, pathName, seenPhaseIds) {
@@ -522,125 +398,160 @@ function validatePhase(phaseDefinition, issues, pathName, seenPhaseIds) {
522
398
  if (!Array.isArray(phaseValue.tasks) || !phaseValue.tasks.length) {
523
399
  issues.push(issue("workflow-phase-tasks", `Workflow phase ${String(phaseValue.id || phaseValue.name || "")} must have tasks`, joinPath(pathName, "tasks")));
524
400
  }
525
- // Bounded dynamic loop spec (fail-closed shape check; the predicate ref need not be
526
- // registered at validation time — the expander stops fail-closed if it is missing).
527
401
  if (phaseValue.loop !== undefined) {
528
- const loop = phaseValue.loop;
529
- if (!isRecord(loop)) {
402
+ const loopValue = phaseValue.loop;
403
+ if (!isRecord(loopValue)) {
530
404
  issues.push(issue("workflow-phase-loop", "Workflow phase loop must be an object", joinPath(pathName, "loop")));
531
405
  }
532
406
  else {
533
- if (typeof loop.maxRounds !== "number" || !Number.isInteger(loop.maxRounds) || loop.maxRounds < 1) {
407
+ if (typeof loopValue.maxRounds !== "number" || !Number.isInteger(loopValue.maxRounds) || loopValue.maxRounds < 1) {
534
408
  issues.push(issue("workflow-phase-loop-maxrounds", "loop.maxRounds must be a positive integer", joinPath(pathName, "loop.maxRounds")));
535
409
  }
536
- const until = loop.until;
537
- const validUntil = isRecord(until)
538
- && ((until.kind === "predicate" && isNonEmptyString(until.ref))
539
- || (until.kind === "budget-target" && typeof until.target === "number" && until.target > 0));
410
+ const until = loopValue.until;
411
+ const validUntil = isRecord(until) &&
412
+ ((until.kind === "predicate" && isNonEmptyString(until.ref)) ||
413
+ (until.kind === "budget-target" && typeof until.target === "number" && until.target > 0));
540
414
  if (!validUntil) {
541
415
  issues.push(issue("workflow-phase-loop-until", 'loop.until must be { kind: "predicate", ref } or { kind: "budget-target", target }', joinPath(pathName, "loop.until")));
542
416
  }
543
417
  }
544
418
  }
545
419
  }
546
- function validateTask(taskDefinition, issues, pathName, seenTaskIds, options) {
547
- if (!isRecord(taskDefinition)) {
548
- issues.push(issue("workflow-task", "Workflow task must be an object", pathName));
420
+ function validatePhases(phases, limits, issues, pathName, options) {
421
+ if (!Array.isArray(phases) || !phases.length) {
422
+ issues.push(issue("workflow-phases", "Workflow phases must be a non-empty array", pathName));
549
423
  return;
550
424
  }
551
- const taskValue = taskDefinition;
552
- if (!isNonEmptyString(taskValue.id) || !/^[A-Za-z0-9][A-Za-z0-9_.:/-]*$/.test(taskValue.id)) {
553
- issues.push(issue("workflow-task-id", `Workflow task id is malformed: ${String(taskValue.id || "")}`, joinPath(pathName, "id")));
554
- }
555
- else if (seenTaskIds.has(taskValue.id)) {
556
- issues.push(issue("workflow-task-duplicate", `Duplicate workflow task id: ${taskValue.id}`, joinPath(pathName, "id")));
425
+ const seenPhaseIds = new Set();
426
+ const seenTaskIds = new Set();
427
+ let taskCount = 0;
428
+ for (const [phaseIndex, phaseDefinition] of phases.entries()) {
429
+ const phasePath = joinPath(pathName, String(phaseIndex));
430
+ validatePhase(phaseDefinition, issues, phasePath, seenPhaseIds);
431
+ if (!isRecord(phaseDefinition) || !Array.isArray(phaseDefinition.tasks))
432
+ continue;
433
+ for (const [taskIndex, taskDefinition] of phaseDefinition.tasks.entries()) {
434
+ taskCount += 1;
435
+ validateTask(taskDefinition, issues, joinPath(joinPath(phasePath, "tasks"), String(taskIndex)), seenTaskIds, options);
436
+ }
557
437
  }
558
- else {
559
- seenTaskIds.add(taskValue.id);
438
+ if (isRecord(limits) && Number.isInteger(Number(limits.maxAgents)) && taskCount > Number(limits.maxAgents)) {
439
+ issues.push(issue("workflow-limits", `Workflow defines ${taskCount} tasks but limits.maxAgents is ${String(limits.maxAgents)}`, joinPath(pathName, "limits.maxAgents")));
560
440
  }
561
- if (!["agent", "artifact"].includes(String(taskValue.kind))) {
562
- issues.push(issue("workflow-task-kind", `Workflow task ${String(taskValue.id || "")} kind must be agent or artifact`, joinPath(pathName, "kind")));
441
+ }
442
+ function validateWorkflowDefinition(candidate, context, issues = [], basePath = "workflow", options = {}) {
443
+ if (!isRecord(candidate)) {
444
+ issues.push(issue("workflow-invalid", "Workflow definition must be an object", basePath));
445
+ return issues;
563
446
  }
564
- if (!isNonEmptyString(taskValue.prompt)) {
565
- issues.push(issue("workflow-task-prompt", `Workflow task ${String(taskValue.id || "")} prompt is required`, joinPath(pathName, "prompt")));
447
+ const workflowDefinition = candidate;
448
+ validateWorkflowId(workflowDefinition.id, issues, joinPath(basePath, "id"));
449
+ if (!isNonEmptyString(workflowDefinition.title)) {
450
+ issues.push(issue("workflow-title", "Workflow title is required", joinPath(basePath, "title")));
566
451
  }
567
- if (taskValue.requiresEvidence !== undefined && typeof taskValue.requiresEvidence !== "boolean") {
568
- issues.push(issue("workflow-task-evidence", `Workflow task ${String(taskValue.id || "")} requiresEvidence must be boolean`, joinPath(pathName, "requiresEvidence")));
452
+ if (options.appId && workflowDefinition.id !== options.appId) {
453
+ issues.push(issue("workflow-app-id-mismatch", `Workflow id must match app id ${options.appId}`, joinPath(basePath, "id")));
569
454
  }
570
- const sandboxProfileId = taskValue.sandboxProfileId;
571
- if (sandboxProfileId !== undefined) {
572
- if (!isNonEmptyString(sandboxProfileId)) {
573
- issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandboxProfileId must be a string`, joinPath(pathName, "sandboxProfileId")));
574
- }
575
- else if (!(0, sandbox_profile_1.isBundledSandboxProfileId)(sandboxProfileId)) {
576
- issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} references unknown sandbox profile ${sandboxProfileId}`, joinPath(pathName, "sandboxProfileId")));
577
- }
578
- else if (options.appSandboxProfiles && !options.appSandboxProfiles.includes(sandboxProfileId)) {
579
- issues.push(issue("workflow-task-sandbox-profile", `Workflow task ${String(taskValue.id || "")} sandbox profile ${sandboxProfileId} must be listed in app sandboxProfiles`, joinPath(pathName, "sandboxProfileId")));
580
- }
455
+ if (options.appTitle && workflowDefinition.title !== options.appTitle) {
456
+ issues.push(issue("workflow-app-title-mismatch", `Workflow title must match app title ${options.appTitle}`, joinPath(basePath, "title")));
581
457
  }
458
+ validateLimits(workflowDefinition.limits, issues, joinPath(basePath, "limits"));
459
+ validateInputDefinitions(workflowDefinition.inputs, issues, joinPath(basePath, "inputs"), { optional: false });
460
+ validateSandboxProfileReferences(workflowDefinition.sandboxProfiles, issues, joinPath(basePath, "sandboxProfiles"), context.bundledSandboxProfileIds, {
461
+ optional: true,
462
+ });
463
+ validatePhases(workflowDefinition.phases, workflowDefinition.limits, issues, joinPath(basePath, "phases"), {
464
+ appSandboxProfiles: options.appSandboxProfiles || workflowDefinition.sandboxProfiles,
465
+ bundledIds: context.bundledSandboxProfileIds,
466
+ });
467
+ return issues;
582
468
  }
583
- function isWorkflowDefinition(value) {
584
- return (isRecord(value) &&
585
- isNonEmptyString(value.id) &&
586
- isNonEmptyString(value.title) &&
587
- Array.isArray(value.phases) &&
588
- isRecord(value.limits) &&
589
- Array.isArray(value.inputs));
590
- }
591
- function isWorkflowAppDefinition(value) {
592
- return isRecord(value) && value.schemaVersion === version_1.WORKFLOW_APP_SCHEMA_VERSION && isNonEmptyString(value.id) && "workflow" in value;
593
- }
594
- function isWorkflowEntrypoint(value) {
595
- return isRecord(value) && "entrypoint" in value && !("phases" in value);
596
- }
597
- function isAppCompatible(app) {
598
- return !validateWorkflowApp(app).issues.some((compatIssue) => compatIssue.code === "workflow-app-incompatible");
599
- }
600
- function collectWorkflowSandboxProfiles(workflowDefinition) {
601
- const profiles = new Set();
602
- for (const phaseDefinition of workflowDefinition.phases) {
603
- for (const taskDefinition of phaseDefinition.tasks) {
604
- if (taskDefinition.sandboxProfileId)
605
- profiles.add(taskDefinition.sandboxProfileId);
469
+ /** The full `cw app validate` check: byte-exact issue codes/messages to
470
+ * src/workflow-app-framework.ts's `validateWorkflowApp`. Never throws. */
471
+ function validateWorkflowApp(candidate, context, options = {}) {
472
+ const issues = [];
473
+ const appPath = options.appPath;
474
+ if (!isRecord(candidate)) {
475
+ return { valid: false, appPath, issues: [issue("workflow-app-invalid", "Workflow app must be an object", appPath)] };
476
+ }
477
+ const app = candidate;
478
+ if (app.schemaVersion !== 1) {
479
+ issues.push(issue("workflow-app-schema-version", "Workflow app schemaVersion must be 1", joinPath(appPath, "schemaVersion")));
480
+ }
481
+ validateAppId(app.id, issues, joinPath(appPath, "id"));
482
+ if (!isNonEmptyString(app.title)) {
483
+ issues.push(issue("workflow-app-title", "Workflow app title is required", joinPath(appPath, "title")));
484
+ }
485
+ if (!isSemver(app.version)) {
486
+ issues.push(issue("workflow-app-version", "Workflow app version must be a semver string such as 0.1.0", joinPath(appPath, "version")));
487
+ }
488
+ validateAuthor(app.author, issues, joinPath(appPath, "author"));
489
+ validateInputDefinitions(app.inputs, issues, joinPath(appPath, "inputs"), { optional: true });
490
+ validateSandboxProfileReferences(app.sandboxProfiles, issues, joinPath(appPath, "sandboxProfiles"), context.bundledSandboxProfileIds, { optional: true });
491
+ validateCompatibility(app.compatibility, issues, joinPath(appPath, "compatibility"), context.currentCoolWorkflowVersion);
492
+ const workflowValue = options.loadedWorkflow || app.workflow;
493
+ if (!workflowValue) {
494
+ issues.push(issue("workflow-app-workflow", "Workflow app workflow is required", joinPath(appPath, "workflow")));
495
+ }
496
+ else if (isWorkflowEntrypoint(workflowValue)) {
497
+ validateEntrypoint(workflowValue, issues, joinPath(appPath, "workflow"));
498
+ }
499
+ else {
500
+ validateWorkflowDefinition(workflowValue, context, issues, joinPath(appPath, "workflow"), {
501
+ appId: isNonEmptyString(app.id) ? app.id : undefined,
502
+ appTitle: isNonEmptyString(app.title) ? app.title : undefined,
503
+ appSandboxProfiles: app.sandboxProfiles,
504
+ });
505
+ if (Array.isArray(app.inputs) && isWorkflowDefinition(workflowValue)) {
506
+ validateMatchingInputs(app.inputs, workflowValue.inputs || [], issues, joinPath(appPath, "inputs"));
606
507
  }
607
508
  }
608
- return [...profiles].sort();
509
+ return {
510
+ valid: issues.length === 0,
511
+ appId: isNonEmptyString(app.id) ? app.id : undefined,
512
+ appPath,
513
+ issues,
514
+ };
609
515
  }
610
- function compareSemver(left, right) {
611
- const leftParts = semverParts(left);
612
- const rightParts = semverParts(right);
613
- for (let index = 0; index < 3; index += 1) {
614
- if (leftParts[index] !== rightParts[index])
615
- return leftParts[index] < rightParts[index] ? -1 : 1;
516
+ function assertValidWorkflowApp(candidate, context, options = {}) {
517
+ const result = validateWorkflowApp(candidate, context, options);
518
+ if (!result.valid) {
519
+ throw new WorkflowAppValidationError("Invalid workflow app", result.issues);
616
520
  }
617
- return 0;
618
521
  }
619
- function semverParts(value) {
620
- const [major, minor, patch] = value.split(/[+-]/)[0].split(".").map((part) => Number(part));
621
- return [major || 0, minor || 0, patch || 0];
622
- }
623
- function isSemver(value) {
624
- return typeof value === "string" && /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(value);
625
- }
626
- function isNonEmptyString(value) {
627
- return typeof value === "string" && value.trim().length > 0;
628
- }
629
- function isRecord(value) {
630
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
631
- }
632
- function issue(code, message, pathName) {
522
+ /** `isAppCompatible` (WorkflowAppSummary.compatible): re-validate and check
523
+ * ONLY for a `workflow-app-incompatible` issue — see PLAN.md rebuild risk
524
+ * #3. */
525
+ function isWorkflowAppCompatible(app, context) {
526
+ return !validateWorkflowApp(app, context).issues.some((one) => one.code === "workflow-app-incompatible");
527
+ }
528
+ /** `validationIssuesFromError` — turns any thrown error (typically a
529
+ * `WorkflowAppValidationError`) into an issues array for the
530
+ * `{valid:false, issues}` catch-branch shape `cw app validate` needs on
531
+ * an unresolvable target. */
532
+ function validationIssuesFromError(error) {
533
+ if (error instanceof WorkflowAppValidationError)
534
+ return error.issues;
535
+ return [{ code: "workflow-app-invalid", message: error instanceof Error ? error.message : String(error) }];
536
+ }
537
+ /** Byte-exact render of the workflow-app run-metadata block that lands in
538
+ * `run.workflow.app` (report.md's "Workflow App:" line reads
539
+ * `run.workflow.app.id`/`.version`). */
540
+ function workflowAppRunMetadata(app) {
633
541
  return {
634
- code,
635
- message,
636
- path: pathName
542
+ schemaVersion: 1,
543
+ id: app.id,
544
+ title: app.title,
545
+ summary: app.summary,
546
+ version: app.version,
547
+ author: app.author,
548
+ // compatibility + metadata ride into run.workflow.app so report.md can
549
+ // render the domain-gated "- Source:" label (metadata.domain ===
550
+ // "research") and downstream tools can read the app's compatibility
551
+ // window. Byte-behavior port of the old build's workflowAppRunMetadata.
552
+ compatibility: app.compatibility,
553
+ sandboxProfiles: app.sandboxProfiles,
554
+ source: { manifestPath: app.sourcePath },
555
+ metadata: app.metadata,
637
556
  };
638
557
  }
639
- function joinPath(basePath, segment) {
640
- if (!basePath)
641
- return segment;
642
- return `${basePath}.${segment}`;
643
- }
644
- function messageOf(error) {
645
- return error instanceof Error ? error.message : String(error);
646
- }