aoaoe 0.114.0 → 0.116.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 +40 -0
- package/dist/input.d.ts +3 -0
- package/dist/input.js +16 -0
- package/dist/tui.d.ts +9 -2
- package/dist/tui.js +51 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -757,6 +757,46 @@ async function main() {
|
|
|
757
757
|
if (!any)
|
|
758
758
|
tui.log("system", ` threshold: ${CONTEXT_BURN_THRESHOLD.toLocaleString()} tokens/min`);
|
|
759
759
|
});
|
|
760
|
+
// wire /copy session pane output to clipboard
|
|
761
|
+
input.onCopySession((target) => {
|
|
762
|
+
// resolve target: null = current drill-down session
|
|
763
|
+
let lines = null;
|
|
764
|
+
let label = "current session";
|
|
765
|
+
if (target === null) {
|
|
766
|
+
const ddId = tui.getDrilldownId();
|
|
767
|
+
if (!ddId) {
|
|
768
|
+
tui.log("system", "no session in view — use /copy N or drill into a session first");
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
lines = tui.getSessionOutput(ddId);
|
|
772
|
+
const s = tui.getSessions().find((s) => s.id === ddId);
|
|
773
|
+
label = s?.title ?? ddId.slice(0, 8);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
|
|
777
|
+
lines = tui.getSessionOutput(num ?? target);
|
|
778
|
+
label = target;
|
|
779
|
+
}
|
|
780
|
+
if (!lines || lines.length === 0) {
|
|
781
|
+
tui.log("system", `no output stored for ${label} — session may not have been polled yet`);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const text = lines.join("\n") + "\n";
|
|
785
|
+
try {
|
|
786
|
+
execSync("pbcopy", { input: text, timeout: 5000 });
|
|
787
|
+
tui.log("system", `copied ${lines.length} lines from ${label} to clipboard`);
|
|
788
|
+
}
|
|
789
|
+
catch {
|
|
790
|
+
try {
|
|
791
|
+
const copyPath = join(homedir(), ".aoaoe", "copy.txt");
|
|
792
|
+
writeFileSync(copyPath, text, "utf-8");
|
|
793
|
+
tui.log("system", `saved ${lines.length} lines from ${label} to ~/.aoaoe/copy.txt`);
|
|
794
|
+
}
|
|
795
|
+
catch (writeErr) {
|
|
796
|
+
tui.log("error", `copy failed: ${writeErr}`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
});
|
|
760
800
|
// wire /rename custom display name
|
|
761
801
|
input.onRename((target, displayName) => {
|
|
762
802
|
const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
|
package/dist/input.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export type WatchdogHandler = (thresholdMinutes: number | null) => void;
|
|
|
33
33
|
export type TopHandler = (mode: string) => void;
|
|
34
34
|
export type CeilingHandler = () => void;
|
|
35
35
|
export type RenameHandler = (target: string, name: string) => void;
|
|
36
|
+
export type CopySessionHandler = (target: string | null) => void;
|
|
36
37
|
export interface MouseEvent {
|
|
37
38
|
button: number;
|
|
38
39
|
col: number;
|
|
@@ -88,6 +89,7 @@ export declare class InputReader {
|
|
|
88
89
|
private topHandler;
|
|
89
90
|
private ceilingHandler;
|
|
90
91
|
private renameHandler;
|
|
92
|
+
private copySessionHandler;
|
|
91
93
|
private aliases;
|
|
92
94
|
private mouseDataListener;
|
|
93
95
|
onScroll(handler: (dir: ScrollDirection) => void): void;
|
|
@@ -126,6 +128,7 @@ export declare class InputReader {
|
|
|
126
128
|
onTop(handler: TopHandler): void;
|
|
127
129
|
onCeiling(handler: CeilingHandler): void;
|
|
128
130
|
onRename(handler: RenameHandler): void;
|
|
131
|
+
onCopySession(handler: CopySessionHandler): void;
|
|
129
132
|
/** Set aliases from persisted prefs. */
|
|
130
133
|
setAliases(aliases: Record<string, string>): void;
|
|
131
134
|
/** Get current aliases as a plain object. */
|
package/dist/input.js
CHANGED
|
@@ -66,6 +66,7 @@ export class InputReader {
|
|
|
66
66
|
topHandler = null;
|
|
67
67
|
ceilingHandler = null;
|
|
68
68
|
renameHandler = null;
|
|
69
|
+
copySessionHandler = null;
|
|
69
70
|
aliases = new Map(); // /shortcut → /full command
|
|
70
71
|
mouseDataListener = null;
|
|
71
72
|
// register a callback for scroll key events (PgUp/PgDn/Home/End)
|
|
@@ -212,6 +213,10 @@ export class InputReader {
|
|
|
212
213
|
onRename(handler) {
|
|
213
214
|
this.renameHandler = handler;
|
|
214
215
|
}
|
|
216
|
+
// register a callback for /copy [N|name] — copy session pane output
|
|
217
|
+
onCopySession(handler) {
|
|
218
|
+
this.copySessionHandler = handler;
|
|
219
|
+
}
|
|
215
220
|
/** Set aliases from persisted prefs. */
|
|
216
221
|
setAliases(aliases) {
|
|
217
222
|
this.aliases.clear();
|
|
@@ -448,6 +453,7 @@ ${BOLD}navigation:${RESET}
|
|
|
448
453
|
/top [mode] rank sessions by errors (default), burn, or idle
|
|
449
454
|
/ceiling show context token usage vs limit for all sessions
|
|
450
455
|
/rename N|name [display] set custom display name in TUI (no display = clear)
|
|
456
|
+
/copy [N|name] copy session's current pane output to clipboard (default: current drill-down)
|
|
451
457
|
/clip [N] copy last N activity entries to clipboard (default 20)
|
|
452
458
|
/diff N show activity since bookmark N
|
|
453
459
|
/mark bookmark current activity position
|
|
@@ -837,6 +843,16 @@ ${BOLD}other:${RESET}
|
|
|
837
843
|
}
|
|
838
844
|
break;
|
|
839
845
|
}
|
|
846
|
+
case "/copy": {
|
|
847
|
+
const copyArg = line.slice("/copy".length).trim() || null;
|
|
848
|
+
if (this.copySessionHandler) {
|
|
849
|
+
this.copySessionHandler(copyArg);
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
console.error(`${DIM}copy not available (no TUI)${RESET}`);
|
|
853
|
+
}
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
840
856
|
case "/rename": {
|
|
841
857
|
const renameArg = line.slice("/rename".length).trim();
|
|
842
858
|
if (!renameArg) {
|
package/dist/tui.d.ts
CHANGED
|
@@ -28,10 +28,10 @@ declare const COMPACT_NAME_LEN = 10;
|
|
|
28
28
|
declare const PIN_ICON = "\u25B2";
|
|
29
29
|
/**
|
|
30
30
|
* Format sessions as inline compact tokens, wrapped to fit maxWidth.
|
|
31
|
-
* Each token: "{idx}{pin?}{mute?}{dot}{name}" — e.g. "1▲●Alpha" for pinned, "2◌●Bravo" for muted.
|
|
31
|
+
* Each token: "{idx}{pin?}{mute?}{dot}{name}{health?}" — 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>, noteIds?: Set<string>): string[];
|
|
34
|
+
declare function formatCompactRows(sessions: DaemonSessionState[], maxWidth: number, pinnedIds?: Set<string>, mutedIds?: Set<string>, noteIds?: Set<string>, healthScores?: Map<string, number>): 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;
|
|
@@ -304,6 +304,13 @@ export declare class TUI {
|
|
|
304
304
|
getAllFirstSeen(): ReadonlyMap<string, number>;
|
|
305
305
|
/** Return the activity buffer (for /clip export). */
|
|
306
306
|
getActivityBuffer(): readonly ActivityEntry[];
|
|
307
|
+
/**
|
|
308
|
+
* Return the stored pane output lines for a session (by 1-indexed number, ID, prefix, or title).
|
|
309
|
+
* Returns null if session not found or no output stored.
|
|
310
|
+
*/
|
|
311
|
+
getSessionOutput(sessionIdOrIndex: string | number): string[] | null;
|
|
312
|
+
/** Return the current drill-down session ID (for /copy default target). */
|
|
313
|
+
getDrilldownId(): string | null;
|
|
307
314
|
/** Return per-session error counts (for /who). */
|
|
308
315
|
getSessionErrorCounts(): ReadonlyMap<string, number>;
|
|
309
316
|
/** Return recent error timestamps for a session (for sparkline rendering). */
|
package/dist/tui.js
CHANGED
|
@@ -100,10 +100,10 @@ const COMPACT_NAME_LEN = 10;
|
|
|
100
100
|
const PIN_ICON = "▲";
|
|
101
101
|
/**
|
|
102
102
|
* Format sessions as inline compact tokens, wrapped to fit maxWidth.
|
|
103
|
-
* Each token: "{idx}{pin?}{mute?}{dot}{name}" — e.g. "1▲●Alpha" for pinned, "2◌●Bravo" for muted.
|
|
103
|
+
* Each token: "{idx}{pin?}{mute?}{dot}{name}{health?}" — e.g. "1▲●Alpha" for pinned, "2◌●Bravo" for muted.
|
|
104
104
|
* Returns array of formatted row strings (one per display row).
|
|
105
105
|
*/
|
|
106
|
-
function formatCompactRows(sessions, maxWidth, pinnedIds, mutedIds, noteIds) {
|
|
106
|
+
function formatCompactRows(sessions, maxWidth, pinnedIds, mutedIds, noteIds, healthScores) {
|
|
107
107
|
if (sessions.length === 0)
|
|
108
108
|
return [`${DIM}no agents connected${RESET}`];
|
|
109
109
|
const tokens = [];
|
|
@@ -119,8 +119,13 @@ function formatCompactRows(sessions, maxWidth, pinnedIds, mutedIds, noteIds) {
|
|
|
119
119
|
const muteIcon = muted ? `${DIM}${MUTE_ICON}${RESET}` : "";
|
|
120
120
|
const noteIcon = noted ? `${TEAL}${NOTE_ICON}${RESET}` : "";
|
|
121
121
|
const name = truncatePlain(s.title, COMPACT_NAME_LEN);
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
// health indicator: single ⬡ glyph when score < HEALTH_GOOD, colored by severity
|
|
123
|
+
const score = healthScores?.get(s.id);
|
|
124
|
+
const healthGlyph = (score !== undefined && score < HEALTH_GOOD)
|
|
125
|
+
? `${score < HEALTH_WARN ? ROSE : AMBER}${HEALTH_ICON}${RESET}` : "";
|
|
126
|
+
const healthWidth = (score !== undefined && score < HEALTH_GOOD) ? 1 : 0;
|
|
127
|
+
tokens.push(`${SLATE}${idx}${RESET}${pin}${muteIcon}${noteIcon}${dot}${BOLD}${name}${RESET}${healthGlyph}`);
|
|
128
|
+
widths.push(idx.length + (pinned ? 1 : 0) + (muted ? 1 : 0) + (noted ? 1 : 0) + 1 + name.length + healthWidth);
|
|
124
129
|
}
|
|
125
130
|
const rows = [];
|
|
126
131
|
let currentRow = "";
|
|
@@ -281,7 +286,7 @@ export const BUILTIN_COMMANDS = new Set([
|
|
|
281
286
|
"/pin", "/bell", "/focus", "/mute", "/unmute-all", "/filter", "/who",
|
|
282
287
|
"/uptime", "/auto-pin", "/note", "/notes", "/clip", "/diff", "/mark",
|
|
283
288
|
"/jump", "/marks", "/search", "/alias", "/insist", "/task", "/tasks",
|
|
284
|
-
"/group", "/groups", "/group-filter", "/burn-rate", "/snapshot", "/broadcast", "/watchdog", "/top", "/ceiling", "/rename",
|
|
289
|
+
"/group", "/groups", "/group-filter", "/burn-rate", "/snapshot", "/broadcast", "/watchdog", "/top", "/ceiling", "/rename", "/copy",
|
|
285
290
|
]);
|
|
286
291
|
/** Resolve a slash command through the alias map. Returns the expanded command or the original. */
|
|
287
292
|
export function resolveAlias(line, aliases) {
|
|
@@ -829,6 +834,28 @@ export class TUI {
|
|
|
829
834
|
getActivityBuffer() {
|
|
830
835
|
return this.activityBuffer;
|
|
831
836
|
}
|
|
837
|
+
/**
|
|
838
|
+
* Return the stored pane output lines for a session (by 1-indexed number, ID, prefix, or title).
|
|
839
|
+
* Returns null if session not found or no output stored.
|
|
840
|
+
*/
|
|
841
|
+
getSessionOutput(sessionIdOrIndex) {
|
|
842
|
+
let sessionId;
|
|
843
|
+
if (typeof sessionIdOrIndex === "number") {
|
|
844
|
+
sessionId = this.sessions[sessionIdOrIndex - 1]?.id;
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
const needle = sessionIdOrIndex.toLowerCase();
|
|
848
|
+
const match = this.sessions.find((s) => s.id === sessionIdOrIndex || s.id.startsWith(needle) || s.title.toLowerCase() === needle);
|
|
849
|
+
sessionId = match?.id;
|
|
850
|
+
}
|
|
851
|
+
if (!sessionId)
|
|
852
|
+
return null;
|
|
853
|
+
return this.sessionOutputs.get(sessionId) ?? null;
|
|
854
|
+
}
|
|
855
|
+
/** Return the current drill-down session ID (for /copy default target). */
|
|
856
|
+
getDrilldownId() {
|
|
857
|
+
return this.drilldownSessionId;
|
|
858
|
+
}
|
|
832
859
|
/** Return per-session error counts (for /who). */
|
|
833
860
|
getSessionErrorCounts() {
|
|
834
861
|
return this.sessionErrorCounts;
|
|
@@ -1521,9 +1548,26 @@ export class TUI {
|
|
|
1521
1548
|
process.stderr.write(moveTo(startRow + 1, 1) + CLEAR_LINE + padded);
|
|
1522
1549
|
}
|
|
1523
1550
|
else if (this.compactMode) {
|
|
1524
|
-
// compact: inline tokens, multiple per row (with pin indicators)
|
|
1551
|
+
// compact: inline tokens, multiple per row (with pin indicators + health glyphs)
|
|
1552
|
+
const nowMsCompact = Date.now();
|
|
1525
1553
|
const noteIdSet = new Set(this.sessionNotes.keys());
|
|
1526
|
-
const
|
|
1554
|
+
const compactHealthScores = new Map();
|
|
1555
|
+
for (const s of visibleSessions) {
|
|
1556
|
+
const ceilingC = parseContextCeiling(s.contextTokens);
|
|
1557
|
+
const cfC = ceilingC ? ceilingC.current / ceilingC.max : null;
|
|
1558
|
+
const bhC = this.sessionContextHistory.get(s.id);
|
|
1559
|
+
const brC = bhC ? computeContextBurnRate(bhC, nowMsCompact) : null;
|
|
1560
|
+
const lcC = this.lastChangeAt.get(s.id);
|
|
1561
|
+
const idleC = lcC !== undefined ? nowMsCompact - lcC : null;
|
|
1562
|
+
compactHealthScores.set(s.id, computeHealthScore({
|
|
1563
|
+
errorCount: this.sessionErrorCounts.get(s.id) ?? 0,
|
|
1564
|
+
burnRatePerMin: brC,
|
|
1565
|
+
contextFraction: cfC,
|
|
1566
|
+
idleMs: idleC,
|
|
1567
|
+
watchdogThresholdMs: this.watchdogThresholdMs,
|
|
1568
|
+
}));
|
|
1569
|
+
}
|
|
1570
|
+
const compactRows = formatCompactRows(visibleSessions, innerWidth - 1, this.pinnedIds, this.mutedIds, noteIdSet, compactHealthScores);
|
|
1527
1571
|
for (let r = 0; r < compactRows.length; r++) {
|
|
1528
1572
|
const line = `${SLATE}${BOX.v}${RESET} ${compactRows[r]}`;
|
|
1529
1573
|
const padded = padBoxLine(line, this.cols);
|