@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.17.0-fork.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/CHANGELOG.md +67 -0
- package/dist/src/agent-execution.js +36 -51
- package/dist/src/background-widget.d.ts +56 -0
- package/dist/src/background-widget.js +22 -15
- package/dist/src/decoders.js +2 -1
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-view.d.ts +16 -1
- package/dist/src/host-view.js +109 -42
- package/dist/src/host.js +57 -24
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +867 -545
- package/dist/src/index.js.map +3 -3
- package/dist/src/registry.js +2 -1
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +325 -0
- package/dist/src/settings.d.ts +13 -0
- package/dist/src/settings.js +253 -0
- package/dist/src/trajectory.d.ts +29 -4
- package/dist/src/trajectory.js +58 -3
- package/dist/src/types.d.ts +0 -1
- package/dist/src/utils.d.ts +2 -1
- package/dist/src/utils.js +10 -0
- package/dist/src/validation.d.ts +5 -17
- package/dist/src/validation.js +9 -458
- package/dist/subagents/index.js +1132 -694
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +4 -0
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +2 -1
- package/dist/subagents/src/manager.js +29 -10
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +23 -2
- package/dist/subagents/src/view.js +96 -30
- package/dist/trajectory/assets/index.html +347 -231
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +377 -257
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +347 -231
- package/dist/trajectory/src/assets/prism.min.js +13 -0
- package/dist/trajectory/src/export.js +4 -2
- package/dist/trajectory/src/index.d.ts +2 -0
- package/dist/trajectory/src/index.js +143 -42
- package/dist/trajectory/src/server.js +134 -9
- package/dist/trajectory/src/server.js.map +1 -1
- package/package.json +2 -1
- package/src/agent-execution.ts +36 -42
- package/src/background-widget.ts +25 -18
- package/src/decoders.ts +2 -1
- package/src/host-navigator.ts +58 -14
- package/src/host-view.ts +93 -37
- package/src/host.ts +51 -21
- package/src/index.ts +3 -1
- package/src/registry.ts +2 -1
- package/src/roles.ts +326 -0
- package/src/settings.ts +206 -0
- package/src/trajectory.ts +76 -7
- package/src/types.ts +1 -1
- package/src/utils.ts +8 -1
- package/src/validation.ts +12 -356
- package/subagents/README.md +1 -1
- package/subagents/src/contracts.ts +4 -0
- package/subagents/src/index.ts +3 -2
- package/subagents/src/manager.ts +28 -10
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +97 -28
- package/trajectory/src/assets/index.html +347 -231
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +118 -38
- package/trajectory/src/server.ts +112 -12
package/dist/src/host-view.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { keyHint, truncateToVisualLines } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import { budgetUsage } from "./budget.js";
|
|
4
4
|
import { formatCost, formatTokens } from "./background-widget.js";
|
|
5
|
-
import { BUDGET_DIMENSIONS, HARD_TERMINAL_RUN_STATES, SETTLED_AGENT_STATES, WORKFLOW_AGENT_STALL_THRESHOLD_MS, sumAccounting } from "./types.js";
|
|
5
|
+
import { BUDGET_DIMENSIONS, HARD_TERMINAL_RUN_STATES, SETTLED_AGENT_STATES, WORKFLOW_AGENT_STALL_THRESHOLD_MS, sumAccounting, zeroAccounting } from "./types.js";
|
|
6
6
|
import { object, sanitizeDisplayText } from "./utils.js";
|
|
7
7
|
import { WORKFLOW_PHASE_STATES, buildWorkflowPhaseModel, buildWorkflowPhaseTree, phaseAgentCounts, shellActivityFor, workflowPhaseTreeInitialExpanded, workflowPhaseTreePath, workflowPhaseTreeVisibleNodes, } from "./host-phases.js";
|
|
8
8
|
export { SETTLED_AGENT_STATES } from "./types.js";
|
|
9
|
-
const PLAIN_WORKFLOW_PROGRESS_STYLES = { accent: (text) => text, success: (text) => text, error: (text) => text, warning: (text) => text, muted: (text) => text, dim: (text) => text, bold: (text) => text };
|
|
9
|
+
export const PLAIN_WORKFLOW_PROGRESS_STYLES = { accent: (text) => text, success: (text) => text, error: (text) => text, warning: (text) => text, muted: (text) => text, dim: (text) => text, bold: (text) => text };
|
|
10
10
|
function agentGroupKey(agent) { return JSON.stringify([agent.structuralPath ?? [], agent.parentBreadcrumb ?? null]); }
|
|
11
11
|
function agentGroupLabel(agents) {
|
|
12
12
|
const structural = agents[0]?.structuralPath ?? [];
|
|
@@ -42,7 +42,7 @@ function renderGroupedAgents(agents, render, allAgents = agents, groupLabel = (l
|
|
|
42
42
|
}
|
|
43
43
|
const RUN_STATE_GLYPH = { "not started": "○", queued: "○", pausing: "⏸", paused: "⏸", completed: "✓", failed: "✗", stopped: "✗", interrupted: "↯", budget_exhausted: "!", awaiting_input: "?" };
|
|
44
44
|
const AGENT_STATE_GLYPH = { queued: "○", waiting_for_child: "…", paused: "⏸", retrying: "↻", completed: "✓", failed: "✗", cancelled: "✗" };
|
|
45
|
-
function runStateGlyph(state, running) { return state === "running" ? running : RUN_STATE_GLYPH[state] ?? "◆"; }
|
|
45
|
+
export function runStateGlyph(state, running) { return state === "running" ? running : RUN_STATE_GLYPH[state] ?? "◆"; }
|
|
46
46
|
function agentStateGlyph(state, running) { return state === "running" ? running : AGENT_STATE_GLYPH[state] ?? "○"; }
|
|
47
47
|
const PROGRESS_STATE_STYLE = { completed: "success", failed: "error", cancelled: "error", running: "accent", paused: "warning", pausing: "warning", interrupted: "warning", retrying: "accent", budget_exhausted: "warning", awaiting_input: "warning" };
|
|
48
48
|
const WORKFLOW_ICON_STYLE = { completed: "success", failed: "error", stopped: "error", interrupted: "warning", budget_exhausted: "warning", awaiting_input: "warning", paused: "warning", pausing: "warning", running: "accent" };
|
|
@@ -51,10 +51,10 @@ function styleForState(map, state, styles) {
|
|
|
51
51
|
const key = map[state] ?? "muted";
|
|
52
52
|
return (text) => styles[key](text);
|
|
53
53
|
}
|
|
54
|
-
function progressStyleForState(state, styles) { return styleForState(PROGRESS_STATE_STYLE, state, styles); }
|
|
54
|
+
export function progressStyleForState(state, styles) { return styleForState(PROGRESS_STATE_STYLE, state, styles); }
|
|
55
55
|
function workflowIconStyle(state, styles) { return styleForState(WORKFLOW_ICON_STYLE, state, styles); }
|
|
56
56
|
function phaseStyleForState(state, styles) { return styleForState(PHASE_STATE_STYLE, state, styles); }
|
|
57
|
-
function formatWorkflowRuntime(durationMs) {
|
|
57
|
+
export function formatWorkflowRuntime(durationMs) {
|
|
58
58
|
const seconds = Math.max(0, Math.floor(durationMs / 1000));
|
|
59
59
|
if (seconds < 60)
|
|
60
60
|
return `${String(seconds)}s`;
|
|
@@ -82,7 +82,7 @@ function formatLogTimestamp(timestamp) {
|
|
|
82
82
|
return "--:--:--";
|
|
83
83
|
return [date.getHours(), date.getMinutes(), date.getSeconds()].map((part) => String(part).padStart(2, "0")).join(":");
|
|
84
84
|
}
|
|
85
|
-
function workflowLogLines(run, styles, expanded, width, showHint = false) {
|
|
85
|
+
function workflowLogLines(run, styles, expanded, width, showHint = false, tail = 5) {
|
|
86
86
|
const events = (run.events ?? []).filter((event) => event.type === "log");
|
|
87
87
|
if (!events.length)
|
|
88
88
|
return [];
|
|
@@ -94,7 +94,7 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
|
|
|
94
94
|
const chunks = truncateToVisualLines(message, Number.MAX_SAFE_INTEGER, Math.max(1, width - 11), 0).visualLines;
|
|
95
95
|
return chunks.map((chunk, chunkIndex) => `${chunkIndex === 0 ? prefix : indent}${chunk}`);
|
|
96
96
|
}));
|
|
97
|
-
const visible = expanded ? visualLines : visualLines.slice(-
|
|
97
|
+
const visible = expanded ? visualLines : visualLines.slice(-tail);
|
|
98
98
|
let hint = "";
|
|
99
99
|
if (!expanded && showHint) {
|
|
100
100
|
try {
|
|
@@ -104,7 +104,55 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
|
|
|
104
104
|
}
|
|
105
105
|
return [expanded ? ` ${styles.muted("Logs")}` : ` ${styles.muted(`Logs${hint}`)}`, ...visible];
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
const ACTIVE_AGENT_STATES = new Set(["running", "waiting_for_child", "retrying", "paused"]);
|
|
108
|
+
const COMPACT_RECENT_SETTLED = 3;
|
|
109
|
+
const COMPACT_LOG_LINES = 3;
|
|
110
|
+
function expandHint() { try {
|
|
111
|
+
return ` (${keyHint("app.tools.expand", "to expand")})`;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return "";
|
|
115
|
+
} }
|
|
116
|
+
function agentEnd(agent) { return (agent.startedAt ?? 0) + (agent.durationMs ?? 0); }
|
|
117
|
+
/** One line for a finished phase: outcome counts, wall time across its agents, tokens, and cost. */
|
|
118
|
+
function phaseSummary(agents, styles) {
|
|
119
|
+
const count = (state) => agents.filter((agent) => agent.state === state).length;
|
|
120
|
+
const starts = agents.map((agent) => agent.startedAt).filter((value) => value !== undefined);
|
|
121
|
+
const span = starts.length ? Math.max(...agents.map(agentEnd)) - Math.min(...starts) : 0;
|
|
122
|
+
const accounting = sumAccounting(agents.map((agent) => agent.accounting ?? zeroAccounting()));
|
|
123
|
+
const failed = count("failed");
|
|
124
|
+
const parts = [`${String(count("completed"))} done`, ...(failed ? [styles.error(`${String(failed)} failed`)] : []), ...(count("cancelled") ? [`${String(count("cancelled"))} cancelled`] : []), ...(span > 0 ? [formatWorkflowRuntime(span)] : []), formatTokens(accounting.input + accounting.output), formatCost(accounting.cost)].filter(Boolean);
|
|
125
|
+
return styles.muted(parts.join(" · "));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Renders the run; with `maxLines`, a view taller than that limit switches to a compact one so the live block stays on screen.
|
|
129
|
+
* A block taller than the terminal pushes its changing header above the viewport, and pi-tui then clears and redraws the whole
|
|
130
|
+
* scrollback on every update, which snaps the reader back to the bottom.
|
|
131
|
+
*/
|
|
132
|
+
export function formatWorkflowProgress(run, spinner = "◇", styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now = Date.now(), expanded = false, width, showHint = false, maxLines) {
|
|
133
|
+
const full = workflowProgressLines(run, spinner, styles, now, expanded, width, showHint);
|
|
134
|
+
if (expanded || maxLines === undefined || full.length <= maxLines)
|
|
135
|
+
return full.join("\n");
|
|
136
|
+
// Finished phases collapse to a summary line; the current one keeps failed, active, and the latest settled agents, in that priority.
|
|
137
|
+
const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
|
|
138
|
+
const currentStart = Math.min(run.agents.length, phases.at(-1)?.afterAgent ?? 0);
|
|
139
|
+
const current = run.agents.slice(currentStart);
|
|
140
|
+
const recent = current.filter((agent) => SETTLED_AGENT_STATES.has(agent.state) && agent.state !== "failed").sort((left, right) => agentEnd(right) - agentEnd(left)).slice(0, COMPACT_RECENT_SETTLED);
|
|
141
|
+
const candidates = [...current.filter((agent) => agent.state === "failed"), ...current.filter((agent) => ACTIVE_AGENT_STATES.has(agent.state)), ...recent];
|
|
142
|
+
const render = (count) => workflowProgressLines(run, spinner, styles, now, false, width, showHint, new Set(candidates.slice(0, count)));
|
|
143
|
+
let low = 0;
|
|
144
|
+
let high = candidates.length;
|
|
145
|
+
while (low < high) {
|
|
146
|
+
const middle = Math.ceil((low + high) / 2);
|
|
147
|
+
if (render(middle).length <= maxLines)
|
|
148
|
+
low = middle;
|
|
149
|
+
else
|
|
150
|
+
high = middle - 1;
|
|
151
|
+
}
|
|
152
|
+
// NOTE: failures in finished phases always stay listed, so a run with more of them than maxLines still overflows.
|
|
153
|
+
return render(low).join("\n");
|
|
154
|
+
}
|
|
155
|
+
function workflowProgressLines(run, spinner, styles, now, expanded, width, showHint, shown) {
|
|
108
156
|
const done = run.agents.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length;
|
|
109
157
|
const workflowIcon = runStateGlyph(run.state, spinner);
|
|
110
158
|
const iconStyle = workflowIconStyle(run.state, styles);
|
|
@@ -120,14 +168,15 @@ export function formatWorkflowProgress(run, spinner = "◇", styles = PLAIN_WORK
|
|
|
120
168
|
if (shellActivity)
|
|
121
169
|
lines.push(` ${shellActivity}`);
|
|
122
170
|
const byId = new Map(run.agents.map((agent) => [agent.id, agent]));
|
|
123
|
-
const
|
|
171
|
+
const position = new Map(run.agents.map((agent, index) => [agent, index]));
|
|
172
|
+
const renderAgents = (agents, nested) => renderGroupedAgents(agents, ({ agent, depth }, grouped) => {
|
|
124
173
|
const icon = agentStateGlyph(agent.state, spinner);
|
|
125
174
|
const indent = " ".repeat((grouped ? 2 : 1) + depth);
|
|
126
175
|
const activity = SETTLED_AGENT_STATES.has(agent.state) ? "" : formatAgentActivity(agent, spinner, styles, now);
|
|
127
176
|
const name = grouped ? agent.label ?? agent.name : styledAgentBreadcrumb(agent, byId, styles);
|
|
128
177
|
const state = progressStyleForState(agent.state, styles);
|
|
129
178
|
const detail = expanded ? formatWorkflowAgentDetail(agent, now) : "";
|
|
130
|
-
return `${indent}#${String(
|
|
179
|
+
return `${indent}#${String((position.get(agent) ?? 0) + 1)} ${state(icon)} ${name} ${state(`[${agent.state}]`)}${activity ? ` ${activity}` : ""}${detail ? ` ${detail}` : ""}`;
|
|
131
180
|
}, run.agents, (label) => styles.muted(label)).map((line) => nested ? ` ${line}` : line);
|
|
132
181
|
const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
|
|
133
182
|
if (scopedShells) {
|
|
@@ -141,10 +190,25 @@ export function formatWorkflowProgress(run, spinner = "◇", styles = PLAIN_WORK
|
|
|
141
190
|
}
|
|
142
191
|
let renderedAgents = 0;
|
|
143
192
|
let nested = false;
|
|
193
|
+
// In compact mode (`shown` set) earlier segments list only their failures, and the current one only the shown agents plus a count of the rest.
|
|
194
|
+
const segment = (agents, closed) => {
|
|
195
|
+
if (!shown)
|
|
196
|
+
return renderAgents(agents, nested);
|
|
197
|
+
const listed = agents.filter((agent) => closed ? agent.state === "failed" : shown.has(agent));
|
|
198
|
+
const hidden = closed ? [] : agents.filter((agent) => !shown.has(agent));
|
|
199
|
+
const counts = [["queued", hidden.filter((agent) => !SETTLED_AGENT_STATES.has(agent.state) && !ACTIVE_AGENT_STATES.has(agent.state)).length], ["running", hidden.filter((agent) => ACTIVE_AGENT_STATES.has(agent.state)).length], ["done", hidden.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length]];
|
|
200
|
+
const summary = counts.filter(([, count]) => count > 0).map(([label, count]) => `+${String(count)} ${label}`).join(" · ");
|
|
201
|
+
return [...renderAgents(listed, nested), ...(summary ? [`${nested ? " " : " "}${styles.muted(`… ${summary}${showHint ? expandHint() : ""}`)}`] : [])];
|
|
202
|
+
};
|
|
144
203
|
for (const [phaseIndex, phase] of phases.entries()) {
|
|
145
204
|
const boundary = Math.max(renderedAgents, Math.min(run.agents.length, phase.afterAgent));
|
|
146
|
-
|
|
147
|
-
|
|
205
|
+
const before = run.agents.slice(renderedAgents, boundary);
|
|
206
|
+
if (shown && !nested && before.length)
|
|
207
|
+
lines.push(` ${phaseSummary(before, styles)}`);
|
|
208
|
+
lines.push(...segment(before, true));
|
|
209
|
+
const next = phases[phaseIndex + 1];
|
|
210
|
+
const phaseAgents = next ? run.agents.slice(boundary, Math.max(boundary, Math.min(run.agents.length, next.afterAgent))) : [];
|
|
211
|
+
lines.push(` ${styles.muted(`[Phase: ${phase.phase}]`)}${shown && next ? ` ${phaseSummary(phaseAgents, styles)}` : ""}`);
|
|
148
212
|
const phaseShell = scopedShells ? shellActivityFor(run, phaseIndex) : undefined;
|
|
149
213
|
if (phaseShell) {
|
|
150
214
|
const rendered = formatShellActivity(phaseShell.active, phaseShell.startedAt, spinner, styles, now);
|
|
@@ -154,9 +218,9 @@ export function formatWorkflowProgress(run, spinner = "◇", styles = PLAIN_WORK
|
|
|
154
218
|
renderedAgents = boundary;
|
|
155
219
|
nested = true;
|
|
156
220
|
}
|
|
157
|
-
lines.push(...
|
|
158
|
-
lines.push(...workflowLogLines(run, styles, expanded, width, showHint));
|
|
159
|
-
return lines
|
|
221
|
+
lines.push(...segment(run.agents.slice(renderedAgents), false));
|
|
222
|
+
lines.push(...workflowLogLines(run, styles, expanded, width, showHint, shown ? COMPACT_LOG_LINES : 5));
|
|
223
|
+
return lines;
|
|
160
224
|
}
|
|
161
225
|
const workflowSpinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
162
226
|
const WORKFLOW_PROGRESS_REFRESH_MS = 1_000;
|
|
@@ -418,7 +482,11 @@ export function workflowProgressBlock(run, theme, progress, refresh, invalidate,
|
|
|
418
482
|
}
|
|
419
483
|
previousState = displayed.state;
|
|
420
484
|
const frame = displayed.state === "running" ? workflowSpinner[Math.floor(now / 80) % workflowSpinner.length] ?? "◇" : "◇";
|
|
421
|
-
|
|
485
|
+
// pi-tui only patches rows that are still on screen; leave room below the block for the editor and footer.
|
|
486
|
+
// A finished or frozen block no longer changes, so it keeps the full list.
|
|
487
|
+
const rows = freezeAt === undefined && !terminal ? process.stdout.rows : undefined;
|
|
488
|
+
const maxLines = rows ? Math.max(6, rows - 10) - (prefix ? prefix.split("\n").length + 1 : 0) : undefined;
|
|
489
|
+
const progressText = formatWorkflowProgress(displayed, frame, styles, now, expanded, width, true, maxLines);
|
|
422
490
|
return truncateWorkflowProgress(prefix ? `${prefix}\n\n${progressText}` : progressText, width);
|
|
423
491
|
},
|
|
424
492
|
setExpanded(value) { expanded = value; },
|
|
@@ -588,7 +656,7 @@ function formatAccounting(accounting) {
|
|
|
588
656
|
const total = accounting.input + accounting.output + accounting.cacheRead + accounting.cacheWrite;
|
|
589
657
|
return `${formatAccountingValue(total)} tok`;
|
|
590
658
|
}
|
|
591
|
-
function formatAgentError(error, styles = PLAIN_WORKFLOW_PROGRESS_STYLES) {
|
|
659
|
+
export function formatAgentError(error, styles = PLAIN_WORKFLOW_PROGRESS_STYLES) {
|
|
592
660
|
return styles.error(`Error: ${error.code}: ${error.message}`);
|
|
593
661
|
}
|
|
594
662
|
export function formatAgentDetail(agent, styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now = Date.now(), options = {}) {
|
|
@@ -751,11 +819,7 @@ export function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {
|
|
|
751
819
|
const model = buildWorkflowPhaseModel(run, snapshot);
|
|
752
820
|
const tree = buildWorkflowPhaseTree(model);
|
|
753
821
|
const expanded = selection.expandedNodeIds === undefined ? workflowPhaseTreeInitialExpanded(tree) : new Set(selection.expandedNodeIds);
|
|
754
|
-
const wrap = (text
|
|
755
|
-
// ponytail: ANSI-only width, good enough for the ASCII labels the tree renders
|
|
756
|
-
const ansiPattern = new RegExp(ANSI_SGR_SOURCE, "g");
|
|
757
|
-
const visibleLength = (text) => text.replace(ansiPattern, "").length;
|
|
758
|
-
const padTo = (text, limit) => `${text}${" ".repeat(Math.max(0, limit - visibleLength(text)))}`;
|
|
822
|
+
const wrap = (text) => wrapNavigatorLine(text, safeWidth);
|
|
759
823
|
const phaseStyle = (state) => phaseStyleForState(state, styles);
|
|
760
824
|
const phase = selection.phaseId ? model.phases.find((candidate) => candidate.id === selection.phaseId) : undefined;
|
|
761
825
|
const selectedByAgent = selection.agentId ? tree.nodes.find((node) => node.kind === "agent" && node.agentId === selection.agentId && (!selection.phaseId || node.phaseId === selection.phaseId)) : undefined;
|
|
@@ -833,35 +897,38 @@ export function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {
|
|
|
833
897
|
const shellActivity = scopedShells ? undefined : formatShellActivity(run.activeShells, run.activeShellStartedAt, "⠦", styles, now);
|
|
834
898
|
if (shellActivity)
|
|
835
899
|
lines.push(` ${shellActivity}`);
|
|
836
|
-
const renderTree = (limit) => [styles.bold("Tree"), ...(visibleNodes.length ? visibleNodes.flatMap((node) => wrap(treeLine(node), limit)) : [styles.muted("(empty)")])];
|
|
837
900
|
const actionRows = () => {
|
|
838
901
|
const actions = selection.actions;
|
|
839
902
|
if (!actions)
|
|
840
903
|
return [];
|
|
841
904
|
return ["", styles.bold(actions.title), ...actions.options.map((option, index) => `${index === actions.index ? "→ " : " "}${index === actions.index ? styles.accent(option) : option}`)];
|
|
842
905
|
};
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
|
|
846
|
-
const detailWidth = Math.max(1, safeWidth - sidebarWidth - 3);
|
|
847
|
-
const sidebar = renderTree(sidebarWidth).flatMap((line) => wrap(line, sidebarWidth));
|
|
848
|
-
const detail = detailRows().flatMap((line) => wrap(line, detailWidth));
|
|
849
|
-
const rows = Math.max(sidebar.length, detail.length);
|
|
850
|
-
for (let index = 0; index < rows; index += 1)
|
|
851
|
-
lines.push(`${padTo(sidebar[index] ?? "", sidebarWidth)} | ${detail[index] ?? ""}`);
|
|
852
|
-
}
|
|
853
|
-
else if (selection.detailsOnly) {
|
|
854
|
-
lines.push(...detailRows().flatMap((line) => wrap(line)));
|
|
855
|
-
}
|
|
856
|
-
else {
|
|
857
|
-
lines.push(...renderTree(safeWidth));
|
|
858
|
-
if (!selection.treeOnly)
|
|
859
|
-
lines.push("", ...detailRows().flatMap((line) => wrap(line)));
|
|
860
|
-
}
|
|
906
|
+
const treeRows = [styles.bold("Tree"), ...(visibleNodes.length ? visibleNodes.map(treeLine) : [styles.muted("(empty)")])];
|
|
907
|
+
lines.push(...formatNavigatorColumns(treeRows, [...details(selectedNode), ...actionRows()], safeWidth, selection));
|
|
861
908
|
if (model.unassignedAgents?.length && !tree.nodes.some((node) => node.phaseId === "unassigned"))
|
|
862
909
|
lines.push(...wrap(styles.muted(`Unassigned agents: ${String(model.unassignedAgents.length)}`)));
|
|
863
910
|
return lines.flatMap((line) => wrap(line));
|
|
864
911
|
}
|
|
912
|
+
function wrapNavigatorLine(text, width) {
|
|
913
|
+
return truncateToVisualLines(text, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
|
|
914
|
+
}
|
|
915
|
+
/** The navigator layout: a sidebar beside details at 80 columns or wider, otherwise one of them (or both stacked). */
|
|
916
|
+
export function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
|
|
917
|
+
const safeWidth = Math.max(1, width);
|
|
918
|
+
if (safeWidth >= 80) {
|
|
919
|
+
const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
|
|
920
|
+
const left = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
|
|
921
|
+
const right = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
|
|
922
|
+
return Array.from({ length: Math.max(left.length, right.length) }, (_, index) => {
|
|
923
|
+
const cell = left[index] ?? "";
|
|
924
|
+
return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth(cell)))} | ${right[index] ?? ""}`;
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
if (layout.detailsOnly)
|
|
928
|
+
return detail.flatMap((line) => wrapNavigatorLine(line, safeWidth));
|
|
929
|
+
const left = sidebar.flatMap((line) => wrapNavigatorLine(line, safeWidth));
|
|
930
|
+
return layout.treeOnly ? left : [...left, ...detail.flatMap((line) => wrapNavigatorLine(line, safeWidth))];
|
|
931
|
+
}
|
|
865
932
|
export function formatCheckpointReview(checkpoint) {
|
|
866
933
|
const context = JSON.stringify(checkpoint.context, null, 2);
|
|
867
934
|
return [`Name: ${checkpoint.name}`, "Prompt:", checkpoint.prompt, context === "null" ? "Context: null" : "Context:", ...(context === "null" ? [] : [context])].join("\n");
|
package/dist/src/host.js
CHANGED
|
@@ -13,7 +13,8 @@ import { acquireSessionLease, isPersistedRun, listPersistedSessionIds, listRunId
|
|
|
13
13
|
import { retainTerminalRuns } from "./retention.js";
|
|
14
14
|
import { validateBudget, WorkflowBudgetRuntime } from "./budget.js";
|
|
15
15
|
import { SerialLane, asWorkflowError, createLaunchSnapshot, errorCode, errorText, fail, isNodeError, jsonValue, mergeWorkflowExtensionSettings, modelAliasErrorName, modelCapability, object, parseModelReference, positiveInteger, sanitizeDisplayText, validateModelAliases } from "./utils.js";
|
|
16
|
-
import {
|
|
16
|
+
import { loadSettings, preflight, resolveAgentResourcePolicy, resolveWorkflowSettings, validateCheckpoint, validateModelAliasAvailability, validateWorkflowLaunchWithRegistry, workflowProjectSettingsPath, workflowSettingsPath } from "./validation.js";
|
|
17
|
+
import { loadAgentDefinitions, loadProjectAgentDefinitions } from "./roles.js";
|
|
17
18
|
import { beginWorkflowExtensionLoading, loadingRegistry, resetWorkflowRegistryIfIdle, retainWorkflowRegistry } from "./registry.js";
|
|
18
19
|
import { agentHandleTurnPath, agentIdentityPath, agentWorktree, encoded, executeShellCommand, persistActiveAgentAttempt, persistAgentAttempts, readShellResult, runWorkflow, shellIdentityPath } from "./execution.js";
|
|
19
20
|
import backgroundWidget from "./background-widget.js";
|
|
@@ -22,7 +23,7 @@ import { createTrajectoryRunLoader, createTrajectoryRunMetadataLoader, createTra
|
|
|
22
23
|
import { getTrajectoryHost } from "./trajectory-host-handle.js";
|
|
23
24
|
import { getSubagentManager } from "./subagent-manager-handle.js";
|
|
24
25
|
import { HARD_TERMINAL_RUN_STATES, LAUNCH_SNAPSHOT_IDENTITY_VERSION, WORKFLOW_BLOCKED_EVENT, WorkflowError, isContextFileScope, isExternallyEndedRunState, isHardTerminalRunState, roleNameOf } from "./types.js";
|
|
25
|
-
import { SETTLED_AGENT_STATES, catalogResultValue, formatWorkflowCatalog, styledTextBlock, textBlock, workflowCatalogBlock, workflowControlCall, workflowControlResult, workflowProgressBlock,
|
|
26
|
+
import { SETTLED_AGENT_STATES, catalogResultValue, formatWorkflowCatalog, styledTextBlock, textBlock, workflowCatalogBlock, workflowControlCall, workflowControlResult, workflowProgressBlock, } from "./host-view.js";
|
|
26
27
|
import { DELIVERY_LIMIT_BYTES, ForegroundDeliveryController, markWorkflowFailureDiagnostics, WORKFLOW_LOG_ENTRY, completionDescriptor, completionDeliveryFromStore, createWorkflowFailureDiagnostics, failureDiagnosticsFrom, formatWorkflowFailure, formatWorkflowFailureDelivery, formatWorkflowFailureDeliveryFallback, formatWorkflowFailureDiagnostics, isWorkflowFailureDiagnostics, serializeWorkflowFailureDiagnostics, utf8Prefix, } from "./host-delivery.js";
|
|
27
28
|
export { agentBreadcrumb, agentBreadcrumbParts, formatBudgetStatus, formatNavigatorDashboard, formatNavigatorRun, formatStalledDuration, formatWorkflowPhaseDashboard, formatWorkflowProgress, navigatorAttentionSort, truncateWorkflowProgress, } from "./host-view.js";
|
|
28
29
|
export { buildWorkflowPhaseModel, buildWorkflowPhaseTree, navigateWorkflowPhaseTree, preserveWorkflowPhaseSelection, preserveWorkflowPhaseTreeSelection, workflowPhaseTreeInitialExpanded, workflowPhaseTreeVisibleNodes } from "./host-phases.js";
|
|
@@ -119,7 +120,9 @@ export const WORKFLOW_TOOL_PARAMETERS = Type.Object({
|
|
|
119
120
|
export const WORKFLOW_STATUS_PARAMETERS = Type.Object({ runId: Type.String({ description: "Workflow run ID visible in the current project" }) }, { additionalProperties: false });
|
|
120
121
|
export const WORKFLOW_RETRY_PARAMETERS = Type.Object({ runId: Type.String({ description: "Explicit failed workflow run ID" }), expectedState: Type.Optional(Type.String({ description: "Persisted source state observed before recovery" })), foreground: Type.Optional(Type.Boolean({ description: "Override the source launch mode for this recovery" })) });
|
|
121
122
|
function workflowToolUpdate(run) {
|
|
122
|
-
|
|
123
|
+
//NOTE: renderers read details.run; the partial text stays O(1) so high-frequency updates never format the whole tree.
|
|
124
|
+
const done = run.agents.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length;
|
|
125
|
+
return { content: [{ type: "text", text: `Workflow: ${run.workflowName} (${String(done)}/${String(run.agents.length)} done) [${run.state}]` }], details: { runId: run.id, run } };
|
|
123
126
|
}
|
|
124
127
|
function agentWithProgress(agent, progress) {
|
|
125
128
|
const next = { ...agent, accounting: progress.accounting, toolCalls: progress.toolCalls };
|
|
@@ -143,6 +146,7 @@ function deliver(pi, content) {
|
|
|
143
146
|
pi.sendMessage({ customType: "workflow", content, display: true }, { deliverAs: "followUp", triggerTurn: true });
|
|
144
147
|
}
|
|
145
148
|
const WORKFLOW_WARNING_ENTRY = "workflow-warning";
|
|
149
|
+
const PROGRESS_COALESCE_MS = 100;
|
|
146
150
|
function deliverWarning(pi, content) {
|
|
147
151
|
pi.appendEntry(WORKFLOW_WARNING_ENTRY, { message: content });
|
|
148
152
|
}
|
|
@@ -406,7 +410,6 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
|
|
|
406
410
|
const trusted = projectTrusted(context);
|
|
407
411
|
const settings = resolveWorkflowSettings(cwd, trusted, workflowSettingsPath(extensionAgentDir)).effective.extensionSettings?.trajectory;
|
|
408
412
|
const port = settings?.port;
|
|
409
|
-
const themes = settings?.themes ?? false;
|
|
410
413
|
const loadRuns = createTrajectoryRunLoader(cwd, sessionId, home, overlayLiveRun);
|
|
411
414
|
const loadSubagents = createTrajectorySubagentLoader(cwd, sessionId, extensionAgentDir, overlayLiveSubagent);
|
|
412
415
|
const loadRunsMetadata = createTrajectoryRunMetadataLoader(cwd, sessionId, home, overlayLiveRun);
|
|
@@ -415,7 +418,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
|
|
|
415
418
|
const live = liveSubagents.get(subagentId);
|
|
416
419
|
return live?.status.sessionId === sessionId ? live.status.attemptDetails?.at(-1)?.session : undefined;
|
|
417
420
|
});
|
|
418
|
-
return { cwd, sessionId, ...(port === undefined ? {} : { port }),
|
|
421
|
+
return { cwd, sessionId, ...(port === undefined ? {} : { port }), loadRuns, loadSubagents, loadMetadata: async () => ({ runs: await loadRunsMetadata(), subagents: await loadSubagentsMetadata() }), loadTranscript, handleAction: (request) => trajectoryAction(request, context) };
|
|
419
422
|
};
|
|
420
423
|
const withLiveActivities = (run) => liveAgents.overlay(run);
|
|
421
424
|
/** Overlays the in-memory lifecycle state and budget usage of an active run onto its persisted record. */
|
|
@@ -619,25 +622,54 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
|
|
|
619
622
|
const run = runs.get(runId);
|
|
620
623
|
if (!run)
|
|
621
624
|
throw new WorkflowError("INTERNAL_ERROR", `Unknown production run: ${runId}`);
|
|
625
|
+
const applyProgress = async (progress) => {
|
|
626
|
+
let runState;
|
|
627
|
+
if (progress.persist) {
|
|
628
|
+
runState = await persistRunState(run.store, run.metadata, (current) => current.agents.some((agent) => agent.id === id) ? { ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) } : current);
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
const loaded = await run.store.loadStatus();
|
|
632
|
+
if (!loaded.agents.some((agent) => agent.id === id))
|
|
633
|
+
return;
|
|
634
|
+
runState = { ...loaded, ...run.budget.snapshot(), agents: loaded.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) };
|
|
635
|
+
}
|
|
636
|
+
if (!runState.agents.some((agent) => agent.id === id))
|
|
637
|
+
return;
|
|
638
|
+
liveAgents.setActivity(runId, id, progress.activity);
|
|
639
|
+
liveAgents.setEventTime(runId, id, progress.lastEventAt);
|
|
640
|
+
run.update?.(workflowToolUpdate(withLiveActivities(runState)));
|
|
641
|
+
};
|
|
642
|
+
// Display-only progress bursts coalesce to one trailing apply per window; persisted progress always applies immediately.
|
|
643
|
+
let deferred;
|
|
644
|
+
let deferredTimer;
|
|
645
|
+
let appliedAt = 0;
|
|
646
|
+
const cancelDeferredProgress = () => {
|
|
647
|
+
if (deferredTimer)
|
|
648
|
+
clearTimeout(deferredTimer);
|
|
649
|
+
deferredTimer = undefined;
|
|
650
|
+
deferred = undefined;
|
|
651
|
+
};
|
|
652
|
+
const onProgress = async (progress) => {
|
|
653
|
+
const now = Date.now();
|
|
654
|
+
if (progress.persist || (!deferredTimer && now - appliedAt >= PROGRESS_COALESCE_MS)) {
|
|
655
|
+
cancelDeferredProgress();
|
|
656
|
+
appliedAt = now;
|
|
657
|
+
await applyProgress(progress);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
deferred = progress;
|
|
661
|
+
deferredTimer ??= setTimeout(() => {
|
|
662
|
+
deferredTimer = undefined;
|
|
663
|
+
const next = deferred;
|
|
664
|
+
deferred = undefined;
|
|
665
|
+
if (!next)
|
|
666
|
+
return;
|
|
667
|
+
appliedAt = Date.now();
|
|
668
|
+
void applyProgress(next).catch(() => undefined);
|
|
669
|
+
}, Math.max(0, PROGRESS_COALESCE_MS - (now - appliedAt))).unref();
|
|
670
|
+
};
|
|
622
671
|
try {
|
|
623
672
|
const budget = run.budget.forAgent(id);
|
|
624
|
-
const onProgress = async (progress) => {
|
|
625
|
-
let runState;
|
|
626
|
-
if (progress.persist) {
|
|
627
|
-
runState = await persistRunState(run.store, run.metadata, (current) => current.agents.some((agent) => agent.id === id) ? { ...current, ...run.budget.snapshot(), agents: current.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) } : current);
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
const loaded = await run.store.load();
|
|
631
|
-
if (!loaded.run.agents.some((agent) => agent.id === id))
|
|
632
|
-
return;
|
|
633
|
-
runState = { ...loaded.run, ...run.budget.snapshot(), agents: loaded.run.agents.map((agent) => agent.id === id ? agentWithProgress(agent, progress) : agent) };
|
|
634
|
-
}
|
|
635
|
-
if (!runState.agents.some((agent) => agent.id === id))
|
|
636
|
-
return;
|
|
637
|
-
liveAgents.setActivity(runId, id, progress.activity);
|
|
638
|
-
liveAgents.setEventTime(runId, id, progress.lastEventAt);
|
|
639
|
-
run.update?.(workflowToolUpdate(withLiveActivities(runState)));
|
|
640
|
-
};
|
|
641
673
|
const onAttempt = async (attempt) => {
|
|
642
674
|
liveAgents.setSession(runId, id, attempt.liveSession);
|
|
643
675
|
liveAgents.setHandoff(runId, id, attempt);
|
|
@@ -654,6 +686,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
|
|
|
654
686
|
run.update?.(workflowToolUpdate(withLiveActivities(persisted)));
|
|
655
687
|
};
|
|
656
688
|
const result = await run.executor.execute(prompt, { label: options.label, workflowName: run.metadata.name, tuiIndex, tuiLabel: options.requestedLabel ?? options.label, agentNodeId: id, ...(options.extensionSettings === undefined ? {} : { inheritedExtensionSettings: options.extensionSettings }), onProgress, onAttempt, budget, ...(run.providerErrorRecovery ? { providerErrorRecovery: run.providerErrorRecovery } : {}), ...(parentId ? { parent: parentId, cwd: options.cwd, ...(options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}) } : options.worktreeOwner ? { worktreeOwner: options.worktreeOwner } : {}), ...(options.model ? { model: options.model } : {}), ...(options.role ? { role: options.role } : {}), ...(options.contextFiles ? { contextFiles: options.contextFiles } : {}), tools: options.tools, ...(options.skills ? { skills: options.skills } : {}), ...(options.extensions ? { extensions: options.extensions } : {}), effectiveTools: options.tools, ...(options.schema ? { schema: options.schema } : {}), ...(options.retries === undefined ? {} : { retries: options.retries }), ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }), ...(options.sessionPath ? { sessionPath: options.sessionPath } : {}), ...(options.agentOptions ? { agentOptions: options.agentOptions } : {}), ...(options.agentIdentity ? { agentIdentity: options.agentIdentity } : {}) }, signal, scheduler.toolsFor(id, (role, tools, model, inheritedTools, skills, extensions) => run.executor.resolve({ label: "child", workflowName: run.metadata.name, ...(model ? { model } : {}), ...(role ? { role } : {}), ...(tools !== undefined ? { tools } : {}), ...(skills !== undefined ? { skills } : {}), ...(extensions !== undefined ? { extensions } : {}) }, inheritedTools).tools), setSteer, () => { scheduler.cancelChildren(id); scheduler.retry(id); });
|
|
689
|
+
cancelDeferredProgress();
|
|
657
690
|
const before = (await run.store.load()).run;
|
|
658
691
|
await persistAgentAttempts(run.store, id, result.attempts);
|
|
659
692
|
const completed = (await run.store.load()).run;
|
|
@@ -665,6 +698,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
|
|
|
665
698
|
return result.value;
|
|
666
699
|
}
|
|
667
700
|
catch (error) {
|
|
701
|
+
cancelDeferredProgress();
|
|
668
702
|
liveAgents.setSession(runId, id);
|
|
669
703
|
const attempts = getAgentAttempts(error);
|
|
670
704
|
if (attempts?.length) {
|
|
@@ -1484,8 +1518,7 @@ export default function workflowExtension(pi, home, clipboard = copyToClipboard,
|
|
|
1484
1518
|
state.workflowProgressComponent = workflowProgressBlock(currentProgress?.run ?? incoming, theme, currentProgress, async () => {
|
|
1485
1519
|
const active = runs.get(incoming.id);
|
|
1486
1520
|
const store = active?.store ?? new RunStore(incoming.cwd, incoming.sessionId, incoming.id, home);
|
|
1487
|
-
|
|
1488
|
-
return withLiveActivities(loaded.run);
|
|
1521
|
+
return withLiveActivities(await store.loadStatus());
|
|
1489
1522
|
}, () => { if (state.workflowProgress === currentProgress)
|
|
1490
1523
|
requestRender(); }, undefined, state.workflowProgressFrozenAt);
|
|
1491
1524
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -6,12 +6,14 @@ export * from "./utils.js";
|
|
|
6
6
|
export { canonicalPath, sameFilesystemPath } from "./paths.js";
|
|
7
7
|
export * from "./budget.js";
|
|
8
8
|
export * from "./validation.js";
|
|
9
|
+
export * from "./roles.js";
|
|
9
10
|
export * from "./registry.js";
|
|
10
11
|
export * from "./execution.js";
|
|
11
12
|
export * from "./host.js";
|
|
12
13
|
export * from "./workflow-artifacts.js";
|
|
13
14
|
export { formatCost } from "./background-widget.js";
|
|
14
|
-
export { agentActionLabels, formatAgentDetail, navigatorAttentionSortByState, themeWorkflowProgressStyles, visibleStandaloneAgentAttemptActions } from "./host-view.js";
|
|
15
|
+
export { agentActionLabels, formatAgentDetail, formatAgentError, formatNavigatorColumns, formatWorkflowRuntime, navigatorAttentionSortByState, PLAIN_WORKFLOW_PROGRESS_STYLES, progressStyleForState, runStateGlyph, themeWorkflowProgressStyles, visibleStandaloneAgentAttemptActions } from "./host-view.js";
|
|
16
|
+
export { createInlineConfirm, workflowKeyLabel, workflowKeyMatches } from "./host-navigator.js";
|
|
15
17
|
export type { AgentDetailPresentation, WorkflowProgressStyles } from "./host-view.js";
|
|
16
18
|
export { default } from "./host.js";
|
|
17
19
|
export { acquireSessionLease, hasLiveSessionLease, listPersistedSessionIds, projectSessionsDirectory, projectStorageKey, RunStore, runsDirectory, SessionLease, structuralPath } from "./persistence.js";
|