aoaoe 0.64.0 → 0.65.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 +19 -0
- package/dist/input.d.ts +3 -0
- package/dist/input.js +22 -0
- package/dist/tui.d.ts +13 -1
- package/dist/tui.js +77 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -235,6 +235,25 @@ async function main() {
|
|
|
235
235
|
// start interactive input listener and conversation log
|
|
236
236
|
input.start();
|
|
237
237
|
await reasonerConsole.start();
|
|
238
|
+
// wire scroll keys to TUI (PgUp/PgDn/Home/End)
|
|
239
|
+
if (tui) {
|
|
240
|
+
input.onScroll((dir) => {
|
|
241
|
+
switch (dir) {
|
|
242
|
+
case "up":
|
|
243
|
+
tui.scrollUp();
|
|
244
|
+
break;
|
|
245
|
+
case "down":
|
|
246
|
+
tui.scrollDown();
|
|
247
|
+
break;
|
|
248
|
+
case "top":
|
|
249
|
+
tui.scrollToTop();
|
|
250
|
+
break;
|
|
251
|
+
case "bottom":
|
|
252
|
+
tui.scrollToBottom();
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
238
257
|
// start TUI (alternate screen buffer) after input is ready
|
|
239
258
|
if (tui) {
|
|
240
259
|
// replay persisted history from previous runs before entering alt screen
|
package/dist/input.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
export type ScrollDirection = "up" | "down" | "top" | "bottom";
|
|
1
2
|
export declare class InputReader {
|
|
2
3
|
private rl;
|
|
3
4
|
private queue;
|
|
4
5
|
private paused;
|
|
5
6
|
private lastEscTime;
|
|
7
|
+
private scrollHandler;
|
|
8
|
+
onScroll(handler: (dir: ScrollDirection) => void): void;
|
|
6
9
|
start(): void;
|
|
7
10
|
drain(): string[];
|
|
8
11
|
isPaused(): boolean;
|
package/dist/input.js
CHANGED
|
@@ -11,6 +11,11 @@ export class InputReader {
|
|
|
11
11
|
queue = []; // pending user messages for the reasoner
|
|
12
12
|
paused = false;
|
|
13
13
|
lastEscTime = 0;
|
|
14
|
+
scrollHandler = null;
|
|
15
|
+
// register a callback for scroll key events (PgUp/PgDn/Home/End)
|
|
16
|
+
onScroll(handler) {
|
|
17
|
+
this.scrollHandler = handler;
|
|
18
|
+
}
|
|
14
19
|
start() {
|
|
15
20
|
// only works if stdin is a TTY (not piped)
|
|
16
21
|
if (!process.stdin.isTTY)
|
|
@@ -39,6 +44,21 @@ export class InputReader {
|
|
|
39
44
|
else {
|
|
40
45
|
this.lastEscTime = 0;
|
|
41
46
|
}
|
|
47
|
+
// scroll key detection (PgUp, PgDn, Home, End)
|
|
48
|
+
if (this.scrollHandler) {
|
|
49
|
+
if (key?.name === "pageup" || key?.sequence === "\x1b[5~") {
|
|
50
|
+
this.scrollHandler("up");
|
|
51
|
+
}
|
|
52
|
+
else if (key?.name === "pagedown" || key?.sequence === "\x1b[6~") {
|
|
53
|
+
this.scrollHandler("down");
|
|
54
|
+
}
|
|
55
|
+
else if (key?.name === "home" || key?.sequence === "\x1b[1~") {
|
|
56
|
+
this.scrollHandler("top");
|
|
57
|
+
}
|
|
58
|
+
else if (key?.name === "end" || key?.sequence === "\x1b[4~") {
|
|
59
|
+
this.scrollHandler("bottom");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
42
62
|
});
|
|
43
63
|
// show hint on startup
|
|
44
64
|
console.error(`${DIM}type a message to talk to the AI supervisor, /help for commands, ESC ESC to interrupt${RESET}`);
|
|
@@ -105,6 +125,8 @@ ${BOLD}controls:${RESET}
|
|
|
105
125
|
/resume resume the supervisor
|
|
106
126
|
/interrupt interrupt the AI mid-thought
|
|
107
127
|
ESC ESC same as /interrupt (shortcut)
|
|
128
|
+
PgUp / PgDn scroll through activity history
|
|
129
|
+
Home / End jump to oldest / return to live
|
|
108
130
|
|
|
109
131
|
${BOLD}info:${RESET}
|
|
110
132
|
/status show daemon state
|
package/dist/tui.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export declare class TUI {
|
|
|
20
20
|
private activityBuffer;
|
|
21
21
|
private maxActivity;
|
|
22
22
|
private spinnerFrame;
|
|
23
|
+
private scrollOffset;
|
|
24
|
+
private newWhileScrolled;
|
|
23
25
|
private phase;
|
|
24
26
|
private pollCount;
|
|
25
27
|
private sessions;
|
|
@@ -40,6 +42,11 @@ export declare class TUI {
|
|
|
40
42
|
}): void;
|
|
41
43
|
log(tag: string, text: string): void;
|
|
42
44
|
replayHistory(entries: HistoryEntry[]): void;
|
|
45
|
+
scrollUp(lines?: number): void;
|
|
46
|
+
scrollDown(lines?: number): void;
|
|
47
|
+
scrollToTop(): void;
|
|
48
|
+
scrollToBottom(): void;
|
|
49
|
+
isScrolledBack(): boolean;
|
|
43
50
|
private updateDimensions;
|
|
44
51
|
private computeLayout;
|
|
45
52
|
private onResize;
|
|
@@ -63,5 +70,10 @@ declare function truncatePlain(str: string, max: number): string;
|
|
|
63
70
|
* Kept for backward compatibility — used by non-TUI output paths.
|
|
64
71
|
*/
|
|
65
72
|
export declare function formatSessionSentence(s: DaemonSessionState, maxCols: number): string;
|
|
66
|
-
|
|
73
|
+
declare function computeScrollSlice(bufferLen: number, visibleLines: number, scrollOffset: number): {
|
|
74
|
+
start: number;
|
|
75
|
+
end: number;
|
|
76
|
+
};
|
|
77
|
+
declare function formatScrollIndicator(offset: number, totalEntries: number, visibleLines: number, newCount: number): string;
|
|
78
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator };
|
|
67
79
|
//# sourceMappingURL=tui.d.ts.map
|
package/dist/tui.js
CHANGED
|
@@ -55,6 +55,8 @@ export class TUI {
|
|
|
55
55
|
activityBuffer = []; // ring buffer for activity log
|
|
56
56
|
maxActivity = 500; // max entries to keep
|
|
57
57
|
spinnerFrame = 0; // current spinner animation frame
|
|
58
|
+
scrollOffset = 0; // 0 = live (bottom), >0 = scrolled back N entries
|
|
59
|
+
newWhileScrolled = 0; // entries added while user is scrolled back
|
|
58
60
|
// current state for repaints
|
|
59
61
|
phase = "sleeping";
|
|
60
62
|
pollCount = 0;
|
|
@@ -139,8 +141,16 @@ export class TUI {
|
|
|
139
141
|
if (this.activityBuffer.length > this.maxActivity) {
|
|
140
142
|
this.activityBuffer = this.activityBuffer.slice(-this.maxActivity);
|
|
141
143
|
}
|
|
142
|
-
if (this.active)
|
|
143
|
-
this.
|
|
144
|
+
if (this.active) {
|
|
145
|
+
if (this.scrollOffset > 0) {
|
|
146
|
+
// user is scrolled back — don't auto-scroll, just show indicator
|
|
147
|
+
this.newWhileScrolled++;
|
|
148
|
+
this.paintSeparator();
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this.writeActivityLine(entry);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
144
154
|
// persist to disk (fire-and-forget, never blocks)
|
|
145
155
|
appendHistoryEntry({ ts: now.getTime(), time, tag, text });
|
|
146
156
|
}
|
|
@@ -155,6 +165,48 @@ export class TUI {
|
|
|
155
165
|
this.activityBuffer = this.activityBuffer.slice(-this.maxActivity);
|
|
156
166
|
}
|
|
157
167
|
}
|
|
168
|
+
// ── Scroll navigation ────────────────────────────────────────────────────
|
|
169
|
+
scrollUp(lines) {
|
|
170
|
+
if (!this.active)
|
|
171
|
+
return;
|
|
172
|
+
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
173
|
+
const n = lines ?? Math.max(1, Math.floor(visibleLines / 2));
|
|
174
|
+
const maxOffset = Math.max(0, this.activityBuffer.length - visibleLines);
|
|
175
|
+
this.scrollOffset = Math.min(maxOffset, this.scrollOffset + n);
|
|
176
|
+
this.repaintActivityRegion();
|
|
177
|
+
this.paintSeparator();
|
|
178
|
+
}
|
|
179
|
+
scrollDown(lines) {
|
|
180
|
+
if (!this.active)
|
|
181
|
+
return;
|
|
182
|
+
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
183
|
+
const n = lines ?? Math.max(1, Math.floor(visibleLines / 2));
|
|
184
|
+
const wasScrolled = this.scrollOffset > 0;
|
|
185
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - n);
|
|
186
|
+
if (wasScrolled && this.scrollOffset === 0)
|
|
187
|
+
this.newWhileScrolled = 0;
|
|
188
|
+
this.repaintActivityRegion();
|
|
189
|
+
this.paintSeparator();
|
|
190
|
+
}
|
|
191
|
+
scrollToTop() {
|
|
192
|
+
if (!this.active)
|
|
193
|
+
return;
|
|
194
|
+
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
195
|
+
this.scrollOffset = Math.max(0, this.activityBuffer.length - visibleLines);
|
|
196
|
+
this.repaintActivityRegion();
|
|
197
|
+
this.paintSeparator();
|
|
198
|
+
}
|
|
199
|
+
scrollToBottom() {
|
|
200
|
+
if (!this.active)
|
|
201
|
+
return;
|
|
202
|
+
this.scrollOffset = 0;
|
|
203
|
+
this.newWhileScrolled = 0;
|
|
204
|
+
this.repaintActivityRegion();
|
|
205
|
+
this.paintSeparator();
|
|
206
|
+
}
|
|
207
|
+
isScrolledBack() {
|
|
208
|
+
return this.scrollOffset > 0;
|
|
209
|
+
}
|
|
158
210
|
// ── Layout computation ──────────────────────────────────────────────────
|
|
159
211
|
updateDimensions() {
|
|
160
212
|
this.cols = process.stderr.columns || 80;
|
|
@@ -245,8 +297,14 @@ export class TUI {
|
|
|
245
297
|
process.stderr.write(RESTORE_CURSOR);
|
|
246
298
|
}
|
|
247
299
|
paintSeparator() {
|
|
248
|
-
const hints = " esc esc: interrupt /help /explain /pause ";
|
|
249
300
|
const prefix = `${BOX.h}${BOX.h} activity `;
|
|
301
|
+
let hints;
|
|
302
|
+
if (this.scrollOffset > 0) {
|
|
303
|
+
hints = formatScrollIndicator(this.scrollOffset, this.activityBuffer.length, this.scrollBottom - this.scrollTop + 1, this.newWhileScrolled);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
hints = " esc esc: interrupt /help /explain /pause ";
|
|
307
|
+
}
|
|
250
308
|
const totalLen = prefix.length + hints.length;
|
|
251
309
|
const fill = Math.max(0, this.cols - totalLen);
|
|
252
310
|
const left = Math.floor(fill / 2);
|
|
@@ -265,7 +323,8 @@ export class TUI {
|
|
|
265
323
|
}
|
|
266
324
|
repaintActivityRegion() {
|
|
267
325
|
const visibleLines = this.scrollBottom - this.scrollTop + 1;
|
|
268
|
-
const
|
|
326
|
+
const { start, end } = computeScrollSlice(this.activityBuffer.length, visibleLines, this.scrollOffset);
|
|
327
|
+
const entries = this.activityBuffer.slice(start, end);
|
|
269
328
|
for (let i = 0; i < visibleLines; i++) {
|
|
270
329
|
const row = this.scrollTop + i;
|
|
271
330
|
if (i < entries.length) {
|
|
@@ -431,6 +490,19 @@ export function formatSessionSentence(s, maxCols) {
|
|
|
431
490
|
}
|
|
432
491
|
return truncateAnsi(`${dot} ${BOLD}${name}${RESET} ${tool} ${SLATE}—${RESET} ${statusDesc}`, maxCols);
|
|
433
492
|
}
|
|
493
|
+
// ── Scroll helpers (pure, exported for testing) ─────────────────────────────
|
|
494
|
+
// compute the slice indices for the activity buffer given scroll state
|
|
495
|
+
function computeScrollSlice(bufferLen, visibleLines, scrollOffset) {
|
|
496
|
+
const end = Math.max(0, bufferLen - scrollOffset);
|
|
497
|
+
const start = Math.max(0, end - visibleLines);
|
|
498
|
+
return { start, end };
|
|
499
|
+
}
|
|
500
|
+
// format the scroll indicator text for the separator bar
|
|
501
|
+
function formatScrollIndicator(offset, totalEntries, visibleLines, newCount) {
|
|
502
|
+
const position = totalEntries - offset;
|
|
503
|
+
const newTag = newCount > 0 ? ` ${newCount} new ↓` : "";
|
|
504
|
+
return ` ↑ ${offset} older │ ${position}/${totalEntries} │ PgUp/PgDn End=live${newTag} `;
|
|
505
|
+
}
|
|
434
506
|
// ── Exported pure helpers (for testing) ─────────────────────────────────────
|
|
435
|
-
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay };
|
|
507
|
+
export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay, computeScrollSlice, formatScrollIndicator };
|
|
436
508
|
//# sourceMappingURL=tui.js.map
|