pi-subagents 0.80.0 → 0.82.0
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 +66 -0
- package/bundle/agents.js +18 -0
- package/bundle/background-work.js +16 -0
- package/bundle/capability-ceiling.js +22 -0
- package/bundle/child-tool-plan.js +11 -0
- package/bundle/chunk-43SCHIMD.js +57 -0
- package/bundle/chunk-4QFZZV3O.js +57 -0
- package/bundle/chunk-4SDU2BZX.js +1190 -0
- package/bundle/chunk-52UBALJL.js +753 -0
- package/bundle/chunk-5LFLVSTM.js +260 -0
- package/bundle/chunk-5TYMSZX3.js +235 -0
- package/bundle/chunk-62OCY2S4.js +14 -0
- package/bundle/chunk-7F6RKT6W.js +132 -0
- package/bundle/chunk-7IVSUMQQ.js +1337 -0
- package/bundle/chunk-7J4ZXMY7.js +18 -0
- package/bundle/chunk-7PCKZHR5.js +1234 -0
- package/bundle/chunk-7YMXOYEU.js +1373 -0
- package/bundle/chunk-B5EKXDHE.js +16870 -0
- package/bundle/chunk-CIZQXJ5Q.js +8619 -0
- package/bundle/chunk-CTPAE36Y.js +94 -0
- package/bundle/chunk-D4EHT5SJ.js +14 -0
- package/bundle/chunk-GC4DSPMH.js +946 -0
- package/bundle/chunk-GJECHSAR.js +179 -0
- package/bundle/chunk-M2CTFYT3.js +4164 -0
- package/bundle/chunk-MHZ4G23A.js +1296 -0
- package/bundle/chunk-N2BN7BF5.js +159 -0
- package/bundle/chunk-NF7IEKBH.js +490 -0
- package/bundle/chunk-NGGAFDL6.js +742 -0
- package/bundle/chunk-NIXWDZZV.js +9 -0
- package/bundle/chunk-NRQ75MGH.js +8327 -0
- package/bundle/chunk-OYF6AFDY.js +458 -0
- package/bundle/chunk-PLCCBLPY.js +526 -0
- package/bundle/chunk-RMQFMMAT.js +416 -0
- package/bundle/chunk-SIJ4S3EN.js +276 -0
- package/bundle/chunk-UKG4V45O.js +228 -0
- package/bundle/chunk-VH5OAWVG.js +4096 -0
- package/bundle/chunk-W2SZ22EM.js +5462 -0
- package/bundle/chunk-WVD4UEL4.js +9367 -0
- package/bundle/chunk-XSWOTIA3.js +175 -0
- package/bundle/chunk-YFCK7H2S.js +129 -0
- package/bundle/chunk-YKEWIB4M.js +380 -0
- package/bundle/chunk-YYREO77V.js +137 -0
- package/bundle/chunk-Z7KVHQHL.js +173 -0
- package/bundle/chunk-ZLINIYTP.js +8613 -0
- package/bundle/chunk-ZQGSDHL6.js +128 -0
- package/bundle/control-channel.js +11 -0
- package/bundle/delegation.js +14 -0
- package/bundle/external-job-provider.js +20 -0
- package/bundle/external-runs.js +21 -0
- package/bundle/index.js +30 -0
- package/bundle/inspectors.js +8 -0
- package/bundle/intercom-bridge.js +11 -0
- package/bundle/preflight.js +425 -0
- package/bundle/project-panes.js +28 -0
- package/bundle/required-child-extensions.js +6 -0
- package/bundle/shared-types.js +8 -0
- package/bundle/workflow-resources.js +7 -0
- package/docs/agents.md +31 -0
- package/docs/configuration.md +9 -1
- package/docs/extension-api.md +27 -0
- package/docs/observability.md +6 -2
- package/docs/tool-reference.md +3 -1
- package/docs/workflows.md +16 -0
- package/package.json +19 -19
- package/skills/pi-subagents/references/execution-controls.md +5 -4
- package/src/agents/agent-management.js +3 -3
- package/src/agents/agent-memory.js +3 -1
- package/src/agents/agents.js +6 -6
- package/src/agents/frontmatter.d.ts +4 -0
- package/src/agents/frontmatter.js +24 -3
- package/src/agents/skills.js +2 -1
- package/src/api/delegation.d.ts +20 -0
- package/src/extension/config.js +3 -0
- package/src/extension/herdr-pi-bridge.js +17 -10
- package/src/extension/index.js +109 -9
- package/src/extension/schemas.js +3 -3
- package/src/intercom/native-supervisor-channel.js +20 -0
- package/src/missions/lifecycle.js +7 -1
- package/src/runs/background/active-async-capacity.d.ts +1 -0
- package/src/runs/background/active-async-capacity.js +9 -3
- package/src/runs/background/async-execution.js +20 -10
- package/src/runs/background/async-job-tracker.d.ts +6 -1
- package/src/runs/background/async-job-tracker.js +9 -4
- package/src/runs/background/async-status.d.ts +2 -0
- package/src/runs/background/async-status.js +21 -2
- package/src/runs/background/await-async-run.js +3 -7
- package/src/runs/background/boot-id.d.ts +12 -0
- package/src/runs/background/boot-id.js +47 -0
- package/src/runs/background/interrupted-elsewhere.d.ts +22 -0
- package/src/runs/background/interrupted-elsewhere.js +68 -0
- package/src/runs/background/notify.d.ts +2 -0
- package/src/runs/background/notify.js +13 -0
- package/src/runs/background/result-delivery-ownership.d.ts +6 -0
- package/src/runs/background/result-delivery-ownership.js +19 -1
- package/src/runs/background/result-watcher.d.ts +3 -1
- package/src/runs/background/result-watcher.js +24 -8
- package/src/runs/background/run-child-session.d.ts +1 -6
- package/src/runs/background/run-child-session.js +19 -1
- package/src/runs/background/run-status.js +2 -1
- package/src/runs/background/stale-run-reconciler.d.ts +25 -0
- package/src/runs/background/stale-run-reconciler.js +54 -13
- package/src/runs/background/subagent-runner-bootstrap.js +3 -0
- package/src/runs/background/subagent-runner.d.ts +4 -0
- package/src/runs/background/subagent-runner.js +13 -5
- package/src/runs/background/subagent-wait.d.ts +1 -1
- package/src/runs/background/subagent-wait.js +6 -1
- package/src/runs/background/terminal-run-index.d.ts +1 -0
- package/src/runs/background/terminal-run-index.js +4 -2
- package/src/runs/foreground/async-stop-action.js +3 -1
- package/src/runs/foreground/execution.js +45 -5
- package/src/runs/foreground/foreground-history.d.ts +16 -2
- package/src/runs/foreground/foreground-history.js +133 -31
- package/src/runs/foreground/subagent-executor.d.ts +11 -0
- package/src/runs/foreground/subagent-executor.js +277 -74
- package/src/runs/shared/child-launch.d.ts +2 -2
- package/src/runs/shared/child-session.d.ts +3 -2
- package/src/runs/shared/child-session.js +51 -8
- package/src/runs/shared/child-tool-plan.d.ts +7 -0
- package/src/runs/shared/child-tool-plan.js +21 -6
- package/src/runs/shared/herdr-connection.d.ts +17 -8
- package/src/runs/shared/herdr-connection.js +19 -7
- package/src/runs/shared/herdr-external-adapters.d.ts +3 -2
- package/src/runs/shared/herdr-external-adapters.js +1 -1
- package/src/runs/shared/herdr-machine.d.ts +12 -2
- package/src/runs/shared/herdr-machine.js +55 -9
- package/src/runs/shared/herdr-pane-host.d.ts +88 -0
- package/src/runs/shared/herdr-pane-host.js +155 -0
- package/src/runs/shared/herdr-placed-run.d.ts +27 -150
- package/src/runs/shared/herdr-placed-run.js +48 -395
- package/src/runs/shared/local-machine-access.d.ts +5 -0
- package/src/runs/shared/local-machine-access.js +28 -0
- package/src/runs/shared/orca-progress-tabs.d.ts +0 -1
- package/src/runs/shared/orca-progress-tabs.js +1 -24
- package/src/runs/shared/parallel-utils.d.ts +1 -1
- package/src/runs/shared/placed-run-owner.d.ts +49 -0
- package/src/runs/shared/placed-run-owner.js +93 -0
- package/src/runs/shared/placement-host.d.ts +99 -0
- package/src/runs/shared/placement-host.js +157 -0
- package/src/runs/shared/rex-machine.d.ts +25 -0
- package/src/runs/shared/rex-machine.js +83 -0
- package/src/runs/shared/rex-pane-host.d.ts +62 -0
- package/src/runs/shared/rex-pane-host.js +327 -0
- package/src/runs/shared/structured-output.d.ts +1 -0
- package/src/runs/shared/structured-output.js +1 -0
- package/src/shared/accessible-dir.js +4 -1
- package/src/shared/completion-owner.d.ts +5 -0
- package/src/shared/completion-owner.js +5 -0
- package/src/shared/provider-diagnostics.d.ts +9 -0
- package/src/shared/provider-diagnostics.js +75 -0
- package/src/shared/session-identity.d.ts +2 -0
- package/src/shared/session-identity.js +26 -0
- package/src/shared/types.d.ts +48 -6
- package/src/slash/delegation-adapters.d.ts +3 -1
- package/src/slash/delegation-adapters.js +7 -0
- package/src/slash/prompt-template-bridge.js +3 -1
- package/src/slash/subagents-admin.d.ts +1 -0
- package/src/slash/subagents-admin.js +1 -1
- package/src/tui/fleet-layout.d.ts +20 -0
- package/src/tui/fleet-layout.js +17 -0
- package/src/tui/fleet-viewport.d.ts +12 -0
- package/src/tui/fleet-viewport.js +35 -0
- package/src/tui/fleet.d.ts +6 -6
- package/src/tui/fleet.js +93 -66
- package/src/tui/render.d.ts +6 -0
- package/src/tui/render.js +3 -3
- package/src/tui/subagent-messages.js +1 -0
- package/src/workflows/scripted-workflow.d.ts +1 -1
- package/src/workflows/structured-workflow-scripts.d.ts +1 -0
- package/src/workflows/structured-workflow-scripts.js +5 -4
- package/src/workflows/workflow-auto-resume.d.ts +32 -0
- package/src/workflows/workflow-auto-resume.js +102 -0
- package/src/workflows/workflow-launch-record.d.ts +27 -0
- package/src/workflows/workflow-launch-record.js +48 -0
- package/src/workflows/workflow-resources.js +2 -2
- package/src/workflows/workflow-resume-claim.d.ts +82 -0
- package/src/workflows/workflow-resume-claim.js +210 -0
- package/src/workflows/workflow-resume.d.ts +56 -0
- package/src/workflows/workflow-resume.js +106 -0
- package/src/workflows/workflow-reuse.d.ts +15 -9
- package/src/workflows/workflow-reuse.js +77 -14
- package/src/workflows/workflow-revival.d.ts +2 -0
- package/src/workflows/workflow-revival.js +2 -1
- package/src/workflows/workflow-stop-cause.d.ts +6 -0
- package/src/workflows/workflow-stop-cause.js +8 -0
|
@@ -0,0 +1,1373 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLivePromptAudit,
|
|
3
|
+
handleInspectorAction,
|
|
4
|
+
readFleetTranscript,
|
|
5
|
+
renderFleetTranscript,
|
|
6
|
+
resolveWorkflowForegroundSteeringTarget,
|
|
7
|
+
steerAsyncRun,
|
|
8
|
+
steerWorkflowForegroundTarget
|
|
9
|
+
} from "./chunk-7IVSUMQQ.js";
|
|
10
|
+
import {
|
|
11
|
+
FLEET_STATUS_WIDGET_KEY,
|
|
12
|
+
reportFleetWarning,
|
|
13
|
+
runningTone,
|
|
14
|
+
stepTaskSummary
|
|
15
|
+
} from "./chunk-M2CTFYT3.js";
|
|
16
|
+
import {
|
|
17
|
+
foregroundChildStateLabel,
|
|
18
|
+
formatAsyncRunTranscript,
|
|
19
|
+
stopAsyncRun
|
|
20
|
+
} from "./chunk-7PCKZHR5.js";
|
|
21
|
+
import {
|
|
22
|
+
contextModeBadge,
|
|
23
|
+
contextModeLabel,
|
|
24
|
+
decodeUtf8Tail,
|
|
25
|
+
listAsyncRuns
|
|
26
|
+
} from "./chunk-VH5OAWVG.js";
|
|
27
|
+
import "./chunk-NRQ75MGH.js";
|
|
28
|
+
import "./chunk-OYF6AFDY.js";
|
|
29
|
+
import "./chunk-NIXWDZZV.js";
|
|
30
|
+
import "./chunk-XSWOTIA3.js";
|
|
31
|
+
import "./chunk-7F6RKT6W.js";
|
|
32
|
+
import "./chunk-CTPAE36Y.js";
|
|
33
|
+
import "./chunk-7J4ZXMY7.js";
|
|
34
|
+
import {
|
|
35
|
+
getArtifactPaths,
|
|
36
|
+
getArtifactsDir
|
|
37
|
+
} from "./chunk-5TYMSZX3.js";
|
|
38
|
+
import {
|
|
39
|
+
snapshotExternalRuns
|
|
40
|
+
} from "./chunk-UKG4V45O.js";
|
|
41
|
+
import {
|
|
42
|
+
childThinkingLevel,
|
|
43
|
+
formatDuration,
|
|
44
|
+
formatModelThinking,
|
|
45
|
+
formatTokenUsage,
|
|
46
|
+
formatTokens,
|
|
47
|
+
readStatus,
|
|
48
|
+
shortenPath
|
|
49
|
+
} from "./chunk-CIZQXJ5Q.js";
|
|
50
|
+
import {
|
|
51
|
+
DIRS
|
|
52
|
+
} from "./chunk-5LFLVSTM.js";
|
|
53
|
+
import "./chunk-YFCK7H2S.js";
|
|
54
|
+
import "./chunk-Z7KVHQHL.js";
|
|
55
|
+
|
|
56
|
+
// src/tui/fleet.ts
|
|
57
|
+
import * as fs from "node:fs";
|
|
58
|
+
import * as path from "node:path";
|
|
59
|
+
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
60
|
+
import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
61
|
+
|
|
62
|
+
// src/tui/fleet-layout.ts
|
|
63
|
+
function fleetLayout(width, rows, selected, items) {
|
|
64
|
+
if (width < 36) return void 0;
|
|
65
|
+
const bodyHeight = Math.max(2, Math.floor(rows * 0.85) - 6);
|
|
66
|
+
const rosterWidth = Math.max(22, Math.min(46, Math.floor((width - 3) * 0.38)));
|
|
67
|
+
const rosterStart = Math.max(0, Math.min(selected - bodyHeight + 1, Math.max(0, items.length - bodyHeight)));
|
|
68
|
+
return {
|
|
69
|
+
width,
|
|
70
|
+
rows,
|
|
71
|
+
bodyHeight,
|
|
72
|
+
rosterStart,
|
|
73
|
+
roster: { x: 1, y: 3, width: rosterWidth, height: bodyHeight },
|
|
74
|
+
output: { x: rosterWidth + 2, y: 3, width: Math.max(1, width - rosterWidth - 3), height: bodyHeight },
|
|
75
|
+
rosterKeys: items.slice(rosterStart, rosterStart + bodyHeight).map((item) => item.key)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function fleetContains(rect, x, y) {
|
|
79
|
+
return x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/tui/fleet-viewport.ts
|
|
83
|
+
import { ScrollView } from "@earendil-works/pi-tui";
|
|
84
|
+
var FleetViewport = class {
|
|
85
|
+
body = {
|
|
86
|
+
lines: [],
|
|
87
|
+
render() {
|
|
88
|
+
return this.lines;
|
|
89
|
+
},
|
|
90
|
+
invalidate() {
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
scroll = new ScrollView(this.body, { follow: "end", overscroll: "contain" });
|
|
94
|
+
requestRender;
|
|
95
|
+
resetTopOnLayout = false;
|
|
96
|
+
constructor(requestRender) {
|
|
97
|
+
this.requestRender = requestRender;
|
|
98
|
+
}
|
|
99
|
+
render(lines, width, height) {
|
|
100
|
+
this.body.lines = lines;
|
|
101
|
+
const rendered = this.scroll.render(width);
|
|
102
|
+
this.scroll.updateLayout(rendered.length, height, this.requestRender);
|
|
103
|
+
if (this.resetTopOnLayout) {
|
|
104
|
+
this.resetTopOnLayout = false;
|
|
105
|
+
this.scroll.scrollTo(0, { disableFollow: true });
|
|
106
|
+
}
|
|
107
|
+
return rendered.slice(this.scroll.scrollTop, this.scroll.scrollTop + this.scroll.viewportHeight);
|
|
108
|
+
}
|
|
109
|
+
resetTop() {
|
|
110
|
+
this.resetTopOnLayout = true;
|
|
111
|
+
this.scroll.scrollTo(0, { disableFollow: true });
|
|
112
|
+
}
|
|
113
|
+
follow() {
|
|
114
|
+
this.resetTopOnLayout = false;
|
|
115
|
+
this.scroll.scrollToEnd();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/tui/fleet.ts
|
|
120
|
+
var REFRESH_MS = 750;
|
|
121
|
+
var MIN_REFRESH_MS = 250;
|
|
122
|
+
var MAX_RECENT_ASYNC_RUNS = 20;
|
|
123
|
+
var MAX_FLEET_HISTORY_CANDIDATES = 100;
|
|
124
|
+
var TRANSCRIPT_LINES = 200;
|
|
125
|
+
var OUTPUT_TAIL_BYTES = 64 * 1024;
|
|
126
|
+
var PROMPT_AUDIT_SUMMARY_WIDTH = 160;
|
|
127
|
+
var DEFAULT_FLEET_KEYBINDINGS = {
|
|
128
|
+
close: ["escape", "ctrl+c", "q"],
|
|
129
|
+
scrollUp: ["K"],
|
|
130
|
+
scrollDown: ["J"],
|
|
131
|
+
selectUp: ["up", "k"],
|
|
132
|
+
selectDown: ["down", "j"],
|
|
133
|
+
selectFirst: ["home"],
|
|
134
|
+
selectLast: ["end"],
|
|
135
|
+
pageUp: ["pageUp"],
|
|
136
|
+
pageDown: ["pageDown"],
|
|
137
|
+
refresh: ["r", "R"],
|
|
138
|
+
steer: ["s"],
|
|
139
|
+
inspect: ["return", "H"],
|
|
140
|
+
stop: ["D"],
|
|
141
|
+
toggleTools: ["x", "X", "ctrl+o"]
|
|
142
|
+
};
|
|
143
|
+
function resolveFleetKeybindings(config) {
|
|
144
|
+
return Object.fromEntries(
|
|
145
|
+
Object.entries(DEFAULT_FLEET_KEYBINDINGS).map(([action, defaults]) => [action, config?.[action] ?? defaults])
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function matchesFleetBinding(data, binding) {
|
|
149
|
+
const key = /^[A-Z]$/.test(binding) ? `shift+${binding.toLowerCase()}` : binding;
|
|
150
|
+
return matchesKey(data, key);
|
|
151
|
+
}
|
|
152
|
+
function matchesFleetAction(data, bindings, action) {
|
|
153
|
+
return bindings[action].some((binding) => matchesFleetBinding(data, binding));
|
|
154
|
+
}
|
|
155
|
+
function bindingLabel(bindings, action) {
|
|
156
|
+
return bindings[action].map((binding) => binding === "up" ? "\u2191" : binding === "down" ? "\u2193" : binding === "escape" ? "Esc" : binding === "return" ? "Enter" : binding).join("/");
|
|
157
|
+
}
|
|
158
|
+
function belongsToCurrentSession(sessionId, currentSessionId) {
|
|
159
|
+
return !currentSessionId || sessionId === currentSessionId;
|
|
160
|
+
}
|
|
161
|
+
function trackedJobSummary(job) {
|
|
162
|
+
const startedAt = job.startedAt ?? job.updatedAt ?? Date.now();
|
|
163
|
+
return {
|
|
164
|
+
id: job.asyncId,
|
|
165
|
+
asyncDir: job.asyncDir,
|
|
166
|
+
...job.sessionId ? { sessionId: job.sessionId } : {},
|
|
167
|
+
state: job.status,
|
|
168
|
+
activityState: job.activityState,
|
|
169
|
+
lastActivityAt: job.lastActivityAt,
|
|
170
|
+
currentTool: job.currentTool,
|
|
171
|
+
currentToolStartedAt: job.currentToolStartedAt,
|
|
172
|
+
currentPath: job.currentPath,
|
|
173
|
+
turnCount: job.turnCount,
|
|
174
|
+
toolCount: job.toolCount,
|
|
175
|
+
steering: job.steering,
|
|
176
|
+
mode: job.mode ?? "single",
|
|
177
|
+
...job.context ? { context: job.context } : {},
|
|
178
|
+
...job.cwd ? { cwd: job.cwd } : {},
|
|
179
|
+
startedAt,
|
|
180
|
+
...job.updatedAt !== void 0 ? { lastUpdate: job.updatedAt } : {},
|
|
181
|
+
...job.timeoutMs !== void 0 ? { timeoutMs: job.timeoutMs } : {},
|
|
182
|
+
...job.deadlineAt !== void 0 ? { deadlineAt: job.deadlineAt } : {},
|
|
183
|
+
...job.timedOut !== void 0 ? { timedOut: job.timedOut } : {},
|
|
184
|
+
...job.stopped !== void 0 ? { stopped: job.stopped } : {},
|
|
185
|
+
...job.turnBudget ? { turnBudget: job.turnBudget } : {},
|
|
186
|
+
...job.turnBudgetExceeded !== void 0 ? { turnBudgetExceeded: job.turnBudgetExceeded } : {},
|
|
187
|
+
...job.wrapUpRequested !== void 0 ? { wrapUpRequested: job.wrapUpRequested } : {},
|
|
188
|
+
...job.currentStep !== void 0 ? { currentStep: job.currentStep } : {},
|
|
189
|
+
...job.chainStepCount !== void 0 ? { chainStepCount: job.chainStepCount } : {},
|
|
190
|
+
...job.parallelGroups?.length ? { parallelGroups: job.parallelGroups } : {},
|
|
191
|
+
...job.preflight ? { preflight: job.preflight } : {},
|
|
192
|
+
steps: (job.steps ?? job.agents?.map((agent, index) => ({ agent, index, status: job.status === "queued" ? "pending" : job.status })) ?? []).map((step, index) => ({
|
|
193
|
+
...step,
|
|
194
|
+
index: step.index ?? index
|
|
195
|
+
})),
|
|
196
|
+
...job.sessionDir ? { sessionDir: job.sessionDir } : {},
|
|
197
|
+
...job.outputFile ? { outputFile: job.outputFile } : {},
|
|
198
|
+
...job.totalTokens ? { totalTokens: job.totalTokens } : {},
|
|
199
|
+
...job.sessionFile ? { sessionFile: job.sessionFile } : {},
|
|
200
|
+
...job.nestedChildren?.length ? { nestedChildren: job.nestedChildren } : {}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function asyncItems(run, description) {
|
|
204
|
+
const updatedAt = run.lastUpdate ?? run.endedAt ?? run.startedAt;
|
|
205
|
+
if (run.steps.length === 0 || run.mode === "workflow") {
|
|
206
|
+
return [{ key: `async:${run.id}`, kind: "async", runId: run.id, agent: run.mode, state: run.state, updatedAt, run, ...description ? { description } : {} }];
|
|
207
|
+
}
|
|
208
|
+
return run.steps.map((step) => {
|
|
209
|
+
const task = stepTaskSummary(step, step.agent);
|
|
210
|
+
return {
|
|
211
|
+
key: `async:${run.id}:${step.index}`,
|
|
212
|
+
kind: "async",
|
|
213
|
+
runId: run.id,
|
|
214
|
+
index: step.index,
|
|
215
|
+
agent: step.label ? `${step.label} (${step.agent})` : step.agent,
|
|
216
|
+
state: step.status,
|
|
217
|
+
updatedAt: step.lastActivityAt ?? updatedAt,
|
|
218
|
+
run,
|
|
219
|
+
step,
|
|
220
|
+
...task ? { task } : {},
|
|
221
|
+
...description ? { description } : {}
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function orderFleetAsyncRuns(runs, terminalLimit, trackedJobs) {
|
|
226
|
+
const updatedAt = (run) => run.lastUpdate ?? run.endedAt ?? run.startedAt;
|
|
227
|
+
const byNewest = (left, right) => updatedAt(right) - updatedAt(left);
|
|
228
|
+
const startedAt = (run) => {
|
|
229
|
+
const job = trackedJobs.get(run.id);
|
|
230
|
+
return job ? job.startedAt ?? 0 : run.startedAt;
|
|
231
|
+
};
|
|
232
|
+
const byStartedAt = (left, right) => startedAt(left) - startedAt(right) || left.id.localeCompare(right.id);
|
|
233
|
+
const active = runs.filter((run) => run.state === "queued" || run.state === "running").sort(byStartedAt);
|
|
234
|
+
const terminal = runs.filter((run) => run.state !== "queued" && run.state !== "running").sort(byNewest);
|
|
235
|
+
return [...active, ...terminal.slice(0, terminalLimit)];
|
|
236
|
+
}
|
|
237
|
+
function collectFleetSnapshot(state, options = {}) {
|
|
238
|
+
const items = [];
|
|
239
|
+
const activeForegroundIds = /* @__PURE__ */ new Set();
|
|
240
|
+
const trackedJobs = state.fleetJobs ?? state.asyncJobs;
|
|
241
|
+
const workflowParentIds = new Set([...trackedJobs.values()].filter((job) => job.mode === "workflow" && belongsToCurrentSession(job.sessionId, state.currentSessionId)).map((job) => job.asyncId));
|
|
242
|
+
const workflowForegroundChildCounts = /* @__PURE__ */ new Map();
|
|
243
|
+
const liveWorkflowForegroundControls = /* @__PURE__ */ new Set();
|
|
244
|
+
for (const control of state.foregroundControls.values()) {
|
|
245
|
+
const activeChildCount = control.activeChildren?.size ?? 0;
|
|
246
|
+
if (!control.parentWorkflowRunId || !workflowParentIds.has(control.parentWorkflowRunId) || !belongsToCurrentSession(control.sessionId, state.currentSessionId) || activeChildCount === 0) continue;
|
|
247
|
+
liveWorkflowForegroundControls.add(control);
|
|
248
|
+
workflowForegroundChildCounts.set(control.parentWorkflowRunId, (workflowForegroundChildCounts.get(control.parentWorkflowRunId) ?? 0) + activeChildCount);
|
|
249
|
+
}
|
|
250
|
+
for (const control of [...state.foregroundControls.values()].sort((left, right) => left.startedAt - right.startedAt || left.runId.localeCompare(right.runId))) {
|
|
251
|
+
activeForegroundIds.add(control.runId);
|
|
252
|
+
if (control.parentWorkflowRunId && workflowParentIds.has(control.parentWorkflowRunId) && ((workflowForegroundChildCounts.get(control.parentWorkflowRunId) ?? 0) <= 1 || !liveWorkflowForegroundControls.has(control))) continue;
|
|
253
|
+
if (control.activeChildren) {
|
|
254
|
+
for (const child of [...control.activeChildren.values()].sort((left, right) => left.index - right.index)) {
|
|
255
|
+
items.push({
|
|
256
|
+
key: `foreground-active:${control.runId}:${child.index}`,
|
|
257
|
+
kind: "foreground-active",
|
|
258
|
+
runId: control.runId,
|
|
259
|
+
index: child.index,
|
|
260
|
+
agent: child.agent,
|
|
261
|
+
state: "running",
|
|
262
|
+
updatedAt: child.updatedAt,
|
|
263
|
+
control,
|
|
264
|
+
activeChild: child,
|
|
265
|
+
...child.description ? { description: child.description } : {}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
items.push({
|
|
271
|
+
key: `foreground-active:${control.runId}:${control.currentIndex ?? 0}`,
|
|
272
|
+
kind: "foreground-active",
|
|
273
|
+
runId: control.runId,
|
|
274
|
+
...control.currentIndex !== void 0 ? { index: control.currentIndex } : {},
|
|
275
|
+
agent: control.currentAgent ?? control.mode,
|
|
276
|
+
state: "running",
|
|
277
|
+
updatedAt: control.updatedAt,
|
|
278
|
+
control,
|
|
279
|
+
...control.description ? { description: control.description } : {}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
let error;
|
|
283
|
+
try {
|
|
284
|
+
let runs;
|
|
285
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
286
|
+
const tracked = [...trackedJobs.values()].filter((job) => belongsToCurrentSession(job.sessionId, state.currentSessionId));
|
|
287
|
+
const byUpdate = (left, right) => (right.updatedAt ?? right.startedAt ?? 0) - (left.updatedAt ?? left.startedAt ?? 0);
|
|
288
|
+
const active = tracked.filter((job) => job.status === "queued" || job.status === "running");
|
|
289
|
+
const recent = tracked.filter((job) => job.status !== "queued" && job.status !== "running").sort(byUpdate).slice(0, options.limit ?? MAX_RECENT_ASYNC_RUNS);
|
|
290
|
+
const trackedRuns = [];
|
|
291
|
+
for (const job of [...active, ...recent]) {
|
|
292
|
+
try {
|
|
293
|
+
trackedRuns.push(trackedJobSummary(job));
|
|
294
|
+
if (job.description) descriptions.set(job.asyncId, job.description);
|
|
295
|
+
} catch (cause) {
|
|
296
|
+
error = `Failed to inspect async run '${job.asyncId}': ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (options.asyncDirRoot !== void 0) {
|
|
300
|
+
const trackedIds = new Set(trackedRuns.map((run) => run.id));
|
|
301
|
+
const history = listAsyncRuns(options.asyncDirRoot, {
|
|
302
|
+
...state.currentSessionId ? { sessionId: state.currentSessionId } : {},
|
|
303
|
+
entryLimit: MAX_FLEET_HISTORY_CANDIDATES,
|
|
304
|
+
resultsDir: options.resultsDir ?? DIRS.results,
|
|
305
|
+
reconcile: false
|
|
306
|
+
}).filter((run) => !trackedIds.has(run.id));
|
|
307
|
+
runs = [...trackedRuns, ...history];
|
|
308
|
+
} else {
|
|
309
|
+
runs = trackedRuns;
|
|
310
|
+
}
|
|
311
|
+
for (const run of orderFleetAsyncRuns(runs, options.limit ?? MAX_RECENT_ASYNC_RUNS, trackedJobs)) {
|
|
312
|
+
items.push(...asyncItems(run, descriptions.get(run.id)));
|
|
313
|
+
}
|
|
314
|
+
} catch (cause) {
|
|
315
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
316
|
+
}
|
|
317
|
+
if (state.currentSessionId) {
|
|
318
|
+
try {
|
|
319
|
+
for (const run of snapshotExternalRuns(state.currentSessionId, { ignoreMalformed: true, onMalformedRecord: (message) => reportFleetWarning(state, `Removed ${message}`) })) {
|
|
320
|
+
items.push({
|
|
321
|
+
key: `external:${run.id}`,
|
|
322
|
+
kind: "external",
|
|
323
|
+
runId: run.id,
|
|
324
|
+
agent: run.label,
|
|
325
|
+
state: run.state,
|
|
326
|
+
updatedAt: run.updatedAt ?? run.endedAt ?? run.startedAt,
|
|
327
|
+
run,
|
|
328
|
+
...run.currentAction ? { description: run.currentAction } : {}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
} catch (cause) {
|
|
332
|
+
const message = `Failed to inspect external jobs: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
333
|
+
error = error ? `${error}; ${message}` : message;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const recentForeground = [...state.foregroundRuns?.values() ?? []].filter((run) => belongsToCurrentSession(run.sessionId, state.currentSessionId) && !activeForegroundIds.has(run.runId)).sort((left, right) => right.updatedAt - left.updatedAt);
|
|
337
|
+
for (const run of recentForeground) {
|
|
338
|
+
for (const child of run.children) {
|
|
339
|
+
items.push({
|
|
340
|
+
key: `foreground-recent:${run.runId}:${child.index}`,
|
|
341
|
+
kind: "foreground-recent",
|
|
342
|
+
runId: run.runId,
|
|
343
|
+
index: child.index,
|
|
344
|
+
agent: child.agent,
|
|
345
|
+
state: child.status,
|
|
346
|
+
updatedAt: child.updatedAt ?? run.updatedAt,
|
|
347
|
+
run,
|
|
348
|
+
child
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return { items, ...error ? { error } : {} };
|
|
353
|
+
}
|
|
354
|
+
function visibleWorkflowParentKeyForForegroundKey(state, key, items) {
|
|
355
|
+
for (const control of state.foregroundControls.values()) {
|
|
356
|
+
if (!control.parentWorkflowRunId) continue;
|
|
357
|
+
let matches = false;
|
|
358
|
+
if (control.activeChildren) {
|
|
359
|
+
for (const child of control.activeChildren.values()) {
|
|
360
|
+
if (`foreground-active:${control.runId}:${child.index}` === key) {
|
|
361
|
+
matches = true;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
matches = `foreground-active:${control.runId}:${control.currentIndex ?? 0}` === key;
|
|
367
|
+
}
|
|
368
|
+
if (!matches) continue;
|
|
369
|
+
const parentKey = `async:${control.parentWorkflowRunId}`;
|
|
370
|
+
return items.some((item) => item.key === parentKey) ? parentKey : void 0;
|
|
371
|
+
}
|
|
372
|
+
return void 0;
|
|
373
|
+
}
|
|
374
|
+
function fleetItemThinkingLevel(item) {
|
|
375
|
+
switch (item.kind) {
|
|
376
|
+
case "foreground-active":
|
|
377
|
+
return childThinkingLevel(item.activeChild ?? item.control);
|
|
378
|
+
case "foreground-recent":
|
|
379
|
+
return childThinkingLevel(item.child);
|
|
380
|
+
case "async":
|
|
381
|
+
return childThinkingLevel(item.step);
|
|
382
|
+
case "external":
|
|
383
|
+
return void 0;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function statusGlyph(item, theme) {
|
|
387
|
+
if (item.state === "running") return runningTone(theme, fleetItemThinkingLevel(item))("\u25CF");
|
|
388
|
+
if (item.state === "queued" || item.state === "pending") return theme.fg("muted", "\u25E6");
|
|
389
|
+
if (item.state === "complete" || item.state === "completed") return theme.fg("success", "\u2713");
|
|
390
|
+
if (item.state === "paused" || item.state === "stopped" || item.state === "detached") return theme.fg("warning", "\u25A0");
|
|
391
|
+
return theme.fg("error", "\u2717");
|
|
392
|
+
}
|
|
393
|
+
function foregroundPromptAuditCount(item, state) {
|
|
394
|
+
if (!state.currentSessionId || item.control.sessionId !== state.currentSessionId) return 0;
|
|
395
|
+
if (!item.control.activeChildren) return getLivePromptAudit(item.control, item.index ?? 0) ? 1 : 0;
|
|
396
|
+
return [...item.control.activeChildren.keys()].filter((index) => getLivePromptAudit(item.control, index)).length;
|
|
397
|
+
}
|
|
398
|
+
function promptAuditString(value) {
|
|
399
|
+
return typeof value === "string" ? value : void 0;
|
|
400
|
+
}
|
|
401
|
+
function authoredPromptSummary(text) {
|
|
402
|
+
if (typeof text !== "string") return void 0;
|
|
403
|
+
const summary = text.replace(/\s+/g, " ").trim();
|
|
404
|
+
return summary ? truncateToWidth(summary, PROMPT_AUDIT_SUMMARY_WIDTH, "\u2026") : void 0;
|
|
405
|
+
}
|
|
406
|
+
function foregroundAuthoredPromptSummary(item, state) {
|
|
407
|
+
if (!state.currentSessionId || item.control.sessionId !== state.currentSessionId) return void 0;
|
|
408
|
+
const prompt = getLivePromptAudit(item.control, item.index ?? 0);
|
|
409
|
+
return prompt ? authoredPromptSummary(prompt.authoredTask) : void 0;
|
|
410
|
+
}
|
|
411
|
+
function promptAuditText(prompt, view) {
|
|
412
|
+
switch (view) {
|
|
413
|
+
case "authored":
|
|
414
|
+
return promptAuditString(prompt.authoredTask);
|
|
415
|
+
case "runtime":
|
|
416
|
+
return promptAuditString(prompt.runtimeAdditions);
|
|
417
|
+
case "effective":
|
|
418
|
+
return promptAuditString(prompt.finalEffectivePrompt);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function promptAuditViewLabel(view) {
|
|
422
|
+
switch (view) {
|
|
423
|
+
case "authored":
|
|
424
|
+
return "[1] Authored task";
|
|
425
|
+
case "runtime":
|
|
426
|
+
return "[2] Runtime additions";
|
|
427
|
+
case "effective":
|
|
428
|
+
return "[3] Final effective prompt";
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function foregroundActiveDetail(item, state) {
|
|
432
|
+
const { control } = item;
|
|
433
|
+
const live = item.activeChild ?? control;
|
|
434
|
+
const modelThinking = formatModelThinking(live.model, live.thinking);
|
|
435
|
+
const promptAuditCount = foregroundPromptAuditCount(item, state);
|
|
436
|
+
const promptSummary = foregroundAuthoredPromptSummary(item, state);
|
|
437
|
+
const lines = [
|
|
438
|
+
`Run: ${item.runId}`,
|
|
439
|
+
"Source: foreground",
|
|
440
|
+
`State: running`,
|
|
441
|
+
`Mode: ${control.mode}`,
|
|
442
|
+
control.parentWorkflowRunId ? `Workflow child of: ${control.parentWorkflowRunId}${control.workflowKey ? ` (${control.workflowKey})` : ""}` : void 0,
|
|
443
|
+
control.sourceRunId ? `Redo source: ${control.sourceRunId}` : void 0,
|
|
444
|
+
control.supersededByRunId ? `Superseded by: ${control.supersededByRunId}` : void 0,
|
|
445
|
+
item.index !== void 0 ? `Child: ${item.index} (${item.agent})` : `Agent: ${item.agent}`,
|
|
446
|
+
modelThinking ? `Model: ${modelThinking}` : void 0,
|
|
447
|
+
promptSummary ? `Task: ${promptSummary}` : void 0,
|
|
448
|
+
`Started: ${new Date(live.startedAt).toISOString()}`,
|
|
449
|
+
live.currentTool ? `Current tool: ${live.currentTool}${live.currentPath ? ` \xB7 ${shortenPath(live.currentPath)}` : ""}` : void 0,
|
|
450
|
+
live.turnCount !== void 0 ? `Turns: ${live.turnCount}` : void 0,
|
|
451
|
+
live.toolCount !== void 0 ? `Tools: ${live.toolCount}` : void 0,
|
|
452
|
+
live.tokens !== void 0 ? `Tokens: ${formatTokenUsage({ input: live.inputTokens ?? 0, output: live.outputTokens ?? 0, total: live.tokens, ...live.window !== void 0 ? { window: live.window } : {}, ...live.windowPeak !== void 0 ? { windowPeak: live.windowPeak } : {} }, "tokens")}` : void 0,
|
|
453
|
+
promptAuditCount > 0 ? `Prompt audit: ${promptAuditCount} live \xB7 3 views \xB7 p opens` : void 0,
|
|
454
|
+
"",
|
|
455
|
+
"Transcript",
|
|
456
|
+
"Live foreground output remains in the expanded subagent tool result. Persisted output and session paths appear here after the child settles."
|
|
457
|
+
];
|
|
458
|
+
return lines.filter((line) => line !== void 0);
|
|
459
|
+
}
|
|
460
|
+
function pathWithin(base, candidate) {
|
|
461
|
+
const resolvedBase = path.resolve(base);
|
|
462
|
+
const resolvedCandidate = path.resolve(candidate);
|
|
463
|
+
return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`);
|
|
464
|
+
}
|
|
465
|
+
function trustedFileTail(filePath, trustedRoots) {
|
|
466
|
+
const resolvedPath = path.resolve(filePath);
|
|
467
|
+
if (trustedRoots.length === 0 || !trustedRoots.some((root) => pathWithin(root, resolvedPath))) return { warning: `output artifact is outside trusted roots: ${filePath}` };
|
|
468
|
+
let stat;
|
|
469
|
+
try {
|
|
470
|
+
stat = fs.lstatSync(resolvedPath);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return { unavailable: `output artifact unavailable: ${filePath}` };
|
|
473
|
+
return { warning: `output artifact could not be inspected: ${error instanceof Error ? error.message : String(error)}` };
|
|
474
|
+
}
|
|
475
|
+
if (stat.isSymbolicLink()) return { warning: `output artifact refused a symlink: ${filePath}` };
|
|
476
|
+
if (!stat.isFile()) return { warning: `output artifact is not a file: ${filePath}` };
|
|
477
|
+
try {
|
|
478
|
+
const realPath = fs.realpathSync(resolvedPath);
|
|
479
|
+
const realRoots = trustedRoots.filter((root) => fs.existsSync(root)).map((root) => fs.realpathSync(root));
|
|
480
|
+
if (!realRoots.some((root) => pathWithin(root, realPath))) return { warning: `output artifact resolves outside trusted roots: ${filePath}` };
|
|
481
|
+
const fd = fs.openSync(realPath, "r");
|
|
482
|
+
try {
|
|
483
|
+
const bytes = Math.min(stat.size, OUTPUT_TAIL_BYTES);
|
|
484
|
+
const buffer = Buffer.alloc(bytes);
|
|
485
|
+
fs.readSync(fd, buffer, 0, bytes, stat.size - bytes);
|
|
486
|
+
return { text: decodeUtf8Tail(buffer) };
|
|
487
|
+
} finally {
|
|
488
|
+
fs.closeSync(fd);
|
|
489
|
+
}
|
|
490
|
+
} catch (error) {
|
|
491
|
+
return { warning: `output artifact could not be read: ${error instanceof Error ? error.message : String(error)}` };
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function foregroundRecentOutputLines(item, state) {
|
|
495
|
+
const outputPath = item.child.artifactPaths?.outputPath ?? item.child.savedOutputPath;
|
|
496
|
+
const output = item.child.finalOutput ? { text: item.child.finalOutput } : outputPath ? trustedFileTail(path.isAbsolute(outputPath) ? outputPath : path.resolve(item.run.cwd, outputPath), uniquePaths([
|
|
497
|
+
fleetArtifactsRoot(state, item.run.cwd),
|
|
498
|
+
fleetArtifactsRoot(state, state.baseCwd)
|
|
499
|
+
])) : void 0;
|
|
500
|
+
if (output?.warning) return [`(${output.warning})`];
|
|
501
|
+
if (output?.unavailable) return [`(${output.unavailable})`];
|
|
502
|
+
const outputLines = (output?.text ?? "").split(/\r?\n/).filter((line) => line.trim()).slice(-TRANSCRIPT_LINES);
|
|
503
|
+
return outputLines.length ? outputLines : ["(no recovered output available)"];
|
|
504
|
+
}
|
|
505
|
+
function foregroundRecentDetail(item, state) {
|
|
506
|
+
const { child, run } = item;
|
|
507
|
+
const outputPath = child.artifactPaths?.outputPath ?? child.savedOutputPath;
|
|
508
|
+
const modelThinking = formatModelThinking(child.model, child.thinking);
|
|
509
|
+
const lines = [
|
|
510
|
+
`Run: ${item.runId}`,
|
|
511
|
+
"Source: foreground",
|
|
512
|
+
`State: ${foregroundChildStateLabel(run, child)}`,
|
|
513
|
+
`Mode: ${run.mode}`,
|
|
514
|
+
`Child: ${child.index} (${child.agent})${contextModeLabel(child.context) ? ` ${contextModeLabel(child.context)}` : ""}`,
|
|
515
|
+
modelThinking ? `Model: ${modelThinking}` : void 0,
|
|
516
|
+
`Updated: ${new Date(child.updatedAt ?? run.updatedAt).toISOString()}`,
|
|
517
|
+
outputPath ? `Output: ${outputPath}` : void 0,
|
|
518
|
+
child.sessionFile ? `Session: ${child.sessionFile}` : void 0,
|
|
519
|
+
child.transcriptPath ? `Transcript file: ${child.transcriptPath}` : void 0,
|
|
520
|
+
child.error ? `Error: ${child.error}` : void 0,
|
|
521
|
+
child.outputSaveError ? `Output warning: ${child.outputSaveError}` : void 0,
|
|
522
|
+
child.transcriptError ? `Transcript warning: ${child.transcriptError}` : void 0,
|
|
523
|
+
"",
|
|
524
|
+
"Result transcript tail"
|
|
525
|
+
];
|
|
526
|
+
lines.push(...foregroundRecentOutputLines(item, state));
|
|
527
|
+
return lines.filter((line) => line !== void 0);
|
|
528
|
+
}
|
|
529
|
+
function externalElapsedEnd(run) {
|
|
530
|
+
const terminal = run.state !== "queued" && run.state !== "running";
|
|
531
|
+
return terminal ? run.endedAt ?? run.updatedAt ?? Date.now() : Date.now();
|
|
532
|
+
}
|
|
533
|
+
function workflowStepLabel(step, index) {
|
|
534
|
+
const key = step.workflowKey ?? `step ${index + 1}`;
|
|
535
|
+
const label = step.label && step.label !== key ? ` \xB7 ${step.label}` : "";
|
|
536
|
+
const phase = step.phase ? `${step.phase}: ` : "";
|
|
537
|
+
return `${phase}${key}${label} (${step.agent})`;
|
|
538
|
+
}
|
|
539
|
+
function workflowStepActivity(step) {
|
|
540
|
+
if (step.currentTool) return `tool ${step.currentTool}`;
|
|
541
|
+
if (step.currentPath) return shortenPath(step.currentPath);
|
|
542
|
+
if (step.activityState === "needs_attention") return "needs attention";
|
|
543
|
+
if (step.activityState === "active_long_running") return "long-running";
|
|
544
|
+
if (step.turnCount !== void 0) return `${step.turnCount} turns`;
|
|
545
|
+
if (step.toolCount !== void 0) return `${step.toolCount} tools`;
|
|
546
|
+
return void 0;
|
|
547
|
+
}
|
|
548
|
+
function visibleWorkflowProgressSteps(steps, visibleLimit) {
|
|
549
|
+
if (steps.length <= visibleLimit) return steps.map((step, index) => ({ step, index }));
|
|
550
|
+
const selected = /* @__PURE__ */ new Set();
|
|
551
|
+
for (const [index, step] of steps.entries()) {
|
|
552
|
+
if (step.status !== "complete" && step.status !== "completed") selected.add(index);
|
|
553
|
+
if (selected.size >= visibleLimit) break;
|
|
554
|
+
}
|
|
555
|
+
for (let index = steps.length - 1; index >= 0 && selected.size < visibleLimit; index--) selected.add(index);
|
|
556
|
+
const visible = [...selected].sort((left, right) => left - right).map((index) => ({ step: steps[index], index }));
|
|
557
|
+
return [{ hidden: steps.length - visible.length }, ...visible];
|
|
558
|
+
}
|
|
559
|
+
function workflowProgressLines(steps) {
|
|
560
|
+
if (!steps?.length) return [];
|
|
561
|
+
const lines = ["Workflow progress:"];
|
|
562
|
+
for (const row of visibleWorkflowProgressSteps(steps, 8)) {
|
|
563
|
+
if ("hidden" in row) {
|
|
564
|
+
lines.push(` +${row.hidden} hidden workflow steps`);
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const activity = workflowStepActivity(row.step);
|
|
568
|
+
const context = contextModeLabel(row.step.context);
|
|
569
|
+
const details = [row.step.status, activity, context, row.step.tokens ? formatTokenUsage(row.step.tokens) : void 0].filter(Boolean).join(" \xB7 ");
|
|
570
|
+
lines.push(` ${row.index + 1}. ${workflowStepLabel(row.step, row.index)}${details ? ` \u2014 ${details}` : ""}`);
|
|
571
|
+
}
|
|
572
|
+
return lines;
|
|
573
|
+
}
|
|
574
|
+
function asyncDetail(item, state) {
|
|
575
|
+
const status = readStatus(item.run.asyncDir);
|
|
576
|
+
if (status) {
|
|
577
|
+
const trackedJob = state.fleetJobs?.get(item.runId) ?? state.asyncJobs.get(item.runId);
|
|
578
|
+
const lines = formatAsyncRunTranscript(status, item.run.asyncDir, {
|
|
579
|
+
index: item.index,
|
|
580
|
+
lines: TRANSCRIPT_LINES,
|
|
581
|
+
sessionRoots: uniquePaths([...state.trustedSessionRoots ?? [], trackedJob?.sessionRoot]),
|
|
582
|
+
trustedSessionFiles: [item.step?.sessionFile ?? item.run.sessionFile].filter((value) => Boolean(value)),
|
|
583
|
+
trustedSessionFileRoot: state.trustedSessionFileRoot
|
|
584
|
+
}).split("\n");
|
|
585
|
+
if (status.mode === "workflow" && item.index === void 0) {
|
|
586
|
+
const progress = workflowProgressLines(status.steps ?? item.run.steps);
|
|
587
|
+
if (progress.length) {
|
|
588
|
+
const modeIndex = lines.findIndex((line) => line.startsWith("Mode:"));
|
|
589
|
+
lines.splice(modeIndex >= 0 ? modeIndex + 1 : 0, 0, "", ...progress);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return lines;
|
|
593
|
+
}
|
|
594
|
+
const outputPath = item.index !== void 0 ? path.join(item.run.asyncDir, `output-${item.index}.log`) : void 0;
|
|
595
|
+
return [
|
|
596
|
+
`Run: ${item.runId}`,
|
|
597
|
+
"Source: async",
|
|
598
|
+
`State: ${item.state}`,
|
|
599
|
+
`Mode: ${item.run.mode}${contextModeLabel(item.run.context) ? ` ${contextModeLabel(item.run.context)}` : ""}`,
|
|
600
|
+
item.index !== void 0 ? `Child: ${item.index} (${item.agent})${contextModeLabel(item.step?.context) ? ` ${contextModeLabel(item.step?.context)}` : ""}` : `Agent: ${item.agent}${contextModeLabel(item.run.context) ? ` ${contextModeLabel(item.run.context)}` : ""}`,
|
|
601
|
+
outputPath ? `Output: ${outputPath}` : void 0,
|
|
602
|
+
item.step?.sessionFile ? `Session: ${item.step.sessionFile}` : item.run.sessionFile ? `Session: ${item.run.sessionFile}` : void 0,
|
|
603
|
+
"",
|
|
604
|
+
"Transcript",
|
|
605
|
+
"(status is no longer available)"
|
|
606
|
+
].filter((line) => line !== void 0);
|
|
607
|
+
}
|
|
608
|
+
function externalDetail(item) {
|
|
609
|
+
return [
|
|
610
|
+
`Run: ${item.runId}`,
|
|
611
|
+
"Source: external \xB7 display-only",
|
|
612
|
+
`Owner: ${item.run.source}`,
|
|
613
|
+
`State: ${item.state}`,
|
|
614
|
+
`Started: ${new Date(item.run.startedAt).toISOString()}`,
|
|
615
|
+
item.run.updatedAt !== void 0 ? `Updated: ${new Date(item.run.updatedAt).toISOString()}` : void 0,
|
|
616
|
+
item.run.endedAt !== void 0 ? `Ended: ${new Date(item.run.endedAt).toISOString()}` : void 0,
|
|
617
|
+
`Elapsed: ${formatDuration(Math.max(0, externalElapsedEnd(item.run) - item.run.startedAt))}`,
|
|
618
|
+
item.run.currentAction ? `Current action: ${item.run.currentAction}` : void 0,
|
|
619
|
+
item.run.reportPath ? `Report path: ${item.run.reportPath}` : void 0,
|
|
620
|
+
item.run.transcriptPath ? `Transcript path: ${item.run.transcriptPath}` : void 0,
|
|
621
|
+
"",
|
|
622
|
+
"Preview",
|
|
623
|
+
item.run.preview ?? "(no preview supplied)",
|
|
624
|
+
"",
|
|
625
|
+
"The owning extension controls execution, persistence, cancellation, and results."
|
|
626
|
+
].filter((line) => line !== void 0);
|
|
627
|
+
}
|
|
628
|
+
function detailLines(item, error, state) {
|
|
629
|
+
if (!item) return [error ? `Fleet scan failed: ${error}` : "No current-session Fleet jobs.", "", "New jobs appear here automatically while this inspector remains open."];
|
|
630
|
+
const lines = item.kind === "foreground-active" ? foregroundActiveDetail(item, state) : item.kind === "foreground-recent" ? foregroundRecentDetail(item, state) : item.kind === "external" ? externalDetail(item) : asyncDetail(item, state);
|
|
631
|
+
if (error) lines.unshift(`Fleet scan warning: ${error}`, "");
|
|
632
|
+
return lines;
|
|
633
|
+
}
|
|
634
|
+
function isActionableAsyncState(state) {
|
|
635
|
+
return state === "running" || state === "queued" || state === "pending";
|
|
636
|
+
}
|
|
637
|
+
function firstToolResultText(result, fallback) {
|
|
638
|
+
if (!result) return { text: fallback, isError: true };
|
|
639
|
+
const text = result.content.find((item) => item.type === "text")?.text ?? fallback;
|
|
640
|
+
return { text, ...result.isError ? { isError: true } : {} };
|
|
641
|
+
}
|
|
642
|
+
function uniquePaths(values) {
|
|
643
|
+
return [...new Set(values.filter((value) => Boolean(value)).map((value) => path.resolve(value)))];
|
|
644
|
+
}
|
|
645
|
+
function fleetArtifactsRoot(state, cwd) {
|
|
646
|
+
return getArtifactsDir(
|
|
647
|
+
state.parentSessionFile ?? null,
|
|
648
|
+
cwd,
|
|
649
|
+
state.artifactDirPreference
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
function transcriptTarget(item, state) {
|
|
653
|
+
if (item.kind === "external") return void 0;
|
|
654
|
+
if (item.kind === "foreground-active") {
|
|
655
|
+
const artifactsRoot = fleetArtifactsRoot(state, item.control.cwd ?? state.baseCwd);
|
|
656
|
+
return {
|
|
657
|
+
path: getArtifactPaths(artifactsRoot, item.runId, item.agent, item.index ?? 0).transcriptPath,
|
|
658
|
+
trustedRoots: [artifactsRoot]
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
if (item.kind === "foreground-recent") {
|
|
662
|
+
if (!item.child.transcriptPath) return void 0;
|
|
663
|
+
const transcriptPath2 = path.isAbsolute(item.child.transcriptPath) ? item.child.transcriptPath : path.resolve(item.run.cwd, item.child.transcriptPath);
|
|
664
|
+
return {
|
|
665
|
+
path: transcriptPath2,
|
|
666
|
+
trustedRoots: uniquePaths([
|
|
667
|
+
fleetArtifactsRoot(state, item.run.cwd),
|
|
668
|
+
fleetArtifactsRoot(state, state.baseCwd)
|
|
669
|
+
])
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
const step = item.step ?? (item.run.steps.length === 1 ? item.run.steps[0] : void 0);
|
|
673
|
+
if (step?.runner?.type === "external-cli") return void 0;
|
|
674
|
+
const recordedSessionFile = step?.sessionFile ?? item.run.sessionFile;
|
|
675
|
+
const recordedPath = step?.transcriptPath ?? recordedSessionFile;
|
|
676
|
+
if (!recordedPath) return void 0;
|
|
677
|
+
const transcriptPath = path.isAbsolute(recordedPath) ? recordedPath : path.resolve(item.run.asyncDir, recordedPath);
|
|
678
|
+
const trackedJob = state.fleetJobs?.get(item.runId) ?? state.asyncJobs.get(item.runId);
|
|
679
|
+
return {
|
|
680
|
+
path: transcriptPath,
|
|
681
|
+
trustedRoots: uniquePaths([
|
|
682
|
+
item.run.asyncDir,
|
|
683
|
+
fleetArtifactsRoot(state, state.baseCwd),
|
|
684
|
+
trackedJob?.cwd ? fleetArtifactsRoot(state, trackedJob.cwd) : void 0,
|
|
685
|
+
item.run.sessionFile ? getArtifactsDir(item.run.sessionFile, item.run.cwd ?? state.baseCwd, state.artifactDirPreference) : void 0
|
|
686
|
+
]),
|
|
687
|
+
...!step?.transcriptPath && recordedSessionFile ? { trustedFiles: [recordedSessionFile], trustedFileRoot: state.trustedSessionFileRoot } : {}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function itemContext(item) {
|
|
691
|
+
if (item.kind === "async") return contextModeLabel(item.step?.context ?? item.run.context);
|
|
692
|
+
if (item.kind === "foreground-recent") return contextModeLabel(item.child.context);
|
|
693
|
+
return void 0;
|
|
694
|
+
}
|
|
695
|
+
function itemMode(item) {
|
|
696
|
+
if (item.kind === "external") return "display-only";
|
|
697
|
+
return item.kind === "foreground-active" ? item.control.mode : item.run.mode;
|
|
698
|
+
}
|
|
699
|
+
function itemSource(item) {
|
|
700
|
+
if (item.kind === "external") return `external \xB7 ${item.run.source}`;
|
|
701
|
+
if (item.kind === "async") return "background";
|
|
702
|
+
return item.kind === "foreground-active" ? "foreground \xB7 live" : "foreground \xB7 recent";
|
|
703
|
+
}
|
|
704
|
+
function itemStats(item) {
|
|
705
|
+
let model;
|
|
706
|
+
let tokens;
|
|
707
|
+
let tokenUsage;
|
|
708
|
+
let tools;
|
|
709
|
+
let durationMs;
|
|
710
|
+
if (item.kind === "foreground-active") {
|
|
711
|
+
const live = item.activeChild ?? item.control;
|
|
712
|
+
model = formatModelThinking(live.model, live.thinking) || void 0;
|
|
713
|
+
tokens = live.tokens;
|
|
714
|
+
if (tokens !== void 0) tokenUsage = { input: live.inputTokens ?? 0, output: live.outputTokens ?? 0, total: tokens, ...live.window !== void 0 ? { window: live.window } : {}, ...live.windowPeak !== void 0 ? { windowPeak: live.windowPeak } : {} };
|
|
715
|
+
tools = live.toolCount;
|
|
716
|
+
durationMs = Math.max(0, Date.now() - live.startedAt);
|
|
717
|
+
} else if (item.kind === "foreground-recent") {
|
|
718
|
+
model = formatModelThinking(item.child.model, item.child.thinking) || void 0;
|
|
719
|
+
tokens = item.child.tokens;
|
|
720
|
+
if (tokens !== void 0) tokenUsage = { input: 0, output: 0, total: tokens, ...item.child.window !== void 0 ? { window: item.child.window } : {}, ...item.child.windowPeak !== void 0 ? { windowPeak: item.child.windowPeak } : {} };
|
|
721
|
+
tools = item.child.toolCount;
|
|
722
|
+
} else if (item.kind === "external") {
|
|
723
|
+
durationMs = Math.max(0, externalElapsedEnd(item.run) - item.run.startedAt);
|
|
724
|
+
} else {
|
|
725
|
+
model = formatModelThinking(item.step?.model, item.step?.thinking) || void 0;
|
|
726
|
+
tokenUsage = item.step?.tokens ?? (item.index === void 0 ? item.run.totalTokens : void 0);
|
|
727
|
+
tokens = tokenUsage?.total;
|
|
728
|
+
tools = item.step?.toolCount ?? (item.index === void 0 ? item.run.toolCount : void 0);
|
|
729
|
+
const terminalRun = item.state !== "queued" && item.state !== "running" && item.state !== "pending";
|
|
730
|
+
const endTime = item.run.endedAt ?? (terminalRun ? item.run.lastUpdate : void 0) ?? Date.now();
|
|
731
|
+
durationMs = item.step?.durationMs ?? Math.max(0, endTime - item.run.startedAt);
|
|
732
|
+
}
|
|
733
|
+
return [
|
|
734
|
+
model,
|
|
735
|
+
tokenUsage ? formatTokenUsage(tokenUsage) : tokens !== void 0 ? `${formatTokens(tokens)} tok` : void 0,
|
|
736
|
+
tools !== void 0 ? `${tools} tool${tools === 1 ? "" : "s"}` : void 0,
|
|
737
|
+
durationMs !== void 0 ? formatDuration(durationMs) : void 0
|
|
738
|
+
].filter((value) => Boolean(value));
|
|
739
|
+
}
|
|
740
|
+
function structuredHeader(item, width, theme, conversationState, promptSummary) {
|
|
741
|
+
const lines = [];
|
|
742
|
+
lines.push(rightAligned(` ${statusGlyph(item, theme)} ${theme.bold(item.agent)}`, theme.fg("dim", item.state), width));
|
|
743
|
+
const child = "index" in item && item.index !== void 0 ? ` \xB7 child ${item.index + 1}` : "";
|
|
744
|
+
const context = itemContext(item);
|
|
745
|
+
const identity = `${itemSource(item)} \xB7 ${item.runId.slice(0, 8)}${child} \xB7 ${itemMode(item)}${context ? ` ${context}` : ""}`;
|
|
746
|
+
lines.push(` ${theme.fg("dim", identity)}`);
|
|
747
|
+
const stats = itemStats(item);
|
|
748
|
+
if (stats.length) lines.push(` ${theme.fg("muted", stats.join(" \xB7 "))}`);
|
|
749
|
+
if (promptSummary) lines.push(` ${theme.fg("muted", `Task: ${promptSummary}`)}`);
|
|
750
|
+
lines.push(`${theme.fg("accent", "Conversation")} ${theme.fg("dim", `\xB7 ${conversationState}`)}`);
|
|
751
|
+
return lines.map((line) => truncateToWidth(line, width));
|
|
752
|
+
}
|
|
753
|
+
function fit(text, width) {
|
|
754
|
+
const clipped = truncateToWidth(text, Math.max(0, width));
|
|
755
|
+
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
756
|
+
}
|
|
757
|
+
function rightAligned(left, right, width) {
|
|
758
|
+
const rightWidth = visibleWidth(right);
|
|
759
|
+
const leftWidth = Math.max(0, width - rightWidth - 1);
|
|
760
|
+
return fit(left, leftWidth) + " ".repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);
|
|
761
|
+
}
|
|
762
|
+
function transcriptFingerprint(filePath) {
|
|
763
|
+
try {
|
|
764
|
+
const stat = fs.statSync(filePath, { bigint: true });
|
|
765
|
+
return `${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
766
|
+
} catch {
|
|
767
|
+
return "missing";
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
var SubagentFleetComponent = class {
|
|
771
|
+
snapshot = { items: [] };
|
|
772
|
+
selected = 0;
|
|
773
|
+
selectedKey;
|
|
774
|
+
viewport;
|
|
775
|
+
renderedLayout;
|
|
776
|
+
expandedTools = false;
|
|
777
|
+
promptAuditOpen = false;
|
|
778
|
+
promptAuditView = "authored";
|
|
779
|
+
actionNotice;
|
|
780
|
+
steerDraft;
|
|
781
|
+
redoGuidanceDraft;
|
|
782
|
+
steerMode = "steer";
|
|
783
|
+
stopConfirming = false;
|
|
784
|
+
actionBusy = false;
|
|
785
|
+
transcriptCache;
|
|
786
|
+
disposed = false;
|
|
787
|
+
refreshTimer;
|
|
788
|
+
refreshMs;
|
|
789
|
+
tui;
|
|
790
|
+
theme;
|
|
791
|
+
markdownTheme;
|
|
792
|
+
state;
|
|
793
|
+
done;
|
|
794
|
+
options;
|
|
795
|
+
keybindings;
|
|
796
|
+
constructor(tui, theme, state, done, options = {}) {
|
|
797
|
+
this.tui = tui;
|
|
798
|
+
this.viewport = new FleetViewport(() => this.tui.requestRender());
|
|
799
|
+
this.theme = theme;
|
|
800
|
+
this.markdownTheme = options.markdownTheme ?? getMarkdownTheme();
|
|
801
|
+
this.state = state;
|
|
802
|
+
this.done = done;
|
|
803
|
+
this.options = options;
|
|
804
|
+
this.keybindings = resolveFleetKeybindings(options.fleetKeybindings);
|
|
805
|
+
this.refreshMs = Math.max(MIN_REFRESH_MS, options.refreshMs ?? REFRESH_MS);
|
|
806
|
+
this.selectedKey = options.initialKey;
|
|
807
|
+
this.refresh();
|
|
808
|
+
this.scheduleRefresh();
|
|
809
|
+
}
|
|
810
|
+
scheduleRefresh() {
|
|
811
|
+
if (this.disposed || this.refreshTimer) return;
|
|
812
|
+
this.refreshTimer = setTimeout(() => {
|
|
813
|
+
this.refreshTimer = void 0;
|
|
814
|
+
if (this.disposed) return;
|
|
815
|
+
try {
|
|
816
|
+
this.refresh();
|
|
817
|
+
this.tui.requestRender();
|
|
818
|
+
} finally {
|
|
819
|
+
this.scheduleRefresh();
|
|
820
|
+
}
|
|
821
|
+
}, this.refreshMs);
|
|
822
|
+
this.refreshTimer.unref?.();
|
|
823
|
+
}
|
|
824
|
+
stopRefresh() {
|
|
825
|
+
this.disposed = true;
|
|
826
|
+
if (this.refreshTimer) clearTimeout(this.refreshTimer);
|
|
827
|
+
this.refreshTimer = void 0;
|
|
828
|
+
}
|
|
829
|
+
refresh() {
|
|
830
|
+
const previousKey = this.snapshot.items[this.selected]?.key ?? this.selectedKey;
|
|
831
|
+
this.snapshot = collectFleetSnapshot(this.state, this.options);
|
|
832
|
+
let preserved = previousKey ? this.snapshot.items.findIndex((item) => item.key === previousKey) : -1;
|
|
833
|
+
if (preserved < 0 && previousKey) {
|
|
834
|
+
const parentKey = visibleWorkflowParentKeyForForegroundKey(this.state, previousKey, this.snapshot.items);
|
|
835
|
+
if (parentKey) preserved = this.snapshot.items.findIndex((item) => item.key === parentKey);
|
|
836
|
+
}
|
|
837
|
+
this.selected = preserved >= 0 ? preserved : Math.min(this.selected, Math.max(0, this.snapshot.items.length - 1));
|
|
838
|
+
this.selectedKey = this.snapshot.items[this.selected]?.key;
|
|
839
|
+
}
|
|
840
|
+
moveSelection(delta) {
|
|
841
|
+
this.selectItem(Math.max(0, Math.min(this.snapshot.items.length - 1, this.selected + delta)));
|
|
842
|
+
}
|
|
843
|
+
selectItem(index) {
|
|
844
|
+
if (!this.snapshot.items[index]) return;
|
|
845
|
+
this.selected = index;
|
|
846
|
+
this.selectedKey = this.snapshot.items[this.selected]?.key;
|
|
847
|
+
this.viewport.follow();
|
|
848
|
+
this.resetActionInput();
|
|
849
|
+
this.tui.requestRender();
|
|
850
|
+
}
|
|
851
|
+
selectedPromptAudit() {
|
|
852
|
+
const item = this.snapshot.items[this.selected];
|
|
853
|
+
if (item?.kind !== "foreground-active") return void 0;
|
|
854
|
+
if (!this.state.currentSessionId || item.control.sessionId !== this.state.currentSessionId) return void 0;
|
|
855
|
+
return getLivePromptAudit(item.control, item.index ?? 0);
|
|
856
|
+
}
|
|
857
|
+
promptAuditItems() {
|
|
858
|
+
const selected = this.snapshot.items[this.selected];
|
|
859
|
+
if (selected?.kind !== "foreground-active" || !this.state.currentSessionId || selected.control.sessionId !== this.state.currentSessionId) return [];
|
|
860
|
+
return this.snapshot.items.flatMap((item) => {
|
|
861
|
+
if (item.kind !== "foreground-active" || item.control !== selected.control) return [];
|
|
862
|
+
const prompt = getLivePromptAudit(item.control, item.index ?? 0);
|
|
863
|
+
return prompt ? [{ item, prompt }] : [];
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
selectedPromptText() {
|
|
867
|
+
const prompt = this.selectedPromptAudit();
|
|
868
|
+
return prompt ? promptAuditText(prompt, this.promptAuditView) : void 0;
|
|
869
|
+
}
|
|
870
|
+
movePromptSelection(delta) {
|
|
871
|
+
const items = this.promptAuditItems();
|
|
872
|
+
if (items.length === 0) return;
|
|
873
|
+
const currentKey = this.snapshot.items[this.selected]?.key;
|
|
874
|
+
const current = Math.max(0, items.findIndex(({ item }) => item.key === currentKey));
|
|
875
|
+
const target = items[Math.max(0, Math.min(items.length - 1, current + delta))]?.item;
|
|
876
|
+
if (!target) return;
|
|
877
|
+
this.selected = this.snapshot.items.findIndex((item) => item.key === target.key);
|
|
878
|
+
this.selectedKey = target.key;
|
|
879
|
+
this.viewport.resetTop();
|
|
880
|
+
this.tui.requestRender();
|
|
881
|
+
}
|
|
882
|
+
resetActionInput() {
|
|
883
|
+
this.steerDraft = void 0;
|
|
884
|
+
this.redoGuidanceDraft = void 0;
|
|
885
|
+
this.steerMode = "steer";
|
|
886
|
+
this.stopConfirming = false;
|
|
887
|
+
}
|
|
888
|
+
selectedAsyncAction() {
|
|
889
|
+
const item = this.snapshot.items[this.selected];
|
|
890
|
+
if (!item) return { reason: "No child is selected." };
|
|
891
|
+
if (item.kind === "external") return { reason: "External jobs are display-only and remain controlled by their owning extension." };
|
|
892
|
+
if (item.kind !== "async") return { reason: "Fleet controls are available for current-session top-level async runs only." };
|
|
893
|
+
if (!isActionableAsyncState(item.run.state) || !isActionableAsyncState(item.state)) return { reason: `Selected child is ${item.state}; controls require a running or queued async child.` };
|
|
894
|
+
return { item };
|
|
895
|
+
}
|
|
896
|
+
selectedSteerAction() {
|
|
897
|
+
const item = this.snapshot.items[this.selected];
|
|
898
|
+
if (item?.kind === "foreground-active" && item.control.parentWorkflowRunId) {
|
|
899
|
+
const parent = this.state.asyncJobs.get(item.control.parentWorkflowRunId) ?? this.state.fleetJobs?.get(item.control.parentWorkflowRunId);
|
|
900
|
+
if (!parent || !isActionableAsyncState(parent.status)) return { reason: "The parent workflow is no longer available for steering." };
|
|
901
|
+
return { runId: item.runId, asyncDir: parent.asyncDir, ...item.index !== void 0 ? { index: item.index } : {} };
|
|
902
|
+
}
|
|
903
|
+
const target = this.selectedAsyncAction();
|
|
904
|
+
if ("reason" in target) return target;
|
|
905
|
+
return { runId: target.item.runId, asyncDir: target.item.run.asyncDir, ...target.item.index !== void 0 ? { index: target.item.index } : {} };
|
|
906
|
+
}
|
|
907
|
+
selectedInspectAction() {
|
|
908
|
+
const item = this.snapshot.items[this.selected];
|
|
909
|
+
if (!item) return { reason: "No child is selected." };
|
|
910
|
+
if (item.kind === "external") return { reason: "External jobs are display-only and have no inspector controls." };
|
|
911
|
+
if (item.kind === "async") {
|
|
912
|
+
if (!isActionableAsyncState(item.run.state) || !isActionableAsyncState(item.state)) return { reason: `Selected child is ${item.state}; controls require a running or queued async child.` };
|
|
913
|
+
return { runId: item.runId, asyncDir: item.run.asyncDir, ...item.index !== void 0 ? { index: item.index } : {} };
|
|
914
|
+
}
|
|
915
|
+
if (item.kind !== "foreground-active" || !item.control.parentWorkflowRunId) return { reason: "Fleet controls are available for current-session top-level async runs only." };
|
|
916
|
+
const parent = this.state.asyncJobs.get(item.control.parentWorkflowRunId) ?? this.state.fleetJobs?.get(item.control.parentWorkflowRunId);
|
|
917
|
+
if (!parent || !isActionableAsyncState(parent.status)) return { reason: "The parent workflow is no longer available for inspection." };
|
|
918
|
+
return { runId: parent.asyncId, asyncDir: parent.asyncDir };
|
|
919
|
+
}
|
|
920
|
+
inspectSelected() {
|
|
921
|
+
const target = this.selectedInspectAction();
|
|
922
|
+
if ("reason" in target || !this.options.actions?.inspect) this.setActionNotice({ text: "reason" in target ? target.reason : "Inspector controls are unavailable in this context.", isError: true });
|
|
923
|
+
else this.runAction(() => this.options.actions.inspect(target));
|
|
924
|
+
}
|
|
925
|
+
actionLines() {
|
|
926
|
+
const lines = [];
|
|
927
|
+
if (this.actionBusy) lines.push(this.theme.fg("accent", "Action pending..."));
|
|
928
|
+
if (this.steerDraft !== void 0) {
|
|
929
|
+
lines.push(this.theme.fg("accent", `Steer message (${this.steerMode}): ${this.steerDraft}${this.theme.fg("dim", "\u258C")}`));
|
|
930
|
+
lines.push(this.theme.fg("dim", "Enter sends \xB7 Tab changes mode \xB7 Esc cancels \xB7 Backspace edits"));
|
|
931
|
+
} else if (this.redoGuidanceDraft !== void 0) {
|
|
932
|
+
lines.push(this.theme.fg("accent", `Redo guidance: ${this.redoGuidanceDraft}${this.theme.fg("dim", "\u258C")}`));
|
|
933
|
+
lines.push(this.theme.fg("dim", "Enter rewrites and reruns \xB7 Esc cancels \xB7 Backspace edits"));
|
|
934
|
+
} else if (this.stopConfirming) {
|
|
935
|
+
const selected = this.snapshot.items[this.selected];
|
|
936
|
+
lines.push(this.theme.fg("warning", `Confirm stop for async run ${selected?.runId ?? "selected run"}?`));
|
|
937
|
+
lines.push(this.theme.fg("dim", "Stop ends the run; use interrupt for a resumable pause. Enter/Y confirms \xB7 N returns \xB7 Esc cancels"));
|
|
938
|
+
} else if (this.actionNotice) {
|
|
939
|
+
lines.push(this.theme.fg(this.actionNotice.isError ? "error" : "success", this.actionNotice.text));
|
|
940
|
+
}
|
|
941
|
+
return lines;
|
|
942
|
+
}
|
|
943
|
+
withActionLines(body) {
|
|
944
|
+
const actionLines = this.actionLines();
|
|
945
|
+
return actionLines.length ? [...actionLines, "", ...body] : body;
|
|
946
|
+
}
|
|
947
|
+
setActionNotice(result) {
|
|
948
|
+
this.actionNotice = result;
|
|
949
|
+
this.resetActionInput();
|
|
950
|
+
this.viewport.resetTop();
|
|
951
|
+
this.refresh();
|
|
952
|
+
this.tui.requestRender();
|
|
953
|
+
}
|
|
954
|
+
runAction(action) {
|
|
955
|
+
if (this.actionBusy) return;
|
|
956
|
+
this.actionBusy = true;
|
|
957
|
+
this.actionNotice = void 0;
|
|
958
|
+
this.tui.requestRender();
|
|
959
|
+
void action().then((result) => this.setActionNotice(result)).catch((error) => this.setActionNotice({ text: error instanceof Error ? error.message : String(error), isError: true })).finally(() => {
|
|
960
|
+
this.actionBusy = false;
|
|
961
|
+
if (!this.disposed) this.tui.requestRender();
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
scrollDetail(delta) {
|
|
965
|
+
this.viewport.scroll.scrollBy(delta);
|
|
966
|
+
}
|
|
967
|
+
handleMouse(event) {
|
|
968
|
+
if (this.steerDraft !== void 0 || this.redoGuidanceDraft !== void 0 || this.stopConfirming || this.actionBusy) {
|
|
969
|
+
return { handled: true, render: false };
|
|
970
|
+
}
|
|
971
|
+
const layout = this.renderedLayout;
|
|
972
|
+
if (!layout || layout.width !== event.width || layout.rows !== (this.tui.terminal?.rows ?? 32)) {
|
|
973
|
+
return event.type === "wheel" ? { handled: true, render: false } : void 0;
|
|
974
|
+
}
|
|
975
|
+
if (event.type === "wheel" && fleetContains(layout.output, event.x, event.y)) {
|
|
976
|
+
this.scrollDetail(event.wheelDelta ?? 0);
|
|
977
|
+
return { handled: true, render: false };
|
|
978
|
+
}
|
|
979
|
+
if (this.promptAuditOpen) return { handled: true, render: false };
|
|
980
|
+
if (event.type === "click" && event.button === "left" && !event.shift && !event.alt && !event.ctrl && fleetContains(layout.roster, event.x, event.y)) {
|
|
981
|
+
const key = layout.rosterKeys[event.y - layout.roster.y];
|
|
982
|
+
const index = key === void 0 ? -1 : this.snapshot.items.findIndex((item) => item.key === key);
|
|
983
|
+
if (index < 0) return void 0;
|
|
984
|
+
this.selectItem(index);
|
|
985
|
+
return { handled: true, render: false };
|
|
986
|
+
}
|
|
987
|
+
return event.type === "wheel" ? { handled: true, render: false } : void 0;
|
|
988
|
+
}
|
|
989
|
+
handleInput(data) {
|
|
990
|
+
if (this.promptAuditOpen && this.redoGuidanceDraft !== void 0) {
|
|
991
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
992
|
+
this.resetActionInput();
|
|
993
|
+
this.tui.requestRender();
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
if (matchesKey(data, "return") || data === "\r" || data === "\n") {
|
|
997
|
+
const guidance = this.redoGuidanceDraft.trim();
|
|
998
|
+
if (!guidance) {
|
|
999
|
+
this.setActionNotice({ text: "Redo guidance cannot be empty.", isError: true });
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const selected = this.snapshot.items[this.selected];
|
|
1003
|
+
if (selected?.kind !== "foreground-active" || !this.options.actions?.redoPrompt) {
|
|
1004
|
+
this.setActionNotice({ text: "Redo is unavailable for this prompt.", isError: true });
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
this.runAction(() => this.options.actions.redoPrompt({ runId: selected.runId, index: selected.index ?? 0, guidance, control: selected.control }));
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (matchesKey(data, "backspace") || data === "\x7F") {
|
|
1011
|
+
this.redoGuidanceDraft = this.redoGuidanceDraft.slice(0, -1);
|
|
1012
|
+
this.tui.requestRender();
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
if (data.length === 1 && data >= " " && data !== "\x7F") {
|
|
1016
|
+
this.redoGuidanceDraft += data;
|
|
1017
|
+
this.tui.requestRender();
|
|
1018
|
+
}
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (this.promptAuditOpen) {
|
|
1022
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
1023
|
+
this.promptAuditOpen = false;
|
|
1024
|
+
this.viewport.follow();
|
|
1025
|
+
this.tui.requestRender();
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (data === "j") return this.movePromptSelection(1);
|
|
1029
|
+
if (data === "k") return this.movePromptSelection(-1);
|
|
1030
|
+
if (data === "1" || data === "2" || data === "3") {
|
|
1031
|
+
this.promptAuditView = data === "1" ? "authored" : data === "2" ? "runtime" : "effective";
|
|
1032
|
+
this.viewport.resetTop();
|
|
1033
|
+
this.tui.requestRender();
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
if (data === "g") {
|
|
1037
|
+
const prompt = this.selectedPromptAudit();
|
|
1038
|
+
if (!prompt) this.setActionNotice({ text: "Prompt Audit is available only for a live child owned by this session.", isError: true });
|
|
1039
|
+
else if (!prompt.rerun) this.setActionNotice({ text: "Redo is not safe for this prompt in this slice.", isError: true });
|
|
1040
|
+
else if (!this.options.actions?.redoPrompt) this.setActionNotice({ text: "Redo controls are unavailable in this context.", isError: true });
|
|
1041
|
+
else {
|
|
1042
|
+
this.actionNotice = void 0;
|
|
1043
|
+
this.redoGuidanceDraft = "";
|
|
1044
|
+
this.viewport.resetTop();
|
|
1045
|
+
this.tui.requestRender();
|
|
1046
|
+
}
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
if (data === "c") {
|
|
1050
|
+
const text = this.selectedPromptText();
|
|
1051
|
+
if (!text) this.setActionNotice({ text: "No prompt view is available to copy.", isError: true });
|
|
1052
|
+
else if (!this.options.copyText) this.setActionNotice({ text: "Clipboard is unavailable in this context.", isError: true });
|
|
1053
|
+
else this.runAction(async () => {
|
|
1054
|
+
await this.options.copyText(text);
|
|
1055
|
+
return { text: "Copied visible prompt view." };
|
|
1056
|
+
});
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
if (matchesFleetAction(data, this.keybindings, "scrollUp")) return this.scrollDetail(-1);
|
|
1060
|
+
if (matchesFleetAction(data, this.keybindings, "scrollDown")) return this.scrollDetail(1);
|
|
1061
|
+
if (matchesFleetAction(data, this.keybindings, "pageUp")) return this.scrollDetail(-this.viewport.scroll.viewportHeight);
|
|
1062
|
+
if (matchesFleetAction(data, this.keybindings, "pageDown")) return this.scrollDetail(this.viewport.scroll.viewportHeight);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
if (this.steerDraft !== void 0) {
|
|
1066
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
1067
|
+
this.resetActionInput();
|
|
1068
|
+
this.tui.requestRender();
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
if (matchesKey(data, "return") || data === "\r" || data === "\n") {
|
|
1072
|
+
const message = this.steerDraft.trim();
|
|
1073
|
+
if (!message) {
|
|
1074
|
+
this.setActionNotice({ text: "Steer message cannot be empty.", isError: true });
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
const target = this.selectedSteerAction();
|
|
1078
|
+
if ("reason" in target || !this.options.actions) {
|
|
1079
|
+
this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
this.runAction(() => this.options.actions.steer({ ...target, message, mode: this.steerMode }));
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (matchesKey(data, "tab") || data === " ") {
|
|
1086
|
+
const modes = ["steer", "follow_up", "auto"];
|
|
1087
|
+
this.steerMode = modes[(modes.indexOf(this.steerMode) + 1) % modes.length];
|
|
1088
|
+
this.tui.requestRender();
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
if (matchesKey(data, "backspace") || data === "\x7F") {
|
|
1092
|
+
this.steerDraft = this.steerDraft.slice(0, -1);
|
|
1093
|
+
this.tui.requestRender();
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (data.length === 1 && data >= " " && data !== "\x7F") {
|
|
1097
|
+
this.steerDraft += data;
|
|
1098
|
+
this.tui.requestRender();
|
|
1099
|
+
}
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (this.stopConfirming) {
|
|
1103
|
+
if (matchesKey(data, "return") || data.toLowerCase() === "y") {
|
|
1104
|
+
const target = this.selectedAsyncAction();
|
|
1105
|
+
if ("reason" in target || !this.options.actions) {
|
|
1106
|
+
this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
this.runAction(() => Promise.resolve(this.options.actions.stop({ runId: target.item.runId, asyncDir: target.item.run.asyncDir, ...target.item.index !== void 0 ? { index: target.item.index } : {} })));
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c") || data.toLowerCase() === "n" || matchesKey(data, "backspace")) {
|
|
1113
|
+
this.resetActionInput();
|
|
1114
|
+
this.tui.requestRender();
|
|
1115
|
+
}
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (matchesFleetAction(data, this.keybindings, "close")) {
|
|
1119
|
+
this.stopRefresh();
|
|
1120
|
+
this.done(void 0);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (matchesFleetAction(data, this.keybindings, "scrollUp")) return this.scrollDetail(-1);
|
|
1124
|
+
if (matchesFleetAction(data, this.keybindings, "scrollDown")) return this.scrollDetail(1);
|
|
1125
|
+
if (matchesFleetAction(data, this.keybindings, "selectUp")) return this.moveSelection(-1);
|
|
1126
|
+
if (matchesFleetAction(data, this.keybindings, "selectDown")) return this.moveSelection(1);
|
|
1127
|
+
if (matchesFleetAction(data, this.keybindings, "selectFirst")) return this.moveSelection(-this.snapshot.items.length);
|
|
1128
|
+
if (matchesFleetAction(data, this.keybindings, "selectLast")) return this.moveSelection(this.snapshot.items.length);
|
|
1129
|
+
if (matchesFleetAction(data, this.keybindings, "pageUp")) return this.scrollDetail(-this.viewport.scroll.viewportHeight);
|
|
1130
|
+
if (matchesFleetAction(data, this.keybindings, "pageDown")) return this.scrollDetail(this.viewport.scroll.viewportHeight);
|
|
1131
|
+
if (matchesFleetAction(data, this.keybindings, "refresh")) {
|
|
1132
|
+
this.transcriptCache = void 0;
|
|
1133
|
+
this.refresh();
|
|
1134
|
+
this.tui.requestRender();
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
if (data === "p") {
|
|
1138
|
+
if (!this.selectedPromptAudit()) this.setActionNotice({ text: "Prompt Audit is available only for a live child owned by this session.", isError: true });
|
|
1139
|
+
else {
|
|
1140
|
+
this.promptAuditOpen = true;
|
|
1141
|
+
this.promptAuditView = "authored";
|
|
1142
|
+
this.viewport.resetTop();
|
|
1143
|
+
this.actionNotice = void 0;
|
|
1144
|
+
this.tui.requestRender();
|
|
1145
|
+
}
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (matchesFleetAction(data, this.keybindings, "steer")) {
|
|
1149
|
+
const target = this.selectedSteerAction();
|
|
1150
|
+
if ("reason" in target || !this.options.actions) this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
|
|
1151
|
+
else {
|
|
1152
|
+
this.actionNotice = void 0;
|
|
1153
|
+
this.steerDraft = "";
|
|
1154
|
+
this.viewport.resetTop();
|
|
1155
|
+
this.tui.requestRender();
|
|
1156
|
+
}
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (matchesFleetAction(data, this.keybindings, "stop")) {
|
|
1160
|
+
const target = this.selectedAsyncAction();
|
|
1161
|
+
if ("reason" in target || !this.options.actions) this.setActionNotice({ text: "reason" in target ? target.reason : "Fleet controls are unavailable in this context.", isError: true });
|
|
1162
|
+
else {
|
|
1163
|
+
this.actionNotice = void 0;
|
|
1164
|
+
this.stopConfirming = true;
|
|
1165
|
+
this.viewport.resetTop();
|
|
1166
|
+
this.tui.requestRender();
|
|
1167
|
+
}
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if (matchesFleetAction(data, this.keybindings, "toggleTools")) {
|
|
1171
|
+
this.expandedTools = !this.expandedTools;
|
|
1172
|
+
this.transcriptCache = void 0;
|
|
1173
|
+
this.tui.requestRender();
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if (matchesFleetAction(data, this.keybindings, "inspect")) {
|
|
1177
|
+
this.inspectSelected();
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
rosterLines(layout) {
|
|
1182
|
+
if (this.snapshot.items.length === 0) return [this.theme.fg("dim", "No tracked children")];
|
|
1183
|
+
return layout.rosterKeys.map((_key, offset) => {
|
|
1184
|
+
const index = layout.rosterStart + offset;
|
|
1185
|
+
const item = this.snapshot.items[index];
|
|
1186
|
+
const marker = index === this.selected ? this.theme.fg("accent", "\u203A") : " ";
|
|
1187
|
+
const context = item.kind === "async" ? contextModeBadge(this.theme, item.step?.context ?? item.run.context) : item.kind === "foreground-recent" ? contextModeBadge(this.theme, item.child.context) : "";
|
|
1188
|
+
const task = item.kind === "async" ? item.task : void 0;
|
|
1189
|
+
const name = task && item.kind === "async" && item.step ? item.step.agent : item.agent;
|
|
1190
|
+
const agent = index === this.selected ? this.theme.bold(name) : name;
|
|
1191
|
+
const identity = task ?? item.runId.slice(0, 8);
|
|
1192
|
+
const left = `${marker} ${statusGlyph(item, this.theme)} ${agent}${context} ${this.theme.fg("dim", `\xB7 ${identity}`)}`;
|
|
1193
|
+
return rightAligned(left, this.theme.fg("dim", item.state), layout.roster.width);
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
renderedTranscript(target, width) {
|
|
1197
|
+
const fingerprint = `${target.trustedRoots.join("\0")}|${target.trustedFiles?.join("\0") ?? ""}|${target.trustedFileRoot ?? ""}|${transcriptFingerprint(target.path)}`;
|
|
1198
|
+
if (this.transcriptCache && this.transcriptCache.path === target.path && this.transcriptCache.fingerprint === fingerprint && this.transcriptCache.width === width && this.transcriptCache.expandedTools === this.expandedTools) {
|
|
1199
|
+
return { transcript: this.transcriptCache.transcript, body: [...this.transcriptCache.body] };
|
|
1200
|
+
}
|
|
1201
|
+
const transcript = readFleetTranscript(target.path, {
|
|
1202
|
+
trustedRoots: target.trustedRoots,
|
|
1203
|
+
...target.trustedFiles ? { trustedFiles: target.trustedFiles } : {},
|
|
1204
|
+
...target.trustedFileRoot ? { trustedFileRoot: target.trustedFileRoot } : {}
|
|
1205
|
+
});
|
|
1206
|
+
const body = transcript.events.length > 0 ? renderFleetTranscript(transcript, width, this.theme, this.markdownTheme, { expandedTools: this.expandedTools }) : [];
|
|
1207
|
+
this.transcriptCache = transcript.readFailed ? void 0 : { path: target.path, fingerprint, width, expandedTools: this.expandedTools, transcript, body };
|
|
1208
|
+
return { transcript, body: [...body] };
|
|
1209
|
+
}
|
|
1210
|
+
promptAuditDetail(width) {
|
|
1211
|
+
const selected = this.snapshot.items[this.selected];
|
|
1212
|
+
const prompt = this.selectedPromptAudit();
|
|
1213
|
+
const items = this.promptAuditItems();
|
|
1214
|
+
const selectedPosition = selected ? items.findIndex(({ item }) => item.key === selected.key) : -1;
|
|
1215
|
+
const live = selected?.kind === "foreground-active" ? selected.activeChild ?? selected.control : void 0;
|
|
1216
|
+
const viewLabel = promptAuditViewLabel(this.promptAuditView);
|
|
1217
|
+
const promptText = this.selectedPromptText() ?? this.theme.fg("muted", "Selected prompt unavailable.");
|
|
1218
|
+
const raw = [
|
|
1219
|
+
this.theme.bold("Prompt Audit"),
|
|
1220
|
+
this.theme.fg("dim", "Retention: live memory only \xB7 no storage"),
|
|
1221
|
+
selected?.kind === "foreground-active" ? `Run: ${selected.runId}${selected.index !== void 0 ? ` \xB7 Child: ${selected.index}` : ""} \xB7 Agent: ${selected.agent}` : "Selected prompt unavailable",
|
|
1222
|
+
live ? `Started: ${new Date(live.startedAt).toISOString()}` : void 0,
|
|
1223
|
+
live ? `Model: ${formatModelThinking(live.model, live.thinking) || "default"}` : void 0,
|
|
1224
|
+
prompt?.cwd ? `Cwd: ${prompt.cwd}` : void 0,
|
|
1225
|
+
prompt?.outputPath ? `Output: ${prompt.outputPath}` : void 0,
|
|
1226
|
+
this.theme.fg("dim", `Live children: ${items.map(({ item }) => item.agent).join(", ") || "none"}`),
|
|
1227
|
+
this.theme.fg("dim", selectedPosition >= 0 ? `Selected: ${selectedPosition + 1}/${items.length}` : "Selected prompt unavailable"),
|
|
1228
|
+
"",
|
|
1229
|
+
this.theme.fg("accent", viewLabel),
|
|
1230
|
+
promptText
|
|
1231
|
+
];
|
|
1232
|
+
const body = raw.filter((line) => line !== void 0).flatMap((line) => wrapTextWithAnsi(line, Math.max(1, width)));
|
|
1233
|
+
return { header: [], body: this.withActionLines(body) };
|
|
1234
|
+
}
|
|
1235
|
+
wrappedDetail(width) {
|
|
1236
|
+
if (this.promptAuditOpen) return this.promptAuditDetail(width);
|
|
1237
|
+
const selected = this.snapshot.items[this.selected];
|
|
1238
|
+
let transcriptWarning;
|
|
1239
|
+
if (selected) {
|
|
1240
|
+
const target = transcriptTarget(selected, this.state);
|
|
1241
|
+
if (target) {
|
|
1242
|
+
const { transcript, body } = this.renderedTranscript(target, width);
|
|
1243
|
+
transcriptWarning = transcript.warning;
|
|
1244
|
+
if (transcript.events.length > 0) {
|
|
1245
|
+
if (this.snapshot.error) body.unshift(this.theme.fg("warning", `Fleet scan warning: ${this.snapshot.error}`), "");
|
|
1246
|
+
const latest = transcript.events.at(-1);
|
|
1247
|
+
const conversationState = latest?.kind === "assistant" ? "assistant response" : latest?.kind === "user" ? "supervisor message" : latest?.kind === "tool" ? `${latest.name} \xB7 ${latest.status}` : "activity";
|
|
1248
|
+
const promptSummary = selected.kind === "foreground-active" ? foregroundAuthoredPromptSummary(selected, this.state) : void 0;
|
|
1249
|
+
return { header: structuredHeader(selected, width, this.theme, conversationState, promptSummary), body: this.withActionLines(body) };
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
const raw = detailLines(selected, this.snapshot.error, this.state);
|
|
1254
|
+
if (transcriptWarning) raw.unshift(`Transcript preview warning: ${transcriptWarning}`, "");
|
|
1255
|
+
const lines = [];
|
|
1256
|
+
for (const line of raw) {
|
|
1257
|
+
const styled = /^(Run|State|Mode|Source|Child|Agent|Model|Task):/.test(line) ? this.theme.bold(line) : /^(Transcript|Result transcript tail)/.test(line) ? this.theme.fg("accent", line) : /^(Output|Session|Transcript file|Artifacts):/.test(line) ? this.theme.fg("muted", line) : /^Transcript preview warning:/.test(line) ? this.theme.fg("warning", line) : line;
|
|
1258
|
+
const wrapped = wrapTextWithAnsi(styled, Math.max(1, width));
|
|
1259
|
+
lines.push(...wrapped.length ? wrapped : [""]);
|
|
1260
|
+
}
|
|
1261
|
+
return { header: [], body: this.withActionLines(lines) };
|
|
1262
|
+
}
|
|
1263
|
+
render(width) {
|
|
1264
|
+
const layout = fleetLayout(width, this.tui.terminal?.rows ?? 32, this.selected, this.snapshot.items);
|
|
1265
|
+
this.renderedLayout = layout;
|
|
1266
|
+
if (!layout) return [truncateToWidth("Subagent fleet needs at least 36 columns. Esc closes.", width)];
|
|
1267
|
+
const innerWidth = width - 2;
|
|
1268
|
+
const { bodyHeight } = layout;
|
|
1269
|
+
const rosterWidth = layout.roster.width;
|
|
1270
|
+
const detailWidth = layout.output.width;
|
|
1271
|
+
const roster = this.rosterLines(layout);
|
|
1272
|
+
const detail = this.wrappedDetail(detailWidth);
|
|
1273
|
+
const detailHeader = detail.header.slice(0, Math.max(0, bodyHeight - 1));
|
|
1274
|
+
const outputBodyHeight = bodyHeight - detailHeader.length;
|
|
1275
|
+
const visibleDetails = [
|
|
1276
|
+
...detailHeader,
|
|
1277
|
+
...this.viewport.render(detail.body, detailWidth, outputBodyHeight)
|
|
1278
|
+
];
|
|
1279
|
+
const lines = [this.theme.fg("border", `\u256D${"\u2500".repeat(innerWidth)}\u256E`)];
|
|
1280
|
+
const selected = this.snapshot.items[this.selected];
|
|
1281
|
+
const title = selected?.kind === "external" ? ` ${this.theme.bold("Fleet inspector")} ${this.theme.fg("dim", "\xB7 external display-only")}` : ` ${this.theme.bold("Subagent fleet inspector")} ${this.theme.fg("dim", "\xB7 live controls")}`;
|
|
1282
|
+
const selectedStatus = selected ? `${statusGlyph(selected, this.theme)} ${selected.agent} \xB7 ${selected.state} ` : this.theme.fg("dim", "no children ");
|
|
1283
|
+
lines.push(this.theme.fg("border", "\u2502") + rightAligned(title, selectedStatus, innerWidth) + this.theme.fg("border", "\u2502"));
|
|
1284
|
+
lines.push(this.theme.fg("border", `\u251C${"\u2500".repeat(rosterWidth)}\u252C${"\u2500".repeat(detailWidth)}\u2524`));
|
|
1285
|
+
for (let index = 0; index < bodyHeight; index++) {
|
|
1286
|
+
lines.push(
|
|
1287
|
+
this.theme.fg("border", "\u2502") + fit(roster[index] ?? "", rosterWidth) + this.theme.fg("border", "\u2502") + fit(visibleDetails[index] ?? "", detailWidth) + this.theme.fg("border", "\u2502")
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
lines.push(this.theme.fg("border", `\u251C${"\u2500".repeat(rosterWidth)}\u2534${"\u2500".repeat(detailWidth)}\u2524`));
|
|
1291
|
+
const position = this.snapshot.items.length ? `${this.selected + 1}/${this.snapshot.items.length}` : "0/0";
|
|
1292
|
+
const footer = this.promptAuditOpen ? ` j/k child \xB7 1/2/3 view \xB7 g redo with guidance \xB7 c copy \xB7 Esc close Prompt Audit \xB7 ${position}` : selected?.kind === "external" ? ` ${bindingLabel(this.keybindings, "selectUp")}/${bindingLabel(this.keybindings, "selectDown")} job \xB7 display-only \xB7 ${bindingLabel(this.keybindings, "refresh")} refresh \xB7 ${bindingLabel(this.keybindings, "close")} close \xB7 ${position}` : ` ${bindingLabel(this.keybindings, "selectUp")}/${bindingLabel(this.keybindings, "selectDown")} agent \xB7 p Prompt Audit \xB7 ${bindingLabel(this.keybindings, "inspect")} Inspect \xB7 ${bindingLabel(this.keybindings, "steer")} steer \xB7 ${bindingLabel(this.keybindings, "stop")} stop \xB7 ${bindingLabel(this.keybindings, "toggleTools")} tools \xB7 ${bindingLabel(this.keybindings, "refresh")} refresh \xB7 ${bindingLabel(this.keybindings, "close")} close \xB7 ${position}`;
|
|
1293
|
+
lines.push(this.theme.fg("border", "\u2502") + fit(this.theme.fg("dim", footer), innerWidth) + this.theme.fg("border", "\u2502"));
|
|
1294
|
+
lines.push(this.theme.fg("border", `\u2570${"\u2500".repeat(innerWidth)}\u256F`));
|
|
1295
|
+
return lines.map((line) => truncateToWidth(line, width));
|
|
1296
|
+
}
|
|
1297
|
+
invalidate() {
|
|
1298
|
+
this.renderedLayout = void 0;
|
|
1299
|
+
this.transcriptCache = void 0;
|
|
1300
|
+
this.refresh();
|
|
1301
|
+
}
|
|
1302
|
+
dispose() {
|
|
1303
|
+
this.stopRefresh();
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
async function openSubagentFleet(ctx, state, options = {}) {
|
|
1307
|
+
const wasOpen = state.fleetInspectorOpen === true;
|
|
1308
|
+
state.fleetInspectorOpen = true;
|
|
1309
|
+
if (typeof ctx.ui.setWidget === "function") ctx.ui.setWidget(FLEET_STATUS_WIDGET_KEY, void 0);
|
|
1310
|
+
const copyText = options.copyText ?? (async (text) => {
|
|
1311
|
+
const module = await import("@earendil-works/pi-coding-agent");
|
|
1312
|
+
const copyToClipboard = module.copyToClipboard;
|
|
1313
|
+
if (!copyToClipboard) throw new Error("Clipboard is unavailable in this Pi version.");
|
|
1314
|
+
await copyToClipboard(text);
|
|
1315
|
+
});
|
|
1316
|
+
const actions = options.actions ?? {
|
|
1317
|
+
steer: async (input) => {
|
|
1318
|
+
const status = readStatus(input.asyncDir);
|
|
1319
|
+
const liveWorkflowRunId = status?.mode === "workflow" && state.workflowControllers?.has(status.runId || input.runId) ? status.runId || input.runId : void 0;
|
|
1320
|
+
if (liveWorkflowRunId) {
|
|
1321
|
+
const route = state.foregroundControls.get(input.runId)?.parentWorkflowRunId === liveWorkflowRunId ? resolveWorkflowForegroundSteeringTarget({ state, childRunId: input.runId, asyncDirRoot: options.asyncDirRoot ?? DIRS.async }) : resolveWorkflowForegroundSteeringTarget({ state, workflowRunId: liveWorkflowRunId, asyncDirRoot: options.asyncDirRoot ?? DIRS.async });
|
|
1322
|
+
if (!route.ok) return { text: route.message, isError: true };
|
|
1323
|
+
return firstToolResultText(await steerWorkflowForegroundTarget({ target: route.target, message: input.message, mode: input.mode, ...input.index !== void 0 ? { index: input.index } : {} }), `Failed to steer foreground run ${input.runId}.`);
|
|
1324
|
+
}
|
|
1325
|
+
return firstToolResultText(await steerAsyncRun({
|
|
1326
|
+
state,
|
|
1327
|
+
runId: input.runId,
|
|
1328
|
+
...input.index !== void 0 ? { index: input.index } : {},
|
|
1329
|
+
message: input.message,
|
|
1330
|
+
mode: input.mode,
|
|
1331
|
+
location: { asyncDir: input.asyncDir, resolvedId: input.runId }
|
|
1332
|
+
}), `Failed to steer async run ${input.runId}.`);
|
|
1333
|
+
},
|
|
1334
|
+
stop: (input) => firstToolResultText(stopAsyncRun(state, input.runId, void 0, { asyncDir: input.asyncDir, resolvedId: input.runId }), `Failed to stop async run ${input.runId}.`),
|
|
1335
|
+
inspect: async (input) => firstToolResultText(await handleInspectorAction("inspector.open", {
|
|
1336
|
+
id: input.runId,
|
|
1337
|
+
dir: input.asyncDir,
|
|
1338
|
+
focus: true,
|
|
1339
|
+
...input.index !== void 0 ? { index: input.index } : {}
|
|
1340
|
+
}, {
|
|
1341
|
+
state,
|
|
1342
|
+
sessionRoots: state.trustedSessionRoots,
|
|
1343
|
+
cwd: state.baseCwd,
|
|
1344
|
+
...state.authorityPolicy ? { authorityPolicy: state.authorityPolicy } : {},
|
|
1345
|
+
...state.missionStoreConfig ? { missions: state.missionStoreConfig } : {},
|
|
1346
|
+
plugins: options.inspectorPlugins?.(),
|
|
1347
|
+
...options.inspectorEnv ? { env: options.inspectorEnv } : {}
|
|
1348
|
+
}), `Failed to open inspector for async run ${input.runId}.`),
|
|
1349
|
+
redoPrompt: async (input) => {
|
|
1350
|
+
const control = input.control ?? state.foregroundControls.get(input.runId);
|
|
1351
|
+
if (!control?.promptAuditRedo) return { text: "Redo is not available for this live child.", isError: true };
|
|
1352
|
+
return control.promptAuditRedo(input.index, input.guidance);
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
try {
|
|
1356
|
+
await ctx.ui.custom(
|
|
1357
|
+
(tui, theme, _keybindings, done) => new SubagentFleetComponent(tui, theme, state, done, { ...options, actions, copyText }),
|
|
1358
|
+
{
|
|
1359
|
+
overlay: true,
|
|
1360
|
+
overlayOptions: { anchor: "center", width: "95%", minWidth: 60, maxHeight: "85%", margin: 1 }
|
|
1361
|
+
}
|
|
1362
|
+
);
|
|
1363
|
+
} finally {
|
|
1364
|
+
state.fleetInspectorOpen = wasOpen;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
export {
|
|
1368
|
+
DEFAULT_FLEET_KEYBINDINGS,
|
|
1369
|
+
SubagentFleetComponent,
|
|
1370
|
+
collectFleetSnapshot,
|
|
1371
|
+
openSubagentFleet,
|
|
1372
|
+
resolveFleetKeybindings
|
|
1373
|
+
};
|