@ian-pascoe/pi-minimal-subagents 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +6 -6
- package/src/minimal-subagents-config.ts +65 -53
- package/src/minimal-subagents-coordinator.ts +1 -1
- package/src/minimal-subagents-extension.ts +58 -19
- package/src/minimal-subagents-rendering.ts +214 -193
- package/src/minimal-subagents-settings-writer.ts +22 -36
- package/src/minimal-subagents-status-panel.ts +93 -69
- package/src/minimal-subagents-tools.ts +28 -10
- package/src/minimal-subagents-transcript.ts +16 -11
- package/src/minimal-subagents-ui.ts +51 -154
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { JsonValue } from "@earendil-works/pi-ai";
|
|
2
1
|
import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { rewriteNamespaceDocument } from "@ian-pascoe/pi-utils/layered-settings";
|
|
3
3
|
import { updateFileLocked } from "@ian-pascoe/pi-utils/locked-file-update";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
|
|
@@ -12,31 +12,6 @@ export interface MinimalSubagentsSettingsWriteContext {
|
|
|
12
12
|
isProjectTrusted(): boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
type SettingsJsonObject = Record<string, JsonValue>;
|
|
16
|
-
|
|
17
|
-
function isSettingsJsonObject(value: JsonValue | undefined): value is SettingsJsonObject {
|
|
18
|
-
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- JSON.parse already established JSON data; distinguish object roots and settings blocks from primitives and arrays.
|
|
19
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function parseSettings(content: string | undefined, path: string): SettingsJsonObject {
|
|
23
|
-
if (content === undefined) return {};
|
|
24
|
-
// JSON.parse is the provenance for this JSON type; the object shape is checked below.
|
|
25
|
-
let parsed: JsonValue;
|
|
26
|
-
try {
|
|
27
|
-
parsed = JSON.parse(content.charCodeAt(0) === 0xfeff ? content.slice(1) : content);
|
|
28
|
-
} catch (cause) {
|
|
29
|
-
throw new Error(`Minimal subagents settings JSON is malformed at ${path}`, { cause });
|
|
30
|
-
}
|
|
31
|
-
if (!isSettingsJsonObject(parsed)) {
|
|
32
|
-
throw new Error(`Minimal subagents settings at ${path} must have an object root`);
|
|
33
|
-
}
|
|
34
|
-
if (parsed.minimalSubagents !== undefined && !isSettingsJsonObject(parsed.minimalSubagents)) {
|
|
35
|
-
throw new Error(`Minimal subagents settings at ${path} must have an object minimalSubagents`);
|
|
36
|
-
}
|
|
37
|
-
return parsed;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
15
|
/**
|
|
41
16
|
* Set or remove `minimalSubagents.enabled` in global or trusted-project Pi settings,
|
|
42
17
|
* preserving every unrelated setting. Writes re-read the file under Pi's settings lock.
|
|
@@ -58,15 +33,26 @@ export async function writeMinimalSubagentsEnabled(
|
|
|
58
33
|
);
|
|
59
34
|
}
|
|
60
35
|
|
|
61
|
-
await updateFileLocked(path, (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
36
|
+
await updateFileLocked(path, (current) =>
|
|
37
|
+
rewriteNamespaceDocument({
|
|
38
|
+
current,
|
|
39
|
+
namespace: "minimalSubagents",
|
|
40
|
+
invalid: (reason, cause) => {
|
|
41
|
+
if (reason === "malformed")
|
|
42
|
+
return new Error(`Minimal subagents settings JSON is malformed at ${path}`, { cause });
|
|
43
|
+
return new Error(
|
|
44
|
+
reason === "root"
|
|
45
|
+
? `Minimal subagents settings at ${path} must have an object root`
|
|
46
|
+
: `Minimal subagents settings at ${path} must have an object minimalSubagents`,
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
update: (stored) => {
|
|
50
|
+
const minimalSubagents = { ...stored };
|
|
51
|
+
if (enabled === undefined) delete minimalSubagents.enabled;
|
|
52
|
+
else minimalSubagents.enabled = enabled;
|
|
53
|
+
return minimalSubagents;
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
71
57
|
return path;
|
|
72
58
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { stripVTControlCharacters } from "node:util";
|
|
2
2
|
import {
|
|
3
|
+
DynamicBorder,
|
|
3
4
|
type ExtensionContext,
|
|
4
5
|
type KeybindingsManager,
|
|
5
6
|
type Theme,
|
|
@@ -8,16 +9,20 @@ import {
|
|
|
8
9
|
Text,
|
|
9
10
|
matchesKey,
|
|
10
11
|
truncateToWidth,
|
|
11
|
-
|
|
12
|
+
wrapTextWithAnsi,
|
|
12
13
|
type Component,
|
|
13
14
|
type OverlayHandle,
|
|
14
15
|
type TUI,
|
|
15
16
|
} from "@earendil-works/pi-tui";
|
|
17
|
+
import { hintLine, joinInline, noticeText } from "@ian-pascoe/pi-utils/ui";
|
|
16
18
|
import type { MinimalSubagentsCoordinator } from "./minimal-subagents-coordinator.js";
|
|
17
19
|
import {
|
|
18
20
|
formatSubagentDuration,
|
|
19
21
|
orderActiveAgentSubtrees,
|
|
22
|
+
renderSubagentStatusLabel,
|
|
23
|
+
renderSubagentStatusMark,
|
|
20
24
|
subagentStatusLadder,
|
|
25
|
+
treeRowPrefixes,
|
|
21
26
|
} from "./minimal-subagents-rendering.js";
|
|
22
27
|
import { COORDINATOR_TOOL_NAMES } from "./minimal-subagents-capabilities.js";
|
|
23
28
|
import {
|
|
@@ -82,21 +87,11 @@ function statusAccessSourceLabel(source: SubagentAccessSnapshot["source"]): stri
|
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* backgrounds; without this the rest of the row (ellipsis, padding, border) loses the panel fill.
|
|
91
|
-
*/
|
|
92
|
-
function keepBackgroundThroughResets(text: string, backgroundOpen: string): string {
|
|
93
|
-
if (!backgroundOpen) return text;
|
|
94
|
-
let result = text;
|
|
95
|
-
for (const sequence of BACKGROUND_CLEARING_SEQUENCES) {
|
|
96
|
-
result = result.replaceAll(sequence, `${sequence}${backgroundOpen}`);
|
|
97
|
-
}
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
90
|
+
/** The panel's ellipsis, in Pi's three-dot form. */
|
|
91
|
+
const ELLIPSIS = "...";
|
|
92
|
+
|
|
93
|
+
/** The theme operations the panel draws with; Pi's `Theme` satisfies it. */
|
|
94
|
+
type StatusPanelTheme = Pick<Theme, "fg" | "bold">;
|
|
100
95
|
|
|
101
96
|
function transcriptText(line: string): string {
|
|
102
97
|
return stripVTControlCharacters(line).replace(/\s/g, "");
|
|
@@ -152,14 +147,15 @@ export class MinimalSubagentsStatusPanelComponent implements Component {
|
|
|
152
147
|
private toolOutputExpanded = false;
|
|
153
148
|
private disposed = false;
|
|
154
149
|
private readonly stopRefresh: () => void;
|
|
150
|
+
private readonly border = new DynamicBorder((text) => this.theme.fg("border", text));
|
|
155
151
|
|
|
156
152
|
/** Bind one live status component to its coordinator, terminal, and explicit refresh owner. */
|
|
157
153
|
constructor(
|
|
158
154
|
private readonly coordinator: MinimalSubagentsCoordinator,
|
|
159
155
|
private readonly getAccess: () => MinimalSubagentsStatusAccess,
|
|
160
156
|
private readonly tui: TUI,
|
|
161
|
-
private readonly theme:
|
|
162
|
-
private readonly keybindings: KeybindingsManager,
|
|
157
|
+
private readonly theme: StatusPanelTheme,
|
|
158
|
+
private readonly keybindings: Pick<KeybindingsManager, "matches" | "getKeys">,
|
|
163
159
|
private readonly cwd: string,
|
|
164
160
|
private readonly onClose: () => void,
|
|
165
161
|
startRefresh: StartStatusPanelRefresh = startStatusPanelRefresh,
|
|
@@ -208,7 +204,7 @@ export class MinimalSubagentsStatusPanelComponent implements Component {
|
|
|
208
204
|
this.tui.requestRender();
|
|
209
205
|
}
|
|
210
206
|
|
|
211
|
-
/** Render
|
|
207
|
+
/** Render the tree or Child Session Transcript in Pi's selector frame, within the terminal. */
|
|
212
208
|
render(width: number): string[] {
|
|
213
209
|
if (width <= 0) return [];
|
|
214
210
|
const height = Math.max(
|
|
@@ -219,24 +215,24 @@ export class MinimalSubagentsStatusPanelComponent implements Component {
|
|
|
219
215
|
),
|
|
220
216
|
);
|
|
221
217
|
if (width < 6 || height < 5) {
|
|
222
|
-
|
|
218
|
+
const cancelKey = this.keybindings.getKeys("tui.select.cancel").join("/");
|
|
219
|
+
return new Text(this.theme.fg("muted", `${cancelKey} back · Enlarge terminal`), 0, 0)
|
|
220
|
+
.render(width)
|
|
221
|
+
.slice(0, height);
|
|
223
222
|
}
|
|
224
|
-
const innerWidth = width -
|
|
223
|
+
const innerWidth = width - 2;
|
|
225
224
|
const selected = this.flattened.find(({ agent }) => agent.agent_id === this.selectedAgentId);
|
|
226
225
|
const transcriptView = this.view === "transcript" && this.transcript;
|
|
227
226
|
const header = transcriptView
|
|
228
227
|
? [
|
|
229
|
-
this.theme.bold(`Transcript · ${this.selectedAgentId}`),
|
|
230
|
-
selected ? this.renderAgentRow(selected.agent,
|
|
228
|
+
this.theme.fg("accent", this.theme.bold(`Transcript · ${this.selectedAgentId}`)),
|
|
229
|
+
selected ? this.renderAgentRow(selected.agent, "", false) : "",
|
|
231
230
|
]
|
|
232
|
-
: this.renderHeader(
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const help = new Text(this.theme.fg("text", helpText), 0, 0)
|
|
238
|
-
.render(innerWidth)
|
|
239
|
-
.slice(0, Math.min(2, height - 4));
|
|
231
|
+
: this.renderHeader();
|
|
232
|
+
const help = [
|
|
233
|
+
"",
|
|
234
|
+
...wrapTextWithAnsi(this.renderHints(Boolean(transcriptView)), innerWidth),
|
|
235
|
+
].slice(0, Math.min(3, height - 4));
|
|
240
236
|
const visibleHeader = header.slice(0, Math.max(1, height - help.length - 3));
|
|
241
237
|
this.bodyHeight = Math.max(1, height - 2 - visibleHeader.length - help.length);
|
|
242
238
|
let body: string[];
|
|
@@ -262,10 +258,11 @@ export class MinimalSubagentsStatusPanelComponent implements Component {
|
|
|
262
258
|
const maximum = Math.max(0, body.length - this.bodyHeight);
|
|
263
259
|
this.scrollOffset = this.following ? maximum : Math.min(this.scrollOffset, maximum);
|
|
264
260
|
} else {
|
|
265
|
-
|
|
266
|
-
|
|
261
|
+
const prefixes = treeRowPrefixes(this.flattened.map(({ depth }) => depth));
|
|
262
|
+
body = this.flattened.map(({ agent }, index) =>
|
|
263
|
+
this.renderAgentRow(agent, prefixes[index] ?? "", agent.agent_id === this.selectedAgentId),
|
|
267
264
|
);
|
|
268
|
-
if (body.length === 0) body.push("No Child Agents yet.");
|
|
265
|
+
if (body.length === 0) body.push(this.theme.fg("muted", "No Child Agents yet."));
|
|
269
266
|
const selectedLine = this.flattened.findIndex(
|
|
270
267
|
({ agent }) => agent.agent_id === this.selectedAgentId,
|
|
271
268
|
);
|
|
@@ -279,19 +276,12 @@ export class MinimalSubagentsStatusPanelComponent implements Component {
|
|
|
279
276
|
}
|
|
280
277
|
const visibleBody = body.slice(this.scrollOffset, this.scrollOffset + this.bodyHeight);
|
|
281
278
|
while (visibleBody.length < this.bodyHeight) visibleBody.push("");
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
);
|
|
289
|
-
return this.theme.bg(
|
|
290
|
-
"customMessageBg",
|
|
291
|
-
`${border("│")} ${content}${" ".repeat(innerWidth - visibleWidth(content))} ${border("│")}`,
|
|
292
|
-
);
|
|
293
|
-
});
|
|
294
|
-
return [border(`╭${"─".repeat(width - 2)}╮`), ...rows, border(`╰${"─".repeat(width - 2)}╯`)];
|
|
279
|
+
// Each row keeps Pi's one-column margin and fills the pane so the overlay covers what is under it.
|
|
280
|
+
const rows = [...visibleHeader, ...visibleBody, ...help].map((line) =>
|
|
281
|
+
truncateToWidth(` ${line}`, width, ELLIPSIS, true),
|
|
282
|
+
);
|
|
283
|
+
const [border = ""] = this.border.render(width);
|
|
284
|
+
return [border, ...rows, border];
|
|
295
285
|
}
|
|
296
286
|
|
|
297
287
|
/** Rebuild native transcript components when their theme changes. */
|
|
@@ -326,7 +316,34 @@ export class MinimalSubagentsStatusPanelComponent implements Component {
|
|
|
326
316
|
}
|
|
327
317
|
}
|
|
328
318
|
|
|
329
|
-
private
|
|
319
|
+
private keyLabel(binding: Parameters<KeybindingsManager["getKeys"]>[0]): string {
|
|
320
|
+
return this.keybindings.getKeys(binding).join("/");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private renderHints(transcript: boolean): string {
|
|
324
|
+
const page = `${this.keyLabel("tui.select.pageUp")}/${this.keyLabel("tui.select.pageDown")}`;
|
|
325
|
+
const hints = transcript
|
|
326
|
+
? hintLine(this.theme, [
|
|
327
|
+
{ key: this.keyLabel("tui.select.cancel"), description: "tree" },
|
|
328
|
+
{ key: "end", description: "live" },
|
|
329
|
+
{ key: this.keyLabel("app.tools.expand"), description: "tools" },
|
|
330
|
+
{ key: "\u2191\u2193", description: "scroll" },
|
|
331
|
+
{ key: page, description: "page" },
|
|
332
|
+
])
|
|
333
|
+
: hintLine(this.theme, [
|
|
334
|
+
{ key: "\u2191\u2193", description: "select" },
|
|
335
|
+
{ key: this.keyLabel("tui.select.confirm"), description: "transcript" },
|
|
336
|
+
{ key: page, description: "page" },
|
|
337
|
+
{ key: this.keyLabel("tui.select.cancel"), description: "close" },
|
|
338
|
+
]);
|
|
339
|
+
if (!transcript) return hints;
|
|
340
|
+
const following = this.following
|
|
341
|
+
? this.theme.fg("muted", "following")
|
|
342
|
+
: this.theme.fg("warning", "paused");
|
|
343
|
+
return `${hints} ${following}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private renderHeader(): string[] {
|
|
330
347
|
const direct = "agents" in this.status ? this.status.agents : [this.status.agent];
|
|
331
348
|
const running = direct.filter((agent) => agent.state === "running").length;
|
|
332
349
|
const idle = direct.length - running;
|
|
@@ -340,34 +357,41 @@ export class MinimalSubagentsStatusPanelComponent implements Component {
|
|
|
340
357
|
const projectValue = this.access.projectTrusted
|
|
341
358
|
? authoredAccessValue(this.access.projectEnabled)
|
|
342
359
|
: "unavailable (untrusted)";
|
|
360
|
+
const field = (label: string, ...values: string[]) =>
|
|
361
|
+
`${this.theme.fg("muted", `${label}:`)} ${joinInline(this.theme, values)}`;
|
|
343
362
|
return [
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
`Defaults: branch ${this.access.branchOverride} · project ${projectValue} · global ${authoredAccessValue(this.access.globalEnabled)}`,
|
|
352
|
-
width,
|
|
353
|
-
"…",
|
|
363
|
+
this.theme.fg("accent", this.theme.bold("Subagents status")),
|
|
364
|
+
field("Access", accessState, statusAccessSourceLabel(this.access.source)),
|
|
365
|
+
field(
|
|
366
|
+
"Defaults",
|
|
367
|
+
`branch ${this.access.branchOverride}`,
|
|
368
|
+
`project ${projectValue}`,
|
|
369
|
+
`global ${authoredAccessValue(this.access.globalEnabled)}`,
|
|
354
370
|
),
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
this.theme.fg("warning", this.notice),
|
|
371
|
+
field("Coordinator Tools", toolState),
|
|
372
|
+
field("Direct Children", `${running} running`, `${idle} idle`),
|
|
373
|
+
...(this.notice ? [this.theme.fg("warning", this.notice)] : []),
|
|
358
374
|
];
|
|
359
375
|
}
|
|
360
376
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const disclosure = "▸";
|
|
377
|
+
/** One row of the hierarchy: selection marker, tree prefix, Status Mark, id, status, profile, task. */
|
|
378
|
+
private renderAgentRow(agent: AgentSummary, prefix: string, selected: boolean): string {
|
|
364
379
|
const status = subagentStatusLadder(agent);
|
|
365
380
|
const elapsed = formatSubagentDuration(agent.elapsed_ms);
|
|
366
381
|
const task = agent.task?.replace(/\s+/g, " ").trim();
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
382
|
+
const identity = `${renderSubagentStatusMark(this.theme, status)} ${
|
|
383
|
+
selected ? this.theme.fg("accent", agent.agent_id) : agent.agent_id
|
|
384
|
+
}`;
|
|
385
|
+
const details = joinInline(this.theme, [
|
|
386
|
+
identity,
|
|
387
|
+
`${renderSubagentStatusLabel(this.theme, status)}${
|
|
388
|
+
elapsed ? ` ${this.theme.fg("muted", elapsed)}` : ""
|
|
389
|
+
}`,
|
|
390
|
+
this.theme.fg("muted", `${agent.model}:${agent.thinking_level}`),
|
|
391
|
+
task ? this.theme.fg("muted", task) : undefined,
|
|
392
|
+
]);
|
|
393
|
+
const marker = selected ? this.theme.fg("accent", "→ ") : " ";
|
|
394
|
+
return `${marker}${prefix ? this.theme.fg("dim", prefix) : ""}${details}`;
|
|
371
395
|
}
|
|
372
396
|
|
|
373
397
|
private scroll(delta: number): void {
|
|
@@ -491,7 +515,7 @@ export class MinimalSubagentsStatusPanelController {
|
|
|
491
515
|
},
|
|
492
516
|
)
|
|
493
517
|
.catch(() => {
|
|
494
|
-
this.context.ui.notify("Subagents
|
|
518
|
+
this.context.ui.notify(noticeText("Subagents", "Status view failed."), "error");
|
|
495
519
|
})
|
|
496
520
|
.finally(() => {
|
|
497
521
|
this.activePanel?.dispose();
|
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
type ToolDefinition,
|
|
10
10
|
type ToolRenderResultOptions,
|
|
11
11
|
} from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import type
|
|
12
|
+
import { Container, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
13
|
+
import { acceptNullForOptionalArguments } from "@ian-pascoe/pi-utils/null-optional-arguments";
|
|
14
|
+
import { appendDurationFooter, callDurationFooter } from "@ian-pascoe/pi-utils/ui";
|
|
13
15
|
import type { MinimalSubagentsCoordinator } from "./minimal-subagents-coordinator.js";
|
|
14
16
|
import { withTroubleshootingHint } from "./troubleshooting-skill.js";
|
|
15
17
|
import type { MinimalSubagentsModelRole } from "./minimal-subagents-config.js";
|
|
@@ -134,6 +136,13 @@ interface CoordinatorToolRowState {
|
|
|
134
136
|
liveTurnCache?: TranscriptRenderCache;
|
|
135
137
|
}
|
|
136
138
|
|
|
139
|
+
function withFooter(body: Component, footer: Component): Component {
|
|
140
|
+
const container = new Container();
|
|
141
|
+
container.addChild(body);
|
|
142
|
+
container.addChild(footer);
|
|
143
|
+
return container;
|
|
144
|
+
}
|
|
145
|
+
|
|
137
146
|
function createCoordinatorToolRendering(
|
|
138
147
|
options: CoordinatorToolDefinitionOptions,
|
|
139
148
|
toolName: CoordinatorToolName,
|
|
@@ -142,8 +151,13 @@ function createCoordinatorToolRendering(
|
|
|
142
151
|
renderCall: (
|
|
143
152
|
args: CoordinatorToolCallInput,
|
|
144
153
|
theme: Theme,
|
|
145
|
-
context:
|
|
146
|
-
) =>
|
|
154
|
+
context: CoordinatorToolRenderContext | undefined,
|
|
155
|
+
) => {
|
|
156
|
+
const call = renderCoordinatorToolCall(toolName, args, theme, context?.expanded ?? false);
|
|
157
|
+
// A wait streams partial results, whose footer already shows its running time.
|
|
158
|
+
if (!context || toolName === "subagent_wait") return call;
|
|
159
|
+
return withFooter(call, callDurationFooter(theme, context));
|
|
160
|
+
},
|
|
147
161
|
renderResult: (
|
|
148
162
|
result: AgentToolResult<CoordinatorToolResultDetails>,
|
|
149
163
|
renderOptions: ToolRenderResultOptions,
|
|
@@ -168,7 +182,7 @@ function createCoordinatorToolRendering(
|
|
|
168
182
|
theme,
|
|
169
183
|
);
|
|
170
184
|
};
|
|
171
|
-
|
|
185
|
+
const rendered = renderCoordinatorToolResult(
|
|
172
186
|
toolName,
|
|
173
187
|
result,
|
|
174
188
|
renderOptions,
|
|
@@ -177,6 +191,10 @@ function createCoordinatorToolRendering(
|
|
|
177
191
|
context.isError,
|
|
178
192
|
toolName === "subagent_wait" ? renderLiveTurn : undefined,
|
|
179
193
|
);
|
|
194
|
+
const container = new Container();
|
|
195
|
+
container.addChild(rendered);
|
|
196
|
+
appendDurationFooter(container, theme, context, { isPartial: renderOptions.isPartial });
|
|
197
|
+
return container;
|
|
180
198
|
},
|
|
181
199
|
};
|
|
182
200
|
}
|
|
@@ -475,9 +493,7 @@ export function createCoordinatorToolDefinitions(
|
|
|
475
493
|
parameters.recursive ?? true,
|
|
476
494
|
);
|
|
477
495
|
if (result.failures.length > 0) {
|
|
478
|
-
options.onAttention?.(
|
|
479
|
-
`Minimal subagents deletion partially failed for ${parameters.agent_id}`,
|
|
480
|
-
);
|
|
496
|
+
options.onAttention?.(`Deletion partially failed for ${parameters.agent_id}`);
|
|
481
497
|
return failedStructuredToolResult("Minimal subagents deletion partially failed", result, {
|
|
482
498
|
troubleshootingHint: true,
|
|
483
499
|
});
|
|
@@ -497,7 +513,9 @@ export function createCoordinatorToolDefinitions(
|
|
|
497
513
|
Object.assign(deleteTool, { outputSchema: CoordinatorToolOutputSchemas.subagent_delete }),
|
|
498
514
|
];
|
|
499
515
|
const allowFanoutTools = options.allowFanoutTools ?? options.callerId === "root";
|
|
500
|
-
return
|
|
501
|
-
|
|
502
|
-
|
|
516
|
+
return (
|
|
517
|
+
allowFanoutTools
|
|
518
|
+
? coordinatorTools
|
|
519
|
+
: coordinatorTools.filter((tool) => NON_SPAWNING_CHILD_COORDINATOR_TOOL_NAMES.has(tool.name))
|
|
520
|
+
).map((tool) => acceptNullForOptionalArguments(tool));
|
|
503
521
|
}
|
|
@@ -11,7 +11,14 @@ import {
|
|
|
11
11
|
UserMessageComponent,
|
|
12
12
|
type TruncationResult,
|
|
13
13
|
} from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
Container,
|
|
16
|
+
Text,
|
|
17
|
+
wrapTextWithAnsi,
|
|
18
|
+
type Component,
|
|
19
|
+
type TUI,
|
|
20
|
+
} from "@earendil-works/pi-tui";
|
|
21
|
+
import { COLLAPSED_LINES, expandHint } from "@ian-pascoe/pi-utils/ui";
|
|
15
22
|
import {
|
|
16
23
|
ACTIVITY_RAIL_WIDTH,
|
|
17
24
|
drawActivityRail,
|
|
@@ -224,7 +231,7 @@ export function createTranscriptRenderCache(): TranscriptRenderCache {
|
|
|
224
231
|
}
|
|
225
232
|
|
|
226
233
|
/** Collapsed live views keep at most this many lines of the child's recent work. */
|
|
227
|
-
const COLLAPSED_TRANSCRIPT_RAIL_LINES =
|
|
234
|
+
const COLLAPSED_TRANSCRIPT_RAIL_LINES = COLLAPSED_LINES.fallback;
|
|
228
235
|
|
|
229
236
|
function isSpacerLine(line: string): boolean {
|
|
230
237
|
// Includes a tool box's coloured padding rows, so each rail connector meets visible content.
|
|
@@ -277,16 +284,14 @@ export class TranscriptRail implements Component {
|
|
|
277
284
|
const kept = items
|
|
278
285
|
.slice(items.length - shown)
|
|
279
286
|
.map((lines) => lines.slice(0, COLLAPSED_TRANSCRIPT_RAIL_LINES));
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
]
|
|
287
|
-
: kept,
|
|
288
|
-
this.theme,
|
|
287
|
+
const hidden =
|
|
288
|
+
items.reduce((total, lines) => total + lines.length, 0) -
|
|
289
|
+
kept.reduce((total, lines) => total + lines.length, 0);
|
|
290
|
+
const hint = wrapTextWithAnsi(
|
|
291
|
+
expandHint(this.theme, hidden, "earlier"),
|
|
292
|
+
Math.max(1, width - ACTIVITY_RAIL_WIDTH),
|
|
289
293
|
);
|
|
294
|
+
return drawActivityRail(hidden > 0 ? [hint, ...kept] : kept, this.theme);
|
|
290
295
|
}
|
|
291
296
|
|
|
292
297
|
invalidate(): void {
|