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
@@ -11,7 +11,7 @@
11
11
  //
12
12
  // Evidence: SPEC/state-core.md "src/state-node.ts — StateNode lifecycle and
13
13
  // contract gates", "StateNode transition matrix", "Deterministic id
14
- // fallback", "Contract gates" (v2/PLAN.md byte-compat item 7 — the double
14
+ // fallback", "Contract gates" (docs/rebuild/PLAN.md byte-compat item 7 — the double
15
15
  // commit gate).
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.PipelineContractError = exports.PIPELINE_CONTRACT_SCHEMA_VERSION = exports.STATE_NODE_SCHEMA_VERSION = void 0;
@@ -115,7 +115,7 @@ function validatePipelineContract(contract) {
115
115
  * check against; every real caller (shell/) passes `fs.existsSync` so the
116
116
  * `missing-artifact-path` gate behaves exactly like the old build. Kept as
117
117
  * an explicit parameter (never a top-level `require("node:fs")`) so this
118
- * stays a pure core/ module per v2/PLAN.md's core/shell split. */
118
+ * stays a pure core/ module per docs/rebuild/PLAN.md's core/shell split. */
119
119
  function assertNodeSatisfiesContract(node, contract, stageId, pathExists = () => true) {
120
120
  validatePipelineContract(contract);
121
121
  const stage = contract.stages.find((candidate) => candidate.id === stageId);
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // core/state/types.ts — the state-kernel's own type set.
3
3
  //
4
- // MILESTONE 3 (v2/PLAN.md build order, step 3). Scoped to exactly what
4
+ // MILESTONE 3 (docs/rebuild/PLAN.md build order, step 3). Scoped to exactly what
5
5
  // state-core.md's public surface needs: WorkflowRun and everything it
6
6
  // carries, StateNode + its snapshot/replay/diff family, the pipeline
7
7
  // contract shape, and the small persisted-record shapes validation.ts
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // core/trust/ledger.ts — the cross-agent handoff ledger kernel.
3
3
  //
4
- // MILESTONE 8 (v2/PLAN.md build order, step 8; SPEC/ledger-trust.md in
4
+ // MILESTONE 8 (docs/rebuild/PLAN.md build order, step 8; SPEC/ledger-trust.md in
5
5
  // full). Pure: computeLedgerDigest, buildLedgerProposal/Review,
6
6
  // verifyLedgerEntry, applyLedgerProposal. Directory reads
7
7
  // (listLedgerEntries/unionLedgerEntries) live in shell/ledger-io.ts, NOT
@@ -6,7 +6,7 @@
6
6
  // node:crypto's built-in ed25519 sign/verify (no npm dependency).
7
7
  //
8
8
  // `crypto.verify`/`crypto.createPublicKey` are allowed here per
9
- // v2/PLAN.md's lint carve-out for this exact file (Target shape,
9
+ // docs/rebuild/PLAN.md's lint carve-out for this exact file (Target shape,
10
10
  // core/trust/telemetry-attestation.ts comment) — this remains otherwise
11
11
  // pure (no fs, no process.env, no clock; `resolveTrustPublicKey`'s lazy
12
12
  // `require("node:fs")` is the one exception, ported byte-identically
@@ -62,7 +62,7 @@ const crypto = __importStar(require("node:crypto"));
62
62
  * recursively, AND maps a top-level `undefined` input to the literal
63
63
  * string `"null"`. Divergent from `core/hash.ts`'s
64
64
  * `ledgerStableStringify` exactly at the top-level-undefined edge (see
65
- * v2/PLAN.md byte-compat item 2). Re-exported under this file's own
65
+ * docs/rebuild/PLAN.md byte-compat item 2). Re-exported under this file's own
66
66
  * name (rather than importing `telemetryStableStringify` from
67
67
  * core/hash.ts) because SPEC/ledger-trust.md's "Exported functions"
68
68
  * list names `stableStringify` as a real export of THIS module — the
@@ -118,7 +118,7 @@ function normalizeReportedUsage(usage) {
118
118
  };
119
119
  }
