cool-workflow 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +7 -5
  4. package/apps/architecture-review/app.json +2 -2
  5. package/apps/architecture-review/workflow.js +12 -12
  6. package/apps/architecture-review-fast/app.json +1 -1
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/cli/dispatch.js +2 -2
  12. package/dist/cli/parseargv.js +2 -2
  13. package/dist/core/capability-table.js +13 -29
  14. package/dist/core/format/help.js +2 -2
  15. package/dist/core/hash.js +1 -1
  16. package/dist/core/multi-agent/candidate-scoring.js +1 -1
  17. package/dist/core/multi-agent/eval-replay.js +1 -1
  18. package/dist/core/multi-agent/runtime.js +2 -2
  19. package/dist/core/multi-agent/trust-policy.js +1 -1
  20. package/dist/core/pipeline/contract.js +1 -1
  21. package/dist/core/pipeline/drive-decide.js +1 -1
  22. package/dist/core/state/contract-migration.js +1 -1
  23. package/dist/core/state/migrations.js +2 -2
  24. package/dist/core/state/node-snapshot.js +1 -1
  25. package/dist/core/state/state-explosion/graph.js +4 -4
  26. package/dist/core/state/state-explosion/helpers.js +3 -3
  27. package/dist/core/state/state-explosion/report.js +1 -1
  28. package/dist/core/state/state-explosion/size.js +1 -1
  29. package/dist/core/state/state-node.js +2 -2
  30. package/dist/core/state/types.js +1 -1
  31. package/dist/core/trust/ledger.js +1 -1
  32. package/dist/core/trust/telemetry-attestation.js +3 -3
  33. package/dist/core/trust/telemetry-ledger.js +2 -2
  34. package/dist/core/version.js +1 -1
  35. package/dist/core/workflow-apps/app-schema.js +1 -1
  36. package/dist/mcp/dispatch.js +1 -1
  37. package/dist/mcp/server.js +1 -1
  38. package/dist/shell/agent-config.js +1 -1
  39. package/dist/shell/doctor.js +17 -4
  40. package/dist/shell/drive.js +21 -9
  41. package/dist/shell/execution-backend/agent.js +42 -10
  42. package/dist/shell/execution-backend/ci.js +1 -1
  43. package/dist/shell/execution-backend/container.js +1 -1
  44. package/dist/shell/execution-backend/envelopes.js +1 -1
  45. package/dist/shell/execution-backend/local.js +1 -1
  46. package/dist/shell/execution-backend/probes.js +1 -1
  47. package/dist/shell/execution-backend/registry.js +1 -1
  48. package/dist/shell/execution-backend/remote.js +1 -1
  49. package/dist/shell/execution-backend/types.js +1 -1
  50. package/dist/shell/fs-atomic.js +1 -1
  51. package/dist/shell/ledger-io.js +1 -1
  52. package/dist/shell/metrics-cli.js +4 -2
  53. package/dist/shell/multi-agent-host.js +1 -1
  54. package/dist/shell/reclamation-io.js +1 -1
  55. package/dist/shell/registry-cli.js +15 -0
  56. package/dist/shell/run-registry-io.js +1 -1
  57. package/dist/shell/sandbox-profile.js +1 -1
  58. package/dist/shell/scheduler-io.js +46 -37
  59. package/dist/shell/scheduling-io.js +32 -22
  60. package/dist/shell/telemetry-ledger-io.js +1 -1
  61. package/dist/shell/term.js +1 -1
  62. package/dist/shell/trust-audit.js +4 -3
  63. package/dist/shell/workbench-host.js +9 -1
  64. package/dist/shell/workbench.js +6 -11
  65. package/docs/agent-delegation-drive.7.md +2 -0
  66. package/docs/cli-mcp-parity.7.md +2 -0
  67. package/docs/contract-migration-tooling.7.md +2 -0
  68. package/docs/control-plane-scheduling.7.md +2 -0
  69. package/docs/durable-state-and-locking.7.md +2 -0
  70. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  71. package/docs/execution-backends.7.md +2 -0
  72. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  73. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  74. package/docs/multi-agent-operator-ux.7.md +2 -0
  75. package/docs/node-snapshot-diff-replay.7.md +2 -0
  76. package/docs/observability-cost-accounting.7.md +4 -1
  77. package/docs/project-index.md +8 -3
  78. package/docs/real-execution-backends.7.md +2 -0
  79. package/docs/release-and-migration.7.md +2 -0
  80. package/docs/release-tooling.7.md +2 -0
  81. package/docs/remote-source-review.7.md +4 -4
  82. package/docs/report-verifiable-bundle.7.md +1 -1
  83. package/docs/run-registry-control-plane.7.md +2 -0
  84. package/docs/run-retention-reclamation.7.md +2 -0
  85. package/docs/security-trust-hardening.7.md +3 -1
  86. package/docs/state-explosion-management.7.md +2 -0
  87. package/docs/team-collaboration.7.md +2 -0
  88. package/docs/web-desktop-workbench.7.md +13 -1
  89. package/manifest/plugin.manifest.json +1 -1
  90. package/package.json +1 -1
  91. package/scripts/agents/agent-adapter-core.js +22 -2
  92. package/scripts/agents/claude-p-agent.js +8 -3
  93. package/scripts/agents/gemini-agent.js +5 -1
  94. package/scripts/agents/opencode-agent.js +5 -1
  95. package/scripts/canonical-apps.js +4 -4
  96. package/scripts/dogfood-release.js +1 -1
  97. package/scripts/golden-path.js +4 -4
