@oh-my-pi/pi-tui 18.13.0 → 18.13.2
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 +36 -32
- package/dist/types/chrome/context-map.d.ts +15 -0
- package/dist/types/chrome/context-presentation.d.ts +48 -0
- package/dist/types/chrome/context-view-model.d.ts +77 -0
- package/dist/types/overlays/advisor-config.d.ts +6 -0
- package/dist/types/overlays/ask-dialog.d.ts +12 -3
- package/dist/types/overlays/context-viewer-format.d.ts +42 -0
- package/dist/types/overlays/context-viewer-overview.d.ts +70 -0
- package/dist/types/overlays/context-viewer.d.ts +25 -0
- package/dist/types/overlays/hook-input.d.ts +1 -0
- package/dist/types/overlays/pause-screen.d.ts +13 -3
- package/dist/types/overlays/running-subagent-badge.d.ts +7 -1
- package/dist/types/status-line/component.d.ts +2 -2
- package/dist/types/status-line/context-usage.d.ts +15 -30
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +4 -0
- package/dist/types/tools/fetch.d.ts +1 -0
- package/dist/types/tools/github.d.ts +3 -0
- package/dist/types/tools/read.d.ts +3 -0
- package/dist/types/tools/wait.d.ts +6 -0
- package/dist/types/tui.d.ts +2 -0
- package/package.json +9 -9
- package/src/chrome/context-map.ts +62 -0
- package/src/chrome/context-presentation.ts +26 -0
- package/src/chrome/context-view-model.ts +73 -0
- package/src/components/editor.ts +23 -0
- package/src/components/markdown.ts +14 -2
- package/src/overlays/advisor-config.ts +9 -2
- package/src/overlays/ask-dialog.ts +122 -35
- package/src/overlays/context-viewer-format.ts +145 -0
- package/src/overlays/context-viewer-overview.ts +381 -0
- package/src/overlays/context-viewer.ts +728 -0
- package/src/overlays/hook-input.ts +2 -0
- package/src/overlays/pause-screen.ts +166 -37
- package/src/overlays/running-subagent-badge.ts +12 -1
- package/src/render/output-pane.ts +3 -3
- package/src/status-line/component.ts +6 -4
- package/src/status-line/context-usage.ts +56 -580
- package/src/theme/symbols.ts +16 -0
- package/src/tools/ask.ts +4 -0
- package/src/tools/bash.ts +5 -23
- package/src/tools/fetch.ts +1 -0
- package/src/tools/github.ts +23 -3
- package/src/tools/read.ts +3 -1
- package/src/tools/wait.ts +6 -0
- package/src/tui.ts +23 -3
|
@@ -21,6 +21,7 @@ export interface HookInputOptions {
|
|
|
21
21
|
tui?: TUI;
|
|
22
22
|
timeout?: number;
|
|
23
23
|
onTimeout?: () => void;
|
|
24
|
+
secret?: boolean;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/** Terminal-only key legend under the field; a native terminal puts the keys in the buttons' tooltips. */
|
|
@@ -84,6 +85,7 @@ export class HookInputComponent extends OverlayPanel {
|
|
|
84
85
|
|
|
85
86
|
this.#field = new TextFormField({
|
|
86
87
|
theme: formTheme,
|
|
88
|
+
secret: opts?.secret ?? false,
|
|
87
89
|
empty: "submit",
|
|
88
90
|
onSubmit: value => this.#onSubmitCallback(value),
|
|
89
91
|
onCancel: () => this.#onCancelCallback(),
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { agentPauseGate } from "@oh-my-pi/pi-agent-core";
|
|
15
15
|
import { type Component, matchesKey, type OverlayFocusOwner, type OverlayHandle, type OverlayOptions } from "../index";
|
|
16
16
|
import { formatCoarseDuration } from "../chrome/format";
|
|
17
|
-
import { centerLine } from "../utils";
|
|
17
|
+
import { centerLine, visibleWidth } from "../utils";
|
|
18
18
|
import { theme } from "../theme/theme";
|
|
19
19
|
import { matchesAppInterrupt } from "../keybinding-matchers";
|
|
20
20
|
import { formatKeyHint, formatTooltipKey } from "../app-keybindings";
|
|
@@ -24,6 +24,17 @@ import { col, keyed, node, row, span, text } from "../native/describe";
|
|
|
24
24
|
import { actionBar, actionButton } from "../native/overlay";
|
|
25
25
|
import { isNativeRendering } from "../native/state";
|
|
26
26
|
|
|
27
|
+
/** Outstanding turn and job counts supplied by the pause-screen host. */
|
|
28
|
+
export interface PauseWorkStatus {
|
|
29
|
+
unsettledTurns: number;
|
|
30
|
+
unfinishedJobs: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Optional live work source for the pause overlay. */
|
|
34
|
+
export interface PauseScreenOptions {
|
|
35
|
+
getWorkStatus?: () => PauseWorkStatus;
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
/**
|
|
28
39
|
* Slice of `InteractiveModeContext` the pause screen drives. Narrow so tests
|
|
29
40
|
* can exercise the full engage → hold → release lifecycle without a real TUI.
|
|
@@ -39,9 +50,10 @@ export interface PauseScreenHost {
|
|
|
39
50
|
readonly sessionName?: string;
|
|
40
51
|
}
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
const TICK_MS = 1_000;
|
|
53
|
+
const EMPTY_WORK_STATUS: PauseWorkStatus = { unsettledTurns: 0, unfinishedJobs: 0 };
|
|
44
54
|
|
|
55
|
+
/** Refresh cadence for the live hold clock and work-status snapshot. */
|
|
56
|
+
const TICK_MS = 1_000;
|
|
45
57
|
/** Pause-bar glyph geometry (rows × columns of full blocks per bar). */
|
|
46
58
|
const BAR_ROWS = 7;
|
|
47
59
|
const BAR_WIDTH = 5;
|
|
@@ -49,7 +61,7 @@ const BAR_GAP = 4;
|
|
|
49
61
|
|
|
50
62
|
/** Below either bound the full scene cannot breathe; drop to the compact card. */
|
|
51
63
|
const MIN_FULL_WIDTH = 64;
|
|
52
|
-
const MIN_FULL_HEIGHT =
|
|
64
|
+
const MIN_FULL_HEIGHT = 23;
|
|
53
65
|
|
|
54
66
|
const TITLE = "P A U S E D";
|
|
55
67
|
const BODY_LINES = [
|
|
@@ -79,51 +91,110 @@ function formatClock(ms: number): string {
|
|
|
79
91
|
* Paint the pause scene as exactly `height` rows, vertically centered.
|
|
80
92
|
* Exported for tests.
|
|
81
93
|
*/
|
|
82
|
-
export function renderPauseScreen(
|
|
83
|
-
|
|
94
|
+
export function renderPauseScreen(
|
|
95
|
+
width: number,
|
|
96
|
+
height: number,
|
|
97
|
+
elapsedMs: number,
|
|
98
|
+
sessionName?: string,
|
|
99
|
+
workStatus: PauseWorkStatus = EMPTY_WORK_STATUS,
|
|
100
|
+
): string[] {
|
|
101
|
+
const screenWidth = Math.max(1, Math.floor(width));
|
|
102
|
+
const screenHeight = Math.max(1, Math.floor(height));
|
|
103
|
+
const compact = screenWidth < MIN_FULL_WIDTH || screenHeight < MIN_FULL_HEIGHT;
|
|
104
|
+
const outstanding = workStatus.unsettledTurns > 0 || workStatus.unfinishedJobs > 0;
|
|
105
|
+
const status = outstanding ? "Pausing — waiting for work to settle" : "Paused — ready to suspend";
|
|
106
|
+
const workCounts = [
|
|
107
|
+
`Unsettled turns: ${workStatus.unsettledTurns}`,
|
|
108
|
+
`Unfinished jobs: ${workStatus.unfinishedJobs}`,
|
|
109
|
+
];
|
|
84
110
|
const content: string[] = [];
|
|
85
111
|
// Resume keys mirror PauseScreenComponent#handleInput: `app.interrupt` (raw Escape when unbound), Enter, Space.
|
|
86
112
|
const esc = interruptKey();
|
|
87
113
|
|
|
88
114
|
if (compact) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
115
|
+
const sessionLine = sessionName
|
|
116
|
+
? centerLine(theme.bold(sessionName.replace(/\s+/g, " ").trim()), screenWidth).trimEnd()
|
|
117
|
+
: undefined;
|
|
118
|
+
const titleLine = centerLine(theme.bold(theme.fg("accent", `▌▌ ${TITLE}`)), screenWidth).trimEnd();
|
|
119
|
+
const clockLine = centerLine(theme.fg("dim", `paused for ${formatClock(elapsedMs)}`), screenWidth).trimEnd();
|
|
120
|
+
const statusLine = centerLine(theme.fg(outstanding ? "warning" : "success", status), screenWidth).trimEnd();
|
|
121
|
+
const resumeLine = centerLine(theme.fg("dim", `${esc} to resume`), screenWidth).trimEnd();
|
|
122
|
+
const fullContent: string[] = [];
|
|
123
|
+
if (sessionLine) fullContent.push(sessionLine);
|
|
124
|
+
fullContent.push(titleLine, "", clockLine, statusLine);
|
|
125
|
+
if (outstanding)
|
|
126
|
+
fullContent.push(...workCounts.map(line => centerLine(theme.fg("muted", line), screenWidth).trimEnd()));
|
|
127
|
+
fullContent.push(resumeLine);
|
|
128
|
+
|
|
129
|
+
if (screenHeight >= fullContent.length) {
|
|
130
|
+
content.push(...fullContent);
|
|
131
|
+
} else {
|
|
132
|
+
// Keep the live state and a way out on screen before the decorative card details.
|
|
133
|
+
if (screenHeight === 1) {
|
|
134
|
+
const counts = `T:${workStatus.unsettledTurns} J:${workStatus.unfinishedJobs}`;
|
|
135
|
+
const summary = outstanding ? `${counts} · ${esc} to resume` : `Paused · ${esc} to resume`;
|
|
136
|
+
const abbreviatedSummary = outstanding
|
|
137
|
+
? `T${workStatus.unsettledTurns} J${workStatus.unfinishedJobs} ${esc} resume`
|
|
138
|
+
: `${esc} resume`;
|
|
139
|
+
const visibleSummary = visibleWidth(summary) > screenWidth ? abbreviatedSummary : summary;
|
|
140
|
+
content.push(
|
|
141
|
+
centerLine(theme.fg(outstanding ? "warning" : "success", visibleSummary), screenWidth).trimEnd(),
|
|
142
|
+
);
|
|
143
|
+
} else if (screenHeight === 2 && outstanding) {
|
|
144
|
+
const counts = `Pausing · T:${workStatus.unsettledTurns} J:${workStatus.unfinishedJobs}`;
|
|
145
|
+
content.push(centerLine(theme.fg("warning", counts), screenWidth).trimEnd(), resumeLine);
|
|
146
|
+
} else {
|
|
147
|
+
content.push(statusLine);
|
|
148
|
+
if (outstanding && screenHeight === 3) {
|
|
149
|
+
content.push(
|
|
150
|
+
centerLine(
|
|
151
|
+
theme.fg("muted", `Turns: ${workStatus.unsettledTurns} · Jobs: ${workStatus.unfinishedJobs}`),
|
|
152
|
+
screenWidth,
|
|
153
|
+
).trimEnd(),
|
|
154
|
+
);
|
|
155
|
+
} else if (outstanding) {
|
|
156
|
+
content.push(...workCounts.map(line => centerLine(theme.fg("muted", line), screenWidth).trimEnd()));
|
|
157
|
+
}
|
|
158
|
+
content.push(resumeLine);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (content.length < screenHeight && sessionLine) content.push(sessionLine);
|
|
162
|
+
if (content.length < screenHeight) content.push(titleLine);
|
|
163
|
+
if (content.length < screenHeight) content.push(clockLine);
|
|
92
164
|
}
|
|
93
|
-
content.push(centerLine(theme.bold(theme.fg("accent", `▌▌ ${TITLE}`)), width).trimEnd());
|
|
94
|
-
content.push("");
|
|
95
|
-
content.push(centerLine(theme.fg("dim", `paused for ${formatClock(elapsedMs)}`), width).trimEnd());
|
|
96
|
-
content.push(centerLine(theme.fg("dim", `${esc} to resume`), width).trimEnd());
|
|
97
165
|
} else {
|
|
98
166
|
if (sessionName) {
|
|
99
|
-
content.push(centerLine(theme.bold(sessionName),
|
|
100
|
-
content.push("");
|
|
167
|
+
content.push(centerLine(theme.bold(sessionName.replace(/\s+/g, " ").trim()), screenWidth).trimEnd());
|
|
101
168
|
content.push("");
|
|
102
169
|
}
|
|
103
170
|
const bar = "█".repeat(BAR_WIDTH);
|
|
104
171
|
const glyphRow = `${bar}${" ".repeat(BAR_GAP)}${bar}`;
|
|
105
172
|
for (let i = 0; i < BAR_ROWS; i++) {
|
|
106
|
-
content.push(centerLine(theme.fg("accent", glyphRow),
|
|
173
|
+
content.push(centerLine(theme.fg("accent", glyphRow), screenWidth).trimEnd());
|
|
107
174
|
}
|
|
108
175
|
content.push("");
|
|
109
|
-
content.push(centerLine(theme.bold(theme.fg("accent", TITLE)),
|
|
176
|
+
content.push(centerLine(theme.bold(theme.fg("accent", TITLE)), screenWidth).trimEnd());
|
|
110
177
|
content.push("");
|
|
111
178
|
for (const line of BODY_LINES) {
|
|
112
|
-
content.push(centerLine(theme.fg("muted", line),
|
|
179
|
+
content.push(centerLine(theme.fg("muted", line), screenWidth).trimEnd());
|
|
113
180
|
}
|
|
114
|
-
content.push("");
|
|
115
|
-
|
|
181
|
+
content.push(centerLine(theme.fg(outstanding ? "warning" : "success", status), screenWidth).trimEnd());
|
|
182
|
+
if (outstanding) {
|
|
183
|
+
content.push(centerLine(theme.fg("muted", workCounts[0]!), screenWidth).trimEnd());
|
|
184
|
+
content.push(centerLine(theme.fg("muted", workCounts[1]!), screenWidth).trimEnd());
|
|
185
|
+
}
|
|
186
|
+
content.push(centerLine(theme.fg("dim", `paused for ${formatClock(elapsedMs)}`), screenWidth).trimEnd());
|
|
116
187
|
content.push("");
|
|
117
188
|
const resumeHint = `${esc} · ${formatKeyHint("enter")} · ${formatKeyHint("space")} — resume`;
|
|
118
|
-
content.push(centerLine(theme.fg("dim", resumeHint),
|
|
189
|
+
content.push(centerLine(theme.fg("dim", resumeHint), screenWidth).trimEnd());
|
|
119
190
|
}
|
|
120
191
|
|
|
121
|
-
const topPad = Math.max(0, Math.floor((
|
|
192
|
+
const topPad = Math.max(0, Math.floor((screenHeight - content.length) / 2));
|
|
122
193
|
// oxlint-disable-next-line unicorn/no-new-array -- length preallocation
|
|
123
194
|
const lines: string[] = new Array(topPad).fill("");
|
|
124
195
|
lines.push(...content);
|
|
125
|
-
while (lines.length <
|
|
126
|
-
return lines.slice(0,
|
|
196
|
+
while (lines.length < screenHeight) lines.push("");
|
|
197
|
+
return lines.slice(0, screenHeight);
|
|
127
198
|
}
|
|
128
199
|
|
|
129
200
|
/** Fullscreen overlay component; resolves {@link run} when a resume key lands. */
|
|
@@ -133,19 +204,25 @@ export class PauseScreenComponent implements Component, OverlayFocusOwner {
|
|
|
133
204
|
#disposed = false;
|
|
134
205
|
#startedAt = Date.now();
|
|
135
206
|
#native: NativeNode | undefined;
|
|
207
|
+
#workSnapshot: PauseWorkStatus;
|
|
136
208
|
|
|
137
|
-
constructor(
|
|
209
|
+
constructor(
|
|
210
|
+
readonly host: PauseScreenHost,
|
|
211
|
+
readonly options: PauseScreenOptions = {},
|
|
212
|
+
) {
|
|
213
|
+
this.#workSnapshot = this.#readWorkSnapshot();
|
|
214
|
+
}
|
|
138
215
|
|
|
139
216
|
/** Start the clock; resolves once the user asks to resume. */
|
|
140
217
|
run(): Promise<void> {
|
|
141
218
|
this.#startedAt = agentPauseGate.pausedAt ?? Date.now();
|
|
142
219
|
this.#native = undefined;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.#
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
220
|
+
this.#refreshWorkSnapshot();
|
|
221
|
+
this.#timer ??= setInterval(() => {
|
|
222
|
+
if (this.#disposed) return;
|
|
223
|
+
const statusChanged = this.#refreshWorkSnapshot();
|
|
224
|
+
if (statusChanged || !isNativeRendering()) this.host.ui.requestRender();
|
|
225
|
+
}, TICK_MS);
|
|
149
226
|
this.host.ui.requestRender();
|
|
150
227
|
return this.#done.promise;
|
|
151
228
|
}
|
|
@@ -181,6 +258,25 @@ export class PauseScreenComponent implements Component, OverlayFocusOwner {
|
|
|
181
258
|
this.#native = undefined;
|
|
182
259
|
}
|
|
183
260
|
|
|
261
|
+
#readWorkSnapshot(): PauseWorkStatus {
|
|
262
|
+
const workStatus = this.options.getWorkStatus?.() ?? EMPTY_WORK_STATUS;
|
|
263
|
+
return {
|
|
264
|
+
unsettledTurns: workStatus.unsettledTurns,
|
|
265
|
+
unfinishedJobs: workStatus.unfinishedJobs,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#refreshWorkSnapshot(): boolean {
|
|
270
|
+
const next = this.#readWorkSnapshot();
|
|
271
|
+
const previous = this.#workSnapshot;
|
|
272
|
+
if (next.unsettledTurns === previous.unsettledTurns && next.unfinishedJobs === previous.unfinishedJobs) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
this.#workSnapshot = next;
|
|
276
|
+
this.#native = undefined;
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
184
280
|
/** A glass sheet titled "Paused" (not the whole pane: the transcript stays visible, frozen). */
|
|
185
281
|
readonly nativeOverlay = { role: "omp.overlay.pause", size: "md", anchor: "center", head: "Paused" } as const;
|
|
186
282
|
|
|
@@ -191,6 +287,41 @@ export class PauseScreenComponent implements Component, OverlayFocusOwner {
|
|
|
191
287
|
*/
|
|
192
288
|
describe(): NativeNode {
|
|
193
289
|
if (this.#native) return this.#native;
|
|
290
|
+
const snapshot = this.#workSnapshot;
|
|
291
|
+
const outstanding = snapshot.unsettledTurns > 0 || snapshot.unfinishedJobs > 0;
|
|
292
|
+
const status = col(
|
|
293
|
+
[
|
|
294
|
+
keyed(
|
|
295
|
+
text(
|
|
296
|
+
[
|
|
297
|
+
span(
|
|
298
|
+
outstanding ? "Pausing — waiting for work to settle" : "Paused — ready to suspend",
|
|
299
|
+
outstanding ? "warning" : "success",
|
|
300
|
+
),
|
|
301
|
+
],
|
|
302
|
+
{ role: "omp.pause.settlement" },
|
|
303
|
+
),
|
|
304
|
+
"settlement",
|
|
305
|
+
),
|
|
306
|
+
...(outstanding
|
|
307
|
+
? [
|
|
308
|
+
keyed(
|
|
309
|
+
text([span(`Unsettled turns: ${snapshot.unsettledTurns}`, "muted")], {
|
|
310
|
+
role: "omp.pause.unsettled-turn-count",
|
|
311
|
+
}),
|
|
312
|
+
"unsettled-turns",
|
|
313
|
+
),
|
|
314
|
+
keyed(
|
|
315
|
+
text([span(`Unfinished jobs: ${snapshot.unfinishedJobs}`, "muted")], {
|
|
316
|
+
role: "omp.pause.unfinished-job-count",
|
|
317
|
+
}),
|
|
318
|
+
"unfinished-jobs",
|
|
319
|
+
),
|
|
320
|
+
]
|
|
321
|
+
: []),
|
|
322
|
+
],
|
|
323
|
+
{ role: "omp.pause.settlement-status", gap: "sm" },
|
|
324
|
+
);
|
|
194
325
|
const resumeKey = boundKeys("app.interrupt", ["escape"])[0] ?? "escape";
|
|
195
326
|
const hero = row(
|
|
196
327
|
[
|
|
@@ -244,12 +375,9 @@ export class PauseScreenComponent implements Component, OverlayFocusOwner {
|
|
|
244
375
|
this.#native = col(
|
|
245
376
|
[
|
|
246
377
|
keyed(hero, "hero"),
|
|
378
|
+
keyed(status, "settlement-status"),
|
|
247
379
|
keyed(facts, "facts"),
|
|
248
380
|
actionBar([
|
|
249
|
-
keyed(
|
|
250
|
-
text([span("Each agent continues where it stopped.", "muted")], { role: "omp.pause.note" }),
|
|
251
|
-
"note",
|
|
252
|
-
),
|
|
253
381
|
null,
|
|
254
382
|
actionButton("Resume", "resume", {
|
|
255
383
|
keys: resumeKey,
|
|
@@ -276,6 +404,7 @@ export class PauseScreenComponent implements Component, OverlayFocusOwner {
|
|
|
276
404
|
Math.max(1, this.host.ui.terminal.rows),
|
|
277
405
|
elapsed,
|
|
278
406
|
this.host.sessionName,
|
|
407
|
+
this.#workSnapshot,
|
|
279
408
|
);
|
|
280
409
|
}
|
|
281
410
|
}
|
|
@@ -286,9 +415,9 @@ export class PauseScreenComponent implements Component, OverlayFocusOwner {
|
|
|
286
415
|
* gate on the way out (including teardown throws) — a leaked pause would
|
|
287
416
|
* freeze every agent in the process with no UI left to release it.
|
|
288
417
|
*/
|
|
289
|
-
export async function runPauseScreen(host: PauseScreenHost): Promise<void> {
|
|
418
|
+
export async function runPauseScreen(host: PauseScreenHost, options?: PauseScreenOptions): Promise<void> {
|
|
290
419
|
if (!agentPauseGate.pause()) return;
|
|
291
|
-
const component = new PauseScreenComponent(host);
|
|
420
|
+
const component = new PauseScreenComponent(host, options);
|
|
292
421
|
const overlay = host.ui.showOverlay(component, {
|
|
293
422
|
width: "100%",
|
|
294
423
|
maxHeight: "100%",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AgentHubRegistry, MAIN_AGENT_ID } from "./agent-hub-types";
|
|
2
2
|
|
|
3
3
|
export interface RunningSubagentRegistrySource<T extends AgentHubRegistry = AgentHubRegistry> {
|
|
4
4
|
agentRegistry: T;
|
|
@@ -17,3 +17,14 @@ export function getRunningSubagentBadgeAgentIds(registry: AgentHubRegistry): str
|
|
|
17
17
|
.filter(ref => ref.kind === "sub" && ref.status === "running")
|
|
18
18
|
.map(ref => ref.id);
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Agents the Agent Hub roster can show (every registry ref but the main
|
|
23
|
+
* session), whatever their status: finished, parked and killed subagents
|
|
24
|
+
* stay inspectable after the last one stops running.
|
|
25
|
+
*/
|
|
26
|
+
export function getInspectableSubagentCount(registry: AgentHubRegistry): number {
|
|
27
|
+
let count = 0;
|
|
28
|
+
for (const ref of registry.list()) if (ref.id !== MAIN_AGENT_ID) count++;
|
|
29
|
+
return count;
|
|
30
|
+
}
|
|
@@ -296,8 +296,8 @@ export class OutputPane implements Component {
|
|
|
296
296
|
* *without* the pane hearing about it, and every one of them shapes the
|
|
297
297
|
* content:
|
|
298
298
|
*
|
|
299
|
-
* - `
|
|
300
|
-
* the inner `Text` render.
|
|
299
|
+
* - `contentWidth` — the wrapping width that feeds the visual cap as well
|
|
300
|
+
* as the inner `Text` render: `width` minus both horizontal paddings.
|
|
301
301
|
* - `widthConfigEpoch` — the width-affecting runtime configuration (the
|
|
302
302
|
* Hangul compatibility-Jamo width), which `visibleWidth` — and so the
|
|
303
303
|
* visual cap — depends on.
|
|
@@ -317,7 +317,7 @@ export class OutputPane implements Component {
|
|
|
317
317
|
render(width: number): readonly string[] {
|
|
318
318
|
const paddingX = getPaddingX(this.#options.paddingX ?? 0);
|
|
319
319
|
const contentWidth = Math.max(1, width - paddingX * 2);
|
|
320
|
-
const content = this.#memo.get([...this.#renderDeps(),
|
|
320
|
+
const content = this.#memo.get([...this.#renderDeps(), contentWidth], () => {
|
|
321
321
|
const formatted = formatOutputPaneLines(
|
|
322
322
|
{
|
|
323
323
|
...this.#options,
|
|
@@ -990,24 +990,26 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
990
990
|
this.#invalidateStatusLineRenderCache();
|
|
991
991
|
}
|
|
992
992
|
|
|
993
|
-
setRunningSubagents(agentIds: readonly string[]):
|
|
993
|
+
setRunningSubagents(agentIds: readonly string[]): boolean {
|
|
994
994
|
if (
|
|
995
995
|
agentIds.length === this.#runningSubagentIds.size &&
|
|
996
996
|
agentIds.every(agentId => this.#runningSubagentIds.has(agentId))
|
|
997
997
|
) {
|
|
998
|
-
return;
|
|
998
|
+
return false;
|
|
999
999
|
}
|
|
1000
1000
|
this.#subagentCount = agentIds.length;
|
|
1001
1001
|
this.#runningSubagentIds = new Set(agentIds);
|
|
1002
1002
|
this.#invalidateStatusLineRenderCache();
|
|
1003
|
+
return true;
|
|
1003
1004
|
}
|
|
1004
1005
|
|
|
1005
1006
|
/** Host-computed spend of the main session's whole subagent tree (Agent Hub projection). */
|
|
1006
|
-
setSubagentTreeCost(cost: number):
|
|
1007
|
+
setSubagentTreeCost(cost: number): boolean {
|
|
1007
1008
|
const next = Number.isFinite(cost) && cost > 0 ? cost : 0;
|
|
1008
|
-
if (next === this.#subagentTreeCost) return;
|
|
1009
|
+
if (next === this.#subagentTreeCost) return false;
|
|
1009
1010
|
this.#subagentTreeCost = next;
|
|
1010
1011
|
this.#invalidateStatusLineRenderCache();
|
|
1012
|
+
return true;
|
|
1011
1013
|
}
|
|
1012
1014
|
|
|
1013
1015
|
/**
|