aboutsummaryrefslogtreecommitdiff
path: root/src/api
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-20 00:54:05 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-20 00:55:36 +0300
commitd30ab1b54f6b6be95a0cb47ddc38da1d5e088700 (patch)
treeeb2002b408c856d9fcd7a573afe73b917f22cd97 /src/api
parent3f80947678a9723434aa7fddfe23750b5393e24c (diff)
feat: add plugin-driven agent views and startup hooks
Diffstat (limited to 'src/api')
-rw-r--r--src/api/mod.rs4
-rw-r--r--src/api/schema.rs4
-rw-r--r--src/api/schema/agents.rs114
-rw-r--r--src/api/schema/plugins.rs9
-rw-r--r--src/api/schema/response.rs7
-rw-r--r--src/api/schema/tests.rs44
-rw-r--r--src/api/server.rs2
7 files changed, 184 insertions, 0 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs
index badf1707..994b2d87 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -42,6 +42,8 @@ pub(crate) fn request_changes_ui(request: &Request) -> bool {
| Method::LayoutApply(_)
| Method::LayoutSetSplitRatio(_)
| Method::AgentRename(_)
+ | Method::AgentViewSet(_)
+ | Method::AgentViewClear(_)
| Method::AgentFocus(_)
| Method::AgentStart(_)
| Method::AgentPrompt(_)
@@ -67,6 +69,8 @@ pub(crate) fn request_changes_ui(request: &Request) -> bool {
| Method::PaneReleaseAgent(_)
| Method::PaneClose(_)
| Method::PopupClose(_)
+ | Method::PluginUnlink(_)
+ | Method::PluginDisable(_)
| Method::PluginActionInvoke(_)
| Method::PluginPaneOpen(_)
| Method::PluginPaneFocus(_)
diff --git a/src/api/schema.rs b/src/api/schema.rs
index efaee54c..72d9e53a 100644
--- a/src/api/schema.rs
+++ b/src/api/schema.rs
@@ -113,6 +113,10 @@ pub enum Method {
AgentSendKeys(AgentSendKeysParams),
#[serde(rename = "agent.rename")]
AgentRename(AgentRenameParams),
+ #[serde(rename = "agent.view.set")]
+ AgentViewSet(AgentViewSetParams),
+ #[serde(rename = "agent.view.clear")]
+ AgentViewClear(AgentViewClearParams),
#[serde(rename = "agent.focus")]
AgentFocus(AgentTarget),
#[serde(rename = "agent.start")]
diff --git a/src/api/schema/agents.rs b/src/api/schema/agents.rs
index 59949293..9e1e7b15 100644
--- a/src/api/schema/agents.rs
+++ b/src/api/schema/agents.rs
@@ -47,6 +47,120 @@ pub struct AgentRenameParams {
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+pub struct AgentViewSetParams {
+ pub source: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub label: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub filter: Option<AgentViewFilter>,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub sort: Vec<AgentViewSort>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema, Default)]
+pub struct AgentViewClearParams {
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub source: Option<String>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+#[serde(tag = "op", rename_all = "snake_case")]
+pub enum AgentViewFilter {
+ All {
+ filters: Vec<AgentViewFilter>,
+ },
+ Any {
+ filters: Vec<AgentViewFilter>,
+ },
+ Not {
+ filter: Box<AgentViewFilter>,
+ },
+ Eq {
+ field: AgentViewField,
+ value: AgentViewValue,
+ },
+ In {
+ field: AgentViewField,
+ values: Vec<AgentViewValue>,
+ },
+ Exists {
+ field: AgentViewField,
+ },
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+#[serde(untagged)]
+pub enum AgentViewField {
+ Builtin(AgentViewBuiltinField),
+ Token { token: String },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum AgentViewBuiltinField {
+ Status,
+ WorkspaceId,
+ TabId,
+ PaneId,
+ Agent,
+ Seen,
+ StateChangeSeq,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+#[serde(untagged)]
+pub enum AgentViewValue {
+ String(String),
+ Bool(bool),
+ Number(u64),
+ Context { context: AgentViewContext },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum AgentViewContext {
+ CurrentWorkspaceId,
+ CurrentTabId,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+pub struct AgentViewSort {
+ pub field: AgentViewSortField,
+ #[serde(default)]
+ pub order: AgentViewSortOrder,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+#[serde(untagged)]
+pub enum AgentViewSortField {
+ Builtin(AgentViewBuiltinSortField),
+ Token { token: String },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum AgentViewBuiltinSortField {
+ WorkspaceOrder,
+ TabOrder,
+ PaneOrder,
+ Attention,
+ Status,
+ Agent,
+ Seen,
+ StateChangeSeq,
+}
+
+#[derive(
+ Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema, Default,
+)]
+#[serde(rename_all = "snake_case")]
+pub enum AgentViewSortOrder {
+ #[default]
+ Asc,
+ Desc,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct AgentStartParams {
pub name: String,
pub kind: String,
diff --git a/src/api/schema/plugins.rs b/src/api/schema/plugins.rs
index 75b40f97..52df3ca6 100644
--- a/src/api/schema/plugins.rs
+++ b/src/api/schema/plugins.rs
@@ -50,6 +50,8 @@ pub struct InstalledPluginInfo {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub build: Vec<PluginManifestBuild>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
+ pub startup: Vec<PluginManifestStartup>,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub actions: Vec<PluginManifestAction>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<PluginManifestEventHook>,
@@ -232,6 +234,13 @@ pub struct PluginManifestBuild {
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
+pub struct PluginManifestStartup {
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub platforms: Option<Vec<PluginPlatform>>,
+ pub command: Vec<String>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PluginManifestAction {
pub id: String,
pub title: String,
diff --git a/src/api/schema/response.rs b/src/api/schema/response.rs
index e1f47efd..db8cfc7b 100644
--- a/src/api/schema/response.rs
+++ b/src/api/schema/response.rs
@@ -107,6 +107,13 @@ pub enum ResponseResult {
AgentList {
agents: Vec<AgentInfo>,
},
+ AgentView {
+ active: bool,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ source: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ label: Option<String>,
+ },
PaneInfo {
pane: PaneInfo,
},
diff --git a/src/api/schema/tests.rs b/src/api/schema/tests.rs
index 236d4f12..9fcc03b0 100644
--- a/src/api/schema/tests.rs
+++ b/src/api/schema/tests.rs
@@ -296,6 +296,49 @@ fn client_window_title_requests_round_trip() {
}
#[test]
+fn agent_view_requests_round_trip() {
+ let set_json = serde_json::json!({
+ "id": "view-set",
+ "method": "agent.view.set",
+ "params": {
+ "source": "example.views",
+ "label": "current + attention",
+ "filter": {
+ "op": "any",
+ "filters": [
+ {
+ "op": "eq",
+ "field": "workspace_id",
+ "value": {"context": "current_workspace_id"}
+ },
+ {
+ "op": "in",
+ "field": "status",
+ "values": ["blocked", "done"]
+ }
+ ]
+ },
+ "sort": [
+ {"field": "attention", "order": "desc"},
+ {"field": "state_change_seq", "order": "desc"}
+ ]
+ }
+ });
+ let request: Request = serde_json::from_value(set_json.clone()).unwrap();
+ assert!(matches!(request.method, Method::AgentViewSet(_)));
+ assert_eq!(serde_json::to_value(request).unwrap(), set_json);
+
+ let clear_json = serde_json::json!({
+ "id": "view-clear",
+ "method": "agent.view.clear",
+ "params": {"source": "example.views"}
+ });
+ let request: Request = serde_json::from_value(clear_json.clone()).unwrap();
+ assert!(matches!(request.method, Method::AgentViewClear(_)));
+ assert_eq!(serde_json::to_value(request).unwrap(), clear_json);
+}
+
+#[test]
fn unknown_method_is_rejected() {
let json = r#"{"id":"req_1","method":"nope","params":{}}"#;
let err = serde_json::from_str::<Request>(json)
@@ -856,6 +899,7 @@ fn plugin_link_list_unlink_round_trip() {
platforms: None,
command: vec!["bun".into(), "install".into()],
}],
+ startup: vec![],
actions: vec![PluginManifestAction {
id: "bootstrap".into(),
title: "Bootstrap worktree".into(),
diff --git a/src/api/server.rs b/src/api/server.rs
index 0a36e2fa..5bc90ef9 100644
--- a/src/api/server.rs
+++ b/src/api/server.rs
@@ -378,6 +378,8 @@ fn api_method_name(method: &Method) -> &'static str {
Method::AgentExplain(_) => "agent.explain",
Method::AgentSendKeys(_) => "agent.send_keys",
Method::AgentRename(_) => "agent.rename",
+ Method::AgentViewSet(_) => "agent.view.set",
+ Method::AgentViewClear(_) => "agent.view.clear",
Method::AgentFocus(_) => "agent.focus",
Method::AgentStart(_) => "agent.start",
Method::AgentPrompt(_) => "agent.prompt",