@pi-unipi/subagents 2.3.0 → 2.4.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 +3 -1
- package/dist/agent-manager.d.ts +81 -0
- package/dist/agent-manager.d.ts.map +1 -0
- package/dist/agent-manager.js +292 -0
- package/dist/agent-manager.js.map +1 -0
- package/dist/agent-runner.d.ts +51 -0
- package/dist/agent-runner.d.ts.map +1 -0
- package/dist/agent-runner.js +262 -0
- package/dist/agent-runner.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +132 -0
- package/dist/config.js.map +1 -0
- package/dist/conversation-viewer.d.ts +40 -0
- package/dist/conversation-viewer.d.ts.map +1 -0
- package/dist/conversation-viewer.js +276 -0
- package/dist/conversation-viewer.js.map +1 -0
- package/dist/core-compat.d.ts +14 -0
- package/dist/core-compat.d.ts.map +1 -0
- package/dist/core-compat.js +24 -0
- package/dist/core-compat.js.map +1 -0
- package/dist/custom-agents.d.ts +14 -0
- package/dist/custom-agents.d.ts.map +1 -0
- package/dist/custom-agents.js +106 -0
- package/dist/custom-agents.js.map +1 -0
- package/dist/file-lock.d.ts +42 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +91 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +751 -0
- package/dist/index.js.map +1 -0
- package/dist/model-resolver.d.ts +19 -0
- package/dist/model-resolver.d.ts.map +1 -0
- package/dist/model-resolver.js +61 -0
- package/dist/model-resolver.js.map +1 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +47 -0
- package/dist/types.js.map +1 -0
- package/dist/widget.d.ts +56 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +396 -0
- package/dist/widget.js.map +1 -0
- package/package.json +10 -6
- package/src/__tests__/badge-generation.test.ts +0 -315
- package/src/__tests__/config.test.ts +0 -240
- package/src/__tests__/esc-propagation.test.ts +0 -162
- package/src/__tests__/file-lock.test.ts +0 -244
- package/src/__tests__/shutdown-stale-ctx.test.ts +0 -185
- package/src/__tests__/workflow-integration.test.ts +0 -334
- package/src/agent-manager.ts +0 -334
- package/src/agent-runner.ts +0 -329
- package/src/config.ts +0 -147
- package/src/conversation-viewer.ts +0 -299
- package/src/custom-agents.ts +0 -118
- package/src/file-lock.ts +0 -102
- package/src/index.ts +0 -862
- package/src/model-resolver.ts +0 -79
- package/src/prompts.ts +0 -39
- package/src/skills/explore/SKILL.md +0 -32
- package/src/skills/work/SKILL.md +0 -40
- package/src/types.ts +0 -146
- package/src/widget.ts +0 -454
- package/tsconfig.json +0 -19
package/src/widget.ts
DELETED
|
@@ -1,454 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @pi-unipi/subagents — Live widget
|
|
3
|
-
*
|
|
4
|
-
* Shows running/completed agents above the editor with:
|
|
5
|
-
* - Animated braille spinners
|
|
6
|
-
* - Finished agent lingering (1 turn success, 2 turns error)
|
|
7
|
-
* - Priority-based overflow (running > queued > finished)
|
|
8
|
-
* - Status bar integration
|
|
9
|
-
* - Activity description grouping
|
|
10
|
-
* - ANSI-aware truncation via pi-tui
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
14
|
-
import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import type { AgentManager } from "./agent-manager.js";
|
|
16
|
-
import type { AgentActivity } from "./types.js";
|
|
17
|
-
|
|
18
|
-
// ---- Constants ----
|
|
19
|
-
|
|
20
|
-
/** Maximum lines the widget may render. */
|
|
21
|
-
const MAX_WIDGET_LINES = 12;
|
|
22
|
-
|
|
23
|
-
/** Braille spinner frames. */
|
|
24
|
-
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
25
|
-
|
|
26
|
-
/** Statuses that indicate error/non-success (for linger behavior). */
|
|
27
|
-
const ERROR_STATUSES = new Set(["error", "aborted", "stopped"]);
|
|
28
|
-
|
|
29
|
-
/** Tool name → human-readable action for activity descriptions. */
|
|
30
|
-
const TOOL_DISPLAY: Record<string, string> = {
|
|
31
|
-
read: "reading",
|
|
32
|
-
bash: "running command",
|
|
33
|
-
edit: "editing",
|
|
34
|
-
write: "writing",
|
|
35
|
-
grep: "searching",
|
|
36
|
-
find: "finding files",
|
|
37
|
-
ls: "listing",
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// ---- Formatting helpers ----
|
|
41
|
-
|
|
42
|
-
/** Format duration. */
|
|
43
|
-
function formatMs(ms: number): string {
|
|
44
|
-
if (ms >= 60_000) return `${(ms / 60_000).toFixed(1)}m`;
|
|
45
|
-
if (ms >= 1_000) return `${(ms / 1_000).toFixed(1)}s`;
|
|
46
|
-
return `${ms}ms`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Format turns with optional max limit: "⟳5≤30" or "⟳5". */
|
|
50
|
-
function formatTurns(turn: number, max?: number): string {
|
|
51
|
-
return max != null ? `⟳${turn}≤${max}` : `⟳${turn}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Format token count compactly: "33.8k token", "1.2M token". */
|
|
55
|
-
function formatTokens(count: number): string {
|
|
56
|
-
if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M token`;
|
|
57
|
-
if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k token`;
|
|
58
|
-
return `${count} token`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Build a human-readable activity string from currently-running tools.
|
|
63
|
-
* Groups by tool type with counts: "reading 3 files, searching 2 patterns".
|
|
64
|
-
*/
|
|
65
|
-
function describeActivity(activeTools: Map<string, string>, responseText?: string): string {
|
|
66
|
-
if (activeTools.size > 0) {
|
|
67
|
-
const groups = new Map<string, number>();
|
|
68
|
-
for (const toolName of activeTools.values()) {
|
|
69
|
-
const action = TOOL_DISPLAY[toolName] ?? toolName;
|
|
70
|
-
groups.set(action, (groups.get(action) ?? 0) + 1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const parts: string[] = [];
|
|
74
|
-
for (const [action, count] of groups) {
|
|
75
|
-
if (count > 1) {
|
|
76
|
-
parts.push(`${action} ${count} ${action === "searching" ? "patterns" : "files"}`);
|
|
77
|
-
} else {
|
|
78
|
-
parts.push(action);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return parts.join(", ") + "…";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (responseText && responseText.trim().length > 0) {
|
|
85
|
-
const lastLine = responseText.split("\n").find((l) => l.trim())?.trim() ?? "";
|
|
86
|
-
if (lastLine.length > 60) return lastLine.slice(0, 60) + "…";
|
|
87
|
-
if (lastLine.length > 0) return lastLine;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return "thinking…";
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ---- Widget ----
|
|
94
|
-
|
|
95
|
-
export class AgentWidget {
|
|
96
|
-
private spinnerFrame = 0;
|
|
97
|
-
private timer?: ReturnType<typeof setInterval>;
|
|
98
|
-
private uiCtx?: ExtensionUIContext;
|
|
99
|
-
private tui?: import("@earendil-works/pi-tui").TUI;
|
|
100
|
-
private widgetRegistered = false;
|
|
101
|
-
/** Last content key — skips requestRender when only spinner changed. */
|
|
102
|
-
private lastContentKey = "";
|
|
103
|
-
|
|
104
|
-
/** Tracks how many turns each finished agent has survived. */
|
|
105
|
-
private finishedTurnAge = new Map<string, number>();
|
|
106
|
-
/** How many extra turns error/aborted agents linger. */
|
|
107
|
-
private static readonly ERROR_LINGER_TURNS = 2;
|
|
108
|
-
/** Last status bar text for dedup. */
|
|
109
|
-
private lastStatusText: string | undefined;
|
|
110
|
-
|
|
111
|
-
constructor(
|
|
112
|
-
private manager: AgentManager,
|
|
113
|
-
private activity: Map<string, AgentActivity>,
|
|
114
|
-
) {}
|
|
115
|
-
|
|
116
|
-
setUICtx(ctx: ExtensionUIContext) {
|
|
117
|
-
if (ctx !== this.uiCtx) {
|
|
118
|
-
this.uiCtx = ctx;
|
|
119
|
-
this.widgetRegistered = false;
|
|
120
|
-
this.tui = undefined;
|
|
121
|
-
this.lastStatusText = undefined;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Called on each new turn (tool_execution_start).
|
|
127
|
-
* Ages finished agents and clears those that have lingered long enough.
|
|
128
|
-
*/
|
|
129
|
-
onTurnStart() {
|
|
130
|
-
for (const [id, age] of this.finishedTurnAge) {
|
|
131
|
-
this.finishedTurnAge.set(id, age + 1);
|
|
132
|
-
}
|
|
133
|
-
this.update();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
ensureTimer() {
|
|
137
|
-
if (this.timer) return;
|
|
138
|
-
this.timer = setInterval(() => {
|
|
139
|
-
this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER.length;
|
|
140
|
-
if (this.lastContentKey) this.triggerRender();
|
|
141
|
-
}, 80);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** Record an agent as finished (call when agent completes). */
|
|
145
|
-
markFinished(agentId: string) {
|
|
146
|
-
if (!this.finishedTurnAge.has(agentId)) {
|
|
147
|
-
this.finishedTurnAge.set(agentId, 0);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
update() {
|
|
152
|
-
this.triggerRender();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Check if a finished agent should still be shown. */
|
|
156
|
-
private shouldShowFinished(agentId: string, status: string): boolean {
|
|
157
|
-
const age = this.finishedTurnAge.get(agentId) ?? 0;
|
|
158
|
-
const maxAge = ERROR_STATUSES.has(status) ? AgentWidget.ERROR_LINGER_TURNS : 1;
|
|
159
|
-
return age < maxAge;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Build a content key capturing what's actually visible.
|
|
164
|
-
* Excludes spinner — spinner-only changes skip requestRender.
|
|
165
|
-
*/
|
|
166
|
-
private buildContentKey(): string {
|
|
167
|
-
const allAgents = this.manager.listAgents();
|
|
168
|
-
const parts: string[] = [];
|
|
169
|
-
for (const a of allAgents) {
|
|
170
|
-
if (a.status === "running" || a.status === "queued") {
|
|
171
|
-
const act = this.activity.get(a.id);
|
|
172
|
-
parts.push(
|
|
173
|
-
`${a.id}:${a.status}:${a.toolUses}:${act?.turnCount ?? 0}:${act?.tokens ?? ""}:${describeActivity(act?.activeTools ?? new Map(), act?.responseText ?? "")}`,
|
|
174
|
-
);
|
|
175
|
-
} else if (a.completedAt && this.shouldShowFinished(a.id, a.status)) {
|
|
176
|
-
parts.push(`${a.id}:${a.status}:${a.toolUses}:finished`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return parts.join("|");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private triggerRender() {
|
|
183
|
-
if (!this.uiCtx) return;
|
|
184
|
-
|
|
185
|
-
const contentKey = this.buildContentKey();
|
|
186
|
-
const hasContent = contentKey.length > 0;
|
|
187
|
-
|
|
188
|
-
// Nothing to show — clear widget
|
|
189
|
-
if (!hasContent) {
|
|
190
|
-
if (this.widgetRegistered) {
|
|
191
|
-
this.uiCtx.setWidget("unipi-agents", undefined);
|
|
192
|
-
this.widgetRegistered = false;
|
|
193
|
-
this.tui = undefined;
|
|
194
|
-
this.lastContentKey = "";
|
|
195
|
-
}
|
|
196
|
-
if (this.lastStatusText !== undefined) {
|
|
197
|
-
this.uiCtx.setStatus?.("subagents", undefined);
|
|
198
|
-
this.lastStatusText = undefined;
|
|
199
|
-
}
|
|
200
|
-
// Clean up stale finished entries
|
|
201
|
-
const allAgents = this.manager.listAgents();
|
|
202
|
-
for (const [id] of this.finishedTurnAge) {
|
|
203
|
-
if (!allAgents.some((a) => a.id === id)) this.finishedTurnAge.delete(id);
|
|
204
|
-
}
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Status bar
|
|
209
|
-
this.updateStatusBar();
|
|
210
|
-
|
|
211
|
-
// Register widget callback once
|
|
212
|
-
if (!this.widgetRegistered) {
|
|
213
|
-
this.uiCtx.setWidget(
|
|
214
|
-
"unipi-agents",
|
|
215
|
-
(tui: any, theme: any) => {
|
|
216
|
-
this.tui = tui;
|
|
217
|
-
return {
|
|
218
|
-
render: (width: number) => this.renderWidget(tui, theme, width),
|
|
219
|
-
invalidate: () => {
|
|
220
|
-
this.widgetRegistered = false;
|
|
221
|
-
this.tui = undefined;
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
|
-
},
|
|
225
|
-
{ placement: "aboveEditor" },
|
|
226
|
-
);
|
|
227
|
-
this.widgetRegistered = true;
|
|
228
|
-
this.lastContentKey = "";
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Only request render when content actually changed
|
|
232
|
-
if (contentKey !== this.lastContentKey) {
|
|
233
|
-
this.lastContentKey = contentKey;
|
|
234
|
-
this.tui?.requestRender?.();
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private updateStatusBar() {
|
|
239
|
-
if (!this.uiCtx?.setStatus) return;
|
|
240
|
-
const allAgents = this.manager.listAgents();
|
|
241
|
-
let runningCount = 0;
|
|
242
|
-
let queuedCount = 0;
|
|
243
|
-
for (const a of allAgents) {
|
|
244
|
-
if (a.status === "running") runningCount++;
|
|
245
|
-
else if (a.status === "queued") queuedCount++;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
let newStatusText: string | undefined;
|
|
249
|
-
if (runningCount > 0 || queuedCount > 0) {
|
|
250
|
-
const parts: string[] = [];
|
|
251
|
-
if (runningCount > 0) parts.push(`${runningCount} running`);
|
|
252
|
-
if (queuedCount > 0) parts.push(`${queuedCount} queued`);
|
|
253
|
-
const total = runningCount + queuedCount;
|
|
254
|
-
newStatusText = `${parts.join(", ")} agent${total === 1 ? "" : "s"}`;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (newStatusText !== this.lastStatusText) {
|
|
258
|
-
this.uiCtx.setStatus("subagents", newStatusText);
|
|
259
|
-
this.lastStatusText = newStatusText;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/** Render a finished agent line. */
|
|
264
|
-
private renderFinishedLine(
|
|
265
|
-
a: { id: string; type: string; status: string; description: string; toolUses: number; startedAt: number; completedAt?: number; error?: string },
|
|
266
|
-
theme: any,
|
|
267
|
-
w: number,
|
|
268
|
-
): string {
|
|
269
|
-
const duration = formatMs((a.completedAt ?? Date.now()) - a.startedAt);
|
|
270
|
-
|
|
271
|
-
let icon: string;
|
|
272
|
-
let statusText: string;
|
|
273
|
-
if (a.status === "completed") {
|
|
274
|
-
icon = theme.fg("success", "✓");
|
|
275
|
-
statusText = "";
|
|
276
|
-
} else if (a.status === "stopped") {
|
|
277
|
-
icon = theme.fg("dim", "■");
|
|
278
|
-
statusText = theme.fg("dim", " stopped");
|
|
279
|
-
} else if (a.status === "error") {
|
|
280
|
-
icon = theme.fg("error", "✗");
|
|
281
|
-
const errMsg = a.error ? `: ${a.error.slice(0, 40)}` : "";
|
|
282
|
-
statusText = theme.fg("error", ` error${errMsg}`);
|
|
283
|
-
} else {
|
|
284
|
-
// aborted
|
|
285
|
-
icon = theme.fg("error", "✗");
|
|
286
|
-
statusText = theme.fg("warning", " aborted");
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const parts: string[] = [];
|
|
290
|
-
const act = this.activity.get(a.id);
|
|
291
|
-
if (act) parts.push(formatTurns(act.turnCount, act.maxTurns));
|
|
292
|
-
if (a.toolUses > 0) parts.push(`${a.toolUses} tool use${a.toolUses === 1 ? "" : "s"}`);
|
|
293
|
-
parts.push(duration);
|
|
294
|
-
|
|
295
|
-
const line =
|
|
296
|
-
theme.fg("dim", "├─") +
|
|
297
|
-
" " +
|
|
298
|
-
icon +
|
|
299
|
-
" " +
|
|
300
|
-
theme.fg("dim", a.type) +
|
|
301
|
-
" " +
|
|
302
|
-
theme.fg("dim", a.description) +
|
|
303
|
-
" " +
|
|
304
|
-
theme.fg("dim", "·") +
|
|
305
|
-
" " +
|
|
306
|
-
theme.fg("dim", parts.join(" · ")) +
|
|
307
|
-
statusText;
|
|
308
|
-
|
|
309
|
-
return truncateToWidth(line, w);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
private renderWidget(tui: any, theme: any, width?: number): string[] {
|
|
313
|
-
const allAgents = this.manager.listAgents();
|
|
314
|
-
const running = allAgents.filter((a) => a.status === "running");
|
|
315
|
-
const queued = allAgents.filter((a) => a.status === "queued");
|
|
316
|
-
const finished = allAgents.filter(
|
|
317
|
-
(a) =>
|
|
318
|
-
a.status !== "running" &&
|
|
319
|
-
a.status !== "queued" &&
|
|
320
|
-
a.completedAt &&
|
|
321
|
-
this.shouldShowFinished(a.id, a.status),
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
const hasActive = running.length > 0 || queued.length > 0;
|
|
325
|
-
const hasFinished = finished.length > 0;
|
|
326
|
-
if (!hasActive && !hasFinished) return [];
|
|
327
|
-
|
|
328
|
-
const w = width ?? tui.terminal?.columns ?? 80;
|
|
329
|
-
const frame = SPINNER[this.spinnerFrame % SPINNER.length];
|
|
330
|
-
const headingColor = hasActive ? "accent" : "dim";
|
|
331
|
-
const headingIcon = hasActive ? "●" : "○";
|
|
332
|
-
|
|
333
|
-
// Build sections: finished (1 line each), running (2 lines each), queued (1 line)
|
|
334
|
-
const finishedLines: string[] = finished.map((a) => this.renderFinishedLine(a, theme, w));
|
|
335
|
-
|
|
336
|
-
const runningLines: string[][] = running.map((a) => {
|
|
337
|
-
const act = this.activity.get(a.id);
|
|
338
|
-
const toolCount = a.toolUses;
|
|
339
|
-
const tokens = act?.tokens ?? "";
|
|
340
|
-
const elapsed = formatMs(Date.now() - a.startedAt);
|
|
341
|
-
const activity = act ? describeActivity(act.activeTools, act.responseText) : "starting…";
|
|
342
|
-
|
|
343
|
-
const parts: string[] = [];
|
|
344
|
-
if (act?.turnCount) parts.push(formatTurns(act.turnCount, act.maxTurns));
|
|
345
|
-
if (toolCount > 0) parts.push(`${toolCount} tool use${toolCount === 1 ? "" : "s"}`);
|
|
346
|
-
if (tokens) parts.push(tokens);
|
|
347
|
-
parts.push(elapsed);
|
|
348
|
-
|
|
349
|
-
return [
|
|
350
|
-
truncateToWidth(
|
|
351
|
-
theme.fg("dim", "├─") +
|
|
352
|
-
` ${theme.fg("accent", frame)} ${theme.bold(a.type)} ${theme.fg("dim", a.description)} ${theme.fg("dim", "·")} ${theme.fg("dim", parts.join(" · "))}`,
|
|
353
|
-
w,
|
|
354
|
-
),
|
|
355
|
-
truncateToWidth(theme.fg("dim", "│ ") + theme.fg("dim", ` ⎿ ${activity}`), w),
|
|
356
|
-
];
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const queuedLine =
|
|
360
|
-
queued.length > 0
|
|
361
|
-
? truncateToWidth(
|
|
362
|
-
theme.fg("dim", "├─") + ` ${theme.fg("muted", "◦")} ${theme.fg("dim", `${queued.length} queued`)}`,
|
|
363
|
-
w,
|
|
364
|
-
)
|
|
365
|
-
: undefined;
|
|
366
|
-
|
|
367
|
-
// Assemble with overflow cap
|
|
368
|
-
const maxBody = MAX_WIDGET_LINES - 1; // heading takes 1 line
|
|
369
|
-
const totalBody = finishedLines.length + runningLines.length * 2 + (queuedLine ? 1 : 0);
|
|
370
|
-
|
|
371
|
-
const lines: string[] = [truncateToWidth(theme.fg(headingColor, headingIcon) + " " + theme.fg(headingColor, "Agents"), w)];
|
|
372
|
-
|
|
373
|
-
if (totalBody <= maxBody) {
|
|
374
|
-
// Everything fits
|
|
375
|
-
lines.push(...finishedLines);
|
|
376
|
-
for (const pair of runningLines) lines.push(...pair);
|
|
377
|
-
if (queuedLine) lines.push(queuedLine);
|
|
378
|
-
|
|
379
|
-
// Fix last connector: ├─ → └─
|
|
380
|
-
if (lines.length > 1) {
|
|
381
|
-
const last = lines.length - 1;
|
|
382
|
-
lines[last] = lines[last].replace("├─", "└─");
|
|
383
|
-
if (runningLines.length > 0 && !queuedLine && last >= 2) {
|
|
384
|
-
lines[last - 1] = lines[last - 1].replace("├─", "└─");
|
|
385
|
-
lines[last] = lines[last].replace("│ ", " ");
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
} else {
|
|
389
|
-
// Overflow — prioritize: running > queued > finished
|
|
390
|
-
let budget = maxBody - 1; // reserve 1 for overflow indicator
|
|
391
|
-
let hiddenRunning = 0;
|
|
392
|
-
let hiddenFinished = 0;
|
|
393
|
-
|
|
394
|
-
// 1. Running agents (2 lines each)
|
|
395
|
-
for (const pair of runningLines) {
|
|
396
|
-
if (budget >= 2) {
|
|
397
|
-
lines.push(...pair);
|
|
398
|
-
budget -= 2;
|
|
399
|
-
} else {
|
|
400
|
-
hiddenRunning++;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// 2. Queued
|
|
405
|
-
if (queuedLine && budget >= 1) {
|
|
406
|
-
lines.push(queuedLine);
|
|
407
|
-
budget--;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// 3. Finished
|
|
411
|
-
for (const fl of finishedLines) {
|
|
412
|
-
if (budget >= 1) {
|
|
413
|
-
lines.push(fl);
|
|
414
|
-
budget--;
|
|
415
|
-
} else {
|
|
416
|
-
hiddenFinished++;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Overflow summary
|
|
421
|
-
const overflowParts: string[] = [];
|
|
422
|
-
if (hiddenRunning > 0) overflowParts.push(`${hiddenRunning} running`);
|
|
423
|
-
if (hiddenFinished > 0) overflowParts.push(`${hiddenFinished} finished`);
|
|
424
|
-
if (overflowParts.length > 0) {
|
|
425
|
-
lines.push(
|
|
426
|
-
truncateToWidth(
|
|
427
|
-
theme.fg("dim", "└─") + ` ${theme.fg("dim", `+${hiddenRunning + hiddenFinished} more (${overflowParts.join(", ")})`)}`,
|
|
428
|
-
w,
|
|
429
|
-
),
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return lines;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
dispose() {
|
|
438
|
-
if (this.timer) {
|
|
439
|
-
clearInterval(this.timer);
|
|
440
|
-
this.timer = undefined;
|
|
441
|
-
}
|
|
442
|
-
if (this.uiCtx) {
|
|
443
|
-
if (this.widgetRegistered) {
|
|
444
|
-
this.uiCtx.setWidget("unipi-agents", undefined);
|
|
445
|
-
}
|
|
446
|
-
if (this.lastStatusText !== undefined) {
|
|
447
|
-
this.uiCtx.setStatus?.("subagents", undefined);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
this.widgetRegistered = false;
|
|
451
|
-
this.tui = undefined;
|
|
452
|
-
this.lastStatusText = undefined;
|
|
453
|
-
}
|
|
454
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "Node16",
|
|
5
|
-
"moduleResolution": "Node16",
|
|
6
|
-
"lib": ["ES2022"],
|
|
7
|
-
"outDir": "dist",
|
|
8
|
-
"rootDir": "src",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"declarationMap": true,
|
|
15
|
-
"sourceMap": true
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|