120
120
  /** Verify the agent's signed usage against the operator-provisioned
121
- * public key. Two-arm check (v2/PLAN.md byte-compat item 11): try the
121
+ * public key. Two-arm check (docs/rebuild/PLAN.md byte-compat item 11): try the
122
122
  * 5-field payload (with resultDigest) first; on a miss, retry the
123
123
  * 4-field payload (old signers still verify). `coversResult` is set
124
124
  * ONLY on a first-arm match — a 4-field fallback match never covers the
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // MILESTONE 8. Byte-exact port of the old build's src/telemetry-ledger.ts
9
9
  // verify-side logic. Uses core/hash.ts's `sha256`/`telemetryStableStringify`
10
- // (see v2/PLAN.md byte-compat item 2's key-omission-vs-null rule).
10
+ // (see docs/rebuild/PLAN.md byte-compat item 2's key-omission-vs-null rule).
11
11
  //
12
12
  // Evidence: SPEC/ledger-trust.md "Telemetry ledger record", byte-compat
13
13
  // items 2 and 12; plugins/cool-workflow/src/telemetry-ledger.ts:1-224.
@@ -43,7 +43,7 @@ function genesisPrevHash(runId) {
43
43
  * recordHash itself. `reportedUsage`/`resultDigest` are OMITTED (not
44
44
  * `null`) when absent, so a usage-only record's hash is byte-identical
45
45
  * to a pre-result-coverage one (back-compat with old ledgers) — see
46
- * v2/PLAN.md byte-compat item 2. */
46
+ * docs/rebuild/PLAN.md byte-compat item 2. */
47
47
  function recordHashInput(record) {
48
48
  return (0, hash_1.telemetryStableStringify)({
49
49
  schemaVersion: record.schemaVersion,
@@ -7,7 +7,7 @@ exports.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION = exports.LEGACY_RUN_STATE_SCHEMA
7
7
  // `cw version` output does not change bytes just because the code under it
8
8
  // was rebuilt. See SPEC/cli-surface.md "Exact outputs > Version" and
9
9
  // conformance/cases/version-basic.case.js (regex `/^\d+\.\d+\.\d+\n$/`).
10
- exports.CURRENT_COOL_WORKFLOW_VERSION = "0.2.0";
10
+ exports.CURRENT_COOL_WORKFLOW_VERSION = "0.2.1";
11
11
  // State-kernel schema version constants (SPEC/state-core.md "Version
12
12
  // constants"). Pinned to the old build's src/version.ts byte-for-byte.
13
13
  exports.WORKFLOW_APP_SCHEMA_VERSION = 1;
@@ -4,7 +4,7 @@
4
4
  // `agent`, `artifact`, `subWorkflow`, `input`), and full manifest
5
5
  // validation (`validateWorkflowApp`).
6
6
  //
7
- // MILESTONE 12 (v2/PLAN.md build order; workflow-apps.md). Milestone 6+7
7
+ // MILESTONE 12 (docs/rebuild/PLAN.md build order; workflow-apps.md). Milestone 6+7
8
8
  // built the minimal real subset needed to load ONE app and drive it
9
9
  // end to end (see git history / the old header note this replaces).
10
10
  // This milestone adds, on top of that, WITHOUT changing any existing
@@ -2,7 +2,7 @@
2
2
  // mcp/dispatch.ts — the generic MCP tool-call reader over
3
3
  // core/capability-table.ts.
4
4
  //
5
- // MILESTONE 2 (v2/PLAN.md build order, step 2). Replaces the old build's
5
+ // MILESTONE 2 (docs/rebuild/PLAN.md build order, step 2). Replaces the old build's
6
6
  // 196-arm `callTool` switch (src/mcp/tool-call.ts) with a data-driven
7
7
  // lookup: resolve the row by tool name, check its declared required-arg
8
8
  // groups (SPEC/mcp.md's `McpBinding.requiredArgs`), re-base `cwd` if
@@ -2,7 +2,7 @@
2
2
  // mcp/server.ts — the stdio JSON-RPC loop: initialize / tools/list /
3
3
  // tools/call.
4
4
  //
5
- // MILESTONE 2 (v2/PLAN.md build order, step 2). Byte-exact port of the
5
+ // MILESTONE 2 (docs/rebuild/PLAN.md build order, step 2). Byte-exact port of the
6
6
  // framing rules in SPEC/mcp.md's "JSON-RPC methods" / "Exact outputs" /
7
7
  // "Invariants and error behavior" #9 / "Edge cases" sections:
8
8
  // - transport: stdin/stdout, ONE JSON object per line, no
@@ -2,7 +2,7 @@
2
2
  // shell/agent-config.ts — agent delegation config: env/file/flag resolution
3
3
  // order, CW_HOME resolution, builtin:<name> expansion, PATH auto-detect.
4
4
  //
5
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
5
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
6
6
  // plugins/cool-workflow/src/agent-config.ts.
7
7
  //
8
8
  // Resolution order per field: flags > env > a durable
@@ -2,7 +2,7 @@
2
2
  // shell/doctor.ts — `cw doctor` environment diagnostics, in the spirit of
3
3
  // `brew doctor`.
4
4
  //
5
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
5
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
6
6
  // plugins/cool-workflow/src/doctor.ts's checks this milestone needs
7
7
  // (node/agent/agent-binary/git/home-registry/repo-state). The `--onramp`
8
8
  // section (buildDoctorOnramp) is later-milestone (reporting) territory and
@@ -167,7 +167,20 @@ function runDoctor(args = {}, env = process.env, cwd = process.cwd()) {
167
167
  });
168
168
  }
169
169
  }
