aoaoe 0.87.0 → 0.89.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
@@ -15,7 +15,7 @@ import { wakeableSleep } from "./wake.js";
15
15
  import { classifyMessages, formatUserMessages, buildReceipts, shouldSkipSleep, hasPendingFile, isInsistMessage, stripInsistPrefix } from "./message.js";
16
16
  import { TaskManager, loadTaskDefinitions, loadTaskState, formatTaskTable } from "./task-manager.js";
17
17
  import { runTaskCli, handleTaskSlashCommand } from "./task-cli.js";
18
- import { TUI, hitTestSession, nextSortMode, SORT_MODES } from "./tui.js";
18
+ import { TUI, hitTestSession, nextSortMode, SORT_MODES, formatUptime } from "./tui.js";
19
19
  import { isDaemonRunningFromState } from "./chat.js";
20
20
  import { sendNotification, sendTestNotification } from "./notify.js";
21
21
  import { startHealthServer } from "./health.js";
@@ -464,6 +464,28 @@ async function main() {
464
464
  tui.log("system", "filter cleared");
465
465
  }
466
466
  });
467
+ // wire /uptime listing
468
+ input.onUptime(() => {
469
+ const firstSeen = tui.getAllFirstSeen();
470
+ const sessions = tui.getSessions();
471
+ if (sessions.length === 0) {
472
+ tui.log("system", "no sessions — uptime not available");
473
+ }
474
+ else {
475
+ const now = Date.now();
476
+ for (const s of sessions) {
477
+ const start = firstSeen.get(s.id);
478
+ const up = start !== undefined ? formatUptime(now - start) : "unknown";
479
+ tui.log("system", ` ${s.title}: ${up}`);
480
+ }
481
+ }
482
+ });
483
+ // wire /auto-pin toggle
484
+ input.onAutoPin(() => {
485
+ const enabled = !tui.isAutoPinEnabled();
486
+ tui.setAutoPin(enabled);
487
+ tui.log("system", `auto-pin on error: ${enabled ? "on" : "off"}`);
488
+ });
467
489
  // wire /note set/clear
