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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cool-workflow",
|
|
3
3
|
"description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.98",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "COOLWHITE LLC"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cool-workflow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.98",
|
|
4
4
|
"description": "A workflow control plane and run-time you are able to check: it sends out jobs in TypeScript, makes certain of work against facts before it goes through, puts state into fixed records, orders jobs by time, runs jobs again and again, gets a group of agents to do their parts together, and talks MCP. It gives the doing of the work to outside agents — it never runs the models itself.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "COOLWHITE LLC"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "architecture-review",
|
|
4
4
|
"title": "Architecture Review",
|
|
5
5
|
"summary": "Map a repository architecture, assess risks, verify important findings, and synthesize an evidence-backed verdict.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "architecture-review-fast",
|
|
4
4
|
"title": "Architecture Review Fast",
|
|
5
5
|
"summary": "Run a shorter architecture review with parallel map and assess phases for faster first results.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "pr-review-fix-ci",
|
|
4
4
|
"title": "PR Review Fix CI",
|
|
5
5
|
"summary": "Review a pull request or branch, inspect CI failures, diagnose actionable issues, optionally patch, verify, and summarize with evidence.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "release-cut",
|
|
4
4
|
"title": "Release Cut",
|
|
5
5
|
"summary": "Prepare a release with checklist discipline: version checks, changelog, tests, packaging, release notes, and final verification.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "research-synthesis",
|
|
4
4
|
"title": "Research Synthesis",
|
|
5
5
|
"summary": "Split a research question into claims, investigate sources, cross-check evidence, verify claims, and synthesize a concise answer.",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.98",
|
|
7
7
|
"author": "COOLWHITE LLC",
|
|
8
8
|
"inputs": [
|
|
9
9
|
{
|
package/dist/capability-core.js
CHANGED
|
@@ -86,6 +86,7 @@ const telemetry_demo_1 = require("./telemetry-demo");
|
|
|
86
86
|
const state_1 = require("./state");
|
|
87
87
|
const run_export_1 = require("./run-export");
|
|
88
88
|
const result_normalize_1 = require("./result-normalize");
|
|
89
|
+
const cli_options_1 = require("./orchestrator/cli-options");
|
|
89
90
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
90
91
|
const node_path_1 = __importDefault(require("node:path"));
|
|
91
92
|
const scheduling_1 = require("./scheduling");
|
|
@@ -553,6 +554,7 @@ const DRIVE_RUNTIME_KEYS = [
|
|
|
553
554
|
"agent-timeout-ms",
|
|
554
555
|
"resume",
|
|
555
556
|
"incremental",
|
|
557
|
+
"concurrency",
|
|
556
558
|
// Remote-source flags (v0.1.91): materialized into a local checkout in the capability
|
|
557
559
|
// layer, never passed to plan as inputs (the resolved sourceUrl/sourceCommit ARE inputs).
|
|
558
560
|
"link",
|
|
@@ -592,6 +594,7 @@ function runDrive(runner, args) {
|
|
|
592
594
|
once: isTrue(args.once),
|
|
593
595
|
now: optionalString(args.now),
|
|
594
596
|
incremental: isTrue(args.incremental),
|
|
597
|
+
concurrency: (0, cli_options_1.numberOption)(args.concurrency),
|
|
595
598
|
args
|
|
596
599
|
});
|
|
597
600
|
}
|
|
@@ -467,7 +467,13 @@ const BUILTIN_CAPABILITIES = [
|
|
|
467
467
|
{ capability: "comment.list", summary: "List append-only comments for a run (optionally one target).", entry: "collaborationCommentList", surface: "both", cli: { path: ["comment", "list"], caseTokens: ["comment"], jsonMode: "flag" }, mcp: { tool: "cw_comment_list", requiredArgs: ["runId"] } },
|
|
468
468
|
{ capability: "handoff", summary: "Record an ownership transfer (from-actor → to-actor) of a run/task.", entry: "collaborationHandoff", surface: "both", cli: { path: ["handoff"], jsonMode: "default" }, mcp: { tool: "cw_handoff", requiredArgs: ["runId", "targetKind|kind", "targetId|target", "to|toActor"] } },
|
|
469
469
|
{ capability: "review.status", summary: "Read the derived per-target review state + collaboration timeline for a run.", entry: "reviewStatus", surface: "both", cli: { path: ["review", "status"], caseTokens: ["review"], jsonMode: "flag" }, mcp: { tool: "cw_review_status", requiredArgs: ["runId"] } },
|
|
470
|
-
{ capability: "review.policy", summary: "Set the run's review-gate policy (required approvals, authorized roles, self-approval rule).", entry: "reviewPolicy", surface: "both", cli: { path: ["review", "policy"], caseTokens: ["review"], jsonMode: "default" }, mcp: { tool: "cw_review_policy", requiredArgs: ["runId"] } }
|
|
470
|
+
{ capability: "review.policy", summary: "Set the run's review-gate policy (required approvals, authorized roles, self-approval rule).", entry: "reviewPolicy", surface: "both", cli: { path: ["review", "policy"], caseTokens: ["review"], jsonMode: "default" }, mcp: { tool: "cw_review_policy", requiredArgs: ["runId"] } },
|
|
471
|
+
// ---- Cross-agent handoff ledger (stage 2, MCP surface + git transport) --
|
|
472
|
+
{ capability: "ledger.propose", summary: "Build a verifiable cross-agent change proposal entry (printed as JSON).", entry: "buildLedgerProposal", surface: "both", cli: { path: ["ledger", "propose"], caseTokens: ["ledger", "propose"], jsonMode: "default" }, mcp: { tool: "cw_ledger_propose", requiredArgs: ["from", "to", "title", "rationale"] }, payloadIdentical: false, reason: "Mints a fresh entry each call: createdAt is the wall-clock instant and the id/digest are derived from it, so the output is inherently non-deterministic and a byte-identity probe does not apply. Both surfaces call the same buildLedgerProposal core; round-trip + fail-closed behavior is covered by ledger-verify-smoke." },
|
|
473
|
+
{ capability: "ledger.review", summary: "Build a verifiable cross-agent review verdict entry (printed as JSON).", entry: "buildLedgerReview", surface: "both", cli: { path: ["ledger", "review"], caseTokens: ["ledger", "review"], jsonMode: "default" }, mcp: { tool: "cw_ledger_review", requiredArgs: ["from", "to", "target", "verdict"] }, payloadIdentical: false, reason: "Mints a fresh timestamped/digested verdict each call — non-deterministic output, same reasoning as ledger.propose. Both surfaces call the same buildLedgerReview core." },
|
|
474
|
+
{ capability: "ledger.verify", summary: "Verify a ledger entry against its content digest (fail-closed on tampering).", entry: "verifyLedgerEntry", surface: "both", cli: { path: ["ledger", "verify"], caseTokens: ["ledger", "verify"], jsonMode: "default" }, mcp: { tool: "cw_ledger_verify", requiredArgs: ["entry"] }, payloadIdentical: false, reason: "The entry arrives by --file/stdin on the CLI and as an `entry` argument over MCP; there is no shared arg-bag the byte-identity probe can feed both. Both surfaces call the same verifyLedgerEntry core; ledger-verify-smoke proves the fail-closed contract." },
|
|
475
|
+
{ capability: "ledger.apply", summary: "Verify a proposal entry and return its suggestedDiff for `git apply` (fail-closed: no diff unless the entry verifies as a proposal).", entry: "applyLedgerProposal", surface: "both", cli: { path: ["ledger", "apply"], caseTokens: ["ledger", "apply"], jsonMode: "default" }, mcp: { tool: "cw_ledger_apply", requiredArgs: ["entry"] }, payloadIdentical: false, reason: "The entry arrives by --file/stdin on the CLI and as an `entry` argument over MCP; there is no shared arg-bag the byte-identity probe can feed both. Both surfaces call the same applyLedgerProposal core (a fail-closed wrapper over verifyLedgerEntry); ledger-apply-smoke proves the diff only escapes a verified proposal." },
|
|
476
|
+
{ capability: "ledger.list", summary: "Read + verify every entry in one or more shared ledger directories (fail-closed inbox; 2+ dirs union-verify mirrors).", entry: "listLedgerEntries", surface: "both", cli: { path: ["ledger", "list"], caseTokens: ["ledger", "list"], jsonMode: "default" }, mcp: { tool: "cw_ledger_list", requiredArgs: ["dir|dirs"] }, payloadIdentical: false, reason: "Output depends on the on-disk contents of the named ledger directory/directories, which the generic payload probe does not populate. Both surfaces call the same listLedgerEntries/unionLedgerEntries core; ledger-verify-smoke covers the fail-closed inbox and the multi-mirror union." }
|
|
471
477
|
];
|
|
472
478
|
/** The capability registry — the single source of truth, deduplicated by
|
|
473
479
|
* capability id (last declaration wins). Derived directly from the table above:
|
|
@@ -54,6 +54,7 @@ const registry_1 = require("./handlers/registry");
|
|
|
54
54
|
const multi_agent_1 = require("./handlers/multi-agent");
|
|
55
55
|
const run_1 = require("./handlers/run");
|
|
56
56
|
const collaboration_1 = require("./handlers/collaboration");
|
|
57
|
+
const ledger_1 = require("./handlers/ledger");
|
|
57
58
|
const blackboard_1 = require("./handlers/blackboard");
|
|
58
59
|
const eval_1 = require("./handlers/eval");
|
|
59
60
|
const node_1 = require("./handlers/node");
|
|
@@ -427,6 +428,9 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
427
428
|
case "review":
|
|
428
429
|
(0, collaboration_1.handleReview)(args, runner);
|
|
429
430
|
return;
|
|
431
|
+
case "ledger":
|
|
432
|
+
(0, ledger_1.handleLedger)(args, runner);
|
|
433
|
+
return;
|
|
430
434
|
case "loop": {
|
|
431
435
|
(0, io_1.printJson)(scheduler.create({ ...args.options, kind: "loop" }));
|
|
432
436
|
return;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `cw ledger propose|review|verify` — the cross-agent handoff ledger CLI surface.
|
|
3
|
+
// A proposing agent prints a proposal or a review verdict as a verifiable JSON
|
|
4
|
+
// entry; the receiving side verifies it fail-closed before acting. See
|
|
5
|
+
// docs/cross-agent-ledger.7.md and docs/designs/handoff-ledger.md.
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.handleLedger = handleLedger;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const ledger_1 = require("../../ledger");
|
|
43
|
+
const io_1 = require("../io");
|
|
44
|
+
/** Coerce a repeatable/comma-joined list option to a clean string[]. */
|
|
45
|
+
function listOption(value) {
|
|
46
|
+
const parts = Array.isArray(value) ? value : typeof value === "string" ? value.split(",") : [];
|
|
47
|
+
return parts.map((p) => String(p).trim()).filter(Boolean);
|
|
48
|
+
}
|
|
49
|
+
function stringOption(value) {
|
|
50
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
51
|
+
}
|
|
52
|
+
function nowIso() {
|
|
53
|
+
return new Date().toISOString();
|
|
54
|
+
}
|
|
55
|
+
function handleLedger(args, _runner) {
|
|
56
|
+
const [subcommand] = args.positionals;
|
|
57
|
+
const opts = args.options;
|
|
58
|
+
switch (subcommand) {
|
|
59
|
+
case "propose": {
|
|
60
|
+
const entry = (0, ledger_1.buildLedgerProposal)({
|
|
61
|
+
from: (0, io_1.required)(stringOption(opts.from), "--from <agent/repo>"),
|
|
62
|
+
to: (0, io_1.required)(stringOption(opts.to), "--to <agent/repo>"),
|
|
63
|
+
title: (0, io_1.required)(stringOption(opts.title), "--title <text>"),
|
|
64
|
+
rationale: (0, io_1.required)(stringOption(opts.rationale), "--rationale <text>"),
|
|
65
|
+
targetFiles: listOption(opts.files),
|
|
66
|
+
// Do NOT trim the diff: it is a unified patch (payload, not a label), and
|
|
67
|
+
// trimming strips the trailing newline `git apply` requires — a trimmed
|
|
68
|
+
// diff is a corrupt patch. Presence is detected with a trimmed test, but
|
|
69
|
+
// the bytes are passed through verbatim (matching the MCP propose path).
|
|
70
|
+
suggestedDiff: typeof opts.diff === "string" && opts.diff.trim() ? opts.diff : undefined,
|
|
71
|
+
createdAt: nowIso()
|
|
72
|
+
});
|
|
73
|
+
(0, io_1.printJson)(entry);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
case "review": {
|
|
77
|
+
const verdictRaw = (0, io_1.required)(stringOption(opts.verdict), "--verdict <approved|rejected>").toUpperCase();
|
|
78
|
+
if (verdictRaw !== "APPROVED" && verdictRaw !== "REJECTED") {
|
|
79
|
+
throw new Error('--verdict must be "approved" or "rejected".');
|
|
80
|
+
}
|
|
81
|
+
const entry = (0, ledger_1.buildLedgerReview)({
|
|
82
|
+
from: (0, io_1.required)(stringOption(opts.from), "--from <agent/repo>"),
|
|
83
|
+
to: (0, io_1.required)(stringOption(opts.to), "--to <agent/repo>"),
|
|
84
|
+
target: (0, io_1.required)(stringOption(opts.target), "--target <proposal-id|pr-ref>"),
|
|
85
|
+
verdict: verdictRaw,
|
|
86
|
+
findings: listOption(opts.findings),
|
|
87
|
+
createdAt: nowIso()
|
|
88
|
+
});
|
|
89
|
+
(0, io_1.printJson)(entry);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
case "verify": {
|
|
93
|
+
const file = stringOption(opts.file);
|
|
94
|
+
let text;
|
|
95
|
+
try {
|
|
96
|
+
// --file <path>, else read the entry from stdin (fd 0).
|
|
97
|
+
text = fs.readFileSync(file || 0, "utf8");
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new Error(`Cannot read ledger entry${file ? ` from ${file}` : " from stdin"}: ${error.message}`);
|
|
101
|
+
}
|
|
102
|
+
let parsed;
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(text);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// A non-JSON input is itself a fail-closed refusal, not a crash.
|
|
108
|
+
(0, io_1.printJson)({ ok: false, id: null, kind: null, checks: [{ name: "parse", pass: false, code: "ledger-bad-json" }], failedChecks: [{ name: "parse", code: "ledger-bad-json" }] });
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const result = (0, ledger_1.verifyLedgerEntry)(parsed);
|
|
113
|
+
(0, io_1.printJson)(result);
|
|
114
|
+
// Fail-closed: a tampered/malformed entry exits non-zero so
|
|
115
|
+
// `cw ledger verify <file> && open-pr` cannot proceed on a lie.
|
|
116
|
+
if (!result.ok)
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
case "apply": {
|
|
121
|
+
const file = stringOption(opts.file);
|
|
122
|
+
let text;
|
|
123
|
+
try {
|
|
124
|
+
// --file <path>, else read the entry from stdin (fd 0), same as verify.
|
|
125
|
+
text = fs.readFileSync(file || 0, "utf8");
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
throw new Error(`Cannot read ledger entry${file ? ` from ${file}` : " from stdin"}: ${error.message}`);
|
|
129
|
+
}
|
|
130
|
+
let parsed;
|
|
131
|
+
try {
|
|
132
|
+
parsed = JSON.parse(text);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
(0, io_1.printJson)({ ok: false, id: null, kind: null, diff: null, failedChecks: [{ name: "parse", code: "ledger-bad-json" }] });
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const result = (0, ledger_1.applyLedgerProposal)(parsed);
|
|
140
|
+
(0, io_1.printJson)(result);
|
|
141
|
+
// Fail-closed: the diff only comes out (ok:true) when the proposal verifies,
|
|
142
|
+
// so `cw ledger apply <file> | git apply` never feeds git an unverified patch.
|
|
143
|
+
if (!result.ok)
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
case "list": {
|
|
148
|
+
// `--dir` is repeatable: 2+ dirs union-verify multiple mirrors into one
|
|
149
|
+
// inbox; a single --dir keeps the original single-directory output (POLA).
|
|
150
|
+
const dirs = Array.isArray(opts.dir) ? opts.dir.map(String).filter(Boolean) : [];
|
|
151
|
+
if (dirs.length > 1) {
|
|
152
|
+
const union = (0, ledger_1.unionLedgerEntries)(dirs);
|
|
153
|
+
(0, io_1.printJson)(union);
|
|
154
|
+
if (!union.allOk)
|
|
155
|
+
process.exitCode = 1;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const dir = (0, io_1.required)(dirs[0] || stringOption(opts.dir), "--dir <ledger-directory>");
|
|
159
|
+
const result = (0, ledger_1.listLedgerEntries)(dir);
|
|
160
|
+
(0, io_1.printJson)(result);
|
|
161
|
+
// Fail-closed inbox: refuse the whole batch if any entry does not verify.
|
|
162
|
+
if (!result.allOk)
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
default:
|
|
167
|
+
throw new Error("Usage: cw ledger propose|review|verify|apply|list [options]");
|
|
168
|
+
}
|
|
169
|
+
}
|
package/dist/drive.js
CHANGED
|
@@ -43,6 +43,8 @@ const observability_1 = require("./observability");
|
|
|
43
43
|
const loop_expansion_1 = require("./loop-expansion");
|
|
44
44
|
const telemetry_attestation_1 = require("./telemetry-attestation");
|
|
45
45
|
const state_1 = require("./state");
|
|
46
|
+
const commit_1 = require("./commit");
|
|
47
|
+
const report_1 = require("./orchestrator/report");
|
|
46
48
|
const trust_audit_1 = require("./trust-audit");
|
|
47
49
|
const compare_1 = require("./compare");
|
|
48
50
|
exports.DRIVE_SCHEMA_VERSION = 1;
|
|
@@ -183,7 +185,7 @@ function buildAgentRequest(ctx, run, task, manifest, preparedOutcome) {
|
|
|
183
185
|
* fulfill step then settles it through runBackend instead of spawning again,
|
|
184
186
|
* so the concurrent round and the serial step share EVERY envelope/accept
|
|
185
187
|
* branch by construction. */
|
|
186
|
-
function processSelectedTask(ctx, selected, preparedOutcome) {
|
|
188
|
+
function processSelectedTask(ctx, selected, preparedOutcome, deferPersist = false) {
|
|
187
189
|
const { runner, runId } = ctx;
|
|
188
190
|
let run = runner.loadRun(runId);
|
|
189
191
|
// 1. DISPATCH (only a fresh pending task; a running task is a retry on its scope).
|
|
@@ -191,7 +193,7 @@ function processSelectedTask(ctx, selected, preparedOutcome) {
|
|
|
191
193
|
let workerId = selected.workerId;
|
|
192
194
|
let dispatched = false;
|
|
193
195
|
if (selected.status === "pending") {
|
|
194
|
-
const manifest = runner.dispatch(runId, { limit: 1, backend: selected.agentType || "agent" });
|
|
196
|
+
const manifest = runner.dispatch(runId, { limit: 1, backend: selected.agentType || "agent", ...(deferPersist ? { persistState: false } : {}) });
|
|
195
197
|
const task = manifest.tasks.find((entry) => entry.id === selected.id) || manifest.tasks[0];
|
|
196
198
|
if (!task || !task.workerId) {
|
|
197
199
|
return step("dispatch", "failed", { runId, taskId: selected.id, phase: selected.phase, reason: "dispatch produced no worker scope" });
|
|
@@ -216,10 +218,10 @@ function processSelectedTask(ctx, selected, preparedOutcome) {
|
|
|
216
218
|
emitProgress(`↺ ${selected.label || selected.id} (${selected.phase}) — accepting cached result`);
|
|
217
219
|
try {
|
|
218
220
|
node_fs_1.default.writeFileSync(manifest.resultPath, node_fs_1.default.readFileSync(cachePath, "utf8"), "utf8");
|
|
219
|
-
runner.recordWorkerOutput(runId, workerId, manifest.resultPath, {});
|
|
221
|
+
runner.recordWorkerOutput(runId, workerId, manifest.resultPath, deferPersist ? { persistState: false } : {});
|
|
220
222
|
}
|
|
221
223
|
catch (error) {
|
|
222
|
-
return handleHop(ctx, selected, workerId, `result cache rejected: ${error instanceof Error ? error.message : String(error)}
|
|
224
|
+
return handleHop(ctx, selected, workerId, `result cache rejected: ${error instanceof Error ? error.message : String(error)}`, deferPersist);
|
|
223
225
|
}
|
|
224
226
|
return step("accept", "ok", {
|
|
225
227
|
runId,
|
|
@@ -233,7 +235,7 @@ function processSelectedTask(ctx, selected, preparedOutcome) {
|
|
|
233
235
|
// CHILD run and bind its report back as this task's result. Leaf work is still
|
|
234
236
|
// external-agent delegation at every level; CW imports no model SDK here.
|
|
235
237
|
if (selected.subWorkflow) {
|
|
236
|
-
return runSubWorkflow(ctx, run, selected, workerId, manifest);
|
|
238
|
+
return runSubWorkflow(ctx, run, selected, workerId, manifest, deferPersist);
|
|
237
239
|
}
|
|
238
240
|
emitProgress(`→ ${selected.label || selected.id} (${selected.phase}) — ${dispatched ? "dispatched, " : ""}spawning agent, may take minutes…`);
|
|
239
241
|
const envelope = (0, execution_backend_1.runBackend)(buildAgentRequest(ctx, run, selected, manifest, preparedOutcome));
|
|
@@ -242,16 +244,17 @@ function processSelectedTask(ctx, selected, preparedOutcome) {
|
|
|
242
244
|
const reportedUsage = handle?.metadata?.reportedUsage;
|
|
243
245
|
const usageSignature = handle?.metadata?.usageSignature;
|
|
244
246
|
if (envelope.status !== "completed") {
|
|
245
|
-
return handleHop(ctx, selected, workerId, `agent hop ${envelope.status}: ${envelope.result.summary}
|
|
247
|
+
return handleHop(ctx, selected, workerId, `agent hop ${envelope.status}: ${envelope.result.summary}`, deferPersist);
|
|
246
248
|
}
|
|
247
249
|
// 3. ACCEPT — the SEPARATE recordWorkerOutput layer validates + records result.md.
|
|
248
250
|
// A missing result.md is a failed hop (pre-checked so no terminal side effect);
|
|
249
251
|
// an invalid result.md throws at validation BEFORE any state mutation.
|
|
250
252
|
if (!manifest.resultPath || !node_fs_1.default.existsSync(manifest.resultPath)) {
|
|
251
|
-
return handleHop(ctx, selected, workerId, "agent produced no result.md");
|
|
253
|
+
return handleHop(ctx, selected, workerId, "agent produced no result.md", deferPersist);
|
|
252
254
|
}
|
|
253
255
|
try {
|
|
254
256
|
runner.recordWorkerOutput(runId, workerId, manifest.resultPath, {
|
|
257
|
+
...(deferPersist ? { persistState: false } : {}),
|
|
255
258
|
agentDelegation: {
|
|
256
259
|
handle: handle,
|
|
257
260
|
model: reportedModel,
|
|
@@ -270,7 +273,7 @@ function processSelectedTask(ctx, selected, preparedOutcome) {
|
|
|
270
273
|
});
|
|
271
274
|
}
|
|
272
275
|
catch (error) {
|
|
273
|
-
return handleHop(ctx, selected, workerId, `result.md rejected: ${error instanceof Error ? error.message : String(error)}
|
|
276
|
+
return handleHop(ctx, selected, workerId, `result.md rejected: ${error instanceof Error ? error.message : String(error)}`, deferPersist);
|
|
274
277
|
}
|
|
275
278
|
if (cachePath && manifest.resultPath && node_fs_1.default.existsSync(manifest.resultPath)) {
|
|
276
279
|
writeResultCache(cachePath, node_fs_1.default.readFileSync(manifest.resultPath, "utf8"));
|
|
@@ -404,41 +407,61 @@ function writeResultCache(file, content) {
|
|
|
404
407
|
* collect-all: a failed/hung/dirty hop never aborts its siblings, every hop
|
|
405
408
|
* settles and is recorded (failures park via the same retryOrPark). */
|
|
406
409
|
function driveConcurrentRound(ctx, limit) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
410
|
+
// The whole round runs inside ONE cached in-memory run object (loadWithCache —
|
|
411
|
+
// reentrant, so a sub-workflow task's recursive drive() call cannot clobber this
|
|
412
|
+
// scope's cache). Every dispatch/accept in the round defers its own state.json
|
|
413
|
+
// write (persistState:false / deferPersist) and mutates that SAME shared object;
|
|
414
|
+
// the round flushes to disk exactly ONCE at the end instead of once per task —
|
|
415
|
+
// was O(N) full-state durable rewrites per round (measured: dominates wall time
|
|
416
|
+
// at scale), now O(1). A crash mid-round loses that round's dispatch/accept
|
|
417
|
+
// bookkeeping (bounded by the round width, i.e. limits.maxConcurrentAgents) and
|
|
418
|
+
// forces a safe-but-wasteful re-dispatch/re-spawn on the next drive — never
|
|
419
|
+
// disk corruption or double-counting, since the atomic-rename+fsync write
|
|
420
|
+
// itself is untouched; only the write FREQUENCY changed.
|
|
421
|
+
return ctx.runner.loadWithCache(() => {
|
|
422
|
+
const run = ctx.runner.loadRun(ctx.runId);
|
|
423
|
+
const selected = selectDriveTask(run);
|
|
424
|
+
const gate = terminalOrConfigStep(ctx, run, selected);
|
|
425
|
+
if (gate)
|
|
426
|
+
return [gate];
|
|
427
|
+
const phase = (0, dispatch_1.firstRunnablePhase)(run);
|
|
428
|
+
const width = Math.max(1, Math.floor(limit) || 1);
|
|
429
|
+
const batch = run.tasks
|
|
430
|
+
.filter((task) => phase.taskIds.includes(task.id) && (task.status === "pending" || task.status === "running"))
|
|
431
|
+
.slice(0, width)
|
|
432
|
+
.map((task) => task.id);
|
|
433
|
+
// Phase A+B: dispatch every batch task (sequential — dispatch mutates state),
|
|
434
|
+
// then collect ALL spawn-style child outcomes in one concurrent window. The
|
|
435
|
+
// token-budget gate ran at round entry; it is NOT re-checked between accepts —
|
|
436
|
+
// the spawns already happened, and refusing to RECORD finished work would
|
|
437
|
+
// discard real results (collect-all + never-claw-back). Overshoot is bounded
|
|
438
|
+
// by the round width; the next round blocks.
|
|
439
|
+
const prepared = prepareConcurrentOutcomes(ctx, batch);
|
|
440
|
+
// Phase C: settle + accept in deterministic batch order, regardless of the
|
|
441
|
+
// wall-clock order the children finished in.
|
|
442
|
+
const steps = [];
|
|
443
|
+
for (const taskId of batch) {
|
|
444
|
+
const failStep = prepared.failSteps.get(taskId);
|
|
445
|
+
if (failStep) {
|
|
446
|
+
steps.push(failStep);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
// Re-read per task: a prior accept in this round mutated state (the SAME
|
|
450
|
+
// cached object — no disk round-trip until the round-end flush below).
|
|
451
|
+
const freshRun = ctx.runner.loadRun(ctx.runId);
|
|
452
|
+
const fresh = freshRun.tasks.find((task) => task.id === taskId);
|
|
453
|
+
if (!fresh || (fresh.status !== "pending" && fresh.status !== "running"))
|
|
454
|
+
continue;
|
|
455
|
+
steps.push(processSelectedTask(ctx, fresh, prepared.outcomes.get(taskId), true));
|
|
433
456
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
457
|
+
if (steps.length > 0) {
|
|
458
|
+
const settledRun = ctx.runner.loadRun(ctx.runId);
|
|
459
|
+
(0, commit_1.commitState)(settledRun, `concurrent-round:${batch.length}-tasks`);
|
|
460
|
+
(0, report_1.writeReport)(settledRun);
|
|
461
|
+
(0, state_1.saveCheckpoint)(settledRun);
|
|
462
|
+
}
|
|
463
|
+
return steps.length > 0 ? steps : [driveStep(ctx)];
|
|
464
|
+
});
|
|
442
465
|
}
|
|
443
466
|
/** Dispatch each batch task and run every spawn-style agent child concurrently
|
|
444
467
|
* (one batch delegate child, per-job timeout kill). Returns outcomes keyed by
|
|
@@ -457,7 +480,7 @@ function prepareConcurrentOutcomes(ctx, batch) {
|
|
|
457
480
|
continue;
|
|
458
481
|
let workerId = task.workerId;
|
|
459
482
|
if (task.status === "pending") {
|
|
460
|
-
const manifest = runner.dispatch(runId, { limit: 1, backend: task.agentType || "agent" });
|
|
483
|
+
const manifest = runner.dispatch(runId, { limit: 1, backend: task.agentType || "agent", persistState: false });
|
|
461
484
|
const dispatchedTask = manifest.tasks.find((entry) => entry.id === task.id) || manifest.tasks[0];
|
|
462
485
|
if (!dispatchedTask || !dispatchedTask.workerId) {
|
|
463
486
|
failSteps.set(taskId, step("dispatch", "failed", { runId, taskId, phase: task.phase, reason: "dispatch produced no worker scope" }));
|
|
@@ -499,7 +522,7 @@ function prepareConcurrentOutcomes(ctx, batch) {
|
|
|
499
522
|
}
|
|
500
523
|
/** A failed agent hop: charge one attempt and (reuse v0.1.37 retryOrPark) either
|
|
501
524
|
* retry on the SAME worker scope next step, or PARK past the retry budget. */
|
|
502
|
-
function handleHop(ctx, task, workerId, reason) {
|
|
525
|
+
function handleHop(ctx, task, workerId, reason, deferPersist = false) {
|
|
503
526
|
const persisted = ctx.runner.showWorker(ctx.runId, workerId).retryCount || 0;
|
|
504
527
|
const prior = Math.max(ctx.attempts.get(task.id) || 0, persisted);
|
|
505
528
|
const entry = {
|
|
@@ -520,7 +543,8 @@ function handleHop(ctx, task, workerId, reason) {
|
|
|
520
543
|
ctx.runner.recordWorkerFailure(ctx.runId, workerId, decided.parkedReason || reason, {
|
|
521
544
|
code: "agent-delegation-parked",
|
|
522
545
|
retryable: false,
|
|
523
|
-
retryCount: attempts
|
|
546
|
+
retryCount: attempts,
|
|
547
|
+
...(deferPersist ? { persistState: false } : {})
|
|
524
548
|
});
|
|
525
549
|
return step("park", "parked", {
|
|
526
550
|
runId: ctx.runId,
|
|
@@ -532,7 +556,7 @@ function handleHop(ctx, task, workerId, reason) {
|
|
|
532
556
|
});
|
|
533
557
|
}
|
|
534
558
|
// Retryable: leave the task running (scope reused) for the next step.
|
|
535
|
-
(0, worker_isolation_1.recordWorkerRetryAttempt)(ctx.runner.loadRun(ctx.runId), workerId, decided.attempts || prior + 1, reason);
|
|
559
|
+
(0, worker_isolation_1.recordWorkerRetryAttempt)(ctx.runner.loadRun(ctx.runId), workerId, decided.attempts || prior + 1, reason, deferPersist ? { persist: false } : {});
|
|
536
560
|
return step("fulfill", "failed", {
|
|
537
561
|
runId: ctx.runId,
|
|
538
562
|
taskId: task.id,
|
|
@@ -565,17 +589,17 @@ function renderSubInputs(spec, parentInputs) {
|
|
|
565
589
|
* own telemetry/audit live in the child run; the parent records ONE honest
|
|
566
590
|
* `worker.sub-workflow` cross-link (child run id + report digest + child audit
|
|
567
591
|
* verdict) — nothing is summed or fabricated. */
|
|
568
|
-
function runSubWorkflow(ctx, run, selected, workerId, manifest) {
|
|
592
|
+
function runSubWorkflow(ctx, run, selected, workerId, manifest, deferPersist = false) {
|
|
569
593
|
const spec = selected.subWorkflow;
|
|
570
594
|
const parentApp = run.workflow.id;
|
|
571
595
|
// Fail-closed BEFORE planning a child (no child state minted when refused).
|
|
572
596
|
if (ctx.depth + 1 > exports.MAX_SUB_WORKFLOW_DEPTH) {
|
|
573
|
-
return handleHop(ctx, selected, workerId, `sub-workflow depth limit exceeded (> ${exports.MAX_SUB_WORKFLOW_DEPTH})
|
|
597
|
+
return handleHop(ctx, selected, workerId, `sub-workflow depth limit exceeded (> ${exports.MAX_SUB_WORKFLOW_DEPTH})`, deferPersist);
|
|
574
598
|
}
|
|
575
599
|
// Include the CURRENT app on the path, so a direct self-cycle (A→A) is caught at
|
|
576
600
|
// depth 0 — before any child run dir is minted.
|
|
577
601
|
if ([...ctx.visitedAppIds, parentApp].includes(spec.appId)) {
|
|
578
|
-
return handleHop(ctx, selected, workerId, `sub-workflow cycle detected: ${[...ctx.visitedAppIds, parentApp, spec.appId].join(" -> ")}
|
|
602
|
+
return handleHop(ctx, selected, workerId, `sub-workflow cycle detected: ${[...ctx.visitedAppIds, parentApp, spec.appId].join(" -> ")}`, deferPersist);
|
|
579
603
|
}
|
|
580
604
|
// Deterministic child run id derived from the parent run + task (no clock/random).
|
|
581
605
|
const childRunId = `sub-${run.id}-${(0, state_1.safeFileName)(selected.id)}`;
|
|
@@ -592,7 +616,7 @@ function runSubWorkflow(ctx, run, selected, workerId, manifest) {
|
|
|
592
616
|
childRun = ctx.runner.plan(spec.appId, childInputs);
|
|
593
617
|
}
|
|
594
618
|
catch (error) {
|
|
595
|
-
return handleHop(ctx, selected, workerId, `sub-workflow plan failed (${spec.appId}): ${errMessage(error)}
|
|
619
|
+
return handleHop(ctx, selected, workerId, `sub-workflow plan failed (${spec.appId}): ${errMessage(error)}`, deferPersist);
|
|
596
620
|
}
|
|
597
621
|
const childResult = drive(ctx.runner, childRun.id, {
|
|
598
622
|
now: ctx.now,
|
|
@@ -603,7 +627,7 @@ function runSubWorkflow(ctx, run, selected, workerId, manifest) {
|
|
|
603
627
|
visitedAppIds: [...ctx.visitedAppIds, parentApp]
|
|
604
628
|
});
|
|
605
629
|
if (childResult.status !== "complete") {
|
|
606
|
-
return handleHop(ctx, selected, workerId, `sub-workflow ${spec.appId} did not complete (status: ${childResult.status})
|
|
630
|
+
return handleHop(ctx, selected, workerId, `sub-workflow ${spec.appId} did not complete (status: ${childResult.status})`, deferPersist);
|
|
607
631
|
}
|
|
608
632
|
// Bind the child's bytes: the rendered report (default) or the verdict result.
|
|
609
633
|
const finalChild = ctx.runner.loadRun(childRun.id);
|
|
@@ -616,15 +640,15 @@ function runSubWorkflow(ctx, run, selected, workerId, manifest) {
|
|
|
616
640
|
childBytes = node_fs_1.default.existsSync(finalChild.paths.report) ? node_fs_1.default.readFileSync(finalChild.paths.report, "utf8") : undefined;
|
|
617
641
|
}
|
|
618
642
|
if (childBytes === undefined) {
|
|
619
|
-
return handleHop(ctx, selected, workerId, `sub-workflow ${spec.appId} produced no ${spec.bindResult || "report"}
|
|
643
|
+
return handleHop(ctx, selected, workerId, `sub-workflow ${spec.appId} produced no ${spec.bindResult || "report"}`, deferPersist);
|
|
620
644
|
}
|
|
621
645
|
// Accept through the SAME path as any other result (verifier/schema/evidence gate).
|
|
622
646
|
try {
|
|
623
647
|
node_fs_1.default.writeFileSync(manifest.resultPath, childBytes, "utf8");
|
|
624
|
-
ctx.runner.recordWorkerOutput(run.id, workerId, manifest.resultPath, {});
|
|
648
|
+
ctx.runner.recordWorkerOutput(run.id, workerId, manifest.resultPath, deferPersist ? { persistState: false } : {});
|
|
625
649
|
}
|
|
626
650
|
catch (error) {
|
|
627
|
-
return handleHop(ctx, selected, workerId, `sub-workflow result rejected by parent gate: ${errMessage(error)}
|
|
651
|
+
return handleHop(ctx, selected, workerId, `sub-workflow result rejected by parent gate: ${errMessage(error)}`, deferPersist);
|
|
628
652
|
}
|
|
629
653
|
// Honest cross-link (provenance only — never fails the accepted hop): one
|
|
630
654
|
// worker.sub-workflow audit event on the parent pins the child run + report digest
|
|
@@ -730,6 +754,7 @@ function drive(runner, runId, options = {}) {
|
|
|
730
754
|
return; // only the first not-yet-complete phase is "active"
|
|
731
755
|
}
|
|
732
756
|
};
|
|
757
|
+
let exhaustedMaxIterations = !options.once;
|
|
733
758
|
for (let i = 0; i < maxIterations; i++) {
|
|
734
759
|
const width = concurrency > 1 ? concurrency : autoWidth(runner.loadRun(runId));
|
|
735
760
|
const roundSteps = width > 1 ? driveConcurrentRound(ctx, width) : [driveStep(ctx)];
|
|
@@ -751,27 +776,39 @@ function drive(runner, runId, options = {}) {
|
|
|
751
776
|
// reuses the run we just advanced; goes to stderr via emitProgress so stdout is clean.
|
|
752
777
|
emitPhaseProgress(runner.loadRun(runId));
|
|
753
778
|
const last = roundSteps[roundSteps.length - 1];
|
|
754
|
-
if (options.once)
|
|
779
|
+
if (options.once) {
|
|
780
|
+
exhaustedMaxIterations = false;
|
|
755
781
|
break;
|
|
756
|
-
|
|
782
|
+
}
|
|
783
|
+
if (last && (last.status === "complete" || last.status === "parked" || last.status === "blocked")) {
|
|
784
|
+
exhaustedMaxIterations = false;
|
|
757
785
|
break;
|
|
786
|
+
}
|
|
758
787
|
}
|
|
759
788
|
const run = runner.loadRun(runId);
|
|
760
789
|
const completedWorkers = countCompleted(run);
|
|
761
790
|
const parkedWorkers = countParked(run);
|
|
762
791
|
const committed = (run.commits || []).find((commit) => commit.reason && commit.reason.startsWith("agent-delegation-drive"));
|
|
763
792
|
const last = steps[steps.length - 1];
|
|
793
|
+
if (exhaustedMaxIterations) {
|
|
794
|
+
steps.push(step("blocked", "blocked", {
|
|
795
|
+
runId,
|
|
796
|
+
reason: `drive reached max iteration limit (${maxIterations}) before a terminal state`
|
|
797
|
+
}));
|
|
798
|
+
}
|
|
764
799
|
const status = options.once
|
|
765
800
|
? completedWorkers === plannedWorkers && committed
|
|
766
801
|
? "complete"
|
|
767
802
|
: last && (last.status === "parked" || last.status === "blocked")
|
|
768
803
|
? last.status
|
|
769
804
|
: "in-progress"
|
|
770
|
-
:
|
|
771
|
-
? "
|
|
772
|
-
: last && last.status === "
|
|
773
|
-
? "
|
|
774
|
-
: "
|
|
805
|
+
: exhaustedMaxIterations
|
|
806
|
+
? "blocked"
|
|
807
|
+
: parkedWorkers > 0 || (last && last.status === "parked")
|
|
808
|
+
? "parked"
|
|
809
|
+
: last && last.status === "blocked"
|
|
810
|
+
? "blocked"
|
|
811
|
+
: "complete";
|
|
775
812
|
return {
|
|
776
813
|
schemaVersion: 1,
|
|
777
814
|
runId,
|