170
- // 3. git only needed for commit provenance; a warn, not a hard fail.
170
+ // 3. Sandbox enforcement boundary a fixed architectural fact (see
171
+ // docs/sandbox-profiles.7.md), not a host-specific problem to fix, so this
172
+ // is "ok" (informational), never "warn": CW validates sandbox policy and
173
+ // gates worker-output acceptance, but OS-level read/write/execute/network/
174
+ // env isolation is the execution host's responsibility, which this doctor
175
+ // run cannot itself verify. An "ok" check carries no `fix` line by
176
+ // convention (see the node/git checks above), so the pointer to the docs
177
+ // lives in `detail` instead.
178
+ checks.push({
179
+ name: "sandbox-enforceability",
180
+ status: "ok",
181
+ detail: "CW sandbox profiles validate policy and gate worker-output acceptance; OS-level read/write/execute/network/env isolation is the execution host's responsibility (see docs/sandbox-profiles.7.md's ENFORCEMENT section) — this doctor run cannot verify that host enforcement.",
182
+ });
183
+ // 4. git — only needed for commit provenance; a warn, not a hard fail.
171
184
  const git = (0, node_child_process_1.spawnSync)("git", ["--version"], { encoding: "utf8", timeout: 5000 });
172
185
  checks.push(!git.error && git.status === 0
173
186
  ? { name: "git", status: "ok", detail: `${String(git.stdout || "git").trim()}.` }
@@ -177,7 +190,7 @@ function runDoctor(args = {}, env = process.env, cwd = process.cwd()) {
177
190
  detail: "git is not available — commit provenance (git HEAD) is recorded as absent.",
178
191
  fix: "Install git (e.g. `brew install git`) if you want commit provenance.",
179
192
  });
180
- // 4. Home registry — the cross-repo run index lives here; must be writable.
193
+ // 5. Home registry — the cross-repo run index lives here; must be writable.
181
194
  const home = env.CW_HOME && String(env.CW_HOME).trim() ? path.resolve(String(env.CW_HOME)) : path.join(os.homedir(), ".local", "state", "cool-workflow");
182
195
  checks.push(dirWritable(home)
183
196
  ? { name: "home-registry", status: "ok", detail: `Home registry location is writable (${home}).` }
@@ -187,7 +200,7 @@ function runDoctor(args = {}, env = process.env, cwd = process.cwd()) {
187
200
  detail: `Home registry location is not writable: ${home}`,
188
201
  fix: "Set $CW_HOME to a writable directory, or fix the permissions.",
189
202
  });
190
- // 5. Working-dir state — per-repo runs land under <cwd>/.cw.
203
+ // 6. Working-dir state — per-repo runs land under <cwd>/.cw.
191
204
  const cwState = path.join(path.resolve(cwd), ".cw");
