cool-workflow 0.1.79 → 0.1.81

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 (131) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +51 -3
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +64 -0
  6. package/apps/architecture-review-fast/workflow.js +153 -0
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/agent-config.js +21 -7
  12. package/dist/candidate-scoring.js +42 -22
  13. package/dist/capability-core.js +132 -17
  14. package/dist/capability-registry.js +138 -168
  15. package/dist/cli.js +97 -98
  16. package/dist/collaboration.js +5 -6
  17. package/dist/commit.js +20 -6
  18. package/dist/compare.js +18 -0
  19. package/dist/coordinator/classify.js +45 -0
  20. package/dist/coordinator/paths.js +42 -0
  21. package/dist/coordinator/util.js +129 -0
  22. package/dist/coordinator.js +127 -300
  23. package/dist/dispatch.js +35 -0
  24. package/dist/drive.js +79 -6
  25. package/dist/error-feedback.js +8 -4
  26. package/dist/evidence-reasoning.js +3 -3
  27. package/dist/execution-backend/agent.js +331 -0
  28. package/dist/execution-backend/probes.js +96 -0
  29. package/dist/execution-backend/util.js +47 -0
  30. package/dist/execution-backend.js +73 -421
  31. package/dist/mcp-server.js +79 -183
  32. package/dist/multi-agent/graph.js +84 -0
  33. package/dist/multi-agent/helpers.js +145 -0
  34. package/dist/multi-agent/paths.js +22 -0
  35. package/dist/multi-agent-eval/format.js +194 -0
  36. package/dist/multi-agent-eval/normalize.js +51 -0
  37. package/dist/multi-agent-eval.js +39 -244
  38. package/dist/multi-agent-host.js +0 -19
  39. package/dist/multi-agent.js +125 -314
  40. package/dist/node-snapshot.js +3 -3
  41. package/dist/observability/format.js +61 -0
  42. package/dist/observability/intake.js +98 -0
  43. package/dist/observability.js +14 -160
  44. package/dist/operator-ux/format.js +364 -0
  45. package/dist/operator-ux.js +22 -363
  46. package/dist/orchestrator/lifecycle-operations.js +2 -1
  47. package/dist/orchestrator/report.js +8 -0
  48. package/dist/orchestrator.js +26 -9
  49. package/dist/reclamation.js +26 -21
  50. package/dist/run-export.js +494 -25
  51. package/dist/run-registry/derive.js +172 -0
  52. package/dist/run-registry/format.js +124 -0
  53. package/dist/run-registry/gc.js +251 -0
  54. package/dist/run-registry/policy.js +16 -0
  55. package/dist/run-registry/queue.js +116 -0
  56. package/dist/run-registry.js +89 -597
  57. package/dist/run-state-schema.js +1 -0
  58. package/dist/sandbox-profile.js +43 -2
  59. package/dist/state-explosion/format.js +159 -0
  60. package/dist/state-explosion/helpers.js +82 -0
  61. package/dist/state-explosion.js +165 -304
  62. package/dist/state-node.js +19 -4
  63. package/dist/telemetry-attestation.js +55 -0
  64. package/dist/telemetry-demo.js +15 -3
  65. package/dist/telemetry-ledger.js +60 -15
  66. package/dist/topology.js +25 -8
  67. package/dist/triggers.js +33 -14
  68. package/dist/trust-audit.js +145 -33
  69. package/dist/version.js +1 -1
  70. package/dist/worker-isolation/helpers.js +51 -0
  71. package/dist/worker-isolation/paths.js +46 -0
  72. package/dist/worker-isolation.js +39 -115
  73. package/docs/agent-delegation-drive.7.md +71 -0
  74. package/docs/canonical-workflow-apps.7.md +37 -0
  75. package/docs/cli-mcp-parity.7.md +16 -0
  76. package/docs/contract-migration-tooling.7.md +6 -0
  77. package/docs/control-plane-scheduling.7.md +6 -0
  78. package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +40 -0
  79. package/docs/durable-state-and-locking.7.md +8 -0
  80. package/docs/evidence-adoption-reasoning-chain.7.md +6 -0
  81. package/docs/execution-backends.7.md +6 -0
  82. package/docs/index.md +2 -0
  83. package/docs/launch/demo.tape +28 -0
  84. package/docs/launch/launch-kit.md +96 -17
  85. package/docs/launch/pre-launch-checklist.md +53 -0
  86. package/docs/multi-agent-cli-mcp-surface.7.md +8 -0
  87. package/docs/multi-agent-eval-replay-harness.7.md +6 -0
  88. package/docs/multi-agent-operator-ux.7.md +6 -0
  89. package/docs/multi-agent-trust-policy-audit.7.md +27 -0
  90. package/docs/node-snapshot-diff-replay.7.md +6 -0
  91. package/docs/observability-cost-accounting.7.md +6 -0
  92. package/docs/project-index.md +27 -6
  93. package/docs/real-execution-backends.7.md +6 -0
  94. package/docs/release-and-migration.7.md +8 -0
  95. package/docs/release-tooling.7.md +6 -0
  96. package/docs/routines.md +23 -0
  97. package/docs/run-registry-control-plane.7.md +89 -2
  98. package/docs/run-retention-reclamation.7.md +8 -0
  99. package/docs/source-context-profiles.7.md +119 -0
  100. package/docs/state-explosion-management.7.md +13 -0
  101. package/docs/team-collaboration.7.md +6 -0
  102. package/docs/trust-model.md +267 -0
  103. package/docs/unix-principles.md +49 -1
  104. package/docs/vendor-manifest-loadability.7.md +43 -0
  105. package/docs/web-desktop-workbench.7.md +6 -0
  106. package/manifest/plugin.manifest.json +1 -1
  107. package/manifest/source-context-profiles.json +142 -0
  108. package/package.json +4 -1
  109. package/scripts/agents/builtin-templates.json +7 -0
  110. package/scripts/agents/claude-p-agent.js +129 -43
  111. package/scripts/architecture-review-fast.js +362 -0
  112. package/scripts/bump-version.js +5 -10
  113. package/scripts/canonical-apps-list.js +64 -0
  114. package/scripts/canonical-apps.js +36 -4
  115. package/scripts/coverage-gate.js +211 -0
  116. package/scripts/dogfood-release.js +1 -1
  117. package/scripts/golden-path.js +4 -4
  118. package/scripts/parity-check.js +5 -0
  119. package/scripts/release-check.js +5 -1
  120. package/scripts/source-context.js +291 -0
  121. package/scripts/version-sync-check.js +5 -7
  122. package/skills/ci-triage/SKILL.md +50 -0
  123. package/skills/ci-triage/agents/openai.yaml +4 -0
  124. package/skills/cool-workflow/SKILL.md +4 -1
  125. package/skills/deploy-check/SKILL.md +55 -0
  126. package/skills/deploy-check/agents/openai.yaml +4 -0
  127. package/skills/design-qa/SKILL.md +49 -0
  128. package/skills/design-qa/agents/openai.yaml +4 -0
  129. package/skills/pr-review/SKILL.md +45 -0
  130. package/skills/pr-review/agents/openai.yaml +4 -0
  131. package/dist/capability-dispatcher.js +0 -86
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WORKER_ISOLATION_SCHEMA_VERSION = void 0;
7
- exports.createWorkerIsolation = createWorkerIsolation;
8
7
  exports.allocateWorkerScope = allocateWorkerScope;
