supercode-interchange 0.4.19

Canonical, provider-neutral session interchange primitives for Supercode
Documentation
//! The conversation-on-a-surface nouns (ORCH-3 / ORCH-6): where a
//! conversation is reached, why it exists, which job it belongs to, where it
//! moved. They are the vocabulary a [`crate::ontology::Binding`] is made of and
//! the wire block every discovery row carries.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// ORCH-3: why a session exists (the trigger noun; `docs/HERMES-IDEAL-SUPPORT-DESIGN.md` §3).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum Trigger {
    /// A person started it on a terminal, IDE, or chat surface.
    Human,
    /// A message on a channel opened it.
    Channel,
    /// A scheduled job fired it.
    Cron,
    /// A periodic main-session turn.
    Heartbeat,
    /// An inbound webhook / mapped hook.
    Webhook,
    /// Spawned by a parent session (delegate / subagent).
    Parent,
    /// An HTTP API / bridge client.
    Api,
    /// A board's dispatcher spawned it to work a task (the workflow layer).
    Task,
    /// The source does not say.
    #[default]
    Unknown,
}

/// ORCH-3: the conversation identity on a surface. Full tuple on a channel;
/// degenerate (all `None`) on a terminal.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
pub struct SurfaceKey {
    /// The harness's own key string, verbatim.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub key: Option<String>,
    /// Transport / platform (`telegram`, `slack`, `acp`, …).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub platform: Option<String>,
    /// `dm` | `group` | `channel` | `thread` | `main` (harness vocabulary, verbatim).
    /// `chat_type` is the orchestrator IR's spelling of the same field.
    #[serde(default, skip_serializing_if = "Option::is_none", alias = "chat_type")]
    pub kind: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    /// The chat / group / channel id on the platform.
    pub chat_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    /// The thread or topic within the chat.
    pub thread_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    /// The participant, when the surface is per person.
    pub participant_id: Option<String>,
}

impl SurfaceKey {
    /// A key with no platform is not a channel surface.
    pub fn is_channel(&self) -> bool {
        self.platform.is_some()
    }
}

/// ORCH-3: the job a recurring session belongs to.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct Recurrence {
    /// The job id.
    pub job_id: String,
    /// `cron` | `heartbeat`.
    #[serde(default = "recurrence_kind_cron")]
    pub kind: String,
}

/// ORCH-3: a conversation moved to another surface (Hermes `handoff_*`).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct CrossSurface {
    /// `pending` | `done` | `failed` (the source's own word).
    pub state: String,
    /// The destination platform.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub platform: Option<String>,
    /// The failure, when `state` is `failed`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

/// ORCH-3 / UNI-9: the typed workspace, derived — never stored.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum WorkspaceKind {
    /// A repository checkout (a cwd).
    Repo,
    /// A chat surface with no checkout.
    Channel,
    /// Neither.
    None,
}

/// ORCH-6: the wire form of [`SessionMeta::workspace`] — a typed workspace
/// carried on a discovered or loaded row. The D2 precedence that produces it
/// lives in `workspace()` (ORCH-3's contract); this only names the result.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct WorkspaceRef {
    /// `repo` | `channel` | `none`.
    pub kind: WorkspaceKind,
    /// Path for `repo`, `<platform>:<chat_id>` for `channel`, `None` for `none`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub value: Option<String>,
}

fn recurrence_kind_cron() -> String {
    "cron".to_string()
}