aoaoe 0.84.0 → 0.86.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
@@ -444,6 +444,47 @@ async function main() {
444
444
  tui.log("system", `session not found: ${target}`);
445
445
  }
446
446
  });
447
+ // wire /unmute-all
448
+ input.onUnmuteAll(() => {
449
+ const count = tui.unmuteAll();
450
+ if (count > 0) {
451
+ tui.log("system", `unmuted ${count} session${count === 1 ? "" : "s"}`);
452
+ }
453
+ else {
454
+ tui.log("system", "no sessions are muted");
455
+ }
456
+ });
457
+ // wire /note set/clear
458
+ input.onNote((target, text) => {
459
+ const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
460
+ const ok = tui.setNote(num ?? target, text);
461
+ if (ok) {
462
+ if (text) {
463
+ tui.log("system", `note set for ${target}: "${text}"`);
464
+ }
465
+ else {
466
+ tui.log("system", `note cleared for ${target}`);
467
+ }
468
+ }
469
+ else {
470
+ tui.log("system", `session not found: ${target}`);
471
+ }
472
+ });
473
+ // wire /notes listing
474
+ input.onNotes(() => {
475
+ const notes = tui.getAllNotes();
476
+ if (notes.size === 0) {
477
+ tui.log("system", "no notes — use /note <N|name> <text> to add one");
478
+ }
479
+ else {
480
+ const sessions = tui.getSessions();
481
+ for (const [id, text] of notes) {
482
+ const session = sessions.find((s) => s.id === id);
483
+ const label = session ? session.title : id.slice(0, 8);
484
+ tui.log("system", ` ${label}: "${text}"`);
485
+ }
486
+ }
487
+ });
447
488
  // wire mouse move to hover highlight on session cards (disabled in compact)
