Skip to main content

supercode_harness/
harness_service.rs

1//! Versioned, language-neutral service over persisted harness sessions.
2//!
3//! The service is transport-agnostic: [`HarnessSessionService::handle`] accepts
4//! one JSON-RPC value and [`HarnessSessionService::poll`] produces subscription
5//! notifications. The CLI exposes those primitives as NDJSON over stdio.
6
7use std::collections::{BTreeMap, BTreeSet};
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use std::time::Duration;
11
12use serde::{Deserialize, Serialize};
13use serde_json::{json, Value};
14use tokio::sync::Notify;
15
16use crate::reduce;
17use crate::runtime::generated_session_id;
18#[cfg(feature = "adapter-api")]
19use crate::runtime::{HostedHarnessConnection, HostedHarnessRuntime};
20use crate::sdk::{
21    discover_session_page, load_session, load_session_with_fidelity, SdkCapabilities, SdkError,
22    SdkErrorCode, SdkEvent, SdkOperation, SdkRequest, SdkRuntimeEvent, SdkService,
23};
24use crate::Fidelity;
25#[cfg(feature = "adapter-api")]
26use crate::SupercodeHttpRuntimeBackend;
27use crate::{
28    discover_live_runtime, harness_support_registry, AcpRuntimeBackend, ClaudeCodeRuntimeBackend,
29    CodexRuntimeBackend, DiscoveryQuery, HarnessCatalog, HarnessHomes, HarnessId,
30    ImplementationKind, LiveRuntimeEndpoint, LiveRuntimeSource, OpenCodeRuntimeBackend,
31    PiRuntimeBackend, Role, RuntimeAttachRequest, RuntimeBackend, RuntimeConnection, RuntimeInput,
32    RuntimeLaunch, RuntimeStartRequest, Session, SessionDescriptor, SessionFollower, SessionFormat,
33    SessionLocator, SessionSource,
34};
35#[cfg(feature = "adapter-api")]
36use crate::{register_live_runtime, resolve_live_runtime, LiveRuntimeRegistration};
37use supercode_interchange::watch::{bound_session_view, message_json, normalized_session_json};
38
39/// Every JSON-RPC method the harness service dispatches (`harness.v1.capabilities`
40/// reports it; ORCH-4 registry tiers must cite entries of it).
41pub const HARNESS_SERVICE_METHODS: &[&str] = &[
42    "harness.v1.support.report",
43    "harness.v1.harnesses.list",
44    "harness.v1.harnesses.probe",
45    "harness.v1.harnesses.settings",
46    "harness.v1.harnesses.configure",
47    "harness.v1.harnesses.auth.methods",
48    "harness.v1.harnesses.auth.begin",
49    "harness.v1.harnesses.auth.verify",
50    "harness.v1.sessions.discover",
51    "harness.v1.sessions.load",
52    "harness.v1.sessions.follow",
53    "harness.v1.sessions.unfollow",
54    "harness.v1.sessions.activity.subscribe",
55    "harness.v1.sessions.activity.unsubscribe",
56    "harness.v1.sessions.activity_under",
57    "harness.v1.sessions.index.subscribe",
58    "harness.v1.sessions.index.resize",
59    "harness.v1.sessions.index.unsubscribe",
60    "harness.v1.sessions.message",
61    "harness.v1.sessions.inbox",
62    "harness.v1.sessions.import",
63    "harness.v1.sessions.export",
64    "harness.v1.sessions.translate",
65    "harness.v1.sessions.reduce",
66    "harness.v1.sessions.branch",
67    "harness.v1.sessions.handoff",
68    "harness.v1.sessions.materialize",
69    "harness.v1.sessions.resume_instructions",
70    "harness.v1.skills.list",
71    "harness.v1.skills.install",
72    "harness.v1.skills.remove",
73    "harness.v1.memory.show",
74    "harness.v1.memory.search",
75    "harness.v1.jobs.list",
76    "harness.v1.jobs.get",
77    "harness.v1.jobs.create",
78    "harness.v1.jobs.update",
79    "harness.v1.jobs.pause",
80    "harness.v1.jobs.resume",
81    "harness.v1.jobs.run",
82    "harness.v1.jobs.delete",
83    "harness.v1.jobs.notepad",
84    "harness.v1.jobs.notepad_set",
85    "harness.v1.jobs.notepad_delete",
86    "harness.v1.sessions.new",
87    "harness.v1.sessions.reset",
88    "harness.v1.sessions.archive",
89    "harness.v1.sessions.delete",
90    "harness.v1.runs.list",
91    "harness.v1.runs.get",
92    "harness.v1.approvals.list",
93    "harness.v1.approvals.resolve",
94    "harness.v1.runtimes.capabilities",
95    "harness.v1.runtimes.start",
96    "harness.v1.runtimes.resume",
97    "harness.v1.runtimes.attach_existing",
98    "harness.v1.runtimes.attach",
99    "harness.v1.runtimes.send_input",
100    "harness.v1.runtimes.interrupt",
101    "harness.v1.runtimes.steer",
102    "harness.v1.runtimes.respond",
103    "harness.v1.runtimes.terminal_instructions",
104    "harness.v1.runtimes.acquire_control",
105    "harness.v1.runtimes.heartbeat",
106    "harness.v1.runtimes.detach",
107    "harness.v1.runtimes.close",
108    "harness.v1.profiles.list",
109    "harness.v1.profiles.get",
110    "harness.v1.profiles.create",
111    "harness.v1.profiles.delete",
112    "harness.v1.channels.list",
113    "harness.v1.routes.list",
114    "harness.v1.triggers.list",
115    "harness.v1.channels.status",
116    "harness.v1.orchestration.load",
117    "harness.v1.orchestration.save",
118    "harness.v1.orchestration.compile",
119    "harness.v1.orchestration.decompile",
120    "harness.v1.orchestration.import",
121    "harness.v1.orchestration.export",
122    "harness.v1.workflow.load",
123];
124
125/// Protocol namespace implemented by this service.
126pub const HARNESS_SERVICE_VERSION: &str = "harness.v1";
127/// Notification method emitted for followed-session changes.
128pub const SESSION_EVENT_METHOD: &str = "harness.v1.sessions.event";
129/// Notification method emitted for normalized session-activity transitions.
130pub const SESSION_ACTIVITY_EVENT_METHOD: &str = "harness.v1.sessions.activity_event";
131/// Notification method emitted for revisioned session-list changes.
132pub const SESSION_INDEX_EVENT_METHOD: &str = "harness.v1.sessions.index_event";
133/// Notification method emitted for live runtime events.
134pub const RUNTIME_EVENT_METHOD: &str = "harness.v1.runtimes.event";
135
136/// Stateful persisted-session service. Each instance owns its follow
137/// subscriptions; discovery and loading remain read-only.
138pub struct HarnessSessionService {
139    catalog: HarnessCatalog,
140    followers: BTreeMap<String, SessionFollower>,
141    followed_sources: BTreeMap<String, FollowedSource>,
142    activity_subscriptions: BTreeMap<String, ActivitySubscription>,
143    index_subscriptions: BTreeMap<String, crate::session_index::SessionIndexSubscription>,
144    index_notifier: Arc<Notify>,
145    #[cfg(feature = "adapter-api")]
146    activity_monitor: crate::session_activity::SessionActivityMonitor,
147    next_subscription: u64,
148    runtimes: BTreeMap<String, Box<dyn RuntimeConnection>>,
149    /// Connections lent to a detached call that is running right now. The
150    /// runtime itself is OUT of `runtimes` for that whole call, and these
151    /// names are how a second caller is told the connection is busy rather
152    /// than unknown.
153    runtimes_in_flight: BTreeSet<String>,
154    terminal_launches: BTreeMap<String, StructuredLaunch>,
155    runtime_sequences: BTreeMap<String, u64>,
156    next_runtime: u64,
157    reduction_store_root: Option<PathBuf>,
158    /// ORCH-9: live permission/approval requests outstanding on the open
159    /// runtime connections above, fed by the same event pump that publishes
160    /// `harness.v1.runtimes.event`.
161    approvals: crate::approvals::ApprovalRegistry,
162    /// ORCH-9: supercode's own queued subagent approvals, when the host that
163    /// owns this service publishes its parent queue here.
164    subagent_approvals: Option<Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>>,
165}
166
167impl Default for HarnessSessionService {
168    fn default() -> Self {
169        Self::new()
170    }
171}
172
173impl HarnessSessionService {
174    /// Create an empty service instance.
175    pub fn new() -> Self {
176        Self {
177            catalog: HarnessCatalog::new(),
178            followers: BTreeMap::new(),
179            followed_sources: BTreeMap::new(),
180            activity_subscriptions: BTreeMap::new(),
181            index_subscriptions: BTreeMap::new(),
182            index_notifier: Arc::new(Notify::new()),
183            #[cfg(feature = "adapter-api")]
184            activity_monitor: Default::default(),
185            next_subscription: 1,
186            runtimes: BTreeMap::new(),
187            runtimes_in_flight: BTreeSet::new(),
188            terminal_launches: BTreeMap::new(),
189            runtime_sequences: BTreeMap::new(),
190            next_runtime: 1,
191            reduction_store_root: None,
192            approvals: crate::approvals::ApprovalRegistry::new(),
193            subagent_approvals: None,
194        }
195    }
196
197    /// Override the trusted, service-owned store used for durable reduction
198    /// bundles. Embedders and tests use this to keep all writes inside an
199    /// explicitly selected root; the CLI otherwise uses the normal
200    /// `$SUPERCODE_HOME/sessions` location.
201    pub fn with_reduction_store_root(mut self, root: impl Into<PathBuf>) -> Self {
202        self.reduction_store_root = Some(root.into());
203        self
204    }
205
206    /// ORCH-9: publish the parent's own subagent-approval queue into
207    /// `harness.v1.approvals.list`.
208    ///
209    /// This is the SAME `Arc` an [`crate::Agent`] pushes into
210    /// (`Agent::pending_child_approvals`), so a host that runs supercode's own
211    /// loop beside this service surfaces those requests through the uniform
212    /// door without copying them anywhere.
213    pub fn observe_subagent_approvals(
214        &mut self,
215        queue: Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>,
216    ) {
217        self.subagent_approvals = Some(queue);
218    }
219
220    /// ORCH-9: every approval request this service can see, newest last.
221    ///
222    /// Two sources, both live: the requests outstanding on the open runtime
223    /// connections, and supercode's own queued subagent approvals. There is
224    /// no file or database source at the pinned harness versions (see
225    /// [`crate::approvals`]), so a stored or proposal row is never produced.
226    pub fn approvals(&self, query: &crate::approvals::ApprovalsQuery) -> Vec<crate::ApprovalRow> {
227        let now = crate::approvals::now_ms();
228        let mut rows = self.approvals.rows(now);
229        if let Some(queue) = self.subagent_approvals.as_ref() {
230            let queued = queue
231                .lock()
232                .unwrap_or_else(std::sync::PoisonError::into_inner)
233                .clone();
234            rows.extend(crate::approvals::subagent_rows(&queued, now));
235        }
236        rows.retain(|row| query.matches(row));
237        rows.sort_by(|left, right| {
238            left.requested_at_ms
239                .cmp(&right.requested_at_ms)
240                .then_with(|| left.id.cmp(&right.id))
241        });
242        rows
243    }
244
245    /// ORCH-20 (controlled tier): answer one listed approval request by its
246    /// row id and one uniform decision.
247    ///
248    /// The decision is translated into the option token and reply envelope
249    /// the door that raised the request already accepts
250    /// ([`crate::approvals::plan_reply`]), and the answer is then sent by
251    /// calling `harness.v1.runtimes.respond` itself — the same code path, the
252    /// same adapter, the same bookkeeping that drops the row. This verb adds
253    /// a translation and nothing else.
254    async fn approvals_resolve(
255        &mut self,
256        params: Value,
257    ) -> std::result::Result<Value, ServiceError> {
258        let params = decode::<crate::approvals::ApprovalsResolveParams>(params)?;
259        if params.id.trim().is_empty() {
260            return Err(ServiceError::InvalidParams(
261                "approvals resolve requires the `id` of a listed approval row".into(),
262            ));
263        }
264        let choice = match (params.decision, params.option_id.as_deref()) {
265            (Some(_), Some(_)) => {
266                return Err(ServiceError::InvalidParams(
267                    "approvals resolve takes either `decision` or `option_id`, not both".into(),
268                ))
269            }
270            (Some(decision), None) => crate::approvals::ApprovalChoice::Decision(decision),
271            (None, Some(option)) => crate::approvals::ApprovalChoice::Option(option.to_string()),
272            (None, None) => {
273                return Err(ServiceError::InvalidParams(format!(
274                    "approvals resolve requires `decision` ({}) or an explicit `option_id`",
275                    crate::approvals::ApprovalDecision::ALL
276                        .map(|decision| decision.as_str())
277                        .join(" | "),
278                )))
279            }
280        };
281        let resolution = self
282            .approvals
283            .resolution(&params.id, &choice)
284            .map_err(|error| ServiceError::InvalidParams(error.to_string()))?;
285        // The harness's own door, unchanged: this is the identical call
286        // `harness.v1.runtimes.respond` performs for a caller who built the
287        // envelope by hand, including dropping the answered row.
288        self.runtime_call(
289            "harness.v1.runtimes.respond",
290            json!({
291                "connection": resolution.connection,
292                "request_id": resolution.request_id,
293                "response": resolution.response,
294            }),
295        )
296        .await?;
297        Ok(json!({
298            "id": params.id,
299            "decision": params.decision.map(|decision| decision.as_str()),
300            "option_id": resolution.option_id,
301            "resolved": true,
302        }))
303    }
304
305    /// Return the edge-triggered wakeup used by session-index filesystem
306    /// subscriptions. Transports can await this instead of polling indexes.
307    #[cfg(feature = "adapter-api")]
308    pub fn session_index_notifier(&self) -> Arc<Notify> {
309        Arc::clone(&self.index_notifier)
310    }
311
312    /// Handle one JSON-RPC 2.0 request and return one JSON-RPC response.
313    #[cfg(feature = "adapter-api")]
314    pub fn handle(&mut self, request: Value) -> Value {
315        let id = request.get("id").cloned().unwrap_or(Value::Null);
316        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
317            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
318        }
319        let Some(method) = request.get("method").and_then(Value::as_str) else {
320            return rpc_error(id, -32600, "request is missing `method`");
321        };
322        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
323        match self.call(method, params) {
324            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
325            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
326            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
327            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
328            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
329            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
330        }
331    }
332
333    /// Handle either a persisted-session request or an asynchronous live
334    /// runtime request.
335    #[cfg(feature = "adapter-api")]
336    pub async fn handle_async(&mut self, request: Value) -> Value {
337        let method = request
338            .get("method")
339            .and_then(Value::as_str)
340            .unwrap_or_default();
341        if matches!(
342            method,
343            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe"
344        ) {
345            let id = request.get("id").cloned().unwrap_or(Value::Null);
346            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
347                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
348            }
349            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
350            return match self.inventory_call(method, params).await {
351                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
352                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
353                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
354                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
355                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
356                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
357            };
358        }
359        if matches!(
360            method,
361            "harness.v1.harnesses.auth.methods"
362                | "harness.v1.harnesses.auth.begin"
363                | "harness.v1.harnesses.auth.verify"
364        ) {
365            let id = request.get("id").cloned().unwrap_or(Value::Null);
366            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
367                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
368            }
369            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
370            return match self.harness_authentication_call(method, params).await {
371                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
372                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
373                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
374                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
375                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
376                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
377            };
378        }
379        // ORCH-19 controlled tier. Answered here rather than through the SDK
380        // operation dispatch below so the harness's OWN refusal reaches the
381        // caller: `sdk_error` collapses every `UnsupportedAction` to one
382        // generic sentence, and the whole point of this tier is that a
383        // refusal names which door the harness does have.
384        if matches!(
385            method,
386            "harness.v1.sessions.new"
387                | "harness.v1.sessions.reset"
388                | "harness.v1.sessions.archive"
389                | "harness.v1.sessions.delete"
390        ) {
391            let id = request.get("id").cloned().unwrap_or(Value::Null);
392            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
393                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
394            }
395            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
396            let verb = match method {
397                "harness.v1.sessions.new" => crate::SessionVerb::New,
398                "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
399                "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
400                _ => crate::SessionVerb::Delete,
401            };
402            return match self.mutate_session(verb, params).await {
403                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
404                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
405                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
406                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
407                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
408                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
409            };
410        }
411        if method == "harness.v1.sessions.message" {
412            let id = request.get("id").cloned().unwrap_or(Value::Null);
413            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
414                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
415            }
416            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
417            return match self.message_call(params).await {
418                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
419                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
420                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
421                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
422                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
423                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
424            };
425        }
426        if matches!(
427            method,
428            "harness.v1.harnesses.settings" | "harness.v1.harnesses.configure"
429        ) {
430            let id = request.get("id").cloned().unwrap_or(Value::Null);
431            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
432                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
433            }
434            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
435            return match self.harness_settings_call(method, params) {
436                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
437                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
438                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
439                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
440                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
441                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
442            };
443        }
444        if method == "harness.v1.sessions.activity_under" {
445            let id = request.get("id").cloned().unwrap_or(Value::Null);
446            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
447            return match activity_under_call(params).await {
448                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
449                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
450                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
451                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
452                Err(_) => rpc_error(id, -32000, "sessions.activity_under failed"),
453            };
454        }
455        if method == "harness.v1.sessions.activity.subscribe" {
456            let id = request.get("id").cloned().unwrap_or(Value::Null);
457            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
458                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
459            }
460            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
461            return match self.subscribe_session_activity(params).await {
462                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
463                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
464                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
465                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
466                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
467                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
468            };
469        }
470        if let Some(operation) = SdkOperation::from_method(method) {
471            let id = request.get("id").cloned().unwrap_or(Value::Null);
472            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
473                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
474            }
475            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
476            return match self.execute(SdkRequest { operation, params }).await {
477                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
478                Err(error) => sdk_rpc_error(id, &error),
479            };
480        }
481        if !method.starts_with("harness.v1.runtimes.") {
482            return self.handle(request);
483        }
484        let id = request.get("id").cloned().unwrap_or(Value::Null);
485        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
486            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
487        }
488        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
489        match self.runtime_call(method, params).await {
490            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
491            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
492            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
493            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
494            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
495            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
496        }
497    }
498
499    /// Poll all active subscriptions once and return zero or more JSON-RPC
500    /// notifications. Recoverable follower errors are delivered as events.
501    #[cfg(feature = "adapter-api")]
502    pub fn poll(&mut self) -> Vec<Value> {
503        let mut notifications = Vec::new();
504        for (subscription, follower) in &mut self.followers {
505            match follower.poll() {
506                Ok(Some(event)) => notifications.push(json!({
507                    "jsonrpc": "2.0",
508                    "method": SESSION_EVENT_METHOD,
509                    "params": {
510                        "subscription": subscription,
511                        "event": event.to_json(),
512                    }
513                })),
514                Ok(None) => {}
515                Err(error) => notifications.push(json!({
516                    "jsonrpc": "2.0",
517                    "method": SESSION_EVENT_METHOD,
518                    "params": {
519                        "subscription": subscription,
520                        "event": {
521                            "type": "watch_error",
522                            "recoverable": true,
523                            "message": error.to_string(),
524                        },
525                    }
526                })),
527            }
528        }
529        notifications
530    }
531
532    /// Report each followed session's live-runtime lifecycle state on that
533    /// session's own subscription, emitting only when the state changes.
534    ///
535    /// A growing transcript is not evidence that an agent is working, so the
536    /// state comes from the live-runtime registry and nowhere else. A followed
537    /// session with no registered Supercode runtime — a harness running outside
538    /// Supercode — reports `persisted`, which says plainly that its activity is
539    /// unknown rather than guessing at it. These events carry no sequence
540    /// number and no transcript content; they never interleave with the
541    /// content follower's sequenced stream.
542    #[cfg(feature = "adapter-api")]
543    pub async fn poll_session_runtime_states(&mut self) -> Vec<Value> {
544        let registry = crate::LocalRuntimeRegistry::new();
545        let authorization = crate::RuntimeAuthorization::observer();
546        let mut notifications = Vec::new();
547        for (subscription, source) in &mut self.followed_sources {
548            let state = match registry
549                .source_state(&source.harness, &source.session_id, &authorization)
550                .await
551            {
552                Ok(Some(state)) => state,
553                Ok(None) => crate::RuntimeRegistryState::Persisted,
554                // A failed registry read is not evidence of a state change.
555                Err(_) => continue,
556            };
557            if source.reported.as_deref() == Some(state.as_str()) {
558                continue;
559            }
560            source.reported = Some(state.as_str().to_string());
561            notifications.push(json!({
562                "jsonrpc": "2.0",
563                "method": SESSION_EVENT_METHOD,
564                "params": {
565                    "subscription": subscription,
566                    "event": {"type": "runtime_state", "state": state.as_str()},
567                },
568            }));
569        }
570        notifications
571    }
572
573    /// Poll normalized activity subscriptions, emitting only proven state
574    /// transitions. Every subscription is bulk-sampled so stock-harness
575    /// process and registry discovery happens once per UI, not once per row.
576    #[cfg(feature = "adapter-api")]
577    pub async fn poll_session_activities(&mut self) -> Vec<Value> {
578        let subscriptions = self
579            .activity_subscriptions
580            .iter()
581            .map(|(id, subscription)| {
582                (
583                    id.clone(),
584                    subscription.locators.clone(),
585                    subscription.homes.clone(),
586                )
587            })
588            .collect::<Vec<_>>();
589        let mut notifications = Vec::new();
590        for (subscription_id, locators, homes) in subscriptions {
591            let Ok(activities) = self.activity_monitor.resolve(&locators, &homes).await else {
592                // A failed evidence read proves no transition. Retain the last
593                // good state instead of flashing every row to persisted.
594                continue;
595            };
596            let Some(subscription) = self.activity_subscriptions.get_mut(&subscription_id) else {
597                continue;
598            };
599            let mut changed = Vec::new();
600            for activity in activities {
601                let key = activity.key();
602                if subscription
603                    .reported
604                    .get(&key)
605                    .is_some_and(|previous| previous.same_state(&activity))
606                {
607                    continue;
608                }
609                subscription.reported.insert(key, activity.clone());
610                changed.push(activity);
611            }
612            if !changed.is_empty() {
613                notifications.push(json!({
614                    "jsonrpc": "2.0",
615                    "method": SESSION_ACTIVITY_EVENT_METHOD,
616                    "params": {
617                        "subscription": subscription_id,
618                        "activities": changed,
619                    },
620                }));
621            }
622        }
623        notifications
624    }
625
626    /// Drain native-store invalidations and emit revisioned descriptor deltas.
627    /// An idle subscription performs no catalog or transcript reads between
628    /// its minute-scale recovery reconciliations.
629    #[cfg(feature = "adapter-api")]
630    pub fn poll_session_indexes(&mut self) -> Vec<Value> {
631        let mut notifications = Vec::new();
632        for (subscription, index) in &mut self.index_subscriptions {
633            let homes = index.homes().clone();
634            match index.poll() {
635                Ok(Some(delta)) => match live_index_changes(delta.changes, &homes) {
636                    Ok(changes) => notifications.push(json!({
637                        "jsonrpc": "2.0",
638                        "method": SESSION_INDEX_EVENT_METHOD,
639                        "params": {
640                            "subscription": subscription,
641                            "revision": delta.revision,
642                            "changes": changes,
643                        },
644                    })),
645                    Err(error) => notifications.push(json!({
646                        "jsonrpc": "2.0",
647                        "method": SESSION_INDEX_EVENT_METHOD,
648                        "params": {
649                            "subscription": subscription,
650                            "error": {"recoverable": true, "message": error_message(error)},
651                        },
652                    })),
653                },
654                Ok(None) => {}
655                Err(error) => notifications.push(json!({
656                    "jsonrpc": "2.0",
657                    "method": SESSION_INDEX_EVENT_METHOD,
658                    "params": {
659                        "subscription": subscription,
660                        "error": {"recoverable": true, "message": error},
661                    },
662                })),
663            }
664        }
665        notifications
666    }
667
668    #[cfg(feature = "adapter-api")]
669    async fn subscribe_session_activity(
670        &mut self,
671        params: Value,
672    ) -> std::result::Result<Value, ServiceError> {
673        let params = decode::<ActivitySubscribeParams>(params)?;
674        if params.locators.is_empty() {
675            return Err(ServiceError::InvalidParams(
676                "sessions.activity.subscribe requires at least one locator".into(),
677            ));
678        }
679        if params.locators.len() > 2_048 {
680            return Err(ServiceError::InvalidParams(
681                "sessions.activity.subscribe accepts at most 2048 locators".into(),
682            ));
683        }
684        let initial = self
685            .activity_monitor
686            .resolve(&params.locators, &params.homes)
687            .await
688            .map_err(ServiceError::Sdk)?;
689        let subscription = format!("activity-sub-{}", self.next_subscription);
690        self.next_subscription += 1;
691        let reported = initial
692            .iter()
693            .cloned()
694            .map(|activity| (activity.key(), activity))
695            .collect();
696        self.activity_subscriptions.insert(
697            subscription.clone(),
698            ActivitySubscription {
699                locators: params.locators,
700                homes: params.homes,
701                reported,
702            },
703        );
704        Ok(json!({"subscription": subscription, "initial": initial}))
705    }
706
707    /// Non-blockingly sample one event from every connected live runtime.
708    #[cfg(feature = "adapter-api")]
709    pub async fn poll_runtimes(&mut self) -> Vec<Value> {
710        self.poll_sdk_events()
711            .await
712            .into_iter()
713            .map(|(connection, runtime_event)| {
714                json!({
715                    "jsonrpc": "2.0",
716                    "method": RUNTIME_EVENT_METHOD,
717                    "params": {
718                        "connection": connection,
719                        "session_id": runtime_event.session_id,
720                        "sequence": runtime_event.event.sequence,
721                        "event": {
722                            "kind": runtime_event.event.kind,
723                            "payload": runtime_event.event.payload,
724                        },
725                    },
726                })
727            })
728            .collect()
729    }
730
731    async fn poll_sdk_events(&mut self) -> Vec<(String, SdkRuntimeEvent)> {
732        let mut events = Vec::new();
733        let mut closed = Vec::new();
734        let now_ms = crate::approvals::now_ms();
735        for (connection, runtime) in &mut self.runtimes {
736            let session_id = runtime.handle().runtime_id.clone();
737            let harness = runtime.handle().harness.clone();
738            // Drain what the runtime already has: a turn is several events
739            // (updates, then the protocol's completion), and delivering one
740            // per poll would cost a poll interval each. A zero timeout takes
741            // only what is ready — an idle runtime costs nothing.
742            for _ in 0..256 {
743                match tokio::time::timeout(Duration::ZERO, runtime.next_event()).await {
744                    Ok(Ok(Some(event))) => {
745                        let terminal = event.kind == "transport_closed";
746                        // ORCH-9: a permission/approval request arrives as an
747                        // ordinary event; it becomes listable here and stops
748                        // being listable when `runtimes.respond` answers it.
749                        self.approvals
750                            .observe(connection, &harness, &session_id, &event, now_ms);
751                        let next_sequence = self
752                            .runtime_sequences
753                            .entry(session_id.clone())
754                            .or_insert(0);
755                        let sequence = event.sequence.unwrap_or_else(|| {
756                            *next_sequence = next_sequence.saturating_add(1);
757                            *next_sequence
758                        });
759                        *next_sequence = (*next_sequence).max(sequence);
760                        events.push((
761                            connection.clone(),
762                            SdkRuntimeEvent {
763                                session_id: session_id.clone(),
764                                event: SdkEvent {
765                                    sequence,
766                                    kind: event.kind,
767                                    payload: event.payload,
768                                },
769                            },
770                        ));
771                        if terminal {
772                            closed.push(connection.clone());
773                            break;
774                        }
775                    }
776                    Ok(Ok(None)) => {
777                        let sequence = self
778                            .runtime_sequences
779                            .entry(session_id.clone())
780                            .or_insert(0);
781                        *sequence = sequence.saturating_add(1);
782                        events.push((
783                        connection.clone(),
784                        SdkRuntimeEvent {
785                            session_id,
786                            event: SdkEvent {
787                                sequence: *sequence,
788                                kind: "transport_closed".into(),
789                                payload: json!({"message": "Harness runtime transport closed."}),
790                            },
791                        },
792                    ));
793                        closed.push(connection.clone());
794                        break;
795                    }
796                    Err(_) => break,
797                    Ok(Err(error)) => {
798                        let sequence = self
799                            .runtime_sequences
800                            .entry(session_id.clone())
801                            .or_insert(0);
802                        *sequence = sequence.saturating_add(1);
803                        events.push((
804                        connection.clone(),
805                        SdkRuntimeEvent {
806                            session_id,
807                            event: SdkEvent {
808                                sequence: *sequence,
809                                kind: "transport_error".into(),
810                                payload: json!({"message": error.to_string(), "terminal": true}),
811                            },
812                        },
813                    ));
814                        closed.push(connection.clone());
815                        break;
816                    }
817                }
818            }
819        }
820        for connection in closed {
821            if let Some(runtime) = self.runtimes.remove(&connection) {
822                self.runtime_sequences.remove(&runtime.handle().runtime_id);
823            }
824            self.terminal_launches.remove(&connection);
825            // A connection that is gone cannot answer anything it was
826            // holding; those requests stop being listable with it.
827            self.approvals.forget(&connection);
828        }
829        events
830    }
831
832    fn call(&mut self, method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
833        match method {
834            "harness.v1.capabilities" => Ok(json!({
835                "version": HARNESS_SERVICE_VERSION,
836                "sdk": self.capabilities(),
837                "methods": HARNESS_SERVICE_METHODS,
838                "notifications": [
839                    SESSION_EVENT_METHOD,
840                    SESSION_ACTIVITY_EVENT_METHOD,
841                    SESSION_INDEX_EVENT_METHOD,
842                    RUNTIME_EVENT_METHOD
843                ],
844                "harnesses": harness_support_registry()
845                    .harnesses
846                    .into_iter()
847                    .map(|harness| harness.id)
848                    .collect::<Vec<_>>(),
849            })),
850            "harness.v1.support.report" => serde_json::to_value(harness_support_registry())
851                .map_err(|error| ServiceError::Operation(error.to_string())),
852            "harness.v1.profiles.list" | "harness.v1.profiles.get" => profiles_call(method, params),
853            // ORCH-21 controlled tier. Each verb translates to the HARNESS'S
854            // OWN profile verb and runs it (`crate::profiles_control`);
855            // supercode makes and removes nothing itself. The row returned is
856            // re-read through the ORCH-10 loader afterwards, and `ran`
857            // narrates the exact command.
858            "harness.v1.profiles.create" => {
859                mutate_profile(crate::profiles_control::ProfileVerb::Create, params)
860            }
861            "harness.v1.profiles.delete" => {
862                mutate_profile(crate::profiles_control::ProfileVerb::Delete, params)
863            }
864            "harness.v1.channels.list" | "harness.v1.channels.status" => {
865                channels_call(method, params)
866            }
867            // ORCH-15 observed tier: which profile / agent a surface tuple
868            // resolves to, read from each gateway harness's own config.
869            "harness.v1.routes.list" => routes_call(params),
870            // ORCH-16 observed tier: inbound webhook routes / hook mappings.
871            "harness.v1.triggers.list" => triggers_call(params),
872            // ONT-4: the orchestration doors. One home folder in, one typed orchestration
873            // value out (and back). Every one of the four is
874            // `crate::orchestration_doors`, which the `supercode orchestration` verbs call
875            // too — the RPC adds nothing but the envelope. A vault VALUE
876            // never crosses this wire: a load or a compile answers with the
877            // `.env` KEY NAMES, and a caller that needs a value reads the
878            // home's own `.env`.
879            // the workflow layer's read door: a harness's board as one typed value,
880            // the same code the `supercode workflow load` verb calls
881            "harness.v1.workflow.load" => {
882                let params = decode::<WorkflowLoadParams>(params)?;
883                let read =
884                    crate::workflow_doors::load(params.from, &params.home).map_err(operation)?;
885                serde_json::to_value(read)
886                    .map_err(|error| ServiceError::Operation(error.to_string()))
887            }
888            "harness.v1.orchestration.load" => {
889                let params = decode::<OrchestrationLoadParams>(params)?;
890                let read = crate::orchestration_doors::load(&params.root, params.flavor)
891                    .map_err(operation)?;
892                serde_json::to_value(read)
893                    .map_err(|error| ServiceError::Operation(error.to_string()))
894            }
895            "harness.v1.orchestration.save" => {
896                let params = decode::<OrchestrationSaveParams>(params)?;
897                let saved = crate::orchestration_doors::save(
898                    &params.root,
899                    params.orchestration,
900                    params.vault,
901                )
902                .map_err(operation)?;
903                serde_json::to_value(saved)
904                    .map_err(|error| ServiceError::Operation(error.to_string()))
905            }
906            "harness.v1.orchestration.compile" => {
907                let params = decode::<OrchestrationCompileParams>(params)?;
908                let read = crate::orchestration_doors::compile(params.from, &params.home)
909                    .map_err(operation)?;
910                serde_json::to_value(read)
911                    .map_err(|error| ServiceError::Operation(error.to_string()))
912            }
913            "harness.v1.orchestration.decompile" => {
914                let params = decode::<OrchestrationDecompileParams>(params)?;
915                let report = crate::orchestration_doors::decompile(
916                    params.to,
917                    params.orchestration,
918                    &params.source,
919                    params.source_flavor,
920                    &params.dest,
921                    params.vault,
922                )
923                .map_err(operation)?;
924                serde_json::to_value(report)
925                    .map_err(|error| ServiceError::Operation(error.to_string()))
926            }
927            // a migration keeps the credential in this process: a compile and
928            // a save (import), a load and a decompile (export), composed here
929            // because composed by a client the secret would have to cross
930            // the wire
931            "harness.v1.orchestration.import" => {
932                let params = decode::<OrchestrationImportParams>(params)?;
933                let imported =
934                    crate::orchestration_doors::import(params.from, &params.home, &params.into)
935                        .map_err(operation)?;
936                serde_json::to_value(imported)
937                    .map_err(|error| ServiceError::Operation(error.to_string()))
938            }
939            "harness.v1.orchestration.export" => {
940                let params = decode::<OrchestrationExportParams>(params)?;
941                let report =
942                    crate::orchestration_doors::export(params.to, &params.root, &params.dest)
943                        .map_err(operation)?;
944                serde_json::to_value(report)
945                    .map_err(|error| ServiceError::Operation(error.to_string()))
946            }
947            // ORCH-12 observed tier: read and search the persistent memory
948            // documents a harness keeps on disk. Read-only — every write
949            // (`hermes memory off`, `openclaw memory forget|reset`, Claude
950            // Code's `/memory`) stays the harness's own verb. A harness with
951            // no memory store is refused with UnsupportedAction.
952            "harness.v1.memory.show" | "harness.v1.memory.search" => memory_call(method, params),
953            // ORCH-11 observed tier: read-only enumeration of every harness's
954            // installed skill packages. An unknown harness id is refused with
955            // UnsupportedAction — every harness supports skills, so a filter
956            // that matches nothing is a caller error, never an empty listing.
957            "harness.v1.skills.list" => {
958                let query = decode::<crate::skills::SkillsQuery>(params)?;
959                if let Some(harness) = query.harness.as_deref() {
960                    if !crate::skills::SKILL_HARNESSES.contains(&harness) {
961                        return Err(ServiceError::UnsupportedAction(format!(
962                            "`{harness}` has no skills root Volter Harness reads"
963                        )));
964                    }
965                }
966                serde_json::to_value(crate::skills::list_skills(&query))
967                    .map_err(|error| ServiceError::Operation(error.to_string()))
968            }
969            // ORCH-22 controlled tier: each verb goes through the door the
970            // HARNESS publishes — `hermes skills install|uninstall`,
971            // `openclaw skills install`, and for the core four the loader's
972            // own directory, which is the only skills door those harnesses
973            // have. supercode resolves no registry and unpacks no archive.
974            // The row returned is re-read through the ORCH-11 loader
975            // afterwards, and `ran` narrates exactly what was performed.
976            "harness.v1.skills.install" => {
977                mutate_skill(crate::skills_control::SkillVerb::Install, params)
978            }
979            "harness.v1.skills.remove" => {
980                mutate_skill(crate::skills_control::SkillVerb::Remove, params)
981            }
982            // ORCH-9 observed tier: the approval requests waiting for an
983            // answer. At the pinned harness versions the only uniform source
984            // is a LIVE request held by an open runtime connection, plus
985            // supercode's own queued subagent approvals — neither Hermes
986            // 0.21.0 nor OpenClaw 2026.7.1-2 has an approvals door to read
987            // (see `crate::approvals`). A harness whose runtime cannot carry
988            // a protocol request at all is refused by name.
989            "harness.v1.approvals.list" => {
990                let query = decode::<crate::approvals::ApprovalsQuery>(params)?;
991                if let Some(harness) = query.harness.as_deref() {
992                    if !crate::approvals::lists_approvals(harness) {
993                        return Err(ServiceError::UnsupportedAction(format!(
994                            "`{harness}` has no runtime door that carries an approval request"
995                        )));
996                    }
997                }
998                serde_json::to_value(self.approvals(&query))
999                    .map_err(|error| ServiceError::Operation(error.to_string()))
1000            }
1001            "harness.v1.sessions.discover" => {
1002                let query = decode::<DiscoveryQuery>(params)?;
1003                let page = discover_session_page(&query).map_err(operation)?;
1004                // Claude Code is the one harness that publishes its RUNNING
1005                // sessions. The registry is read once per discovery and joined
1006                // by session id; every record in it has already survived a
1007                // `kill(pid, 0)` liveness check inside `read_registry`.
1008                let doors = crate::mail_route::LiveSessions::read(&query.homes);
1009                let activities = crate::session_activity::resolve_stock_session_activities(
1010                    &page
1011                        .sessions
1012                        .iter()
1013                        .map(|session| session.locator.clone())
1014                        .collect::<Vec<_>>(),
1015                    &query.homes,
1016                )
1017                .into_iter()
1018                .map(|activity| (activity.key(), activity))
1019                .collect::<BTreeMap<_, _>>();
1020                let sessions = page
1021                    .sessions
1022                    .into_iter()
1023                    .map(|session| {
1024                        let mut value = live_descriptor_value(&session, &doors)?;
1025                        let activity_key = (
1026                            session.locator.harness.as_str().to_string(),
1027                            session.locator.session_id.clone(),
1028                        );
1029                        if let Some(activity) = activities.get(&activity_key) {
1030                            value["activity"] = serde_json::to_value(activity)
1031                                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1032                            if let Some(status) = legacy_live_status(activity) {
1033                                value["live_status"] = json!(status);
1034                            }
1035                        }
1036                        Ok(value)
1037                    })
1038                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1039                let mut result = json!({"sessions": sessions, "next_cursor": page.next_cursor});
1040                // Preserve the metadata-only wire shape, but carry the catalog's
1041                // proof/counts when the caller explicitly requests preview search.
1042                if query.search_previews {
1043                    result["receipt"] = serde_json::to_value(page.receipt)
1044                        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1045                }
1046                Ok(result)
1047            }
1048            "harness.v1.sessions.inbox" => inbox_call(decode::<InboxParams>(params)?),
1049            "harness.v1.sessions.load" => {
1050                let params = decode::<LoadSessionParams>(params)?;
1051                if let Some(options) = &params.options {
1052                    options.validate()?;
1053                    if let Some(result) = indexed_claude_window(&params.read.locator, options)? {
1054                        return Ok(result);
1055                    }
1056                    return load_session(&params.read.locator)
1057                        .map(|session| projected_session_result(&session, options))
1058                        .map_err(operation);
1059                }
1060                let mut session = if params.read.display_history() {
1061                    self.catalog
1062                        .load_display_view(
1063                            &params.read.locator,
1064                            params.read.read_fidelity(),
1065                            params.read.tail_messages().unwrap_or(500),
1066                        )
1067                        .map_err(crate::Error::from)
1068                } else if params.read.include_subagents() {
1069                    load_session_with_fidelity(&params.read.locator, params.read.read_fidelity())
1070                } else {
1071                    self.catalog
1072                        .load_parent_with_fidelity(
1073                            &params.read.locator,
1074                            params.read.read_fidelity(),
1075                        )
1076                        .map_err(crate::Error::from)
1077                }
1078                .map_err(operation)?;
1079                params.read.bound_session(&mut session);
1080                Ok(json!({"session": normalized_session_json(&session)}))
1081            }
1082            "harness.v1.sessions.follow" => {
1083                let params = decode::<LocatorParams>(params)?;
1084                let mut follower = self
1085                    .catalog
1086                    .follow_read_view(
1087                        &params.locator,
1088                        params.read_fidelity(),
1089                        params.include_subagents(),
1090                        params.tail_messages(),
1091                        params.max_message_chars(),
1092                        params.display_history(),
1093                    )
1094                    .map_err(operation)?;
1095                let initial = follower
1096                    .poll()
1097                    .map_err(operation)?
1098                    .map(|event| event.to_json());
1099                let subscription = format!("sub-{}", self.next_subscription);
1100                self.next_subscription += 1;
1101                self.followers.insert(subscription.clone(), follower);
1102                self.followed_sources.insert(
1103                    subscription.clone(),
1104                    FollowedSource {
1105                        harness: params.locator.harness.as_str().to_string(),
1106                        session_id: params.locator.session_id.clone(),
1107                        reported: None,
1108                    },
1109                );
1110                Ok(json!({"subscription": subscription, "initial": initial}))
1111            }
1112            "harness.v1.sessions.unfollow" => {
1113                let params = decode::<UnfollowParams>(params)?;
1114                self.followed_sources.remove(&params.subscription);
1115                Ok(json!({
1116                    "removed": self.followers.remove(&params.subscription).is_some()
1117                }))
1118            }
1119            "harness.v1.sessions.activity.unsubscribe" => {
1120                let params = decode::<UnfollowParams>(params)?;
1121                Ok(json!({
1122                    "removed": self.activity_subscriptions.remove(&params.subscription).is_some()
1123                }))
1124            }
1125            "harness.v1.sessions.index.subscribe" => {
1126                let query = decode::<DiscoveryQuery>(params)?;
1127                crate::session_index::validate_query(&query)
1128                    .map_err(ServiceError::InvalidParams)?;
1129                let homes = query.homes.clone();
1130                let (index, initial) = crate::session_index::SessionIndexSubscription::open(
1131                    query,
1132                    Arc::clone(&self.index_notifier),
1133                )
1134                .map_err(ServiceError::Operation)?;
1135                let doors = crate::mail_route::LiveSessions::read(&homes);
1136                let initial = initial
1137                    .iter()
1138                    .map(|descriptor| live_descriptor_value(descriptor, &doors))
1139                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1140                let subscription = format!("index-sub-{}", self.next_subscription);
1141                self.next_subscription += 1;
1142                self.index_subscriptions.insert(subscription.clone(), index);
1143                Ok(json!({
1144                    "subscription": subscription,
1145                    "revision": 1,
1146                    "initial": initial,
1147                }))
1148            }
1149            "harness.v1.sessions.index.resize" => {
1150                let params = decode::<IndexResizeParams>(params)?;
1151                crate::session_index::validate_limit(params.limit)
1152                    .map_err(ServiceError::InvalidParams)?;
1153                let index = self
1154                    .index_subscriptions
1155                    .get_mut(&params.subscription)
1156                    .ok_or_else(|| {
1157                        ServiceError::InvalidParams("unknown session index subscription".into())
1158                    })?;
1159                let prepared = index
1160                    .prepare_resize(params.limit)
1161                    .map_err(ServiceError::Operation)?;
1162                let doors = crate::mail_route::LiveSessions::read(index.homes());
1163                let initial = prepared
1164                    .page
1165                    .sessions
1166                    .iter()
1167                    .map(|descriptor| live_descriptor_value(descriptor, &doors))
1168                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1169                let response = json!({
1170                    "subscription": params.subscription,
1171                    "revision": prepared.revision,
1172                    "initial": initial,
1173                    "receipt": prepared.page.receipt,
1174                });
1175                index.commit_resize(prepared);
1176                Ok(response)
1177            }
1178            "harness.v1.sessions.index.unsubscribe" => {
1179                let params = decode::<UnfollowParams>(params)?;
1180                Ok(json!({
1181                    "removed": self.index_subscriptions.remove(&params.subscription).is_some()
1182                }))
1183            }
1184            "harness.v1.sessions.import" => {
1185                let params = decode::<ImportSessionParams>(params)?;
1186                let session = Session::load_str(&params.content, params.source_harness.into())
1187                    .map_err(operation)?;
1188                Ok(json!({"session": normalized_session_json(&session)}))
1189            }
1190            "harness.v1.sessions.export" | "harness.v1.sessions.translate" => {
1191                let params = decode::<ExportSessionParams>(params)?;
1192                let session = load_session(&params.locator).map_err(operation)?;
1193                let artifact = session_artifact(&params.locator, &session, params.target_harness)?;
1194                if method == "harness.v1.sessions.export"
1195                    && params.target_harness == TransferFormat::Hermes
1196                {
1197                    // UNI-18: write through Hermes's own door, never into its store
1198                    let imported = crate::hermes_import::import_into_hermes(&session, None)
1199                        .map_err(operation)?;
1200                    return Ok(json!({"artifact": artifact, "imported": imported}));
1201                }
1202                Ok(json!({"artifact": artifact}))
1203            }
1204            "harness.v1.sessions.reduce" => {
1205                let params = decode::<ReduceSessionParams>(params)?;
1206                self.reduce_session(params)
1207            }
1208            "harness.v1.sessions.branch" => {
1209                let params = decode::<BranchSessionParams>(params)?;
1210                let session = load_session(&params.locator).map_err(operation)?;
1211                let storage = params.locator.storage.path().display().to_string();
1212                let bootstrap_prompt = format!(
1213                    "Continue as a new branch from {} session {}. The frozen parent transcript is at {}. Read or load that parent for context, summarize the relevant state, then continue independently without mutating the parent session.",
1214                    params.locator.harness.as_str(), params.locator.session_id, storage
1215                );
1216                let artifact = params
1217                    .target_harness
1218                    .map(|target| session_artifact(&params.locator, &session, target))
1219                    .transpose()?;
1220                Ok(json!({
1221                    "parent": params.locator,
1222                    "session": normalized_session_json(&session),
1223                    "bootstrap_prompt": bootstrap_prompt,
1224                    "artifact": artifact,
1225                }))
1226            }
1227            "harness.v1.sessions.handoff" => {
1228                let params = decode::<HandoffSessionParams>(params)?;
1229                let session = load_session(&params.locator).map_err(operation)?;
1230                let cwd = params
1231                    .cwd
1232                    .or_else(|| session.meta.cwd.clone())
1233                    .unwrap_or_else(|| PathBuf::from("."));
1234                let artifact = handoff_artifact(&params.locator, &session, params.target_harness)?;
1235                let target_session_id = artifact.session_id.as_deref().ok_or_else(|| {
1236                    ServiceError::Operation(
1237                        "handoff artifact omitted target session identity".into(),
1238                    )
1239                })?;
1240                let instructions =
1241                    handoff_instructions(params.target_harness, target_session_id, &cwd);
1242                Ok(json!({
1243                    "artifact": artifact,
1244                    "launch": instructions.launch,
1245                    "materialize": instructions.materialize,
1246                    "requires_materialization": instructions.requires_materialization,
1247                    "note": instructions.note,
1248                }))
1249            }
1250            "harness.v1.sessions.materialize" => {
1251                let params = decode::<MaterializeSessionParams>(params)?;
1252                // An artifact from another machine carries its whole source as a recovery file;
1253                // keeping its segments here lets a later write back to that format restore it
1254                // byte for byte on this machine too (docs/plans/portable-residue.md).
1255                for file in &params.artifact.files {
1256                    if file.role == "source_recovery"
1257                        && file.path == "recovery/source.supercode.jsonl"
1258                    {
1259                        if let Ok(source) = Session::from_native_str(&file.content) {
1260                            crate::residue_store::store_segments(&source);
1261                        }
1262                    }
1263                }
1264                let locator = crate::native_materialize::materialize_native_artifact(
1265                    params.artifact,
1266                    &params.cwd,
1267                    &params.homes,
1268                )
1269                .map_err(ServiceError::Operation)?;
1270                Ok(json!({"locator": locator}))
1271            }
1272            // ORCH-7 observed tier. Read-only: the handlers open the harness's
1273            // own job store (Claude Code's session JSONL, Hermes's and
1274            // OpenClaw's `cron/jobs.json`) and never write, fire, or schedule.
1275            "harness.v1.jobs.list" => {
1276                let query = decode::<crate::jobs::JobsQuery>(params)?;
1277                if let Some(harness) = query.harness.as_deref() {
1278                    refuse_harness_without_jobs(harness, "jobs.list")?;
1279                }
1280                let listing = crate::jobs::list_jobs(&query).map_err(operation)?;
1281                serde_json::to_value(listing)
1282                    .map_err(|error| ServiceError::Operation(error.to_string()))
1283            }
1284            "harness.v1.jobs.get" => {
1285                let params = decode::<JobsGetParams>(params)?;
1286                refuse_harness_without_jobs(&params.harness, "jobs.get")?;
1287                match crate::jobs::get_job(&params.harness, &params.id, &params.homes)
1288                    .map_err(operation)?
1289                {
1290                    Some((job, source)) => Ok(json!({"job": job, "source": source})),
1291                    None => Err(ServiceError::Operation(format!(
1292                        "`{}` has no scheduled job `{}`",
1293                        params.harness, params.id
1294                    ))),
1295                }
1296            }
1297            // ORCH-18 controlled tier. Each verb translates to the HARNESS'S
1298            // OWN cron verb and runs it (`crate::jobs_control`); supercode
1299            // schedules nothing. The row returned is re-read from the
1300            // harness's store afterwards, and `ran` narrates the exact command
1301            // with any credential redacted.
1302            "harness.v1.jobs.create" => mutate_job(crate::jobs_control::JobVerb::Create, params),
1303            "harness.v1.jobs.update" => mutate_job(crate::jobs_control::JobVerb::Update, params),
1304            "harness.v1.jobs.pause" => mutate_job(crate::jobs_control::JobVerb::Pause, params),
1305            "harness.v1.jobs.resume" => mutate_job(crate::jobs_control::JobVerb::Resume, params),
1306            "harness.v1.jobs.run" => mutate_job(crate::jobs_control::JobVerb::Run, params),
1307            "harness.v1.jobs.delete" => mutate_job(crate::jobs_control::JobVerb::Delete, params),
1308            "harness.v1.jobs.notepad"
1309            | "harness.v1.jobs.notepad_set"
1310            | "harness.v1.jobs.notepad_delete" => {
1311                let request = decode::<crate::jobs_notepad::JobNotepadRequest>(params)?;
1312                refuse_harness_without_jobs(&request.harness, "jobs.notepad")?;
1313                let answer = match method {
1314                    "harness.v1.jobs.notepad_set" => crate::jobs_notepad::set(&request),
1315                    "harness.v1.jobs.notepad_delete" => crate::jobs_notepad::delete(&request),
1316                    _ => crate::jobs_notepad::read(&request),
1317                }
1318                .map_err(job_control_error)?;
1319                serde_json::to_value(answer)
1320                    .map_err(|error| ServiceError::Operation(error.to_string()))
1321            }
1322            // ORCH-8 observed tier. Read-only: the handlers open the harness's
1323            // own run store (Hermes's `cron/executions.db`, OpenClaw's
1324            // `cron_run_logs`) and never claim, retry, or prune a fire.
1325            "harness.v1.runs.list" => {
1326                let query = decode::<crate::runs::RunsQuery>(params)?;
1327                if let Some(harness) = query.harness.as_deref() {
1328                    refuse_harness_without_runs(harness, "runs.list")?;
1329                }
1330                let listing = crate::runs::list_runs(&query).map_err(operation)?;
1331                serde_json::to_value(listing)
1332                    .map_err(|error| ServiceError::Operation(error.to_string()))
1333            }
1334            "harness.v1.runs.get" => {
1335                let params = decode::<RunsGetParams>(params)?;
1336                refuse_harness_without_runs(&params.harness, "runs.get")?;
1337                match crate::runs::get_run(&params.harness, &params.id, &params.homes)
1338                    .map_err(operation)?
1339                {
1340                    Some((run, source)) => Ok(json!({"run": run, "source": source})),
1341                    None => Err(ServiceError::Operation(format!(
1342                        "`{}` has no run `{}`",
1343                        params.harness, params.id
1344                    ))),
1345                }
1346            }
1347            "harness.v1.sessions.resume_instructions" => {
1348                let params = decode::<ResumeInstructionsParams>(params)?;
1349                let session = load_session(&params.locator).map_err(operation)?;
1350                let cwd = params
1351                    .cwd
1352                    .or(session.meta.cwd)
1353                    .unwrap_or_else(|| PathBuf::from("."));
1354                let launch = resume_launch(
1355                    params.locator.harness.as_str(),
1356                    &params.locator.session_id,
1357                    &cwd,
1358                    params.policy,
1359                )?;
1360                Ok(json!({"launch": launch}))
1361            }
1362            _ => Err(ServiceError::MethodNotFound),
1363        }
1364    }
1365
1366    fn reduce_session(
1367        &self,
1368        params: ReduceSessionParams,
1369    ) -> std::result::Result<Value, ServiceError> {
1370        let session = load_session(&params.locator).map_err(operation)?;
1371        if session.messages.is_empty() {
1372            return Err(ServiceError::InvalidParams(
1373                "cannot reduce an empty session".into(),
1374            ));
1375        }
1376        let keep_last = params.keep_last.clamp(1, 128);
1377        let policy = reduce::ReductionPolicy {
1378            clear_turns_older_than: Some(keep_last),
1379            ..Default::default()
1380        };
1381        let (view, log) =
1382            reduce::project_messages(&session.messages, &policy, &reduce::ReductionLog::default());
1383        if log.reductions.is_empty() {
1384            return Err(ServiceError::UnsupportedAction(format!(
1385                "session `{}` is already too small for a meaningful reversible reduction",
1386                params.locator.session_id
1387            )));
1388        }
1389        let source_tokens = supercode_runtime::estimate_view_tokens(&session.messages);
1390        let reduced_tokens = supercode_runtime::estimate_view_tokens(&view);
1391        if reduced_tokens >= source_tokens {
1392            return Err(ServiceError::UnsupportedAction(format!(
1393                "session `{}` has no token-reducing reversible projection",
1394                params.locator.session_id
1395            )));
1396        }
1397
1398        let store_root = self
1399            .reduction_store_root
1400            .clone()
1401            .unwrap_or_else(default_reduction_store_root);
1402        let store = crate::SessionStore::open(&store_root).map_err(operation)?;
1403        let rescue_id = format!("rescue-{}", generated_session_id());
1404        let imported = session
1405            .imported_message_count
1406            .unwrap_or(session.messages.len())
1407            .min(session.messages.len());
1408        let sidecar_jsonl = session.to_native_jsonl_v2(&session.messages[imported..]);
1409        let view_jsonl = messages_jsonl(&view)?;
1410        let title = format!(
1411            "Reduced {} continuation from {}",
1412            params.target_harness.id(),
1413            params.locator.session_id
1414        );
1415
1416        // Durability order is intentional: the full source of truth lands
1417        // before either object that can refer to it. A crash may leave an
1418        // unused sidecar, but can never leave a reduced view whose originals
1419        // were not durably written first.
1420        store
1421            .save_sidecar(&rescue_id, &sidecar_jsonl)
1422            .map_err(operation)?;
1423        store
1424            .save_reduction_log(&rescue_id, &log)
1425            .map_err(operation)?;
1426        store
1427            .save(&rescue_id, &title, &view_jsonl)
1428            .map_err(operation)?;
1429
1430        let source_bytes = serde_json::to_vec(&session.messages)
1431            .map_err(|error| ServiceError::Operation(error.to_string()))?
1432            .len() as u64;
1433        let reduced_bytes = serde_json::to_vec(&view)
1434            .map_err(|error| ServiceError::Operation(error.to_string()))?
1435            .len() as u64;
1436        store
1437            .set_reduction_stats(
1438                &rescue_id,
1439                &title,
1440                source_bytes,
1441                reduced_bytes,
1442                log.reductions.len() as u32,
1443            )
1444            .map_err(operation)?;
1445
1446        // The receipt is issued only after a real disk reload. This proves
1447        // the exact files another process will consume, not the convenient
1448        // in-memory values that produced them.
1449        let reloaded_sidecar = store
1450            .load_sidecar(&rescue_id)
1451            .map_err(operation)?
1452            .ok_or_else(|| ServiceError::Operation("reduction sidecar disappeared".into()))?;
1453        let reloaded_sidecar = Session::from_sidecar_str(&reloaded_sidecar).map_err(operation)?;
1454        let reloaded_log = store
1455            .load_reduction_log(&rescue_id)
1456            .map_err(operation)?
1457            .ok_or_else(|| ServiceError::Operation("reduction log disappeared".into()))?;
1458        let reloaded_view = parse_messages_jsonl(&store.load(&rescue_id).map_err(operation)?)?;
1459        reduce::verify_log(&reloaded_log, &reloaded_sidecar).map_err(operation)?;
1460        // `sc.reduction` is deliberately in-memory-only metadata: it must
1461        // never leak onto a provider-facing transcript. Reapplying the
1462        // durable log to the durable sidecar restores those ids. Comparing
1463        // its wire form with the transcript reloaded above proves that the
1464        // persisted view is exactly the deterministic projection before we
1465        // use the restamped form for inversion.
1466        let (restamped_view, restamped_log) =
1467            reduce::project_messages(&reloaded_sidecar.messages, &policy, &reloaded_log);
1468        if messages_jsonl(&restamped_view)? != messages_jsonl(&reloaded_view)? {
1469            return Err(ServiceError::Operation(
1470                "persisted reduction view does not match its durable log and sidecar".into(),
1471            ));
1472        }
1473        if restamped_log != reloaded_log {
1474            return Err(ServiceError::Operation(
1475                "reapplying the durable reduction log changed its identity".into(),
1476            ));
1477        }
1478        let inverted =
1479            reduce::invert(&restamped_view, &reloaded_log, &reloaded_sidecar).map_err(operation)?;
1480        if inverted != session.messages {
1481            return Err(ServiceError::Operation(
1482                "reduction inversion did not restore the source messages byte-exactly".into(),
1483            ));
1484        }
1485
1486        let ratio = source_tokens as f64 / reduced_tokens.max(1) as f64;
1487        let sidecar_path = store.sidecar_path(&rescue_id);
1488        let reduction_log_path = store.reduction_log_path(&rescue_id).map_err(operation)?;
1489        let bootstrap_prompt = reduced_bootstrap_prompt(
1490            &params.locator,
1491            params.target_harness,
1492            &view_jsonl,
1493            &sidecar_path,
1494            &reduction_log_path,
1495        );
1496        let mut reduced_session = session.clone();
1497        reduced_session.meta.session_id = Some(rescue_id.clone());
1498        reduced_session.messages = view;
1499
1500        Ok(json!({
1501            "session": normalized_session_json(&reduced_session),
1502            "bootstrap_prompt": bootstrap_prompt,
1503            "receipt": {
1504                "id": rescue_id,
1505                "sidecar_id": rescue_id,
1506                "source_harness": params.locator.harness,
1507                "target_harness": params.target_harness.id(),
1508                "source_tokens": source_tokens,
1509                "reduced_tokens": reduced_tokens,
1510                "ratio": ratio,
1511                "source_bytes": source_bytes,
1512                "reduced_bytes": reduced_bytes,
1513                "reductions": reloaded_log.reductions.len(),
1514                "sidecar_path": sidecar_path,
1515                "reduction_log_path": reduction_log_path,
1516                "verified": true,
1517                "reversible": true,
1518            }
1519        }))
1520    }
1521
1522    /// Recognize the one request family whose waiting happens entirely
1523    /// outside this service's state, and hand a transport the half it can run
1524    /// off the task that owns the service.
1525    ///
1526    /// Opening a runtime is the only door here that waits on a foreign
1527    /// program: it spawns the harness's own binary and completes that
1528    /// program's protocol handshake, which takes as long as the program takes
1529    /// to answer. A transport that awaited the whole request inline would
1530    /// stop reading its own input for that whole time, so ONE slow launch
1531    /// would queue every later request on the same server — including reads
1532    /// like `sessions.discover` that touch no runtime at all. Splitting the
1533    /// request lets the transport spawn [`RuntimeOpen::open`] and keep
1534    /// reading, then pay only the short bookkeeping half
1535    /// ([`Self::register_open_runtime`]) when the runtime is up.
1536    ///
1537    /// `None` for every other method: those are answered by
1538    /// [`Self::handle_async`] as before.
1539    pub fn runtime_open(request: &Value) -> Option<RuntimeOpen> {
1540        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1541            return None;
1542        }
1543        let method = request.get("method").and_then(Value::as_str)?;
1544        if !RUNTIME_OPEN_METHODS.contains(&method) {
1545            return None;
1546        }
1547        Some(RuntimeOpen {
1548            id: request.get("id").cloned().unwrap_or(Value::Null),
1549            method: method.to_string(),
1550            params: request.get("params").cloned().unwrap_or_else(|| json!({})),
1551        })
1552    }
1553
1554    /// Recognize a [`DETACHED_METHODS`] request and hand a transport the
1555    /// whole of it: the service-state half is read here and now, and what
1556    /// remains waits on a foreign program with nothing of this service's in
1557    /// hand.
1558    ///
1559    /// Same reason as [`Self::runtime_open`], different doors. Probing a
1560    /// harness starts it and completes its handshake; messaging a live
1561    /// session waits on its Claude relay's send; a conversation verb runs the
1562    /// harness's own CLI or calls its HTTP API. A transport that awaited any
1563    /// of those inline would stop reading its own input for that whole time,
1564    /// so one probe of an unhealthy harness would queue every later request
1565    /// on the same server.
1566    ///
1567    /// Unlike an opening runtime there is no bookkeeping half: the answer
1568    /// [`DetachedCall::run`] produces is the caller's complete response, so a
1569    /// transport writes it without coming back here.
1570    ///
1571    /// `None` for every other method — including the LIVE `sessions.new` /
1572    /// `sessions.reset` door and `runtimes.close`, which wait on a runtime
1573    /// connection this service owns and so are split off by
1574    /// [`Self::detach_runtime`] instead.
1575    pub fn detach(&self, request: &Value) -> Option<DetachedCall> {
1576        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1577            return None;
1578        }
1579        let method = request.get("method").and_then(Value::as_str)?;
1580        if !DETACHED_METHODS.contains(&method) {
1581            return None;
1582        }
1583        let id = request.get("id").cloned().unwrap_or(Value::Null);
1584        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1585        let work = match method {
1586            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe" => self
1587                .inventory_work(method, params)
1588                .map(DetachedWork::Inventory),
1589            "harness.v1.sessions.message" => {
1590                decode::<MessageSessionParams>(params).map(DetachedWork::Message)
1591            }
1592            _ => {
1593                let verb = match method {
1594                    "harness.v1.sessions.new" => crate::SessionVerb::New,
1595                    "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
1596                    "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
1597                    _ => crate::SessionVerb::Delete,
1598                };
1599                match decode::<crate::SessionMutation>(params) {
1600                    Ok(mutation) => {
1601                        match crate::sessions_control::door(&mutation.harness, verb) {
1602                            // The live door needs the open runtime connection
1603                            // this service owns; it stays inline.
1604                            Ok(crate::SessionDoor::Live(_)) => return None,
1605                            Ok(_) => Ok(DetachedWork::SessionMutation { verb, mutation }),
1606                            Err(error) => Err(session_control_error(error)),
1607                        }
1608                    }
1609                    Err(error) => Err(error),
1610                }
1611            }
1612        };
1613        Some(DetachedCall {
1614            id,
1615            method: method.to_string(),
1616            work: work.map(Work::Free),
1617        })
1618    }
1619
1620    /// Recognize the two doors that wait on a runtime THIS SERVICE OWNS, and
1621    /// hand a transport the whole of each by lending the connection out.
1622    ///
1623    /// `runtimes.close` surrenders its runtime for good; the LIVE
1624    /// `sessions.new` / `sessions.reset` door borrows one for the length of
1625    /// the slash command and gives it back through
1626    /// [`Self::finish_detached`]. Both are bounded by
1627    /// [`RUNTIME_CONTROL_DEADLINE`], and a wedged runtime spends all of it —
1628    /// which is exactly as long as a transport that awaited them inline would
1629    /// stop reading its own input.
1630    ///
1631    /// `None` for every other method, and for the `sessions.new` /
1632    /// `sessions.reset` doors that are not live: [`Self::detach`] owns those.
1633    pub fn detach_runtime(&mut self, request: &Value) -> Option<DetachedCall> {
1634        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1635            return None;
1636        }
1637        let method = request.get("method").and_then(Value::as_str)?;
1638        let id = request.get("id").cloned().unwrap_or(Value::Null);
1639        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1640        let work = match method {
1641            "harness.v1.runtimes.close" => decode::<RuntimeConnectionParams>(params)
1642                .and_then(|params| self.surrender_runtime(&params.connection))
1643                .map(|(runtime, process_group)| {
1644                    Work::Runtime(RuntimeWork::Close {
1645                        runtime,
1646                        process_group,
1647                    })
1648                }),
1649            "harness.v1.sessions.new" | "harness.v1.sessions.reset" => {
1650                let verb = if method == "harness.v1.sessions.new" {
1651                    crate::SessionVerb::New
1652                } else {
1653                    crate::SessionVerb::Reset
1654                };
1655                let mutation = decode::<crate::SessionMutation>(params).ok()?;
1656                // Everything but the live door — including a refusal and a
1657                // request naming no connection — is `detach`'s or
1658                // `handle_async`'s to answer.
1659                let Ok(crate::SessionDoor::Live(command)) =
1660                    crate::sessions_control::door(&mutation.harness, verb)
1661                else {
1662                    return None;
1663                };
1664                let connection = mutation
1665                    .connection
1666                    .clone()
1667                    .filter(|value| !value.trim().is_empty())?;
1668                self.lend_runtime(&connection).map(|runtime| {
1669                    let session = live_session_name(runtime.as_ref(), &mutation);
1670                    Work::Runtime(RuntimeWork::LiveCommand {
1671                        connection,
1672                        runtime,
1673                        verb,
1674                        mutation,
1675                        command,
1676                        session,
1677                    })
1678                })
1679            }
1680            _ => return None,
1681        };
1682        Some(DetachedCall {
1683            id,
1684            method: method.to_string(),
1685            work,
1686        })
1687    }
1688
1689    /// Take back whatever a detached call borrowed and hand over the caller's
1690    /// response. Every answer from [`DetachedCall::run`] comes through here,
1691    /// so a lent-out connection is back in the service before the response
1692    /// that used it is written.
1693    pub fn finish_detached(&mut self, answer: DetachedAnswer) -> Value {
1694        let DetachedAnswer { response, returned } = answer;
1695        if let Some(ReturnedRuntime {
1696            connection,
1697            runtime,
1698        }) = returned
1699        {
1700            self.runtimes_in_flight.remove(&connection);
1701            self.runtimes.insert(connection, runtime);
1702        }
1703        response
1704    }
1705
1706    /// Answer a request split out by [`Self::runtime_open`] and already
1707    /// awaited by [`RuntimeOpen::open`]: register the runtime this service now
1708    /// owns and build its JSON-RPC response.
1709    pub async fn finish_runtime_open(&mut self, opened: OpenedRuntime) -> Value {
1710        let OpenedRuntime { id, outcome } = opened;
1711        let result = match outcome {
1712            Ok(open) => self.register_open_runtime(open).await,
1713            Err(error) => Err(error),
1714        };
1715        service_response(id, result)
1716    }
1717
1718    /// Take ownership of an opened runtime.
1719    async fn register_open_runtime(
1720        &mut self,
1721        open: OpenRuntime,
1722    ) -> std::result::Result<Value, ServiceError> {
1723        match open {
1724            OpenRuntime::Hosted {
1725                runtime,
1726                capabilities,
1727                workspace,
1728            } => {
1729                self.insert_hosted_runtime(runtime, capabilities, workspace)
1730                    .await
1731            }
1732            OpenRuntime::Joined { runtime } => self.insert_runtime(runtime),
1733        }
1734    }
1735
1736    async fn runtime_call(
1737        &mut self,
1738        method: &str,
1739        params: Value,
1740    ) -> std::result::Result<Value, ServiceError> {
1741        match method {
1742            "harness.v1.runtimes.capabilities" => {
1743                let params = decode::<RuntimeBackendParams>(params)?;
1744                let backend = runtime_backend(&params)?;
1745                Ok(json!({
1746                    "harness": backend.harness(),
1747                    "capabilities": backend.capabilities(),
1748                }))
1749            }
1750            method if RUNTIME_OPEN_METHODS.contains(&method) => {
1751                self.register_open_runtime(open_runtime(method, params).await?)
1752                    .await
1753            }
1754            "harness.v1.runtimes.send_input" => {
1755                let params = decode::<RuntimeInputParams>(params)?;
1756                let image_urls = validate_runtime_image_urls(params.image_urls)?;
1757                let runtime = self.runtime_mut(&params.connection)?;
1758                let turn_id = within_control_deadline(
1759                    method,
1760                    runtime.send_input(RuntimeInput {
1761                        text: params.text,
1762                        image_urls,
1763                    }),
1764                )
1765                .await?
1766                .map_err(operation)?;
1767                Ok(json!({"turn_id": turn_id}))
1768            }
1769            "harness.v1.runtimes.interrupt" => {
1770                let params = decode::<RuntimeConnectionParams>(params)?;
1771                within_control_deadline(method, self.runtime_mut(&params.connection)?.interrupt())
1772                    .await?
1773                    .map_err(operation)?;
1774                Ok(json!({}))
1775            }
1776            "harness.v1.runtimes.steer" => {
1777                let params = decode::<RuntimeInputParams>(params)?;
1778                if !params.image_urls.is_empty() {
1779                    return Err(ServiceError::InvalidParams(
1780                        "runtime steering accepts text only".into(),
1781                    ));
1782                }
1783                let text = params.text.trim();
1784                if text.is_empty() || text.chars().count() > 50_000 {
1785                    return Err(ServiceError::InvalidParams(
1786                        "runtime steering requires 1 to 50,000 text characters".into(),
1787                    ));
1788                }
1789                within_control_deadline(
1790                    method,
1791                    self.runtime_mut(&params.connection)?
1792                        .steer(text.to_string()),
1793                )
1794                .await?
1795                .map_err(operation)?;
1796                Ok(json!({}))
1797            }
1798            "harness.v1.runtimes.respond" => {
1799                let params = decode::<RuntimeRespondParams>(params)?;
1800                let request_id = params.request_id.clone();
1801                within_control_deadline(
1802                    method,
1803                    self.runtime_mut(&params.connection)?
1804                        .respond(params.request_id, params.response),
1805                )
1806                .await?
1807                .map_err(operation)?;
1808                // ORCH-9: an answered request is no longer waiting for one.
1809                self.approvals.answered(&params.connection, &request_id);
1810                Ok(json!({}))
1811            }
1812            "harness.v1.runtimes.acquire_control" => {
1813                let params = decode::<RuntimeConnectionParams>(params)?;
1814                let snapshot = within_control_deadline(
1815                    method,
1816                    self.runtime_mut(&params.connection)?.acquire_control(),
1817                )
1818                .await?
1819                .map_err(operation)?;
1820                serde_json::to_value(snapshot)
1821                    .map_err(|error| ServiceError::Operation(error.to_string()))
1822            }
1823            "harness.v1.runtimes.heartbeat" => {
1824                let params = decode::<RuntimeConnectionParams>(params)?;
1825                let snapshot = within_control_deadline(
1826                    method,
1827                    self.runtime_mut(&params.connection)?.heartbeat(),
1828                )
1829                .await?
1830                .map_err(operation)?;
1831                serde_json::to_value(snapshot)
1832                    .map_err(|error| ServiceError::Operation(error.to_string()))
1833            }
1834            "harness.v1.runtimes.detach" => {
1835                let params = decode::<RuntimeConnectionParams>(params)?;
1836                let snapshot =
1837                    within_control_deadline(method, self.runtime_mut(&params.connection)?.detach())
1838                        .await?
1839                        .map_err(operation)?;
1840                serde_json::to_value(snapshot)
1841                    .map_err(|error| ServiceError::Operation(error.to_string()))
1842            }
1843            "harness.v1.runtimes.terminal_instructions" => {
1844                let params = decode::<RuntimeConnectionParams>(params)?;
1845                let launch = self
1846                    .terminal_launches
1847                    .get(&params.connection)
1848                    .ok_or_else(|| {
1849                        ServiceError::Operation(
1850                            "this runtime is not hosted for terminal attachment".into(),
1851                        )
1852                    })?;
1853                Ok(json!({"launch":launch}))
1854            }
1855            "harness.v1.runtimes.close" => {
1856                let params = decode::<RuntimeConnectionParams>(params)?;
1857                let (runtime, process_group) = self.surrender_runtime(&params.connection)?;
1858                close_runtime(runtime, process_group).await
1859            }
1860            _ => Err(ServiceError::MethodNotFound),
1861        }
1862    }
1863
1864    /// Deliver one message into a session that is running right now.
1865    #[cfg(feature = "adapter-api")]
1866    async fn message_call(&self, params: Value) -> std::result::Result<Value, ServiceError> {
1867        let params = decode::<MessageSessionParams>(params)?;
1868        Ok(message_live_session(&params).await)
1869    }
1870
1871    #[cfg(feature = "adapter-api")]
1872    fn harness_settings_call(
1873        &self,
1874        method: &str,
1875        params: Value,
1876    ) -> std::result::Result<Value, ServiceError> {
1877        let homes = crate::HarnessHomes::default();
1878        match method {
1879            "harness.v1.harnesses.settings" => {
1880                let params = decode::<HarnessSettingsParams>(params)?;
1881                let report = crate::inspect_harness_interop_settings(&homes, &params.harness)
1882                    .map_err(|error| ServiceError::Operation(error.to_string()))?;
1883                serde_json::to_value(report)
1884                    .map_err(|error| ServiceError::Operation(error.to_string()))
1885            }
1886            "harness.v1.harnesses.configure" => {
1887                let params = decode::<ConfigureHarnessParams>(params)?;
1888                let report = crate::configure_harness_interop_settings(
1889                    &homes,
1890                    &params.harness,
1891                    &params.changes,
1892                    params.expected_revision.as_deref(),
1893                )
1894                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1895                serde_json::to_value(report)
1896                    .map_err(|error| ServiceError::Operation(error.to_string()))
1897            }
1898            _ => Err(ServiceError::MethodNotFound),
1899        }
1900    }
1901
1902    fn insert_runtime(
1903        &mut self,
1904        runtime: Box<dyn RuntimeConnection>,
1905    ) -> std::result::Result<Value, ServiceError> {
1906        let connection = format!("runtime-{}", self.next_runtime);
1907        self.next_runtime += 1;
1908        let handle = runtime.handle().clone();
1909        self.runtime_sequences
1910            .entry(handle.runtime_id.clone())
1911            .or_insert(0);
1912        self.runtimes.insert(connection.clone(), runtime);
1913        Ok(json!({"connection": connection, "handle": handle}))
1914    }
1915
1916    #[cfg(feature = "adapter-api")]
1917    async fn insert_hosted_runtime(
1918        &mut self,
1919        runtime: Box<dyn RuntimeConnection>,
1920        capabilities: crate::RuntimeCapabilities,
1921        workspace: PathBuf,
1922    ) -> std::result::Result<Value, ServiceError> {
1923        let (host, connection) = HostedHarnessRuntime::spawn(runtime, capabilities);
1924        let token: std::sync::Arc<str> = crate::server::generate_token().into();
1925        let server = crate::server::run_frontend_http(
1926            host.clone(),
1927            host.frontend_sender(),
1928            "127.0.0.1:0",
1929            token.clone(),
1930            connection.handle().runtime_id.clone(),
1931        )
1932        .await
1933        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1934        let source = LiveRuntimeSource {
1935            harness: connection.handle().harness.as_str().to_string(),
1936            session_id: connection.handle().runtime_id.clone(),
1937            workspace: workspace.clone(),
1938        };
1939        let registration = register_live_runtime(
1940            connection.handle().runtime_id.clone(),
1941            source.clone(),
1942            format!("http://{}", server.address()),
1943            token.to_string(),
1944        )
1945        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1946        let endpoint = registration.endpoint().to_string();
1947        let launch = StructuredLaunch {
1948            cwd: workspace,
1949            // Pin attachment to the executable hosting this runtime. A bare
1950            // `supercode` could resolve to an older global install whose CLI
1951            // does not understand the receipt it is being asked to open.
1952            program: std::env::current_exe()
1953                .ok()
1954                .map(|path| path.to_string_lossy().into_owned())
1955                .unwrap_or_else(|| "supercode".into()),
1956            arguments: vec![
1957                "open".into(),
1958                endpoint,
1959                "--harness".into(),
1960                source.harness,
1961                "--session".into(),
1962                source.session_id,
1963            ],
1964            env: BTreeMap::new(),
1965        };
1966        let lease = HostedRuntimeLease {
1967            connection,
1968            _host: host,
1969            _registration: registration,
1970            _server: server,
1971        };
1972        let opened = self.insert_runtime(Box::new(lease))?;
1973        let connection_id = opened["connection"]
1974            .as_str()
1975            .expect("insert_runtime returns a connection id")
1976            .to_string();
1977        self.terminal_launches.insert(connection_id, launch);
1978        Ok(opened)
1979    }
1980
1981    #[cfg(not(feature = "adapter-api"))]
1982    async fn insert_hosted_runtime(
1983        &mut self,
1984        runtime: Box<dyn RuntimeConnection>,
1985        _capabilities: crate::RuntimeCapabilities,
1986        _workspace: PathBuf,
1987    ) -> std::result::Result<Value, ServiceError> {
1988        self.insert_runtime(runtime)
1989    }
1990
1991    fn runtime_mut(
1992        &mut self,
1993        connection: &str,
1994    ) -> std::result::Result<&mut Box<dyn RuntimeConnection>, ServiceError> {
1995        if self.runtimes_in_flight.contains(connection) {
1996            return Err(self.lent_out(connection));
1997        }
1998        self.runtimes.get_mut(connection).ok_or_else(|| {
1999            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2000        })
2001    }
2002
2003    /// What a caller is told about a connection that is out on a detached
2004    /// call. It is not gone and it is not free: it is mid-call, which is the
2005    /// same answer the runtime itself gives a second turn.
2006    fn lent_out(&self, connection: &str) -> ServiceError {
2007        ServiceError::Operation(format!(
2008            "runtime connection `{connection}`: a harness turn is already in progress"
2009        ))
2010    }
2011
2012    /// Take a runtime OUT of the service for the duration of one detached
2013    /// call, leaving its name marked as lent out.
2014    fn lend_runtime(
2015        &mut self,
2016        connection: &str,
2017    ) -> std::result::Result<Box<dyn RuntimeConnection>, ServiceError> {
2018        if self.runtimes_in_flight.contains(connection) {
2019            return Err(self.lent_out(connection));
2020        }
2021        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
2022            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2023        })?;
2024        self.runtimes_in_flight.insert(connection.to_string());
2025        Ok(runtime)
2026    }
2027
2028    /// Surrender a runtime for good: the connection and everything the
2029    /// service hung off it are gone before its teardown is even attempted.
2030    ///
2031    /// `close` is what a caller reaches for when a runtime has stopped
2032    /// answering, and a runtime that has stopped answering is exactly the one
2033    /// whose graceful close cannot complete: a hosted runtime's own loop
2034    /// parks on the call the runtime never answered, so it never dequeues the
2035    /// shutdown either. Keeping the entry until teardown succeeded made a
2036    /// wedged runtime permanent — every later call on that connection, and
2037    /// every new turn, answered "a harness turn is already in progress" with
2038    /// no way to take the connection back.
2039    fn surrender_runtime(
2040        &mut self,
2041        connection: &str,
2042    ) -> std::result::Result<(Box<dyn RuntimeConnection>, Option<u32>), ServiceError> {
2043        if self.runtimes_in_flight.contains(connection) {
2044            return Err(self.lent_out(connection));
2045        }
2046        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
2047            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2048        })?;
2049        let process_group = runtime_process_group(runtime.handle());
2050        let runtime_id = runtime.handle().runtime_id.clone();
2051        self.terminal_launches.remove(connection);
2052        self.runtime_sequences.remove(&runtime_id);
2053        self.approvals.forget(connection);
2054        Ok((runtime, process_group))
2055    }
2056
2057    /// SIGKILL the process group of every runtime this service owns, without
2058    /// waiting on any of them.
2059    ///
2060    /// A host leaving for good calls this BEFORE dropping the service. The
2061    /// handle this service holds is not the runtime's connection: a hosted
2062    /// runtime's real transport lives in the task driving it, so neither
2063    /// exiting the process nor dropping these handles reaches the harness
2064    /// process — while dropping them does remove each runtime's live-runtime
2065    /// receipt. Signalling first is what keeps a removed receipt from
2066    /// advertising a harness that is still running.
2067    pub fn kill_all_runtime_groups(&self) -> usize {
2068        self.runtimes
2069            .values()
2070            .filter(|runtime| kill_runtime_process_group(runtime_process_group(runtime.handle())))
2071            .count()
2072    }
2073
2074    /// ORCH-19: run one conversation-lifecycle verb through the harness's own
2075    /// door.
2076    ///
2077    /// Two doors, one shape. A CLI / HTTP / own-store door is self-contained
2078    /// in [`crate::sessions_control`]. A LIVE door (Hermes's and OpenClaw's
2079    /// `/new` and `/reset`, which are slash commands their gateway interprets
2080    /// INSIDE a session) is performed here, because only the service owns the
2081    /// open runtime connection — the command is typed through the very same
2082    /// `send_input` path a human's message takes, so supercode invents no
2083    /// private channel.
2084    async fn mutate_session(
2085        &mut self,
2086        verb: crate::SessionVerb,
2087        params: Value,
2088    ) -> std::result::Result<Value, ServiceError> {
2089        let mutation = decode::<crate::SessionMutation>(params)?;
2090        let door = crate::sessions_control::door(&mutation.harness, verb)
2091            .map_err(session_control_error)?;
2092        let outcome = match door {
2093            // The live door types the slash command through an open hosted
2094            // runtime, which only exists with the `adapter-api` feature; the
2095            // CLI / HTTP / own-store doors below need nothing extra.
2096            #[cfg(not(feature = "adapter-api"))]
2097            crate::SessionDoor::Live(command) => {
2098                return Err(ServiceError::Operation(format!(
2099                    "`{}` performs `sessions.{}` by typing `{command}` into a live driven \
2100                     session, which needs this build's `adapter-api` feature",
2101                    mutation.harness,
2102                    verb.as_str()
2103                )));
2104            }
2105            #[cfg(feature = "adapter-api")]
2106            crate::SessionDoor::Live(command) => {
2107                let connection = mutation
2108                    .connection
2109                    .clone()
2110                    .filter(|value| !value.trim().is_empty())
2111                    .ok_or_else(|| {
2112                        ServiceError::InvalidParams(format!(
2113                            "`{}` performs `sessions.{}` by typing `{command}` into a live \
2114                             driven session: pass the `connection` of an open runtime \
2115                             (`harness.v1.runtimes.start`)",
2116                            mutation.harness,
2117                            verb.as_str()
2118                        ))
2119                    })?;
2120                let runtime = self.runtime_mut(&connection)?;
2121                let session = live_session_name(runtime.as_ref(), &mutation);
2122                // Typing into a live session is a control call on an open
2123                // runtime, and a wedged runtime never accepts one, so it is
2124                // bounded exactly like the other control verbs. A transport
2125                // with a loop of its own lends the connection out instead of
2126                // waiting here: see [`Self::detach_runtime`].
2127                return type_live_command(runtime.as_mut(), verb, &mutation, command, session)
2128                    .await;
2129            }
2130            _ => run_session_mutation(verb, &mutation).await?,
2131        };
2132        serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
2133    }
2134
2135    /// Answer an inventory request whole, for callers that have nowhere to
2136    /// put the waiting half. A transport with a loop of its own splits it
2137    /// instead: see [`Self::detach`].
2138    async fn inventory_call(
2139        &self,
2140        method: &str,
2141        params: Value,
2142    ) -> std::result::Result<Value, ServiceError> {
2143        run_inventory(self.inventory_work(method, params)?).await
2144    }
2145
2146    /// The half of an inventory request that reads this service's state:
2147    /// resolve the selection and count the persisted sessions each row
2148    /// reports. What remains — finding executables, asking them their
2149    /// version, and (at `probe: handshake`) starting each harness and
2150    /// completing its protocol handshake — touches no service state at all.
2151    fn inventory_work(
2152        &self,
2153        method: &str,
2154        params: Value,
2155    ) -> std::result::Result<InventoryWork, ServiceError> {
2156        let mut params = decode::<HarnessInventoryParams>(params)?;
2157        if method == "harness.v1.harnesses.probe" {
2158            let harness = params.harness.take().ok_or_else(|| {
2159                ServiceError::InvalidParams("harnesses.probe requires `harness`".into())
2160            })?;
2161            params.harnesses = vec![harness];
2162        }
2163        let selected = params
2164            .harnesses
2165            .iter()
2166            .map(HarnessId::as_str)
2167            .collect::<std::collections::BTreeSet<_>>();
2168        let supported = harness_support_registry()
2169            .harnesses
2170            .into_iter()
2171            .filter(|descriptor| selected.is_empty() || selected.contains(descriptor.id.as_str()))
2172            .collect::<Vec<_>>();
2173        if !params.harnesses.is_empty() && supported.len() != selected.len() {
2174            let known = supported
2175                .iter()
2176                .map(|harness| harness.id.as_str())
2177                .collect::<std::collections::BTreeSet<_>>();
2178            let missing = params
2179                .harnesses
2180                .iter()
2181                .filter(|id| !known.contains(id.as_str()))
2182                .map(HarnessId::as_str)
2183                .collect::<Vec<_>>();
2184            return Err(ServiceError::InvalidParams(format!(
2185                "unknown harness(es): {}",
2186                missing.join(", ")
2187            )));
2188        }
2189        let global_counts = params
2190            .include_sessions
2191            .then(|| self.session_counts(None, &params.harnesses));
2192        let workspace_counts = params
2193            .include_sessions
2194            .then(|| {
2195                params
2196                    .workspace
2197                    .as_deref()
2198                    .map(|workspace| self.session_counts(Some(workspace), &params.harnesses))
2199            })
2200            .flatten();
2201        Ok(InventoryWork {
2202            params,
2203            supported,
2204            global_counts,
2205            workspace_counts,
2206        })
2207    }
2208
2209    #[cfg(feature = "adapter-api")]
2210    async fn harness_authentication_call(
2211        &self,
2212        method: &str,
2213        params: Value,
2214    ) -> std::result::Result<Value, ServiceError> {
2215        match method {
2216            "harness.v1.harnesses.auth.methods" | "harness.v1.harnesses.auth.verify" => {
2217                let params = decode::<HarnessAuthenticationParams>(params)?;
2218                serde_json::to_value(crate::inspect_harness_authentication(&params.harness).await)
2219                    .map_err(|error| ServiceError::Operation(error.to_string()))
2220            }
2221            "harness.v1.harnesses.auth.begin" => {
2222                let params = decode::<BeginHarnessAuthenticationParams>(params)?;
2223                let cwd = params
2224                    .cwd
2225                    .or_else(|| std::env::current_dir().ok())
2226                    .unwrap_or_else(|| PathBuf::from("."));
2227                let plan = crate::harness_authentication_plan(
2228                    &params.harness,
2229                    params.environment,
2230                    params.method,
2231                    &cwd,
2232                )
2233                .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
2234                serde_json::to_value(plan)
2235                    .map_err(|error| ServiceError::Operation(error.to_string()))
2236            }
2237            _ => Err(ServiceError::MethodNotFound),
2238        }
2239    }
2240
2241    fn session_counts(
2242        &self,
2243        workspace: Option<&Path>,
2244        harnesses: &[HarnessId],
2245    ) -> BTreeMap<String, usize> {
2246        let mut counts = BTreeMap::new();
2247        for session in self
2248            .catalog
2249            .discover(&DiscoveryQuery {
2250                workspace: workspace.map(Path::to_path_buf),
2251                harnesses: harnesses.to_vec(),
2252                ..DiscoveryQuery::default()
2253            })
2254            .unwrap_or_default()
2255        {
2256            *counts
2257                .entry(session.locator.harness.as_str().to_string())
2258                .or_insert(0) += 1;
2259        }
2260        counts
2261    }
2262}
2263
2264#[async_trait::async_trait]
2265impl SdkService for HarnessSessionService {
2266    fn capabilities(&self) -> SdkCapabilities {
2267        SdkCapabilities::default()
2268    }
2269
2270    async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError> {
2271        if request.operation == SdkOperation::Events {
2272            let events = self
2273                .poll_sdk_events()
2274                .await
2275                .into_iter()
2276                .map(|(_, event)| event)
2277                .collect::<Vec<_>>();
2278            return serde_json::to_value(events).map_err(|error| {
2279                SdkError::new(
2280                    SdkErrorCode::Execution,
2281                    request.operation,
2282                    error.to_string(),
2283                )
2284            });
2285        }
2286        if self.runtimes.is_empty()
2287            && matches!(
2288                request.operation,
2289                SdkOperation::Input
2290                    | SdkOperation::Interrupt
2291                    | SdkOperation::Steer
2292                    | SdkOperation::Respond
2293                    | SdkOperation::Close
2294            )
2295        {
2296            return Err(SdkError::unsupported(request.operation));
2297        }
2298        let method = request
2299            .operation
2300            .method()
2301            .ok_or_else(|| SdkError::unsupported(request.operation))?;
2302        let result = match request.operation {
2303            SdkOperation::Discover
2304            | SdkOperation::Load
2305            | SdkOperation::Export
2306            | SdkOperation::ProfilesList
2307            | SdkOperation::ProfilesGet
2308            | SdkOperation::ProfilesCreate
2309            | SdkOperation::ProfilesDelete
2310            | SdkOperation::SkillsList
2311            | SdkOperation::SkillsInstall
2312            | SdkOperation::SkillsRemove
2313            | SdkOperation::ChannelsList
2314            | SdkOperation::RoutesList
2315            | SdkOperation::TriggersList
2316            | SdkOperation::ChannelsStatus
2317            | SdkOperation::MemoryShow
2318            | SdkOperation::MemorySearch
2319            | SdkOperation::JobsList
2320            | SdkOperation::JobsGet
2321            | SdkOperation::JobsCreate
2322            | SdkOperation::JobsUpdate
2323            | SdkOperation::JobsPause
2324            | SdkOperation::JobsResume
2325            | SdkOperation::JobsRun
2326            | SdkOperation::JobsDelete
2327            | SdkOperation::JobsNotepad
2328            | SdkOperation::JobsNotepadSet
2329            | SdkOperation::JobsNotepadDelete
2330            | SdkOperation::RunsList
2331            | SdkOperation::RunsGet
2332            | SdkOperation::ApprovalsList
2333            | SdkOperation::OrchestrationLoad
2334            | SdkOperation::OrchestrationSave
2335            | SdkOperation::OrchestrationCompile
2336            | SdkOperation::OrchestrationDecompile
2337            | SdkOperation::OrchestrationImport
2338            | SdkOperation::OrchestrationExport
2339            | SdkOperation::WorkflowLoad => self.call(method, request.params),
2340            // ORCH-20: answering needs the live connection, so it takes the
2341            // async door and ends in `harness.v1.runtimes.respond`.
2342            SdkOperation::ApprovalsResolve => self.approvals_resolve(request.params).await,
2343            SdkOperation::Start
2344            | SdkOperation::Resume
2345            | SdkOperation::Input
2346            | SdkOperation::Interrupt
2347            | SdkOperation::Steer
2348            | SdkOperation::Respond
2349            | SdkOperation::Close => self.runtime_call(method, request.params).await,
2350            // ORCH-19 controlled tier. Every verb goes through the HARNESS'S
2351            // OWN door — its CLI, its HTTP API, or its slash command typed
2352            // into a live driven session — and returns the row re-read from
2353            // the harness's store afterwards.
2354            SdkOperation::SessionsNew => {
2355                self.mutate_session(crate::SessionVerb::New, request.params)
2356                    .await
2357            }
2358            SdkOperation::SessionsReset => {
2359                self.mutate_session(crate::SessionVerb::Reset, request.params)
2360                    .await
2361            }
2362            SdkOperation::SessionsArchive => {
2363                self.mutate_session(crate::SessionVerb::Archive, request.params)
2364                    .await
2365            }
2366            SdkOperation::SessionsDelete => {
2367                self.mutate_session(crate::SessionVerb::Delete, request.params)
2368                    .await
2369            }
2370            SdkOperation::Events => unreachable!("handled before method dispatch"),
2371        };
2372        result.map_err(|error| sdk_error(request.operation, error))
2373    }
2374
2375    async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError> {
2376        Ok(self
2377            .poll_sdk_events()
2378            .await
2379            .into_iter()
2380            .map(|(_, event)| event)
2381            .collect())
2382    }
2383}
2384
2385#[cfg(feature = "adapter-api")]
2386struct HostedRuntimeLease {
2387    connection: HostedHarnessConnection,
2388    _host: std::sync::Arc<HostedHarnessRuntime>,
2389    _registration: LiveRuntimeRegistration,
2390    _server: crate::server::FrontendHttpServer,
2391}
2392
2393#[async_trait::async_trait]
2394#[cfg(feature = "adapter-api")]
2395impl RuntimeConnection for HostedRuntimeLease {
2396    fn handle(&self) -> &crate::RuntimeHandle {
2397        self.connection.handle()
2398    }
2399
2400    async fn send_input(&mut self, input: RuntimeInput) -> crate::Result<Option<String>> {
2401        self.connection.send_input(input).await
2402    }
2403
2404    async fn next_event(&mut self) -> crate::Result<Option<crate::HarnessEvent>> {
2405        self.connection.next_event().await
2406    }
2407
2408    async fn interrupt(&mut self) -> crate::Result<()> {
2409        self.connection.interrupt().await
2410    }
2411
2412    // the lease must forward every verb its capabilities advertise; without
2413    // this, steer fell to the trait default and refused a turn it claimed
2414    async fn steer(&mut self, text: String) -> crate::Result<()> {
2415        self.connection.steer(text).await
2416    }
2417
2418    async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
2419        self.connection.respond(request_id, response).await
2420    }
2421
2422    async fn close(&mut self) -> crate::Result<()> {
2423        self.connection.close().await
2424    }
2425}
2426
2427/// One inventory request's waiting half, already separated from the service
2428/// state it reads. See [`HarnessSessionService::inventory_work`].
2429struct InventoryWork {
2430    params: HarnessInventoryParams,
2431    supported: Vec<crate::HarnessSupportDescriptor>,
2432    global_counts: Option<BTreeMap<String, usize>>,
2433    workspace_counts: Option<BTreeMap<String, usize>>,
2434}
2435
2436/// Perform one conversation-lifecycle verb through a door that is
2437/// self-contained in [`crate::sessions_control`]: the harness's own CLI, its
2438/// HTTP API, the orchestrator daemon's socket, or supercode's own store.
2439/// Touches no service state, so this runs on any task. The LIVE door is not
2440/// here — it types its slash command through a runtime connection the service
2441/// owns, and is performed by [`HarnessSessionService::mutate_session`].
2442async fn run_session_mutation(
2443    verb: crate::SessionVerb,
2444    mutation: &crate::SessionMutation,
2445) -> std::result::Result<crate::SessionMutationOutcome, ServiceError> {
2446    // Only the HTTP door actually awaits anything. The CLI, store and daemon
2447    // doors run the harness's own program, or its store, with calls that
2448    // block the calling THREAD from start to finish — a future that never
2449    // yields, which no timeout around it can interrupt and which would hold a
2450    // runtime worker for as long as the harness takes. They go to a blocking
2451    // task, where blocking is what the thread is for.
2452    let door =
2453        crate::sessions_control::door(&mutation.harness, verb).map_err(session_control_error)?;
2454    if let crate::SessionDoor::Http = door {
2455        return crate::sessions_control::mutate(verb, mutation)
2456            .await
2457            .map_err(session_control_error);
2458    }
2459    let mutation = mutation.clone();
2460    tokio::task::spawn_blocking(move || crate::sessions_control::mutate_blocking(verb, &mutation))
2461        .await
2462        .map_err(|error| {
2463            ServiceError::Operation(format!("the conversation verb could not be run: {error}"))
2464        })?
2465        .map_err(session_control_error)
2466}
2467
2468/// Probe every selected harness and assemble the report. Touches no service
2469/// state, so this runs on any task.
2470async fn run_inventory(work: InventoryWork) -> std::result::Result<Value, ServiceError> {
2471    let InventoryWork {
2472        params,
2473        supported,
2474        global_counts,
2475        workspace_counts,
2476    } = work;
2477    let probes = supported.into_iter().map(|descriptor| {
2478        let global = global_counts
2479            .as_ref()
2480            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2481        let workspace = workspace_counts
2482            .as_ref()
2483            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2484        probe_harness(descriptor, &params, global, workspace)
2485    });
2486    let harnesses = futures::future::join_all(probes).await;
2487    serde_json::to_value(HarnessInventoryReport {
2488        probe: params.probe,
2489        workspace: params.workspace,
2490        harnesses,
2491    })
2492    .map_err(|error| ServiceError::Operation(error.to_string()))
2493}
2494
2495async fn probe_harness(
2496    descriptor: crate::HarnessSupportDescriptor,
2497    params: &HarnessInventoryParams,
2498    global: Option<usize>,
2499    workspace: Option<usize>,
2500) -> LocalHarness {
2501    let launch = descriptor.runtime.default_launch.as_ref();
2502    // ORC-7: the orchestrator publishes no runtime launch — it is not an
2503    // adapter supercode connects a turn to. What "installed" means for it
2504    // is that its Node daemon entry is present, so the row answers from
2505    // that instead of from a PATH lookup it could never satisfy.
2506    let orchestrator_entry = (descriptor.id.as_str() == HarnessId::ORCHESTRATOR)
2507        .then(crate::orchestrator::daemon_entry)
2508        .and_then(Result::ok);
2509    let executable = match &orchestrator_entry {
2510        Some(entry) => Some(entry.clone()),
2511        None => launch.and_then(|launch| find_executable(&launch.program)),
2512    };
2513    let installed = executable.is_some();
2514    let version = if params.skip_versions || orchestrator_entry.is_some() {
2515        // The orchestrator's "executable" is a Node module, not a CLI
2516        // with a `--version` flag; running it to ask would start a daemon.
2517        None
2518    } else {
2519        match executable.as_deref() {
2520            Some(path) => executable_version(path).await,
2521            None => None,
2522        }
2523    };
2524    let configured = auth_evidence(descriptor.id.as_str());
2525    let mut auth = if configured {
2526        HarnessAuthState::Configured
2527    } else if matches!(
2528        descriptor.id.as_str(),
2529        HarnessId::CLAUDE_CODE | HarnessId::CODEX
2530    ) {
2531        // These two adapters have explicit native status/login contracts
2532        // and complete local evidence coverage (including Claude's macOS
2533        // Keychain-backed oauthAccount marker). Treating absent evidence
2534        // as unknown advertises a start that will only fail interactively.
2535        HarnessAuthState::Required
2536    } else {
2537        HarnessAuthState::Unknown
2538    };
2539    let mut runtime = if installed {
2540        HarnessRuntimeState::Degraded
2541    } else {
2542        HarnessRuntimeState::Unavailable
2543    };
2544    let is_orchestrator = descriptor.id.as_str() == HarnessId::ORCHESTRATOR;
2545    let mut reason = (!installed).then(|| {
2546        if is_orchestrator {
2547            format!(
2548                "{} is supported but its daemon entry `{}` was not found",
2549                descriptor.display_name,
2550                crate::orchestrator::DAEMON_ENTRY
2551            )
2552        } else {
2553            format!(
2554                "{} is supported but `{}` was not found on PATH",
2555                descriptor.display_name,
2556                launch
2557                    .map(|launch| launch.program.as_str())
2558                    .unwrap_or("executable")
2559            )
2560        }
2561    });
2562    let mut repair = (!installed).then(|| {
2563        if is_orchestrator {
2564            format!(
2565                "Install the `supercode-orchestrator` package so `{}` resolves.",
2566                crate::orchestrator::DAEMON_ENTRY
2567            )
2568        } else {
2569            format!(
2570                "Install {} and ensure `{}` is on PATH.",
2571                descriptor.display_name,
2572                launch
2573                    .map(|launch| launch.program.as_str())
2574                    .unwrap_or("its executable")
2575            )
2576        }
2577    });
2578
2579    if installed && params.probe == HarnessProbeLevel::Handshake {
2580        let backend_params = RuntimeBackendParams {
2581            harness: descriptor.id.clone(),
2582            protocol: None,
2583            launch: None,
2584            base_url: None,
2585            policy: RuntimePolicy::Default,
2586        };
2587        match runtime_backend(&backend_params) {
2588            Ok(backend) => {
2589                let cwd = params
2590                    .workspace
2591                    .clone()
2592                    .or_else(|| std::env::current_dir().ok())
2593                    .unwrap_or_else(|| PathBuf::from("."));
2594                let isolated = descriptor
2595                    .runtime
2596                    .default_launch
2597                    .clone()
2598                    .and_then(|launch| IsolatedProbeHome::new(descriptor.id.as_str(), launch).ok());
2599                let Some(isolated) = isolated else {
2600                    reason = Some(
2601                        "No-prompt runtime handshake could not create its isolated harness home."
2602                            .into(),
2603                    );
2604                    repair = Some(
2605                        "Check temporary-directory permissions, then run the handshake probe again."
2606                            .into(),
2607                    );
2608                    let running = probe_running_instance(descriptor.id.as_str());
2609                    return LocalHarness {
2610                        gateway: gateway_health(
2611                            descriptor.id.as_str(),
2612                            installed,
2613                            running.as_ref(),
2614                            version.as_deref(),
2615                        ),
2616                        id: descriptor.id,
2617                        display_name: descriptor.display_name,
2618                        supported: true,
2619                        installed,
2620                        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2621                        version,
2622                        auth,
2623                        runtime,
2624                        protocol: descriptor.runtime.protocol,
2625                        capabilities: descriptor.runtime.capabilities.clone(),
2626                        effective_capabilities: descriptor.runtime.capabilities,
2627                        sessions: HarnessSessionCounts { global, workspace },
2628                        running,
2629                        reason,
2630                        repair,
2631                    };
2632                };
2633                match tokio::time::timeout(
2634                    Duration::from_secs(30),
2635                    backend.start(RuntimeStartRequest {
2636                        cwd,
2637                        launch: Some(isolated.launch.clone()),
2638                        mcp_servers: Vec::new(),
2639                        approval_policy: None,
2640                    }),
2641                )
2642                .await
2643                {
2644                    Ok(Ok(mut connection)) => {
2645                        match stabilize_handshake(connection.as_mut()).await {
2646                            Ok(()) => {
2647                                auth = HarnessAuthState::Ready;
2648                                runtime = HarnessRuntimeState::Ready;
2649                                reason = Some(
2650                                    "No-prompt runtime handshake remained healthy through the startup stabilization window; no model request was sent."
2651                                        .into(),
2652                                );
2653                                repair = None;
2654                            }
2655                            Err(message) => {
2656                                auth = if looks_like_auth_error(&message) {
2657                                    HarnessAuthState::Required
2658                                } else if configured {
2659                                    HarnessAuthState::Configured
2660                                } else {
2661                                    HarnessAuthState::Unknown
2662                                };
2663                                reason = Some(format!(
2664                                    "No-prompt runtime handshake became unhealthy during startup: {message}"
2665                                ));
2666                                repair = Some(if auth == HarnessAuthState::Required {
2667                                    format!(
2668                                        "Run `{}` interactively once and complete sign-in, then probe again.",
2669                                        launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2670                                    )
2671                                } else {
2672                                    "Run the harness directly to inspect its startup failure, then probe again."
2673                                        .into()
2674                                });
2675                            }
2676                        }
2677                        let _ =
2678                            tokio::time::timeout(Duration::from_secs(3), connection.close()).await;
2679                    }
2680                    Ok(Err(error)) => {
2681                        let message = truncate_text(&error.to_string(), 500);
2682                        auth = if looks_like_auth_error(&message) {
2683                            HarnessAuthState::Required
2684                        } else if configured {
2685                            HarnessAuthState::Configured
2686                        } else {
2687                            HarnessAuthState::Unknown
2688                        };
2689                        reason = Some(format!("No-prompt runtime handshake failed: {message}"));
2690                        repair = Some(if auth == HarnessAuthState::Required {
2691                            format!(
2692                                "Run `{}` interactively once and complete sign-in, then probe again.",
2693                                launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2694                            )
2695                        } else {
2696                            "Check the harness installation and run the handshake probe again."
2697                                .into()
2698                        });
2699                    }
2700                    Err(_) => {
2701                        reason =
2702                            Some("No-prompt runtime handshake timed out after 30 seconds.".into());
2703                        repair = Some("Run the harness directly to check startup or authentication, then probe again.".into());
2704                    }
2705                }
2706                // Keep the isolated home alive through process teardown.
2707                // Otherwise the compiler may release the last meaningful
2708                // use after cloning `launch`, and a still-starting CLI can
2709                // recreate its state directory after Drop removed it.
2710                // Some Node-based launchers finish a short asynchronous
2711                // installation-id write just after their parent process
2712                // is reaped. Remove once immediately, allow that bounded
2713                // writer to settle, then perform the authoritative pass.
2714                let _ = isolated.cleanup();
2715                tokio::time::sleep(Duration::from_millis(250)).await;
2716                if let Err(error) = isolated.cleanup() {
2717                    auth = if configured {
2718                        HarnessAuthState::Configured
2719                    } else {
2720                        HarnessAuthState::Unknown
2721                    };
2722                    runtime = HarnessRuntimeState::Degraded;
2723                    reason = Some(format!(
2724                        "No-prompt runtime handshake could not remove its isolated harness home: {error}"
2725                    ));
2726                    repair = Some(
2727                        "Check temporary-directory permissions, remove the reported disposable probe home, then run the handshake again."
2728                            .into(),
2729                    );
2730                }
2731            }
2732            Err(error) => {
2733                reason = Some(error_message(error));
2734            }
2735        }
2736    } else if installed && configured {
2737        reason = Some("Executable and local authentication evidence found; use a handshake probe to verify readiness.".into());
2738    } else if installed && auth == HarnessAuthState::Required {
2739        reason = Some("Executable found, but no native authentication evidence is present.".into());
2740        repair = Some(format!(
2741            "Run `supercode harness login {}` to use the harness-owned sign-in flow.",
2742            descriptor.id.as_str()
2743        ));
2744    } else if installed {
2745        reason = Some("Executable found; authentication readiness is unknown until a no-prompt handshake succeeds.".into());
2746        repair = Some(format!(
2747            "Run `{}` interactively once if sign-in is required, or use `--probe handshake`.",
2748            launch
2749                .map(|launch| launch.program.as_str())
2750                .unwrap_or("the harness")
2751        ));
2752    }
2753
2754    let effective_capabilities = if installed {
2755        descriptor.runtime.capabilities.clone()
2756    } else {
2757        unavailable_capabilities()
2758    };
2759    let running = probe_running_instance(descriptor.id.as_str());
2760    LocalHarness {
2761        gateway: gateway_health(
2762            descriptor.id.as_str(),
2763            installed,
2764            running.as_ref(),
2765            version.as_deref(),
2766        ),
2767        id: descriptor.id,
2768        display_name: descriptor.display_name,
2769        supported: true,
2770        installed,
2771        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2772        version,
2773        auth,
2774        runtime,
2775        protocol: descriptor.runtime.protocol,
2776        capabilities: descriptor.runtime.capabilities,
2777        effective_capabilities,
2778        sessions: HarnessSessionCounts { global, workspace },
2779        running,
2780        reason,
2781        repair,
2782    }
2783}
2784
2785async fn stabilize_handshake(connection: &mut dyn RuntimeConnection) -> Result<(), String> {
2786    let deadline = tokio::time::Instant::now() + Duration::from_secs(3);
2787    loop {
2788        let now = tokio::time::Instant::now();
2789        if now >= deadline {
2790            return Ok(());
2791        }
2792        match tokio::time::timeout(deadline - now, connection.next_event()).await {
2793            Err(_) => return Ok(()),
2794            Ok(Ok(Some(event))) => {
2795                if let Some(message) = handshake_event_failure(&event) {
2796                    return Err(truncate_text(&message, 500));
2797                }
2798            }
2799            Ok(Ok(None)) => return Err("runtime transport closed during startup".into()),
2800            Ok(Err(error)) => return Err(error.to_string()),
2801        }
2802    }
2803}
2804
2805fn handshake_event_failure(event: &crate::HarnessEvent) -> Option<String> {
2806    let detail = event
2807        .payload
2808        .get("message")
2809        .or_else(|| event.payload.get("line"))
2810        .and_then(Value::as_str)
2811        .unwrap_or(event.kind.as_str());
2812    match event.kind.as_str() {
2813        "transport_closed" => Some("runtime transport closed during startup".into()),
2814        "transport_error" => Some(format!("runtime transport error: {detail}")),
2815        "malformed_output" => Some(format!("runtime emitted non-protocol output: {detail}")),
2816        // Stderr is retained as a runtime event, but is not transport health.
2817        // Grok, for example, can log an AuthorizationRequired error from an
2818        // optional background worker while its ACP session continues to send
2819        // updates and complete prompts normally.
2820        _ => None,
2821    }
2822}
2823
2824fn indexed_claude_window(
2825    locator: &SessionLocator,
2826    options: &SessionLoadOptions,
2827) -> std::result::Result<Option<Value>, ServiceError> {
2828    use supercode_interchange::session::ClaudeReadIndex;
2829    // Exact parent-only window: recursive/full-artifact requests retain the
2830    // existing owner. This is not a bounded display-history substitution.
2831    if locator.harness.as_str() != HarnessId::CLAUDE_CODE
2832        || options.include_subagents != Some(false)
2833    {
2834        return Ok(None);
2835    }
2836    let crate::StorageLocator::File { path } = &locator.storage else {
2837        return Ok(None);
2838    };
2839    if !ClaudeReadIndex::supports(path)
2840        .map_err(|error| ServiceError::Operation(error.to_string()))?
2841    {
2842        return Ok(None);
2843    }
2844    let mut index = ClaudeReadIndex::open(path, Fidelity::ByteLossless)
2845        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2846    let total = index.len();
2847    let (offset, end) = projected_message_window(total, options);
2848    let session = index
2849        .read_messages(offset..end)
2850        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2851    let summary = index
2852        .read_summary()
2853        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2854    let selected_options = SessionLoadOptions {
2855        message_offset: None,
2856        message_limit: None,
2857        message_tail: None,
2858        ..options.clone()
2859    };
2860    let mut selected = projected_session_json(&session, &selected_options);
2861    selected["raw_record_count"] = json!(index.raw_record_count());
2862    Ok(Some(json!({
2863        "session": selected,
2864        "summary": projected_session_summary(&summary, options),
2865        "window": {
2866            "has_more": offset > 0 || end < total, "has_newer": end < total,
2867            "has_older": offset > 0, "newer_items": index.item_count(end..total),
2868            "offset": offset, "older_items": index.item_count(0..offset),
2869            "returned": end - offset, "total_messages": total,
2870        }
2871    })))
2872}
2873
2874fn projected_session_result(session: &Session, options: &SessionLoadOptions) -> Value {
2875    let total_messages = session.messages.len();
2876    let (offset, end) = projected_message_window(total_messages, options);
2877    json!({
2878        "session": projected_session_json(session, options),
2879        "summary": projected_session_summary(session, options),
2880        "window": {
2881            "has_more": offset > 0 || end < total_messages,
2882            "has_newer": end < total_messages,
2883            "has_older": offset > 0,
2884            "newer_items": normalized_item_count(&session.messages[end..]),
2885            "offset": offset,
2886            "older_items": normalized_item_count(&session.messages[..offset]),
2887            "returned": end.saturating_sub(offset),
2888            "total_messages": total_messages,
2889        }
2890    })
2891}
2892
2893fn normalized_item_count(messages: &[crate::ChatMessage]) -> usize {
2894    messages
2895        .iter()
2896        .map(|message| {
2897            let conversation = usize::from(
2898                matches!(message.role, Role::Assistant | Role::User)
2899                    && message_has_content(message),
2900            );
2901            let tool_result =
2902                usize::from(message.role == Role::Tool && message_has_content(message));
2903            conversation + tool_result + message.tool_calls().len()
2904        })
2905        .sum()
2906}
2907
2908fn projected_session_summary(session: &Session, options: &SessionLoadOptions) -> Value {
2909    let mut conversational = session.messages.iter().filter(|message| {
2910        matches!(message.role, Role::Assistant | Role::User) && message_has_content(message)
2911    });
2912    let first_message = conversational.clone().next();
2913    let last_message = conversational.next_back();
2914    let mut assistant = session
2915        .messages
2916        .iter()
2917        .filter(|message| message.role == Role::Assistant && message_has_content(message));
2918    let first_assistant_message = assistant.clone().next();
2919    let last_assistant_message = assistant.next_back();
2920    let end_of_turn = session
2921        .messages
2922        .iter()
2923        .rev()
2924        .find(|message| message.role != Role::System)
2925        .is_some_and(|message| {
2926            message.role == Role::Assistant
2927                && message_has_content(message)
2928                && message.tool_calls().is_empty()
2929                // Codex narrates while it works (`phase: commentary`); only its `final_answer` ends a turn
2930                && message.metadata.get("phase").map(String::as_str) != Some("commentary")
2931        });
2932    let project = |message: Option<&crate::ChatMessage>| {
2933        message.map(|message| project_inline_media(message_json(message), options))
2934    };
2935    json!({
2936        "end_of_turn": end_of_turn,
2937        "first_assistant_message": project(first_assistant_message),
2938        "first_message": project(first_message),
2939        "last_assistant_message": project(last_assistant_message),
2940        "last_assistant_text": last_assistant_message.map(message_text).unwrap_or_default(),
2941        "last_message": project(last_message),
2942    })
2943}
2944
2945fn message_has_content(message: &crate::ChatMessage) -> bool {
2946    message
2947        .content
2948        .as_deref()
2949        .is_some_and(|content| !content.trim().is_empty())
2950        || message
2951            .content_parts
2952            .as_ref()
2953            .is_some_and(|parts| !parts.is_empty())
2954}
2955
2956fn message_text(message: &crate::ChatMessage) -> String {
2957    if let Some(content) = &message.content {
2958        return content.clone();
2959    }
2960    message
2961        .content_parts
2962        .as_ref()
2963        .into_iter()
2964        .flatten()
2965        .filter_map(|part| part.get("text").and_then(Value::as_str))
2966        .collect::<Vec<_>>()
2967        .join("\n")
2968}
2969
2970fn projected_session_json(session: &Session, options: &SessionLoadOptions) -> Value {
2971    let (offset, end) = projected_message_window(session.messages.len(), options);
2972    let messages = session.messages[offset..end]
2973        .iter()
2974        .map(|message| project_inline_media(message_json(message), options))
2975        .collect::<Vec<_>>();
2976    let subagents = if options.include_subagents.unwrap_or(true) {
2977        // The reported window describes the top-level transcript. Applying it
2978        // recursively would silently truncate subagents without returning a
2979        // window for each child. Keep their histories complete while carrying
2980        // the caller's media policy through the tree.
2981        let subagent_options = SessionLoadOptions {
2982            message_limit: None,
2983            message_offset: None,
2984            message_tail: None,
2985            ..options.clone()
2986        };
2987        session
2988            .subagents
2989            .iter()
2990            .map(|subagent| projected_session_json(subagent, &subagent_options))
2991            .collect::<Vec<_>>()
2992    } else {
2993        Vec::new()
2994    };
2995    json!({
2996        "source": match session.meta.source {
2997            SessionSource::ClaudeCode => "claude_code",
2998            SessionSource::Codex => "codex",
2999            SessionSource::Gemini => "gemini",
3000            SessionSource::Goose => "goose",
3001            SessionSource::Grok => "grok",
3002            SessionSource::Native => "native",
3003            SessionSource::OpenClaw => "openclaw",
3004            SessionSource::Hermes => "hermes",
3005            SessionSource::OpenCode => "opencode",
3006            SessionSource::Pi => "pi",
3007        },
3008        "session_id": session.meta.session_id,
3009        "ended_at": session.meta.ended_at,
3010        "end_reason": session.meta.end_reason,
3011        "model": session.meta.model,
3012        "cwd": session.meta.cwd,
3013        "system_prompt": session.meta.system_prompt,
3014        "agent_id": session.meta.agent_id,
3015        "parent_tool_use_id": session.meta.parent_tool_use_id,
3016        "lineage": session.meta.lineage,
3017        "messages": messages,
3018        "subagents": subagents,
3019        "raw_record_count": session.raw.len(),
3020        "parse_error_lines": session.parse_error_lines,
3021    })
3022}
3023
3024fn projected_message_window(total: usize, options: &SessionLoadOptions) -> (usize, usize) {
3025    if let Some(tail) = options.message_tail {
3026        return (total.saturating_sub(tail), total);
3027    }
3028    let offset = options.message_offset.unwrap_or(0).min(total);
3029    let end = options
3030        .message_limit
3031        .map(|limit| offset.saturating_add(limit).min(total))
3032        .unwrap_or(total);
3033    (offset, end)
3034}
3035
3036fn project_inline_media(mut message: Value, options: &SessionLoadOptions) -> Value {
3037    let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else {
3038        return message;
3039    };
3040    for part in parts {
3041        let Some(url) = part
3042            .get("image_url")
3043            .and_then(|image| image.get("url"))
3044            .and_then(Value::as_str)
3045        else {
3046            continue;
3047        };
3048        let Some(rest) = url.strip_prefix("data:") else {
3049            continue;
3050        };
3051        let Some((media_type, encoded)) = rest.split_once(";base64,") else {
3052            continue;
3053        };
3054        let padding = usize::from(encoded.ends_with('=')) + usize::from(encoded.ends_with("=="));
3055        let decoded_bytes = encoded.len().saturating_mul(3) / 4;
3056        let decoded_bytes = decoded_bytes.saturating_sub(padding);
3057        let should_elide = matches!(options.inline_media, InlineMediaMode::Metadata)
3058            || options
3059                .max_inline_media_bytes
3060                .is_some_and(|limit| decoded_bytes > limit);
3061        if should_elide {
3062            *part = json!({
3063                "type": "media_reference",
3064                "media_type": media_type,
3065                "encoding": "base64",
3066                "encoded_bytes": encoded.len(),
3067                "decoded_bytes": decoded_bytes,
3068                "omitted": true,
3069            });
3070        }
3071    }
3072    message
3073}
3074
3075#[derive(Deserialize)]
3076struct LocatorParams {
3077    locator: SessionLocator,
3078    /// Optional fidelity for the READ surfaces (`sessions.load`,
3079    /// `sessions.follow`).
3080    ///
3081    /// Omitted means [`Fidelity::Semantic`]: these two methods only ever
3082    /// produce a read-only view, and a compacted or resumed-across-files
3083    /// transcript — the everyday shape of a long Claude Code session — has no
3084    /// losslessly reconstructable record graph, so refusing to render it made
3085    /// the mirror unusable rather than accurate. A caller that intends to
3086    /// CONTINUE from what it reads asks for a lossless level explicitly and
3087    /// gets the strict refusal back. Every other method (export, translate,
3088    /// branch, handoff, resume_instructions) is lossless-only and has no
3089    /// such knob.
3090    #[serde(default)]
3091    fidelity: Option<Fidelity>,
3092    /// Optional bounded frontend projection. Absent preserves the historical
3093    /// complete-session read contract.
3094    #[serde(default)]
3095    view: Option<SessionReadView>,
3096}
3097
3098#[derive(Deserialize)]
3099struct SessionReadView {
3100    /// Number of trailing normalized messages to return. Zero is treated as
3101    /// one so a caller cannot accidentally request an unbounded empty mode.
3102    #[serde(default)]
3103    tail_messages: Option<usize>,
3104    /// Whether Claude Code child transcripts belong in this view. The
3105    /// frontend default is false; the legacy no-view path remains true.
3106    #[serde(default)]
3107    include_subagents: bool,
3108    /// Preserve human-visible native history across model-context compaction.
3109    #[serde(default)]
3110    display_history: bool,
3111    /// Bound each individual text field so a single tool result cannot turn a
3112    /// small message window into a hundred-megabyte RPC response.
3113    #[serde(default)]
3114    max_message_chars: Option<usize>,
3115}
3116
3117impl LocatorParams {
3118    fn read_fidelity(&self) -> Fidelity {
3119        self.fidelity.unwrap_or(Fidelity::Semantic)
3120    }
3121
3122    fn include_subagents(&self) -> bool {
3123        self.view
3124            .as_ref()
3125            .map(|view| view.include_subagents)
3126            .unwrap_or(true)
3127    }
3128
3129    fn tail_messages(&self) -> Option<usize> {
3130        self.view
3131            .as_ref()
3132            .and_then(|view| view.tail_messages)
3133            .map(|limit| limit.clamp(1, 5_000))
3134    }
3135
3136    fn display_history(&self) -> bool {
3137        self.view.as_ref().is_some_and(|view| view.display_history)
3138    }
3139
3140    fn max_message_chars(&self) -> Option<usize> {
3141        self.view
3142            .as_ref()
3143            .and_then(|view| view.max_message_chars)
3144            .map(|limit| limit.clamp(256, 64_000))
3145    }
3146
3147    fn bound_session(&self, session: &mut Session) {
3148        bound_session_view(session, self.tail_messages(), self.max_message_chars());
3149    }
3150}
3151
3152#[derive(Debug, Clone, Copy, Default, Deserialize)]
3153#[serde(rename_all = "snake_case")]
3154enum InlineMediaMode {
3155    #[default]
3156    Full,
3157    Metadata,
3158}
3159
3160#[derive(Debug, Clone, Default, Deserialize)]
3161#[serde(default)]
3162struct SessionLoadOptions {
3163    include_subagents: Option<bool>,
3164    inline_media: InlineMediaMode,
3165    max_inline_media_bytes: Option<usize>,
3166    message_limit: Option<usize>,
3167    message_offset: Option<usize>,
3168    message_tail: Option<usize>,
3169}
3170
3171impl SessionLoadOptions {
3172    fn validate(&self) -> std::result::Result<(), ServiceError> {
3173        if self.message_tail.is_some()
3174            && (self.message_limit.is_some() || self.message_offset.is_some())
3175        {
3176            return Err(ServiceError::InvalidParams(
3177                "sessions.load options.message_tail cannot be combined with message_limit or message_offset"
3178                    .into(),
3179            ));
3180        }
3181        Ok(())
3182    }
3183}
3184
3185#[derive(Deserialize)]
3186struct LoadSessionParams {
3187    #[serde(flatten)]
3188    read: LocatorParams,
3189    #[serde(default)]
3190    options: Option<SessionLoadOptions>,
3191}
3192
3193#[derive(Deserialize)]
3194struct UnfollowParams {
3195    subscription: String,
3196}
3197
3198#[derive(Debug, Deserialize)]
3199#[serde(deny_unknown_fields)]
3200struct IndexResizeParams {
3201    subscription: String,
3202    limit: usize,
3203}
3204
3205#[derive(Deserialize)]
3206struct ActivitySubscribeParams {
3207    locators: Vec<SessionLocator>,
3208    #[serde(default)]
3209    homes: crate::HarnessHomes,
3210}
3211
3212#[derive(Deserialize)]
3213struct MessageSessionParams {
3214    locator: SessionLocator,
3215    text: String,
3216    /// Name the sender is known by (`fleet-board`, `aaron`). Replies to the
3217    /// message are filed in this sender's mailbox, read with
3218    /// `sessions.inbox`. Defaults to `supercode`.
3219    #[serde(default)]
3220    from_name: Option<String>,
3221    /// Id of the message this one answers.
3222    #[serde(default)]
3223    in_reply_to: Option<String>,
3224    /// File one notice in the sender's mailbox when the receiver next goes idle.
3225    #[serde(default)]
3226    notify_when_idle: bool,
3227    /// The text is the session's own user speaking (a voice bridge, a board
3228    /// the owner types in): user mail, delivered as the user's own turn.
3229    /// `harness.v1` is served only to the machine's owner (its daemon admits
3230    /// operators only), which is the authority this carries.
3231    #[serde(default)]
3232    as_user: bool,
3233    /// Same storage roots discovery accepts, so a caller (and a test) can
3234    /// point the live-session registry somewhere other than `$HOME`.
3235    #[serde(default)]
3236    homes: crate::HarnessHomes,
3237}
3238
3239#[derive(Deserialize)]
3240#[serde(deny_unknown_fields)]
3241struct InboxParams {
3242    /// Sender name used with `sessions.message` (its mailbox), or
3243    #[serde(default)]
3244    from_name: Option<String>,
3245    /// an explicit session address `sc:<machine>:<harness>:<id>`.
3246    #[serde(default)]
3247    address: Option<String>,
3248    /// Include messages already read.
3249    #[serde(default)]
3250    all: bool,
3251}
3252
3253#[derive(Deserialize)]
3254#[serde(deny_unknown_fields)]
3255struct HarnessSettingsParams {
3256    harness: String,
3257}
3258
3259#[derive(Deserialize)]
3260#[serde(deny_unknown_fields)]
3261struct ConfigureHarnessParams {
3262    harness: String,
3263    #[serde(default)]
3264    changes: Vec<crate::HarnessSettingChange>,
3265    #[serde(default)]
3266    expected_revision: Option<String>,
3267}
3268
3269fn claude_inbound_controls_or_error(homes: &crate::HarnessHomes) -> (Value, Value) {
3270    match crate::inspect_harness_interop_settings(homes, HarnessId::CLAUDE_CODE) {
3271        Ok(report) => (
3272            serde_json::to_value(report).unwrap_or(Value::Null),
3273            Value::Null,
3274        ),
3275        Err(error) => (
3276            Value::Null,
3277            Value::String(format!(
3278                "Volter Harness could not inspect Claude Code inbound controls: {error}"
3279            )),
3280        ),
3281    }
3282}
3283
3284/// Deliver `text` into a session that is running right now, or say why not.
3285///
3286/// A refusal is a RESULT, not a JSON-RPC error: "that session is persisted
3287/// only" is an answer about the session, which a mirror renders next to the
3288/// transcript, and this service's error envelope carries no structured data
3289/// field a machine-readable reason could survive in.
3290///
3291/// The door is chosen by the one router every sender uses
3292/// ([`crate::mail_route`]): a session supercode controls gets it through its
3293/// runtime (the default tier); a Claude session it does not control through a
3294/// Claude relay, so the reply comes back; a Codex session through its hook;
3295/// anything else is stored. Replies are filed in the sender's mailbox under
3296/// `reply_to`, read with `sessions.inbox`.
3297async fn message_live_session(params: &MessageSessionParams) -> Value {
3298    use crate::mail_route::{Delivered, NoDoor, Refused};
3299    let (inbound_controls, inbound_controls_error) =
3300        claude_inbound_controls_or_error(&params.homes);
3301    let refused = |reason: &str, message: String| {
3302        json!({
3303            "delivered_to_bus": false,
3304            "refusal": {"reason": reason, "message": message},
3305            "inbound_controls": inbound_controls,
3306            "inbound_controls_error": inbound_controls_error,
3307        })
3308    };
3309    if params.text.trim().is_empty() {
3310        return refused(
3311            crate::claude_peer::ClaudePeerRefusal::DeliveryFailed.as_str(),
3312            "refusing to deliver an empty message".into(),
3313        );
3314    }
3315    let sender = match operator_address(params.from_name.as_deref()) {
3316        Ok(sender) => sender,
3317        Err(message) => return refused("invalid_sender", message),
3318    };
3319    let receiver = match crate::mailbox::MailAddress::new(
3320        crate::mailbox::local_machine_name(),
3321        params.locator.harness.as_str(),
3322        &params.locator.session_id,
3323    ) {
3324        Ok(receiver) => receiver,
3325        Err(error) => return refused("delivery_failed", error.to_string()),
3326    };
3327    // A Claude session's registry name must still name only that session.
3328    if params.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3329        && crate::runtime_mail::controlled_runtime(
3330            HarnessId::CLAUDE_CODE,
3331            &params.locator.session_id,
3332        )
3333        .is_none()
3334    {
3335        if let Err(refusal) =
3336            crate::claude_peer::resolve_live_session(&params.homes, &params.locator.session_id)
3337        {
3338            return refused(refusal.reason.as_str(), refusal.message);
3339        }
3340    }
3341    if params.as_user {
3342        return message_as_user(
3343            params,
3344            sender,
3345            receiver,
3346            inbound_controls,
3347            inbound_controls_error,
3348        )
3349        .await;
3350    }
3351    let door = match crate::mail_route::door_for(&params.homes, &receiver) {
3352        Ok(door) => door,
3353        Err(NoDoor::NotRunning | NoDoor::OtherMachine(_)) => {
3354            return refused(
3355                crate::claude_peer::ClaudePeerRefusal::NotLive.as_str(),
3356                format!(
3357                    "no running `{}` session `{}` is reachable; its transcript is persisted only",
3358                    params.locator.harness.as_str(),
3359                    params.locator.session_id
3360                ),
3361            )
3362        }
3363    };
3364    let mut envelope = match crate::mailbox::Envelope::new(
3365        sender.clone(),
3366        format!("{}@{}", sender.session_id, sender.machine),
3367        crate::mailbox::MailKind::Peer,
3368        crate::mailbox::ReplyVia::Command,
3369        params.text.clone(),
3370    ) {
3371        Ok(envelope) => envelope,
3372        Err(error) => return refused("delivery_failed", error.to_string()),
3373    };
3374    envelope.in_reply_to = params.in_reply_to.clone();
3375    let how = match crate::mail_route::deliver(
3376        &envelope,
3377        &receiver,
3378        &door,
3379        true,
3380        params.notify_when_idle,
3381    )
3382    .await
3383    {
3384        Err(detail) => {
3385            return refused(
3386                crate::claude_peer::ClaudePeerRefusal::DeliveryFailed.as_str(),
3387                detail,
3388            )
3389        }
3390        Ok(Err(Refused::CannotQueueNative)) => unreachable!("sessions.message always wakes"),
3391        Ok(Err(Refused::TooLong(bytes))) => {
3392            return refused(
3393                "too_long",
3394                format!(
3395                    "the message is {bytes} bytes; the limit is {}",
3396                    crate::mail_route::MAX_RELAYED_BYTES
3397                ),
3398            )
3399        }
3400        Ok(Ok(delivered)) => match delivered {
3401            Delivered::Steered => "steered",
3402            Delivered::Started => "started",
3403            Delivered::Native { busy: true } => "next_tool_call",
3404            Delivered::Native { busy: false } => "started",
3405            Delivered::Hooked => "hook",
3406            Delivered::Queued => "queued",
3407            Delivered::Stored => "stored",
3408            Delivered::Operator => "filed",
3409        },
3410    };
3411    json!({
3412        "delivered_to_bus": !matches!(door, crate::mail_route::Door::Stored),
3413        "message_id": envelope.id,
3414        "reply_to": sender.to_string(),
3415        "target": {
3416            "harness": params.locator.harness.as_str(),
3417            "session_id": params.locator.session_id,
3418            "name": match &door {
3419                crate::mail_route::Door::Native(session) => Some(session.name.clone()),
3420                _ => None,
3421            },
3422        },
3423        "delivery": {"door": door.name(), "how": how},
3424        "inbound_controls": inbound_controls,
3425        "inbound_controls_error": inbound_controls_error,
3426    })
3427}
3428
3429/// `sessions.message` with `as_user`: user mail, through the one door that
3430/// carries the user's authority (a hosted runtime's input, or the session's
3431/// pane when its composer is empty; otherwise it waits in the mailbox).
3432async fn message_as_user(
3433    params: &MessageSessionParams,
3434    sender: crate::mailbox::MailAddress,
3435    receiver: crate::mailbox::MailAddress,
3436    inbound_controls: Value,
3437    inbound_controls_error: Value,
3438) -> Value {
3439    let envelope = match crate::mailbox::Envelope::new(
3440        sender.clone(),
3441        format!("{}@{}", sender.session_id, sender.machine),
3442        crate::mailbox::MailKind::User,
3443        crate::mailbox::ReplyVia::None,
3444        params.text.clone(),
3445    ) {
3446        Ok(envelope) => envelope,
3447        Err(error) => {
3448            return json!({
3449                "delivered_to_bus": false,
3450                "refusal": {"reason": "delivery_failed", "message": error.to_string()},
3451                "inbound_controls": inbound_controls,
3452                "inbound_controls_error": inbound_controls_error,
3453            })
3454        }
3455    };
3456    match crate::mail_route::deliver_user_turn(&params.homes, &envelope, &receiver).await {
3457        Ok(turn) => json!({
3458            "delivered_to_bus": turn != crate::mail_route::UserTurn::Waiting,
3459            "message_id": envelope.id,
3460            "target": {
3461                "harness": params.locator.harness.as_str(),
3462                "session_id": params.locator.session_id,
3463            },
3464            "delivery": {
3465                "door": match turn {
3466                    crate::mail_route::UserTurn::Steered | crate::mail_route::UserTurn::Started => "runtime",
3467                    _ => "pane",
3468                },
3469                "how": turn.as_str(),
3470            },
3471            "inbound_controls": inbound_controls,
3472            "inbound_controls_error": inbound_controls_error,
3473        }),
3474        Err(message) => json!({
3475            "delivered_to_bus": false,
3476            "refusal": {"reason": "no_user_door", "message": message},
3477            "inbound_controls": inbound_controls,
3478            "inbound_controls_error": inbound_controls_error,
3479        }),
3480    }
3481}
3482
3483#[derive(Deserialize)]
3484#[serde(deny_unknown_fields)]
3485struct ActivityUnderParams {
3486    /// Root processes (a terminal pane's shell, say).
3487    pids: Vec<u32>,
3488    #[serde(default)]
3489    homes: crate::HarnessHomes,
3490}
3491
3492/// `harness.v1.sessions.activity_under`: the harness session running beneath
3493/// each root process, with its activity, so a terminal's attention takes a
3494/// harness pane's liveness from the harness's own lifecycle.
3495async fn activity_under_call(params: Value) -> std::result::Result<Value, ServiceError> {
3496    let params = decode::<ActivityUnderParams>(params)?;
3497    if params.pids.len() > 1024 {
3498        return Err(ServiceError::InvalidParams(
3499            "sessions.activity_under accepts at most 1024 pids".into(),
3500        ));
3501    }
3502    let found = crate::session_activity::activity_under(&params.pids, &params.homes)
3503        .await
3504        .map_err(ServiceError::Sdk)?;
3505    Ok(json!({
3506        "activities": found
3507            .into_iter()
3508            .map(|(pid, activity)| json!({"pid": pid, "activity": activity}))
3509            .collect::<Vec<_>>(),
3510    }))
3511}
3512
3513/// Mailbox address of an operator sender (a board, a person's shell) that is
3514/// not itself a harness session: `sc:<machine>:operator:<name>`.
3515fn operator_address(
3516    name: Option<&str>,
3517) -> std::result::Result<crate::mailbox::MailAddress, String> {
3518    let name: String = name
3519        .unwrap_or("supercode")
3520        .trim()
3521        .chars()
3522        .map(|character| {
3523            if character.is_whitespace() || character == '@' {
3524                '-'
3525            } else {
3526                character
3527            }
3528        })
3529        .collect();
3530    if name.is_empty() {
3531        return Err("from_name must not be empty".into());
3532    }
3533    crate::mailbox::MailAddress::new(crate::mailbox::local_machine_name(), "operator", name)
3534        .map_err(|error| error.to_string())
3535}
3536
3537/// `harness.v1.sessions.inbox`: a sender's mailbox, each message with the
3538/// text its reader sees. Unread messages are claimed and acknowledged by this
3539/// read, so a second read does not return them again.
3540fn inbox_call(params: InboxParams) -> std::result::Result<Value, ServiceError> {
3541    let address = match (&params.address, &params.from_name) {
3542        (Some(address), None) => crate::mailbox::MailAddress::parse(address)
3543            .map_err(|error| ServiceError::InvalidParams(error.to_string()))?,
3544        (None, name) => operator_address(name.as_deref()).map_err(ServiceError::InvalidParams)?,
3545        (Some(_), Some(_)) => {
3546            return Err(ServiceError::InvalidParams(
3547                "sessions.inbox takes from_name or address, not both".into(),
3548            ))
3549        }
3550    };
3551    let operation = |error: std::io::Error| ServiceError::Operation(error.to_string());
3552    let mailbox =
3553        crate::mailbox::Mailbox::open(&crate::mailbox::mail_root(), &address).map_err(operation)?;
3554    let claimed = mailbox.claim_unread().map_err(operation)?;
3555    let mut messages: Vec<Value> = Vec::new();
3556    if params.all {
3557        for stored in mailbox.list().map_err(operation)? {
3558            if stored.state == crate::mailbox::MailState::Read {
3559                messages.push(json!({"state": "read", "envelope": stored.envelope, "rendered": stored.envelope.render()}));
3560            }
3561        }
3562    }
3563    for stored in &claimed {
3564        messages.push(json!({"state": "unread", "envelope": stored.envelope, "rendered": stored.envelope.render()}));
3565    }
3566    for stored in &claimed {
3567        mailbox.acknowledge(stored).map_err(operation)?;
3568    }
3569    Ok(json!({"address": address.to_string(), "messages": messages}))
3570}
3571
3572/// Source identity of one follow subscription, plus the last lifecycle state
3573/// already reported on it. The follower itself stays purely persistence-facing.
3574// Only the adapter-api poll reads these; the subscription bookkeeping itself is
3575// shared by both builds.
3576#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3577struct FollowedSource {
3578    harness: String,
3579    session_id: String,
3580    reported: Option<String>,
3581}
3582
3583#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3584struct ActivitySubscription {
3585    locators: Vec<SessionLocator>,
3586    homes: crate::HarnessHomes,
3587    reported: BTreeMap<(String, String), crate::SessionActivity>,
3588}
3589
3590/// Add what makes an indexed row behaviorally equivalent to a discovered row: the attach
3591/// endpoint of a session supercode hosts, and the door a message reaches it by.
3592///
3593/// The durable index owns only persistence metadata. Both are projections: every message/attach
3594/// operation revalidates its authority, so publishing one here never trusts a stale browser-held
3595/// handle. The doors are read once per batch ([`crate::mail_route::LiveSessions`]).
3596fn live_descriptor_value(
3597    session: &SessionDescriptor,
3598    doors: &crate::mail_route::LiveSessions,
3599) -> std::result::Result<Value, ServiceError> {
3600    let mut value = serde_json::to_value(session)
3601        .map_err(|error| ServiceError::Operation(error.to_string()))?;
3602    if let Some(workspace) = &session.cwd {
3603        let source = LiveRuntimeSource {
3604            harness: session.locator.harness.as_str().to_string(),
3605            session_id: session.locator.session_id.clone(),
3606            workspace: workspace.clone(),
3607        };
3608        if let Some(endpoint) = discover_live_runtime(&source)
3609            .map_err(|error| ServiceError::Operation(error.to_string()))?
3610        {
3611            value["live_endpoint"] = json!(endpoint.as_str());
3612        }
3613    }
3614    // How a message reaches this session right now, chosen by the same
3615    // router every sender uses: `runtime` (supercode controls it), `native`,
3616    // `hook` or `stored`. Absent when no process is running it.
3617    if let Some(door) = doors.door(
3618        session.locator.harness.as_str(),
3619        &session.locator.session_id,
3620    ) {
3621        value["delivery"] = json!(door);
3622    }
3623    Ok(value)
3624}
3625
3626fn live_index_changes(
3627    changes: Vec<crate::session_index::SessionIndexChange>,
3628    homes: &HarnessHomes,
3629) -> std::result::Result<Vec<Value>, ServiceError> {
3630    use crate::session_index::SessionIndexChange;
3631    let doors = crate::mail_route::LiveSessions::read(homes);
3632    changes
3633        .into_iter()
3634        .map(|change| match change {
3635            SessionIndexChange::Added { descriptor } => Ok(json!({
3636                "kind": "added",
3637                "descriptor": live_descriptor_value(&descriptor, &doors)?,
3638            })),
3639            SessionIndexChange::Updated { descriptor } => Ok(json!({
3640                "kind": "updated",
3641                "descriptor": live_descriptor_value(&descriptor, &doors)?,
3642            })),
3643            SessionIndexChange::Removed { key } => Ok(json!({
3644                "kind": "removed",
3645                "key": key,
3646            })),
3647        })
3648        .collect()
3649}
3650
3651fn legacy_live_status(activity: &crate::SessionActivity) -> Option<&'static str> {
3652    use crate::{SessionPresence, SessionTurnState};
3653    match (activity.presence, activity.turn) {
3654        (SessionPresence::Persisted, _) => None,
3655        (SessionPresence::Running, SessionTurnState::Working) => Some("busy"),
3656        (SessionPresence::Running, SessionTurnState::Idle) => Some("idle"),
3657        // The normalized activity object can honestly report a live owner even
3658        // when the stock harness never published a turn status. Preserve the
3659        // older field's stricter contract instead of guessing `running`.
3660        (SessionPresence::Running, SessionTurnState::Unknown)
3661            if activity.evidence.native_state.is_none() =>
3662        {
3663            None
3664        }
3665        (SessionPresence::Running, _) | (SessionPresence::ShuttingDown, _) => Some("running"),
3666    }
3667}
3668
3669#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
3670#[serde(rename_all = "kebab-case")]
3671enum TransferFormat {
3672    ClaudeCode,
3673    Codex,
3674    #[serde(rename = "opencode", alias = "open-code")]
3675    OpenCode,
3676    Pi,
3677    Grok,
3678    Gemini,
3679    Goose,
3680    /// UNI-18: a Hermes target. Its artifact is the Codex rollout that
3681    /// `hermes sessions import --from codex` reads; `sessions.export` performs
3682    /// that import into the Hermes home.
3683    Hermes,
3684}
3685
3686impl TransferFormat {
3687    fn id(self) -> &'static str {
3688        match self {
3689            Self::ClaudeCode => HarnessId::CLAUDE_CODE,
3690            Self::Codex => HarnessId::CODEX,
3691            Self::OpenCode => HarnessId::OPENCODE,
3692            Self::Pi => HarnessId::PI,
3693            Self::Grok => HarnessId::GROK,
3694            Self::Gemini => HarnessId::GEMINI,
3695            Self::Goose => HarnessId::GOOSE,
3696            Self::Hermes => HarnessId::HERMES,
3697        }
3698    }
3699}
3700
3701impl From<TransferFormat> for SessionFormat {
3702    fn from(value: TransferFormat) -> Self {
3703        match value {
3704            TransferFormat::ClaudeCode => Self::ClaudeCode,
3705            TransferFormat::Codex => Self::Codex,
3706            TransferFormat::OpenCode => Self::OpenCode,
3707            TransferFormat::Pi => Self::Pi,
3708            TransferFormat::Grok => Self::Grok,
3709            TransferFormat::Gemini => Self::Gemini,
3710            TransferFormat::Goose => Self::Goose,
3711            // a Hermes artifact is the Codex rollout Hermes imports
3712            TransferFormat::Hermes => Self::Codex,
3713        }
3714    }
3715}
3716
3717#[derive(Deserialize)]
3718struct ImportSessionParams {
3719    source_harness: TransferFormat,
3720    content: String,
3721}
3722
3723#[derive(Deserialize)]
3724struct ExportSessionParams {
3725    locator: SessionLocator,
3726    target_harness: TransferFormat,
3727}
3728
3729#[derive(Deserialize)]
3730struct ReduceSessionParams {
3731    locator: SessionLocator,
3732    target_harness: TransferFormat,
3733    #[serde(default = "default_keep_last")]
3734    keep_last: usize,
3735}
3736
3737fn default_keep_last() -> usize {
3738    6
3739}
3740
3741#[derive(Deserialize)]
3742struct BranchSessionParams {
3743    locator: SessionLocator,
3744    #[serde(default)]
3745    target_harness: Option<TransferFormat>,
3746}
3747
3748#[derive(Deserialize)]
3749struct HandoffSessionParams {
3750    locator: SessionLocator,
3751    target_harness: TransferFormat,
3752    #[serde(default)]
3753    cwd: Option<PathBuf>,
3754}
3755
3756#[derive(Deserialize)]
3757struct MaterializeSessionParams {
3758    artifact: crate::native_materialize::MaterializeArtifact,
3759    cwd: PathBuf,
3760    /// Where the continuation is written; unset roots are the environment's own, as discovery reads them.
3761    #[serde(default)]
3762    homes: HarnessHomes,
3763}
3764
3765#[derive(Debug, Clone, Copy, Default, Deserialize)]
3766#[serde(rename_all = "snake_case")]
3767enum ResumePolicy {
3768    #[default]
3769    Default,
3770    Yolo,
3771}
3772
3773#[derive(Deserialize)]
3774struct ResumeInstructionsParams {
3775    locator: SessionLocator,
3776    #[serde(default)]
3777    cwd: Option<PathBuf>,
3778    #[serde(default)]
3779    policy: ResumePolicy,
3780}
3781
3782/// `harness.v1.workflow.load` parameters: which harness's board, and its home.
3783#[derive(Deserialize)]
3784struct WorkflowLoadParams {
3785    from: crate::workflow_doors::WorkflowHarness,
3786    home: PathBuf,
3787}
3788
3789/// ONT-4 `harness.v1.orchestration.load` parameters. `flavor` says which layout the
3790/// folder is read as; our own is the default.
3791#[derive(Deserialize)]
3792struct OrchestrationLoadParams {
3793    root: PathBuf,
3794    #[serde(default)]
3795    flavor: crate::orchestration_doors::HomeFlavor,
3796}
3797
3798/// ONT-4 `harness.v1.orchestration.save` parameters. `vault` is merged into the
3799/// home's own secrets; a caller that sends none keeps what is on disk.
3800#[derive(Deserialize)]
3801struct OrchestrationSaveParams {
3802    root: PathBuf,
3803    orchestration: crate::orchestration::Orchestration,
3804    #[serde(default)]
3805    vault: BTreeMap<String, String>,
3806}
3807
3808/// ONT-4 `harness.v1.orchestration.compile` parameters.
3809#[derive(Deserialize)]
3810struct OrchestrationCompileParams {
3811    from: crate::orchestration_doors::OrchestrationHarness,
3812    home: PathBuf,
3813}
3814
3815/// ONT-4 `harness.v1.orchestration.decompile` parameters. `source` is the home the
3816/// orchestration was compiled from: it is re-compiled to recover the io bookkeeping
3817/// that byte reuse and the live-store refusal (UNI-18) are decided from.
3818#[derive(Deserialize)]
3819struct OrchestrationDecompileParams {
3820    to: crate::orchestration_doors::OrchestrationHarness,
3821    orchestration: crate::orchestration::Orchestration,
3822    source: PathBuf,
3823    #[serde(default)]
3824    source_flavor: crate::orchestration_doors::SourceFlavor,
3825    dest: PathBuf,
3826    #[serde(default)]
3827    vault: BTreeMap<String, String>,
3828}
3829
3830/// `harness.v1.orchestration.import` parameters: another harness's home, and the
3831/// folder of ours it becomes.
3832#[derive(Deserialize)]
3833struct OrchestrationImportParams {
3834    from: crate::orchestration_doors::OrchestrationHarness,
3835    home: PathBuf,
3836    into: PathBuf,
3837}
3838
3839/// `harness.v1.orchestration.export` parameters: a folder of ours, and the home of
3840/// another harness it becomes.
3841#[derive(Deserialize)]
3842struct OrchestrationExportParams {
3843    to: crate::orchestration_doors::OrchestrationHarness,
3844    root: PathBuf,
3845    dest: PathBuf,
3846}
3847
3848/// `harness.v1.jobs.get` parameters.
3849#[derive(Deserialize)]
3850struct JobsGetParams {
3851    harness: String,
3852    id: String,
3853    #[serde(default)]
3854    homes: crate::HarnessHomes,
3855}
3856
3857/// ORCH-18: run one mutating job verb through the harness's own CLI.
3858///
3859/// The refusal ladder is deliberate: a harness with no scheduled-job concept
3860/// at all answers with the SAME sentence `jobs.list` gives it, and a harness
3861/// that has jobs but publishes no client-callable verb (Claude Code, whose
3862/// jobs are created by the model inside a session) answers with its own
3863/// reason. Neither is ever a silent no-op.
3864fn mutate_job(
3865    verb: crate::jobs_control::JobVerb,
3866    params: Value,
3867) -> std::result::Result<Value, ServiceError> {
3868    let mutation = decode::<crate::jobs_control::JobMutation>(params)?;
3869    refuse_harness_without_jobs(&mutation.harness, &format!("jobs.{}", verb.as_str()))?;
3870    let outcome = crate::jobs_control::mutate(verb, &mutation).map_err(job_control_error)?;
3871    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3872}
3873
3874/// ORCH-22: run one mutating skills verb through the harness's own door.
3875///
3876/// The refusal ladder mirrors `jobs.*`: a harness with no skills root at all
3877/// answers with the same sentence `skills.list` gives it, and a harness whose
3878/// door does not publish this verb (OpenClaw has no `skills remove` at the
3879/// pin) answers with its own reason. Neither is ever a silent no-op.
3880fn mutate_skill(
3881    verb: crate::skills_control::SkillVerb,
3882    params: Value,
3883) -> std::result::Result<Value, ServiceError> {
3884    let mutation = decode::<crate::skills_control::SkillMutation>(params)?;
3885    if !crate::skills_control::supports_skill_control(&mutation.harness) {
3886        return Err(ServiceError::UnsupportedAction(format!(
3887            "`{}` has no skills root Volter Harness reads; `skills.{}` is supported for: {}",
3888            mutation.harness,
3889            verb.as_str(),
3890            crate::skills_control::CONTROLLED_SKILL_HARNESSES.join(", ")
3891        )));
3892    }
3893    let outcome =
3894        crate::skills_control::mutate_skill(verb, &mutation).map_err(skill_control_error)?;
3895    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3896}
3897
3898/// The skills twin of [`job_control_error`], with the same mapping rule.
3899fn skill_control_error(error: crate::skills_control::SkillControlError) -> ServiceError {
3900    match error {
3901        crate::skills_control::SkillControlError::Unsupported(message) => {
3902            ServiceError::UnsupportedAction(message)
3903        }
3904        crate::skills_control::SkillControlError::Invalid(message) => {
3905            ServiceError::InvalidParams(message)
3906        }
3907        crate::skills_control::SkillControlError::Failed(message) => {
3908            ServiceError::Operation(message)
3909        }
3910    }
3911}
3912
3913/// ORCH-21: run one mutating profile verb through the harness's own CLI.
3914///
3915/// The refusal ladder mirrors `mutate_job`'s: a harness with no profile
3916/// concept at all answers with the SAME sentence `profiles.list` gives it, and
3917/// a harness that HAS profiles but publishes no client-callable verb (Codex's
3918/// file-authored `[profiles.<name>]` tables, supercode's compiled-in presets)
3919/// answers with its own reason. Neither is ever a silent no-op.
3920fn mutate_profile(
3921    verb: crate::profiles_control::ProfileVerb,
3922    params: Value,
3923) -> std::result::Result<Value, ServiceError> {
3924    let mutation = decode::<crate::profiles_control::ProfileMutation>(params)?;
3925    let outcome =
3926        crate::profiles_control::mutate(verb, &mutation).map_err(profile_control_error)?;
3927    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3928}
3929
3930/// The same mapping `job_control_error` applies, for the profile noun.
3931fn profile_control_error(error: crate::profiles_control::ProfileControlError) -> ServiceError {
3932    match error {
3933        crate::profiles_control::ProfileControlError::Unsupported(message) => {
3934            ServiceError::UnsupportedAction(message)
3935        }
3936        crate::profiles_control::ProfileControlError::Invalid(message) => {
3937            ServiceError::InvalidParams(message)
3938        }
3939        crate::profiles_control::ProfileControlError::Failed(message) => {
3940            ServiceError::Operation(message)
3941        }
3942    }
3943}
3944
3945/// Map a controlled-tier failure onto the service's error vocabulary. A verb
3946/// the harness lacks is `UnsupportedAction`; a harness verb that RAN and
3947/// failed carries its own stderr through as the operation error.
3948fn job_control_error(error: crate::jobs_control::JobControlError) -> ServiceError {
3949    match error {
3950        crate::jobs_control::JobControlError::Unsupported(message) => {
3951            ServiceError::UnsupportedAction(message)
3952        }
3953        crate::jobs_control::JobControlError::Invalid(message) => {
3954            ServiceError::InvalidParams(message)
3955        }
3956        crate::jobs_control::JobControlError::Failed(message) => ServiceError::Operation(message),
3957    }
3958}
3959
3960/// Map an ORCH-19 controlled-tier failure onto the service's error
3961/// vocabulary. A verb the harness has no door for is `UnsupportedAction`; a
3962/// door that RAN and failed carries the harness's own stderr / HTTP body
3963/// through as the operation error.
3964fn session_control_error(error: crate::SessionControlError) -> ServiceError {
3965    match error {
3966        crate::SessionControlError::Unsupported(message) => {
3967            ServiceError::UnsupportedAction(message)
3968        }
3969        crate::SessionControlError::Invalid(message) => ServiceError::InvalidParams(message),
3970        crate::SessionControlError::Failed(message) => ServiceError::Operation(message),
3971    }
3972}
3973
3974/// A harness without a scheduled-job concept refuses the verb rather than
3975/// answering with an empty list — an absent capability and an empty inventory
3976/// are different answers (the same rule `runtimes.capabilities` applies to
3977/// `steer`).
3978fn refuse_harness_without_jobs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3979    if crate::jobs::supports_jobs(harness) {
3980        return Ok(());
3981    }
3982    Err(ServiceError::UnsupportedAction(format!(
3983        "`{harness}` has no scheduled jobs; `{verb}` is supported for: {}",
3984        crate::jobs::JOB_HARNESSES.join(", ")
3985    )))
3986}
3987
3988/// `harness.v1.runs.get` parameters.
3989#[derive(Deserialize)]
3990struct RunsGetParams {
3991    harness: String,
3992    id: String,
3993    #[serde(default)]
3994    homes: crate::HarnessHomes,
3995}
3996
3997/// A harness with no run store refuses the verb rather than answering with an
3998/// empty history — the same rule `jobs.list` applies. Claude Code lands here
3999/// on purpose: its cron fires are ordinary turns inside the session that
4000/// created the job, so there is no fire record to list.
4001fn refuse_harness_without_runs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
4002    if crate::runs::supports_runs(harness) {
4003        return Ok(());
4004    }
4005    Err(ServiceError::UnsupportedAction(format!(
4006        "`{harness}` keeps no run store; `{verb}` is supported for: {}",
4007        crate::runs::RUN_HARNESSES.join(", ")
4008    )))
4009}
4010
4011#[derive(Serialize)]
4012struct SessionArtifact {
4013    source_harness: HarnessId,
4014    target_harness: &'static str,
4015    session_id: Option<String>,
4016    content: String,
4017    suggested_filename: String,
4018    files: Vec<SessionArtifactFile>,
4019    fidelity: Fidelity,
4020    residue: Vec<String>,
4021}
4022
4023#[derive(Serialize)]
4024struct SessionArtifactFile {
4025    path: String,
4026    content: String,
4027    role: ArtifactFileRole,
4028}
4029
4030#[derive(Serialize)]
4031#[serde(rename_all = "snake_case")]
4032enum ArtifactFileRole {
4033    Primary,
4034    Subagent,
4035    Bundle,
4036    SourceRecovery,
4037}
4038
4039#[derive(Serialize)]
4040struct StructuredLaunch {
4041    cwd: PathBuf,
4042    program: String,
4043    arguments: Vec<String>,
4044    env: BTreeMap<String, String>,
4045}
4046
4047struct HandoffInstructions {
4048    launch: StructuredLaunch,
4049    materialize: Option<StructuredLaunch>,
4050    requires_materialization: bool,
4051    note: String,
4052}
4053
4054#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
4055#[serde(rename_all = "snake_case")]
4056enum HarnessProbeLevel {
4057    #[default]
4058    Passive,
4059    Handshake,
4060}
4061
4062#[derive(Default, Deserialize)]
4063#[serde(default)]
4064struct HarnessInventoryParams {
4065    harness: Option<HarnessId>,
4066    harnesses: Vec<HarnessId>,
4067    workspace: Option<PathBuf>,
4068    probe: HarnessProbeLevel,
4069    include_sessions: bool,
4070    /// Omit subprocess-based `--version` calls when a latency-sensitive UI only needs readiness.
4071    skip_versions: bool,
4072}
4073
4074#[derive(Deserialize)]
4075struct HarnessAuthenticationParams {
4076    harness: HarnessId,
4077}
4078
4079#[derive(Deserialize)]
4080struct BeginHarnessAuthenticationParams {
4081    harness: HarnessId,
4082    #[serde(default = "local_browser_authentication_environment")]
4083    environment: crate::HarnessAuthenticationEnvironment,
4084    #[serde(default)]
4085    method: Option<crate::HarnessAuthenticationMethodId>,
4086    #[serde(default)]
4087    cwd: Option<PathBuf>,
4088}
4089
4090fn local_browser_authentication_environment() -> crate::HarnessAuthenticationEnvironment {
4091    crate::HarnessAuthenticationEnvironment::LocalBrowser
4092}
4093
4094#[derive(Serialize)]
4095struct HarnessInventoryReport {
4096    probe: HarnessProbeLevel,
4097    workspace: Option<PathBuf>,
4098    harnesses: Vec<LocalHarness>,
4099}
4100
4101#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4102#[serde(rename_all = "snake_case")]
4103enum HarnessAuthState {
4104    Ready,
4105    Configured,
4106    Required,
4107    Unknown,
4108}
4109
4110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4111#[serde(rename_all = "snake_case")]
4112enum HarnessRuntimeState {
4113    Ready,
4114    Degraded,
4115    Unavailable,
4116}
4117
4118#[derive(Serialize)]
4119struct HarnessSessionCounts {
4120    global: Option<usize>,
4121    workspace: Option<usize>,
4122}
4123
4124/// Receipt-backed evidence that a harness has a RUNNING instance right now,
4125/// distinct from being merely installed (UNI-7). Detection is passive and
4126/// default-on: a gateway liveness connect for daemon harnesses, a fresh
4127/// SQLite WAL stamp for store-writer harnesses (precedent: the opencode
4128/// follower's -wal/-shm freshness). Control stays behind per-connection
4129/// grants — this reports observations only.
4130/// ORCH-17: the gateway-health noun on an inventory row. Derived from the
4131/// UNI-7 running-instance probe (Hermes: `state.db-wal` freshness; OpenClaw:
4132/// a TCP connect to the gateway endpoint resolved from its OWN config) plus
4133/// the executable version — never by starting anything.
4134#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
4135#[serde(rename_all = "snake_case")]
4136pub enum GatewayState {
4137    Up,
4138    Down,
4139    Unknown,
4140}
4141
4142/// ORCH-17: `gateway` on a `harness.v1.harnesses.list` row.
4143#[derive(Debug, Clone, Serialize)]
4144pub struct GatewayHealth {
4145    pub state: GatewayState,
4146    /// The endpoint supercode would connect to (OpenClaw: the gateway
4147    /// WebSocket resolved from `openclaw.json`; core harnesses: their
4148    /// declared connect address when one exists). `None` when the harness
4149    /// has no single endpoint (Hermes multiplexes platforms).
4150    #[serde(skip_serializing_if = "Option::is_none")]
4151    pub endpoint: Option<String>,
4152    #[serde(skip_serializing_if = "Option::is_none")]
4153    pub version: Option<String>,
4154    /// What the verdict rests on, or why it is `unknown`.
4155    pub evidence: String,
4156    pub checked_at_ms: u64,
4157}
4158
4159/// OpenClaw's gateway WebSocket endpoint, resolved from its own config the
4160/// way the registry's connect descriptor prescribes (`gateway.url`, else
4161/// `gateway.port`, else the documented default).
4162fn openclaw_gateway_endpoint(home: &Path) -> String {
4163    let config_path = home.join(".openclaw/openclaw.json");
4164    let gateway = std::fs::read_to_string(&config_path)
4165        .ok()
4166        .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
4167        .and_then(|config| config.get("gateway").cloned());
4168    if let Some(url) = gateway
4169        .as_ref()
4170        .and_then(|gateway| gateway.get("url"))
4171        .and_then(serde_json::Value::as_str)
4172    {
4173        return url.to_string();
4174    }
4175    let port = gateway
4176        .as_ref()
4177        .and_then(|gateway| gateway.get("port"))
4178        .and_then(serde_json::Value::as_u64)
4179        .unwrap_or(18789);
4180    format!("ws://127.0.0.1:{port}")
4181}
4182
4183/// Ask Hermes itself (`hermes gateway status`, read-only, ~1 s) whether its
4184/// gateway is up. The command is per-host launchd/systemd text without a JSON
4185/// form at 0.19–0.21; the verdict is read from the lines it prints:
4186/// "supervised by launchd (PID …)" / "is running" → up, "not running" /
4187/// "not installed" → down, anything else → no verdict. `SUPERCODE_HERMES_BIN`
4188/// overrides the executable so a fake can stand in under test.
4189fn hermes_gateway_status() -> Option<(GatewayState, String)> {
4190    let program = crate::harness_command::harness_program(HarnessId::HERMES).ok()?;
4191    let output = std::process::Command::new(&program)
4192        .args(["gateway", "status"])
4193        .stdin(std::process::Stdio::null())
4194        .output()
4195        .ok()?;
4196    let text = format!(
4197        "{}{}",
4198        String::from_utf8_lossy(&output.stdout),
4199        String::from_utf8_lossy(&output.stderr)
4200    );
4201    let verdict = text.lines().find_map(|line| {
4202        let l = line.trim();
4203        if l.contains("supervised by launchd (PID")
4204            || l.contains("supervised by systemd (PID")
4205            || l.contains("Gateway is running")
4206            || l.contains("process is running")
4207        {
4208            Some((GatewayState::Up, format!("`hermes gateway status`: {l}")))
4209        } else if l.contains("not running") || l.contains("not installed") {
4210            Some((GatewayState::Down, format!("`hermes gateway status`: {l}")))
4211        } else {
4212            None
4213        }
4214    });
4215    verdict
4216}
4217
4218fn gateway_health(
4219    id: &str,
4220    installed: bool,
4221    running: Option<&RunningInstance>,
4222    version: Option<&str>,
4223) -> GatewayHealth {
4224    let checked_at_ms = now_epoch_ms();
4225    let home = std::env::var_os("HOME").map(PathBuf::from);
4226    match id {
4227        HarnessId::HERMES | HarnessId::OPENCLAW => {
4228            let endpoint = (id == HarnessId::OPENCLAW)
4229                .then(|| home.as_deref().map(openclaw_gateway_endpoint))
4230                .flatten();
4231            let (state, evidence) = match running {
4232                Some(instance) => (GatewayState::Up, instance.evidence.clone()),
4233                None if !installed => (
4234                    GatewayState::Unknown,
4235                    format!("`{id}` is not installed; no gateway to probe"),
4236                ),
4237                None if id == HarnessId::HERMES => match hermes_gateway_status() {
4238                    // The harness's own door outranks the WAL heuristic: an idle
4239                    // gateway writes nothing for minutes yet is up.
4240                    Some((state, evidence)) => (state, evidence),
4241                    None => (
4242                        GatewayState::Down,
4243                        "no fresh state.db-wal activity under ~/.hermes and `hermes gateway status` gave no verdict".to_string(),
4244                    ),
4245                },
4246                None => (
4247                    GatewayState::Down,
4248                    format!(
4249                        "no TCP listener at {}",
4250                        endpoint.as_deref().unwrap_or("the gateway endpoint")
4251                    ),
4252                ),
4253            };
4254            GatewayHealth {
4255                state,
4256                endpoint,
4257                version: version.map(str::to_string),
4258                evidence,
4259                checked_at_ms,
4260            }
4261        }
4262        // ORC-7: the orchestrator's gateway IS its daemon, and the daemon's
4263        // own lease file is the record of it. A lease naming a live pid is
4264        // up; a lease whose process is gone is down and says so as a STALE
4265        // lease, never as "no lease"; no lease at all is down. Nothing is
4266        // started, and no port is guessed — the daemon multiplexes adapters
4267        // the way Hermes does, so it has no single endpoint either.
4268        HarnessId::ORCHESTRATOR => {
4269            let root = crate::HarnessHomes::default().orchestrator;
4270            let (state, evidence) = match crate::orchestrator::read_lease(&root) {
4271                Some(lease) if lease.is_live() => (
4272                    GatewayState::Up,
4273                    format!(
4274                        "`{}` names pid {} (started {}), which is live",
4275                        crate::orchestrator::lock_path(&root).display(),
4276                        lease.pid,
4277                        lease.started_at
4278                    ),
4279                ),
4280                Some(lease) => (
4281                    GatewayState::Down,
4282                    format!(
4283                        "stale lease `{}`: pid {} is gone",
4284                        crate::orchestrator::lock_path(&root).display(),
4285                        lease.pid
4286                    ),
4287                ),
4288                None => (
4289                    GatewayState::Down,
4290                    format!(
4291                        "no lease at `{}`; `supercode orchestrator start` writes one",
4292                        crate::orchestrator::lock_path(&root).display()
4293                    ),
4294                ),
4295            };
4296            GatewayHealth {
4297                state,
4298                endpoint: None,
4299                version: version.map(str::to_string),
4300                evidence,
4301                checked_at_ms,
4302            }
4303        }
4304        _ => GatewayHealth {
4305            state: GatewayState::Unknown,
4306            endpoint: None,
4307            version: version.map(str::to_string),
4308            evidence: format!("`{id}` runs per session, not as a gateway"),
4309            checked_at_ms,
4310        },
4311    }
4312}
4313
4314#[derive(Debug, Clone, Serialize)]
4315struct RunningInstance {
4316    /// How the instance was detected.
4317    method: RunningInstanceMethod,
4318    /// The evidence the verdict rests on (endpoint reached / WAL path+age).
4319    evidence: String,
4320    /// Epoch-ms instant the probe executed.
4321    checked_at_ms: u64,
4322}
4323
4324#[derive(Debug, Clone, Copy, Serialize)]
4325#[serde(rename_all = "snake_case")]
4326enum RunningInstanceMethod {
4327    /// A TCP connect to the harness's own configured gateway endpoint
4328    /// succeeded.
4329    GatewayConnect,
4330    /// The harness's session store has an active SQLite WAL (a live writer
4331    /// holds the store open and stamped it recently).
4332    StoreWalActivity,
4333}
4334
4335fn now_epoch_ms() -> u64 {
4336    std::time::SystemTime::now()
4337        .duration_since(std::time::UNIX_EPOCH)
4338        .map(|elapsed| elapsed.as_millis() as u64)
4339        .unwrap_or(0)
4340}
4341
4342/// OpenClaw: the gateway endpoint comes from the harness's OWN config
4343/// (`<home>/.openclaw/openclaw.json` — `gateway.url` or `gateway.port`,
4344/// default port 18789); a successful TCP connect is the running signal.
4345fn probe_openclaw_running(home: &Path) -> Option<RunningInstance> {
4346    let config_path = home.join(".openclaw/openclaw.json");
4347    let text = std::fs::read_to_string(&config_path).ok();
4348    let gateway = text
4349        .as_deref()
4350        .and_then(|raw| serde_json::from_str::<serde_json::Value>(raw).ok())
4351        .and_then(|config| config.get("gateway").cloned());
4352    let address = gateway
4353        .as_ref()
4354        .and_then(|gateway| gateway.get("url"))
4355        .and_then(serde_json::Value::as_str)
4356        .and_then(|url| {
4357            url.split("://").nth(1).map(|rest| {
4358                rest.trim_end_matches('/')
4359                    .split('/')
4360                    .next()
4361                    .unwrap_or(rest)
4362                    .to_string()
4363            })
4364        })
4365        .unwrap_or_else(|| {
4366            let port = gateway
4367                .as_ref()
4368                .and_then(|gateway| gateway.get("port"))
4369                .and_then(serde_json::Value::as_u64)
4370                .unwrap_or(18789);
4371            format!("127.0.0.1:{port}")
4372        });
4373    let reachable = std::net::TcpStream::connect_timeout(
4374        &address.parse().ok()?,
4375        std::time::Duration::from_millis(400),
4376    )
4377    .is_ok();
4378    reachable.then(|| RunningInstance {
4379        method: RunningInstanceMethod::GatewayConnect,
4380        evidence: format!(
4381            "gateway endpoint {address} accepted a TCP connect (from {})",
4382            config_path.display()
4383        ),
4384        checked_at_ms: now_epoch_ms(),
4385    })
4386}
4387
4388/// Hermes: `<home>/.hermes/state.db-wal` freshly modified means a live writer
4389/// holds the store open (SQLite WAL exists only while a connection is open;
4390/// a recent stamp distinguishes an active instance from a stale crash
4391/// leftover).
4392fn probe_hermes_running(home: &Path, max_wal_age_ms: u64) -> Option<RunningInstance> {
4393    let wal = home.join(".hermes/state.db-wal");
4394    let modified = std::fs::metadata(&wal).ok()?.modified().ok()?;
4395    let age_ms = std::time::SystemTime::now()
4396        .duration_since(modified)
4397        .map(|age| age.as_millis() as u64)
4398        .unwrap_or(u64::MAX);
4399    (age_ms <= max_wal_age_ms).then(|| RunningInstance {
4400        method: RunningInstanceMethod::StoreWalActivity,
4401        evidence: format!(
4402            "{} stamped {age_ms}ms ago (threshold {max_wal_age_ms}ms)",
4403            wal.display()
4404        ),
4405        checked_at_ms: now_epoch_ms(),
4406    })
4407}
4408
4409/// Default-on running-instance detection for the harnesses that have one.
4410fn probe_running_instance(id: &str) -> Option<RunningInstance> {
4411    let home = std::env::var_os("HOME").map(PathBuf::from)?;
4412    match id {
4413        HarnessId::OPENCLAW => probe_openclaw_running(&home),
4414        HarnessId::HERMES => probe_hermes_running(&home, 300_000),
4415        _ => None,
4416    }
4417}
4418
4419#[derive(Serialize)]
4420struct LocalHarness {
4421    id: HarnessId,
4422    display_name: String,
4423    supported: bool,
4424    installed: bool,
4425    executable: Option<String>,
4426    version: Option<String>,
4427    auth: HarnessAuthState,
4428    runtime: HarnessRuntimeState,
4429    protocol: String,
4430    capabilities: crate::RuntimeCapabilities,
4431    effective_capabilities: crate::RuntimeCapabilities,
4432    sessions: HarnessSessionCounts,
4433    /// Receipt-backed running-instance detection (None = not detected or the
4434    /// harness has no running-instance concept). Distinct from `installed`.
4435    #[serde(skip_serializing_if = "Option::is_none")]
4436    running: Option<RunningInstance>,
4437    /// ORCH-17: gateway health derived from `running` + the harness's own config.
4438    gateway: GatewayHealth,
4439    reason: Option<String>,
4440    repair: Option<String>,
4441}
4442
4443#[derive(Clone, Deserialize)]
4444struct RuntimeBackendParams {
4445    harness: HarnessId,
4446    #[serde(default)]
4447    protocol: Option<String>,
4448    #[serde(default)]
4449    launch: Option<RuntimeLaunch>,
4450    #[serde(default)]
4451    base_url: Option<String>,
4452    #[serde(default)]
4453    policy: RuntimePolicy,
4454}
4455
4456#[derive(Debug, Clone, Copy, Default, Deserialize)]
4457#[serde(rename_all = "snake_case")]
4458enum RuntimePolicy {
4459    #[default]
4460    Default,
4461    Yolo,
4462}
4463
4464#[derive(Deserialize)]
4465struct RuntimeStartParams {
4466    #[serde(flatten)]
4467    backend: RuntimeBackendParams,
4468    cwd: PathBuf,
4469    /// MCP servers to mount into the new session through the harness's own
4470    /// start door (ORC-6). Backends without such a door ignore them.
4471    #[serde(default)]
4472    mcp_servers: Vec<crate::McpServerLaunch>,
4473    /// The session's approval policy, where the harness's start door takes one (Codex).
4474    #[serde(default)]
4475    approval_policy: Option<String>,
4476}
4477
4478#[derive(Deserialize)]
4479struct RuntimeAttachParams {
4480    #[serde(flatten)]
4481    backend: RuntimeBackendParams,
4482    runtime_id: String,
4483    #[serde(default)]
4484    cwd: Option<PathBuf>,
4485    /// MCP servers to mount into the resumed session (the start door's own
4486    /// field, carried again because a session's tools die with its process).
4487    #[serde(default)]
4488    mcp_servers: Vec<crate::McpServerLaunch>,
4489    /// The session's approval policy, carried again on resume as on start (Codex).
4490    #[serde(default)]
4491    approval_policy: Option<String>,
4492}
4493
4494#[derive(Deserialize)]
4495struct RuntimeConnectionParams {
4496    connection: String,
4497}
4498
4499#[derive(Deserialize)]
4500struct RuntimeInputParams {
4501    connection: String,
4502    text: String,
4503    #[serde(default)]
4504    image_urls: Vec<String>,
4505}
4506
4507const MAX_RUNTIME_IMAGES: usize = 4;
4508const MAX_RUNTIME_IMAGE_URL_BYTES: usize = 12 * 1024 * 1024;
4509const MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL: usize = 32 * 1024 * 1024;
4510
4511fn validate_runtime_image_urls(image_urls: Vec<String>) -> Result<Vec<String>, ServiceError> {
4512    if image_urls.len() > MAX_RUNTIME_IMAGES {
4513        return Err(ServiceError::InvalidParams(format!(
4514            "a runtime prompt accepts at most {MAX_RUNTIME_IMAGES} images"
4515        )));
4516    }
4517    let mut total = 0usize;
4518    for url in &image_urls {
4519        if !(url.starts_with("data:image/")
4520            || url.starts_with("https://")
4521            || url.starts_with("http://"))
4522        {
4523            return Err(ServiceError::InvalidParams(
4524                "runtime images must be image data URLs or HTTP(S) URLs".into(),
4525            ));
4526        }
4527        if url.len() > MAX_RUNTIME_IMAGE_URL_BYTES {
4528            return Err(ServiceError::InvalidParams(format!(
4529                "one runtime image exceeds the {MAX_RUNTIME_IMAGE_URL_BYTES}-byte encoded limit"
4530            )));
4531        }
4532        total = total.saturating_add(url.len());
4533    }
4534    if total > MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL {
4535        return Err(ServiceError::InvalidParams(format!(
4536            "runtime images exceed the {MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL}-byte encoded total limit"
4537        )));
4538    }
4539    Ok(image_urls)
4540}
4541
4542#[derive(Deserialize)]
4543struct RuntimeRespondParams {
4544    connection: String,
4545    request_id: Value,
4546    response: Value,
4547}
4548
4549fn default_reduction_store_root() -> PathBuf {
4550    if let Some(root) = std::env::var_os("SUPERCODE_HOME") {
4551        return PathBuf::from(root).join("sessions");
4552    }
4553    if let Some(home) = std::env::var_os("HOME") {
4554        return PathBuf::from(home).join(".supercode").join("sessions");
4555    }
4556    PathBuf::from(".supercode").join("sessions")
4557}
4558
4559fn messages_jsonl(messages: &[crate::ChatMessage]) -> std::result::Result<String, ServiceError> {
4560    let mut output = String::new();
4561    for message in messages {
4562        output.push_str(
4563            &serde_json::to_string(message)
4564                .map_err(|error| ServiceError::Operation(error.to_string()))?,
4565        );
4566        output.push('\n');
4567    }
4568    Ok(output)
4569}
4570
4571fn parse_messages_jsonl(
4572    content: &str,
4573) -> std::result::Result<Vec<crate::ChatMessage>, ServiceError> {
4574    content
4575        .lines()
4576        .enumerate()
4577        .filter(|(_, line)| !line.trim().is_empty())
4578        .map(|(index, line)| {
4579            serde_json::from_str::<crate::ChatMessage>(line).map_err(|error| {
4580                ServiceError::Operation(format!(
4581                    "reduced transcript line {} is invalid: {error}",
4582                    index + 1
4583                ))
4584            })
4585        })
4586        .collect()
4587}
4588
4589fn reduced_bootstrap_prompt(
4590    source: &SessionLocator,
4591    target: TransferFormat,
4592    view_jsonl: &str,
4593    sidecar_path: &Path,
4594    reduction_log_path: &Path,
4595) -> String {
4596    format!(
4597        "Continue the work from this losslessly reduced {source_harness} session in {target_harness}.\n\
4598         \n\
4599         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\
4600         \n\
4601         <supercode-reduced-session source-session=\"{source_id}\">\n\
4602         {view_jsonl}\
4603         </supercode-reduced-session>\n\
4604         \n\
4605         Resume from the latest unresolved user request and preserve the source session's decisions and constraints.",
4606        source_harness = source.harness.as_str(),
4607        target_harness = target.id(),
4608        sidecar = sidecar_path.display(),
4609        log = reduction_log_path.display(),
4610        source_id = source.session_id,
4611    )
4612}
4613
4614fn session_artifact(
4615    locator: &SessionLocator,
4616    session: &Session,
4617    target: TransferFormat,
4618) -> std::result::Result<SessionArtifact, ServiceError> {
4619    session_artifact_with_id(locator, session, target, None)
4620}
4621
4622fn session_artifact_with_id(
4623    locator: &SessionLocator,
4624    session: &Session,
4625    target: TransferFormat,
4626    target_session_id: Option<&str>,
4627) -> std::result::Result<SessionArtifact, ServiceError> {
4628    let format: SessionFormat = target.into();
4629    let diagonal = format.source() == session.meta.source;
4630    crate::residue_store::store_segments(session);
4631    let has_appended_turns = session
4632        .imported_message_count
4633        .is_some_and(|imported| imported < session.messages.len());
4634    let mut restoration = None;
4635    let content = if let Some(id) = target_session_id {
4636        if diagonal && format != SessionFormat::OpenCode {
4637            session
4638                .to_jsonl_spliced(format, Some(id))
4639                .map_err(operation)?
4640        } else {
4641            let mut rewritten = session.clone();
4642            rewritten.meta.session_id = Some(id.to_string());
4643            rewritten.to_jsonl(format).map_err(operation)?
4644        }
4645    } else if diagonal && session.raw_is_verbatim && !has_appended_turns {
4646        session.raw_verbatim()
4647    } else if diagonal {
4648        session.to_jsonl_spliced(format, None).map_err(operation)?
4649    } else {
4650        // A session that came from `format` before returns its source records verbatim for the
4651        // prefix the residue store holds (docs/plans/portable-residue.md).
4652        match session
4653            .restore_residue(format, crate::residue_store::lookup)
4654            .map_err(operation)?
4655        {
4656            Some((content, report)) => {
4657                restoration = Some(report);
4658                content
4659            }
4660            None => session.to_jsonl(format).map_err(operation)?,
4661        }
4662    };
4663    let stem = sanitize_filename(
4664        target_session_id
4665            .or(session.meta.session_id.as_deref())
4666            .unwrap_or(&locator.session_id),
4667    );
4668    let suggested_filename = if diagonal && target == TransferFormat::Grok {
4669        "chat_history.jsonl".to_string()
4670    } else if target == TransferFormat::Goose {
4671        format!("{stem}.goose.json")
4672    } else {
4673        format!("{stem}.{}.jsonl", target.id())
4674    };
4675    let mut files = vec![SessionArtifactFile {
4676        path: suggested_filename.clone(),
4677        content: content.clone(),
4678        role: ArtifactFileRole::Primary,
4679    }];
4680    if target == TransferFormat::ClaudeCode {
4681        let bundle_stem = Path::new(&suggested_filename)
4682            .file_stem()
4683            .and_then(|stem| stem.to_str())
4684            .unwrap_or(&stem);
4685        let mut child_paths = BTreeSet::new();
4686        for (index, subagent) in session.subagents.iter().enumerate() {
4687            let agent_id = subagent
4688                .meta
4689                .agent_id
4690                .as_deref()
4691                .map(|id| id.strip_prefix("agent-").unwrap_or(id))
4692                .map(sanitize_filename)
4693                .filter(|id| !id.is_empty())
4694                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4695            let child_has_appended_turns = subagent
4696                .imported_message_count
4697                .is_some_and(|imported| imported < subagent.messages.len());
4698            let child_content = if target_session_id.is_none()
4699                && subagent.meta.source == SessionSource::ClaudeCode
4700                && subagent.raw_is_verbatim
4701                && !child_has_appended_turns
4702            {
4703                subagent.raw_verbatim()
4704            } else if subagent.meta.source == SessionSource::ClaudeCode {
4705                subagent
4706                    .to_jsonl_spliced(SessionFormat::ClaudeCode, target_session_id)
4707                    .map_err(operation)?
4708            } else {
4709                let mut child = subagent.clone();
4710                if let Some(id) = target_session_id {
4711                    child.meta.session_id = Some(id.to_string());
4712                }
4713                child
4714                    .to_jsonl(SessionFormat::ClaudeCode)
4715                    .map_err(operation)?
4716            };
4717            let path = format!("{bundle_stem}/subagents/agent-{agent_id}.jsonl");
4718            if !child_paths.insert(path.clone()) {
4719                return Err(ServiceError::Operation(format!(
4720                    "Claude subagent ids collide at artifact path `{path}`"
4721                )));
4722            }
4723            files.push(SessionArtifactFile {
4724                path,
4725                content: child_content,
4726                role: ArtifactFileRole::Subagent,
4727            });
4728        }
4729    }
4730    if diagonal && target == TransferFormat::Grok {
4731        append_grok_bundle_files(locator, "", ArtifactFileRole::Bundle, &mut files)?;
4732    }
4733    if !diagonal || !session.raw_is_verbatim {
4734        files.push(SessionArtifactFile {
4735            path: "recovery/source.supercode.jsonl".into(),
4736            content: session.to_native_jsonl(),
4737            role: ArtifactFileRole::SourceRecovery,
4738        });
4739        for (index, subagent) in session.subagents.iter().enumerate() {
4740            let id = subagent
4741                .meta
4742                .agent_id
4743                .as_deref()
4744                .map(sanitize_filename)
4745                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4746            files.push(SessionArtifactFile {
4747                path: format!("recovery/subagents/{id}.supercode.jsonl"),
4748                content: subagent.to_native_jsonl(),
4749                role: ArtifactFileRole::SourceRecovery,
4750            });
4751        }
4752    }
4753    if !diagonal && session.meta.source == SessionSource::Grok {
4754        append_grok_bundle_files(
4755            locator,
4756            "recovery/grok/",
4757            ArtifactFileRole::SourceRecovery,
4758            &mut files,
4759        )?;
4760    }
4761    let (fidelity, residue) = if diagonal
4762        && target_session_id.is_none()
4763        && session.raw_is_verbatim
4764        && !has_appended_turns
4765    {
4766        (Fidelity::ByteLossless, Vec::new())
4767    } else if diagonal && !(target_session_id.is_some() && target == TransferFormat::OpenCode) {
4768        (
4769            Fidelity::ValueLossless,
4770            vec![if target_session_id.is_some() {
4771                "target identity was rewritten, so the artifact intentionally differs from source bytes".into()
4772            } else {
4773                "source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured".into()
4774            }],
4775        )
4776    } else {
4777        match restoration {
4778            Some(report) if report.rendered_messages == 0 => (
4779                Fidelity::ByteLossless,
4780                vec![format!(
4781                    "restored verbatim from this conversation's {} source records in the residue store",
4782                    target.id()
4783                )],
4784            ),
4785            Some(report) => (
4786                Fidelity::Semantic,
4787                vec![format!(
4788                    "{} of {} messages restored verbatim from the residue store; the other {} written by the {} writer",
4789                    report.restored_messages,
4790                    report.restored_messages + report.rendered_messages,
4791                    report.rendered_messages,
4792                    target.id()
4793                )],
4794            ),
4795            None => (
4796                Fidelity::Semantic,
4797                vec!["target schema has no portable slot for every source-native record and metadata field".into()],
4798            ),
4799        }
4800    };
4801    Ok(SessionArtifact {
4802        source_harness: locator.harness.clone(),
4803        target_harness: target.id(),
4804        session_id: target_session_id
4805            .map(str::to_string)
4806            .or_else(|| session.meta.session_id.clone()),
4807        content,
4808        suggested_filename,
4809        files,
4810        fidelity,
4811        residue,
4812    })
4813}
4814
4815fn append_grok_bundle_files(
4816    locator: &SessionLocator,
4817    prefix: &str,
4818    role: ArtifactFileRole,
4819    files: &mut Vec<SessionArtifactFile>,
4820) -> std::result::Result<(), ServiceError> {
4821    let primary = locator.storage.path();
4822    if primary.file_name().and_then(|name| name.to_str()) != Some("chat_history.jsonl") {
4823        return Err(ServiceError::Operation(format!(
4824            "Grok bundle locator must name chat_history.jsonl, got {}",
4825            primary.display()
4826        )));
4827    }
4828    let parent = primary.parent().ok_or_else(|| {
4829        ServiceError::Operation("Grok chat_history.jsonl has no session directory".into())
4830    })?;
4831    for name in ["summary.json", "updates.jsonl"] {
4832        let path = parent.join(name);
4833        let metadata = match std::fs::symlink_metadata(&path) {
4834            Ok(metadata) => metadata,
4835            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
4836            Err(error) => return Err(ServiceError::Operation(error.to_string())),
4837        };
4838        if metadata.file_type().is_symlink() || !metadata.is_file() {
4839            return Err(ServiceError::Operation(format!(
4840                "refusing non-regular Grok bundle member {}",
4841                path.display()
4842            )));
4843        }
4844        let content = std::fs::read_to_string(&path).map_err(|error| {
4845            ServiceError::Operation(format!(
4846                "Grok bundle member {} is not representable as UTF-8: {error}",
4847                path.display()
4848            ))
4849        })?;
4850        files.push(SessionArtifactFile {
4851            path: format!("{prefix}{name}"),
4852            content,
4853            role: match role {
4854                ArtifactFileRole::Bundle => ArtifactFileRole::Bundle,
4855                _ => ArtifactFileRole::SourceRecovery,
4856            },
4857        });
4858    }
4859    Ok(())
4860}
4861
4862fn handoff_artifact(
4863    locator: &SessionLocator,
4864    session: &Session,
4865    target: TransferFormat,
4866) -> std::result::Result<SessionArtifact, ServiceError> {
4867    let target_session_id = target_session_id(target);
4868    session_artifact_with_id(locator, session, target, Some(&target_session_id))
4869}
4870
4871fn target_session_id(target: TransferFormat) -> String {
4872    let uuid = generated_session_id();
4873    match target {
4874        TransferFormat::OpenCode => format!("ses_{}", uuid.replace('-', "")),
4875        TransferFormat::ClaudeCode
4876        | TransferFormat::Codex
4877        | TransferFormat::Pi
4878        | TransferFormat::Grok
4879        | TransferFormat::Gemini
4880        | TransferFormat::Goose
4881        | TransferFormat::Hermes => uuid,
4882    }
4883}
4884
4885fn sanitize_filename(value: &str) -> String {
4886    let value = value
4887        .chars()
4888        .map(|character| {
4889            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
4890                character
4891            } else {
4892                '-'
4893            }
4894        })
4895        .collect::<String>();
4896    let value = value.trim_matches('-');
4897    if value.is_empty() {
4898        "session".into()
4899    } else {
4900        value.chars().take(100).collect()
4901    }
4902}
4903
4904fn handoff_instructions(
4905    target: TransferFormat,
4906    session_id: &str,
4907    cwd: &Path,
4908) -> HandoffInstructions {
4909    let launch = |program: &str, arguments: Vec<String>| StructuredLaunch {
4910        cwd: cwd.to_path_buf(),
4911        program: program.into(),
4912        arguments,
4913        env: BTreeMap::new(),
4914    };
4915    match target {
4916        TransferFormat::ClaudeCode => HandoffInstructions {
4917            launch: launch("claude", vec!["--resume".into(), session_id.into()]),
4918            materialize: None,
4919            requires_materialization: true,
4920            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(),
4921        },
4922        TransferFormat::Hermes => HandoffInstructions {
4923            launch: launch("hermes", vec!["--resume".into(), session_id.into()]),
4924            materialize: None,
4925            requires_materialization: true,
4926            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(),
4927        },
4928        TransferFormat::Codex => HandoffInstructions {
4929            launch: launch("codex", vec!["resume".into(), session_id.into()]),
4930            materialize: None,
4931            requires_materialization: true,
4932            note: "Write the artifact into Codex's native rollout store before running the resume launch; Codex has no general transcript-import command.".into(),
4933        },
4934        TransferFormat::OpenCode => HandoffInstructions {
4935            launch: launch("opencode", vec!["--session".into(), session_id.into()]),
4936            materialize: Some(launch(
4937                "opencode",
4938                vec!["import".into(), "{artifact_path}".into()],
4939            )),
4940            requires_materialization: true,
4941            note: "Write the artifact to a file, run the materialize command with its path, then launch the imported session.".into(),
4942        },
4943        TransferFormat::Pi => HandoffInstructions {
4944            launch: launch("pi", vec!["--session".into(), "{artifact_path}".into()]),
4945            materialize: None,
4946            requires_materialization: true,
4947            note: "Write the artifact to a file and replace {artifact_path} in the launch arguments; Pi can resume that file directly.".into(),
4948        },
4949        TransferFormat::Grok => HandoffInstructions {
4950            launch: launch(
4951                "grok",
4952                vec!["--resume".into(), "{materialized_session_id}".into()],
4953            ),
4954            materialize: None,
4955            requires_materialization: true,
4956            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(),
4957        },
4958        TransferFormat::Gemini => HandoffInstructions {
4959            launch: launch(
4960                "gemini",
4961                vec!["--session-file".into(), "{artifact_path}".into()],
4962            ),
4963            materialize: None,
4964            requires_materialization: true,
4965            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(),
4966        },
4967        TransferFormat::Goose => HandoffInstructions {
4968            launch: launch(
4969                "goose",
4970                vec![
4971                    "session".into(),
4972                    "--resume".into(),
4973                    "--session-id".into(),
4974                    "{imported_session_id}".into(),
4975                ],
4976            ),
4977            materialize: Some(launch(
4978                "goose",
4979                vec!["session".into(), "import".into(), "{artifact_path}".into()],
4980            )),
4981            requires_materialization: true,
4982            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(),
4983        },
4984    }
4985}
4986
4987fn resume_launch(
4988    harness: &str,
4989    session_id: &str,
4990    cwd: &Path,
4991    policy: ResumePolicy,
4992) -> std::result::Result<StructuredLaunch, ServiceError> {
4993    let mut arguments = Vec::new();
4994    let program = match harness {
4995        HarnessId::GROK => {
4996            if matches!(policy, ResumePolicy::Yolo) {
4997                if crate::support::self_sandbox_supported() {
4998                    arguments.extend(["--sandbox".into(), "workspace".into()]);
4999                }
5000                arguments.push("--always-approve".into());
5001            }
5002            arguments.extend(["--resume".into(), session_id.into()]);
5003            "grok"
5004        }
5005        HarnessId::CODEX => {
5006            let cwd_key = serde_json::to_string(cwd.to_string_lossy().as_ref())
5007                .expect("a filesystem path always serializes as JSON text");
5008            arguments.extend([
5009                "-c".into(),
5010                "check_for_update_on_startup=false".into(),
5011                "-c".into(),
5012                format!("projects.{cwd_key}.trust_level=\"trusted\""),
5013            ]);
5014            if matches!(policy, ResumePolicy::Yolo) {
5015                arguments.extend([
5016                    "--dangerously-bypass-approvals-and-sandbox".into(),
5017                    "--dangerously-bypass-hook-trust".into(),
5018                ]);
5019            }
5020            arguments.extend(["resume".into(), session_id.into()]);
5021            "codex"
5022        }
5023        HarnessId::CLAUDE_CODE => {
5024            if matches!(policy, ResumePolicy::Yolo) {
5025                arguments.push("--dangerously-skip-permissions".into());
5026            }
5027            arguments.extend(["--resume".into(), session_id.into()]);
5028            "claude"
5029        }
5030        HarnessId::GEMINI => {
5031            if matches!(policy, ResumePolicy::Yolo) {
5032                arguments.push("--yolo".into());
5033            }
5034            arguments.extend(["--resume".into(), session_id.into()]);
5035            "gemini"
5036        }
5037        HarnessId::GOOSE => {
5038            arguments.extend([
5039                "session".into(),
5040                "--resume".into(),
5041                "--session-id".into(),
5042                session_id.into(),
5043            ]);
5044            "goose"
5045        }
5046        HarnessId::PI => {
5047            if matches!(policy, ResumePolicy::Yolo) {
5048                arguments.push("--approve".into());
5049            }
5050            arguments.extend(["--session".into(), session_id.into()]);
5051            "pi"
5052        }
5053        HarnessId::OPENCODE => {
5054            arguments.extend(["--session".into(), session_id.into()]);
5055            "opencode"
5056        }
5057        HarnessId::SUPERCODE => {
5058            if matches!(policy, ResumePolicy::Yolo) {
5059                arguments.push("--dangerous".into());
5060            }
5061            arguments.extend(["resume".into(), session_id.into()]);
5062            "supercode"
5063        }
5064        other => {
5065            return Err(ServiceError::InvalidParams(format!(
5066                "no structured resume launch is registered for harness `{other}`"
5067            )))
5068        }
5069    };
5070    Ok(StructuredLaunch {
5071        cwd: cwd.to_path_buf(),
5072        env: if program == "grok" {
5073            crate::support::grok_home_env()
5074        } else {
5075            BTreeMap::new()
5076        },
5077        program: program.into(),
5078        arguments,
5079    })
5080}
5081
5082/// Stage the resolved gateway credential in a private (0600) file so the
5083/// bridge can read it via `--token-file` — the delivery the real `openclaw
5084/// acp` accepts. One stable file per endpoint (keyed by an address digest,
5085/// no secret material in the name), overwritten on every connect so files
5086/// never accumulate and a rotated token never goes stale on disk.
5087fn openclaw_gateway_token_file(address: &str, secret: &str) -> std::io::Result<PathBuf> {
5088    let digest = blake3::hash(address.as_bytes()).to_hex();
5089    let path = std::env::temp_dir().join(format!(
5090        "supercode-openclaw-gateway-token-{}",
5091        &digest.as_str()[..16]
5092    ));
5093    #[cfg(unix)]
5094    {
5095        use std::io::Write;
5096        use std::os::unix::fs::OpenOptionsExt;
5097        let mut file = std::fs::OpenOptions::new()
5098            .write(true)
5099            .create(true)
5100            .truncate(true)
5101            .mode(0o600)
5102            .open(&path)?;
5103        file.write_all(secret.as_bytes())?;
5104    }
5105    #[cfg(not(unix))]
5106    std::fs::write(&path, secret)?;
5107    Ok(path)
5108}
5109
5110/// Open a connect-mode descriptor: resolve the endpoint address and
5111/// credential from the harness's own config file and build the backend that
5112/// joins the already-running endpoint. Fails closed with a specific
5113/// diagnostic when the config cannot be resolved or the declared protocol has
5114/// no connect-capable client yet.
5115fn open_connect_descriptor(
5116    descriptor: &crate::HarnessSupportDescriptor,
5117    home: &Path,
5118) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
5119    let Some(connect) = &descriptor.runtime.connect_launch else {
5120        return Err(ServiceError::InvalidParams(format!(
5121            "harness `{}` has no registered connect-mode launch",
5122            descriptor.id.as_str()
5123        )));
5124    };
5125    let resolved = connect
5126        .resolve(home)
5127        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
5128    match (descriptor.id.as_str(), connect.protocol.as_str()) {
5129        (HarnessId::OPENCODE, protocol) if protocol.starts_with("opencode-http") => {
5130            let mut backend = OpenCodeRuntimeBackend::connect(&resolved.address);
5131            if let Some(token) = resolved.auth {
5132                backend = backend.with_bearer(token);
5133            }
5134            Ok(Box::new(backend))
5135        }
5136        (HarnessId::OPENCLAW, protocol) if protocol.starts_with("acp") => {
5137            // OpenClaw's own `openclaw acp` binary is the gateway client: a
5138            // stdio ACP bridge that joins the RUNNING gateway at the resolved
5139            // endpoint. Blind-walk finding 2026-08-31: the real bridge does
5140            // NOT honor OPENCLAW_GATEWAY_TOKEN from the environment — the
5141            // credential must arrive via `--token-file` (never bare `--token`
5142            // on argv, where process listings could read it). The env var is
5143            // still set for older bridges that did read it. Requires openclaw
5144            // >= 2026.7: the 2026.2 bridge drops its gateway socket
5145            // mid-prompt and advertises no session resume (executed finding,
5146            // docs/interop/research/openclaw-acp-dialect-2026-08-30.json).
5147            let mut env = BTreeMap::new();
5148            let mut arguments = vec!["acp".into(), "--url".into(), resolved.address.clone()];
5149            if let Some(token) = resolved.auth {
5150                let token_path = openclaw_gateway_token_file(&resolved.address, token.secret())
5151                    .map_err(|error| {
5152                        ServiceError::UnsupportedAction(format!(
5153                            "could not stage the gateway credential for the bridge: {error}"
5154                        ))
5155                    })?;
5156                arguments.push("--token-file".into());
5157                arguments.push(token_path.to_string_lossy().into_owned());
5158                env.insert("OPENCLAW_GATEWAY_TOKEN".to_string(), token.secret().to_string());
5159            }
5160            // The bridge program comes from the descriptor's own default
5161            // launch (the compiled registry pins `openclaw`), so tests can
5162            // substitute an absolute mock-bridge path without touching
5163            // process-global state.
5164            let program = descriptor
5165                .runtime
5166                .default_launch
5167                .as_ref()
5168                .map(|launch| launch.program.clone())
5169                .unwrap_or_else(|| "openclaw".into());
5170            let launch = RuntimeLaunch {
5171                program,
5172                arguments,
5173                env,
5174            };
5175            Ok(Box::new(
5176                crate::AcpRuntimeBackend::new(descriptor.id.clone(), launch)
5177                    .with_resume_support(descriptor.runtime.capabilities.resume_session),
5178            ))
5179        }
5180        _ => Err(ServiceError::UnsupportedAction(format!(
5181            "connect-mode endpoint for `{}` speaks `{}`; joining it needs that protocol's gateway client",
5182            descriptor.id.as_str(),
5183            connect.protocol
5184        ))),
5185    }
5186}
5187
5188/// The registry's connect-mode launch for this harness, honored only when the
5189/// caller supplied neither an explicit launch nor a base URL.
5190fn registry_connect_descriptor(
5191    params: &RuntimeBackendParams,
5192) -> Option<crate::HarnessSupportDescriptor> {
5193    if params.launch.is_some() || params.base_url.is_some() {
5194        return None;
5195    }
5196    harness_support_registry()
5197        .harnesses
5198        .into_iter()
5199        .find(|descriptor| descriptor.id == params.harness)
5200        .filter(|descriptor| descriptor.runtime.connect_launch.is_some())
5201}
5202
5203fn service_home() -> std::result::Result<PathBuf, ServiceError> {
5204    std::env::var_os("HOME").map(PathBuf::from).ok_or_else(|| {
5205        ServiceError::UnsupportedAction(
5206            "connect-mode launches need HOME to locate the harness config".into(),
5207        )
5208    })
5209}
5210
5211/// The doors that open a runtime: each spawns or joins a program and waits on
5212/// that program's protocol handshake before it can answer.
5213pub const RUNTIME_OPEN_METHODS: &[&str] = &[
5214    "harness.v1.runtimes.start",
5215    "harness.v1.runtimes.resume",
5216    "harness.v1.runtimes.attach",
5217    "harness.v1.runtimes.attach_existing",
5218];
5219
5220/// How long a runtime gets to finish opening before its caller is answered an
5221/// error instead. A program that never speaks the protocol at all — the wrong
5222/// binary, a shim that prints usage and waits — never answers the handshake,
5223/// so the wait is unbounded without this.
5224pub const RUNTIME_OPEN_DEADLINE: Duration = Duration::from_secs(60);
5225
5226/// How long a control call on an ALREADY-open runtime — send input, interrupt,
5227/// steer, respond, close — gets before its caller is answered an error
5228/// instead. A live runtime answers these in milliseconds; a wedged one never
5229/// answers at all, and `close` is exactly what a caller reaches for when it
5230/// suspects that.
5231pub const RUNTIME_CONTROL_DEADLINE: Duration = Duration::from_secs(30);
5232
5233/// The doors whose work happens entirely OUTSIDE this service's state once
5234/// its state has been read: probing harnesses, relaying a message into a
5235/// live session, and performing a conversation verb through a harness's own
5236/// CLI / HTTP / store door. Every one of them waits on a child process or a
5237/// network peer. See [`HarnessSessionService::detach`].
5238pub const DETACHED_METHODS: &[&str] = &[
5239    "harness.v1.harnesses.list",
5240    "harness.v1.harnesses.probe",
5241    "harness.v1.sessions.message",
5242    "harness.v1.sessions.new",
5243    "harness.v1.sessions.reset",
5244    "harness.v1.sessions.archive",
5245    "harness.v1.sessions.delete",
5246];
5247
5248/// How long a request moved off a transport's loop gets before its caller is
5249/// answered an error instead. Each of these already bounds its own inner
5250/// waits (a probe's handshake, the relay's send); this is the backstop for
5251/// the ones that do not — a harness CLI that never exits — so no caller waits
5252/// forever on a detached task no one is watching.
5253pub const DETACHED_CALL_DEADLINE: Duration = Duration::from_secs(120);
5254
5255/// How long `sessions.discover` gets before its caller is answered an error
5256/// instead. Discovery reads each harness's own store, and a store on a cold
5257/// or unavailable mount answers at the filesystem's pace rather than its own.
5258///
5259/// Deliberately shorter than the clients' own request deadline (30s): the
5260/// server's answer names the store that did not answer, and it is only read
5261/// if it lands before the client stops listening.
5262pub const SESSION_DISCOVER_DEADLINE: Duration = Duration::from_secs(25);
5263
5264/// Bound one control call on an open runtime by [`RUNTIME_CONTROL_DEADLINE`],
5265/// naming the method and the bound when it blows.
5266async fn within_control_deadline<F: std::future::Future>(
5267    method: &str,
5268    call: F,
5269) -> std::result::Result<F::Output, ServiceError> {
5270    tokio::time::timeout(RUNTIME_CONTROL_DEADLINE, call)
5271        .await
5272        .map_err(|_| {
5273            ServiceError::Operation(format!(
5274                "`{method}` gave up after {}s: the runtime did not answer",
5275                RUNTIME_CONTROL_DEADLINE.as_secs()
5276            ))
5277        })
5278}
5279
5280/// One [`RUNTIME_OPEN_METHODS`] request, parsed but not yet started. See
5281/// [`HarnessSessionService::runtime_open`] for why it exists apart from
5282/// [`HarnessSessionService::handle_async`].
5283pub struct RuntimeOpen {
5284    id: Value,
5285    method: String,
5286    params: Value,
5287}
5288
5289impl RuntimeOpen {
5290    /// Do the waiting: spawn or join the program and complete its handshake,
5291    /// bounded by [`RUNTIME_OPEN_DEADLINE`]. Touches no service state, so this
5292    /// runs on any task.
5293    pub async fn open(self) -> OpenedRuntime {
5294        let Self { id, method, params } = self;
5295        let outcome = open_runtime(&method, params).await;
5296        OpenedRuntime { id, outcome }
5297    }
5298}
5299
5300/// The result of [`RuntimeOpen::open`], ready for
5301/// [`HarnessSessionService::finish_runtime_open`].
5302pub struct OpenedRuntime {
5303    id: Value,
5304    outcome: std::result::Result<OpenRuntime, ServiceError>,
5305}
5306
5307/// One detached request: the half that reads this service's state already
5308/// done, and the half that waits not yet started. See
5309/// [`HarnessSessionService::detach`] and
5310/// [`HarnessSessionService::detach_runtime`].
5311pub struct DetachedCall {
5312    id: Value,
5313    method: String,
5314    work: std::result::Result<Work, ServiceError>,
5315}
5316
5317impl DetachedCall {
5318    /// Do the waiting and answer. Runs on any task: whatever this call needed
5319    /// from the service was taken before it left.
5320    pub async fn run(self) -> DetachedAnswer {
5321        let Self { id, method, work } = self;
5322        match work {
5323            // A call holding a runtime is already bounded by
5324            // RUNTIME_CONTROL_DEADLINE, and its future OWNS that connection:
5325            // a second timeout around it would drop the connection mid-call
5326            // and take down a runtime its caller still has.
5327            Ok(Work::Runtime(work)) => {
5328                let (result, returned) = work.run().await;
5329                DetachedAnswer {
5330                    response: service_response(id, result),
5331                    returned,
5332                }
5333            }
5334            Ok(Work::Free(work)) => {
5335                let result = match tokio::time::timeout(DETACHED_CALL_DEADLINE, work.run()).await {
5336                    Ok(result) => result,
5337                    Err(_) => Err(ServiceError::Operation(format!(
5338                        "`{method}` gave up after {}s: the harness it waits on did not answer",
5339                        DETACHED_CALL_DEADLINE.as_secs()
5340                    ))),
5341                };
5342                DetachedAnswer {
5343                    response: service_response(id, result),
5344                    returned: None,
5345                }
5346            }
5347            Err(error) => DetachedAnswer {
5348                response: service_response(id, Err(error)),
5349                returned: None,
5350            },
5351        }
5352    }
5353}
5354
5355/// One detached call's complete answer, plus whatever it must hand back to
5356/// the service before that answer is written. See
5357/// [`HarnessSessionService::finish_detached`].
5358pub struct DetachedAnswer {
5359    response: Value,
5360    returned: Option<ReturnedRuntime>,
5361}
5362
5363impl DetachedAnswer {
5364    /// The caller's JSON-RPC response, for a transport that owns no service
5365    /// to give a borrowed connection back to.
5366    pub fn into_response(self) -> Value {
5367        self.response
5368    }
5369}
5370
5371/// A connection lent to a detached call, on its way back to the service that
5372/// owns it.
5373pub struct ReturnedRuntime {
5374    connection: String,
5375    runtime: Box<dyn RuntimeConnection>,
5376}
5377
5378/// The waiting half of one detached request: with nothing of the service's
5379/// in hand, or holding a connection the service lent out for the call.
5380enum Work {
5381    Free(DetachedWork),
5382    Runtime(RuntimeWork),
5383}
5384
5385/// The waiting half of one detached request that holds nothing of the
5386/// service's.
5387enum DetachedWork {
5388    /// Probe the selected harnesses: find their executables, ask each its
5389    /// version, and at `probe: handshake` start each one and complete its
5390    /// protocol handshake.
5391    Inventory(InventoryWork),
5392    /// Relay one message into a live session.
5393    Message(MessageSessionParams),
5394    /// Perform one conversation verb through the harness's own CLI, HTTP API,
5395    /// daemon socket, or supercode's own store.
5396    SessionMutation {
5397        verb: crate::SessionVerb,
5398        mutation: crate::SessionMutation,
5399    },
5400}
5401
5402impl DetachedWork {
5403    async fn run(self) -> std::result::Result<Value, ServiceError> {
5404        match self {
5405            Self::Inventory(work) => run_inventory(work).await,
5406            Self::Message(params) => Ok(message_live_session(&params).await),
5407            Self::SessionMutation { verb, mutation } => {
5408                let outcome = run_session_mutation(verb, &mutation).await?;
5409                serde_json::to_value(outcome)
5410                    .map_err(|error| ServiceError::Operation(error.to_string()))
5411            }
5412        }
5413    }
5414}
5415
5416/// One detached call that holds a runtime connection for its whole run.
5417enum RuntimeWork {
5418    /// Tear down a runtime the service has already surrendered.
5419    Close {
5420        runtime: Box<dyn RuntimeConnection>,
5421        process_group: Option<u32>,
5422    },
5423    /// Type one live slash command through a borrowed connection, then give
5424    /// the connection back.
5425    LiveCommand {
5426        connection: String,
5427        runtime: Box<dyn RuntimeConnection>,
5428        verb: crate::SessionVerb,
5429        mutation: crate::SessionMutation,
5430        command: &'static str,
5431        session: String,
5432    },
5433}
5434
5435/// What one [`RuntimeWork`] answers with: the caller's result, and the
5436/// connection to give back when the call only borrowed one.
5437type RuntimeWorkAnswer = (
5438    std::result::Result<Value, ServiceError>,
5439    Option<ReturnedRuntime>,
5440);
5441
5442impl RuntimeWork {
5443    async fn run(self) -> RuntimeWorkAnswer {
5444        match self {
5445            Self::Close {
5446                runtime,
5447                process_group,
5448            } => (close_runtime(runtime, process_group).await, None),
5449            Self::LiveCommand {
5450                connection,
5451                mut runtime,
5452                verb,
5453                mutation,
5454                command,
5455                session,
5456            } => {
5457                let result =
5458                    type_live_command(runtime.as_mut(), verb, &mutation, command, session).await;
5459                (
5460                    result,
5461                    Some(ReturnedRuntime {
5462                        connection,
5463                        runtime,
5464                    }),
5465                )
5466            }
5467        }
5468    }
5469}
5470
5471/// Tear down a runtime already out of the service, within
5472/// [`RUNTIME_CONTROL_DEADLINE`].
5473async fn close_runtime(
5474    mut runtime: Box<dyn RuntimeConnection>,
5475    process_group: Option<u32>,
5476) -> std::result::Result<Value, ServiceError> {
5477    match within_control_deadline("harness.v1.runtimes.close", runtime.close()).await {
5478        Ok(result) => {
5479            result.map_err(operation)?;
5480            Ok(json!({"closed": true}))
5481        }
5482        Err(deadline) => {
5483            // Dropping the handle is not enough: the process that stopped
5484            // answering is held by a task parked on it, so nothing here runs
5485            // its Drop. Signal the group the graceful path would have
5486            // signalled, then say so.
5487            let killed = kill_runtime_process_group(process_group);
5488            drop(runtime);
5489            Ok(json!({
5490                "closed": true,
5491                "killed": killed,
5492                "detail": error_message(deadline),
5493            }))
5494        }
5495    }
5496}
5497
5498/// The conversation a live `sessions.new` / `sessions.reset` acts on: the one
5499/// the request named, or the runtime's own session.
5500fn live_session_name(runtime: &dyn RuntimeConnection, mutation: &crate::SessionMutation) -> String {
5501    mutation
5502        .session
5503        .clone()
5504        .filter(|value| !value.trim().is_empty())
5505        .unwrap_or_else(|| runtime.handle().runtime_id.clone())
5506}
5507
5508/// Type one harness slash command into a live session through the very same
5509/// `send_input` path a human's message takes, within
5510/// [`RUNTIME_CONTROL_DEADLINE`].
5511async fn type_live_command(
5512    runtime: &mut dyn RuntimeConnection,
5513    verb: crate::SessionVerb,
5514    mutation: &crate::SessionMutation,
5515    command: &str,
5516    session: String,
5517) -> std::result::Result<Value, ServiceError> {
5518    within_control_deadline(
5519        &format!("sessions.{}", verb.as_str()),
5520        runtime.send_input(RuntimeInput {
5521            text: command.to_string(),
5522            image_urls: Vec::new(),
5523        }),
5524    )
5525    .await?
5526    .map_err(operation)?;
5527    let outcome = crate::sessions_control::live_outcome(verb, mutation, command, session)
5528        .map_err(session_control_error)?;
5529    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
5530}
5531
5532/// A runtime that is up and whose handshake completed, with what the service
5533/// needs to take ownership of it.
5534enum OpenRuntime {
5535    /// supercode spawned this process, so it also hosts it: a frontend server,
5536    /// a live-runtime registration and a terminal launch of its own.
5537    Hosted {
5538        runtime: Box<dyn RuntimeConnection>,
5539        capabilities: crate::RuntimeCapabilities,
5540        workspace: PathBuf,
5541    },
5542    /// `attach_existing` joined a process supercode does not own. It is
5543    /// registered as a bare connection and hosts nothing.
5544    Joined { runtime: Box<dyn RuntimeConnection> },
5545}
5546
5547/// Open the runtime one [`RUNTIME_OPEN_METHODS`] request asks for, within
5548/// [`RUNTIME_OPEN_DEADLINE`]. The error a blown deadline answers names the
5549/// method and the bound, so a caller reads why it was cut loose instead of
5550/// waiting on a handshake that is never coming.
5551async fn open_runtime(
5552    method: &str,
5553    params: Value,
5554) -> std::result::Result<OpenRuntime, ServiceError> {
5555    match tokio::time::timeout(
5556        RUNTIME_OPEN_DEADLINE,
5557        open_runtime_unbounded(method, params),
5558    )
5559    .await
5560    {
5561        Ok(result) => result,
5562        Err(_) => Err(ServiceError::Operation(format!(
5563            "`{method}` gave up after {}s: the runtime never finished its protocol handshake",
5564            RUNTIME_OPEN_DEADLINE.as_secs()
5565        ))),
5566    }
5567}
5568
5569async fn open_runtime_unbounded(
5570    method: &str,
5571    params: Value,
5572) -> std::result::Result<OpenRuntime, ServiceError> {
5573    match method {
5574        "harness.v1.runtimes.start" => {
5575            let params = decode::<RuntimeStartParams>(params)?;
5576            let backend = runtime_backend(&params.backend)?;
5577            let capabilities = backend.capabilities();
5578            let workspace = params.cwd.clone();
5579            let runtime = backend
5580                .start(RuntimeStartRequest {
5581                    cwd: params.cwd,
5582                    launch: runtime_launch(&params.backend),
5583                    mcp_servers: params.mcp_servers,
5584                    approval_policy: params.approval_policy,
5585                })
5586                .await
5587                .map_err(operation)?;
5588            Ok(OpenRuntime::Hosted {
5589                runtime,
5590                capabilities,
5591                workspace,
5592            })
5593        }
5594        "harness.v1.runtimes.resume" | "harness.v1.runtimes.attach" => {
5595            let params = decode::<RuntimeAttachParams>(params)?;
5596            let backend = runtime_backend(&params.backend)?;
5597            let capabilities = backend.capabilities();
5598            let workspace = params
5599                .cwd
5600                .clone()
5601                .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
5602            let runtime = backend
5603                .attach(RuntimeAttachRequest {
5604                    runtime_id: params.runtime_id,
5605                    cwd: params.cwd,
5606                    launch: runtime_launch(&params.backend),
5607                    mcp_servers: params.mcp_servers,
5608                    approval_policy: params.approval_policy,
5609                })
5610                .await
5611                .map_err(operation)?;
5612            Ok(OpenRuntime::Hosted {
5613                runtime,
5614                capabilities,
5615                workspace,
5616            })
5617        }
5618        "harness.v1.runtimes.attach_existing" => {
5619            let params = decode::<RuntimeAttachParams>(params)?;
5620            let backend: Box<dyn RuntimeBackend> = match params
5621                .backend
5622                .base_url
5623                .as_deref()
5624                .and_then(|value| LiveRuntimeEndpoint::parse(value).ok())
5625            {
5626                Some(endpoint) => {
5627                    #[cfg(not(feature = "adapter-api"))]
5628                    {
5629                        let _ = endpoint;
5630                        return Err(ServiceError::UnsupportedAction(
5631                            "live HTTP attachment adapter is not compiled".into(),
5632                        ));
5633                    }
5634                    #[cfg(feature = "adapter-api")]
5635                    {
5636                        let workspace = params.cwd.clone().ok_or_else(|| {
5637                            ServiceError::InvalidParams(
5638                                "Volter Harness live attach requires the project cwd".into(),
5639                            )
5640                        })?;
5641                        let source = LiveRuntimeSource {
5642                            harness: params.backend.harness.as_str().to_string(),
5643                            session_id: params.runtime_id.clone(),
5644                            workspace,
5645                        };
5646                        let receipt = resolve_live_runtime(&endpoint, &source)
5647                            .map_err(|error| ServiceError::Operation(error.to_string()))?;
5648                        Box::new(SupercodeHttpRuntimeBackend::new(receipt))
5649                    }
5650                }
5651                None => runtime_backend(&params.backend)?,
5652            };
5653            let capabilities = backend.capabilities();
5654            if !capabilities.attach_existing_process {
5655                return Err(ServiceError::Operation(format!(
5656                    "{} cannot attach to an already-running process; use runtimes.resume for a persisted session",
5657                    backend.harness().as_str()
5658                )));
5659            }
5660            let runtime = backend
5661                .attach_existing(RuntimeAttachRequest {
5662                    runtime_id: params.runtime_id,
5663                    cwd: params.cwd,
5664                    launch: runtime_launch(&params.backend),
5665                    mcp_servers: params.mcp_servers,
5666                    approval_policy: params.approval_policy,
5667                })
5668                .await
5669                .map_err(operation)?;
5670            Ok(OpenRuntime::Joined { runtime })
5671        }
5672        _ => Err(ServiceError::MethodNotFound),
5673    }
5674}
5675
5676/// Wrap one service outcome in its JSON-RPC 2.0 envelope.
5677fn service_response(id: Value, result: std::result::Result<Value, ServiceError>) -> Value {
5678    match result {
5679        Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
5680        Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
5681        Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
5682        Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
5683        Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
5684        Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
5685    }
5686}
5687
5688fn runtime_backend(
5689    params: &RuntimeBackendParams,
5690) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
5691    if let Some(descriptor) = registry_connect_descriptor(params) {
5692        return open_connect_descriptor(&descriptor, &service_home()?);
5693    }
5694    if params.protocol.as_deref() == Some("acp") {
5695        let launch = params
5696            .launch
5697            .clone()
5698            .or_else(|| {
5699                harness_support_registry()
5700                    .harnesses
5701                    .into_iter()
5702                    .find(|harness| harness.id == params.harness)
5703                    .filter(|harness| {
5704                        harness.runtime.implementation == ImplementationKind::GenericProtocol
5705                            && harness.runtime.protocol.starts_with("acp")
5706                    })
5707                    .and_then(|harness| harness.runtime.default_launch)
5708            })
5709            .ok_or_else(|| {
5710                ServiceError::InvalidParams(
5711                    "an ACP runtime requires `launch` unless the harness has a registered default"
5712                        .into(),
5713                )
5714            })?;
5715        let resume_session = harness_support_registry()
5716            .harnesses
5717            .into_iter()
5718            .find(|harness| harness.id == params.harness)
5719            .is_some_and(|harness| harness.runtime.capabilities.resume_session);
5720        return Ok(Box::new(
5721            AcpRuntimeBackend::new(params.harness.clone(), launch)
5722                .with_resume_support(resume_session),
5723        ));
5724    }
5725    let backend: Box<dyn RuntimeBackend> = match params.harness.as_str() {
5726        HarnessId::CODEX => Box::new(CodexRuntimeBackend::new()),
5727        HarnessId::CLAUDE_CODE => Box::new(ClaudeCodeRuntimeBackend::new()),
5728        HarnessId::PI => Box::new(PiRuntimeBackend::new()),
5729        HarnessId::OPENCODE => match &params.base_url {
5730            Some(url) => Box::new(OpenCodeRuntimeBackend::connect(url)),
5731            None => Box::new(OpenCodeRuntimeBackend::new()),
5732        },
5733        harness => {
5734            let descriptor = harness_support_registry()
5735                .harnesses
5736                .into_iter()
5737                .find(|descriptor| descriptor.id.as_str() == harness)
5738                .filter(|descriptor| {
5739                    descriptor.runtime.implementation == ImplementationKind::GenericProtocol
5740                        && descriptor.runtime.protocol.starts_with("acp")
5741                });
5742            let Some(descriptor) = descriptor else {
5743                return Err(ServiceError::InvalidParams(format!(
5744                    "no runtime adapter for harness `{harness}`; use protocol `acp` with a launch command"
5745                )));
5746            };
5747            let resume = descriptor.runtime.capabilities.resume_session;
5748            Box::new(
5749                AcpRuntimeBackend::new(
5750                    descriptor.id,
5751                    descriptor
5752                        .runtime
5753                        .default_launch
5754                        .expect("generic ACP registry entry includes its launch"),
5755                )
5756                .with_resume_support(resume),
5757            )
5758        }
5759    };
5760    Ok(backend)
5761}
5762
5763fn runtime_launch(params: &RuntimeBackendParams) -> Option<RuntimeLaunch> {
5764    if let Some(launch) = &params.launch {
5765        return Some(launch.clone());
5766    }
5767    if !matches!(params.policy, RuntimePolicy::Yolo) {
5768        return None;
5769    }
5770    let launch = match params.harness.as_str() {
5771        HarnessId::GROK => RuntimeLaunch {
5772            program: "grok".into(),
5773            arguments: {
5774                let mut arguments: Vec<String> = Vec::new();
5775                if crate::support::self_sandbox_supported() {
5776                    arguments.extend(["--sandbox".into(), "workspace".into()]);
5777                }
5778                arguments.extend([
5779                    "--always-approve".into(),
5780                    "agent".into(),
5781                    "--no-leader".into(),
5782                    "stdio".into(),
5783                ]);
5784                arguments
5785            },
5786            env: crate::support::grok_env(),
5787        },
5788        HarnessId::CODEX => RuntimeLaunch {
5789            program: "codex".into(),
5790            arguments: vec![
5791                "--dangerously-bypass-approvals-and-sandbox".into(),
5792                "--dangerously-bypass-hook-trust".into(),
5793                "app-server".into(),
5794            ],
5795            env: BTreeMap::new(),
5796        },
5797        HarnessId::CLAUDE_CODE => RuntimeLaunch {
5798            program: "claude".into(),
5799            arguments: vec![
5800                "--dangerously-skip-permissions".into(),
5801                "--print".into(),
5802                "--input-format".into(),
5803                "stream-json".into(),
5804                "--output-format".into(),
5805                "stream-json".into(),
5806                "--verbose".into(),
5807            ],
5808            env: BTreeMap::new(),
5809        },
5810        HarnessId::PI => RuntimeLaunch {
5811            program: "pi".into(),
5812            arguments: vec!["--approve".into(), "--mode".into(), "rpc".into()],
5813            env: BTreeMap::new(),
5814        },
5815        HarnessId::OPENCODE => RuntimeLaunch {
5816            program: "opencode".into(),
5817            arguments: vec!["serve".into()],
5818            env: BTreeMap::new(),
5819        },
5820        HarnessId::GEMINI => RuntimeLaunch {
5821            program: "gemini".into(),
5822            arguments: vec!["--acp".into(), "--yolo".into()],
5823            env: BTreeMap::new(),
5824        },
5825        HarnessId::GOOSE => RuntimeLaunch {
5826            program: "goose".into(),
5827            arguments: vec!["acp".into()],
5828            env: BTreeMap::new(),
5829        },
5830        HarnessId::SUPERCODE => RuntimeLaunch {
5831            program: "supercode".into(),
5832            arguments: vec!["acp".into(), "--dangerous".into()],
5833            env: BTreeMap::new(),
5834        },
5835        _ => return None,
5836    };
5837    Some(launch)
5838}
5839
5840/// Disposable harness state for a no-prompt readiness probe. Merely opening
5841/// several stock CLIs writes a session header or migrates configuration, so a
5842/// handshake must never point at the user's real home. Authentication files
5843/// are copied into the private temporary home; all writes disappear with the
5844/// guard after the connection closes.
5845struct IsolatedProbeHome {
5846    launch: RuntimeLaunch,
5847    root: PathBuf,
5848}
5849
5850impl IsolatedProbeHome {
5851    fn new(harness: &str, mut launch: RuntimeLaunch) -> std::io::Result<Self> {
5852        let root = std::env::temp_dir().join(format!(
5853            "supercode-harness-probe-{harness}-{}",
5854            generated_session_id()
5855        ));
5856        std::fs::create_dir_all(&root)?;
5857        set_private_dir_permissions(&root)?;
5858
5859        if let Some(source_home) = std::env::var_os("HOME").map(PathBuf::from) {
5860            for relative in probe_auth_files(harness) {
5861                copy_probe_file(&source_home, &root, relative)?;
5862            }
5863        }
5864        // supercode reads its own config home ($SUPERCODE_HOME, else
5865        // $XDG_CONFIG_HOME/supercode, else ~/.config/supercode), not a fixed
5866        // place under HOME: a login kept under XDG_CONFIG_HOME probed as
5867        // "no API key found" while `supercode run` answered.
5868        if harness == HarnessId::SUPERCODE {
5869            let config_home = crate::agent::global_instructions_dir();
5870            for file in ["config.toml", "credentials.toml"] {
5871                copy_probe_path(
5872                    &config_home.join(file),
5873                    &root.join(".config/supercode").join(file),
5874                )?;
5875            }
5876        }
5877        configure_isolated_probe_auth(harness, &root)?;
5878
5879        let root_text = root.to_string_lossy().into_owned();
5880        for (key, value) in [
5881            ("HOME", root_text.clone()),
5882            (
5883                "XDG_CACHE_HOME",
5884                root.join(".cache").to_string_lossy().into_owned(),
5885            ),
5886            (
5887                "XDG_CONFIG_HOME",
5888                root.join(".config").to_string_lossy().into_owned(),
5889            ),
5890            (
5891                "XDG_DATA_HOME",
5892                root.join(".local/share").to_string_lossy().into_owned(),
5893            ),
5894        ] {
5895            launch.env.insert(key.into(), value);
5896        }
5897        let scoped = match harness {
5898            HarnessId::CLAUDE_CODE => Some(("CLAUDE_CONFIG_DIR", root.join(".claude"))),
5899            HarnessId::CODEX => Some(("CODEX_HOME", root.join(".codex"))),
5900            HarnessId::GEMINI => Some(("GEMINI_CLI_HOME", root.clone())),
5901            HarnessId::GROK => Some(("GROK_HOME", root.join(".grok"))),
5902            HarnessId::PI => Some(("PI_CODING_AGENT_DIR", root.join(".pi/agent"))),
5903            HarnessId::SUPERCODE => Some(("SUPERCODE_HOME", root.join(".config/supercode"))),
5904            _ => None,
5905        };
5906        if let Some((key, value)) = scoped {
5907            launch
5908                .env
5909                .insert(key.into(), value.to_string_lossy().into_owned());
5910        }
5911        Ok(Self { launch, root })
5912    }
5913
5914    fn cleanup(&self) -> std::io::Result<()> {
5915        match std::fs::remove_dir_all(&self.root) {
5916            Ok(()) => Ok(()),
5917            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
5918            Err(error) => Err(error),
5919        }
5920    }
5921}
5922
5923impl Drop for IsolatedProbeHome {
5924    fn drop(&mut self) {
5925        let _ = self.cleanup();
5926    }
5927}
5928
5929fn probe_auth_files(harness: &str) -> &'static [&'static str] {
5930    match harness {
5931        HarnessId::CLAUDE_CODE => &[".claude/.credentials.json", ".claude.json"],
5932        // The gateway endpoint + token live in openclaw's own config; without
5933        // it the isolated probe dials the default endpoint unauthenticated
5934        // (PARITY-24 finding 2026-08-31).
5935        HarnessId::OPENCLAW => &[".openclaw/openclaw.json"],
5936        HarnessId::CODEX => &[".codex/auth.json"],
5937        HarnessId::GEMINI => &[
5938            ".gemini/google_accounts.json",
5939            ".gemini/oauth_creds.json",
5940            ".gemini/settings.json",
5941        ],
5942        HarnessId::GROK => &[".grok/auth.json", ".grok/config.toml"],
5943        HarnessId::OPENCODE => &[
5944            ".config/opencode/auth.json",
5945            ".local/share/opencode/auth.json",
5946        ],
5947        HarnessId::PI => &[".pi/agent/auth.json"],
5948        // Hermes keeps its provider selection in config.yaml, its OAuth
5949        // credential pool in auth.json, and API keys in .env; without them
5950        // the isolated probe sees "No LLM provider configured" for a
5951        // hermes that answers fine from the user's real home.
5952        HarnessId::HERMES => &[".hermes/config.yaml", ".hermes/auth.json", ".hermes/.env"],
5953        _ => &[],
5954    }
5955}
5956
5957fn copy_probe_file(source_home: &Path, probe_home: &Path, relative: &str) -> std::io::Result<()> {
5958    copy_probe_path(&source_home.join(relative), &probe_home.join(relative))
5959}
5960
5961fn copy_probe_path(source: &Path, destination: &Path) -> std::io::Result<()> {
5962    if !source.is_file() {
5963        return Ok(());
5964    }
5965    if let Some(parent) = destination.parent() {
5966        std::fs::create_dir_all(parent)?;
5967        set_private_dir_permissions(parent)?;
5968    }
5969    std::fs::copy(source, destination)?;
5970    set_private_file_permissions(destination)
5971}
5972
5973fn configure_isolated_probe_auth(harness: &str, probe_home: &Path) -> std::io::Result<()> {
5974    if harness != HarnessId::GEMINI {
5975        return Ok(());
5976    }
5977    let oauth = probe_home.join(".gemini/oauth_creds.json");
5978    if !oauth.is_file() {
5979        return Ok(());
5980    }
5981    let settings_path = probe_home.join(".gemini/settings.json");
5982    let mut settings = std::fs::read_to_string(&settings_path)
5983        .ok()
5984        .and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
5985        .unwrap_or_else(|| json!({}));
5986    settings["security"]["auth"]["selectedType"] = Value::String("oauth-personal".into());
5987    std::fs::write(
5988        &settings_path,
5989        serde_json::to_vec_pretty(&settings).map_err(std::io::Error::other)?,
5990    )?;
5991    set_private_file_permissions(&settings_path)
5992}
5993
5994#[cfg(unix)]
5995fn set_private_dir_permissions(path: &Path) -> std::io::Result<()> {
5996    use std::os::unix::fs::PermissionsExt;
5997    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
5998}
5999
6000#[cfg(not(unix))]
6001fn set_private_dir_permissions(_path: &Path) -> std::io::Result<()> {
6002    Ok(())
6003}
6004
6005#[cfg(unix)]
6006fn set_private_file_permissions(path: &Path) -> std::io::Result<()> {
6007    use std::os::unix::fs::PermissionsExt;
6008    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
6009}
6010
6011#[cfg(not(unix))]
6012fn set_private_file_permissions(_path: &Path) -> std::io::Result<()> {
6013    Ok(())
6014}
6015
6016fn find_executable(program: &str) -> Option<PathBuf> {
6017    let candidate = PathBuf::from(program);
6018    if candidate.components().count() > 1 {
6019        return candidate.is_file().then_some(candidate);
6020    }
6021    let path = std::env::var_os("PATH")?;
6022    for directory in std::env::split_paths(&path) {
6023        let candidate = directory.join(program);
6024        if candidate.is_file() {
6025            return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
6026        }
6027        #[cfg(windows)]
6028        {
6029            for extension in ["exe", "cmd", "bat"] {
6030                let candidate = directory.join(format!("{program}.{extension}"));
6031                if candidate.is_file() {
6032                    return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
6033                }
6034            }
6035        }
6036    }
6037    None
6038}
6039
6040async fn executable_version(executable: &Path) -> Option<String> {
6041    let mut command = tokio::process::Command::new(executable);
6042    command
6043        .arg("--version")
6044        .stdin(std::process::Stdio::null())
6045        .stdout(std::process::Stdio::piped())
6046        .stderr(std::process::Stdio::piped())
6047        .kill_on_drop(true);
6048    let output = tokio::time::timeout(Duration::from_secs(3), command.output())
6049        .await
6050        .ok()?
6051        .ok()?;
6052    let stdout = String::from_utf8_lossy(&output.stdout);
6053    let stderr = String::from_utf8_lossy(&output.stderr);
6054    stdout
6055        .lines()
6056        .chain(stderr.lines())
6057        .map(str::trim)
6058        .find(|line| !line.is_empty())
6059        .map(|line| truncate_text(line, 200))
6060}
6061
6062pub(crate) fn auth_evidence(harness: &str) -> bool {
6063    let env_names: &[&str] = match harness {
6064        HarnessId::CLAUDE_CODE => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
6065        HarnessId::CODEX => &["OPENAI_API_KEY"],
6066        HarnessId::OPENCODE => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
6067        HarnessId::PI => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
6068        HarnessId::GROK => &["XAI_API_KEY", "GROK_API_KEY"],
6069        HarnessId::GEMINI => &["GEMINI_API_KEY", "GOOGLE_API_KEY"],
6070        HarnessId::SUPERCODE => &["OPENROUTER_API_KEY"],
6071        _ => &[],
6072    };
6073    if env_names
6074        .iter()
6075        .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
6076    {
6077        return true;
6078    }
6079    let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else {
6080        return false;
6081    };
6082    let files: Vec<PathBuf> = match harness {
6083        HarnessId::CLAUDE_CODE => vec![home.join(".claude/.credentials.json")],
6084        HarnessId::CODEX => vec![home.join(".codex/auth.json")],
6085        HarnessId::OPENCODE => vec![
6086            home.join(".local/share/opencode/auth.json"),
6087            home.join(".config/opencode/auth.json"),
6088        ],
6089        HarnessId::PI => vec![home.join(".pi/agent/auth.json")],
6090        HarnessId::GROK => vec![home.join(".grok/auth.json")],
6091        HarnessId::GEMINI => vec![
6092            home.join(".gemini/oauth_creds.json"),
6093            home.join(".gemini/google_accounts.json"),
6094        ],
6095        HarnessId::SUPERCODE => vec![home.join(".config/supercode/credentials.toml")],
6096        HarnessId::HERMES => vec![home.join(".hermes/auth.json"), home.join(".hermes/.env")],
6097        _ => Vec::new(),
6098    };
6099    if files.into_iter().any(|path| {
6100        std::fs::metadata(path)
6101            .map(|metadata| metadata.is_file() && metadata.len() > 2)
6102            .unwrap_or(false)
6103    }) {
6104        return true;
6105    }
6106    // macOS keeps Claude Code's OAuth login in the Keychain, so
6107    // `.claude/.credentials.json` never exists there and the file probe above
6108    // reports a signed-in install as unauthenticated forever. A completed
6109    // login also writes an `oauthAccount` record into `~/.claude.json` on
6110    // every platform — file-based, prompt-free evidence (querying the
6111    // Keychain itself from an unsigned daemon can raise a UI prompt).
6112    if harness == HarnessId::CLAUDE_CODE {
6113        return std::fs::read_to_string(home.join(".claude.json"))
6114            .map(|text| text.contains("\"oauthAccount\""))
6115            .unwrap_or(false);
6116    }
6117    false
6118}
6119
6120fn looks_like_auth_error(message: &str) -> bool {
6121    let message = message.to_ascii_lowercase();
6122    [
6123        "auth",
6124        "login",
6125        "sign in",
6126        "sign-in",
6127        "credential",
6128        "unauthorized",
6129        "forbidden",
6130        "token",
6131    ]
6132    .iter()
6133    .any(|needle| message.contains(needle))
6134}
6135
6136fn unavailable_capabilities() -> crate::RuntimeCapabilities {
6137    crate::RuntimeCapabilities {
6138        start_session: false,
6139        resume_session: false,
6140        attach_existing_process: false,
6141        send_input: false,
6142        stream_events: false,
6143        interrupt: false,
6144        steer: false,
6145        respond_to_requests: false,
6146    }
6147}
6148
6149fn truncate_text(text: &str, max_chars: usize) -> String {
6150    let mut chars = text.chars();
6151    let truncated = chars.by_ref().take(max_chars).collect::<String>();
6152    if chars.next().is_some() {
6153        format!("{truncated}…")
6154    } else {
6155        truncated
6156    }
6157}
6158
6159/// The process group a runtime's own handle names, when it names one.
6160///
6161/// Every adapter that spawns a local process spawns it as its own group
6162/// leader (`Command::process_group(0)`), so the endpoint's pid IS the group
6163/// id. A runtime reached over HTTP, or one supercode joined rather than
6164/// spawned, names no group here and is left alone.
6165fn runtime_process_group(handle: &crate::RuntimeHandle) -> Option<u32> {
6166    match &handle.endpoint {
6167        crate::RuntimeEndpoint::LocalProcess { pid, .. } => *pid,
6168        crate::RuntimeEndpoint::Http { .. } => None,
6169    }
6170}
6171
6172/// SIGKILL a wedged runtime's whole process group, reporting whether there
6173/// was one to signal. This is the same group teardown a graceful `close`
6174/// performs; it runs here only when the graceful path blew its deadline,
6175/// because the task parked on the unanswered call still owns the process
6176/// handle and so no `Drop` of ours can reach it.
6177fn kill_runtime_process_group(process_group: Option<u32>) -> bool {
6178    match process_group {
6179        #[cfg(unix)]
6180        Some(pid) => {
6181            crate::lsp::kill_process_group(pid);
6182            true
6183        }
6184        #[cfg(not(unix))]
6185        Some(_) => false,
6186        None => false,
6187    }
6188}
6189
6190fn error_message(error: ServiceError) -> String {
6191    match error {
6192        ServiceError::InvalidParams(message)
6193        | ServiceError::Operation(message)
6194        | ServiceError::UnsupportedAction(message) => message,
6195        ServiceError::MethodNotFound => "runtime adapter is not available".into(),
6196        ServiceError::Sdk(error) => error.to_string(),
6197    }
6198}
6199
6200#[derive(Debug)]
6201enum ServiceError {
6202    InvalidParams(String),
6203    MethodNotFound,
6204    UnsupportedAction(String),
6205    Operation(String),
6206    Sdk(SdkError),
6207}
6208
6209fn sdk_error(operation: SdkOperation, error: ServiceError) -> SdkError {
6210    match error {
6211        ServiceError::InvalidParams(message) => {
6212            SdkError::new(SdkErrorCode::InvalidArgument, operation, message)
6213        }
6214        ServiceError::MethodNotFound | ServiceError::UnsupportedAction(_) => {
6215            SdkError::unsupported(operation)
6216        }
6217        ServiceError::Operation(message) => {
6218            let code = if message.contains("already in progress") {
6219                SdkErrorCode::Busy
6220            } else if message.contains("not supported by this runtime") {
6221                SdkErrorCode::UnsupportedAction
6222            } else if message.contains("unknown runtime connection") {
6223                SdkErrorCode::NotFound
6224            } else {
6225                SdkErrorCode::Execution
6226            };
6227            SdkError::new(code, operation, message)
6228        }
6229        ServiceError::Sdk(error) => error,
6230    }
6231}
6232
6233fn sdk_rpc_error(id: Value, error: &SdkError) -> Value {
6234    let error_code = error.code();
6235    let code = match error_code {
6236        SdkErrorCode::Unauthenticated => -32030,
6237        SdkErrorCode::Unauthorized => -32031,
6238        SdkErrorCode::ControllerRequired => -32032,
6239        SdkErrorCode::LeaseExpired => -32033,
6240        SdkErrorCode::InvalidArgument => -32602,
6241        SdkErrorCode::NotFound => -32004,
6242        SdkErrorCode::Busy => -32000,
6243        SdkErrorCode::UnsupportedAction => -32020,
6244        SdkErrorCode::Execution => -32002,
6245        SdkErrorCode::Transport => -32003,
6246    };
6247    json!({
6248        "jsonrpc": "2.0",
6249        "id": id,
6250        "error": {
6251            "code": code,
6252            "name": error_code,
6253            "operation": error.operation(),
6254            "message": error.to_string(),
6255        },
6256    })
6257}
6258
6259fn decode<T: for<'de> Deserialize<'de>>(value: Value) -> std::result::Result<T, ServiceError> {
6260    serde_json::from_value(value).map_err(|error| ServiceError::InvalidParams(error.to_string()))
6261}
6262
6263fn operation(error: impl Into<crate::Error>) -> ServiceError {
6264    let error = error.into();
6265    match error {
6266        crate::Error::Sdk(error) => ServiceError::Sdk(error),
6267        error => ServiceError::Operation(error.to_string()),
6268    }
6269}
6270
6271/// ORCH-12 `harness.v1.memory.show|search` params. `homes` is the same
6272/// storage-root override every read-only method accepts, so a caller can
6273/// point the read at a fixture home without touching the real ones.
6274#[derive(Debug, Clone, Deserialize, Default)]
6275#[serde(default)]
6276struct MemoryRequest {
6277    /// Harness whose store is read. Required.
6278    harness: Option<String>,
6279    /// The needle, required by `search`.
6280    query: Option<String>,
6281    /// Hermes profile, OpenClaw agent, or Claude Code project.
6282    profile: Option<String>,
6283    /// Claude Code session id selecting a project store (`show` only).
6284    session: Option<String>,
6285    /// Include each document's whole text (`show` only).
6286    full: bool,
6287    /// Treat `query` as a regular expression (`search` only).
6288    regex: bool,
6289    /// Working tree whose project store is read.
6290    cwd: Option<std::path::PathBuf>,
6291    /// Storage roots to read.
6292    homes: crate::HarnessHomes,
6293}
6294
6295/// Read the memory noun. A harness with no memory store fails with
6296/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6297fn memory_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6298    let request = decode::<MemoryRequest>(params)?;
6299    let harness = request
6300        .harness
6301        .clone()
6302        .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6303    let to_service = |error: crate::memory::MemoryError| match error {
6304        crate::memory::MemoryError::UnsupportedHarness { .. }
6305        | crate::memory::MemoryError::SessionNotScoped { .. } => {
6306            ServiceError::UnsupportedAction(error.to_string())
6307        }
6308        other => ServiceError::InvalidParams(other.to_string()),
6309    };
6310    match method {
6311        "harness.v1.memory.show" => {
6312            let documents = crate::memory::show_memory(&crate::memory::MemoryQuery {
6313                harness,
6314                profile: request.profile,
6315                session: request.session,
6316                full: request.full,
6317                cwd: request.cwd,
6318                homes: request.homes,
6319            })
6320            .map_err(to_service)?;
6321            Ok(json!({
6322                "schema": crate::memory::MEMORY_SCHEMA,
6323                "documents": documents,
6324            }))
6325        }
6326        "harness.v1.memory.search" => {
6327            let query = request
6328                .query
6329                .ok_or_else(|| ServiceError::InvalidParams("`query` is required".into()))?;
6330            let matches = crate::memory::search_memory(&crate::memory::MemorySearchQuery {
6331                harness,
6332                query,
6333                profile: request.profile,
6334                regex: request.regex,
6335                cwd: request.cwd,
6336                homes: request.homes,
6337            })
6338            .map_err(to_service)?;
6339            Ok(json!({
6340                "schema": crate::memory::MEMORY_SCHEMA,
6341                "matches": matches,
6342            }))
6343        }
6344        _ => Err(ServiceError::MethodNotFound),
6345    }
6346}
6347
6348/// ORCH-10 `harness.v1.profiles.list|get` params. `homes` is the same
6349/// storage-root override every read-only method accepts, so a caller can
6350/// point the read at a fixture home without touching the real ones.
6351#[derive(Debug, Clone, Deserialize)]
6352#[serde(default)]
6353struct ProfilesQuery {
6354    /// Restrict the listing to one harness. `get` requires it.
6355    harness: Option<String>,
6356    /// Profile name, required by `get`.
6357    name: Option<String>,
6358    /// Storage roots to read.
6359    homes: crate::HarnessHomes,
6360}
6361
6362impl Default for ProfilesQuery {
6363    fn default() -> Self {
6364        Self {
6365            harness: None,
6366            name: None,
6367            homes: crate::HarnessHomes::default(),
6368        }
6369    }
6370}
6371
6372/// Read the profile noun. A harness with no profile concept fails with
6373/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6374fn profiles_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6375    let query = decode::<ProfilesQuery>(params)?;
6376    let to_service = |error: crate::profiles::ProfileError| match error {
6377        crate::profiles::ProfileError::UnsupportedHarness { .. } => {
6378            ServiceError::UnsupportedAction(error.to_string())
6379        }
6380        crate::profiles::ProfileError::NotFound { .. } => {
6381            ServiceError::InvalidParams(error.to_string())
6382        }
6383    };
6384    match method {
6385        "harness.v1.profiles.list" => {
6386            let profiles = crate::profiles::list_profiles(&query.homes, query.harness.as_deref())
6387                .map_err(to_service)?;
6388            Ok(json!({
6389                "schema": crate::profiles::PROFILES_SCHEMA,
6390                "profiles": profiles,
6391            }))
6392        }
6393        "harness.v1.profiles.get" => {
6394            let harness = query
6395                .harness
6396                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6397            let name = query
6398                .name
6399                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6400            let profile =
6401                crate::profiles::get_profile(&query.homes, &harness, &name).map_err(to_service)?;
6402            Ok(json!({
6403                "schema": crate::profiles::PROFILES_SCHEMA,
6404                "profile": profile,
6405            }))
6406        }
6407        _ => Err(ServiceError::MethodNotFound),
6408    }
6409}
6410
6411/// ORCH-14 `harness.v1.channels.list|status` params, the same storage-root
6412/// override every read-only method accepts so a caller can point the read at
6413/// a fixture home without touching the real ones.
6414#[derive(Debug, Clone, Deserialize)]
6415#[serde(default)]
6416struct ChannelsQuery {
6417    /// Restrict the listing to one harness. `status` requires it.
6418    harness: Option<String>,
6419    /// Channel name, required by `status`.
6420    name: Option<String>,
6421    /// Storage roots to read.
6422    homes: crate::HarnessHomes,
6423}
6424
6425impl Default for ChannelsQuery {
6426    fn default() -> Self {
6427        Self {
6428            harness: None,
6429            name: None,
6430            homes: crate::HarnessHomes::default(),
6431        }
6432    }
6433}
6434
6435/// Read the channel noun. A harness with no channel concept fails with
6436/// `UnsupportedAction` (RPC `-32020`), never an empty list. No row carries a
6437/// token, key or secret — see `crate::channels` "Secrecy".
6438#[derive(Debug, Clone, Deserialize)]
6439#[serde(default)]
6440struct RoutesQuery {
6441    harness: Option<String>,
6442    /// Restrict to routes targeting one profile / agent.
6443    profile: Option<String>,
6444    homes: crate::HarnessHomes,
6445}
6446
6447impl Default for RoutesQuery {
6448    fn default() -> Self {
6449        Self {
6450            harness: None,
6451            profile: None,
6452            homes: crate::HarnessHomes::default(),
6453        }
6454    }
6455}
6456
6457#[derive(Debug, Clone, Deserialize)]
6458#[serde(default)]
6459struct TriggersQuery {
6460    harness: Option<String>,
6461    homes: crate::HarnessHomes,
6462}
6463
6464impl Default for TriggersQuery {
6465    fn default() -> Self {
6466        Self {
6467            harness: None,
6468            homes: crate::HarnessHomes::default(),
6469        }
6470    }
6471}
6472
6473fn triggers_call(params: Value) -> std::result::Result<Value, ServiceError> {
6474    let query = decode::<TriggersQuery>(params)?;
6475    let triggers = crate::triggers::list_triggers(&query.homes, query.harness.as_deref())
6476        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6477    Ok(json!({
6478        "schema": crate::triggers::TRIGGERS_SCHEMA,
6479        "triggers": triggers,
6480    }))
6481}
6482
6483fn routes_call(params: Value) -> std::result::Result<Value, ServiceError> {
6484    let query = decode::<RoutesQuery>(params)?;
6485    let routes = crate::routes::list_routes(
6486        &query.homes,
6487        query.harness.as_deref(),
6488        query.profile.as_deref(),
6489    )
6490    .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6491    Ok(json!({
6492        "schema": crate::routes::ROUTES_SCHEMA,
6493        "routes": routes,
6494    }))
6495}
6496
6497fn channels_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6498    let query = decode::<ChannelsQuery>(params)?;
6499    let to_service = |error: crate::channels::ChannelError| match error {
6500        crate::channels::ChannelError::UnsupportedHarness { .. } => {
6501            ServiceError::UnsupportedAction(error.to_string())
6502        }
6503        crate::channels::ChannelError::NotFound { .. } => {
6504            ServiceError::InvalidParams(error.to_string())
6505        }
6506    };
6507    match method {
6508        "harness.v1.channels.list" => {
6509            let channels = crate::channels::list_channels(&query.homes, query.harness.as_deref())
6510                .map_err(to_service)?;
6511            Ok(json!({
6512                "schema": crate::channels::CHANNELS_SCHEMA,
6513                "channels": channels,
6514            }))
6515        }
6516        "harness.v1.channels.status" => {
6517            let harness = query
6518                .harness
6519                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6520            let name = query
6521                .name
6522                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6523            let channel = crate::channels::channel_status(&query.homes, &harness, &name)
6524                .map_err(to_service)?;
6525            Ok(json!({
6526                "schema": crate::channels::CHANNELS_SCHEMA,
6527                "channel": channel,
6528            }))
6529        }
6530        _ => Err(ServiceError::MethodNotFound),
6531    }
6532}
6533
6534fn rpc_error(id: Value, code: i64, message: &str) -> Value {
6535    json!({
6536        "jsonrpc": "2.0",
6537        "id": id,
6538        "error": {"code": code, "message": message},
6539    })
6540}
6541
6542#[cfg(test)]
6543mod tests {
6544    use super::*;
6545    use crate::{HarnessEvent, HarnessId, RuntimeEndpoint, RuntimeHandle, StorageLocator};
6546    use async_trait::async_trait;
6547    use std::io::Write;
6548    use std::path::PathBuf;
6549    use std::time::Instant;
6550
6551    #[test]
6552    fn indexed_claude_descriptor_carries_its_delivery_door() {
6553        let descriptor = SessionDescriptor {
6554            locator: SessionLocator {
6555                harness: HarnessId::new(HarnessId::CLAUDE_CODE),
6556                session_id: "live-session".into(),
6557                storage: StorageLocator::File {
6558                    path: PathBuf::from("/tmp/live-session.jsonl"),
6559                },
6560            },
6561            cwd: Some(PathBuf::from("/project")),
6562            title: None,
6563            preview_candidates: Vec::new(),
6564            latest_message_candidates: Vec::new(),
6565            updated_at_ms: Some(1),
6566            message_count: None,
6567            model: None,
6568            parent_session_id: None,
6569            child_session_count: 0,
6570            nouns: Default::default(),
6571        };
6572        let doors =
6573            crate::mail_route::LiveSessions::with(&[("claude-code", "live-session", "native")]);
6574
6575        let value = live_descriptor_value(&descriptor, &doors).unwrap();
6576        assert_eq!(value["delivery"], "native");
6577        assert!(value.get("live_endpoint").is_none());
6578    }
6579
6580    struct EndingRuntime {
6581        handle: RuntimeHandle,
6582        event: Option<HarnessEvent>,
6583        close_failures: usize,
6584    }
6585
6586    #[async_trait]
6587    impl RuntimeConnection for EndingRuntime {
6588        fn handle(&self) -> &RuntimeHandle {
6589            &self.handle
6590        }
6591
6592        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
6593            unreachable!("ending runtime does not accept input")
6594        }
6595
6596        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
6597            Ok(self.event.take())
6598        }
6599
6600        async fn interrupt(&mut self) -> crate::Result<()> {
6601            Ok(())
6602        }
6603
6604        async fn respond(&mut self, _request_id: Value, _response: Value) -> crate::Result<()> {
6605            Ok(())
6606        }
6607
6608        async fn close(&mut self) -> crate::Result<()> {
6609            if self.close_failures > 0 {
6610                self.close_failures -= 1;
6611                return Err(crate::Error::Other(
6612                    "cleanup temporarily unavailable".into(),
6613                ));
6614            }
6615            Ok(())
6616        }
6617    }
6618
6619    fn ending_runtime(event: Option<HarnessEvent>) -> Box<dyn RuntimeConnection> {
6620        Box::new(EndingRuntime {
6621            handle: RuntimeHandle {
6622                harness: HarnessId::from(HarnessId::CLAUDE_CODE),
6623                runtime_id: "ending-session".into(),
6624                endpoint: RuntimeEndpoint::LocalProcess {
6625                    pid: None,
6626                    command: vec!["ending-runtime".into()],
6627                    protocol: "test".into(),
6628                },
6629            },
6630            event,
6631            close_failures: 0,
6632        })
6633    }
6634
6635    #[tokio::test]
6636    async fn closing_a_runtime_surrenders_the_connection_even_when_teardown_fails() {
6637        let mut service = HarnessSessionService::new();
6638        let handle = ending_runtime(None).handle().clone();
6639        let runtime_id = handle.runtime_id.clone();
6640        let opened = service
6641            .insert_runtime(Box::new(EndingRuntime {
6642                handle,
6643                event: None,
6644                close_failures: 1,
6645            }))
6646            .unwrap();
6647        let connection = opened["connection"].as_str().unwrap().to_string();
6648        service.terminal_launches.insert(
6649            connection.clone(),
6650            StructuredLaunch {
6651                cwd: PathBuf::from("/fixture"),
6652                program: "fixture".into(),
6653                arguments: Vec::new(),
6654                env: BTreeMap::new(),
6655            },
6656        );
6657        let first = service
6658            .handle_async(request(
6659                1,
6660                "harness.v1.runtimes.close",
6661                json!({"connection": connection}),
6662            ))
6663            .await;
6664        // The harness's own teardown failed and the caller is told so...
6665        assert!(first.get("error").is_some(), "{first}");
6666        // ...but the connection is gone all the same. A connection whose close
6667        // cannot complete is exactly the one that must not stay registered:
6668        // holding it would answer every later call on this node with a turn
6669        // that is never going to end.
6670        assert!(!service.runtimes.contains_key(&connection));
6671        assert!(!service.terminal_launches.contains_key(&connection));
6672        assert!(!service.runtime_sequences.contains_key(&runtime_id));
6673        let again = service
6674            .handle_async(request(
6675                2,
6676                "harness.v1.runtimes.close",
6677                json!({"connection": connection}),
6678            ))
6679            .await;
6680        assert_eq!(again["error"]["code"], -32602, "{again}");
6681    }
6682
6683    fn request(id: u64, method: &str, params: Value) -> Value {
6684        json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
6685    }
6686
6687    // ---- ORCH-6: conversation nouns on `sessions.*` ----------------------
6688
6689    fn hermes_store() -> PathBuf {
6690        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hermes_home/state.db")
6691    }
6692
6693    /// The discovery response for the Hermes fixture home, with the one
6694    /// machine-specific value (the absolute store path) replaced so the exact
6695    /// same JSON can be committed and replayed by the UI story.
6696    fn hermes_discovery(params: Value) -> Value {
6697        let mut response =
6698            HarnessSessionService::new().handle(request(1, "harness.v1.sessions.discover", params));
6699        let store = hermes_store().display().to_string();
6700        for session in response["result"]["sessions"]
6701            .as_array_mut()
6702            .expect("sessions array")
6703        {
6704            if session["locator"]["storage"]["path"] == json!(store) {
6705                session["locator"]["storage"]["path"] = json!("<fixtures>/hermes_home/state.db");
6706            }
6707            // `activity` reports a wall-clock observation instant, not a fact
6708            // about the session; it would make this response differ on every
6709            // call. The nouns under test are all session facts.
6710            session.as_object_mut().unwrap().remove("activity");
6711        }
6712        response["result"].take()
6713    }
6714
6715    fn hermes_query() -> Value {
6716        json!({
6717            "harnesses": ["hermes"],
6718            "homes": {"hermes": hermes_store()},
6719        })
6720    }
6721
6722    fn row<'a>(result: &'a Value, id: &str) -> &'a Value {
6723        result["sessions"]
6724            .as_array()
6725            .expect("sessions array")
6726            .iter()
6727            .find(|session| session["locator"]["session_id"] == json!(id))
6728            .unwrap_or_else(|| panic!("no discovered row for `{id}` in {result:#}"))
6729    }
6730
6731    #[test]
6732    fn orch6_discover_rows_carry_the_conversation_nouns() {
6733        let result = hermes_discovery(hermes_query());
6734
6735        // A Telegram DM: reached on a channel, no repo — the workspace IS the
6736        // channel (D2 precedence), and `main` is not a profile.
6737        let dm = row(&result, "tg-dm-1");
6738        assert_eq!(dm["trigger"], json!("channel"));
6739        assert_eq!(dm["surface"]["platform"], json!("telegram"));
6740        assert_eq!(dm["surface"]["kind"], json!("dm"));
6741        assert_eq!(dm["surface"]["chat_id"], json!("123456"));
6742        assert_eq!(dm["surface"]["participant_id"], json!("u1"));
6743        assert_eq!(
6744            dm["workspace"],
6745            json!({"kind": "channel", "value": "telegram:123456"})
6746        );
6747        assert!(dm.get("profile").is_none(), "{dm:#}");
6748
6749        // A cron fire: recurring, with the job recovered from the minted id.
6750        let fire = row(&result, "cron_job42_20260902_120000");
6751        assert_eq!(fire["trigger"], json!("cron"));
6752        assert_eq!(
6753            fire["recurrence"],
6754            json!({"job_id": "job42", "kind": "cron"})
6755        );
6756        assert_eq!(fire["workspace"]["kind"], json!("repo"));
6757
6758        // A profiled group session with a pending handoff: repo workspace
6759        // wins over the channel, and the chat stays on the surface key.
6760        let coder = row(&result, "tg-coder-1");
6761        assert_eq!(coder["trigger"], json!("channel"));
6762        assert_eq!(coder["profile"], json!("coder"));
6763        assert_eq!(coder["surface"]["thread_id"], json!("55"));
6764        assert_eq!(
6765            coder["surface"]["key"],
6766            json!("agent:coder:telegram:group:-100777:55")
6767        );
6768        assert_eq!(
6769            coder["workspace"],
6770            json!({"kind": "repo", "value": "/workspace/project"})
6771        );
6772        assert_eq!(
6773            coder["cross_surface"],
6774            json!({"state": "pending", "platform": "discord"})
6775        );
6776
6777        // A plain ACP session stays human-triggered with no surface at all.
6778        let acp = row(&result, "cef97234-e8e8-428a-99ab-e8fff4e7e613");
6779        assert_eq!(acp["trigger"], json!("human"));
6780        assert!(acp.get("surface").is_none(), "{acp:#}");
6781        assert_eq!(acp["workspace"], json!({"kind": "none"}));
6782    }
6783
6784    #[test]
6785    fn orch6_discover_filters_by_harness_and_profile() {
6786        let mut params = hermes_query();
6787        params["profile"] = json!("coder");
6788        let result = hermes_discovery(params);
6789        let ids: Vec<&str> = result["sessions"]
6790            .as_array()
6791            .expect("sessions array")
6792            .iter()
6793            .map(|session| session["locator"]["session_id"].as_str().unwrap())
6794            .collect();
6795        assert_eq!(ids, vec!["tg-coder-1"]);
6796
6797        // A profile no session is routed through returns nothing rather than
6798        // silently ignoring the filter.
6799        let mut missing = hermes_query();
6800        missing["profile"] = json!("nobody");
6801        assert_eq!(hermes_discovery(missing)["sessions"], json!([]));
6802
6803        // The harness filter is `harnesses`; an id no harness answers to is
6804        // an empty page, never every store on the box.
6805        let elsewhere = json!({"harnesses": ["codex"], "homes": {"codex": hermes_store()}});
6806        assert_eq!(hermes_discovery(elsewhere)["sessions"], json!([]));
6807    }
6808
6809    #[test]
6810    fn orch6_load_reports_the_same_nouns_as_discovery() {
6811        let mut service = HarnessSessionService::new();
6812        let loaded = service.handle(request(
6813            1,
6814            "harness.v1.sessions.load",
6815            json!({"locator": {
6816                "harness": "hermes",
6817                "session_id": "tg-coder-1",
6818                "storage": {"kind": "file", "path": hermes_store()},
6819            }}),
6820        ));
6821        let session = &loaded["result"]["session"];
6822        let discovered = hermes_discovery(hermes_query());
6823        let row = row(&discovered, "tg-coder-1");
6824        for noun in [
6825            "trigger",
6826            "surface",
6827            "profile",
6828            "recurrence",
6829            "cross_surface",
6830            "workspace",
6831        ] {
6832            assert_eq!(
6833                session[noun],
6834                row.get(noun).cloned().unwrap_or(Value::Null),
6835                "`{noun}` disagrees between sessions.load and sessions.discover"
6836            );
6837        }
6838    }
6839
6840    /// ORCH-10: the fixture homes, as the RPC's `homes` override. Hermes's
6841    /// home is named by its `state.db`; OpenClaw's is the state directory.
6842    fn profile_fixture_homes() -> Value {
6843        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6844        json!({
6845            "hermes": fixtures.join("hermes_home/state.db"),
6846            "openclaw": fixtures.join("openclaw_home"),
6847        })
6848    }
6849
6850    fn profile_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
6851        response["result"]["profiles"]
6852            .as_array()
6853            .unwrap_or_else(|| panic!("no profiles array in {response}"))
6854            .iter()
6855            .find(|row| row["harness"] == harness && row["name"] == name)
6856            .unwrap_or_else(|| panic!("no `{harness}` profile `{name}` in {response}"))
6857    }
6858
6859    /// dev/01: every source answers in one row shape, over the committed
6860    /// fixture homes — the Hermes profile directory and its `state.db`
6861    /// partition, the OpenClaw agent directories and `openclaw.json`, and
6862    /// supercode's own presets.
6863    #[test]
6864    fn profiles_list_reads_every_source_uniformly() {
6865        let mut service = HarnessSessionService::new();
6866        let response = service.handle(request(
6867            1,
6868            "harness.v1.profiles.list",
6869            json!({"homes": profile_fixture_homes()}),
6870        ));
6871        assert_eq!(
6872            response["result"]["schema"],
6873            crate::profiles::PROFILES_SCHEMA
6874        );
6875
6876        let default = profile_row(&response, "hermes", "default");
6877        assert_eq!(default["kind"], "hermes_profile");
6878        assert_eq!(default["default"], true);
6879        assert_eq!(default["routes"], 0);
6880        assert_eq!(default["sessions"], 11);
6881        assert_eq!(default["model"], "anthropic/claude-sonnet-4-5");
6882
6883        let coder = profile_row(&response, "hermes", "coder");
6884        assert_eq!(coder["kind"], "hermes_profile");
6885        assert_eq!(coder["default"], false);
6886        assert_eq!(coder["routes"], 1, "gateway.profile_routes targets coder");
6887        assert_eq!(coder["sessions"], 1, "state.db profile_name = 'coder'");
6888        assert_eq!(coder["model"], "anthropic/claude-opus-4-8");
6889        assert!(coder["home"]
6890            .as_str()
6891            .unwrap()
6892            .ends_with("hermes_home/profiles/coder"));
6893
6894        let main = profile_row(&response, "openclaw", "main");
6895        assert_eq!(main["kind"], "openclaw_agent");
6896        // No entry declares `default: true` (real configs do not), so `main`
6897        // wins on OpenClaw's own convention rather than alphabetically.
6898        assert_eq!(main["default"], true);
6899        assert_eq!(main["routes"], 0);
6900        assert_eq!(main["sessions"], 4);
6901        assert_eq!(
6902            main["model"],
6903            Value::Null,
6904            "`agents.defaults.model` is an install default, not this agent's pin"
6905        );
6906
6907        let design = profile_row(&response, "openclaw", "design");
6908        assert_eq!(design["default"], false);
6909        assert_eq!(design["routes"], 1, "one binding names agentId `design`");
6910        assert_eq!(design["sessions"], 0);
6911        assert_eq!(design["model"], "anthropic/claude-opus-4-8");
6912
6913        let preset = profile_row(&response, "supercode", "supercode-default");
6914        assert_eq!(preset["kind"], "preset");
6915        assert_eq!(preset["default"], true);
6916        assert_eq!(preset["home"], Value::Null);
6917        assert_eq!(preset["routes"], Value::Null);
6918    }
6919
6920    /// Codex's own profiles are `[profiles.<name>]` tables, with the
6921    /// top-level `profile` key naming the default.
6922    #[test]
6923    fn profiles_list_reads_codex_profile_tables() {
6924        let codex_home = std::env::temp_dir().join(format!(
6925            "supercode-orch10-codex-{}-{}",
6926            std::process::id(),
6927            std::time::SystemTime::now()
6928                .duration_since(std::time::UNIX_EPOCH)
6929                .unwrap()
6930                .as_nanos()
6931        ));
6932        std::fs::create_dir_all(codex_home.join("sessions")).unwrap();
6933        std::fs::write(
6934            codex_home.join("config.toml"),
6935            "profile = \"review\"\n\n[profiles.review]\nmodel = \"gpt-5.1-codex\"\n\n[profiles.fast]\nmodel = \"gpt-5.1-codex-mini\"\n",
6936        )
6937        .unwrap();
6938
6939        let mut service = HarnessSessionService::new();
6940        let response = service.handle(request(
6941            1,
6942            "harness.v1.profiles.list",
6943            json!({"harness": "codex", "homes": {"codex": codex_home.join("sessions")}}),
6944        ));
6945        let rows = response["result"]["profiles"].as_array().unwrap();
6946        assert_eq!(rows.len(), 2, "{response}");
6947        let review = profile_row(&response, "codex", "review");
6948        assert_eq!(review["kind"], "codex_profile");
6949        assert_eq!(review["default"], true);
6950        assert_eq!(review["model"], "gpt-5.1-codex");
6951        assert_eq!(review["home"], Value::Null);
6952        assert_eq!(profile_row(&response, "codex", "fast")["default"], false);
6953
6954        let got = service.handle(request(
6955            2,
6956            "harness.v1.profiles.get",
6957            json!({
6958                "harness": "codex",
6959                "name": "fast",
6960                "homes": {"codex": codex_home.join("sessions")},
6961            }),
6962        ));
6963        assert_eq!(got["result"]["profile"]["model"], "gpt-5.1-codex-mini");
6964        std::fs::remove_dir_all(&codex_home).ok();
6965    }
6966
6967    /// A verb a harness lacks fails with `UnsupportedAction`, never a silent
6968    /// empty list; an unknown name is an invalid argument, not an empty row.
6969    #[test]
6970    fn profiles_refuse_harnesses_without_the_concept() {
6971        let mut service = HarnessSessionService::new();
6972        let response = service.handle(request(
6973            1,
6974            "harness.v1.profiles.list",
6975            json!({"harness": "claude-code"}),
6976        ));
6977        assert_eq!(response["error"]["code"], -32020, "{response}");
6978
6979        let missing = service.handle(request(
6980            2,
6981            "harness.v1.profiles.get",
6982            json!({
6983                "harness": "hermes",
6984                "name": "no-such-profile",
6985                "homes": profile_fixture_homes(),
6986            }),
6987        ));
6988        assert_eq!(missing["error"]["code"], -32602, "{missing}");
6989    }
6990
6991    /// The two methods are advertised, so a client discovers them from
6992    /// `harness.v1.capabilities` rather than from documentation.
6993    #[test]
6994    fn profiles_methods_are_advertised() {
6995        let mut service = HarnessSessionService::new();
6996        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
6997        let methods = response["result"]["methods"].as_array().unwrap();
6998        for method in ["harness.v1.profiles.list", "harness.v1.profiles.get"] {
6999            assert!(
7000                methods.iter().any(|entry| entry == method),
7001                "{method} is not advertised"
7002            );
7003        }
7004    }
7005
7006    // -----------------------------------------------------------------
7007    // ORCH-14 — channels
7008    // -----------------------------------------------------------------
7009
7010    fn channel_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
7011        response["result"]["channels"]
7012            .as_array()
7013            .unwrap_or_else(|| panic!("no channels array in {response}"))
7014            .iter()
7015            .find(|row| row["harness"] == harness && row["name"] == name)
7016            .unwrap_or_else(|| panic!("no `{harness}` channel `{name}` in {response}"))
7017    }
7018
7019    fn channels_list(harness: Option<&str>) -> Value {
7020        let mut params = json!({"homes": profile_fixture_homes()});
7021        if let Some(harness) = harness {
7022            params["harness"] = json!(harness);
7023        }
7024        HarnessSessionService::new().handle(request(1, "harness.v1.channels.list", params))
7025    }
7026
7027    /// dev/01: both sources answer in one row shape over the committed
7028    /// fixture homes — Hermes's `platforms:` blocks with their `extra` maps,
7029    /// and OpenClaw's `channels.<name>` entries split per account.
7030    #[test]
7031    fn channels_list_reads_both_gateway_harnesses_uniformly() {
7032        let response = channels_list(None);
7033        assert_eq!(
7034            response["result"]["schema"],
7035            crate::channels::CHANNELS_SCHEMA
7036        );
7037
7038        // Hermes: a credentialed platform, a bridged `extra.key` platform,
7039        // and one the config explicitly disables.
7040        let telegram = channel_row(&response, "hermes", "telegram");
7041        assert_eq!(telegram["kind"], "telegram");
7042        assert_eq!(telegram["enabled"], true);
7043        assert_eq!(telegram["configured"], true);
7044        // The `sessions` count is the discovery rows whose surface platform
7045        // is telegram: the fixture's `agent:main:telegram:…` DM and the
7046        // `agent:coder:telegram:…` group.
7047        assert_eq!(telegram["sessions"], 2);
7048        let api = channel_row(&response, "hermes", "api_server");
7049        assert_eq!(api["configured"], true, "extra.key is a credential key");
7050        assert_eq!(api["sessions"], 0);
7051        let webhook = channel_row(&response, "hermes", "webhook");
7052        assert_eq!(webhook["enabled"], false);
7053        // Hermes lists no credential for `webhook`: declaring it is all it
7054        // needs, so a credential-less entry is still `configured`.
7055        assert_eq!(webhook["configured"], true);
7056
7057        // OpenClaw: one row per account, named `<channel>/<accountId>`.
7058        let linked = channel_row(&response, "openclaw", "slack/T0FIXTURE");
7059        assert_eq!(linked["kind"], "slack");
7060        assert_eq!(linked["account"], "T0FIXTURE");
7061        assert_eq!(linked["enabled"], true);
7062        assert_eq!(linked["configured"], true);
7063        let unlinked = channel_row(&response, "openclaw", "slack/T1FIXTURE");
7064        assert_eq!(unlinked["enabled"], false);
7065        assert_eq!(
7066            unlinked["configured"], false,
7067            "an account with no credential key is not configured"
7068        );
7069        // A single-account channel keeps its own name and names its account
7070        // inline.
7071        let telegram = channel_row(&response, "openclaw", "telegram");
7072        assert_eq!(telegram["account"], "hermes-fixture-bot");
7073        assert_eq!(telegram["configured"], true);
7074
7075        // `status` is never claimed from a config file.
7076        for row in response["result"]["channels"].as_array().unwrap() {
7077            assert_eq!(row["status"], "unknown", "{row}");
7078        }
7079    }
7080
7081    /// dev/01: no field of any emitted row carries a credential. The fixture
7082    /// homes hold four FAKE credential strings; a row that leaked one — as a
7083    /// value, an account label, or a name — fails here.
7084    #[test]
7085    fn channels_rows_never_carry_a_fixture_secret() {
7086        let secrets = [
7087            "FAKE-TOKEN-DO-NOT-EMIT",
7088            "FAKE-API-SERVER-KEY-DO-NOT-EMIT",
7089            "FAKE-SLACK-BOT-TOKEN-DO-NOT-EMIT",
7090            "FAKE-SLACK-APP-TOKEN-DO-NOT-EMIT",
7091            "FAKE-TELEGRAM-TOKEN-DO-NOT-EMIT",
7092        ];
7093        // The strings really are in the fixtures, so this test can fail.
7094        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7095        let raw = format!(
7096            "{}{}",
7097            std::fs::read_to_string(fixtures.join("hermes_home/config.yaml")).unwrap(),
7098            std::fs::read_to_string(fixtures.join("openclaw_home/openclaw.json")).unwrap(),
7099        );
7100        for secret in secrets {
7101            assert!(raw.contains(secret), "fixture no longer holds `{secret}`");
7102        }
7103
7104        let emitted = serde_json::to_string(&channels_list(None)["result"]).unwrap();
7105        for secret in secrets {
7106            assert!(
7107                !emitted.contains(secret),
7108                "`{secret}` leaked into a channel row: {emitted}"
7109            );
7110        }
7111        // Belt and braces: no row FIELD is credential-shaped either, so a
7112        // future field cannot smuggle one past the literal scan.
7113        for row in channels_list(None)["result"]["channels"]
7114            .as_array()
7115            .unwrap()
7116        {
7117            for key in row.as_object().unwrap().keys() {
7118                let key = key.to_ascii_lowercase();
7119                assert!(
7120                    !["token", "key", "secret", "password", "credential"]
7121                        .iter()
7122                        .any(|marker| key.ends_with(marker)),
7123                    "`{key}` is a credential-shaped field on a channel row"
7124                );
7125            }
7126        }
7127    }
7128
7129    /// `status` answers one row by name, and refuses an unknown one.
7130    #[test]
7131    fn channels_status_reads_one_row_by_name() {
7132        let mut service = HarnessSessionService::new();
7133        let got = service.handle(request(
7134            1,
7135            "harness.v1.channels.status",
7136            json!({
7137                "harness": "openclaw",
7138                "name": "slack/T0FIXTURE",
7139                "homes": profile_fixture_homes(),
7140            }),
7141        ));
7142        assert_eq!(got["result"]["channel"]["kind"], "slack");
7143        assert_eq!(got["result"]["channel"]["account"], "T0FIXTURE");
7144        assert_eq!(got["result"]["channel"]["status"], "unknown");
7145
7146        let missing = service.handle(request(
7147            2,
7148            "harness.v1.channels.status",
7149            json!({
7150                "harness": "openclaw",
7151                "name": "no-such-channel",
7152                "homes": profile_fixture_homes(),
7153            }),
7154        ));
7155        assert_eq!(missing["error"]["code"], -32602, "{missing}");
7156    }
7157
7158    /// A harness with no channel concept fails with `UnsupportedAction`,
7159    /// never a silent empty list — Claude Code included, because its channels
7160    /// are MCP-protocol declarations no config file names.
7161    #[test]
7162    fn channels_refuse_harnesses_without_the_concept() {
7163        let response = channels_list(Some("claude-code"));
7164        assert_eq!(response["error"]["code"], -32020, "{response}");
7165        let codex = channels_list(Some("codex"));
7166        assert_eq!(codex["error"]["code"], -32020, "{codex}");
7167    }
7168
7169    /// The harness filter restricts the rows rather than being ignored.
7170    #[test]
7171    fn channels_list_filters_by_harness() {
7172        let response = channels_list(Some("openclaw"));
7173        let rows = response["result"]["channels"].as_array().unwrap();
7174        assert!(!rows.is_empty(), "{response}");
7175        assert!(
7176            rows.iter().all(|row| row["harness"] == "openclaw"),
7177            "harness filter leaked: {response}"
7178        );
7179    }
7180
7181    /// Both methods are advertised, so a client discovers them from
7182    /// `harness.v1.capabilities` rather than from documentation.
7183    #[test]
7184    fn channels_methods_are_advertised() {
7185        let mut service = HarnessSessionService::new();
7186        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
7187        let methods = response["result"]["methods"].as_array().unwrap();
7188        for method in ["harness.v1.channels.list", "harness.v1.channels.status"] {
7189            assert!(
7190                methods.iter().any(|entry| entry == method),
7191                "{method} is not advertised"
7192            );
7193        }
7194    }
7195
7196    /// The UI story renders REAL rows: this writes the discovery response the
7197    /// two assertions above pin into the fixture the Storybook
7198    /// `Compositions/Universal nouns` stories import, and fails when the
7199    /// committed copy has drifted from what the service now answers.
7200    #[test]
7201    fn orch6_story_fixture_matches_the_live_discovery_response() {
7202        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7203            .join("../../sdk/ui/stories/fixtures/hermes-discovery.json");
7204        let mut result = hermes_discovery(hermes_query());
7205        // `updated_at_ms` is derived from the fixture's own stored timestamps,
7206        // so the whole response is deterministic; drop only the cursor, which
7207        // is pagination state rather than a session fact.
7208        result.as_object_mut().unwrap().remove("next_cursor");
7209        let rendered = format!("{}\n", serde_json::to_string_pretty(&result).unwrap());
7210        if std::env::var_os("SUPERCODE_UPDATE_FIXTURES").is_some() {
7211            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
7212            std::fs::write(&path, &rendered).unwrap();
7213        }
7214        let committed = std::fs::read_to_string(&path).unwrap_or_default();
7215        assert_eq!(
7216            committed, rendered,
7217            "sdk/ui/stories/fixtures/hermes-discovery.json is stale — \
7218             re-run with SUPERCODE_UPDATE_FIXTURES=1"
7219        );
7220    }
7221
7222    fn pi_locator() -> SessionLocator {
7223        SessionLocator {
7224            harness: HarnessId::from(HarnessId::PI),
7225            session_id: "1e6f2a3b-0000-4000-8000-000000000001".into(),
7226            storage: StorageLocator::File {
7227                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7228                    .join("tests/fixtures/pi_session.jsonl"),
7229            },
7230        }
7231    }
7232
7233    fn opencode_locator() -> SessionLocator {
7234        let session_id = "ses_fixtureAAAAAAAAAAAAAAA1";
7235        SessionLocator {
7236            harness: HarnessId::from(HarnessId::OPENCODE),
7237            session_id: session_id.into(),
7238            storage: StorageLocator::Sqlite {
7239                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7240                    .join("tests/fixtures/opencode_fixture/opencode.db"),
7241                selector: session_id.into(),
7242            },
7243        }
7244    }
7245
7246    fn grok_locator() -> SessionLocator {
7247        SessionLocator {
7248            harness: HarnessId::from(HarnessId::GROK),
7249            session_id: "73c09283-4b33-41fa-90f1-0bcb0f7be523".into(),
7250            storage: StorageLocator::File {
7251                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7252                    .join("tests/fixtures/grok_session/chat_history.jsonl"),
7253            },
7254        }
7255    }
7256
7257    // ---- ORCH-11: `harness.v1.skills.list` -------------------------------
7258
7259    fn fixture_homes() -> Value {
7260        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7261        json!({
7262            "claude_code": fixtures.join("__absent__"),
7263            "codex": fixtures.join("__absent__"),
7264            "opencode": fixtures.join("__absent__"),
7265            "pi": fixtures.join("__absent__"),
7266            "agents": fixtures.join("__absent__"),
7267            "hermes": fixtures.join("hermes_home"),
7268            "openclaw": fixtures.join("openclaw_home"),
7269        })
7270    }
7271
7272    #[test]
7273    fn preview_search_uses_the_discovery_rpc_and_refuses_live_subscription() {
7274        let root = std::env::temp_dir().join(format!(
7275            "supercode-preview-rpc-{}-{}",
7276            std::process::id(),
7277            std::time::SystemTime::now()
7278                .duration_since(std::time::UNIX_EPOCH)
7279                .unwrap()
7280                .as_nanos()
7281        ));
7282        std::fs::create_dir_all(&root).unwrap();
7283        for id in ["first", "second"] {
7284            std::fs::write(root.join(format!("{id}.jsonl")), format!("{}\n{}\n",
7285                json!({"type": "session_meta", "payload": {"id": id, "cwd": "/workspace"}}),
7286                json!({"type": "event_msg", "payload": {"type": "agent_message", "message": "NEBULA result"}}),
7287            )).unwrap();
7288        }
7289        let mut service = HarnessSessionService::new();
7290        let query = json!({
7291            "harnesses": ["codex"], "homes": {"codex": root},
7292            "query": "nebula", "search_previews": true, "limit": 1
7293        });
7294        let first = service.handle(request(1, "harness.v1.sessions.discover", query.clone()));
7295        assert!(first.get("error").is_none(), "{first}");
7296        assert_eq!(first["result"]["receipt"]["searched_previews"], true);
7297        assert_eq!(first["result"]["receipt"]["total_matched"], 2);
7298        let mut next_query = query.clone();
7299        next_query["cursor"] = first["result"]["next_cursor"].clone();
7300        let next = service.handle(request(2, "harness.v1.sessions.discover", next_query));
7301        assert_eq!(next["result"]["receipt"]["returned"], 1);
7302        assert_eq!(next["result"]["receipt"]["total_matched"], 2);
7303        assert_eq!(next["result"]["receipt"]["truncated"], false);
7304        assert_ne!(
7305            first["result"]["sessions"][0]["locator"],
7306            next["result"]["sessions"][0]["locator"]
7307        );
7308        let refused = service.handle(request(3, "harness.v1.sessions.index.subscribe", query));
7309        assert!(
7310            refused["error"]["message"]
7311                .as_str()
7312                .unwrap()
7313                .contains("use sessions.discover"),
7314            "{refused}"
7315        );
7316        std::fs::remove_dir_all(root).unwrap();
7317    }
7318
7319    #[test]
7320    fn session_index_resize_preserves_subscription_and_rejects_invalid_requests() {
7321        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.sessions.index.resize"));
7322        let root = std::env::temp_dir().join(format!(
7323            "supercode-index-rpc-{}-{}",
7324            std::process::id(),
7325            std::time::SystemTime::now()
7326                .duration_since(std::time::UNIX_EPOCH)
7327                .unwrap()
7328                .as_nanos()
7329        ));
7330        std::fs::create_dir_all(&root).unwrap();
7331        for id in ["first", "second"] {
7332            std::fs::write(root.join(format!("{id}.jsonl")), format!(
7333                "{{\"type\":\"session_meta\",\"payload\":{{\"id\":\"{id}\",\"cwd\":\"/workspace\"}}}}\n"
7334            )).unwrap();
7335        }
7336        let mut service = HarnessSessionService::new();
7337        let opened = service.handle(request(
7338            1,
7339            "harness.v1.sessions.index.subscribe",
7340            json!({
7341                "harnesses": ["codex"], "homes": { "codex": root }, "limit": 1
7342            }),
7343        ));
7344        assert!(opened.get("error").is_none(), "{opened:#}");
7345        let subscription = opened["result"]["subscription"]
7346            .as_str()
7347            .unwrap()
7348            .to_owned();
7349        assert_eq!(opened["result"]["initial"].as_array().unwrap().len(), 1);
7350        for params in [
7351            json!({"subscription": subscription, "limit": 0}),
7352            json!({"subscription": subscription, "limit": 2049}),
7353            json!({"subscription": subscription, "limit": 2, "cursor": "not-allowed"}),
7354            json!({"subscription": "unknown", "limit": 2}),
7355        ] {
7356            let rejected = service.handle(request(2, "harness.v1.sessions.index.resize", params));
7357            assert_eq!(rejected["error"]["code"], -32602, "{rejected:#}");
7358        }
7359        for (limit, revision) in [(1, 1), (2, 2), (2, 2), (1, 3)] {
7360            let response = service.handle(request(
7361                3,
7362                "harness.v1.sessions.index.resize",
7363                json!({
7364                    "subscription": subscription, "limit": limit
7365                }),
7366            ));
7367            assert!(response.get("error").is_none(), "{response:#}");
7368            assert_eq!(response["result"]["subscription"], subscription);
7369            assert_eq!(response["result"]["revision"], revision);
7370            assert_eq!(
7371                response["result"]["initial"].as_array().unwrap().len(),
7372                limit
7373            );
7374            assert_eq!(response["result"]["receipt"]["total_matched"], 2);
7375            assert_eq!(service.index_subscriptions.len(), 1);
7376        }
7377        let removed = service.handle(request(
7378            4,
7379            "harness.v1.sessions.index.unsubscribe",
7380            json!({
7381                "subscription": subscription
7382            }),
7383        ));
7384        assert_eq!(removed["result"]["removed"], true);
7385        let stale = service.handle(request(
7386            5,
7387            "harness.v1.sessions.index.resize",
7388            json!({
7389                "subscription": subscription, "limit": 1
7390            }),
7391        ));
7392        assert_eq!(stale["error"]["code"], -32602);
7393        drop(service);
7394        std::fs::remove_dir_all(root).unwrap();
7395    }
7396
7397    fn skills_rows(params: Value) -> Vec<Value> {
7398        let response =
7399            HarnessSessionService::new().handle(request(1, "harness.v1.skills.list", params));
7400        assert!(response.get("error").is_none(), "{response:#}");
7401        response["result"].as_array().cloned().unwrap_or_default()
7402    }
7403
7404    /// The uniform row over two harnesses at once, from the harnesses' own
7405    /// skill roots: name, harness, scope, location, description, version.
7406    #[test]
7407    fn skills_list_reads_the_hermes_and_openclaw_roots() {
7408        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7409        let rows = skills_rows(json!({
7410            "homes": fixture_homes(),
7411            "cwd": fixtures.join("hermes_home"),
7412        }));
7413        let arxiv = rows
7414            .iter()
7415            .find(|row| row["name"] == json!("arxiv-search"))
7416            .unwrap_or_else(|| panic!("no arxiv row in {rows:#?}"));
7417        assert_eq!(arxiv["harness"], json!(HarnessId::HERMES));
7418        assert_eq!(arxiv["scope"], json!("user"));
7419        assert_eq!(arxiv["version"], json!("1.4.0"));
7420        assert!(arxiv["location"]
7421            .as_str()
7422            .unwrap()
7423            .ends_with("hermes_home/skills/research/arxiv"));
7424
7425        // A directory with no SKILL.md still lists, by directory name.
7426        let bare = rows
7427            .iter()
7428            .find(|row| row["name"] == json!("bare-skill"))
7429            .unwrap_or_else(|| panic!("no bare-skill row in {rows:#?}"));
7430        assert_eq!(bare["enabled"], json!(null));
7431        assert!(bare.get("description").is_none());
7432
7433        let demo = rows
7434            .iter()
7435            .find(|row| row["name"] == json!("clawhub-demo"))
7436            .unwrap_or_else(|| panic!("no clawhub-demo row in {rows:#?}"));
7437        assert_eq!(demo["harness"], json!(HarnessId::OPENCLAW));
7438        assert_eq!(demo["scope"], json!("managed"));
7439        assert_eq!(demo["enabled"], json!(false));
7440    }
7441
7442    /// Both filters select against the same rows.
7443    #[test]
7444    fn skills_list_filters_by_harness_and_scope() {
7445        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7446        let hermes = skills_rows(json!({
7447            "homes": fixture_homes(),
7448            "cwd": fixtures.join("hermes_home"),
7449            "harness": HarnessId::HERMES,
7450        }));
7451        assert!(!hermes.is_empty());
7452        assert!(hermes
7453            .iter()
7454            .all(|row| row["harness"] == json!(HarnessId::HERMES)));
7455
7456        let managed = skills_rows(json!({
7457            "homes": fixture_homes(),
7458            "cwd": fixtures.join("openclaw_home"),
7459            "harness": HarnessId::OPENCLAW,
7460            "scope": "managed",
7461        }));
7462        assert_eq!(managed.len(), 1, "{managed:#?}");
7463        assert_eq!(managed[0]["name"], json!("clawhub-demo"));
7464
7465        let bundled = skills_rows(json!({
7466            "homes": fixture_homes(),
7467            "cwd": fixtures.join("openclaw_home"),
7468            "harness": HarnessId::OPENCLAW,
7469            "scope": "bundled",
7470        }));
7471        assert!(bundled.is_empty(), "{bundled:#?}");
7472    }
7473
7474    /// A harness supercode has no skills root for is refused by name, not
7475    /// answered with an empty list.
7476    #[test]
7477    fn skills_list_refuses_an_unknown_harness() {
7478        let response = HarnessSessionService::new().handle(request(
7479            1,
7480            "harness.v1.skills.list",
7481            json!({"harness": "not-a-harness", "homes": fixture_homes()}),
7482        ));
7483        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7484        assert!(response["error"]["message"]
7485            .as_str()
7486            .unwrap()
7487            .contains("not-a-harness"));
7488    }
7489
7490    /// The method is advertised, and its SDK operation resolves it.
7491    #[test]
7492    fn skills_list_is_an_advertised_method_and_sdk_operation() {
7493        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.list"));
7494        assert_eq!(
7495            SdkOperation::from_method("harness.v1.skills.list"),
7496            Some(SdkOperation::SkillsList)
7497        );
7498    }
7499
7500    // ---- ORCH-22: `harness.v1.skills.install|remove` ----------------------
7501
7502    /// Both controlled verbs are advertised and resolve to their operation.
7503    #[test]
7504    fn skills_install_and_remove_are_advertised_methods_and_sdk_operations() {
7505        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.install"));
7506        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.remove"));
7507        assert_eq!(
7508            SdkOperation::from_method("harness.v1.skills.install"),
7509            Some(SdkOperation::SkillsInstall)
7510        );
7511        assert_eq!(
7512            SdkOperation::from_method("harness.v1.skills.remove"),
7513            Some(SdkOperation::SkillsRemove)
7514        );
7515    }
7516
7517    /// The directory door, end to end over the RPC: a local package lands in
7518    /// Claude Code's own user root and the outcome carries the operation and
7519    /// the row the ORCH-11 loader reads back.
7520    #[test]
7521    fn skills_install_and_remove_drive_the_directory_door() {
7522        let root = std::env::temp_dir().join(format!(
7523            "supercode-orch22-rpc-{}-{}",
7524            std::process::id(),
7525            std::time::SystemTime::now()
7526                .duration_since(std::time::UNIX_EPOCH)
7527                .unwrap()
7528                .as_nanos()
7529        ));
7530        let source = root.join("probe-src");
7531        std::fs::create_dir_all(&source).unwrap();
7532        std::fs::write(
7533            source.join("SKILL.md"),
7534            "---\nname: orch22-rpc\ndescription: a probe\n---\nbody\n",
7535        )
7536        .unwrap();
7537        let homes = json!({
7538            "claude_code": root.join("claude_home"),
7539            "codex": root.join("__absent__"),
7540            "opencode": root.join("__absent__"),
7541            "pi": root.join("__absent__"),
7542            "hermes": root.join("__absent__"),
7543            "openclaw": root.join("__absent__"),
7544            "agents": root.join("__absent__"),
7545        });
7546
7547        let mut service = HarnessSessionService::new();
7548        let installed = service.handle(request(
7549            1,
7550            "harness.v1.skills.install",
7551            json!({
7552                "harness": HarnessId::CLAUDE_CODE,
7553                "source": source,
7554                "scope": "user",
7555                "cwd": root,
7556                "homes": homes,
7557            }),
7558        ));
7559        let result = &installed["result"];
7560        assert_eq!(result["name"], json!("orch22-rpc"), "{installed:#}");
7561        assert_eq!(result["verb"], json!("install"));
7562        assert!(result["ran"]
7563            .as_str()
7564            .is_some_and(|ran| ran.starts_with("cp -R ")));
7565        assert_eq!(result["skill"]["scope"], json!("user"));
7566
7567        let removed = service.handle(request(
7568            2,
7569            "harness.v1.skills.remove",
7570            json!({
7571                "harness": HarnessId::CLAUDE_CODE,
7572                "name": "orch22-rpc",
7573                "scope": "user",
7574                "cwd": root,
7575                "homes": homes,
7576            }),
7577        ));
7578        assert_eq!(removed["result"]["removed"], json!(true), "{removed:#}");
7579        assert!(!root.join("claude_home/skills/orch22-rpc").exists());
7580        std::fs::remove_dir_all(&root).ok();
7581    }
7582
7583    /// OpenClaw publishes no `skills remove` at the pin, so the uniform verb
7584    /// refuses with UnsupportedAction instead of deleting files itself.
7585    #[test]
7586    fn skills_remove_refuses_openclaw_at_the_pin() {
7587        let response = HarnessSessionService::new().handle(request(
7588            1,
7589            "harness.v1.skills.remove",
7590            json!({"harness": HarnessId::OPENCLAW, "name": "clawhub-demo"}),
7591        ));
7592        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7593        assert!(response["error"]["message"]
7594            .as_str()
7595            .unwrap()
7596            .contains("no `skills remove` verb"));
7597    }
7598
7599    /// A harness with no skills root at all is refused by name, with the
7600    /// same sentence `skills.list` gives it.
7601    #[test]
7602    fn skills_install_refuses_a_harness_without_a_skills_root() {
7603        let response = HarnessSessionService::new().handle(request(
7604            1,
7605            "harness.v1.skills.install",
7606            json!({"harness": "not-a-harness", "source": "/tmp/x"}),
7607        ));
7608        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7609        assert!(response["error"]["message"]
7610            .as_str()
7611            .unwrap()
7612            .contains("not-a-harness"));
7613    }
7614
7615    // ---- ORCH-12: `harness.v1.memory.show|search` ------------------------
7616
7617    /// `HarnessHomes` for the committed fixture homes. Every root a test does
7618    /// not name is pinned at an absent path, so a read can never fall through
7619    /// to this machine's real harness homes. Note `hermes` is the `state.db`
7620    /// PATH (its parent is HERMES_HOME) and `claude_code` is the `projects`
7621    /// directory — the same contract discovery uses.
7622    fn memory_homes() -> Value {
7623        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7624        json!({
7625            "claude_code": fixtures.join("__absent__"),
7626            "codex": fixtures.join("__absent__"),
7627            "opencode": fixtures.join("__absent__"),
7628            "pi": fixtures.join("__absent__"),
7629            "grok": fixtures.join("__absent__"),
7630            "gemini": fixtures.join("__absent__"),
7631            "goose": fixtures.join("__absent__"),
7632            "supercode": fixtures.join("__absent__"),
7633            "hermes": fixtures.join("hermes_home/state.db"),
7634            "openclaw": fixtures.join("openclaw_home"),
7635        })
7636    }
7637
7638    fn memory_call_ok(method: &str, params: Value, key: &str) -> Vec<Value> {
7639        let response = HarnessSessionService::new().handle(request(1, method, params));
7640        assert!(response.get("error").is_none(), "{response:#}");
7641        assert_eq!(response["result"]["schema"], json!("supercode.memory.v1"));
7642        response["result"][key]
7643            .as_array()
7644            .cloned()
7645            .unwrap_or_default()
7646    }
7647
7648    fn memory_documents(params: Value) -> Vec<Value> {
7649        memory_call_ok("harness.v1.memory.show", params, "documents")
7650    }
7651
7652    fn memory_matches(params: Value) -> Vec<Value> {
7653        memory_call_ok("harness.v1.memory.search", params, "matches")
7654    }
7655
7656    fn find_document<'a>(rows: &'a [Value], profile: &str, name: &str) -> &'a Value {
7657        rows.iter()
7658            .find(|row| row["profile"] == profile && row["name"] == name)
7659            .unwrap_or_else(|| panic!("no `{profile}` document `{name}` in {rows:#?}"))
7660    }
7661
7662    /// Hermes: the built-in `MEMORY.md`/`USER.md` pair and the `memories/`
7663    /// topic files, for HERMES_HOME itself and for every profile home.
7664    #[test]
7665    fn memory_show_reads_the_hermes_profile_homes() {
7666        let rows = memory_documents(json!({"harness": "hermes", "homes": memory_homes()}));
7667
7668        let notes = find_document(&rows, "default", "MEMORY.md");
7669        assert_eq!(notes["harness"], "hermes");
7670        assert_eq!(notes["scope"], "user");
7671        assert!(notes["size"].as_u64().unwrap() > 0);
7672        assert!(notes["updated_at"].is_string(), "{notes:#?}");
7673        // The default answer previews the head and never the whole body.
7674        assert!(notes.get("content").is_none(), "{notes:#?}");
7675        assert_eq!(notes["truncated"], true);
7676        assert_eq!(notes["preview"].as_array().unwrap().len(), 5);
7677
7678        let user = find_document(&rows, "default", "USER.md");
7679        assert_eq!(user["scope"], "user");
7680        assert!(user["preview"]
7681            .as_array()
7682            .unwrap()
7683            .iter()
7684            .any(|line| line.as_str().unwrap().contains("neovim")));
7685
7686        let topic = find_document(&rows, "default", "memories/2026-09-01-notes.md");
7687        assert!(topic["path"]
7688            .as_str()
7689            .unwrap()
7690            .ends_with("hermes_home/memories/2026-09-01-notes.md"));
7691
7692        // Profile mode points HERMES_HOME at `<root>/profiles/<name>`.
7693        let coder = find_document(&rows, "coder", "MEMORY.md");
7694        assert_eq!(coder["scope"], "profile");
7695        assert!(coder["path"]
7696            .as_str()
7697            .unwrap()
7698            .ends_with("hermes_home/profiles/coder/MEMORY.md"));
7699    }
7700
7701    /// `full` is the only way a body crosses the wire, and `profile` narrows
7702    /// the read to one home.
7703    #[test]
7704    fn memory_show_returns_bodies_only_under_full_and_narrows_by_profile() {
7705        let rows = memory_documents(json!({
7706            "harness": "hermes",
7707            "profile": "coder",
7708            "full": true,
7709            "homes": memory_homes(),
7710        }));
7711        assert!(
7712            rows.iter().all(|row| row["profile"] == "coder"),
7713            "{rows:#?}"
7714        );
7715        let coder = find_document(&rows, "coder", "MEMORY.md");
7716        assert!(coder["content"]
7717            .as_str()
7718            .expect("full returns the body")
7719            .contains("anthropic/claude-opus-4-8"));
7720    }
7721
7722    /// OpenClaw: memory-core's files under each agent's workspace —
7723    /// `<state>/workspace` for the default agent, `<state>/workspace-<id>`
7724    /// for any other.
7725    #[test]
7726    fn memory_show_reads_the_openclaw_agent_workspaces() {
7727        let rows = memory_documents(json!({"harness": "openclaw", "homes": memory_homes()}));
7728
7729        let main = find_document(&rows, "main", "MEMORY.md");
7730        assert_eq!(main["scope"], "agent");
7731        assert!(main["path"]
7732            .as_str()
7733            .unwrap()
7734            .ends_with("openclaw_home/workspace/MEMORY.md"));
7735
7736        let topic = find_document(&rows, "main", "memory/2026-09-01-standup.md");
7737        assert!(topic["path"]
7738            .as_str()
7739            .unwrap()
7740            .ends_with("openclaw_home/workspace/memory/2026-09-01-standup.md"));
7741
7742        let design = find_document(&rows, "design", "MEMORY.md");
7743        assert!(design["path"]
7744            .as_str()
7745            .unwrap()
7746            .ends_with("openclaw_home/workspace-design/MEMORY.md"));
7747    }
7748
7749    /// Claude Code: the auto-memory directory of the project the working tree
7750    /// belongs to, keyed by the enclosing git repository.
7751    #[test]
7752    fn memory_show_reads_a_claude_code_project_auto_memory_directory() {
7753        let scratch = std::env::temp_dir().join(format!(
7754            "supercode-orch12-cc-{}-{}",
7755            std::process::id(),
7756            std::time::SystemTime::now()
7757                .duration_since(std::time::UNIX_EPOCH)
7758                .unwrap()
7759                .as_nanos()
7760        ));
7761        let project = scratch.join("repo");
7762        std::fs::create_dir_all(project.join(".git")).unwrap();
7763        // Auto-memory is shared across a repo's worktrees, so a nested
7764        // working directory must resolve to the repo's own project dir.
7765        let worktree = project.join("crates/harness");
7766        std::fs::create_dir_all(&worktree).unwrap();
7767        let slug: String = project
7768            .to_string_lossy()
7769            .chars()
7770            .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
7771            .collect();
7772        let projects = scratch.join("claude/projects");
7773        let memory = projects.join(&slug).join("memory");
7774        std::fs::create_dir_all(&memory).unwrap();
7775        std::fs::write(
7776            memory.join("MEMORY.md"),
7777            "# index\n- [build box](build-box.md) — the pinned harnesses\n",
7778        )
7779        .unwrap();
7780        std::fs::write(
7781            memory.join("build-box.md"),
7782            "hermes 0.21.0 and openclaw 2026.7.1-2 are the pins\n",
7783        )
7784        .unwrap();
7785
7786        let mut homes = memory_homes();
7787        homes["claude_code"] = json!(projects);
7788        let rows = memory_documents(json!({
7789            "harness": "claude-code",
7790            "cwd": worktree,
7791            "homes": homes,
7792        }));
7793        let index = find_document(&rows, &slug, "MEMORY.md");
7794        assert_eq!(index["harness"], "claude-code");
7795        assert_eq!(index["scope"], "project");
7796        let topic = find_document(&rows, &slug, "build-box.md");
7797        assert!(topic["preview"]
7798            .as_array()
7799            .unwrap()
7800            .iter()
7801            .any(|line| line.as_str().unwrap().contains("2026.7.1-2")));
7802
7803        let hits = memory_matches(json!({
7804            "harness": "claude-code",
7805            "query": "pinned harnesses",
7806            "cwd": worktree,
7807            "homes": homes,
7808        }));
7809        assert_eq!(hits.len(), 1, "{hits:#?}");
7810        assert_eq!(hits[0]["name"], "MEMORY.md");
7811        assert_eq!(hits[0]["line"], 2);
7812
7813        let _ = std::fs::remove_dir_all(&scratch);
7814    }
7815
7816    /// A config-less OpenClaw install declares no default agent, but
7817    /// memory-core still resolves ONE agent to the default `workspace`
7818    /// directory — the same `main`-then-first convention the profile rows
7819    /// use. Measured against `openclaw memory status` on the pinned CLI
7820    /// (`docs/interop/research/orch12-memory-receipt-2026-09-03.json`).
7821    #[test]
7822    fn memory_show_resolves_the_default_workspace_without_an_openclaw_config() {
7823        let state = std::env::temp_dir().join(format!(
7824            "supercode-orch12-oc-{}-{}",
7825            std::process::id(),
7826            std::time::SystemTime::now()
7827                .duration_since(std::time::UNIX_EPOCH)
7828                .unwrap()
7829                .as_nanos()
7830        ));
7831        // No `openclaw.json`: only the agent home the gateway creates.
7832        std::fs::create_dir_all(state.join("agents/main/agent")).unwrap();
7833        std::fs::create_dir_all(state.join("workspace")).unwrap();
7834        std::fs::write(
7835            state.join("workspace/MEMORY.md"),
7836            "the gateway websocket needs credentials\n",
7837        )
7838        .unwrap();
7839
7840        let mut homes = memory_homes();
7841        homes["openclaw"] = json!(state);
7842        let rows = memory_documents(json!({"harness": "openclaw", "homes": homes}));
7843        assert_eq!(rows.len(), 1, "{rows:#?}");
7844        let row = find_document(&rows, "main", "MEMORY.md");
7845        assert_eq!(row["scope"], "agent");
7846        assert!(row["path"]
7847            .as_str()
7848            .unwrap()
7849            .ends_with("workspace/MEMORY.md"));
7850
7851        let _ = std::fs::remove_dir_all(&state);
7852    }
7853
7854    /// Search is a plain scan over the same documents: a hit carries the
7855    /// path, line and excerpt; a miss is an empty list, not an error.
7856    #[test]
7857    fn memory_search_reports_hits_by_line_and_misses_as_empty() {
7858        let hit = memory_matches(json!({
7859            "harness": "hermes",
7860            "query": "NEOVIM",
7861            "homes": memory_homes(),
7862        }));
7863        assert_eq!(hit.len(), 1, "{hit:#?}");
7864        assert_eq!(hit[0]["harness"], "hermes");
7865        assert_eq!(hit[0]["name"], "USER.md");
7866        assert_eq!(hit[0]["scope"], "user");
7867        assert_eq!(hit[0]["line"], 5);
7868        assert!(hit[0]["excerpt"].as_str().unwrap().contains("neovim"));
7869
7870        // A regular expression reaches the same lines.
7871        let regex = memory_matches(json!({
7872            "harness": "hermes",
7873            "query": "neo(vim|vi)",
7874            "regex": true,
7875            "homes": memory_homes(),
7876        }));
7877        assert_eq!(regex.len(), 1, "{regex:#?}");
7878
7879        let miss = memory_matches(json!({
7880            "harness": "hermes",
7881            "query": "no-memory-line-says-this",
7882            "homes": memory_homes(),
7883        }));
7884        assert!(miss.is_empty(), "{miss:#?}");
7885    }
7886
7887    /// The uniform-verb contract: a harness with no memory store at the pin
7888    /// is refused by name, and `session` only selects a Claude Code project.
7889    #[test]
7890    fn memory_refuses_harnesses_without_a_store_and_misplaced_session_scoping() {
7891        for method in ["harness.v1.memory.show", "harness.v1.memory.search"] {
7892            let response = HarnessSessionService::new().handle(request(
7893                1,
7894                method,
7895                json!({"harness": "codex", "query": "anything", "homes": memory_homes()}),
7896            ));
7897            assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7898            assert!(response["error"]["message"]
7899                .as_str()
7900                .unwrap()
7901                .contains("codex"));
7902        }
7903
7904        let response = HarnessSessionService::new().handle(request(
7905            1,
7906            "harness.v1.memory.show",
7907            json!({"harness": "hermes", "session": "abc", "homes": memory_homes()}),
7908        ));
7909        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7910
7911        // `harness` is not optional: memory documents are the user's prose.
7912        let response = HarnessSessionService::new().handle(request(
7913            1,
7914            "harness.v1.memory.show",
7915            json!({"homes": memory_homes()}),
7916        ));
7917        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7918    }
7919
7920    /// Both methods are advertised, and their SDK operations resolve them.
7921    #[test]
7922    fn memory_methods_are_advertised_and_map_to_sdk_operations() {
7923        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.show"));
7924        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.search"));
7925        assert_eq!(
7926            SdkOperation::from_method("harness.v1.memory.show"),
7927            Some(SdkOperation::MemoryShow)
7928        );
7929        assert_eq!(
7930            SdkOperation::from_method("harness.v1.memory.search"),
7931            Some(SdkOperation::MemorySearch)
7932        );
7933    }
7934
7935    // ---- ORCH-9: `harness.v1.approvals.list` -----------------------------
7936
7937    /// A runtime that raises one protocol request and then goes quiet, so a
7938    /// single poll delivers the request without closing the connection.
7939    struct RequestingRuntime {
7940        handle: RuntimeHandle,
7941        events: std::collections::VecDeque<HarnessEvent>,
7942        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7943    }
7944
7945    #[async_trait]
7946    impl RuntimeConnection for RequestingRuntime {
7947        fn handle(&self) -> &RuntimeHandle {
7948            &self.handle
7949        }
7950
7951        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
7952            unreachable!("this runtime only raises requests")
7953        }
7954
7955        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
7956            match self.events.pop_front() {
7957                Some(event) => Ok(Some(event)),
7958                // Quiet, not closed: `poll_sdk_events` times out and leaves
7959                // the connection open, the way a runtime blocked on a
7960                // permission request behaves.
7961                None => std::future::pending().await,
7962            }
7963        }
7964
7965        async fn interrupt(&mut self) -> crate::Result<()> {
7966            Ok(())
7967        }
7968
7969        async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
7970            // Both halves are recorded: ORCH-20 has to prove not just that the
7971            // right request was answered but that the door received its own
7972            // reply envelope.
7973            self.answered
7974                .lock()
7975                .unwrap_or_else(std::sync::PoisonError::into_inner)
7976                .push(json!({"request_id": request_id, "response": response}));
7977            Ok(())
7978        }
7979
7980        async fn close(&mut self) -> crate::Result<()> {
7981            Ok(())
7982        }
7983    }
7984
7985    fn requesting_runtime(
7986        harness: &str,
7987        events: Vec<HarnessEvent>,
7988        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7989    ) -> Box<dyn RuntimeConnection> {
7990        requesting_runtime_named(harness, "hermes-live-session", events, answered)
7991    }
7992
7993    fn requesting_runtime_named(
7994        harness: &str,
7995        runtime_id: &str,
7996        events: Vec<HarnessEvent>,
7997        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7998    ) -> Box<dyn RuntimeConnection> {
7999        Box::new(RequestingRuntime {
8000            handle: RuntimeHandle {
8001                harness: HarnessId::from(harness),
8002                runtime_id: runtime_id.into(),
8003                endpoint: RuntimeEndpoint::LocalProcess {
8004                    pid: None,
8005                    command: vec!["hermes-acp".into()],
8006                    protocol: "acp".into(),
8007                },
8008            },
8009            events: events.into(),
8010            answered,
8011        })
8012    }
8013
8014    fn permission_event(id: u64, title: &str) -> HarnessEvent {
8015        HarnessEvent {
8016            sequence: None,
8017            kind: "session/request_permission".into(),
8018            payload: json!({
8019                "jsonrpc": "2.0",
8020                "id": id,
8021                "method": "session/request_permission",
8022                "params": {
8023                    "sessionId": "hermes-live-session",
8024                    "toolCall": {"toolCallId": "call-1", "title": title, "kind": "execute"},
8025                    "options": [
8026                        {"optionId": "allow_once", "name": "Allow once", "kind": "allow_once"},
8027                        {"optionId": "allow_for_session", "name": "Allow for session", "kind": "allow_always"},
8028                        {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
8029                    ],
8030                },
8031            }),
8032        }
8033    }
8034
8035    fn approvals(service: &mut HarnessSessionService, params: Value) -> Value {
8036        let response = service.handle(request(1, "harness.v1.approvals.list", params));
8037        assert!(response.get("error").is_none(), "{response:#}");
8038        response["result"].clone()
8039    }
8040
8041    /// ORC-2 dev/01: the same uniform loop over the CLAUDE CODE door. The
8042    /// `can_use_tool` control request the CLI raises to its registered
8043    /// permission handler lists as one pending row, `approvals.resolve <id>
8044    /// allow_once` sends the `{behavior}` result the CLI accepts through
8045    /// `runtimes.respond`, and the row is gone. The frame is the one claude
8046    /// 2.1.258 wrote, transcribed from
8047    /// `docs/interop/research/orc2-claude-respond-receipt-2026-09-04.json`.
8048    #[tokio::test]
8049    async fn a_claude_code_permission_request_lists_and_resolves_on_the_uniform_door() {
8050        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8051        let mut service = HarnessSessionService::new();
8052        service.runtimes.insert(
8053            "runtime-cc".into(),
8054            requesting_runtime_named(
8055                HarnessId::CLAUDE_CODE,
8056                "claude-live-session",
8057                vec![HarnessEvent {
8058                    sequence: None,
8059                    kind: "control_request".into(),
8060                    payload: json!({
8061                        "type": "control_request",
8062                        "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
8063                        "request": {
8064                            "subtype": "can_use_tool",
8065                            "tool_name": "Bash",
8066                            "display_name": "Bash",
8067                            "input": {"command": "touch probe-artifact.txt"},
8068                            "tool_use_id": "toolu_mock_1",
8069                        },
8070                    }),
8071                }],
8072                answered.clone(),
8073            ),
8074        );
8075
8076        let notifications = service.poll_runtimes().await;
8077        assert_eq!(notifications.len(), 1, "{notifications:#?}");
8078
8079        let rows = approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}));
8080        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
8081        let row = &rows[0];
8082        assert_eq!(row["id"], "runtime-cc/053f8a2d-3445-4011-a259-4261b31c7326");
8083        assert_eq!(row["harness"], HarnessId::CLAUDE_CODE);
8084        assert_eq!(row["status"], "pending");
8085        assert_eq!(row["subject"], "Bash touch probe-artifact.txt");
8086        assert_eq!(row["runtime_id"], "claude-live-session");
8087        assert_eq!(
8088            row["options"]
8089                .as_array()
8090                .unwrap()
8091                .iter()
8092                .map(|option| option["id"].as_str().unwrap())
8093                .collect::<Vec<_>>(),
8094            vec!["allow", "deny"],
8095        );
8096
8097        let response = resolve(
8098            &mut service,
8099            json!({"id": row["id"], "decision": "allow_once"}),
8100        )
8101        .await;
8102        assert!(response.get("error").is_none(), "{response:#}");
8103        assert_eq!(response["result"]["option_id"], "allow");
8104        assert_eq!(
8105            answered
8106                .lock()
8107                .unwrap_or_else(std::sync::PoisonError::into_inner)
8108                .as_slice(),
8109            &[json!({
8110                "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
8111                "response": {"behavior": "allow"},
8112            })],
8113        );
8114        assert_eq!(
8115            approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}))
8116                .as_array()
8117                .map(Vec::len),
8118            Some(0),
8119        );
8120    }
8121
8122    /// dev/01: a live ACP permission request raised on a driven runtime is
8123    /// listable while the turn is blocked on it, and stops being listable
8124    /// the moment `runtimes.respond` answers it.
8125    #[tokio::test]
8126    async fn a_live_permission_request_lists_until_it_is_answered() {
8127        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8128        let mut service = HarnessSessionService::new();
8129        service.runtimes.insert(
8130            "runtime-1".into(),
8131            requesting_runtime(
8132                HarnessId::HERMES,
8133                vec![permission_event(7, "rm -rf build")],
8134                answered.clone(),
8135            ),
8136        );
8137
8138        let notifications = service.poll_runtimes().await;
8139        assert_eq!(notifications.len(), 1, "{notifications:#?}");
8140
8141        let rows = approvals(&mut service, json!({}));
8142        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
8143        let row = &rows[0];
8144        assert_eq!(row["id"], "runtime-1/7");
8145        assert_eq!(row["harness"], HarnessId::HERMES);
8146        assert_eq!(row["kind"], "live");
8147        assert_eq!(row["status"], "pending");
8148        assert_eq!(row["subject"], "rm -rf build");
8149        assert_eq!(row["session_id"], "hermes-live-session");
8150        assert_eq!(row["runtime_id"], "hermes-live-session");
8151        assert!(row["requested_at_ms"].as_i64().is_some(), "{row:#}");
8152        assert!(
8153            row["age_ms"].as_i64().is_some_and(|age| age >= 0),
8154            "{row:#}"
8155        );
8156        assert_eq!(
8157            row["options"]
8158                .as_array()
8159                .unwrap()
8160                .iter()
8161                .map(|option| option["id"].as_str().unwrap())
8162                .collect::<Vec<_>>(),
8163            vec!["allow_once", "allow_for_session", "deny"],
8164        );
8165
8166        // The filters select against the same rows.
8167        assert_eq!(
8168            approvals(&mut service, json!({"harness": HarnessId::HERMES}))
8169                .as_array()
8170                .map(Vec::len),
8171            Some(1),
8172        );
8173        assert_eq!(
8174            approvals(&mut service, json!({"session": "some-other-session"}))
8175                .as_array()
8176                .map(Vec::len),
8177            Some(0),
8178        );
8179
8180        let response = service
8181            .handle_async(request(
8182                2,
8183                "harness.v1.runtimes.respond",
8184                json!({
8185                    "connection": "runtime-1",
8186                    "request_id": 7,
8187                    "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8188                }),
8189            ))
8190            .await;
8191        assert!(response.get("error").is_none(), "{response:#}");
8192        assert_eq!(
8193            answered
8194                .lock()
8195                .unwrap_or_else(std::sync::PoisonError::into_inner)
8196                .as_slice(),
8197            &[json!({
8198                "request_id": 7,
8199                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8200            })],
8201        );
8202
8203        let rows = approvals(&mut service, json!({}));
8204        assert_eq!(rows.as_array().map(Vec::len), Some(0), "{rows:#}");
8205    }
8206
8207    /// dev/01: supercode's own queued subagent approvals list through the
8208    /// same door, carrying the outcome the record holds.
8209    #[test]
8210    fn queued_subagent_approvals_list_through_the_same_door() {
8211        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
8212            crate::subagents::QueuedApproval {
8213                child_agent_id: "child-7".into(),
8214                tool: "shell".into(),
8215                subject: Some("cargo publish --dry-run".into()),
8216                queued_at_ms: 1,
8217                outcome: None,
8218            },
8219            crate::subagents::QueuedApproval {
8220                child_agent_id: "child-8".into(),
8221                tool: "write_file".into(),
8222                subject: None,
8223                queued_at_ms: 2,
8224                outcome: Some(crate::subagents::QueuedApprovalOutcome::Denied),
8225            },
8226        ]));
8227        let mut service = HarnessSessionService::new();
8228        service.observe_subagent_approvals(queue);
8229
8230        let rows = approvals(&mut service, json!({}));
8231        assert_eq!(rows.as_array().map(Vec::len), Some(2), "{rows:#}");
8232        assert_eq!(rows[0]["id"], "supercode/subagent/child-7/1/0");
8233        assert_eq!(rows[0]["harness"], HarnessId::SUPERCODE);
8234        assert_eq!(rows[0]["status"], "pending");
8235        assert_eq!(rows[0]["subject"], "shell cargo publish --dry-run");
8236        assert_eq!(rows[1]["status"], "denied");
8237        assert!(rows[1]["options"].as_array().unwrap().is_empty());
8238
8239        // `--session` addresses a subagent row by its child agent id.
8240        let only = approvals(&mut service, json!({"session": "child-8"}));
8241        assert_eq!(only.as_array().map(Vec::len), Some(1), "{only:#}");
8242        assert_eq!(only[0]["id"], "supercode/subagent/child-8/2/1");
8243    }
8244
8245    /// The uniform-verb contract: an id whose runtime door cannot carry a
8246    /// protocol request is refused BY NAME rather than answered with an empty
8247    /// list. Since ORC-2 gave Claude Code a permission-response primitive
8248    /// every registered harness can carry one, so the refusal is exercised on
8249    /// an unknown id — and the registered ids are asserted to be accepted.
8250    #[test]
8251    fn approvals_list_refuses_a_harness_that_cannot_carry_a_request() {
8252        let response = HarnessSessionService::new().handle(request(
8253            1,
8254            "harness.v1.approvals.list",
8255            json!({"harness": "not-a-harness"}),
8256        ));
8257        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
8258        assert!(response["error"]["message"]
8259            .as_str()
8260            .unwrap()
8261            .contains("not-a-harness"));
8262        for harness in [HarnessId::CLAUDE_CODE, HarnessId::CODEX] {
8263            let response = HarnessSessionService::new().handle(request(
8264                1,
8265                "harness.v1.approvals.list",
8266                json!({"harness": harness}),
8267            ));
8268            assert!(response.get("error").is_none(), "{harness}: {response:#}");
8269        }
8270    }
8271
8272    /// The method is advertised, its SDK operation resolves it, and the
8273    /// registry reports the concept as observed for every harness whose
8274    /// runtime door can carry a request.
8275    #[test]
8276    fn approvals_list_is_an_advertised_method_and_an_observed_tier() {
8277        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.list"));
8278        assert_eq!(
8279            SdkOperation::from_method("harness.v1.approvals.list"),
8280            Some(SdkOperation::ApprovalsList)
8281        );
8282        let registry = harness_support_registry();
8283        for id in [
8284            HarnessId::HERMES,
8285            HarnessId::OPENCLAW,
8286            HarnessId::CODEX,
8287            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8288            // pending_request concept joins the other driven doors.
8289            HarnessId::CLAUDE_CODE,
8290        ] {
8291            let concept = registry
8292                .harnesses
8293                .iter()
8294                .find(|harness| harness.id.as_str() == id)
8295                .unwrap()
8296                .orchestration
8297                .concepts
8298                .iter()
8299                .find(|concept| concept.concept == "pending_request")
8300                .unwrap();
8301            assert_eq!(concept.observed, crate::ImplementationKind::BuiltIn, "{id}");
8302            assert!(concept
8303                .methods
8304                .iter()
8305                .any(|method| method == "harness.v1.approvals.list"));
8306        }
8307    }
8308
8309    // ---- ORCH-20: `harness.v1.approvals.resolve` -------------------------
8310
8311    async fn resolve(service: &mut HarnessSessionService, params: Value) -> Value {
8312        service
8313            .handle_async(request(3, "harness.v1.approvals.resolve", params))
8314            .await
8315    }
8316
8317    /// dev/01: the whole loop on a driven runtime — list one pending row,
8318    /// answer it by ROW ID with one uniform decision, and see it gone. The
8319    /// door receives its own ACP envelope carrying the option it enumerated.
8320    #[tokio::test]
8321    async fn a_listed_row_resolves_with_one_uniform_decision_and_then_is_gone() {
8322        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8323        let mut service = HarnessSessionService::new();
8324        service.runtimes.insert(
8325            "runtime-1".into(),
8326            requesting_runtime(
8327                HarnessId::HERMES,
8328                vec![permission_event(7, "rm -rf build")],
8329                answered.clone(),
8330            ),
8331        );
8332        service.poll_runtimes().await;
8333
8334        let rows = approvals(&mut service, json!({}));
8335        assert_eq!(rows[0]["id"], "runtime-1/7");
8336
8337        let response = resolve(
8338            &mut service,
8339            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8340        )
8341        .await;
8342        assert!(response.get("error").is_none(), "{response:#}");
8343        assert_eq!(
8344            response["result"],
8345            json!({
8346                "id": "runtime-1/7",
8347                "decision": "allow_once",
8348                "option_id": "allow_once",
8349                "resolved": true,
8350            }),
8351        );
8352        // The harness's own door was called with its own envelope.
8353        assert_eq!(
8354            answered
8355                .lock()
8356                .unwrap_or_else(std::sync::PoisonError::into_inner)
8357                .as_slice(),
8358            &[json!({
8359                "request_id": 7,
8360                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8361            })],
8362        );
8363        // And the row is gone, the same way `runtimes.respond` drops it.
8364        assert_eq!(
8365            approvals(&mut service, json!({})).as_array().map(Vec::len),
8366            Some(0),
8367        );
8368        // Answering it twice is an honest miss, not a silent success.
8369        let response = resolve(
8370            &mut service,
8371            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8372        )
8373        .await;
8374        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8375    }
8376
8377    /// dev/01: deny travels the same path and picks the option the request
8378    /// itself classified as a refusal.
8379    #[tokio::test]
8380    async fn deny_selects_the_requests_own_reject_option() {
8381        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8382        let mut service = HarnessSessionService::new();
8383        service.runtimes.insert(
8384            "runtime-1".into(),
8385            requesting_runtime(
8386                HarnessId::HERMES,
8387                vec![permission_event(11, "git push --force")],
8388                answered.clone(),
8389            ),
8390        );
8391        service.poll_runtimes().await;
8392
8393        let response = resolve(
8394            &mut service,
8395            json!({"id": "runtime-1/11", "decision": "deny"}),
8396        )
8397        .await;
8398        assert!(response.get("error").is_none(), "{response:#}");
8399        // `deny` is the optionId whose ACP `kind` is `reject_once`.
8400        assert_eq!(response["result"]["option_id"], "deny");
8401        assert_eq!(
8402            answered
8403                .lock()
8404                .unwrap_or_else(std::sync::PoisonError::into_inner)[0]["response"],
8405            json!({"outcome": {"outcome": "selected", "optionId": "deny"}}),
8406        );
8407        assert_eq!(
8408            approvals(&mut service, json!({})).as_array().map(Vec::len),
8409            Some(0),
8410        );
8411    }
8412
8413    /// dev/01: a decision this request does not offer is refused by name,
8414    /// listing the ones it does — never silently downgraded to a neighbour.
8415    #[tokio::test]
8416    async fn a_decision_the_request_does_not_offer_is_refused_with_the_offered_ones() {
8417        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8418        let mut service = HarnessSessionService::new();
8419        let mut event = permission_event(3, "rm -rf build");
8420        // A request offering only allow-once and deny, as hermes 0.21.0's
8421        // edit-approval layer raises one.
8422        event.payload["params"]["options"] = json!([
8423            {"optionId": "allow_once", "name": "Allow edit", "kind": "allow_once"},
8424            {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
8425        ]);
8426        service.runtimes.insert(
8427            "runtime-1".into(),
8428            requesting_runtime(HarnessId::HERMES, vec![event], answered.clone()),
8429        );
8430        service.poll_runtimes().await;
8431
8432        let response = resolve(
8433            &mut service,
8434            json!({"id": "runtime-1/3", "decision": "allow_always"}),
8435        )
8436        .await;
8437        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8438        let message = response["error"]["message"].as_str().unwrap();
8439        assert!(message.contains("allow_always"), "{message}");
8440        assert!(message.contains("allow_once, deny"), "{message}");
8441        // Nothing was sent, and the request is still waiting for an answer.
8442        assert!(answered
8443            .lock()
8444            .unwrap_or_else(std::sync::PoisonError::into_inner)
8445            .is_empty());
8446        assert_eq!(
8447            approvals(&mut service, json!({})).as_array().map(Vec::len),
8448            Some(1),
8449        );
8450    }
8451
8452    /// dev/01: supercode's own queued subagent row is addressable but not
8453    /// answerable through this door — it is the parent's audit copy of a
8454    /// request its own handler answers. Refused by name, never a no-op.
8455    #[tokio::test]
8456    async fn a_queued_subagent_row_is_refused_by_name_rather_than_silently_answered() {
8457        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
8458            crate::subagents::QueuedApproval {
8459                child_agent_id: "child-7".into(),
8460                tool: "shell".into(),
8461                subject: Some("cargo publish --dry-run".into()),
8462                queued_at_ms: 1,
8463                outcome: None,
8464            },
8465        ]));
8466        let mut service = HarnessSessionService::new();
8467        service.observe_subagent_approvals(queue.clone());
8468        let row = approvals(&mut service, json!({}))[0]["id"]
8469            .as_str()
8470            .unwrap()
8471            .to_string();
8472        assert_eq!(row, "supercode/subagent/child-7/1/0");
8473
8474        let response = resolve(&mut service, json!({"id": row, "decision": "allow_once"})).await;
8475        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8476        let message = response["error"]["message"].as_str().unwrap();
8477        assert!(message.contains("queued subagent record"), "{message}");
8478        assert!(message.contains("request"), "{message}");
8479        // The audit record is untouched: nothing pretended to answer it.
8480        assert!(queue
8481            .lock()
8482            .unwrap_or_else(std::sync::PoisonError::into_inner)[0]
8483            .outcome
8484            .is_none());
8485    }
8486
8487    /// An id nobody is holding, and a call that names no decision at all,
8488    /// both fail with a message that says why.
8489    #[tokio::test]
8490    async fn an_unknown_row_and_a_missing_decision_are_both_named() {
8491        let mut service = HarnessSessionService::new();
8492        let response = resolve(
8493            &mut service,
8494            json!({"id": "runtime-9/4", "decision": "deny"}),
8495        )
8496        .await;
8497        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8498        assert!(response["error"]["message"]
8499            .as_str()
8500            .unwrap()
8501            .contains("runtime-9/4"));
8502
8503        let response = resolve(&mut service, json!({"id": "runtime-9/4"})).await;
8504        let message = response["error"]["message"].as_str().unwrap();
8505        assert!(
8506            message.contains("allow_once | allow_always | deny"),
8507            "{message}"
8508        );
8509
8510        let response = resolve(
8511            &mut service,
8512            json!({"id": "runtime-9/4", "decision": "deny", "option_id": "deny"}),
8513        )
8514        .await;
8515        assert!(response["error"]["message"]
8516            .as_str()
8517            .unwrap()
8518            .contains("not both"));
8519    }
8520
8521    /// The method is advertised, its SDK operation resolves it, and every
8522    /// harness whose runtime door can carry a request reports it on the
8523    /// CONTROLLED tier beside `runtimes.respond`.
8524    #[test]
8525    fn approvals_resolve_is_an_advertised_method_and_a_controlled_tier() {
8526        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.resolve"));
8527        assert_eq!(
8528            SdkOperation::from_method("harness.v1.approvals.resolve"),
8529            Some(SdkOperation::ApprovalsResolve)
8530        );
8531        assert_eq!(
8532            SdkOperation::ApprovalsResolve.action_name(),
8533            "approvals_resolve"
8534        );
8535        let registry = harness_support_registry();
8536        for id in [
8537            HarnessId::HERMES,
8538            HarnessId::OPENCLAW,
8539            HarnessId::CODEX,
8540            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8541            // pending_request concept joins the other driven doors.
8542            HarnessId::CLAUDE_CODE,
8543        ] {
8544            let concept = registry
8545                .harnesses
8546                .iter()
8547                .find(|harness| harness.id.as_str() == id)
8548                .unwrap()
8549                .orchestration
8550                .concepts
8551                .iter()
8552                .find(|concept| concept.concept == "pending_request")
8553                .unwrap();
8554            assert_eq!(
8555                concept.controlled,
8556                crate::ImplementationKind::BuiltIn,
8557                "{id}"
8558            );
8559            assert!(
8560                concept
8561                    .methods
8562                    .iter()
8563                    .any(|method| method == "harness.v1.approvals.resolve"),
8564                "{id}"
8565            );
8566        }
8567    }
8568
8569    #[test]
8570    fn capabilities_are_explicit_and_versioned() {
8571        let mut service = HarnessSessionService::new();
8572        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
8573        assert_eq!(response["result"]["version"], HARNESS_SERVICE_VERSION);
8574        assert_eq!(
8575            response["result"]["sdk"]["schema_version"],
8576            crate::SDK_SCHEMA_VERSION
8577        );
8578        assert_eq!(
8579            response["result"]["sdk"]["operations"]
8580                .as_array()
8581                .unwrap()
8582                .len(),
8583            SdkOperation::ALL.len()
8584        );
8585        assert_eq!(
8586            response["result"]["harnesses"].as_array().unwrap().len(),
8587            11
8588        );
8589        assert!(response["result"]["harnesses"]
8590            .as_array()
8591            .unwrap()
8592            .iter()
8593            .any(|harness| harness == HarnessId::GROK));
8594        assert!(response["result"]["harnesses"]
8595            .as_array()
8596            .unwrap()
8597            .iter()
8598            .any(|harness| harness == HarnessId::GOOSE));
8599    }
8600
8601    #[test]
8602    fn handshake_health_uses_protocol_liveness_not_stderr_severity() {
8603        let noisy_stderr = crate::HarnessEvent {
8604            sequence: None,
8605            kind: "transport_stderr".into(),
8606            payload: json!({"line": "ERROR optional worker AuthorizationRequired"}),
8607        };
8608        assert_eq!(handshake_event_failure(&noisy_stderr), None);
8609
8610        let closed = crate::HarnessEvent {
8611            sequence: None,
8612            kind: "transport_closed".into(),
8613            payload: json!({}),
8614        };
8615        assert!(handshake_event_failure(&closed).is_some());
8616    }
8617
8618    #[tokio::test]
8619    async fn runtime_eof_is_notified_and_removed_for_raw_and_explicit_close() {
8620        let mut service = HarnessSessionService::new();
8621        service
8622            .runtimes
8623            .insert("raw-eof".into(), ending_runtime(None));
8624        service.runtimes.insert(
8625            "explicit-close".into(),
8626            ending_runtime(Some(HarnessEvent {
8627                sequence: None,
8628                kind: "transport_closed".into(),
8629                payload: json!({"message": "native transport exited"}),
8630            })),
8631        );
8632
8633        let notifications = service.poll_runtimes().await;
8634
8635        assert_eq!(notifications.len(), 2);
8636        assert!(notifications
8637            .iter()
8638            .all(|notification| { notification["params"]["event"]["kind"] == "transport_closed" }));
8639        assert!(notifications.iter().all(|notification| {
8640            notification["params"]["session_id"] == "ending-session"
8641                && notification["params"]["connection"].is_string()
8642        }));
8643        let mut sequences = notifications
8644            .iter()
8645            .filter_map(|notification| notification["params"]["sequence"].as_u64())
8646            .collect::<Vec<_>>();
8647        sequences.sort_unstable();
8648        assert_eq!(sequences, vec![1, 2]);
8649        assert!(service.runtimes.is_empty());
8650    }
8651
8652    #[test]
8653    fn support_report_and_grok_default_binding_share_the_registry() {
8654        let mut service = HarnessSessionService::new();
8655        let response = service.handle(request(1, "harness.v1.support.report", json!({})));
8656        assert_eq!(response["result"]["schema"], crate::SUPPORT_REGISTRY_SCHEMA);
8657        let params = RuntimeBackendParams {
8658            harness: HarnessId::from(HarnessId::GROK),
8659            protocol: None,
8660            launch: None,
8661            base_url: None,
8662            policy: RuntimePolicy::Default,
8663        };
8664        let backend = match runtime_backend(&params) {
8665            Ok(backend) => backend,
8666            Err(_) => panic!("Grok should bind through its registered ACP launch"),
8667        };
8668        assert_eq!(backend.harness().as_str(), HarnessId::GROK);
8669        assert!(backend.capabilities().start_session);
8670        let registered = harness_support_registry()
8671            .harnesses
8672            .into_iter()
8673            .find(|harness| harness.id.as_str() == HarnessId::GROK)
8674            .and_then(|harness| harness.runtime.default_launch)
8675            .unwrap();
8676        assert!(!registered
8677            .arguments
8678            .iter()
8679            .any(|argument| argument == "--always-approve"));
8680        assert!(runtime_launch(&params).is_none());
8681
8682        let yolo = RuntimeBackendParams {
8683            policy: RuntimePolicy::Yolo,
8684            ..params
8685        };
8686        assert!(runtime_launch(&yolo)
8687            .unwrap()
8688            .arguments
8689            .iter()
8690            .any(|argument| argument == "--always-approve"));
8691
8692        let mismatched_protocol = RuntimeBackendParams {
8693            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8694            protocol: Some("acp".into()),
8695            launch: None,
8696            base_url: None,
8697            policy: RuntimePolicy::Default,
8698        };
8699        assert!(runtime_backend(&mismatched_protocol).is_err());
8700    }
8701
8702    #[test]
8703    fn load_follow_and_unfollow_share_the_same_locator() {
8704        let mut service = HarnessSessionService::new();
8705        let locator = pi_locator();
8706        let loaded = service.handle(request(
8707            1,
8708            "harness.v1.sessions.load",
8709            json!({"locator": locator}),
8710        ));
8711        assert_eq!(
8712            loaded["result"]["session"]["session_id"],
8713            locator.session_id
8714        );
8715
8716        let followed = service.handle(request(
8717            2,
8718            "harness.v1.sessions.follow",
8719            json!({"locator": locator}),
8720        ));
8721        assert_eq!(followed["result"]["subscription"], "sub-1");
8722        assert_eq!(followed["result"]["initial"]["type"], "session_snapshot");
8723        assert!(service.poll().is_empty());
8724
8725        let unfollowed = service.handle(request(
8726            3,
8727            "harness.v1.sessions.unfollow",
8728            json!({"subscription": "sub-1"}),
8729        ));
8730        assert_eq!(unfollowed["result"]["removed"], true);
8731    }
8732
8733    #[test]
8734    fn bounded_read_view_excludes_subagents_and_keeps_only_the_tail() {
8735        let temp = std::env::temp_dir().join(format!(
8736            "supercode-bounded-view-{}-{}",
8737            std::process::id(),
8738            generated_session_id()
8739        ));
8740        let path = temp.join("parent.jsonl");
8741        let subagents = temp.join("parent/subagents");
8742        std::fs::create_dir_all(&subagents).unwrap();
8743        let long_last = "x".repeat(300);
8744        let parent_records = [
8745            json!({"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":"first"}}),
8746            json!({"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"middle"}]}}),
8747            json!({"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":long_last}}),
8748        ];
8749        std::fs::write(
8750            &path,
8751            format!(
8752                "{}\n",
8753                parent_records
8754                    .iter()
8755                    .map(Value::to_string)
8756                    .collect::<Vec<_>>()
8757                    .join("\n")
8758            ),
8759        )
8760        .unwrap();
8761        std::fs::write(
8762            subagents.join("agent-child.jsonl"),
8763            concat!(
8764                r#"{"type":"user","uuid":"cu","parentUuid":null,"agentId":"child","message":{"role":"user","content":"child work"}}"#,
8765                "\n",
8766            ),
8767        )
8768        .unwrap();
8769        let locator = SessionLocator {
8770            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8771            session_id: "parent".into(),
8772            storage: StorageLocator::File { path },
8773        };
8774        let mut service = HarnessSessionService::new();
8775
8776        let complete = service.handle(request(
8777            1,
8778            "harness.v1.sessions.load",
8779            json!({"locator": locator}),
8780        ));
8781        assert_eq!(
8782            complete["result"]["session"]["subagents"]
8783                .as_array()
8784                .unwrap()
8785                .len(),
8786            1
8787        );
8788
8789        let bounded = service.handle(request(
8790            2,
8791            "harness.v1.sessions.load",
8792            json!({
8793                "locator": locator,
8794                "view": {
8795                    "tail_messages": 1,
8796                    "max_message_chars": 256,
8797                    "include_subagents": false
8798                },
8799            }),
8800        ));
8801        let session = &bounded["result"]["session"];
8802        assert!(session["subagents"].as_array().unwrap().is_empty());
8803        assert_eq!(session["messages"].as_array().unwrap().len(), 1);
8804        assert_eq!(
8805            session["messages"][0]["content"],
8806            format!("{}\n…", "x".repeat(256))
8807        );
8808
8809        let followed = service.handle(request(
8810            3,
8811            "harness.v1.sessions.follow",
8812            json!({
8813                "locator": locator,
8814                "view": {
8815                    "tail_messages": 1,
8816                    "max_message_chars": 256,
8817                    "include_subagents": false
8818                },
8819            }),
8820        ));
8821        let initial = &followed["result"]["initial"]["session"];
8822        assert!(initial["subagents"].as_array().unwrap().is_empty());
8823        assert_eq!(initial["messages"].as_array().unwrap().len(), 1);
8824
8825        let _ = std::fs::remove_dir_all(&temp);
8826    }
8827
8828    #[test]
8829    fn forty_megabyte_display_load_is_bounded_and_prompt() {
8830        let temp = std::env::temp_dir().join(format!(
8831            "supercode-large-display-view-{}-{}",
8832            std::process::id(),
8833            generated_session_id()
8834        ));
8835        std::fs::create_dir_all(&temp).unwrap();
8836        let path = temp.join("rollout.jsonl");
8837        let mut file = std::io::BufWriter::new(std::fs::File::create(&path).unwrap());
8838        writeln!(
8839            file,
8840            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8841        )
8842        .unwrap();
8843        let padding = "x".repeat(80 * 1024);
8844        for index in 0..512 {
8845            let marker = if index == 0 {
8846                "OLDEST-SHOULD-NOT-LOAD"
8847            } else if index == 511 {
8848                "LATEST-MUST-LOAD"
8849            } else {
8850                "bulk"
8851            };
8852            writeln!(
8853                file,
8854                "{}",
8855                json!({
8856                    "timestamp": "2026-01-01T00:00:01Z",
8857                    "type": "response_item",
8858                    "payload": {
8859                        "type": "message",
8860                        "role": "assistant",
8861                        "content": [{"type": "output_text", "text": format!("{marker}:{padding}")}],
8862                    },
8863                })
8864            )
8865            .unwrap();
8866        }
8867        file.flush().unwrap();
8868        drop(file);
8869        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
8870
8871        let locator = SessionLocator {
8872            harness: HarnessId::from(HarnessId::CODEX),
8873            session_id: "large-display".into(),
8874            storage: StorageLocator::File { path },
8875        };
8876        let started = Instant::now();
8877        let response = HarnessSessionService::new().handle(request(
8878            1,
8879            "harness.v1.sessions.load",
8880            json!({
8881                "locator": locator,
8882                "view": {
8883                    "tail_messages": 500,
8884                    "max_message_chars": 1024,
8885                    "include_subagents": false,
8886                    "display_history": true,
8887                },
8888            }),
8889        ));
8890        let elapsed = started.elapsed();
8891        let wire = response.to_string();
8892        eprintln!(
8893            "bounded 40 MiB display load: {elapsed:?}, {} response bytes",
8894            wire.len()
8895        );
8896        assert!(response.get("error").is_none(), "{response:#}");
8897        assert!(wire.contains("LATEST-MUST-LOAD"));
8898        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
8899        assert!(
8900            wire.len() < 2 * 1024 * 1024,
8901            "bounded wire was {} bytes",
8902            wire.len()
8903        );
8904        assert!(
8905            elapsed.as_secs_f64() < 3.0,
8906            "bounded 40 MiB load took {elapsed:?}"
8907        );
8908
8909        // Timing-free: a store with no human turn widens its window to the 64 MiB ceiling
8910        // looking for anchors, so the bounded read shows on one with a human turn every eight
8911        // records: a short view stops well short of the first record and says so.
8912        let anchored = temp.join("anchored.jsonl");
8913        let mut file = std::io::BufWriter::new(std::fs::File::create(&anchored).unwrap());
8914        writeln!(
8915            file,
8916            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8917        )
8918        .unwrap();
8919        for index in 0..512 {
8920            let marker = if index == 0 {
8921                "OLDEST-SHOULD-NOT-LOAD"
8922            } else if index == 511 {
8923                "LATEST-MUST-LOAD"
8924            } else {
8925                "bulk"
8926            };
8927            let (role, kind) = if index % 8 == 0 {
8928                ("user", "input_text")
8929            } else {
8930                ("assistant", "output_text")
8931            };
8932            writeln!(
8933                file,
8934                "{}",
8935                json!({
8936                    "timestamp": "2026-01-01T00:00:01Z",
8937                    "type": "response_item",
8938                    "payload": {
8939                        "type": "message",
8940                        "role": role,
8941                        "content": [{"type": kind, "text": format!("{marker}:{padding}")}],
8942                    },
8943                })
8944            )
8945            .unwrap();
8946        }
8947        file.flush().unwrap();
8948        drop(file);
8949        let short = HarnessSessionService::new().handle(request(
8950            2,
8951            "harness.v1.sessions.load",
8952            json!({
8953                "locator": SessionLocator {
8954                    harness: HarnessId::from(HarnessId::CODEX),
8955                    session_id: "large-display".into(),
8956                    storage: StorageLocator::File { path: anchored },
8957                },
8958                "view": {
8959                    "tail_messages": 20,
8960                    "max_message_chars": 1024,
8961                    "include_subagents": false,
8962                    "display_history": true,
8963                },
8964            }),
8965        ));
8966        let records = short["result"]["session"]["raw_record_count"].as_u64();
8967        assert!(
8968            records.is_some_and(|records| records < 128),
8969            "{records:?} records read"
8970        );
8971        let short = short.to_string();
8972        assert!(short.contains("LATEST-MUST-LOAD"));
8973        assert!(short.contains("older native records remain outside this bounded display window"));
8974
8975        let _ = std::fs::remove_dir_all(&temp);
8976    }
8977
8978    #[test]
8979    fn forty_megabyte_goose_store_display_load_reads_only_the_tail() {
8980        let temp = std::env::temp_dir().join(format!(
8981            "supercode-large-goose-view-{}-{}",
8982            std::process::id(),
8983            generated_session_id()
8984        ));
8985        std::fs::create_dir_all(&temp).unwrap();
8986        let path = temp.join("sessions.db");
8987        let connection = rusqlite::Connection::open(&path).unwrap();
8988        connection
8989            .execute_batch(
8990                "CREATE TABLE sessions (
8991                    id TEXT PRIMARY KEY, name TEXT NOT NULL, working_dir TEXT NOT NULL,
8992                    created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
8993                    session_type TEXT NOT NULL, extension_data TEXT,
8994                    goose_mode TEXT NOT NULL, provider_name TEXT, model_config_json TEXT,
8995                    archived_at TEXT
8996                 );
8997                 CREATE TABLE messages (
8998                    id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, message_id TEXT,
8999                    role TEXT NOT NULL, content_json TEXT NOT NULL,
9000                    created_timestamp INTEGER NOT NULL, metadata_json TEXT
9001                 );",
9002            )
9003            .unwrap();
9004        connection
9005            .execute(
9006                "INSERT INTO sessions VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL)",
9007                rusqlite::params![
9008                    "goose-large",
9009                    "Large Goose session",
9010                    "/tmp",
9011                    "2026-01-01 00:00:00",
9012                    "2026-01-01 00:00:02",
9013                    "user",
9014                    "{}",
9015                    "auto",
9016                    "anthropic",
9017                    r#"{"model_name":"claude-sonnet"}"#,
9018                ],
9019            )
9020            .unwrap();
9021        let old_content = serde_json::to_string(&vec![json!({
9022            "type": "text",
9023            "text": format!("OLDEST-SHOULD-NOT-LOAD:{}", "x".repeat(40 * 1024 * 1024)),
9024        })])
9025        .unwrap();
9026        connection
9027            .execute(
9028                "INSERT INTO messages VALUES (1, ?1, 'old', 'user', ?2, 1, '{}')",
9029                rusqlite::params!["goose-large", old_content],
9030            )
9031            .unwrap();
9032        connection
9033            .execute(
9034                "INSERT INTO messages VALUES (2, ?1, 'new', 'assistant', ?2, 2, '{}')",
9035                rusqlite::params![
9036                    "goose-large",
9037                    r#"[{"type":"text","text":"LATEST-MUST-LOAD"}]"#
9038                ],
9039            )
9040            .unwrap();
9041        drop(connection);
9042        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
9043
9044        let locator = SessionLocator {
9045            harness: HarnessId::from(HarnessId::GOOSE),
9046            session_id: "goose-large".into(),
9047            storage: StorageLocator::Sqlite {
9048                path,
9049                selector: "goose-large".into(),
9050            },
9051        };
9052        let started = Instant::now();
9053        let response = HarnessSessionService::new().handle(request(
9054            1,
9055            "harness.v1.sessions.load",
9056            json!({
9057                "locator": locator,
9058                "view": {
9059                    "tail_messages": 1,
9060                    "max_message_chars": 1024,
9061                    "include_subagents": false,
9062                    "display_history": true,
9063                },
9064            }),
9065        ));
9066        let elapsed = started.elapsed();
9067        let wire = response.to_string();
9068        eprintln!(
9069            "bounded 40 MiB Goose display load: {elapsed:?}, {} response bytes",
9070            wire.len()
9071        );
9072        assert!(response.get("error").is_none(), "{response:#}");
9073        assert!(wire.contains("LATEST-MUST-LOAD"));
9074        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
9075        assert!(
9076            wire.len() < 64 * 1024,
9077            "bounded wire was {} bytes",
9078            wire.len()
9079        );
9080        assert!(
9081            elapsed.as_secs_f64() < 1.0,
9082            "bounded Goose load took {elapsed:?}"
9083        );
9084
9085        let _ = std::fs::remove_dir_all(&temp);
9086    }
9087
9088    #[test]
9089    fn display_view_keeps_codex_assistant_history_across_compaction() {
9090        let temp = std::env::temp_dir().join(format!(
9091            "supercode-codex-display-view-{}-{}",
9092            std::process::id(),
9093            generated_session_id()
9094        ));
9095        std::fs::create_dir_all(&temp).unwrap();
9096        let path = temp.join("rollout.jsonl");
9097        std::fs::write(
9098            &path,
9099            concat!(
9100                r#"{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"codex-display","cwd":"/tmp"}}"#,
9101                "\n",
9102                r#"{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]}}"#,
9103                "\n",
9104                r#"{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"old answer"}]}}"#,
9105                "\n",
9106                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"}]}}"#,
9107                "\n",
9108                r#"{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"new prompt"}]}}"#,
9109                "\n",
9110                r#"{"timestamp":"2026-01-01T00:00:05Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"new answer"}]}}"#,
9111                "\n",
9112            ),
9113        )
9114        .unwrap();
9115        let locator = SessionLocator {
9116            harness: HarnessId::from(HarnessId::CODEX),
9117            session_id: "codex-display".into(),
9118            storage: StorageLocator::File { path },
9119        };
9120        let mut service = HarnessSessionService::new();
9121
9122        let continuation = service.handle(request(
9123            1,
9124            "harness.v1.sessions.load",
9125            json!({"locator": locator}),
9126        ));
9127        let continuation_text = continuation["result"]["session"]["messages"].to_string();
9128        assert!(!continuation_text.contains("old answer"));
9129
9130        let display = service.handle(request(
9131            2,
9132            "harness.v1.sessions.load",
9133            json!({
9134                "locator": locator,
9135                "view": {
9136                    "tail_messages": 10,
9137                    "include_subagents": false,
9138                    "display_history": true,
9139                },
9140            }),
9141        ));
9142        let display_text = display["result"]["session"]["messages"].to_string();
9143        assert!(display_text.contains("old prompt"));
9144        assert!(display_text.contains("old answer"));
9145        assert!(display_text.contains("new prompt"));
9146        assert!(display_text.contains("new answer"));
9147
9148        let _ = std::fs::remove_dir_all(&temp);
9149    }
9150
9151    #[test]
9152    fn indexed_claude_windows_match_the_existing_wire_projection() {
9153        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9154            .join("tests/fixtures/claude_code_session.jsonl");
9155        let locator = SessionLocator {
9156            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9157            session_id: "fixture".into(),
9158            storage: StorageLocator::File { path },
9159        };
9160        let full = load_session(&locator).unwrap();
9161        for inline_media in [InlineMediaMode::Full, InlineMediaMode::Metadata] {
9162            for offset in [0, 1, full.messages.len(), usize::MAX] {
9163                for limit in [0, 1, 3, usize::MAX] {
9164                    let options = SessionLoadOptions {
9165                        include_subagents: Some(false),
9166                        inline_media,
9167                        message_offset: Some(offset),
9168                        message_limit: Some(limit),
9169                        ..Default::default()
9170                    };
9171                    let expected = projected_session_result(&full, &options);
9172                    assert_eq!(
9173                        indexed_claude_window(&locator, &options).unwrap().unwrap(),
9174                        expected
9175                    );
9176                }
9177            }
9178            for tail in [0, 1, 3, usize::MAX] {
9179                let options = SessionLoadOptions {
9180                    include_subagents: Some(false),
9181                    inline_media,
9182                    message_tail: Some(tail),
9183                    ..Default::default()
9184                };
9185                assert_eq!(
9186                    indexed_claude_window(&locator, &options).unwrap().unwrap(),
9187                    projected_session_result(&full, &options)
9188                );
9189            }
9190        }
9191    }
9192
9193    #[test]
9194    fn load_supports_bounded_windows_and_media_metadata() {
9195        let mut service = HarnessSessionService::new();
9196        let locator = pi_locator();
9197        let bounded = service.handle(request(
9198            1,
9199            "harness.v1.sessions.load",
9200            json!({
9201                "locator": locator,
9202                "options": {
9203                    "include_subagents": false,
9204                    "message_limit": 2,
9205                    "message_offset": 1
9206                }
9207            }),
9208        ));
9209        assert_eq!(bounded["result"]["window"]["offset"], 1);
9210        assert_eq!(bounded["result"]["window"]["returned"], 2);
9211        assert!(bounded["result"]["summary"]["first_message"].is_object());
9212        assert!(bounded["result"]["summary"]["last_message"].is_object());
9213        assert_eq!(
9214            bounded["result"]["session"]["messages"]
9215                .as_array()
9216                .unwrap()
9217                .len(),
9218            2
9219        );
9220        assert!(bounded["result"]["session"]["subagents"]
9221            .as_array()
9222            .unwrap()
9223            .is_empty());
9224
9225        let tail = service.handle(request(
9226            2,
9227            "harness.v1.sessions.load",
9228            json!({"locator": locator, "options": {"message_tail": 1}}),
9229        ));
9230        assert_eq!(tail["result"]["window"]["returned"], 1);
9231        assert_eq!(tail["result"]["window"]["has_more"], true);
9232        assert_eq!(tail["result"]["window"]["has_older"], true);
9233        assert!(tail["result"]["window"]["older_items"].as_u64().unwrap() > 0);
9234        assert!(tail["result"]["summary"]["first_message"].is_object());
9235
9236        let metadata_only = service.handle(request(
9237            3,
9238            "harness.v1.sessions.load",
9239            json!({"locator": locator, "options": {"inline_media": "metadata"}}),
9240        ));
9241        assert!(metadata_only["result"]["session"]
9242            .to_string()
9243            .contains("media_reference"));
9244        assert!(!metadata_only["result"]["session"]
9245            .to_string()
9246            .contains("data:image/"));
9247    }
9248
9249    #[test]
9250    fn import_translate_branch_and_handoff_use_typed_artifacts() {
9251        let mut service = HarnessSessionService::new();
9252        let locator = pi_locator();
9253        let translated = service.handle(request(
9254            1,
9255            "harness.v1.sessions.translate",
9256            json!({"locator": locator, "target_harness": "grok"}),
9257        ));
9258        assert_eq!(translated["result"]["artifact"]["source_harness"], "pi");
9259        assert_eq!(translated["result"]["artifact"]["target_harness"], "grok");
9260        assert!(translated["result"]["artifact"]["content"]
9261            .as_str()
9262            .is_some_and(|content| !content.is_empty()));
9263
9264        for target in ["opencode", "open-code"] {
9265            let opencode = service.handle(request(
9266                6,
9267                "harness.v1.sessions.translate",
9268                json!({"locator": locator, "target_harness": target}),
9269            ));
9270            assert_eq!(opencode["result"]["artifact"]["target_harness"], "opencode");
9271        }
9272        let goose = service.handle(request(
9273            7,
9274            "harness.v1.sessions.translate",
9275            json!({"locator": locator, "target_harness": "goose"}),
9276        ));
9277        assert_eq!(goose["result"]["artifact"]["target_harness"], "goose");
9278        assert!(serde_json::from_str::<Value>(
9279            goose["result"]["artifact"]["content"].as_str().unwrap()
9280        )
9281        .unwrap()["conversation"]
9282            .is_array());
9283
9284        let imported = service.handle(request(
9285            2,
9286            "harness.v1.sessions.import",
9287            json!({
9288                "source_harness": "grok",
9289                "content": translated["result"]["artifact"]["content"],
9290            }),
9291        ));
9292        assert_eq!(imported["result"]["session"]["source"], "grok");
9293
9294        let branched = service.handle(request(
9295            3,
9296            "harness.v1.sessions.branch",
9297            json!({"locator": locator, "target_harness": "codex"}),
9298        ));
9299        assert_eq!(branched["result"]["parent"]["harness"], "pi");
9300        assert!(branched["result"]["bootstrap_prompt"]
9301            .as_str()
9302            .unwrap()
9303            .contains("frozen parent transcript"));
9304        assert_eq!(branched["result"]["artifact"]["target_harness"], "codex");
9305
9306        let handoff = service.handle(request(
9307            4,
9308            "harness.v1.sessions.handoff",
9309            json!({"locator": locator, "target_harness": "pi", "cwd": "/tmp/project"}),
9310        ));
9311        assert_eq!(handoff["result"]["launch"]["program"], "pi");
9312        assert_eq!(handoff["result"]["launch"]["cwd"], "/tmp/project");
9313        assert_eq!(handoff["result"]["requires_materialization"], true);
9314
9315        let goose_handoff = service.handle(request(
9316            8,
9317            "harness.v1.sessions.handoff",
9318            json!({"locator": locator, "target_harness": "goose", "cwd": "/tmp/project"}),
9319        ));
9320        assert_eq!(goose_handoff["result"]["launch"]["program"], "goose");
9321        assert_eq!(
9322            goose_handoff["result"]["materialize"]["arguments"],
9323            json!(["session", "import", "{artifact_path}"])
9324        );
9325
9326        let resumed = service.handle(request(
9327            5,
9328            "harness.v1.sessions.resume_instructions",
9329            json!({"locator": locator, "cwd": "/tmp/project", "policy": "yolo"}),
9330        ));
9331        assert_eq!(resumed["result"]["launch"]["program"], "pi");
9332        assert_eq!(resumed["result"]["launch"]["arguments"][0], "--approve");
9333    }
9334
9335    #[test]
9336    fn reduce_persists_and_reloads_a_byte_exact_reversible_bundle() {
9337        let temp = std::env::temp_dir().join(format!(
9338            "supercode-service-reduce-{}-{}",
9339            std::process::id(),
9340            generated_session_id()
9341        ));
9342        let source_path = temp.join("source.jsonl");
9343        let store_root = temp.join("store");
9344        std::fs::create_dir_all(&temp).unwrap();
9345
9346        let mut records = vec![json!({
9347            "timestamp": "2026-01-01T00:00:00Z",
9348            "type": "session_meta",
9349            "payload": {"id": "codex-reduce", "cwd": "/tmp/project"},
9350        })];
9351        for turn in 0..16 {
9352            records.push(json!({
9353                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 1),
9354                "type": "response_item",
9355                "payload": {
9356                    "type": "message",
9357                    "role": "user",
9358                    "content": [{
9359                        "type": "input_text",
9360                        "text": format!("request {turn}: {}", "context ".repeat(80)),
9361                    }],
9362                },
9363            }));
9364            records.push(json!({
9365                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 2),
9366                "type": "response_item",
9367                "payload": {
9368                    "type": "message",
9369                    "role": "assistant",
9370                    "content": [{
9371                        "type": "output_text",
9372                        "text": format!("answer {turn}: {}", "implementation detail ".repeat(80)),
9373                    }],
9374                },
9375            }));
9376        }
9377        let source = format!(
9378            "{}\n",
9379            records
9380                .iter()
9381                .map(Value::to_string)
9382                .collect::<Vec<_>>()
9383                .join("\n")
9384        );
9385        std::fs::write(&source_path, &source).unwrap();
9386        let locator = SessionLocator {
9387            harness: HarnessId::from(HarnessId::CODEX),
9388            session_id: "codex-reduce".into(),
9389            storage: StorageLocator::File {
9390                path: source_path.clone(),
9391            },
9392        };
9393        let original = load_session(&locator).unwrap();
9394        let mut service =
9395            HarnessSessionService::new().with_reduction_store_root(store_root.clone());
9396
9397        let response = service.handle(request(
9398            1,
9399            "harness.v1.sessions.reduce",
9400            json!({
9401                "locator": locator,
9402                "target_harness": "claude-code",
9403                "keep_last": 4,
9404            }),
9405        ));
9406        assert!(response.get("error").is_none(), "{response:#}");
9407        let receipt = &response["result"]["receipt"];
9408        assert_eq!(receipt["source_harness"], "codex");
9409        assert_eq!(receipt["target_harness"], "claude-code");
9410        assert_eq!(receipt["verified"], true);
9411        assert_eq!(receipt["reversible"], true);
9412        assert!(receipt["reductions"].as_u64().unwrap() > 0);
9413        assert!(
9414            receipt["source_tokens"].as_u64().unwrap()
9415                > receipt["reduced_tokens"].as_u64().unwrap()
9416        );
9417        assert!(receipt["ratio"].as_f64().unwrap() > 1.0);
9418        assert!(response["result"]["bootstrap_prompt"]
9419            .as_str()
9420            .unwrap()
9421            .contains("Do not guess hidden content"));
9422
9423        let rescue_id = receipt["id"].as_str().unwrap();
9424        let store = crate::SessionStore::open(&store_root).unwrap();
9425        let sidecar =
9426            Session::from_sidecar_str(&store.load_sidecar(rescue_id).unwrap().unwrap()).unwrap();
9427        let log = store.load_reduction_log(rescue_id).unwrap().unwrap();
9428        let persisted_view = parse_messages_jsonl(&store.load(rescue_id).unwrap()).unwrap();
9429        let policy = reduce::ReductionPolicy {
9430            clear_turns_older_than: Some(4),
9431            ..Default::default()
9432        };
9433        let (restamped_view, reapplied_log) =
9434            reduce::project_messages(&sidecar.messages, &policy, &log);
9435        assert_eq!(
9436            messages_jsonl(&persisted_view).unwrap(),
9437            messages_jsonl(&restamped_view).unwrap()
9438        );
9439        assert_eq!(reapplied_log, log);
9440        reduce::verify_log(&log, &sidecar).unwrap();
9441        assert_eq!(
9442            reduce::invert(&restamped_view, &log, &sidecar).unwrap(),
9443            original.messages
9444        );
9445        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), source);
9446
9447        std::fs::remove_dir_all(temp).ok();
9448    }
9449
9450    #[test]
9451    fn read_surfaces_view_a_severed_claude_graph_while_transfer_still_refuses_it() {
9452        let temp = std::env::temp_dir().join(format!(
9453            "supercode-severed-view-{}-{}",
9454            std::process::id(),
9455            generated_session_id()
9456        ));
9457        std::fs::create_dir_all(&temp).unwrap();
9458        let path = temp.join("severed.jsonl");
9459        // A live record whose parent was pruned — what a compacted or
9460        // resumed-across-files Claude Code session looks like on disk.
9461        std::fs::write(
9462            &path,
9463            concat!(
9464                r#"{"type":"user","uuid":"orphan-u","parentUuid":null,"message":{"role":"user","content":"stranded prompt"}}"#,
9465                "\n",
9466                r#"{"type":"assistant","uuid":"live-a","parentUuid":"pruned","message":{"id":"m","role":"assistant","content":[{"type":"text","text":"live answer"}]}}"#,
9467                "\n",
9468            ),
9469        )
9470        .unwrap();
9471        let locator = SessionLocator {
9472            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9473            session_id: "severed".into(),
9474            storage: StorageLocator::File { path },
9475        };
9476        let mut service = HarnessSessionService::new();
9477
9478        let viewed = service.handle(request(
9479            1,
9480            "harness.v1.sessions.load",
9481            json!({"locator": locator}),
9482        ));
9483        let session = &viewed["result"]["session"];
9484        assert_eq!(session["fidelity"], "semantic");
9485        assert_eq!(session["messages"].as_array().unwrap().len(), 2);
9486        assert!(session["residue"].as_array().unwrap().iter().any(|entry| {
9487            entry
9488                .as_str()
9489                .is_some_and(|entry| entry.contains("live-a") && entry.contains("pruned"))
9490        }));
9491
9492        // Asking a READ surface for a lossless reconstruction gets the strict
9493        // refusal back, unchanged.
9494        let strict = service.handle(request(
9495            2,
9496            "harness.v1.sessions.load",
9497            json!({"locator": locator, "fidelity": "byte_lossless"}),
9498        ));
9499        assert!(strict["error"]["message"]
9500            .as_str()
9501            .unwrap()
9502            .contains("cannot reconstruct lossless Claude continuation"));
9503
9504        // Transfer/continuation surfaces have no view mode at all.
9505        let translated = service.handle(request(
9506            3,
9507            "harness.v1.sessions.translate",
9508            json!({"locator": locator, "target_harness": "codex"}),
9509        ));
9510        assert!(translated["error"]["message"]
9511            .as_str()
9512            .unwrap()
9513            .contains("cannot reconstruct lossless Claude continuation"));
9514        let resumed = service.handle(request(
9515            4,
9516            "harness.v1.sessions.resume_instructions",
9517            json!({"locator": locator}),
9518        ));
9519        assert!(resumed["error"]["message"]
9520            .as_str()
9521            .unwrap()
9522            .contains("cannot reconstruct lossless Claude continuation"));
9523
9524        let _ = std::fs::remove_dir_all(&temp);
9525    }
9526
9527    #[test]
9528    fn structured_resume_launches_cover_gemini_goose_and_supercode() {
9529        let codex = resume_launch(
9530            HarnessId::CODEX,
9531            "codex-session",
9532            Path::new("/tmp/project"),
9533            ResumePolicy::Yolo,
9534        )
9535        .unwrap_or_else(|_| panic!("Codex resume launch must be registered"));
9536        assert_eq!(codex.program, "codex");
9537        assert_eq!(
9538            codex.arguments,
9539            [
9540                "-c",
9541                "check_for_update_on_startup=false",
9542                "-c",
9543                "projects.\"/tmp/project\".trust_level=\"trusted\"",
9544                "--dangerously-bypass-approvals-and-sandbox",
9545                "--dangerously-bypass-hook-trust",
9546                "resume",
9547                "codex-session",
9548            ]
9549        );
9550
9551        let gemini = resume_launch(
9552            HarnessId::GEMINI,
9553            "gemini-session",
9554            Path::new("/tmp/project"),
9555            ResumePolicy::Yolo,
9556        )
9557        .unwrap_or_else(|_| panic!("Gemini resume launch must be registered"));
9558        assert_eq!(gemini.program, "gemini");
9559        assert_eq!(gemini.arguments, ["--yolo", "--resume", "gemini-session"]);
9560
9561        let goose = resume_launch(
9562            HarnessId::GOOSE,
9563            "goose-session",
9564            Path::new("/tmp/project"),
9565            ResumePolicy::Yolo,
9566        )
9567        .unwrap_or_else(|_| panic!("Goose resume launch must be registered"));
9568        assert_eq!(goose.program, "goose");
9569        assert_eq!(
9570            goose.arguments,
9571            ["session", "--resume", "--session-id", "goose-session"]
9572        );
9573
9574        let supercode = resume_launch(
9575            HarnessId::SUPERCODE,
9576            "supercode-session",
9577            Path::new("/tmp/project"),
9578            ResumePolicy::Yolo,
9579        )
9580        .unwrap_or_else(|_| panic!("Volter Harness resume launch must be registered"));
9581        assert_eq!(supercode.program, "supercode");
9582        assert_eq!(
9583            supercode.arguments,
9584            ["--dangerous", "resume", "supercode-session"]
9585        );
9586    }
9587
9588    #[test]
9589    fn diagonal_artifacts_preserve_claude_subagents_and_grok_bundle_members() {
9590        let temp = std::env::temp_dir().join(format!(
9591            "supercode-harness-artifact-{}-{}",
9592            std::process::id(),
9593            generated_session_id()
9594        ));
9595        let main_path = temp.join("parent.jsonl");
9596        let subagent_path = temp.join("parent/subagents/agent-child.jsonl");
9597        std::fs::create_dir_all(subagent_path.parent().unwrap()).unwrap();
9598        let fixture = std::fs::read_to_string(
9599            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9600                .join("tests/fixtures/claude_code_session.jsonl"),
9601        )
9602        .unwrap();
9603        let parent = fixture.trim_end_matches('\n');
9604        let child = fixture.trim_end_matches('\n');
9605        std::fs::write(&main_path, parent).unwrap();
9606        std::fs::write(&subagent_path, child).unwrap();
9607        let locator = SessionLocator {
9608            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9609            session_id: "213bb148-51ea-453f-9206-f8b4b1168547".into(),
9610            storage: StorageLocator::File {
9611                path: main_path.clone(),
9612            },
9613        };
9614        let mut service = HarnessSessionService::new();
9615        let claude = service.handle(request(
9616            1,
9617            "harness.v1.sessions.translate",
9618            json!({"locator": locator, "target_harness": "claude-code"}),
9619        ));
9620        let artifact = &claude["result"]["artifact"];
9621        assert_eq!(artifact["fidelity"], "byte_lossless");
9622        assert_eq!(artifact["content"], parent);
9623        let files = artifact["files"].as_array().unwrap();
9624        assert!(files.iter().any(|file| {
9625            file["role"] == "subagent"
9626                && file["path"]
9627                    .as_str()
9628                    .is_some_and(|path| path.ends_with("/subagents/agent-child.jsonl"))
9629                && file["content"] == child
9630        }));
9631        assert!(!artifact["content"].as_str().unwrap().ends_with('\n'));
9632
9633        let grok = service.handle(request(
9634            2,
9635            "harness.v1.sessions.translate",
9636            json!({"locator": grok_locator(), "target_harness": "grok"}),
9637        ));
9638        let files = grok["result"]["artifact"]["files"].as_array().unwrap();
9639        for name in ["summary.json", "updates.jsonl"] {
9640            let expected = std::fs::read_to_string(
9641                PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9642                    .join("tests/fixtures/grok_session")
9643                    .join(name),
9644            )
9645            .unwrap();
9646            assert!(files.iter().any(|file| {
9647                file["path"] == name && file["role"] == "bundle" && file["content"] == expected
9648            }));
9649        }
9650        std::fs::remove_dir_all(temp).ok();
9651    }
9652
9653    #[test]
9654    fn every_non_grok_handoff_mints_and_uses_a_fresh_target_identity() {
9655        let mut service = HarnessSessionService::new();
9656        let source = pi_locator();
9657        for (target, format) in [
9658            ("claude-code", SessionFormat::ClaudeCode),
9659            ("codex", SessionFormat::Codex),
9660            ("opencode", SessionFormat::OpenCode),
9661            ("pi", SessionFormat::Pi),
9662        ] {
9663            let result = service.handle(request(
9664                1,
9665                "harness.v1.sessions.handoff",
9666                json!({"locator": source, "target_harness": target, "cwd": "/tmp/project"}),
9667            ));
9668            let artifact = &result["result"]["artifact"];
9669            let target_id = artifact["session_id"].as_str().unwrap();
9670            assert_ne!(target_id, source.session_id, "{target}");
9671            let parsed = Session::load_str(artifact["content"].as_str().unwrap(), format).unwrap();
9672            assert_eq!(
9673                parsed.meta.session_id.as_deref(),
9674                Some(target_id),
9675                "{target}"
9676            );
9677            if target != "pi" {
9678                assert!(result["result"]["launch"]["arguments"]
9679                    .as_array()
9680                    .unwrap()
9681                    .iter()
9682                    .any(|argument| argument == target_id));
9683            }
9684            if target == "opencode" {
9685                assert!(target_id.starts_with("ses_"));
9686                fn assert_session_ids(value: &Value, target_id: &str) {
9687                    match value {
9688                        Value::Object(fields) => {
9689                            if let Some(session_id) = fields.get("sessionID") {
9690                                assert_eq!(session_id, target_id);
9691                            }
9692                            for child in fields.values() {
9693                                assert_session_ids(child, target_id);
9694                            }
9695                        }
9696                        Value::Array(values) => {
9697                            for child in values {
9698                                assert_session_ids(child, target_id);
9699                            }
9700                        }
9701                        _ => {}
9702                    }
9703                }
9704                let document: Value =
9705                    serde_json::from_str(artifact["content"].as_str().unwrap()).unwrap();
9706                assert_session_ids(&document, target_id);
9707            }
9708        }
9709
9710        let first = service.handle(request(
9711            2,
9712            "harness.v1.sessions.handoff",
9713            json!({"locator": source, "target_harness": "codex"}),
9714        ));
9715        let second = service.handle(request(
9716            3,
9717            "harness.v1.sessions.handoff",
9718            json!({"locator": source, "target_harness": "codex"}),
9719        ));
9720        assert_ne!(
9721            first["result"]["artifact"]["session_id"],
9722            second["result"]["artifact"]["session_id"]
9723        );
9724    }
9725
9726    #[test]
9727    fn grok_handoff_materializes_through_the_core_door() {
9728        let mut service = HarnessSessionService::new();
9729        let source = opencode_locator();
9730        let response = service.handle(request(
9731            1,
9732            "harness.v1.sessions.handoff",
9733            json!({
9734                "locator": source,
9735                "target_harness": "grok",
9736                "cwd": "/tmp/grok-handoff-project",
9737            }),
9738        ));
9739        let result = &response["result"];
9740
9741        // Grok has no import command: the artifact is Grok's own transcript under a fresh
9742        // identity, and `harness.v1.sessions.materialize` writes its store entry.
9743        assert_eq!(result["artifact"]["target_harness"], "grok");
9744        let artifact = Session::load_str(
9745            result["artifact"]["content"].as_str().unwrap(),
9746            SessionFormat::Grok,
9747        )
9748        .unwrap();
9749        assert!(!artifact.messages.is_empty());
9750        let target_session_id = result["artifact"]["session_id"].as_str().unwrap();
9751        assert_eq!(target_session_id.len(), 36);
9752        assert_ne!(target_session_id, opencode_locator().session_id);
9753        assert!(result["materialize"].is_null());
9754        assert_eq!(
9755            result["launch"]["arguments"],
9756            json!(["--resume", "{materialized_session_id}"])
9757        );
9758        assert!(result["note"]
9759            .as_str()
9760            .unwrap()
9761            .contains("harness.v1.sessions.materialize"));
9762    }
9763
9764    #[tokio::test]
9765    async fn inventory_rejects_unknown_harnesses_and_runtime_attach_is_honest() {
9766        let mut service = HarnessSessionService::new();
9767        let inventory = service
9768            .handle_async(request(
9769                1,
9770                "harness.v1.harnesses.list",
9771                json!({"harnesses": ["missing"]}),
9772            ))
9773            .await;
9774        assert_eq!(inventory["error"]["code"], -32602);
9775
9776        let attached = service
9777            .handle_async(request(
9778                2,
9779                "harness.v1.runtimes.attach_existing",
9780                json!({"harness": "codex", "runtime_id": "thread-1"}),
9781            ))
9782            .await;
9783        assert_eq!(attached["error"]["code"], -32000);
9784        assert!(attached["error"]["message"]
9785            .as_str()
9786            .unwrap()
9787            .contains("runtimes.resume"));
9788    }
9789
9790    #[test]
9791    fn invalid_params_and_unknown_methods_use_json_rpc_errors() {
9792        let mut service = HarnessSessionService::new();
9793        let invalid = service.handle(request(1, "harness.v1.sessions.load", json!({})));
9794        assert_eq!(invalid["error"]["code"], -32602);
9795        let unknown = service.handle(request(2, "harness.v1.unknown", json!({})));
9796        assert_eq!(unknown["error"]["code"], -32601);
9797    }
9798
9799    #[cfg(unix)]
9800    #[tokio::test]
9801    // The test mutates process-wide harness environment and deliberately
9802    // holds the global test lock until every async runtime operation ends.
9803    #[allow(clippy::await_holding_lock)]
9804    async fn async_service_drives_a_generic_acp_runtime() {
9805        let _environment_guard = crate::live_runtime::test_environment_lock();
9806        let script = r#"
9807            i=0
9808            while IFS= read -r line; do
9809              i=$((i + 1))
9810              case "$i" in
9811                1) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{},"authMethods":[]}}' ;;
9812                2) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"svc_acp"}}' ;;
9813                3)
9814                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ok"}}}}'
9815                  printf '%s\n' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}'
9816                  ;;
9817                4)
9818                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"from terminal"}}}}'
9819                  printf '%s\n' '{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}'
9820                  ;;
9821              esac
9822            done
9823        "#;
9824        let mut service = HarnessSessionService::new();
9825        let started = service
9826            .handle_async(request(
9827                1,
9828                "harness.v1.runtimes.start",
9829                json!({
9830                    "harness": "codex",
9831                    "protocol": "acp",
9832                    "cwd": std::env::current_dir().unwrap(),
9833                    "launch": {"program": "/bin/sh", "arguments": ["-c", script], "env": {}},
9834                }),
9835            ))
9836            .await;
9837        assert_eq!(started["result"]["connection"], "runtime-1");
9838        assert_eq!(started["result"]["handle"]["runtime_id"], "svc_acp");
9839
9840        let terminal = service
9841            .handle_async(request(
9842                9,
9843                "harness.v1.runtimes.terminal_instructions",
9844                json!({"connection":"runtime-1"}),
9845            ))
9846            .await;
9847        let arguments = terminal["result"]["launch"]["arguments"]
9848            .as_array()
9849            .expect("hosted runtime should return terminal arguments");
9850        let endpoint_index = arguments
9851            .iter()
9852            .position(|value| value == "--endpoint")
9853            .expect("terminal command should use an opaque endpoint");
9854        let endpoint = LiveRuntimeEndpoint::parse(
9855            arguments[endpoint_index + 1]
9856                .as_str()
9857                .expect("endpoint argument should be text"),
9858        )
9859        .unwrap();
9860        assert!(!terminal.to_string().contains("Bearer"));
9861        let workspace = std::env::current_dir().unwrap();
9862        let receipt = resolve_live_runtime(
9863            &endpoint,
9864            &LiveRuntimeSource {
9865                harness: "codex".into(),
9866                session_id: "svc_acp".into(),
9867                workspace,
9868            },
9869        )
9870        .unwrap();
9871        let remote = crate::HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
9872            .await
9873            .unwrap();
9874        let mut attachment = crate::FrontendRuntime::attach(remote.as_ref(), 100)
9875            .await
9876            .unwrap();
9877
9878        let sent = service
9879            .handle_async(request(
9880                2,
9881                "harness.v1.runtimes.send_input",
9882                json!({"connection": "runtime-1", "text": "hi"}),
9883            ))
9884            .await;
9885        assert_eq!(sent["result"]["turn_id"], "3");
9886
9887        let mut events = Vec::new();
9888        for _ in 0..20 {
9889            events.extend(service.poll_runtimes().await);
9890            if events.len() >= 2 {
9891                break;
9892            }
9893            tokio::time::sleep(Duration::from_millis(2)).await;
9894        }
9895        assert!(events
9896            .iter()
9897            .any(|event| { event["params"]["event"]["kind"] == "session/update" }));
9898        assert!(events.iter().any(|event| {
9899            event["params"]["event"]["kind"] == "supercode/acp_request_completed"
9900        }));
9901
9902        let saw_editor_reply = tokio::time::timeout(Duration::from_secs(2), async {
9903            loop {
9904                let event = attachment.next_event().await.unwrap();
9905                if event.kind == "text_delta" && event.payload["text"] == "ok" {
9906                    break;
9907                }
9908            }
9909        })
9910        .await;
9911        assert!(
9912            saw_editor_reply.is_ok(),
9913            "terminal should observe the editor-driven turn"
9914        );
9915
9916        crate::FrontendRuntime::submit(remote.as_ref(), "DRIVE FROM TERMINAL".into())
9917            .await
9918            .unwrap();
9919        let saw_terminal_reply = tokio::time::timeout(Duration::from_secs(2), async {
9920            loop {
9921                let event = attachment.next_event().await.unwrap();
9922                if event.kind == "text_delta" && event.payload["text"] == "from terminal" {
9923                    break;
9924                }
9925            }
9926        })
9927        .await;
9928        assert!(
9929            saw_terminal_reply.is_ok(),
9930            "terminal should drive the same runtime"
9931        );
9932
9933        let closed = service
9934            .handle_async(request(
9935                3,
9936                "harness.v1.runtimes.close",
9937                json!({"connection": "runtime-1"}),
9938            ))
9939            .await;
9940        assert_eq!(closed["result"]["closed"], true);
9941    }
9942
9943    /// UNI-7 dev/02: a RUNNING mock gateway is detected through the real
9944    /// openclaw probe (config-declared endpoint, TCP connect), and an ACTIVE
9945    /// hermes WAL is detected through the real WAL-freshness probe; the
9946    /// negative sides (no listener, stale WAL, no config) stay undetected.
9947    #[test]
9948    fn running_instances_are_detected_from_mock_gateway_and_active_wal() {
9949        let home = connect_scratch_home("uni7-running");
9950
9951        // No config at all: hermes has no default endpoint, so no detection.
9952        // (openclaw's no-config behavior now probes its DOCUMENTED default
9953        // endpoint ws://127.0.0.1:18789 — see the connect launch's
9954        // `default_address` — which is real box state a hermetic test must
9955        // not assert either way; the closed-port negative below covers the
9956        // no-listener side deterministically.)
9957        assert!(probe_hermes_running(&home, 300_000).is_none());
9958
9959        // Mock gateway: a real TCP listener on an ephemeral port, declared in
9960        // the harness's own config file.
9961        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9962        let port = listener.local_addr().unwrap().port();
9963        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9964        std::fs::write(
9965            home.join(".openclaw/openclaw.json"),
9966            format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9967        )
9968        .unwrap();
9969        let running = probe_openclaw_running(&home).expect("listening gateway must be detected");
9970        assert!(matches!(
9971            running.method,
9972            RunningInstanceMethod::GatewayConnect
9973        ));
9974        assert!(running.evidence.contains(&format!("127.0.0.1:{port}")));
9975        drop(listener);
9976        // Parallel tests also bind ephemeral loopback ports, so a just-freed
9977        // port can be re-bound by a NEIGHBORING test between drop and probe.
9978        // Detection on a closed port must fail — retry on a fresh port when
9979        // the freed one was recycled by someone else.
9980        let mut closed_detected = probe_openclaw_running(&home).is_some();
9981        for _ in 0..3 {
9982            if !closed_detected {
9983                break;
9984            }
9985            let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9986            let port = listener.local_addr().unwrap().port();
9987            drop(listener);
9988            std::fs::write(
9989                home.join(".openclaw/openclaw.json"),
9990                format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9991            )
9992            .unwrap();
9993            closed_detected = probe_openclaw_running(&home).is_some();
9994        }
9995        assert!(
9996            !closed_detected,
9997            "a closed gateway must not read as running"
9998        );
9999
10000        // gateway.url form takes precedence over port.
10001        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
10002        let port = listener.local_addr().unwrap().port();
10003        std::fs::write(
10004            home.join(".openclaw/openclaw.json"),
10005            format!(r#"{{"gateway": {{"url": "ws://127.0.0.1:{port}", "auth": {{"mode": "token", "token": "t"}}}}}}"#),
10006        )
10007        .unwrap();
10008        assert!(probe_openclaw_running(&home).is_some());
10009        drop(listener);
10010
10011        // Hermes: an ACTIVE WAL (fresh stamp) is detected; a stale one is not.
10012        std::fs::create_dir_all(home.join(".hermes")).unwrap();
10013        let wal = home.join(".hermes/state.db-wal");
10014        std::fs::write(&wal, b"wal").unwrap();
10015        let running = probe_hermes_running(&home, 300_000).expect("fresh WAL must be detected");
10016        assert!(matches!(
10017            running.method,
10018            RunningInstanceMethod::StoreWalActivity
10019        ));
10020        assert!(running.evidence.contains("state.db-wal"));
10021        let stale = std::time::SystemTime::now() - std::time::Duration::from_secs(3_600);
10022        std::fs::File::options()
10023            .append(true)
10024            .open(&wal)
10025            .unwrap()
10026            .set_modified(stale)
10027            .unwrap();
10028        assert!(
10029            probe_hermes_running(&home, 300_000).is_none(),
10030            "a stale WAL (crash leftover) must not read as running"
10031        );
10032    }
10033
10034    fn connect_scratch_home(tag: &str) -> PathBuf {
10035        let dir = std::env::temp_dir().join(format!(
10036            "supercode-connect-service-{tag}-{}-{}",
10037            std::process::id(),
10038            std::time::SystemTime::now()
10039                .duration_since(std::time::UNIX_EPOCH)
10040                .unwrap()
10041                .as_nanos()
10042        ));
10043        std::fs::create_dir_all(&dir).unwrap();
10044        dir
10045    }
10046
10047    /// Minimal HTTP responder that speaks just enough OpenCode server to
10048    /// accept a health check, create a session, and hold an SSE stream open,
10049    /// while recording each request line with its Authorization header.
10050    async fn mock_opencode_endpoint() -> (String, tokio::sync::mpsc::UnboundedReceiver<String>) {
10051        use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
10052        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
10053        let address = listener.local_addr().unwrap();
10054        let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel();
10055        tokio::spawn(async move {
10056            loop {
10057                let Ok((mut stream, _)) = listener.accept().await else {
10058                    break;
10059                };
10060                let request_sender = request_sender.clone();
10061                tokio::spawn(async move {
10062                    let (reader, mut writer) = stream.split();
10063                    let mut reader = BufReader::new(reader);
10064                    let mut request_line = String::new();
10065                    if reader.read_line(&mut request_line).await.unwrap_or(0) == 0 {
10066                        return;
10067                    }
10068                    let request_line = request_line.trim_end().to_string();
10069                    let mut authorization = String::new();
10070                    let mut content_length = 0usize;
10071                    loop {
10072                        let mut line = String::new();
10073                        if reader.read_line(&mut line).await.unwrap_or(0) == 0 {
10074                            return;
10075                        }
10076                        let line = line.trim_end();
10077                        if line.is_empty() {
10078                            break;
10079                        }
10080                        let lower = line.to_ascii_lowercase();
10081                        if let Some(value) = lower.strip_prefix("authorization:") {
10082                            authorization = value.trim().to_string();
10083                        }
10084                        if let Some(value) = lower.strip_prefix("content-length:") {
10085                            content_length = value.trim().parse().unwrap_or(0);
10086                        }
10087                    }
10088                    if content_length > 0 {
10089                        let mut body = vec![0u8; content_length];
10090                        let _ = reader.read_exact(&mut body).await;
10091                    }
10092                    let _ = request_sender.send(format!("{request_line} :: {authorization}"));
10093                    if request_line.starts_with("GET /event") {
10094                        let _ = writer
10095                            .write_all(
10096                                b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n",
10097                            )
10098                            .await;
10099                        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
10100                        return;
10101                    }
10102                    let body = if request_line.starts_with("POST /session") {
10103                        r#"{"id":"mock-session"}"#
10104                    } else {
10105                        r#"{"status":"ok"}"#
10106                    };
10107                    let response = format!(
10108                        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
10109                        body.len(),
10110                        body
10111                    );
10112                    let _ = writer.write_all(response.as_bytes()).await;
10113                });
10114            }
10115        });
10116        (format!("http://{address}"), request_receiver)
10117    }
10118
10119    fn connect_descriptor(protocol: &str) -> crate::HarnessSupportDescriptor {
10120        crate::HarnessSupportDescriptor {
10121            orchestration: Default::default(),
10122            id: HarnessId::from(HarnessId::OPENCODE),
10123            display_name: "OpenCode".into(),
10124            native: crate::NativeSupport {
10125                discover: crate::ImplementationKind::Absent,
10126                load: crate::ImplementationKind::Absent,
10127                follow: crate::ImplementationKind::Absent,
10128                import: crate::ImplementationKind::Absent,
10129                export: crate::ImplementationKind::Absent,
10130            },
10131            runtime: crate::RuntimeSupport {
10132                implementation: crate::ImplementationKind::BuiltIn,
10133                protocol: protocol.into(),
10134                default_launch: None,
10135                connect_launch: Some(crate::RuntimeConnectLaunch {
10136                    config_path: "~/opencode-tui.json".into(),
10137                    address_pointer: "/server/url".into(),
10138                    port_pointer: None,
10139                    default_address: None,
10140                    auth_pointer: Some("/server/token".into()),
10141                    protocol: protocol.into(),
10142                }),
10143                capabilities: crate::RuntimeCapabilities {
10144                    start_session: true,
10145                    resume_session: true,
10146                    attach_existing_process: true,
10147                    send_input: true,
10148                    stream_events: true,
10149                    interrupt: true,
10150                    steer: false,
10151                    respond_to_requests: true,
10152                },
10153            },
10154        }
10155    }
10156
10157    #[tokio::test]
10158    async fn connect_mode_descriptor_opens_a_running_endpoint_with_config_sourced_auth() {
10159        let (base_url, mut requests) = mock_opencode_endpoint().await;
10160        let home = connect_scratch_home("open");
10161        std::fs::write(
10162            home.join("opencode-tui.json"),
10163            format!(r#"{{"server": {{"url": "{base_url}", "token": "connect-secret"}}}}"#),
10164        )
10165        .unwrap();
10166
10167        let descriptor = connect_descriptor("opencode-http-sse");
10168        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
10169        assert!(backend.capabilities().attach_existing_process);
10170
10171        let connection = backend
10172            .start(crate::RuntimeStartRequest {
10173                cwd: home.clone(),
10174                launch: None,
10175                mcp_servers: Vec::new(),
10176                approval_policy: None,
10177            })
10178            .await
10179            .unwrap();
10180        let handle = connection.handle();
10181        assert_eq!(handle.runtime_id, "mock-session");
10182        match &handle.endpoint {
10183            crate::RuntimeEndpoint::Http {
10184                base_url: endpoint, ..
10185            } => assert_eq!(endpoint, &base_url),
10186            other => panic!("connect mode must join the running endpoint, got {other:?}"),
10187        }
10188
10189        let mut seen = Vec::new();
10190        while let Ok(line) = requests.try_recv() {
10191            seen.push(line);
10192        }
10193        assert!(seen
10194            .iter()
10195            .any(|line| line.starts_with("GET /global/health")
10196                && line.contains("bearer connect-secret")));
10197        assert!(seen.iter().any(
10198            |line| line.starts_with("POST /session") && line.contains("bearer connect-secret")
10199        ));
10200    }
10201
10202    /// UNI-5 dev/02, contract corrected by the 2026-08-31 blind walk: the
10203    /// full connect-mode attach path against a MOCK gateway bridge — no live
10204    /// gateway, no model spend. A scripted fake `openclaw` binary (a)
10205    /// asserts the REAL bridge contract — the resolved --url on argv and the
10206    /// credential via --token-file (the real bridge ignores the env var; the
10207    /// endpoint comes from openclaw-native `gateway.remote.url`, never the
10208    /// schema-invalid `gateway.url`) — then (b) speaks scripted ACP:
10209    /// initialize advertising sessionCapabilities.{list,resume},
10210    /// session/resume rebinding the requested session (join), and a
10211    /// prompted turn.
10212    #[tokio::test]
10213    async fn openclaw_connect_mode_attaches_lists_and_resumes_via_a_mock_bridge() {
10214        let home = connect_scratch_home("openclaw");
10215        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
10216        std::fs::write(
10217            home.join(".openclaw/openclaw.json"),
10218            r#"{"gateway": {"remote": {"url": "ws://127.0.0.1:19789"}, "auth": {"mode": "token", "token": "mock-gateway-token"}}}"#,
10219        )
10220        .unwrap();
10221        let script = home.join("openclaw");
10222        std::fs::write(
10223            &script,
10224            r#"#!/bin/sh
10225# Fake `openclaw acp` bridge: verify the connect-mode contract, then speak ACP.
10226[ "$1" = "acp" ] || { echo "unexpected argv: $*" >&2; exit 9; }
10227[ "$2" = "--url" ] && [ "$3" = "ws://127.0.0.1:19789" ] || { echo "missing --url: $*" >&2; exit 9; }
10228[ "$4" = "--token-file" ] || { echo "missing --token-file: $*" >&2; exit 9; }
10229[ "$(cat "$5")" = "mock-gateway-token" ] || { echo "token file wrong" >&2; exit 9; }
10230while IFS= read -r line; do
10231  case "$line" in
10232    *'"initialize"'*)
10233      printf '%s
10234' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"sessionCapabilities":{"list":{},"resume":{}}},"agentInfo":{"name":"openclaw-acp","version":"2026.7.1-2"},"authMethods":[]}}' ;;
10235    *'"session/resume"'*)
10236      printf '%s
10237' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:main"}}' ;;
10238    *'"session/new"'*)
10239      printf '%s
10240' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:fresh"}}' ;;
10241    *'"session/prompt"'*)
10242      printf '%s
10243' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"agent:main:main","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"joined"}}}}'
10244      printf '%s
10245' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}' ;;
10246  esac
10247done
10248"#,
10249        )
10250        .unwrap();
10251        use std::os::unix::fs::PermissionsExt;
10252        std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
10253
10254        let mut descriptor = crate::harness_support_registry()
10255            .harnesses
10256            .into_iter()
10257            .find(|harness| harness.id.as_str() == HarnessId::OPENCLAW)
10258            .expect("openclaw must be registered");
10259        descriptor
10260            .runtime
10261            .connect_launch
10262            .as_mut()
10263            .unwrap()
10264            .config_path = "~/.openclaw/openclaw.json".into();
10265        descriptor.runtime.default_launch.as_mut().unwrap().program =
10266            script.to_string_lossy().into_owned();
10267        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
10268        assert!(backend.capabilities().resume_session);
10269
10270        let joined = backend
10271            .attach(crate::RuntimeAttachRequest {
10272                runtime_id: "agent:main:main".into(),
10273                cwd: Some(home.clone()),
10274                launch: None,
10275                mcp_servers: Vec::new(),
10276                approval_policy: None,
10277            })
10278            .await;
10279        let mut connection = joined.expect("mock bridge attach must succeed");
10280        assert_eq!(connection.handle().runtime_id, "agent:main:main");
10281        let turn = connection
10282            .send_input(crate::RuntimeInput {
10283                text: "hello".into(),
10284                image_urls: Vec::new(),
10285            })
10286            .await;
10287        assert!(turn.is_ok(), "prompt through the mock bridge: {turn:?}");
10288        connection.close().await.unwrap();
10289    }
10290
10291    #[tokio::test]
10292    async fn connect_mode_fails_closed_without_a_protocol_client_or_config() {
10293        let home = connect_scratch_home("fail");
10294        std::fs::write(
10295            home.join("opencode-tui.json"),
10296            r#"{"server": {"url": "http://127.0.0.1:1", "token": "connect-secret"}}"#,
10297        )
10298        .unwrap();
10299
10300        let gateway_only = connect_descriptor("acp-v1-jsonrpc");
10301        let Err(error) = open_connect_descriptor(&gateway_only, &home) else {
10302            panic!("an ACP connect endpoint has no gateway client yet");
10303        };
10304        let message = format!("{error:?}");
10305        assert!(message.contains("acp-v1-jsonrpc"));
10306        assert!(!message.contains("connect-secret"));
10307
10308        let unreadable = connect_descriptor("opencode-http-sse");
10309        let missing_home = connect_scratch_home("missing");
10310        let Err(error) = open_connect_descriptor(&unreadable, &missing_home) else {
10311            panic!("an unreadable connect config must fail closed");
10312        };
10313        let message = format!("{error:?}");
10314        assert!(message.contains("opencode-tui.json"));
10315        assert!(!message.contains("connect-secret"));
10316    }
10317
10318    // ---------------------------------------------------------------------
10319    // ORCH-7 — `harness.v1.jobs.list` / `jobs.get` over the committed fixtures
10320    // ---------------------------------------------------------------------
10321
10322    fn jobs_fixture_root() -> PathBuf {
10323        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
10324    }
10325
10326    /// Point only the three job-bearing homes at the fixtures. Nothing else is
10327    /// read, so the host machine's own harness homes cannot leak into a row.
10328    fn jobs_fixture_homes() -> Value {
10329        let root = jobs_fixture_root();
10330        json!({
10331            "claude_code": root.join("claude_jobs_home/projects"),
10332            "hermes": root.join("hermes_home/state.db"),
10333            "openclaw": root.join("openclaw_home"),
10334        })
10335    }
10336
10337    fn jobs_list(params: Value) -> Value {
10338        let mut service = HarnessSessionService::new();
10339        service.handle(request(1, "harness.v1.jobs.list", params))
10340    }
10341
10342    fn job_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10343        result["jobs"]
10344            .as_array()
10345            .expect("jobs is an array")
10346            .iter()
10347            .find(|job| job["id"] == id)
10348            .unwrap_or_else(|| panic!("no job `{id}` in {result}"))
10349    }
10350
10351    #[test]
10352    fn gateway_health_derives_from_running_probe_and_install_state() {
10353        let running = RunningInstance {
10354            method: RunningInstanceMethod::GatewayConnect,
10355            evidence: "gateway endpoint 127.0.0.1:18789 accepted a TCP connect".into(),
10356            checked_at_ms: 1,
10357        };
10358        let up = gateway_health(
10359            HarnessId::OPENCLAW,
10360            true,
10361            Some(&running),
10362            Some("2026.7.1-2"),
10363        );
10364        assert_eq!(up.state, GatewayState::Up);
10365        assert!(up.endpoint.as_deref().unwrap().starts_with("ws://"));
10366        assert_eq!(up.version.as_deref(), Some("2026.7.1-2"));
10367        // Hermes consults its own `gateway status` when the WAL heuristic says
10368        // nothing; a fake binary decides the verdict (the env var is global, so
10369        // the up/down cases run inside this one test, never in parallel).
10370        let dir = std::env::temp_dir().join(format!("supercode-orch17-{}", std::process::id()));
10371        std::fs::create_dir_all(&dir).unwrap();
10372        let fake = dir.join("hermes");
10373        let write_fake = |body: &str| {
10374            std::fs::write(&fake, format!("#!/bin/sh\n{body}\n")).unwrap();
10375            #[cfg(unix)]
10376            {
10377                use std::os::unix::fs::PermissionsExt;
10378                std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o755)).unwrap();
10379            }
10380        };
10381        write_fake("echo '✗ Gateway service is not installed'");
10382        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| {
10383            *slot.borrow_mut() = Some((
10384                HarnessId::HERMES.to_string(),
10385                fake.to_string_lossy().into_owned(),
10386            ))
10387        });
10388        let down = gateway_health(HarnessId::HERMES, true, None, None);
10389        assert_eq!(down.state, GatewayState::Down, "{down:?}");
10390        assert!(down.endpoint.is_none());
10391        assert!(down.evidence.contains("not installed"));
10392        write_fake("echo 'Launchd plist: /x/ai.hermes.gateway.plist'; echo '✓ Gateway is supervised by launchd (PID 4242)'");
10393        let idle_but_up = gateway_health(HarnessId::HERMES, true, None, Some("0.21.0"));
10394        assert_eq!(idle_but_up.state, GatewayState::Up, "{idle_but_up:?}");
10395        assert!(idle_but_up.evidence.contains("PID 4242"));
10396        write_fake("echo 'something unparseable'");
10397        let no_verdict = gateway_health(HarnessId::HERMES, true, None, None);
10398        assert_eq!(no_verdict.state, GatewayState::Down);
10399        assert!(no_verdict.evidence.contains("no verdict"));
10400        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| *slot.borrow_mut() = None);
10401        let absent = gateway_health(HarnessId::HERMES, false, None, None);
10402        assert_eq!(absent.state, GatewayState::Unknown);
10403        let core = gateway_health(HarnessId::CODEX, true, None, Some("0.144.4"));
10404        assert_eq!(core.state, GatewayState::Unknown);
10405        assert!(core.evidence.contains("per session"));
10406    }
10407
10408    #[test]
10409    fn triggers_list_reads_both_stores_and_never_emits_secrets() {
10410        let response = triggers_list(json!({"homes": jobs_fixture_homes()}));
10411        let rows = response["result"]["triggers"]
10412            .as_array()
10413            .expect("triggers")
10414            .clone();
10415        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10416        assert!(
10417            hermes.iter().any(|r| r["name"] == "deploys"
10418                && r["route"] == "/webhooks/deploys"
10419                && r["kind"] == "webhook"),
10420            "{rows:#?}"
10421        );
10422        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10423        assert!(openclaw
10424            .iter()
10425            .any(|r| r["name"] == "wake" && r["kind"] == "builtin_wake"));
10426        assert!(openclaw.iter().any(|r| r["name"] == "gmail"
10427            && r["kind"] == "hook_mapping"
10428            && r["target"]["action"] == "agent"));
10429        let rendered = response.to_string();
10430        for secret in [
10431            "FAKE-WEBHOOK-HMAC-DO-NOT-EMIT",
10432            "FAKE-HOOK-TOKEN-DO-NOT-EMIT",
10433        ] {
10434            assert!(!rendered.contains(secret), "{rendered}");
10435        }
10436        let refused =
10437            triggers_list(json!({"harness": "claude-code", "homes": jobs_fixture_homes()}));
10438        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10439    }
10440
10441    fn triggers_list(params: Value) -> Value {
10442        let mut service = HarnessSessionService::new();
10443        service.handle(request(1, "harness.v1.triggers.list", params))
10444    }
10445
10446    #[test]
10447    fn routes_list_reads_both_gateway_configs_and_flags_the_defaults() {
10448        let response = routes_list(json!({"homes": jobs_fixture_homes()}));
10449        let rows = response["result"]["routes"]
10450            .as_array()
10451            .expect("routes")
10452            .clone();
10453        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10454        assert_eq!(hermes.len(), 2, "{rows:#?}");
10455        assert_eq!(hermes[0]["target"], "coder");
10456        assert_eq!(hermes[0]["match"]["platform"], "slack");
10457        assert_eq!(hermes[0]["match"]["chat_id"], "C0FIXTURE");
10458        assert_eq!(hermes[0]["specificity"], 4);
10459        assert_eq!(hermes[1]["default"], true);
10460        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10461        assert!(
10462            openclaw.iter().any(|r| r["target"] == "design"
10463                && r["match"]["platform"] == "slack"
10464                && r["specificity"] == 1),
10465            "{openclaw:#?}"
10466        );
10467        assert!(openclaw.iter().any(|r| r["default"] == true));
10468        // A core harness has no routing concept and is refused, never an empty list.
10469        let refused = routes_list(json!({"harness": "codex", "homes": jobs_fixture_homes()}));
10470        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10471    }
10472
10473    fn routes_list(params: Value) -> Value {
10474        let mut service = HarnessSessionService::new();
10475        service.handle(request(1, "harness.v1.routes.list", params))
10476    }
10477
10478    #[test]
10479    fn jobs_list_projects_every_fixture_store_onto_the_uniform_row() {
10480        let response = jobs_list(json!({"homes": jobs_fixture_homes()}));
10481        let result = &response["result"];
10482        let ids: Vec<&str> = result["jobs"]
10483            .as_array()
10484            .unwrap()
10485            .iter()
10486            .map(|job| job["id"].as_str().unwrap())
10487            .collect();
10488        assert_eq!(
10489            ids,
10490            vec![
10491                "release-watch",
10492                "toolu_wake_recheck",
10493                "digest-15m",
10494                "nightly-audit",
10495                "coder-standup",
10496                "ops-once-boot",
10497                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10498                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10499                "cron_standup",
10500                "cron_reindex",
10501            ],
10502            "{result}"
10503        );
10504
10505        // OpenClaw, pinned shape: rows come from `state/openclaw.sqlite`
10506        // (`cron_jobs.job_json` + runtime columns), captured from a real
10507        // 2026.7.1-2 gateway.
10508        let health = job_row(result, "85ad7832-896f-42be-af31-3e1ed2fbdc4b");
10509        assert_eq!(health["harness"], "openclaw");
10510        assert_eq!(health["schedule"]["kind"], "interval");
10511        assert_eq!(health["schedule"]["minutes"], 10.0);
10512        assert_eq!(health["session_target"], "isolated");
10513        assert_eq!(health["payload"]["kind"], "prompt");
10514        assert_eq!(health["payload"]["text"], "nightly health check");
10515        // ORCH-13: the mode word (`announce`) and the channel it announces on
10516        // (`last`) are separate facts, and the store keeps both — in
10517        // `job_json.delivery` and in the `delivery_*` columns beside it.
10518        assert_eq!(health["deliver"]["mode"], "announce");
10519        assert_eq!(health["deliver"]["target"], "last");
10520        assert_eq!(health["next_run_at"], "2026-09-03T06:52:26Z");
10521        let digest = job_row(result, "8bb7d938-ca46-4a6d-90eb-c92331155566");
10522        assert_eq!(digest["schedule"]["kind"], "cron");
10523        assert_eq!(digest["schedule"]["expr"], "0 9 * * 1");
10524        assert_eq!(digest["session_target"], "main");
10525        assert_eq!(digest["payload"]["kind"], "system_event");
10526
10527        // Claude Code: session-scoped, one recurring cron and one one-shot wakeup.
10528        let cron = job_row(result, "release-watch");
10529        assert_eq!(cron["harness"], "claude-code");
10530        assert_eq!(cron["scope"], "session");
10531        assert_eq!(cron["session_id"], "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
10532        assert_eq!(cron["schedule"]["kind"], "cron");
10533        assert_eq!(cron["schedule"]["expr"], "*/10 * * * *");
10534        assert_eq!(cron["schedule"]["display"], "*/10 * * * *");
10535        assert_eq!(cron["payload"]["kind"], "prompt");
10536        assert_eq!(cron["recurring"], true);
10537        assert_eq!(cron["deliver"]["target"], "session");
10538        let wakeup = job_row(result, "toolu_wake_recheck");
10539        assert_eq!(wakeup["payload"]["kind"], "wakeup");
10540        assert_eq!(wakeup["schedule"]["kind"], "once");
10541        assert_eq!(wakeup["recurring"], false);
10542        assert_eq!(wakeup["state"], "pending");
10543
10544        // Hermes: install-scoped, interval + origin delivery, and a paused cron.
10545        let interval = job_row(result, "digest-15m");
10546        assert_eq!(interval["harness"], "hermes");
10547        assert_eq!(interval["scope"], "install");
10548        assert_eq!(interval["profile"], Value::Null);
10549        assert_eq!(interval["schedule"]["kind"], "interval");
10550        assert_eq!(interval["schedule"]["minutes"], 15.0);
10551        assert_eq!(interval["schedule"]["display"], "every 15 min");
10552        assert_eq!(interval["deliver"]["target"], "origin");
10553        assert_eq!(interval["deliver"]["chat_id"], "-1002233445566");
10554        assert_eq!(interval["next_run_at"], "2026-09-02T11:15:00Z");
10555        assert_eq!(interval["last_status"], "ok");
10556        let nightly = job_row(result, "nightly-audit");
10557        assert_eq!(nightly["schedule"]["expr"], "0 3 * * *");
10558        assert_eq!(nightly["deliver"]["target"], "local");
10559        assert_eq!(nightly["enabled"], false);
10560        assert_eq!(nightly["state"], "paused");
10561        // The per-profile store carries the profile name from its own path.
10562        let profiled = job_row(result, "ops-once-boot");
10563        assert_eq!(profiled["profile"], "ops");
10564        assert_eq!(profiled["schedule"]["kind"], "once");
10565        assert_eq!(profiled["schedule"]["run_at"], "2026-09-03T06:00:00Z");
10566        assert_eq!(profiled["payload"]["kind"], "script");
10567        // An explicit `<platform>:<chat>` target carries the chat itself.
10568        assert_eq!(profiled["deliver"]["target"], "slack:C0429ABCD");
10569        assert_eq!(profiled["deliver"]["chat_id"], "C0429ABCD");
10570        assert_eq!(profiled["recurring"], false);
10571
10572        // ORCH-13: a job delivering to its creating conversation carries that
10573        // conversation's whole surface — platform word, chat AND thread.
10574        let standup_to_group = job_row(result, "coder-standup");
10575        assert_eq!(standup_to_group["deliver"]["target"], "origin");
10576        assert_eq!(standup_to_group["deliver"]["chat_id"], "-100777");
10577        assert_eq!(standup_to_group["deliver"]["thread_id"], "55");
10578        // Hermes has no mode word and routes by adapter profile, not account.
10579        assert!(standup_to_group["deliver"]["mode"].is_null());
10580        assert!(standup_to_group["deliver"]["account"].is_null());
10581
10582        // OpenClaw: the session target and the delivery mode are the row's own
10583        // columns, not a footnote.
10584        let standup = job_row(result, "cron_standup");
10585        assert_eq!(standup["harness"], "openclaw");
10586        assert_eq!(standup["session_target"], "isolated");
10587        assert_eq!(standup["deliver"]["mode"], "announce");
10588        assert_eq!(standup["deliver"]["target"], "slack");
10589        assert_eq!(standup["deliver"]["chat_id"], "C0429ABCD");
10590        assert_eq!(standup["payload"]["kind"], "prompt");
10591        assert_eq!(standup["profile"], "main");
10592        let reindex = job_row(result, "cron_reindex");
10593        assert_eq!(reindex["session_target"], "main");
10594        assert_eq!(reindex["payload"]["kind"], "system_event");
10595        assert_eq!(reindex["schedule"]["kind"], "interval");
10596        assert_eq!(reindex["schedule"]["display"], "every 240 min");
10597        assert_eq!(reindex["enabled"], false);
10598
10599        // Every store consulted is named, so an empty answer is never silent.
10600        let states: Vec<(&str, &str)> = result["sources"]
10601            .as_array()
10602            .unwrap()
10603            .iter()
10604            .map(|source| {
10605                (
10606                    source["harness"].as_str().unwrap(),
10607                    source["state"].as_str().unwrap(),
10608                )
10609            })
10610            .collect();
10611        // The `coder` profile home has no cron store at all: it is named as
10612        // `absent_store`, not skipped, so "this profile schedules nothing" and
10613        // "this profile was never looked at" stay distinguishable.
10614        assert_eq!(
10615            states,
10616            vec![
10617                ("claude-code", "scanned"),
10618                ("hermes", "read"),
10619                ("hermes", "absent_store"),
10620                ("hermes", "read"),
10621                ("openclaw", "read"),
10622                ("openclaw", "read"),
10623            ],
10624            "{result}"
10625        );
10626    }
10627
10628    #[test]
10629    fn jobs_list_filters_by_harness_session_and_profile() {
10630        let by_harness = jobs_list(json!({"harness": "openclaw", "homes": jobs_fixture_homes()}));
10631        let ids: Vec<&str> = by_harness["result"]["jobs"]
10632            .as_array()
10633            .unwrap()
10634            .iter()
10635            .map(|job| job["id"].as_str().unwrap())
10636            .collect();
10637        assert_eq!(
10638            ids,
10639            vec![
10640                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10641                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10642                "cron_standup",
10643                "cron_reindex",
10644            ]
10645        );
10646
10647        let by_session = jobs_list(json!({
10648            "session": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
10649            "homes": jobs_fixture_homes(),
10650        }));
10651        let jobs = by_session["result"]["jobs"].as_array().unwrap();
10652        assert_eq!(jobs.len(), 2, "{by_session}");
10653        assert!(jobs
10654            .iter()
10655            .all(|job| job["harness"] == "claude-code" && job["scope"] == "session"));
10656
10657        let by_profile = jobs_list(json!({
10658            "harness": "hermes",
10659            "profile": "ops",
10660            "homes": jobs_fixture_homes(),
10661        }));
10662        let jobs = by_profile["result"]["jobs"].as_array().unwrap();
10663        assert_eq!(jobs.len(), 1, "{by_profile}");
10664        assert_eq!(jobs[0]["id"], "ops-once-boot");
10665    }
10666
10667    #[test]
10668    fn jobs_get_answers_with_the_row_and_the_verbatim_native_record() {
10669        let mut service = HarnessSessionService::new();
10670        let hermes = service.handle(request(
10671            1,
10672            "harness.v1.jobs.get",
10673            json!({"harness": "hermes", "id": "digest-15m", "homes": jobs_fixture_homes()}),
10674        ));
10675        assert_eq!(hermes["result"]["job"]["schedule"]["kind"], "interval");
10676        // Native fields the uniform row does not carry survive on `source`.
10677        assert_eq!(hermes["result"]["source"]["provider"], "nous");
10678        assert_eq!(hermes["result"]["source"]["failure_deliver"], "local");
10679
10680        let claude = service.handle(request(
10681            2,
10682            "harness.v1.jobs.get",
10683            json!({"harness": "claude-code", "id": "release-watch", "homes": jobs_fixture_homes()}),
10684        ));
10685        assert_eq!(claude["result"]["job"]["payload"]["kind"], "prompt");
10686        assert_eq!(
10687            claude["result"]["source"]["tool_use_id"],
10688            "toolu_cron_release_watch"
10689        );
10690
10691        let missing = service.handle(request(
10692            3,
10693            "harness.v1.jobs.get",
10694            json!({"harness": "hermes", "id": "no-such-job", "homes": jobs_fixture_homes()}),
10695        ));
10696        assert!(missing["error"]["message"]
10697            .as_str()
10698            .is_some_and(|message| message.contains("no scheduled job `no-such-job`")));
10699    }
10700
10701    #[test]
10702    fn jobs_refuse_a_harness_without_a_scheduled_job_concept() {
10703        let mut service = HarnessSessionService::new();
10704        for (id, method, params) in [
10705            (
10706                1,
10707                "harness.v1.jobs.list",
10708                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10709            ),
10710            (
10711                2,
10712                "harness.v1.jobs.get",
10713                json!({"harness": "codex", "id": "anything"}),
10714            ),
10715        ] {
10716            let response = service.handle(request(id, method, params));
10717            assert_eq!(response["error"]["code"], -32020, "{response}");
10718            assert!(response["error"]["message"]
10719                .as_str()
10720                .is_some_and(|message| message.contains("has no scheduled jobs")));
10721            assert!(response.get("result").is_none());
10722        }
10723    }
10724
10725    #[test]
10726    fn jobs_list_reports_a_migrated_openclaw_store_as_absent_instead_of_failing() {
10727        let scratch = std::env::temp_dir().join(format!(
10728            "supercode-jobs-migrated-{}-{}",
10729            std::process::id(),
10730            generated_session_id()
10731        ));
10732        std::fs::create_dir_all(&scratch).unwrap();
10733        let response = jobs_list(json!({
10734            "harness": "openclaw",
10735            "homes": {"openclaw": scratch.clone()},
10736        }));
10737        let result = &response["result"];
10738        assert_eq!(result["jobs"].as_array().unwrap().len(), 0, "{result}");
10739        assert_eq!(result["sources"][0]["state"], "absent_store");
10740        assert_eq!(result["sources"][0]["harness"], "openclaw");
10741        std::fs::remove_dir_all(&scratch).ok();
10742    }
10743
10744    // ---------------------------------------------------------------------
10745    // ORCH-8 — `harness.v1.runs.list` / `runs.get` over the committed fire
10746    // stores: Hermes's `cron/executions.db` (root home + profile home) and
10747    // OpenClaw's `cron_run_logs`. Every fixture row is written by
10748    // `tests/fixtures/gen_runs_fixtures.py` against the harnesses' own DDL.
10749    // ---------------------------------------------------------------------
10750
10751    /// The health job in the committed OpenClaw fixture, which fired twice.
10752    const OPENCLAW_HEALTH_JOB: &str = "85ad7832-896f-42be-af31-3e1ed2fbdc4b";
10753    /// The digest job, whose single fire predates run ids.
10754    const OPENCLAW_DIGEST_JOB: &str = "8bb7d938-ca46-4a6d-90eb-c92331155566";
10755
10756    fn runs_list(params: Value) -> Value {
10757        let mut service = HarnessSessionService::new();
10758        service.handle(request(1, "harness.v1.runs.list", params))
10759    }
10760
10761    fn run_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10762        result["runs"]
10763            .as_array()
10764            .expect("runs is an array")
10765            .iter()
10766            .find(|run| run["id"] == id)
10767            .unwrap_or_else(|| panic!("no run `{id}` in {result}"))
10768    }
10769
10770    #[test]
10771    fn runs_list_projects_both_fixture_stores_onto_the_uniform_row() {
10772        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10773        let result = &response["result"];
10774        let ids: Vec<&str> = result["runs"]
10775            .as_array()
10776            .expect("runs is an array")
10777            .iter()
10778            .map(|run| run["id"].as_str().unwrap())
10779            .collect();
10780        let digest_fire = format!("{OPENCLAW_DIGEST_JOB}#1");
10781        assert_eq!(
10782            ids,
10783            vec![
10784                // Hermes, newest claim first, root ledger then profile ledger.
10785                "b2c3d4e5f60718293a4b5c6d7e8f9012",
10786                "a1b2c3d4e5f60718293a4b5c6d7e8f90",
10787                "c3d4e5f60718293a4b5c6d7e8f901234",
10788                "f60718293a4b5c6d7e8f901234567890",
10789                "e5f60718293a4b5c6d7e8f9012345678",
10790                "d4e5f60718293a4b5c6d7e8f90123456",
10791                // OpenClaw, newest `ts` first.
10792                "run_health_0002",
10793                digest_fire.as_str(),
10794                "run_health_0001",
10795            ],
10796            "{result}"
10797        );
10798
10799        // The harness's OWN outcome word survives; nothing is renamed onto a
10800        // shared vocabulary.
10801        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10802        assert_eq!(failed["harness"], "hermes");
10803        assert_eq!(failed["job_id"], "job42");
10804        assert_eq!(failed["status"], "failed");
10805        assert_eq!(failed["error"], "provider returned 500 after 3 attempts");
10806        assert_eq!(failed["claimed_at"], "2026-09-02T13:05:00.100442");
10807
10808        // Hermes's `unknown` — an attempt whose owner died before writing a
10809        // terminal state — is a fourth status, not folded into `failed`.
10810        let abandoned = run_row(result, "d4e5f60718293a4b5c6d7e8f90123456");
10811        assert_eq!(abandoned["status"], "unknown");
10812        assert_eq!(abandoned["job_id"], "ops-once-boot");
10813
10814        // An unterminated fire has no finish, and no session is invented.
10815        let running = run_row(result, "c3d4e5f60718293a4b5c6d7e8f901234");
10816        assert_eq!(running["status"], "running");
10817        assert!(running["finished_at"].is_null(), "{running}");
10818        assert!(running["session_id"].is_null(), "{running}");
10819
10820        // OpenClaw records the session on the row itself, and epoch-ms
10821        // timestamps are rendered as RFC 3339.
10822        let ok = run_row(result, "run_health_0001");
10823        assert_eq!(ok["harness"], "openclaw");
10824        assert_eq!(ok["job_id"], OPENCLAW_HEALTH_JOB);
10825        assert_eq!(ok["status"], "ok");
10826        assert_eq!(ok["started_at"], "2026-09-02T08:30:00.000Z");
10827        assert_eq!(ok["finished_at"], "2026-09-02T08:30:30.000Z");
10828        assert_eq!(ok["session_id"], "3dd577ae-a0a3-4b5b-8063-f402be4f5fd4");
10829        // OpenClaw's run log is written once, at finish: there is no claim.
10830        assert!(ok["claimed_at"].is_null(), "{ok}");
10831
10832        // A run-log row with no `run_id` falls back to the store's own
10833        // `(job_id, seq)` key rather than being dropped.
10834        assert_eq!(run_row(result, &digest_fire)["status"], "skipped");
10835
10836        // ORCH-13: a fire whose delivery nothing recorded says so, rather than
10837        // borrowing a neighbouring fire's outcome. Both of these ran on jobs
10838        // that deliver `local` (or have no job record at all), so no
10839        // obligation is addressed to a surface they could match.
10840        for id in [
10841            "b2c3d4e5f60718293a4b5c6d7e8f9012",
10842            "d4e5f60718293a4b5c6d7e8f90123456",
10843        ] {
10844            assert!(run_row(result, id)["delivery"].is_null(), "{id}");
10845        }
10846
10847        // Every store consulted is named, including the profile home that has
10848        // no ledger — an empty history and an absent store are different.
10849        let sources = result["sources"].as_array().unwrap();
10850        let states: Vec<(&str, &str)> = sources
10851            .iter()
10852            .map(|source| {
10853                (
10854                    source["harness"].as_str().unwrap(),
10855                    source["state"].as_str().unwrap(),
10856                )
10857            })
10858            .collect();
10859        assert_eq!(
10860            states,
10861            vec![
10862                ("hermes", "read"),
10863                ("hermes", "absent_store"),
10864                ("hermes", "read"),
10865                ("openclaw", "read"),
10866            ],
10867            "{result}"
10868        );
10869        assert_eq!(sources[2]["profile"], "ops");
10870        assert!(sources[3]["path"]
10871            .as_str()
10872            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10873    }
10874
10875    #[test]
10876    fn runs_list_joins_a_hermes_fire_to_the_session_it_opened() {
10877        let response = runs_list(json!({
10878            "harness": "hermes",
10879            "job": "job42",
10880            "homes": jobs_fixture_homes(),
10881        }));
10882        let result = &response["result"];
10883        assert_eq!(result["runs"].as_array().unwrap().len(), 2, "{result}");
10884
10885        // Hermes writes NO link from an execution to its session. The fire
10886        // that ran the agent is joined to `cron_job42_<stamp>` because that
10887        // id's instant falls inside its [claimed_at, finished_at] window.
10888        let ran = run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90");
10889        assert_eq!(ran["session_id"], "cron_job42_20260902_120000");
10890
10891        // The later fire failed before opening one. Its window holds no
10892        // session, so the row says so instead of re-using the earlier fire's
10893        // — the join is per-FIRE, not per-job.
10894        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10895        assert!(failed["session_id"].is_null(), "{failed}");
10896    }
10897
10898    /// ORCH-13: where a fire's output went, read from each harness's own
10899    /// delivery record — Hermes's `delivery_obligations` ledger inside
10900    /// `state.db`, OpenClaw's `delivery_*` run-log columns.
10901    #[test]
10902    fn runs_list_reads_the_delivery_each_harness_recorded_for_a_fire() {
10903        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10904        let result = &response["result"];
10905
10906        // Hermes: the ledger is the GATEWAY's, keyed by conversation and
10907        // surface, so the fire's own [claimed_at, finished_at] window picks
10908        // the obligation. The fire succeeded and so did the send.
10909        let delivered = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
10910        assert_eq!(delivered["status"], "completed");
10911        assert_eq!(delivered["delivery"]["state"], "delivered");
10912        assert_eq!(delivered["delivery"]["target"], "telegram:-100777:55");
10913        assert_eq!(delivered["delivery"]["attempts"], 1);
10914        assert!(delivered["delivery"]["last_error"].is_null(), "{delivered}");
10915        assert_eq!(
10916            delivered["delivery"]["delivered_at"],
10917            "2026-09-02T09:00:30.400Z"
10918        );
10919
10920        // The next fire of the same job ALSO succeeded — and its output never
10921        // arrived. That is the fact `status` alone cannot carry.
10922        let undelivered = run_row(result, "f60718293a4b5c6d7e8f901234567890");
10923        assert_eq!(undelivered["status"], "completed");
10924        assert_eq!(undelivered["delivery"]["state"], "failed");
10925        assert_eq!(undelivered["delivery"]["attempts"], 3);
10926        assert_eq!(
10927            undelivered["delivery"]["last_error"],
10928            "telegram send failed: Bad Request: chat not found"
10929        );
10930        // Only a delivered obligation carries an instant of delivery; the
10931        // ledger's `updated_at` on a failed row dates the failure.
10932        assert!(
10933            undelivered["delivery"]["delivered_at"].is_null(),
10934            "{undelivered}"
10935        );
10936
10937        // OpenClaw writes the outcome onto the run-log row and declares the
10938        // address on the job, so the row's target is joined from `cron_jobs`.
10939        let announced = run_row(result, "run_health_0001");
10940        assert_eq!(announced["delivery"]["state"], "delivered");
10941        assert_eq!(announced["delivery"]["target"], "last");
10942        // Its run log counts no attempts and stamps no delivered-at.
10943        assert!(announced["delivery"]["attempts"].is_null(), "{announced}");
10944        assert!(
10945            announced["delivery"]["delivered_at"].is_null(),
10946            "{announced}"
10947        );
10948        let refused = run_row(result, "run_health_0002");
10949        assert_eq!(refused["delivery"]["state"], "not-delivered");
10950        assert_eq!(refused["delivery"]["last_error"], "channel_not_found");
10951
10952        // A run-log row with no delivery columns at all recorded no delivery:
10953        // the job's declared target is not evidence that anything was sent.
10954        let skipped = run_row(result, &format!("{OPENCLAW_DIGEST_JOB}#1"));
10955        assert!(skipped["delivery"].is_null(), "{skipped}");
10956    }
10957
10958    /// A Hermes fire whose session carries a `session_key` is matched on that
10959    /// key FIRST — the most specific question the ledger can answer. Proven by
10960    /// moving the obligations off the job's surface on a COPY of the fixture,
10961    /// so only the session-key question can still find them.
10962    #[test]
10963    fn runs_list_matches_a_hermes_obligation_by_the_session_key_first() {
10964        let scratch = std::env::temp_dir().join(format!(
10965            "supercode-runs-delivery-{}-{}",
10966            std::process::id(),
10967            generated_session_id()
10968        ));
10969        std::fs::create_dir_all(scratch.join("cron")).unwrap();
10970        let fixture = jobs_fixture_root().join("hermes_home");
10971        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
10972        for name in ["cron/executions.db", "cron/jobs.json"] {
10973            std::fs::copy(fixture.join(name), scratch.join(name)).unwrap();
10974        }
10975        {
10976            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
10977            // The obligations now sit on a surface no job in this store
10978            // delivers to, so the surface question cannot match them.
10979            connection
10980                .execute(
10981                    "UPDATE delivery_obligations SET platform = 'slack', chat_id = 'C0FALLBACK'",
10982                    [],
10983                )
10984                .unwrap();
10985            // A cron fire that ran inside a keyed conversation: the session
10986            // the window recovers carries `tg-coder-1`'s key.
10987            connection
10988                .execute(
10989                    "INSERT INTO sessions (id, source, session_key, started_at) VALUES \
10990                     ('cron_coder-standup_20260902_090010', 'cron', \
10991                      'agent:coder:telegram:group:-100777:55', 1788339610.0)",
10992                    [],
10993                )
10994                .unwrap();
10995        }
10996        let response = runs_list(json!({
10997            "harness": "hermes",
10998            "job": "coder-standup",
10999            "homes": {"hermes": scratch.join("state.db")},
11000        }));
11001        let result = &response["result"];
11002        let matched = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
11003        assert_eq!(
11004            matched["session_id"], "cron_coder-standup_20260902_090010",
11005            "{result}"
11006        );
11007        assert_eq!(matched["delivery"]["state"], "delivered", "{result}");
11008        assert_eq!(
11009            matched["delivery"]["target"], "slack:C0FALLBACK:55",
11010            "{result}"
11011        );
11012        std::fs::remove_dir_all(&scratch).ok();
11013    }
11014
11015    #[test]
11016    fn runs_list_follows_a_compression_chain_to_the_readable_tip() {
11017        // A fire whose session was compressed mid-run is only readable at the
11018        // continuation, so that is what the row must report. Built on a COPY
11019        // of the committed fixture: no test writes to a fixture or to a real
11020        // harness home.
11021        let scratch = std::env::temp_dir().join(format!(
11022            "supercode-runs-compressed-{}-{}",
11023            std::process::id(),
11024            generated_session_id()
11025        ));
11026        std::fs::create_dir_all(scratch.join("cron")).unwrap();
11027        let fixture = jobs_fixture_root().join("hermes_home");
11028        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
11029        std::fs::copy(
11030            fixture.join("cron/executions.db"),
11031            scratch.join("cron/executions.db"),
11032        )
11033        .unwrap();
11034        {
11035            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
11036            connection
11037                .execute(
11038                    "UPDATE sessions SET end_reason = 'compression' WHERE id = ?1",
11039                    ["cron_job42_20260902_120000"],
11040                )
11041                .unwrap();
11042            connection
11043                .execute(
11044                    "INSERT INTO sessions (id, source, parent_session_id, started_at) \
11045                     VALUES ('job42-after-compaction', 'cron', \
11046                             'cron_job42_20260902_120000', 1788350000.0)",
11047                    [],
11048                )
11049                .unwrap();
11050        }
11051        let response = runs_list(json!({
11052            "harness": "hermes",
11053            "job": "job42",
11054            "homes": {"hermes": scratch.join("state.db")},
11055        }));
11056        let result = &response["result"];
11057        assert_eq!(
11058            run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90")["session_id"],
11059            "job42-after-compaction",
11060            "{result}"
11061        );
11062        std::fs::remove_dir_all(&scratch).ok();
11063    }
11064
11065    #[test]
11066    fn runs_list_filters_by_job_and_caps_by_limit() {
11067        let by_job = runs_list(json!({
11068            "harness": "openclaw",
11069            "job": OPENCLAW_HEALTH_JOB,
11070            "homes": jobs_fixture_homes(),
11071        }));
11072        let ids: Vec<&str> = by_job["result"]["runs"]
11073            .as_array()
11074            .unwrap()
11075            .iter()
11076            .map(|run| run["id"].as_str().unwrap())
11077            .collect();
11078        assert_eq!(ids, vec!["run_health_0002", "run_health_0001"], "{by_job}");
11079
11080        let capped = runs_list(json!({
11081            "harness": "openclaw",
11082            "limit": 1,
11083            "homes": jobs_fixture_homes(),
11084        }));
11085        let runs = capped["result"]["runs"].as_array().unwrap();
11086        assert_eq!(runs.len(), 1, "{capped}");
11087        // Newest first, so the cap keeps the recent fire.
11088        assert_eq!(runs[0]["id"], "run_health_0002");
11089    }
11090
11091    #[test]
11092    fn runs_get_answers_with_the_row_and_the_verbatim_native_record() {
11093        let mut service = HarnessSessionService::new();
11094        let hermes = service.handle(request(
11095            1,
11096            "harness.v1.runs.get",
11097            json!({
11098                "harness": "hermes",
11099                "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
11100                "homes": jobs_fixture_homes(),
11101            }),
11102        ));
11103        assert_eq!(hermes["result"]["run"]["status"], "completed");
11104        assert_eq!(
11105            hermes["result"]["run"]["session_id"],
11106            "cron_job42_20260902_120000"
11107        );
11108        // Ledger columns the uniform row does not carry survive on `source`.
11109        assert_eq!(hermes["result"]["source"]["source"], "scheduler");
11110        assert_eq!(hermes["result"]["source"]["pid"], 4242);
11111        assert_eq!(hermes["result"]["source"]["process_id"], "9f1c2d");
11112
11113        let openclaw = service.handle(request(
11114            2,
11115            "harness.v1.runs.get",
11116            json!({
11117                "harness": "openclaw",
11118                "id": "run_health_0002",
11119                "homes": jobs_fixture_homes(),
11120            }),
11121        ));
11122        assert_eq!(openclaw["result"]["run"]["status"], "error");
11123        // ORCH-13: the run's delivery is projected AND the store's own columns
11124        // stay verbatim on `source`, so nothing about the fire is lost.
11125        assert_eq!(
11126            openclaw["result"]["source"]["delivery_status"],
11127            "not-delivered"
11128        );
11129        assert_eq!(
11130            openclaw["result"]["source"]["delivery_error"],
11131            "channel_not_found"
11132        );
11133        assert_eq!(openclaw["result"]["source"]["delivered"], 0);
11134        assert_eq!(
11135            openclaw["result"]["run"]["delivery"]["state"],
11136            "not-delivered"
11137        );
11138        assert_eq!(
11139            openclaw["result"]["run"]["delivery"]["last_error"],
11140            "channel_not_found"
11141        );
11142
11143        let missing = service.handle(request(
11144            3,
11145            "harness.v1.runs.get",
11146            json!({"harness": "hermes", "id": "no-such-run", "homes": jobs_fixture_homes()}),
11147        ));
11148        assert!(missing["error"]["message"]
11149            .as_str()
11150            .is_some_and(|message| message.contains("no run `no-such-run`")));
11151    }
11152
11153    #[test]
11154    fn runs_refuse_a_harness_that_keeps_no_run_store() {
11155        let mut service = HarnessSessionService::new();
11156        for (id, method, params) in [
11157            // Claude Code HAS scheduled jobs but no fire store: its fires are
11158            // ordinary turns. It must refuse, not answer with an empty list.
11159            (
11160                1,
11161                "harness.v1.runs.list",
11162                json!({"harness": "claude-code", "homes": jobs_fixture_homes()}),
11163            ),
11164            (
11165                2,
11166                "harness.v1.runs.get",
11167                json!({"harness": "claude-code", "id": "anything"}),
11168            ),
11169            (
11170                3,
11171                "harness.v1.runs.list",
11172                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
11173            ),
11174        ] {
11175            let response = service.handle(request(id, method, params));
11176            assert_eq!(response["error"]["code"], -32020, "{response}");
11177            assert!(response["error"]["message"]
11178                .as_str()
11179                .is_some_and(|message| message.contains("keeps no run store")));
11180            assert!(response.get("result").is_none());
11181        }
11182    }
11183
11184    #[test]
11185    fn runs_list_reports_an_install_with_no_run_store_as_absent() {
11186        let scratch = std::env::temp_dir().join(format!(
11187            "supercode-runs-empty-{}-{}",
11188            std::process::id(),
11189            generated_session_id()
11190        ));
11191        std::fs::create_dir_all(&scratch).unwrap();
11192        let response = runs_list(json!({
11193            "harness": "openclaw",
11194            "homes": {"openclaw": scratch.clone()},
11195        }));
11196        let result = &response["result"];
11197        assert_eq!(result["runs"].as_array().unwrap().len(), 0, "{result}");
11198        assert_eq!(result["sources"][0]["state"], "absent_store");
11199        assert!(result["sources"][0]["path"]
11200            .as_str()
11201            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
11202        std::fs::remove_dir_all(&scratch).ok();
11203    }
11204}