aoaoe 0.84.0 → 0.85.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,37 @@ async function main() {
444
444
  tui.log("system", `session not found: ${target}`);
445
445
  }
446
446
  });
447
+ // wire /note set/clear
448
+ input.onNote((target, text) => {
449
+ const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
450
+ const ok = tui.setNote(num ?? target, text);
451
+ if (ok) {
452
+ if (text) {
453
+ tui.log("system", `note set for ${target}: "${text}"`);
454
+ }
455
+ else {
456
+ tui.log("system", `note cleared for ${target}`);
457
+ }
458
+ }
459
+ else {
460
+ tui.log("system", `session not found: ${target}`);
461
+ }
462
+ });
463
+ // wire /notes listing
464
+ input.onNotes(() => {
465
+ const notes = tui.getAllNotes();
466
+ if (notes.size === 0) {
467
+ tui.log("system", "no notes — use /note <N|name> <text> to add one");
468
+ }
469
+ else {
470
+ const sessions = tui.getSessions();
471
+ for (const [id, text] of notes) {
472
+ const session = sessions.find((s) => s.id === id);
473
+ const label = session ? session.title : id.slice(0, 8);
474
+ tui.log("system", ` ${label}: "${text}"`);
475
+ }
476
+ }
477
+ });
447
478
  // wire mouse move to hover highlight on session cards (disabled in compact)