@@ -2,7 +2,7 @@
2
2
  // shell/scheduler-io.ts — Scheduler (cw schedule/loop), the daemon tick
3
3
  // (cw schedule daemon), and routine triggers (cw routine).
4
4
  //
5
- // MILESTONE 10 (v2/PLAN.md build order, step 10). Byte-exact port of the
5
+ // MILESTONE 10 (docs/rebuild/PLAN.md build order, step 10). Byte-exact port of the
6
6
  // old build's src/scheduler.ts + src/daemon.ts + src/triggers.ts. Reuses
7
7
  // shell/fs-atomic.ts's `withFileLock`/`readJson`/`writeJson`/`safeFileName`
8
8
  // directly (no reimplementation).
@@ -387,51 +387,60 @@ class RoutineTriggerBridge {
387
387
  this.storePath = path.join(this.cwd, ".cw", "routines", "triggers.json");
388
388
  this.payloadsDir = path.join(this.cwd, ".cw", "routines", "payloads");
389
389
  }
390
+ locked(fn) {
391
+ return (0, fs_atomic_1.withFileLock)(this.storePath, fn);
392
+ }
390
393
  create(options) {
391
- const now = new Date().toISOString();
392
- const store = this.load();
393
- // Monotonic id, NOT triggers.length: delete shrinks the collection, so
394
- // a length-based seq would reuse a live id after delete+create.
395
- const seq = (store.nextTriggerSeq || 0) + 1;
396
- store.nextTriggerSeq = seq;
397
- const trigger = {
398
- id: createTriggerId(normalizeTriggerKind(options.kind), seq),
399
- kind: normalizeTriggerKind(options.kind),
400
- createdAt: now,
401
- updatedAt: now,
402
- source: String(options.source || options.kind || "api"),
403
- prompt: requiredString(options.prompt, "prompt"),
404
- workflowId: stringOption(options.workflowId),
405
- runId: stringOption(options.runId),
406
- match: parseJsonObject(options.match),
407
- metadata: parseJsonObject(options.metadata),
408
- };
409
- store.triggers.push(trigger);
410
- this.save(store);
411
- return trigger;
394
+ return this.locked(() => {
395
+ const now = new Date().toISOString();
396
+ const store = this.load();
397
+ // Monotonic id, NOT triggers.length: delete shrinks the collection, so
398
+ // a length-based seq would reuse a live id after delete+create.
399
+ const seq = (store.nextTriggerSeq || 0) + 1;
400
+ store.nextTriggerSeq = seq;
401
+ const trigger = {
402
+ id: createTriggerId(normalizeTriggerKind(options.kind), seq),
403
+ kind: normalizeTriggerKind(options.kind),
404
+ createdAt: now,
405
+ updatedAt: now,
406
+ source: String(options.source || options.kind || "api"),
407
+ prompt: requiredString(options.prompt, "prompt"),
408
+ workflowId: stringOption(options.workflowId),
409
+ runId: stringOption(options.runId),
410
+ match: parseJsonObject(options.match),
411
+ metadata: parseJsonObject(options.metadata),
412
+ };
413
+ store.triggers.push(trigger);
414
+ this.save(store);
415
+ return trigger;
416
+ });
412
417
  }
413
418
  list(kind) {
414
419
  const store = this.load();
415
420
  return kind ? store.triggers.filter((trigger) => trigger.kind === kind) : store.triggers;
416
421
  }
417
422
  delete(id) {
418
- const store = this.load();
419
- const before = store.triggers.length;
420
- store.triggers = store.triggers.filter((trigger) => trigger.id !== id);
421
- this.save(store);
422
- return { deleted: store.triggers.length !== before, id };
423
+ return this.locked(() => {
424
+ const store = this.load();
425
+ const before = store.triggers.length;
426
+ store.triggers = store.triggers.filter((trigger) => trigger.id !== id);
427
+ this.save(store);
428
+ return { deleted: store.triggers.length !== before, id };
429
+ });
423
430
  }
