cool-workflow 0.1.78

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 (193) hide show
  1. package/.claude-plugin/plugin.json +20 -0
  2. package/.codex-plugin/mcp.json +10 -0
  3. package/.codex-plugin/plugin.json +38 -0
  4. package/.mcp.json +10 -0
  5. package/LICENSE +24 -0
  6. package/README.md +638 -0
  7. package/apps/architecture-review/app.json +51 -0
  8. package/apps/architecture-review/workflow.js +116 -0
  9. package/apps/end-to-end-golden-path/app.json +30 -0
  10. package/apps/end-to-end-golden-path/workflow.js +33 -0
  11. package/apps/pr-review-fix-ci/app.json +59 -0
  12. package/apps/pr-review-fix-ci/workflow.js +90 -0
  13. package/apps/release-cut/app.json +54 -0
  14. package/apps/release-cut/workflow.js +82 -0
  15. package/apps/research-synthesis/app.json +50 -0
  16. package/apps/research-synthesis/workflow.js +76 -0
  17. package/apps/workflow-app-framework-demo/app.json +29 -0
  18. package/apps/workflow-app-framework-demo/workflow.js +44 -0
  19. package/dist/agent-config.js +223 -0
  20. package/dist/candidate-scoring.js +715 -0
  21. package/dist/capability-core.js +630 -0
  22. package/dist/capability-dispatcher.js +86 -0
  23. package/dist/capability-registry.js +523 -0
  24. package/dist/cli.js +1276 -0
  25. package/dist/collaboration.js +727 -0
  26. package/dist/commit.js +570 -0
  27. package/dist/contract-migration.js +234 -0
  28. package/dist/coordinator.js +1163 -0
  29. package/dist/daemon.js +44 -0
  30. package/dist/dispatch.js +201 -0
  31. package/dist/drive.js +503 -0
  32. package/dist/error-feedback.js +415 -0
  33. package/dist/evidence-grounding.js +179 -0
  34. package/dist/evidence-reasoning.js +733 -0
  35. package/dist/execution-backend.js +1279 -0
  36. package/dist/harness.js +61 -0
  37. package/dist/mcp-server.js +1615 -0
  38. package/dist/multi-agent-eval.js +857 -0
  39. package/dist/multi-agent-host.js +764 -0
  40. package/dist/multi-agent-operator-ux.js +537 -0
  41. package/dist/multi-agent-trust.js +366 -0
  42. package/dist/multi-agent.js +1173 -0
  43. package/dist/node-snapshot.js +270 -0
  44. package/dist/observability.js +922 -0
  45. package/dist/operator-ux.js +971 -0
  46. package/dist/orchestrator/audit-operations.js +182 -0
  47. package/dist/orchestrator/candidate-operations.js +117 -0
  48. package/dist/orchestrator/cli-options.js +288 -0
  49. package/dist/orchestrator/collaboration-operations.js +86 -0
  50. package/dist/orchestrator/feedback-operations.js +81 -0
  51. package/dist/orchestrator/host-operations.js +78 -0
  52. package/dist/orchestrator/lifecycle-operations.js +462 -0
  53. package/dist/orchestrator/migration-operations.js +44 -0
  54. package/dist/orchestrator/multi-agent-operations.js +362 -0
  55. package/dist/orchestrator/report.js +369 -0
  56. package/dist/orchestrator/topology-operations.js +84 -0
  57. package/dist/orchestrator.js +874 -0
  58. package/dist/pipeline-contract.js +92 -0
  59. package/dist/pipeline-runner.js +285 -0
  60. package/dist/reclamation.js +882 -0
  61. package/dist/result-normalize.js +194 -0
  62. package/dist/run-export.js +64 -0
  63. package/dist/run-registry.js +1347 -0
  64. package/dist/run-state-schema.js +67 -0
  65. package/dist/sandbox-profile.js +471 -0
  66. package/dist/scheduler.js +266 -0
  67. package/dist/scheduling.js +184 -0
  68. package/dist/schema-validate.js +98 -0
  69. package/dist/state-explosion.js +1213 -0
  70. package/dist/state-migrations.js +463 -0
  71. package/dist/state-node.js +301 -0
  72. package/dist/state.js +308 -0
  73. package/dist/telemetry-attestation.js +156 -0
  74. package/dist/telemetry-ledger.js +145 -0
  75. package/dist/topology.js +527 -0
  76. package/dist/triggers.js +159 -0
  77. package/dist/trust-audit.js +475 -0
  78. package/dist/types/blackboard.js +2 -0
  79. package/dist/types/boundary.js +29 -0
  80. package/dist/types/candidate.js +2 -0
  81. package/dist/types/collaboration.js +2 -0
  82. package/dist/types/core.js +2 -0
  83. package/dist/types/drive.js +10 -0
  84. package/dist/types/error-feedback.js +2 -0
  85. package/dist/types/evidence-reasoning.js +2 -0
  86. package/dist/types/execution-backend.js +2 -0
  87. package/dist/types/multi-agent.js +2 -0
  88. package/dist/types/observability.js +2 -0
  89. package/dist/types/pipeline.js +2 -0
  90. package/dist/types/reclamation.js +8 -0
  91. package/dist/types/result.js +2 -0
  92. package/dist/types/run-registry.js +2 -0
  93. package/dist/types/run.js +2 -0
  94. package/dist/types/sandbox.js +2 -0
  95. package/dist/types/schedule.js +2 -0
  96. package/dist/types/state-node.js +2 -0
  97. package/dist/types/topology.js +2 -0
  98. package/dist/types/trust.js +2 -0
  99. package/dist/types/workbench.js +2 -0
  100. package/dist/types/worker.js +2 -0
  101. package/dist/types/workflow-app.js +2 -0
  102. package/dist/types.js +43 -0
  103. package/dist/verifier-registry.js +46 -0
  104. package/dist/verifier.js +78 -0
  105. package/dist/version.js +8 -0
  106. package/dist/workbench-host.js +172 -0
  107. package/dist/workbench.js +190 -0
  108. package/dist/worker-isolation.js +1028 -0
  109. package/dist/workflow-api.js +98 -0
  110. package/dist/workflow-app-framework.js +626 -0
  111. package/docs/agent-delegation-drive.7.md +190 -0
  112. package/docs/agent-framework.md +176 -0
  113. package/docs/candidate-scoring.7.md +106 -0
  114. package/docs/canonical-workflow-apps.7.md +137 -0
  115. package/docs/capability-topology-registry.7.md +168 -0
  116. package/docs/cli-mcp-parity.7.md +373 -0
  117. package/docs/contract-migration-tooling.7.md +123 -0
  118. package/docs/control-plane-scheduling.7.md +110 -0
  119. package/docs/coordinator-blackboard.7.md +183 -0
  120. package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
  121. package/docs/dogfood-one-real-repo.7.md +168 -0
  122. package/docs/durable-state-and-locking.7.md +107 -0
  123. package/docs/end-to-end-golden-path.7.md +117 -0
  124. package/docs/error-feedback.7.md +153 -0
  125. package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
  126. package/docs/execution-backends.7.md +300 -0
  127. package/docs/getting-started.md +99 -0
  128. package/docs/index.md +41 -0
  129. package/docs/mcp-app-surface.7.md +235 -0
  130. package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
  131. package/docs/multi-agent-eval-replay-harness.7.md +302 -0
  132. package/docs/multi-agent-operator-ux.7.md +314 -0
  133. package/docs/multi-agent-runtime-core.7.md +231 -0
  134. package/docs/multi-agent-topologies.7.md +103 -0
  135. package/docs/multi-agent-trust-policy-audit.7.md +154 -0
  136. package/docs/node-snapshot-diff-replay.7.md +135 -0
  137. package/docs/observability-cost-accounting.7.md +194 -0
  138. package/docs/operator-ux.7.md +180 -0
  139. package/docs/pipeline-runner.7.md +136 -0
  140. package/docs/project-index.md +261 -0
  141. package/docs/real-execution-backends.7.md +142 -0
  142. package/docs/release-and-migration.7.md +280 -0
  143. package/docs/release-tooling.7.md +159 -0
  144. package/docs/routines.md +48 -0
  145. package/docs/run-registry-control-plane.7.md +312 -0
  146. package/docs/run-retention-reclamation.7.md +191 -0
  147. package/docs/sandbox-profiles.7.md +137 -0
  148. package/docs/scheduled-tasks.md +80 -0
  149. package/docs/security-trust-hardening.7.md +117 -0
  150. package/docs/state-explosion-management.7.md +264 -0
  151. package/docs/state-node.7.md +96 -0
  152. package/docs/team-collaboration.7.md +207 -0
  153. package/docs/unix-principles.md +192 -0
  154. package/docs/verifier-gated-commit.7.md +140 -0
  155. package/docs/web-desktop-workbench.7.md +215 -0
  156. package/docs/worker-isolation.7.md +167 -0
  157. package/docs/workflow-app-framework.7.md +274 -0
  158. package/manifest/README.md +43 -0
  159. package/manifest/plugin.manifest.json +316 -0
  160. package/manifest/pricing.policy.json +14 -0
  161. package/package.json +79 -0
  162. package/scripts/agents/claude-p-agent.js +104 -0
  163. package/scripts/agents/claude-p-agent.sh +9 -0
  164. package/scripts/agents/cw-attest-keygen.js +55 -0
  165. package/scripts/agents/cw-attest-wrap.js +143 -0
  166. package/scripts/block-unapproved-tag.sh +39 -0
  167. package/scripts/bump-version.js +249 -0
  168. package/scripts/canonical-apps.js +171 -0
  169. package/scripts/cw.js +4 -0
  170. package/scripts/dist-drift-check.js +79 -0
  171. package/scripts/dogfood-architecture-review.js +237 -0
  172. package/scripts/dogfood-release.js +624 -0
  173. package/scripts/forward-ref-docs.js +73 -0
  174. package/scripts/gen-manifests.js +232 -0
  175. package/scripts/golden-path.js +300 -0
  176. package/scripts/mcp-server.js +4 -0
  177. package/scripts/new-feature.js +121 -0
  178. package/scripts/parity-check.js +213 -0
  179. package/scripts/release-check.js +118 -0
  180. package/scripts/release-flow.js +272 -0
  181. package/scripts/release-gate.sh +85 -0
  182. package/scripts/sync-project-index.js +387 -0
  183. package/scripts/validate-run-state-schema.js +126 -0
  184. package/scripts/verify-container-selfref.js +64 -0
  185. package/scripts/version-sync-check.js +237 -0
  186. package/skills/cool-workflow/SKILL.md +162 -0
  187. package/skills/cool-workflow/references/commands.md +282 -0
  188. package/tsconfig.json +16 -0
  189. package/ui/workbench/app.css +76 -0
  190. package/ui/workbench/app.js +159 -0
  191. package/ui/workbench/index.html +32 -0
  192. package/workflows/architecture-review.workflow.js +84 -0
  193. package/workflows/research-synthesis.workflow.js +47 -0
