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/// A session supercode starts or resumes runs without approval prompts
3766/// (`yolo`) unless the caller asks for the harness's own (`default`).
3767#[derive(Debug, Clone, Copy, Default, Deserialize)]
3768#[serde(rename_all = "snake_case")]
3769enum ResumePolicy {
3770    Default,
3771    #[default]
3772    Yolo,
3773}
3774
3775#[derive(Deserialize)]
3776struct ResumeInstructionsParams {
3777    locator: SessionLocator,
3778    #[serde(default)]
3779    cwd: Option<PathBuf>,
3780    #[serde(default)]
3781    policy: ResumePolicy,
3782}
3783
3784/// `harness.v1.workflow.load` parameters: which harness's board, and its home.
3785#[derive(Deserialize)]
3786struct WorkflowLoadParams {
3787    from: crate::workflow_doors::WorkflowHarness,
3788    home: PathBuf,
3789}
3790
3791/// ONT-4 `harness.v1.orchestration.load` parameters. `flavor` says which layout the
3792/// folder is read as; our own is the default.
3793#[derive(Deserialize)]
3794struct OrchestrationLoadParams {
3795    root: PathBuf,
3796    #[serde(default)]
3797    flavor: crate::orchestration_doors::HomeFlavor,
3798}
3799
3800/// ONT-4 `harness.v1.orchestration.save` parameters. `vault` is merged into the
3801/// home's own secrets; a caller that sends none keeps what is on disk.
3802#[derive(Deserialize)]
3803struct OrchestrationSaveParams {
3804    root: PathBuf,
3805    orchestration: crate::orchestration::Orchestration,
3806    #[serde(default)]
3807    vault: BTreeMap<String, String>,
3808}
3809
3810/// ONT-4 `harness.v1.orchestration.compile` parameters.
3811#[derive(Deserialize)]
3812struct OrchestrationCompileParams {
3813    from: crate::orchestration_doors::OrchestrationHarness,
3814    home: PathBuf,
3815}
3816
3817/// ONT-4 `harness.v1.orchestration.decompile` parameters. `source` is the home the
3818/// orchestration was compiled from: it is re-compiled to recover the io bookkeeping
3819/// that byte reuse and the live-store refusal (UNI-18) are decided from.
3820#[derive(Deserialize)]
3821struct OrchestrationDecompileParams {
3822    to: crate::orchestration_doors::OrchestrationHarness,
3823    orchestration: crate::orchestration::Orchestration,
3824    source: PathBuf,
3825    #[serde(default)]
3826    source_flavor: crate::orchestration_doors::SourceFlavor,
3827    dest: PathBuf,
3828    #[serde(default)]
3829    vault: BTreeMap<String, String>,
3830}
3831
3832/// `harness.v1.orchestration.import` parameters: another harness's home, and the
3833/// folder of ours it becomes.
3834#[derive(Deserialize)]
3835struct OrchestrationImportParams {
3836    from: crate::orchestration_doors::OrchestrationHarness,
3837    home: PathBuf,
3838    into: PathBuf,
3839}
3840
3841/// `harness.v1.orchestration.export` parameters: a folder of ours, and the home of
3842/// another harness it becomes.
3843#[derive(Deserialize)]
3844struct OrchestrationExportParams {
3845    to: crate::orchestration_doors::OrchestrationHarness,
3846    root: PathBuf,
3847    dest: PathBuf,
3848}
3849
3850/// `harness.v1.jobs.get` parameters.
3851#[derive(Deserialize)]
3852struct JobsGetParams {
3853    harness: String,
3854    id: String,
3855    #[serde(default)]
3856    homes: crate::HarnessHomes,
3857}
3858
3859/// ORCH-18: run one mutating job verb through the harness's own CLI.
3860///
3861/// The refusal ladder is deliberate: a harness with no scheduled-job concept
3862/// at all answers with the SAME sentence `jobs.list` gives it, and a harness
3863/// that has jobs but publishes no client-callable verb (Claude Code, whose
3864/// jobs are created by the model inside a session) answers with its own
3865/// reason. Neither is ever a silent no-op.
3866fn mutate_job(
3867    verb: crate::jobs_control::JobVerb,
3868    params: Value,
3869) -> std::result::Result<Value, ServiceError> {
3870    let mutation = decode::<crate::jobs_control::JobMutation>(params)?;
3871    refuse_harness_without_jobs(&mutation.harness, &format!("jobs.{}", verb.as_str()))?;
3872    let outcome = crate::jobs_control::mutate(verb, &mutation).map_err(job_control_error)?;
3873    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3874}
3875
3876/// ORCH-22: run one mutating skills verb through the harness's own door.
3877///
3878/// The refusal ladder mirrors `jobs.*`: a harness with no skills root at all
3879/// answers with the same sentence `skills.list` gives it, and a harness whose
3880/// door does not publish this verb (OpenClaw has no `skills remove` at the
3881/// pin) answers with its own reason. Neither is ever a silent no-op.
3882fn mutate_skill(
3883    verb: crate::skills_control::SkillVerb,
3884    params: Value,
3885) -> std::result::Result<Value, ServiceError> {
3886    let mutation = decode::<crate::skills_control::SkillMutation>(params)?;
3887    if !crate::skills_control::supports_skill_control(&mutation.harness) {
3888        return Err(ServiceError::UnsupportedAction(format!(
3889            "`{}` has no skills root Volter Harness reads; `skills.{}` is supported for: {}",
3890            mutation.harness,
3891            verb.as_str(),
3892            crate::skills_control::CONTROLLED_SKILL_HARNESSES.join(", ")
3893        )));
3894    }
3895    let outcome =
3896        crate::skills_control::mutate_skill(verb, &mutation).map_err(skill_control_error)?;
3897    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3898}
3899
3900/// The skills twin of [`job_control_error`], with the same mapping rule.
3901fn skill_control_error(error: crate::skills_control::SkillControlError) -> ServiceError {
3902    match error {
3903        crate::skills_control::SkillControlError::Unsupported(message) => {
3904            ServiceError::UnsupportedAction(message)
3905        }
3906        crate::skills_control::SkillControlError::Invalid(message) => {
3907            ServiceError::InvalidParams(message)
3908        }
3909        crate::skills_control::SkillControlError::Failed(message) => {
3910            ServiceError::Operation(message)
3911        }
3912    }
3913}
3914
3915/// ORCH-21: run one mutating profile verb through the harness's own CLI.
3916///
3917/// The refusal ladder mirrors `mutate_job`'s: a harness with no profile
3918/// concept at all answers with the SAME sentence `profiles.list` gives it, and
3919/// a harness that HAS profiles but publishes no client-callable verb (Codex's
3920/// file-authored `[profiles.<name>]` tables, supercode's compiled-in presets)
3921/// answers with its own reason. Neither is ever a silent no-op.
3922fn mutate_profile(
3923    verb: crate::profiles_control::ProfileVerb,
3924    params: Value,
3925) -> std::result::Result<Value, ServiceError> {
3926    let mutation = decode::<crate::profiles_control::ProfileMutation>(params)?;
3927    let outcome =
3928        crate::profiles_control::mutate(verb, &mutation).map_err(profile_control_error)?;
3929    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3930}
3931
3932/// The same mapping `job_control_error` applies, for the profile noun.
3933fn profile_control_error(error: crate::profiles_control::ProfileControlError) -> ServiceError {
3934    match error {
3935        crate::profiles_control::ProfileControlError::Unsupported(message) => {
3936            ServiceError::UnsupportedAction(message)
3937        }
3938        crate::profiles_control::ProfileControlError::Invalid(message) => {
3939            ServiceError::InvalidParams(message)
3940        }
3941        crate::profiles_control::ProfileControlError::Failed(message) => {
3942            ServiceError::Operation(message)
3943        }
3944    }
3945}
3946
3947/// Map a controlled-tier failure onto the service's error vocabulary. A verb
3948/// the harness lacks is `UnsupportedAction`; a harness verb that RAN and
3949/// failed carries its own stderr through as the operation error.
3950fn job_control_error(error: crate::jobs_control::JobControlError) -> ServiceError {
3951    match error {
3952        crate::jobs_control::JobControlError::Unsupported(message) => {
3953            ServiceError::UnsupportedAction(message)
3954        }
3955        crate::jobs_control::JobControlError::Invalid(message) => {
3956            ServiceError::InvalidParams(message)
3957        }
3958        crate::jobs_control::JobControlError::Failed(message) => ServiceError::Operation(message),
3959    }
3960}
3961
3962/// Map an ORCH-19 controlled-tier failure onto the service's error
3963/// vocabulary. A verb the harness has no door for is `UnsupportedAction`; a
3964/// door that RAN and failed carries the harness's own stderr / HTTP body
3965/// through as the operation error.
3966fn session_control_error(error: crate::SessionControlError) -> ServiceError {
3967    match error {
3968        crate::SessionControlError::Unsupported(message) => {
3969            ServiceError::UnsupportedAction(message)
3970        }
3971        crate::SessionControlError::Invalid(message) => ServiceError::InvalidParams(message),
3972        crate::SessionControlError::Failed(message) => ServiceError::Operation(message),
3973    }
3974}
3975
3976/// A harness without a scheduled-job concept refuses the verb rather than
3977/// answering with an empty list — an absent capability and an empty inventory
3978/// are different answers (the same rule `runtimes.capabilities` applies to
3979/// `steer`).
3980fn refuse_harness_without_jobs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3981    if crate::jobs::supports_jobs(harness) {
3982        return Ok(());
3983    }
3984    Err(ServiceError::UnsupportedAction(format!(
3985        "`{harness}` has no scheduled jobs; `{verb}` is supported for: {}",
3986        crate::jobs::JOB_HARNESSES.join(", ")
3987    )))
3988}
3989
3990/// `harness.v1.runs.get` parameters.
3991#[derive(Deserialize)]
3992struct RunsGetParams {
3993    harness: String,
3994    id: String,
3995    #[serde(default)]
3996    homes: crate::HarnessHomes,
3997}
3998
3999/// A harness with no run store refuses the verb rather than answering with an
4000/// empty history — the same rule `jobs.list` applies. Claude Code lands here
4001/// on purpose: its cron fires are ordinary turns inside the session that
4002/// created the job, so there is no fire record to list.
4003fn refuse_harness_without_runs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
4004    if crate::runs::supports_runs(harness) {
4005        return Ok(());
4006    }
4007    Err(ServiceError::UnsupportedAction(format!(
4008        "`{harness}` keeps no run store; `{verb}` is supported for: {}",
4009        crate::runs::RUN_HARNESSES.join(", ")
4010    )))
4011}
4012
4013#[derive(Serialize)]
4014struct SessionArtifact {
4015    source_harness: HarnessId,
4016    target_harness: &'static str,
4017    session_id: Option<String>,
4018    content: String,
4019    suggested_filename: String,
4020    files: Vec<SessionArtifactFile>,
4021    fidelity: Fidelity,
4022    residue: Vec<String>,
4023}
4024
4025#[derive(Serialize)]
4026struct SessionArtifactFile {
4027    path: String,
4028    content: String,
4029    role: ArtifactFileRole,
4030}
4031
4032#[derive(Serialize)]
4033#[serde(rename_all = "snake_case")]
4034enum ArtifactFileRole {
4035    Primary,
4036    Subagent,
4037    Bundle,
4038    SourceRecovery,
4039}
4040
4041#[derive(Serialize)]
4042struct StructuredLaunch {
4043    cwd: PathBuf,
4044    program: String,
4045    arguments: Vec<String>,
4046    env: BTreeMap<String, String>,
4047}
4048
4049struct HandoffInstructions {
4050    launch: StructuredLaunch,
4051    materialize: Option<StructuredLaunch>,
4052    requires_materialization: bool,
4053    note: String,
4054}
4055
4056#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
4057#[serde(rename_all = "snake_case")]
4058enum HarnessProbeLevel {
4059    #[default]
4060    Passive,
4061    Handshake,
4062}
4063
4064#[derive(Default, Deserialize)]
4065#[serde(default)]
4066struct HarnessInventoryParams {
4067    harness: Option<HarnessId>,
4068    harnesses: Vec<HarnessId>,
4069    workspace: Option<PathBuf>,
4070    probe: HarnessProbeLevel,
4071    include_sessions: bool,
4072    /// Omit subprocess-based `--version` calls when a latency-sensitive UI only needs readiness.
4073    skip_versions: bool,
4074}
4075
4076#[derive(Deserialize)]
4077struct HarnessAuthenticationParams {
4078    harness: HarnessId,
4079}
4080
4081#[derive(Deserialize)]
4082struct BeginHarnessAuthenticationParams {
4083    harness: HarnessId,
4084    #[serde(default = "local_browser_authentication_environment")]
4085    environment: crate::HarnessAuthenticationEnvironment,
4086    #[serde(default)]
4087    method: Option<crate::HarnessAuthenticationMethodId>,
4088    #[serde(default)]
4089    cwd: Option<PathBuf>,
4090}
4091
4092fn local_browser_authentication_environment() -> crate::HarnessAuthenticationEnvironment {
4093    crate::HarnessAuthenticationEnvironment::LocalBrowser
4094}
4095
4096#[derive(Serialize)]
4097struct HarnessInventoryReport {
4098    probe: HarnessProbeLevel,
4099    workspace: Option<PathBuf>,
4100    harnesses: Vec<LocalHarness>,
4101}
4102
4103#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4104#[serde(rename_all = "snake_case")]
4105enum HarnessAuthState {
4106    Ready,
4107    Configured,
4108    Required,
4109    Unknown,
4110}
4111
4112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4113#[serde(rename_all = "snake_case")]
4114enum HarnessRuntimeState {
4115    Ready,
4116    Degraded,
4117    Unavailable,
4118}
4119
4120#[derive(Serialize)]
4121struct HarnessSessionCounts {
4122    global: Option<usize>,
4123    workspace: Option<usize>,
4124}
4125
4126/// Receipt-backed evidence that a harness has a RUNNING instance right now,
4127/// distinct from being merely installed (UNI-7). Detection is passive and
4128/// default-on: a gateway liveness connect for daemon harnesses, a fresh
4129/// SQLite WAL stamp for store-writer harnesses (precedent: the opencode
4130/// follower's -wal/-shm freshness). Control stays behind per-connection
4131/// grants — this reports observations only.
4132/// ORCH-17: the gateway-health noun on an inventory row. Derived from the
4133/// UNI-7 running-instance probe (Hermes: `state.db-wal` freshness; OpenClaw:
4134/// a TCP connect to the gateway endpoint resolved from its OWN config) plus
4135/// the executable version — never by starting anything.
4136#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4137#[serde(rename_all = "snake_case")]
4138pub enum GatewayState {
4139    Up,
4140    Down,
4141    Unknown,
4142}
4143
4144/// ORCH-17: `gateway` on a `harness.v1.harnesses.list` row.
4145#[derive(Debug, Clone, Serialize)]
4146pub struct GatewayHealth {
4147    pub state: GatewayState,
4148    /// The endpoint supercode would connect to (OpenClaw: the gateway
4149    /// WebSocket resolved from `openclaw.json`; core harnesses: their
4150    /// declared connect address when one exists). `None` when the harness
4151    /// has no single endpoint (Hermes multiplexes platforms).
4152    #[serde(skip_serializing_if = "Option::is_none")]
4153    pub endpoint: Option<String>,
4154    #[serde(skip_serializing_if = "Option::is_none")]
4155    pub version: Option<String>,
4156    /// What the verdict rests on, or why it is `unknown`.
4157    pub evidence: String,
4158    pub checked_at_ms: u64,
4159}
4160
4161/// OpenClaw's gateway WebSocket endpoint, resolved from its own config the
4162/// way the registry's connect descriptor prescribes (`gateway.url`, else
4163/// `gateway.port`, else the documented default).
4164fn openclaw_gateway_endpoint(home: &Path) -> String {
4165    let config_path = home.join(".openclaw/openclaw.json");
4166    let gateway = std::fs::read_to_string(&config_path)
4167        .ok()
4168        .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
4169        .and_then(|config| config.get("gateway").cloned());
4170    if let Some(url) = gateway
4171        .as_ref()
4172        .and_then(|gateway| gateway.get("url"))
4173        .and_then(serde_json::Value::as_str)
4174    {
4175        return url.to_string();
4176    }
4177    let port = gateway
4178        .as_ref()
4179        .and_then(|gateway| gateway.get("port"))
4180        .and_then(serde_json::Value::as_u64)
4181        .unwrap_or(18789);
4182    format!("ws://127.0.0.1:{port}")
4183}
4184
4185/// Ask Hermes itself (`hermes gateway status`, read-only, ~1 s) whether its
4186/// gateway is up. The command is per-host launchd/systemd text without a JSON
4187/// form at 0.19–0.21; the verdict is read from the lines it prints:
4188/// "supervised by launchd (PID …)" / "is running" → up, "not running" /
4189/// "not installed" → down, anything else → no verdict. `SUPERCODE_HERMES_BIN`
4190/// overrides the executable so a fake can stand in under test.
4191fn hermes_gateway_status() -> Option<(GatewayState, String)> {
4192    let program = crate::harness_command::harness_program(HarnessId::HERMES).ok()?;
4193    let output = std::process::Command::new(&program)
4194        .args(["gateway", "status"])
4195        .stdin(std::process::Stdio::null())
4196        .output()
4197        .ok()?;
4198    let text = format!(
4199        "{}{}",
4200        String::from_utf8_lossy(&output.stdout),
4201        String::from_utf8_lossy(&output.stderr)
4202    );
4203    let verdict = text.lines().find_map(|line| {
4204        let l = line.trim();
4205        if l.contains("supervised by launchd (PID")
4206            || l.contains("supervised by systemd (PID")
4207            || l.contains("Gateway is running")
4208            || l.contains("process is running")
4209        {
4210            Some((GatewayState::Up, format!("`hermes gateway status`: {l}")))
4211        } else if l.contains("not running") || l.contains("not installed") {
4212            Some((GatewayState::Down, format!("`hermes gateway status`: {l}")))
4213        } else {
4214            None
4215        }
4216    });
4217    verdict
4218}
4219
4220fn gateway_health(
4221    id: &str,
4222    installed: bool,
4223    running: Option<&RunningInstance>,
4224    version: Option<&str>,
4225) -> GatewayHealth {
4226    let checked_at_ms = now_epoch_ms();
4227    let home = supercode_interchange::user_home()
4228        .map(std::path::PathBuf::into_os_string)
4229        .map(PathBuf::from);
4230    match id {
4231        HarnessId::HERMES | HarnessId::OPENCLAW => {
4232            let endpoint = (id == HarnessId::OPENCLAW)
4233                .then(|| home.as_deref().map(openclaw_gateway_endpoint))
4234                .flatten();
4235            let (state, evidence) = match running {
4236                Some(instance) => (GatewayState::Up, instance.evidence.clone()),
4237                None if !installed => (
4238                    GatewayState::Unknown,
4239                    format!("`{id}` is not installed; no gateway to probe"),
4240                ),
4241                None if id == HarnessId::HERMES => match hermes_gateway_status() {
4242                    // The harness's own door outranks the WAL heuristic: an idle
4243                    // gateway writes nothing for minutes yet is up.
4244                    Some((state, evidence)) => (state, evidence),
4245                    None => (
4246                        GatewayState::Down,
4247                        "no fresh state.db-wal activity under ~/.hermes and `hermes gateway status` gave no verdict".to_string(),
4248                    ),
4249                },
4250                None => (
4251                    GatewayState::Down,
4252                    format!(
4253                        "no TCP listener at {}",
4254                        endpoint.as_deref().unwrap_or("the gateway endpoint")
4255                    ),
4256                ),
4257            };
4258            GatewayHealth {
4259                state,
4260                endpoint,
4261                version: version.map(str::to_string),
4262                evidence,
4263                checked_at_ms,
4264            }
4265        }
4266        // ORC-7: the orchestrator's gateway IS its daemon, and the daemon's
4267        // own lease file is the record of it. A lease naming a live pid is
4268        // up; a lease whose process is gone is down and says so as a STALE
4269        // lease, never as "no lease"; no lease at all is down. Nothing is
4270        // started, and no port is guessed — the daemon multiplexes adapters
4271        // the way Hermes does, so it has no single endpoint either.
4272        HarnessId::ORCHESTRATOR => {
4273            let root = crate::HarnessHomes::default().orchestrator;
4274            let (state, evidence) = match crate::orchestrator::read_lease(&root) {
4275                Some(lease) if lease.is_live() => (
4276                    GatewayState::Up,
4277                    format!(
4278                        "`{}` names pid {} (started {}), which is live",
4279                        crate::orchestrator::lock_path(&root).display(),
4280                        lease.pid,
4281                        lease.started_at
4282                    ),
4283                ),
4284                Some(lease) => (
4285                    GatewayState::Down,
4286                    format!(
4287                        "stale lease `{}`: pid {} is gone",
4288                        crate::orchestrator::lock_path(&root).display(),
4289                        lease.pid
4290                    ),
4291                ),
4292                None => (
4293                    GatewayState::Down,
4294                    format!(
4295                        "no lease at `{}`; `supercode orchestrator start` writes one",
4296                        crate::orchestrator::lock_path(&root).display()
4297                    ),
4298                ),
4299            };
4300            GatewayHealth {
4301                state,
4302                endpoint: None,
4303                version: version.map(str::to_string),
4304                evidence,
4305                checked_at_ms,
4306            }
4307        }
4308        _ => GatewayHealth {
4309            state: GatewayState::Unknown,
4310            endpoint: None,
4311            version: version.map(str::to_string),
4312            evidence: format!("`{id}` runs per session, not as a gateway"),
4313            checked_at_ms,
4314        },
4315    }
4316}
4317
4318#[derive(Debug, Clone, Serialize)]
4319struct RunningInstance {
4320    /// How the instance was detected.
4321    method: RunningInstanceMethod,
4322    /// The evidence the verdict rests on (endpoint reached / WAL path+age).
4323    evidence: String,
4324    /// Epoch-ms instant the probe executed.
4325    checked_at_ms: u64,
4326}
4327
4328#[derive(Debug, Clone, Copy, Serialize)]
4329#[serde(rename_all = "snake_case")]
4330enum RunningInstanceMethod {
4331    /// A TCP connect to the harness's own configured gateway endpoint
4332    /// succeeded.
4333    GatewayConnect,
4334    /// The harness's session store has an active SQLite WAL (a live writer
4335    /// holds the store open and stamped it recently).
4336    StoreWalActivity,
4337}
4338
4339fn now_epoch_ms() -> u64 {
4340    std::time::SystemTime::now()
4341        .duration_since(std::time::UNIX_EPOCH)
4342        .map(|elapsed| elapsed.as_millis() as u64)
4343        .unwrap_or(0)
4344}
4345
4346/// OpenClaw: the gateway endpoint comes from the harness's OWN config
4347/// (`<home>/.openclaw/openclaw.json` — `gateway.url` or `gateway.port`,
4348/// default port 18789); a successful TCP connect is the running signal.
4349fn probe_openclaw_running(home: &Path) -> Option<RunningInstance> {
4350    let config_path = home.join(".openclaw/openclaw.json");
4351    let text = std::fs::read_to_string(&config_path).ok();
4352    let gateway = text
4353        .as_deref()
4354        .and_then(|raw| serde_json::from_str::<serde_json::Value>(raw).ok())
4355        .and_then(|config| config.get("gateway").cloned());
4356    let address = gateway
4357        .as_ref()
4358        .and_then(|gateway| gateway.get("url"))
4359        .and_then(serde_json::Value::as_str)
4360        .and_then(|url| {
4361            url.split("://").nth(1).map(|rest| {
4362                rest.trim_end_matches('/')
4363                    .split('/')
4364                    .next()
4365                    .unwrap_or(rest)
4366                    .to_string()
4367            })
4368        })
4369        .unwrap_or_else(|| {
4370            let port = gateway
4371                .as_ref()
4372                .and_then(|gateway| gateway.get("port"))
4373                .and_then(serde_json::Value::as_u64)
4374                .unwrap_or(18789);
4375            format!("127.0.0.1:{port}")
4376        });
4377    let reachable = std::net::TcpStream::connect_timeout(
4378        &address.parse().ok()?,
4379        std::time::Duration::from_millis(400),
4380    )
4381    .is_ok();
4382    reachable.then(|| RunningInstance {
4383        method: RunningInstanceMethod::GatewayConnect,
4384        evidence: format!(
4385            "gateway endpoint {address} accepted a TCP connect (from {})",
4386            config_path.display()
4387        ),
4388        checked_at_ms: now_epoch_ms(),
4389    })
4390}
4391
4392/// Hermes: `<home>/.hermes/state.db-wal` freshly modified means a live writer
4393/// holds the store open (SQLite WAL exists only while a connection is open;
4394/// a recent stamp distinguishes an active instance from a stale crash
4395/// leftover).
4396fn probe_hermes_running(home: &Path, max_wal_age_ms: u64) -> Option<RunningInstance> {
4397    let wal = home.join(".hermes/state.db-wal");
4398    let modified = std::fs::metadata(&wal).ok()?.modified().ok()?;
4399    let age_ms = std::time::SystemTime::now()
4400        .duration_since(modified)
4401        .map(|age| age.as_millis() as u64)
4402        .unwrap_or(u64::MAX);
4403    (age_ms <= max_wal_age_ms).then(|| RunningInstance {
4404        method: RunningInstanceMethod::StoreWalActivity,
4405        evidence: format!(
4406            "{} stamped {age_ms}ms ago (threshold {max_wal_age_ms}ms)",
4407            wal.display()
4408        ),
4409        checked_at_ms: now_epoch_ms(),
4410    })
4411}
4412
4413/// Default-on running-instance detection for the harnesses that have one.
4414fn probe_running_instance(id: &str) -> Option<RunningInstance> {
4415    let home = supercode_interchange::user_home()
4416        .map(std::path::PathBuf::into_os_string)
4417        .map(PathBuf::from)?;
4418    match id {
4419        HarnessId::OPENCLAW => probe_openclaw_running(&home),
4420        HarnessId::HERMES => probe_hermes_running(&home, 300_000),
4421        _ => None,
4422    }
4423}
4424
4425#[derive(Serialize)]
4426struct LocalHarness {
4427    id: HarnessId,
4428    display_name: String,
4429    supported: bool,
4430    installed: bool,
4431    executable: Option<String>,
4432    version: Option<String>,
4433    auth: HarnessAuthState,
4434    runtime: HarnessRuntimeState,
4435    protocol: String,
4436    capabilities: crate::RuntimeCapabilities,
4437    effective_capabilities: crate::RuntimeCapabilities,
4438    sessions: HarnessSessionCounts,
4439    /// Receipt-backed running-instance detection (None = not detected or the
4440    /// harness has no running-instance concept). Distinct from `installed`.
4441    #[serde(skip_serializing_if = "Option::is_none")]
4442    running: Option<RunningInstance>,
4443    /// ORCH-17: gateway health derived from `running` + the harness's own config.
4444    gateway: GatewayHealth,
4445    reason: Option<String>,
4446    repair: Option<String>,
4447}
4448
4449#[derive(Clone, Deserialize)]
4450struct RuntimeBackendParams {
4451    harness: HarnessId,
4452    #[serde(default)]
4453    protocol: Option<String>,
4454    #[serde(default)]
4455    launch: Option<RuntimeLaunch>,
4456    #[serde(default)]
4457    base_url: Option<String>,
4458    #[serde(default)]
4459    policy: RuntimePolicy,
4460}
4461
4462/// A session supercode starts or resumes runs without approval prompts
4463/// (`yolo`) unless the caller asks for the harness's own (`default`).
4464#[derive(Debug, Clone, Copy, Default, Deserialize)]
4465#[serde(rename_all = "snake_case")]
4466enum RuntimePolicy {
4467    Default,
4468    #[default]
4469    Yolo,
4470}
4471
4472#[derive(Deserialize)]
4473struct RuntimeStartParams {
4474    #[serde(flatten)]
4475    backend: RuntimeBackendParams,
4476    cwd: PathBuf,
4477    /// MCP servers to mount into the new session through the harness's own
4478    /// start door (ORC-6). Backends without such a door ignore them.
4479    #[serde(default)]
4480    mcp_servers: Vec<crate::McpServerLaunch>,
4481    /// The session's approval policy, where the harness's start door takes one (Codex).
4482    #[serde(default)]
4483    approval_policy: Option<String>,
4484}
4485
4486#[derive(Deserialize)]
4487struct RuntimeAttachParams {
4488    #[serde(flatten)]
4489    backend: RuntimeBackendParams,
4490    runtime_id: String,
4491    #[serde(default)]
4492    cwd: Option<PathBuf>,
4493    /// MCP servers to mount into the resumed session (the start door's own
4494    /// field, carried again because a session's tools die with its process).
4495    #[serde(default)]
4496    mcp_servers: Vec<crate::McpServerLaunch>,
4497    /// The session's approval policy, carried again on resume as on start (Codex).
4498    #[serde(default)]
4499    approval_policy: Option<String>,
4500}
4501
4502#[derive(Deserialize)]
4503struct RuntimeConnectionParams {
4504    connection: String,
4505}
4506
4507#[derive(Deserialize)]
4508struct RuntimeInputParams {
4509    connection: String,
4510    text: String,
4511    #[serde(default)]
4512    image_urls: Vec<String>,
4513}
4514
4515const MAX_RUNTIME_IMAGES: usize = 4;
4516const MAX_RUNTIME_IMAGE_URL_BYTES: usize = 12 * 1024 * 1024;
4517const MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL: usize = 32 * 1024 * 1024;
4518
4519fn validate_runtime_image_urls(image_urls: Vec<String>) -> Result<Vec<String>, ServiceError> {
4520    if image_urls.len() > MAX_RUNTIME_IMAGES {
4521        return Err(ServiceError::InvalidParams(format!(
4522            "a runtime prompt accepts at most {MAX_RUNTIME_IMAGES} images"
4523        )));
4524    }
4525    let mut total = 0usize;
4526    for url in &image_urls {
4527        if !(url.starts_with("data:image/")
4528            || url.starts_with("https://")
4529            || url.starts_with("http://"))
4530        {
4531            return Err(ServiceError::InvalidParams(
4532                "runtime images must be image data URLs or HTTP(S) URLs".into(),
4533            ));
4534        }
4535        if url.len() > MAX_RUNTIME_IMAGE_URL_BYTES {
4536            return Err(ServiceError::InvalidParams(format!(
4537                "one runtime image exceeds the {MAX_RUNTIME_IMAGE_URL_BYTES}-byte encoded limit"
4538            )));
4539        }
4540        total = total.saturating_add(url.len());
4541    }
4542    if total > MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL {
4543        return Err(ServiceError::InvalidParams(format!(
4544            "runtime images exceed the {MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL}-byte encoded total limit"
4545        )));
4546    }
4547    Ok(image_urls)
4548}
4549
4550#[derive(Deserialize)]
4551struct RuntimeRespondParams {
4552    connection: String,
4553    request_id: Value,
4554    response: Value,
4555}
4556
4557fn default_reduction_store_root() -> PathBuf {
4558    if let Some(root) = std::env::var_os("SUPERCODE_HOME") {
4559        return PathBuf::from(root).join("sessions");
4560    }
4561    if let Some(home) = supercode_interchange::user_home().map(std::path::PathBuf::into_os_string) {
4562        return PathBuf::from(home).join(".supercode").join("sessions");
4563    }
4564    PathBuf::from(".supercode").join("sessions")
4565}
4566
4567fn messages_jsonl(messages: &[crate::ChatMessage]) -> std::result::Result<String, ServiceError> {
4568    let mut output = String::new();
4569    for message in messages {
4570        output.push_str(
4571            &serde_json::to_string(message)
4572                .map_err(|error| ServiceError::Operation(error.to_string()))?,
4573        );
4574        output.push('\n');
4575    }
4576    Ok(output)
4577}
4578
4579fn parse_messages_jsonl(
4580    content: &str,
4581) -> std::result::Result<Vec<crate::ChatMessage>, ServiceError> {
4582    content
4583        .lines()
4584        .enumerate()
4585        .filter(|(_, line)| !line.trim().is_empty())
4586        .map(|(index, line)| {
4587            serde_json::from_str::<crate::ChatMessage>(line).map_err(|error| {
4588                ServiceError::Operation(format!(
4589                    "reduced transcript line {} is invalid: {error}",
4590                    index + 1
4591                ))
4592            })
4593        })
4594        .collect()
4595}
4596
4597fn reduced_bootstrap_prompt(
4598    source: &SessionLocator,
4599    target: TransferFormat,
4600    view_jsonl: &str,
4601    sidecar_path: &Path,
4602    reduction_log_path: &Path,
4603) -> String {
4604    format!(
4605        "Continue the work from this losslessly reduced {source_harness} session in {target_harness}.\n\
4606         \n\
4607         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\
4608         \n\
4609         <supercode-reduced-session source-session=\"{source_id}\">\n\
4610         {view_jsonl}\
4611         </supercode-reduced-session>\n\
4612         \n\
4613         Resume from the latest unresolved user request and preserve the source session's decisions and constraints.",
4614        source_harness = source.harness.as_str(),
4615        target_harness = target.id(),
4616        sidecar = sidecar_path.display(),
4617        log = reduction_log_path.display(),
4618        source_id = source.session_id,
4619    )
4620}
4621
4622fn session_artifact(
4623    locator: &SessionLocator,
4624    session: &Session,
4625    target: TransferFormat,
4626) -> std::result::Result<SessionArtifact, ServiceError> {
4627    session_artifact_with_id(locator, session, target, None)
4628}
4629
4630fn session_artifact_with_id(
4631    locator: &SessionLocator,
4632    session: &Session,
4633    target: TransferFormat,
4634    target_session_id: Option<&str>,
4635) -> std::result::Result<SessionArtifact, ServiceError> {
4636    let format: SessionFormat = target.into();
4637    let diagonal = format.source() == session.meta.source;
4638    crate::residue_store::store_segments(session);
4639    let has_appended_turns = session
4640        .imported_message_count
4641        .is_some_and(|imported| imported < session.messages.len());
4642    let mut restoration = None;
4643    let content = if let Some(id) = target_session_id {
4644        if diagonal && format != SessionFormat::OpenCode {
4645            session
4646                .to_jsonl_spliced(format, Some(id))
4647                .map_err(operation)?
4648        } else {
4649            let mut rewritten = session.clone();
4650            rewritten.meta.session_id = Some(id.to_string());
4651            rewritten.to_jsonl(format).map_err(operation)?
4652        }
4653    } else if diagonal && session.raw_is_verbatim && !has_appended_turns {
4654        session.raw_verbatim()
4655    } else if diagonal {
4656        session.to_jsonl_spliced(format, None).map_err(operation)?
4657    } else {
4658        // A session that came from `format` before returns its source records verbatim for the
4659        // prefix the residue store holds (docs/plans/portable-residue.md).
4660        match session
4661            .restore_residue(format, crate::residue_store::lookup)
4662            .map_err(operation)?
4663        {
4664            Some((content, report)) => {
4665                restoration = Some(report);
4666                content
4667            }
4668            None => session.to_jsonl(format).map_err(operation)?,
4669        }
4670    };
4671    let stem = sanitize_filename(
4672        target_session_id
4673            .or(session.meta.session_id.as_deref())
4674            .unwrap_or(&locator.session_id),
4675    );
4676    let suggested_filename = if diagonal && target == TransferFormat::Grok {
4677        "chat_history.jsonl".to_string()
4678    } else if target == TransferFormat::Goose {
4679        format!("{stem}.goose.json")
4680    } else {
4681        format!("{stem}.{}.jsonl", target.id())
4682    };
4683    let mut files = vec![SessionArtifactFile {
4684        path: suggested_filename.clone(),
4685        content: content.clone(),
4686        role: ArtifactFileRole::Primary,
4687    }];
4688    if target == TransferFormat::ClaudeCode {
4689        let bundle_stem = Path::new(&suggested_filename)
4690            .file_stem()
4691            .and_then(|stem| stem.to_str())
4692            .unwrap_or(&stem);
4693        let mut child_paths = BTreeSet::new();
4694        for (index, subagent) in session.subagents.iter().enumerate() {
4695            let agent_id = subagent
4696                .meta
4697                .agent_id
4698                .as_deref()
4699                .map(|id| id.strip_prefix("agent-").unwrap_or(id))
4700                .map(sanitize_filename)
4701                .filter(|id| !id.is_empty())
4702                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4703            let child_has_appended_turns = subagent
4704                .imported_message_count
4705                .is_some_and(|imported| imported < subagent.messages.len());
4706            let child_content = if target_session_id.is_none()
4707                && subagent.meta.source == SessionSource::ClaudeCode
4708                && subagent.raw_is_verbatim
4709                && !child_has_appended_turns
4710            {
4711                subagent.raw_verbatim()
4712            } else if subagent.meta.source == SessionSource::ClaudeCode {
4713                subagent
4714                    .to_jsonl_spliced(SessionFormat::ClaudeCode, target_session_id)
4715                    .map_err(operation)?
4716            } else {
4717                let mut child = subagent.clone();
4718                if let Some(id) = target_session_id {
4719                    child.meta.session_id = Some(id.to_string());
4720                }
4721                child
4722                    .to_jsonl(SessionFormat::ClaudeCode)
4723                    .map_err(operation)?
4724            };
4725            let path = format!("{bundle_stem}/subagents/agent-{agent_id}.jsonl");
4726            if !child_paths.insert(path.clone()) {
4727                return Err(ServiceError::Operation(format!(
4728                    "Claude subagent ids collide at artifact path `{path}`"
4729                )));
4730            }
4731            files.push(SessionArtifactFile {
4732                path,
4733                content: child_content,
4734                role: ArtifactFileRole::Subagent,
4735            });
4736        }
4737    }
4738    if diagonal && target == TransferFormat::Grok {
4739        append_grok_bundle_files(locator, "", ArtifactFileRole::Bundle, &mut files)?;
4740    }
4741    if !diagonal || !session.raw_is_verbatim {
4742        files.push(SessionArtifactFile {
4743            path: "recovery/source.supercode.jsonl".into(),
4744            content: session.to_native_jsonl(),
4745            role: ArtifactFileRole::SourceRecovery,
4746        });
4747        for (index, subagent) in session.subagents.iter().enumerate() {
4748            let id = subagent
4749                .meta
4750                .agent_id
4751                .as_deref()
4752                .map(sanitize_filename)
4753                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4754            files.push(SessionArtifactFile {
4755                path: format!("recovery/subagents/{id}.supercode.jsonl"),
4756                content: subagent.to_native_jsonl(),
4757                role: ArtifactFileRole::SourceRecovery,
4758            });
4759        }
4760    }
4761    if !diagonal && session.meta.source == SessionSource::Grok {
4762        append_grok_bundle_files(
4763            locator,
4764            "recovery/grok/",
4765            ArtifactFileRole::SourceRecovery,
4766            &mut files,
4767        )?;
4768    }
4769    let (fidelity, residue) = if diagonal
4770        && target_session_id.is_none()
4771        && session.raw_is_verbatim
4772        && !has_appended_turns
4773    {
4774        (Fidelity::ByteLossless, Vec::new())
4775    } else if diagonal && !(target_session_id.is_some() && target == TransferFormat::OpenCode) {
4776        (
4777            Fidelity::ValueLossless,
4778            vec![if target_session_id.is_some() {
4779                "target identity was rewritten, so the artifact intentionally differs from source bytes".into()
4780            } else {
4781                "source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured".into()
4782            }],
4783        )
4784    } else {
4785        match restoration {
4786            Some(report) if report.rendered_messages == 0 => (
4787                Fidelity::ByteLossless,
4788                vec![format!(
4789                    "restored verbatim from this conversation's {} source records in the residue store",
4790                    target.id()
4791                )],
4792            ),
4793            Some(report) => (
4794                Fidelity::Semantic,
4795                vec![format!(
4796                    "{} of {} messages restored verbatim from the residue store; the other {} written by the {} writer",
4797                    report.restored_messages,
4798                    report.restored_messages + report.rendered_messages,
4799                    report.rendered_messages,
4800                    target.id()
4801                )],
4802            ),
4803            None => (
4804                Fidelity::Semantic,
4805                vec!["target schema has no portable slot for every source-native record and metadata field".into()],
4806            ),
4807        }
4808    };
4809    Ok(SessionArtifact {
4810        source_harness: locator.harness.clone(),
4811        target_harness: target.id(),
4812        session_id: target_session_id
4813            .map(str::to_string)
4814            .or_else(|| session.meta.session_id.clone()),
4815        content,
4816        suggested_filename,
4817        files,
4818        fidelity,
4819        residue,
4820    })
4821}
4822
4823fn append_grok_bundle_files(
4824    locator: &SessionLocator,
4825    prefix: &str,
4826    role: ArtifactFileRole,
4827    files: &mut Vec<SessionArtifactFile>,
4828) -> std::result::Result<(), ServiceError> {
4829    let primary = locator.storage.path();
4830    if primary.file_name().and_then(|name| name.to_str()) != Some("chat_history.jsonl") {
4831        return Err(ServiceError::Operation(format!(
4832            "Grok bundle locator must name chat_history.jsonl, got {}",
4833            primary.display()
4834        )));
4835    }
4836    let parent = primary.parent().ok_or_else(|| {
4837        ServiceError::Operation("Grok chat_history.jsonl has no session directory".into())
4838    })?;
4839    for name in ["summary.json", "updates.jsonl"] {
4840        let path = parent.join(name);
4841        let metadata = match std::fs::symlink_metadata(&path) {
4842            Ok(metadata) => metadata,
4843            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
4844            Err(error) => return Err(ServiceError::Operation(error.to_string())),
4845        };
4846        if metadata.file_type().is_symlink() || !metadata.is_file() {
4847            return Err(ServiceError::Operation(format!(
4848                "refusing non-regular Grok bundle member {}",
4849                path.display()
4850            )));
4851        }
4852        let content = std::fs::read_to_string(&path).map_err(|error| {
4853            ServiceError::Operation(format!(
4854                "Grok bundle member {} is not representable as UTF-8: {error}",
4855                path.display()
4856            ))
4857        })?;
4858        files.push(SessionArtifactFile {
4859            path: format!("{prefix}{name}"),
4860            content,
4861            role: match role {
4862                ArtifactFileRole::Bundle => ArtifactFileRole::Bundle,
4863                _ => ArtifactFileRole::SourceRecovery,
4864            },
4865        });
4866    }
4867    Ok(())
4868}
4869
4870fn handoff_artifact(
4871    locator: &SessionLocator,
4872    session: &Session,
4873    target: TransferFormat,
4874) -> std::result::Result<SessionArtifact, ServiceError> {
4875    let target_session_id = target_session_id(target);
4876    session_artifact_with_id(locator, session, target, Some(&target_session_id))
4877}
4878
4879fn target_session_id(target: TransferFormat) -> String {
4880    let uuid = generated_session_id();
4881    match target {
4882        TransferFormat::OpenCode => format!("ses_{}", uuid.replace('-', "")),
4883        TransferFormat::ClaudeCode
4884        | TransferFormat::Codex
4885        | TransferFormat::Pi
4886        | TransferFormat::Grok
4887        | TransferFormat::Gemini
4888        | TransferFormat::Goose
4889        | TransferFormat::Hermes => uuid,
4890    }
4891}
4892
4893fn sanitize_filename(value: &str) -> String {
4894    let value = value
4895        .chars()
4896        .map(|character| {
4897            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
4898                character
4899            } else {
4900                '-'
4901            }
4902        })
4903        .collect::<String>();
4904    let value = value.trim_matches('-');
4905    if value.is_empty() {
4906        "session".into()
4907    } else {
4908        value.chars().take(100).collect()
4909    }
4910}
4911
4912fn handoff_instructions(
4913    target: TransferFormat,
4914    session_id: &str,
4915    cwd: &Path,
4916) -> HandoffInstructions {
4917    let launch = |program: &str, arguments: Vec<String>| StructuredLaunch {
4918        cwd: cwd.to_path_buf(),
4919        program: program.into(),
4920        arguments,
4921        env: BTreeMap::new(),
4922    };
4923    match target {
4924        TransferFormat::ClaudeCode => HandoffInstructions {
4925            launch: launch("claude", vec!["--resume".into(), session_id.into()]),
4926            materialize: None,
4927            requires_materialization: true,
4928            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(),
4929        },
4930        TransferFormat::Hermes => HandoffInstructions {
4931            launch: launch("hermes", vec!["--resume".into(), session_id.into()]),
4932            materialize: None,
4933            requires_materialization: true,
4934            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(),
4935        },
4936        TransferFormat::Codex => HandoffInstructions {
4937            launch: launch("codex", vec!["resume".into(), session_id.into()]),
4938            materialize: None,
4939            requires_materialization: true,
4940            note: "Write the artifact into Codex's native rollout store before running the resume launch; Codex has no general transcript-import command.".into(),
4941        },
4942        TransferFormat::OpenCode => HandoffInstructions {
4943            launch: launch("opencode", vec!["--session".into(), session_id.into()]),
4944            materialize: Some(launch(
4945                "opencode",
4946                vec!["import".into(), "{artifact_path}".into()],
4947            )),
4948            requires_materialization: true,
4949            note: "Write the artifact to a file, run the materialize command with its path, then launch the imported session.".into(),
4950        },
4951        TransferFormat::Pi => HandoffInstructions {
4952            launch: launch("pi", vec!["--session".into(), "{artifact_path}".into()]),
4953            materialize: None,
4954            requires_materialization: true,
4955            note: "Write the artifact to a file and replace {artifact_path} in the launch arguments; Pi can resume that file directly.".into(),
4956        },
4957        TransferFormat::Grok => HandoffInstructions {
4958            launch: launch(
4959                "grok",
4960                vec!["--resume".into(), "{materialized_session_id}".into()],
4961            ),
4962            materialize: None,
4963            requires_materialization: true,
4964            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(),
4965        },
4966        TransferFormat::Gemini => HandoffInstructions {
4967            launch: launch(
4968                "gemini",
4969                vec!["--session-file".into(), "{artifact_path}".into()],
4970            ),
4971            materialize: None,
4972            requires_materialization: true,
4973            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(),
4974        },
4975        TransferFormat::Goose => HandoffInstructions {
4976            launch: launch(
4977                "goose",
4978                vec![
4979                    "session".into(),
4980                    "--resume".into(),
4981                    "--session-id".into(),
4982                    "{imported_session_id}".into(),
4983                ],
4984            ),
4985            materialize: Some(launch(
4986                "goose",
4987                vec!["session".into(), "import".into(), "{artifact_path}".into()],
4988            )),
4989            requires_materialization: true,
4990            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(),
4991        },
4992    }
4993}
4994
4995fn resume_launch(
4996    harness: &str,
4997    session_id: &str,
4998    cwd: &Path,
4999    policy: ResumePolicy,
5000) -> std::result::Result<StructuredLaunch, ServiceError> {
5001    let mut arguments = Vec::new();
5002    let program = match harness {
5003        HarnessId::GROK => {
5004            if matches!(policy, ResumePolicy::Yolo) {
5005                if crate::support::self_sandbox_supported() {
5006                    arguments.extend(["--sandbox".into(), "workspace".into()]);
5007                }
5008                arguments.push("--always-approve".into());
5009            }
5010            arguments.extend(["--resume".into(), session_id.into()]);
5011            "grok"
5012        }
5013        HarnessId::CODEX => {
5014            let cwd_key = serde_json::to_string(cwd.to_string_lossy().as_ref())
5015                .expect("a filesystem path always serializes as JSON text");
5016            arguments.extend([
5017                "-c".into(),
5018                "check_for_update_on_startup=false".into(),
5019                "-c".into(),
5020                // A dotted `-c` key splits a quoted path at its dots; an inline
5021                // table carries the path as one TOML string.
5022                format!("projects={{{cwd_key}={{trust_level=\"trusted\"}}}}"),
5023            ]);
5024            if matches!(policy, ResumePolicy::Yolo) {
5025                arguments.extend([
5026                    "--dangerously-bypass-approvals-and-sandbox".into(),
5027                    "--dangerously-bypass-hook-trust".into(),
5028                ]);
5029            }
5030            arguments.extend(["resume".into(), session_id.into()]);
5031            "codex"
5032        }
5033        HarnessId::CLAUDE_CODE => {
5034            if matches!(policy, ResumePolicy::Yolo) {
5035                arguments.push("--dangerously-skip-permissions".into());
5036            }
5037            arguments.extend(["--resume".into(), session_id.into()]);
5038            "claude"
5039        }
5040        HarnessId::GEMINI => {
5041            if matches!(policy, ResumePolicy::Yolo) {
5042                arguments.push("--yolo".into());
5043            }
5044            arguments.extend(["--resume".into(), session_id.into()]);
5045            "gemini"
5046        }
5047        HarnessId::GOOSE => {
5048            arguments.extend([
5049                "session".into(),
5050                "--resume".into(),
5051                "--session-id".into(),
5052                session_id.into(),
5053            ]);
5054            "goose"
5055        }
5056        HarnessId::PI => {
5057            if matches!(policy, ResumePolicy::Yolo) {
5058                arguments.push("--approve".into());
5059            }
5060            arguments.extend(["--session".into(), session_id.into()]);
5061            "pi"
5062        }
5063        HarnessId::OPENCODE => {
5064            arguments.extend(["--session".into(), session_id.into()]);
5065            "opencode"
5066        }
5067        HarnessId::SUPERCODE => {
5068            if matches!(policy, ResumePolicy::Yolo) {
5069                arguments.push("--dangerous".into());
5070            }
5071            arguments.extend(["resume".into(), session_id.into()]);
5072            "supercode"
5073        }
5074        other => {
5075            return Err(ServiceError::InvalidParams(format!(
5076                "no structured resume launch is registered for harness `{other}`"
5077            )))
5078        }
5079    };
5080    Ok(StructuredLaunch {
5081        cwd: cwd.to_path_buf(),
5082        env: if program == "grok" {
5083            crate::support::grok_home_env()
5084        } else {
5085            BTreeMap::new()
5086        },
5087        program: program.into(),
5088        arguments,
5089    })
5090}
5091
5092/// Stage the resolved gateway credential in a private (0600) file so the
5093/// bridge can read it via `--token-file` — the delivery the real `openclaw
5094/// acp` accepts. One stable file per endpoint (keyed by an address digest,
5095/// no secret material in the name), overwritten on every connect so files
5096/// never accumulate and a rotated token never goes stale on disk.
5097fn openclaw_gateway_token_file(address: &str, secret: &str) -> std::io::Result<PathBuf> {
5098    let digest = blake3::hash(address.as_bytes()).to_hex();
5099    let path = std::env::temp_dir().join(format!(
5100        "supercode-openclaw-gateway-token-{}",
5101        &digest.as_str()[..16]
5102    ));
5103    #[cfg(unix)]
5104    {
5105        use std::io::Write;
5106        use std::os::unix::fs::OpenOptionsExt;
5107        let mut file = std::fs::OpenOptions::new()
5108            .write(true)
5109            .create(true)
5110            .truncate(true)
5111            .mode(0o600)
5112            .open(&path)?;
5113        file.write_all(secret.as_bytes())?;
5114    }
5115    #[cfg(not(unix))]
5116    std::fs::write(&path, secret)?;
5117    Ok(path)
5118}
5119
5120/// Open a connect-mode descriptor: resolve the endpoint address and
5121/// credential from the harness's own config file and build the backend that
5122/// joins the already-running endpoint. Fails closed with a specific
5123/// diagnostic when the config cannot be resolved or the declared protocol has
5124/// no connect-capable client yet.
5125fn open_connect_descriptor(
5126    descriptor: &crate::HarnessSupportDescriptor,
5127    home: &Path,
5128) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
5129    let Some(connect) = &descriptor.runtime.connect_launch else {
5130        return Err(ServiceError::InvalidParams(format!(
5131            "harness `{}` has no registered connect-mode launch",
5132            descriptor.id.as_str()
5133        )));
5134    };
5135    let resolved = connect
5136        .resolve(home)
5137        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
5138    match (descriptor.id.as_str(), connect.protocol.as_str()) {
5139        (HarnessId::OPENCODE, protocol) if protocol.starts_with("opencode-http") => {
5140            let mut backend = OpenCodeRuntimeBackend::connect(&resolved.address);
5141            if let Some(token) = resolved.auth {
5142                backend = backend.with_bearer(token);
5143            }
5144            Ok(Box::new(backend))
5145        }
5146        (HarnessId::OPENCLAW, protocol) if protocol.starts_with("acp") => {
5147            // OpenClaw's own `openclaw acp` binary is the gateway client: a
5148            // stdio ACP bridge that joins the RUNNING gateway at the resolved
5149            // endpoint. Blind-walk finding 2026-08-31: the real bridge does
5150            // NOT honor OPENCLAW_GATEWAY_TOKEN from the environment — the
5151            // credential must arrive via `--token-file` (never bare `--token`
5152            // on argv, where process listings could read it). The env var is
5153            // still set for older bridges that did read it. Requires openclaw
5154            // >= 2026.7: the 2026.2 bridge drops its gateway socket
5155            // mid-prompt and advertises no session resume (executed finding,
5156            // docs/interop/research/openclaw-acp-dialect-2026-08-30.json).
5157            let mut env = BTreeMap::new();
5158            let mut arguments = vec!["acp".into(), "--url".into(), resolved.address.clone()];
5159            if let Some(token) = resolved.auth {
5160                let token_path = openclaw_gateway_token_file(&resolved.address, token.secret())
5161                    .map_err(|error| {
5162                        ServiceError::UnsupportedAction(format!(
5163                            "could not stage the gateway credential for the bridge: {error}"
5164                        ))
5165                    })?;
5166                arguments.push("--token-file".into());
5167                arguments.push(token_path.to_string_lossy().into_owned());
5168                env.insert("OPENCLAW_GATEWAY_TOKEN".to_string(), token.secret().to_string());
5169            }
5170            // The bridge program comes from the descriptor's own default
5171            // launch (the compiled registry pins `openclaw`), so tests can
5172            // substitute an absolute mock-bridge path without touching
5173            // process-global state.
5174            let program = descriptor
5175                .runtime
5176                .default_launch
5177                .as_ref()
5178                .map(|launch| launch.program.clone())
5179                .unwrap_or_else(|| "openclaw".into());
5180            let launch = RuntimeLaunch {
5181                program,
5182                arguments,
5183                env,
5184            };
5185            Ok(Box::new(
5186                crate::AcpRuntimeBackend::new(descriptor.id.clone(), launch)
5187                    .with_resume_support(descriptor.runtime.capabilities.resume_session),
5188            ))
5189        }
5190        _ => Err(ServiceError::UnsupportedAction(format!(
5191            "connect-mode endpoint for `{}` speaks `{}`; joining it needs that protocol's gateway client",
5192            descriptor.id.as_str(),
5193            connect.protocol
5194        ))),
5195    }
5196}
5197
5198/// The registry's connect-mode launch for this harness, honored only when the
5199/// caller supplied neither an explicit launch nor a base URL.
5200fn registry_connect_descriptor(
5201    params: &RuntimeBackendParams,
5202) -> Option<crate::HarnessSupportDescriptor> {
5203    if params.launch.is_some() || params.base_url.is_some() {
5204        return None;
5205    }
5206    harness_support_registry()
5207        .harnesses
5208        .into_iter()
5209        .find(|descriptor| descriptor.id == params.harness)
5210        .filter(|descriptor| descriptor.runtime.connect_launch.is_some())
5211}
5212
5213fn service_home() -> std::result::Result<PathBuf, ServiceError> {
5214    supercode_interchange::user_home()
5215        .map(std::path::PathBuf::into_os_string)
5216        .map(PathBuf::from)
5217        .ok_or_else(|| {
5218            ServiceError::UnsupportedAction(
5219                "connect-mode launches need HOME to locate the harness config".into(),
5220            )
5221        })
5222}
5223
5224/// The doors that open a runtime: each spawns or joins a program and waits on
5225/// that program's protocol handshake before it can answer.
5226pub const RUNTIME_OPEN_METHODS: &[&str] = &[
5227    "harness.v1.runtimes.start",
5228    "harness.v1.runtimes.resume",
5229    "harness.v1.runtimes.attach",
5230    "harness.v1.runtimes.attach_existing",
5231];
5232
5233/// How long a runtime gets to finish opening before its caller is answered an
5234/// error instead. A program that never speaks the protocol at all — the wrong
5235/// binary, a shim that prints usage and waits — never answers the handshake,
5236/// so the wait is unbounded without this.
5237pub const RUNTIME_OPEN_DEADLINE: Duration = Duration::from_secs(60);
5238
5239/// How long a control call on an ALREADY-open runtime — send input, interrupt,
5240/// steer, respond, close — gets before its caller is answered an error
5241/// instead. A live runtime answers these in milliseconds; a wedged one never
5242/// answers at all, and `close` is exactly what a caller reaches for when it
5243/// suspects that.
5244pub const RUNTIME_CONTROL_DEADLINE: Duration = Duration::from_secs(30);
5245
5246/// The doors whose work happens entirely OUTSIDE this service's state once
5247/// its state has been read: probing harnesses, relaying a message into a
5248/// live session, and performing a conversation verb through a harness's own
5249/// CLI / HTTP / store door. Every one of them waits on a child process or a
5250/// network peer. See [`HarnessSessionService::detach`].
5251pub const DETACHED_METHODS: &[&str] = &[
5252    "harness.v1.harnesses.list",
5253    "harness.v1.harnesses.probe",
5254    "harness.v1.sessions.message",
5255    "harness.v1.sessions.new",
5256    "harness.v1.sessions.reset",
5257    "harness.v1.sessions.archive",
5258    "harness.v1.sessions.delete",
5259];
5260
5261/// How long a request moved off a transport's loop gets before its caller is
5262/// answered an error instead. Each of these already bounds its own inner
5263/// waits (a probe's handshake, the relay's send); this is the backstop for
5264/// the ones that do not — a harness CLI that never exits — so no caller waits
5265/// forever on a detached task no one is watching.
5266pub const DETACHED_CALL_DEADLINE: Duration = Duration::from_secs(120);
5267
5268/// How long `sessions.discover` gets before its caller is answered an error
5269/// instead. Discovery reads each harness's own store, and a store on a cold
5270/// or unavailable mount answers at the filesystem's pace rather than its own.
5271///
5272/// Deliberately shorter than the clients' own request deadline (30s): the
5273/// server's answer names the store that did not answer, and it is only read
5274/// if it lands before the client stops listening.
5275pub const SESSION_DISCOVER_DEADLINE: Duration = Duration::from_secs(25);
5276
5277/// Bound one control call on an open runtime by [`RUNTIME_CONTROL_DEADLINE`],
5278/// naming the method and the bound when it blows.
5279async fn within_control_deadline<F: std::future::Future>(
5280    method: &str,
5281    call: F,
5282) -> std::result::Result<F::Output, ServiceError> {
5283    tokio::time::timeout(RUNTIME_CONTROL_DEADLINE, call)
5284        .await
5285        .map_err(|_| {
5286            ServiceError::Operation(format!(
5287                "`{method}` gave up after {}s: the runtime did not answer",
5288                RUNTIME_CONTROL_DEADLINE.as_secs()
5289            ))
5290        })
5291}
5292
5293/// One [`RUNTIME_OPEN_METHODS`] request, parsed but not yet started. See
5294/// [`HarnessSessionService::runtime_open`] for why it exists apart from
5295/// [`HarnessSessionService::handle_async`].
5296pub struct RuntimeOpen {
5297    id: Value,
5298    method: String,
5299    params: Value,
5300}
5301
5302impl RuntimeOpen {
5303    /// Do the waiting: spawn or join the program and complete its handshake,
5304    /// bounded by [`RUNTIME_OPEN_DEADLINE`]. Touches no service state, so this
5305    /// runs on any task.
5306    pub async fn open(self) -> OpenedRuntime {
5307        let Self { id, method, params } = self;
5308        let outcome = open_runtime(&method, params).await;
5309        OpenedRuntime { id, outcome }
5310    }
5311}
5312
5313/// The result of [`RuntimeOpen::open`], ready for
5314/// [`HarnessSessionService::finish_runtime_open`].
5315pub struct OpenedRuntime {
5316    id: Value,
5317    outcome: std::result::Result<OpenRuntime, ServiceError>,
5318}
5319
5320/// One detached request: the half that reads this service's state already
5321/// done, and the half that waits not yet started. See
5322/// [`HarnessSessionService::detach`] and
5323/// [`HarnessSessionService::detach_runtime`].
5324pub struct DetachedCall {
5325    id: Value,
5326    method: String,
5327    work: std::result::Result<Work, ServiceError>,
5328}
5329
5330impl DetachedCall {
5331    /// Do the waiting and answer. Runs on any task: whatever this call needed
5332    /// from the service was taken before it left.
5333    pub async fn run(self) -> DetachedAnswer {
5334        let Self { id, method, work } = self;
5335        match work {
5336            // A call holding a runtime is already bounded by
5337            // RUNTIME_CONTROL_DEADLINE, and its future OWNS that connection:
5338            // a second timeout around it would drop the connection mid-call
5339            // and take down a runtime its caller still has.
5340            Ok(Work::Runtime(work)) => {
5341                let (result, returned) = work.run().await;
5342                DetachedAnswer {
5343                    response: service_response(id, result),
5344                    returned,
5345                }
5346            }
5347            Ok(Work::Free(work)) => {
5348                let result = match tokio::time::timeout(DETACHED_CALL_DEADLINE, work.run()).await {
5349                    Ok(result) => result,
5350                    Err(_) => Err(ServiceError::Operation(format!(
5351                        "`{method}` gave up after {}s: the harness it waits on did not answer",
5352                        DETACHED_CALL_DEADLINE.as_secs()
5353                    ))),
5354                };
5355                DetachedAnswer {
5356                    response: service_response(id, result),
5357                    returned: None,
5358                }
5359            }
5360            Err(error) => DetachedAnswer {
5361                response: service_response(id, Err(error)),
5362                returned: None,
5363            },
5364        }
5365    }
5366}
5367
5368/// One detached call's complete answer, plus whatever it must hand back to
5369/// the service before that answer is written. See
5370/// [`HarnessSessionService::finish_detached`].
5371pub struct DetachedAnswer {
5372    response: Value,
5373    returned: Option<ReturnedRuntime>,
5374}
5375
5376impl DetachedAnswer {
5377    /// The caller's JSON-RPC response, for a transport that owns no service
5378    /// to give a borrowed connection back to.
5379    pub fn into_response(self) -> Value {
5380        self.response
5381    }
5382}
5383
5384/// A connection lent to a detached call, on its way back to the service that
5385/// owns it.
5386pub struct ReturnedRuntime {
5387    connection: String,
5388    runtime: Box<dyn RuntimeConnection>,
5389}
5390
5391/// The waiting half of one detached request: with nothing of the service's
5392/// in hand, or holding a connection the service lent out for the call.
5393enum Work {
5394    Free(DetachedWork),
5395    Runtime(RuntimeWork),
5396}
5397
5398/// The waiting half of one detached request that holds nothing of the
5399/// service's.
5400enum DetachedWork {
5401    /// Probe the selected harnesses: find their executables, ask each its
5402    /// version, and at `probe: handshake` start each one and complete its
5403    /// protocol handshake.
5404    Inventory(InventoryWork),
5405    /// Relay one message into a live session.
5406    Message(MessageSessionParams),
5407    /// Perform one conversation verb through the harness's own CLI, HTTP API,
5408    /// daemon socket, or supercode's own store.
5409    SessionMutation {
5410        verb: crate::SessionVerb,
5411        mutation: crate::SessionMutation,
5412    },
5413}
5414
5415impl DetachedWork {
5416    async fn run(self) -> std::result::Result<Value, ServiceError> {
5417        match self {
5418            Self::Inventory(work) => run_inventory(work).await,
5419            Self::Message(params) => Ok(message_live_session(&params).await),
5420            Self::SessionMutation { verb, mutation } => {
5421                let outcome = run_session_mutation(verb, &mutation).await?;
5422                serde_json::to_value(outcome)
5423                    .map_err(|error| ServiceError::Operation(error.to_string()))
5424            }
5425        }
5426    }
5427}
5428
5429/// One detached call that holds a runtime connection for its whole run.
5430enum RuntimeWork {
5431    /// Tear down a runtime the service has already surrendered.
5432    Close {
5433        runtime: Box<dyn RuntimeConnection>,
5434        process_group: Option<u32>,
5435    },
5436    /// Type one live slash command through a borrowed connection, then give
5437    /// the connection back.
5438    LiveCommand {
5439        connection: String,
5440        runtime: Box<dyn RuntimeConnection>,
5441        verb: crate::SessionVerb,
5442        mutation: crate::SessionMutation,
5443        command: &'static str,
5444        session: String,
5445    },
5446}
5447
5448/// What one [`RuntimeWork`] answers with: the caller's result, and the
5449/// connection to give back when the call only borrowed one.
5450type RuntimeWorkAnswer = (
5451    std::result::Result<Value, ServiceError>,
5452    Option<ReturnedRuntime>,
5453);
5454
5455impl RuntimeWork {
5456    async fn run(self) -> RuntimeWorkAnswer {
5457        match self {
5458            Self::Close {
5459                runtime,
5460                process_group,
5461            } => (close_runtime(runtime, process_group).await, None),
5462            Self::LiveCommand {
5463                connection,
5464                mut runtime,
5465                verb,
5466                mutation,
5467                command,
5468                session,
5469            } => {
5470                let result =
5471                    type_live_command(runtime.as_mut(), verb, &mutation, command, session).await;
5472                (
5473                    result,
5474                    Some(ReturnedRuntime {
5475                        connection,
5476                        runtime,
5477                    }),
5478                )
5479            }
5480        }
5481    }
5482}
5483
5484/// Tear down a runtime already out of the service, within
5485/// [`RUNTIME_CONTROL_DEADLINE`].
5486async fn close_runtime(
5487    mut runtime: Box<dyn RuntimeConnection>,
5488    process_group: Option<u32>,
5489) -> std::result::Result<Value, ServiceError> {
5490    match within_control_deadline("harness.v1.runtimes.close", runtime.close()).await {
5491        Ok(result) => {
5492            result.map_err(operation)?;
5493            Ok(json!({"closed": true}))
5494        }
5495        Err(deadline) => {
5496            // Dropping the handle is not enough: the process that stopped
5497            // answering is held by a task parked on it, so nothing here runs
5498            // its Drop. Signal the group the graceful path would have
5499            // signalled, then say so.
5500            let killed = kill_runtime_process_group(process_group);
5501            drop(runtime);
5502            Ok(json!({
5503                "closed": true,
5504                "killed": killed,
5505                "detail": error_message(deadline),
5506            }))
5507        }
5508    }
5509}
5510
5511/// The conversation a live `sessions.new` / `sessions.reset` acts on: the one
5512/// the request named, or the runtime's own session.
5513fn live_session_name(runtime: &dyn RuntimeConnection, mutation: &crate::SessionMutation) -> String {
5514    mutation
5515        .session
5516        .clone()
5517        .filter(|value| !value.trim().is_empty())
5518        .unwrap_or_else(|| runtime.handle().runtime_id.clone())
5519}
5520
5521/// Type one harness slash command into a live session through the very same
5522/// `send_input` path a human's message takes, within
5523/// [`RUNTIME_CONTROL_DEADLINE`].
5524async fn type_live_command(
5525    runtime: &mut dyn RuntimeConnection,
5526    verb: crate::SessionVerb,
5527    mutation: &crate::SessionMutation,
5528    command: &str,
5529    session: String,
5530) -> std::result::Result<Value, ServiceError> {
5531    within_control_deadline(
5532        &format!("sessions.{}", verb.as_str()),
5533        runtime.send_input(RuntimeInput {
5534            text: command.to_string(),
5535            image_urls: Vec::new(),
5536        }),
5537    )
5538    .await?
5539    .map_err(operation)?;
5540    let outcome = crate::sessions_control::live_outcome(verb, mutation, command, session)
5541        .map_err(session_control_error)?;
5542    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
5543}
5544
5545/// A runtime that is up and whose handshake completed, with what the service
5546/// needs to take ownership of it.
5547enum OpenRuntime {
5548    /// supercode spawned this process, so it also hosts it: a frontend server,
5549    /// a live-runtime registration and a terminal launch of its own.
5550    Hosted {
5551        runtime: Box<dyn RuntimeConnection>,
5552        capabilities: crate::RuntimeCapabilities,
5553        workspace: PathBuf,
5554    },
5555    /// `attach_existing` joined a process supercode does not own. It is
5556    /// registered as a bare connection and hosts nothing.
5557    Joined { runtime: Box<dyn RuntimeConnection> },
5558}
5559
5560/// Open the runtime one [`RUNTIME_OPEN_METHODS`] request asks for, within
5561/// [`RUNTIME_OPEN_DEADLINE`]. The error a blown deadline answers names the
5562/// method and the bound, so a caller reads why it was cut loose instead of
5563/// waiting on a handshake that is never coming.
5564async fn open_runtime(
5565    method: &str,
5566    params: Value,
5567) -> std::result::Result<OpenRuntime, ServiceError> {
5568    match tokio::time::timeout(
5569        RUNTIME_OPEN_DEADLINE,
5570        open_runtime_unbounded(method, params),
5571    )
5572    .await
5573    {
5574        Ok(result) => result,
5575        Err(_) => Err(ServiceError::Operation(format!(
5576            "`{method}` gave up after {}s: the runtime never finished its protocol handshake",
5577            RUNTIME_OPEN_DEADLINE.as_secs()
5578        ))),
5579    }
5580}
5581
5582async fn open_runtime_unbounded(
5583    method: &str,
5584    params: Value,
5585) -> std::result::Result<OpenRuntime, ServiceError> {
5586    match method {
5587        "harness.v1.runtimes.start" => {
5588            let params = decode::<RuntimeStartParams>(params)?;
5589            let backend = runtime_backend(&params.backend)?;
5590            let capabilities = backend.capabilities();
5591            let workspace = params.cwd.clone();
5592            let runtime = backend
5593                .start(RuntimeStartRequest {
5594                    cwd: params.cwd,
5595                    launch: runtime_launch(&params.backend),
5596                    mcp_servers: params.mcp_servers,
5597                    approval_policy: params.approval_policy,
5598                })
5599                .await
5600                .map_err(operation)?;
5601            Ok(OpenRuntime::Hosted {
5602                runtime,
5603                capabilities,
5604                workspace,
5605            })
5606        }
5607        "harness.v1.runtimes.resume" | "harness.v1.runtimes.attach" => {
5608            let params = decode::<RuntimeAttachParams>(params)?;
5609            let backend = runtime_backend(&params.backend)?;
5610            let capabilities = backend.capabilities();
5611            let workspace = params
5612                .cwd
5613                .clone()
5614                .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
5615            let runtime = backend
5616                .attach(RuntimeAttachRequest {
5617                    runtime_id: params.runtime_id,
5618                    cwd: params.cwd,
5619                    launch: runtime_launch(&params.backend),
5620                    mcp_servers: params.mcp_servers,
5621                    approval_policy: params.approval_policy,
5622                })
5623                .await
5624                .map_err(operation)?;
5625            Ok(OpenRuntime::Hosted {
5626                runtime,
5627                capabilities,
5628                workspace,
5629            })
5630        }
5631        "harness.v1.runtimes.attach_existing" => {
5632            let params = decode::<RuntimeAttachParams>(params)?;
5633            let backend: Box<dyn RuntimeBackend> = match params
5634                .backend
5635                .base_url
5636                .as_deref()
5637                .and_then(|value| LiveRuntimeEndpoint::parse(value).ok())
5638            {
5639                Some(endpoint) => {
5640                    #[cfg(not(feature = "adapter-api"))]
5641                    {
5642                        let _ = endpoint;
5643                        return Err(ServiceError::UnsupportedAction(
5644                            "live HTTP attachment adapter is not compiled".into(),
5645                        ));
5646                    }
5647                    #[cfg(feature = "adapter-api")]
5648                    {
5649                        let workspace = params.cwd.clone().ok_or_else(|| {
5650                            ServiceError::InvalidParams(
5651                                "Volter Harness live attach requires the project cwd".into(),
5652                            )
5653                        })?;
5654                        let source = LiveRuntimeSource {
5655                            harness: params.backend.harness.as_str().to_string(),
5656                            session_id: params.runtime_id.clone(),
5657                            workspace,
5658                        };
5659                        let receipt = resolve_live_runtime(&endpoint, &source)
5660                            .map_err(|error| ServiceError::Operation(error.to_string()))?;
5661                        Box::new(SupercodeHttpRuntimeBackend::new(receipt))
5662                    }
5663                }
5664                None => runtime_backend(&params.backend)?,
5665            };
5666            let capabilities = backend.capabilities();
5667            if !capabilities.attach_existing_process {
5668                return Err(ServiceError::Operation(format!(
5669                    "{} cannot attach to an already-running process; use runtimes.resume for a persisted session",
5670                    backend.harness().as_str()
5671                )));
5672            }
5673            let runtime = backend
5674                .attach_existing(RuntimeAttachRequest {
5675                    runtime_id: params.runtime_id,
5676                    cwd: params.cwd,
5677                    launch: runtime_launch(&params.backend),
5678                    mcp_servers: params.mcp_servers,
5679                    approval_policy: params.approval_policy,
5680                })
5681                .await
5682                .map_err(operation)?;
5683            Ok(OpenRuntime::Joined { runtime })
5684        }
5685        _ => Err(ServiceError::MethodNotFound),
5686    }
5687}
5688
5689/// Wrap one service outcome in its JSON-RPC 2.0 envelope.
5690fn service_response(id: Value, result: std::result::Result<Value, ServiceError>) -> Value {
5691    match result {
5692        Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
5693        Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
5694        Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
5695        Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
5696        Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
5697        Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
5698    }
5699}
5700
5701fn runtime_backend(
5702    params: &RuntimeBackendParams,
5703) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
5704    if let Some(descriptor) = registry_connect_descriptor(params) {
5705        return open_connect_descriptor(&descriptor, &service_home()?);
5706    }
5707    if params.protocol.as_deref() == Some("acp") {
5708        let launch = params
5709            .launch
5710            .clone()
5711            .or_else(|| {
5712                harness_support_registry()
5713                    .harnesses
5714                    .into_iter()
5715                    .find(|harness| harness.id == params.harness)
5716                    .filter(|harness| {
5717                        harness.runtime.implementation == ImplementationKind::GenericProtocol
5718                            && harness.runtime.protocol.starts_with("acp")
5719                    })
5720                    .and_then(|harness| harness.runtime.default_launch)
5721            })
5722            .ok_or_else(|| {
5723                ServiceError::InvalidParams(
5724                    "an ACP runtime requires `launch` unless the harness has a registered default"
5725                        .into(),
5726                )
5727            })?;
5728        let resume_session = harness_support_registry()
5729            .harnesses
5730            .into_iter()
5731            .find(|harness| harness.id == params.harness)
5732            .is_some_and(|harness| harness.runtime.capabilities.resume_session);
5733        return Ok(Box::new(
5734            AcpRuntimeBackend::new(params.harness.clone(), launch)
5735                .with_resume_support(resume_session),
5736        ));
5737    }
5738    let backend: Box<dyn RuntimeBackend> = match params.harness.as_str() {
5739        HarnessId::CODEX => Box::new(CodexRuntimeBackend::new()),
5740        HarnessId::CLAUDE_CODE => Box::new(ClaudeCodeRuntimeBackend::new()),
5741        HarnessId::PI => Box::new(PiRuntimeBackend::new()),
5742        HarnessId::OPENCODE => match &params.base_url {
5743            Some(url) => Box::new(OpenCodeRuntimeBackend::connect(url)),
5744            None => Box::new(OpenCodeRuntimeBackend::new()),
5745        },
5746        harness => {
5747            let descriptor = harness_support_registry()
5748                .harnesses
5749                .into_iter()
5750                .find(|descriptor| descriptor.id.as_str() == harness)
5751                .filter(|descriptor| {
5752                    descriptor.runtime.implementation == ImplementationKind::GenericProtocol
5753                        && descriptor.runtime.protocol.starts_with("acp")
5754                });
5755            let Some(descriptor) = descriptor else {
5756                return Err(ServiceError::InvalidParams(format!(
5757                    "no runtime adapter for harness `{harness}`; use protocol `acp` with a launch command"
5758                )));
5759            };
5760            let resume = descriptor.runtime.capabilities.resume_session;
5761            Box::new(
5762                AcpRuntimeBackend::new(
5763                    descriptor.id,
5764                    descriptor
5765                        .runtime
5766                        .default_launch
5767                        .expect("generic ACP registry entry includes its launch"),
5768                )
5769                .with_resume_support(resume),
5770            )
5771        }
5772    };
5773    Ok(backend)
5774}
5775
5776fn runtime_launch(params: &RuntimeBackendParams) -> Option<RuntimeLaunch> {
5777    if let Some(launch) = &params.launch {
5778        return Some(launch.clone());
5779    }
5780    if !matches!(params.policy, RuntimePolicy::Yolo) {
5781        return None;
5782    }
5783    let launch = match params.harness.as_str() {
5784        HarnessId::GROK => RuntimeLaunch {
5785            program: "grok".into(),
5786            arguments: {
5787                let mut arguments: Vec<String> = Vec::new();
5788                if crate::support::self_sandbox_supported() {
5789                    arguments.extend(["--sandbox".into(), "workspace".into()]);
5790                }
5791                arguments.extend([
5792                    "--always-approve".into(),
5793                    "agent".into(),
5794                    "--no-leader".into(),
5795                    "stdio".into(),
5796                ]);
5797                arguments
5798            },
5799            env: crate::support::grok_env(),
5800        },
5801        HarnessId::CODEX => RuntimeLaunch {
5802            program: "codex".into(),
5803            arguments: vec![
5804                "--dangerously-bypass-approvals-and-sandbox".into(),
5805                "--dangerously-bypass-hook-trust".into(),
5806                "app-server".into(),
5807            ],
5808            env: BTreeMap::new(),
5809        },
5810        HarnessId::CLAUDE_CODE => RuntimeLaunch {
5811            program: "claude".into(),
5812            arguments: vec![
5813                "--dangerously-skip-permissions".into(),
5814                "--print".into(),
5815                "--input-format".into(),
5816                "stream-json".into(),
5817                "--output-format".into(),
5818                "stream-json".into(),
5819                "--verbose".into(),
5820            ],
5821            env: BTreeMap::new(),
5822        },
5823        HarnessId::PI => RuntimeLaunch {
5824            program: "pi".into(),
5825            arguments: vec!["--approve".into(), "--mode".into(), "rpc".into()],
5826            env: BTreeMap::new(),
5827        },
5828        HarnessId::OPENCODE => RuntimeLaunch {
5829            program: "opencode".into(),
5830            arguments: vec!["serve".into()],
5831            env: BTreeMap::new(),
5832        },
5833        HarnessId::GEMINI => RuntimeLaunch {
5834            program: "gemini".into(),
5835            arguments: vec!["--acp".into(), "--yolo".into()],
5836            env: BTreeMap::new(),
5837        },
5838        HarnessId::GOOSE => RuntimeLaunch {
5839            program: "goose".into(),
5840            arguments: vec!["acp".into()],
5841            env: BTreeMap::new(),
5842        },
5843        HarnessId::SUPERCODE => RuntimeLaunch {
5844            program: "supercode".into(),
5845            arguments: vec!["acp".into(), "--dangerous".into()],
5846            env: BTreeMap::new(),
5847        },
5848        _ => return None,
5849    };
5850    Some(launch)
5851}
5852
5853/// Disposable harness state for a no-prompt readiness probe. Merely opening
5854/// several stock CLIs writes a session header or migrates configuration, so a
5855/// handshake must never point at the user's real home. Authentication files
5856/// are copied into the private temporary home; all writes disappear with the
5857/// guard after the connection closes.
5858struct IsolatedProbeHome {
5859    launch: RuntimeLaunch,
5860    root: PathBuf,
5861}
5862
5863impl IsolatedProbeHome {
5864    fn new(harness: &str, mut launch: RuntimeLaunch) -> std::io::Result<Self> {
5865        let root = std::env::temp_dir().join(format!(
5866            "supercode-harness-probe-{harness}-{}",
5867            generated_session_id()
5868        ));
5869        std::fs::create_dir_all(&root)?;
5870        set_private_dir_permissions(&root)?;
5871
5872        if let Some(source_home) = supercode_interchange::user_home()
5873            .map(std::path::PathBuf::into_os_string)
5874            .map(PathBuf::from)
5875        {
5876            for relative in probe_auth_files(harness) {
5877                copy_probe_file(&source_home, &root, relative)?;
5878            }
5879        }
5880        // supercode reads its own config home ($SUPERCODE_HOME, else
5881        // $XDG_CONFIG_HOME/supercode, else ~/.config/supercode), not a fixed
5882        // place under HOME: a login kept under XDG_CONFIG_HOME probed as
5883        // "no API key found" while `supercode run` answered.
5884        if harness == HarnessId::SUPERCODE {
5885            let config_home = crate::agent::global_instructions_dir();
5886            for file in ["config.toml", "credentials.toml"] {
5887                copy_probe_path(
5888                    &config_home.join(file),
5889                    &root.join(".config/supercode").join(file),
5890                )?;
5891            }
5892        }
5893        configure_isolated_probe_auth(harness, &root)?;
5894
5895        let root_text = root.to_string_lossy().into_owned();
5896        for (key, value) in [
5897            ("HOME", root_text.clone()),
5898            (
5899                "XDG_CACHE_HOME",
5900                root.join(".cache").to_string_lossy().into_owned(),
5901            ),
5902            (
5903                "XDG_CONFIG_HOME",
5904                root.join(".config").to_string_lossy().into_owned(),
5905            ),
5906            (
5907                "XDG_DATA_HOME",
5908                root.join(".local/share").to_string_lossy().into_owned(),
5909            ),
5910        ] {
5911            launch.env.insert(key.into(), value);
5912        }
5913        let scoped = match harness {
5914            HarnessId::CLAUDE_CODE => Some(("CLAUDE_CONFIG_DIR", root.join(".claude"))),
5915            HarnessId::CODEX => Some(("CODEX_HOME", root.join(".codex"))),
5916            HarnessId::GEMINI => Some(("GEMINI_CLI_HOME", root.clone())),
5917            HarnessId::GROK => Some(("GROK_HOME", root.join(".grok"))),
5918            HarnessId::PI => Some(("PI_CODING_AGENT_DIR", root.join(".pi/agent"))),
5919            HarnessId::SUPERCODE => Some(("SUPERCODE_HOME", root.join(".config/supercode"))),
5920            _ => None,
5921        };
5922        if let Some((key, value)) = scoped {
5923            launch
5924                .env
5925                .insert(key.into(), value.to_string_lossy().into_owned());
5926        }
5927        Ok(Self { launch, root })
5928    }
5929
5930    fn cleanup(&self) -> std::io::Result<()> {
5931        match std::fs::remove_dir_all(&self.root) {
5932            Ok(()) => Ok(()),
5933            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
5934            Err(error) => Err(error),
5935        }
5936    }
5937}
5938
5939impl Drop for IsolatedProbeHome {
5940    fn drop(&mut self) {
5941        let _ = self.cleanup();
5942    }
5943}
5944
5945fn probe_auth_files(harness: &str) -> &'static [&'static str] {
5946    match harness {
5947        HarnessId::CLAUDE_CODE => &[".claude/.credentials.json", ".claude.json"],
5948        // The gateway endpoint + token live in openclaw's own config; without
5949        // it the isolated probe dials the default endpoint unauthenticated
5950        // (PARITY-24 finding 2026-08-31).
5951        HarnessId::OPENCLAW => &[".openclaw/openclaw.json"],
5952        HarnessId::CODEX => &[".codex/auth.json"],
5953        HarnessId::GEMINI => &[
5954            ".gemini/google_accounts.json",
5955            ".gemini/oauth_creds.json",
5956            ".gemini/settings.json",
5957        ],
5958        HarnessId::GROK => &[".grok/auth.json", ".grok/config.toml"],
5959        HarnessId::OPENCODE => &[
5960            ".config/opencode/auth.json",
5961            ".local/share/opencode/auth.json",
5962        ],
5963        HarnessId::PI => &[".pi/agent/auth.json"],
5964        // Hermes keeps its provider selection in config.yaml, its OAuth
5965        // credential pool in auth.json, and API keys in .env; without them
5966        // the isolated probe sees "No LLM provider configured" for a
5967        // hermes that answers fine from the user's real home.
5968        HarnessId::HERMES => &[".hermes/config.yaml", ".hermes/auth.json", ".hermes/.env"],
5969        _ => &[],
5970    }
5971}
5972
5973fn copy_probe_file(source_home: &Path, probe_home: &Path, relative: &str) -> std::io::Result<()> {
5974    copy_probe_path(&source_home.join(relative), &probe_home.join(relative))
5975}
5976
5977fn copy_probe_path(source: &Path, destination: &Path) -> std::io::Result<()> {
5978    if !source.is_file() {
5979        return Ok(());
5980    }
5981    if let Some(parent) = destination.parent() {
5982        std::fs::create_dir_all(parent)?;
5983        set_private_dir_permissions(parent)?;
5984    }
5985    std::fs::copy(source, destination)?;
5986    set_private_file_permissions(destination)
5987}
5988
5989fn configure_isolated_probe_auth(harness: &str, probe_home: &Path) -> std::io::Result<()> {
5990    if harness != HarnessId::GEMINI {
5991        return Ok(());
5992    }
5993    let oauth = probe_home.join(".gemini/oauth_creds.json");
5994    if !oauth.is_file() {
5995        return Ok(());
5996    }
5997    let settings_path = probe_home.join(".gemini/settings.json");
5998    let mut settings = std::fs::read_to_string(&settings_path)
5999        .ok()
6000        .and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
6001        .unwrap_or_else(|| json!({}));
6002    settings["security"]["auth"]["selectedType"] = Value::String("oauth-personal".into());
6003    std::fs::write(
6004        &settings_path,
6005        serde_json::to_vec_pretty(&settings).map_err(std::io::Error::other)?,
6006    )?;
6007    set_private_file_permissions(&settings_path)
6008}
6009
6010#[cfg(unix)]
6011fn set_private_dir_permissions(path: &Path) -> std::io::Result<()> {
6012    use std::os::unix::fs::PermissionsExt;
6013    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
6014}
6015
6016#[cfg(not(unix))]
6017fn set_private_dir_permissions(_path: &Path) -> std::io::Result<()> {
6018    Ok(())
6019}
6020
6021#[cfg(unix)]
6022fn set_private_file_permissions(path: &Path) -> std::io::Result<()> {
6023    use std::os::unix::fs::PermissionsExt;
6024    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
6025}
6026
6027#[cfg(not(unix))]
6028fn set_private_file_permissions(_path: &Path) -> std::io::Result<()> {
6029    Ok(())
6030}
6031
6032fn find_executable(program: &str) -> Option<PathBuf> {
6033    let candidate = PathBuf::from(program);
6034    if candidate.components().count() > 1 {
6035        return candidate.is_file().then_some(candidate);
6036    }
6037    let path = std::env::var_os("PATH")?;
6038    for directory in std::env::split_paths(&path) {
6039        let candidate = directory.join(program);
6040        if candidate.is_file() {
6041            return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
6042        }
6043        #[cfg(windows)]
6044        {
6045            for extension in ["exe", "cmd", "bat"] {
6046                let candidate = directory.join(format!("{program}.{extension}"));
6047                if candidate.is_file() {
6048                    return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
6049                }
6050            }
6051        }
6052    }
6053    None
6054}
6055
6056async fn executable_version(executable: &Path) -> Option<String> {
6057    let mut command = tokio::process::Command::new(executable);
6058    command
6059        .arg("--version")
6060        .stdin(std::process::Stdio::null())
6061        .stdout(std::process::Stdio::piped())
6062        .stderr(std::process::Stdio::piped())
6063        .kill_on_drop(true);
6064    let output = tokio::time::timeout(Duration::from_secs(3), command.output())
6065        .await
6066        .ok()?
6067        .ok()?;
6068    let stdout = String::from_utf8_lossy(&output.stdout);
6069    let stderr = String::from_utf8_lossy(&output.stderr);
6070    stdout
6071        .lines()
6072        .chain(stderr.lines())
6073        .map(str::trim)
6074        .find(|line| !line.is_empty())
6075        .map(|line| truncate_text(line, 200))
6076}
6077
6078pub(crate) fn auth_evidence(harness: &str) -> bool {
6079    let env_names: &[&str] = match harness {
6080        HarnessId::CLAUDE_CODE => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
6081        HarnessId::CODEX => &["OPENAI_API_KEY"],
6082        HarnessId::OPENCODE => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
6083        HarnessId::PI => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
6084        HarnessId::GROK => &["XAI_API_KEY", "GROK_API_KEY"],
6085        HarnessId::GEMINI => &["GEMINI_API_KEY", "GOOGLE_API_KEY"],
6086        HarnessId::SUPERCODE => &["OPENROUTER_API_KEY"],
6087        _ => &[],
6088    };
6089    if env_names
6090        .iter()
6091        .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
6092    {
6093        return true;
6094    }
6095    let Some(home) = supercode_interchange::user_home()
6096        .map(std::path::PathBuf::into_os_string)
6097        .map(PathBuf::from)
6098    else {
6099        return false;
6100    };
6101    let files: Vec<PathBuf> = match harness {
6102        HarnessId::CLAUDE_CODE => vec![home.join(".claude/.credentials.json")],
6103        HarnessId::CODEX => vec![home.join(".codex/auth.json")],
6104        HarnessId::OPENCODE => vec![
6105            home.join(".local/share/opencode/auth.json"),
6106            home.join(".config/opencode/auth.json"),
6107        ],
6108        HarnessId::PI => vec![home.join(".pi/agent/auth.json")],
6109        HarnessId::GROK => vec![home.join(".grok/auth.json")],
6110        HarnessId::GEMINI => vec![
6111            home.join(".gemini/oauth_creds.json"),
6112            home.join(".gemini/google_accounts.json"),
6113        ],
6114        HarnessId::SUPERCODE => vec![home.join(".config/supercode/credentials.toml")],
6115        HarnessId::HERMES => vec![home.join(".hermes/auth.json"), home.join(".hermes/.env")],
6116        _ => Vec::new(),
6117    };
6118    if files.into_iter().any(|path| {
6119        std::fs::metadata(path)
6120            .map(|metadata| metadata.is_file() && metadata.len() > 2)
6121            .unwrap_or(false)
6122    }) {
6123        return true;
6124    }
6125    // macOS keeps Claude Code's OAuth login in the Keychain, so
6126    // `.claude/.credentials.json` never exists there and the file probe above
6127    // reports a signed-in install as unauthenticated forever. A completed
6128    // login also writes an `oauthAccount` record into `~/.claude.json` on
6129    // every platform — file-based, prompt-free evidence (querying the
6130    // Keychain itself from an unsigned daemon can raise a UI prompt).
6131    if harness == HarnessId::CLAUDE_CODE {
6132        return std::fs::read_to_string(home.join(".claude.json"))
6133            .map(|text| text.contains("\"oauthAccount\""))
6134            .unwrap_or(false);
6135    }
6136    false
6137}
6138
6139fn looks_like_auth_error(message: &str) -> bool {
6140    let message = message.to_ascii_lowercase();
6141    [
6142        "auth",
6143        "login",
6144        "sign in",
6145        "sign-in",
6146        "credential",
6147        "unauthorized",
6148        "forbidden",
6149        "token",
6150    ]
6151    .iter()
6152    .any(|needle| message.contains(needle))
6153}
6154
6155fn unavailable_capabilities() -> crate::RuntimeCapabilities {
6156    crate::RuntimeCapabilities {
6157        start_session: false,
6158        resume_session: false,
6159        attach_existing_process: false,
6160        send_input: false,
6161        stream_events: false,
6162        interrupt: false,
6163        steer: false,
6164        respond_to_requests: false,
6165    }
6166}
6167
6168fn truncate_text(text: &str, max_chars: usize) -> String {
6169    let mut chars = text.chars();
6170    let truncated = chars.by_ref().take(max_chars).collect::<String>();
6171    if chars.next().is_some() {
6172        format!("{truncated}…")
6173    } else {
6174        truncated
6175    }
6176}
6177
6178/// The process group a runtime's own handle names, when it names one.
6179///
6180/// Every adapter that spawns a local process spawns it as its own group
6181/// leader (`Command::process_group(0)`), so the endpoint's pid IS the group
6182/// id. A runtime reached over HTTP, or one supercode joined rather than
6183/// spawned, names no group here and is left alone.
6184fn runtime_process_group(handle: &crate::RuntimeHandle) -> Option<u32> {
6185    match &handle.endpoint {
6186        crate::RuntimeEndpoint::LocalProcess { pid, .. } => *pid,
6187        crate::RuntimeEndpoint::Http { .. } => None,
6188    }
6189}
6190
6191/// SIGKILL a wedged runtime's whole process group, reporting whether there
6192/// was one to signal. This is the same group teardown a graceful `close`
6193/// performs; it runs here only when the graceful path blew its deadline,
6194/// because the task parked on the unanswered call still owns the process
6195/// handle and so no `Drop` of ours can reach it.
6196fn kill_runtime_process_group(process_group: Option<u32>) -> bool {
6197    match process_group {
6198        #[cfg(unix)]
6199        Some(pid) => {
6200            crate::lsp::kill_process_group(pid);
6201            true
6202        }
6203        #[cfg(not(unix))]
6204        Some(_) => false,
6205        None => false,
6206    }
6207}
6208
6209fn error_message(error: ServiceError) -> String {
6210    match error {
6211        ServiceError::InvalidParams(message)
6212        | ServiceError::Operation(message)
6213        | ServiceError::UnsupportedAction(message) => message,
6214        ServiceError::MethodNotFound => "runtime adapter is not available".into(),
6215        ServiceError::Sdk(error) => error.to_string(),
6216    }
6217}
6218
6219#[derive(Debug)]
6220enum ServiceError {
6221    InvalidParams(String),
6222    MethodNotFound,
6223    UnsupportedAction(String),
6224    Operation(String),
6225    Sdk(SdkError),
6226}
6227
6228fn sdk_error(operation: SdkOperation, error: ServiceError) -> SdkError {
6229    match error {
6230        ServiceError::InvalidParams(message) => {
6231            SdkError::new(SdkErrorCode::InvalidArgument, operation, message)
6232        }
6233        ServiceError::MethodNotFound | ServiceError::UnsupportedAction(_) => {
6234            SdkError::unsupported(operation)
6235        }
6236        ServiceError::Operation(message) => {
6237            let code = if message.contains("already in progress") {
6238                SdkErrorCode::Busy
6239            } else if message.contains("not supported by this runtime") {
6240                SdkErrorCode::UnsupportedAction
6241            } else if message.contains("unknown runtime connection") {
6242                SdkErrorCode::NotFound
6243            } else {
6244                SdkErrorCode::Execution
6245            };
6246            SdkError::new(code, operation, message)
6247        }
6248        ServiceError::Sdk(error) => error,
6249    }
6250}
6251
6252fn sdk_rpc_error(id: Value, error: &SdkError) -> Value {
6253    let error_code = error.code();
6254    let code = match error_code {
6255        SdkErrorCode::Unauthenticated => -32030,
6256        SdkErrorCode::Unauthorized => -32031,
6257        SdkErrorCode::ControllerRequired => -32032,
6258        SdkErrorCode::LeaseExpired => -32033,
6259        SdkErrorCode::InvalidArgument => -32602,
6260        SdkErrorCode::NotFound => -32004,
6261        SdkErrorCode::Busy => -32000,
6262        SdkErrorCode::UnsupportedAction => -32020,
6263        SdkErrorCode::Execution => -32002,
6264        SdkErrorCode::Transport => -32003,
6265    };
6266    json!({
6267        "jsonrpc": "2.0",
6268        "id": id,
6269        "error": {
6270            "code": code,
6271            "name": error_code,
6272            "operation": error.operation(),
6273            "message": error.to_string(),
6274        },
6275    })
6276}
6277
6278fn decode<T: for<'de> Deserialize<'de>>(value: Value) -> std::result::Result<T, ServiceError> {
6279    serde_json::from_value(value).map_err(|error| ServiceError::InvalidParams(error.to_string()))
6280}
6281
6282fn operation(error: impl Into<crate::Error>) -> ServiceError {
6283    let error = error.into();
6284    match error {
6285        crate::Error::Sdk(error) => ServiceError::Sdk(error),
6286        error => ServiceError::Operation(error.to_string()),
6287    }
6288}
6289
6290/// ORCH-12 `harness.v1.memory.show|search` params. `homes` is the same
6291/// storage-root override every read-only method accepts, so a caller can
6292/// point the read at a fixture home without touching the real ones.
6293#[derive(Debug, Clone, Deserialize, Default)]
6294#[serde(default)]
6295struct MemoryRequest {
6296    /// Harness whose store is read. Required.
6297    harness: Option<String>,
6298    /// The needle, required by `search`.
6299    query: Option<String>,
6300    /// Hermes profile, OpenClaw agent, or Claude Code project.
6301    profile: Option<String>,
6302    /// Claude Code session id selecting a project store (`show` only).
6303    session: Option<String>,
6304    /// Include each document's whole text (`show` only).
6305    full: bool,
6306    /// Treat `query` as a regular expression (`search` only).
6307    regex: bool,
6308    /// Working tree whose project store is read.
6309    cwd: Option<std::path::PathBuf>,
6310    /// Storage roots to read.
6311    homes: crate::HarnessHomes,
6312}
6313
6314/// Read the memory noun. A harness with no memory store fails with
6315/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6316fn memory_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6317    let request = decode::<MemoryRequest>(params)?;
6318    let harness = request
6319        .harness
6320        .clone()
6321        .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6322    let to_service = |error: crate::memory::MemoryError| match error {
6323        crate::memory::MemoryError::UnsupportedHarness { .. }
6324        | crate::memory::MemoryError::SessionNotScoped { .. } => {
6325            ServiceError::UnsupportedAction(error.to_string())
6326        }
6327        other => ServiceError::InvalidParams(other.to_string()),
6328    };
6329    match method {
6330        "harness.v1.memory.show" => {
6331            let documents = crate::memory::show_memory(&crate::memory::MemoryQuery {
6332                harness,
6333                profile: request.profile,
6334                session: request.session,
6335                full: request.full,
6336                cwd: request.cwd,
6337                homes: request.homes,
6338            })
6339            .map_err(to_service)?;
6340            Ok(json!({
6341                "schema": crate::memory::MEMORY_SCHEMA,
6342                "documents": documents,
6343            }))
6344        }
6345        "harness.v1.memory.search" => {
6346            let query = request
6347                .query
6348                .ok_or_else(|| ServiceError::InvalidParams("`query` is required".into()))?;
6349            let matches = crate::memory::search_memory(&crate::memory::MemorySearchQuery {
6350                harness,
6351                query,
6352                profile: request.profile,
6353                regex: request.regex,
6354                cwd: request.cwd,
6355                homes: request.homes,
6356            })
6357            .map_err(to_service)?;
6358            Ok(json!({
6359                "schema": crate::memory::MEMORY_SCHEMA,
6360                "matches": matches,
6361            }))
6362        }
6363        _ => Err(ServiceError::MethodNotFound),
6364    }
6365}
6366
6367/// ORCH-10 `harness.v1.profiles.list|get` params. `homes` is the same
6368/// storage-root override every read-only method accepts, so a caller can
6369/// point the read at a fixture home without touching the real ones.
6370#[derive(Debug, Clone, Deserialize)]
6371#[serde(default)]
6372struct ProfilesQuery {
6373    /// Restrict the listing to one harness. `get` requires it.
6374    harness: Option<String>,
6375    /// Profile name, required by `get`.
6376    name: Option<String>,
6377    /// Storage roots to read.
6378    homes: crate::HarnessHomes,
6379}
6380
6381impl Default for ProfilesQuery {
6382    fn default() -> Self {
6383        Self {
6384            harness: None,
6385            name: None,
6386            homes: crate::HarnessHomes::default(),
6387        }
6388    }
6389}
6390
6391/// Read the profile noun. A harness with no profile concept fails with
6392/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6393fn profiles_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6394    let query = decode::<ProfilesQuery>(params)?;
6395    let to_service = |error: crate::profiles::ProfileError| match error {
6396        crate::profiles::ProfileError::UnsupportedHarness { .. } => {
6397            ServiceError::UnsupportedAction(error.to_string())
6398        }
6399        crate::profiles::ProfileError::NotFound { .. } => {
6400            ServiceError::InvalidParams(error.to_string())
6401        }
6402    };
6403    match method {
6404        "harness.v1.profiles.list" => {
6405            let profiles = crate::profiles::list_profiles(&query.homes, query.harness.as_deref())
6406                .map_err(to_service)?;
6407            Ok(json!({
6408                "schema": crate::profiles::PROFILES_SCHEMA,
6409                "profiles": profiles,
6410            }))
6411        }
6412        "harness.v1.profiles.get" => {
6413            let harness = query
6414                .harness
6415                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6416            let name = query
6417                .name
6418                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6419            let profile =
6420                crate::profiles::get_profile(&query.homes, &harness, &name).map_err(to_service)?;
6421            Ok(json!({
6422                "schema": crate::profiles::PROFILES_SCHEMA,
6423                "profile": profile,
6424            }))
6425        }
6426        _ => Err(ServiceError::MethodNotFound),
6427    }
6428}
6429
6430/// ORCH-14 `harness.v1.channels.list|status` params, the same storage-root
6431/// override every read-only method accepts so a caller can point the read at
6432/// a fixture home without touching the real ones.
6433#[derive(Debug, Clone, Deserialize)]
6434#[serde(default)]
6435struct ChannelsQuery {
6436    /// Restrict the listing to one harness. `status` requires it.
6437    harness: Option<String>,
6438    /// Channel name, required by `status`.
6439    name: Option<String>,
6440    /// Storage roots to read.
6441    homes: crate::HarnessHomes,
6442}
6443
6444impl Default for ChannelsQuery {
6445    fn default() -> Self {
6446        Self {
6447            harness: None,
6448            name: None,
6449            homes: crate::HarnessHomes::default(),
6450        }
6451    }
6452}
6453
6454/// Read the channel noun. A harness with no channel concept fails with
6455/// `UnsupportedAction` (RPC `-32020`), never an empty list. No row carries a
6456/// token, key or secret — see `crate::channels` "Secrecy".
6457#[derive(Debug, Clone, Deserialize)]
6458#[serde(default)]
6459struct RoutesQuery {
6460    harness: Option<String>,
6461    /// Restrict to routes targeting one profile / agent.
6462    profile: Option<String>,
6463    homes: crate::HarnessHomes,
6464}
6465
6466impl Default for RoutesQuery {
6467    fn default() -> Self {
6468        Self {
6469            harness: None,
6470            profile: None,
6471            homes: crate::HarnessHomes::default(),
6472        }
6473    }
6474}
6475
6476#[derive(Debug, Clone, Deserialize)]
6477#[serde(default)]
6478struct TriggersQuery {
6479    harness: Option<String>,
6480    homes: crate::HarnessHomes,
6481}
6482
6483impl Default for TriggersQuery {
6484    fn default() -> Self {
6485        Self {
6486            harness: None,
6487            homes: crate::HarnessHomes::default(),
6488        }
6489    }
6490}
6491
6492fn triggers_call(params: Value) -> std::result::Result<Value, ServiceError> {
6493    let query = decode::<TriggersQuery>(params)?;
6494    let triggers = crate::triggers::list_triggers(&query.homes, query.harness.as_deref())
6495        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6496    Ok(json!({
6497        "schema": crate::triggers::TRIGGERS_SCHEMA,
6498        "triggers": triggers,
6499    }))
6500}
6501
6502fn routes_call(params: Value) -> std::result::Result<Value, ServiceError> {
6503    let query = decode::<RoutesQuery>(params)?;
6504    let routes = crate::routes::list_routes(
6505        &query.homes,
6506        query.harness.as_deref(),
6507        query.profile.as_deref(),
6508    )
6509    .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6510    Ok(json!({
6511        "schema": crate::routes::ROUTES_SCHEMA,
6512        "routes": routes,
6513    }))
6514}
6515
6516fn channels_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6517    let query = decode::<ChannelsQuery>(params)?;
6518    let to_service = |error: crate::channels::ChannelError| match error {
6519        crate::channels::ChannelError::UnsupportedHarness { .. } => {
6520            ServiceError::UnsupportedAction(error.to_string())
6521        }
6522        crate::channels::ChannelError::NotFound { .. } => {
6523            ServiceError::InvalidParams(error.to_string())
6524        }
6525    };
6526    match method {
6527        "harness.v1.channels.list" => {
6528            let channels = crate::channels::list_channels(&query.homes, query.harness.as_deref())
6529                .map_err(to_service)?;
6530            Ok(json!({
6531                "schema": crate::channels::CHANNELS_SCHEMA,
6532                "channels": channels,
6533            }))
6534        }
6535        "harness.v1.channels.status" => {
6536            let harness = query
6537                .harness
6538                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6539            let name = query
6540                .name
6541                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6542            let channel = crate::channels::channel_status(&query.homes, &harness, &name)
6543                .map_err(to_service)?;
6544            Ok(json!({
6545                "schema": crate::channels::CHANNELS_SCHEMA,
6546                "channel": channel,
6547            }))
6548        }
6549        _ => Err(ServiceError::MethodNotFound),
6550    }
6551}
6552
6553fn rpc_error(id: Value, code: i64, message: &str) -> Value {
6554    json!({
6555        "jsonrpc": "2.0",
6556        "id": id,
6557        "error": {"code": code, "message": message},
6558    })
6559}
6560
6561#[cfg(test)]
6562mod tests {
6563    use super::*;
6564    use crate::{HarnessEvent, HarnessId, RuntimeEndpoint, RuntimeHandle, StorageLocator};
6565    use async_trait::async_trait;
6566    use std::io::Write;
6567    use std::path::PathBuf;
6568    use std::time::Instant;
6569
6570    #[test]
6571    fn indexed_claude_descriptor_carries_its_delivery_door() {
6572        let descriptor = SessionDescriptor {
6573            locator: SessionLocator {
6574                harness: HarnessId::new(HarnessId::CLAUDE_CODE),
6575                session_id: "live-session".into(),
6576                storage: StorageLocator::File {
6577                    path: PathBuf::from("/tmp/live-session.jsonl"),
6578                },
6579            },
6580            cwd: Some(PathBuf::from("/project")),
6581            title: None,
6582            preview_candidates: Vec::new(),
6583            latest_message_candidates: Vec::new(),
6584            updated_at_ms: Some(1),
6585            message_count: None,
6586            model: None,
6587            parent_session_id: None,
6588            child_session_count: 0,
6589            nouns: Default::default(),
6590        };
6591        let doors =
6592            crate::mail_route::LiveSessions::with(&[("claude-code", "live-session", "native")]);
6593
6594        let value = live_descriptor_value(&descriptor, &doors).unwrap();
6595        assert_eq!(value["delivery"], "native");
6596        assert!(value.get("live_endpoint").is_none());
6597    }
6598
6599    struct EndingRuntime {
6600        handle: RuntimeHandle,
6601        event: Option<HarnessEvent>,
6602        close_failures: usize,
6603    }
6604
6605    #[async_trait]
6606    impl RuntimeConnection for EndingRuntime {
6607        fn handle(&self) -> &RuntimeHandle {
6608            &self.handle
6609        }
6610
6611        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
6612            unreachable!("ending runtime does not accept input")
6613        }
6614
6615        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
6616            Ok(self.event.take())
6617        }
6618
6619        async fn interrupt(&mut self) -> crate::Result<()> {
6620            Ok(())
6621        }
6622
6623        async fn respond(&mut self, _request_id: Value, _response: Value) -> crate::Result<()> {
6624            Ok(())
6625        }
6626
6627        async fn close(&mut self) -> crate::Result<()> {
6628            if self.close_failures > 0 {
6629                self.close_failures -= 1;
6630                return Err(crate::Error::Other(
6631                    "cleanup temporarily unavailable".into(),
6632                ));
6633            }
6634            Ok(())
6635        }
6636    }
6637
6638    fn ending_runtime(event: Option<HarnessEvent>) -> Box<dyn RuntimeConnection> {
6639        Box::new(EndingRuntime {
6640            handle: RuntimeHandle {
6641                harness: HarnessId::from(HarnessId::CLAUDE_CODE),
6642                runtime_id: "ending-session".into(),
6643                endpoint: RuntimeEndpoint::LocalProcess {
6644                    pid: None,
6645                    command: vec!["ending-runtime".into()],
6646                    protocol: "test".into(),
6647                },
6648            },
6649            event,
6650            close_failures: 0,
6651        })
6652    }
6653
6654    #[tokio::test]
6655    async fn closing_a_runtime_surrenders_the_connection_even_when_teardown_fails() {
6656        let mut service = HarnessSessionService::new();
6657        let handle = ending_runtime(None).handle().clone();
6658        let runtime_id = handle.runtime_id.clone();
6659        let opened = service
6660            .insert_runtime(Box::new(EndingRuntime {
6661                handle,
6662                event: None,
6663                close_failures: 1,
6664            }))
6665            .unwrap();
6666        let connection = opened["connection"].as_str().unwrap().to_string();
6667        service.terminal_launches.insert(
6668            connection.clone(),
6669            StructuredLaunch {
6670                cwd: PathBuf::from("/fixture"),
6671                program: "fixture".into(),
6672                arguments: Vec::new(),
6673                env: BTreeMap::new(),
6674            },
6675        );
6676        let first = service
6677            .handle_async(request(
6678                1,
6679                "harness.v1.runtimes.close",
6680                json!({"connection": connection}),
6681            ))
6682            .await;
6683        // The harness's own teardown failed and the caller is told so...
6684        assert!(first.get("error").is_some(), "{first}");
6685        // ...but the connection is gone all the same. A connection whose close
6686        // cannot complete is exactly the one that must not stay registered:
6687        // holding it would answer every later call on this node with a turn
6688        // that is never going to end.
6689        assert!(!service.runtimes.contains_key(&connection));
6690        assert!(!service.terminal_launches.contains_key(&connection));
6691        assert!(!service.runtime_sequences.contains_key(&runtime_id));
6692        let again = service
6693            .handle_async(request(
6694                2,
6695                "harness.v1.runtimes.close",
6696                json!({"connection": connection}),
6697            ))
6698            .await;
6699        assert_eq!(again["error"]["code"], -32602, "{again}");
6700    }
6701
6702    fn request(id: u64, method: &str, params: Value) -> Value {
6703        json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
6704    }
6705
6706    // ---- ORCH-6: conversation nouns on `sessions.*` ----------------------
6707
6708    fn hermes_store() -> PathBuf {
6709        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hermes_home/state.db")
6710    }
6711
6712    /// The discovery response for the Hermes fixture home, with the one
6713    /// machine-specific value (the absolute store path) replaced so the exact
6714    /// same JSON can be committed and replayed by the UI story.
6715    fn hermes_discovery(params: Value) -> Value {
6716        let mut response =
6717            HarnessSessionService::new().handle(request(1, "harness.v1.sessions.discover", params));
6718        let store = hermes_store().display().to_string();
6719        for session in response["result"]["sessions"]
6720            .as_array_mut()
6721            .expect("sessions array")
6722        {
6723            if session["locator"]["storage"]["path"] == json!(store) {
6724                session["locator"]["storage"]["path"] = json!("<fixtures>/hermes_home/state.db");
6725            }
6726            // `activity` reports a wall-clock observation instant, not a fact
6727            // about the session; it would make this response differ on every
6728            // call. The nouns under test are all session facts.
6729            session.as_object_mut().unwrap().remove("activity");
6730        }
6731        response["result"].take()
6732    }
6733
6734    fn hermes_query() -> Value {
6735        json!({
6736            "harnesses": ["hermes"],
6737            "homes": {"hermes": hermes_store()},
6738        })
6739    }
6740
6741    fn row<'a>(result: &'a Value, id: &str) -> &'a Value {
6742        result["sessions"]
6743            .as_array()
6744            .expect("sessions array")
6745            .iter()
6746            .find(|session| session["locator"]["session_id"] == json!(id))
6747            .unwrap_or_else(|| panic!("no discovered row for `{id}` in {result:#}"))
6748    }
6749
6750    #[test]
6751    fn orch6_discover_rows_carry_the_conversation_nouns() {
6752        let result = hermes_discovery(hermes_query());
6753
6754        // A Telegram DM: reached on a channel, no repo — the workspace IS the
6755        // channel (D2 precedence), and `main` is not a profile.
6756        let dm = row(&result, "tg-dm-1");
6757        assert_eq!(dm["trigger"], json!("channel"));
6758        assert_eq!(dm["surface"]["platform"], json!("telegram"));
6759        assert_eq!(dm["surface"]["kind"], json!("dm"));
6760        assert_eq!(dm["surface"]["chat_id"], json!("123456"));
6761        assert_eq!(dm["surface"]["participant_id"], json!("u1"));
6762        assert_eq!(
6763            dm["workspace"],
6764            json!({"kind": "channel", "value": "telegram:123456"})
6765        );
6766        assert!(dm.get("profile").is_none(), "{dm:#}");
6767
6768        // A cron fire: recurring, with the job recovered from the minted id.
6769        let fire = row(&result, "cron_job42_20260902_120000");
6770        assert_eq!(fire["trigger"], json!("cron"));
6771        assert_eq!(
6772            fire["recurrence"],
6773            json!({"job_id": "job42", "kind": "cron"})
6774        );
6775        assert_eq!(fire["workspace"]["kind"], json!("repo"));
6776
6777        // A profiled group session with a pending handoff: repo workspace
6778        // wins over the channel, and the chat stays on the surface key.
6779        let coder = row(&result, "tg-coder-1");
6780        assert_eq!(coder["trigger"], json!("channel"));
6781        assert_eq!(coder["profile"], json!("coder"));
6782        assert_eq!(coder["surface"]["thread_id"], json!("55"));
6783        assert_eq!(
6784            coder["surface"]["key"],
6785            json!("agent:coder:telegram:group:-100777:55")
6786        );
6787        assert_eq!(
6788            coder["workspace"],
6789            json!({"kind": "repo", "value": "/workspace/project"})
6790        );
6791        assert_eq!(
6792            coder["cross_surface"],
6793            json!({"state": "pending", "platform": "discord"})
6794        );
6795
6796        // A plain ACP session stays human-triggered with no surface at all.
6797        let acp = row(&result, "cef97234-e8e8-428a-99ab-e8fff4e7e613");
6798        assert_eq!(acp["trigger"], json!("human"));
6799        assert!(acp.get("surface").is_none(), "{acp:#}");
6800        assert_eq!(acp["workspace"], json!({"kind": "none"}));
6801    }
6802
6803    #[test]
6804    fn orch6_discover_filters_by_harness_and_profile() {
6805        let mut params = hermes_query();
6806        params["profile"] = json!("coder");
6807        let result = hermes_discovery(params);
6808        let ids: Vec<&str> = result["sessions"]
6809            .as_array()
6810            .expect("sessions array")
6811            .iter()
6812            .map(|session| session["locator"]["session_id"].as_str().unwrap())
6813            .collect();
6814        assert_eq!(ids, vec!["tg-coder-1"]);
6815
6816        // A profile no session is routed through returns nothing rather than
6817        // silently ignoring the filter.
6818        let mut missing = hermes_query();
6819        missing["profile"] = json!("nobody");
6820        assert_eq!(hermes_discovery(missing)["sessions"], json!([]));
6821
6822        // The harness filter is `harnesses`; an id no harness answers to is
6823        // an empty page, never every store on the box.
6824        let elsewhere = json!({"harnesses": ["codex"], "homes": {"codex": hermes_store()}});
6825        assert_eq!(hermes_discovery(elsewhere)["sessions"], json!([]));
6826    }
6827
6828    #[test]
6829    fn orch6_load_reports_the_same_nouns_as_discovery() {
6830        let mut service = HarnessSessionService::new();
6831        let loaded = service.handle(request(
6832            1,
6833            "harness.v1.sessions.load",
6834            json!({"locator": {
6835                "harness": "hermes",
6836                "session_id": "tg-coder-1",
6837                "storage": {"kind": "file", "path": hermes_store()},
6838            }}),
6839        ));
6840        let session = &loaded["result"]["session"];
6841        let discovered = hermes_discovery(hermes_query());
6842        let row = row(&discovered, "tg-coder-1");
6843        for noun in [
6844            "trigger",
6845            "surface",
6846            "profile",
6847            "recurrence",
6848            "cross_surface",
6849            "workspace",
6850        ] {
6851            assert_eq!(
6852                session[noun],
6853                row.get(noun).cloned().unwrap_or(Value::Null),
6854                "`{noun}` disagrees between sessions.load and sessions.discover"
6855            );
6856        }
6857    }
6858
6859    /// ORCH-10: the fixture homes, as the RPC's `homes` override. Hermes's
6860    /// home is named by its `state.db`; OpenClaw's is the state directory.
6861    fn profile_fixture_homes() -> Value {
6862        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6863        json!({
6864            "hermes": fixtures.join("hermes_home/state.db"),
6865            "openclaw": fixtures.join("openclaw_home"),
6866        })
6867    }
6868
6869    fn profile_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
6870        response["result"]["profiles"]
6871            .as_array()
6872            .unwrap_or_else(|| panic!("no profiles array in {response}"))
6873            .iter()
6874            .find(|row| row["harness"] == harness && row["name"] == name)
6875            .unwrap_or_else(|| panic!("no `{harness}` profile `{name}` in {response}"))
6876    }
6877
6878    /// dev/01: every source answers in one row shape, over the committed
6879    /// fixture homes — the Hermes profile directory and its `state.db`
6880    /// partition, the OpenClaw agent directories and `openclaw.json`, and
6881    /// supercode's own presets.
6882    #[test]
6883    fn profiles_list_reads_every_source_uniformly() {
6884        let mut service = HarnessSessionService::new();
6885        let response = service.handle(request(
6886            1,
6887            "harness.v1.profiles.list",
6888            json!({"homes": profile_fixture_homes()}),
6889        ));
6890        assert_eq!(
6891            response["result"]["schema"],
6892            crate::profiles::PROFILES_SCHEMA
6893        );
6894
6895        let default = profile_row(&response, "hermes", "default");
6896        assert_eq!(default["kind"], "hermes_profile");
6897        assert_eq!(default["default"], true);
6898        assert_eq!(default["routes"], 0);
6899        assert_eq!(default["sessions"], 11);
6900        assert_eq!(default["model"], "anthropic/claude-sonnet-4-5");
6901
6902        let coder = profile_row(&response, "hermes", "coder");
6903        assert_eq!(coder["kind"], "hermes_profile");
6904        assert_eq!(coder["default"], false);
6905        assert_eq!(coder["routes"], 1, "gateway.profile_routes targets coder");
6906        assert_eq!(coder["sessions"], 1, "state.db profile_name = 'coder'");
6907        assert_eq!(coder["model"], "anthropic/claude-opus-4-8");
6908        assert!(coder["home"]
6909            .as_str()
6910            .unwrap()
6911            .ends_with("hermes_home/profiles/coder"));
6912
6913        let main = profile_row(&response, "openclaw", "main");
6914        assert_eq!(main["kind"], "openclaw_agent");
6915        // No entry declares `default: true` (real configs do not), so `main`
6916        // wins on OpenClaw's own convention rather than alphabetically.
6917        assert_eq!(main["default"], true);
6918        assert_eq!(main["routes"], 0);
6919        assert_eq!(main["sessions"], 4);
6920        assert_eq!(
6921            main["model"],
6922            Value::Null,
6923            "`agents.defaults.model` is an install default, not this agent's pin"
6924        );
6925
6926        let design = profile_row(&response, "openclaw", "design");
6927        assert_eq!(design["default"], false);
6928        assert_eq!(design["routes"], 1, "one binding names agentId `design`");
6929        assert_eq!(design["sessions"], 0);
6930        assert_eq!(design["model"], "anthropic/claude-opus-4-8");
6931
6932        let preset = profile_row(&response, "supercode", "supercode-default");
6933        assert_eq!(preset["kind"], "preset");
6934        assert_eq!(preset["default"], true);
6935        assert_eq!(preset["home"], Value::Null);
6936        assert_eq!(preset["routes"], Value::Null);
6937    }
6938
6939    /// Codex's own profiles are `[profiles.<name>]` tables, with the
6940    /// top-level `profile` key naming the default.
6941    #[test]
6942    fn profiles_list_reads_codex_profile_tables() {
6943        let codex_home = std::env::temp_dir().join(format!(
6944            "supercode-orch10-codex-{}-{}",
6945            std::process::id(),
6946            std::time::SystemTime::now()
6947                .duration_since(std::time::UNIX_EPOCH)
6948                .unwrap()
6949                .as_nanos()
6950        ));
6951        std::fs::create_dir_all(codex_home.join("sessions")).unwrap();
6952        std::fs::write(
6953            codex_home.join("config.toml"),
6954            "profile = \"review\"\n\n[profiles.review]\nmodel = \"gpt-5.1-codex\"\n\n[profiles.fast]\nmodel = \"gpt-5.1-codex-mini\"\n",
6955        )
6956        .unwrap();
6957
6958        let mut service = HarnessSessionService::new();
6959        let response = service.handle(request(
6960            1,
6961            "harness.v1.profiles.list",
6962            json!({"harness": "codex", "homes": {"codex": codex_home.join("sessions")}}),
6963        ));
6964        let rows = response["result"]["profiles"].as_array().unwrap();
6965        assert_eq!(rows.len(), 2, "{response}");
6966        let review = profile_row(&response, "codex", "review");
6967        assert_eq!(review["kind"], "codex_profile");
6968        assert_eq!(review["default"], true);
6969        assert_eq!(review["model"], "gpt-5.1-codex");
6970        assert_eq!(review["home"], Value::Null);
6971        assert_eq!(profile_row(&response, "codex", "fast")["default"], false);
6972
6973        let got = service.handle(request(
6974            2,
6975            "harness.v1.profiles.get",
6976            json!({
6977                "harness": "codex",
6978                "name": "fast",
6979                "homes": {"codex": codex_home.join("sessions")},
6980            }),
6981        ));
6982        assert_eq!(got["result"]["profile"]["model"], "gpt-5.1-codex-mini");
6983        std::fs::remove_dir_all(&codex_home).ok();
6984    }
6985
6986    /// A verb a harness lacks fails with `UnsupportedAction`, never a silent
6987    /// empty list; an unknown name is an invalid argument, not an empty row.
6988    #[test]
6989    fn profiles_refuse_harnesses_without_the_concept() {
6990        let mut service = HarnessSessionService::new();
6991        let response = service.handle(request(
6992            1,
6993            "harness.v1.profiles.list",
6994            json!({"harness": "claude-code"}),
6995        ));
6996        assert_eq!(response["error"]["code"], -32020, "{response}");
6997
6998        let missing = service.handle(request(
6999            2,
7000            "harness.v1.profiles.get",
7001            json!({
7002                "harness": "hermes",
7003                "name": "no-such-profile",
7004                "homes": profile_fixture_homes(),
7005            }),
7006        ));
7007        assert_eq!(missing["error"]["code"], -32602, "{missing}");
7008    }
7009
7010    /// The two methods are advertised, so a client discovers them from
7011    /// `harness.v1.capabilities` rather than from documentation.
7012    #[test]
7013    fn profiles_methods_are_advertised() {
7014        let mut service = HarnessSessionService::new();
7015        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
7016        let methods = response["result"]["methods"].as_array().unwrap();
7017        for method in ["harness.v1.profiles.list", "harness.v1.profiles.get"] {
7018            assert!(
7019                methods.iter().any(|entry| entry == method),
7020                "{method} is not advertised"
7021            );
7022        }
7023    }
7024
7025    // -----------------------------------------------------------------
7026    // ORCH-14 — channels
7027    // -----------------------------------------------------------------
7028
7029    fn channel_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
7030        response["result"]["channels"]
7031            .as_array()
7032            .unwrap_or_else(|| panic!("no channels array in {response}"))
7033            .iter()
7034            .find(|row| row["harness"] == harness && row["name"] == name)
7035            .unwrap_or_else(|| panic!("no `{harness}` channel `{name}` in {response}"))
7036    }
7037
7038    fn channels_list(harness: Option<&str>) -> Value {
7039        let mut params = json!({"homes": profile_fixture_homes()});
7040        if let Some(harness) = harness {
7041            params["harness"] = json!(harness);
7042        }
7043        HarnessSessionService::new().handle(request(1, "harness.v1.channels.list", params))
7044    }
7045
7046    /// dev/01: both sources answer in one row shape over the committed
7047    /// fixture homes — Hermes's `platforms:` blocks with their `extra` maps,
7048    /// and OpenClaw's `channels.<name>` entries split per account.
7049    #[test]
7050    fn channels_list_reads_both_gateway_harnesses_uniformly() {
7051        let response = channels_list(None);
7052        assert_eq!(
7053            response["result"]["schema"],
7054            crate::channels::CHANNELS_SCHEMA
7055        );
7056
7057        // Hermes: a credentialed platform, a bridged `extra.key` platform,
7058        // and one the config explicitly disables.
7059        let telegram = channel_row(&response, "hermes", "telegram");
7060        assert_eq!(telegram["kind"], "telegram");
7061        assert_eq!(telegram["enabled"], true);
7062        assert_eq!(telegram["configured"], true);
7063        // The `sessions` count is the discovery rows whose surface platform
7064        // is telegram: the fixture's `agent:main:telegram:…` DM and the
7065        // `agent:coder:telegram:…` group.
7066        assert_eq!(telegram["sessions"], 2);
7067        let api = channel_row(&response, "hermes", "api_server");
7068        assert_eq!(api["configured"], true, "extra.key is a credential key");
7069        assert_eq!(api["sessions"], 0);
7070        let webhook = channel_row(&response, "hermes", "webhook");
7071        assert_eq!(webhook["enabled"], false);
7072        // Hermes lists no credential for `webhook`: declaring it is all it
7073        // needs, so a credential-less entry is still `configured`.
7074        assert_eq!(webhook["configured"], true);
7075
7076        // OpenClaw: one row per account, named `<channel>/<accountId>`.
7077        let linked = channel_row(&response, "openclaw", "slack/T0FIXTURE");
7078        assert_eq!(linked["kind"], "slack");
7079        assert_eq!(linked["account"], "T0FIXTURE");
7080        assert_eq!(linked["enabled"], true);
7081        assert_eq!(linked["configured"], true);
7082        let unlinked = channel_row(&response, "openclaw", "slack/T1FIXTURE");
7083        assert_eq!(unlinked["enabled"], false);
7084        assert_eq!(
7085            unlinked["configured"], false,
7086            "an account with no credential key is not configured"
7087        );
7088        // A single-account channel keeps its own name and names its account
7089        // inline.
7090        let telegram = channel_row(&response, "openclaw", "telegram");
7091        assert_eq!(telegram["account"], "hermes-fixture-bot");
7092        assert_eq!(telegram["configured"], true);
7093
7094        // `status` is never claimed from a config file.
7095        for row in response["result"]["channels"].as_array().unwrap() {
7096            assert_eq!(row["status"], "unknown", "{row}");
7097        }
7098    }
7099
7100    /// dev/01: no field of any emitted row carries a credential. The fixture
7101    /// homes hold four FAKE credential strings; a row that leaked one — as a
7102    /// value, an account label, or a name — fails here.
7103    #[test]
7104    fn channels_rows_never_carry_a_fixture_secret() {
7105        let secrets = [
7106            "FAKE-TOKEN-DO-NOT-EMIT",
7107            "FAKE-API-SERVER-KEY-DO-NOT-EMIT",
7108            "FAKE-SLACK-BOT-TOKEN-DO-NOT-EMIT",
7109            "FAKE-SLACK-APP-TOKEN-DO-NOT-EMIT",
7110            "FAKE-TELEGRAM-TOKEN-DO-NOT-EMIT",
7111        ];
7112        // The strings really are in the fixtures, so this test can fail.
7113        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7114        let raw = format!(
7115            "{}{}",
7116            std::fs::read_to_string(fixtures.join("hermes_home/config.yaml")).unwrap(),
7117            std::fs::read_to_string(fixtures.join("openclaw_home/openclaw.json")).unwrap(),
7118        );
7119        for secret in secrets {
7120            assert!(raw.contains(secret), "fixture no longer holds `{secret}`");
7121        }
7122
7123        let emitted = serde_json::to_string(&channels_list(None)["result"]).unwrap();
7124        for secret in secrets {
7125            assert!(
7126                !emitted.contains(secret),
7127                "`{secret}` leaked into a channel row: {emitted}"
7128            );
7129        }
7130        // Belt and braces: no row FIELD is credential-shaped either, so a
7131        // future field cannot smuggle one past the literal scan.
7132        for row in channels_list(None)["result"]["channels"]
7133            .as_array()
7134            .unwrap()
7135        {
7136            for key in row.as_object().unwrap().keys() {
7137                let key = key.to_ascii_lowercase();
7138                assert!(
7139                    !["token", "key", "secret", "password", "credential"]
7140                        .iter()
7141                        .any(|marker| key.ends_with(marker)),
7142                    "`{key}` is a credential-shaped field on a channel row"
7143                );
7144            }
7145        }
7146    }
7147
7148    /// `status` answers one row by name, and refuses an unknown one.
7149    #[test]
7150    fn channels_status_reads_one_row_by_name() {
7151        let mut service = HarnessSessionService::new();
7152        let got = service.handle(request(
7153            1,
7154            "harness.v1.channels.status",
7155            json!({
7156                "harness": "openclaw",
7157                "name": "slack/T0FIXTURE",
7158                "homes": profile_fixture_homes(),
7159            }),
7160        ));
7161        assert_eq!(got["result"]["channel"]["kind"], "slack");
7162        assert_eq!(got["result"]["channel"]["account"], "T0FIXTURE");
7163        assert_eq!(got["result"]["channel"]["status"], "unknown");
7164
7165        let missing = service.handle(request(
7166            2,
7167            "harness.v1.channels.status",
7168            json!({
7169                "harness": "openclaw",
7170                "name": "no-such-channel",
7171                "homes": profile_fixture_homes(),
7172            }),
7173        ));
7174        assert_eq!(missing["error"]["code"], -32602, "{missing}");
7175    }
7176
7177    /// A harness with no channel concept fails with `UnsupportedAction`,
7178    /// never a silent empty list — Claude Code included, because its channels
7179    /// are MCP-protocol declarations no config file names.
7180    #[test]
7181    fn channels_refuse_harnesses_without_the_concept() {
7182        let response = channels_list(Some("claude-code"));
7183        assert_eq!(response["error"]["code"], -32020, "{response}");
7184        let codex = channels_list(Some("codex"));
7185        assert_eq!(codex["error"]["code"], -32020, "{codex}");
7186    }
7187
7188    /// The harness filter restricts the rows rather than being ignored.
7189    #[test]
7190    fn channels_list_filters_by_harness() {
7191        let response = channels_list(Some("openclaw"));
7192        let rows = response["result"]["channels"].as_array().unwrap();
7193        assert!(!rows.is_empty(), "{response}");
7194        assert!(
7195            rows.iter().all(|row| row["harness"] == "openclaw"),
7196            "harness filter leaked: {response}"
7197        );
7198    }
7199
7200    /// Both methods are advertised, so a client discovers them from
7201    /// `harness.v1.capabilities` rather than from documentation.
7202    #[test]
7203    fn channels_methods_are_advertised() {
7204        let mut service = HarnessSessionService::new();
7205        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
7206        let methods = response["result"]["methods"].as_array().unwrap();
7207        for method in ["harness.v1.channels.list", "harness.v1.channels.status"] {
7208            assert!(
7209                methods.iter().any(|entry| entry == method),
7210                "{method} is not advertised"
7211            );
7212        }
7213    }
7214
7215    /// The UI story renders REAL rows: this writes the discovery response the
7216    /// two assertions above pin into the fixture the Storybook
7217    /// `Compositions/Universal nouns` stories import, and fails when the
7218    /// committed copy has drifted from what the service now answers.
7219    #[test]
7220    fn orch6_story_fixture_matches_the_live_discovery_response() {
7221        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7222            .join("../../sdk/ui/stories/fixtures/hermes-discovery.json");
7223        let mut result = hermes_discovery(hermes_query());
7224        // `updated_at_ms` is derived from the fixture's own stored timestamps,
7225        // so the whole response is deterministic; drop only the cursor, which
7226        // is pagination state rather than a session fact.
7227        result.as_object_mut().unwrap().remove("next_cursor");
7228        let rendered = format!("{}\n", serde_json::to_string_pretty(&result).unwrap());
7229        if std::env::var_os("SUPERCODE_UPDATE_FIXTURES").is_some() {
7230            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
7231            std::fs::write(&path, &rendered).unwrap();
7232        }
7233        let committed = std::fs::read_to_string(&path).unwrap_or_default();
7234        assert_eq!(
7235            committed, rendered,
7236            "sdk/ui/stories/fixtures/hermes-discovery.json is stale — \
7237             re-run with SUPERCODE_UPDATE_FIXTURES=1"
7238        );
7239    }
7240
7241    fn pi_locator() -> SessionLocator {
7242        SessionLocator {
7243            harness: HarnessId::from(HarnessId::PI),
7244            session_id: "1e6f2a3b-0000-4000-8000-000000000001".into(),
7245            storage: StorageLocator::File {
7246                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7247                    .join("tests/fixtures/pi_session.jsonl"),
7248            },
7249        }
7250    }
7251
7252    fn opencode_locator() -> SessionLocator {
7253        let session_id = "ses_fixtureAAAAAAAAAAAAAAA1";
7254        SessionLocator {
7255            harness: HarnessId::from(HarnessId::OPENCODE),
7256            session_id: session_id.into(),
7257            storage: StorageLocator::Sqlite {
7258                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7259                    .join("tests/fixtures/opencode_fixture/opencode.db"),
7260                selector: session_id.into(),
7261            },
7262        }
7263    }
7264
7265    fn grok_locator() -> SessionLocator {
7266        SessionLocator {
7267            harness: HarnessId::from(HarnessId::GROK),
7268            session_id: "73c09283-4b33-41fa-90f1-0bcb0f7be523".into(),
7269            storage: StorageLocator::File {
7270                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7271                    .join("tests/fixtures/grok_session/chat_history.jsonl"),
7272            },
7273        }
7274    }
7275
7276    // ---- ORCH-11: `harness.v1.skills.list` -------------------------------
7277
7278    fn fixture_homes() -> Value {
7279        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7280        json!({
7281            "claude_code": fixtures.join("__absent__"),
7282            "codex": fixtures.join("__absent__"),
7283            "opencode": fixtures.join("__absent__"),
7284            "pi": fixtures.join("__absent__"),
7285            "agents": fixtures.join("__absent__"),
7286            "hermes": fixtures.join("hermes_home"),
7287            "openclaw": fixtures.join("openclaw_home"),
7288        })
7289    }
7290
7291    #[test]
7292    fn preview_search_uses_the_discovery_rpc_and_refuses_live_subscription() {
7293        let root = std::env::temp_dir().join(format!(
7294            "supercode-preview-rpc-{}-{}",
7295            std::process::id(),
7296            std::time::SystemTime::now()
7297                .duration_since(std::time::UNIX_EPOCH)
7298                .unwrap()
7299                .as_nanos()
7300        ));
7301        std::fs::create_dir_all(&root).unwrap();
7302        for id in ["first", "second"] {
7303            std::fs::write(root.join(format!("{id}.jsonl")), format!("{}\n{}\n",
7304                json!({"type": "session_meta", "payload": {"id": id, "cwd": "/workspace"}}),
7305                json!({"type": "event_msg", "payload": {"type": "agent_message", "message": "NEBULA result"}}),
7306            )).unwrap();
7307        }
7308        let mut service = HarnessSessionService::new();
7309        let query = json!({
7310            "harnesses": ["codex"], "homes": {"codex": root},
7311            "query": "nebula", "search_previews": true, "limit": 1
7312        });
7313        let first = service.handle(request(1, "harness.v1.sessions.discover", query.clone()));
7314        assert!(first.get("error").is_none(), "{first}");
7315        assert_eq!(first["result"]["receipt"]["searched_previews"], true);
7316        assert_eq!(first["result"]["receipt"]["total_matched"], 2);
7317        let mut next_query = query.clone();
7318        next_query["cursor"] = first["result"]["next_cursor"].clone();
7319        let next = service.handle(request(2, "harness.v1.sessions.discover", next_query));
7320        assert_eq!(next["result"]["receipt"]["returned"], 1);
7321        assert_eq!(next["result"]["receipt"]["total_matched"], 2);
7322        assert_eq!(next["result"]["receipt"]["truncated"], false);
7323        assert_ne!(
7324            first["result"]["sessions"][0]["locator"],
7325            next["result"]["sessions"][0]["locator"]
7326        );
7327        let refused = service.handle(request(3, "harness.v1.sessions.index.subscribe", query));
7328        assert!(
7329            refused["error"]["message"]
7330                .as_str()
7331                .unwrap()
7332                .contains("use sessions.discover"),
7333            "{refused}"
7334        );
7335        std::fs::remove_dir_all(root).unwrap();
7336    }
7337
7338    #[test]
7339    fn session_index_resize_preserves_subscription_and_rejects_invalid_requests() {
7340        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.sessions.index.resize"));
7341        let root = std::env::temp_dir().join(format!(
7342            "supercode-index-rpc-{}-{}",
7343            std::process::id(),
7344            std::time::SystemTime::now()
7345                .duration_since(std::time::UNIX_EPOCH)
7346                .unwrap()
7347                .as_nanos()
7348        ));
7349        std::fs::create_dir_all(&root).unwrap();
7350        for id in ["first", "second"] {
7351            std::fs::write(root.join(format!("{id}.jsonl")), format!(
7352                "{{\"type\":\"session_meta\",\"payload\":{{\"id\":\"{id}\",\"cwd\":\"/workspace\"}}}}\n"
7353            )).unwrap();
7354        }
7355        let mut service = HarnessSessionService::new();
7356        let opened = service.handle(request(
7357            1,
7358            "harness.v1.sessions.index.subscribe",
7359            json!({
7360                "harnesses": ["codex"], "homes": { "codex": root }, "limit": 1
7361            }),
7362        ));
7363        assert!(opened.get("error").is_none(), "{opened:#}");
7364        let subscription = opened["result"]["subscription"]
7365            .as_str()
7366            .unwrap()
7367            .to_owned();
7368        assert_eq!(opened["result"]["initial"].as_array().unwrap().len(), 1);
7369        for params in [
7370            json!({"subscription": subscription, "limit": 0}),
7371            json!({"subscription": subscription, "limit": 2049}),
7372            json!({"subscription": subscription, "limit": 2, "cursor": "not-allowed"}),
7373            json!({"subscription": "unknown", "limit": 2}),
7374        ] {
7375            let rejected = service.handle(request(2, "harness.v1.sessions.index.resize", params));
7376            assert_eq!(rejected["error"]["code"], -32602, "{rejected:#}");
7377        }
7378        for (limit, revision) in [(1, 1), (2, 2), (2, 2), (1, 3)] {
7379            let response = service.handle(request(
7380                3,
7381                "harness.v1.sessions.index.resize",
7382                json!({
7383                    "subscription": subscription, "limit": limit
7384                }),
7385            ));
7386            assert!(response.get("error").is_none(), "{response:#}");
7387            assert_eq!(response["result"]["subscription"], subscription);
7388            assert_eq!(response["result"]["revision"], revision);
7389            assert_eq!(
7390                response["result"]["initial"].as_array().unwrap().len(),
7391                limit
7392            );
7393            assert_eq!(response["result"]["receipt"]["total_matched"], 2);
7394            assert_eq!(service.index_subscriptions.len(), 1);
7395        }
7396        let removed = service.handle(request(
7397            4,
7398            "harness.v1.sessions.index.unsubscribe",
7399            json!({
7400                "subscription": subscription
7401            }),
7402        ));
7403        assert_eq!(removed["result"]["removed"], true);
7404        let stale = service.handle(request(
7405            5,
7406            "harness.v1.sessions.index.resize",
7407            json!({
7408                "subscription": subscription, "limit": 1
7409            }),
7410        ));
7411        assert_eq!(stale["error"]["code"], -32602);
7412        drop(service);
7413        std::fs::remove_dir_all(root).unwrap();
7414    }
7415
7416    fn skills_rows(params: Value) -> Vec<Value> {
7417        let response =
7418            HarnessSessionService::new().handle(request(1, "harness.v1.skills.list", params));
7419        assert!(response.get("error").is_none(), "{response:#}");
7420        response["result"].as_array().cloned().unwrap_or_default()
7421    }
7422
7423    /// The uniform row over two harnesses at once, from the harnesses' own
7424    /// skill roots: name, harness, scope, location, description, version.
7425    #[test]
7426    fn skills_list_reads_the_hermes_and_openclaw_roots() {
7427        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7428        let rows = skills_rows(json!({
7429            "homes": fixture_homes(),
7430            "cwd": fixtures.join("hermes_home"),
7431        }));
7432        let arxiv = rows
7433            .iter()
7434            .find(|row| row["name"] == json!("arxiv-search"))
7435            .unwrap_or_else(|| panic!("no arxiv row in {rows:#?}"));
7436        assert_eq!(arxiv["harness"], json!(HarnessId::HERMES));
7437        assert_eq!(arxiv["scope"], json!("user"));
7438        assert_eq!(arxiv["version"], json!("1.4.0"));
7439        assert!(arxiv["location"]
7440            .as_str()
7441            .unwrap()
7442            .ends_with("hermes_home/skills/research/arxiv"));
7443
7444        // A directory with no SKILL.md still lists, by directory name.
7445        let bare = rows
7446            .iter()
7447            .find(|row| row["name"] == json!("bare-skill"))
7448            .unwrap_or_else(|| panic!("no bare-skill row in {rows:#?}"));
7449        assert_eq!(bare["enabled"], json!(null));
7450        assert!(bare.get("description").is_none());
7451
7452        let demo = rows
7453            .iter()
7454            .find(|row| row["name"] == json!("clawhub-demo"))
7455            .unwrap_or_else(|| panic!("no clawhub-demo row in {rows:#?}"));
7456        assert_eq!(demo["harness"], json!(HarnessId::OPENCLAW));
7457        assert_eq!(demo["scope"], json!("managed"));
7458        assert_eq!(demo["enabled"], json!(false));
7459    }
7460
7461    /// Both filters select against the same rows.
7462    #[test]
7463    fn skills_list_filters_by_harness_and_scope() {
7464        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7465        let hermes = skills_rows(json!({
7466            "homes": fixture_homes(),
7467            "cwd": fixtures.join("hermes_home"),
7468            "harness": HarnessId::HERMES,
7469        }));
7470        assert!(!hermes.is_empty());
7471        assert!(hermes
7472            .iter()
7473            .all(|row| row["harness"] == json!(HarnessId::HERMES)));
7474
7475        let managed = skills_rows(json!({
7476            "homes": fixture_homes(),
7477            "cwd": fixtures.join("openclaw_home"),
7478            "harness": HarnessId::OPENCLAW,
7479            "scope": "managed",
7480        }));
7481        assert_eq!(managed.len(), 1, "{managed:#?}");
7482        assert_eq!(managed[0]["name"], json!("clawhub-demo"));
7483
7484        let bundled = skills_rows(json!({
7485            "homes": fixture_homes(),
7486            "cwd": fixtures.join("openclaw_home"),
7487            "harness": HarnessId::OPENCLAW,
7488            "scope": "bundled",
7489        }));
7490        assert!(bundled.is_empty(), "{bundled:#?}");
7491    }
7492
7493    /// A harness supercode has no skills root for is refused by name, not
7494    /// answered with an empty list.
7495    #[test]
7496    fn skills_list_refuses_an_unknown_harness() {
7497        let response = HarnessSessionService::new().handle(request(
7498            1,
7499            "harness.v1.skills.list",
7500            json!({"harness": "not-a-harness", "homes": fixture_homes()}),
7501        ));
7502        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7503        assert!(response["error"]["message"]
7504            .as_str()
7505            .unwrap()
7506            .contains("not-a-harness"));
7507    }
7508
7509    /// The method is advertised, and its SDK operation resolves it.
7510    #[test]
7511    fn skills_list_is_an_advertised_method_and_sdk_operation() {
7512        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.list"));
7513        assert_eq!(
7514            SdkOperation::from_method("harness.v1.skills.list"),
7515            Some(SdkOperation::SkillsList)
7516        );
7517    }
7518
7519    // ---- ORCH-22: `harness.v1.skills.install|remove` ----------------------
7520
7521    /// Both controlled verbs are advertised and resolve to their operation.
7522    #[test]
7523    fn skills_install_and_remove_are_advertised_methods_and_sdk_operations() {
7524        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.install"));
7525        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.remove"));
7526        assert_eq!(
7527            SdkOperation::from_method("harness.v1.skills.install"),
7528            Some(SdkOperation::SkillsInstall)
7529        );
7530        assert_eq!(
7531            SdkOperation::from_method("harness.v1.skills.remove"),
7532            Some(SdkOperation::SkillsRemove)
7533        );
7534    }
7535
7536    /// The directory door, end to end over the RPC: a local package lands in
7537    /// Claude Code's own user root and the outcome carries the operation and
7538    /// the row the ORCH-11 loader reads back.
7539    #[test]
7540    fn skills_install_and_remove_drive_the_directory_door() {
7541        let root = std::env::temp_dir().join(format!(
7542            "supercode-orch22-rpc-{}-{}",
7543            std::process::id(),
7544            std::time::SystemTime::now()
7545                .duration_since(std::time::UNIX_EPOCH)
7546                .unwrap()
7547                .as_nanos()
7548        ));
7549        let source = root.join("probe-src");
7550        std::fs::create_dir_all(&source).unwrap();
7551        std::fs::write(
7552            source.join("SKILL.md"),
7553            "---\nname: orch22-rpc\ndescription: a probe\n---\nbody\n",
7554        )
7555        .unwrap();
7556        let homes = json!({
7557            "claude_code": root.join("claude_home"),
7558            "codex": root.join("__absent__"),
7559            "opencode": root.join("__absent__"),
7560            "pi": root.join("__absent__"),
7561            "hermes": root.join("__absent__"),
7562            "openclaw": root.join("__absent__"),
7563            "agents": root.join("__absent__"),
7564        });
7565
7566        let mut service = HarnessSessionService::new();
7567        let installed = service.handle(request(
7568            1,
7569            "harness.v1.skills.install",
7570            json!({
7571                "harness": HarnessId::CLAUDE_CODE,
7572                "source": source,
7573                "scope": "user",
7574                "cwd": root,
7575                "homes": homes,
7576            }),
7577        ));
7578        let result = &installed["result"];
7579        assert_eq!(result["name"], json!("orch22-rpc"), "{installed:#}");
7580        assert_eq!(result["verb"], json!("install"));
7581        assert!(result["ran"]
7582            .as_str()
7583            .is_some_and(|ran| ran.starts_with("cp -R ")));
7584        assert_eq!(result["skill"]["scope"], json!("user"));
7585
7586        let removed = service.handle(request(
7587            2,
7588            "harness.v1.skills.remove",
7589            json!({
7590                "harness": HarnessId::CLAUDE_CODE,
7591                "name": "orch22-rpc",
7592                "scope": "user",
7593                "cwd": root,
7594                "homes": homes,
7595            }),
7596        ));
7597        assert_eq!(removed["result"]["removed"], json!(true), "{removed:#}");
7598        assert!(!root.join("claude_home/skills/orch22-rpc").exists());
7599        std::fs::remove_dir_all(&root).ok();
7600    }
7601
7602    /// OpenClaw publishes no `skills remove` at the pin, so the uniform verb
7603    /// refuses with UnsupportedAction instead of deleting files itself.
7604    #[test]
7605    fn skills_remove_refuses_openclaw_at_the_pin() {
7606        let response = HarnessSessionService::new().handle(request(
7607            1,
7608            "harness.v1.skills.remove",
7609            json!({"harness": HarnessId::OPENCLAW, "name": "clawhub-demo"}),
7610        ));
7611        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7612        assert!(response["error"]["message"]
7613            .as_str()
7614            .unwrap()
7615            .contains("no `skills remove` verb"));
7616    }
7617
7618    /// A harness with no skills root at all is refused by name, with the
7619    /// same sentence `skills.list` gives it.
7620    #[test]
7621    fn skills_install_refuses_a_harness_without_a_skills_root() {
7622        let response = HarnessSessionService::new().handle(request(
7623            1,
7624            "harness.v1.skills.install",
7625            json!({"harness": "not-a-harness", "source": "/tmp/x"}),
7626        ));
7627        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7628        assert!(response["error"]["message"]
7629            .as_str()
7630            .unwrap()
7631            .contains("not-a-harness"));
7632    }
7633
7634    // ---- ORCH-12: `harness.v1.memory.show|search` ------------------------
7635
7636    /// `HarnessHomes` for the committed fixture homes. Every root a test does
7637    /// not name is pinned at an absent path, so a read can never fall through
7638    /// to this machine's real harness homes. Note `hermes` is the `state.db`
7639    /// PATH (its parent is HERMES_HOME) and `claude_code` is the `projects`
7640    /// directory — the same contract discovery uses.
7641    fn memory_homes() -> Value {
7642        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7643        json!({
7644            "claude_code": fixtures.join("__absent__"),
7645            "codex": fixtures.join("__absent__"),
7646            "opencode": fixtures.join("__absent__"),
7647            "pi": fixtures.join("__absent__"),
7648            "grok": fixtures.join("__absent__"),
7649            "gemini": fixtures.join("__absent__"),
7650            "goose": fixtures.join("__absent__"),
7651            "supercode": fixtures.join("__absent__"),
7652            "hermes": fixtures.join("hermes_home/state.db"),
7653            "openclaw": fixtures.join("openclaw_home"),
7654        })
7655    }
7656
7657    fn memory_call_ok(method: &str, params: Value, key: &str) -> Vec<Value> {
7658        let response = HarnessSessionService::new().handle(request(1, method, params));
7659        assert!(response.get("error").is_none(), "{response:#}");
7660        assert_eq!(response["result"]["schema"], json!("supercode.memory.v1"));
7661        response["result"][key]
7662            .as_array()
7663            .cloned()
7664            .unwrap_or_default()
7665    }
7666
7667    fn memory_documents(params: Value) -> Vec<Value> {
7668        memory_call_ok("harness.v1.memory.show", params, "documents")
7669    }
7670
7671    fn memory_matches(params: Value) -> Vec<Value> {
7672        memory_call_ok("harness.v1.memory.search", params, "matches")
7673    }
7674
7675    fn find_document<'a>(rows: &'a [Value], profile: &str, name: &str) -> &'a Value {
7676        rows.iter()
7677            .find(|row| row["profile"] == profile && row["name"] == name)
7678            .unwrap_or_else(|| panic!("no `{profile}` document `{name}` in {rows:#?}"))
7679    }
7680
7681    /// Hermes: the built-in `MEMORY.md`/`USER.md` pair and the `memories/`
7682    /// topic files, for HERMES_HOME itself and for every profile home.
7683    #[test]
7684    fn memory_show_reads_the_hermes_profile_homes() {
7685        let rows = memory_documents(json!({"harness": "hermes", "homes": memory_homes()}));
7686
7687        let notes = find_document(&rows, "default", "MEMORY.md");
7688        assert_eq!(notes["harness"], "hermes");
7689        assert_eq!(notes["scope"], "user");
7690        assert!(notes["size"].as_u64().unwrap() > 0);
7691        assert!(notes["updated_at"].is_string(), "{notes:#?}");
7692        // The default answer previews the head and never the whole body.
7693        assert!(notes.get("content").is_none(), "{notes:#?}");
7694        assert_eq!(notes["truncated"], true);
7695        assert_eq!(notes["preview"].as_array().unwrap().len(), 5);
7696
7697        let user = find_document(&rows, "default", "USER.md");
7698        assert_eq!(user["scope"], "user");
7699        assert!(user["preview"]
7700            .as_array()
7701            .unwrap()
7702            .iter()
7703            .any(|line| line.as_str().unwrap().contains("neovim")));
7704
7705        let topic = find_document(&rows, "default", "memories/2026-09-01-notes.md");
7706        assert!(topic["path"]
7707            .as_str()
7708            .unwrap()
7709            .ends_with("hermes_home/memories/2026-09-01-notes.md"));
7710
7711        // Profile mode points HERMES_HOME at `<root>/profiles/<name>`.
7712        let coder = find_document(&rows, "coder", "MEMORY.md");
7713        assert_eq!(coder["scope"], "profile");
7714        assert!(coder["path"]
7715            .as_str()
7716            .unwrap()
7717            .ends_with("hermes_home/profiles/coder/MEMORY.md"));
7718    }
7719
7720    /// `full` is the only way a body crosses the wire, and `profile` narrows
7721    /// the read to one home.
7722    #[test]
7723    fn memory_show_returns_bodies_only_under_full_and_narrows_by_profile() {
7724        let rows = memory_documents(json!({
7725            "harness": "hermes",
7726            "profile": "coder",
7727            "full": true,
7728            "homes": memory_homes(),
7729        }));
7730        assert!(
7731            rows.iter().all(|row| row["profile"] == "coder"),
7732            "{rows:#?}"
7733        );
7734        let coder = find_document(&rows, "coder", "MEMORY.md");
7735        assert!(coder["content"]
7736            .as_str()
7737            .expect("full returns the body")
7738            .contains("anthropic/claude-opus-4-8"));
7739    }
7740
7741    /// OpenClaw: memory-core's files under each agent's workspace —
7742    /// `<state>/workspace` for the default agent, `<state>/workspace-<id>`
7743    /// for any other.
7744    #[test]
7745    fn memory_show_reads_the_openclaw_agent_workspaces() {
7746        let rows = memory_documents(json!({"harness": "openclaw", "homes": memory_homes()}));
7747
7748        let main = find_document(&rows, "main", "MEMORY.md");
7749        assert_eq!(main["scope"], "agent");
7750        assert!(main["path"]
7751            .as_str()
7752            .unwrap()
7753            .ends_with("openclaw_home/workspace/MEMORY.md"));
7754
7755        let topic = find_document(&rows, "main", "memory/2026-09-01-standup.md");
7756        assert!(topic["path"]
7757            .as_str()
7758            .unwrap()
7759            .ends_with("openclaw_home/workspace/memory/2026-09-01-standup.md"));
7760
7761        let design = find_document(&rows, "design", "MEMORY.md");
7762        assert!(design["path"]
7763            .as_str()
7764            .unwrap()
7765            .ends_with("openclaw_home/workspace-design/MEMORY.md"));
7766    }
7767
7768    /// Claude Code: the auto-memory directory of the project the working tree
7769    /// belongs to, keyed by the enclosing git repository.
7770    #[test]
7771    fn memory_show_reads_a_claude_code_project_auto_memory_directory() {
7772        let scratch = std::env::temp_dir().join(format!(
7773            "supercode-orch12-cc-{}-{}",
7774            std::process::id(),
7775            std::time::SystemTime::now()
7776                .duration_since(std::time::UNIX_EPOCH)
7777                .unwrap()
7778                .as_nanos()
7779        ));
7780        let project = scratch.join("repo");
7781        std::fs::create_dir_all(project.join(".git")).unwrap();
7782        // Auto-memory is shared across a repo's worktrees, so a nested
7783        // working directory must resolve to the repo's own project dir.
7784        let worktree = project.join("crates/harness");
7785        std::fs::create_dir_all(&worktree).unwrap();
7786        let slug: String = project
7787            .to_string_lossy()
7788            .chars()
7789            .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
7790            .collect();
7791        let projects = scratch.join("claude/projects");
7792        let memory = projects.join(&slug).join("memory");
7793        std::fs::create_dir_all(&memory).unwrap();
7794        std::fs::write(
7795            memory.join("MEMORY.md"),
7796            "# index\n- [build box](build-box.md) — the pinned harnesses\n",
7797        )
7798        .unwrap();
7799        std::fs::write(
7800            memory.join("build-box.md"),
7801            "hermes 0.21.0 and openclaw 2026.7.1-2 are the pins\n",
7802        )
7803        .unwrap();
7804
7805        let mut homes = memory_homes();
7806        homes["claude_code"] = json!(projects);
7807        let rows = memory_documents(json!({
7808            "harness": "claude-code",
7809            "cwd": worktree,
7810            "homes": homes,
7811        }));
7812        let index = find_document(&rows, &slug, "MEMORY.md");
7813        assert_eq!(index["harness"], "claude-code");
7814        assert_eq!(index["scope"], "project");
7815        let topic = find_document(&rows, &slug, "build-box.md");
7816        assert!(topic["preview"]
7817            .as_array()
7818            .unwrap()
7819            .iter()
7820            .any(|line| line.as_str().unwrap().contains("2026.7.1-2")));
7821
7822        let hits = memory_matches(json!({
7823            "harness": "claude-code",
7824            "query": "pinned harnesses",
7825            "cwd": worktree,
7826            "homes": homes,
7827        }));
7828        assert_eq!(hits.len(), 1, "{hits:#?}");
7829        assert_eq!(hits[0]["name"], "MEMORY.md");
7830        assert_eq!(hits[0]["line"], 2);
7831
7832        let _ = std::fs::remove_dir_all(&scratch);
7833    }
7834
7835    /// A config-less OpenClaw install declares no default agent, but
7836    /// memory-core still resolves ONE agent to the default `workspace`
7837    /// directory — the same `main`-then-first convention the profile rows
7838    /// use. Measured against `openclaw memory status` on the pinned CLI
7839    /// (`docs/interop/research/orch12-memory-receipt-2026-09-03.json`).
7840    #[test]
7841    fn memory_show_resolves_the_default_workspace_without_an_openclaw_config() {
7842        let state = std::env::temp_dir().join(format!(
7843            "supercode-orch12-oc-{}-{}",
7844            std::process::id(),
7845            std::time::SystemTime::now()
7846                .duration_since(std::time::UNIX_EPOCH)
7847                .unwrap()
7848                .as_nanos()
7849        ));
7850        // No `openclaw.json`: only the agent home the gateway creates.
7851        std::fs::create_dir_all(state.join("agents/main/agent")).unwrap();
7852        std::fs::create_dir_all(state.join("workspace")).unwrap();
7853        std::fs::write(
7854            state.join("workspace/MEMORY.md"),
7855            "the gateway websocket needs credentials\n",
7856        )
7857        .unwrap();
7858
7859        let mut homes = memory_homes();
7860        homes["openclaw"] = json!(state);
7861        let rows = memory_documents(json!({"harness": "openclaw", "homes": homes}));
7862        assert_eq!(rows.len(), 1, "{rows:#?}");
7863        let row = find_document(&rows, "main", "MEMORY.md");
7864        assert_eq!(row["scope"], "agent");
7865        assert!(row["path"]
7866            .as_str()
7867            .unwrap()
7868            .ends_with("workspace/MEMORY.md"));
7869
7870        let _ = std::fs::remove_dir_all(&state);
7871    }
7872
7873    /// Search is a plain scan over the same documents: a hit carries the
7874    /// path, line and excerpt; a miss is an empty list, not an error.
7875    #[test]
7876    fn memory_search_reports_hits_by_line_and_misses_as_empty() {
7877        let hit = memory_matches(json!({
7878            "harness": "hermes",
7879            "query": "NEOVIM",
7880            "homes": memory_homes(),
7881        }));
7882        assert_eq!(hit.len(), 1, "{hit:#?}");
7883        assert_eq!(hit[0]["harness"], "hermes");
7884        assert_eq!(hit[0]["name"], "USER.md");
7885        assert_eq!(hit[0]["scope"], "user");
7886        assert_eq!(hit[0]["line"], 5);
7887        assert!(hit[0]["excerpt"].as_str().unwrap().contains("neovim"));
7888
7889        // A regular expression reaches the same lines.
7890        let regex = memory_matches(json!({
7891            "harness": "hermes",
7892            "query": "neo(vim|vi)",
7893            "regex": true,
7894            "homes": memory_homes(),
7895        }));
7896        assert_eq!(regex.len(), 1, "{regex:#?}");
7897
7898        let miss = memory_matches(json!({
7899            "harness": "hermes",
7900            "query": "no-memory-line-says-this",
7901            "homes": memory_homes(),
7902        }));
7903        assert!(miss.is_empty(), "{miss:#?}");
7904    }
7905
7906    /// The uniform-verb contract: a harness with no memory store at the pin
7907    /// is refused by name, and `session` only selects a Claude Code project.
7908    #[test]
7909    fn memory_refuses_harnesses_without_a_store_and_misplaced_session_scoping() {
7910        for method in ["harness.v1.memory.show", "harness.v1.memory.search"] {
7911            let response = HarnessSessionService::new().handle(request(
7912                1,
7913                method,
7914                json!({"harness": "codex", "query": "anything", "homes": memory_homes()}),
7915            ));
7916            assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7917            assert!(response["error"]["message"]
7918                .as_str()
7919                .unwrap()
7920                .contains("codex"));
7921        }
7922
7923        let response = HarnessSessionService::new().handle(request(
7924            1,
7925            "harness.v1.memory.show",
7926            json!({"harness": "hermes", "session": "abc", "homes": memory_homes()}),
7927        ));
7928        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7929
7930        // `harness` is not optional: memory documents are the user's prose.
7931        let response = HarnessSessionService::new().handle(request(
7932            1,
7933            "harness.v1.memory.show",
7934            json!({"homes": memory_homes()}),
7935        ));
7936        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7937    }
7938
7939    /// Both methods are advertised, and their SDK operations resolve them.
7940    #[test]
7941    fn memory_methods_are_advertised_and_map_to_sdk_operations() {
7942        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.show"));
7943        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.search"));
7944        assert_eq!(
7945            SdkOperation::from_method("harness.v1.memory.show"),
7946            Some(SdkOperation::MemoryShow)
7947        );
7948        assert_eq!(
7949            SdkOperation::from_method("harness.v1.memory.search"),
7950            Some(SdkOperation::MemorySearch)
7951        );
7952    }
7953
7954    // ---- ORCH-9: `harness.v1.approvals.list` -----------------------------
7955
7956    /// A runtime that raises one protocol request and then goes quiet, so a
7957    /// single poll delivers the request without closing the connection.
7958    struct RequestingRuntime {
7959        handle: RuntimeHandle,
7960        events: std::collections::VecDeque<HarnessEvent>,
7961        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7962    }
7963
7964    #[async_trait]
7965    impl RuntimeConnection for RequestingRuntime {
7966        fn handle(&self) -> &RuntimeHandle {
7967            &self.handle
7968        }
7969
7970        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
7971            unreachable!("this runtime only raises requests")
7972        }
7973
7974        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
7975            match self.events.pop_front() {
7976                Some(event) => Ok(Some(event)),
7977                // Quiet, not closed: `poll_sdk_events` times out and leaves
7978                // the connection open, the way a runtime blocked on a
7979                // permission request behaves.
7980                None => std::future::pending().await,
7981            }
7982        }
7983
7984        async fn interrupt(&mut self) -> crate::Result<()> {
7985            Ok(())
7986        }
7987
7988        async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
7989            // Both halves are recorded: ORCH-20 has to prove not just that the
7990            // right request was answered but that the door received its own
7991            // reply envelope.
7992            self.answered
7993                .lock()
7994                .unwrap_or_else(std::sync::PoisonError::into_inner)
7995                .push(json!({"request_id": request_id, "response": response}));
7996            Ok(())
7997        }
7998
7999        async fn close(&mut self) -> crate::Result<()> {
8000            Ok(())
8001        }
8002    }
8003
8004    fn requesting_runtime(
8005        harness: &str,
8006        events: Vec<HarnessEvent>,
8007        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
8008    ) -> Box<dyn RuntimeConnection> {
8009        requesting_runtime_named(harness, "hermes-live-session", events, answered)
8010    }
8011
8012    fn requesting_runtime_named(
8013        harness: &str,
8014        runtime_id: &str,
8015        events: Vec<HarnessEvent>,
8016        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
8017    ) -> Box<dyn RuntimeConnection> {
8018        Box::new(RequestingRuntime {
8019            handle: RuntimeHandle {
8020                harness: HarnessId::from(harness),
8021                runtime_id: runtime_id.into(),
8022                endpoint: RuntimeEndpoint::LocalProcess {
8023                    pid: None,
8024                    command: vec!["hermes-acp".into()],
8025                    protocol: "acp".into(),
8026                },
8027            },
8028            events: events.into(),
8029            answered,
8030        })
8031    }
8032
8033    fn permission_event(id: u64, title: &str) -> HarnessEvent {
8034        HarnessEvent {
8035            sequence: None,
8036            kind: "session/request_permission".into(),
8037            payload: json!({
8038                "jsonrpc": "2.0",
8039                "id": id,
8040                "method": "session/request_permission",
8041                "params": {
8042                    "sessionId": "hermes-live-session",
8043                    "toolCall": {"toolCallId": "call-1", "title": title, "kind": "execute"},
8044                    "options": [
8045                        {"optionId": "allow_once", "name": "Allow once", "kind": "allow_once"},
8046                        {"optionId": "allow_for_session", "name": "Allow for session", "kind": "allow_always"},
8047                        {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
8048                    ],
8049                },
8050            }),
8051        }
8052    }
8053
8054    fn approvals(service: &mut HarnessSessionService, params: Value) -> Value {
8055        let response = service.handle(request(1, "harness.v1.approvals.list", params));
8056        assert!(response.get("error").is_none(), "{response:#}");
8057        response["result"].clone()
8058    }
8059
8060    /// ORC-2 dev/01: the same uniform loop over the CLAUDE CODE door. The
8061    /// `can_use_tool` control request the CLI raises to its registered
8062    /// permission handler lists as one pending row, `approvals.resolve <id>
8063    /// allow_once` sends the `{behavior}` result the CLI accepts through
8064    /// `runtimes.respond`, and the row is gone. The frame is the one claude
8065    /// 2.1.258 wrote, transcribed from
8066    /// `docs/interop/research/orc2-claude-respond-receipt-2026-09-04.json`.
8067    #[tokio::test]
8068    async fn a_claude_code_permission_request_lists_and_resolves_on_the_uniform_door() {
8069        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8070        let mut service = HarnessSessionService::new();
8071        service.runtimes.insert(
8072            "runtime-cc".into(),
8073            requesting_runtime_named(
8074                HarnessId::CLAUDE_CODE,
8075                "claude-live-session",
8076                vec![HarnessEvent {
8077                    sequence: None,
8078                    kind: "control_request".into(),
8079                    payload: json!({
8080                        "type": "control_request",
8081                        "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
8082                        "request": {
8083                            "subtype": "can_use_tool",
8084                            "tool_name": "Bash",
8085                            "display_name": "Bash",
8086                            "input": {"command": "touch probe-artifact.txt"},
8087                            "tool_use_id": "toolu_mock_1",
8088                        },
8089                    }),
8090                }],
8091                answered.clone(),
8092            ),
8093        );
8094
8095        let notifications = service.poll_runtimes().await;
8096        assert_eq!(notifications.len(), 1, "{notifications:#?}");
8097
8098        let rows = approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}));
8099        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
8100        let row = &rows[0];
8101        assert_eq!(row["id"], "runtime-cc/053f8a2d-3445-4011-a259-4261b31c7326");
8102        assert_eq!(row["harness"], HarnessId::CLAUDE_CODE);
8103        assert_eq!(row["status"], "pending");
8104        assert_eq!(row["subject"], "Bash touch probe-artifact.txt");
8105        assert_eq!(row["runtime_id"], "claude-live-session");
8106        assert_eq!(
8107            row["options"]
8108                .as_array()
8109                .unwrap()
8110                .iter()
8111                .map(|option| option["id"].as_str().unwrap())
8112                .collect::<Vec<_>>(),
8113            vec!["allow", "deny"],
8114        );
8115
8116        let response = resolve(
8117            &mut service,
8118            json!({"id": row["id"], "decision": "allow_once"}),
8119        )
8120        .await;
8121        assert!(response.get("error").is_none(), "{response:#}");
8122        assert_eq!(response["result"]["option_id"], "allow");
8123        assert_eq!(
8124            answered
8125                .lock()
8126                .unwrap_or_else(std::sync::PoisonError::into_inner)
8127                .as_slice(),
8128            &[json!({
8129                "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
8130                "response": {"behavior": "allow"},
8131            })],
8132        );
8133        assert_eq!(
8134            approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}))
8135                .as_array()
8136                .map(Vec::len),
8137            Some(0),
8138        );
8139    }
8140
8141    /// dev/01: a live ACP permission request raised on a driven runtime is
8142    /// listable while the turn is blocked on it, and stops being listable
8143    /// the moment `runtimes.respond` answers it.
8144    #[tokio::test]
8145    async fn a_live_permission_request_lists_until_it_is_answered() {
8146        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8147        let mut service = HarnessSessionService::new();
8148        service.runtimes.insert(
8149            "runtime-1".into(),
8150            requesting_runtime(
8151                HarnessId::HERMES,
8152                vec![permission_event(7, "rm -rf build")],
8153                answered.clone(),
8154            ),
8155        );
8156
8157        let notifications = service.poll_runtimes().await;
8158        assert_eq!(notifications.len(), 1, "{notifications:#?}");
8159
8160        let rows = approvals(&mut service, json!({}));
8161        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
8162        let row = &rows[0];
8163        assert_eq!(row["id"], "runtime-1/7");
8164        assert_eq!(row["harness"], HarnessId::HERMES);
8165        assert_eq!(row["kind"], "live");
8166        assert_eq!(row["status"], "pending");
8167        assert_eq!(row["subject"], "rm -rf build");
8168        assert_eq!(row["session_id"], "hermes-live-session");
8169        assert_eq!(row["runtime_id"], "hermes-live-session");
8170        assert!(row["requested_at_ms"].as_i64().is_some(), "{row:#}");
8171        assert!(
8172            row["age_ms"].as_i64().is_some_and(|age| age >= 0),
8173            "{row:#}"
8174        );
8175        assert_eq!(
8176            row["options"]
8177                .as_array()
8178                .unwrap()
8179                .iter()
8180                .map(|option| option["id"].as_str().unwrap())
8181                .collect::<Vec<_>>(),
8182            vec!["allow_once", "allow_for_session", "deny"],
8183        );
8184
8185        // The filters select against the same rows.
8186        assert_eq!(
8187            approvals(&mut service, json!({"harness": HarnessId::HERMES}))
8188                .as_array()
8189                .map(Vec::len),
8190            Some(1),
8191        );
8192        assert_eq!(
8193            approvals(&mut service, json!({"session": "some-other-session"}))
8194                .as_array()
8195                .map(Vec::len),
8196            Some(0),
8197        );
8198
8199        let response = service
8200            .handle_async(request(
8201                2,
8202                "harness.v1.runtimes.respond",
8203                json!({
8204                    "connection": "runtime-1",
8205                    "request_id": 7,
8206                    "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8207                }),
8208            ))
8209            .await;
8210        assert!(response.get("error").is_none(), "{response:#}");
8211        assert_eq!(
8212            answered
8213                .lock()
8214                .unwrap_or_else(std::sync::PoisonError::into_inner)
8215                .as_slice(),
8216            &[json!({
8217                "request_id": 7,
8218                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8219            })],
8220        );
8221
8222        let rows = approvals(&mut service, json!({}));
8223        assert_eq!(rows.as_array().map(Vec::len), Some(0), "{rows:#}");
8224    }
8225
8226    /// dev/01: supercode's own queued subagent approvals list through the
8227    /// same door, carrying the outcome the record holds.
8228    #[test]
8229    fn queued_subagent_approvals_list_through_the_same_door() {
8230        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
8231            crate::subagents::QueuedApproval {
8232                child_agent_id: "child-7".into(),
8233                tool: "shell".into(),
8234                subject: Some("cargo publish --dry-run".into()),
8235                queued_at_ms: 1,
8236                outcome: None,
8237            },
8238            crate::subagents::QueuedApproval {
8239                child_agent_id: "child-8".into(),
8240                tool: "write_file".into(),
8241                subject: None,
8242                queued_at_ms: 2,
8243                outcome: Some(crate::subagents::QueuedApprovalOutcome::Denied),
8244            },
8245        ]));
8246        let mut service = HarnessSessionService::new();
8247        service.observe_subagent_approvals(queue);
8248
8249        let rows = approvals(&mut service, json!({}));
8250        assert_eq!(rows.as_array().map(Vec::len), Some(2), "{rows:#}");
8251        assert_eq!(rows[0]["id"], "supercode/subagent/child-7/1/0");
8252        assert_eq!(rows[0]["harness"], HarnessId::SUPERCODE);
8253        assert_eq!(rows[0]["status"], "pending");
8254        assert_eq!(rows[0]["subject"], "shell cargo publish --dry-run");
8255        assert_eq!(rows[1]["status"], "denied");
8256        assert!(rows[1]["options"].as_array().unwrap().is_empty());
8257
8258        // `--session` addresses a subagent row by its child agent id.
8259        let only = approvals(&mut service, json!({"session": "child-8"}));
8260        assert_eq!(only.as_array().map(Vec::len), Some(1), "{only:#}");
8261        assert_eq!(only[0]["id"], "supercode/subagent/child-8/2/1");
8262    }
8263
8264    /// The uniform-verb contract: an id whose runtime door cannot carry a
8265    /// protocol request is refused BY NAME rather than answered with an empty
8266    /// list. Since ORC-2 gave Claude Code a permission-response primitive
8267    /// every registered harness can carry one, so the refusal is exercised on
8268    /// an unknown id — and the registered ids are asserted to be accepted.
8269    #[test]
8270    fn approvals_list_refuses_a_harness_that_cannot_carry_a_request() {
8271        let response = HarnessSessionService::new().handle(request(
8272            1,
8273            "harness.v1.approvals.list",
8274            json!({"harness": "not-a-harness"}),
8275        ));
8276        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
8277        assert!(response["error"]["message"]
8278            .as_str()
8279            .unwrap()
8280            .contains("not-a-harness"));
8281        for harness in [HarnessId::CLAUDE_CODE, HarnessId::CODEX] {
8282            let response = HarnessSessionService::new().handle(request(
8283                1,
8284                "harness.v1.approvals.list",
8285                json!({"harness": harness}),
8286            ));
8287            assert!(response.get("error").is_none(), "{harness}: {response:#}");
8288        }
8289    }
8290
8291    /// The method is advertised, its SDK operation resolves it, and the
8292    /// registry reports the concept as observed for every harness whose
8293    /// runtime door can carry a request.
8294    #[test]
8295    fn approvals_list_is_an_advertised_method_and_an_observed_tier() {
8296        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.list"));
8297        assert_eq!(
8298            SdkOperation::from_method("harness.v1.approvals.list"),
8299            Some(SdkOperation::ApprovalsList)
8300        );
8301        let registry = harness_support_registry();
8302        for id in [
8303            HarnessId::HERMES,
8304            HarnessId::OPENCLAW,
8305            HarnessId::CODEX,
8306            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8307            // pending_request concept joins the other driven doors.
8308            HarnessId::CLAUDE_CODE,
8309        ] {
8310            let concept = registry
8311                .harnesses
8312                .iter()
8313                .find(|harness| harness.id.as_str() == id)
8314                .unwrap()
8315                .orchestration
8316                .concepts
8317                .iter()
8318                .find(|concept| concept.concept == "pending_request")
8319                .unwrap();
8320            assert_eq!(concept.observed, crate::ImplementationKind::BuiltIn, "{id}");
8321            assert!(concept
8322                .methods
8323                .iter()
8324                .any(|method| method == "harness.v1.approvals.list"));
8325        }
8326    }
8327
8328    // ---- ORCH-20: `harness.v1.approvals.resolve` -------------------------
8329
8330    async fn resolve(service: &mut HarnessSessionService, params: Value) -> Value {
8331        service
8332            .handle_async(request(3, "harness.v1.approvals.resolve", params))
8333            .await
8334    }
8335
8336    /// dev/01: the whole loop on a driven runtime — list one pending row,
8337    /// answer it by ROW ID with one uniform decision, and see it gone. The
8338    /// door receives its own ACP envelope carrying the option it enumerated.
8339    #[tokio::test]
8340    async fn a_listed_row_resolves_with_one_uniform_decision_and_then_is_gone() {
8341        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8342        let mut service = HarnessSessionService::new();
8343        service.runtimes.insert(
8344            "runtime-1".into(),
8345            requesting_runtime(
8346                HarnessId::HERMES,
8347                vec![permission_event(7, "rm -rf build")],
8348                answered.clone(),
8349            ),
8350        );
8351        service.poll_runtimes().await;
8352
8353        let rows = approvals(&mut service, json!({}));
8354        assert_eq!(rows[0]["id"], "runtime-1/7");
8355
8356        let response = resolve(
8357            &mut service,
8358            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8359        )
8360        .await;
8361        assert!(response.get("error").is_none(), "{response:#}");
8362        assert_eq!(
8363            response["result"],
8364            json!({
8365                "id": "runtime-1/7",
8366                "decision": "allow_once",
8367                "option_id": "allow_once",
8368                "resolved": true,
8369            }),
8370        );
8371        // The harness's own door was called with its own envelope.
8372        assert_eq!(
8373            answered
8374                .lock()
8375                .unwrap_or_else(std::sync::PoisonError::into_inner)
8376                .as_slice(),
8377            &[json!({
8378                "request_id": 7,
8379                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8380            })],
8381        );
8382        // And the row is gone, the same way `runtimes.respond` drops it.
8383        assert_eq!(
8384            approvals(&mut service, json!({})).as_array().map(Vec::len),
8385            Some(0),
8386        );
8387        // Answering it twice is an honest miss, not a silent success.
8388        let response = resolve(
8389            &mut service,
8390            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8391        )
8392        .await;
8393        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8394    }
8395
8396    /// dev/01: deny travels the same path and picks the option the request
8397    /// itself classified as a refusal.
8398    #[tokio::test]
8399    async fn deny_selects_the_requests_own_reject_option() {
8400        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8401        let mut service = HarnessSessionService::new();
8402        service.runtimes.insert(
8403            "runtime-1".into(),
8404            requesting_runtime(
8405                HarnessId::HERMES,
8406                vec![permission_event(11, "git push --force")],
8407                answered.clone(),
8408            ),
8409        );
8410        service.poll_runtimes().await;
8411
8412        let response = resolve(
8413            &mut service,
8414            json!({"id": "runtime-1/11", "decision": "deny"}),
8415        )
8416        .await;
8417        assert!(response.get("error").is_none(), "{response:#}");
8418        // `deny` is the optionId whose ACP `kind` is `reject_once`.
8419        assert_eq!(response["result"]["option_id"], "deny");
8420        assert_eq!(
8421            answered
8422                .lock()
8423                .unwrap_or_else(std::sync::PoisonError::into_inner)[0]["response"],
8424            json!({"outcome": {"outcome": "selected", "optionId": "deny"}}),
8425        );
8426        assert_eq!(
8427            approvals(&mut service, json!({})).as_array().map(Vec::len),
8428            Some(0),
8429        );
8430    }
8431
8432    /// dev/01: a decision this request does not offer is refused by name,
8433    /// listing the ones it does — never silently downgraded to a neighbour.
8434    #[tokio::test]
8435    async fn a_decision_the_request_does_not_offer_is_refused_with_the_offered_ones() {
8436        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8437        let mut service = HarnessSessionService::new();
8438        let mut event = permission_event(3, "rm -rf build");
8439        // A request offering only allow-once and deny, as hermes 0.21.0's
8440        // edit-approval layer raises one.
8441        event.payload["params"]["options"] = json!([
8442            {"optionId": "allow_once", "name": "Allow edit", "kind": "allow_once"},
8443            {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
8444        ]);
8445        service.runtimes.insert(
8446            "runtime-1".into(),
8447            requesting_runtime(HarnessId::HERMES, vec![event], answered.clone()),
8448        );
8449        service.poll_runtimes().await;
8450
8451        let response = resolve(
8452            &mut service,
8453            json!({"id": "runtime-1/3", "decision": "allow_always"}),
8454        )
8455        .await;
8456        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8457        let message = response["error"]["message"].as_str().unwrap();
8458        assert!(message.contains("allow_always"), "{message}");
8459        assert!(message.contains("allow_once, deny"), "{message}");
8460        // Nothing was sent, and the request is still waiting for an answer.
8461        assert!(answered
8462            .lock()
8463            .unwrap_or_else(std::sync::PoisonError::into_inner)
8464            .is_empty());
8465        assert_eq!(
8466            approvals(&mut service, json!({})).as_array().map(Vec::len),
8467            Some(1),
8468        );
8469    }
8470
8471    /// dev/01: supercode's own queued subagent row is addressable but not
8472    /// answerable through this door — it is the parent's audit copy of a
8473    /// request its own handler answers. Refused by name, never a no-op.
8474    #[tokio::test]
8475    async fn a_queued_subagent_row_is_refused_by_name_rather_than_silently_answered() {
8476        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
8477            crate::subagents::QueuedApproval {
8478                child_agent_id: "child-7".into(),
8479                tool: "shell".into(),
8480                subject: Some("cargo publish --dry-run".into()),
8481                queued_at_ms: 1,
8482                outcome: None,
8483            },
8484        ]));
8485        let mut service = HarnessSessionService::new();
8486        service.observe_subagent_approvals(queue.clone());
8487        let row = approvals(&mut service, json!({}))[0]["id"]
8488            .as_str()
8489            .unwrap()
8490            .to_string();
8491        assert_eq!(row, "supercode/subagent/child-7/1/0");
8492
8493        let response = resolve(&mut service, json!({"id": row, "decision": "allow_once"})).await;
8494        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8495        let message = response["error"]["message"].as_str().unwrap();
8496        assert!(message.contains("queued subagent record"), "{message}");
8497        assert!(message.contains("request"), "{message}");
8498        // The audit record is untouched: nothing pretended to answer it.
8499        assert!(queue
8500            .lock()
8501            .unwrap_or_else(std::sync::PoisonError::into_inner)[0]
8502            .outcome
8503            .is_none());
8504    }
8505
8506    /// An id nobody is holding, and a call that names no decision at all,
8507    /// both fail with a message that says why.
8508    #[tokio::test]
8509    async fn an_unknown_row_and_a_missing_decision_are_both_named() {
8510        let mut service = HarnessSessionService::new();
8511        let response = resolve(
8512            &mut service,
8513            json!({"id": "runtime-9/4", "decision": "deny"}),
8514        )
8515        .await;
8516        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8517        assert!(response["error"]["message"]
8518            .as_str()
8519            .unwrap()
8520            .contains("runtime-9/4"));
8521
8522        let response = resolve(&mut service, json!({"id": "runtime-9/4"})).await;
8523        let message = response["error"]["message"].as_str().unwrap();
8524        assert!(
8525            message.contains("allow_once | allow_always | deny"),
8526            "{message}"
8527        );
8528
8529        let response = resolve(
8530            &mut service,
8531            json!({"id": "runtime-9/4", "decision": "deny", "option_id": "deny"}),
8532        )
8533        .await;
8534        assert!(response["error"]["message"]
8535            .as_str()
8536            .unwrap()
8537            .contains("not both"));
8538    }
8539
8540    /// The method is advertised, its SDK operation resolves it, and every
8541    /// harness whose runtime door can carry a request reports it on the
8542    /// CONTROLLED tier beside `runtimes.respond`.
8543    #[test]
8544    fn approvals_resolve_is_an_advertised_method_and_a_controlled_tier() {
8545        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.resolve"));
8546        assert_eq!(
8547            SdkOperation::from_method("harness.v1.approvals.resolve"),
8548            Some(SdkOperation::ApprovalsResolve)
8549        );
8550        assert_eq!(
8551            SdkOperation::ApprovalsResolve.action_name(),
8552            "approvals_resolve"
8553        );
8554        let registry = harness_support_registry();
8555        for id in [
8556            HarnessId::HERMES,
8557            HarnessId::OPENCLAW,
8558            HarnessId::CODEX,
8559            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8560            // pending_request concept joins the other driven doors.
8561            HarnessId::CLAUDE_CODE,
8562        ] {
8563            let concept = registry
8564                .harnesses
8565                .iter()
8566                .find(|harness| harness.id.as_str() == id)
8567                .unwrap()
8568                .orchestration
8569                .concepts
8570                .iter()
8571                .find(|concept| concept.concept == "pending_request")
8572                .unwrap();
8573            assert_eq!(
8574                concept.controlled,
8575                crate::ImplementationKind::BuiltIn,
8576                "{id}"
8577            );
8578            assert!(
8579                concept
8580                    .methods
8581                    .iter()
8582                    .any(|method| method == "harness.v1.approvals.resolve"),
8583                "{id}"
8584            );
8585        }
8586    }
8587
8588    #[test]
8589    fn capabilities_are_explicit_and_versioned() {
8590        let mut service = HarnessSessionService::new();
8591        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
8592        assert_eq!(response["result"]["version"], HARNESS_SERVICE_VERSION);
8593        assert_eq!(
8594            response["result"]["sdk"]["schema_version"],
8595            crate::SDK_SCHEMA_VERSION
8596        );
8597        assert_eq!(
8598            response["result"]["sdk"]["operations"]
8599                .as_array()
8600                .unwrap()
8601                .len(),
8602            SdkOperation::ALL.len()
8603        );
8604        assert_eq!(
8605            response["result"]["harnesses"].as_array().unwrap().len(),
8606            11
8607        );
8608        assert!(response["result"]["harnesses"]
8609            .as_array()
8610            .unwrap()
8611            .iter()
8612            .any(|harness| harness == HarnessId::GROK));
8613        assert!(response["result"]["harnesses"]
8614            .as_array()
8615            .unwrap()
8616            .iter()
8617            .any(|harness| harness == HarnessId::GOOSE));
8618    }
8619
8620    #[test]
8621    fn handshake_health_uses_protocol_liveness_not_stderr_severity() {
8622        let noisy_stderr = crate::HarnessEvent {
8623            sequence: None,
8624            kind: "transport_stderr".into(),
8625            payload: json!({"line": "ERROR optional worker AuthorizationRequired"}),
8626        };
8627        assert_eq!(handshake_event_failure(&noisy_stderr), None);
8628
8629        let closed = crate::HarnessEvent {
8630            sequence: None,
8631            kind: "transport_closed".into(),
8632            payload: json!({}),
8633        };
8634        assert!(handshake_event_failure(&closed).is_some());
8635    }
8636
8637    #[tokio::test]
8638    async fn runtime_eof_is_notified_and_removed_for_raw_and_explicit_close() {
8639        let mut service = HarnessSessionService::new();
8640        service
8641            .runtimes
8642            .insert("raw-eof".into(), ending_runtime(None));
8643        service.runtimes.insert(
8644            "explicit-close".into(),
8645            ending_runtime(Some(HarnessEvent {
8646                sequence: None,
8647                kind: "transport_closed".into(),
8648                payload: json!({"message": "native transport exited"}),
8649            })),
8650        );
8651
8652        let notifications = service.poll_runtimes().await;
8653
8654        assert_eq!(notifications.len(), 2);
8655        assert!(notifications
8656            .iter()
8657            .all(|notification| { notification["params"]["event"]["kind"] == "transport_closed" }));
8658        assert!(notifications.iter().all(|notification| {
8659            notification["params"]["session_id"] == "ending-session"
8660                && notification["params"]["connection"].is_string()
8661        }));
8662        let mut sequences = notifications
8663            .iter()
8664            .filter_map(|notification| notification["params"]["sequence"].as_u64())
8665            .collect::<Vec<_>>();
8666        sequences.sort_unstable();
8667        assert_eq!(sequences, vec![1, 2]);
8668        assert!(service.runtimes.is_empty());
8669    }
8670
8671    #[test]
8672    fn support_report_and_grok_default_binding_share_the_registry() {
8673        let mut service = HarnessSessionService::new();
8674        let response = service.handle(request(1, "harness.v1.support.report", json!({})));
8675        assert_eq!(response["result"]["schema"], crate::SUPPORT_REGISTRY_SCHEMA);
8676        let params = RuntimeBackendParams {
8677            harness: HarnessId::from(HarnessId::GROK),
8678            protocol: None,
8679            launch: None,
8680            base_url: None,
8681            policy: RuntimePolicy::Default,
8682        };
8683        let backend = match runtime_backend(&params) {
8684            Ok(backend) => backend,
8685            Err(_) => panic!("Grok should bind through its registered ACP launch"),
8686        };
8687        assert_eq!(backend.harness().as_str(), HarnessId::GROK);
8688        assert!(backend.capabilities().start_session);
8689        let registered = harness_support_registry()
8690            .harnesses
8691            .into_iter()
8692            .find(|harness| harness.id.as_str() == HarnessId::GROK)
8693            .and_then(|harness| harness.runtime.default_launch)
8694            .unwrap();
8695        assert!(!registered
8696            .arguments
8697            .iter()
8698            .any(|argument| argument == "--always-approve"));
8699        assert!(runtime_launch(&params).is_none());
8700
8701        let yolo = RuntimeBackendParams {
8702            policy: RuntimePolicy::Yolo,
8703            ..params
8704        };
8705        assert!(runtime_launch(&yolo)
8706            .unwrap()
8707            .arguments
8708            .iter()
8709            .any(|argument| argument == "--always-approve"));
8710
8711        let mismatched_protocol = RuntimeBackendParams {
8712            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8713            protocol: Some("acp".into()),
8714            launch: None,
8715            base_url: None,
8716            policy: RuntimePolicy::Default,
8717        };
8718        assert!(runtime_backend(&mismatched_protocol).is_err());
8719    }
8720
8721    #[test]
8722    fn load_follow_and_unfollow_share_the_same_locator() {
8723        let mut service = HarnessSessionService::new();
8724        let locator = pi_locator();
8725        let loaded = service.handle(request(
8726            1,
8727            "harness.v1.sessions.load",
8728            json!({"locator": locator}),
8729        ));
8730        assert_eq!(
8731            loaded["result"]["session"]["session_id"],
8732            locator.session_id
8733        );
8734
8735        let followed = service.handle(request(
8736            2,
8737            "harness.v1.sessions.follow",
8738            json!({"locator": locator}),
8739        ));
8740        assert_eq!(followed["result"]["subscription"], "sub-1");
8741        assert_eq!(followed["result"]["initial"]["type"], "session_snapshot");
8742        assert!(service.poll().is_empty());
8743
8744        let unfollowed = service.handle(request(
8745            3,
8746            "harness.v1.sessions.unfollow",
8747            json!({"subscription": "sub-1"}),
8748        ));
8749        assert_eq!(unfollowed["result"]["removed"], true);
8750    }
8751
8752    #[test]
8753    fn bounded_read_view_excludes_subagents_and_keeps_only_the_tail() {
8754        let temp = std::env::temp_dir().join(format!(
8755            "supercode-bounded-view-{}-{}",
8756            std::process::id(),
8757            generated_session_id()
8758        ));
8759        let path = temp.join("parent.jsonl");
8760        let subagents = temp.join("parent/subagents");
8761        std::fs::create_dir_all(&subagents).unwrap();
8762        let long_last = "x".repeat(300);
8763        let parent_records = [
8764            json!({"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":"first"}}),
8765            json!({"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"middle"}]}}),
8766            json!({"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":long_last}}),
8767        ];
8768        std::fs::write(
8769            &path,
8770            format!(
8771                "{}\n",
8772                parent_records
8773                    .iter()
8774                    .map(Value::to_string)
8775                    .collect::<Vec<_>>()
8776                    .join("\n")
8777            ),
8778        )
8779        .unwrap();
8780        std::fs::write(
8781            subagents.join("agent-child.jsonl"),
8782            concat!(
8783                r#"{"type":"user","uuid":"cu","parentUuid":null,"agentId":"child","message":{"role":"user","content":"child work"}}"#,
8784                "\n",
8785            ),
8786        )
8787        .unwrap();
8788        let locator = SessionLocator {
8789            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8790            session_id: "parent".into(),
8791            storage: StorageLocator::File { path },
8792        };
8793        let mut service = HarnessSessionService::new();
8794
8795        let complete = service.handle(request(
8796            1,
8797            "harness.v1.sessions.load",
8798            json!({"locator": locator}),
8799        ));
8800        assert_eq!(
8801            complete["result"]["session"]["subagents"]
8802                .as_array()
8803                .unwrap()
8804                .len(),
8805            1
8806        );
8807
8808        let bounded = service.handle(request(
8809            2,
8810            "harness.v1.sessions.load",
8811            json!({
8812                "locator": locator,
8813                "view": {
8814                    "tail_messages": 1,
8815                    "max_message_chars": 256,
8816                    "include_subagents": false
8817                },
8818            }),
8819        ));
8820        let session = &bounded["result"]["session"];
8821        assert!(session["subagents"].as_array().unwrap().is_empty());
8822        assert_eq!(session["messages"].as_array().unwrap().len(), 1);
8823        assert_eq!(
8824            session["messages"][0]["content"],
8825            format!("{}\n…", "x".repeat(256))
8826        );
8827
8828        let followed = service.handle(request(
8829            3,
8830            "harness.v1.sessions.follow",
8831            json!({
8832                "locator": locator,
8833                "view": {
8834                    "tail_messages": 1,
8835                    "max_message_chars": 256,
8836                    "include_subagents": false
8837                },
8838            }),
8839        ));
8840        let initial = &followed["result"]["initial"]["session"];
8841        assert!(initial["subagents"].as_array().unwrap().is_empty());
8842        assert_eq!(initial["messages"].as_array().unwrap().len(), 1);
8843
8844        let _ = std::fs::remove_dir_all(&temp);
8845    }
8846
8847    #[test]
8848    fn forty_megabyte_display_load_is_bounded_and_prompt() {
8849        let temp = std::env::temp_dir().join(format!(
8850            "supercode-large-display-view-{}-{}",
8851            std::process::id(),
8852            generated_session_id()
8853        ));
8854        std::fs::create_dir_all(&temp).unwrap();
8855        let path = temp.join("rollout.jsonl");
8856        let mut file = std::io::BufWriter::new(std::fs::File::create(&path).unwrap());
8857        writeln!(
8858            file,
8859            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8860        )
8861        .unwrap();
8862        let padding = "x".repeat(80 * 1024);
8863        for index in 0..512 {
8864            let marker = if index == 0 {
8865                "OLDEST-SHOULD-NOT-LOAD"
8866            } else if index == 511 {
8867                "LATEST-MUST-LOAD"
8868            } else {
8869                "bulk"
8870            };
8871            writeln!(
8872                file,
8873                "{}",
8874                json!({
8875                    "timestamp": "2026-01-01T00:00:01Z",
8876                    "type": "response_item",
8877                    "payload": {
8878                        "type": "message",
8879                        "role": "assistant",
8880                        "content": [{"type": "output_text", "text": format!("{marker}:{padding}")}],
8881                    },
8882                })
8883            )
8884            .unwrap();
8885        }
8886        file.flush().unwrap();
8887        drop(file);
8888        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
8889
8890        let locator = SessionLocator {
8891            harness: HarnessId::from(HarnessId::CODEX),
8892            session_id: "large-display".into(),
8893            storage: StorageLocator::File { path },
8894        };
8895        let started = Instant::now();
8896        let response = HarnessSessionService::new().handle(request(
8897            1,
8898            "harness.v1.sessions.load",
8899            json!({
8900                "locator": locator,
8901                "view": {
8902                    "tail_messages": 500,
8903                    "max_message_chars": 1024,
8904                    "include_subagents": false,
8905                    "display_history": true,
8906                },
8907            }),
8908        ));
8909        let elapsed = started.elapsed();
8910        let wire = response.to_string();
8911        eprintln!(
8912            "bounded 40 MiB display load: {elapsed:?}, {} response bytes",
8913            wire.len()
8914        );
8915        assert!(response.get("error").is_none(), "{response:#}");
8916        assert!(wire.contains("LATEST-MUST-LOAD"));
8917        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
8918        assert!(
8919            wire.len() < 2 * 1024 * 1024,
8920            "bounded wire was {} bytes",
8921            wire.len()
8922        );
8923        assert!(
8924            elapsed.as_secs_f64() < 3.0,
8925            "bounded 40 MiB load took {elapsed:?}"
8926        );
8927
8928        // Timing-free: a store with no human turn widens its window to the 64 MiB ceiling
8929        // looking for anchors, so the bounded read shows on one with a human turn every eight
8930        // records: a short view stops well short of the first record and says so.
8931        let anchored = temp.join("anchored.jsonl");
8932        let mut file = std::io::BufWriter::new(std::fs::File::create(&anchored).unwrap());
8933        writeln!(
8934            file,
8935            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8936        )
8937        .unwrap();
8938        for index in 0..512 {
8939            let marker = if index == 0 {
8940                "OLDEST-SHOULD-NOT-LOAD"
8941            } else if index == 511 {
8942                "LATEST-MUST-LOAD"
8943            } else {
8944                "bulk"
8945            };
8946            let (role, kind) = if index % 8 == 0 {
8947                ("user", "input_text")
8948            } else {
8949                ("assistant", "output_text")
8950            };
8951            writeln!(
8952                file,
8953                "{}",
8954                json!({
8955                    "timestamp": "2026-01-01T00:00:01Z",
8956                    "type": "response_item",
8957                    "payload": {
8958                        "type": "message",
8959                        "role": role,
8960                        "content": [{"type": kind, "text": format!("{marker}:{padding}")}],
8961                    },
8962                })
8963            )
8964            .unwrap();
8965        }
8966        file.flush().unwrap();
8967        drop(file);
8968        let short = HarnessSessionService::new().handle(request(
8969            2,
8970            "harness.v1.sessions.load",
8971            json!({
8972                "locator": SessionLocator {
8973                    harness: HarnessId::from(HarnessId::CODEX),
8974                    session_id: "large-display".into(),
8975                    storage: StorageLocator::File { path: anchored },
8976                },
8977                "view": {
8978                    "tail_messages": 20,
8979                    "max_message_chars": 1024,
8980                    "include_subagents": false,
8981                    "display_history": true,
8982                },
8983            }),
8984        ));
8985        let records = short["result"]["session"]["raw_record_count"].as_u64();
8986        assert!(
8987            records.is_some_and(|records| records < 128),
8988            "{records:?} records read"
8989        );
8990        let short = short.to_string();
8991        assert!(short.contains("LATEST-MUST-LOAD"));
8992        assert!(short.contains("older native records remain outside this bounded display window"));
8993
8994        let _ = std::fs::remove_dir_all(&temp);
8995    }
8996
8997    #[test]
8998    fn forty_megabyte_goose_store_display_load_reads_only_the_tail() {
8999        let temp = std::env::temp_dir().join(format!(
9000            "supercode-large-goose-view-{}-{}",
9001            std::process::id(),
9002            generated_session_id()
9003        ));
9004        std::fs::create_dir_all(&temp).unwrap();
9005        let path = temp.join("sessions.db");
9006        let connection = rusqlite::Connection::open(&path).unwrap();
9007        connection
9008            .execute_batch(
9009                "CREATE TABLE sessions (
9010                    id TEXT PRIMARY KEY, name TEXT NOT NULL, working_dir TEXT NOT NULL,
9011                    created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
9012                    session_type TEXT NOT NULL, extension_data TEXT,
9013                    goose_mode TEXT NOT NULL, provider_name TEXT, model_config_json TEXT,
9014                    archived_at TEXT
9015                 );
9016                 CREATE TABLE messages (
9017                    id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, message_id TEXT,
9018                    role TEXT NOT NULL, content_json TEXT NOT NULL,
9019                    created_timestamp INTEGER NOT NULL, metadata_json TEXT
9020                 );",
9021            )
9022            .unwrap();
9023        connection
9024            .execute(
9025                "INSERT INTO sessions VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL)",
9026                rusqlite::params![
9027                    "goose-large",
9028                    "Large Goose session",
9029                    "/tmp",
9030                    "2026-01-01 00:00:00",
9031                    "2026-01-01 00:00:02",
9032                    "user",
9033                    "{}",
9034                    "auto",
9035                    "anthropic",
9036                    r#"{"model_name":"claude-sonnet"}"#,
9037                ],
9038            )
9039            .unwrap();
9040        let old_content = serde_json::to_string(&vec![json!({
9041            "type": "text",
9042            "text": format!("OLDEST-SHOULD-NOT-LOAD:{}", "x".repeat(40 * 1024 * 1024)),
9043        })])
9044        .unwrap();
9045        connection
9046            .execute(
9047                "INSERT INTO messages VALUES (1, ?1, 'old', 'user', ?2, 1, '{}')",
9048                rusqlite::params!["goose-large", old_content],
9049            )
9050            .unwrap();
9051        connection
9052            .execute(
9053                "INSERT INTO messages VALUES (2, ?1, 'new', 'assistant', ?2, 2, '{}')",
9054                rusqlite::params![
9055                    "goose-large",
9056                    r#"[{"type":"text","text":"LATEST-MUST-LOAD"}]"#
9057                ],
9058            )
9059            .unwrap();
9060        drop(connection);
9061        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
9062
9063        let locator = SessionLocator {
9064            harness: HarnessId::from(HarnessId::GOOSE),
9065            session_id: "goose-large".into(),
9066            storage: StorageLocator::Sqlite {
9067                path,
9068                selector: "goose-large".into(),
9069            },
9070        };
9071        let started = Instant::now();
9072        let response = HarnessSessionService::new().handle(request(
9073            1,
9074            "harness.v1.sessions.load",
9075            json!({
9076                "locator": locator,
9077                "view": {
9078                    "tail_messages": 1,
9079                    "max_message_chars": 1024,
9080                    "include_subagents": false,
9081                    "display_history": true,
9082                },
9083            }),
9084        ));
9085        let elapsed = started.elapsed();
9086        let wire = response.to_string();
9087        eprintln!(
9088            "bounded 40 MiB Goose display load: {elapsed:?}, {} response bytes",
9089            wire.len()
9090        );
9091        assert!(response.get("error").is_none(), "{response:#}");
9092        assert!(wire.contains("LATEST-MUST-LOAD"));
9093        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
9094        assert!(
9095            wire.len() < 64 * 1024,
9096            "bounded wire was {} bytes",
9097            wire.len()
9098        );
9099        assert!(
9100            elapsed.as_secs_f64() < 1.0,
9101            "bounded Goose load took {elapsed:?}"
9102        );
9103
9104        let _ = std::fs::remove_dir_all(&temp);
9105    }
9106
9107    #[test]
9108    fn display_view_keeps_codex_assistant_history_across_compaction() {
9109        let temp = std::env::temp_dir().join(format!(
9110            "supercode-codex-display-view-{}-{}",
9111            std::process::id(),
9112            generated_session_id()
9113        ));
9114        std::fs::create_dir_all(&temp).unwrap();
9115        let path = temp.join("rollout.jsonl");
9116        std::fs::write(
9117            &path,
9118            concat!(
9119                r#"{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"codex-display","cwd":"/tmp"}}"#,
9120                "\n",
9121                r#"{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]}}"#,
9122                "\n",
9123                r#"{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"old answer"}]}}"#,
9124                "\n",
9125                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"}]}}"#,
9126                "\n",
9127                r#"{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"new prompt"}]}}"#,
9128                "\n",
9129                r#"{"timestamp":"2026-01-01T00:00:05Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"new answer"}]}}"#,
9130                "\n",
9131            ),
9132        )
9133        .unwrap();
9134        let locator = SessionLocator {
9135            harness: HarnessId::from(HarnessId::CODEX),
9136            session_id: "codex-display".into(),
9137            storage: StorageLocator::File { path },
9138        };
9139        let mut service = HarnessSessionService::new();
9140
9141        let continuation = service.handle(request(
9142            1,
9143            "harness.v1.sessions.load",
9144            json!({"locator": locator}),
9145        ));
9146        let continuation_text = continuation["result"]["session"]["messages"].to_string();
9147        assert!(!continuation_text.contains("old answer"));
9148
9149        let display = service.handle(request(
9150            2,
9151            "harness.v1.sessions.load",
9152            json!({
9153                "locator": locator,
9154                "view": {
9155                    "tail_messages": 10,
9156                    "include_subagents": false,
9157                    "display_history": true,
9158                },
9159            }),
9160        ));
9161        let display_text = display["result"]["session"]["messages"].to_string();
9162        assert!(display_text.contains("old prompt"));
9163        assert!(display_text.contains("old answer"));
9164        assert!(display_text.contains("new prompt"));
9165        assert!(display_text.contains("new answer"));
9166
9167        let _ = std::fs::remove_dir_all(&temp);
9168    }
9169
9170    #[test]
9171    fn indexed_claude_windows_match_the_existing_wire_projection() {
9172        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9173            .join("tests/fixtures/claude_code_session.jsonl");
9174        let locator = SessionLocator {
9175            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9176            session_id: "fixture".into(),
9177            storage: StorageLocator::File { path },
9178        };
9179        let full = load_session(&locator).unwrap();
9180        for inline_media in [InlineMediaMode::Full, InlineMediaMode::Metadata] {
9181            for offset in [0, 1, full.messages.len(), usize::MAX] {
9182                for limit in [0, 1, 3, usize::MAX] {
9183                    let options = SessionLoadOptions {
9184                        include_subagents: Some(false),
9185                        inline_media,
9186                        message_offset: Some(offset),
9187                        message_limit: Some(limit),
9188                        ..Default::default()
9189                    };
9190                    let expected = projected_session_result(&full, &options);
9191                    assert_eq!(
9192                        indexed_claude_window(&locator, &options).unwrap().unwrap(),
9193                        expected
9194                    );
9195                }
9196            }
9197            for tail in [0, 1, 3, usize::MAX] {
9198                let options = SessionLoadOptions {
9199                    include_subagents: Some(false),
9200                    inline_media,
9201                    message_tail: Some(tail),
9202                    ..Default::default()
9203                };
9204                assert_eq!(
9205                    indexed_claude_window(&locator, &options).unwrap().unwrap(),
9206                    projected_session_result(&full, &options)
9207                );
9208            }
9209        }
9210    }
9211
9212    #[test]
9213    fn load_supports_bounded_windows_and_media_metadata() {
9214        let mut service = HarnessSessionService::new();
9215        let locator = pi_locator();
9216        let bounded = service.handle(request(
9217            1,
9218            "harness.v1.sessions.load",
9219            json!({
9220                "locator": locator,
9221                "options": {
9222                    "include_subagents": false,
9223                    "message_limit": 2,
9224                    "message_offset": 1
9225                }
9226            }),
9227        ));
9228        assert_eq!(bounded["result"]["window"]["offset"], 1);
9229        assert_eq!(bounded["result"]["window"]["returned"], 2);
9230        assert!(bounded["result"]["summary"]["first_message"].is_object());
9231        assert!(bounded["result"]["summary"]["last_message"].is_object());
9232        assert_eq!(
9233            bounded["result"]["session"]["messages"]
9234                .as_array()
9235                .unwrap()
9236                .len(),
9237            2
9238        );
9239        assert!(bounded["result"]["session"]["subagents"]
9240            .as_array()
9241            .unwrap()
9242            .is_empty());
9243
9244        let tail = service.handle(request(
9245            2,
9246            "harness.v1.sessions.load",
9247            json!({"locator": locator, "options": {"message_tail": 1}}),
9248        ));
9249        assert_eq!(tail["result"]["window"]["returned"], 1);
9250        assert_eq!(tail["result"]["window"]["has_more"], true);
9251        assert_eq!(tail["result"]["window"]["has_older"], true);
9252        assert!(tail["result"]["window"]["older_items"].as_u64().unwrap() > 0);
9253        assert!(tail["result"]["summary"]["first_message"].is_object());
9254
9255        let metadata_only = service.handle(request(
9256            3,
9257            "harness.v1.sessions.load",
9258            json!({"locator": locator, "options": {"inline_media": "metadata"}}),
9259        ));
9260        assert!(metadata_only["result"]["session"]
9261            .to_string()
9262            .contains("media_reference"));
9263        assert!(!metadata_only["result"]["session"]
9264            .to_string()
9265            .contains("data:image/"));
9266    }
9267
9268    #[test]
9269    fn import_translate_branch_and_handoff_use_typed_artifacts() {
9270        let mut service = HarnessSessionService::new();
9271        let locator = pi_locator();
9272        let translated = service.handle(request(
9273            1,
9274            "harness.v1.sessions.translate",
9275            json!({"locator": locator, "target_harness": "grok"}),
9276        ));
9277        assert_eq!(translated["result"]["artifact"]["source_harness"], "pi");
9278        assert_eq!(translated["result"]["artifact"]["target_harness"], "grok");
9279        assert!(translated["result"]["artifact"]["content"]
9280            .as_str()
9281            .is_some_and(|content| !content.is_empty()));
9282
9283        for target in ["opencode", "open-code"] {
9284            let opencode = service.handle(request(
9285                6,
9286                "harness.v1.sessions.translate",
9287                json!({"locator": locator, "target_harness": target}),
9288            ));
9289            assert_eq!(opencode["result"]["artifact"]["target_harness"], "opencode");
9290        }
9291        let goose = service.handle(request(
9292            7,
9293            "harness.v1.sessions.translate",
9294            json!({"locator": locator, "target_harness": "goose"}),
9295        ));
9296        assert_eq!(goose["result"]["artifact"]["target_harness"], "goose");
9297        assert!(serde_json::from_str::<Value>(
9298            goose["result"]["artifact"]["content"].as_str().unwrap()
9299        )
9300        .unwrap()["conversation"]
9301            .is_array());
9302
9303        let imported = service.handle(request(
9304            2,
9305            "harness.v1.sessions.import",
9306            json!({
9307                "source_harness": "grok",
9308                "content": translated["result"]["artifact"]["content"],
9309            }),
9310        ));
9311        assert_eq!(imported["result"]["session"]["source"], "grok");
9312
9313        let branched = service.handle(request(
9314            3,
9315            "harness.v1.sessions.branch",
9316            json!({"locator": locator, "target_harness": "codex"}),
9317        ));
9318        assert_eq!(branched["result"]["parent"]["harness"], "pi");
9319        assert!(branched["result"]["bootstrap_prompt"]
9320            .as_str()
9321            .unwrap()
9322            .contains("frozen parent transcript"));
9323        assert_eq!(branched["result"]["artifact"]["target_harness"], "codex");
9324
9325        let handoff = service.handle(request(
9326            4,
9327            "harness.v1.sessions.handoff",
9328            json!({"locator": locator, "target_harness": "pi", "cwd": "/tmp/project"}),
9329        ));
9330        assert_eq!(handoff["result"]["launch"]["program"], "pi");
9331        assert_eq!(handoff["result"]["launch"]["cwd"], "/tmp/project");
9332        assert_eq!(handoff["result"]["requires_materialization"], true);
9333
9334        let goose_handoff = service.handle(request(
9335            8,
9336            "harness.v1.sessions.handoff",
9337            json!({"locator": locator, "target_harness": "goose", "cwd": "/tmp/project"}),
9338        ));
9339        assert_eq!(goose_handoff["result"]["launch"]["program"], "goose");
9340        assert_eq!(
9341            goose_handoff["result"]["materialize"]["arguments"],
9342            json!(["session", "import", "{artifact_path}"])
9343        );
9344
9345        let resumed = service.handle(request(
9346            5,
9347            "harness.v1.sessions.resume_instructions",
9348            json!({"locator": locator, "cwd": "/tmp/project", "policy": "yolo"}),
9349        ));
9350        assert_eq!(resumed["result"]["launch"]["program"], "pi");
9351        assert_eq!(resumed["result"]["launch"]["arguments"][0], "--approve");
9352    }
9353
9354    #[test]
9355    fn reduce_persists_and_reloads_a_byte_exact_reversible_bundle() {
9356        let temp = std::env::temp_dir().join(format!(
9357            "supercode-service-reduce-{}-{}",
9358            std::process::id(),
9359            generated_session_id()
9360        ));
9361        let source_path = temp.join("source.jsonl");
9362        let store_root = temp.join("store");
9363        std::fs::create_dir_all(&temp).unwrap();
9364
9365        let mut records = vec![json!({
9366            "timestamp": "2026-01-01T00:00:00Z",
9367            "type": "session_meta",
9368            "payload": {"id": "codex-reduce", "cwd": "/tmp/project"},
9369        })];
9370        for turn in 0..16 {
9371            records.push(json!({
9372                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 1),
9373                "type": "response_item",
9374                "payload": {
9375                    "type": "message",
9376                    "role": "user",
9377                    "content": [{
9378                        "type": "input_text",
9379                        "text": format!("request {turn}: {}", "context ".repeat(80)),
9380                    }],
9381                },
9382            }));
9383            records.push(json!({
9384                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 2),
9385                "type": "response_item",
9386                "payload": {
9387                    "type": "message",
9388                    "role": "assistant",
9389                    "content": [{
9390                        "type": "output_text",
9391                        "text": format!("answer {turn}: {}", "implementation detail ".repeat(80)),
9392                    }],
9393                },
9394            }));
9395        }
9396        let source = format!(
9397            "{}\n",
9398            records
9399                .iter()
9400                .map(Value::to_string)
9401                .collect::<Vec<_>>()
9402                .join("\n")
9403        );
9404        std::fs::write(&source_path, &source).unwrap();
9405        let locator = SessionLocator {
9406            harness: HarnessId::from(HarnessId::CODEX),
9407            session_id: "codex-reduce".into(),
9408            storage: StorageLocator::File {
9409                path: source_path.clone(),
9410            },
9411        };
9412        let original = load_session(&locator).unwrap();
9413        let mut service =
9414            HarnessSessionService::new().with_reduction_store_root(store_root.clone());
9415
9416        let response = service.handle(request(
9417            1,
9418            "harness.v1.sessions.reduce",
9419            json!({
9420                "locator": locator,
9421                "target_harness": "claude-code",
9422                "keep_last": 4,
9423            }),
9424        ));
9425        assert!(response.get("error").is_none(), "{response:#}");
9426        let receipt = &response["result"]["receipt"];
9427        assert_eq!(receipt["source_harness"], "codex");
9428        assert_eq!(receipt["target_harness"], "claude-code");
9429        assert_eq!(receipt["verified"], true);
9430        assert_eq!(receipt["reversible"], true);
9431        assert!(receipt["reductions"].as_u64().unwrap() > 0);
9432        assert!(
9433            receipt["source_tokens"].as_u64().unwrap()
9434                > receipt["reduced_tokens"].as_u64().unwrap()
9435        );
9436        assert!(receipt["ratio"].as_f64().unwrap() > 1.0);
9437        assert!(response["result"]["bootstrap_prompt"]
9438            .as_str()
9439            .unwrap()
9440            .contains("Do not guess hidden content"));
9441
9442        let rescue_id = receipt["id"].as_str().unwrap();
9443        let store = crate::SessionStore::open(&store_root).unwrap();
9444        let sidecar =
9445            Session::from_sidecar_str(&store.load_sidecar(rescue_id).unwrap().unwrap()).unwrap();
9446        let log = store.load_reduction_log(rescue_id).unwrap().unwrap();
9447        let persisted_view = parse_messages_jsonl(&store.load(rescue_id).unwrap()).unwrap();
9448        let policy = reduce::ReductionPolicy {
9449            clear_turns_older_than: Some(4),
9450            ..Default::default()
9451        };
9452        let (restamped_view, reapplied_log) =
9453            reduce::project_messages(&sidecar.messages, &policy, &log);
9454        assert_eq!(
9455            messages_jsonl(&persisted_view).unwrap(),
9456            messages_jsonl(&restamped_view).unwrap()
9457        );
9458        assert_eq!(reapplied_log, log);
9459        reduce::verify_log(&log, &sidecar).unwrap();
9460        assert_eq!(
9461            reduce::invert(&restamped_view, &log, &sidecar).unwrap(),
9462            original.messages
9463        );
9464        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), source);
9465
9466        std::fs::remove_dir_all(temp).ok();
9467    }
9468
9469    #[test]
9470    fn read_surfaces_view_a_severed_claude_graph_while_transfer_still_refuses_it() {
9471        let temp = std::env::temp_dir().join(format!(
9472            "supercode-severed-view-{}-{}",
9473            std::process::id(),
9474            generated_session_id()
9475        ));
9476        std::fs::create_dir_all(&temp).unwrap();
9477        let path = temp.join("severed.jsonl");
9478        // A live record whose parent was pruned — what a compacted or
9479        // resumed-across-files Claude Code session looks like on disk.
9480        std::fs::write(
9481            &path,
9482            concat!(
9483                r#"{"type":"user","uuid":"orphan-u","parentUuid":null,"message":{"role":"user","content":"stranded prompt"}}"#,
9484                "\n",
9485                r#"{"type":"assistant","uuid":"live-a","parentUuid":"pruned","message":{"id":"m","role":"assistant","content":[{"type":"text","text":"live answer"}]}}"#,
9486                "\n",
9487            ),
9488        )
9489        .unwrap();
9490        let locator = SessionLocator {
9491            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9492            session_id: "severed".into(),
9493            storage: StorageLocator::File { path },
9494        };
9495        let mut service = HarnessSessionService::new();
9496
9497        let viewed = service.handle(request(
9498            1,
9499            "harness.v1.sessions.load",
9500            json!({"locator": locator}),
9501        ));
9502        let session = &viewed["result"]["session"];
9503        assert_eq!(session["fidelity"], "semantic");
9504        assert_eq!(session["messages"].as_array().unwrap().len(), 2);
9505        assert!(session["residue"].as_array().unwrap().iter().any(|entry| {
9506            entry
9507                .as_str()
9508                .is_some_and(|entry| entry.contains("live-a") && entry.contains("pruned"))
9509        }));
9510
9511        // Asking a READ surface for a lossless reconstruction gets the strict
9512        // refusal back, unchanged.
9513        let strict = service.handle(request(
9514            2,
9515            "harness.v1.sessions.load",
9516            json!({"locator": locator, "fidelity": "byte_lossless"}),
9517        ));
9518        assert!(strict["error"]["message"]
9519            .as_str()
9520            .unwrap()
9521            .contains("cannot reconstruct lossless Claude continuation"));
9522
9523        // Transfer/continuation surfaces have no view mode at all.
9524        let translated = service.handle(request(
9525            3,
9526            "harness.v1.sessions.translate",
9527            json!({"locator": locator, "target_harness": "codex"}),
9528        ));
9529        assert!(translated["error"]["message"]
9530            .as_str()
9531            .unwrap()
9532            .contains("cannot reconstruct lossless Claude continuation"));
9533        let resumed = service.handle(request(
9534            4,
9535            "harness.v1.sessions.resume_instructions",
9536            json!({"locator": locator}),
9537        ));
9538        assert!(resumed["error"]["message"]
9539            .as_str()
9540            .unwrap()
9541            .contains("cannot reconstruct lossless Claude continuation"));
9542
9543        let _ = std::fs::remove_dir_all(&temp);
9544    }
9545
9546    #[test]
9547    fn structured_resume_launches_cover_gemini_goose_and_supercode() {
9548        let codex = resume_launch(
9549            HarnessId::CODEX,
9550            "codex-session",
9551            Path::new("/tmp/project"),
9552            ResumePolicy::Yolo,
9553        )
9554        .unwrap_or_else(|_| panic!("Codex resume launch must be registered"));
9555        assert_eq!(codex.program, "codex");
9556        assert_eq!(
9557            codex.arguments,
9558            [
9559                "-c",
9560                "check_for_update_on_startup=false",
9561                "-c",
9562                "projects={\"/tmp/project\"={trust_level=\"trusted\"}}",
9563                "--dangerously-bypass-approvals-and-sandbox",
9564                "--dangerously-bypass-hook-trust",
9565                "resume",
9566                "codex-session",
9567            ]
9568        );
9569
9570        let gemini = resume_launch(
9571            HarnessId::GEMINI,
9572            "gemini-session",
9573            Path::new("/tmp/project"),
9574            ResumePolicy::Yolo,
9575        )
9576        .unwrap_or_else(|_| panic!("Gemini resume launch must be registered"));
9577        assert_eq!(gemini.program, "gemini");
9578        assert_eq!(gemini.arguments, ["--yolo", "--resume", "gemini-session"]);
9579
9580        let goose = resume_launch(
9581            HarnessId::GOOSE,
9582            "goose-session",
9583            Path::new("/tmp/project"),
9584            ResumePolicy::Yolo,
9585        )
9586        .unwrap_or_else(|_| panic!("Goose resume launch must be registered"));
9587        assert_eq!(goose.program, "goose");
9588        assert_eq!(
9589            goose.arguments,
9590            ["session", "--resume", "--session-id", "goose-session"]
9591        );
9592
9593        let supercode = resume_launch(
9594            HarnessId::SUPERCODE,
9595            "supercode-session",
9596            Path::new("/tmp/project"),
9597            ResumePolicy::Yolo,
9598        )
9599        .unwrap_or_else(|_| panic!("Volter Harness resume launch must be registered"));
9600        assert_eq!(supercode.program, "supercode");
9601        assert_eq!(
9602            supercode.arguments,
9603            ["--dangerous", "resume", "supercode-session"]
9604        );
9605    }
9606
9607    #[test]
9608    fn diagonal_artifacts_preserve_claude_subagents_and_grok_bundle_members() {
9609        let temp = std::env::temp_dir().join(format!(
9610            "supercode-harness-artifact-{}-{}",
9611            std::process::id(),
9612            generated_session_id()
9613        ));
9614        let main_path = temp.join("parent.jsonl");
9615        let subagent_path = temp.join("parent/subagents/agent-child.jsonl");
9616        std::fs::create_dir_all(subagent_path.parent().unwrap()).unwrap();
9617        let fixture = std::fs::read_to_string(
9618            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9619                .join("tests/fixtures/claude_code_session.jsonl"),
9620        )
9621        .unwrap();
9622        let parent = fixture.trim_end_matches('\n');
9623        let child = fixture.trim_end_matches('\n');
9624        std::fs::write(&main_path, parent).unwrap();
9625        std::fs::write(&subagent_path, child).unwrap();
9626        let locator = SessionLocator {
9627            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9628            session_id: "213bb148-51ea-453f-9206-f8b4b1168547".into(),
9629            storage: StorageLocator::File {
9630                path: main_path.clone(),
9631            },
9632        };
9633        let mut service = HarnessSessionService::new();
9634        let claude = service.handle(request(
9635            1,
9636            "harness.v1.sessions.translate",
9637            json!({"locator": locator, "target_harness": "claude-code"}),
9638        ));
9639        let artifact = &claude["result"]["artifact"];
9640        assert_eq!(artifact["fidelity"], "byte_lossless");
9641        assert_eq!(artifact["content"], parent);
9642        let files = artifact["files"].as_array().unwrap();
9643        assert!(files.iter().any(|file| {
9644            file["role"] == "subagent"
9645                && file["path"]
9646                    .as_str()
9647                    .is_some_and(|path| path.ends_with("/subagents/agent-child.jsonl"))
9648                && file["content"] == child
9649        }));
9650        assert!(!artifact["content"].as_str().unwrap().ends_with('\n'));
9651
9652        let grok = service.handle(request(
9653            2,
9654            "harness.v1.sessions.translate",
9655            json!({"locator": grok_locator(), "target_harness": "grok"}),
9656        ));
9657        let files = grok["result"]["artifact"]["files"].as_array().unwrap();
9658        for name in ["summary.json", "updates.jsonl"] {
9659            let expected = std::fs::read_to_string(
9660                PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9661                    .join("tests/fixtures/grok_session")
9662                    .join(name),
9663            )
9664            .unwrap();
9665            assert!(files.iter().any(|file| {
9666                file["path"] == name && file["role"] == "bundle" && file["content"] == expected
9667            }));
9668        }
9669        std::fs::remove_dir_all(temp).ok();
9670    }
9671
9672    #[test]
9673    fn every_non_grok_handoff_mints_and_uses_a_fresh_target_identity() {
9674        let mut service = HarnessSessionService::new();
9675        let source = pi_locator();
9676        for (target, format) in [
9677            ("claude-code", SessionFormat::ClaudeCode),
9678            ("codex", SessionFormat::Codex),
9679            ("opencode", SessionFormat::OpenCode),
9680            ("pi", SessionFormat::Pi),
9681        ] {
9682            let result = service.handle(request(
9683                1,
9684                "harness.v1.sessions.handoff",
9685                json!({"locator": source, "target_harness": target, "cwd": "/tmp/project"}),
9686            ));
9687            let artifact = &result["result"]["artifact"];
9688            let target_id = artifact["session_id"].as_str().unwrap();
9689            assert_ne!(target_id, source.session_id, "{target}");
9690            let parsed = Session::load_str(artifact["content"].as_str().unwrap(), format).unwrap();
9691            assert_eq!(
9692                parsed.meta.session_id.as_deref(),
9693                Some(target_id),
9694                "{target}"
9695            );
9696            if target != "pi" {
9697                assert!(result["result"]["launch"]["arguments"]
9698                    .as_array()
9699                    .unwrap()
9700                    .iter()
9701                    .any(|argument| argument == target_id));
9702            }
9703            if target == "opencode" {
9704                assert!(target_id.starts_with("ses_"));
9705                fn assert_session_ids(value: &Value, target_id: &str) {
9706                    match value {
9707                        Value::Object(fields) => {
9708                            if let Some(session_id) = fields.get("sessionID") {
9709                                assert_eq!(session_id, target_id);
9710                            }
9711                            for child in fields.values() {
9712                                assert_session_ids(child, target_id);
9713                            }
9714                        }
9715                        Value::Array(values) => {
9716                            for child in values {
9717                                assert_session_ids(child, target_id);
9718                            }
9719                        }
9720                        _ => {}
9721                    }
9722                }
9723                let document: Value =
9724                    serde_json::from_str(artifact["content"].as_str().unwrap()).unwrap();
9725                assert_session_ids(&document, target_id);
9726            }
9727        }
9728
9729        let first = service.handle(request(
9730            2,
9731            "harness.v1.sessions.handoff",
9732            json!({"locator": source, "target_harness": "codex"}),
9733        ));
9734        let second = service.handle(request(
9735            3,
9736            "harness.v1.sessions.handoff",
9737            json!({"locator": source, "target_harness": "codex"}),
9738        ));
9739        assert_ne!(
9740            first["result"]["artifact"]["session_id"],
9741            second["result"]["artifact"]["session_id"]
9742        );
9743    }
9744
9745    #[test]
9746    fn grok_handoff_materializes_through_the_core_door() {
9747        let mut service = HarnessSessionService::new();
9748        let source = opencode_locator();
9749        let response = service.handle(request(
9750            1,
9751            "harness.v1.sessions.handoff",
9752            json!({
9753                "locator": source,
9754                "target_harness": "grok",
9755                "cwd": "/tmp/grok-handoff-project",
9756            }),
9757        ));
9758        let result = &response["result"];
9759
9760        // Grok has no import command: the artifact is Grok's own transcript under a fresh
9761        // identity, and `harness.v1.sessions.materialize` writes its store entry.
9762        assert_eq!(result["artifact"]["target_harness"], "grok");
9763        let artifact = Session::load_str(
9764            result["artifact"]["content"].as_str().unwrap(),
9765            SessionFormat::Grok,
9766        )
9767        .unwrap();
9768        assert!(!artifact.messages.is_empty());
9769        let target_session_id = result["artifact"]["session_id"].as_str().unwrap();
9770        assert_eq!(target_session_id.len(), 36);
9771        assert_ne!(target_session_id, opencode_locator().session_id);
9772        assert!(result["materialize"].is_null());
9773        assert_eq!(
9774            result["launch"]["arguments"],
9775            json!(["--resume", "{materialized_session_id}"])
9776        );
9777        assert!(result["note"]
9778            .as_str()
9779            .unwrap()
9780            .contains("harness.v1.sessions.materialize"));
9781    }
9782
9783    #[tokio::test]
9784    async fn inventory_rejects_unknown_harnesses_and_runtime_attach_is_honest() {
9785        let mut service = HarnessSessionService::new();
9786        let inventory = service
9787            .handle_async(request(
9788                1,
9789                "harness.v1.harnesses.list",
9790                json!({"harnesses": ["missing"]}),
9791            ))
9792            .await;
9793        assert_eq!(inventory["error"]["code"], -32602);
9794
9795        let attached = service
9796            .handle_async(request(
9797                2,
9798                "harness.v1.runtimes.attach_existing",
9799                json!({"harness": "codex", "runtime_id": "thread-1"}),
9800            ))
9801            .await;
9802        assert_eq!(attached["error"]["code"], -32000);
9803        assert!(attached["error"]["message"]
9804            .as_str()
9805            .unwrap()
9806            .contains("runtimes.resume"));
9807    }
9808
9809    #[test]
9810    fn invalid_params_and_unknown_methods_use_json_rpc_errors() {
9811        let mut service = HarnessSessionService::new();
9812        let invalid = service.handle(request(1, "harness.v1.sessions.load", json!({})));
9813        assert_eq!(invalid["error"]["code"], -32602);
9814        let unknown = service.handle(request(2, "harness.v1.unknown", json!({})));
9815        assert_eq!(unknown["error"]["code"], -32601);
9816    }
9817
9818    #[cfg(unix)]
9819    #[tokio::test]
9820    // The test mutates process-wide harness environment and deliberately
9821    // holds the global test lock until every async runtime operation ends.
9822    #[allow(clippy::await_holding_lock)]
9823    async fn async_service_drives_a_generic_acp_runtime() {
9824        let _environment_guard = crate::live_runtime::test_environment_lock();
9825        let script = r#"
9826            i=0
9827            while IFS= read -r line; do
9828              i=$((i + 1))
9829              case "$i" in
9830                1) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{},"authMethods":[]}}' ;;
9831                2) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"svc_acp"}}' ;;
9832                3)
9833                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ok"}}}}'
9834                  printf '%s\n' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}'
9835                  ;;
9836                4)
9837                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"from terminal"}}}}'
9838                  printf '%s\n' '{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}'
9839                  ;;
9840              esac
9841            done
9842        "#;
9843        let mut service = HarnessSessionService::new();
9844        let started = service
9845            .handle_async(request(
9846                1,
9847                "harness.v1.runtimes.start",
9848                json!({
9849                    "harness": "codex",
9850                    "protocol": "acp",
9851                    "cwd": std::env::current_dir().unwrap(),
9852                    "launch": {"program": "/bin/sh", "arguments": ["-c", script], "env": {}},
9853                }),
9854            ))
9855            .await;
9856        assert_eq!(started["result"]["connection"], "runtime-1");
9857        assert_eq!(started["result"]["handle"]["runtime_id"], "svc_acp");
9858
9859        let terminal = service
9860            .handle_async(request(
9861                9,
9862                "harness.v1.runtimes.terminal_instructions",
9863                json!({"connection":"runtime-1"}),
9864            ))
9865            .await;
9866        let arguments = terminal["result"]["launch"]["arguments"]
9867            .as_array()
9868            .expect("hosted runtime should return terminal arguments");
9869        let endpoint_index = arguments
9870            .iter()
9871            .position(|value| value == "--endpoint")
9872            .expect("terminal command should use an opaque endpoint");
9873        let endpoint = LiveRuntimeEndpoint::parse(
9874            arguments[endpoint_index + 1]
9875                .as_str()
9876                .expect("endpoint argument should be text"),
9877        )
9878        .unwrap();
9879        assert!(!terminal.to_string().contains("Bearer"));
9880        let workspace = std::env::current_dir().unwrap();
9881        let receipt = resolve_live_runtime(
9882            &endpoint,
9883            &LiveRuntimeSource {
9884                harness: "codex".into(),
9885                session_id: "svc_acp".into(),
9886                workspace,
9887            },
9888        )
9889        .unwrap();
9890        let remote = crate::HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
9891            .await
9892            .unwrap();
9893        let mut attachment = crate::FrontendRuntime::attach(remote.as_ref(), 100)
9894            .await
9895            .unwrap();
9896
9897        let sent = service
9898            .handle_async(request(
9899                2,
9900                "harness.v1.runtimes.send_input",
9901                json!({"connection": "runtime-1", "text": "hi"}),
9902            ))
9903            .await;
9904        assert_eq!(sent["result"]["turn_id"], "3");
9905
9906        let mut events = Vec::new();
9907        for _ in 0..20 {
9908            events.extend(service.poll_runtimes().await);
9909            if events.len() >= 2 {
9910                break;
9911            }
9912            tokio::time::sleep(Duration::from_millis(2)).await;
9913        }
9914        assert!(events
9915            .iter()
9916            .any(|event| { event["params"]["event"]["kind"] == "session/update" }));
9917        assert!(events.iter().any(|event| {
9918            event["params"]["event"]["kind"] == "supercode/acp_request_completed"
9919        }));
9920
9921        let saw_editor_reply = tokio::time::timeout(Duration::from_secs(2), async {
9922            loop {
9923                let event = attachment.next_event().await.unwrap();
9924                if event.kind == "text_delta" && event.payload["text"] == "ok" {
9925                    break;
9926                }
9927            }
9928        })
9929        .await;
9930        assert!(
9931            saw_editor_reply.is_ok(),
9932            "terminal should observe the editor-driven turn"
9933        );
9934
9935        crate::FrontendRuntime::submit(remote.as_ref(), "DRIVE FROM TERMINAL".into())
9936            .await
9937            .unwrap();
9938        let saw_terminal_reply = tokio::time::timeout(Duration::from_secs(2), async {
9939            loop {
9940                let event = attachment.next_event().await.unwrap();
9941                if event.kind == "text_delta" && event.payload["text"] == "from terminal" {
9942                    break;
9943                }
9944            }
9945        })
9946        .await;
9947        assert!(
9948            saw_terminal_reply.is_ok(),
9949            "terminal should drive the same runtime"
9950        );
9951
9952        let closed = service
9953            .handle_async(request(
9954                3,
9955                "harness.v1.runtimes.close",
9956                json!({"connection": "runtime-1"}),
9957            ))
9958            .await;
9959        assert_eq!(closed["result"]["closed"], true);
9960    }
9961
9962    /// UNI-7 dev/02: a RUNNING mock gateway is detected through the real
9963    /// openclaw probe (config-declared endpoint, TCP connect), and an ACTIVE
9964    /// hermes WAL is detected through the real WAL-freshness probe; the
9965    /// negative sides (no listener, stale WAL, no config) stay undetected.
9966    #[test]
9967    fn running_instances_are_detected_from_mock_gateway_and_active_wal() {
9968        let home = connect_scratch_home("uni7-running");
9969
9970        // No config at all: hermes has no default endpoint, so no detection.
9971        // (openclaw's no-config behavior now probes its DOCUMENTED default
9972        // endpoint ws://127.0.0.1:18789 — see the connect launch's
9973        // `default_address` — which is real box state a hermetic test must
9974        // not assert either way; the closed-port negative below covers the
9975        // no-listener side deterministically.)
9976        assert!(probe_hermes_running(&home, 300_000).is_none());
9977
9978        // Mock gateway: a real TCP listener on an ephemeral port, declared in
9979        // the harness's own config file.
9980        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9981        let port = listener.local_addr().unwrap().port();
9982        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9983        std::fs::write(
9984            home.join(".openclaw/openclaw.json"),
9985            format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9986        )
9987        .unwrap();
9988        let running = probe_openclaw_running(&home).expect("listening gateway must be detected");
9989        assert!(matches!(
9990            running.method,
9991            RunningInstanceMethod::GatewayConnect
9992        ));
9993        assert!(running.evidence.contains(&format!("127.0.0.1:{port}")));
9994        drop(listener);
9995        // Parallel tests also bind ephemeral loopback ports, so a just-freed
9996        // port can be re-bound by a NEIGHBORING test between drop and probe.
9997        // Detection on a closed port must fail — retry on a fresh port when
9998        // the freed one was recycled by someone else.
9999        let mut closed_detected = probe_openclaw_running(&home).is_some();
10000        for _ in 0..3 {
10001            if !closed_detected {
10002                break;
10003            }
10004            let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
10005            let port = listener.local_addr().unwrap().port();
10006            drop(listener);
10007            std::fs::write(
10008                home.join(".openclaw/openclaw.json"),
10009                format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
10010            )
10011            .unwrap();
10012            closed_detected = probe_openclaw_running(&home).is_some();
10013        }
10014        assert!(
10015            !closed_detected,
10016            "a closed gateway must not read as running"
10017        );
10018
10019        // gateway.url form takes precedence over port.
10020        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
10021        let port = listener.local_addr().unwrap().port();
10022        std::fs::write(
10023            home.join(".openclaw/openclaw.json"),
10024            format!(r#"{{"gateway": {{"url": "ws://127.0.0.1:{port}", "auth": {{"mode": "token", "token": "t"}}}}}}"#),
10025        )
10026        .unwrap();
10027        assert!(probe_openclaw_running(&home).is_some());
10028        drop(listener);
10029
10030        // Hermes: an ACTIVE WAL (fresh stamp) is detected; a stale one is not.
10031        std::fs::create_dir_all(home.join(".hermes")).unwrap();
10032        let wal = home.join(".hermes/state.db-wal");
10033        std::fs::write(&wal, b"wal").unwrap();
10034        let running = probe_hermes_running(&home, 300_000).expect("fresh WAL must be detected");
10035        assert!(matches!(
10036            running.method,
10037            RunningInstanceMethod::StoreWalActivity
10038        ));
10039        assert!(running.evidence.contains("state.db-wal"));
10040        let stale = std::time::SystemTime::now() - std::time::Duration::from_secs(3_600);
10041        std::fs::File::options()
10042            .append(true)
10043            .open(&wal)
10044            .unwrap()
10045            .set_modified(stale)
10046            .unwrap();
10047        assert!(
10048            probe_hermes_running(&home, 300_000).is_none(),
10049            "a stale WAL (crash leftover) must not read as running"
10050        );
10051    }
10052
10053    fn connect_scratch_home(tag: &str) -> PathBuf {
10054        let dir = std::env::temp_dir().join(format!(
10055            "supercode-connect-service-{tag}-{}-{}",
10056            std::process::id(),
10057            std::time::SystemTime::now()
10058                .duration_since(std::time::UNIX_EPOCH)
10059                .unwrap()
10060                .as_nanos()
10061        ));
10062        std::fs::create_dir_all(&dir).unwrap();
10063        dir
10064    }
10065
10066    /// Minimal HTTP responder that speaks just enough OpenCode server to
10067    /// accept a health check, create a session, and hold an SSE stream open,
10068    /// while recording each request line with its Authorization header.
10069    async fn mock_opencode_endpoint() -> (String, tokio::sync::mpsc::UnboundedReceiver<String>) {
10070        use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
10071        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
10072        let address = listener.local_addr().unwrap();
10073        let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel();
10074        tokio::spawn(async move {
10075            loop {
10076                let Ok((mut stream, _)) = listener.accept().await else {
10077                    break;
10078                };
10079                let request_sender = request_sender.clone();
10080                tokio::spawn(async move {
10081                    let (reader, mut writer) = stream.split();
10082                    let mut reader = BufReader::new(reader);
10083                    let mut request_line = String::new();
10084                    if reader.read_line(&mut request_line).await.unwrap_or(0) == 0 {
10085                        return;
10086                    }
10087                    let request_line = request_line.trim_end().to_string();
10088                    let mut authorization = String::new();
10089                    let mut content_length = 0usize;
10090                    loop {
10091                        let mut line = String::new();
10092                        if reader.read_line(&mut line).await.unwrap_or(0) == 0 {
10093                            return;
10094                        }
10095                        let line = line.trim_end();
10096                        if line.is_empty() {
10097                            break;
10098                        }
10099                        let lower = line.to_ascii_lowercase();
10100                        if let Some(value) = lower.strip_prefix("authorization:") {
10101                            authorization = value.trim().to_string();
10102                        }
10103                        if let Some(value) = lower.strip_prefix("content-length:") {
10104                            content_length = value.trim().parse().unwrap_or(0);
10105                        }
10106                    }
10107                    if content_length > 0 {
10108                        let mut body = vec![0u8; content_length];
10109                        let _ = reader.read_exact(&mut body).await;
10110                    }
10111                    let _ = request_sender.send(format!("{request_line} :: {authorization}"));
10112                    if request_line.starts_with("GET /event") {
10113                        let _ = writer
10114                            .write_all(
10115                                b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n",
10116                            )
10117                            .await;
10118                        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
10119                        return;
10120                    }
10121                    let body = if request_line.starts_with("POST /session") {
10122                        r#"{"id":"mock-session"}"#
10123                    } else {
10124                        r#"{"status":"ok"}"#
10125                    };
10126                    let response = format!(
10127                        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
10128                        body.len(),
10129                        body
10130                    );
10131                    let _ = writer.write_all(response.as_bytes()).await;
10132                });
10133            }
10134        });
10135        (format!("http://{address}"), request_receiver)
10136    }
10137
10138    fn connect_descriptor(protocol: &str) -> crate::HarnessSupportDescriptor {
10139        crate::HarnessSupportDescriptor {
10140            orchestration: Default::default(),
10141            id: HarnessId::from(HarnessId::OPENCODE),
10142            display_name: "OpenCode".into(),
10143            native: crate::NativeSupport {
10144                discover: crate::ImplementationKind::Absent,
10145                load: crate::ImplementationKind::Absent,
10146                follow: crate::ImplementationKind::Absent,
10147                import: crate::ImplementationKind::Absent,
10148                export: crate::ImplementationKind::Absent,
10149            },
10150            runtime: crate::RuntimeSupport {
10151                implementation: crate::ImplementationKind::BuiltIn,
10152                protocol: protocol.into(),
10153                default_launch: None,
10154                connect_launch: Some(crate::RuntimeConnectLaunch {
10155                    config_path: "~/opencode-tui.json".into(),
10156                    address_pointer: "/server/url".into(),
10157                    port_pointer: None,
10158                    default_address: None,
10159                    auth_pointer: Some("/server/token".into()),
10160                    protocol: protocol.into(),
10161                }),
10162                capabilities: crate::RuntimeCapabilities {
10163                    start_session: true,
10164                    resume_session: true,
10165                    attach_existing_process: true,
10166                    send_input: true,
10167                    stream_events: true,
10168                    interrupt: true,
10169                    steer: false,
10170                    respond_to_requests: true,
10171                },
10172            },
10173        }
10174    }
10175
10176    #[tokio::test]
10177    async fn connect_mode_descriptor_opens_a_running_endpoint_with_config_sourced_auth() {
10178        let (base_url, mut requests) = mock_opencode_endpoint().await;
10179        let home = connect_scratch_home("open");
10180        std::fs::write(
10181            home.join("opencode-tui.json"),
10182            format!(r#"{{"server": {{"url": "{base_url}", "token": "connect-secret"}}}}"#),
10183        )
10184        .unwrap();
10185
10186        let descriptor = connect_descriptor("opencode-http-sse");
10187        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
10188        assert!(backend.capabilities().attach_existing_process);
10189
10190        let connection = backend
10191            .start(crate::RuntimeStartRequest {
10192                cwd: home.clone(),
10193                launch: None,
10194                mcp_servers: Vec::new(),
10195                approval_policy: None,
10196            })
10197            .await
10198            .unwrap();
10199        let handle = connection.handle();
10200        assert_eq!(handle.runtime_id, "mock-session");
10201        match &handle.endpoint {
10202            crate::RuntimeEndpoint::Http {
10203                base_url: endpoint, ..
10204            } => assert_eq!(endpoint, &base_url),
10205            other => panic!("connect mode must join the running endpoint, got {other:?}"),
10206        }
10207
10208        let mut seen = Vec::new();
10209        while let Ok(line) = requests.try_recv() {
10210            seen.push(line);
10211        }
10212        assert!(seen
10213            .iter()
10214            .any(|line| line.starts_with("GET /global/health")
10215                && line.contains("bearer connect-secret")));
10216        assert!(seen.iter().any(
10217            |line| line.starts_with("POST /session") && line.contains("bearer connect-secret")
10218        ));
10219    }
10220
10221    /// UNI-5 dev/02, contract corrected by the 2026-08-31 blind walk: the
10222    /// full connect-mode attach path against a MOCK gateway bridge — no live
10223    /// gateway, no model spend. A scripted fake `openclaw` binary (a)
10224    /// asserts the REAL bridge contract — the resolved --url on argv and the
10225    /// credential via --token-file (the real bridge ignores the env var; the
10226    /// endpoint comes from openclaw-native `gateway.remote.url`, never the
10227    /// schema-invalid `gateway.url`) — then (b) speaks scripted ACP:
10228    /// initialize advertising sessionCapabilities.{list,resume},
10229    /// session/resume rebinding the requested session (join), and a
10230    /// prompted turn.
10231    #[cfg(unix)] // a `sh` mock bridge
10232    #[tokio::test]
10233    async fn openclaw_connect_mode_attaches_lists_and_resumes_via_a_mock_bridge() {
10234        let home = connect_scratch_home("openclaw");
10235        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
10236        std::fs::write(
10237            home.join(".openclaw/openclaw.json"),
10238            r#"{"gateway": {"remote": {"url": "ws://127.0.0.1:19789"}, "auth": {"mode": "token", "token": "mock-gateway-token"}}}"#,
10239        )
10240        .unwrap();
10241        let script = home.join("openclaw");
10242        std::fs::write(
10243            &script,
10244            r#"#!/bin/sh
10245# Fake `openclaw acp` bridge: verify the connect-mode contract, then speak ACP.
10246[ "$1" = "acp" ] || { echo "unexpected argv: $*" >&2; exit 9; }
10247[ "$2" = "--url" ] && [ "$3" = "ws://127.0.0.1:19789" ] || { echo "missing --url: $*" >&2; exit 9; }
10248[ "$4" = "--token-file" ] || { echo "missing --token-file: $*" >&2; exit 9; }
10249[ "$(cat "$5")" = "mock-gateway-token" ] || { echo "token file wrong" >&2; exit 9; }
10250while IFS= read -r line; do
10251  case "$line" in
10252    *'"initialize"'*)
10253      printf '%s
10254' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"sessionCapabilities":{"list":{},"resume":{}}},"agentInfo":{"name":"openclaw-acp","version":"2026.7.1-2"},"authMethods":[]}}' ;;
10255    *'"session/resume"'*)
10256      printf '%s
10257' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:main"}}' ;;
10258    *'"session/new"'*)
10259      printf '%s
10260' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:fresh"}}' ;;
10261    *'"session/prompt"'*)
10262      printf '%s
10263' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"agent:main:main","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"joined"}}}}'
10264      printf '%s
10265' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}' ;;
10266  esac
10267done
10268"#,
10269        )
10270        .unwrap();
10271        use std::os::unix::fs::PermissionsExt;
10272        std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
10273
10274        let mut descriptor = crate::harness_support_registry()
10275            .harnesses
10276            .into_iter()
10277            .find(|harness| harness.id.as_str() == HarnessId::OPENCLAW)
10278            .expect("openclaw must be registered");
10279        descriptor
10280            .runtime
10281            .connect_launch
10282            .as_mut()
10283            .unwrap()
10284            .config_path = "~/.openclaw/openclaw.json".into();
10285        descriptor.runtime.default_launch.as_mut().unwrap().program =
10286            script.to_string_lossy().into_owned();
10287        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
10288        assert!(backend.capabilities().resume_session);
10289
10290        let joined = backend
10291            .attach(crate::RuntimeAttachRequest {
10292                runtime_id: "agent:main:main".into(),
10293                cwd: Some(home.clone()),
10294                launch: None,
10295                mcp_servers: Vec::new(),
10296                approval_policy: None,
10297            })
10298            .await;
10299        let mut connection = joined.expect("mock bridge attach must succeed");
10300        assert_eq!(connection.handle().runtime_id, "agent:main:main");
10301        let turn = connection
10302            .send_input(crate::RuntimeInput {
10303                text: "hello".into(),
10304                image_urls: Vec::new(),
10305            })
10306            .await;
10307        assert!(turn.is_ok(), "prompt through the mock bridge: {turn:?}");
10308        connection.close().await.unwrap();
10309    }
10310
10311    #[tokio::test]
10312    async fn connect_mode_fails_closed_without_a_protocol_client_or_config() {
10313        let home = connect_scratch_home("fail");
10314        std::fs::write(
10315            home.join("opencode-tui.json"),
10316            r#"{"server": {"url": "http://127.0.0.1:1", "token": "connect-secret"}}"#,
10317        )
10318        .unwrap();
10319
10320        let gateway_only = connect_descriptor("acp-v1-jsonrpc");
10321        let Err(error) = open_connect_descriptor(&gateway_only, &home) else {
10322            panic!("an ACP connect endpoint has no gateway client yet");
10323        };
10324        let message = format!("{error:?}");
10325        assert!(message.contains("acp-v1-jsonrpc"));
10326        assert!(!message.contains("connect-secret"));
10327
10328        let unreadable = connect_descriptor("opencode-http-sse");
10329        let missing_home = connect_scratch_home("missing");
10330        let Err(error) = open_connect_descriptor(&unreadable, &missing_home) else {
10331            panic!("an unreadable connect config must fail closed");
10332        };
10333        let message = format!("{error:?}");
10334        assert!(message.contains("opencode-tui.json"));
10335        assert!(!message.contains("connect-secret"));
10336    }
10337
10338    // ---------------------------------------------------------------------
10339    // ORCH-7 — `harness.v1.jobs.list` / `jobs.get` over the committed fixtures
10340    // ---------------------------------------------------------------------
10341
10342    fn jobs_fixture_root() -> PathBuf {
10343        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
10344    }
10345
10346    /// Point only the three job-bearing homes at the fixtures. Nothing else is
10347    /// read, so the host machine's own harness homes cannot leak into a row.
10348    fn jobs_fixture_homes() -> Value {
10349        let root = jobs_fixture_root();
10350        json!({
10351            "claude_code": root.join("claude_jobs_home/projects"),
10352            "hermes": root.join("hermes_home/state.db"),
10353            "openclaw": root.join("openclaw_home"),
10354        })
10355    }
10356
10357    fn jobs_list(params: Value) -> Value {
10358        let mut service = HarnessSessionService::new();
10359        service.handle(request(1, "harness.v1.jobs.list", params))
10360    }
10361
10362    fn job_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10363        result["jobs"]
10364            .as_array()
10365            .expect("jobs is an array")
10366            .iter()
10367            .find(|job| job["id"] == id)
10368            .unwrap_or_else(|| panic!("no job `{id}` in {result}"))
10369    }
10370
10371    #[test]
10372    fn gateway_health_derives_from_running_probe_and_install_state() {
10373        let running = RunningInstance {
10374            method: RunningInstanceMethod::GatewayConnect,
10375            evidence: "gateway endpoint 127.0.0.1:18789 accepted a TCP connect".into(),
10376            checked_at_ms: 1,
10377        };
10378        let up = gateway_health(
10379            HarnessId::OPENCLAW,
10380            true,
10381            Some(&running),
10382            Some("2026.7.1-2"),
10383        );
10384        assert_eq!(up.state, GatewayState::Up);
10385        assert!(up.endpoint.as_deref().unwrap().starts_with("ws://"));
10386        assert_eq!(up.version.as_deref(), Some("2026.7.1-2"));
10387        // Hermes consults its own `gateway status` when the WAL heuristic says
10388        // nothing; a fake binary decides the verdict (the env var is global, so
10389        // the up/down cases run inside this one test, never in parallel).
10390        let dir = std::env::temp_dir().join(format!("supercode-orch17-{}", std::process::id()));
10391        std::fs::create_dir_all(&dir).unwrap();
10392        let fake = dir.join("hermes");
10393        let write_fake = |body: &str| {
10394            std::fs::write(&fake, format!("#!/bin/sh\n{body}\n")).unwrap();
10395            #[cfg(unix)]
10396            {
10397                use std::os::unix::fs::PermissionsExt;
10398                std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o755)).unwrap();
10399            }
10400        };
10401        write_fake("echo '✗ Gateway service is not installed'");
10402        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| {
10403            *slot.borrow_mut() = Some((
10404                HarnessId::HERMES.to_string(),
10405                fake.to_string_lossy().into_owned(),
10406            ))
10407        });
10408        let down = gateway_health(HarnessId::HERMES, true, None, None);
10409        assert_eq!(down.state, GatewayState::Down, "{down:?}");
10410        assert!(down.endpoint.is_none());
10411        assert!(down.evidence.contains("not installed"));
10412        write_fake("echo 'Launchd plist: /x/ai.hermes.gateway.plist'; echo '✓ Gateway is supervised by launchd (PID 4242)'");
10413        let idle_but_up = gateway_health(HarnessId::HERMES, true, None, Some("0.21.0"));
10414        assert_eq!(idle_but_up.state, GatewayState::Up, "{idle_but_up:?}");
10415        assert!(idle_but_up.evidence.contains("PID 4242"));
10416        write_fake("echo 'something unparseable'");
10417        let no_verdict = gateway_health(HarnessId::HERMES, true, None, None);
10418        assert_eq!(no_verdict.state, GatewayState::Down);
10419        assert!(no_verdict.evidence.contains("no verdict"));
10420        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| *slot.borrow_mut() = None);
10421        let absent = gateway_health(HarnessId::HERMES, false, None, None);
10422        assert_eq!(absent.state, GatewayState::Unknown);
10423        let core = gateway_health(HarnessId::CODEX, true, None, Some("0.144.4"));
10424        assert_eq!(core.state, GatewayState::Unknown);
10425        assert!(core.evidence.contains("per session"));
10426    }
10427
10428    #[test]
10429    fn triggers_list_reads_both_stores_and_never_emits_secrets() {
10430        let response = triggers_list(json!({"homes": jobs_fixture_homes()}));
10431        let rows = response["result"]["triggers"]
10432            .as_array()
10433            .expect("triggers")
10434            .clone();
10435        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10436        assert!(
10437            hermes.iter().any(|r| r["name"] == "deploys"
10438                && r["route"] == "/webhooks/deploys"
10439                && r["kind"] == "webhook"),
10440            "{rows:#?}"
10441        );
10442        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10443        assert!(openclaw
10444            .iter()
10445            .any(|r| r["name"] == "wake" && r["kind"] == "builtin_wake"));
10446        assert!(openclaw.iter().any(|r| r["name"] == "gmail"
10447            && r["kind"] == "hook_mapping"
10448            && r["target"]["action"] == "agent"));
10449        let rendered = response.to_string();
10450        for secret in [
10451            "FAKE-WEBHOOK-HMAC-DO-NOT-EMIT",
10452            "FAKE-HOOK-TOKEN-DO-NOT-EMIT",
10453        ] {
10454            assert!(!rendered.contains(secret), "{rendered}");
10455        }
10456        let refused =
10457            triggers_list(json!({"harness": "claude-code", "homes": jobs_fixture_homes()}));
10458        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10459    }
10460
10461    fn triggers_list(params: Value) -> Value {
10462        let mut service = HarnessSessionService::new();
10463        service.handle(request(1, "harness.v1.triggers.list", params))
10464    }
10465
10466    #[test]
10467    fn routes_list_reads_both_gateway_configs_and_flags_the_defaults() {
10468        let response = routes_list(json!({"homes": jobs_fixture_homes()}));
10469        let rows = response["result"]["routes"]
10470            .as_array()
10471            .expect("routes")
10472            .clone();
10473        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10474        assert_eq!(hermes.len(), 2, "{rows:#?}");
10475        assert_eq!(hermes[0]["target"], "coder");
10476        assert_eq!(hermes[0]["match"]["platform"], "slack");
10477        assert_eq!(hermes[0]["match"]["chat_id"], "C0FIXTURE");
10478        assert_eq!(hermes[0]["specificity"], 4);
10479        assert_eq!(hermes[1]["default"], true);
10480        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10481        assert!(
10482            openclaw.iter().any(|r| r["target"] == "design"
10483                && r["match"]["platform"] == "slack"
10484                && r["specificity"] == 1),
10485            "{openclaw:#?}"
10486        );
10487        assert!(openclaw.iter().any(|r| r["default"] == true));
10488        // A core harness has no routing concept and is refused, never an empty list.
10489        let refused = routes_list(json!({"harness": "codex", "homes": jobs_fixture_homes()}));
10490        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10491    }
10492
10493    fn routes_list(params: Value) -> Value {
10494        let mut service = HarnessSessionService::new();
10495        service.handle(request(1, "harness.v1.routes.list", params))
10496    }
10497
10498    #[test]
10499    fn jobs_list_projects_every_fixture_store_onto_the_uniform_row() {
10500        let response = jobs_list(json!({"homes": jobs_fixture_homes()}));
10501        let result = &response["result"];
10502        let ids: Vec<&str> = result["jobs"]
10503            .as_array()
10504            .unwrap()
10505            .iter()
10506            .map(|job| job["id"].as_str().unwrap())
10507            .collect();
10508        assert_eq!(
10509            ids,
10510            vec![
10511                "release-watch",
10512                "toolu_wake_recheck",
10513                "digest-15m",
10514                "nightly-audit",
10515                "coder-standup",
10516                "ops-once-boot",
10517                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10518                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10519                "cron_standup",
10520                "cron_reindex",
10521            ],
10522            "{result}"
10523        );
10524
10525        // OpenClaw, pinned shape: rows come from `state/openclaw.sqlite`
10526        // (`cron_jobs.job_json` + runtime columns), captured from a real
10527        // 2026.7.1-2 gateway.
10528        let health = job_row(result, "85ad7832-896f-42be-af31-3e1ed2fbdc4b");
10529        assert_eq!(health["harness"], "openclaw");
10530        assert_eq!(health["schedule"]["kind"], "interval");
10531        assert_eq!(health["schedule"]["minutes"], 10.0);
10532        assert_eq!(health["session_target"], "isolated");
10533        assert_eq!(health["payload"]["kind"], "prompt");
10534        assert_eq!(health["payload"]["text"], "nightly health check");
10535        // ORCH-13: the mode word (`announce`) and the channel it announces on
10536        // (`last`) are separate facts, and the store keeps both — in
10537        // `job_json.delivery` and in the `delivery_*` columns beside it.
10538        assert_eq!(health["deliver"]["mode"], "announce");
10539        assert_eq!(health["deliver"]["target"], "last");
10540        assert_eq!(health["next_run_at"], "2026-09-03T06:52:26Z");
10541        let digest = job_row(result, "8bb7d938-ca46-4a6d-90eb-c92331155566");
10542        assert_eq!(digest["schedule"]["kind"], "cron");
10543        assert_eq!(digest["schedule"]["expr"], "0 9 * * 1");
10544        assert_eq!(digest["session_target"], "main");
10545        assert_eq!(digest["payload"]["kind"], "system_event");
10546
10547        // Claude Code: session-scoped, one recurring cron and one one-shot wakeup.
10548        let cron = job_row(result, "release-watch");
10549        assert_eq!(cron["harness"], "claude-code");
10550        assert_eq!(cron["scope"], "session");
10551        assert_eq!(cron["session_id"], "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
10552        assert_eq!(cron["schedule"]["kind"], "cron");
10553        assert_eq!(cron["schedule"]["expr"], "*/10 * * * *");
10554        assert_eq!(cron["schedule"]["display"], "*/10 * * * *");
10555        assert_eq!(cron["payload"]["kind"], "prompt");
10556        assert_eq!(cron["recurring"], true);
10557        assert_eq!(cron["deliver"]["target"], "session");
10558        let wakeup = job_row(result, "toolu_wake_recheck");
10559        assert_eq!(wakeup["payload"]["kind"], "wakeup");
10560        assert_eq!(wakeup["schedule"]["kind"], "once");
10561        assert_eq!(wakeup["recurring"], false);
10562        assert_eq!(wakeup["state"], "pending");
10563
10564        // Hermes: install-scoped, interval + origin delivery, and a paused cron.
10565        let interval = job_row(result, "digest-15m");
10566        assert_eq!(interval["harness"], "hermes");
10567        assert_eq!(interval["scope"], "install");
10568        assert_eq!(interval["profile"], Value::Null);
10569        assert_eq!(interval["schedule"]["kind"], "interval");
10570        assert_eq!(interval["schedule"]["minutes"], 15.0);
10571        assert_eq!(interval["schedule"]["display"], "every 15 min");
10572        assert_eq!(interval["deliver"]["target"], "origin");
10573        assert_eq!(interval["deliver"]["chat_id"], "-1002233445566");
10574        assert_eq!(interval["next_run_at"], "2026-09-02T11:15:00Z");
10575        assert_eq!(interval["last_status"], "ok");
10576        let nightly = job_row(result, "nightly-audit");
10577        assert_eq!(nightly["schedule"]["expr"], "0 3 * * *");
10578        assert_eq!(nightly["deliver"]["target"], "local");
10579        assert_eq!(nightly["enabled"], false);
10580        assert_eq!(nightly["state"], "paused");
10581        // The per-profile store carries the profile name from its own path.
10582        let profiled = job_row(result, "ops-once-boot");
10583        assert_eq!(profiled["profile"], "ops");
10584        assert_eq!(profiled["schedule"]["kind"], "once");
10585        assert_eq!(profiled["schedule"]["run_at"], "2026-09-03T06:00:00Z");
10586        assert_eq!(profiled["payload"]["kind"], "script");
10587        // An explicit `<platform>:<chat>` target carries the chat itself.
10588        assert_eq!(profiled["deliver"]["target"], "slack:C0429ABCD");
10589        assert_eq!(profiled["deliver"]["chat_id"], "C0429ABCD");
10590        assert_eq!(profiled["recurring"], false);
10591
10592        // ORCH-13: a job delivering to its creating conversation carries that
10593        // conversation's whole surface — platform word, chat AND thread.
10594        let standup_to_group = job_row(result, "coder-standup");
10595        assert_eq!(standup_to_group["deliver"]["target"], "origin");
10596        assert_eq!(standup_to_group["deliver"]["chat_id"], "-100777");
10597        assert_eq!(standup_to_group["deliver"]["thread_id"], "55");
10598        // Hermes has no mode word and routes by adapter profile, not account.
10599        assert!(standup_to_group["deliver"]["mode"].is_null());
10600        assert!(standup_to_group["deliver"]["account"].is_null());
10601
10602        // OpenClaw: the session target and the delivery mode are the row's own
10603        // columns, not a footnote.
10604        let standup = job_row(result, "cron_standup");
10605        assert_eq!(standup["harness"], "openclaw");
10606        assert_eq!(standup["session_target"], "isolated");
10607        assert_eq!(standup["deliver"]["mode"], "announce");
10608        assert_eq!(standup["deliver"]["target"], "slack");
10609        assert_eq!(standup["deliver"]["chat_id"], "C0429ABCD");
10610        assert_eq!(standup["payload"]["kind"], "prompt");
10611        assert_eq!(standup["profile"], "main");
10612        let reindex = job_row(result, "cron_reindex");
10613        assert_eq!(reindex["session_target"], "main");
10614        assert_eq!(reindex["payload"]["kind"], "system_event");
10615        assert_eq!(reindex["schedule"]["kind"], "interval");
10616        assert_eq!(reindex["schedule"]["display"], "every 240 min");
10617        assert_eq!(reindex["enabled"], false);
10618
10619        // Every store consulted is named, so an empty answer is never silent.
10620        let states: Vec<(&str, &str)> = result["sources"]
10621            .as_array()
10622            .unwrap()
10623            .iter()
10624            .map(|source| {
10625                (
10626                    source["harness"].as_str().unwrap(),
10627                    source["state"].as_str().unwrap(),
10628                )
10629            })
10630            .collect();
10631        // The `coder` profile home has no cron store at all: it is named as
10632        // `absent_store`, not skipped, so "this profile schedules nothing" and
10633        // "this profile was never looked at" stay distinguishable.
10634        assert_eq!(
10635            states,
10636            vec![
10637                ("claude-code", "scanned"),
10638                ("hermes", "read"),
10639                ("hermes", "absent_store"),
10640                ("hermes", "read"),
10641                ("openclaw", "read"),
10642                ("openclaw", "read"),
10643            ],
10644            "{result}"
10645        );
10646    }
10647
10648    #[test]
10649    fn jobs_list_filters_by_harness_session_and_profile() {
10650        let by_harness = jobs_list(json!({"harness": "openclaw", "homes": jobs_fixture_homes()}));
10651        let ids: Vec<&str> = by_harness["result"]["jobs"]
10652            .as_array()
10653            .unwrap()
10654            .iter()
10655            .map(|job| job["id"].as_str().unwrap())
10656            .collect();
10657        assert_eq!(
10658            ids,
10659            vec![
10660                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10661                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10662                "cron_standup",
10663                "cron_reindex",
10664            ]
10665        );
10666
10667        let by_session = jobs_list(json!({
10668            "session": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
10669            "homes": jobs_fixture_homes(),
10670        }));
10671        let jobs = by_session["result"]["jobs"].as_array().unwrap();
10672        assert_eq!(jobs.len(), 2, "{by_session}");
10673        assert!(jobs
10674            .iter()
10675            .all(|job| job["harness"] == "claude-code" && job["scope"] == "session"));
10676
10677        let by_profile = jobs_list(json!({
10678            "harness": "hermes",
10679            "profile": "ops",
10680            "homes": jobs_fixture_homes(),
10681        }));
10682        let jobs = by_profile["result"]["jobs"].as_array().unwrap();
10683        assert_eq!(jobs.len(), 1, "{by_profile}");
10684        assert_eq!(jobs[0]["id"], "ops-once-boot");
10685    }
10686
10687    #[test]
10688    fn jobs_get_answers_with_the_row_and_the_verbatim_native_record() {
10689        let mut service = HarnessSessionService::new();
10690        let hermes = service.handle(request(
10691            1,
10692            "harness.v1.jobs.get",
10693            json!({"harness": "hermes", "id": "digest-15m", "homes": jobs_fixture_homes()}),
10694        ));
10695        assert_eq!(hermes["result"]["job"]["schedule"]["kind"], "interval");
10696        // Native fields the uniform row does not carry survive on `source`.
10697        assert_eq!(hermes["result"]["source"]["provider"], "nous");
10698        assert_eq!(hermes["result"]["source"]["failure_deliver"], "local");
10699
10700        let claude = service.handle(request(
10701            2,
10702            "harness.v1.jobs.get",
10703            json!({"harness": "claude-code", "id": "release-watch", "homes": jobs_fixture_homes()}),
10704        ));
10705        assert_eq!(claude["result"]["job"]["payload"]["kind"], "prompt");
10706        assert_eq!(
10707            claude["result"]["source"]["tool_use_id"],
10708            "toolu_cron_release_watch"
10709        );
10710
10711        let missing = service.handle(request(
10712            3,
10713            "harness.v1.jobs.get",
10714            json!({"harness": "hermes", "id": "no-such-job", "homes": jobs_fixture_homes()}),
10715        ));
10716        assert!(missing["error"]["message"]
10717            .as_str()
10718            .is_some_and(|message| message.contains("no scheduled job `no-such-job`")));
10719    }
10720
10721    #[test]
10722    fn jobs_refuse_a_harness_without_a_scheduled_job_concept() {
10723        let mut service = HarnessSessionService::new();
10724        for (id, method, params) in [
10725            (
10726                1,
10727                "harness.v1.jobs.list",
10728                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10729            ),
10730            (
10731                2,
10732                "harness.v1.jobs.get",
10733                json!({"harness": "codex", "id": "anything"}),
10734            ),
10735        ] {
10736            let response = service.handle(request(id, method, params));
10737            assert_eq!(response["error"]["code"], -32020, "{response}");
10738            assert!(response["error"]["message"]
10739                .as_str()
10740                .is_some_and(|message| message.contains("has no scheduled jobs")));
10741            assert!(response.get("result").is_none());
10742        }
10743    }
10744
10745    #[test]
10746    fn jobs_list_reports_a_migrated_openclaw_store_as_absent_instead_of_failing() {
10747        let scratch = std::env::temp_dir().join(format!(
10748            "supercode-jobs-migrated-{}-{}",
10749            std::process::id(),
10750            generated_session_id()
10751        ));
10752        std::fs::create_dir_all(&scratch).unwrap();
10753        let response = jobs_list(json!({
10754            "harness": "openclaw",
10755            "homes": {"openclaw": scratch.clone()},
10756        }));
10757        let result = &response["result"];
10758        assert_eq!(result["jobs"].as_array().unwrap().len(), 0, "{result}");
10759        assert_eq!(result["sources"][0]["state"], "absent_store");
10760        assert_eq!(result["sources"][0]["harness"], "openclaw");
10761        std::fs::remove_dir_all(&scratch).ok();
10762    }
10763
10764    // ---------------------------------------------------------------------
10765    // ORCH-8 — `harness.v1.runs.list` / `runs.get` over the committed fire
10766    // stores: Hermes's `cron/executions.db` (root home + profile home) and
10767    // OpenClaw's `cron_run_logs`. Every fixture row is written by
10768    // `tests/fixtures/gen_runs_fixtures.py` against the harnesses' own DDL.
10769    // ---------------------------------------------------------------------
10770
10771    /// The health job in the committed OpenClaw fixture, which fired twice.
10772    const OPENCLAW_HEALTH_JOB: &str = "85ad7832-896f-42be-af31-3e1ed2fbdc4b";
10773    /// The digest job, whose single fire predates run ids.
10774    const OPENCLAW_DIGEST_JOB: &str = "8bb7d938-ca46-4a6d-90eb-c92331155566";
10775
10776    fn runs_list(params: Value) -> Value {
10777        let mut service = HarnessSessionService::new();
10778        service.handle(request(1, "harness.v1.runs.list", params))
10779    }
10780
10781    fn run_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10782        result["runs"]
10783            .as_array()
10784            .expect("runs is an array")
10785            .iter()
10786            .find(|run| run["id"] == id)
10787            .unwrap_or_else(|| panic!("no run `{id}` in {result}"))
10788    }
10789
10790    #[test]
10791    fn runs_list_projects_both_fixture_stores_onto_the_uniform_row() {
10792        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10793        let result = &response["result"];
10794        let ids: Vec<&str> = result["runs"]
10795            .as_array()
10796            .expect("runs is an array")
10797            .iter()
10798            .map(|run| run["id"].as_str().unwrap())
10799            .collect();
10800        let digest_fire = format!("{OPENCLAW_DIGEST_JOB}#1");
10801        assert_eq!(
10802            ids,
10803            vec![
10804                // Hermes, newest claim first, root ledger then profile ledger.
10805                "b2c3d4e5f60718293a4b5c6d7e8f9012",
10806                "a1b2c3d4e5f60718293a4b5c6d7e8f90",
10807                "c3d4e5f60718293a4b5c6d7e8f901234",
10808                "f60718293a4b5c6d7e8f901234567890",
10809                "e5f60718293a4b5c6d7e8f9012345678",
10810                "d4e5f60718293a4b5c6d7e8f90123456",
10811                // OpenClaw, newest `ts` first.
10812                "run_health_0002",
10813                digest_fire.as_str(),
10814                "run_health_0001",
10815            ],
10816            "{result}"
10817        );
10818
10819        // The harness's OWN outcome word survives; nothing is renamed onto a
10820        // shared vocabulary.
10821        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10822        assert_eq!(failed["harness"], "hermes");
10823        assert_eq!(failed["job_id"], "job42");
10824        assert_eq!(failed["status"], "failed");
10825        assert_eq!(failed["error"], "provider returned 500 after 3 attempts");
10826        assert_eq!(failed["claimed_at"], "2026-09-02T13:05:00.100442");
10827
10828        // Hermes's `unknown` — an attempt whose owner died before writing a
10829        // terminal state — is a fourth status, not folded into `failed`.
10830        let abandoned = run_row(result, "d4e5f60718293a4b5c6d7e8f90123456");
10831        assert_eq!(abandoned["status"], "unknown");
10832        assert_eq!(abandoned["job_id"], "ops-once-boot");
10833
10834        // An unterminated fire has no finish, and no session is invented.
10835        let running = run_row(result, "c3d4e5f60718293a4b5c6d7e8f901234");
10836        assert_eq!(running["status"], "running");
10837        assert!(running["finished_at"].is_null(), "{running}");
10838        assert!(running["session_id"].is_null(), "{running}");
10839
10840        // OpenClaw records the session on the row itself, and epoch-ms
10841        // timestamps are rendered as RFC 3339.
10842        let ok = run_row(result, "run_health_0001");
10843        assert_eq!(ok["harness"], "openclaw");
10844        assert_eq!(ok["job_id"], OPENCLAW_HEALTH_JOB);
10845        assert_eq!(ok["status"], "ok");
10846        assert_eq!(ok["started_at"], "2026-09-02T08:30:00.000Z");
10847        assert_eq!(ok["finished_at"], "2026-09-02T08:30:30.000Z");
10848        assert_eq!(ok["session_id"], "3dd577ae-a0a3-4b5b-8063-f402be4f5fd4");
10849        // OpenClaw's run log is written once, at finish: there is no claim.
10850        assert!(ok["claimed_at"].is_null(), "{ok}");
10851
10852        // A run-log row with no `run_id` falls back to the store's own
10853        // `(job_id, seq)` key rather than being dropped.
10854        assert_eq!(run_row(result, &digest_fire)["status"], "skipped");
10855
10856        // ORCH-13: a fire whose delivery nothing recorded says so, rather than
10857        // borrowing a neighbouring fire's outcome. Both of these ran on jobs
10858        // that deliver `local` (or have no job record at all), so no
10859        // obligation is addressed to a surface they could match.
10860        for id in [
10861            "b2c3d4e5f60718293a4b5c6d7e8f9012",
10862            "d4e5f60718293a4b5c6d7e8f90123456",
10863        ] {
10864            assert!(run_row(result, id)["delivery"].is_null(), "{id}");
10865        }
10866
10867        // Every store consulted is named, including the profile home that has
10868        // no ledger — an empty history and an absent store are different.
10869        let sources = result["sources"].as_array().unwrap();
10870        let states: Vec<(&str, &str)> = sources
10871            .iter()
10872            .map(|source| {
10873                (
10874                    source["harness"].as_str().unwrap(),
10875                    source["state"].as_str().unwrap(),
10876                )
10877            })
10878            .collect();
10879        assert_eq!(
10880            states,
10881            vec![
10882                ("hermes", "read"),
10883                ("hermes", "absent_store"),
10884                ("hermes", "read"),
10885                ("openclaw", "read"),
10886            ],
10887            "{result}"
10888        );
10889        assert_eq!(sources[2]["profile"], "ops");
10890        assert!(sources[3]["path"]
10891            .as_str()
10892            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10893    }
10894
10895    #[test]
10896    fn runs_list_joins_a_hermes_fire_to_the_session_it_opened() {
10897        let response = runs_list(json!({
10898            "harness": "hermes",
10899            "job": "job42",
10900            "homes": jobs_fixture_homes(),
10901        }));
10902        let result = &response["result"];
10903        assert_eq!(result["runs"].as_array().unwrap().len(), 2, "{result}");
10904
10905        // Hermes writes NO link from an execution to its session. The fire
10906        // that ran the agent is joined to `cron_job42_<stamp>` because that
10907        // id's instant falls inside its [claimed_at, finished_at] window.
10908        let ran = run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90");
10909        assert_eq!(ran["session_id"], "cron_job42_20260902_120000");
10910
10911        // The later fire failed before opening one. Its window holds no
10912        // session, so the row says so instead of re-using the earlier fire's
10913        // — the join is per-FIRE, not per-job.
10914        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10915        assert!(failed["session_id"].is_null(), "{failed}");
10916    }
10917
10918    /// ORCH-13: where a fire's output went, read from each harness's own
10919    /// delivery record — Hermes's `delivery_obligations` ledger inside
10920    /// `state.db`, OpenClaw's `delivery_*` run-log columns.
10921    #[test]
10922    fn runs_list_reads_the_delivery_each_harness_recorded_for_a_fire() {
10923        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10924        let result = &response["result"];
10925
10926        // Hermes: the ledger is the GATEWAY's, keyed by conversation and
10927        // surface, so the fire's own [claimed_at, finished_at] window picks
10928        // the obligation. The fire succeeded and so did the send.
10929        let delivered = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
10930        assert_eq!(delivered["status"], "completed");
10931        assert_eq!(delivered["delivery"]["state"], "delivered");
10932        assert_eq!(delivered["delivery"]["target"], "telegram:-100777:55");
10933        assert_eq!(delivered["delivery"]["attempts"], 1);
10934        assert!(delivered["delivery"]["last_error"].is_null(), "{delivered}");
10935        assert_eq!(
10936            delivered["delivery"]["delivered_at"],
10937            "2026-09-02T09:00:30.400Z"
10938        );
10939
10940        // The next fire of the same job ALSO succeeded — and its output never
10941        // arrived. That is the fact `status` alone cannot carry.
10942        let undelivered = run_row(result, "f60718293a4b5c6d7e8f901234567890");
10943        assert_eq!(undelivered["status"], "completed");
10944        assert_eq!(undelivered["delivery"]["state"], "failed");
10945        assert_eq!(undelivered["delivery"]["attempts"], 3);
10946        assert_eq!(
10947            undelivered["delivery"]["last_error"],
10948            "telegram send failed: Bad Request: chat not found"
10949        );
10950        // Only a delivered obligation carries an instant of delivery; the
10951        // ledger's `updated_at` on a failed row dates the failure.
10952        assert!(
10953            undelivered["delivery"]["delivered_at"].is_null(),
10954            "{undelivered}"
10955        );
10956
10957        // OpenClaw writes the outcome onto the run-log row and declares the
10958        // address on the job, so the row's target is joined from `cron_jobs`.
10959        let announced = run_row(result, "run_health_0001");
10960        assert_eq!(announced["delivery"]["state"], "delivered");
10961        assert_eq!(announced["delivery"]["target"], "last");
10962        // Its run log counts no attempts and stamps no delivered-at.
10963        assert!(announced["delivery"]["attempts"].is_null(), "{announced}");
10964        assert!(
10965            announced["delivery"]["delivered_at"].is_null(),
10966            "{announced}"
10967        );
10968        let refused = run_row(result, "run_health_0002");
10969        assert_eq!(refused["delivery"]["state"], "not-delivered");
10970        assert_eq!(refused["delivery"]["last_error"], "channel_not_found");
10971
10972        // A run-log row with no delivery columns at all recorded no delivery:
10973        // the job's declared target is not evidence that anything was sent.
10974        let skipped = run_row(result, &format!("{OPENCLAW_DIGEST_JOB}#1"));
10975        assert!(skipped["delivery"].is_null(), "{skipped}");
10976    }
10977
10978    /// A Hermes fire whose session carries a `session_key` is matched on that
10979    /// key FIRST — the most specific question the ledger can answer. Proven by
10980    /// moving the obligations off the job's surface on a COPY of the fixture,
10981    /// so only the session-key question can still find them.
10982    #[test]
10983    fn runs_list_matches_a_hermes_obligation_by_the_session_key_first() {
10984        let scratch = std::env::temp_dir().join(format!(
10985            "supercode-runs-delivery-{}-{}",
10986            std::process::id(),
10987            generated_session_id()
10988        ));
10989        std::fs::create_dir_all(scratch.join("cron")).unwrap();
10990        let fixture = jobs_fixture_root().join("hermes_home");
10991        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
10992        for name in ["cron/executions.db", "cron/jobs.json"] {
10993            std::fs::copy(fixture.join(name), scratch.join(name)).unwrap();
10994        }
10995        {
10996            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
10997            // The obligations now sit on a surface no job in this store
10998            // delivers to, so the surface question cannot match them.
10999            connection
11000                .execute(
11001                    "UPDATE delivery_obligations SET platform = 'slack', chat_id = 'C0FALLBACK'",
11002                    [],
11003                )
11004                .unwrap();
11005            // A cron fire that ran inside a keyed conversation: the session
11006            // the window recovers carries `tg-coder-1`'s key.
11007            connection
11008                .execute(
11009                    "INSERT INTO sessions (id, source, session_key, started_at) VALUES \
11010                     ('cron_coder-standup_20260902_090010', 'cron', \
11011                      'agent:coder:telegram:group:-100777:55', 1788339610.0)",
11012                    [],
11013                )
11014                .unwrap();
11015        }
11016        let response = runs_list(json!({
11017            "harness": "hermes",
11018            "job": "coder-standup",
11019            "homes": {"hermes": scratch.join("state.db")},
11020        }));
11021        let result = &response["result"];
11022        let matched = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
11023        assert_eq!(
11024            matched["session_id"], "cron_coder-standup_20260902_090010",
11025            "{result}"
11026        );
11027        assert_eq!(matched["delivery"]["state"], "delivered", "{result}");
11028        assert_eq!(
11029            matched["delivery"]["target"], "slack:C0FALLBACK:55",
11030            "{result}"
11031        );
11032        std::fs::remove_dir_all(&scratch).ok();
11033    }
11034
11035    #[test]
11036    fn runs_list_follows_a_compression_chain_to_the_readable_tip() {
11037        // A fire whose session was compressed mid-run is only readable at the
11038        // continuation, so that is what the row must report. Built on a COPY
11039        // of the committed fixture: no test writes to a fixture or to a real
11040        // harness home.
11041        let scratch = std::env::temp_dir().join(format!(
11042            "supercode-runs-compressed-{}-{}",
11043            std::process::id(),
11044            generated_session_id()
11045        ));
11046        std::fs::create_dir_all(scratch.join("cron")).unwrap();
11047        let fixture = jobs_fixture_root().join("hermes_home");
11048        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
11049        std::fs::copy(
11050            fixture.join("cron/executions.db"),
11051            scratch.join("cron/executions.db"),
11052        )
11053        .unwrap();
11054        {
11055            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
11056            connection
11057                .execute(
11058                    "UPDATE sessions SET end_reason = 'compression' WHERE id = ?1",
11059                    ["cron_job42_20260902_120000"],
11060                )
11061                .unwrap();
11062            connection
11063                .execute(
11064                    "INSERT INTO sessions (id, source, parent_session_id, started_at) \
11065                     VALUES ('job42-after-compaction', 'cron', \
11066                             'cron_job42_20260902_120000', 1788350000.0)",
11067                    [],
11068                )
11069                .unwrap();
11070        }
11071        let response = runs_list(json!({
11072            "harness": "hermes",
11073            "job": "job42",
11074            "homes": {"hermes": scratch.join("state.db")},
11075        }));
11076        let result = &response["result"];
11077        assert_eq!(
11078            run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90")["session_id"],
11079            "job42-after-compaction",
11080            "{result}"
11081        );
11082        std::fs::remove_dir_all(&scratch).ok();
11083    }
11084
11085    #[test]
11086    fn runs_list_filters_by_job_and_caps_by_limit() {
11087        let by_job = runs_list(json!({
11088            "harness": "openclaw",
11089            "job": OPENCLAW_HEALTH_JOB,
11090            "homes": jobs_fixture_homes(),
11091        }));
11092        let ids: Vec<&str> = by_job["result"]["runs"]
11093            .as_array()
11094            .unwrap()
11095            .iter()
11096            .map(|run| run["id"].as_str().unwrap())
11097            .collect();
11098        assert_eq!(ids, vec!["run_health_0002", "run_health_0001"], "{by_job}");
11099
11100        let capped = runs_list(json!({
11101            "harness": "openclaw",
11102            "limit": 1,
11103            "homes": jobs_fixture_homes(),
11104        }));
11105        let runs = capped["result"]["runs"].as_array().unwrap();
11106        assert_eq!(runs.len(), 1, "{capped}");
11107        // Newest first, so the cap keeps the recent fire.
11108        assert_eq!(runs[0]["id"], "run_health_0002");
11109    }
11110
11111    #[test]
11112    fn runs_get_answers_with_the_row_and_the_verbatim_native_record() {
11113        let mut service = HarnessSessionService::new();
11114        let hermes = service.handle(request(
11115            1,
11116            "harness.v1.runs.get",
11117            json!({
11118                "harness": "hermes",
11119                "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
11120                "homes": jobs_fixture_homes(),
11121            }),
11122        ));
11123        assert_eq!(hermes["result"]["run"]["status"], "completed");
11124        assert_eq!(
11125            hermes["result"]["run"]["session_id"],
11126            "cron_job42_20260902_120000"
11127        );
11128        // Ledger columns the uniform row does not carry survive on `source`.
11129        assert_eq!(hermes["result"]["source"]["source"], "scheduler");
11130        assert_eq!(hermes["result"]["source"]["pid"], 4242);
11131        assert_eq!(hermes["result"]["source"]["process_id"], "9f1c2d");
11132
11133        let openclaw = service.handle(request(
11134            2,
11135            "harness.v1.runs.get",
11136            json!({
11137                "harness": "openclaw",
11138                "id": "run_health_0002",
11139                "homes": jobs_fixture_homes(),
11140            }),
11141        ));
11142        assert_eq!(openclaw["result"]["run"]["status"], "error");
11143        // ORCH-13: the run's delivery is projected AND the store's own columns
11144        // stay verbatim on `source`, so nothing about the fire is lost.
11145        assert_eq!(
11146            openclaw["result"]["source"]["delivery_status"],
11147            "not-delivered"
11148        );
11149        assert_eq!(
11150            openclaw["result"]["source"]["delivery_error"],
11151            "channel_not_found"
11152        );
11153        assert_eq!(openclaw["result"]["source"]["delivered"], 0);
11154        assert_eq!(
11155            openclaw["result"]["run"]["delivery"]["state"],
11156            "not-delivered"
11157        );
11158        assert_eq!(
11159            openclaw["result"]["run"]["delivery"]["last_error"],
11160            "channel_not_found"
11161        );
11162
11163        let missing = service.handle(request(
11164            3,
11165            "harness.v1.runs.get",
11166            json!({"harness": "hermes", "id": "no-such-run", "homes": jobs_fixture_homes()}),
11167        ));
11168        assert!(missing["error"]["message"]
11169            .as_str()
11170            .is_some_and(|message| message.contains("no run `no-such-run`")));
11171    }
11172
11173    #[test]
11174    fn runs_refuse_a_harness_that_keeps_no_run_store() {
11175        let mut service = HarnessSessionService::new();
11176        for (id, method, params) in [
11177            // Claude Code HAS scheduled jobs but no fire store: its fires are
11178            // ordinary turns. It must refuse, not answer with an empty list.
11179            (
11180                1,
11181                "harness.v1.runs.list",
11182                json!({"harness": "claude-code", "homes": jobs_fixture_homes()}),
11183            ),
11184            (
11185                2,
11186                "harness.v1.runs.get",
11187                json!({"harness": "claude-code", "id": "anything"}),
11188            ),
11189            (
11190                3,
11191                "harness.v1.runs.list",
11192                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
11193            ),
11194        ] {
11195            let response = service.handle(request(id, method, params));
11196            assert_eq!(response["error"]["code"], -32020, "{response}");
11197            assert!(response["error"]["message"]
11198                .as_str()
11199                .is_some_and(|message| message.contains("keeps no run store")));
11200            assert!(response.get("result").is_none());
11201        }
11202    }
11203
11204    #[test]
11205    fn runs_list_reports_an_install_with_no_run_store_as_absent() {
11206        let scratch = std::env::temp_dir().join(format!(
11207            "supercode-runs-empty-{}-{}",
11208            std::process::id(),
11209            generated_session_id()
11210        ));
11211        std::fs::create_dir_all(&scratch).unwrap();
11212        let response = runs_list(json!({
11213            "harness": "openclaw",
11214            "homes": {"openclaw": scratch.clone()},
11215        }));
11216        let result = &response["result"];
11217        assert_eq!(result["runs"].as_array().unwrap().len(), 0, "{result}");
11218        assert_eq!(result["sources"][0]["state"], "absent_store");
11219        assert!(result["sources"][0]["path"]
11220            .as_str()
11221            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
11222        std::fs::remove_dir_all(&scratch).ok();
11223    }
11224}