Skip to main content

supercode_harness/
runtime_mail.rs

1//! The default delivery tier of the mailbox: sessions supercode controls.
2//!
3//! A session supercode hosts as a runtime (any harness: Codex over
4//! app-server, Claude Code over stream-json, pi, the ACP harnesses, OpenCode)
5//! registers a live-runtime receipt ([`crate::live_runtime`]). Mail to such a
6//! session goes through the runtime's own doors, the same ones every
7//! frontend uses: `steer` while a turn runs, `send_input` to start one. No
8//! hook, relay or per-harness code is involved; those are the degraded tier,
9//! for sessions supercode does not control.
10//!
11//! The text delivered is the rendered envelope: who sent it, that it is not
12//! the user, and how to answer.
13
14use crate::frontend::{FrontendRuntime, FrontendTurnState, HttpFrontendRuntime};
15use crate::live_runtime::{list_live_runtimes, resolve_live_runtime, LiveRuntimeRecord};
16
17/// The live runtime supercode hosts for `harness`'s session `session_id`, if
18/// it controls that session.
19pub fn controlled_runtime(harness: &str, session_id: &str) -> Option<LiveRuntimeRecord> {
20    list_live_runtimes().ok()?.into_iter().find(|record| {
21        record.source.harness == harness
22            && (record.source.session_id == session_id || record.runtime_session_id == session_id)
23    })
24}
25
26/// Every session supercode controls on this machine.
27pub fn controlled_runtimes() -> Vec<LiveRuntimeRecord> {
28    list_live_runtimes().unwrap_or_default()
29}
30
31/// How a message entered a controlled session.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum RuntimeDelivery {
34    /// A turn was running; the message was steered into it.
35    Steered,
36    /// The session was idle; the message started a turn.
37    Started,
38    /// The session was idle and the sender asked not to wake it; nothing was
39    /// delivered into the runtime.
40    NotWoken,
41}
42
43/// The runtime's turn state right now.
44pub async fn runtime_turn_state(record: &LiveRuntimeRecord) -> Result<FrontendTurnState, String> {
45    let remote = connect(record).await?;
46    FrontendRuntime::describe(remote.as_ref())
47        .await
48        .map(|descriptor| descriptor.turn_state)
49        .map_err(|error| error.to_string())
50}
51
52/// Deliver `text` into a controlled session: steer a running turn, or start
53/// one when `wake` (a `--queue` send leaves an idle session alone).
54pub async fn deliver_to_runtime(
55    record: &LiveRuntimeRecord,
56    text: String,
57    wake: bool,
58) -> Result<RuntimeDelivery, String> {
59    let remote = connect(record).await?;
60    let descriptor = FrontendRuntime::describe(remote.as_ref())
61        .await
62        .map_err(|error| error.to_string())?;
63    if descriptor.session_id != record.runtime_session_id {
64        return Err("the live runtime's identity did not match its receipt".into());
65    }
66    match descriptor.turn_state {
67        FrontendTurnState::Busy if descriptor.actions.steer => {
68            FrontendRuntime::steer(remote.as_ref(), text)
69                .await
70                .map_err(|error| error.to_string())?;
71            Ok(RuntimeDelivery::Steered)
72        }
73        FrontendTurnState::Busy => Err(
74            "a turn is running and this runtime does not accept steering; the message was not \
75             delivered into it"
76                .into(),
77        ),
78        FrontendTurnState::Idle if wake => {
79            FrontendRuntime::send_input_with_images(remote.clone(), text, Vec::new())
80                .await
81                .map_err(|error| error.to_string())?;
82            Ok(RuntimeDelivery::Started)
83        }
84        FrontendTurnState::Idle => Ok(RuntimeDelivery::NotWoken),
85    }
86}
87
88/// The runtime's answer to the message `message_id`: the text of its last
89/// assistant message after the user message that carried that id, read from
90/// the session's own transcript (a hosted runtime's attach history can be
91/// empty; its harness's transcript never is). `None` until it has answered.
92pub fn answer_to(
93    homes: &crate::HarnessHomes,
94    record: &LiveRuntimeRecord,
95    message_id: &str,
96) -> Option<String> {
97    let query = crate::DiscoveryQuery {
98        harnesses: vec![crate::HarnessId::new(record.source.harness.clone())],
99        homes: homes.clone(),
100        workspace: Some(record.source.workspace.clone()),
101        ..Default::default()
102    };
103    let descriptor = crate::discover_sessions(&query)
104        .ok()?
105        .into_iter()
106        .find(|descriptor| {
107            descriptor.locator.session_id == record.source.session_id
108                || descriptor.locator.session_id == record.runtime_session_id
109        })?;
110    let session =
111        crate::sdk::load_session_with_fidelity(&descriptor.locator, crate::Fidelity::Semantic)
112            .ok()?;
113    let text = |message: &crate::ChatMessage| message.content.clone().unwrap_or_default();
114    let asked = session.messages.iter().rposition(|message| {
115        message.role == crate::Role::User && text(message).contains(message_id)
116    })?;
117    session.messages[asked + 1..]
118        .iter()
119        .rev()
120        .find(|message| message.role == crate::Role::Assistant && !text(message).trim().is_empty())
121        .map(text)
122}
123
124async fn connect(
125    record: &LiveRuntimeRecord,
126) -> Result<std::sync::Arc<HttpFrontendRuntime>, String> {
127    let receipt = resolve_live_runtime(&record.endpoint, &record.source)
128        .map_err(|error| error.to_string())?;
129    HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
130        .await
131        .map_err(|error| error.to_string())
132}