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/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