aoaoe 0.49.0 → 0.51.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/chat.js CHANGED
@@ -53,8 +53,9 @@ function main() {
53
53
  lastSize = currSize;
54
54
  }
55
55
  }
56
- catch {
56
+ catch (e) {
57
57
  // file may be truncated or removed — reset so we pick up from start of new file
58
+ console.error(`[chat] conversation log read failed: ${e}`);
58
59
  lastSize = 0;
59
60
  }
60
61
  };
@@ -341,7 +342,8 @@ async function captureTmuxPane(tmuxName) {
341
342
  const result = await exec("tmux", ["capture-pane", "-t", tmuxName, "-p", "-S", "-100"], 5_000);
342
343
  return result.exitCode === 0 ? result.stdout : null;
343
344
  }
344
- catch {
345
+ catch (e) {
346
+ console.error(`[chat] tmux capture-pane failed for ${tmuxName}: ${e}`);
345
347
  return null;
346
348
  }
347
349
  }
@@ -437,7 +439,9 @@ function appendToInput(msg) {
437
439
  try {
438
440
  appendFileSync(INPUT_FILE, msg + "\n");
439
441
  }
440
- catch { }
442
+ catch (e) {
443
+ console.error(`[chat] pending-input write failed: ${e}`);
444
+ }
441
445
  }
442
446
  function replayLog() {
443
447
  if (!existsSync(CONVO_LOG))
@@ -449,7 +453,9 @@ function replayLog() {
449
453
  console.log(`${DIM}--- end of history ---${RESET}\n`);
450
454
  }
451
455
  }
452
- catch { }
456
+ catch (e) {
457
+ console.error(`[chat] conversation log replay failed: ${e}`);
458
+ }
453
459
  }
