aoaoe 1.3.0 → 2.5.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.
Files changed (42) hide show
  1. package/dist/ab-reasoning.d.ts +42 -0
  2. package/dist/ab-reasoning.js +91 -0
  3. package/dist/alert-composer.d.ts +32 -0
  4. package/dist/alert-composer.js +51 -0
  5. package/dist/alert-rule-dsl.d.ts +32 -0
  6. package/dist/alert-rule-dsl.js +87 -0
  7. package/dist/alert-rules.d.ts +42 -0
  8. package/dist/alert-rules.js +94 -0
  9. package/dist/fleet-federation.d.ts +34 -0
  10. package/dist/fleet-federation.js +55 -0
  11. package/dist/fleet-grep.d.ts +22 -0
  12. package/dist/fleet-grep.js +70 -0
  13. package/dist/health-forecast.d.ts +23 -0
  14. package/dist/health-forecast.js +101 -0
  15. package/dist/index.js +271 -1
  16. package/dist/input.d.ts +42 -0
  17. package/dist/input.js +130 -0
  18. package/dist/metrics-export.d.ts +29 -0
  19. package/dist/metrics-export.js +58 -0
  20. package/dist/multi-reasoner.d.ts +36 -0
  21. package/dist/multi-reasoner.js +87 -0
  22. package/dist/output-archival.d.ts +23 -0
  23. package/dist/output-archival.js +72 -0
  24. package/dist/runbook-executor.d.ts +37 -0
  25. package/dist/runbook-executor.js +78 -0
  26. package/dist/runbook-generator.d.ts +21 -0
  27. package/dist/runbook-generator.js +104 -0
  28. package/dist/session-checkpoint.d.ts +55 -0
  29. package/dist/session-checkpoint.js +69 -0
  30. package/dist/session-tail.d.ts +24 -0
  31. package/dist/session-tail.js +52 -0
  32. package/dist/token-quota.d.ts +45 -0
  33. package/dist/token-quota.js +76 -0
  34. package/dist/workflow-chain.d.ts +33 -0
  35. package/dist/workflow-chain.js +69 -0
  36. package/dist/workflow-cost-forecast.d.ts +22 -0
  37. package/dist/workflow-cost-forecast.js +55 -0
  38. package/dist/workflow-templates.d.ts +25 -0
  39. package/dist/workflow-templates.js +92 -0
  40. package/dist/workflow-viz.d.ts +15 -0
  41. package/dist/workflow-viz.js +74 -0
  42. package/package.json +1 -1