192
205
  checks.push(dirWritable(cwState)
193
206
  ? { name: "repo-state", status: "ok", detail: `Run state location is writable (${cwState}).` }
@@ -3,7 +3,7 @@
3
3
  // once per step and performs the spawn/commit/cache-write IO the
4
4
  // decision names.
5
5
  //
6
- // MILESTONE 6+7 (combined; see v2/PLAN.md Open risk 9/10 — the LARGEST
6
+ // MILESTONE 6+7 (combined; see docs/rebuild/PLAN.md Open risk 9/10 — the LARGEST
7
7
  // milestone). Byte-exact port of the old build's src/drive.ts's
8
8
  // imperative shell around the pure decision core now in
9
9
  // core/pipeline/drive-decide.ts. Sub-workflow nesting and `--incremental`
@@ -76,7 +76,6 @@ const trust_audit_1 = require("./trust-audit");
76
76
  const agent_config_1 = require("./agent-config");
77
77
  const registry_1 = require("./execution-backend/registry");
78
78
  const agent_1 = require("./execution-backend/agent");
79
- const local_1 = require("./execution-backend/local");
80
79
  const hash_1 = require("../core/hash");
81
80
  const pipeline_1 = require("./pipeline");
82
81
  const reporter_1 = require("./reporter");
@@ -384,6 +383,25 @@ function processSelectedTask(ctx, selectedId, preparedOutcome, deferPersist = fa
384
383
  catch (error) {
385
384
  return handleHop(ctx, selected, workerId, `result.md rejected: ${errMessage(error)}`, deferPersist, deferPersist ? run : undefined);
386
385
  }
386
+ // Trust visibility (purely additive, no execution-behavior change): when
387
+ // the agent backend forwarded provider-namespace env vars (CW_/ANTHROPIC_/
388
+ // etc.) to the delegated child, record the NAMES (never values) as their
389
+ // own tamper-evident audit event, mirroring the worker.sub-workflow event
390
+ // pattern above. Only ever populated by execution-backend/agent.ts's
391
+ // recordedAgentHandle() — a no-op for every other backend.
392
+ const forwardedEnvVars = handle?.metadata?.forwardedEnvVars || [];
393
+ if (forwardedEnvVars.length) {
394
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
395
+ kind: "worker.agent-env",
396
+ decision: "delegated",
397
+ source: "runtime-derived",
398
+ workerId,
399
+ taskId: selected.id,
400
+ nodeId: selected.resultNodeId,
401
+ envVars: forwardedEnvVars,
402
+ metadata: { reason: "provider-namespace env vars forwarded from host process env to the delegated agent child" },
403
+ });
404
+ }
387
405
  if (cachePath && fs.existsSync(manifest.resultPath)) {
388
406
  writeResultCache(cachePath, fs.readFileSync(manifest.resultPath, "utf8"));
389
407
  }
