@gjczone/pi-swarm 0.5.0 → 0.7.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/README.md +14 -21
- package/dist/index.d.ts +3 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -16
- package/dist/index.js.map +1 -1
- package/dist/shared/controller.d.ts +10 -1
- package/dist/shared/controller.d.ts.map +1 -1
- package/dist/shared/controller.js +47 -12
- package/dist/shared/controller.js.map +1 -1
- package/dist/shared/render.d.ts +1 -1
- package/dist/shared/render.js +1 -1
- package/dist/shared/spawner.js +49 -5
- package/dist/shared/spawner.js.map +1 -1
- package/dist/shared/types.d.ts +8 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/worktree.d.ts +2 -0
- package/dist/shared/worktree.d.ts.map +1 -1
- package/dist/shared/worktree.js +10 -3
- package/dist/shared/worktree.js.map +1 -1
- package/dist/state/recovery.d.ts.map +1 -1
- package/dist/state/recovery.js +25 -4
- package/dist/state/recovery.js.map +1 -1
- package/dist/swarm/command.d.ts +4 -5
- package/dist/swarm/command.d.ts.map +1 -1
- package/dist/swarm/command.js +26 -74
- package/dist/swarm/command.js.map +1 -1
- package/dist/swarm/mode.d.ts +1 -1
- package/dist/swarm/mode.js +2 -2
- package/dist/swarm/mode.js.map +1 -1
- package/dist/swarm/tool.d.ts +4 -4
- package/dist/swarm/tool.d.ts.map +1 -1
- package/dist/swarm/tool.js +107 -167
- package/dist/swarm/tool.js.map +1 -1
- package/dist/team/command.d.ts +2 -4
- package/dist/team/command.d.ts.map +1 -1
- package/dist/team/command.js +5 -13
- package/dist/team/command.js.map +1 -1
- package/dist/team/mailbox.d.ts +7 -0
- package/dist/team/mailbox.d.ts.map +1 -1
- package/dist/team/mailbox.js +99 -13
- package/dist/team/mailbox.js.map +1 -1
- package/dist/tui/progress.d.ts +18 -50
- package/dist/tui/progress.d.ts.map +1 -1
- package/dist/tui/progress.js +200 -483
- package/dist/tui/progress.js.map +1 -1
- package/dist/tui/swarm-markers.d.ts +1 -1
- package/dist/tui/swarm-markers.js +1 -1
- package/package.json +13 -2
- package/dist/team/supervisor.d.ts +0 -171
- package/dist/team/supervisor.d.ts.map +0 -1
- package/dist/team/supervisor.js +0 -685
- package/dist/team/supervisor.js.map +0 -1
- package/dist/team/task-graph.d.ts +0 -64
- package/dist/team/task-graph.d.ts.map +0 -1
- package/dist/team/task-graph.js +0 -216
- package/dist/team/task-graph.js.map +0 -1
- package/dist/team/tool.d.ts +0 -11
- package/dist/team/tool.d.ts.map +0 -1
- package/dist/team/tool.js +0 -491
- package/dist/team/tool.js.map +0 -1
- package/dist/tui/permission-prompt.d.ts +0 -26
- package/dist/tui/permission-prompt.d.ts.map +0 -1
- package/dist/tui/permission-prompt.js +0 -98
- package/dist/tui/permission-prompt.js.map +0 -1
- package/dist/tui/team-dashboard.d.ts +0 -81
- package/dist/tui/team-dashboard.d.ts.map +0 -1
- package/dist/tui/team-dashboard.js +0 -657
- package/dist/tui/team-dashboard.js.map +0 -1
package/dist/tui/progress.js
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* tui/progress — AgentSwarm live progress panel.
|
|
2
|
+
* tui/progress — AgentSwarm live progress panel (minimal).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Grid layout with braille progress bars and scrolling model output.
|
|
5
|
+
* - Multiple agents: grid cells with ID + braille bar + model text
|
|
6
|
+
* - Single agent: compact status line with spinner + text
|
|
7
|
+
* - No token/in/out display — replaced by scrolling model output
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
* - Debounced, event-driven rendering (replaces pure setInterval polling)
|
|
10
|
-
* - Keyboard interaction: j/k scroll, Enter detail, ? help, tab panel switch
|
|
11
|
-
* - Panel switching: members list / event log
|
|
12
|
-
* - Detail overlay for individual members
|
|
13
|
-
* - Activity/tool tracking per member
|
|
14
|
-
* - ETA estimation
|
|
15
|
-
*
|
|
16
|
-
* Ported from MoonshotAI/kimi-code's AgentSwarmProgressComponent.
|
|
9
|
+
* Architecture reference: AgentSwarm pattern.
|
|
17
10
|
*/
|
|
18
|
-
import { matchesKey, Key, isKeyRepeat } from "@earendil-works/pi-tui";
|
|
19
11
|
// ---------------------------------------------------------------------------
|
|
20
12
|
// Constants
|
|
21
13
|
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
const BRAILLE_LEVELS = [
|
|
15
|
+
"\u28C0",
|
|
16
|
+
"\u28C4",
|
|
17
|
+
"\u28E4",
|
|
18
|
+
"\u28E6",
|
|
19
|
+
"\u28F6",
|
|
20
|
+
"\u28F7",
|
|
21
|
+
"\u28FF",
|
|
22
|
+
];
|
|
23
|
+
const BRAILLE_EMPTY = "\u2800"; // truly empty braille cell (no dots)
|
|
25
24
|
const BRAILLE_SPINNER = [
|
|
26
25
|
"\u28BF",
|
|
27
26
|
"\u28FB",
|
|
@@ -32,46 +31,18 @@ const BRAILLE_SPINNER = [
|
|
|
32
31
|
"\u28DF",
|
|
33
32
|
"\u287F",
|
|
34
33
|
];
|
|
35
|
-
/** Braille characters representing fill levels 0-6 dots for completed bar. */
|
|
36
|
-
const BRAILLE_LEVELS = [
|
|
37
|
-
"\u28C0",
|
|
38
|
-
"\u28C4",
|
|
39
|
-
"\u28E4",
|
|
40
|
-
"\u28E6",
|
|
41
|
-
"\u28F6",
|
|
42
|
-
"\u28F7",
|
|
43
|
-
"\u28FF",
|
|
44
|
-
];
|
|
45
|
-
const BRAILLE_EMPTY = BRAILLE_LEVELS[0];
|
|
46
|
-
const BRAILLE_FULL = BRAILLE_LEVELS[6];
|
|
47
|
-
const COMPLETED_BAR_WIDTH = 4;
|
|
48
|
-
/** Maximum members shown at once in the list view. */
|
|
49
|
-
const VISIBLE_MEMBERS = 8;
|
|
50
|
-
/** Maximum events shown in the event log panel. */
|
|
51
|
-
const VISIBLE_EVENTS = 10;
|
|
52
|
-
/** Debounce window for coalescing render requests. */
|
|
53
|
-
const DEBOUNCE_MS = 75;
|
|
54
|
-
/** Fallback polling interval when state is active (has running members). */
|
|
55
|
-
const ACTIVE_POLL_MS = 800;
|
|
56
|
-
/** Fallback polling interval when idle (no running members). */
|
|
57
|
-
const IDLE_POLL_MS = 2000;
|
|
58
|
-
/** Animation interval for the braille spinner. */
|
|
59
34
|
const FRAME_INTERVAL_MS = 80;
|
|
35
|
+
const DEBOUNCE_MS = 75;
|
|
36
|
+
const POLL_MS = 800;
|
|
37
|
+
const MAX_VISIBLE_MEMBERS = 20;
|
|
38
|
+
const CELL_GAP = " ";
|
|
39
|
+
const BRAILLE_BAR_MIN_WIDTH = 2;
|
|
40
|
+
const BRAILLE_BAR_MAX_WIDTH = 5;
|
|
41
|
+
const ID_WIDTH = 3;
|
|
60
42
|
// ---------------------------------------------------------------------------
|
|
61
43
|
// Snapshot conversion
|
|
62
44
|
// ---------------------------------------------------------------------------
|
|
63
45
|
export function snapshotToProgressState(snapshot, title) {
|
|
64
|
-
const now = Date.now();
|
|
65
|
-
const members = snapshot.members.map((m) => ({
|
|
66
|
-
index: m.index,
|
|
67
|
-
phase: mapMemberPhase(m.phase),
|
|
68
|
-
item: m.item,
|
|
69
|
-
error: m.error,
|
|
70
|
-
phaseStartedAt: isAnimatedPhase(m.phase) ? now : undefined,
|
|
71
|
-
currentTool: m.currentTool,
|
|
72
|
-
activity: m.activity,
|
|
73
|
-
usage: m.usage,
|
|
74
|
-
}));
|
|
75
46
|
return {
|
|
76
47
|
title,
|
|
77
48
|
total: snapshot.total,
|
|
@@ -79,11 +50,18 @@ export function snapshotToProgressState(snapshot, title) {
|
|
|
79
50
|
failed: snapshot.failed,
|
|
80
51
|
active: snapshot.active,
|
|
81
52
|
queued: snapshot.queued,
|
|
82
|
-
members
|
|
53
|
+
members: snapshot.members.map((m) => ({
|
|
54
|
+
index: m.index,
|
|
55
|
+
phase: mapMemberPhase(m.phase),
|
|
56
|
+
item: m.item,
|
|
57
|
+
error: m.error,
|
|
58
|
+
currentTool: m.currentTool,
|
|
59
|
+
activity: m.activity,
|
|
60
|
+
usage: m.usage,
|
|
61
|
+
progressTick: m.progressTick,
|
|
62
|
+
})),
|
|
83
63
|
totalUsage: snapshot.totalUsage,
|
|
84
|
-
startedAt: snapshot.startedAt ?? now,
|
|
85
|
-
estimatedRemainingMs: snapshot.estimatedRemainingMs,
|
|
86
|
-
eventLog: snapshot.eventLog ? [...snapshot.eventLog] : undefined,
|
|
64
|
+
startedAt: snapshot.startedAt ?? Date.now(),
|
|
87
65
|
};
|
|
88
66
|
}
|
|
89
67
|
function mapMemberPhase(phase) {
|
|
@@ -100,29 +78,16 @@ function mapMemberPhase(phase) {
|
|
|
100
78
|
return "suspended";
|
|
101
79
|
}
|
|
102
80
|
}
|
|
103
|
-
function isAnimatedPhase(phase) {
|
|
104
|
-
return phase === "working" || phase === "suspended";
|
|
105
|
-
}
|
|
106
81
|
// ---------------------------------------------------------------------------
|
|
107
82
|
// Component
|
|
108
83
|
// ---------------------------------------------------------------------------
|
|
109
84
|
export class AgentSwarmProgressComponent {
|
|
110
85
|
state_ = null;
|
|
111
|
-
renderedWidth;
|
|
112
|
-
cachedLines;
|
|
113
86
|
onRequestRender;
|
|
114
|
-
// Render scheduler
|
|
115
87
|
animationFrame;
|
|
116
88
|
frameIndex = 0;
|
|
117
89
|
pollTimer;
|
|
118
90
|
debounceTimer;
|
|
119
|
-
pendingInvalidate = false;
|
|
120
|
-
hasActiveMembers = false;
|
|
121
|
-
// Input / UI state
|
|
122
|
-
scrollOffset = 0;
|
|
123
|
-
selectedIndex = -1;
|
|
124
|
-
activePanel = "members";
|
|
125
|
-
overlay = null;
|
|
126
91
|
constructor(onRequestRender) {
|
|
127
92
|
this.onRequestRender = onRequestRender;
|
|
128
93
|
this.startPolling();
|
|
@@ -133,11 +98,6 @@ export class AgentSwarmProgressComponent {
|
|
|
133
98
|
// -------------------------------------------------------------------
|
|
134
99
|
update(state) {
|
|
135
100
|
this.state_ = state;
|
|
136
|
-
this.hasActiveMembers = state.active > 0;
|
|
137
|
-
// Reset scroll offset if it exceeds member count
|
|
138
|
-
if (this.scrollOffset > Math.max(0, state.members.length - VISIBLE_MEMBERS)) {
|
|
139
|
-
this.scrollOffset = Math.max(0, state.members.length - VISIBLE_MEMBERS);
|
|
140
|
-
}
|
|
141
101
|
this.requestRender();
|
|
142
102
|
}
|
|
143
103
|
complete() {
|
|
@@ -147,14 +107,12 @@ export class AgentSwarmProgressComponent {
|
|
|
147
107
|
m.phase !== "failed" &&
|
|
148
108
|
m.phase !== "cancelled") {
|
|
149
109
|
m.phase = "completed";
|
|
150
|
-
m.phaseStartedAt = Date.now();
|
|
151
110
|
}
|
|
152
111
|
}
|
|
153
112
|
this.state_.active = 0;
|
|
154
113
|
this.state_.queued = 0;
|
|
155
114
|
this.state_.completed = this.state_.total - this.state_.failed;
|
|
156
115
|
}
|
|
157
|
-
this.hasActiveMembers = false;
|
|
158
116
|
this.requestRender();
|
|
159
117
|
}
|
|
160
118
|
dispose() {
|
|
@@ -162,351 +120,208 @@ export class AgentSwarmProgressComponent {
|
|
|
162
120
|
this.onRequestRender = undefined;
|
|
163
121
|
}
|
|
164
122
|
invalidate() {
|
|
165
|
-
|
|
166
|
-
this.cachedLines = undefined;
|
|
123
|
+
/* no-op */
|
|
167
124
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// -------------------------------------------------------------------
|
|
171
|
-
handleInput(data) {
|
|
172
|
-
// Ignore key repeats for navigation keys to avoid scroll jank
|
|
173
|
-
const isRepeat = isKeyRepeat(data);
|
|
174
|
-
// Close overlay on Escape / q
|
|
175
|
-
if (this.overlay) {
|
|
176
|
-
if (matchesKey(data, Key.escape) || matchesKey(data, "q")) {
|
|
177
|
-
this.overlay = null;
|
|
178
|
-
this.requestRender();
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
// Navigation
|
|
184
|
-
if (matchesKey(data, "j") || matchesKey(data, Key.down)) {
|
|
185
|
-
if (!isRepeat) {
|
|
186
|
-
this.scrollDown(1);
|
|
187
|
-
}
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (matchesKey(data, "k") || matchesKey(data, Key.up)) {
|
|
191
|
-
if (!isRepeat) {
|
|
192
|
-
this.scrollUp(1);
|
|
193
|
-
}
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (matchesKey(data, Key.pageDown)) {
|
|
197
|
-
this.scrollDown(VISIBLE_MEMBERS);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (matchesKey(data, Key.pageUp)) {
|
|
201
|
-
this.scrollUp(VISIBLE_MEMBERS);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (matchesKey(data, "g") || matchesKey(data, Key.home)) {
|
|
205
|
-
this.scrollOffset = 0;
|
|
206
|
-
this.selectedIndex = -1;
|
|
207
|
-
this.requestRender();
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
if (matchesKey(data, Key.shift("g")) || matchesKey(data, Key.end)) {
|
|
211
|
-
if (this.state_) {
|
|
212
|
-
const maxScroll = Math.max(0, this.state_.members.length - VISIBLE_MEMBERS);
|
|
213
|
-
this.scrollOffset = maxScroll;
|
|
214
|
-
this.selectedIndex = Math.max(0, this.state_.members.length - 1);
|
|
215
|
-
}
|
|
216
|
-
this.requestRender();
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
// Panel switching
|
|
220
|
-
if (matchesKey(data, Key.tab) || matchesKey(data, "1")) {
|
|
221
|
-
this.activePanel = "members";
|
|
222
|
-
this.scrollOffset = 0;
|
|
223
|
-
this.selectedIndex = -1;
|
|
224
|
-
this.requestRender();
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (matchesKey(data, "2")) {
|
|
228
|
-
this.activePanel = "events";
|
|
229
|
-
this.scrollOffset = 0;
|
|
230
|
-
this.requestRender();
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
// Detail overlay
|
|
234
|
-
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
235
|
-
if (this.activePanel === "members" && this.state_) {
|
|
236
|
-
const idx = this.selectedIndex >= 0 ? this.selectedIndex : this.scrollOffset;
|
|
237
|
-
if (idx >= 0 && idx < this.state_.members.length) {
|
|
238
|
-
this.overlay = { kind: "detail" };
|
|
239
|
-
this.requestRender();
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
// Help overlay
|
|
245
|
-
if (matchesKey(data, "?")) {
|
|
246
|
-
this.overlay = { kind: "help" };
|
|
247
|
-
this.requestRender();
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
125
|
+
handleInput(_data) {
|
|
126
|
+
/* minimal: no keyboard */
|
|
250
127
|
}
|
|
251
128
|
// -------------------------------------------------------------------
|
|
252
129
|
// Rendering
|
|
253
130
|
// -------------------------------------------------------------------
|
|
254
131
|
render(width) {
|
|
255
132
|
const safeWidth = Math.max(20, width);
|
|
256
|
-
if (this.
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
if (!this.state_ || this.state_.members.length === 0) {
|
|
260
|
-
this.cachedLines = [];
|
|
261
|
-
return this.cachedLines;
|
|
262
|
-
}
|
|
263
|
-
// If overlay is active, render overlay content
|
|
264
|
-
if (this.overlay) {
|
|
265
|
-
const lines = this.renderOverlay(safeWidth);
|
|
266
|
-
this.cachedLines = lines;
|
|
267
|
-
return this.cachedLines;
|
|
268
|
-
}
|
|
133
|
+
if (!this.state_ || this.state_.members.length === 0)
|
|
134
|
+
return [];
|
|
269
135
|
const state = this.state_;
|
|
270
|
-
const contentWidth = safeWidth - 4;
|
|
271
136
|
const lines = [];
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const total = state.total || 1;
|
|
280
|
-
const doneRatio = (state.completed + state.failed) / total;
|
|
281
|
-
const doneChars = Math.round(doneRatio * barWidth);
|
|
282
|
-
const done = "\u2501".repeat(doneChars);
|
|
283
|
-
const remaining = "\u2501".repeat(barWidth - doneChars);
|
|
284
|
-
lines.push(` ${done}${remaining}`);
|
|
285
|
-
if (this.activePanel === "members") {
|
|
286
|
-
this.renderMembersPanel(lines, state, contentWidth);
|
|
137
|
+
// Layout: header → grid → status line
|
|
138
|
+
lines.push(this.renderHeader(safeWidth, state));
|
|
139
|
+
lines.push("");
|
|
140
|
+
if (state.members.length === 1) {
|
|
141
|
+
// Single agent: compact line, no grid
|
|
142
|
+
const row = this.renderSingleAgent(state.members[0], safeWidth - 2);
|
|
143
|
+
lines.push(row);
|
|
287
144
|
}
|
|
288
145
|
else {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
lines.push(
|
|
294
|
-
//
|
|
295
|
-
const
|
|
296
|
-
lines.push(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
lines.push(bottom);
|
|
300
|
-
this.cachedLines = lines;
|
|
301
|
-
this.renderedWidth = safeWidth;
|
|
302
|
-
return this.cachedLines;
|
|
146
|
+
// Multiple agents: grid layout
|
|
147
|
+
const gridLines = this.renderGrid(safeWidth - 2, state);
|
|
148
|
+
lines.push(...gridLines);
|
|
149
|
+
}
|
|
150
|
+
lines.push("");
|
|
151
|
+
// Bottom separator
|
|
152
|
+
const sepWidth = safeWidth - 2;
|
|
153
|
+
lines.push(truncateText(repeatStr("─", sepWidth), sepWidth));
|
|
154
|
+
lines.push(this.renderStatusLine(safeWidth, state));
|
|
155
|
+
return lines;
|
|
303
156
|
}
|
|
304
157
|
// -------------------------------------------------------------------
|
|
305
|
-
//
|
|
158
|
+
// Kimi-code style header: ─ Agent Swarm ─ description ──────
|
|
306
159
|
// -------------------------------------------------------------------
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const row = renderMemberRow(member, contentWidth, this.frameIndex, isSelected);
|
|
321
|
-
lines.push(` ${row}`);
|
|
322
|
-
}
|
|
323
|
-
// Scroll indicator (bottom)
|
|
324
|
-
if (hasMore && maxMembers < state.members.length) {
|
|
325
|
-
const remaining = state.members.length - maxMembers;
|
|
326
|
-
lines.push(` \u2193 ${remaining} more below`);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
renderEventsPanel(lines, state, contentWidth) {
|
|
330
|
-
const events = state.eventLog ?? [];
|
|
331
|
-
const displayEvents = events.slice(-VISIBLE_EVENTS);
|
|
332
|
-
if (displayEvents.length === 0) {
|
|
333
|
-
lines.push(` ${padRight("(no events yet)", contentWidth)}`);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
for (const event of displayEvents) {
|
|
337
|
-
const time = new Date(event.timestamp).toLocaleTimeString();
|
|
338
|
-
const icon = eventIcon(event.type);
|
|
339
|
-
const detail = truncateText(event.detail, contentWidth - 12);
|
|
340
|
-
lines.push(` ${icon} ${time} ${detail}`);
|
|
341
|
-
}
|
|
160
|
+
renderHeader(width, state) {
|
|
161
|
+
const mode = state.mailbox ? "Swarm Team" : "Agent Swarm";
|
|
162
|
+
const desc = state.title ?? "";
|
|
163
|
+
const mailboxInfo = state.mailboxCount && state.mailboxCount > 0
|
|
164
|
+
? ` │ Mailbox: ${state.mailboxCount}`
|
|
165
|
+
: "";
|
|
166
|
+
// ─ Agent Swarm ─ <desc> ────── or ─ Swarm Team ─ <desc> ─ Mailbox: 3
|
|
167
|
+
const prefix = `─ ${mode}`;
|
|
168
|
+
const content = desc ? ` ─ ${desc}` : "";
|
|
169
|
+
const label = `${prefix}${content}${mailboxInfo}`;
|
|
170
|
+
const suffixLen = Math.max(0, width - visibleLen(label) - 2);
|
|
171
|
+
const suffix = suffixLen > 0 ? ` ─${repeatStr("─", suffixLen)}` : "";
|
|
172
|
+
return truncateText(`${label}${suffix}`, width);
|
|
342
173
|
}
|
|
343
174
|
// -------------------------------------------------------------------
|
|
344
|
-
//
|
|
175
|
+
// Status line
|
|
345
176
|
// -------------------------------------------------------------------
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return
|
|
177
|
+
renderStatusLine(width, state) {
|
|
178
|
+
const done = state.completed + state.failed;
|
|
179
|
+
const total = state.total;
|
|
180
|
+
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
181
|
+
const elapsed = formatElapsed(Date.now() - state.startedAt);
|
|
182
|
+
const label = state.active > 0 ? "Working..." : "Completed";
|
|
183
|
+
return truncateText(`${label} ${done}/${total} (${pct}%) ${elapsed}`, width);
|
|
353
184
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
185
|
+
// -------------------------------------------------------------------
|
|
186
|
+
// Single agent mode
|
|
187
|
+
// -------------------------------------------------------------------
|
|
188
|
+
renderSingleAgent(member, width) {
|
|
189
|
+
const spinner = member.phase === "working" || member.phase === "prompting"
|
|
190
|
+
? BRAILLE_SPINNER[this.frameIndex % BRAILLE_SPINNER.length]
|
|
191
|
+
: member.phase === "completed"
|
|
192
|
+
? "\u2713"
|
|
193
|
+
: member.phase === "failed"
|
|
194
|
+
? "\u2717"
|
|
195
|
+
: "\u25CB";
|
|
196
|
+
const item = member.item ?? `#${String(member.index).padStart(2, "0")}`;
|
|
197
|
+
const itemTrunc = truncateText(item, Math.max(4, Math.floor(width * 0.4)));
|
|
198
|
+
let suffix = "";
|
|
199
|
+
const remaining = Math.max(0, width - visibleLen(`${spinner} ${itemTrunc}`) - 2);
|
|
200
|
+
if (member.phase === "working" && member.activity && remaining > 4) {
|
|
201
|
+
suffix = ` ${truncateText(member.activity, Math.min(remaining - 1, width - 10))}`;
|
|
202
|
+
}
|
|
203
|
+
else if (member.phase === "completed") {
|
|
204
|
+
suffix = " ok";
|
|
205
|
+
}
|
|
206
|
+
else if (member.phase === "failed" && member.error && remaining > 4) {
|
|
207
|
+
suffix = ` ${truncateText(member.error, Math.min(remaining - 1, 30))}`;
|
|
208
|
+
}
|
|
209
|
+
// No braille bar for single agent — just spinner + item + scrolling text
|
|
210
|
+
return truncateText(`${spinner} ${itemTrunc}${suffix}`, width);
|
|
379
211
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const idx = this.selectedIndex >= 0 ? this.selectedIndex : this.scrollOffset;
|
|
387
|
-
const member = this.state_.members[idx];
|
|
388
|
-
if (!member)
|
|
212
|
+
// -------------------------------------------------------------------
|
|
213
|
+
// Grid layout (multiple agents)
|
|
214
|
+
// -------------------------------------------------------------------
|
|
215
|
+
renderGrid(width, state) {
|
|
216
|
+
const count = Math.min(state.members.length, MAX_VISIBLE_MEMBERS);
|
|
217
|
+
if (count <= 0)
|
|
389
218
|
return [];
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
219
|
+
// Calculate grid dimensions
|
|
220
|
+
const gapWidth = visibleLen(CELL_GAP);
|
|
221
|
+
const minLabelWidth = 8;
|
|
222
|
+
const estCellWidth = ID_WIDTH + BRAILLE_BAR_MIN_WIDTH + minLabelWidth;
|
|
223
|
+
const columns = Math.max(1, Math.min(count, Math.floor((width + gapWidth) / (estCellWidth + gapWidth))));
|
|
224
|
+
const rows = Math.ceil(count / columns);
|
|
225
|
+
const actualCellWidth = Math.floor((width - gapWidth * (columns - 1)) / columns);
|
|
226
|
+
// Bar gets what's left after ID + minimum label; cap to keep label readable
|
|
227
|
+
const barCells = Math.max(BRAILLE_BAR_MIN_WIDTH, Math.min(BRAILLE_BAR_MAX_WIDTH, actualCellWidth - ID_WIDTH - minLabelWidth));
|
|
228
|
+
const leftPad = Math.floor((width - (actualCellWidth * columns + gapWidth * (columns - 1))) / 2);
|
|
229
|
+
const lines = [];
|
|
230
|
+
for (let row = 0; row < rows; row++) {
|
|
231
|
+
let line = " ".repeat(Math.max(0, leftPad));
|
|
232
|
+
for (let col = 0; col < columns; col++) {
|
|
233
|
+
const idx = row * columns + col;
|
|
234
|
+
const member = state.members[idx];
|
|
235
|
+
if (!member)
|
|
236
|
+
continue;
|
|
237
|
+
const cell = this.renderCell(member, actualCellWidth, barCells);
|
|
238
|
+
line += cell;
|
|
239
|
+
if (col < columns - 1)
|
|
240
|
+
line += CELL_GAP;
|
|
241
|
+
}
|
|
242
|
+
lines.push(truncateText(line, width));
|
|
414
243
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
lines.push(`
|
|
244
|
+
// Extra info line for truncated members
|
|
245
|
+
if (state.members.length > MAX_VISIBLE_MEMBERS) {
|
|
246
|
+
lines.push(` ... ${state.members.length - MAX_VISIBLE_MEMBERS} more`);
|
|
418
247
|
}
|
|
419
|
-
lines.push(` \u2502 ${padRight("", safeWidth - 4)}\u2502`);
|
|
420
|
-
lines.push(` \u2502 ${padRight("Press q or Esc to close", safeWidth - 4)}\u2502`);
|
|
421
|
-
lines.push(bottom);
|
|
422
248
|
return lines;
|
|
423
249
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
return;
|
|
430
|
-
const memberCount = this.state_.members.length;
|
|
431
|
-
const maxOffset = Math.max(0, memberCount - 1);
|
|
432
|
-
this.selectedIndex = Math.min(maxOffset, Math.max(0, this.selectedIndex < 0 ? this.scrollOffset : this.selectedIndex) + n);
|
|
433
|
-
if (this.selectedIndex - this.scrollOffset >= VISIBLE_MEMBERS ||
|
|
434
|
-
this.selectedIndex < this.scrollOffset) {
|
|
435
|
-
this.scrollOffset = Math.max(0, this.selectedIndex - VISIBLE_MEMBERS + 1);
|
|
436
|
-
const maxScroll = Math.max(0, memberCount - VISIBLE_MEMBERS);
|
|
437
|
-
this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
|
|
438
|
-
}
|
|
439
|
-
this.requestRender();
|
|
250
|
+
renderCell(member, cellWidth, barCells) {
|
|
251
|
+
const id = String(member.index).padStart(ID_WIDTH, "0");
|
|
252
|
+
const bar = this.renderBrailleBar(member, barCells);
|
|
253
|
+
const label = this.renderCellLabel(member, Math.max(1, cellWidth - ID_WIDTH - barCells - 3));
|
|
254
|
+
return `${id} ${bar}${label ? " " + label : ""}`;
|
|
440
255
|
}
|
|
441
|
-
|
|
442
|
-
if (
|
|
443
|
-
return;
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
256
|
+
renderBrailleBar(member, width) {
|
|
257
|
+
if (width <= 0)
|
|
258
|
+
return "";
|
|
259
|
+
const capacity = width * BRAILLE_LEVELS.length;
|
|
260
|
+
const ticks = member.phase === "completed"
|
|
261
|
+
? capacity
|
|
262
|
+
: member.phase === "working"
|
|
263
|
+
? Math.min(capacity, (member.progressTick ?? 0) + (this.frameIndex % 3))
|
|
264
|
+
: 0;
|
|
265
|
+
const fullBars = Math.floor(ticks / BRAILLE_LEVELS.length);
|
|
266
|
+
const partial = ticks % BRAILLE_LEVELS.length;
|
|
267
|
+
const partialChar = partial > 0 ? BRAILLE_LEVELS[partial - 1] : "";
|
|
268
|
+
let bar = "";
|
|
269
|
+
for (let i = 0; i < width; i++) {
|
|
270
|
+
if (i < fullBars) {
|
|
271
|
+
bar += BRAILLE_LEVELS[BRAILLE_LEVELS.length - 1]; // Full █
|
|
272
|
+
}
|
|
273
|
+
else if (i === fullBars && partialChar) {
|
|
274
|
+
bar += partialChar;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
bar += BRAILLE_EMPTY;
|
|
278
|
+
}
|
|
447
279
|
}
|
|
448
|
-
|
|
280
|
+
return bar;
|
|
281
|
+
}
|
|
282
|
+
renderCellLabel(member, width) {
|
|
283
|
+
if (width <= 0)
|
|
284
|
+
return "";
|
|
285
|
+
// Show latest activity (model output or tool call) for running agents
|
|
286
|
+
if (member.phase === "working" || member.phase === "prompting") {
|
|
287
|
+
const text = member.activity ?? member.item ?? "";
|
|
288
|
+
return truncateText(text, width);
|
|
289
|
+
}
|
|
290
|
+
if (member.phase === "completed")
|
|
291
|
+
return truncateText("ok", width);
|
|
292
|
+
if (member.phase === "failed" && member.error)
|
|
293
|
+
return truncateText(member.error, Math.min(width, 20));
|
|
294
|
+
if (member.phase === "queued")
|
|
295
|
+
return truncateText(member.item ?? "...", width);
|
|
296
|
+
return "";
|
|
449
297
|
}
|
|
450
298
|
// -------------------------------------------------------------------
|
|
451
299
|
// Render scheduling
|
|
452
300
|
// -------------------------------------------------------------------
|
|
453
|
-
/**
|
|
454
|
-
* Request a debounced render. Multiple calls within the debounce window
|
|
455
|
-
* coalesce into a single render pass. State changes also trigger immediate
|
|
456
|
-
* invalidation.
|
|
457
|
-
*/
|
|
458
301
|
requestRender() {
|
|
459
|
-
this.invalidate();
|
|
460
302
|
if (this.debounceTimer !== undefined) {
|
|
461
|
-
this.pendingInvalidate = true;
|
|
462
303
|
return;
|
|
463
304
|
}
|
|
464
305
|
this.debounceTimer = setTimeout(() => {
|
|
465
306
|
this.debounceTimer = undefined;
|
|
466
|
-
this.pendingInvalidate = false;
|
|
467
307
|
this.onRequestRender?.();
|
|
468
|
-
this.updatePollingInterval();
|
|
469
308
|
}, DEBOUNCE_MS);
|
|
470
|
-
// Also trigger an immediate render for faster response to inputs
|
|
471
309
|
this.onRequestRender?.();
|
|
472
310
|
}
|
|
473
|
-
/**
|
|
474
|
-
* Start the fallback polling timer. Falls back to polling when no
|
|
475
|
-
* state changes trigger requests, ensuring the spinner animates.
|
|
476
|
-
*/
|
|
477
311
|
startPolling() {
|
|
478
|
-
this.schedulePoll();
|
|
479
|
-
}
|
|
480
|
-
schedulePoll() {
|
|
481
|
-
const interval = this.hasActiveMembers ? ACTIVE_POLL_MS : IDLE_POLL_MS;
|
|
482
312
|
this.pollTimer = setTimeout(() => {
|
|
483
313
|
this.pollTimer = undefined;
|
|
484
|
-
|
|
485
|
-
if (this.state_) {
|
|
486
|
-
this.invalidate();
|
|
314
|
+
if (this.state_)
|
|
487
315
|
this.onRequestRender?.();
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}, interval);
|
|
316
|
+
this.startPolling();
|
|
317
|
+
}, POLL_MS);
|
|
491
318
|
}
|
|
492
|
-
/**
|
|
493
|
-
* Adjust polling interval based on active member count.
|
|
494
|
-
*/
|
|
495
|
-
updatePollingInterval() {
|
|
496
|
-
// Will be picked up on next poll cycle
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Start the animation tick for the braille spinner.
|
|
500
|
-
*/
|
|
501
319
|
startAnimation() {
|
|
502
320
|
this.animationFrame = setInterval(() => {
|
|
503
321
|
this.frameIndex = (this.frameIndex + 1) % BRAILLE_SPINNER.length;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (this.hasActiveMembers || this.overlay) {
|
|
507
|
-
this.invalidate();
|
|
322
|
+
const hasActive = this.state_?.active ?? 0 > 0;
|
|
323
|
+
if (hasActive)
|
|
508
324
|
this.onRequestRender?.();
|
|
509
|
-
}
|
|
510
325
|
}, FRAME_INTERVAL_MS);
|
|
511
326
|
}
|
|
512
327
|
stopTimers() {
|
|
@@ -525,108 +340,13 @@ export class AgentSwarmProgressComponent {
|
|
|
525
340
|
}
|
|
526
341
|
}
|
|
527
342
|
// ---------------------------------------------------------------------------
|
|
528
|
-
//
|
|
343
|
+
// Text utilities
|
|
529
344
|
// ---------------------------------------------------------------------------
|
|
530
|
-
function
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
return "\u2713"; // ✓
|
|
536
|
-
case "failed":
|
|
537
|
-
return "\u2717"; // ✗
|
|
538
|
-
case "tool_execution":
|
|
539
|
-
return "\u2699"; // ⚙
|
|
540
|
-
case "suspended":
|
|
541
|
-
return "\u23F3"; // ⏳
|
|
542
|
-
case "phase_change":
|
|
543
|
-
return "\u2192"; // →
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
function renderMemberRow(member, width, frameIndex, selected) {
|
|
547
|
-
const prefix = selected ? ">" : " ";
|
|
548
|
-
const indexLabel = `#${String(member.index).padStart(2, "0")}`;
|
|
549
|
-
const icon = memberIcon(member, frameIndex);
|
|
550
|
-
const statusLabel = shortPhaseLabel(member.phase);
|
|
551
|
-
// Layout with optional activity column
|
|
552
|
-
const fixed = `${prefix}${indexLabel} ${icon} ${statusLabel}`;
|
|
553
|
-
const fixedLen = visibleLen(fixed);
|
|
554
|
-
let remaining = Math.max(0, width - fixedLen - 2);
|
|
555
|
-
// Show current tool if available
|
|
556
|
-
let activitySuffix = "";
|
|
557
|
-
if (member.currentTool && member.phase === "working") {
|
|
558
|
-
const toolInfo = member.activity
|
|
559
|
-
? `${member.currentTool}: ${member.activity}`
|
|
560
|
-
: member.currentTool;
|
|
561
|
-
if (toolInfo.length < remaining - 2) {
|
|
562
|
-
activitySuffix = ` ${truncateText(toolInfo, Math.min(30, remaining - 2))}`;
|
|
563
|
-
remaining -= visibleLen(activitySuffix);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
const itemLabel = member.item ?? "";
|
|
567
|
-
const truncatedItem = truncateText(itemLabel, Math.max(0, remaining));
|
|
568
|
-
return `${fixed} ${truncatedItem}${activitySuffix}`;
|
|
569
|
-
}
|
|
570
|
-
function memberIcon(member, frameIndex) {
|
|
571
|
-
switch (member.phase) {
|
|
572
|
-
case "completed":
|
|
573
|
-
return BRAILLE_FULL.repeat(2);
|
|
574
|
-
case "failed":
|
|
575
|
-
case "cancelled":
|
|
576
|
-
return "\u2717 ";
|
|
577
|
-
case "pending":
|
|
578
|
-
case "queued":
|
|
579
|
-
return "\u25CB ";
|
|
580
|
-
case "prompting":
|
|
581
|
-
case "working":
|
|
582
|
-
case "suspended":
|
|
583
|
-
return BRAILLE_SPINNER[frameIndex % BRAILLE_SPINNER.length] + " ";
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
function shortPhaseLabel(phase) {
|
|
587
|
-
switch (phase) {
|
|
588
|
-
case "pending":
|
|
589
|
-
case "queued":
|
|
590
|
-
return "wait";
|
|
591
|
-
case "prompting":
|
|
592
|
-
return "init";
|
|
593
|
-
case "working":
|
|
594
|
-
return "work";
|
|
595
|
-
case "completed":
|
|
596
|
-
return "done";
|
|
597
|
-
case "failed":
|
|
598
|
-
return "fail";
|
|
599
|
-
case "cancelled":
|
|
600
|
-
return "abort";
|
|
601
|
-
case "suspended":
|
|
602
|
-
return "retry";
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
function buildFooter(state, width) {
|
|
606
|
-
const usage = state.totalUsage ?? {
|
|
607
|
-
input: 0,
|
|
608
|
-
output: 0,
|
|
609
|
-
cacheRead: 0,
|
|
610
|
-
cacheWrite: 0,
|
|
611
|
-
totalTokens: 0,
|
|
612
|
-
};
|
|
613
|
-
const counts = `${state.completed + state.failed}/${state.total} ag`;
|
|
614
|
-
const tokens = `${Math.round(usage.input)}in/${Math.round(usage.output)}out`;
|
|
615
|
-
const elapsed = formatElapsed(Date.now() - state.startedAt);
|
|
616
|
-
const parts = [counts, tokens, elapsed];
|
|
617
|
-
if (state.failed > 0)
|
|
618
|
-
parts.splice(1, 0, `${state.failed}fail`);
|
|
619
|
-
if (state.active > 0)
|
|
620
|
-
parts.splice(1, 0, `${state.active}act`);
|
|
621
|
-
// ETA
|
|
622
|
-
if (state.estimatedRemainingMs !== undefined &&
|
|
623
|
-
state.estimatedRemainingMs > 0) {
|
|
624
|
-
parts.push(formatElapsed(state.estimatedRemainingMs));
|
|
625
|
-
}
|
|
626
|
-
const full = parts.join(" | ");
|
|
627
|
-
if (full.length <= width)
|
|
628
|
-
return full;
|
|
629
|
-
return truncateText(full, width);
|
|
345
|
+
function repeatStr(ch, count) {
|
|
346
|
+
let out = "";
|
|
347
|
+
for (let i = 0; i < count; i++)
|
|
348
|
+
out += ch;
|
|
349
|
+
return out;
|
|
630
350
|
}
|
|
631
351
|
function formatElapsed(ms) {
|
|
632
352
|
const totalSec = Math.floor(ms / 1000);
|
|
@@ -634,24 +354,21 @@ function formatElapsed(ms) {
|
|
|
634
354
|
return `${totalSec}s`;
|
|
635
355
|
const m = Math.floor(totalSec / 60);
|
|
636
356
|
const s = totalSec % 60;
|
|
637
|
-
|
|
357
|
+
if (m < 60)
|
|
358
|
+
return `${m}m ${s}s`;
|
|
359
|
+
const h = Math.floor(m / 60);
|
|
360
|
+
return `${h}h ${m % 60}m ${s}s`;
|
|
638
361
|
}
|
|
639
|
-
// ---------------------------------------------------------------------------
|
|
640
|
-
// Text utilities
|
|
641
|
-
// ---------------------------------------------------------------------------
|
|
642
362
|
function visibleLen(text) {
|
|
643
363
|
return text.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
644
364
|
}
|
|
645
365
|
function truncateText(text, maxLen) {
|
|
366
|
+
if (maxLen <= 0)
|
|
367
|
+
return "";
|
|
646
368
|
if (text.length <= maxLen)
|
|
647
369
|
return text;
|
|
648
370
|
if (maxLen <= 1)
|
|
649
371
|
return text.slice(0, 1);
|
|
650
|
-
return text.slice(0,
|
|
651
|
-
}
|
|
652
|
-
function padRight(text, width) {
|
|
653
|
-
if (text.length >= width)
|
|
654
|
-
return text.slice(0, width);
|
|
655
|
-
return text + " ".repeat(width - text.length);
|
|
372
|
+
return text.slice(0, maxLen - 1) + "\u2026";
|
|
656
373
|
}
|
|
657
374
|
//# sourceMappingURL=progress.js.map
|