@@ -0,0 +1,58 @@
1
+ // metrics-export.ts — Prometheus-compatible /metrics endpoint for daemon observability.
2
+ // exports fleet health, session counts, cost, reasoning stats as text/plain metrics.
3
+ /**
4
+ * Format metrics as Prometheus text exposition format.
5
+ */
6
+ export function formatPrometheusMetrics(m) {
7
+ const lines = [];
8
+ const metric = (name, help, type, value) => {
9
+ lines.push(`# HELP aoaoe_${name} ${help}`);
10
+ lines.push(`# TYPE aoaoe_${name} ${type}`);
11
+ lines.push(`aoaoe_${name} ${value}`);
12
+ };
13
+ metric("fleet_health", "Fleet health score 0-100", "gauge", m.fleetHealth);
14
+ metric("sessions_total", "Total number of sessions", "gauge", m.totalSessions);
15
+ metric("sessions_active", "Number of active sessions", "gauge", m.activeSessions);
16
+ metric("sessions_error", "Number of sessions in error state", "gauge", m.errorSessions);
17
+ metric("tasks_total", "Total number of tasks", "gauge", m.totalTasks);
18
+ metric("tasks_active", "Number of active tasks", "gauge", m.activeTasks);
19
+ metric("tasks_completed_total", "Total completed tasks", "counter", m.completedTasks);
20
+ metric("tasks_failed_total", "Total failed tasks", "counter", m.failedTasks);
21
+ metric("cost_usd_total", "Total cost in USD", "counter", m.totalCostUsd);
22
+ metric("reasoner_calls_total", "Total reasoning calls made", "counter", m.reasonerCallsTotal);
23
+ metric("reasoner_cost_usd_total", "Total reasoner cost in USD", "counter", m.reasonerCostTotal);
24
+ metric("cache_hits_total", "Observation cache hits", "counter", m.cacheHits);
25
+ metric("cache_misses_total", "Observation cache misses", "counter", m.cacheMisses);
26
+ metric("alerts_fired_total", "Total alerts fired", "counter", m.alertsFired);
27
+ metric("nudges_sent_total", "Total nudges sent", "counter", m.nudgesSent);
28
+ metric("nudges_effective_total", "Nudges that led to progress", "counter", m.nudgesEffective);
29
+ metric("poll_interval_ms", "Current adaptive poll interval", "gauge", m.pollIntervalMs);
30
+ metric("uptime_seconds", "Daemon uptime in seconds", "gauge", Math.round(m.uptimeMs / 1000));
31
+ return lines.join("\n") + "\n";
32
+ }
33
+ /**
34
+ * Build a metrics snapshot from daemon state.
35
+ */
36
+ export function buildMetricsSnapshot(state) {
37
+ return {
38
+ fleetHealth: state.fleetHealth ?? 0,
39
+ totalSessions: state.totalSessions ?? 0,
40
+ activeSessions: state.activeSessions ?? 0,
41
+ errorSessions: state.errorSessions ?? 0,
42
+ totalTasks: state.totalTasks ?? 0,
43
+ activeTasks: state.activeTasks ?? 0,
44
+ completedTasks: state.completedTasks ?? 0,
45
+ failedTasks: state.failedTasks ?? 0,
46
+ totalCostUsd: state.totalCostUsd ?? 0,
47
+ reasonerCallsTotal: state.reasonerCallsTotal ?? 0,
48
+ reasonerCostTotal: state.reasonerCostTotal ?? 0,
49
+ cacheHits: state.cacheHits ?? 0,
50
+ cacheMisses: state.cacheMisses ?? 0,
51
+ alertsFired: state.alertsFired ?? 0,
52
+ nudgesSent: state.nudgesSent ?? 0,
53
+ nudgesEffective: state.nudgesEffective ?? 0,
54
+ pollIntervalMs: state.pollIntervalMs ?? 10_000,
55
+ uptimeMs: state.uptimeMs ?? 0,
56
+ };
57
+ }
58
+ //# sourceMappingURL=metrics-export.js.map
@@ -0,0 +1,36 @@
1
+ import type { Observation, ReasonerResult } from "./types.js";
2
+ export type ReasonerBackend = "opencode" | "claude-code" | "gemini" | "custom";
3
+ export interface ReasonerAssignment {
4
+ sessionTitle: string;
5
+ backend: ReasonerBackend;
6
+ reason: string;
7
+ }
8
+ export interface MultiReasonerConfig {
9
+ defaultBackend: ReasonerBackend;
10
+ sessionOverrides: Record<string, ReasonerBackend>;
11
+ templateMappings: Record<string, ReasonerBackend>;
12
+ difficultyThreshold: number;
13
+ premiumBackend: ReasonerBackend;
14
+ economyBackend: ReasonerBackend;
15
+ }
16
+ /**
17
+ * Determine which reasoner backend to use for each session.
18
+ */
19
+ export declare function assignReasonerBackends(sessions: Array<{
20
+ title: string;
21
+ template?: string;
22
+ difficultyScore?: number;
23
+ }>, config?: Partial<MultiReasonerConfig>): ReasonerAssignment[];
24
+ /**
25
+ * Split an observation into per-backend groups for routing.
26
+ */
27
+ export declare function routeObservation(observation: Observation, assignments: ReasonerAssignment[]): Map<ReasonerBackend, Observation>;
28
+ /**
29
+ * Merge results from multiple reasoner backends into a single result.
30
+ */
31
+ export declare function mergeReasonerResults(results: ReasonerResult[]): ReasonerResult;
32
+ /**
33
+ * Format assignments for TUI display.
34
+ */
35
+ export declare function formatAssignments(assignments: ReasonerAssignment[]): string[];
36
+ //# sourceMappingURL=multi-reasoner.d.ts.map
@@ -0,0 +1,87 @@
1
+ // multi-reasoner.ts — assign different LLM backends per session.
2
+ // routes observations to the appropriate reasoner based on session config,
3
+ // template, or difficulty. supports reasoner pools with load balancing.
4
+ const DEFAULT_CONFIG = {
5
+ defaultBackend: "opencode",
6
+ sessionOverrides: {},
7
+ templateMappings: {},
8
+ difficultyThreshold: 7,
9
+ premiumBackend: "opencode",
10
+ economyBackend: "claude-code",
11
+ };
12
+ /**
13
+ * Determine which reasoner backend to use for each session.
14
+ */
15
+ export function assignReasonerBackends(sessions, config = {}) {
16
+ const c = { ...DEFAULT_CONFIG, ...config };
17
+ return sessions.map((s) => {
18
+ // explicit override wins
19
+ if (c.sessionOverrides[s.title]) {
20
+ return { sessionTitle: s.title, backend: c.sessionOverrides[s.title], reason: "explicit override" };
21
+ }
22
+ // template mapping
23
+ if (s.template && c.templateMappings[s.template]) {
24
+ return { sessionTitle: s.title, backend: c.templateMappings[s.template], reason: `template: ${s.template}` };
25
+ }
26
+ // difficulty-based routing
27
+ if (s.difficultyScore !== undefined) {
28
+ if (s.difficultyScore >= c.difficultyThreshold) {
29
+ return { sessionTitle: s.title, backend: c.premiumBackend, reason: `high difficulty (${s.difficultyScore}/10)` };
30
+ }
31
+ return { sessionTitle: s.title, backend: c.economyBackend, reason: `low difficulty (${s.difficultyScore}/10)` };
32
+ }
33
+ return { sessionTitle: s.title, backend: c.defaultBackend, reason: "default" };
34
+ });
35
+ }
36
+ /**
37
+ * Split an observation into per-backend groups for routing.
38
+ */
39
+ export function routeObservation(observation, assignments) {
40
+ const assignMap = new Map(assignments.map((a) => [a.sessionTitle, a.backend]));
41
+ const groups = new Map();
42
+ for (const snap of observation.sessions) {
43
+ const backend = assignMap.get(snap.session.title) ?? "opencode";
44
+ if (!groups.has(backend))
45
+ groups.set(backend, { sessions: [], changes: [] });
46
+ groups.get(backend).sessions.push(snap);
47
+ }
48
+ for (const change of observation.changes) {
49
+ const backend = assignMap.get(change.title) ?? "opencode";
50
+ if (groups.has(backend))
51
+ groups.get(backend).changes.push(change);
52
+ }
53
+ const result = new Map();
54
+ for (const [backend, data] of groups) {
55
+ result.set(backend, { timestamp: observation.timestamp, sessions: data.sessions, changes: data.changes });
56
+ }
57
+ return result;
58
+ }
59
+ /**
60
+ * Merge results from multiple reasoner backends into a single result.
61
+ */
62
+ export function mergeReasonerResults(results) {
63
+ const allActions = results.flatMap((r) => r.actions);
64
+ // use lowest confidence across all results
65
+ const confidences = results.map((r) => r.confidence).filter(Boolean);
66
+ const confidence = confidences.length > 0
67
+ ? (confidences.includes("low") ? "low" : confidences.includes("medium") ? "medium" : "high")
68
+ : undefined;
69
+ return { actions: allActions, confidence: confidence };
70
+ }
71
+ /**
72
+ * Format assignments for TUI display.
73
+ */
74
+ export function formatAssignments(assignments) {
75
+ if (assignments.length === 0)
76
+ return [" (no sessions to assign)"];
77
+ const lines = [];
78
+ const backendCounts = new Map();
79
+ for (const a of assignments) {
80
+ backendCounts.set(a.backend, (backendCounts.get(a.backend) ?? 0) + 1);
81
+ lines.push(` ${a.sessionTitle}: ${a.backend} (${a.reason})`);
82
+ }
83
+ const summary = [...backendCounts.entries()].map(([b, c]) => `${b}: ${c}`).join(", ");
84
+ lines.unshift(` Multi-reasoner assignments (${summary}):`);
85
+ return lines;
86
+ }
87
+ //# sourceMappingURL=multi-reasoner.js.map
@@ -0,0 +1,23 @@
1
+ export interface ArchiveResult {
2
+ sessionTitle: string;
3
+ filepath: string;
4
+ originalLines: number;
5
+ compressedBytes: number;
6
+ archivedAt: number;
7
+ }
8
+ /**
9
+ * Archive session output to a gzipped file on disk.
10
+ */
11
+ export declare function archiveSessionOutput(sessionTitle: string, output: string[], now?: number): ArchiveResult;
12
+ /**
13
+ * List available archives.
14
+ */
15
+ export declare function listArchives(): Array<{
16
+ filename: string;
17
+ sessionTitle: string;
18
+ }>;
19
+ /**
20
+ * Format archive list for TUI display.
21
+ */
22
+ export declare function formatArchiveList(): string[];
23
+ //# sourceMappingURL=output-archival.d.ts.map
@@ -0,0 +1,72 @@
1
+ // output-archival.ts — compress and archive old session outputs to disk.
2
+ // keeps the daemon's memory footprint manageable over long runs by
3
+ // offloading old output to gzipped files.
4
+ import { writeFileSync, mkdirSync, existsSync, readdirSync, unlinkSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
7
+ import { gzipSync } from "node:zlib";
8
+ const ARCHIVE_DIR = join(homedir(), ".aoaoe", "output-archive");
9
+ const MAX_ARCHIVES = 200;
10
+ /**
11
+ * Archive session output to a gzipped file on disk.
12
+ */
13
+ export function archiveSessionOutput(sessionTitle, output, now = Date.now()) {
14
+ mkdirSync(ARCHIVE_DIR, { recursive: true });
15
+ const safe = sessionTitle.replace(/[^a-zA-Z0-9_-]/g, "_");
16
+ const timestamp = new Date(now).toISOString().replace(/[:.]/g, "-").slice(0, 19);
17
+ const filename = `${safe}_${timestamp}.txt.gz`;
18
+ const filepath = join(ARCHIVE_DIR, filename);
19
+ const content = output.join("\n");
20
+ const compressed = gzipSync(Buffer.from(content, "utf-8"));
21
+ writeFileSync(filepath, compressed);
22
+ pruneOldArchives();
23
+ return {
24
+ sessionTitle,
25
+ filepath,
26
+ originalLines: output.length,
27
+ compressedBytes: compressed.length,
28
+ archivedAt: now,
29
+ };
30
+ }
31
+ /**
32
+ * List available archives.
33
+ */
34
+ export function listArchives() {
35
+ if (!existsSync(ARCHIVE_DIR))
36
+ return [];
37
+ return readdirSync(ARCHIVE_DIR)
38
+ .filter((f) => f.endsWith(".txt.gz"))
39
+ .sort()
40
+ .reverse()
41
+ .map((f) => ({
42
+ filename: f,
43
+ sessionTitle: f.split("_").slice(0, -2).join("_") || f,
44
+ }));
45
+ }
46
+ /**
47
+ * Format archive list for TUI display.
48
+ */
49
+ export function formatArchiveList() {
50
+ const archives = listArchives();
51
+ if (archives.length === 0)
52
+ return [" (no archived outputs)"];
53
+ const lines = [];
54
+ lines.push(` Output archives: ${archives.length} files`);
55
+ for (const a of archives.slice(0, 10)) {
56
+ lines.push(` ${a.filename}`);
57
+ }
58
+ if (archives.length > 10)
59
+ lines.push(` ... and ${archives.length - 10} more`);
60
+ return lines;
61
+ }
62
+ function pruneOldArchives() {
63
+ try {
64
+ const files = readdirSync(ARCHIVE_DIR).filter((f) => f.endsWith(".txt.gz")).sort();
65
+ while (files.length > MAX_ARCHIVES) {
66
+ const oldest = files.shift();
67
+ unlinkSync(join(ARCHIVE_DIR, oldest));
68
+ }
69
+ }
70
+ catch { /* best-effort */ }
71
+ }
72
+ //# sourceMappingURL=output-archival.js.map
@@ -0,0 +1,37 @@
1
+ import type { GeneratedRunbook } from "./runbook-generator.js";
2
+ export interface RunbookExecution {
3
+ runbookTitle: string;
4
+ steps: ExecutionStep[];
5
+ status: "pending" | "running" | "completed" | "failed";
6
+ currentStep: number;
7
+ startedAt?: number;
8
+ completedAt?: number;
9
+ }
10
+ export interface ExecutionStep {
11
+ action: string;
12
+ detail: string;
13
+ status: "pending" | "running" | "completed" | "skipped" | "failed";
14
+ result?: string;
15
+ }
16
+ /**
17
+ * Create an execution plan from a generated runbook.
18
+ */
19
+ export declare function createExecution(runbook: GeneratedRunbook): RunbookExecution;
20
+ /**
21
+ * Advance execution to the next step.
22
+ * Returns the step to execute, or null if done.
23
+ */
24
+ export declare function advanceExecution(exec: RunbookExecution, previousStepResult?: string): ExecutionStep | null;
25
+ /**
26
+ * Skip the current step.
27
+ */
28
+ export declare function skipStep(exec: RunbookExecution): void;
29
+ /**
30
+ * Fail the current execution.
31
+ */
32
+ export declare function failExecution(exec: RunbookExecution, reason: string): void;
33
+ /**
34
+ * Format execution state for TUI display.
35
+ */
36
+ export declare function formatExecution(exec: RunbookExecution): string[];
37
+ //# sourceMappingURL=runbook-executor.d.ts.map
@@ -0,0 +1,78 @@
1
+ // runbook-executor.ts — auto-execute generated runbook steps.
2
+ // takes a GeneratedRunbook and produces actionable commands for the daemon
3
+ // to execute, with dry-run support and step-by-step confirmation.
4
+ /**
5
+ * Create an execution plan from a generated runbook.
6
+ */
7
+ export function createExecution(runbook) {
8
+ return {
9
+ runbookTitle: runbook.title,
10
+ steps: runbook.steps.map((s) => ({
11
+ action: s.action,
12
+ detail: s.detail,
13
+ status: "pending",
14
+ })),
15
+ status: "pending",
16
+ currentStep: 0,
17
+ };
18
+ }
19
+ /**
20
+ * Advance execution to the next step.
21
+ * Returns the step to execute, or null if done.
22
+ */
23
+ export function advanceExecution(exec, previousStepResult) {
24
+ if (exec.status === "completed" || exec.status === "failed")
25
+ return null;
26
+ // mark previous step as completed
27
+ if (exec.currentStep > 0 && exec.steps[exec.currentStep - 1].status === "running") {
28
+ exec.steps[exec.currentStep - 1].status = "completed";
29
+ exec.steps[exec.currentStep - 1].result = previousStepResult;
30
+ }
31
+ if (exec.status === "pending") {
32
+ exec.status = "running";
33
+ exec.startedAt = Date.now();
34
+ }
35
+ if (exec.currentStep >= exec.steps.length) {
36
+ exec.status = "completed";
37
+ exec.completedAt = Date.now();
38
+ return null;
39
+ }
40
+ const step = exec.steps[exec.currentStep];
41
+ step.status = "running";
42
+ exec.currentStep++;
43
+ return step;
44
+ }
45
+ /**
46
+ * Skip the current step.
47
+ */
48
+ export function skipStep(exec) {
49
+ if (exec.currentStep > 0 && exec.steps[exec.currentStep - 1].status === "running") {
50
+ exec.steps[exec.currentStep - 1].status = "skipped";
51
+ }
52
+ }
53
+ /**
54
+ * Fail the current execution.
55
+ */
56
+ export function failExecution(exec, reason) {
57
+ exec.status = "failed";
58
+ if (exec.currentStep > 0) {
59
+ exec.steps[exec.currentStep - 1].status = "failed";
60
+ exec.steps[exec.currentStep - 1].result = reason;
61
+ }
62
+ }
63
+ /**
64
+ * Format execution state for TUI display.
65
+ */
66
+ export function formatExecution(exec) {
67
+ const lines = [];
68
+ const duration = exec.startedAt ? (exec.completedAt ?? Date.now()) - exec.startedAt : 0;
69
+ lines.push(` Runbook: ${exec.runbookTitle} (${exec.status}, ${Math.round(duration / 1000)}s)`);
70
+ for (let i = 0; i < exec.steps.length; i++) {
71
+ const s = exec.steps[i];
72
+ const icon = s.status === "completed" ? "✓" : s.status === "running" ? "▶" : s.status === "failed" ? "✗" : s.status === "skipped" ? "⏭" : "○";
73
+ const result = s.result ? ` → ${s.result.slice(0, 50)}` : "";
74
+ lines.push(` ${icon} ${i + 1}. ${s.action}: ${s.detail}${result}`);
75
+ }
76
+ return lines;
77
+ }
78
+ //# sourceMappingURL=runbook-executor.js.map
@@ -0,0 +1,21 @@
1
+ export interface RunbookStep {
2
+ action: string;
3
+ detail: string;
4
+ frequency: number;
5
+ }
6
+ export interface GeneratedRunbook {
7
+ title: string;
8
+ scenario: string;
9
+ steps: RunbookStep[];
10
+ basedOnEvents: number;
11
+ confidence: "low" | "medium" | "high";
12
+ }
13
+ /**
14
+ * Analyze audit trail and generate runbooks for common patterns.
15
+ */
16
+ export declare function generateRunbooks(): GeneratedRunbook[];
17
+ /**
18
+ * Format generated runbooks for TUI display.
19
+ */
20
+ export declare function formatGeneratedRunbooks(runbooks: GeneratedRunbook[]): string[];
21
+ //# sourceMappingURL=runbook-generator.d.ts.map
@@ -0,0 +1,104 @@
1
+ // runbook-generator.ts — auto-generate operator runbooks from audit trail patterns.
2
+ // analyzes recurring event sequences and produces step-by-step playbooks
3
+ // for common scenarios (stuck sessions, budget overruns, error recovery).
4
+ import { readRecentAuditEntries } from "./audit-trail.js";
5
+ /**
6
+ * Analyze audit trail and generate runbooks for common patterns.
7
+ */
8
+ export function generateRunbooks() {
9
+ const entries = readRecentAuditEntries(5_000);
10
+ if (entries.length < 10)
11
+ return [];
12
+ const runbooks = [];
13
+ // pattern 1: stuck session recovery
14
+ const stuckEntries = entries.filter((e) => e.type === "stuck_nudge" || e.type === "session_restart");
15
+ if (stuckEntries.length >= 3) {
16
+ const actions = countActions(stuckEntries);
17
+ runbooks.push({
18
+ title: "Stuck Session Recovery",
19
+ scenario: "Session has not made progress for >30 minutes",
20
+ steps: actions,
21
+ basedOnEvents: stuckEntries.length,
22
+ confidence: stuckEntries.length >= 10 ? "high" : stuckEntries.length >= 5 ? "medium" : "low",
23
+ });
24
+ }
25
+ // pattern 2: budget management
26
+ const budgetEntries = entries.filter((e) => e.type === "budget_pause");
27
+ if (budgetEntries.length >= 2) {
28
+ runbooks.push({
29
+ title: "Budget Overrun Response",
30
+ scenario: "Session cost exceeds configured budget",
31
+ steps: [
32
+ { action: "Review cost attribution", detail: "Use /cost-report to identify top spenders", frequency: budgetEntries.length },
33
+ { action: "Adjust budget or pause", detail: "Use /budget-predict to estimate remaining runway", frequency: budgetEntries.length },
34
+ { action: "Check for runaway loops", detail: "Use /drift to verify session is on-task", frequency: Math.ceil(budgetEntries.length * 0.5) },
35
+ ],
36
+ basedOnEvents: budgetEntries.length,
37
+ confidence: budgetEntries.length >= 5 ? "high" : "medium",
38
+ });
39
+ }
40
+ // pattern 3: error recovery
41
+ const errorEntries = entries.filter((e) => e.type === "session_error");
42
+ if (errorEntries.length >= 3) {
43
+ runbooks.push({
44
+ title: "Session Error Recovery",
45
+ scenario: "Session enters error state",
46
+ steps: [
47
+ { action: "Check session output", detail: "Use /session-replay to review recent activity", frequency: errorEntries.length },
48
+ { action: "Review recovery playbook", detail: "Use /recovery to see auto-recovery status", frequency: errorEntries.length },
49
+ { action: "Restart if needed", detail: "Recovery playbook auto-restarts at health <40", frequency: Math.ceil(errorEntries.length * 0.3) },
50
+ ],
51
+ basedOnEvents: errorEntries.length,
52
+ confidence: errorEntries.length >= 10 ? "high" : "medium",
53
+ });
54
+ }
55
+ // pattern 4: goal completion
56
+ const completionEntries = entries.filter((e) => e.type === "auto_complete" || e.type === "task_completed");
57
+ if (completionEntries.length >= 3) {
58
+ runbooks.push({
59
+ title: "Task Completion Workflow",
60
+ scenario: "Task auto-detected as complete",
61
+ steps: [
62
+ { action: "Verify completion", detail: "Check /goal-progress and /velocity for confirmation", frequency: completionEntries.length },
63
+ { action: "Review output", detail: "Use /session-replay to verify work quality", frequency: completionEntries.length },
64
+ { action: "Advance dependencies", detail: "Use /schedule to activate dependent tasks", frequency: Math.ceil(completionEntries.length * 0.5) },
65
+ ],
66
+ basedOnEvents: completionEntries.length,
67
+ confidence: completionEntries.length >= 10 ? "high" : "medium",
68
+ });
69
+ }
70
+ return runbooks;
71
+ }
72
+ /**
73
+ * Format generated runbooks for TUI display.
74
+ */
75
+ export function formatGeneratedRunbooks(runbooks) {
76
+ if (runbooks.length === 0)
77
+ return [" (insufficient audit data to generate runbooks — need 10+ events)"];
78
+ const lines = [];
79
+ for (const rb of runbooks) {
80
+ const conf = rb.confidence === "high" ? "●" : rb.confidence === "medium" ? "◐" : "○";
81
+ lines.push(` ${conf} ${rb.title} (based on ${rb.basedOnEvents} events)`);
82
+ lines.push(` Scenario: ${rb.scenario}`);
83
+ for (let i = 0; i < rb.steps.length; i++) {
84
+ lines.push(` ${i + 1}. ${rb.steps[i].action} — ${rb.steps[i].detail}`);
85
+ }
86
+ lines.push("");
87
+ }
88
+ return lines;
89
+ }
90
+ function countActions(entries) {
91
+ const counts = new Map();
92
+ for (const e of entries) {
93
+ const key = e.type;
94
+ const existing = counts.get(key);
95
+ if (existing)
96
+ existing.count++;
97
+ else
98
+ counts.set(key, { detail: e.detail.slice(0, 80), count: 1 });
99
+ }
100
+ return [...counts.entries()].map(([action, { detail, count }]) => ({
101
+ action, detail, frequency: count,
102
+ })).sort((a, b) => b.frequency - a.frequency);
103
+ }
104
+ //# sourceMappingURL=runbook-generator.js.map
@@ -0,0 +1,55 @@
1
+ export interface DaemonCheckpoint {
2
+ version: number;
3
+ savedAt: number;
4
+ graduation: Record<string, {
5
+ mode: string;
6
+ successes: number;
7
+ failures: number;
8
+ rate: number;
9
+ }>;
10
+ escalation: Record<string, {
11
+ level: string;
12
+ notifyCount: number;
13
+ }>;
14
+ velocitySamples: Record<string, Array<{
15
+ timestamp: number;
16
+ percent: number;
17
+ }>>;
18
+ nudgeRecords: Array<{
19
+ session: string;
20
+ sentAt: number;
21
+ effective: boolean;
22
+ }>;
23
+ budgetSamples: Record<string, Array<{
24
+ timestamp: number;
25
+ costUsd: number;
26
+ }>>;
27
+ cacheStats: {
28
+ hits: number;
29
+ misses: number;
30
+ };
31
+ slaHistory: number[];
32
+ pollInterval: number;
33
+ }
34
+ /**
35
+ * Save daemon state to a checkpoint file.
36
+ */
37
+ export declare function saveCheckpoint(checkpoint: DaemonCheckpoint): string;
38
+ /**
39
+ * Load the most recent checkpoint. Returns null if none exists.
40
+ */
41
+ export declare function loadCheckpoint(): DaemonCheckpoint | null;
42
+ /**
43
+ * Build a checkpoint from current module states.
44
+ * This is a generic serialization — callers extract state from each module.
45
+ */
46
+ export declare function buildCheckpoint(state: Omit<DaemonCheckpoint, "version" | "savedAt">): DaemonCheckpoint;
47
+ /**
48
+ * Format checkpoint info for TUI display.
49
+ */
50
+ export declare function formatCheckpointInfo(): string[];
51
+ /**
52
+ * Check if a checkpoint exists and is recent enough to restore.
53
+ */
54
+ export declare function shouldRestoreCheckpoint(maxAgeMs?: number): boolean;
55
+ //# sourceMappingURL=session-checkpoint.d.ts.map
@@ -0,0 +1,69 @@
1
+ // session-checkpoint.ts — save + resume session state across daemon restarts.
2
+ // serializes all transient module state (graduation, escalation, velocity,
3
+ // nudge tracker, etc.) to disk so the daemon can pick up where it left off.
4
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
7
+ const CHECKPOINT_DIR = join(homedir(), ".aoaoe", "checkpoints");
8
+ const CHECKPOINT_FILE = join(CHECKPOINT_DIR, "daemon-state.json");
9
+ /**
10
+ * Save daemon state to a checkpoint file.
11
+ */
12
+ export function saveCheckpoint(checkpoint) {
13
+ mkdirSync(CHECKPOINT_DIR, { recursive: true });
14
+ checkpoint.savedAt = Date.now();
15
+ checkpoint.version = 1;
16
+ writeFileSync(CHECKPOINT_FILE, JSON.stringify(checkpoint, null, 2) + "\n");
17
+ return CHECKPOINT_FILE;
18
+ }
19
+ /**
20
+ * Load the most recent checkpoint. Returns null if none exists.
21
+ */
22
+ export function loadCheckpoint() {
23
+ if (!existsSync(CHECKPOINT_FILE))
24
+ return null;
25
+ try {
26
+ const data = JSON.parse(readFileSync(CHECKPOINT_FILE, "utf-8"));
27
+ if (data.version !== 1)
28
+ return null; // incompatible version
29
+ return data;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ /**
36
+ * Build a checkpoint from current module states.
37
+ * This is a generic serialization — callers extract state from each module.
38
+ */
39
+ export function buildCheckpoint(state) {
40
+ return { version: 1, savedAt: Date.now(), ...state };
41
+ }
42
+ /**
43
+ * Format checkpoint info for TUI display.
44
+ */
45
+ export function formatCheckpointInfo() {
46
+ const cp = loadCheckpoint();
47
+ if (!cp)
48
+ return [" (no checkpoint found)"];
49
+ const age = Math.round((Date.now() - cp.savedAt) / 60_000);
50
+ const lines = [];
51
+ lines.push(` Checkpoint: saved ${age}min ago`);
52
+ lines.push(` Graduation: ${Object.keys(cp.graduation).length} sessions`);
53
+ lines.push(` Escalation: ${Object.keys(cp.escalation).length} active`);
54
+ lines.push(` Velocity: ${Object.keys(cp.velocitySamples).length} tracked`);
55
+ lines.push(` Nudges: ${cp.nudgeRecords.length} records`);
56
+ lines.push(` Cache: ${cp.cacheStats.hits} hits / ${cp.cacheStats.misses} misses`);
57
+ lines.push(` SLA history: ${cp.slaHistory.length} ticks`);
58
+ return lines;
59
+ }
60
+ /**
61
+ * Check if a checkpoint exists and is recent enough to restore.
62
+ */
63
+ export function shouldRestoreCheckpoint(maxAgeMs = 30 * 60_000) {
64
+ const cp = loadCheckpoint();
65
+ if (!cp)
66
+ return false;
67
+ return (Date.now() - cp.savedAt) <= maxAgeMs;
68
+ }
69
+ //# sourceMappingURL=session-checkpoint.js.map
@@ -0,0 +1,24 @@
1
+ export interface TailOptions {
2
+ sessionTitle: string;
3
+ lineCount: number;
4
+ highlightPattern?: string;
5
+ stripAnsi: boolean;
6
+ }
7
+ /**
8
+ * Extract the last N lines from session output, optionally highlighting matches.
9
+ */
10
+ export declare function tailSession(output: string[], options: Partial<TailOptions> & {
11
+ sessionTitle: string;
12
+ }): string[];
13
+ /**
14
+ * Format tail output for TUI display.
15
+ */
16
+ export declare function formatTail(sessionTitle: string, lines: string[], total: number): string[];
17
+ /**
18
+ * Parse tail command arguments.
19
+ * Format: /tail <session> [count] [pattern]
20
+ */
21
+ export declare function parseTailArgs(args: string): Partial<TailOptions> & {
22
+ sessionTitle: string;
23
+ };
24
+ //# sourceMappingURL=session-tail.d.ts.map