9
8
  exports.writeWorkerManifest = writeWorkerManifest;
10
9
  exports.syncWorkerScopeFromTask = syncWorkerScopeFromTask;
@@ -33,21 +32,9 @@ const multi_agent_1 = require("./multi-agent");
33
32
  const telemetry_attestation_1 = require("./telemetry-attestation");
34
33
  const telemetry_ledger_1 = require("./telemetry-ledger");
35
34
  const coordinator_1 = require("./coordinator");
35
+ const helpers_1 = require("./worker-isolation/helpers");
36
+ const paths_1 = require("./worker-isolation/paths");
36
37
  exports.WORKER_ISOLATION_SCHEMA_VERSION = 1;
37
- const WORKER_SCOPE_FILE = "worker.json";
38
- const WORKER_MANIFEST_FILE = "manifest.json";
39
- function createWorkerIsolation(options = {}) {
40
- return {
41
- allocateWorkerScope: (run, task, allocateOptions) => allocateWorkerScope(run, task, { ...options, ...allocateOptions }),
42
- writeWorkerManifest,
43
- listWorkerScopes: (run, listOptions) => listWorkerScopes(run, listOptions),
44
- getWorkerScope,
45
- recordWorkerOutput,
46
- recordWorkerFailure,
47
- validateWorkerBoundary,
48
- summarizeWorkers
49
- };
50
- }
51
38
  function allocateWorkerScope(run, task, options = {}) {
52
39
  ensureWorkerState(run);
53
40
  const existing = task.workerId ? getWorkerScope(run, task.workerId) : undefined;
@@ -64,7 +51,7 @@ function allocateWorkerScope(run, task, options = {}) {
64
51
  return existing;
65
52
  }
66
53
  const now = new Date().toISOString();
67
- const workerId = options.workerId || createWorkerId(run, task.id);
54
+ const workerId = options.workerId || (0, paths_1.createWorkerId)(run, task.id);
68
55
  const workerDir = node_path_1.default.join(workerRoot(run), (0, state_1.safeFileName)(workerId));
69
56
  const inputPath = node_path_1.default.join(workerDir, "input.md");
70
57
  const resultPath = node_path_1.default.join(workerDir, "result.md");
@@ -82,7 +69,10 @@ function allocateWorkerScope(run, task, options = {}) {
82
69
  extraReadPaths: options.policy?.readPaths || [],
83
70
  extraWritePaths: [...(options.policy?.writePaths || []), ...(options.policy?.allowedPaths || [])],
84
71
  allowArtifacts: options.policy?.allowArtifacts,
85
- allowLogs: options.policy?.allowLogs
72
+ allowLogs: options.policy?.allowLogs,
73
+ // H7: persisted custom profile definitions so a custom logical id resolves
74
+ // against THIS worker's context (worker-specific path tokens bind correctly).
75
+ customProfiles: run.customSandboxProfiles
86
76
  });
87
77
  const allowedPaths = (0, sandbox_profile_1.effectiveSandboxWritePaths)(sandboxPolicy);
88
78
  (0, sandbox_profile_1.upsertRunSandboxPolicy)(run, sandboxPolicy);
