turn: unify run_turn / compact_session via TurnFiles

new TurnFiles bundle (mcp_config + settings + system_prompt +
flavor) materialised once per harness boot, passed to drive_turn
and compact_session alike. operator-initiated /compact now uses
the exact same session shape as a normal turn — same MCP
surface, same allowed tools, same role prompt — only the stdin
payload differs (/compact vs the wake-up body). web_ui's
AppState carries the TurnFiles instead of (label + socket +
flavor + ad-hoc file writes per click). bin/hive-ag3nt and
bin/hive-m1nd prepare TurnFiles before spawning the web UI and
pass them to both surfaces. web_ui::Flavor folds into a type
alias for mcp::Flavor — no two-stage enum mapping.

removes ClaudeMode + the run_claude variant fork (system prompt
was Option, mcp args were skipped on Compact). dead 'mode'
plumbing gone.
This commit is contained in:
müde 2026-05-16 00:57:58 +02:00
parent 87c7b05b05
commit d94712bde8
4 changed files with 109 additions and 163 deletions

View file

@ -56,24 +56,16 @@ async fn main() -> Result<()> {
let initial = LoginState::from_dir(&claude_dir);
tracing::info!(state = ?initial, claude_dir = %claude_dir.display(), "harness boot");
let login_state = Arc::new(Mutex::new(initial));
let ui_state = login_state.clone();
let bus = Bus::new();
let ui_bus = bus.clone();
let ui_socket = cli.socket.clone();
tokio::spawn(async move {
if let Err(e) = web_ui::serve(
label,
port,
ui_state,
ui_bus,
ui_socket,
web_ui::Flavor::Agent,
)
.await
{
tracing::error!(error = ?e, "web ui failed");
}
});
let files = turn::TurnFiles::prepare(&cli.socket, &label, mcp::Flavor::Agent).await?;
tokio::spawn(web_ui::serve(
label,
port,
login_state.clone(),
bus.clone(),
cli.socket.clone(),
files.clone(),
));
match initial {
LoginState::Online => {
serve(
@ -81,6 +73,7 @@ async fn main() -> Result<()> {
Duration::from_millis(poll_ms),
login_state,
bus,
&files,
)
.await
}
@ -94,6 +87,7 @@ async fn main() -> Result<()> {
Duration::from_millis(poll_ms),
login_state,
bus,
&files,
)
.await
}
@ -108,13 +102,10 @@ async fn serve(
interval: Duration,
state: Arc<Mutex<LoginState>>,
bus: Bus,
files: &turn::TurnFiles,
) -> Result<()> {
tracing::info!(socket = %socket.display(), "hive-ag3nt serve");
let _ = state; // reserved for future state transitions (turn-loop -> needs-login)
let mcp_config = turn::write_mcp_config(socket).await?;
let settings = turn::write_settings(socket).await?;
let label = std::env::var("HIVE_LABEL").unwrap_or_else(|_| "hive-ag3nt".into());
let system_prompt = turn::write_system_prompt(socket, &label, mcp::Flavor::Agent).await?;
loop {
let recv: Result<AgentResponse> =
client::request(socket, &AgentRequest::Recv { wait_seconds: None }).await;
@ -129,15 +120,7 @@ async fn serve(
});
bus.set_state(TurnState::Thinking);
let prompt = format_wake_prompt(&from, &body, unread);
let outcome = turn::drive_turn(
&prompt,
&mcp_config,
&system_prompt,
&settings,
&bus,
mcp::Flavor::Agent,
)
.await;
let outcome = turn::drive_turn(&prompt, files, &bus).await;
turn::emit_turn_end(&bus, &outcome);
bus.set_state(TurnState::Idle);
}

View file

@ -59,29 +59,23 @@ async fn main() -> Result<()> {
let initial = LoginState::from_dir(&claude_dir);
tracing::info!(state = ?initial, claude_dir = %claude_dir.display(), "hm1nd boot");
let login_state = Arc::new(Mutex::new(initial));
let ui_state = login_state.clone();
let bus = Bus::new();
let ui_bus = bus.clone();
let ui_socket = cli.socket.clone();
tokio::spawn(async move {
if let Err(e) = web_ui::serve(
label,
port,
ui_state,
ui_bus,
ui_socket,
web_ui::Flavor::Manager,
)
.await
{
tracing::error!(error = ?e, "web ui failed");
}
});
let files = turn::TurnFiles::prepare(&cli.socket, &label, mcp::Flavor::Manager).await?;
tokio::spawn(web_ui::serve(
label,
port,
login_state.clone(),
bus.clone(),
cli.socket.clone(),
files.clone(),
));
match initial {
LoginState::Online => serve(&cli.socket, Duration::from_millis(poll_ms), bus).await,
LoginState::Online => {
serve(&cli.socket, Duration::from_millis(poll_ms), bus, &files).await
}
LoginState::NeedsLogin => {
turn::wait_for_login(&claude_dir, login_state, poll_ms).await;
serve(&cli.socket, Duration::from_millis(poll_ms), bus).await
serve(&cli.socket, Duration::from_millis(poll_ms), bus, &files).await
}
}
}
@ -89,12 +83,13 @@ async fn main() -> Result<()> {
}
}
async fn serve(socket: &Path, interval: Duration, bus: Bus) -> Result<()> {
async fn serve(
socket: &Path,
interval: Duration,
bus: Bus,
files: &turn::TurnFiles,
) -> Result<()> {
tracing::info!(socket = %socket.display(), "hive-m1nd serve");
let mcp_config = turn::write_mcp_config(socket).await?;
let settings = turn::write_settings(socket).await?;
let label = std::env::var("HIVE_LABEL").unwrap_or_else(|_| "hm1nd".into());
let system_prompt = turn::write_system_prompt(socket, &label, mcp::Flavor::Manager).await?;
loop {
let recv: Result<ManagerResponse> =
client::request(socket, &ManagerRequest::Recv { wait_seconds: None }).await;
@ -126,15 +121,7 @@ async fn serve(socket: &Path, interval: Duration, bus: Bus) -> Result<()> {
});
let prompt = format_wake_prompt(&from, &body, unread);
bus.set_state(TurnState::Thinking);
let outcome = turn::drive_turn(
&prompt,
&mcp_config,
&system_prompt,
&settings,
&bus,
mcp::Flavor::Manager,
)
.await;
let outcome = turn::drive_turn(&prompt, files, &bus).await;
turn::emit_turn_end(&bus, &outcome);
bus.set_state(TurnState::Idle);
}