@@ -0,0 +1,301 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PipelineContractError = exports.PIPELINE_CONTRACT_SCHEMA_VERSION = exports.STATE_NODE_SCHEMA_VERSION = void 0;
7
+ exports.createStateNode = createStateNode;
8
+ exports.transitionStateNode = transitionStateNode;
9
+ exports.validatePipelineContract = validatePipelineContract;
10
+ exports.assertNodeSatisfiesContract = assertNodeSatisfiesContract;
11
+ exports.recordNodeError = recordNodeError;
12
+ exports.linkStateNodes = linkStateNodes;
13
+ exports.appendRunNode = appendRunNode;
14
+ exports.upsertRunContract = upsertRunContract;
15
+ exports.writeRunNode = writeRunNode;
16
+ exports.artifactExists = artifactExists;
17
+ const node_fs_1 = __importDefault(require("node:fs"));
18
+ const node_path_1 = __importDefault(require("node:path"));
19
+ const state_1 = require("./state");
20
+ exports.STATE_NODE_SCHEMA_VERSION = 1;
21
+ exports.PIPELINE_CONTRACT_SCHEMA_VERSION = 1;
22
+ class PipelineContractError extends Error {
23
+ structured;
24
+ constructor(error) {
25
+ super(error.message);
26
+ this.name = "PipelineContractError";
27
+ this.structured = {
28
+ ...error,
29
+ at: error.at || new Date().toISOString()
30
+ };
31
+ }
32
+ }
33
+ exports.PipelineContractError = PipelineContractError;
34
+ function createStateNode(input) {
35
+ const now = new Date().toISOString();
36
+ return {
37
+ schemaVersion: exports.STATE_NODE_SCHEMA_VERSION,
38
+ id: input.id || createNodeId(input.kind),
39
+ kind: input.kind,
40
+ status: input.status || "pending",
41
+ loopStage: input.loopStage,
42
+ createdAt: now,
43
+ updatedAt: now,
44
+ inputs: input.inputs || {},
45
+ outputs: input.outputs || {},
46
+ artifacts: input.artifacts || [],
47
+ evidence: input.evidence || [],
48
+ errors: input.errors || [],
49
+ parents: input.parents || [],
50
+ children: input.children || [],
51
+ contractId: input.contractId,
52
+ metadata: input.metadata
53
+ };
54
+ }
55
+ function transitionStateNode(node, input) {
56
+ if (!isLegalTransition(node.status, input.status)) {
57
+ throw contractError("illegal-transition", `State node ${node.id} cannot transition from ${node.status} to ${input.status}`, {
58
+ nodeId: node.id,
59
+ details: {
60
+ from: node.status,
61
+ to: input.status
62
+ }
63
+ });
64
+ }
65
+ if (input.status === "committed" && node.status !== "verified") {
66
+ throw contractError("commit-without-verifier", `State node ${node.id} cannot be committed before it is verified`, {
67
+ nodeId: node.id,
68
+ details: {
69
+ from: node.status,
70
+ to: input.status
71
+ }
72
+ });
73
+ }
74
+ return {
75
+ ...node,
76
+ status: input.status,
77
+ loopStage: input.loopStage || node.loopStage,
78
+ updatedAt: new Date().toISOString(),
79
+ outputs: input.outputs ? { ...node.outputs, ...input.outputs } : node.outputs,
80
+ artifacts: input.artifacts ? mergeById(node.artifacts, input.artifacts) : node.artifacts,
81
+ evidence: input.evidence ? mergeById(node.evidence, input.evidence) : node.evidence,
82
+ metadata: input.metadata ? { ...(node.metadata || {}), ...input.metadata } : node.metadata
83
+ };
84
+ }
85
+ function validatePipelineContract(contract) {
86
+ if (contract.schemaVersion !== exports.PIPELINE_CONTRACT_SCHEMA_VERSION) {
87
+ throw contractError("invalid-contract-schema", `Pipeline contract ${contract.id || "(missing id)"} has unsupported schemaVersion`, {
88
+ details: { schemaVersion: contract.schemaVersion }
89
+ });
90
+ }
91
+ if (!contract.id)
92
+ throw contractError("invalid-contract-id", "Pipeline contract id is required");
93
+ if (!contract.title)
94
+ throw contractError("invalid-contract-title", `Pipeline contract ${contract.id} title is required`);
95
+ if (!Array.isArray(contract.stages) || !contract.stages.length) {
96
+ throw contractError("invalid-contract-stages", `Pipeline contract ${contract.id} must include at least one stage`);
97
+ }
98
+ const seen = new Set();
99
+ for (const stage of contract.stages) {
100
+ validateStage(contract, stage, seen);
101
+ }
102
+ if (!contract.compatibility) {
103
+ throw contractError("invalid-contract-compatibility", `Pipeline contract ${contract.id} compatibility is required`);
104
+ }
105
+ if (contract.compatibility.minSchemaVersion > exports.STATE_NODE_SCHEMA_VERSION) {
106
+ throw contractError("incompatible-contract", `Pipeline contract ${contract.id} requires newer StateNode schema`, {
107
+ details: contract.compatibility
108
+ });
109
+ }
110
+ }
111
+ function assertNodeSatisfiesContract(node, contract, stageId) {
112
+ validatePipelineContract(contract);
113
+ const stage = contract.stages.find((candidate) => candidate.id === stageId);
114
+ if (!stage) {
115
+ throw contractError("unknown-contract-stage", `Pipeline contract ${contract.id} has no stage ${stageId}`, {
116
+ nodeId: node.id
117
+ });
118
+ }
119
+ if (!stage.acceptedInputKinds.includes(node.kind)) {
120
+ throw contractError("unexpected-node-kind", `Stage ${stage.id} does not accept node kind ${node.kind}`, {
121
+ nodeId: node.id,
122
+ details: { expected: stage.acceptedInputKinds, actual: node.kind }
123
+ });
124
+ }
125
+ if (!stage.acceptedInputStatuses.includes(node.status)) {
126
+ throw contractError("unexpected-node-status", `Stage ${stage.id} does not accept node status ${node.status}`, {
127
+ nodeId: node.id,
128
+ details: { expected: stage.acceptedInputStatuses, actual: node.status }
129
+ });
130
+ }
131
+ assertRequiredArtifacts(node, stage);
132
+ assertRequiredEvidence(node, stage, contract);
133
+ assertVerifierGate(node, stage, contract);
134
+ }
135
+ function recordNodeError(node, error) {
136
+ return {
137
+ ...node,
138
+ status: "failed",
139
+ updatedAt: new Date().toISOString(),
140
+ errors: [
141
+ ...node.errors,
142
+ {
143
+ ...error,
144
+ at: error.at || new Date().toISOString(),
145
+ nodeId: error.nodeId || node.id
146
+ }
147
+ ]
148
+ };
149
+ }
150
+ function linkStateNodes(parent, child) {
151
+ return [
152
+ {
153
+ ...parent,
154
+ updatedAt: new Date().toISOString(),
155
+ children: unique([...parent.children, child.id])
156
+ },
157
+ {
158
+ ...child,
159
+ updatedAt: new Date().toISOString(),
160
+ parents: unique([...child.parents, parent.id])
161
+ }
162
+ ];
163
+ }
164
+ function appendRunNode(run, node) {
165
+ const nodes = run.nodes || [];
166
+ const index = nodes.findIndex((candidate) => candidate.id === node.id);
167
+ const nextNodes = index >= 0 ? nodes.map((candidate) => (candidate.id === node.id ? node : candidate)) : [...nodes, node];
168
+ run.nodes = nextNodes;
169
+ writeRunNode(run, node);
170
+ return node;
171
+ }
172
+ function upsertRunContract(run, contract) {
173
+ validatePipelineContract(contract);
174
+ const contracts = run.contracts || [];
175
+ const index = contracts.findIndex((candidate) => candidate.id === contract.id);
176
+ run.contracts =
177
+ index >= 0 ? contracts.map((candidate) => (candidate.id === contract.id ? contract : candidate)) : [...contracts, contract];
178
+ return contract;
179
+ }
180
+ function writeRunNode(run, node) {
181
+ const dir = run.paths.stateNodesDir || node_path_1.default.join(run.paths.runDir, "nodes");
182
+ const file = node_path_1.default.join(dir, `${(0, state_1.safeFileName)(node.id)}.json`);
183
+ (0, state_1.writeJson)(file, node);
184
+ return file;
185
+ }
186
+ function artifactExists(artifact) {
187
+ return Boolean(artifact.path && node_fs_1.default.existsSync(artifact.path));
188
+ }
189
+ function validateStage(contract, stage, seen) {
190
+ if (!stage.id)
191
+ throw contractError("invalid-contract-stage-id", `Pipeline contract ${contract.id} has a stage without id`);
192
+ if (seen.has(stage.id))
193
+ throw contractError("duplicate-contract-stage", `Pipeline contract ${contract.id} repeats stage ${stage.id}`);
194
+ seen.add(stage.id);
195
+ if (!stage.name)
196
+ throw contractError("invalid-contract-stage-name", `Stage ${stage.id} name is required`);
197
+ if (!Array.isArray(stage.acceptedInputKinds) || !stage.acceptedInputKinds.length) {
198
+ throw contractError("invalid-contract-stage-kinds", `Stage ${stage.id} must accept at least one input kind`);
199
+ }
200
+ if (!Array.isArray(stage.acceptedInputStatuses) || !stage.acceptedInputStatuses.length) {
201
+ throw contractError("invalid-contract-stage-statuses", `Stage ${stage.id} must accept at least one input status`);
202
+ }
203
+ if (!stage.producedOutputKind) {
204
+ throw contractError("invalid-contract-stage-output", `Stage ${stage.id} producedOutputKind is required`);
205
+ }
206
+ }
207
+ function assertRequiredArtifacts(node, stage) {
208
+ for (const required of stage.requiredArtifacts || []) {
209
+ const artifact = node.artifacts.find((candidate) => candidate.id === required || candidate.kind === required);
210
+ if (!artifact) {
211
+ throw contractError("missing-required-artifact", `Node ${node.id} is missing required artifact ${required}`, {
212
+ nodeId: node.id,
213
+ details: { requiredArtifact: required }
214
+ });
215
+ }
216
+ if (!artifactExists(artifact)) {
217
+ throw contractError("missing-artifact-path", `Node ${node.id} artifact ${artifact.id} path does not exist`, {
218
+ nodeId: node.id,
219
+ path: artifact.path,
220
+ details: { artifactId: artifact.id }
221
+ });
222
+ }
223
+ }
224
+ }
225
+ function assertRequiredEvidence(node, stage, contract) {
226
+ const requiredEvidence = stage.requiredEvidence || [];
227
+ const contractRequiresEvidence = Boolean(contract.evidencePolicy?.requireEvidence);
228
+ if ((requiredEvidence.length || contractRequiresEvidence) && !node.evidence.length) {
229
+ throw contractError("missing-required-evidence", `Node ${node.id} is missing required evidence`, {
230
+ nodeId: node.id,
231
+ details: { requiredEvidence }
232
+ });
233
+ }
234
+ for (const required of requiredEvidence) {
235
+ const evidence = node.evidence.find((candidate) => candidate.id === required || candidate.source === required);
236
+ if (!evidence) {
237
+ throw contractError("missing-required-evidence", `Node ${node.id} is missing required evidence ${required}`, {
238
+ nodeId: node.id,
239
+ details: { requiredEvidence: required }
240
+ });
241
+ }
242
+ }
243
+ }
244
+ function assertVerifierGate(node, stage, contract) {
245
+ const gate = stage.verifierGate;
246
+ const commitRequiresGate = contract.commitPolicy?.requiresVerifierGate && stage.producedOutputKind === "commit";
247
+ if (!gate?.required && !commitRequiresGate)
248
+ return;
249
+ const acceptedStatuses = gate?.acceptedStatuses || contract.commitPolicy?.acceptedVerifierStatuses || ["verified"];
250
+ if (!acceptedStatuses.includes(node.status)) {
251
+ throw contractError("verifier-gate-blocked", `Stage ${stage.id} requires verifier status ${acceptedStatuses.join(", ")}`, {
252
+ nodeId: node.id,
253
+ details: { actual: node.status, accepted: acceptedStatuses }
254
+ });
255
+ }
256
+ if ((gate?.requiredEvidence || contract.evidencePolicy?.requireEvidence) && !node.evidence.length) {
257
+ throw contractError("verifier-gate-missing-evidence", `Stage ${stage.id} requires evidence before commit`, {
258
+ nodeId: node.id
259
+ });
260
+ }
261
+ }
262
+ function isLegalTransition(from, to) {
263
+ if (from === to)
264
+ return true;
265
+ const allowed = {
266
+ pending: ["running", "blocked", "failed", "completed", "verified", "rejected"],
267
+ running: ["completed", "failed", "blocked"],
268
+ completed: ["verified", "rejected", "failed"],
269
+ failed: ["pending", "blocked"],
270
+ blocked: ["pending", "failed"],
271
+ verified: ["committed", "rejected"],
272
+ rejected: ["pending", "failed"],
273
+ committed: []
274
+ };
275
+ return allowed[from].includes(to);
276
+ }
277
+ function contractError(code, message, options = {}) {
278
+ return new PipelineContractError({
279
+ code,
280
+ message,
281
+ ...options
282
+ });
283
+ }
284
+ function createNodeId(kind) {
285
+ const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\..+/, "Z");
286
+ return `${kind}-${stamp}-${Math.random().toString(36).slice(2, 8)}`;
287
+ }
288
+ function mergeById(existing, next) {
289
+ const values = [...existing];
290
+ for (const item of next) {
291
+ const index = values.findIndex((candidate) => candidate.id === item.id);
292
+ if (index >= 0)
293
+ values[index] = item;
294
+ else
295
+ values.push(item);
296
+ }
297
+ return values;
298
+ }
299
+ function unique(values) {
300
+ return [...new Set(values)];
301
+ }
package/dist/state.js ADDED
@@ -0,0 +1,308 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CURRENT_RUN_STATE_SCHEMA_VERSION = void 0;
7
+ exports.createRunPaths = createRunPaths;
8
+ exports.ensureRunDirs = ensureRunDirs;
9
+ exports.loadRunFromCwd = loadRunFromCwd;
10
+ exports.loadRunStateFile = loadRunStateFile;
11
+ exports.checkRunStateFile = checkRunStateFile;
12
+ exports.migrateRunStateFile = migrateRunStateFile;
13
+ exports.saveCheckpoint = saveCheckpoint;
14
+ exports.compactCheckpoint = compactCheckpoint;
15
+ exports.readJson = readJson;
16
+ exports.writeJson = writeJson;
17
+ exports.durableAppendFileSync = durableAppendFileSync;
18
+ exports.realResolve = realResolve;
19
+ exports.isContainedPath = isContainedPath;
20
+ exports.withFileLock = withFileLock;
21
+ exports.safeFileName = safeFileName;
22
+ exports.hashArtifactFile = hashArtifactFile;
23
+ const node_fs_1 = __importDefault(require("node:fs"));
24
+ const node_path_1 = __importDefault(require("node:path"));
25
+ const state_migrations_1 = require("./state-migrations");
26
+ const version_1 = require("./version");
27
+ Object.defineProperty(exports, "CURRENT_RUN_STATE_SCHEMA_VERSION", { enumerable: true, get: function () { return version_1.CURRENT_RUN_STATE_SCHEMA_VERSION; } });
28
+ const execution_backend_1 = require("./execution-backend");
29
+ function createRunPaths(runDir) {
30
+ return {
31
+ runDir,
32
+ state: node_path_1.default.join(runDir, "state.json"),
33
+ report: node_path_1.default.join(runDir, "report.md"),
34
+ tasksDir: node_path_1.default.join(runDir, "tasks"),
35
+ resultsDir: node_path_1.default.join(runDir, "results"),
36
+ dispatchesDir: node_path_1.default.join(runDir, "dispatches"),
37
+ artifactsDir: node_path_1.default.join(runDir, "artifacts"),
38
+ commitsDir: node_path_1.default.join(runDir, "commits"),
39
+ stateNodesDir: node_path_1.default.join(runDir, "nodes"),
40
+ feedbackDir: node_path_1.default.join(runDir, "feedback"),
41
+ auditDir: node_path_1.default.join(runDir, "audit"),
42
+ workersDir: node_path_1.default.join(runDir, "workers"),
43
+ candidatesDir: node_path_1.default.join(runDir, "candidates"),
44
+ multiAgentDir: node_path_1.default.join(runDir, "multi-agent"),
45
+ blackboardDir: node_path_1.default.join(runDir, "blackboard"),
46
+ topologiesDir: node_path_1.default.join(runDir, "topologies")
47
+ };
48
+ }
49
+ function ensureRunDirs(paths) {
50
+ for (const dir of [
51
+ paths.runDir,
52
+ paths.tasksDir,
53
+ paths.resultsDir,
54
+ paths.dispatchesDir,
55
+ paths.artifactsDir,
56
+ paths.commitsDir,
57
+ paths.stateNodesDir,
58
+ paths.feedbackDir,
59
+ paths.auditDir || node_path_1.default.join(paths.runDir, "audit"),
60
+ paths.workersDir || node_path_1.default.join(paths.runDir, "workers"),
61
+ paths.candidatesDir || node_path_1.default.join(paths.runDir, "candidates"),
62
+ paths.multiAgentDir || node_path_1.default.join(paths.runDir, "multi-agent"),
63
+ paths.blackboardDir || node_path_1.default.join(paths.runDir, "blackboard"),
64
+ paths.topologiesDir || node_path_1.default.join(paths.runDir, "topologies")
65
+ ]) {
66
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
67
+ }
68
+ }
69
+ function loadRunFromCwd(runId, cwd = process.cwd()) {
70
+ if (!runId)
71
+ throw new Error("Missing run id");
72
+ const statePath = node_path_1.default.join(cwd, ".cw", "runs", runId, "state.json");
73
+ const result = loadRunStateFile(statePath, { dryRun: true });
74
+ if (result.report.status === "unsupported") {
75
+ throw new Error(`Unsupported CW run state: ${result.report.errors.join("; ")}`);
76
+ }
77
+ return result.run;
78
+ }
79
+ function loadRunStateFile(statePath, options = {}) {
80
+ const result = (0, state_migrations_1.migrateRunState)(readJson(statePath), {
81
+ statePath,
82
+ dryRun: options.dryRun === undefined ? true : options.dryRun
83
+ });
84
+ if (result.report.status === "unsupported")
85
+ return result;
86
+ return result;
87
+ }
88
+ function checkRunStateFile(statePath) {
89
+ return loadRunStateFile(statePath, { dryRun: true });
90
+ }
91
+ function migrateRunStateFile(statePath, options = {}) {
92
+ const result = loadRunStateFile(statePath, { dryRun: !options.write });
93
+ if (result.report.status !== "unsupported" && options.write && result.report.writeRequired) {
94
+ writeJson(statePath, result.run);
95
+ }
96
+ return result;
97
+ }
98
+ function saveCheckpoint(run) {
99
+ run.updatedAt = new Date().toISOString();
100
+ // state.json is the single source of truth — write it DURABLY (v0.1.40).
101
+ writeJson(run.paths.state, run, { durable: true });
102
+ }
103
+ /** Compact a run checkpoint by stripping empty optional arrays and null values
104
+ * that don't carry semantic meaning (v0.1.60). The normalization layer
105
+ * (normalizeRunState) backfills these on load, so stripping them saves disk
106
+ * without losing information. Returns the number of keys stripped. */
107
+ function compactCheckpoint(run) {
108
+ const optionalArrays = [
109
+ "nodes", "contracts", "feedback", "workers", "sandboxProfiles",
110
+ "candidates", "candidateSelections"
111
+ ];
112
+ let stripped = 0;
113
+ const state = run;
114
+ for (const key of optionalArrays) {
115
+ if (Array.isArray(state[key]) && state[key].length === 0) {
116
+ delete state[key];
117
+ stripped++;
118
+ }
119
+ }
120
+ if (stripped > 0)
121
+ saveCheckpoint(run);
122
+ return stripped;
123
+ }
124
+ function readJson(file) {
125
+ if (!node_fs_1.default.existsSync(file))
126
+ throw new Error(`File not found: ${file}`);
127
+ try {
128
+ return JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
129
+ }
130
+ catch (error) {
131
+ const message = error instanceof Error ? error.message : String(error);
132
+ throw new Error(`Invalid JSON in ${file}: ${message}`);
133
+ }
134
+ }
135
+ // ---------------------------------------------------------------------------
136
+ // Atomic, optionally-durable JSON write (v0.1.40, closes the prior P1).
137
+ //
138
+ // ORDER IS THE SAFETY PROPERTY: write to a unique temp file, then rename over the
139
+ // target. `rename(2)` is atomic on POSIX, so a crash/`ENOSPC` mid-write can never
140
+ // leave a truncated `state.json` that throws `Invalid JSON` on reload — a reader
141
+ // always sees EITHER the old bytes OR the new bytes, never a torn file. With
142
+ // `{ durable: true }` we additionally fsync the file (and best-effort the dir)
143
+ // before/after the rename so the bytes survive power loss — used for AUTHORITATIVE
144
+ // state (state.json, registry overlays, the scheduler store, reclaimed.json). The
145
+ // fsync is skipped for high-frequency derived/rebuildable writes so the atomic
146
+ // rename (the actual torn-write fix) stays cheap everywhere.
147
+ // ---------------------------------------------------------------------------
148
+ let atomicWriteCounter = 0;
149
+ function writeJson(file, value, options = {}) {
150
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
151
+ const tmp = `${file}.tmp.${process.pid}.${atomicWriteCounter++}`;
152
+ const fd = node_fs_1.default.openSync(tmp, "w");
153
+ try {
154
+ node_fs_1.default.writeFileSync(fd, `${JSON.stringify(value, null, 2)}\n`, "utf8");
155
+ if (options.durable)
156
+ node_fs_1.default.fsyncSync(fd);
157
+ }
158
+ finally {
159
+ node_fs_1.default.closeSync(fd);
160
+ }
161
+ try {
162
+ node_fs_1.default.renameSync(tmp, file);
163
+ }
164
+ catch (error) {
165
+ try {
166
+ node_fs_1.default.rmSync(tmp, { force: true });
167
+ }
168
+ catch {
169
+ /* best-effort temp cleanup */
170
+ }
171
+ throw error;
172
+ }
173
+ if (options.durable) {
174
+ try {
175
+ const dirFd = node_fs_1.default.openSync(node_path_1.default.dirname(file), "r");
176
+ try {
177
+ node_fs_1.default.fsyncSync(dirFd);
178
+ }
179
+ finally {
180
+ node_fs_1.default.closeSync(dirFd);
181
+ }
182
+ }
183
+ catch {
184
+ /* directory fsync is best-effort (not supported on every platform) */
185
+ }
186
+ }
187
+ }
188
+ // ---------------------------------------------------------------------------
189
+ // Durable append (v0.1.40 self-audit P1) — append a line and fsync it before
190
+ // returning. The trust-audit event log is the ONE artifact whose loss breaks
191
+ // audit-completeness/non-repudiation, so unlike high-frequency derived writes it
192
+ // must survive power loss. `appendFileSync` alone does NOT fsync, so a crash
193
+ // after it returns could drop the most recent event while durable state.json
194
+ // advanced past it. We open O_APPEND, write, fsync the fd, then close.
195
+ // ---------------------------------------------------------------------------
196
+ function durableAppendFileSync(file, data) {
197
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
198
+ const fd = node_fs_1.default.openSync(file, "a");
199
+ try {
200
+ node_fs_1.default.writeFileSync(fd, data, "utf8");
201
+ node_fs_1.default.fsyncSync(fd);
202
+ }
203
+ finally {
204
+ node_fs_1.default.closeSync(fd);
205
+ }
206
+ }
207
+ // ---------------------------------------------------------------------------
208
+ // Symlink-hardened path containment (v0.1.40 self-audit P1) — `path.resolve()`
209
+ // only normalizes `.`/`..` textually; it does NOT follow symlinks, so a planted
210
+ // symlink whose textual path sits "inside" an allowed root but whose real target
211
+ // escapes it would pass a `startsWith` containment check. `realResolve` resolves
212
+ // the deepest EXISTING ancestor with `realpathSync` (which follows symlinks) and
213
+ // re-joins the not-yet-created remainder, so a not-yet-created file is still
214
+ // pinned to its real parent. `isContainedPath` realpaths BOTH sides so the
215
+ // comparison stays consistent on platforms where the temp root itself is a
216
+ // symlink (e.g. macOS /tmp -> /private/tmp).
217
+ // ---------------------------------------------------------------------------
218
+ function realResolve(target) {
219
+ let current = node_path_1.default.resolve(target);
220
+ const tail = [];
221
+ // Walk up to the deepest existing ancestor, realpath it, then re-append the tail.
222
+ for (;;) {
223
+ try {
224
+ const real = node_fs_1.default.realpathSync.native ? node_fs_1.default.realpathSync.native(current) : node_fs_1.default.realpathSync(current);
225
+ return tail.length ? node_path_1.default.join(real, ...tail.reverse()) : real;
226
+ }
227
+ catch {
228
+ const parent = node_path_1.default.dirname(current);
229
+ if (parent === current)
230
+ return node_path_1.default.resolve(target); // reached root; nothing existed
231
+ tail.push(node_path_1.default.basename(current));
232
+ current = parent;
233
+ }
234
+ }
235
+ }
236
+ function isContainedPath(candidate, allowed) {
237
+ const realCandidate = realResolve(candidate);
238
+ const realAllowed = realResolve(allowed);
239
+ return realCandidate === realAllowed || realCandidate.startsWith(realAllowed + node_path_1.default.sep);
240
+ }
241
+ // ---------------------------------------------------------------------------
242
+ // Portable advisory file lock (v0.1.40) — serialize cross-process read-modify-
243
+ // write on shared stores (home queue, scheduler store, archive overlay, the
244
+ // per-run reclamation chain) so a concurrent writer can never lose a record.
245
+ // O_EXCL (`wx`) is portable (no native flock); a stale holder is stolen so a
246
+ // crashed process can never wedge the store forever.
247
+ // ---------------------------------------------------------------------------
248
+ const FILE_LOCK_STALE_MS = 30_000;
249
+ function sleepSync(ms) {
250
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
251
+ }
252
+ /** Run `fn` while holding an advisory lock for `targetPath`; always released. */
253
+ function withFileLock(targetPath, fn) {
254
+ const lock = `${targetPath}.lock`;
255
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(lock), { recursive: true });
256
+ let acquired = false;
257
+ for (let attempt = 0; attempt < 240 && !acquired; attempt++) {
258
+ try {
259
+ const fd = node_fs_1.default.openSync(lock, "wx");
260
+ node_fs_1.default.writeFileSync(fd, `${process.pid}@${new Date().toISOString()}\n`, "utf8");
261
+ node_fs_1.default.closeSync(fd);
262
+ acquired = true;
263
+ }
264
+ catch (error) {
265
+ if (!(error && typeof error === "object" && error.code === "EEXIST"))
266
+ throw error;
267
+ try {
268
+ if (Date.now() - node_fs_1.default.statSync(lock).mtimeMs > FILE_LOCK_STALE_MS) {
269
+ node_fs_1.default.rmSync(lock, { force: true });
270
+ continue;
271
+ }
272
+ }
273
+ catch {
274
+ continue; // lock vanished between open and stat — retry immediately
275
+ }
276
+ sleepSync(25);
277
+ }
278
+ }
279
+ if (!acquired)
280
+ throw new Error(`could not acquire file lock for ${targetPath}`);
281
+ try {
282
+ return fn();
283
+ }
284
+ finally {
285
+ try {
286
+ node_fs_1.default.rmSync(lock, { force: true });
287
+ }
288
+ catch {
289
+ /* releasing a missing lock is fine */
290
+ }
291
+ }
292
+ }
293
+ function safeFileName(value) {
294
+ return String(value).replace(/[^a-zA-Z0-9_.:-]+/g, "_");
295
+ }
296
+ /** Compute and set SHA256 + sizeBytes on a StateArtifact from its file path
297
+ * (v0.1.73). Fails silently when the file doesn't exist — does not throw. */
298
+ function hashArtifactFile(artifact) {
299
+ try {
300
+ const content = node_fs_1.default.readFileSync(artifact.path, "utf8");
301
+ artifact.sha256 = (0, execution_backend_1.sha256)(content);
302
+ artifact.sizeBytes = Buffer.byteLength(content, "utf8");
303
+ }
304
+ catch {
305
+ /* file missing — silently skip */
306
+ }
307
+ return artifact;
308
+ }