aoaoe 0.93.0 → 0.95.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
@@ -522,6 +522,36 @@ async function main() {
522
522
  }
523
523
  persistPrefs();
524
524
  });
525
+ // wire /who fleet status
526
+ input.onWho(() => {
527
+ const sessions = tui.getSessions();
528
+ if (sessions.length === 0) {
529
+ tui.log("system", "no sessions");
530
+ return;
531
+ }
532
+ const firstSeen = tui.getAllFirstSeen();
533
+ const errors = tui.getSessionErrorCounts();
534
+ const notes = tui.getAllNotes();
535
+ // sort: errors first, then status priority, then alphabetical
536
+ const statusPriority = { error: 0, waiting: 1, working: 2, running: 2, idle: 3, stopped: 4, done: 5 };
537
+ const sorted = [...sessions].sort((a, b) => {
538
+ const ea = errors.get(a.id) ?? 0, eb = errors.get(b.id) ?? 0;
539
+ if (ea !== eb)
540
+ return eb - ea; // most errors first
541
+ const pa = statusPriority[a.status] ?? 3, pb = statusPriority[b.status] ?? 3;
542
+ if (pa !== pb)
543
+ return pa - pb;
544
+ return a.title.localeCompare(b.title);
545
+ });
546
+ for (const s of sorted) {
547
+ const up = firstSeen.has(s.id) ? formatUptime(Date.now() - firstSeen.get(s.id)) : "?";
548
+ const errCount = errors.get(s.id) ?? 0;
549
+ const errStr = errCount > 0 ? ` ${errCount}err` : "";
550
+ const note = notes.get(s.id);
551
+ const noteStr = note ? ` "${note}"` : "";
552
+ tui.log("system", ` ${s.title} — ${s.status} ${up}${errStr}${noteStr}`);
553
+ }
554
+ });
525
555
  // wire /uptime listing
