aoaoe 0.60.0 → 0.61.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/README.md CHANGED
@@ -497,6 +497,7 @@ src/
497
497
  dashboard.ts # periodic CLI status table with task column
498
498
  daemon-state.ts # shared IPC state file + interrupt flag
499
499
  tui.ts # in-place terminal UI (alternate screen, scroll regions)
500
+ tui-history.ts # persisted TUI history (JSONL file with rotation, replay on startup)
500
501
  input.ts # stdin readline listener with inject() for post-interrupt
501
502
  init.ts # `aoaoe init`: auto-discover tools, sessions, generate config
502
503
  notify.ts # webhook + Slack notification dispatcher for daemon events
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ import { TUI } 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";
22
+ import { loadTuiHistory } from "./tui-history.js";
22
23
  import { actionSession, actionDetail, toActionLogEntry } from "./types.js";
23
24
  import { YELLOW, GREEN, DIM, BOLD, RED, RESET } from "./colors.js";
24
25
  import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync, chmodSync } from "node:fs";
@@ -230,6 +231,10 @@ async function main() {
230
231
  await reasonerConsole.start();
231
232
  // start TUI (alternate screen buffer) after input is ready
232
233
  if (tui) {
234
+ // replay persisted history from previous runs before entering alt screen
235
+ const history = loadTuiHistory();
236
+ if (history.length > 0)
237
+ tui.replayHistory(history);
233
238
  tui.start(pkg || "dev");
234
239
  tui.updateState({ reasonerName: config.observe ? "observe-only" : config.reasoner });
235
240
  // welcome banner — plain-English explanation of what's happening
@@ -0,0 +1,27 @@
1
+ /** JSONL entry format — extends ActivityEntry with epoch timestamp for filtering */
2
+ export interface HistoryEntry {
3
+ ts: number;
4
+ time: string;
5
+ tag: string;
6
+ text: string;
7
+ }
8
+ /**
9
+ * Append a single history entry to the JSONL file.
10
+ * Fire-and-forget — errors are silently swallowed so they never block the TUI.
11
+ * Rotates the file if it exceeds MAX_FILE_SIZE before appending.
12
+ */
13
+ export declare function appendHistoryEntry(entry: HistoryEntry, filePath?: string, maxSize?: number): void;
14
+ /**
15
+ * Load recent TUI history entries from the JSONL file.
16
+ * Returns the last `maxEntries` entries (default 200), newest last.
17
+ * Returns empty array if the file doesn't exist or is unreadable.
18
+ */
19
+ export declare function loadTuiHistory(maxEntries?: number, filePath?: string): HistoryEntry[];
20
+ /**
21
+ * Rotate the history file if it exceeds the size threshold.
22
+ * Renames current file to .old (overwriting any previous .old) and starts fresh.
23
+ */
24
+ export declare function rotateTuiHistory(filePath?: string, maxSize?: number): boolean;
25
+ /** Default history file path (for wiring in index.ts) */
26
+ export declare const TUI_HISTORY_FILE: string;
27
+ //# sourceMappingURL=tui-history.d.ts.map
@@ -0,0 +1,89 @@
1
+ // tui-history.ts — persisted TUI activity history
2
+ // JSONL file at ~/.aoaoe/tui-history.jsonl with rotation at 500KB.
3
+ // pure exported functions for testability — no classes, no singletons.
4
+ import { appendFileSync, readFileSync, renameSync, statSync, mkdirSync, existsSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
7
+ const AOAOE_DIR = join(homedir(), ".aoaoe");
8
+ const HISTORY_FILE = join(AOAOE_DIR, "tui-history.jsonl");
9
+ const HISTORY_OLD = join(AOAOE_DIR, "tui-history.jsonl.old");
10
+ const MAX_FILE_SIZE = 500 * 1024; // 500KB rotation threshold
11
+ /**
12
+ * Append a single history entry to the JSONL file.
13
+ * Fire-and-forget — errors are silently swallowed so they never block the TUI.
14
+ * Rotates the file if it exceeds MAX_FILE_SIZE before appending.
15
+ */
16
+ export function appendHistoryEntry(entry, filePath = HISTORY_FILE, maxSize = MAX_FILE_SIZE) {
17
+ try {
18
+ const dir = join(filePath, "..");
19
+ if (!existsSync(dir))
20
+ mkdirSync(dir, { recursive: true });
21
+ rotateTuiHistory(filePath, maxSize);
22
+ const line = JSON.stringify(entry) + "\n";
23
+ appendFileSync(filePath, line, "utf-8");
24
+ }
25
+ catch {
26
+ // fire-and-forget — never crash the daemon over history persistence
27
+ }
28
+ }
29
+ /**
30
+ * Load recent TUI history entries from the JSONL file.
31
+ * Returns the last `maxEntries` entries (default 200), newest last.
32
+ * Returns empty array if the file doesn't exist or is unreadable.
33
+ */
34
+ export function loadTuiHistory(maxEntries = 200, filePath = HISTORY_FILE) {
35
+ try {
36
+ if (!existsSync(filePath))
37
+ return [];
38
+ const content = readFileSync(filePath, "utf-8");
39
+ const lines = content.split("\n").filter((l) => l.trim());
40
+ const recent = lines.slice(-maxEntries);
41
+ const entries = [];
42
+ for (const line of recent) {
43
+ try {
44
+ const parsed = JSON.parse(line);
45
+ if (isValidEntry(parsed))
46
+ entries.push(parsed);
47
+ }
48
+ catch {
49
+ // skip malformed lines
50
+ }
51
+ }
52
+ return entries;
53
+ }
54
+ catch {
55
+ return [];
56
+ }
57
+ }
58
+ /**
59
+ * Rotate the history file if it exceeds the size threshold.
60
+ * Renames current file to .old (overwriting any previous .old) and starts fresh.
61
+ */
62
+ export function rotateTuiHistory(filePath = HISTORY_FILE, maxSize = MAX_FILE_SIZE) {
63
+ try {
64
+ if (!existsSync(filePath))
65
+ return false;
66
+ const size = statSync(filePath).size;
67
+ if (size < maxSize)
68
+ return false;
69
+ const oldPath = filePath + ".old";
70
+ renameSync(filePath, oldPath);
71
+ return true;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ /** Validate that a parsed JSON value has the shape of a HistoryEntry */
78
+ function isValidEntry(val) {
79
+ if (typeof val !== "object" || val === null)
80
+ return false;
81
+ const obj = val;
82
+ return (typeof obj.ts === "number" &&
83
+ typeof obj.time === "string" &&
84
+ typeof obj.tag === "string" &&
85
+ typeof obj.text === "string");
86
+ }
87
+ /** Default history file path (for wiring in index.ts) */
88
+ export const TUI_HISTORY_FILE = HISTORY_FILE;
89
+ //# sourceMappingURL=tui-history.js.map
package/dist/tui.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { DaemonSessionState, DaemonPhase } from "./types.js";
2
+ import type { HistoryEntry } from "./tui-history.js";
2
3
  declare function phaseDisplay(phase: DaemonPhase, paused: boolean, spinnerFrame: number): string;
3
4
  export interface ActivityEntry {
4
5
  time: string;
@@ -38,6 +39,7 @@ export declare class TUI {
38
39
  nextTickAt?: number;
39
40
  }): void;
40
41
  log(tag: string, text: string): void;
42
+ replayHistory(entries: HistoryEntry[]): void;
41
43
  private updateDimensions;
42
44
  private computeLayout;
43
45
  private onResize;
package/dist/tui.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { BOLD, DIM, RESET, GREEN, CYAN, WHITE, BG_DARK, INDIGO, TEAL, AMBER, SLATE, ROSE, LIME, SKY, BOX, SPINNER, DOT, } from "./colors.js";
2
+ import { appendHistoryEntry } from "./tui-history.js";
2
3
  // ── ANSI helpers ────────────────────────────────────────────────────────────
3
4
  const ESC = "\x1b";
4
5
  const CSI = `${ESC}[`;
@@ -140,6 +141,19 @@ export class TUI {
140
141
  }
141
142
  if (this.active)
142
143
  this.writeActivityLine(entry);
144
+ // persist to disk (fire-and-forget, never blocks)
145
+ appendHistoryEntry({ ts: now.getTime(), time, tag, text });
146
+ }
147
+ // populate activity buffer from persisted history before start()
148
+ // entries are loaded from the JSONL file and added to the in-memory buffer
149
+ replayHistory(entries) {
150
+ for (const e of entries) {
151
+ this.activityBuffer.push({ time: e.time, tag: e.tag, text: e.text });
152
+ }
153
+ // trim to max
154
+ if (this.activityBuffer.length > this.maxActivity) {
155
+ this.activityBuffer = this.activityBuffer.slice(-this.maxActivity);
156
+ }
143
157
  }
144
158
  // ── Layout computation ──────────────────────────────────────────────────
145
159
  updateDimensions() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.60.0",
3
+ "version": "0.61.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",