aoaoe 0.47.0 → 0.48.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/colors.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export declare const RESET = "\u001B[0m";
2
2
  export declare const BOLD = "\u001B[1m";
3
3
  export declare const DIM = "\u001B[2m";
4
- export declare const ITALIC = "\u001B[3m";
5
4
  export declare const RED = "\u001B[31m";
6
5
  export declare const GREEN = "\u001B[32m";
7
6
  export declare const YELLOW = "\u001B[33m";
@@ -15,9 +14,6 @@ export declare const ROSE = "\u001B[38;5;204m";
15
14
  export declare const LIME = "\u001B[38;5;114m";
16
15
  export declare const SKY = "\u001B[38;5;117m";
17
16
  export declare const BG_DARK = "\u001B[48;5;236m";
18
- export declare const BG_DARKER = "\u001B[48;5;234m";
19
- export declare const BG_PANEL = "\u001B[48;5;237m";
20
- export declare const BG_HIGHLIGHT = "\u001B[48;5;238m";
21
17
  export declare const BOX: {
22
18
  readonly tl: "┌";
23
19
  readonly tr: "┐";
package/dist/colors.js CHANGED
@@ -3,7 +3,6 @@
3
3
  export const RESET = "\x1b[0m";
4
4
  export const BOLD = "\x1b[1m";
5
5
  export const DIM = "\x1b[2m";
6
- export const ITALIC = "\x1b[3m";
7
6
  export const RED = "\x1b[31m";
8
7
  export const GREEN = "\x1b[32m";
9
8
  export const YELLOW = "\x1b[33m";
@@ -20,9 +19,6 @@ export const LIME = "\x1b[38;5;114m"; // fresh green for success/working
20
19
  export const SKY = "\x1b[38;5;117m"; // light blue for reasoning
21
20
  // background variants (256-color)
22
21
  export const BG_DARK = "\x1b[48;5;236m"; // dark gray for header bar
23
- export const BG_DARKER = "\x1b[48;5;234m"; // near-black for contrast panels
24
- export const BG_PANEL = "\x1b[48;5;237m"; // subtle panel background
25
- export const BG_HIGHLIGHT = "\x1b[48;5;238m"; // highlight row
26
22
  // box-drawing characters — Unicode block elements
27
23
  export const BOX = {
28
24
  tl: "┌", tr: "┐", bl: "└", br: "┘",
package/dist/config.js CHANGED
@@ -3,6 +3,7 @@ import { resolve, join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { execFile as execFileCb } from "node:child_process";
5
5
  import { promisify } from "node:util";
6
+ import { toReasonerBackend } from "./types.js";
6
7
  const execFileAsync = promisify(execFileCb);
7
8
  const AOAOE_DIR = join(homedir(), ".aoaoe");
8
9
  const CONFIG_NAMES = ["aoaoe.config.json", ".aoaoe.json"];
@@ -250,7 +251,7 @@ export function parseCliArgs(argv) {
250
251
  const arg = argv[i];
251
252
  switch (arg) {
252
253
  case "--reasoner":
253
- overrides.reasoner = nextArg(i, arg);
254
+ overrides.reasoner = toReasonerBackend(nextArg(i, arg));
254
255
  i++;
255
256
  break;
256
257
  case "--poll-interval": {
@@ -4,6 +4,7 @@
4
4
  import { writeFileSync, readFileSync, existsSync, unlinkSync, mkdirSync, renameSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { homedir } from "node:os";
7
+ import { toDaemonState } from "./types.js";
7
8
  import { parseTasks, formatTaskList } from "./task-parser.js";
8
9
  const AOAOE_DIR = join(homedir(), ".aoaoe");
9
10
  const STATE_FILE = join(AOAOE_DIR, "daemon-state.json");
@@ -122,7 +123,7 @@ export function readState() {
122
123
  try {
123
124
  if (!existsSync(STATE_FILE))
124
125
  return null;
125
- return JSON.parse(readFileSync(STATE_FILE, "utf-8"));
126
+ return toDaemonState(JSON.parse(readFileSync(STATE_FILE, "utf-8")));
126
127
  }
127
128
  catch {
128
129
  return null;
package/dist/index.js CHANGED
@@ -467,6 +467,8 @@ async function main() {
467
467
  else {
468
468
  // ── normal mode: full tick ─────────────────────────────────────────
469
469
  const activeTaskContext = taskManager ? taskManager.tasks.filter((t) => t.status !== "completed") : undefined;
470
+ if (!reasoner || !executor)
471
+ throw new Error("reasoner/executor unexpectedly null in normal mode");
470
472
  const { interrupted, decisionsThisTick, actionsOk, actionsFail } = await daemonTick(config, poller, reasoner, executor, reasonerConsole, pollCount, policyStates, userMessage, forceDashboard, activeTaskContext, taskManager, tui);
471
473
  totalDecisions += decisionsThisTick;
472
474
  totalActionsExecuted += actionsOk;
package/dist/init.js CHANGED
@@ -36,7 +36,10 @@ async function discoverSessions() {
36
36
  if (result.exitCode !== 0)
37
37
  return [];
38
38
  try {
39
- const raw = JSON.parse(result.stdout);
39
+ const parsed = JSON.parse(result.stdout);
40
+ if (!Array.isArray(parsed))
41
+ return [];
42
+ const raw = parsed;
40
43
  // fetch status for each session in parallel (allSettled so one failure doesn't kill all)
41
44
  const results = await Promise.allSettled(raw.map(async (r) => {
42
45
  const id = String(r.id ?? "");
@@ -78,7 +78,7 @@ function checkAndClear() {
78
78
  .pop() || '';
79
79
  appendFileSync(logFile,
80
80
  new Date().toISOString() + ' CLEARED: ' + line.trim().slice(0, 120) + '\\n');
81
- } catch(e) {}
81
+ } catch {}
82
82
  }
83
83
  }
84
84
  }
@@ -80,6 +80,8 @@ export class OpencodeReasoner {
80
80
  }
81
81
  }
82
82
  async decideViaSDK(prompt, signal) {
83
+ if (!this.client)
84
+ throw new Error("decideViaSDK called without a connected client");
83
85
  const client = this.client;
84
86
  // create session on first call, after rotation, or after error reset
85
87
  if (!this.sessionId) {
@@ -281,7 +283,7 @@ class OpencodeClient {
281
283
  // extract text from assistant response parts
282
284
  const textParts = (data.parts ?? [])
283
285
  .filter((p) => p.type === "text" && p.text)
284
- .map((p) => p.text);
286
+ .map((p) => p.text ?? "");
285
287
  return textParts.join("\n");
286
288
  }
287
289
  }
@@ -160,7 +160,7 @@ export function formatObservation(obs) {
160
160
  parts.push("Project context for sessions:");
161
161
  let contextBudget = 50_000; // max bytes for all project context combined
162
162
  for (const snap of sortedContextSessions) {
163
- const ctx = snap.projectContext;
163
+ const ctx = snap.projectContext ?? "";
164
164
  const ctxBytes = Buffer.byteLength(ctx, "utf-8");
165
165
  if (ctxBytes > contextBudget) {
166
166
  // truncate this context to fit remaining budget
package/dist/task-cli.js CHANGED
@@ -4,6 +4,7 @@ import { exec } from "./shell.js";
4
4
  import { existsSync } from "node:fs";
5
5
  import { resolve, basename } from "node:path";
6
6
  import { loadTaskState, saveTaskState, formatTaskTable } from "./task-manager.js";
7
+ import { toAoeSessionList } from "./types.js";
7
8
  import { BOLD, DIM, GREEN, YELLOW, RED, RESET } from "./colors.js";
8
9
  // resolve a fuzzy reference to a task: match by title, repo basename, or session ID prefix
9
10
  export function resolveTask(ref, tasks) {
@@ -112,7 +113,7 @@ export async function taskNew(title, path, tool = "opencode") {
112
113
  let sessionId;
113
114
  if (listResult.exitCode === 0) {
114
115
  try {
115
- const sessions = JSON.parse(listResult.stdout);
116
+ const sessions = toAoeSessionList(JSON.parse(listResult.stdout));
116
117
  const found = sessions.find((s) => s.title.toLowerCase() === lower);
117
118
  sessionId = found?.id;
118
119
  }
@@ -5,6 +5,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from "
5
5
  import { join, resolve, basename } from "node:path";
6
6
  import { homedir } from "node:os";
7
7
  import { exec } from "./shell.js";
8
+ import { toTaskState, toAoeSessionList } from "./types.js";
8
9
  import { RESET, BOLD, DIM, GREEN, YELLOW, RED, CYAN } from "./colors.js";
9
10
  const AOAOE_DIR = join(homedir(), ".aoaoe");
10
11
  const STATE_FILE = join(AOAOE_DIR, "task-state.json");
@@ -84,8 +85,9 @@ export function loadTaskState() {
84
85
  const raw = JSON.parse(readFileSync(STATE_FILE, "utf-8"));
85
86
  if (raw && typeof raw.tasks === "object") {
86
87
  for (const [repo, state] of Object.entries(raw.tasks)) {
87
- if (state && typeof state === "object") {
88
- map.set(repo, state);
88
+ const validated = toTaskState(state);
89
+ if (validated) {
90
+ map.set(repo, validated);
89
91
  }
90
92
  }
91
93
  }
@@ -138,10 +140,12 @@ export class TaskManager {
138
140
  else {
139
141
  // update goal/tool if definition changed (don't reset progress)
140
142
  const existing = this.states.get(def.repo);
141
- if (def.goal)
142
- existing.goal = def.goal;
143
- if (def.tool)
144
- existing.tool = def.tool;
143
+ if (existing) {
144
+ if (def.goal)
145
+ existing.goal = def.goal;
146
+ if (def.tool)
147
+ existing.tool = def.tool;
148
+ }
145
149
  }
146
150
  }
147
151
  this.save();
@@ -201,7 +205,7 @@ export class TaskManager {
201
205
  const refreshResult = await exec("aoe", ["list", "--json"]);
202
206
  if (refreshResult.exitCode === 0) {
203
207
  try {
204
- const refreshed = JSON.parse(refreshResult.stdout);
208
+ const refreshed = toAoeSessionList(JSON.parse(refreshResult.stdout));
205
209
  const newSession = refreshed.find((s) => s.title.toLowerCase() === task.sessionTitle.toLowerCase());
206
210
  if (newSession) {
207
211
  task.sessionId = newSession.id;
package/dist/types.d.ts CHANGED
@@ -162,4 +162,11 @@ export interface TaskState {
162
162
  completedAt?: number;
163
163
  progress: TaskProgress[];
164
164
  }
165
+ export declare function toTaskState(raw: unknown): TaskState | null;
166
+ export declare function toDaemonState(raw: unknown): DaemonState | null;
167
+ export declare function toAoeSessionList(raw: unknown): Array<{
168
+ id: string;
169
+ title: string;
170
+ }>;
171
+ export declare function toReasonerBackend(raw: string): ReasonerBackend;
165
172
  //# sourceMappingURL=types.d.ts.map
package/dist/types.js CHANGED
@@ -37,4 +37,79 @@ export function actionDetail(action) {
37
37
  return undefined;
38
38
  }
39
39
  }
40
+ const VALID_TASK_STATUSES = new Set(["pending", "active", "completed", "paused", "failed"]);
41
+ // validate an unknown value (e.g. from JSON.parse) as a TaskState, returning null if invalid
42
+ export function toTaskState(raw) {
43
+ if (!raw || typeof raw !== "object")
44
+ return null;
45
+ const r = raw;
46
+ if (typeof r.repo !== "string" || !r.repo)
47
+ return null;
48
+ if (typeof r.sessionTitle !== "string")
49
+ return null;
50
+ if (typeof r.tool !== "string")
51
+ return null;
52
+ if (typeof r.goal !== "string")
53
+ return null;
54
+ if (typeof r.status !== "string" || !VALID_TASK_STATUSES.has(r.status))
55
+ return null;
56
+ if (!Array.isArray(r.progress))
57
+ return null;
58
+ return {
59
+ repo: r.repo,
60
+ sessionTitle: r.sessionTitle,
61
+ tool: r.tool,
62
+ goal: r.goal,
63
+ status: r.status,
64
+ sessionId: typeof r.sessionId === "string" ? r.sessionId : undefined,
65
+ createdAt: typeof r.createdAt === "number" ? r.createdAt : undefined,
66
+ lastProgressAt: typeof r.lastProgressAt === "number" ? r.lastProgressAt : undefined,
67
+ completedAt: typeof r.completedAt === "number" ? r.completedAt : undefined,
68
+ progress: r.progress.filter((p) => !!p && typeof p === "object" &&
69
+ typeof p.at === "number" &&
70
+ typeof p.summary === "string"),
71
+ };
72
+ }
73
+ // validate an unknown value as a DaemonState, returning null if invalid
74
+ export function toDaemonState(raw) {
75
+ if (!raw || typeof raw !== "object")
76
+ return null;
77
+ const r = raw;
78
+ if (typeof r.tickStartedAt !== "number")
79
+ return null;
80
+ if (typeof r.nextTickAt !== "number")
81
+ return null;
82
+ if (typeof r.pollIntervalMs !== "number")
83
+ return null;
84
+ if (typeof r.phase !== "string")
85
+ return null;
86
+ if (typeof r.phaseStartedAt !== "number")
87
+ return null;
88
+ if (typeof r.pollCount !== "number")
89
+ return null;
90
+ if (typeof r.paused !== "boolean")
91
+ return null;
92
+ if (typeof r.sessionCount !== "number")
93
+ return null;
94
+ if (typeof r.changeCount !== "number")
95
+ return null;
96
+ if (!Array.isArray(r.sessions))
97
+ return null;
98
+ return raw;
99
+ }
100
+ // validate an unknown array as an AoE session list (from `aoe list --json`)
101
+ export function toAoeSessionList(raw) {
102
+ if (!Array.isArray(raw))
103
+ return [];
104
+ return raw.filter((item) => !!item && typeof item === "object" &&
105
+ typeof item.id === "string" &&
106
+ typeof item.title === "string");
107
+ }
108
+ // validate a string as a ReasonerBackend, throwing on invalid input
109
+ const VALID_REASONER_BACKENDS = new Set(["opencode", "claude-code"]);
110
+ export function toReasonerBackend(raw) {
111
+ if (VALID_REASONER_BACKENDS.has(raw))
112
+ return raw;
113
+ throw new Error(`--reasoner must be "opencode" or "claude-code", got "${raw}"`);
114
+ }
40
115
  //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.47.0",
3
+ "version": "0.48.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",