@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.19.1-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 +135 -0
- package/dist/src/agent-execution.js +114 -87
- package/dist/src/background-widget.d.ts +61 -0
- package/dist/src/background-widget.js +23 -16
- package/dist/src/decoders.js +28 -1
- package/dist/src/execution.d.ts +4 -2
- package/dist/src/execution.js +43 -2
- package/dist/src/host-delivery.js +1 -0
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-recovery.d.ts +5 -2
- package/dist/src/host-recovery.js +3 -3
- package/dist/src/host-view.d.ts +21 -1
- package/dist/src/host-view.js +138 -47
- package/dist/src/host.d.ts +2 -2
- package/dist/src/host.js +246 -60
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +1346 -622
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +2 -0
- package/dist/src/registry.js +3 -2
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +326 -0
- package/dist/src/script-tools.d.ts +27 -0
- package/dist/src/script-tools.js +87 -0
- package/dist/src/settings.d.ts +26 -0
- package/dist/src/settings.js +277 -0
- package/dist/src/trajectory.d.ts +39 -4
- package/dist/src/trajectory.js +82 -5
- package/dist/src/types.d.ts +30 -3
- package/dist/src/types.js +1 -1
- package/dist/src/utils.d.ts +21 -1
- package/dist/src/utils.js +40 -0
- package/dist/src/validation.d.ts +10 -17
- package/dist/src/validation.js +29 -458
- package/dist/starter/index.js +0 -2
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.d.ts +0 -13
- package/dist/starter/review-loop.js +0 -2
- package/dist/subagents/index.js +1344 -798
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +49 -29
- package/dist/subagents/src/contracts.js +18 -7
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +15 -7
- package/dist/subagents/src/manager.js +32 -13
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +24 -2
- package/dist/subagents/src/view.js +107 -80
- package/dist/trajectory/assets/index.html +370 -232
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +445 -277
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +370 -232
- 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 +151 -60
- package/dist/trajectory/src/server.js +138 -11
- package/dist/trajectory/src/server.js.map +2 -2
- package/package.json +7 -6
- package/skills/pi-extensible-workflows/SKILL.md +2 -0
- package/src/agent-execution.ts +110 -80
- package/src/background-widget.ts +26 -19
- package/src/decoders.ts +22 -1
- package/src/execution.ts +41 -4
- package/src/host-delivery.ts +1 -0
- package/src/host-navigator.ts +58 -14
- package/src/host-recovery.ts +8 -7
- package/src/host-view.ts +116 -42
- package/src/host.ts +223 -55
- package/src/index.ts +3 -1
- package/src/paths.ts +3 -0
- package/src/registry.ts +3 -2
- package/src/roles.ts +327 -0
- package/src/script-tools.ts +76 -0
- package/src/settings.ts +224 -0
- package/src/trajectory.ts +97 -9
- package/src/types.ts +12 -5
- package/src/utils.ts +36 -1
- package/src/validation.ts +30 -356
- package/starter/review-loop.ts +0 -2
- package/subagents/README.md +3 -3
- package/subagents/src/contracts.ts +22 -7
- package/subagents/src/index.ts +18 -7
- package/subagents/src/manager.ts +31 -13
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +108 -76
- package/trajectory/src/assets/index.html +370 -232
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +124 -47
- package/trajectory/src/server.ts +114 -14
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`;
|
|
@@ -74,6 +74,17 @@ function formatShellActivity(activeShells, startedAt, spinner, styles, now) {
|
|
|
74
74
|
const timing = started && startedAt !== undefined && !Number.isNaN(started.getTime()) ? ` ${styles.dim(`started=${started.toISOString()} elapsed=${formatWorkflowRuntime(Math.max(0, now - startedAt))}`)}` : "";
|
|
75
75
|
return `${styles.accent(spinner)} shell ${styles.accent("[running]")} ${styles.dim(`(${String(count)} active)`)}${timing}`;
|
|
76
76
|
}
|
|
77
|
+
function formatToolActivity(run, spinner, styles, now) {
|
|
78
|
+
const calls = run.activeTools ?? [];
|
|
79
|
+
if (!calls.length)
|
|
80
|
+
return undefined;
|
|
81
|
+
const counts = new Map();
|
|
82
|
+
for (const { name } of calls)
|
|
83
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
84
|
+
const names = [...counts].map(([name, count]) => count > 1 ? `${name} ×${String(count)}` : name).join(", ");
|
|
85
|
+
const elapsed = formatWorkflowRuntime(Math.max(0, now - Math.min(...calls.map(({ startedAt }) => startedAt))));
|
|
86
|
+
return `${styles.accent(spinner)} tools ${styles.accent("[running]")} ${sanitizeDisplayText(names)} ${styles.dim(`elapsed=${elapsed}`)}`;
|
|
87
|
+
}
|
|
77
88
|
function formatLogTimestamp(timestamp) {
|
|
78
89
|
if (timestamp === undefined || !Number.isFinite(timestamp))
|
|
79
90
|
return "--:--:--";
|
|
@@ -82,7 +93,7 @@ function formatLogTimestamp(timestamp) {
|
|
|
82
93
|
return "--:--:--";
|
|
83
94
|
return [date.getHours(), date.getMinutes(), date.getSeconds()].map((part) => String(part).padStart(2, "0")).join(":");
|
|
84
95
|
}
|
|
85
|
-
function workflowLogLines(run, styles, expanded, width, showHint = false) {
|
|
96
|
+
function workflowLogLines(run, styles, expanded, width, showHint = false, tail = 5) {
|
|
86
97
|
const events = (run.events ?? []).filter((event) => event.type === "log");
|
|
87
98
|
if (!events.length)
|
|
88
99
|
return [];
|
|
@@ -94,7 +105,7 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
|
|
|
94
105
|
const chunks = truncateToVisualLines(message, Number.MAX_SAFE_INTEGER, Math.max(1, width - 11), 0).visualLines;
|
|
95
106
|
return chunks.map((chunk, chunkIndex) => `${chunkIndex === 0 ? prefix : indent}${chunk}`);
|
|
96
107
|
}));
|
|
97
|
-
const visible = expanded ? visualLines : visualLines.slice(-
|
|
108
|
+
const visible = expanded ? visualLines : visualLines.slice(-tail);
|
|
98
109
|
let hint = "";
|
|
99
110
|
if (!expanded && showHint) {
|
|
100
111
|
try {
|
|
@@ -104,7 +115,55 @@ function workflowLogLines(run, styles, expanded, width, showHint = false) {
|
|
|
104
115
|
}
|
|
105
116
|
return [expanded ? ` ${styles.muted("Logs")}` : ` ${styles.muted(`Logs${hint}`)}`, ...visible];
|
|
106
117
|
}
|
|
107
|
-
|
|
118
|
+
const ACTIVE_AGENT_STATES = new Set(["running", "waiting_for_child", "retrying", "paused"]);
|
|
119
|
+
const COMPACT_RECENT_SETTLED = 3;
|
|
120
|
+
const COMPACT_LOG_LINES = 3;
|
|
121
|
+
function expandHint() { try {
|
|
122
|
+
return ` (${keyHint("app.tools.expand", "to expand")})`;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return "";
|
|
126
|
+
} }
|
|
127
|
+
function agentEnd(agent) { return (agent.startedAt ?? 0) + (agent.durationMs ?? 0); }
|
|
128
|
+
/** One line for a finished phase: outcome counts, wall time across its agents, tokens, and cost. */
|
|
129
|
+
function phaseSummary(agents, styles) {
|
|
130
|
+
const count = (state) => agents.filter((agent) => agent.state === state).length;
|
|
131
|
+
const starts = agents.map((agent) => agent.startedAt).filter((value) => value !== undefined);
|
|
132
|
+
const span = starts.length ? Math.max(...agents.map(agentEnd)) - Math.min(...starts) : 0;
|
|
133
|
+
const accounting = sumAccounting(agents.map((agent) => agent.accounting ?? zeroAccounting()));
|
|
134
|
+
const failed = count("failed");
|
|
135
|
+
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);
|
|
136
|
+
return styles.muted(parts.join(" · "));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Renders the run; with `maxLines`, a view taller than that limit switches to a compact one so the live block stays on screen.
|
|
140
|
+
* A block taller than the terminal pushes its changing header above the viewport, and pi-tui then clears and redraws the whole
|
|
141
|
+
* scrollback on every update, which snaps the reader back to the bottom.
|
|
142
|
+
*/
|
|
143
|
+
export function formatWorkflowProgress(run, spinner = "◇", styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now = Date.now(), expanded = false, width, showHint = false, maxLines) {
|
|
144
|
+
const full = workflowProgressLines(run, spinner, styles, now, expanded, width, showHint);
|
|
145
|
+
if (expanded || maxLines === undefined || full.length <= maxLines)
|
|
146
|
+
return full.join("\n");
|
|
147
|
+
// Finished phases collapse to a summary line; the current one keeps failed, active, and the latest settled agents, in that priority.
|
|
148
|
+
const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
|
|
149
|
+
const currentStart = Math.min(run.agents.length, phases.at(-1)?.afterAgent ?? 0);
|
|
150
|
+
const current = run.agents.slice(currentStart);
|
|
151
|
+
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);
|
|
152
|
+
const candidates = [...current.filter((agent) => agent.state === "failed"), ...current.filter((agent) => ACTIVE_AGENT_STATES.has(agent.state)), ...recent];
|
|
153
|
+
const render = (count) => workflowProgressLines(run, spinner, styles, now, false, width, showHint, new Set(candidates.slice(0, count)));
|
|
154
|
+
let low = 0;
|
|
155
|
+
let high = candidates.length;
|
|
156
|
+
while (low < high) {
|
|
157
|
+
const middle = Math.ceil((low + high) / 2);
|
|
158
|
+
if (render(middle).length <= maxLines)
|
|
159
|
+
low = middle;
|
|
160
|
+
else
|
|
161
|
+
high = middle - 1;
|
|
162
|
+
}
|
|
163
|
+
// NOTE: failures in finished phases always stay listed, so a run with more of them than maxLines still overflows.
|
|
164
|
+
return render(low).join("\n");
|
|
165
|
+
}
|
|
166
|
+
function workflowProgressLines(run, spinner, styles, now, expanded, width, showHint, shown) {
|
|
108
167
|
const done = run.agents.filter((agent) => SETTLED_AGENT_STATES.has(agent.state)).length;
|
|
109
168
|
const workflowIcon = runStateGlyph(run.state, spinner);
|
|
110
169
|
const iconStyle = workflowIconStyle(run.state, styles);
|
|
@@ -119,15 +178,20 @@ export function formatWorkflowProgress(run, spinner = "◇", styles = PLAIN_WORK
|
|
|
119
178
|
const shellActivity = scopedShells ? undefined : formatShellActivity(run.activeShells, run.activeShellStartedAt, spinner, styles, now);
|
|
120
179
|
if (shellActivity)
|
|
121
180
|
lines.push(` ${shellActivity}`);
|
|
181
|
+
const toolActivity = formatToolActivity(run, spinner, styles, now);
|
|
182
|
+
if (toolActivity)
|
|
183
|
+
lines.push(` ${toolActivity}`);
|
|
122
184
|
const byId = new Map(run.agents.map((agent) => [agent.id, agent]));
|
|
123
|
-
const
|
|
124
|
-
|
|
185
|
+
const position = new Map(run.agents.map((agent, index) => [agent, index]));
|
|
186
|
+
const renderAgents = (agents, nested) => renderGroupedAgents(agents, ({ agent, depth }, grouped) => {
|
|
187
|
+
const icon = agentStateGlyph(agent.state, "●");
|
|
125
188
|
const indent = " ".repeat((grouped ? 2 : 1) + depth);
|
|
126
|
-
const
|
|
189
|
+
const idleSpinner = agent.state === "running" && !agentActivityLabel(agent) ? styles.accent(spinner) : "";
|
|
190
|
+
const activity = SETTLED_AGENT_STATES.has(agent.state) ? "" : [idleSpinner, formatAgentActivity(agent, spinner, styles, now)].filter(Boolean).join(" ");
|
|
127
191
|
const name = grouped ? agent.label ?? agent.name : styledAgentBreadcrumb(agent, byId, styles);
|
|
128
192
|
const state = progressStyleForState(agent.state, styles);
|
|
129
193
|
const detail = expanded ? formatWorkflowAgentDetail(agent, now) : "";
|
|
130
|
-
return `${indent}#${String(
|
|
194
|
+
return `${indent}#${String((position.get(agent) ?? 0) + 1)} ${state(icon)} ${name} ${state(`[${agent.state}]`)}${activity ? ` ${activity}` : ""}${detail ? ` ${detail}` : ""}`;
|
|
131
195
|
}, run.agents, (label) => styles.muted(label)).map((line) => nested ? ` ${line}` : line);
|
|
132
196
|
const phases = run.phaseHistory?.length ? run.phaseHistory : run.phase ? [{ phase: run.phase, afterAgent: 0 }] : [];
|
|
133
197
|
if (scopedShells) {
|
|
@@ -141,10 +205,25 @@ export function formatWorkflowProgress(run, spinner = "◇", styles = PLAIN_WORK
|
|
|
141
205
|
}
|
|
142
206
|
let renderedAgents = 0;
|
|
143
207
|
let nested = false;
|
|
208
|
+
// 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.
|
|
209
|
+
const segment = (agents, closed) => {
|
|
210
|
+
if (!shown)
|
|
211
|
+
return renderAgents(agents, nested);
|
|
212
|
+
const listed = agents.filter((agent) => closed ? agent.state === "failed" : shown.has(agent));
|
|
213
|
+
const hidden = closed ? [] : agents.filter((agent) => !shown.has(agent));
|
|
214
|
+
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]];
|
|
215
|
+
const summary = counts.filter(([, count]) => count > 0).map(([label, count]) => `+${String(count)} ${label}`).join(" · ");
|
|
216
|
+
return [...renderAgents(listed, nested), ...(summary ? [`${nested ? " " : " "}${styles.muted(`… ${summary}${showHint ? expandHint() : ""}`)}`] : [])];
|
|
217
|
+
};
|
|
144
218
|
for (const [phaseIndex, phase] of phases.entries()) {
|
|
145
219
|
const boundary = Math.max(renderedAgents, Math.min(run.agents.length, phase.afterAgent));
|
|
146
|
-
|
|
147
|
-
|
|
220
|
+
const before = run.agents.slice(renderedAgents, boundary);
|
|
221
|
+
if (shown && !nested && before.length)
|
|
222
|
+
lines.push(` ${phaseSummary(before, styles)}`);
|
|
223
|
+
lines.push(...segment(before, true));
|
|
224
|
+
const next = phases[phaseIndex + 1];
|
|
225
|
+
const phaseAgents = next ? run.agents.slice(boundary, Math.max(boundary, Math.min(run.agents.length, next.afterAgent))) : [];
|
|
226
|
+
lines.push(` ${styles.muted(`[Phase: ${phase.phase}]`)}${shown && next ? ` ${phaseSummary(phaseAgents, styles)}` : ""}`);
|
|
148
227
|
const phaseShell = scopedShells ? shellActivityFor(run, phaseIndex) : undefined;
|
|
149
228
|
if (phaseShell) {
|
|
150
229
|
const rendered = formatShellActivity(phaseShell.active, phaseShell.startedAt, spinner, styles, now);
|
|
@@ -154,9 +233,9 @@ export function formatWorkflowProgress(run, spinner = "◇", styles = PLAIN_WORK
|
|
|
154
233
|
renderedAgents = boundary;
|
|
155
234
|
nested = true;
|
|
156
235
|
}
|
|
157
|
-
lines.push(...
|
|
158
|
-
lines.push(...workflowLogLines(run, styles, expanded, width, showHint));
|
|
159
|
-
return lines
|
|
236
|
+
lines.push(...segment(run.agents.slice(renderedAgents), false));
|
|
237
|
+
lines.push(...workflowLogLines(run, styles, expanded, width, showHint, shown ? COMPACT_LOG_LINES : 5));
|
|
238
|
+
return lines;
|
|
160
239
|
}
|
|
161
240
|
const workflowSpinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
162
241
|
const WORKFLOW_PROGRESS_REFRESH_MS = 1_000;
|
|
@@ -418,14 +497,18 @@ export function workflowProgressBlock(run, theme, progress, refresh, invalidate,
|
|
|
418
497
|
}
|
|
419
498
|
previousState = displayed.state;
|
|
420
499
|
const frame = displayed.state === "running" ? workflowSpinner[Math.floor(now / 80) % workflowSpinner.length] ?? "◇" : "◇";
|
|
421
|
-
|
|
500
|
+
// pi-tui only patches rows that are still on screen; leave room below the block for the editor and footer.
|
|
501
|
+
// A finished or frozen block no longer changes, so it keeps the full list.
|
|
502
|
+
const rows = freezeAt === undefined && !terminal ? process.stdout.rows : undefined;
|
|
503
|
+
const maxLines = rows ? Math.max(6, rows - 10) - (prefix ? prefix.split("\n").length + 1 : 0) : undefined;
|
|
504
|
+
const progressText = formatWorkflowProgress(displayed, frame, styles, now, expanded, width, true, maxLines);
|
|
422
505
|
return truncateWorkflowProgress(prefix ? `${prefix}\n\n${progressText}` : progressText, width);
|
|
423
506
|
},
|
|
424
507
|
setExpanded(value) { expanded = value; },
|
|
425
508
|
invalidate() {
|
|
426
509
|
const displayed = currentRun();
|
|
427
510
|
const now = Date.now();
|
|
428
|
-
if (!progress || !refresh || displayed.state !== "running" || (!displayed.agents.some((agent) => agent.state === "running") && (displayed.activeShells ?? 0) <= 0))
|
|
511
|
+
if (!progress || !refresh || displayed.state !== "running" || (!displayed.agents.some((agent) => agent.state === "running") && (displayed.activeShells ?? 0) <= 0 && !displayed.activeTools?.length))
|
|
429
512
|
return;
|
|
430
513
|
if (progress.refresh || now - progress.lastRefreshAt < WORKFLOW_PROGRESS_REFRESH_MS)
|
|
431
514
|
return;
|
|
@@ -538,7 +621,7 @@ export function formatStalledDuration(durationMs) {
|
|
|
538
621
|
const remainingMinutes = minutes % 60;
|
|
539
622
|
return `${String(hours)}h${remainingMinutes ? ` ${String(remainingMinutes)}m` : ""}`;
|
|
540
623
|
}
|
|
541
|
-
function stalledDuration(agent, now) {
|
|
624
|
+
export function stalledDuration(agent, now) {
|
|
542
625
|
if (agent.state !== "running" || agent.lastEventAt === undefined || !Number.isFinite(agent.lastEventAt))
|
|
543
626
|
return undefined;
|
|
544
627
|
const duration = now - agent.lastEventAt;
|
|
@@ -550,7 +633,7 @@ function elapsedDurationMs(agent, end) {
|
|
|
550
633
|
return Math.max(0, agent.durationMs);
|
|
551
634
|
return agent.startedAt === undefined || !Number.isFinite(agent.startedAt) ? undefined : Math.max(0, end - agent.startedAt);
|
|
552
635
|
}
|
|
553
|
-
function agentActivityLabel(agent) {
|
|
636
|
+
export function agentActivityLabel(agent) {
|
|
554
637
|
const activity = agent.activity;
|
|
555
638
|
if (activity?.kind === "reasoning" || activity?.kind === "text") {
|
|
556
639
|
const name = activity.kind === "reasoning" ? "reasoning" : "responding";
|
|
@@ -588,7 +671,7 @@ function formatAccounting(accounting) {
|
|
|
588
671
|
const total = accounting.input + accounting.output + accounting.cacheRead + accounting.cacheWrite;
|
|
589
672
|
return `${formatAccountingValue(total)} tok`;
|
|
590
673
|
}
|
|
591
|
-
function formatAgentError(error, styles = PLAIN_WORKFLOW_PROGRESS_STYLES) {
|
|
674
|
+
export function formatAgentError(error, styles = PLAIN_WORKFLOW_PROGRESS_STYLES) {
|
|
592
675
|
return styles.error(`Error: ${error.code}: ${error.message}`);
|
|
593
676
|
}
|
|
594
677
|
export function formatAgentDetail(agent, styles = PLAIN_WORKFLOW_PROGRESS_STYLES, now = Date.now(), options = {}) {
|
|
@@ -663,6 +746,9 @@ export function formatNavigatorDashboard(run, checkpoints, worktrees, now = Date
|
|
|
663
746
|
const shellActivity = formatShellActivity(run.activeShells, run.activeShellStartedAt, "⠦", PLAIN_WORKFLOW_PROGRESS_STYLES, now);
|
|
664
747
|
if (shellActivity)
|
|
665
748
|
lines.push(` ${shellActivity}`);
|
|
749
|
+
const toolActivity = formatToolActivity(run, "⠦", PLAIN_WORKFLOW_PROGRESS_STYLES, now);
|
|
750
|
+
if (toolActivity)
|
|
751
|
+
lines.push(` ${toolActivity}`);
|
|
666
752
|
if (run.error)
|
|
667
753
|
lines.push(`Error: ${run.error.code}: ${run.error.message}`);
|
|
668
754
|
if (run.events?.length)
|
|
@@ -708,6 +794,9 @@ export function formatNavigatorRun(loaded, checkpoints, worktrees, now = Date.no
|
|
|
708
794
|
const shellActivity = formatShellActivity(run.activeShells, run.activeShellStartedAt, "⠦", PLAIN_WORKFLOW_PROGRESS_STYLES, now);
|
|
709
795
|
if (shellActivity)
|
|
710
796
|
lines.push(` ${shellActivity}`);
|
|
797
|
+
const toolActivity = formatToolActivity(run, "⠦", PLAIN_WORKFLOW_PROGRESS_STYLES, now);
|
|
798
|
+
if (toolActivity)
|
|
799
|
+
lines.push(` ${toolActivity}`);
|
|
711
800
|
if (run.error)
|
|
712
801
|
lines.push(`Run error: ${run.error.code}: ${run.error.message}`);
|
|
713
802
|
if (run.events?.length)
|
|
@@ -751,11 +840,7 @@ export function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {
|
|
|
751
840
|
const model = buildWorkflowPhaseModel(run, snapshot);
|
|
752
841
|
const tree = buildWorkflowPhaseTree(model);
|
|
753
842
|
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)))}`;
|
|
843
|
+
const wrap = (text) => wrapNavigatorLine(text, safeWidth);
|
|
759
844
|
const phaseStyle = (state) => phaseStyleForState(state, styles);
|
|
760
845
|
const phase = selection.phaseId ? model.phases.find((candidate) => candidate.id === selection.phaseId) : undefined;
|
|
761
846
|
const selectedByAgent = selection.agentId ? tree.nodes.find((node) => node.kind === "agent" && node.agentId === selection.agentId && (!selection.phaseId || node.phaseId === selection.phaseId)) : undefined;
|
|
@@ -833,35 +918,41 @@ export function formatWorkflowPhaseDashboard(run, snapshot, width, selection = {
|
|
|
833
918
|
const shellActivity = scopedShells ? undefined : formatShellActivity(run.activeShells, run.activeShellStartedAt, "⠦", styles, now);
|
|
834
919
|
if (shellActivity)
|
|
835
920
|
lines.push(` ${shellActivity}`);
|
|
836
|
-
const
|
|
921
|
+
const toolActivity = formatToolActivity(run, "⠦", styles, now);
|
|
922
|
+
if (toolActivity)
|
|
923
|
+
lines.push(` ${toolActivity}`);
|
|
837
924
|
const actionRows = () => {
|
|
838
925
|
const actions = selection.actions;
|
|
839
926
|
if (!actions)
|
|
840
927
|
return [];
|
|
841
928
|
return ["", styles.bold(actions.title), ...actions.options.map((option, index) => `${index === actions.index ? "→ " : " "}${index === actions.index ? styles.accent(option) : option}`)];
|
|
842
929
|
};
|
|
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
|
-
}
|
|
930
|
+
const treeRows = [styles.bold("Tree"), ...(visibleNodes.length ? visibleNodes.map(treeLine) : [styles.muted("(empty)")])];
|
|
931
|
+
lines.push(...formatNavigatorColumns(treeRows, [...details(selectedNode), ...actionRows()], safeWidth, selection));
|
|
861
932
|
if (model.unassignedAgents?.length && !tree.nodes.some((node) => node.phaseId === "unassigned"))
|
|
862
933
|
lines.push(...wrap(styles.muted(`Unassigned agents: ${String(model.unassignedAgents.length)}`)));
|
|
863
934
|
return lines.flatMap((line) => wrap(line));
|
|
864
935
|
}
|
|
936
|
+
function wrapNavigatorLine(text, width) {
|
|
937
|
+
return truncateToVisualLines(text, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
|
|
938
|
+
}
|
|
939
|
+
/** The navigator layout: a sidebar beside details at 80 columns or wider, otherwise one of them (or both stacked). */
|
|
940
|
+
export function formatNavigatorColumns(sidebar, detail, width, layout = {}) {
|
|
941
|
+
const safeWidth = Math.max(1, width);
|
|
942
|
+
if (safeWidth >= 80) {
|
|
943
|
+
const sidebarWidth = Math.min(42, Math.max(24, Math.floor((safeWidth - 3) * 0.38)));
|
|
944
|
+
const left = sidebar.flatMap((line) => wrapNavigatorLine(line, sidebarWidth));
|
|
945
|
+
const right = detail.flatMap((line) => wrapNavigatorLine(line, safeWidth - sidebarWidth - 3));
|
|
946
|
+
return Array.from({ length: Math.max(left.length, right.length) }, (_, index) => {
|
|
947
|
+
const cell = left[index] ?? "";
|
|
948
|
+
return `${cell}${" ".repeat(Math.max(0, sidebarWidth - visibleWidth(cell)))} | ${right[index] ?? ""}`;
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
if (layout.detailsOnly)
|
|
952
|
+
return detail.flatMap((line) => wrapNavigatorLine(line, safeWidth));
|
|
953
|
+
const left = sidebar.flatMap((line) => wrapNavigatorLine(line, safeWidth));
|
|
954
|
+
return layout.treeOnly ? left : [...left, ...detail.flatMap((line) => wrapNavigatorLine(line, safeWidth))];
|
|
955
|
+
}
|
|
865
956
|
export function formatCheckpointReview(checkpoint) {
|
|
866
957
|
const context = JSON.stringify(checkpoint.context, null, 2);
|
|
867
958
|
return [`Name: ${checkpoint.name}`, "Prompt:", checkpoint.prompt, context === "null" ? "Context: null" : "Context:", ...(context === "null" ? [] : [context])].join("\n");
|
package/dist/src/host.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Type } from "@earendil-works/pi-ai";
|
|
|
2
2
|
import { copyToClipboard, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { type BackgroundWidgetAPI } from "./background-widget.js";
|
|
4
4
|
import { type AgentTransport } from "./types.js";
|
|
5
|
-
export type WorkflowExtensionAPI = Pick<ExtensionAPI, "appendEntry" | "getActiveTools" | "getThinkingLevel" | "on" | "registerCommand" | "registerTool" | "sendMessage"> & Pick<BackgroundWidgetAPI, "events" | "registerEntryRenderer" | "registerShortcut">;
|
|
5
|
+
export type WorkflowExtensionAPI = Pick<ExtensionAPI, "appendEntry" | "getActiveTools" | "getThinkingLevel" | "on" | "registerCommand" | "registerTool" | "sendMessage"> & Partial<Pick<ExtensionAPI, "getAllTools">> & Pick<BackgroundWidgetAPI, "events" | "registerEntryRenderer" | "registerShortcut">;
|
|
6
6
|
export { agentBreadcrumb, agentBreadcrumbParts, formatBudgetStatus, formatNavigatorDashboard, formatNavigatorRun, formatStalledDuration, formatWorkflowPhaseDashboard, formatWorkflowProgress, navigatorAttentionSort, truncateWorkflowProgress, } from "./host-view.js";
|
|
7
7
|
export { buildWorkflowPhaseModel, buildWorkflowPhaseTree, navigateWorkflowPhaseTree, preserveWorkflowPhaseSelection, preserveWorkflowPhaseTreeSelection, workflowPhaseTreeInitialExpanded, workflowPhaseTreeVisibleNodes } from "./host-phases.js";
|
|
8
8
|
export type { WorkflowPhaseAgentCounts, WorkflowPhaseModel, WorkflowPhaseSelection, WorkflowPhaseState, WorkflowPhaseTree, WorkflowPhaseTreeDirection, WorkflowPhaseTreeNode, WorkflowPhaseTreeNodeKind, WorkflowPhaseTreeSelection, WorkflowPhaseView, } from "./host-phases.js";
|
|
@@ -16,7 +16,7 @@ export declare function formatWorkflowPreview(args: {
|
|
|
16
16
|
description?: unknown;
|
|
17
17
|
}): string;
|
|
18
18
|
export declare const WORKFLOW_TOOL_LABEL = "Workflow";
|
|
19
|
-
export declare const WORKFLOW_TOOL_DESCRIPTION = "Run a deterministic JavaScript workflow with a named inline or file-backed parallel-to-summary path by default";
|
|
19
|
+
export declare const WORKFLOW_TOOL_DESCRIPTION = "Run a deterministic JavaScript workflow with a named inline or file-backed parallel-to-summary path by default. Scripts call this session's tools as `await tools.<name>(args)`, outside withWorktree: a tool resolves to its text unless its description gives a `tools.<name>(args)` result shape, and failures reject";
|
|
20
20
|
export declare const WORKFLOW_TOOL_PROMPT_SNIPPET = "Run a deterministic, resumable JavaScript workflow. Prefer a named inline script that fans out independent work with parallel(...), awaits the keyed results before interpolating them into one summarizing agent(...), and returns. Provide exactly one of script or scriptPath and a non-empty name. Registered catalog functions are available as globals inside the script; call them there, for example return await someFunction(args). Advanced controls include registered functions, outputSchema, budgets, checkpoints, worktrees, retry/resume, CLI export, and pipelines. Runs are in the background by default; completion arrives as a follow-up message. Set foreground: true when the caller must wait for the final value. Manage runs from the interactive /workflow picker; use workflow_status, workflow_resume, workflow_retry, workflow_stop, and workflow_respond for explicit tool controls. If a foreground call detaches before its result is accepted, its terminal success or failure is promoted to one follow-up message. Foreground results include the completed run ID. Recovery inherits the source launch mode; legacy snapshots without launchMode recover in the background. Set foreground: true or false on workflow_resume/workflow_retry to override it; foreground recovery waits for terminal value and run details, while background recovery returns immediately and delivers completion or failure as a follow-up. After failure follow-ups, especially CANCELLED or interrupted runs, call workflow_status({ runId }) before recovery or replacement work, then pass its state as expectedState to workflow_retry/workflow_resume so recovery cannot act on a state that changed. Recovery map: agent(..., { retries }) reruns one agent call in the same run for transient failures; workflow_retry({ runId, expectedState?, foreground? }) replays a failed run into a child; workflow_resume({ runId, expectedState?, budget?, foreground? }) continues a budget_exhausted run; parentRunId on a new launch only borrows named worktrees and never replays or resumes.";
|
|
21
21
|
export declare const WORKFLOW_TOOL_PARAMETERS: Type.TObject<{
|
|
22
22
|
name: Type.TString;
|