cool-workflow 0.1.98 → 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 (306) 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} +2 -2
  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 +2 -0
  139. package/docs/cli-mcp-parity.7.md +280 -219
  140. package/docs/contract-migration-tooling.7.md +2 -0
  141. package/docs/control-plane-scheduling.7.md +2 -0
  142. package/docs/durable-state-and-locking.7.md +2 -0
  143. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  144. package/docs/execution-backends.7.md +2 -0
  145. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  146. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  147. package/docs/multi-agent-operator-ux.7.md +2 -0
  148. package/docs/node-snapshot-diff-replay.7.md +2 -0
  149. package/docs/observability-cost-accounting.7.md +2 -0
  150. package/docs/project-index.md +132 -71
  151. package/docs/real-execution-backends.7.md +2 -0
  152. package/docs/release-and-migration.7.md +2 -0
  153. package/docs/release-tooling.7.md +2 -0
  154. package/docs/run-registry-control-plane.7.md +2 -0
  155. package/docs/run-retention-reclamation.7.md +23 -0
  156. package/docs/state-explosion-management.7.md +2 -0
  157. package/docs/team-collaboration.7.md +2 -0
  158. package/docs/web-desktop-workbench.7.md +2 -0
  159. package/manifest/plugin.manifest.json +1 -1
  160. package/manifest/source-context-profiles.json +9 -13
  161. package/package.json +1 -1
  162. package/scripts/agents/cw-attest-wrap.js +2 -2
  163. package/scripts/bump-version.js +4 -3
  164. package/scripts/canonical-apps.js +4 -4
  165. package/scripts/dogfood-architecture-review.js +2 -3
  166. package/scripts/dogfood-release.js +3 -3
  167. package/scripts/gen-parity-doc.js +15 -2
  168. package/scripts/golden-path.js +4 -4
  169. package/scripts/onramp-check.js +1 -1
  170. package/scripts/parity-check.js +38 -21
  171. package/scripts/release-flow.js +2 -2
  172. package/scripts/sync-project-index.js +51 -27
  173. package/scripts/validate-run-state-schema.js +2 -2
  174. package/scripts/version-sync-check.js +30 -30
  175. package/dist/candidate-scoring.js +0 -729
  176. package/dist/capability-core.js +0 -1189
  177. package/dist/capability-registry.js +0 -885
  178. package/dist/cli/command-surface.js +0 -494
  179. package/dist/cli/format.js +0 -56
  180. package/dist/cli/handlers/audit.js +0 -82
  181. package/dist/cli/handlers/blackboard.js +0 -81
  182. package/dist/cli/handlers/candidate.js +0 -40
  183. package/dist/cli/handlers/clones.js +0 -34
  184. package/dist/cli/handlers/collaboration.js +0 -61
  185. package/dist/cli/handlers/eval.js +0 -40
  186. package/dist/cli/handlers/ledger.js +0 -169
  187. package/dist/cli/handlers/maintenance.js +0 -107
  188. package/dist/cli/handlers/multi-agent.js +0 -165
  189. package/dist/cli/handlers/node.js +0 -41
  190. package/dist/cli/handlers/operational.js +0 -155
  191. package/dist/cli/handlers/operator.js +0 -146
  192. package/dist/cli/handlers/registry.js +0 -68
  193. package/dist/cli/handlers/run.js +0 -153
  194. package/dist/cli/handlers/scheduling.js +0 -132
  195. package/dist/cli/handlers/workbench.js +0 -41
  196. package/dist/cli/handlers/worker.js +0 -45
  197. package/dist/cli/run-summary.js +0 -45
  198. package/dist/clones.js +0 -162
  199. package/dist/collaboration.js +0 -726
  200. package/dist/commit.js +0 -592
  201. package/dist/compare.js +0 -18
  202. package/dist/coordinator/classify.js +0 -45
  203. package/dist/coordinator/paths.js +0 -42
  204. package/dist/coordinator/util.js +0 -126
  205. package/dist/coordinator.js +0 -990
  206. package/dist/daemon.js +0 -44
  207. package/dist/dispatch.js +0 -250
  208. package/dist/drive.js +0 -864
  209. package/dist/error-feedback.js +0 -419
  210. package/dist/evidence-grounding.js +0 -184
  211. package/dist/execution-backend/agent.js +0 -354
  212. package/dist/execution-backend/probes.js +0 -112
  213. package/dist/execution-backend/util.js +0 -47
  214. package/dist/execution-backend.js +0 -961
  215. package/dist/gates.js +0 -48
  216. package/dist/harness.js +0 -61
  217. package/dist/ledger.js +0 -313
  218. package/dist/loop-expansion.js +0 -60
  219. package/dist/mcp/tool-call.js +0 -470
  220. package/dist/mcp/tool-definitions.js +0 -1066
  221. package/dist/mcp-surface.js +0 -30
  222. package/dist/multi-agent/graph.js +0 -84
  223. package/dist/multi-agent/helpers.js +0 -141
  224. package/dist/multi-agent/ids.js +0 -20
  225. package/dist/multi-agent/paths.js +0 -22
  226. package/dist/multi-agent-eval/normalize.js +0 -51
  227. package/dist/multi-agent-eval.js +0 -678
  228. package/dist/multi-agent-host.js +0 -777
  229. package/dist/multi-agent.js +0 -984
  230. package/dist/node-projection.js +0 -59
  231. package/dist/node-snapshot.js +0 -260
  232. package/dist/operator-ux.js +0 -631
  233. package/dist/orchestrator/app-operations.js +0 -211
  234. package/dist/orchestrator/audit-operations.js +0 -182
  235. package/dist/orchestrator/candidate-operations.js +0 -117
  236. package/dist/orchestrator/cli-options.js +0 -294
  237. package/dist/orchestrator/collaboration-operations.js +0 -86
  238. package/dist/orchestrator/feedback-operations.js +0 -81
  239. package/dist/orchestrator/host-operations.js +0 -78
  240. package/dist/orchestrator/lifecycle-operations.js +0 -650
  241. package/dist/orchestrator/migration-operations.js +0 -44
  242. package/dist/orchestrator/multi-agent-operations.js +0 -362
  243. package/dist/orchestrator/topology-operations.js +0 -84
  244. package/dist/orchestrator.js +0 -925
  245. package/dist/pipeline-runner.js +0 -285
  246. package/dist/reclamation/hash.js +0 -72
  247. package/dist/reclamation.js +0 -812
  248. package/dist/reporter.js +0 -67
  249. package/dist/run-export.js +0 -815
  250. package/dist/run-registry/derive.js +0 -175
  251. package/dist/run-registry/format.js +0 -124
  252. package/dist/run-registry/gc.js +0 -251
  253. package/dist/run-registry/policy.js +0 -16
  254. package/dist/run-registry/queue.js +0 -115
  255. package/dist/run-registry.js +0 -850
  256. package/dist/run-state-schema.js +0 -68
  257. package/dist/scheduling.js +0 -184
  258. package/dist/state-explosion.js +0 -1014
  259. package/dist/state.js +0 -367
  260. package/dist/telemetry-ledger.js +0 -196
  261. package/dist/topology.js +0 -565
  262. package/dist/triggers.js +0 -184
  263. package/dist/trust-audit.js +0 -644
  264. package/dist/types/blackboard.js +0 -2
  265. package/dist/types/candidate.js +0 -2
  266. package/dist/types/collaboration.js +0 -2
  267. package/dist/types/core.js +0 -2
  268. package/dist/types/drive.js +0 -10
  269. package/dist/types/error-feedback.js +0 -2
  270. package/dist/types/evidence-reasoning.js +0 -2
  271. package/dist/types/execution-backend.js +0 -2
  272. package/dist/types/multi-agent.js +0 -2
  273. package/dist/types/observability.js +0 -2
  274. package/dist/types/pipeline.js +0 -2
  275. package/dist/types/reclamation.js +0 -8
  276. package/dist/types/report-bundle.js +0 -6
  277. package/dist/types/result.js +0 -2
  278. package/dist/types/run-registry.js +0 -2
  279. package/dist/types/run.js +0 -2
  280. package/dist/types/sandbox.js +0 -2
  281. package/dist/types/schedule.js +0 -2
  282. package/dist/types/state-node.js +0 -2
  283. package/dist/types/topology.js +0 -2
  284. package/dist/types/trust.js +0 -2
  285. package/dist/types/workbench.js +0 -2
  286. package/dist/types/worker.js +0 -2
  287. package/dist/types/workflow-app.js +0 -2
  288. package/dist/types.js +0 -44
  289. package/dist/util/fingerprint.js +0 -19
  290. package/dist/util/fingerprint.test.js +0 -27
  291. package/dist/verifier.js +0 -78
  292. package/dist/version.js +0 -8
  293. package/dist/workbench-host.js +0 -192
  294. package/dist/workbench.js +0 -192
  295. package/dist/worker-accept/acceptance.js +0 -114
  296. package/dist/worker-accept/blackboard-fanout.js +0 -80
  297. package/dist/worker-accept/blackboard-linkage.js +0 -19
  298. package/dist/worker-accept/context.js +0 -2
  299. package/dist/worker-accept/telemetry-ledger.js +0 -126
  300. package/dist/worker-accept/validation.js +0 -77
  301. package/dist/worker-accept/verifier-completion.js +0 -73
  302. package/dist/worker-isolation/helpers.js +0 -51
  303. package/dist/worker-isolation/paths.js +0 -46
  304. package/dist/worker-isolation.js +0 -656
  305. package/dist/workflow-api.js +0 -131
  306. /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
- }