448
479
  input.onMouseMove((row, _col) => {
449
480
  if (tui.getViewMode() === "overview" && !tui.isCompact()) {
package/dist/input.d.ts CHANGED
@@ -12,6 +12,8 @@ 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 NoteHandler = (target: string, text: string) => void;
16
+ export type NotesHandler = () => void;
15
17
  export interface MouseEvent {
16
18
  button: number;
17
19
  col: number;
@@ -46,6 +48,8 @@ export declare class InputReader {
46
48
  private jumpHandler;
47
49
  private marksHandler;
48
50
  private muteHandler;
51
+ private noteHandler;
52
+ private notesHandler;
49
53
  private mouseDataListener;
50
54
  onScroll(handler: (dir: ScrollDirection) => void): void;
51
55
  onQueueChange(handler: (count: number) => void): void;
@@ -64,6 +68,8 @@ export declare class InputReader {
64
68
  onJump(handler: JumpHandler): void;
65
69
  onMarks(handler: MarksHandler): void;
66
70
  onMute(handler: MuteHandler): void;
71
+ onNote(handler: NoteHandler): void;
72
+ onNotes(handler: NotesHandler): void;
67
73
  private notifyQueueChange;
68
74
  start(): void;
69
75
  drain(): string[];
package/dist/input.js CHANGED
@@ -44,6 +44,8 @@ export class InputReader {
44
44
  jumpHandler = null;
45
45
  marksHandler = null;
46
46
  muteHandler = null;
47
+ noteHandler = null;
48
+ notesHandler = null;
47
49
  mouseDataListener = null;
48
50
  // register a callback for scroll key events (PgUp/PgDn/Home/End)
49
51
  onScroll(handler) {
@@ -113,6 +115,14 @@ export class InputReader {
113
115
  onMute(handler) {
114
116
  this.muteHandler = handler;
115
117
  }
118
+ // register a callback for note commands (/note <target> <text>)
119
+ onNote(handler) {
120
+ this.noteHandler = handler;
121
+ }
122
+ // register a callback for listing notes (/notes)
123
+ onNotes(handler) {
124
+ this.notesHandler = handler;
125
+ }
116
126
  notifyQueueChange() {
117
127
  this.queueChangeHandler?.(this.queue.length);
118
128
  }
@@ -296,6 +306,8 @@ ${BOLD}navigation:${RESET}
296
306
  /bell toggle terminal bell on errors/completions
297
307
  /focus toggle focus mode (show only pinned sessions)
298
308
  /mute [N|name] mute/unmute a session's activity entries (toggle)
309
+ /note N|name text attach a note to a session (no text = clear)
310
+ /notes list all session notes
299
311
  /mark bookmark current activity position
300
312
  /jump N jump to bookmark N
301
313
  /marks list all bookmarks
@@ -442,6 +454,37 @@ ${BOLD}other:${RESET}
442
454
  }
443
455
  break;
444
456
  }
457
+ case "/note": {
458
+ const noteArg = line.slice("/note".length).trim();
459
+ if (this.noteHandler) {
460
+ // split: first word is target, rest is note text
461
+ const spaceIdx = noteArg.indexOf(" ");
462
+ if (spaceIdx > 0) {
463
+ const target = noteArg.slice(0, spaceIdx);
464
+ const text = noteArg.slice(spaceIdx + 1).trim();
465
+ this.noteHandler(target, text);
466
+ }
467
+ else if (noteArg) {
468
+ // target only, no text — clear note
469
+ this.noteHandler(noteArg, "");
470
+ }
471
+ else {
472
+ console.error(`${DIM}usage: /note <N|name> <text> — set note, or /note <N|name> — clear${RESET}`);
473
+ }
474
+ }
475
+ else {
476
+ console.error(`${DIM}notes not available (no TUI)${RESET}`);
477
+ }
478
+ break;
479
+ }
480
+ case "/notes":
481
+ if (this.notesHandler) {
482
+ this.notesHandler();
483
+ }
484
+ else {
485
+ console.error(`${DIM}notes not available (no TUI)${RESET}`);
486
+ }
487
+ break;
445
488
  case "/mark":
446
489
  if (this.markHandler) {
447
490
  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,6 +43,12 @@ 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;
48
54
  export declare class TUI {
@@ -75,6 +81,7 @@ export declare class TUI {
75
81
  private bellEnabled;
76
82
  private lastBellAt;
77
83
  private mutedIds;
84
+ private sessionNotes;
78
85
  private viewMode;
79
86
  private drilldownSessionId;
80
87
  private sessionOutputs;
@@ -129,6 +136,19 @@ export declare class TUI {
129
136
  isMuted(id: string): boolean;
130
137
  /** Return count of muted sessions. */
131
138
  getMutedCount(): number;
139
+ /**
140
+ * Set a note on a session (by 1-indexed number, ID, ID prefix, or title).
141
+ * Returns true if session found. Pass empty text to clear.
142
+ */
143
+ setNote(sessionIdOrIndex: string | number, text: string): boolean;
144
+ /** Get the note for a session ID (or undefined if none). */
145
+ getNote(id: string): string | undefined;
146
+ /** Return count of sessions with notes. */
147
+ getNoteCount(): number;
148
+ /** Return all session notes (for /notes listing). */
149
+ getAllNotes(): ReadonlyMap<string, string>;
150
+ /** Return the current sessions (read-only, for resolving IDs to titles in the UI). */
151
+ getSessions(): readonly DaemonSessionState[];
132
152
  /**
133
153
  * Add a bookmark at the current activity position.
134
154
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
@@ -233,5 +253,5 @@ declare function formatSearchIndicator(pattern: string, matchCount: number, tota
233
253
  * (row = headerHeight + 2 + i for 0-indexed session i)
234
254
  */
235
255
  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 };
256
+ 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
257
  //# 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,6 +159,15 @@ 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)
@@ -194,6 +205,7 @@ export class TUI {
194
205
  bellEnabled = false;
195
206
  lastBellAt = 0;
196
207
  mutedIds = new Set(); // muted session IDs (activity entries hidden)
208
+ sessionNotes = new Map(); // session ID → note text
197
209
  // drill-down mode: show a single session's full output
198
210
  viewMode = "overview";
199
211
  drilldownSessionId = null;
@@ -387,6 +399,52 @@ export class TUI {
387
399
  getMutedCount() {
388
400
  return this.mutedIds.size;
389
401
  }
402
+ /**
403
+ * Set a note on a session (by 1-indexed number, ID, ID prefix, or title).
404
+ * Returns true if session found. Pass empty text to clear.
405
+ */
406
+ setNote(sessionIdOrIndex, text) {
407
+ let sessionId;
408
+ if (typeof sessionIdOrIndex === "number") {
409
+ sessionId = this.sessions[sessionIdOrIndex - 1]?.id;
410
+ }
411
+ else {
412
+ const needle = sessionIdOrIndex.toLowerCase();
413
+ const match = this.sessions.find((s) => s.id === sessionIdOrIndex || s.id.startsWith(needle) || s.title.toLowerCase() === needle);
414
+ sessionId = match?.id;
415
+ }
416
+ if (!sessionId)
417
+ return false;
418
+ if (text.trim() === "") {
419
+ this.sessionNotes.delete(sessionId);
420
+ }
421
+ else {
422
+ this.sessionNotes.set(sessionId, truncateNote(text.trim()));
423
+ }
424
+ if (this.active) {
425
+ this.paintSessions();
426
+ if (this.viewMode === "drilldown" && this.drilldownSessionId === sessionId) {
427
+ this.paintDrilldownSeparator();
428
+ }
429
+ }
430
+ return true;
431
+ }
432
+ /** Get the note for a session ID (or undefined if none). */
433
+ getNote(id) {
434
+ return this.sessionNotes.get(id);
435
+ }
436
+ /** Return count of sessions with notes. */
437
+ getNoteCount() {
438
+ return this.sessionNotes.size;
439
+ }
440
+ /** Return all session notes (for /notes listing). */
441
+ getAllNotes() {
442
+ return this.sessionNotes;
443
+ }
444
+ /** Return the current sessions (read-only, for resolving IDs to titles in the UI). */
445
+ getSessions() {
446
+ return this.sessions;
447
+ }
390
448
  /**
391
449
  * Add a bookmark at the current activity position.
392
450
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
@@ -837,7 +895,8 @@ export class TUI {
837
895
  }
838
896
  else if (this.compactMode) {
839
897
  // compact: inline tokens, multiple per row (with pin indicators)
840
- const compactRows = formatCompactRows(visibleSessions, innerWidth - 1, this.pinnedIds, this.mutedIds);
898
+ const noteIdSet = new Set(this.sessionNotes.keys());
899
+ const compactRows = formatCompactRows(visibleSessions, innerWidth - 1, this.pinnedIds, this.mutedIds, noteIdSet);
841
900
  for (let r = 0; r < compactRows.length; r++) {
842
901
  const line = `${SLATE}${BOX.v}${RESET} ${compactRows[r]}`;
843
902
  const padded = padBoxLine(line, this.cols);
@@ -851,11 +910,13 @@ export class TUI {
851
910
  const bg = isHovered ? BG_HOVER : "";
852
911
  const pinned = this.pinnedIds.has(s.id);
853
912
  const muted = this.mutedIds.has(s.id);
913
+ const noted = this.sessionNotes.has(s.id);
854
914
  const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
855
915
  const mute = muted ? `${DIM}${MUTE_ICON}${RESET} ` : "";
856
- const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0); // each icon + space = 2 chars
916
+ const note = noted ? `${TEAL}${NOTE_ICON}${RESET} ` : "";
917
+ const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0) + (noted ? 2 : 0);
857
918
  const cardWidth = innerWidth - 1 - iconsWidth;
858
- const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${formatSessionCard(s, cardWidth)}`;
919
+ const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${note}${formatSessionCard(s, cardWidth)}`;
859
920
  const padded = padBoxLineHover(line, this.cols, isHovered);
860
921
  process.stderr.write(moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded);
861
922
  }
@@ -887,11 +948,13 @@ export class TUI {
887
948
  const bg = isHovered ? BG_HOVER : "";
888
949
  const pinned = this.pinnedIds.has(s.id);
889
950
  const muted = this.mutedIds.has(s.id);
951
+ const noted = this.sessionNotes.has(s.id);
890
952
  const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
891
953
  const mute = muted ? `${DIM}${MUTE_ICON}${RESET} ` : "";
892
- const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0);
954
+ const note = noted ? `${TEAL}${NOTE_ICON}${RESET} ` : "";
955
+ const iconsWidth = (pinned ? 2 : 0) + (muted ? 2 : 0) + (noted ? 2 : 0);
893
956
  const cardWidth = innerWidth - 1 - iconsWidth;
894
- const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${formatSessionCard(s, cardWidth)}`;
957
+ const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${mute}${note}${formatSessionCard(s, cardWidth)}`;
895
958
  const padded = padBoxLineHover(line, this.cols, isHovered);
896
959
  process.stderr.write(SAVE_CURSOR + moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded + RESTORE_CURSOR);
897
960
  }
@@ -952,7 +1015,9 @@ export class TUI {
952
1015
  paintDrilldownSeparator() {
953
1016
  const session = this.sessions.find((s) => s.id === this.drilldownSessionId);
954
1017
  const title = session ? session.title : this.drilldownSessionId ?? "?";
955
- const prefix = `${BOX.h}${BOX.h} ${title} `;
1018
+ const noteText = this.drilldownSessionId ? this.sessionNotes.get(this.drilldownSessionId) : undefined;
1019
+ const noteSuffix = noteText ? `"${noteText}" ` : "";
1020
+ const prefix = `${BOX.h}${BOX.h} ${title} ${noteSuffix}`;
956
1021
  let hints;
957
1022
  if (this.drilldownScrollOffset > 0) {
958
1023
  const outputLines = this.sessionOutputs.get(this.drilldownSessionId ?? "") ?? [];
@@ -1261,5 +1326,5 @@ export function hitTestSession(row, headerHeight, sessionCount) {
1261
1326
  return row - firstSessionRow + 1; // 1-indexed
1262
1327
  }
1263
1328
  // ── 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 };
1329
+ 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
1330
  //# 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.85.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",