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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// core/format/completion.ts — formatCompletionScript.
|
|
3
|
+
//
|
|
4
|
+
// Pure function of already-loaded data (MORE_COMMANDS_TOKENS from
|
|
5
|
+
// help.ts). No new command list to hand-maintain: the completion word
|
|
6
|
+
// list is the SAME token set formatHelp()'s "More commands" section
|
|
7
|
+
// displays, plus the two top-level verbs that list doesn't carry
|
|
8
|
+
// ("help" and "doctor" — see help.ts's formatHelp comment on why "help"
|
|
9
|
+
// is absent from both places). Deliberately shallow: completes only
|
|
10
|
+
// TOP-LEVEL command names, not subcommands or flags — a v1 scope that
|
|
11
|
+
// still covers the single most common completion need (typing the verb)
|
|
12
|
+
// without hand-maintaining a second, deeper copy of the capability
|
|
13
|
+
// table's subcommand shape.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.completionWords = completionWords;
|
|
16
|
+
exports.formatCompletionScript = formatCompletionScript;
|
|
17
|
+
const help_1 = require("./help");
|
|
18
|
+
/** The full top-level completion word list: `help`/`doctor` (absent from
|
|
19
|
+
* MORE_COMMANDS_TOKENS — see help.ts) plus every token formatHelp's
|
|
20
|
+
* "More commands" section lists (already includes `version`/`fix`). */
|
|
21
|
+
function completionWords() {
|
|
22
|
+
return ["help", "doctor", ...help_1.MORE_COMMANDS_TOKENS];
|
|
23
|
+
}
|
|
24
|
+
function bashScript(words) {
|
|
25
|
+
return [
|
|
26
|
+
"# cw bash completion — generated by `cw completion bash`.",
|
|
27
|
+
"# Install: eval \"$(cw completion bash)\" (e.g. in ~/.bashrc)",
|
|
28
|
+
"_cw_complete() {",
|
|
29
|
+
' local cur="${COMP_WORDS[COMP_CWORD]}"',
|
|
30
|
+
" if [ \"$COMP_CWORD\" -eq 1 ]; then",
|
|
31
|
+
` COMPREPLY=( $(compgen -W "${words.join(" ")}" -- "$cur") )`,
|
|
32
|
+
" fi",
|
|
33
|
+
"}",
|
|
34
|
+
"complete -F _cw_complete cw",
|
|
35
|
+
"",
|
|
36
|
+
].join("\n");
|
|
37
|
+
}
|
|
38
|
+
function zshScript(words) {
|
|
39
|
+
return [
|
|
40
|
+
"#compdef cw",
|
|
41
|
+
"# cw zsh completion — generated by `cw completion zsh`.",
|
|
42
|
+
"# Install: cw completion zsh > \"${fpath[1]}/_cw\" (then restart the shell)",
|
|
43
|
+
`_arguments '1: :(${words.join(" ")})'`,
|
|
44
|
+
"",
|
|
45
|
+
].join("\n");
|
|
46
|
+
}
|
|
47
|
+
function fishScript(words) {
|
|
48
|
+
return [
|
|
49
|
+
"# cw fish completion — generated by `cw completion fish`.",
|
|
50
|
+
"# Install: cw completion fish > ~/.config/fish/completions/cw.fish",
|
|
51
|
+
`complete -c cw -n "__fish_use_subcommand" -a "${words.join(" ")}"`,
|
|
52
|
+
"",
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
/** `cw completion <bash|zsh|fish>` — a static completion script for the
|
|
56
|
+
* top-level command word list. Throws on an unrecognized shell name
|
|
57
|
+
* (the CLI layer turns that into the standard `cw: <message>` /
|
|
58
|
+
* `Try:` shape, same as every other input-validation error). */
|
|
59
|
+
function formatCompletionScript(shell) {
|
|
60
|
+
const words = completionWords();
|
|
61
|
+
if (shell === "bash")
|
|
62
|
+
return bashScript(words);
|
|
63
|
+
if (shell === "zsh")
|
|
64
|
+
return zshScript(words);
|
|
65
|
+
if (shell === "fish")
|
|
66
|
+
return fishScript(words);
|
|
67
|
+
throw new Error(`Unknown shell: ${shell}\n Try: cw completion bash|zsh|fish`);
|
|
68
|
+
}
|
package/dist/core/format/help.js
CHANGED
|
@@ -25,13 +25,22 @@
|
|
|
25
25
|
// `tools/list` round-trip read the SAME table rows for any capability
|
|
26
26
|
// wired into both.
|
|
27
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.MORE_COMMANDS_TOKENS = void 0;
|
|
28
29
|
exports.formatHelp = formatHelp;
|
|
29
30
|
exports.formatCommandHelp = formatCommandHelp;
|
|
30
31
|
exports.formatInfo = formatInfo;
|
|
32
|
+
exports.formatSearchResults = formatSearchResults;
|
|
31
33
|
const capability_table_1 = require("../capability-table");
|
|
32
|
-
/** src/orchestrator.ts:934-951 — the
|
|
33
|
-
*
|
|
34
|
-
|
|
34
|
+
/** src/orchestrator.ts:934-951 — the "More commands" token set, in the old
|
|
35
|
+
* build's order (space-joined in the source, pipe-joined for display).
|
|
36
|
+
* One change from the old capture: `update` is gone. The verb had no code
|
|
37
|
+
* behind it in this build (`cw update` said "Unknown command"), so the
|
|
38
|
+
* help must not offer it. See parseargv.ts KNOWN_COMMANDS. Exported (only
|
|
39
|
+
* as a read-only source list, `formatHelp` below still owns the byte-
|
|
40
|
+
* pinned display format) so `core/format/completion.ts` can build a
|
|
41
|
+
* shell-completion word list from the SAME data instead of a third
|
|
42
|
+
* hand-maintained copy. */
|
|
43
|
+
exports.MORE_COMMANDS_TOKENS = [
|
|
35
44
|
"list", "search", "info", "init", "plan", "status", "next", "dispatch",
|
|
36
45
|
"result", "state", "commit", "report", "app", "sandbox", "backend",
|
|
37
46
|
"contract", "node", "feedback", "worker", "audit", "candidate", "review",
|
|
@@ -40,7 +49,7 @@ const MORE_COMMANDS_TOKENS = [
|
|
|
40
49
|
"summary", "blackboard", "coordinator", "metrics", "operator", "sched",
|
|
41
50
|
"gc", "telemetry", "migration", "demo", "workbench", "approve", "reject",
|
|
42
51
|
"comment", "handoff", "ledger", "graph", "eval", "man", "version",
|
|
43
|
-
"
|
|
52
|
+
"fix", "completion",
|
|
44
53
|
];
|
|
45
54
|
const MORE_COMMANDS_WRAP_WIDTH = 76;
|
|
46
55
|
/** src/orchestrator.ts:940-951 — greedily pack pipe-joined tokens into
|
|
@@ -124,7 +133,14 @@ const COMMAND_HELP_ROWS = {
|
|
|
124
133
|
{ command: "cw sched complete", summary: "Complete a held lease (terminal success)." },
|
|
125
134
|
{ command: "cw sched reclaim", summary: "Reclaim expired leases (each counts a failed attempt)." },
|
|
126
135
|
{ command: "cw sched reset", summary: "Reset a parked entry to ready (operator recovery)." },
|
|
127
|
-
|
|
136
|
+
// Two rows for one command string, on purpose — SPEC/cli-probe.md's
|
|
137
|
+
// "Odd things a rebuild must copy or fix on purpose" item 5 names
|
|
138
|
+
// `sched policy` as one of the show/set pairs a rebuild's help
|
|
139
|
+
// printer must keep doubled; this single merged row was a regression
|
|
140
|
+
// that lost the doubling (docs/rebuild/SPEC/cli-help/sched.txt has
|
|
141
|
+
// both), restored here with the old ground truth's exact wording.
|
|
142
|
+
{ command: "cw sched policy", summary: "Show the scheduling policy (file or default)." },
|
|
143
|
+
{ command: "cw sched policy", summary: "Set scheduling policy fields (concurrency/attempts/backoff/TTL)." },
|
|
128
144
|
],
|
|
129
145
|
registry: [
|
|
130
146
|
{ command: "cw registry refresh", summary: "Recompute and persist the derived run registry index." },
|
|
@@ -205,16 +221,22 @@ const COMMAND_HELP_ROWS = {
|
|
|
205
221
|
/** src/orchestrator.ts:899-933 — the top-level `cw help` text. Color is
|
|
206
222
|
* intentionally NOT applied here (this milestone's conformance runs pipe
|
|
207
223
|
* stdout, so NO_COLOR/non-TTY always wins); byte content matches the
|
|
208
|
-
* plain-text capture at SPEC/cli-help/_root.txt
|
|
224
|
+
* plain-text capture at SPEC/cli-help/_root.txt, but for the dead
|
|
225
|
+
* `update` lines, which were taken out on purpose (no code was behind
|
|
226
|
+
* the verb — see MORE_COMMANDS_TOKENS note above), for a `--json`
|
|
227
|
+
* Flags row, added because the old build's capture predates the ~68
|
|
228
|
+
* capability-table rows that now support `--json`/`--format json`
|
|
229
|
+
* (io.ts's wantsJson) — the flag existed but was never documented here —
|
|
230
|
+
* and for a `--quiet` Flags row, a new CLI spelling of the existing
|
|
231
|
+
* CW_DRIVE_PROGRESS=0 env var (see cli/entry.ts). */
|
|
209
232
|
function formatHelp() {
|
|
210
|
-
const moreCommandsLines = wrapPipeJoined(MORE_COMMANDS_TOKENS, MORE_COMMANDS_WRAP_WIDTH);
|
|
233
|
+
const moreCommandsLines = wrapPipeJoined(exports.MORE_COMMANDS_TOKENS, MORE_COMMANDS_WRAP_WIDTH);
|
|
211
234
|
const lines = [
|
|
212
235
|
"Cool Workflow",
|
|
213
236
|
"",
|
|
214
237
|
' -q "question" [-claude|-codex|-gemini|-deepseek] Ask a question, get a report',
|
|
215
238
|
' -q "question" --link <url> Review a remote repo by URL',
|
|
216
239
|
" version Show version",
|
|
217
|
-
" update Update to latest release",
|
|
218
240
|
" doctor Check setup",
|
|
219
241
|
" fix Show fix commands for setup issues",
|
|
220
242
|
"",
|
|
@@ -229,6 +251,8 @@ function formatHelp() {
|
|
|
229
251
|
" --verbose Show full agent narration live (default: compact)",
|
|
230
252
|
" --full Verbose, plus the report printed inline at the end",
|
|
231
253
|
" --no-color Disable ANSI color (also honors NO_COLOR / FORCE_COLOR)",
|
|
254
|
+
" --json Print JSON for commands that support it",
|
|
255
|
+
" --quiet Suppress [drive] progress lines (not agent output)",
|
|
232
256
|
"",
|
|
233
257
|
"More commands",
|
|
234
258
|
...moreCommandsLines,
|
|
@@ -312,3 +336,21 @@ function formatInfo(appId, data) {
|
|
|
312
336
|
lines.push(` Run: cw quickstart ${appId} --repo . --question "..."`);
|
|
313
337
|
return lines.join("\n");
|
|
314
338
|
}
|
|
339
|
+
/** `cw search <keyword>`'s human text — byte-exact to the milestone-1
|
|
340
|
+
* carry-over's own formatSearchResults (moved here from cli/dispatch.ts
|
|
341
|
+
* so the search capability-table row, which lives in core/, can render
|
|
342
|
+
* its own text without core importing from cli/). */
|
|
343
|
+
function formatSearchResults(keyword, results) {
|
|
344
|
+
if (results.length === 0) {
|
|
345
|
+
return `No workflows matched "${keyword}".\n Tip: cw list for all available workflows.`;
|
|
346
|
+
}
|
|
347
|
+
const lines = [`${results.length} workflow${results.length === 1 ? "" : "s"} matching "${keyword}"`];
|
|
348
|
+
for (const r of results) {
|
|
349
|
+
lines.push(` ${r.id} — ${r.title}`);
|
|
350
|
+
const cut = r.summary.length > 120 ? `${r.summary.slice(0, 119)}…` : r.summary;
|
|
351
|
+
lines.push(` ${cut}`);
|
|
352
|
+
}
|
|
353
|
+
lines.push("");
|
|
354
|
+
lines.push("Use cw info <id> for full details.");
|
|
355
|
+
return lines.join("\n");
|
|
356
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// core/format/safe-json.ts — a byte-capped JSON.stringify shared by the CLI's
|
|
3
|
+
// `--json` output (cli/io.ts) and the MCP `tools/call` result (mcp/server.ts).
|
|
4
|
+
//
|
|
5
|
+
// Both surfaces JSON.stringify a caller-selected result with no size limit.
|
|
6
|
+
// An aggregate/dashboard capability (e.g. cw_workbench_view, cw comment
|
|
7
|
+
// list --json on a run with thousands of comments) can grow to hundreds of
|
|
8
|
+
// MB: a useless dump for a calling agent's context window at best, and at
|
|
9
|
+
// worst a RangeError ("Invalid string length") once the string crosses
|
|
10
|
+
// V8's per-string ceiling (~256M UTF-16 code units) — the same failure
|
|
11
|
+
// class PR #316 fixed for batch stdout reconciliation, here on the output
|
|
12
|
+
// side instead. Both `--json` and MCP callers already catch a thrown error
|
|
13
|
+
// and report it cleanly (cli/entry.ts's top-level catch; mcp/server.ts's
|
|
14
|
+
// tools/call try/catch), so this is not a crash fix so much as a "return
|
|
15
|
+
// something useful instead of a cryptic RangeError, or an unusable giant
|
|
16
|
+
// payload" fix.
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.MAX_JSON_OUTPUT_BYTES = void 0;
|
|
19
|
+
exports.safeJsonStringify = safeJsonStringify;
|
|
20
|
+
/** Generous but bounded: comfortably above any real cw result today, far
|
|
21
|
+
* below V8's ~256M-UTF-16-code-unit ceiling. */
|
|
22
|
+
exports.MAX_JSON_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
23
|
+
/** Overflow-notice `detail` (a caught error's message) is truncated to this
|
|
24
|
+
* many characters — the notice itself must stay small and predictable
|
|
25
|
+
* regardless of how large the underlying error's message was (e.g. an
|
|
26
|
+
* error wrapping a huge stored blackboard-message body), both so the
|
|
27
|
+
* notice can never itself approach any cap, and so JSON.stringify-ing the
|
|
28
|
+
* notice can never itself throw. */
|
|
29
|
+
const MAX_DETAIL_CHARS = 500;
|
|
30
|
+
/** JSON.stringify `value` the same way `JSON.stringify(value, null, 2)`
|
|
31
|
+
* always has. If that would exceed `maxBytes` (or throws — e.g. a
|
|
32
|
+
* RangeError from a value so large stringify itself blows V8's string
|
|
33
|
+
* ceiling), returns a small, valid JSON string describing the overflow
|
|
34
|
+
* instead of the real payload. Byte-identical to the un-capped form for
|
|
35
|
+
* every result under the cap — the common case is untouched.
|
|
36
|
+
*
|
|
37
|
+
* `JSON.stringify` returns the actual value `undefined` (not a string,
|
|
38
|
+
* despite its `string` return-type declaration) for `undefined`, a
|
|
39
|
+
* function, or a Symbol — never throws for those. That is passed through
|
|
40
|
+
* unchanged (matching the un-capped form's pre-existing behavior for this
|
|
41
|
+
* edge case: `content[0].text`/`printJson`'s `${text}` end up printing
|
|
42
|
+
* literally `undefined`) rather than measured as a byte length. */
|
|
43
|
+
function safeJsonStringify(value, maxBytes = exports.MAX_JSON_OUTPUT_BYTES) {
|
|
44
|
+
let text;
|
|
45
|
+
try {
|
|
46
|
+
text = JSON.stringify(value, null, 2);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return JSON.stringify(overflowPayload(undefined, maxBytes, error), null, 2);
|
|
50
|
+
}
|
|
51
|
+
if (typeof text !== "string")
|
|
52
|
+
return text;
|
|
53
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
54
|
+
if (bytes <= maxBytes)
|
|
55
|
+
return text;
|
|
56
|
+
return JSON.stringify(overflowPayload(bytes, maxBytes), null, 2);
|
|
57
|
+
}
|
|
58
|
+
function overflowPayload(bytes, maxBytes, error) {
|
|
59
|
+
const rawDetail = error instanceof Error ? error.message : error !== undefined ? String(error) : undefined;
|
|
60
|
+
const detail = rawDetail !== undefined
|
|
61
|
+
? rawDetail.length > MAX_DETAIL_CHARS
|
|
62
|
+
? `${rawDetail.slice(0, MAX_DETAIL_CHARS)}… (truncated)`
|
|
63
|
+
: rawDetail
|
|
64
|
+
: undefined;
|
|
65
|
+
return {
|
|
66
|
+
error: "result-too-large",
|
|
67
|
+
message: `This result is too large to return in full (${bytes !== undefined ? `${bytes} bytes` : "exceeds the size limit while serializing"}, ` +
|
|
68
|
+
`cap is ${maxBytes} bytes). Narrow the query (a --limit/--status/--since filter, or a more specific tool/verb) and try again.`,
|
|
69
|
+
capBytes: maxBytes,
|
|
70
|
+
...(bytes !== undefined ? { actualBytes: bytes } : {}),
|
|
71
|
+
...(detail ? { detail } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -156,7 +156,7 @@ function stateExplosionReportLines(report) {
|
|
|
156
156
|
lines.push(` - ${reason}`);
|
|
157
157
|
if (report.compactGraph.collapsedNodeCount > 0) {
|
|
158
158
|
lines.push(`- Graph compacted: ${report.compactGraph.collapsedNodeCount} nodes collapsed into ${report.compactGraph.syntheticNodes.length} summary nodes`);
|
|
159
|
-
lines.push(` - Use: \`
|
|
159
|
+
lines.push(` - Use: \`cw multi-agent graph ${report.runId} --view full --json\``);
|
|
160
160
|
}
|
|
161
161
|
if (report.hiddenSourceRecords.length) {
|
|
162
162
|
for (const hidden of report.hiddenSourceRecords) {
|
|
@@ -123,7 +123,11 @@ function detectTies(candidates) {
|
|
|
123
123
|
const groups = new Map();
|
|
124
124
|
for (const candidate of candidates) {
|
|
125
125
|
const key = String(candidate.normalized);
|
|
126
|
-
|
|
126
|
+
const group = groups.get(key);
|
|
127
|
+
if (group)
|
|
128
|
+
group.push(candidate.candidateId);
|
|
129
|
+
else
|
|
130
|
+
groups.set(key, [candidate.candidateId]);
|
|
127
131
|
}
|
|
128
132
|
return Array.from(groups.values()).filter((group) => group.length > 1);
|
|
129
133
|
}
|
|
@@ -36,6 +36,7 @@ exports.listComments = listComments;
|
|
|
36
36
|
exports.distinctTargets = distinctTargets;
|
|
37
37
|
exports.formatReviewStatus = formatReviewStatus;
|
|
38
38
|
exports.formatCommentList = formatCommentList;
|
|
39
|
+
const collate_1 = require("../util/collate");
|
|
39
40
|
exports.COLLABORATION_SCHEMA_VERSION = 1;
|
|
40
41
|
/** The single, honest stand-in for an absent identity. */
|
|
41
42
|
exports.UNATTRIBUTED_ACTOR = {
|
|
@@ -213,7 +214,7 @@ function matchesAnyTarget(target, related) {
|
|
|
213
214
|
return related.some((entry) => sameTarget(target, entry));
|
|
214
215
|
}
|
|
215
216
|
function compareByCreated(left, right) {
|
|
216
|
-
return left.createdAt
|
|
217
|
+
return (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id);
|
|
217
218
|
}
|
|
218
219
|
function disqualify(record, policy, selfIds) {
|
|
219
220
|
const actor = record.actor;
|
|
@@ -414,16 +415,16 @@ function buildTimeline(state) {
|
|
|
414
415
|
function buildNextActions(runId, states, policy) {
|
|
415
416
|
const actions = [];
|
|
416
417
|
if (!policy) {
|
|
417
|
-
actions.push(`
|
|
418
|
+
actions.push(`cw review policy ${runId} --requiredApprovals 1 --authorizedRoles reviewer --appliesTo commit`);
|
|
418
419
|
return actions;
|
|
419
420
|
}
|
|
420
421
|
for (const state of states) {
|
|
421
422
|
if (state.status === "pending" || state.status === "blocked" || state.status === "unattributed") {
|
|
422
|
-
actions.push(`
|
|
423
|
+
actions.push(`cw approve ${state.target.kind} ${runId} ${state.target.id} --role <authorized-role> --actor <id> --attested`);
|
|
423
424
|
}
|
|
424
425
|
}
|
|
425
426
|
if (!actions.length)
|
|
426
|
-
actions.push(`
|
|
427
|
+
actions.push(`cw review status ${runId} --json`);
|
|
427
428
|
return actions;
|
|
428
429
|
}
|
|
429
430
|
/** Self ids for a candidate/selection target: its producing worker +
|
|
@@ -453,7 +454,7 @@ function distinctTargets(state) {
|
|
|
453
454
|
seen.set(targetKey(record.target), record.target);
|
|
454
455
|
for (const record of state.handoffs)
|
|
455
456
|
seen.set(targetKey(record.target), record.target);
|
|
456
|
-
return [...seen.values()].sort((left, right) => targetKey(left)
|
|
457
|
+
return [...seen.values()].sort((left, right) => (0, collate_1.stableCompare)(targetKey(left), targetKey(right)));
|
|
457
458
|
}
|
|
458
459
|
function formatReviewStatus(report) {
|
|
459
460
|
const lines = [];
|
|
@@ -47,6 +47,7 @@ exports.summarizeBlackboard = summarizeBlackboard;
|
|
|
47
47
|
exports.listBlackboardMessages = listBlackboardMessages;
|
|
48
48
|
exports.listBlackboardArtifacts = listBlackboardArtifacts;
|
|
49
49
|
exports.buildBlackboardGraph = buildBlackboardGraph;
|
|
50
|
+
const collate_1 = require("../util/collate");
|
|
50
51
|
exports.BLACKBOARD_SCHEMA_VERSION = 1;
|
|
51
52
|
/** Dedup, SORTS. Coordinator-side sorting `unique` — byte-identical
|
|
52
53
|
* behavior to core/multi-agent/runtime.ts's own copy, kept as a
|
|
@@ -420,7 +421,7 @@ function summarizeBlackboard(runId, state, blackboardId, defaultIndexPath) {
|
|
|
420
421
|
].sort();
|
|
421
422
|
const readyForFanin = Boolean(board && !openQuestions.length && !conflicts.length && artifacts.length > 0 && missingEvidence.length === 0);
|
|
422
423
|
const latestSnapshot = scoped(state.snapshots)
|
|
423
|
-
.sort((left, right) => left.createdAt
|
|
424
|
+
.sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt))
|
|
424
425
|
.at(-1);
|
|
425
426
|
return {
|
|
426
427
|
runId,
|
|
@@ -442,24 +443,24 @@ function summarizeBlackboard(runId, state, blackboardId, defaultIndexPath) {
|
|
|
442
443
|
}
|
|
443
444
|
function nextAction(runId, board, openQuestions, conflicts, artifacts) {
|
|
444
445
|
if (!board)
|
|
445
|
-
return `
|
|
446
|
+
return `cw blackboard topic create ${runId} --id <topic-id> --title "<title>"`;
|
|
446
447
|
if (conflicts.length)
|
|
447
|
-
return `
|
|
448
|
+
return `cw coordinator decision ${runId} --kind conflict-resolution --outcome accepted --subject ${conflicts[0].id} --reason "<reason>"`;
|
|
448
449
|
if (openQuestions.length)
|
|
449
|
-
return `
|
|
450
|
+
return `cw blackboard message post ${runId} --topic ${openQuestions[0].topicId} --body "<answer with evidence>"`;
|
|
450
451
|
if (!artifacts.length)
|
|
451
|
-
return `
|
|
452
|
-
return `
|
|
452
|
+
return `cw blackboard artifact add ${runId} --path <path> --kind <kind>`;
|
|
453
|
+
return `cw blackboard snapshot ${runId}`;
|
|
453
454
|
}
|
|
454
455
|
function listBlackboardMessages(state, options = {}) {
|
|
455
456
|
return state.messages
|
|
456
457
|
.filter((message) => (!options.blackboardId || message.blackboardId === options.blackboardId) && (!options.topicId || message.topicId === options.topicId))
|
|
457
|
-
.sort((left, right) => left.createdAt
|
|
458
|
+
.sort((left, right) => (0, collate_1.stableCompare)(left.createdAt, right.createdAt) || (0, collate_1.stableCompare)(left.id, right.id));
|
|
458
459
|
}
|
|
459
460
|
function listBlackboardArtifacts(state, options = {}) {
|
|
460
461
|
return state.artifacts
|
|
461
462
|
.filter((artifact) => (!options.blackboardId || artifact.blackboardId === options.blackboardId) && (!options.topicId || artifact.topicId === options.topicId))
|
|
462
|
-
.sort((left, right) => left.id
|
|
463
|
+
.sort((left, right) => (0, collate_1.stableCompare)(left.id, right.id));
|
|
463
464
|
}
|
|
464
465
|
function buildBlackboardGraph(runId, state, recordPath, messagesPath) {
|
|
465
466
|
const nodes = [];
|
|
@@ -841,15 +841,15 @@ function summarizeMultiAgent(run) {
|
|
|
841
841
|
function nextMultiAgentAction(run, blockedReasons) {
|
|
842
842
|
const state = ensureMultiAgentState(run);
|
|
843
843
|
if (!state.runs.length)
|
|
844
|
-
return `
|
|
844
|
+
return `cw multi-agent run ${run.id} --id <multi-agent-run-id>`;
|
|
845
845
|
if (blockedReasons.length)
|
|
846
|
-
return `
|
|
846
|
+
return `cw multi-agent fanin ${run.id} --group <group-id> --fanout <fanout-id>`;
|
|
847
847
|
const running = state.memberships.find((membership) => membership.status === "running");
|
|
848
848
|
if (running?.workerId)
|
|
849
|
-
return `
|
|
849
|
+
return `cw worker manifest ${run.id} ${running.workerId}`;
|
|
850
850
|
const groupWithoutFanin = state.groups.find((group) => group.membershipIds.length && !group.faninIds.length);
|
|
851
851
|
if (groupWithoutFanin)
|
|
852
|
-
return `
|
|
852
|
+
return `cw multi-agent fanin ${run.id} --group ${groupWithoutFanin.id}`;
|
|
853
853
|
return undefined;
|
|
854
854
|
}
|
|
855
855
|
function buildMultiAgentGraph(run) {
|
|
@@ -244,9 +244,9 @@ function topologyRunId(definition, taskIds, runId, sequence, hash) {
|
|
|
244
244
|
}
|
|
245
245
|
function nextActionsFor(runId, topologyRunId, fanoutId) {
|
|
246
246
|
return [
|
|
247
|
-
`
|
|
248
|
-
`
|
|
249
|
-
`
|
|
247
|
+
`cw dispatch ${runId} --multi-agent-fanout ${fanoutId}`,
|
|
248
|
+
`cw multi-agent fanin ${runId} ${topologyRunId}-fanin --fanout ${fanoutId}`,
|
|
249
|
+
`cw topology summary ${runId}`,
|
|
250
250
|
];
|
|
251
251
|
}
|
|
252
252
|
function statusToNodeStatus(status) {
|
|
@@ -20,6 +20,7 @@ exports.formatCommitId = formatCommitId;
|
|
|
20
20
|
exports.gateFailureSeq = gateFailureSeq;
|
|
21
21
|
const evidence_grounding_1 = require("../trust/evidence-grounding");
|
|
22
22
|
const result_normalize_1 = require("./result-normalize");
|
|
23
|
+
const collate_1 = require("../util/collate");
|
|
23
24
|
/** The sandbox profile that accepted a candidate's worker output — the
|
|
24
25
|
* worker's own profile when present, else the backing task's. Pure port of
|
|
25
26
|
* the old build's src/gates.ts sandboxProfileForCandidate. */
|
|
@@ -97,7 +98,7 @@ function findSelectionNode(run, selectionId) {
|
|
|
97
98
|
function latestSelectionForCandidate(run, candidateId) {
|
|
98
99
|
return [...(run.candidateSelections || [])]
|
|
99
100
|
.filter((s) => s.candidateId === candidateId)
|
|
100
|
-
.sort((a, b) => (b.selectedAt || ""
|
|
101
|
+
.sort((a, b) => (0, collate_1.stableCompare)(b.selectedAt || "", a.selectedAt || ""))[0];
|
|
101
102
|
}
|
|
102
103
|
function evidenceLocatorString(entry) {
|
|
103
104
|
const ref = entry.locator || entry.path || entry.summary || entry.id;
|
|
@@ -20,7 +20,14 @@ exports.formatDispatchId = formatDispatchId;
|
|
|
20
20
|
* everything after it (`null`). */
|
|
21
21
|
function firstRunnablePhase(run) {
|
|
22
22
|
for (const phase of run.phases) {
|
|
23
|
-
|
|
23
|
+
// A Set lookup, not `taskIds.includes()` re-scanned per task: this loop is
|
|
24
|
+
// O(phases x tasks), not O(phases x tasks x taskIds-per-phase) -- the
|
|
25
|
+
// latter degrades to O(tasks^2) since total taskIds across phases scales
|
|
26
|
+
// with total tasks, and this function is called several times per drive
|
|
27
|
+
// hop (once per hop directly, again inside selectDriveTask, again inside
|
|
28
|
+
// driveConcurrentRound), compounding to O(tasks^3) over a whole run.
|
|
29
|
+
const taskIds = new Set(phase.taskIds);
|
|
30
|
+
const phaseTasks = run.tasks.filter((task) => taskIds.has(task.id));
|
|
24
31
|
if (phaseTasks.some((task) => task.status === "running"))
|
|
25
32
|
return phase;
|
|
26
33
|
if (phaseTasks.some((task) => task.status === "pending"))
|
|
@@ -34,7 +41,8 @@ function firstRunnablePhase(run) {
|
|
|
34
41
|
* running when some task is running or completed, else pending. */
|
|
35
42
|
function updatePhaseStatuses(run) {
|
|
36
43
|
for (const phase of run.phases) {
|
|
37
|
-
const
|
|
44
|
+
const taskIds = new Set(phase.taskIds);
|
|
45
|
+
const phaseTasks = run.tasks.filter((task) => taskIds.has(task.id));
|
|
38
46
|
if (phaseTasks.length > 0 && phaseTasks.every((task) => task.status === "completed")) {
|
|
39
47
|
phase.status = "completed";
|
|
40
48
|
}
|
|
@@ -69,12 +77,18 @@ function formatDispatchTask(task) {
|
|
|
69
77
|
};
|
|
70
78
|
}
|
|
71
79
|
/** `nextDispatchTasks(run, limit?)` — pending tasks of the first runnable
|
|
72
|
-
* phase, capped, mapped through formatDispatchTask.
|
|
80
|
+
* phase, capped, mapped through formatDispatchTask. `??`, not `||`: an
|
|
81
|
+
* explicit `limit: 0` (or a configured `maxConcurrentAgents: 0`) means
|
|
82
|
+
* "dispatch nothing", not "no limit was given" — `0 || fallback` used to
|
|
83
|
+
* silently replace a real zero with the fallback. Negative numbers are
|
|
84
|
+
* clamped to 0 before reaching `.slice()`, which otherwise treats a
|
|
85
|
+
* negative end index as "drop that many from the end" instead of "cap at
|
|
86
|
+
* this many". */
|
|
73
87
|
function nextDispatchTasks(run, limit) {
|
|
74
88
|
const runnablePhase = firstRunnablePhase(run);
|
|
75
89
|
if (!runnablePhase)
|
|
76
90
|
return [];
|
|
77
|
-
const max =
|
|
91
|
+
const max = Math.max(0, Math.floor(limit ?? run.workflow.limits.maxConcurrentAgents ?? 4));
|
|
78
92
|
const runnableTaskIds = new Set(runnablePhase.taskIds);
|
|
79
93
|
return run.tasks
|
|
80
94
|
.filter((task) => task.status === "pending" && runnableTaskIds.has(task.id))
|
|
@@ -45,7 +45,8 @@ function selectDriveTask(run) {
|
|
|
45
45
|
const phase = (0, dispatch_1.firstRunnablePhase)(run);
|
|
46
46
|
if (!phase)
|
|
47
47
|
return undefined;
|
|
48
|
-
const
|
|
48
|
+
const taskIds = new Set(phase.taskIds);
|
|
49
|
+
const phaseTasks = run.tasks.filter((task) => taskIds.has(task.id));
|
|
49
50
|
return phaseTasks.find((task) => task.status === "running") || phaseTasks.find((task) => task.status === "pending");
|
|
50
51
|
}
|
|
51
52
|
function countCompleted(run) {
|
|
@@ -49,6 +49,7 @@ exports.RUN_STATE_MIGRATIONS = void 0;
|
|
|
49
49
|
exports.findMigrationPath = findMigrationPath;
|
|
50
50
|
exports.migrateRunState = migrateRunState;
|
|
51
51
|
exports.reverseRunState = reverseRunState;
|
|
52
|
+
exports.derivePhases = derivePhases;
|
|
52
53
|
const path = __importStar(require("node:path"));
|
|
53
54
|
const version_1 = require("../version");
|
|
54
55
|
const schema_1 = require("./schema");
|
|
@@ -118,6 +119,12 @@ function findMigrationPath(steps, fromVersion, toVersion) {
|
|
|
118
119
|
}
|
|
119
120
|
return { reachable: false, path: [], error: `no migration path from schemaVersion ${fromVersion} to ${toVersion}` };
|
|
120
121
|
}
|
|
122
|
+
/** Shared by migrateRunState/reverseRunState: missing EITHER `workflow` OR
|
|
123
|
+
* `paths` (or both) is the signal — a real run always has both together,
|
|
124
|
+
* so a lone surviving one of the two is not proof the rest wasn't lost. */
|
|
125
|
+
function computeSuspectedDataLoss(input) {
|
|
126
|
+
return !("workflow" in input) || !("paths" in input);
|
|
127
|
+
}
|
|
121
128
|
function migrateRunState(input, options = {}) {
|
|
122
129
|
const report = {
|
|
123
130
|
status: "current",
|
|
@@ -130,12 +137,14 @@ function migrateRunState(input, options = {}) {
|
|
|
130
137
|
changes: [],
|
|
131
138
|
warnings: [],
|
|
132
139
|
errors: [],
|
|
140
|
+
suspectedDataLoss: false,
|
|
133
141
|
};
|
|
134
142
|
if (!isRecord(input)) {
|
|
135
143
|
report.status = "unsupported";
|
|
136
144
|
report.errors.push("Run state must be a JSON object.");
|
|
137
145
|
return { run: {}, report };
|
|
138
146
|
}
|
|
147
|
+
report.suspectedDataLoss = computeSuspectedDataLoss(input);
|
|
139
148
|
if (report.detectedSchemaVersion < version_1.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION) {
|
|
140
149
|
report.status = "unsupported";
|
|
141
150
|
report.errors.push(`Unsupported run-state schemaVersion ${schemaVersionDescription(input, report.detectedSchemaVersion)}.`);
|
|
@@ -185,12 +194,14 @@ function reverseRunState(input, targetSchemaVersion, options = {}) {
|
|
|
185
194
|
changes: [],
|
|
186
195
|
warnings: [],
|
|
187
196
|
errors: [],
|
|
197
|
+
suspectedDataLoss: false,
|
|
188
198
|
};
|
|
189
199
|
if (!isRecord(input)) {
|
|
190
200
|
report.status = "unsupported";
|
|
191
201
|
report.errors.push("Run state must be a JSON object.");
|
|
192
202
|
return { run: {}, report };
|
|
193
203
|
}
|
|
204
|
+
report.suspectedDataLoss = computeSuspectedDataLoss(input);
|
|
194
205
|
if (targetSchemaVersion < version_1.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION) {
|
|
195
206
|
report.status = "unsupported";
|
|
196
207
|
report.errors.push(`Target schemaVersion ${targetSchemaVersion} is below the minimum supported ${version_1.MIN_SUPPORTED_RUN_STATE_SCHEMA_VERSION}.`);
|
|
@@ -428,7 +439,11 @@ function derivePhases(tasks) {
|
|
|
428
439
|
const taskId = stringValue(task.id);
|
|
429
440
|
if (!taskId)
|
|
430
441
|
continue;
|
|
431
|
-
|
|
442
|
+
const taskIds = byPhase.get(phase);
|
|
443
|
+
if (taskIds)
|
|
444
|
+
taskIds.push(taskId);
|
|
445
|
+
else
|
|
446
|
+
byPhase.set(phase, [taskId]);
|
|
432
447
|
}
|
|
433
448
|
if (byPhase.size === 0)
|
|
434
449
|
return [];
|