aoaoe 0.71.0 → 0.72.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
@@ -15,7 +15,7 @@ import { wakeableSleep } from "./wake.js";
15
15
  import { classifyMessages, formatUserMessages, buildReceipts, shouldSkipSleep, hasPendingFile, isInsistMessage, stripInsistPrefix } from "./message.js";
16
16
  import { TaskManager, loadTaskDefinitions, loadTaskState, formatTaskTable } from "./task-manager.js";
17
17
  import { runTaskCli, handleTaskSlashCommand } from "./task-cli.js";
18
- import { TUI } from "./tui.js";
18
+ import { TUI, hitTestSession } from "./tui.js";
19
19
  import { isDaemonRunningFromState } from "./chat.js";
20
20
  import { sendNotification, sendTestNotification } from "./notify.js";
21
21
  import { startHealthServer } from "./health.js";
@@ -293,6 +293,21 @@ async function main() {
293
293
  }
294
294
  }
295
295
  });
296
+ // wire mouse clicks on session cards to drill-down
297
+ input.onMouseClick((row, _col) => {
298
+ if (tui.getViewMode() === "drilldown") {
299
+ // click anywhere in drilldown = back to overview
300
+ tui.exitDrilldown();
301
+ tui.log("system", "returned to overview");
302
+ return;
303
+ }
304
+ const sessionIdx = hitTestSession(row, 1, tui.getSessionCount());
305
+ if (sessionIdx !== null) {
306
+ const ok = tui.enterDrilldown(sessionIdx);
307
+ if (ok)
308
+ tui.log("system", `viewing session #${sessionIdx}`);
309
+ }
310
+ });
296
311
  }
297
312
  // start TUI (alternate screen buffer) after input is ready
