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 +23 -1
- package/dist/input.d.ts +6 -0
- package/dist/input.js +28 -0
- package/dist/tui.d.ts +14 -0
- package/dist/tui.js +56 -2
- package/package.json +1 -1
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
|
|
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 ?? "") ?? [];
|