468
490
  input.onNote((target, text) => {
469
491
  const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
package/dist/input.d.ts CHANGED
@@ -14,6 +14,8 @@ export type MarksHandler = () => void;
14
14
  export type MuteHandler = (target: string) => void;
15
15
  export type UnmuteAllHandler = () => void;
16
16
  export type TagFilterHandler = (tag: string | null) => void;
17
+ export type UptimeHandler = () => void;
18
+ export type AutoPinHandler = () => void;
17
19
  export type NoteHandler = (target: string, text: string) => void;
18
20
  export type NotesHandler = () => void;
19
21
  export interface MouseEvent {
@@ -52,6 +54,8 @@ export declare class InputReader {
52
54
  private muteHandler;
53
55
  private unmuteAllHandler;
54
56
  private tagFilterHandler;
57
+ private uptimeHandler;
58
+ private autoPinHandler;
55
59
  private noteHandler;
56
60
  private notesHandler;
57
61
  private mouseDataListener;
@@ -74,6 +78,8 @@ export declare class InputReader {
74
78
  onMute(handler: MuteHandler): void;
75
79
  onUnmuteAll(handler: UnmuteAllHandler): void;
76
80
  onTagFilter(handler: TagFilterHandler): void;
81
+ onUptime(handler: UptimeHandler): void;
82
+ onAutoPin(handler: AutoPinHandler): void;
77
83
  onNote(handler: NoteHandler): void;
78
84
  onNotes(handler: NotesHandler): void;
79
85
  private notifyQueueChange;
package/dist/input.js CHANGED
@@ -46,6 +46,8 @@ export class InputReader {
46
46
  muteHandler = null;
47
47
  unmuteAllHandler = null;
48
48
  tagFilterHandler = null;
49
+ uptimeHandler = null;
50
+ autoPinHandler = null;
49
51
  noteHandler = null;
50
52
  notesHandler = null;
51
53
  mouseDataListener = null;
@@ -125,6 +127,14 @@ export class InputReader {
125
127
  onTagFilter(handler) {
126
128
  this.tagFilterHandler = handler;
127
129
  }
130
+ // register a callback for uptime listing (/uptime)
131
+ onUptime(handler) {
132
+ this.uptimeHandler = handler;
133
+ }
134
+ // register a callback for auto-pin toggle (/auto-pin)
135
+ onAutoPin(handler) {
136
+ this.autoPinHandler = handler;
137
+ }
128
138
  // register a callback for note commands (/note <target> <text>)
129
139
  onNote(handler) {
130
140
  this.noteHandler = handler;
@@ -318,6 +328,8 @@ ${BOLD}navigation:${RESET}
318
328
  /mute [N|name] mute/unmute a session's activity entries (toggle)
319
329
  /unmute-all unmute all sessions at once
320
330
  /filter [tag] filter activity by tag (error, system, etc. — no arg = clear)
331
+ /uptime show session uptimes (time since first observed)
332
+ /auto-pin toggle auto-pin on error (pin sessions that emit errors)
321
333
  /note N|name text attach a note to a session (no text = clear)
322
334
  /notes list all session notes
323
335
  /mark bookmark current activity position
@@ -484,6 +496,22 @@ ${BOLD}other:${RESET}
484
496
  }
485
497
  break;
486
498
  }
499
+ case "/uptime":
500
+ if (this.uptimeHandler) {
501
+ this.uptimeHandler();
502
+ }
503
+ else {
504
+ console.error(`${DIM}uptime not available (no TUI)${RESET}`);
505
+ }
506
+ break;
507
+ case "/auto-pin":
508
+ if (this.autoPinHandler) {
509
+ this.autoPinHandler();
510
+ }
511
+ else {
512
+ console.error(`${DIM}auto-pin not available (no TUI)${RESET}`);
513
+ }
514
+ break;
487
515
  case "/note": {
488
516
  const noteArg = line.slice("/note".length).trim();
489
517
  if (this.noteHandler) {
package/dist/tui.d.ts CHANGED
@@ -57,6 +57,10 @@ export declare function formatMuteBadge(count: number): string;
57
57
  export declare function matchesTagFilter(entry: ActivityEntry, tag: string): boolean;
58
58
  /** Format the tag filter indicator text for the separator bar. */
59
59
  export declare function formatTagFilterIndicator(tag: string, matchCount: number, totalCount: number): string;
60
+ /** Determine if a log entry should trigger auto-pin (error-like tags). */
61
+ export declare function shouldAutoPin(tag: string): boolean;
62
+ /** Format milliseconds as human-readable uptime: "2h 15m", "45m", "3d 2h", "< 1m". */
63
+ export declare function formatUptime(ms: number): string;
60
64
  export declare class TUI {
61
65
  private active;
62
66
  private countdownTimer;
@@ -90,6 +94,8 @@ export declare class TUI {
90
94
  private mutedIds;
91
95
  private mutedEntryCounts;
92
96
  private sessionNotes;
97
+ private sessionFirstSeen;
98
+ private autoPinOnError;
93
99
  private viewMode;
94
100
  private drilldownSessionId;
95
101
  private sessionOutputs;
@@ -134,6 +140,10 @@ export declare class TUI {
134
140
  setBell(enabled: boolean): void;
135
141
  /** Return whether terminal bell is enabled. */
136
142
  isBellEnabled(): boolean;
143
+ /** Enable or disable auto-pin on error. */
144
+ setAutoPin(enabled: boolean): void;
145
+ /** Return whether auto-pin on error is enabled. */
146
+ isAutoPinEnabled(): boolean;
137
147
  /**
138
148
  * Toggle mute for a session (by 1-indexed number, ID, ID prefix, or title).
139
149
  * Muted sessions' activity entries are hidden from the log (still buffered + persisted).
@@ -161,6 +171,10 @@ export declare class TUI {
161
171
  getAllNotes(): ReadonlyMap<string, string>;
162
172
  /** Return the current sessions (read-only, for resolving IDs to titles in the UI). */
163
173
  getSessions(): readonly DaemonSessionState[];
174
+ /** Return the uptime in ms for a session (0 if not tracked). */
175
+ getUptime(id: string): number;
176
+ /** Return all session first-seen timestamps (for /uptime listing). */
177
+ getAllFirstSeen(): ReadonlyMap<string, number>;
164
178
  /**
165
179
  * Add a bookmark at the current activity position.
166
180
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
package/dist/tui.js CHANGED
@@ -191,6 +191,29 @@ export function matchesTagFilter(entry, tag) {
191
191
  export function formatTagFilterIndicator(tag, matchCount, totalCount) {
192
192
  return `${SLATE}filter:${RESET} ${AMBER}${tag}${RESET} ${DIM}(${matchCount}/${totalCount})${RESET}`;
193
193
  }
194
+ // ── Auto-pin ─────────────────────────────────────────────────────────────────
195
+ /** Determine if a log entry should trigger auto-pin (error-like tags). */
196
+ export function shouldAutoPin(tag) {
197
+ const lower = tag.toLowerCase();
198
+ return lower === "! action" || lower === "error";
199
+ }
200
+ // ── Uptime ───────────────────────────────────────────────────────────────────
201
+ /** Format milliseconds as human-readable uptime: "2h 15m", "45m", "3d 2h", "< 1m". */
202
+ export function formatUptime(ms) {
203
+ if (ms < 0)
204
+ return "< 1m";
205
+ const totalMin = Math.floor(ms / 60_000);
206
+ if (totalMin < 1)
207
+ return "< 1m";
208
+ const days = Math.floor(totalMin / 1440);
209
+ const hours = Math.floor((totalMin % 1440) / 60);
210
+ const minutes = totalMin % 60;
211
+ if (days > 0)
212
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
213
+ if (hours > 0)
214
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
215
+ return `${minutes}m`;
216
+ }
194
217
  // ── TUI class ───────────────────────────────────────────────────────────────
195
218
  export class TUI {
196
219
  active = false;
@@ -225,6 +248,8 @@ export class TUI {
225
248
  mutedIds = new Set(); // muted session IDs (activity entries hidden)
226
249
  mutedEntryCounts = new Map(); // session ID → suppressed entry count since mute
227
250
  sessionNotes = new Map(); // session ID → note text
251
+ sessionFirstSeen = new Map(); // session ID → epoch ms when first observed
252
+ autoPinOnError = false; // auto-pin sessions that emit errors
228
253
  // drill-down mode: show a single session's full output
229
254
  viewMode = "overview";
230
255
  drilldownSessionId = null;
@@ -380,6 +405,14 @@ export class TUI {
380
405
  isBellEnabled() {
381
406
  return this.bellEnabled;
382
407
  }
408
+ /** Enable or disable auto-pin on error. */
409
+ setAutoPin(enabled) {
410
+ this.autoPinOnError = enabled;
411
+ }
412
+ /** Return whether auto-pin on error is enabled. */
413
+ isAutoPinEnabled() {
414
+ return this.autoPinOnError;
415
+ }
383
416
  /**
384
417
  * Toggle mute for a session (by 1-indexed number, ID, ID prefix, or title).
385
418
  * Muted sessions' activity entries are hidden from the log (still buffered + persisted).
@@ -483,6 +516,17 @@ export class TUI {
483
516
  getSessions() {
484
517
  return this.sessions;
485
518
  }
519
+ /** Return the uptime in ms for a session (0 if not tracked). */
520
+ getUptime(id) {
521
+ const firstSeen = this.sessionFirstSeen.get(id);
522
+ if (firstSeen === undefined)
523
+ return 0;
524
+ return Date.now() - firstSeen;
525
+ }
526
+ /** Return all session first-seen timestamps (for /uptime listing). */
527
+ getAllFirstSeen() {
528
+ return this.sessionFirstSeen;
529
+ }
486
530
  /**
487
531
  * Add a bookmark at the current activity position.
488
532
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
@@ -547,9 +591,11 @@ export class TUI {
547
591
  if (opts.pendingCount !== undefined)
548
592
  this.pendingCount = opts.pendingCount;
549
593
  if (opts.sessions !== undefined) {
550
- // track activity changes for sort-by-activity
594
+ // track activity changes for sort-by-activity + first-seen for uptime
551
595
  const now = Date.now();
552
596
  for (const s of opts.sessions) {
597
+ if (!this.sessionFirstSeen.has(s.id))
598
+ this.sessionFirstSeen.set(s.id, now);
553
599
  const prev = this.prevLastActivity.get(s.id);
554
600
  if (s.lastActivity !== undefined && s.lastActivity !== prev) {
555
601
  this.lastChangeAt.set(s.id, now);
@@ -595,6 +641,12 @@ export class TUI {
595
641
  process.stderr.write("\x07");
596
642
  }
597
643
  }
644
+ // auto-pin sessions that emit errors (when enabled)
645
+ if (this.autoPinOnError && sessionId && shouldAutoPin(tag) && !this.pinnedIds.has(sessionId)) {
646
+ this.pinnedIds.add(sessionId);
647
+ if (this.active)
648
+ this.paintSessions();
649
+ }
598
650
  // track suppressed entry counts regardless of active state (for badge accuracy)
599
651
  if (shouldMuteEntry(entry, this.mutedIds) && entry.sessionId) {
600
652
  this.mutedEntryCounts.set(entry.sessionId, (this.mutedEntryCounts.get(entry.sessionId) ?? 0) + 1);
@@ -1099,7 +1151,9 @@ export class TUI {
1099
1151
  const title = session ? session.title : this.drilldownSessionId ?? "?";
1100
1152
  const noteText = this.drilldownSessionId ? this.sessionNotes.get(this.drilldownSessionId) : undefined;
1101
1153
  const noteSuffix = noteText ? `"${noteText}" ` : "";
1102
- const prefix = `${BOX.h}${BOX.h} ${title} ${noteSuffix}`;
1154
+ const firstSeen = this.drilldownSessionId ? this.sessionFirstSeen.get(this.drilldownSessionId) : undefined;
1155
+ const uptimeSuffix = firstSeen !== undefined ? `${DIM}${formatUptime(Date.now() - firstSeen)}${RESET} ` : "";
1156
+ const prefix = `${BOX.h}${BOX.h} ${title} ${uptimeSuffix}${noteSuffix}`;
1103
1157
  let hints;
1104
1158
  if (this.drilldownScrollOffset > 0) {
1105
1159
  const outputLines = this.sessionOutputs.get(this.drilldownSessionId ?? "") ?? [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.87.0",
3
+ "version": "0.89.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",