@worca/app 1.0.0-rc.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -9
- package/agents/clarify.meta.json +4 -4
- package/agents/decomposer.meta.json +5 -5
- package/agents/implementer.meta.json +15 -5
- package/agents/manualTestsChecklist.meta.json +5 -4
- package/agents/manualWebUiTesting.meta.json +9 -4
- package/agents/planReviewer.meta.json +12 -4
- package/agents/planner.meta.json +12 -5
- package/agents/refiner.meta.json +15 -4
- package/agents/reviewer.meta.json +14 -4
- package/agents/worca-cc-clarify.md +7 -0
- package/agents/worca-cc-code-reviewer.md +11 -6
- package/agents/worca-cc-decomposer.md +7 -0
- package/agents/worca-cc-implementer.md +9 -0
- package/agents/worca-cc-manual-tests-checklist.md +8 -5
- package/agents/worca-cc-manual-web-ui-testing.md +10 -6
- package/agents/worca-cc-plan-refiner.md +11 -6
- package/agents/worca-cc-plan-reviewer.md +10 -7
- package/agents/worca-cc-planner.md +9 -0
- package/agents/worca-cc-workspace-reviewer.md +11 -4
- package/agents/worca-cc-workspace-scanner.md +8 -4
- package/agents/workspaceReviewer.meta.json +15 -4
- package/agents/workspaceScanner.meta.json +5 -4
- package/package.json +8 -2
- package/skills/worca/SKILL.md +5 -5
- package/src/cli/render.mjs +148 -0
- package/src/cli/worca-cc.mjs +319 -45
- package/src/core/agent-gen.mjs +69 -31
- package/src/core/agent-registry.mjs +124 -144
- package/src/core/agent-store.mjs +164 -4
- package/src/core/artifacts.mjs +189 -21
- package/src/core/ask/catalog.mjs +111 -0
- package/src/core/ask/comment-deps.mjs +55 -0
- package/src/core/ask/events.mjs +506 -0
- package/src/core/ask/follow.mjs +107 -0
- package/src/core/ask/git-allowlist.mjs +226 -0
- package/src/core/ask/limits.mjs +54 -0
- package/src/core/ask/mcp-stdio.mjs +135 -0
- package/src/core/ask/models.mjs +125 -0
- package/src/core/ask/prompt.mjs +261 -0
- package/src/core/ask/proposal.mjs +170 -0
- package/src/core/ask/redact.mjs +30 -0
- package/src/core/ask/spawn.mjs +153 -0
- package/src/core/ask/store.mjs +360 -0
- package/src/core/ask/tool-deps.mjs +63 -0
- package/src/core/ask/tools.mjs +848 -0
- package/src/core/ask/turn.mjs +416 -0
- package/src/core/ask/worktree-deps.mjs +27 -0
- package/src/core/ask/worktrees.mjs +285 -0
- package/src/core/chat/command-router.mjs +20 -3
- package/src/core/claude-runner.mjs +434 -57
- package/src/core/config.mjs +264 -41
- package/src/core/cost-budget.mjs +29 -2
- package/src/core/db.mjs +684 -47
- package/src/core/diff-anchor.mjs +213 -0
- package/src/core/diff-comments.mjs +273 -0
- package/src/core/engine-select.mjs +32 -0
- package/src/core/git-info.mjs +49 -10
- package/src/core/graph/builtin-workflows.mjs +51 -0
- package/src/core/graph/executor.mjs +894 -0
- package/src/core/graph/registry-ports.mjs +12 -0
- package/src/core/graph/scheduler.mjs +1065 -0
- package/src/core/graph/seed-templates.mjs +318 -0
- package/src/core/model-env.mjs +112 -8
- package/src/core/model-test.mjs +79 -0
- package/src/core/orchestrator.mjs +902 -4098
- package/src/core/overview-agent.mjs +15 -3
- package/src/core/phases.mjs +208 -537
- package/src/core/pipeline-delete.mjs +13 -2
- package/src/core/plugin-api.mjs +8 -3
- package/src/core/plugin-config.mjs +178 -28
- package/src/core/plugin-inventory.mjs +6 -2
- package/src/core/plugin-manifest.mjs +199 -11
- package/src/core/plugin-models.mjs +1 -0
- package/src/core/plugin-repo.mjs +16 -4
- package/src/core/plugin-shim-child.mjs +9 -3
- package/src/core/plugin-shim.mjs +77 -14
- package/src/core/plugin-store.mjs +236 -29
- package/src/core/plugin-workflows.mjs +90 -41
- package/src/core/preflight.mjs +135 -3
- package/src/core/projects.mjs +7 -5
- package/src/core/protocol.mjs +8 -35
- package/src/core/recoverable-error.mjs +1 -1
- package/src/core/run-harness.mjs +3585 -0
- package/src/core/run-manifest.mjs +5 -1
- package/src/core/settings.mjs +109 -13
- package/src/core/skills.mjs +10 -3
- package/src/core/source-bindings.mjs +175 -0
- package/src/core/sources.mjs +87 -25
- package/src/core/stats.mjs +25 -6
- package/src/core/title.mjs +51 -4
- package/src/core/workflows.mjs +358 -259
- package/src/core/workspace-scan.mjs +4 -0
- package/src/core/worktree.mjs +98 -7
- package/src/shared/graph/agent-meta.mjs +278 -0
- package/src/shared/graph/constants.mjs +105 -0
- package/src/shared/graph/geometry.mjs +157 -0
- package/src/shared/graph/layout.mjs +134 -0
- package/src/shared/graph/loops.mjs +130 -0
- package/src/shared/graph/manifest.mjs +257 -0
- package/src/shared/graph/ports.mjs +153 -0
- package/src/shared/graph/route.mjs +397 -0
- package/src/shared/graph/template.mjs +165 -0
- package/src/shared/graph/thumbnail.mjs +67 -0
- package/src/shared/graph/validate.mjs +491 -0
- package/src/shared/graph/verdict.mjs +41 -0
- package/ui/public/app.js +4008 -1670
- package/ui/public/ask-markdown.mjs +145 -0
- package/ui/public/ask-model.mjs +264 -0
- package/ui/public/ask-panel.mjs +1880 -0
- package/ui/public/chat-settings-view.mjs +6 -2
- package/ui/public/diff-view.mjs +66 -11
- package/ui/public/file-tree.mjs +305 -0
- package/ui/public/graph/composer.mjs +889 -0
- package/ui/public/graph/inspector.mjs +183 -0
- package/ui/public/graph/model.mjs +37 -0
- package/ui/public/graph/palette.mjs +144 -0
- package/ui/public/graph/run-decor.mjs +410 -0
- package/ui/public/graph/run-hosts.mjs +201 -0
- package/ui/public/graph/save-dialog.mjs +56 -0
- package/ui/public/graph/view.mjs +858 -0
- package/ui/public/guardrails-view.mjs +4 -2
- package/ui/public/hljs-loader.mjs +180 -0
- package/ui/public/index.html +269 -265
- package/ui/public/log-filter.mjs +22 -4
- package/ui/public/log-line.mjs +45 -19
- package/ui/public/models-view.mjs +171 -9
- package/ui/public/plugins-view.mjs +106 -4
- package/ui/public/source-pane.mjs +190 -8
- package/ui/public/stats-view.mjs +81 -1
- package/ui/public/style.css +1459 -229
- package/ui/public/syntax-highlight.mjs +270 -0
- package/ui/public/thinking-orb.mjs +110 -0
- package/ui/server.mjs +1667 -98
- package/src/core/channels.mjs +0 -302
- package/src/core/runners.mjs +0 -167
- package/src/core/workflow-validator.mjs +0 -185
- package/ui/public/composer-core.mjs +0 -211
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"key": "workspaceScanner",
|
|
3
|
+
"metaVersion": 2,
|
|
4
|
+
"mockRole": "workspace-scan",
|
|
5
|
+
"placeable": false,
|
|
6
|
+
"inputs": [{ "id": "task", "type": "md" }],
|
|
7
|
+
"outputs": [{ "id": "workspace", "type": "md", "filename": "workspace-description.md" }],
|
|
3
8
|
"domain": "shared",
|
|
4
9
|
"displayName": "Workspace Scan",
|
|
5
10
|
"description": "Maps how the workspace's projects relate before pipelines run. Shared APIs, schemas, queues, and build dependencies.",
|
|
@@ -7,11 +12,7 @@
|
|
|
7
12
|
"icon": "<path d=\"M4 7h16M4 12h10M4 17h7\" stroke-linecap=\"round\"/><circle cx=\"18\" cy=\"15\" r=\"3\"/><path d=\"M20.2 17.2L22 19\" stroke-linecap=\"round\"/>",
|
|
8
13
|
"agentFile": "worca-cc-workspace-scanner.md",
|
|
9
14
|
"runnerType": "producer",
|
|
10
|
-
"loopSource": false,
|
|
11
15
|
"fanOut": true,
|
|
12
|
-
"produces": ["workspace"],
|
|
13
|
-
"consumes": ["userPrompt"],
|
|
14
|
-
"connectsTo": [],
|
|
15
16
|
"asksQuestions": false,
|
|
16
17
|
"order": 0.5,
|
|
17
18
|
"scope": "workspace-only"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@worca/app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Worca — deterministic multi-agent pipeline that drives Claude Code (headless) through Plan -> Refine -> Implement -> Review, with a CLI, an installable /worca skill, and a web UI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sinisha Djukic",
|
|
@@ -46,10 +46,16 @@
|
|
|
46
46
|
"smoke": "WORCA_MOCK=1 WORCA_HOME=.worca-cc-smoke node --disable-warning=ExperimentalWarning src/cli/worca-cc.mjs --project sandbox --prompt \"demo task\" --mock --yes",
|
|
47
47
|
"smoke:workspace": "WORCA_MOCK=1 WORCA_HOME=.worca-cc-smoke node --disable-warning=ExperimentalWarning scripts/smoke-workspace.mjs",
|
|
48
48
|
"smoke:plugin": "WORCA_MOCK=1 WORCA_HOME=.worca-cc-smoke node --disable-warning=ExperimentalWarning scripts/smoke-plugin.mjs",
|
|
49
|
-
"
|
|
49
|
+
"ask:fixtures": "node --disable-warning=ExperimentalWarning scripts/ask-capture-fixtures.mjs",
|
|
50
|
+
"verify:composer": "node --disable-warning=ExperimentalWarning scripts/verify-composer-cdp.mjs",
|
|
51
|
+
"verify:run-monitor": "node --disable-warning=ExperimentalWarning scripts/verify-run-monitor-cdp.mjs",
|
|
52
|
+
"test": "rm -rf .worca-cc-test && PATH=\"$PWD/test/helpers/no-real-claude:$PATH\" WORCA_HOME=.worca-cc-test node --disable-warning=ExperimentalWarning --test test/*.mjs; s=$?; node test/helpers/no-real-claude/assert-none.mjs || s=1; exit $s"
|
|
50
53
|
},
|
|
51
54
|
"dependencies": {
|
|
55
|
+
"@highlightjs/cdn-assets": "11.12.0",
|
|
56
|
+
"dompurify": "3.4.14",
|
|
52
57
|
"express": "^4.19.2",
|
|
58
|
+
"marked": "18.0.10",
|
|
53
59
|
"ws": "^8.18.0"
|
|
54
60
|
},
|
|
55
61
|
"devDependencies": {
|
package/skills/worca/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: worca
|
|
3
|
-
description: Run the
|
|
3
|
+
description: Run the node-graph multi-agent orchestrator over a software task in the current project. Triggers on "/worca", "/worca <prompt>", "/worca --ui", and on requests to orchestrate, run the orchestration pipeline, or drive plan/refine/implement/review for a task.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Orchestrate
|
|
7
7
|
|
|
8
|
-
Drive the current project through the
|
|
8
|
+
Drive the current project through the selected pipeline template (default `wf_default`: Plan → Refine ↺ → Implement → Review ↺ → End). Orchestration is performed by a deterministic Node.js script; this skill just launches it. Artifacts (plans, reviews, pipeline audit logs) are written under `ai-artifacts/` in the project.
|
|
9
9
|
|
|
10
10
|
The orchestrator repo lives wherever it was installed. `<WORCA_REPO>` below is the absolute path of that repo (the directory containing `src/cli/worca-cc.mjs`). If you installed via `scripts/install.mjs`, the installer rewrites `<WORCA_REPO>` in this file to the real path automatically; otherwise substitute it yourself (or set an `WORCA_REPO` environment variable and use `"$WORCA_REPO"`).
|
|
11
11
|
|
|
@@ -19,13 +19,13 @@ node <WORCA_REPO>/src/cli/worca-cc.mjs --project "$PWD" --prompt "<args>"
|
|
|
19
19
|
|
|
20
20
|
- `--project "$PWD"` — operate inside the user's current project (the orchestrator does all file writes here).
|
|
21
21
|
- `--prompt "<args>"` — everything the user typed after `/worca`. Quote it.
|
|
22
|
-
- The CLI streams
|
|
22
|
+
- The CLI streams every agent execution (start/done, cycle, cost) and live agent logs to the terminal. When the clarify step needs a decision it shows each question's 2–4 options plus a free-text field; when a refine/review loop hits its cap it shows the open critical/major issues and asks whether to continue or approve another cycle. Answer interactively.
|
|
23
23
|
- On completion it prints the pipeline directory under `ai-artifacts/pipelines/`.
|
|
24
24
|
|
|
25
25
|
Useful flags (pass through when the user asks):
|
|
26
26
|
- `--file <path.md>` — use a markdown file as the prompt instead of `--prompt`.
|
|
27
27
|
- `--title "<name>"` — label the pipeline.
|
|
28
|
-
- `--
|
|
28
|
+
- `--workflow <id>` — run a saved pipeline template instead of the built-in default (`wf_default`).
|
|
29
29
|
- `--model <m>` / `--permission-mode <m>` — Claude model / permission mode (default `acceptEdits`).
|
|
30
30
|
- `--mock` — run the full pipeline offline with canned agents (no Claude spawn, no tokens); great for a dry run. Equivalent to setting `WORCA_MOCK=1`.
|
|
31
31
|
- `--yes` / `--non-interactive` — auto-answer (clarify picks the first option; gates choose "continue"). Use for unattended runs.
|
|
@@ -39,7 +39,7 @@ node <WORCA_REPO>/src/cli/worca-cc.mjs \
|
|
|
39
39
|
|
|
40
40
|
## /worca --ui — launch the web UI
|
|
41
41
|
|
|
42
|
-
To start the web app (new-pipeline form,
|
|
42
|
+
To start the web app (new-pipeline form, live pipeline graph, live log window, question + loop-gate panels, Stop button, run history):
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
node <WORCA_REPO>/src/cli/worca-cc.mjs --ui
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// src/cli/render.mjs
|
|
2
|
+
//
|
|
3
|
+
// The CLI's rendering of the graph engine's `exec` stream. PURE: no IO, no
|
|
4
|
+
// colour codes of its own — the caller injects `color(name, text)`. Labels come
|
|
5
|
+
// from the run manifest (pipelines.stepper); execution ids are NEVER printed.
|
|
6
|
+
//
|
|
7
|
+
// The line shapes (spec §8, literal):
|
|
8
|
+
// ▶ Implementer #2 · fix ← Reviewer agent start (loop port from trigger.freshPorts; source = the wire's from-node)
|
|
9
|
+
// ▶ task 3/7 · Add schema kind:'task' slice start (indented; index within its phase)
|
|
10
|
+
// ✓ Implementer #2 1m03s · $0.12 agent done (verifiers append " — blocking" / " — clean")
|
|
11
|
+
// ✓ OR · OR → Implementer flow-node done: ONE dim line, no ordinal/duration/cost; the
|
|
12
|
+
// marker only for AND / OR / COMBINE (`✓ Task` is bare)
|
|
13
|
+
// ✗ Reviewer #1 12s — <error> agent error
|
|
14
|
+
// ⏸ Implementer #1 paused agent paused
|
|
15
|
+
// ■ End ← Reviewer.pass → plan-review.md End bound — End's ONLY line
|
|
16
|
+
// Flow nodes never print a start/paused/error line; `skipped` and P8's bookend
|
|
17
|
+
// executions render nothing; `token` events are never rendered.
|
|
18
|
+
import { BOOKEND_EXECUTION_IDS } from '../shared/graph/constants.mjs';
|
|
19
|
+
|
|
20
|
+
const nodesOf = (m) => ((m && m.graph && m.graph.nodes) || []).filter(Boolean);
|
|
21
|
+
const wiresOf = (m) => ((m && m.graph && m.graph.wires) || []).filter(Boolean);
|
|
22
|
+
const nodeOf = (m, id) => nodesOf(m).find((n) => n.id === id) || null;
|
|
23
|
+
const labelOf = (m, id) => { const n = nodeOf(m, id); return (n && (n.label || n.id)) || id; };
|
|
24
|
+
const base = (p) => String(p || '').split('/').filter(Boolean).pop() || '';
|
|
25
|
+
/** Flow kinds whose done line carries a marker (` · AND`, ` · OR → X`, ` · COMBINE → X`). */
|
|
26
|
+
const MARKED_FLOW = new Set(['and', 'or', 'combine']);
|
|
27
|
+
|
|
28
|
+
/** `12s` / `1m03s` / `1h01m` — the CLI's compact shape (NOT the UI's `1m 3s`). */
|
|
29
|
+
export function fmtDur(ms) {
|
|
30
|
+
const s = Math.max(0, Math.round((Number(ms) || 0) / 1000));
|
|
31
|
+
if (s < 60) return `${s}s`;
|
|
32
|
+
const m = Math.floor(s / 60);
|
|
33
|
+
if (m < 60) return `${m}m${String(s % 60).padStart(2, '0')}s`;
|
|
34
|
+
return `${Math.floor(m / 60)}h${String(m % 60).padStart(2, '0')}m`;
|
|
35
|
+
}
|
|
36
|
+
const usd = (n) => `$${(Number(n) || 0).toFixed(2)}`;
|
|
37
|
+
|
|
38
|
+
/** ` · fix ← Reviewer`: the loop port that re-fired this execution
|
|
39
|
+
* (trigger.freshPorts ∩ the node's `loop: true` inputs) and the node that
|
|
40
|
+
* published on the wire that DELIVERED it. Through an OR valve that wire is
|
|
41
|
+
* the valve's out-wire, so the source reads `← OR` (probe P13). */
|
|
42
|
+
function loopSource(ev, node, m) {
|
|
43
|
+
const loopIns = new Set(((node && node.ports && node.ports.inputs) || []).filter((p) => p && p.loop).map((p) => p.id));
|
|
44
|
+
const trig = ev.trigger || {};
|
|
45
|
+
const port = (trig.freshPorts || []).find((p) => loopIns.has(p));
|
|
46
|
+
if (!port) return '';
|
|
47
|
+
const wire = wiresOf(m).find((w) => (trig.wireIds || []).includes(w.id) && w.to && w.to.port === port);
|
|
48
|
+
return wire ? ` · ${port} ← ${labelOf(m, wire.from.node)}` : ` · ${port}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The dim marker an AND/OR/COMBINE card carries: ` · AND`, ` · OR → Implementer`. */
|
|
52
|
+
function flowMarker(node, m, color) {
|
|
53
|
+
const kind = String(node.kind).toUpperCase();
|
|
54
|
+
const out = wiresOf(m).find((w) => w.from && w.from.node === node.id);
|
|
55
|
+
return color('dim', out ? ` · ${kind} → ${labelOf(m, out.to.node)}` : ` · ${kind}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** ONE `exec` event -> ONE terminal line ('' when the event renders nothing). */
|
|
59
|
+
export function formatExecLine(ev, manifest, { color = (n, s) => s } = {}) {
|
|
60
|
+
if (!ev || !ev.nodeId) return '';
|
|
61
|
+
if (BOOKEND_EXECUTION_IDS.includes(ev.executionId)) return ''; // P8's preflight/done rows render nothing
|
|
62
|
+
const m = manifest || {};
|
|
63
|
+
const node = nodeOf(m, ev.nodeId);
|
|
64
|
+
const label = labelOf(m, ev.nodeId);
|
|
65
|
+
if (ev.kind === 'task') { // a composite slice: one indented start line, nothing else
|
|
66
|
+
if (ev.status !== 'start') return '';
|
|
67
|
+
const n = ev.taskIndex, t = ev.taskTotal;
|
|
68
|
+
const which = Number.isFinite(n) && Number.isFinite(t) ? ` ${n}/${t}` : '';
|
|
69
|
+
return ` ${color('cyan', '▶')} task${which}${ev.title ? ` · ${ev.title}` : ''}`;
|
|
70
|
+
}
|
|
71
|
+
if (node && node.kind === 'end') { // End renders ONE line: the binding
|
|
72
|
+
if (ev.status !== 'done') return '';
|
|
73
|
+
const wire = wiresOf(m).find((w) => ((ev.trigger && ev.trigger.wireIds) || []).includes(w.id));
|
|
74
|
+
const from = wire ? ` ← ${labelOf(m, wire.from.node)}.${wire.from.port}` : '';
|
|
75
|
+
const r = ev.result || {};
|
|
76
|
+
const tail = r.path ? ` → ${base(r.path)}` : (r.value != null ? ` → ${String(r.value)}` : '');
|
|
77
|
+
return `${color('bold', '■')} ${label}${from}${tail}`;
|
|
78
|
+
}
|
|
79
|
+
if (node && node.kind !== 'agent') { // a flow card: one dim ✓ line, no ordinal / duration / cost
|
|
80
|
+
if (ev.status !== 'done') return '';
|
|
81
|
+
return `${color('green', '✓')} ${label}${MARKED_FLOW.has(node.kind) ? flowMarker(node, m, color) : ''}`;
|
|
82
|
+
}
|
|
83
|
+
const ord = ` #${ev.ordinal ?? 1}`;
|
|
84
|
+
if (ev.status === 'start') return `${color('cyan', '▶')} ${label}${ord}${loopSource(ev, node, m)}`;
|
|
85
|
+
if (ev.status === 'paused') return `${color('yellow', '⏸')} ${label}${ord} paused`;
|
|
86
|
+
const dur = ev.durationMs != null ? ` ${fmtDur(ev.durationMs)}` : '';
|
|
87
|
+
if (ev.status === 'error') return `${color('red', '✗')} ${label}${ord}${dur} — ${ev.error || 'failed'}`;
|
|
88
|
+
if (ev.status !== 'done') return ''; // `skipped` (and anything unknown) renders nothing
|
|
89
|
+
const cost = ev.costUsd != null ? ` · ${usd(ev.costUsd)}` : '';
|
|
90
|
+
const verdict = ev.verdict ? (ev.verdict.missing ? ' — no verdict written (treated as clean)' : ev.verdict.hasBlocking ? ' — blocking' : ' — clean') : '';
|
|
91
|
+
return `${color('green', '✓')} ${label}${ord}${dur}${cost}${verdict}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The interactive gate prompt's header. */
|
|
95
|
+
export function formatGateHeader(payload, manifest) {
|
|
96
|
+
const m = manifest || {};
|
|
97
|
+
const wire = wiresOf(m).find((w) => w.id === (payload && payload.wireId)) || null;
|
|
98
|
+
const where = wire ? ` · ${labelOf(m, wire.from.node)} → ${labelOf(m, wire.to.node)}` : '';
|
|
99
|
+
const max = wire && wire.maxCycles ? Number(wire.maxCycles) : null;
|
|
100
|
+
// The cycle comes from the PAYLOAD's `deliveryNo`, never from the id: a wire that
|
|
101
|
+
// holds more than once mints `gate-<wireId>-<deliveryNo>-h<holdNo>`, so the id's
|
|
102
|
+
// trailing number is the HOLD ordinal, not the delivery (MAJ-11). No deliveryNo
|
|
103
|
+
// (an older resume point, a hand-built payload) falls back to the budget.
|
|
104
|
+
const n = Number(payload && payload.deliveryNo);
|
|
105
|
+
const used = Number.isFinite(n) && n > 0 ? n : max;
|
|
106
|
+
const budget = max ? ` ${used || max}/${max} cycles used` : '';
|
|
107
|
+
return `? Loop gate${where}${budget}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The summary's result line. */
|
|
111
|
+
export function formatResultLine(result) {
|
|
112
|
+
const r = result || {};
|
|
113
|
+
if (r.path) return `Result: ${r.path}`;
|
|
114
|
+
if (r.value != null && r.value !== '') return `Result: ${String(r.value)}`;
|
|
115
|
+
return 'Result: completed';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** `9 executions · 12m00s active · $1.23`. */
|
|
119
|
+
export function formatTotals({ executions = 0, activeMs = 0, costUsd = 0 } = {}) {
|
|
120
|
+
return `${executions} execution${executions === 1 ? '' : 's'} · ${fmtDur(activeMs)} active · ${usd(costUsd)}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The v2 run summary the CLI prints under `Pipeline complete.` — pure, so the
|
|
125
|
+
* quiescence arm and the executions count are testable without spawning a run.
|
|
126
|
+
* Returns [] for a v1 run. An EXECUTION row carries an executionId that is not
|
|
127
|
+
* a bookend id: today's bookends are `_recordStep('preflight'|'done', …)` rows
|
|
128
|
+
* with key 'preflight' / 'done' and NO executionId; P8's become real
|
|
129
|
+
* x:preflight:1 / x:done:1 executions (BOOKEND_EXECUTION_IDS). The active time
|
|
130
|
+
* is the SUM over execution rows — state.totalActiveMs includes preflight.
|
|
131
|
+
*/
|
|
132
|
+
export function formatRunSummary(state) {
|
|
133
|
+
const st = state || {};
|
|
134
|
+
if (!(st.stepper && st.stepper.version === 2)) return [];
|
|
135
|
+
const rows = (Array.isArray(st.steps) ? st.steps : [])
|
|
136
|
+
.filter((s) => s && s.executionId && !BOOKEND_EXECUTION_IDS.includes(s.executionId));
|
|
137
|
+
const lines = [];
|
|
138
|
+
// Quiescence is the reducer's rule: the run reached `done` WITHOUT the End node
|
|
139
|
+
// firing. A stopped/errored run also has endReached false and must NOT claim it.
|
|
140
|
+
if (st.endReached === false && String(st.status || '').toLowerCase() === 'done') lines.push('Finished at quiescence — End not reached');
|
|
141
|
+
else lines.push(formatResultLine(st.result));
|
|
142
|
+
lines.push(formatTotals({
|
|
143
|
+
executions: rows.length,
|
|
144
|
+
activeMs: rows.reduce((a, s) => a + (Number(s.activeMs) || 0), 0),
|
|
145
|
+
costUsd: st.totalCostUsd,
|
|
146
|
+
}));
|
|
147
|
+
return lines;
|
|
148
|
+
}
|