refactor(desktop): split observer lifecycle command to sibling module

The runtime lifecycle scope-match fix pushed runtime_commands.rs past the
1000-line file-size ratchet. Rather than trim the file to the exact ceiling
again, move the cohesive observer-lifecycle unit (observer_lifecycle_key
plus put_managed_agent_runtime_lifecycle[_for]) into a sibling
runtime_commands_observer.rs, #[path]-included like the existing seams
module. Mechanical move only — no behavior change.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
Duncan
2026-08-17 15:29:50 -04:00
co-authored by Will Pfleger
parent 0e77706443
commit 072b08ef6f
2 changed files with 93 additions and 74 deletions
@@ -79,80 +79,17 @@ fn emit_status<R: tauri::Runtime>(app: &tauri::AppHandle<R>, status: &ManagedAge
let _ = app.emit(STATUS_EVENT, status);
}
fn observer_lifecycle_key(
outer_pubkey: &str,
payload: &super::ManagedAgentRuntimeLifecycleObserverPayload,
) -> Result<ManagedAgentRuntimeKey, String> {
if !outer_pubkey.eq_ignore_ascii_case(&payload.pubkey) {
return Err("observer signer does not match lifecycle payload pubkey".into());
}
if matches!(
payload.lifecycle,
ManagedAgentRuntimeLifecycle::Starting | ManagedAgentRuntimeLifecycle::Stopped
) {
return Err("observer cannot author starting or stopped lifecycle".into());
}
if payload.lifecycle == ManagedAgentRuntimeLifecycle::Failed && payload.error.is_none() {
return Err("failed lifecycle requires an error".into());
}
if payload.lifecycle != ManagedAgentRuntimeLifecycle::Failed && payload.error.is_some() {
return Err("lifecycle error is only valid for failed".into());
}
ManagedAgentRuntimeKey::new(payload.pubkey.clone(), &payload.relay_url)
}
#[tauri::command]
pub fn put_managed_agent_runtime_lifecycle(
outer_pubkey: String,
payload: super::ManagedAgentRuntimeLifecycleObserverPayload,
app: AppHandle,
) -> Result<ManagedAgentRuntimeStatus, String> {
put_managed_agent_runtime_lifecycle_for(outer_pubkey, payload, &app)
}
/// Runtime-generic core so the sibling capability check is testable under `MockRuntime` (§3.3a / §7).
pub(crate) fn put_managed_agent_runtime_lifecycle_for<R: tauri::Runtime>(
outer_pubkey: String,
payload: super::ManagedAgentRuntimeLifecycleObserverPayload,
app: &tauri::AppHandle<R>,
) -> Result<ManagedAgentRuntimeStatus, String> {
let key = observer_lifecycle_key(&outer_pubkey, &payload)?;
let state = app.state::<AppState>();
let records = load_managed_agents(app)?;
let record = records
.iter()
.find(|record| record.pubkey.eq_ignore_ascii_case(&key.pubkey))
.ok_or_else(|| format!("agent {} not found", key.pubkey))?;
// Capture the active scope BEFORE taking the runtime lock so a concurrent
// workspace switch cannot slip a stale-scope frame past the check.
let current_scope_id = state.capture_active_scope().map(|scope| scope.scope_id);
let mut runtimes = state
.managed_agent_processes
.lock()
.map_err(|e| e.to_string())?;
let runtime = runtimes
.get_mut(&key)
.ok_or_else(|| "lifecycle frame does not match a tracked runtime pair".to_string())?;
if runtime.start_nonce != payload.start_nonce {
return Err("lifecycle frame does not match the current harness generation".into());
}
if runtime.scope_id != current_scope_id {
return Err("lifecycle frame does not match the current workspace scope".into());
}
let exited = runtime
.child
.try_wait()
.map_err(|e| e.to_string())?
.is_some();
if exited {
return Err("lifecycle frame arrived after process exit".into());
}
runtime.lifecycle = payload.lifecycle;
runtime.error = payload.error;
let status = status_for(app, record, &key, Some(runtime), None);
emit_status(app, &status);
Ok(status)
}
// Observer-authored runtime-lifecycle capability command (§3.3a): extracted to
// keep this file under the 1000-line size ratchet.
#[path = "runtime_commands_observer.rs"]
mod observer;
pub use observer::put_managed_agent_runtime_lifecycle;
// Re-exported for the capability and unit test suites only; production code
// reaches the core through the `#[tauri::command]` wrapper inside `observer`.
#[cfg(test)]
use observer::observer_lifecycle_key;
#[cfg(test)]
pub(crate) use observer::put_managed_agent_runtime_lifecycle_for;
#[tauri::command]
pub fn list_managed_agent_runtimes(
@@ -0,0 +1,82 @@
//! Observer-authored runtime-lifecycle capability command (§3.3a).
//!
//! Extracted here to keep `runtime_commands.rs` under the 1000-line size
//! ratchet; `#[path]`-included from there. Behavior is identical to the inline
//! definition — this is a mechanical move.
use super::*;
pub(super) fn observer_lifecycle_key(
outer_pubkey: &str,
payload: &crate::managed_agents::ManagedAgentRuntimeLifecycleObserverPayload,
) -> Result<ManagedAgentRuntimeKey, String> {
if !outer_pubkey.eq_ignore_ascii_case(&payload.pubkey) {
return Err("observer signer does not match lifecycle payload pubkey".into());
}
if matches!(
payload.lifecycle,
ManagedAgentRuntimeLifecycle::Starting | ManagedAgentRuntimeLifecycle::Stopped
) {
return Err("observer cannot author starting or stopped lifecycle".into());
}
if payload.lifecycle == ManagedAgentRuntimeLifecycle::Failed && payload.error.is_none() {
return Err("failed lifecycle requires an error".into());
}
if payload.lifecycle != ManagedAgentRuntimeLifecycle::Failed && payload.error.is_some() {
return Err("lifecycle error is only valid for failed".into());
}
ManagedAgentRuntimeKey::new(payload.pubkey.clone(), &payload.relay_url)
}
#[tauri::command]
pub fn put_managed_agent_runtime_lifecycle(
outer_pubkey: String,
payload: crate::managed_agents::ManagedAgentRuntimeLifecycleObserverPayload,
app: AppHandle,
) -> Result<ManagedAgentRuntimeStatus, String> {
put_managed_agent_runtime_lifecycle_for(outer_pubkey, payload, &app)
}
/// Runtime-generic core so the sibling capability check is testable under `MockRuntime` (§3.3a / §7).
pub(crate) fn put_managed_agent_runtime_lifecycle_for<R: tauri::Runtime>(
outer_pubkey: String,
payload: crate::managed_agents::ManagedAgentRuntimeLifecycleObserverPayload,
app: &tauri::AppHandle<R>,
) -> Result<ManagedAgentRuntimeStatus, String> {
let key = observer_lifecycle_key(&outer_pubkey, &payload)?;
let state = app.state::<AppState>();
let records = load_managed_agents(app)?;
let record = records
.iter()
.find(|record| record.pubkey.eq_ignore_ascii_case(&key.pubkey))
.ok_or_else(|| format!("agent {} not found", key.pubkey))?;
// Capture the active scope BEFORE taking the runtime lock so a concurrent
// workspace switch cannot slip a stale-scope frame past the check.
let current_scope_id = state.capture_active_scope().map(|scope| scope.scope_id);
let mut runtimes = state
.managed_agent_processes
.lock()
.map_err(|e| e.to_string())?;
let runtime = runtimes
.get_mut(&key)
.ok_or_else(|| "lifecycle frame does not match a tracked runtime pair".to_string())?;
if runtime.start_nonce != payload.start_nonce {
return Err("lifecycle frame does not match the current harness generation".into());
}
if runtime.scope_id != current_scope_id {
return Err("lifecycle frame does not match the current workspace scope".into());
}
let exited = runtime
.child
.try_wait()
.map_err(|e| e.to_string())?
.is_some();
if exited {
return Err("lifecycle frame arrived after process exit".into());
}
runtime.lifecycle = payload.lifecycle;
runtime.error = payload.error;
let status = status_for(app, record, &key, Some(runtime), None);
emit_status(app, &status);
Ok(status)
}