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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/apps/architecture-review/app.json +1 -1
- package/apps/architecture-review-fast/app.json +1 -1
- package/apps/end-to-end-golden-path/app.json +1 -1
- package/apps/pr-review-fix-ci/app.json +1 -1
- package/apps/release-cut/app.json +1 -1
- package/apps/research-synthesis/app.json +1 -1
- package/dist/capability-core.js +3 -0
- package/dist/capability-registry.js +7 -1
- package/dist/cli/command-surface.js +4 -0
- package/dist/cli/handlers/ledger.js +169 -0
- package/dist/drive.js +98 -61
- package/dist/execution-backend/agent.js +84 -24
- package/dist/ledger.js +313 -0
- package/dist/mcp/tool-call.js +36 -0
- package/dist/mcp/tool-definitions.js +26 -0
- package/dist/orchestrator/lifecycle-operations.js +37 -13
- package/dist/orchestrator.js +11 -3
- package/dist/remote-source.js +10 -3
- package/dist/run-export.js +35 -4
- package/dist/version.js +1 -1
- package/dist/workbench-host.js +11 -1
- package/docs/agent-delegation-drive.7.md +2 -0
- package/docs/cli-mcp-parity.7.md +14 -2
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -0
- package/docs/cross-agent-ledger.7.md +217 -0
- package/docs/designs/handoff-ledger.md +145 -0
- package/docs/durable-state-and-locking.7.md +2 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/handoff-setup.md +120 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
- package/docs/multi-agent-eval-replay-harness.7.md +2 -0
- package/docs/multi-agent-operator-ux.7.md +2 -0
- package/docs/node-snapshot-diff-replay.7.md +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/project-index.md +16 -5
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +2 -0
- package/docs/release-tooling.7.md +2 -0
- package/docs/run-registry-control-plane.7.md +2 -0
- package/docs/run-retention-reclamation.7.md +2 -0
- package/docs/state-explosion-management.7.md +2 -0
- package/docs/team-collaboration.7.md +2 -0
- package/docs/web-desktop-workbench.7.md +2 -0
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/agents/codex-agent.js +34 -4
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +30 -10
- package/scripts/dogfood-release.js +1 -1
- package/scripts/golden-path.js +4 -4
- 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`.
|
package/docs/project-index.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Cool Workflow Project Index
|
|
2
2
|
|
|
3
|
-
Generated from the current repository code on 2026-
|
|
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.
|
|
9
|
-
- Source modules: `
|
|
8
|
+
- Version: `0.1.98`
|
|
9
|
+
- Source modules: `69`
|
|
10
10
|
- Workflow apps: `8`
|
|
11
|
-
- Docs: `
|
|
12
|
-
- Smoke tests: `
|
|
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)
|
|
@@ -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.
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
12
|
-
//
|
|
13
|
-
//
|
|
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.
|
|
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
|
-
|
|
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) => {
|
|
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.
|
|
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, "..", "..");
|
package/scripts/golden-path.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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\.
|
|
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/);
|
package/scripts/release-flow.js
CHANGED
|
@@ -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
|
-
|
|
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,
|