mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 16:01:07 +00:00
@@ -69,6 +69,24 @@ pub fn session_ref_from_report(
|
||||
agent_session_id.and_then(AgentSessionRef::id)
|
||||
}
|
||||
|
||||
pub fn persisted_session_from_launch_args(
|
||||
agent: crate::detect::Agent,
|
||||
args: &[String],
|
||||
) -> Option<PersistedAgentSession> {
|
||||
let [command, session_id] = args else {
|
||||
return None;
|
||||
};
|
||||
if agent != crate::detect::Agent::Codex || command != "resume" || session_id.starts_with('-') {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(PersistedAgentSession {
|
||||
source: "herdr:codex".into(),
|
||||
agent: "codex".into(),
|
||||
session_ref: AgentSessionRef::id(session_id.clone())?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn normalize_session_start_source(value: Option<String>) -> Option<String> {
|
||||
match value.as_deref().map(str::trim) {
|
||||
Some(
|
||||
@@ -282,6 +300,40 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_noncanonical_resume_launch_has_no_explicit_session() {
|
||||
assert_eq!(
|
||||
persisted_session_from_launch_args(
|
||||
crate::detect::Agent::Codex,
|
||||
&["resume".into(), "codex-session".into()]
|
||||
)
|
||||
.unwrap()
|
||||
.session_ref
|
||||
.value,
|
||||
"codex-session"
|
||||
);
|
||||
assert!(persisted_session_from_launch_args(
|
||||
crate::detect::Agent::Codex,
|
||||
&["resume".into(), "--last".into()]
|
||||
)
|
||||
.is_none());
|
||||
assert!(persisted_session_from_launch_args(
|
||||
crate::detect::Agent::Codex,
|
||||
&["resume".into(), "not-a-session".into(), "--last".into()]
|
||||
)
|
||||
.is_none());
|
||||
assert!(persisted_session_from_launch_args(
|
||||
crate::detect::Agent::Codex,
|
||||
&[
|
||||
"--remote".into(),
|
||||
"ws://example.test".into(),
|
||||
"resume".into(),
|
||||
"remote-session".into(),
|
||||
]
|
||||
)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn planner_allows_supported_agents() {
|
||||
let pi_session = absolute_test_path("pi-session.jsonl");
|
||||
|
||||
@@ -160,6 +160,8 @@ impl App {
|
||||
{
|
||||
return Err(AgentStartError::InvalidArgument);
|
||||
}
|
||||
let persisted_agent_session =
|
||||
crate::agent_resume::persisted_session_from_launch_args(kind, ¶ms.args);
|
||||
let conflicts = self.agent_name_conflicts(&name, "");
|
||||
if !conflicts.is_empty() {
|
||||
return Err(AgentStartError::DuplicateName {
|
||||
@@ -217,6 +219,9 @@ impl App {
|
||||
terminal.clear_agent_name();
|
||||
return Err(AgentStartError::InputFailed(err.to_string()));
|
||||
}
|
||||
if let Some(session) = persisted_agent_session {
|
||||
terminal.set_managed_agent_launch_session(session);
|
||||
}
|
||||
self.state.mark_session_dirty();
|
||||
self.schedule_session_save();
|
||||
|
||||
|
||||
+14
-2
@@ -2782,9 +2782,9 @@ mod tests {
|
||||
id: "req_agent_start_input".into(),
|
||||
method: crate::api::schema::Method::AgentStart(crate::api::schema::AgentStartParams {
|
||||
name: "worker".into(),
|
||||
kind: "pi".into(),
|
||||
kind: "codex".into(),
|
||||
pane_id: pane_id.clone(),
|
||||
args: Vec::new(),
|
||||
args: vec!["resume".into(), "codex-session".into()],
|
||||
timeout_ms: Some(4_000),
|
||||
}),
|
||||
};
|
||||
@@ -2792,6 +2792,9 @@ mod tests {
|
||||
let response: serde_json::Value = serde_json::from_str(&response).unwrap();
|
||||
assert_eq!(response["error"]["code"], "agent_start_input_failed");
|
||||
assert_eq!(app.state.terminals[&terminal_id].agent_name, None);
|
||||
assert!(app.state.terminals[&terminal_id]
|
||||
.persisted_agent_session
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
app.state.terminals[&terminal_id].manual_label.as_deref(),
|
||||
Some("shell")
|
||||
@@ -2804,6 +2807,15 @@ mod tests {
|
||||
let retry = app.handle_api_request(request());
|
||||
let retry: serde_json::Value = serde_json::from_str(&retry).unwrap();
|
||||
assert_eq!(retry["result"]["type"], "agent_started");
|
||||
assert_eq!(
|
||||
retry["result"]["agent"]["agent_session"],
|
||||
serde_json::json!({
|
||||
"source": "herdr:codex",
|
||||
"agent": "codex",
|
||||
"kind": "id",
|
||||
"value": "codex-session",
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
app.state.terminals[&terminal_id].agent_name.as_deref(),
|
||||
Some("worker")
|
||||
|
||||
+32
-2
@@ -133,6 +133,7 @@ pub struct TerminalState {
|
||||
pub agent_name: Option<String>,
|
||||
agent_name_owner: Option<AgentNameOwner>,
|
||||
managed_agent: Option<ManagedAgent>,
|
||||
managed_agent_launch_session: Option<crate::agent_resume::PersistedAgentSession>,
|
||||
hook_report_sequences: HashMap<String, u64>,
|
||||
suppressed_full_lifecycle_hook_reports: HashMap<String, SuppressedFullLifecycleHookReport>,
|
||||
stale_full_lifecycle_hook_sessions: HashMap<String, Vec<StaleFullLifecycleHookSession>>,
|
||||
@@ -167,6 +168,7 @@ impl TerminalState {
|
||||
agent_name: None,
|
||||
agent_name_owner: None,
|
||||
managed_agent: None,
|
||||
managed_agent_launch_session: None,
|
||||
hook_report_sequences: HashMap::new(),
|
||||
suppressed_full_lifecycle_hook_reports: HashMap::new(),
|
||||
stale_full_lifecycle_hook_sessions: HashMap::new(),
|
||||
@@ -1367,6 +1369,14 @@ impl TerminalState {
|
||||
self.persisted_agent_session = Some(session);
|
||||
}
|
||||
|
||||
pub fn set_managed_agent_launch_session(
|
||||
&mut self,
|
||||
session: crate::agent_resume::PersistedAgentSession,
|
||||
) {
|
||||
self.persisted_agent_session = Some(session.clone());
|
||||
self.managed_agent_launch_session = Some(session);
|
||||
}
|
||||
|
||||
pub fn set_agent_session_ref(
|
||||
&mut self,
|
||||
source: String,
|
||||
@@ -1587,11 +1597,15 @@ impl TerminalState {
|
||||
self.hook_authority = None;
|
||||
}
|
||||
self.reconcile_agent_name_owner(&agent_label, Some(&session_ref));
|
||||
self.persisted_agent_session = Some(crate::agent_resume::PersistedAgentSession {
|
||||
let persisted_session = crate::agent_resume::PersistedAgentSession {
|
||||
source,
|
||||
agent: agent_label,
|
||||
session_ref,
|
||||
});
|
||||
};
|
||||
if self.managed_agent_launch_session.as_ref() == Some(&persisted_session) {
|
||||
self.managed_agent_launch_session = None;
|
||||
}
|
||||
self.persisted_agent_session = Some(persisted_session);
|
||||
let current_session = self.current_session_identity_for_persistence();
|
||||
Some(TerminalStateMutation {
|
||||
effective_state_change: self.recompute_effective_state(
|
||||
@@ -1972,6 +1986,7 @@ impl TerminalState {
|
||||
kind: managed.kind,
|
||||
phase: ManagedAgentPhase::Active,
|
||||
});
|
||||
self.managed_agent_launch_session = None;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1999,6 +2014,7 @@ impl TerminalState {
|
||||
kind: managed.kind,
|
||||
phase: ManagedAgentPhase::Active,
|
||||
});
|
||||
self.managed_agent_launch_session = None;
|
||||
return true;
|
||||
}
|
||||
if ready_after.is_some() {
|
||||
@@ -2041,6 +2057,14 @@ impl TerminalState {
|
||||
}
|
||||
|
||||
pub fn clear_agent_name(&mut self) {
|
||||
if self
|
||||
.managed_agent_launch_session
|
||||
.take()
|
||||
.as_ref()
|
||||
.is_some_and(|session| self.persisted_agent_session.as_ref() == Some(session))
|
||||
{
|
||||
self.persisted_agent_session = None;
|
||||
}
|
||||
self.agent_name = None;
|
||||
self.agent_name_owner = None;
|
||||
self.managed_agent = None;
|
||||
@@ -2269,9 +2293,15 @@ mod tests {
|
||||
Duration::from_millis(10),
|
||||
Duration::from_millis(20),
|
||||
);
|
||||
timed_out.set_managed_agent_launch_session(crate::agent_resume::PersistedAgentSession {
|
||||
source: "herdr:codex".into(),
|
||||
agent: "codex".into(),
|
||||
session_ref: crate::agent_resume::AgentSessionRef::id("codex-session").unwrap(),
|
||||
});
|
||||
assert!(timed_out.reconcile_managed_agent_at(now + Duration::from_millis(20), false));
|
||||
assert_eq!(timed_out.agent_name, None);
|
||||
assert_eq!(timed_out.managed_agent_kind(), None);
|
||||
assert!(timed_out.persisted_agent_session.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user