aoaoe 0.86.0 → 0.87.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
@@ -454,6 +454,16 @@ async function main() {
454
454
  tui.log("system", "no sessions are muted");
455
455
  }
456
456
  });
457
+ // wire /filter tag
458
+ input.onTagFilter((tag) => {
459
+ tui.setTagFilter(tag);
460
+ if (tag) {
461
+ tui.log("system", `filter: ${tag}`);
462
+ }
463
+ else {
464
+ tui.log("system", "filter cleared");
465
+ }
466
+ });
457
467
  // wire /note set/clear
458
468
  input.onNote((target, text) => {
459
469
  const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
package/dist/input.d.ts CHANGED
@@ -13,6 +13,7 @@ export type JumpHandler = (num: number) => void;
13
13
  export type MarksHandler = () => void;
14
14
  export type MuteHandler = (target: string) => void;
15
15
  export type UnmuteAllHandler = () => void;
16
+ export type TagFilterHandler = (tag: string | null) => void;
16
17
  export type NoteHandler = (target: string, text: string) => void;
17
18
  export type NotesHandler = () => void;
18
19
  export interface MouseEvent {
@@ -50,6 +51,7 @@ export declare class InputReader {
50
51
  private marksHandler;
51
52
  private muteHandler;
52
53
  private unmuteAllHandler;
54
+ private tagFilterHandler;
53
55
  private noteHandler;
54
56
  private notesHandler;
55
57
  private mouseDataListener;
@@ -71,6 +73,7 @@ export declare class InputReader {
71
73
  onMarks(handler: MarksHandler): void;
72
74
  onMute(handler: MuteHandler): void;
73
75
  onUnmuteAll(handler: UnmuteAllHandler): void;
76
+ onTagFilter(handler: TagFilterHandler): void;
74
77
  onNote(handler: NoteHandler): void;
75
78
  onNotes(handler: NotesHandler): void;
76
79
  private notifyQueueChange;
package/dist/input.js CHANGED
@@ -45,6 +45,7 @@ export class InputReader {
45
45
  marksHandler = null;
46
46
  muteHandler = null;
47
47
  unmuteAllHandler = null;
48
+ tagFilterHandler = null;
48
49
  noteHandler = null;
49
50
  notesHandler = null;
50
51
  mouseDataListener = null;
@@ -120,6 +121,10 @@ export class InputReader {
120
121
  onUnmuteAll(handler) {
121
122
  this.unmuteAllHandler = handler;
122
123
  }
124
+ // register a callback for tag filter commands (/filter <tag>)
125
+ onTagFilter(handler) {
126
+ this.tagFilterHandler = handler;
127
+ }
123
128
  // register a callback for note commands (/note <target> <text>)
124
129
  onNote(handler) {
125
130
  this.noteHandler = handler;
@@ -312,6 +317,7 @@ ${BOLD}navigation:${RESET}
312
317
  /focus toggle focus mode (show only pinned sessions)
313
318
  /mute [N|name] mute/unmute a session's activity entries (toggle)
314
319
  /unmute-all unmute all sessions at once
320
+ /filter [tag] filter activity by tag (error, system, etc. — no arg = clear)
315
321
  /note N|name text attach a note to a session (no text = clear)
316
322
  /notes list all session notes
317
323
  /mark bookmark current activity position
@@ -468,6 +474,16 @@ ${BOLD}other:${RESET}
468
474
  console.error(`${DIM}unmute-all not available (no TUI)${RESET}`);
469
475
  }
470
476
  break;
477
+ case "/filter": {
478
+ const filterArg = line.slice("/filter".length).trim();
479
+ if (this.tagFilterHandler) {
480
+ this.tagFilterHandler(filterArg || null);
481
+ }
482
+ else {
483
+ console.error(`${DIM}filter not available (no TUI)${RESET}`);
484
+ }
485
+ break;
486
+ }
471
487
  case "/note": {
472
488
  const noteArg = line.slice("/note".length).trim();
473
489
  if (this.noteHandler) {
package/dist/tui.d.ts CHANGED
@@ -53,6 +53,10 @@ export declare function truncateNote(text: string): string;
53
53
  export declare function shouldMuteEntry(entry: ActivityEntry, mutedIds: Set<string>): boolean;
54
54
  /** Format a suppressed entry count badge for muted sessions. Returns empty string for 0. */
55
55
  export declare function formatMuteBadge(count: number): string;
56
+ /** Check if an activity entry matches a tag filter (case-insensitive exact match on tag). */
57
+ export declare function matchesTagFilter(entry: ActivityEntry, tag: string): boolean;
58
+ /** Format the tag filter indicator text for the separator bar. */
59
+ export declare function formatTagFilterIndicator(tag: string, matchCount: number, totalCount: number): string;
56
60
  export declare class TUI {
57
61
  private active;
58
62
  private countdownTimer;
@@ -71,6 +75,7 @@ export declare class TUI {
71
75
  private newWhileScrolled;
72
76
  private pendingCount;
73
77
  private searchPattern;
78
+ private filterTag;
74
79
  private hoverSessionIdx;
75
80
  private activityTimestamps;
76
81
  private sortMode;
@@ -204,6 +209,10 @@ export declare class TUI {
204
209
  setSearch(pattern: string | null): void;
205
210
  /** Get the current search pattern (or null if no active search). */
206
211
  getSearchPattern(): string | null;
212
+ /** Set or clear the tag filter. Resets scroll and repaints. */
213
+ setTagFilter(tag: string | null): void;
214
+ /** Get the current tag filter (or null if none active). */
215
+ getTagFilter(): string | null;
207
216
  /** Set the hovered session index (1-indexed) or null to clear. Only repaints the affected cards. */
208
217
  setHoverSession(idx: number | null): void;
209
218
  /** Get the current hovered session index (1-indexed, null if none). */
package/dist/tui.js CHANGED
@@ -181,6 +181,16 @@ export function formatMuteBadge(count) {
181
181
  const label = count > 999 ? "999+" : String(count);
182
182
  return `${DIM}(${label})${RESET}`;
183
183
  }
184
+ /** Check if an activity entry matches a tag filter (case-insensitive exact match on tag). */
185
+ export function matchesTagFilter(entry, tag) {
186
+ if (!tag)
187
+ return true;
188
+ return entry.tag.toLowerCase() === tag.toLowerCase();
189
+ }
190
+ /** Format the tag filter indicator text for the separator bar. */
191
+ export function formatTagFilterIndicator(tag, matchCount, totalCount) {
192
+ return `${SLATE}filter:${RESET} ${AMBER}${tag}${RESET} ${DIM}(${matchCount}/${totalCount})${RESET}`;
193
+ }
184
194
  // ── TUI class ───────────────────────────────────────────────────────────────
185
195
  export class TUI {
186
196
  active = false;
@@ -200,6 +210,7 @@ export class TUI {
200
210
  newWhileScrolled = 0; // entries added while user is scrolled back
201
211
  pendingCount = 0; // queued user messages awaiting next tick
202
212
  searchPattern = null; // active search filter pattern
213
+ filterTag = null; // active tag filter (exact match on entry.tag)
203
214
  hoverSessionIdx = null; // 1-indexed session under mouse cursor (null = none)
204
215
  activityTimestamps = []; // epoch ms of each log() call for sparkline
205
216
  sortMode = "default";
@@ -593,6 +604,9 @@ export class TUI {
593
604
  if (shouldMuteEntry(entry, this.mutedIds)) {
594
605
  // silently skip display — entry is in buffer for scroll-back if unmuted later
595
606
  }
607
+ else if (this.filterTag && !matchesTagFilter(entry, this.filterTag)) {
608
+ // tag filter active: silently skip non-matching entries
609
+ }
596
610
  else if (this.searchPattern) {
597
611
  // search active: only show new entry if it matches
598
612
  if (matchesSearch(entry, this.searchPattern)) {
@@ -638,6 +652,8 @@ export class TUI {
638
652
  let filtered = this.mutedIds.size > 0
639
653
  ? this.activityBuffer.filter((e) => !shouldMuteEntry(e, this.mutedIds))
640
654
  : this.activityBuffer;
655
+ if (this.filterTag)
656
+ filtered = filtered.filter((e) => matchesTagFilter(e, this.filterTag));
641
657
  const entryCount = this.searchPattern
642
658
  ? filtered.filter((e) => matchesSearch(e, this.searchPattern)).length
643
659
  : filtered.length;
@@ -665,6 +681,8 @@ export class TUI {
665
681
  let filtered = this.mutedIds.size > 0
666
682
  ? this.activityBuffer.filter((e) => !shouldMuteEntry(e, this.mutedIds))
667
683
  : this.activityBuffer;
684
+ if (this.filterTag)
685
+ filtered = filtered.filter((e) => matchesTagFilter(e, this.filterTag));
668
686
  const entryCount = this.searchPattern
669
687
  ? filtered.filter((e) => matchesSearch(e, this.searchPattern)).length
670
688
  : filtered.length;
@@ -802,6 +820,21 @@ export class TUI {
802
820
  getSearchPattern() {
803
821
  return this.searchPattern;
804
822
  }
823
+ // ── Tag filter ─────────────────────────────────────────────────────────
824
+ /** Set or clear the tag filter. Resets scroll and repaints. */
825
+ setTagFilter(tag) {
826
+ this.filterTag = tag && tag.length > 0 ? tag : null;
827
+ this.scrollOffset = 0;
828
+ this.newWhileScrolled = 0;
829
+ if (this.active && this.viewMode === "overview") {
830
+ this.repaintActivityRegion();
831
+ this.paintSeparator();
832
+ }
833
+ }
834
+ /** Get the current tag filter (or null if none active). */
835
+ getTagFilter() {
836
+ return this.filterTag;
837
+ }
805
838
  // ── Hover ───────────────────────────────────────────────────────────────
806
839
  /** Set the hovered session index (1-indexed) or null to clear. Only repaints the affected cards. */
807
840
  setHoverSession(idx) {
@@ -1000,7 +1033,15 @@ export class TUI {
1000
1033
  paintSeparator() {
1001
1034
  const prefix = `${BOX.h}${BOX.h} activity `;
1002
1035
  let hints;
1003
- if (this.searchPattern) {
1036
+ if (this.filterTag) {
1037
+ // tag filter takes precedence in the separator display
1038
+ let source = this.mutedIds.size > 0
1039
+ ? this.activityBuffer.filter((e) => !shouldMuteEntry(e, this.mutedIds))
1040
+ : this.activityBuffer;
1041
+ const matchCount = source.filter((e) => matchesTagFilter(e, this.filterTag)).length;
1042
+ hints = formatTagFilterIndicator(this.filterTag, matchCount, source.length);
1043
+ }
1044
+ else if (this.searchPattern) {
1004
1045
  const filtered = this.activityBuffer.filter((e) => matchesSearch(e, this.searchPattern));
1005
1046
  hints = formatSearchIndicator(this.searchPattern, filtered.length, this.activityBuffer.length);
1006
1047
  }
@@ -1030,10 +1071,12 @@ export class TUI {
1030
1071
  }
1031
1072
  repaintActivityRegion() {
1032
1073
  const visibleLines = this.scrollBottom - this.scrollTop + 1;
1033
- // filter: muted entries first, then search on top
1074
+ // filter pipeline: muted tag search
1034
1075
  let source = this.mutedIds.size > 0
1035
1076
  ? this.activityBuffer.filter((e) => !shouldMuteEntry(e, this.mutedIds))
1036
1077
  : this.activityBuffer;
1078
+ if (this.filterTag)
1079
+ source = source.filter((e) => matchesTagFilter(e, this.filterTag));
1037
1080
  if (this.searchPattern) {
1038
1081
  source = source.filter((e) => matchesSearch(e, this.searchPattern));
1039
1082
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.86.0",
3
+ "version": "0.87.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",