424
431
  fire(kind, payload) {
425
- const normalizedKind = normalizeTriggerKind(kind);
426
- const store = this.load();
427
- const now = new Date().toISOString();
428
- const base = store.events.length;
429
- const events = store.triggers
430
- .filter((trigger) => trigger.kind === normalizedKind)
431
- .map((trigger, index) => this.createEvent(trigger, payload, now, base + index + 1));
432
- store.events.push(...events);
433
- this.save(store);
434
- return events;
432
+ return this.locked(() => {
433
+ const normalizedKind = normalizeTriggerKind(kind);
434
+ const store = this.load();
435
+ const now = new Date().toISOString();
436
+ const base = store.events.length;
437
+ const events = store.triggers
438
+ .filter((trigger) => trigger.kind === normalizedKind)
439
+ .map((trigger, index) => this.createEvent(trigger, payload, now, base + index + 1));
440
+ store.events.push(...events);
441
+ this.save(store);
442
+ return events;
443
+ });
435
444
  }
436
445
  events(triggerId) {
437
446
  const store = this.load();
@@ -3,7 +3,7 @@
3
3
  // lease-plan mechanism over the run-registry-io.ts queue store, plus the
4
4
  // policy file IO.
5
5
  //
6
- // MILESTONE 10 (v2/PLAN.md build order, step 10). Byte-exact port of the
6
+ // MILESTONE 10 (docs/rebuild/PLAN.md build order, step 10). Byte-exact port of the
7
7
  // old build's src/scheduling.ts + the sched-specific slice of
8
8
  // src/capability-core.ts. `sched` is a DIFFERENT system from `schedule`
9
9
  // (see scheduler-io.ts's file header) — this operates on
@@ -251,43 +251,53 @@ function schedLeaseCli(options = {}) {
251
251
  const now = nowIso(options);
252
252
  const policy = loadSchedulingPolicy(registry).policy;
253
253
  const limit = options.limit === undefined ? undefined : Number(options.limit);
254
- const { entries, leases } = applyLease(registry.loadQueueEntries(), policy, now, limit);
255
- registry.saveQueueEntries(entries);
256
- return { schemaVersion: 1, now, granted: leases.length, leases };
254
+ return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
255
+ const { entries, leases } = applyLease(registry.loadQueueEntries(), policy, now, limit);
256
+ registry.saveQueueEntries(entries);
257
+ return { schemaVersion: 1, now, granted: leases.length, leases };
258
+ });
257
259
  }
258
260
  function schedReleaseCli(leaseId, options = {}) {
259
261
  const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
260
262
  const now = nowIso(options);
261
263
  const failed = isTrue(options.failed);
262
264
  const reason = typeof options.reason === "string" ? options.reason : undefined;
263
- const { entries, matched } = leaseRelease(registry.loadQueueEntries(), leaseId, loadSchedulingPolicy(registry).policy, now, { failed, reason });
264
- if (!matched)
265
- throw new Error(`No active lease to release: ${leaseId}`);
266
- registry.saveQueueEntries(entries);
267
- return { schemaVersion: 1, released: leaseId, failed };
265
+ return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
266
+ const { entries, matched } = leaseRelease(registry.loadQueueEntries(), leaseId, loadSchedulingPolicy(registry).policy, now, { failed, reason });
267
+ if (!matched)
268
+ throw new Error(`No active lease to release: ${leaseId}`);
269
+ registry.saveQueueEntries(entries);
270
+ return { schemaVersion: 1, released: leaseId, failed };
271
+ });
268
272
  }
269
273
  function schedCompleteCli(leaseId, options = {}) {
270
274
  const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
271
- const { entries, matched } = leaseComplete(registry.loadQueueEntries(), leaseId, nowIso(options));
272
- if (!matched)
273
- throw new Error(`No active lease to complete: ${leaseId}`);
274
- registry.saveQueueEntries(entries);
275
- return { schemaVersion: 1, completed: leaseId };
275
+ return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
276
+ const { entries, matched } = leaseComplete(registry.loadQueueEntries(), leaseId, nowIso(options));
277
+ if (!matched)
278
+ throw new Error(`No active lease to complete: ${leaseId}`);
279
+ registry.saveQueueEntries(entries);
280
+ return { schemaVersion: 1, completed: leaseId };
281
+ });
276
282
  }
