cool-workflow 0.1.97 → 0.1.98

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 (55) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/apps/architecture-review/app.json +1 -1
  4. package/apps/architecture-review-fast/app.json +1 -1
  5. package/apps/end-to-end-golden-path/app.json +1 -1
  6. package/apps/pr-review-fix-ci/app.json +1 -1
  7. package/apps/release-cut/app.json +1 -1
  8. package/apps/research-synthesis/app.json +1 -1
  9. package/dist/capability-core.js +3 -0
  10. package/dist/capability-registry.js +7 -1
  11. package/dist/cli/command-surface.js +4 -0
  12. package/dist/cli/handlers/ledger.js +169 -0
  13. package/dist/drive.js +98 -61
  14. package/dist/execution-backend/agent.js +84 -24
  15. package/dist/ledger.js +313 -0
  16. package/dist/mcp/tool-call.js +36 -0
  17. package/dist/mcp/tool-definitions.js +26 -0
  18. package/dist/orchestrator/lifecycle-operations.js +37 -13
  19. package/dist/orchestrator.js +11 -3
  20. package/dist/remote-source.js +10 -3
  21. package/dist/run-export.js +35 -4
  22. package/dist/version.js +1 -1
  23. package/dist/workbench-host.js +11 -1
  24. package/docs/agent-delegation-drive.7.md +2 -0
  25. package/docs/cli-mcp-parity.7.md +14 -2
  26. package/docs/contract-migration-tooling.7.md +2 -0
  27. package/docs/control-plane-scheduling.7.md +2 -0
  28. package/docs/cross-agent-ledger.7.md +217 -0
  29. package/docs/designs/handoff-ledger.md +145 -0
  30. package/docs/durable-state-and-locking.7.md +2 -0
  31. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  32. package/docs/execution-backends.7.md +2 -0
  33. package/docs/handoff-setup.md +120 -0
  34. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  35. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  36. package/docs/multi-agent-operator-ux.7.md +2 -0
  37. package/docs/node-snapshot-diff-replay.7.md +2 -0
  38. package/docs/observability-cost-accounting.7.md +2 -0
  39. package/docs/project-index.md +16 -5
  40. package/docs/real-execution-backends.7.md +2 -0
  41. package/docs/release-and-migration.7.md +2 -0
  42. package/docs/release-tooling.7.md +2 -0
  43. package/docs/run-registry-control-plane.7.md +2 -0
  44. package/docs/run-retention-reclamation.7.md +2 -0
  45. package/docs/state-explosion-management.7.md +2 -0
  46. package/docs/team-collaboration.7.md +2 -0
  47. package/docs/web-desktop-workbench.7.md +2 -0
  48. package/manifest/plugin.manifest.json +1 -1
  49. package/package.json +1 -1
  50. package/scripts/agents/codex-agent.js +34 -4
  51. package/scripts/canonical-apps.js +4 -4
  52. package/scripts/children/batch-delegate-child.js +30 -10
  53. package/scripts/dogfood-release.js +1 -1
  54. package/scripts/golden-path.js +4 -4
  55. package/scripts/release-flow.js +7 -1
