aoaoe 0.73.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
@@ -325,6 +325,16 @@ async function main() {
325
325
  tui.log("system", `viewing session #${sessionIdx}`);
326
326
  }
327
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
+ });
328
338
  // wire mouse wheel to scroll (3 lines per tick for smooth scrolling)
329
339
  input.onMouseWheel((direction) => {
330
340
  if (tui.getViewMode() === "drilldown") {
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;
@@ -21,12 +22,14 @@ export declare class InputReader {
21
22
  private viewHandler;
22
23
  private mouseClickHandler;
23
24
  private mouseWheelHandler;
25
+ private searchHandler;
24
26
  private mouseDataListener;
25
27
  onScroll(handler: (dir: ScrollDirection) => void): void;
26
28
  onQueueChange(handler: (count: number) => void): void;
27
29
  onView(handler: ViewHandler): void;
28
30
  onMouseClick(handler: MouseClickHandler): void;
29
31
  onMouseWheel(handler: MouseWheelHandler): void;
32
+ onSearch(handler: SearchHandler): void;
30
33
  private notifyQueueChange;
31
34
  start(): void;
32
35
  drain(): string[];
package/dist/input.js CHANGED
@@ -31,6 +31,7 @@ export class InputReader {
31
31
  viewHandler = null;
32
32
  mouseClickHandler = null;
33
33
  mouseWheelHandler = null;
34
+ searchHandler = null;
34
35
  mouseDataListener = null;
35
36
  // register a callback for scroll key events (PgUp/PgDn/Home/End)
36
37
  onScroll(handler) {
@@ -52,6 +53,10 @@ export class InputReader {
52
53
  onMouseWheel(handler) {
53
54
  this.mouseWheelHandler = handler;
54
55
  }
56
+ // register a callback for search commands (/search <pattern> or /search to clear)
57
+ onSearch(handler) {
58
+ this.searchHandler = handler;
59
+ }
55
60
  notifyQueueChange() {
56
61
  this.queueChangeHandler?.(this.queue.length);
57
62
  }
@@ -215,6 +220,8 @@ ${BOLD}controls:${RESET}
215
220
  ${BOLD}navigation:${RESET}
216
221
  /view [N|name] drill into a session's live output (default: 1)
217
222
  /back return to overview from drill-down
223
+ /search <pattern> filter activity entries by substring (case-insensitive)
224
+ /search clear active search filter
218
225
  click session click an agent card to drill down (click again to go back)
219
226
  mouse wheel scroll activity (overview) or session output (drill-down)
220
227
  PgUp / PgDn scroll through activity or session output
@@ -292,6 +299,16 @@ ${BOLD}other:${RESET}
292
299
  console.error(`${DIM}already in overview${RESET}`);
293
300
  }
294
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
+ }
295
312
  case "/clear":
296
313
  process.stderr.write("\x1b[2J\x1b[H");
297
314
  break;
package/dist/tui.d.ts CHANGED
@@ -23,6 +23,7 @@ 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;
@@ -70,6 +71,10 @@ export declare class TUI {
70
71
  getViewMode(): "overview" | "drilldown";
71
72
  /** Get drill-down session ID (or null) */
72
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;
73
78
  private updateDimensions;
74
79
  private computeLayout;
75
80
  private onResize;
@@ -103,6 +108,10 @@ declare function computeScrollSlice(bufferLen: number, visibleLines: number, scr
103
108
  };
104
109
  declare function formatScrollIndicator(offset: number, totalEntries: number, visibleLines: number, newCount: number): string;
105
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;
106
115
  /**
107
116
  * Hit-test a mouse click row against the session panel.
108
117
  * Returns 1-indexed session number if the click hit a session card, null otherwise.
@@ -111,5 +120,5 @@ declare function formatDrilldownScrollIndicator(offset: number, totalLines: numb
111
120
  * (row = headerHeight + 2 + i for 0-indexed session i)
112
121
  */
113
122
  export declare function hitTestSession(row: number, headerHeight: number, sessionCount: number): number | null;
114
- export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader };
123
+ export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator };
115
124
  //# sourceMappingURL=tui.d.ts.map
package/dist/tui.js CHANGED
@@ -61,6 +61,7 @@ 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;
@@ -158,7 +159,20 @@ export class TUI {
158
159
  this.activityBuffer = this.activityBuffer.slice(-this.maxActivity);
159
160
  }
160
161
  if (this.active) {
161
- 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) {
162
176
  // user is scrolled back — don't auto-scroll, just show indicator
163
177
  this.newWhileScrolled++;
164
178
  this.paintSeparator();
@@ -187,7 +201,10 @@ export class TUI {
187
201
  return;
188
202
  const visibleLines = this.scrollBottom - this.scrollTop + 1;
189
203
  const n = lines ?? Math.max(1, Math.floor(visibleLines / 2));
190
- 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);
191
208
  this.scrollOffset = Math.min(maxOffset, this.scrollOffset + n);
192
209
  this.repaintActivityRegion();
193
210
  this.paintSeparator();
@@ -208,7 +225,10 @@ export class TUI {
208
225
  if (!this.active)
209
226
  return;
210
227
  const visibleLines = this.scrollBottom - this.scrollTop + 1;
211
- 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);
212
232
  this.repaintActivityRegion();
213
233
  this.paintSeparator();
214
234
  }
@@ -325,6 +345,21 @@ export class TUI {
325
345
  getDrilldownSessionId() {
326
346
  return this.drilldownSessionId;
327
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
+ }
328
363
  // ── Layout computation ──────────────────────────────────────────────────
329
364
  updateDimensions() {
330
365
  this.cols = process.stderr.columns || 80;
@@ -435,7 +470,11 @@ export class TUI {
435
470
  paintSeparator() {
436
471
  const prefix = `${BOX.h}${BOX.h} activity `;
437
472
  let hints;
438
- 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) {
439
478
  hints = formatScrollIndicator(this.scrollOffset, this.activityBuffer.length, this.scrollBottom - this.scrollTop + 1, this.newWhileScrolled);
440
479
  }
441
480
  else {
@@ -459,8 +498,12 @@ export class TUI {
459
498
  }
460
499
  repaintActivityRegion() {
461
500
  const visibleLines = this.scrollBottom - this.scrollTop + 1;
462
- const { start, end } = computeScrollSlice(this.activityBuffer.length, visibleLines, this.scrollOffset);
463
- 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);
464
507
  for (let i = 0; i < visibleLines; i++) {
465
508
  const row = this.scrollTop + i;
466
509
  if (i < entries.length) {
@@ -709,6 +752,20 @@ function formatDrilldownScrollIndicator(offset, totalLines, visibleLines, newCou
709
752
  const newTag = newCount > 0 ? ` ${newCount} new ↓` : "";
710
753
  return ` ↑ ${offset} lines │ ${position}/${totalLines} │ scroll: navigate End=live${newTag} `;
711
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
+ }
712
769
  // ── Mouse hit testing (pure, exported for testing) ──────────────────────────
713
770
  /**
714
771
  * Hit-test a mouse click row against the session panel.
@@ -727,5 +784,5 @@ export function hitTestSession(row, headerHeight, sessionCount) {
727
784
  return row - firstSessionRow + 1; // 1-indexed
728
785
  }
729
786
  // ── Exported pure helpers (for testing) ─────────────────────────────────────
730
- export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader };
787
+ export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator };
731
788
  //# sourceMappingURL=tui.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.73.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",