aboutsummaryrefslogtreecommitdiff
path: root/src/api/schema.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/schema.rs')
-rw-r--r--src/api/schema.rs1534
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!(
- &params.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!(
- &params.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;