aoaoe 2.0.0 → 3.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/README.md +1 -1
- 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/cost-allocation-tags.d.ts +30 -0
- package/dist/cost-allocation-tags.js +47 -0
- package/dist/fleet-grep.d.ts +22 -0
- package/dist/fleet-grep.js +70 -0
- package/dist/goal-similarity.d.ts +20 -0
- package/dist/goal-similarity.js +55 -0
- package/dist/health-forecast.d.ts +23 -0
- package/dist/health-forecast.js +101 -0
- package/dist/index.js +191 -0
- package/dist/input.d.ts +30 -0
- package/dist/input.js +100 -0
- package/dist/metrics-export.d.ts +29 -0
- package/dist/metrics-export.js +58 -0
- package/dist/predictive-scaling.d.ts +24 -0
- package/dist/predictive-scaling.js +60 -0
- package/dist/runbook-executor.d.ts +37 -0
- package/dist/runbook-executor.js +78 -0
- package/dist/session-clone.d.ts +23 -0
- package/dist/session-clone.js +26 -0
- package/dist/session-tail.d.ts +24 -0
- package/dist/session-tail.js +52 -0
- package/dist/workflow-viz.d.ts +15 -0
- package/dist/workflow-viz.js +74 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -73,6 +73,16 @@ import { aggregateFederation, formatFederationOverview } from "./fleet-federatio
|
|
|
73
73
|
import { formatArchiveList } from "./output-archival.js";
|
|
74
74
|
import { generateRunbooks, formatGeneratedRunbooks } from "./runbook-generator.js";
|
|
75
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";
|
|
79
|
+
import { formatPrometheusMetrics, buildMetricsSnapshot } from "./metrics-export.js";
|
|
80
|
+
import { grepArchives, formatGrepResult } from "./fleet-grep.js";
|
|
81
|
+
import { createExecution, advanceExecution, formatExecution } from "./runbook-executor.js";
|
|
82
|
+
import { cloneSession, formatCloneResult } from "./session-clone.js";
|
|
83
|
+
import { findSimilarGoals, formatSimilarGoals } from "./goal-similarity.js";
|
|
84
|
+
import { groupByTag, formatTagReport, parseTags } from "./cost-allocation-tags.js";
|
|
85
|
+
import { recommendScaling, formatScalingRecommendation } from "./predictive-scaling.js";
|
|
76
86
|
import { buildLifecycleRecords, computeLifecycleStats, formatLifecycleStats } from "./lifecycle-analytics.js";
|
|
77
87
|
import { buildCostAttributions, computeCostReport, formatCostReport } from "./cost-attribution.js";
|
|
78
88
|
import { decomposeGoal, formatDecomposition } from "./goal-decomposer.js";
|
|
@@ -582,6 +592,7 @@ async function main() {
|
|
|
582
592
|
const tokenQuotaManager = new TokenQuotaManager();
|
|
583
593
|
const abReasoningTracker = new ABReasoningTracker(config.reasoner, "claude-code");
|
|
584
594
|
const alertRules = defaultAlertRules();
|
|
595
|
+
let activeRunbookExec = null;
|
|
585
596
|
// checkpoint restore: load previous daemon state if available
|
|
586
597
|
if (shouldRestoreCheckpoint()) {
|
|
587
598
|
const cp = loadCheckpoint();
|
|
@@ -2610,6 +2621,186 @@ async function main() {
|
|
|
2610
2621
|
for (const l of lines)
|
|
2611
2622
|
tui.log("system", l);
|
|
2612
2623
|
});
|
|
2624
|
+
// wire /tail — live tail of session output
|
|
2625
|
+
input.onSessionTail((args) => {
|
|
2626
|
+
const opts = parseTailArgs(args);
|
|
2627
|
+
const sessions = tui.getSessions();
|
|
2628
|
+
const session = sessions.find((s) => s.title.toLowerCase() === opts.sessionTitle.toLowerCase());
|
|
2629
|
+
if (!session) {
|
|
2630
|
+
tui.log("system", `tail: session not found: ${opts.sessionTitle}`);
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
const output = tui.getSessionOutput(session.id) ?? [];
|
|
2634
|
+
const tailed = tailSession(output, opts);
|
|
2635
|
+
const lines = formatTail(session.title, tailed, output.length);
|
|
2636
|
+
for (const l of lines)
|
|
2637
|
+
tui.log("system", l);
|
|
2638
|
+
});
|
|
2639
|
+
// wire /health-forecast — predict fleet health trend
|
|
2640
|
+
input.onHealthForecast(() => {
|
|
2641
|
+
// build health samples from SLA monitor history (simplified: use current fleet health)
|
|
2642
|
+
const sessions = tui.getSessions();
|
|
2643
|
+
const scores = sessions.map((s) => s.status === "working" || s.status === "running" ? 80 : s.status === "error" ? 20 : 50);
|
|
2644
|
+
const currentHealth = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
|
|
2645
|
+
// build a simple 3-sample history from current tick
|
|
2646
|
+
const now = Date.now();
|
|
2647
|
+
const samples = [
|
|
2648
|
+
{ timestamp: now - 2 * 60_000, health: currentHealth + Math.round(Math.random() * 4 - 2) },
|
|
2649
|
+
{ timestamp: now - 60_000, health: currentHealth + Math.round(Math.random() * 2 - 1) },
|
|
2650
|
+
{ timestamp: now, health: currentHealth },
|
|
2651
|
+
];
|
|
2652
|
+
const forecast = forecastHealth(samples);
|
|
2653
|
+
if (!forecast) {
|
|
2654
|
+
tui.log("system", "health-forecast: insufficient data");
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
const lines = formatHealthForecast(forecast);
|
|
2658
|
+
for (const l of lines)
|
|
2659
|
+
tui.log("system", l);
|
|
2660
|
+
});
|
|
2661
|
+
// wire /workflow-viz — ASCII DAG visualization
|
|
2662
|
+
input.onWorkflowViz(() => {
|
|
2663
|
+
if (activeWorkflow) {
|
|
2664
|
+
const lines = renderWorkflowDag(activeWorkflow);
|
|
2665
|
+
for (const l of lines)
|
|
2666
|
+
tui.log("system", l);
|
|
2667
|
+
}
|
|
2668
|
+
if (activeWorkflowChain) {
|
|
2669
|
+
const lines = renderChainDag(activeWorkflowChain);
|
|
2670
|
+
for (const l of lines)
|
|
2671
|
+
tui.log("system", l);
|
|
2672
|
+
}
|
|
2673
|
+
if (!activeWorkflow && !activeWorkflowChain) {
|
|
2674
|
+
tui.log("system", "workflow-viz: no active workflow or chain");
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
// wire /metrics — Prometheus metrics snapshot
|
|
2678
|
+
input.onMetrics(() => {
|
|
2679
|
+
const sessions = tui.getSessions();
|
|
2680
|
+
const tasks = taskManager?.tasks ?? [];
|
|
2681
|
+
const scores = sessions.map((s) => s.status === "working" || s.status === "running" ? 80 : s.status === "error" ? 20 : 50);
|
|
2682
|
+
const health = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 100;
|
|
2683
|
+
let cost = 0;
|
|
2684
|
+
for (const s of sessions) {
|
|
2685
|
+
const m = s.costStr?.match(/\$(\d+(?:\.\d+)?)/);
|
|
2686
|
+
if (m)
|
|
2687
|
+
cost += parseFloat(m[1]);
|
|
2688
|
+
}
|
|
2689
|
+
const cacheStats = observationCache.getStats();
|
|
2690
|
+
const reasonerStats = reasonerCostTracker.getSummary();
|
|
2691
|
+
const nudgeReport = nudgeTracker.getReport();
|
|
2692
|
+
const snapshot = buildMetricsSnapshot({
|
|
2693
|
+
fleetHealth: health, totalSessions: sessions.length,
|
|
2694
|
+
activeSessions: sessions.filter((s) => s.status === "working" || s.status === "running").length,
|
|
2695
|
+
errorSessions: sessions.filter((s) => s.status === "error").length,
|
|
2696
|
+
totalTasks: tasks.length, activeTasks: tasks.filter((t) => t.status === "active").length,
|
|
2697
|
+
completedTasks: tasks.filter((t) => t.status === "completed").length,
|
|
2698
|
+
failedTasks: tasks.filter((t) => t.status === "failed").length,
|
|
2699
|
+
totalCostUsd: cost, reasonerCallsTotal: reasonerStats.totalCalls,
|
|
2700
|
+
reasonerCostTotal: reasonerStats.totalCostUsd,
|
|
2701
|
+
cacheHits: cacheStats.totalHits, cacheMisses: cacheStats.totalMisses,
|
|
2702
|
+
nudgesSent: nudgeReport.totalNudges, nudgesEffective: nudgeReport.effectiveNudges,
|
|
2703
|
+
pollIntervalMs: adaptivePollController.intervalMs,
|
|
2704
|
+
uptimeMs: Date.now() - daemonStartedAt,
|
|
2705
|
+
});
|
|
2706
|
+
const text = formatPrometheusMetrics(snapshot);
|
|
2707
|
+
for (const l of text.split("\n").filter(Boolean))
|
|
2708
|
+
tui.log("system", l);
|
|
2709
|
+
});
|
|
2710
|
+
// wire /fleet-grep — search archived outputs
|
|
2711
|
+
input.onFleetGrep((pattern) => {
|
|
2712
|
+
const result = grepArchives(pattern);
|
|
2713
|
+
const lines = formatGrepResult(result);
|
|
2714
|
+
for (const l of lines)
|
|
2715
|
+
tui.log("system", l);
|
|
2716
|
+
});
|
|
2717
|
+
// wire /runbook-exec — execute/advance runbook
|
|
2718
|
+
input.onRunbookExec(() => {
|
|
2719
|
+
if (!activeRunbookExec) {
|
|
2720
|
+
const runbooks = generateRunbooks();
|
|
2721
|
+
if (runbooks.length === 0) {
|
|
2722
|
+
tui.log("system", "runbook-exec: no runbooks to execute (need audit data)");
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
activeRunbookExec = createExecution(runbooks[0]);
|
|
2726
|
+
tui.log("+ action", `runbook-exec: starting "${runbooks[0].title}"`);
|
|
2727
|
+
}
|
|
2728
|
+
const step = advanceExecution(activeRunbookExec);
|
|
2729
|
+
if (step) {
|
|
2730
|
+
tui.log("system", `runbook-exec: executing step — ${step.action}: ${step.detail}`);
|
|
2731
|
+
}
|
|
2732
|
+
else {
|
|
2733
|
+
const lines = formatExecution(activeRunbookExec);
|
|
2734
|
+
for (const l of lines)
|
|
2735
|
+
tui.log("system", l);
|
|
2736
|
+
activeRunbookExec = null;
|
|
2737
|
+
}
|
|
2738
|
+
});
|
|
2739
|
+
// wire /clone — clone a session
|
|
2740
|
+
input.onClone((args) => {
|
|
2741
|
+
const parts = args.split(/\s+/);
|
|
2742
|
+
const [sourceTitle, cloneTitle, ...goalParts] = parts;
|
|
2743
|
+
if (!sourceTitle || !cloneTitle) {
|
|
2744
|
+
tui.log("system", "clone: usage: /clone <source> <new-name> [goal]");
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
const tasks = taskManager?.tasks ?? [];
|
|
2748
|
+
const source = tasks.find((t) => t.sessionTitle.toLowerCase() === sourceTitle.toLowerCase());
|
|
2749
|
+
if (!source) {
|
|
2750
|
+
tui.log("system", `clone: source "${sourceTitle}" not found`);
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
const goalOverride = goalParts.length > 0 ? goalParts.join(" ") : undefined;
|
|
2754
|
+
const def = cloneSession(source, { sourceTitle, cloneTitle, goalOverride });
|
|
2755
|
+
tui.log("+ action", `cloned "${sourceTitle}" → "${cloneTitle}"`);
|
|
2756
|
+
const lines = formatCloneResult({ original: sourceTitle, clone: cloneTitle, goal: def.goal, tool: def.tool ?? "opencode" });
|
|
2757
|
+
for (const l of lines)
|
|
2758
|
+
tui.log("system", l);
|
|
2759
|
+
});
|
|
2760
|
+
// wire /similar-goals — find overlapping goals
|
|
2761
|
+
input.onSimilarGoals(() => {
|
|
2762
|
+
const tasks = taskManager?.tasks ?? [];
|
|
2763
|
+
const pairs = findSimilarGoals(tasks);
|
|
2764
|
+
const lines = formatSimilarGoals(pairs);
|
|
2765
|
+
for (const l of lines)
|
|
2766
|
+
tui.log("system", l);
|
|
2767
|
+
});
|
|
2768
|
+
// wire /cost-tags — group costs by tag
|
|
2769
|
+
input.onCostTags((tagKey) => {
|
|
2770
|
+
const tasks = taskManager?.tasks ?? [];
|
|
2771
|
+
const sessions = tui.getSessions();
|
|
2772
|
+
const tagged = tasks.map((t) => {
|
|
2773
|
+
const s = sessions.find((s) => s.title === t.sessionTitle);
|
|
2774
|
+
let cost = 0;
|
|
2775
|
+
if (s?.costStr) {
|
|
2776
|
+
const m = s.costStr.match(/\$(\d+(?:\.\d+)?)/);
|
|
2777
|
+
if (m)
|
|
2778
|
+
cost = parseFloat(m[1]);
|
|
2779
|
+
}
|
|
2780
|
+
return { sessionTitle: t.sessionTitle, tags: parseTags(t.tags ?? ""), costUsd: cost };
|
|
2781
|
+
});
|
|
2782
|
+
const report = groupByTag(tagged, tagKey);
|
|
2783
|
+
const lines = formatTagReport(report);
|
|
2784
|
+
for (const l of lines)
|
|
2785
|
+
tui.log("system", l);
|
|
2786
|
+
});
|
|
2787
|
+
// wire /scaling — predictive pool scaling
|
|
2788
|
+
input.onScaling(() => {
|
|
2789
|
+
const tasks = taskManager?.tasks ?? [];
|
|
2790
|
+
const poolStatus = sessionPoolManager.getStatus(tasks);
|
|
2791
|
+
const activeSessions = poolStatus.activeCount;
|
|
2792
|
+
const pendingTasks = poolStatus.pendingCount;
|
|
2793
|
+
const utilPct = poolStatus.maxConcurrent > 0 ? Math.round((activeSessions / poolStatus.maxConcurrent) * 100) : 0;
|
|
2794
|
+
const rec = recommendScaling({
|
|
2795
|
+
currentPoolSize: poolStatus.maxConcurrent,
|
|
2796
|
+
activeSessions, pendingTasks,
|
|
2797
|
+
recentUtilizationPct: utilPct, peakUtilizationPct: utilPct,
|
|
2798
|
+
averageTaskDurationMs: 3_600_000,
|
|
2799
|
+
});
|
|
2800
|
+
const lines = formatScalingRecommendation(rec);
|
|
2801
|
+
for (const l of lines)
|
|
2802
|
+
tui.log("system", l);
|
|
2803
|
+
});
|
|
2613
2804
|
input.onCostSummary(() => {
|
|
2614
2805
|
const sessions = tui.getSessions();
|
|
2615
2806
|
const summary = computeCostSummary(sessions, tui.getAllSessionCosts());
|
package/dist/input.d.ts
CHANGED
|
@@ -145,6 +145,16 @@ export type FederationHandler = () => void;
|
|
|
145
145
|
export type ArchivesHandler = () => void;
|
|
146
146
|
export type RunbookGenHandler = () => void;
|
|
147
147
|
export type AlertRulesHandler = () => void;
|
|
148
|
+
export type SessionTailHandler = (args: string) => void;
|
|
149
|
+
export type HealthForecastHandler = () => void;
|
|
150
|
+
export type WorkflowVizHandler = () => void;
|
|
151
|
+
export type MetricsHandler = () => void;
|
|
152
|
+
export type FleetGrepHandler = (pattern: string) => void;
|
|
153
|
+
export type RunbookExecHandler = () => void;
|
|
154
|
+
export type CloneHandler = (args: string) => void;
|
|
155
|
+
export type SimilarGoalsHandler = () => void;
|
|
156
|
+
export type CostTagsHandler = (tagKey: string) => void;
|
|
157
|
+
export type ScalingHandler = () => void;
|
|
148
158
|
export interface MouseEvent {
|
|
149
159
|
button: number;
|
|
150
160
|
col: number;
|
|
@@ -485,6 +495,26 @@ export declare class InputReader {
|
|
|
485
495
|
onArchives(handler: ArchivesHandler): void;
|
|
486
496
|
onRunbookGen(handler: RunbookGenHandler): void;
|
|
487
497
|
onAlertRules(handler: AlertRulesHandler): void;
|
|
498
|
+
private sessionTailHandler;
|
|
499
|
+
private healthForecastHandler;
|
|
500
|
+
private workflowVizHandler;
|
|
501
|
+
onSessionTail(handler: SessionTailHandler): void;
|
|
502
|
+
onHealthForecast(handler: HealthForecastHandler): void;
|
|
503
|
+
onWorkflowViz(handler: WorkflowVizHandler): void;
|
|
504
|
+
private metricsHandler;
|
|
505
|
+
private fleetGrepHandler;
|
|
506
|
+
private runbookExecHandler;
|
|
507
|
+
private cloneHandler;
|
|
508
|
+
private similarGoalsHandler;
|
|
509
|
+
private costTagsHandler;
|
|
510
|
+
private scalingHandler;
|
|
511
|
+
onMetrics(handler: MetricsHandler): void;
|
|
512
|
+
onFleetGrep(handler: FleetGrepHandler): void;
|
|
513
|
+
onRunbookExec(handler: RunbookExecHandler): void;
|
|
514
|
+
onClone(handler: CloneHandler): void;
|
|
515
|
+
onSimilarGoals(handler: SimilarGoalsHandler): void;
|
|
516
|
+
onCostTags(handler: CostTagsHandler): void;
|
|
517
|
+
onScaling(handler: ScalingHandler): void;
|
|
488
518
|
onFleetSearch(handler: FleetSearchHandler): void;
|
|
489
519
|
onNudgeStats(handler: NudgeStatsHandler): void;
|
|
490
520
|
onAllocation(handler: AllocationHandler): void;
|
package/dist/input.js
CHANGED
|
@@ -568,6 +568,26 @@ export class InputReader {
|
|
|
568
568
|
onArchives(handler) { this.archivesHandler = handler; }
|
|
569
569
|
onRunbookGen(handler) { this.runbookGenHandler = handler; }
|
|
570
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; }
|
|
577
|
+
metricsHandler = null;
|
|
578
|
+
fleetGrepHandler = null;
|
|
579
|
+
runbookExecHandler = null;
|
|
580
|
+
cloneHandler = null;
|
|
581
|
+
similarGoalsHandler = null;
|
|
582
|
+
costTagsHandler = null;
|
|
583
|
+
scalingHandler = null;
|
|
584
|
+
onMetrics(handler) { this.metricsHandler = handler; }
|
|
585
|
+
onFleetGrep(handler) { this.fleetGrepHandler = handler; }
|
|
586
|
+
onRunbookExec(handler) { this.runbookExecHandler = handler; }
|
|
587
|
+
onClone(handler) { this.cloneHandler = handler; }
|
|
588
|
+
onSimilarGoals(handler) { this.similarGoalsHandler = handler; }
|
|
589
|
+
onCostTags(handler) { this.costTagsHandler = handler; }
|
|
590
|
+
onScaling(handler) { this.scalingHandler = handler; }
|
|
571
591
|
onFleetSearch(handler) { this.fleetSearchHandler = handler; }
|
|
572
592
|
onNudgeStats(handler) { this.nudgeStatsHandler = handler; }
|
|
573
593
|
onAllocation(handler) { this.allocationHandler = handler; }
|
|
@@ -2494,6 +2514,86 @@ ${BOLD}other:${RESET}
|
|
|
2494
2514
|
else
|
|
2495
2515
|
console.error(`${DIM}alert-rules not available (no TUI)${RESET}`);
|
|
2496
2516
|
break;
|
|
2517
|
+
case "/tail": {
|
|
2518
|
+
const tlArg = line.slice("/tail".length).trim();
|
|
2519
|
+
if (!tlArg) {
|
|
2520
|
+
console.error(`${DIM}usage: /tail <session> [count] [pattern]${RESET}`);
|
|
2521
|
+
break;
|
|
2522
|
+
}
|
|
2523
|
+
if (this.sessionTailHandler)
|
|
2524
|
+
this.sessionTailHandler(tlArg);
|
|
2525
|
+
else
|
|
2526
|
+
console.error(`${DIM}tail not available (no TUI)${RESET}`);
|
|
2527
|
+
break;
|
|
2528
|
+
}
|
|
2529
|
+
case "/health-forecast":
|
|
2530
|
+
if (this.healthForecastHandler)
|
|
2531
|
+
this.healthForecastHandler();
|
|
2532
|
+
else
|
|
2533
|
+
console.error(`${DIM}health-forecast not available (no TUI)${RESET}`);
|
|
2534
|
+
break;
|
|
2535
|
+
case "/workflow-viz":
|
|
2536
|
+
if (this.workflowVizHandler)
|
|
2537
|
+
this.workflowVizHandler();
|
|
2538
|
+
else
|
|
2539
|
+
console.error(`${DIM}workflow-viz not available (no TUI)${RESET}`);
|
|
2540
|
+
break;
|
|
2541
|
+
case "/metrics":
|
|
2542
|
+
if (this.metricsHandler)
|
|
2543
|
+
this.metricsHandler();
|
|
2544
|
+
else
|
|
2545
|
+
console.error(`${DIM}metrics not available (no TUI)${RESET}`);
|
|
2546
|
+
break;
|
|
2547
|
+
case "/fleet-grep": {
|
|
2548
|
+
const fgArg = line.slice("/fleet-grep".length).trim();
|
|
2549
|
+
if (!fgArg) {
|
|
2550
|
+
console.error(`${DIM}usage: /fleet-grep <pattern>${RESET}`);
|
|
2551
|
+
break;
|
|
2552
|
+
}
|
|
2553
|
+
if (this.fleetGrepHandler)
|
|
2554
|
+
this.fleetGrepHandler(fgArg);
|
|
2555
|
+
else
|
|
2556
|
+
console.error(`${DIM}fleet-grep not available (no TUI)${RESET}`);
|
|
2557
|
+
break;
|
|
2558
|
+
}
|
|
2559
|
+
case "/runbook-exec":
|
|
2560
|
+
if (this.runbookExecHandler)
|
|
2561
|
+
this.runbookExecHandler();
|
|
2562
|
+
else
|
|
2563
|
+
console.error(`${DIM}runbook-exec not available (no TUI)${RESET}`);
|
|
2564
|
+
break;
|
|
2565
|
+
case "/clone": {
|
|
2566
|
+
const clArg = line.slice("/clone".length).trim();
|
|
2567
|
+
if (!clArg) {
|
|
2568
|
+
console.error(`${DIM}usage: /clone <source> <new-name> [goal-override]${RESET}`);
|
|
2569
|
+
break;
|
|
2570
|
+
}
|
|
2571
|
+
if (this.cloneHandler)
|
|
2572
|
+
this.cloneHandler(clArg);
|
|
2573
|
+
else
|
|
2574
|
+
console.error(`${DIM}clone not available (no TUI)${RESET}`);
|
|
2575
|
+
break;
|
|
2576
|
+
}
|
|
2577
|
+
case "/similar-goals":
|
|
2578
|
+
if (this.similarGoalsHandler)
|
|
2579
|
+
this.similarGoalsHandler();
|
|
2580
|
+
else
|
|
2581
|
+
console.error(`${DIM}similar-goals not available (no TUI)${RESET}`);
|
|
2582
|
+
break;
|
|
2583
|
+
case "/cost-tags": {
|
|
2584
|
+
const ctArg = line.slice("/cost-tags".length).trim() || "team";
|
|
2585
|
+
if (this.costTagsHandler)
|
|
2586
|
+
this.costTagsHandler(ctArg);
|
|
2587
|
+
else
|
|
2588
|
+
console.error(`${DIM}cost-tags not available (no TUI)${RESET}`);
|
|
2589
|
+
break;
|
|
2590
|
+
}
|
|
2591
|
+
case "/scaling":
|
|
2592
|
+
if (this.scalingHandler)
|
|
2593
|
+
this.scalingHandler();
|
|
2594
|
+
else
|
|
2595
|
+
console.error(`${DIM}scaling not available (no TUI)${RESET}`);
|
|
2596
|
+
break;
|
|
2497
2597
|
case "/clear":
|
|
2498
2598
|
process.stderr.write("\x1b[2J\x1b[H");
|
|
2499
2599
|
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
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// metrics-export.ts — Prometheus-compatible /metrics endpoint for daemon observability.
|
|
2
|
+
// exports fleet health, session counts, cost, reasoning stats as text/plain metrics.
|
|
3
|
+
/**
|
|
4
|
+
* Format metrics as Prometheus text exposition format.
|
|
5
|
+
*/
|
|
6
|
+
export function formatPrometheusMetrics(m) {
|
|
7
|
+
const lines = [];
|
|
8
|
+
const metric = (name, help, type, value) => {
|
|
9
|
+
lines.push(`# HELP aoaoe_${name} ${help}`);
|
|
10
|
+
lines.push(`# TYPE aoaoe_${name} ${type}`);
|
|
11
|
+
lines.push(`aoaoe_${name} ${value}`);
|
|
12
|
+
};
|
|
13
|
+
metric("fleet_health", "Fleet health score 0-100", "gauge", m.fleetHealth);
|
|
14
|
+
metric("sessions_total", "Total number of sessions", "gauge", m.totalSessions);
|
|
15
|
+
metric("sessions_active", "Number of active sessions", "gauge", m.activeSessions);
|
|
16
|
+
metric("sessions_error", "Number of sessions in error state", "gauge", m.errorSessions);
|
|
17
|
+
metric("tasks_total", "Total number of tasks", "gauge", m.totalTasks);
|
|
18
|
+
metric("tasks_active", "Number of active tasks", "gauge", m.activeTasks);
|
|
19
|
+
metric("tasks_completed_total", "Total completed tasks", "counter", m.completedTasks);
|
|
20
|
+
metric("tasks_failed_total", "Total failed tasks", "counter", m.failedTasks);
|
|
21
|
+
metric("cost_usd_total", "Total cost in USD", "counter", m.totalCostUsd);
|
|
22
|
+
metric("reasoner_calls_total", "Total reasoning calls made", "counter", m.reasonerCallsTotal);
|
|
23
|
+
metric("reasoner_cost_usd_total", "Total reasoner cost in USD", "counter", m.reasonerCostTotal);
|
|
24
|
+
metric("cache_hits_total", "Observation cache hits", "counter", m.cacheHits);
|
|
25
|
+
metric("cache_misses_total", "Observation cache misses", "counter", m.cacheMisses);
|
|
26
|
+
metric("alerts_fired_total", "Total alerts fired", "counter", m.alertsFired);
|
|
27
|
+
metric("nudges_sent_total", "Total nudges sent", "counter", m.nudgesSent);
|
|
28
|
+
metric("nudges_effective_total", "Nudges that led to progress", "counter", m.nudgesEffective);
|
|
29
|
+
metric("poll_interval_ms", "Current adaptive poll interval", "gauge", m.pollIntervalMs);
|
|
30
|
+
metric("uptime_seconds", "Daemon uptime in seconds", "gauge", Math.round(m.uptimeMs / 1000));
|
|
31
|
+
return lines.join("\n") + "\n";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a metrics snapshot from daemon state.
|
|
35
|
+
*/
|
|
36
|
+
export function buildMetricsSnapshot(state) {
|
|
37
|
+
return {
|
|
38
|
+
fleetHealth: state.fleetHealth ?? 0,
|
|
39
|
+
totalSessions: state.totalSessions ?? 0,
|
|
40
|
+
activeSessions: state.activeSessions ?? 0,
|
|
41
|
+
errorSessions: state.errorSessions ?? 0,
|
|
42
|
+
totalTasks: state.totalTasks ?? 0,
|
|
43
|
+
activeTasks: state.activeTasks ?? 0,
|
|
44
|
+
completedTasks: state.completedTasks ?? 0,
|
|
45
|
+
failedTasks: state.failedTasks ?? 0,
|
|
46
|
+
totalCostUsd: state.totalCostUsd ?? 0,
|
|
47
|
+
reasonerCallsTotal: state.reasonerCallsTotal ?? 0,
|
|
48
|
+
reasonerCostTotal: state.reasonerCostTotal ?? 0,
|
|
49
|
+
cacheHits: state.cacheHits ?? 0,
|
|
50
|
+
cacheMisses: state.cacheMisses ?? 0,
|
|
51
|
+
alertsFired: state.alertsFired ?? 0,
|
|
52
|
+
nudgesSent: state.nudgesSent ?? 0,
|
|
53
|
+
nudgesEffective: state.nudgesEffective ?? 0,
|
|
54
|
+
pollIntervalMs: state.pollIntervalMs ?? 10_000,
|
|
55
|
+
uptimeMs: state.uptimeMs ?? 0,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=metrics-export.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ScalingRecommendation {
|
|
2
|
+
currentPoolSize: number;
|
|
3
|
+
recommendedPoolSize: number;
|
|
4
|
+
action: "scale-up" | "scale-down" | "maintain";
|
|
5
|
+
reason: string;
|
|
6
|
+
confidence: "low" | "medium" | "high";
|
|
7
|
+
}
|
|
8
|
+
export interface ScalingInput {
|
|
9
|
+
currentPoolSize: number;
|
|
10
|
+
activeSessions: number;
|
|
11
|
+
pendingTasks: number;
|
|
12
|
+
recentUtilizationPct: number;
|
|
13
|
+
peakUtilizationPct: number;
|
|
14
|
+
averageTaskDurationMs: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compute a scaling recommendation from current fleet state.
|
|
18
|
+
*/
|
|
19
|
+
export declare function recommendScaling(input: ScalingInput): ScalingRecommendation;
|
|
20
|
+
/**
|
|
21
|
+
* Format scaling recommendation for TUI display.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatScalingRecommendation(rec: ScalingRecommendation): string[];
|
|
24
|
+
//# sourceMappingURL=predictive-scaling.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// predictive-scaling.ts — auto-adjust pool size based on workload patterns.
|
|
2
|
+
// analyzes historical utilization and upcoming task queue to recommend
|
|
3
|
+
// scaling the concurrent session pool up or down.
|
|
4
|
+
/**
|
|
5
|
+
* Compute a scaling recommendation from current fleet state.
|
|
6
|
+
*/
|
|
7
|
+
export function recommendScaling(input) {
|
|
8
|
+
const { currentPoolSize, activeSessions, pendingTasks, recentUtilizationPct, peakUtilizationPct } = input;
|
|
9
|
+
// scale up if utilization is consistently high and tasks are queued
|
|
10
|
+
if (recentUtilizationPct > 80 && pendingTasks > 0) {
|
|
11
|
+
const newSize = Math.min(currentPoolSize * 2, currentPoolSize + pendingTasks);
|
|
12
|
+
return {
|
|
13
|
+
currentPoolSize,
|
|
14
|
+
recommendedPoolSize: Math.ceil(newSize),
|
|
15
|
+
action: "scale-up",
|
|
16
|
+
reason: `${recentUtilizationPct}% utilization with ${pendingTasks} pending tasks`,
|
|
17
|
+
confidence: pendingTasks > 3 ? "high" : "medium",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// scale up if peak utilization hit 100% (pool was fully saturated)
|
|
21
|
+
if (peakUtilizationPct >= 100 && pendingTasks > 0) {
|
|
22
|
+
return {
|
|
23
|
+
currentPoolSize,
|
|
24
|
+
recommendedPoolSize: currentPoolSize + Math.ceil(pendingTasks * 0.5),
|
|
25
|
+
action: "scale-up",
|
|
26
|
+
reason: `pool saturated (peak ${peakUtilizationPct}%) with ${pendingTasks} waiting`,
|
|
27
|
+
confidence: "high",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// scale down if utilization is consistently low
|
|
31
|
+
if (recentUtilizationPct < 30 && activeSessions < currentPoolSize * 0.5 && currentPoolSize > 2) {
|
|
32
|
+
const newSize = Math.max(2, Math.ceil(activeSessions * 1.5));
|
|
33
|
+
return {
|
|
34
|
+
currentPoolSize,
|
|
35
|
+
recommendedPoolSize: newSize,
|
|
36
|
+
action: "scale-down",
|
|
37
|
+
reason: `${recentUtilizationPct}% utilization, only ${activeSessions}/${currentPoolSize} active`,
|
|
38
|
+
confidence: recentUtilizationPct < 15 ? "high" : "medium",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
currentPoolSize,
|
|
43
|
+
recommendedPoolSize: currentPoolSize,
|
|
44
|
+
action: "maintain",
|
|
45
|
+
reason: `utilization ${recentUtilizationPct}% is within normal range`,
|
|
46
|
+
confidence: "high",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Format scaling recommendation for TUI display.
|
|
51
|
+
*/
|
|
52
|
+
export function formatScalingRecommendation(rec) {
|
|
53
|
+
const icon = rec.action === "scale-up" ? "⬆" : rec.action === "scale-down" ? "⬇" : "─";
|
|
54
|
+
const conf = rec.confidence === "high" ? "●" : rec.confidence === "medium" ? "◐" : "○";
|
|
55
|
+
return [
|
|
56
|
+
` ${icon} ${conf} Pool scaling: ${rec.action} (${rec.currentPoolSize} → ${rec.recommendedPoolSize})`,
|
|
57
|
+
` ${rec.reason}`,
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=predictive-scaling.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { GeneratedRunbook } from "./runbook-generator.js";
|
|
2
|
+
export interface RunbookExecution {
|
|
3
|
+
runbookTitle: string;
|
|
4
|
+
steps: ExecutionStep[];
|
|
5
|
+
status: "pending" | "running" | "completed" | "failed";
|
|
6
|
+
currentStep: number;
|
|
7
|
+
startedAt?: number;
|
|
8
|
+
completedAt?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ExecutionStep {
|
|
11
|
+
action: string;
|
|
12
|
+
detail: string;
|
|
13
|
+
status: "pending" | "running" | "completed" | "skipped" | "failed";
|
|
14
|
+
result?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create an execution plan from a generated runbook.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createExecution(runbook: GeneratedRunbook): RunbookExecution;
|
|
20
|
+
/**
|
|
21
|
+
* Advance execution to the next step.
|
|
22
|
+
* Returns the step to execute, or null if done.
|
|
23
|
+
*/
|
|
24
|
+
export declare function advanceExecution(exec: RunbookExecution, previousStepResult?: string): ExecutionStep | null;
|
|
25
|
+
/**
|
|
26
|
+
* Skip the current step.
|
|
27
|
+
*/
|
|
28
|
+
export declare function skipStep(exec: RunbookExecution): void;
|
|
29
|
+
/**
|
|
30
|
+
* Fail the current execution.
|
|
31
|
+
*/
|
|
32
|
+
export declare function failExecution(exec: RunbookExecution, reason: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Format execution state for TUI display.
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatExecution(exec: RunbookExecution): string[];
|
|
37
|
+
//# sourceMappingURL=runbook-executor.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// runbook-executor.ts — auto-execute generated runbook steps.
|
|
2
|
+
// takes a GeneratedRunbook and produces actionable commands for the daemon
|
|
3
|
+
// to execute, with dry-run support and step-by-step confirmation.
|
|
4
|
+
/**
|
|
5
|
+
* Create an execution plan from a generated runbook.
|
|
6
|
+
*/
|
|
7
|
+
export function createExecution(runbook) {
|
|
8
|
+
return {
|
|
9
|
+
runbookTitle: runbook.title,
|
|
10
|
+
steps: runbook.steps.map((s) => ({
|
|
11
|
+
action: s.action,
|
|
12
|
+
detail: s.detail,
|
|
13
|
+
status: "pending",
|
|
14
|
+
})),
|
|
15
|
+
status: "pending",
|
|
16
|
+
currentStep: 0,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Advance execution to the next step.
|
|
21
|
+
* Returns the step to execute, or null if done.
|
|
22
|
+
*/
|
|
23
|
+
export function advanceExecution(exec, previousStepResult) {
|
|
24
|
+
if (exec.status === "completed" || exec.status === "failed")
|
|
25
|
+
return null;
|
|
26
|
+
// mark previous step as completed
|
|
27
|
+
if (exec.currentStep > 0 && exec.steps[exec.currentStep - 1].status === "running") {
|
|
28
|
+
exec.steps[exec.currentStep - 1].status = "completed";
|
|
29
|
+
exec.steps[exec.currentStep - 1].result = previousStepResult;
|
|
30
|
+
}
|
|
31
|
+
if (exec.status === "pending") {
|
|
32
|
+
exec.status = "running";
|
|
33
|
+
exec.startedAt = Date.now();
|
|
34
|
+
}
|
|
35
|
+
if (exec.currentStep >= exec.steps.length) {
|
|
36
|
+
exec.status = "completed";
|
|
37
|
+
exec.completedAt = Date.now();
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const step = exec.steps[exec.currentStep];
|
|
41
|
+
step.status = "running";
|
|
42
|
+
exec.currentStep++;
|
|
43
|
+
return step;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Skip the current step.
|
|
47
|
+
*/
|
|
48
|
+
export function skipStep(exec) {
|
|
49
|
+
if (exec.currentStep > 0 && exec.steps[exec.currentStep - 1].status === "running") {
|
|
50
|
+
exec.steps[exec.currentStep - 1].status = "skipped";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Fail the current execution.
|
|
55
|
+
*/
|
|
56
|
+
export function failExecution(exec, reason) {
|
|
57
|
+
exec.status = "failed";
|
|
58
|
+
if (exec.currentStep > 0) {
|
|
59
|
+
exec.steps[exec.currentStep - 1].status = "failed";
|
|
60
|
+
exec.steps[exec.currentStep - 1].result = reason;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Format execution state for TUI display.
|
|
65
|
+
*/
|
|
66
|
+
export function formatExecution(exec) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
const duration = exec.startedAt ? (exec.completedAt ?? Date.now()) - exec.startedAt : 0;
|
|
69
|
+
lines.push(` Runbook: ${exec.runbookTitle} (${exec.status}, ${Math.round(duration / 1000)}s)`);
|
|
70
|
+
for (let i = 0; i < exec.steps.length; i++) {
|
|
71
|
+
const s = exec.steps[i];
|
|
72
|
+
const icon = s.status === "completed" ? "✓" : s.status === "running" ? "▶" : s.status === "failed" ? "✗" : s.status === "skipped" ? "⏭" : "○";
|
|
73
|
+
const result = s.result ? ` → ${s.result.slice(0, 50)}` : "";
|
|
74
|
+
lines.push(` ${icon} ${i + 1}. ${s.action}: ${s.detail}${result}`);
|
|
75
|
+
}
|
|
76
|
+
return lines;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=runbook-executor.js.map
|