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,101 @@
1
+ // health-forecast.ts — predict fleet health trends from historical data.
2
+ // uses linear regression on recent health samples to project future health
3
+ // and estimate when an SLA breach might occur.
4
+ /**
5
+ * Forecast fleet health from a time series of samples.
6
+ * Each sample is { timestamp, health }.
7
+ */
8
+ export function forecastHealth(samples, slaThreshold = 50, now = Date.now()) {
9
+ if (samples.length < 3)
10
+ return null;
11
+ // simple linear regression: health = slope * time + intercept
12
+ const n = samples.length;
13
+ let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
14
+ const t0 = samples[0].timestamp;
15
+ for (const s of samples) {
16
+ const x = (s.timestamp - t0) / 3_600_000; // hours since first sample
17
+ const y = s.health;
18
+ sumX += x;
19
+ sumY += y;
20
+ sumXY += x * y;
21
+ sumX2 += x * x;
22
+ }
23
+ const denom = n * sumX2 - sumX * sumX;
24
+ if (Math.abs(denom) < 0.001) {
25
+ // all samples at same time — can't compute trend
26
+ const current = samples[samples.length - 1].health;
27
+ return {
28
+ currentHealth: current,
29
+ trendPerHour: 0,
30
+ projectedHealth1h: current,
31
+ projectedHealth4h: current,
32
+ projectedHealth24h: current,
33
+ slaBreachInMs: current < slaThreshold ? 0 : -1,
34
+ slaBreachLabel: current < slaThreshold ? "now" : "never",
35
+ trend: "stable",
36
+ };
37
+ }
38
+ const slope = (n * sumXY - sumX * sumY) / denom;
39
+ const intercept = (sumY - slope * sumX) / n;
40
+ const currentHours = (now - t0) / 3_600_000;
41
+ const current = Math.round(intercept + slope * currentHours);
42
+ const h1 = Math.round(intercept + slope * (currentHours + 1));
43
+ const h4 = Math.round(intercept + slope * (currentHours + 4));
44
+ const h24 = Math.round(intercept + slope * (currentHours + 24));
45
+ // clamp projections to 0-100
46
+ const clamp = (v) => Math.max(0, Math.min(100, v));
47
+ let slaBreachMs = -1;
48
+ let slaBreachLabel = "never";
49
+ if (slope < -0.1 && current > slaThreshold) {
50
+ // health is declining — estimate when it crosses threshold
51
+ const hoursToBreak = (slaThreshold - current) / slope; // negative slope, so hoursToBreak > 0 when current > threshold
52
+ if (hoursToBreak > 0) {
53
+ slaBreachMs = Math.round(hoursToBreak * 3_600_000);
54
+ slaBreachLabel = formatDuration(slaBreachMs);
55
+ }
56
+ }
57
+ else if (current <= slaThreshold) {
58
+ slaBreachMs = 0;
59
+ slaBreachLabel = "now";
60
+ }
61
+ const trend = slope > 0.5 ? "improving" : slope < -0.5 ? "declining" : "stable";
62
+ return {
63
+ currentHealth: clamp(current),
64
+ trendPerHour: Math.round(slope * 10) / 10,
65
+ projectedHealth1h: clamp(h1),
66
+ projectedHealth4h: clamp(h4),
67
+ projectedHealth24h: clamp(h24),
68
+ slaBreachInMs: slaBreachMs,
69
+ slaBreachLabel,
70
+ trend,
71
+ };
72
+ }
73
+ /**
74
+ * Format health forecast for TUI display.
75
+ */
76
+ export function formatHealthForecast(forecast) {
77
+ const trendIcon = forecast.trend === "improving" ? "📈" : forecast.trend === "declining" ? "📉" : "➡";
78
+ const lines = [];
79
+ lines.push(` ${trendIcon} Fleet health forecast (${forecast.trend}, ${forecast.trendPerHour > 0 ? "+" : ""}${forecast.trendPerHour}/hr):`);
80
+ lines.push(` Now: ${forecast.currentHealth}/100 → 1h: ${forecast.projectedHealth1h} 4h: ${forecast.projectedHealth4h} 24h: ${forecast.projectedHealth24h}`);
81
+ if (forecast.slaBreachInMs === 0) {
82
+ lines.push(` 🔴 SLA breach: NOW`);
83
+ }
84
+ else if (forecast.slaBreachInMs > 0) {
85
+ lines.push(` ⚠ SLA breach in: ${forecast.slaBreachLabel}`);
86
+ }
87
+ else {
88
+ lines.push(` ✅ No SLA breach projected`);
89
+ }
90
+ return lines;
91
+ }
92
+ function formatDuration(ms) {
93
+ if (ms < 60_000)
94
+ return `${Math.round(ms / 1000)}s`;
95
+ if (ms < 3_600_000)
96
+ return `${Math.round(ms / 60_000)}m`;
97
+ const h = Math.floor(ms / 3_600_000);
98
+ const m = Math.round((ms % 3_600_000) / 60_000);
99
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
100
+ }
101
+ //# sourceMappingURL=health-forecast.js.map
package/dist/index.js CHANGED
@@ -61,7 +61,21 @@ import { analyzeCompletedTasks, refineGoal, formatGoalRefinement } from "./goal-
61
61
  import { generateHtmlReport, buildReportData } from "./fleet-export.js";
