aoaoe 1.3.0 → 2.0.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-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/index.js +215 -1
- package/dist/input.d.ts +33 -0
- package/dist/input.js +100 -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-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/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/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReasonerResult, Action } from "./types.js";
|
|
2
|
+
export interface ABTrialResult {
|
|
3
|
+
timestamp: number;
|
|
4
|
+
backendA: string;
|
|
5
|
+
backendB: string;
|
|
6
|
+
actionsA: Action[];
|
|
7
|
+
actionsB: Action[];
|
|
8
|
+
confidenceA?: string;
|
|
9
|
+
confidenceB?: string;
|
|
10
|
+
winner: "a" | "b" | "tie";
|
|
11
|
+
reason: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ABStats {
|
|
14
|
+
totalTrials: number;
|
|
15
|
+
winsA: number;
|
|
16
|
+
winsB: number;
|
|
17
|
+
ties: number;
|
|
18
|
+
backendA: string;
|
|
19
|
+
backendB: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Compare two reasoner results and determine which is better.
|
|
23
|
+
* Heuristic: more specific actions > wait, higher confidence > lower,
|
|
24
|
+
* fewer redundant actions = better.
|
|
25
|
+
*/
|
|
26
|
+
export declare function compareResults(resultA: ReasonerResult, resultB: ReasonerResult, backendA: string, backendB: string, now?: number): ABTrialResult;
|
|
27
|
+
/**
|
|
28
|
+
* Track A/B trial results over time.
|
|
29
|
+
*/
|
|
30
|
+
export declare class ABReasoningTracker {
|
|
31
|
+
private trials;
|
|
32
|
+
private backendA;
|
|
33
|
+
private backendB;
|
|
34
|
+
constructor(backendA: string, backendB: string);
|
|
35
|
+
/** Record a trial result. */
|
|
36
|
+
recordTrial(result: ABTrialResult): void;
|
|
37
|
+
/** Get aggregate stats. */
|
|
38
|
+
getStats(): ABStats;
|
|
39
|
+
/** Format stats for TUI display. */
|
|
40
|
+
formatStats(): string[];
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=ab-reasoning.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// ab-reasoning.ts — run two reasoner backends on the same observation,
|
|
2
|
+
// compare their outputs, and track which performs better over time.
|
|
3
|
+
/**
|
|
4
|
+
* Compare two reasoner results and determine which is better.
|
|
5
|
+
* Heuristic: more specific actions > wait, higher confidence > lower,
|
|
6
|
+
* fewer redundant actions = better.
|
|
7
|
+
*/
|
|
8
|
+
export function compareResults(resultA, resultB, backendA, backendB, now = Date.now()) {
|
|
9
|
+
let scoreA = 0;
|
|
10
|
+
let scoreB = 0;
|
|
11
|
+
// prefer non-wait actions over wait
|
|
12
|
+
const nonWaitA = resultA.actions.filter((a) => a.action !== "wait").length;
|
|
13
|
+
const nonWaitB = resultB.actions.filter((a) => a.action !== "wait").length;
|
|
14
|
+
if (nonWaitA > nonWaitB)
|
|
15
|
+
scoreA += 2;
|
|
16
|
+
else if (nonWaitB > nonWaitA)
|
|
17
|
+
scoreB += 2;
|
|
18
|
+
// prefer higher confidence
|
|
19
|
+
const confOrder = { high: 3, medium: 2, low: 1 };
|
|
20
|
+
const confA = confOrder[resultA.confidence ?? "medium"] ?? 2;
|
|
21
|
+
const confB = confOrder[resultB.confidence ?? "medium"] ?? 2;
|
|
22
|
+
if (confA > confB)
|
|
23
|
+
scoreA += 1;
|
|
24
|
+
else if (confB > confA)
|
|
25
|
+
scoreB += 1;
|
|
26
|
+
// prefer fewer total actions (more focused)
|
|
27
|
+
if (resultA.actions.length > 0 && resultB.actions.length > 0) {
|
|
28
|
+
if (resultA.actions.length < resultB.actions.length)
|
|
29
|
+
scoreA += 1;
|
|
30
|
+
else if (resultB.actions.length < resultA.actions.length)
|
|
31
|
+
scoreB += 1;
|
|
32
|
+
}
|
|
33
|
+
const winner = scoreA > scoreB ? "a" : scoreB > scoreA ? "b" : "tie";
|
|
34
|
+
const reason = `A(${nonWaitA} actions, ${resultA.confidence ?? "?"}) vs B(${nonWaitB} actions, ${resultB.confidence ?? "?"})`;
|
|
35
|
+
return {
|
|
36
|
+
timestamp: now,
|
|
37
|
+
backendA,
|
|
38
|
+
backendB,
|
|
39
|
+
actionsA: resultA.actions,
|
|
40
|
+
actionsB: resultB.actions,
|
|
41
|
+
confidenceA: resultA.confidence,
|
|
42
|
+
confidenceB: resultB.confidence,
|
|
43
|
+
winner,
|
|
44
|
+
reason,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Track A/B trial results over time.
|
|
49
|
+
*/
|
|
50
|
+
export class ABReasoningTracker {
|
|
51
|
+
trials = [];
|
|
52
|
+
backendA;
|
|
53
|
+
backendB;
|
|
54
|
+
constructor(backendA, backendB) {
|
|
55
|
+
this.backendA = backendA;
|
|
56
|
+
this.backendB = backendB;
|
|
57
|
+
}
|
|
58
|
+
/** Record a trial result. */
|
|
59
|
+
recordTrial(result) {
|
|
60
|
+
this.trials.push(result);
|
|
61
|
+
}
|
|
62
|
+
/** Get aggregate stats. */
|
|
63
|
+
getStats() {
|
|
64
|
+
return {
|
|
65
|
+
totalTrials: this.trials.length,
|
|
66
|
+
winsA: this.trials.filter((t) => t.winner === "a").length,
|
|
67
|
+
winsB: this.trials.filter((t) => t.winner === "b").length,
|
|
68
|
+
ties: this.trials.filter((t) => t.winner === "tie").length,
|
|
69
|
+
backendA: this.backendA,
|
|
70
|
+
backendB: this.backendB,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Format stats for TUI display. */
|
|
74
|
+
formatStats() {
|
|
75
|
+
const s = this.getStats();
|
|
76
|
+
if (s.totalTrials === 0)
|
|
77
|
+
return [" (no A/B trials recorded yet)"];
|
|
78
|
+
const pctA = s.totalTrials > 0 ? Math.round((s.winsA / s.totalTrials) * 100) : 0;
|
|
79
|
+
const pctB = s.totalTrials > 0 ? Math.round((s.winsB / s.totalTrials) * 100) : 0;
|
|
80
|
+
return [
|
|
81
|
+
` A/B Reasoning: ${s.totalTrials} trials`,
|
|
82
|
+
` ${s.backendA}: ${s.winsA} wins (${pctA}%)`,
|
|
83
|
+
` ${s.backendB}: ${s.winsB} wins (${pctB}%)`,
|
|
84
|
+
` Ties: ${s.ties}`,
|
|
85
|
+
s.winsA > s.winsB ? ` → ${s.backendA} is performing better` :
|
|
86
|
+
s.winsB > s.winsA ? ` → ${s.backendB} is performing better` :
|
|
87
|
+
` → Both backends performing equally`,
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=ab-reasoning.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type AlertSeverity = "info" | "warning" | "critical";
|
|
2
|
+
export type AlertCondition = (ctx: AlertContext) => boolean;
|
|
3
|
+
export interface AlertContext {
|
|
4
|
+
fleetHealth: number;
|
|
5
|
+
activeSessions: number;
|
|
6
|
+
errorSessions: number;
|
|
7
|
+
totalCostUsd: number;
|
|
8
|
+
hourlyCostRate: number;
|
|
9
|
+
stuckSessions: number;
|
|
10
|
+
idleMinutes: Map<string, number>;
|
|
11
|
+
}
|
|
12
|
+
export interface AlertRule {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
severity: AlertSeverity;
|
|
16
|
+
condition: AlertCondition;
|
|
17
|
+
cooldownMs: number;
|
|
18
|
+
lastFiredAt: number;
|
|
19
|
+
}
|
|
20
|
+
export interface FiredAlert {
|
|
21
|
+
ruleName: string;
|
|
22
|
+
severity: AlertSeverity;
|
|
23
|
+
message: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Built-in alert rules.
|
|
28
|
+
*/
|
|
29
|
+
export declare function defaultAlertRules(): AlertRule[];
|
|
30
|
+
/**
|
|
31
|
+
* Evaluate all alert rules against current fleet state.
|
|
32
|
+
*/
|
|
33
|
+
export declare function evaluateAlertRules(rules: AlertRule[], ctx: AlertContext, now?: number): FiredAlert[];
|
|
34
|
+
/**
|
|
35
|
+
* Format fired alerts for TUI display.
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatFiredAlerts(alerts: FiredAlert[]): string[];
|
|
38
|
+
/**
|
|
39
|
+
* Format all rules and their status for TUI display.
|
|
40
|
+
*/
|
|
41
|
+
export declare function formatAlertRules(rules: AlertRule[], now?: number): string[];
|
|
42
|
+
//# sourceMappingURL=alert-rules.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// alert-rules.ts — custom fleet health alerting rules beyond SLA threshold.
|
|
2
|
+
// define conditions that trigger alerts when met, with configurable
|
|
3
|
+
// severity, cooldown, and notification routing.
|
|
4
|
+
/**
|
|
5
|
+
* Built-in alert rules.
|
|
6
|
+
*/
|
|
7
|
+
export function defaultAlertRules() {
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
name: "fleet-health-critical",
|
|
11
|
+
description: "Fleet health dropped below 30",
|
|
12
|
+
severity: "critical",
|
|
13
|
+
condition: (ctx) => ctx.fleetHealth < 30,
|
|
14
|
+
cooldownMs: 10 * 60_000,
|
|
15
|
+
lastFiredAt: 0,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "high-error-rate",
|
|
19
|
+
description: "More than 50% of sessions in error state",
|
|
20
|
+
severity: "critical",
|
|
21
|
+
condition: (ctx) => ctx.activeSessions > 0 && (ctx.errorSessions / ctx.activeSessions) > 0.5,
|
|
22
|
+
cooldownMs: 5 * 60_000,
|
|
23
|
+
lastFiredAt: 0,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "cost-spike",
|
|
27
|
+
description: "Hourly cost rate exceeds $5",
|
|
28
|
+
severity: "warning",
|
|
29
|
+
condition: (ctx) => ctx.hourlyCostRate > 5,
|
|
30
|
+
cooldownMs: 15 * 60_000,
|
|
31
|
+
lastFiredAt: 0,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "all-stuck",
|
|
35
|
+
description: "All active sessions are stuck",
|
|
36
|
+
severity: "critical",
|
|
37
|
+
condition: (ctx) => ctx.activeSessions > 0 && ctx.stuckSessions === ctx.activeSessions,
|
|
38
|
+
cooldownMs: 10 * 60_000,
|
|
39
|
+
lastFiredAt: 0,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "no-active-sessions",
|
|
43
|
+
description: "No active sessions running",
|
|
44
|
+
severity: "info",
|
|
45
|
+
condition: (ctx) => ctx.activeSessions === 0,
|
|
46
|
+
cooldownMs: 30 * 60_000,
|
|
47
|
+
lastFiredAt: 0,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Evaluate all alert rules against current fleet state.
|
|
53
|
+
*/
|
|
54
|
+
export function evaluateAlertRules(rules, ctx, now = Date.now()) {
|
|
55
|
+
const fired = [];
|
|
56
|
+
for (const rule of rules) {
|
|
57
|
+
if (now - rule.lastFiredAt < rule.cooldownMs)
|
|
58
|
+
continue;
|
|
59
|
+
if (rule.condition(ctx)) {
|
|
60
|
+
rule.lastFiredAt = now;
|
|
61
|
+
fired.push({
|
|
62
|
+
ruleName: rule.name,
|
|
63
|
+
severity: rule.severity,
|
|
64
|
+
message: rule.description,
|
|
65
|
+
timestamp: now,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return fired;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Format fired alerts for TUI display.
|
|
73
|
+
*/
|
|
74
|
+
export function formatFiredAlerts(alerts) {
|
|
75
|
+
if (alerts.length === 0)
|
|
76
|
+
return [" ✅ no alerts fired"];
|
|
77
|
+
const icons = { info: "ℹ", warning: "⚠", critical: "🚨" };
|
|
78
|
+
return alerts.map((a) => ` ${icons[a.severity]} [${a.severity}] ${a.ruleName}: ${a.message}`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Format all rules and their status for TUI display.
|
|
82
|
+
*/
|
|
83
|
+
export function formatAlertRules(rules, now = Date.now()) {
|
|
84
|
+
const lines = [];
|
|
85
|
+
lines.push(` Alert rules (${rules.length}):`);
|
|
86
|
+
for (const r of rules) {
|
|
87
|
+
const cooldownRemaining = Math.max(0, r.cooldownMs - (now - r.lastFiredAt));
|
|
88
|
+
const cooldownStr = cooldownRemaining > 0 ? ` (cooldown: ${Math.round(cooldownRemaining / 60_000)}m)` : "";
|
|
89
|
+
const icon = r.severity === "critical" ? "🚨" : r.severity === "warning" ? "⚠" : "ℹ";
|
|
90
|
+
lines.push(` ${icon} ${r.name}: ${r.description}${cooldownStr}`);
|
|
91
|
+
}
|
|
92
|
+
return lines;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=alert-rules.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface FederationPeer {
|
|
2
|
+
name: string;
|
|
3
|
+
url: string;
|
|
4
|
+
lastSeenAt?: number;
|
|
5
|
+
status: "online" | "offline" | "unknown";
|
|
6
|
+
}
|
|
7
|
+
export interface FederatedFleetState {
|
|
8
|
+
peer: string;
|
|
9
|
+
sessions: number;
|
|
10
|
+
activeTasks: number;
|
|
11
|
+
fleetHealth: number;
|
|
12
|
+
totalCostUsd: number;
|
|
13
|
+
lastUpdatedAt: number;
|
|
14
|
+
}
|
|
15
|
+
export interface FederationOverview {
|
|
16
|
+
peers: FederatedFleetState[];
|
|
17
|
+
totalSessions: number;
|
|
18
|
+
totalActiveTasks: number;
|
|
19
|
+
averageHealth: number;
|
|
20
|
+
totalCostUsd: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fetch fleet state from a peer daemon's health endpoint.
|
|
24
|
+
*/
|
|
25
|
+
export declare function fetchPeerState(peer: FederationPeer, timeoutMs?: number): Promise<FederatedFleetState | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Aggregate fleet state from all peers into an overview.
|
|
28
|
+
*/
|
|
29
|
+
export declare function aggregateFederation(states: FederatedFleetState[]): FederationOverview;
|
|
30
|
+
/**
|
|
31
|
+
* Format federation overview for TUI display.
|
|
32
|
+
*/
|
|
33
|
+
export declare function formatFederationOverview(overview: FederationOverview): string[];
|
|
34
|
+
//# sourceMappingURL=fleet-federation.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// fleet-federation.ts — coordinate across multiple aoaoe daemons via HTTP.
|
|
2
|
+
// each daemon exposes a lightweight status endpoint; the federation client
|
|
3
|
+
// aggregates fleet state across hosts for unified monitoring.
|
|
4
|
+
/**
|
|
5
|
+
* Fetch fleet state from a peer daemon's health endpoint.
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchPeerState(peer, timeoutMs = 5000) {
|
|
8
|
+
try {
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
11
|
+
const response = await fetch(`${peer.url}/health`, { signal: controller.signal });
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
if (!response.ok)
|
|
14
|
+
return null;
|
|
15
|
+
const data = await response.json();
|
|
16
|
+
return {
|
|
17
|
+
peer: peer.name,
|
|
18
|
+
sessions: data.sessions ?? 0,
|
|
19
|
+
activeTasks: data.activeTasks ?? 0,
|
|
20
|
+
fleetHealth: data.fleetHealth ?? 0,
|
|
21
|
+
totalCostUsd: data.totalCostUsd ?? 0,
|
|
22
|
+
lastUpdatedAt: Date.now(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Aggregate fleet state from all peers into an overview.
|
|
31
|
+
*/
|
|
32
|
+
export function aggregateFederation(states) {
|
|
33
|
+
const totalSessions = states.reduce((s, p) => s + p.sessions, 0);
|
|
34
|
+
const totalActiveTasks = states.reduce((s, p) => s + p.activeTasks, 0);
|
|
35
|
+
const totalCost = states.reduce((s, p) => s + p.totalCostUsd, 0);
|
|
36
|
+
const avgHealth = states.length > 0
|
|
37
|
+
? Math.round(states.reduce((s, p) => s + p.fleetHealth, 0) / states.length)
|
|
38
|
+
: 0;
|
|
39
|
+
return { peers: states, totalSessions, totalActiveTasks, averageHealth: avgHealth, totalCostUsd: totalCost };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Format federation overview for TUI display.
|
|
43
|
+
*/
|
|
44
|
+
export function formatFederationOverview(overview) {
|
|
45
|
+
if (overview.peers.length === 0)
|
|
46
|
+
return [" (no federation peers configured)"];
|
|
47
|
+
const lines = [];
|
|
48
|
+
lines.push(` Federation: ${overview.peers.length} peers, ${overview.totalSessions} sessions, health ${overview.averageHealth}/100, $${overview.totalCostUsd.toFixed(2)} total`);
|
|
49
|
+
for (const p of overview.peers) {
|
|
50
|
+
const age = Math.round((Date.now() - p.lastUpdatedAt) / 60_000);
|
|
51
|
+
lines.push(` ${p.peer}: ${p.sessions} sessions, ${p.activeTasks} active, health ${p.fleetHealth}/100, $${p.totalCostUsd.toFixed(2)} (${age}m ago)`);
|
|
52
|
+
}
|
|
53
|
+
return lines;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=fleet-federation.js.map
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,18 @@ 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";
|
|
65
76
|
import { buildLifecycleRecords, computeLifecycleStats, formatLifecycleStats } from "./lifecycle-analytics.js";
|
|
66
77
|
import { buildCostAttributions, computeCostReport, formatCostReport } from "./cost-attribution.js";
|
|
67
78
|
import { decomposeGoal, formatDecomposition } from "./goal-decomposer.js";
|
|
@@ -567,6 +578,24 @@ async function main() {
|
|
|
567
578
|
const approvalQueue = new ApprovalQueue();
|
|
568
579
|
const graduationManager = new GraduationManager();
|
|
569
580
|
let activeWorkflow = null;
|
|
581
|
+
let activeWorkflowChain = null;
|
|
582
|
+
const tokenQuotaManager = new TokenQuotaManager();
|
|
583
|
+
const abReasoningTracker = new ABReasoningTracker(config.reasoner, "claude-code");
|
|
584
|
+
const alertRules = defaultAlertRules();
|
|
585
|
+
// checkpoint restore: load previous daemon state if available
|
|
586
|
+
if (shouldRestoreCheckpoint()) {
|
|
587
|
+
const cp = loadCheckpoint();
|
|
588
|
+
if (cp) {
|
|
589
|
+
// restore adaptive poll interval
|
|
590
|
+
if (cp.pollInterval && cp.pollInterval !== config.pollIntervalMs) {
|
|
591
|
+
// poll controller will naturally adjust, but log what was saved
|
|
592
|
+
}
|
|
593
|
+
const restoredSessions = Object.keys(cp.graduation).length;
|
|
594
|
+
if (restoredSessions > 0) {
|
|
595
|
+
audit("daemon_start", `restored checkpoint: ${restoredSessions} graduation states, cache ${cp.cacheStats.hits}/${cp.cacheStats.misses}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
570
599
|
// audit: log daemon start
|
|
571
600
|
audit("daemon_start", `daemon started (v${pkg ?? "dev"}, reasoner=${config.reasoner})`);
|
|
572
601
|
const refreshTaskSupervisorState = (reason) => {
|
|
@@ -2467,6 +2496,120 @@ async function main() {
|
|
|
2467
2496
|
for (const l of lines)
|
|
2468
2497
|
tui.log("system", l);
|
|
2469
2498
|
});
|
|
2499
|
+
// wire /multi-reasoner — show reasoner assignments
|
|
2500
|
+
input.onMultiReasoner(() => {
|
|
2501
|
+
const sessions = tui.getSessions();
|
|
2502
|
+
const sessionInfos = sessions.map((s) => ({ title: s.title, template: undefined, difficultyScore: undefined }));
|
|
2503
|
+
const assignments = assignReasonerBackends(sessionInfos, { defaultBackend: config.reasoner });
|
|
2504
|
+
const lines = formatAssignments(assignments);
|
|
2505
|
+
for (const l of lines)
|
|
2506
|
+
tui.log("system", l);
|
|
2507
|
+
});
|
|
2508
|
+
// wire /token-quota — per-model token quotas
|
|
2509
|
+
input.onTokenQuota(() => {
|
|
2510
|
+
const lines = tokenQuotaManager.formatAll();
|
|
2511
|
+
for (const l of lines)
|
|
2512
|
+
tui.log("system", l);
|
|
2513
|
+
});
|
|
2514
|
+
// wire /checkpoint — show checkpoint info
|
|
2515
|
+
input.onCheckpoint(() => {
|
|
2516
|
+
const lines = formatCheckpointInfo();
|
|
2517
|
+
for (const l of lines)
|
|
2518
|
+
tui.log("system", l);
|
|
2519
|
+
});
|
|
2520
|
+
// wire /workflow-new — create workflow from template
|
|
2521
|
+
input.onWorkflowNew((args) => {
|
|
2522
|
+
const parts = args.split(/\s+/);
|
|
2523
|
+
const templateName = parts[0];
|
|
2524
|
+
const prefix = parts[1] ?? "wf";
|
|
2525
|
+
const template = findWorkflowTemplate(templateName);
|
|
2526
|
+
if (!template) {
|
|
2527
|
+
tui.log("system", `workflow-new: template "${templateName}" not found`);
|
|
2528
|
+
const lines = formatWorkflowTemplateList();
|
|
2529
|
+
for (const l of lines)
|
|
2530
|
+
tui.log("system", l);
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
const def = instantiateWorkflow(template, prefix);
|
|
2534
|
+
// show cost forecast before creating
|
|
2535
|
+
const forecast = forecastWorkflowCost(def);
|
|
2536
|
+
const forecastLines = formatWorkflowCostForecast(forecast);
|
|
2537
|
+
for (const l of forecastLines)
|
|
2538
|
+
tui.log("system", l);
|
|
2539
|
+
activeWorkflow = createWorkflowState(def);
|
|
2540
|
+
tui.log("+ action", `workflow "${def.name}" created from template "${templateName}" (${def.stages.length} stages)`);
|
|
2541
|
+
audit("task_created", `workflow created: ${def.name} from ${templateName}`, undefined, { stages: def.stages.length });
|
|
2542
|
+
const lines = formatWorkflow(activeWorkflow);
|
|
2543
|
+
for (const l of lines)
|
|
2544
|
+
tui.log("system", l);
|
|
2545
|
+
});
|
|
2546
|
+
// wire /ab-stats — A/B reasoning statistics
|
|
2547
|
+
input.onABStats(() => {
|
|
2548
|
+
const lines = abReasoningTracker.formatStats();
|
|
2549
|
+
for (const l of lines)
|
|
2550
|
+
tui.log("system", l);
|
|
2551
|
+
});
|
|
2552
|
+
// wire /workflow-chain — show active workflow chain
|
|
2553
|
+
input.onWorkflowChain(() => {
|
|
2554
|
+
if (!activeWorkflowChain) {
|
|
2555
|
+
tui.log("system", "workflow-chain: no active chain");
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
const lines = formatWorkflowChain(activeWorkflowChain);
|
|
2559
|
+
for (const l of lines)
|
|
2560
|
+
tui.log("system", l);
|
|
2561
|
+
});
|
|
2562
|
+
// wire /workflow-forecast — preview cost estimate for a template
|
|
2563
|
+
input.onWorkflowForecast((templateName) => {
|
|
2564
|
+
const template = findWorkflowTemplate(templateName);
|
|
2565
|
+
if (!template) {
|
|
2566
|
+
tui.log("system", `workflow-forecast: template "${templateName}" not found`);
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
const def = instantiateWorkflow(template, "preview");
|
|
2570
|
+
const forecast = forecastWorkflowCost(def);
|
|
2571
|
+
const lines = formatWorkflowCostForecast(forecast);
|
|
2572
|
+
for (const l of lines)
|
|
2573
|
+
tui.log("system", l);
|
|
2574
|
+
});
|
|
2575
|
+
// wire /federation — multi-host fleet overview
|
|
2576
|
+
input.onFederation(() => {
|
|
2577
|
+
// in practice, would fetch from configured peers; show local state as single peer
|
|
2578
|
+
const sessions = tui.getSessions();
|
|
2579
|
+
const tasks = taskManager?.tasks ?? [];
|
|
2580
|
+
const scores = sessions.map((s) => s.status === "working" || s.status === "running" ? 80 : s.status === "error" ? 20 : 50);
|
|
2581
|
+
const health = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
|
|
2582
|
+
let cost = 0;
|
|
2583
|
+
for (const s of sessions) {
|
|
2584
|
+
const m = s.costStr?.match(/\$(\d+(?:\.\d+)?)/);
|
|
2585
|
+
if (m)
|
|
2586
|
+
cost += parseFloat(m[1]);
|
|
2587
|
+
}
|
|
2588
|
+
const localState = { peer: "local", sessions: sessions.length, activeTasks: tasks.filter((t) => t.status === "active").length, fleetHealth: health, totalCostUsd: cost, lastUpdatedAt: Date.now() };
|
|
2589
|
+
const overview = aggregateFederation([localState]);
|
|
2590
|
+
const lines = formatFederationOverview(overview);
|
|
2591
|
+
for (const l of lines)
|
|
2592
|
+
tui.log("system", l);
|
|
2593
|
+
});
|
|
2594
|
+
// wire /archives — show output archive list
|
|
2595
|
+
input.onArchives(() => {
|
|
2596
|
+
const lines = formatArchiveList();
|
|
2597
|
+
for (const l of lines)
|
|
2598
|
+
tui.log("system", l);
|
|
2599
|
+
});
|
|
2600
|
+
// wire /runbook-gen — generate runbooks from audit trail
|
|
2601
|
+
input.onRunbookGen(() => {
|
|
2602
|
+
const runbooks = generateRunbooks();
|
|
2603
|
+
const lines = formatGeneratedRunbooks(runbooks);
|
|
2604
|
+
for (const l of lines)
|
|
2605
|
+
tui.log("system", l);
|
|
2606
|
+
});
|
|
2607
|
+
// wire /alert-rules — show alert rules and their status
|
|
2608
|
+
input.onAlertRules(() => {
|
|
2609
|
+
const lines = formatAlertRules(alertRules);
|
|
2610
|
+
for (const l of lines)
|
|
2611
|
+
tui.log("system", l);
|
|
2612
|
+
});
|
|
2470
2613
|
input.onCostSummary(() => {
|
|
2471
2614
|
const sessions = tui.getSessions();
|
|
2472
2615
|
const summary = computeCostSummary(sessions, tui.getAllSessionCosts());
|
|
@@ -2928,6 +3071,27 @@ async function main() {
|
|
|
2928
3071
|
.then(() => reasoner?.shutdown())
|
|
2929
3072
|
.catch((err) => console.error(`[shutdown] error during cleanup: ${err}`))
|
|
2930
3073
|
.finally(() => {
|
|
3074
|
+
// save daemon state checkpoint before exit
|
|
3075
|
+
try {
|
|
3076
|
+
const cp = buildCheckpoint({
|
|
3077
|
+
graduation: Object.fromEntries([...(tui?.getSessions() ?? [])].map((s) => [s.title, {
|
|
3078
|
+
mode: graduationManager.getState(s.title)?.currentMode ?? "confirm",
|
|
3079
|
+
successes: graduationManager.getState(s.title)?.successfulActions ?? 0,
|
|
3080
|
+
failures: graduationManager.getState(s.title)?.failedActions ?? 0,
|
|
3081
|
+
rate: graduationManager.getState(s.title)?.successRate ?? 0,
|
|
3082
|
+
}])),
|
|
3083
|
+
escalation: {},
|
|
3084
|
+
velocitySamples: {},
|
|
3085
|
+
nudgeRecords: [],
|
|
3086
|
+
budgetSamples: {},
|
|
3087
|
+
cacheStats: { hits: observationCache.getStats().totalHits, misses: observationCache.getStats().totalMisses },
|
|
3088
|
+
slaHistory: [],
|
|
3089
|
+
pollInterval: adaptivePollController.intervalMs,
|
|
3090
|
+
});
|
|
3091
|
+
saveCheckpoint(cp);
|
|
3092
|
+
audit("daemon_stop", "daemon stopped, checkpoint saved");
|
|
3093
|
+
}
|
|
3094
|
+
catch { /* best-effort */ }
|
|
2931
3095
|
cleanupState();
|
|
2932
3096
|
process.exit(0);
|
|
2933
3097
|
});
|
|
@@ -4006,6 +4170,7 @@ async function main() {
|
|
|
4006
4170
|
escalationManager,
|
|
4007
4171
|
graduationManager,
|
|
4008
4172
|
approvalQueue,
|
|
4173
|
+
tokenQuotaManager,
|
|
4009
4174
|
pushSupervisorEvent,
|
|
4010
4175
|
refreshTaskSupervisorState,
|
|
4011
4176
|
});
|
|
@@ -4111,6 +4276,45 @@ async function main() {
|
|
|
4111
4276
|
activeWorkflow = null;
|
|
4112
4277
|
}
|
|
4113
4278
|
}
|
|
4279
|
+
// workflow chain: advance cross-workflow dependencies
|
|
4280
|
+
if (activeWorkflowChain && tui) {
|
|
4281
|
+
const chain = activeWorkflowChain;
|
|
4282
|
+
const wfStates = new Map();
|
|
4283
|
+
const { activate, completed: chainDone, failed: chainFailed } = advanceChain(chain, wfStates);
|
|
4284
|
+
for (const name of activate) {
|
|
4285
|
+
tui.log("status", `workflow-chain: activating workflow "${name}"`);
|
|
4286
|
+
audit("task_created", `chain activated: ${name}`, name);
|
|
4287
|
+
}
|
|
4288
|
+
if (chainDone) {
|
|
4289
|
+
tui.log("+ action", `workflow chain "${chain.name}" completed`);
|
|
4290
|
+
activeWorkflowChain = null;
|
|
4291
|
+
}
|
|
4292
|
+
if (chainFailed) {
|
|
4293
|
+
tui.log("status", `workflow chain "${chain.name}" has failures`);
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
// custom alert rules: evaluate fleet conditions per tick
|
|
4297
|
+
if (tui) {
|
|
4298
|
+
const sessions = tui.getSessions();
|
|
4299
|
+
const tasks = taskManager?.tasks ?? [];
|
|
4300
|
+
const scores = sessions.map((s) => s.status === "working" || s.status === "running" ? 80 : s.status === "error" ? 20 : 50);
|
|
4301
|
+
const fleetHealth = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
|
|
4302
|
+
const activeSessions = sessions.filter((s) => s.status === "working" || s.status === "running").length;
|
|
4303
|
+
const errorSessions = sessions.filter((s) => s.status === "error").length;
|
|
4304
|
+
const stuckSessions = tasks.filter((t) => t.status === "active" && (t.stuckNudgeCount ?? 0) > 0).length;
|
|
4305
|
+
let hourlyCost = 0;
|
|
4306
|
+
for (const s of sessions) {
|
|
4307
|
+
const m = s.costStr?.match(/\$(\d+(?:\.\d+)?)/);
|
|
4308
|
+
if (m)
|
|
4309
|
+
hourlyCost += parseFloat(m[1]);
|
|
4310
|
+
}
|
|
4311
|
+
const alertCtx = { fleetHealth, activeSessions, errorSessions, totalCostUsd: hourlyCost, hourlyCostRate: hourlyCost, stuckSessions, idleMinutes: new Map() };
|
|
4312
|
+
const firedAlerts = evaluateAlertRules(alertRules, alertCtx);
|
|
4313
|
+
for (const alert of firedAlerts) {
|
|
4314
|
+
tui.log("status", `${alert.severity === "critical" ? "🚨" : alert.severity === "warning" ? "⚠" : "ℹ"} ALERT: ${alert.message}`);
|
|
4315
|
+
audit("session_error", `alert fired: ${alert.ruleName} — ${alert.message}`, undefined, { severity: alert.severity });
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4114
4318
|
// trust ladder: record stable tick or failure, sync mode if escalated
|
|
4115
4319
|
if (tui && decisionsThisTick > 0) {
|
|
4116
4320
|
if (actionsFail > 0) {
|
|
@@ -4248,6 +4452,14 @@ async function daemonTick(config, poller, reasoner, executor, reasonerConsole, p
|
|
|
4248
4452
|
init: () => reasoner.init(),
|
|
4249
4453
|
shutdown: () => reasoner.shutdown(),
|
|
4250
4454
|
decide: async (obs) => {
|
|
4455
|
+
// ── gate 0: per-model token quota — block if model quota exceeded ──
|
|
4456
|
+
if (intelligence?.tokenQuotaManager.isBlocked(config.reasoner)) {
|
|
4457
|
+
const status = intelligence.tokenQuotaManager.getStatus(config.reasoner);
|
|
4458
|
+
if (tui)
|
|
4459
|
+
tui.log("status", `⏸ token quota exceeded for ${config.reasoner}: ${status.reason}`);
|
|
4460
|
+
audit("reasoner_action", `token quota blocked: ${config.reasoner} — ${status.reason}`);
|
|
4461
|
+
return { actions: [{ action: "wait", reason: `token quota: ${status.reason}` }] };
|
|
4462
|
+
}
|
|
4251
4463
|
// ── gate 1: fleet rate limiter — block if over API spend limits ──
|
|
4252
4464
|
if (intelligence?.fleetRateLimiter.isBlocked()) {
|
|
4253
4465
|
const status = intelligence.fleetRateLimiter.getStatus();
|
|
@@ -4326,6 +4538,8 @@ async function daemonTick(config, poller, reasoner, executor, reasonerConsole, p
|
|
|
4326
4538
|
intelligence.reasonerCostTracker.recordCall("fleet", tokenEstimate, outputEstimate, reasonerDurationMs);
|
|
4327
4539
|
intelligence.fleetRateLimiter.recordCost(estimateCallCost(tokenEstimate, outputEstimate));
|
|
4328
4540
|
intelligence.observationCache.set(obsJson, r);
|
|
4541
|
+
// per-model token quota tracking
|
|
4542
|
+
intelligence.tokenQuotaManager.recordUsage(config.reasoner, tokenEstimate, outputEstimate);
|
|
4329
4543
|
// approval workflow: gate risky/low-confidence actions through approval queue
|
|
4330
4544
|
if (config.confirm || r.confidence === "low") {
|
|
4331
4545
|
const { immediate, queued } = filterThroughApproval(r, intelligence.approvalQueue);
|
package/dist/input.d.ts
CHANGED
|
@@ -134,6 +134,17 @@ 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;
|
|
137
148
|
export interface MouseEvent {
|
|
138
149
|
button: number;
|
|
139
150
|
col: number;
|
|
@@ -452,6 +463,28 @@ export declare class InputReader {
|
|
|
452
463
|
onService(handler: ServiceHandler): void;
|
|
453
464
|
onSessionReplay(handler: SessionReplayHandler): void;
|
|
454
465
|
onWorkflow(handler: WorkflowHandler): void;
|
|
466
|
+
private multiReasonerHandler;
|
|
467
|
+
private tokenQuotaHandler;
|
|
468
|
+
private checkpointHandler;
|
|
469
|
+
private workflowNewHandler;
|
|
470
|
+
onMultiReasoner(handler: MultiReasonerHandler): void;
|
|
471
|
+
onTokenQuota(handler: TokenQuotaHandler): void;
|
|
472
|
+
onCheckpoint(handler: CheckpointHandler): void;
|
|
473
|
+
onWorkflowNew(handler: WorkflowNewHandler): void;
|
|
474
|
+
private abStatsHandler;
|
|
475
|
+
private workflowChainHandler;
|
|
476
|
+
private workflowForecastHandler;
|
|
477
|
+
onABStats(handler: ABStatsHandler): void;
|
|
478
|
+
onWorkflowChain(handler: WorkflowChainHandler): void;
|
|
479
|
+
onWorkflowForecast(handler: WorkflowForecastHandler): void;
|
|
480
|
+
private federationHandler;
|
|
481
|
+
private archivesHandler;
|
|
482
|
+
private runbookGenHandler;
|
|
483
|
+
private alertRulesHandler;
|
|
484
|
+
onFederation(handler: FederationHandler): void;
|
|
485
|
+
onArchives(handler: ArchivesHandler): void;
|
|
486
|
+
onRunbookGen(handler: RunbookGenHandler): void;
|
|
487
|
+
onAlertRules(handler: AlertRulesHandler): void;
|
|
455
488
|
onFleetSearch(handler: FleetSearchHandler): void;
|
|
456
489
|
onNudgeStats(handler: NudgeStatsHandler): void;
|
|
457
490
|
onAllocation(handler: AllocationHandler): void;
|