aoaoe 0.72.0 → 0.73.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 +45 -13
- package/dist/input.d.ts +3 -0
- package/dist/input.js +18 -2
- package/dist/tui.d.ts +8 -1
- package/dist/tui.js +69 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -256,19 +256,36 @@ async function main() {
|
|
|
256
256
|
// wire scroll keys to TUI (PgUp/PgDn/Home/End)
|
|
257
257
|
if (tui) {
|
|
258
258
|
input.onScroll((dir) => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
259
|
+
if (tui.getViewMode() === "drilldown") {
|
|
260
|
+
// PgUp/PgDn/Home/End scroll the session output in drill-down mode
|
|
261
|
+
switch (dir) {
|
|
262
|
+
case "up":
|
|
263
|
+
tui.scrollDrilldownUp();
|
|
264
|
+
break;
|
|
265
|
+
case "down":
|
|
266
|
+
tui.scrollDrilldownDown();
|
|
267
|
+
break;
|
|
268
|
+
case "bottom":
|
|
269
|
+
tui.scrollDrilldownToBottom();
|
|
270
|
+
break;
|
|
271
|
+
// "top" not wired for drilldown — could add scrollDrilldownToTop() later
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
switch (dir) {
|
|
276
|
+
case "up":
|
|
277
|
+
tui.scrollUp();
|
|
278
|
+
break;
|
|
279
|
+
case "down":
|
|
280
|
+
tui.scrollDown();
|
|
281
|
+
break;
|
|
282
|
+
case "top":
|
|
283
|
+
tui.scrollToTop();
|
|
284
|
+
break;
|
|
285
|
+
case "bottom":
|
|
286
|
+
tui.scrollToBottom();
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
272
289
|
}
|
|
273
290
|
});
|
|
274
291
|
// wire queue count changes to TUI prompt display
|
|
@@ -308,6 +325,21 @@ async function main() {
|
|
|
308
325
|
tui.log("system", `viewing session #${sessionIdx}`);
|
|
309
326
|
}
|
|
310
327
|
});
|
|
328
|
+
// wire mouse wheel to scroll (3 lines per tick for smooth scrolling)
|
|
329
|
+
input.onMouseWheel((direction) => {
|
|
330
|
+
if (tui.getViewMode() === "drilldown") {
|
|
331
|
+
if (direction === "up")
|
|
332
|
+
tui.scrollDrilldownUp(3);
|
|
333
|
+
else
|
|
334
|
+
tui.scrollDrilldownDown(3);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
if (direction === "up")
|
|
338
|
+
tui.scrollUp(3);
|
|
339
|
+
else
|
|
340
|
+
tui.scrollDown(3);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
311
343
|
}
|
|
312
344
|
// start TUI (alternate screen buffer) after input is ready
|
|
313
345
|
if (tui) {
|
package/dist/input.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface MouseEvent {
|
|
|
8
8
|
press: boolean;
|
|
9
9
|
}
|
|
10
10
|
export type MouseClickHandler = (row: number, col: number) => void;
|
|
11
|
+
export type MouseWheelHandler = (direction: "up" | "down") => void;
|
|
11
12
|
/** Parse an SGR extended mouse event from raw terminal data. Returns null if not a mouse event. */
|
|
12
13
|
export declare function parseMouseEvent(data: string): MouseEvent | null;
|
|
13
14
|
export declare class InputReader {
|
|
@@ -19,11 +20,13 @@ export declare class InputReader {
|
|
|
19
20
|
private queueChangeHandler;
|
|
20
21
|
private viewHandler;
|
|
21
22
|
private mouseClickHandler;
|
|
23
|
+
private mouseWheelHandler;
|
|
22
24
|
private mouseDataListener;
|
|
23
25
|
onScroll(handler: (dir: ScrollDirection) => void): void;
|
|
24
26
|
onQueueChange(handler: (count: number) => void): void;
|
|
25
27
|
onView(handler: ViewHandler): void;
|
|
26
28
|
onMouseClick(handler: MouseClickHandler): void;
|
|
29
|
+
onMouseWheel(handler: MouseWheelHandler): void;
|
|
27
30
|
private notifyQueueChange;
|
|
28
31
|
start(): void;
|
|
29
32
|
drain(): string[];
|
package/dist/input.js
CHANGED
|
@@ -30,6 +30,7 @@ export class InputReader {
|
|
|
30
30
|
queueChangeHandler = null;
|
|
31
31
|
viewHandler = null;
|
|
32
32
|
mouseClickHandler = null;
|
|
33
|
+
mouseWheelHandler = null;
|
|
33
34
|
mouseDataListener = null;
|
|
34
35
|
// register a callback for scroll key events (PgUp/PgDn/Home/End)
|
|
35
36
|
onScroll(handler) {
|
|
@@ -47,6 +48,10 @@ export class InputReader {
|
|
|
47
48
|
onMouseClick(handler) {
|
|
48
49
|
this.mouseClickHandler = handler;
|
|
49
50
|
}
|
|
51
|
+
// register a callback for mouse wheel events (scroll up/down)
|
|
52
|
+
onMouseWheel(handler) {
|
|
53
|
+
this.mouseWheelHandler = handler;
|
|
54
|
+
}
|
|
50
55
|
notifyQueueChange() {
|
|
51
56
|
this.queueChangeHandler?.(this.queue.length);
|
|
52
57
|
}
|
|
@@ -68,9 +73,19 @@ export class InputReader {
|
|
|
68
73
|
this.mouseDataListener = (data) => {
|
|
69
74
|
const str = data.toString("utf8");
|
|
70
75
|
const evt = parseMouseEvent(str);
|
|
71
|
-
if (evt
|
|
76
|
+
if (!evt)
|
|
77
|
+
return;
|
|
78
|
+
// left click press
|
|
79
|
+
if (evt.press && evt.button === 0 && this.mouseClickHandler) {
|
|
72
80
|
this.mouseClickHandler(evt.row, evt.col);
|
|
73
81
|
}
|
|
82
|
+
// mouse wheel: button 64 = scroll up, 65 = scroll down
|
|
83
|
+
if (evt.button === 64 && this.mouseWheelHandler) {
|
|
84
|
+
this.mouseWheelHandler("up");
|
|
85
|
+
}
|
|
86
|
+
else if (evt.button === 65 && this.mouseWheelHandler) {
|
|
87
|
+
this.mouseWheelHandler("down");
|
|
88
|
+
}
|
|
74
89
|
};
|
|
75
90
|
process.stdin.on("data", this.mouseDataListener);
|
|
76
91
|
process.stdin.on("keypress", (_ch, key) => {
|
|
@@ -201,7 +216,8 @@ ${BOLD}navigation:${RESET}
|
|
|
201
216
|
/view [N|name] drill into a session's live output (default: 1)
|
|
202
217
|
/back return to overview from drill-down
|
|
203
218
|
click session click an agent card to drill down (click again to go back)
|
|
204
|
-
|
|
219
|
+
mouse wheel scroll activity (overview) or session output (drill-down)
|
|
220
|
+
PgUp / PgDn scroll through activity or session output
|
|
205
221
|
Home / End jump to oldest / return to live
|
|
206
222
|
|
|
207
223
|
${BOLD}info:${RESET}
|
package/dist/tui.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export declare class TUI {
|
|
|
26
26
|
private viewMode;
|
|
27
27
|
private drilldownSessionId;
|
|
28
28
|
private sessionOutputs;
|
|
29
|
+
private drilldownScrollOffset;
|
|
30
|
+
private drilldownNewWhileScrolled;
|
|
29
31
|
private phase;
|
|
30
32
|
private pollCount;
|
|
31
33
|
private sessions;
|
|
@@ -54,6 +56,10 @@ export declare class TUI {
|
|
|
54
56
|
scrollToTop(): void;
|
|
55
57
|
scrollToBottom(): void;
|
|
56
58
|
isScrolledBack(): boolean;
|
|
59
|
+
scrollDrilldownUp(lines?: number): void;
|
|
60
|
+
scrollDrilldownDown(lines?: number): void;
|
|
61
|
+
scrollDrilldownToBottom(): void;
|
|
62
|
+
isDrilldownScrolledBack(): boolean;
|
|
57
63
|
/** Store full session outputs (called each tick from main loop) */
|
|
58
64
|
setSessionOutputs(outputs: Map<string, string>): void;
|
|
59
65
|
/** Enter drill-down view for a session. Returns false if session not found. */
|
|
@@ -96,6 +102,7 @@ declare function computeScrollSlice(bufferLen: number, visibleLines: number, scr
|
|
|
96
102
|
end: number;
|
|
97
103
|
};
|
|
98
104
|
declare function formatScrollIndicator(offset: number, totalEntries: number, visibleLines: number, newCount: number): string;
|
|
105
|
+
declare function formatDrilldownScrollIndicator(offset: number, totalLines: number, visibleLines: number, newCount: number): string;
|
|
99
106
|
/**
|
|
100
107
|
* Hit-test a mouse click row against the session panel.
|
|
101
108
|
* Returns 1-indexed session number if the click hit a session card, null otherwise.
|
|
@@ -104,5 +111,5 @@ declare function formatScrollIndicator(offset: number, totalEntries: number, vis
|
|
|
104
111
|
* (row = headerHeight + 2 + i for 0-indexed session i)
|
|
105
112
|
*/
|
|
106
113
|
export declare function hitTestSession(row: number, headerHeight: number, sessionCount: number): number | null;
|
|
107
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatPrompt, formatDrilldownHeader };
|
|
114
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader };
|
|
108
115
|
//# sourceMappingURL=tui.d.ts.map
|
package/dist/tui.js
CHANGED
|
@@ -65,6 +65,8 @@ export class TUI {
|
|
|
65
65
|
viewMode = "overview";
|
|
66
66
|
drilldownSessionId = null;
|
|
67
67
|
sessionOutputs = new Map(); // full output lines per session
|
|
68
|
+
drilldownScrollOffset = 0; // 0 = live (tail), >0 = scrolled back N lines
|
|
69
|
+
drilldownNewWhileScrolled = 0; // lines added while scrolled back
|
|
68
70
|
// current state for repaints
|
|
69
71
|
phase = "sleeping";
|
|
70
72
|
pollCount = 0;
|
|
@@ -221,15 +223,58 @@ export class TUI {
|
|
|
221
223
|
isScrolledBack() {
|
|
222
224
|
return this.scrollOffset > 0;
|
|
223
225
|
}
|
|
226
|
+
// ── Drill-down scroll ─────────────────────────────────────────────────
|
|
227
|
+
scrollDrilldownUp(lines) {
|
|
228
|
+
if (!this.active || this.viewMode !== "drilldown" || !this.drilldownSessionId)
|
|
229
|
+
return;
|
|
230
|
+
const outputLines = this.sessionOutputs.get(this.drilldownSessionId) ?? [];
|
|
231
|
+
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
232
|
+
const n = lines ?? Math.max(1, Math.floor(visibleLines / 2));
|
|
233
|
+
const maxOffset = Math.max(0, outputLines.length - visibleLines);
|
|
234
|
+
this.drilldownScrollOffset = Math.min(maxOffset, this.drilldownScrollOffset + n);
|
|
235
|
+
this.repaintDrilldownContent();
|
|
236
|
+
this.paintDrilldownSeparator();
|
|
237
|
+
}
|
|
238
|
+
scrollDrilldownDown(lines) {
|
|
239
|
+
if (!this.active || this.viewMode !== "drilldown")
|
|
240
|
+
return;
|
|
241
|
+
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
242
|
+
const n = lines ?? Math.max(1, Math.floor(visibleLines / 2));
|
|
243
|
+
const wasScrolled = this.drilldownScrollOffset > 0;
|
|
244
|
+
this.drilldownScrollOffset = Math.max(0, this.drilldownScrollOffset - n);
|
|
245
|
+
if (wasScrolled && this.drilldownScrollOffset === 0)
|
|
246
|
+
this.drilldownNewWhileScrolled = 0;
|
|
247
|
+
this.repaintDrilldownContent();
|
|
248
|
+
this.paintDrilldownSeparator();
|
|
249
|
+
}
|
|
250
|
+
scrollDrilldownToBottom() {
|
|
251
|
+
if (!this.active || this.viewMode !== "drilldown")
|
|
252
|
+
return;
|
|
253
|
+
this.drilldownScrollOffset = 0;
|
|
254
|
+
this.drilldownNewWhileScrolled = 0;
|
|
255
|
+
this.repaintDrilldownContent();
|
|
256
|
+
this.paintDrilldownSeparator();
|
|
257
|
+
}
|
|
258
|
+
isDrilldownScrolledBack() {
|
|
259
|
+
return this.drilldownScrollOffset > 0;
|
|
260
|
+
}
|
|
224
261
|
// ── Drill-down mode ────────────────────────────────────────────────────
|
|
225
262
|
/** Store full session outputs (called each tick from main loop) */
|
|
226
263
|
setSessionOutputs(outputs) {
|
|
227
264
|
for (const [id, text] of outputs) {
|
|
228
|
-
this.sessionOutputs.
|
|
265
|
+
const prevLen = this.sessionOutputs.get(id)?.length ?? 0;
|
|
266
|
+
const lines = text.split("\n");
|
|
267
|
+
this.sessionOutputs.set(id, lines);
|
|
268
|
+
// track new lines while scrolled back in drill-down
|
|
269
|
+
if (this.viewMode === "drilldown" && this.drilldownSessionId === id && this.drilldownScrollOffset > 0) {
|
|
270
|
+
const newLines = Math.max(0, lines.length - prevLen);
|
|
271
|
+
this.drilldownNewWhileScrolled += newLines;
|
|
272
|
+
}
|
|
229
273
|
}
|
|
230
274
|
// repaint drill-down view if we're watching this session
|
|
231
275
|
if (this.active && this.viewMode === "drilldown" && this.drilldownSessionId) {
|
|
232
276
|
this.repaintDrilldownContent();
|
|
277
|
+
this.paintDrilldownSeparator();
|
|
233
278
|
}
|
|
234
279
|
}
|
|
235
280
|
/** Enter drill-down view for a session. Returns false if session not found. */
|
|
@@ -251,6 +296,8 @@ export class TUI {
|
|
|
251
296
|
return false;
|
|
252
297
|
this.viewMode = "drilldown";
|
|
253
298
|
this.drilldownSessionId = sessionId;
|
|
299
|
+
this.drilldownScrollOffset = 0;
|
|
300
|
+
this.drilldownNewWhileScrolled = 0;
|
|
254
301
|
if (this.active) {
|
|
255
302
|
this.computeLayout(this.sessions.length);
|
|
256
303
|
this.paintAll();
|
|
@@ -263,6 +310,8 @@ export class TUI {
|
|
|
263
310
|
return;
|
|
264
311
|
this.viewMode = "overview";
|
|
265
312
|
this.drilldownSessionId = null;
|
|
313
|
+
this.drilldownScrollOffset = 0;
|
|
314
|
+
this.drilldownNewWhileScrolled = 0;
|
|
266
315
|
if (this.active) {
|
|
267
316
|
this.computeLayout(this.sessions.length);
|
|
268
317
|
this.paintAll();
|
|
@@ -428,7 +477,15 @@ export class TUI {
|
|
|
428
477
|
const session = this.sessions.find((s) => s.id === this.drilldownSessionId);
|
|
429
478
|
const title = session ? session.title : this.drilldownSessionId ?? "?";
|
|
430
479
|
const prefix = `${BOX.h}${BOX.h} ${title} `;
|
|
431
|
-
|
|
480
|
+
let hints;
|
|
481
|
+
if (this.drilldownScrollOffset > 0) {
|
|
482
|
+
const outputLines = this.sessionOutputs.get(this.drilldownSessionId ?? "") ?? [];
|
|
483
|
+
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
484
|
+
hints = formatDrilldownScrollIndicator(this.drilldownScrollOffset, outputLines.length, visibleLines, this.drilldownNewWhileScrolled);
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
hints = " click or /back: overview scroll: navigate /view N: switch ";
|
|
488
|
+
}
|
|
432
489
|
const totalLen = prefix.length + hints.length;
|
|
433
490
|
const fill = Math.max(0, this.cols - totalLen);
|
|
434
491
|
const left = Math.floor(fill / 2);
|
|
@@ -441,9 +498,9 @@ export class TUI {
|
|
|
441
498
|
return;
|
|
442
499
|
const outputLines = this.sessionOutputs.get(this.drilldownSessionId) ?? [];
|
|
443
500
|
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
444
|
-
//
|
|
445
|
-
const
|
|
446
|
-
const visible = outputLines.slice(
|
|
501
|
+
// use scroll offset: 0 = tail (live), >0 = scrolled back
|
|
502
|
+
const { start, end } = computeScrollSlice(outputLines.length, visibleLines, this.drilldownScrollOffset);
|
|
503
|
+
const visible = outputLines.slice(start, end);
|
|
447
504
|
for (let i = 0; i < visibleLines; i++) {
|
|
448
505
|
const row = this.scrollTop + i;
|
|
449
506
|
if (i < visible.length) {
|
|
@@ -646,6 +703,12 @@ function formatScrollIndicator(offset, totalEntries, visibleLines, newCount) {
|
|
|
646
703
|
const newTag = newCount > 0 ? ` ${newCount} new ↓` : "";
|
|
647
704
|
return ` ↑ ${offset} older │ ${position}/${totalEntries} │ PgUp/PgDn End=live${newTag} `;
|
|
648
705
|
}
|
|
706
|
+
// format the scroll indicator for drill-down separator bar
|
|
707
|
+
function formatDrilldownScrollIndicator(offset, totalLines, visibleLines, newCount) {
|
|
708
|
+
const position = totalLines - offset;
|
|
709
|
+
const newTag = newCount > 0 ? ` ${newCount} new ↓` : "";
|
|
710
|
+
return ` ↑ ${offset} lines │ ${position}/${totalLines} │ scroll: navigate End=live${newTag} `;
|
|
711
|
+
}
|
|
649
712
|
// ── Mouse hit testing (pure, exported for testing) ──────────────────────────
|
|
650
713
|
/**
|
|
651
714
|
* Hit-test a mouse click row against the session panel.
|
|
@@ -664,5 +727,5 @@ export function hitTestSession(row, headerHeight, sessionCount) {
|
|
|
664
727
|
return row - firstSessionRow + 1; // 1-indexed
|
|
665
728
|
}
|
|
666
729
|
// ── Exported pure helpers (for testing) ─────────────────────────────────────
|
|
667
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatPrompt, formatDrilldownHeader };
|
|
730
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader };
|
|
668
731
|
//# sourceMappingURL=tui.js.map
|