aoaoe 0.81.0 → 0.83.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/dist/index.js +38 -0
- package/dist/input.d.ts +12 -0
- package/dist/input.js +62 -0
- package/dist/tui.d.ts +35 -1
- package/dist/tui.js +124 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -378,6 +378,44 @@ async function main() {
|
|
|
378
378
|
tui.setCompact(enabled);
|
|
379
379
|
tui.log("system", `compact mode: ${enabled ? "on" : "off"}`);
|
|
380
380
|
});
|
|
381
|
+
// wire /mark bookmark
|
|
382
|
+
input.onMark(() => {
|
|
383
|
+
const num = tui.addBookmark();
|
|
384
|
+
if (num > 0) {
|
|
385
|
+
tui.log("system", `bookmark #${num} saved`);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
tui.log("system", "nothing to bookmark (activity buffer empty)");
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
// wire /jump to bookmark
|
|
392
|
+
input.onJump((num) => {
|
|
393
|
+
const ok = tui.jumpToBookmark(num);
|
|
394
|
+
if (ok) {
|
|
395
|
+
tui.log("system", `jumped to bookmark #${num}`);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
tui.log("system", `bookmark #${num} not found`);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
// wire /marks listing
|
|
402
|
+
input.onMarks(() => {
|
|
403
|
+
const bms = tui.getBookmarks();
|
|
404
|
+
if (bms.length === 0) {
|
|
405
|
+
tui.log("system", "no bookmarks — use /mark to save one");
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
for (let i = 0; i < bms.length; i++) {
|
|
409
|
+
tui.log("system", ` #${i + 1}: ${bms[i].label}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
// wire /focus toggle
|
|
414
|
+
input.onFocus(() => {
|
|
415
|
+
const enabled = !tui.isFocused();
|
|
416
|
+
tui.setFocus(enabled);
|
|
417
|
+
tui.log("system", `focus mode: ${enabled ? "on (pinned only)" : "off (all sessions)"}`);
|
|
418
|
+
});
|
|
381
419
|
// wire /bell toggle
|
|
382
420
|
input.onBell(() => {
|
|
383
421
|
const enabled = !tui.isBellEnabled();
|
package/dist/input.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export type SortHandler = (mode: string | null) => void;
|
|
|
7
7
|
export type CompactHandler = () => void;
|
|
8
8
|
export type PinHandler = (target: string) => void;
|
|
9
9
|
export type BellHandler = () => void;
|
|
10
|
+
export type FocusHandler = () => void;
|
|
11
|
+
export type MarkHandler = () => void;
|
|
12
|
+
export type JumpHandler = (num: number) => void;
|
|
13
|
+
export type MarksHandler = () => void;
|
|
10
14
|
export interface MouseEvent {
|
|
11
15
|
button: number;
|
|
12
16
|
col: number;
|
|
@@ -36,6 +40,10 @@ export declare class InputReader {
|
|
|
36
40
|
private compactHandler;
|
|
37
41
|
private pinHandler;
|
|
38
42
|
private bellHandler;
|
|
43
|
+
private focusHandler;
|
|
44
|
+
private markHandler;
|
|
45
|
+
private jumpHandler;
|
|
46
|
+
private marksHandler;
|
|
39
47
|
private mouseDataListener;
|
|
40
48
|
onScroll(handler: (dir: ScrollDirection) => void): void;
|
|
41
49
|
onQueueChange(handler: (count: number) => void): void;
|
|
@@ -49,6 +57,10 @@ export declare class InputReader {
|
|
|
49
57
|
onCompact(handler: CompactHandler): void;
|
|
50
58
|
onPin(handler: PinHandler): void;
|
|
51
59
|
onBell(handler: BellHandler): void;
|
|
60
|
+
onFocus(handler: FocusHandler): void;
|
|
61
|
+
onMark(handler: MarkHandler): void;
|
|
62
|
+
onJump(handler: JumpHandler): void;
|
|
63
|
+
onMarks(handler: MarksHandler): void;
|
|
52
64
|
private notifyQueueChange;
|
|
53
65
|
start(): void;
|
|
54
66
|
drain(): string[];
|
package/dist/input.js
CHANGED
|
@@ -39,6 +39,10 @@ export class InputReader {
|
|
|
39
39
|
compactHandler = null;
|
|
40
40
|
pinHandler = null;
|
|
41
41
|
bellHandler = null;
|
|
42
|
+
focusHandler = null;
|
|
43
|
+
markHandler = null;
|
|
44
|
+
jumpHandler = null;
|
|
45
|
+
marksHandler = null;
|
|
42
46
|
mouseDataListener = null;
|
|
43
47
|
// register a callback for scroll key events (PgUp/PgDn/Home/End)
|
|
44
48
|
onScroll(handler) {
|
|
@@ -88,6 +92,22 @@ export class InputReader {
|
|
|
88
92
|
onBell(handler) {
|
|
89
93
|
this.bellHandler = handler;
|
|
90
94
|
}
|
|
95
|
+
// register a callback for focus mode toggle (/focus)
|
|
96
|
+
onFocus(handler) {
|
|
97
|
+
this.focusHandler = handler;
|
|
98
|
+
}
|
|
99
|
+
// register a callback for adding bookmarks (/mark)
|
|
100
|
+
onMark(handler) {
|
|
101
|
+
this.markHandler = handler;
|
|
102
|
+
}
|
|
103
|
+
// register a callback for jumping to bookmarks (/jump N)
|
|
104
|
+
onJump(handler) {
|
|
105
|
+
this.jumpHandler = handler;
|
|
106
|
+
}
|
|
107
|
+
// register a callback for listing bookmarks (/marks)
|
|
108
|
+
onMarks(handler) {
|
|
109
|
+
this.marksHandler = handler;
|
|
110
|
+
}
|
|
91
111
|
notifyQueueChange() {
|
|
92
112
|
this.queueChangeHandler?.(this.queue.length);
|
|
93
113
|
}
|
|
@@ -269,6 +289,10 @@ ${BOLD}navigation:${RESET}
|
|
|
269
289
|
/compact toggle compact mode (dense session panel)
|
|
270
290
|
/pin [N|name] pin/unpin a session to the top (toggle)
|
|
271
291
|
/bell toggle terminal bell on errors/completions
|
|
292
|
+
/focus toggle focus mode (show only pinned sessions)
|
|
293
|
+
/mark bookmark current activity position
|
|
294
|
+
/jump N jump to bookmark N
|
|
295
|
+
/marks list all bookmarks
|
|
272
296
|
/search <pattern> filter activity entries by substring (case-insensitive)
|
|
273
297
|
/search clear active search filter
|
|
274
298
|
click session click an agent card to drill down (click again to go back)
|
|
@@ -389,6 +413,44 @@ ${BOLD}other:${RESET}
|
|
|
389
413
|
console.error(`${DIM}bell not available (no TUI)${RESET}`);
|
|
390
414
|
}
|
|
391
415
|
break;
|
|
416
|
+
case "/focus":
|
|
417
|
+
if (this.focusHandler) {
|
|
418
|
+
this.focusHandler();
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
console.error(`${DIM}focus not available (no TUI)${RESET}`);
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
case "/mark":
|
|
425
|
+
if (this.markHandler) {
|
|
426
|
+
this.markHandler();
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
console.error(`${DIM}bookmarks not available (no TUI)${RESET}`);
|
|
430
|
+
}
|
|
431
|
+
break;
|
|
432
|
+
case "/jump": {
|
|
433
|
+
const jumpArg = line.slice("/jump".length).trim();
|
|
434
|
+
const jumpNum = parseInt(jumpArg, 10);
|
|
435
|
+
if (this.jumpHandler && !isNaN(jumpNum) && jumpNum > 0) {
|
|
436
|
+
this.jumpHandler(jumpNum);
|
|
437
|
+
}
|
|
438
|
+
else if (!this.jumpHandler) {
|
|
439
|
+
console.error(`${DIM}bookmarks not available (no TUI)${RESET}`);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
console.error(`${DIM}usage: /jump N — jump to bookmark number N${RESET}`);
|
|
443
|
+
}
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case "/marks":
|
|
447
|
+
if (this.marksHandler) {
|
|
448
|
+
this.marksHandler();
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
console.error(`${DIM}bookmarks not available (no TUI)${RESET}`);
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
392
454
|
case "/search": {
|
|
393
455
|
const searchArg = line.slice("/search".length).trim();
|
|
394
456
|
if (this.searchHandler) {
|
package/dist/tui.d.ts
CHANGED
|
@@ -10,6 +10,18 @@ declare function nextSortMode(current: SortMode): SortMode;
|
|
|
10
10
|
export declare const BELL_COOLDOWN_MS = 5000;
|
|
11
11
|
/** Determine if an activity entry should trigger a terminal bell. High-signal events only. */
|
|
12
12
|
export declare function shouldBell(tag: string, text: string): boolean;
|
|
13
|
+
export interface Bookmark {
|
|
14
|
+
index: number;
|
|
15
|
+
label: string;
|
|
16
|
+
}
|
|
17
|
+
/** Max number of bookmarks. */
|
|
18
|
+
export declare const MAX_BOOKMARKS = 20;
|
|
19
|
+
/**
|
|
20
|
+
* Compute the scroll offset needed to show a bookmarked entry.
|
|
21
|
+
* Centers the entry in the visible region when possible.
|
|
22
|
+
* Returns 0 (live) if the entry is within the visible tail.
|
|
23
|
+
*/
|
|
24
|
+
export declare function computeBookmarkOffset(bookmarkIndex: number, bufferLen: number, visibleLines: number): number;
|
|
13
25
|
/** Max name length in compact token. */
|
|
14
26
|
declare const COMPACT_NAME_LEN = 10;
|
|
15
27
|
/** Pin indicator for pinned sessions. */
|
|
@@ -53,6 +65,8 @@ export declare class TUI {
|
|
|
53
65
|
private prevLastActivity;
|
|
54
66
|
private compactMode;
|
|
55
67
|
private pinnedIds;
|
|
68
|
+
private focusMode;
|
|
69
|
+
private bookmarks;
|
|
56
70
|
private bellEnabled;
|
|
57
71
|
private lastBellAt;
|
|
58
72
|
private viewMode;
|
|
@@ -70,7 +84,7 @@ export declare class TUI {
|
|
|
70
84
|
start(version: string): void;
|
|
71
85
|
stop(): void;
|
|
72
86
|
isActive(): boolean;
|
|
73
|
-
/** Return the current number of sessions (for mouse hit testing) */
|
|
87
|
+
/** Return the current number of visible sessions (for mouse hit testing) */
|
|
74
88
|
getSessionCount(): number;
|
|
75
89
|
/** Set the session sort mode and repaint. */
|
|
76
90
|
setSortMode(mode: SortMode): void;
|
|
@@ -89,10 +103,30 @@ export declare class TUI {
|
|
|
89
103
|
isPinned(id: string): boolean;
|
|
90
104
|
/** Return count of pinned sessions. */
|
|
91
105
|
getPinnedCount(): number;
|
|
106
|
+
/** Enable or disable focus mode. When focused, only pinned sessions are visible. */
|
|
107
|
+
setFocus(enabled: boolean): void;
|
|
108
|
+
/** Return whether focus mode is enabled. */
|
|
109
|
+
isFocused(): boolean;
|
|
110
|
+
/** Return count of visible sessions (all in normal mode, pinned-only in focus mode). */
|
|
111
|
+
private getVisibleCount;
|
|
92
112
|
/** Enable or disable terminal bell notifications. */
|
|
93
113
|
setBell(enabled: boolean): void;
|
|
94
114
|
/** Return whether terminal bell is enabled. */
|
|
95
115
|
isBellEnabled(): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Add a bookmark at the current activity position.
|
|
118
|
+
* Returns the bookmark number (1-indexed) or 0 if buffer is empty.
|
|
119
|
+
*/
|
|
120
|
+
addBookmark(): number;
|
|
121
|
+
/**
|
|
122
|
+
* Jump to a bookmark by number (1-indexed). Returns false if not found.
|
|
123
|
+
* Adjusts scroll offset to center the bookmarked entry.
|
|
124
|
+
*/
|
|
125
|
+
jumpToBookmark(num: number): boolean;
|
|
126
|
+
/** Return all bookmarks (for /marks listing). */
|
|
127
|
+
getBookmarks(): readonly Bookmark[];
|
|
128
|
+
/** Return bookmark count. */
|
|
129
|
+
getBookmarkCount(): number;
|
|
96
130
|
updateState(opts: {
|
|
97
131
|
phase?: DaemonPhase;
|
|
98
132
|
pollCount?: number;
|
package/dist/tui.js
CHANGED
|
@@ -63,6 +63,23 @@ export function shouldBell(tag, text) {
|
|
|
63
63
|
return true;
|
|
64
64
|
return false;
|
|
65
65
|
}
|
|
66
|
+
/** Max number of bookmarks. */
|
|
67
|
+
export const MAX_BOOKMARKS = 20;
|
|
68
|
+
/**
|
|
69
|
+
* Compute the scroll offset needed to show a bookmarked entry.
|
|
70
|
+
* Centers the entry in the visible region when possible.
|
|
71
|
+
* Returns 0 (live) if the entry is within the visible tail.
|
|
72
|
+
*/
|
|
73
|
+
export function computeBookmarkOffset(bookmarkIndex, bufferLen, visibleLines) {
|
|
74
|
+
// entry position from the end of the buffer
|
|
75
|
+
const fromEnd = bufferLen - 1 - bookmarkIndex;
|
|
76
|
+
// if entry is within the visible tail, no scroll needed
|
|
77
|
+
if (fromEnd < visibleLines)
|
|
78
|
+
return 0;
|
|
79
|
+
// center the entry in the visible region
|
|
80
|
+
const half = Math.floor(visibleLines / 2);
|
|
81
|
+
return Math.max(0, fromEnd - half);
|
|
82
|
+
}
|
|
66
83
|
// ── Compact mode ────────────────────────────────────────────────────────────
|
|
67
84
|
/** Max name length in compact token. */
|
|
68
85
|
const COMPACT_NAME_LEN = 10;
|
|
@@ -161,6 +178,8 @@ export class TUI {
|
|
|
161
178
|
prevLastActivity = new Map(); // session ID → previous lastActivity string
|
|
162
179
|
compactMode = false;
|
|
163
180
|
pinnedIds = new Set(); // pinned session IDs (always sort to top)
|
|
181
|
+
focusMode = false; // focus mode: hide all sessions except pinned
|
|
182
|
+
bookmarks = []; // saved positions in activity buffer
|
|
164
183
|
bellEnabled = false;
|
|
165
184
|
lastBellAt = 0;
|
|
166
185
|
// drill-down mode: show a single session's full output
|
|
@@ -215,9 +234,9 @@ export class TUI {
|
|
|
215
234
|
isActive() {
|
|
216
235
|
return this.active;
|
|
217
236
|
}
|
|
218
|
-
/** Return the current number of sessions (for mouse hit testing) */
|
|
237
|
+
/** Return the current number of visible sessions (for mouse hit testing) */
|
|
219
238
|
getSessionCount() {
|
|
220
|
-
return this.
|
|
239
|
+
return this.getVisibleCount();
|
|
221
240
|
}
|
|
222
241
|
/** Set the session sort mode and repaint. */
|
|
223
242
|
setSortMode(mode) {
|
|
@@ -240,7 +259,7 @@ export class TUI {
|
|
|
240
259
|
return;
|
|
241
260
|
this.compactMode = enabled;
|
|
242
261
|
if (this.active) {
|
|
243
|
-
this.computeLayout(this.
|
|
262
|
+
this.computeLayout(this.getVisibleCount());
|
|
244
263
|
this.paintAll();
|
|
245
264
|
}
|
|
246
265
|
}
|
|
@@ -285,6 +304,31 @@ export class TUI {
|
|
|
285
304
|
getPinnedCount() {
|
|
286
305
|
return this.pinnedIds.size;
|
|
287
306
|
}
|
|
307
|
+
/** Enable or disable focus mode. When focused, only pinned sessions are visible. */
|
|
308
|
+
setFocus(enabled) {
|
|
309
|
+
if (enabled === this.focusMode)
|
|
310
|
+
return;
|
|
311
|
+
this.focusMode = enabled;
|
|
312
|
+
if (this.active) {
|
|
313
|
+
this.computeLayout(this.getVisibleCount());
|
|
314
|
+
this.paintAll();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/** Return whether focus mode is enabled. */
|
|
318
|
+
isFocused() {
|
|
319
|
+
return this.focusMode;
|
|
320
|
+
}
|
|
321
|
+
/** Return count of visible sessions (all in normal mode, pinned-only in focus mode). */
|
|
322
|
+
getVisibleCount() {
|
|
323
|
+
if (!this.focusMode)
|
|
324
|
+
return this.sessions.length;
|
|
325
|
+
let count = 0;
|
|
326
|
+
for (const s of this.sessions) {
|
|
327
|
+
if (this.pinnedIds.has(s.id))
|
|
328
|
+
count++;
|
|
329
|
+
}
|
|
330
|
+
return count;
|
|
331
|
+
}
|
|
288
332
|
/** Enable or disable terminal bell notifications. */
|
|
289
333
|
setBell(enabled) {
|
|
290
334
|
this.bellEnabled = enabled;
|
|
@@ -293,6 +337,55 @@ export class TUI {
|
|
|
293
337
|
isBellEnabled() {
|
|
294
338
|
return this.bellEnabled;
|
|
295
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Add a bookmark at the current activity position.
|
|
342
|
+
* Returns the bookmark number (1-indexed) or 0 if buffer is empty.
|
|
343
|
+
*/
|
|
344
|
+
addBookmark() {
|
|
345
|
+
if (this.activityBuffer.length === 0)
|
|
346
|
+
return 0;
|
|
347
|
+
// bookmark the entry at the current view position
|
|
348
|
+
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
349
|
+
const { start } = computeScrollSlice(this.activityBuffer.length, visibleLines, this.scrollOffset);
|
|
350
|
+
const entry = this.activityBuffer[start];
|
|
351
|
+
if (!entry)
|
|
352
|
+
return 0;
|
|
353
|
+
const bm = { index: start, label: `${entry.time} ${entry.tag}` };
|
|
354
|
+
this.bookmarks.push(bm);
|
|
355
|
+
if (this.bookmarks.length > MAX_BOOKMARKS) {
|
|
356
|
+
this.bookmarks = this.bookmarks.slice(-MAX_BOOKMARKS);
|
|
357
|
+
}
|
|
358
|
+
return this.bookmarks.length;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Jump to a bookmark by number (1-indexed). Returns false if not found.
|
|
362
|
+
* Adjusts scroll offset to center the bookmarked entry.
|
|
363
|
+
*/
|
|
364
|
+
jumpToBookmark(num) {
|
|
365
|
+
const bm = this.bookmarks[num - 1];
|
|
366
|
+
if (!bm)
|
|
367
|
+
return false;
|
|
368
|
+
// clamp index to current buffer
|
|
369
|
+
if (bm.index >= this.activityBuffer.length)
|
|
370
|
+
return false;
|
|
371
|
+
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
372
|
+
this.scrollOffset = computeBookmarkOffset(bm.index, this.activityBuffer.length, visibleLines);
|
|
373
|
+
if (this.scrollOffset === 0)
|
|
374
|
+
this.newWhileScrolled = 0;
|
|
375
|
+
if (this.active && this.viewMode === "overview") {
|
|
376
|
+
this.repaintActivityRegion();
|
|
377
|
+
this.paintSeparator();
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
/** Return all bookmarks (for /marks listing). */
|
|
382
|
+
getBookmarks() {
|
|
383
|
+
return this.bookmarks;
|
|
384
|
+
}
|
|
385
|
+
/** Return bookmark count. */
|
|
386
|
+
getBookmarkCount() {
|
|
387
|
+
return this.bookmarks.length;
|
|
388
|
+
}
|
|
296
389
|
// ── State updates ───────────────────────────────────────────────────────
|
|
297
390
|
updateState(opts) {
|
|
298
391
|
if (opts.phase !== undefined)
|
|
@@ -319,10 +412,11 @@ export class TUI {
|
|
|
319
412
|
this.prevLastActivity.set(s.id, s.lastActivity);
|
|
320
413
|
}
|
|
321
414
|
const sorted = sortSessions(opts.sessions, this.sortMode, this.lastChangeAt, this.pinnedIds);
|
|
322
|
-
const
|
|
415
|
+
const prevVisibleCount = this.getVisibleCount();
|
|
323
416
|
this.sessions = sorted;
|
|
324
|
-
|
|
325
|
-
|
|
417
|
+
const newVisibleCount = this.getVisibleCount();
|
|
418
|
+
if (newVisibleCount !== prevVisibleCount) {
|
|
419
|
+
this.computeLayout(newVisibleCount);
|
|
326
420
|
this.paintAll();
|
|
327
421
|
return;
|
|
328
422
|
}
|
|
@@ -516,7 +610,7 @@ export class TUI {
|
|
|
516
610
|
this.drilldownNewWhileScrolled = 0;
|
|
517
611
|
this.hoverSessionIdx = null;
|
|
518
612
|
if (this.active) {
|
|
519
|
-
this.computeLayout(this.
|
|
613
|
+
this.computeLayout(this.getVisibleCount());
|
|
520
614
|
this.paintAll();
|
|
521
615
|
}
|
|
522
616
|
return true;
|
|
@@ -531,7 +625,7 @@ export class TUI {
|
|
|
531
625
|
this.drilldownNewWhileScrolled = 0;
|
|
532
626
|
this.hoverSessionIdx = null;
|
|
533
627
|
if (this.active) {
|
|
534
|
-
this.computeLayout(this.
|
|
628
|
+
this.computeLayout(this.getVisibleCount());
|
|
535
629
|
this.paintAll();
|
|
536
630
|
}
|
|
537
631
|
}
|
|
@@ -594,8 +688,9 @@ export class TUI {
|
|
|
594
688
|
}
|
|
595
689
|
else {
|
|
596
690
|
// overview: header (1) + sessions box + separator + activity + input
|
|
691
|
+
const visibleSessions = this.sessions.slice(0, this.getVisibleCount());
|
|
597
692
|
const sessBodyRows = this.compactMode
|
|
598
|
-
? computeCompactRowCount(
|
|
693
|
+
? computeCompactRowCount(visibleSessions, this.cols - 2)
|
|
599
694
|
: Math.max(sessionCount, 1);
|
|
600
695
|
this.sessionRows = sessBodyRows + 2; // + top/bottom borders
|
|
601
696
|
this.separatorRow = this.headerHeight + this.sessionRows + 1;
|
|
@@ -608,7 +703,7 @@ export class TUI {
|
|
|
608
703
|
}
|
|
609
704
|
}
|
|
610
705
|
onResize() {
|
|
611
|
-
this.computeLayout(this.
|
|
706
|
+
this.computeLayout(this.getVisibleCount());
|
|
612
707
|
this.paintAll();
|
|
613
708
|
}
|
|
614
709
|
// ── Painting ────────────────────────────────────────────────────────────
|
|
@@ -636,7 +731,10 @@ export class TUI {
|
|
|
636
731
|
}
|
|
637
732
|
else {
|
|
638
733
|
const phaseText = phaseDisplay(this.phase, this.paused, this.spinnerFrame);
|
|
639
|
-
const
|
|
734
|
+
const visCount = this.getVisibleCount();
|
|
735
|
+
const sessCount = this.focusMode
|
|
736
|
+
? `${visCount}/${this.sessions.length} agent${this.sessions.length !== 1 ? "s" : ""}`
|
|
737
|
+
: `${this.sessions.length} agent${this.sessions.length !== 1 ? "s" : ""}`;
|
|
640
738
|
const activeCount = this.sessions.filter((s) => s.userActive).length;
|
|
641
739
|
const activeTag = activeCount > 0 ? ` ${SLATE}│${RESET} ${AMBER}${activeCount} user${RESET}` : "";
|
|
642
740
|
// countdown to next tick (only in sleeping phase)
|
|
@@ -656,23 +754,29 @@ export class TUI {
|
|
|
656
754
|
paintSessions() {
|
|
657
755
|
const startRow = this.headerHeight + 1;
|
|
658
756
|
const innerWidth = this.cols - 2; // inside the box borders
|
|
659
|
-
|
|
757
|
+
const visibleCount = this.getVisibleCount();
|
|
758
|
+
const visibleSessions = this.sessions.slice(0, visibleCount); // pinned sort to top
|
|
759
|
+
// top border with label (includes focus/compact/sort mode tags)
|
|
760
|
+
const focusTag = this.focusMode ? "focus" : "";
|
|
660
761
|
const sortTag = this.sortMode !== "default" ? this.sortMode : "";
|
|
661
762
|
const compactTag = this.compactMode ? "compact" : "";
|
|
662
|
-
const tags = [compactTag, sortTag].filter(Boolean).join(", ");
|
|
763
|
+
const tags = [focusTag, compactTag, sortTag].filter(Boolean).join(", ");
|
|
663
764
|
const label = tags ? ` agents (${tags}) ` : " agents ";
|
|
664
765
|
const borderAfterLabel = Math.max(0, innerWidth - label.length);
|
|
665
766
|
const topBorder = `${SLATE}${BOX.rtl}${BOX.h}${RESET}${SLATE}${label}${RESET}${SLATE}${BOX.h.repeat(borderAfterLabel)}${BOX.rtr}${RESET}`;
|
|
666
767
|
process.stderr.write(SAVE_CURSOR + moveTo(startRow, 1) + CLEAR_LINE + truncateAnsi(topBorder, this.cols));
|
|
667
|
-
if (
|
|
768
|
+
if (visibleSessions.length === 0) {
|
|
668
769
|
// empty state
|
|
669
|
-
const
|
|
770
|
+
const msg = this.focusMode && this.sessions.length > 0
|
|
771
|
+
? `${DIM}no pinned agents — /pin to add, /focus to exit${RESET}`
|
|
772
|
+
: `${DIM}no agents connected${RESET}`;
|
|
773
|
+
const empty = `${SLATE}${BOX.v}${RESET} ${msg}`;
|
|
670
774
|
const padded = padBoxLine(empty, this.cols);
|
|
671
775
|
process.stderr.write(moveTo(startRow + 1, 1) + CLEAR_LINE + padded);
|
|
672
776
|
}
|
|
673
777
|
else if (this.compactMode) {
|
|
674
778
|
// compact: inline tokens, multiple per row (with pin indicators)
|
|
675
|
-
const compactRows = formatCompactRows(
|
|
779
|
+
const compactRows = formatCompactRows(visibleSessions, innerWidth - 1, this.pinnedIds);
|
|
676
780
|
for (let r = 0; r < compactRows.length; r++) {
|
|
677
781
|
const line = `${SLATE}${BOX.v}${RESET} ${compactRows[r]}`;
|
|
678
782
|
const padded = padBoxLine(line, this.cols);
|
|
@@ -680,8 +784,8 @@ export class TUI {
|
|
|
680
784
|
}
|
|
681
785
|
}
|
|
682
786
|
else {
|
|
683
|
-
for (let i = 0; i <
|
|
684
|
-
const s =
|
|
787
|
+
for (let i = 0; i < visibleSessions.length; i++) {
|
|
788
|
+
const s = visibleSessions[i];
|
|
685
789
|
const isHovered = this.hoverSessionIdx === i + 1; // 1-indexed
|
|
686
790
|
const bg = isHovered ? BG_HOVER : "";
|
|
687
791
|
const pinned = this.pinnedIds.has(s.id);
|
|
@@ -694,8 +798,8 @@ export class TUI {
|
|
|
694
798
|
}
|
|
695
799
|
// bottom border
|
|
696
800
|
const bodyRows = this.compactMode
|
|
697
|
-
? computeCompactRowCount(
|
|
698
|
-
: Math.max(
|
|
801
|
+
? computeCompactRowCount(visibleSessions, innerWidth)
|
|
802
|
+
: Math.max(visibleCount, 1);
|
|
699
803
|
const bottomRow = startRow + 1 + bodyRows;
|
|
700
804
|
const bottomBorder = `${SLATE}${BOX.rbl}${BOX.h.repeat(Math.max(0, this.cols - 2))}${BOX.rbr}${RESET}`;
|
|
701
805
|
process.stderr.write(moveTo(bottomRow, 1) + CLEAR_LINE + truncateAnsi(bottomBorder, this.cols));
|