aoaoe 0.87.0 → 0.88.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,22 @@ 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
+ });
467
483
  // wire /note set/clear
468
484
  input.onNote((target, text) => {
469
485
  const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
package/dist/input.d.ts CHANGED
@@ -14,6 +14,7 @@ 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;
17
18
  export type NoteHandler = (target: string, text: string) => void;
18
19
  export type NotesHandler = () => void;
19
20
  export interface MouseEvent {
@@ -52,6 +53,7 @@ export declare class InputReader {
52
53
  private muteHandler;
53
54
  private unmuteAllHandler;
54
55
  private tagFilterHandler;
56
+ private uptimeHandler;
55
57
  private noteHandler;
56
58
  private notesHandler;
57
59
  private mouseDataListener;
@@ -74,6 +76,7 @@ export declare class InputReader {
74
76
  onMute(handler: MuteHandler): void;
75
77
  onUnmuteAll(handler: UnmuteAllHandler): void;
76
78
  onTagFilter(handler: TagFilterHandler): void;
79
+ onUptime(handler: UptimeHandler): void;
77
80
  onNote(handler: NoteHandler): void;
78
81
  onNotes(handler: NotesHandler): void;
79
82
  private notifyQueueChange;
package/dist/input.js CHANGED
@@ -46,6 +46,7 @@ export class InputReader {
46
46
  muteHandler = null;
47
47
  unmuteAllHandler = null;
48
48
  tagFilterHandler = null;
49
+ uptimeHandler = null;
49
50
  noteHandler = null;
50
51
  notesHandler = null;
51
52
  mouseDataListener = null;
@@ -125,6 +126,10 @@ export class InputReader {
125
126
  onTagFilter(handler) {
126
127
  this.tagFilterHandler = handler;
127
128
  }
129
+ // register a callback for uptime listing (/uptime)
130
+ onUptime(handler) {
131
+ this.uptimeHandler = handler;
132
+ }
128
133
  // register a callback for note commands (/note <target> <text>)
129
134
  onNote(handler) {
130
135
  this.noteHandler = handler;
@@ -318,6 +323,7 @@ ${BOLD}navigation:${RESET}
318
323
  /mute [N|name] mute/unmute a session's activity entries (toggle)
319
324
  /unmute-all unmute all sessions at once
320
325
  /filter [tag] filter activity by tag (error, system, etc. — no arg = clear)
326
+ /uptime show session uptimes (time since first observed)
321
327
  /note N|name text attach a note to a session (no text = clear)
322
328
  /notes list all session notes
323
329
  /mark bookmark current activity position
@@ -484,6 +490,14 @@ ${BOLD}other:${RESET}
484
490
  }
485
491
  break;
486
492
  }
493
+ case "/uptime":
494
+ if (this.uptimeHandler) {
495
+ this.uptimeHandler();
496
+ }
497
+ else {
498
+ console.error(`${DIM}uptime not available (no TUI)${RESET}`);
499
+ }
500
+ break;
487
501
  case "/note": {
488
502
  const noteArg = line.slice("/note".length).trim();
489
503
  if (this.noteHandler) {
package/dist/tui.d.ts CHANGED
@@ -57,6 +57,8 @@ 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
+ /** Format milliseconds as human-readable uptime: "2h 15m", "45m", "3d 2h", "< 1m". */
61
+ export declare function formatUptime(ms: number): string;
60
62
  export declare class TUI {
61
63
  private active;
62
64
  private countdownTimer;
@@ -90,6 +92,7 @@ export declare class TUI {
90
92
  private mutedIds;
91
93
  private mutedEntryCounts;
92
94
  private sessionNotes;
95
+ private sessionFirstSeen;
93
96
  private viewMode;
94
97
  private drilldownSessionId;
95
98
  private sessionOutputs;
@@ -161,6 +164,10 @@ export declare class TUI {
161
164
  getAllNotes(): ReadonlyMap<string, string>;
162
165
  /** Return the current sessions (read-only, for resolving IDs to titles in the UI). */
163
166
  getSessions(): readonly DaemonSessionState[];
167
+ /** Return the uptime in ms for a session (0 if not tracked). */
168
+ getUptime(id: string): number;
169
+ /** Return all session first-seen timestamps (for /uptime listing). */
170
+ getAllFirstSeen(): ReadonlyMap<string, number>;
164
171
  /**
165
172
  * Add a bookmark at the current activity position.
166
173
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
package/dist/tui.js CHANGED
@@ -191,6 +191,23 @@ 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
+ // ── Uptime ───────────────────────────────────────────────────────────────────
195
+ /** Format milliseconds as human-readable uptime: "2h 15m", "45m", "3d 2h", "< 1m". */
196
+ export function formatUptime(ms) {
197
+ if (ms < 0)
198
+ return "< 1m";
199
+ const totalMin = Math.floor(ms / 60_000);
200
+ if (totalMin < 1)
201
+ return "< 1m";
202
+ const days = Math.floor(totalMin / 1440);
203
+ const hours = Math.floor((totalMin % 1440) / 60);
204
+ const minutes = totalMin % 60;
205
+ if (days > 0)
206
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
207
+ if (hours > 0)
208
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
209
+ return `${minutes}m`;
210
+ }
194
211
  // ── TUI class ───────────────────────────────────────────────────────────────
195
212
  export class TUI {
196
213
  active = false;
@@ -225,6 +242,7 @@ export class TUI {
225
242
  mutedIds = new Set(); // muted session IDs (activity entries hidden)
226
243
  mutedEntryCounts = new Map(); // session ID → suppressed entry count since mute
227
244
  sessionNotes = new Map(); // session ID → note text
245
+ sessionFirstSeen = new Map(); // session ID → epoch ms when first observed
228
246
  // drill-down mode: show a single session's full output
229
247
  viewMode = "overview";
230
248
  drilldownSessionId = null;
@@ -483,6 +501,17 @@ export class TUI {
483
501
  getSessions() {
484
502
  return this.sessions;
485
503
  }
504
+ /** Return the uptime in ms for a session (0 if not tracked). */
505
+ getUptime(id) {
506
+ const firstSeen = this.sessionFirstSeen.get(id);
507
+ if (firstSeen === undefined)
508
+ return 0;
509
+ return Date.now() - firstSeen;
510
+ }
511
+ /** Return all session first-seen timestamps (for /uptime listing). */
512
+ getAllFirstSeen() {
513
+ return this.sessionFirstSeen;
514
+ }
486
515
  /**
487
516
  * Add a bookmark at the current activity position.
488
517
  * Returns the bookmark number (1-indexed) or 0 if buffer is empty.
@@ -547,9 +576,11 @@ export class TUI {
547
576
  if (opts.pendingCount !== undefined)
548
577
  this.pendingCount = opts.pendingCount;
549
578
  if (opts.sessions !== undefined) {
550
- // track activity changes for sort-by-activity
579
+ // track activity changes for sort-by-activity + first-seen for uptime
551
580
  const now = Date.now();
552
581
  for (const s of opts.sessions) {
582
+ if (!this.sessionFirstSeen.has(s.id))
583
+ this.sessionFirstSeen.set(s.id, now);
553
584
  const prev = this.prevLastActivity.get(s.id);
554
585
  if (s.lastActivity !== undefined && s.lastActivity !== prev) {
555
586
  this.lastChangeAt.set(s.id, now);
@@ -1099,7 +1130,9 @@ export class TUI {
1099
1130
  const title = session ? session.title : this.drilldownSessionId ?? "?";
1100
1131
  const noteText = this.drilldownSessionId ? this.sessionNotes.get(this.drilldownSessionId) : undefined;
1101
1132
  const noteSuffix = noteText ? `"${noteText}" ` : "";
1102
- const prefix = `${BOX.h}${BOX.h} ${title} ${noteSuffix}`;
1133
+ const firstSeen = this.drilldownSessionId ? this.sessionFirstSeen.get(this.drilldownSessionId) : undefined;
1134
+ const uptimeSuffix = firstSeen !== undefined ? `${DIM}${formatUptime(Date.now() - firstSeen)}${RESET} ` : "";
1135
+ const prefix = `${BOX.h}${BOX.h} ${title} ${uptimeSuffix}${noteSuffix}`;
1103
1136
  let hints;
1104
1137
  if (this.drilldownScrollOffset > 0) {
1105
1138
  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.88.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",