aoaoe 0.187.0 → 0.188.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/config.d.ts +2 -0
- package/dist/config.js +8 -2
- package/dist/index.js +71 -2
- package/dist/reasoner/opencode.js +26 -4
- package/dist/wake.d.ts +4 -3
- package/dist/wake.js +22 -4
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -331,7 +331,7 @@ export function parseCliArgs(argv) {
|
|
|
331
331
|
let initForce = false;
|
|
332
332
|
let runTaskCli = false;
|
|
333
333
|
let registerTitle;
|
|
334
|
-
const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
|
|
334
|
+
const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, runHealth: false, healthJson: false, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
|
|
335
335
|
// check for subcommand as first non-flag arg
|
|
336
336
|
if (argv[2] === "test-context") {
|
|
337
337
|
return { ...defaults, testContext: true };
|
|
@@ -355,6 +355,10 @@ export function parseCliArgs(argv) {
|
|
|
355
355
|
}
|
|
356
356
|
return { ...defaults, runProgress: true, progressSince: since, progressJson: json };
|
|
357
357
|
}
|
|
358
|
+
if (argv[2] === "health") {
|
|
359
|
+
const json = argv.includes("--json");
|
|
360
|
+
return { ...defaults, runHealth: true, healthJson: json };
|
|
361
|
+
}
|
|
358
362
|
if (argv[2] === "history") {
|
|
359
363
|
return { ...defaults, showHistory: true };
|
|
360
364
|
}
|
|
@@ -700,7 +704,7 @@ export function parseCliArgs(argv) {
|
|
|
700
704
|
break;
|
|
701
705
|
}
|
|
702
706
|
}
|
|
703
|
-
return { overrides, help, version, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
|
|
707
|
+
return { overrides, help, version, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, runHealth: false, healthJson: false, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
|
|
704
708
|
}
|
|
705
709
|
export function printHelp() {
|
|
706
710
|
console.log(`aoaoe - autonomous supervisor for agent-of-empires sessions
|
|
@@ -768,6 +772,8 @@ commands:
|
|
|
768
772
|
progress per-session accomplishment digest (last 24h)
|
|
769
773
|
progress --since <duration> filter progress window (1h, 8h, 7d)
|
|
770
774
|
progress --json machine-readable progress output
|
|
775
|
+
health per-session health scores (0-100, fleet average)
|
|
776
|
+
health --json machine-readable health output
|
|
771
777
|
history review recent actions (from ~/.aoaoe/actions.log)
|
|
772
778
|
test run integration test (creates sessions, tests, cleans up)
|
|
773
779
|
test-context scan sessions + context files (read-only, no LLM, safe)
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import { loadTuiHistory, searchHistory, TUI_HISTORY_FILE, computeHistoryStats }
|
|
|
26
26
|
import { appendSupervisorEvent, loadSupervisorEvents } from "./supervisor-history.js";
|
|
27
27
|
import { savePreset, deletePreset, getPreset, formatPresetList } from "./pin-presets.js";
|
|
28
28
|
import { resolvePromptTemplate, formatPromptTemplateList } from "./reasoner/prompt-templates.js";
|
|
29
|
-
import { formatHealthReport } from "./health-score.js";
|
|
29
|
+
import { formatHealthReport, computeAllHealth } from "./health-score.js";
|
|
30
30
|
import { ConfigWatcher, formatConfigChange } from "./config-watcher.js";
|
|
31
31
|
import { parseActionLogEntries, parseActivityEntries, mergeTimeline, filterByAge, parseDuration, formatTimelineJson, formatTimelineMarkdown } from "./export.js";
|
|
32
32
|
import { actionSession, actionDetail, toActionLogEntry } from "./types.js";
|
|
@@ -40,7 +40,7 @@ const AOAOE_DIR = join(homedir(), ".aoaoe"); // watch dir for wakeable sleep
|
|
|
40
40
|
const INPUT_FILE = join(AOAOE_DIR, "pending-input.txt"); // file IPC from chat.ts
|
|
41
41
|
const TASK_RECONCILE_EVERY_POLLS = 6;
|
|
42
42
|
async function main() {
|
|
43
|
-
const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showTasksJson, runProgress, progressSince, progressJson, showHistory, showStatus, runRunbook, runbookJson, runbookSection, runIncident, incidentSince, incidentLimit, incidentJson, incidentNdjson, incidentWatch, incidentChangesOnly, incidentHeartbeatSec, incidentIntervalMs, runSupervisor, supervisorAll, supervisorSince, supervisorLimit, supervisorJson, supervisorNdjson, supervisorWatch, supervisorChangesOnly, supervisorHeartbeatSec, supervisorIntervalMs, showConfig, configValidate, configDiff, notifyTest, runDoctor, runLogs, logsActions, logsGrep, logsCount, runExport, exportFormat, exportOutput, exportLast, runInit, initForce, runTaskCli: isTaskCli, runTail: isTail, tailFollow, tailCount, runStats: isStats, statsLast, runReplay: isReplay, replaySpeed, replayLast, registerTitle } = parseCliArgs(process.argv);
|
|
43
|
+
const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showTasksJson, runProgress, progressSince, progressJson, runHealth, healthJson, showHistory, showStatus, runRunbook, runbookJson, runbookSection, runIncident, incidentSince, incidentLimit, incidentJson, incidentNdjson, incidentWatch, incidentChangesOnly, incidentHeartbeatSec, incidentIntervalMs, runSupervisor, supervisorAll, supervisorSince, supervisorLimit, supervisorJson, supervisorNdjson, supervisorWatch, supervisorChangesOnly, supervisorHeartbeatSec, supervisorIntervalMs, showConfig, configValidate, configDiff, notifyTest, runDoctor, runLogs, logsActions, logsGrep, logsCount, runExport, exportFormat, exportOutput, exportLast, runInit, initForce, runTaskCli: isTaskCli, runTail: isTail, tailFollow, tailCount, runStats: isStats, statsLast, runReplay: isReplay, replaySpeed, replayLast, registerTitle } = parseCliArgs(process.argv);
|
|
44
44
|
if (help) {
|
|
45
45
|
printHelp();
|
|
46
46
|
process.exit(0);
|
|
@@ -80,6 +80,11 @@ async function main() {
|
|
|
80
80
|
await showProgressDigest(progressSince, progressJson);
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
+
// `aoaoe health` -- per-session health scores
|
|
84
|
+
if (runHealth) {
|
|
85
|
+
showHealthStatus(healthJson);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
83
88
|
// `aoaoe history` -- review recent actions
|
|
84
89
|
if (showHistory) {
|
|
85
90
|
await showActionHistory();
|
|
@@ -4415,6 +4420,29 @@ async function showProgressDigest(since, asJson = false) {
|
|
|
4415
4420
|
}
|
|
4416
4421
|
console.log(formatProgressDigest(tasks, maxAgeMs));
|
|
4417
4422
|
}
|
|
4423
|
+
function showHealthStatus(asJson = false) {
|
|
4424
|
+
const basePath = process.cwd();
|
|
4425
|
+
const defs = loadTaskDefinitions(basePath);
|
|
4426
|
+
const taskProfiles = resolveProfiles(loadConfig());
|
|
4427
|
+
const tm = defs.length > 0 ? new TaskManager(basePath, defs, taskProfiles) : undefined;
|
|
4428
|
+
const tasks = tm?.tasks ?? [];
|
|
4429
|
+
if (tasks.length === 0) {
|
|
4430
|
+
if (asJson) {
|
|
4431
|
+
console.log("[]");
|
|
4432
|
+
return;
|
|
4433
|
+
}
|
|
4434
|
+
console.log("no tasks defined.");
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
if (asJson) {
|
|
4438
|
+
const healths = computeAllHealth(tasks);
|
|
4439
|
+
console.log(JSON.stringify(healths, null, 2));
|
|
4440
|
+
return;
|
|
4441
|
+
}
|
|
4442
|
+
console.log("");
|
|
4443
|
+
console.log(formatHealthReport(tasks));
|
|
4444
|
+
console.log("");
|
|
4445
|
+
}
|
|
4418
4446
|
// `aoaoe history` -- review recent actions from the persistent action log
|
|
4419
4447
|
async function showActionHistory() {
|
|
4420
4448
|
const logFile = join(homedir(), ".aoaoe", "actions.log");
|
|
@@ -5253,6 +5281,47 @@ async function runDoctorCheck() {
|
|
|
5253
5281
|
catch {
|
|
5254
5282
|
console.log(` ${RED}✗${RESET} could not run 'aoe list --json'`);
|
|
5255
5283
|
}
|
|
5284
|
+
// ── 7. tasks ────────────────────────────────────────────────────────────
|
|
5285
|
+
console.log(`\n ${BOLD}tasks${RESET}`);
|
|
5286
|
+
checks++;
|
|
5287
|
+
try {
|
|
5288
|
+
const defs = loadTaskDefinitions(process.cwd());
|
|
5289
|
+
const taskProfiles = resolveProfiles(config);
|
|
5290
|
+
if (defs.length === 0) {
|
|
5291
|
+
console.log(` ${DIM}○${RESET} no task definitions (create aoaoe.tasks.json or run 'aoaoe init')`);
|
|
5292
|
+
passed++;
|
|
5293
|
+
}
|
|
5294
|
+
else {
|
|
5295
|
+
const tm = new TaskManager(process.cwd(), defs, taskProfiles);
|
|
5296
|
+
const tasks = tm.tasks;
|
|
5297
|
+
const active = tasks.filter((t) => t.status === "active").length;
|
|
5298
|
+
const pending = tasks.filter((t) => t.status === "pending").length;
|
|
5299
|
+
const paused = tasks.filter((t) => t.status === "paused").length;
|
|
5300
|
+
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
5301
|
+
const stuck = tasks.filter((t) => t.status === "active" && t.lastProgressAt && (Date.now() - t.lastProgressAt > 30 * 60_000)).length;
|
|
5302
|
+
console.log(` ${GREEN}✓${RESET} ${tasks.length} task(s): ${active} active, ${pending} pending, ${paused} paused, ${completed} completed`);
|
|
5303
|
+
if (stuck > 0) {
|
|
5304
|
+
console.log(` ${YELLOW}!${RESET} ${stuck} task(s) possibly stuck (no progress >30min)`);
|
|
5305
|
+
warnings++;
|
|
5306
|
+
}
|
|
5307
|
+
// check for untracked sessions
|
|
5308
|
+
try {
|
|
5309
|
+
const liveStatus = await probeLiveSessionStatus();
|
|
5310
|
+
const trackedTitles = new Set(tasks.map((t) => t.sessionTitle.toLowerCase()));
|
|
5311
|
+
const untracked = [...liveStatus.keys()].filter((t) => !trackedTitles.has(t));
|
|
5312
|
+
if (untracked.length > 0) {
|
|
5313
|
+
console.log(` ${YELLOW}!${RESET} ${untracked.length} untracked session(s): ${untracked.join(", ")}`);
|
|
5314
|
+
console.log(` ${DIM}adopt with: /task <name> :: <goal>${RESET}`);
|
|
5315
|
+
warnings++;
|
|
5316
|
+
}
|
|
5317
|
+
}
|
|
5318
|
+
catch { /* aoe not available */ }
|
|
5319
|
+
passed++;
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
catch (err) {
|
|
5323
|
+
console.log(` ${RED}✗${RESET} task check failed: ${err instanceof Error ? err.message : err}`);
|
|
5324
|
+
}
|
|
5256
5325
|
// ── summary ────────────────────────────────────────────────────────────
|
|
5257
5326
|
const failed = checks - passed - warnings;
|
|
5258
5327
|
console.log("");
|
|
@@ -140,6 +140,24 @@ export class OpencodeReasoner {
|
|
|
140
140
|
if (errMsg.includes("401") || errMsg.includes("Unauthorized")) {
|
|
141
141
|
this.log("hint: auth token may be expired — run `opencode auth login` to re-authenticate");
|
|
142
142
|
}
|
|
143
|
+
// auto-restart opencode server on persistent 500s (likely stale state)
|
|
144
|
+
if (errMsg.includes("500") || errMsg.includes("ECONNREFUSED")) {
|
|
145
|
+
this.log("opencode server appears unhealthy — attempting restart");
|
|
146
|
+
this.killOrphanedServer();
|
|
147
|
+
try {
|
|
148
|
+
await this.startServer(this.config.opencode.port);
|
|
149
|
+
for (let i = 0; i < 15; i++) {
|
|
150
|
+
if (await this.tryConnect(this.config.opencode.port)) {
|
|
151
|
+
this.log("opencode server restarted successfully");
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
await sleep(1000);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (restartErr) {
|
|
158
|
+
this.log(`opencode server restart failed: ${restartErr}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
143
161
|
this.sessionId = null;
|
|
144
162
|
this.messageCount = 0;
|
|
145
163
|
return { actions: [{ action: "wait", reason: "SDK session error" }] };
|
|
@@ -254,8 +272,10 @@ class OpencodeClient {
|
|
|
254
272
|
headers: { "Content-Type": "application/json" },
|
|
255
273
|
body: JSON.stringify({ title }),
|
|
256
274
|
});
|
|
257
|
-
if (!res.ok)
|
|
258
|
-
|
|
275
|
+
if (!res.ok) {
|
|
276
|
+
const body = await res.text().catch(() => "(no body)");
|
|
277
|
+
throw new Error(`create session failed: ${res.status} ${res.statusText} — ${body.slice(0, 200)}`);
|
|
278
|
+
}
|
|
259
279
|
return (await res.json());
|
|
260
280
|
}
|
|
261
281
|
async sendMessage(sessionId, text, noReply, signal) {
|
|
@@ -268,8 +288,10 @@ class OpencodeClient {
|
|
|
268
288
|
}),
|
|
269
289
|
signal,
|
|
270
290
|
});
|
|
271
|
-
if (!res.ok)
|
|
272
|
-
|
|
291
|
+
if (!res.ok) {
|
|
292
|
+
const body = await res.text().catch(() => "(no body)");
|
|
293
|
+
throw new Error(`send message failed: ${res.status} ${res.statusText} — ${body.slice(0, 200)}`);
|
|
294
|
+
}
|
|
273
295
|
if (noReply)
|
|
274
296
|
return "";
|
|
275
297
|
const data = (await res.json());
|
package/dist/wake.d.ts
CHANGED
|
@@ -5,12 +5,13 @@ export interface WakeResult {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Sleep for up to `ms` milliseconds, but wake early if:
|
|
8
|
-
* - a file change is detected in `watchDir` (returns "wake")
|
|
8
|
+
* - a relevant file change is detected in `watchDir` (returns "wake")
|
|
9
9
|
* - the AbortSignal fires (returns "abort")
|
|
10
10
|
* - the timeout expires naturally (returns "timeout")
|
|
11
11
|
*
|
|
12
12
|
* Uses fs.watch on the directory — fires when pending-input.txt is written
|
|
13
|
-
* or the interrupt flag file is created.
|
|
13
|
+
* or the interrupt flag file is created. Ignores daemon-state.json and other
|
|
14
|
+
* internal files to prevent wake storms.
|
|
14
15
|
*/
|
|
15
|
-
export declare function wakeableSleep(ms: number, watchDir: string, signal?: AbortSignal): Promise<WakeResult>;
|
|
16
|
+
export declare function wakeableSleep(ms: number, watchDir: string, signal?: AbortSignal, minWakeIntervalMs?: number): Promise<WakeResult>;
|
|
16
17
|
//# sourceMappingURL=wake.d.ts.map
|
package/dist/wake.js
CHANGED
|
@@ -2,19 +2,29 @@
|
|
|
2
2
|
// that wakes immediately when files appear in the watch directory.
|
|
3
3
|
// this drops message latency from up to 10s (full poll interval) to ~100ms.
|
|
4
4
|
import { watch } from "node:fs";
|
|
5
|
+
// minimum interval between wake events — prevents storm from daemon-state.json writes
|
|
6
|
+
const MIN_WAKE_INTERVAL_MS = 2000;
|
|
7
|
+
// files that should trigger a wake (user input, interrupt flag)
|
|
8
|
+
// daemon-state.json and supervisor-history.jsonl are excluded to prevent wake storms
|
|
9
|
+
const WAKE_FILES = new Set([
|
|
10
|
+
"pending-input.txt",
|
|
11
|
+
"interrupt",
|
|
12
|
+
]);
|
|
5
13
|
/**
|
|
6
14
|
* Sleep for up to `ms` milliseconds, but wake early if:
|
|
7
|
-
* - a file change is detected in `watchDir` (returns "wake")
|
|
15
|
+
* - a relevant file change is detected in `watchDir` (returns "wake")
|
|
8
16
|
* - the AbortSignal fires (returns "abort")
|
|
9
17
|
* - the timeout expires naturally (returns "timeout")
|
|
10
18
|
*
|
|
11
19
|
* Uses fs.watch on the directory — fires when pending-input.txt is written
|
|
12
|
-
* or the interrupt flag file is created.
|
|
20
|
+
* or the interrupt flag file is created. Ignores daemon-state.json and other
|
|
21
|
+
* internal files to prevent wake storms.
|
|
13
22
|
*/
|
|
14
|
-
export function wakeableSleep(ms, watchDir, signal) {
|
|
23
|
+
export function wakeableSleep(ms, watchDir, signal, minWakeIntervalMs = MIN_WAKE_INTERVAL_MS) {
|
|
15
24
|
return new Promise((resolve) => {
|
|
16
25
|
const start = Date.now();
|
|
17
26
|
let settled = false;
|
|
27
|
+
let lastWakeAt = 0;
|
|
18
28
|
const settle = (reason) => {
|
|
19
29
|
if (settled)
|
|
20
30
|
return;
|
|
@@ -27,7 +37,15 @@ export function wakeableSleep(ms, watchDir, signal) {
|
|
|
27
37
|
// fs.watch on directory — fires on any file create/rename/change
|
|
28
38
|
let watcher = null;
|
|
29
39
|
try {
|
|
30
|
-
watcher = watch(watchDir, { persistent: false }, (_event,
|
|
40
|
+
watcher = watch(watchDir, { persistent: false }, (_event, filename) => {
|
|
41
|
+
// only wake for relevant files (user input, interrupt) — not internal state files
|
|
42
|
+
if (filename && !WAKE_FILES.has(filename))
|
|
43
|
+
return;
|
|
44
|
+
// debounce: don't wake more than once per minWakeIntervalMs
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
if (now - lastWakeAt < minWakeIntervalMs)
|
|
47
|
+
return;
|
|
48
|
+
lastWakeAt = now;
|
|
31
49
|
settle("wake");
|
|
32
50
|
});
|
|
33
51
|
// fs.watch can emit 'error' on broken watchers (e.g. dir deleted)
|