448
489
  input.onMouseMove((row, _col) => {
449
490
  if (tui.getViewMode() === "overview" && !tui.isCompact()) {
package/dist/input.d.ts CHANGED
@@ -12,6 +12,9 @@ export type MarkHandler = () => void;
12
12
  export type JumpHandler = (num: number) => void;
13
13
  export type MarksHandler = () => void;
14
14
  export type MuteHandler = (target: string) => void;
15
+ export type UnmuteAllHandler = () => void;
16
+ export type NoteHandler = (target: string, text: string) => void;
17
+ export type NotesHandler = () => void;
15
18
  export interface MouseEvent {
16
19
  button: number;
17
20
  col: number;
@@ -46,6 +49,9 @@ export declare class InputReader {
46
49
  private jumpHandler;
47
50
  private marksHandler;
48
51
  private muteHandler;
52
+ private unmuteAllHandler;
53
+ private noteHandler;
54
+ private notesHandler;
49
55
  private mouseDataListener;
50
56
  onScroll(handler: (dir: ScrollDirection) => void): void;
51
57
  onQueueChange(handler: (count: number) => void): void;
@@ -64,6 +70,9 @@ export declare class InputReader {
64
70
  onJump(handler: JumpHandler): void;
65
71
  onMarks(handler: MarksHandler): void;
66
72
  onMute(handler: MuteHandler): void;
73
+ onUnmuteAll(handler: UnmuteAllHandler): void;
74
+ onNote(handler: NoteHandler): void;
75
+ onNotes(handler: NotesHandler): void;
67
76
  private notifyQueueChange;
68
77
  start(): void;
69
78
  drain(): string[];
package/dist/input.js CHANGED
@@ -44,6 +44,9 @@ export class InputReader {
44
44
  jumpHandler = null;
45
45
  marksHandler = null;
46
46
  muteHandler = null;
47
+ unmuteAllHandler = null;
48
+ noteHandler = null;
49
+ notesHandler = null;
47
50
  mouseDataListener = null;
48
51
  // register a callback for scroll key events (PgUp/PgDn/Home/End)
49
52
  onScroll(handler) {
@@ -113,6 +116,18 @@ export class InputReader {
113
116
  onMute(handler) {
114
117
  this.muteHandler = handler;
115
118
  }
119
+ // register a callback for unmuting all sessions (/unmute-all)
120
+ onUnmuteAll(handler) {
121
+ this.unmuteAllHandler = handler;
122
+ }
123
+ // register a callback for note commands (/note <target> <text>)
124
+ onNote(handler) {
125
+ this.noteHandler = handler;
126
+ }
127
+ // register a callback for listing notes (/notes)
128
+ onNotes(handler) {
129
+ this.notesHandler = handler;
130
+ }
116
131
  notifyQueueChange() {
117
132
  this.queueChangeHandler?.(this.queue.length);
118
133
  }
@@ -296,6 +311,9 @@ ${BOLD}navigation:${RESET}
296
311
  /bell toggle terminal bell on errors/completions
297
312
  /focus toggle focus mode (show only pinned sessions)
298
313
  /mute [N|name] mute/unmute a session's activity entries (toggle)
314
+ /unmute-all unmute all sessions at once
315
+ /note N|name text attach a note to a session (no text = clear)
316
+ /notes list all session notes
299
317
  /mark bookmark current activity position
300
318
  /jump N jump to bookmark N
301
319
  /marks list all bookmarks
@@ -442,6 +460,45 @@ ${BOLD}other:${RESET}
442
460
  }
443
461
  break;
444
462
  }
463
+ case "/unmute-all":
464
+ if (this.unmuteAllHandler) {
465
+ this.unmuteAllHandler();
466
+ }
467
+ else {
468
+ console.error(`${DIM}unmute-all not available (no TUI)${RESET}`);
469
+ }
470
+ break;
471
+ case "/note": {
472
+ const noteArg = line.slice("/note".length).trim();
473
+ if (this.noteHandler) {
474
+ // split: first word is target, rest is note text
475
+ const spaceIdx = noteArg.indexOf(" ");
476
+ if (spaceIdx > 0) {
477
+ const target = noteArg.slice(0, spaceIdx);
478
+ const text = noteArg.slice(spaceIdx + 1).trim();
479
+ this.noteHandler(target, text);
480
+ }
481
+ else if (noteArg) {
482
+ // target only, no text — clear note
483
+ this.noteHandler(noteArg, "");
484
+ }
485
+ else {
486
+ console.error(`${DIM}usage: /note <N|name> <text> — set note, or /note <N|name> — clear${RESET}`);
487
+ }
488
+ }
489
+ else {
490
+ console.error(`${DIM}notes not available (no TUI)${RESET}`);
491
+ }
492
+ break;
493
+ }
494
+ case "/notes":
495
+ if (this.notesHandler) {
496
+ this.notesHandler();
497
+ }
498
+ else {
499
+ console.error(`${DIM}notes not available (no TUI)${RESET}`);
500
+ }
501
+ break;
445
502
  case "/mark":
446
503
  if (this.markHandler) {
447
504
  this.markHandler();
package/dist/tui.d.ts CHANGED
@@ -31,7 +31,7 @@ declare const PIN_ICON = "\u25B2";
31
31
  * Each token: "{idx}{pin?}{mute?}{dot}{name}" — e.g. "1▲●Alpha" for pinned, "2◌●Bravo" for muted.
32
32
  * Returns array of formatted row strings (one per display row).
33
33
  */
34
- declare function formatCompactRows(sessions: DaemonSessionState[], maxWidth: number, pinnedIds?: Set<string>, mutedIds?: Set<string>): string[];
34
+ declare function formatCompactRows(sessions: DaemonSessionState[], maxWidth: number, pinnedIds?: Set<string>, mutedIds?: Set<string>, noteIds?: Set<string>): string[];
35
35
  /** Compute how many display rows compact mode needs (minimum 1). */
36
36
  declare function computeCompactRowCount(sessions: DaemonSessionState[], maxWidth: number): number;
37
37
  declare function phaseDisplay(phase: DaemonPhase, paused: boolean, spinnerFrame: number): string;
@@ -43,8 +43,16 @@ export interface ActivityEntry {
43
43
  }
44
44
  /** Mute indicator for muted sessions (shown dim beside session card). */
45
45
  declare const MUTE_ICON = "\u25CC";
46
+ /** Note indicator for sessions with notes. */
47
+ declare const NOTE_ICON = "\u270E";
48
+ /** Max length for a session note (visible chars). */
49
+ export declare const MAX_NOTE_LEN = 80;
50
+ /** Truncate a note to the max length. */
51
+ export declare function truncateNote(text: string): string;
46
52
  /** Determine if an activity entry should be hidden due to muting. */
47
53
  export declare function shouldMuteEntry(entry: ActivityEntry, mutedIds: Set<string>): boolean;
54
+ /** Format a suppressed entry count badge for muted sessions. Returns empty string for 0. */
55
+ export declare function formatMuteBadge(count: number): string;
48
56
  export declare class TUI {
49
57
  private active;
50
58
  private countdownTimer;
@@ -75,6 +83,8 @@ export declare class TUI {
75
83
  private bellEnabled;
76
84
  private lastBellAt;
77
85
  private mutedIds;
86
+ private mutedEntryCounts;
87
+ private sessionNotes;
78
88
  private viewMode;
79
89
  private drilldownSessionId;
80
90
  private sessionOutputs;
@@ -125,10 +135,27 @@ export declare class TUI {
125
135
  * Returns true if session found.
126
136
  */
127
137
  toggleMute(sessionIdOrIndex: string | number): boolean;
138
+ /** Unmute all sessions at once. Returns count of sessions unmuted. */
139
+ unmuteAll(): number;
128
140
  /** Check if a session ID is muted. */
129
141
  isMuted(id: string): boolean;
130
142
  /** Return count of muted sessions. */
131
143
  getMutedCount(): number;
144
+ /** Return count of suppressed entries for a muted session (0 if not muted). */
145
+ getMutedEntryCount(id: string): number;
146
+ /**
147
+ * Set a note on a session (by 1-indexed number, ID, ID prefix, or title).
148
+ * Returns true if session found. Pass empty text to clear.
149
+ */
150
+ setNote(sessionIdOrIndex: string | number, text: string): boolean;
151
+ /** Get the note for a session ID (or undefined if none). */
152
+ getNote(id: string): string | undefined;
153
+ /** Return count of sessions with notes. */
154
+ getNoteCount(): number;
155
+ /** Return all session notes (for /notes listing). */
156
+ getAllNotes(): ReadonlyMap<string, string>;
157
+ /** Return the current sessions (read-only, for resolving IDs to titles in the UI). */
158
+ getSessions(): readonly DaemonSessionState[];
132
159
  /**
133
160
  * Add a bookmark at the current activity position.
134
161
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
@@ -233,5 +260,5 @@ declare function formatSearchIndicator(pattern: string, matchCount: number, tota
233
260
  * (row = headerHeight + 2 + i for 0-indexed session i)
234
261
  */
235
262
  export declare function hitTestSession(row: number, headerHeight: number, sessionCount: number): number | null;
236
- export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN, PIN_ICON, MUTE_ICON };
263
+ export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN, PIN_ICON, MUTE_ICON, NOTE_ICON };
237
264
  //# sourceMappingURL=tui.d.ts.map
package/dist/tui.js CHANGED
@@ -90,7 +90,7 @@ const PIN_ICON = "▲";
90
90
  * Each token: "{idx}{pin?}{mute?}{dot}{name}" — e.g. "1▲●Alpha" for pinned, "2◌●Bravo" for muted.
91
91
  * Returns array of formatted row strings (one per display row).
92
92
  */
93
- function formatCompactRows(sessions, maxWidth, pinnedIds, mutedIds) {
93
+ function formatCompactRows(sessions, maxWidth, pinnedIds, mutedIds, noteIds) {
94
94
  if (sessions.length === 0)
95
95
  return [`${DIM}no agents connected${RESET}`];
96
96
  const tokens = [];
@@ -101,11 +101,13 @@ function formatCompactRows(sessions, maxWidth, pinnedIds, mutedIds) {
101
101
  const dot = STATUS_DOT[s.status] ?? `${AMBER}${DOT.filled}${RESET}`;
102
102
  const pinned = pinnedIds?.has(s.id) ?? false;
103
103
  const muted = mutedIds?.has(s.id) ?? false;
104
+ const noted = noteIds?.has(s.id) ?? false;
104
105
  const pin = pinned ? `${AMBER}${PIN_ICON}${RESET}` : "";
105
106
  const muteIcon = muted ? `${DIM}${MUTE_ICON}${RESET}` : "";
107
+ const noteIcon = noted ? `${TEAL}${NOTE_ICON}${RESET}` : "";
106
108
  const name = truncatePlain(s.title, COMPACT_NAME_LEN);
107
- tokens.push(`${SLATE}${idx}${RESET}${pin}${muteIcon}${dot}${BOLD}${name}${RESET}`);
108
- widths.push(idx.length + (pinned ? 1 : 0) + (muted ? 1 : 0) + 1 + name.length);
109
+ tokens.push(`${SLATE}${idx}${RESET}${pin}${muteIcon}${noteIcon}${dot}${BOLD}${name}${RESET}`);
110
+ widths.push(idx.length + (pinned ? 1 : 0) + (muted ? 1 : 0) + (noted ? 1 : 0) + 1 + name.length);
109
111
  }
110
112
  const rows = [];
111
113
  let currentRow = "";
@@ -157,12 +159,28 @@ function phaseDisplay(phase, paused, spinnerFrame) {
157
159
  // ── Mute helpers ──────────────────────────────────────────────────────────────
158
160
  /** Mute indicator for muted sessions (shown dim beside session card). */
159
161
  const MUTE_ICON = "◌";
162
+ // ── Notes ─────────────────────────────────────────────────────────────────────
163
+ /** Note indicator for sessions with notes. */
164
+ const NOTE_ICON = "✎";
165
+ /** Max length for a session note (visible chars). */
166
+ export const MAX_NOTE_LEN = 80;
167
+ /** Truncate a note to the max length. */
168
+ export function truncateNote(text) {
169
+ return text.length > MAX_NOTE_LEN ? text.slice(0, MAX_NOTE_LEN - 2) + ".." : text;
170
+ }
160
171
  /** Determine if an activity entry should be hidden due to muting. */
161
172
  export function shouldMuteEntry(entry, mutedIds) {
162
173
  if (!entry.sessionId)
163
174
  return false;
164
175
  return mutedIds.has(entry.sessionId);
165
176
  }
177
+ /** Format a suppressed entry count badge for muted sessions. Returns empty string for 0. */
178
+ export function formatMuteBadge(count) {
179
+ if (count <= 0)
180
+ return "";
181
+ const label = count > 999 ? "999+" : String(count);
182
+ return `${DIM}(${label})${RESET}`;
183
+ }
166
184
  // ── TUI class ───────────────────────────────────────────────────────────────
167
185
  export class TUI {
168
186
  active = false;
@@ -194,6 +212,8 @@ export class TUI {
194
212
  bellEnabled = false;
195
213
  lastBellAt = 0;
196
214
  mutedIds = new Set(); // muted session IDs (activity entries hidden)
215
+ mutedEntryCounts = new Map(); // session ID → suppressed entry count since mute
216
+ sessionNotes = new Map(); // session ID → note text
197
217
  // drill-down mode: show a single session's full output
198
218
  viewMode = "overview";
199
219
  drilldownSessionId = null;
@@ -368,9 +388,11 @@ export class TUI {
368
388
  return false;
369
389
  if (this.mutedIds.has(sessionId)) {
370
390
  this.mutedIds.delete(sessionId);
391
+ this.mutedEntryCounts.delete(sessionId);
371
392
  }
372
393
  else {
373
394
  this.mutedIds.add(sessionId);
395
+ this.mutedEntryCounts.set(sessionId, 0);
374
396
  }
375
397
  // repaint sessions (mute icon) and activity (filter changes)
376
398
  if (this.active) {
@@ -379,6 +401,19 @@ export class TUI {
379
401
  }
380
402
  return true;
381
403
  }
404
+ /** Unmute all sessions at once. Returns count of sessions unmuted. */
405
+ unmuteAll() {
406
+ const count = this.mutedIds.size;
407
+ if (count === 0)
408
+ return 0;
409
+ this.mutedIds.clear();
410
+ this.mutedEntryCounts.clear();
411
+ if (this.active) {
412
+ this.paintSessions();
413
+ this.repaintActivityRegion();
414
+ }
415
+ return count;
416
+ }
382
417
  /** Check if a session ID is muted. */
383
418
  isMuted(id) {
384
419
  return this.mutedIds.has(id);
@@ -387,6 +422,56 @@ export class TUI {
387
422
  getMutedCount() {
388
423
  return this.mutedIds.size;
389
424
  }
425
+ /** Return count of suppressed entries for a muted session (0 if not muted). */
426
+ getMutedEntryCount(id) {
427
+ return this.mutedEntryCounts.get(id) ?? 0;
428
+ }
429
+ /**
430
+ * Set a note on a session (by 1-indexed number, ID, ID prefix, or title).
431
+ * Returns true if session found. Pass empty text to clear.
432
+ */
433
+ setNote(sessionIdOrIndex, text) {
434
+ let sessionId;
435
+ if (typeof sessionIdOrIndex === "number") {
436
+ sessionId = this.sessions[sessionIdOrIndex - 1]?.id;
437
+ }
438
+ else {
439
+ const needle = sessionIdOrIndex.toLowerCase();
440
+ const match = this.sessions.find((s) => s.id === sessionIdOrIndex || s.id.startsWith(needle) || s.title.toLowerCase() === needle);
441
+ sessionId = match?.id;
442
+ }
443
+ if (!sessionId)
444
+ return false;
445
+ if (text.trim() === "") {
446
+ this.sessionNotes.delete(sessionId);
447
+ }
448
+ else {
449
+ this.sessionNotes.set(sessionId, truncateNote(text.trim()));
450
+ }
451
+ if (this.active) {
452
+ this.paintSessions();
453
+ if (this.viewMode === "drilldown" && this.drilldownSessionId === sessionId) {
454
+ this.paintDrilldownSeparator();
455
+ }
456
+ }
457
+ return true;
458
+ }
459
+ /** Get the note for a session ID (or undefined if none). */
460
+ getNote(id) {
461
+ return this.sessionNotes.get(id);
462
+ }
463
+ /** Return count of sessions with notes. */
464
+ getNoteCount() {
465
+ return this.sessionNotes.size;
466
+ }
467
+ /** Return all session notes (for /notes listing). */
468
+ getAllNotes() {
469
+ return this.sessionNotes;
470
+ }
471
+ /** Return the current sessions (read-only, for resolving IDs to titles in the UI). */
472
+ getSessions() {
473
+ return this.sessions;
474
+ }
390
475
  /**
391
476
  * Add a bookmark at the current activity position.
392
477
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
@@ -499,6 +584,10 @@ export class TUI {
499
584
  process.stderr.write("\x07");
500
585
  }
501
586
  }
587
+ // track suppressed entry counts regardless of active state (for badge accuracy)
588
+ if (shouldMuteEntry(entry, this.mutedIds) && entry.sessionId) {
589
+ this.mutedEntryCounts.set(entry.sessionId, (this.mutedEntryCounts.get(entry.sessionId) ?? 0) + 1);
590
+ }
502
591
  if (this.active) {
503
592
  // muted entries are still buffered + persisted but hidden from display
504
593
  if (shouldMuteEntry(entry, this.mutedIds)) {
@@ -837,7 +926,8 @@ export class TUI {
837
926
  }
838
927
  else if (this.compactMode) {
839
928
  // compact: inline tokens, multiple per row (with pin indicators)
840
- const compactRows = formatCompactRows(visibleSessions, innerWidth - 1, this.pinnedIds, this.mutedIds);
929
+ const noteIdSet = new Set(this.sessionNotes.keys());
930
+ const compactRows = formatCompactRows(visibleSessions, innerWidth - 1, this.pinnedIds, this.mutedIds, noteIdSet);
841
931
  for (let r = 0; r < compactRows.length; r++) {
842
932
  const line = `${SLATE}${BOX.v}${RESET} ${compactRows[r]}`;
843
933
  const padded = padBoxLine(line, this.cols);
@@ -851,11 +941,17 @@ export class TUI {
851
941
  const bg = isHovered ? BG_HOVER : "";
852
942
  const pinned = this.pinnedIds.has(s.id);
853
943
  const muted = this.mutedIds.has(s.id);
944
+ const noted = this.sessionNotes.has(s.id);
945
+ const muteBadge = muted ? formatMuteBadge(this.mutedEntryCounts.get(s.id) ?? 0) : "";
946
+ const muteBadgeWidth = muted ? String(Math.min(this.mutedEntryCounts.get(s.id) ?? 0, 9999)).length + 2 : 0; // "(N)" visible chars, 0 when count is 0
947
+ const actualBadgeWidth = (this.mutedEntryCounts.get(s.id) ?? 0) > 0 ? muteBadgeWidth + 1 : 0; // +1 for trailing space
854
948
  const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
855
949
  const mute = muted ? `${DIM}${MUTE_ICON}${RESET} ` : "";
856
- const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0); // each icon + space = 2 chars
950
+ const note = noted ? `${TEAL}${NOTE_ICON}${RESET} ` : "";
951
+ const badgeSuffix = muteBadge ? `${muteBadge} ` : "";
952
+ const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0) + (noted ? 2 : 0) + actualBadgeWidth;
857
953
  const cardWidth = innerWidth - 1 - iconsWidth;
858
- const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${formatSessionCard(s, cardWidth)}`;
954
+ const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${badgeSuffix}${note}${formatSessionCard(s, cardWidth)}`;
859
955
  const padded = padBoxLineHover(line, this.cols, isHovered);
860
956
  process.stderr.write(moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded);
861
957
  }
@@ -887,11 +983,17 @@ export class TUI {
887
983
  const bg = isHovered ? BG_HOVER : "";
888
984
  const pinned = this.pinnedIds.has(s.id);
889
985
  const muted = this.mutedIds.has(s.id);
986
+ const noted = this.sessionNotes.has(s.id);
987
+ const muteBadge = muted ? formatMuteBadge(this.mutedEntryCounts.get(s.id) ?? 0) : "";
988
+ const actualBadgeWidth = (this.mutedEntryCounts.get(s.id) ?? 0) > 0
989
+ ? String(Math.min(this.mutedEntryCounts.get(s.id) ?? 0, 9999)).length + 3 : 0; // "(N) " visible chars
890
990
  const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
891
991
  const mute = muted ? `${DIM}${MUTE_ICON}${RESET} ` : "";
892
- const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0);
992
+ const note = noted ? `${TEAL}${NOTE_ICON}${RESET} ` : "";
993
+ const badgeSuffix = muteBadge ? `${muteBadge} ` : "";
994
+ const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0) + (noted ? 2 : 0) + actualBadgeWidth;
893
995
  const cardWidth = innerWidth - 1 - iconsWidth;
894
- const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${formatSessionCard(s, cardWidth)}`;
996
+ const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${badgeSuffix}${note}${formatSessionCard(s, cardWidth)}`;
895
997
  const padded = padBoxLineHover(line, this.cols, isHovered);
896
998
  process.stderr.write(SAVE_CURSOR + moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded + RESTORE_CURSOR);
897
999
  }
@@ -952,7 +1054,9 @@ export class TUI {
952
1054
  paintDrilldownSeparator() {
953
1055
  const session = this.sessions.find((s) => s.id === this.drilldownSessionId);
954
1056
  const title = session ? session.title : this.drilldownSessionId ?? "?";
955
- const prefix = `${BOX.h}${BOX.h} ${title} `;
1057
+ const noteText = this.drilldownSessionId ? this.sessionNotes.get(this.drilldownSessionId) : undefined;
1058
+ const noteSuffix = noteText ? `"${noteText}" ` : "";
1059
+ const prefix = `${BOX.h}${BOX.h} ${title} ${noteSuffix}`;
956
1060
  let hints;
957
1061
  if (this.drilldownScrollOffset > 0) {
958
1062
  const outputLines = this.sessionOutputs.get(this.drilldownSessionId ?? "") ?? [];
@@ -1261,5 +1365,5 @@ export function hitTestSession(row, headerHeight, sessionCount) {
1261
1365
  return row - firstSessionRow + 1; // 1-indexed
1262
1366
  }
1263
1367
  // ── Exported pure helpers (for testing) ─────────────────────────────────────
1264
- export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN, PIN_ICON, MUTE_ICON };
1368
+ export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN, PIN_ICON, MUTE_ICON, NOTE_ICON };
1265
1369
  //# sourceMappingURL=tui.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.84.0",
3
+ "version": "0.86.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",