277
283
  function schedReclaimCli(options = {}) {
278
284
  const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
279
285
  const now = nowIso(options);
280
- const { entries, reclaimed } = reclaimExpired(registry.loadQueueEntries(), loadSchedulingPolicy(registry).policy, now);
281
- registry.saveQueueEntries(entries);
282
- return { schemaVersion: 1, now, reclaimed };
286
+ return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
287
+ const { entries, reclaimed } = reclaimExpired(registry.loadQueueEntries(), loadSchedulingPolicy(registry).policy, now);
288
+ registry.saveQueueEntries(entries);
289
+ return { schemaVersion: 1, now, reclaimed };
290
+ });
283
291
  }
284
292
  function schedResetCli(id, options = {}) {
285
293
  const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
286
- const { entries, matched } = resetEntry(registry.loadQueueEntries(), id);
287
- if (!matched)
288
- throw new Error(`No parked entry to reset: ${id}`);
289
- registry.saveQueueEntries(entries);
290
- return { schemaVersion: 1, reset: id };
294
+ return (0, fs_atomic_1.withFileLock)(registry.queueFilePath(), () => {
295
+ const { entries, matched } = resetEntry(registry.loadQueueEntries(), id);
296
+ if (!matched)
297
+ throw new Error(`No parked entry to reset: ${id}`);
298
+ registry.saveQueueEntries(entries);
299
+ return { schemaVersion: 1, reset: id };
300
+ });
291
301
  }
292
302
  function schedPolicyShowCli(options = {}) {
293
303
  const registry = new run_registry_io_1.RunRegistry(resolveCwd(options));
@@ -4,7 +4,7 @@
4
4
  // math lives in core/trust/telemetry-ledger.ts.
5
5
  //
6
6
  // MILESTONE 8. Byte-exact port of the old build's src/telemetry-ledger.ts
7
- // IO-touching half. CRITICAL invariant (v2/PLAN.md byte-compat item 12):
7
+ // IO-touching half. CRITICAL invariant (docs/rebuild/PLAN.md byte-compat item 12):
8
8
  // an ABSENT telemetry.json is an empty, clean-verifying chain
9
9
  // (`present:false`); a PRESENT but unparseable one is CORRUPT — reads
10
10
  // report `telemetry-ledger-corrupt`, and append THROWS
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // shell/term.ts — zero-dependency terminal styling.
3
3
  //
4
- // MILESTONE 5 (v2/PLAN.md build order, step 5, doctor/fix): TTY-gated
4
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5, doctor/fix): TTY-gated
5
5
  // ANSI formatting, so a piped run (every conformance case pipes with
6
6
  // NO_COLOR=1) prints plain text. EXTENDED at MILESTONE 11 (reporting/
7
7
  // observability) with the rest of the old build's src/term.ts: indent,
@@ -13,7 +13,7 @@
13
13
  // the era-rule check + trustAuditGenesis on top, and switches
14
14
  // computeEventHash to use core/hash.ts's named `eventHashInput` export
15
15
  // (byte-identical behavior to the pre-existing inline JSON-round-trip,
16
- // now shared with the rest of the hash-dedup story per v2/PLAN.md byte-
16
+ // now shared with the rest of the hash-dedup story per docs/rebuild/PLAN.md byte-
17
17
  // compat item 2).
18
18
  //
19
19
  // Evidence: SPEC/ledger-trust.md "Trust-audit chain", invariant 10 (era
@@ -97,7 +97,7 @@ function trustAuditGenesis(runId) {
97
97
  * itself, via core/hash.ts's `eventHashInput` (the JSON round-trip
98
98
  * pre-pass that drops nested `undefined`-valued keys BEFORE the
99
99
  * sort-and-stringify step — not a formatting flag on the same shape,
100
- * see v2/PLAN.md byte-compat item 2). Hashing the PERSISTED form this
100
+ * see docs/rebuild/PLAN.md byte-compat item 2). Hashing the PERSISTED form this
101
101
  * way makes record-time hashes equal verify-time (parsed-from-disk)
102
102
  * hashes. */
