storkit: merge 355_story_bot_rebuild_command_to_trigger_server_rebuild_and_restart

This commit is contained in:
Dave
2026-03-20 15:27:36 +00:00
parent cb663b620b
commit 0cb43a4de4
7 changed files with 297 additions and 91 deletions

View File

@@ -1,4 +1,4 @@
use crate::agents::{AgentStatus, move_story_to_stage};
use crate::agents::move_story_to_stage;
use crate::http::context::AppContext;
use crate::log_buffer;
use crate::slog;
@@ -26,98 +26,11 @@ pub(super) fn tool_get_server_logs(args: &Value) -> Result<String, String> {
Ok(all_lines[start..].join("\n"))
}
/// Rebuild the server binary and re-exec.
///
/// 1. Gracefully stops all running agents (kills PTY children).
/// 2. Runs `cargo build [-p storkit]` from the workspace root, matching
/// the current build profile (debug or release).
/// 3. If the build fails, returns the build error (server stays up).
/// 4. If the build succeeds, re-execs the process with the new binary via
/// `std::os::unix::process::CommandExt::exec()`.
/// Rebuild the server binary and re-exec (delegates to `crate::rebuild`).
pub(super) async fn tool_rebuild_and_restart(ctx: &AppContext) -> Result<String, String> {
slog!("[rebuild] Rebuild and restart requested via MCP tool");
// 1. Gracefully stop all running agents.
let running_agents = ctx.agents.list_agents().unwrap_or_default();
let running_count = running_agents
.iter()
.filter(|a| a.status == AgentStatus::Running)
.count();
if running_count > 0 {
slog!("[rebuild] Stopping {running_count} running agent(s) before rebuild");
}
ctx.agents.kill_all_children();
// 2. Find the workspace root (parent of the server binary's source).
// CARGO_MANIFEST_DIR at compile time points to the `server/` crate;
// the workspace root is its parent.
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let workspace_root = manifest_dir
.parent()
.ok_or_else(|| "Cannot determine workspace root from CARGO_MANIFEST_DIR".to_string())?;
slog!(
"[rebuild] Building server from workspace root: {}",
workspace_root.display()
);
// 3. Build the server binary, matching the current build profile so the
// re-exec via current_exe() picks up the new binary.
let build_args: Vec<&str> = if cfg!(debug_assertions) {
vec!["build", "-p", "storkit"]
} else {
vec!["build", "--release", "-p", "storkit"]
};
slog!("[rebuild] cargo {}", build_args.join(" "));
let output = tokio::task::spawn_blocking({
let workspace_root = workspace_root.to_path_buf();
move || {
std::process::Command::new("cargo")
.args(&build_args)
.current_dir(&workspace_root)
.output()
}
})
.await
.map_err(|e| format!("Build task panicked: {e}"))?
.map_err(|e| format!("Failed to run cargo build: {e}"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
slog!("[rebuild] Build failed:\n{stderr}");
return Err(format!("Build failed:\n{stderr}"));
}
slog!("[rebuild] Build succeeded, re-execing with new binary");
// 4. Re-exec with the new binary.
// Collect current argv so we preserve any CLI arguments (e.g. project path).
let current_exe =
std::env::current_exe().map_err(|e| format!("Cannot determine current executable: {e}"))?;
let args: Vec<String> = std::env::args().collect();
// Remove the port file before re-exec so the new process can write its own.
if let Ok(root) = ctx.state.get_project_root() {
let port_file = root.join(".storkit_port");
if port_file.exists() {
let _ = std::fs::remove_file(&port_file);
}
}
// Also check cwd for port file.
let cwd_port_file = std::path::Path::new(".storkit_port");
if cwd_port_file.exists() {
let _ = std::fs::remove_file(cwd_port_file);
}
// Use exec() to replace the current process.
// This never returns on success.
use std::os::unix::process::CommandExt;
let err = std::process::Command::new(&current_exe)
.args(&args[1..])
.exec();
// If we get here, exec() failed.
Err(format!("Failed to exec new binary: {err}"))
let project_root = ctx.state.get_project_root().unwrap_or_default();
crate::rebuild::rebuild_and_restart(&ctx.agents, &project_root).await
}
/// Generate a Claude Code permission rule string for the given tool name and input.