supercode-interchange 0.5.46

Canonical, provider-neutral session interchange primitives for Volter Harness
Documentation
//! The worker: which harness runs a profile's conversations, and how (§2.2).

use std::collections::BTreeMap;

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

use crate::ontology::{HarnessId, SecretRef};

/// A worker environment value: a literal, or a secret by reference.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum EnvValue {
    /// A plain, non-secret value.
    Literal(String),
    /// A secret, named and never held.
    Secret(SecretRef),
}

/// What happens to a worker's permission prompt when no human answers.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum PermissionDefault {
    /// Refuse after the timeout.
    #[default]
    Deny,
    /// Allow after the timeout.
    Allow,
}

/// What a prompt raised where nobody attends (a cron fire, a webhook turn) is answered with, at
/// once (Hermes `approvals.cron_mode`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum PermissionUnattended {
    /// Refuse it.
    #[default]
    Deny,
    /// Approve it.
    Approve,
}

impl PermissionUnattended {
    fn is_deny(&self) -> bool {
        *self == Self::Deny
    }
}

/// How prompts are answered when no human is reachable (§4.6).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct PermissionPolicy {
    /// Seconds a relayed prompt waits for an answer.
    #[serde(default = "default_permission_timeout")]
    pub timeout_seconds: u32,
    /// The answer given when nobody replies in time.
    #[serde(default)]
    pub default: PermissionDefault,
    /// The answer given at once where nobody attends: a cron fire or a webhook turn.
    #[serde(default, skip_serializing_if = "PermissionUnattended::is_deny")]
    pub unattended: PermissionUnattended,
}

fn default_permission_timeout() -> u32 {
    300
}

impl Default for PermissionPolicy {
    fn default() -> Self {
        Self {
            timeout_seconds: 300,
            default: PermissionDefault::Deny,
            unattended: PermissionUnattended::Deny,
        }
    }
}

/// Whose Claude home (or harness home) a worker runs in.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum WorkerHome {
    /// The profile folder is the harness's whole config home (its persona, skills and MCP servers only).
    #[default]
    Profile,
    /// The user's own harness home (their instructions, skills, hooks, plugins, MCP servers and login), with the
    /// profile's persona and skills brought in beside it for the session.
    User,
}

impl WorkerHome {
    fn is_profile(&self) -> bool {
        matches!(self, Self::Profile)
    }
}

/// How a board task's session runs when the board dispatches it to this profile.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum WorkerSurface {
    /// Hermes's worker: one query, headless, closed when it answers.
    #[default]
    Headless,
    /// An interactive session in a terminal pane on the machine the card names, started with a
    /// session id the dispatcher chose, kept open, resumed when lost and replaced from the card only
    /// when it cannot be resumed.
    Pane,
}

impl WorkerSurface {
    fn is_headless(&self) -> bool {
        matches!(self, Self::Headless)
    }
}

/// The worker specification (O-record; Hermes has no worker choice).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct WorkerSpec {
    /// Any registry id whose runtime can start a session.
    pub harness: HarnessId,
    /// Model, where the harness's door accepts one.
    #[serde(default)]
    pub model: Option<String>,
    /// supercode preset name.
    #[serde(default)]
    pub preset: Option<String>,
    /// Relative to the profile dir; `.` by default.
    #[serde(default = "default_cwd")]
    pub cwd: String,
    /// Extra environment for the worker process; secrets by reference.
    #[serde(default)]
    pub env: BTreeMap<String, EnvValue>,
    /// Permission prompt policy.
    #[serde(default)]
    pub permission: PermissionPolicy,
    /// Whose harness home the worker runs in.
    #[serde(default, skip_serializing_if = "WorkerHome::is_profile")]
    pub home: WorkerHome,
    /// How the board runs this profile's tasks.
    #[serde(default, skip_serializing_if = "WorkerSurface::is_headless")]
    pub surface: WorkerSurface,
    /// At most this many of the profile's board tasks run at once; the rest wait in `ready`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub capacity: Option<u32>,
}

fn default_cwd() -> String {
    ".".to_string()
}