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,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 (v2/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
- }