aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/agent_resume.rs52
-rw-r--r--src/app/agents.rs5
-rw-r--r--src/app/mod.rs16
-rw-r--r--src/terminal/state.rs34
4 files changed, 103 insertions, 4 deletions
diff --git a/src/agent_resume.rs b/src/agent_resume.rs
index cd1ae7bb..075c396f 100644
--- a/src/agent_resume.rs
+++ b/src/agent_resume.rs
@@ -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(
@@ -283,6 +301,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");
let omp_session = absolute_test_path("omp-session.jsonl");
diff --git a/src/app/agents.rs b/src/app/agents.rs
index f25383fe..5033505d 100644
--- a/src/app/agents.rs
+++ b/src/app/agents.rs
@@ -160,6 +160,8 @@ impl App {
{
return Err(AgentStartError::InvalidArgument);
}
+ let persisted_agent_session =
+ crate::agent_resume::persisted_session_from_launch_args(kind, &params.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();
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 131b9729..02175481 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -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")
@@ -2805,6 +2808,15 @@ mod tests {
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")
);
diff --git a/src/terminal/state.rs b/src/terminal/state.rs
index 5c659eb9..ee170198 100644
--- a/src/terminal/state.rs
+++ b/src/terminal/state.rs
@@ -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]