mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-12 19:14:51 +08:00
feat(runtime): add session health probe for dead-session detection (ROADMAP #38)
Implements ROADMAP #38: Dead-session opacity detection via health canary. - Add run_session_health_probe() to ConversationRuntime - Probe runs after compaction to verify tool executor responsiveness - Add last_health_check_ms field to Session for tracking - Returns structured error if session appears broken after compaction Ultraclaw droid session: ultraclaw-02-session-health Tests: runtime crate 436 passed, integration 12 passed
This commit is contained in:
@@ -292,6 +292,24 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run a session health probe to verify the runtime is functional after compaction.
|
||||||
|
/// Returns Ok(()) if healthy, Err if the session appears broken.
|
||||||
|
fn run_session_health_probe(&mut self) -> Result<(), String> {
|
||||||
|
// Check if we have basic session integrity
|
||||||
|
if self.session.messages.is_empty() && self.session.compaction.is_some() {
|
||||||
|
// Freshly compacted with no messages - this is normal
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify tool executor is responsive with a non-destructive probe
|
||||||
|
// Using glob_search with a pattern that won't match anything
|
||||||
|
let probe_input = r#"{"pattern": "*.health-check-probe-"}"#;
|
||||||
|
match self.tool_executor.execute("glob_search", probe_input) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(format!("Tool executor probe failed: {e}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn run_turn(
|
pub fn run_turn(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -299,6 +317,18 @@ where
|
|||||||
mut prompter: Option<&mut dyn PermissionPrompter>,
|
mut prompter: Option<&mut dyn PermissionPrompter>,
|
||||||
) -> Result<TurnSummary, RuntimeError> {
|
) -> Result<TurnSummary, RuntimeError> {
|
||||||
let user_input = user_input.into();
|
let user_input = user_input.into();
|
||||||
|
|
||||||
|
// ROADMAP #38: Session-health canary - probe if context was compacted
|
||||||
|
if self.session.compaction.is_some() {
|
||||||
|
if let Err(error) = self.run_session_health_probe() {
|
||||||
|
return Err(RuntimeError::new(format!(
|
||||||
|
"Session health probe failed after compaction: {error}. \
|
||||||
|
The session may be in an inconsistent state. \
|
||||||
|
Consider starting a fresh session with /session new."
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.record_turn_started(&user_input);
|
self.record_turn_started(&user_input);
|
||||||
self.session
|
self.session
|
||||||
.push_user_text(user_input)
|
.push_user_text(user_input)
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ pub struct Session {
|
|||||||
pub prompt_history: Vec<SessionPromptEntry>,
|
pub prompt_history: Vec<SessionPromptEntry>,
|
||||||
/// The model used in this session, persisted so resumed sessions can
|
/// The model used in this session, persisted so resumed sessions can
|
||||||
/// report which model was originally used.
|
/// report which model was originally used.
|
||||||
|
/// Timestamp of last successful health check (ROADMAP #38)
|
||||||
|
pub last_health_check_ms: Option<u64>,
|
||||||
pub model: Option<String>,
|
pub model: Option<String>,
|
||||||
persistence: Option<SessionPersistence>,
|
persistence: Option<SessionPersistence>,
|
||||||
}
|
}
|
||||||
@@ -113,6 +115,7 @@ impl PartialEq for Session {
|
|||||||
&& self.fork == other.fork
|
&& self.fork == other.fork
|
||||||
&& self.workspace_root == other.workspace_root
|
&& self.workspace_root == other.workspace_root
|
||||||
&& self.prompt_history == other.prompt_history
|
&& self.prompt_history == other.prompt_history
|
||||||
|
&& self.last_health_check_ms == other.last_health_check_ms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +167,7 @@ impl Session {
|
|||||||
fork: None,
|
fork: None,
|
||||||
workspace_root: None,
|
workspace_root: None,
|
||||||
prompt_history: Vec::new(),
|
prompt_history: Vec::new(),
|
||||||
|
last_health_check_ms: None,
|
||||||
model: None,
|
model: None,
|
||||||
persistence: None,
|
persistence: None,
|
||||||
}
|
}
|
||||||
@@ -267,6 +271,7 @@ impl Session {
|
|||||||
}),
|
}),
|
||||||
workspace_root: self.workspace_root.clone(),
|
workspace_root: self.workspace_root.clone(),
|
||||||
prompt_history: self.prompt_history.clone(),
|
prompt_history: self.prompt_history.clone(),
|
||||||
|
last_health_check_ms: self.last_health_check_ms,
|
||||||
model: self.model.clone(),
|
model: self.model.clone(),
|
||||||
persistence: None,
|
persistence: None,
|
||||||
}
|
}
|
||||||
@@ -390,6 +395,7 @@ impl Session {
|
|||||||
fork,
|
fork,
|
||||||
workspace_root,
|
workspace_root,
|
||||||
prompt_history,
|
prompt_history,
|
||||||
|
last_health_check_ms: None,
|
||||||
model,
|
model,
|
||||||
persistence: None,
|
persistence: None,
|
||||||
})
|
})
|
||||||
@@ -490,6 +496,7 @@ impl Session {
|
|||||||
fork,
|
fork,
|
||||||
workspace_root,
|
workspace_root,
|
||||||
prompt_history,
|
prompt_history,
|
||||||
|
last_health_check_ms: None,
|
||||||
model,
|
model,
|
||||||
persistence: None,
|
persistence: None,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user