aoaoe 0.79.0 → 0.80.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 +11 -0
- package/dist/input.d.ts +3 -0
- package/dist/input.js +21 -0
- package/dist/tui.d.ts +17 -5
- package/dist/tui.js +65 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -378,6 +378,17 @@ async function main() {
|
|
|
378
378
|
tui.setCompact(enabled);
|
|
379
379
|
tui.log("system", `compact mode: ${enabled ? "on" : "off"}`);
|
|
380
380
|
});
|
|
381
|
+
// wire /pin toggle
|
|
382
|
+
input.onPin((target) => {
|
|
383
|
+
const num = /^\d+$/.test(target) ? parseInt(target, 10) : undefined;
|
|
384
|
+
const ok = tui.togglePin(num ?? target);
|
|
385
|
+
if (ok) {
|
|
386
|
+
tui.log("system", `pin toggled: ${target}`);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
tui.log("system", `session not found: ${target}`);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
381
392
|
// wire mouse move to hover highlight on session cards (disabled in compact)
|
|
382
393
|
input.onMouseMove((row, _col) => {
|
|
383
394
|
if (tui.getViewMode() === "overview" && !tui.isCompact()) {
|
package/dist/input.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type SearchHandler = (pattern: string | null) => void;
|
|
|
5
5
|
export type QuickSwitchHandler = (sessionNum: number) => void;
|
|
6
6
|
export type SortHandler = (mode: string | null) => void;
|
|
7
7
|
export type CompactHandler = () => void;
|
|
8
|
+
export type PinHandler = (target: string) => void;
|
|
8
9
|
export interface MouseEvent {
|
|
9
10
|
button: number;
|
|
10
11
|
col: number;
|
|
@@ -32,6 +33,7 @@ export declare class InputReader {
|
|
|
32
33
|
private quickSwitchHandler;
|
|
33
34
|
private sortHandler;
|
|
34
35
|
private compactHandler;
|
|
36
|
+
private pinHandler;
|
|
35
37
|
private mouseDataListener;
|
|
36
38
|
onScroll(handler: (dir: ScrollDirection) => void): void;
|
|
37
39
|
onQueueChange(handler: (count: number) => void): void;
|
|
@@ -43,6 +45,7 @@ export declare class InputReader {
|
|
|
43
45
|
onQuickSwitch(handler: QuickSwitchHandler): void;
|
|
44
46
|
onSort(handler: SortHandler): void;
|
|
45
47
|
onCompact(handler: CompactHandler): void;
|
|
48
|
+
onPin(handler: PinHandler): void;
|
|
46
49
|
private notifyQueueChange;
|
|
47
50
|
start(): void;
|
|
48
51
|
drain(): string[];
|
package/dist/input.js
CHANGED
|
@@ -37,6 +37,7 @@ export class InputReader {
|
|
|
37
37
|
quickSwitchHandler = null;
|
|
38
38
|
sortHandler = null;
|
|
39
39
|
compactHandler = null;
|
|
40
|
+
pinHandler = null;
|
|
40
41
|
mouseDataListener = null;
|
|
41
42
|
// register a callback for scroll key events (PgUp/PgDn/Home/End)
|
|
42
43
|
onScroll(handler) {
|
|
@@ -78,6 +79,10 @@ export class InputReader {
|
|
|
78
79
|
onCompact(handler) {
|
|
79
80
|
this.compactHandler = handler;
|
|
80
81
|
}
|
|
82
|
+
// register a callback for pin/unpin commands (/pin <target>)
|
|
83
|
+
onPin(handler) {
|
|
84
|
+
this.pinHandler = handler;
|
|
85
|
+
}
|
|
81
86
|
notifyQueueChange() {
|
|
82
87
|
this.queueChangeHandler?.(this.queue.length);
|
|
83
88
|
}
|
|
@@ -257,6 +262,7 @@ ${BOLD}navigation:${RESET}
|
|
|
257
262
|
/back return to overview from drill-down
|
|
258
263
|
/sort [mode] sort sessions: status, name, activity, default (or cycle)
|
|
259
264
|
/compact toggle compact mode (dense session panel)
|
|
265
|
+
/pin [N|name] pin/unpin a session to the top (toggle)
|
|
260
266
|
/search <pattern> filter activity entries by substring (case-insensitive)
|
|
261
267
|
/search clear active search filter
|
|
262
268
|
click session click an agent card to drill down (click again to go back)
|
|
@@ -354,6 +360,21 @@ ${BOLD}other:${RESET}
|
|
|
354
360
|
console.error(`${DIM}compact mode not available (no TUI)${RESET}`);
|
|
355
361
|
}
|
|
356
362
|
break;
|
|
363
|
+
case "/pin": {
|
|
364
|
+
const pinArg = line.slice("/pin".length).trim();
|
|
365
|
+
if (this.pinHandler) {
|
|
366
|
+
if (pinArg) {
|
|
367
|
+
this.pinHandler(pinArg);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
console.error(`${DIM}usage: /pin <N|name> — toggle pin for a session${RESET}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
console.error(`${DIM}pin not available (no TUI)${RESET}`);
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
357
378
|
case "/search": {
|
|
358
379
|
const searchArg = line.slice("/search".length).trim();
|
|
359
380
|
if (this.searchHandler) {
|
package/dist/tui.d.ts
CHANGED
|
@@ -2,18 +2,20 @@ import type { DaemonSessionState, DaemonPhase } from "./types.js";
|
|
|
2
2
|
import type { HistoryEntry } from "./tui-history.js";
|
|
3
3
|
export type SortMode = "default" | "status" | "name" | "activity";
|
|
4
4
|
declare const SORT_MODES: SortMode[];
|
|
5
|
-
/** Sort sessions by mode. Returns a new array (never mutates). */
|
|
6
|
-
declare function sortSessions(sessions: DaemonSessionState[], mode: SortMode, lastChangeAt?: Map<string, number>): DaemonSessionState[];
|
|
5
|
+
/** Sort sessions by mode. Pinned sessions always sort first (stable). Returns a new array (never mutates). */
|
|
6
|
+
declare function sortSessions(sessions: DaemonSessionState[], mode: SortMode, lastChangeAt?: Map<string, number>, pinnedIds?: Set<string>): DaemonSessionState[];
|
|
7
7
|
/** Cycle to the next sort mode. */
|
|
8
8
|
declare function nextSortMode(current: SortMode): SortMode;
|
|
9
9
|
/** Max name length in compact token. */
|
|
10
10
|
declare const COMPACT_NAME_LEN = 10;
|
|
11
|
+
/** Pin indicator for pinned sessions. */
|
|
12
|
+
declare const PIN_ICON = "\u25B2";
|
|
11
13
|
/**
|
|
12
14
|
* Format sessions as inline compact tokens, wrapped to fit maxWidth.
|
|
13
|
-
* Each token: "{idx}{dot}{name}" — e.g. "1
|
|
15
|
+
* Each token: "{idx}{pin?}{dot}{name}" — e.g. "1▲●Alpha" for pinned, "2●Bravo" for unpinned.
|
|
14
16
|
* Returns array of formatted row strings (one per display row).
|
|
15
17
|
*/
|
|
16
|
-
declare function formatCompactRows(sessions: DaemonSessionState[], maxWidth: number): string[];
|
|
18
|
+
declare function formatCompactRows(sessions: DaemonSessionState[], maxWidth: number, pinnedIds?: Set<string>): string[];
|
|
17
19
|
/** Compute how many display rows compact mode needs (minimum 1). */
|
|
18
20
|
declare function computeCompactRowCount(sessions: DaemonSessionState[], maxWidth: number): number;
|
|
19
21
|
declare function phaseDisplay(phase: DaemonPhase, paused: boolean, spinnerFrame: number): string;
|
|
@@ -46,6 +48,7 @@ export declare class TUI {
|
|
|
46
48
|
private lastChangeAt;
|
|
47
49
|
private prevLastActivity;
|
|
48
50
|
private compactMode;
|
|
51
|
+
private pinnedIds;
|
|
49
52
|
private viewMode;
|
|
50
53
|
private drilldownSessionId;
|
|
51
54
|
private sessionOutputs;
|
|
@@ -71,6 +74,15 @@ export declare class TUI {
|
|
|
71
74
|
setCompact(enabled: boolean): void;
|
|
72
75
|
/** Return whether compact mode is enabled. */
|
|
73
76
|
isCompact(): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Toggle pin for a session (by 1-indexed number, ID, ID prefix, or title).
|
|
79
|
+
* Pinned sessions always sort to the top. Returns true if session found.
|
|
80
|
+
*/
|
|
81
|
+
togglePin(sessionIdOrIndex: string | number): boolean;
|
|
82
|
+
/** Check if a session ID is pinned. */
|
|
83
|
+
isPinned(id: string): boolean;
|
|
84
|
+
/** Return count of pinned sessions. */
|
|
85
|
+
getPinnedCount(): number;
|
|
74
86
|
updateState(opts: {
|
|
75
87
|
phase?: DaemonPhase;
|
|
76
88
|
pollCount?: number;
|
|
@@ -161,5 +173,5 @@ declare function formatSearchIndicator(pattern: string, matchCount: number, tota
|
|
|
161
173
|
* (row = headerHeight + 2 + i for 0-indexed session i)
|
|
162
174
|
*/
|
|
163
175
|
export declare function hitTestSession(row: number, headerHeight: number, sessionCount: number): number | null;
|
|
164
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN };
|
|
176
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN, PIN_ICON };
|
|
165
177
|
//# sourceMappingURL=tui.d.ts.map
|
package/dist/tui.js
CHANGED
|
@@ -26,8 +26,8 @@ const STATUS_PRIORITY = {
|
|
|
26
26
|
error: 0, waiting: 1, working: 2, running: 2,
|
|
27
27
|
idle: 3, done: 4, stopped: 5, unknown: 6,
|
|
28
28
|
};
|
|
29
|
-
/** Sort sessions by mode. Returns a new array (never mutates). */
|
|
30
|
-
function sortSessions(sessions, mode, lastChangeAt) {
|
|
29
|
+
/** Sort sessions by mode. Pinned sessions always sort first (stable). Returns a new array (never mutates). */
|
|
30
|
+
function sortSessions(sessions, mode, lastChangeAt, pinnedIds) {
|
|
31
31
|
const copy = sessions.slice();
|
|
32
32
|
switch (mode) {
|
|
33
33
|
case "status":
|
|
@@ -41,6 +41,10 @@ function sortSessions(sessions, mode, lastChangeAt) {
|
|
|
41
41
|
break;
|
|
42
42
|
// "default" — preserve original order
|
|
43
43
|
}
|
|
44
|
+
// stable-sort pinned to top (preserves mode order within each group)
|
|
45
|
+
if (pinnedIds && pinnedIds.size > 0) {
|
|
46
|
+
copy.sort((a, b) => (pinnedIds.has(a.id) ? 0 : 1) - (pinnedIds.has(b.id) ? 0 : 1));
|
|
47
|
+
}
|
|
44
48
|
return copy;
|
|
45
49
|
}
|
|
46
50
|
/** Cycle to the next sort mode. */
|
|
@@ -51,12 +55,14 @@ function nextSortMode(current) {
|
|
|
51
55
|
// ── Compact mode ────────────────────────────────────────────────────────────
|
|
52
56
|
/** Max name length in compact token. */
|
|
53
57
|
const COMPACT_NAME_LEN = 10;
|
|
58
|
+
/** Pin indicator for pinned sessions. */
|
|
59
|
+
const PIN_ICON = "▲";
|
|
54
60
|
/**
|
|
55
61
|
* Format sessions as inline compact tokens, wrapped to fit maxWidth.
|
|
56
|
-
* Each token: "{idx}{dot}{name}" — e.g. "1
|
|
62
|
+
* Each token: "{idx}{pin?}{dot}{name}" — e.g. "1▲●Alpha" for pinned, "2●Bravo" for unpinned.
|
|
57
63
|
* Returns array of formatted row strings (one per display row).
|
|
58
64
|
*/
|
|
59
|
-
function formatCompactRows(sessions, maxWidth) {
|
|
65
|
+
function formatCompactRows(sessions, maxWidth, pinnedIds) {
|
|
60
66
|
if (sessions.length === 0)
|
|
61
67
|
return [`${DIM}no agents connected${RESET}`];
|
|
62
68
|
const tokens = [];
|
|
@@ -65,9 +71,11 @@ function formatCompactRows(sessions, maxWidth) {
|
|
|
65
71
|
const s = sessions[i];
|
|
66
72
|
const idx = String(i + 1);
|
|
67
73
|
const dot = STATUS_DOT[s.status] ?? `${AMBER}${DOT.filled}${RESET}`;
|
|
74
|
+
const pinned = pinnedIds?.has(s.id) ?? false;
|
|
75
|
+
const pin = pinned ? `${AMBER}${PIN_ICON}${RESET}` : "";
|
|
68
76
|
const name = truncatePlain(s.title, COMPACT_NAME_LEN);
|
|
69
|
-
tokens.push(`${SLATE}${idx}${RESET}${dot}${BOLD}${name}${RESET}`);
|
|
70
|
-
widths.push(idx.length + 1
|
|
77
|
+
tokens.push(`${SLATE}${idx}${RESET}${pin}${dot}${BOLD}${name}${RESET}`);
|
|
78
|
+
widths.push(idx.length + (pinned ? 1 : 0) + 1 + name.length);
|
|
71
79
|
}
|
|
72
80
|
const rows = [];
|
|
73
81
|
let currentRow = "";
|
|
@@ -141,6 +149,7 @@ export class TUI {
|
|
|
141
149
|
lastChangeAt = new Map(); // session ID → epoch ms of last activity change
|
|
142
150
|
prevLastActivity = new Map(); // session ID → previous lastActivity string
|
|
143
151
|
compactMode = false;
|
|
152
|
+
pinnedIds = new Set(); // pinned session IDs (always sort to top)
|
|
144
153
|
// drill-down mode: show a single session's full output
|
|
145
154
|
viewMode = "overview";
|
|
146
155
|
drilldownSessionId = null;
|
|
@@ -203,7 +212,7 @@ export class TUI {
|
|
|
203
212
|
return;
|
|
204
213
|
this.sortMode = mode;
|
|
205
214
|
// re-sort current sessions and repaint
|
|
206
|
-
this.sessions = sortSessions(this.sessions, this.sortMode, this.lastChangeAt);
|
|
215
|
+
this.sessions = sortSessions(this.sessions, this.sortMode, this.lastChangeAt, this.pinnedIds);
|
|
207
216
|
if (this.active) {
|
|
208
217
|
this.paintSessions();
|
|
209
218
|
}
|
|
@@ -226,6 +235,43 @@ export class TUI {
|
|
|
226
235
|
isCompact() {
|
|
227
236
|
return this.compactMode;
|
|
228
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Toggle pin for a session (by 1-indexed number, ID, ID prefix, or title).
|
|
240
|
+
* Pinned sessions always sort to the top. Returns true if session found.
|
|
241
|
+
*/
|
|
242
|
+
togglePin(sessionIdOrIndex) {
|
|
243
|
+
let sessionId;
|
|
244
|
+
if (typeof sessionIdOrIndex === "number") {
|
|
245
|
+
sessionId = this.sessions[sessionIdOrIndex - 1]?.id;
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
const needle = sessionIdOrIndex.toLowerCase();
|
|
249
|
+
const match = this.sessions.find((s) => s.id === sessionIdOrIndex || s.id.startsWith(needle) || s.title.toLowerCase() === needle);
|
|
250
|
+
sessionId = match?.id;
|
|
251
|
+
}
|
|
252
|
+
if (!sessionId)
|
|
253
|
+
return false;
|
|
254
|
+
if (this.pinnedIds.has(sessionId)) {
|
|
255
|
+
this.pinnedIds.delete(sessionId);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
this.pinnedIds.add(sessionId);
|
|
259
|
+
}
|
|
260
|
+
// re-sort and repaint
|
|
261
|
+
this.sessions = sortSessions(this.sessions, this.sortMode, this.lastChangeAt, this.pinnedIds);
|
|
262
|
+
if (this.active) {
|
|
263
|
+
this.paintSessions();
|
|
264
|
+
}
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
/** Check if a session ID is pinned. */
|
|
268
|
+
isPinned(id) {
|
|
269
|
+
return this.pinnedIds.has(id);
|
|
270
|
+
}
|
|
271
|
+
/** Return count of pinned sessions. */
|
|
272
|
+
getPinnedCount() {
|
|
273
|
+
return this.pinnedIds.size;
|
|
274
|
+
}
|
|
229
275
|
// ── State updates ───────────────────────────────────────────────────────
|
|
230
276
|
updateState(opts) {
|
|
231
277
|
if (opts.phase !== undefined)
|
|
@@ -251,7 +297,7 @@ export class TUI {
|
|
|
251
297
|
if (s.lastActivity !== undefined)
|
|
252
298
|
this.prevLastActivity.set(s.id, s.lastActivity);
|
|
253
299
|
}
|
|
254
|
-
const sorted = sortSessions(opts.sessions, this.sortMode, this.lastChangeAt);
|
|
300
|
+
const sorted = sortSessions(opts.sessions, this.sortMode, this.lastChangeAt, this.pinnedIds);
|
|
255
301
|
const sessionCountChanged = sorted.length !== this.sessions.length;
|
|
256
302
|
this.sessions = sorted;
|
|
257
303
|
if (sessionCountChanged) {
|
|
@@ -596,8 +642,8 @@ export class TUI {
|
|
|
596
642
|
process.stderr.write(moveTo(startRow + 1, 1) + CLEAR_LINE + padded);
|
|
597
643
|
}
|
|
598
644
|
else if (this.compactMode) {
|
|
599
|
-
// compact: inline tokens, multiple per row
|
|
600
|
-
const compactRows = formatCompactRows(this.sessions, innerWidth - 1);
|
|
645
|
+
// compact: inline tokens, multiple per row (with pin indicators)
|
|
646
|
+
const compactRows = formatCompactRows(this.sessions, innerWidth - 1, this.pinnedIds);
|
|
601
647
|
for (let r = 0; r < compactRows.length; r++) {
|
|
602
648
|
const line = `${SLATE}${BOX.v}${RESET} ${compactRows[r]}`;
|
|
603
649
|
const padded = padBoxLine(line, this.cols);
|
|
@@ -609,7 +655,10 @@ export class TUI {
|
|
|
609
655
|
const s = this.sessions[i];
|
|
610
656
|
const isHovered = this.hoverSessionIdx === i + 1; // 1-indexed
|
|
611
657
|
const bg = isHovered ? BG_HOVER : "";
|
|
612
|
-
const
|
|
658
|
+
const pinned = this.pinnedIds.has(s.id);
|
|
659
|
+
const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
|
|
660
|
+
const cardWidth = pinned ? innerWidth - 3 : innerWidth - 1; // pin takes 2 extra chars
|
|
661
|
+
const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${formatSessionCard(s, cardWidth)}`;
|
|
613
662
|
const padded = padBoxLineHover(line, this.cols, isHovered);
|
|
614
663
|
process.stderr.write(moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded);
|
|
615
664
|
}
|
|
@@ -639,7 +688,10 @@ export class TUI {
|
|
|
639
688
|
const s = this.sessions[i];
|
|
640
689
|
const isHovered = this.hoverSessionIdx === idx;
|
|
641
690
|
const bg = isHovered ? BG_HOVER : "";
|
|
642
|
-
const
|
|
691
|
+
const pinned = this.pinnedIds.has(s.id);
|
|
692
|
+
const pin = pinned ? `${AMBER}${PIN_ICON}${RESET} ` : "";
|
|
693
|
+
const cardWidth = pinned ? innerWidth - 3 : innerWidth - 1;
|
|
694
|
+
const line = `${bg}${SLATE}${BOX.v}${RESET}${bg} ${pin}${formatSessionCard(s, cardWidth)}`;
|
|
643
695
|
const padded = padBoxLineHover(line, this.cols, isHovered);
|
|
644
696
|
process.stderr.write(SAVE_CURSOR + moveTo(startRow + 1 + i, 1) + CLEAR_LINE + padded + RESTORE_CURSOR);
|
|
645
697
|
}
|
|
@@ -1006,5 +1058,5 @@ export function hitTestSession(row, headerHeight, sessionCount) {
|
|
|
1006
1058
|
return row - firstSessionRow + 1; // 1-indexed
|
|
1007
1059
|
}
|
|
1008
1060
|
// ── Exported pure helpers (for testing) ─────────────────────────────────────
|
|
1009
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN };
|
|
1061
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padBoxLineHover, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator, formatDrilldownScrollIndicator, formatPrompt, formatDrilldownHeader, matchesSearch, formatSearchIndicator, computeSparkline, formatSparkline, sortSessions, nextSortMode, SORT_MODES, formatCompactRows, computeCompactRowCount, COMPACT_NAME_LEN, PIN_ICON };
|
|
1010
1062
|
//# sourceMappingURL=tui.js.map
|