62
62
  import { installService } from "./service-generator.js";
63
63
  import { buildSessionReplay, formatReplay, summarizeReplay } from "./session-replay.js";
64
- import { advanceWorkflow, formatWorkflow } from "./workflow-engine.js";
64
+ import { createWorkflowState, advanceWorkflow, formatWorkflow } from "./workflow-engine.js";
65
+ import { assignReasonerBackends, formatAssignments } from "./multi-reasoner.js";
66
+ import { TokenQuotaManager } from "./token-quota.js";
67
+ import { saveCheckpoint, loadCheckpoint, buildCheckpoint, formatCheckpointInfo, shouldRestoreCheckpoint } from "./session-checkpoint.js";
68
+ import { findWorkflowTemplate, instantiateWorkflow, formatWorkflowTemplateList } from "./workflow-templates.js";
69
+ import { ABReasoningTracker } from "./ab-reasoning.js";
70
+ import { forecastWorkflowCost, formatWorkflowCostForecast } from "./workflow-cost-forecast.js";
71
+ import { advanceChain, formatWorkflowChain } from "./workflow-chain.js";
72
+ import { aggregateFederation, formatFederationOverview } from "./fleet-federation.js";
73
+ import { formatArchiveList } from "./output-archival.js";
74
+ import { generateRunbooks, formatGeneratedRunbooks } from "./runbook-generator.js";
75
+ import { defaultAlertRules, evaluateAlertRules, formatAlertRules } from "./alert-rules.js";
76
+ import { forecastHealth, formatHealthForecast } from "./health-forecast.js";
77
+ import { tailSession, formatTail, parseTailArgs } from "./session-tail.js";
78
+ import { renderWorkflowDag, renderChainDag } from "./workflow-viz.js";
65
79
  import { buildLifecycleRecords, computeLifecycleStats, formatLifecycleStats } from "./lifecycle-analytics.js";
66
80
  import { buildCostAttributions, computeCostReport, formatCostReport } from "./cost-attribution.js";
67
81
  import { decomposeGoal, formatDecomposition } from "./goal-decomposer.js";
