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 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
- export { formatActivity, formatSessionCard, truncateAnsi, truncatePlain, padBoxLine, padToWidth, stripAnsiForLen, phaseDisplay };
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.writeActivityLine(entry);
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 entries = this.activityBuffer.slice(-visibleLines);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.64.0",
3
+ "version": "0.65.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",