cool-workflow 0.2.1 → 0.2.3
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/cli/dispatch.js +29 -91
- package/dist/cli/entry.js +41 -1
- package/dist/cli/io.js +6 -2
- package/dist/cli/parseargv.js +6 -2
- package/dist/core/capability-data.js +271 -0
- package/dist/core/capability-table.js +16 -3504
- package/dist/core/format/completion.js +68 -0
- package/dist/core/format/help.js +50 -8
- package/dist/core/format/safe-json.js +73 -0
- package/dist/core/format/state-explosion-text.js +1 -1
- package/dist/core/multi-agent/candidate-scoring.js +5 -1
- package/dist/core/multi-agent/collaboration.js +6 -5
- package/dist/core/multi-agent/coordinator.js +9 -8
- package/dist/core/multi-agent/runtime.js +4 -4
- package/dist/core/multi-agent/topology.js +3 -3
- package/dist/core/pipeline/commit-gate.js +2 -1
- package/dist/core/pipeline/dispatch.js +18 -4
- package/dist/core/pipeline/drive-decide.js +2 -1
- package/dist/core/state/migrations.js +16 -1
- package/dist/core/state/state-explosion/digest.js +16 -15
- package/dist/core/state/state-explosion/graph.js +26 -19
- package/dist/core/state/state-explosion/helpers.js +2 -1
- package/dist/core/state/state-explosion/report.js +6 -6
- package/dist/core/trust/ledger.js +2 -1
- package/dist/core/types/execution-backend.js +18 -0
- package/dist/core/types/observability.js +7 -0
- package/dist/core/util/collate.js +23 -0
- package/dist/core/util/numeric-flag.js +40 -0
- package/dist/core/version.js +1 -1
- package/dist/mcp/server.js +99 -11
- package/dist/shell/audit-cli.js +99 -23
- package/dist/shell/commit-summary.js +2 -1
- package/dist/shell/coordinator-io.js +73 -13
- package/dist/shell/dispatch.js +1 -1
- package/dist/shell/doctor.js +80 -1
- package/dist/shell/drive.js +269 -59
- package/dist/shell/eval-text.js +2 -2
- package/dist/shell/evidence-reasoning.js +8 -7
- package/dist/shell/execution-backend/agent.js +20 -1
- package/dist/shell/execution-backend/container.js +4 -1
- package/dist/shell/execution-backend/local.js +4 -1
- package/dist/shell/execution-backend/registry.js +2 -1
- package/dist/shell/execution-backend/types.js +0 -9
- package/dist/shell/feedback-cli.js +6 -6
- package/dist/shell/fs-atomic.js +232 -30
- package/dist/shell/man-cli.js +6 -0
- package/dist/shell/metrics-cli.js +2 -1
- package/dist/shell/multi-agent-cli.js +367 -323
- package/dist/shell/multi-agent-host.js +9 -9
- package/dist/shell/multi-agent-operator-ux.js +82 -39
- package/dist/shell/node-store.js +10 -4
- package/dist/shell/observability.js +13 -12
- package/dist/shell/onramp.js +17 -1
- package/dist/shell/operator-ux-text.js +24 -23
- package/dist/shell/operator-ux.js +22 -20
- package/dist/shell/orchestrator.js +49 -38
- package/dist/shell/pipeline-cli.js +168 -111
- package/dist/shell/reclamation-io.js +16 -5
- package/dist/shell/registry-cli.js +19 -17
- package/dist/shell/remote-source.js +13 -8
- package/dist/shell/report-cli.js +45 -0
- package/dist/shell/report.js +4 -2
- package/dist/shell/run-registry-io.js +77 -19
- package/dist/shell/run-store.js +91 -2
- package/dist/shell/scheduling-io.js +12 -13
- package/dist/shell/state-cli.js +2 -7
- package/dist/shell/state-explosion-cli.js +19 -10
- package/dist/shell/topology-io.js +38 -6
- package/dist/shell/trust-audit.js +264 -23
- package/dist/shell/trust-policy-io.js +1 -1
- package/dist/shell/worker-cli.js +41 -29
- package/dist/shell/worker-isolation.js +34 -9
- package/dist/shell/workflow-app-loader.js +3 -2
- package/dist/wiring/capability-table/basics.js +91 -0
- package/dist/wiring/capability-table/exec-backend.js +171 -0
- package/dist/wiring/capability-table/index.js +42 -0
- package/dist/wiring/capability-table/multi-agent.js +630 -0
- package/dist/wiring/capability-table/parity.js +467 -0
- package/dist/wiring/capability-table/pipeline.js +292 -0
- package/dist/wiring/capability-table/registry-core.js +208 -0
- package/dist/wiring/capability-table/reporting.js +435 -0
- package/dist/wiring/capability-table/scheduling-registry.js +592 -0
- package/dist/wiring/capability-table/state.js +181 -0
- package/dist/wiring/capability-table/trust-ledger.js +158 -0
- package/dist/wiring/capability-table/workflow-apps.js +381 -0
- package/docs/agent-delegation-drive.7.md +4 -0
- package/docs/candidate-scoring.7.md +1 -1
- package/docs/canonical-workflow-apps.7.md +7 -7
- package/docs/cli-mcp-parity.7.md +13 -3
- package/docs/contract-migration-tooling.7.md +4 -0
- package/docs/control-plane-scheduling.7.md +4 -0
- package/docs/coordinator-blackboard.7.md +17 -17
- package/docs/dogfood-one-real-repo.7.md +11 -11
- package/docs/durable-state-and-locking.7.md +24 -0
- package/docs/end-to-end-golden-path.7.md +14 -14
- package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
- package/docs/execution-backends.7.md +4 -0
- package/docs/getting-started.md +37 -37
- package/docs/index.md +1 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +21 -17
- package/docs/multi-agent-eval-replay-harness.7.md +17 -13
- package/docs/multi-agent-operator-ux.7.md +23 -19
- package/docs/multi-agent-runtime-core.7.md +22 -22
- package/docs/multi-agent-topologies.7.md +6 -6
- package/docs/multi-agent-trust-policy-audit.7.md +11 -11
- package/docs/node-snapshot-diff-replay.7.md +4 -0
- package/docs/observability-cost-accounting.7.md +4 -0
- package/docs/operator-ux.7.md +34 -34
- package/docs/pipeline-runner.7.md +4 -4
- package/docs/project-index.md +48 -5
- package/docs/real-execution-backends.7.md +4 -0
- package/docs/release-and-migration.7.md +5 -1
- package/docs/release-tooling.7.md +4 -0
- package/docs/routines.md +4 -4
- package/docs/run-registry-control-plane.7.md +23 -19
- package/docs/run-retention-reclamation.7.md +4 -0
- package/docs/scheduled-tasks.md +14 -14
- package/docs/security-trust-hardening.7.md +43 -13
- package/docs/state-explosion-management.7.md +14 -10
- package/docs/team-collaboration.7.md +4 -0
- package/docs/trust-audit-anchor.7.md +71 -0
- package/docs/unix-principles.md +3 -1
- package/docs/verifier-gated-commit.7.md +1 -1
- package/docs/web-desktop-workbench.7.md +4 -0
- package/docs/workflow-app-framework.7.md +13 -13
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +4 -2
- package/scripts/agents/claude-p-agent.js +2 -2
- package/scripts/block-unapproved-tag.sh +23 -2
- package/scripts/bump-version.js +24 -2
- package/scripts/canonical-apps.js +4 -4
- package/scripts/children/batch-delegate-child.js +52 -2
- package/scripts/dogfood-release.js +1 -1
- package/scripts/fake-date-for-reproduction.js +44 -0
- package/scripts/golden-path.js +4 -4
- package/scripts/purity-baseline.json +71 -0
- package/scripts/purity-gate.js +239 -0
- package/scripts/release-check.js +8 -1
- package/scripts/release-flow.js +57 -1
- package/scripts/verdict-keygen.js +83 -0
- package/scripts/verify-bump-reproduction.sh +148 -0
- package/scripts/verify-verdict-signature.js +61 -0
- package/scripts/version-sync-check.js +33 -12
- package/skills/cool-workflow/SKILL.md +9 -9
- package/skills/cool-workflow/references/commands.md +89 -88
- package/ui/workbench/app.css +37 -1
- package/ui/workbench/app.js +124 -6
- package/workflows/README.md +19 -0
|
@@ -102,18 +102,22 @@ class CoolWorkflowRunner {
|
|
|
102
102
|
/** `dispatch` — build the next dispatch manifest (persisting through
|
|
103
103
|
* createDispatchManifest's own writes) and checkpoint. */
|
|
104
104
|
dispatch(runId, options = {}) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
// Hold the state.json lock across the whole load -> change -> save so a
|
|
106
|
+
// concurrent run mutation cannot drop this dispatch (lost-update class,
|
|
107
|
+
// matching pipeline-cli.ts's dispatchRun).
|
|
108
|
+
return (0, run_store_2.withRunStateLock)(runId, this.cwd(), (run) => {
|
|
109
|
+
const limit = numberOption(options.limit);
|
|
110
|
+
const manifest = (0, dispatch_1.createDispatchManifest)(run, limit, {
|
|
111
|
+
sandboxProfileId: stringOption(options.sandbox) || stringOption(options.sandboxProfile) || stringOption(options.sandboxProfileId),
|
|
112
|
+
backendId: stringOption(options.backend) || stringOption(options.backendId) || stringOption(options.executionBackend),
|
|
113
|
+
multiAgentRunId: stringOption(options.multiAgentRun || options.multiAgentRunId || options["multi-agent-run"]),
|
|
114
|
+
multiAgentGroupId: stringOption(options.multiAgentGroup || options.multiAgentGroupId || options.group || options["multi-agent-group"]),
|
|
115
|
+
multiAgentRoleId: stringOption(options.multiAgentRole || options.multiAgentRoleId || options.role || options["multi-agent-role"]),
|
|
116
|
+
multiAgentFanoutId: stringOption(options.multiAgentFanout || options.multiAgentFanoutId || options.fanout || options["multi-agent-fanout"]),
|
|
117
|
+
});
|
|
118
|
+
(0, run_store_2.saveCheckpoint)(run);
|
|
119
|
+
return manifest;
|
|
114
120
|
});
|
|
115
|
-
(0, run_store_2.saveCheckpoint)(run);
|
|
116
|
-
return manifest;
|
|
117
121
|
}
|
|
118
122
|
/** `showWorkerManifest` — write + return a worker's manifest. */
|
|
119
123
|
showWorkerManifest(runId, workerId) {
|
|
@@ -126,10 +130,13 @@ class CoolWorkflowRunner {
|
|
|
126
130
|
/** `recordWorkerOutput` — accept a worker's result and checkpoint. Mirrors
|
|
127
131
|
* v2's workerOutputCli: recordWorkerOutput + saveCheckpoint. */
|
|
128
132
|
recordWorkerOutput(runId, workerId, resultPath, options = {}) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
(0, run_store_2.
|
|
132
|
-
|
|
133
|
+
// Hold the state.json lock across the whole load -> change -> save so a
|
|
134
|
+
// concurrent run mutation cannot drop this worker output (lost-update class).
|
|
135
|
+
return (0, run_store_2.withRunStateLock)(runId, this.cwd(), (run) => {
|
|
136
|
+
const output = (0, worker_isolation_1.recordWorkerOutput)(run, workerId, this.resolveFromBase(resultPath), options);
|
|
137
|
+
(0, run_store_2.saveCheckpoint)(run);
|
|
138
|
+
return output;
|
|
139
|
+
});
|
|
133
140
|
}
|
|
134
141
|
/** `auditSummary` — the trust-audit rollup. */
|
|
135
142
|
auditSummary(runId) {
|
|
@@ -172,29 +179,33 @@ class CoolWorkflowRunner {
|
|
|
172
179
|
* `{ runId, commit }` to match the old orchestrator's shape (the scripts
|
|
173
180
|
* read `commitResult.commit`). */
|
|
174
181
|
commit(runId, input = {}) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
182
|
+
// Hold the state.json lock across the whole load -> commit -> save (both
|
|
183
|
+
// the success and the fail-closed catch persist) so a concurrent run
|
|
184
|
+
// mutation cannot drop this commit (lost-update class).
|
|
185
|
+
return (0, run_store_2.withRunStateLock)(runId, this.cwd(), (run) => {
|
|
186
|
+
const options = typeof input === "string" ? { reason: input } : input;
|
|
187
|
+
const allowCheckpoint = Boolean(options.allowUnverifiedCheckpoint || options["allow-unverified-checkpoint"]);
|
|
188
|
+
const hasGateOption = Boolean(options.verifier || options.verifierNode || options["verifier-node"] || options.candidate || options.selection);
|
|
189
|
+
try {
|
|
190
|
+
const commit = (0, commit_1.commitState)(run, {
|
|
191
|
+
reason: stringOption(options.reason) || "manual",
|
|
192
|
+
verifierNodeId: stringOption(options.verifier) || stringOption(options.verifierNode) || stringOption(options["verifier-node"]),
|
|
193
|
+
candidateId: stringOption(options.candidate),
|
|
194
|
+
selectionId: stringOption(options.selection),
|
|
195
|
+
verifierGated: hasGateOption || !allowCheckpoint,
|
|
196
|
+
allowUnverifiedCheckpoint: allowCheckpoint,
|
|
197
|
+
source: "cli",
|
|
198
|
+
});
|
|
199
|
+
(0, report_1.writeReport)(run);
|
|
200
|
+
(0, run_store_2.saveCheckpoint)(run);
|
|
201
|
+
return { runId: run.id, commit };
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
(0, report_1.writeReport)(run);
|
|
205
|
+
(0, run_store_2.saveCheckpoint)(run);
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
198
209
|
}
|
|
199
210
|
/** `report` — write the run's report.md; returns `{ path }` (old shape). */
|
|
200
211
|
report(runId) {
|
|
@@ -52,6 +52,8 @@ exports.recordResultRun = recordResultRun;
|
|
|
52
52
|
exports.commitRun = commitRun;
|
|
53
53
|
const fs = __importStar(require("node:fs"));
|
|
54
54
|
const path = __importStar(require("node:path"));
|
|
55
|
+
const readlinePromises = __importStar(require("node:readline/promises"));
|
|
56
|
+
const numeric_flag_1 = require("../core/util/numeric-flag");
|
|
55
57
|
const pipeline_1 = require("./pipeline");
|
|
56
58
|
const workflow_app_loader_1 = require("./workflow-app-loader");
|
|
57
59
|
const drive_1 = require("./drive");
|
|
@@ -223,8 +225,11 @@ function runDrivePreview(args) {
|
|
|
223
225
|
return (0, drive_1.drivePreview)(runId, cwd, args);
|
|
224
226
|
}
|
|
225
227
|
/** `cw run <app|--run id> --drive [--once]` — plans a fresh run (unless
|
|
226
|
-
* `--run` continues an existing one) and drives it.
|
|
227
|
-
|
|
228
|
+
* `--run` continues an existing one) and drives it. Uses driveAsync (not
|
|
229
|
+
* the plain synchronous drive()) so a live multi-round run actually
|
|
230
|
+
* responds to Ctrl-C/SIGTERM instead of silently ignoring it — see
|
|
231
|
+
* shell/drive.ts's driveAsync doc comment. */
|
|
232
|
+
async function runDriveStep(args) {
|
|
228
233
|
const existingRunId = String(args.runId || args.run || "");
|
|
229
234
|
const options = {
|
|
230
235
|
once: Boolean(args.once),
|
|
@@ -236,7 +241,7 @@ function runDriveStep(args) {
|
|
|
236
241
|
if (existingRunId) {
|
|
237
242
|
const cwd = invocationCwd(args);
|
|
238
243
|
const run = (0, run_store_1.loadRunFromCwd)(existingRunId, cwd);
|
|
239
|
-
return (0, drive_1.
|
|
244
|
+
return (0, drive_1.driveAsync)(existingRunId, run.cwd, options);
|
|
240
245
|
}
|
|
241
246
|
const appId = String(args.appId || args.app || args.positionalApp || "");
|
|
242
247
|
if (!appId)
|
|
@@ -245,7 +250,7 @@ function runDriveStep(args) {
|
|
|
245
250
|
args.repo = invocationCwd(args);
|
|
246
251
|
const app = (0, workflow_app_loader_1.loadWorkflowApp)(appId);
|
|
247
252
|
const run = (0, pipeline_1.plan)(app, planInputsFor(args));
|
|
248
|
-
return (0, drive_1.
|
|
253
|
+
return (0, drive_1.driveAsync)(run.id, run.cwd, options);
|
|
249
254
|
}
|
|
250
255
|
/** `cw quickstart [app] --check` — read-only preflight: does the app
|
|
251
256
|
* resolve, is the repo readable/writable, is a question set, is an
|
|
@@ -385,6 +390,21 @@ function remoteQuickstartCheck(appId, args, candidate) {
|
|
|
385
390
|
const nextCommand = `cw quickstart ${shellWord(appId)} --link ${shellWord(validation.url)}${question ? ` --question ${shellWord(question)}` : ""}`;
|
|
386
391
|
return { schemaVersion: 1, mode: "check", ok, appId, repo: validation.url, checks, nextCommand };
|
|
387
392
|
}
|
|
393
|
+
/** Prompts `Question: ` on STDERR (stdout stays data-only, matching the
|
|
394
|
+
* Rule of Silence) and reads one line from stdin. Returns the trimmed
|
|
395
|
+
* answer, or `undefined` for a blank line. Callers must only invoke this
|
|
396
|
+
* when `process.stdin.isTTY` — it would otherwise block forever on a
|
|
397
|
+
* piped/CI invocation. */
|
|
398
|
+
async function promptForQuestion() {
|
|
399
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stderr });
|
|
400
|
+
try {
|
|
401
|
+
const answer = await rl.question("Question: ");
|
|
402
|
+
return answer.trim() || undefined;
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
rl.close();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
388
408
|
/** `cw quickstart [app] --question ...` — composes plan -> runDrive ->
|
|
389
409
|
* report in one call. Default app is architecture-review. `--check` is a
|
|
390
410
|
* read-only preflight that never plans/drives/writes (see
|
|
@@ -392,7 +412,7 @@ function remoteQuickstartCheck(appId, args, candidate) {
|
|
|
392
412
|
* projection (never drives), `--resume` advances one step (no --run) or
|
|
393
413
|
* continues a named run to completion (--run <id>) — both ported byte-for-
|
|
394
414
|
* byte from the old build's src/capability-core.ts quickstart(). */
|
|
395
|
-
function quickstartRun(args) {
|
|
415
|
+
async function quickstartRun(args) {
|
|
396
416
|
const appId = String(args.appId || args.app || args.workflowId || QUICKSTART_DEFAULT_APP);
|
|
397
417
|
// Remote source: a `--link <url>` — or a URL passed to `--repo`/`-dir` — is
|
|
398
418
|
// materialized to a LOCAL checkout HERE (capability/shell layer). Cloning is
|
|
@@ -406,6 +426,37 @@ function quickstartRun(args) {
|
|
|
406
426
|
args.repo = invocationCwd(args);
|
|
407
427
|
if (Boolean(args.check))
|
|
408
428
|
return quickstartCheck(appId, args, remoteCandidate);
|
|
429
|
+
// `--resume`: a discoverability flag over the existing continuation. With no
|
|
430
|
+
// `--run`, advance exactly ONE step (reuse the `--once` path) and print a
|
|
431
|
+
// copy-pasteable continue line; with `--run <id>`, continue that run to
|
|
432
|
+
// completion (the default drive). It adds no new execution path. Byte-exact to
|
|
433
|
+
// the old build's src/capability-core.ts quickstart(). Hoisted above the TTY
|
|
434
|
+
// prompt below: `existingRunId` is the one fact (mirrored at both the
|
|
435
|
+
// `--preview` branch's `if (!previewRunId)` and the main path's `if
|
|
436
|
+
// (existingRunId) {...} else { run = plan(...) }`) that decides whether THIS
|
|
437
|
+
// invocation plans a fresh run at all — an existing run never needs a
|
|
438
|
+
// question, no matter which mode (--preview/--resume/plain) is asking for it.
|
|
439
|
+
const resume = Boolean(args.resume);
|
|
440
|
+
const existingRunId = String(args.runId || args.run || "");
|
|
441
|
+
const resumeRunId = resume && existingRunId ? existingRunId : undefined;
|
|
442
|
+
// On an interactive TTY with no --question, ask for one instead of
|
|
443
|
+
// failing closed with "Missing required input: question" (byte-behavior
|
|
444
|
+
// port of the old build's TTY prompt — SPEC/cli-surface.md:34,502). A
|
|
445
|
+
// blank/empty answer leaves `question` unset, same as never passing
|
|
446
|
+
// --question at all — plan()'s own required-input check still fails
|
|
447
|
+
// closed for an app that declares `question` required. Never fires
|
|
448
|
+
// under --check (returned above; --check reports a missing question as
|
|
449
|
+
// a preflight issue, not something to prompt for), off a TTY (a piped
|
|
450
|
+
// invocation must never block on stdin), or when `existingRunId` is set
|
|
451
|
+
// (a `--preview --run <id>` or `--resume --run <id>` continues an
|
|
452
|
+
// already-planned run and never calls `plan()` again, so it never
|
|
453
|
+
// needs a question — prompting there would block on stdin for no
|
|
454
|
+
// reason; a review on this cycle caught exactly that regression).
|
|
455
|
+
if (process.stdin.isTTY && !existingRunId && !(typeof args.question === "string" && args.question.trim())) {
|
|
456
|
+
const answer = await promptForQuestion();
|
|
457
|
+
if (answer)
|
|
458
|
+
args.question = answer;
|
|
459
|
+
}
|
|
409
460
|
// Materialize the remote NOW — after `--check` (never fetches) and before any
|
|
410
461
|
// plan/drive — so the core only ever sees the local checkout. Fails closed: a
|
|
411
462
|
// bad URL / blocked scheme / missing git / fetch failure throws here.
|
|
@@ -424,14 +475,6 @@ function quickstartRun(args) {
|
|
|
424
475
|
if (remoteSource.ref)
|
|
425
476
|
args.sourceRef = remoteSource.ref;
|
|
426
477
|
}
|
|
427
|
-
// `--resume`: a discoverability flag over the existing continuation. With no
|
|
428
|
-
// `--run`, advance exactly ONE step (reuse the `--once` path) and print a
|
|
429
|
-
// copy-pasteable continue line; with `--run <id>`, continue that run to
|
|
430
|
-
// completion (the default drive). It adds no new execution path. Byte-exact to
|
|
431
|
-
// the old build's src/capability-core.ts quickstart().
|
|
432
|
-
const resume = Boolean(args.resume);
|
|
433
|
-
const existingRunId = String(args.runId || args.run || "");
|
|
434
|
-
const resumeRunId = resume && existingRunId ? existingRunId : undefined;
|
|
435
478
|
// `--preview`: read-only, deterministic next-step projection (no spawn, no
|
|
436
479
|
// commit). Plan a fresh run (the read-only first verb) then project the next
|
|
437
480
|
// drive step. Never drives.
|
|
@@ -464,7 +507,7 @@ function quickstartRun(args) {
|
|
|
464
507
|
else {
|
|
465
508
|
run = (0, pipeline_1.plan)(resolveWorkflowAppForPlan(appId), planInputsFor(args));
|
|
466
509
|
}
|
|
467
|
-
const result = (0, drive_1.
|
|
510
|
+
const result = await (0, drive_1.driveAsync)(run.id, run.cwd, options);
|
|
468
511
|
const finalRun = (0, run_store_1.loadRunFromCwd)(run.id, run.cwd);
|
|
469
512
|
(0, report_1.writeReport)(finalRun);
|
|
470
513
|
// Tamper-evident provenance: bind the remote origin (url@sha) into the run's
|
|
@@ -552,84 +595,94 @@ function quickstartRun(args) {
|
|
|
552
595
|
}
|
|
553
596
|
function dispatchRun(args) {
|
|
554
597
|
const runId = String(args.runId);
|
|
555
|
-
|
|
556
|
-
//
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
598
|
+
// The whole load -> change -> save cycle holds the state.json lock so a
|
|
599
|
+
// concurrent dispatch/result on the same run cannot drop this update.
|
|
600
|
+
return (0, run_store_1.withRunStateLock)(runId, invocationCwd(args), (run) => {
|
|
601
|
+
// parseArgv keys long flags in kebab-case; accept camelCase as a fallback.
|
|
602
|
+
const flag = (kebab, camel) => {
|
|
603
|
+
const v = args[kebab] ?? args[camel];
|
|
604
|
+
return typeof v === "string" && v.trim() ? v : undefined;
|
|
605
|
+
};
|
|
606
|
+
const manifest = (0, dispatch_1.createDispatchManifest)(run, (0, numeric_flag_1.requiredNumberFlag)(args.limit, "--limit"), {
|
|
607
|
+
sandboxProfileId: typeof args.sandbox === "string" ? args.sandbox : undefined,
|
|
608
|
+
sandbox: typeof args.sandbox === "string" ? args.sandbox : undefined,
|
|
609
|
+
backendId: typeof args.backend === "string" ? args.backend : undefined,
|
|
610
|
+
multiAgentRunId: flag("multi-agent-run", "multiAgentRun"),
|
|
611
|
+
multiAgentGroupId: flag("multi-agent-group", "multiAgentGroup"),
|
|
612
|
+
multiAgentRoleId: flag("multi-agent-role", "multiAgentRole"),
|
|
613
|
+
multiAgentFanoutId: flag("multi-agent-fanout", "multiAgentFanout"),
|
|
614
|
+
});
|
|
615
|
+
if (manifest.dispatchId) {
|
|
616
|
+
(0, commit_1.commitState)(run, `dispatch:${manifest.dispatchId}`);
|
|
617
|
+
(0, run_store_1.saveCheckpoint)(run);
|
|
618
|
+
(0, report_1.writeReport)(run);
|
|
619
|
+
}
|
|
620
|
+
return manifest;
|
|
569
621
|
});
|
|
570
|
-
if (manifest.dispatchId) {
|
|
571
|
-
(0, commit_1.commitState)(run, `dispatch:${manifest.dispatchId}`);
|
|
572
|
-
(0, run_store_1.saveCheckpoint)(run);
|
|
573
|
-
(0, report_1.writeReport)(run);
|
|
574
|
-
}
|
|
575
|
-
return manifest;
|
|
576
622
|
}
|
|
577
623
|
function recordResultRun(args) {
|
|
578
624
|
const runId = String(args.runId);
|
|
579
625
|
const taskId = String(args.taskId);
|
|
580
626
|
const resultPath = String(args.resultPath);
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
627
|
+
// Two processes recording results for two tasks of the SAME run used to
|
|
628
|
+
// race: both loaded, and the later saveCheckpoint dropped the earlier
|
|
629
|
+
// task's completion. The lock now covers the whole cycle.
|
|
630
|
+
return (0, run_store_1.withRunStateLock)(runId, invocationCwd(args), (run) => {
|
|
631
|
+
const task = run.tasks.find((t) => t.id === taskId);
|
|
632
|
+
if (!task || !task.workerId)
|
|
633
|
+
throw new Error(`Unknown task id for run ${runId}: ${taskId}`);
|
|
634
|
+
const absolute = path.resolve(resultPath);
|
|
635
|
+
// A result path inside a system directory is never accepted (POLA): the
|
|
636
|
+
// operator file gets copied into the worker's result.md, so a /etc/passwd
|
|
637
|
+
// source would smuggle system content into a run. Byte-behavior port of the
|
|
638
|
+
// old build's recordResult system-directory blacklist.
|
|
639
|
+
if (/^\/(etc|bin|sbin|usr|Library|System|Applications|boot|dev|proc|sys|root|var\/log|var\/run)\//.test(absolute)) {
|
|
640
|
+
throw new Error(`Result path must not be a system directory: ${resultPath}`);
|
|
641
|
+
}
|
|
642
|
+
if (!fs.existsSync(absolute))
|
|
643
|
+
throw new Error(`Result file does not exist: ${resultPath}`);
|
|
644
|
+
const workerId = String(task.workerId);
|
|
645
|
+
// Host-attested `cw result <run> <task> <file>` intake: the operator hands CW
|
|
646
|
+
// an EXTERNAL result file that lives OUTSIDE the worker's read-only write
|
|
647
|
+
// boundary. The old task-level recordResult (lifecycle-operations.ts:279-280)
|
|
648
|
+
// COPIED that external file into the run's results area and recorded the
|
|
649
|
+
// internal path — it never ran the external path through validateSandboxWrite.
|
|
650
|
+
// v2 collapsed the two intakes into recordWorkerOutput, which sandbox-validates
|
|
651
|
+
// its input against the worker boundary, so a bare external path is rejected
|
|
652
|
+
// ("write path is outside sandbox profile <id>"). Restore the copy-in: stage
|
|
653
|
+
// the operator file at the worker's OWN result.md (which IS inside the write
|
|
654
|
+
// boundary), then record that internal path exactly like a driven worker.
|
|
655
|
+
const manifest = (0, worker_isolation_1.showWorkerManifest)(run, workerId);
|
|
656
|
+
fs.mkdirSync(path.dirname(manifest.resultPath), { recursive: true });
|
|
657
|
+
fs.copyFileSync(absolute, manifest.resultPath);
|
|
658
|
+
const output = (0, worker_isolation_1.recordWorkerOutput)(run, workerId, manifest.resultPath, {
|
|
659
|
+
requireAttestedTelemetry: (0, agent_config_1.resolveAgentConfig)(args).requireAttestedTelemetry,
|
|
660
|
+
allowUnattested: Boolean(args.allowUnattested ?? args["allow-unattested"]),
|
|
661
|
+
});
|
|
662
|
+
// Host-attested token usage (v0.1.31): record it verbatim as provenance when
|
|
663
|
+
// the operator supplied `--usage-*` flags; CW never synthesizes usage. The old
|
|
664
|
+
// task-level recordResult set `task.usage = usage` (lifecycle-operations.ts:286)
|
|
665
|
+
// and its unit was the TASK. v2 records through recordWorkerOutput, which gives
|
|
666
|
+
// the worker an `output` record — so the observability usage UNIT becomes the
|
|
667
|
+
// WORKER (deriveUsageTotals reads worker.usage for workers with output, and
|
|
668
|
+
// EXCLUDES that task). Attach the usage to the worker scope so the report counts
|
|
669
|
+
// it as an attested unit; also stamp task.usage for byte-parity with the old
|
|
670
|
+
// task-level record.
|
|
671
|
+
const usage = (0, observability_1.parseUsageFromArgs)(args, new Date().toISOString());
|
|
672
|
+
if (usage) {
|
|
673
|
+
task.usage = usage;
|
|
674
|
+
const scope = (0, worker_isolation_1.getWorkerScope)(run, workerId);
|
|
675
|
+
if (scope)
|
|
676
|
+
scope.usage = usage;
|
|
677
|
+
}
|
|
678
|
+
// Byte-exact to the old build's orchestrator recordWorkerOutput()
|
|
679
|
+
// wrapper: an accepted result is its own checkpoint commit, not just a
|
|
680
|
+
// bare saveCheckpoint (SPEC/pipeline-run.md's persist-ordering rule).
|
|
681
|
+
(0, commit_1.commitState)(run, `worker:${workerId}:result`);
|
|
682
|
+
(0, run_store_1.saveCheckpoint)(run);
|
|
683
|
+
(0, report_1.writeReport)(run);
|
|
684
|
+
return output;
|
|
685
|
+
});
|
|
633
686
|
}
|
|
634
687
|
/** `cw commit <run-id>` — byte-exact port of the old build's
|
|
635
688
|
* `orchestrator/lifecycle-operations.ts`'s `commit()`: the CLI/MCP
|
|
@@ -639,29 +692,33 @@ function recordResultRun(args) {
|
|
|
639
692
|
* still leaves the run's report/state current on disk. */
|
|
640
693
|
function commitRun(args) {
|
|
641
694
|
const runId = String(args.runId);
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
695
|
+
// Hold the state.json lock across the whole load -> commit -> save (both the
|
|
696
|
+
// success and the fail-closed catch persist) so a concurrent run mutation
|
|
697
|
+
// cannot drop this commit (lost-update class, matching dispatchRun/recordResultRun).
|
|
698
|
+
return (0, run_store_1.withRunStateLock)(runId, invocationCwd(args), (run) => {
|
|
699
|
+
const allowCheckpoint = Boolean(args.allowUnverifiedCheckpoint || args["allow-unverified-checkpoint"]);
|
|
700
|
+
const hasGateOption = Boolean(args.verifier || args.verifierNode || args["verifier-node"] || args.candidate || args.selection);
|
|
701
|
+
try {
|
|
702
|
+
const commit = (0, commit_1.commitState)(run, {
|
|
703
|
+
reason: typeof args.reason === "string" && args.reason ? args.reason : "manual",
|
|
704
|
+
verifierNodeId: (typeof args.verifier === "string" && args.verifier) ||
|
|
705
|
+
(typeof args.verifierNode === "string" && args.verifierNode) ||
|
|
706
|
+
(typeof args["verifier-node"] === "string" && args["verifier-node"]) ||
|
|
707
|
+
undefined,
|
|
708
|
+
candidateId: typeof args.candidate === "string" ? args.candidate : undefined,
|
|
709
|
+
selectionId: typeof args.selection === "string" ? args.selection : undefined,
|
|
710
|
+
verifierGated: hasGateOption || !allowCheckpoint,
|
|
711
|
+
allowUnverifiedCheckpoint: allowCheckpoint,
|
|
712
|
+
source: "cli",
|
|
713
|
+
});
|
|
714
|
+
(0, report_1.writeReport)(run);
|
|
715
|
+
(0, run_store_1.saveCheckpoint)(run);
|
|
716
|
+
return { runId: run.id, commit };
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
(0, report_1.writeReport)(run);
|
|
720
|
+
(0, run_store_1.saveCheckpoint)(run);
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
723
|
+
});
|
|
667
724
|
}
|
|
@@ -106,6 +106,7 @@ const node_store_1 = require("./node-store");
|
|
|
106
106
|
const node_snapshot_1 = require("../core/state/node-snapshot");
|
|
107
107
|
const node_projection_1 = require("../core/state/node-projection");
|
|
108
108
|
const hash_1 = require("../core/hash");
|
|
109
|
+
const collate_1 = require("../core/util/collate");
|
|
109
110
|
const run_registry_io_1 = require("./run-registry-io");
|
|
110
111
|
// ---------------------------------------------------------------------------
|
|
111
112
|
// Content addressing + byte measurement (in-process, no `du`) — carried
|
|
@@ -638,6 +639,16 @@ function freeBulk(run, tombstone) {
|
|
|
638
639
|
let freedBytes = 0;
|
|
639
640
|
for (const entry of tombstone.freed) {
|
|
640
641
|
const abs = path.join(runDir, entry.path);
|
|
642
|
+
// planReclamation always derives entry.path as a relative path already
|
|
643
|
+
// confined under runDir, but a tampered/imported state.json could carry a
|
|
644
|
+
// worker/artifact path that resolves outside it — re-check containment
|
|
645
|
+
// right before the recursive delete so that can never turn into an
|
|
646
|
+
// out-of-tree rmSync.
|
|
647
|
+
if (!(0, fs_atomic_1.isContainedPath)(abs, runDir)) {
|
|
648
|
+
throw new ReclamationError("unsafe-free-path", `refusing to free path outside the run directory: ${entry.path}`, {
|
|
649
|
+
path: entry.path,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
641
652
|
const before = dirBytes(abs);
|
|
642
653
|
fs.rmSync(abs, { recursive: true, force: true });
|
|
643
654
|
freedBytes += before;
|
|
@@ -866,7 +877,7 @@ function gcPlan(host, options = {}) {
|
|
|
866
877
|
eligibleCount,
|
|
867
878
|
bytesToFree,
|
|
868
879
|
entries,
|
|
869
|
-
nextAction: eligibleCount ? "
|
|
880
|
+
nextAction: eligibleCount ? "cw gc run" : "cw run search",
|
|
870
881
|
};
|
|
871
882
|
}
|
|
872
883
|
function gcRun(host, options = {}) {
|
|
@@ -935,7 +946,7 @@ function gcRun(host, options = {}) {
|
|
|
935
946
|
reclaimed,
|
|
936
947
|
refused,
|
|
937
948
|
totalBytesFreed,
|
|
938
|
-
nextAction: reclaimed.length ? "
|
|
949
|
+
nextAction: reclaimed.length ? "cw gc verify <run-id>" : "cw gc plan",
|
|
939
950
|
};
|
|
940
951
|
}
|
|
941
952
|
function gcVerify(host, runId, options = {}) {
|
|
@@ -951,7 +962,7 @@ function gcVerify(host, runId, options = {}) {
|
|
|
951
962
|
capability: "re-runnable",
|
|
952
963
|
chainLength: 0,
|
|
953
964
|
checks: [{ name: "located", pass: false, code: "not-reclaimed", detail: "run source not found" }],
|
|
954
|
-
nextAction: "
|
|
965
|
+
nextAction: "cw registry refresh" + (scope === "home" ? " --scope home" : ""),
|
|
955
966
|
};
|
|
956
967
|
}
|
|
957
968
|
const run = host.loadRun(located.record.repo, runId);
|
|
@@ -984,7 +995,7 @@ function gcVerify(host, runId, options = {}) {
|
|
|
984
995
|
tombstoneHash: last?.tombstoneHash,
|
|
985
996
|
chainLength: result.tombstones.length,
|
|
986
997
|
checks,
|
|
987
|
-
nextAction: verified ? "
|
|
998
|
+
nextAction: verified ? "cw run show " + runId : "cw gc plan",
|
|
988
999
|
};
|
|
989
1000
|
}
|
|
990
1001
|
// ---------------------------------------------------------------------------
|
|
@@ -1287,7 +1298,7 @@ function readCloneEntries(root) {
|
|
|
1287
1298
|
bytes: dirSize(dir),
|
|
1288
1299
|
});
|
|
1289
1300
|
}
|
|
1290
|
-
entries.sort((a, b) => (a.fetchedAt || ""
|
|
1301
|
+
entries.sort((a, b) => (0, collate_1.stableCompare)(a.fetchedAt || "", b.fetchedAt || ""));
|
|
1291
1302
|
return entries;
|
|
1292
1303
|
}
|
|
1293
1304
|
/** `cw clones list` — every cached remote checkout with its origin,
|