@@ -664,13 +682,7 @@ function prepareConcurrentOutcomes(ctx, batch) {
664
682
  if (job) {
665
683
  const sandboxPolicy = manifest.sandboxPolicy;
666
684
  if (sandboxPolicy) {
667
- const filteredEnv = (0, local_1.buildChildEnv)(sandboxPolicy);
668
- for (const key of Object.keys(process.env)) {
669
- if (/^(CW_|ANTHROPIC_|OPENAI_|GEMINI_|DEEPSEEK_|CODEX_|GOOGLE_|COHERE_|MISTRAL_|OLLAMA_|AZURE_|AWS_)/i.test(key)) {
670
- filteredEnv[key] = process.env[key];
671
- }
672
- }
673
- job.env = filteredEnv;
685
+ job.env = (0, agent_1.buildAgentChildEnv)(sandboxPolicy).env;
674
686
  }
675
687
  jobs.push(job);
676
688
  jobTaskIds.push(taskId);
@@ -2,7 +2,7 @@
2
2
  // shell/execution-backend/agent.ts — agent-delegation pure helpers +
3
3
  // concurrent batch fulfillment.
4
4
  //
5
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
5
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
6
6
  // plugins/cool-workflow/src/execution-backend/agent.ts. This module holds
7
7
  // the PURE data-transform helpers (invocation resolution, arg substitution,
8
8
  // secret redaction, report parsing) plus the batch delegate-child spawn.
@@ -49,6 +49,7 @@ var __importStar = (this && this.__importStar) || (function () {
49
49
  };
50
50
  })();
51
51
  Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.buildAgentChildEnv = buildAgentChildEnv;
52
53
  exports.resolveAgentInvocation = resolveAgentInvocation;
53
54
  exports.stripSecretArgs = stripSecretArgs;
54
55
  exports.parseAgentReport = parseAgentReport;
@@ -70,6 +71,35 @@ const envelopes_1 = require("./envelopes");
70
71
  function messageOf(error) {
71
72
  return error instanceof Error ? error.message : String(error);
72
73
  }
74
+ const AGENT_PROVIDER_KEY_ENV_RE = /^(CW_|ANTHROPIC_|OPENAI_|GEMINI_|DEEPSEEK_|CODEX_|GOOGLE_|COHERE_|MISTRAL_|OLLAMA_|AZURE_|AWS_)/i;
75
+ /** Build the env for a REAL spawned agent child under a sandbox policy: the
76
+ * policy's own PATH+HOME+expose/deny base (buildChildEnv), plus provider-key
77
+ * vars (the CW_, ANTHROPIC_, etc. prefixes), plus USER. USER is not a provider key, but a
78
+ * real vendor CLI (claude, at least) reads it to resolve its own OS-level
79
+ * login/keychain credential in headless mode -- buildChildEnv alone keeps
80
+ * only PATH+HOME under the readonly policy, so a spawned agent that IS
81
+ * logged in interactively reported "Not logged in" here. Found live: PATH+
82
+ * HOME alone reproduces it, adding USER back fixes it, LOGNAME alone does
83
+ * not. This is the ONE place that builds this env -- the single-request
84
+ * (runAgentProcess) and concurrent-batch (shell/drive.ts) spawn paths both
85
+ * call it, so the allowlist cannot drift into a second silent copy again.
86
+ * Returns the forwarded var NAMES too (never values) for the
87
+ * worker.agent-env trust-audit event. */
88
+ function buildAgentChildEnv(policy, baseEnv = process.env) {
89
+ const env = (0, local_1.buildChildEnv)(policy, baseEnv);
90
+ const forwarded = [];
91
+ for (const key of Object.keys(baseEnv)) {
92
+ if (AGENT_PROVIDER_KEY_ENV_RE.test(key)) {
93
+ env[key] = baseEnv[key];
94
+ forwarded.push(key);
95
+ }
96
+ }
97
+ if (baseEnv.USER !== undefined && env.USER === undefined) {
98
+ env.USER = baseEnv.USER;
99
+ forwarded.push("USER");
100
+ }
101
+ return { env, forwarded };
102
+ }
73
103
  /** Resolve the agent invocation from the request delegation > env.
74
104
  * Vendor-neutral; the durable file config is folded in by the drive layer
75
105
  * before this point (see shell/agent-config.ts's resolveAgentConfig). */
@@ -203,7 +233,7 @@ function substituteAgentArg(arg, subst) {
203
233
  /** Build the recorded process handle for the envelope — secret-stripped +
204
234
  * the agent-reported model. Same SHAPE that lands in provenance, never in
205
235
  * evidence. */
206
- function recordedAgentHandle(binary, endpoint, recordedArgs, model, reportedModel, reportedUsage, usageSignature) {
236
+ function recordedAgentHandle(binary, endpoint, recordedArgs, model, reportedModel, reportedUsage, usageSignature, forwardedEnvVars) {
207
237
  const ref = binary ? [binary, ...recordedArgs].join(" ") : endpoint || "";
208
238
  return {
209
239
  kind: "process",
@@ -217,6 +247,7 @@ function recordedAgentHandle(binary, endpoint, recordedArgs, model, reportedMode
217
247
  reportedModel,
218
248
  ...(reportedUsage ? { reportedUsage } : {}),
219
249
  ...(usageSignature ? { usageSignature } : {}),
250
+ ...(forwardedEnvVars && forwardedEnvVars.length ? { forwardedEnvVars } : {}),
220
251
  },
221
252
  };
222
253
  }
@@ -365,17 +396,18 @@ function runAgentProcess(descriptor, policy, request, label, attestation) {
365
396
  const realArgs = resolved.rawArgs.map((arg) => substituteAgentArg(arg, subst));
366
397
  const recordedArgs = stripSecretArgs(realArgs);
367
398
  let outcome;
399
+ // Names only, never values — for the worker.agent-env trust-audit event
400
+ // below. Stays empty for a preparedAgentOutcome (a batch-delegated child
401
+ // that already ran elsewhere; this code path forwards nothing itself).
402
+ let forwardedEnvVars = [];
368
403
  if (request.preparedAgentOutcome) {
369
404
  outcome = request.preparedAgentOutcome;
370
405
  }
371
406
  else {
372
407
  const streamStderr = shouldStreamAgentStderr(process.env, Boolean(process.stderr.isTTY));
373
- const childEnv = (0, local_1.buildChildEnv)(policy);
374
- for (const key of Object.keys(process.env)) {
375
- if (/^(CW_|ANTHROPIC_|OPENAI_|GEMINI_|DEEPSEEK_|CODEX_|GOOGLE_|COHERE_|MISTRAL_|OLLAMA_|AZURE_|AWS_)/i.test(key)) {
376
- childEnv[key] = process.env[key];
377
- }
378
- }
408
+ const built = buildAgentChildEnv(policy);
409
+ const childEnv = built.env;
410
+ forwardedEnvVars = built.forwarded;
379
411
  const child = (0, node_child_process_1.spawnSync)(resolved.binary, realArgs, {
380
412
  cwd: request.cwd,
381
413
  env: childEnv,
@@ -392,7 +424,7 @@ function runAgentProcess(descriptor, policy, request, label, attestation) {
392
424
  };
393
425
  }
394
426
  if (outcome.spawnError) {
395
- const handleOut = recordedAgentHandle(resolved.binary, undefined, recordedArgs, resolved.model, "unreported");
427
+ const handleOut = recordedAgentHandle(resolved.binary, undefined, recordedArgs, resolved.model, "unreported", undefined, undefined, forwardedEnvVars);
396
428
  return (0, envelopes_1.refusedEnvelope)(descriptor, policy, label, "delegation-failed", `agent process failed to spawn: ${outcome.spawnError}`, {
397
429
  ...attestation,
398
430
  handle: handleOut,
@@ -402,7 +434,7 @@ function runAgentProcess(descriptor, policy, request, label, attestation) {
402
434
  const stdout = outcome.stdout;
403
435
  const report = parseAgentReport(stdout);
404
436
  const reportedModel = report.model && report.model.trim() ? report.model.trim() : "unreported";
405
- const handleOut = recordedAgentHandle(resolved.binary, undefined, recordedArgs, resolved.model, reportedModel, report.usage, report.usageSignature);
437
+ const handleOut = recordedAgentHandle(resolved.binary, undefined, recordedArgs, resolved.model, reportedModel, report.usage, report.usageSignature, forwardedEnvVars);
406
438
  if (exitCode === null) {
407
439
  return (0, envelopes_1.refusedEnvelope)(descriptor, policy, label, "delegation-failed", `agent process returned no exit code (timed out or killed)`, { ...attestation, handle: handleOut });
408
440
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // shell/execution-backend/ci.ts — the ci delegating driver.
3
3
  //
4
- // MILESTONE 5 (v2/PLAN.md build order, step 5). The ci driver shares its
4
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). The ci driver shares its
5
5
  // entire HTTP-delegation body with the remote driver (byte-exact port of
6
6
  // plugins/cool-workflow/src/execution-backend.ts, which also shares
7
7
  // `runHttpDelegation` between the two); this file just re-exports the ci
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // shell/execution-backend/container.ts — the container delegating driver.
3
3
  //
4
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
4
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
5
5
  // plugins/cool-workflow/src/execution-backend.ts's container handle builder
6
6
  // (:1068-1075) and `runContainer` (:720-792).
7
7
  //
@@ -3,7 +3,7 @@
3
3
  // every delegating driver (container/remote/ci/agent) plus the registry's
4
4
  // own refusal path.
5
5
  //
6
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of the
6
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of the
7
7
  // `delegatedEnvelope` (src/execution-backend.ts:680-713) and
8
8
  // `refusedEnvelope` (:1095-1126) helpers, factored into one shared module
9
9
  // since every delegating driver builds the identical shape (byte-stable
@@ -2,7 +2,7 @@
2
2
  // shell/execution-backend/local.ts — the local-execution driver body
3
3
  // (executeLocal) shared by the node/bun/shell built-in drivers.
4
4
  //
5
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
5
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
6
6
  // plugins/cool-workflow/src/execution-backend.ts's `executeLocal` (lines
7
7
  // 547-626) plus the leaf helpers it needs (sha256/hasExecutable/messageOf).
8
8
  //
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // shell/execution-backend/probes.ts — per-backend readiness probe bodies.
3
3
  //
4
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
4
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
5
5
  // plugins/cool-workflow/src/execution-backend/probes.ts. Each probe is a
6
6
  // pure free function of the host (PATH + configured env); the registry's
7
7
  // `probeBackend` wraps these with the descriptor-derived envelope.
@@ -3,7 +3,7 @@
3
3
  // registerBackend, resolveBackendSelection, attestSandbox, probeBackend,
4
4
  // runBackend, and the CLI-facing inspection payloads.
5
5
  //
6
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
6
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
7
7
  // plugins/cool-workflow/src/execution-backend.ts (the parts not carved into
8
8
  // local.ts/container.ts/remote.ts/ci.ts/agent.ts/probes.ts).
9
9
  //
@@ -2,7 +2,7 @@
2
2
  // shell/execution-backend/remote.ts — remote/ci delegating drivers (shared
3
3
  // HTTP-delegation body) + handle builders.
4
4
  //
5
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
5
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
6
6
  // plugins/cool-workflow/src/execution-backend.ts's `runHttpDelegation`
7
7
  // (:821-876), `remoteHandle` (:1077-1084), `ciHandle` (:1086-1093), and
8
8
  // `delegateChildScript` (:801-811).
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // shell/execution-backend/types.ts — plain data shapes for the driver layer.
3
3
  //
4
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of the shapes
4
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of the shapes
5
5
  // in the old build's src/types/execution-backend.ts and the sandbox slice of
6
6
  // src/types/sandbox.ts that this subsystem needs. Types only — no logic — so
7
7
  // this file is safe to import from both shell/ (impure) and any future core/
@@ -5,7 +5,7 @@
5
5
  // `writeJson` here, so the write bytes stay exactly one place.
6
6
  //
7
7
  // Evidence: SPEC/state-core.md "Write ordering and atomic rules", "Lock
8
- // protocol"; v2/PLAN.md byte-compat items 1 and 6.
8
+ // protocol"; docs/rebuild/PLAN.md byte-compat items 1 and 6.
9
9
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
10
  if (k2 === undefined) k2 = k;
11
11
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -5,7 +5,7 @@
5
5
  //
6
6
  // MILESTONE 8. Byte-exact port of the old build's src/ledger.ts stage-2
7
7
  // git-transport functions (listLedgerEntries, unionLedgerEntries), split
8
- // out per v2/PLAN.md's core/shell rule since these two functions are the
8
+ // out per docs/rebuild/PLAN.md's core/shell rule since these two functions are the
9
9
  // ONLY impure ones in the old ledger.ts module.
10
10
  //
11
11
  // Evidence: SPEC/ledger-trust.md "Handoff ledger entry" (listLedgerEntries/
@@ -72,12 +72,14 @@ function metricsShowCli(runId, args) {
72
72
  const policy = (0, observability_1.loadCostPolicy)(args, pluginRoot());
73
73
  return (0, observability_1.showMetricsReport)(run, { now: nowOf(args), policy });
74
74
  }
75
- /** `cw metrics summary [--scope repo|home] [--pricing ...] [--now ISO]`. */
75
+ /** `cw metrics summary [--scope repo|home] [--pricing ...] [--now ISO] [--limit N]`.
76
+ * `--limit` defaults to RunRegistry.list()'s own 50-record floor when omitted. */
76
77
  function metricsSummaryCli(args) {
77
78
  const cwd = invocationCwd(args);
78
79
  const scope = args.scope === "home" ? "home" : "repo";
79
80
  const registry = new run_registry_io_1.RunRegistry(cwd);
80
- const listing = registry.list({ scope, includeArchived: true });
81
+ const limit = args.limit === undefined ? undefined : Number(args.limit);
82
+ const listing = registry.list({ scope, includeArchived: true, limit });
81
83
  const inputs = [];
82
84
  let unreadableRuns = 0;
83
85
  for (const record of listing.records) {
@@ -9,7 +9,7 @@
9
9
  // deterministic step order), hostBlackboard's action dispatch. The
10
10
  // `summaries.workers/candidates/feedback/commits/operator` blocks are
11
11
  // simplified real projections (not the full operator-ux/multi-agent-
12
- // operator-ux module — out of this milestone's scope per v2/PLAN.md's
12
+ // operator-ux module — out of this milestone's scope per docs/rebuild/PLAN.md's
13
13
  // build-order list) — every FIELD the conformance cases actually assert
14
14
  // on (`summaries.topologies.officialTopologies`, the envelope's fixed key
15
15
  // set, `ids.*`) is still byte-exact; the parts no case reaches are
@@ -2,7 +2,7 @@
2
2
  // shell/reclamation-io.ts — gc plan/run/verify's write-ahead reclamation
3
3
  // transaction, plus the orphan-run sweep and the clone cache gc.
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/reclamation.ts + src/reclamation/hash.ts +
7
7
  // src/run-registry/{gc,orphans}.ts + src/clones.ts. Reuses
8
8
  // shell/fs-atomic.ts's `withFileLock` directly (no reimplementation, per
@@ -58,6 +58,7 @@ exports.routineCreateCli = routineCreateCli;
58
58
  exports.routineListCli = routineListCli;
59
59
  exports.routineDeleteCli = routineDeleteCli;
60
60
  exports.routineFireCli = routineFireCli;
61
+ exports.resolveRoutineFirePayload = resolveRoutineFirePayload;
61
62
  exports.routineEventsCli = routineEventsCli;
62
63
  exports.registryRefreshCli = registryRefreshCli;
63
64
  exports.registryShowCli = registryShowCli;
@@ -79,6 +80,7 @@ exports.orphansListCli = orphansListCli;
79
80
  exports.orphansGcCli = orphansGcCli;
80
81
  exports.clonesListCli = clonesListCli;
81
82
  exports.clonesGcCli = clonesGcCli;
83
+ const fs = __importStar(require("node:fs"));
82
84
  const path = __importStar(require("node:path"));
83
85
  const run_registry_io_1 = require("./run-registry-io");
84
86
  const pipeline_1 = require("./pipeline");
@@ -149,6 +151,19 @@ function routineDeleteCli(id, options = {}) {
149
151
  function routineFireCli(kind, payload, options = {}) {
150
152
  return new scheduler_io_1.RoutineTriggerBridge(resolveCwd(options)).fire(kind, payload);
151
153
  }
154
+ /** Resolves a `routine fire` payload: a `--payload-path`/positional file wins
155
+ * (parsed as JSON) over the raw CLI/MCP options bag. The file read lives
156
+ * here, in the shell layer, not in core/capability-table.ts. */
157
+ function resolveRoutineFirePayload(payloadPath, options) {
158
+ if (!payloadPath)
159
+ return options;
160
+ try {
161
+ return JSON.parse(fs.readFileSync(payloadPath, "utf8"));
162
+ }
163
+ catch (e) {
164
+ throw new Error(`Failed to parse payload file "${payloadPath}": ${String((e && e.message) || e)}`);
165
+ }
166
+ }
152
167
  function routineEventsCli(id, options = {}) {
153
168
  return new scheduler_io_1.RoutineTriggerBridge(resolveCwd(options)).events(id);
154
169
  }
@@ -2,7 +2,7 @@
2
2
  // shell/run-registry-io.ts — the on-disk run registry: index/queue/history
3
3
  // persistence, lifecycle derivation, search/resume/archive/rerun.
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/run-registry.ts + src/run-registry/{derive,policy,
7
7
  // queue}.ts. The registry is a DERIVED, rebuildable index over each repo's
8
8
  // `.cw/runs/<id>/state.json` (the single source of truth, never mutated
@@ -2,7 +2,7 @@
2
2
  // shell/sandbox-profile.ts — the 4 bundled sandbox profiles, resolution,
3
3
  // validation, and the runtime read/write/command/network checks.
4
4
  //
5
- // MILESTONE 5 (v2/PLAN.md build order, step 5). Byte-exact port of
5
+ // MILESTONE 5 (docs/rebuild/PLAN.md build order, step 5). Byte-exact port of
6
6
  // plugins/cool-workflow/src/sandbox-profile.ts.
7
7
  //
8
8
  // Evidence: SPEC/execution-backend.md "Sandbox profiles".