summaryrefslogtreecommitdiff
path: root/src/api/schema.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-14 02:43:45 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-14 03:47:07 +0300
commitfbd20ad6e488fb3b9efc1612fdffee6e672976c1 (patch)
treed21daf93241786f8aa6fde23b41cdb9e15182b79 /src/api/schema.rs
parentd35c642ef36ee5ac2afe7c1b18ad5a717bc807d8 (diff)
feat: add plugin v1 system
Add manifest-driven plugin actions, event hooks, pane entrypoints, link handlers, and host APIs. Support plugin link, GitHub install, build commands, installed plugin registry, pane movement, runtime context, and plugin docs.
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;