103
103
  function computeEventHash(event) {
@@ -142,7 +142,7 @@ function listTrustAuditEvents(run) {
142
142
  * hash are reported as `unchained` (skipped), NOT treated as a forgery
143
143
  * — they predate the chain.
144
144
  *
145
- * ERA RULE (v2/PLAN.md, SPEC/ledger-trust.md invariant 10): a single
145
+ * ERA RULE (docs/rebuild/PLAN.md, SPEC/ledger-trust.md invariant 10): a single
146
146
  * run is written by one code version, so a log is all-chained (chain
147
147
  * era) or all-legacy (pre-chain). An unchained (eventHash-less) line
148
148
  * mixed into an otherwise-chained log is a forgery attempt — dropping
@@ -277,6 +277,7 @@ function recordTrustAuditEvent(run, input) {
277
277
  normalizedPath: input.normalizedPath ? path.resolve(input.normalizedPath) : undefined,
278
278
  command: input.command,
279
279
  networkTarget: input.networkTarget,
280
+ envVars: input.envVars?.filter(Boolean).sort(),
280
281
  evidence: normalizeEvidence(run, input.evidence || [], { source: input.source, workerId: input.workerId, taskId: input.taskId, resultNodeId: input.nodeId }),
281
282
  evidenceRefs: unique(input.evidenceRefs || []).sort(),
282
283
  parentEventIds: unique(input.parentEventIds || []).sort(),
@@ -233,8 +233,16 @@ class WorkbenchHost {
233
233
  /** `cw workbench serve` entry point: `--once`/`--json` prints ONLY the
234
234
  * descriptor (compact JSON) and starts nothing; the default binds and
235
235
  * blocks, printing one compact line (`{...descriptor, boundPort}`)
236
- * once listening. */
236
+ * once listening. `--require-token` is a strict opt-in: default behavior
237
+ * (unauthenticated local reads when no token is configured) is
238
+ * unchanged unless the caller explicitly asks to fail closed. */
237
239
  async run() {
240
+ const requireToken = Boolean(this.args.requireToken || this.args["require-token"]);
241
+ if (requireToken && !this.token) {
242
+ process.stderr.write("workbench serve --require-token: CW_WORKBENCH_TOKEN is not set; refusing to start.\n");
243
+ process.exitCode = 1;
244
+ return;
245
+ }
238
246
  const once = Boolean(this.args.once || this.args.json);
239
247
  if (once) {
240
248
  process.stdout.write(`${JSON.stringify(this.descriptor(true))}\n`);
@@ -116,18 +116,13 @@ function buildWorkbenchRunView(runId, args = {}) {
116
116
  }
117
117
  return { schemaVersion: 1, surface: "workbench", runId, resolved, ...(resolveError ? { error: resolveError } : {}), panels };
118
118
  }
119
+ /** Package-relative resolution only — never falls back to the invocation
120
+ * cwd. `ui/` ships as a sibling of `dist/` in the published package
121
+ * (package.json's `files`), so from `dist/shell/workbench.js` the fixed
122
+ * path is two levels up. Matches the existing precedent in
123
+ * execution-backend/agent.ts's BATCH_DELEGATE_CHILD_SCRIPT resolution. */
119
124
  function workbenchUiRoot() {
120
- let dir = __dirname;
121
- for (let i = 0; i < 8; i++) {
122
- const candidate = path.join(dir, "plugins", "cool-workflow", exports.WORKBENCH_UI_RELATIVE);
123
- if (require("node:fs").existsSync(candidate))
124
- return candidate;
125
- const parent = path.dirname(dir);
126
- if (parent === dir)
127
- break;
128
- dir = parent;
129
- }
130
- return path.join(process.cwd(), exports.WORKBENCH_UI_RELATIVE);
125
+ return path.resolve(__dirname, "..", "..", exports.WORKBENCH_UI_RELATIVE);
131
126
  }
132
127
  const WORKBENCH_ROUTES = [
133
128
  { method: "GET", path: "/", description: "Index page (or the UI's index.html, when installed)." },
@@ -415,3 +415,5 @@ The one-command `cw -q` headline now routes the question and defaults the repo t
415
415
  0.1.98
416
416
 
417
417
  0.2.0
418
+
419
+ 0.2.1
@@ -626,3 +626,5 @@ CLI golden-path fixes: `cw -q "…"` routes the question (was read as an app id
626
626
  0.1.98
627
627
 
628
628
  0.2.0
629
+
630
+ 0.2.1
@@ -175,3 +175,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
175
175
  0.1.98
176
176
 
177
177
  0.2.0
178
+
179
+ 0.2.1
@@ -159,3 +159,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
159
159
  0.1.98
160
160
 
161
161
  0.2.0
162
+
163
+ 0.2.1
@@ -158,3 +158,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
158
158
  0.1.98
159
159
 
160
160
  0.2.0
161
+
162
+ 0.2.1
@@ -319,3 +319,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
319
319
  0.1.98
320
320
 
321
321
  0.2.0
322
+
323
+ 0.2.1
@@ -349,3 +349,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
349
349
  0.1.98
350
350
 
351
351
  0.2.0
352
+
353
+ 0.2.1
@@ -325,3 +325,5 @@ The host-facing surface tracks the CLI golden-path fixes (`cw -q` routing + repo
325
325
  0.1.98
326
326
 
327
327
  0.2.0
328
+
329
+ 0.2.1
@@ -351,3 +351,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
351
351
  0.1.98
352
352
 
353
353
  0.2.0
354
+
355
+ 0.2.1
@@ -363,3 +363,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
363
363
  0.1.98
364
364
 
365
365
  0.2.0
366
+
367
+ 0.2.1
@@ -184,3 +184,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
184
184
  0.1.98
185
185
 
186
186
  0.2.0
187
+
188
+ 0.2.1
@@ -113,7 +113,8 @@ read-only metrics panel from the same payload, showing coverage and
113
113
  - `cw metrics summary` — the cross-repo rollup over the v0.1.28 run registry:
114
114
  pooled rates, summed attested usage/cost with coverage, and per-app and
115
115
  per-backend breakdowns. `--scope repo|home`; runs that cannot be read are counted
116
- (`unreadableRuns`), never quietly dropped.
116
+ (`unreadableRuns`), never quietly dropped. `--limit N` caps how many run
117
+ states are loaded (default 50, matching `run list`/`run search`'s own floor).
117
118
 
118
119
  MCP hosts call `cw_metrics_show` and `cw_metrics_summary` with the same
119
120
  payloads. Old runs load and report `unreported` cost while still giving correct
@@ -243,3 +244,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
243
244
  0.1.98
244
245
 
245
246
  0.2.0
247
+
248
+ 0.2.1
@@ -1,15 +1,15 @@
1
1
  # Cool Workflow Project Index
2
2
 
3
- Generated from the current repository code on 2026-07-05 by `npm run sync:project-index`.
3
+ Generated from the current repository code on 2026-07-07 by `npm run sync:project-index`.
4
4
 
5
5
  ## Snapshot
6
6
 
7
7
  - Package: `cool-workflow`
8
- - Version: `0.2.0`
8
+ - Version: `0.2.1`
9
9
  - Source modules: `129`
10
10
  - Workflow apps: `8`
11
11
  - Docs: `61`
12
- - Smoke tests: `173`
12
+ - Smoke tests: `178`
13
13
  - Repository: https://github.com/coo1white/cool-workflow
14
14
 
15
15
  ## Architecture
@@ -263,6 +263,8 @@ multi-agent host -> topology -> blackboard/coordinator
263
263
 
264
264
  Smoke tests mirror the public contracts. The high-signal suites are:
265
265
 
266
+ - [agent-backend-concurrent-user-env-smoke.js](../test/agent-backend-concurrent-user-env-smoke.js)
267
+ - [agent-backend-user-env-smoke.js](../test/agent-backend-user-env-smoke.js)
266
268
  - [agent-config-atomic-write-smoke.js](../test/agent-config-atomic-write-smoke.js)
267
269
  - [agent-delegation-drive-smoke.js](../test/agent-delegation-drive-smoke.js)
268
270
  - [agent-stream-gate-smoke.js](../test/agent-stream-gate-smoke.js)
@@ -270,6 +272,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
270
272
  - [architecture-review-fast-automation-smoke.js](../test/architecture-review-fast-automation-smoke.js)
271
273
  - [architecture-review-fast-phase-cache-smoke.js](../test/architecture-review-fast-phase-cache-smoke.js)
272
274
  - [architecture-review-fast-smoke.js](../test/architecture-review-fast-smoke.js)
275
+ - [architecture-review-question-aware-smoke.js](../test/architecture-review-question-aware-smoke.js)
273
276
  - [artifact-integrity-smoke.js](../test/artifact-integrity-smoke.js)
274
277
  - [audit-verify-smoke.js](../test/audit-verify-smoke.js)
275
278
  - [backend-registry-smoke.js](../test/backend-registry-smoke.js)
@@ -339,6 +342,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
339
342
  - [mcp-app-surface-smoke.js](../test/mcp-app-surface-smoke.js)
340
343
  - [mcp-surface-registry-smoke.js](../test/mcp-surface-registry-smoke.js)
341
344
  - [mcp-tool-call-coverage-smoke.js](../test/mcp-tool-call-coverage-smoke.js)
345
+ - [metrics-summary-limit-smoke.js](../test/metrics-summary-limit-smoke.js)
342
346
  - [multi-agent-cli-mcp-surface-smoke.js](../test/multi-agent-cli-mcp-surface-smoke.js)
343
347
  - [multi-agent-eval-determinism-regression-smoke.js](../test/multi-agent-eval-determinism-regression-smoke.js)
344
348
  - [multi-agent-eval-replay-harness-smoke.js](../test/multi-agent-eval-replay-harness-smoke.js)
@@ -410,6 +414,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
410
414
  - [sandbox-profile-smoke.js](../test/sandbox-profile-smoke.js)
411
415
  - [sched-policy-validation-smoke.js](../test/sched-policy-validation-smoke.js)
412
416
  - [schedule-routine-daemon-smoke.js](../test/schedule-routine-daemon-smoke.js)
417
+ - [scheduling-routine-lock-concurrency-smoke.js](../test/scheduling-routine-lock-concurrency-smoke.js)
413
418
  - [schema-validation-smoke.js](../test/schema-validation-smoke.js)
414
419
  - [security-trust-hardening-smoke.js](../test/security-trust-hardening-smoke.js)
415
420
  - [self-audit-hardening-smoke.js](../test/self-audit-hardening-smoke.js)
@@ -191,3 +191,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
191
191
  0.1.98
192
192
 
193
193
  0.2.0
194
+
195
+ 0.2.1
@@ -331,3 +331,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
331
331
  0.1.98
332
332
 
333
333
  0.2.0
334
+
335
+ 0.2.1
@@ -294,3 +294,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
294
294
  0.1.98
295
295
 
296
296
  0.2.0
297
+
298
+ 0.2.1
@@ -1,15 +1,15 @@
1
1
  # Remote-Source Review (`--link`)
2
2
 
3
3
  CW v0.1.91 lets you point a review at **any repository on the internet** instead
4
- of only a local path: `cw -q "what are the risks?" --link <url>`. CW materializes
4
+ of only a local path: `cw -q "how does this work?" --link <url>`. CW materializes
5
5
  the remote into a local checkout and runs the **existing** review pipeline against
6
6
  it — identical downstream to reviewing a folder. A URL passed to `-dir`/`--repo`
7
7
  is auto-detected, so `--link <url>` and `-dir <url>` are equivalent.
8
8
 
9
9
  ```bash
10
- cw -q "What are the risks?" --link https://github.com/owner/repo
11
- cw -q "What are the risks?" --link git@gitlab.com:owner/repo.git --ref v1.2.0
12
- cw -q "What are the risks?" --link https://github.com/owner/repo/archive/refs/heads/main.tar.gz
10
+ cw -q "How does auth work end-to-end here?" --link https://github.com/owner/repo
11
+ cw -q "What are the security risks?" --link git@gitlab.com:owner/repo.git --ref v1.2.0
12
+ cw -q "Is it safe to swap this queue for Redis?" --link https://github.com/owner/repo/archive/refs/heads/main.tar.gz
13
13
  cw -q "..." --link <url> --check # validate the URL + tooling WITHOUT fetching
14
14
  ```
15
15
 
@@ -127,7 +127,7 @@ npx cool-workflow demo bundle
127
127
  # catching both offline with only the embedded public key.
128
128
 
129
129
  # Run the review AND get a shippable, client-verifiable bundle from ONE command:
130
- cw quickstart architecture-review --repo . --question "What are the risks?" \
130
+ cw quickstart architecture-review --repo . --question "How does auth work end-to-end here?" \
131
131
  --agent-command "claude -p" --bundle --with-trust-key ./trust-pub.pem
132
132
  # -> after the drive COMPLETES, the run is sealed into a self-verified bundle and
133
133
  # the verdict is folded into the quickstart JSON (result.bundle). Exits non-zero
@@ -474,3 +474,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
474
474
  0.1.98
475
475
 
476
476
  0.2.0
477
+
478
+ 0.2.1
@@ -263,3 +263,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
263
263
  0.1.98
264
264
 
265
265
  0.2.0
266
+
267
+ 0.2.1
@@ -37,7 +37,9 @@ Event sources are clear:
37
37
  - `runtime-derived`: CW got the event from run state.
38
38
 
39
39
  CW does not keep secrets or raw environment values. Environment audit records
40
- keep names only.
40
+ keep names only — for example, `worker.agent-env` records which provider-namespace
41
+ env var NAMES (`CW_`, `ANTHROPIC_`, `OPENAI_`, and similar) were forwarded from
42
+ the host process to a delegated agent child, queryable via `cw audit summary`/`cw audit`.
41
43
 
42
44
  ## Enforcement Boundary
43
45
 
@@ -320,3 +320,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
320
320
  0.1.98
321
321
 
322
322
  0.2.0
323
+
324
+ 0.2.1
@@ -256,3 +256,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
256
256
  0.1.98
257
257
 
258
258
  0.2.0
259
+
260
+ 0.2.1
@@ -96,11 +96,21 @@ The console is read-only. It offers no actions. If a later release adds an actio
96
96
  capability core entry that already exists — never a side code path — so it cannot drift from the
97
97
  CLI/MCP and is covered by the parity gate.
98
98
 
99
+ Authentication is opt-in, not on by default: set `CW_WORKBENCH_TOKEN` and every
100
+ request must carry it as `Authorization: Bearer <token>` or `?token=<token>`
101
+ (timing-safe compare); left unset, the loopback/read-only/GET-only controls
102
+ above are the only defense and any local process can read. Pass
103
+ `--require-token` to `cw workbench serve` to fail closed instead: the server
104
+ refuses to start at all unless `CW_WORKBENCH_TOKEN` is already set. This is a
105
+ strict opt-in — leaving it off keeps today's default behavior unchanged.
106
+ (`--require-token` has no effect on `cw_workbench_serve`'s MCP path, which
107
+ never actually binds a listener.)
108
+
99
109
  ## Surfaces
100
110
 
101
111
  ```
102
112
  cw workbench view <run-id> [--json] # five-panel WorkbenchRunView for one run
103
- cw workbench serve [--port N] [--scope repo|home] [--once|--json]
113
+ cw workbench serve [--port N] [--scope repo|home] [--once|--json] [--require-token]
104
114
  ```
105
115
 
106
116
  `cw workbench serve` with `--once`/`--json` prints the serve descriptor (bind
@@ -264,3 +274,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
264
274
  0.1.98
265
275
 
266
276
  0.2.0
277
+
278
+ 0.2.1
@@ -2,7 +2,7 @@
2
2
  "_comment": "SINGLE SOURCE OF TRUTH for every vendor manifest. Edit THIS file, then run `npm run gen:manifests`. Do NOT hand-edit the generated vendor manifests (.claude-plugin/, .codex-plugin/, .agents/, .mcp.json) — `npm run gen:manifests -- --check` (run by release:check) will fail if they drift from this source.",
3
3
  "identity": {
4
4
  "name": "cool-workflow",
5
- "version": "0.2.0",
5
+ "version": "0.2.1",
6
6
  "license": "BSD-2-Clause",
7
7
  "homepage": "https://github.com/coo1white/cool-workflow",
8
8
  "author": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cool-workflow",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "bin": {
5
5
  "cool-workflow": "scripts/cw.js",
6
6
  "cw": "scripts/cw.js"
@@ -18,7 +18,7 @@ cw:result block that EXACTLY follows this schema:
18
18
  "findings": [
19
19
  {
20
20
  "id": "unique-kebab-id",
21
- "title": "short risk title",
21
+ "title": "short finding title",
22
22
  "severity": "P0",
23
23
  "classification": "real",
24
24
  "evidence": ["path/to/file.ts:42"]
@@ -471,6 +471,25 @@ function persistStderr(resultPath, text) {
471
471
  }
472
472
  }
473
473
 
474
+ // Build the failure-path diagnostic string for a non-zero exit. Real OS-level
475
+ // stderr wins when present (the common case: a crash, a killed process, a
476
+ // vendor CLI that DOES write to stderr) — byte-identical to before this
477
+ // helper existed. When stderr is EMPTY, fall back to whatever partial/
478
+ // structured text the wrapper already parsed from stdout before the child
479
+ // died (a stream-json `result` event with is_error:true is how claude
480
+ // reports terminal errors like an auth failure — that text was being parsed
481
+ // and then silently thrown away). If there is no partial text either, say so
482
+ // plainly instead of just repeating the bare exit code. Never throws; always
483
+ // returns a non-empty string.
484
+ function buildFailureDetail({ label, code, childStderr, partialText }) {
485
+ const stderrText = String(childStderr || "").trim();
486
+ if (stderrText) return stderrText;
487
+ const partial = String(partialText || "").trim();
488
+ const codeText = `${label} exited ${code === null ? "(timeout/killed)" : code}`;
489
+ if (partial) return `${codeText} — ${label} stdout (no stderr) said: ${partial}`;
490
+ return codeText;
491
+ }
492
+
474
493
  module.exports = {
475
494
  RESULT_CONTRACT,
476
495
  buildPrompt,
@@ -485,5 +504,6 @@ module.exports = {
485
504
  flushJsonLines,
486
505
  writeResult,
487
506
  emitReport,
488
- persistStderr // save a failed agent's stderr to <workerDir>/logs/agent-stderr.log (shared by all wrappers)
507
+ persistStderr, // save a failed agent's stderr to <workerDir>/logs/agent-stderr.log (shared by all wrappers)
508
+ buildFailureDetail // fold in parsed partial stdout text when real stderr is empty (shared by all wrappers)
489
509
  };