From 2d8b4e2db4642dede084801c03516046f205626b Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Thu, 28 May 2026 05:06:04 +0300 Subject: [PATCH] feat: expose agent session refs in api --- .../src/content/docs/cli-reference.mdx | 6 ++++- .../website/src/content/docs/socket-api.mdx | 15 +++++++++++ src/api/schema.rs | 14 ++++++++++ src/app/agents.rs | 1 + src/app/creation.rs | 26 +++++++++++++++++++ tests/api_ping.rs | 26 +++++++++++++++++-- 6 files changed, 85 insertions(+), 3 deletions(-) diff --git a/docs/next/website/src/content/docs/cli-reference.mdx b/docs/next/website/src/content/docs/cli-reference.mdx index ed51ce1e..d79e34d0 100644 --- a/docs/next/website/src/content/docs/cli-reference.mdx +++ b/docs/next/website/src/content/docs/cli-reference.mdx @@ -129,9 +129,13 @@ herdr pane report-agent \ --state idle|working|blocked|unknown \ [--message TEXT] \ [--custom-status TEXT] \ - [--seq N] + [--seq N] \ + [--agent-session-id ID] \ + [--agent-session-path PATH] ``` +`pane get`, `pane list`, `agent get`, and `agent list` include a read-only `agent_session` object when an official integration has reported a native session reference. If no native session reference is stored, the field is omitted. + Report display-only pane metadata without taking over semantic state: ```bash diff --git a/docs/next/website/src/content/docs/socket-api.mdx b/docs/next/website/src/content/docs/socket-api.mdx index 3b12d928..382a3c62 100644 --- a/docs/next/website/src/content/docs/socket-api.mdx +++ b/docs/next/website/src/content/docs/socket-api.mdx @@ -163,6 +163,21 @@ Integrations report agent state with `pane.report_agent`. `custom_status` is visual. It can show a short label like `indexing` without changing semantic behavior. +Official integrations can also report a native session reference. `pane.get`, `pane.list`, `agent.get`, and `agent.list` expose a read-only `agent_session` object when Herdr has a stored native session reference: + +```json +{ + "agent_session": { + "source": "herdr:codex", + "agent": "codex", + "kind": "id", + "value": "..." + } +} +``` + +If no native session reference is stored, the field is omitted. + Use `pane.report_metadata` when a user hook wants to customize presentation without taking over lifecycle state from a Herdr integration. ```json diff --git a/src/api/schema.rs b/src/api/schema.rs index aa7cbd3c..cd82f99f 100644 --- a/src/api/schema.rs +++ b/src/api/schema.rs @@ -803,6 +803,8 @@ pub struct AgentInfo { pub custom_status: Option, #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub state_labels: HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub agent_session: Option, pub workspace_id: String, pub tab_id: String, pub pane_id: String, @@ -834,9 +836,19 @@ pub struct PaneInfo { pub custom_status: Option, #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub state_labels: HashMap, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub agent_session: Option, pub revision: u64, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AgentSessionInfo { + pub source: String, + pub agent: String, + pub kind: crate::agent_resume::AgentSessionRefKind, + pub value: String, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PaneReadResult { pub pane_id: String, @@ -1402,6 +1414,7 @@ mod tests { agent_status: AgentStatus::Unknown, custom_status: None, state_labels: HashMap::new(), + agent_session: None, revision: 0, }, worktree: WorktreeInfo { @@ -1451,6 +1464,7 @@ mod tests { agent_status: AgentStatus::Unknown, custom_status: None, state_labels: HashMap::new(), + agent_session: None, revision: 0, }, }, diff --git a/src/app/agents.rs b/src/app/agents.rs index 8868fb9c..55ee15df 100644 --- a/src/app/agents.rs +++ b/src/app/agents.rs @@ -417,6 +417,7 @@ impl App { agent_status: pane.agent_status, custom_status: pane.custom_status, state_labels: pane.state_labels, + agent_session: pane.agent_session, workspace_id: pane.workspace_id, tab_id: pane.tab_id, pane_id: pane.pane_id, diff --git a/src/app/creation.rs b/src/app/creation.rs index aa727dd5..d483eb1a 100644 --- a/src/app/creation.rs +++ b/src/app/creation.rs @@ -295,6 +295,7 @@ impl App { agent_status: pane_agent_status(terminal.state, pane.seen), custom_status: presentation.custom_status, state_labels: presentation.state_labels, + agent_session: terminal_agent_session_info(terminal), revision: terminal.revision, }) } @@ -345,3 +346,28 @@ impl App { } } } + +fn terminal_agent_session_info( + terminal: &crate::terminal::TerminalState, +) -> Option { + if let Some(authority) = terminal.hook_authority.as_ref() { + if let Some(session_ref) = authority.session_ref.as_ref() { + return Some(crate::api::schema::AgentSessionInfo { + source: authority.source.clone(), + agent: authority.agent_label.clone(), + kind: session_ref.kind, + value: session_ref.value.clone(), + }); + } + } + + terminal + .persisted_agent_session + .as_ref() + .map(|session| crate::api::schema::AgentSessionInfo { + source: session.source.clone(), + agent: session.agent.clone(), + kind: session.session_ref.kind, + value: session.session_ref.value.clone(), + }) +} diff --git a/tests/api_ping.rs b/tests/api_ping.rs index 239daeae..064bac13 100644 --- a/tests/api_ping.rs +++ b/tests/api_ping.rs @@ -1209,11 +1209,13 @@ fn pane_report_agent_updates_effective_state() { thread::sleep(Duration::from_millis(100)); } + let session_path = base.join("pi-session.jsonl"); let hook = send_request( &socket_path, &format!( - r#"{{"id":"req_hook_5","method":"pane.report_agent","params":{{"pane_id":"{}","source":"herdr:pi","agent":"pi","state":"working","message":"thinking"}}}}"#, - pane_id + r#"{{"id":"req_hook_5","method":"pane.report_agent","params":{{"pane_id":"{}","source":"herdr:pi","agent":"pi","state":"working","message":"thinking","agent_session_path":"{}"}}}}"#, + pane_id, + session_path.display() ), ); assert_eq!(hook["result"]["type"], "ok"); @@ -1227,6 +1229,16 @@ fn pane_report_agent_updates_effective_state() { ); assert_eq!(pane["result"]["pane"]["agent"], "pi"); assert_eq!(pane["result"]["pane"]["agent_status"], "working"); + assert_eq!( + pane["result"]["pane"]["agent_session"]["source"], + "herdr:pi" + ); + assert_eq!(pane["result"]["pane"]["agent_session"]["agent"], "pi"); + assert_eq!(pane["result"]["pane"]["agent_session"]["kind"], "path"); + assert_eq!( + pane["result"]["pane"]["agent_session"]["value"], + session_path.display().to_string() + ); let metadata = send_request( &socket_path, @@ -1259,6 +1271,16 @@ fn pane_report_agent_updates_effective_state() { r#"{"id":"req_hook_metadata_agent","method":"agent.get","params":{"target":"pi"}}"#, ); assert_eq!(agent["result"]["agent"]["agent"], "pi"); + assert_eq!( + agent["result"]["agent"]["agent_session"]["source"], + "herdr:pi" + ); + assert_eq!(agent["result"]["agent"]["agent_session"]["agent"], "pi"); + assert_eq!(agent["result"]["agent"]["agent_session"]["kind"], "path"); + assert_eq!( + agent["result"]["agent"]["agent_session"]["value"], + session_path.display().to_string() + ); assert_eq!(agent["result"]["agent"]["title"], "Refactor auth"); assert_eq!(agent["result"]["agent"]["display_agent"], "Pi auth"); assert_eq!(agent["result"]["agent"]["custom_status"], "middleware");