aoaoe 0.60.0 → 0.62.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 +2 -0
- package/dist/config.js +12 -1
- package/dist/index.js +7 -0
- package/dist/tui-history.d.ts +28 -0
- package/dist/tui-history.js +91 -0
- package/dist/tui.d.ts +2 -0
- package/dist/tui.js +14 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -342,6 +342,7 @@ Config lives at `~/.aoaoe/aoaoe.config.json` (canonical, written by `aoaoe init`
|
|
|
342
342
|
| `notifications.slackWebhookUrl` | Slack incoming webhook URL (block kit format) | (none) |
|
|
343
343
|
| `notifications.events` | Filter which events fire (omit to send all). Valid: `session_error`, `session_done`, `action_executed`, `action_failed`, `daemon_started`, `daemon_stopped` | (all) |
|
|
344
344
|
| `notifications.maxRetries` | Retry failed webhook deliveries with exponential backoff (1s, 2s, 4s, ...) | `0` (no retry) |
|
|
345
|
+
| `tuiHistoryRetentionDays` | How many days of TUI history to replay on startup (1-365) | `7` |
|
|
345
346
|
|
|
346
347
|
Also reads `.aoaoe.json` as an alternative config filename.
|
|
347
348
|
|
|
@@ -497,6 +498,7 @@ src/
|
|
|
497
498
|
dashboard.ts # periodic CLI status table with task column
|
|
498
499
|
daemon-state.ts # shared IPC state file + interrupt flag
|
|
499
500
|
tui.ts # in-place terminal UI (alternate screen, scroll regions)
|
|
501
|
+
tui-history.ts # persisted TUI history (JSONL file with rotation, replay on startup)
|
|
500
502
|
input.ts # stdin readline listener with inject() for post-interrupt
|
|
501
503
|
init.ts # `aoaoe init`: auto-discover tools, sessions, generate config
|
|
502
504
|
notify.ts # webhook + Slack notification dispatcher for daemon events
|
package/dist/config.js
CHANGED
|
@@ -83,7 +83,7 @@ export function loadConfig(overrides) {
|
|
|
83
83
|
const KNOWN_KEYS = {
|
|
84
84
|
reasoner: true, pollIntervalMs: true, captureLinesCount: true,
|
|
85
85
|
verbose: true, dryRun: true, observe: true, confirm: true,
|
|
86
|
-
contextFiles: true, sessionDirs: true, protectedSessions: true, healthPort: true,
|
|
86
|
+
contextFiles: true, sessionDirs: true, protectedSessions: true, healthPort: true, tuiHistoryRetentionDays: true,
|
|
87
87
|
opencode: new Set(["port", "model"]),
|
|
88
88
|
claudeCode: new Set(["model", "yolo", "resume"]),
|
|
89
89
|
aoe: new Set(["profile"]),
|
|
@@ -133,6 +133,13 @@ export function validateConfig(config) {
|
|
|
133
133
|
errors.push(`healthPort must be 1-65535, got ${config.healthPort}`);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
+
// tuiHistoryRetentionDays: must be a positive integer, 1-365
|
|
137
|
+
if (config.tuiHistoryRetentionDays !== undefined) {
|
|
138
|
+
const d = config.tuiHistoryRetentionDays;
|
|
139
|
+
if (typeof d !== "number" || !isFinite(d) || !Number.isInteger(d) || d < 1 || d > 365) {
|
|
140
|
+
errors.push(`tuiHistoryRetentionDays must be an integer 1-365, got ${d}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
136
143
|
if (typeof config.policies?.maxErrorsBeforeRestart !== "number" || config.policies.maxErrorsBeforeRestart < 1) {
|
|
137
144
|
errors.push(`policies.maxErrorsBeforeRestart must be >= 1, got ${config.policies?.maxErrorsBeforeRestart}`);
|
|
138
145
|
}
|
|
@@ -540,6 +547,7 @@ example config:
|
|
|
540
547
|
"other-repo": "/path/to/other-repo"
|
|
541
548
|
},
|
|
542
549
|
"healthPort": 4098,
|
|
550
|
+
"tuiHistoryRetentionDays": 7,
|
|
543
551
|
"notifications": {
|
|
544
552
|
"webhookUrl": "https://example.com/webhook",
|
|
545
553
|
"slackWebhookUrl": "https://hooks.slack.com/services/T.../B.../xxx",
|
|
@@ -552,6 +560,9 @@ example config:
|
|
|
552
560
|
aoaoe loads AGENTS.md, claude.md, and other AI instruction files
|
|
553
561
|
from each project directory to give the reasoner per-session context.
|
|
554
562
|
|
|
563
|
+
tuiHistoryRetentionDays controls how many days of TUI history to replay
|
|
564
|
+
on daemon startup (default: 7, range: 1-365). History file rotates at 50MB.
|
|
565
|
+
|
|
555
566
|
notifications sends webhook alerts for daemon events. Both webhookUrl
|
|
556
567
|
and slackWebhookUrl are optional. events filters which events fire
|
|
557
568
|
(omit to send all). maxRetries enables exponential backoff retry on
|
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,12 @@ 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 retentionDays = config.tuiHistoryRetentionDays ?? 7;
|
|
236
|
+
const retentionMs = retentionDays * 24 * 60 * 60 * 1000;
|
|
237
|
+
const history = loadTuiHistory(200, undefined, retentionMs);
|
|
238
|
+
if (history.length > 0)
|
|
239
|
+
tui.replayHistory(history);
|
|
233
240
|
tui.start(pkg || "dev");
|
|
234
241
|
tui.updateState({ reasonerName: config.observe ? "observe-only" : config.reasoner });
|
|
235
242
|
// welcome banner — plain-English explanation of what's happening
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
* Filters out entries older than `maxAgeMs` (default: 7 days).
|
|
18
|
+
* Returns empty array if the file doesn't exist or is unreadable.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadTuiHistory(maxEntries?: number, filePath?: string, maxAgeMs?: number): HistoryEntry[];
|
|
21
|
+
/**
|
|
22
|
+
* Rotate the history file if it exceeds the size threshold.
|
|
23
|
+
* Renames current file to .old (overwriting any previous .old) and starts fresh.
|
|
24
|
+
*/
|
|
25
|
+
export declare function rotateTuiHistory(filePath?: string, maxSize?: number): boolean;
|
|
26
|
+
/** Default history file path (for wiring in index.ts) */
|
|
27
|
+
export declare const TUI_HISTORY_FILE: string;
|
|
28
|
+
//# sourceMappingURL=tui-history.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// tui-history.ts — persisted TUI activity history
|
|
2
|
+
// JSONL file at ~/.aoaoe/tui-history.jsonl with rotation at 50MB.
|
|
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 = 50 * 1024 * 1024; // 50MB 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
|
+
* Filters out entries older than `maxAgeMs` (default: 7 days).
|
|
33
|
+
* Returns empty array if the file doesn't exist or is unreadable.
|
|
34
|
+
*/
|
|
35
|
+
export function loadTuiHistory(maxEntries = 200, filePath = HISTORY_FILE, maxAgeMs = 7 * 24 * 60 * 60 * 1000) {
|
|
36
|
+
try {
|
|
37
|
+
if (!existsSync(filePath))
|
|
38
|
+
return [];
|
|
39
|
+
const content = readFileSync(filePath, "utf-8");
|
|
40
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
41
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
42
|
+
const recent = lines.slice(-maxEntries * 2); // read extra to compensate for age filtering
|
|
43
|
+
const entries = [];
|
|
44
|
+
for (const line of recent) {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(line);
|
|
47
|
+
if (isValidEntry(parsed) && parsed.ts >= cutoff)
|
|
48
|
+
entries.push(parsed);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// skip malformed lines
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return entries.slice(-maxEntries);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Rotate the history file if it exceeds the size threshold.
|
|
62
|
+
* Renames current file to .old (overwriting any previous .old) and starts fresh.
|
|
63
|
+
*/
|
|
64
|
+
export function rotateTuiHistory(filePath = HISTORY_FILE, maxSize = MAX_FILE_SIZE) {
|
|
65
|
+
try {
|
|
66
|
+
if (!existsSync(filePath))
|
|
67
|
+
return false;
|
|
68
|
+
const size = statSync(filePath).size;
|
|
69
|
+
if (size < maxSize)
|
|
70
|
+
return false;
|
|
71
|
+
const oldPath = filePath + ".old";
|
|
72
|
+
renameSync(filePath, oldPath);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Validate that a parsed JSON value has the shape of a HistoryEntry */
|
|
80
|
+
function isValidEntry(val) {
|
|
81
|
+
if (typeof val !== "object" || val === null)
|
|
82
|
+
return false;
|
|
83
|
+
const obj = val;
|
|
84
|
+
return (typeof obj.ts === "number" &&
|
|
85
|
+
typeof obj.time === "string" &&
|
|
86
|
+
typeof obj.tag === "string" &&
|
|
87
|
+
typeof obj.text === "string");
|
|
88
|
+
}
|
|
89
|
+
/** Default history file path (for wiring in index.ts) */
|
|
90
|
+
export const TUI_HISTORY_FILE = HISTORY_FILE;
|
|
91
|
+
//# 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/dist/types.d.ts
CHANGED
|
@@ -122,6 +122,7 @@ export interface AoaoeConfig {
|
|
|
122
122
|
maxRetries?: number;
|
|
123
123
|
};
|
|
124
124
|
healthPort?: number;
|
|
125
|
+
tuiHistoryRetentionDays?: number;
|
|
125
126
|
}
|
|
126
127
|
export type NotificationEvent = "session_error" | "session_done" | "action_executed" | "action_failed" | "daemon_started" | "daemon_stopped";
|
|
127
128
|
export type DaemonPhase = "sleeping" | "polling" | "reasoning" | "executing" | "interrupted";
|