@@ -123,7 +113,7 @@ function allocateWorkerScope(run, task, options = {}) {
123
113
  feedbackIds: [],
124
114
  errors: [],
125
115
  multiAgent: options.multiAgent,
126
- metadata: compactMetadata({
116
+ metadata: (0, helpers_1.compactMetadata)({
127
117
  ...options.metadata,
128
118
  multiAgent: options.multiAgent,
129
119
  phase: task.phase,
@@ -165,7 +155,7 @@ function allocateWorkerScope(run, task, options = {}) {
165
155
  });
166
156
  }
167
157
  task.workerId = scope.id;
168
- task.workerManifestPath = manifestPath(scope);
158
+ task.workerManifestPath = (0, paths_1.manifestPath)(scope);
169
159
  task.sandboxProfileId = sandboxPolicy.id;
170
160
  task.sandboxPolicy = sandboxPolicy;
171
161
  task.backendId = backendId;
@@ -180,8 +170,8 @@ function writeWorkerManifest(run, scope) {
180
170
  const task = run.tasks.find((candidate) => candidate.id === scope.taskId);
181
171
  const sandboxPolicy = scope.sandboxPolicy || sandboxPolicyForBoundary(run, scope);
182
172
  const sandboxProfileId = scope.sandboxProfileId || sandboxPolicy.id;
183
- const scopePath = workerScopePath(scope);
184
- const workerManifestPath = manifestPath(scope);
173
+ const scopePath = (0, paths_1.workerScopePath)(scope);
174
+ const workerManifestPath = (0, paths_1.manifestPath)(scope);
185
175
  const manifest = {
186
176
  schemaVersion: exports.WORKER_ISOLATION_SCHEMA_VERSION,
187
177
  id: scope.id,
@@ -257,7 +247,7 @@ function syncWorkerScopeFromTask(run, workerId) {
257
247
  ...scope,
258
248
  updatedAt: new Date().toISOString(),
259
249
  multiAgent: task.multiAgent,
260
- metadata: compactMetadata({
250
+ metadata: (0, helpers_1.compactMetadata)({
261
251
  ...(scope.metadata || {}),
262
252
  multiAgent: task.multiAgent
263
253
  })
@@ -267,7 +257,7 @@ function syncWorkerScopeFromTask(run, workerId) {
267
257
  function listWorkerScopes(run, options = {}) {
268
258
  ensureWorkerState(run);
269
259
  const scopes = loadWorkerScopesFromDisk(run);
270
- run.workers = mergeScopes(run.workers || [], scopes);
260
+ run.workers = (0, helpers_1.mergeScopes)(run.workers || [], scopes);
271
261
  const listed = run.workers || [];
272
262
  return options.status ? listed.filter((scope) => scope.status === options.status) : listed;
273
263
  }
@@ -276,7 +266,7 @@ function getWorkerScope(run, workerId) {
276
266
  const existing = (run.workers || []).find((scope) => scope.id === workerId);
277
267
  if (existing)
278
268
  return existing;
279
- const file = node_path_1.default.join(workerRoot(run), (0, state_1.safeFileName)(workerId), WORKER_SCOPE_FILE);
269
+ const file = node_path_1.default.join(workerRoot(run), (0, state_1.safeFileName)(workerId), paths_1.WORKER_SCOPE_FILE);
280
270
  if (!node_fs_1.default.existsSync(file))
281
271
  return undefined;
282
272
  const scope = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
@@ -302,7 +292,7 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
302
292
  throw new Error(violation.message);
303
293
  }
304
294
  if (!node_fs_1.default.existsSync(absoluteResultPath)) {
305
- const error = structuredError("worker-result-missing", `Worker result file does not exist: ${absoluteResultPath}`, {
295
+ const error = (0, helpers_1.structuredError)("worker-result-missing", `Worker result file does not exist: ${absoluteResultPath}`, {
306
296
  path: absoluteResultPath,
307
297
  retryable: true
308
298
  });
@@ -321,7 +311,7 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
321
311
  const baseDirs = Array.from(new Set([run.cwd, process.cwd(), scope.workerDir, run.paths.runDir].filter(Boolean)));
322
312
  const unresolved = (0, evidence_grounding_1.unresolvedFileEvidence)(parsedResult.evidence, baseDirs);
323
313
  if (unresolved.length) {
324
- const error = structuredError("worker-evidence-unresolvable", `Worker ${workerId} result cites file evidence that does not resolve on disk: ${unresolved.join(", ")}`, { path: absoluteResultPath, retryable: false });
314
+ const error = (0, helpers_1.structuredError)("worker-evidence-unresolvable", `Worker ${workerId} result cites file evidence that does not resolve on disk: ${unresolved.join(", ")}`, { path: absoluteResultPath, retryable: false });
325
315
  recordWorkerFailure(run, workerId, error, { ...options, persist: options.persist });
326
316
  throw new Error(error.message);
327
317
  }
@@ -345,7 +335,7 @@ function recordWorkerOutput(run, workerId, resultPath, options = {}) {
345
335
  // it instead of recording unverifiable usage. Default behavior is unchanged
346
336
  // (flag-and-surface). Non-agent hops carry no verdict and are never blocked.
347
337
  if (options.requireAttestedTelemetry && telemetry && telemetry.status !== "attested") {
348
- const error = structuredError("telemetry-unattested-blocked", `Worker ${workerId} telemetry is ${telemetry.status} (${telemetry.reason || "unverified"}) and require-attested-telemetry is enabled — refusing to accept a hop whose usage cannot be cryptographically verified`, { path: absoluteResultPath, retryable: false });
338
+ const error = (0, helpers_1.structuredError)("telemetry-unattested-blocked", `Worker ${workerId} telemetry is ${telemetry.status} (${telemetry.reason || "unverified"}) and require-attested-telemetry is enabled — refusing to accept a hop whose usage cannot be cryptographically verified`, { path: absoluteResultPath, retryable: false });
349
339
  recordWorkerFailure(run, workerId, error, { ...options, persist: options.persist });
350
340
  throw new Error(error.message);
351
341
  }
@@ -581,7 +571,7 @@ function recordWorkerFailure(run, workerId, error, options = {}) {
581
571
  status: "pending",
582
572
  loopStage: "adjust",
583
573
  inputs: { workerId, taskId: task.id, dispatchId: scope.dispatchId },
584
- artifacts: workerArtifacts(scope),
574
+ artifacts: (0, paths_1.workerArtifacts)(scope),
585
575
  parents: task.stateNodeId ? [task.stateNodeId] : [],
586
576
  contractId: pipeline_contract_1.DEFAULT_PIPELINE_CONTRACT_ID,
587
577
  metadata: { workerId, taskId: task.id, dispatchId: scope.dispatchId, workerDir: scope.workerDir, sandboxProfileId: scope.sandboxProfileId }
@@ -636,7 +626,7 @@ function recordWorkerFailure(run, workerId, error, options = {}) {
636
626
  updatedAt: new Date().toISOString(),
637
627
  status: structured.code === "worker-boundary-violation" || structured.code.startsWith("sandbox-") ? "rejected" : "failed",
638
628
  retryCount: typeof options.retryCount === "number" ? options.retryCount : scope.retryCount,
639
- feedbackIds: unique([...(scope.feedbackIds || []), feedback.id]),
629
+ feedbackIds: (0, helpers_1.unique)([...(scope.feedbackIds || []), feedback.id]),
640
630
  errors: [...(scope.errors || []), structured]
641
631
  });
642
632
  if (options.persist !== false)
@@ -649,7 +639,7 @@ function recordWorkerRetryAttempt(run, workerId, attempts, reason, options = {})
649
639
  ...scope,
650
640
  updatedAt: new Date().toISOString(),
651
641
  retryCount: attempts,
652
- metadata: compactMetadata({
642
+ metadata: (0, helpers_1.compactMetadata)({
653
643
  ...scope.metadata,
654
644
  agentDelegationAttempts: attempts,
655
645
  agentDelegationLastFailure: reason
@@ -668,8 +658,8 @@ function summarizeWorkers(run) {
668
658
  const workers = listWorkerScopes(run);
669
659
  return {
670
660
  total: workers.length,
671
- byStatus: countBy(workers, (scope) => scope.status),
672
- manifestPaths: workers.map(manifestPath),
661
+ byStatus: (0, helpers_1.countBy)(workers, (scope) => scope.status),
662
+ manifestPaths: workers.map(paths_1.manifestPath),
673
663
  failed: workers
674
664
  .filter((scope) => scope.status === "failed" || scope.status === "rejected")
675
665
  .map((scope) => ({ id: scope.id, status: scope.status, feedbackIds: scope.feedbackIds || [] }))
@@ -705,15 +695,9 @@ function reclaimOrphans(run, now) {
705
695
  }
706
696
  if (orphans.length) {
707
697
  writeWorkerIndex(run);
708
- saveWorkerCheckpoint(run);
709
698
  }
710
699
  return { runId: run.id, reclaimed: orphans.length, orphans };
711
700
  }
712
- function saveWorkerCheckpoint(run) {
713
- // Durable write via atomic temp+rename (same contract as saveCheckpoint)
714
- // For worker index, the atomic write in writeWorkerIndex already handles it.
715
- // This is a no-op wrapper that signals the checkpoint boundary.
716
- }
717
701
  function ensureWorkerState(run) {
718
702
  run.paths.workersDir = run.paths.workersDir || node_path_1.default.join(run.paths.runDir, "workers");
719
703
  node_fs_1.default.mkdirSync(run.paths.workersDir, { recursive: true });
@@ -771,7 +755,7 @@ function updateWorkerScope(run, scope) {
771
755
  return updated;
772
756
  }
773
757
  function writeWorkerScope(scope) {
774
- (0, state_1.writeJson)(workerScopePath(scope), scope);
758
+ (0, state_1.writeJson)((0, paths_1.workerScopePath)(scope), scope);
775
759
  }
776
760
  function writeWorkerIndex(run) {
777
761
  ensureWorkerState(run);
@@ -784,7 +768,7 @@ function writeWorkerIndex(run) {
784
768
  dispatchId: scope.dispatchId,
785
769
  status: scope.status,
786
770
  workerDir: scope.workerDir,
787
- manifestPath: manifestPath(scope),
771
+ manifestPath: (0, paths_1.manifestPath)(scope),
788
772
  resultPath: scope.resultPath,
789
773
  sandboxProfileId: scope.sandboxProfileId,
790
774
  backendId: scope.backendId,
@@ -800,7 +784,7 @@ function loadWorkerScopesFromDisk(run) {
800
784
  return node_fs_1.default
801
785
  .readdirSync(workerRoot(run), { withFileTypes: true })
802
786
  .filter((entry) => entry.isDirectory())
803
- .map((entry) => node_path_1.default.join(workerRoot(run), entry.name, WORKER_SCOPE_FILE))
787
+ .map((entry) => node_path_1.default.join(workerRoot(run), entry.name, paths_1.WORKER_SCOPE_FILE))
804
788
  .filter((file) => node_fs_1.default.existsSync(file))
805
789
  .map((file) => JSON.parse(node_fs_1.default.readFileSync(file, "utf8")));
806
790
  }
@@ -823,6 +807,12 @@ function sandboxPolicyForBoundary(run, scope, options = {}) {
823
807
  if (scope.sandboxPolicy && !options.policy && !options.sandboxProfileId)
824
808
  return scope.sandboxPolicy;
825
809
  const profileId = options.sandboxProfileId || options.policy?.sandboxProfileId || scope.sandboxProfileId || sandbox_profile_1.DEFAULT_SANDBOX_PROFILE_ID;
810
+ // H7: when the scope.sandboxPolicy snapshot is LOST, this re-resolves the policy
811
+ // by its logical profileId against the WORKER's paths (scope.workerDir etc.). For
812
+ // a CUSTOM profile the bundled lookup would throw not-found; threading
813
+ // run.customSandboxProfiles lets resolveSandboxProfileById re-resolve the persisted
814
+ // DEFINITION here — re-enforcing the same policy with worker-correct path tokens
815
+ // (NOT the dispatch-time paths), so a legitimate worker write is not falsely denied.
826
816
  return (0, sandbox_profile_1.sandboxPolicyForWorker)(profileId, {
827
817
  cwd: run.cwd,
828
818
  runDir: run.paths.runDir,
@@ -838,7 +828,8 @@ function sandboxPolicyForBoundary(run, scope, options = {}) {
838
828
  ...(!scope.sandboxPolicy ? scope.allowedPaths || [] : [])
839
829
  ],
840
830
  allowArtifacts: options.policy?.allowArtifacts,
841
- allowLogs: options.policy?.allowLogs
831
+ allowLogs: options.policy?.allowLogs,
832
+ customProfiles: run.customSandboxProfiles
842
833
  });
843
834
  }
844
835
  function blackboardManifest(run, scope) {
@@ -927,7 +918,7 @@ function blackboardLinkage(run, scope) {
927
918
  const role = scope.multiAgent?.roleId ? run.multiAgent?.roles.find((entry) => entry.id === scope.multiAgent?.roleId) : undefined;
928
919
  const multiAgentRun = scope.multiAgent?.runId ? run.multiAgent?.runs.find((entry) => entry.id === scope.multiAgent?.runId) : undefined;
929
920
  const blackboardId = membership?.blackboardId || group?.blackboardId || role?.blackboardId || multiAgentRun?.blackboardId;
930
- const topicIds = unique([
921
+ const topicIds = (0, helpers_1.unique)([
931
922
  ...(membership?.topicIds || []),
932
923
  ...(group?.topicIds || []),
933
924
  ...(role?.topicIds || []),
@@ -935,94 +926,27 @@ function blackboardLinkage(run, scope) {
935
926
  ]);
936
927
  return { blackboardId, topicIds };
937
928
  }
938
- function manifestPath(scope) {
939
- return node_path_1.default.join(scope.workerDir, WORKER_MANIFEST_FILE);
940
- }
941
- function workerScopePath(scope) {
942
- return node_path_1.default.join(scope.workerDir, WORKER_SCOPE_FILE);
943
- }
944
- // Deterministic worker id (v0.1.40 self-audit P2): a wall-clock stamp + Math.random()
945
- // made every dispatch mint a different id, so audit references were not reproducible
946
- // across re-runs of the same inputs. The id is now derived from the task plus a
947
- // per-task sequence (count of worker scopes already allocated for that task + 1),
948
- // so re-running the same workflow yields byte-identical worker ids while retries of
949
- // the SAME task still get a fresh, unique id. (workerId is excluded from the
950
- // snapshot source fingerprint, so this does not change replay digests.)
951
- function createWorkerId(run, taskId) {
952
- const prefix = `worker-${(0, state_1.safeFileName)(taskId)}-`;
953
- const seq = (run.workers || []).filter((scope) => scope.id.startsWith(prefix)).length + 1;
954
- return `${prefix}${String(seq).padStart(4, "0")}`;
955
- }
956
- function workerArtifacts(scope) {
957
- return [
958
- { id: "worker", kind: "json", path: workerScopePath(scope) },
959
- { id: "worker-manifest", kind: "json", path: manifestPath(scope) },
960
- { id: "worker-input", kind: "markdown", path: scope.inputPath }
961
- ];
962
- }
963
929
  function normalizeWorkerError(error, scope, options) {
964
- if (isBoundaryViolation(error)) {
965
- return structuredError(error.code, error.message, {
930
+ if ((0, helpers_1.isBoundaryViolation)(error)) {
931
+ return (0, helpers_1.structuredError)(error.code, error.message, {
966
932
  path: error.path,
967
933
  retryable: false,
968
934
  details: { allowedPaths: error.allowedPaths, workerId: scope.id, taskId: scope.taskId, sandboxProfileId: scope.sandboxProfileId }
969
935
  });
970
936
  }
971
- if (isStateNodeError(error)) {
937
+ if ((0, helpers_1.isStateNodeError)(error)) {
972
938
  return {
973
939
  ...error,
974
940
  at: error.at || new Date().toISOString(),
975
941
  path: options.path || error.path,
976
942
  retryable: options.retryable ?? error.retryable ?? false,
977
- details: compactMetadata({ ...(error.details || {}), workerId: scope.id, taskId: scope.taskId })
943
+ details: (0, helpers_1.compactMetadata)({ ...(error.details || {}), workerId: scope.id, taskId: scope.taskId })
978
944
  };
979
945
  }
980
946
  const message = error instanceof Error ? error.message : String(error);
981
- return structuredError(options.code || "worker-runtime-error", message, {
947
+ return (0, helpers_1.structuredError)(options.code || "worker-runtime-error", message, {
982
948
  path: options.path,
983
949
  retryable: options.retryable ?? false,
984
950
  details: { workerId: scope.id, taskId: scope.taskId }
985
951
  });
986
952
  }
987
- function structuredError(code, message, options = {}) {
988
- return {
989
- code,
990
- message,
991
- at: new Date().toISOString(),
992
- path: options.path,
993
- retryable: options.retryable,
994
- details: options.details
995
- };
996
- }
997
- function isBoundaryViolation(value) {
998
- return Boolean(value && typeof value === "object" && "allowedPaths" in value && "message" in value);
999
- }
1000
- function isStateNodeError(value) {
1001
- return Boolean(value && typeof value === "object" && "code" in value && "message" in value);
1002
- }
1003
- function mergeScopes(left, right) {
1004
- const merged = [...left];
1005
- for (const scope of right) {
1006
- const index = merged.findIndex((candidate) => candidate.id === scope.id);
1007
- if (index >= 0)
1008
- merged[index] = scope;
1009
- else
1010
- merged.push(scope);
1011
- }
1012
- return merged;
1013
- }
1014
- function unique(values) {
1015
- return Array.from(new Set(values.filter(Boolean)));
1016
- }
1017
- function compactMetadata(value) {
1018
- const entries = Object.entries(value).filter(([, entry]) => entry !== undefined);
1019
- return entries.length ? Object.fromEntries(entries) : undefined;
1020
- }
1021
- function countBy(items, key) {
1022
- const counts = {};
1023
- for (const item of items) {
1024
- const value = key(item);
1025
- counts[value] = (counts[value] || 0) + 1;
1026
- }
1027
- return counts;
1028
- }
@@ -131,14 +131,77 @@ node dist/cli.js backend probe agent --json # ready iff configured, else un
131
131
  node dist/cli.js run architecture-review --drive --repo /path/to/repo --question "Is the design sound?"
132
132
  node dist/cli.js run architecture-review --drive --once --repo /path/to/repo --question "..." # one step
133
133
  node dist/cli.js run drive <run-id> --json # read-only preview of the next step
134
+
135
+ # quickstart --resume: a guided stop-then-resume a newcomer can WITNESS in <5 min
136
+ node dist/cli.js quickstart --resume --repo /path/to/repo --question "..." # advances ONE step, prints a continue line
137
+ node dist/cli.js quickstart --run <run-id> --resume # continues that run to completion
138
+ ```
139
+
140
+ `quickstart --resume` with no `--run` drives a single step and prints a
141
+ copy-pasteable `cw quickstart --run <id> --resume` continue line; rerun it with the
142
+ `--run <id>` to finish. The continuing invocation echoes `resumedFrom: <id>`. Bare
143
+ `quickstart` (no `--resume`) is unchanged — it drives straight to completion.
144
+
145
+ For faster first results, use the opt-in fast app instead of changing the full
146
+ review contract:
147
+
148
+ ```text
149
+ node scripts/architecture-review-fast.js --repo /path/to/repo --question "Is the design sound?" --fast-model gpt-5.5-high --strong-model gpt-5.5-extra-high --metrics --schedule-full
134
150
  ```
135
151
 
152
+ `architecture-review-fast` has six workers: two Map and two Assess workers in
153
+ parallel, then sequential Verify and Verdict workers. The original
154
+ `architecture-review` app remains the full 14-worker review and is the right
155
+ target for background routines when a deep audit can finish outside the user's
156
+ foreground wait.
157
+
158
+ The model flags are policy, not attestation: they set task-level `{{model}}`
159
+ hints for the delegated agent process. The recorded model still comes only from
160
+ the agent-reported output.
161
+
162
+ The wrapper computes the source-context digest and supplies it to the fast app.
163
+ For external repositories, the documented no-profile command creates a repo-local
164
+ default `repo` profile over common tracked text surfaces. If the selected profile
165
+ exports zero records, the wrapper refuses rather than handing the app an empty
166
+ context digest.
167
+ The two Map workers opt in to result caching keyed by source-context digest plus
168
+ prompt digest. The two Assess workers also opt in, but their cache key includes
169
+ the completed previous-phase result digests so stale Map outputs do not satisfy
170
+ an Assess cache hit. A cache hit still passes through `recordWorkerOutput`
171
+ validation; a corrupt cached result parks/fails closed rather than spawning a
172
+ silent fallback.
173
+
174
+ `--metrics` is diagnostic and opt-in. It adds elapsed milliseconds, step counts,
175
+ agent-spawn counts, and `result-cache` hit counts to the wrapper JSON payload;
176
+ without it, the wrapper's default output shape stays unchanged.
177
+
136
178
  `{{manifest}}`, `{{input}}`, `{{result}}`, `{{workerDir}}`, `{{model}}`, and
137
179
  `{{prompt}}` are substituted into DISCRETE argv elements (never a shell-interpreted
138
180
  string). Each verb is declared once in `capability-registry.ts`, so `cw <cmd>
139
181
  --json` is byte-identical to the matching `cw_<tool>` MCP tool for the read-only
140
182
  preview/config-show verbs.
141
183
 
184
+ ## Live output — opt-in stderr passthrough (Unix-clean)
185
+
186
+ A drive can show the agent's activity live, without touching the evidence
187
+ contract, when the operator opts in with `CW_AGENT_STREAM=1`:
188
+
189
+ - **Default stays buffered.** Without `CW_AGENT_STREAM=1`, the bundled wrapper
190
+ preserves the legacy `--output-format json` path and forwards claude's JSON
191
+ stdout verbatim after writing `result.md`.
192
+ - **The opt-in wrapper renders; stderr only.** With `CW_AGENT_STREAM=1`, the
193
+ bundled wrapper runs claude in `--output-format stream-json` and renders a
194
+ concise human trace (tool uses, assistant text, per-turn summaries) to its
195
+ **stderr** — diagnostics, never data. It reconstructs the single
196
+ `{model, usage, result}` object for stdout only on that opt-in path.
197
+ - **Core forwards, never parses.** `runAgentProcess` passes the agent child's
198
+ stderr straight through to the operator's terminal (`stdio` inherit) only when
199
+ `CW_AGENT_STREAM=1`, CW's own stderr is a TTY, and `CW_NO_STREAM` is not set.
200
+ Piped / CI runs stay silent (the Rule of Silence). Vendor-specific rendering
201
+ lives in the wrapper (policy), not the kernel (mechanism).
202
+ - **Determinism intact.** The backend evidence triple hashes stdout only, so
203
+ the live stderr stream never affects recorded evidence or replay.
204
+
142
205
  ## Compatibility
143
206
 
144
207
  Agent Delegation Drive is introduced in CW v0.1.38. Adding the `agent` row leaves
@@ -190,3 +253,11 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
190
253
  0.1.78
191
254
 
192
255
  0.1.79
256
+
257
+ ## Fast Architecture Review (v0.1.80)
258
+
259
+ Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
260
+
261
+ ## Resumable Drive & Resume Routing (v0.1.81)
262
+
263
+ Adds `run resume <id> --drive/--once` alongside `quickstart --resume`: a stopped pipeline resumes in-place, advancing to completion (`--drive`) or one deterministic step (`--once`) over the same plan->dispatch->agent-fulfill->accept->commit lifecycle, echoing `resumedFrom: <id>`. Fixes the `run resume --drive` CLI routing so the drive flag reaches the resumed run instead of being read as an app name. Replay determinism and the agent evidence triple are unchanged.
@@ -28,6 +28,43 @@ node scripts/cw.js plan architecture-review \
28
28
  --focus "runtime"
29
29
  ```
30
30
 
31
+ `architecture-review-fast`
32
+
33
+ Run a shorter architecture review for a fast first result. The app keeps the
34
+ full `architecture-review` contract available under its original id, but uses two
35
+ parallel Map workers, two parallel Assess workers, one verifier, and one verdict
36
+ worker. Operators can optionally provide a pinned JSONL source context and route
37
+ mapping/assessment work to a faster model while reserving stronger models for
38
+ verification and synthesis.
39
+
40
+ ```bash
41
+ node scripts/architecture-review-fast.js \
42
+ --repo /path/to/repo \
43
+ --question "Is this architecture sound?" \
44
+ --fast-model gpt-5.5-high \
45
+ --strong-model gpt-5.5-extra-high \
46
+ --metrics \
47
+ --schedule-full
48
+ ```
49
+
50
+ The wrapper prepares one cached JSONL source context, passes its sha256 digest to
51
+ the fast app, runs `quickstart architecture-review-fast`, and optionally creates
52
+ a one-shot background schedule for the full `architecture-review` app. When run
53
+ against an external repo without `--profile` or `--profile-file`, it writes a
54
+ small repo-local `repo` profile covering common tracked text surfaces such as
55
+ README/package metadata, `src/`, `lib/`, `apps/`, `scripts/`, docs, and tests.
56
+ If the selected profile exports zero records, the wrapper fails closed instead of
57
+ passing an empty context digest to the app.
58
+ `--fast-model` and `--strong-model` are userland policy flags; internally they
59
+ set the same task-level hints as `CW_ARCHITECTURE_REVIEW_FAST_MODEL` and
60
+ `CW_ARCHITECTURE_REVIEW_STRONG_MODEL`.
61
+ `--metrics` is opt-in; when present the wrapper adds elapsed-time, worker-step,
62
+ agent-spawn, and result-cache-hit counts to the JSON payload so operators can
63
+ measure foreground wait reductions without changing the default output shape.
64
+
65
+ For long full reviews, use the existing routine or schedule surfaces to run
66
+ `architecture-review` in the background after the fast report has returned.
67
+
31
68
  `pr-review-fix-ci`
32
69
 
33
70
  Review a pull request or branch, inspect CI failures, diagnose actionable
@@ -39,6 +39,14 @@ A new runtime capability is added once, in the registry, against one core entry.
39
39
  The CLI command and the MCP tool are then two policies over that one mechanism —
40
40
  which is exactly what the parity gate checks.
41
41
 
42
+ The MCP tool list is also being collapsed toward that single source. The first
43
+ read-only inspection group (`operator.status`, `graph`, `operator.report`,
44
+ worker/candidate/feedback/commit summaries, and the basic multi-agent inspection
45
+ views) derives its MCP tool name and description directly from the capability
46
+ registry; `mcp-server.ts` still owns the MCP input schema for those tools. This
47
+ keeps the public `tools/list` output unchanged while removing one duplicate
48
+ description table at a time.
49
+
42
50
  ## Human vs Machine Contract
43
51
 
44
52
  The two surfaces have different contracts and must not interfere:
@@ -373,3 +381,11 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
373
381
  0.1.78
374
382
 
375
383
  0.1.79
384
+
385
+ ## Fast Architecture Review (v0.1.80)
386
+
387
+ Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
388
+
389
+ ## Re-Prove Verbs on Both Surfaces (v0.1.81)
390
+
391
+ v0.1.81 grows the parity surface with two new both-surface, fail-closed verbs declared once in the capability registry: `cw audit verify` / `cw_audit_verify` re-proves the trust-audit chain and exits non-zero on any unverified or corrupt chain, and `cw run inspect-archive` / `cw_run_inspect_archive` is a read-only archive integrity check. Each `cw <cmd> --json` is schema-identical to its `cw_<tool>` and validated by the same parity gate.
@@ -123,3 +123,9 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
123
123
  0.1.78
124
124
 
125
125
  0.1.79
126
+
127
+ ## Fast Architecture Review (v0.1.80)
128
+
129
+ Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
130
+
131
+ _No changes to the contract-migration subsystem in v0.1.81._
@@ -110,3 +110,9 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
110
110
  0.1.78
111
111
 
112
112
  0.1.79
113
+
114
+ ## Fast Architecture Review (v0.1.80)
115
+
116
+ Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
117
+
118
+ _No changes to the control-plane scheduling surface in v0.1.81._
@@ -0,0 +1,40 @@
1
+ # Dogfood: real `builtin:claude` agent + `run resume --drive` (2026-06-14)
2
+
3
+ A live dogfood run with a REAL external agent (`CW_AGENT_COMMAND=builtin:claude`, the
4
+ bundled read-only claude wrapper). The model ran in the agent's own process; CW
5
+ spawned it and recorded the attested output — CW holds no API key and imports no
6
+ model SDK. This run had two purposes and delivered both: it confirmed the real-agent
7
+ delegation path works end to end, and — because it exercised the **CLI** rather than
8
+ the unit-test function path — it **caught a real shipped bug** in `run resume --drive`.
9
+
10
+ ## What ran
11
+
12
+ - `cw run architecture-review --drive --once --repo <tmp> --question "…"` with a real
13
+ `builtin:claude` agent: **1 worker completed** end-to-end with zero hand-written
14
+ result.md — the worker's `result.md` was produced by real claude, passed the
15
+ evidence-gated acceptance, and a `report.md` (7.5 KB, "# Architecture Review …")
16
+ + 3 state commits were written. The real-agent path (spawn → attested output →
17
+ evidence gate → commit) works.
18
+ - Run: `architecture-review-20260614T104416Z-upkor2`, status `in-progress` (1/14)
19
+ after the single `--once` step.
20
+
21
+ ## The bug it caught (and the fix)
22
+
23
+ Resuming the partway run with `cw run resume <id> --drive` failed:
24
+ `cw: Workflow app not found: resume`. The `run` command's early `--drive` branch
25
+ (the `cw run <app> --drive` one-command form) intercepted the invocation *before* the
26
+ subcommand switch, so the `resume` keyword was misread as an app name and never
27
+ reached the `runResume` continuation shipped in #155.
28
+
29
+ The A1 unit smoke (`run-resume-drive-smoke`) had tested `runResume()` **directly**, so
30
+ it never exercised the CLI dispatch — only a real CLI run surfaced it. Fixed by
31
+ guarding the early app-drive route so a leading run-registry subcommand keyword
32
+ (resume/show/export/…) falls through to the switch; `run-resume-drive-smoke` now drives
33
+ `cw run resume <id> --drive` through the actual CLI and asserts it routes to the verb,
34
+ plus a regression guard that `run <app> --drive` still routes to the app drive.
35
+
36
+ ## Takeaway
37
+
38
+ Unit tests that call the capability function directly can miss CLI-dispatch bugs.
39
+ Every both-surface verb that adds a flag wants at least one test through the real CLI
40
+ argv path, not just the exported function.
@@ -107,3 +107,11 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
107
107
  0.1.78
108
108
 
109
109
  0.1.79
110
+
111
+ ## Fast Architecture Review (v0.1.80)
112
+
113
+ Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
114
+
115
+ ## Deterministic Tombstone Hash (v0.1.81)
116
+
117
+ The reclamation tombstone's freed-manifest is now path-sorted before it feeds `tombstoneHash`, so the same freed set always yields the same hash regardless of filesystem enumeration order. This removes a non-determinism from the write-ahead chain (v0.1.39/v0.1.40), keeping the per-run tombstone hash-chain replayable and stable across hosts. Atomicity, locking, and the durable re-point seam are unchanged. v0.1.81 also adds import-time refusal (`CW_REQUIRE_ARCHIVE_INTEGRITY=1`) and restore-time trust-audit re-proving — see run-registry-control-plane(7).
@@ -270,3 +270,9 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
270
270
  0.1.78
271
271
 
272
272
  0.1.79
273
+
274
+ ## Fast Architecture Review (v0.1.80)
275
+
276
+ Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
277
+
278
+ _No changes to the Evidence Adoption Reasoning Chain surface in v0.1.81. The v0.1.81 trust-audit `computeEventHash` fix hardens the underlying audit records this chain links to by reference, but the derived reasoning view, its commands, and its eval gates are unchanged._
@@ -300,3 +300,9 @@ Migration DAG with reversible edges (v0.1.45), capability auto-discovery (v0.1.4
300
300
  0.1.78
301
301
 
302
302
  0.1.79
303
+
304
+ ## Fast Architecture Review (v0.1.80)
305
+
306
+ Adds the opt-in fast architecture-review lane: scoped JSONL source contexts, diff-aware exports, reusable Map and Assess results, measurable wrapper metrics, actionable background full-review handoff, and userland model policy flags for routing fast/strong workers without changing the full review contract.
307
+
308
+ _No changes to the execution-backends surface in v0.1.81._
package/docs/index.md CHANGED
@@ -7,6 +7,7 @@ Read these in order when you are new to CW:
7
7
  3. [Workflow App framework](workflow-app-framework.7.md) - userland app manifests, entrypoints, compatibility, and validation.
8
8
  4. [Sandbox Profiles](sandbox-profiles.7.md) - named worker policy contracts for read/write/execute/network/env handling.
9
9
  5. [Security / Trust Hardening](security-trust-hardening.7.md) - audit records, provenance, sandbox attestations, and acceptance rationale.
10
+ - [Trust Model & Limitations](trust-model.md) - what the ed25519 + hash-chain tamper-evidence proves and, honestly, what it does **not** (the single-keyholder ceiling). Read this before relying on a green verdict.
10
11
  6. [Multi-Agent Runtime Core](multi-agent-runtime-core.7.md) - first-class MultiAgentRun, roles, groups, memberships, fanout, fanin, and lifecycle state.
11
12
  7. [Coordinator / Blackboard](coordinator-blackboard.7.md) - shared topics, messages, context frames, artifact refs, snapshots, decisions, conflicts, and fanin evidence.
12
13
  8. [Multi-Agent Topologies](multi-agent-topologies.7.md) - official map-reduce, debate, and judge-panel recipes built on multi-agent and blackboard records.
@@ -35,6 +36,7 @@ Read these in order when you are new to CW:
35
36
  31. [Agent Delegation Drive](agent-delegation-drive.7.md) - the `agent` backend delegates each worker to an EXTERNAL agent process (claude/codex/HTTP endpoint) and `run --drive` auto-advances plan→dispatch→fulfill→accept→commit; the model runs in the agent's process, never in CW. Two-layer evidence, operator-vs-attested model, fail-closed park, replay without re-spawn.
36
37
  32. [Run Retention & Provable Reclamation](run-retention-reclamation.7.md) - tiered, append-only, cryptographically-verifiable disk reclamation over the v0.1.28 archive overlay: seal the audit skeleton, free the reconstructable/scratch bulk, and prove it via a hash-chained tombstone; `gc plan|run|verify`, write-ahead + fail-closed, explicit capability downgrade.
37
38
  33. [Durable State & Locking](durable-state-and-locking.7.md) - atomic (temp→rename) writes for every authoritative store with fsync-durability for the audit-essential ones, plus a portable stale-stealing file lock serializing the cross-process read-modify-write stores (home queue, archive overlay, reclamation chain); closes the prior verdict's non-atomic/unlocked P1.
39
+ 34. [Source Context Profiles](source-context-profiles.7.md) - opt-in JSONL source exports for AI context slimming, with profile policy in manifest data and manifest records proving every included or omitted tracked file.
38
40
 
39
41
  CW is the base system. Workflow apps are userland. Release and migration rules
40
42
  must preserve that line: stable contracts, explicit compatibility checks, and
@@ -0,0 +1,28 @@
1
+ # VHS tape — records the `cw demo tamper` proof to a GIF for the README hero.
2
+ #
3
+ # Reproducible, deterministic capture (no manual screen-recording). Install VHS
4
+ # (https://github.com/charmbracelet/vhs), then from the repo root:
5
+ #
6
+ # vhs plugins/cool-workflow/docs/launch/demo.tape
7
+ #
8
+ # Output: docs/launch/demo-tamper.gif. Then in the root README, replace the
9
+ # fenced demo output block under "See it in 30 seconds" with:
10
+ # ![cw demo tamper](plugins/cool-workflow/docs/launch/demo-tamper.gif)
11
+
12
+ Output plugins/cool-workflow/docs/launch/demo-tamper.gif
13
+
14
+ Set FontSize 15
15
+ Set Width 1180
16
+ Set Height 760
17
+ Set Padding 18
18
+ Set Theme "Dracula"
19
+ Set Framerate 24
20
+ Set PlaybackSpeed 1.0
21
+
22
+ # Use the published package so the GIF reflects exactly what a new user runs.
23
+ Type "npx cool-workflow demo tamper"
24
+ Sleep 600ms
25
+ Enter
26
+
27
+ # Allow npx fetch + the demo to run and print the verdict.
28
+ Sleep 14s