swactor/crates/runtime-dashboard/src/investigate.rs
Developer 3a88294696 feat: command interface — frontend-agnostic dispatch crate
Add swactor-command crate that decouples command dispatch from the
dashboard, enabling the same commands from REPL, REST, TUI, or any
future frontend.

- CommandRouter with dispatch(), CommandHandler trait, CommandMeta
- CommandRequest/CommandResponse (serde-serializable JSON protocol)
- CommandContext with Arc<Runtime> + optional StatsEnricher trait
- 9 built-in commands extracted from investigate.rs:
  help, overview, workers, worker, actors, actor, hot, phases, diff,
  shutdown
- REPL line parser (parse_line) with positional arg mapping
- REST adapter (from_query_params) for HTTP query parameters
- Dashboard integration: StatsEnricher impl for StatsCollector,
  investigate.rs refactored to thin CommandRouter wrapper,
  server.rs updated to share CommandRouter across handler threads
- 18 behavioral tests in crates/command/tests/command_api.rs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 18:03:30 +00:00

71 lines
2.1 KiB
Rust

//! Line-oriented diagnostic protocol for LLM-driven runtime investigation.
//!
//! Delegates all command logic to the `swactor-command` crate.
//!
//! Send text commands on stdin, receive JSON responses on stdout (one per line).
//! All human-readable diagnostics go to stderr.
//!
//! # Protocol
//!
//! ```text
//! → overview
//! ← {"ok":true,"command":"overview","data":{"workers":4,"actors":120,...}}
//!
//! → hot 5
//! ← {"ok":true,"command":"hot","data":[{"address":"a1b2...","worker_id":2,"mailbox_depth":47},..]}
//!
//! → diff 2
//! ← {"ok":true,"command":"diff","data":{"elapsed_s":2.0,"delta_messages":8432,"msg_per_sec":4216.0,...}}
//! ```
use std::collections::HashMap;
use std::io::{self, BufRead, Write};
use std::sync::Arc;
use swactor::runtime::Runtime;
use swactor_command::{CommandContext, CommandRouter};
use crate::collector::StatsCollector;
/// Run the investigate REPL. Blocks until stdin is closed or `quit` is received.
pub fn run_investigate(runtime: Arc<Runtime>, collector: Arc<StatsCollector>) -> io::Result<()> {
let router = CommandRouter::with_builtins();
let ctx = CommandContext::with_enricher(runtime, collector);
let stdin = io::stdin();
let mut stdout = io::stdout();
eprintln!("swactor investigate protocol ready — send `help` for commands");
for line in stdin.lock().lines() {
let line = line?;
let line = line.trim();
if line.is_empty() {
continue;
}
if line == "quit" || line == "exit" {
break;
}
let req = swactor_command::parse_line(line);
let resp = router.dispatch(&req, &ctx);
stdout.write_all(resp.to_json_line().as_bytes())?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
Ok(())
}
/// Dispatch an investigate command from HTTP query parameters.
///
/// Maps `?cmd=overview`, `?cmd=hot&n=10`, etc. to the appropriate command.
pub fn dispatch_command(
params: &HashMap<String, String>,
router: &CommandRouter,
ctx: &CommandContext,
) -> String {
let req = swactor_command::from_query_params(params);
router.dispatch(&req, ctx).to_json_line()
}