Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 50 additions & 6 deletions crates/runner-app/src/bootstrap.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::{Arc, RwLock};

use anyhow::{bail, Context as _, Result};
use runner_backend::{db, event_bus, events, mcp, repo, session, shell_path, windows, AppCore};
use runner_backend::{
db, event_bus, events, mcp, repo, runtime_status, session, shell_path, windows, AppCore,
};

pub const APP_IDENTIFIER: &str = "com.wycstudios.runner";

Expand Down Expand Up @@ -50,29 +52,62 @@ pub fn boot_core(paths: &NativePaths) -> Result<AppCore> {
let pool = Arc::new(
db::open_pool(&paths.app_data_dir.join("runner.db")).context("open Runner database")?,
);
let login_shell_env = shell_path::resolve_login_shell_env().env;
let login_shell_lkg = match db::login_shell_env_lkg(&pool) {
Ok(snapshot) => snapshot,
Err(error) => {
eprintln!("runtime discovery LKG read failed: {error}");
None
}
};
let runtime_shell_env = Arc::new(RwLock::new(
login_shell_lkg
.as_ref()
.map(|snapshot| snapshot.env.clone())
.unwrap_or_default(),
));
let runtime_discovery = Arc::new(RwLock::new(shell_path::DiscoveryState::startup(
login_shell_lkg
.as_ref()
.map(|snapshot| snapshot.shell.clone()),
login_shell_lkg
.as_ref()
.map(|snapshot| snapshot.captured_at.clone()),
)));
let runtime: Arc<dyn session::runtime::SessionRuntime> =
Arc::new(session::pty_runtime::PtyRuntime::new());
let sessions = session::SessionManager::new(login_shell_env, runtime);
let sessions = session::SessionManager::new(
Arc::clone(&runtime_shell_env),
Arc::clone(&runtime_discovery),
runtime,
);
let window_registry = Arc::new(windows::WindowRegistry::new());
window_registry.register("main");
let event_channel = events::EventChannel::new();

let core = AppCore {
db: Arc::clone(&pool),
app_data_dir: paths.app_data_dir.clone(),
sessions,
runtime_shell_env: Arc::clone(&runtime_shell_env),
runtime_discovery: Arc::clone(&runtime_discovery),
buses: event_bus::BusRegistry::new(),
routers: runner_backend::router::RouterRegistry::new(),
mcp: Arc::new(mcp::McpHandle::new()),
windows: window_registry,
events: events::EventChannel::new(),
events: event_channel.clone(),
app_version: env!("CARGO_PKG_VERSION").to_string(),
};

session::pty_runtime::cleanup_stale_running_rows_on_startup(&pool)
.context("clean up stale PTY sessions")?;
session::pty_runtime::cleanup_orphan_processes_on_startup(&pool)
.context("clean up orphan PTY processes")?;
runtime_status::start_background_discovery(
event_channel,
Arc::clone(&pool),
runtime_shell_env,
runtime_discovery,
);
Ok(core)
}

