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:
parent
87c7b05b05
commit
d94712bde8
4 changed files with 109 additions and 163 deletions
|
|
@ -28,6 +28,8 @@ use crate::client;
|
|||
use crate::events::Bus;
|
||||
use crate::login::LoginState;
|
||||
use crate::login_session::{LoginSession, drop_if_finished};
|
||||
use crate::mcp;
|
||||
use crate::turn::TurnFiles;
|
||||
|
||||
/// Live login state for the web UI. The harness updates this in place as it
|
||||
/// transitions between `NeedsLogin` and `Online`; the UI reads on each
|
||||
|
|
@ -41,16 +43,25 @@ struct AppState {
|
|||
session: Arc<Mutex<Option<Arc<LoginSession>>>>,
|
||||
bus: Bus,
|
||||
socket: PathBuf,
|
||||
flavor: Flavor,
|
||||
/// Same `TurnFiles` the harness's turn loop uses. Shared so
|
||||
/// `/api/compact` re-uses the exact MCP config / system prompt /
|
||||
/// settings claude saw on the last regular turn — keeps the
|
||||
/// session shape identical across compact + normal turns.
|
||||
files: TurnFiles,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
fn flavor(&self) -> Flavor {
|
||||
self.files.flavor
|
||||
}
|
||||
}
|
||||
|
||||
/// Which wire protocol the per-agent UI's `/send` handler should speak.
|
||||
/// Sub-agent → `AgentRequest::OperatorMsg`; manager → `ManagerRequest::OperatorMsg`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Flavor {
|
||||
Agent,
|
||||
Manager,
|
||||
}
|
||||
/// Sub-agent → `AgentRequest::OperatorMsg`; manager →
|
||||
/// `ManagerRequest::OperatorMsg`. Reuses the MCP-side enum so a
|
||||
/// single value drives both the send protocol and (in
|
||||
/// `post_compact`) the allowed-tools surface claude sees.
|
||||
pub type Flavor = mcp::Flavor;
|
||||
|
||||
pub async fn serve(
|
||||
label: String,
|
||||
|
|
@ -58,7 +69,7 @@ pub async fn serve(
|
|||
login: LoginStateCell,
|
||||
bus: Bus,
|
||||
socket: PathBuf,
|
||||
flavor: Flavor,
|
||||
files: TurnFiles,
|
||||
) -> Result<()> {
|
||||
let state = AppState {
|
||||
label,
|
||||
|
|
@ -66,7 +77,7 @@ pub async fn serve(
|
|||
session: Arc::new(Mutex::new(None)),
|
||||
bus,
|
||||
socket,
|
||||
flavor,
|
||||
files,
|
||||
};
|
||||
let app = Router::new()
|
||||
.route("/", get(serve_index))
|
||||
|
|
@ -208,7 +219,7 @@ async fn api_state(State(state): State<AppState>) -> axum::Json<StateSnapshot> {
|
|||
.ok()
|
||||
.and_then(|s| s.parse::<u16>().ok())
|
||||
.unwrap_or(7000);
|
||||
let inbox = recent_inbox(&state.socket, state.flavor).await;
|
||||
let inbox = recent_inbox(&state.socket, state.flavor()).await;
|
||||
let (turn_state, turn_state_since) = state.bus.state_snapshot();
|
||||
let model = state.bus.model();
|
||||
axum::Json(StateSnapshot {
|
||||
|
|
@ -268,7 +279,7 @@ async fn post_send(State(state): State<AppState>, Form(form): Form<SendForm>) ->
|
|||
if body.is_empty() {
|
||||
return error_response("send: `body` required");
|
||||
}
|
||||
let result = match state.flavor {
|
||||
let result = match state.flavor() {
|
||||
Flavor::Agent => match client::request::<_, hive_sh4re::AgentResponse>(
|
||||
&state.socket,
|
||||
&hive_sh4re::AgentRequest::OperatorMsg { body },
|
||||
|
|
@ -396,22 +407,13 @@ async fn post_set_model(State(state): State<AppState>, Form(form): Form<ModelFor
|
|||
|
||||
async fn post_compact(State(state): State<AppState>) -> Response {
|
||||
let bus = state.bus.clone();
|
||||
let socket = state.socket.clone();
|
||||
let files = state.files.clone();
|
||||
tokio::spawn(async move {
|
||||
bus.emit(crate::events::LiveEvent::Note(
|
||||
"operator: /compact — running on persistent session".into(),
|
||||
));
|
||||
let settings = match crate::turn::write_settings(&socket).await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
bus.emit(crate::events::LiveEvent::Note(format!(
|
||||
"/compact failed: settings write — {e:#}"
|
||||
)));
|
||||
return;
|
||||
}
|
||||
};
|
||||
bus.set_state(crate::events::TurnState::Compacting);
|
||||
let r = crate::turn::compact_session(&settings, &bus).await;
|
||||
let r = crate::turn::compact_session(&files, &bus).await;
|
||||
bus.set_state(crate::events::TurnState::Idle);
|
||||
if let Err(e) = r {
|
||||
bus.emit(crate::events::LiveEvent::Note(format!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue