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 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
- switch (dir) {
260
- case "up":
261
- tui.scrollUp();
262
- break;
263
- case "down":
264
- tui.scrollDown();
265
- break;
266
- case "top":
267
- tui.scrollToTop();
268
- break;
269
- case "bottom":
270
- tui.scrollToBottom();
271
- break;
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 && evt.press && evt.button === 0 && this.mouseClickHandler) {
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
- PgUp / PgDn scroll through activity history
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.set(id, text.split("\n"));
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
- const hints = " /back: overview /view N: switch session ";
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
- // show the last N lines (tail view, like following output)
445
- const startIdx = Math.max(0, outputLines.length - visibleLines);
446
- const visible = outputLines.slice(startIdx);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.72.0",
3
+ "version": "0.73.0",
4
4
  "description": "Autonomous supervisor for agent-of-empires sessions using OpenCode or Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",