aboutsummaryrefslogtreecommitdiff
path: root/src/api/schema/events.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/schema/events.rs')
-rw-r--r--src/api/schema/events.rs474
1 files changed, 474 insertions, 0 deletions
diff --git a/src/api/schema/events.rs b/src/api/schema/events.rs
new file mode 100644
index 00000000..353f75b9
--- /dev/null
+++ b/src/api/schema/events.rs
@@ -0,0 +1,474 @@
+use std::collections::HashMap;
+
+use serde::{Deserialize, Serialize};
+
+use super::common::{AgentStatus, ReadSource};
+use super::panes::{PaneInfo, PaneReadResult};
+use super::tabs::TabInfo;
+use super::workspaces::WorkspaceInfo;
+use super::worktrees::WorktreeInfo;
+
+#[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 = "worktree.created")]
+ WorktreeCreated {},
+ #[serde(rename = "worktree.opened")]
+ WorktreeOpened {},
+ #[serde(rename = "worktree.removed")]
+ WorktreeRemoved {},
+ #[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 = "super::common::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 = "super::common::default_true")]
+ pub strip_ansi: bool,
+}
+
+#[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,
+ WorktreeCreated,
+ WorktreeOpened,
+ WorktreeRemoved,
+ TabCreated,
+ TabClosed,
+ TabRenamed,
+ TabFocused,
+ PaneCreated,
+ PaneClosed,
+ PaneFocused,
+ PaneMoved,
+ PaneOutputChanged,
+ PaneExited,
+ PaneAgentDetected,
+ PaneAgentStatusChanged,
+}
+
+impl EventKind {
+ pub fn dot_name(self) -> &'static str {
+ match self {
+ EventKind::WorkspaceCreated => "workspace.created",
+ EventKind::WorkspaceUpdated => "workspace.updated",
+ EventKind::WorkspaceClosed => "workspace.closed",
+ EventKind::WorkspaceRenamed => "workspace.renamed",
+ EventKind::WorkspaceFocused => "workspace.focused",
+ EventKind::WorktreeCreated => "worktree.created",
+ EventKind::WorktreeOpened => "worktree.opened",
+ EventKind::WorktreeRemoved => "worktree.removed",
+ EventKind::TabCreated => "tab.created",
+ EventKind::TabClosed => "tab.closed",
+ EventKind::TabRenamed => "tab.renamed",
+ EventKind::TabFocused => "tab.focused",
+ EventKind::PaneCreated => "pane.created",
+ EventKind::PaneClosed => "pane.closed",
+ EventKind::PaneFocused => "pane.focused",
+ EventKind::PaneMoved => "pane.moved",
+ EventKind::PaneOutputChanged => "pane.output_changed",
+ EventKind::PaneExited => "pane.exited",
+ EventKind::PaneAgentDetected => "pane.agent_detected",
+ EventKind::PaneAgentStatusChanged => "pane.agent_status_changed",
+ }
+ }
+}
+
+#[cfg(test)]
+pub const KNOWN_EVENT_KINDS: &[EventKind] = &[
+ EventKind::WorkspaceCreated,
+ EventKind::WorkspaceUpdated,
+ EventKind::WorkspaceClosed,
+ EventKind::WorkspaceRenamed,
+ EventKind::WorkspaceFocused,
+ EventKind::WorktreeCreated,
+ EventKind::WorktreeOpened,
+ EventKind::WorktreeRemoved,
+ EventKind::TabCreated,
+ EventKind::TabClosed,
+ EventKind::TabRenamed,
+ EventKind::TabFocused,
+ EventKind::PaneCreated,
+ EventKind::PaneClosed,
+ EventKind::PaneFocused,
+ EventKind::PaneMoved,
+ EventKind::PaneOutputChanged,
+ EventKind::PaneExited,
+ EventKind::PaneAgentDetected,
+ EventKind::PaneAgentStatusChanged,
+];
+
+pub const PLUGIN_HOOK_EVENT_KINDS: &[EventKind] = &[
+ EventKind::WorkspaceCreated,
+ EventKind::WorkspaceUpdated,
+ EventKind::WorkspaceClosed,
+ EventKind::WorkspaceRenamed,
+ EventKind::WorkspaceFocused,
+ EventKind::WorktreeCreated,
+ EventKind::WorktreeOpened,
+ EventKind::WorktreeRemoved,
+ EventKind::TabCreated,
+ EventKind::TabClosed,
+ EventKind::TabRenamed,
+ EventKind::TabFocused,
+ EventKind::PaneCreated,
+ EventKind::PaneClosed,
+ EventKind::PaneFocused,
+ EventKind::PaneMoved,
+ EventKind::PaneExited,
+ EventKind::PaneAgentDetected,
+ EventKind::PaneAgentStatusChanged,
+];
+
+#[cfg(test)]
+pub fn known_event_names() -> Vec<&'static str> {
+ KNOWN_EVENT_KINDS
+ .iter()
+ .copied()
+ .map(EventKind::dot_name)
+ .collect()
+}
+
+/// Event names that manifest `[[events]] on` hooks can reference. This is
+/// intentionally narrower than `EventKind` until high-volume output-change hook
+/// semantics are implemented.
+pub fn plugin_hook_event_names() -> Vec<&'static str> {
+ PLUGIN_HOOK_EVENT_KINDS
+ .iter()
+ .copied()
+ .map(EventKind::dot_name)
+ .collect()
+}
+
+#[cfg(test)]
+mod known_event_name_tests {
+ use super::*;
+
+ #[test]
+ fn known_event_names_stay_in_sync_with_event_kind() {
+ let mut from_kind = KNOWN_EVENT_KINDS
+ .iter()
+ .map(|kind| kind.dot_name())
+ .collect::<Vec<_>>();
+ from_kind.sort_unstable();
+ let mut known = known_event_names();
+ known.sort_unstable();
+ assert_eq!(
+ from_kind, known,
+ "known_event_names() out of sync with EventKind"
+ );
+ }
+
+ #[test]
+ fn plugin_hook_event_names_exclude_unemitted_output_change() {
+ let names = plugin_hook_event_names();
+ assert!(!names.contains(&"pane.output_changed"));
+ assert!(names.contains(&"pane.moved"));
+ }
+}
+
+#[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,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ workspace: Option<WorkspaceInfo>,
+ },
+ WorkspaceRenamed {
+ workspace_id: String,
+ label: String,
+ },
+ WorkspaceFocused {
+ workspace_id: String,
+ },
+ WorktreeCreated {
+ workspace: WorkspaceInfo,
+ worktree: WorktreeInfo,
+ },
+ WorktreeOpened {
+ workspace: WorkspaceInfo,
+ worktree: WorktreeInfo,
+ already_open: bool,
+ },
+ WorktreeRemoved {
+ workspace_id: String,
+ worktree: WorktreeInfo,
+ forced: bool,
+ },
+ 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>,
+ },
+}