aoaoe 0.80.0 → 0.82.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
@@ -378,6 +378,18 @@ async function main() {
378
378
  tui.setCompact(enabled);
379
379
  tui.log("system", `compact mode: ${enabled ? "on" : "off"}`);
380
380
  });
381
+ // wire /focus toggle
382
+ input.onFocus(() => {
383
+ const enabled = !tui.isFocused();
384
+ tui.setFocus(enabled);
385
+ tui.log("system", `focus mode: ${enabled ? "on (pinned only)" : "off (all sessions)"}`);
386
+ });
387
+ // wire /bell toggle
388
+ input.onBell(() => {
389
+ const enabled = !tui.isBellEnabled();
390
+ tui.setBell(enabled);
391
+ tui.log("system", `bell notifications: ${enabled ? "on" : "off"}`);
392
+ });
381
393
  // wire /pin toggle
382
394
  input.onPin((target) => {
383
395
  const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
package/dist/input.d.ts CHANGED
@@ -6,6 +6,8 @@ export type QuickSwitchHandler = (sessionNum: number) => void;
6
6
  export type SortHandler = (mode: string | null) => void;
7
7
  export type CompactHandler = () => void;
8
8
  export type PinHandler = (target: string) => void;
9
+ export type BellHandler = () => void;
10
+ export type FocusHandler = () => void;
9
11
  export interface MouseEvent {
10
12
  button: number;
11
13
  col: number;
@@ -34,6 +36,8 @@ export declare class InputReader {
34
36
  private sortHandler;
35
37
  private compactHandler;
36
38
  private pinHandler;
39
+ private bellHandler;
40
+ private focusHandler;
37
41
  private mouseDataListener;
38
42
  onScroll(handler: (dir: ScrollDirection) => void): void;
39
43
  onQueueChange(handler: (count: number) => void): void;
@@ -46,6 +50,8 @@ export declare class InputReader {
46
50
  onSort(handler: SortHandler): void;
47
51
  onCompact(handler: CompactHandler): void;
48
52
  onPin(handler: PinHandler): void;
53
+ onBell(handler: BellHandler): void;
54
+ onFocus(handler: FocusHandler): void;
49
55
  private notifyQueueChange;
50
56
  start(): void;
51
57
  drain(): string[];
package/dist/input.js CHANGED
@@ -38,6 +38,8 @@ export class InputReader {
38
38
  sortHandler = null;
39
39
  compactHandler = null;
40
40
  pinHandler = null;
41
+ bellHandler = null;
42
+ focusHandler = null;
41
43
  mouseDataListener = null;
42
44
  // register a callback for scroll key events (PgUp/PgDn/Home/End)
43
45
  onScroll(handler) {
@@ -83,6 +85,14 @@ export class InputReader {
83
85
  onPin(handler) {
84
86
  this.pinHandler = handler;
85
87
  }
88
+ // register a callback for bell toggle (/bell)
89
+ onBell(handler) {
90
+ this.bellHandler = handler;
91
+ }
92
+ // register a callback for focus mode toggle (/focus)
93
+ onFocus(handler) {
94
+ this.focusHandler = handler;
95
+ }
86
96
  notifyQueueChange() {
87
97
  this.queueChangeHandler?.(this.queue.length);
88
98
  }
@@ -263,6 +273,8 @@ ${BOLD}navigation:${RESET}
263
273
  /sort [mode] sort sessions: status, name, activity, default (or cycle)
264
274
  /compact toggle compact mode (dense session panel)
265
275
  /pin [N|name] pin/unpin a session to the top (toggle)
276
+ /bell toggle terminal bell on errors/completions
277
+ /focus toggle focus mode (show only pinned sessions)
266
278
  /search <pattern> filter activity entries by substring (case-insensitive)
267
279
  /search clear active search filter
268
280
  click session click an agent card to drill down (click again to go back)
@@ -375,6 +387,22 @@ ${BOLD}other:${RESET}
375
387
  }
376
388
  break;
377
389
  }
390
+ case "/bell":
391
+ if (this.bellHandler) {
392
+ this.bellHandler();
393
+ }
394
+ else {
395
+ console.error(`${DIM}bell not available (no TUI)${RESET}`);
396
+ }
397
+ break;
398
+ case "/focus":
399
+ if (this.focusHandler) {
400
+ this.focusHandler();
401
+ }
402
+ else {
403
+ console.error(`${DIM}focus not available (no TUI)${RESET}`);
404
+ }
405
+ break;
378
406
  case "/search": {
379
407
  const searchArg = line.slice("/search".length).trim();
380
408
  if (this.searchHandler) {
package/dist/tui.d.ts CHANGED
@@ -6,6 +6,10 @@ declare const SORT_MODES: SortMode[];
6
6
  declare function sortSessions(sessions: DaemonSessionState[], mode: SortMode, lastChangeAt?: Map<string, number>, pinnedIds?: Set<string>): DaemonSessionState[];
7
7
  /** Cycle to the next sort mode. */
8
8
  declare function nextSortMode(current: SortMode): SortMode;
9
+ /** Cooldown between terminal bells to avoid buzzing. */
10
+ export declare const BELL_COOLDOWN_MS = 5000;
11
+ /** Determine if an activity entry should trigger a terminal bell. High-signal events only. */
12
+ export declare function shouldBell(tag: string, text: string): boolean;
9
13
  /** Max name length in compact token. */
10
14
  declare const COMPACT_NAME_LEN = 10;
11
15
  /** Pin indicator for pinned sessions. */
@@ -49,6 +53,9 @@ export declare class TUI {
49
53
  private prevLastActivity;
50
54
  private compactMode;
51
55
  private pinnedIds;
56
+ private focusMode;
57
+ private bellEnabled;
58
+ private lastBellAt;
52
59
  private viewMode;
53
60
  private drilldownSessionId;
54
61
  private sessionOutputs;
@@ -64,7 +71,7 @@ export declare class TUI {
64
71
  start(version: string): void;
65
72
  stop(): void;
66
73
  isActive(): boolean;
67
- /** Return the current number of sessions (for mouse hit testing) */
74
+ /** Return the current number of visible sessions (for mouse hit testing) */
68
75
  getSessionCount(): number;
69
76
  /** Set the session sort mode and repaint. */
70
77
  setSortMode(mode: SortMode): void;
@@ -83,6 +90,16 @@ export declare class TUI {
83
90
  isPinned(id: string): boolean;
84
91
  /** Return count of pinned sessions. */
85
92
  getPinnedCount(): number;
93
+ /** Enable or disable focus mode. When focused, only pinned sessions are visible. */
94
+ setFocus(enabled: boolean): void;
95
+ /** Return whether focus mode is enabled. */
96
+ isFocused(): boolean;
97
+ /** Return count of visible sessions (all in normal mode, pinned-only in focus mode). */
98
+ private getVisibleCount;
99
+ /** Enable or disable terminal bell notifications. */
100
+ setBell(enabled: boolean): void;
101
+ /** Return whether terminal bell is enabled. */
102
+ isBellEnabled(): boolean;
86
103
  updateState(opts: {
87
104
  phase?: DaemonPhase;
88
105
  pollCount?: number;
package/dist/tui.js CHANGED
@@ -52,6 +52,17 @@ function nextSortMode(current) {
52
52
  const idx = SORT_MODES.indexOf(current);
53
53
  return SORT_MODES[(idx + 1) % SORT_MODES.length];
54
54
  }
55
+ // ── Bell notifications ──────────────────────────────────────────────────────
56
+ /** Cooldown between terminal bells to avoid buzzing. */
57
+ export const BELL_COOLDOWN_MS = 5000;
58
+ /** Determine if an activity entry should trigger a terminal bell. High-signal events only. */
59
+ export function shouldBell(tag, text) {
60
+ if (tag === "! action" || tag === "error")
61
+ return true;
62
+ if (tag === "+ action" && text.toLowerCase().includes("complete"))
63
+ return true;
64
+ return false;
65
+ }
55
66
  // ── Compact mode ────────────────────────────────────────────────────────────
56
67
  /** Max name length in compact token. */
57
68
  const COMPACT_NAME_LEN = 10;
@@ -150,6 +161,9 @@ export class TUI {
150
161
  prevLastActivity = new Map(); // session ID → previous lastActivity string
151
162
  compactMode = false;
152
163
  pinnedIds = new Set(); // pinned session IDs (always sort to top)
164
+ focusMode = false; // focus mode: hide all sessions except pinned
165
+ bellEnabled = false;
166
+ lastBellAt = 0;
153
167
  // drill-down mode: show a single session's full output
154
168
  viewMode = "overview";
155
169
  drilldownSessionId = null;
@@ -202,9 +216,9 @@ export class TUI {
202
216
  isActive() {
203
217
  return this.active;
204
218
  }
205
- /** Return the current number of sessions (for mouse hit testing) */
219
+ /** Return the current number of visible sessions (for mouse hit testing) */
206
220
  getSessionCount() {
207
- return this.sessions.length;
221
+ return this.getVisibleCount();
208
222
  }
209
223
  /** Set the session sort mode and repaint. */
210
224
  setSortMode(mode) {
@@ -227,7 +241,7 @@ export class TUI {
227
241
  return;
228
242
  this.compactMode = enabled;
229
243
  if (this.active) {
230
- this.computeLayout(this.sessions.length);
244
+ this.computeLayout(this.getVisibleCount());
231
245
  this.paintAll();
232
246
  }
233
247
  }
@@ -272,6 +286,39 @@ export class TUI {
272
286
  getPinnedCount() {
273
287
  return this.pinnedIds.size;
274
288
  }
289
+ /** Enable or disable focus mode. When focused, only pinned sessions are visible. */
290
+ setFocus(enabled) {
291
+ if (enabled === this.focusMode)
292
+ return;
293
+ this.focusMode = enabled;
294
+ if (this.active) {
295
+ this.computeLayout(this.getVisibleCount());
296
+ this.paintAll();
297
+ }
298
+ }
299
+ /** Return whether focus mode is enabled. */
300
+ isFocused() {
301
+ return this.focusMode;
302
+ }
303
+ /** Return count of visible sessions (all in normal mode, pinned-only in focus mode). */
304
+ getVisibleCount() {
305
+ if (!this.focusMode)
306
+ return this.sessions.length;
307
+ let count = 0;
308
+ for (const s of this.sessions) {
309
+ if (this.pinnedIds.has(s.id))
310
+ count++;
311
+ }
312
+ return count;
313
+ }
314
+ /** Enable or disable terminal bell notifications. */
315
+ setBell(enabled) {
316
+ this.bellEnabled = enabled;
317
+ }
318
+ /** Return whether terminal bell is enabled. */
319
+ isBellEnabled() {
320
+ return this.bellEnabled;
321
+ }
275
322
  // ── State updates ───────────────────────────────────────────────────────
276
323
  updateState(opts) {
277
324
  if (opts.phase !== undefined)
@@ -298,10 +345,11 @@ export class TUI {
298
345
  this.prevLastActivity.set(s.id, s.lastActivity);
299
346
  }
300
347
  const sorted = sortSessions(opts.sessions, this.sortMode, this.lastChangeAt, this.pinnedIds);
301
- const sessionCountChanged = sorted.length !== this.sessions.length;
348
+ const prevVisibleCount = this.getVisibleCount();
302
349
  this.sessions = sorted;
303
- if (sessionCountChanged) {
304
- this.computeLayout(this.sessions.length);
350
+ const newVisibleCount = this.getVisibleCount();
351
+ if (newVisibleCount !== prevVisibleCount) {
352
+ this.computeLayout(newVisibleCount);
305
353
  this.paintAll();
306
354
  return;
307
355
  }
@@ -325,6 +373,14 @@ export class TUI {
325
373
  this.activityBuffer = this.activityBuffer.slice(-this.maxActivity);
326
374
  this.activityTimestamps = this.activityTimestamps.slice(-this.maxActivity);
327
375
  }
376
+ // terminal bell for high-signal events (with cooldown)
377
+ if (this.bellEnabled && shouldBell(tag, text)) {
378
+ const nowMs = now.getTime();
379
+ if (nowMs - this.lastBellAt >= BELL_COOLDOWN_MS) {
380
+ this.lastBellAt = nowMs;
381
+ process.stderr.write("\x07");
382
+ }
383
+ }
328
384
  if (this.active) {
329
385
  if (this.searchPattern) {
330
386
  // search active: only show new entry if it matches
@@ -487,7 +543,7 @@ export class TUI {
487
543
  this.drilldownNewWhileScrolled = 0;
488
544
  this.hoverSessionIdx = null;
489
545
  if (this.active) {
490
- this.computeLayout(this.sessions.length);
546
+ this.computeLayout(this.getVisibleCount());
491
547
  this.paintAll();
492
548
  }
493
549
  return true;
@@ -502,7 +558,7 @@ export class TUI {
502
558
  this.drilldownNewWhileScrolled = 0;
503
559
  this.hoverSessionIdx = null;
504
560
  if (this.active) {
505
- this.computeLayout(this.sessions.length);
561
+ this.computeLayout(this.getVisibleCount());
506
562
  this.paintAll();
507
563
  }
508
564
  }
@@ -565,8 +621,9 @@ export class TUI {
565
621
  }
566
622
  else {
567
623
  // overview: header (1) + sessions box + separator + activity + input
624
+ const visibleSessions = this.sessions.slice(0, this.getVisibleCount());
568
625
  const sessBodyRows = this.compactMode
569
- ? computeCompactRowCount(this.sessions, this.cols - 2)
626
+ ? computeCompactRowCount(visibleSessions, this.cols - 2)
570
627
  : Math.max(sessionCount, 1);
571
628
  this.sessionRows = sessBodyRows + 2; // + top/bottom borders
572
629
  this.separatorRow = this.headerHeight + this.sessionRows + 1;
@@ -579,7 +636,7 @@ export class TUI {
579
636
  }
580
637
  }
581
638
  onResize() {
582
- this.computeLayout(this.sessions.length);
639
+ this.computeLayout(this.getVisibleCount());
583
640
  this.paintAll();
584
641
  }
585
642
  // ── Painting ────────────────────────────────────────────────────────────
@@ -607,7 +664,10 @@ export class TUI {
607
664
  }
608
665
  else {
609
666
  const phaseText = phaseDisplay(this.phase, this.paused, this.spinnerFrame);
610
- const sessCount = `${this.sessions.length} agent${this.sessions.length !== 1 ? "s" : ""}`;
667
+ const visCount = this.getVisibleCount();
668
+ const sessCount = this.focusMode
669
+ ? `${visCount}/${this.sessions.length} agent${this.sessions.length !== 1 ? "s" : ""}`
670
+ : `${this.sessions.length} agent${this.sessions.length !== 1 ? "s" : ""}`;
611
671
  const activeCount = this.sessions.filter((s) => s.userActive).length;
612
672
  const activeTag = activeCount > 0 ? ` ${SLATE}│${RESET} ${AMBER}${activeCount} user${RESET}` : "";
613
673
  // countdown to next tick (only in sleeping phase)
@@ -627,23 +687,29 @@ export class TUI {
627
687
  paintSessions() {
628
688
  const startRow = this.headerHeight + 1;
629
689
  const innerWidth = this.cols - 2; // inside the box borders
630
- // top border with label (includes compact/sort mode tags)
690
+ const visibleCount = this.getVisibleCount();
691
+ const visibleSessions = this.sessions.slice(0, visibleCount); // pinned sort to top
692
+ // top border with label (includes focus/compact/sort mode tags)
693
+ const focusTag = this.focusMode ? "focus" : "";
631
694
  const sortTag = this.sortMode !== "default" ? this.sortMode : "";
632
695
  const compactTag = this.compactMode ? "compact" : "";
633
- const tags = [compactTag, sortTag].filter(Boolean).join(", ");
696
+ const tags = [focusTag, compactTag, sortTag].filter(Boolean).join(", ");
634
697
  const label = tags ? ` agents (${tags}) ` : " agents ";
635
698
  const borderAfterLabel = Math.max(0, innerWidth - label.length);
636
699
  const topBorder = `${SLATE}${BOX.rtl}${BOX.h}${RESET}${SLATE}${label}${RESET}${SLATE}${BOX.h.repeat(borderAfterLabel)}${BOX.rtr}${RESET}`;
637
700
  process.stderr.write(SAVE_CURSOR + moveTo(startRow, 1) + CLEAR_LINE + truncateAnsi(topBorder, this.cols));
638
- if (this.sessions.length === 0) {
701
+ if (visibleSessions.length === 0) {
639
702
  // empty state
640
- const empty = `${SLATE}${BOX.v}${RESET} ${DIM}no agents connected${RESET}`;
703
+ const msg = this.focusMode && this.sessions.length > 0
704
+ ? `${DIM}no pinned agents — /pin to add, /focus to exit${RESET}`
705
+ : `${DIM}no agents connected${RESET}`;
706
+ const empty = `${SLATE}${BOX.v}${RESET} ${msg}`;
641
707
  const padded = padBoxLine(empty, this.cols);
642
708
  process.stderr.write(moveTo(startRow + 1, 1) + CLEAR_LINE + padded);
643
709
  }
644
710
  else if (this.compactMode) {
645
711
  // compact: inline tokens, multiple per row (with pin indicators)
646
- const compactRows = formatCompactRows(this.sessions, innerWidth - 1, this.pinnedIds);
712
+ const compactRows = formatCompactRows(visibleSessions, innerWidth - 1, this.pinnedIds);
647
713
  for (let r = 0; r < compactRows.length; r++) {
648
714
  const line = `${SLATE}${BOX.v}${RESET} ${compactRows[r]}`;
649
715
  const padded = padBoxLine(line, this.cols);
@@ -651,8 +717,8 @@ export class TUI {
651
717
  }
652
718
  }
653
719
  else {
654
- for (let i = 0; i < this.sessions.length; i++) {
655
- const s = this.sessions[i];
720
+ for (let i = 0; i < visibleSessions.length; i++) {
721
+ const s = visibleSessions[i];
656
722
  const isHovered = this.hoverSessionIdx === i + 1; // 1-indexed
657
723
  const bg = isHovered ? BG_HOVER : "";
658
724
  const pinned = this.pinnedIds.has(s.id);
@@ -665,8 +731,8 @@ export class TUI {
665
731
  }
666
732
  // bottom border
667
733
  const bodyRows = this.compactMode
668
- ? computeCompactRowCount(this.sessions, innerWidth)
669
- : Math.max(this.sessions.length, 1);
734
+ ? computeCompactRowCount(visibleSessions, innerWidth)
735
+ : Math.max(visibleCount, 1);
670
736
  const bottomRow = startRow + 1 + bodyRows;
671
737
  const bottomBorder = `${SLATE}${BOX.rbl}${BOX.h.repeat(Math.max(0, this.cols - 2))}${BOX.rbr}${RESET}`;
672
738
  process.stderr.write(moveTo(bottomRow, 1) + CLEAR_LINE + truncateAnsi(bottomBorder, this.cols));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.80.0",
3
+ "version": "0.82.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",