aoaoe 1.0.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <a href="https://github.com/Talador12/agent-of-agent-of-empires/actions/workflows/ci.yml"><img src="https://github.com/Talador12/agent-of-agent-of-empires/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
5
5
  <a href="https://www.npmjs.com/package/aoaoe"><img src="https://img.shields.io/npm/v/aoaoe" alt="npm version"></a>
6
6
  <a href="https://github.com/Talador12/agent-of-agent-of-empires/releases"><img src="https://img.shields.io/github/v/release/Talador12/agent-of-agent-of-empires" alt="GitHub release"></a>
7
- <img src="https://img.shields.io/badge/tests-2427-brightgreen" alt="tests">
7
+ <img src="https://img.shields.io/badge/tests-3332-brightgreen" alt="tests">
8
8
  <img src="https://img.shields.io/badge/node-%3E%3D20-blue" alt="Node.js >= 20">
9
9
  <img src="https://img.shields.io/badge/runtime%20deps-0-brightgreen" alt="zero runtime dependencies">
10
10
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
@@ -822,6 +822,28 @@ The daemon and chat UI communicate via files in `~/.aoaoe/`:
822
822
  | `chat.pid` | chat UI | daemon | Chat process PID for detection |
823
823
  | `actions.log` | daemon | -- | Persistent action history (JSONL) |
824
824
 
825
+ ## Intelligence Modules
826
+
827
+ aoaoe includes 55 intelligence modules that run every daemon tick without LLM calls. They handle observability, automation, and fleet management autonomously:
828
+
829
+ **Reasoning Pipeline** (7 gates, runs before every LLM call):
830
+ - Fleet rate limiter, observation cache, priority filter, context compressor, LLM call, approval workflow, cost tracker
831
+
832
+ **Per-Tick Autonomous Systems**:
833
+ - Session summarizer, conflict detector + auto-resolver, goal completion detector, cost budget enforcer
834
+ - Activity heatmap, budget predictor, task retry with backoff, adaptive poll controller
835
+ - Fleet SLA monitor, progress velocity tracker, recovery playbook, dependency scheduler
836
+ - Session graduation (trust ladder), fleet utilization tracker, anomaly detector
837
+ - Workflow engine (fan-out/fan-in stage orchestration)
838
+
839
+ **On-Demand Analytics** (via TUI commands):
840
+ - Audit trail + search, fleet snapshots + diff, lifecycle analytics, cost attribution
841
+ - Goal decomposition, difficulty scoring, smart nudge generation, template auto-detection
842
+ - Fleet-wide search, nudge effectiveness tracking, difficulty-based allocation
843
+ - Goal refinement from completed task patterns, fleet HTML export, session replay
844
+
845
+ See `AGENTS.md` for the full source layout with all 55 modules described.
846
+
825
847
  ## Project Structure
826
848
 