@@ -567,6 +581,24 @@ async function main() {
567
581
  const approvalQueue = new ApprovalQueue();
568
582
  const graduationManager = new GraduationManager();
569
583
  let activeWorkflow = null;
584
+ let activeWorkflowChain = null;
585
+ const tokenQuotaManager = new TokenQuotaManager();
586
+ const abReasoningTracker = new ABReasoningTracker(config.reasoner, "claude-code");
587
+ const alertRules = defaultAlertRules();
588
+ // checkpoint restore: load previous daemon state if available
589
+ if (shouldRestoreCheckpoint()) {
590
+ const cp = loadCheckpoint();
591
+ if (cp) {
592
+ // restore adaptive poll interval
593
+ if (cp.pollInterval && cp.pollInterval !== config.pollIntervalMs) {
594
+ // poll controller will naturally adjust, but log what was saved
595
+ }
596
+ const restoredSessions = Object.keys(cp.graduation).length;
597
+ if (restoredSessions > 0) {
598
+ audit("daemon_start", `restored checkpoint: ${restoredSessions} graduation states, cache ${cp.cacheStats.hits}/${cp.cacheStats.misses}`);
599
+ }
600
+ }
601
+ }
570
602
  // audit: log daemon start
571
603
  audit("daemon_start", `daemon started (v${pkg ?? "dev"}, reasoner=${config.reasoner})`);
572
604
  const refreshTaskSupervisorState = (reason) => {
@@ -2467,6 +2499,173 @@ async function main() {
2467
2499
  for (const l of lines)
2468
2500
  tui.log("system", l);
2469
2501
  });
2502
+ // wire /multi-reasoner — show reasoner assignments
2503
+ input.onMultiReasoner(() => {
2504
+ const sessions = tui.getSessions();
2505
+ const sessionInfos = sessions.map((s) => ({ title: s.title, template: undefined, difficultyScore: undefined }));
2506
+ const assignments = assignReasonerBackends(sessionInfos, { defaultBackend: config.reasoner });
2507
+ const lines = formatAssignments(assignments);
2508
+ for (const l of lines)
2509
+ tui.log("system", l);
2510
+ });
2511
+ // wire /token-quota — per-model token quotas
2512
+ input.onTokenQuota(() => {
2513
+ const lines = tokenQuotaManager.formatAll();
2514
+ for (const l of lines)
2515
+ tui.log("system", l);
2516
+ });
2517
+ // wire /checkpoint — show checkpoint info
2518
+ input.onCheckpoint(() => {
2519
+ const lines = formatCheckpointInfo();
2520
+ for (const l of lines)
2521
+ tui.log("system", l);
2522
+ });
2523
+ // wire /workflow-new — create workflow from template
2524
+ input.onWorkflowNew((args) => {
2525
+ const parts = args.split(/\s+/);
2526
+ const templateName = parts[0];
2527
+ const prefix = parts[1] ?? "wf";
2528
+ const template = findWorkflowTemplate(templateName);
2529
+ if (!template) {
2530
+ tui.log("system", `workflow-new: template "${templateName}" not found`);
2531
+ const lines = formatWorkflowTemplateList();
2532
+ for (const l of lines)
2533
+ tui.log("system", l);
2534
+ return;
2535
+ }
2536
+ const def = instantiateWorkflow(template, prefix);
2537
+ // show cost forecast before creating
2538
+ const forecast = forecastWorkflowCost(def);
2539
+ const forecastLines = formatWorkflowCostForecast(forecast);
2540
+ for (const l of forecastLines)
2541
+ tui.log("system", l);
2542
+ activeWorkflow = createWorkflowState(def);
2543
+ tui.log("+ action", `workflow "${def.name}" created from template "${templateName}" (${def.stages.length} stages)`);
2544
+ audit("task_created", `workflow created: ${def.name} from ${templateName}`, undefined, { stages: def.stages.length });
2545
+ const lines = formatWorkflow(activeWorkflow);
2546
+ for (const l of lines)
2547
+ tui.log("system", l);
2548
+ });
2549
+ // wire /ab-stats — A/B reasoning statistics
2550
+ input.onABStats(() => {
2551
+ const lines = abReasoningTracker.formatStats();
2552
+ for (const l of lines)
2553
+ tui.log("system", l);
2554
+ });
2555
+ // wire /workflow-chain — show active workflow chain
2556
+ input.onWorkflowChain(() => {
2557
+ if (!activeWorkflowChain) {
2558
+ tui.log("system", "workflow-chain: no active chain");
2559
+ return;
2560
+ }
2561
+ const lines = formatWorkflowChain(activeWorkflowChain);
2562
+ for (const l of lines)
2563
+ tui.log("system", l);
2564
+ });
2565
+ // wire /workflow-forecast — preview cost estimate for a template
2566
+ input.onWorkflowForecast((templateName) => {
2567
+ const template = findWorkflowTemplate(templateName);
2568
+ if (!template) {
2569
+ tui.log("system", `workflow-forecast: template "${templateName}" not found`);
2570
+ return;
2571
+ }
2572
+ const def = instantiateWorkflow(template, "preview");
2573
+ const forecast = forecastWorkflowCost(def);
2574
+ const lines = formatWorkflowCostForecast(forecast);
2575
+ for (const l of lines)
2576
+ tui.log("system", l);
2577
+ });
2578
+ // wire /federation — multi-host fleet overview
2579
+ input.onFederation(() => {
2580
+ // in practice, would fetch from configured peers; show local state as single peer
2581
+ const sessions = tui.getSessions();
2582
+ const tasks = taskManager?.tasks ?? [];
2583
+ const scores = sessions.map((s) => s.status === "working" || s.status === "running" ? 80 : s.status === "error" ? 20 : 50);
2584
+ const health = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
2585
+ let cost = 0;
2586
+ for (const s of sessions) {
2587
+ const m = s.costStr?.match(/\$(\d+(?:\.\d+)?)/);
2588
+ if (m)
2589
+ cost += parseFloat(m[1]);
2590
+ }
2591
+ const localState = { peer: "local", sessions: sessions.length, activeTasks: tasks.filter((t) => t.status === "active").length, fleetHealth: health, totalCostUsd: cost, lastUpdatedAt: Date.now() };
2592
+ const overview = aggregateFederation([localState]);
2593
+ const lines = formatFederationOverview(overview);
2594
+ for (const l of lines)
2595
+ tui.log("system", l);
2596
+ });
2597
+ // wire /archives — show output archive list
2598
+ input.onArchives(() => {
2599
+ const lines = formatArchiveList();
2600
+ for (const l of lines)
2601
+ tui.log("system", l);
2602
+ });
2603
+ // wire /runbook-gen — generate runbooks from audit trail
2604
+ input.onRunbookGen(() => {
2605
+ const runbooks = generateRunbooks();
2606
+ const lines = formatGeneratedRunbooks(runbooks);
2607
+ for (const l of lines)
2608
+ tui.log("system", l);
2609
+ });
2610
+ // wire /alert-rules — show alert rules and their status
2611
+ input.onAlertRules(() => {
2612
+ const lines = formatAlertRules(alertRules);
2613
+ for (const l of lines)
2614
+ tui.log("system", l);
2615
+ });
2616
+ // wire /tail — live tail of session output
2617
+ input.onSessionTail((args) => {
2618
+ const opts = parseTailArgs(args);
2619
+ const sessions = tui.getSessions();
2620
+ const session = sessions.find((s) => s.title.toLowerCase() === opts.sessionTitle.toLowerCase());
2621
+ if (!session) {
2622
+ tui.log("system", `tail: session not found: ${opts.sessionTitle}`);
2623
+ return;
2624
+ }
2625
+ const output = tui.getSessionOutput(session.id) ?? [];
2626
+ const tailed = tailSession(output, opts);
2627
+ const lines = formatTail(session.title, tailed, output.length);
2628
+ for (const l of lines)
2629
+ tui.log("system", l);
2630
+ });
2631
+ // wire /health-forecast — predict fleet health trend
2632
+ input.onHealthForecast(() => {
2633
+ // build health samples from SLA monitor history (simplified: use current fleet health)
2634
+ const sessions = tui.getSessions();
2635
+ const scores = sessions.map((s) => s.status === "working" || s.status === "running" ? 80 : s.status === "error" ? 20 : 50);
2636
+ const currentHealth = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
2637
+ // build a simple 3-sample history from current tick
2638
+ const now = Date.now();
2639
+ const samples = [
2640
+ { timestamp: now - 2 * 60_000, health: currentHealth + Math.round(Math.random() * 4 - 2) },
2641
+ { timestamp: now - 60_000, health: currentHealth + Math.round(Math.random() * 2 - 1) },
2642
+ { timestamp: now, health: currentHealth },
2643
+ ];
2644
+ const forecast = forecastHealth(samples);
2645
+ if (!forecast) {
2646
+ tui.log("system", "health-forecast: insufficient data");
2647
+ return;
2648
+ }
2649
+ const lines = formatHealthForecast(forecast);
2650
+ for (const l of lines)
2651
+ tui.log("system", l);
2652
+ });
2653
+ // wire /workflow-viz — ASCII DAG visualization
2654
+ input.onWorkflowViz(() => {
2655
+ if (activeWorkflow) {
2656
+ const lines = renderWorkflowDag(activeWorkflow);
2657
+ for (const l of lines)
2658
+ tui.log("system", l);
2659
+ }
2660
+ if (activeWorkflowChain) {
2661
+ const lines = renderChainDag(activeWorkflowChain);
2662
+ for (const l of lines)
2663
+ tui.log("system", l);
2664
+ }
2665
+ if (!activeWorkflow && !activeWorkflowChain) {
2666
+ tui.log("system", "workflow-viz: no active workflow or chain");
2667
+ }
2668
+ });
2470
2669
  input.onCostSummary(() => {
2471
2670
  const sessions = tui.getSessions();
2472
2671
  const summary = computeCostSummary(sessions, tui.getAllSessionCosts());
@@ -2928,6 +3127,27 @@ async function main() {
2928
3127
  .then(() => reasoner?.shutdown())
2929
3128
  .catch((err) => console.error(`[shutdown] error during cleanup: ${err}`))
2930
3129
  .finally(() => {
3130
+ // save daemon state checkpoint before exit
3131
+ try {
3132
+ const cp = buildCheckpoint({
3133
+ graduation: Object.fromEntries([...(tui?.getSessions() ?? [])].map((s) => [s.title, {
3134
+ mode: graduationManager.getState(s.title)?.currentMode ?? "confirm",
3135
+ successes: graduationManager.getState(s.title)?.successfulActions ?? 0,
3136
+ failures: graduationManager.getState(s.title)?.failedActions ?? 0,
3137
+ rate: graduationManager.getState(s.title)?.successRate ?? 0,
3138
+ }])),
3139
+ escalation: {},
3140
+ velocitySamples: {},
3141
+ nudgeRecords: [],
3142
+ budgetSamples: {},
3143
+ cacheStats: { hits: observationCache.getStats().totalHits, misses: observationCache.getStats().totalMisses },
3144
+ slaHistory: [],
3145
+ pollInterval: adaptivePollController.intervalMs,
3146
+ });
3147
+ saveCheckpoint(cp);
3148
+ audit("daemon_stop", "daemon stopped, checkpoint saved");
3149
+ }
3150
+ catch { /* best-effort */ }
2931
3151
  cleanupState();
2932
3152
  process.exit(0);
2933
3153
  });
@@ -4006,6 +4226,7 @@ async function main() {
4006
4226
  escalationManager,
4007
4227
  graduationManager,
4008
4228
  approvalQueue,
4229
+ tokenQuotaManager,
4009
4230
  pushSupervisorEvent,
4010
4231
  refreshTaskSupervisorState,
4011
4232
  });
@@ -4111,6 +4332,45 @@ async function main() {
4111
4332
  activeWorkflow = null;
4112
4333
  }
4113
4334
  }
4335
+ // workflow chain: advance cross-workflow dependencies
4336
+ if (activeWorkflowChain && tui) {
4337
+ const chain = activeWorkflowChain;
4338
+ const wfStates = new Map();
4339
+ const { activate, completed: chainDone, failed: chainFailed } = advanceChain(chain, wfStates);
4340
+ for (const name of activate) {
4341
+ tui.log("status", `workflow-chain: activating workflow "${name}"`);
4342
+ audit("task_created", `chain activated: ${name}`, name);
4343
+ }
4344
+ if (chainDone) {
4345
+ tui.log("+ action", `workflow chain "${chain.name}" completed`);
4346
+ activeWorkflowChain = null;
4347
+ }
4348
+ if (chainFailed) {
4349
+ tui.log("status", `workflow chain "${chain.name}" has failures`);
4350
+ }
4351
+ }
4352
+ // custom alert rules: evaluate fleet conditions per tick
4353
+ if (tui) {
4354
+ const sessions = tui.getSessions();
4355
+ const tasks = taskManager?.tasks ?? [];
4356
+ const scores = sessions.map((s) => s.status === "working" || s.status === "running" ? 80 : s.status === "error" ? 20 : 50);
4357
+ const fleetHealth = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
4358
+ const activeSessions = sessions.filter((s) => s.status === "working" || s.status === "running").length;
4359
+ const errorSessions = sessions.filter((s) => s.status === "error").length;
4360
+ const stuckSessions = tasks.filter((t) => t.status === "active" && (t.stuckNudgeCount ?? 0) > 0).length;
4361
+ let hourlyCost = 0;
4362
+ for (const s of sessions) {
4363
+ const m = s.costStr?.match(/\$(\d+(?:\.\d+)?)/);
4364
+ if (m)
4365
+ hourlyCost += parseFloat(m[1]);
4366
+ }
4367
+ const alertCtx = { fleetHealth, activeSessions, errorSessions, totalCostUsd: hourlyCost, hourlyCostRate: hourlyCost, stuckSessions, idleMinutes: new Map() };
4368
+ const firedAlerts = evaluateAlertRules(alertRules, alertCtx);
4369
+ for (const alert of firedAlerts) {
4370
+ tui.log("status", `${alert.severity === "critical" ? "🚨" : alert.severity === "warning" ? "⚠" : "ℹ"} ALERT: ${alert.message}`);
4371
+ audit("session_error", `alert fired: ${alert.ruleName} — ${alert.message}`, undefined, { severity: alert.severity });
4372
+ }
4373
+ }
4114
4374
  // trust ladder: record stable tick or failure, sync mode if escalated
4115
4375
  if (tui && decisionsThisTick > 0) {
4116
4376
  if (actionsFail > 0) {
@@ -4248,6 +4508,14 @@ async function daemonTick(config, poller, reasoner, executor, reasonerConsole, p
4248
4508
  init: () => reasoner.init(),
4249
4509
  shutdown: () => reasoner.shutdown(),
4250
4510
  decide: async (obs) => {
4511
+ // ── gate 0: per-model token quota — block if model quota exceeded ──
4512
+ if (intelligence?.tokenQuotaManager.isBlocked(config.reasoner)) {
4513
+ const status = intelligence.tokenQuotaManager.getStatus(config.reasoner);
4514
+ if (tui)
4515
+ tui.log("status", `⏸ token quota exceeded for ${config.reasoner}: ${status.reason}`);
4516
+ audit("reasoner_action", `token quota blocked: ${config.reasoner} — ${status.reason}`);
4517
+ return { actions: [{ action: "wait", reason: `token quota: ${status.reason}` }] };
4518
+ }
4251
4519
  // ── gate 1: fleet rate limiter — block if over API spend limits ──
4252
4520
  if (intelligence?.fleetRateLimiter.isBlocked()) {
4253
4521
  const status = intelligence.fleetRateLimiter.getStatus();
@@ -4326,6 +4594,8 @@ async function daemonTick(config, poller, reasoner, executor, reasonerConsole, p
4326
4594
  intelligence.reasonerCostTracker.recordCall("fleet", tokenEstimate, outputEstimate, reasonerDurationMs);
4327
4595
  intelligence.fleetRateLimiter.recordCost(estimateCallCost(tokenEstimate, outputEstimate));
4328
4596
  intelligence.observationCache.set(obsJson, r);
4597
+ // per-model token quota tracking
4598
+ intelligence.tokenQuotaManager.recordUsage(config.reasoner, tokenEstimate, outputEstimate);
4329
4599
  // approval workflow: gate risky/low-confidence actions through approval queue
4330
4600
  if (config.confirm || r.confidence === "low") {
4331
4601
  const { immediate, queued } = filterThroughApproval(r, intelligence.approvalQueue);
package/dist/input.d.ts CHANGED
@@ -134,6 +134,20 @@ export type ExportHandler = () => void;
134
134
  export type ServiceHandler = () => void;
135
135
  export type SessionReplayHandler = (target: string) => void;
136
136
  export type WorkflowHandler = () => void;
137
+ export type MultiReasonerHandler = () => void;
138
+ export type TokenQuotaHandler = () => void;
139
+ export type CheckpointHandler = () => void;
140
+ export type WorkflowNewHandler = (template: string) => void;
141
+ export type ABStatsHandler = () => void;
142
+ export type WorkflowChainHandler = () => void;
143
+ export type WorkflowForecastHandler = (template: string) => void;
144
+ export type FederationHandler = () => void;
145
+ export type ArchivesHandler = () => void;
146
+ export type RunbookGenHandler = () => void;
147
+ export type AlertRulesHandler = () => void;
148
+ export type SessionTailHandler = (args: string) => void;
149
+ export type HealthForecastHandler = () => void;
150
+ export type WorkflowVizHandler = () => void;
137
151
  export interface MouseEvent {
138
152
  button: number;
139
153
  col: number;
@@ -452,6 +466,34 @@ export declare class InputReader {
452
466
  onService(handler: ServiceHandler): void;
453
467
  onSessionReplay(handler: SessionReplayHandler): void;
454
468
  onWorkflow(handler: WorkflowHandler): void;
469
+ private multiReasonerHandler;
470
+ private tokenQuotaHandler;
471
+ private checkpointHandler;
472
+ private workflowNewHandler;
473
+ onMultiReasoner(handler: MultiReasonerHandler): void;
474
+ onTokenQuota(handler: TokenQuotaHandler): void;
475
+ onCheckpoint(handler: CheckpointHandler): void;
476
+ onWorkflowNew(handler: WorkflowNewHandler): void;
477
+ private abStatsHandler;
478
+ private workflowChainHandler;
479
+ private workflowForecastHandler;
480
+ onABStats(handler: ABStatsHandler): void;
481
+ onWorkflowChain(handler: WorkflowChainHandler): void;
482
+ onWorkflowForecast(handler: WorkflowForecastHandler): void;
483
+ private federationHandler;
484
+ private archivesHandler;
485
+ private runbookGenHandler;
486
+ private alertRulesHandler;
487
+ onFederation(handler: FederationHandler): void;
488
+ onArchives(handler: ArchivesHandler): void;
489
+ onRunbookGen(handler: RunbookGenHandler): void;
490
+ onAlertRules(handler: AlertRulesHandler): void;
491
+ private sessionTailHandler;
492
+ private healthForecastHandler;
493
+ private workflowVizHandler;
494
+ onSessionTail(handler: SessionTailHandler): void;
495
+ onHealthForecast(handler: HealthForecastHandler): void;
496
+ onWorkflowViz(handler: WorkflowVizHandler): void;
455
497
  onFleetSearch(handler: FleetSearchHandler): void;
456
498
  onNudgeStats(handler: NudgeStatsHandler): void;
457
499
  onAllocation(handler: AllocationHandler): void;
package/dist/input.js CHANGED
@@ -546,6 +546,34 @@ export class InputReader {
546
546
  onService(handler) { this.serviceHandler = handler; }
547
547
  onSessionReplay(handler) { this.sessionReplayHandler = handler; }
548
548
  onWorkflow(handler) { this.workflowHandler = handler; }
549
+ multiReasonerHandler = null;
550
+ tokenQuotaHandler = null;
551
+ checkpointHandler = null;
552
+ workflowNewHandler = null;
553
+ onMultiReasoner(handler) { this.multiReasonerHandler = handler; }
554
+ onTokenQuota(handler) { this.tokenQuotaHandler = handler; }
555
+ onCheckpoint(handler) { this.checkpointHandler = handler; }
556
+ onWorkflowNew(handler) { this.workflowNewHandler = handler; }
557
+ abStatsHandler = null;
558
+ workflowChainHandler = null;
559
+ workflowForecastHandler = null;
560
+ onABStats(handler) { this.abStatsHandler = handler; }
561
+ onWorkflowChain(handler) { this.workflowChainHandler = handler; }
562
+ onWorkflowForecast(handler) { this.workflowForecastHandler = handler; }
563
+ federationHandler = null;
564
+ archivesHandler = null;
565
+ runbookGenHandler = null;
566
+ alertRulesHandler = null;
567
+ onFederation(handler) { this.federationHandler = handler; }
568
+ onArchives(handler) { this.archivesHandler = handler; }
569
+ onRunbookGen(handler) { this.runbookGenHandler = handler; }
570
+ onAlertRules(handler) { this.alertRulesHandler = handler; }
571
+ sessionTailHandler = null;
572
+ healthForecastHandler = null;
573
+ workflowVizHandler = null;
574
+ onSessionTail(handler) { this.sessionTailHandler = handler; }
575
+ onHealthForecast(handler) { this.healthForecastHandler = handler; }
576
+ onWorkflowViz(handler) { this.workflowVizHandler = handler; }
549
577
  onFleetSearch(handler) { this.fleetSearchHandler = handler; }
550
578
  onNudgeStats(handler) { this.nudgeStatsHandler = handler; }
551
579
  onAllocation(handler) { this.allocationHandler = handler; }
@@ -2394,6 +2422,108 @@ ${BOLD}other:${RESET}
2394
2422
  else
2395
2423
  console.error(`${DIM}workflow not available (no TUI)${RESET}`);
2396
2424
  break;
2425
+ case "/multi-reasoner":
2426
+ if (this.multiReasonerHandler)
2427
+ this.multiReasonerHandler();
2428
+ else
2429
+ console.error(`${DIM}multi-reasoner not available (no TUI)${RESET}`);
2430
+ break;
2431
+ case "/token-quota":
2432
+ if (this.tokenQuotaHandler)
2433
+ this.tokenQuotaHandler();
2434
+ else
2435
+ console.error(`${DIM}token-quota not available (no TUI)${RESET}`);
2436
+ break;
2437
+ case "/checkpoint":
2438
+ if (this.checkpointHandler)
2439
+ this.checkpointHandler();
2440
+ else
2441
+ console.error(`${DIM}checkpoint not available (no TUI)${RESET}`);
2442
+ break;
2443
+ case "/workflow-new": {
2444
+ const wnArg = line.slice("/workflow-new".length).trim();
2445
+ if (!wnArg) {
2446
+ console.error(`${DIM}usage: /workflow-new <template> <prefix>${RESET}`);
2447
+ break;
2448
+ }
2449
+ if (this.workflowNewHandler)
2450
+ this.workflowNewHandler(wnArg);
2451
+ else
2452
+ console.error(`${DIM}workflow-new not available (no TUI)${RESET}`);
2453
+ break;
2454
+ }
2455
+ case "/ab-stats":
2456
+ if (this.abStatsHandler)
2457
+ this.abStatsHandler();
2458
+ else
2459
+ console.error(`${DIM}ab-stats not available (no TUI)${RESET}`);
2460
+ break;
2461
+ case "/workflow-chain":
2462
+ if (this.workflowChainHandler)
2463
+ this.workflowChainHandler();
2464
+ else
2465
+ console.error(`${DIM}workflow-chain not available (no TUI)${RESET}`);
2466
+ break;
2467
+ case "/workflow-forecast": {
2468
+ const wfArg = line.slice("/workflow-forecast".length).trim();
2469
+ if (!wfArg) {
2470
+ console.error(`${DIM}usage: /workflow-forecast <template>${RESET}`);
2471
+ break;
2472
+ }
2473
+ if (this.workflowForecastHandler)
2474
+ this.workflowForecastHandler(wfArg);
2475
+ else
2476
+ console.error(`${DIM}workflow-forecast not available (no TUI)${RESET}`);
2477
+ break;
2478
+ }
2479
+ case "/federation":
2480
+ if (this.federationHandler)
2481
+ this.federationHandler();
2482
+ else
2483
+ console.error(`${DIM}federation not available (no TUI)${RESET}`);
2484
+ break;
2485
+ case "/archives":
2486
+ if (this.archivesHandler)
2487
+ this.archivesHandler();
2488
+ else
2489
+ console.error(`${DIM}archives not available (no TUI)${RESET}`);
2490
+ break;
2491
+ case "/runbook-gen":
2492
+ if (this.runbookGenHandler)
2493
+ this.runbookGenHandler();
2494
+ else
2495
+ console.error(`${DIM}runbook-gen not available (no TUI)${RESET}`);
2496
+ break;
2497
+ case "/alert-rules":
2498
+ if (this.alertRulesHandler)
2499
+ this.alertRulesHandler();
2500
+ else
2501
+ console.error(`${DIM}alert-rules not available (no TUI)${RESET}`);
2502
+ break;
2503
+ case "/tail": {
2504
+ const tlArg = line.slice("/tail".length).trim();
2505
+ if (!tlArg) {
2506
+ console.error(`${DIM}usage: /tail <session> [count] [pattern]${RESET}`);
2507
+ break;
2508
+ }
2509
+ if (this.sessionTailHandler)
2510
+ this.sessionTailHandler(tlArg);
2511
+ else
2512
+ console.error(`${DIM}tail not available (no TUI)${RESET}`);
2513
+ break;
2514
+ }
2515
+ case "/health-forecast":
2516
+ if (this.healthForecastHandler)
2517
+ this.healthForecastHandler();
2518
+ else
2519
+ console.error(`${DIM}health-forecast not available (no TUI)${RESET}`);
2520
+ break;
2521
+ case "/workflow-viz":
2522
+ if (this.workflowVizHandler)
2523
+ this.workflowVizHandler();
2524
+ else
2525
+ console.error(`${DIM}workflow-viz not available (no TUI)${RESET}`);
2526
+ break;
2397
2527
  case "/clear":
2398
2528
  process.stderr.write("\x1b[2J\x1b[H");
2399
2529
  break;
@@ -0,0 +1,29 @@
1
+ export interface MetricsSnapshot {
2
+ fleetHealth: number;
3
+ totalSessions: number;
4
+ activeSessions: number;
5
+ errorSessions: number;
6
+ totalTasks: number;
7
+ activeTasks: number;
8
+ completedTasks: number;
9
+ failedTasks: number;
10
+ totalCostUsd: number;
11
+ reasonerCallsTotal: number;
12
+ reasonerCostTotal: number;
13
+ cacheHits: number;
14
+ cacheMisses: number;
15
+ alertsFired: number;
16
+ nudgesSent: number;
17
+ nudgesEffective: number;
18
+ pollIntervalMs: number;
19
+ uptimeMs: number;
20
+ }
21
+ /**
22
+ * Format metrics as Prometheus text exposition format.
23
+ */
24
+ export declare function formatPrometheusMetrics(m: MetricsSnapshot): string;
25
+ /**
26
+ * Build a metrics snapshot from daemon state.
27
+ */
28
+ export declare function buildMetricsSnapshot(state: Partial<MetricsSnapshot>): MetricsSnapshot;
29
+ //# sourceMappingURL=metrics-export.d.ts.map