aoaoe 0.186.0 → 0.187.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.
@@ -0,0 +1,11 @@
1
+ import type { TaskState } from "./types.js";
2
+ export interface SessionHealth {
3
+ session: string;
4
+ score: number;
5
+ grade: "healthy" | "ok" | "degraded" | "critical" | "inactive";
6
+ factors: string[];
7
+ }
8
+ export declare function computeHealth(task: TaskState): SessionHealth;
9
+ export declare function computeAllHealth(tasks: TaskState[]): SessionHealth[];
10
+ export declare function formatHealthReport(tasks: TaskState[]): string;
11
+ //# sourceMappingURL=health-score.d.ts.map
@@ -0,0 +1,100 @@
1
+ // health-score.ts — compute per-session health from task state + progress patterns.
2
+ // health is a 0-100 score where 100 = healthy, active, progressing.
3
+ import { BOLD, DIM, GREEN, YELLOW, RED, CYAN, RESET } from "./colors.js";
4
+ // compute health score for a single task
5
+ export function computeHealth(task) {
6
+ const factors = [];
7
+ let score = 100;
8
+ const now = Date.now();
9
+ // status penalties
10
+ if (task.status === "completed") {
11
+ return { session: task.sessionTitle, score: 100, grade: "healthy", factors: ["completed"] };
12
+ }
13
+ if (task.status === "failed") {
14
+ return { session: task.sessionTitle, score: 0, grade: "critical", factors: ["task failed"] };
15
+ }
16
+ if (task.status === "paused") {
17
+ return { session: task.sessionTitle, score: 30, grade: "degraded", factors: ["paused"] };
18
+ }
19
+ if (task.status === "pending") {
20
+ const hasDeps = task.dependsOn && task.dependsOn.length > 0;
21
+ return { session: task.sessionTitle, score: 50, grade: "inactive", factors: [hasDeps ? "waiting on dependencies" : "not started"] };
22
+ }
23
+ // progress recency (active tasks)
24
+ if (task.lastProgressAt) {
25
+ const ageMs = now - task.lastProgressAt;
26
+ if (ageMs < 10 * 60_000) {
27
+ factors.push("progress <10m ago");
28
+ }
29
+ else if (ageMs < 30 * 60_000) {
30
+ score -= 10;
31
+ factors.push("progress 10-30m ago");
32
+ }
33
+ else if (ageMs < 60 * 60_000) {
34
+ score -= 25;
35
+ factors.push("progress 30-60m ago");
36
+ }
37
+ else if (ageMs < 4 * 60 * 60_000) {
38
+ score -= 40;
39
+ factors.push("progress 1-4h ago");
40
+ }
41
+ else {
42
+ score -= 55;
43
+ factors.push(`progress ${Math.round(ageMs / 3_600_000)}h ago`);
44
+ }
45
+ }
46
+ else {
47
+ score -= 30;
48
+ factors.push("no progress recorded");
49
+ }
50
+ // stuck nudge count
51
+ const nudges = task.stuckNudgeCount ?? 0;
52
+ if (nudges > 0) {
53
+ score -= Math.min(nudges * 10, 30);
54
+ factors.push(`${nudges} stuck nudge(s)`);
55
+ }
56
+ // progress velocity (more entries = healthier)
57
+ const recentEntries = task.progress.filter((p) => p.at >= now - 4 * 60 * 60_000).length;
58
+ if (recentEntries >= 3) {
59
+ score = Math.min(score + 10, 100);
60
+ factors.push(`${recentEntries} progress entries in 4h`);
61
+ }
62
+ else if (recentEntries === 0 && task.progress.length > 0) {
63
+ score -= 10;
64
+ factors.push("no progress in last 4h");
65
+ }
66
+ score = Math.max(0, Math.min(100, score));
67
+ const grade = score >= 80 ? "healthy" :
68
+ score >= 60 ? "ok" :
69
+ score >= 40 ? "degraded" :
70
+ "critical";
71
+ return { session: task.sessionTitle, score, grade, factors };
72
+ }
73
+ // compute health for all tasks
74
+ export function computeAllHealth(tasks) {
75
+ return tasks.map(computeHealth);
76
+ }
77
+ // format health report for display
78
+ export function formatHealthReport(tasks) {
79
+ const healths = computeAllHealth(tasks);
80
+ if (healths.length === 0)
81
+ return " (no tasks)";
82
+ const lines = [];
83
+ lines.push(` ${BOLD}SESSION HEALTH${RESET}`);
84
+ lines.push(` ${"-".repeat(60)}`);
85
+ for (const h of healths) {
86
+ const color = h.grade === "healthy" ? GREEN
87
+ : h.grade === "ok" ? CYAN
88
+ : h.grade === "degraded" ? YELLOW
89
+ : h.grade === "critical" ? RED
90
+ : DIM;
91
+ const bar = "█".repeat(Math.round(h.score / 10)) + "░".repeat(10 - Math.round(h.score / 10));
92
+ lines.push(` ${color}${bar}${RESET} ${h.score.toString().padStart(3)} ${BOLD}${h.session}${RESET} ${DIM}(${h.grade})${RESET}`);
93
+ lines.push(` ${DIM} ${h.factors.join(" · ")}${RESET}`);
94
+ }
95
+ const avg = Math.round(healths.reduce((sum, h) => sum + h.score, 0) / healths.length);
96
+ lines.push("");
97
+ lines.push(` ${DIM}fleet average: ${avg}/100${RESET}`);
98
+ return lines.join("\n");
99
+ }
100
+ //# sourceMappingURL=health-score.js.map
package/dist/index.js CHANGED
@@ -24,6 +24,9 @@ import { sendNotification, sendTestNotification, formatNotifyFilters, parseNotif
24
24
  import { startHealthServer } from "./health.js";
25
25
  import { loadTuiHistory, searchHistory, TUI_HISTORY_FILE, computeHistoryStats } from "./tui-history.js";
26
26
  import { appendSupervisorEvent, loadSupervisorEvents } from "./supervisor-history.js";
27
+ import { savePreset, deletePreset, getPreset, formatPresetList } from "./pin-presets.js";
28
+ import { resolvePromptTemplate, formatPromptTemplateList } from "./reasoner/prompt-templates.js";
29
+ import { formatHealthReport } from "./health-score.js";
27
30
  import { ConfigWatcher, formatConfigChange } from "./config-watcher.js";
28
31
  import { parseActionLogEntries, parseActivityEntries, mergeTimeline, filterByAge, parseDuration, formatTimelineJson, formatTimelineMarkdown } from "./export.js";
29
32
  import { actionSession, actionDetail, toActionLogEntry } from "./types.js";
@@ -2295,6 +2298,150 @@ async function main() {
2295
2298
  const userMessage = userMessages.length > 0 ? formatUserMessages(userMessages) : undefined;
2296
2299
  // handle built-in command markers (from stdin or chat.ts file IPC)
2297
2300
  for (const cmd of commands) {
2301
+ if (cmd.startsWith("__CMD_PIN_SAVE__")) {
2302
+ const name = cmd.slice("__CMD_PIN_SAVE__".length).trim();
2303
+ if (!name) {
2304
+ const msg = "usage: /pin-save <preset-name>";
2305
+ if (tui)
2306
+ tui.log("error", msg);
2307
+ else
2308
+ log(msg);
2309
+ continue;
2310
+ }
2311
+ if (tui) {
2312
+ const titles = tui.getPinnedTitles();
2313
+ if (titles.length === 0) {
2314
+ const msg = "no sessions pinned — /pin a session first";
2315
+ tui.log("status", msg);
2316
+ }
2317
+ else {
2318
+ savePreset(name, titles);
2319
+ const msg = `pin preset saved: ${name} (${titles.join(", ")})`;
2320
+ tui.log("system", msg);
2321
+ pushSupervisorEvent(`pin-save: ${name} [${titles.length} sessions]`);
2322
+ }
2323
+ }
2324
+ else {
2325
+ log("pin presets require TUI mode");
2326
+ }
2327
+ continue;
2328
+ }
2329
+ if (cmd.startsWith("__CMD_PIN_LOAD__")) {
2330
+ const name = cmd.slice("__CMD_PIN_LOAD__".length).trim();
2331
+ if (!name) {
2332
+ const msg = "usage: /pin-load <preset-name>";
2333
+ if (tui)
2334
+ tui.log("error", msg);
2335
+ else
2336
+ log(msg);
2337
+ continue;
2338
+ }
2339
+ const titles = getPreset(name);
2340
+ if (!titles) {
2341
+ const msg = `pin preset not found: ${name}`;
2342
+ if (tui)
2343
+ tui.log("error", msg);
2344
+ else
2345
+ log(msg);
2346
+ continue;
2347
+ }
2348
+ if (tui) {
2349
+ const count = tui.loadPinPreset(titles);
2350
+ const msg = `pin preset loaded: ${name} (${count} sessions pinned)`;
2351
+ tui.log("system", msg);
2352
+ pushSupervisorEvent(`pin-load: ${name} [${count} pinned]`);
2353
+ }
2354
+ continue;
2355
+ }
2356
+ if (cmd.startsWith("__CMD_PIN_DELETE__")) {
2357
+ const name = cmd.slice("__CMD_PIN_DELETE__".length).trim();
2358
+ if (!name) {
2359
+ const msg = "usage: /pin-delete <preset-name>";
2360
+ if (tui)
2361
+ tui.log("error", msg);
2362
+ else
2363
+ log(msg);
2364
+ continue;
2365
+ }
2366
+ const ok = deletePreset(name);
2367
+ const msg = ok ? `pin preset deleted: ${name}` : `pin preset not found: ${name}`;
2368
+ if (tui)
2369
+ tui.log(ok ? "system" : "error", msg);
2370
+ else
2371
+ log(msg);
2372
+ continue;
2373
+ }
2374
+ if (cmd === "__CMD_PIN_PRESETS__") {
2375
+ const msg = formatPresetList();
2376
+ for (const line of msg.split("\n")) {
2377
+ if (tui)
2378
+ tui.log("status", line);
2379
+ else
2380
+ log(line);
2381
+ }
2382
+ continue;
2383
+ }
2384
+ if (cmd.startsWith("__CMD_PROMPT_TEMPLATE__")) {
2385
+ const name = cmd.slice("__CMD_PROMPT_TEMPLATE__".length).trim();
2386
+ if (!name) {
2387
+ const current = config.promptTemplate || "default";
2388
+ const msg = `current prompt template: ${current}`;
2389
+ if (tui)
2390
+ tui.log("status", msg);
2391
+ else
2392
+ log(msg);
2393
+ const list = formatPromptTemplateList();
2394
+ for (const line of list.split("\n")) {
2395
+ if (tui)
2396
+ tui.log("status", line);
2397
+ else
2398
+ log(line);
2399
+ }
2400
+ }
2401
+ else {
2402
+ const tmpl = resolvePromptTemplate(name);
2403
+ if (!tmpl) {
2404
+ const msg = `unknown prompt template: ${name}`;
2405
+ if (tui)
2406
+ tui.log("error", msg);
2407
+ else
2408
+ log(msg);
2409
+ const list = formatPromptTemplateList();
2410
+ for (const line of list.split("\n")) {
2411
+ if (tui)
2412
+ tui.log("status", line);
2413
+ else
2414
+ log(line);
2415
+ }
2416
+ }
2417
+ else {
2418
+ config.promptTemplate = tmpl.name;
2419
+ const msg = `prompt template set to: ${tmpl.name} — ${tmpl.description}`;
2420
+ if (tui)
2421
+ tui.log("system", msg);
2422
+ else
2423
+ log(msg);
2424
+ pushSupervisorEvent(`prompt-template: ${tmpl.name}`);
2425
+ reasonerConsole.writeSystem(msg);
2426
+ // note: takes effect on next reasoner init (next reasoning cycle)
2427
+ if (tui)
2428
+ tui.log("status", `(takes effect on next reasoning cycle)`);
2429
+ }
2430
+ }
2431
+ continue;
2432
+ }
2433
+ if (cmd === "__CMD_HEALTH__") {
2434
+ const tasks = taskManager?.tasks ?? [];
2435
+ const report = formatHealthReport(tasks);
2436
+ for (const line of report.split("\n")) {
2437
+ if (tui)
2438
+ tui.log("status", line);
2439
+ else
2440
+ log(line);
2441
+ reasonerConsole.writeSystem(line);
2442
+ }
2443
+ continue;
2444
+ }
2298
2445
  if (cmd === "__CMD_STATUS__") {
2299
2446
  const isPausedNow = paused || input.isPaused();
2300
2447
  const modeMsg = `status: poll #${pollCount}, mode=${getRunMode()}, reasoner=${getReasonerLabel()}, paused=${isPausedNow}`;
@@ -3600,6 +3747,36 @@ async function daemonTick(config, poller, reasoner, executor, reasonerConsole, p
3600
3747
  }
3601
3748
  }
3602
3749
  }
3750
+ // auto-pause tracking: record stuck nudges for send_input actions targeting stuck sessions.
3751
+ // a "stuck nudge" = send_input to a session that hasn't had progress in >30 min.
3752
+ if (taskManager) {
3753
+ const maxNudges = config.policies.maxStuckNudgesBeforePause ?? 0;
3754
+ const stuckThresholdMs = 30 * 60 * 1000;
3755
+ const now = Date.now();
3756
+ for (const entry of executed) {
3757
+ if (entry.action.action !== "send_input" || !entry.success)
3758
+ continue;
3759
+ const sid = actionSession(entry.action);
3760
+ const title = sid ? (sessionTitleMap.get(sid) ?? sid) : undefined;
3761
+ if (!title)
3762
+ continue;
3763
+ const task = taskManager.getTaskForSession(title);
3764
+ if (!task || task.status !== "active")
3765
+ continue;
3766
+ const lastProgress = task.lastProgressAt ?? 0;
3767
+ if (lastProgress > 0 && (now - lastProgress) > stuckThresholdMs) {
3768
+ const paused = taskManager.recordStuckNudge(title, maxNudges);
3769
+ if (paused) {
3770
+ const msg = `auto-paused '${title}' after ${task.stuckNudgeCount} stuck nudges`;
3771
+ if (tui)
3772
+ tui.log("system", msg);
3773
+ else
3774
+ log(msg);
3775
+ appendSupervisorEvent({ at: Date.now(), detail: `auto-pause: ${title}` });
3776
+ }
3777
+ }
3778
+ }
3779
+ }
3603
3780
  // fire lifecycle hooks for completed actions
3604
3781
  if (tui) {
3605
3782
  const hooks = tui.getLifecycleHooks();
@@ -4224,7 +4401,18 @@ async function showProgressDigest(since, asJson = false) {
4224
4401
  console.log("no tasks defined.");
4225
4402
  return;
4226
4403
  }
4404
+ // enrich with live session status
4405
+ const liveStatus = await probeLiveSessionStatus();
4406
+ const liveCount = liveStatus.size;
4407
+ const missingFromTasks = [...liveStatus.keys()].filter((title) => !tasks.some((t) => t.sessionTitle.toLowerCase() === title));
4227
4408
  console.log("");
4409
+ if (liveCount > 0) {
4410
+ console.log(` ${DIM}live sessions: ${liveCount} (${[...liveStatus.entries()].map(([t, s]) => `${t}=${s}`).join(", ")})${RESET}`);
4411
+ if (missingFromTasks.length > 0) {
4412
+ console.log(` ${YELLOW}untracked sessions: ${missingFromTasks.join(", ")} — use /task <name> :: <goal> to adopt${RESET}`);
4413
+ }
4414
+ console.log("");
4415
+ }
4228
4416
  console.log(formatProgressDigest(tasks, maxAgeMs));
4229
4417
  }
4230
4418
  // `aoaoe history` -- review recent actions from the persistent action log
package/dist/input.js CHANGED
@@ -709,6 +709,10 @@ ${BOLD}navigation:${RESET}
709
709
  /sort [mode] sort sessions: status, name, activity, default (or cycle)
710
710
  /compact toggle compact mode (dense session panel)
711
711
  /pin [N|name] pin/unpin a session to the top (toggle)
712
+ /pin-save <name> save current pins as a named preset
713
+ /pin-load <name> restore a saved pin preset
714
+ /pin-delete <name> delete a saved preset
715
+ /pin-presets list saved pin presets
712
716
  /bell toggle terminal bell on errors/completions
713
717
  /focus toggle focus mode (show only pinned sessions)
714
718
  /mute [N|name] mute/unmute a session's activity entries (toggle)
@@ -797,6 +801,8 @@ ${BOLD}navigation:${RESET}
797
801
  ${BOLD}info:${RESET}
798
802
  /status show daemon state
799
803
  /progress [opts] what each session accomplished recently; opts: --since <1h|8h|24h> --json
804
+ /health session health scores (0-100 per task, fleet average)
805
+ /prompt-template [name] set/show reasoner prompt strategy (default, hands-off, aggressive, review-focused, shipping)
800
806
  /incident [opts] quick incident view; opts: --since <30m|2h|1d> --limit N --json --ndjson --follow (watch via CLI)
801
807
  /runbook [section] show operator playbook (opts: quickstart|response-flow|incident|all, --json)
802
808
  /supervisor [opts] show judge/orchestrator status; opts: --all --since <1h|30m|2d> --limit N --json
@@ -994,6 +1000,24 @@ ${BOLD}other:${RESET}
994
1000
  }
995
1001
  break;
996
1002
  }
