diff options
Diffstat (limited to 'src/api/schema.rs')
| -rw-r--r-- | src/api/schema.rs | 1534 |
1 files changed, 59 insertions, 1475 deletions
diff --git a/src/api/schema.rs b/src/api/schema.rs index 512c030f..b2d6f62e 100644 --- a/src/api/schema.rs +++ b/src/api/schema.rs @@ -1,10 +1,28 @@ -use std::collections::HashMap; - use serde::{Deserialize, Serialize}; +pub mod agents; +pub mod common; +pub mod events; +pub mod integrations; pub mod panes; - +pub mod plugins; +pub mod response; +pub mod server; +pub mod tabs; +pub mod workspaces; +pub mod worktrees; + +pub use agents::*; +pub use common::*; +pub use events::*; +pub use integrations::*; pub use panes::*; +pub use plugins::*; +pub use response::*; +pub use server::*; +pub use tabs::*; +pub use workspaces::*; +pub use worktrees::*; fn is_false(value: &bool) -> bool { !*value @@ -19,6 +37,9 @@ pub struct Request { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "method", content = "params")] +// Request enums are short-lived wire values; keeping variants direct preserves +// the simple serde shape and avoids boxing churn across every caller. +#[allow(clippy::large_enum_variant)] pub enum Method { #[serde(rename = "ping")] Ping(PingParams), @@ -34,6 +55,10 @@ pub enum Method { ServerReloadAgentManifests(EmptyParams), #[serde(rename = "notification.show")] NotificationShow(NotificationShowParams), + #[serde(rename = "client.window_title.set")] + ClientWindowTitleSet(ClientWindowTitleSetParams), + #[serde(rename = "client.window_title.clear")] + ClientWindowTitleClear(EmptyParams), #[serde(rename = "workspace.create")] WorkspaceCreate(WorkspaceCreateParams), #[serde(rename = "workspace.list")] @@ -92,6 +117,12 @@ pub enum Method { PaneZoom(PaneZoomParams), #[serde(rename = "pane.layout")] PaneLayout(PaneLayoutParams), + #[serde(rename = "pane.process_info")] + PaneProcessInfo(PaneProcessInfoParams), + #[serde(rename = "layout.export")] + LayoutExport(LayoutExportParams), + #[serde(rename = "layout.apply")] + LayoutApply(LayoutApplyParams), #[serde(rename = "pane.neighbor")] PaneNeighbor(PaneNeighborParams), #[serde(rename = "pane.edges")] @@ -102,6 +133,8 @@ pub enum Method { PaneResize(PaneResizeParams), #[serde(rename = "pane.list")] PaneList(PaneListParams), + #[serde(rename = "pane.current")] + PaneCurrent(PaneCurrentParams), #[serde(rename = "pane.get")] PaneGet(PaneTarget), #[serde(rename = "pane.rename")] @@ -136,1478 +169,29 @@ pub enum Method { IntegrationInstall(IntegrationInstallParams), #[serde(rename = "integration.uninstall")] IntegrationUninstall(IntegrationUninstallParams), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct EmptyParams {} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct PingParams {} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct NotificationShowParams { - pub title: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub body: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub position: Option<crate::config::ToastHerdrPosition>, - #[serde(default, skip_serializing_if = "NotificationShowSound::is_none")] - pub sound: NotificationShowSound, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] -#[serde(rename_all = "snake_case")] -pub enum NotificationShowSound { - #[default] - None, - Done, - Request, -} - -impl NotificationShowSound { - pub fn is_none(&self) -> bool { - matches!(self, Self::None) - } - - pub fn to_sound(self) -> Option<crate::sound::Sound> { - match self { - Self::None => None, - Self::Done => Some(crate::sound::Sound::Done), - Self::Request => Some(crate::sound::Sound::Request), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum NotificationShowReason { - Shown, - Disabled, - RateLimited, - NoForegroundClient, - Busy, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorkspaceTarget { - pub workspace_id: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PaneTarget { - pub pane_id: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TabTarget { - pub tab_id: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorkspaceCreateParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option<String>, - #[serde(default)] - pub focus: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub label: Option<String>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorkspaceRenameParams { - pub workspace_id: String, - pub label: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct WorktreeListParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub workspace_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option<String>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct WorktreeCreateParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub workspace_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub branch: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub base: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub label: Option<String>, - #[serde(default)] - pub focus: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct WorktreeOpenParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub workspace_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub path: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub branch: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub label: Option<String>, - #[serde(default)] - pub focus: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorktreeRemoveParams { - pub workspace_id: String, - #[serde(default)] - pub force: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TabCreateParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub workspace_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option<String>, - #[serde(default)] - pub focus: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub label: Option<String>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct TabListParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub workspace_id: Option<String>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TabRenameParams { - pub tab_id: String, - pub label: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AgentTarget { - pub target: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AgentReadParams { - pub target: String, - pub source: ReadSource, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub lines: Option<u32>, - #[serde(default)] - pub format: ReadFormat, - #[serde(default = "default_true")] - pub strip_ansi: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AgentSendParams { - pub target: String, - pub text: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AgentRenameParams { - pub target: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub name: Option<String>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AgentStartParams { - pub name: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub workspace_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub tab_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub split: Option<SplitDirection>, - #[serde(default)] - pub focus: bool, - pub argv: Vec<String>, -} - -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct ServerLiveHandoffParams { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub import_exe: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub expected_protocol: Option<u32>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub expected_version: Option<String>, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum ReadSource { - Visible, - Recent, - RecentUnwrapped, - Detection, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] -#[serde(rename_all = "snake_case")] -pub enum ReadFormat { - #[default] - Text, - Ansi, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct EventsSubscribeParams { - pub subscriptions: Vec<Subscription>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum Subscription { - #[serde(rename = "workspace.created")] - WorkspaceCreated {}, - #[serde(rename = "workspace.updated")] - WorkspaceUpdated {}, - #[serde(rename = "workspace.renamed")] - WorkspaceRenamed {}, - #[serde(rename = "workspace.closed")] - WorkspaceClosed {}, - #[serde(rename = "workspace.focused")] - WorkspaceFocused {}, - #[serde(rename = "tab.created")] - TabCreated {}, - #[serde(rename = "tab.closed")] - TabClosed {}, - #[serde(rename = "tab.focused")] - TabFocused {}, - #[serde(rename = "tab.renamed")] - TabRenamed {}, - #[serde(rename = "pane.created")] - PaneCreated {}, - #[serde(rename = "pane.closed")] - PaneClosed {}, - #[serde(rename = "pane.focused")] - PaneFocused {}, - #[serde(rename = "pane.moved")] - PaneMoved {}, - #[serde(rename = "pane.exited")] - PaneExited {}, - #[serde(rename = "pane.agent_detected")] - PaneAgentDetected {}, - #[serde(rename = "pane.output_matched")] - PaneOutputMatched { - pane_id: String, - source: ReadSource, - #[serde(default, skip_serializing_if = "Option::is_none")] - lines: Option<u32>, - r#match: OutputMatch, - #[serde(default = "default_true")] - strip_ansi: bool, - }, - #[serde(rename = "pane.agent_status_changed")] - PaneAgentStatusChanged { - pane_id: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - agent_status: Option<AgentStatus>, - }, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct EventsWaitParams { - pub match_event: EventMatch, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub timeout_ms: Option<u64>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PaneWaitForOutputParams { - pub pane_id: String, - pub source: ReadSource, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub lines: Option<u32>, - pub r#match: OutputMatch, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub timeout_ms: Option<u64>, - #[serde(default = "default_true")] - pub strip_ansi: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct IntegrationInstallParams { - pub target: IntegrationTarget, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct IntegrationUninstallParams { - pub target: IntegrationTarget, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum IntegrationTarget { - Pi, - Omp, - Claude, - Codex, - Copilot, - Droid, - Kimi, - Opencode, - Kilo, - Hermes, - Qodercli, - Cursor, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum OutputMatch { - Substring { value: String }, - Regex { value: String }, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "event", rename_all = "snake_case")] -pub enum EventMatch { - WorkspaceCreated { - #[serde(default, skip_serializing_if = "Option::is_none")] - workspace_id: Option<String>, - }, - WorkspaceUpdated { - workspace_id: String, - }, - WorkspaceClosed { - workspace_id: String, - }, - WorkspaceRenamed { - workspace_id: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - label: Option<String>, - }, - WorkspaceFocused { - workspace_id: String, - }, - TabCreated { - #[serde(default, skip_serializing_if = "Option::is_none")] - tab_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - workspace_id: Option<String>, - }, - TabClosed { - tab_id: String, - }, - TabRenamed { - tab_id: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - label: Option<String>, - }, - TabFocused { - tab_id: String, - }, - PaneCreated { - #[serde(default, skip_serializing_if = "Option::is_none")] - pane_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - workspace_id: Option<String>, - }, - PaneClosed { - pane_id: String, - }, - PaneFocused { - pane_id: String, - }, - PaneMoved { - pane_id: String, - }, - PaneOutputChanged { - pane_id: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - min_revision: Option<u64>, - }, - PaneExited { - pane_id: String, - }, - PaneAgentDetected { - pane_id: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - agent: Option<String>, - }, - PaneAgentStatusChanged { - pane_id: String, - agent_status: AgentStatus, - }, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum EventKind { - WorkspaceCreated, - WorkspaceUpdated, - WorkspaceClosed, - WorkspaceRenamed, - WorkspaceFocused, - TabCreated, - TabClosed, - TabRenamed, - TabFocused, - PaneCreated, - PaneClosed, - PaneFocused, - PaneMoved, - PaneOutputChanged, - PaneExited, - PaneAgentDetected, - PaneAgentStatusChanged, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct SuccessResponse { - pub id: String, - pub result: ResponseResult, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ErrorResponse { - pub id: String, - pub error: ErrorBody, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ErrorBody { - pub code: String, - pub message: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ServerCapabilities { - pub live_handoff: bool, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ResponseResult { - Pong { - version: String, - protocol: u32, - #[serde(default)] - capabilities: Option<ServerCapabilities>, - }, - WorkspaceInfo { - workspace: WorkspaceInfo, - }, - WorkspaceCreated { - workspace: WorkspaceInfo, - tab: TabInfo, - root_pane: PaneInfo, - }, - WorkspaceList { - workspaces: Vec<WorkspaceInfo>, - }, - WorktreeList { - source: WorktreeSourceInfo, - worktrees: Vec<WorktreeInfo>, - }, - WorktreeCreated { - workspace: WorkspaceInfo, - tab: TabInfo, - root_pane: PaneInfo, - worktree: WorktreeInfo, - }, - WorktreeOpened { - workspace: WorkspaceInfo, - tab: TabInfo, - root_pane: PaneInfo, - worktree: WorktreeInfo, - already_open: bool, - }, - WorktreeRemoved { - workspace_id: String, - path: String, - forced: bool, - }, - TabInfo { - tab: TabInfo, - }, - TabCreated { - tab: TabInfo, - root_pane: PaneInfo, - }, - TabList { - tabs: Vec<TabInfo>, - }, - AgentInfo { - agent: AgentInfo, - }, - AgentStarted { - agent: AgentInfo, - argv: Vec<String>, - }, - AgentList { - agents: Vec<AgentInfo>, - }, - PaneInfo { - pane: PaneInfo, - }, - PaneList { - panes: Vec<PaneInfo>, - }, - PaneSwap { - swap: PaneSwapResult, - }, - PaneMove { - move_result: PaneMoveResult, - }, - PaneZoom { - zoom: PaneZoomResult, - }, - PaneLayout { - layout: PaneLayoutSnapshot, - }, - PaneNeighbor { - neighbor: PaneNeighborResult, - }, - PaneEdges { - edges: PaneEdgesResult, - }, - PaneFocusDirection { - focus: PaneFocusDirectionResult, - }, - PaneResize { - resize: PaneResizeResult, - }, - PaneRead { - read: PaneReadResult, - }, - AgentExplain { - explain: serde_json::Value, - }, - SubscriptionStarted {}, - WaitMatched { - event: EventEnvelope, - }, - OutputMatched { - pane_id: String, - revision: u64, - matched_line: Option<String>, - read: PaneReadResult, - }, - NotificationShow { - shown: bool, - reason: NotificationShowReason, - }, - IntegrationInstall { - target: IntegrationTarget, - details: IntegrationInstallResult, - }, - IntegrationUninstall { - target: IntegrationTarget, - details: IntegrationUninstallResult, - }, - AgentManifestReload { - manifests: Vec<AgentManifestInfo>, - }, - AgentManifestStatus { - #[serde(default, skip_serializing_if = "Option::is_none")] - last_check_unix: Option<u64>, - #[serde(default, skip_serializing_if = "Option::is_none")] - last_result: Option<String>, - manifests: Vec<AgentManifestInfo>, - }, - ConfigReload { - status: crate::config::ConfigReloadStatus, - diagnostics: Vec<String>, - }, - Ok {}, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AgentManifestInfo { - pub agent: String, - pub source: String, - pub source_kind: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub active_version: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cached_remote_version: Option<String>, - pub local_override_shadowing_remote: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub remote_update_result: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub remote_update_error: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub remote_last_checked_unix: Option<u64>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub warning: Option<String>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorkspaceInfo { - pub workspace_id: String, - pub number: usize, - pub label: String, - pub focused: bool, - pub pane_count: usize, - pub tab_count: usize, - pub active_tab_id: String, - pub agent_status: AgentStatus, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub worktree: Option<WorkspaceWorktreeInfo>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorkspaceWorktreeInfo { - pub repo_key: String, - pub repo_name: String, - pub repo_root: String, - pub checkout_path: String, - pub is_linked_worktree: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorktreeSourceInfo { - pub repo_key: String, - pub repo_name: String, - pub repo_root: String, - pub source_checkout_path: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub source_workspace_id: Option<String>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorktreeInfo { - pub path: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub branch: Option<String>, - pub is_bare: bool, - pub is_detached: bool, - pub is_prunable: bool, - pub is_linked_worktree: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub open_workspace_id: Option<String>, - pub label: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TabInfo { - pub tab_id: String, - pub workspace_id: String, - pub number: usize, - pub label: String, - pub focused: bool, - pub pane_count: usize, - pub agent_status: AgentStatus, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AgentInfo { - pub terminal_id: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - 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 = "is_false")] - pub screen_detection_skipped: bool, - #[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, skip_serializing_if = "Option::is_none")] - pub agent_session: Option<AgentSessionInfo>, - pub workspace_id: String, - pub tab_id: String, - pub pane_id: String, - pub focused: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub foreground_cwd: Option<String>, - 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 IntegrationInstallResult { - pub messages: Vec<String>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct IntegrationUninstallResult { - pub messages: Vec<String>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct EventEnvelope { - pub event: EventKind, - pub data: EventData, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum SubscriptionEventKind { - #[serde(rename = "pane.output_matched")] - PaneOutputMatched, - #[serde(rename = "pane.agent_status_changed")] - PaneAgentStatusChanged, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct SubscriptionEventEnvelope { - pub event: SubscriptionEventKind, - pub data: SubscriptionEventData, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum SubscriptionEventData { - PaneOutputMatched(PaneOutputMatchedEvent), - PaneAgentStatusChanged(PaneAgentStatusChangedEvent), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PaneOutputMatchedEvent { - pub pane_id: String, - pub matched_line: String, - pub read: PaneReadResult, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PaneAgentStatusChangedEvent { - pub pane_id: String, - pub workspace_id: String, - pub agent_status: AgentStatus, - #[serde(default, skip_serializing_if = "Option::is_none")] - 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)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum EventData { - WorkspaceCreated { - workspace: WorkspaceInfo, - }, - WorkspaceUpdated { - workspace: WorkspaceInfo, - }, - WorkspaceClosed { - workspace_id: String, - }, - WorkspaceRenamed { - workspace_id: String, - label: String, - }, - WorkspaceFocused { - workspace_id: String, - }, - TabCreated { - tab: TabInfo, - }, - TabClosed { - tab_id: String, - workspace_id: String, - }, - TabRenamed { - tab_id: String, - workspace_id: String, - label: String, - }, - TabFocused { - tab_id: String, - workspace_id: String, - }, - PaneCreated { - pane: PaneInfo, - }, - PaneClosed { - pane_id: String, - workspace_id: String, - }, - PaneFocused { - pane_id: String, - workspace_id: String, - }, - PaneMoved { - previous_pane_id: String, - previous_workspace_id: String, - previous_tab_id: String, - pane: Box<PaneInfo>, - #[serde(default, skip_serializing_if = "Option::is_none")] - created_workspace: Option<WorkspaceInfo>, - #[serde(default, skip_serializing_if = "Option::is_none")] - created_tab: Option<TabInfo>, - #[serde(default, skip_serializing_if = "Option::is_none")] - closed_workspace_id: Option<String>, - #[serde(default, skip_serializing_if = "Option::is_none")] - closed_tab_id: Option<String>, - }, - PaneOutputChanged { - pane_id: String, - workspace_id: String, - revision: u64, - }, - PaneExited { - pane_id: String, - workspace_id: String, - }, - PaneAgentDetected { - pane_id: String, - workspace_id: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - agent: Option<String>, - }, - PaneAgentStatusChanged { - pane_id: String, - workspace_id: String, - agent_status: AgentStatus, - #[serde(default, skip_serializing_if = "Option::is_none")] - agent: Option<String>, - #[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>, - }, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum PaneAgentState { - Idle, - Working, - Blocked, - Unknown, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum AgentStatus { - Idle, - Working, - Blocked, - Done, - Unknown, -} - -fn default_true() -> bool { - true + #[serde(rename = "plugin.link")] + PluginLink(PluginLinkParams), + #[serde(rename = "plugin.list")] + PluginList(PluginListParams), + #[serde(rename = "plugin.unlink")] + PluginUnlink(PluginUnlinkParams), + #[serde(rename = "plugin.enable")] + PluginEnable(PluginSetEnabledParams), + #[serde(rename = "plugin.disable")] + PluginDisable(PluginSetEnabledParams), + #[serde(rename = "plugin.action.list")] + PluginActionList(PluginActionListParams), + #[serde(rename = "plugin.action.invoke")] + PluginActionInvoke(PluginActionInvokeParams), + #[serde(rename = "plugin.log.list")] + PluginLogList(PluginLogListParams), + #[serde(rename = "plugin.pane.open")] + PluginPaneOpen(PluginPaneOpenParams), + #[serde(rename = "plugin.pane.focus")] + PluginPaneFocus(PluginPaneFocusParams), + #[serde(rename = "plugin.pane.close")] + PluginPaneClose(PluginPaneCloseParams), } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn request_round_trips_for_pane_read() { - let request = Request { - id: "req_1".into(), - method: Method::PaneRead(PaneReadParams { - pane_id: "p_1".into(), - source: ReadSource::Recent, - lines: Some(80), - format: ReadFormat::Text, - strip_ansi: true, - }), - }; - - 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_report_agent() { - let request = Request { - id: "req_hook".into(), - method: Method::PaneReportAgent(PaneReportAgentParams { - pane_id: "w1:p1".into(), - source: "herdr:pi".into(), - agent: "pi".into(), - state: PaneAgentState::Working, - message: Some("thinking".into()), - custom_status: Some("indexing".into()), - seq: Some(42), - agent_session_id: Some("pi-session".into()), - agent_session_path: Some("/tmp/pi-session.jsonl".into()), - }), - }; - - 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_report_agent_session() { - let request = Request { - id: "req_session".into(), - method: Method::PaneReportAgentSession(PaneReportAgentSessionParams { - pane_id: "w1:p1".into(), - source: "herdr:claude".into(), - agent: "claude".into(), - seq: Some(42), - agent_session_id: Some("claude-session".into()), - agent_session_path: None, - }), - }; - - 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_report_metadata() { - let request = Request { - id: "req_metadata".into(), - method: Method::PaneReportMetadata(PaneReportMetadataParams { - pane_id: "w1:p1".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(), - method: Method::PaneClearAgentAuthority(PaneClearAgentAuthorityParams { - pane_id: "w1:p1".into(), - source: Some("herdr:pi".into()), - seq: Some(42), - }), - }; - - 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_release_agent() { - let request = Request { - id: "req_release".into(), - method: Method::PaneReleaseAgent(PaneReleaseAgentParams { - pane_id: "w1:p1".into(), - source: "herdr:pi".into(), - agent: "pi".into(), - seq: Some(42), - }), - }; - - let json = serde_json::to_string(&request).unwrap(); - let restored: Request = serde_json::from_str(&json).unwrap(); - assert_eq!(restored, request); - } - - #[test] - fn request_uses_dot_method_names() { - let request = Request { - id: "req_1".into(), - method: Method::WorkspaceCreate(WorkspaceCreateParams { - cwd: Some("/tmp".into()), - focus: true, - label: Some("api".into()), - }), - }; - - let json = serde_json::to_value(&request).unwrap(); - assert_eq!(json["method"], "workspace.create"); - } - - #[test] - fn request_round_trips_for_server_stop() { - let request = Request { - id: "req_stop".into(), - method: Method::ServerStop(EmptyParams::default()), - }; - - let json = serde_json::to_value(&request).unwrap(); - assert_eq!(json["method"], "server.stop"); - let restored: Request = serde_json::from_value(json).unwrap(); - assert_eq!(restored, request); - } - - #[test] - fn request_round_trips_for_server_reload_config() { - let request = Request { - id: "req_reload".into(), - method: Method::ServerReloadConfig(EmptyParams::default()), - }; - - let json = serde_json::to_value(&request).unwrap(); - assert_eq!(json["method"], "server.reload_config"); - let restored: Request = serde_json::from_value(json).unwrap(); - assert_eq!(restored, request); - } - - #[test] - fn request_round_trips_for_server_reload_agent_manifests() { - let request = Request { - id: "req_reload_agent_manifests".into(), - method: Method::ServerReloadAgentManifests(EmptyParams::default()), - }; - - let json = serde_json::to_value(&request).unwrap(); - assert_eq!(json["method"], "server.reload_agent_manifests"); - let restored: Request = serde_json::from_value(json).unwrap(); - assert_eq!(restored, request); - } - - #[test] - fn request_round_trips_for_server_agent_manifests() { - let request = Request { - id: "req_agent_manifests".into(), - method: Method::ServerAgentManifests(EmptyParams::default()), - }; - - let json = serde_json::to_value(&request).unwrap(); - assert_eq!(json["method"], "server.agent_manifests"); - let restored: Request = serde_json::from_value(json).unwrap(); - assert_eq!(restored, request); - } - - #[test] - fn request_round_trips_for_agent_explain() { - let request = Request { - id: "req_agent_explain".into(), - method: Method::AgentExplain(AgentTarget { - target: "agent-1".into(), - }), - }; - - let json = serde_json::to_value(&request).unwrap(); - assert_eq!(json["method"], "agent.explain"); - let restored: Request = serde_json::from_value(json).unwrap(); - assert_eq!(restored, request); - } - - #[test] - fn notification_show_request_parses() { - let json = r#"{"id":"req_1","method":"notification.show","params":{"title":"build failed","body":"api workspace","position":"top-left","sound":"request"}}"#; - let request: Request = serde_json::from_str(json).unwrap(); - let Method::NotificationShow(params) = request.method else { - panic!("wrong method parsed"); - }; - assert_eq!(params.title, "build failed"); - assert_eq!(params.body.as_deref(), Some("api workspace")); - assert_eq!( - params.position, - Some(crate::config::ToastHerdrPosition::TopLeft) - ); - assert_eq!(params.sound, NotificationShowSound::Request); - } - - #[test] - fn notification_show_sound_defaults_to_none() { - let json = - r#"{"id":"req_1","method":"notification.show","params":{"title":"build failed"}}"#; - let request: Request = serde_json::from_str(json).unwrap(); - let Method::NotificationShow(params) = request.method else { - panic!("wrong method parsed"); - }; - - assert_eq!(params.sound, NotificationShowSound::None); - } - - #[test] - fn unknown_method_is_rejected() { - let json = r#"{"id":"req_1","method":"nope","params":{}}"#; - let err = serde_json::from_str::<Request>(json) - .unwrap_err() - .to_string(); - assert!(err.contains("unknown variant")); - } - - #[test] - fn missing_required_params_are_rejected() { - let json = r#"{"id":"req_1","method":"pane.send_text","params":{"pane_id":"p_1"}}"#; - let err = serde_json::from_str::<Request>(json) - .unwrap_err() - .to_string(); - assert!(err.contains("text")); - } - - #[test] - fn pane_send_input_defaults_to_empty_text_and_keys() { - let json = r#" - { - "id": "req_1", - "method": "pane.send_input", - "params": { - "pane_id": "p_1" - } - } - "#; - - let request: Request = serde_json::from_str(json).unwrap(); - let Method::PaneSendInput(params) = request.method else { - panic!("wrong method parsed"); - }; - assert_eq!(params.pane_id, "p_1"); - assert!(params.text.is_empty()); - assert!(params.keys.is_empty()); - } - - #[test] - fn pane_wait_for_output_defaults_strip_ansi_to_true() { - let json = r#" - { - "id": "req_1", - "method": "pane.wait_for_output", - "params": { - "pane_id": "p_1", - "source": "recent", - "match": { "type": "substring", "value": "ready" } - } - } - "#; - - let request: Request = serde_json::from_str(json).unwrap(); - let Method::PaneWaitForOutput(params) = request.method else { - panic!("wrong method parsed"); - }; - assert!(params.strip_ansi); - } - - #[test] - fn pane_read_defaults_to_text_format() { - let json = r#" - { - "id": "req_1", - "method": "pane.read", - "params": { - "pane_id": "p_1", - "source": "visible" - } - } - "#; - - let request: Request = serde_json::from_str(json).unwrap(); - let Method::PaneRead(params) = request.method else { - panic!("wrong method parsed"); - }; - assert_eq!(params.format, ReadFormat::Text); - } - - #[test] - fn event_envelope_round_trips() { - let event = EventEnvelope { - event: EventKind::PaneOutputChanged, - data: EventData::PaneOutputChanged { - pane_id: "w1:p1".into(), - workspace_id: "w1".into(), - revision: 42, - }, - }; - - let json = serde_json::to_string(&event).unwrap(); - let restored: EventEnvelope = serde_json::from_str(&json).unwrap(); - assert_eq!(restored, event); - } - - #[test] - fn subscribe_request_parses_parameterized_subscriptions() { - let json = r#" - { - "id": "sub_1", - "method": "events.subscribe", - "params": { - "subscriptions": [ - { - "type": "pane.output_matched", - "pane_id": "p_1_1", - "source": "recent", - "lines": 200, - "match": { "type": "substring", "value": "auth: received" } - }, - { - "type": "pane.agent_status_changed", - "pane_id": "p_1_1", - "agent_status": "done" - } - ] - } - } - "#; - - let request: Request = serde_json::from_str(json).unwrap(); - let Method::EventsSubscribe(params) = request.method else { - panic!("wrong method parsed"); - }; - assert_eq!(params.subscriptions.len(), 2); - assert!(matches!( - ¶ms.subscriptions[0], - Subscription::PaneOutputMatched { - pane_id, - source: ReadSource::Recent, - lines: Some(200), - r#match: OutputMatch::Substring { value }, - strip_ansi: true, - } if pane_id == "p_1_1" && value == "auth: received" - )); - assert!(matches!( - ¶ms.subscriptions[1], - Subscription::PaneAgentStatusChanged { - pane_id, - agent_status: Some(AgentStatus::Done), - } if pane_id == "p_1_1" - )); - } - - #[test] - fn subscription_event_envelope_round_trips() { - let event = SubscriptionEventEnvelope { - event: SubscriptionEventKind::PaneOutputMatched, - data: SubscriptionEventData::PaneOutputMatched(PaneOutputMatchedEvent { - pane_id: "w1:p1".into(), - matched_line: "auth: received".into(), - read: PaneReadResult { - pane_id: "w1:p1".into(), - workspace_id: "w1".into(), - tab_id: "w1:t1".into(), - source: ReadSource::Recent, - format: ReadFormat::Text, - text: "auth: received\n".into(), - revision: 0, - truncated: false, - }, - }), - }; - - let json = serde_json::to_string(&event).unwrap(); - assert!(json.contains("\"event\":\"pane.output_matched\"")); - let restored: SubscriptionEventEnvelope = serde_json::from_str(&json).unwrap(); - assert_eq!(restored, event); - } - - #[test] - fn success_response_round_trips() { - let response = SuccessResponse { - id: "req_1".into(), - result: ResponseResult::Pong { - version: "0.1.2".into(), - protocol: 6, - capabilities: Some(ServerCapabilities { live_handoff: true }), - }, - }; - - let json = serde_json::to_string(&response).unwrap(); - let restored: SuccessResponse = serde_json::from_str(&json).unwrap(); - assert_eq!(restored, response); - } - - #[test] - fn worktree_request_and_response_round_trip() { - let request = Request { - id: "req_worktree".into(), - method: Method::WorktreeCreate(WorktreeCreateParams { - workspace_id: Some("1".into()), - branch: Some("worktree/api".into()), - base: Some("HEAD".into()), - focus: true, - ..WorktreeCreateParams::default() - }), - }; - let json = serde_json::to_string(&request).unwrap(); - let restored: Request = serde_json::from_str(&json).unwrap(); - assert_eq!(restored, request); - - let response = SuccessResponse { - id: "req_worktree".into(), - result: ResponseResult::WorktreeCreated { - workspace: WorkspaceInfo { - workspace_id: "w1".into(), - number: 2, - label: "herdr".into(), - focused: true, - pane_count: 1, - tab_count: 1, - active_tab_id: "w1:t1".into(), - agent_status: AgentStatus::Unknown, - worktree: Some(WorkspaceWorktreeInfo { - repo_key: "/repo/herdr/.git".into(), - repo_name: "herdr".into(), - repo_root: "/repo/herdr".into(), - checkout_path: "/worktrees/herdr/worktree-api".into(), - is_linked_worktree: true, - }), - }, - tab: TabInfo { - tab_id: "w1:t1".into(), - workspace_id: "w1".into(), - number: 1, - label: "herdr".into(), - focused: true, - pane_count: 1, - agent_status: AgentStatus::Unknown, - }, - root_pane: PaneInfo { - pane_id: "w1:p1".into(), - terminal_id: "term_1".into(), - workspace_id: "w1".into(), - tab_id: "w1:t1".into(), - focused: true, - cwd: Some("/worktrees/herdr/worktree-api".into()), - foreground_cwd: None, - label: None, - agent: None, - title: None, - display_agent: None, - agent_status: AgentStatus::Unknown, - custom_status: None, - state_labels: HashMap::new(), - agent_session: None, - revision: 0, - }, - worktree: WorktreeInfo { - path: "/worktrees/herdr/worktree-api".into(), - branch: Some("worktree/api".into()), - is_bare: false, - is_detached: false, - is_prunable: false, - is_linked_worktree: true, - open_workspace_id: Some("w1".into()), - label: "herdr".into(), - }, - }, - }; - let json = serde_json::to_string(&response).unwrap(); - assert!(json.contains("\"type\":\"worktree_created\"")); - assert!(json.contains("\"worktree\"")); - let restored: SuccessResponse = serde_json::from_str(&json).unwrap(); - assert_eq!(restored, response); - } - - #[test] - fn create_response_round_trips_with_root_pane() { - let response = SuccessResponse { - id: "req_2".into(), - result: ResponseResult::TabCreated { - tab: TabInfo { - tab_id: "w1:t2".into(), - workspace_id: "w1".into(), - number: 2, - label: "review".into(), - focused: false, - pane_count: 1, - agent_status: AgentStatus::Unknown, - }, - root_pane: PaneInfo { - pane_id: "w1:p3".into(), - terminal_id: "term_example".into(), - workspace_id: "w1".into(), - tab_id: "w1:t2".into(), - focused: false, - cwd: Some("/tmp/review".into()), - foreground_cwd: None, - label: None, - agent: None, - title: None, - display_agent: None, - agent_status: AgentStatus::Unknown, - custom_status: None, - state_labels: HashMap::new(), - agent_session: None, - revision: 0, - }, - }, - }; - - let json = serde_json::to_string(&response).unwrap(); - assert!(json.contains("\"type\":\"tab_created\"")); - assert!(json.contains("\"root_pane\"")); - let restored: SuccessResponse = serde_json::from_str(&json).unwrap(); - assert_eq!(restored, response); - } - - #[test] - fn error_response_round_trips() { - let response = ErrorResponse { - id: "req_1".into(), - error: ErrorBody { - code: "pane_not_found".into(), - message: "pane p_1 not found".into(), - }, - }; - - let json = serde_json::to_string(&response).unwrap(); - let restored: ErrorResponse = serde_json::from_str(&json).unwrap(); - assert_eq!(restored, response); - } - - #[test] - fn event_wait_parses_typed_match() { - let json = r#" - { - "id": "req_9", - "method": "events.wait", - "params": { - "match_event": { - "event": "pane_agent_status_changed", - "pane_id": "p_1", - "agent_status": "done" - }, - "timeout_ms": 30000 - } - } - "#; - - let request: Request = serde_json::from_str(json).unwrap(); - let Method::EventsWait(params) = request.method else { - panic!("wrong method parsed"); - }; - assert_eq!( - params.match_event, - EventMatch::PaneAgentStatusChanged { - pane_id: "p_1".into(), - agent_status: AgentStatus::Done, - } - ); - } -} +mod tests; |