526
556
  input.onUptime(() => {
527
557
  const firstSeen = tui.getAllFirstSeen();
package/dist/input.d.ts CHANGED
@@ -20,6 +20,7 @@ export type NoteHandler = (target: string, text: string) => void;
20
20
  export type NotesHandler = () => void;
21
21
  export type ClipHandler = (count: number) => void;
22
22
  export type DiffHandler = (bookmarkNum: number) => void;
23
+ export type WhoHandler = () => void;
23
24
  export interface MouseEvent {
24
25
  button: number;
25
26
  col: number;
@@ -62,6 +63,7 @@ export declare class InputReader {
62
63
  private notesHandler;
63
64
  private clipHandler;
64
65
  private diffHandler;
66
+ private whoHandler;
65
67
  private mouseDataListener;
66
68
  onScroll(handler: (dir: ScrollDirection) => void): void;
67
69
  onQueueChange(handler: (count: number) => void): void;
@@ -86,6 +88,7 @@ export declare class InputReader {
86
88
  onAutoPin(handler: AutoPinHandler): void;
87
89
  onNote(handler: NoteHandler): void;
88
90
  onNotes(handler: NotesHandler): void;
91
+ onWho(handler: WhoHandler): void;
89
92
  onClip(handler: ClipHandler): void;
90
93
  onDiff(handler: DiffHandler): void;
91
94
  private notifyQueueChange;
package/dist/input.js CHANGED
@@ -52,6 +52,7 @@ export class InputReader {
52
52
  notesHandler = null;
53
53
  clipHandler = null;
54
54
  diffHandler = null;
55
+ whoHandler = null;
55
56
  mouseDataListener = null;
56
57
  // register a callback for scroll key events (PgUp/PgDn/Home/End)
57
58
  onScroll(handler) {
@@ -145,6 +146,10 @@ export class InputReader {
145
146
  onNotes(handler) {
146
147
  this.notesHandler = handler;
147
148
  }
149
+ // register a callback for fleet status (/who)
150
+ onWho(handler) {
151
+ this.whoHandler = handler;
152
+ }
148
153
  // register a callback for clipboard export (/clip [N])
149
154
  onClip(handler) {
150
155
  this.clipHandler = handler;
@@ -338,6 +343,7 @@ ${BOLD}navigation:${RESET}
338
343
  /mute [N|name] mute/unmute a session's activity entries (toggle)
339
344
  /unmute-all unmute all sessions at once
340
345
  /filter [tag] filter activity by tag — presets: errors, actions, system (no arg = clear)
346
+ /who show fleet status (all sessions at a glance)
341
347
  /uptime show session uptimes (time since first observed)
342
348
  /auto-pin toggle auto-pin on error (pin sessions that emit errors)
343
349
  /note N|name text attach a note to a session (no text = clear)
@@ -508,6 +514,14 @@ ${BOLD}other:${RESET}
508
514
  }
509
515
  break;
510
516
  }
517
+ case "/who":
518
+ if (this.whoHandler) {
519
+ this.whoHandler();
520
+ }
521
+ else {
522
+ console.error(`${DIM}who not available (no TUI)${RESET}`);
523
+ }
524
+ break;
511
525
  case "/uptime":
512
526
  if (this.uptimeHandler) {
513
527
  this.uptimeHandler();
package/dist/stats.d.ts CHANGED
@@ -15,6 +15,7 @@ export interface ActionStats {
15
15
  export interface HistoryStats {
16
16
  total: number;
17
17
  byTag: Map<string, number>;
18
+ byHour: number[];
18
19
  firstTs: number;
19
20
  lastTs: number;
20
21
  }
@@ -51,4 +52,6 @@ export declare function formatRate(count: number, spanMs: number): string;
51
52
  * Format the full stats display for terminal output.
52
53
  */
53
54
  export declare function formatStats(stats: CombinedStats, windowLabel?: string): string;
55
+ /** Format a 24-element heatmap as a colored block string. */
56
+ export declare function formatHeatmap(counts: number[]): string;
54
57
  //# sourceMappingURL=stats.d.ts.map
package/dist/stats.js CHANGED
@@ -59,6 +59,7 @@ export function parseActionStats(lines, maxAgeMs, now) {
59
59
  export function parseHistoryStats(entries, maxAgeMs, now) {
60
60
  const cutoff = maxAgeMs ? (now ?? Date.now()) - maxAgeMs : 0;
61
61
  const byTag = new Map();
62
+ const byHour = new Array(24).fill(0);
62
63
  let total = 0;
63
64
  let firstTs = Infinity;
64
65
  let lastTs = 0;
@@ -71,10 +72,11 @@ export function parseHistoryStats(entries, maxAgeMs, now) {
71
72
  if (entry.ts > lastTs)
72
73
  lastTs = entry.ts;
73
74
  byTag.set(entry.tag, (byTag.get(entry.tag) ?? 0) + 1);
75
+ byHour[new Date(entry.ts).getHours()]++;
74
76
  }
75
77
  if (total === 0)
76
78
  return null;
77
- return { total, byTag, firstTs, lastTs };
79
+ return { total, byTag, byHour, firstTs, lastTs };
78
80
  }
79
81
  /**
80
82
  * Combine action and history stats into a unified stats object.
@@ -214,8 +216,30 @@ export function formatStats(stats, windowLabel) {
214
216
  lines.push(` ${color}${tag.padEnd(18)}${RESET} ${String(count).padStart(4)} ${SLATE}${bar}${RESET} ${DIM}${pct}%${RESET}`);
215
217
  }
216
218
  }
219
+ // hourly heatmap
220
+ if (stats.history && stats.history.total > 0) {
221
+ lines.push("");
222
+ lines.push(` ${BOLD}hourly activity${RESET}`);
223
+ lines.push(` ${formatHeatmap(stats.history.byHour)}`);
224
+ lines.push(` ${DIM}${"0".padEnd(6)}${"6".padEnd(6)}${"12".padEnd(6)}${"18".padEnd(5)}23${RESET}`);
225
+ }
217
226
  lines.push(` ${hr}`);
218
227
  lines.push("");
219
228
  return lines.join("\n");
220
229
  }
230
+ // ── Heatmap ──────────────────────────────────────────────────────────────────
231
+ const HEAT_BLOCKS = [" ", "░", "▒", "▓", "█"];
232
+ /** Format a 24-element heatmap as a colored block string. */
233
+ export function formatHeatmap(counts) {
234
+ const max = Math.max(...counts);
235
+ if (max === 0)
236
+ return DIM + HEAT_BLOCKS[0].repeat(24) + RESET;
237
+ return counts.map((c) => {
238
+ if (c === 0)
239
+ return `${SLATE}${HEAT_BLOCKS[0]}${RESET}`;
240
+ const level = Math.min(HEAT_BLOCKS.length - 1, Math.ceil((c / max) * (HEAT_BLOCKS.length - 1)));
241
+ const color = level <= 1 ? SLATE : level <= 2 ? SKY : level <= 3 ? AMBER : LIME;
242
+ return `${color}${HEAT_BLOCKS[level]}${RESET}`;
243
+ }).join("");
244
+ }
221
245
  //# sourceMappingURL=stats.js.map
package/dist/tui.d.ts CHANGED
@@ -116,6 +116,7 @@ export declare class TUI {
116
116
  private sessionNotes;
117
117
  private sessionFirstSeen;
118
118
  private autoPinOnError;
119
+ private sessionErrorCounts;
119
120
  private viewMode;
120
121
  private drilldownSessionId;
121
122
  private sessionOutputs;
@@ -197,6 +198,8 @@ export declare class TUI {
197
198
  getAllFirstSeen(): ReadonlyMap<string, number>;
198
199
  /** Return the activity buffer (for /clip export). */
199
200
  getActivityBuffer(): readonly ActivityEntry[];
201
+ /** Return per-session error counts (for /who). */
202
+ getSessionErrorCounts(): ReadonlyMap<string, number>;
200
203
  /**
201
204
  * Add a bookmark at the current activity position.
202
205
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
package/dist/tui.js CHANGED
@@ -301,6 +301,7 @@ export class TUI {
301
301
  sessionNotes = new Map(); // session ID → note text
302
302
  sessionFirstSeen = new Map(); // session ID → epoch ms when first observed
303
303
  autoPinOnError = false; // auto-pin sessions that emit errors
304
+ sessionErrorCounts = new Map(); // session ID → cumulative error count
304
305
  // drill-down mode: show a single session's full output
305
306
  viewMode = "overview";
306
307
  drilldownSessionId = null;
@@ -582,6 +583,10 @@ export class TUI {
582
583
  getActivityBuffer() {
583
584
  return this.activityBuffer;
584
585
  }
586
+ /** Return per-session error counts (for /who). */
587
+ getSessionErrorCounts() {
588
+ return this.sessionErrorCounts;
589
+ }
585
590
  /**
586
591
  * Add a bookmark at the current activity position.
587
592
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
@@ -696,6 +701,10 @@ export class TUI {
696
701
  process.stderr.write("\x07");
697
702
  }
698
703
  }
704
+ // track per-session error counts
705
+ if (sessionId && shouldAutoPin(tag)) {
706
+ this.sessionErrorCounts.set(sessionId, (this.sessionErrorCounts.get(sessionId) ?? 0) + 1);
707
+ }
699
708
  // auto-pin sessions that emit errors (when enabled)
700
709
  if (this.autoPinOnError && sessionId && shouldAutoPin(tag) && !this.pinnedIds.has(sessionId)) {
701
710
  this.pinnedIds.add(sessionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.93.0",
3
+ "version": "0.95.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",