1003
+ case "/pin-save":
1004
+ this.queue.push(`__CMD_PIN_SAVE__${line.slice("/pin-save".length)}`);
1005
+ break;
1006
+ case "/pin-load":
1007
+ this.queue.push(`__CMD_PIN_LOAD__${line.slice("/pin-load".length)}`);
1008
+ break;
1009
+ case "/pin-delete":
1010
+ this.queue.push(`__CMD_PIN_DELETE__${line.slice("/pin-delete".length)}`);
1011
+ break;
1012
+ case "/pin-presets":
1013
+ this.queue.push("__CMD_PIN_PRESETS__");
1014
+ break;
1015
+ case "/prompt-template":
1016
+ this.queue.push(`__CMD_PROMPT_TEMPLATE__${line.slice("/prompt-template".length)}`);
1017
+ break;
1018
+ case "/health":
1019
+ this.queue.push("__CMD_HEALTH__");
1020
+ break;
997
1021
  case "/bell":
998
1022
  if (this.bellHandler) {
999
1023
  this.bellHandler();
@@ -0,0 +1,10 @@
1
+ export interface PinPresets {
2
+ [name: string]: string[];
3
+ }
4
+ export declare function loadPinPresets(filePath?: string): PinPresets;
5
+ export declare function savePinPresets(presets: PinPresets, filePath?: string): void;
6
+ export declare function savePreset(name: string, titles: string[], filePath?: string): void;
7
+ export declare function deletePreset(name: string, filePath?: string): boolean;
8
+ export declare function getPreset(name: string, filePath?: string): string[] | undefined;
9
+ export declare function formatPresetList(filePath?: string): string;
10
+ //# sourceMappingURL=pin-presets.d.ts.map
@@ -0,0 +1,72 @@
1
+ // pin-presets.ts — save/restore named sets of pinned session titles.
2
+ // stored in ~/.aoaoe/pin-presets.json as { presetName: ["title1", "title2"] }.
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { BOLD, DIM, GREEN, RESET } from "./colors.js";
7
+ const AOAOE_DIR = join(homedir(), ".aoaoe");
8
+ const PRESETS_FILE = join(AOAOE_DIR, "pin-presets.json");
9
+ export function loadPinPresets(filePath = PRESETS_FILE) {
10
+ try {
11
+ if (!existsSync(filePath))
12
+ return {};
13
+ const raw = JSON.parse(readFileSync(filePath, "utf-8"));
14
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
15
+ return {};
16
+ const result = {};
17
+ for (const [k, v] of Object.entries(raw)) {
18
+ if (Array.isArray(v) && v.every((s) => typeof s === "string")) {
19
+ result[k] = v;
20
+ }
21
+ }
22
+ return result;
23
+ }
24
+ catch {
25
+ return {};
26
+ }
27
+ }
28
+ export function savePinPresets(presets, filePath = PRESETS_FILE) {
29
+ try {
30
+ if (!existsSync(AOAOE_DIR))
31
+ mkdirSync(AOAOE_DIR, { recursive: true });
32
+ writeFileSync(filePath, JSON.stringify(presets, null, 2) + "\n");
33
+ }
34
+ catch (e) {
35
+ console.error(`failed to save pin presets: ${e}`);
36
+ }
37
+ }
38
+ export function savePreset(name, titles, filePath = PRESETS_FILE) {
39
+ const presets = loadPinPresets(filePath);
40
+ presets[name] = titles;
41
+ savePinPresets(presets, filePath);
42
+ }
43
+ export function deletePreset(name, filePath = PRESETS_FILE) {
44
+ const presets = loadPinPresets(filePath);
45
+ if (!(name in presets))
46
+ return false;
47
+ delete presets[name];
48
+ savePinPresets(presets, filePath);
49
+ return true;
50
+ }
51
+ export function getPreset(name, filePath = PRESETS_FILE) {
52
+ const presets = loadPinPresets(filePath);
53
+ const lower = name.toLowerCase();
54
+ const key = Object.keys(presets).find((k) => k.toLowerCase() === lower);
55
+ return key ? presets[key] : undefined;
56
+ }
57
+ export function formatPresetList(filePath = PRESETS_FILE) {
58
+ const presets = loadPinPresets(filePath);
59
+ const names = Object.keys(presets);
60
+ if (names.length === 0)
61
+ return ` ${DIM}(no saved presets)${RESET}\n ${DIM}save: /pin-save <name>${RESET}`;
62
+ const lines = [];
63
+ lines.push(` ${BOLD}saved pin presets:${RESET}`);
64
+ for (const name of names) {
65
+ const titles = presets[name];
66
+ lines.push(` ${GREEN}${name}${RESET} — ${titles.join(", ")} ${DIM}(${titles.length} sessions)${RESET}`);
67
+ }
68
+ lines.push("");
69
+ lines.push(` ${DIM}restore: /pin-load <name> | delete: /pin-delete <name>${RESET}`);
70
+ return lines.join("\n");
71
+ }
72
+ //# sourceMappingURL=pin-presets.js.map
@@ -9,7 +9,7 @@ export class ClaudeCodeReasoner {
9
9
  sessionId = null;
10
10
  constructor(config, globalContext) {
11
11
  this.config = config;
12
- this.systemPrompt = buildSystemPrompt(globalContext);
12
+ this.systemPrompt = buildSystemPrompt(globalContext, config.promptTemplate);
13
13
  }
14
14
  async init() {
15
15
  // verify claude is available
@@ -20,7 +20,7 @@ export class OpencodeReasoner {
20
20
  static MAX_SESSION_MESSAGES = 7;
21
21
  constructor(config, globalContext) {
22
22
  this.config = config;
23
- this.systemPrompt = buildSystemPrompt(globalContext);
23
+ this.systemPrompt = buildSystemPrompt(globalContext, config.promptTemplate);
24
24
  }
25
25
  async init() {
26
26
  // try to connect to existing opencode server first, then start one
@@ -0,0 +1,10 @@
1
+ export interface PromptTemplate {
2
+ name: string;
3
+ description: string;
4
+ preamble: string;
5
+ }
6
+ export declare function getAllPromptTemplates(): PromptTemplate[];
7
+ export declare function resolvePromptTemplate(name: string): PromptTemplate | undefined;
8
+ export declare function applyPromptTemplate(basePrompt: string, templateName: string): string;
9
+ export declare function formatPromptTemplateList(): string;
10
+ //# sourceMappingURL=prompt-templates.d.ts.map
@@ -0,0 +1,111 @@
1
+ // prompt-templates.ts — named system prompt strategies for the reasoner.
2
+ // the active template is set via config or /prompt-template command.
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { BOLD, DIM, GREEN, RESET } from "../colors.js";
7
+ const BUILTIN_TEMPLATES = [
8
+ {
9
+ name: "default",
10
+ description: "balanced supervisor — intervene only when needed",
11
+ preamble: "",
12
+ },
13
+ {
14
+ name: "hands-off",
15
+ description: "minimal intervention — only act on errors and permission prompts",
16
+ preamble: `IMPORTANT OVERRIDE: You are in hands-off mode. Be extremely conservative with interventions.
17
+ - Only send_input when a session has an ERROR or a PERMISSION prompt that needs clearing.
18
+ - Do NOT nudge idle sessions — they may be thinking or compiling.
19
+ - Do NOT send motivational or progress-check messages.
20
+ - Prefer "wait" in almost all cases. Let agents work autonomously.
21
+ `,
22
+ },
23
+ {
24
+ name: "aggressive",
25
+ description: "proactive supervisor — nudge idle agents, push for progress",
26
+ preamble: `IMPORTANT OVERRIDE: You are in aggressive supervision mode. Be proactive.
27
+ - If a session has been idle for more than 60 seconds, nudge it with a prompt.
28
+ - If a session seems to be going in circles, redirect it with clearer instructions.
29
+ - Report progress frequently — every meaningful commit or test pass.
30
+ - Push agents toward completing their goals. Don't let them drift.
31
+ - Still avoid micromanaging specific code decisions.
32
+ `,
33
+ },
34
+ {
35
+ name: "review-focused",
36
+ description: "PR/MR review cycle — focus on CI, reviewer feedback, rebasing",
37
+ preamble: `IMPORTANT OVERRIDE: You are in review-focused mode. Prioritize PR/MR lifecycle.
38
+ - Watch for CI pipeline failures and push fixes immediately.
39
+ - Monitor for reviewer comments and address them promptly.
40
+ - If a PR is approved, push to merge. If blocked, ping the reviewer.
41
+ - Prefer rebasing over merge commits. Keep commit history clean.
42
+ - Report progress for each PR status change (CI green, review received, merged).
43
+ `,
44
+ },
45
+ {
46
+ name: "shipping",
47
+ description: "ship mode — focus on commits, pushes, and version bumps",
48
+ preamble: `IMPORTANT OVERRIDE: You are in shipping mode. Focus on getting code out the door.
49
+ - Push agents to commit and push frequently. Small atomic commits are better than big batches.
50
+ - If an agent has uncommitted work, nudge them to commit.
51
+ - Report progress for every push/commit.
52
+ - Don't let perfect be the enemy of good — ship working code, iterate later.
53
+ - Complete tasks as soon as their core goal is met. Don't gold-plate.
54
+ `,
55
+ },
56
+ ];
57
+ const AOAOE_DIR = join(homedir(), ".aoaoe");
58
+ const USER_TEMPLATES_FILE = join(AOAOE_DIR, "prompt-templates.json");
59
+ function loadUserTemplates() {
60
+ try {
61
+ if (!existsSync(USER_TEMPLATES_FILE))
62
+ return [];
63
+ const raw = JSON.parse(readFileSync(USER_TEMPLATES_FILE, "utf-8"));
64
+ if (!Array.isArray(raw))
65
+ return [];
66
+ return raw.filter((t) => !!t && typeof t === "object" &&
67
+ typeof t.name === "string" &&
68
+ typeof t.preamble === "string").map((t) => ({
69
+ ...t,
70
+ description: t.description || "(user-defined)",
71
+ }));
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ }
77
+ export function getAllPromptTemplates() {
78
+ const user = loadUserTemplates();
79
+ const userNames = new Set(user.map((t) => t.name.toLowerCase()));
80
+ const builtins = BUILTIN_TEMPLATES.filter((t) => !userNames.has(t.name.toLowerCase()));
81
+ return [...builtins, ...user];
82
+ }
83
+ export function resolvePromptTemplate(name) {
84
+ const templates = getAllPromptTemplates();
85
+ const lower = name.toLowerCase();
86
+ return (templates.find((t) => t.name.toLowerCase() === lower) ??
87
+ templates.find((t) => t.name.toLowerCase().startsWith(lower)));
88
+ }
89
+ export function applyPromptTemplate(basePrompt, templateName) {
90
+ if (!templateName || templateName === "default")
91
+ return basePrompt;
92
+ const template = resolvePromptTemplate(templateName);
93
+ if (!template || !template.preamble)
94
+ return basePrompt;
95
+ return `${template.preamble}\n${basePrompt}`;
96
+ }
97
+ export function formatPromptTemplateList() {
98
+ const templates = getAllPromptTemplates();
99
+ const lines = [];
100
+ lines.push(` ${BOLD}available prompt templates:${RESET}`);
101
+ lines.push("");
102
+ for (const t of templates) {
103
+ const preview = t.preamble ? t.preamble.split("\n")[0].slice(0, 60) : "(no preamble — base prompt only)";
104
+ lines.push(` ${GREEN}${t.name}${RESET} — ${t.description}`);
105
+ lines.push(` ${DIM} ${preview}${t.preamble.length > 60 ? "..." : ""}${RESET}`);
106
+ lines.push("");
107
+ }
108
+ lines.push(` ${DIM}custom templates: ${USER_TEMPLATES_FILE}${RESET}`);
109
+ return lines.join("\n");
110
+ }
111
+ //# sourceMappingURL=prompt-templates.js.map
@@ -1,5 +1,5 @@
1
1
  import type { Observation, TaskState } from "../types.js";
2
- export declare function buildSystemPrompt(globalContext?: string): string;
2
+ export declare function buildSystemPrompt(globalContext?: string, promptTemplate?: string): string;
3
3
  export interface SessionPolicyState {
4
4
  sessionId: string;
5
5
  lastOutputChangeAt: number;
@@ -1,4 +1,5 @@
1
1
  import { goalToList } from "../types.js";
2
+ import { applyPromptTemplate } from "./prompt-templates.js";
2
3
  // base system prompt -- global context appended at runtime via buildSystemPrompt()
3
4
  const BASE_SYSTEM_PROMPT = `You are a supervisor managing multiple AI coding agents in an agent-of-empires (AoE) tmux session.
4
5
 
@@ -43,10 +44,11 @@ Rules:
43
44
  - Use "complete_task" when a task's goal has been fully achieved and the agent has nothing left to do.
44
45
  This will clean up the session. Only use when truly done, not just idle.`;
45
46
  // build the full system prompt with global context appended
46
- export function buildSystemPrompt(globalContext) {
47
+ export function buildSystemPrompt(globalContext, promptTemplate) {
48
+ const base = applyPromptTemplate(BASE_SYSTEM_PROMPT, promptTemplate ?? "default");
47
49
  if (!globalContext)
48
- return BASE_SYSTEM_PROMPT;
49
- return `${BASE_SYSTEM_PROMPT}\n${globalContext}`;
50
+ return base;
51
+ return `${base}\n${globalContext}`;
50
52
  }
51
53
  // detect permission prompts in tmux output.
52
54
  // the prompt-watcher module (prompt-watcher.ts) handles these reactively via
package/dist/task-cli.js CHANGED
@@ -8,6 +8,7 @@ import { loadConfig } from "./config.js";
8
8
  import { resolveProfiles } from "./tui.js";
9
9
  import { loadTaskState, saveTaskState, formatTaskTable, syncTaskDefinitionsFromState, taskStateKey, resolveTaskRepoPath, TaskManager, loadTaskDefinitions, injectGoalToSession } from "./task-manager.js";
10
10
  import { goalToList } from "./types.js";
11
+ import { resolveTemplate, formatTemplateList } from "./task-templates.js";
11
12
  import { BOLD, DIM, GREEN, YELLOW, RED, RESET } from "./colors.js";
12
13
  function buildProfileAwareAoeArgs(profile, tailArgs) {
13
14
  if (!profile || profile === "default")
@@ -25,9 +26,15 @@ function getTaskProfiles() {
25
26
  function taskCommandHelp(prefix = "aoaoe task") {
26
27
  return [
27
28
  `${prefix} list show tracked tasks`,
29
+ `${prefix} templates show available task templates`,
28
30
  `${prefix} reconcile link/create sessions now`,
29
31
  `${prefix} new <title> <path> create task + session`,
32
+ `${prefix} new <t> <p> --template roadmap create with template goal`,
30
33
  `${prefix} start|stop <task> control task session`,
34
+ `${prefix} start-all start all paused/pending tasks`,
35
+ `${prefix} stop-all stop all active tasks`,
36
+ `${prefix} pause-all pause all active tasks (no session stop)`,
37
+ `${prefix} resume-all resume all paused tasks`,
31
38
  `${prefix} edit <task> <goal> update task goal`,
32
39
  `${prefix} rm <task> remove task + session`,
33
40
  "step-in quick path: /task <session> :: <new instructions>",
@@ -331,6 +338,67 @@ export async function runTaskCli(argv) {
331
338
  await taskStop(args[0]);
332
339
  return;
333
340
  }
341
+ case "start-all": {
342
+ const states = loadTaskState();
343
+ const tasks = [...states.values()].filter((t) => t.status === "paused" || t.status === "pending");
344
+ if (tasks.length === 0) {
345
+ console.log(`${DIM}no paused/pending tasks to start${RESET}`);
346
+ return;
347
+ }
348
+ for (const t of tasks)
349
+ await taskStart(t.sessionTitle);
350
+ console.log(`${GREEN}started ${tasks.length} task(s)${RESET}`);
351
+ return;
352
+ }
353
+ case "stop-all": {
354
+ const states = loadTaskState();
355
+ const tasks = [...states.values()].filter((t) => t.status === "active");
356
+ if (tasks.length === 0) {
357
+ console.log(`${DIM}no active tasks to stop${RESET}`);
358
+ return;
359
+ }
360
+ for (const t of tasks)
361
+ await taskStop(t.sessionTitle);
362
+ console.log(`${YELLOW}stopped ${tasks.length} task(s)${RESET}`);
363
+ return;
364
+ }
365
+ case "pause-all": {
366
+ const states = loadTaskState();
367
+ let count = 0;
368
+ for (const [key, t] of states) {
369
+ if (t.status === "active") {
370
+ t.status = "paused";
371
+ states.set(key, t);
372
+ count++;
373
+ }
374
+ }
375
+ if (count === 0) {
376
+ console.log(`${DIM}no active tasks to pause${RESET}`);
377
+ return;
378
+ }
379
+ saveTaskState(states);
380
+ console.log(`${YELLOW}paused ${count} task(s)${RESET}`);
381
+ return;
382
+ }
383
+ case "resume-all": {
384
+ const states = loadTaskState();
385
+ let count = 0;
386
+ for (const [key, t] of states) {
387
+ if (t.status === "paused") {
388
+ t.status = "active";
389
+ t.stuckNudgeCount = 0;
390
+ states.set(key, t);
391
+ count++;
392
+ }
393
+ }
394
+ if (count === 0) {
395
+ console.log(`${DIM}no paused tasks to resume${RESET}`);
396
+ return;
397
+ }
398
+ saveTaskState(states);
399
+ console.log(`${GREEN}resumed ${count} task(s)${RESET}`);
400
+ return;
401
+ }
334
402
  case "edit": {
335
403
  if (!args[0] || !args[1]) {
336
404
  console.error(`usage: aoaoe task edit <name|id> <new goal text>`);
@@ -343,7 +411,7 @@ export async function runTaskCli(argv) {
343
411
  case "create":
344
412
  case "add": {
345
413
  if (!args[0] || !args[1]) {
346
- console.error(`usage: aoaoe task new <title> <path> [--tool opencode] [--mode new|existing|auto]`);
414
+ console.error(`usage: aoaoe task new <title> <path> [--template roadmap] [--tool opencode] [--mode new|existing|auto]`);
347
415
  return;
348
416
  }
349
417
  const title = args[0];
@@ -360,7 +428,30 @@ export async function runTaskCli(argv) {
360
428
  const profileIdx = args.indexOf("--profile");
361
429
  if (profileIdx !== -1 && args[profileIdx + 1])
362
430
  profile = args[profileIdx + 1];
363
- await taskNew(title, path, tool, mode, profile);
431
+ // apply template if specified
432
+ const templateIdx = args.indexOf("--template");
433
+ let templateGoal;
434
+ if (templateIdx !== -1 && args[templateIdx + 1]) {
435
+ const tmpl = resolveTemplate(args[templateIdx + 1]);
436
+ if (!tmpl) {
437
+ console.error(`${RED}unknown template: ${args[templateIdx + 1]}${RESET}`);
438
+ console.log(formatTemplateList());
439
+ return;
440
+ }
441
+ templateGoal = tmpl.goal;
442
+ if (tmpl.tool)
443
+ tool = tmpl.tool;
444
+ console.log(`${DIM}using template: ${tmpl.name}${RESET}`);
445
+ }
446
+ const ok = await taskNew(title, path, tool, mode, profile);
447
+ if (ok && templateGoal) {
448
+ await taskEdit(title, templateGoal);
449
+ }
450
+ return;
451
+ }
452
+ case "templates":
453
+ case "template": {
454
+ console.log(formatTemplateList());
364
455
  return;
365
456
  }
366
457
  case "reconcile": {
@@ -383,7 +474,7 @@ export async function runTaskCli(argv) {
383
474
  }
384
475
  default:
385
476
  console.error(`unknown task subcommand: ${sub}`);
386
- console.error(`usage: aoaoe task [list|start|stop|edit|new|rm|reconcile|help]`);
477
+ console.error(`usage: aoaoe task [list|start|stop|edit|new|rm|reconcile|templates|help]`);
387
478
  }
388
479
  }
389
480
  /**
@@ -529,6 +620,9 @@ export async function handleTaskSlashCommand(args) {
529
620
  const { created, linked, goalsInjected } = await tm.reconcileSessions();
530
621
  return `reconciled tasks: +${created.length} created, +${linked.length} linked, +${goalsInjected.length} goals injected`;
531
622
  }
532
- return "usage: /task [list|start|stop|edit|new|rm|reconcile|help] [args]";
623
+ if (sub === "templates" || sub === "template") {
624
+ return formatTemplateList();
625
+ }
626
+ return "usage: /task [list|start|stop|edit|new|rm|reconcile|templates|help] [args]";
533
627
  }
534
628
  //# sourceMappingURL=task-cli.js.map
@@ -31,6 +31,7 @@ export declare class TaskManager {
31
31
  goalsInjected: string[];
32
32
  }>;
33
33
  reportProgress(sessionTitle: string, summary: string): void;
34
+ recordStuckNudge(sessionTitle: string, maxNudges: number): boolean;
34
35
  completeTask(sessionTitle: string, summary: string, cleanupSession?: boolean): Promise<void>;
35
36
  private save;
36
37
  }
@@ -460,12 +460,28 @@ export class TaskManager {
460
460
  const entry = { at: Date.now(), summary };
461
461
  task.progress.push(entry);
462
462
  task.lastProgressAt = Date.now();
463
+ task.stuckNudgeCount = 0; // progress means it's not stuck anymore
463
464
  // keep progress bounded (last 50 entries)
464
465
  if (task.progress.length > 50) {
465
466
  task.progress = task.progress.slice(-50);
466
467
  }
467
468
  this.save();
468
469
  }
470
+ // record that the reasoner nudged a stuck session. returns true if the task should be auto-paused.
471
+ recordStuckNudge(sessionTitle, maxNudges) {
472
+ const task = this.getTaskForSession(sessionTitle);
473
+ if (!task || task.status !== "active")
474
+ return false;
475
+ task.stuckNudgeCount = (task.stuckNudgeCount ?? 0) + 1;
476
+ if (maxNudges > 0 && task.stuckNudgeCount >= maxNudges) {
477
+ task.status = "paused";
478
+ log(`auto-paused '${sessionTitle}' after ${task.stuckNudgeCount} stuck nudges (threshold: ${maxNudges})`);
479
+ this.save();
480
+ return true;
481
+ }
482
+ this.save();
483
+ return false;
484
+ }
469
485
  // mark a task as completed, optionally clean up its session.
470
486
  // if the task definition has continueOnRoadmap:true, resets with next backlog items instead.
471
487
  async completeTask(sessionTitle, summary, cleanupSession = true) {
@@ -0,0 +1,11 @@
1
+ export interface TaskTemplate {
2
+ name: string;
3
+ description: string;
4
+ goal: string;
5
+ tool?: string;
6
+ continueOnRoadmap?: boolean;
7
+ }
8
+ export declare function getAllTemplates(): TaskTemplate[];
9
+ export declare function resolveTemplate(name: string): TaskTemplate | undefined;
10
+ export declare function formatTemplateList(): string;
11
+ //# sourceMappingURL=task-templates.d.ts.map
@@ -0,0 +1,96 @@
1
+ // task-templates.ts — built-in + user-defined task goal templates.
2
+ // templates are just named goal strings that can be applied via --template flag.
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { BOLD, DIM, GREEN, RESET } from "./colors.js";
7
+ // built-in templates derived from real usage patterns
8
+ const BUILTIN_TEMPLATES = [
9
+ {
10
+ name: "roadmap",
11
+ description: "grind through claude.md/AGENTS.md roadmap items, commit each improvement",
12
+ goal: "Find the roadmap (check claude.md, README.md, AGENTS.md). Pick the next item and implement it. Add new ideas to the roadmap as you or the human think of them. Commit and push after each self-contained improvement.",
13
+ continueOnRoadmap: true,
14
+ },
15
+ {
16
+ name: "roadmap-strict",
17
+ description: "roadmap grind with full tests required before push",
18
+ goal: "Find the roadmap (check claude.md, README.md, AGENTS.md). Pick the next item and implement it with full tests. Add new ideas to the roadmap as you or the human think of them. Commit and push after each self-contained improvement.",
19
+ continueOnRoadmap: true,
20
+ },
21
+ {
22
+ name: "pr-review",
23
+ description: "monitor open PRs/MRs, address review feedback, push fixes, ping reviewers",
24
+ goal: "Check for open PRs/MRs in this repo. Address any review feedback by pushing fixes. Monitor CI pipelines and fix failures. Ping reviewers when changes are ready for re-review. Update claude.md with PR status.",
25
+ },
26
+ {
27
+ name: "bugfix",
28
+ description: "reproduce, fix, test, and commit a specific bug",
29
+ goal: "Investigate the reported bug. Reproduce it with a minimal test case. Fix the root cause. Add regression tests. Commit and push the fix.",
30
+ },
31
+ {
32
+ name: "explore",
33
+ description: "read and understand the codebase, update claude.md with findings",
34
+ goal: "Explore the codebase. Read AGENTS.md and claude.md for context. Understand the architecture, key modules, and current state. Update claude.md with your findings and any issues you discover.",
35
+ },
36
+ {
37
+ name: "ci-fix",
38
+ description: "fix failing CI pipelines — build errors, test failures, flaky tests",
39
+ goal: "Check CI pipeline status. Fix any build errors, test failures, or flaky tests. Push fixes and verify pipelines go green. Add retry config for transient infra failures if needed.",
40
+ },
41
+ ];
42
+ const AOAOE_DIR = join(homedir(), ".aoaoe");
43
+ const USER_TEMPLATES_FILE = join(AOAOE_DIR, "templates.json");
44
+ // load user-defined templates from ~/.aoaoe/templates.json (optional)
45
+ function loadUserTemplates() {
46
+ try {
47
+ if (!existsSync(USER_TEMPLATES_FILE))
48
+ return [];
49
+ const raw = JSON.parse(readFileSync(USER_TEMPLATES_FILE, "utf-8"));
50
+ if (!Array.isArray(raw))
51
+ return [];
52
+ return raw.filter((t) => !!t && typeof t === "object" &&
53
+ typeof t.name === "string" &&
54
+ typeof t.goal === "string").map((t) => ({
55
+ ...t,
56
+ description: t.description || "(user-defined)",
57
+ }));
58
+ }
59
+ catch {
60
+ return [];
61
+ }
62
+ }
63
+ // get all templates — user templates override builtins with the same name
64
+ export function getAllTemplates() {
65
+ const user = loadUserTemplates();
66
+ const userNames = new Set(user.map((t) => t.name.toLowerCase()));
67
+ const builtins = BUILTIN_TEMPLATES.filter((t) => !userNames.has(t.name.toLowerCase()));
68
+ return [...builtins, ...user];
69
+ }
70
+ // resolve a template by name (case-insensitive, prefix match)
71
+ export function resolveTemplate(name) {
72
+ const templates = getAllTemplates();
73
+ const lower = name.toLowerCase();
74
+ return (templates.find((t) => t.name.toLowerCase() === lower) ??
75
+ templates.find((t) => t.name.toLowerCase().startsWith(lower)));
76
+ }
77
+ // format template list for display
78
+ export function formatTemplateList() {
79
+ const templates = getAllTemplates();
80
+ const lines = [];
81
+ lines.push(` ${BOLD}available task templates:${RESET}`);
82
+ lines.push("");
83
+ for (const t of templates) {
84
+ const goalPreview = t.goal.length > 70 ? t.goal.slice(0, 67) + "..." : t.goal;
85
+ lines.push(` ${GREEN}${t.name}${RESET} — ${t.description}`);
86
+ lines.push(` ${DIM} goal: ${goalPreview}${RESET}`);
87
+ if (t.tool)
88
+ lines.push(` ${DIM} tool: ${t.tool}${RESET}`);
89
+ if (t.continueOnRoadmap)
90
+ lines.push(` ${DIM} continueOnRoadmap: true${RESET}`);
91
+ lines.push("");
92
+ }
93
+ lines.push(` ${DIM}custom templates: ${USER_TEMPLATES_FILE}${RESET}`);
94
+ return lines.join("\n");
95
+ }
96
+ //# sourceMappingURL=task-templates.js.map
package/dist/tui.d.ts CHANGED
@@ -579,6 +579,10 @@ export declare class TUI {
579
579
  isPinned(id: string): boolean;
580
580
  /** Return count of pinned sessions. */
581
581
  getPinnedCount(): number;
582
+ /** Return titles of all pinned sessions. */
583
+ getPinnedTitles(): string[];
584
+ /** Load a pin preset: clear current pins, pin sessions matching given titles. Returns count pinned. */
585
+ loadPinPreset(titles: string[]): number;
582
586
  /** Enable or disable focus mode. When focused, only pinned sessions are visible. */
583
587
  setFocus(enabled: boolean): void;
584
588
  /** Return whether focus mode is enabled. */
package/dist/tui.js CHANGED
@@ -971,6 +971,8 @@ export const BUILTIN_COMMANDS = new Set([
971
971
  "/supervisor",
972
972
  "/runbook",
973
973
  "/incident",
974
+ "/pin-save", "/pin-load", "/pin-delete", "/pin-presets",
975
+ "/prompt-template", "/health",
974
976
  "/explain", "/verbose", "/clear", "/view", "/back", "/sort", "/compact",
975
977
  "/pin", "/bell", "/focus", "/mute", "/unmute-all", "/filter", "/who",
976
978
  "/uptime", "/auto-pin", "/note", "/notes", "/clip", "/diff", "/mark",
@@ -1442,6 +1444,26 @@ export class TUI {
1442
1444
  getPinnedCount() {
1443
1445
  return this.pinnedIds.size;
1444
1446
  }
1447
+ /** Return titles of all pinned sessions. */
1448
+ getPinnedTitles() {
1449
+ return this.sessions.filter((s) => this.pinnedIds.has(s.id)).map((s) => s.title);
1450
+ }
1451
+ /** Load a pin preset: clear current pins, pin sessions matching given titles. Returns count pinned. */
1452
+ loadPinPreset(titles) {
1453
+ this.pinnedIds.clear();
1454
+ const lowerTitles = new Set(titles.map((t) => t.toLowerCase()));
1455
+ let count = 0;
1456
+ for (const s of this.sessions) {
1457
+ if (lowerTitles.has(s.title.toLowerCase())) {
1458
+ this.pinnedIds.add(s.id);
1459
+ count++;
1460
+ }
1461
+ }
1462
+ this.sessions = sortSessions(this.sessions, this.sortMode, this.lastChangeAt, this.pinnedIds);
1463
+ if (this.active)
1464
+ this.paintAll();
1465
+ return count;
1466
+ }
1445
1467
  /** Enable or disable focus mode. When focused, only pinned sessions are visible. */
1446
1468
  setFocus(enabled) {
1447
1469
  if (enabled === this.focusMode)
package/dist/types.d.ts CHANGED
@@ -111,12 +111,14 @@ export interface AoaoeConfig {
111
111
  actionCooldownMs?: number;
112
112
  userActivityThresholdMs?: number;
113
113
  allowDestructive?: boolean;
114
+ maxStuckNudgesBeforePause?: number;
114
115
  };
115
116
  contextFiles: string[];
116
117
  sessionDirs: Record<string, string>;
117
118
  protectedSessions: string[];
118
119
  captureLinesCount: number;
119
120
  reasonIntervalMs: number;
121
+ promptTemplate?: string;
120
122
  verbose: boolean;
121
123
  dryRun: boolean;
122
124
  observe: boolean;
@@ -200,6 +202,7 @@ export interface TaskState {
200
202
  createdAt?: number;
201
203
  lastProgressAt?: number;
202
204
  completedAt?: number;
205
+ stuckNudgeCount?: number;
203
206
  progress: TaskProgress[];
204
207
  }
205
208
  export declare function toTaskState(raw: unknown): TaskState | null;
package/dist/types.js CHANGED
@@ -91,6 +91,7 @@ export function toTaskState(raw) {
91
91
  goal: r.goal,
92
92
  status: r.status,
93
93
  dependsOn: Array.isArray(r.dependsOn) ? r.dependsOn.filter((d) => typeof d === "string" && d.length > 0) : undefined,
94
+ stuckNudgeCount: typeof r.stuckNudgeCount === "number" ? r.stuckNudgeCount : undefined,
94
95
  sessionId: typeof r.sessionId === "string" ? r.sessionId : undefined,
95
96
  createdAt: typeof r.createdAt === "number" ? r.createdAt : undefined,
96
97
  lastProgressAt: typeof r.lastProgressAt === "number" ? r.lastProgressAt : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "0.186.0",
3
+ "version": "0.187.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",