aoaoe 0.195.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.
Files changed (113) hide show
  1. package/README.md +23 -1
  2. package/dist/activity-heatmap.d.ts +42 -0
  3. package/dist/activity-heatmap.js +112 -0
  4. package/dist/adaptive-poll.d.ts +31 -0
  5. package/dist/adaptive-poll.js +64 -0
  6. package/dist/anomaly-detector.d.ts +38 -0
  7. package/dist/anomaly-detector.js +77 -0
  8. package/dist/approval-queue.d.ts +47 -0
  9. package/dist/approval-queue.js +113 -0
  10. package/dist/approval-workflow.d.ts +25 -0
  11. package/dist/approval-workflow.js +78 -0
  12. package/dist/audit-search.d.ts +24 -0
  13. package/dist/audit-search.js +118 -0
  14. package/dist/audit-trail.d.ts +21 -0
  15. package/dist/audit-trail.js +99 -0
  16. package/dist/budget-predictor.d.ts +35 -0
  17. package/dist/budget-predictor.js +142 -0
  18. package/dist/cli-completions.d.ts +24 -0
  19. package/dist/cli-completions.js +114 -0
  20. package/dist/config.d.ts +3 -0
  21. package/dist/config.js +37 -2
  22. package/dist/conflict-detector.d.ts +47 -0
  23. package/dist/conflict-detector.js +152 -0
  24. package/dist/context-compressor.d.ts +30 -0
  25. package/dist/context-compressor.js +95 -0
  26. package/dist/cost-attribution.d.ts +39 -0
  27. package/dist/cost-attribution.js +93 -0
  28. package/dist/cost-budget.d.ts +47 -0
  29. package/dist/cost-budget.js +94 -0
  30. package/dist/dashboard.js +10 -5
  31. package/dist/dep-graph-viz.d.ts +24 -0
  32. package/dist/dep-graph-viz.js +125 -0
  33. package/dist/dep-scheduler.d.ts +20 -0
  34. package/dist/dep-scheduler.js +72 -0
  35. package/dist/difficulty-allocator.d.ts +19 -0
  36. package/dist/difficulty-allocator.js +53 -0
  37. package/dist/difficulty-scorer.d.ts +16 -0
  38. package/dist/difficulty-scorer.js +82 -0
  39. package/dist/drift-detector.d.ts +29 -0
  40. package/dist/drift-detector.js +75 -0
  41. package/dist/fleet-diff.d.ts +25 -0
  42. package/dist/fleet-diff.js +69 -0
  43. package/dist/fleet-export.d.ts +20 -0
  44. package/dist/fleet-export.js +124 -0
  45. package/dist/fleet-forecast.d.ts +25 -0
  46. package/dist/fleet-forecast.js +68 -0
  47. package/dist/fleet-rate-limiter.d.ts +35 -0
  48. package/dist/fleet-rate-limiter.js +94 -0
  49. package/dist/fleet-search.d.ts +28 -0
  50. package/dist/fleet-search.js +85 -0
  51. package/dist/fleet-sla.d.ts +34 -0
  52. package/dist/fleet-sla.js +70 -0
  53. package/dist/fleet-snapshot.d.ts +46 -0
  54. package/dist/fleet-snapshot.js +164 -0
  55. package/dist/fleet-utilization.d.ts +29 -0
  56. package/dist/fleet-utilization.js +78 -0
  57. package/dist/goal-decomposer.d.ts +26 -0
  58. package/dist/goal-decomposer.js +79 -0
  59. package/dist/goal-detector.d.ts +27 -0
  60. package/dist/goal-detector.js +104 -0
  61. package/dist/goal-progress.d.ts +18 -0
  62. package/dist/goal-progress.js +103 -0
  63. package/dist/goal-refiner.d.ts +27 -0
  64. package/dist/goal-refiner.js +91 -0
  65. package/dist/index.js +1048 -11
  66. package/dist/input.d.ts +141 -0
  67. package/dist/input.js +441 -0
  68. package/dist/lifecycle-analytics.d.ts +42 -0
  69. package/dist/lifecycle-analytics.js +93 -0
  70. package/dist/notify-escalation.d.ts +42 -0
  71. package/dist/notify-escalation.js +92 -0
  72. package/dist/nudge-tracker.d.ts +34 -0
  73. package/dist/nudge-tracker.js +72 -0
  74. package/dist/observation-cache.d.ts +42 -0
  75. package/dist/observation-cache.js +85 -0
  76. package/dist/poller.d.ts +7 -0
  77. package/dist/poller.js +30 -0
  78. package/dist/priority-reasoning.d.ts +32 -0
  79. package/dist/priority-reasoning.js +73 -0
  80. package/dist/progress-velocity.d.ts +32 -0
  81. package/dist/progress-velocity.js +110 -0
  82. package/dist/reasoner/opencode.d.ts +1 -0
  83. package/dist/reasoner/opencode.js +72 -10
  84. package/dist/reasoner-cost.d.ts +50 -0
  85. package/dist/reasoner-cost.js +101 -0
  86. package/dist/recovery-playbook.d.ts +44 -0
  87. package/dist/recovery-playbook.js +78 -0
  88. package/dist/service-generator.d.ts +32 -0
  89. package/dist/service-generator.js +132 -0
  90. package/dist/session-graduation.d.ts +44 -0
  91. package/dist/session-graduation.js +98 -0
  92. package/dist/session-memory.d.ts +37 -0
  93. package/dist/session-memory.js +105 -0
  94. package/dist/session-pool.d.ts +34 -0
  95. package/dist/session-pool.js +65 -0
  96. package/dist/session-priority.d.ts +34 -0
  97. package/dist/session-priority.js +94 -0
  98. package/dist/session-replay.d.ts +25 -0
  99. package/dist/session-replay.js +103 -0
  100. package/dist/session-summarizer.d.ts +28 -0
  101. package/dist/session-summarizer.js +119 -0
  102. package/dist/session-templates.d.ts +34 -0
  103. package/dist/session-templates.js +142 -0
  104. package/dist/smart-nudge.d.ts +25 -0
  105. package/dist/smart-nudge.js +77 -0
  106. package/dist/task-retry.d.ts +45 -0
  107. package/dist/task-retry.js +110 -0
  108. package/dist/template-detector.d.ts +23 -0
  109. package/dist/template-detector.js +75 -0
  110. package/dist/types.d.ts +5 -0
  111. package/dist/workflow-engine.d.ts +48 -0
  112. package/dist/workflow-engine.js +91 -0
  113. package/package.json +1 -1
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,42 @@
1
+ export interface ActivityBucket {
2
+ startMs: number;
3
+ count: number;
4
+ }
5
+ export interface SessionHeatmap {
6
+ sessionTitle: string;
7
+ buckets: ActivityBucket[];
8
+ sparkline: string;
9
+ totalEvents: number;
10
+ peakRate: number;
11
+ }
12
+ /**
13
+ * Render a sparkline string from an array of values.
14
+ * Values are normalized to 0-8 range based on the max value.
15
+ */
16
+ export declare function renderSparkline(values: number[]): string;
17
+ /**
18
+ * Track activity events per session in time buckets.
19
+ * Each tick, call `recordEvent()` for sessions with new output.
20
+ * Call `getHeatmap()` to render the sparkline for display.
21
+ */
22
+ export declare class ActivityTracker {
23
+ private events;
24
+ private bucketMs;
25
+ private windowMs;
26
+ private bucketCount;
27
+ constructor(bucketMs?: number, bucketCount?: number);
28
+ /** Record an activity event for a session. */
29
+ recordEvent(sessionTitle: string, now?: number): void;
30
+ /** Get the heatmap for a single session. */
31
+ getHeatmap(sessionTitle: string, now?: number): SessionHeatmap;
32
+ /** Get heatmaps for all tracked sessions. */
33
+ getAllHeatmaps(now?: number): SessionHeatmap[];
34
+ /** Format a heatmap for TUI display. */
35
+ static format(heatmap: SessionHeatmap): string;
36
+ /** Format all heatmaps for TUI display. */
37
+ formatAll(now?: number): string[];
38
+ /** Get the number of tracked sessions (for testing). */
39
+ get sessionCount(): number;
40
+ private prune;
41
+ }
42
+ //# sourceMappingURL=activity-heatmap.d.ts.map
@@ -0,0 +1,112 @@
1
+ // activity-heatmap.ts — per-session activity sparkline for TUI display.
2
+ // tracks change events per session in fixed-width time buckets and renders
3
+ // as a compact sparkline using Unicode block characters.
4
+ // sparkline characters: ▁▂▃▄▅▆▇█ (8 levels)
5
+ const SPARK_CHARS = " ▁▂▃▄▅▆▇";
6
+ /**
7
+ * Render a sparkline string from an array of values.
8
+ * Values are normalized to 0-8 range based on the max value.
9
+ */
10
+ export function renderSparkline(values) {
11
+ if (values.length === 0)
12
+ return "";
13
+ const max = Math.max(...values);
14
+ if (max === 0)
15
+ return " ".repeat(values.length);
16
+ return values.map((v) => {
17
+ const idx = Math.round((v / max) * (SPARK_CHARS.length - 1));
18
+ return SPARK_CHARS[idx];
19
+ }).join("");
20
+ }
21
+ /**
22
+ * Track activity events per session in time buckets.
23
+ * Each tick, call `recordEvent()` for sessions with new output.
24
+ * Call `getHeatmap()` to render the sparkline for display.
25
+ */
26
+ export class ActivityTracker {
27
+ events = new Map(); // session -> timestamps
28
+ bucketMs;
29
+ windowMs;
30
+ bucketCount;
31
+ constructor(bucketMs = 60_000, bucketCount = 30) {
32
+ this.bucketMs = bucketMs;
33
+ this.bucketCount = bucketCount;
34
+ this.windowMs = bucketMs * bucketCount;
35
+ }
36
+ /** Record an activity event for a session. */
37
+ recordEvent(sessionTitle, now = Date.now()) {
38
+ if (!this.events.has(sessionTitle))
39
+ this.events.set(sessionTitle, []);
40
+ this.events.get(sessionTitle).push(now);
41
+ this.prune(sessionTitle, now);
42
+ }
43
+ /** Get the heatmap for a single session. */
44
+ getHeatmap(sessionTitle, now = Date.now()) {
45
+ const timestamps = this.events.get(sessionTitle) ?? [];
46
+ const windowStart = now - this.windowMs;
47
+ // build buckets
48
+ const buckets = [];
49
+ for (let i = 0; i < this.bucketCount; i++) {
50
+ const startMs = windowStart + i * this.bucketMs;
51
+ const endMs = startMs + this.bucketMs;
52
+ // last bucket uses <= to include events at exactly `now`
53
+ const count = timestamps.filter((t) => t >= startMs && (i === this.bucketCount - 1 ? t <= endMs : t < endMs)).length;
54
+ buckets.push({ startMs, count });
55
+ }
56
+ const counts = buckets.map((b) => b.count);
57
+ const totalEvents = counts.reduce((a, b) => a + b, 0);
58
+ const peakRate = Math.max(...counts);
59
+ return {
60
+ sessionTitle,
61
+ buckets,
62
+ sparkline: renderSparkline(counts),
63
+ totalEvents,
64
+ peakRate,
65
+ };
66
+ }
67
+ /** Get heatmaps for all tracked sessions. */
68
+ getAllHeatmaps(now = Date.now()) {
69
+ const result = [];
70
+ for (const title of this.events.keys()) {
71
+ result.push(this.getHeatmap(title, now));
72
+ }
73
+ return result;
74
+ }
75
+ /** Format a heatmap for TUI display. */
76
+ static format(heatmap) {
77
+ const title = heatmap.sessionTitle.length > 18
78
+ ? heatmap.sessionTitle.slice(0, 15) + "..."
79
+ : heatmap.sessionTitle.padEnd(18);
80
+ const events = `${heatmap.totalEvents}ev`.padStart(5);
81
+ const peak = heatmap.peakRate > 0 ? ` peak:${heatmap.peakRate}/min` : "";
82
+ return ` ${title} ${heatmap.sparkline} ${events}${peak}`;
83
+ }
84
+ /** Format all heatmaps for TUI display. */
85
+ formatAll(now = Date.now()) {
86
+ const heatmaps = this.getAllHeatmaps(now);
87
+ if (heatmaps.length === 0)
88
+ return [" (no activity data yet)"];
89
+ const lines = [];
90
+ lines.push(` Activity heatmap (last ${this.bucketCount}min, 1min buckets):`);
91
+ for (const h of heatmaps) {
92
+ lines.push(ActivityTracker.format(h));
93
+ }
94
+ return lines;
95
+ }
96
+ /** Get the number of tracked sessions (for testing). */
97
+ get sessionCount() {
98
+ return this.events.size;
99
+ }
100
+ prune(sessionTitle, now) {
101
+ const cutoff = now - this.windowMs;
102
+ const timestamps = this.events.get(sessionTitle);
103
+ if (timestamps) {
104
+ const pruned = timestamps.filter((t) => t >= cutoff);
105
+ if (pruned.length === 0)
106
+ this.events.delete(sessionTitle);
107
+ else
108
+ this.events.set(sessionTitle, pruned);
109
+ }
110
+ }
111
+ }
112
+ //# sourceMappingURL=activity-heatmap.js.map
@@ -0,0 +1,31 @@
1
+ export interface AdaptivePollConfig {
2
+ minIntervalMs: number;
3
+ maxIntervalMs: number;
4
+ baseIntervalMs: number;
5
+ rampUpFactor: number;
6
+ rampDownFactor: number;
7
+ }
8
+ /**
9
+ * Track session activity and compute adaptive poll intervals.
10
+ * Call `recordTick()` after each daemonTick with the number of changes observed.
11
+ */
12
+ export declare class AdaptivePollController {
13
+ private config;
14
+ private currentIntervalMs;
15
+ private consecutiveIdleTicks;
16
+ private consecutiveActiveTicks;
17
+ constructor(config?: Partial<AdaptivePollConfig>);
18
+ /**
19
+ * Record the result of a tick and update the interval.
20
+ * @param changeCount - number of session changes in this tick
21
+ * @param hadReasonerAction - whether the reasoner took an action
22
+ */
23
+ recordTick(changeCount: number, hadReasonerAction: boolean): void;
24
+ /** Get the current adaptive poll interval. */
25
+ get intervalMs(): number;
26
+ /** Reset to base interval (e.g., on operator input). */
27
+ reset(): void;
28
+ /** Get a human-readable status string. */
29
+ formatStatus(): string;
30
+ }
31
+ //# sourceMappingURL=adaptive-poll.d.ts.map
@@ -0,0 +1,64 @@
1
+ // adaptive-poll.ts — dynamic poll interval: speed up when sessions are active,
2
+ // slow down when everything is idle. avoids wasting CPU/API on sleeping sessions
3
+ // while staying responsive when work is happening.
4
+ const DEFAULT_CONFIG = {
5
+ minIntervalMs: 5_000,
6
+ maxIntervalMs: 60_000,
7
+ baseIntervalMs: 10_000,
8
+ rampUpFactor: 1.5,
9
+ rampDownFactor: 2,
10
+ };
11
+ /**
12
+ * Track session activity and compute adaptive poll intervals.
13
+ * Call `recordTick()` after each daemonTick with the number of changes observed.
14
+ */
15
+ export class AdaptivePollController {
16
+ config;
17
+ currentIntervalMs;
18
+ consecutiveIdleTicks = 0;
19
+ consecutiveActiveTicks = 0;
20
+ constructor(config = {}) {
21
+ this.config = { ...DEFAULT_CONFIG, ...config };
22
+ this.currentIntervalMs = this.config.baseIntervalMs;
23
+ }
24
+ /**
25
+ * Record the result of a tick and update the interval.
26
+ * @param changeCount - number of session changes in this tick
27
+ * @param hadReasonerAction - whether the reasoner took an action
28
+ */
29
+ recordTick(changeCount, hadReasonerAction) {
30
+ const active = changeCount > 0 || hadReasonerAction;
31
+ if (active) {
32
+ this.consecutiveActiveTicks++;
33
+ this.consecutiveIdleTicks = 0;
34
+ // ramp down (speed up) when active
35
+ if (this.consecutiveActiveTicks >= 2) {
36
+ this.currentIntervalMs = Math.max(this.config.minIntervalMs, Math.round(this.currentIntervalMs / this.config.rampDownFactor));
37
+ }
38
+ }
39
+ else {
40
+ this.consecutiveIdleTicks++;
41
+ this.consecutiveActiveTicks = 0;
42
+ // ramp up (slow down) when idle — but only after 3+ idle ticks
43
+ if (this.consecutiveIdleTicks >= 3) {
44
+ this.currentIntervalMs = Math.min(this.config.maxIntervalMs, Math.round(this.currentIntervalMs * this.config.rampUpFactor));
45
+ }
46
+ }
47
+ }
48
+ /** Get the current adaptive poll interval. */
49
+ get intervalMs() {
50
+ return this.currentIntervalMs;
51
+ }
52
+ /** Reset to base interval (e.g., on operator input). */
53
+ reset() {
54
+ this.currentIntervalMs = this.config.baseIntervalMs;
55
+ this.consecutiveIdleTicks = 0;
56
+ this.consecutiveActiveTicks = 0;
57
+ }
58
+ /** Get a human-readable status string. */
59
+ formatStatus() {
60
+ const mode = this.consecutiveActiveTicks >= 2 ? "fast" : this.consecutiveIdleTicks >= 3 ? "slow" : "normal";
61
+ return `poll: ${(this.currentIntervalMs / 1000).toFixed(1)}s (${mode}, idle=${this.consecutiveIdleTicks}, active=${this.consecutiveActiveTicks})`;
62
+ }
63
+ }
64
+ //# sourceMappingURL=adaptive-poll.js.map
@@ -0,0 +1,38 @@
1
+ export interface AnomalySignal {
2
+ sessionTitle: string;
3
+ metric: "cost_rate" | "activity_rate" | "error_rate" | "idle_duration";
4
+ value: number;
5
+ fleetMean: number;
6
+ fleetStdDev: number;
7
+ zScore: number;
8
+ anomalous: boolean;
9
+ detail: string;
10
+ }
11
+ /**
12
+ * Compute mean and standard deviation for an array of numbers.
13
+ */
14
+ export declare function computeStats(values: number[]): {
15
+ mean: number;
16
+ stdDev: number;
17
+ };
18
+ /**
19
+ * Compute z-score: how many standard deviations from the mean.
20
+ */
21
+ export declare function zScore(value: number, mean: number, stdDev: number): number;
22
+ export interface SessionMetrics {
23
+ sessionTitle: string;
24
+ costRatePerHour: number;
25
+ activityEventsPerHour: number;
26
+ errorCount: number;
27
+ idleDurationMs: number;
28
+ }
29
+ /**
30
+ * Detect anomalies across fleet sessions.
31
+ * A session is anomalous if any metric has |z-score| > threshold.
32
+ */
33
+ export declare function detectAnomalies(sessions: SessionMetrics[], threshold?: number): AnomalySignal[];
34
+ /**
35
+ * Format anomaly signals for TUI display.
36
+ */
37
+ export declare function formatAnomalies(signals: AnomalySignal[]): string[];
38
+ //# sourceMappingURL=anomaly-detector.d.ts.map
@@ -0,0 +1,77 @@
1
+ // anomaly-detector.ts — flag sessions with unusual cost or activity patterns.
2
+ // uses z-score against fleet-wide statistics to identify outliers.
3
+ /**
4
+ * Compute mean and standard deviation for an array of numbers.
5
+ */
6
+ export function computeStats(values) {
7
+ if (values.length === 0)
8
+ return { mean: 0, stdDev: 0 };
9
+ const mean = values.reduce((a, b) => a + b, 0) / values.length;
10
+ if (values.length === 1)
11
+ return { mean, stdDev: 0 };
12
+ const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;
13
+ return { mean, stdDev: Math.sqrt(variance) };
14
+ }
15
+ /**
16
+ * Compute z-score: how many standard deviations from the mean.
17
+ */
18
+ export function zScore(value, mean, stdDev) {
19
+ if (stdDev === 0)
20
+ return 0;
21
+ return (value - mean) / stdDev;
22
+ }
23
+ /**
24
+ * Detect anomalies across fleet sessions.
25
+ * A session is anomalous if any metric has |z-score| > threshold.
26
+ */
27
+ export function detectAnomalies(sessions, threshold = 2.0) {
28
+ if (sessions.length < 3)
29
+ return []; // need 3+ for meaningful statistics
30
+ const signals = [];
31
+ // check each metric across fleet
32
+ const metrics = [
33
+ { key: "costRatePerHour", metric: "cost_rate", highIsAnomaly: true },
34
+ { key: "activityEventsPerHour", metric: "activity_rate", highIsAnomaly: false }, // both high and low
35
+ { key: "errorCount", metric: "error_rate", highIsAnomaly: true },
36
+ { key: "idleDurationMs", metric: "idle_duration", highIsAnomaly: true },
37
+ ];
38
+ for (const { key, metric, highIsAnomaly } of metrics) {
39
+ const values = sessions.map((s) => s[key]);
40
+ const { mean, stdDev } = computeStats(values);
41
+ if (stdDev === 0)
42
+ continue; // all same value, no anomaly possible
43
+ for (const session of sessions) {
44
+ const value = session[key];
45
+ const z = zScore(value, mean, stdDev);
46
+ const anomalous = highIsAnomaly ? z > threshold : Math.abs(z) > threshold;
47
+ if (anomalous) {
48
+ const direction = z > 0 ? "above" : "below";
49
+ signals.push({
50
+ sessionTitle: session.sessionTitle,
51
+ metric,
52
+ value,
53
+ fleetMean: mean,
54
+ fleetStdDev: stdDev,
55
+ zScore: z,
56
+ anomalous: true,
57
+ detail: `${metric}: ${value.toFixed(2)} is ${Math.abs(z).toFixed(1)}σ ${direction} fleet mean (${mean.toFixed(2)})`,
58
+ });
59
+ }
60
+ }
61
+ }
62
+ return signals;
63
+ }
64
+ /**
65
+ * Format anomaly signals for TUI display.
66
+ */
67
+ export function formatAnomalies(signals) {
68
+ if (signals.length === 0)
69
+ return [" ✅ no anomalies detected"];
70
+ const lines = [];
71
+ lines.push(` ⚠ ${signals.length} anomal${signals.length !== 1 ? "ies" : "y"} detected:`);
72
+ for (const s of signals) {
73
+ lines.push(` ${s.sessionTitle}: ${s.detail}`);
74
+ }
75
+ return lines;
76
+ }
77
+ //# sourceMappingURL=anomaly-detector.js.map
@@ -0,0 +1,47 @@
1
+ export interface PendingApproval {
2
+ id: string;
3
+ timestamp: number;
4
+ sessionTitle: string;
5
+ actionType: string;
6
+ detail: string;
7
+ confidence: "high" | "medium" | "low";
8
+ status: "pending" | "approved" | "rejected" | "expired";
9
+ }
10
+ export interface ApprovalQueueConfig {
11
+ maxPending: number;
12
+ expiryMs: number;
13
+ }
14
+ /**
15
+ * Manage an operator approval queue for daemon decisions.
16
+ */
17
+ export declare class ApprovalQueue {
18
+ private items;
19
+ private config;
20
+ constructor(config?: Partial<ApprovalQueueConfig>);
21
+ /** Queue a decision for approval. Returns the approval ID. */
22
+ enqueue(sessionTitle: string, actionType: string, detail: string, confidence?: PendingApproval["confidence"], now?: number): string;
23
+ /** Approve a queued decision by ID. */
24
+ approve(id: string): boolean;
25
+ /** Reject a queued decision by ID. */
26
+ reject(id: string): boolean;
27
+ /** Approve all pending items. Returns count approved. */
28
+ approveAll(): number;
29
+ /** Reject all pending items. Returns count rejected. */
30
+ rejectAll(): number;
31
+ /** Get all pending items. */
32
+ getPending(now?: number): PendingApproval[];
33
+ /** Get approved items that haven't been consumed yet. */
34
+ consumeApproved(): PendingApproval[];
35
+ /** Get queue statistics. */
36
+ getStats(): {
37
+ pending: number;
38
+ approved: number;
39
+ rejected: number;
40
+ expired: number;
41
+ total: number;
42
+ };
43
+ /** Format queue for TUI display. */
44
+ formatQueue(now?: number): string[];
45
+ private expire;
46
+ }
47
+ //# sourceMappingURL=approval-queue.d.ts.map
@@ -0,0 +1,113 @@
1
+ // approval-queue.ts — batch pending decisions for async human review.
2
+ // when the daemon is in confirm mode or has low-confidence decisions,
3
+ // queues them for the operator to approve/reject in bulk.
4
+ const DEFAULT_CONFIG = {
5
+ maxPending: 50,
6
+ expiryMs: 30 * 60_000,
7
+ };
8
+ let nextId = 1;
9
+ /**
10
+ * Manage an operator approval queue for daemon decisions.
11
+ */
12
+ export class ApprovalQueue {
13
+ items = [];
14
+ config;
15
+ constructor(config = {}) {
16
+ this.config = { ...DEFAULT_CONFIG, ...config };
17
+ }
18
+ /** Queue a decision for approval. Returns the approval ID. */
19
+ enqueue(sessionTitle, actionType, detail, confidence = "medium", now = Date.now()) {
20
+ this.expire(now);
21
+ const id = `approval-${nextId++}`;
22
+ this.items.push({ id, timestamp: now, sessionTitle, actionType, detail, confidence, status: "pending" });
23
+ // enforce max pending
24
+ const pending = this.items.filter((i) => i.status === "pending");
25
+ if (pending.length > this.config.maxPending) {
26
+ pending[0].status = "expired";
27
+ }
28
+ return id;
29
+ }
30
+ /** Approve a queued decision by ID. */
31
+ approve(id) {
32
+ const item = this.items.find((i) => i.id === id && i.status === "pending");
33
+ if (!item)
34
+ return false;
35
+ item.status = "approved";
36
+ return true;
37
+ }
38
+ /** Reject a queued decision by ID. */
39
+ reject(id) {
40
+ const item = this.items.find((i) => i.id === id && i.status === "pending");
41
+ if (!item)
42
+ return false;
43
+ item.status = "rejected";
44
+ return true;
45
+ }
46
+ /** Approve all pending items. Returns count approved. */
47
+ approveAll() {
48
+ let count = 0;
49
+ for (const item of this.items) {
50
+ if (item.status === "pending") {
51
+ item.status = "approved";
52
+ count++;
53
+ }
54
+ }
55
+ return count;
56
+ }
57
+ /** Reject all pending items. Returns count rejected. */
58
+ rejectAll() {
59
+ let count = 0;
60
+ for (const item of this.items) {
61
+ if (item.status === "pending") {
62
+ item.status = "rejected";
63
+ count++;
64
+ }
65
+ }
66
+ return count;
67
+ }
68
+ /** Get all pending items. */
69
+ getPending(now = Date.now()) {
70
+ this.expire(now);
71
+ return this.items.filter((i) => i.status === "pending");
72
+ }
73
+ /** Get approved items that haven't been consumed yet. */
74
+ consumeApproved() {
75
+ const approved = this.items.filter((i) => i.status === "approved");
76
+ // remove consumed items
77
+ this.items = this.items.filter((i) => i.status !== "approved");
78
+ return approved;
79
+ }
80
+ /** Get queue statistics. */
81
+ getStats() {
82
+ const counts = { pending: 0, approved: 0, rejected: 0, expired: 0, total: this.items.length };
83
+ for (const i of this.items) {
84
+ if (i.status in counts)
85
+ counts[i.status]++;
86
+ }
87
+ return counts;
88
+ }
89
+ /** Format queue for TUI display. */
90
+ formatQueue(now = Date.now()) {
91
+ const pending = this.getPending(now);
92
+ if (pending.length === 0)
93
+ return [" (no pending approvals)"];
94
+ const lines = [];
95
+ lines.push(` Approval queue: ${pending.length} pending:`);
96
+ for (const p of pending) {
97
+ const age = Math.round((now - p.timestamp) / 60_000);
98
+ const conf = p.confidence === "high" ? "●" : p.confidence === "medium" ? "◐" : "○";
99
+ lines.push(` ${conf} [${p.id}] ${p.sessionTitle}: ${p.actionType} — ${p.detail} (${age}m ago)`);
100
+ }
101
+ lines.push(` Use /approve <id|all> or /reject <id|all>`);
102
+ return lines;
103
+ }
104
+ expire(now) {
105
+ const cutoff = now - this.config.expiryMs;
106
+ for (const item of this.items) {
107
+ if (item.status === "pending" && item.timestamp < cutoff) {
108
+ item.status = "expired";
109
+ }
110
+ }
111
+ }
112
+ }
113
+ //# sourceMappingURL=approval-queue.js.map
@@ -0,0 +1,25 @@
1
+ import type { ReasonerResult, Action } from "./types.js";
2
+ import type { ApprovalQueue } from "./approval-queue.js";
3
+ export interface ApprovalPolicy {
4
+ requireApprovalBelow: "high" | "medium" | "low";
5
+ alwaysApproveActions: string[];
6
+ alwaysRequireActions: string[];
7
+ }
8
+ /**
9
+ * Filter a reasoner result through the approval workflow.
10
+ * Actions that need approval are queued; approved actions pass through.
11
+ * Returns the filtered result with only immediately-executable actions.
12
+ */
13
+ export declare function filterThroughApproval(result: ReasonerResult, queue: ApprovalQueue, policy?: Partial<ApprovalPolicy>): {
14
+ immediate: Action[];
15
+ queued: string[];
16
+ };
17
+ /**
18
+ * Determine if an action should be auto-approved.
19
+ */
20
+ export declare function shouldAutoApprove(action: Action, confidence: ReasonerResult["confidence"], policy?: ApprovalPolicy): boolean;
21
+ /**
22
+ * Format approval workflow status for TUI display.
23
+ */
24
+ export declare function formatApprovalWorkflowStatus(queuedCount: number, immediateCount: number): string;
25
+ //# sourceMappingURL=approval-workflow.d.ts.map
@@ -0,0 +1,78 @@
1
+ // approval-workflow.ts — route low-confidence decisions through the approval
2
+ // queue automatically. integrates reasoner confidence levels with the
3
+ // existing ApprovalQueue to gate risky actions.
4
+ const DEFAULT_POLICY = {
5
+ requireApprovalBelow: "low",
6
+ alwaysApproveActions: ["wait", "report_progress"],
7
+ alwaysRequireActions: ["remove_agent", "stop_session"],
8
+ };
9
+ /**
10
+ * Filter a reasoner result through the approval workflow.
11
+ * Actions that need approval are queued; approved actions pass through.
12
+ * Returns the filtered result with only immediately-executable actions.
13
+ */
14
+ export function filterThroughApproval(result, queue, policy = {}) {
15
+ const p = { ...DEFAULT_POLICY, ...policy };
16
+ const immediate = [];
17
+ const queued = [];
18
+ for (const action of result.actions) {
19
+ if (shouldAutoApprove(action, result.confidence, p)) {
20
+ immediate.push(action);
21
+ }
22
+ else {
23
+ const session = actionSessionTitle(action);
24
+ const id = queue.enqueue(session ?? "fleet", action.action, actionSummary(action), result.confidence ?? "medium");
25
+ queued.push(id);
26
+ }
27
+ }
28
+ return { immediate, queued };
29
+ }
30
+ /**
31
+ * Determine if an action should be auto-approved.
32
+ */
33
+ export function shouldAutoApprove(action, confidence, policy = DEFAULT_POLICY) {
34
+ // always-approve list
35
+ if (policy.alwaysApproveActions.includes(action.action))
36
+ return true;
37
+ // always-require list
38
+ if (policy.alwaysRequireActions.includes(action.action))
39
+ return false;
40
+ // confidence-based: require approval when confidence is BELOW threshold
41
+ // "low" threshold = require approval for nothing (all pass)
42
+ // "medium" threshold = require approval for "low" confidence
43
+ // "high" threshold = require approval for "low" and "medium"
44
+ const confidenceOrder = { high: 3, medium: 2, low: 1, undefined: 2 };
45
+ const thresholdOrder = { high: 3, medium: 2, low: 1 };
46
+ const confLevel = confidenceOrder[confidence ?? "undefined"];
47
+ const threshold = thresholdOrder[policy.requireApprovalBelow];
48
+ // auto-approve if confidence is AT or ABOVE the threshold
49
+ return confLevel > threshold;
50
+ }
51
+ function actionSessionTitle(action) {
52
+ if ("session" in action && typeof action.session === "string") {
53
+ return action.session;
54
+ }
55
+ return undefined;
56
+ }
57
+ function actionSummary(action) {
58
+ switch (action.action) {
59
+ case "send_input": return `send: "${action.text.slice(0, 60)}"`;
60
+ case "start_session": return "start session";
61
+ case "stop_session": return "stop session";
62
+ case "create_agent": return `create: ${action.title}`;
63
+ case "remove_agent": return "remove agent";
64
+ case "report_progress": return `progress: ${action.summary.slice(0, 60)}`;
65
+ case "complete_task": return `complete: ${action.summary.slice(0, 60)}`;
66
+ case "wait": return action.reason ?? "wait";
67
+ default: return action.action;
68
+ }
69
+ }
70
+ /**
71
+ * Format approval workflow status for TUI display.
72
+ */
73
+ export function formatApprovalWorkflowStatus(queuedCount, immediateCount) {
74
+ if (queuedCount === 0)
75
+ return `all ${immediateCount} actions auto-approved`;
76
+ return `${immediateCount} auto-approved, ${queuedCount} queued for approval`;
77
+ }
78
+ //# sourceMappingURL=approval-workflow.js.map