454
460
  export function colorize(text) {
455
461
  // first pass: colorize tick separator lines (──── tick #N ────)
package/dist/config.d.ts CHANGED
@@ -2,7 +2,10 @@ import type { AoaoeConfig } from "./types.js";
2
2
  export declare function findConfigFile(): string | null;
3
3
  export declare function configFileExists(): boolean;
4
4
  export declare function defaultConfigPath(): string;
5
- export declare function loadConfig(overrides?: Partial<AoaoeConfig>): AoaoeConfig;
5
+ export declare function loadConfig(overrides?: Partial<AoaoeConfig>): AoaoeConfig & {
6
+ _configPath?: string;
7
+ };
8
+ export declare function warnUnknownKeys(raw: unknown, source: string): void;
6
9
  export declare function validateConfig(config: AoaoeConfig): void;
7
10
  export declare function validateEnvironment(config: AoaoeConfig): Promise<void>;
8
11
  export declare function deepMerge(...objects: Record<string, unknown>[]): AoaoeConfig;
@@ -15,6 +18,8 @@ export declare function parseCliArgs(argv: string[]): {
15
18
  runTest: boolean;
16
19
  showTasks: boolean;
17
20
  showHistory: boolean;
21
+ showStatus: boolean;
22
+ showConfig: boolean;
18
23
  runInit: boolean;
19
24
  initForce: boolean;
20
25
  runTaskCli: boolean;
package/dist/config.js CHANGED
@@ -61,16 +61,56 @@ export function loadConfig(overrides) {
61
61
  const found = findConfigFile();
62
62
  if (found) {
63
63
  try {
64
- fileConfig = JSON.parse(readFileSync(found, "utf-8"));
64
+ const raw = JSON.parse(readFileSync(found, "utf-8"));
65
+ warnUnknownKeys(raw, found);
66
+ fileConfig = raw;
65
67
  log(`loaded config from ${found}`);
66
68
  }
67
69
  catch (e) {
68
- console.error(`warning: failed to parse ${found}, using defaults`);
70
+ if (e instanceof SyntaxError) {
71
+ console.error(`warning: failed to parse ${found}, using defaults`);
72
+ }
73
+ else {
74
+ throw e; // re-throw validation errors from warnUnknownKeys
75
+ }
69
76
  }
70
77
  }
71
78
  const config = deepMerge(DEFAULTS, fileConfig, (overrides ?? {}));
72
79
  validateConfig(config);
73
- return config;
80
+ return { ...config, _configPath: found ?? undefined };
81
+ }
82
+ // known top-level and nested config keys — used to warn on typos
83
+ const KNOWN_KEYS = {
84
+ reasoner: true, pollIntervalMs: true, captureLinesCount: true,
85
+ verbose: true, dryRun: true, observe: true, confirm: true,
86
+ contextFiles: true, sessionDirs: true, protectedSessions: true,
87
+ opencode: new Set(["port", "model"]),
88
+ claudeCode: new Set(["model", "yolo", "resume"]),
89
+ aoe: new Set(["profile"]),
90
+ policies: new Set([
91
+ "maxIdleBeforeNudgeMs", "maxErrorsBeforeRestart", "autoAnswerPermissions",
92
+ "actionCooldownMs", "userActivityThresholdMs", "allowDestructive",
93
+ ]),
94
+ };
95
+ export function warnUnknownKeys(raw, source) {
96
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
97
+ return;
98
+ const obj = raw;
99
+ for (const key of Object.keys(obj)) {
100
+ if (!(key in KNOWN_KEYS)) {
101
+ console.error(`warning: unknown config key "${key}" in ${source} (typo?)`);
102
+ continue;
103
+ }
104
+ // check nested keys for known sub-objects
105
+ const schema = KNOWN_KEYS[key];
106
+ if (schema instanceof Set && obj[key] && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
107
+ for (const subKey of Object.keys(obj[key])) {
108
+ if (!schema.has(subKey)) {
109
+ console.error(`warning: unknown config key "${key}.${subKey}" in ${source} (typo?)`);
110
+ }
111
+ }
112
+ }
113
+ }
74
114
  }
75
115
  // validate config values to catch bad configs at startup rather than runtime
76
116
  export function validateConfig(config) {
@@ -205,7 +245,7 @@ export function parseCliArgs(argv) {
205
245
  let initForce = false;
206
246
  let runTaskCli = false;
207
247
  let registerTitle;
208
- const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showHistory: false, runInit: false, initForce: false, runTaskCli: false };
248
+ const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showHistory: false, showStatus: false, showConfig: false, runInit: false, initForce: false, runTaskCli: false };
209
249
  // check for subcommand as first non-flag arg
210
250
  if (argv[2] === "test-context") {
211
251
  return { ...defaults, testContext: true };
@@ -222,6 +262,12 @@ export function parseCliArgs(argv) {
222
262
  if (argv[2] === "history") {
223
263
  return { ...defaults, showHistory: true };
224
264
  }
265
+ if (argv[2] === "status") {
266
+ return { ...defaults, showStatus: true };
267
+ }
268
+ if (argv[2] === "config") {
269
+ return { ...defaults, showConfig: true };
270
+ }
225
271
  if (argv[2] === "init") {
226
272
  const force = argv.includes("--force") || argv.includes("-f");
227
273
  return { ...defaults, runInit: true, initForce: force };
@@ -311,7 +357,7 @@ export function parseCliArgs(argv) {
311
357
  break;
312
358
  }
313
359
  }
314
- return { overrides, help, version, register: false, testContext: false, runTest: false, showTasks: false, showHistory: false, runInit: false, initForce: false, runTaskCli: false };
360
+ return { overrides, help, version, register: false, testContext: false, runTest: false, showTasks: false, showHistory: false, showStatus: false, showConfig: false, runInit: false, initForce: false, runTaskCli: false };
315
361
  }
316
362
  export function printHelp() {
317
363
  console.log(`aoaoe - autonomous supervisor for agent-of-empires sessions
@@ -327,6 +373,8 @@ getting started:
327
373
  commands:
328
374
  init detect tools + sessions, import history, generate config
329
375
  (none) start the supervisor daemon (interactive TUI)
376
+ status quick daemon health check (is it running? what's it doing?)
377
+ config show the effective resolved config (defaults + file)
330
378
  task manage tasks and sessions (list, start, stop, new, rm, edit)
331
379
  tasks show task progress (from aoaoe.tasks.json)
332
380
  history review recent actions (from ~/.aoaoe/actions.log)
package/dist/console.js CHANGED
@@ -91,7 +91,8 @@ export class ReasonerConsole {
91
91
  const st = statSync(INPUT_FILE);
92
92
  return st.size > 0;
93
93
  }
94
- catch {
94
+ catch (e) {
95
+ console.error(`[console] pending-input size check failed: ${e}`);
95
96
  return false;
96
97
  }
97
98
  }
@@ -133,7 +134,9 @@ export class ReasonerConsole {
133
134
  try {
134
135
  appendFileSync(CONVO_LOG, line + "\n");
135
136
  }
136
- catch { }
137
+ catch (e) {
138
+ console.error(`[console] conversation log write failed: ${e}`);
139
+ }
137
140
  // in inline mode, also print colorized output to stderr
138
141
  if (this.inlineMode) {
139
142
  process.stderr.write(colorizeConsoleLine(line) + "\n");
package/dist/context.js CHANGED
@@ -78,7 +78,8 @@ export function readContextFile(filePath) {
78
78
  evictCache();
79
79
  return content;
80
80
  }
81
- catch {
81
+ catch (e) {
82
+ console.error(`[context] context file read failed for ${filePath}: ${e}`);
82
83
  return "";
83
84
  }
84
85
  }
@@ -109,8 +110,9 @@ export function discoverContextFiles(dir) {
109
110
  seenInodes.add(inodeKey);
110
111
  }
111
112
  }
112
- catch {
113
+ catch (e) {
113
114
  // stat failed — still add by path to avoid silently dropping
115
+ console.error(`[context] inode de-dup stat failed for ${resolved}: ${e}`);
114
116
  }
115
117
  seenPaths.add(resolved);
116
118
  found.push(filePath);
package/dist/executor.js CHANGED
@@ -164,7 +164,8 @@ export class Executor {
164
164
  return this.logAction({ action: "create_agent", path, title, tool }, false, `path is not a directory: ${path}`);
165
165
  }
166
166
  }
167
- catch {
167
+ catch (e) {
168
+ console.error(`[executor] statSync failed for create_agent path ${path}: ${e}`);
168
169
  return this.logAction({ action: "create_agent", path, title, tool }, false, `cannot stat path: ${path}`);
169
170
  }
170
171
  // validate tool name
@@ -281,7 +282,9 @@ export class Executor {
281
282
  this.rotateLogIfNeeded();
282
283
  appendFileSync(LOG_FILE, JSON.stringify(entry) + "\n");
283
284
  }
284
- catch { } // best-effort, don't crash the daemon
285
+ catch (e) {
286
+ console.error(`[executor] action log write failed: ${e}`);
287
+ } // best-effort, don't crash the daemon
285
288
  }
286
289
  return entry;
287
290
  }
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { loadConfig, validateEnvironment, parseCliArgs, printHelp, configFileExists } from "./config.js";
2
+ import { loadConfig, validateEnvironment, parseCliArgs, printHelp, configFileExists, findConfigFile } from "./config.js";
3
3
  import { Poller, computeTmuxName } from "./poller.js";
4
4
  import { createReasoner } from "./reasoner/index.js";
5
5
  import { Executor } from "./executor.js";
6
6
  import { printDashboard } from "./dashboard.js";
7
7
  import { InputReader } from "./input.js";
8
8
  import { ReasonerConsole } from "./console.js";
9
- import { writeState, buildSessionStates, checkInterrupt, clearInterrupt, cleanupState, acquireLock } from "./daemon-state.js";
9
+ import { writeState, buildSessionStates, checkInterrupt, clearInterrupt, cleanupState, acquireLock, readState } from "./daemon-state.js";
10
10
  import { formatSessionSummaries, formatActionDetail, formatPlainEnglishAction, narrateObservation, summarizeRecentActions, friendlyError } from "./console.js";
11
11
  import { loadGlobalContext, resolveProjectDirWithSource, discoverContextFiles, loadSessionContext } from "./context.js";
12
12
  import { tick as loopTick } from "./loop.js";
@@ -16,6 +16,7 @@ import { classifyMessages, formatUserMessages, buildReceipts, shouldSkipSleep, h
16
16
  import { TaskManager, loadTaskDefinitions, loadTaskState, formatTaskTable } from "./task-manager.js";
17
17
  import { runTaskCli, handleTaskSlashCommand } from "./task-cli.js";
18
18
  import { TUI } from "./tui.js";
19
+ import { isDaemonRunningFromState } from "./chat.js";
19
20
  import { actionSession, actionDetail } from "./types.js";
20
21
  import { YELLOW, GREEN, DIM, BOLD, RED, RESET } from "./colors.js";
21
22
  import { readFileSync, existsSync, statSync, mkdirSync, writeFileSync, chmodSync } from "node:fs";
@@ -26,7 +27,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
26
27
  const AOAOE_DIR = join(homedir(), ".aoaoe"); // watch dir for wakeable sleep
27
28
  const INPUT_FILE = join(AOAOE_DIR, "pending-input.txt"); // file IPC from chat.ts
28
29
  async function main() {
29
- const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showHistory, runInit, initForce, runTaskCli: isTaskCli, registerTitle } = parseCliArgs(process.argv);
30
+ const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showHistory, showStatus, showConfig, runInit, initForce, runTaskCli: isTaskCli, registerTitle } = parseCliArgs(process.argv);
30
31
  if (help) {
31
32
  printHelp();
32
33
  process.exit(0);
@@ -66,6 +67,16 @@ async function main() {
66
67
  await showActionHistory();
67
68
  return;
68
69
  }
70
+ // `aoaoe status` -- quick one-shot daemon health check
71
+ if (showStatus) {
72
+ showDaemonStatus();
73
+ return;
74
+ }
75
+ // `aoaoe config` -- show effective resolved config
76
+ if (showConfig) {
77
+ showEffectiveConfig();
78
+ return;
79
+ }
69
80
  // `aoaoe task` -- task management CLI
70
81
  if (isTaskCli) {
71
82
  await runTaskCli(process.argv);
@@ -92,7 +103,9 @@ async function main() {
92
103
  console.error("");
93
104
  }
94
105
  }
95
- const config = loadConfig(overrides);
106
+ const configResult = loadConfig(overrides);
107
+ const configPath = configResult._configPath;
108
+ const config = configResult; // strip _configPath from type for downstream
96
109
  // acquire daemon lock — prevent two daemons from running simultaneously
97
110
  const lock = acquireLock();
98
111
  if (!lock.acquired) {
@@ -112,6 +125,7 @@ async function main() {
112
125
  console.error("");
113
126
  console.error(" aoaoe" + (pkg ? ` v${pkg}` : "") + " — autonomous supervisor");
114
127
  console.error(` reasoner: ${config.reasoner} | poll: ${config.pollIntervalMs / 1000}s`);
128
+ console.error(` config: ${configPath ?? "defaults (no config file found)"}`);
115
129
  if (config.observe)
116
130
  console.error(" OBSERVE MODE — watching only, no AI, no actions");
117
131
  else if (config.confirm)
@@ -210,6 +224,7 @@ async function main() {
210
224
  tui.log("system", `The AI supervisor is watching your agents and will help when needed.`);
211
225
  }
212
226
  tui.log("system", "");
227
+ tui.log("system", `config: ${configPath ?? "using defaults (no config file found)"}`);
213
228
  tui.log("system", "Type a message to talk to the AI, or use /help for commands.");
214
229
  tui.log("system", "Press ESC twice to interrupt the AI mid-thought.");
215
230
  tui.log("system", "");
@@ -1097,6 +1112,80 @@ async function runIntegrationTest() {
1097
1112
  // the integration test is a self-contained script that runs main() on import
1098
1113
  await import(testModule);
1099
1114
  }
1115
+ // `aoaoe status` -- quick one-shot health check: is the daemon running? what's it doing?
1116
+ function showDaemonStatus() {
1117
+ const state = readState();
1118
+ const running = isDaemonRunningFromState(state);
1119
+ const pkg = readPkgVersion();
1120
+ console.log("");
1121
+ console.log(` aoaoe${pkg ? ` v${pkg}` : ""} — daemon status`);
1122
+ console.log(` ${"─".repeat(50)}`);
1123
+ if (!running || !state) {
1124
+ console.log(` ${RED}●${RESET} daemon is ${BOLD}not running${RESET}`);
1125
+ const configPath = findConfigFile();
1126
+ console.log(` config: ${configPath ?? "none found (run 'aoaoe init')"}`);
1127
+ console.log("");
1128
+ console.log(" start with: aoaoe");
1129
+ console.log(" or observe: aoaoe --observe");
1130
+ console.log("");
1131
+ return;
1132
+ }
1133
+ // daemon is running — show details
1134
+ const elapsed = Date.now() - state.phaseStartedAt;
1135
+ const elapsedStr = elapsed < 60_000 ? `${Math.floor(elapsed / 1000)}s` : `${Math.floor(elapsed / 60_000)}m`;
1136
+ const phaseIcon = state.phase === "sleeping" ? `${DIM}○${RESET}` :
1137
+ state.phase === "reasoning" ? `${YELLOW}●${RESET}` :
1138
+ state.phase === "executing" ? `${GREEN}●${RESET}` :
1139
+ state.phase === "polling" ? `${YELLOW}○${RESET}` :
1140
+ `${RED}●${RESET}`;
1141
+ console.log(` ${GREEN}●${RESET} daemon is ${BOLD}running${RESET} (poll #${state.pollCount})`);
1142
+ console.log(` ${phaseIcon} phase: ${state.phase} (${elapsedStr})`);
1143
+ if (state.paused)
1144
+ console.log(` ${YELLOW}${BOLD} PAUSED${RESET}`);
1145
+ console.log(` poll interval: ${state.pollIntervalMs / 1000}s`);
1146
+ if (state.nextTickAt > Date.now()) {
1147
+ const countdown = Math.ceil((state.nextTickAt - Date.now()) / 1000);
1148
+ console.log(` next tick: ${countdown}s`);
1149
+ }
1150
+ console.log("");
1151
+ // sessions
1152
+ if (state.sessions.length === 0) {
1153
+ console.log(" no active sessions");
1154
+ }
1155
+ else {
1156
+ console.log(` ${state.sessions.length} session(s):`);
1157
+ for (const s of state.sessions) {
1158
+ const statusIcon = s.status === "working" || s.status === "running" ? `${GREEN}●${RESET}` :
1159
+ s.status === "idle" ? `${DIM}○${RESET}` :
1160
+ s.status === "error" ? `${RED}●${RESET}` :
1161
+ s.status === "done" ? `${GREEN}✓${RESET}` :
1162
+ `${DIM}?${RESET}`;
1163
+ const userTag = s.userActive ? ` ${DIM}(user active)${RESET}` : "";
1164
+ const taskTag = s.currentTask ? ` — ${DIM}${s.currentTask.slice(0, 50)}${RESET}` : "";
1165
+ console.log(` ${statusIcon} ${BOLD}${s.title}${RESET} (${s.tool}) ${s.status}${userTag}${taskTag}`);
1166
+ }
1167
+ }
1168
+ console.log("");
1169
+ }
1170
+ // `aoaoe config` -- show the effective resolved config (defaults + file + any notes)
1171
+ function showEffectiveConfig() {
1172
+ const configPath = findConfigFile();
1173
+ const configResult = loadConfig();
1174
+ // strip _configPath from output
1175
+ const { _configPath, ...config } = configResult;
1176
+ console.log("");
1177
+ console.log(" aoaoe — effective config");
1178
+ console.log(` ${"─".repeat(50)}`);
1179
+ console.log(` source: ${configPath ?? "defaults (no config file found)"}`);
1180
+ console.log("");
1181
+ console.log(JSON.stringify(config, null, 2));
1182
+ console.log("");
1183
+ // helpful notes
1184
+ if (!configPath) {
1185
+ console.log(` ${DIM}create a config: aoaoe init${RESET}`);
1186
+ console.log("");
1187
+ }
1188
+ }
1100
1189
  main().catch((err) => {
1101
1190
  console.error(`fatal: ${err}`);
1102
1191
  process.exit(1);
package/dist/init.js CHANGED
@@ -60,7 +60,8 @@ async function discoverSessions() {
60
60
  .filter((r) => r.status === "fulfilled")
61
61
  .map((r) => r.value);
62
62
  }
63
- catch {
63
+ catch (e) {
64
+ console.error(`[init] failed to parse session list: ${e}`);
64
65
  return [];
65
66
  }
66
67
  }
@@ -72,7 +73,8 @@ async function getSessionStatus(id) {
72
73
  const data = JSON.parse(result.stdout);
73
74
  return toSessionStatus(data.status);
74
75
  }
75
- catch {
76
+ catch (e) {
77
+ console.error(`[init] failed to parse session status for ${id}: ${e}`);
76
78
  return "unknown";
77
79
  }
78
80
  }
package/dist/poller.js CHANGED
@@ -115,7 +115,8 @@ export class Poller {
115
115
  const data = JSON.parse(result.stdout);
116
116
  return toSessionStatus(data.status);
117
117
  }
118
- catch {
118
+ catch (e) {
119
+ console.error(`[poller] failed to parse session status for ${id}: ${e}`);
119
120
  return "unknown";
120
121
  }
121
122
  }
@@ -232,7 +233,8 @@ export async function listAoeSessionsShared(timeoutMs = 10_000) {
232
233
  const parsed = JSON.parse(result.stdout);
233
234
  raw = Array.isArray(parsed) ? parsed : [];
234
235
  }
235
- catch {
236
+ catch (e) {
237
+ console.error(`[poller] failed to parse session list: ${e}`);
236
238
  return [];
237
239
  }
238
240
  const results = await Promise.allSettled(raw.map(async (s) => {
@@ -245,7 +247,9 @@ export async function listAoeSessionsShared(timeoutMs = 10_000) {
245
247
  status = JSON.parse(showResult.stdout).status ?? "unknown";
246
248
  }
247
249
  }
248
- catch { }
250
+ catch (e) {
251
+ console.error(`[poller] failed to parse session show for ${id}: ${e}`);
252
+ }
249
253
  return { id, title, tool: s.tool ?? "", status, tmuxName: computeTmuxName(id, title) };
250
254
  }));
251
255
  return results
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.49.0",
3
+ "version": "0.51.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",