298
313
  if (tui) {
package/dist/input.d.ts CHANGED
@@ -1,6 +1,15 @@
1
1
  export type ScrollDirection = "up" | "down" | "top" | "bottom";
2
2
  export declare const INSIST_PREFIX = "__INSIST__";
3
3
  export type ViewHandler = (target: string | null) => void;
4
+ export interface MouseEvent {
5
+ button: number;
6
+ col: number;
7
+ row: number;
8
+ press: boolean;
9
+ }
10
+ export type MouseClickHandler = (row: number, col: number) => void;
11
+ /** Parse an SGR extended mouse event from raw terminal data. Returns null if not a mouse event. */
12
+ export declare function parseMouseEvent(data: string): MouseEvent | null;
4
13
  export declare class InputReader {
5
14
  private rl;
6
15
  private queue;
@@ -9,9 +18,12 @@ export declare class InputReader {
9
18
  private scrollHandler;
10
19
  private queueChangeHandler;
11
20
  private viewHandler;
21
+ private mouseClickHandler;
22
+ private mouseDataListener;
12
23
  onScroll(handler: (dir: ScrollDirection) => void): void;
13
24
  onQueueChange(handler: (count: number) => void): void;
14
25
  onView(handler: ViewHandler): void;
26
+ onMouseClick(handler: MouseClickHandler): void;
15
27
  private notifyQueueChange;
16
28
  start(): void;
17
29
  drain(): string[];
package/dist/input.js CHANGED
@@ -7,6 +7,20 @@ import { GREEN, DIM, YELLOW, RED, BOLD, RESET } from "./colors.js";
7
7
  // ESC-ESC interrupt detection
8
8
  const ESC_DOUBLE_TAP_MS = 500;
9
9
  export const INSIST_PREFIX = "__INSIST__";
10
+ // SGR extended mouse format: \x1b[<btn;col;rowM (press) or \x1b[<btn;col;rowm (release)
11
+ const SGR_MOUSE_RE = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/;
12
+ /** Parse an SGR extended mouse event from raw terminal data. Returns null if not a mouse event. */
13
+ export function parseMouseEvent(data) {
14
+ const m = SGR_MOUSE_RE.exec(data);
15
+ if (!m)
16
+ return null;
17
+ return {
18
+ button: parseInt(m[1], 10),
19
+ col: parseInt(m[2], 10),
20
+ row: parseInt(m[3], 10),
21
+ press: m[4] === "M",
22
+ };
23
+ }
10
24
  export class InputReader {
11
25
  rl = null;
12
26
  queue = []; // pending user messages for the reasoner
@@ -15,6 +29,8 @@ export class InputReader {
15
29
  scrollHandler = null;
16
30
  queueChangeHandler = null;
17
31
  viewHandler = null;
32
+ mouseClickHandler = null;
33
+ mouseDataListener = null;
18
34
  // register a callback for scroll key events (PgUp/PgDn/Home/End)
19
35
  onScroll(handler) {
20
36
  this.scrollHandler = handler;
@@ -27,6 +43,10 @@ export class InputReader {
27
43
  onView(handler) {
28
44
  this.viewHandler = handler;
29
45
  }
46
+ // register a callback for mouse left-click events (row, col are 1-indexed)
47
+ onMouseClick(handler) {
48
+ this.mouseClickHandler = handler;
49
+ }
30
50
  notifyQueueChange() {
31
51
  this.queueChangeHandler?.(this.queue.length);
32
52
  }
@@ -44,6 +64,15 @@ export class InputReader {
44
64
  this.rl.on("close", () => { this.rl = null; });
45
65
  // ESC-ESC interrupt detection (same as chat.ts)
46
66
  emitKeypressEvents(process.stdin);
67
+ // intercept raw SGR mouse sequences before keypress parsing
68
+ this.mouseDataListener = (data) => {
69
+ const str = data.toString("utf8");
70
+ const evt = parseMouseEvent(str);
71
+ if (evt && evt.press && evt.button === 0 && this.mouseClickHandler) {
72
+ this.mouseClickHandler(evt.row, evt.col);
73
+ }
74
+ };
75
+ process.stdin.on("data", this.mouseDataListener);
47
76
  process.stdin.on("keypress", (_ch, key) => {
48
77
  if (key?.name === "escape" || key?.sequence === "\x1b") {
49
78
  const now = Date.now();
@@ -102,6 +131,10 @@ export class InputReader {
102
131
  this.rl?.prompt(true);
103
132
  }
104
133
  stop() {
134
+ if (this.mouseDataListener) {
135
+ process.stdin.removeListener("data", this.mouseDataListener);
136
+ this.mouseDataListener = null;
137
+ }
105
138
  this.rl?.close();
106
139
  this.rl = null;
107
140
  }
@@ -167,6 +200,7 @@ ${BOLD}controls:${RESET}
167
200
  ${BOLD}navigation:${RESET}
168
201
  /view [N|name] drill into a session's live output (default: 1)
169
202
  /back return to overview from drill-down
203
+ click session click an agent card to drill down (click again to go back)
170
204
  PgUp / PgDn scroll through activity history
171
205
  Home / End jump to oldest / return to live
172
206
 
package/dist/tui.d.ts CHANGED
@@ -36,6 +36,8 @@ export declare class TUI {
36
36
  start(version: string): void;
37
37
  stop(): void;
38
38
  isActive(): boolean;
39
+ /** Return the current number of sessions (for mouse hit testing) */
40
+ getSessionCount(): number;
39
41
  updateState(opts: {
40
42
  phase?: DaemonPhase;
41
43
  pollCount?: number;
@@ -94,5 +96,13 @@ declare function computeScrollSlice(bufferLen: number, visibleLines: number, scr
94
96
  end: number;
95
97
  };
96
98
  declare function formatScrollIndicator(offset: number, totalEntries: number, visibleLines: number, newCount: number): string;
99
+ /**
100
+ * Hit-test a mouse click row against the session panel.
101
+ * Returns 1-indexed session number if the click hit a session card, null otherwise.
102
+ *
103
+ * Session cards occupy rows: headerHeight + 2 through headerHeight + 1 + sessionCount
104
+ * (row = headerHeight + 2 + i for 0-indexed session i)
105
+ */
106
+ export declare function hitTestSession(row: number, headerHeight: number, sessionCount: number): number | null;
97
107
  export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatPrompt, formatDrilldownHeader };
98
108
  //# sourceMappingURL=tui.d.ts.map
package/dist/tui.js CHANGED
@@ -12,6 +12,9 @@ const CURSOR_HIDE = `${CSI}?25l`;
12
12
  const CURSOR_SHOW = `${CSI}?25h`;
13
13
  const SAVE_CURSOR = `${ESC}7`;
14
14
  const RESTORE_CURSOR = `${ESC}8`;
15
+ // mouse tracking (SGR extended mode — button events + extended coordinates)
16
+ const MOUSE_ON = `${CSI}?1000h${CSI}?1006h`;
17
+ const MOUSE_OFF = `${CSI}?1000l${CSI}?1006l`;
15
18
  // cursor movement
16
19
  const moveTo = (row, col) => `${CSI}${row};${col}H`;
17
20
  const setScrollRegion = (top, bottom) => `${CSI}${top};${bottom}r`;
@@ -76,8 +79,8 @@ export class TUI {
76
79
  this.active = true;
77
80
  this.version = version;
78
81
  this.updateDimensions();
79
- // enter alternate screen, hide cursor, clear
80
- process.stderr.write(ALT_SCREEN_ON + CURSOR_HIDE + CLEAR_SCREEN);
82
+ // enter alternate screen, hide cursor, clear, enable mouse
83
+ process.stderr.write(ALT_SCREEN_ON + CURSOR_HIDE + CLEAR_SCREEN + MOUSE_ON);
81
84
  // handle terminal resize
82
85
  process.stdout.on("resize", () => this.onResize());
83
86
  // tick timer: countdown + spinner animation (~4 fps for smooth braille spin)
@@ -102,12 +105,16 @@ export class TUI {
102
105
  clearInterval(this.countdownTimer);
103
106
  this.countdownTimer = null;
104
107
  }
105
- // restore normal screen, show cursor, reset scroll region
106
- process.stderr.write(resetScrollRegion() + CURSOR_SHOW + ALT_SCREEN_OFF);
108
+ // disable mouse, restore normal screen, show cursor, reset scroll region
109
+ process.stderr.write(MOUSE_OFF + resetScrollRegion() + CURSOR_SHOW + ALT_SCREEN_OFF);
107
110
  }
108
111
  isActive() {
109
112
  return this.active;
110
113
  }
114
+ /** Return the current number of sessions (for mouse hit testing) */
115
+ getSessionCount() {
116
+ return this.sessions.length;
117
+ }
111
118
  // ── State updates ───────────────────────────────────────────────────────
112
119
  updateState(opts) {
113
120
  if (opts.phase !== undefined)
@@ -383,7 +390,7 @@ export class TUI {
383
390
  hints = formatScrollIndicator(this.scrollOffset, this.activityBuffer.length, this.scrollBottom - this.scrollTop + 1, this.newWhileScrolled);
384
391
  }
385
392
  else {
386
- hints = " esc esc: interrupt /help /explain /pause ";
393
+ hints = " click agent to view esc esc: interrupt /help ";
387
394
  }
388
395
  const totalLen = prefix.length + hints.length;
389
396
  const fill = Math.max(0, this.cols - totalLen);
@@ -639,6 +646,23 @@ function formatScrollIndicator(offset, totalEntries, visibleLines, newCount) {
639
646
  const newTag = newCount > 0 ? ` ${newCount} new ↓` : "";
640
647
  return ` ↑ ${offset} older │ ${position}/${totalEntries} │ PgUp/PgDn End=live${newTag} `;
641
648
  }
649
+ // ── Mouse hit testing (pure, exported for testing) ──────────────────────────
650
+ /**
651
+ * Hit-test a mouse click row against the session panel.
652
+ * Returns 1-indexed session number if the click hit a session card, null otherwise.
653
+ *
654
+ * Session cards occupy rows: headerHeight + 2 through headerHeight + 1 + sessionCount
655
+ * (row = headerHeight + 2 + i for 0-indexed session i)
656
+ */
657
+ export function hitTestSession(row, headerHeight, sessionCount) {
658
+ if (sessionCount <= 0)
659
+ return null;
660
+ const firstSessionRow = headerHeight + 2; // top border is headerHeight+1, first card is +2
661
+ const lastSessionRow = firstSessionRow + sessionCount - 1;
662
+ if (row < firstSessionRow || row > lastSessionRow)
663
+ return null;
664
+ return row - firstSessionRow + 1; // 1-indexed
665
+ }
642
666
  // ── Exported pure helpers (for testing) ─────────────────────────────────────
643
667
  export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatPrompt, formatDrilldownHeader };
644
668
  //# sourceMappingURL=tui.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.71.0",
3
+ "version": "0.72.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",