cool-workflow 0.2.2 → 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 +11 -5
- package/dist/cli/entry.js +41 -1
- package/dist/cli/io.js +6 -2
- package/dist/cli/parseargv.js +1 -0
- package/dist/core/capability-data.js +1 -0
- package/dist/core/format/completion.js +68 -0
- package/dist/core/format/help.js +25 -6
- 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 +3 -3
- package/dist/core/multi-agent/coordinator.js +5 -5
- package/dist/core/multi-agent/runtime.js +4 -4
- package/dist/core/multi-agent/topology.js +3 -3
- 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 +13 -13
- package/dist/core/state/state-explosion/graph.js +17 -11
- package/dist/core/state/state-explosion/report.js +6 -6
- 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 +57 -25
- 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 +243 -49
- package/dist/shell/eval-text.js +2 -2
- package/dist/shell/evidence-reasoning.js +4 -4
- 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/feedback-cli.js +6 -6
- package/dist/shell/fs-atomic.js +218 -29
- 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 +80 -38
- package/dist/shell/node-store.js +10 -4
- package/dist/shell/observability.js +1 -1
- package/dist/shell/operator-ux-text.js +22 -22
- package/dist/shell/operator-ux.js +15 -14
- package/dist/shell/orchestrator.js +49 -38
- package/dist/shell/pipeline-cli.js +87 -40
- package/dist/shell/reclamation-io.js +14 -4
- 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 +2 -1
- package/dist/shell/run-registry-io.js +77 -19
- package/dist/shell/run-store.js +74 -2
- package/dist/shell/scheduling-io.js +12 -13
- package/dist/shell/state-cli.js +2 -7
- package/dist/shell/state-explosion-cli.js +17 -9
- package/dist/shell/topology-io.js +36 -5
- package/dist/shell/trust-audit.js +224 -22
- package/dist/shell/trust-policy-io.js +1 -1
- package/dist/shell/worker-cli.js +35 -31
- package/dist/wiring/capability-table/basics.js +33 -8
- package/dist/wiring/capability-table/exec-backend.js +20 -11
- package/dist/wiring/capability-table/multi-agent.js +172 -165
- package/dist/wiring/capability-table/parity.js +1 -0
- package/dist/wiring/capability-table/pipeline.js +54 -42
- package/dist/wiring/capability-table/registry-core.js +29 -10
- package/dist/wiring/capability-table/reporting.js +137 -96
- package/dist/wiring/capability-table/scheduling-registry.js +195 -161
- package/dist/wiring/capability-table/state.js +36 -28
- package/dist/wiring/capability-table/trust-ledger.js +46 -22
- package/dist/wiring/capability-table/workflow-apps.js +83 -68
- package/docs/agent-delegation-drive.7.md +2 -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 +8 -3
- package/docs/contract-migration-tooling.7.md +2 -0
- package/docs/control-plane-scheduling.7.md +2 -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 +2 -0
- package/docs/end-to-end-golden-path.7.md +14 -14
- package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
- package/docs/execution-backends.7.md +2 -0
- package/docs/getting-started.md +37 -37
- package/docs/multi-agent-cli-mcp-surface.7.md +19 -17
- package/docs/multi-agent-eval-replay-harness.7.md +15 -13
- package/docs/multi-agent-operator-ux.7.md +21 -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 +2 -0
- package/docs/observability-cost-accounting.7.md +2 -0
- package/docs/operator-ux.7.md +34 -34
- package/docs/pipeline-runner.7.md +4 -4
- package/docs/project-index.md +27 -4
- package/docs/real-execution-backends.7.md +2 -0
- package/docs/release-and-migration.7.md +3 -1
- package/docs/release-tooling.7.md +2 -0
- package/docs/routines.md +4 -4
- package/docs/run-registry-control-plane.7.md +21 -19
- package/docs/run-retention-reclamation.7.md +2 -0
- package/docs/scheduled-tasks.md +14 -14
- package/docs/security-trust-hardening.7.md +15 -15
- package/docs/state-explosion-management.7.md +12 -10
- package/docs/team-collaboration.7.md +2 -0
- package/docs/trust-audit-anchor.7.md +2 -0
- package/docs/unix-principles.md +3 -1
- package/docs/verifier-gated-commit.7.md +1 -1
- package/docs/web-desktop-workbench.7.md +2 -0
- package/docs/workflow-app-framework.7.md +13 -13
- package/manifest/plugin.manifest.json +1 -1
- package/package.json +2 -2
- package/scripts/agents/claude-p-agent.js +2 -2
- package/scripts/block-unapproved-tag.sh +23 -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 +3 -0
- 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/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
|
@@ -294,27 +294,27 @@ function hostNextActions(run, state, active, requiredHostAction, readyForCommit)
|
|
|
294
294
|
const runId = run.id;
|
|
295
295
|
switch (state) {
|
|
296
296
|
case "needs-run":
|
|
297
|
-
return [{ command: `
|
|
297
|
+
return [{ command: `cw multi-agent run ${runId} --topology map-reduce`, reason: "Materialize a host-facing multi-agent topology.", priority: "high" }];
|
|
298
298
|
case "ready-for-dispatch":
|
|
299
|
-
return [{ command: `
|
|
299
|
+
return [{ command: `cw multi-agent step ${runId}`, reason: "Create the next dispatch manifest without spawning workers.", priority: "high" }];
|
|
300
300
|
case "awaiting-worker-output":
|
|
301
|
-
return [{ command: `
|
|
301
|
+
return [{ command: `cw worker output ${runId} <worker-id> <result.md>`, reason: "A host-executed worker must report result evidence.", priority: "high" }];
|
|
302
302
|
case "ready-for-fanin":
|
|
303
|
-
return [{ command: `
|
|
303
|
+
return [{ command: `cw multi-agent step ${runId}`, reason: "Collect fanin once required worker evidence is present.", priority: "high" }];
|
|
304
304
|
case "ready-for-scoring":
|
|
305
|
-
return [{ command: `
|
|
305
|
+
return [{ command: `cw multi-agent score ${runId} --candidate <candidate-id> --criterion correctness=1 --evidence <path-or-ref>`, reason: "Score a candidate with explicit evidence.", priority: "high" }];
|
|
306
306
|
case "ready-for-selection":
|
|
307
|
-
return [{ command: `
|
|
307
|
+
return [{ command: `cw multi-agent select ${runId} --candidate <candidate-id> --reason "<rationale>"`, reason: "Select a scored candidate after verifier gates pass.", priority: "high" }];
|
|
308
308
|
case "ready-for-commit": {
|
|
309
309
|
const ready = readyForCommit[0];
|
|
310
|
-
return [{ command: `
|
|
310
|
+
return [{ command: `cw commit ${runId} --selection ${ready.selectionId} --reason "<verified rationale>"`, reason: "Create a verifier-gated CW state commit.", priority: "high" }];
|
|
311
311
|
}
|
|
312
312
|
case "complete":
|
|
313
|
-
return [{ command: `
|
|
313
|
+
return [{ command: `cw report ${runId} --show`, reason: "Review the completed run report.", priority: "normal" }];
|
|
314
314
|
case "failed":
|
|
315
315
|
case "blocked":
|
|
316
316
|
default:
|
|
317
|
-
return [{ command: `
|
|
317
|
+
return [{ command: `cw multi-agent status ${runId}`, reason: active.length > 1 ? "Resolve ambiguous active topology state." : "Inspect specific blocked reasons.", priority: "high" }];
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
function envelope(run, command, options = {}) {
|
|
@@ -106,13 +106,13 @@ function summarizeMultiAgentOperator(run) {
|
|
|
106
106
|
multiAgent.nextAction ||
|
|
107
107
|
blackboard.nextAction ||
|
|
108
108
|
readyCommitCommand(run) ||
|
|
109
|
-
`
|
|
109
|
+
`cw multi-agent status ${run.id} --json`;
|
|
110
110
|
return {
|
|
111
111
|
schemaVersion: 1,
|
|
112
112
|
runId: run.id,
|
|
113
|
-
activeMultiAgentRunIds: [...activeMultiAgentRunIds, ...((state.runs || []).filter((entry) => !isTerminal(entry.status)).map((entry) => entry.id))]
|
|
113
|
+
activeMultiAgentRunIds: [...new Set([...activeMultiAgentRunIds, ...((state.runs || []).filter((entry) => !isTerminal(entry.status)).map((entry) => entry.id))])],
|
|
114
114
|
topologyRunIds: [...activeTopologyIds],
|
|
115
|
-
topologyIds: topologies.active.map((entry) => entry.topologyId)
|
|
115
|
+
topologyIds: [...new Set(topologies.active.map((entry) => entry.topologyId))],
|
|
116
116
|
groups: (state.groups || []).map((entry) => entry.id).sort(),
|
|
117
117
|
roles: (state.roles || []).map((entry) => entry.id).sort(),
|
|
118
118
|
memberships: (state.memberships || []).map((entry) => entry.id).sort(),
|
|
@@ -261,7 +261,7 @@ function deriveDependencies(run) {
|
|
|
261
261
|
for (const fanin of state.fanins || []) {
|
|
262
262
|
add(fanin.fanoutId ? `${run.id}:multi-agent:fanout:${fanin.fanoutId}` : `${run.id}:multi-agent:group:${fanin.groupId}`, `${run.id}:multi-agent:fanin:${fanin.id}`, "fanin");
|
|
263
263
|
for (const coverage of fanin.evidenceCoverage || []) {
|
|
264
|
-
add(`${run.id}:multi-agent:membership:${coverage.membershipId}`, `${run.id}:multi-agent:fanin:${fanin.id}`, coverage.complete ? "adopted-by" : "blocks", coverage.complete ? "ready" : "blocked", coverage.complete ? undefined : "membership has not reported required evidence", `
|
|
264
|
+
add(`${run.id}:multi-agent:membership:${coverage.membershipId}`, `${run.id}:multi-agent:fanin:${fanin.id}`, coverage.complete ? "adopted-by" : "blocks", coverage.complete ? "ready" : "blocked", coverage.complete ? undefined : "membership has not reported required evidence", `cw worker manifest ${run.id} ${coverage.workerId || "<worker-id>"}`);
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
for (const candidate of candidatesOf(run)) {
|
|
@@ -276,7 +276,7 @@ function deriveDependencies(run) {
|
|
|
276
276
|
for (const commit of commitsOf(run)) {
|
|
277
277
|
add(commit.selectionId ? `${run.id}:selection:${commit.selectionId}` : undefined, String(commit.stateNodeId || `${run.id}:commit:${commit.id}`), "commits", commit.verifierGated ? "committed" : "checkpoint");
|
|
278
278
|
}
|
|
279
|
-
return rows
|
|
279
|
+
return uniqueById(rows).sort((left, right) => (0, collate_1.stableCompare)(left.from, right.from) || (0, collate_1.stableCompare)(left.to, right.to));
|
|
280
280
|
}
|
|
281
281
|
function deriveFailures(run, dependencies) {
|
|
282
282
|
const rows = [];
|
|
@@ -284,61 +284,74 @@ function deriveFailures(run, dependencies) {
|
|
|
284
284
|
rows.push({ id, kind, status, owner, linked, reason, nextCommand });
|
|
285
285
|
};
|
|
286
286
|
const state = maOf(run);
|
|
287
|
+
// Grouped/indexed once instead of re-filtering/re-scanning the whole
|
|
288
|
+
// memberships/workers array per role/membership below (O(roles x
|
|
289
|
+
// memberships) and O(memberships x workers) otherwise -- the same
|
|
290
|
+
// array-scan-per-item shape 024b007 fixed for phase/task selection).
|
|
291
|
+
const membershipsByRole = new Map();
|
|
292
|
+
for (const entry of state.memberships || []) {
|
|
293
|
+
const list = membershipsByRole.get(entry.roleId);
|
|
294
|
+
if (list)
|
|
295
|
+
list.push(entry);
|
|
296
|
+
else
|
|
297
|
+
membershipsByRole.set(entry.roleId, [entry]);
|
|
298
|
+
}
|
|
299
|
+
const workersById = new Map(workersOf(run).map((entry) => [entry.id, entry]));
|
|
287
300
|
for (const role of state.roles || []) {
|
|
288
|
-
const memberships = (
|
|
301
|
+
const memberships = membershipsByRole.get(role.id) || [];
|
|
289
302
|
if (!memberships.length && role.status !== "completed" && role.status !== "cancelled") {
|
|
290
|
-
add(role.id, "missing-role-coverage", role.status, `role ${role.id} has no membership`, `
|
|
303
|
+
add(role.id, "missing-role-coverage", role.status, `role ${role.id} has no membership`, `cw multi-agent step ${run.id}`, role.id);
|
|
291
304
|
}
|
|
292
305
|
if (role.status === "blocked" || role.status === "cancelled")
|
|
293
|
-
add(role.id, "agent-role", role.status, `role ${role.id} is ${role.status}`, `
|
|
306
|
+
add(role.id, "agent-role", role.status, `role ${role.id} is ${role.status}`, `cw multi-agent status ${run.id} --json`, role.id);
|
|
294
307
|
}
|
|
295
308
|
for (const membership of state.memberships || []) {
|
|
296
|
-
const worker = membership.workerId ?
|
|
309
|
+
const worker = membership.workerId ? workersById.get(membership.workerId) : undefined;
|
|
297
310
|
if (membership.status === "failed" || membership.status === "cancelled")
|
|
298
|
-
add(membership.id, "agent-membership", membership.status, `membership ${membership.id} is ${membership.status}`, `
|
|
311
|
+
add(membership.id, "agent-membership", membership.status, `membership ${membership.id} is ${membership.status}`, `cw multi-agent membership ${run.id} ${membership.id}`, membership.roleId, membership.workerId);
|
|
299
312
|
if (!membership.workerId)
|
|
300
|
-
add(membership.id, "missing-worker", membership.status, `membership ${membership.id} has no worker`, `
|
|
313
|
+
add(membership.id, "missing-worker", membership.status, `membership ${membership.id} has no worker`, `cw multi-agent step ${run.id}`, membership.roleId, membership.taskId);
|
|
301
314
|
if (worker && (worker.status === "failed" || worker.status === "rejected"))
|
|
302
|
-
add(String(worker.id), "worker", String(worker.status), worker.errors?.[0]?.message || `worker ${worker.id} is ${worker.status}`, `
|
|
315
|
+
add(String(worker.id), "worker", String(worker.status), worker.errors?.[0]?.message || `worker ${worker.id} is ${worker.status}`, `cw worker show ${run.id} ${worker.id}`, membership.roleId, membership.id);
|
|
303
316
|
if (worker && (worker.status === "allocated" || worker.status === "running"))
|
|
304
|
-
add(String(worker.id), "worker-output", String(worker.status), `worker ${worker.id} has not reported output`, `
|
|
317
|
+
add(String(worker.id), "worker-output", String(worker.status), `worker ${worker.id} has not reported output`, `cw worker manifest ${run.id} ${worker.id}`, membership.roleId, membership.id);
|
|
305
318
|
}
|
|
306
319
|
for (const fanin of state.fanins || []) {
|
|
307
320
|
for (const reason of fanin.blockedReasons || [])
|
|
308
|
-
add(fanin.id, "fanin", fanin.status, reason, `
|
|
321
|
+
add(fanin.id, "fanin", fanin.status, reason, `cw multi-agent failures ${run.id}`, fanin.groupId, fanin.fanoutId);
|
|
309
322
|
for (const roleId of fanin.missingRoleIds || [])
|
|
310
|
-
add(`${fanin.id}:${roleId}`, "missing-role-evidence", "missing", `fanin ${fanin.id} is missing role ${roleId}`, `
|
|
323
|
+
add(`${fanin.id}:${roleId}`, "missing-role-evidence", "missing", `fanin ${fanin.id} is missing role ${roleId}`, `cw multi-agent step ${run.id}`, roleId, fanin.id);
|
|
311
324
|
for (const membershipId of fanin.missingMembershipIds || [])
|
|
312
|
-
add(`${fanin.id}:${membershipId}`, "missing-membership-evidence", "missing", `fanin ${fanin.id} is missing membership ${membershipId}`, `
|
|
325
|
+
add(`${fanin.id}:${membershipId}`, "missing-membership-evidence", "missing", `fanin ${fanin.id} is missing membership ${membershipId}`, `cw multi-agent membership ${run.id} ${membershipId}`, membershipId, fanin.id);
|
|
313
326
|
}
|
|
314
327
|
for (const topology of topologyRunsOf(run)) {
|
|
315
328
|
for (const missing of topology.missingEvidence || [])
|
|
316
|
-
add(`${topology.id}:${missing}`, "missing-topology-evidence", "missing", missing, topology.nextActions?.[0] || `
|
|
329
|
+
add(`${topology.id}:${missing}`, "missing-topology-evidence", "missing", missing, topology.nextActions?.[0] || `cw topology summary ${run.id}`, String(topology.id));
|
|
317
330
|
if (topology.status === "blocked" || topology.status === "failed")
|
|
318
|
-
add(String(topology.id), "topology", String(topology.status), `topology ${topology.id} is ${topology.status}`, `
|
|
331
|
+
add(String(topology.id), "topology", String(topology.status), `topology ${topology.id} is ${topology.status}`, `cw topology summary ${run.id}`, String(topology.id));
|
|
319
332
|
}
|
|
320
333
|
for (const feedback of feedbackOf(run)) {
|
|
321
334
|
if (feedback.status === "open" || feedback.status === "tasked")
|
|
322
|
-
add(String(feedback.id), String(feedback.classification), String(feedback.status), String(feedback.message), `
|
|
335
|
+
add(String(feedback.id), String(feedback.classification), String(feedback.status), String(feedback.message), `cw feedback show ${run.id} ${feedback.id}`, feedback.taskId, feedback.nodeId);
|
|
323
336
|
}
|
|
324
337
|
for (const candidate of candidatesOf(run)) {
|
|
325
338
|
const scores = candidate.scores || [];
|
|
326
339
|
if (candidate.status === "rejected" || candidate.status === "failed")
|
|
327
|
-
add(String(candidate.id), "candidate", String(candidate.status), candidate.feedbackIds?.[0] || `candidate ${candidate.id} is ${candidate.status}`, `
|
|
340
|
+
add(String(candidate.id), "candidate", String(candidate.status), candidate.feedbackIds?.[0] || `candidate ${candidate.id} is ${candidate.status}`, `cw candidate show ${run.id} ${candidate.id}`, candidate.workerId, candidate.taskId);
|
|
328
341
|
if (!scores.length && candidate.status !== "rejected" && candidate.status !== "failed")
|
|
329
|
-
add(String(candidate.id), "candidate-score-gap", String(candidate.status), `candidate ${candidate.id} has no score`, `
|
|
342
|
+
add(String(candidate.id), "candidate-score-gap", String(candidate.status), `candidate ${candidate.id} has no score`, `cw multi-agent score ${run.id} --candidate ${candidate.id} --evidence <path-or-ref>`, candidate.workerId, candidate.taskId);
|
|
330
343
|
if (!candidate.verifierNodeId)
|
|
331
|
-
add(`${candidate.id}:verifier`, "candidate-verifier-gap", String(candidate.status), `candidate ${candidate.id} has no verifier gate`, `
|
|
344
|
+
add(`${candidate.id}:verifier`, "candidate-verifier-gap", String(candidate.status), `candidate ${candidate.id} has no verifier gate`, `cw candidate show ${run.id} ${candidate.id}`, candidate.workerId, candidate.taskId);
|
|
332
345
|
}
|
|
333
346
|
if (candidatesOf(run).some((candidate) => (candidate.scores || []).length) && !selectionsOf(run).length) {
|
|
334
|
-
add("selection-gap", "selection", "missing", "scored candidates exist but no selection is recorded", `
|
|
347
|
+
add("selection-gap", "selection", "missing", "scored candidates exist but no selection is recorded", `cw multi-agent select ${run.id} --candidate <candidate-id> --reason "<rationale>"`);
|
|
335
348
|
}
|
|
336
349
|
for (const dep of dependencies.filter((entry) => entry.status === "blocked"))
|
|
337
|
-
add(dep.id, "ambiguous-dependency", dep.status, dep.reason || "dependency is blocked", dep.nextCommand || `
|
|
338
|
-
const readySelection =
|
|
350
|
+
add(dep.id, "ambiguous-dependency", dep.status, dep.reason || "dependency is blocked", dep.nextCommand || `cw multi-agent status ${run.id} --json`);
|
|
351
|
+
const readySelection = firstUngatedSelection(run);
|
|
339
352
|
if (readySelection)
|
|
340
|
-
add(String(readySelection.id), "commit-gate", "not-ready", `selection ${readySelection.id} has no verifier-gated commit`, `
|
|
341
|
-
return rows
|
|
353
|
+
add(String(readySelection.id), "commit-gate", "not-ready", `selection ${readySelection.id} has no verifier-gated commit`, `cw commit ${run.id} --selection ${readySelection.id} --reason "<verified rationale>"`, readySelection.candidateId);
|
|
354
|
+
return uniqueByFailure(rows).sort((left, right) => (0, collate_1.stableCompare)(left.kind, right.kind) || (0, collate_1.stableCompare)(left.id, right.id));
|
|
342
355
|
}
|
|
343
356
|
function deriveEvidence(run) {
|
|
344
357
|
const rows = new Map();
|
|
@@ -493,9 +506,18 @@ function readScores(run, candidateId) {
|
|
|
493
506
|
.sort()
|
|
494
507
|
.map((file) => JSON.parse(fs.readFileSync(path.join(dir, file), "utf8")));
|
|
495
508
|
}
|
|
509
|
+
/** The first selection with no verifier-gated commit yet, if any -- shared
|
|
510
|
+
* by deriveFailures and readyCommitCommand. A Set of already-gated
|
|
511
|
+
* selection ids, built once, replaces re-scanning ALL commits per
|
|
512
|
+
* selection (O(selections x commits) otherwise -- the same array-scan-
|
|
513
|
+
* per-item shape 024b007 fixed for phase/task selection). */
|
|
514
|
+
function firstUngatedSelection(run) {
|
|
515
|
+
const gatedSelectionIds = new Set(commitsOf(run).filter((commit) => commit.verifierGated).map((commit) => commit.selectionId));
|
|
516
|
+
return selectionsOf(run).find((entry) => !gatedSelectionIds.has(entry.id));
|
|
517
|
+
}
|
|
496
518
|
function readyCommitCommand(run) {
|
|
497
|
-
const selection =
|
|
498
|
-
return selection ? `
|
|
519
|
+
const selection = firstUngatedSelection(run);
|
|
520
|
+
return selection ? `cw commit ${run.id} --selection ${selection.id} --reason "<verified rationale>"` : undefined;
|
|
499
521
|
}
|
|
500
522
|
function normalizeEvidenceStatus(row) {
|
|
501
523
|
if (row.rejectedBy.length)
|
|
@@ -559,16 +581,36 @@ function isTerminal(status) {
|
|
|
559
581
|
return status === "completed" || status === "failed" || status === "cancelled";
|
|
560
582
|
}
|
|
561
583
|
function unique(values) {
|
|
562
|
-
return values.filter(Boolean)
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
584
|
+
return [...new Set(values.filter(Boolean))].sort();
|
|
585
|
+
}
|
|
586
|
+
// Each dedup below keeps the FIRST occurrence of a duplicate key, same as
|
|
587
|
+
// the `values.findIndex(...) === index` shape it replaces -- a Set of seen
|
|
588
|
+
// keys does this in one O(N) pass instead of an O(N) findIndex per item
|
|
589
|
+
// (O(N^2) total; this was the dominant cost of deriveDependencies/
|
|
590
|
+
// deriveFailures at large membership counts, found while pinning perf
|
|
591
|
+
// cycle P1-1's review-fix regression test).
|
|
592
|
+
function uniqueById(values) {
|
|
593
|
+
const seen = new Set();
|
|
594
|
+
const result = [];
|
|
595
|
+
for (const value of values) {
|
|
596
|
+
if (seen.has(value.id))
|
|
597
|
+
continue;
|
|
598
|
+
seen.add(value.id);
|
|
599
|
+
result.push(value);
|
|
600
|
+
}
|
|
601
|
+
return result;
|
|
569
602
|
}
|
|
570
|
-
function uniqueByFailure(
|
|
571
|
-
|
|
603
|
+
function uniqueByFailure(values) {
|
|
604
|
+
const seen = new Set();
|
|
605
|
+
const result = [];
|
|
606
|
+
for (const value of values) {
|
|
607
|
+
const key = `${value.id}\0${value.kind}`;
|
|
608
|
+
if (seen.has(key))
|
|
609
|
+
continue;
|
|
610
|
+
seen.add(key);
|
|
611
|
+
result.push(value);
|
|
612
|
+
}
|
|
613
|
+
return result;
|
|
572
614
|
}
|
|
573
615
|
function uniqueEdges(edges) {
|
|
574
616
|
const seen = new Set();
|
package/dist/shell/node-store.js
CHANGED
|
@@ -94,12 +94,16 @@ function replayNodeSnapshot(run, snapshot, options = {}) {
|
|
|
94
94
|
: (replay) => (0, fs_atomic_1.writeJson)(path.join(snapshotDir(run, snapshot.nodeId), "replays", `${replay.replayId}.json`), replay),
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
-
/** Scans every dir under `nodes/snapshots/` for `<snapshotId>.json`.
|
|
97
|
+
/** Scans every dir under `nodes/snapshots/` for `<snapshotId>.json`. Runs
|
|
98
|
+
* `snapshotId` through `safeFileName` first (matching every write-side path
|
|
99
|
+
* segment in this file) so a traversal-shaped id can never escape `nodeDir`
|
|
100
|
+
* into an arbitrary file elsewhere on disk. */
|
|
98
101
|
function readNodeSnapshot(run, snapshotId) {
|
|
99
102
|
const root = snapshotsRoot(run);
|
|
103
|
+
const safeId = (0, fs_atomic_1.safeFileName)(snapshotId);
|
|
100
104
|
if (fs.existsSync(root)) {
|
|
101
105
|
for (const nodeDir of fs.readdirSync(root)) {
|
|
102
|
-
const file = path.join(root, nodeDir, `${
|
|
106
|
+
const file = path.join(root, nodeDir, `${safeId}.json`);
|
|
103
107
|
if (fs.existsSync(file))
|
|
104
108
|
return (0, validation_1.validateNodeSnapshot)(JSON.parse(fs.readFileSync(file, "utf8")));
|
|
105
109
|
}
|
|
@@ -108,12 +112,14 @@ function readNodeSnapshot(run, snapshotId) {
|
|
|
108
112
|
freshness: "absent",
|
|
109
113
|
});
|
|
110
114
|
}
|
|
111
|
-
/** Scans every dir under `nodes/snapshots/<nodeDir>/replays/` for
|
|
115
|
+
/** Scans every dir under `nodes/snapshots/<nodeDir>/replays/` for
|
|
116
|
+
* `<replayId>.json`. Same `safeFileName` guard as `readNodeSnapshot`. */
|
|
112
117
|
function readNodeReplay(run, replayId) {
|
|
113
118
|
const root = snapshotsRoot(run);
|
|
119
|
+
const safeId = (0, fs_atomic_1.safeFileName)(replayId);
|
|
114
120
|
if (fs.existsSync(root)) {
|
|
115
121
|
for (const nodeDir of fs.readdirSync(root)) {
|
|
116
|
-
const file = path.join(root, nodeDir, "replays", `${
|
|
122
|
+
const file = path.join(root, nodeDir, "replays", `${safeId}.json`);
|
|
117
123
|
if (fs.existsSync(file))
|
|
118
124
|
return (0, validation_1.validateNodeReplayRun)(JSON.parse(fs.readFileSync(file, "utf8")));
|
|
119
125
|
}
|
|
@@ -431,7 +431,7 @@ function deriveMetricsReport(run, options) {
|
|
|
431
431
|
collaboration: deriveCollaborationMetrics(run),
|
|
432
432
|
nextAction: totals.unreportedUnits > 0 && totals.attestedUnits === 0
|
|
433
433
|
? "No attested usage yet — record host usage on result/worker intake (cw result ... --usage-input-tokens N --usage-output-tokens M --usage-model ID)."
|
|
434
|
-
: `
|
|
434
|
+
: `cw metrics show ${run.id} --json`,
|
|
435
435
|
};
|
|
436
436
|
}
|
|
437
437
|
function metricsDir(run) {
|
|
@@ -152,7 +152,7 @@ function formatMultiAgentTrustAudit(view) {
|
|
|
152
152
|
...formatAuditEventRows(arrayView(view.policyViolations)),
|
|
153
153
|
"",
|
|
154
154
|
"Next Action",
|
|
155
|
-
` ${String(view.nextAction || `
|
|
155
|
+
` ${String(view.nextAction || `cw audit multi-agent ${String(view.runId || "<run-id>")} --json`)}`,
|
|
156
156
|
].join("\n");
|
|
157
157
|
}
|
|
158
158
|
/** `Topologies` panel — port of the old build's formatTopologyPanel
|
|
@@ -299,27 +299,27 @@ function formatOperatorReport(summary, evidencePaths) {
|
|
|
299
299
|
(0, multi_agent_operator_ux_1.formatMultiAgentEvidence)(summary.multiAgentOperator.evidence),
|
|
300
300
|
"",
|
|
301
301
|
"Resource Commands",
|
|
302
|
-
`
|
|
303
|
-
`
|
|
304
|
-
`
|
|
305
|
-
`
|
|
306
|
-
`
|
|
307
|
-
`
|
|
308
|
-
`
|
|
309
|
-
`
|
|
310
|
-
`
|
|
311
|
-
`
|
|
312
|
-
`
|
|
313
|
-
`
|
|
314
|
-
`
|
|
315
|
-
`
|
|
316
|
-
`
|
|
317
|
-
`
|
|
318
|
-
`
|
|
319
|
-
`
|
|
320
|
-
`
|
|
321
|
-
`
|
|
322
|
-
`
|
|
302
|
+
` cw graph ${summary.runId}`,
|
|
303
|
+
` cw worker summary ${summary.runId}`,
|
|
304
|
+
` cw topology summary ${summary.runId}`,
|
|
305
|
+
` cw topology graph ${summary.runId}`,
|
|
306
|
+
` cw multi-agent summary ${summary.runId}`,
|
|
307
|
+
` cw multi-agent graph ${summary.runId}`,
|
|
308
|
+
` cw multi-agent dependencies ${summary.runId}`,
|
|
309
|
+
` cw multi-agent failures ${summary.runId}`,
|
|
310
|
+
` cw multi-agent evidence ${summary.runId}`,
|
|
311
|
+
` cw blackboard summary ${summary.runId}`,
|
|
312
|
+
` cw blackboard graph ${summary.runId}`,
|
|
313
|
+
` cw coordinator summary ${summary.runId}`,
|
|
314
|
+
` cw candidate summary ${summary.runId}`,
|
|
315
|
+
` cw feedback summary ${summary.runId}`,
|
|
316
|
+
` cw commit summary ${summary.runId}`,
|
|
317
|
+
` cw audit summary ${summary.runId}`,
|
|
318
|
+
` cw audit provenance ${summary.runId}`,
|
|
319
|
+
` cw audit multi-agent ${summary.runId}`,
|
|
320
|
+
` cw audit policy ${summary.runId}`,
|
|
321
|
+
` cw audit blackboard ${summary.runId}`,
|
|
322
|
+
` cw audit judge ${summary.runId}`,
|
|
323
323
|
].join("\n");
|
|
324
324
|
}
|
|
325
325
|
function groupBy(values, key) {
|
|
@@ -115,7 +115,7 @@ function summarizeRun(run) {
|
|
|
115
115
|
function adviseNoRun() {
|
|
116
116
|
return [
|
|
117
117
|
{
|
|
118
|
-
command: "
|
|
118
|
+
command: "cw plan <workflow-id> --repo <path>",
|
|
119
119
|
reason: "No run id is available yet; create a workflow run before dispatching or recording evidence.",
|
|
120
120
|
priority: "high",
|
|
121
121
|
},
|
|
@@ -123,7 +123,8 @@ function adviseNoRun() {
|
|
|
123
123
|
}
|
|
124
124
|
function summarizePhases(run) {
|
|
125
125
|
return run.phases.map((phase) => {
|
|
126
|
-
const
|
|
126
|
+
const taskIds = new Set(phase.taskIds);
|
|
127
|
+
const phaseTasks = run.tasks.filter((t) => taskIds.has(t.id));
|
|
127
128
|
const byStatus = countBy(phaseTasks, (t) => t.status);
|
|
128
129
|
return { id: phase.id, name: phase.name, status: phase.status, tasks: { total: phaseTasks.length, ...byStatus } };
|
|
129
130
|
});
|
|
@@ -225,52 +226,52 @@ function adviseNextSteps(run, ctx) {
|
|
|
225
226
|
const openFeedback = feedbackRecords.filter((record) => record.status === "open");
|
|
226
227
|
if (openFeedback.length) {
|
|
227
228
|
const feedback = openFeedback[0];
|
|
228
|
-
actions.push({ command: `
|
|
229
|
-
actions.push({ command: `
|
|
229
|
+
actions.push({ command: `cw feedback show ${run.id} ${feedback.id}`, reason: `Open ${feedback.severity} ${feedback.classification} feedback should be inspected before more dispatch.`, priority: "high" });
|
|
230
|
+
actions.push({ command: `cw feedback task ${run.id} ${feedback.id}`, reason: "Create a correction task if the feedback is actionable.", priority: "normal" });
|
|
230
231
|
return actions;
|
|
231
232
|
}
|
|
232
233
|
const failedWorker = ctx.workers.workers.find((w) => w.status === "failed" || w.status === "rejected");
|
|
233
234
|
if (failedWorker) {
|
|
234
|
-
actions.push({ command: `
|
|
235
|
+
actions.push({ command: `cw feedback list ${run.id}`, reason: `Worker ${failedWorker.id} is ${failedWorker.status}; inspect linked feedback before retrying.`, priority: "high" });
|
|
235
236
|
return actions;
|
|
236
237
|
}
|
|
237
238
|
if (ctx.tasks.running.length) {
|
|
238
239
|
const worker = ctx.workers.workers.find((w) => w.status === "running" || w.status === "allocated");
|
|
239
|
-
actions.push({ command: worker ? `
|
|
240
|
+
actions.push({ command: worker ? `cw worker manifest ${run.id} ${worker.id}` : `cw worker list ${run.id}`, reason: "Running workers need their manifests inspected and final output recorded.", priority: "high" });
|
|
240
241
|
if (worker && worker.resultPath) {
|
|
241
|
-
actions.push({ command: `
|
|
242
|
+
actions.push({ command: `cw worker output ${run.id} ${worker.id} ${worker.resultPath}`, reason: "Record the worker result after its result.md is ready.", priority: "normal" });
|
|
242
243
|
}
|
|
243
244
|
return actions;
|
|
244
245
|
}
|
|
245
246
|
if (ctx.tasks.pending.length) {
|
|
246
247
|
const limit = Math.min(ctx.tasks.pending.length, run.workflow.limits?.maxConcurrentAgents || 4);
|
|
247
|
-
actions.push({ command: `
|
|
248
|
+
actions.push({ command: `cw dispatch ${run.id} --limit ${limit}`, reason: `${ctx.tasks.pending.length} pending task(s) are ready for the active phase.`, priority: "high" });
|
|
248
249
|
return actions;
|
|
249
250
|
}
|
|
250
251
|
const candidateRecords = (run.candidates || []);
|
|
251
252
|
const selectionRecords = (run.candidateSelections || []);
|
|
252
253
|
const completedWithoutCandidate = (run.tasks || []).find((task) => task.status === "completed" && task.workerId && !candidateRecords.some((candidate) => candidate.workerId === task.workerId));
|
|
253
254
|
if (completedWithoutCandidate?.workerId) {
|
|
254
|
-
actions.push({ command: `
|
|
255
|
+
actions.push({ command: `cw candidate register ${run.id} --worker ${completedWithoutCandidate.workerId}`, reason: "A completed worker result is available but has not been registered as a candidate.", priority: "high" });
|
|
255
256
|
return actions;
|
|
256
257
|
}
|
|
257
258
|
const unscored = candidateRecords.find((candidate) => candidate.status === "registered" && !(candidate.scores || []).length);
|
|
258
259
|
if (unscored) {
|
|
259
|
-
actions.push({ command: `
|
|
260
|
+
actions.push({ command: `cw candidate score ${run.id} ${unscored.id} --criterion correctness=1 --evidence <path-or-locator>`, reason: "Registered candidates need score evidence before ranking or selection.", priority: "high" });
|
|
260
261
|
return actions;
|
|
261
262
|
}
|
|
262
263
|
const scoredWithoutSelection = candidateRecords.find((candidate) => candidate.status === "scored" && !selectionRecords.some((selection) => selection.candidateId === candidate.id));
|
|
263
264
|
if (scoredWithoutSelection) {
|
|
264
|
-
actions.push({ command: `
|
|
265
|
-
actions.push({ command: `
|
|
265
|
+
actions.push({ command: `cw candidate rank ${run.id}`, reason: "Scored candidates can be ranked before selection.", priority: "high" });
|
|
266
|
+
actions.push({ command: `cw candidate select ${run.id} ${scoredWithoutSelection.id}`, reason: "Select the candidate once the ranking supports it.", priority: "normal" });
|
|
266
267
|
return actions;
|
|
267
268
|
}
|
|
268
269
|
if (ctx.candidates.readyForCommit.length) {
|
|
269
270
|
const ready = ctx.candidates.readyForCommit[0];
|
|
270
|
-
actions.push({ command: `
|
|
271
|
+
actions.push({ command: `cw commit ${run.id} --selection ${ready.selectionId}`, reason: "A verified selected candidate is ready for a verifier-gated commit.", priority: "high" });
|
|
271
272
|
return actions;
|
|
272
273
|
}
|
|
273
|
-
actions.push({ command: `
|
|
274
|
+
actions.push({ command: `cw report ${run.id} --show`, reason: "All tracked phases are complete or no further operator action is currently available.", priority: "normal" });
|
|
274
275
|
return actions;
|
|
275
276
|
}
|
|
276
277
|
/** `cw operator status <id> --json` — a wider payload than `summarizeRun`
|
|
@@ -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) {
|