aboutsummaryrefslogtreecommitdiff
path: root/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/api')
-rw-r--r--src/api/mod.rs1
-rw-r--r--src/api/schema.rs93
-rw-r--r--src/api/server.rs1
-rw-r--r--src/api/subscriptions.rs32
4 files changed, 126 insertions, 1 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs
index 1f2ab2bb..67964e83 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -39,6 +39,7 @@ pub(crate) fn request_changes_ui(request: &Request) -> bool {
| Method::PaneSplit(_)
| Method::PaneRename(_)
| Method::PaneReportAgent(_)
+ | Method::PaneReportMetadata(_)
| Method::PaneClearAgentAuthority(_)
| Method::PaneReleaseAgent(_)
| Method::PaneClose(_)
diff --git a/src/api/schema.rs b/src/api/schema.rs
index 4929d4a7..675d0cbc 100644
--- a/src/api/schema.rs
+++ b/src/api/schema.rs
@@ -1,3 +1,5 @@
+use std::collections::HashMap;
+
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -82,6 +84,8 @@ pub enum Method {
PaneRead(PaneReadParams),
#[serde(rename = "pane.report_agent")]
PaneReportAgent(PaneReportAgentParams),
+ #[serde(rename = "pane.report_metadata")]
+ PaneReportMetadata(PaneReportMetadataParams),
#[serde(rename = "pane.clear_agent_authority")]
PaneClearAgentAuthority(PaneClearAgentAuthorityParams),
#[serde(rename = "pane.release_agent")]
@@ -350,6 +354,36 @@ pub struct PaneReportAgentParams {
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct PaneReportMetadataParams {
+ pub pane_id: String,
+ pub source: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub agent: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub applies_to_source: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub title: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub display_agent: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub custom_status: Option<String>,
+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+ pub state_labels: HashMap<String, String>,
+ #[serde(default)]
+ pub clear_title: bool,
+ #[serde(default)]
+ pub clear_display_agent: bool,
+ #[serde(default)]
+ pub clear_custom_status: bool,
+ #[serde(default)]
+ pub clear_state_labels: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub seq: Option<u64>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub ttl_ms: Option<u64>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PaneClearAgentAuthorityParams {
pub pane_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -759,7 +793,15 @@ pub struct AgentInfo {
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub title: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub display_agent: Option<String>,
pub agent_status: AgentStatus,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub custom_status: Option<String>,
+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+ pub state_labels: HashMap<String, String>,
pub workspace_id: String,
pub tab_id: String,
pub pane_id: String,
@@ -782,9 +824,15 @@ pub struct PaneInfo {
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub title: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub display_agent: Option<String>,
pub agent_status: AgentStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_status: Option<String>,
+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+ pub state_labels: HashMap<String, String>,
pub revision: u64,
}
@@ -853,6 +901,12 @@ pub struct PaneAgentStatusChangedEvent {
pub agent: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_status: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub title: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub display_agent: Option<String>,
+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+ pub state_labels: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -921,7 +975,13 @@ pub enum EventData {
workspace_id: String,
agent_status: AgentStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
+ title: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ display_agent: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
custom_status: Option<String>,
+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
+ state_labels: HashMap<String, String>,
},
}
@@ -993,6 +1053,33 @@ mod tests {
}
#[test]
+ fn request_round_trips_for_pane_report_metadata() {
+ let request = Request {
+ id: "req_metadata".into(),
+ method: Method::PaneReportMetadata(PaneReportMetadataParams {
+ pane_id: "1-1".into(),
+ source: "user:claude-title".into(),
+ agent: Some("claude".into()),
+ applies_to_source: Some("herdr:claude".into()),
+ title: Some("Refactor auth".into()),
+ display_agent: Some("Claude auth".into()),
+ custom_status: Some("refactor auth".into()),
+ state_labels: HashMap::from([("working".into(), "deep in the mines".into())]),
+ clear_title: false,
+ clear_display_agent: false,
+ clear_custom_status: false,
+ clear_state_labels: false,
+ seq: Some(42),
+ ttl_ms: Some(3_600_000),
+ }),
+ };
+
+ let json = serde_json::to_string(&request).unwrap();
+ let restored: Request = serde_json::from_str(&json).unwrap();
+ assert_eq!(restored, request);
+ }
+
+ #[test]
fn request_round_trips_for_pane_clear_agent_authority() {
let request = Request {
id: "req_clear".into(),
@@ -1307,8 +1394,11 @@ mod tests {
cwd: Some("/worktrees/herdr/worktree-api".into()),
label: None,
agent: None,
+ title: None,
+ display_agent: None,
agent_status: AgentStatus::Unknown,
custom_status: None,
+ state_labels: HashMap::new(),
revision: 0,
},
worktree: WorktreeInfo {
@@ -1353,8 +1443,11 @@ mod tests {
cwd: Some("/tmp/review".into()),
label: None,
agent: None,
+ title: None,
+ display_agent: None,
agent_status: AgentStatus::Unknown,
custom_status: None,
+ state_labels: HashMap::new(),
revision: 0,
},
},
diff --git a/src/api/server.rs b/src/api/server.rs
index 696e1b65..cd130e47 100644
--- a/src/api/server.rs
+++ b/src/api/server.rs
@@ -303,6 +303,7 @@ fn api_method_name(method: &Method) -> &'static str {
Method::PaneSendInput(_) => "pane.send_input",
Method::PaneRead(_) => "pane.read",
Method::PaneReportAgent(_) => "pane.report_agent",
+ Method::PaneReportMetadata(_) => "pane.report_metadata",
Method::PaneClearAgentAuthority(_) => "pane.clear_agent_authority",
Method::PaneReleaseAgent(_) => "pane.release_agent",
Method::PaneClose(_) => "pane.close",
diff --git a/src/api/subscriptions.rs b/src/api/subscriptions.rs
index a6d8ff97..aaa7e5ac 100644
--- a/src/api/subscriptions.rs
+++ b/src/api/subscriptions.rs
@@ -50,9 +50,29 @@ pub(super) struct ActiveAgentStatusChangedSubscription {
status_filter: Option<crate::api::schema::AgentStatus>,
last_status: Option<crate::api::schema::AgentStatus>,
emit_initial_match: bool,
+ last_presentation: Option<PanePresentationSnapshot>,
request_prefix: String,
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct PanePresentationSnapshot {
+ title: Option<String>,
+ display_agent: Option<String>,
+ custom_status: Option<String>,
+ state_labels: std::collections::HashMap<String, String>,
+}
+
+impl PanePresentationSnapshot {
+ fn from(pane: &crate::api::schema::PaneInfo) -> Self {
+ Self {
+ title: pane.title.clone(),
+ display_agent: pane.display_agent.clone(),
+ custom_status: pane.custom_status.clone(),
+ state_labels: pane.state_labels.clone(),
+ }
+ }
+}
+
pub(super) struct ActiveEventSubscription {
event_kind: crate::api::schema::EventKind,
last_sequence: u64,
@@ -187,6 +207,7 @@ impl ActiveSubscription {
status_filter: agent_status,
last_status: Some(probe.agent_status),
emit_initial_match,
+ last_presentation: Some(PanePresentationSnapshot::from(&probe)),
request_prefix: format!("{request_id}:sub:{index}"),
},
))
@@ -268,13 +289,19 @@ impl ActiveAgentStatusChangedSubscription {
)
.ok()?;
let current_status = pane.agent_status;
+ let current_presentation = PanePresentationSnapshot::from(&pane);
let previous_status = self.last_status.replace(current_status);
+ let previous_presentation = self.last_presentation.replace(current_presentation.clone());
+ let presentation_changed = previous_presentation
+ .as_ref()
+ .is_some_and(|previous| previous != &current_presentation);
+ let status_changed = previous_status.is_some_and(|previous| previous != current_status);
let should_emit = if self.emit_initial_match {
self.emit_initial_match = false;
self.status_filter
.is_some_and(|wanted| wanted == current_status)
} else {
- previous_status.is_some_and(|previous| previous != current_status)
+ (status_changed || presentation_changed)
&& !self
.status_filter
.is_some_and(|wanted| wanted != current_status)
@@ -290,7 +317,10 @@ impl ActiveAgentStatusChangedSubscription {
workspace_id: pane.workspace_id,
agent_status: current_status,
agent: pane.agent,
+ title: pane.title,
+ display_agent: pane.display_agent,
custom_status: pane.custom_status,
+ state_labels: pane.state_labels,
}),
})
}