Expand Down Expand Up @@ -143,10 +178,19 @@ mod tests {
}
let runtime: Arc<dyn session::runtime::SessionRuntime> =
Arc::new(session::pty_runtime::PtyRuntime::new());
let runtime_shell_env = Arc::new(RwLock::new(shell_path::LoginShellEnv::default()));
let runtime_discovery =
Arc::new(RwLock::new(shell_path::DiscoveryState::startup(None, None)));
let core = AppCore {
db: Arc::clone(&pool),
app_data_dir: PathBuf::new(),
sessions: session::SessionManager::new(shell_path::LoginShellEnv::default(), runtime),
sessions: session::SessionManager::new(
Arc::clone(&runtime_shell_env),
Arc::clone(&runtime_discovery),
runtime,
),
runtime_shell_env,
runtime_discovery,
buses: event_bus::BusRegistry::new(),
routers: runner_backend::router::RouterRegistry::new(),
mcp: Arc::new(mcp::McpHandle::new()),
Expand Down
2 changes: 2 additions & 0 deletions crates/runner-app/src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,8 @@ impl NativeRoot {
None,
None,
None,
None,
None,
Some(initial_size.0),
Some(initial_size.1),
)?;
Expand Down
8 changes: 8 additions & 0 deletions crates/runner-app/tests/session_manager_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ fn direct_chat_flows_from_app_core_session_manager_into_terminal_grid() {
None,
None,
None,
None,
None,
Some(80),
Some(24),
)
Expand Down Expand Up @@ -104,6 +106,8 @@ fn terminal_ime_commit_forwards_utf8_through_session_manager() {
None,
None,
None,
None,
None,
Some(80),
Some(24),
)
Expand Down Expand Up @@ -157,6 +161,8 @@ fn bridge_keeps_multiple_tab_sessions_attached_with_independent_geometry() {
None,
None,
None,
None,
None,
Some(80),
Some(24),
)
Expand All @@ -167,6 +173,8 @@ fn bridge_keeps_multiple_tab_sessions_attached_with_independent_geometry() {
None,
None,
None,
None,
None,
Some(120),
Some(40),
)
Expand Down
2 changes: 1 addition & 1 deletion crates/runner-backend/src/cli_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const AGENT_DEST_BIN_NAME: &str = if cfg!(windows) {
"runner"
};

/// Name of the MCP proxy binary registered with Claude Code / Codex.
/// Name of the MCP proxy binary registered with Claude Code, Codex, Qoder, and TRAE.
pub const MCP_DEST_BIN_NAME: &str = if cfg!(windows) {
"runner-mcp.exe"
} else {
Expand Down
6 changes: 6 additions & 0 deletions crates/runner-backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod model;
pub mod ops;
pub mod repo;
pub mod router;
pub mod runtime_status;
pub mod session;
pub mod shell_path;
pub mod windows;
Expand All @@ -41,6 +42,11 @@ pub struct AppCore {
/// start, shared across all frontends and the per-session
/// forwarder threads it spawns.
pub sessions: Arc<session::SessionManager>,
/// Swappable login-shell snapshot shared by runtime discovery and
/// every spawn path. Successful background probes replace it in place.
pub runtime_shell_env: runtime_status::SharedShellEnv,
/// Current login-shell probe state and diagnostics for agent availability.
pub runtime_discovery: runtime_status::SharedDiscoveryState,
/// Live per-mission event-bus watchers. Mounted by `mission_start` once
/// the opening events are durable; unmounted by `mission_stop` and on
/// any rollback path.
Expand Down
14 changes: 12 additions & 2 deletions crates/runner-backend/src/mcp/tools/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@ use crate::ops::session;
pub struct StartDirectSessionArgs {
/// Runner template ID.
pub runner_id: String,
/// Optional runtime override (registry name, e.g. "codex" or
/// "claude-code"). Omit to use the runner's own runtime. When it
/// Optional runtime override (registry name, e.g. "codex",
/// "claude-code", "qoder", or "trae"). Omit to use the runner's own runtime. When it
/// differs, the chat spawns that engine with registry defaults
/// while the runner's persona (system prompt, working dir, env)
/// carries over.
#[serde(default)]
pub runtime: Option<String>,
/// Optional model for the overridden runtime. Only meaningful with
/// `runtime`; omit for the engine's own default.
#[serde(default)]
pub model: Option<String>,
/// Optional reasoning-effort for the overridden runtime. Only
/// meaningful with `runtime`; omit for the engine's own default.
#[serde(default)]
pub effort: Option<String>,
/// Optional project membership. Its cwd is used when cwd is omitted.
#[serde(default)]
pub project_id: Option<String>,
Expand Down Expand Up @@ -48,6 +56,8 @@ impl RunnerMcpHandler {
&app_state,
args.runner_id,
args.runtime,
args.model,
args.effort,
args.project_id,
args.cwd,
None,
Expand Down
5 changes: 4 additions & 1 deletion crates/runner-backend/src/mcp/tools/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,16 @@ impl RunnerMcpHandler {
&input.runner_id,
&input.slot_handle,
input.runtime_override.as_deref(),
input.model_override.as_deref(),
)
.map_err(|e| ErrorData::internal_error(e.to_string(), None))?;
self.state.events.emit("slot/changed", &());
Ok(CallToolResult::success(vec![Content::json(&slot)?]))
}

#[tool(description = "Update a slot by ID. Omitted fields are preserved.")]
#[tool(
description = "Update a slot by ID, including independent runtime, model, and effort overrides. Omitted fields are preserved."
)]
pub async fn slot_update(
&self,
Parameters(UpdateSlotArgs { slot_id, input }): Parameters<UpdateSlotArgs>,
Expand Down
Loading