827
849
  ```
@@ -0,0 +1,24 @@
1
+ export declare const CLI_COMMANDS: readonly ["init", "tasks", "progress", "health", "summary", "supervisor", "incident", "runbook", "adopt", "doctor", "stats", "replay", "export", "tail", "web", "sync", "test-context", "task", "config"];
2
+ export declare const CLI_FLAGS: readonly ["--config", "--verbose", "--dry-run", "--observe", "--confirm", "--json", "--ndjson", "--watch", "--changes-only", "--heartbeat", "--follow", "--help", "--version"];
3
+ export declare const TUI_COMMANDS: readonly ["/supervisor", "/incident", "/runbook", "/progress", "/health", "/prompt-template", "/pin-save", "/pin-load", "/pin-presets", "/activity", "/conflicts", "/heatmap", "/audit", "/audit-stats", "/audit-search", "/fleet-snap", "/budget-predict", "/retries", "/fleet-forecast", "/priority", "/escalations", "/poll-status", "/drift", "/goal-progress", "/pool", "/reasoner-cost", "/anomaly", "/sla", "/velocity", "/schedule", "/cost-summary", "/session-report", "/cache", "/rate-limit", "/recovery", "/lifecycle", "/cost-report", "/decompose", "/memory", "/dep-graph", "/approvals", "/approve", "/reject", "/fleet-diff", "/template", "/difficulty", "/smart-nudge", "/utilization", "/detect-template", "/fleet-search", "/nudge-stats", "/allocation", "/graduation", "/refine", "/export", "/clear"];
4
+ /**
5
+ * Generate bash completion script.
6
+ */
7
+ export declare function generateBashCompletion(): string;
8
+ /**
9
+ * Generate zsh completion script.
10
+ */
11
+ export declare function generateZshCompletion(): string;
12
+ /**
13
+ * Generate fish completion script.
14
+ */
15
+ export declare function generateFishCompletion(): string;
16
+ /**
17
+ * Generate completion script for the specified shell.
18
+ */
19
+ export declare function generateCompletion(shell: "bash" | "zsh" | "fish"): string;
20
+ /**
21
+ * List all available commands for help display.
22
+ */
23
+ export declare function formatCommandList(): string[];
24
+ //# sourceMappingURL=cli-completions.d.ts.map
@@ -0,0 +1,114 @@
1
+ // cli-completions.ts — shell autocomplete for all aoaoe commands and TUI slash commands.
2
+ // generates completion scripts for bash, zsh, and fish.
3
+ export const CLI_COMMANDS = [
4
+ "init", "tasks", "progress", "health", "summary", "supervisor",
5
+ "incident", "runbook", "adopt", "doctor", "stats", "replay",
6
+ "export", "tail", "web", "sync", "test-context",
7
+ "task", "config",
8
+ ];
9
+ export const CLI_FLAGS = [
10
+ "--config", "--verbose", "--dry-run", "--observe", "--confirm",
11
+ "--json", "--ndjson", "--watch", "--changes-only", "--heartbeat", "--follow",
12
+ "--help", "--version",
13
+ ];
14
+ export const TUI_COMMANDS = [
15
+ "/supervisor", "/incident", "/runbook", "/progress", "/health",
16
+ "/prompt-template", "/pin-save", "/pin-load", "/pin-presets",
17
+ "/activity", "/conflicts", "/heatmap", "/audit", "/audit-stats",
18
+ "/audit-search", "/fleet-snap", "/budget-predict", "/retries",
19
+ "/fleet-forecast", "/priority", "/escalations", "/poll-status",
20
+ "/drift", "/goal-progress", "/pool", "/reasoner-cost",
21
+ "/anomaly", "/sla", "/velocity", "/schedule", "/cost-summary",
22
+ "/session-report", "/cache", "/rate-limit", "/recovery",
23
+ "/lifecycle", "/cost-report", "/decompose", "/memory",
24
+ "/dep-graph", "/approvals", "/approve", "/reject", "/fleet-diff",
25
+ "/template", "/difficulty", "/smart-nudge", "/utilization",
26
+ "/detect-template", "/fleet-search", "/nudge-stats", "/allocation",
27
+ "/graduation", "/refine", "/export", "/clear",
28
+ ];
29
+ /**
30
+ * Generate bash completion script.
31
+ */
32
+ export function generateBashCompletion() {
33
+ const cmds = CLI_COMMANDS.join(" ");
34
+ const flags = CLI_FLAGS.join(" ");
35
+ return `# aoaoe bash completion
36
+ # Add to ~/.bashrc: eval "$(aoaoe completions bash)"
37
+ _aoaoe() {
38
+ local cur prev
39
+ cur="\${COMP_WORDS[COMP_CWORD]}"
40
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
41
+
42
+ if [[ "\${cur}" == --* ]]; then
43
+ COMPREPLY=($(compgen -W "${flags}" -- "\${cur}"))
44
+ elif [[ "\${COMP_CWORD}" -eq 1 ]]; then
45
+ COMPREPLY=($(compgen -W "${cmds}" -- "\${cur}"))
46
+ fi
47
+ }
48
+ complete -F _aoaoe aoaoe
49
+ `;
50
+ }
51
+ /**
52
+ * Generate zsh completion script.
53
+ */
54
+ export function generateZshCompletion() {
55
+ const cmdList = CLI_COMMANDS.map((c) => `'${c}'`).join(" ");
56
+ const flagList = CLI_FLAGS.map((f) => `'${f}'`).join(" ");
57
+ return `# aoaoe zsh completion
58
+ # Add to ~/.zshrc: eval "$(aoaoe completions zsh)"
59
+ _aoaoe() {
60
+ local -a commands flags
61
+ commands=(${cmdList})
62
+ flags=(${flagList})
63
+
64
+ if (( CURRENT == 2 )); then
65
+ _describe 'command' commands
66
+ elif [[ "\${words[CURRENT]}" == --* ]]; then
67
+ _describe 'flag' flags
68
+ fi
69
+ }
70
+ compdef _aoaoe aoaoe
71
+ `;
72
+ }
73
+ /**
74
+ * Generate fish completion script.
75
+ */
76
+ export function generateFishCompletion() {
77
+ const lines = [
78
+ "# aoaoe fish completion",
79
+ "# Add to ~/.config/fish/completions/aoaoe.fish",
80
+ ];
81
+ for (const cmd of CLI_COMMANDS) {
82
+ lines.push(`complete -c aoaoe -n '__fish_use_subcommand' -a '${cmd}'`);
83
+ }
84
+ for (const flag of CLI_FLAGS) {
85
+ lines.push(`complete -c aoaoe -l '${flag.replace(/^--/, "")}'`);
86
+ }
87
+ return lines.join("\n") + "\n";
88
+ }
89
+ /**
90
+ * Generate completion script for the specified shell.
91
+ */
92
+ export function generateCompletion(shell) {
93
+ switch (shell) {
94
+ case "bash": return generateBashCompletion();
95
+ case "zsh": return generateZshCompletion();
96
+ case "fish": return generateFishCompletion();
97
+ }
98
+ }
99
+ /**
100
+ * List all available commands for help display.
101
+ */
102
+ export function formatCommandList() {
103
+ const lines = [];
104
+ lines.push(` CLI commands (${CLI_COMMANDS.length}):`);
105
+ lines.push(` ${CLI_COMMANDS.join(", ")}`);
106
+ lines.push("");
107
+ lines.push(` TUI slash commands (${TUI_COMMANDS.length}):`);
108
+ // group in rows of 6
109
+ for (let i = 0; i < TUI_COMMANDS.length; i += 6) {
110
+ lines.push(` ${TUI_COMMANDS.slice(i, i + 6).join(" ")}`);
111
+ }
112
+ return lines;
113
+ }
114
+ //# sourceMappingURL=cli-completions.js.map
package/dist/config.d.ts CHANGED
@@ -91,6 +91,9 @@ export declare function parseCliArgs(argv: string[]): {
91
91
  replaySpeed?: number;
92
92
  replayLast?: string;
93
93
  registerTitle?: string;
94
+ runService: boolean;
95
+ runCompletions: boolean;
96
+ completionsShell?: string;
94
97
  };
95
98
  export declare function printHelp(): void;
96
99
  //# sourceMappingURL=config.d.ts.map
package/dist/config.js CHANGED
@@ -359,7 +359,7 @@ export function parseCliArgs(argv) {
359
359
  let initForce = false;
360
360
  let runTaskCli = false;
361
361
  let registerTitle;
362
- 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, runSummary: false, runAdopt: false, adoptTemplate: undefined, 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, runBackup: false, backupOutput: undefined, runRestore: false, restoreInput: undefined, runSync: false, syncAction: undefined, syncRemote: undefined, runWeb: false, webPort: undefined, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, exportTasks: false, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
362
+ 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, runSummary: false, runAdopt: false, adoptTemplate: undefined, 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, runBackup: false, backupOutput: undefined, runRestore: false, restoreInput: undefined, runSync: false, syncAction: undefined, syncRemote: undefined, runWeb: false, webPort: undefined, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, exportTasks: false, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined, runService: false, runCompletions: false, completionsShell: undefined };
363
363
  // check for subcommand as first non-flag arg
364
364
  if (argv[2] === "test-context") {
365
365
  return { ...defaults, testContext: true };
@@ -573,6 +573,13 @@ export function parseCliArgs(argv) {
573
573
  if (argv[2] === "doctor") {
574
574
  return { ...defaults, runDoctor: true };
575
575
  }
576
+ if (argv[2] === "service") {
577
+ return { ...defaults, runService: true };
578
+ }
579
+ if (argv[2] === "completions") {
580
+ const shell = argv[3] ?? "bash";
581
+ return { ...defaults, runCompletions: true, completionsShell: shell };
582
+ }
576
583
  if (argv[2] === "backup") {
577
584
  const output = argv[3] && !argv[3].startsWith("-") ? argv[3] : undefined;
578
585
  return { ...defaults, runBackup: true, backupOutput: output };
package/dist/index.js CHANGED
@@ -59,6 +59,9 @@ import { GraduationManager } from "./session-graduation.js";
59
59
  import { filterThroughApproval, formatApprovalWorkflowStatus } from "./approval-workflow.js";
60
60
  import { analyzeCompletedTasks, refineGoal, formatGoalRefinement } from "./goal-refiner.js";
61
61
  import { generateHtmlReport, buildReportData } from "./fleet-export.js";
62
+ import { installService } from "./service-generator.js";
63
+ import { buildSessionReplay, formatReplay, summarizeReplay } from "./session-replay.js";
64
+ import { advanceWorkflow, formatWorkflow } from "./workflow-engine.js";
62
65
  import { buildLifecycleRecords, computeLifecycleStats, formatLifecycleStats } from "./lifecycle-analytics.js";
63
66
  import { buildCostAttributions, computeCostReport, formatCostReport } from "./cost-attribution.js";
64
67
  import { decomposeGoal, formatDecomposition } from "./goal-decomposer.js";
@@ -87,7 +90,7 @@ const AOAOE_DIR = join(homedir(), ".aoaoe"); // watch dir for wakeable sleep
87
90
  const INPUT_FILE = join(AOAOE_DIR, "pending-input.txt"); // file IPC from chat.ts
88
91
  const TASK_RECONCILE_EVERY_POLLS = 6;
89
92
  async function main() {
90
- const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showTasksJson, runProgress, progressSince, progressJson, runHealth, healthJson, runSummary, runAdopt, adoptTemplate, 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, runBackup, backupOutput, runRestore, restoreInput, runSync, syncAction, syncRemote, runWeb, webPort, 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);
93
+ const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showTasksJson, runProgress, progressSince, progressJson, runHealth, healthJson, runSummary, runAdopt, adoptTemplate, 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, runBackup, backupOutput, runRestore, restoreInput, runSync, syncAction, syncRemote, runWeb, webPort, runLogs, logsActions, logsGrep, logsCount, runExport, exportFormat, exportOutput, exportLast, runInit, initForce, runTaskCli: isTaskCli, runTail: isTail, tailFollow, tailCount, runStats: isStats, statsLast, runReplay: isReplay, replaySpeed, replayLast, registerTitle, runService, runCompletions, completionsShell } = parseCliArgs(process.argv);
91
94
  if (help) {
92
95
  printHelp();
93
96
  process.exit(0);
@@ -232,6 +235,18 @@ async function main() {
232
235
  await runDoctorCheck();
233
236
  return;
234
237
  }
238
+ if (runService) {
239
+ const lines = installService({ workingDir: process.cwd() });
240
+ for (const l of lines)
241
+ console.log(l);
242
+ return;
243
+ }
244
+ if (runCompletions) {
245
+ const { generateCompletion } = await import("./cli-completions.js");
246
+ const shell = (completionsShell ?? "bash");
247
+ console.log(generateCompletion(shell));
248
+ return;
249
+ }
235
250
  if (runBackup) {
236
251
  try {
237
252
  const result = await createBackup(backupOutput);
@@ -551,6 +566,7 @@ async function main() {
551
566
  const recoveryPlaybookManager = new RecoveryPlaybookManager();
552
567
  const approvalQueue = new ApprovalQueue();
553
568
  const graduationManager = new GraduationManager();
569
+ let activeWorkflow = null;
554
570
  // audit: log daemon start
555
571
  audit("daemon_start", `daemon started (v${pkg ?? "dev"}, reasoner=${config.reasoner})`);
556
572
  const refreshTaskSupervisorState = (reason) => {
@@ -2420,6 +2436,37 @@ async function main() {
2420
2436
  writeFileSync(filepath, html);
2421
2437
  tui.log("system", `fleet report exported: ${filepath}`);
2422
2438
  });
2439
+ // wire /service — generate systemd/launchd service file
2440
+ input.onService(() => {
2441
+ const lines = installService({ workingDir: process.cwd() });
2442
+ for (const l of lines)
2443
+ tui.log("system", l);
2444
+ });
2445
+ // wire /session-replay — session activity timeline replay
2446
+ input.onSessionReplay((target) => {
2447
+ const replay = buildSessionReplay(target);
2448
+ if (replay.events.length === 0) {
2449
+ tui.log("system", `replay: no events for "${target}" — run the daemon to generate audit data first`);
2450
+ return;
2451
+ }
2452
+ const summary = summarizeReplay(replay);
2453
+ for (const l of summary)
2454
+ tui.log("system", l);
2455
+ tui.log("system", "");
2456
+ const detailed = formatReplay(replay);
2457
+ for (const l of detailed)
2458
+ tui.log("system", l);
2459
+ });
2460
+ // wire /workflow — show active workflow state
2461
+ input.onWorkflow(() => {
2462
+ if (!activeWorkflow) {
2463
+ tui.log("system", "workflow: no active workflow (define one in aoaoe.tasks.json with workflow stages)");
2464
+ return;
2465
+ }
2466
+ const lines = formatWorkflow(activeWorkflow);
2467
+ for (const l of lines)
2468
+ tui.log("system", l);
2469
+ });
2423
2470
  input.onCostSummary(() => {
2424
2471
  const sessions = tui.getSessions();
2425
2472
  const summary = computeCostSummary(sessions, tui.getAllSessionCosts());
@@ -4045,6 +4092,25 @@ async function main() {
4045
4092
  }
4046
4093
  }
4047
4094
  }
4095
+ // workflow engine: advance active workflow based on task states
4096
+ if (activeWorkflow && taskManager && tui) {
4097
+ const wf = activeWorkflow; // capture before potential null assignment
4098
+ const taskStates = new Map(taskManager.tasks.map((t) => [t.sessionTitle, t.status]));
4099
+ const { actions: wfActions, completed } = advanceWorkflow(wf, taskStates);
4100
+ for (const a of wfActions) {
4101
+ if (a.type === "activate_task") {
4102
+ const task = taskManager.getTaskForSession(a.detail);
4103
+ if (task && task.status === "pending")
4104
+ task.status = "active";
4105
+ }
4106
+ tui.log("status", `workflow: ${a.type} — ${a.detail}`);
4107
+ audit("task_created", `workflow ${a.type}: ${a.detail}`, a.detail);
4108
+ }
4109
+ if (completed) {
4110
+ tui.log("+ action", `workflow "${wf.name}" completed`);
4111
+ activeWorkflow = null;
4112
+ }
4113
+ }
4048
4114
  // trust ladder: record stable tick or failure, sync mode if escalated
4049
4115
  if (tui && decisionsThisTick > 0) {
4050
4116
  if (actionsFail > 0) {
package/dist/input.d.ts CHANGED
@@ -131,6 +131,9 @@ export type AllocationHandler = () => void;
131
131
  export type GraduationHandler = () => void;
132
132
  export type RefineHandler = (target: string) => void;
133
133
  export type ExportHandler = () => void;
134
+ export type ServiceHandler = () => void;
135
+ export type SessionReplayHandler = (target: string) => void;
136
+ export type WorkflowHandler = () => void;
134
137
  export interface MouseEvent {
135
138
  button: number;
136
139
  col: number;
@@ -443,6 +446,12 @@ export declare class InputReader {
443
446
  onGraduation(handler: GraduationHandler): void;
444
447
  onRefine(handler: RefineHandler): void;
445
448
  onExport(handler: ExportHandler): void;
449
+ private serviceHandler;
450
+ private sessionReplayHandler;
451
+ private workflowHandler;
452
+ onService(handler: ServiceHandler): void;
453
+ onSessionReplay(handler: SessionReplayHandler): void;
454
+ onWorkflow(handler: WorkflowHandler): void;
446
455
  onFleetSearch(handler: FleetSearchHandler): void;
447
456
  onNudgeStats(handler: NudgeStatsHandler): void;
448
457
  onAllocation(handler: AllocationHandler): void;
package/dist/input.js CHANGED
@@ -540,6 +540,12 @@ export class InputReader {
540
540
  onGraduation(handler) { this.graduationHandler = handler; }
541
541
  onRefine(handler) { this.refineHandler = handler; }
542
542
  onExport(handler) { this.exportHandler = handler; }
543
+ serviceHandler = null;
544
+ sessionReplayHandler = null;
545
+ workflowHandler = null;
546
+ onService(handler) { this.serviceHandler = handler; }
547
+ onSessionReplay(handler) { this.sessionReplayHandler = handler; }
548
+ onWorkflow(handler) { this.workflowHandler = handler; }
543
549
  onFleetSearch(handler) { this.fleetSearchHandler = handler; }
544
550
  onNudgeStats(handler) { this.nudgeStatsHandler = handler; }
545
551
  onAllocation(handler) { this.allocationHandler = handler; }
@@ -2364,6 +2370,30 @@ ${BOLD}other:${RESET}
2364
2370
  else
2365
2371
  console.error(`${DIM}export not available (no TUI)${RESET}`);
2366
2372
  break;
2373
+ case "/service":
2374
+ if (this.serviceHandler)
2375
+ this.serviceHandler();
2376
+ else
2377
+ console.error(`${DIM}service not available (no TUI)${RESET}`);
2378
+ break;
2379
+ case "/session-replay": {
2380
+ const rpArg = line.slice("/session-replay".length).trim();
2381
+ if (!rpArg) {
2382
+ console.error(`${DIM}usage: /session-replay <session-name>${RESET}`);
2383
+ break;
2384
+ }
2385
+ if (this.sessionReplayHandler)
2386
+ this.sessionReplayHandler(rpArg);
2387
+ else
2388
+ console.error(`${DIM}session-replay not available (no TUI)${RESET}`);
2389
+ break;
2390
+ }
2391
+ case "/workflow":
2392
+ if (this.workflowHandler)
2393
+ this.workflowHandler();
2394
+ else
2395
+ console.error(`${DIM}workflow not available (no TUI)${RESET}`);
2396
+ break;
2367
2397
  case "/clear":
2368
2398
  process.stderr.write("\x1b[2J\x1b[H");
2369
2399
  break;
@@ -0,0 +1,32 @@
1
+ export interface ServiceConfig {
2
+ name: string;
3
+ description: string;
4
+ execPath: string;
5
+ workingDir: string;
6
+ configPath?: string;
7
+ user?: string;
8
+ restartSec: number;
9
+ logPath?: string;
10
+ }
11
+ /**
12
+ * Generate a systemd unit file for Linux.
13
+ */
14
+ export declare function generateSystemdUnit(config?: Partial<ServiceConfig>): string;
15
+ /**
16
+ * Generate a launchd plist file for macOS.
17
+ */
18
+ export declare function generateLaunchdPlist(config?: Partial<ServiceConfig>): string;
19
+ /**
20
+ * Detect the current platform and generate the appropriate service file.
21
+ */
22
+ export declare function generateServiceFile(config?: Partial<ServiceConfig>): {
23
+ content: string;
24
+ filename: string;
25
+ installPath: string;
26
+ platform: string;
27
+ };
28
+ /**
29
+ * Write the service file and return install instructions.
30
+ */
31
+ export declare function installService(config?: Partial<ServiceConfig>): string[];
32
+ //# sourceMappingURL=service-generator.d.ts.map
@@ -0,0 +1,132 @@
1
+ // service-generator.ts — generate systemd/launchd service files for daemon
2
+ // auto-start on boot and crash restart. supports both Linux (systemd) and
3
+ // macOS (launchd) platforms.
4
+ import { writeFileSync, mkdirSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { homedir, platform } from "node:os";
7
+ const DEFAULT_CONFIG = {
8
+ name: "aoaoe",
9
+ description: "aoaoe — autonomous supervisor daemon for agent-of-empires",
10
+ execPath: "aoaoe",
11
+ workingDir: process.cwd(),
12
+ restartSec: 5,
13
+ };
14
+ /**
15
+ * Generate a systemd unit file for Linux.
16
+ */
17
+ export function generateSystemdUnit(config = {}) {
18
+ const c = { ...DEFAULT_CONFIG, ...config };
19
+ const execStart = c.configPath
20
+ ? `${c.execPath} --config ${c.configPath}`
21
+ : c.execPath;
22
+ return `[Unit]
23
+ Description=${c.description}
24
+ After=network.target
25
+
26
+ [Service]
27
+ Type=simple
28
+ ExecStart=${execStart}
29
+ WorkingDirectory=${c.workingDir}
30
+ Restart=on-failure
31
+ RestartSec=${c.restartSec}
32
+ ${c.user ? `User=${c.user}` : ""}
33
+ ${c.logPath ? `StandardOutput=append:${c.logPath}\nStandardError=append:${c.logPath}` : "StandardOutput=journal\nStandardError=journal"}
34
+ Environment=NODE_ENV=production
35
+
36
+ [Install]
37
+ WantedBy=multi-user.target
38
+ `.replace(/\n{3,}/g, "\n\n").trim() + "\n";
39
+ }
40
+ /**
41
+ * Generate a launchd plist file for macOS.
42
+ */
43
+ export function generateLaunchdPlist(config = {}) {
44
+ const c = { ...DEFAULT_CONFIG, ...config };
45
+ const logPath = c.logPath ?? join(homedir(), "Library", "Logs", "aoaoe.log");
46
+ const errPath = c.logPath ? c.logPath.replace(".log", ".err.log") : join(homedir(), "Library", "Logs", "aoaoe.err.log");
47
+ const args = c.configPath
48
+ ? [`${c.execPath}`, "--config", c.configPath]
49
+ : [c.execPath];
50
+ return `<?xml version="1.0" encoding="UTF-8"?>
51
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
52
+ <plist version="1.0">
53
+ <dict>
54
+ <key>Label</key>
55
+ <string>com.aoaoe.daemon</string>
56
+ <key>ProgramArguments</key>
57
+ <array>
58
+ ${args.map((a) => ` <string>${escXml(a)}</string>`).join("\n")}
59
+ </array>
60
+ <key>WorkingDirectory</key>
61
+ <string>${escXml(c.workingDir)}</string>
62
+ <key>RunAtLoad</key>
63
+ <true/>
64
+ <key>KeepAlive</key>
65
+ <dict>
66
+ <key>SuccessfulExit</key>
67
+ <false/>
68
+ </dict>
69
+ <key>ThrottleInterval</key>
70
+ <integer>${c.restartSec}</integer>
71
+ <key>StandardOutPath</key>
72
+ <string>${escXml(logPath)}</string>
73
+ <key>StandardErrorPath</key>
74
+ <string>${escXml(errPath)}</string>
75
+ <key>EnvironmentVariables</key>
76
+ <dict>
77
+ <key>NODE_ENV</key>
78
+ <string>production</string>
79
+ </dict>
80
+ </dict>
81
+ </plist>
82
+ `;
83
+ }
84
+ /**
85
+ * Detect the current platform and generate the appropriate service file.
86
+ */
87
+ export function generateServiceFile(config = {}) {
88
+ const os = platform();
89
+ if (os === "darwin") {
90
+ const filename = "com.aoaoe.daemon.plist";
91
+ const installPath = join(homedir(), "Library", "LaunchAgents", filename);
92
+ return { content: generateLaunchdPlist(config), filename, installPath, platform: "launchd" };
93
+ }
94
+ // Linux + fallback
95
+ const filename = `${config.name ?? "aoaoe"}.service`;
96
+ const installPath = join("/etc", "systemd", "system", filename);
97
+ return { content: generateSystemdUnit(config), filename, installPath, platform: "systemd" };
98
+ }
99
+ /**
100
+ * Write the service file and return install instructions.
101
+ */
102
+ export function installService(config = {}) {
103
+ const { content, filename, installPath, platform: plat } = generateServiceFile(config);
104
+ const outDir = join(homedir(), ".aoaoe");
105
+ mkdirSync(outDir, { recursive: true });
106
+ const outPath = join(outDir, filename);
107
+ writeFileSync(outPath, content);
108
+ const lines = [];
109
+ lines.push(`Generated ${plat} service file: ${outPath}`);
110
+ lines.push("");
111
+ if (plat === "systemd") {
112
+ lines.push("Install with:");
113
+ lines.push(` sudo cp ${outPath} ${installPath}`);
114
+ lines.push(" sudo systemctl daemon-reload");
115
+ lines.push(` sudo systemctl enable ${filename}`);
116
+ lines.push(` sudo systemctl start ${filename.replace(".service", "")}`);
117
+ }
118
+ else {
119
+ lines.push("Install with:");
120
+ lines.push(` cp ${outPath} ${installPath}`);
121
+ lines.push(` launchctl load ${installPath}`);
122
+ lines.push("");
123
+ lines.push("Uninstall with:");
124
+ lines.push(` launchctl unload ${installPath}`);
125
+ lines.push(` rm ${installPath}`);
126
+ }
127
+ return lines;
128
+ }
129
+ function escXml(s) {
130
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
131
+ }
132
+ //# sourceMappingURL=service-generator.js.map
@@ -0,0 +1,25 @@
1
+ export interface ReplayEvent {
2
+ timestamp: number;
3
+ timeLabel: string;
4
+ type: string;
5
+ detail: string;
6
+ }
7
+ export interface SessionReplay {
8
+ sessionTitle: string;
9
+ events: ReplayEvent[];
10
+ totalDurationMs: number;
11
+ eventCount: number;
12
+ }
13
+ /**
14
+ * Build a replay timeline for a session from the audit trail.
15
+ */
16
+ export declare function buildSessionReplay(sessionTitle: string, maxEvents?: number): SessionReplay;
17
+ /**
18
+ * Format replay as a chronological narrative.
19
+ */
20
+ export declare function formatReplay(replay: SessionReplay): string[];
21
+ /**
22
+ * Generate a summary of the replay (for quick review).
23
+ */
24
+ export declare function summarizeReplay(replay: SessionReplay): string[];
25
+ //# sourceMappingURL=session-replay.d.ts.map
@@ -0,0 +1,103 @@
1
+ // session-replay.ts — replay a session's activity timeline step-by-step.
2
+ // reconstructs the session's history from audit trail + fleet snapshots
3
+ // and presents it as a chronological narrative for post-mortem analysis.
4
+ import { readRecentAuditEntries } from "./audit-trail.js";
5
+ /**
6
+ * Build a replay timeline for a session from the audit trail.
7
+ */
8
+ export function buildSessionReplay(sessionTitle, maxEvents = 200) {
9
+ const all = readRecentAuditEntries(10_000);
10
+ const sessionEvents = all.filter((e) => e.session === sessionTitle ||
11
+ e.detail.toLowerCase().includes(sessionTitle.toLowerCase()));
12
+ const events = sessionEvents.slice(-maxEvents).map((e) => ({
13
+ timestamp: new Date(e.timestamp).getTime(),
14
+ timeLabel: e.timestamp.slice(11, 19),
15
+ type: e.type,
16
+ detail: e.detail,
17
+ }));
18
+ const totalDuration = events.length >= 2
19
+ ? events[events.length - 1].timestamp - events[0].timestamp
20
+ : 0;
21
+ return {
22
+ sessionTitle,
23
+ events,
24
+ totalDurationMs: totalDuration,
25
+ eventCount: events.length,
26
+ };
27
+ }
28
+ /**
29
+ * Format replay as a chronological narrative.
30
+ */
31
+ export function formatReplay(replay) {
32
+ if (replay.events.length === 0) {
33
+ return [` No replay data for "${replay.sessionTitle}" (check audit trail)`];
34
+ }
35
+ const lines = [];
36
+ const durationLabel = replay.totalDurationMs > 0
37
+ ? formatDuration(replay.totalDurationMs)
38
+ : "instant";
39
+ lines.push(` Replay: "${replay.sessionTitle}" (${replay.eventCount} events, ${durationLabel})`);
40
+ lines.push("");
41
+ let lastTimestamp = 0;
42
+ for (const e of replay.events) {
43
+ // show time gap between events
44
+ if (lastTimestamp > 0) {
45
+ const gap = e.timestamp - lastTimestamp;
46
+ if (gap > 60_000) {
47
+ lines.push(` ... ${formatDuration(gap)} later ...`);
48
+ }
49
+ }
50
+ lastTimestamp = e.timestamp;
51
+ const icon = eventIcon(e.type);
52
+ lines.push(` ${e.timeLabel} ${icon} ${e.type}: ${e.detail}`);
53
+ }
54
+ return lines;
55
+ }
56
+ /**
57
+ * Generate a summary of the replay (for quick review).
58
+ */
59
+ export function summarizeReplay(replay) {
60
+ if (replay.events.length === 0)
61
+ return [` "${replay.sessionTitle}": no events`];
62
+ // count by type
63
+ const typeCounts = new Map();
64
+ for (const e of replay.events) {
65
+ typeCounts.set(e.type, (typeCounts.get(e.type) ?? 0) + 1);
66
+ }
67
+ const lines = [];
68
+ lines.push(` "${replay.sessionTitle}" summary:`);
69
+ lines.push(` Duration: ${formatDuration(replay.totalDurationMs)} Events: ${replay.eventCount}`);
70
+ const sorted = [...typeCounts.entries()].sort((a, b) => b[1] - a[1]);
71
+ for (const [type, count] of sorted) {
72
+ lines.push(` ${eventIcon(type)} ${type}: ${count}`);
73
+ }
74
+ return lines;
75
+ }
76
+ function eventIcon(type) {
77
+ const icons = {
78
+ reasoner_action: "🧠",
79
+ auto_complete: "✅",
80
+ budget_pause: "💰",
81
+ conflict_detected: "⚔",
82
+ operator_command: "👤",
83
+ task_created: "📋",
84
+ task_completed: "🏁",
85
+ session_error: "❌",
86
+ session_restart: "🔄",
87
+ config_change: "⚙",
88
+ stuck_nudge: "👉",
89
+ daemon_start: "🚀",
90
+ daemon_stop: "🛑",
91
+ };
92
+ return icons[type] ?? "·";
93
+ }
94
+ function formatDuration(ms) {
95
+ if (ms < 60_000)
96
+ return `${Math.round(ms / 1000)}s`;
97
+ if (ms < 3_600_000)
98
+ return `${Math.round(ms / 60_000)}m`;
99
+ const h = Math.floor(ms / 3_600_000);
100
+ const m = Math.round((ms % 3_600_000) / 60_000);
101
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
102
+ }
103
+ //# sourceMappingURL=session-replay.js.map
@@ -0,0 +1,48 @@
1
+ export interface WorkflowStage {
2
+ name: string;
3
+ tasks: Array<{
4
+ sessionTitle: string;
5
+ goal: string;
6
+ }>;
7
+ dependsOnStage?: string;
8
+ }
9
+ export interface WorkflowDefinition {
10
+ name: string;
11
+ stages: WorkflowStage[];
12
+ }
13
+ export interface WorkflowState {
14
+ name: string;
15
+ currentStage: number;
16
+ stages: Array<{
17
+ name: string;
18
+ status: "pending" | "active" | "completed" | "failed";
19
+ tasks: Array<{
20
+ sessionTitle: string;
21
+ status: string;
22
+ }>;
23
+ }>;
24
+ startedAt: number;
25
+ completedAt?: number;
26
+ }
27
+ /**
28
+ * Create a workflow state from a definition.
29
+ */
30
+ export declare function createWorkflowState(def: WorkflowDefinition, now?: number): WorkflowState;
31
+ /**
32
+ * Advance the workflow based on current task states.
33
+ * Returns actions to take (activate next stage's tasks, complete workflow, etc.)
34
+ */
35
+ export declare function advanceWorkflow(workflow: WorkflowState, taskStates: ReadonlyMap<string, string>): {
36
+ actions: WorkflowAction[];
37
+ completed: boolean;
38
+ };
39
+ export interface WorkflowAction {
40
+ type: "activate_task" | "stage_started" | "stage_completed" | "stage_failed" | "workflow_completed";
41
+ stage: string;
42
+ detail: string;
43
+ }
44
+ /**
45
+ * Format workflow state for TUI display.
46
+ */
47
+ export declare function formatWorkflow(workflow: WorkflowState): string[];
48
+ //# sourceMappingURL=workflow-engine.d.ts.map
@@ -0,0 +1,91 @@
1
+ // workflow-engine.ts — define multi-session workflows with fan-out/fan-in.
2
+ // a workflow is a DAG of stages, each containing one or more parallel tasks.
3
+ // stages execute sequentially; tasks within a stage execute in parallel.
4
+ /**
5
+ * Create a workflow state from a definition.
6
+ */
7
+ export function createWorkflowState(def, now = Date.now()) {
8
+ return {
9
+ name: def.name,
10
+ currentStage: 0,
11
+ stages: def.stages.map((s) => ({
12
+ name: s.name,
13
+ status: "pending",
14
+ tasks: s.tasks.map((t) => ({ sessionTitle: t.sessionTitle, status: "pending" })),
15
+ })),
16
+ startedAt: now,
17
+ };
18
+ }
19
+ /**
20
+ * Advance the workflow based on current task states.
21
+ * Returns actions to take (activate next stage's tasks, complete workflow, etc.)
22
+ */
23
+ export function advanceWorkflow(workflow, taskStates) {
24
+ const actions = [];
25
+ if (workflow.currentStage >= workflow.stages.length) {
26
+ return { actions: [], completed: true };
27
+ }
28
+ const current = workflow.stages[workflow.currentStage];
29
+ // update task statuses from live data
30
+ for (const task of current.tasks) {
31
+ const liveStatus = taskStates.get(task.sessionTitle);
32
+ if (liveStatus)
33
+ task.status = liveStatus;
34
+ }
35
+ // check if current stage is completed
36
+ const allDone = current.tasks.every((t) => t.status === "completed");
37
+ const anyFailed = current.tasks.some((t) => t.status === "failed");
38
+ if (anyFailed) {
39
+ current.status = "failed";
40
+ actions.push({ type: "stage_failed", stage: current.name, detail: `stage "${current.name}" has failed tasks` });
41
+ return { actions, completed: false };
42
+ }
43
+ if (allDone) {
44
+ current.status = "completed";
45
+ workflow.currentStage++;
46
+ if (workflow.currentStage >= workflow.stages.length) {
47
+ workflow.completedAt = Date.now();
48
+ actions.push({ type: "workflow_completed", stage: workflow.name, detail: `workflow "${workflow.name}" completed` });
49
+ return { actions, completed: true };
50
+ }
51
+ // activate next stage
52
+ const next = workflow.stages[workflow.currentStage];
53
+ next.status = "active";
54
+ for (const task of next.tasks) {
55
+ actions.push({ type: "activate_task", stage: next.name, detail: task.sessionTitle });
56
+ }
57
+ actions.push({ type: "stage_started", stage: next.name, detail: `stage "${next.name}" started (${next.tasks.length} tasks)` });
58
+ }
59
+ else if (current.status === "pending") {
60
+ // activate first stage
61
+ current.status = "active";
62
+ for (const task of current.tasks) {
63
+ actions.push({ type: "activate_task", stage: current.name, detail: task.sessionTitle });
64
+ }
65
+ actions.push({ type: "stage_started", stage: current.name, detail: `stage "${current.name}" started (${current.tasks.length} tasks)` });
66
+ }
67
+ return { actions, completed: false };
68
+ }
69
+ /**
70
+ * Format workflow state for TUI display.
71
+ */
72
+ export function formatWorkflow(workflow) {
73
+ const lines = [];
74
+ const elapsed = workflow.completedAt
75
+ ? workflow.completedAt - workflow.startedAt
76
+ : Date.now() - workflow.startedAt;
77
+ const duration = elapsed < 60_000 ? `${Math.round(elapsed / 1000)}s` : `${Math.round(elapsed / 60_000)}m`;
78
+ lines.push(` Workflow: ${workflow.name} (${duration})`);
79
+ for (let i = 0; i < workflow.stages.length; i++) {
80
+ const s = workflow.stages[i];
81
+ const icon = s.status === "completed" ? "✓" : s.status === "active" ? "▶" : s.status === "failed" ? "✗" : "○";
82
+ const current = i === workflow.currentStage ? " ←" : "";
83
+ lines.push(` ${icon} Stage ${i + 1}: ${s.name} (${s.status})${current}`);
84
+ for (const t of s.tasks) {
85
+ const tIcon = t.status === "completed" ? "✓" : t.status === "active" ? "~" : t.status === "failed" ? "!" : ".";
86
+ lines.push(` [${tIcon}] ${t.sessionTitle}`);
87
+ }
88
+ }
89
+ return lines;
90
+ }
91
+ //# sourceMappingURL=workflow-engine.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aoaoe",
3
- "version": "1.0.0",
3
+ "version": "1.3.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",