Skip to main content

supercode_harness/
harness_service.rs

1//! Versioned, language-neutral service over persisted harness sessions.
2//!
3//! The service is transport-agnostic: [`HarnessSessionService::handle`] accepts
4//! one JSON-RPC value and [`HarnessSessionService::poll`] produces subscription
5//! notifications. The CLI exposes those primitives as NDJSON over stdio.
6
7use std::collections::{BTreeMap, BTreeSet};
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use std::time::Duration;
11
12use serde::{Deserialize, Serialize};
13use serde_json::{json, Value};
14use tokio::sync::Notify;
15
16use crate::reduce;
17use crate::runtime::generated_session_id;
18#[cfg(feature = "adapter-api")]
19use crate::runtime::{HostedHarnessConnection, HostedHarnessRuntime};
20use crate::sdk::{
21    discover_session_page, load_session, load_session_with_fidelity, SdkCapabilities, SdkError,
22    SdkErrorCode, SdkEvent, SdkOperation, SdkRequest, SdkRuntimeEvent, SdkService,
23};
24use crate::Fidelity;
25#[cfg(feature = "adapter-api")]
26use crate::SupercodeHttpRuntimeBackend;
27use crate::{
28    discover_live_runtime, harness_support_registry, AcpRuntimeBackend, ClaudeCodeRuntimeBackend,
29    CodexRuntimeBackend, DiscoveryQuery, HarnessCatalog, HarnessHomes, HarnessId,
30    ImplementationKind, LiveRuntimeEndpoint, LiveRuntimeSource, OpenCodeRuntimeBackend,
31    PiRuntimeBackend, Role, RuntimeAttachRequest, RuntimeBackend, RuntimeConnection, RuntimeInput,
32    RuntimeLaunch, RuntimeStartRequest, Session, SessionDescriptor, SessionFollower, SessionFormat,
33    SessionLocator, SessionSource,
34};
35#[cfg(feature = "adapter-api")]
36use crate::{register_live_runtime, resolve_live_runtime, LiveRuntimeRegistration};
37use supercode_interchange::watch::{bound_session_view, message_json, normalized_session_json};
38
39/// Every JSON-RPC method the harness service dispatches (`harness.v1.capabilities`
40/// reports it; ORCH-4 registry tiers must cite entries of it).
41pub const HARNESS_SERVICE_METHODS: &[&str] = &[
42    "harness.v1.support.report",
43    "harness.v1.harnesses.list",
44    "harness.v1.harnesses.probe",
45    "harness.v1.harnesses.settings",
46    "harness.v1.harnesses.configure",
47    "harness.v1.harnesses.auth.methods",
48    "harness.v1.harnesses.auth.begin",
49    "harness.v1.harnesses.auth.verify",
50    "harness.v1.sessions.discover",
51    "harness.v1.sessions.load",
52    "harness.v1.sessions.follow",
53    "harness.v1.sessions.unfollow",
54    "harness.v1.sessions.activity.subscribe",
55    "harness.v1.sessions.activity.unsubscribe",
56    "harness.v1.sessions.activity_under",
57    "harness.v1.sessions.index.subscribe",
58    "harness.v1.sessions.index.resize",
59    "harness.v1.sessions.index.unsubscribe",
60    "harness.v1.sessions.message",
61    "harness.v1.sessions.inbox",
62    "harness.v1.sessions.import",
63    "harness.v1.sessions.export",
64    "harness.v1.sessions.translate",
65    "harness.v1.sessions.reduce",
66    "harness.v1.sessions.branch",
67    "harness.v1.sessions.handoff",
68    "harness.v1.sessions.materialize",
69    "harness.v1.sessions.resume_instructions",
70    "harness.v1.skills.list",
71    "harness.v1.skills.install",
72    "harness.v1.skills.remove",
73    "harness.v1.memory.show",
74    "harness.v1.memory.search",
75    "harness.v1.jobs.list",
76    "harness.v1.jobs.get",
77    "harness.v1.jobs.create",
78    "harness.v1.jobs.update",
79    "harness.v1.jobs.pause",
80    "harness.v1.jobs.resume",
81    "harness.v1.jobs.run",
82    "harness.v1.jobs.delete",
83    "harness.v1.jobs.notepad",
84    "harness.v1.jobs.notepad_set",
85    "harness.v1.jobs.notepad_delete",
86    "harness.v1.sessions.new",
87    "harness.v1.sessions.reset",
88    "harness.v1.sessions.archive",
89    "harness.v1.sessions.delete",
90    "harness.v1.runs.list",
91    "harness.v1.runs.get",
92    "harness.v1.approvals.list",
93    "harness.v1.approvals.resolve",
94    "harness.v1.runtimes.capabilities",
95    "harness.v1.runtimes.start",
96    "harness.v1.runtimes.resume",
97    "harness.v1.runtimes.attach_existing",
98    "harness.v1.runtimes.attach",
99    "harness.v1.runtimes.send_input",
100    "harness.v1.runtimes.interrupt",
101    "harness.v1.runtimes.steer",
102    "harness.v1.runtimes.respond",
103    "harness.v1.runtimes.terminal_instructions",
104    "harness.v1.runtimes.acquire_control",
105    "harness.v1.runtimes.heartbeat",
106    "harness.v1.runtimes.detach",
107    "harness.v1.runtimes.close",
108    "harness.v1.profiles.list",
109    "harness.v1.profiles.get",
110    "harness.v1.profiles.create",
111    "harness.v1.profiles.delete",
112    "harness.v1.channels.list",
113    "harness.v1.routes.list",
114    "harness.v1.triggers.list",
115    "harness.v1.channels.status",
116    "harness.v1.orchestration.load",
117    "harness.v1.orchestration.save",
118    "harness.v1.orchestration.compile",
119    "harness.v1.orchestration.decompile",
120    "harness.v1.orchestration.import",
121    "harness.v1.orchestration.export",
122    "harness.v1.workflow.load",
123];
124
125/// Protocol namespace implemented by this service.
126pub const HARNESS_SERVICE_VERSION: &str = "harness.v1";
127/// Notification method emitted for followed-session changes.
128pub const SESSION_EVENT_METHOD: &str = "harness.v1.sessions.event";
129/// Notification method emitted for normalized session-activity transitions.
130pub const SESSION_ACTIVITY_EVENT_METHOD: &str = "harness.v1.sessions.activity_event";
131/// Notification method emitted for revisioned session-list changes.
132pub const SESSION_INDEX_EVENT_METHOD: &str = "harness.v1.sessions.index_event";
133/// Notification method emitted for live runtime events.
134pub const RUNTIME_EVENT_METHOD: &str = "harness.v1.runtimes.event";
135
136/// Stateful persisted-session service. Each instance owns its follow
137/// subscriptions; discovery and loading remain read-only.
138pub struct HarnessSessionService {
139    catalog: HarnessCatalog,
140    followers: BTreeMap<String, SessionFollower>,
141    followed_sources: BTreeMap<String, FollowedSource>,
142    activity_subscriptions: BTreeMap<String, ActivitySubscription>,
143    index_subscriptions: BTreeMap<String, crate::session_index::SessionIndexSubscription>,
144    index_notifier: Arc<Notify>,
145    #[cfg(feature = "adapter-api")]
146    activity_monitor: crate::session_activity::SessionActivityMonitor,
147    next_subscription: u64,
148    runtimes: BTreeMap<String, Box<dyn RuntimeConnection>>,
149    /// Connections lent to a detached call that is running right now. The
150    /// runtime itself is OUT of `runtimes` for that whole call, and these
151    /// names are how a second caller is told the connection is busy rather
152    /// than unknown.
153    runtimes_in_flight: BTreeSet<String>,
154    terminal_launches: BTreeMap<String, StructuredLaunch>,
155    runtime_sequences: BTreeMap<String, u64>,
156    next_runtime: u64,
157    reduction_store_root: Option<PathBuf>,
158    /// ORCH-9: live permission/approval requests outstanding on the open
159    /// runtime connections above, fed by the same event pump that publishes
160    /// `harness.v1.runtimes.event`.
161    approvals: crate::approvals::ApprovalRegistry,
162    /// ORCH-9: supercode's own queued subagent approvals, when the host that
163    /// owns this service publishes its parent queue here.
164    subagent_approvals: Option<Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>>,
165}
166
167impl Default for HarnessSessionService {
168    fn default() -> Self {
169        Self::new()
170    }
171}
172
173impl HarnessSessionService {
174    /// Create an empty service instance.
175    pub fn new() -> Self {
176        Self {
177            catalog: HarnessCatalog::new(),
178            followers: BTreeMap::new(),
179            followed_sources: BTreeMap::new(),
180            activity_subscriptions: BTreeMap::new(),
181            index_subscriptions: BTreeMap::new(),
182            index_notifier: Arc::new(Notify::new()),
183            #[cfg(feature = "adapter-api")]
184            activity_monitor: Default::default(),
185            next_subscription: 1,
186            runtimes: BTreeMap::new(),
187            runtimes_in_flight: BTreeSet::new(),
188            terminal_launches: BTreeMap::new(),
189            runtime_sequences: BTreeMap::new(),
190            next_runtime: 1,
191            reduction_store_root: None,
192            approvals: crate::approvals::ApprovalRegistry::new(),
193            subagent_approvals: None,
194        }
195    }
196
197    /// Override the trusted, service-owned store used for durable reduction
198    /// bundles. Embedders and tests use this to keep all writes inside an
199    /// explicitly selected root; the CLI otherwise uses the normal
200    /// `$SUPERCODE_HOME/sessions` location.
201    pub fn with_reduction_store_root(mut self, root: impl Into<PathBuf>) -> Self {
202        self.reduction_store_root = Some(root.into());
203        self
204    }
205
206    /// ORCH-9: publish the parent's own subagent-approval queue into
207    /// `harness.v1.approvals.list`.
208    ///
209    /// This is the SAME `Arc` an [`crate::Agent`] pushes into
210    /// (`Agent::pending_child_approvals`), so a host that runs supercode's own
211    /// loop beside this service surfaces those requests through the uniform
212    /// door without copying them anywhere.
213    pub fn observe_subagent_approvals(
214        &mut self,
215        queue: Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>,
216    ) {
217        self.subagent_approvals = Some(queue);
218    }
219
220    /// ORCH-9: every approval request this service can see, newest last.
221    ///
222    /// Two sources, both live: the requests outstanding on the open runtime
223    /// connections, and supercode's own queued subagent approvals. There is
224    /// no file or database source at the pinned harness versions (see
225    /// [`crate::approvals`]), so a stored or proposal row is never produced.
226    pub fn approvals(&self, query: &crate::approvals::ApprovalsQuery) -> Vec<crate::ApprovalRow> {
227        let now = crate::approvals::now_ms();
228        let mut rows = self.approvals.rows(now);
229        if let Some(queue) = self.subagent_approvals.as_ref() {
230            let queued = queue
231                .lock()
232                .unwrap_or_else(std::sync::PoisonError::into_inner)
233                .clone();
234            rows.extend(crate::approvals::subagent_rows(&queued, now));
235        }
236        rows.retain(|row| query.matches(row));
237        rows.sort_by(|left, right| {
238            left.requested_at_ms
239                .cmp(&right.requested_at_ms)
240                .then_with(|| left.id.cmp(&right.id))
241        });
242        rows
243    }
244
245    /// ORCH-20 (controlled tier): answer one listed approval request by its
246    /// row id and one uniform decision.
247    ///
248    /// The decision is translated into the option token and reply envelope
249    /// the door that raised the request already accepts
250    /// ([`crate::approvals::plan_reply`]), and the answer is then sent by
251    /// calling `harness.v1.runtimes.respond` itself — the same code path, the
252    /// same adapter, the same bookkeeping that drops the row. This verb adds
253    /// a translation and nothing else.
254    async fn approvals_resolve(
255        &mut self,
256        params: Value,
257    ) -> std::result::Result<Value, ServiceError> {
258        let params = decode::<crate::approvals::ApprovalsResolveParams>(params)?;
259        if params.id.trim().is_empty() {
260            return Err(ServiceError::InvalidParams(
261                "approvals resolve requires the `id` of a listed approval row".into(),
262            ));
263        }
264        let choice = match (params.decision, params.option_id.as_deref()) {
265            (Some(_), Some(_)) => {
266                return Err(ServiceError::InvalidParams(
267                    "approvals resolve takes either `decision` or `option_id`, not both".into(),
268                ))
269            }
270            (Some(decision), None) => crate::approvals::ApprovalChoice::Decision(decision),
271            (None, Some(option)) => crate::approvals::ApprovalChoice::Option(option.to_string()),
272            (None, None) => {
273                return Err(ServiceError::InvalidParams(format!(
274                    "approvals resolve requires `decision` ({}) or an explicit `option_id`",
275                    crate::approvals::ApprovalDecision::ALL
276                        .map(|decision| decision.as_str())
277                        .join(" | "),
278                )))
279            }
280        };
281        let resolution = self
282            .approvals
283            .resolution(&params.id, &choice)
284            .map_err(|error| ServiceError::InvalidParams(error.to_string()))?;
285        // The harness's own door, unchanged: this is the identical call
286        // `harness.v1.runtimes.respond` performs for a caller who built the
287        // envelope by hand, including dropping the answered row.
288        self.runtime_call(
289            "harness.v1.runtimes.respond",
290            json!({
291                "connection": resolution.connection,
292                "request_id": resolution.request_id,
293                "response": resolution.response,
294            }),
295        )
296        .await?;
297        Ok(json!({
298            "id": params.id,
299            "decision": params.decision.map(|decision| decision.as_str()),
300            "option_id": resolution.option_id,
301            "resolved": true,
302        }))
303    }
304
305    /// Return the edge-triggered wakeup used by session-index filesystem
306    /// subscriptions. Transports can await this instead of polling indexes.
307    #[cfg(feature = "adapter-api")]
308    pub fn session_index_notifier(&self) -> Arc<Notify> {
309        Arc::clone(&self.index_notifier)
310    }
311
312    /// Handle one JSON-RPC 2.0 request and return one JSON-RPC response.
313    #[cfg(feature = "adapter-api")]
314    pub fn handle(&mut self, request: Value) -> Value {
315        let id = request.get("id").cloned().unwrap_or(Value::Null);
316        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
317            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
318        }
319        let Some(method) = request.get("method").and_then(Value::as_str) else {
320            return rpc_error(id, -32600, "request is missing `method`");
321        };
322        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
323        match self.call(method, params) {
324            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
325            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
326            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
327            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
328            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
329            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
330        }
331    }
332
333    /// Handle either a persisted-session request or an asynchronous live
334    /// runtime request.
335    #[cfg(feature = "adapter-api")]
336    pub async fn handle_async(&mut self, request: Value) -> Value {
337        let method = request
338            .get("method")
339            .and_then(Value::as_str)
340            .unwrap_or_default();
341        if matches!(
342            method,
343            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe"
344        ) {
345            let id = request.get("id").cloned().unwrap_or(Value::Null);
346            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
347                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
348            }
349            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
350            return match self.inventory_call(method, params).await {
351                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
352                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
353                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
354                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
355                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
356                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
357            };
358        }
359        if matches!(
360            method,
361            "harness.v1.harnesses.auth.methods"
362                | "harness.v1.harnesses.auth.begin"
363                | "harness.v1.harnesses.auth.verify"
364        ) {
365            let id = request.get("id").cloned().unwrap_or(Value::Null);
366            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
367                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
368            }
369            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
370            return match self.harness_authentication_call(method, params).await {
371                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
372                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
373                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
374                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
375                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
376                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
377            };
378        }
379        // ORCH-19 controlled tier. Answered here rather than through the SDK
380        // operation dispatch below so the harness's OWN refusal reaches the
381        // caller: `sdk_error` collapses every `UnsupportedAction` to one
382        // generic sentence, and the whole point of this tier is that a
383        // refusal names which door the harness does have.
384        if matches!(
385            method,
386            "harness.v1.sessions.new"
387                | "harness.v1.sessions.reset"
388                | "harness.v1.sessions.archive"
389                | "harness.v1.sessions.delete"
390        ) {
391            let id = request.get("id").cloned().unwrap_or(Value::Null);
392            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
393                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
394            }
395            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
396            let verb = match method {
397                "harness.v1.sessions.new" => crate::SessionVerb::New,
398                "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
399                "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
400                _ => crate::SessionVerb::Delete,
401            };
402            return match self.mutate_session(verb, params).await {
403                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
404                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
405                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
406                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
407                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
408                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
409            };
410        }
411        if method == "harness.v1.sessions.message" {
412            let id = request.get("id").cloned().unwrap_or(Value::Null);
413            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
414                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
415            }
416            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
417            return match self.message_call(params).await {
418                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
419                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
420                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
421                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
422                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
423                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
424            };
425        }
426        if matches!(
427            method,
428            "harness.v1.harnesses.settings" | "harness.v1.harnesses.configure"
429        ) {
430            let id = request.get("id").cloned().unwrap_or(Value::Null);
431            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
432                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
433            }
434            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
435            return match self.harness_settings_call(method, params) {
436                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
437                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
438                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
439                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
440                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
441                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
442            };
443        }
444        if method == "harness.v1.sessions.activity_under" {
445            let id = request.get("id").cloned().unwrap_or(Value::Null);
446            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
447            return match activity_under_call(params).await {
448                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
449                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
450                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
451                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
452                Err(_) => rpc_error(id, -32000, "sessions.activity_under failed"),
453            };
454        }
455        if method == "harness.v1.sessions.activity.subscribe" {
456            let id = request.get("id").cloned().unwrap_or(Value::Null);
457            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
458                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
459            }
460            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
461            return match self.subscribe_session_activity(params).await {
462                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
463                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
464                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
465                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
466                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
467                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
468            };
469        }
470        if let Some(operation) = SdkOperation::from_method(method) {
471            let id = request.get("id").cloned().unwrap_or(Value::Null);
472            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
473                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
474            }
475            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
476            return match self.execute(SdkRequest { operation, params }).await {
477                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
478                Err(error) => sdk_rpc_error(id, &error),
479            };
480        }
481        if !method.starts_with("harness.v1.runtimes.") {
482            return self.handle(request);
483        }
484        let id = request.get("id").cloned().unwrap_or(Value::Null);
485        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
486            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
487        }
488        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
489        match self.runtime_call(method, params).await {
490            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
491            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
492            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
493            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
494            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
495            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
496        }
497    }
498
499    /// Poll all active subscriptions once and return zero or more JSON-RPC
500    /// notifications. Recoverable follower errors are delivered as events.
501    #[cfg(feature = "adapter-api")]
502    pub fn poll(&mut self) -> Vec<Value> {
503        let mut notifications = Vec::new();
504        for (subscription, follower) in &mut self.followers {
505            match follower.poll() {
506                Ok(Some(event)) => notifications.push(json!({
507                    "jsonrpc": "2.0",
508                    "method": SESSION_EVENT_METHOD,
509                    "params": {
510                        "subscription": subscription,
511                        "event": event.to_json(),
512                    }
513                })),
514                Ok(None) => {}
515                Err(error) => notifications.push(json!({
516                    "jsonrpc": "2.0",
517                    "method": SESSION_EVENT_METHOD,
518                    "params": {
519                        "subscription": subscription,
520                        "event": {
521                            "type": "watch_error",
522                            "recoverable": true,
523                            "message": error.to_string(),
524                        },
525                    }
526                })),
527            }
528        }
529        notifications
530    }
531
532    /// Report each followed session's live-runtime lifecycle state on that
533    /// session's own subscription, emitting only when the state changes.
534    ///
535    /// A growing transcript is not evidence that an agent is working, so the
536    /// state comes from the live-runtime registry and nowhere else. A followed
537    /// session with no registered Supercode runtime — a harness running outside
538    /// Supercode — reports `persisted`, which says plainly that its activity is
539    /// unknown rather than guessing at it. These events carry no sequence
540    /// number and no transcript content; they never interleave with the
541    /// content follower's sequenced stream.
542    #[cfg(feature = "adapter-api")]
543    pub async fn poll_session_runtime_states(&mut self) -> Vec<Value> {
544        let registry = crate::LocalRuntimeRegistry::new();
545        let authorization = crate::RuntimeAuthorization::observer();
546        let mut notifications = Vec::new();
547        for (subscription, source) in &mut self.followed_sources {
548            let state = match registry
549                .source_state(&source.harness, &source.session_id, &authorization)
550                .await
551            {
552                Ok(Some(state)) => state,
553                Ok(None) => crate::RuntimeRegistryState::Persisted,
554                // A failed registry read is not evidence of a state change.
555                Err(_) => continue,
556            };
557            if source.reported.as_deref() == Some(state.as_str()) {
558                continue;
559            }
560            source.reported = Some(state.as_str().to_string());
561            notifications.push(json!({
562                "jsonrpc": "2.0",
563                "method": SESSION_EVENT_METHOD,
564                "params": {
565                    "subscription": subscription,
566                    "event": {"type": "runtime_state", "state": state.as_str()},
567                },
568            }));
569        }
570        notifications
571    }
572
573    /// Poll normalized activity subscriptions, emitting only proven state
574    /// transitions. Every subscription is bulk-sampled so stock-harness
575    /// process and registry discovery happens once per UI, not once per row.
576    #[cfg(feature = "adapter-api")]
577    pub async fn poll_session_activities(&mut self) -> Vec<Value> {
578        let subscriptions = self
579            .activity_subscriptions
580            .iter()
581            .map(|(id, subscription)| {
582                (
583                    id.clone(),
584                    subscription.locators.clone(),
585                    subscription.homes.clone(),
586                )
587            })
588            .collect::<Vec<_>>();
589        let mut notifications = Vec::new();
590        for (subscription_id, locators, homes) in subscriptions {
591            let Ok(activities) = self.activity_monitor.resolve(&locators, &homes).await else {
592                // A failed evidence read proves no transition. Retain the last
593                // good state instead of flashing every row to persisted.
594                continue;
595            };
596            let Some(subscription) = self.activity_subscriptions.get_mut(&subscription_id) else {
597                continue;
598            };
599            let mut changed = Vec::new();
600            for activity in activities {
601                let key = activity.key();
602                if subscription
603                    .reported
604                    .get(&key)
605                    .is_some_and(|previous| previous.same_state(&activity))
606                {
607                    continue;
608                }
609                subscription.reported.insert(key, activity.clone());
610                changed.push(activity);
611            }
612            if !changed.is_empty() {
613                notifications.push(json!({
614                    "jsonrpc": "2.0",
615                    "method": SESSION_ACTIVITY_EVENT_METHOD,
616                    "params": {
617                        "subscription": subscription_id,
618                        "activities": changed,
619                    },
620                }));
621            }
622        }
623        notifications
624    }
625
626    /// Drain native-store invalidations and emit revisioned descriptor deltas.
627    /// An idle subscription performs no catalog or transcript reads between
628    /// its minute-scale recovery reconciliations.
629    #[cfg(feature = "adapter-api")]
630    pub fn poll_session_indexes(&mut self) -> Vec<Value> {
631        let mut notifications = Vec::new();
632        for (subscription, index) in &mut self.index_subscriptions {
633            let homes = index.homes().clone();
634            match index.poll() {
635                Ok(Some(delta)) => match live_index_changes(delta.changes, &homes) {
636                    Ok(changes) => notifications.push(json!({
637                        "jsonrpc": "2.0",
638                        "method": SESSION_INDEX_EVENT_METHOD,
639                        "params": {
640                            "subscription": subscription,
641                            "revision": delta.revision,
642                            "changes": changes,
643                        },
644                    })),
645                    Err(error) => notifications.push(json!({
646                        "jsonrpc": "2.0",
647                        "method": SESSION_INDEX_EVENT_METHOD,
648                        "params": {
649                            "subscription": subscription,
650                            "error": {"recoverable": true, "message": error_message(error)},
651                        },
652                    })),
653                },
654                Ok(None) => {}
655                Err(error) => notifications.push(json!({
656                    "jsonrpc": "2.0",
657                    "method": SESSION_INDEX_EVENT_METHOD,
658                    "params": {
659                        "subscription": subscription,
660                        "error": {"recoverable": true, "message": error},
661                    },
662                })),
663            }
664        }
665        notifications
666    }
667
668    #[cfg(feature = "adapter-api")]
669    async fn subscribe_session_activity(
670        &mut self,
671        params: Value,
672    ) -> std::result::Result<Value, ServiceError> {
673        let params = decode::<ActivitySubscribeParams>(params)?;
674        if params.locators.is_empty() {
675            return Err(ServiceError::InvalidParams(
676                "sessions.activity.subscribe requires at least one locator".into(),
677            ));
678        }
679        if params.locators.len() > 2_048 {
680            return Err(ServiceError::InvalidParams(
681                "sessions.activity.subscribe accepts at most 2048 locators".into(),
682            ));
683        }
684        let initial = self
685            .activity_monitor
686            .resolve(&params.locators, &params.homes)
687            .await
688            .map_err(ServiceError::Sdk)?;
689        let subscription = format!("activity-sub-{}", self.next_subscription);
690        self.next_subscription += 1;
691        let reported = initial
692            .iter()
693            .cloned()
694            .map(|activity| (activity.key(), activity))
695            .collect();
696        self.activity_subscriptions.insert(
697            subscription.clone(),
698            ActivitySubscription {
699                locators: params.locators,
700                homes: params.homes,
701                reported,
702            },
703        );
704        Ok(json!({"subscription": subscription, "initial": initial}))
705    }
706
707    /// Non-blockingly sample one event from every connected live runtime.
708    #[cfg(feature = "adapter-api")]
709    pub async fn poll_runtimes(&mut self) -> Vec<Value> {
710        self.poll_sdk_events()
711            .await
712            .into_iter()
713            .map(|(connection, runtime_event)| {
714                json!({
715                    "jsonrpc": "2.0",
716                    "method": RUNTIME_EVENT_METHOD,
717                    "params": {
718                        "connection": connection,
719                        "session_id": runtime_event.session_id,
720                        "sequence": runtime_event.event.sequence,
721                        "event": {
722                            "kind": runtime_event.event.kind,
723                            "payload": runtime_event.event.payload,
724                        },
725                    },
726                })
727            })
728            .collect()
729    }
730
731    async fn poll_sdk_events(&mut self) -> Vec<(String, SdkRuntimeEvent)> {
732        let mut events = Vec::new();
733        let mut closed = Vec::new();
734        let now_ms = crate::approvals::now_ms();
735        for (connection, runtime) in &mut self.runtimes {
736            let session_id = runtime.handle().runtime_id.clone();
737            let harness = runtime.handle().harness.clone();
738            // Drain what the runtime already has: a turn is several events
739            // (updates, then the protocol's completion), and delivering one
740            // per poll would cost a poll interval each. A zero timeout takes
741            // only what is ready — an idle runtime costs nothing.
742            for _ in 0..256 {
743                match tokio::time::timeout(Duration::ZERO, runtime.next_event()).await {
744                    Ok(Ok(Some(event))) => {
745                        let terminal = event.kind == "transport_closed";
746                        // ORCH-9: a permission/approval request arrives as an
747                        // ordinary event; it becomes listable here and stops
748                        // being listable when `runtimes.respond` answers it.
749                        self.approvals
750                            .observe(connection, &harness, &session_id, &event, now_ms);
751                        let next_sequence = self
752                            .runtime_sequences
753                            .entry(session_id.clone())
754                            .or_insert(0);
755                        let sequence = event.sequence.unwrap_or_else(|| {
756                            *next_sequence = next_sequence.saturating_add(1);
757                            *next_sequence
758                        });
759                        *next_sequence = (*next_sequence).max(sequence);
760                        events.push((
761                            connection.clone(),
762                            SdkRuntimeEvent {
763                                session_id: session_id.clone(),
764                                event: SdkEvent {
765                                    sequence,
766                                    kind: event.kind,
767                                    payload: event.payload,
768                                },
769                            },
770                        ));
771                        if terminal {
772                            closed.push(connection.clone());
773                            break;
774                        }
775                    }
776                    Ok(Ok(None)) => {
777                        let sequence = self
778                            .runtime_sequences
779                            .entry(session_id.clone())
780                            .or_insert(0);
781                        *sequence = sequence.saturating_add(1);
782                        events.push((
783                        connection.clone(),
784                        SdkRuntimeEvent {
785                            session_id,
786                            event: SdkEvent {
787                                sequence: *sequence,
788                                kind: "transport_closed".into(),
789                                payload: json!({"message": "Harness runtime transport closed."}),
790                            },
791                        },
792                    ));
793                        closed.push(connection.clone());
794                        break;
795                    }
796                    Err(_) => break,
797                    Ok(Err(error)) => {
798                        let sequence = self
799                            .runtime_sequences
800                            .entry(session_id.clone())
801                            .or_insert(0);
802                        *sequence = sequence.saturating_add(1);
803                        events.push((
804                        connection.clone(),
805                        SdkRuntimeEvent {
806                            session_id,
807                            event: SdkEvent {
808                                sequence: *sequence,
809                                kind: "transport_error".into(),
810                                payload: json!({"message": error.to_string(), "terminal": true}),
811                            },
812                        },
813                    ));
814                        closed.push(connection.clone());
815                        break;
816                    }
817                }
818            }
819        }
820        for connection in closed {
821            if let Some(runtime) = self.runtimes.remove(&connection) {
822                self.runtime_sequences.remove(&runtime.handle().runtime_id);
823            }
824            self.terminal_launches.remove(&connection);
825            // A connection that is gone cannot answer anything it was
826            // holding; those requests stop being listable with it.
827            self.approvals.forget(&connection);
828        }
829        events
830    }
831
832    fn call(&mut self, method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
833        match method {
834            "harness.v1.capabilities" => Ok(json!({
835                "version": HARNESS_SERVICE_VERSION,
836                "sdk": self.capabilities(),
837                "methods": HARNESS_SERVICE_METHODS,
838                "notifications": [
839                    SESSION_EVENT_METHOD,
840                    SESSION_ACTIVITY_EVENT_METHOD,
841                    SESSION_INDEX_EVENT_METHOD,
842                    RUNTIME_EVENT_METHOD
843                ],
844                "harnesses": harness_support_registry()
845                    .harnesses
846                    .into_iter()
847                    .map(|harness| harness.id)
848                    .collect::<Vec<_>>(),
849            })),
850            "harness.v1.support.report" => serde_json::to_value(harness_support_registry())
851                .map_err(|error| ServiceError::Operation(error.to_string())),
852            "harness.v1.profiles.list" | "harness.v1.profiles.get" => profiles_call(method, params),
853            // ORCH-21 controlled tier. Each verb translates to the HARNESS'S
854            // OWN profile verb and runs it (`crate::profiles_control`);
855            // supercode makes and removes nothing itself. The row returned is
856            // re-read through the ORCH-10 loader afterwards, and `ran`
857            // narrates the exact command.
858            "harness.v1.profiles.create" => {
859                mutate_profile(crate::profiles_control::ProfileVerb::Create, params)
860            }
861            "harness.v1.profiles.delete" => {
862                mutate_profile(crate::profiles_control::ProfileVerb::Delete, params)
863            }
864            "harness.v1.channels.list" | "harness.v1.channels.status" => {
865                channels_call(method, params)
866            }
867            // ORCH-15 observed tier: which profile / agent a surface tuple
868            // resolves to, read from each gateway harness's own config.
869            "harness.v1.routes.list" => routes_call(params),
870            // ORCH-16 observed tier: inbound webhook routes / hook mappings.
871            "harness.v1.triggers.list" => triggers_call(params),
872            // ONT-4: the orchestration doors. One home folder in, one typed orchestration
873            // value out (and back). Every one of the four is
874            // `crate::orchestration_doors`, which the `supercode orchestration` verbs call
875            // too — the RPC adds nothing but the envelope. A vault VALUE
876            // never crosses this wire: a load or a compile answers with the
877            // `.env` KEY NAMES, and a caller that needs a value reads the
878            // home's own `.env`.
879            // the workflow layer's read door: a harness's board as one typed value,
880            // the same code the `supercode workflow load` verb calls
881            "harness.v1.workflow.load" => {
882                let params = decode::<WorkflowLoadParams>(params)?;
883                let read =
884                    crate::workflow_doors::load(params.from, &params.home).map_err(operation)?;
885                serde_json::to_value(read)
886                    .map_err(|error| ServiceError::Operation(error.to_string()))
887            }
888            "harness.v1.orchestration.load" => {
889                let params = decode::<OrchestrationLoadParams>(params)?;
890                let read = crate::orchestration_doors::load(&params.root, params.flavor)
891                    .map_err(operation)?;
892                serde_json::to_value(read)
893                    .map_err(|error| ServiceError::Operation(error.to_string()))
894            }
895            "harness.v1.orchestration.save" => {
896                let params = decode::<OrchestrationSaveParams>(params)?;
897                let saved = crate::orchestration_doors::save(
898                    &params.root,
899                    params.orchestration,
900                    params.vault,
901                )
902                .map_err(operation)?;
903                serde_json::to_value(saved)
904                    .map_err(|error| ServiceError::Operation(error.to_string()))
905            }
906            "harness.v1.orchestration.compile" => {
907                let params = decode::<OrchestrationCompileParams>(params)?;
908                let read = crate::orchestration_doors::compile(params.from, &params.home)
909                    .map_err(operation)?;
910                serde_json::to_value(read)
911                    .map_err(|error| ServiceError::Operation(error.to_string()))
912            }
913            "harness.v1.orchestration.decompile" => {
914                let params = decode::<OrchestrationDecompileParams>(params)?;
915                let report = crate::orchestration_doors::decompile(
916                    params.to,
917                    params.orchestration,
918                    &params.source,
919                    params.source_flavor,
920                    &params.dest,
921                    params.vault,
922                )
923                .map_err(operation)?;
924                serde_json::to_value(report)
925                    .map_err(|error| ServiceError::Operation(error.to_string()))
926            }
927            // a migration keeps the credential in this process: a compile and
928            // a save (import), a load and a decompile (export), composed here
929            // because composed by a client the secret would have to cross
930            // the wire
931            "harness.v1.orchestration.import" => {
932                let params = decode::<OrchestrationImportParams>(params)?;
933                let imported =
934                    crate::orchestration_doors::import(params.from, &params.home, &params.into)
935                        .map_err(operation)?;
936                serde_json::to_value(imported)
937                    .map_err(|error| ServiceError::Operation(error.to_string()))
938            }
939            "harness.v1.orchestration.export" => {
940                let params = decode::<OrchestrationExportParams>(params)?;
941                let report =
942                    crate::orchestration_doors::export(params.to, &params.root, &params.dest)
943                        .map_err(operation)?;
944                serde_json::to_value(report)
945                    .map_err(|error| ServiceError::Operation(error.to_string()))
946            }
947            // ORCH-12 observed tier: read and search the persistent memory
948            // documents a harness keeps on disk. Read-only — every write
949            // (`hermes memory off`, `openclaw memory forget|reset`, Claude
950            // Code's `/memory`) stays the harness's own verb. A harness with
951            // no memory store is refused with UnsupportedAction.
952            "harness.v1.memory.show" | "harness.v1.memory.search" => memory_call(method, params),
953            // ORCH-11 observed tier: read-only enumeration of every harness's
954            // installed skill packages. An unknown harness id is refused with
955            // UnsupportedAction — every harness supports skills, so a filter
956            // that matches nothing is a caller error, never an empty listing.
957            "harness.v1.skills.list" => {
958                let query = decode::<crate::skills::SkillsQuery>(params)?;
959                if let Some(harness) = query.harness.as_deref() {
960                    if !crate::skills::SKILL_HARNESSES.contains(&harness) {
961                        return Err(ServiceError::UnsupportedAction(format!(
962                            "`{harness}` has no skills root Volter Harness reads"
963                        )));
964                    }
965                }
966                serde_json::to_value(crate::skills::list_skills(&query))
967                    .map_err(|error| ServiceError::Operation(error.to_string()))
968            }
969            // ORCH-22 controlled tier: each verb goes through the door the
970            // HARNESS publishes — `hermes skills install|uninstall`,
971            // `openclaw skills install`, and for the core four the loader's
972            // own directory, which is the only skills door those harnesses
973            // have. supercode resolves no registry and unpacks no archive.
974            // The row returned is re-read through the ORCH-11 loader
975            // afterwards, and `ran` narrates exactly what was performed.
976            "harness.v1.skills.install" => {
977                mutate_skill(crate::skills_control::SkillVerb::Install, params)
978            }
979            "harness.v1.skills.remove" => {
980                mutate_skill(crate::skills_control::SkillVerb::Remove, params)
981            }
982            // ORCH-9 observed tier: the approval requests waiting for an
983            // answer. At the pinned harness versions the only uniform source
984            // is a LIVE request held by an open runtime connection, plus
985            // supercode's own queued subagent approvals — neither Hermes
986            // 0.21.0 nor OpenClaw 2026.7.1-2 has an approvals door to read
987            // (see `crate::approvals`). A harness whose runtime cannot carry
988            // a protocol request at all is refused by name.
989            "harness.v1.approvals.list" => {
990                let query = decode::<crate::approvals::ApprovalsQuery>(params)?;
991                if let Some(harness) = query.harness.as_deref() {
992                    if !crate::approvals::lists_approvals(harness) {
993                        return Err(ServiceError::UnsupportedAction(format!(
994                            "`{harness}` has no runtime door that carries an approval request"
995                        )));
996                    }
997                }
998                serde_json::to_value(self.approvals(&query))
999                    .map_err(|error| ServiceError::Operation(error.to_string()))
1000            }
1001            "harness.v1.sessions.discover" => {
1002                let query = decode::<DiscoveryQuery>(params)?;
1003                let page = discover_session_page(&query).map_err(operation)?;
1004                // Claude Code is the one harness that publishes its RUNNING
1005                // sessions. The registry is read once per discovery and joined
1006                // by session id; every record in it has already survived a
1007                // `kill(pid, 0)` liveness check inside `read_registry`.
1008                let doors = crate::mail_route::LiveSessions::read(&query.homes);
1009                let activities = crate::session_activity::resolve_stock_session_activities(
1010                    &page
1011                        .sessions
1012                        .iter()
1013                        .map(|session| session.locator.clone())
1014                        .collect::<Vec<_>>(),
1015                    &query.homes,
1016                )
1017                .into_iter()
1018                .map(|activity| (activity.key(), activity))
1019                .collect::<BTreeMap<_, _>>();
1020                let sessions = page
1021                    .sessions
1022                    .into_iter()
1023                    .map(|session| {
1024                        let mut value = live_descriptor_value(&session, &doors)?;
1025                        let activity_key = (
1026                            session.locator.harness.as_str().to_string(),
1027                            session.locator.session_id.clone(),
1028                        );
1029                        if let Some(activity) = activities.get(&activity_key) {
1030                            value["activity"] = serde_json::to_value(activity)
1031                                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1032                            if let Some(status) = legacy_live_status(activity) {
1033                                value["live_status"] = json!(status);
1034                            }
1035                        }
1036                        Ok(value)
1037                    })
1038                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1039                let mut result = json!({"sessions": sessions, "next_cursor": page.next_cursor});
1040                // Preserve the metadata-only wire shape, but carry the catalog's
1041                // proof/counts when the caller explicitly requests preview search.
1042                if query.search_previews {
1043                    result["receipt"] = serde_json::to_value(page.receipt)
1044                        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1045                }
1046                Ok(result)
1047            }
1048            "harness.v1.sessions.inbox" => inbox_call(decode::<InboxParams>(params)?),
1049            "harness.v1.sessions.load" => {
1050                let params = decode::<LoadSessionParams>(params)?;
1051                if let Some(options) = &params.options {
1052                    options.validate()?;
1053                    if let Some(result) = indexed_claude_window(&params.read.locator, options)? {
1054                        return Ok(result);
1055                    }
1056                    return load_session(&params.read.locator)
1057                        .map(|session| projected_session_result(&session, options))
1058                        .map_err(operation);
1059                }
1060                let mut session = if params.read.display_history() {
1061                    self.catalog
1062                        .load_display_view(
1063                            &params.read.locator,
1064                            params.read.read_fidelity(),
1065                            params.read.tail_messages().unwrap_or(500),
1066                        )
1067                        .map_err(crate::Error::from)
1068                } else if params.read.include_subagents() {
1069                    load_session_with_fidelity(&params.read.locator, params.read.read_fidelity())
1070                } else {
1071                    self.catalog
1072                        .load_parent_with_fidelity(
1073                            &params.read.locator,
1074                            params.read.read_fidelity(),
1075                        )
1076                        .map_err(crate::Error::from)
1077                }
1078                .map_err(operation)?;
1079                params.read.bound_session(&mut session);
1080                Ok(json!({"session": normalized_session_json(&session)}))
1081            }
1082            "harness.v1.sessions.follow" => {
1083                let params = decode::<LocatorParams>(params)?;
1084                let mut follower = self
1085                    .catalog
1086                    .follow_read_view(
1087                        &params.locator,
1088                        params.read_fidelity(),
1089                        params.include_subagents(),
1090                        params.tail_messages(),
1091                        params.max_message_chars(),
1092                        params.display_history(),
1093                    )
1094                    .map_err(operation)?;
1095                let initial = follower
1096                    .poll()
1097                    .map_err(operation)?
1098                    .map(|event| event.to_json());
1099                let subscription = format!("sub-{}", self.next_subscription);
1100                self.next_subscription += 1;
1101                self.followers.insert(subscription.clone(), follower);
1102                self.followed_sources.insert(
1103                    subscription.clone(),
1104                    FollowedSource {
1105                        harness: params.locator.harness.as_str().to_string(),
1106                        session_id: params.locator.session_id.clone(),
1107                        reported: None,
1108                    },
1109                );
1110                Ok(json!({"subscription": subscription, "initial": initial}))
1111            }
1112            "harness.v1.sessions.unfollow" => {
1113                let params = decode::<UnfollowParams>(params)?;
1114                self.followed_sources.remove(&params.subscription);
1115                Ok(json!({
1116                    "removed": self.followers.remove(&params.subscription).is_some()
1117                }))
1118            }
1119            "harness.v1.sessions.activity.unsubscribe" => {
1120                let params = decode::<UnfollowParams>(params)?;
1121                Ok(json!({
1122                    "removed": self.activity_subscriptions.remove(&params.subscription).is_some()
1123                }))
1124            }
1125            "harness.v1.sessions.index.subscribe" => {
1126                let query = decode::<DiscoveryQuery>(params)?;
1127                crate::session_index::validate_query(&query)
1128                    .map_err(ServiceError::InvalidParams)?;
1129                let homes = query.homes.clone();
1130                let (index, initial) = crate::session_index::SessionIndexSubscription::open(
1131                    query,
1132                    Arc::clone(&self.index_notifier),
1133                )
1134                .map_err(ServiceError::Operation)?;
1135                let doors = crate::mail_route::LiveSessions::read(&homes);
1136                let initial = initial
1137                    .iter()
1138                    .map(|descriptor| live_descriptor_value(descriptor, &doors))
1139                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1140                let subscription = format!("index-sub-{}", self.next_subscription);
1141                self.next_subscription += 1;
1142                self.index_subscriptions.insert(subscription.clone(), index);
1143                Ok(json!({
1144                    "subscription": subscription,
1145                    "revision": 1,
1146                    "initial": initial,
1147                }))
1148            }
1149            "harness.v1.sessions.index.resize" => {
1150                let params = decode::<IndexResizeParams>(params)?;
1151                crate::session_index::validate_limit(params.limit)
1152                    .map_err(ServiceError::InvalidParams)?;
1153                let index = self
1154                    .index_subscriptions
1155                    .get_mut(&params.subscription)
1156                    .ok_or_else(|| {
1157                        ServiceError::InvalidParams("unknown session index subscription".into())
1158                    })?;
1159                let prepared = index
1160                    .prepare_resize(params.limit)
1161                    .map_err(ServiceError::Operation)?;
1162                let doors = crate::mail_route::LiveSessions::read(index.homes());
1163                let initial = prepared
1164                    .page
1165                    .sessions
1166                    .iter()
1167                    .map(|descriptor| live_descriptor_value(descriptor, &doors))
1168                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1169                let response = json!({
1170                    "subscription": params.subscription,
1171                    "revision": prepared.revision,
1172                    "initial": initial,
1173                    "receipt": prepared.page.receipt,
1174                });
1175                index.commit_resize(prepared);
1176                Ok(response)
1177            }
1178            "harness.v1.sessions.index.unsubscribe" => {
1179                let params = decode::<UnfollowParams>(params)?;
1180                Ok(json!({
1181                    "removed": self.index_subscriptions.remove(&params.subscription).is_some()
1182                }))
1183            }
1184            "harness.v1.sessions.import" => {
1185                let params = decode::<ImportSessionParams>(params)?;
1186                let session = Session::load_str(&params.content, params.source_harness.into())
1187                    .map_err(operation)?;
1188                Ok(json!({"session": normalized_session_json(&session)}))
1189            }
1190            "harness.v1.sessions.export" | "harness.v1.sessions.translate" => {
1191                let params = decode::<ExportSessionParams>(params)?;
1192                let session = load_session(&params.locator).map_err(operation)?;
1193                let artifact = session_artifact(&params.locator, &session, params.target_harness)?;
1194                if method == "harness.v1.sessions.export"
1195                    && params.target_harness == TransferFormat::Hermes
1196                {
1197                    // UNI-18: write through Hermes's own door, never into its store
1198                    let imported = crate::hermes_import::import_into_hermes(&session, None)
1199                        .map_err(operation)?;
1200                    return Ok(json!({"artifact": artifact, "imported": imported}));
1201                }
1202                Ok(json!({"artifact": artifact}))
1203            }
1204            "harness.v1.sessions.reduce" => {
1205                let params = decode::<ReduceSessionParams>(params)?;
1206                self.reduce_session(params)
1207            }
1208            "harness.v1.sessions.branch" => {
1209                let params = decode::<BranchSessionParams>(params)?;
1210                let session = load_session(&params.locator).map_err(operation)?;
1211                let storage = params.locator.storage.path().display().to_string();
1212                let bootstrap_prompt = format!(
1213                    "Continue as a new branch from {} session {}. The frozen parent transcript is at {}. Read or load that parent for context, summarize the relevant state, then continue independently without mutating the parent session.",
1214                    params.locator.harness.as_str(), params.locator.session_id, storage
1215                );
1216                let artifact = params
1217                    .target_harness
1218                    .map(|target| session_artifact(&params.locator, &session, target))
1219                    .transpose()?;
1220                Ok(json!({
1221                    "parent": params.locator,
1222                    "session": normalized_session_json(&session),
1223                    "bootstrap_prompt": bootstrap_prompt,
1224                    "artifact": artifact,
1225                }))
1226            }
1227            "harness.v1.sessions.handoff" => {
1228                let params = decode::<HandoffSessionParams>(params)?;
1229                let session = load_session(&params.locator).map_err(operation)?;
1230                let cwd = params
1231                    .cwd
1232                    .or_else(|| session.meta.cwd.clone())
1233                    .unwrap_or_else(|| PathBuf::from("."));
1234                let artifact = handoff_artifact(&params.locator, &session, params.target_harness)?;
1235                let target_session_id = artifact.session_id.as_deref().ok_or_else(|| {
1236                    ServiceError::Operation(
1237                        "handoff artifact omitted target session identity".into(),
1238                    )
1239                })?;
1240                let instructions =
1241                    handoff_instructions(params.target_harness, target_session_id, &cwd);
1242                Ok(json!({
1243                    "artifact": artifact,
1244                    "launch": instructions.launch,
1245                    "materialize": instructions.materialize,
1246                    "requires_materialization": instructions.requires_materialization,
1247                    "note": instructions.note,
1248                }))
1249            }
1250            "harness.v1.sessions.materialize" => {
1251                let params = decode::<MaterializeSessionParams>(params)?;
1252                // An artifact from another machine carries its whole source as a recovery file;
1253                // keeping its segments here lets a later write back to that format restore it
1254                // byte for byte on this machine too (docs/plans/portable-residue.md).
1255                for file in &params.artifact.files {
1256                    if file.role == "source_recovery"
1257                        && file.path == "recovery/source.supercode.jsonl"
1258                    {
1259                        if let Ok(source) = Session::from_native_str(&file.content) {
1260                            crate::residue_store::store_segments(&source);
1261                        }
1262                    }
1263                }
1264                let locator = crate::native_materialize::materialize_native_artifact(
1265                    params.artifact,
1266                    &params.cwd,
1267                    &params.homes,
1268                )
1269                .map_err(ServiceError::Operation)?;
1270                Ok(json!({"locator": locator}))
1271            }
1272            // ORCH-7 observed tier. Read-only: the handlers open the harness's
1273            // own job store (Claude Code's session JSONL, Hermes's and
1274            // OpenClaw's `cron/jobs.json`) and never write, fire, or schedule.
1275            "harness.v1.jobs.list" => {
1276                let query = decode::<crate::jobs::JobsQuery>(params)?;
1277                if let Some(harness) = query.harness.as_deref() {
1278                    refuse_harness_without_jobs(harness, "jobs.list")?;
1279                }
1280                let listing = crate::jobs::list_jobs(&query).map_err(operation)?;
1281                serde_json::to_value(listing)
1282                    .map_err(|error| ServiceError::Operation(error.to_string()))
1283            }
1284            "harness.v1.jobs.get" => {
1285                let params = decode::<JobsGetParams>(params)?;
1286                refuse_harness_without_jobs(&params.harness, "jobs.get")?;
1287                match crate::jobs::get_job(&params.harness, &params.id, &params.homes)
1288                    .map_err(operation)?
1289                {
1290                    Some((job, source)) => Ok(json!({"job": job, "source": source})),
1291                    None => Err(ServiceError::Operation(format!(
1292                        "`{}` has no scheduled job `{}`",
1293                        params.harness, params.id
1294                    ))),
1295                }
1296            }
1297            // ORCH-18 controlled tier. Each verb translates to the HARNESS'S
1298            // OWN cron verb and runs it (`crate::jobs_control`); supercode
1299            // schedules nothing. The row returned is re-read from the
1300            // harness's store afterwards, and `ran` narrates the exact command
1301            // with any credential redacted.
1302            "harness.v1.jobs.create" => mutate_job(crate::jobs_control::JobVerb::Create, params),
1303            "harness.v1.jobs.update" => mutate_job(crate::jobs_control::JobVerb::Update, params),
1304            "harness.v1.jobs.pause" => mutate_job(crate::jobs_control::JobVerb::Pause, params),
1305            "harness.v1.jobs.resume" => mutate_job(crate::jobs_control::JobVerb::Resume, params),
1306            "harness.v1.jobs.run" => mutate_job(crate::jobs_control::JobVerb::Run, params),
1307            "harness.v1.jobs.delete" => mutate_job(crate::jobs_control::JobVerb::Delete, params),
1308            "harness.v1.jobs.notepad"
1309            | "harness.v1.jobs.notepad_set"
1310            | "harness.v1.jobs.notepad_delete" => {
1311                let request = decode::<crate::jobs_notepad::JobNotepadRequest>(params)?;
1312                refuse_harness_without_jobs(&request.harness, "jobs.notepad")?;
1313                let answer = match method {
1314                    "harness.v1.jobs.notepad_set" => crate::jobs_notepad::set(&request),
1315                    "harness.v1.jobs.notepad_delete" => crate::jobs_notepad::delete(&request),
1316                    _ => crate::jobs_notepad::read(&request),
1317                }
1318                .map_err(job_control_error)?;
1319                serde_json::to_value(answer)
1320                    .map_err(|error| ServiceError::Operation(error.to_string()))
1321            }
1322            // ORCH-8 observed tier. Read-only: the handlers open the harness's
1323            // own run store (Hermes's `cron/executions.db`, OpenClaw's
1324            // `cron_run_logs`) and never claim, retry, or prune a fire.
1325            "harness.v1.runs.list" => {
1326                let query = decode::<crate::runs::RunsQuery>(params)?;
1327                if let Some(harness) = query.harness.as_deref() {
1328                    refuse_harness_without_runs(harness, "runs.list")?;
1329                }
1330                let listing = crate::runs::list_runs(&query).map_err(operation)?;
1331                serde_json::to_value(listing)
1332                    .map_err(|error| ServiceError::Operation(error.to_string()))
1333            }
1334            "harness.v1.runs.get" => {
1335                let params = decode::<RunsGetParams>(params)?;
1336                refuse_harness_without_runs(&params.harness, "runs.get")?;
1337                match crate::runs::get_run(&params.harness, &params.id, &params.homes)
1338                    .map_err(operation)?
1339                {
1340                    Some((run, source)) => Ok(json!({"run": run, "source": source})),
1341                    None => Err(ServiceError::Operation(format!(
1342                        "`{}` has no run `{}`",
1343                        params.harness, params.id
1344                    ))),
1345                }
1346            }
1347            "harness.v1.sessions.resume_instructions" => {
1348                let params = decode::<ResumeInstructionsParams>(params)?;
1349                let session = load_session(&params.locator).map_err(operation)?;
1350                let cwd = params
1351                    .cwd
1352                    .or(session.meta.cwd)
1353                    .unwrap_or_else(|| PathBuf::from("."));
1354                let launch = resume_launch(
1355                    params.locator.harness.as_str(),
1356                    &params.locator.session_id,
1357                    &cwd,
1358                    params.policy,
1359                )?;
1360                Ok(json!({"launch": launch}))
1361            }
1362            _ => Err(ServiceError::MethodNotFound),
1363        }
1364    }
1365
1366    fn reduce_session(
1367        &self,
1368        params: ReduceSessionParams,
1369    ) -> std::result::Result<Value, ServiceError> {
1370        let session = load_session(&params.locator).map_err(operation)?;
1371        if session.messages.is_empty() {
1372            return Err(ServiceError::InvalidParams(
1373                "cannot reduce an empty session".into(),
1374            ));
1375        }
1376        let keep_last = params.keep_last.clamp(1, 128);
1377        let policy = reduce::ReductionPolicy {
1378            clear_turns_older_than: Some(keep_last),
1379            ..Default::default()
1380        };
1381        let (view, log) =
1382            reduce::project_messages(&session.messages, &policy, &reduce::ReductionLog::default());
1383        if log.reductions.is_empty() {
1384            return Err(ServiceError::UnsupportedAction(format!(
1385                "session `{}` is already too small for a meaningful reversible reduction",
1386                params.locator.session_id
1387            )));
1388        }
1389        let source_tokens = supercode_runtime::estimate_view_tokens(&session.messages);
1390        let reduced_tokens = supercode_runtime::estimate_view_tokens(&view);
1391        if reduced_tokens >= source_tokens {
1392            return Err(ServiceError::UnsupportedAction(format!(
1393                "session `{}` has no token-reducing reversible projection",
1394                params.locator.session_id
1395            )));
1396        }
1397
1398        let store_root = self
1399            .reduction_store_root
1400            .clone()
1401            .unwrap_or_else(default_reduction_store_root);
1402        let store = crate::SessionStore::open(&store_root).map_err(operation)?;
1403        let rescue_id = format!("rescue-{}", generated_session_id());
1404        let imported = session
1405            .imported_message_count
1406            .unwrap_or(session.messages.len())
1407            .min(session.messages.len());
1408        let sidecar_jsonl = session.to_native_jsonl_v2(&session.messages[imported..]);
1409        let view_jsonl = messages_jsonl(&view)?;
1410        let title = format!(
1411            "Reduced {} continuation from {}",
1412            params.target_harness.id(),
1413            params.locator.session_id
1414        );
1415
1416        // Durability order is intentional: the full source of truth lands
1417        // before either object that can refer to it. A crash may leave an
1418        // unused sidecar, but can never leave a reduced view whose originals
1419        // were not durably written first.
1420        store
1421            .save_sidecar(&rescue_id, &sidecar_jsonl)
1422            .map_err(operation)?;
1423        store
1424            .save_reduction_log(&rescue_id, &log)
1425            .map_err(operation)?;
1426        store
1427            .save(&rescue_id, &title, &view_jsonl)
1428            .map_err(operation)?;
1429
1430        let source_bytes = serde_json::to_vec(&session.messages)
1431            .map_err(|error| ServiceError::Operation(error.to_string()))?
1432            .len() as u64;
1433        let reduced_bytes = serde_json::to_vec(&view)
1434            .map_err(|error| ServiceError::Operation(error.to_string()))?
1435            .len() as u64;
1436        store
1437            .set_reduction_stats(
1438                &rescue_id,
1439                &title,
1440                source_bytes,
1441                reduced_bytes,
1442                log.reductions.len() as u32,
1443            )
1444            .map_err(operation)?;
1445
1446        // The receipt is issued only after a real disk reload. This proves
1447        // the exact files another process will consume, not the convenient
1448        // in-memory values that produced them.
1449        let reloaded_sidecar = store
1450            .load_sidecar(&rescue_id)
1451            .map_err(operation)?
1452            .ok_or_else(|| ServiceError::Operation("reduction sidecar disappeared".into()))?;
1453        let reloaded_sidecar = Session::from_sidecar_str(&reloaded_sidecar).map_err(operation)?;
1454        let reloaded_log = store
1455            .load_reduction_log(&rescue_id)
1456            .map_err(operation)?
1457            .ok_or_else(|| ServiceError::Operation("reduction log disappeared".into()))?;
1458        let reloaded_view = parse_messages_jsonl(&store.load(&rescue_id).map_err(operation)?)?;
1459        reduce::verify_log(&reloaded_log, &reloaded_sidecar).map_err(operation)?;
1460        // `sc.reduction` is deliberately in-memory-only metadata: it must
1461        // never leak onto a provider-facing transcript. Reapplying the
1462        // durable log to the durable sidecar restores those ids. Comparing
1463        // its wire form with the transcript reloaded above proves that the
1464        // persisted view is exactly the deterministic projection before we
1465        // use the restamped form for inversion.
1466        let (restamped_view, restamped_log) =
1467            reduce::project_messages(&reloaded_sidecar.messages, &policy, &reloaded_log);
1468        if messages_jsonl(&restamped_view)? != messages_jsonl(&reloaded_view)? {
1469            return Err(ServiceError::Operation(
1470                "persisted reduction view does not match its durable log and sidecar".into(),
1471            ));
1472        }
1473        if restamped_log != reloaded_log {
1474            return Err(ServiceError::Operation(
1475                "reapplying the durable reduction log changed its identity".into(),
1476            ));
1477        }
1478        let inverted =
1479            reduce::invert(&restamped_view, &reloaded_log, &reloaded_sidecar).map_err(operation)?;
1480        if inverted != session.messages {
1481            return Err(ServiceError::Operation(
1482                "reduction inversion did not restore the source messages byte-exactly".into(),
1483            ));
1484        }
1485
1486        let ratio = source_tokens as f64 / reduced_tokens.max(1) as f64;
1487        let sidecar_path = store.sidecar_path(&rescue_id);
1488        let reduction_log_path = store.reduction_log_path(&rescue_id).map_err(operation)?;
1489        let bootstrap_prompt = reduced_bootstrap_prompt(
1490            &params.locator,
1491            params.target_harness,
1492            &view_jsonl,
1493            &sidecar_path,
1494            &reduction_log_path,
1495        );
1496        let mut reduced_session = session.clone();
1497        reduced_session.meta.session_id = Some(rescue_id.clone());
1498        reduced_session.messages = view;
1499
1500        Ok(json!({
1501            "session": normalized_session_json(&reduced_session),
1502            "bootstrap_prompt": bootstrap_prompt,
1503            "receipt": {
1504                "id": rescue_id,
1505                "sidecar_id": rescue_id,
1506                "source_harness": params.locator.harness,
1507                "target_harness": params.target_harness.id(),
1508                "source_tokens": source_tokens,
1509                "reduced_tokens": reduced_tokens,
1510                "ratio": ratio,
1511                "source_bytes": source_bytes,
1512                "reduced_bytes": reduced_bytes,
1513                "reductions": reloaded_log.reductions.len(),
1514                "sidecar_path": sidecar_path,
1515                "reduction_log_path": reduction_log_path,
1516                "verified": true,
1517                "reversible": true,
1518            }
1519        }))
1520    }
1521
1522    /// Recognize the one request family whose waiting happens entirely
1523    /// outside this service's state, and hand a transport the half it can run
1524    /// off the task that owns the service.
1525    ///
1526    /// Opening a runtime is the only door here that waits on a foreign
1527    /// program: it spawns the harness's own binary and completes that
1528    /// program's protocol handshake, which takes as long as the program takes
1529    /// to answer. A transport that awaited the whole request inline would
1530    /// stop reading its own input for that whole time, so ONE slow launch
1531    /// would queue every later request on the same server — including reads
1532    /// like `sessions.discover` that touch no runtime at all. Splitting the
1533    /// request lets the transport spawn [`RuntimeOpen::open`] and keep
1534    /// reading, then pay only the short bookkeeping half
1535    /// ([`Self::register_open_runtime`]) when the runtime is up.
1536    ///
1537    /// `None` for every other method: those are answered by
1538    /// [`Self::handle_async`] as before.
1539    pub fn runtime_open(request: &Value) -> Option<RuntimeOpen> {
1540        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1541            return None;
1542        }
1543        let method = request.get("method").and_then(Value::as_str)?;
1544        if !RUNTIME_OPEN_METHODS.contains(&method) {
1545            return None;
1546        }
1547        Some(RuntimeOpen {
1548            id: request.get("id").cloned().unwrap_or(Value::Null),
1549            method: method.to_string(),
1550            params: request.get("params").cloned().unwrap_or_else(|| json!({})),
1551        })
1552    }
1553
1554    /// Recognize a [`DETACHED_METHODS`] request and hand a transport the
1555    /// whole of it: the service-state half is read here and now, and what
1556    /// remains waits on a foreign program with nothing of this service's in
1557    /// hand.
1558    ///
1559    /// Same reason as [`Self::runtime_open`], different doors. Probing a
1560    /// harness starts it and completes its handshake; messaging a live
1561    /// session waits on its Claude relay's send; a conversation verb runs the
1562    /// harness's own CLI or calls its HTTP API. A transport that awaited any
1563    /// of those inline would stop reading its own input for that whole time,
1564    /// so one probe of an unhealthy harness would queue every later request
1565    /// on the same server.
1566    ///
1567    /// Unlike an opening runtime there is no bookkeeping half: the answer
1568    /// [`DetachedCall::run`] produces is the caller's complete response, so a
1569    /// transport writes it without coming back here.
1570    ///
1571    /// `None` for every other method — including the LIVE `sessions.new` /
1572    /// `sessions.reset` door and `runtimes.close`, which wait on a runtime
1573    /// connection this service owns and so are split off by
1574    /// [`Self::detach_runtime`] instead.
1575    pub fn detach(&self, request: &Value) -> Option<DetachedCall> {
1576        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1577            return None;
1578        }
1579        let method = request.get("method").and_then(Value::as_str)?;
1580        if !DETACHED_METHODS.contains(&method) {
1581            return None;
1582        }
1583        let id = request.get("id").cloned().unwrap_or(Value::Null);
1584        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1585        let work = match method {
1586            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe" => self
1587                .inventory_work(method, params)
1588                .map(DetachedWork::Inventory),
1589            "harness.v1.sessions.message" => {
1590                decode::<MessageSessionParams>(params).map(DetachedWork::Message)
1591            }
1592            _ => {
1593                let verb = match method {
1594                    "harness.v1.sessions.new" => crate::SessionVerb::New,
1595                    "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
1596                    "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
1597                    _ => crate::SessionVerb::Delete,
1598                };
1599                match decode::<crate::SessionMutation>(params) {
1600                    Ok(mutation) => {
1601                        match crate::sessions_control::door(&mutation.harness, verb) {
1602                            // The live door needs the open runtime connection
1603                            // this service owns; it stays inline.
1604                            Ok(crate::SessionDoor::Live(_)) => return None,
1605                            Ok(_) => Ok(DetachedWork::SessionMutation { verb, mutation }),
1606                            Err(error) => Err(session_control_error(error)),
1607                        }
1608                    }
1609                    Err(error) => Err(error),
1610                }
1611            }
1612        };
1613        Some(DetachedCall {
1614            id,
1615            method: method.to_string(),
1616            work: work.map(Work::Free),
1617        })
1618    }
1619
1620    /// Recognize the two doors that wait on a runtime THIS SERVICE OWNS, and
1621    /// hand a transport the whole of each by lending the connection out.
1622    ///
1623    /// `runtimes.close` surrenders its runtime for good; the LIVE
1624    /// `sessions.new` / `sessions.reset` door borrows one for the length of
1625    /// the slash command and gives it back through
1626    /// [`Self::finish_detached`]. Both are bounded by
1627    /// [`RUNTIME_CONTROL_DEADLINE`], and a wedged runtime spends all of it —
1628    /// which is exactly as long as a transport that awaited them inline would
1629    /// stop reading its own input.
1630    ///
1631    /// `None` for every other method, and for the `sessions.new` /
1632    /// `sessions.reset` doors that are not live: [`Self::detach`] owns those.
1633    pub fn detach_runtime(&mut self, request: &Value) -> Option<DetachedCall> {
1634        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1635            return None;
1636        }
1637        let method = request.get("method").and_then(Value::as_str)?;
1638        let id = request.get("id").cloned().unwrap_or(Value::Null);
1639        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1640        let work = match method {
1641            "harness.v1.runtimes.close" => decode::<RuntimeConnectionParams>(params)
1642                .and_then(|params| self.surrender_runtime(&params.connection))
1643                .map(|(runtime, process_group)| {
1644                    Work::Runtime(RuntimeWork::Close {
1645                        runtime,
1646                        process_group,
1647                    })
1648                }),
1649            "harness.v1.sessions.new" | "harness.v1.sessions.reset" => {
1650                let verb = if method == "harness.v1.sessions.new" {
1651                    crate::SessionVerb::New
1652                } else {
1653                    crate::SessionVerb::Reset
1654                };
1655                let mutation = decode::<crate::SessionMutation>(params).ok()?;
1656                // Everything but the live door — including a refusal and a
1657                // request naming no connection — is `detach`'s or
1658                // `handle_async`'s to answer.
1659                let Ok(crate::SessionDoor::Live(command)) =
1660                    crate::sessions_control::door(&mutation.harness, verb)
1661                else {
1662                    return None;
1663                };
1664                let connection = mutation
1665                    .connection
1666                    .clone()
1667                    .filter(|value| !value.trim().is_empty())?;
1668                self.lend_runtime(&connection).map(|runtime| {
1669                    let session = live_session_name(runtime.as_ref(), &mutation);
1670                    Work::Runtime(RuntimeWork::LiveCommand {
1671                        connection,
1672                        runtime,
1673                        verb,
1674                        mutation,
1675                        command,
1676                        session,
1677                    })
1678                })
1679            }
1680            _ => return None,
1681        };
1682        Some(DetachedCall {
1683            id,
1684            method: method.to_string(),
1685            work,
1686        })
1687    }
1688
1689    /// Take back whatever a detached call borrowed and hand over the caller's
1690    /// response. Every answer from [`DetachedCall::run`] comes through here,
1691    /// so a lent-out connection is back in the service before the response
1692    /// that used it is written.
1693    pub fn finish_detached(&mut self, answer: DetachedAnswer) -> Value {
1694        let DetachedAnswer { response, returned } = answer;
1695        if let Some(ReturnedRuntime {
1696            connection,
1697            runtime,
1698        }) = returned
1699        {
1700            self.runtimes_in_flight.remove(&connection);
1701            self.runtimes.insert(connection, runtime);
1702        }
1703        response
1704    }
1705
1706    /// Answer a request split out by [`Self::runtime_open`] and already
1707    /// awaited by [`RuntimeOpen::open`]: register the runtime this service now
1708    /// owns and build its JSON-RPC response.
1709    pub async fn finish_runtime_open(&mut self, opened: OpenedRuntime) -> Value {
1710        let OpenedRuntime { id, outcome } = opened;
1711        let result = match outcome {
1712            Ok(open) => self.register_open_runtime(open).await,
1713            Err(error) => Err(error),
1714        };
1715        service_response(id, result)
1716    }
1717
1718    /// Take ownership of an opened runtime.
1719    async fn register_open_runtime(
1720        &mut self,
1721        open: OpenRuntime,
1722    ) -> std::result::Result<Value, ServiceError> {
1723        match open {
1724            OpenRuntime::Hosted {
1725                runtime,
1726                capabilities,
1727                workspace,
1728            } => {
1729                self.insert_hosted_runtime(runtime, capabilities, workspace)
1730                    .await
1731            }
1732            OpenRuntime::Joined { runtime } => self.insert_runtime(runtime),
1733        }
1734    }
1735
1736    async fn runtime_call(
1737        &mut self,
1738        method: &str,
1739        params: Value,
1740    ) -> std::result::Result<Value, ServiceError> {
1741        match method {
1742            "harness.v1.runtimes.capabilities" => {
1743                let params = decode::<RuntimeBackendParams>(params)?;
1744                let backend = runtime_backend(&params)?;
1745                Ok(json!({
1746                    "harness": backend.harness(),
1747                    "capabilities": backend.capabilities(),
1748                }))
1749            }
1750            method if RUNTIME_OPEN_METHODS.contains(&method) => {
1751                self.register_open_runtime(open_runtime(method, params).await?)
1752                    .await
1753            }
1754            "harness.v1.runtimes.send_input" => {
1755                let params = decode::<RuntimeInputParams>(params)?;
1756                let image_urls = validate_runtime_image_urls(params.image_urls)?;
1757                let runtime = self.runtime_mut(&params.connection)?;
1758                let turn_id = within_control_deadline(
1759                    method,
1760                    runtime.send_input(RuntimeInput {
1761                        text: params.text,
1762                        image_urls,
1763                    }),
1764                )
1765                .await?
1766                .map_err(operation)?;
1767                Ok(json!({"turn_id": turn_id}))
1768            }
1769            "harness.v1.runtimes.interrupt" => {
1770                let params = decode::<RuntimeConnectionParams>(params)?;
1771                within_control_deadline(method, self.runtime_mut(&params.connection)?.interrupt())
1772                    .await?
1773                    .map_err(operation)?;
1774                Ok(json!({}))
1775            }
1776            "harness.v1.runtimes.steer" => {
1777                let params = decode::<RuntimeInputParams>(params)?;
1778                if !params.image_urls.is_empty() {
1779                    return Err(ServiceError::InvalidParams(
1780                        "runtime steering accepts text only".into(),
1781                    ));
1782                }
1783                let text = params.text.trim();
1784                if text.is_empty() || text.chars().count() > 50_000 {
1785                    return Err(ServiceError::InvalidParams(
1786                        "runtime steering requires 1 to 50,000 text characters".into(),
1787                    ));
1788                }
1789                within_control_deadline(
1790                    method,
1791                    self.runtime_mut(&params.connection)?
1792                        .steer(text.to_string()),
1793                )
1794                .await?
1795                .map_err(operation)?;
1796                Ok(json!({}))
1797            }
1798            "harness.v1.runtimes.respond" => {
1799                let params = decode::<RuntimeRespondParams>(params)?;
1800                let request_id = params.request_id.clone();
1801                within_control_deadline(
1802                    method,
1803                    self.runtime_mut(&params.connection)?
1804                        .respond(params.request_id, params.response),
1805                )
1806                .await?
1807                .map_err(operation)?;
1808                // ORCH-9: an answered request is no longer waiting for one.
1809                self.approvals.answered(&params.connection, &request_id);
1810                Ok(json!({}))
1811            }
1812            "harness.v1.runtimes.acquire_control" => {
1813                let params = decode::<RuntimeConnectionParams>(params)?;
1814                let snapshot = within_control_deadline(
1815                    method,
1816                    self.runtime_mut(&params.connection)?.acquire_control(),
1817                )
1818                .await?
1819                .map_err(operation)?;
1820                serde_json::to_value(snapshot)
1821                    .map_err(|error| ServiceError::Operation(error.to_string()))
1822            }
1823            "harness.v1.runtimes.heartbeat" => {
1824                let params = decode::<RuntimeConnectionParams>(params)?;
1825                let snapshot = within_control_deadline(
1826                    method,
1827                    self.runtime_mut(&params.connection)?.heartbeat(),
1828                )
1829                .await?
1830                .map_err(operation)?;
1831                serde_json::to_value(snapshot)
1832                    .map_err(|error| ServiceError::Operation(error.to_string()))
1833            }
1834            "harness.v1.runtimes.detach" => {
1835                let params = decode::<RuntimeConnectionParams>(params)?;
1836                let snapshot =
1837                    within_control_deadline(method, self.runtime_mut(&params.connection)?.detach())
1838                        .await?
1839                        .map_err(operation)?;
1840                serde_json::to_value(snapshot)
1841                    .map_err(|error| ServiceError::Operation(error.to_string()))
1842            }
1843            "harness.v1.runtimes.terminal_instructions" => {
1844                let params = decode::<RuntimeConnectionParams>(params)?;
1845                let launch = self
1846                    .terminal_launches
1847                    .get(&params.connection)
1848                    .ok_or_else(|| {
1849                        ServiceError::Operation(
1850                            "this runtime is not hosted for terminal attachment".into(),
1851                        )
1852                    })?;
1853                Ok(json!({"launch":launch}))
1854            }
1855            "harness.v1.runtimes.close" => {
1856                let params = decode::<RuntimeConnectionParams>(params)?;
1857                let (runtime, process_group) = self.surrender_runtime(&params.connection)?;
1858                close_runtime(runtime, process_group).await
1859            }
1860            _ => Err(ServiceError::MethodNotFound),
1861        }
1862    }
1863
1864    /// Deliver one message into a session that is running right now.
1865    #[cfg(feature = "adapter-api")]
1866    async fn message_call(&self, params: Value) -> std::result::Result<Value, ServiceError> {
1867        let params = decode::<MessageSessionParams>(params)?;
1868        Ok(message_live_session(&params).await)
1869    }
1870
1871    #[cfg(feature = "adapter-api")]
1872    fn harness_settings_call(
1873        &self,
1874        method: &str,
1875        params: Value,
1876    ) -> std::result::Result<Value, ServiceError> {
1877        let homes = crate::HarnessHomes::default();
1878        match method {
1879            "harness.v1.harnesses.settings" => {
1880                let params = decode::<HarnessSettingsParams>(params)?;
1881                let report = crate::inspect_harness_interop_settings(&homes, &params.harness)
1882                    .map_err(|error| ServiceError::Operation(error.to_string()))?;
1883                serde_json::to_value(report)
1884                    .map_err(|error| ServiceError::Operation(error.to_string()))
1885            }
1886            "harness.v1.harnesses.configure" => {
1887                let params = decode::<ConfigureHarnessParams>(params)?;
1888                let report = crate::configure_harness_interop_settings(
1889                    &homes,
1890                    &params.harness,
1891                    &params.changes,
1892                    params.expected_revision.as_deref(),
1893                )
1894                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1895                serde_json::to_value(report)
1896                    .map_err(|error| ServiceError::Operation(error.to_string()))
1897            }
1898            _ => Err(ServiceError::MethodNotFound),
1899        }
1900    }
1901
1902    fn insert_runtime(
1903        &mut self,
1904        runtime: Box<dyn RuntimeConnection>,
1905    ) -> std::result::Result<Value, ServiceError> {
1906        let connection = format!("runtime-{}", self.next_runtime);
1907        self.next_runtime += 1;
1908        let handle = runtime.handle().clone();
1909        self.runtime_sequences
1910            .entry(handle.runtime_id.clone())
1911            .or_insert(0);
1912        self.runtimes.insert(connection.clone(), runtime);
1913        Ok(json!({"connection": connection, "handle": handle}))
1914    }
1915
1916    #[cfg(feature = "adapter-api")]
1917    async fn insert_hosted_runtime(
1918        &mut self,
1919        runtime: Box<dyn RuntimeConnection>,
1920        capabilities: crate::RuntimeCapabilities,
1921        workspace: PathBuf,
1922    ) -> std::result::Result<Value, ServiceError> {
1923        let (host, connection) = HostedHarnessRuntime::spawn(runtime, capabilities);
1924        let token: std::sync::Arc<str> = crate::server::generate_token().into();
1925        let server = crate::server::run_frontend_http(
1926            host.clone(),
1927            host.frontend_sender(),
1928            "127.0.0.1:0",
1929            token.clone(),
1930            connection.handle().runtime_id.clone(),
1931        )
1932        .await
1933        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1934        let source = LiveRuntimeSource {
1935            harness: connection.handle().harness.as_str().to_string(),
1936            session_id: connection.handle().runtime_id.clone(),
1937            workspace: workspace.clone(),
1938        };
1939        let registration = register_live_runtime(
1940            connection.handle().runtime_id.clone(),
1941            source.clone(),
1942            format!("http://{}", server.address()),
1943            token.to_string(),
1944        )
1945        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1946        let endpoint = registration.endpoint().to_string();
1947        let launch = StructuredLaunch {
1948            cwd: workspace,
1949            // Pin attachment to the executable hosting this runtime. A bare
1950            // `supercode` could resolve to an older global install whose CLI
1951            // does not understand the receipt it is being asked to open.
1952            program: std::env::current_exe()
1953                .ok()
1954                .map(|path| path.to_string_lossy().into_owned())
1955                .unwrap_or_else(|| "supercode".into()),
1956            arguments: vec![
1957                "open".into(),
1958                endpoint,
1959                "--harness".into(),
1960                source.harness,
1961                "--session".into(),
1962                source.session_id,
1963            ],
1964            env: BTreeMap::new(),
1965        };
1966        let lease = HostedRuntimeLease {
1967            connection,
1968            _host: host,
1969            _registration: registration,
1970            _server: server,
1971        };
1972        let opened = self.insert_runtime(Box::new(lease))?;
1973        let connection_id = opened["connection"]
1974            .as_str()
1975            .expect("insert_runtime returns a connection id")
1976            .to_string();
1977        self.terminal_launches.insert(connection_id, launch);
1978        Ok(opened)
1979    }
1980
1981    #[cfg(not(feature = "adapter-api"))]
1982    async fn insert_hosted_runtime(
1983        &mut self,
1984        runtime: Box<dyn RuntimeConnection>,
1985        _capabilities: crate::RuntimeCapabilities,
1986        _workspace: PathBuf,
1987    ) -> std::result::Result<Value, ServiceError> {
1988        self.insert_runtime(runtime)
1989    }
1990
1991    fn runtime_mut(
1992        &mut self,
1993        connection: &str,
1994    ) -> std::result::Result<&mut Box<dyn RuntimeConnection>, ServiceError> {
1995        if self.runtimes_in_flight.contains(connection) {
1996            return Err(self.lent_out(connection));
1997        }
1998        self.runtimes.get_mut(connection).ok_or_else(|| {
1999            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2000        })
2001    }
2002
2003    /// What a caller is told about a connection that is out on a detached
2004    /// call. It is not gone and it is not free: it is mid-call, which is the
2005    /// same answer the runtime itself gives a second turn.
2006    fn lent_out(&self, connection: &str) -> ServiceError {
2007        ServiceError::Operation(format!(
2008            "runtime connection `{connection}`: a harness turn is already in progress"
2009        ))
2010    }
2011
2012    /// Take a runtime OUT of the service for the duration of one detached
2013    /// call, leaving its name marked as lent out.
2014    fn lend_runtime(
2015        &mut self,
2016        connection: &str,
2017    ) -> std::result::Result<Box<dyn RuntimeConnection>, ServiceError> {
2018        if self.runtimes_in_flight.contains(connection) {
2019            return Err(self.lent_out(connection));
2020        }
2021        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
2022            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2023        })?;
2024        self.runtimes_in_flight.insert(connection.to_string());
2025        Ok(runtime)
2026    }
2027
2028    /// Surrender a runtime for good: the connection and everything the
2029    /// service hung off it are gone before its teardown is even attempted.
2030    ///
2031    /// `close` is what a caller reaches for when a runtime has stopped
2032    /// answering, and a runtime that has stopped answering is exactly the one
2033    /// whose graceful close cannot complete: a hosted runtime's own loop
2034    /// parks on the call the runtime never answered, so it never dequeues the
2035    /// shutdown either. Keeping the entry until teardown succeeded made a
2036    /// wedged runtime permanent — every later call on that connection, and
2037    /// every new turn, answered "a harness turn is already in progress" with
2038    /// no way to take the connection back.
2039    fn surrender_runtime(
2040        &mut self,
2041        connection: &str,
2042    ) -> std::result::Result<(Box<dyn RuntimeConnection>, Option<u32>), ServiceError> {
2043        if self.runtimes_in_flight.contains(connection) {
2044            return Err(self.lent_out(connection));
2045        }
2046        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
2047            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2048        })?;
2049        let process_group = runtime_process_group(runtime.handle());
2050        let runtime_id = runtime.handle().runtime_id.clone();
2051        self.terminal_launches.remove(connection);
2052        self.runtime_sequences.remove(&runtime_id);
2053        self.approvals.forget(connection);
2054        Ok((runtime, process_group))
2055    }
2056
2057    /// SIGKILL the process group of every runtime this service owns, without
2058    /// waiting on any of them.
2059    ///
2060    /// A host leaving for good calls this BEFORE dropping the service. The
2061    /// handle this service holds is not the runtime's connection: a hosted
2062    /// runtime's real transport lives in the task driving it, so neither
2063    /// exiting the process nor dropping these handles reaches the harness
2064    /// process — while dropping them does remove each runtime's live-runtime
2065    /// receipt. Signalling first is what keeps a removed receipt from
2066    /// advertising a harness that is still running.
2067    pub fn kill_all_runtime_groups(&self) -> usize {
2068        self.runtimes
2069            .values()
2070            .filter(|runtime| kill_runtime_process_group(runtime_process_group(runtime.handle())))
2071            .count()
2072    }
2073
2074    /// ORCH-19: run one conversation-lifecycle verb through the harness's own
2075    /// door.
2076    ///
2077    /// Two doors, one shape. A CLI / HTTP / own-store door is self-contained
2078    /// in [`crate::sessions_control`]. A LIVE door (Hermes's and OpenClaw's
2079    /// `/new` and `/reset`, which are slash commands their gateway interprets
2080    /// INSIDE a session) is performed here, because only the service owns the
2081    /// open runtime connection — the command is typed through the very same
2082    /// `send_input` path a human's message takes, so supercode invents no
2083    /// private channel.
2084    async fn mutate_session(
2085        &mut self,
2086        verb: crate::SessionVerb,
2087        params: Value,
2088    ) -> std::result::Result<Value, ServiceError> {
2089        let mutation = decode::<crate::SessionMutation>(params)?;
2090        let door = crate::sessions_control::door(&mutation.harness, verb)
2091            .map_err(session_control_error)?;
2092        let outcome = match door {
2093            // The live door types the slash command through an open hosted
2094            // runtime, which only exists with the `adapter-api` feature; the
2095            // CLI / HTTP / own-store doors below need nothing extra.
2096            #[cfg(not(feature = "adapter-api"))]
2097            crate::SessionDoor::Live(command) => {
2098                return Err(ServiceError::Operation(format!(
2099                    "`{}` performs `sessions.{}` by typing `{command}` into a live driven \
2100                     session, which needs this build's `adapter-api` feature",
2101                    mutation.harness,
2102                    verb.as_str()
2103                )));
2104            }
2105            #[cfg(feature = "adapter-api")]
2106            crate::SessionDoor::Live(command) => {
2107                let connection = mutation
2108                    .connection
2109                    .clone()
2110                    .filter(|value| !value.trim().is_empty())
2111                    .ok_or_else(|| {
2112                        ServiceError::InvalidParams(format!(
2113                            "`{}` performs `sessions.{}` by typing `{command}` into a live \
2114                             driven session: pass the `connection` of an open runtime \
2115                             (`harness.v1.runtimes.start`)",
2116                            mutation.harness,
2117                            verb.as_str()
2118                        ))
2119                    })?;
2120                let runtime = self.runtime_mut(&connection)?;
2121                let session = live_session_name(runtime.as_ref(), &mutation);
2122                // Typing into a live session is a control call on an open
2123                // runtime, and a wedged runtime never accepts one, so it is
2124                // bounded exactly like the other control verbs. A transport
2125                // with a loop of its own lends the connection out instead of
2126                // waiting here: see [`Self::detach_runtime`].
2127                return type_live_command(runtime.as_mut(), verb, &mutation, command, session)
2128                    .await;
2129            }
2130            _ => run_session_mutation(verb, &mutation).await?,
2131        };
2132        serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
2133    }
2134
2135    /// Answer an inventory request whole, for callers that have nowhere to
2136    /// put the waiting half. A transport with a loop of its own splits it
2137    /// instead: see [`Self::detach`].
2138    async fn inventory_call(
2139        &self,
2140        method: &str,
2141        params: Value,
2142    ) -> std::result::Result<Value, ServiceError> {
2143        run_inventory(self.inventory_work(method, params)?).await
2144    }
2145
2146    /// The half of an inventory request that reads this service's state:
2147    /// resolve the selection and count the persisted sessions each row
2148    /// reports. What remains — finding executables, asking them their
2149    /// version, and (at `probe: handshake`) starting each harness and
2150    /// completing its protocol handshake — touches no service state at all.
2151    fn inventory_work(
2152        &self,
2153        method: &str,
2154        params: Value,
2155    ) -> std::result::Result<InventoryWork, ServiceError> {
2156        let mut params = decode::<HarnessInventoryParams>(params)?;
2157        if method == "harness.v1.harnesses.probe" {
2158            let harness = params.harness.take().ok_or_else(|| {
2159                ServiceError::InvalidParams("harnesses.probe requires `harness`".into())
2160            })?;
2161            params.harnesses = vec![harness];
2162        }
2163        let selected = params
2164            .harnesses
2165            .iter()
2166            .map(HarnessId::as_str)
2167            .collect::<std::collections::BTreeSet<_>>();
2168        let supported = harness_support_registry()
2169            .harnesses
2170            .into_iter()
2171            .filter(|descriptor| selected.is_empty() || selected.contains(descriptor.id.as_str()))
2172            .collect::<Vec<_>>();
2173        if !params.harnesses.is_empty() && supported.len() != selected.len() {
2174            let known = supported
2175                .iter()
2176                .map(|harness| harness.id.as_str())
2177                .collect::<std::collections::BTreeSet<_>>();
2178            let missing = params
2179                .harnesses
2180                .iter()
2181                .filter(|id| !known.contains(id.as_str()))
2182                .map(HarnessId::as_str)
2183                .collect::<Vec<_>>();
2184            return Err(ServiceError::InvalidParams(format!(
2185                "unknown harness(es): {}",
2186                missing.join(", ")
2187            )));
2188        }
2189        let global_counts = params
2190            .include_sessions
2191            .then(|| self.session_counts(None, &params.harnesses));
2192        let workspace_counts = params
2193            .include_sessions
2194            .then(|| {
2195                params
2196                    .workspace
2197                    .as_deref()
2198                    .map(|workspace| self.session_counts(Some(workspace), &params.harnesses))
2199            })
2200            .flatten();
2201        Ok(InventoryWork {
2202            params,
2203            supported,
2204            global_counts,
2205            workspace_counts,
2206        })
2207    }
2208
2209    #[cfg(feature = "adapter-api")]
2210    async fn harness_authentication_call(
2211        &self,
2212        method: &str,
2213        params: Value,
2214    ) -> std::result::Result<Value, ServiceError> {
2215        match method {
2216            "harness.v1.harnesses.auth.methods" | "harness.v1.harnesses.auth.verify" => {
2217                let params = decode::<HarnessAuthenticationParams>(params)?;
2218                serde_json::to_value(crate::inspect_harness_authentication(&params.harness).await)
2219                    .map_err(|error| ServiceError::Operation(error.to_string()))
2220            }
2221            "harness.v1.harnesses.auth.begin" => {
2222                let params = decode::<BeginHarnessAuthenticationParams>(params)?;
2223                let cwd = params
2224                    .cwd
2225                    .or_else(|| std::env::current_dir().ok())
2226                    .unwrap_or_else(|| PathBuf::from("."));
2227                let plan = crate::harness_authentication_plan(
2228                    &params.harness,
2229                    params.environment,
2230                    params.method,
2231                    &cwd,
2232                )
2233                .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
2234                serde_json::to_value(plan)
2235                    .map_err(|error| ServiceError::Operation(error.to_string()))
2236            }
2237            _ => Err(ServiceError::MethodNotFound),
2238        }
2239    }
2240
2241    fn session_counts(
2242        &self,
2243        workspace: Option<&Path>,
2244        harnesses: &[HarnessId],
2245    ) -> BTreeMap<String, usize> {
2246        let mut counts = BTreeMap::new();
2247        for session in self
2248            .catalog
2249            .discover(&DiscoveryQuery {
2250                workspace: workspace.map(Path::to_path_buf),
2251                harnesses: harnesses.to_vec(),
2252                ..DiscoveryQuery::default()
2253            })
2254            .unwrap_or_default()
2255        {
2256            *counts
2257                .entry(session.locator.harness.as_str().to_string())
2258                .or_insert(0) += 1;
2259        }
2260        counts
2261    }
2262}
2263
2264#[async_trait::async_trait]
2265impl SdkService for HarnessSessionService {
2266    fn capabilities(&self) -> SdkCapabilities {
2267        SdkCapabilities::default()
2268    }
2269
2270    async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError> {
2271        if request.operation == SdkOperation::Events {
2272            let events = self
2273                .poll_sdk_events()
2274                .await
2275                .into_iter()
2276                .map(|(_, event)| event)
2277                .collect::<Vec<_>>();
2278            return serde_json::to_value(events).map_err(|error| {
2279                SdkError::new(
2280                    SdkErrorCode::Execution,
2281                    request.operation,
2282                    error.to_string(),
2283                )
2284            });
2285        }
2286        if self.runtimes.is_empty()
2287            && matches!(
2288                request.operation,
2289                SdkOperation::Input
2290                    | SdkOperation::Interrupt
2291                    | SdkOperation::Steer
2292                    | SdkOperation::Respond
2293                    | SdkOperation::Close
2294            )
2295        {
2296            return Err(SdkError::unsupported(request.operation));
2297        }
2298        let method = request
2299            .operation
2300            .method()
2301            .ok_or_else(|| SdkError::unsupported(request.operation))?;
2302        let result = match request.operation {
2303            SdkOperation::Discover
2304            | SdkOperation::Load
2305            | SdkOperation::Export
2306            | SdkOperation::ProfilesList
2307            | SdkOperation::ProfilesGet
2308            | SdkOperation::ProfilesCreate
2309            | SdkOperation::ProfilesDelete
2310            | SdkOperation::SkillsList
2311            | SdkOperation::SkillsInstall
2312            | SdkOperation::SkillsRemove
2313            | SdkOperation::ChannelsList
2314            | SdkOperation::RoutesList
2315            | SdkOperation::TriggersList
2316            | SdkOperation::ChannelsStatus
2317            | SdkOperation::MemoryShow
2318            | SdkOperation::MemorySearch
2319            | SdkOperation::JobsList
2320            | SdkOperation::JobsGet
2321            | SdkOperation::JobsCreate
2322            | SdkOperation::JobsUpdate
2323            | SdkOperation::JobsPause
2324            | SdkOperation::JobsResume
2325            | SdkOperation::JobsRun
2326            | SdkOperation::JobsDelete
2327            | SdkOperation::JobsNotepad
2328            | SdkOperation::JobsNotepadSet
2329            | SdkOperation::JobsNotepadDelete
2330            | SdkOperation::RunsList
2331            | SdkOperation::RunsGet
2332            | SdkOperation::ApprovalsList
2333            | SdkOperation::OrchestrationLoad
2334            | SdkOperation::OrchestrationSave
2335            | SdkOperation::OrchestrationCompile
2336            | SdkOperation::OrchestrationDecompile
2337            | SdkOperation::OrchestrationImport
2338            | SdkOperation::OrchestrationExport
2339            | SdkOperation::WorkflowLoad => self.call(method, request.params),
2340            // ORCH-20: answering needs the live connection, so it takes the
2341            // async door and ends in `harness.v1.runtimes.respond`.
2342            SdkOperation::ApprovalsResolve => self.approvals_resolve(request.params).await,
2343            SdkOperation::Start
2344            | SdkOperation::Resume
2345            | SdkOperation::Input
2346            | SdkOperation::Interrupt
2347            | SdkOperation::Steer
2348            | SdkOperation::Respond
2349            | SdkOperation::Close => self.runtime_call(method, request.params).await,
2350            // ORCH-19 controlled tier. Every verb goes through the HARNESS'S
2351            // OWN door — its CLI, its HTTP API, or its slash command typed
2352            // into a live driven session — and returns the row re-read from
2353            // the harness's store afterwards.
2354            SdkOperation::SessionsNew => {
2355                self.mutate_session(crate::SessionVerb::New, request.params)
2356                    .await
2357            }
2358            SdkOperation::SessionsReset => {
2359                self.mutate_session(crate::SessionVerb::Reset, request.params)
2360                    .await
2361            }
2362            SdkOperation::SessionsArchive => {
2363                self.mutate_session(crate::SessionVerb::Archive, request.params)
2364                    .await
2365            }
2366            SdkOperation::SessionsDelete => {
2367                self.mutate_session(crate::SessionVerb::Delete, request.params)
2368                    .await
2369            }
2370            SdkOperation::Events => unreachable!("handled before method dispatch"),
2371        };
2372        result.map_err(|error| sdk_error(request.operation, error))
2373    }
2374
2375    async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError> {
2376        Ok(self
2377            .poll_sdk_events()
2378            .await
2379            .into_iter()
2380            .map(|(_, event)| event)
2381            .collect())
2382    }
2383}
2384
2385#[cfg(feature = "adapter-api")]
2386struct HostedRuntimeLease {
2387    connection: HostedHarnessConnection,
2388    _host: std::sync::Arc<HostedHarnessRuntime>,
2389    _registration: LiveRuntimeRegistration,
2390    _server: crate::server::FrontendHttpServer,
2391}
2392
2393#[async_trait::async_trait]
2394#[cfg(feature = "adapter-api")]
2395impl RuntimeConnection for HostedRuntimeLease {
2396    fn handle(&self) -> &crate::RuntimeHandle {
2397        self.connection.handle()
2398    }
2399
2400    async fn send_input(&mut self, input: RuntimeInput) -> crate::Result<Option<String>> {
2401        self.connection.send_input(input).await
2402    }
2403
2404    async fn next_event(&mut self) -> crate::Result<Option<crate::HarnessEvent>> {
2405        self.connection.next_event().await
2406    }
2407
2408    async fn interrupt(&mut self) -> crate::Result<()> {
2409        self.connection.interrupt().await
2410    }
2411
2412    // the lease must forward every verb its capabilities advertise; without
2413    // this, steer fell to the trait default and refused a turn it claimed
2414    async fn steer(&mut self, text: String) -> crate::Result<()> {
2415        self.connection.steer(text).await
2416    }
2417
2418    async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
2419        self.connection.respond(request_id, response).await
2420    }
2421
2422    async fn close(&mut self) -> crate::Result<()> {
2423        self.connection.close().await
2424    }
2425}
2426
2427/// One inventory request's waiting half, already separated from the service
2428/// state it reads. See [`HarnessSessionService::inventory_work`].
2429struct InventoryWork {
2430    params: HarnessInventoryParams,
2431    supported: Vec<crate::HarnessSupportDescriptor>,
2432    global_counts: Option<BTreeMap<String, usize>>,
2433    workspace_counts: Option<BTreeMap<String, usize>>,
2434}
2435
2436/// Perform one conversation-lifecycle verb through a door that is
2437/// self-contained in [`crate::sessions_control`]: the harness's own CLI, its
2438/// HTTP API, the orchestrator daemon's socket, or supercode's own store.
2439/// Touches no service state, so this runs on any task. The LIVE door is not
2440/// here — it types its slash command through a runtime connection the service
2441/// owns, and is performed by [`HarnessSessionService::mutate_session`].
2442async fn run_session_mutation(
2443    verb: crate::SessionVerb,
2444    mutation: &crate::SessionMutation,
2445) -> std::result::Result<crate::SessionMutationOutcome, ServiceError> {
2446    // Only the HTTP door actually awaits anything. The CLI, store and daemon
2447    // doors run the harness's own program, or its store, with calls that
2448    // block the calling THREAD from start to finish — a future that never
2449    // yields, which no timeout around it can interrupt and which would hold a
2450    // runtime worker for as long as the harness takes. They go to a blocking
2451    // task, where blocking is what the thread is for.
2452    let door =
2453        crate::sessions_control::door(&mutation.harness, verb).map_err(session_control_error)?;
2454    if let crate::SessionDoor::Http = door {
2455        return crate::sessions_control::mutate(verb, mutation)
2456            .await
2457            .map_err(session_control_error);
2458    }
2459    let mutation = mutation.clone();
2460    tokio::task::spawn_blocking(move || crate::sessions_control::mutate_blocking(verb, &mutation))
2461        .await
2462        .map_err(|error| {
2463            ServiceError::Operation(format!("the conversation verb could not be run: {error}"))
2464        })?
2465        .map_err(session_control_error)
2466}
2467
2468/// Probe every selected harness and assemble the report. Touches no service
2469/// state, so this runs on any task.
2470async fn run_inventory(work: InventoryWork) -> std::result::Result<Value, ServiceError> {
2471    let InventoryWork {
2472        params,
2473        supported,
2474        global_counts,
2475        workspace_counts,
2476    } = work;
2477    let probes = supported.into_iter().map(|descriptor| {
2478        let global = global_counts
2479            .as_ref()
2480            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2481        let workspace = workspace_counts
2482            .as_ref()
2483            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2484        probe_harness(descriptor, &params, global, workspace)
2485    });
2486    let harnesses = futures::future::join_all(probes).await;
2487    serde_json::to_value(HarnessInventoryReport {
2488        probe: params.probe,
2489        workspace: params.workspace,
2490        harnesses,
2491    })
2492    .map_err(|error| ServiceError::Operation(error.to_string()))
2493}
2494
2495async fn probe_harness(
2496    descriptor: crate::HarnessSupportDescriptor,
2497    params: &HarnessInventoryParams,
2498    global: Option<usize>,
2499    workspace: Option<usize>,
2500) -> LocalHarness {
2501    let launch = descriptor.runtime.default_launch.as_ref();
2502    // ORC-7: the orchestrator publishes no runtime launch — it is not an
2503    // adapter supercode connects a turn to. What "installed" means for it
2504    // is that its Node daemon entry is present, so the row answers from
2505    // that instead of from a PATH lookup it could never satisfy.
2506    let orchestrator_entry = (descriptor.id.as_str() == HarnessId::ORCHESTRATOR)
2507        .then(crate::orchestrator::daemon_entry)
2508        .and_then(Result::ok);
2509    let executable = match &orchestrator_entry {
2510        Some(entry) => Some(entry.clone()),
2511        None => launch.and_then(|launch| find_executable(&launch.program)),
2512    };
2513    let installed = executable.is_some();
2514    let version = if params.skip_versions || orchestrator_entry.is_some() {
2515        // The orchestrator's "executable" is a Node module, not a CLI
2516        // with a `--version` flag; running it to ask would start a daemon.
2517        None
2518    } else {
2519        match executable.as_deref() {
2520            Some(path) => executable_version(path).await,
2521            None => None,
2522        }
2523    };
2524    let configured = auth_evidence(descriptor.id.as_str());
2525    let mut auth = if configured {
2526        HarnessAuthState::Configured
2527    } else if matches!(
2528        descriptor.id.as_str(),
2529        HarnessId::CLAUDE_CODE | HarnessId::CODEX
2530    ) {
2531        // These two adapters have explicit native status/login contracts
2532        // and complete local evidence coverage (including Claude's macOS
2533        // Keychain-backed oauthAccount marker). Treating absent evidence
2534        // as unknown advertises a start that will only fail interactively.
2535        HarnessAuthState::Required
2536    } else {
2537        HarnessAuthState::Unknown
2538    };
2539    let mut runtime = if installed {
2540        HarnessRuntimeState::Degraded
2541    } else {
2542        HarnessRuntimeState::Unavailable
2543    };
2544    let is_orchestrator = descriptor.id.as_str() == HarnessId::ORCHESTRATOR;
2545    let mut reason = (!installed).then(|| {
2546        if is_orchestrator {
2547            format!(
2548                "{} is supported but its daemon entry `{}` was not found",
2549                descriptor.display_name,
2550                crate::orchestrator::DAEMON_ENTRY
2551            )
2552        } else {
2553            format!(
2554                "{} is supported but `{}` was not found on PATH",
2555                descriptor.display_name,
2556                launch
2557                    .map(|launch| launch.program.as_str())
2558                    .unwrap_or("executable")
2559            )
2560        }
2561    });
2562    let mut repair = (!installed).then(|| {
2563        if is_orchestrator {
2564            format!(
2565                "Install the `supercode-orchestrator` package so `{}` resolves.",
2566                crate::orchestrator::DAEMON_ENTRY
2567            )
2568        } else {
2569            format!(
2570                "Install {} and ensure `{}` is on PATH.",
2571                descriptor.display_name,
2572                launch
2573                    .map(|launch| launch.program.as_str())
2574                    .unwrap_or("its executable")
2575            )
2576        }
2577    });
2578
2579    if installed && params.probe == HarnessProbeLevel::Handshake {
2580        let backend_params = RuntimeBackendParams {
2581            harness: descriptor.id.clone(),
2582            protocol: None,
2583            launch: None,
2584            base_url: None,
2585            policy: RuntimePolicy::Default,
2586        };
2587        match runtime_backend(&backend_params) {
2588            Ok(backend) => {
2589                let cwd = params
2590                    .workspace
2591                    .clone()
2592                    .or_else(|| std::env::current_dir().ok())
2593                    .unwrap_or_else(|| PathBuf::from("."));
2594                let isolated = descriptor
2595                    .runtime
2596                    .default_launch
2597                    .clone()
2598                    .and_then(|launch| IsolatedProbeHome::new(descriptor.id.as_str(), launch).ok());
2599                let Some(isolated) = isolated else {
2600                    reason = Some(
2601                        "No-prompt runtime handshake could not create its isolated harness home."
2602                            .into(),
2603                    );
2604                    repair = Some(
2605                        "Check temporary-directory permissions, then run the handshake probe again."
2606                            .into(),
2607                    );
2608                    let running = probe_running_instance(descriptor.id.as_str());
2609                    return LocalHarness {
2610                        gateway: gateway_health(
2611                            descriptor.id.as_str(),
2612                            installed,
2613                            running.as_ref(),
2614                            version.as_deref(),
2615                        ),
2616                        id: descriptor.id,
2617                        display_name: descriptor.display_name,
2618                        supported: true,
2619                        installed,
2620                        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2621                        version,
2622                        auth,
2623                        runtime,
2624                        protocol: descriptor.runtime.protocol,
2625                        capabilities: descriptor.runtime.capabilities.clone(),
2626                        effective_capabilities: descriptor.runtime.capabilities,
2627                        sessions: HarnessSessionCounts { global, workspace },
2628                        running,
2629                        reason,
2630                        repair,
2631                    };
2632                };
2633                match tokio::time::timeout(
2634                    Duration::from_secs(30),
2635                    backend.start(RuntimeStartRequest {
2636                        cwd,
2637                        launch: Some(isolated.launch.clone()),
2638                        mcp_servers: Vec::new(),
2639                        approval_policy: None,
2640                    }),
2641                )
2642                .await
2643                {
2644                    Ok(Ok(mut connection)) => {
2645                        match stabilize_handshake(connection.as_mut()).await {
2646                            Ok(()) => {
2647                                auth = HarnessAuthState::Ready;
2648                                runtime = HarnessRuntimeState::Ready;
2649                                reason = Some(
2650                                    "No-prompt runtime handshake remained healthy through the startup stabilization window; no model request was sent."
2651                                        .into(),
2652                                );
2653                                repair = None;
2654                            }
2655                            Err(message) => {
2656                                auth = if looks_like_auth_error(&message) {
2657                                    HarnessAuthState::Required
2658                                } else if configured {
2659                                    HarnessAuthState::Configured
2660                                } else {
2661                                    HarnessAuthState::Unknown
2662                                };
2663                                reason = Some(format!(
2664                                    "No-prompt runtime handshake became unhealthy during startup: {message}"
2665                                ));
2666                                repair = Some(if auth == HarnessAuthState::Required {
2667                                    format!(
2668                                        "Run `{}` interactively once and complete sign-in, then probe again.",
2669                                        launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2670                                    )
2671                                } else {
2672                                    "Run the harness directly to inspect its startup failure, then probe again."
2673                                        .into()
2674                                });
2675                            }
2676                        }
2677                        let _ =
2678                            tokio::time::timeout(Duration::from_secs(3), connection.close()).await;
2679                    }
2680                    Ok(Err(error)) => {
2681                        let message = truncate_text(&error.to_string(), 500);
2682                        auth = if looks_like_auth_error(&message) {
2683                            HarnessAuthState::Required
2684                        } else if configured {
2685                            HarnessAuthState::Configured
2686                        } else {
2687                            HarnessAuthState::Unknown
2688                        };
2689                        reason = Some(format!("No-prompt runtime handshake failed: {message}"));
2690                        repair = Some(if auth == HarnessAuthState::Required {
2691                            format!(
2692                                "Run `{}` interactively once and complete sign-in, then probe again.",
2693                                launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2694                            )
2695                        } else {
2696                            "Check the harness installation and run the handshake probe again."
2697                                .into()
2698                        });
2699                    }
2700                    Err(_) => {
2701                        reason =
2702                            Some("No-prompt runtime handshake timed out after 30 seconds.".into());
2703                        repair = Some("Run the harness directly to check startup or authentication, then probe again.".into());
2704                    }
2705                }
2706                // Keep the isolated home alive through process teardown.
2707                // Otherwise the compiler may release the last meaningful
2708                // use after cloning `launch`, and a still-starting CLI can
2709                // recreate its state directory after Drop removed it.
2710                // Some Node-based launchers finish a short asynchronous
2711                // installation-id write just after their parent process
2712                // is reaped. Remove once immediately, allow that bounded
2713                // writer to settle, then perform the authoritative pass.
2714                let _ = isolated.cleanup();
2715                tokio::time::sleep(Duration::from_millis(250)).await;
2716                if let Err(error) = isolated.cleanup() {
2717                    auth = if configured {
2718                        HarnessAuthState::Configured
2719                    } else {
2720                        HarnessAuthState::Unknown
2721                    };
2722                    runtime = HarnessRuntimeState::Degraded;
2723                    reason = Some(format!(
2724                        "No-prompt runtime handshake could not remove its isolated harness home: {error}"
2725                    ));
2726                    repair = Some(
2727                        "Check temporary-directory permissions, remove the reported disposable probe home, then run the handshake again."
2728                            .into(),
2729                    );
2730                }
2731            }
2732            Err(error) => {
2733                reason = Some(error_message(error));
2734            }
2735        }
2736    } else if installed && configured {
2737        reason = Some("Executable and local authentication evidence found; use a handshake probe to verify readiness.".into());
2738    } else if installed && auth == HarnessAuthState::Required {
2739        reason = Some("Executable found, but no native authentication evidence is present.".into());
2740        repair = Some(format!(
2741            "Run `supercode harness login {}` to use the harness-owned sign-in flow.",
2742            descriptor.id.as_str()
2743        ));
2744    } else if installed {
2745        reason = Some("Executable found; authentication readiness is unknown until a no-prompt handshake succeeds.".into());
2746        repair = Some(format!(
2747            "Run `{}` interactively once if sign-in is required, or use `--probe handshake`.",
2748            launch
2749                .map(|launch| launch.program.as_str())
2750                .unwrap_or("the harness")
2751        ));
2752    }
2753
2754    let effective_capabilities = if installed {
2755        descriptor.runtime.capabilities.clone()
2756    } else {
2757        unavailable_capabilities()
2758    };
2759    let running = probe_running_instance(descriptor.id.as_str());
2760    LocalHarness {
2761        gateway: gateway_health(
2762            descriptor.id.as_str(),
2763            installed,
2764            running.as_ref(),
2765            version.as_deref(),
2766        ),
2767        id: descriptor.id,
2768        display_name: descriptor.display_name,
2769        supported: true,
2770        installed,
2771        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2772        version,
2773        auth,
2774        runtime,
2775        protocol: descriptor.runtime.protocol,
2776        capabilities: descriptor.runtime.capabilities,
2777        effective_capabilities,
2778        sessions: HarnessSessionCounts { global, workspace },
2779        running,
2780        reason,
2781        repair,
2782    }
2783}
2784
2785async fn stabilize_handshake(connection: &mut dyn RuntimeConnection) -> Result<(), String> {
2786    let deadline = tokio::time::Instant::now() + Duration::from_secs(3);
2787    loop {
2788        let now = tokio::time::Instant::now();
2789        if now >= deadline {
2790            return Ok(());
2791        }
2792        match tokio::time::timeout(deadline - now, connection.next_event()).await {
2793            Err(_) => return Ok(()),
2794            Ok(Ok(Some(event))) => {
2795                if let Some(message) = handshake_event_failure(&event) {
2796                    return Err(truncate_text(&message, 500));
2797                }
2798            }
2799            Ok(Ok(None)) => return Err("runtime transport closed during startup".into()),
2800            Ok(Err(error)) => return Err(error.to_string()),
2801        }
2802    }
2803}
2804
2805fn handshake_event_failure(event: &crate::HarnessEvent) -> Option<String> {
2806    let detail = event
2807        .payload
2808        .get("message")
2809        .or_else(|| event.payload.get("line"))
2810        .and_then(Value::as_str)
2811        .unwrap_or(event.kind.as_str());
2812    match event.kind.as_str() {
2813        "transport_closed" => Some("runtime transport closed during startup".into()),
2814        "transport_error" => Some(format!("runtime transport error: {detail}")),
2815        "malformed_output" => Some(format!("runtime emitted non-protocol output: {detail}")),
2816        // Stderr is retained as a runtime event, but is not transport health.
2817        // Grok, for example, can log an AuthorizationRequired error from an
2818        // optional background worker while its ACP session continues to send
2819        // updates and complete prompts normally.
2820        _ => None,
2821    }
2822}
2823
2824fn indexed_claude_window(
2825    locator: &SessionLocator,
2826    options: &SessionLoadOptions,
2827) -> std::result::Result<Option<Value>, ServiceError> {
2828    use supercode_interchange::session::ClaudeReadIndex;
2829    // Exact parent-only window: recursive/full-artifact requests retain the
2830    // existing owner. This is not a bounded display-history substitution.
2831    if locator.harness.as_str() != HarnessId::CLAUDE_CODE
2832        || options.include_subagents != Some(false)
2833    {
2834        return Ok(None);
2835    }
2836    let crate::StorageLocator::File { path } = &locator.storage else {
2837        return Ok(None);
2838    };
2839    if !ClaudeReadIndex::supports(path)
2840        .map_err(|error| ServiceError::Operation(error.to_string()))?
2841    {
2842        return Ok(None);
2843    }
2844    let mut index = ClaudeReadIndex::open(path, Fidelity::ByteLossless)
2845        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2846    let total = index.len();
2847    let (offset, end) = projected_message_window(total, options);
2848    let session = index
2849        .read_messages(offset..end)
2850        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2851    let summary = index
2852        .read_summary()
2853        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2854    let selected_options = SessionLoadOptions {
2855        message_offset: None,
2856        message_limit: None,
2857        message_tail: None,
2858        ..options.clone()
2859    };
2860    let mut selected = projected_session_json(&session, &selected_options);
2861    selected["raw_record_count"] = json!(index.raw_record_count());
2862    Ok(Some(json!({
2863        "session": selected,
2864        "summary": projected_session_summary(&summary, options),
2865        "window": {
2866            "has_more": offset > 0 || end < total, "has_newer": end < total,
2867            "has_older": offset > 0, "newer_items": index.item_count(end..total),
2868            "offset": offset, "older_items": index.item_count(0..offset),
2869            "returned": end - offset, "total_messages": total,
2870        }
2871    })))
2872}
2873
2874fn projected_session_result(session: &Session, options: &SessionLoadOptions) -> Value {
2875    let total_messages = session.messages.len();
2876    let (offset, end) = projected_message_window(total_messages, options);
2877    json!({
2878        "session": projected_session_json(session, options),
2879        "summary": projected_session_summary(session, options),
2880        "window": {
2881            "has_more": offset > 0 || end < total_messages,
2882            "has_newer": end < total_messages,
2883            "has_older": offset > 0,
2884            "newer_items": normalized_item_count(&session.messages[end..]),
2885            "offset": offset,
2886            "older_items": normalized_item_count(&session.messages[..offset]),
2887            "returned": end.saturating_sub(offset),
2888            "total_messages": total_messages,
2889        }
2890    })
2891}
2892
2893fn normalized_item_count(messages: &[crate::ChatMessage]) -> usize {
2894    messages
2895        .iter()
2896        .map(|message| {
2897            let conversation = usize::from(
2898                matches!(message.role, Role::Assistant | Role::User)
2899                    && message_has_content(message),
2900            );
2901            let tool_result =
2902                usize::from(message.role == Role::Tool && message_has_content(message));
2903            conversation + tool_result + message.tool_calls().len()
2904        })
2905        .sum()
2906}
2907
2908fn projected_session_summary(session: &Session, options: &SessionLoadOptions) -> Value {
2909    let mut conversational = session.messages.iter().filter(|message| {
2910        matches!(message.role, Role::Assistant | Role::User) && message_has_content(message)
2911    });
2912    let first_message = conversational.clone().next();
2913    let last_message = conversational.next_back();
2914    let mut assistant = session
2915        .messages
2916        .iter()
2917        .filter(|message| message.role == Role::Assistant && message_has_content(message));
2918    let first_assistant_message = assistant.clone().next();
2919    let last_assistant_message = assistant.next_back();
2920    let end_of_turn = session
2921        .messages
2922        .iter()
2923        .rev()
2924        .find(|message| message.role != Role::System)
2925        .is_some_and(|message| {
2926            message.role == Role::Assistant
2927                && message_has_content(message)
2928                && message.tool_calls().is_empty()
2929                // Codex narrates while it works (`phase: commentary`); only its `final_answer` ends a turn
2930                && message.metadata.get("phase").map(String::as_str) != Some("commentary")
2931        });
2932    let project = |message: Option<&crate::ChatMessage>| {
2933        message.map(|message| project_inline_media(message_json(message), options))
2934    };
2935    json!({
2936        "end_of_turn": end_of_turn,
2937        "first_assistant_message": project(first_assistant_message),
2938        "first_message": project(first_message),
2939        "last_assistant_message": project(last_assistant_message),
2940        "last_assistant_text": last_assistant_message.map(message_text).unwrap_or_default(),
2941        "last_message": project(last_message),
2942    })
2943}
2944
2945fn message_has_content(message: &crate::ChatMessage) -> bool {
2946    message
2947        .content
2948        .as_deref()
2949        .is_some_and(|content| !content.trim().is_empty())
2950        || message
2951            .content_parts
2952            .as_ref()
2953            .is_some_and(|parts| !parts.is_empty())
2954}
2955
2956fn message_text(message: &crate::ChatMessage) -> String {
2957    if let Some(content) = &message.content {
2958        return content.clone();
2959    }
2960    message
2961        .content_parts
2962        .as_ref()
2963        .into_iter()
2964        .flatten()
2965        .filter_map(|part| part.get("text").and_then(Value::as_str))
2966        .collect::<Vec<_>>()
2967        .join("\n")
2968}
2969
2970fn projected_session_json(session: &Session, options: &SessionLoadOptions) -> Value {
2971    let (offset, end) = projected_message_window(session.messages.len(), options);
2972    let messages = session.messages[offset..end]
2973        .iter()
2974        .map(|message| project_inline_media(message_json(message), options))
2975        .collect::<Vec<_>>();
2976    let subagents = if options.include_subagents.unwrap_or(true) {
2977        // The reported window describes the top-level transcript. Applying it
2978        // recursively would silently truncate subagents without returning a
2979        // window for each child. Keep their histories complete while carrying
2980        // the caller's media policy through the tree.
2981        let subagent_options = SessionLoadOptions {
2982            message_limit: None,
2983            message_offset: None,
2984            message_tail: None,
2985            ..options.clone()
2986        };
2987        session
2988            .subagents
2989            .iter()
2990            .map(|subagent| projected_session_json(subagent, &subagent_options))
2991            .collect::<Vec<_>>()
2992    } else {
2993        Vec::new()
2994    };
2995    json!({
2996        "source": match session.meta.source {
2997            SessionSource::ClaudeCode => "claude_code",
2998            SessionSource::Codex => "codex",
2999            SessionSource::Gemini => "gemini",
3000            SessionSource::Goose => "goose",
3001            SessionSource::Grok => "grok",
3002            SessionSource::Native => "native",
3003            SessionSource::OpenClaw => "openclaw",
3004            SessionSource::Hermes => "hermes",
3005            SessionSource::OpenCode => "opencode",
3006            SessionSource::Pi => "pi",
3007        },
3008        "session_id": session.meta.session_id,
3009        "ended_at": session.meta.ended_at,
3010        "end_reason": session.meta.end_reason,
3011        "model": session.meta.model,
3012        "cwd": session.meta.cwd,
3013        "system_prompt": session.meta.system_prompt,
3014        "agent_id": session.meta.agent_id,
3015        "parent_tool_use_id": session.meta.parent_tool_use_id,
3016        "lineage": session.meta.lineage,
3017        "messages": messages,
3018        "subagents": subagents,
3019        "raw_record_count": session.raw.len(),
3020        "parse_error_lines": session.parse_error_lines,
3021    })
3022}
3023
3024fn projected_message_window(total: usize, options: &SessionLoadOptions) -> (usize, usize) {
3025    if let Some(tail) = options.message_tail {
3026        return (total.saturating_sub(tail), total);
3027    }
3028    let offset = options.message_offset.unwrap_or(0).min(total);
3029    let end = options
3030        .message_limit
3031        .map(|limit| offset.saturating_add(limit).min(total))
3032        .unwrap_or(total);
3033    (offset, end)
3034}
3035
3036fn project_inline_media(mut message: Value, options: &SessionLoadOptions) -> Value {
3037    let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else {
3038        return message;
3039    };
3040    for part in parts {
3041        let Some(url) = part
3042            .get("image_url")
3043            .and_then(|image| image.get("url"))
3044            .and_then(Value::as_str)
3045        else {
3046            continue;
3047        };
3048        let Some(rest) = url.strip_prefix("data:") else {
3049            continue;
3050        };
3051        let Some((media_type, encoded)) = rest.split_once(";base64,") else {
3052            continue;
3053        };
3054        let padding = usize::from(encoded.ends_with('=')) + usize::from(encoded.ends_with("=="));
3055        let decoded_bytes = encoded.len().saturating_mul(3) / 4;
3056        let decoded_bytes = decoded_bytes.saturating_sub(padding);
3057        let should_elide = matches!(options.inline_media, InlineMediaMode::Metadata)
3058            || options
3059                .max_inline_media_bytes
3060                .is_some_and(|limit| decoded_bytes > limit);
3061        if should_elide {
3062            *part = json!({
3063                "type": "media_reference",
3064                "media_type": media_type,
3065                "encoding": "base64",
3066                "encoded_bytes": encoded.len(),
3067                "decoded_bytes": decoded_bytes,
3068                "omitted": true,
3069            });
3070        }
3071    }
3072    message
3073}
3074
3075#[derive(Deserialize)]
3076struct LocatorParams {
3077    locator: SessionLocator,
3078    /// Optional fidelity for the READ surfaces (`sessions.load`,
3079    /// `sessions.follow`).
3080    ///
3081    /// Omitted means [`Fidelity::Semantic`]: these two methods only ever
3082    /// produce a read-only view, and a compacted or resumed-across-files
3083    /// transcript — the everyday shape of a long Claude Code session — has no
3084    /// losslessly reconstructable record graph, so refusing to render it made
3085    /// the mirror unusable rather than accurate. A caller that intends to
3086    /// CONTINUE from what it reads asks for a lossless level explicitly and
3087    /// gets the strict refusal back. Every other method (export, translate,
3088    /// branch, handoff, resume_instructions) is lossless-only and has no
3089    /// such knob.
3090    #[serde(default)]
3091    fidelity: Option<Fidelity>,
3092    /// Optional bounded frontend projection. Absent preserves the historical
3093    /// complete-session read contract.
3094    #[serde(default)]
3095    view: Option<SessionReadView>,
3096}
3097
3098#[derive(Deserialize)]
3099struct SessionReadView {
3100    /// Number of trailing normalized messages to return. Zero is treated as
3101    /// one so a caller cannot accidentally request an unbounded empty mode.
3102    #[serde(default)]
3103    tail_messages: Option<usize>,
3104    /// Whether Claude Code child transcripts belong in this view. The
3105    /// frontend default is false; the legacy no-view path remains true.
3106    #[serde(default)]
3107    include_subagents: bool,
3108    /// Preserve human-visible native history across model-context compaction.
3109    #[serde(default)]
3110    display_history: bool,
3111    /// Bound each individual text field so a single tool result cannot turn a
3112    /// small message window into a hundred-megabyte RPC response.
3113    #[serde(default)]
3114    max_message_chars: Option<usize>,
3115}
3116
3117impl LocatorParams {
3118    fn read_fidelity(&self) -> Fidelity {
3119        self.fidelity.unwrap_or(Fidelity::Semantic)
3120    }
3121
3122    fn include_subagents(&self) -> bool {
3123        self.view
3124            .as_ref()
3125            .map(|view| view.include_subagents)
3126            .unwrap_or(true)
3127    }
3128
3129    fn tail_messages(&self) -> Option<usize> {
3130        self.view
3131            .as_ref()
3132            .and_then(|view| view.tail_messages)
3133            .map(|limit| limit.clamp(1, 5_000))
3134    }
3135
3136    fn display_history(&self) -> bool {
3137        self.view.as_ref().is_some_and(|view| view.display_history)
3138    }
3139
3140    fn max_message_chars(&self) -> Option<usize> {
3141        self.view
3142            .as_ref()
3143            .and_then(|view| view.max_message_chars)
3144            .map(|limit| limit.clamp(256, 64_000))
3145    }
3146
3147    fn bound_session(&self, session: &mut Session) {
3148        bound_session_view(session, self.tail_messages(), self.max_message_chars());
3149    }
3150}
3151
3152#[derive(Debug, Clone, Copy, Default, Deserialize)]
3153#[serde(rename_all = "snake_case")]
3154enum InlineMediaMode {
3155    #[default]
3156    Full,
3157    Metadata,
3158}
3159
3160#[derive(Debug, Clone, Default, Deserialize)]
3161#[serde(default)]
3162struct SessionLoadOptions {
3163    include_subagents: Option<bool>,
3164    inline_media: InlineMediaMode,
3165    max_inline_media_bytes: Option<usize>,
3166    message_limit: Option<usize>,
3167    message_offset: Option<usize>,
3168    message_tail: Option<usize>,
3169}
3170
3171impl SessionLoadOptions {
3172    fn validate(&self) -> std::result::Result<(), ServiceError> {
3173        if self.message_tail.is_some()
3174            && (self.message_limit.is_some() || self.message_offset.is_some())
3175        {
3176            return Err(ServiceError::InvalidParams(
3177                "sessions.load options.message_tail cannot be combined with message_limit or message_offset"
3178                    .into(),
3179            ));
3180        }
3181        Ok(())
3182    }
3183}
3184
3185#[derive(Deserialize)]
3186struct LoadSessionParams {
3187    #[serde(flatten)]
3188    read: LocatorParams,
3189    #[serde(default)]
3190    options: Option<SessionLoadOptions>,
3191}
3192
3193#[derive(Deserialize)]
3194struct UnfollowParams {
3195    subscription: String,
3196}
3197
3198#[derive(Debug, Deserialize)]
3199#[serde(deny_unknown_fields)]
3200struct IndexResizeParams {
3201    subscription: String,
3202    limit: usize,
3203}
3204
3205#[derive(Deserialize)]
3206struct ActivitySubscribeParams {
3207    locators: Vec<SessionLocator>,
3208    #[serde(default)]
3209    homes: crate::HarnessHomes,
3210}
3211
3212#[derive(Deserialize)]
3213struct MessageSessionParams {
3214    locator: SessionLocator,
3215    text: String,
3216    /// Name the sender is known by (`fleet-board`, `aaron`). Replies to the
3217    /// message are filed in this sender's mailbox, read with
3218    /// `sessions.inbox`. Defaults to `supercode`.
3219    #[serde(default)]
3220    from_name: Option<String>,
3221    /// Id of the message this one answers.
3222    #[serde(default)]
3223    in_reply_to: Option<String>,
3224    /// File one notice in the sender's mailbox when the receiver next goes idle.
3225    #[serde(default)]
3226    notify_when_idle: bool,
3227    /// The text is the session's own user speaking (a voice bridge, a board
3228    /// the owner types in): user mail, delivered as the user's own turn.
3229    /// `harness.v1` is served only to the machine's owner (its daemon admits
3230    /// operators only), which is the authority this carries.
3231    #[serde(default)]
3232    as_user: bool,
3233    /// Same storage roots discovery accepts, so a caller (and a test) can
3234    /// point the live-session registry somewhere other than `$HOME`.
3235    #[serde(default)]
3236    homes: crate::HarnessHomes,
3237}
3238
3239#[derive(Deserialize)]
3240#[serde(deny_unknown_fields)]
3241struct InboxParams {
3242    /// Sender name used with `sessions.message` (its mailbox), or
3243    #[serde(default)]
3244    from_name: Option<String>,
3245    /// an explicit session address `sc:<machine>:<harness>:<id>`.
3246    #[serde(default)]
3247    address: Option<String>,
3248    /// Include messages already read.
3249    #[serde(default)]
3250    all: bool,
3251}
3252
3253#[derive(Deserialize)]
3254#[serde(deny_unknown_fields)]
3255struct HarnessSettingsParams {
3256    harness: String,
3257}
3258
3259#[derive(Deserialize)]
3260#[serde(deny_unknown_fields)]
3261struct ConfigureHarnessParams {
3262    harness: String,
3263    #[serde(default)]
3264    changes: Vec<crate::HarnessSettingChange>,
3265    #[serde(default)]
3266    expected_revision: Option<String>,
3267}
3268
3269fn claude_inbound_controls_or_error(homes: &crate::HarnessHomes) -> (Value, Value) {
3270    match crate::inspect_harness_interop_settings(homes, HarnessId::CLAUDE_CODE) {
3271        Ok(report) => (
3272            serde_json::to_value(report).unwrap_or(Value::Null),
3273            Value::Null,
3274        ),
3275        Err(error) => (
3276            Value::Null,
3277            Value::String(format!(
3278                "Volter Harness could not inspect Claude Code inbound controls: {error}"
3279            )),
3280        ),
3281    }
3282}
3283
3284/// Deliver `text` into a session that is running right now, or say why not.
3285///
3286/// A refusal is a RESULT, not a JSON-RPC error: "that session is persisted
3287/// only" is an answer about the session, which a mirror renders next to the
3288/// transcript, and this service's error envelope carries no structured data
3289/// field a machine-readable reason could survive in.
3290///
3291/// The door is chosen by the one router every sender uses
3292/// ([`crate::mail_route`]): a session supercode controls gets it through its
3293/// runtime (the default tier); a Claude session it does not control through a
3294/// Claude relay, so the reply comes back; a Codex session through its hook;
3295/// anything else is stored. Replies are filed in the sender's mailbox under
3296/// `reply_to`, read with `sessions.inbox`.
3297async fn message_live_session(params: &MessageSessionParams) -> Value {
3298    use crate::mail_route::{Delivered, NoDoor, Refused};
3299    let (inbound_controls, inbound_controls_error) =
3300        claude_inbound_controls_or_error(&params.homes);
3301    let refused = |reason: &str, message: String| {
3302        json!({
3303            "delivered_to_bus": false,
3304            "refusal": {"reason": reason, "message": message},
3305            "inbound_controls": inbound_controls,
3306            "inbound_controls_error": inbound_controls_error,
3307        })
3308    };
3309    if params.text.trim().is_empty() {
3310        return refused(
3311            crate::claude_peer::ClaudePeerRefusal::DeliveryFailed.as_str(),
3312            "refusing to deliver an empty message".into(),
3313        );
3314    }
3315    let sender = match operator_address(params.from_name.as_deref()) {
3316        Ok(sender) => sender,
3317        Err(message) => return refused("invalid_sender", message),
3318    };
3319    let receiver = match crate::mailbox::MailAddress::new(
3320        crate::mailbox::local_machine_name(),
3321        params.locator.harness.as_str(),
3322        &params.locator.session_id,
3323    ) {
3324        Ok(receiver) => receiver,
3325        Err(error) => return refused("delivery_failed", error.to_string()),
3326    };
3327    // A Claude session's registry name must still name only that session.
3328    if params.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3329        && crate::runtime_mail::controlled_runtime(
3330            HarnessId::CLAUDE_CODE,
3331            &params.locator.session_id,
3332        )
3333        .is_none()
3334    {
3335        if let Err(refusal) =
3336            crate::claude_peer::resolve_live_session(&params.homes, &params.locator.session_id)
3337        {
3338            return refused(refusal.reason.as_str(), refusal.message);
3339        }
3340    }
3341    if params.as_user {
3342        return message_as_user(
3343            params,
3344            sender,
3345            receiver,
3346            inbound_controls,
3347            inbound_controls_error,
3348        )
3349        .await;
3350    }
3351    let door = match crate::mail_route::door_for(&params.homes, &receiver) {
3352        Ok(door) => door,
3353        Err(NoDoor::NotRunning | NoDoor::OtherMachine(_)) => {
3354            return refused(
3355                crate::claude_peer::ClaudePeerRefusal::NotLive.as_str(),
3356                format!(
3357                    "no running `{}` session `{}` is reachable; its transcript is persisted only",
3358                    params.locator.harness.as_str(),
3359                    params.locator.session_id
3360                ),
3361            )
3362        }
3363    };
3364    let mut envelope = match crate::mailbox::Envelope::new(
3365        sender.clone(),
3366        format!("{}@{}", sender.session_id, sender.machine),
3367        crate::mailbox::MailKind::Peer,
3368        crate::mailbox::ReplyVia::Command,
3369        params.text.clone(),
3370    ) {
3371        Ok(envelope) => envelope,
3372        Err(error) => return refused("delivery_failed", error.to_string()),
3373    };
3374    envelope.in_reply_to = params.in_reply_to.clone();
3375    let how = match crate::mail_route::deliver(
3376        &envelope,
3377        &receiver,
3378        &door,
3379        true,
3380        params.notify_when_idle,
3381    )
3382    .await
3383    {
3384        Err(detail) => {
3385            return refused(
3386                crate::claude_peer::ClaudePeerRefusal::DeliveryFailed.as_str(),
3387                detail,
3388            )
3389        }
3390        Ok(Err(Refused::CannotQueueNative)) => unreachable!("sessions.message always wakes"),
3391        Ok(Err(Refused::TooLong(bytes))) => {
3392            return refused(
3393                "too_long",
3394                format!(
3395                    "the message is {bytes} bytes; the limit is {}",
3396                    crate::mail_route::MAX_RELAYED_BYTES
3397                ),
3398            )
3399        }
3400        Ok(Ok(delivered)) => match delivered {
3401            Delivered::Steered => "steered",
3402            Delivered::Started => "started",
3403            Delivered::Native { busy: true } => "next_tool_call",
3404            Delivered::Native { busy: false } => "started",
3405            Delivered::Hooked => "hook",
3406            Delivered::Queued => "queued",
3407            Delivered::Stored => "stored",
3408            Delivered::Operator => "filed",
3409        },
3410    };
3411    json!({
3412        "delivered_to_bus": !matches!(door, crate::mail_route::Door::Stored),
3413        "message_id": envelope.id,
3414        "reply_to": sender.to_string(),
3415        "target": {
3416            "harness": params.locator.harness.as_str(),
3417            "session_id": params.locator.session_id,
3418            "name": match &door {
3419                crate::mail_route::Door::Native(session) => Some(session.name.clone()),
3420                _ => None,
3421            },
3422        },
3423        "delivery": {"door": door.name(), "how": how},
3424        "inbound_controls": inbound_controls,
3425        "inbound_controls_error": inbound_controls_error,
3426    })
3427}
3428
3429/// `sessions.message` with `as_user`: user mail, through the one door that
3430/// carries the user's authority (a hosted runtime's input, or the session's
3431/// pane when its composer is empty; otherwise it waits in the mailbox).
3432async fn message_as_user(
3433    params: &MessageSessionParams,
3434    sender: crate::mailbox::MailAddress,
3435    receiver: crate::mailbox::MailAddress,
3436    inbound_controls: Value,
3437    inbound_controls_error: Value,
3438) -> Value {
3439    let envelope = match crate::mailbox::Envelope::new(
3440        sender.clone(),
3441        format!("{}@{}", sender.session_id, sender.machine),
3442        crate::mailbox::MailKind::User,
3443        crate::mailbox::ReplyVia::None,
3444        params.text.clone(),
3445    ) {
3446        Ok(envelope) => envelope,
3447        Err(error) => {
3448            return json!({
3449                "delivered_to_bus": false,
3450                "refusal": {"reason": "delivery_failed", "message": error.to_string()},
3451                "inbound_controls": inbound_controls,
3452                "inbound_controls_error": inbound_controls_error,
3453            })
3454        }
3455    };
3456    match crate::mail_route::deliver_user_turn(&params.homes, &envelope, &receiver).await {
3457        Ok(turn) => json!({
3458            "delivered_to_bus": turn != crate::mail_route::UserTurn::Waiting,
3459            "message_id": envelope.id,
3460            "target": {
3461                "harness": params.locator.harness.as_str(),
3462                "session_id": params.locator.session_id,
3463            },
3464            "delivery": {
3465                "door": match turn {
3466                    crate::mail_route::UserTurn::Steered | crate::mail_route::UserTurn::Started => "runtime",
3467                    _ => "pane",
3468                },
3469                "how": turn.as_str(),
3470            },
3471            "inbound_controls": inbound_controls,
3472            "inbound_controls_error": inbound_controls_error,
3473        }),
3474        Err(message) => json!({
3475            "delivered_to_bus": false,
3476            "refusal": {"reason": "no_user_door", "message": message},
3477            "inbound_controls": inbound_controls,
3478            "inbound_controls_error": inbound_controls_error,
3479        }),
3480    }
3481}
3482
3483#[derive(Deserialize)]
3484#[serde(deny_unknown_fields)]
3485struct ActivityUnderParams {
3486    /// Root processes (a terminal pane's shell, say).
3487    pids: Vec<u32>,
3488    #[serde(default)]
3489    homes: crate::HarnessHomes,
3490}
3491
3492/// `harness.v1.sessions.activity_under`: the harness session running beneath
3493/// each root process, with its activity, so a terminal's attention takes a
3494/// harness pane's liveness from the harness's own lifecycle.
3495async fn activity_under_call(params: Value) -> std::result::Result<Value, ServiceError> {
3496    let params = decode::<ActivityUnderParams>(params)?;
3497    if params.pids.len() > 1024 {
3498        return Err(ServiceError::InvalidParams(
3499            "sessions.activity_under accepts at most 1024 pids".into(),
3500        ));
3501    }
3502    let found = crate::session_activity::activity_under(&params.pids, &params.homes)
3503        .await
3504        .map_err(ServiceError::Sdk)?;
3505    Ok(json!({
3506        "activities": found
3507            .into_iter()
3508            .map(|(pid, activity)| json!({"pid": pid, "activity": activity}))
3509            .collect::<Vec<_>>(),
3510    }))
3511}
3512
3513/// Mailbox address of an operator sender (a board, a person's shell) that is
3514/// not itself a harness session: `sc:<machine>:operator:<name>`.
3515fn operator_address(
3516    name: Option<&str>,
3517) -> std::result::Result<crate::mailbox::MailAddress, String> {
3518    let name: String = name
3519        .unwrap_or("supercode")
3520        .trim()
3521        .chars()
3522        .map(|character| {
3523            if character.is_whitespace() || character == '@' {
3524                '-'
3525            } else {
3526                character
3527            }
3528        })
3529        .collect();
3530    if name.is_empty() {
3531        return Err("from_name must not be empty".into());
3532    }
3533    crate::mailbox::MailAddress::new(crate::mailbox::local_machine_name(), "operator", name)
3534        .map_err(|error| error.to_string())
3535}
3536
3537/// `harness.v1.sessions.inbox`: a sender's mailbox, each message with the
3538/// text its reader sees. Unread messages are claimed and acknowledged by this
3539/// read, so a second read does not return them again.
3540fn inbox_call(params: InboxParams) -> std::result::Result<Value, ServiceError> {
3541    let address = match (&params.address, &params.from_name) {
3542        (Some(address), None) => crate::mailbox::MailAddress::parse(address)
3543            .map_err(|error| ServiceError::InvalidParams(error.to_string()))?,
3544        (None, name) => operator_address(name.as_deref()).map_err(ServiceError::InvalidParams)?,
3545        (Some(_), Some(_)) => {
3546            return Err(ServiceError::InvalidParams(
3547                "sessions.inbox takes from_name or address, not both".into(),
3548            ))
3549        }
3550    };
3551    let operation = |error: std::io::Error| ServiceError::Operation(error.to_string());
3552    let mailbox =
3553        crate::mailbox::Mailbox::open(&crate::mailbox::mail_root(), &address).map_err(operation)?;
3554    let claimed = mailbox.claim_unread().map_err(operation)?;
3555    let mut messages: Vec<Value> = Vec::new();
3556    if params.all {
3557        for stored in mailbox.list().map_err(operation)? {
3558            if stored.state == crate::mailbox::MailState::Read {
3559                messages.push(json!({"state": "read", "envelope": stored.envelope, "rendered": stored.envelope.render()}));
3560            }
3561        }
3562    }
3563    for stored in &claimed {
3564        messages.push(json!({"state": "unread", "envelope": stored.envelope, "rendered": stored.envelope.render()}));
3565    }
3566    for stored in &claimed {
3567        mailbox.acknowledge(stored).map_err(operation)?;
3568    }
3569    Ok(json!({"address": address.to_string(), "messages": messages}))
3570}
3571
3572/// Source identity of one follow subscription, plus the last lifecycle state
3573/// already reported on it. The follower itself stays purely persistence-facing.
3574// Only the adapter-api poll reads these; the subscription bookkeeping itself is
3575// shared by both builds.
3576#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3577struct FollowedSource {
3578    harness: String,
3579    session_id: String,
3580    reported: Option<String>,
3581}
3582
3583#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3584struct ActivitySubscription {
3585    locators: Vec<SessionLocator>,
3586    homes: crate::HarnessHomes,
3587    reported: BTreeMap<(String, String), crate::SessionActivity>,
3588}
3589
3590/// Add what makes an indexed row behaviorally equivalent to a discovered row: the attach
3591/// endpoint of a session supercode hosts, and the door a message reaches it by.
3592///
3593/// The durable index owns only persistence metadata. Both are projections: every message/attach
3594/// operation revalidates its authority, so publishing one here never trusts a stale browser-held
3595/// handle. The doors are read once per batch ([`crate::mail_route::LiveSessions`]).
3596fn live_descriptor_value(
3597    session: &SessionDescriptor,
3598    doors: &crate::mail_route::LiveSessions,
3599) -> std::result::Result<Value, ServiceError> {
3600    let mut value = serde_json::to_value(session)
3601        .map_err(|error| ServiceError::Operation(error.to_string()))?;
3602    if let Some(workspace) = &session.cwd {
3603        let source = LiveRuntimeSource {
3604            harness: session.locator.harness.as_str().to_string(),
3605            session_id: session.locator.session_id.clone(),
3606            workspace: workspace.clone(),
3607        };
3608        if let Some(endpoint) = discover_live_runtime(&source)
3609            .map_err(|error| ServiceError::Operation(error.to_string()))?
3610        {
3611            value["live_endpoint"] = json!(endpoint.as_str());
3612        }
3613    }
3614    // How a message reaches this session right now, chosen by the same
3615    // router every sender uses: `runtime` (supercode controls it), `native`,
3616    // `hook` or `stored`. Absent when no process is running it.
3617    if let Some(door) = doors.door(
3618        session.locator.harness.as_str(),
3619        &session.locator.session_id,
3620    ) {
3621        value["delivery"] = json!(door);
3622    }
3623    Ok(value)
3624}
3625
3626fn live_index_changes(
3627    changes: Vec<crate::session_index::SessionIndexChange>,
3628    homes: &HarnessHomes,
3629) -> std::result::Result<Vec<Value>, ServiceError> {
3630    use crate::session_index::SessionIndexChange;
3631    let doors = crate::mail_route::LiveSessions::read(homes);
3632    changes
3633        .into_iter()
3634        .map(|change| match change {
3635            SessionIndexChange::Added { descriptor } => Ok(json!({
3636                "kind": "added",
3637                "descriptor": live_descriptor_value(&descriptor, &doors)?,
3638            })),
3639            SessionIndexChange::Updated { descriptor } => Ok(json!({
3640                "kind": "updated",
3641                "descriptor": live_descriptor_value(&descriptor, &doors)?,
3642            })),
3643            SessionIndexChange::Removed { key } => Ok(json!({
3644                "kind": "removed",
3645                "key": key,
3646            })),
3647        })
3648        .collect()
3649}
3650
3651fn legacy_live_status(activity: &crate::SessionActivity) -> Option<&'static str> {
3652    use crate::{SessionPresence, SessionTurnState};
3653    match (activity.presence, activity.turn) {
3654        (SessionPresence::Persisted, _) => None,
3655        (SessionPresence::Running, SessionTurnState::Working) => Some("busy"),
3656        (SessionPresence::Running, SessionTurnState::Idle) => Some("idle"),
3657        // The normalized activity object can honestly report a live owner even
3658        // when the stock harness never published a turn status. Preserve the
3659        // older field's stricter contract instead of guessing `running`.
3660        (SessionPresence::Running, SessionTurnState::Unknown)
3661            if activity.evidence.native_state.is_none() =>
3662        {
3663            None
3664        }
3665        (SessionPresence::Running, _) | (SessionPresence::ShuttingDown, _) => Some("running"),
3666    }
3667}
3668
3669#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
3670#[serde(rename_all = "kebab-case")]
3671enum TransferFormat {
3672    ClaudeCode,
3673    Codex,
3674    #[serde(rename = "opencode", alias = "open-code")]
3675    OpenCode,
3676    Pi,
3677    Grok,
3678    Gemini,
3679    Goose,
3680    /// UNI-18: a Hermes target. Its artifact is the Codex rollout that
3681    /// `hermes sessions import --from codex` reads; `sessions.export` performs
3682    /// that import into the Hermes home.
3683    Hermes,
3684}
3685
3686impl TransferFormat {
3687    fn id(self) -> &'static str {
3688        match self {
3689            Self::ClaudeCode => HarnessId::CLAUDE_CODE,
3690            Self::Codex => HarnessId::CODEX,
3691            Self::OpenCode => HarnessId::OPENCODE,
3692            Self::Pi => HarnessId::PI,
3693            Self::Grok => HarnessId::GROK,
3694            Self::Gemini => HarnessId::GEMINI,
3695            Self::Goose => HarnessId::GOOSE,
3696            Self::Hermes => HarnessId::HERMES,
3697        }
3698    }
3699}
3700
3701impl From<TransferFormat> for SessionFormat {
3702    fn from(value: TransferFormat) -> Self {
3703        match value {
3704            TransferFormat::ClaudeCode => Self::ClaudeCode,
3705            TransferFormat::Codex => Self::Codex,
3706            TransferFormat::OpenCode => Self::OpenCode,
3707            TransferFormat::Pi => Self::Pi,
3708            TransferFormat::Grok => Self::Grok,
3709            TransferFormat::Gemini => Self::Gemini,
3710            TransferFormat::Goose => Self::Goose,
3711            // a Hermes artifact is the Codex rollout Hermes imports
3712            TransferFormat::Hermes => Self::Codex,
3713        }
3714    }
3715}
3716
3717#[derive(Deserialize)]
3718struct ImportSessionParams {
3719    source_harness: TransferFormat,
3720    content: String,
3721}
3722
3723#[derive(Deserialize)]
3724struct ExportSessionParams {
3725    locator: SessionLocator,
3726    target_harness: TransferFormat,
3727}
3728
3729#[derive(Deserialize)]
3730struct ReduceSessionParams {
3731    locator: SessionLocator,
3732    target_harness: TransferFormat,
3733    #[serde(default = "default_keep_last")]
3734    keep_last: usize,
3735}
3736
3737fn default_keep_last() -> usize {
3738    6
3739}
3740
3741#[derive(Deserialize)]
3742struct BranchSessionParams {
3743    locator: SessionLocator,
3744    #[serde(default)]
3745    target_harness: Option<TransferFormat>,
3746}
3747
3748#[derive(Deserialize)]
3749struct HandoffSessionParams {
3750    locator: SessionLocator,
3751    target_harness: TransferFormat,
3752    #[serde(default)]
3753    cwd: Option<PathBuf>,
3754}
3755
3756#[derive(Deserialize)]
3757struct MaterializeSessionParams {
3758    artifact: crate::native_materialize::MaterializeArtifact,
3759    cwd: PathBuf,
3760    /// Where the continuation is written; unset roots are the environment's own, as discovery reads them.
3761    #[serde(default)]
3762    homes: HarnessHomes,
3763}
3764
3765#[derive(Debug, Clone, Copy, Default, Deserialize)]
3766#[serde(rename_all = "snake_case")]
3767enum ResumePolicy {
3768    #[default]
3769    Default,
3770    Yolo,
3771}
3772
3773#[derive(Deserialize)]
3774struct ResumeInstructionsParams {
3775    locator: SessionLocator,
3776    #[serde(default)]
3777    cwd: Option<PathBuf>,
3778    #[serde(default)]
3779    policy: ResumePolicy,
3780}
3781
3782/// `harness.v1.workflow.load` parameters: which harness's board, and its home.
3783#[derive(Deserialize)]
3784struct WorkflowLoadParams {
3785    from: crate::workflow_doors::WorkflowHarness,
3786    home: PathBuf,
3787}
3788
3789/// ONT-4 `harness.v1.orchestration.load` parameters. `flavor` says which layout the
3790/// folder is read as; our own is the default.
3791#[derive(Deserialize)]
3792struct OrchestrationLoadParams {
3793    root: PathBuf,
3794    #[serde(default)]
3795    flavor: crate::orchestration_doors::HomeFlavor,
3796}
3797
3798/// ONT-4 `harness.v1.orchestration.save` parameters. `vault` is merged into the
3799/// home's own secrets; a caller that sends none keeps what is on disk.
3800#[derive(Deserialize)]
3801struct OrchestrationSaveParams {
3802    root: PathBuf,
3803    orchestration: crate::orchestration::Orchestration,
3804    #[serde(default)]
3805    vault: BTreeMap<String, String>,
3806}
3807
3808/// ONT-4 `harness.v1.orchestration.compile` parameters.
3809#[derive(Deserialize)]
3810struct OrchestrationCompileParams {
3811    from: crate::orchestration_doors::OrchestrationHarness,
3812    home: PathBuf,
3813}
3814
3815/// ONT-4 `harness.v1.orchestration.decompile` parameters. `source` is the home the
3816/// orchestration was compiled from: it is re-compiled to recover the io bookkeeping
3817/// that byte reuse and the live-store refusal (UNI-18) are decided from.
3818#[derive(Deserialize)]
3819struct OrchestrationDecompileParams {
3820    to: crate::orchestration_doors::OrchestrationHarness,
3821    orchestration: crate::orchestration::Orchestration,
3822    source: PathBuf,
3823    #[serde(default)]
3824    source_flavor: crate::orchestration_doors::SourceFlavor,
3825    dest: PathBuf,
3826    #[serde(default)]
3827    vault: BTreeMap<String, String>,
3828}
3829
3830/// `harness.v1.orchestration.import` parameters: another harness's home, and the
3831/// folder of ours it becomes.
3832#[derive(Deserialize)]
3833struct OrchestrationImportParams {
3834    from: crate::orchestration_doors::OrchestrationHarness,
3835    home: PathBuf,
3836    into: PathBuf,
3837}
3838
3839/// `harness.v1.orchestration.export` parameters: a folder of ours, and the home of
3840/// another harness it becomes.
3841#[derive(Deserialize)]
3842struct OrchestrationExportParams {
3843    to: crate::orchestration_doors::OrchestrationHarness,
3844    root: PathBuf,
3845    dest: PathBuf,
3846}
3847
3848/// `harness.v1.jobs.get` parameters.
3849#[derive(Deserialize)]
3850struct JobsGetParams {
3851    harness: String,
3852    id: String,
3853    #[serde(default)]
3854    homes: crate::HarnessHomes,
3855}
3856
3857/// ORCH-18: run one mutating job verb through the harness's own CLI.
3858///
3859/// The refusal ladder is deliberate: a harness with no scheduled-job concept
3860/// at all answers with the SAME sentence `jobs.list` gives it, and a harness
3861/// that has jobs but publishes no client-callable verb (Claude Code, whose
3862/// jobs are created by the model inside a session) answers with its own
3863/// reason. Neither is ever a silent no-op.
3864fn mutate_job(
3865    verb: crate::jobs_control::JobVerb,
3866    params: Value,
3867) -> std::result::Result<Value, ServiceError> {
3868    let mutation = decode::<crate::jobs_control::JobMutation>(params)?;
3869    refuse_harness_without_jobs(&mutation.harness, &format!("jobs.{}", verb.as_str()))?;
3870    let outcome = crate::jobs_control::mutate(verb, &mutation).map_err(job_control_error)?;
3871    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3872}
3873
3874/// ORCH-22: run one mutating skills verb through the harness's own door.
3875///
3876/// The refusal ladder mirrors `jobs.*`: a harness with no skills root at all
3877/// answers with the same sentence `skills.list` gives it, and a harness whose
3878/// door does not publish this verb (OpenClaw has no `skills remove` at the
3879/// pin) answers with its own reason. Neither is ever a silent no-op.
3880fn mutate_skill(
3881    verb: crate::skills_control::SkillVerb,
3882    params: Value,
3883) -> std::result::Result<Value, ServiceError> {
3884    let mutation = decode::<crate::skills_control::SkillMutation>(params)?;
3885    if !crate::skills_control::supports_skill_control(&mutation.harness) {
3886        return Err(ServiceError::UnsupportedAction(format!(
3887            "`{}` has no skills root Volter Harness reads; `skills.{}` is supported for: {}",
3888            mutation.harness,
3889            verb.as_str(),
3890            crate::skills_control::CONTROLLED_SKILL_HARNESSES.join(", ")
3891        )));
3892    }
3893    let outcome =
3894        crate::skills_control::mutate_skill(verb, &mutation).map_err(skill_control_error)?;
3895    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3896}
3897
3898/// The skills twin of [`job_control_error`], with the same mapping rule.
3899fn skill_control_error(error: crate::skills_control::SkillControlError) -> ServiceError {
3900    match error {
3901        crate::skills_control::SkillControlError::Unsupported(message) => {
3902            ServiceError::UnsupportedAction(message)
3903        }
3904        crate::skills_control::SkillControlError::Invalid(message) => {
3905            ServiceError::InvalidParams(message)
3906        }
3907        crate::skills_control::SkillControlError::Failed(message) => {
3908            ServiceError::Operation(message)
3909        }
3910    }
3911}
3912
3913/// ORCH-21: run one mutating profile verb through the harness's own CLI.
3914///
3915/// The refusal ladder mirrors `mutate_job`'s: a harness with no profile
3916/// concept at all answers with the SAME sentence `profiles.list` gives it, and
3917/// a harness that HAS profiles but publishes no client-callable verb (Codex's
3918/// file-authored `[profiles.<name>]` tables, supercode's compiled-in presets)
3919/// answers with its own reason. Neither is ever a silent no-op.
3920fn mutate_profile(
3921    verb: crate::profiles_control::ProfileVerb,
3922    params: Value,
3923) -> std::result::Result<Value, ServiceError> {
3924    let mutation = decode::<crate::profiles_control::ProfileMutation>(params)?;
3925    let outcome =
3926        crate::profiles_control::mutate(verb, &mutation).map_err(profile_control_error)?;
3927    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3928}
3929
3930/// The same mapping `job_control_error` applies, for the profile noun.
3931fn profile_control_error(error: crate::profiles_control::ProfileControlError) -> ServiceError {
3932    match error {
3933        crate::profiles_control::ProfileControlError::Unsupported(message) => {
3934            ServiceError::UnsupportedAction(message)
3935        }
3936        crate::profiles_control::ProfileControlError::Invalid(message) => {
3937            ServiceError::InvalidParams(message)
3938        }
3939        crate::profiles_control::ProfileControlError::Failed(message) => {
3940            ServiceError::Operation(message)
3941        }
3942    }
3943}
3944
3945/// Map a controlled-tier failure onto the service's error vocabulary. A verb
3946/// the harness lacks is `UnsupportedAction`; a harness verb that RAN and
3947/// failed carries its own stderr through as the operation error.
3948fn job_control_error(error: crate::jobs_control::JobControlError) -> ServiceError {
3949    match error {
3950        crate::jobs_control::JobControlError::Unsupported(message) => {
3951            ServiceError::UnsupportedAction(message)
3952        }
3953        crate::jobs_control::JobControlError::Invalid(message) => {
3954            ServiceError::InvalidParams(message)
3955        }
3956        crate::jobs_control::JobControlError::Failed(message) => ServiceError::Operation(message),
3957    }
3958}
3959
3960/// Map an ORCH-19 controlled-tier failure onto the service's error
3961/// vocabulary. A verb the harness has no door for is `UnsupportedAction`; a
3962/// door that RAN and failed carries the harness's own stderr / HTTP body
3963/// through as the operation error.
3964fn session_control_error(error: crate::SessionControlError) -> ServiceError {
3965    match error {
3966        crate::SessionControlError::Unsupported(message) => {
3967            ServiceError::UnsupportedAction(message)
3968        }
3969        crate::SessionControlError::Invalid(message) => ServiceError::InvalidParams(message),
3970        crate::SessionControlError::Failed(message) => ServiceError::Operation(message),
3971    }
3972}
3973
3974/// A harness without a scheduled-job concept refuses the verb rather than
3975/// answering with an empty list — an absent capability and an empty inventory
3976/// are different answers (the same rule `runtimes.capabilities` applies to
3977/// `steer`).
3978fn refuse_harness_without_jobs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3979    if crate::jobs::supports_jobs(harness) {
3980        return Ok(());
3981    }
3982    Err(ServiceError::UnsupportedAction(format!(
3983        "`{harness}` has no scheduled jobs; `{verb}` is supported for: {}",
3984        crate::jobs::JOB_HARNESSES.join(", ")
3985    )))
3986}
3987
3988/// `harness.v1.runs.get` parameters.
3989#[derive(Deserialize)]
3990struct RunsGetParams {
3991    harness: String,
3992    id: String,
3993    #[serde(default)]
3994    homes: crate::HarnessHomes,
3995}
3996
3997/// A harness with no run store refuses the verb rather than answering with an
3998/// empty history — the same rule `jobs.list` applies. Claude Code lands here
3999/// on purpose: its cron fires are ordinary turns inside the session that
4000/// created the job, so there is no fire record to list.
4001fn refuse_harness_without_runs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
4002    if crate::runs::supports_runs(harness) {
4003        return Ok(());
4004    }
4005    Err(ServiceError::UnsupportedAction(format!(
4006        "`{harness}` keeps no run store; `{verb}` is supported for: {}",
4007        crate::runs::RUN_HARNESSES.join(", ")
4008    )))
4009}
4010
4011#[derive(Serialize)]
4012struct SessionArtifact {
4013    source_harness: HarnessId,
4014    target_harness: &'static str,
4015    session_id: Option<String>,
4016    content: String,
4017    suggested_filename: String,
4018    files: Vec<SessionArtifactFile>,
4019    fidelity: Fidelity,
4020    residue: Vec<String>,
4021}
4022
4023#[derive(Serialize)]
4024struct SessionArtifactFile {
4025    path: String,
4026    content: String,
4027    role: ArtifactFileRole,
4028}
4029
4030#[derive(Serialize)]
4031#[serde(rename_all = "snake_case")]
4032enum ArtifactFileRole {
4033    Primary,
4034    Subagent,
4035    Bundle,
4036    SourceRecovery,
4037}
4038
4039#[derive(Serialize)]
4040struct StructuredLaunch {
4041    cwd: PathBuf,
4042    program: String,
4043    arguments: Vec<String>,
4044    env: BTreeMap<String, String>,
4045}
4046
4047struct HandoffInstructions {
4048    launch: StructuredLaunch,
4049    materialize: Option<StructuredLaunch>,
4050    requires_materialization: bool,
4051    note: String,
4052}
4053
4054#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
4055#[serde(rename_all = "snake_case")]
4056enum HarnessProbeLevel {
4057    #[default]
4058    Passive,
4059    Handshake,
4060}
4061
4062#[derive(Default, Deserialize)]
4063#[serde(default)]
4064struct HarnessInventoryParams {
4065    harness: Option<HarnessId>,
4066    harnesses: Vec<HarnessId>,
4067    workspace: Option<PathBuf>,
4068    probe: HarnessProbeLevel,
4069    include_sessions: bool,
4070    /// Omit subprocess-based `--version` calls when a latency-sensitive UI only needs readiness.
4071    skip_versions: bool,
4072}
4073
4074#[derive(Deserialize)]
4075struct HarnessAuthenticationParams {
4076    harness: HarnessId,
4077}
4078
4079#[derive(Deserialize)]
4080struct BeginHarnessAuthenticationParams {
4081    harness: HarnessId,
4082    #[serde(default = "local_browser_authentication_environment")]
4083    environment: crate::HarnessAuthenticationEnvironment,
4084    #[serde(default)]
4085    method: Option<crate::HarnessAuthenticationMethodId>,
4086    #[serde(default)]
4087    cwd: Option<PathBuf>,
4088}
4089
4090fn local_browser_authentication_environment() -> crate::HarnessAuthenticationEnvironment {
4091    crate::HarnessAuthenticationEnvironment::LocalBrowser
4092}
4093
4094#[derive(Serialize)]
4095struct HarnessInventoryReport {
4096    probe: HarnessProbeLevel,
4097    workspace: Option<PathBuf>,
4098    harnesses: Vec<LocalHarness>,
4099}
4100
4101#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4102#[serde(rename_all = "snake_case")]
4103enum HarnessAuthState {
4104    Ready,
4105    Configured,
4106    Required,
4107    Unknown,
4108}
4109
4110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4111#[serde(rename_all = "snake_case")]
4112enum HarnessRuntimeState {
4113    Ready,
4114    Degraded,
4115    Unavailable,
4116}
4117
4118#[derive(Serialize)]
4119struct HarnessSessionCounts {
4120    global: Option<usize>,
4121    workspace: Option<usize>,
4122}
4123
4124/// Receipt-backed evidence that a harness has a RUNNING instance right now,
4125/// distinct from being merely installed (UNI-7). Detection is passive and
4126/// default-on: a gateway liveness connect for daemon harnesses, a fresh
4127/// SQLite WAL stamp for store-writer harnesses (precedent: the opencode
4128/// follower's -wal/-shm freshness). Control stays behind per-connection
4129/// grants — this reports observations only.
4130/// ORCH-17: the gateway-health noun on an inventory row. Derived from the
4131/// UNI-7 running-instance probe (Hermes: `state.db-wal` freshness; OpenClaw:
4132/// a TCP connect to the gateway endpoint resolved from its OWN config) plus
4133/// the executable version — never by starting anything.
4134#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4135#[serde(rename_all = "snake_case")]
4136pub enum GatewayState {
4137    Up,
4138    Down,
4139    Unknown,
4140}
4141
4142/// ORCH-17: `gateway` on a `harness.v1.harnesses.list` row.
4143#[derive(Debug, Clone, Serialize)]
4144pub struct GatewayHealth {
4145    pub state: GatewayState,
4146    /// The endpoint supercode would connect to (OpenClaw: the gateway
4147    /// WebSocket resolved from `openclaw.json`; core harnesses: their
4148    /// declared connect address when one exists). `None` when the harness
4149    /// has no single endpoint (Hermes multiplexes platforms).
4150    #[serde(skip_serializing_if = "Option::is_none")]
4151    pub endpoint: Option<String>,
4152    #[serde(skip_serializing_if = "Option::is_none")]
4153    pub version: Option<String>,
4154    /// What the verdict rests on, or why it is `unknown`.
4155    pub evidence: String,
4156    pub checked_at_ms: u64,
4157}
4158
4159/// OpenClaw's gateway WebSocket endpoint, resolved from its own config the
4160/// way the registry's connect descriptor prescribes (`gateway.url`, else
4161/// `gateway.port`, else the documented default).
4162fn openclaw_gateway_endpoint(home: &Path) -> String {
4163    let config_path = home.join(".openclaw/openclaw.json");
4164    let gateway = std::fs::read_to_string(&config_path)
4165        .ok()
4166        .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
4167        .and_then(|config| config.get("gateway").cloned());
4168    if let Some(url) = gateway
4169        .as_ref()
4170        .and_then(|gateway| gateway.get("url"))
4171        .and_then(serde_json::Value::as_str)
4172    {
4173        return url.to_string();
4174    }
4175    let port = gateway
4176        .as_ref()
4177        .and_then(|gateway| gateway.get("port"))
4178        .and_then(serde_json::Value::as_u64)
4179        .unwrap_or(18789);
4180    format!("ws://127.0.0.1:{port}")
4181}
4182
4183/// Ask Hermes itself (`hermes gateway status`, read-only, ~1 s) whether its
4184/// gateway is up. The command is per-host launchd/systemd text without a JSON
4185/// form at 0.19–0.21; the verdict is read from the lines it prints:
4186/// "supervised by launchd (PID …)" / "is running" → up, "not running" /
4187/// "not installed" → down, anything else → no verdict. `SUPERCODE_HERMES_BIN`
4188/// overrides the executable so a fake can stand in under test.
4189fn hermes_gateway_status() -> Option<(GatewayState, String)> {
4190    let program = crate::harness_command::harness_program(HarnessId::HERMES).ok()?;
4191    let output = std::process::Command::new(&program)
4192        .args(["gateway", "status"])
4193        .stdin(std::process::Stdio::null())
4194        .output()
4195        .ok()?;
4196    let text = format!(
4197        "{}{}",
4198        String::from_utf8_lossy(&output.stdout),
4199        String::from_utf8_lossy(&output.stderr)
4200    );
4201    let verdict = text.lines().find_map(|line| {
4202        let l = line.trim();
4203        if l.contains("supervised by launchd (PID")
4204            || l.contains("supervised by systemd (PID")
4205            || l.contains("Gateway is running")
4206            || l.contains("process is running")
4207        {
4208            Some((GatewayState::Up, format!("`hermes gateway status`: {l}")))
4209        } else if l.contains("not running") || l.contains("not installed") {
4210            Some((GatewayState::Down, format!("`hermes gateway status`: {l}")))
4211        } else {
4212            None
4213        }
4214    });
4215    verdict
4216}
4217
4218fn gateway_health(
4219    id: &str,
4220    installed: bool,
4221    running: Option<&RunningInstance>,
4222    version: Option<&str>,
4223) -> GatewayHealth {
4224    let checked_at_ms = now_epoch_ms();
4225    let home = supercode_interchange::user_home()
4226        .map(std::path::PathBuf::into_os_string)
4227        .map(PathBuf::from);
4228    match id {
4229        HarnessId::HERMES | HarnessId::OPENCLAW => {
4230            let endpoint = (id == HarnessId::OPENCLAW)
4231                .then(|| home.as_deref().map(openclaw_gateway_endpoint))
4232                .flatten();
4233            let (state, evidence) = match running {
4234                Some(instance) => (GatewayState::Up, instance.evidence.clone()),
4235                None if !installed => (
4236                    GatewayState::Unknown,
4237                    format!("`{id}` is not installed; no gateway to probe"),
4238                ),
4239                None if id == HarnessId::HERMES => match hermes_gateway_status() {
4240                    // The harness's own door outranks the WAL heuristic: an idle
4241                    // gateway writes nothing for minutes yet is up.
4242                    Some((state, evidence)) => (state, evidence),
4243                    None => (
4244                        GatewayState::Down,
4245                        "no fresh state.db-wal activity under ~/.hermes and `hermes gateway status` gave no verdict".to_string(),
4246                    ),
4247                },
4248                None => (
4249                    GatewayState::Down,
4250                    format!(
4251                        "no TCP listener at {}",
4252                        endpoint.as_deref().unwrap_or("the gateway endpoint")
4253                    ),
4254                ),
4255            };
4256            GatewayHealth {
4257                state,
4258                endpoint,
4259                version: version.map(str::to_string),
4260                evidence,
4261                checked_at_ms,
4262            }
4263        }
4264        // ORC-7: the orchestrator's gateway IS its daemon, and the daemon's
4265        // own lease file is the record of it. A lease naming a live pid is
4266        // up; a lease whose process is gone is down and says so as a STALE
4267        // lease, never as "no lease"; no lease at all is down. Nothing is
4268        // started, and no port is guessed — the daemon multiplexes adapters
4269        // the way Hermes does, so it has no single endpoint either.
4270        HarnessId::ORCHESTRATOR => {
4271            let root = crate::HarnessHomes::default().orchestrator;
4272            let (state, evidence) = match crate::orchestrator::read_lease(&root) {
4273                Some(lease) if lease.is_live() => (
4274                    GatewayState::Up,
4275                    format!(
4276                        "`{}` names pid {} (started {}), which is live",
4277                        crate::orchestrator::lock_path(&root).display(),
4278                        lease.pid,
4279                        lease.started_at
4280                    ),
4281                ),
4282                Some(lease) => (
4283                    GatewayState::Down,
4284                    format!(
4285                        "stale lease `{}`: pid {} is gone",
4286                        crate::orchestrator::lock_path(&root).display(),
4287                        lease.pid
4288                    ),
4289                ),
4290                None => (
4291                    GatewayState::Down,
4292                    format!(
4293                        "no lease at `{}`; `supercode orchestrator start` writes one",
4294                        crate::orchestrator::lock_path(&root).display()
4295                    ),
4296                ),
4297            };
4298            GatewayHealth {
4299                state,
4300                endpoint: None,
4301                version: version.map(str::to_string),
4302                evidence,
4303                checked_at_ms,
4304            }
4305        }
4306        _ => GatewayHealth {
4307            state: GatewayState::Unknown,
4308            endpoint: None,
4309            version: version.map(str::to_string),
4310            evidence: format!("`{id}` runs per session, not as a gateway"),
4311            checked_at_ms,
4312        },
4313    }
4314}
4315
4316#[derive(Debug, Clone, Serialize)]
4317struct RunningInstance {
4318    /// How the instance was detected.
4319    method: RunningInstanceMethod,
4320    /// The evidence the verdict rests on (endpoint reached / WAL path+age).
4321    evidence: String,
4322    /// Epoch-ms instant the probe executed.
4323    checked_at_ms: u64,
4324}
4325
4326#[derive(Debug, Clone, Copy, Serialize)]
4327#[serde(rename_all = "snake_case")]
4328enum RunningInstanceMethod {
4329    /// A TCP connect to the harness's own configured gateway endpoint
4330    /// succeeded.
4331    GatewayConnect,
4332    /// The harness's session store has an active SQLite WAL (a live writer
4333    /// holds the store open and stamped it recently).
4334    StoreWalActivity,
4335}
4336
4337fn now_epoch_ms() -> u64 {
4338    std::time::SystemTime::now()
4339        .duration_since(std::time::UNIX_EPOCH)
4340        .map(|elapsed| elapsed.as_millis() as u64)
4341        .unwrap_or(0)
4342}
4343
4344/// OpenClaw: the gateway endpoint comes from the harness's OWN config
4345/// (`<home>/.openclaw/openclaw.json` — `gateway.url` or `gateway.port`,
4346/// default port 18789); a successful TCP connect is the running signal.
4347fn probe_openclaw_running(home: &Path) -> Option<RunningInstance> {
4348    let config_path = home.join(".openclaw/openclaw.json");
4349    let text = std::fs::read_to_string(&config_path).ok();
4350    let gateway = text
4351        .as_deref()
4352        .and_then(|raw| serde_json::from_str::<serde_json::Value>(raw).ok())
4353        .and_then(|config| config.get("gateway").cloned());
4354    let address = gateway
4355        .as_ref()
4356        .and_then(|gateway| gateway.get("url"))
4357        .and_then(serde_json::Value::as_str)
4358        .and_then(|url| {
4359            url.split("://").nth(1).map(|rest| {
4360                rest.trim_end_matches('/')
4361                    .split('/')
4362                    .next()
4363                    .unwrap_or(rest)
4364                    .to_string()
4365            })
4366        })
4367        .unwrap_or_else(|| {
4368            let port = gateway
4369                .as_ref()
4370                .and_then(|gateway| gateway.get("port"))
4371                .and_then(serde_json::Value::as_u64)
4372                .unwrap_or(18789);
4373            format!("127.0.0.1:{port}")
4374        });
4375    let reachable = std::net::TcpStream::connect_timeout(
4376        &address.parse().ok()?,
4377        std::time::Duration::from_millis(400),
4378    )
4379    .is_ok();
4380    reachable.then(|| RunningInstance {
4381        method: RunningInstanceMethod::GatewayConnect,
4382        evidence: format!(
4383            "gateway endpoint {address} accepted a TCP connect (from {})",
4384            config_path.display()
4385        ),
4386        checked_at_ms: now_epoch_ms(),
4387    })
4388}
4389
4390/// Hermes: `<home>/.hermes/state.db-wal` freshly modified means a live writer
4391/// holds the store open (SQLite WAL exists only while a connection is open;
4392/// a recent stamp distinguishes an active instance from a stale crash
4393/// leftover).
4394fn probe_hermes_running(home: &Path, max_wal_age_ms: u64) -> Option<RunningInstance> {
4395    let wal = home.join(".hermes/state.db-wal");
4396    let modified = std::fs::metadata(&wal).ok()?.modified().ok()?;
4397    let age_ms = std::time::SystemTime::now()
4398        .duration_since(modified)
4399        .map(|age| age.as_millis() as u64)
4400        .unwrap_or(u64::MAX);
4401    (age_ms <= max_wal_age_ms).then(|| RunningInstance {
4402        method: RunningInstanceMethod::StoreWalActivity,
4403        evidence: format!(
4404            "{} stamped {age_ms}ms ago (threshold {max_wal_age_ms}ms)",
4405            wal.display()
4406        ),
4407        checked_at_ms: now_epoch_ms(),
4408    })
4409}
4410
4411/// Default-on running-instance detection for the harnesses that have one.
4412fn probe_running_instance(id: &str) -> Option<RunningInstance> {
4413    let home = supercode_interchange::user_home()
4414        .map(std::path::PathBuf::into_os_string)
4415        .map(PathBuf::from)?;
4416    match id {
4417        HarnessId::OPENCLAW => probe_openclaw_running(&home),
4418        HarnessId::HERMES => probe_hermes_running(&home, 300_000),
4419        _ => None,
4420    }
4421}
4422
4423#[derive(Serialize)]
4424struct LocalHarness {
4425    id: HarnessId,
4426    display_name: String,
4427    supported: bool,
4428    installed: bool,
4429    executable: Option<String>,
4430    version: Option<String>,
4431    auth: HarnessAuthState,
4432    runtime: HarnessRuntimeState,
4433    protocol: String,
4434    capabilities: crate::RuntimeCapabilities,
4435    effective_capabilities: crate::RuntimeCapabilities,
4436    sessions: HarnessSessionCounts,
4437    /// Receipt-backed running-instance detection (None = not detected or the
4438    /// harness has no running-instance concept). Distinct from `installed`.
4439    #[serde(skip_serializing_if = "Option::is_none")]
4440    running: Option<RunningInstance>,
4441    /// ORCH-17: gateway health derived from `running` + the harness's own config.
4442    gateway: GatewayHealth,
4443    reason: Option<String>,
4444    repair: Option<String>,
4445}
4446
4447#[derive(Clone, Deserialize)]
4448struct RuntimeBackendParams {
4449    harness: HarnessId,
4450    #[serde(default)]
4451    protocol: Option<String>,
4452    #[serde(default)]
4453    launch: Option<RuntimeLaunch>,
4454    #[serde(default)]
4455    base_url: Option<String>,
4456    #[serde(default)]
4457    policy: RuntimePolicy,
4458}
4459
4460#[derive(Debug, Clone, Copy, Default, Deserialize)]
4461#[serde(rename_all = "snake_case")]
4462enum RuntimePolicy {
4463    #[default]
4464    Default,
4465    Yolo,
4466}
4467
4468#[derive(Deserialize)]
4469struct RuntimeStartParams {
4470    #[serde(flatten)]
4471    backend: RuntimeBackendParams,
4472    cwd: PathBuf,
4473    /// MCP servers to mount into the new session through the harness's own
4474    /// start door (ORC-6). Backends without such a door ignore them.
4475    #[serde(default)]
4476    mcp_servers: Vec<crate::McpServerLaunch>,
4477    /// The session's approval policy, where the harness's start door takes one (Codex).
4478    #[serde(default)]
4479    approval_policy: Option<String>,
4480}
4481
4482#[derive(Deserialize)]
4483struct RuntimeAttachParams {
4484    #[serde(flatten)]
4485    backend: RuntimeBackendParams,
4486    runtime_id: String,
4487    #[serde(default)]
4488    cwd: Option<PathBuf>,
4489    /// MCP servers to mount into the resumed session (the start door's own
4490    /// field, carried again because a session's tools die with its process).
4491    #[serde(default)]
4492    mcp_servers: Vec<crate::McpServerLaunch>,
4493    /// The session's approval policy, carried again on resume as on start (Codex).
4494    #[serde(default)]
4495    approval_policy: Option<String>,
4496}
4497
4498#[derive(Deserialize)]
4499struct RuntimeConnectionParams {
4500    connection: String,
4501}
4502
4503#[derive(Deserialize)]
4504struct RuntimeInputParams {
4505    connection: String,
4506    text: String,
4507    #[serde(default)]
4508    image_urls: Vec<String>,
4509}
4510
4511const MAX_RUNTIME_IMAGES: usize = 4;
4512const MAX_RUNTIME_IMAGE_URL_BYTES: usize = 12 * 1024 * 1024;
4513const MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL: usize = 32 * 1024 * 1024;
4514
4515fn validate_runtime_image_urls(image_urls: Vec<String>) -> Result<Vec<String>, ServiceError> {
4516    if image_urls.len() > MAX_RUNTIME_IMAGES {
4517        return Err(ServiceError::InvalidParams(format!(
4518            "a runtime prompt accepts at most {MAX_RUNTIME_IMAGES} images"
4519        )));
4520    }
4521    let mut total = 0usize;
4522    for url in &image_urls {
4523        if !(url.starts_with("data:image/")
4524            || url.starts_with("https://")
4525            || url.starts_with("http://"))
4526        {
4527            return Err(ServiceError::InvalidParams(
4528                "runtime images must be image data URLs or HTTP(S) URLs".into(),
4529            ));
4530        }
4531        if url.len() > MAX_RUNTIME_IMAGE_URL_BYTES {
4532            return Err(ServiceError::InvalidParams(format!(
4533                "one runtime image exceeds the {MAX_RUNTIME_IMAGE_URL_BYTES}-byte encoded limit"
4534            )));
4535        }
4536        total = total.saturating_add(url.len());
4537    }
4538    if total > MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL {
4539        return Err(ServiceError::InvalidParams(format!(
4540            "runtime images exceed the {MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL}-byte encoded total limit"
4541        )));
4542    }
4543    Ok(image_urls)
4544}
4545
4546#[derive(Deserialize)]
4547struct RuntimeRespondParams {
4548    connection: String,
4549    request_id: Value,
4550    response: Value,
4551}
4552
4553fn default_reduction_store_root() -> PathBuf {
4554    if let Some(root) = std::env::var_os("SUPERCODE_HOME") {
4555        return PathBuf::from(root).join("sessions");
4556    }
4557    if let Some(home) = supercode_interchange::user_home().map(std::path::PathBuf::into_os_string) {
4558        return PathBuf::from(home).join(".supercode").join("sessions");
4559    }
4560    PathBuf::from(".supercode").join("sessions")
4561}
4562
4563fn messages_jsonl(messages: &[crate::ChatMessage]) -> std::result::Result<String, ServiceError> {
4564    let mut output = String::new();
4565    for message in messages {
4566        output.push_str(
4567            &serde_json::to_string(message)
4568                .map_err(|error| ServiceError::Operation(error.to_string()))?,
4569        );
4570        output.push('\n');
4571    }
4572    Ok(output)
4573}
4574
4575fn parse_messages_jsonl(
4576    content: &str,
4577) -> std::result::Result<Vec<crate::ChatMessage>, ServiceError> {
4578    content
4579        .lines()
4580        .enumerate()
4581        .filter(|(_, line)| !line.trim().is_empty())
4582        .map(|(index, line)| {
4583            serde_json::from_str::<crate::ChatMessage>(line).map_err(|error| {
4584                ServiceError::Operation(format!(
4585                    "reduced transcript line {} is invalid: {error}",
4586                    index + 1
4587                ))
4588            })
4589        })
4590        .collect()
4591}
4592
4593fn reduced_bootstrap_prompt(
4594    source: &SessionLocator,
4595    target: TransferFormat,
4596    view_jsonl: &str,
4597    sidecar_path: &Path,
4598    reduction_log_path: &Path,
4599) -> String {
4600    format!(
4601        "Continue the work from this losslessly reduced {source_harness} session in {target_harness}.\n\
4602         \n\
4603         The bounded working transcript is below. Treat reduction markers as transparent placeholders, not missing work. If a detail behind a marker is needed, use ordinary file-reading/search tools against the full Volter Harness sidecar at `{sidecar}` and its reduction index at `{log}`. Do not guess hidden content. Both files were reloaded and verified before this continuation was issued.\n\
4604         \n\
4605         <supercode-reduced-session source-session=\"{source_id}\">\n\
4606         {view_jsonl}\
4607         </supercode-reduced-session>\n\
4608         \n\
4609         Resume from the latest unresolved user request and preserve the source session's decisions and constraints.",
4610        source_harness = source.harness.as_str(),
4611        target_harness = target.id(),
4612        sidecar = sidecar_path.display(),
4613        log = reduction_log_path.display(),
4614        source_id = source.session_id,
4615    )
4616}
4617
4618fn session_artifact(
4619    locator: &SessionLocator,
4620    session: &Session,
4621    target: TransferFormat,
4622) -> std::result::Result<SessionArtifact, ServiceError> {
4623    session_artifact_with_id(locator, session, target, None)
4624}
4625
4626fn session_artifact_with_id(
4627    locator: &SessionLocator,
4628    session: &Session,
4629    target: TransferFormat,
4630    target_session_id: Option<&str>,
4631) -> std::result::Result<SessionArtifact, ServiceError> {
4632    let format: SessionFormat = target.into();
4633    let diagonal = format.source() == session.meta.source;
4634    crate::residue_store::store_segments(session);
4635    let has_appended_turns = session
4636        .imported_message_count
4637        .is_some_and(|imported| imported < session.messages.len());
4638    let mut restoration = None;
4639    let content = if let Some(id) = target_session_id {
4640        if diagonal && format != SessionFormat::OpenCode {
4641            session
4642                .to_jsonl_spliced(format, Some(id))
4643                .map_err(operation)?
4644        } else {
4645            let mut rewritten = session.clone();
4646            rewritten.meta.session_id = Some(id.to_string());
4647            rewritten.to_jsonl(format).map_err(operation)?
4648        }
4649    } else if diagonal && session.raw_is_verbatim && !has_appended_turns {
4650        session.raw_verbatim()
4651    } else if diagonal {
4652        session.to_jsonl_spliced(format, None).map_err(operation)?
4653    } else {
4654        // A session that came from `format` before returns its source records verbatim for the
4655        // prefix the residue store holds (docs/plans/portable-residue.md).
4656        match session
4657            .restore_residue(format, crate::residue_store::lookup)
4658            .map_err(operation)?
4659        {
4660            Some((content, report)) => {
4661                restoration = Some(report);
4662                content
4663            }
4664            None => session.to_jsonl(format).map_err(operation)?,
4665        }
4666    };
4667    let stem = sanitize_filename(
4668        target_session_id
4669            .or(session.meta.session_id.as_deref())
4670            .unwrap_or(&locator.session_id),
4671    );
4672    let suggested_filename = if diagonal && target == TransferFormat::Grok {
4673        "chat_history.jsonl".to_string()
4674    } else if target == TransferFormat::Goose {
4675        format!("{stem}.goose.json")
4676    } else {
4677        format!("{stem}.{}.jsonl", target.id())
4678    };
4679    let mut files = vec![SessionArtifactFile {
4680        path: suggested_filename.clone(),
4681        content: content.clone(),
4682        role: ArtifactFileRole::Primary,
4683    }];
4684    if target == TransferFormat::ClaudeCode {
4685        let bundle_stem = Path::new(&suggested_filename)
4686            .file_stem()
4687            .and_then(|stem| stem.to_str())
4688            .unwrap_or(&stem);
4689        let mut child_paths = BTreeSet::new();
4690        for (index, subagent) in session.subagents.iter().enumerate() {
4691            let agent_id = subagent
4692                .meta
4693                .agent_id
4694                .as_deref()
4695                .map(|id| id.strip_prefix("agent-").unwrap_or(id))
4696                .map(sanitize_filename)
4697                .filter(|id| !id.is_empty())
4698                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4699            let child_has_appended_turns = subagent
4700                .imported_message_count
4701                .is_some_and(|imported| imported < subagent.messages.len());
4702            let child_content = if target_session_id.is_none()
4703                && subagent.meta.source == SessionSource::ClaudeCode
4704                && subagent.raw_is_verbatim
4705                && !child_has_appended_turns
4706            {
4707                subagent.raw_verbatim()
4708            } else if subagent.meta.source == SessionSource::ClaudeCode {
4709                subagent
4710                    .to_jsonl_spliced(SessionFormat::ClaudeCode, target_session_id)
4711                    .map_err(operation)?
4712            } else {
4713                let mut child = subagent.clone();
4714                if let Some(id) = target_session_id {
4715                    child.meta.session_id = Some(id.to_string());
4716                }
4717                child
4718                    .to_jsonl(SessionFormat::ClaudeCode)
4719                    .map_err(operation)?
4720            };
4721            let path = format!("{bundle_stem}/subagents/agent-{agent_id}.jsonl");
4722            if !child_paths.insert(path.clone()) {
4723                return Err(ServiceError::Operation(format!(
4724                    "Claude subagent ids collide at artifact path `{path}`"
4725                )));
4726            }
4727            files.push(SessionArtifactFile {
4728                path,
4729                content: child_content,
4730                role: ArtifactFileRole::Subagent,
4731            });
4732        }
4733    }
4734    if diagonal && target == TransferFormat::Grok {
4735        append_grok_bundle_files(locator, "", ArtifactFileRole::Bundle, &mut files)?;
4736    }
4737    if !diagonal || !session.raw_is_verbatim {
4738        files.push(SessionArtifactFile {
4739            path: "recovery/source.supercode.jsonl".into(),
4740            content: session.to_native_jsonl(),
4741            role: ArtifactFileRole::SourceRecovery,
4742        });
4743        for (index, subagent) in session.subagents.iter().enumerate() {
4744            let id = subagent
4745                .meta
4746                .agent_id
4747                .as_deref()
4748                .map(sanitize_filename)
4749                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4750            files.push(SessionArtifactFile {
4751                path: format!("recovery/subagents/{id}.supercode.jsonl"),
4752                content: subagent.to_native_jsonl(),
4753                role: ArtifactFileRole::SourceRecovery,
4754            });
4755        }
4756    }
4757    if !diagonal && session.meta.source == SessionSource::Grok {
4758        append_grok_bundle_files(
4759            locator,
4760            "recovery/grok/",
4761            ArtifactFileRole::SourceRecovery,
4762            &mut files,
4763        )?;
4764    }
4765    let (fidelity, residue) = if diagonal
4766        && target_session_id.is_none()
4767        && session.raw_is_verbatim
4768        && !has_appended_turns
4769    {
4770        (Fidelity::ByteLossless, Vec::new())
4771    } else if diagonal && !(target_session_id.is_some() && target == TransferFormat::OpenCode) {
4772        (
4773            Fidelity::ValueLossless,
4774            vec![if target_session_id.is_some() {
4775                "target identity was rewritten, so the artifact intentionally differs from source bytes".into()
4776            } else {
4777                "source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured".into()
4778            }],
4779        )
4780    } else {
4781        match restoration {
4782            Some(report) if report.rendered_messages == 0 => (
4783                Fidelity::ByteLossless,
4784                vec![format!(
4785                    "restored verbatim from this conversation's {} source records in the residue store",
4786                    target.id()
4787                )],
4788            ),
4789            Some(report) => (
4790                Fidelity::Semantic,
4791                vec![format!(
4792                    "{} of {} messages restored verbatim from the residue store; the other {} written by the {} writer",
4793                    report.restored_messages,
4794                    report.restored_messages + report.rendered_messages,
4795                    report.rendered_messages,
4796                    target.id()
4797                )],
4798            ),
4799            None => (
4800                Fidelity::Semantic,
4801                vec!["target schema has no portable slot for every source-native record and metadata field".into()],
4802            ),
4803        }
4804    };
4805    Ok(SessionArtifact {
4806        source_harness: locator.harness.clone(),
4807        target_harness: target.id(),
4808        session_id: target_session_id
4809            .map(str::to_string)
4810            .or_else(|| session.meta.session_id.clone()),
4811        content,
4812        suggested_filename,
4813        files,
4814        fidelity,
4815        residue,
4816    })
4817}
4818
4819fn append_grok_bundle_files(
4820    locator: &SessionLocator,
4821    prefix: &str,
4822    role: ArtifactFileRole,
4823    files: &mut Vec<SessionArtifactFile>,
4824) -> std::result::Result<(), ServiceError> {
4825    let primary = locator.storage.path();
4826    if primary.file_name().and_then(|name| name.to_str()) != Some("chat_history.jsonl") {
4827        return Err(ServiceError::Operation(format!(
4828            "Grok bundle locator must name chat_history.jsonl, got {}",
4829            primary.display()
4830        )));
4831    }
4832    let parent = primary.parent().ok_or_else(|| {
4833        ServiceError::Operation("Grok chat_history.jsonl has no session directory".into())
4834    })?;
4835    for name in ["summary.json", "updates.jsonl"] {
4836        let path = parent.join(name);
4837        let metadata = match std::fs::symlink_metadata(&path) {
4838            Ok(metadata) => metadata,
4839            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
4840            Err(error) => return Err(ServiceError::Operation(error.to_string())),
4841        };
4842        if metadata.file_type().is_symlink() || !metadata.is_file() {
4843            return Err(ServiceError::Operation(format!(
4844                "refusing non-regular Grok bundle member {}",
4845                path.display()
4846            )));
4847        }
4848        let content = std::fs::read_to_string(&path).map_err(|error| {
4849            ServiceError::Operation(format!(
4850                "Grok bundle member {} is not representable as UTF-8: {error}",
4851                path.display()
4852            ))
4853        })?;
4854        files.push(SessionArtifactFile {
4855            path: format!("{prefix}{name}"),
4856            content,
4857            role: match role {
4858                ArtifactFileRole::Bundle => ArtifactFileRole::Bundle,
4859                _ => ArtifactFileRole::SourceRecovery,
4860            },
4861        });
4862    }
4863    Ok(())
4864}
4865
4866fn handoff_artifact(
4867    locator: &SessionLocator,
4868    session: &Session,
4869    target: TransferFormat,
4870) -> std::result::Result<SessionArtifact, ServiceError> {
4871    let target_session_id = target_session_id(target);
4872    session_artifact_with_id(locator, session, target, Some(&target_session_id))
4873}
4874
4875fn target_session_id(target: TransferFormat) -> String {
4876    let uuid = generated_session_id();
4877    match target {
4878        TransferFormat::OpenCode => format!("ses_{}", uuid.replace('-', "")),
4879        TransferFormat::ClaudeCode
4880        | TransferFormat::Codex
4881        | TransferFormat::Pi
4882        | TransferFormat::Grok
4883        | TransferFormat::Gemini
4884        | TransferFormat::Goose
4885        | TransferFormat::Hermes => uuid,
4886    }
4887}
4888
4889fn sanitize_filename(value: &str) -> String {
4890    let value = value
4891        .chars()
4892        .map(|character| {
4893            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
4894                character
4895            } else {
4896                '-'
4897            }
4898        })
4899        .collect::<String>();
4900    let value = value.trim_matches('-');
4901    if value.is_empty() {
4902        "session".into()
4903    } else {
4904        value.chars().take(100).collect()
4905    }
4906}
4907
4908fn handoff_instructions(
4909    target: TransferFormat,
4910    session_id: &str,
4911    cwd: &Path,
4912) -> HandoffInstructions {
4913    let launch = |program: &str, arguments: Vec<String>| StructuredLaunch {
4914        cwd: cwd.to_path_buf(),
4915        program: program.into(),
4916        arguments,
4917        env: BTreeMap::new(),
4918    };
4919    match target {
4920        TransferFormat::ClaudeCode => HandoffInstructions {
4921            launch: launch("claude", vec!["--resume".into(), session_id.into()]),
4922            materialize: None,
4923            requires_materialization: true,
4924            note: "Write the artifact into Claude Code's native project session store before running the resume launch; Claude Code has no general transcript-import command.".into(),
4925        },
4926        TransferFormat::Hermes => HandoffInstructions {
4927            launch: launch("hermes", vec!["--resume".into(), session_id.into()]),
4928            materialize: None,
4929            requires_materialization: true,
4930            note: "Hand the artifact (a Codex rollout) to `hermes sessions import --from codex <file>` — `sessions.export --to hermes` does exactly that — and resume the id Hermes prints: Hermes mints its own id and writes its own store.".into(),
4931        },
4932        TransferFormat::Codex => HandoffInstructions {
4933            launch: launch("codex", vec!["resume".into(), session_id.into()]),
4934            materialize: None,
4935            requires_materialization: true,
4936            note: "Write the artifact into Codex's native rollout store before running the resume launch; Codex has no general transcript-import command.".into(),
4937        },
4938        TransferFormat::OpenCode => HandoffInstructions {
4939            launch: launch("opencode", vec!["--session".into(), session_id.into()]),
4940            materialize: Some(launch(
4941                "opencode",
4942                vec!["import".into(), "{artifact_path}".into()],
4943            )),
4944            requires_materialization: true,
4945            note: "Write the artifact to a file, run the materialize command with its path, then launch the imported session.".into(),
4946        },
4947        TransferFormat::Pi => HandoffInstructions {
4948            launch: launch("pi", vec!["--session".into(), "{artifact_path}".into()]),
4949            materialize: None,
4950            requires_materialization: true,
4951            note: "Write the artifact to a file and replace {artifact_path} in the launch arguments; Pi can resume that file directly.".into(),
4952        },
4953        TransferFormat::Grok => HandoffInstructions {
4954            launch: launch(
4955                "grok",
4956                vec!["--resume".into(), "{materialized_session_id}".into()],
4957            ),
4958            materialize: None,
4959            requires_materialization: true,
4960            note: "Grok has no import command. Materialize the artifact through `harness.v1.sessions.materialize` (target `grok`, `value_lossless`, the destination cwd): it writes Grok's store entry (`chat_history.jsonl` and the `summary.json` `--resume` requires) under a fresh id; replace {materialized_session_id} with the id it returns.".into(),
4961        },
4962        TransferFormat::Gemini => HandoffInstructions {
4963            launch: launch(
4964                "gemini",
4965                vec!["--session-file".into(), "{artifact_path}".into()],
4966            ),
4967            materialize: None,
4968            requires_materialization: true,
4969            note: "Write the Gemini JSONL artifact to a file and replace {artifact_path}; Gemini imports it into the current project's chat store before opening the continuation.".into(),
4970        },
4971        TransferFormat::Goose => HandoffInstructions {
4972            launch: launch(
4973                "goose",
4974                vec![
4975                    "session".into(),
4976                    "--resume".into(),
4977                    "--session-id".into(),
4978                    "{imported_session_id}".into(),
4979                ],
4980            ),
4981            materialize: Some(launch(
4982                "goose",
4983                vec!["session".into(), "import".into(), "{artifact_path}".into()],
4984            )),
4985            requires_materialization: true,
4986            note: "Write the Goose JSON artifact to a file, run the materialize command, read the imported session id from its output, replace {imported_session_id}, then resume that native Goose session.".into(),
4987        },
4988    }
4989}
4990
4991fn resume_launch(
4992    harness: &str,
4993    session_id: &str,
4994    cwd: &Path,
4995    policy: ResumePolicy,
4996) -> std::result::Result<StructuredLaunch, ServiceError> {
4997    let mut arguments = Vec::new();
4998    let program = match harness {
4999        HarnessId::GROK => {
5000            if matches!(policy, ResumePolicy::Yolo) {
5001                if crate::support::self_sandbox_supported() {
5002                    arguments.extend(["--sandbox".into(), "workspace".into()]);
5003                }
5004                arguments.push("--always-approve".into());
5005            }
5006            arguments.extend(["--resume".into(), session_id.into()]);
5007            "grok"
5008        }
5009        HarnessId::CODEX => {
5010            let cwd_key = serde_json::to_string(cwd.to_string_lossy().as_ref())
5011                .expect("a filesystem path always serializes as JSON text");
5012            arguments.extend([
5013                "-c".into(),
5014                "check_for_update_on_startup=false".into(),
5015                "-c".into(),
5016                format!("projects.{cwd_key}.trust_level=\"trusted\""),
5017            ]);
5018            if matches!(policy, ResumePolicy::Yolo) {
5019                arguments.extend([
5020                    "--dangerously-bypass-approvals-and-sandbox".into(),
5021                    "--dangerously-bypass-hook-trust".into(),
5022                ]);
5023            }
5024            arguments.extend(["resume".into(), session_id.into()]);
5025            "codex"
5026        }
5027        HarnessId::CLAUDE_CODE => {
5028            if matches!(policy, ResumePolicy::Yolo) {
5029                arguments.push("--dangerously-skip-permissions".into());
5030            }
5031            arguments.extend(["--resume".into(), session_id.into()]);
5032            "claude"
5033        }
5034        HarnessId::GEMINI => {
5035            if matches!(policy, ResumePolicy::Yolo) {
5036                arguments.push("--yolo".into());
5037            }
5038            arguments.extend(["--resume".into(), session_id.into()]);
5039            "gemini"
5040        }
5041        HarnessId::GOOSE => {
5042            arguments.extend([
5043                "session".into(),
5044                "--resume".into(),
5045                "--session-id".into(),
5046                session_id.into(),
5047            ]);
5048            "goose"
5049        }
5050        HarnessId::PI => {
5051            if matches!(policy, ResumePolicy::Yolo) {
5052                arguments.push("--approve".into());
5053            }
5054            arguments.extend(["--session".into(), session_id.into()]);
5055            "pi"
5056        }
5057        HarnessId::OPENCODE => {
5058            arguments.extend(["--session".into(), session_id.into()]);
5059            "opencode"
5060        }
5061        HarnessId::SUPERCODE => {
5062            if matches!(policy, ResumePolicy::Yolo) {
5063                arguments.push("--dangerous".into());
5064            }
5065            arguments.extend(["resume".into(), session_id.into()]);
5066            "supercode"
5067        }
5068        other => {
5069            return Err(ServiceError::InvalidParams(format!(
5070                "no structured resume launch is registered for harness `{other}`"
5071            )))
5072        }
5073    };
5074    Ok(StructuredLaunch {
5075        cwd: cwd.to_path_buf(),
5076        env: if program == "grok" {
5077            crate::support::grok_home_env()
5078        } else {
5079            BTreeMap::new()
5080        },
5081        program: program.into(),
5082        arguments,
5083    })
5084}
5085
5086/// Stage the resolved gateway credential in a private (0600) file so the
5087/// bridge can read it via `--token-file` — the delivery the real `openclaw
5088/// acp` accepts. One stable file per endpoint (keyed by an address digest,
5089/// no secret material in the name), overwritten on every connect so files
5090/// never accumulate and a rotated token never goes stale on disk.
5091fn openclaw_gateway_token_file(address: &str, secret: &str) -> std::io::Result<PathBuf> {
5092    let digest = blake3::hash(address.as_bytes()).to_hex();
5093    let path = std::env::temp_dir().join(format!(
5094        "supercode-openclaw-gateway-token-{}",
5095        &digest.as_str()[..16]
5096    ));
5097    #[cfg(unix)]
5098    {
5099        use std::io::Write;
5100        use std::os::unix::fs::OpenOptionsExt;
5101        let mut file = std::fs::OpenOptions::new()
5102            .write(true)
5103            .create(true)
5104            .truncate(true)
5105            .mode(0o600)
5106            .open(&path)?;
5107        file.write_all(secret.as_bytes())?;
5108    }
5109    #[cfg(not(unix))]
5110    std::fs::write(&path, secret)?;
5111    Ok(path)
5112}
5113
5114/// Open a connect-mode descriptor: resolve the endpoint address and
5115/// credential from the harness's own config file and build the backend that
5116/// joins the already-running endpoint. Fails closed with a specific
5117/// diagnostic when the config cannot be resolved or the declared protocol has
5118/// no connect-capable client yet.
5119fn open_connect_descriptor(
5120    descriptor: &crate::HarnessSupportDescriptor,
5121    home: &Path,
5122) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
5123    let Some(connect) = &descriptor.runtime.connect_launch else {
5124        return Err(ServiceError::InvalidParams(format!(
5125            "harness `{}` has no registered connect-mode launch",
5126            descriptor.id.as_str()
5127        )));
5128    };
5129    let resolved = connect
5130        .resolve(home)
5131        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
5132    match (descriptor.id.as_str(), connect.protocol.as_str()) {
5133        (HarnessId::OPENCODE, protocol) if protocol.starts_with("opencode-http") => {
5134            let mut backend = OpenCodeRuntimeBackend::connect(&resolved.address);
5135            if let Some(token) = resolved.auth {
5136                backend = backend.with_bearer(token);
5137            }
5138            Ok(Box::new(backend))
5139        }
5140        (HarnessId::OPENCLAW, protocol) if protocol.starts_with("acp") => {
5141            // OpenClaw's own `openclaw acp` binary is the gateway client: a
5142            // stdio ACP bridge that joins the RUNNING gateway at the resolved
5143            // endpoint. Blind-walk finding 2026-08-31: the real bridge does
5144            // NOT honor OPENCLAW_GATEWAY_TOKEN from the environment — the
5145            // credential must arrive via `--token-file` (never bare `--token`
5146            // on argv, where process listings could read it). The env var is
5147            // still set for older bridges that did read it. Requires openclaw
5148            // >= 2026.7: the 2026.2 bridge drops its gateway socket
5149            // mid-prompt and advertises no session resume (executed finding,
5150            // docs/interop/research/openclaw-acp-dialect-2026-08-30.json).
5151            let mut env = BTreeMap::new();
5152            let mut arguments = vec!["acp".into(), "--url".into(), resolved.address.clone()];
5153            if let Some(token) = resolved.auth {
5154                let token_path = openclaw_gateway_token_file(&resolved.address, token.secret())
5155                    .map_err(|error| {
5156                        ServiceError::UnsupportedAction(format!(
5157                            "could not stage the gateway credential for the bridge: {error}"
5158                        ))
5159                    })?;
5160                arguments.push("--token-file".into());
5161                arguments.push(token_path.to_string_lossy().into_owned());
5162                env.insert("OPENCLAW_GATEWAY_TOKEN".to_string(), token.secret().to_string());
5163            }
5164            // The bridge program comes from the descriptor's own default
5165            // launch (the compiled registry pins `openclaw`), so tests can
5166            // substitute an absolute mock-bridge path without touching
5167            // process-global state.
5168            let program = descriptor
5169                .runtime
5170                .default_launch
5171                .as_ref()
5172                .map(|launch| launch.program.clone())
5173                .unwrap_or_else(|| "openclaw".into());
5174            let launch = RuntimeLaunch {
5175                program,
5176                arguments,
5177                env,
5178            };
5179            Ok(Box::new(
5180                crate::AcpRuntimeBackend::new(descriptor.id.clone(), launch)
5181                    .with_resume_support(descriptor.runtime.capabilities.resume_session),
5182            ))
5183        }
5184        _ => Err(ServiceError::UnsupportedAction(format!(
5185            "connect-mode endpoint for `{}` speaks `{}`; joining it needs that protocol's gateway client",
5186            descriptor.id.as_str(),
5187            connect.protocol
5188        ))),
5189    }
5190}
5191
5192/// The registry's connect-mode launch for this harness, honored only when the
5193/// caller supplied neither an explicit launch nor a base URL.
5194fn registry_connect_descriptor(
5195    params: &RuntimeBackendParams,
5196) -> Option<crate::HarnessSupportDescriptor> {
5197    if params.launch.is_some() || params.base_url.is_some() {
5198        return None;
5199    }
5200    harness_support_registry()
5201        .harnesses
5202        .into_iter()
5203        .find(|descriptor| descriptor.id == params.harness)
5204        .filter(|descriptor| descriptor.runtime.connect_launch.is_some())
5205}
5206
5207fn service_home() -> std::result::Result<PathBuf, ServiceError> {
5208    supercode_interchange::user_home()
5209        .map(std::path::PathBuf::into_os_string)
5210        .map(PathBuf::from)
5211        .ok_or_else(|| {
5212            ServiceError::UnsupportedAction(
5213                "connect-mode launches need HOME to locate the harness config".into(),
5214            )
5215        })
5216}
5217
5218/// The doors that open a runtime: each spawns or joins a program and waits on
5219/// that program's protocol handshake before it can answer.
5220pub const RUNTIME_OPEN_METHODS: &[&str] = &[
5221    "harness.v1.runtimes.start",
5222    "harness.v1.runtimes.resume",
5223    "harness.v1.runtimes.attach",
5224    "harness.v1.runtimes.attach_existing",
5225];
5226
5227/// How long a runtime gets to finish opening before its caller is answered an
5228/// error instead. A program that never speaks the protocol at all — the wrong
5229/// binary, a shim that prints usage and waits — never answers the handshake,
5230/// so the wait is unbounded without this.
5231pub const RUNTIME_OPEN_DEADLINE: Duration = Duration::from_secs(60);
5232
5233/// How long a control call on an ALREADY-open runtime — send input, interrupt,
5234/// steer, respond, close — gets before its caller is answered an error
5235/// instead. A live runtime answers these in milliseconds; a wedged one never
5236/// answers at all, and `close` is exactly what a caller reaches for when it
5237/// suspects that.
5238pub const RUNTIME_CONTROL_DEADLINE: Duration = Duration::from_secs(30);
5239
5240/// The doors whose work happens entirely OUTSIDE this service's state once
5241/// its state has been read: probing harnesses, relaying a message into a
5242/// live session, and performing a conversation verb through a harness's own
5243/// CLI / HTTP / store door. Every one of them waits on a child process or a
5244/// network peer. See [`HarnessSessionService::detach`].
5245pub const DETACHED_METHODS: &[&str] = &[
5246    "harness.v1.harnesses.list",
5247    "harness.v1.harnesses.probe",
5248    "harness.v1.sessions.message",
5249    "harness.v1.sessions.new",
5250    "harness.v1.sessions.reset",
5251    "harness.v1.sessions.archive",
5252    "harness.v1.sessions.delete",
5253];
5254
5255/// How long a request moved off a transport's loop gets before its caller is
5256/// answered an error instead. Each of these already bounds its own inner
5257/// waits (a probe's handshake, the relay's send); this is the backstop for
5258/// the ones that do not — a harness CLI that never exits — so no caller waits
5259/// forever on a detached task no one is watching.
5260pub const DETACHED_CALL_DEADLINE: Duration = Duration::from_secs(120);
5261
5262/// How long `sessions.discover` gets before its caller is answered an error
5263/// instead. Discovery reads each harness's own store, and a store on a cold
5264/// or unavailable mount answers at the filesystem's pace rather than its own.
5265///
5266/// Deliberately shorter than the clients' own request deadline (30s): the
5267/// server's answer names the store that did not answer, and it is only read
5268/// if it lands before the client stops listening.
5269pub const SESSION_DISCOVER_DEADLINE: Duration = Duration::from_secs(25);
5270
5271/// Bound one control call on an open runtime by [`RUNTIME_CONTROL_DEADLINE`],
5272/// naming the method and the bound when it blows.
5273async fn within_control_deadline<F: std::future::Future>(
5274    method: &str,
5275    call: F,
5276) -> std::result::Result<F::Output, ServiceError> {
5277    tokio::time::timeout(RUNTIME_CONTROL_DEADLINE, call)
5278        .await
5279        .map_err(|_| {
5280            ServiceError::Operation(format!(
5281                "`{method}` gave up after {}s: the runtime did not answer",
5282                RUNTIME_CONTROL_DEADLINE.as_secs()
5283            ))
5284        })
5285}
5286
5287/// One [`RUNTIME_OPEN_METHODS`] request, parsed but not yet started. See
5288/// [`HarnessSessionService::runtime_open`] for why it exists apart from
5289/// [`HarnessSessionService::handle_async`].
5290pub struct RuntimeOpen {
5291    id: Value,
5292    method: String,
5293    params: Value,
5294}
5295
5296impl RuntimeOpen {
5297    /// Do the waiting: spawn or join the program and complete its handshake,
5298    /// bounded by [`RUNTIME_OPEN_DEADLINE`]. Touches no service state, so this
5299    /// runs on any task.
5300    pub async fn open(self) -> OpenedRuntime {
5301        let Self { id, method, params } = self;
5302        let outcome = open_runtime(&method, params).await;
5303        OpenedRuntime { id, outcome }
5304    }
5305}
5306
5307/// The result of [`RuntimeOpen::open`], ready for
5308/// [`HarnessSessionService::finish_runtime_open`].
5309pub struct OpenedRuntime {
5310    id: Value,
5311    outcome: std::result::Result<OpenRuntime, ServiceError>,
5312}
5313
5314/// One detached request: the half that reads this service's state already
5315/// done, and the half that waits not yet started. See
5316/// [`HarnessSessionService::detach`] and
5317/// [`HarnessSessionService::detach_runtime`].
5318pub struct DetachedCall {
5319    id: Value,
5320    method: String,
5321    work: std::result::Result<Work, ServiceError>,
5322}
5323
5324impl DetachedCall {
5325    /// Do the waiting and answer. Runs on any task: whatever this call needed
5326    /// from the service was taken before it left.
5327    pub async fn run(self) -> DetachedAnswer {
5328        let Self { id, method, work } = self;
5329        match work {
5330            // A call holding a runtime is already bounded by
5331            // RUNTIME_CONTROL_DEADLINE, and its future OWNS that connection:
5332            // a second timeout around it would drop the connection mid-call
5333            // and take down a runtime its caller still has.
5334            Ok(Work::Runtime(work)) => {
5335                let (result, returned) = work.run().await;
5336                DetachedAnswer {
5337                    response: service_response(id, result),
5338                    returned,
5339                }
5340            }
5341            Ok(Work::Free(work)) => {
5342                let result = match tokio::time::timeout(DETACHED_CALL_DEADLINE, work.run()).await {
5343                    Ok(result) => result,
5344                    Err(_) => Err(ServiceError::Operation(format!(
5345                        "`{method}` gave up after {}s: the harness it waits on did not answer",
5346                        DETACHED_CALL_DEADLINE.as_secs()
5347                    ))),
5348                };
5349                DetachedAnswer {
5350                    response: service_response(id, result),
5351                    returned: None,
5352                }
5353            }
5354            Err(error) => DetachedAnswer {
5355                response: service_response(id, Err(error)),
5356                returned: None,
5357            },
5358        }
5359    }
5360}
5361
5362/// One detached call's complete answer, plus whatever it must hand back to
5363/// the service before that answer is written. See
5364/// [`HarnessSessionService::finish_detached`].
5365pub struct DetachedAnswer {
5366    response: Value,
5367    returned: Option<ReturnedRuntime>,
5368}
5369
5370impl DetachedAnswer {
5371    /// The caller's JSON-RPC response, for a transport that owns no service
5372    /// to give a borrowed connection back to.
5373    pub fn into_response(self) -> Value {
5374        self.response
5375    }
5376}
5377
5378/// A connection lent to a detached call, on its way back to the service that
5379/// owns it.
5380pub struct ReturnedRuntime {
5381    connection: String,
5382    runtime: Box<dyn RuntimeConnection>,
5383}
5384
5385/// The waiting half of one detached request: with nothing of the service's
5386/// in hand, or holding a connection the service lent out for the call.
5387enum Work {
5388    Free(DetachedWork),
5389    Runtime(RuntimeWork),
5390}
5391
5392/// The waiting half of one detached request that holds nothing of the
5393/// service's.
5394enum DetachedWork {
5395    /// Probe the selected harnesses: find their executables, ask each its
5396    /// version, and at `probe: handshake` start each one and complete its
5397    /// protocol handshake.
5398    Inventory(InventoryWork),
5399    /// Relay one message into a live session.
5400    Message(MessageSessionParams),
5401    /// Perform one conversation verb through the harness's own CLI, HTTP API,
5402    /// daemon socket, or supercode's own store.
5403    SessionMutation {
5404        verb: crate::SessionVerb,
5405        mutation: crate::SessionMutation,
5406    },
5407}
5408
5409impl DetachedWork {
5410    async fn run(self) -> std::result::Result<Value, ServiceError> {
5411        match self {
5412            Self::Inventory(work) => run_inventory(work).await,
5413            Self::Message(params) => Ok(message_live_session(&params).await),
5414            Self::SessionMutation { verb, mutation } => {
5415                let outcome = run_session_mutation(verb, &mutation).await?;
5416                serde_json::to_value(outcome)
5417                    .map_err(|error| ServiceError::Operation(error.to_string()))
5418            }
5419        }
5420    }
5421}
5422
5423/// One detached call that holds a runtime connection for its whole run.
5424enum RuntimeWork {
5425    /// Tear down a runtime the service has already surrendered.
5426    Close {
5427        runtime: Box<dyn RuntimeConnection>,
5428        process_group: Option<u32>,
5429    },
5430    /// Type one live slash command through a borrowed connection, then give
5431    /// the connection back.
5432    LiveCommand {
5433        connection: String,
5434        runtime: Box<dyn RuntimeConnection>,
5435        verb: crate::SessionVerb,
5436        mutation: crate::SessionMutation,
5437        command: &'static str,
5438        session: String,
5439    },
5440}
5441
5442/// What one [`RuntimeWork`] answers with: the caller's result, and the
5443/// connection to give back when the call only borrowed one.
5444type RuntimeWorkAnswer = (
5445    std::result::Result<Value, ServiceError>,
5446    Option<ReturnedRuntime>,
5447);
5448
5449impl RuntimeWork {
5450    async fn run(self) -> RuntimeWorkAnswer {
5451        match self {
5452            Self::Close {
5453                runtime,
5454                process_group,
5455            } => (close_runtime(runtime, process_group).await, None),
5456            Self::LiveCommand {
5457                connection,
5458                mut runtime,
5459                verb,
5460                mutation,
5461                command,
5462                session,
5463            } => {
5464                let result =
5465                    type_live_command(runtime.as_mut(), verb, &mutation, command, session).await;
5466                (
5467                    result,
5468                    Some(ReturnedRuntime {
5469                        connection,
5470                        runtime,
5471                    }),
5472                )
5473            }
5474        }
5475    }
5476}
5477
5478/// Tear down a runtime already out of the service, within
5479/// [`RUNTIME_CONTROL_DEADLINE`].
5480async fn close_runtime(
5481    mut runtime: Box<dyn RuntimeConnection>,
5482    process_group: Option<u32>,
5483) -> std::result::Result<Value, ServiceError> {
5484    match within_control_deadline("harness.v1.runtimes.close", runtime.close()).await {
5485        Ok(result) => {
5486            result.map_err(operation)?;
5487            Ok(json!({"closed": true}))
5488        }
5489        Err(deadline) => {
5490            // Dropping the handle is not enough: the process that stopped
5491            // answering is held by a task parked on it, so nothing here runs
5492            // its Drop. Signal the group the graceful path would have
5493            // signalled, then say so.
5494            let killed = kill_runtime_process_group(process_group);
5495            drop(runtime);
5496            Ok(json!({
5497                "closed": true,
5498                "killed": killed,
5499                "detail": error_message(deadline),
5500            }))
5501        }
5502    }
5503}
5504
5505/// The conversation a live `sessions.new` / `sessions.reset` acts on: the one
5506/// the request named, or the runtime's own session.
5507fn live_session_name(runtime: &dyn RuntimeConnection, mutation: &crate::SessionMutation) -> String {
5508    mutation
5509        .session
5510        .clone()
5511        .filter(|value| !value.trim().is_empty())
5512        .unwrap_or_else(|| runtime.handle().runtime_id.clone())
5513}
5514
5515/// Type one harness slash command into a live session through the very same
5516/// `send_input` path a human's message takes, within
5517/// [`RUNTIME_CONTROL_DEADLINE`].
5518async fn type_live_command(
5519    runtime: &mut dyn RuntimeConnection,
5520    verb: crate::SessionVerb,
5521    mutation: &crate::SessionMutation,
5522    command: &str,
5523    session: String,
5524) -> std::result::Result<Value, ServiceError> {
5525    within_control_deadline(
5526        &format!("sessions.{}", verb.as_str()),
5527        runtime.send_input(RuntimeInput {
5528            text: command.to_string(),
5529            image_urls: Vec::new(),
5530        }),
5531    )
5532    .await?
5533    .map_err(operation)?;
5534    let outcome = crate::sessions_control::live_outcome(verb, mutation, command, session)
5535        .map_err(session_control_error)?;
5536    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
5537}
5538
5539/// A runtime that is up and whose handshake completed, with what the service
5540/// needs to take ownership of it.
5541enum OpenRuntime {
5542    /// supercode spawned this process, so it also hosts it: a frontend server,
5543    /// a live-runtime registration and a terminal launch of its own.
5544    Hosted {
5545        runtime: Box<dyn RuntimeConnection>,
5546        capabilities: crate::RuntimeCapabilities,
5547        workspace: PathBuf,
5548    },
5549    /// `attach_existing` joined a process supercode does not own. It is
5550    /// registered as a bare connection and hosts nothing.
5551    Joined { runtime: Box<dyn RuntimeConnection> },
5552}
5553
5554/// Open the runtime one [`RUNTIME_OPEN_METHODS`] request asks for, within
5555/// [`RUNTIME_OPEN_DEADLINE`]. The error a blown deadline answers names the
5556/// method and the bound, so a caller reads why it was cut loose instead of
5557/// waiting on a handshake that is never coming.
5558async fn open_runtime(
5559    method: &str,
5560    params: Value,
5561) -> std::result::Result<OpenRuntime, ServiceError> {
5562    match tokio::time::timeout(
5563        RUNTIME_OPEN_DEADLINE,
5564        open_runtime_unbounded(method, params),
5565    )
5566    .await
5567    {
5568        Ok(result) => result,
5569        Err(_) => Err(ServiceError::Operation(format!(
5570            "`{method}` gave up after {}s: the runtime never finished its protocol handshake",
5571            RUNTIME_OPEN_DEADLINE.as_secs()
5572        ))),
5573    }
5574}
5575
5576async fn open_runtime_unbounded(
5577    method: &str,
5578    params: Value,
5579) -> std::result::Result<OpenRuntime, ServiceError> {
5580    match method {
5581        "harness.v1.runtimes.start" => {
5582            let params = decode::<RuntimeStartParams>(params)?;
5583            let backend = runtime_backend(&params.backend)?;
5584            let capabilities = backend.capabilities();
5585            let workspace = params.cwd.clone();
5586            let runtime = backend
5587                .start(RuntimeStartRequest {
5588                    cwd: params.cwd,
5589                    launch: runtime_launch(&params.backend),
5590                    mcp_servers: params.mcp_servers,
5591                    approval_policy: params.approval_policy,
5592                })
5593                .await
5594                .map_err(operation)?;
5595            Ok(OpenRuntime::Hosted {
5596                runtime,
5597                capabilities,
5598                workspace,
5599            })
5600        }
5601        "harness.v1.runtimes.resume" | "harness.v1.runtimes.attach" => {
5602            let params = decode::<RuntimeAttachParams>(params)?;
5603            let backend = runtime_backend(&params.backend)?;
5604            let capabilities = backend.capabilities();
5605            let workspace = params
5606                .cwd
5607                .clone()
5608                .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
5609            let runtime = backend
5610                .attach(RuntimeAttachRequest {
5611                    runtime_id: params.runtime_id,
5612                    cwd: params.cwd,
5613                    launch: runtime_launch(&params.backend),
5614                    mcp_servers: params.mcp_servers,
5615                    approval_policy: params.approval_policy,
5616                })
5617                .await
5618                .map_err(operation)?;
5619            Ok(OpenRuntime::Hosted {
5620                runtime,
5621                capabilities,
5622                workspace,
5623            })
5624        }
5625        "harness.v1.runtimes.attach_existing" => {
5626            let params = decode::<RuntimeAttachParams>(params)?;
5627            let backend: Box<dyn RuntimeBackend> = match params
5628                .backend
5629                .base_url
5630                .as_deref()
5631                .and_then(|value| LiveRuntimeEndpoint::parse(value).ok())
5632            {
5633                Some(endpoint) => {
5634                    #[cfg(not(feature = "adapter-api"))]
5635                    {
5636                        let _ = endpoint;
5637                        return Err(ServiceError::UnsupportedAction(
5638                            "live HTTP attachment adapter is not compiled".into(),
5639                        ));
5640                    }
5641                    #[cfg(feature = "adapter-api")]
5642                    {
5643                        let workspace = params.cwd.clone().ok_or_else(|| {
5644                            ServiceError::InvalidParams(
5645                                "Volter Harness live attach requires the project cwd".into(),
5646                            )
5647                        })?;
5648                        let source = LiveRuntimeSource {
5649                            harness: params.backend.harness.as_str().to_string(),
5650                            session_id: params.runtime_id.clone(),
5651                            workspace,
5652                        };
5653                        let receipt = resolve_live_runtime(&endpoint, &source)
5654                            .map_err(|error| ServiceError::Operation(error.to_string()))?;
5655                        Box::new(SupercodeHttpRuntimeBackend::new(receipt))
5656                    }
5657                }
5658                None => runtime_backend(&params.backend)?,
5659            };
5660            let capabilities = backend.capabilities();
5661            if !capabilities.attach_existing_process {
5662                return Err(ServiceError::Operation(format!(
5663                    "{} cannot attach to an already-running process; use runtimes.resume for a persisted session",
5664                    backend.harness().as_str()
5665                )));
5666            }
5667            let runtime = backend
5668                .attach_existing(RuntimeAttachRequest {
5669                    runtime_id: params.runtime_id,
5670                    cwd: params.cwd,
5671                    launch: runtime_launch(&params.backend),
5672                    mcp_servers: params.mcp_servers,
5673                    approval_policy: params.approval_policy,
5674                })
5675                .await
5676                .map_err(operation)?;
5677            Ok(OpenRuntime::Joined { runtime })
5678        }
5679        _ => Err(ServiceError::MethodNotFound),
5680    }
5681}
5682
5683/// Wrap one service outcome in its JSON-RPC 2.0 envelope.
5684fn service_response(id: Value, result: std::result::Result<Value, ServiceError>) -> Value {
5685    match result {
5686        Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
5687        Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
5688        Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
5689        Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
5690        Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
5691        Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
5692    }
5693}
5694
5695fn runtime_backend(
5696    params: &RuntimeBackendParams,
5697) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
5698    if let Some(descriptor) = registry_connect_descriptor(params) {
5699        return open_connect_descriptor(&descriptor, &service_home()?);
5700    }
5701    if params.protocol.as_deref() == Some("acp") {
5702        let launch = params
5703            .launch
5704            .clone()
5705            .or_else(|| {
5706                harness_support_registry()
5707                    .harnesses
5708                    .into_iter()
5709                    .find(|harness| harness.id == params.harness)
5710                    .filter(|harness| {
5711                        harness.runtime.implementation == ImplementationKind::GenericProtocol
5712                            && harness.runtime.protocol.starts_with("acp")
5713                    })
5714                    .and_then(|harness| harness.runtime.default_launch)
5715            })
5716            .ok_or_else(|| {
5717                ServiceError::InvalidParams(
5718                    "an ACP runtime requires `launch` unless the harness has a registered default"
5719                        .into(),
5720                )
5721            })?;
5722        let resume_session = harness_support_registry()
5723            .harnesses
5724            .into_iter()
5725            .find(|harness| harness.id == params.harness)
5726            .is_some_and(|harness| harness.runtime.capabilities.resume_session);
5727        return Ok(Box::new(
5728            AcpRuntimeBackend::new(params.harness.clone(), launch)
5729                .with_resume_support(resume_session),
5730        ));
5731    }
5732    let backend: Box<dyn RuntimeBackend> = match params.harness.as_str() {
5733        HarnessId::CODEX => Box::new(CodexRuntimeBackend::new()),
5734        HarnessId::CLAUDE_CODE => Box::new(ClaudeCodeRuntimeBackend::new()),
5735        HarnessId::PI => Box::new(PiRuntimeBackend::new()),
5736        HarnessId::OPENCODE => match &params.base_url {
5737            Some(url) => Box::new(OpenCodeRuntimeBackend::connect(url)),
5738            None => Box::new(OpenCodeRuntimeBackend::new()),
5739        },
5740        harness => {
5741            let descriptor = harness_support_registry()
5742                .harnesses
5743                .into_iter()
5744                .find(|descriptor| descriptor.id.as_str() == harness)
5745                .filter(|descriptor| {
5746                    descriptor.runtime.implementation == ImplementationKind::GenericProtocol
5747                        && descriptor.runtime.protocol.starts_with("acp")
5748                });
5749            let Some(descriptor) = descriptor else {
5750                return Err(ServiceError::InvalidParams(format!(
5751                    "no runtime adapter for harness `{harness}`; use protocol `acp` with a launch command"
5752                )));
5753            };
5754            let resume = descriptor.runtime.capabilities.resume_session;
5755            Box::new(
5756                AcpRuntimeBackend::new(
5757                    descriptor.id,
5758                    descriptor
5759                        .runtime
5760                        .default_launch
5761                        .expect("generic ACP registry entry includes its launch"),
5762                )
5763                .with_resume_support(resume),
5764            )
5765        }
5766    };
5767    Ok(backend)
5768}
5769
5770fn runtime_launch(params: &RuntimeBackendParams) -> Option<RuntimeLaunch> {
5771    if let Some(launch) = &params.launch {
5772        return Some(launch.clone());
5773    }
5774    if !matches!(params.policy, RuntimePolicy::Yolo) {
5775        return None;
5776    }
5777    let launch = match params.harness.as_str() {
5778        HarnessId::GROK => RuntimeLaunch {
5779            program: "grok".into(),
5780            arguments: {
5781                let mut arguments: Vec<String> = Vec::new();
5782                if crate::support::self_sandbox_supported() {
5783                    arguments.extend(["--sandbox".into(), "workspace".into()]);
5784                }
5785                arguments.extend([
5786                    "--always-approve".into(),
5787                    "agent".into(),
5788                    "--no-leader".into(),
5789                    "stdio".into(),
5790                ]);
5791                arguments
5792            },
5793            env: crate::support::grok_env(),
5794        },
5795        HarnessId::CODEX => RuntimeLaunch {
5796            program: "codex".into(),
5797            arguments: vec![
5798                "--dangerously-bypass-approvals-and-sandbox".into(),
5799                "--dangerously-bypass-hook-trust".into(),
5800                "app-server".into(),
5801            ],
5802            env: BTreeMap::new(),
5803        },
5804        HarnessId::CLAUDE_CODE => RuntimeLaunch {
5805            program: "claude".into(),
5806            arguments: vec![
5807                "--dangerously-skip-permissions".into(),
5808                "--print".into(),
5809                "--input-format".into(),
5810                "stream-json".into(),
5811                "--output-format".into(),
5812                "stream-json".into(),
5813                "--verbose".into(),
5814            ],
5815            env: BTreeMap::new(),
5816        },
5817        HarnessId::PI => RuntimeLaunch {
5818            program: "pi".into(),
5819            arguments: vec!["--approve".into(), "--mode".into(), "rpc".into()],
5820            env: BTreeMap::new(),
5821        },
5822        HarnessId::OPENCODE => RuntimeLaunch {
5823            program: "opencode".into(),
5824            arguments: vec!["serve".into()],
5825            env: BTreeMap::new(),
5826        },
5827        HarnessId::GEMINI => RuntimeLaunch {
5828            program: "gemini".into(),
5829            arguments: vec!["--acp".into(), "--yolo".into()],
5830            env: BTreeMap::new(),
5831        },
5832        HarnessId::GOOSE => RuntimeLaunch {
5833            program: "goose".into(),
5834            arguments: vec!["acp".into()],
5835            env: BTreeMap::new(),
5836        },
5837        HarnessId::SUPERCODE => RuntimeLaunch {
5838            program: "supercode".into(),
5839            arguments: vec!["acp".into(), "--dangerous".into()],
5840            env: BTreeMap::new(),
5841        },
5842        _ => return None,
5843    };
5844    Some(launch)
5845}
5846
5847/// Disposable harness state for a no-prompt readiness probe. Merely opening
5848/// several stock CLIs writes a session header or migrates configuration, so a
5849/// handshake must never point at the user's real home. Authentication files
5850/// are copied into the private temporary home; all writes disappear with the
5851/// guard after the connection closes.
5852struct IsolatedProbeHome {
5853    launch: RuntimeLaunch,
5854    root: PathBuf,
5855}
5856
5857impl IsolatedProbeHome {
5858    fn new(harness: &str, mut launch: RuntimeLaunch) -> std::io::Result<Self> {
5859        let root = std::env::temp_dir().join(format!(
5860            "supercode-harness-probe-{harness}-{}",
5861            generated_session_id()
5862        ));
5863        std::fs::create_dir_all(&root)?;
5864        set_private_dir_permissions(&root)?;
5865
5866        if let Some(source_home) = supercode_interchange::user_home()
5867            .map(std::path::PathBuf::into_os_string)
5868            .map(PathBuf::from)
5869        {
5870            for relative in probe_auth_files(harness) {
5871                copy_probe_file(&source_home, &root, relative)?;
5872            }
5873        }
5874        // supercode reads its own config home ($SUPERCODE_HOME, else
5875        // $XDG_CONFIG_HOME/supercode, else ~/.config/supercode), not a fixed
5876        // place under HOME: a login kept under XDG_CONFIG_HOME probed as
5877        // "no API key found" while `supercode run` answered.
5878        if harness == HarnessId::SUPERCODE {
5879            let config_home = crate::agent::global_instructions_dir();
5880            for file in ["config.toml", "credentials.toml"] {
5881                copy_probe_path(
5882                    &config_home.join(file),
5883                    &root.join(".config/supercode").join(file),
5884                )?;
5885            }
5886        }
5887        configure_isolated_probe_auth(harness, &root)?;
5888
5889        let root_text = root.to_string_lossy().into_owned();
5890        for (key, value) in [
5891            ("HOME", root_text.clone()),
5892            (
5893                "XDG_CACHE_HOME",
5894                root.join(".cache").to_string_lossy().into_owned(),
5895            ),
5896            (
5897                "XDG_CONFIG_HOME",
5898                root.join(".config").to_string_lossy().into_owned(),
5899            ),
5900            (
5901                "XDG_DATA_HOME",
5902                root.join(".local/share").to_string_lossy().into_owned(),
5903            ),
5904        ] {
5905            launch.env.insert(key.into(), value);
5906        }
5907        let scoped = match harness {
5908            HarnessId::CLAUDE_CODE => Some(("CLAUDE_CONFIG_DIR", root.join(".claude"))),
5909            HarnessId::CODEX => Some(("CODEX_HOME", root.join(".codex"))),
5910            HarnessId::GEMINI => Some(("GEMINI_CLI_HOME", root.clone())),
5911            HarnessId::GROK => Some(("GROK_HOME", root.join(".grok"))),
5912            HarnessId::PI => Some(("PI_CODING_AGENT_DIR", root.join(".pi/agent"))),
5913            HarnessId::SUPERCODE => Some(("SUPERCODE_HOME", root.join(".config/supercode"))),
5914            _ => None,
5915        };
5916        if let Some((key, value)) = scoped {
5917            launch
5918                .env
5919                .insert(key.into(), value.to_string_lossy().into_owned());
5920        }
5921        Ok(Self { launch, root })
5922    }
5923
5924    fn cleanup(&self) -> std::io::Result<()> {
5925        match std::fs::remove_dir_all(&self.root) {
5926            Ok(()) => Ok(()),
5927            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
5928            Err(error) => Err(error),
5929        }
5930    }
5931}
5932
5933impl Drop for IsolatedProbeHome {
5934    fn drop(&mut self) {
5935        let _ = self.cleanup();
5936    }
5937}
5938
5939fn probe_auth_files(harness: &str) -> &'static [&'static str] {
5940    match harness {
5941        HarnessId::CLAUDE_CODE => &[".claude/.credentials.json", ".claude.json"],
5942        // The gateway endpoint + token live in openclaw's own config; without
5943        // it the isolated probe dials the default endpoint unauthenticated
5944        // (PARITY-24 finding 2026-08-31).
5945        HarnessId::OPENCLAW => &[".openclaw/openclaw.json"],
5946        HarnessId::CODEX => &[".codex/auth.json"],
5947        HarnessId::GEMINI => &[
5948            ".gemini/google_accounts.json",
5949            ".gemini/oauth_creds.json",
5950            ".gemini/settings.json",
5951        ],
5952        HarnessId::GROK => &[".grok/auth.json", ".grok/config.toml"],
5953        HarnessId::OPENCODE => &[
5954            ".config/opencode/auth.json",
5955            ".local/share/opencode/auth.json",
5956        ],
5957        HarnessId::PI => &[".pi/agent/auth.json"],
5958        // Hermes keeps its provider selection in config.yaml, its OAuth
5959        // credential pool in auth.json, and API keys in .env; without them
5960        // the isolated probe sees "No LLM provider configured" for a
5961        // hermes that answers fine from the user's real home.
5962        HarnessId::HERMES => &[".hermes/config.yaml", ".hermes/auth.json", ".hermes/.env"],
5963        _ => &[],
5964    }
5965}
5966
5967fn copy_probe_file(source_home: &Path, probe_home: &Path, relative: &str) -> std::io::Result<()> {
5968    copy_probe_path(&source_home.join(relative), &probe_home.join(relative))
5969}
5970
5971fn copy_probe_path(source: &Path, destination: &Path) -> std::io::Result<()> {
5972    if !source.is_file() {
5973        return Ok(());
5974    }
5975    if let Some(parent) = destination.parent() {
5976        std::fs::create_dir_all(parent)?;
5977        set_private_dir_permissions(parent)?;
5978    }
5979    std::fs::copy(source, destination)?;
5980    set_private_file_permissions(destination)
5981}
5982
5983fn configure_isolated_probe_auth(harness: &str, probe_home: &Path) -> std::io::Result<()> {
5984    if harness != HarnessId::GEMINI {
5985        return Ok(());
5986    }
5987    let oauth = probe_home.join(".gemini/oauth_creds.json");
5988    if !oauth.is_file() {
5989        return Ok(());
5990    }
5991    let settings_path = probe_home.join(".gemini/settings.json");
5992    let mut settings = std::fs::read_to_string(&settings_path)
5993        .ok()
5994        .and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
5995        .unwrap_or_else(|| json!({}));
5996    settings["security"]["auth"]["selectedType"] = Value::String("oauth-personal".into());
5997    std::fs::write(
5998        &settings_path,
5999        serde_json::to_vec_pretty(&settings).map_err(std::io::Error::other)?,
6000    )?;
6001    set_private_file_permissions(&settings_path)
6002}
6003
6004#[cfg(unix)]
6005fn set_private_dir_permissions(path: &Path) -> std::io::Result<()> {
6006    use std::os::unix::fs::PermissionsExt;
6007    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
6008}
6009
6010#[cfg(not(unix))]
6011fn set_private_dir_permissions(_path: &Path) -> std::io::Result<()> {
6012    Ok(())
6013}
6014
6015#[cfg(unix)]
6016fn set_private_file_permissions(path: &Path) -> std::io::Result<()> {
6017    use std::os::unix::fs::PermissionsExt;
6018    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
6019}
6020
6021#[cfg(not(unix))]
6022fn set_private_file_permissions(_path: &Path) -> std::io::Result<()> {
6023    Ok(())
6024}
6025
6026fn find_executable(program: &str) -> Option<PathBuf> {
6027    let candidate = PathBuf::from(program);
6028    if candidate.components().count() > 1 {
6029        return candidate.is_file().then_some(candidate);
6030    }
6031    let path = std::env::var_os("PATH")?;
6032    for directory in std::env::split_paths(&path) {
6033        let candidate = directory.join(program);
6034        if candidate.is_file() {
6035            return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
6036        }
6037        #[cfg(windows)]
6038        {
6039            for extension in ["exe", "cmd", "bat"] {
6040                let candidate = directory.join(format!("{program}.{extension}"));
6041                if candidate.is_file() {
6042                    return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
6043                }
6044            }
6045        }
6046    }
6047    None
6048}
6049
6050async fn executable_version(executable: &Path) -> Option<String> {
6051    let mut command = tokio::process::Command::new(executable);
6052    command
6053        .arg("--version")
6054        .stdin(std::process::Stdio::null())
6055        .stdout(std::process::Stdio::piped())
6056        .stderr(std::process::Stdio::piped())
6057        .kill_on_drop(true);
6058    let output = tokio::time::timeout(Duration::from_secs(3), command.output())
6059        .await
6060        .ok()?
6061        .ok()?;
6062    let stdout = String::from_utf8_lossy(&output.stdout);
6063    let stderr = String::from_utf8_lossy(&output.stderr);
6064    stdout
6065        .lines()
6066        .chain(stderr.lines())
6067        .map(str::trim)
6068        .find(|line| !line.is_empty())
6069        .map(|line| truncate_text(line, 200))
6070}
6071
6072pub(crate) fn auth_evidence(harness: &str) -> bool {
6073    let env_names: &[&str] = match harness {
6074        HarnessId::CLAUDE_CODE => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
6075        HarnessId::CODEX => &["OPENAI_API_KEY"],
6076        HarnessId::OPENCODE => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
6077        HarnessId::PI => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
6078        HarnessId::GROK => &["XAI_API_KEY", "GROK_API_KEY"],
6079        HarnessId::GEMINI => &["GEMINI_API_KEY", "GOOGLE_API_KEY"],
6080        HarnessId::SUPERCODE => &["OPENROUTER_API_KEY"],
6081        _ => &[],
6082    };
6083    if env_names
6084        .iter()
6085        .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
6086    {
6087        return true;
6088    }
6089    let Some(home) = supercode_interchange::user_home()
6090        .map(std::path::PathBuf::into_os_string)
6091        .map(PathBuf::from)
6092    else {
6093        return false;
6094    };
6095    let files: Vec<PathBuf> = match harness {
6096        HarnessId::CLAUDE_CODE => vec![home.join(".claude/.credentials.json")],
6097        HarnessId::CODEX => vec![home.join(".codex/auth.json")],
6098        HarnessId::OPENCODE => vec![
6099            home.join(".local/share/opencode/auth.json"),
6100            home.join(".config/opencode/auth.json"),
6101        ],
6102        HarnessId::PI => vec![home.join(".pi/agent/auth.json")],
6103        HarnessId::GROK => vec![home.join(".grok/auth.json")],
6104        HarnessId::GEMINI => vec![
6105            home.join(".gemini/oauth_creds.json"),
6106            home.join(".gemini/google_accounts.json"),
6107        ],
6108        HarnessId::SUPERCODE => vec![home.join(".config/supercode/credentials.toml")],
6109        HarnessId::HERMES => vec![home.join(".hermes/auth.json"), home.join(".hermes/.env")],
6110        _ => Vec::new(),
6111    };
6112    if files.into_iter().any(|path| {
6113        std::fs::metadata(path)
6114            .map(|metadata| metadata.is_file() && metadata.len() > 2)
6115            .unwrap_or(false)
6116    }) {
6117        return true;
6118    }
6119    // macOS keeps Claude Code's OAuth login in the Keychain, so
6120    // `.claude/.credentials.json` never exists there and the file probe above
6121    // reports a signed-in install as unauthenticated forever. A completed
6122    // login also writes an `oauthAccount` record into `~/.claude.json` on
6123    // every platform — file-based, prompt-free evidence (querying the
6124    // Keychain itself from an unsigned daemon can raise a UI prompt).
6125    if harness == HarnessId::CLAUDE_CODE {
6126        return std::fs::read_to_string(home.join(".claude.json"))
6127            .map(|text| text.contains("\"oauthAccount\""))
6128            .unwrap_or(false);
6129    }
6130    false
6131}
6132
6133fn looks_like_auth_error(message: &str) -> bool {
6134    let message = message.to_ascii_lowercase();
6135    [
6136        "auth",
6137        "login",
6138        "sign in",
6139        "sign-in",
6140        "credential",
6141        "unauthorized",
6142        "forbidden",
6143        "token",
6144    ]
6145    .iter()
6146    .any(|needle| message.contains(needle))
6147}
6148
6149fn unavailable_capabilities() -> crate::RuntimeCapabilities {
6150    crate::RuntimeCapabilities {
6151        start_session: false,
6152        resume_session: false,
6153        attach_existing_process: false,
6154        send_input: false,
6155        stream_events: false,
6156        interrupt: false,
6157        steer: false,
6158        respond_to_requests: false,
6159    }
6160}
6161
6162fn truncate_text(text: &str, max_chars: usize) -> String {
6163    let mut chars = text.chars();
6164    let truncated = chars.by_ref().take(max_chars).collect::<String>();
6165    if chars.next().is_some() {
6166        format!("{truncated}…")
6167    } else {
6168        truncated
6169    }
6170}
6171
6172/// The process group a runtime's own handle names, when it names one.
6173///
6174/// Every adapter that spawns a local process spawns it as its own group
6175/// leader (`Command::process_group(0)`), so the endpoint's pid IS the group
6176/// id. A runtime reached over HTTP, or one supercode joined rather than
6177/// spawned, names no group here and is left alone.
6178fn runtime_process_group(handle: &crate::RuntimeHandle) -> Option<u32> {
6179    match &handle.endpoint {
6180        crate::RuntimeEndpoint::LocalProcess { pid, .. } => *pid,
6181        crate::RuntimeEndpoint::Http { .. } => None,
6182    }
6183}
6184
6185/// SIGKILL a wedged runtime's whole process group, reporting whether there
6186/// was one to signal. This is the same group teardown a graceful `close`
6187/// performs; it runs here only when the graceful path blew its deadline,
6188/// because the task parked on the unanswered call still owns the process
6189/// handle and so no `Drop` of ours can reach it.
6190fn kill_runtime_process_group(process_group: Option<u32>) -> bool {
6191    match process_group {
6192        #[cfg(unix)]
6193        Some(pid) => {
6194            crate::lsp::kill_process_group(pid);
6195            true
6196        }
6197        #[cfg(not(unix))]
6198        Some(_) => false,
6199        None => false,
6200    }
6201}
6202
6203fn error_message(error: ServiceError) -> String {
6204    match error {
6205        ServiceError::InvalidParams(message)
6206        | ServiceError::Operation(message)
6207        | ServiceError::UnsupportedAction(message) => message,
6208        ServiceError::MethodNotFound => "runtime adapter is not available".into(),
6209        ServiceError::Sdk(error) => error.to_string(),
6210    }
6211}
6212
6213#[derive(Debug)]
6214enum ServiceError {
6215    InvalidParams(String),
6216    MethodNotFound,
6217    UnsupportedAction(String),
6218    Operation(String),
6219    Sdk(SdkError),
6220}
6221
6222fn sdk_error(operation: SdkOperation, error: ServiceError) -> SdkError {
6223    match error {
6224        ServiceError::InvalidParams(message) => {
6225            SdkError::new(SdkErrorCode::InvalidArgument, operation, message)
6226        }
6227        ServiceError::MethodNotFound | ServiceError::UnsupportedAction(_) => {
6228            SdkError::unsupported(operation)
6229        }
6230        ServiceError::Operation(message) => {
6231            let code = if message.contains("already in progress") {
6232                SdkErrorCode::Busy
6233            } else if message.contains("not supported by this runtime") {
6234                SdkErrorCode::UnsupportedAction
6235            } else if message.contains("unknown runtime connection") {
6236                SdkErrorCode::NotFound
6237            } else {
6238                SdkErrorCode::Execution
6239            };
6240            SdkError::new(code, operation, message)
6241        }
6242        ServiceError::Sdk(error) => error,
6243    }
6244}
6245
6246fn sdk_rpc_error(id: Value, error: &SdkError) -> Value {
6247    let error_code = error.code();
6248    let code = match error_code {
6249        SdkErrorCode::Unauthenticated => -32030,
6250        SdkErrorCode::Unauthorized => -32031,
6251        SdkErrorCode::ControllerRequired => -32032,
6252        SdkErrorCode::LeaseExpired => -32033,
6253        SdkErrorCode::InvalidArgument => -32602,
6254        SdkErrorCode::NotFound => -32004,
6255        SdkErrorCode::Busy => -32000,
6256        SdkErrorCode::UnsupportedAction => -32020,
6257        SdkErrorCode::Execution => -32002,
6258        SdkErrorCode::Transport => -32003,
6259    };
6260    json!({
6261        "jsonrpc": "2.0",
6262        "id": id,
6263        "error": {
6264            "code": code,
6265            "name": error_code,
6266            "operation": error.operation(),
6267            "message": error.to_string(),
6268        },
6269    })
6270}
6271
6272fn decode<T: for<'de> Deserialize<'de>>(value: Value) -> std::result::Result<T, ServiceError> {
6273    serde_json::from_value(value).map_err(|error| ServiceError::InvalidParams(error.to_string()))
6274}
6275
6276fn operation(error: impl Into<crate::Error>) -> ServiceError {
6277    let error = error.into();
6278    match error {
6279        crate::Error::Sdk(error) => ServiceError::Sdk(error),
6280        error => ServiceError::Operation(error.to_string()),
6281    }
6282}
6283
6284/// ORCH-12 `harness.v1.memory.show|search` params. `homes` is the same
6285/// storage-root override every read-only method accepts, so a caller can
6286/// point the read at a fixture home without touching the real ones.
6287#[derive(Debug, Clone, Deserialize, Default)]
6288#[serde(default)]
6289struct MemoryRequest {
6290    /// Harness whose store is read. Required.
6291    harness: Option<String>,
6292    /// The needle, required by `search`.
6293    query: Option<String>,
6294    /// Hermes profile, OpenClaw agent, or Claude Code project.
6295    profile: Option<String>,
6296    /// Claude Code session id selecting a project store (`show` only).
6297    session: Option<String>,
6298    /// Include each document's whole text (`show` only).
6299    full: bool,
6300    /// Treat `query` as a regular expression (`search` only).
6301    regex: bool,
6302    /// Working tree whose project store is read.
6303    cwd: Option<std::path::PathBuf>,
6304    /// Storage roots to read.
6305    homes: crate::HarnessHomes,
6306}
6307
6308/// Read the memory noun. A harness with no memory store fails with
6309/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6310fn memory_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6311    let request = decode::<MemoryRequest>(params)?;
6312    let harness = request
6313        .harness
6314        .clone()
6315        .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6316    let to_service = |error: crate::memory::MemoryError| match error {
6317        crate::memory::MemoryError::UnsupportedHarness { .. }
6318        | crate::memory::MemoryError::SessionNotScoped { .. } => {
6319            ServiceError::UnsupportedAction(error.to_string())
6320        }
6321        other => ServiceError::InvalidParams(other.to_string()),
6322    };
6323    match method {
6324        "harness.v1.memory.show" => {
6325            let documents = crate::memory::show_memory(&crate::memory::MemoryQuery {
6326                harness,
6327                profile: request.profile,
6328                session: request.session,
6329                full: request.full,
6330                cwd: request.cwd,
6331                homes: request.homes,
6332            })
6333            .map_err(to_service)?;
6334            Ok(json!({
6335                "schema": crate::memory::MEMORY_SCHEMA,
6336                "documents": documents,
6337            }))
6338        }
6339        "harness.v1.memory.search" => {
6340            let query = request
6341                .query
6342                .ok_or_else(|| ServiceError::InvalidParams("`query` is required".into()))?;
6343            let matches = crate::memory::search_memory(&crate::memory::MemorySearchQuery {
6344                harness,
6345                query,
6346                profile: request.profile,
6347                regex: request.regex,
6348                cwd: request.cwd,
6349                homes: request.homes,
6350            })
6351            .map_err(to_service)?;
6352            Ok(json!({
6353                "schema": crate::memory::MEMORY_SCHEMA,
6354                "matches": matches,
6355            }))
6356        }
6357        _ => Err(ServiceError::MethodNotFound),
6358    }
6359}
6360
6361/// ORCH-10 `harness.v1.profiles.list|get` params. `homes` is the same
6362/// storage-root override every read-only method accepts, so a caller can
6363/// point the read at a fixture home without touching the real ones.
6364#[derive(Debug, Clone, Deserialize)]
6365#[serde(default)]
6366struct ProfilesQuery {
6367    /// Restrict the listing to one harness. `get` requires it.
6368    harness: Option<String>,
6369    /// Profile name, required by `get`.
6370    name: Option<String>,
6371    /// Storage roots to read.
6372    homes: crate::HarnessHomes,
6373}
6374
6375impl Default for ProfilesQuery {
6376    fn default() -> Self {
6377        Self {
6378            harness: None,
6379            name: None,
6380            homes: crate::HarnessHomes::default(),
6381        }
6382    }
6383}
6384
6385/// Read the profile noun. A harness with no profile concept fails with
6386/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6387fn profiles_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6388    let query = decode::<ProfilesQuery>(params)?;
6389    let to_service = |error: crate::profiles::ProfileError| match error {
6390        crate::profiles::ProfileError::UnsupportedHarness { .. } => {
6391            ServiceError::UnsupportedAction(error.to_string())
6392        }
6393        crate::profiles::ProfileError::NotFound { .. } => {
6394            ServiceError::InvalidParams(error.to_string())
6395        }
6396    };
6397    match method {
6398        "harness.v1.profiles.list" => {
6399            let profiles = crate::profiles::list_profiles(&query.homes, query.harness.as_deref())
6400                .map_err(to_service)?;
6401            Ok(json!({
6402                "schema": crate::profiles::PROFILES_SCHEMA,
6403                "profiles": profiles,
6404            }))
6405        }
6406        "harness.v1.profiles.get" => {
6407            let harness = query
6408                .harness
6409                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6410            let name = query
6411                .name
6412                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6413            let profile =
6414                crate::profiles::get_profile(&query.homes, &harness, &name).map_err(to_service)?;
6415            Ok(json!({
6416                "schema": crate::profiles::PROFILES_SCHEMA,
6417                "profile": profile,
6418            }))
6419        }
6420        _ => Err(ServiceError::MethodNotFound),
6421    }
6422}
6423
6424/// ORCH-14 `harness.v1.channels.list|status` params, the same storage-root
6425/// override every read-only method accepts so a caller can point the read at
6426/// a fixture home without touching the real ones.
6427#[derive(Debug, Clone, Deserialize)]
6428#[serde(default)]
6429struct ChannelsQuery {
6430    /// Restrict the listing to one harness. `status` requires it.
6431    harness: Option<String>,
6432    /// Channel name, required by `status`.
6433    name: Option<String>,
6434    /// Storage roots to read.
6435    homes: crate::HarnessHomes,
6436}
6437
6438impl Default for ChannelsQuery {
6439    fn default() -> Self {
6440        Self {
6441            harness: None,
6442            name: None,
6443            homes: crate::HarnessHomes::default(),
6444        }
6445    }
6446}
6447
6448/// Read the channel noun. A harness with no channel concept fails with
6449/// `UnsupportedAction` (RPC `-32020`), never an empty list. No row carries a
6450/// token, key or secret — see `crate::channels` "Secrecy".
6451#[derive(Debug, Clone, Deserialize)]
6452#[serde(default)]
6453struct RoutesQuery {
6454    harness: Option<String>,
6455    /// Restrict to routes targeting one profile / agent.
6456    profile: Option<String>,
6457    homes: crate::HarnessHomes,
6458}
6459
6460impl Default for RoutesQuery {
6461    fn default() -> Self {
6462        Self {
6463            harness: None,
6464            profile: None,
6465            homes: crate::HarnessHomes::default(),
6466        }
6467    }
6468}
6469
6470#[derive(Debug, Clone, Deserialize)]
6471#[serde(default)]
6472struct TriggersQuery {
6473    harness: Option<String>,
6474    homes: crate::HarnessHomes,
6475}
6476
6477impl Default for TriggersQuery {
6478    fn default() -> Self {
6479        Self {
6480            harness: None,
6481            homes: crate::HarnessHomes::default(),
6482        }
6483    }
6484}
6485
6486fn triggers_call(params: Value) -> std::result::Result<Value, ServiceError> {
6487    let query = decode::<TriggersQuery>(params)?;
6488    let triggers = crate::triggers::list_triggers(&query.homes, query.harness.as_deref())
6489        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6490    Ok(json!({
6491        "schema": crate::triggers::TRIGGERS_SCHEMA,
6492        "triggers": triggers,
6493    }))
6494}
6495
6496fn routes_call(params: Value) -> std::result::Result<Value, ServiceError> {
6497    let query = decode::<RoutesQuery>(params)?;
6498    let routes = crate::routes::list_routes(
6499        &query.homes,
6500        query.harness.as_deref(),
6501        query.profile.as_deref(),
6502    )
6503    .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6504    Ok(json!({
6505        "schema": crate::routes::ROUTES_SCHEMA,
6506        "routes": routes,
6507    }))
6508}
6509
6510fn channels_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6511    let query = decode::<ChannelsQuery>(params)?;
6512    let to_service = |error: crate::channels::ChannelError| match error {
6513        crate::channels::ChannelError::UnsupportedHarness { .. } => {
6514            ServiceError::UnsupportedAction(error.to_string())
6515        }
6516        crate::channels::ChannelError::NotFound { .. } => {
6517            ServiceError::InvalidParams(error.to_string())
6518        }
6519    };
6520    match method {
6521        "harness.v1.channels.list" => {
6522            let channels = crate::channels::list_channels(&query.homes, query.harness.as_deref())
6523                .map_err(to_service)?;
6524            Ok(json!({
6525                "schema": crate::channels::CHANNELS_SCHEMA,
6526                "channels": channels,
6527            }))
6528        }
6529        "harness.v1.channels.status" => {
6530            let harness = query
6531                .harness
6532                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6533            let name = query
6534                .name
6535                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6536            let channel = crate::channels::channel_status(&query.homes, &harness, &name)
6537                .map_err(to_service)?;
6538            Ok(json!({
6539                "schema": crate::channels::CHANNELS_SCHEMA,
6540                "channel": channel,
6541            }))
6542        }
6543        _ => Err(ServiceError::MethodNotFound),
6544    }
6545}
6546
6547fn rpc_error(id: Value, code: i64, message: &str) -> Value {
6548    json!({
6549        "jsonrpc": "2.0",
6550        "id": id,
6551        "error": {"code": code, "message": message},
6552    })
6553}
6554
6555#[cfg(test)]
6556mod tests {
6557    use super::*;
6558    use crate::{HarnessEvent, HarnessId, RuntimeEndpoint, RuntimeHandle, StorageLocator};
6559    use async_trait::async_trait;
6560    use std::io::Write;
6561    use std::path::PathBuf;
6562    use std::time::Instant;
6563
6564    #[test]
6565    fn indexed_claude_descriptor_carries_its_delivery_door() {
6566        let descriptor = SessionDescriptor {
6567            locator: SessionLocator {
6568                harness: HarnessId::new(HarnessId::CLAUDE_CODE),
6569                session_id: "live-session".into(),
6570                storage: StorageLocator::File {
6571                    path: PathBuf::from("/tmp/live-session.jsonl"),
6572                },
6573            },
6574            cwd: Some(PathBuf::from("/project")),
6575            title: None,
6576            preview_candidates: Vec::new(),
6577            latest_message_candidates: Vec::new(),
6578            updated_at_ms: Some(1),
6579            message_count: None,
6580            model: None,
6581            parent_session_id: None,
6582            child_session_count: 0,
6583            nouns: Default::default(),
6584        };
6585        let doors =
6586            crate::mail_route::LiveSessions::with(&[("claude-code", "live-session", "native")]);
6587
6588        let value = live_descriptor_value(&descriptor, &doors).unwrap();
6589        assert_eq!(value["delivery"], "native");
6590        assert!(value.get("live_endpoint").is_none());
6591    }
6592
6593    struct EndingRuntime {
6594        handle: RuntimeHandle,
6595        event: Option<HarnessEvent>,
6596        close_failures: usize,
6597    }
6598
6599    #[async_trait]
6600    impl RuntimeConnection for EndingRuntime {
6601        fn handle(&self) -> &RuntimeHandle {
6602            &self.handle
6603        }
6604
6605        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
6606            unreachable!("ending runtime does not accept input")
6607        }
6608
6609        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
6610            Ok(self.event.take())
6611        }
6612
6613        async fn interrupt(&mut self) -> crate::Result<()> {
6614            Ok(())
6615        }
6616
6617        async fn respond(&mut self, _request_id: Value, _response: Value) -> crate::Result<()> {
6618            Ok(())
6619        }
6620
6621        async fn close(&mut self) -> crate::Result<()> {
6622            if self.close_failures > 0 {
6623                self.close_failures -= 1;
6624                return Err(crate::Error::Other(
6625                    "cleanup temporarily unavailable".into(),
6626                ));
6627            }
6628            Ok(())
6629        }
6630    }
6631
6632    fn ending_runtime(event: Option<HarnessEvent>) -> Box<dyn RuntimeConnection> {
6633        Box::new(EndingRuntime {
6634            handle: RuntimeHandle {
6635                harness: HarnessId::from(HarnessId::CLAUDE_CODE),
6636                runtime_id: "ending-session".into(),
6637                endpoint: RuntimeEndpoint::LocalProcess {
6638                    pid: None,
6639                    command: vec!["ending-runtime".into()],
6640                    protocol: "test".into(),
6641                },
6642            },
6643            event,
6644            close_failures: 0,
6645        })
6646    }
6647
6648    #[tokio::test]
6649    async fn closing_a_runtime_surrenders_the_connection_even_when_teardown_fails() {
6650        let mut service = HarnessSessionService::new();
6651        let handle = ending_runtime(None).handle().clone();
6652        let runtime_id = handle.runtime_id.clone();
6653        let opened = service
6654            .insert_runtime(Box::new(EndingRuntime {
6655                handle,
6656                event: None,
6657                close_failures: 1,
6658            }))
6659            .unwrap();
6660        let connection = opened["connection"].as_str().unwrap().to_string();
6661        service.terminal_launches.insert(
6662            connection.clone(),
6663            StructuredLaunch {
6664                cwd: PathBuf::from("/fixture"),
6665                program: "fixture".into(),
6666                arguments: Vec::new(),
6667                env: BTreeMap::new(),
6668            },
6669        );
6670        let first = service
6671            .handle_async(request(
6672                1,
6673                "harness.v1.runtimes.close",
6674                json!({"connection": connection}),
6675            ))
6676            .await;
6677        // The harness's own teardown failed and the caller is told so...
6678        assert!(first.get("error").is_some(), "{first}");
6679        // ...but the connection is gone all the same. A connection whose close
6680        // cannot complete is exactly the one that must not stay registered:
6681        // holding it would answer every later call on this node with a turn
6682        // that is never going to end.
6683        assert!(!service.runtimes.contains_key(&connection));
6684        assert!(!service.terminal_launches.contains_key(&connection));
6685        assert!(!service.runtime_sequences.contains_key(&runtime_id));
6686        let again = service
6687            .handle_async(request(
6688                2,
6689                "harness.v1.runtimes.close",
6690                json!({"connection": connection}),
6691            ))
6692            .await;
6693        assert_eq!(again["error"]["code"], -32602, "{again}");
6694    }
6695
6696    fn request(id: u64, method: &str, params: Value) -> Value {
6697        json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
6698    }
6699
6700    // ---- ORCH-6: conversation nouns on `sessions.*` ----------------------
6701
6702    fn hermes_store() -> PathBuf {
6703        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hermes_home/state.db")
6704    }
6705
6706    /// The discovery response for the Hermes fixture home, with the one
6707    /// machine-specific value (the absolute store path) replaced so the exact
6708    /// same JSON can be committed and replayed by the UI story.
6709    fn hermes_discovery(params: Value) -> Value {
6710        let mut response =
6711            HarnessSessionService::new().handle(request(1, "harness.v1.sessions.discover", params));
6712        let store = hermes_store().display().to_string();
6713        for session in response["result"]["sessions"]
6714            .as_array_mut()
6715            .expect("sessions array")
6716        {
6717            if session["locator"]["storage"]["path"] == json!(store) {
6718                session["locator"]["storage"]["path"] = json!("<fixtures>/hermes_home/state.db");
6719            }
6720            // `activity` reports a wall-clock observation instant, not a fact
6721            // about the session; it would make this response differ on every
6722            // call. The nouns under test are all session facts.
6723            session.as_object_mut().unwrap().remove("activity");
6724        }
6725        response["result"].take()
6726    }
6727
6728    fn hermes_query() -> Value {
6729        json!({
6730            "harnesses": ["hermes"],
6731            "homes": {"hermes": hermes_store()},
6732        })
6733    }
6734
6735    fn row<'a>(result: &'a Value, id: &str) -> &'a Value {
6736        result["sessions"]
6737            .as_array()
6738            .expect("sessions array")
6739            .iter()
6740            .find(|session| session["locator"]["session_id"] == json!(id))
6741            .unwrap_or_else(|| panic!("no discovered row for `{id}` in {result:#}"))
6742    }
6743
6744    #[test]
6745    fn orch6_discover_rows_carry_the_conversation_nouns() {
6746        let result = hermes_discovery(hermes_query());
6747
6748        // A Telegram DM: reached on a channel, no repo — the workspace IS the
6749        // channel (D2 precedence), and `main` is not a profile.
6750        let dm = row(&result, "tg-dm-1");
6751        assert_eq!(dm["trigger"], json!("channel"));
6752        assert_eq!(dm["surface"]["platform"], json!("telegram"));
6753        assert_eq!(dm["surface"]["kind"], json!("dm"));
6754        assert_eq!(dm["surface"]["chat_id"], json!("123456"));
6755        assert_eq!(dm["surface"]["participant_id"], json!("u1"));
6756        assert_eq!(
6757            dm["workspace"],
6758            json!({"kind": "channel", "value": "telegram:123456"})
6759        );
6760        assert!(dm.get("profile").is_none(), "{dm:#}");
6761
6762        // A cron fire: recurring, with the job recovered from the minted id.
6763        let fire = row(&result, "cron_job42_20260902_120000");
6764        assert_eq!(fire["trigger"], json!("cron"));
6765        assert_eq!(
6766            fire["recurrence"],
6767            json!({"job_id": "job42", "kind": "cron"})
6768        );
6769        assert_eq!(fire["workspace"]["kind"], json!("repo"));
6770
6771        // A profiled group session with a pending handoff: repo workspace
6772        // wins over the channel, and the chat stays on the surface key.
6773        let coder = row(&result, "tg-coder-1");
6774        assert_eq!(coder["trigger"], json!("channel"));
6775        assert_eq!(coder["profile"], json!("coder"));
6776        assert_eq!(coder["surface"]["thread_id"], json!("55"));
6777        assert_eq!(
6778            coder["surface"]["key"],
6779            json!("agent:coder:telegram:group:-100777:55")
6780        );
6781        assert_eq!(
6782            coder["workspace"],
6783            json!({"kind": "repo", "value": "/workspace/project"})
6784        );
6785        assert_eq!(
6786            coder["cross_surface"],
6787            json!({"state": "pending", "platform": "discord"})
6788        );
6789
6790        // A plain ACP session stays human-triggered with no surface at all.
6791        let acp = row(&result, "cef97234-e8e8-428a-99ab-e8fff4e7e613");
6792        assert_eq!(acp["trigger"], json!("human"));
6793        assert!(acp.get("surface").is_none(), "{acp:#}");
6794        assert_eq!(acp["workspace"], json!({"kind": "none"}));
6795    }
6796
6797    #[test]
6798    fn orch6_discover_filters_by_harness_and_profile() {
6799        let mut params = hermes_query();
6800        params["profile"] = json!("coder");
6801        let result = hermes_discovery(params);
6802        let ids: Vec<&str> = result["sessions"]
6803            .as_array()
6804            .expect("sessions array")
6805            .iter()
6806            .map(|session| session["locator"]["session_id"].as_str().unwrap())
6807            .collect();
6808        assert_eq!(ids, vec!["tg-coder-1"]);
6809
6810        // A profile no session is routed through returns nothing rather than
6811        // silently ignoring the filter.
6812        let mut missing = hermes_query();
6813        missing["profile"] = json!("nobody");
6814        assert_eq!(hermes_discovery(missing)["sessions"], json!([]));
6815
6816        // The harness filter is `harnesses`; an id no harness answers to is
6817        // an empty page, never every store on the box.
6818        let elsewhere = json!({"harnesses": ["codex"], "homes": {"codex": hermes_store()}});
6819        assert_eq!(hermes_discovery(elsewhere)["sessions"], json!([]));
6820    }
6821
6822    #[test]
6823    fn orch6_load_reports_the_same_nouns_as_discovery() {
6824        let mut service = HarnessSessionService::new();
6825        let loaded = service.handle(request(
6826            1,
6827            "harness.v1.sessions.load",
6828            json!({"locator": {
6829                "harness": "hermes",
6830                "session_id": "tg-coder-1",
6831                "storage": {"kind": "file", "path": hermes_store()},
6832            }}),
6833        ));
6834        let session = &loaded["result"]["session"];
6835        let discovered = hermes_discovery(hermes_query());
6836        let row = row(&discovered, "tg-coder-1");
6837        for noun in [
6838            "trigger",
6839            "surface",
6840            "profile",
6841            "recurrence",
6842            "cross_surface",
6843            "workspace",
6844        ] {
6845            assert_eq!(
6846                session[noun],
6847                row.get(noun).cloned().unwrap_or(Value::Null),
6848                "`{noun}` disagrees between sessions.load and sessions.discover"
6849            );
6850        }
6851    }
6852
6853    /// ORCH-10: the fixture homes, as the RPC's `homes` override. Hermes's
6854    /// home is named by its `state.db`; OpenClaw's is the state directory.
6855    fn profile_fixture_homes() -> Value {
6856        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6857        json!({
6858            "hermes": fixtures.join("hermes_home/state.db"),
6859            "openclaw": fixtures.join("openclaw_home"),
6860        })
6861    }
6862
6863    fn profile_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
6864        response["result"]["profiles"]
6865            .as_array()
6866            .unwrap_or_else(|| panic!("no profiles array in {response}"))
6867            .iter()
6868            .find(|row| row["harness"] == harness && row["name"] == name)
6869            .unwrap_or_else(|| panic!("no `{harness}` profile `{name}` in {response}"))
6870    }
6871
6872    /// dev/01: every source answers in one row shape, over the committed
6873    /// fixture homes — the Hermes profile directory and its `state.db`
6874    /// partition, the OpenClaw agent directories and `openclaw.json`, and
6875    /// supercode's own presets.
6876    #[test]
6877    fn profiles_list_reads_every_source_uniformly() {
6878        let mut service = HarnessSessionService::new();
6879        let response = service.handle(request(
6880            1,
6881            "harness.v1.profiles.list",
6882            json!({"homes": profile_fixture_homes()}),
6883        ));
6884        assert_eq!(
6885            response["result"]["schema"],
6886            crate::profiles::PROFILES_SCHEMA
6887        );
6888
6889        let default = profile_row(&response, "hermes", "default");
6890        assert_eq!(default["kind"], "hermes_profile");
6891        assert_eq!(default["default"], true);
6892        assert_eq!(default["routes"], 0);
6893        assert_eq!(default["sessions"], 11);
6894        assert_eq!(default["model"], "anthropic/claude-sonnet-4-5");
6895
6896        let coder = profile_row(&response, "hermes", "coder");
6897        assert_eq!(coder["kind"], "hermes_profile");
6898        assert_eq!(coder["default"], false);
6899        assert_eq!(coder["routes"], 1, "gateway.profile_routes targets coder");
6900        assert_eq!(coder["sessions"], 1, "state.db profile_name = 'coder'");
6901        assert_eq!(coder["model"], "anthropic/claude-opus-4-8");
6902        assert!(coder["home"]
6903            .as_str()
6904            .unwrap()
6905            .ends_with("hermes_home/profiles/coder"));
6906
6907        let main = profile_row(&response, "openclaw", "main");
6908        assert_eq!(main["kind"], "openclaw_agent");
6909        // No entry declares `default: true` (real configs do not), so `main`
6910        // wins on OpenClaw's own convention rather than alphabetically.
6911        assert_eq!(main["default"], true);
6912        assert_eq!(main["routes"], 0);
6913        assert_eq!(main["sessions"], 4);
6914        assert_eq!(
6915            main["model"],
6916            Value::Null,
6917            "`agents.defaults.model` is an install default, not this agent's pin"
6918        );
6919
6920        let design = profile_row(&response, "openclaw", "design");
6921        assert_eq!(design["default"], false);
6922        assert_eq!(design["routes"], 1, "one binding names agentId `design`");
6923        assert_eq!(design["sessions"], 0);
6924        assert_eq!(design["model"], "anthropic/claude-opus-4-8");
6925
6926        let preset = profile_row(&response, "supercode", "supercode-default");
6927        assert_eq!(preset["kind"], "preset");
6928        assert_eq!(preset["default"], true);
6929        assert_eq!(preset["home"], Value::Null);
6930        assert_eq!(preset["routes"], Value::Null);
6931    }
6932
6933    /// Codex's own profiles are `[profiles.<name>]` tables, with the
6934    /// top-level `profile` key naming the default.
6935    #[test]
6936    fn profiles_list_reads_codex_profile_tables() {
6937        let codex_home = std::env::temp_dir().join(format!(
6938            "supercode-orch10-codex-{}-{}",
6939            std::process::id(),
6940            std::time::SystemTime::now()
6941                .duration_since(std::time::UNIX_EPOCH)
6942                .unwrap()
6943                .as_nanos()
6944        ));
6945        std::fs::create_dir_all(codex_home.join("sessions")).unwrap();
6946        std::fs::write(
6947            codex_home.join("config.toml"),
6948            "profile = \"review\"\n\n[profiles.review]\nmodel = \"gpt-5.1-codex\"\n\n[profiles.fast]\nmodel = \"gpt-5.1-codex-mini\"\n",
6949        )
6950        .unwrap();
6951
6952        let mut service = HarnessSessionService::new();
6953        let response = service.handle(request(
6954            1,
6955            "harness.v1.profiles.list",
6956            json!({"harness": "codex", "homes": {"codex": codex_home.join("sessions")}}),
6957        ));
6958        let rows = response["result"]["profiles"].as_array().unwrap();
6959        assert_eq!(rows.len(), 2, "{response}");
6960        let review = profile_row(&response, "codex", "review");
6961        assert_eq!(review["kind"], "codex_profile");
6962        assert_eq!(review["default"], true);
6963        assert_eq!(review["model"], "gpt-5.1-codex");
6964        assert_eq!(review["home"], Value::Null);
6965        assert_eq!(profile_row(&response, "codex", "fast")["default"], false);
6966
6967        let got = service.handle(request(
6968            2,
6969            "harness.v1.profiles.get",
6970            json!({
6971                "harness": "codex",
6972                "name": "fast",
6973                "homes": {"codex": codex_home.join("sessions")},
6974            }),
6975        ));
6976        assert_eq!(got["result"]["profile"]["model"], "gpt-5.1-codex-mini");
6977        std::fs::remove_dir_all(&codex_home).ok();
6978    }
6979
6980    /// A verb a harness lacks fails with `UnsupportedAction`, never a silent
6981    /// empty list; an unknown name is an invalid argument, not an empty row.
6982    #[test]
6983    fn profiles_refuse_harnesses_without_the_concept() {
6984        let mut service = HarnessSessionService::new();
6985        let response = service.handle(request(
6986            1,
6987            "harness.v1.profiles.list",
6988            json!({"harness": "claude-code"}),
6989        ));
6990        assert_eq!(response["error"]["code"], -32020, "{response}");
6991
6992        let missing = service.handle(request(
6993            2,
6994            "harness.v1.profiles.get",
6995            json!({
6996                "harness": "hermes",
6997                "name": "no-such-profile",
6998                "homes": profile_fixture_homes(),
6999            }),
7000        ));
7001        assert_eq!(missing["error"]["code"], -32602, "{missing}");
7002    }
7003
7004    /// The two methods are advertised, so a client discovers them from
7005    /// `harness.v1.capabilities` rather than from documentation.
7006    #[test]
7007    fn profiles_methods_are_advertised() {
7008        let mut service = HarnessSessionService::new();
7009        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
7010        let methods = response["result"]["methods"].as_array().unwrap();
7011        for method in ["harness.v1.profiles.list", "harness.v1.profiles.get"] {
7012            assert!(
7013                methods.iter().any(|entry| entry == method),
7014                "{method} is not advertised"
7015            );
7016        }
7017    }
7018
7019    // -----------------------------------------------------------------
7020    // ORCH-14 — channels
7021    // -----------------------------------------------------------------
7022
7023    fn channel_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
7024        response["result"]["channels"]
7025            .as_array()
7026            .unwrap_or_else(|| panic!("no channels array in {response}"))
7027            .iter()
7028            .find(|row| row["harness"] == harness && row["name"] == name)
7029            .unwrap_or_else(|| panic!("no `{harness}` channel `{name}` in {response}"))
7030    }
7031
7032    fn channels_list(harness: Option<&str>) -> Value {
7033        let mut params = json!({"homes": profile_fixture_homes()});
7034        if let Some(harness) = harness {
7035            params["harness"] = json!(harness);
7036        }
7037        HarnessSessionService::new().handle(request(1, "harness.v1.channels.list", params))
7038    }
7039
7040    /// dev/01: both sources answer in one row shape over the committed
7041    /// fixture homes — Hermes's `platforms:` blocks with their `extra` maps,
7042    /// and OpenClaw's `channels.<name>` entries split per account.
7043    #[test]
7044    fn channels_list_reads_both_gateway_harnesses_uniformly() {
7045        let response = channels_list(None);
7046        assert_eq!(
7047            response["result"]["schema"],
7048            crate::channels::CHANNELS_SCHEMA
7049        );
7050
7051        // Hermes: a credentialed platform, a bridged `extra.key` platform,
7052        // and one the config explicitly disables.
7053        let telegram = channel_row(&response, "hermes", "telegram");
7054        assert_eq!(telegram["kind"], "telegram");
7055        assert_eq!(telegram["enabled"], true);
7056        assert_eq!(telegram["configured"], true);
7057        // The `sessions` count is the discovery rows whose surface platform
7058        // is telegram: the fixture's `agent:main:telegram:…` DM and the
7059        // `agent:coder:telegram:…` group.
7060        assert_eq!(telegram["sessions"], 2);
7061        let api = channel_row(&response, "hermes", "api_server");
7062        assert_eq!(api["configured"], true, "extra.key is a credential key");
7063        assert_eq!(api["sessions"], 0);
7064        let webhook = channel_row(&response, "hermes", "webhook");
7065        assert_eq!(webhook["enabled"], false);
7066        // Hermes lists no credential for `webhook`: declaring it is all it
7067        // needs, so a credential-less entry is still `configured`.
7068        assert_eq!(webhook["configured"], true);
7069
7070        // OpenClaw: one row per account, named `<channel>/<accountId>`.
7071        let linked = channel_row(&response, "openclaw", "slack/T0FIXTURE");
7072        assert_eq!(linked["kind"], "slack");
7073        assert_eq!(linked["account"], "T0FIXTURE");
7074        assert_eq!(linked["enabled"], true);
7075        assert_eq!(linked["configured"], true);
7076        let unlinked = channel_row(&response, "openclaw", "slack/T1FIXTURE");
7077        assert_eq!(unlinked["enabled"], false);
7078        assert_eq!(
7079            unlinked["configured"], false,
7080            "an account with no credential key is not configured"
7081        );
7082        // A single-account channel keeps its own name and names its account
7083        // inline.
7084        let telegram = channel_row(&response, "openclaw", "telegram");
7085        assert_eq!(telegram["account"], "hermes-fixture-bot");
7086        assert_eq!(telegram["configured"], true);
7087
7088        // `status` is never claimed from a config file.
7089        for row in response["result"]["channels"].as_array().unwrap() {
7090            assert_eq!(row["status"], "unknown", "{row}");
7091        }
7092    }
7093
7094    /// dev/01: no field of any emitted row carries a credential. The fixture
7095    /// homes hold four FAKE credential strings; a row that leaked one — as a
7096    /// value, an account label, or a name — fails here.
7097    #[test]
7098    fn channels_rows_never_carry_a_fixture_secret() {
7099        let secrets = [
7100            "FAKE-TOKEN-DO-NOT-EMIT",
7101            "FAKE-API-SERVER-KEY-DO-NOT-EMIT",
7102            "FAKE-SLACK-BOT-TOKEN-DO-NOT-EMIT",
7103            "FAKE-SLACK-APP-TOKEN-DO-NOT-EMIT",
7104            "FAKE-TELEGRAM-TOKEN-DO-NOT-EMIT",
7105        ];
7106        // The strings really are in the fixtures, so this test can fail.
7107        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7108        let raw = format!(
7109            "{}{}",
7110            std::fs::read_to_string(fixtures.join("hermes_home/config.yaml")).unwrap(),
7111            std::fs::read_to_string(fixtures.join("openclaw_home/openclaw.json")).unwrap(),
7112        );
7113        for secret in secrets {
7114            assert!(raw.contains(secret), "fixture no longer holds `{secret}`");
7115        }
7116
7117        let emitted = serde_json::to_string(&channels_list(None)["result"]).unwrap();
7118        for secret in secrets {
7119            assert!(
7120                !emitted.contains(secret),
7121                "`{secret}` leaked into a channel row: {emitted}"
7122            );
7123        }
7124        // Belt and braces: no row FIELD is credential-shaped either, so a
7125        // future field cannot smuggle one past the literal scan.
7126        for row in channels_list(None)["result"]["channels"]
7127            .as_array()
7128            .unwrap()
7129        {
7130            for key in row.as_object().unwrap().keys() {
7131                let key = key.to_ascii_lowercase();
7132                assert!(
7133                    !["token", "key", "secret", "password", "credential"]
7134                        .iter()
7135                        .any(|marker| key.ends_with(marker)),
7136                    "`{key}` is a credential-shaped field on a channel row"
7137                );
7138            }
7139        }
7140    }
7141
7142    /// `status` answers one row by name, and refuses an unknown one.
7143    #[test]
7144    fn channels_status_reads_one_row_by_name() {
7145        let mut service = HarnessSessionService::new();
7146        let got = service.handle(request(
7147            1,
7148            "harness.v1.channels.status",
7149            json!({
7150                "harness": "openclaw",
7151                "name": "slack/T0FIXTURE",
7152                "homes": profile_fixture_homes(),
7153            }),
7154        ));
7155        assert_eq!(got["result"]["channel"]["kind"], "slack");
7156        assert_eq!(got["result"]["channel"]["account"], "T0FIXTURE");
7157        assert_eq!(got["result"]["channel"]["status"], "unknown");
7158
7159        let missing = service.handle(request(
7160            2,
7161            "harness.v1.channels.status",
7162            json!({
7163                "harness": "openclaw",
7164                "name": "no-such-channel",
7165                "homes": profile_fixture_homes(),
7166            }),
7167        ));
7168        assert_eq!(missing["error"]["code"], -32602, "{missing}");
7169    }
7170
7171    /// A harness with no channel concept fails with `UnsupportedAction`,
7172    /// never a silent empty list — Claude Code included, because its channels
7173    /// are MCP-protocol declarations no config file names.
7174    #[test]
7175    fn channels_refuse_harnesses_without_the_concept() {
7176        let response = channels_list(Some("claude-code"));
7177        assert_eq!(response["error"]["code"], -32020, "{response}");
7178        let codex = channels_list(Some("codex"));
7179        assert_eq!(codex["error"]["code"], -32020, "{codex}");
7180    }
7181
7182    /// The harness filter restricts the rows rather than being ignored.
7183    #[test]
7184    fn channels_list_filters_by_harness() {
7185        let response = channels_list(Some("openclaw"));
7186        let rows = response["result"]["channels"].as_array().unwrap();
7187        assert!(!rows.is_empty(), "{response}");
7188        assert!(
7189            rows.iter().all(|row| row["harness"] == "openclaw"),
7190            "harness filter leaked: {response}"
7191        );
7192    }
7193
7194    /// Both methods are advertised, so a client discovers them from
7195    /// `harness.v1.capabilities` rather than from documentation.
7196    #[test]
7197    fn channels_methods_are_advertised() {
7198        let mut service = HarnessSessionService::new();
7199        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
7200        let methods = response["result"]["methods"].as_array().unwrap();
7201        for method in ["harness.v1.channels.list", "harness.v1.channels.status"] {
7202            assert!(
7203                methods.iter().any(|entry| entry == method),
7204                "{method} is not advertised"
7205            );
7206        }
7207    }
7208
7209    /// The UI story renders REAL rows: this writes the discovery response the
7210    /// two assertions above pin into the fixture the Storybook
7211    /// `Compositions/Universal nouns` stories import, and fails when the
7212    /// committed copy has drifted from what the service now answers.
7213    #[test]
7214    fn orch6_story_fixture_matches_the_live_discovery_response() {
7215        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7216            .join("../../sdk/ui/stories/fixtures/hermes-discovery.json");
7217        let mut result = hermes_discovery(hermes_query());
7218        // `updated_at_ms` is derived from the fixture's own stored timestamps,
7219        // so the whole response is deterministic; drop only the cursor, which
7220        // is pagination state rather than a session fact.
7221        result.as_object_mut().unwrap().remove("next_cursor");
7222        let rendered = format!("{}\n", serde_json::to_string_pretty(&result).unwrap());
7223        if std::env::var_os("SUPERCODE_UPDATE_FIXTURES").is_some() {
7224            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
7225            std::fs::write(&path, &rendered).unwrap();
7226        }
7227        let committed = std::fs::read_to_string(&path).unwrap_or_default();
7228        assert_eq!(
7229            committed, rendered,
7230            "sdk/ui/stories/fixtures/hermes-discovery.json is stale — \
7231             re-run with SUPERCODE_UPDATE_FIXTURES=1"
7232        );
7233    }
7234
7235    fn pi_locator() -> SessionLocator {
7236        SessionLocator {
7237            harness: HarnessId::from(HarnessId::PI),
7238            session_id: "1e6f2a3b-0000-4000-8000-000000000001".into(),
7239            storage: StorageLocator::File {
7240                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7241                    .join("tests/fixtures/pi_session.jsonl"),
7242            },
7243        }
7244    }
7245
7246    fn opencode_locator() -> SessionLocator {
7247        let session_id = "ses_fixtureAAAAAAAAAAAAAAA1";
7248        SessionLocator {
7249            harness: HarnessId::from(HarnessId::OPENCODE),
7250            session_id: session_id.into(),
7251            storage: StorageLocator::Sqlite {
7252                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7253                    .join("tests/fixtures/opencode_fixture/opencode.db"),
7254                selector: session_id.into(),
7255            },
7256        }
7257    }
7258
7259    fn grok_locator() -> SessionLocator {
7260        SessionLocator {
7261            harness: HarnessId::from(HarnessId::GROK),
7262            session_id: "73c09283-4b33-41fa-90f1-0bcb0f7be523".into(),
7263            storage: StorageLocator::File {
7264                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7265                    .join("tests/fixtures/grok_session/chat_history.jsonl"),
7266            },
7267        }
7268    }
7269
7270    // ---- ORCH-11: `harness.v1.skills.list` -------------------------------
7271
7272    fn fixture_homes() -> Value {
7273        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7274        json!({
7275            "claude_code": fixtures.join("__absent__"),
7276            "codex": fixtures.join("__absent__"),
7277            "opencode": fixtures.join("__absent__"),
7278            "pi": fixtures.join("__absent__"),
7279            "agents": fixtures.join("__absent__"),
7280            "hermes": fixtures.join("hermes_home"),
7281            "openclaw": fixtures.join("openclaw_home"),
7282        })
7283    }
7284
7285    #[test]
7286    fn preview_search_uses_the_discovery_rpc_and_refuses_live_subscription() {
7287        let root = std::env::temp_dir().join(format!(
7288            "supercode-preview-rpc-{}-{}",
7289            std::process::id(),
7290            std::time::SystemTime::now()
7291                .duration_since(std::time::UNIX_EPOCH)
7292                .unwrap()
7293                .as_nanos()
7294        ));
7295        std::fs::create_dir_all(&root).unwrap();
7296        for id in ["first", "second"] {
7297            std::fs::write(root.join(format!("{id}.jsonl")), format!("{}\n{}\n",
7298                json!({"type": "session_meta", "payload": {"id": id, "cwd": "/workspace"}}),
7299                json!({"type": "event_msg", "payload": {"type": "agent_message", "message": "NEBULA result"}}),
7300            )).unwrap();
7301        }
7302        let mut service = HarnessSessionService::new();
7303        let query = json!({
7304            "harnesses": ["codex"], "homes": {"codex": root},
7305            "query": "nebula", "search_previews": true, "limit": 1
7306        });
7307        let first = service.handle(request(1, "harness.v1.sessions.discover", query.clone()));
7308        assert!(first.get("error").is_none(), "{first}");
7309        assert_eq!(first["result"]["receipt"]["searched_previews"], true);
7310        assert_eq!(first["result"]["receipt"]["total_matched"], 2);
7311        let mut next_query = query.clone();
7312        next_query["cursor"] = first["result"]["next_cursor"].clone();
7313        let next = service.handle(request(2, "harness.v1.sessions.discover", next_query));
7314        assert_eq!(next["result"]["receipt"]["returned"], 1);
7315        assert_eq!(next["result"]["receipt"]["total_matched"], 2);
7316        assert_eq!(next["result"]["receipt"]["truncated"], false);
7317        assert_ne!(
7318            first["result"]["sessions"][0]["locator"],
7319            next["result"]["sessions"][0]["locator"]
7320        );
7321        let refused = service.handle(request(3, "harness.v1.sessions.index.subscribe", query));
7322        assert!(
7323            refused["error"]["message"]
7324                .as_str()
7325                .unwrap()
7326                .contains("use sessions.discover"),
7327            "{refused}"
7328        );
7329        std::fs::remove_dir_all(root).unwrap();
7330    }
7331
7332    #[test]
7333    fn session_index_resize_preserves_subscription_and_rejects_invalid_requests() {
7334        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.sessions.index.resize"));
7335        let root = std::env::temp_dir().join(format!(
7336            "supercode-index-rpc-{}-{}",
7337            std::process::id(),
7338            std::time::SystemTime::now()
7339                .duration_since(std::time::UNIX_EPOCH)
7340                .unwrap()
7341                .as_nanos()
7342        ));
7343        std::fs::create_dir_all(&root).unwrap();
7344        for id in ["first", "second"] {
7345            std::fs::write(root.join(format!("{id}.jsonl")), format!(
7346                "{{\"type\":\"session_meta\",\"payload\":{{\"id\":\"{id}\",\"cwd\":\"/workspace\"}}}}\n"
7347            )).unwrap();
7348        }
7349        let mut service = HarnessSessionService::new();
7350        let opened = service.handle(request(
7351            1,
7352            "harness.v1.sessions.index.subscribe",
7353            json!({
7354                "harnesses": ["codex"], "homes": { "codex": root }, "limit": 1
7355            }),
7356        ));
7357        assert!(opened.get("error").is_none(), "{opened:#}");
7358        let subscription = opened["result"]["subscription"]
7359            .as_str()
7360            .unwrap()
7361            .to_owned();
7362        assert_eq!(opened["result"]["initial"].as_array().unwrap().len(), 1);
7363        for params in [
7364            json!({"subscription": subscription, "limit": 0}),
7365            json!({"subscription": subscription, "limit": 2049}),
7366            json!({"subscription": subscription, "limit": 2, "cursor": "not-allowed"}),
7367            json!({"subscription": "unknown", "limit": 2}),
7368        ] {
7369            let rejected = service.handle(request(2, "harness.v1.sessions.index.resize", params));
7370            assert_eq!(rejected["error"]["code"], -32602, "{rejected:#}");
7371        }
7372        for (limit, revision) in [(1, 1), (2, 2), (2, 2), (1, 3)] {
7373            let response = service.handle(request(
7374                3,
7375                "harness.v1.sessions.index.resize",
7376                json!({
7377                    "subscription": subscription, "limit": limit
7378                }),
7379            ));
7380            assert!(response.get("error").is_none(), "{response:#}");
7381            assert_eq!(response["result"]["subscription"], subscription);
7382            assert_eq!(response["result"]["revision"], revision);
7383            assert_eq!(
7384                response["result"]["initial"].as_array().unwrap().len(),
7385                limit
7386            );
7387            assert_eq!(response["result"]["receipt"]["total_matched"], 2);
7388            assert_eq!(service.index_subscriptions.len(), 1);
7389        }
7390        let removed = service.handle(request(
7391            4,
7392            "harness.v1.sessions.index.unsubscribe",
7393            json!({
7394                "subscription": subscription
7395            }),
7396        ));
7397        assert_eq!(removed["result"]["removed"], true);
7398        let stale = service.handle(request(
7399            5,
7400            "harness.v1.sessions.index.resize",
7401            json!({
7402                "subscription": subscription, "limit": 1
7403            }),
7404        ));
7405        assert_eq!(stale["error"]["code"], -32602);
7406        drop(service);
7407        std::fs::remove_dir_all(root).unwrap();
7408    }
7409
7410    fn skills_rows(params: Value) -> Vec<Value> {
7411        let response =
7412            HarnessSessionService::new().handle(request(1, "harness.v1.skills.list", params));
7413        assert!(response.get("error").is_none(), "{response:#}");
7414        response["result"].as_array().cloned().unwrap_or_default()
7415    }
7416
7417    /// The uniform row over two harnesses at once, from the harnesses' own
7418    /// skill roots: name, harness, scope, location, description, version.
7419    #[test]
7420    fn skills_list_reads_the_hermes_and_openclaw_roots() {
7421        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7422        let rows = skills_rows(json!({
7423            "homes": fixture_homes(),
7424            "cwd": fixtures.join("hermes_home"),
7425        }));
7426        let arxiv = rows
7427            .iter()
7428            .find(|row| row["name"] == json!("arxiv-search"))
7429            .unwrap_or_else(|| panic!("no arxiv row in {rows:#?}"));
7430        assert_eq!(arxiv["harness"], json!(HarnessId::HERMES));
7431        assert_eq!(arxiv["scope"], json!("user"));
7432        assert_eq!(arxiv["version"], json!("1.4.0"));
7433        assert!(arxiv["location"]
7434            .as_str()
7435            .unwrap()
7436            .ends_with("hermes_home/skills/research/arxiv"));
7437
7438        // A directory with no SKILL.md still lists, by directory name.
7439        let bare = rows
7440            .iter()
7441            .find(|row| row["name"] == json!("bare-skill"))
7442            .unwrap_or_else(|| panic!("no bare-skill row in {rows:#?}"));
7443        assert_eq!(bare["enabled"], json!(null));
7444        assert!(bare.get("description").is_none());
7445
7446        let demo = rows
7447            .iter()
7448            .find(|row| row["name"] == json!("clawhub-demo"))
7449            .unwrap_or_else(|| panic!("no clawhub-demo row in {rows:#?}"));
7450        assert_eq!(demo["harness"], json!(HarnessId::OPENCLAW));
7451        assert_eq!(demo["scope"], json!("managed"));
7452        assert_eq!(demo["enabled"], json!(false));
7453    }
7454
7455    /// Both filters select against the same rows.
7456    #[test]
7457    fn skills_list_filters_by_harness_and_scope() {
7458        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7459        let hermes = skills_rows(json!({
7460            "homes": fixture_homes(),
7461            "cwd": fixtures.join("hermes_home"),
7462            "harness": HarnessId::HERMES,
7463        }));
7464        assert!(!hermes.is_empty());
7465        assert!(hermes
7466            .iter()
7467            .all(|row| row["harness"] == json!(HarnessId::HERMES)));
7468
7469        let managed = skills_rows(json!({
7470            "homes": fixture_homes(),
7471            "cwd": fixtures.join("openclaw_home"),
7472            "harness": HarnessId::OPENCLAW,
7473            "scope": "managed",
7474        }));
7475        assert_eq!(managed.len(), 1, "{managed:#?}");
7476        assert_eq!(managed[0]["name"], json!("clawhub-demo"));
7477
7478        let bundled = skills_rows(json!({
7479            "homes": fixture_homes(),
7480            "cwd": fixtures.join("openclaw_home"),
7481            "harness": HarnessId::OPENCLAW,
7482            "scope": "bundled",
7483        }));
7484        assert!(bundled.is_empty(), "{bundled:#?}");
7485    }
7486
7487    /// A harness supercode has no skills root for is refused by name, not
7488    /// answered with an empty list.
7489    #[test]
7490    fn skills_list_refuses_an_unknown_harness() {
7491        let response = HarnessSessionService::new().handle(request(
7492            1,
7493            "harness.v1.skills.list",
7494            json!({"harness": "not-a-harness", "homes": fixture_homes()}),
7495        ));
7496        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7497        assert!(response["error"]["message"]
7498            .as_str()
7499            .unwrap()
7500            .contains("not-a-harness"));
7501    }
7502
7503    /// The method is advertised, and its SDK operation resolves it.
7504    #[test]
7505    fn skills_list_is_an_advertised_method_and_sdk_operation() {
7506        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.list"));
7507        assert_eq!(
7508            SdkOperation::from_method("harness.v1.skills.list"),
7509            Some(SdkOperation::SkillsList)
7510        );
7511    }
7512
7513    // ---- ORCH-22: `harness.v1.skills.install|remove` ----------------------
7514
7515    /// Both controlled verbs are advertised and resolve to their operation.
7516    #[test]
7517    fn skills_install_and_remove_are_advertised_methods_and_sdk_operations() {
7518        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.install"));
7519        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.remove"));
7520        assert_eq!(
7521            SdkOperation::from_method("harness.v1.skills.install"),
7522            Some(SdkOperation::SkillsInstall)
7523        );
7524        assert_eq!(
7525            SdkOperation::from_method("harness.v1.skills.remove"),
7526            Some(SdkOperation::SkillsRemove)
7527        );
7528    }
7529
7530    /// The directory door, end to end over the RPC: a local package lands in
7531    /// Claude Code's own user root and the outcome carries the operation and
7532    /// the row the ORCH-11 loader reads back.
7533    #[test]
7534    fn skills_install_and_remove_drive_the_directory_door() {
7535        let root = std::env::temp_dir().join(format!(
7536            "supercode-orch22-rpc-{}-{}",
7537            std::process::id(),
7538            std::time::SystemTime::now()
7539                .duration_since(std::time::UNIX_EPOCH)
7540                .unwrap()
7541                .as_nanos()
7542        ));
7543        let source = root.join("probe-src");
7544        std::fs::create_dir_all(&source).unwrap();
7545        std::fs::write(
7546            source.join("SKILL.md"),
7547            "---\nname: orch22-rpc\ndescription: a probe\n---\nbody\n",
7548        )
7549        .unwrap();
7550        let homes = json!({
7551            "claude_code": root.join("claude_home"),
7552            "codex": root.join("__absent__"),
7553            "opencode": root.join("__absent__"),
7554            "pi": root.join("__absent__"),
7555            "hermes": root.join("__absent__"),
7556            "openclaw": root.join("__absent__"),
7557            "agents": root.join("__absent__"),
7558        });
7559
7560        let mut service = HarnessSessionService::new();
7561        let installed = service.handle(request(
7562            1,
7563            "harness.v1.skills.install",
7564            json!({
7565                "harness": HarnessId::CLAUDE_CODE,
7566                "source": source,
7567                "scope": "user",
7568                "cwd": root,
7569                "homes": homes,
7570            }),
7571        ));
7572        let result = &installed["result"];
7573        assert_eq!(result["name"], json!("orch22-rpc"), "{installed:#}");
7574        assert_eq!(result["verb"], json!("install"));
7575        assert!(result["ran"]
7576            .as_str()
7577            .is_some_and(|ran| ran.starts_with("cp -R ")));
7578        assert_eq!(result["skill"]["scope"], json!("user"));
7579
7580        let removed = service.handle(request(
7581            2,
7582            "harness.v1.skills.remove",
7583            json!({
7584                "harness": HarnessId::CLAUDE_CODE,
7585                "name": "orch22-rpc",
7586                "scope": "user",
7587                "cwd": root,
7588                "homes": homes,
7589            }),
7590        ));
7591        assert_eq!(removed["result"]["removed"], json!(true), "{removed:#}");
7592        assert!(!root.join("claude_home/skills/orch22-rpc").exists());
7593        std::fs::remove_dir_all(&root).ok();
7594    }
7595
7596    /// OpenClaw publishes no `skills remove` at the pin, so the uniform verb
7597    /// refuses with UnsupportedAction instead of deleting files itself.
7598    #[test]
7599    fn skills_remove_refuses_openclaw_at_the_pin() {
7600        let response = HarnessSessionService::new().handle(request(
7601            1,
7602            "harness.v1.skills.remove",
7603            json!({"harness": HarnessId::OPENCLAW, "name": "clawhub-demo"}),
7604        ));
7605        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7606        assert!(response["error"]["message"]
7607            .as_str()
7608            .unwrap()
7609            .contains("no `skills remove` verb"));
7610    }
7611
7612    /// A harness with no skills root at all is refused by name, with the
7613    /// same sentence `skills.list` gives it.
7614    #[test]
7615    fn skills_install_refuses_a_harness_without_a_skills_root() {
7616        let response = HarnessSessionService::new().handle(request(
7617            1,
7618            "harness.v1.skills.install",
7619            json!({"harness": "not-a-harness", "source": "/tmp/x"}),
7620        ));
7621        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7622        assert!(response["error"]["message"]
7623            .as_str()
7624            .unwrap()
7625            .contains("not-a-harness"));
7626    }
7627
7628    // ---- ORCH-12: `harness.v1.memory.show|search` ------------------------
7629
7630    /// `HarnessHomes` for the committed fixture homes. Every root a test does
7631    /// not name is pinned at an absent path, so a read can never fall through
7632    /// to this machine's real harness homes. Note `hermes` is the `state.db`
7633    /// PATH (its parent is HERMES_HOME) and `claude_code` is the `projects`
7634    /// directory — the same contract discovery uses.
7635    fn memory_homes() -> Value {
7636        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7637        json!({
7638            "claude_code": fixtures.join("__absent__"),
7639            "codex": fixtures.join("__absent__"),
7640            "opencode": fixtures.join("__absent__"),
7641            "pi": fixtures.join("__absent__"),
7642            "grok": fixtures.join("__absent__"),
7643            "gemini": fixtures.join("__absent__"),
7644            "goose": fixtures.join("__absent__"),
7645            "supercode": fixtures.join("__absent__"),
7646            "hermes": fixtures.join("hermes_home/state.db"),
7647            "openclaw": fixtures.join("openclaw_home"),
7648        })
7649    }
7650
7651    fn memory_call_ok(method: &str, params: Value, key: &str) -> Vec<Value> {
7652        let response = HarnessSessionService::new().handle(request(1, method, params));
7653        assert!(response.get("error").is_none(), "{response:#}");
7654        assert_eq!(response["result"]["schema"], json!("supercode.memory.v1"));
7655        response["result"][key]
7656            .as_array()
7657            .cloned()
7658            .unwrap_or_default()
7659    }
7660
7661    fn memory_documents(params: Value) -> Vec<Value> {
7662        memory_call_ok("harness.v1.memory.show", params, "documents")
7663    }
7664
7665    fn memory_matches(params: Value) -> Vec<Value> {
7666        memory_call_ok("harness.v1.memory.search", params, "matches")
7667    }
7668
7669    fn find_document<'a>(rows: &'a [Value], profile: &str, name: &str) -> &'a Value {
7670        rows.iter()
7671            .find(|row| row["profile"] == profile && row["name"] == name)
7672            .unwrap_or_else(|| panic!("no `{profile}` document `{name}` in {rows:#?}"))
7673    }
7674
7675    /// Hermes: the built-in `MEMORY.md`/`USER.md` pair and the `memories/`
7676    /// topic files, for HERMES_HOME itself and for every profile home.
7677    #[test]
7678    fn memory_show_reads_the_hermes_profile_homes() {
7679        let rows = memory_documents(json!({"harness": "hermes", "homes": memory_homes()}));
7680
7681        let notes = find_document(&rows, "default", "MEMORY.md");
7682        assert_eq!(notes["harness"], "hermes");
7683        assert_eq!(notes["scope"], "user");
7684        assert!(notes["size"].as_u64().unwrap() > 0);
7685        assert!(notes["updated_at"].is_string(), "{notes:#?}");
7686        // The default answer previews the head and never the whole body.
7687        assert!(notes.get("content").is_none(), "{notes:#?}");
7688        assert_eq!(notes["truncated"], true);
7689        assert_eq!(notes["preview"].as_array().unwrap().len(), 5);
7690
7691        let user = find_document(&rows, "default", "USER.md");
7692        assert_eq!(user["scope"], "user");
7693        assert!(user["preview"]
7694            .as_array()
7695            .unwrap()
7696            .iter()
7697            .any(|line| line.as_str().unwrap().contains("neovim")));
7698
7699        let topic = find_document(&rows, "default", "memories/2026-09-01-notes.md");
7700        assert!(topic["path"]
7701            .as_str()
7702            .unwrap()
7703            .ends_with("hermes_home/memories/2026-09-01-notes.md"));
7704
7705        // Profile mode points HERMES_HOME at `<root>/profiles/<name>`.
7706        let coder = find_document(&rows, "coder", "MEMORY.md");
7707        assert_eq!(coder["scope"], "profile");
7708        assert!(coder["path"]
7709            .as_str()
7710            .unwrap()
7711            .ends_with("hermes_home/profiles/coder/MEMORY.md"));
7712    }
7713
7714    /// `full` is the only way a body crosses the wire, and `profile` narrows
7715    /// the read to one home.
7716    #[test]
7717    fn memory_show_returns_bodies_only_under_full_and_narrows_by_profile() {
7718        let rows = memory_documents(json!({
7719            "harness": "hermes",
7720            "profile": "coder",
7721            "full": true,
7722            "homes": memory_homes(),
7723        }));
7724        assert!(
7725            rows.iter().all(|row| row["profile"] == "coder"),
7726            "{rows:#?}"
7727        );
7728        let coder = find_document(&rows, "coder", "MEMORY.md");
7729        assert!(coder["content"]
7730            .as_str()
7731            .expect("full returns the body")
7732            .contains("anthropic/claude-opus-4-8"));
7733    }
7734
7735    /// OpenClaw: memory-core's files under each agent's workspace —
7736    /// `<state>/workspace` for the default agent, `<state>/workspace-<id>`
7737    /// for any other.
7738    #[test]
7739    fn memory_show_reads_the_openclaw_agent_workspaces() {
7740        let rows = memory_documents(json!({"harness": "openclaw", "homes": memory_homes()}));
7741
7742        let main = find_document(&rows, "main", "MEMORY.md");
7743        assert_eq!(main["scope"], "agent");
7744        assert!(main["path"]
7745            .as_str()
7746            .unwrap()
7747            .ends_with("openclaw_home/workspace/MEMORY.md"));
7748
7749        let topic = find_document(&rows, "main", "memory/2026-09-01-standup.md");
7750        assert!(topic["path"]
7751            .as_str()
7752            .unwrap()
7753            .ends_with("openclaw_home/workspace/memory/2026-09-01-standup.md"));
7754
7755        let design = find_document(&rows, "design", "MEMORY.md");
7756        assert!(design["path"]
7757            .as_str()
7758            .unwrap()
7759            .ends_with("openclaw_home/workspace-design/MEMORY.md"));
7760    }
7761
7762    /// Claude Code: the auto-memory directory of the project the working tree
7763    /// belongs to, keyed by the enclosing git repository.
7764    #[test]
7765    fn memory_show_reads_a_claude_code_project_auto_memory_directory() {
7766        let scratch = std::env::temp_dir().join(format!(
7767            "supercode-orch12-cc-{}-{}",
7768            std::process::id(),
7769            std::time::SystemTime::now()
7770                .duration_since(std::time::UNIX_EPOCH)
7771                .unwrap()
7772                .as_nanos()
7773        ));
7774        let project = scratch.join("repo");
7775        std::fs::create_dir_all(project.join(".git")).unwrap();
7776        // Auto-memory is shared across a repo's worktrees, so a nested
7777        // working directory must resolve to the repo's own project dir.
7778        let worktree = project.join("crates/harness");
7779        std::fs::create_dir_all(&worktree).unwrap();
7780        let slug: String = project
7781            .to_string_lossy()
7782            .chars()
7783            .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
7784            .collect();
7785        let projects = scratch.join("claude/projects");
7786        let memory = projects.join(&slug).join("memory");
7787        std::fs::create_dir_all(&memory).unwrap();
7788        std::fs::write(
7789            memory.join("MEMORY.md"),
7790            "# index\n- [build box](build-box.md) — the pinned harnesses\n",
7791        )
7792        .unwrap();
7793        std::fs::write(
7794            memory.join("build-box.md"),
7795            "hermes 0.21.0 and openclaw 2026.7.1-2 are the pins\n",
7796        )
7797        .unwrap();
7798
7799        let mut homes = memory_homes();
7800        homes["claude_code"] = json!(projects);
7801        let rows = memory_documents(json!({
7802            "harness": "claude-code",
7803            "cwd": worktree,
7804            "homes": homes,
7805        }));
7806        let index = find_document(&rows, &slug, "MEMORY.md");
7807        assert_eq!(index["harness"], "claude-code");
7808        assert_eq!(index["scope"], "project");
7809        let topic = find_document(&rows, &slug, "build-box.md");
7810        assert!(topic["preview"]
7811            .as_array()
7812            .unwrap()
7813            .iter()
7814            .any(|line| line.as_str().unwrap().contains("2026.7.1-2")));
7815
7816        let hits = memory_matches(json!({
7817            "harness": "claude-code",
7818            "query": "pinned harnesses",
7819            "cwd": worktree,
7820            "homes": homes,
7821        }));
7822        assert_eq!(hits.len(), 1, "{hits:#?}");
7823        assert_eq!(hits[0]["name"], "MEMORY.md");
7824        assert_eq!(hits[0]["line"], 2);
7825
7826        let _ = std::fs::remove_dir_all(&scratch);
7827    }
7828
7829    /// A config-less OpenClaw install declares no default agent, but
7830    /// memory-core still resolves ONE agent to the default `workspace`
7831    /// directory — the same `main`-then-first convention the profile rows
7832    /// use. Measured against `openclaw memory status` on the pinned CLI
7833    /// (`docs/interop/research/orch12-memory-receipt-2026-09-03.json`).
7834    #[test]
7835    fn memory_show_resolves_the_default_workspace_without_an_openclaw_config() {
7836        let state = std::env::temp_dir().join(format!(
7837            "supercode-orch12-oc-{}-{}",
7838            std::process::id(),
7839            std::time::SystemTime::now()
7840                .duration_since(std::time::UNIX_EPOCH)
7841                .unwrap()
7842                .as_nanos()
7843        ));
7844        // No `openclaw.json`: only the agent home the gateway creates.
7845        std::fs::create_dir_all(state.join("agents/main/agent")).unwrap();
7846        std::fs::create_dir_all(state.join("workspace")).unwrap();
7847        std::fs::write(
7848            state.join("workspace/MEMORY.md"),
7849            "the gateway websocket needs credentials\n",
7850        )
7851        .unwrap();
7852
7853        let mut homes = memory_homes();
7854        homes["openclaw"] = json!(state);
7855        let rows = memory_documents(json!({"harness": "openclaw", "homes": homes}));
7856        assert_eq!(rows.len(), 1, "{rows:#?}");
7857        let row = find_document(&rows, "main", "MEMORY.md");
7858        assert_eq!(row["scope"], "agent");
7859        assert!(row["path"]
7860            .as_str()
7861            .unwrap()
7862            .ends_with("workspace/MEMORY.md"));
7863
7864        let _ = std::fs::remove_dir_all(&state);
7865    }
7866
7867    /// Search is a plain scan over the same documents: a hit carries the
7868    /// path, line and excerpt; a miss is an empty list, not an error.
7869    #[test]
7870    fn memory_search_reports_hits_by_line_and_misses_as_empty() {
7871        let hit = memory_matches(json!({
7872            "harness": "hermes",
7873            "query": "NEOVIM",
7874            "homes": memory_homes(),
7875        }));
7876        assert_eq!(hit.len(), 1, "{hit:#?}");
7877        assert_eq!(hit[0]["harness"], "hermes");
7878        assert_eq!(hit[0]["name"], "USER.md");
7879        assert_eq!(hit[0]["scope"], "user");
7880        assert_eq!(hit[0]["line"], 5);
7881        assert!(hit[0]["excerpt"].as_str().unwrap().contains("neovim"));
7882
7883        // A regular expression reaches the same lines.
7884        let regex = memory_matches(json!({
7885            "harness": "hermes",
7886            "query": "neo(vim|vi)",
7887            "regex": true,
7888            "homes": memory_homes(),
7889        }));
7890        assert_eq!(regex.len(), 1, "{regex:#?}");
7891
7892        let miss = memory_matches(json!({
7893            "harness": "hermes",
7894            "query": "no-memory-line-says-this",
7895            "homes": memory_homes(),
7896        }));
7897        assert!(miss.is_empty(), "{miss:#?}");
7898    }
7899
7900    /// The uniform-verb contract: a harness with no memory store at the pin
7901    /// is refused by name, and `session` only selects a Claude Code project.
7902    #[test]
7903    fn memory_refuses_harnesses_without_a_store_and_misplaced_session_scoping() {
7904        for method in ["harness.v1.memory.show", "harness.v1.memory.search"] {
7905            let response = HarnessSessionService::new().handle(request(
7906                1,
7907                method,
7908                json!({"harness": "codex", "query": "anything", "homes": memory_homes()}),
7909            ));
7910            assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7911            assert!(response["error"]["message"]
7912                .as_str()
7913                .unwrap()
7914                .contains("codex"));
7915        }
7916
7917        let response = HarnessSessionService::new().handle(request(
7918            1,
7919            "harness.v1.memory.show",
7920            json!({"harness": "hermes", "session": "abc", "homes": memory_homes()}),
7921        ));
7922        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7923
7924        // `harness` is not optional: memory documents are the user's prose.
7925        let response = HarnessSessionService::new().handle(request(
7926            1,
7927            "harness.v1.memory.show",
7928            json!({"homes": memory_homes()}),
7929        ));
7930        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7931    }
7932
7933    /// Both methods are advertised, and their SDK operations resolve them.
7934    #[test]
7935    fn memory_methods_are_advertised_and_map_to_sdk_operations() {
7936        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.show"));
7937        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.search"));
7938        assert_eq!(
7939            SdkOperation::from_method("harness.v1.memory.show"),
7940            Some(SdkOperation::MemoryShow)
7941        );
7942        assert_eq!(
7943            SdkOperation::from_method("harness.v1.memory.search"),
7944            Some(SdkOperation::MemorySearch)
7945        );
7946    }
7947
7948    // ---- ORCH-9: `harness.v1.approvals.list` -----------------------------
7949
7950    /// A runtime that raises one protocol request and then goes quiet, so a
7951    /// single poll delivers the request without closing the connection.
7952    struct RequestingRuntime {
7953        handle: RuntimeHandle,
7954        events: std::collections::VecDeque<HarnessEvent>,
7955        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7956    }
7957
7958    #[async_trait]
7959    impl RuntimeConnection for RequestingRuntime {
7960        fn handle(&self) -> &RuntimeHandle {
7961            &self.handle
7962        }
7963
7964        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
7965            unreachable!("this runtime only raises requests")
7966        }
7967
7968        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
7969            match self.events.pop_front() {
7970                Some(event) => Ok(Some(event)),
7971                // Quiet, not closed: `poll_sdk_events` times out and leaves
7972                // the connection open, the way a runtime blocked on a
7973                // permission request behaves.
7974                None => std::future::pending().await,
7975            }
7976        }
7977
7978        async fn interrupt(&mut self) -> crate::Result<()> {
7979            Ok(())
7980        }
7981
7982        async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
7983            // Both halves are recorded: ORCH-20 has to prove not just that the
7984            // right request was answered but that the door received its own
7985            // reply envelope.
7986            self.answered
7987                .lock()
7988                .unwrap_or_else(std::sync::PoisonError::into_inner)
7989                .push(json!({"request_id": request_id, "response": response}));
7990            Ok(())
7991        }
7992
7993        async fn close(&mut self) -> crate::Result<()> {
7994            Ok(())
7995        }
7996    }
7997
7998    fn requesting_runtime(
7999        harness: &str,
8000        events: Vec<HarnessEvent>,
8001        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
8002    ) -> Box<dyn RuntimeConnection> {
8003        requesting_runtime_named(harness, "hermes-live-session", events, answered)
8004    }
8005
8006    fn requesting_runtime_named(
8007        harness: &str,
8008        runtime_id: &str,
8009        events: Vec<HarnessEvent>,
8010        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
8011    ) -> Box<dyn RuntimeConnection> {
8012        Box::new(RequestingRuntime {
8013            handle: RuntimeHandle {
8014                harness: HarnessId::from(harness),
8015                runtime_id: runtime_id.into(),
8016                endpoint: RuntimeEndpoint::LocalProcess {
8017                    pid: None,
8018                    command: vec!["hermes-acp".into()],
8019                    protocol: "acp".into(),
8020                },
8021            },
8022            events: events.into(),
8023            answered,
8024        })
8025    }
8026
8027    fn permission_event(id: u64, title: &str) -> HarnessEvent {
8028        HarnessEvent {
8029            sequence: None,
8030            kind: "session/request_permission".into(),
8031            payload: json!({
8032                "jsonrpc": "2.0",
8033                "id": id,
8034                "method": "session/request_permission",
8035                "params": {
8036                    "sessionId": "hermes-live-session",
8037                    "toolCall": {"toolCallId": "call-1", "title": title, "kind": "execute"},
8038                    "options": [
8039                        {"optionId": "allow_once", "name": "Allow once", "kind": "allow_once"},
8040                        {"optionId": "allow_for_session", "name": "Allow for session", "kind": "allow_always"},
8041                        {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
8042                    ],
8043                },
8044            }),
8045        }
8046    }
8047
8048    fn approvals(service: &mut HarnessSessionService, params: Value) -> Value {
8049        let response = service.handle(request(1, "harness.v1.approvals.list", params));
8050        assert!(response.get("error").is_none(), "{response:#}");
8051        response["result"].clone()
8052    }
8053
8054    /// ORC-2 dev/01: the same uniform loop over the CLAUDE CODE door. The
8055    /// `can_use_tool` control request the CLI raises to its registered
8056    /// permission handler lists as one pending row, `approvals.resolve <id>
8057    /// allow_once` sends the `{behavior}` result the CLI accepts through
8058    /// `runtimes.respond`, and the row is gone. The frame is the one claude
8059    /// 2.1.258 wrote, transcribed from
8060    /// `docs/interop/research/orc2-claude-respond-receipt-2026-09-04.json`.
8061    #[tokio::test]
8062    async fn a_claude_code_permission_request_lists_and_resolves_on_the_uniform_door() {
8063        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8064        let mut service = HarnessSessionService::new();
8065        service.runtimes.insert(
8066            "runtime-cc".into(),
8067            requesting_runtime_named(
8068                HarnessId::CLAUDE_CODE,
8069                "claude-live-session",
8070                vec![HarnessEvent {
8071                    sequence: None,
8072                    kind: "control_request".into(),
8073                    payload: json!({
8074                        "type": "control_request",
8075                        "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
8076                        "request": {
8077                            "subtype": "can_use_tool",
8078                            "tool_name": "Bash",
8079                            "display_name": "Bash",
8080                            "input": {"command": "touch probe-artifact.txt"},
8081                            "tool_use_id": "toolu_mock_1",
8082                        },
8083                    }),
8084                }],
8085                answered.clone(),
8086            ),
8087        );
8088
8089        let notifications = service.poll_runtimes().await;
8090        assert_eq!(notifications.len(), 1, "{notifications:#?}");
8091
8092        let rows = approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}));
8093        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
8094        let row = &rows[0];
8095        assert_eq!(row["id"], "runtime-cc/053f8a2d-3445-4011-a259-4261b31c7326");
8096        assert_eq!(row["harness"], HarnessId::CLAUDE_CODE);
8097        assert_eq!(row["status"], "pending");
8098        assert_eq!(row["subject"], "Bash touch probe-artifact.txt");
8099        assert_eq!(row["runtime_id"], "claude-live-session");
8100        assert_eq!(
8101            row["options"]
8102                .as_array()
8103                .unwrap()
8104                .iter()
8105                .map(|option| option["id"].as_str().unwrap())
8106                .collect::<Vec<_>>(),
8107            vec!["allow", "deny"],
8108        );
8109
8110        let response = resolve(
8111            &mut service,
8112            json!({"id": row["id"], "decision": "allow_once"}),
8113        )
8114        .await;
8115        assert!(response.get("error").is_none(), "{response:#}");
8116        assert_eq!(response["result"]["option_id"], "allow");
8117        assert_eq!(
8118            answered
8119                .lock()
8120                .unwrap_or_else(std::sync::PoisonError::into_inner)
8121                .as_slice(),
8122            &[json!({
8123                "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
8124                "response": {"behavior": "allow"},
8125            })],
8126        );
8127        assert_eq!(
8128            approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}))
8129                .as_array()
8130                .map(Vec::len),
8131            Some(0),
8132        );
8133    }
8134
8135    /// dev/01: a live ACP permission request raised on a driven runtime is
8136    /// listable while the turn is blocked on it, and stops being listable
8137    /// the moment `runtimes.respond` answers it.
8138    #[tokio::test]
8139    async fn a_live_permission_request_lists_until_it_is_answered() {
8140        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8141        let mut service = HarnessSessionService::new();
8142        service.runtimes.insert(
8143            "runtime-1".into(),
8144            requesting_runtime(
8145                HarnessId::HERMES,
8146                vec![permission_event(7, "rm -rf build")],
8147                answered.clone(),
8148            ),
8149        );
8150
8151        let notifications = service.poll_runtimes().await;
8152        assert_eq!(notifications.len(), 1, "{notifications:#?}");
8153
8154        let rows = approvals(&mut service, json!({}));
8155        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
8156        let row = &rows[0];
8157        assert_eq!(row["id"], "runtime-1/7");
8158        assert_eq!(row["harness"], HarnessId::HERMES);
8159        assert_eq!(row["kind"], "live");
8160        assert_eq!(row["status"], "pending");
8161        assert_eq!(row["subject"], "rm -rf build");
8162        assert_eq!(row["session_id"], "hermes-live-session");
8163        assert_eq!(row["runtime_id"], "hermes-live-session");
8164        assert!(row["requested_at_ms"].as_i64().is_some(), "{row:#}");
8165        assert!(
8166            row["age_ms"].as_i64().is_some_and(|age| age >= 0),
8167            "{row:#}"
8168        );
8169        assert_eq!(
8170            row["options"]
8171                .as_array()
8172                .unwrap()
8173                .iter()
8174                .map(|option| option["id"].as_str().unwrap())
8175                .collect::<Vec<_>>(),
8176            vec!["allow_once", "allow_for_session", "deny"],
8177        );
8178
8179        // The filters select against the same rows.
8180        assert_eq!(
8181            approvals(&mut service, json!({"harness": HarnessId::HERMES}))
8182                .as_array()
8183                .map(Vec::len),
8184            Some(1),
8185        );
8186        assert_eq!(
8187            approvals(&mut service, json!({"session": "some-other-session"}))
8188                .as_array()
8189                .map(Vec::len),
8190            Some(0),
8191        );
8192
8193        let response = service
8194            .handle_async(request(
8195                2,
8196                "harness.v1.runtimes.respond",
8197                json!({
8198                    "connection": "runtime-1",
8199                    "request_id": 7,
8200                    "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8201                }),
8202            ))
8203            .await;
8204        assert!(response.get("error").is_none(), "{response:#}");
8205        assert_eq!(
8206            answered
8207                .lock()
8208                .unwrap_or_else(std::sync::PoisonError::into_inner)
8209                .as_slice(),
8210            &[json!({
8211                "request_id": 7,
8212                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8213            })],
8214        );
8215
8216        let rows = approvals(&mut service, json!({}));
8217        assert_eq!(rows.as_array().map(Vec::len), Some(0), "{rows:#}");
8218    }
8219
8220    /// dev/01: supercode's own queued subagent approvals list through the
8221    /// same door, carrying the outcome the record holds.
8222    #[test]
8223    fn queued_subagent_approvals_list_through_the_same_door() {
8224        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
8225            crate::subagents::QueuedApproval {
8226                child_agent_id: "child-7".into(),
8227                tool: "shell".into(),
8228                subject: Some("cargo publish --dry-run".into()),
8229                queued_at_ms: 1,
8230                outcome: None,
8231            },
8232            crate::subagents::QueuedApproval {
8233                child_agent_id: "child-8".into(),
8234                tool: "write_file".into(),
8235                subject: None,
8236                queued_at_ms: 2,
8237                outcome: Some(crate::subagents::QueuedApprovalOutcome::Denied),
8238            },
8239        ]));
8240        let mut service = HarnessSessionService::new();
8241        service.observe_subagent_approvals(queue);
8242
8243        let rows = approvals(&mut service, json!({}));
8244        assert_eq!(rows.as_array().map(Vec::len), Some(2), "{rows:#}");
8245        assert_eq!(rows[0]["id"], "supercode/subagent/child-7/1/0");
8246        assert_eq!(rows[0]["harness"], HarnessId::SUPERCODE);
8247        assert_eq!(rows[0]["status"], "pending");
8248        assert_eq!(rows[0]["subject"], "shell cargo publish --dry-run");
8249        assert_eq!(rows[1]["status"], "denied");
8250        assert!(rows[1]["options"].as_array().unwrap().is_empty());
8251
8252        // `--session` addresses a subagent row by its child agent id.
8253        let only = approvals(&mut service, json!({"session": "child-8"}));
8254        assert_eq!(only.as_array().map(Vec::len), Some(1), "{only:#}");
8255        assert_eq!(only[0]["id"], "supercode/subagent/child-8/2/1");
8256    }
8257
8258    /// The uniform-verb contract: an id whose runtime door cannot carry a
8259    /// protocol request is refused BY NAME rather than answered with an empty
8260    /// list. Since ORC-2 gave Claude Code a permission-response primitive
8261    /// every registered harness can carry one, so the refusal is exercised on
8262    /// an unknown id — and the registered ids are asserted to be accepted.
8263    #[test]
8264    fn approvals_list_refuses_a_harness_that_cannot_carry_a_request() {
8265        let response = HarnessSessionService::new().handle(request(
8266            1,
8267            "harness.v1.approvals.list",
8268            json!({"harness": "not-a-harness"}),
8269        ));
8270        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
8271        assert!(response["error"]["message"]
8272            .as_str()
8273            .unwrap()
8274            .contains("not-a-harness"));
8275        for harness in [HarnessId::CLAUDE_CODE, HarnessId::CODEX] {
8276            let response = HarnessSessionService::new().handle(request(
8277                1,
8278                "harness.v1.approvals.list",
8279                json!({"harness": harness}),
8280            ));
8281            assert!(response.get("error").is_none(), "{harness}: {response:#}");
8282        }
8283    }
8284
8285    /// The method is advertised, its SDK operation resolves it, and the
8286    /// registry reports the concept as observed for every harness whose
8287    /// runtime door can carry a request.
8288    #[test]
8289    fn approvals_list_is_an_advertised_method_and_an_observed_tier() {
8290        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.list"));
8291        assert_eq!(
8292            SdkOperation::from_method("harness.v1.approvals.list"),
8293            Some(SdkOperation::ApprovalsList)
8294        );
8295        let registry = harness_support_registry();
8296        for id in [
8297            HarnessId::HERMES,
8298            HarnessId::OPENCLAW,
8299            HarnessId::CODEX,
8300            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8301            // pending_request concept joins the other driven doors.
8302            HarnessId::CLAUDE_CODE,
8303        ] {
8304            let concept = registry
8305                .harnesses
8306                .iter()
8307                .find(|harness| harness.id.as_str() == id)
8308                .unwrap()
8309                .orchestration
8310                .concepts
8311                .iter()
8312                .find(|concept| concept.concept == "pending_request")
8313                .unwrap();
8314            assert_eq!(concept.observed, crate::ImplementationKind::BuiltIn, "{id}");
8315            assert!(concept
8316                .methods
8317                .iter()
8318                .any(|method| method == "harness.v1.approvals.list"));
8319        }
8320    }
8321
8322    // ---- ORCH-20: `harness.v1.approvals.resolve` -------------------------
8323
8324    async fn resolve(service: &mut HarnessSessionService, params: Value) -> Value {
8325        service
8326            .handle_async(request(3, "harness.v1.approvals.resolve", params))
8327            .await
8328    }
8329
8330    /// dev/01: the whole loop on a driven runtime — list one pending row,
8331    /// answer it by ROW ID with one uniform decision, and see it gone. The
8332    /// door receives its own ACP envelope carrying the option it enumerated.
8333    #[tokio::test]
8334    async fn a_listed_row_resolves_with_one_uniform_decision_and_then_is_gone() {
8335        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8336        let mut service = HarnessSessionService::new();
8337        service.runtimes.insert(
8338            "runtime-1".into(),
8339            requesting_runtime(
8340                HarnessId::HERMES,
8341                vec![permission_event(7, "rm -rf build")],
8342                answered.clone(),
8343            ),
8344        );
8345        service.poll_runtimes().await;
8346
8347        let rows = approvals(&mut service, json!({}));
8348        assert_eq!(rows[0]["id"], "runtime-1/7");
8349
8350        let response = resolve(
8351            &mut service,
8352            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8353        )
8354        .await;
8355        assert!(response.get("error").is_none(), "{response:#}");
8356        assert_eq!(
8357            response["result"],
8358            json!({
8359                "id": "runtime-1/7",
8360                "decision": "allow_once",
8361                "option_id": "allow_once",
8362                "resolved": true,
8363            }),
8364        );
8365        // The harness's own door was called with its own envelope.
8366        assert_eq!(
8367            answered
8368                .lock()
8369                .unwrap_or_else(std::sync::PoisonError::into_inner)
8370                .as_slice(),
8371            &[json!({
8372                "request_id": 7,
8373                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8374            })],
8375        );
8376        // And the row is gone, the same way `runtimes.respond` drops it.
8377        assert_eq!(
8378            approvals(&mut service, json!({})).as_array().map(Vec::len),
8379            Some(0),
8380        );
8381        // Answering it twice is an honest miss, not a silent success.
8382        let response = resolve(
8383            &mut service,
8384            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8385        )
8386        .await;
8387        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8388    }
8389
8390    /// dev/01: deny travels the same path and picks the option the request
8391    /// itself classified as a refusal.
8392    #[tokio::test]
8393    async fn deny_selects_the_requests_own_reject_option() {
8394        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8395        let mut service = HarnessSessionService::new();
8396        service.runtimes.insert(
8397            "runtime-1".into(),
8398            requesting_runtime(
8399                HarnessId::HERMES,
8400                vec![permission_event(11, "git push --force")],
8401                answered.clone(),
8402            ),
8403        );
8404        service.poll_runtimes().await;
8405
8406        let response = resolve(
8407            &mut service,
8408            json!({"id": "runtime-1/11", "decision": "deny"}),
8409        )
8410        .await;
8411        assert!(response.get("error").is_none(), "{response:#}");
8412        // `deny` is the optionId whose ACP `kind` is `reject_once`.
8413        assert_eq!(response["result"]["option_id"], "deny");
8414        assert_eq!(
8415            answered
8416                .lock()
8417                .unwrap_or_else(std::sync::PoisonError::into_inner)[0]["response"],
8418            json!({"outcome": {"outcome": "selected", "optionId": "deny"}}),
8419        );
8420        assert_eq!(
8421            approvals(&mut service, json!({})).as_array().map(Vec::len),
8422            Some(0),
8423        );
8424    }
8425
8426    /// dev/01: a decision this request does not offer is refused by name,
8427    /// listing the ones it does — never silently downgraded to a neighbour.
8428    #[tokio::test]
8429    async fn a_decision_the_request_does_not_offer_is_refused_with_the_offered_ones() {
8430        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8431        let mut service = HarnessSessionService::new();
8432        let mut event = permission_event(3, "rm -rf build");
8433        // A request offering only allow-once and deny, as hermes 0.21.0's
8434        // edit-approval layer raises one.
8435        event.payload["params"]["options"] = json!([
8436            {"optionId": "allow_once", "name": "Allow edit", "kind": "allow_once"},
8437            {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
8438        ]);
8439        service.runtimes.insert(
8440            "runtime-1".into(),
8441            requesting_runtime(HarnessId::HERMES, vec![event], answered.clone()),
8442        );
8443        service.poll_runtimes().await;
8444
8445        let response = resolve(
8446            &mut service,
8447            json!({"id": "runtime-1/3", "decision": "allow_always"}),
8448        )
8449        .await;
8450        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8451        let message = response["error"]["message"].as_str().unwrap();
8452        assert!(message.contains("allow_always"), "{message}");
8453        assert!(message.contains("allow_once, deny"), "{message}");
8454        // Nothing was sent, and the request is still waiting for an answer.
8455        assert!(answered
8456            .lock()
8457            .unwrap_or_else(std::sync::PoisonError::into_inner)
8458            .is_empty());
8459        assert_eq!(
8460            approvals(&mut service, json!({})).as_array().map(Vec::len),
8461            Some(1),
8462        );
8463    }
8464
8465    /// dev/01: supercode's own queued subagent row is addressable but not
8466    /// answerable through this door — it is the parent's audit copy of a
8467    /// request its own handler answers. Refused by name, never a no-op.
8468    #[tokio::test]
8469    async fn a_queued_subagent_row_is_refused_by_name_rather_than_silently_answered() {
8470        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
8471            crate::subagents::QueuedApproval {
8472                child_agent_id: "child-7".into(),
8473                tool: "shell".into(),
8474                subject: Some("cargo publish --dry-run".into()),
8475                queued_at_ms: 1,
8476                outcome: None,
8477            },
8478        ]));
8479        let mut service = HarnessSessionService::new();
8480        service.observe_subagent_approvals(queue.clone());
8481        let row = approvals(&mut service, json!({}))[0]["id"]
8482            .as_str()
8483            .unwrap()
8484            .to_string();
8485        assert_eq!(row, "supercode/subagent/child-7/1/0");
8486
8487        let response = resolve(&mut service, json!({"id": row, "decision": "allow_once"})).await;
8488        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8489        let message = response["error"]["message"].as_str().unwrap();
8490        assert!(message.contains("queued subagent record"), "{message}");
8491        assert!(message.contains("request"), "{message}");
8492        // The audit record is untouched: nothing pretended to answer it.
8493        assert!(queue
8494            .lock()
8495            .unwrap_or_else(std::sync::PoisonError::into_inner)[0]
8496            .outcome
8497            .is_none());
8498    }
8499
8500    /// An id nobody is holding, and a call that names no decision at all,
8501    /// both fail with a message that says why.
8502    #[tokio::test]
8503    async fn an_unknown_row_and_a_missing_decision_are_both_named() {
8504        let mut service = HarnessSessionService::new();
8505        let response = resolve(
8506            &mut service,
8507            json!({"id": "runtime-9/4", "decision": "deny"}),
8508        )
8509        .await;
8510        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8511        assert!(response["error"]["message"]
8512            .as_str()
8513            .unwrap()
8514            .contains("runtime-9/4"));
8515
8516        let response = resolve(&mut service, json!({"id": "runtime-9/4"})).await;
8517        let message = response["error"]["message"].as_str().unwrap();
8518        assert!(
8519            message.contains("allow_once | allow_always | deny"),
8520            "{message}"
8521        );
8522
8523        let response = resolve(
8524            &mut service,
8525            json!({"id": "runtime-9/4", "decision": "deny", "option_id": "deny"}),
8526        )
8527        .await;
8528        assert!(response["error"]["message"]
8529            .as_str()
8530            .unwrap()
8531            .contains("not both"));
8532    }
8533
8534    /// The method is advertised, its SDK operation resolves it, and every
8535    /// harness whose runtime door can carry a request reports it on the
8536    /// CONTROLLED tier beside `runtimes.respond`.
8537    #[test]
8538    fn approvals_resolve_is_an_advertised_method_and_a_controlled_tier() {
8539        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.resolve"));
8540        assert_eq!(
8541            SdkOperation::from_method("harness.v1.approvals.resolve"),
8542            Some(SdkOperation::ApprovalsResolve)
8543        );
8544        assert_eq!(
8545            SdkOperation::ApprovalsResolve.action_name(),
8546            "approvals_resolve"
8547        );
8548        let registry = harness_support_registry();
8549        for id in [
8550            HarnessId::HERMES,
8551            HarnessId::OPENCLAW,
8552            HarnessId::CODEX,
8553            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8554            // pending_request concept joins the other driven doors.
8555            HarnessId::CLAUDE_CODE,
8556        ] {
8557            let concept = registry
8558                .harnesses
8559                .iter()
8560                .find(|harness| harness.id.as_str() == id)
8561                .unwrap()
8562                .orchestration
8563                .concepts
8564                .iter()
8565                .find(|concept| concept.concept == "pending_request")
8566                .unwrap();
8567            assert_eq!(
8568                concept.controlled,
8569                crate::ImplementationKind::BuiltIn,
8570                "{id}"
8571            );
8572            assert!(
8573                concept
8574                    .methods
8575                    .iter()
8576                    .any(|method| method == "harness.v1.approvals.resolve"),
8577                "{id}"
8578            );
8579        }
8580    }
8581
8582    #[test]
8583    fn capabilities_are_explicit_and_versioned() {
8584        let mut service = HarnessSessionService::new();
8585        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
8586        assert_eq!(response["result"]["version"], HARNESS_SERVICE_VERSION);
8587        assert_eq!(
8588            response["result"]["sdk"]["schema_version"],
8589            crate::SDK_SCHEMA_VERSION
8590        );
8591        assert_eq!(
8592            response["result"]["sdk"]["operations"]
8593                .as_array()
8594                .unwrap()
8595                .len(),
8596            SdkOperation::ALL.len()
8597        );
8598        assert_eq!(
8599            response["result"]["harnesses"].as_array().unwrap().len(),
8600            11
8601        );
8602        assert!(response["result"]["harnesses"]
8603            .as_array()
8604            .unwrap()
8605            .iter()
8606            .any(|harness| harness == HarnessId::GROK));
8607        assert!(response["result"]["harnesses"]
8608            .as_array()
8609            .unwrap()
8610            .iter()
8611            .any(|harness| harness == HarnessId::GOOSE));
8612    }
8613
8614    #[test]
8615    fn handshake_health_uses_protocol_liveness_not_stderr_severity() {
8616        let noisy_stderr = crate::HarnessEvent {
8617            sequence: None,
8618            kind: "transport_stderr".into(),
8619            payload: json!({"line": "ERROR optional worker AuthorizationRequired"}),
8620        };
8621        assert_eq!(handshake_event_failure(&noisy_stderr), None);
8622
8623        let closed = crate::HarnessEvent {
8624            sequence: None,
8625            kind: "transport_closed".into(),
8626            payload: json!({}),
8627        };
8628        assert!(handshake_event_failure(&closed).is_some());
8629    }
8630
8631    #[tokio::test]
8632    async fn runtime_eof_is_notified_and_removed_for_raw_and_explicit_close() {
8633        let mut service = HarnessSessionService::new();
8634        service
8635            .runtimes
8636            .insert("raw-eof".into(), ending_runtime(None));
8637        service.runtimes.insert(
8638            "explicit-close".into(),
8639            ending_runtime(Some(HarnessEvent {
8640                sequence: None,
8641                kind: "transport_closed".into(),
8642                payload: json!({"message": "native transport exited"}),
8643            })),
8644        );
8645
8646        let notifications = service.poll_runtimes().await;
8647
8648        assert_eq!(notifications.len(), 2);
8649        assert!(notifications
8650            .iter()
8651            .all(|notification| { notification["params"]["event"]["kind"] == "transport_closed" }));
8652        assert!(notifications.iter().all(|notification| {
8653            notification["params"]["session_id"] == "ending-session"
8654                && notification["params"]["connection"].is_string()
8655        }));
8656        let mut sequences = notifications
8657            .iter()
8658            .filter_map(|notification| notification["params"]["sequence"].as_u64())
8659            .collect::<Vec<_>>();
8660        sequences.sort_unstable();
8661        assert_eq!(sequences, vec![1, 2]);
8662        assert!(service.runtimes.is_empty());
8663    }
8664
8665    #[test]
8666    fn support_report_and_grok_default_binding_share_the_registry() {
8667        let mut service = HarnessSessionService::new();
8668        let response = service.handle(request(1, "harness.v1.support.report", json!({})));
8669        assert_eq!(response["result"]["schema"], crate::SUPPORT_REGISTRY_SCHEMA);
8670        let params = RuntimeBackendParams {
8671            harness: HarnessId::from(HarnessId::GROK),
8672            protocol: None,
8673            launch: None,
8674            base_url: None,
8675            policy: RuntimePolicy::Default,
8676        };
8677        let backend = match runtime_backend(&params) {
8678            Ok(backend) => backend,
8679            Err(_) => panic!("Grok should bind through its registered ACP launch"),
8680        };
8681        assert_eq!(backend.harness().as_str(), HarnessId::GROK);
8682        assert!(backend.capabilities().start_session);
8683        let registered = harness_support_registry()
8684            .harnesses
8685            .into_iter()
8686            .find(|harness| harness.id.as_str() == HarnessId::GROK)
8687            .and_then(|harness| harness.runtime.default_launch)
8688            .unwrap();
8689        assert!(!registered
8690            .arguments
8691            .iter()
8692            .any(|argument| argument == "--always-approve"));
8693        assert!(runtime_launch(&params).is_none());
8694
8695        let yolo = RuntimeBackendParams {
8696            policy: RuntimePolicy::Yolo,
8697            ..params
8698        };
8699        assert!(runtime_launch(&yolo)
8700            .unwrap()
8701            .arguments
8702            .iter()
8703            .any(|argument| argument == "--always-approve"));
8704
8705        let mismatched_protocol = RuntimeBackendParams {
8706            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8707            protocol: Some("acp".into()),
8708            launch: None,
8709            base_url: None,
8710            policy: RuntimePolicy::Default,
8711        };
8712        assert!(runtime_backend(&mismatched_protocol).is_err());
8713    }
8714
8715    #[test]
8716    fn load_follow_and_unfollow_share_the_same_locator() {
8717        let mut service = HarnessSessionService::new();
8718        let locator = pi_locator();
8719        let loaded = service.handle(request(
8720            1,
8721            "harness.v1.sessions.load",
8722            json!({"locator": locator}),
8723        ));
8724        assert_eq!(
8725            loaded["result"]["session"]["session_id"],
8726            locator.session_id
8727        );
8728
8729        let followed = service.handle(request(
8730            2,
8731            "harness.v1.sessions.follow",
8732            json!({"locator": locator}),
8733        ));
8734        assert_eq!(followed["result"]["subscription"], "sub-1");
8735        assert_eq!(followed["result"]["initial"]["type"], "session_snapshot");
8736        assert!(service.poll().is_empty());
8737
8738        let unfollowed = service.handle(request(
8739            3,
8740            "harness.v1.sessions.unfollow",
8741            json!({"subscription": "sub-1"}),
8742        ));
8743        assert_eq!(unfollowed["result"]["removed"], true);
8744    }
8745
8746    #[test]
8747    fn bounded_read_view_excludes_subagents_and_keeps_only_the_tail() {
8748        let temp = std::env::temp_dir().join(format!(
8749            "supercode-bounded-view-{}-{}",
8750            std::process::id(),
8751            generated_session_id()
8752        ));
8753        let path = temp.join("parent.jsonl");
8754        let subagents = temp.join("parent/subagents");
8755        std::fs::create_dir_all(&subagents).unwrap();
8756        let long_last = "x".repeat(300);
8757        let parent_records = [
8758            json!({"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":"first"}}),
8759            json!({"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"middle"}]}}),
8760            json!({"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":long_last}}),
8761        ];
8762        std::fs::write(
8763            &path,
8764            format!(
8765                "{}\n",
8766                parent_records
8767                    .iter()
8768                    .map(Value::to_string)
8769                    .collect::<Vec<_>>()
8770                    .join("\n")
8771            ),
8772        )
8773        .unwrap();
8774        std::fs::write(
8775            subagents.join("agent-child.jsonl"),
8776            concat!(
8777                r#"{"type":"user","uuid":"cu","parentUuid":null,"agentId":"child","message":{"role":"user","content":"child work"}}"#,
8778                "\n",
8779            ),
8780        )
8781        .unwrap();
8782        let locator = SessionLocator {
8783            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8784            session_id: "parent".into(),
8785            storage: StorageLocator::File { path },
8786        };
8787        let mut service = HarnessSessionService::new();
8788
8789        let complete = service.handle(request(
8790            1,
8791            "harness.v1.sessions.load",
8792            json!({"locator": locator}),
8793        ));
8794        assert_eq!(
8795            complete["result"]["session"]["subagents"]
8796                .as_array()
8797                .unwrap()
8798                .len(),
8799            1
8800        );
8801
8802        let bounded = service.handle(request(
8803            2,
8804            "harness.v1.sessions.load",
8805            json!({
8806                "locator": locator,
8807                "view": {
8808                    "tail_messages": 1,
8809                    "max_message_chars": 256,
8810                    "include_subagents": false
8811                },
8812            }),
8813        ));
8814        let session = &bounded["result"]["session"];
8815        assert!(session["subagents"].as_array().unwrap().is_empty());
8816        assert_eq!(session["messages"].as_array().unwrap().len(), 1);
8817        assert_eq!(
8818            session["messages"][0]["content"],
8819            format!("{}\n…", "x".repeat(256))
8820        );
8821
8822        let followed = service.handle(request(
8823            3,
8824            "harness.v1.sessions.follow",
8825            json!({
8826                "locator": locator,
8827                "view": {
8828                    "tail_messages": 1,
8829                    "max_message_chars": 256,
8830                    "include_subagents": false
8831                },
8832            }),
8833        ));
8834        let initial = &followed["result"]["initial"]["session"];
8835        assert!(initial["subagents"].as_array().unwrap().is_empty());
8836        assert_eq!(initial["messages"].as_array().unwrap().len(), 1);
8837
8838        let _ = std::fs::remove_dir_all(&temp);
8839    }
8840
8841    #[test]
8842    fn forty_megabyte_display_load_is_bounded_and_prompt() {
8843        let temp = std::env::temp_dir().join(format!(
8844            "supercode-large-display-view-{}-{}",
8845            std::process::id(),
8846            generated_session_id()
8847        ));
8848        std::fs::create_dir_all(&temp).unwrap();
8849        let path = temp.join("rollout.jsonl");
8850        let mut file = std::io::BufWriter::new(std::fs::File::create(&path).unwrap());
8851        writeln!(
8852            file,
8853            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8854        )
8855        .unwrap();
8856        let padding = "x".repeat(80 * 1024);
8857        for index in 0..512 {
8858            let marker = if index == 0 {
8859                "OLDEST-SHOULD-NOT-LOAD"
8860            } else if index == 511 {
8861                "LATEST-MUST-LOAD"
8862            } else {
8863                "bulk"
8864            };
8865            writeln!(
8866                file,
8867                "{}",
8868                json!({
8869                    "timestamp": "2026-01-01T00:00:01Z",
8870                    "type": "response_item",
8871                    "payload": {
8872                        "type": "message",
8873                        "role": "assistant",
8874                        "content": [{"type": "output_text", "text": format!("{marker}:{padding}")}],
8875                    },
8876                })
8877            )
8878            .unwrap();
8879        }
8880        file.flush().unwrap();
8881        drop(file);
8882        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
8883
8884        let locator = SessionLocator {
8885            harness: HarnessId::from(HarnessId::CODEX),
8886            session_id: "large-display".into(),
8887            storage: StorageLocator::File { path },
8888        };
8889        let started = Instant::now();
8890        let response = HarnessSessionService::new().handle(request(
8891            1,
8892            "harness.v1.sessions.load",
8893            json!({
8894                "locator": locator,
8895                "view": {
8896                    "tail_messages": 500,
8897                    "max_message_chars": 1024,
8898                    "include_subagents": false,
8899                    "display_history": true,
8900                },
8901            }),
8902        ));
8903        let elapsed = started.elapsed();
8904        let wire = response.to_string();
8905        eprintln!(
8906            "bounded 40 MiB display load: {elapsed:?}, {} response bytes",
8907            wire.len()
8908        );
8909        assert!(response.get("error").is_none(), "{response:#}");
8910        assert!(wire.contains("LATEST-MUST-LOAD"));
8911        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
8912        assert!(
8913            wire.len() < 2 * 1024 * 1024,
8914            "bounded wire was {} bytes",
8915            wire.len()
8916        );
8917        assert!(
8918            elapsed.as_secs_f64() < 3.0,
8919            "bounded 40 MiB load took {elapsed:?}"
8920        );
8921
8922        // Timing-free: a store with no human turn widens its window to the 64 MiB ceiling
8923        // looking for anchors, so the bounded read shows on one with a human turn every eight
8924        // records: a short view stops well short of the first record and says so.
8925        let anchored = temp.join("anchored.jsonl");
8926        let mut file = std::io::BufWriter::new(std::fs::File::create(&anchored).unwrap());
8927        writeln!(
8928            file,
8929            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8930        )
8931        .unwrap();
8932        for index in 0..512 {
8933            let marker = if index == 0 {
8934                "OLDEST-SHOULD-NOT-LOAD"
8935            } else if index == 511 {
8936                "LATEST-MUST-LOAD"
8937            } else {
8938                "bulk"
8939            };
8940            let (role, kind) = if index % 8 == 0 {
8941                ("user", "input_text")
8942            } else {
8943                ("assistant", "output_text")
8944            };
8945            writeln!(
8946                file,
8947                "{}",
8948                json!({
8949                    "timestamp": "2026-01-01T00:00:01Z",
8950                    "type": "response_item",
8951                    "payload": {
8952                        "type": "message",
8953                        "role": role,
8954                        "content": [{"type": kind, "text": format!("{marker}:{padding}")}],
8955                    },
8956                })
8957            )
8958            .unwrap();
8959        }
8960        file.flush().unwrap();
8961        drop(file);
8962        let short = HarnessSessionService::new().handle(request(
8963            2,
8964            "harness.v1.sessions.load",
8965            json!({
8966                "locator": SessionLocator {
8967                    harness: HarnessId::from(HarnessId::CODEX),
8968                    session_id: "large-display".into(),
8969                    storage: StorageLocator::File { path: anchored },
8970                },
8971                "view": {
8972                    "tail_messages": 20,
8973                    "max_message_chars": 1024,
8974                    "include_subagents": false,
8975                    "display_history": true,
8976                },
8977            }),
8978        ));
8979        let records = short["result"]["session"]["raw_record_count"].as_u64();
8980        assert!(
8981            records.is_some_and(|records| records < 128),
8982            "{records:?} records read"
8983        );
8984        let short = short.to_string();
8985        assert!(short.contains("LATEST-MUST-LOAD"));
8986        assert!(short.contains("older native records remain outside this bounded display window"));
8987
8988        let _ = std::fs::remove_dir_all(&temp);
8989    }
8990
8991    #[test]
8992    fn forty_megabyte_goose_store_display_load_reads_only_the_tail() {
8993        let temp = std::env::temp_dir().join(format!(
8994            "supercode-large-goose-view-{}-{}",
8995            std::process::id(),
8996            generated_session_id()
8997        ));
8998        std::fs::create_dir_all(&temp).unwrap();
8999        let path = temp.join("sessions.db");
9000        let connection = rusqlite::Connection::open(&path).unwrap();
9001        connection
9002            .execute_batch(
9003                "CREATE TABLE sessions (
9004                    id TEXT PRIMARY KEY, name TEXT NOT NULL, working_dir TEXT NOT NULL,
9005                    created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
9006                    session_type TEXT NOT NULL, extension_data TEXT,
9007                    goose_mode TEXT NOT NULL, provider_name TEXT, model_config_json TEXT,
9008                    archived_at TEXT
9009                 );
9010                 CREATE TABLE messages (
9011                    id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, message_id TEXT,
9012                    role TEXT NOT NULL, content_json TEXT NOT NULL,
9013                    created_timestamp INTEGER NOT NULL, metadata_json TEXT
9014                 );",
9015            )
9016            .unwrap();
9017        connection
9018            .execute(
9019                "INSERT INTO sessions VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL)",
9020                rusqlite::params![
9021                    "goose-large",
9022                    "Large Goose session",
9023                    "/tmp",
9024                    "2026-01-01 00:00:00",
9025                    "2026-01-01 00:00:02",
9026                    "user",
9027                    "{}",
9028                    "auto",
9029                    "anthropic",
9030                    r#"{"model_name":"claude-sonnet"}"#,
9031                ],
9032            )
9033            .unwrap();
9034        let old_content = serde_json::to_string(&vec![json!({
9035            "type": "text",
9036            "text": format!("OLDEST-SHOULD-NOT-LOAD:{}", "x".repeat(40 * 1024 * 1024)),
9037        })])
9038        .unwrap();
9039        connection
9040            .execute(
9041                "INSERT INTO messages VALUES (1, ?1, 'old', 'user', ?2, 1, '{}')",
9042                rusqlite::params!["goose-large", old_content],
9043            )
9044            .unwrap();
9045        connection
9046            .execute(
9047                "INSERT INTO messages VALUES (2, ?1, 'new', 'assistant', ?2, 2, '{}')",
9048                rusqlite::params![
9049                    "goose-large",
9050                    r#"[{"type":"text","text":"LATEST-MUST-LOAD"}]"#
9051                ],
9052            )
9053            .unwrap();
9054        drop(connection);
9055        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
9056
9057        let locator = SessionLocator {
9058            harness: HarnessId::from(HarnessId::GOOSE),
9059            session_id: "goose-large".into(),
9060            storage: StorageLocator::Sqlite {
9061                path,
9062                selector: "goose-large".into(),
9063            },
9064        };
9065        let started = Instant::now();
9066        let response = HarnessSessionService::new().handle(request(
9067            1,
9068            "harness.v1.sessions.load",
9069            json!({
9070                "locator": locator,
9071                "view": {
9072                    "tail_messages": 1,
9073                    "max_message_chars": 1024,
9074                    "include_subagents": false,
9075                    "display_history": true,
9076                },
9077            }),
9078        ));
9079        let elapsed = started.elapsed();
9080        let wire = response.to_string();
9081        eprintln!(
9082            "bounded 40 MiB Goose display load: {elapsed:?}, {} response bytes",
9083            wire.len()
9084        );
9085        assert!(response.get("error").is_none(), "{response:#}");
9086        assert!(wire.contains("LATEST-MUST-LOAD"));
9087        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
9088        assert!(
9089            wire.len() < 64 * 1024,
9090            "bounded wire was {} bytes",
9091            wire.len()
9092        );
9093        assert!(
9094            elapsed.as_secs_f64() < 1.0,
9095            "bounded Goose load took {elapsed:?}"
9096        );
9097
9098        let _ = std::fs::remove_dir_all(&temp);
9099    }
9100
9101    #[test]
9102    fn display_view_keeps_codex_assistant_history_across_compaction() {
9103        let temp = std::env::temp_dir().join(format!(
9104            "supercode-codex-display-view-{}-{}",
9105            std::process::id(),
9106            generated_session_id()
9107        ));
9108        std::fs::create_dir_all(&temp).unwrap();
9109        let path = temp.join("rollout.jsonl");
9110        std::fs::write(
9111            &path,
9112            concat!(
9113                r#"{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"codex-display","cwd":"/tmp"}}"#,
9114                "\n",
9115                r#"{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]}}"#,
9116                "\n",
9117                r#"{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"old answer"}]}}"#,
9118                "\n",
9119                r#"{"timestamp":"2026-01-01T00:00:03Z","type":"compacted","payload":{"replacement_history":[{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]},{"type":"compaction","encrypted_content":"opaque"}]}}"#,
9120                "\n",
9121                r#"{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"new prompt"}]}}"#,
9122                "\n",
9123                r#"{"timestamp":"2026-01-01T00:00:05Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"new answer"}]}}"#,
9124                "\n",
9125            ),
9126        )
9127        .unwrap();
9128        let locator = SessionLocator {
9129            harness: HarnessId::from(HarnessId::CODEX),
9130            session_id: "codex-display".into(),
9131            storage: StorageLocator::File { path },
9132        };
9133        let mut service = HarnessSessionService::new();
9134
9135        let continuation = service.handle(request(
9136            1,
9137            "harness.v1.sessions.load",
9138            json!({"locator": locator}),
9139        ));
9140        let continuation_text = continuation["result"]["session"]["messages"].to_string();
9141        assert!(!continuation_text.contains("old answer"));
9142
9143        let display = service.handle(request(
9144            2,
9145            "harness.v1.sessions.load",
9146            json!({
9147                "locator": locator,
9148                "view": {
9149                    "tail_messages": 10,
9150                    "include_subagents": false,
9151                    "display_history": true,
9152                },
9153            }),
9154        ));
9155        let display_text = display["result"]["session"]["messages"].to_string();
9156        assert!(display_text.contains("old prompt"));
9157        assert!(display_text.contains("old answer"));
9158        assert!(display_text.contains("new prompt"));
9159        assert!(display_text.contains("new answer"));
9160
9161        let _ = std::fs::remove_dir_all(&temp);
9162    }
9163
9164    #[test]
9165    fn indexed_claude_windows_match_the_existing_wire_projection() {
9166        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9167            .join("tests/fixtures/claude_code_session.jsonl");
9168        let locator = SessionLocator {
9169            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9170            session_id: "fixture".into(),
9171            storage: StorageLocator::File { path },
9172        };
9173        let full = load_session(&locator).unwrap();
9174        for inline_media in [InlineMediaMode::Full, InlineMediaMode::Metadata] {
9175            for offset in [0, 1, full.messages.len(), usize::MAX] {
9176                for limit in [0, 1, 3, usize::MAX] {
9177                    let options = SessionLoadOptions {
9178                        include_subagents: Some(false),
9179                        inline_media,
9180                        message_offset: Some(offset),
9181                        message_limit: Some(limit),
9182                        ..Default::default()
9183                    };
9184                    let expected = projected_session_result(&full, &options);
9185                    assert_eq!(
9186                        indexed_claude_window(&locator, &options).unwrap().unwrap(),
9187                        expected
9188                    );
9189                }
9190            }
9191            for tail in [0, 1, 3, usize::MAX] {
9192                let options = SessionLoadOptions {
9193                    include_subagents: Some(false),
9194                    inline_media,
9195                    message_tail: Some(tail),
9196                    ..Default::default()
9197                };
9198                assert_eq!(
9199                    indexed_claude_window(&locator, &options).unwrap().unwrap(),
9200                    projected_session_result(&full, &options)
9201                );
9202            }
9203        }
9204    }
9205
9206    #[test]
9207    fn load_supports_bounded_windows_and_media_metadata() {
9208        let mut service = HarnessSessionService::new();
9209        let locator = pi_locator();
9210        let bounded = service.handle(request(
9211            1,
9212            "harness.v1.sessions.load",
9213            json!({
9214                "locator": locator,
9215                "options": {
9216                    "include_subagents": false,
9217                    "message_limit": 2,
9218                    "message_offset": 1
9219                }
9220            }),
9221        ));
9222        assert_eq!(bounded["result"]["window"]["offset"], 1);
9223        assert_eq!(bounded["result"]["window"]["returned"], 2);
9224        assert!(bounded["result"]["summary"]["first_message"].is_object());
9225        assert!(bounded["result"]["summary"]["last_message"].is_object());
9226        assert_eq!(
9227            bounded["result"]["session"]["messages"]
9228                .as_array()
9229                .unwrap()
9230                .len(),
9231            2
9232        );
9233        assert!(bounded["result"]["session"]["subagents"]
9234            .as_array()
9235            .unwrap()
9236            .is_empty());
9237
9238        let tail = service.handle(request(
9239            2,
9240            "harness.v1.sessions.load",
9241            json!({"locator": locator, "options": {"message_tail": 1}}),
9242        ));
9243        assert_eq!(tail["result"]["window"]["returned"], 1);
9244        assert_eq!(tail["result"]["window"]["has_more"], true);
9245        assert_eq!(tail["result"]["window"]["has_older"], true);
9246        assert!(tail["result"]["window"]["older_items"].as_u64().unwrap() > 0);
9247        assert!(tail["result"]["summary"]["first_message"].is_object());
9248
9249        let metadata_only = service.handle(request(
9250            3,
9251            "harness.v1.sessions.load",
9252            json!({"locator": locator, "options": {"inline_media": "metadata"}}),
9253        ));
9254        assert!(metadata_only["result"]["session"]
9255            .to_string()
9256            .contains("media_reference"));
9257        assert!(!metadata_only["result"]["session"]
9258            .to_string()
9259            .contains("data:image/"));
9260    }
9261
9262    #[test]
9263    fn import_translate_branch_and_handoff_use_typed_artifacts() {
9264        let mut service = HarnessSessionService::new();
9265        let locator = pi_locator();
9266        let translated = service.handle(request(
9267            1,
9268            "harness.v1.sessions.translate",
9269            json!({"locator": locator, "target_harness": "grok"}),
9270        ));
9271        assert_eq!(translated["result"]["artifact"]["source_harness"], "pi");
9272        assert_eq!(translated["result"]["artifact"]["target_harness"], "grok");
9273        assert!(translated["result"]["artifact"]["content"]
9274            .as_str()
9275            .is_some_and(|content| !content.is_empty()));
9276
9277        for target in ["opencode", "open-code"] {
9278            let opencode = service.handle(request(
9279                6,
9280                "harness.v1.sessions.translate",
9281                json!({"locator": locator, "target_harness": target}),
9282            ));
9283            assert_eq!(opencode["result"]["artifact"]["target_harness"], "opencode");
9284        }
9285        let goose = service.handle(request(
9286            7,
9287            "harness.v1.sessions.translate",
9288            json!({"locator": locator, "target_harness": "goose"}),
9289        ));
9290        assert_eq!(goose["result"]["artifact"]["target_harness"], "goose");
9291        assert!(serde_json::from_str::<Value>(
9292            goose["result"]["artifact"]["content"].as_str().unwrap()
9293        )
9294        .unwrap()["conversation"]
9295            .is_array());
9296
9297        let imported = service.handle(request(
9298            2,
9299            "harness.v1.sessions.import",
9300            json!({
9301                "source_harness": "grok",
9302                "content": translated["result"]["artifact"]["content"],
9303            }),
9304        ));
9305        assert_eq!(imported["result"]["session"]["source"], "grok");
9306
9307        let branched = service.handle(request(
9308            3,
9309            "harness.v1.sessions.branch",
9310            json!({"locator": locator, "target_harness": "codex"}),
9311        ));
9312        assert_eq!(branched["result"]["parent"]["harness"], "pi");
9313        assert!(branched["result"]["bootstrap_prompt"]
9314            .as_str()
9315            .unwrap()
9316            .contains("frozen parent transcript"));
9317        assert_eq!(branched["result"]["artifact"]["target_harness"], "codex");
9318
9319        let handoff = service.handle(request(
9320            4,
9321            "harness.v1.sessions.handoff",
9322            json!({"locator": locator, "target_harness": "pi", "cwd": "/tmp/project"}),
9323        ));
9324        assert_eq!(handoff["result"]["launch"]["program"], "pi");
9325        assert_eq!(handoff["result"]["launch"]["cwd"], "/tmp/project");
9326        assert_eq!(handoff["result"]["requires_materialization"], true);
9327
9328        let goose_handoff = service.handle(request(
9329            8,
9330            "harness.v1.sessions.handoff",
9331            json!({"locator": locator, "target_harness": "goose", "cwd": "/tmp/project"}),
9332        ));
9333        assert_eq!(goose_handoff["result"]["launch"]["program"], "goose");
9334        assert_eq!(
9335            goose_handoff["result"]["materialize"]["arguments"],
9336            json!(["session", "import", "{artifact_path}"])
9337        );
9338
9339        let resumed = service.handle(request(
9340            5,
9341            "harness.v1.sessions.resume_instructions",
9342            json!({"locator": locator, "cwd": "/tmp/project", "policy": "yolo"}),
9343        ));
9344        assert_eq!(resumed["result"]["launch"]["program"], "pi");
9345        assert_eq!(resumed["result"]["launch"]["arguments"][0], "--approve");
9346    }
9347
9348    #[test]
9349    fn reduce_persists_and_reloads_a_byte_exact_reversible_bundle() {
9350        let temp = std::env::temp_dir().join(format!(
9351            "supercode-service-reduce-{}-{}",
9352            std::process::id(),
9353            generated_session_id()
9354        ));
9355        let source_path = temp.join("source.jsonl");
9356        let store_root = temp.join("store");
9357        std::fs::create_dir_all(&temp).unwrap();
9358
9359        let mut records = vec![json!({
9360            "timestamp": "2026-01-01T00:00:00Z",
9361            "type": "session_meta",
9362            "payload": {"id": "codex-reduce", "cwd": "/tmp/project"},
9363        })];
9364        for turn in 0..16 {
9365            records.push(json!({
9366                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 1),
9367                "type": "response_item",
9368                "payload": {
9369                    "type": "message",
9370                    "role": "user",
9371                    "content": [{
9372                        "type": "input_text",
9373                        "text": format!("request {turn}: {}", "context ".repeat(80)),
9374                    }],
9375                },
9376            }));
9377            records.push(json!({
9378                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 2),
9379                "type": "response_item",
9380                "payload": {
9381                    "type": "message",
9382                    "role": "assistant",
9383                    "content": [{
9384                        "type": "output_text",
9385                        "text": format!("answer {turn}: {}", "implementation detail ".repeat(80)),
9386                    }],
9387                },
9388            }));
9389        }
9390        let source = format!(
9391            "{}\n",
9392            records
9393                .iter()
9394                .map(Value::to_string)
9395                .collect::<Vec<_>>()
9396                .join("\n")
9397        );
9398        std::fs::write(&source_path, &source).unwrap();
9399        let locator = SessionLocator {
9400            harness: HarnessId::from(HarnessId::CODEX),
9401            session_id: "codex-reduce".into(),
9402            storage: StorageLocator::File {
9403                path: source_path.clone(),
9404            },
9405        };
9406        let original = load_session(&locator).unwrap();
9407        let mut service =
9408            HarnessSessionService::new().with_reduction_store_root(store_root.clone());
9409
9410        let response = service.handle(request(
9411            1,
9412            "harness.v1.sessions.reduce",
9413            json!({
9414                "locator": locator,
9415                "target_harness": "claude-code",
9416                "keep_last": 4,
9417            }),
9418        ));
9419        assert!(response.get("error").is_none(), "{response:#}");
9420        let receipt = &response["result"]["receipt"];
9421        assert_eq!(receipt["source_harness"], "codex");
9422        assert_eq!(receipt["target_harness"], "claude-code");
9423        assert_eq!(receipt["verified"], true);
9424        assert_eq!(receipt["reversible"], true);
9425        assert!(receipt["reductions"].as_u64().unwrap() > 0);
9426        assert!(
9427            receipt["source_tokens"].as_u64().unwrap()
9428                > receipt["reduced_tokens"].as_u64().unwrap()
9429        );
9430        assert!(receipt["ratio"].as_f64().unwrap() > 1.0);
9431        assert!(response["result"]["bootstrap_prompt"]
9432            .as_str()
9433            .unwrap()
9434            .contains("Do not guess hidden content"));
9435
9436        let rescue_id = receipt["id"].as_str().unwrap();
9437        let store = crate::SessionStore::open(&store_root).unwrap();
9438        let sidecar =
9439            Session::from_sidecar_str(&store.load_sidecar(rescue_id).unwrap().unwrap()).unwrap();
9440        let log = store.load_reduction_log(rescue_id).unwrap().unwrap();
9441        let persisted_view = parse_messages_jsonl(&store.load(rescue_id).unwrap()).unwrap();
9442        let policy = reduce::ReductionPolicy {
9443            clear_turns_older_than: Some(4),
9444            ..Default::default()
9445        };
9446        let (restamped_view, reapplied_log) =
9447            reduce::project_messages(&sidecar.messages, &policy, &log);
9448        assert_eq!(
9449            messages_jsonl(&persisted_view).unwrap(),
9450            messages_jsonl(&restamped_view).unwrap()
9451        );
9452        assert_eq!(reapplied_log, log);
9453        reduce::verify_log(&log, &sidecar).unwrap();
9454        assert_eq!(
9455            reduce::invert(&restamped_view, &log, &sidecar).unwrap(),
9456            original.messages
9457        );
9458        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), source);
9459
9460        std::fs::remove_dir_all(temp).ok();
9461    }
9462
9463    #[test]
9464    fn read_surfaces_view_a_severed_claude_graph_while_transfer_still_refuses_it() {
9465        let temp = std::env::temp_dir().join(format!(
9466            "supercode-severed-view-{}-{}",
9467            std::process::id(),
9468            generated_session_id()
9469        ));
9470        std::fs::create_dir_all(&temp).unwrap();
9471        let path = temp.join("severed.jsonl");
9472        // A live record whose parent was pruned — what a compacted or
9473        // resumed-across-files Claude Code session looks like on disk.
9474        std::fs::write(
9475            &path,
9476            concat!(
9477                r#"{"type":"user","uuid":"orphan-u","parentUuid":null,"message":{"role":"user","content":"stranded prompt"}}"#,
9478                "\n",
9479                r#"{"type":"assistant","uuid":"live-a","parentUuid":"pruned","message":{"id":"m","role":"assistant","content":[{"type":"text","text":"live answer"}]}}"#,
9480                "\n",
9481            ),
9482        )
9483        .unwrap();
9484        let locator = SessionLocator {
9485            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9486            session_id: "severed".into(),
9487            storage: StorageLocator::File { path },
9488        };
9489        let mut service = HarnessSessionService::new();
9490
9491        let viewed = service.handle(request(
9492            1,
9493            "harness.v1.sessions.load",
9494            json!({"locator": locator}),
9495        ));
9496        let session = &viewed["result"]["session"];
9497        assert_eq!(session["fidelity"], "semantic");
9498        assert_eq!(session["messages"].as_array().unwrap().len(), 2);
9499        assert!(session["residue"].as_array().unwrap().iter().any(|entry| {
9500            entry
9501                .as_str()
9502                .is_some_and(|entry| entry.contains("live-a") && entry.contains("pruned"))
9503        }));
9504
9505        // Asking a READ surface for a lossless reconstruction gets the strict
9506        // refusal back, unchanged.
9507        let strict = service.handle(request(
9508            2,
9509            "harness.v1.sessions.load",
9510            json!({"locator": locator, "fidelity": "byte_lossless"}),
9511        ));
9512        assert!(strict["error"]["message"]
9513            .as_str()
9514            .unwrap()
9515            .contains("cannot reconstruct lossless Claude continuation"));
9516
9517        // Transfer/continuation surfaces have no view mode at all.
9518        let translated = service.handle(request(
9519            3,
9520            "harness.v1.sessions.translate",
9521            json!({"locator": locator, "target_harness": "codex"}),
9522        ));
9523        assert!(translated["error"]["message"]
9524            .as_str()
9525            .unwrap()
9526            .contains("cannot reconstruct lossless Claude continuation"));
9527        let resumed = service.handle(request(
9528            4,
9529            "harness.v1.sessions.resume_instructions",
9530            json!({"locator": locator}),
9531        ));
9532        assert!(resumed["error"]["message"]
9533            .as_str()
9534            .unwrap()
9535            .contains("cannot reconstruct lossless Claude continuation"));
9536
9537        let _ = std::fs::remove_dir_all(&temp);
9538    }
9539
9540    #[test]
9541    fn structured_resume_launches_cover_gemini_goose_and_supercode() {
9542        let codex = resume_launch(
9543            HarnessId::CODEX,
9544            "codex-session",
9545            Path::new("/tmp/project"),
9546            ResumePolicy::Yolo,
9547        )
9548        .unwrap_or_else(|_| panic!("Codex resume launch must be registered"));
9549        assert_eq!(codex.program, "codex");
9550        assert_eq!(
9551            codex.arguments,
9552            [
9553                "-c",
9554                "check_for_update_on_startup=false",
9555                "-c",
9556                "projects.\"/tmp/project\".trust_level=\"trusted\"",
9557                "--dangerously-bypass-approvals-and-sandbox",
9558                "--dangerously-bypass-hook-trust",
9559                "resume",
9560                "codex-session",
9561            ]
9562        );
9563
9564        let gemini = resume_launch(
9565            HarnessId::GEMINI,
9566            "gemini-session",
9567            Path::new("/tmp/project"),
9568            ResumePolicy::Yolo,
9569        )
9570        .unwrap_or_else(|_| panic!("Gemini resume launch must be registered"));
9571        assert_eq!(gemini.program, "gemini");
9572        assert_eq!(gemini.arguments, ["--yolo", "--resume", "gemini-session"]);
9573
9574        let goose = resume_launch(
9575            HarnessId::GOOSE,
9576            "goose-session",
9577            Path::new("/tmp/project"),
9578            ResumePolicy::Yolo,
9579        )
9580        .unwrap_or_else(|_| panic!("Goose resume launch must be registered"));
9581        assert_eq!(goose.program, "goose");
9582        assert_eq!(
9583            goose.arguments,
9584            ["session", "--resume", "--session-id", "goose-session"]
9585        );
9586
9587        let supercode = resume_launch(
9588            HarnessId::SUPERCODE,
9589            "supercode-session",
9590            Path::new("/tmp/project"),
9591            ResumePolicy::Yolo,
9592        )
9593        .unwrap_or_else(|_| panic!("Volter Harness resume launch must be registered"));
9594        assert_eq!(supercode.program, "supercode");
9595        assert_eq!(
9596            supercode.arguments,
9597            ["--dangerous", "resume", "supercode-session"]
9598        );
9599    }
9600
9601    #[test]
9602    fn diagonal_artifacts_preserve_claude_subagents_and_grok_bundle_members() {
9603        let temp = std::env::temp_dir().join(format!(
9604            "supercode-harness-artifact-{}-{}",
9605            std::process::id(),
9606            generated_session_id()
9607        ));
9608        let main_path = temp.join("parent.jsonl");
9609        let subagent_path = temp.join("parent/subagents/agent-child.jsonl");
9610        std::fs::create_dir_all(subagent_path.parent().unwrap()).unwrap();
9611        let fixture = std::fs::read_to_string(
9612            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9613                .join("tests/fixtures/claude_code_session.jsonl"),
9614        )
9615        .unwrap();
9616        let parent = fixture.trim_end_matches('\n');
9617        let child = fixture.trim_end_matches('\n');
9618        std::fs::write(&main_path, parent).unwrap();
9619        std::fs::write(&subagent_path, child).unwrap();
9620        let locator = SessionLocator {
9621            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9622            session_id: "213bb148-51ea-453f-9206-f8b4b1168547".into(),
9623            storage: StorageLocator::File {
9624                path: main_path.clone(),
9625            },
9626        };
9627        let mut service = HarnessSessionService::new();
9628        let claude = service.handle(request(
9629            1,
9630            "harness.v1.sessions.translate",
9631            json!({"locator": locator, "target_harness": "claude-code"}),
9632        ));
9633        let artifact = &claude["result"]["artifact"];
9634        assert_eq!(artifact["fidelity"], "byte_lossless");
9635        assert_eq!(artifact["content"], parent);
9636        let files = artifact["files"].as_array().unwrap();
9637        assert!(files.iter().any(|file| {
9638            file["role"] == "subagent"
9639                && file["path"]
9640                    .as_str()
9641                    .is_some_and(|path| path.ends_with("/subagents/agent-child.jsonl"))
9642                && file["content"] == child
9643        }));
9644        assert!(!artifact["content"].as_str().unwrap().ends_with('\n'));
9645
9646        let grok = service.handle(request(
9647            2,
9648            "harness.v1.sessions.translate",
9649            json!({"locator": grok_locator(), "target_harness": "grok"}),
9650        ));
9651        let files = grok["result"]["artifact"]["files"].as_array().unwrap();
9652        for name in ["summary.json", "updates.jsonl"] {
9653            let expected = std::fs::read_to_string(
9654                PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9655                    .join("tests/fixtures/grok_session")
9656                    .join(name),
9657            )
9658            .unwrap();
9659            assert!(files.iter().any(|file| {
9660                file["path"] == name && file["role"] == "bundle" && file["content"] == expected
9661            }));
9662        }
9663        std::fs::remove_dir_all(temp).ok();
9664    }
9665
9666    #[test]
9667    fn every_non_grok_handoff_mints_and_uses_a_fresh_target_identity() {
9668        let mut service = HarnessSessionService::new();
9669        let source = pi_locator();
9670        for (target, format) in [
9671            ("claude-code", SessionFormat::ClaudeCode),
9672            ("codex", SessionFormat::Codex),
9673            ("opencode", SessionFormat::OpenCode),
9674            ("pi", SessionFormat::Pi),
9675        ] {
9676            let result = service.handle(request(
9677                1,
9678                "harness.v1.sessions.handoff",
9679                json!({"locator": source, "target_harness": target, "cwd": "/tmp/project"}),
9680            ));
9681            let artifact = &result["result"]["artifact"];
9682            let target_id = artifact["session_id"].as_str().unwrap();
9683            assert_ne!(target_id, source.session_id, "{target}");
9684            let parsed = Session::load_str(artifact["content"].as_str().unwrap(), format).unwrap();
9685            assert_eq!(
9686                parsed.meta.session_id.as_deref(),
9687                Some(target_id),
9688                "{target}"
9689            );
9690            if target != "pi" {
9691                assert!(result["result"]["launch"]["arguments"]
9692                    .as_array()
9693                    .unwrap()
9694                    .iter()
9695                    .any(|argument| argument == target_id));
9696            }
9697            if target == "opencode" {
9698                assert!(target_id.starts_with("ses_"));
9699                fn assert_session_ids(value: &Value, target_id: &str) {
9700                    match value {
9701                        Value::Object(fields) => {
9702                            if let Some(session_id) = fields.get("sessionID") {
9703                                assert_eq!(session_id, target_id);
9704                            }
9705                            for child in fields.values() {
9706                                assert_session_ids(child, target_id);
9707                            }
9708                        }
9709                        Value::Array(values) => {
9710                            for child in values {
9711                                assert_session_ids(child, target_id);
9712                            }
9713                        }
9714                        _ => {}
9715                    }
9716                }
9717                let document: Value =
9718                    serde_json::from_str(artifact["content"].as_str().unwrap()).unwrap();
9719                assert_session_ids(&document, target_id);
9720            }
9721        }
9722
9723        let first = service.handle(request(
9724            2,
9725            "harness.v1.sessions.handoff",
9726            json!({"locator": source, "target_harness": "codex"}),
9727        ));
9728        let second = service.handle(request(
9729            3,
9730            "harness.v1.sessions.handoff",
9731            json!({"locator": source, "target_harness": "codex"}),
9732        ));
9733        assert_ne!(
9734            first["result"]["artifact"]["session_id"],
9735            second["result"]["artifact"]["session_id"]
9736        );
9737    }
9738
9739    #[test]
9740    fn grok_handoff_materializes_through_the_core_door() {
9741        let mut service = HarnessSessionService::new();
9742        let source = opencode_locator();
9743        let response = service.handle(request(
9744            1,
9745            "harness.v1.sessions.handoff",
9746            json!({
9747                "locator": source,
9748                "target_harness": "grok",
9749                "cwd": "/tmp/grok-handoff-project",
9750            }),
9751        ));
9752        let result = &response["result"];
9753
9754        // Grok has no import command: the artifact is Grok's own transcript under a fresh
9755        // identity, and `harness.v1.sessions.materialize` writes its store entry.
9756        assert_eq!(result["artifact"]["target_harness"], "grok");
9757        let artifact = Session::load_str(
9758            result["artifact"]["content"].as_str().unwrap(),
9759            SessionFormat::Grok,
9760        )
9761        .unwrap();
9762        assert!(!artifact.messages.is_empty());
9763        let target_session_id = result["artifact"]["session_id"].as_str().unwrap();
9764        assert_eq!(target_session_id.len(), 36);
9765        assert_ne!(target_session_id, opencode_locator().session_id);
9766        assert!(result["materialize"].is_null());
9767        assert_eq!(
9768            result["launch"]["arguments"],
9769            json!(["--resume", "{materialized_session_id}"])
9770        );
9771        assert!(result["note"]
9772            .as_str()
9773            .unwrap()
9774            .contains("harness.v1.sessions.materialize"));
9775    }
9776
9777    #[tokio::test]
9778    async fn inventory_rejects_unknown_harnesses_and_runtime_attach_is_honest() {
9779        let mut service = HarnessSessionService::new();
9780        let inventory = service
9781            .handle_async(request(
9782                1,
9783                "harness.v1.harnesses.list",
9784                json!({"harnesses": ["missing"]}),
9785            ))
9786            .await;
9787        assert_eq!(inventory["error"]["code"], -32602);
9788
9789        let attached = service
9790            .handle_async(request(
9791                2,
9792                "harness.v1.runtimes.attach_existing",
9793                json!({"harness": "codex", "runtime_id": "thread-1"}),
9794            ))
9795            .await;
9796        assert_eq!(attached["error"]["code"], -32000);
9797        assert!(attached["error"]["message"]
9798            .as_str()
9799            .unwrap()
9800            .contains("runtimes.resume"));
9801    }
9802
9803    #[test]
9804    fn invalid_params_and_unknown_methods_use_json_rpc_errors() {
9805        let mut service = HarnessSessionService::new();
9806        let invalid = service.handle(request(1, "harness.v1.sessions.load", json!({})));
9807        assert_eq!(invalid["error"]["code"], -32602);
9808        let unknown = service.handle(request(2, "harness.v1.unknown", json!({})));
9809        assert_eq!(unknown["error"]["code"], -32601);
9810    }
9811
9812    #[cfg(unix)]
9813    #[tokio::test]
9814    // The test mutates process-wide harness environment and deliberately
9815    // holds the global test lock until every async runtime operation ends.
9816    #[allow(clippy::await_holding_lock)]
9817    async fn async_service_drives_a_generic_acp_runtime() {
9818        let _environment_guard = crate::live_runtime::test_environment_lock();
9819        let script = r#"
9820            i=0
9821            while IFS= read -r line; do
9822              i=$((i + 1))
9823              case "$i" in
9824                1) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{},"authMethods":[]}}' ;;
9825                2) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"svc_acp"}}' ;;
9826                3)
9827                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ok"}}}}'
9828                  printf '%s\n' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}'
9829                  ;;
9830                4)
9831                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"from terminal"}}}}'
9832                  printf '%s\n' '{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}'
9833                  ;;
9834              esac
9835            done
9836        "#;
9837        let mut service = HarnessSessionService::new();
9838        let started = service
9839            .handle_async(request(
9840                1,
9841                "harness.v1.runtimes.start",
9842                json!({
9843                    "harness": "codex",
9844                    "protocol": "acp",
9845                    "cwd": std::env::current_dir().unwrap(),
9846                    "launch": {"program": "/bin/sh", "arguments": ["-c", script], "env": {}},
9847                }),
9848            ))
9849            .await;
9850        assert_eq!(started["result"]["connection"], "runtime-1");
9851        assert_eq!(started["result"]["handle"]["runtime_id"], "svc_acp");
9852
9853        let terminal = service
9854            .handle_async(request(
9855                9,
9856                "harness.v1.runtimes.terminal_instructions",
9857                json!({"connection":"runtime-1"}),
9858            ))
9859            .await;
9860        let arguments = terminal["result"]["launch"]["arguments"]
9861            .as_array()
9862            .expect("hosted runtime should return terminal arguments");
9863        let endpoint_index = arguments
9864            .iter()
9865            .position(|value| value == "--endpoint")
9866            .expect("terminal command should use an opaque endpoint");
9867        let endpoint = LiveRuntimeEndpoint::parse(
9868            arguments[endpoint_index + 1]
9869                .as_str()
9870                .expect("endpoint argument should be text"),
9871        )
9872        .unwrap();
9873        assert!(!terminal.to_string().contains("Bearer"));
9874        let workspace = std::env::current_dir().unwrap();
9875        let receipt = resolve_live_runtime(
9876            &endpoint,
9877            &LiveRuntimeSource {
9878                harness: "codex".into(),
9879                session_id: "svc_acp".into(),
9880                workspace,
9881            },
9882        )
9883        .unwrap();
9884        let remote = crate::HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
9885            .await
9886            .unwrap();
9887        let mut attachment = crate::FrontendRuntime::attach(remote.as_ref(), 100)
9888            .await
9889            .unwrap();
9890
9891        let sent = service
9892            .handle_async(request(
9893                2,
9894                "harness.v1.runtimes.send_input",
9895                json!({"connection": "runtime-1", "text": "hi"}),
9896            ))
9897            .await;
9898        assert_eq!(sent["result"]["turn_id"], "3");
9899
9900        let mut events = Vec::new();
9901        for _ in 0..20 {
9902            events.extend(service.poll_runtimes().await);
9903            if events.len() >= 2 {
9904                break;
9905            }
9906            tokio::time::sleep(Duration::from_millis(2)).await;
9907        }
9908        assert!(events
9909            .iter()
9910            .any(|event| { event["params"]["event"]["kind"] == "session/update" }));
9911        assert!(events.iter().any(|event| {
9912            event["params"]["event"]["kind"] == "supercode/acp_request_completed"
9913        }));
9914
9915        let saw_editor_reply = tokio::time::timeout(Duration::from_secs(2), async {
9916            loop {
9917                let event = attachment.next_event().await.unwrap();
9918                if event.kind == "text_delta" && event.payload["text"] == "ok" {
9919                    break;
9920                }
9921            }
9922        })
9923        .await;
9924        assert!(
9925            saw_editor_reply.is_ok(),
9926            "terminal should observe the editor-driven turn"
9927        );
9928
9929        crate::FrontendRuntime::submit(remote.as_ref(), "DRIVE FROM TERMINAL".into())
9930            .await
9931            .unwrap();
9932        let saw_terminal_reply = tokio::time::timeout(Duration::from_secs(2), async {
9933            loop {
9934                let event = attachment.next_event().await.unwrap();
9935                if event.kind == "text_delta" && event.payload["text"] == "from terminal" {
9936                    break;
9937                }
9938            }
9939        })
9940        .await;
9941        assert!(
9942            saw_terminal_reply.is_ok(),
9943            "terminal should drive the same runtime"
9944        );
9945
9946        let closed = service
9947            .handle_async(request(
9948                3,
9949                "harness.v1.runtimes.close",
9950                json!({"connection": "runtime-1"}),
9951            ))
9952            .await;
9953        assert_eq!(closed["result"]["closed"], true);
9954    }
9955
9956    /// UNI-7 dev/02: a RUNNING mock gateway is detected through the real
9957    /// openclaw probe (config-declared endpoint, TCP connect), and an ACTIVE
9958    /// hermes WAL is detected through the real WAL-freshness probe; the
9959    /// negative sides (no listener, stale WAL, no config) stay undetected.
9960    #[test]
9961    fn running_instances_are_detected_from_mock_gateway_and_active_wal() {
9962        let home = connect_scratch_home("uni7-running");
9963
9964        // No config at all: hermes has no default endpoint, so no detection.
9965        // (openclaw's no-config behavior now probes its DOCUMENTED default
9966        // endpoint ws://127.0.0.1:18789 — see the connect launch's
9967        // `default_address` — which is real box state a hermetic test must
9968        // not assert either way; the closed-port negative below covers the
9969        // no-listener side deterministically.)
9970        assert!(probe_hermes_running(&home, 300_000).is_none());
9971
9972        // Mock gateway: a real TCP listener on an ephemeral port, declared in
9973        // the harness's own config file.
9974        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9975        let port = listener.local_addr().unwrap().port();
9976        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9977        std::fs::write(
9978            home.join(".openclaw/openclaw.json"),
9979            format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9980        )
9981        .unwrap();
9982        let running = probe_openclaw_running(&home).expect("listening gateway must be detected");
9983        assert!(matches!(
9984            running.method,
9985            RunningInstanceMethod::GatewayConnect
9986        ));
9987        assert!(running.evidence.contains(&format!("127.0.0.1:{port}")));
9988        drop(listener);
9989        // Parallel tests also bind ephemeral loopback ports, so a just-freed
9990        // port can be re-bound by a NEIGHBORING test between drop and probe.
9991        // Detection on a closed port must fail — retry on a fresh port when
9992        // the freed one was recycled by someone else.
9993        let mut closed_detected = probe_openclaw_running(&home).is_some();
9994        for _ in 0..3 {
9995            if !closed_detected {
9996                break;
9997            }
9998            let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9999            let port = listener.local_addr().unwrap().port();
10000            drop(listener);
10001            std::fs::write(
10002                home.join(".openclaw/openclaw.json"),
10003                format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
10004            )
10005            .unwrap();
10006            closed_detected = probe_openclaw_running(&home).is_some();
10007        }
10008        assert!(
10009            !closed_detected,
10010            "a closed gateway must not read as running"
10011        );
10012
10013        // gateway.url form takes precedence over port.
10014        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
10015        let port = listener.local_addr().unwrap().port();
10016        std::fs::write(
10017            home.join(".openclaw/openclaw.json"),
10018            format!(r#"{{"gateway": {{"url": "ws://127.0.0.1:{port}", "auth": {{"mode": "token", "token": "t"}}}}}}"#),
10019        )
10020        .unwrap();
10021        assert!(probe_openclaw_running(&home).is_some());
10022        drop(listener);
10023
10024        // Hermes: an ACTIVE WAL (fresh stamp) is detected; a stale one is not.
10025        std::fs::create_dir_all(home.join(".hermes")).unwrap();
10026        let wal = home.join(".hermes/state.db-wal");
10027        std::fs::write(&wal, b"wal").unwrap();
10028        let running = probe_hermes_running(&home, 300_000).expect("fresh WAL must be detected");
10029        assert!(matches!(
10030            running.method,
10031            RunningInstanceMethod::StoreWalActivity
10032        ));
10033        assert!(running.evidence.contains("state.db-wal"));
10034        let stale = std::time::SystemTime::now() - std::time::Duration::from_secs(3_600);
10035        std::fs::File::options()
10036            .append(true)
10037            .open(&wal)
10038            .unwrap()
10039            .set_modified(stale)
10040            .unwrap();
10041        assert!(
10042            probe_hermes_running(&home, 300_000).is_none(),
10043            "a stale WAL (crash leftover) must not read as running"
10044        );
10045    }
10046
10047    fn connect_scratch_home(tag: &str) -> PathBuf {
10048        let dir = std::env::temp_dir().join(format!(
10049            "supercode-connect-service-{tag}-{}-{}",
10050            std::process::id(),
10051            std::time::SystemTime::now()
10052                .duration_since(std::time::UNIX_EPOCH)
10053                .unwrap()
10054                .as_nanos()
10055        ));
10056        std::fs::create_dir_all(&dir).unwrap();
10057        dir
10058    }
10059
10060    /// Minimal HTTP responder that speaks just enough OpenCode server to
10061    /// accept a health check, create a session, and hold an SSE stream open,
10062    /// while recording each request line with its Authorization header.
10063    async fn mock_opencode_endpoint() -> (String, tokio::sync::mpsc::UnboundedReceiver<String>) {
10064        use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
10065        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
10066        let address = listener.local_addr().unwrap();
10067        let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel();
10068        tokio::spawn(async move {
10069            loop {
10070                let Ok((mut stream, _)) = listener.accept().await else {
10071                    break;
10072                };
10073                let request_sender = request_sender.clone();
10074                tokio::spawn(async move {
10075                    let (reader, mut writer) = stream.split();
10076                    let mut reader = BufReader::new(reader);
10077                    let mut request_line = String::new();
10078                    if reader.read_line(&mut request_line).await.unwrap_or(0) == 0 {
10079                        return;
10080                    }
10081                    let request_line = request_line.trim_end().to_string();
10082                    let mut authorization = String::new();
10083                    let mut content_length = 0usize;
10084                    loop {
10085                        let mut line = String::new();
10086                        if reader.read_line(&mut line).await.unwrap_or(0) == 0 {
10087                            return;
10088                        }
10089                        let line = line.trim_end();
10090                        if line.is_empty() {
10091                            break;
10092                        }
10093                        let lower = line.to_ascii_lowercase();
10094                        if let Some(value) = lower.strip_prefix("authorization:") {
10095                            authorization = value.trim().to_string();
10096                        }
10097                        if let Some(value) = lower.strip_prefix("content-length:") {
10098                            content_length = value.trim().parse().unwrap_or(0);
10099                        }
10100                    }
10101                    if content_length > 0 {
10102                        let mut body = vec![0u8; content_length];
10103                        let _ = reader.read_exact(&mut body).await;
10104                    }
10105                    let _ = request_sender.send(format!("{request_line} :: {authorization}"));
10106                    if request_line.starts_with("GET /event") {
10107                        let _ = writer
10108                            .write_all(
10109                                b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n",
10110                            )
10111                            .await;
10112                        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
10113                        return;
10114                    }
10115                    let body = if request_line.starts_with("POST /session") {
10116                        r#"{"id":"mock-session"}"#
10117                    } else {
10118                        r#"{"status":"ok"}"#
10119                    };
10120                    let response = format!(
10121                        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
10122                        body.len(),
10123                        body
10124                    );
10125                    let _ = writer.write_all(response.as_bytes()).await;
10126                });
10127            }
10128        });
10129        (format!("http://{address}"), request_receiver)
10130    }
10131
10132    fn connect_descriptor(protocol: &str) -> crate::HarnessSupportDescriptor {
10133        crate::HarnessSupportDescriptor {
10134            orchestration: Default::default(),
10135            id: HarnessId::from(HarnessId::OPENCODE),
10136            display_name: "OpenCode".into(),
10137            native: crate::NativeSupport {
10138                discover: crate::ImplementationKind::Absent,
10139                load: crate::ImplementationKind::Absent,
10140                follow: crate::ImplementationKind::Absent,
10141                import: crate::ImplementationKind::Absent,
10142                export: crate::ImplementationKind::Absent,
10143            },
10144            runtime: crate::RuntimeSupport {
10145                implementation: crate::ImplementationKind::BuiltIn,
10146                protocol: protocol.into(),
10147                default_launch: None,
10148                connect_launch: Some(crate::RuntimeConnectLaunch {
10149                    config_path: "~/opencode-tui.json".into(),
10150                    address_pointer: "/server/url".into(),
10151                    port_pointer: None,
10152                    default_address: None,
10153                    auth_pointer: Some("/server/token".into()),
10154                    protocol: protocol.into(),
10155                }),
10156                capabilities: crate::RuntimeCapabilities {
10157                    start_session: true,
10158                    resume_session: true,
10159                    attach_existing_process: true,
10160                    send_input: true,
10161                    stream_events: true,
10162                    interrupt: true,
10163                    steer: false,
10164                    respond_to_requests: true,
10165                },
10166            },
10167        }
10168    }
10169
10170    #[tokio::test]
10171    async fn connect_mode_descriptor_opens_a_running_endpoint_with_config_sourced_auth() {
10172        let (base_url, mut requests) = mock_opencode_endpoint().await;
10173        let home = connect_scratch_home("open");
10174        std::fs::write(
10175            home.join("opencode-tui.json"),
10176            format!(r#"{{"server": {{"url": "{base_url}", "token": "connect-secret"}}}}"#),
10177        )
10178        .unwrap();
10179
10180        let descriptor = connect_descriptor("opencode-http-sse");
10181        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
10182        assert!(backend.capabilities().attach_existing_process);
10183
10184        let connection = backend
10185            .start(crate::RuntimeStartRequest {
10186                cwd: home.clone(),
10187                launch: None,
10188                mcp_servers: Vec::new(),
10189                approval_policy: None,
10190            })
10191            .await
10192            .unwrap();
10193        let handle = connection.handle();
10194        assert_eq!(handle.runtime_id, "mock-session");
10195        match &handle.endpoint {
10196            crate::RuntimeEndpoint::Http {
10197                base_url: endpoint, ..
10198            } => assert_eq!(endpoint, &base_url),
10199            other => panic!("connect mode must join the running endpoint, got {other:?}"),
10200        }
10201
10202        let mut seen = Vec::new();
10203        while let Ok(line) = requests.try_recv() {
10204            seen.push(line);
10205        }
10206        assert!(seen
10207            .iter()
10208            .any(|line| line.starts_with("GET /global/health")
10209                && line.contains("bearer connect-secret")));
10210        assert!(seen.iter().any(
10211            |line| line.starts_with("POST /session") && line.contains("bearer connect-secret")
10212        ));
10213    }
10214
10215    /// UNI-5 dev/02, contract corrected by the 2026-08-31 blind walk: the
10216    /// full connect-mode attach path against a MOCK gateway bridge — no live
10217    /// gateway, no model spend. A scripted fake `openclaw` binary (a)
10218    /// asserts the REAL bridge contract — the resolved --url on argv and the
10219    /// credential via --token-file (the real bridge ignores the env var; the
10220    /// endpoint comes from openclaw-native `gateway.remote.url`, never the
10221    /// schema-invalid `gateway.url`) — then (b) speaks scripted ACP:
10222    /// initialize advertising sessionCapabilities.{list,resume},
10223    /// session/resume rebinding the requested session (join), and a
10224    /// prompted turn.
10225    #[tokio::test]
10226    async fn openclaw_connect_mode_attaches_lists_and_resumes_via_a_mock_bridge() {
10227        let home = connect_scratch_home("openclaw");
10228        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
10229        std::fs::write(
10230            home.join(".openclaw/openclaw.json"),
10231            r#"{"gateway": {"remote": {"url": "ws://127.0.0.1:19789"}, "auth": {"mode": "token", "token": "mock-gateway-token"}}}"#,
10232        )
10233        .unwrap();
10234        let script = home.join("openclaw");
10235        std::fs::write(
10236            &script,
10237            r#"#!/bin/sh
10238# Fake `openclaw acp` bridge: verify the connect-mode contract, then speak ACP.
10239[ "$1" = "acp" ] || { echo "unexpected argv: $*" >&2; exit 9; }
10240[ "$2" = "--url" ] && [ "$3" = "ws://127.0.0.1:19789" ] || { echo "missing --url: $*" >&2; exit 9; }
10241[ "$4" = "--token-file" ] || { echo "missing --token-file: $*" >&2; exit 9; }
10242[ "$(cat "$5")" = "mock-gateway-token" ] || { echo "token file wrong" >&2; exit 9; }
10243while IFS= read -r line; do
10244  case "$line" in
10245    *'"initialize"'*)
10246      printf '%s
10247' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"sessionCapabilities":{"list":{},"resume":{}}},"agentInfo":{"name":"openclaw-acp","version":"2026.7.1-2"},"authMethods":[]}}' ;;
10248    *'"session/resume"'*)
10249      printf '%s
10250' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:main"}}' ;;
10251    *'"session/new"'*)
10252      printf '%s
10253' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:fresh"}}' ;;
10254    *'"session/prompt"'*)
10255      printf '%s
10256' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"agent:main:main","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"joined"}}}}'
10257      printf '%s
10258' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}' ;;
10259  esac
10260done
10261"#,
10262        )
10263        .unwrap();
10264        use std::os::unix::fs::PermissionsExt;
10265        std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
10266
10267        let mut descriptor = crate::harness_support_registry()
10268            .harnesses
10269            .into_iter()
10270            .find(|harness| harness.id.as_str() == HarnessId::OPENCLAW)
10271            .expect("openclaw must be registered");
10272        descriptor
10273            .runtime
10274            .connect_launch
10275            .as_mut()
10276            .unwrap()
10277            .config_path = "~/.openclaw/openclaw.json".into();
10278        descriptor.runtime.default_launch.as_mut().unwrap().program =
10279            script.to_string_lossy().into_owned();
10280        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
10281        assert!(backend.capabilities().resume_session);
10282
10283        let joined = backend
10284            .attach(crate::RuntimeAttachRequest {
10285                runtime_id: "agent:main:main".into(),
10286                cwd: Some(home.clone()),
10287                launch: None,
10288                mcp_servers: Vec::new(),
10289                approval_policy: None,
10290            })
10291            .await;
10292        let mut connection = joined.expect("mock bridge attach must succeed");
10293        assert_eq!(connection.handle().runtime_id, "agent:main:main");
10294        let turn = connection
10295            .send_input(crate::RuntimeInput {
10296                text: "hello".into(),
10297                image_urls: Vec::new(),
10298            })
10299            .await;
10300        assert!(turn.is_ok(), "prompt through the mock bridge: {turn:?}");
10301        connection.close().await.unwrap();
10302    }
10303
10304    #[tokio::test]
10305    async fn connect_mode_fails_closed_without_a_protocol_client_or_config() {
10306        let home = connect_scratch_home("fail");
10307        std::fs::write(
10308            home.join("opencode-tui.json"),
10309            r#"{"server": {"url": "http://127.0.0.1:1", "token": "connect-secret"}}"#,
10310        )
10311        .unwrap();
10312
10313        let gateway_only = connect_descriptor("acp-v1-jsonrpc");
10314        let Err(error) = open_connect_descriptor(&gateway_only, &home) else {
10315            panic!("an ACP connect endpoint has no gateway client yet");
10316        };
10317        let message = format!("{error:?}");
10318        assert!(message.contains("acp-v1-jsonrpc"));
10319        assert!(!message.contains("connect-secret"));
10320
10321        let unreadable = connect_descriptor("opencode-http-sse");
10322        let missing_home = connect_scratch_home("missing");
10323        let Err(error) = open_connect_descriptor(&unreadable, &missing_home) else {
10324            panic!("an unreadable connect config must fail closed");
10325        };
10326        let message = format!("{error:?}");
10327        assert!(message.contains("opencode-tui.json"));
10328        assert!(!message.contains("connect-secret"));
10329    }
10330
10331    // ---------------------------------------------------------------------
10332    // ORCH-7 — `harness.v1.jobs.list` / `jobs.get` over the committed fixtures
10333    // ---------------------------------------------------------------------
10334
10335    fn jobs_fixture_root() -> PathBuf {
10336        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
10337    }
10338
10339    /// Point only the three job-bearing homes at the fixtures. Nothing else is
10340    /// read, so the host machine's own harness homes cannot leak into a row.
10341    fn jobs_fixture_homes() -> Value {
10342        let root = jobs_fixture_root();
10343        json!({
10344            "claude_code": root.join("claude_jobs_home/projects"),
10345            "hermes": root.join("hermes_home/state.db"),
10346            "openclaw": root.join("openclaw_home"),
10347        })
10348    }
10349
10350    fn jobs_list(params: Value) -> Value {
10351        let mut service = HarnessSessionService::new();
10352        service.handle(request(1, "harness.v1.jobs.list", params))
10353    }
10354
10355    fn job_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10356        result["jobs"]
10357            .as_array()
10358            .expect("jobs is an array")
10359            .iter()
10360            .find(|job| job["id"] == id)
10361            .unwrap_or_else(|| panic!("no job `{id}` in {result}"))
10362    }
10363
10364    #[test]
10365    fn gateway_health_derives_from_running_probe_and_install_state() {
10366        let running = RunningInstance {
10367            method: RunningInstanceMethod::GatewayConnect,
10368            evidence: "gateway endpoint 127.0.0.1:18789 accepted a TCP connect".into(),
10369            checked_at_ms: 1,
10370        };
10371        let up = gateway_health(
10372            HarnessId::OPENCLAW,
10373            true,
10374            Some(&running),
10375            Some("2026.7.1-2"),
10376        );
10377        assert_eq!(up.state, GatewayState::Up);
10378        assert!(up.endpoint.as_deref().unwrap().starts_with("ws://"));
10379        assert_eq!(up.version.as_deref(), Some("2026.7.1-2"));
10380        // Hermes consults its own `gateway status` when the WAL heuristic says
10381        // nothing; a fake binary decides the verdict (the env var is global, so
10382        // the up/down cases run inside this one test, never in parallel).
10383        let dir = std::env::temp_dir().join(format!("supercode-orch17-{}", std::process::id()));
10384        std::fs::create_dir_all(&dir).unwrap();
10385        let fake = dir.join("hermes");
10386        let write_fake = |body: &str| {
10387            std::fs::write(&fake, format!("#!/bin/sh\n{body}\n")).unwrap();
10388            #[cfg(unix)]
10389            {
10390                use std::os::unix::fs::PermissionsExt;
10391                std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o755)).unwrap();
10392            }
10393        };
10394        write_fake("echo '✗ Gateway service is not installed'");
10395        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| {
10396            *slot.borrow_mut() = Some((
10397                HarnessId::HERMES.to_string(),
10398                fake.to_string_lossy().into_owned(),
10399            ))
10400        });
10401        let down = gateway_health(HarnessId::HERMES, true, None, None);
10402        assert_eq!(down.state, GatewayState::Down, "{down:?}");
10403        assert!(down.endpoint.is_none());
10404        assert!(down.evidence.contains("not installed"));
10405        write_fake("echo 'Launchd plist: /x/ai.hermes.gateway.plist'; echo '✓ Gateway is supervised by launchd (PID 4242)'");
10406        let idle_but_up = gateway_health(HarnessId::HERMES, true, None, Some("0.21.0"));
10407        assert_eq!(idle_but_up.state, GatewayState::Up, "{idle_but_up:?}");
10408        assert!(idle_but_up.evidence.contains("PID 4242"));
10409        write_fake("echo 'something unparseable'");
10410        let no_verdict = gateway_health(HarnessId::HERMES, true, None, None);
10411        assert_eq!(no_verdict.state, GatewayState::Down);
10412        assert!(no_verdict.evidence.contains("no verdict"));
10413        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| *slot.borrow_mut() = None);
10414        let absent = gateway_health(HarnessId::HERMES, false, None, None);
10415        assert_eq!(absent.state, GatewayState::Unknown);
10416        let core = gateway_health(HarnessId::CODEX, true, None, Some("0.144.4"));
10417        assert_eq!(core.state, GatewayState::Unknown);
10418        assert!(core.evidence.contains("per session"));
10419    }
10420
10421    #[test]
10422    fn triggers_list_reads_both_stores_and_never_emits_secrets() {
10423        let response = triggers_list(json!({"homes": jobs_fixture_homes()}));
10424        let rows = response["result"]["triggers"]
10425            .as_array()
10426            .expect("triggers")
10427            .clone();
10428        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10429        assert!(
10430            hermes.iter().any(|r| r["name"] == "deploys"
10431                && r["route"] == "/webhooks/deploys"
10432                && r["kind"] == "webhook"),
10433            "{rows:#?}"
10434        );
10435        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10436        assert!(openclaw
10437            .iter()
10438            .any(|r| r["name"] == "wake" && r["kind"] == "builtin_wake"));
10439        assert!(openclaw.iter().any(|r| r["name"] == "gmail"
10440            && r["kind"] == "hook_mapping"
10441            && r["target"]["action"] == "agent"));
10442        let rendered = response.to_string();
10443        for secret in [
10444            "FAKE-WEBHOOK-HMAC-DO-NOT-EMIT",
10445            "FAKE-HOOK-TOKEN-DO-NOT-EMIT",
10446        ] {
10447            assert!(!rendered.contains(secret), "{rendered}");
10448        }
10449        let refused =
10450            triggers_list(json!({"harness": "claude-code", "homes": jobs_fixture_homes()}));
10451        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10452    }
10453
10454    fn triggers_list(params: Value) -> Value {
10455        let mut service = HarnessSessionService::new();
10456        service.handle(request(1, "harness.v1.triggers.list", params))
10457    }
10458
10459    #[test]
10460    fn routes_list_reads_both_gateway_configs_and_flags_the_defaults() {
10461        let response = routes_list(json!({"homes": jobs_fixture_homes()}));
10462        let rows = response["result"]["routes"]
10463            .as_array()
10464            .expect("routes")
10465            .clone();
10466        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10467        assert_eq!(hermes.len(), 2, "{rows:#?}");
10468        assert_eq!(hermes[0]["target"], "coder");
10469        assert_eq!(hermes[0]["match"]["platform"], "slack");
10470        assert_eq!(hermes[0]["match"]["chat_id"], "C0FIXTURE");
10471        assert_eq!(hermes[0]["specificity"], 4);
10472        assert_eq!(hermes[1]["default"], true);
10473        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10474        assert!(
10475            openclaw.iter().any(|r| r["target"] == "design"
10476                && r["match"]["platform"] == "slack"
10477                && r["specificity"] == 1),
10478            "{openclaw:#?}"
10479        );
10480        assert!(openclaw.iter().any(|r| r["default"] == true));
10481        // A core harness has no routing concept and is refused, never an empty list.
10482        let refused = routes_list(json!({"harness": "codex", "homes": jobs_fixture_homes()}));
10483        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10484    }
10485
10486    fn routes_list(params: Value) -> Value {
10487        let mut service = HarnessSessionService::new();
10488        service.handle(request(1, "harness.v1.routes.list", params))
10489    }
10490
10491    #[test]
10492    fn jobs_list_projects_every_fixture_store_onto_the_uniform_row() {
10493        let response = jobs_list(json!({"homes": jobs_fixture_homes()}));
10494        let result = &response["result"];
10495        let ids: Vec<&str> = result["jobs"]
10496            .as_array()
10497            .unwrap()
10498            .iter()
10499            .map(|job| job["id"].as_str().unwrap())
10500            .collect();
10501        assert_eq!(
10502            ids,
10503            vec![
10504                "release-watch",
10505                "toolu_wake_recheck",
10506                "digest-15m",
10507                "nightly-audit",
10508                "coder-standup",
10509                "ops-once-boot",
10510                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10511                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10512                "cron_standup",
10513                "cron_reindex",
10514            ],
10515            "{result}"
10516        );
10517
10518        // OpenClaw, pinned shape: rows come from `state/openclaw.sqlite`
10519        // (`cron_jobs.job_json` + runtime columns), captured from a real
10520        // 2026.7.1-2 gateway.
10521        let health = job_row(result, "85ad7832-896f-42be-af31-3e1ed2fbdc4b");
10522        assert_eq!(health["harness"], "openclaw");
10523        assert_eq!(health["schedule"]["kind"], "interval");
10524        assert_eq!(health["schedule"]["minutes"], 10.0);
10525        assert_eq!(health["session_target"], "isolated");
10526        assert_eq!(health["payload"]["kind"], "prompt");
10527        assert_eq!(health["payload"]["text"], "nightly health check");
10528        // ORCH-13: the mode word (`announce`) and the channel it announces on
10529        // (`last`) are separate facts, and the store keeps both — in
10530        // `job_json.delivery` and in the `delivery_*` columns beside it.
10531        assert_eq!(health["deliver"]["mode"], "announce");
10532        assert_eq!(health["deliver"]["target"], "last");
10533        assert_eq!(health["next_run_at"], "2026-09-03T06:52:26Z");
10534        let digest = job_row(result, "8bb7d938-ca46-4a6d-90eb-c92331155566");
10535        assert_eq!(digest["schedule"]["kind"], "cron");
10536        assert_eq!(digest["schedule"]["expr"], "0 9 * * 1");
10537        assert_eq!(digest["session_target"], "main");
10538        assert_eq!(digest["payload"]["kind"], "system_event");
10539
10540        // Claude Code: session-scoped, one recurring cron and one one-shot wakeup.
10541        let cron = job_row(result, "release-watch");
10542        assert_eq!(cron["harness"], "claude-code");
10543        assert_eq!(cron["scope"], "session");
10544        assert_eq!(cron["session_id"], "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
10545        assert_eq!(cron["schedule"]["kind"], "cron");
10546        assert_eq!(cron["schedule"]["expr"], "*/10 * * * *");
10547        assert_eq!(cron["schedule"]["display"], "*/10 * * * *");
10548        assert_eq!(cron["payload"]["kind"], "prompt");
10549        assert_eq!(cron["recurring"], true);
10550        assert_eq!(cron["deliver"]["target"], "session");
10551        let wakeup = job_row(result, "toolu_wake_recheck");
10552        assert_eq!(wakeup["payload"]["kind"], "wakeup");
10553        assert_eq!(wakeup["schedule"]["kind"], "once");
10554        assert_eq!(wakeup["recurring"], false);
10555        assert_eq!(wakeup["state"], "pending");
10556
10557        // Hermes: install-scoped, interval + origin delivery, and a paused cron.
10558        let interval = job_row(result, "digest-15m");
10559        assert_eq!(interval["harness"], "hermes");
10560        assert_eq!(interval["scope"], "install");
10561        assert_eq!(interval["profile"], Value::Null);
10562        assert_eq!(interval["schedule"]["kind"], "interval");
10563        assert_eq!(interval["schedule"]["minutes"], 15.0);
10564        assert_eq!(interval["schedule"]["display"], "every 15 min");
10565        assert_eq!(interval["deliver"]["target"], "origin");
10566        assert_eq!(interval["deliver"]["chat_id"], "-1002233445566");
10567        assert_eq!(interval["next_run_at"], "2026-09-02T11:15:00Z");
10568        assert_eq!(interval["last_status"], "ok");
10569        let nightly = job_row(result, "nightly-audit");
10570        assert_eq!(nightly["schedule"]["expr"], "0 3 * * *");
10571        assert_eq!(nightly["deliver"]["target"], "local");
10572        assert_eq!(nightly["enabled"], false);
10573        assert_eq!(nightly["state"], "paused");
10574        // The per-profile store carries the profile name from its own path.
10575        let profiled = job_row(result, "ops-once-boot");
10576        assert_eq!(profiled["profile"], "ops");
10577        assert_eq!(profiled["schedule"]["kind"], "once");
10578        assert_eq!(profiled["schedule"]["run_at"], "2026-09-03T06:00:00Z");
10579        assert_eq!(profiled["payload"]["kind"], "script");
10580        // An explicit `<platform>:<chat>` target carries the chat itself.
10581        assert_eq!(profiled["deliver"]["target"], "slack:C0429ABCD");
10582        assert_eq!(profiled["deliver"]["chat_id"], "C0429ABCD");
10583        assert_eq!(profiled["recurring"], false);
10584
10585        // ORCH-13: a job delivering to its creating conversation carries that
10586        // conversation's whole surface — platform word, chat AND thread.
10587        let standup_to_group = job_row(result, "coder-standup");
10588        assert_eq!(standup_to_group["deliver"]["target"], "origin");
10589        assert_eq!(standup_to_group["deliver"]["chat_id"], "-100777");
10590        assert_eq!(standup_to_group["deliver"]["thread_id"], "55");
10591        // Hermes has no mode word and routes by adapter profile, not account.
10592        assert!(standup_to_group["deliver"]["mode"].is_null());
10593        assert!(standup_to_group["deliver"]["account"].is_null());
10594
10595        // OpenClaw: the session target and the delivery mode are the row's own
10596        // columns, not a footnote.
10597        let standup = job_row(result, "cron_standup");
10598        assert_eq!(standup["harness"], "openclaw");
10599        assert_eq!(standup["session_target"], "isolated");
10600        assert_eq!(standup["deliver"]["mode"], "announce");
10601        assert_eq!(standup["deliver"]["target"], "slack");
10602        assert_eq!(standup["deliver"]["chat_id"], "C0429ABCD");
10603        assert_eq!(standup["payload"]["kind"], "prompt");
10604        assert_eq!(standup["profile"], "main");
10605        let reindex = job_row(result, "cron_reindex");
10606        assert_eq!(reindex["session_target"], "main");
10607        assert_eq!(reindex["payload"]["kind"], "system_event");
10608        assert_eq!(reindex["schedule"]["kind"], "interval");
10609        assert_eq!(reindex["schedule"]["display"], "every 240 min");
10610        assert_eq!(reindex["enabled"], false);
10611
10612        // Every store consulted is named, so an empty answer is never silent.
10613        let states: Vec<(&str, &str)> = result["sources"]
10614            .as_array()
10615            .unwrap()
10616            .iter()
10617            .map(|source| {
10618                (
10619                    source["harness"].as_str().unwrap(),
10620                    source["state"].as_str().unwrap(),
10621                )
10622            })
10623            .collect();
10624        // The `coder` profile home has no cron store at all: it is named as
10625        // `absent_store`, not skipped, so "this profile schedules nothing" and
10626        // "this profile was never looked at" stay distinguishable.
10627        assert_eq!(
10628            states,
10629            vec![
10630                ("claude-code", "scanned"),
10631                ("hermes", "read"),
10632                ("hermes", "absent_store"),
10633                ("hermes", "read"),
10634                ("openclaw", "read"),
10635                ("openclaw", "read"),
10636            ],
10637            "{result}"
10638        );
10639    }
10640
10641    #[test]
10642    fn jobs_list_filters_by_harness_session_and_profile() {
10643        let by_harness = jobs_list(json!({"harness": "openclaw", "homes": jobs_fixture_homes()}));
10644        let ids: Vec<&str> = by_harness["result"]["jobs"]
10645            .as_array()
10646            .unwrap()
10647            .iter()
10648            .map(|job| job["id"].as_str().unwrap())
10649            .collect();
10650        assert_eq!(
10651            ids,
10652            vec![
10653                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10654                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10655                "cron_standup",
10656                "cron_reindex",
10657            ]
10658        );
10659
10660        let by_session = jobs_list(json!({
10661            "session": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
10662            "homes": jobs_fixture_homes(),
10663        }));
10664        let jobs = by_session["result"]["jobs"].as_array().unwrap();
10665        assert_eq!(jobs.len(), 2, "{by_session}");
10666        assert!(jobs
10667            .iter()
10668            .all(|job| job["harness"] == "claude-code" && job["scope"] == "session"));
10669
10670        let by_profile = jobs_list(json!({
10671            "harness": "hermes",
10672            "profile": "ops",
10673            "homes": jobs_fixture_homes(),
10674        }));
10675        let jobs = by_profile["result"]["jobs"].as_array().unwrap();
10676        assert_eq!(jobs.len(), 1, "{by_profile}");
10677        assert_eq!(jobs[0]["id"], "ops-once-boot");
10678    }
10679
10680    #[test]
10681    fn jobs_get_answers_with_the_row_and_the_verbatim_native_record() {
10682        let mut service = HarnessSessionService::new();
10683        let hermes = service.handle(request(
10684            1,
10685            "harness.v1.jobs.get",
10686            json!({"harness": "hermes", "id": "digest-15m", "homes": jobs_fixture_homes()}),
10687        ));
10688        assert_eq!(hermes["result"]["job"]["schedule"]["kind"], "interval");
10689        // Native fields the uniform row does not carry survive on `source`.
10690        assert_eq!(hermes["result"]["source"]["provider"], "nous");
10691        assert_eq!(hermes["result"]["source"]["failure_deliver"], "local");
10692
10693        let claude = service.handle(request(
10694            2,
10695            "harness.v1.jobs.get",
10696            json!({"harness": "claude-code", "id": "release-watch", "homes": jobs_fixture_homes()}),
10697        ));
10698        assert_eq!(claude["result"]["job"]["payload"]["kind"], "prompt");
10699        assert_eq!(
10700            claude["result"]["source"]["tool_use_id"],
10701            "toolu_cron_release_watch"
10702        );
10703
10704        let missing = service.handle(request(
10705            3,
10706            "harness.v1.jobs.get",
10707            json!({"harness": "hermes", "id": "no-such-job", "homes": jobs_fixture_homes()}),
10708        ));
10709        assert!(missing["error"]["message"]
10710            .as_str()
10711            .is_some_and(|message| message.contains("no scheduled job `no-such-job`")));
10712    }
10713
10714    #[test]
10715    fn jobs_refuse_a_harness_without_a_scheduled_job_concept() {
10716        let mut service = HarnessSessionService::new();
10717        for (id, method, params) in [
10718            (
10719                1,
10720                "harness.v1.jobs.list",
10721                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10722            ),
10723            (
10724                2,
10725                "harness.v1.jobs.get",
10726                json!({"harness": "codex", "id": "anything"}),
10727            ),
10728        ] {
10729            let response = service.handle(request(id, method, params));
10730            assert_eq!(response["error"]["code"], -32020, "{response}");
10731            assert!(response["error"]["message"]
10732                .as_str()
10733                .is_some_and(|message| message.contains("has no scheduled jobs")));
10734            assert!(response.get("result").is_none());
10735        }
10736    }
10737
10738    #[test]
10739    fn jobs_list_reports_a_migrated_openclaw_store_as_absent_instead_of_failing() {
10740        let scratch = std::env::temp_dir().join(format!(
10741            "supercode-jobs-migrated-{}-{}",
10742            std::process::id(),
10743            generated_session_id()
10744        ));
10745        std::fs::create_dir_all(&scratch).unwrap();
10746        let response = jobs_list(json!({
10747            "harness": "openclaw",
10748            "homes": {"openclaw": scratch.clone()},
10749        }));
10750        let result = &response["result"];
10751        assert_eq!(result["jobs"].as_array().unwrap().len(), 0, "{result}");
10752        assert_eq!(result["sources"][0]["state"], "absent_store");
10753        assert_eq!(result["sources"][0]["harness"], "openclaw");
10754        std::fs::remove_dir_all(&scratch).ok();
10755    }
10756
10757    // ---------------------------------------------------------------------
10758    // ORCH-8 — `harness.v1.runs.list` / `runs.get` over the committed fire
10759    // stores: Hermes's `cron/executions.db` (root home + profile home) and
10760    // OpenClaw's `cron_run_logs`. Every fixture row is written by
10761    // `tests/fixtures/gen_runs_fixtures.py` against the harnesses' own DDL.
10762    // ---------------------------------------------------------------------
10763
10764    /// The health job in the committed OpenClaw fixture, which fired twice.
10765    const OPENCLAW_HEALTH_JOB: &str = "85ad7832-896f-42be-af31-3e1ed2fbdc4b";
10766    /// The digest job, whose single fire predates run ids.
10767    const OPENCLAW_DIGEST_JOB: &str = "8bb7d938-ca46-4a6d-90eb-c92331155566";
10768
10769    fn runs_list(params: Value) -> Value {
10770        let mut service = HarnessSessionService::new();
10771        service.handle(request(1, "harness.v1.runs.list", params))
10772    }
10773
10774    fn run_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10775        result["runs"]
10776            .as_array()
10777            .expect("runs is an array")
10778            .iter()
10779            .find(|run| run["id"] == id)
10780            .unwrap_or_else(|| panic!("no run `{id}` in {result}"))
10781    }
10782
10783    #[test]
10784    fn runs_list_projects_both_fixture_stores_onto_the_uniform_row() {
10785        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10786        let result = &response["result"];
10787        let ids: Vec<&str> = result["runs"]
10788            .as_array()
10789            .expect("runs is an array")
10790            .iter()
10791            .map(|run| run["id"].as_str().unwrap())
10792            .collect();
10793        let digest_fire = format!("{OPENCLAW_DIGEST_JOB}#1");
10794        assert_eq!(
10795            ids,
10796            vec![
10797                // Hermes, newest claim first, root ledger then profile ledger.
10798                "b2c3d4e5f60718293a4b5c6d7e8f9012",
10799                "a1b2c3d4e5f60718293a4b5c6d7e8f90",
10800                "c3d4e5f60718293a4b5c6d7e8f901234",
10801                "f60718293a4b5c6d7e8f901234567890",
10802                "e5f60718293a4b5c6d7e8f9012345678",
10803                "d4e5f60718293a4b5c6d7e8f90123456",
10804                // OpenClaw, newest `ts` first.
10805                "run_health_0002",
10806                digest_fire.as_str(),
10807                "run_health_0001",
10808            ],
10809            "{result}"
10810        );
10811
10812        // The harness's OWN outcome word survives; nothing is renamed onto a
10813        // shared vocabulary.
10814        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10815        assert_eq!(failed["harness"], "hermes");
10816        assert_eq!(failed["job_id"], "job42");
10817        assert_eq!(failed["status"], "failed");
10818        assert_eq!(failed["error"], "provider returned 500 after 3 attempts");
10819        assert_eq!(failed["claimed_at"], "2026-09-02T13:05:00.100442");
10820
10821        // Hermes's `unknown` — an attempt whose owner died before writing a
10822        // terminal state — is a fourth status, not folded into `failed`.
10823        let abandoned = run_row(result, "d4e5f60718293a4b5c6d7e8f90123456");
10824        assert_eq!(abandoned["status"], "unknown");
10825        assert_eq!(abandoned["job_id"], "ops-once-boot");
10826
10827        // An unterminated fire has no finish, and no session is invented.
10828        let running = run_row(result, "c3d4e5f60718293a4b5c6d7e8f901234");
10829        assert_eq!(running["status"], "running");
10830        assert!(running["finished_at"].is_null(), "{running}");
10831        assert!(running["session_id"].is_null(), "{running}");
10832
10833        // OpenClaw records the session on the row itself, and epoch-ms
10834        // timestamps are rendered as RFC 3339.
10835        let ok = run_row(result, "run_health_0001");
10836        assert_eq!(ok["harness"], "openclaw");
10837        assert_eq!(ok["job_id"], OPENCLAW_HEALTH_JOB);
10838        assert_eq!(ok["status"], "ok");
10839        assert_eq!(ok["started_at"], "2026-09-02T08:30:00.000Z");
10840        assert_eq!(ok["finished_at"], "2026-09-02T08:30:30.000Z");
10841        assert_eq!(ok["session_id"], "3dd577ae-a0a3-4b5b-8063-f402be4f5fd4");
10842        // OpenClaw's run log is written once, at finish: there is no claim.
10843        assert!(ok["claimed_at"].is_null(), "{ok}");
10844
10845        // A run-log row with no `run_id` falls back to the store's own
10846        // `(job_id, seq)` key rather than being dropped.
10847        assert_eq!(run_row(result, &digest_fire)["status"], "skipped");
10848
10849        // ORCH-13: a fire whose delivery nothing recorded says so, rather than
10850        // borrowing a neighbouring fire's outcome. Both of these ran on jobs
10851        // that deliver `local` (or have no job record at all), so no
10852        // obligation is addressed to a surface they could match.
10853        for id in [
10854            "b2c3d4e5f60718293a4b5c6d7e8f9012",
10855            "d4e5f60718293a4b5c6d7e8f90123456",
10856        ] {
10857            assert!(run_row(result, id)["delivery"].is_null(), "{id}");
10858        }
10859
10860        // Every store consulted is named, including the profile home that has
10861        // no ledger — an empty history and an absent store are different.
10862        let sources = result["sources"].as_array().unwrap();
10863        let states: Vec<(&str, &str)> = sources
10864            .iter()
10865            .map(|source| {
10866                (
10867                    source["harness"].as_str().unwrap(),
10868                    source["state"].as_str().unwrap(),
10869                )
10870            })
10871            .collect();
10872        assert_eq!(
10873            states,
10874            vec![
10875                ("hermes", "read"),
10876                ("hermes", "absent_store"),
10877                ("hermes", "read"),
10878                ("openclaw", "read"),
10879            ],
10880            "{result}"
10881        );
10882        assert_eq!(sources[2]["profile"], "ops");
10883        assert!(sources[3]["path"]
10884            .as_str()
10885            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10886    }
10887
10888    #[test]
10889    fn runs_list_joins_a_hermes_fire_to_the_session_it_opened() {
10890        let response = runs_list(json!({
10891            "harness": "hermes",
10892            "job": "job42",
10893            "homes": jobs_fixture_homes(),
10894        }));
10895        let result = &response["result"];
10896        assert_eq!(result["runs"].as_array().unwrap().len(), 2, "{result}");
10897
10898        // Hermes writes NO link from an execution to its session. The fire
10899        // that ran the agent is joined to `cron_job42_<stamp>` because that
10900        // id's instant falls inside its [claimed_at, finished_at] window.
10901        let ran = run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90");
10902        assert_eq!(ran["session_id"], "cron_job42_20260902_120000");
10903
10904        // The later fire failed before opening one. Its window holds no
10905        // session, so the row says so instead of re-using the earlier fire's
10906        // — the join is per-FIRE, not per-job.
10907        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10908        assert!(failed["session_id"].is_null(), "{failed}");
10909    }
10910
10911    /// ORCH-13: where a fire's output went, read from each harness's own
10912    /// delivery record — Hermes's `delivery_obligations` ledger inside
10913    /// `state.db`, OpenClaw's `delivery_*` run-log columns.
10914    #[test]
10915    fn runs_list_reads_the_delivery_each_harness_recorded_for_a_fire() {
10916        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10917        let result = &response["result"];
10918
10919        // Hermes: the ledger is the GATEWAY's, keyed by conversation and
10920        // surface, so the fire's own [claimed_at, finished_at] window picks
10921        // the obligation. The fire succeeded and so did the send.
10922        let delivered = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
10923        assert_eq!(delivered["status"], "completed");
10924        assert_eq!(delivered["delivery"]["state"], "delivered");
10925        assert_eq!(delivered["delivery"]["target"], "telegram:-100777:55");
10926        assert_eq!(delivered["delivery"]["attempts"], 1);
10927        assert!(delivered["delivery"]["last_error"].is_null(), "{delivered}");
10928        assert_eq!(
10929            delivered["delivery"]["delivered_at"],
10930            "2026-09-02T09:00:30.400Z"
10931        );
10932
10933        // The next fire of the same job ALSO succeeded — and its output never
10934        // arrived. That is the fact `status` alone cannot carry.
10935        let undelivered = run_row(result, "f60718293a4b5c6d7e8f901234567890");
10936        assert_eq!(undelivered["status"], "completed");
10937        assert_eq!(undelivered["delivery"]["state"], "failed");
10938        assert_eq!(undelivered["delivery"]["attempts"], 3);
10939        assert_eq!(
10940            undelivered["delivery"]["last_error"],
10941            "telegram send failed: Bad Request: chat not found"
10942        );
10943        // Only a delivered obligation carries an instant of delivery; the
10944        // ledger's `updated_at` on a failed row dates the failure.
10945        assert!(
10946            undelivered["delivery"]["delivered_at"].is_null(),
10947            "{undelivered}"
10948        );
10949
10950        // OpenClaw writes the outcome onto the run-log row and declares the
10951        // address on the job, so the row's target is joined from `cron_jobs`.
10952        let announced = run_row(result, "run_health_0001");
10953        assert_eq!(announced["delivery"]["state"], "delivered");
10954        assert_eq!(announced["delivery"]["target"], "last");
10955        // Its run log counts no attempts and stamps no delivered-at.
10956        assert!(announced["delivery"]["attempts"].is_null(), "{announced}");
10957        assert!(
10958            announced["delivery"]["delivered_at"].is_null(),
10959            "{announced}"
10960        );
10961        let refused = run_row(result, "run_health_0002");
10962        assert_eq!(refused["delivery"]["state"], "not-delivered");
10963        assert_eq!(refused["delivery"]["last_error"], "channel_not_found");
10964
10965        // A run-log row with no delivery columns at all recorded no delivery:
10966        // the job's declared target is not evidence that anything was sent.
10967        let skipped = run_row(result, &format!("{OPENCLAW_DIGEST_JOB}#1"));
10968        assert!(skipped["delivery"].is_null(), "{skipped}");
10969    }
10970
10971    /// A Hermes fire whose session carries a `session_key` is matched on that
10972    /// key FIRST — the most specific question the ledger can answer. Proven by
10973    /// moving the obligations off the job's surface on a COPY of the fixture,
10974    /// so only the session-key question can still find them.
10975    #[test]
10976    fn runs_list_matches_a_hermes_obligation_by_the_session_key_first() {
10977        let scratch = std::env::temp_dir().join(format!(
10978            "supercode-runs-delivery-{}-{}",
10979            std::process::id(),
10980            generated_session_id()
10981        ));
10982        std::fs::create_dir_all(scratch.join("cron")).unwrap();
10983        let fixture = jobs_fixture_root().join("hermes_home");
10984        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
10985        for name in ["cron/executions.db", "cron/jobs.json"] {
10986            std::fs::copy(fixture.join(name), scratch.join(name)).unwrap();
10987        }
10988        {
10989            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
10990            // The obligations now sit on a surface no job in this store
10991            // delivers to, so the surface question cannot match them.
10992            connection
10993                .execute(
10994                    "UPDATE delivery_obligations SET platform = 'slack', chat_id = 'C0FALLBACK'",
10995                    [],
10996                )
10997                .unwrap();
10998            // A cron fire that ran inside a keyed conversation: the session
10999            // the window recovers carries `tg-coder-1`'s key.
11000            connection
11001                .execute(
11002                    "INSERT INTO sessions (id, source, session_key, started_at) VALUES \
11003                     ('cron_coder-standup_20260902_090010', 'cron', \
11004                      'agent:coder:telegram:group:-100777:55', 1788339610.0)",
11005                    [],
11006                )
11007                .unwrap();
11008        }
11009        let response = runs_list(json!({
11010            "harness": "hermes",
11011            "job": "coder-standup",
11012            "homes": {"hermes": scratch.join("state.db")},
11013        }));
11014        let result = &response["result"];
11015        let matched = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
11016        assert_eq!(
11017            matched["session_id"], "cron_coder-standup_20260902_090010",
11018            "{result}"
11019        );
11020        assert_eq!(matched["delivery"]["state"], "delivered", "{result}");
11021        assert_eq!(
11022            matched["delivery"]["target"], "slack:C0FALLBACK:55",
11023            "{result}"
11024        );
11025        std::fs::remove_dir_all(&scratch).ok();
11026    }
11027
11028    #[test]
11029    fn runs_list_follows_a_compression_chain_to_the_readable_tip() {
11030        // A fire whose session was compressed mid-run is only readable at the
11031        // continuation, so that is what the row must report. Built on a COPY
11032        // of the committed fixture: no test writes to a fixture or to a real
11033        // harness home.
11034        let scratch = std::env::temp_dir().join(format!(
11035            "supercode-runs-compressed-{}-{}",
11036            std::process::id(),
11037            generated_session_id()
11038        ));
11039        std::fs::create_dir_all(scratch.join("cron")).unwrap();
11040        let fixture = jobs_fixture_root().join("hermes_home");
11041        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
11042        std::fs::copy(
11043            fixture.join("cron/executions.db"),
11044            scratch.join("cron/executions.db"),
11045        )
11046        .unwrap();
11047        {
11048            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
11049            connection
11050                .execute(
11051                    "UPDATE sessions SET end_reason = 'compression' WHERE id = ?1",
11052                    ["cron_job42_20260902_120000"],
11053                )
11054                .unwrap();
11055            connection
11056                .execute(
11057                    "INSERT INTO sessions (id, source, parent_session_id, started_at) \
11058                     VALUES ('job42-after-compaction', 'cron', \
11059                             'cron_job42_20260902_120000', 1788350000.0)",
11060                    [],
11061                )
11062                .unwrap();
11063        }
11064        let response = runs_list(json!({
11065            "harness": "hermes",
11066            "job": "job42",
11067            "homes": {"hermes": scratch.join("state.db")},
11068        }));
11069        let result = &response["result"];
11070        assert_eq!(
11071            run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90")["session_id"],
11072            "job42-after-compaction",
11073            "{result}"
11074        );
11075        std::fs::remove_dir_all(&scratch).ok();
11076    }
11077
11078    #[test]
11079    fn runs_list_filters_by_job_and_caps_by_limit() {
11080        let by_job = runs_list(json!({
11081            "harness": "openclaw",
11082            "job": OPENCLAW_HEALTH_JOB,
11083            "homes": jobs_fixture_homes(),
11084        }));
11085        let ids: Vec<&str> = by_job["result"]["runs"]
11086            .as_array()
11087            .unwrap()
11088            .iter()
11089            .map(|run| run["id"].as_str().unwrap())
11090            .collect();
11091        assert_eq!(ids, vec!["run_health_0002", "run_health_0001"], "{by_job}");
11092
11093        let capped = runs_list(json!({
11094            "harness": "openclaw",
11095            "limit": 1,
11096            "homes": jobs_fixture_homes(),
11097        }));
11098        let runs = capped["result"]["runs"].as_array().unwrap();
11099        assert_eq!(runs.len(), 1, "{capped}");
11100        // Newest first, so the cap keeps the recent fire.
11101        assert_eq!(runs[0]["id"], "run_health_0002");
11102    }
11103
11104    #[test]
11105    fn runs_get_answers_with_the_row_and_the_verbatim_native_record() {
11106        let mut service = HarnessSessionService::new();
11107        let hermes = service.handle(request(
11108            1,
11109            "harness.v1.runs.get",
11110            json!({
11111                "harness": "hermes",
11112                "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
11113                "homes": jobs_fixture_homes(),
11114            }),
11115        ));
11116        assert_eq!(hermes["result"]["run"]["status"], "completed");
11117        assert_eq!(
11118            hermes["result"]["run"]["session_id"],
11119            "cron_job42_20260902_120000"
11120        );
11121        // Ledger columns the uniform row does not carry survive on `source`.
11122        assert_eq!(hermes["result"]["source"]["source"], "scheduler");
11123        assert_eq!(hermes["result"]["source"]["pid"], 4242);
11124        assert_eq!(hermes["result"]["source"]["process_id"], "9f1c2d");
11125
11126        let openclaw = service.handle(request(
11127            2,
11128            "harness.v1.runs.get",
11129            json!({
11130                "harness": "openclaw",
11131                "id": "run_health_0002",
11132                "homes": jobs_fixture_homes(),
11133            }),
11134        ));
11135        assert_eq!(openclaw["result"]["run"]["status"], "error");
11136        // ORCH-13: the run's delivery is projected AND the store's own columns
11137        // stay verbatim on `source`, so nothing about the fire is lost.
11138        assert_eq!(
11139            openclaw["result"]["source"]["delivery_status"],
11140            "not-delivered"
11141        );
11142        assert_eq!(
11143            openclaw["result"]["source"]["delivery_error"],
11144            "channel_not_found"
11145        );
11146        assert_eq!(openclaw["result"]["source"]["delivered"], 0);
11147        assert_eq!(
11148            openclaw["result"]["run"]["delivery"]["state"],
11149            "not-delivered"
11150        );
11151        assert_eq!(
11152            openclaw["result"]["run"]["delivery"]["last_error"],
11153            "channel_not_found"
11154        );
11155
11156        let missing = service.handle(request(
11157            3,
11158            "harness.v1.runs.get",
11159            json!({"harness": "hermes", "id": "no-such-run", "homes": jobs_fixture_homes()}),
11160        ));
11161        assert!(missing["error"]["message"]
11162            .as_str()
11163            .is_some_and(|message| message.contains("no run `no-such-run`")));
11164    }
11165
11166    #[test]
11167    fn runs_refuse_a_harness_that_keeps_no_run_store() {
11168        let mut service = HarnessSessionService::new();
11169        for (id, method, params) in [
11170            // Claude Code HAS scheduled jobs but no fire store: its fires are
11171            // ordinary turns. It must refuse, not answer with an empty list.
11172            (
11173                1,
11174                "harness.v1.runs.list",
11175                json!({"harness": "claude-code", "homes": jobs_fixture_homes()}),
11176            ),
11177            (
11178                2,
11179                "harness.v1.runs.get",
11180                json!({"harness": "claude-code", "id": "anything"}),
11181            ),
11182            (
11183                3,
11184                "harness.v1.runs.list",
11185                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
11186            ),
11187        ] {
11188            let response = service.handle(request(id, method, params));
11189            assert_eq!(response["error"]["code"], -32020, "{response}");
11190            assert!(response["error"]["message"]
11191                .as_str()
11192                .is_some_and(|message| message.contains("keeps no run store")));
11193            assert!(response.get("result").is_none());
11194        }
11195    }
11196
11197    #[test]
11198    fn runs_list_reports_an_install_with_no_run_store_as_absent() {
11199        let scratch = std::env::temp_dir().join(format!(
11200            "supercode-runs-empty-{}-{}",
11201            std::process::id(),
11202            generated_session_id()
11203        ));
11204        std::fs::create_dir_all(&scratch).unwrap();
11205        let response = runs_list(json!({
11206            "harness": "openclaw",
11207            "homes": {"openclaw": scratch.clone()},
11208        }));
11209        let result = &response["result"];
11210        assert_eq!(result["runs"].as_array().unwrap().len(), 0, "{result}");
11211        assert_eq!(result["sources"][0]["state"], "absent_store");
11212        assert!(result["sources"][0]["path"]
11213            .as_str()
11214            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
11215        std::fs::remove_dir_all(&scratch).ok();
11216    }
11217}