aoaoe 0.72.0 → 0.74.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,31 @@ async function main() {
308
325
  tui.log("system", `viewing session #${sessionIdx}`);
309
326
  }
310
327
  });
328
+ // wire /search command to TUI activity filter
329
+ input.onSearch((pattern) => {
330
+ tui.setSearch(pattern);
331
+ if (pattern) {
332
+ tui.log("system", `search: "${pattern}"`);
333
+ }
334
+ else {
335
+ tui.log("system", "search cleared");
336
+ }
337
+ });
338
+ // wire mouse wheel to scroll (3 lines per tick for smooth scrolling)
339
+ input.onMouseWheel((direction) => {
340
+ if (tui.getViewMode() === "drilldown") {
341
+ if (direction === "up")
342
+ tui.scrollDrilldownUp(3);
343
+ else
344
+ tui.scrollDrilldownDown(3);
345
+ }
346
+ else {
347
+ if (direction === "up")
348
+ tui.scrollUp(3);
349
+ else
350
+ tui.scrollDown(3);
351
+ }
352
+ });
311
353
  }
312
354
  // start TUI (alternate screen buffer) after input is ready
313
355
  if (tui) {
package/dist/input.d.ts CHANGED
@@ -1,6 +1,7 @@
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 type SearchHandler = (pattern: string | null) => void;
4
5
  export interface MouseEvent {
5
6
  button: number;
6
7
  col: number;
@@ -8,6 +9,7 @@ export interface MouseEvent {
8
9
  press: boolean;
9
10
  }
10
11
  export type MouseClickHandler = (row: number, col: number) => void;
12
+ export type MouseWheelHandler = (direction: "up" | "down") => void;
11
13
  /** Parse an SGR extended mouse event from raw terminal data. Returns null if not a mouse event. */
12
14
  export declare function parseMouseEvent(data: string): MouseEvent | null;
13
15
  export declare class InputReader {
@@ -19,11 +21,15 @@ export declare class InputReader {
19
21
  private queueChangeHandler;
20
22
  private viewHandler;
21
23
  private mouseClickHandler;
24
+ private mouseWheelHandler;
25
+ private searchHandler;
22
26
  private mouseDataListener;
23
27
  onScroll(handler: (dir: ScrollDirection) => void): void;
24
28
  onQueueChange(handler: (count: number) => void): void;
25
29
  onView(handler: ViewHandler): void;
26
30
  onMouseClick(handler: MouseClickHandler): void;
31
+ onMouseWheel(handler: MouseWheelHandler): void;
32
+ onSearch(handler: SearchHandler): void;
27
33
  private notifyQueueChange;
28
34
  start(): void;
29
35
  drain(): string[];
package/dist/input.js CHANGED
@@ -30,6 +30,8 @@ export class InputReader {
30
30
  queueChangeHandler = null;
31
31
  viewHandler = null;
32
32
  mouseClickHandler = null;
33
+ mouseWheelHandler = null;
34
+ searchHandler = null;
33
35
  mouseDataListener = null;
34
36
  // register a callback for scroll key events (PgUp/PgDn/Home/End)
35
37
  onScroll(handler) {
@@ -47,6 +49,14 @@ export class InputReader {
47
49
  onMouseClick(handler) {
48
50
  this.mouseClickHandler = handler;
49
51
  }
52
+ // register a callback for mouse wheel events (scroll up/down)
53
+ onMouseWheel(handler) {
54
+ this.mouseWheelHandler = handler;
55
+ }
56
+ // register a callback for search commands (/search <pattern> or /search to clear)
57
+ onSearch(handler) {
58
+ this.searchHandler = handler;
59
+ }
50
60
  notifyQueueChange() {
51
61
  this.queueChangeHandler?.(this.queue.length);
52
62
  }
@@ -68,9 +78,19 @@ export class InputReader {
68
78
  this.mouseDataListener = (data) => {
69
79
  const str = data.toString("utf8");
70
80
  const evt = parseMouseEvent(str);
71
- if (evt && evt.press && evt.button === 0 && this.mouseClickHandler) {
81
+ if (!evt)
82
+ return;
83
+ // left click press
84
+ if (evt.press && evt.button === 0 && this.mouseClickHandler) {
72
85
  this.mouseClickHandler(evt.row, evt.col);
73
86
  }
87
+ // mouse wheel: button 64 = scroll up, 65 = scroll down
88
+ if (evt.button === 64 && this.mouseWheelHandler) {
89
+ this.mouseWheelHandler("up");
90
+ }
91
+ else if (evt.button === 65 && this.mouseWheelHandler) {
92
+ this.mouseWheelHandler("down");
93
+ }
74
94
  };
75
95
  process.stdin.on("data", this.mouseDataListener);
76
96
  process.stdin.on("keypress", (_ch, key) => {
@@ -200,8 +220,11 @@ ${BOLD}controls:${RESET}
200
220
  ${BOLD}navigation:${RESET}
201
221
  /view [N|name] drill into a session's live output (default: 1)
202
222
  /back return to overview from drill-down
223
+ /search <pattern> filter activity entries by substring (case-insensitive)
224
+ /search clear active search filter
203
225
  click session click an agent card to drill down (click again to go back)
204
- PgUp / PgDn scroll through activity history
226
+ mouse wheel scroll activity (overview) or session output (drill-down)
227
+ PgUp / PgDn scroll through activity or session output
205
228
  Home / End jump to oldest / return to live
206
229
 
207
230
  ${BOLD}info:${RESET}
@@ -276,6 +299,16 @@ ${BOLD}other:${RESET}
276
299
  console.error(`${DIM}already in overview${RESET}`);
277
300
  }
278
301
  break;
302
+ case "/search": {
303
+ const searchArg = line.slice("/search".length).trim();
304
+ if (this.searchHandler) {
305
+ this.searchHandler(searchArg || null); // empty = clear search
306
+ }
307
+ else {
308
+ console.error(`${DIM}search not available (no TUI)${RESET}`);
309
+ }
310
+ break;
311
+ }
279
312
  case "/clear":
280
313
  process.stderr.write("\x1b[2J\x1b[H");
281
314
  break;
package/dist/tui.d.ts CHANGED
@@ -23,9 +23,12 @@ export declare class TUI {
23
23
  private scrollOffset;
24
24
  private newWhileScrolled;
25
25
  private pendingCount;
26
+ private searchPattern;
26
27
  private viewMode;
27
28
  private drilldownSessionId;
28
29
  private sessionOutputs;
30
+ private drilldownScrollOffset;
31
+ private drilldownNewWhileScrolled;
29
32
  private phase;
30
33
  private pollCount;
31
34
  private sessions;
@@ -54,6 +57,10 @@ export declare class TUI {
54
57
  scrollToTop(): void;
55
58
  scrollToBottom(): void;
56
59
  isScrolledBack(): boolean;
60
+ scrollDrilldownUp(lines?: number): void;
61
+ scrollDrilldownDown(lines?: number): void;
62
+ scrollDrilldownToBottom(): void;
63
+ isDrilldownScrolledBack(): boolean;
57
64
  /** Store full session outputs (called each tick from main loop) */
58
65
  setSessionOutputs(outputs: Map<string, string>): void;
59
66
  /** Enter drill-down view for a session. Returns false if session not found. */
@@ -64,6 +71,10 @@ export declare class TUI {
64
71
  getViewMode(): "overview" | "drilldown";
65
72
  /** Get drill-down session ID (or null) */
66
73
  getDrilldownSessionId(): string | null;
74
+ /** Set or clear the search filter. Resets scroll and repaints. */
75
+ setSearch(pattern: string | null): void;
76
+ /** Get the current search pattern (or null if no active search). */
77
+ getSearchPattern(): string | null;
67
78
  private updateDimensions;
68
79
  private computeLayout;
69
80
  private onResize;
@@ -96,6 +107,11 @@ declare function computeScrollSlice(bufferLen: number, visibleLines: number, scr
96
107
  end: number;
97
108
  };
98
109
  declare function formatScrollIndicator(offset: number, totalEntries: number, visibleLines: number, newCount: number): string;
110
+ declare function formatDrilldownScrollIndicator(offset: number, totalLines: number, visibleLines: number, newCount: number): string;
111
+ /** Case-insensitive substring match against an activity entry's tag, text, and time. */
112
+ declare function matchesSearch(entry: ActivityEntry, pattern: string): boolean;
113
+ /** Format the search indicator text for the separator bar. */
114
+ declare function formatSearchIndicator(pattern: string, matchCount: number, totalCount: number): string;
99
115
  /**
100
116
  * Hit-test a mouse click row against the session panel.
101
117
  * Returns 1-indexed session number if the click hit a session card, null otherwise.
@@ -104,5 +120,5 @@ declare function formatScrollIndicator(offset: number, totalEntries: number, vis
104
120
  * (row = headerHeight + 2 + i for 0-indexed session i)
105
121
  */
106
122
  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 };
123
+ export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator };
108
124
  //# sourceMappingURL=tui.d.ts.map
package/dist/tui.js CHANGED
@@ -61,10 +61,13 @@ export class TUI {
61
61
  scrollOffset = 0; // 0 = live (bottom), >0 = scrolled back N entries
62
62
  newWhileScrolled = 0; // entries added while user is scrolled back
63
63
  pendingCount = 0; // queued user messages awaiting next tick
64
+ searchPattern = null; // active search filter pattern
64
65
  // drill-down mode: show a single session's full output
65
66
  viewMode = "overview";
66
67
  drilldownSessionId = null;
67
68
  sessionOutputs = new Map(); // full output lines per session
69
+ drilldownScrollOffset = 0; // 0 = live (tail), >0 = scrolled back N lines
70
+ drilldownNewWhileScrolled = 0; // lines added while scrolled back
68
71
  // current state for repaints
69
72
  phase = "sleeping";
70
73
  pollCount = 0;
@@ -156,7 +159,20 @@ export class TUI {
156
159
  this.activityBuffer = this.activityBuffer.slice(-this.maxActivity);
157
160
  }
158
161
  if (this.active) {
159
- if (this.scrollOffset > 0) {
162
+ if (this.searchPattern) {
163
+ // search active: only show new entry if it matches
164
+ if (matchesSearch(entry, this.searchPattern)) {
165
+ if (this.scrollOffset > 0) {
166
+ this.newWhileScrolled++;
167
+ this.paintSeparator();
168
+ }
169
+ else {
170
+ this.writeActivityLine(entry);
171
+ }
172
+ }
173
+ // non-matching entries are silently buffered — visible when search is cleared
174
+ }
175
+ else if (this.scrollOffset > 0) {
160
176
  // user is scrolled back — don't auto-scroll, just show indicator
161
177
  this.newWhileScrolled++;
162
178
  this.paintSeparator();
@@ -185,7 +201,10 @@ export class TUI {
185
201
  return;
186
202
  const visibleLines = this.scrollBottom - this.scrollTop + 1;
187
203
  const n = lines ?? Math.max(1, Math.floor(visibleLines / 2));
188
- const maxOffset = Math.max(0, this.activityBuffer.length - visibleLines);
204
+ const entryCount = this.searchPattern
205
+ ? this.activityBuffer.filter((e) => matchesSearch(e, this.searchPattern)).length
206
+ : this.activityBuffer.length;
207
+ const maxOffset = Math.max(0, entryCount - visibleLines);
189
208
  this.scrollOffset = Math.min(maxOffset, this.scrollOffset + n);
190
209
  this.repaintActivityRegion();
191
210
  this.paintSeparator();
@@ -206,7 +225,10 @@ export class TUI {
206
225
  if (!this.active)
207
226
  return;
208
227
  const visibleLines = this.scrollBottom - this.scrollTop + 1;
209
- this.scrollOffset = Math.max(0, this.activityBuffer.length - visibleLines);
228
+ const entryCount = this.searchPattern
229
+ ? this.activityBuffer.filter((e) => matchesSearch(e, this.searchPattern)).length
230
+ : this.activityBuffer.length;
231
+ this.scrollOffset = Math.max(0, entryCount - visibleLines);
210
232
  this.repaintActivityRegion();
211
233
  this.paintSeparator();
212
234
  }
@@ -221,15 +243,58 @@ export class TUI {
221
243
  isScrolledBack() {
222
244
  return this.scrollOffset > 0;
223
245
  }
246
+ // ── Drill-down scroll ─────────────────────────────────────────────────
247
+ scrollDrilldownUp(lines) {
248
+ if (!this.active || this.viewMode !== "drilldown" || !this.drilldownSessionId)
249
+ return;
250
+ const outputLines = this.sessionOutputs.get(this.drilldownSessionId) ?? [];
251
+ const visibleLines = this.scrollBottom - this.scrollTop + 1;
252
+ const n = lines ?? Math.max(1, Math.floor(visibleLines / 2));
253
+ const maxOffset = Math.max(0, outputLines.length - visibleLines);
254
+ this.drilldownScrollOffset = Math.min(maxOffset, this.drilldownScrollOffset + n);
255
+ this.repaintDrilldownContent();
256
+ this.paintDrilldownSeparator();
257
+ }
258
+ scrollDrilldownDown(lines) {
259
+ if (!this.active || this.viewMode !== "drilldown")
260
+ return;
261
+ const visibleLines = this.scrollBottom - this.scrollTop + 1;
262
+ const n = lines ?? Math.max(1, Math.floor(visibleLines / 2));
263
+ const wasScrolled = this.drilldownScrollOffset > 0;
264
+ this.drilldownScrollOffset = Math.max(0, this.drilldownScrollOffset - n);
265
+ if (wasScrolled && this.drilldownScrollOffset === 0)
266
+ this.drilldownNewWhileScrolled = 0;
267
+ this.repaintDrilldownContent();
268
+ this.paintDrilldownSeparator();
269
+ }
270
+ scrollDrilldownToBottom() {
271
+ if (!this.active || this.viewMode !== "drilldown")
272
+ return;
273
+ this.drilldownScrollOffset = 0;
274
+ this.drilldownNewWhileScrolled = 0;
275
+ this.repaintDrilldownContent();
276
+ this.paintDrilldownSeparator();
277
+ }
278
+ isDrilldownScrolledBack() {
279
+ return this.drilldownScrollOffset > 0;
280
+ }
224
281
  // ── Drill-down mode ────────────────────────────────────────────────────
225
282
  /** Store full session outputs (called each tick from main loop) */
226
283
  setSessionOutputs(outputs) {
227
284
  for (const [id, text] of outputs) {
228
- this.sessionOutputs.set(id, text.split("\n"));
285
+ const prevLen = this.sessionOutputs.get(id)?.length ?? 0;
286
+ const lines = text.split("\n");
287
+ this.sessionOutputs.set(id, lines);
288
+ // track new lines while scrolled back in drill-down
289
+ if (this.viewMode === "drilldown" && this.drilldownSessionId === id && this.drilldownScrollOffset > 0) {
290
+ const newLines = Math.max(0, lines.length - prevLen);
291
+ this.drilldownNewWhileScrolled += newLines;
292
+ }
229
293
  }
230
294
  // repaint drill-down view if we're watching this session
231
295
  if (this.active && this.viewMode === "drilldown" && this.drilldownSessionId) {
232
296
  this.repaintDrilldownContent();
297
+ this.paintDrilldownSeparator();
233
298
  }
234
299
  }
235
300
  /** Enter drill-down view for a session. Returns false if session not found. */
@@ -251,6 +316,8 @@ export class TUI {
251
316
  return false;
252
317
  this.viewMode = "drilldown";
253
318
  this.drilldownSessionId = sessionId;
319
+ this.drilldownScrollOffset = 0;
320
+ this.drilldownNewWhileScrolled = 0;
254
321
  if (this.active) {
255
322
  this.computeLayout(this.sessions.length);
256
323
  this.paintAll();
@@ -263,6 +330,8 @@ export class TUI {
263
330
  return;
264
331
  this.viewMode = "overview";
265
332
  this.drilldownSessionId = null;
333
+ this.drilldownScrollOffset = 0;
334
+ this.drilldownNewWhileScrolled = 0;
266
335
  if (this.active) {
267
336
  this.computeLayout(this.sessions.length);
268
337
  this.paintAll();
@@ -276,6 +345,21 @@ export class TUI {
276
345
  getDrilldownSessionId() {
277
346
  return this.drilldownSessionId;
278
347
  }
348
+ // ── Search ──────────────────────────────────────────────────────────────
349
+ /** Set or clear the search filter. Resets scroll and repaints. */
350
+ setSearch(pattern) {
351
+ this.searchPattern = pattern && pattern.length > 0 ? pattern : null;
352
+ this.scrollOffset = 0;
353
+ this.newWhileScrolled = 0;
354
+ if (this.active && this.viewMode === "overview") {
355
+ this.repaintActivityRegion();
356
+ this.paintSeparator();
357
+ }
358
+ }
359
+ /** Get the current search pattern (or null if no active search). */
360
+ getSearchPattern() {
361
+ return this.searchPattern;
362
+ }
279
363
  // ── Layout computation ──────────────────────────────────────────────────
280
364
  updateDimensions() {
281
365
  this.cols = process.stderr.columns || 80;
@@ -386,7 +470,11 @@ export class TUI {
386
470
  paintSeparator() {
387
471
  const prefix = `${BOX.h}${BOX.h} activity `;
388
472
  let hints;
389
- if (this.scrollOffset > 0) {
473
+ if (this.searchPattern) {
474
+ const filtered = this.activityBuffer.filter((e) => matchesSearch(e, this.searchPattern));
475
+ hints = formatSearchIndicator(this.searchPattern, filtered.length, this.activityBuffer.length);
476
+ }
477
+ else if (this.scrollOffset > 0) {
390
478
  hints = formatScrollIndicator(this.scrollOffset, this.activityBuffer.length, this.scrollBottom - this.scrollTop + 1, this.newWhileScrolled);
391
479
  }
392
480
  else {
@@ -410,8 +498,12 @@ export class TUI {
410
498
  }
411
499
  repaintActivityRegion() {
412
500
  const visibleLines = this.scrollBottom - this.scrollTop + 1;
413
- const { start, end } = computeScrollSlice(this.activityBuffer.length, visibleLines, this.scrollOffset);
414
- const entries = this.activityBuffer.slice(start, end);
501
+ // when search is active, filter entries first, then paginate
502
+ const source = this.searchPattern
503
+ ? this.activityBuffer.filter((e) => matchesSearch(e, this.searchPattern))
504
+ : this.activityBuffer;
505
+ const { start, end } = computeScrollSlice(source.length, visibleLines, this.scrollOffset);
506
+ const entries = source.slice(start, end);
415
507
  for (let i = 0; i < visibleLines; i++) {
416
508
  const row = this.scrollTop + i;
417
509
  if (i < entries.length) {
@@ -428,7 +520,15 @@ export class TUI {
428
520
  const session = this.sessions.find((s) => s.id === this.drilldownSessionId);
429
521
  const title = session ? session.title : this.drilldownSessionId ?? "?";
430
522
  const prefix = `${BOX.h}${BOX.h} ${title} `;
431
- const hints = " /back: overview /view N: switch session ";
523
+ let hints;
524
+ if (this.drilldownScrollOffset > 0) {
525
+ const outputLines = this.sessionOutputs.get(this.drilldownSessionId ?? "") ?? [];
526
+ const visibleLines = this.scrollBottom - this.scrollTop + 1;
527
+ hints = formatDrilldownScrollIndicator(this.drilldownScrollOffset, outputLines.length, visibleLines, this.drilldownNewWhileScrolled);
528
+ }
529
+ else {
530
+ hints = " click or /back: overview scroll: navigate /view N: switch ";
531
+ }
432
532
  const totalLen = prefix.length + hints.length;
433
533
  const fill = Math.max(0, this.cols - totalLen);
434
534
  const left = Math.floor(fill / 2);
@@ -441,9 +541,9 @@ export class TUI {
441
541
  return;
442
542
  const outputLines = this.sessionOutputs.get(this.drilldownSessionId) ?? [];
443
543
  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);
544
+ // use scroll offset: 0 = tail (live), >0 = scrolled back
545
+ const { start, end } = computeScrollSlice(outputLines.length, visibleLines, this.drilldownScrollOffset);
546
+ const visible = outputLines.slice(start, end);
447
547
  for (let i = 0; i < visibleLines; i++) {
448
548
  const row = this.scrollTop + i;
449
549
  if (i < visible.length) {
@@ -646,6 +746,26 @@ function formatScrollIndicator(offset, totalEntries, visibleLines, newCount) {
646
746
  const newTag = newCount > 0 ? ` ${newCount} new ↓` : "";
647
747
  return ` ↑ ${offset} older │ ${position}/${totalEntries} │ PgUp/PgDn End=live${newTag} `;
648
748
  }
749
+ // format the scroll indicator for drill-down separator bar
750
+ function formatDrilldownScrollIndicator(offset, totalLines, visibleLines, newCount) {
751
+ const position = totalLines - offset;
752
+ const newTag = newCount > 0 ? ` ${newCount} new ↓` : "";
753
+ return ` ↑ ${offset} lines │ ${position}/${totalLines} │ scroll: navigate End=live${newTag} `;
754
+ }
755
+ // ── Search helpers (pure, exported for testing) ─────────────────────────────
756
+ /** Case-insensitive substring match against an activity entry's tag, text, and time. */
757
+ function matchesSearch(entry, pattern) {
758
+ if (!pattern)
759
+ return true;
760
+ const lower = pattern.toLowerCase();
761
+ return (entry.tag.toLowerCase().includes(lower) ||
762
+ entry.text.toLowerCase().includes(lower) ||
763
+ entry.time.toLowerCase().includes(lower));
764
+ }
765
+ /** Format the search indicator text for the separator bar. */
766
+ function formatSearchIndicator(pattern, matchCount, totalCount) {
767
+ return ` search: "${pattern}" │ ${matchCount} of ${totalCount} │ /search: clear `;
768
+ }
649
769
  // ── Mouse hit testing (pure, exported for testing) ──────────────────────────
650
770
  /**
651
771
  * Hit-test a mouse click row against the session panel.
@@ -664,5 +784,5 @@ export function hitTestSession(row, headerHeight, sessionCount) {
664
784
  return row - firstSessionRow + 1; // 1-indexed
665
785
  }
666
786
  // ── Exported pure helpers (for testing) ─────────────────────────────────────
667
- export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatPrompt, formatDrilldownHeader };
787
+ export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator };
668
788
  //# 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.74.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",