@@ -0,0 +1,120 @@
1
+ # Handoff ledger — shared-repo setup (T2a)
2
+
3
+ How to stand up the shared repo that carries `cw ledger` entries between two
4
+ agents scoped to two separate repos (e.g. `cool-workflow` and `chime`), each
5
+ running in its own cloud session. The verbs are documented in
6
+ [cross-agent-ledger](cross-agent-ledger.7.md); this is the operator runbook.
7
+
8
+ Examples are portable — replace `<owner>`, `<src-repo>`, and the paths with your
9
+ own, and keep tokens in environment variables, never in files or commit text.
10
+
11
+ ## What only the operator can do
12
+
13
+ - A cool-workflow-scoped web session cannot create the shared repo — the GitHub
14
+ integration returns `403 Resource not accessible by integration` for any repo
15
+ outside its scope. Create it yourself.
16
+ - Scoping the two agent environments (or granting them git credentials) is a
17
+ Claude Code web-UI step; it cannot be done from inside a session.
18
+
19
+ ## Choosing a host: GitHub vs self-hosted (Gitea)
20
+
21
+ The transport is git-host-agnostic — the kernel has no git logic, so any git
22
+ remote works. The choice is about reachability and operations, not code.
23
+
24
+ | | GitHub (private repo) | Self-hosted Gitea (your VPS) |
25
+ |---|---|---|
26
+ | Reachability from cloud sessions | github.com is in the default **Trusted** allowlist — works with no network-policy change | Your VPS host is **not** in the default allowlist — the environment's network access must be configured to permit it |
27
+ | Scope wall | The GitHub MCP scope is per-repo; the ledger uses plain git (not MCP) so it works, but it runs against the grain of the scoping model | Not a GitHub repo at all, so the GitHub scope wall does not apply |
28
+ | Limits / quota | Disable Actions on this repo (it needs no CI) so it burns no minutes; git push/pull is not API-rate-limited; ledger traffic is tiny | Fully self-controlled, unlimited |
29
+ | Operations | Managed, backed up, zero maintenance | You run it: uptime, backups, TLS cert, patching |
30
+ | Data location | GitHub's servers (private) | Your own hardware |
31
+
32
+ **Recommendation.** Start on **GitHub private** — it is reachable out of the box
33
+ and the quota worry is practically moot for tiny ledger traffic. Move to **Gitea**
34
+ if you want full self-hosting AND have confirmed the cloud environment can reach
35
+ your VPS through its network policy (the deciding prerequisite). Migrating later
36
+ is only a change of git remote — no code change.
37
+
38
+ ## GitHub private — setup
39
+
40
+ 1. **Token.** GitHub → Settings → Developer settings → Personal access tokens →
41
+ Fine-grained. Repository access: only `<owner>/handoff`. Permissions:
42
+ Contents = Read and write. Copy the token.
43
+ 2. **Repo.** Create `<owner>/handoff`, private, initialized with a README. In
44
+ Settings → Actions → General, disable Actions (no CI needed → no minutes).
45
+ 3. **Environments.** In each agent environment (both the `cool-workflow` and the
46
+ `chime` environment), add an environment variable `GH_TOKEN=<token>` (`.env`
47
+ format, no quotes). A new session is required for it to take effect.
48
+ 4. **Optional** — put the clone in each environment's setup script so the ledger
49
+ is present at session start:
50
+ ```bash
51
+ #!/bin/bash
52
+ git clone https://oauth2:${GH_TOKEN}@github.com/<owner>/handoff.git /home/user/handoff || true
53
+ ```
54
+
55
+ ## Gitea (self-hosted) — setup
56
+
57
+ Same shape, two extra prerequisites:
58
+
59
+ 1. Serve Gitea over HTTPS with a valid certificate (e.g. Let's Encrypt) so the
60
+ cloud VM's git can verify it.
61
+ 2. Configure the agent environment's **network access** to permit your VPS host —
62
+ the default Trusted allowlist does not include it. If the loop cannot reach
63
+ the VPS, it cannot run.
64
+ 3. Create a Gitea access token, store it as an environment variable, and clone
65
+ with an authenticated remote (`https://<user>:${GIT_TOKEN}@<vps-host>/<owner>/handoff.git`).
66
+
67
+ ## Directory convention
68
+
69
+ Entries live under `ledger/` in the shared repo, one file per entry named by its
70
+ id:
71
+
72
+ ```
73
+ handoff/
74
+ ledger/
75
+ ldg-1de7c92172af1871.json
76
+ ldg-2315e4b33b9a812f.json
77
+ ```
78
+
79
+ ## The loop
80
+
81
+ Producing side (propose a change, hand it over):
82
+
83
+ ```bash
84
+ entry=$(cw ledger propose --from cool-workflow --to chime \
85
+ --title "Add retry" --rationale "flaky net" \
86
+ --files src/net.ts --diff "$(git -C <src-repo> diff)")
87
+ id=$(printf '%s' "$entry" | jq -r .id)
88
+ printf '%s\n' "$entry" > /home/user/handoff/ledger/$id.json
89
+ git -C /home/user/handoff add ledger/$id.json
90
+ git -C /home/user/handoff commit -m "propose $id"
91
+ git -C /home/user/handoff push
92
+ ```
93
+
94
+ Note the single `cw ledger propose` call captured into `$entry` — calling it
95
+ twice would mint two different entries (each carries a fresh `createdAt`).
96
+
97
+ Consuming side (verify the inbox, then act or review back):
98
+
99
+ ```bash
100
+ git -C /home/user/handoff pull
101
+ cw ledger list --dir /home/user/handoff/ledger && echo "inbox verified — safe to act"
102
+
103
+ # hand a verdict back:
104
+ entry=$(cw ledger review --from chime --to cool-workflow \
105
+ --target ldg-1de7c92172af1871 --verdict approved --findings "tests pass,scope ok")
106
+ id=$(printf '%s' "$entry" | jq -r .id)
107
+ printf '%s\n' "$entry" > /home/user/handoff/ledger/$id.json
108
+ git -C /home/user/handoff add ledger/$id.json
109
+ git -C /home/user/handoff commit -m "review $id"
110
+ git -C /home/user/handoff push
111
+ ```
112
+
113
+ ## Notes
114
+
115
+ - Keep private code out of a **public** handoff repo: omit `--diff` and reference
116
+ a commit/branch in the private source repo instead, so only metadata + a
117
+ pointer is exposed. On a private handoff repo, full diffs are fine.
118
+ - The other side may build entries without `cw` as long as they match the
119
+ digest/id rules in [cross-agent-ledger](cross-agent-ledger.7.md); otherwise
120
+ `cw ledger verify` refuses them with `ledger-digest-mismatch`.
@@ -321,3 +321,5 @@ The host-facing surface tracks the CLI golden-path fixes (`cw -q` routing + repo
321
321
  0.1.96
322
322
 
323
323
  0.1.97
324
+
325
+ 0.1.98
@@ -347,3 +347,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
347
347
  0.1.96
348
348
 
349
349
  0.1.97
350
+
351
+ 0.1.98
@@ -359,3 +359,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
359
359
  0.1.96
360
360
 
361
361
  0.1.97
362
+
363
+ 0.1.98
@@ -180,3 +180,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
180
180
  0.1.96
181
181
 
182
182
  0.1.97
183
+
184
+ 0.1.98
@@ -239,3 +239,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
239
239
  0.1.96
240
240
 
241
241
  0.1.97
242
+
243
+ 0.1.98
@@ -1,15 +1,15 @@
1
1
  # Cool Workflow Project Index
2
2
 
3
- Generated from the current repository code on 2026-06-30 by `npm run sync:project-index`.
3
+ Generated from the current repository code on 2026-07-03 by `npm run sync:project-index`.
4
4
 
5
5
  ## Snapshot
6
6
 
7
7
  - Package: `cool-workflow`
8
- - Version: `0.1.97`
9
- - Source modules: `68`
8
+ - Version: `0.1.98`
9
+ - Source modules: `69`
10
10
  - Workflow apps: `8`
11
- - Docs: `59`
12
- - Smoke tests: `164`
11
+ - Docs: `61`
12
+ - Smoke tests: `172`
13
13
  - Repository: https://github.com/coo1white/cool-workflow
14
14
 
15
15
  ## Architecture
@@ -93,6 +93,7 @@ multi-agent host -> topology -> blackboard/coordinator
93
93
  - [evidence-reasoning.ts](../src/evidence-reasoning.ts)
94
94
  - [execution-backend.ts](../src/execution-backend.ts)
95
95
  - [gates.ts](../src/gates.ts)
96
+ - [ledger.ts](../src/ledger.ts)
96
97
  - [loop-expansion.ts](../src/loop-expansion.ts)
97
98
  - [mcp-surface.ts](../src/mcp-surface.ts)
98
99
  - [multi-agent-eval.ts](../src/multi-agent-eval.ts)
@@ -145,6 +146,7 @@ multi-agent host -> topology -> blackboard/coordinator
145
146
  - [Contract Migration Tooling](contract-migration-tooling.7.md)
146
147
  - [Control-Plane Scheduling](control-plane-scheduling.7.md)
147
148
  - [Coordinator / Blackboard](coordinator-blackboard.7.md)
149
+ - [Cross-Agent Handoff Ledger](cross-agent-ledger.7.md)
148
150
  - [DEMO(7)](demo.7.md)
149
151
  - [DOCTOR(7)](doctor.7.md)
150
152
  - [Dogfood One Real Repo](dogfood-one-real-repo.7.md)
@@ -155,6 +157,7 @@ multi-agent host -> topology -> blackboard/coordinator
155
157
  - [EXECUTION-BACKENDS(7)](execution-backends.7.md)
156
158
  - [FIX(7)](fix.7.md)
157
159
  - [Getting Started](getting-started.md)
160
+ - [Handoff ledger — shared-repo setup (T2a)](handoff-setup.md)
158
161
  - [Cool Workflow Docs](index.md)
159
162
  - [INIT(7)](init.7.md)
160
163
  - [MCP App Surface](mcp-app-surface.7.md)
@@ -210,6 +213,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
210
213
  - [artifact-integrity-smoke.js](../test/artifact-integrity-smoke.js)
211
214
  - [audit-verify-smoke.js](../test/audit-verify-smoke.js)
212
215
  - [backend-registry-smoke.js](../test/backend-registry-smoke.js)
216
+ - [batch-output-overflow-smoke.js](../test/batch-output-overflow-smoke.js)
213
217
  - [blackboard-state-explosion-management-smoke.js](../test/blackboard-state-explosion-management-smoke.js)
214
218
  - [block-unapproved-tag-smoke.js](../test/block-unapproved-tag-smoke.js)
215
219
  - [budget-scaling-loop-smoke.js](../test/budget-scaling-loop-smoke.js)
@@ -235,6 +239,7 @@ Smoke tests mirror the public contracts. The high-signal suites are:
235
239
  - [collaboration-ops-unit-smoke.js](../test/collaboration-ops-unit-smoke.js)
236
240
  - [concurrency-default-smoke.js](../test/concurrency-default-smoke.js)
237
241
  - [concurrent-failure-semantics-smoke.js](../test/concurrent-failure-semantics-smoke.js)
242
+ - [concurrent-subworkflow-cache-nesting-smoke.js](../test/concurrent-subworkflow-cache-nesting-smoke.js)
238
243
  - [concurrent-workflow-dsl-smoke.js](../test/concurrent-workflow-dsl-smoke.js)
239
244
  - [contract-migration-tooling-smoke.js](../test/contract-migration-tooling-smoke.js)
240
245
  - [control-plane-scheduling-smoke.js](../test/control-plane-scheduling-smoke.js)
@@ -242,11 +247,14 @@ Smoke tests mirror the public contracts. The high-signal suites are:
242
247
  - [cw-help-per-command-smoke.js](../test/cw-help-per-command-smoke.js)
243
248
  - [dead-export-removal-guard-smoke.js](../test/dead-export-removal-guard-smoke.js)
244
249
  - [deepseek-agent-wrapper-smoke.js](../test/deepseek-agent-wrapper-smoke.js)
250
+ - [deferred-checkpoint-batching-smoke.js](../test/deferred-checkpoint-batching-smoke.js)
245
251
  - [demo-bundle-smoke.js](../test/demo-bundle-smoke.js)
246
252
  - [det-ids-b-smoke.js](../test/det-ids-b-smoke.js)
247
253
  - [doctor-smoke.js](../test/doctor-smoke.js)
248
254
  - [dogfood-architecture-review-smoke.js](../test/dogfood-architecture-review-smoke.js)
249
255
  - [dogfood-release-smoke.js](../test/dogfood-release-smoke.js)
256
+ - [drive-concurrency-flag-smoke.js](../test/drive-concurrency-flag-smoke.js)
257
+ - [drive-exhaustion-blocked-smoke.js](../test/drive-exhaustion-blocked-smoke.js)
250
258
  - [durable-atomic-write-smoke.js](../test/durable-atomic-write-smoke.js)
251
259
  - [end-to-end-demo-smoke.js](../test/end-to-end-demo-smoke.js)
252
260
  - [end-to-end-golden-path-smoke.js](../test/end-to-end-golden-path-smoke.js)
@@ -264,6 +272,9 @@ Smoke tests mirror the public contracts. The high-signal suites are:
264
272
  - [h7-custom-profile-persist-smoke.js](../test/h7-custom-profile-persist-smoke.js)
265
273
  - [headline-commands-smoke.js](../test/headline-commands-smoke.js)
266
274
  - [incremental-resume-smoke.js](../test/incremental-resume-smoke.js)
275
+ - [ledger-apply-smoke.js](../test/ledger-apply-smoke.js)
276
+ - [ledger-resolution-smoke.js](../test/ledger-resolution-smoke.js)
277
+ - [ledger-verify-smoke.js](../test/ledger-verify-smoke.js)
267
278
  - [loop-bounded-expansion-smoke.js](../test/loop-bounded-expansion-smoke.js)
268
279
  - [mcp-app-surface-smoke.js](../test/mcp-app-surface-smoke.js)
269
280
  - [mcp-surface-registry-smoke.js](../test/mcp-surface-registry-smoke.js)
@@ -187,3 +187,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
187
187
  0.1.96
188
188
 
189
189
  0.1.97
190
+
191
+ 0.1.98
@@ -327,3 +327,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
327
327
  0.1.96
328
328
 
329
329
  0.1.97
330
+
331
+ 0.1.98
@@ -290,3 +290,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
290
290
  0.1.96
291
291
 
292
292
  0.1.97
293
+
294
+ 0.1.98
@@ -470,3 +470,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
470
470
  0.1.96
471
471
 
472
472
  0.1.97
473
+
474
+ 0.1.98
@@ -238,3 +238,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
238
238
  0.1.96
239
239
 
240
240
  0.1.97
241
+
242
+ 0.1.98
@@ -316,3 +316,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
316
316
  0.1.96
317
317
 
318
318
  0.1.97
319
+
320
+ 0.1.98
@@ -252,3 +252,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
252
252
  0.1.96
253
253
 
254
254
  0.1.97
255
+
256
+ 0.1.98
@@ -260,3 +260,5 @@ _No behavioral change in v0.1.89 (CLI-surface golden-path + help-output fixes on
260
260
  0.1.96
261
261
 
262
262
  0.1.97
263
+
264
+ 0.1.98
@@ -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.1.97",
5
+ "version": "0.1.98",
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.1.97",
3
+ "version": "0.1.98",
4
4
  "bin": {
5
5
  "cool-workflow": "scripts/cw.js",
6
6
  "cw": "scripts/cw.js"
@@ -21,6 +21,16 @@
21
21
  // per-run override that does NOT touch the user's interactive codex. Tune with
22
22
  // CW_CODEX_REASONING_EFFORT (default "low"); set it to "medium"/"high" to opt back
23
23
  // into more thinking.
24
+ //
25
+ // REVIEW MODE: the default low-effort / read-only sandbox is right for a fast
26
+ // delegated worker or a liveness probe — but WRONG for an independent RELEASE
27
+ // reviewer, which must actually RE-RUN the gate (build, tests, regenerate dist)
28
+ // to earn its verdict. A read-only sandbox can't execute that gate, so the model
29
+ // is structurally unable to verify and tends to fabricate a REJECTED. The release
30
+ // path therefore sets CW_RELEASE_REVIEW=1 (a vendor-agnostic signal from
31
+ // release-flow.js): on that signal this wrapper raises reasoning to "high" and
32
+ // opens the sandbox to "workspace-write" so codex can run the gate it is judging.
33
+ // Explicit CW_CODEX_REASONING_EFFORT / CW_CODEX_SANDBOX always win over the signal.
24
34
 
25
35
  const fs = require("node:fs");
26
36
  const os = require("node:os");
@@ -80,11 +90,31 @@ function recordJsonLine(line) {
80
90
  }
81
91
  }
82
92
 
83
- render.action("codex: reading the repo (read-only)…");
93
+ // A release review (CW_RELEASE_REVIEW=1) must execute the gate it judges, so it
94
+ // needs both stronger reasoning and a sandbox that can write inside the workspace.
95
+ // Explicit env overrides win; otherwise the review signal lifts the fast defaults.
96
+ const reviewMode = process.env.CW_RELEASE_REVIEW === "1";
84
97
 
85
98
  // Cap codex's reasoning effort for CW runs (speed) — overrides config.toml for
86
- // THIS invocation only. Default "low"; CW_CODEX_REASONING_EFFORT opts back up.
87
- const effort = process.env.CW_CODEX_REASONING_EFFORT || "low";
99
+ // THIS invocation only. Default "low"; CW_CODEX_REASONING_EFFORT opts back up, and
100
+ // a release review defaults to "high".
101
+ const effort = process.env.CW_CODEX_REASONING_EFFORT || (reviewMode ? "high" : "low");
102
+
103
+ // Sandbox: read-only is the POLA default (a worker/probe only reads). A release
104
+ // review opens to workspace-write so codex can build/test/regenerate the gate.
105
+ // CW_CODEX_SANDBOX overrides both; an unknown value fails closed (never silently
106
+ // downgraded to read-only, which would re-create the can't-verify failure mode).
107
+ const SANDBOX_MODES = new Set(["read-only", "workspace-write", "danger-full-access"]);
108
+ const sandbox = process.env.CW_CODEX_SANDBOX || (reviewMode ? "workspace-write" : "read-only");
109
+ if (!SANDBOX_MODES.has(sandbox)) {
110
+ process.stderr.write(
111
+ `codex-agent: invalid CW_CODEX_SANDBOX="${sandbox}" — expected one of ${[...SANDBOX_MODES].join(", ")}\n`
112
+ );
113
+ process.exit(2);
114
+ }
115
+
116
+ render.action(`codex: reading the repo (${sandbox})…`);
117
+
88
118
  const args = [
89
119
  "exec",
90
120
  "--json",
@@ -93,7 +123,7 @@ const args = [
93
123
  "--output-last-message",
94
124
  finalPath,
95
125
  "--sandbox",
96
- "read-only",
126
+ sandbox,
97
127
  "--color",
98
128
  "never",
99
129
  "-"
@@ -83,7 +83,7 @@ const canonicalApps = [
83
83
  "--source",
84
84
  "plugins/cool-workflow/docs/workflow-app-framework.7.md",
85
85
  "--scope",
86
- "Cool Workflow v0.1.97",
86
+ "Cool Workflow v0.1.98",
87
87
  "--freshness",
88
88
  "as of release preparation"
89
89
  ]
@@ -117,14 +117,14 @@ function main() {
117
117
  assert.ok(summary, `${app.id} must appear in app list`);
118
118
  assert.equal(summary.sourceKind, "app-directory");
119
119
  assert.equal(summary.legacy, false);
120
- assert.equal(summary.version, "0.1.97");
120
+ assert.equal(summary.version, "0.1.98");
121
121
 
122
122
  const validation = runJson(["app", "validate", manifestPath]);
123
123
  assert.equal(validation.valid, true, `${app.id} manifest must validate`);
124
124
 
125
125
  const shown = runJson(["app", "show", app.id]);
126
126
  assert.equal(shown.app.id, app.id);
127
- assert.equal(shown.app.version, "0.1.97");
127
+ assert.equal(shown.app.version, "0.1.98");
128
128
  assert.ok(shown.app.metadata.canonical, `${app.id} must be marked canonical`);
129
129
  assert.ok(shown.app.sandboxProfiles.length > 0, `${app.id} must declare sandbox profiles`);
130
130
  assertTaskIdsUnique(shown);
@@ -135,7 +135,7 @@ function main() {
135
135
  const plan = runJson(["plan", app.id, ...app.args(workspace)]);
136
136
  const state = JSON.parse(fs.readFileSync(plan.statePath, "utf8"));
137
137
  assert.equal(state.workflow.app.id, app.id);
138
- assert.equal(state.workflow.app.version, "0.1.97");
138
+ assert.equal(state.workflow.app.version, "0.1.98");
139
139
  assert.equal(state.workflow.app.metadata.canonical, true);
140
140
  assert.ok(state.tasks.some((task) => task.requiresEvidence), `${app.id} plan must include evidence gates`);
141
141
  assert.ok(state.tasks.every((task) => task.sandboxProfileId), `${app.id} plan must include sandbox hints`);
@@ -8,13 +8,19 @@
8
8
  //
9
9
  // Reads jobs JSON on stdin, spawns ALL concurrently (shell:false, inherited env —
10
10
  // the agent's own credentials resolve; CW never reads them), per-job SIGTERM at
11
- // timeoutMs + SIGKILL at +5s, caps each captured stdout at 32MB, and prints the
12
- // outcome array when every job has settled. stderr is drained (a full pipe must
13
- // never wedge a child). A kill yields exitCode null the no-exit-code refusal.
11
+ // timeoutMs + SIGKILL at +5s, caps each captured stdout at 32MB. Streams ONE
12
+ // NDJSON line per job `{i, spawnError?, exitCode, stdout}\n` the INSTANT
13
+ // that job settles (not once at the end): the parent's spawnSync call has its
14
+ // own combined-output cap, so writing incrementally means a job whose line
15
+ // already flushed keeps its real outcome even if a LATER job's output pushes
16
+ // the combined stream over that cap and the whole child gets killed. `i` is
17
+ // the job's index (settle order is concurrent, not submission order — the
18
+ // parent cannot infer which line belongs to which job without it). stderr is
19
+ // drained (a full pipe must never wedge a child). A kill yields exitCode null
20
+ // — the no-exit-code refusal.
14
21
  //
15
22
  // THE RED LINE: this child only `spawn`s the operator-resolved agent binary with
16
- // shell:false. It imports NO model SDK and reads NO credentials. Behavior MUST
17
- // stay byte-identical to the previous embedded string.
23
+ // shell:false. It imports NO model SDK and reads NO credentials.
18
24
 
19
25
  const { spawn } = require("node:child_process");
20
26
  let raw = "";
@@ -30,17 +36,16 @@ process.stdin.on("end", () => {
30
36
  return;
31
37
  }
32
38
  if (!jobs.length) { process.stdout.write("[]"); return; }
33
- const out = new Array(jobs.length);
34
- let pending = jobs.length;
35
39
  const CAP = 32 * 1024 * 1024;
36
40
  jobs.forEach((job, i) => {
37
41
  let stdout = "";
42
+ let stdoutBytes = 0;
43
+ let stdoutTruncated = false;
38
44
  let settled = false;
39
45
  const settle = (o) => {
40
46
  if (settled) return;
41
47
  settled = true;
42
- out[i] = o;
43
- if (--pending === 0) process.stdout.write(JSON.stringify(out));
48
+ process.stdout.write(JSON.stringify({ i, ...o }) + "\n");
44
49
  };
45
50
  let child;
46
51
  try {
@@ -51,7 +56,18 @@ process.stdin.on("end", () => {
51
56
  }
52
57
  const term = setTimeout(() => { try { child.kill("SIGTERM"); } catch {} }, job.timeoutMs);
53
58
  const kill = setTimeout(() => { try { child.kill("SIGKILL"); } catch {} }, job.timeoutMs + 5000);
54
- child.stdout.on("data", (d) => { if (stdout.length < CAP) stdout += d; });
59
+ child.stdout.on("data", (d) => {
60
+ const chunk = Buffer.isBuffer(d) ? d : Buffer.from(String(d));
61
+ stdoutBytes += chunk.length;
62
+ if (stdoutTruncated) return;
63
+ const remaining = CAP - Buffer.byteLength(stdout);
64
+ if (remaining <= 0 || chunk.length > remaining) {
65
+ stdoutTruncated = true;
66
+ if (remaining > 0) stdout += chunk.subarray(0, remaining).toString();
67
+ return;
68
+ }
69
+ stdout += chunk.toString();
70
+ });
55
71
  child.stderr.on("data", () => {});
56
72
  child.on("error", (error) => {
57
73
  clearTimeout(term); clearTimeout(kill);
@@ -59,6 +75,10 @@ process.stdin.on("end", () => {
59
75
  });
60
76
  child.on("close", (code) => {
61
77
  clearTimeout(term); clearTimeout(kill);
78
+ if (stdoutTruncated) {
79
+ settle({ spawnError: `stdout exceeded ${CAP} byte cap (${stdoutBytes} bytes)`, exitCode: null, stdout: "" });
80
+ return;
81
+ }
62
82
  settle({ exitCode: typeof code === "number" ? code : null, stdout });
63
83
  });
64
84
  });
@@ -6,7 +6,7 @@ const fs = require("node:fs");
6
6
  const path = require("node:path");
7
7
  const { CoolWorkflowRunner } = require("../dist/orchestrator.js");
8
8
 
9
- const TARGET_VERSION = "0.1.97";
9
+ const TARGET_VERSION = "0.1.98";
10
10
  const PREVIOUS_VERSION = "0.1.31";
11
11
  const pluginRoot = path.resolve(__dirname, "..");
12
12
  const repoRoot = path.resolve(pluginRoot, "..", "..");
@@ -33,7 +33,7 @@ function main() {
33
33
  const appValidation = runJson(["app", "validate", "end-to-end-golden-path"], pluginRoot);
34
34
  assert.equal(appValidation.valid, true);
35
35
  assert.equal(appValidation.summary.id, "end-to-end-golden-path");
36
- assert.equal(appValidation.summary.version, "0.1.97");
36
+ assert.equal(appValidation.summary.version, "0.1.98");
37
37
 
38
38
  const plan = runJson(
39
39
  [
@@ -42,7 +42,7 @@ function main() {
42
42
  "--repo",
43
43
  tmp,
44
44
  "--question",
45
- "Prove the deterministic v0.1.97 end-to-end golden path."
45
+ "Prove the deterministic v0.1.98 end-to-end golden path."
46
46
  ],
47
47
  pluginRoot
48
48
  );
@@ -52,7 +52,7 @@ function main() {
52
52
 
53
53
  let state = readJson(plan.statePath);
54
54
  assert.equal(state.workflow.app.id, "end-to-end-golden-path");
55
- assert.equal(state.workflow.app.version, "0.1.97");
55
+ assert.equal(state.workflow.app.version, "0.1.98");
56
56
  assert.equal(state.loopStage, "interpret");
57
57
 
58
58
  const dispatch = runJson(["dispatch", plan.runId, "--limit", "1", "--sandbox", "readonly"], tmp);
@@ -195,7 +195,7 @@ function main() {
195
195
  assert.equal(reportPath, plan.reportPath);
196
196
  assert.ok(fs.existsSync(reportPath));
197
197
  const report = fs.readFileSync(reportPath, "utf8");
198
- assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.97/);
198
+ assert.match(report, /Workflow App: end-to-end-golden-path@0\.1\.98/);
199
199
  assert.match(report, /## Candidates/);
200
200
  assert.match(report, /## Trust Audit/);
201
201
  assert.match(report, /## Acceptance Rationale/);
@@ -283,7 +283,13 @@ function delegateReview(resultPath, inputPath) {
283
283
  // the file takes precedence. stderr goes to the terminal for live output.
284
284
  const r = spawnSync(bin, args, {
285
285
  cwd: repoRoot,
286
- env: { ...process.env },
286
+ // CW_RELEASE_REVIEW=1 is a vendor-agnostic signal that THIS spawn is a
287
+ // release verdict, not a fast worker turn. Wrappers that can re-run the gate
288
+ // (e.g. codex-agent.js) read it to raise reasoning effort and open an
289
+ // exec-capable sandbox — a read-only/low-effort reviewer can't execute the
290
+ // gate it judges and degrades to fabricated verdicts. Preflight liveness
291
+ // probes never set it, so they stay fast and read-only.
292
+ env: { ...process.env, CW_RELEASE_REVIEW: "1" },
287
293
  encoding: "utf8",
288
294
  timeout: cfg.timeoutMs || REVIEWER_TIMEOUT_MS,
289
295
  shell: false,