diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-20 00:54:05 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-20 00:55:36 +0300 |
| commit | d30ab1b54f6b6be95a0cb47ddc38da1d5e088700 (patch) | |
| tree | eb2002b408c856d9fcd7a573afe73b917f22cd97 /src/api | |
| parent | 3f80947678a9723434aa7fddfe23750b5393e24c (diff) | |
feat: add plugin-driven agent views and startup hooks
Diffstat (limited to 'src/api')
| -rw-r--r-- | src/api/mod.rs | 4 | ||||
| -rw-r--r-- | src/api/schema.rs | 4 | ||||
| -rw-r--r-- | src/api/schema/agents.rs | 114 | ||||
| -rw-r--r-- | src/api/schema/plugins.rs | 9 | ||||
| -rw-r--r-- | src/api/schema/response.rs | 7 | ||||
| -rw-r--r-- | src/api/schema/tests.rs | 44 | ||||
| -rw-r--r-- | src/api/server.rs | 2 |
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