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.
- package/dist/ab-reasoning.d.ts +42 -0
- package/dist/ab-reasoning.js +91 -0
- package/dist/alert-composer.d.ts +32 -0
- package/dist/alert-composer.js +51 -0
- package/dist/alert-rule-dsl.d.ts +32 -0
- package/dist/alert-rule-dsl.js +87 -0
- package/dist/alert-rules.d.ts +42 -0
- package/dist/alert-rules.js +94 -0
- package/dist/fleet-federation.d.ts +34 -0
- package/dist/fleet-federation.js +55 -0
- package/dist/fleet-grep.d.ts +22 -0
- package/dist/fleet-grep.js +70 -0
- package/dist/health-forecast.d.ts +23 -0
- package/dist/health-forecast.js +101 -0
- package/dist/index.js +271 -1
- package/dist/input.d.ts +42 -0
- package/dist/input.js +130 -0
- package/dist/metrics-export.d.ts +29 -0
- package/dist/metrics-export.js +58 -0
- package/dist/multi-reasoner.d.ts +36 -0
- package/dist/multi-reasoner.js +87 -0
- package/dist/output-archival.d.ts +23 -0
- package/dist/output-archival.js +72 -0
- package/dist/runbook-executor.d.ts +37 -0
- package/dist/runbook-executor.js +78 -0
- package/dist/runbook-generator.d.ts +21 -0
- package/dist/runbook-generator.js +104 -0
- package/dist/session-checkpoint.d.ts +55 -0
- package/dist/session-checkpoint.js +69 -0
- package/dist/session-tail.d.ts +24 -0
- package/dist/session-tail.js +52 -0
- package/dist/token-quota.d.ts +45 -0
- package/dist/token-quota.js +76 -0
- package/dist/workflow-chain.d.ts +33 -0
- package/dist/workflow-chain.js +69 -0
- package/dist/workflow-cost-forecast.d.ts +22 -0
- package/dist/workflow-cost-forecast.js +55 -0
- package/dist/workflow-templates.d.ts +25 -0
- package/dist/workflow-templates.js +92 -0
- package/dist/workflow-viz.d.ts +15 -0
- package/dist/workflow-viz.js +74 -0
- 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
|