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::runtime::generated_session_id;
17#[cfg(feature = "adapter-api")]
18use crate::runtime::{HostedHarnessConnection, HostedHarnessRuntime};
19use crate::sdk::{
20    discover_session_page, load_session, load_session_with_fidelity, SdkCapabilities, SdkError,
21    SdkErrorCode, SdkEvent, SdkOperation, SdkRequest, SdkRuntimeEvent, SdkService,
22};
23use crate::watch::{bound_session_view, message_json, normalized_session_json};
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};
35use crate::{reduce, tokens};
36#[cfg(feature = "adapter-api")]
37use crate::{register_live_runtime, resolve_live_runtime, LiveRuntimeRegistration};
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.index.subscribe",
57    "harness.v1.sessions.index.resize",
58    "harness.v1.sessions.index.unsubscribe",
59    "harness.v1.sessions.message",
60    "harness.v1.sessions.import",
61    "harness.v1.sessions.export",
62    "harness.v1.sessions.translate",
63    "harness.v1.sessions.reduce",
64    "harness.v1.sessions.branch",
65    "harness.v1.sessions.handoff",
66    "harness.v1.sessions.resume_instructions",
67    "harness.v1.skills.list",
68    "harness.v1.skills.install",
69    "harness.v1.skills.remove",
70    "harness.v1.memory.show",
71    "harness.v1.memory.search",
72    "harness.v1.jobs.list",
73    "harness.v1.jobs.get",
74    "harness.v1.jobs.create",
75    "harness.v1.jobs.update",
76    "harness.v1.jobs.pause",
77    "harness.v1.jobs.resume",
78    "harness.v1.jobs.run",
79    "harness.v1.jobs.delete",
80    "harness.v1.sessions.new",
81    "harness.v1.sessions.reset",
82    "harness.v1.sessions.archive",
83    "harness.v1.sessions.delete",
84    "harness.v1.runs.list",
85    "harness.v1.runs.get",
86    "harness.v1.approvals.list",
87    "harness.v1.approvals.resolve",
88    "harness.v1.runtimes.capabilities",
89    "harness.v1.runtimes.start",
90    "harness.v1.runtimes.resume",
91    "harness.v1.runtimes.attach_existing",
92    "harness.v1.runtimes.attach",
93    "harness.v1.runtimes.send_input",
94    "harness.v1.runtimes.interrupt",
95    "harness.v1.runtimes.steer",
96    "harness.v1.runtimes.respond",
97    "harness.v1.runtimes.terminal_instructions",
98    "harness.v1.runtimes.close",
99    "harness.v1.profiles.list",
100    "harness.v1.profiles.get",
101    "harness.v1.profiles.create",
102    "harness.v1.profiles.delete",
103    "harness.v1.channels.list",
104    "harness.v1.routes.list",
105    "harness.v1.triggers.list",
106    "harness.v1.channels.status",
107    "harness.v1.orchestration.load",
108    "harness.v1.orchestration.save",
109    "harness.v1.orchestration.compile",
110    "harness.v1.orchestration.decompile",
111    "harness.v1.orchestration.import",
112    "harness.v1.orchestration.export",
113    "harness.v1.workflow.load",
114];
115
116/// Protocol namespace implemented by this service.
117pub const HARNESS_SERVICE_VERSION: &str = "harness.v1";
118/// Notification method emitted for followed-session changes.
119pub const SESSION_EVENT_METHOD: &str = "harness.v1.sessions.event";
120/// Notification method emitted for normalized session-activity transitions.
121pub const SESSION_ACTIVITY_EVENT_METHOD: &str = "harness.v1.sessions.activity_event";
122/// Notification method emitted for revisioned session-list changes.
123pub const SESSION_INDEX_EVENT_METHOD: &str = "harness.v1.sessions.index_event";
124/// Notification method emitted for live runtime events.
125pub const RUNTIME_EVENT_METHOD: &str = "harness.v1.runtimes.event";
126
127/// Stateful persisted-session service. Each instance owns its follow
128/// subscriptions; discovery and loading remain read-only.
129pub struct HarnessSessionService {
130    catalog: HarnessCatalog,
131    followers: BTreeMap<String, SessionFollower>,
132    followed_sources: BTreeMap<String, FollowedSource>,
133    activity_subscriptions: BTreeMap<String, ActivitySubscription>,
134    index_subscriptions: BTreeMap<String, crate::session_index::SessionIndexSubscription>,
135    index_notifier: Arc<Notify>,
136    #[cfg(feature = "adapter-api")]
137    activity_monitor: crate::session_activity::SessionActivityMonitor,
138    next_subscription: u64,
139    runtimes: BTreeMap<String, Box<dyn RuntimeConnection>>,
140    terminal_launches: BTreeMap<String, StructuredLaunch>,
141    runtime_sequences: BTreeMap<String, u64>,
142    next_runtime: u64,
143    reduction_store_root: Option<PathBuf>,
144    /// ORCH-9: live permission/approval requests outstanding on the open
145    /// runtime connections above, fed by the same event pump that publishes
146    /// `harness.v1.runtimes.event`.
147    approvals: crate::approvals::ApprovalRegistry,
148    /// ORCH-9: supercode's own queued subagent approvals, when the host that
149    /// owns this service publishes its parent queue here.
150    subagent_approvals: Option<Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>>,
151}
152
153impl Default for HarnessSessionService {
154    fn default() -> Self {
155        Self::new()
156    }
157}
158
159impl HarnessSessionService {
160    /// Create an empty service instance.
161    pub fn new() -> Self {
162        Self {
163            catalog: HarnessCatalog::new(),
164            followers: BTreeMap::new(),
165            followed_sources: BTreeMap::new(),
166            activity_subscriptions: BTreeMap::new(),
167            index_subscriptions: BTreeMap::new(),
168            index_notifier: Arc::new(Notify::new()),
169            #[cfg(feature = "adapter-api")]
170            activity_monitor: Default::default(),
171            next_subscription: 1,
172            runtimes: BTreeMap::new(),
173            terminal_launches: BTreeMap::new(),
174            runtime_sequences: BTreeMap::new(),
175            next_runtime: 1,
176            reduction_store_root: None,
177            approvals: crate::approvals::ApprovalRegistry::new(),
178            subagent_approvals: None,
179        }
180    }
181
182    /// Override the trusted, service-owned store used for durable reduction
183    /// bundles. Embedders and tests use this to keep all writes inside an
184    /// explicitly selected root; the CLI otherwise uses the normal
185    /// `$SUPERCODE_HOME/sessions` location.
186    pub fn with_reduction_store_root(mut self, root: impl Into<PathBuf>) -> Self {
187        self.reduction_store_root = Some(root.into());
188        self
189    }
190
191    /// ORCH-9: publish the parent's own subagent-approval queue into
192    /// `harness.v1.approvals.list`.
193    ///
194    /// This is the SAME `Arc` an [`crate::Agent`] pushes into
195    /// (`Agent::pending_child_approvals`), so a host that runs supercode's own
196    /// loop beside this service surfaces those requests through the uniform
197    /// door without copying them anywhere.
198    pub fn observe_subagent_approvals(
199        &mut self,
200        queue: Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>,
201    ) {
202        self.subagent_approvals = Some(queue);
203    }
204
205    /// ORCH-9: every approval request this service can see, newest last.
206    ///
207    /// Two sources, both live: the requests outstanding on the open runtime
208    /// connections, and supercode's own queued subagent approvals. There is
209    /// no file or database source at the pinned harness versions (see
210    /// [`crate::approvals`]), so a stored or proposal row is never produced.
211    pub fn approvals(&self, query: &crate::approvals::ApprovalsQuery) -> Vec<crate::ApprovalRow> {
212        let now = crate::approvals::now_ms();
213        let mut rows = self.approvals.rows(now);
214        if let Some(queue) = self.subagent_approvals.as_ref() {
215            let queued = queue
216                .lock()
217                .unwrap_or_else(std::sync::PoisonError::into_inner)
218                .clone();
219            rows.extend(crate::approvals::subagent_rows(&queued, now));
220        }
221        rows.retain(|row| query.matches(row));
222        rows.sort_by(|left, right| {
223            left.requested_at_ms
224                .cmp(&right.requested_at_ms)
225                .then_with(|| left.id.cmp(&right.id))
226        });
227        rows
228    }
229
230    /// ORCH-20 (controlled tier): answer one listed approval request by its
231    /// row id and one uniform decision.
232    ///
233    /// The decision is translated into the option token and reply envelope
234    /// the door that raised the request already accepts
235    /// ([`crate::approvals::plan_reply`]), and the answer is then sent by
236    /// calling `harness.v1.runtimes.respond` itself — the same code path, the
237    /// same adapter, the same bookkeeping that drops the row. This verb adds
238    /// a translation and nothing else.
239    async fn approvals_resolve(
240        &mut self,
241        params: Value,
242    ) -> std::result::Result<Value, ServiceError> {
243        let params = decode::<crate::approvals::ApprovalsResolveParams>(params)?;
244        if params.id.trim().is_empty() {
245            return Err(ServiceError::InvalidParams(
246                "approvals resolve requires the `id` of a listed approval row".into(),
247            ));
248        }
249        let choice = match (params.decision, params.option_id.as_deref()) {
250            (Some(_), Some(_)) => {
251                return Err(ServiceError::InvalidParams(
252                    "approvals resolve takes either `decision` or `option_id`, not both".into(),
253                ))
254            }
255            (Some(decision), None) => crate::approvals::ApprovalChoice::Decision(decision),
256            (None, Some(option)) => crate::approvals::ApprovalChoice::Option(option.to_string()),
257            (None, None) => {
258                return Err(ServiceError::InvalidParams(format!(
259                    "approvals resolve requires `decision` ({}) or an explicit `option_id`",
260                    crate::approvals::ApprovalDecision::ALL
261                        .map(|decision| decision.as_str())
262                        .join(" | "),
263                )))
264            }
265        };
266        let resolution = self
267            .approvals
268            .resolution(&params.id, &choice)
269            .map_err(|error| ServiceError::InvalidParams(error.to_string()))?;
270        // The harness's own door, unchanged: this is the identical call
271        // `harness.v1.runtimes.respond` performs for a caller who built the
272        // envelope by hand, including dropping the answered row.
273        self.runtime_call(
274            "harness.v1.runtimes.respond",
275            json!({
276                "connection": resolution.connection,
277                "request_id": resolution.request_id,
278                "response": resolution.response,
279            }),
280        )
281        .await?;
282        Ok(json!({
283            "id": params.id,
284            "decision": params.decision.map(|decision| decision.as_str()),
285            "option_id": resolution.option_id,
286            "resolved": true,
287        }))
288    }
289
290    /// Return the edge-triggered wakeup used by session-index filesystem
291    /// subscriptions. Transports can await this instead of polling indexes.
292    #[cfg(feature = "adapter-api")]
293    pub fn session_index_notifier(&self) -> Arc<Notify> {
294        Arc::clone(&self.index_notifier)
295    }
296
297    /// Handle one JSON-RPC 2.0 request and return one JSON-RPC response.
298    #[cfg(feature = "adapter-api")]
299    pub fn handle(&mut self, request: Value) -> Value {
300        let id = request.get("id").cloned().unwrap_or(Value::Null);
301        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
302            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
303        }
304        let Some(method) = request.get("method").and_then(Value::as_str) else {
305            return rpc_error(id, -32600, "request is missing `method`");
306        };
307        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
308        match self.call(method, params) {
309            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
310            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
311            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
312            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
313            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
314            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
315        }
316    }
317
318    /// Handle either a persisted-session request or an asynchronous live
319    /// runtime request.
320    #[cfg(feature = "adapter-api")]
321    pub async fn handle_async(&mut self, request: Value) -> Value {
322        let method = request
323            .get("method")
324            .and_then(Value::as_str)
325            .unwrap_or_default();
326        if matches!(
327            method,
328            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe"
329        ) {
330            let id = request.get("id").cloned().unwrap_or(Value::Null);
331            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
332                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
333            }
334            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
335            return match self.inventory_call(method, params).await {
336                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
337                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
338                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
339                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
340                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
341                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
342            };
343        }
344        if matches!(
345            method,
346            "harness.v1.harnesses.auth.methods"
347                | "harness.v1.harnesses.auth.begin"
348                | "harness.v1.harnesses.auth.verify"
349        ) {
350            let id = request.get("id").cloned().unwrap_or(Value::Null);
351            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
352                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
353            }
354            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
355            return match self.harness_authentication_call(method, params).await {
356                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
357                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
358                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
359                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
360                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
361                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
362            };
363        }
364        // ORCH-19 controlled tier. Answered here rather than through the SDK
365        // operation dispatch below so the harness's OWN refusal reaches the
366        // caller: `sdk_error` collapses every `UnsupportedAction` to one
367        // generic sentence, and the whole point of this tier is that a
368        // refusal names which door the harness does have.
369        if matches!(
370            method,
371            "harness.v1.sessions.new"
372                | "harness.v1.sessions.reset"
373                | "harness.v1.sessions.archive"
374                | "harness.v1.sessions.delete"
375        ) {
376            let id = request.get("id").cloned().unwrap_or(Value::Null);
377            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
378                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
379            }
380            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
381            let verb = match method {
382                "harness.v1.sessions.new" => crate::SessionVerb::New,
383                "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
384                "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
385                _ => crate::SessionVerb::Delete,
386            };
387            return match self.mutate_session(verb, params).await {
388                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
389                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
390                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
391                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
392                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
393                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
394            };
395        }
396        if method == "harness.v1.sessions.message" {
397            let id = request.get("id").cloned().unwrap_or(Value::Null);
398            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
399                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
400            }
401            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
402            return match self.message_call(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 matches!(
412            method,
413            "harness.v1.harnesses.settings" | "harness.v1.harnesses.configure"
414        ) {
415            let id = request.get("id").cloned().unwrap_or(Value::Null);
416            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
417                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
418            }
419            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
420            return match self.harness_settings_call(method, params) {
421                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
422                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
423                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
424                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
425                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
426                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
427            };
428        }
429        if method == "harness.v1.sessions.activity.subscribe" {
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.subscribe_session_activity(params).await {
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 let Some(operation) = SdkOperation::from_method(method) {
445            let id = request.get("id").cloned().unwrap_or(Value::Null);
446            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
447                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
448            }
449            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
450            return match self.execute(SdkRequest { operation, params }).await {
451                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
452                Err(error) => sdk_rpc_error(id, &error),
453            };
454        }
455        if !method.starts_with("harness.v1.runtimes.") {
456            return self.handle(request);
457        }
458        let id = request.get("id").cloned().unwrap_or(Value::Null);
459        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
460            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
461        }
462        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
463        match self.runtime_call(method, params).await {
464            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
465            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
466            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
467            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
468            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
469            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
470        }
471    }
472
473    /// Poll all active subscriptions once and return zero or more JSON-RPC
474    /// notifications. Recoverable follower errors are delivered as events.
475    #[cfg(feature = "adapter-api")]
476    pub fn poll(&mut self) -> Vec<Value> {
477        let mut notifications = Vec::new();
478        for (subscription, follower) in &mut self.followers {
479            match follower.poll() {
480                Ok(Some(event)) => notifications.push(json!({
481                    "jsonrpc": "2.0",
482                    "method": SESSION_EVENT_METHOD,
483                    "params": {
484                        "subscription": subscription,
485                        "event": event.to_json(),
486                    }
487                })),
488                Ok(None) => {}
489                Err(error) => notifications.push(json!({
490                    "jsonrpc": "2.0",
491                    "method": SESSION_EVENT_METHOD,
492                    "params": {
493                        "subscription": subscription,
494                        "event": {
495                            "type": "watch_error",
496                            "recoverable": true,
497                            "message": error.to_string(),
498                        },
499                    }
500                })),
501            }
502        }
503        notifications
504    }
505
506    /// Report each followed session's live-runtime lifecycle state on that
507    /// session's own subscription, emitting only when the state changes.
508    ///
509    /// A growing transcript is not evidence that an agent is working, so the
510    /// state comes from the live-runtime registry and nowhere else. A followed
511    /// session with no registered Supercode runtime — a harness running outside
512    /// Supercode — reports `persisted`, which says plainly that its activity is
513    /// unknown rather than guessing at it. These events carry no sequence
514    /// number and no transcript content; they never interleave with the
515    /// content follower's sequenced stream.
516    #[cfg(feature = "adapter-api")]
517    pub async fn poll_session_runtime_states(&mut self) -> Vec<Value> {
518        let registry = crate::LocalRuntimeRegistry::new();
519        let authorization = crate::RuntimeAuthorization::observer();
520        let mut notifications = Vec::new();
521        for (subscription, source) in &mut self.followed_sources {
522            let state = match registry
523                .source_state(&source.harness, &source.session_id, &authorization)
524                .await
525            {
526                Ok(Some(state)) => state,
527                Ok(None) => crate::RuntimeRegistryState::Persisted,
528                // A failed registry read is not evidence of a state change.
529                Err(_) => continue,
530            };
531            if source.reported.as_deref() == Some(state.as_str()) {
532                continue;
533            }
534            source.reported = Some(state.as_str().to_string());
535            notifications.push(json!({
536                "jsonrpc": "2.0",
537                "method": SESSION_EVENT_METHOD,
538                "params": {
539                    "subscription": subscription,
540                    "event": {"type": "runtime_state", "state": state.as_str()},
541                },
542            }));
543        }
544        notifications
545    }
546
547    /// Poll normalized activity subscriptions, emitting only proven state
548    /// transitions. Every subscription is bulk-sampled so stock-harness
549    /// process and registry discovery happens once per UI, not once per row.
550    #[cfg(feature = "adapter-api")]
551    pub async fn poll_session_activities(&mut self) -> Vec<Value> {
552        let subscriptions = self
553            .activity_subscriptions
554            .iter()
555            .map(|(id, subscription)| {
556                (
557                    id.clone(),
558                    subscription.locators.clone(),
559                    subscription.homes.clone(),
560                )
561            })
562            .collect::<Vec<_>>();
563        let mut notifications = Vec::new();
564        for (subscription_id, locators, homes) in subscriptions {
565            let Ok(activities) = self.activity_monitor.resolve(&locators, &homes).await else {
566                // A failed evidence read proves no transition. Retain the last
567                // good state instead of flashing every row to persisted.
568                continue;
569            };
570            let Some(subscription) = self.activity_subscriptions.get_mut(&subscription_id) else {
571                continue;
572            };
573            let mut changed = Vec::new();
574            for activity in activities {
575                let key = activity.key();
576                if subscription
577                    .reported
578                    .get(&key)
579                    .is_some_and(|previous| previous.same_state(&activity))
580                {
581                    continue;
582                }
583                subscription.reported.insert(key, activity.clone());
584                changed.push(activity);
585            }
586            if !changed.is_empty() {
587                notifications.push(json!({
588                    "jsonrpc": "2.0",
589                    "method": SESSION_ACTIVITY_EVENT_METHOD,
590                    "params": {
591                        "subscription": subscription_id,
592                        "activities": changed,
593                    },
594                }));
595            }
596        }
597        notifications
598    }
599
600    /// Drain native-store invalidations and emit revisioned descriptor deltas.
601    /// An idle subscription performs no catalog or transcript reads between
602    /// its minute-scale recovery reconciliations.
603    #[cfg(feature = "adapter-api")]
604    pub fn poll_session_indexes(&mut self) -> Vec<Value> {
605        let mut notifications = Vec::new();
606        for (subscription, index) in &mut self.index_subscriptions {
607            let homes = index.homes().clone();
608            match index.poll() {
609                Ok(Some(delta)) => match live_index_changes(delta.changes, &homes) {
610                    Ok(changes) => notifications.push(json!({
611                        "jsonrpc": "2.0",
612                        "method": SESSION_INDEX_EVENT_METHOD,
613                        "params": {
614                            "subscription": subscription,
615                            "revision": delta.revision,
616                            "changes": changes,
617                        },
618                    })),
619                    Err(error) => notifications.push(json!({
620                        "jsonrpc": "2.0",
621                        "method": SESSION_INDEX_EVENT_METHOD,
622                        "params": {
623                            "subscription": subscription,
624                            "error": {"recoverable": true, "message": error_message(error)},
625                        },
626                    })),
627                },
628                Ok(None) => {}
629                Err(error) => notifications.push(json!({
630                    "jsonrpc": "2.0",
631                    "method": SESSION_INDEX_EVENT_METHOD,
632                    "params": {
633                        "subscription": subscription,
634                        "error": {"recoverable": true, "message": error},
635                    },
636                })),
637            }
638        }
639        notifications
640    }
641
642    #[cfg(feature = "adapter-api")]
643    async fn subscribe_session_activity(
644        &mut self,
645        params: Value,
646    ) -> std::result::Result<Value, ServiceError> {
647        let params = decode::<ActivitySubscribeParams>(params)?;
648        if params.locators.is_empty() {
649            return Err(ServiceError::InvalidParams(
650                "sessions.activity.subscribe requires at least one locator".into(),
651            ));
652        }
653        if params.locators.len() > 2_048 {
654            return Err(ServiceError::InvalidParams(
655                "sessions.activity.subscribe accepts at most 2048 locators".into(),
656            ));
657        }
658        let initial = self
659            .activity_monitor
660            .resolve(&params.locators, &params.homes)
661            .await
662            .map_err(ServiceError::Sdk)?;
663        let subscription = format!("activity-sub-{}", self.next_subscription);
664        self.next_subscription += 1;
665        let reported = initial
666            .iter()
667            .cloned()
668            .map(|activity| (activity.key(), activity))
669            .collect();
670        self.activity_subscriptions.insert(
671            subscription.clone(),
672            ActivitySubscription {
673                locators: params.locators,
674                homes: params.homes,
675                reported,
676            },
677        );
678        Ok(json!({"subscription": subscription, "initial": initial}))
679    }
680
681    /// Non-blockingly sample one event from every connected live runtime.
682    #[cfg(feature = "adapter-api")]
683    pub async fn poll_runtimes(&mut self) -> Vec<Value> {
684        self.poll_sdk_events()
685            .await
686            .into_iter()
687            .map(|(connection, runtime_event)| {
688                json!({
689                    "jsonrpc": "2.0",
690                    "method": RUNTIME_EVENT_METHOD,
691                    "params": {
692                        "connection": connection,
693                        "session_id": runtime_event.session_id,
694                        "sequence": runtime_event.event.sequence,
695                        "event": {
696                            "kind": runtime_event.event.kind,
697                            "payload": runtime_event.event.payload,
698                        },
699                    },
700                })
701            })
702            .collect()
703    }
704
705    async fn poll_sdk_events(&mut self) -> Vec<(String, SdkRuntimeEvent)> {
706        let mut events = Vec::new();
707        let mut closed = Vec::new();
708        let now_ms = crate::approvals::now_ms();
709        for (connection, runtime) in &mut self.runtimes {
710            let session_id = runtime.handle().runtime_id.clone();
711            let harness = runtime.handle().harness.clone();
712            // Drain what the runtime already has: a turn is several events
713            // (updates, then the protocol's completion), and delivering one
714            // per poll would cost a poll interval each. A zero timeout takes
715            // only what is ready — an idle runtime costs nothing.
716            for _ in 0..256 {
717                match tokio::time::timeout(Duration::ZERO, runtime.next_event()).await {
718                    Ok(Ok(Some(event))) => {
719                        let terminal = event.kind == "transport_closed";
720                        // ORCH-9: a permission/approval request arrives as an
721                        // ordinary event; it becomes listable here and stops
722                        // being listable when `runtimes.respond` answers it.
723                        self.approvals
724                            .observe(connection, &harness, &session_id, &event, now_ms);
725                        let next_sequence = self
726                            .runtime_sequences
727                            .entry(session_id.clone())
728                            .or_insert(0);
729                        let sequence = event.sequence.unwrap_or_else(|| {
730                            *next_sequence = next_sequence.saturating_add(1);
731                            *next_sequence
732                        });
733                        *next_sequence = (*next_sequence).max(sequence);
734                        events.push((
735                            connection.clone(),
736                            SdkRuntimeEvent {
737                                session_id: session_id.clone(),
738                                event: SdkEvent {
739                                    sequence,
740                                    kind: event.kind,
741                                    payload: event.payload,
742                                },
743                            },
744                        ));
745                        if terminal {
746                            closed.push(connection.clone());
747                            break;
748                        }
749                    }
750                    Ok(Ok(None)) => {
751                        let sequence = self
752                            .runtime_sequences
753                            .entry(session_id.clone())
754                            .or_insert(0);
755                        *sequence = sequence.saturating_add(1);
756                        events.push((
757                        connection.clone(),
758                        SdkRuntimeEvent {
759                            session_id,
760                            event: SdkEvent {
761                                sequence: *sequence,
762                                kind: "transport_closed".into(),
763                                payload: json!({"message": "Harness runtime transport closed."}),
764                            },
765                        },
766                    ));
767                        closed.push(connection.clone());
768                        break;
769                    }
770                    Err(_) => break,
771                    Ok(Err(error)) => {
772                        let sequence = self
773                            .runtime_sequences
774                            .entry(session_id.clone())
775                            .or_insert(0);
776                        *sequence = sequence.saturating_add(1);
777                        events.push((
778                        connection.clone(),
779                        SdkRuntimeEvent {
780                            session_id,
781                            event: SdkEvent {
782                                sequence: *sequence,
783                                kind: "transport_error".into(),
784                                payload: json!({"message": error.to_string(), "terminal": true}),
785                            },
786                        },
787                    ));
788                        closed.push(connection.clone());
789                        break;
790                    }
791                }
792            }
793        }
794        for connection in closed {
795            if let Some(runtime) = self.runtimes.remove(&connection) {
796                self.runtime_sequences.remove(&runtime.handle().runtime_id);
797            }
798            self.terminal_launches.remove(&connection);
799            // A connection that is gone cannot answer anything it was
800            // holding; those requests stop being listable with it.
801            self.approvals.forget(&connection);
802        }
803        events
804    }
805
806    fn call(&mut self, method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
807        match method {
808            "harness.v1.capabilities" => Ok(json!({
809                "version": HARNESS_SERVICE_VERSION,
810                "sdk": self.capabilities(),
811                "methods": HARNESS_SERVICE_METHODS,
812                "notifications": [
813                    SESSION_EVENT_METHOD,
814                    SESSION_ACTIVITY_EVENT_METHOD,
815                    SESSION_INDEX_EVENT_METHOD,
816                    RUNTIME_EVENT_METHOD
817                ],
818                "harnesses": harness_support_registry()
819                    .harnesses
820                    .into_iter()
821                    .map(|harness| harness.id)
822                    .collect::<Vec<_>>(),
823            })),
824            "harness.v1.support.report" => serde_json::to_value(harness_support_registry())
825                .map_err(|error| ServiceError::Operation(error.to_string())),
826            "harness.v1.profiles.list" | "harness.v1.profiles.get" => profiles_call(method, params),
827            // ORCH-21 controlled tier. Each verb translates to the HARNESS'S
828            // OWN profile verb and runs it (`crate::profiles_control`);
829            // supercode makes and removes nothing itself. The row returned is
830            // re-read through the ORCH-10 loader afterwards, and `ran`
831            // narrates the exact command.
832            "harness.v1.profiles.create" => {
833                mutate_profile(crate::profiles_control::ProfileVerb::Create, params)
834            }
835            "harness.v1.profiles.delete" => {
836                mutate_profile(crate::profiles_control::ProfileVerb::Delete, params)
837            }
838            "harness.v1.channels.list" | "harness.v1.channels.status" => {
839                channels_call(method, params)
840            }
841            // ORCH-15 observed tier: which profile / agent a surface tuple
842            // resolves to, read from each gateway harness's own config.
843            "harness.v1.routes.list" => routes_call(params),
844            // ORCH-16 observed tier: inbound webhook routes / hook mappings.
845            "harness.v1.triggers.list" => triggers_call(params),
846            // ONT-4: the orchestration doors. One home folder in, one typed orchestration
847            // value out (and back). Every one of the four is
848            // `crate::orchestration_doors`, which the `supercode orchestration` verbs call
849            // too — the RPC adds nothing but the envelope. A vault VALUE
850            // never crosses this wire: a load or a compile answers with the
851            // `.env` KEY NAMES, and a caller that needs a value reads the
852            // home's own `.env`.
853            // the workflow layer's read door: a harness's board as one typed value,
854            // the same code the `supercode workflow load` verb calls
855            "harness.v1.workflow.load" => {
856                let params = decode::<WorkflowLoadParams>(params)?;
857                let read =
858                    crate::workflow_doors::load(params.from, &params.home).map_err(operation)?;
859                serde_json::to_value(read)
860                    .map_err(|error| ServiceError::Operation(error.to_string()))
861            }
862            "harness.v1.orchestration.load" => {
863                let params = decode::<OrchestrationLoadParams>(params)?;
864                let read = crate::orchestration_doors::load(&params.root, params.flavor)
865                    .map_err(operation)?;
866                serde_json::to_value(read)
867                    .map_err(|error| ServiceError::Operation(error.to_string()))
868            }
869            "harness.v1.orchestration.save" => {
870                let params = decode::<OrchestrationSaveParams>(params)?;
871                let saved = crate::orchestration_doors::save(
872                    &params.root,
873                    params.orchestration,
874                    params.vault,
875                )
876                .map_err(operation)?;
877                serde_json::to_value(saved)
878                    .map_err(|error| ServiceError::Operation(error.to_string()))
879            }
880            "harness.v1.orchestration.compile" => {
881                let params = decode::<OrchestrationCompileParams>(params)?;
882                let read = crate::orchestration_doors::compile(params.from, &params.home)
883                    .map_err(operation)?;
884                serde_json::to_value(read)
885                    .map_err(|error| ServiceError::Operation(error.to_string()))
886            }
887            "harness.v1.orchestration.decompile" => {
888                let params = decode::<OrchestrationDecompileParams>(params)?;
889                let report = crate::orchestration_doors::decompile(
890                    params.to,
891                    params.orchestration,
892                    &params.source,
893                    params.source_flavor,
894                    &params.dest,
895                    params.vault,
896                )
897                .map_err(operation)?;
898                serde_json::to_value(report)
899                    .map_err(|error| ServiceError::Operation(error.to_string()))
900            }
901            // a migration keeps the credential in this process: a compile and
902            // a save (import), a load and a decompile (export), composed here
903            // because composed by a client the secret would have to cross
904            // the wire
905            "harness.v1.orchestration.import" => {
906                let params = decode::<OrchestrationImportParams>(params)?;
907                let imported =
908                    crate::orchestration_doors::import(params.from, &params.home, &params.into)
909                        .map_err(operation)?;
910                serde_json::to_value(imported)
911                    .map_err(|error| ServiceError::Operation(error.to_string()))
912            }
913            "harness.v1.orchestration.export" => {
914                let params = decode::<OrchestrationExportParams>(params)?;
915                let report =
916                    crate::orchestration_doors::export(params.to, &params.root, &params.dest)
917                        .map_err(operation)?;
918                serde_json::to_value(report)
919                    .map_err(|error| ServiceError::Operation(error.to_string()))
920            }
921            // ORCH-12 observed tier: read and search the persistent memory
922            // documents a harness keeps on disk. Read-only — every write
923            // (`hermes memory off`, `openclaw memory forget|reset`, Claude
924            // Code's `/memory`) stays the harness's own verb. A harness with
925            // no memory store is refused with UnsupportedAction.
926            "harness.v1.memory.show" | "harness.v1.memory.search" => memory_call(method, params),
927            // ORCH-11 observed tier: read-only enumeration of every harness's
928            // installed skill packages. An unknown harness id is refused with
929            // UnsupportedAction — every harness supports skills, so a filter
930            // that matches nothing is a caller error, never an empty listing.
931            "harness.v1.skills.list" => {
932                let query = decode::<crate::skills::SkillsQuery>(params)?;
933                if let Some(harness) = query.harness.as_deref() {
934                    if !crate::skills::SKILL_HARNESSES.contains(&harness) {
935                        return Err(ServiceError::UnsupportedAction(format!(
936                            "`{harness}` has no skills root supercode reads"
937                        )));
938                    }
939                }
940                serde_json::to_value(crate::skills::list_skills(&query))
941                    .map_err(|error| ServiceError::Operation(error.to_string()))
942            }
943            // ORCH-22 controlled tier: each verb goes through the door the
944            // HARNESS publishes — `hermes skills install|uninstall`,
945            // `openclaw skills install`, and for the core four the loader's
946            // own directory, which is the only skills door those harnesses
947            // have. supercode resolves no registry and unpacks no archive.
948            // The row returned is re-read through the ORCH-11 loader
949            // afterwards, and `ran` narrates exactly what was performed.
950            "harness.v1.skills.install" => {
951                mutate_skill(crate::skills_control::SkillVerb::Install, params)
952            }
953            "harness.v1.skills.remove" => {
954                mutate_skill(crate::skills_control::SkillVerb::Remove, params)
955            }
956            // ORCH-9 observed tier: the approval requests waiting for an
957            // answer. At the pinned harness versions the only uniform source
958            // is a LIVE request held by an open runtime connection, plus
959            // supercode's own queued subagent approvals — neither Hermes
960            // 0.21.0 nor OpenClaw 2026.7.1-2 has an approvals door to read
961            // (see `crate::approvals`). A harness whose runtime cannot carry
962            // a protocol request at all is refused by name.
963            "harness.v1.approvals.list" => {
964                let query = decode::<crate::approvals::ApprovalsQuery>(params)?;
965                if let Some(harness) = query.harness.as_deref() {
966                    if !crate::approvals::lists_approvals(harness) {
967                        return Err(ServiceError::UnsupportedAction(format!(
968                            "`{harness}` has no runtime door that carries an approval request"
969                        )));
970                    }
971                }
972                serde_json::to_value(self.approvals(&query))
973                    .map_err(|error| ServiceError::Operation(error.to_string()))
974            }
975            "harness.v1.sessions.discover" => {
976                let query = decode::<DiscoveryQuery>(params)?;
977                let page = discover_session_page(&query).map_err(operation)?;
978                // Claude Code is the one harness that publishes its RUNNING
979                // sessions. The registry is read once per discovery and joined
980                // by session id; every record in it has already survived a
981                // `kill(pid, 0)` liveness check inside `read_registry`.
982                let peers = if page
983                    .sessions
984                    .iter()
985                    .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
986                {
987                    crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(
988                        &query.homes,
989                    ))
990                } else {
991                    Vec::new()
992                };
993                let activities = crate::session_activity::resolve_stock_session_activities(
994                    &page
995                        .sessions
996                        .iter()
997                        .map(|session| session.locator.clone())
998                        .collect::<Vec<_>>(),
999                    &query.homes,
1000                )
1001                .into_iter()
1002                .map(|activity| (activity.key(), activity))
1003                .collect::<BTreeMap<_, _>>();
1004                let sessions = page
1005                    .sessions
1006                    .into_iter()
1007                    .map(|session| {
1008                        let mut value = live_descriptor_value(&session, &peers)?;
1009                        let activity_key = (
1010                            session.locator.harness.as_str().to_string(),
1011                            session.locator.session_id.clone(),
1012                        );
1013                        if let Some(activity) = activities.get(&activity_key) {
1014                            value["activity"] = serde_json::to_value(activity)
1015                                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1016                            if let Some(status) = legacy_live_status(activity) {
1017                                value["live_status"] = json!(status);
1018                            }
1019                        }
1020                        Ok(value)
1021                    })
1022                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1023                let mut result = json!({"sessions": sessions, "next_cursor": page.next_cursor});
1024                // Preserve the metadata-only wire shape, but carry the catalog's
1025                // proof/counts when the caller explicitly requests preview search.
1026                if query.search_previews {
1027                    result["receipt"] = serde_json::to_value(page.receipt)
1028                        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1029                }
1030                Ok(result)
1031            }
1032            "harness.v1.sessions.load" => {
1033                let params = decode::<LoadSessionParams>(params)?;
1034                if let Some(options) = &params.options {
1035                    options.validate()?;
1036                    if let Some(result) = indexed_claude_window(&params.read.locator, options)? {
1037                        return Ok(result);
1038                    }
1039                    return load_session(&params.read.locator)
1040                        .map(|session| projected_session_result(&session, options))
1041                        .map_err(operation);
1042                }
1043                let mut session = if params.read.display_history() {
1044                    self.catalog
1045                        .load_display_view(
1046                            &params.read.locator,
1047                            params.read.read_fidelity(),
1048                            params.read.tail_messages().unwrap_or(500),
1049                        )
1050                        .map_err(crate::Error::from)
1051                } else if params.read.include_subagents() {
1052                    load_session_with_fidelity(&params.read.locator, params.read.read_fidelity())
1053                } else {
1054                    self.catalog
1055                        .load_parent_with_fidelity(
1056                            &params.read.locator,
1057                            params.read.read_fidelity(),
1058                        )
1059                        .map_err(crate::Error::from)
1060                }
1061                .map_err(operation)?;
1062                params.read.bound_session(&mut session);
1063                Ok(json!({"session": normalized_session_json(&session)}))
1064            }
1065            "harness.v1.sessions.follow" => {
1066                let params = decode::<LocatorParams>(params)?;
1067                let mut follower = self
1068                    .catalog
1069                    .follow_read_view(
1070                        &params.locator,
1071                        params.read_fidelity(),
1072                        params.include_subagents(),
1073                        params.tail_messages(),
1074                        params.max_message_chars(),
1075                        params.display_history(),
1076                    )
1077                    .map_err(operation)?;
1078                let initial = follower
1079                    .poll()
1080                    .map_err(operation)?
1081                    .map(|event| event.to_json());
1082                let subscription = format!("sub-{}", self.next_subscription);
1083                self.next_subscription += 1;
1084                self.followers.insert(subscription.clone(), follower);
1085                self.followed_sources.insert(
1086                    subscription.clone(),
1087                    FollowedSource {
1088                        harness: params.locator.harness.as_str().to_string(),
1089                        session_id: params.locator.session_id.clone(),
1090                        reported: None,
1091                    },
1092                );
1093                Ok(json!({"subscription": subscription, "initial": initial}))
1094            }
1095            "harness.v1.sessions.unfollow" => {
1096                let params = decode::<UnfollowParams>(params)?;
1097                self.followed_sources.remove(&params.subscription);
1098                Ok(json!({
1099                    "removed": self.followers.remove(&params.subscription).is_some()
1100                }))
1101            }
1102            "harness.v1.sessions.activity.unsubscribe" => {
1103                let params = decode::<UnfollowParams>(params)?;
1104                Ok(json!({
1105                    "removed": self.activity_subscriptions.remove(&params.subscription).is_some()
1106                }))
1107            }
1108            "harness.v1.sessions.index.subscribe" => {
1109                let query = decode::<DiscoveryQuery>(params)?;
1110                crate::session_index::validate_query(&query)
1111                    .map_err(ServiceError::InvalidParams)?;
1112                let homes = query.homes.clone();
1113                let (index, initial) = crate::session_index::SessionIndexSubscription::open(
1114                    query,
1115                    Arc::clone(&self.index_notifier),
1116                )
1117                .map_err(ServiceError::Operation)?;
1118                let peers = peers_for_descriptors(&initial, &homes);
1119                let initial = initial
1120                    .iter()
1121                    .map(|descriptor| live_descriptor_value(descriptor, &peers))
1122                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1123                let subscription = format!("index-sub-{}", self.next_subscription);
1124                self.next_subscription += 1;
1125                self.index_subscriptions.insert(subscription.clone(), index);
1126                Ok(json!({
1127                    "subscription": subscription,
1128                    "revision": 1,
1129                    "initial": initial,
1130                }))
1131            }
1132            "harness.v1.sessions.index.resize" => {
1133                let params = decode::<IndexResizeParams>(params)?;
1134                crate::session_index::validate_limit(params.limit)
1135                    .map_err(ServiceError::InvalidParams)?;
1136                let index = self
1137                    .index_subscriptions
1138                    .get_mut(&params.subscription)
1139                    .ok_or_else(|| {
1140                        ServiceError::InvalidParams("unknown session index subscription".into())
1141                    })?;
1142                let prepared = index
1143                    .prepare_resize(params.limit)
1144                    .map_err(ServiceError::Operation)?;
1145                let peers = peers_for_descriptors(&prepared.page.sessions, index.homes());
1146                let initial = prepared
1147                    .page
1148                    .sessions
1149                    .iter()
1150                    .map(|descriptor| live_descriptor_value(descriptor, &peers))
1151                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1152                let response = json!({
1153                    "subscription": params.subscription,
1154                    "revision": prepared.revision,
1155                    "initial": initial,
1156                    "receipt": prepared.page.receipt,
1157                });
1158                index.commit_resize(prepared);
1159                Ok(response)
1160            }
1161            "harness.v1.sessions.index.unsubscribe" => {
1162                let params = decode::<UnfollowParams>(params)?;
1163                Ok(json!({
1164                    "removed": self.index_subscriptions.remove(&params.subscription).is_some()
1165                }))
1166            }
1167            "harness.v1.sessions.import" => {
1168                let params = decode::<ImportSessionParams>(params)?;
1169                let session = Session::load_str(&params.content, params.source_harness.into())
1170                    .map_err(operation)?;
1171                Ok(json!({"session": normalized_session_json(&session)}))
1172            }
1173            "harness.v1.sessions.export" | "harness.v1.sessions.translate" => {
1174                let params = decode::<ExportSessionParams>(params)?;
1175                let session = load_session(&params.locator).map_err(operation)?;
1176                let artifact = session_artifact(&params.locator, &session, params.target_harness)?;
1177                if method == "harness.v1.sessions.export"
1178                    && params.target_harness == TransferFormat::Hermes
1179                {
1180                    // UNI-18: write through Hermes's own door, never into its store
1181                    let imported = crate::hermes_import::import_into_hermes(&session, None)
1182                        .map_err(operation)?;
1183                    return Ok(json!({"artifact": artifact, "imported": imported}));
1184                }
1185                Ok(json!({"artifact": artifact}))
1186            }
1187            "harness.v1.sessions.reduce" => {
1188                let params = decode::<ReduceSessionParams>(params)?;
1189                self.reduce_session(params)
1190            }
1191            "harness.v1.sessions.branch" => {
1192                let params = decode::<BranchSessionParams>(params)?;
1193                let session = load_session(&params.locator).map_err(operation)?;
1194                let storage = params.locator.storage.path().display().to_string();
1195                let bootstrap_prompt = format!(
1196                    "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.",
1197                    params.locator.harness.as_str(), params.locator.session_id, storage
1198                );
1199                let artifact = params
1200                    .target_harness
1201                    .map(|target| session_artifact(&params.locator, &session, target))
1202                    .transpose()?;
1203                Ok(json!({
1204                    "parent": params.locator,
1205                    "session": normalized_session_json(&session),
1206                    "bootstrap_prompt": bootstrap_prompt,
1207                    "artifact": artifact,
1208                }))
1209            }
1210            "harness.v1.sessions.handoff" => {
1211                let params = decode::<HandoffSessionParams>(params)?;
1212                let session = load_session(&params.locator).map_err(operation)?;
1213                let cwd = params
1214                    .cwd
1215                    .or_else(|| session.meta.cwd.clone())
1216                    .unwrap_or_else(|| PathBuf::from("."));
1217                let artifact =
1218                    handoff_artifact(&params.locator, &session, params.target_harness, &cwd)?;
1219                let target_session_id = artifact.session_id.as_deref().ok_or_else(|| {
1220                    ServiceError::Operation(
1221                        "handoff artifact omitted target session identity".into(),
1222                    )
1223                })?;
1224                let instructions =
1225                    handoff_instructions(params.target_harness, target_session_id, &cwd);
1226                Ok(json!({
1227                    "artifact": artifact,
1228                    "launch": instructions.launch,
1229                    "materialize": instructions.materialize,
1230                    "requires_materialization": instructions.requires_materialization,
1231                    "note": instructions.note,
1232                }))
1233            }
1234            // ORCH-7 observed tier. Read-only: the handlers open the harness's
1235            // own job store (Claude Code's session JSONL, Hermes's and
1236            // OpenClaw's `cron/jobs.json`) and never write, fire, or schedule.
1237            "harness.v1.jobs.list" => {
1238                let query = decode::<crate::jobs::JobsQuery>(params)?;
1239                if let Some(harness) = query.harness.as_deref() {
1240                    refuse_harness_without_jobs(harness, "jobs.list")?;
1241                }
1242                let listing = crate::jobs::list_jobs(&query).map_err(operation)?;
1243                serde_json::to_value(listing)
1244                    .map_err(|error| ServiceError::Operation(error.to_string()))
1245            }
1246            "harness.v1.jobs.get" => {
1247                let params = decode::<JobsGetParams>(params)?;
1248                refuse_harness_without_jobs(&params.harness, "jobs.get")?;
1249                match crate::jobs::get_job(&params.harness, &params.id, &params.homes)
1250                    .map_err(operation)?
1251                {
1252                    Some((job, source)) => Ok(json!({"job": job, "source": source})),
1253                    None => Err(ServiceError::Operation(format!(
1254                        "`{}` has no scheduled job `{}`",
1255                        params.harness, params.id
1256                    ))),
1257                }
1258            }
1259            // ORCH-18 controlled tier. Each verb translates to the HARNESS'S
1260            // OWN cron verb and runs it (`crate::jobs_control`); supercode
1261            // schedules nothing. The row returned is re-read from the
1262            // harness's store afterwards, and `ran` narrates the exact command
1263            // with any credential redacted.
1264            "harness.v1.jobs.create" => mutate_job(crate::jobs_control::JobVerb::Create, params),
1265            "harness.v1.jobs.update" => mutate_job(crate::jobs_control::JobVerb::Update, params),
1266            "harness.v1.jobs.pause" => mutate_job(crate::jobs_control::JobVerb::Pause, params),
1267            "harness.v1.jobs.resume" => mutate_job(crate::jobs_control::JobVerb::Resume, params),
1268            "harness.v1.jobs.run" => mutate_job(crate::jobs_control::JobVerb::Run, params),
1269            "harness.v1.jobs.delete" => mutate_job(crate::jobs_control::JobVerb::Delete, params),
1270            // ORCH-8 observed tier. Read-only: the handlers open the harness's
1271            // own run store (Hermes's `cron/executions.db`, OpenClaw's
1272            // `cron_run_logs`) and never claim, retry, or prune a fire.
1273            "harness.v1.runs.list" => {
1274                let query = decode::<crate::runs::RunsQuery>(params)?;
1275                if let Some(harness) = query.harness.as_deref() {
1276                    refuse_harness_without_runs(harness, "runs.list")?;
1277                }
1278                let listing = crate::runs::list_runs(&query).map_err(operation)?;
1279                serde_json::to_value(listing)
1280                    .map_err(|error| ServiceError::Operation(error.to_string()))
1281            }
1282            "harness.v1.runs.get" => {
1283                let params = decode::<RunsGetParams>(params)?;
1284                refuse_harness_without_runs(&params.harness, "runs.get")?;
1285                match crate::runs::get_run(&params.harness, &params.id, &params.homes)
1286                    .map_err(operation)?
1287                {
1288                    Some((run, source)) => Ok(json!({"run": run, "source": source})),
1289                    None => Err(ServiceError::Operation(format!(
1290                        "`{}` has no run `{}`",
1291                        params.harness, params.id
1292                    ))),
1293                }
1294            }
1295            "harness.v1.sessions.resume_instructions" => {
1296                let params = decode::<ResumeInstructionsParams>(params)?;
1297                let session = load_session(&params.locator).map_err(operation)?;
1298                let cwd = params
1299                    .cwd
1300                    .or(session.meta.cwd)
1301                    .unwrap_or_else(|| PathBuf::from("."));
1302                let launch = resume_launch(
1303                    params.locator.harness.as_str(),
1304                    &params.locator.session_id,
1305                    &cwd,
1306                    params.policy,
1307                )?;
1308                Ok(json!({"launch": launch}))
1309            }
1310            _ => Err(ServiceError::MethodNotFound),
1311        }
1312    }
1313
1314    fn reduce_session(
1315        &self,
1316        params: ReduceSessionParams,
1317    ) -> std::result::Result<Value, ServiceError> {
1318        let session = load_session(&params.locator).map_err(operation)?;
1319        if session.messages.is_empty() {
1320            return Err(ServiceError::InvalidParams(
1321                "cannot reduce an empty session".into(),
1322            ));
1323        }
1324        let keep_last = params.keep_last.clamp(1, 128);
1325        let policy = reduce::ReductionPolicy {
1326            clear_turns_older_than: Some(keep_last),
1327            ..Default::default()
1328        };
1329        let (view, log) =
1330            reduce::project_messages(&session.messages, &policy, &reduce::ReductionLog::default());
1331        if log.reductions.is_empty() {
1332            return Err(ServiceError::UnsupportedAction(format!(
1333                "session `{}` is already too small for a meaningful reversible reduction",
1334                params.locator.session_id
1335            )));
1336        }
1337        let source_tokens = tokens::estimate_view_tokens(&session.messages);
1338        let reduced_tokens = tokens::estimate_view_tokens(&view);
1339        if reduced_tokens >= source_tokens {
1340            return Err(ServiceError::UnsupportedAction(format!(
1341                "session `{}` has no token-reducing reversible projection",
1342                params.locator.session_id
1343            )));
1344        }
1345
1346        let store_root = self
1347            .reduction_store_root
1348            .clone()
1349            .unwrap_or_else(default_reduction_store_root);
1350        let store = crate::SessionStore::open(&store_root).map_err(operation)?;
1351        let rescue_id = format!("rescue-{}", generated_session_id());
1352        let imported = session
1353            .imported_message_count
1354            .unwrap_or(session.messages.len())
1355            .min(session.messages.len());
1356        let sidecar_jsonl = session.to_native_jsonl_v2(&session.messages[imported..]);
1357        let view_jsonl = messages_jsonl(&view)?;
1358        let title = format!(
1359            "Reduced {} continuation from {}",
1360            params.target_harness.id(),
1361            params.locator.session_id
1362        );
1363
1364        // Durability order is intentional: the full source of truth lands
1365        // before either object that can refer to it. A crash may leave an
1366        // unused sidecar, but can never leave a reduced view whose originals
1367        // were not durably written first.
1368        store
1369            .save_sidecar(&rescue_id, &sidecar_jsonl)
1370            .map_err(operation)?;
1371        store
1372            .save_reduction_log(&rescue_id, &log)
1373            .map_err(operation)?;
1374        store
1375            .save(&rescue_id, &title, &view_jsonl)
1376            .map_err(operation)?;
1377
1378        let source_bytes = serde_json::to_vec(&session.messages)
1379            .map_err(|error| ServiceError::Operation(error.to_string()))?
1380            .len() as u64;
1381        let reduced_bytes = serde_json::to_vec(&view)
1382            .map_err(|error| ServiceError::Operation(error.to_string()))?
1383            .len() as u64;
1384        store
1385            .set_reduction_stats(
1386                &rescue_id,
1387                &title,
1388                source_bytes,
1389                reduced_bytes,
1390                log.reductions.len() as u32,
1391            )
1392            .map_err(operation)?;
1393
1394        // The receipt is issued only after a real disk reload. This proves
1395        // the exact files another process will consume, not the convenient
1396        // in-memory values that produced them.
1397        let reloaded_sidecar = store
1398            .load_sidecar(&rescue_id)
1399            .map_err(operation)?
1400            .ok_or_else(|| ServiceError::Operation("reduction sidecar disappeared".into()))?;
1401        let reloaded_sidecar = Session::from_sidecar_str(&reloaded_sidecar).map_err(operation)?;
1402        let reloaded_log = store
1403            .load_reduction_log(&rescue_id)
1404            .map_err(operation)?
1405            .ok_or_else(|| ServiceError::Operation("reduction log disappeared".into()))?;
1406        let reloaded_view = parse_messages_jsonl(&store.load(&rescue_id).map_err(operation)?)?;
1407        reduce::verify_log(&reloaded_log, &reloaded_sidecar).map_err(operation)?;
1408        // `sc.reduction` is deliberately in-memory-only metadata: it must
1409        // never leak onto a provider-facing transcript. Reapplying the
1410        // durable log to the durable sidecar restores those ids. Comparing
1411        // its wire form with the transcript reloaded above proves that the
1412        // persisted view is exactly the deterministic projection before we
1413        // use the restamped form for inversion.
1414        let (restamped_view, restamped_log) =
1415            reduce::project_messages(&reloaded_sidecar.messages, &policy, &reloaded_log);
1416        if messages_jsonl(&restamped_view)? != messages_jsonl(&reloaded_view)? {
1417            return Err(ServiceError::Operation(
1418                "persisted reduction view does not match its durable log and sidecar".into(),
1419            ));
1420        }
1421        if restamped_log != reloaded_log {
1422            return Err(ServiceError::Operation(
1423                "reapplying the durable reduction log changed its identity".into(),
1424            ));
1425        }
1426        let inverted =
1427            reduce::invert(&restamped_view, &reloaded_log, &reloaded_sidecar).map_err(operation)?;
1428        if inverted != session.messages {
1429            return Err(ServiceError::Operation(
1430                "reduction inversion did not restore the source messages byte-exactly".into(),
1431            ));
1432        }
1433
1434        let ratio = source_tokens as f64 / reduced_tokens.max(1) as f64;
1435        let sidecar_path = store.sidecar_path(&rescue_id);
1436        let reduction_log_path = store.reduction_log_path(&rescue_id).map_err(operation)?;
1437        let bootstrap_prompt = reduced_bootstrap_prompt(
1438            &params.locator,
1439            params.target_harness,
1440            &view_jsonl,
1441            &sidecar_path,
1442            &reduction_log_path,
1443        );
1444        let mut reduced_session = session.clone();
1445        reduced_session.meta.session_id = Some(rescue_id.clone());
1446        reduced_session.messages = view;
1447
1448        Ok(json!({
1449            "session": normalized_session_json(&reduced_session),
1450            "bootstrap_prompt": bootstrap_prompt,
1451            "receipt": {
1452                "id": rescue_id,
1453                "sidecar_id": rescue_id,
1454                "source_harness": params.locator.harness,
1455                "target_harness": params.target_harness.id(),
1456                "source_tokens": source_tokens,
1457                "reduced_tokens": reduced_tokens,
1458                "ratio": ratio,
1459                "source_bytes": source_bytes,
1460                "reduced_bytes": reduced_bytes,
1461                "reductions": reloaded_log.reductions.len(),
1462                "sidecar_path": sidecar_path,
1463                "reduction_log_path": reduction_log_path,
1464                "verified": true,
1465                "reversible": true,
1466            }
1467        }))
1468    }
1469
1470    async fn runtime_call(
1471        &mut self,
1472        method: &str,
1473        params: Value,
1474    ) -> std::result::Result<Value, ServiceError> {
1475        match method {
1476            "harness.v1.runtimes.capabilities" => {
1477                let params = decode::<RuntimeBackendParams>(params)?;
1478                let backend = runtime_backend(&params)?;
1479                Ok(json!({
1480                    "harness": backend.harness(),
1481                    "capabilities": backend.capabilities(),
1482                }))
1483            }
1484            "harness.v1.runtimes.start" => {
1485                let params = decode::<RuntimeStartParams>(params)?;
1486                let backend = runtime_backend(&params.backend)?;
1487                let capabilities = backend.capabilities();
1488                let workspace = params.cwd.clone();
1489                let runtime = backend
1490                    .start(RuntimeStartRequest {
1491                        cwd: params.cwd,
1492                        launch: runtime_launch(&params.backend),
1493                        mcp_servers: params.mcp_servers,
1494                    })
1495                    .await
1496                    .map_err(operation)?;
1497                self.insert_hosted_runtime(runtime, capabilities, workspace)
1498                    .await
1499            }
1500            "harness.v1.runtimes.resume" | "harness.v1.runtimes.attach" => {
1501                let params = decode::<RuntimeAttachParams>(params)?;
1502                let backend = runtime_backend(&params.backend)?;
1503                let capabilities = backend.capabilities();
1504                let workspace = params.cwd.clone().unwrap_or_else(|| {
1505                    std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
1506                });
1507                let runtime = backend
1508                    .attach(RuntimeAttachRequest {
1509                        runtime_id: params.runtime_id,
1510                        cwd: params.cwd,
1511                        launch: runtime_launch(&params.backend),
1512                    })
1513                    .await
1514                    .map_err(operation)?;
1515                self.insert_hosted_runtime(runtime, capabilities, workspace)
1516                    .await
1517            }
1518            "harness.v1.runtimes.attach_existing" => {
1519                let params = decode::<RuntimeAttachParams>(params)?;
1520                let backend: Box<dyn RuntimeBackend> = match params
1521                    .backend
1522                    .base_url
1523                    .as_deref()
1524                    .and_then(|value| LiveRuntimeEndpoint::parse(value).ok())
1525                {
1526                    Some(endpoint) => {
1527                        #[cfg(not(feature = "adapter-api"))]
1528                        {
1529                            let _ = endpoint;
1530                            return Err(ServiceError::UnsupportedAction(
1531                                "live HTTP attachment adapter is not compiled".into(),
1532                            ));
1533                        }
1534                        #[cfg(feature = "adapter-api")]
1535                        {
1536                            let workspace = params.cwd.clone().ok_or_else(|| {
1537                                ServiceError::InvalidParams(
1538                                    "Supercode live attach requires the project cwd".into(),
1539                                )
1540                            })?;
1541                            let source = LiveRuntimeSource {
1542                                harness: params.backend.harness.as_str().to_string(),
1543                                session_id: params.runtime_id.clone(),
1544                                workspace,
1545                            };
1546                            let receipt = resolve_live_runtime(&endpoint, &source)
1547                                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1548                            Box::new(SupercodeHttpRuntimeBackend::new(receipt))
1549                        }
1550                    }
1551                    None => runtime_backend(&params.backend)?,
1552                };
1553                if !backend.capabilities().attach_existing_process {
1554                    return Err(ServiceError::Operation(format!(
1555                        "{} cannot attach to an already-running process; use runtimes.resume for a persisted session",
1556                        backend.harness().as_str()
1557                    )));
1558                }
1559                let runtime = backend
1560                    .attach_existing(RuntimeAttachRequest {
1561                        runtime_id: params.runtime_id,
1562                        cwd: params.cwd,
1563                        launch: runtime_launch(&params.backend),
1564                    })
1565                    .await
1566                    .map_err(operation)?;
1567                self.insert_runtime(runtime)
1568            }
1569            "harness.v1.runtimes.send_input" => {
1570                let params = decode::<RuntimeInputParams>(params)?;
1571                let image_urls = validate_runtime_image_urls(params.image_urls)?;
1572                let runtime = self.runtime_mut(&params.connection)?;
1573                let turn_id = runtime
1574                    .send_input(RuntimeInput {
1575                        text: params.text,
1576                        image_urls,
1577                    })
1578                    .await
1579                    .map_err(operation)?;
1580                Ok(json!({"turn_id": turn_id}))
1581            }
1582            "harness.v1.runtimes.interrupt" => {
1583                let params = decode::<RuntimeConnectionParams>(params)?;
1584                self.runtime_mut(&params.connection)?
1585                    .interrupt()
1586                    .await
1587                    .map_err(operation)?;
1588                Ok(json!({}))
1589            }
1590            "harness.v1.runtimes.steer" => {
1591                let params = decode::<RuntimeInputParams>(params)?;
1592                if !params.image_urls.is_empty() {
1593                    return Err(ServiceError::InvalidParams(
1594                        "runtime steering accepts text only".into(),
1595                    ));
1596                }
1597                let text = params.text.trim();
1598                if text.is_empty() || text.chars().count() > 50_000 {
1599                    return Err(ServiceError::InvalidParams(
1600                        "runtime steering requires 1 to 50,000 text characters".into(),
1601                    ));
1602                }
1603                self.runtime_mut(&params.connection)?
1604                    .steer(text.to_string())
1605                    .await
1606                    .map_err(operation)?;
1607                Ok(json!({}))
1608            }
1609            "harness.v1.runtimes.respond" => {
1610                let params = decode::<RuntimeRespondParams>(params)?;
1611                let request_id = params.request_id.clone();
1612                self.runtime_mut(&params.connection)?
1613                    .respond(params.request_id, params.response)
1614                    .await
1615                    .map_err(operation)?;
1616                // ORCH-9: an answered request is no longer waiting for one.
1617                self.approvals.answered(&params.connection, &request_id);
1618                Ok(json!({}))
1619            }
1620            "harness.v1.runtimes.terminal_instructions" => {
1621                let params = decode::<RuntimeConnectionParams>(params)?;
1622                let launch = self
1623                    .terminal_launches
1624                    .get(&params.connection)
1625                    .ok_or_else(|| {
1626                        ServiceError::Operation(
1627                            "this runtime is not hosted for terminal attachment".into(),
1628                        )
1629                    })?;
1630                Ok(json!({"launch":launch}))
1631            }
1632            "harness.v1.runtimes.close" => {
1633                let params = decode::<RuntimeConnectionParams>(params)?;
1634                let Some(mut runtime) = self.runtimes.remove(&params.connection) else {
1635                    return Err(ServiceError::InvalidParams(format!(
1636                        "unknown runtime connection `{}`",
1637                        params.connection
1638                    )));
1639                };
1640                self.terminal_launches.remove(&params.connection);
1641                self.runtime_sequences.remove(&runtime.handle().runtime_id);
1642                self.approvals.forget(&params.connection);
1643                runtime.close().await.map_err(operation)?;
1644                Ok(json!({"closed": true}))
1645            }
1646            _ => Err(ServiceError::MethodNotFound),
1647        }
1648    }
1649
1650    /// Deliver one message into a session that is running right now.
1651    #[cfg(feature = "adapter-api")]
1652    async fn message_call(&self, params: Value) -> std::result::Result<Value, ServiceError> {
1653        let params = decode::<MessageSessionParams>(params)?;
1654        Ok(message_live_session(&params, &crate::claude_peer::ProcessCourierRunner).await)
1655    }
1656
1657    #[cfg(feature = "adapter-api")]
1658    fn harness_settings_call(
1659        &self,
1660        method: &str,
1661        params: Value,
1662    ) -> std::result::Result<Value, ServiceError> {
1663        let homes = crate::HarnessHomes::default();
1664        match method {
1665            "harness.v1.harnesses.settings" => {
1666                let params = decode::<HarnessSettingsParams>(params)?;
1667                let report = crate::inspect_harness_interop_settings(&homes, &params.harness)
1668                    .map_err(|error| ServiceError::Operation(error.to_string()))?;
1669                serde_json::to_value(report)
1670                    .map_err(|error| ServiceError::Operation(error.to_string()))
1671            }
1672            "harness.v1.harnesses.configure" => {
1673                let params = decode::<ConfigureHarnessParams>(params)?;
1674                let report = crate::configure_harness_interop_settings(
1675                    &homes,
1676                    &params.harness,
1677                    &params.changes,
1678                    params.expected_revision.as_deref(),
1679                )
1680                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1681                serde_json::to_value(report)
1682                    .map_err(|error| ServiceError::Operation(error.to_string()))
1683            }
1684            _ => Err(ServiceError::MethodNotFound),
1685        }
1686    }
1687
1688    fn insert_runtime(
1689        &mut self,
1690        runtime: Box<dyn RuntimeConnection>,
1691    ) -> std::result::Result<Value, ServiceError> {
1692        let connection = format!("runtime-{}", self.next_runtime);
1693        self.next_runtime += 1;
1694        let handle = runtime.handle().clone();
1695        self.runtime_sequences
1696            .entry(handle.runtime_id.clone())
1697            .or_insert(0);
1698        self.runtimes.insert(connection.clone(), runtime);
1699        Ok(json!({"connection": connection, "handle": handle}))
1700    }
1701
1702    #[cfg(feature = "adapter-api")]
1703    async fn insert_hosted_runtime(
1704        &mut self,
1705        runtime: Box<dyn RuntimeConnection>,
1706        capabilities: crate::RuntimeCapabilities,
1707        workspace: PathBuf,
1708    ) -> std::result::Result<Value, ServiceError> {
1709        let (host, connection) = HostedHarnessRuntime::spawn(runtime, capabilities);
1710        let token: std::sync::Arc<str> = crate::server::generate_token().into();
1711        let server = crate::server::run_frontend_http(
1712            host.clone(),
1713            host.frontend_sender(),
1714            "127.0.0.1:0",
1715            token.clone(),
1716        )
1717        .await
1718        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1719        let source = LiveRuntimeSource {
1720            harness: connection.handle().harness.as_str().to_string(),
1721            session_id: connection.handle().runtime_id.clone(),
1722            workspace: workspace.clone(),
1723        };
1724        let registration = register_live_runtime(
1725            connection.handle().runtime_id.clone(),
1726            source.clone(),
1727            format!("http://{}", server.address()),
1728            token.to_string(),
1729        )
1730        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1731        let endpoint = registration.endpoint().to_string();
1732        let launch = StructuredLaunch {
1733            cwd: workspace,
1734            // Pin attachment to the executable hosting this runtime. A bare
1735            // `supercode` could resolve to an older global install whose CLI
1736            // does not understand the receipt it is being asked to open.
1737            program: std::env::current_exe()
1738                .ok()
1739                .map(|path| path.to_string_lossy().into_owned())
1740                .unwrap_or_else(|| "supercode".into()),
1741            arguments: vec![
1742                "harness".into(),
1743                "attach".into(),
1744                "--endpoint".into(),
1745                endpoint,
1746                "--harness".into(),
1747                source.harness,
1748                "--session".into(),
1749                source.session_id,
1750            ],
1751            env: BTreeMap::new(),
1752        };
1753        let lease = HostedRuntimeLease {
1754            connection,
1755            _host: host,
1756            _registration: registration,
1757            _server: server,
1758        };
1759        let opened = self.insert_runtime(Box::new(lease))?;
1760        let connection_id = opened["connection"]
1761            .as_str()
1762            .expect("insert_runtime returns a connection id")
1763            .to_string();
1764        self.terminal_launches.insert(connection_id, launch);
1765        Ok(opened)
1766    }
1767
1768    #[cfg(not(feature = "adapter-api"))]
1769    async fn insert_hosted_runtime(
1770        &mut self,
1771        runtime: Box<dyn RuntimeConnection>,
1772        _capabilities: crate::RuntimeCapabilities,
1773        _workspace: PathBuf,
1774    ) -> std::result::Result<Value, ServiceError> {
1775        self.insert_runtime(runtime)
1776    }
1777
1778    fn runtime_mut(
1779        &mut self,
1780        connection: &str,
1781    ) -> std::result::Result<&mut Box<dyn RuntimeConnection>, ServiceError> {
1782        self.runtimes.get_mut(connection).ok_or_else(|| {
1783            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
1784        })
1785    }
1786
1787    /// ORCH-19: run one conversation-lifecycle verb through the harness's own
1788    /// door.
1789    ///
1790    /// Two doors, one shape. A CLI / HTTP / own-store door is self-contained
1791    /// in [`crate::sessions_control`]. A LIVE door (Hermes's and OpenClaw's
1792    /// `/new` and `/reset`, which are slash commands their gateway interprets
1793    /// INSIDE a session) is performed here, because only the service owns the
1794    /// open runtime connection — the command is typed through the very same
1795    /// `send_input` path a human's message takes, so supercode invents no
1796    /// private channel.
1797    async fn mutate_session(
1798        &mut self,
1799        verb: crate::SessionVerb,
1800        params: Value,
1801    ) -> std::result::Result<Value, ServiceError> {
1802        let mutation = decode::<crate::SessionMutation>(params)?;
1803        let door = crate::sessions_control::door(&mutation.harness, verb)
1804            .map_err(session_control_error)?;
1805        let outcome = match door {
1806            // The live door types the slash command through an open hosted
1807            // runtime, which only exists with the `adapter-api` feature; the
1808            // CLI / HTTP / own-store doors below need nothing extra.
1809            #[cfg(not(feature = "adapter-api"))]
1810            crate::SessionDoor::Live(command) => {
1811                return Err(ServiceError::Operation(format!(
1812                    "`{}` performs `sessions.{}` by typing `{command}` into a live driven \
1813                     session, which needs this build's `adapter-api` feature",
1814                    mutation.harness,
1815                    verb.as_str()
1816                )));
1817            }
1818            #[cfg(feature = "adapter-api")]
1819            crate::SessionDoor::Live(command) => {
1820                let connection = mutation
1821                    .connection
1822                    .clone()
1823                    .filter(|value| !value.trim().is_empty())
1824                    .ok_or_else(|| {
1825                        ServiceError::InvalidParams(format!(
1826                            "`{}` performs `sessions.{}` by typing `{command}` into a live \
1827                             driven session: pass the `connection` of an open runtime \
1828                             (`harness.v1.runtimes.start`)",
1829                            mutation.harness,
1830                            verb.as_str()
1831                        ))
1832                    })?;
1833                let runtime = self.runtime_mut(&connection)?;
1834                let session = mutation
1835                    .session
1836                    .clone()
1837                    .filter(|value| !value.trim().is_empty())
1838                    .unwrap_or_else(|| runtime.handle().runtime_id.clone());
1839                runtime
1840                    .send_input(RuntimeInput {
1841                        text: command.to_string(),
1842                        image_urls: Vec::new(),
1843                    })
1844                    .await
1845                    .map_err(operation)?;
1846                crate::sessions_control::live_outcome(verb, &mutation, command, session)
1847                    .map_err(session_control_error)?
1848            }
1849            _ => crate::sessions_control::mutate(verb, &mutation)
1850                .await
1851                .map_err(session_control_error)?,
1852        };
1853        serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
1854    }
1855
1856    async fn inventory_call(
1857        &self,
1858        method: &str,
1859        params: Value,
1860    ) -> std::result::Result<Value, ServiceError> {
1861        let mut params = decode::<HarnessInventoryParams>(params)?;
1862        if method == "harness.v1.harnesses.probe" {
1863            let harness = params.harness.take().ok_or_else(|| {
1864                ServiceError::InvalidParams("harnesses.probe requires `harness`".into())
1865            })?;
1866            params.harnesses = vec![harness];
1867        }
1868        let selected = params
1869            .harnesses
1870            .iter()
1871            .map(HarnessId::as_str)
1872            .collect::<std::collections::BTreeSet<_>>();
1873        let supported = harness_support_registry()
1874            .harnesses
1875            .into_iter()
1876            .filter(|descriptor| selected.is_empty() || selected.contains(descriptor.id.as_str()))
1877            .collect::<Vec<_>>();
1878        if !params.harnesses.is_empty() && supported.len() != selected.len() {
1879            let known = supported
1880                .iter()
1881                .map(|harness| harness.id.as_str())
1882                .collect::<std::collections::BTreeSet<_>>();
1883            let missing = params
1884                .harnesses
1885                .iter()
1886                .filter(|id| !known.contains(id.as_str()))
1887                .map(HarnessId::as_str)
1888                .collect::<Vec<_>>();
1889            return Err(ServiceError::InvalidParams(format!(
1890                "unknown harness(es): {}",
1891                missing.join(", ")
1892            )));
1893        }
1894        let global_counts = params
1895            .include_sessions
1896            .then(|| self.session_counts(None, &params.harnesses));
1897        let workspace_counts = params.include_sessions.then(|| {
1898            params
1899                .workspace
1900                .as_deref()
1901                .map(|workspace| self.session_counts(Some(workspace), &params.harnesses))
1902        });
1903        let probes = supported.into_iter().map(|descriptor| {
1904            let global = global_counts
1905                .as_ref()
1906                .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
1907            let workspace = workspace_counts
1908                .as_ref()
1909                .and_then(Option::as_ref)
1910                .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
1911            self.probe_harness(descriptor, &params, global, workspace)
1912        });
1913        let harnesses = futures::future::join_all(probes).await;
1914        serde_json::to_value(HarnessInventoryReport {
1915            probe: params.probe,
1916            workspace: params.workspace,
1917            harnesses,
1918        })
1919        .map_err(|error| ServiceError::Operation(error.to_string()))
1920    }
1921
1922    #[cfg(feature = "adapter-api")]
1923    async fn harness_authentication_call(
1924        &self,
1925        method: &str,
1926        params: Value,
1927    ) -> std::result::Result<Value, ServiceError> {
1928        match method {
1929            "harness.v1.harnesses.auth.methods" | "harness.v1.harnesses.auth.verify" => {
1930                let params = decode::<HarnessAuthenticationParams>(params)?;
1931                serde_json::to_value(crate::inspect_harness_authentication(&params.harness).await)
1932                    .map_err(|error| ServiceError::Operation(error.to_string()))
1933            }
1934            "harness.v1.harnesses.auth.begin" => {
1935                let params = decode::<BeginHarnessAuthenticationParams>(params)?;
1936                let cwd = params
1937                    .cwd
1938                    .or_else(|| std::env::current_dir().ok())
1939                    .unwrap_or_else(|| PathBuf::from("."));
1940                let plan = crate::harness_authentication_plan(
1941                    &params.harness,
1942                    params.environment,
1943                    params.method,
1944                    &cwd,
1945                )
1946                .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
1947                serde_json::to_value(plan)
1948                    .map_err(|error| ServiceError::Operation(error.to_string()))
1949            }
1950            _ => Err(ServiceError::MethodNotFound),
1951        }
1952    }
1953
1954    async fn probe_harness(
1955        &self,
1956        descriptor: crate::HarnessSupportDescriptor,
1957        params: &HarnessInventoryParams,
1958        global: Option<usize>,
1959        workspace: Option<usize>,
1960    ) -> LocalHarness {
1961        let launch = descriptor.runtime.default_launch.as_ref();
1962        // ORC-7: the orchestrator publishes no runtime launch — it is not an
1963        // adapter supercode connects a turn to. What "installed" means for it
1964        // is that its Node daemon entry is present, so the row answers from
1965        // that instead of from a PATH lookup it could never satisfy.
1966        let orchestrator_entry = (descriptor.id.as_str() == HarnessId::ORCHESTRATOR)
1967            .then(crate::orchestrator::daemon_entry)
1968            .and_then(Result::ok);
1969        let executable = match &orchestrator_entry {
1970            Some(entry) => Some(entry.clone()),
1971            None => launch.and_then(|launch| find_executable(&launch.program)),
1972        };
1973        let installed = executable.is_some();
1974        let version = if params.skip_versions || orchestrator_entry.is_some() {
1975            // The orchestrator's "executable" is a Node module, not a CLI
1976            // with a `--version` flag; running it to ask would start a daemon.
1977            None
1978        } else {
1979            match executable.as_deref() {
1980                Some(path) => executable_version(path).await,
1981                None => None,
1982            }
1983        };
1984        let configured = auth_evidence(descriptor.id.as_str());
1985        let mut auth = if configured {
1986            HarnessAuthState::Configured
1987        } else if matches!(
1988            descriptor.id.as_str(),
1989            HarnessId::CLAUDE_CODE | HarnessId::CODEX
1990        ) {
1991            // These two adapters have explicit native status/login contracts
1992            // and complete local evidence coverage (including Claude's macOS
1993            // Keychain-backed oauthAccount marker). Treating absent evidence
1994            // as unknown advertises a start that will only fail interactively.
1995            HarnessAuthState::Required
1996        } else {
1997            HarnessAuthState::Unknown
1998        };
1999        let mut runtime = if installed {
2000            HarnessRuntimeState::Degraded
2001        } else {
2002            HarnessRuntimeState::Unavailable
2003        };
2004        let is_orchestrator = descriptor.id.as_str() == HarnessId::ORCHESTRATOR;
2005        let mut reason = (!installed).then(|| {
2006            if is_orchestrator {
2007                format!(
2008                    "{} is supported but its daemon entry `{}` was not found",
2009                    descriptor.display_name,
2010                    crate::orchestrator::DAEMON_ENTRY
2011                )
2012            } else {
2013                format!(
2014                    "{} is supported but `{}` was not found on PATH",
2015                    descriptor.display_name,
2016                    launch
2017                        .map(|launch| launch.program.as_str())
2018                        .unwrap_or("executable")
2019                )
2020            }
2021        });
2022        let mut repair = (!installed).then(|| {
2023            if is_orchestrator {
2024                format!(
2025                    "Install the `supercode-orchestrator` package so `{}` resolves.",
2026                    crate::orchestrator::DAEMON_ENTRY
2027                )
2028            } else {
2029                format!(
2030                    "Install {} and ensure `{}` is on PATH.",
2031                    descriptor.display_name,
2032                    launch
2033                        .map(|launch| launch.program.as_str())
2034                        .unwrap_or("its executable")
2035                )
2036            }
2037        });
2038
2039        if installed && params.probe == HarnessProbeLevel::Handshake {
2040            let backend_params = RuntimeBackendParams {
2041                harness: descriptor.id.clone(),
2042                protocol: None,
2043                launch: None,
2044                base_url: None,
2045                policy: RuntimePolicy::Default,
2046            };
2047            match runtime_backend(&backend_params) {
2048                Ok(backend) => {
2049                    let cwd = params
2050                        .workspace
2051                        .clone()
2052                        .or_else(|| std::env::current_dir().ok())
2053                        .unwrap_or_else(|| PathBuf::from("."));
2054                    let isolated = descriptor
2055                        .runtime
2056                        .default_launch
2057                        .clone()
2058                        .and_then(|launch| {
2059                            IsolatedProbeHome::new(descriptor.id.as_str(), launch).ok()
2060                        });
2061                    let Some(isolated) = isolated else {
2062                        reason = Some(
2063                            "No-prompt runtime handshake could not create its isolated harness home."
2064                                .into(),
2065                        );
2066                        repair = Some(
2067                            "Check temporary-directory permissions, then run the handshake probe again."
2068                                .into(),
2069                        );
2070                        let running = probe_running_instance(descriptor.id.as_str());
2071                        return LocalHarness {
2072                            gateway: gateway_health(
2073                                descriptor.id.as_str(),
2074                                installed,
2075                                running.as_ref(),
2076                                version.as_deref(),
2077                            ),
2078                            id: descriptor.id,
2079                            display_name: descriptor.display_name,
2080                            supported: true,
2081                            installed,
2082                            executable: executable.map(|path| path.to_string_lossy().into_owned()),
2083                            version,
2084                            auth,
2085                            runtime,
2086                            protocol: descriptor.runtime.protocol,
2087                            capabilities: descriptor.runtime.capabilities.clone(),
2088                            effective_capabilities: descriptor.runtime.capabilities,
2089                            sessions: HarnessSessionCounts { global, workspace },
2090                            running,
2091                            reason,
2092                            repair,
2093                        };
2094                    };
2095                    match tokio::time::timeout(
2096                        Duration::from_secs(30),
2097                        backend.start(RuntimeStartRequest {
2098                            cwd,
2099                            launch: Some(isolated.launch.clone()),
2100                            mcp_servers: Vec::new(),
2101                        }),
2102                    )
2103                    .await
2104                    {
2105                        Ok(Ok(mut connection)) => {
2106                            match stabilize_handshake(connection.as_mut()).await {
2107                                Ok(()) => {
2108                                    auth = HarnessAuthState::Ready;
2109                                    runtime = HarnessRuntimeState::Ready;
2110                                    reason = Some(
2111                                        "No-prompt runtime handshake remained healthy through the startup stabilization window; no model request was sent."
2112                                            .into(),
2113                                    );
2114                                    repair = None;
2115                                }
2116                                Err(message) => {
2117                                    auth = if looks_like_auth_error(&message) {
2118                                        HarnessAuthState::Required
2119                                    } else if configured {
2120                                        HarnessAuthState::Configured
2121                                    } else {
2122                                        HarnessAuthState::Unknown
2123                                    };
2124                                    reason = Some(format!(
2125                                        "No-prompt runtime handshake became unhealthy during startup: {message}"
2126                                    ));
2127                                    repair = Some(if auth == HarnessAuthState::Required {
2128                                        format!(
2129                                            "Run `{}` interactively once and complete sign-in, then probe again.",
2130                                            launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2131                                        )
2132                                    } else {
2133                                        "Run the harness directly to inspect its startup failure, then probe again."
2134                                            .into()
2135                                    });
2136                                }
2137                            }
2138                            let _ =
2139                                tokio::time::timeout(Duration::from_secs(3), connection.close())
2140                                    .await;
2141                        }
2142                        Ok(Err(error)) => {
2143                            let message = truncate_text(&error.to_string(), 500);
2144                            auth = if looks_like_auth_error(&message) {
2145                                HarnessAuthState::Required
2146                            } else if configured {
2147                                HarnessAuthState::Configured
2148                            } else {
2149                                HarnessAuthState::Unknown
2150                            };
2151                            reason = Some(format!("No-prompt runtime handshake failed: {message}"));
2152                            repair = Some(if auth == HarnessAuthState::Required {
2153                                format!(
2154                                    "Run `{}` interactively once and complete sign-in, then probe again.",
2155                                    launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2156                                )
2157                            } else {
2158                                "Check the harness installation and run the handshake probe again."
2159                                    .into()
2160                            });
2161                        }
2162                        Err(_) => {
2163                            reason = Some(
2164                                "No-prompt runtime handshake timed out after 30 seconds.".into(),
2165                            );
2166                            repair = Some("Run the harness directly to check startup or authentication, then probe again.".into());
2167                        }
2168                    }
2169                    // Keep the isolated home alive through process teardown.
2170                    // Otherwise the compiler may release the last meaningful
2171                    // use after cloning `launch`, and a still-starting CLI can
2172                    // recreate its state directory after Drop removed it.
2173                    // Some Node-based launchers finish a short asynchronous
2174                    // installation-id write just after their parent process
2175                    // is reaped. Remove once immediately, allow that bounded
2176                    // writer to settle, then perform the authoritative pass.
2177                    let _ = isolated.cleanup();
2178                    tokio::time::sleep(Duration::from_millis(250)).await;
2179                    if let Err(error) = isolated.cleanup() {
2180                        auth = if configured {
2181                            HarnessAuthState::Configured
2182                        } else {
2183                            HarnessAuthState::Unknown
2184                        };
2185                        runtime = HarnessRuntimeState::Degraded;
2186                        reason = Some(format!(
2187                            "No-prompt runtime handshake could not remove its isolated harness home: {error}"
2188                        ));
2189                        repair = Some(
2190                            "Check temporary-directory permissions, remove the reported disposable probe home, then run the handshake again."
2191                                .into(),
2192                        );
2193                    }
2194                }
2195                Err(error) => {
2196                    reason = Some(error_message(error));
2197                }
2198            }
2199        } else if installed && configured {
2200            reason = Some("Executable and local authentication evidence found; use a handshake probe to verify readiness.".into());
2201        } else if installed && auth == HarnessAuthState::Required {
2202            reason =
2203                Some("Executable found, but no native authentication evidence is present.".into());
2204            repair = Some(format!(
2205                "Run `supercode harness login {}` to use the harness-owned sign-in flow.",
2206                descriptor.id.as_str()
2207            ));
2208        } else if installed {
2209            reason = Some("Executable found; authentication readiness is unknown until a no-prompt handshake succeeds.".into());
2210            repair =
2211                Some(format!(
2212                "Run `{}` interactively once if sign-in is required, or use `--probe handshake`.",
2213                launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2214            ));
2215        }
2216
2217        let effective_capabilities = if installed {
2218            descriptor.runtime.capabilities.clone()
2219        } else {
2220            unavailable_capabilities()
2221        };
2222        let running = probe_running_instance(descriptor.id.as_str());
2223        LocalHarness {
2224            gateway: gateway_health(
2225                descriptor.id.as_str(),
2226                installed,
2227                running.as_ref(),
2228                version.as_deref(),
2229            ),
2230            id: descriptor.id,
2231            display_name: descriptor.display_name,
2232            supported: true,
2233            installed,
2234            executable: executable.map(|path| path.to_string_lossy().into_owned()),
2235            version,
2236            auth,
2237            runtime,
2238            protocol: descriptor.runtime.protocol,
2239            capabilities: descriptor.runtime.capabilities,
2240            effective_capabilities,
2241            sessions: HarnessSessionCounts { global, workspace },
2242            running,
2243            reason,
2244            repair,
2245        }
2246    }
2247
2248    fn session_counts(
2249        &self,
2250        workspace: Option<&Path>,
2251        harnesses: &[HarnessId],
2252    ) -> BTreeMap<String, usize> {
2253        let mut counts = BTreeMap::new();
2254        for session in self
2255            .catalog
2256            .discover(&DiscoveryQuery {
2257                workspace: workspace.map(Path::to_path_buf),
2258                harnesses: harnesses.to_vec(),
2259                ..DiscoveryQuery::default()
2260            })
2261            .unwrap_or_default()
2262        {
2263            *counts
2264                .entry(session.locator.harness.as_str().to_string())
2265                .or_insert(0) += 1;
2266        }
2267        counts
2268    }
2269}
2270
2271#[async_trait::async_trait]
2272impl SdkService for HarnessSessionService {
2273    fn capabilities(&self) -> SdkCapabilities {
2274        SdkCapabilities::default()
2275    }
2276
2277    async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError> {
2278        if request.operation == SdkOperation::Events {
2279            let events = self
2280                .poll_sdk_events()
2281                .await
2282                .into_iter()
2283                .map(|(_, event)| event)
2284                .collect::<Vec<_>>();
2285            return serde_json::to_value(events).map_err(|error| {
2286                SdkError::new(
2287                    SdkErrorCode::Execution,
2288                    request.operation,
2289                    error.to_string(),
2290                )
2291            });
2292        }
2293        if self.runtimes.is_empty()
2294            && matches!(
2295                request.operation,
2296                SdkOperation::Input
2297                    | SdkOperation::Interrupt
2298                    | SdkOperation::Steer
2299                    | SdkOperation::Respond
2300                    | SdkOperation::Close
2301            )
2302        {
2303            return Err(SdkError::unsupported(request.operation));
2304        }
2305        let method = request
2306            .operation
2307            .method()
2308            .ok_or_else(|| SdkError::unsupported(request.operation))?;
2309        let result = match request.operation {
2310            SdkOperation::Discover
2311            | SdkOperation::Load
2312            | SdkOperation::Export
2313            | SdkOperation::ProfilesList
2314            | SdkOperation::ProfilesGet
2315            | SdkOperation::ProfilesCreate
2316            | SdkOperation::ProfilesDelete
2317            | SdkOperation::SkillsList
2318            | SdkOperation::SkillsInstall
2319            | SdkOperation::SkillsRemove
2320            | SdkOperation::ChannelsList
2321            | SdkOperation::RoutesList
2322            | SdkOperation::TriggersList
2323            | SdkOperation::ChannelsStatus
2324            | SdkOperation::MemoryShow
2325            | SdkOperation::MemorySearch
2326            | SdkOperation::JobsList
2327            | SdkOperation::JobsGet
2328            | SdkOperation::JobsCreate
2329            | SdkOperation::JobsUpdate
2330            | SdkOperation::JobsPause
2331            | SdkOperation::JobsResume
2332            | SdkOperation::JobsRun
2333            | SdkOperation::JobsDelete
2334            | SdkOperation::RunsList
2335            | SdkOperation::RunsGet
2336            | SdkOperation::ApprovalsList
2337            | SdkOperation::OrchestrationLoad
2338            | SdkOperation::OrchestrationSave
2339            | SdkOperation::OrchestrationCompile
2340            | SdkOperation::OrchestrationDecompile
2341            | SdkOperation::OrchestrationImport
2342            | SdkOperation::OrchestrationExport
2343            | SdkOperation::WorkflowLoad => self.call(method, request.params),
2344            // ORCH-20: answering needs the live connection, so it takes the
2345            // async door and ends in `harness.v1.runtimes.respond`.
2346            SdkOperation::ApprovalsResolve => self.approvals_resolve(request.params).await,
2347            SdkOperation::Start
2348            | SdkOperation::Resume
2349            | SdkOperation::Input
2350            | SdkOperation::Interrupt
2351            | SdkOperation::Steer
2352            | SdkOperation::Respond
2353            | SdkOperation::Close => self.runtime_call(method, request.params).await,
2354            // ORCH-19 controlled tier. Every verb goes through the HARNESS'S
2355            // OWN door — its CLI, its HTTP API, or its slash command typed
2356            // into a live driven session — and returns the row re-read from
2357            // the harness's store afterwards.
2358            SdkOperation::SessionsNew => {
2359                self.mutate_session(crate::SessionVerb::New, request.params)
2360                    .await
2361            }
2362            SdkOperation::SessionsReset => {
2363                self.mutate_session(crate::SessionVerb::Reset, request.params)
2364                    .await
2365            }
2366            SdkOperation::SessionsArchive => {
2367                self.mutate_session(crate::SessionVerb::Archive, request.params)
2368                    .await
2369            }
2370            SdkOperation::SessionsDelete => {
2371                self.mutate_session(crate::SessionVerb::Delete, request.params)
2372                    .await
2373            }
2374            SdkOperation::Events => unreachable!("handled before method dispatch"),
2375        };
2376        result.map_err(|error| sdk_error(request.operation, error))
2377    }
2378
2379    async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError> {
2380        Ok(self
2381            .poll_sdk_events()
2382            .await
2383            .into_iter()
2384            .map(|(_, event)| event)
2385            .collect())
2386    }
2387}
2388
2389#[cfg(feature = "adapter-api")]
2390struct HostedRuntimeLease {
2391    connection: HostedHarnessConnection,
2392    _host: std::sync::Arc<HostedHarnessRuntime>,
2393    _registration: LiveRuntimeRegistration,
2394    _server: crate::server::FrontendHttpServer,
2395}
2396
2397#[async_trait::async_trait]
2398#[cfg(feature = "adapter-api")]
2399impl RuntimeConnection for HostedRuntimeLease {
2400    fn handle(&self) -> &crate::RuntimeHandle {
2401        self.connection.handle()
2402    }
2403
2404    async fn send_input(&mut self, input: RuntimeInput) -> crate::Result<Option<String>> {
2405        self.connection.send_input(input).await
2406    }
2407
2408    async fn next_event(&mut self) -> crate::Result<Option<crate::HarnessEvent>> {
2409        self.connection.next_event().await
2410    }
2411
2412    async fn interrupt(&mut self) -> crate::Result<()> {
2413        self.connection.interrupt().await
2414    }
2415
2416    async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
2417        self.connection.respond(request_id, response).await
2418    }
2419
2420    async fn close(&mut self) -> crate::Result<()> {
2421        self.connection.close().await
2422    }
2423}
2424
2425async fn stabilize_handshake(connection: &mut dyn RuntimeConnection) -> Result<(), String> {
2426    let deadline = tokio::time::Instant::now() + Duration::from_secs(3);
2427    loop {
2428        let now = tokio::time::Instant::now();
2429        if now >= deadline {
2430            return Ok(());
2431        }
2432        match tokio::time::timeout(deadline - now, connection.next_event()).await {
2433            Err(_) => return Ok(()),
2434            Ok(Ok(Some(event))) => {
2435                if let Some(message) = handshake_event_failure(&event) {
2436                    return Err(truncate_text(&message, 500));
2437                }
2438            }
2439            Ok(Ok(None)) => return Err("runtime transport closed during startup".into()),
2440            Ok(Err(error)) => return Err(error.to_string()),
2441        }
2442    }
2443}
2444
2445fn handshake_event_failure(event: &crate::HarnessEvent) -> Option<String> {
2446    let detail = event
2447        .payload
2448        .get("message")
2449        .or_else(|| event.payload.get("line"))
2450        .and_then(Value::as_str)
2451        .unwrap_or(event.kind.as_str());
2452    match event.kind.as_str() {
2453        "transport_closed" => Some("runtime transport closed during startup".into()),
2454        "transport_error" => Some(format!("runtime transport error: {detail}")),
2455        "malformed_output" => Some(format!("runtime emitted non-protocol output: {detail}")),
2456        // Stderr is retained as a runtime event, but is not transport health.
2457        // Grok, for example, can log an AuthorizationRequired error from an
2458        // optional background worker while its ACP session continues to send
2459        // updates and complete prompts normally.
2460        _ => None,
2461    }
2462}
2463
2464fn indexed_claude_window(
2465    locator: &SessionLocator,
2466    options: &SessionLoadOptions,
2467) -> std::result::Result<Option<Value>, ServiceError> {
2468    use supercode_interchange::session::ClaudeReadIndex;
2469    // Exact parent-only window: recursive/full-artifact requests retain the
2470    // existing owner. This is not a bounded display-history substitution.
2471    if locator.harness.as_str() != HarnessId::CLAUDE_CODE
2472        || options.include_subagents != Some(false)
2473    {
2474        return Ok(None);
2475    }
2476    let crate::StorageLocator::File { path } = &locator.storage else {
2477        return Ok(None);
2478    };
2479    if !ClaudeReadIndex::supports(path)
2480        .map_err(|error| ServiceError::Operation(error.to_string()))?
2481    {
2482        return Ok(None);
2483    }
2484    let mut index = ClaudeReadIndex::open(path, Fidelity::ByteLossless)
2485        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2486    let total = index.len();
2487    let (offset, end) = projected_message_window(total, options);
2488    let session = index
2489        .read_messages(offset..end)
2490        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2491    let summary = index
2492        .read_summary()
2493        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2494    let selected_options = SessionLoadOptions {
2495        message_offset: None,
2496        message_limit: None,
2497        message_tail: None,
2498        ..options.clone()
2499    };
2500    let mut selected = projected_session_json(&session, &selected_options);
2501    selected["raw_record_count"] = json!(index.raw_record_count());
2502    Ok(Some(json!({
2503        "session": selected,
2504        "summary": projected_session_summary(&summary, options),
2505        "window": {
2506            "has_more": offset > 0 || end < total, "has_newer": end < total,
2507            "has_older": offset > 0, "newer_items": index.item_count(end..total),
2508            "offset": offset, "older_items": index.item_count(0..offset),
2509            "returned": end - offset, "total_messages": total,
2510        }
2511    })))
2512}
2513
2514fn projected_session_result(session: &Session, options: &SessionLoadOptions) -> Value {
2515    let total_messages = session.messages.len();
2516    let (offset, end) = projected_message_window(total_messages, options);
2517    json!({
2518        "session": projected_session_json(session, options),
2519        "summary": projected_session_summary(session, options),
2520        "window": {
2521            "has_more": offset > 0 || end < total_messages,
2522            "has_newer": end < total_messages,
2523            "has_older": offset > 0,
2524            "newer_items": normalized_item_count(&session.messages[end..]),
2525            "offset": offset,
2526            "older_items": normalized_item_count(&session.messages[..offset]),
2527            "returned": end.saturating_sub(offset),
2528            "total_messages": total_messages,
2529        }
2530    })
2531}
2532
2533fn normalized_item_count(messages: &[crate::ChatMessage]) -> usize {
2534    messages
2535        .iter()
2536        .map(|message| {
2537            let conversation = usize::from(
2538                matches!(message.role, Role::Assistant | Role::User)
2539                    && message_has_content(message),
2540            );
2541            let tool_result =
2542                usize::from(message.role == Role::Tool && message_has_content(message));
2543            conversation + tool_result + message.tool_calls().len()
2544        })
2545        .sum()
2546}
2547
2548fn projected_session_summary(session: &Session, options: &SessionLoadOptions) -> Value {
2549    let mut conversational = session.messages.iter().filter(|message| {
2550        matches!(message.role, Role::Assistant | Role::User) && message_has_content(message)
2551    });
2552    let first_message = conversational.clone().next();
2553    let last_message = conversational.next_back();
2554    let mut assistant = session
2555        .messages
2556        .iter()
2557        .filter(|message| message.role == Role::Assistant && message_has_content(message));
2558    let first_assistant_message = assistant.clone().next();
2559    let last_assistant_message = assistant.next_back();
2560    let end_of_turn = session
2561        .messages
2562        .iter()
2563        .rev()
2564        .find(|message| message.role != Role::System)
2565        .is_some_and(|message| {
2566            message.role == Role::Assistant
2567                && message_has_content(message)
2568                && message.tool_calls().is_empty()
2569        });
2570    let project = |message: Option<&crate::ChatMessage>| {
2571        message.map(|message| project_inline_media(message_json(message), options))
2572    };
2573    json!({
2574        "end_of_turn": end_of_turn,
2575        "first_assistant_message": project(first_assistant_message),
2576        "first_message": project(first_message),
2577        "last_assistant_message": project(last_assistant_message),
2578        "last_assistant_text": last_assistant_message.map(message_text).unwrap_or_default(),
2579        "last_message": project(last_message),
2580    })
2581}
2582
2583fn message_has_content(message: &crate::ChatMessage) -> bool {
2584    message
2585        .content
2586        .as_deref()
2587        .is_some_and(|content| !content.trim().is_empty())
2588        || message
2589            .content_parts
2590            .as_ref()
2591            .is_some_and(|parts| !parts.is_empty())
2592}
2593
2594fn message_text(message: &crate::ChatMessage) -> String {
2595    if let Some(content) = &message.content {
2596        return content.clone();
2597    }
2598    message
2599        .content_parts
2600        .as_ref()
2601        .into_iter()
2602        .flatten()
2603        .filter_map(|part| part.get("text").and_then(Value::as_str))
2604        .collect::<Vec<_>>()
2605        .join("\n")
2606}
2607
2608fn projected_session_json(session: &Session, options: &SessionLoadOptions) -> Value {
2609    let (offset, end) = projected_message_window(session.messages.len(), options);
2610    let messages = session.messages[offset..end]
2611        .iter()
2612        .map(|message| project_inline_media(message_json(message), options))
2613        .collect::<Vec<_>>();
2614    let subagents = if options.include_subagents.unwrap_or(true) {
2615        // The reported window describes the top-level transcript. Applying it
2616        // recursively would silently truncate subagents without returning a
2617        // window for each child. Keep their histories complete while carrying
2618        // the caller's media policy through the tree.
2619        let subagent_options = SessionLoadOptions {
2620            message_limit: None,
2621            message_offset: None,
2622            message_tail: None,
2623            ..options.clone()
2624        };
2625        session
2626            .subagents
2627            .iter()
2628            .map(|subagent| projected_session_json(subagent, &subagent_options))
2629            .collect::<Vec<_>>()
2630    } else {
2631        Vec::new()
2632    };
2633    json!({
2634        "source": match session.meta.source {
2635            SessionSource::ClaudeCode => "claude_code",
2636            SessionSource::Codex => "codex",
2637            SessionSource::Gemini => "gemini",
2638            SessionSource::Goose => "goose",
2639            SessionSource::Grok => "grok",
2640            SessionSource::Native => "native",
2641            SessionSource::OpenClaw => "openclaw",
2642            SessionSource::Hermes => "hermes",
2643            SessionSource::OpenCode => "opencode",
2644            SessionSource::Pi => "pi",
2645        },
2646        "session_id": session.meta.session_id,
2647        "model": session.meta.model,
2648        "cwd": session.meta.cwd,
2649        "system_prompt": session.meta.system_prompt,
2650        "agent_id": session.meta.agent_id,
2651        "parent_tool_use_id": session.meta.parent_tool_use_id,
2652        "lineage": session.meta.lineage,
2653        "messages": messages,
2654        "subagents": subagents,
2655        "raw_record_count": session.raw.len(),
2656        "parse_error_lines": session.parse_error_lines,
2657    })
2658}
2659
2660fn projected_message_window(total: usize, options: &SessionLoadOptions) -> (usize, usize) {
2661    if let Some(tail) = options.message_tail {
2662        return (total.saturating_sub(tail), total);
2663    }
2664    let offset = options.message_offset.unwrap_or(0).min(total);
2665    let end = options
2666        .message_limit
2667        .map(|limit| offset.saturating_add(limit).min(total))
2668        .unwrap_or(total);
2669    (offset, end)
2670}
2671
2672fn project_inline_media(mut message: Value, options: &SessionLoadOptions) -> Value {
2673    let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else {
2674        return message;
2675    };
2676    for part in parts {
2677        let Some(url) = part
2678            .get("image_url")
2679            .and_then(|image| image.get("url"))
2680            .and_then(Value::as_str)
2681        else {
2682            continue;
2683        };
2684        let Some(rest) = url.strip_prefix("data:") else {
2685            continue;
2686        };
2687        let Some((media_type, encoded)) = rest.split_once(";base64,") else {
2688            continue;
2689        };
2690        let padding = usize::from(encoded.ends_with('=')) + usize::from(encoded.ends_with("=="));
2691        let decoded_bytes = encoded.len().saturating_mul(3) / 4;
2692        let decoded_bytes = decoded_bytes.saturating_sub(padding);
2693        let should_elide = matches!(options.inline_media, InlineMediaMode::Metadata)
2694            || options
2695                .max_inline_media_bytes
2696                .is_some_and(|limit| decoded_bytes > limit);
2697        if should_elide {
2698            *part = json!({
2699                "type": "media_reference",
2700                "media_type": media_type,
2701                "encoding": "base64",
2702                "encoded_bytes": encoded.len(),
2703                "decoded_bytes": decoded_bytes,
2704                "omitted": true,
2705            });
2706        }
2707    }
2708    message
2709}
2710
2711#[derive(Deserialize)]
2712struct LocatorParams {
2713    locator: SessionLocator,
2714    /// Optional fidelity for the READ surfaces (`sessions.load`,
2715    /// `sessions.follow`).
2716    ///
2717    /// Omitted means [`Fidelity::Semantic`]: these two methods only ever
2718    /// produce a read-only view, and a compacted or resumed-across-files
2719    /// transcript — the everyday shape of a long Claude Code session — has no
2720    /// losslessly reconstructable record graph, so refusing to render it made
2721    /// the mirror unusable rather than accurate. A caller that intends to
2722    /// CONTINUE from what it reads asks for a lossless level explicitly and
2723    /// gets the strict refusal back. Every other method (export, translate,
2724    /// branch, handoff, resume_instructions) is lossless-only and has no
2725    /// such knob.
2726    #[serde(default)]
2727    fidelity: Option<Fidelity>,
2728    /// Optional bounded frontend projection. Absent preserves the historical
2729    /// complete-session read contract.
2730    #[serde(default)]
2731    view: Option<SessionReadView>,
2732}
2733
2734#[derive(Deserialize)]
2735struct SessionReadView {
2736    /// Number of trailing normalized messages to return. Zero is treated as
2737    /// one so a caller cannot accidentally request an unbounded empty mode.
2738    #[serde(default)]
2739    tail_messages: Option<usize>,
2740    /// Whether Claude Code child transcripts belong in this view. The
2741    /// frontend default is false; the legacy no-view path remains true.
2742    #[serde(default)]
2743    include_subagents: bool,
2744    /// Preserve human-visible native history across model-context compaction.
2745    #[serde(default)]
2746    display_history: bool,
2747    /// Bound each individual text field so a single tool result cannot turn a
2748    /// small message window into a hundred-megabyte RPC response.
2749    #[serde(default)]
2750    max_message_chars: Option<usize>,
2751}
2752
2753impl LocatorParams {
2754    fn read_fidelity(&self) -> Fidelity {
2755        self.fidelity.unwrap_or(Fidelity::Semantic)
2756    }
2757
2758    fn include_subagents(&self) -> bool {
2759        self.view
2760            .as_ref()
2761            .map(|view| view.include_subagents)
2762            .unwrap_or(true)
2763    }
2764
2765    fn tail_messages(&self) -> Option<usize> {
2766        self.view
2767            .as_ref()
2768            .and_then(|view| view.tail_messages)
2769            .map(|limit| limit.clamp(1, 5_000))
2770    }
2771
2772    fn display_history(&self) -> bool {
2773        self.view.as_ref().is_some_and(|view| view.display_history)
2774    }
2775
2776    fn max_message_chars(&self) -> Option<usize> {
2777        self.view
2778            .as_ref()
2779            .and_then(|view| view.max_message_chars)
2780            .map(|limit| limit.clamp(256, 64_000))
2781    }
2782
2783    fn bound_session(&self, session: &mut Session) {
2784        bound_session_view(session, self.tail_messages(), self.max_message_chars());
2785    }
2786}
2787
2788#[derive(Debug, Clone, Copy, Default, Deserialize)]
2789#[serde(rename_all = "snake_case")]
2790enum InlineMediaMode {
2791    #[default]
2792    Full,
2793    Metadata,
2794}
2795
2796#[derive(Debug, Clone, Default, Deserialize)]
2797#[serde(default)]
2798struct SessionLoadOptions {
2799    include_subagents: Option<bool>,
2800    inline_media: InlineMediaMode,
2801    max_inline_media_bytes: Option<usize>,
2802    message_limit: Option<usize>,
2803    message_offset: Option<usize>,
2804    message_tail: Option<usize>,
2805}
2806
2807impl SessionLoadOptions {
2808    fn validate(&self) -> std::result::Result<(), ServiceError> {
2809        if self.message_tail.is_some()
2810            && (self.message_limit.is_some() || self.message_offset.is_some())
2811        {
2812            return Err(ServiceError::InvalidParams(
2813                "sessions.load options.message_tail cannot be combined with message_limit or message_offset"
2814                    .into(),
2815            ));
2816        }
2817        Ok(())
2818    }
2819}
2820
2821#[derive(Deserialize)]
2822struct LoadSessionParams {
2823    #[serde(flatten)]
2824    read: LocatorParams,
2825    #[serde(default)]
2826    options: Option<SessionLoadOptions>,
2827}
2828
2829#[derive(Deserialize)]
2830struct UnfollowParams {
2831    subscription: String,
2832}
2833
2834#[derive(Debug, Deserialize)]
2835#[serde(deny_unknown_fields)]
2836struct IndexResizeParams {
2837    subscription: String,
2838    limit: usize,
2839}
2840
2841#[derive(Deserialize)]
2842struct ActivitySubscribeParams {
2843    locators: Vec<SessionLocator>,
2844    #[serde(default)]
2845    homes: crate::HarnessHomes,
2846}
2847
2848#[derive(Deserialize)]
2849struct MessageSessionParams {
2850    locator: SessionLocator,
2851    text: String,
2852    /// Same storage roots discovery accepts, so a caller (and a test) can
2853    /// point the live-session registry somewhere other than `$HOME`.
2854    #[serde(default)]
2855    homes: crate::HarnessHomes,
2856}
2857
2858#[derive(Deserialize)]
2859#[serde(deny_unknown_fields)]
2860struct HarnessSettingsParams {
2861    harness: String,
2862}
2863
2864#[derive(Deserialize)]
2865#[serde(deny_unknown_fields)]
2866struct ConfigureHarnessParams {
2867    harness: String,
2868    #[serde(default)]
2869    changes: Vec<crate::HarnessSettingChange>,
2870    #[serde(default)]
2871    expected_revision: Option<String>,
2872}
2873
2874fn claude_inbound_controls_or_error(homes: &crate::HarnessHomes) -> (Value, Value) {
2875    match crate::inspect_harness_interop_settings(homes, HarnessId::CLAUDE_CODE) {
2876        Ok(report) => (
2877            serde_json::to_value(report).unwrap_or(Value::Null),
2878            Value::Null,
2879        ),
2880        Err(error) => (
2881            Value::Null,
2882            Value::String(format!(
2883                "Supercode could not inspect Claude Code inbound controls: {error}"
2884            )),
2885        ),
2886    }
2887}
2888
2889/// Deliver `text` into a session that is running right now, or say why not.
2890///
2891/// A refusal is a RESULT, not a JSON-RPC error: "that session is persisted
2892/// only" is an answer about the session, which a mirror renders next to the
2893/// transcript, and this service's error envelope carries no structured data
2894/// field a machine-readable reason could survive in.
2895///
2896/// `delivered_to_bus` is the honest ceiling of what the courier proves. The
2897/// message reached the receiving session's inbox; whether that session ever
2898/// reads it is governed by ITS OWN inbound controls (`crossSessionInbound`,
2899/// approval dialogs), which Supercode neither sees nor overrides.
2900#[cfg(feature = "adapter-api")]
2901async fn message_live_session(
2902    params: &MessageSessionParams,
2903    runner: &dyn crate::claude_peer::CourierRunner,
2904) -> Value {
2905    if params.locator.harness.as_str() != HarnessId::CLAUDE_CODE {
2906        return json!({
2907            "delivered_to_bus": false,
2908            "refusal": {
2909                "reason": crate::claude_peer::ClaudePeerRefusal::HarnessUnsupported.as_str(),
2910                "message": format!(
2911                    "`{}` does not publish a live-session registry; only claude-code sessions can be messaged in place",
2912                    params.locator.harness.as_str()
2913                ),
2914            },
2915        });
2916    }
2917    let (inbound_controls, inbound_controls_error) =
2918        claude_inbound_controls_or_error(&params.homes);
2919    match crate::claude_peer::message_claude_peer(
2920        &params.homes,
2921        &params.locator.session_id,
2922        &params.text,
2923        runner,
2924    )
2925    .await
2926    {
2927        Ok(delivery) => json!({
2928            "delivered_to_bus": true,
2929            "target": {
2930                "session_id": delivery.target.session_id,
2931                "name": delivery.target.name,
2932                "pid": delivery.target.pid,
2933                "cwd": delivery.target.cwd,
2934                "status": delivery.target.status.map(|status| status.as_str()),
2935            },
2936            "courier": {
2937                "model": crate::claude_peer::COURIER_MODEL,
2938                "report": delivery.courier_report,
2939            },
2940            "inbound_controls": inbound_controls,
2941            "inbound_controls_error": inbound_controls_error,
2942        }),
2943        Err(refusal) => json!({
2944            "delivered_to_bus": false,
2945            "refusal": {"reason": refusal.reason.as_str(), "message": refusal.message},
2946            "inbound_controls": inbound_controls,
2947            "inbound_controls_error": inbound_controls_error,
2948        }),
2949    }
2950}
2951
2952/// Source identity of one follow subscription, plus the last lifecycle state
2953/// already reported on it. The follower itself stays purely persistence-facing.
2954// Only the adapter-api poll reads these; the subscription bookkeeping itself is
2955// shared by both builds.
2956#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
2957struct FollowedSource {
2958    harness: String,
2959    session_id: String,
2960    reported: Option<String>,
2961}
2962
2963#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
2964struct ActivitySubscription {
2965    locators: Vec<SessionLocator>,
2966    homes: crate::HarnessHomes,
2967    reported: BTreeMap<(String, String), crate::SessionActivity>,
2968}
2969
2970fn peers_for_descriptors(
2971    descriptors: &[SessionDescriptor],
2972    homes: &HarnessHomes,
2973) -> Vec<crate::claude_peer::ClaudePeerSession> {
2974    if descriptors
2975        .iter()
2976        .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
2977    {
2978        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
2979    } else {
2980        Vec::new()
2981    }
2982}
2983
2984/// Add the live address that makes an indexed row behaviorally equivalent to a discovered row.
2985///
2986/// The durable index owns only persistence metadata. Live endpoints remain projections: every
2987/// message/attach operation revalidates its authority, so publishing one here never trusts a stale
2988/// browser-held handle. Reading the Claude registry once per batch keeps this O(peers + rows).
2989fn live_descriptor_value(
2990    session: &SessionDescriptor,
2991    peers: &[crate::claude_peer::ClaudePeerSession],
2992) -> std::result::Result<Value, ServiceError> {
2993    let mut value = serde_json::to_value(session)
2994        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2995    if let Some(workspace) = &session.cwd {
2996        let source = LiveRuntimeSource {
2997            harness: session.locator.harness.as_str().to_string(),
2998            session_id: session.locator.session_id.clone(),
2999            workspace: workspace.clone(),
3000        };
3001        if let Some(endpoint) = discover_live_runtime(&source)
3002            .map_err(|error| ServiceError::Operation(error.to_string()))?
3003        {
3004            value["live_endpoint"] = json!(endpoint.as_str());
3005        }
3006    }
3007    if value.get("live_endpoint").is_none() {
3008        if let Some(peer) = peers.iter().find(|peer| {
3009            session.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3010                && peer.session_id == session.locator.session_id
3011        }) {
3012            value["live_endpoint"] = json!(peer.endpoint().as_str());
3013        }
3014    }
3015    Ok(value)
3016}
3017
3018fn live_index_changes(
3019    changes: Vec<crate::session_index::SessionIndexChange>,
3020    homes: &HarnessHomes,
3021) -> std::result::Result<Vec<Value>, ServiceError> {
3022    use crate::session_index::SessionIndexChange;
3023    let has_claude = changes.iter().any(|change| match change {
3024        SessionIndexChange::Added { descriptor } | SessionIndexChange::Updated { descriptor } => {
3025            descriptor.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3026        }
3027        SessionIndexChange::Removed { .. } => false,
3028    });
3029    let peers = if has_claude {
3030        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
3031    } else {
3032        Vec::new()
3033    };
3034    changes
3035        .into_iter()
3036        .map(|change| match change {
3037            SessionIndexChange::Added { descriptor } => Ok(json!({
3038                "kind": "added",
3039                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3040            })),
3041            SessionIndexChange::Updated { descriptor } => Ok(json!({
3042                "kind": "updated",
3043                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3044            })),
3045            SessionIndexChange::Removed { key } => Ok(json!({
3046                "kind": "removed",
3047                "key": key,
3048            })),
3049        })
3050        .collect()
3051}
3052
3053fn legacy_live_status(activity: &crate::SessionActivity) -> Option<&'static str> {
3054    use crate::{SessionPresence, SessionTurnState};
3055    match (activity.presence, activity.turn) {
3056        (SessionPresence::Persisted, _) => None,
3057        (SessionPresence::Running, SessionTurnState::Working) => Some("busy"),
3058        (SessionPresence::Running, SessionTurnState::Idle) => Some("idle"),
3059        // The normalized activity object can honestly report a live owner even
3060        // when the stock harness never published a turn status. Preserve the
3061        // older field's stricter contract instead of guessing `running`.
3062        (SessionPresence::Running, SessionTurnState::Unknown)
3063            if activity.evidence.native_state.is_none() =>
3064        {
3065            None
3066        }
3067        (SessionPresence::Running, _) | (SessionPresence::ShuttingDown, _) => Some("running"),
3068    }
3069}
3070
3071#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
3072#[serde(rename_all = "kebab-case")]
3073enum TransferFormat {
3074    ClaudeCode,
3075    Codex,
3076    #[serde(rename = "opencode", alias = "open-code")]
3077    OpenCode,
3078    Pi,
3079    Grok,
3080    Gemini,
3081    Goose,
3082    /// UNI-18: a Hermes target. Its artifact is the Codex rollout that
3083    /// `hermes sessions import --from codex` reads; `sessions.export` performs
3084    /// that import into the Hermes home.
3085    Hermes,
3086}
3087
3088impl TransferFormat {
3089    fn id(self) -> &'static str {
3090        match self {
3091            Self::ClaudeCode => HarnessId::CLAUDE_CODE,
3092            Self::Codex => HarnessId::CODEX,
3093            Self::OpenCode => HarnessId::OPENCODE,
3094            Self::Pi => HarnessId::PI,
3095            Self::Grok => HarnessId::GROK,
3096            Self::Gemini => HarnessId::GEMINI,
3097            Self::Goose => HarnessId::GOOSE,
3098            Self::Hermes => HarnessId::HERMES,
3099        }
3100    }
3101}
3102
3103impl From<TransferFormat> for SessionFormat {
3104    fn from(value: TransferFormat) -> Self {
3105        match value {
3106            TransferFormat::ClaudeCode => Self::ClaudeCode,
3107            TransferFormat::Codex => Self::Codex,
3108            TransferFormat::OpenCode => Self::OpenCode,
3109            TransferFormat::Pi => Self::Pi,
3110            TransferFormat::Grok => Self::Grok,
3111            TransferFormat::Gemini => Self::Gemini,
3112            TransferFormat::Goose => Self::Goose,
3113            // a Hermes artifact is the Codex rollout Hermes imports
3114            TransferFormat::Hermes => Self::Codex,
3115        }
3116    }
3117}
3118
3119#[derive(Deserialize)]
3120struct ImportSessionParams {
3121    source_harness: TransferFormat,
3122    content: String,
3123}
3124
3125#[derive(Deserialize)]
3126struct ExportSessionParams {
3127    locator: SessionLocator,
3128    target_harness: TransferFormat,
3129}
3130
3131#[derive(Deserialize)]
3132struct ReduceSessionParams {
3133    locator: SessionLocator,
3134    target_harness: TransferFormat,
3135    #[serde(default = "default_keep_last")]
3136    keep_last: usize,
3137}
3138
3139fn default_keep_last() -> usize {
3140    6
3141}
3142
3143#[derive(Deserialize)]
3144struct BranchSessionParams {
3145    locator: SessionLocator,
3146    #[serde(default)]
3147    target_harness: Option<TransferFormat>,
3148}
3149
3150#[derive(Deserialize)]
3151struct HandoffSessionParams {
3152    locator: SessionLocator,
3153    target_harness: TransferFormat,
3154    #[serde(default)]
3155    cwd: Option<PathBuf>,
3156}
3157
3158#[derive(Debug, Clone, Copy, Default, Deserialize)]
3159#[serde(rename_all = "snake_case")]
3160enum ResumePolicy {
3161    #[default]
3162    Default,
3163    Yolo,
3164}
3165
3166#[derive(Deserialize)]
3167struct ResumeInstructionsParams {
3168    locator: SessionLocator,
3169    #[serde(default)]
3170    cwd: Option<PathBuf>,
3171    #[serde(default)]
3172    policy: ResumePolicy,
3173}
3174
3175/// `harness.v1.workflow.load` parameters: which harness's board, and its home.
3176#[derive(Deserialize)]
3177struct WorkflowLoadParams {
3178    from: crate::workflow_doors::WorkflowHarness,
3179    home: PathBuf,
3180}
3181
3182/// ONT-4 `harness.v1.orchestration.load` parameters. `flavor` says which layout the
3183/// folder is read as; our own is the default.
3184#[derive(Deserialize)]
3185struct OrchestrationLoadParams {
3186    root: PathBuf,
3187    #[serde(default)]
3188    flavor: crate::orchestration_doors::HomeFlavor,
3189}
3190
3191/// ONT-4 `harness.v1.orchestration.save` parameters. `vault` is merged into the
3192/// home's own secrets; a caller that sends none keeps what is on disk.
3193#[derive(Deserialize)]
3194struct OrchestrationSaveParams {
3195    root: PathBuf,
3196    orchestration: crate::orchestration::Orchestration,
3197    #[serde(default)]
3198    vault: BTreeMap<String, String>,
3199}
3200
3201/// ONT-4 `harness.v1.orchestration.compile` parameters.
3202#[derive(Deserialize)]
3203struct OrchestrationCompileParams {
3204    from: crate::orchestration_doors::OrchestrationHarness,
3205    home: PathBuf,
3206}
3207
3208/// ONT-4 `harness.v1.orchestration.decompile` parameters. `source` is the home the
3209/// orchestration was compiled from: it is re-compiled to recover the io bookkeeping
3210/// that byte reuse and the live-store refusal (UNI-18) are decided from.
3211#[derive(Deserialize)]
3212struct OrchestrationDecompileParams {
3213    to: crate::orchestration_doors::OrchestrationHarness,
3214    orchestration: crate::orchestration::Orchestration,
3215    source: PathBuf,
3216    #[serde(default)]
3217    source_flavor: crate::orchestration_doors::SourceFlavor,
3218    dest: PathBuf,
3219    #[serde(default)]
3220    vault: BTreeMap<String, String>,
3221}
3222
3223/// `harness.v1.orchestration.import` parameters: another harness's home, and the
3224/// folder of ours it becomes.
3225#[derive(Deserialize)]
3226struct OrchestrationImportParams {
3227    from: crate::orchestration_doors::OrchestrationHarness,
3228    home: PathBuf,
3229    into: PathBuf,
3230}
3231
3232/// `harness.v1.orchestration.export` parameters: a folder of ours, and the home of
3233/// another harness it becomes.
3234#[derive(Deserialize)]
3235struct OrchestrationExportParams {
3236    to: crate::orchestration_doors::OrchestrationHarness,
3237    root: PathBuf,
3238    dest: PathBuf,
3239}
3240
3241/// `harness.v1.jobs.get` parameters.
3242#[derive(Deserialize)]
3243struct JobsGetParams {
3244    harness: String,
3245    id: String,
3246    #[serde(default)]
3247    homes: crate::HarnessHomes,
3248}
3249
3250/// ORCH-18: run one mutating job verb through the harness's own CLI.
3251///
3252/// The refusal ladder is deliberate: a harness with no scheduled-job concept
3253/// at all answers with the SAME sentence `jobs.list` gives it, and a harness
3254/// that has jobs but publishes no client-callable verb (Claude Code, whose
3255/// jobs are created by the model inside a session) answers with its own
3256/// reason. Neither is ever a silent no-op.
3257fn mutate_job(
3258    verb: crate::jobs_control::JobVerb,
3259    params: Value,
3260) -> std::result::Result<Value, ServiceError> {
3261    let mutation = decode::<crate::jobs_control::JobMutation>(params)?;
3262    refuse_harness_without_jobs(&mutation.harness, &format!("jobs.{}", verb.as_str()))?;
3263    let outcome = crate::jobs_control::mutate(verb, &mutation).map_err(job_control_error)?;
3264    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3265}
3266
3267/// ORCH-22: run one mutating skills verb through the harness's own door.
3268///
3269/// The refusal ladder mirrors `jobs.*`: a harness with no skills root at all
3270/// answers with the same sentence `skills.list` gives it, and a harness whose
3271/// door does not publish this verb (OpenClaw has no `skills remove` at the
3272/// pin) answers with its own reason. Neither is ever a silent no-op.
3273fn mutate_skill(
3274    verb: crate::skills_control::SkillVerb,
3275    params: Value,
3276) -> std::result::Result<Value, ServiceError> {
3277    let mutation = decode::<crate::skills_control::SkillMutation>(params)?;
3278    if !crate::skills_control::supports_skill_control(&mutation.harness) {
3279        return Err(ServiceError::UnsupportedAction(format!(
3280            "`{}` has no skills root supercode reads; `skills.{}` is supported for: {}",
3281            mutation.harness,
3282            verb.as_str(),
3283            crate::skills_control::CONTROLLED_SKILL_HARNESSES.join(", ")
3284        )));
3285    }
3286    let outcome =
3287        crate::skills_control::mutate_skill(verb, &mutation).map_err(skill_control_error)?;
3288    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3289}
3290
3291/// The skills twin of [`job_control_error`], with the same mapping rule.
3292fn skill_control_error(error: crate::skills_control::SkillControlError) -> ServiceError {
3293    match error {
3294        crate::skills_control::SkillControlError::Unsupported(message) => {
3295            ServiceError::UnsupportedAction(message)
3296        }
3297        crate::skills_control::SkillControlError::Invalid(message) => {
3298            ServiceError::InvalidParams(message)
3299        }
3300        crate::skills_control::SkillControlError::Failed(message) => {
3301            ServiceError::Operation(message)
3302        }
3303    }
3304}
3305
3306/// ORCH-21: run one mutating profile verb through the harness's own CLI.
3307///
3308/// The refusal ladder mirrors `mutate_job`'s: a harness with no profile
3309/// concept at all answers with the SAME sentence `profiles.list` gives it, and
3310/// a harness that HAS profiles but publishes no client-callable verb (Codex's
3311/// file-authored `[profiles.<name>]` tables, supercode's compiled-in presets)
3312/// answers with its own reason. Neither is ever a silent no-op.
3313fn mutate_profile(
3314    verb: crate::profiles_control::ProfileVerb,
3315    params: Value,
3316) -> std::result::Result<Value, ServiceError> {
3317    let mutation = decode::<crate::profiles_control::ProfileMutation>(params)?;
3318    let outcome =
3319        crate::profiles_control::mutate(verb, &mutation).map_err(profile_control_error)?;
3320    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3321}
3322
3323/// The same mapping `job_control_error` applies, for the profile noun.
3324fn profile_control_error(error: crate::profiles_control::ProfileControlError) -> ServiceError {
3325    match error {
3326        crate::profiles_control::ProfileControlError::Unsupported(message) => {
3327            ServiceError::UnsupportedAction(message)
3328        }
3329        crate::profiles_control::ProfileControlError::Invalid(message) => {
3330            ServiceError::InvalidParams(message)
3331        }
3332        crate::profiles_control::ProfileControlError::Failed(message) => {
3333            ServiceError::Operation(message)
3334        }
3335    }
3336}
3337
3338/// Map a controlled-tier failure onto the service's error vocabulary. A verb
3339/// the harness lacks is `UnsupportedAction`; a harness verb that RAN and
3340/// failed carries its own stderr through as the operation error.
3341fn job_control_error(error: crate::jobs_control::JobControlError) -> ServiceError {
3342    match error {
3343        crate::jobs_control::JobControlError::Unsupported(message) => {
3344            ServiceError::UnsupportedAction(message)
3345        }
3346        crate::jobs_control::JobControlError::Invalid(message) => {
3347            ServiceError::InvalidParams(message)
3348        }
3349        crate::jobs_control::JobControlError::Failed(message) => ServiceError::Operation(message),
3350    }
3351}
3352
3353/// Map an ORCH-19 controlled-tier failure onto the service's error
3354/// vocabulary. A verb the harness has no door for is `UnsupportedAction`; a
3355/// door that RAN and failed carries the harness's own stderr / HTTP body
3356/// through as the operation error.
3357fn session_control_error(error: crate::SessionControlError) -> ServiceError {
3358    match error {
3359        crate::SessionControlError::Unsupported(message) => {
3360            ServiceError::UnsupportedAction(message)
3361        }
3362        crate::SessionControlError::Invalid(message) => ServiceError::InvalidParams(message),
3363        crate::SessionControlError::Failed(message) => ServiceError::Operation(message),
3364    }
3365}
3366
3367/// A harness without a scheduled-job concept refuses the verb rather than
3368/// answering with an empty list — an absent capability and an empty inventory
3369/// are different answers (the same rule `runtimes.capabilities` applies to
3370/// `steer`).
3371fn refuse_harness_without_jobs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3372    if crate::jobs::supports_jobs(harness) {
3373        return Ok(());
3374    }
3375    Err(ServiceError::UnsupportedAction(format!(
3376        "`{harness}` has no scheduled jobs; `{verb}` is supported for: {}",
3377        crate::jobs::JOB_HARNESSES.join(", ")
3378    )))
3379}
3380
3381/// `harness.v1.runs.get` parameters.
3382#[derive(Deserialize)]
3383struct RunsGetParams {
3384    harness: String,
3385    id: String,
3386    #[serde(default)]
3387    homes: crate::HarnessHomes,
3388}
3389
3390/// A harness with no run store refuses the verb rather than answering with an
3391/// empty history — the same rule `jobs.list` applies. Claude Code lands here
3392/// on purpose: its cron fires are ordinary turns inside the session that
3393/// created the job, so there is no fire record to list.
3394fn refuse_harness_without_runs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3395    if crate::runs::supports_runs(harness) {
3396        return Ok(());
3397    }
3398    Err(ServiceError::UnsupportedAction(format!(
3399        "`{harness}` keeps no run store; `{verb}` is supported for: {}",
3400        crate::runs::RUN_HARNESSES.join(", ")
3401    )))
3402}
3403
3404#[derive(Serialize)]
3405struct SessionArtifact {
3406    source_harness: HarnessId,
3407    target_harness: &'static str,
3408    session_id: Option<String>,
3409    content: String,
3410    suggested_filename: String,
3411    files: Vec<SessionArtifactFile>,
3412    fidelity: Fidelity,
3413    residue: Vec<String>,
3414}
3415
3416#[derive(Serialize)]
3417struct SessionArtifactFile {
3418    path: String,
3419    content: String,
3420    role: ArtifactFileRole,
3421}
3422
3423#[derive(Serialize)]
3424#[serde(rename_all = "snake_case")]
3425enum ArtifactFileRole {
3426    Primary,
3427    Subagent,
3428    Bundle,
3429    SourceRecovery,
3430}
3431
3432#[derive(Serialize)]
3433struct StructuredLaunch {
3434    cwd: PathBuf,
3435    program: String,
3436    arguments: Vec<String>,
3437    env: BTreeMap<String, String>,
3438}
3439
3440struct HandoffInstructions {
3441    launch: StructuredLaunch,
3442    materialize: Option<StructuredLaunch>,
3443    requires_materialization: bool,
3444    note: String,
3445}
3446
3447#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
3448#[serde(rename_all = "snake_case")]
3449enum HarnessProbeLevel {
3450    #[default]
3451    Passive,
3452    Handshake,
3453}
3454
3455#[derive(Default, Deserialize)]
3456#[serde(default)]
3457struct HarnessInventoryParams {
3458    harness: Option<HarnessId>,
3459    harnesses: Vec<HarnessId>,
3460    workspace: Option<PathBuf>,
3461    probe: HarnessProbeLevel,
3462    include_sessions: bool,
3463    /// Omit subprocess-based `--version` calls when a latency-sensitive UI only needs readiness.
3464    skip_versions: bool,
3465}
3466
3467#[derive(Deserialize)]
3468struct HarnessAuthenticationParams {
3469    harness: HarnessId,
3470}
3471
3472#[derive(Deserialize)]
3473struct BeginHarnessAuthenticationParams {
3474    harness: HarnessId,
3475    #[serde(default = "local_browser_authentication_environment")]
3476    environment: crate::HarnessAuthenticationEnvironment,
3477    #[serde(default)]
3478    method: Option<crate::HarnessAuthenticationMethodId>,
3479    #[serde(default)]
3480    cwd: Option<PathBuf>,
3481}
3482
3483fn local_browser_authentication_environment() -> crate::HarnessAuthenticationEnvironment {
3484    crate::HarnessAuthenticationEnvironment::LocalBrowser
3485}
3486
3487#[derive(Serialize)]
3488struct HarnessInventoryReport {
3489    probe: HarnessProbeLevel,
3490    workspace: Option<PathBuf>,
3491    harnesses: Vec<LocalHarness>,
3492}
3493
3494#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3495#[serde(rename_all = "snake_case")]
3496enum HarnessAuthState {
3497    Ready,
3498    Configured,
3499    Required,
3500    Unknown,
3501}
3502
3503#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3504#[serde(rename_all = "snake_case")]
3505enum HarnessRuntimeState {
3506    Ready,
3507    Degraded,
3508    Unavailable,
3509}
3510
3511#[derive(Serialize)]
3512struct HarnessSessionCounts {
3513    global: Option<usize>,
3514    workspace: Option<usize>,
3515}
3516
3517/// Receipt-backed evidence that a harness has a RUNNING instance right now,
3518/// distinct from being merely installed (UNI-7). Detection is passive and
3519/// default-on: a gateway liveness connect for daemon harnesses, a fresh
3520/// SQLite WAL stamp for store-writer harnesses (precedent: the opencode
3521/// follower's -wal/-shm freshness). Control stays behind per-connection
3522/// grants — this reports observations only.
3523/// ORCH-17: the gateway-health noun on an inventory row. Derived from the
3524/// UNI-7 running-instance probe (Hermes: `state.db-wal` freshness; OpenClaw:
3525/// a TCP connect to the gateway endpoint resolved from its OWN config) plus
3526/// the executable version — never by starting anything.
3527#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3528#[serde(rename_all = "snake_case")]
3529pub enum GatewayState {
3530    Up,
3531    Down,
3532    Unknown,
3533}
3534
3535/// ORCH-17: `gateway` on a `harness.v1.harnesses.list` row.
3536#[derive(Debug, Clone, Serialize)]
3537pub struct GatewayHealth {
3538    pub state: GatewayState,
3539    /// The endpoint supercode would connect to (OpenClaw: the gateway
3540    /// WebSocket resolved from `openclaw.json`; core harnesses: their
3541    /// declared connect address when one exists). `None` when the harness
3542    /// has no single endpoint (Hermes multiplexes platforms).
3543    #[serde(skip_serializing_if = "Option::is_none")]
3544    pub endpoint: Option<String>,
3545    #[serde(skip_serializing_if = "Option::is_none")]
3546    pub version: Option<String>,
3547    /// What the verdict rests on, or why it is `unknown`.
3548    pub evidence: String,
3549    pub checked_at_ms: u64,
3550}
3551
3552/// OpenClaw's gateway WebSocket endpoint, resolved from its own config the
3553/// way the registry's connect descriptor prescribes (`gateway.url`, else
3554/// `gateway.port`, else the documented default).
3555fn openclaw_gateway_endpoint(home: &Path) -> String {
3556    let config_path = home.join(".openclaw/openclaw.json");
3557    let gateway = std::fs::read_to_string(&config_path)
3558        .ok()
3559        .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
3560        .and_then(|config| config.get("gateway").cloned());
3561    if let Some(url) = gateway
3562        .as_ref()
3563        .and_then(|gateway| gateway.get("url"))
3564        .and_then(serde_json::Value::as_str)
3565    {
3566        return url.to_string();
3567    }
3568    let port = gateway
3569        .as_ref()
3570        .and_then(|gateway| gateway.get("port"))
3571        .and_then(serde_json::Value::as_u64)
3572        .unwrap_or(18789);
3573    format!("ws://127.0.0.1:{port}")
3574}
3575
3576/// Ask Hermes itself (`hermes gateway status`, read-only, ~1 s) whether its
3577/// gateway is up. The command is per-host launchd/systemd text without a JSON
3578/// form at 0.19–0.21; the verdict is read from the lines it prints:
3579/// "supervised by launchd (PID …)" / "is running" → up, "not running" /
3580/// "not installed" → down, anything else → no verdict. `SUPERCODE_HERMES_BIN`
3581/// overrides the executable so a fake can stand in under test.
3582fn hermes_gateway_status() -> Option<(GatewayState, String)> {
3583    let program = crate::harness_command::harness_program(HarnessId::HERMES).ok()?;
3584    let output = std::process::Command::new(&program)
3585        .args(["gateway", "status"])
3586        .stdin(std::process::Stdio::null())
3587        .output()
3588        .ok()?;
3589    let text = format!(
3590        "{}{}",
3591        String::from_utf8_lossy(&output.stdout),
3592        String::from_utf8_lossy(&output.stderr)
3593    );
3594    let verdict = text.lines().find_map(|line| {
3595        let l = line.trim();
3596        if l.contains("supervised by launchd (PID")
3597            || l.contains("supervised by systemd (PID")
3598            || l.contains("Gateway is running")
3599            || l.contains("process is running")
3600        {
3601            Some((GatewayState::Up, format!("`hermes gateway status`: {l}")))
3602        } else if l.contains("not running") || l.contains("not installed") {
3603            Some((GatewayState::Down, format!("`hermes gateway status`: {l}")))
3604        } else {
3605            None
3606        }
3607    });
3608    verdict
3609}
3610
3611fn gateway_health(
3612    id: &str,
3613    installed: bool,
3614    running: Option<&RunningInstance>,
3615    version: Option<&str>,
3616) -> GatewayHealth {
3617    let checked_at_ms = now_epoch_ms();
3618    let home = std::env::var_os("HOME").map(PathBuf::from);
3619    match id {
3620        HarnessId::HERMES | HarnessId::OPENCLAW => {
3621            let endpoint = (id == HarnessId::OPENCLAW)
3622                .then(|| home.as_deref().map(openclaw_gateway_endpoint))
3623                .flatten();
3624            let (state, evidence) = match running {
3625                Some(instance) => (GatewayState::Up, instance.evidence.clone()),
3626                None if !installed => (
3627                    GatewayState::Unknown,
3628                    format!("`{id}` is not installed; no gateway to probe"),
3629                ),
3630                None if id == HarnessId::HERMES => match hermes_gateway_status() {
3631                    // The harness's own door outranks the WAL heuristic: an idle
3632                    // gateway writes nothing for minutes yet is up.
3633                    Some((state, evidence)) => (state, evidence),
3634                    None => (
3635                        GatewayState::Down,
3636                        "no fresh state.db-wal activity under ~/.hermes and `hermes gateway status` gave no verdict".to_string(),
3637                    ),
3638                },
3639                None => (
3640                    GatewayState::Down,
3641                    format!(
3642                        "no TCP listener at {}",
3643                        endpoint.as_deref().unwrap_or("the gateway endpoint")
3644                    ),
3645                ),
3646            };
3647            GatewayHealth {
3648                state,
3649                endpoint,
3650                version: version.map(str::to_string),
3651                evidence,
3652                checked_at_ms,
3653            }
3654        }
3655        // ORC-7: the orchestrator's gateway IS its daemon, and the daemon's
3656        // own lease file is the record of it. A lease naming a live pid is
3657        // up; a lease whose process is gone is down and says so as a STALE
3658        // lease, never as "no lease"; no lease at all is down. Nothing is
3659        // started, and no port is guessed — the daemon multiplexes adapters
3660        // the way Hermes does, so it has no single endpoint either.
3661        HarnessId::ORCHESTRATOR => {
3662            let root = crate::HarnessHomes::default().orchestrator;
3663            let (state, evidence) = match crate::orchestrator::read_lease(&root) {
3664                Some(lease) if crate::orchestrator::pid_is_live(lease.pid) => (
3665                    GatewayState::Up,
3666                    format!(
3667                        "`{}` names pid {} (started {}), which is live",
3668                        crate::orchestrator::lock_path(&root).display(),
3669                        lease.pid,
3670                        lease.started_at
3671                    ),
3672                ),
3673                Some(lease) => (
3674                    GatewayState::Down,
3675                    format!(
3676                        "stale lease `{}`: pid {} is gone",
3677                        crate::orchestrator::lock_path(&root).display(),
3678                        lease.pid
3679                    ),
3680                ),
3681                None => (
3682                    GatewayState::Down,
3683                    format!(
3684                        "no lease at `{}`; `supercode orchestrator start` writes one",
3685                        crate::orchestrator::lock_path(&root).display()
3686                    ),
3687                ),
3688            };
3689            GatewayHealth {
3690                state,
3691                endpoint: None,
3692                version: version.map(str::to_string),
3693                evidence,
3694                checked_at_ms,
3695            }
3696        }
3697        _ => GatewayHealth {
3698            state: GatewayState::Unknown,
3699            endpoint: None,
3700            version: version.map(str::to_string),
3701            evidence: format!("`{id}` runs per session, not as a gateway"),
3702            checked_at_ms,
3703        },
3704    }
3705}
3706
3707#[derive(Debug, Clone, Serialize)]
3708struct RunningInstance {
3709    /// How the instance was detected.
3710    method: RunningInstanceMethod,
3711    /// The evidence the verdict rests on (endpoint reached / WAL path+age).
3712    evidence: String,
3713    /// Epoch-ms instant the probe executed.
3714    checked_at_ms: u64,
3715}
3716
3717#[derive(Debug, Clone, Copy, Serialize)]
3718#[serde(rename_all = "snake_case")]
3719enum RunningInstanceMethod {
3720    /// A TCP connect to the harness's own configured gateway endpoint
3721    /// succeeded.
3722    GatewayConnect,
3723    /// The harness's session store has an active SQLite WAL (a live writer
3724    /// holds the store open and stamped it recently).
3725    StoreWalActivity,
3726}
3727
3728fn now_epoch_ms() -> u64 {
3729    std::time::SystemTime::now()
3730        .duration_since(std::time::UNIX_EPOCH)
3731        .map(|elapsed| elapsed.as_millis() as u64)
3732        .unwrap_or(0)
3733}
3734
3735/// OpenClaw: the gateway endpoint comes from the harness's OWN config
3736/// (`<home>/.openclaw/openclaw.json` — `gateway.url` or `gateway.port`,
3737/// default port 18789); a successful TCP connect is the running signal.
3738fn probe_openclaw_running(home: &Path) -> Option<RunningInstance> {
3739    let config_path = home.join(".openclaw/openclaw.json");
3740    let text = std::fs::read_to_string(&config_path).ok();
3741    let gateway = text
3742        .as_deref()
3743        .and_then(|raw| serde_json::from_str::<serde_json::Value>(raw).ok())
3744        .and_then(|config| config.get("gateway").cloned());
3745    let address = gateway
3746        .as_ref()
3747        .and_then(|gateway| gateway.get("url"))
3748        .and_then(serde_json::Value::as_str)
3749        .and_then(|url| {
3750            url.split("://").nth(1).map(|rest| {
3751                rest.trim_end_matches('/')
3752                    .split('/')
3753                    .next()
3754                    .unwrap_or(rest)
3755                    .to_string()
3756            })
3757        })
3758        .unwrap_or_else(|| {
3759            let port = gateway
3760                .as_ref()
3761                .and_then(|gateway| gateway.get("port"))
3762                .and_then(serde_json::Value::as_u64)
3763                .unwrap_or(18789);
3764            format!("127.0.0.1:{port}")
3765        });
3766    let reachable = std::net::TcpStream::connect_timeout(
3767        &address.parse().ok()?,
3768        std::time::Duration::from_millis(400),
3769    )
3770    .is_ok();
3771    reachable.then(|| RunningInstance {
3772        method: RunningInstanceMethod::GatewayConnect,
3773        evidence: format!(
3774            "gateway endpoint {address} accepted a TCP connect (from {})",
3775            config_path.display()
3776        ),
3777        checked_at_ms: now_epoch_ms(),
3778    })
3779}
3780
3781/// Hermes: `<home>/.hermes/state.db-wal` freshly modified means a live writer
3782/// holds the store open (SQLite WAL exists only while a connection is open;
3783/// a recent stamp distinguishes an active instance from a stale crash
3784/// leftover).
3785fn probe_hermes_running(home: &Path, max_wal_age_ms: u64) -> Option<RunningInstance> {
3786    let wal = home.join(".hermes/state.db-wal");
3787    let modified = std::fs::metadata(&wal).ok()?.modified().ok()?;
3788    let age_ms = std::time::SystemTime::now()
3789        .duration_since(modified)
3790        .map(|age| age.as_millis() as u64)
3791        .unwrap_or(u64::MAX);
3792    (age_ms <= max_wal_age_ms).then(|| RunningInstance {
3793        method: RunningInstanceMethod::StoreWalActivity,
3794        evidence: format!(
3795            "{} stamped {age_ms}ms ago (threshold {max_wal_age_ms}ms)",
3796            wal.display()
3797        ),
3798        checked_at_ms: now_epoch_ms(),
3799    })
3800}
3801
3802/// Default-on running-instance detection for the harnesses that have one.
3803fn probe_running_instance(id: &str) -> Option<RunningInstance> {
3804    let home = std::env::var_os("HOME").map(PathBuf::from)?;
3805    match id {
3806        HarnessId::OPENCLAW => probe_openclaw_running(&home),
3807        HarnessId::HERMES => probe_hermes_running(&home, 300_000),
3808        _ => None,
3809    }
3810}
3811
3812#[derive(Serialize)]
3813struct LocalHarness {
3814    id: HarnessId,
3815    display_name: String,
3816    supported: bool,
3817    installed: bool,
3818    executable: Option<String>,
3819    version: Option<String>,
3820    auth: HarnessAuthState,
3821    runtime: HarnessRuntimeState,
3822    protocol: String,
3823    capabilities: crate::RuntimeCapabilities,
3824    effective_capabilities: crate::RuntimeCapabilities,
3825    sessions: HarnessSessionCounts,
3826    /// Receipt-backed running-instance detection (None = not detected or the
3827    /// harness has no running-instance concept). Distinct from `installed`.
3828    #[serde(skip_serializing_if = "Option::is_none")]
3829    running: Option<RunningInstance>,
3830    /// ORCH-17: gateway health derived from `running` + the harness's own config.
3831    gateway: GatewayHealth,
3832    reason: Option<String>,
3833    repair: Option<String>,
3834}
3835
3836#[derive(Clone, Deserialize)]
3837struct RuntimeBackendParams {
3838    harness: HarnessId,
3839    #[serde(default)]
3840    protocol: Option<String>,
3841    #[serde(default)]
3842    launch: Option<RuntimeLaunch>,
3843    #[serde(default)]
3844    base_url: Option<String>,
3845    #[serde(default)]
3846    policy: RuntimePolicy,
3847}
3848
3849#[derive(Debug, Clone, Copy, Default, Deserialize)]
3850#[serde(rename_all = "snake_case")]
3851enum RuntimePolicy {
3852    #[default]
3853    Default,
3854    Yolo,
3855}
3856
3857#[derive(Deserialize)]
3858struct RuntimeStartParams {
3859    #[serde(flatten)]
3860    backend: RuntimeBackendParams,
3861    cwd: PathBuf,
3862    /// MCP servers to mount into the new session through the harness's own
3863    /// start door (ORC-6). Backends without such a door ignore them.
3864    #[serde(default)]
3865    mcp_servers: Vec<crate::McpServerLaunch>,
3866}
3867
3868#[derive(Deserialize)]
3869struct RuntimeAttachParams {
3870    #[serde(flatten)]
3871    backend: RuntimeBackendParams,
3872    runtime_id: String,
3873    #[serde(default)]
3874    cwd: Option<PathBuf>,
3875}
3876
3877#[derive(Deserialize)]
3878struct RuntimeConnectionParams {
3879    connection: String,
3880}
3881
3882#[derive(Deserialize)]
3883struct RuntimeInputParams {
3884    connection: String,
3885    text: String,
3886    #[serde(default)]
3887    image_urls: Vec<String>,
3888}
3889
3890const MAX_RUNTIME_IMAGES: usize = 4;
3891const MAX_RUNTIME_IMAGE_URL_BYTES: usize = 12 * 1024 * 1024;
3892const MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL: usize = 32 * 1024 * 1024;
3893
3894fn validate_runtime_image_urls(image_urls: Vec<String>) -> Result<Vec<String>, ServiceError> {
3895    if image_urls.len() > MAX_RUNTIME_IMAGES {
3896        return Err(ServiceError::InvalidParams(format!(
3897            "a runtime prompt accepts at most {MAX_RUNTIME_IMAGES} images"
3898        )));
3899    }
3900    let mut total = 0usize;
3901    for url in &image_urls {
3902        if !(url.starts_with("data:image/")
3903            || url.starts_with("https://")
3904            || url.starts_with("http://"))
3905        {
3906            return Err(ServiceError::InvalidParams(
3907                "runtime images must be image data URLs or HTTP(S) URLs".into(),
3908            ));
3909        }
3910        if url.len() > MAX_RUNTIME_IMAGE_URL_BYTES {
3911            return Err(ServiceError::InvalidParams(format!(
3912                "one runtime image exceeds the {MAX_RUNTIME_IMAGE_URL_BYTES}-byte encoded limit"
3913            )));
3914        }
3915        total = total.saturating_add(url.len());
3916    }
3917    if total > MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL {
3918        return Err(ServiceError::InvalidParams(format!(
3919            "runtime images exceed the {MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL}-byte encoded total limit"
3920        )));
3921    }
3922    Ok(image_urls)
3923}
3924
3925#[derive(Deserialize)]
3926struct RuntimeRespondParams {
3927    connection: String,
3928    request_id: Value,
3929    response: Value,
3930}
3931
3932fn default_reduction_store_root() -> PathBuf {
3933    if let Some(root) = std::env::var_os("SUPERCODE_HOME") {
3934        return PathBuf::from(root).join("sessions");
3935    }
3936    if let Some(home) = std::env::var_os("HOME") {
3937        return PathBuf::from(home).join(".supercode").join("sessions");
3938    }
3939    PathBuf::from(".supercode").join("sessions")
3940}
3941
3942fn messages_jsonl(messages: &[crate::ChatMessage]) -> std::result::Result<String, ServiceError> {
3943    let mut output = String::new();
3944    for message in messages {
3945        output.push_str(
3946            &serde_json::to_string(message)
3947                .map_err(|error| ServiceError::Operation(error.to_string()))?,
3948        );
3949        output.push('\n');
3950    }
3951    Ok(output)
3952}
3953
3954fn parse_messages_jsonl(
3955    content: &str,
3956) -> std::result::Result<Vec<crate::ChatMessage>, ServiceError> {
3957    content
3958        .lines()
3959        .enumerate()
3960        .filter(|(_, line)| !line.trim().is_empty())
3961        .map(|(index, line)| {
3962            serde_json::from_str::<crate::ChatMessage>(line).map_err(|error| {
3963                ServiceError::Operation(format!(
3964                    "reduced transcript line {} is invalid: {error}",
3965                    index + 1
3966                ))
3967            })
3968        })
3969        .collect()
3970}
3971
3972fn reduced_bootstrap_prompt(
3973    source: &SessionLocator,
3974    target: TransferFormat,
3975    view_jsonl: &str,
3976    sidecar_path: &Path,
3977    reduction_log_path: &Path,
3978) -> String {
3979    format!(
3980        "Continue the work from this losslessly reduced {source_harness} session in {target_harness}.\n\
3981         \n\
3982         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 Supercode 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\
3983         \n\
3984         <supercode-reduced-session source-session=\"{source_id}\">\n\
3985         {view_jsonl}\
3986         </supercode-reduced-session>\n\
3987         \n\
3988         Resume from the latest unresolved user request and preserve the source session's decisions and constraints.",
3989        source_harness = source.harness.as_str(),
3990        target_harness = target.id(),
3991        sidecar = sidecar_path.display(),
3992        log = reduction_log_path.display(),
3993        source_id = source.session_id,
3994    )
3995}
3996
3997fn session_artifact(
3998    locator: &SessionLocator,
3999    session: &Session,
4000    target: TransferFormat,
4001) -> std::result::Result<SessionArtifact, ServiceError> {
4002    session_artifact_with_id(locator, session, target, None)
4003}
4004
4005fn session_artifact_with_id(
4006    locator: &SessionLocator,
4007    session: &Session,
4008    target: TransferFormat,
4009    target_session_id: Option<&str>,
4010) -> std::result::Result<SessionArtifact, ServiceError> {
4011    let format: SessionFormat = target.into();
4012    let diagonal = format.source() == session.meta.source;
4013    let has_appended_turns = session
4014        .imported_message_count
4015        .is_some_and(|imported| imported < session.messages.len());
4016    let content = if let Some(id) = target_session_id {
4017        if diagonal && format != SessionFormat::OpenCode {
4018            session
4019                .to_jsonl_spliced(format, Some(id))
4020                .map_err(operation)?
4021        } else {
4022            let mut rewritten = session.clone();
4023            rewritten.meta.session_id = Some(id.to_string());
4024            rewritten.to_jsonl(format).map_err(operation)?
4025        }
4026    } else if diagonal && session.raw_is_verbatim && !has_appended_turns {
4027        session.raw_verbatim()
4028    } else if diagonal {
4029        session.to_jsonl_spliced(format, None).map_err(operation)?
4030    } else {
4031        session.to_jsonl(format).map_err(operation)?
4032    };
4033    let stem = sanitize_filename(
4034        target_session_id
4035            .or(session.meta.session_id.as_deref())
4036            .unwrap_or(&locator.session_id),
4037    );
4038    let suggested_filename = if diagonal && target == TransferFormat::Grok {
4039        "chat_history.jsonl".to_string()
4040    } else if target == TransferFormat::Goose {
4041        format!("{stem}.goose.json")
4042    } else {
4043        format!("{stem}.{}.jsonl", target.id())
4044    };
4045    let mut files = vec![SessionArtifactFile {
4046        path: suggested_filename.clone(),
4047        content: content.clone(),
4048        role: ArtifactFileRole::Primary,
4049    }];
4050    if target == TransferFormat::ClaudeCode {
4051        let bundle_stem = Path::new(&suggested_filename)
4052            .file_stem()
4053            .and_then(|stem| stem.to_str())
4054            .unwrap_or(&stem);
4055        let mut child_paths = BTreeSet::new();
4056        for (index, subagent) in session.subagents.iter().enumerate() {
4057            let agent_id = subagent
4058                .meta
4059                .agent_id
4060                .as_deref()
4061                .map(|id| id.strip_prefix("agent-").unwrap_or(id))
4062                .map(sanitize_filename)
4063                .filter(|id| !id.is_empty())
4064                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4065            let child_has_appended_turns = subagent
4066                .imported_message_count
4067                .is_some_and(|imported| imported < subagent.messages.len());
4068            let child_content = if target_session_id.is_none()
4069                && subagent.meta.source == SessionSource::ClaudeCode
4070                && subagent.raw_is_verbatim
4071                && !child_has_appended_turns
4072            {
4073                subagent.raw_verbatim()
4074            } else if subagent.meta.source == SessionSource::ClaudeCode {
4075                subagent
4076                    .to_jsonl_spliced(SessionFormat::ClaudeCode, target_session_id)
4077                    .map_err(operation)?
4078            } else {
4079                let mut child = subagent.clone();
4080                if let Some(id) = target_session_id {
4081                    child.meta.session_id = Some(id.to_string());
4082                }
4083                child
4084                    .to_jsonl(SessionFormat::ClaudeCode)
4085                    .map_err(operation)?
4086            };
4087            let path = format!("{bundle_stem}/subagents/agent-{agent_id}.jsonl");
4088            if !child_paths.insert(path.clone()) {
4089                return Err(ServiceError::Operation(format!(
4090                    "Claude subagent ids collide at artifact path `{path}`"
4091                )));
4092            }
4093            files.push(SessionArtifactFile {
4094                path,
4095                content: child_content,
4096                role: ArtifactFileRole::Subagent,
4097            });
4098        }
4099    }
4100    if diagonal && target == TransferFormat::Grok {
4101        append_grok_bundle_files(locator, "", ArtifactFileRole::Bundle, &mut files)?;
4102    }
4103    if !diagonal || !session.raw_is_verbatim {
4104        files.push(SessionArtifactFile {
4105            path: "recovery/source.supercode.jsonl".into(),
4106            content: session.to_native_jsonl(),
4107            role: ArtifactFileRole::SourceRecovery,
4108        });
4109        for (index, subagent) in session.subagents.iter().enumerate() {
4110            let id = subagent
4111                .meta
4112                .agent_id
4113                .as_deref()
4114                .map(sanitize_filename)
4115                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4116            files.push(SessionArtifactFile {
4117                path: format!("recovery/subagents/{id}.supercode.jsonl"),
4118                content: subagent.to_native_jsonl(),
4119                role: ArtifactFileRole::SourceRecovery,
4120            });
4121        }
4122    }
4123    if !diagonal && session.meta.source == SessionSource::Grok {
4124        append_grok_bundle_files(
4125            locator,
4126            "recovery/grok/",
4127            ArtifactFileRole::SourceRecovery,
4128            &mut files,
4129        )?;
4130    }
4131    let (fidelity, residue) = if diagonal
4132        && target_session_id.is_none()
4133        && session.raw_is_verbatim
4134        && !has_appended_turns
4135    {
4136        (Fidelity::ByteLossless, Vec::new())
4137    } else if diagonal && !(target_session_id.is_some() && target == TransferFormat::OpenCode) {
4138        (
4139            Fidelity::ValueLossless,
4140            vec![if target_session_id.is_some() {
4141                "target identity was rewritten, so the artifact intentionally differs from source bytes".into()
4142            } else {
4143                "source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured".into()
4144            }],
4145        )
4146    } else {
4147        (
4148            Fidelity::Semantic,
4149            vec!["target schema has no portable slot for every source-native record and metadata field".into()],
4150        )
4151    };
4152    Ok(SessionArtifact {
4153        source_harness: locator.harness.clone(),
4154        target_harness: target.id(),
4155        session_id: target_session_id
4156            .map(str::to_string)
4157            .or_else(|| session.meta.session_id.clone()),
4158        content,
4159        suggested_filename,
4160        files,
4161        fidelity,
4162        residue,
4163    })
4164}
4165
4166fn append_grok_bundle_files(
4167    locator: &SessionLocator,
4168    prefix: &str,
4169    role: ArtifactFileRole,
4170    files: &mut Vec<SessionArtifactFile>,
4171) -> std::result::Result<(), ServiceError> {
4172    let primary = locator.storage.path();
4173    if primary.file_name().and_then(|name| name.to_str()) != Some("chat_history.jsonl") {
4174        return Err(ServiceError::Operation(format!(
4175            "Grok bundle locator must name chat_history.jsonl, got {}",
4176            primary.display()
4177        )));
4178    }
4179    let parent = primary.parent().ok_or_else(|| {
4180        ServiceError::Operation("Grok chat_history.jsonl has no session directory".into())
4181    })?;
4182    for name in ["summary.json", "updates.jsonl"] {
4183        let path = parent.join(name);
4184        let metadata = match std::fs::symlink_metadata(&path) {
4185            Ok(metadata) => metadata,
4186            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
4187            Err(error) => return Err(ServiceError::Operation(error.to_string())),
4188        };
4189        if metadata.file_type().is_symlink() || !metadata.is_file() {
4190            return Err(ServiceError::Operation(format!(
4191                "refusing non-regular Grok bundle member {}",
4192                path.display()
4193            )));
4194        }
4195        let content = std::fs::read_to_string(&path).map_err(|error| {
4196            ServiceError::Operation(format!(
4197                "Grok bundle member {} is not representable as UTF-8: {error}",
4198                path.display()
4199            ))
4200        })?;
4201        files.push(SessionArtifactFile {
4202            path: format!("{prefix}{name}"),
4203            content,
4204            role: match role {
4205                ArtifactFileRole::Bundle => ArtifactFileRole::Bundle,
4206                _ => ArtifactFileRole::SourceRecovery,
4207            },
4208        });
4209    }
4210    Ok(())
4211}
4212
4213fn handoff_artifact(
4214    locator: &SessionLocator,
4215    session: &Session,
4216    target: TransferFormat,
4217    cwd: &Path,
4218) -> std::result::Result<SessionArtifact, ServiceError> {
4219    if target != TransferFormat::Grok {
4220        let target_session_id = target_session_id(target);
4221        return session_artifact_with_id(locator, session, target, Some(&target_session_id));
4222    }
4223
4224    // Stock Grok's importer accepts Claude/Codex transcripts and materializes its own
4225    // multi-file session bundle. A synthesized Grok chat_history.jsonl alone is not a
4226    // resumable handoff because updates.jsonl is the authoritative restore log.
4227    let mut importable = session.clone();
4228    // The Claude importer validates sessionId as a UUID. Source harness identities
4229    // are not portable (OpenCode, for example, uses `ses_...`), and a handoff must
4230    // not overwrite an existing target session when the source already uses UUIDs.
4231    // Mint a distinct target identity and still bind the importer-returned ID at
4232    // launch time because the importer remains the authority on materialization.
4233    importable.meta.session_id = Some(target_session_id(TransferFormat::ClaudeCode));
4234    importable.meta.cwd = Some(if cwd.is_absolute() {
4235        cwd.to_path_buf()
4236    } else {
4237        std::env::current_dir()
4238            .map_err(|error| ServiceError::Operation(error.to_string()))?
4239            .join(cwd)
4240    });
4241    let content = importable
4242        .to_jsonl(SessionFormat::ClaudeCode)
4243        .map_err(operation)?;
4244    let stem = sanitize_filename(
4245        importable
4246            .meta
4247            .session_id
4248            .as_deref()
4249            .unwrap_or(&locator.session_id),
4250    );
4251    let suggested_filename = format!("{stem}.grok-import.claude-code.jsonl");
4252    Ok(SessionArtifact {
4253        source_harness: locator.harness.clone(),
4254        // This names the artifact's actual wire format. The requested handoff target
4255        // remains Grok; its official importer is the materialization boundary.
4256        target_harness: TransferFormat::ClaudeCode.id(),
4257        session_id: importable.meta.session_id.clone(),
4258        content: content.clone(),
4259        suggested_filename: suggested_filename.clone(),
4260        files: vec![SessionArtifactFile {
4261            path: suggested_filename,
4262            content,
4263            role: ArtifactFileRole::Primary,
4264        }],
4265        fidelity: Fidelity::Semantic,
4266        residue: vec!["Grok's stock importer accepts a Claude Code transcript, not a complete Grok updates/session bundle".into()],
4267    })
4268}
4269
4270fn target_session_id(target: TransferFormat) -> String {
4271    let uuid = generated_session_id();
4272    match target {
4273        TransferFormat::OpenCode => format!("ses_{}", uuid.replace('-', "")),
4274        TransferFormat::ClaudeCode
4275        | TransferFormat::Codex
4276        | TransferFormat::Pi
4277        | TransferFormat::Grok
4278        | TransferFormat::Gemini
4279        | TransferFormat::Goose
4280        | TransferFormat::Hermes => uuid,
4281    }
4282}
4283
4284fn sanitize_filename(value: &str) -> String {
4285    let value = value
4286        .chars()
4287        .map(|character| {
4288            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
4289                character
4290            } else {
4291                '-'
4292            }
4293        })
4294        .collect::<String>();
4295    let value = value.trim_matches('-');
4296    if value.is_empty() {
4297        "session".into()
4298    } else {
4299        value.chars().take(100).collect()
4300    }
4301}
4302
4303fn handoff_instructions(
4304    target: TransferFormat,
4305    session_id: &str,
4306    cwd: &Path,
4307) -> HandoffInstructions {
4308    let launch = |program: &str, arguments: Vec<String>| StructuredLaunch {
4309        cwd: cwd.to_path_buf(),
4310        program: program.into(),
4311        arguments,
4312        env: BTreeMap::new(),
4313    };
4314    match target {
4315        TransferFormat::ClaudeCode => HandoffInstructions {
4316            launch: launch("claude", vec!["--resume".into(), session_id.into()]),
4317            materialize: None,
4318            requires_materialization: true,
4319            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(),
4320        },
4321        TransferFormat::Hermes => HandoffInstructions {
4322            launch: launch("hermes", vec!["--resume".into(), session_id.into()]),
4323            materialize: None,
4324            requires_materialization: true,
4325            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(),
4326        },
4327        TransferFormat::Codex => HandoffInstructions {
4328            launch: launch("codex", vec!["resume".into(), session_id.into()]),
4329            materialize: None,
4330            requires_materialization: true,
4331            note: "Write the artifact into Codex's native rollout store before running the resume launch; Codex has no general transcript-import command.".into(),
4332        },
4333        TransferFormat::OpenCode => HandoffInstructions {
4334            launch: launch("opencode", vec!["--session".into(), session_id.into()]),
4335            materialize: Some(launch(
4336                "opencode",
4337                vec!["import".into(), "{artifact_path}".into()],
4338            )),
4339            requires_materialization: true,
4340            note: "Write the artifact to a file, run the materialize command with its path, then launch the imported session.".into(),
4341        },
4342        TransferFormat::Pi => HandoffInstructions {
4343            launch: launch("pi", vec!["--session".into(), "{artifact_path}".into()]),
4344            materialize: None,
4345            requires_materialization: true,
4346            note: "Write the artifact to a file and replace {artifact_path} in the launch arguments; Pi can resume that file directly.".into(),
4347        },
4348        TransferFormat::Grok => HandoffInstructions {
4349            launch: launch(
4350                "grok",
4351                vec![
4352                    "--resume".into(),
4353                    "{imported_session_id}".into(),
4354                    "--fork-session".into(),
4355                ],
4356            ),
4357            materialize: Some(launch(
4358                "grok",
4359                vec!["import".into(), "--json".into(), "{artifact_path}".into()],
4360            )),
4361            requires_materialization: true,
4362            note: "The artifact is Claude Code JSONL for Grok's official importer. Write it to a file, run the materialize command, read sessionId from its NDJSON outcome=imported record, replace {imported_session_id} in the launch arguments, then launch a writable fork of the imported session.".into(),
4363        },
4364        TransferFormat::Gemini => HandoffInstructions {
4365            launch: launch(
4366                "gemini",
4367                vec!["--session-file".into(), "{artifact_path}".into()],
4368            ),
4369            materialize: None,
4370            requires_materialization: true,
4371            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(),
4372        },
4373        TransferFormat::Goose => HandoffInstructions {
4374            launch: launch(
4375                "goose",
4376                vec![
4377                    "session".into(),
4378                    "--resume".into(),
4379                    "--session-id".into(),
4380                    "{imported_session_id}".into(),
4381                ],
4382            ),
4383            materialize: Some(launch(
4384                "goose",
4385                vec!["session".into(), "import".into(), "{artifact_path}".into()],
4386            )),
4387            requires_materialization: true,
4388            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(),
4389        },
4390    }
4391}
4392
4393fn resume_launch(
4394    harness: &str,
4395    session_id: &str,
4396    cwd: &Path,
4397    policy: ResumePolicy,
4398) -> std::result::Result<StructuredLaunch, ServiceError> {
4399    let mut arguments = Vec::new();
4400    let program = match harness {
4401        HarnessId::GROK => {
4402            if matches!(policy, ResumePolicy::Yolo) {
4403                if crate::support::self_sandbox_supported() {
4404                    arguments.extend(["--sandbox".into(), "workspace".into()]);
4405                }
4406                arguments.push("--always-approve".into());
4407            }
4408            arguments.extend(["--resume".into(), session_id.into()]);
4409            "grok"
4410        }
4411        HarnessId::CODEX => {
4412            let cwd_key = serde_json::to_string(cwd.to_string_lossy().as_ref())
4413                .expect("a filesystem path always serializes as JSON text");
4414            arguments.extend([
4415                "-c".into(),
4416                "check_for_update_on_startup=false".into(),
4417                "-c".into(),
4418                format!("projects.{cwd_key}.trust_level=\"trusted\""),
4419            ]);
4420            if matches!(policy, ResumePolicy::Yolo) {
4421                arguments.extend([
4422                    "--dangerously-bypass-approvals-and-sandbox".into(),
4423                    "--dangerously-bypass-hook-trust".into(),
4424                ]);
4425            }
4426            arguments.extend(["resume".into(), session_id.into()]);
4427            "codex"
4428        }
4429        HarnessId::CLAUDE_CODE => {
4430            if matches!(policy, ResumePolicy::Yolo) {
4431                arguments.push("--dangerously-skip-permissions".into());
4432            }
4433            arguments.extend(["--resume".into(), session_id.into()]);
4434            "claude"
4435        }
4436        HarnessId::GEMINI => {
4437            if matches!(policy, ResumePolicy::Yolo) {
4438                arguments.push("--yolo".into());
4439            }
4440            arguments.extend(["--resume".into(), session_id.into()]);
4441            "gemini"
4442        }
4443        HarnessId::GOOSE => {
4444            arguments.extend([
4445                "session".into(),
4446                "--resume".into(),
4447                "--session-id".into(),
4448                session_id.into(),
4449            ]);
4450            "goose"
4451        }
4452        HarnessId::PI => {
4453            if matches!(policy, ResumePolicy::Yolo) {
4454                arguments.push("--approve".into());
4455            }
4456            arguments.extend(["--session".into(), session_id.into()]);
4457            "pi"
4458        }
4459        HarnessId::OPENCODE => {
4460            arguments.extend(["--session".into(), session_id.into()]);
4461            "opencode"
4462        }
4463        HarnessId::SUPERCODE => {
4464            if matches!(policy, ResumePolicy::Yolo) {
4465                arguments.push("--dangerous".into());
4466            }
4467            arguments.extend(["resume".into(), session_id.into()]);
4468            "supercode"
4469        }
4470        other => {
4471            return Err(ServiceError::InvalidParams(format!(
4472                "no structured resume launch is registered for harness `{other}`"
4473            )))
4474        }
4475    };
4476    Ok(StructuredLaunch {
4477        cwd: cwd.to_path_buf(),
4478        program: program.into(),
4479        arguments,
4480        env: BTreeMap::new(),
4481    })
4482}
4483
4484/// Stage the resolved gateway credential in a private (0600) file so the
4485/// bridge can read it via `--token-file` — the delivery the real `openclaw
4486/// acp` accepts. One stable file per endpoint (keyed by an address digest,
4487/// no secret material in the name), overwritten on every connect so files
4488/// never accumulate and a rotated token never goes stale on disk.
4489fn openclaw_gateway_token_file(address: &str, secret: &str) -> std::io::Result<PathBuf> {
4490    let digest = blake3::hash(address.as_bytes()).to_hex();
4491    let path = std::env::temp_dir().join(format!(
4492        "supercode-openclaw-gateway-token-{}",
4493        &digest.as_str()[..16]
4494    ));
4495    #[cfg(unix)]
4496    {
4497        use std::io::Write;
4498        use std::os::unix::fs::OpenOptionsExt;
4499        let mut file = std::fs::OpenOptions::new()
4500            .write(true)
4501            .create(true)
4502            .truncate(true)
4503            .mode(0o600)
4504            .open(&path)?;
4505        file.write_all(secret.as_bytes())?;
4506    }
4507    #[cfg(not(unix))]
4508    std::fs::write(&path, secret)?;
4509    Ok(path)
4510}
4511
4512/// Open a connect-mode descriptor: resolve the endpoint address and
4513/// credential from the harness's own config file and build the backend that
4514/// joins the already-running endpoint. Fails closed with a specific
4515/// diagnostic when the config cannot be resolved or the declared protocol has
4516/// no connect-capable client yet.
4517fn open_connect_descriptor(
4518    descriptor: &crate::HarnessSupportDescriptor,
4519    home: &Path,
4520) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
4521    let Some(connect) = &descriptor.runtime.connect_launch else {
4522        return Err(ServiceError::InvalidParams(format!(
4523            "harness `{}` has no registered connect-mode launch",
4524            descriptor.id.as_str()
4525        )));
4526    };
4527    let resolved = connect
4528        .resolve(home)
4529        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
4530    match (descriptor.id.as_str(), connect.protocol.as_str()) {
4531        (HarnessId::OPENCODE, protocol) if protocol.starts_with("opencode-http") => {
4532            let mut backend = OpenCodeRuntimeBackend::connect(&resolved.address);
4533            if let Some(token) = resolved.auth {
4534                backend = backend.with_bearer(token);
4535            }
4536            Ok(Box::new(backend))
4537        }
4538        (HarnessId::OPENCLAW, protocol) if protocol.starts_with("acp") => {
4539            // OpenClaw's own `openclaw acp` binary is the gateway client: a
4540            // stdio ACP bridge that joins the RUNNING gateway at the resolved
4541            // endpoint. Blind-walk finding 2026-08-31: the real bridge does
4542            // NOT honor OPENCLAW_GATEWAY_TOKEN from the environment — the
4543            // credential must arrive via `--token-file` (never bare `--token`
4544            // on argv, where process listings could read it). The env var is
4545            // still set for older bridges that did read it. Requires openclaw
4546            // >= 2026.7: the 2026.2 bridge drops its gateway socket
4547            // mid-prompt and advertises no session resume (executed finding,
4548            // docs/interop/research/openclaw-acp-dialect-2026-08-30.json).
4549            let mut env = BTreeMap::new();
4550            let mut arguments = vec!["acp".into(), "--url".into(), resolved.address.clone()];
4551            if let Some(token) = resolved.auth {
4552                let token_path = openclaw_gateway_token_file(&resolved.address, token.secret())
4553                    .map_err(|error| {
4554                        ServiceError::UnsupportedAction(format!(
4555                            "could not stage the gateway credential for the bridge: {error}"
4556                        ))
4557                    })?;
4558                arguments.push("--token-file".into());
4559                arguments.push(token_path.to_string_lossy().into_owned());
4560                env.insert("OPENCLAW_GATEWAY_TOKEN".to_string(), token.secret().to_string());
4561            }
4562            // The bridge program comes from the descriptor's own default
4563            // launch (the compiled registry pins `openclaw`), so tests can
4564            // substitute an absolute mock-bridge path without touching
4565            // process-global state.
4566            let program = descriptor
4567                .runtime
4568                .default_launch
4569                .as_ref()
4570                .map(|launch| launch.program.clone())
4571                .unwrap_or_else(|| "openclaw".into());
4572            let launch = RuntimeLaunch {
4573                program,
4574                arguments,
4575                env,
4576            };
4577            Ok(Box::new(
4578                crate::AcpRuntimeBackend::new(descriptor.id.clone(), launch)
4579                    .with_resume_support(descriptor.runtime.capabilities.resume_session),
4580            ))
4581        }
4582        _ => Err(ServiceError::UnsupportedAction(format!(
4583            "connect-mode endpoint for `{}` speaks `{}`; joining it needs that protocol's gateway client",
4584            descriptor.id.as_str(),
4585            connect.protocol
4586        ))),
4587    }
4588}
4589
4590/// The registry's connect-mode launch for this harness, honored only when the
4591/// caller supplied neither an explicit launch nor a base URL.
4592fn registry_connect_descriptor(
4593    params: &RuntimeBackendParams,
4594) -> Option<crate::HarnessSupportDescriptor> {
4595    if params.launch.is_some() || params.base_url.is_some() {
4596        return None;
4597    }
4598    harness_support_registry()
4599        .harnesses
4600        .into_iter()
4601        .find(|descriptor| descriptor.id == params.harness)
4602        .filter(|descriptor| descriptor.runtime.connect_launch.is_some())
4603}
4604
4605fn service_home() -> std::result::Result<PathBuf, ServiceError> {
4606    std::env::var_os("HOME").map(PathBuf::from).ok_or_else(|| {
4607        ServiceError::UnsupportedAction(
4608            "connect-mode launches need HOME to locate the harness config".into(),
4609        )
4610    })
4611}
4612
4613fn runtime_backend(
4614    params: &RuntimeBackendParams,
4615) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
4616    if let Some(descriptor) = registry_connect_descriptor(params) {
4617        return open_connect_descriptor(&descriptor, &service_home()?);
4618    }
4619    if params.protocol.as_deref() == Some("acp") {
4620        let launch = params
4621            .launch
4622            .clone()
4623            .or_else(|| {
4624                harness_support_registry()
4625                    .harnesses
4626                    .into_iter()
4627                    .find(|harness| harness.id == params.harness)
4628                    .filter(|harness| {
4629                        harness.runtime.implementation == ImplementationKind::GenericProtocol
4630                            && harness.runtime.protocol.starts_with("acp")
4631                    })
4632                    .and_then(|harness| harness.runtime.default_launch)
4633            })
4634            .ok_or_else(|| {
4635                ServiceError::InvalidParams(
4636                    "an ACP runtime requires `launch` unless the harness has a registered default"
4637                        .into(),
4638                )
4639            })?;
4640        let resume_session = harness_support_registry()
4641            .harnesses
4642            .into_iter()
4643            .find(|harness| harness.id == params.harness)
4644            .is_some_and(|harness| harness.runtime.capabilities.resume_session);
4645        return Ok(Box::new(
4646            AcpRuntimeBackend::new(params.harness.clone(), launch)
4647                .with_resume_support(resume_session),
4648        ));
4649    }
4650    let backend: Box<dyn RuntimeBackend> = match params.harness.as_str() {
4651        HarnessId::CODEX => Box::new(CodexRuntimeBackend::new()),
4652        HarnessId::CLAUDE_CODE => Box::new(ClaudeCodeRuntimeBackend::new()),
4653        HarnessId::PI => Box::new(PiRuntimeBackend::new()),
4654        HarnessId::OPENCODE => match &params.base_url {
4655            Some(url) => Box::new(OpenCodeRuntimeBackend::connect(url)),
4656            None => Box::new(OpenCodeRuntimeBackend::new()),
4657        },
4658        harness => {
4659            let descriptor = harness_support_registry()
4660                .harnesses
4661                .into_iter()
4662                .find(|descriptor| descriptor.id.as_str() == harness)
4663                .filter(|descriptor| {
4664                    descriptor.runtime.implementation == ImplementationKind::GenericProtocol
4665                        && descriptor.runtime.protocol.starts_with("acp")
4666                });
4667            let Some(descriptor) = descriptor else {
4668                return Err(ServiceError::InvalidParams(format!(
4669                    "no runtime adapter for harness `{harness}`; use protocol `acp` with a launch command"
4670                )));
4671            };
4672            let resume = descriptor.runtime.capabilities.resume_session;
4673            Box::new(
4674                AcpRuntimeBackend::new(
4675                    descriptor.id,
4676                    descriptor
4677                        .runtime
4678                        .default_launch
4679                        .expect("generic ACP registry entry includes its launch"),
4680                )
4681                .with_resume_support(resume),
4682            )
4683        }
4684    };
4685    Ok(backend)
4686}
4687
4688fn runtime_launch(params: &RuntimeBackendParams) -> Option<RuntimeLaunch> {
4689    if let Some(launch) = &params.launch {
4690        return Some(launch.clone());
4691    }
4692    if !matches!(params.policy, RuntimePolicy::Yolo) {
4693        return None;
4694    }
4695    let launch = match params.harness.as_str() {
4696        HarnessId::GROK => RuntimeLaunch {
4697            program: "grok".into(),
4698            arguments: {
4699                let mut arguments: Vec<String> = Vec::new();
4700                if crate::support::self_sandbox_supported() {
4701                    arguments.extend(["--sandbox".into(), "workspace".into()]);
4702                }
4703                arguments.extend([
4704                    "--always-approve".into(),
4705                    "agent".into(),
4706                    "--no-leader".into(),
4707                    "stdio".into(),
4708                ]);
4709                arguments
4710            },
4711            env: BTreeMap::from([("GROK_AGENT_DASHBOARD".into(), "0".into())]),
4712        },
4713        HarnessId::CODEX => RuntimeLaunch {
4714            program: "codex".into(),
4715            arguments: vec![
4716                "--dangerously-bypass-approvals-and-sandbox".into(),
4717                "--dangerously-bypass-hook-trust".into(),
4718                "app-server".into(),
4719            ],
4720            env: BTreeMap::new(),
4721        },
4722        HarnessId::CLAUDE_CODE => RuntimeLaunch {
4723            program: "claude".into(),
4724            arguments: vec![
4725                "--dangerously-skip-permissions".into(),
4726                "--print".into(),
4727                "--input-format".into(),
4728                "stream-json".into(),
4729                "--output-format".into(),
4730                "stream-json".into(),
4731                "--verbose".into(),
4732            ],
4733            env: BTreeMap::new(),
4734        },
4735        HarnessId::PI => RuntimeLaunch {
4736            program: "pi".into(),
4737            arguments: vec!["--approve".into(), "--mode".into(), "rpc".into()],
4738            env: BTreeMap::new(),
4739        },
4740        HarnessId::OPENCODE => RuntimeLaunch {
4741            program: "opencode".into(),
4742            arguments: vec!["serve".into()],
4743            env: BTreeMap::new(),
4744        },
4745        HarnessId::GEMINI => RuntimeLaunch {
4746            program: "gemini".into(),
4747            arguments: vec!["--acp".into(), "--yolo".into()],
4748            env: BTreeMap::new(),
4749        },
4750        HarnessId::GOOSE => RuntimeLaunch {
4751            program: "goose".into(),
4752            arguments: vec!["acp".into()],
4753            env: BTreeMap::new(),
4754        },
4755        HarnessId::SUPERCODE => RuntimeLaunch {
4756            program: "supercode".into(),
4757            arguments: vec!["acp".into(), "--dangerous".into()],
4758            env: BTreeMap::new(),
4759        },
4760        _ => return None,
4761    };
4762    Some(launch)
4763}
4764
4765/// Disposable harness state for a no-prompt readiness probe. Merely opening
4766/// several stock CLIs writes a session header or migrates configuration, so a
4767/// handshake must never point at the user's real home. Authentication files
4768/// are copied into the private temporary home; all writes disappear with the
4769/// guard after the connection closes.
4770struct IsolatedProbeHome {
4771    launch: RuntimeLaunch,
4772    root: PathBuf,
4773}
4774
4775impl IsolatedProbeHome {
4776    fn new(harness: &str, mut launch: RuntimeLaunch) -> std::io::Result<Self> {
4777        let root = std::env::temp_dir().join(format!(
4778            "supercode-harness-probe-{harness}-{}",
4779            generated_session_id()
4780        ));
4781        std::fs::create_dir_all(&root)?;
4782        set_private_dir_permissions(&root)?;
4783
4784        if let Some(source_home) = std::env::var_os("HOME").map(PathBuf::from) {
4785            for relative in probe_auth_files(harness) {
4786                copy_probe_file(&source_home, &root, relative)?;
4787            }
4788        }
4789        configure_isolated_probe_auth(harness, &root)?;
4790
4791        let root_text = root.to_string_lossy().into_owned();
4792        for (key, value) in [
4793            ("HOME", root_text.clone()),
4794            (
4795                "XDG_CACHE_HOME",
4796                root.join(".cache").to_string_lossy().into_owned(),
4797            ),
4798            (
4799                "XDG_CONFIG_HOME",
4800                root.join(".config").to_string_lossy().into_owned(),
4801            ),
4802            (
4803                "XDG_DATA_HOME",
4804                root.join(".local/share").to_string_lossy().into_owned(),
4805            ),
4806        ] {
4807            launch.env.insert(key.into(), value);
4808        }
4809        let scoped = match harness {
4810            HarnessId::CLAUDE_CODE => Some(("CLAUDE_CONFIG_DIR", root.join(".claude"))),
4811            HarnessId::CODEX => Some(("CODEX_HOME", root.join(".codex"))),
4812            HarnessId::GEMINI => Some(("GEMINI_CLI_HOME", root.clone())),
4813            HarnessId::GROK => Some(("GROK_HOME", root.join(".grok"))),
4814            HarnessId::PI => Some(("PI_CODING_AGENT_DIR", root.join(".pi/agent"))),
4815            HarnessId::SUPERCODE => Some(("SUPERCODE_HOME", root.join(".config/supercode"))),
4816            _ => None,
4817        };
4818        if let Some((key, value)) = scoped {
4819            launch
4820                .env
4821                .insert(key.into(), value.to_string_lossy().into_owned());
4822        }
4823        Ok(Self { launch, root })
4824    }
4825
4826    fn cleanup(&self) -> std::io::Result<()> {
4827        match std::fs::remove_dir_all(&self.root) {
4828            Ok(()) => Ok(()),
4829            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
4830            Err(error) => Err(error),
4831        }
4832    }
4833}
4834
4835impl Drop for IsolatedProbeHome {
4836    fn drop(&mut self) {
4837        let _ = self.cleanup();
4838    }
4839}
4840
4841fn probe_auth_files(harness: &str) -> &'static [&'static str] {
4842    match harness {
4843        HarnessId::CLAUDE_CODE => &[".claude/.credentials.json", ".claude.json"],
4844        // The gateway endpoint + token live in openclaw's own config; without
4845        // it the isolated probe dials the default endpoint unauthenticated
4846        // (PARITY-24 finding 2026-08-31).
4847        HarnessId::OPENCLAW => &[".openclaw/openclaw.json"],
4848        HarnessId::CODEX => &[".codex/auth.json"],
4849        HarnessId::GEMINI => &[
4850            ".gemini/google_accounts.json",
4851            ".gemini/oauth_creds.json",
4852            ".gemini/settings.json",
4853        ],
4854        HarnessId::GROK => &[".grok/auth.json", ".grok/config.toml"],
4855        HarnessId::OPENCODE => &[
4856            ".config/opencode/auth.json",
4857            ".local/share/opencode/auth.json",
4858        ],
4859        HarnessId::PI => &[".pi/agent/auth.json"],
4860        // Hermes keeps its provider selection in config.yaml, its OAuth
4861        // credential pool in auth.json, and API keys in .env; without them
4862        // the isolated probe sees "No LLM provider configured" for a
4863        // hermes that answers fine from the user's real home.
4864        HarnessId::HERMES => &[".hermes/config.yaml", ".hermes/auth.json", ".hermes/.env"],
4865        HarnessId::SUPERCODE => &[
4866            ".config/supercode/config.toml",
4867            ".config/supercode/credentials.toml",
4868        ],
4869        _ => &[],
4870    }
4871}
4872
4873fn copy_probe_file(source_home: &Path, probe_home: &Path, relative: &str) -> std::io::Result<()> {
4874    let source = source_home.join(relative);
4875    if !source.is_file() {
4876        return Ok(());
4877    }
4878    let destination = probe_home.join(relative);
4879    if let Some(parent) = destination.parent() {
4880        std::fs::create_dir_all(parent)?;
4881        set_private_dir_permissions(parent)?;
4882    }
4883    std::fs::copy(source, &destination)?;
4884    set_private_file_permissions(&destination)
4885}
4886
4887fn configure_isolated_probe_auth(harness: &str, probe_home: &Path) -> std::io::Result<()> {
4888    if harness != HarnessId::GEMINI {
4889        return Ok(());
4890    }
4891    let oauth = probe_home.join(".gemini/oauth_creds.json");
4892    if !oauth.is_file() {
4893        return Ok(());
4894    }
4895    let settings_path = probe_home.join(".gemini/settings.json");
4896    let mut settings = std::fs::read_to_string(&settings_path)
4897        .ok()
4898        .and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
4899        .unwrap_or_else(|| json!({}));
4900    settings["security"]["auth"]["selectedType"] = Value::String("oauth-personal".into());
4901    std::fs::write(
4902        &settings_path,
4903        serde_json::to_vec_pretty(&settings).map_err(std::io::Error::other)?,
4904    )?;
4905    set_private_file_permissions(&settings_path)
4906}
4907
4908#[cfg(unix)]
4909fn set_private_dir_permissions(path: &Path) -> std::io::Result<()> {
4910    use std::os::unix::fs::PermissionsExt;
4911    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
4912}
4913
4914#[cfg(not(unix))]
4915fn set_private_dir_permissions(_path: &Path) -> std::io::Result<()> {
4916    Ok(())
4917}
4918
4919#[cfg(unix)]
4920fn set_private_file_permissions(path: &Path) -> std::io::Result<()> {
4921    use std::os::unix::fs::PermissionsExt;
4922    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
4923}
4924
4925#[cfg(not(unix))]
4926fn set_private_file_permissions(_path: &Path) -> std::io::Result<()> {
4927    Ok(())
4928}
4929
4930fn find_executable(program: &str) -> Option<PathBuf> {
4931    let candidate = PathBuf::from(program);
4932    if candidate.components().count() > 1 {
4933        return candidate.is_file().then_some(candidate);
4934    }
4935    let path = std::env::var_os("PATH")?;
4936    for directory in std::env::split_paths(&path) {
4937        let candidate = directory.join(program);
4938        if candidate.is_file() {
4939            return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
4940        }
4941        #[cfg(windows)]
4942        {
4943            for extension in ["exe", "cmd", "bat"] {
4944                let candidate = directory.join(format!("{program}.{extension}"));
4945                if candidate.is_file() {
4946                    return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
4947                }
4948            }
4949        }
4950    }
4951    None
4952}
4953
4954async fn executable_version(executable: &Path) -> Option<String> {
4955    let mut command = tokio::process::Command::new(executable);
4956    command
4957        .arg("--version")
4958        .stdin(std::process::Stdio::null())
4959        .stdout(std::process::Stdio::piped())
4960        .stderr(std::process::Stdio::piped())
4961        .kill_on_drop(true);
4962    let output = tokio::time::timeout(Duration::from_secs(3), command.output())
4963        .await
4964        .ok()?
4965        .ok()?;
4966    let stdout = String::from_utf8_lossy(&output.stdout);
4967    let stderr = String::from_utf8_lossy(&output.stderr);
4968    stdout
4969        .lines()
4970        .chain(stderr.lines())
4971        .map(str::trim)
4972        .find(|line| !line.is_empty())
4973        .map(|line| truncate_text(line, 200))
4974}
4975
4976pub(crate) fn auth_evidence(harness: &str) -> bool {
4977    let env_names: &[&str] = match harness {
4978        HarnessId::CLAUDE_CODE => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
4979        HarnessId::CODEX => &["OPENAI_API_KEY"],
4980        HarnessId::OPENCODE => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
4981        HarnessId::PI => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
4982        HarnessId::GROK => &["XAI_API_KEY", "GROK_API_KEY"],
4983        HarnessId::GEMINI => &["GEMINI_API_KEY", "GOOGLE_API_KEY"],
4984        HarnessId::SUPERCODE => &["OPENROUTER_API_KEY"],
4985        _ => &[],
4986    };
4987    if env_names
4988        .iter()
4989        .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
4990    {
4991        return true;
4992    }
4993    let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else {
4994        return false;
4995    };
4996    let files: Vec<PathBuf> = match harness {
4997        HarnessId::CLAUDE_CODE => vec![home.join(".claude/.credentials.json")],
4998        HarnessId::CODEX => vec![home.join(".codex/auth.json")],
4999        HarnessId::OPENCODE => vec![
5000            home.join(".local/share/opencode/auth.json"),
5001            home.join(".config/opencode/auth.json"),
5002        ],
5003        HarnessId::PI => vec![home.join(".pi/agent/auth.json")],
5004        HarnessId::GROK => vec![home.join(".grok/auth.json")],
5005        HarnessId::GEMINI => vec![
5006            home.join(".gemini/oauth_creds.json"),
5007            home.join(".gemini/google_accounts.json"),
5008        ],
5009        HarnessId::SUPERCODE => vec![home.join(".config/supercode/credentials.toml")],
5010        HarnessId::HERMES => vec![home.join(".hermes/auth.json"), home.join(".hermes/.env")],
5011        _ => Vec::new(),
5012    };
5013    if files.into_iter().any(|path| {
5014        std::fs::metadata(path)
5015            .map(|metadata| metadata.is_file() && metadata.len() > 2)
5016            .unwrap_or(false)
5017    }) {
5018        return true;
5019    }
5020    // macOS keeps Claude Code's OAuth login in the Keychain, so
5021    // `.claude/.credentials.json` never exists there and the file probe above
5022    // reports a signed-in install as unauthenticated forever. A completed
5023    // login also writes an `oauthAccount` record into `~/.claude.json` on
5024    // every platform — file-based, prompt-free evidence (querying the
5025    // Keychain itself from an unsigned daemon can raise a UI prompt).
5026    if harness == HarnessId::CLAUDE_CODE {
5027        return std::fs::read_to_string(home.join(".claude.json"))
5028            .map(|text| text.contains("\"oauthAccount\""))
5029            .unwrap_or(false);
5030    }
5031    false
5032}
5033
5034fn looks_like_auth_error(message: &str) -> bool {
5035    let message = message.to_ascii_lowercase();
5036    [
5037        "auth",
5038        "login",
5039        "sign in",
5040        "sign-in",
5041        "credential",
5042        "unauthorized",
5043        "forbidden",
5044        "token",
5045    ]
5046    .iter()
5047    .any(|needle| message.contains(needle))
5048}
5049
5050fn unavailable_capabilities() -> crate::RuntimeCapabilities {
5051    crate::RuntimeCapabilities {
5052        start_session: false,
5053        resume_session: false,
5054        attach_existing_process: false,
5055        send_input: false,
5056        stream_events: false,
5057        interrupt: false,
5058        steer: false,
5059        respond_to_requests: false,
5060    }
5061}
5062
5063fn truncate_text(text: &str, max_chars: usize) -> String {
5064    let mut chars = text.chars();
5065    let truncated = chars.by_ref().take(max_chars).collect::<String>();
5066    if chars.next().is_some() {
5067        format!("{truncated}…")
5068    } else {
5069        truncated
5070    }
5071}
5072
5073fn error_message(error: ServiceError) -> String {
5074    match error {
5075        ServiceError::InvalidParams(message)
5076        | ServiceError::Operation(message)
5077        | ServiceError::UnsupportedAction(message) => message,
5078        ServiceError::MethodNotFound => "runtime adapter is not available".into(),
5079        ServiceError::Sdk(error) => error.to_string(),
5080    }
5081}
5082
5083#[derive(Debug)]
5084enum ServiceError {
5085    InvalidParams(String),
5086    MethodNotFound,
5087    UnsupportedAction(String),
5088    Operation(String),
5089    Sdk(SdkError),
5090}
5091
5092fn sdk_error(operation: SdkOperation, error: ServiceError) -> SdkError {
5093    match error {
5094        ServiceError::InvalidParams(message) => {
5095            SdkError::new(SdkErrorCode::InvalidArgument, operation, message)
5096        }
5097        ServiceError::MethodNotFound | ServiceError::UnsupportedAction(_) => {
5098            SdkError::unsupported(operation)
5099        }
5100        ServiceError::Operation(message) => {
5101            let code = if message.contains("already in progress") {
5102                SdkErrorCode::Busy
5103            } else if message.contains("not supported by this runtime") {
5104                SdkErrorCode::UnsupportedAction
5105            } else if message.contains("unknown runtime connection") {
5106                SdkErrorCode::NotFound
5107            } else {
5108                SdkErrorCode::Execution
5109            };
5110            SdkError::new(code, operation, message)
5111        }
5112        ServiceError::Sdk(error) => error,
5113    }
5114}
5115
5116fn sdk_rpc_error(id: Value, error: &SdkError) -> Value {
5117    let error_code = error.code();
5118    let code = match error_code {
5119        SdkErrorCode::Unauthenticated => -32030,
5120        SdkErrorCode::Unauthorized => -32031,
5121        SdkErrorCode::ControllerRequired => -32032,
5122        SdkErrorCode::LeaseExpired => -32033,
5123        SdkErrorCode::InvalidArgument => -32602,
5124        SdkErrorCode::NotFound => -32004,
5125        SdkErrorCode::Busy => -32000,
5126        SdkErrorCode::UnsupportedAction => -32020,
5127        SdkErrorCode::Execution => -32002,
5128        SdkErrorCode::Transport => -32003,
5129    };
5130    json!({
5131        "jsonrpc": "2.0",
5132        "id": id,
5133        "error": {
5134            "code": code,
5135            "name": error_code,
5136            "operation": error.operation(),
5137            "message": error.to_string(),
5138        },
5139    })
5140}
5141
5142fn decode<T: for<'de> Deserialize<'de>>(value: Value) -> std::result::Result<T, ServiceError> {
5143    serde_json::from_value(value).map_err(|error| ServiceError::InvalidParams(error.to_string()))
5144}
5145
5146fn operation(error: impl Into<crate::Error>) -> ServiceError {
5147    let error = error.into();
5148    match error {
5149        crate::Error::Sdk(error) => ServiceError::Sdk(error),
5150        error => ServiceError::Operation(error.to_string()),
5151    }
5152}
5153
5154/// ORCH-12 `harness.v1.memory.show|search` params. `homes` is the same
5155/// storage-root override every read-only method accepts, so a caller can
5156/// point the read at a fixture home without touching the real ones.
5157#[derive(Debug, Clone, Deserialize, Default)]
5158#[serde(default)]
5159struct MemoryRequest {
5160    /// Harness whose store is read. Required.
5161    harness: Option<String>,
5162    /// The needle, required by `search`.
5163    query: Option<String>,
5164    /// Hermes profile, OpenClaw agent, or Claude Code project.
5165    profile: Option<String>,
5166    /// Claude Code session id selecting a project store (`show` only).
5167    session: Option<String>,
5168    /// Include each document's whole text (`show` only).
5169    full: bool,
5170    /// Treat `query` as a regular expression (`search` only).
5171    regex: bool,
5172    /// Working tree whose project store is read.
5173    cwd: Option<std::path::PathBuf>,
5174    /// Storage roots to read.
5175    homes: crate::HarnessHomes,
5176}
5177
5178/// Read the memory noun. A harness with no memory store fails with
5179/// `UnsupportedAction` (RPC `-32020`), never an empty list.
5180fn memory_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
5181    let request = decode::<MemoryRequest>(params)?;
5182    let harness = request
5183        .harness
5184        .clone()
5185        .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
5186    let to_service = |error: crate::memory::MemoryError| match error {
5187        crate::memory::MemoryError::UnsupportedHarness { .. }
5188        | crate::memory::MemoryError::SessionNotScoped { .. } => {
5189            ServiceError::UnsupportedAction(error.to_string())
5190        }
5191        other => ServiceError::InvalidParams(other.to_string()),
5192    };
5193    match method {
5194        "harness.v1.memory.show" => {
5195            let documents = crate::memory::show_memory(&crate::memory::MemoryQuery {
5196                harness,
5197                profile: request.profile,
5198                session: request.session,
5199                full: request.full,
5200                cwd: request.cwd,
5201                homes: request.homes,
5202            })
5203            .map_err(to_service)?;
5204            Ok(json!({
5205                "schema": crate::memory::MEMORY_SCHEMA,
5206                "documents": documents,
5207            }))
5208        }
5209        "harness.v1.memory.search" => {
5210            let query = request
5211                .query
5212                .ok_or_else(|| ServiceError::InvalidParams("`query` is required".into()))?;
5213            let matches = crate::memory::search_memory(&crate::memory::MemorySearchQuery {
5214                harness,
5215                query,
5216                profile: request.profile,
5217                regex: request.regex,
5218                cwd: request.cwd,
5219                homes: request.homes,
5220            })
5221            .map_err(to_service)?;
5222            Ok(json!({
5223                "schema": crate::memory::MEMORY_SCHEMA,
5224                "matches": matches,
5225            }))
5226        }
5227        _ => Err(ServiceError::MethodNotFound),
5228    }
5229}
5230
5231/// ORCH-10 `harness.v1.profiles.list|get` params. `homes` is the same
5232/// storage-root override every read-only method accepts, so a caller can
5233/// point the read at a fixture home without touching the real ones.
5234#[derive(Debug, Clone, Deserialize)]
5235#[serde(default)]
5236struct ProfilesQuery {
5237    /// Restrict the listing to one harness. `get` requires it.
5238    harness: Option<String>,
5239    /// Profile name, required by `get`.
5240    name: Option<String>,
5241    /// Storage roots to read.
5242    homes: crate::HarnessHomes,
5243}
5244
5245impl Default for ProfilesQuery {
5246    fn default() -> Self {
5247        Self {
5248            harness: None,
5249            name: None,
5250            homes: crate::HarnessHomes::default(),
5251        }
5252    }
5253}
5254
5255/// Read the profile noun. A harness with no profile concept fails with
5256/// `UnsupportedAction` (RPC `-32020`), never an empty list.
5257fn profiles_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
5258    let query = decode::<ProfilesQuery>(params)?;
5259    let to_service = |error: crate::profiles::ProfileError| match error {
5260        crate::profiles::ProfileError::UnsupportedHarness { .. } => {
5261            ServiceError::UnsupportedAction(error.to_string())
5262        }
5263        crate::profiles::ProfileError::NotFound { .. } => {
5264            ServiceError::InvalidParams(error.to_string())
5265        }
5266    };
5267    match method {
5268        "harness.v1.profiles.list" => {
5269            let profiles = crate::profiles::list_profiles(&query.homes, query.harness.as_deref())
5270                .map_err(to_service)?;
5271            Ok(json!({
5272                "schema": crate::profiles::PROFILES_SCHEMA,
5273                "profiles": profiles,
5274            }))
5275        }
5276        "harness.v1.profiles.get" => {
5277            let harness = query
5278                .harness
5279                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
5280            let name = query
5281                .name
5282                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
5283            let profile =
5284                crate::profiles::get_profile(&query.homes, &harness, &name).map_err(to_service)?;
5285            Ok(json!({
5286                "schema": crate::profiles::PROFILES_SCHEMA,
5287                "profile": profile,
5288            }))
5289        }
5290        _ => Err(ServiceError::MethodNotFound),
5291    }
5292}
5293
5294/// ORCH-14 `harness.v1.channels.list|status` params, the same storage-root
5295/// override every read-only method accepts so a caller can point the read at
5296/// a fixture home without touching the real ones.
5297#[derive(Debug, Clone, Deserialize)]
5298#[serde(default)]
5299struct ChannelsQuery {
5300    /// Restrict the listing to one harness. `status` requires it.
5301    harness: Option<String>,
5302    /// Channel name, required by `status`.
5303    name: Option<String>,
5304    /// Storage roots to read.
5305    homes: crate::HarnessHomes,
5306}
5307
5308impl Default for ChannelsQuery {
5309    fn default() -> Self {
5310        Self {
5311            harness: None,
5312            name: None,
5313            homes: crate::HarnessHomes::default(),
5314        }
5315    }
5316}
5317
5318/// Read the channel noun. A harness with no channel concept fails with
5319/// `UnsupportedAction` (RPC `-32020`), never an empty list. No row carries a
5320/// token, key or secret — see `crate::channels` "Secrecy".
5321#[derive(Debug, Clone, Deserialize)]
5322#[serde(default)]
5323struct RoutesQuery {
5324    harness: Option<String>,
5325    /// Restrict to routes targeting one profile / agent.
5326    profile: Option<String>,
5327    homes: crate::HarnessHomes,
5328}
5329
5330impl Default for RoutesQuery {
5331    fn default() -> Self {
5332        Self {
5333            harness: None,
5334            profile: None,
5335            homes: crate::HarnessHomes::default(),
5336        }
5337    }
5338}
5339
5340#[derive(Debug, Clone, Deserialize)]
5341#[serde(default)]
5342struct TriggersQuery {
5343    harness: Option<String>,
5344    homes: crate::HarnessHomes,
5345}
5346
5347impl Default for TriggersQuery {
5348    fn default() -> Self {
5349        Self {
5350            harness: None,
5351            homes: crate::HarnessHomes::default(),
5352        }
5353    }
5354}
5355
5356fn triggers_call(params: Value) -> std::result::Result<Value, ServiceError> {
5357    let query = decode::<TriggersQuery>(params)?;
5358    let triggers = crate::triggers::list_triggers(&query.homes, query.harness.as_deref())
5359        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
5360    Ok(json!({
5361        "schema": crate::triggers::TRIGGERS_SCHEMA,
5362        "triggers": triggers,
5363    }))
5364}
5365
5366fn routes_call(params: Value) -> std::result::Result<Value, ServiceError> {
5367    let query = decode::<RoutesQuery>(params)?;
5368    let routes = crate::routes::list_routes(
5369        &query.homes,
5370        query.harness.as_deref(),
5371        query.profile.as_deref(),
5372    )
5373    .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
5374    Ok(json!({
5375        "schema": crate::routes::ROUTES_SCHEMA,
5376        "routes": routes,
5377    }))
5378}
5379
5380fn channels_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
5381    let query = decode::<ChannelsQuery>(params)?;
5382    let to_service = |error: crate::channels::ChannelError| match error {
5383        crate::channels::ChannelError::UnsupportedHarness { .. } => {
5384            ServiceError::UnsupportedAction(error.to_string())
5385        }
5386        crate::channels::ChannelError::NotFound { .. } => {
5387            ServiceError::InvalidParams(error.to_string())
5388        }
5389    };
5390    match method {
5391        "harness.v1.channels.list" => {
5392            let channels = crate::channels::list_channels(&query.homes, query.harness.as_deref())
5393                .map_err(to_service)?;
5394            Ok(json!({
5395                "schema": crate::channels::CHANNELS_SCHEMA,
5396                "channels": channels,
5397            }))
5398        }
5399        "harness.v1.channels.status" => {
5400            let harness = query
5401                .harness
5402                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
5403            let name = query
5404                .name
5405                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
5406            let channel = crate::channels::channel_status(&query.homes, &harness, &name)
5407                .map_err(to_service)?;
5408            Ok(json!({
5409                "schema": crate::channels::CHANNELS_SCHEMA,
5410                "channel": channel,
5411            }))
5412        }
5413        _ => Err(ServiceError::MethodNotFound),
5414    }
5415}
5416
5417fn rpc_error(id: Value, code: i64, message: &str) -> Value {
5418    json!({
5419        "jsonrpc": "2.0",
5420        "id": id,
5421        "error": {"code": code, "message": message},
5422    })
5423}
5424
5425#[cfg(test)]
5426mod tests {
5427    use super::*;
5428    use crate::{HarnessEvent, HarnessId, RuntimeEndpoint, RuntimeHandle, StorageLocator};
5429    use async_trait::async_trait;
5430    use std::io::Write;
5431    use std::path::PathBuf;
5432    use std::time::Instant;
5433
5434    #[test]
5435    fn indexed_claude_descriptor_keeps_the_live_peer_address() {
5436        let descriptor = SessionDescriptor {
5437            locator: SessionLocator {
5438                harness: HarnessId::new(HarnessId::CLAUDE_CODE),
5439                session_id: "live-session".into(),
5440                storage: StorageLocator::File {
5441                    path: PathBuf::from("/tmp/live-session.jsonl"),
5442                },
5443            },
5444            cwd: Some(PathBuf::from("/project")),
5445            title: None,
5446            preview_candidates: Vec::new(),
5447            latest_message_candidates: Vec::new(),
5448            updated_at_ms: Some(1),
5449            message_count: None,
5450            model: None,
5451            parent_session_id: None,
5452            child_session_count: 0,
5453            nouns: Default::default(),
5454        };
5455        let peer = crate::claude_peer::ClaudePeerSession {
5456            pid: 42,
5457            session_id: "live-session".into(),
5458            cwd: Some(PathBuf::from("/project")),
5459            name: "peer".into(),
5460            socket_path: PathBuf::from("/tmp/peer.sock"),
5461            status: Some(crate::claude_peer::ClaudePeerStatus::Busy),
5462            updated_at_ms: Some(1),
5463            version: Some("test".into()),
5464        };
5465
5466        let value = live_descriptor_value(&descriptor, &[peer]).unwrap();
5467        assert!(value["live_endpoint"]
5468            .as_str()
5469            .is_some_and(|endpoint| endpoint.starts_with("cc-peer:v1:42:peer:")));
5470    }
5471
5472    struct EndingRuntime {
5473        handle: RuntimeHandle,
5474        event: Option<HarnessEvent>,
5475    }
5476
5477    #[async_trait]
5478    impl RuntimeConnection for EndingRuntime {
5479        fn handle(&self) -> &RuntimeHandle {
5480            &self.handle
5481        }
5482
5483        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
5484            unreachable!("ending runtime does not accept input")
5485        }
5486
5487        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
5488            Ok(self.event.take())
5489        }
5490
5491        async fn interrupt(&mut self) -> crate::Result<()> {
5492            Ok(())
5493        }
5494
5495        async fn respond(&mut self, _request_id: Value, _response: Value) -> crate::Result<()> {
5496            Ok(())
5497        }
5498
5499        async fn close(&mut self) -> crate::Result<()> {
5500            Ok(())
5501        }
5502    }
5503
5504    fn ending_runtime(event: Option<HarnessEvent>) -> Box<dyn RuntimeConnection> {
5505        Box::new(EndingRuntime {
5506            handle: RuntimeHandle {
5507                harness: HarnessId::from(HarnessId::CLAUDE_CODE),
5508                runtime_id: "ending-session".into(),
5509                endpoint: RuntimeEndpoint::LocalProcess {
5510                    pid: None,
5511                    command: vec!["ending-runtime".into()],
5512                    protocol: "test".into(),
5513                },
5514            },
5515            event,
5516        })
5517    }
5518
5519    fn request(id: u64, method: &str, params: Value) -> Value {
5520        json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
5521    }
5522
5523    // ---- ORCH-6: conversation nouns on `sessions.*` ----------------------
5524
5525    fn hermes_store() -> PathBuf {
5526        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hermes_home/state.db")
5527    }
5528
5529    /// The discovery response for the Hermes fixture home, with the one
5530    /// machine-specific value (the absolute store path) replaced so the exact
5531    /// same JSON can be committed and replayed by the UI story.
5532    fn hermes_discovery(params: Value) -> Value {
5533        let mut response =
5534            HarnessSessionService::new().handle(request(1, "harness.v1.sessions.discover", params));
5535        let store = hermes_store().display().to_string();
5536        for session in response["result"]["sessions"]
5537            .as_array_mut()
5538            .expect("sessions array")
5539        {
5540            if session["locator"]["storage"]["path"] == json!(store) {
5541                session["locator"]["storage"]["path"] = json!("<fixtures>/hermes_home/state.db");
5542            }
5543            // `activity` reports a wall-clock observation instant, not a fact
5544            // about the session; it would make this response differ on every
5545            // call. The nouns under test are all session facts.
5546            session.as_object_mut().unwrap().remove("activity");
5547        }
5548        response["result"].take()
5549    }
5550
5551    fn hermes_query() -> Value {
5552        json!({
5553            "harnesses": ["hermes"],
5554            "homes": {"hermes": hermes_store()},
5555        })
5556    }
5557
5558    fn row<'a>(result: &'a Value, id: &str) -> &'a Value {
5559        result["sessions"]
5560            .as_array()
5561            .expect("sessions array")
5562            .iter()
5563            .find(|session| session["locator"]["session_id"] == json!(id))
5564            .unwrap_or_else(|| panic!("no discovered row for `{id}` in {result:#}"))
5565    }
5566
5567    #[test]
5568    fn orch6_discover_rows_carry_the_conversation_nouns() {
5569        let result = hermes_discovery(hermes_query());
5570
5571        // A Telegram DM: reached on a channel, no repo — the workspace IS the
5572        // channel (D2 precedence), and `main` is not a profile.
5573        let dm = row(&result, "tg-dm-1");
5574        assert_eq!(dm["trigger"], json!("channel"));
5575        assert_eq!(dm["surface"]["platform"], json!("telegram"));
5576        assert_eq!(dm["surface"]["kind"], json!("dm"));
5577        assert_eq!(dm["surface"]["chat_id"], json!("123456"));
5578        assert_eq!(dm["surface"]["participant_id"], json!("u1"));
5579        assert_eq!(
5580            dm["workspace"],
5581            json!({"kind": "channel", "value": "telegram:123456"})
5582        );
5583        assert!(dm.get("profile").is_none(), "{dm:#}");
5584
5585        // A cron fire: recurring, with the job recovered from the minted id.
5586        let fire = row(&result, "cron_job42_20260902_120000");
5587        assert_eq!(fire["trigger"], json!("cron"));
5588        assert_eq!(
5589            fire["recurrence"],
5590            json!({"job_id": "job42", "kind": "cron"})
5591        );
5592        assert_eq!(fire["workspace"]["kind"], json!("repo"));
5593
5594        // A profiled group session with a pending handoff: repo workspace
5595        // wins over the channel, and the chat stays on the surface key.
5596        let coder = row(&result, "tg-coder-1");
5597        assert_eq!(coder["trigger"], json!("channel"));
5598        assert_eq!(coder["profile"], json!("coder"));
5599        assert_eq!(coder["surface"]["thread_id"], json!("55"));
5600        assert_eq!(
5601            coder["surface"]["key"],
5602            json!("agent:coder:telegram:group:-100777:55")
5603        );
5604        assert_eq!(
5605            coder["workspace"],
5606            json!({"kind": "repo", "value": "/workspace/project"})
5607        );
5608        assert_eq!(
5609            coder["cross_surface"],
5610            json!({"state": "pending", "platform": "discord"})
5611        );
5612
5613        // A plain ACP session stays human-triggered with no surface at all.
5614        let acp = row(&result, "cef97234-e8e8-428a-99ab-e8fff4e7e613");
5615        assert_eq!(acp["trigger"], json!("human"));
5616        assert!(acp.get("surface").is_none(), "{acp:#}");
5617        assert_eq!(acp["workspace"], json!({"kind": "none"}));
5618    }
5619
5620    #[test]
5621    fn orch6_discover_filters_by_harness_and_profile() {
5622        let mut params = hermes_query();
5623        params["profile"] = json!("coder");
5624        let result = hermes_discovery(params);
5625        let ids: Vec<&str> = result["sessions"]
5626            .as_array()
5627            .expect("sessions array")
5628            .iter()
5629            .map(|session| session["locator"]["session_id"].as_str().unwrap())
5630            .collect();
5631        assert_eq!(ids, vec!["tg-coder-1"]);
5632
5633        // A profile no session is routed through returns nothing rather than
5634        // silently ignoring the filter.
5635        let mut missing = hermes_query();
5636        missing["profile"] = json!("nobody");
5637        assert_eq!(hermes_discovery(missing)["sessions"], json!([]));
5638
5639        // The harness filter is `harnesses`; an id no harness answers to is
5640        // an empty page, never every store on the box.
5641        let elsewhere = json!({"harnesses": ["codex"], "homes": {"codex": hermes_store()}});
5642        assert_eq!(hermes_discovery(elsewhere)["sessions"], json!([]));
5643    }
5644
5645    #[test]
5646    fn orch6_load_reports_the_same_nouns_as_discovery() {
5647        let mut service = HarnessSessionService::new();
5648        let loaded = service.handle(request(
5649            1,
5650            "harness.v1.sessions.load",
5651            json!({"locator": {
5652                "harness": "hermes",
5653                "session_id": "tg-coder-1",
5654                "storage": {"kind": "file", "path": hermes_store()},
5655            }}),
5656        ));
5657        let session = &loaded["result"]["session"];
5658        let discovered = hermes_discovery(hermes_query());
5659        let row = row(&discovered, "tg-coder-1");
5660        for noun in [
5661            "trigger",
5662            "surface",
5663            "profile",
5664            "recurrence",
5665            "cross_surface",
5666            "workspace",
5667        ] {
5668            assert_eq!(
5669                session[noun],
5670                row.get(noun).cloned().unwrap_or(Value::Null),
5671                "`{noun}` disagrees between sessions.load and sessions.discover"
5672            );
5673        }
5674    }
5675
5676    /// ORCH-10: the fixture homes, as the RPC's `homes` override. Hermes's
5677    /// home is named by its `state.db`; OpenClaw's is the state directory.
5678    fn profile_fixture_homes() -> Value {
5679        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
5680        json!({
5681            "hermes": fixtures.join("hermes_home/state.db"),
5682            "openclaw": fixtures.join("openclaw_home"),
5683        })
5684    }
5685
5686    fn profile_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
5687        response["result"]["profiles"]
5688            .as_array()
5689            .unwrap_or_else(|| panic!("no profiles array in {response}"))
5690            .iter()
5691            .find(|row| row["harness"] == harness && row["name"] == name)
5692            .unwrap_or_else(|| panic!("no `{harness}` profile `{name}` in {response}"))
5693    }
5694
5695    /// dev/01: every source answers in one row shape, over the committed
5696    /// fixture homes — the Hermes profile directory and its `state.db`
5697    /// partition, the OpenClaw agent directories and `openclaw.json`, and
5698    /// supercode's own presets.
5699    #[test]
5700    fn profiles_list_reads_every_source_uniformly() {
5701        let mut service = HarnessSessionService::new();
5702        let response = service.handle(request(
5703            1,
5704            "harness.v1.profiles.list",
5705            json!({"homes": profile_fixture_homes()}),
5706        ));
5707        assert_eq!(
5708            response["result"]["schema"],
5709            crate::profiles::PROFILES_SCHEMA
5710        );
5711
5712        let default = profile_row(&response, "hermes", "default");
5713        assert_eq!(default["kind"], "hermes_profile");
5714        assert_eq!(default["default"], true);
5715        assert_eq!(default["routes"], 0);
5716        assert_eq!(default["sessions"], 11);
5717        assert_eq!(default["model"], "anthropic/claude-sonnet-4-5");
5718
5719        let coder = profile_row(&response, "hermes", "coder");
5720        assert_eq!(coder["kind"], "hermes_profile");
5721        assert_eq!(coder["default"], false);
5722        assert_eq!(coder["routes"], 1, "gateway.profile_routes targets coder");
5723        assert_eq!(coder["sessions"], 1, "state.db profile_name = 'coder'");
5724        assert_eq!(coder["model"], "anthropic/claude-opus-4-8");
5725        assert!(coder["home"]
5726            .as_str()
5727            .unwrap()
5728            .ends_with("hermes_home/profiles/coder"));
5729
5730        let main = profile_row(&response, "openclaw", "main");
5731        assert_eq!(main["kind"], "openclaw_agent");
5732        // No entry declares `default: true` (real configs do not), so `main`
5733        // wins on OpenClaw's own convention rather than alphabetically.
5734        assert_eq!(main["default"], true);
5735        assert_eq!(main["routes"], 0);
5736        assert_eq!(main["sessions"], 4);
5737        assert_eq!(
5738            main["model"],
5739            Value::Null,
5740            "`agents.defaults.model` is an install default, not this agent's pin"
5741        );
5742
5743        let design = profile_row(&response, "openclaw", "design");
5744        assert_eq!(design["default"], false);
5745        assert_eq!(design["routes"], 1, "one binding names agentId `design`");
5746        assert_eq!(design["sessions"], 0);
5747        assert_eq!(design["model"], "anthropic/claude-opus-4-8");
5748
5749        let preset = profile_row(&response, "supercode", "supercode-default");
5750        assert_eq!(preset["kind"], "preset");
5751        assert_eq!(preset["default"], true);
5752        assert_eq!(preset["home"], Value::Null);
5753        assert_eq!(preset["routes"], Value::Null);
5754    }
5755
5756    /// Codex's own profiles are `[profiles.<name>]` tables, with the
5757    /// top-level `profile` key naming the default.
5758    #[test]
5759    fn profiles_list_reads_codex_profile_tables() {
5760        let codex_home = std::env::temp_dir().join(format!(
5761            "supercode-orch10-codex-{}-{}",
5762            std::process::id(),
5763            std::time::SystemTime::now()
5764                .duration_since(std::time::UNIX_EPOCH)
5765                .unwrap()
5766                .as_nanos()
5767        ));
5768        std::fs::create_dir_all(codex_home.join("sessions")).unwrap();
5769        std::fs::write(
5770            codex_home.join("config.toml"),
5771            "profile = \"review\"\n\n[profiles.review]\nmodel = \"gpt-5.1-codex\"\n\n[profiles.fast]\nmodel = \"gpt-5.1-codex-mini\"\n",
5772        )
5773        .unwrap();
5774
5775        let mut service = HarnessSessionService::new();
5776        let response = service.handle(request(
5777            1,
5778            "harness.v1.profiles.list",
5779            json!({"harness": "codex", "homes": {"codex": codex_home.join("sessions")}}),
5780        ));
5781        let rows = response["result"]["profiles"].as_array().unwrap();
5782        assert_eq!(rows.len(), 2, "{response}");
5783        let review = profile_row(&response, "codex", "review");
5784        assert_eq!(review["kind"], "codex_profile");
5785        assert_eq!(review["default"], true);
5786        assert_eq!(review["model"], "gpt-5.1-codex");
5787        assert_eq!(review["home"], Value::Null);
5788        assert_eq!(profile_row(&response, "codex", "fast")["default"], false);
5789
5790        let got = service.handle(request(
5791            2,
5792            "harness.v1.profiles.get",
5793            json!({
5794                "harness": "codex",
5795                "name": "fast",
5796                "homes": {"codex": codex_home.join("sessions")},
5797            }),
5798        ));
5799        assert_eq!(got["result"]["profile"]["model"], "gpt-5.1-codex-mini");
5800        std::fs::remove_dir_all(&codex_home).ok();
5801    }
5802
5803    /// A verb a harness lacks fails with `UnsupportedAction`, never a silent
5804    /// empty list; an unknown name is an invalid argument, not an empty row.
5805    #[test]
5806    fn profiles_refuse_harnesses_without_the_concept() {
5807        let mut service = HarnessSessionService::new();
5808        let response = service.handle(request(
5809            1,
5810            "harness.v1.profiles.list",
5811            json!({"harness": "claude-code"}),
5812        ));
5813        assert_eq!(response["error"]["code"], -32020, "{response}");
5814
5815        let missing = service.handle(request(
5816            2,
5817            "harness.v1.profiles.get",
5818            json!({
5819                "harness": "hermes",
5820                "name": "no-such-profile",
5821                "homes": profile_fixture_homes(),
5822            }),
5823        ));
5824        assert_eq!(missing["error"]["code"], -32602, "{missing}");
5825    }
5826
5827    /// The two methods are advertised, so a client discovers them from
5828    /// `harness.v1.capabilities` rather than from documentation.
5829    #[test]
5830    fn profiles_methods_are_advertised() {
5831        let mut service = HarnessSessionService::new();
5832        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
5833        let methods = response["result"]["methods"].as_array().unwrap();
5834        for method in ["harness.v1.profiles.list", "harness.v1.profiles.get"] {
5835            assert!(
5836                methods.iter().any(|entry| entry == method),
5837                "{method} is not advertised"
5838            );
5839        }
5840    }
5841
5842    // -----------------------------------------------------------------
5843    // ORCH-14 — channels
5844    // -----------------------------------------------------------------
5845
5846    fn channel_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
5847        response["result"]["channels"]
5848            .as_array()
5849            .unwrap_or_else(|| panic!("no channels array in {response}"))
5850            .iter()
5851            .find(|row| row["harness"] == harness && row["name"] == name)
5852            .unwrap_or_else(|| panic!("no `{harness}` channel `{name}` in {response}"))
5853    }
5854
5855    fn channels_list(harness: Option<&str>) -> Value {
5856        let mut params = json!({"homes": profile_fixture_homes()});
5857        if let Some(harness) = harness {
5858            params["harness"] = json!(harness);
5859        }
5860        HarnessSessionService::new().handle(request(1, "harness.v1.channels.list", params))
5861    }
5862
5863    /// dev/01: both sources answer in one row shape over the committed
5864    /// fixture homes — Hermes's `platforms:` blocks with their `extra` maps,
5865    /// and OpenClaw's `channels.<name>` entries split per account.
5866    #[test]
5867    fn channels_list_reads_both_gateway_harnesses_uniformly() {
5868        let response = channels_list(None);
5869        assert_eq!(
5870            response["result"]["schema"],
5871            crate::channels::CHANNELS_SCHEMA
5872        );
5873
5874        // Hermes: a credentialed platform, a bridged `extra.key` platform,
5875        // and one the config explicitly disables.
5876        let telegram = channel_row(&response, "hermes", "telegram");
5877        assert_eq!(telegram["kind"], "telegram");
5878        assert_eq!(telegram["enabled"], true);
5879        assert_eq!(telegram["configured"], true);
5880        // The `sessions` count is the discovery rows whose surface platform
5881        // is telegram: the fixture's `agent:main:telegram:…` DM and the
5882        // `agent:coder:telegram:…` group.
5883        assert_eq!(telegram["sessions"], 2);
5884        let api = channel_row(&response, "hermes", "api_server");
5885        assert_eq!(api["configured"], true, "extra.key is a credential key");
5886        assert_eq!(api["sessions"], 0);
5887        let webhook = channel_row(&response, "hermes", "webhook");
5888        assert_eq!(webhook["enabled"], false);
5889        // Hermes lists no credential for `webhook`: declaring it is all it
5890        // needs, so a credential-less entry is still `configured`.
5891        assert_eq!(webhook["configured"], true);
5892
5893        // OpenClaw: one row per account, named `<channel>/<accountId>`.
5894        let linked = channel_row(&response, "openclaw", "slack/T0FIXTURE");
5895        assert_eq!(linked["kind"], "slack");
5896        assert_eq!(linked["account"], "T0FIXTURE");
5897        assert_eq!(linked["enabled"], true);
5898        assert_eq!(linked["configured"], true);
5899        let unlinked = channel_row(&response, "openclaw", "slack/T1FIXTURE");
5900        assert_eq!(unlinked["enabled"], false);
5901        assert_eq!(
5902            unlinked["configured"], false,
5903            "an account with no credential key is not configured"
5904        );
5905        // A single-account channel keeps its own name and names its account
5906        // inline.
5907        let telegram = channel_row(&response, "openclaw", "telegram");
5908        assert_eq!(telegram["account"], "hermes-fixture-bot");
5909        assert_eq!(telegram["configured"], true);
5910
5911        // `status` is never claimed from a config file.
5912        for row in response["result"]["channels"].as_array().unwrap() {
5913            assert_eq!(row["status"], "unknown", "{row}");
5914        }
5915    }
5916
5917    /// dev/01: no field of any emitted row carries a credential. The fixture
5918    /// homes hold four FAKE credential strings; a row that leaked one — as a
5919    /// value, an account label, or a name — fails here.
5920    #[test]
5921    fn channels_rows_never_carry_a_fixture_secret() {
5922        let secrets = [
5923            "FAKE-TOKEN-DO-NOT-EMIT",
5924            "FAKE-API-SERVER-KEY-DO-NOT-EMIT",
5925            "FAKE-SLACK-BOT-TOKEN-DO-NOT-EMIT",
5926            "FAKE-SLACK-APP-TOKEN-DO-NOT-EMIT",
5927            "FAKE-TELEGRAM-TOKEN-DO-NOT-EMIT",
5928        ];
5929        // The strings really are in the fixtures, so this test can fail.
5930        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
5931        let raw = format!(
5932            "{}{}",
5933            std::fs::read_to_string(fixtures.join("hermes_home/config.yaml")).unwrap(),
5934            std::fs::read_to_string(fixtures.join("openclaw_home/openclaw.json")).unwrap(),
5935        );
5936        for secret in secrets {
5937            assert!(raw.contains(secret), "fixture no longer holds `{secret}`");
5938        }
5939
5940        let emitted = serde_json::to_string(&channels_list(None)["result"]).unwrap();
5941        for secret in secrets {
5942            assert!(
5943                !emitted.contains(secret),
5944                "`{secret}` leaked into a channel row: {emitted}"
5945            );
5946        }
5947        // Belt and braces: no row FIELD is credential-shaped either, so a
5948        // future field cannot smuggle one past the literal scan.
5949        for row in channels_list(None)["result"]["channels"]
5950            .as_array()
5951            .unwrap()
5952        {
5953            for key in row.as_object().unwrap().keys() {
5954                let key = key.to_ascii_lowercase();
5955                assert!(
5956                    !["token", "key", "secret", "password", "credential"]
5957                        .iter()
5958                        .any(|marker| key.ends_with(marker)),
5959                    "`{key}` is a credential-shaped field on a channel row"
5960                );
5961            }
5962        }
5963    }
5964
5965    /// `status` answers one row by name, and refuses an unknown one.
5966    #[test]
5967    fn channels_status_reads_one_row_by_name() {
5968        let mut service = HarnessSessionService::new();
5969        let got = service.handle(request(
5970            1,
5971            "harness.v1.channels.status",
5972            json!({
5973                "harness": "openclaw",
5974                "name": "slack/T0FIXTURE",
5975                "homes": profile_fixture_homes(),
5976            }),
5977        ));
5978        assert_eq!(got["result"]["channel"]["kind"], "slack");
5979        assert_eq!(got["result"]["channel"]["account"], "T0FIXTURE");
5980        assert_eq!(got["result"]["channel"]["status"], "unknown");
5981
5982        let missing = service.handle(request(
5983            2,
5984            "harness.v1.channels.status",
5985            json!({
5986                "harness": "openclaw",
5987                "name": "no-such-channel",
5988                "homes": profile_fixture_homes(),
5989            }),
5990        ));
5991        assert_eq!(missing["error"]["code"], -32602, "{missing}");
5992    }
5993
5994    /// A harness with no channel concept fails with `UnsupportedAction`,
5995    /// never a silent empty list — Claude Code included, because its channels
5996    /// are MCP-protocol declarations no config file names.
5997    #[test]
5998    fn channels_refuse_harnesses_without_the_concept() {
5999        let response = channels_list(Some("claude-code"));
6000        assert_eq!(response["error"]["code"], -32020, "{response}");
6001        let codex = channels_list(Some("codex"));
6002        assert_eq!(codex["error"]["code"], -32020, "{codex}");
6003    }
6004
6005    /// The harness filter restricts the rows rather than being ignored.
6006    #[test]
6007    fn channels_list_filters_by_harness() {
6008        let response = channels_list(Some("openclaw"));
6009        let rows = response["result"]["channels"].as_array().unwrap();
6010        assert!(!rows.is_empty(), "{response}");
6011        assert!(
6012            rows.iter().all(|row| row["harness"] == "openclaw"),
6013            "harness filter leaked: {response}"
6014        );
6015    }
6016
6017    /// Both methods are advertised, so a client discovers them from
6018    /// `harness.v1.capabilities` rather than from documentation.
6019    #[test]
6020    fn channels_methods_are_advertised() {
6021        let mut service = HarnessSessionService::new();
6022        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
6023        let methods = response["result"]["methods"].as_array().unwrap();
6024        for method in ["harness.v1.channels.list", "harness.v1.channels.status"] {
6025            assert!(
6026                methods.iter().any(|entry| entry == method),
6027                "{method} is not advertised"
6028            );
6029        }
6030    }
6031
6032    /// The UI story renders REAL rows: this writes the discovery response the
6033    /// two assertions above pin into the fixture the Storybook
6034    /// `Compositions/Universal nouns` stories import, and fails when the
6035    /// committed copy has drifted from what the service now answers.
6036    #[test]
6037    fn orch6_story_fixture_matches_the_live_discovery_response() {
6038        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6039            .join("../../sdk/ui/stories/fixtures/hermes-discovery.json");
6040        let mut result = hermes_discovery(hermes_query());
6041        // `updated_at_ms` is derived from the fixture's own stored timestamps,
6042        // so the whole response is deterministic; drop only the cursor, which
6043        // is pagination state rather than a session fact.
6044        result.as_object_mut().unwrap().remove("next_cursor");
6045        let rendered = format!("{}\n", serde_json::to_string_pretty(&result).unwrap());
6046        if std::env::var_os("SUPERCODE_UPDATE_FIXTURES").is_some() {
6047            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
6048            std::fs::write(&path, &rendered).unwrap();
6049        }
6050        let committed = std::fs::read_to_string(&path).unwrap_or_default();
6051        assert_eq!(
6052            committed, rendered,
6053            "sdk/ui/stories/fixtures/hermes-discovery.json is stale — \
6054             re-run with SUPERCODE_UPDATE_FIXTURES=1"
6055        );
6056    }
6057
6058    fn pi_locator() -> SessionLocator {
6059        SessionLocator {
6060            harness: HarnessId::from(HarnessId::PI),
6061            session_id: "1e6f2a3b-0000-4000-8000-000000000001".into(),
6062            storage: StorageLocator::File {
6063                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6064                    .join("tests/fixtures/pi_session.jsonl"),
6065            },
6066        }
6067    }
6068
6069    fn opencode_locator() -> SessionLocator {
6070        let session_id = "ses_fixtureAAAAAAAAAAAAAAA1";
6071        SessionLocator {
6072            harness: HarnessId::from(HarnessId::OPENCODE),
6073            session_id: session_id.into(),
6074            storage: StorageLocator::Sqlite {
6075                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6076                    .join("tests/fixtures/opencode_fixture/opencode.db"),
6077                selector: session_id.into(),
6078            },
6079        }
6080    }
6081
6082    fn grok_locator() -> SessionLocator {
6083        SessionLocator {
6084            harness: HarnessId::from(HarnessId::GROK),
6085            session_id: "73c09283-4b33-41fa-90f1-0bcb0f7be523".into(),
6086            storage: StorageLocator::File {
6087                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6088                    .join("tests/fixtures/grok_session/chat_history.jsonl"),
6089            },
6090        }
6091    }
6092
6093    // ---- ORCH-11: `harness.v1.skills.list` -------------------------------
6094
6095    fn fixture_homes() -> Value {
6096        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6097        json!({
6098            "claude_code": fixtures.join("__absent__"),
6099            "codex": fixtures.join("__absent__"),
6100            "opencode": fixtures.join("__absent__"),
6101            "pi": fixtures.join("__absent__"),
6102            "agents": fixtures.join("__absent__"),
6103            "hermes": fixtures.join("hermes_home"),
6104            "openclaw": fixtures.join("openclaw_home"),
6105        })
6106    }
6107
6108    #[test]
6109    fn preview_search_uses_the_discovery_rpc_and_refuses_live_subscription() {
6110        let root = std::env::temp_dir().join(format!(
6111            "supercode-preview-rpc-{}-{}",
6112            std::process::id(),
6113            std::time::SystemTime::now()
6114                .duration_since(std::time::UNIX_EPOCH)
6115                .unwrap()
6116                .as_nanos()
6117        ));
6118        std::fs::create_dir_all(&root).unwrap();
6119        for id in ["first", "second"] {
6120            std::fs::write(root.join(format!("{id}.jsonl")), format!("{}\n{}\n",
6121                json!({"type": "session_meta", "payload": {"id": id, "cwd": "/workspace"}}),
6122                json!({"type": "event_msg", "payload": {"type": "agent_message", "message": "NEBULA result"}}),
6123            )).unwrap();
6124        }
6125        let mut service = HarnessSessionService::new();
6126        let query = json!({
6127            "harnesses": ["codex"], "homes": {"codex": root},
6128            "query": "nebula", "search_previews": true, "limit": 1
6129        });
6130        let first = service.handle(request(1, "harness.v1.sessions.discover", query.clone()));
6131        assert!(first.get("error").is_none(), "{first}");
6132        assert_eq!(first["result"]["receipt"]["searched_previews"], true);
6133        assert_eq!(first["result"]["receipt"]["total_matched"], 2);
6134        let mut next_query = query.clone();
6135        next_query["cursor"] = first["result"]["next_cursor"].clone();
6136        let next = service.handle(request(2, "harness.v1.sessions.discover", next_query));
6137        assert_eq!(next["result"]["receipt"]["returned"], 1);
6138        assert_eq!(next["result"]["receipt"]["total_matched"], 2);
6139        assert_eq!(next["result"]["receipt"]["truncated"], false);
6140        assert_ne!(
6141            first["result"]["sessions"][0]["locator"],
6142            next["result"]["sessions"][0]["locator"]
6143        );
6144        let refused = service.handle(request(3, "harness.v1.sessions.index.subscribe", query));
6145        assert!(
6146            refused["error"]["message"]
6147                .as_str()
6148                .unwrap()
6149                .contains("use sessions.discover"),
6150            "{refused}"
6151        );
6152        std::fs::remove_dir_all(root).unwrap();
6153    }
6154
6155    #[test]
6156    fn session_index_resize_preserves_subscription_and_rejects_invalid_requests() {
6157        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.sessions.index.resize"));
6158        let root = std::env::temp_dir().join(format!(
6159            "supercode-index-rpc-{}-{}",
6160            std::process::id(),
6161            std::time::SystemTime::now()
6162                .duration_since(std::time::UNIX_EPOCH)
6163                .unwrap()
6164                .as_nanos()
6165        ));
6166        std::fs::create_dir_all(&root).unwrap();
6167        for id in ["first", "second"] {
6168            std::fs::write(root.join(format!("{id}.jsonl")), format!(
6169                "{{\"type\":\"session_meta\",\"payload\":{{\"id\":\"{id}\",\"cwd\":\"/workspace\"}}}}\n"
6170            )).unwrap();
6171        }
6172        let mut service = HarnessSessionService::new();
6173        let opened = service.handle(request(
6174            1,
6175            "harness.v1.sessions.index.subscribe",
6176            json!({
6177                "harnesses": ["codex"], "homes": { "codex": root }, "limit": 1
6178            }),
6179        ));
6180        assert!(opened.get("error").is_none(), "{opened:#}");
6181        let subscription = opened["result"]["subscription"]
6182            .as_str()
6183            .unwrap()
6184            .to_owned();
6185        assert_eq!(opened["result"]["initial"].as_array().unwrap().len(), 1);
6186        for params in [
6187            json!({"subscription": subscription, "limit": 0}),
6188            json!({"subscription": subscription, "limit": 2049}),
6189            json!({"subscription": subscription, "limit": 2, "cursor": "not-allowed"}),
6190            json!({"subscription": "unknown", "limit": 2}),
6191        ] {
6192            let rejected = service.handle(request(2, "harness.v1.sessions.index.resize", params));
6193            assert_eq!(rejected["error"]["code"], -32602, "{rejected:#}");
6194        }
6195        for (limit, revision) in [(1, 1), (2, 2), (2, 2), (1, 3)] {
6196            let response = service.handle(request(
6197                3,
6198                "harness.v1.sessions.index.resize",
6199                json!({
6200                    "subscription": subscription, "limit": limit
6201                }),
6202            ));
6203            assert!(response.get("error").is_none(), "{response:#}");
6204            assert_eq!(response["result"]["subscription"], subscription);
6205            assert_eq!(response["result"]["revision"], revision);
6206            assert_eq!(
6207                response["result"]["initial"].as_array().unwrap().len(),
6208                limit
6209            );
6210            assert_eq!(response["result"]["receipt"]["total_matched"], 2);
6211            assert_eq!(service.index_subscriptions.len(), 1);
6212        }
6213        let removed = service.handle(request(
6214            4,
6215            "harness.v1.sessions.index.unsubscribe",
6216            json!({
6217                "subscription": subscription
6218            }),
6219        ));
6220        assert_eq!(removed["result"]["removed"], true);
6221        let stale = service.handle(request(
6222            5,
6223            "harness.v1.sessions.index.resize",
6224            json!({
6225                "subscription": subscription, "limit": 1
6226            }),
6227        ));
6228        assert_eq!(stale["error"]["code"], -32602);
6229        drop(service);
6230        std::fs::remove_dir_all(root).unwrap();
6231    }
6232
6233    fn skills_rows(params: Value) -> Vec<Value> {
6234        let response =
6235            HarnessSessionService::new().handle(request(1, "harness.v1.skills.list", params));
6236        assert!(response.get("error").is_none(), "{response:#}");
6237        response["result"].as_array().cloned().unwrap_or_default()
6238    }
6239
6240    /// The uniform row over two harnesses at once, from the harnesses' own
6241    /// skill roots: name, harness, scope, location, description, version.
6242    #[test]
6243    fn skills_list_reads_the_hermes_and_openclaw_roots() {
6244        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6245        let rows = skills_rows(json!({
6246            "homes": fixture_homes(),
6247            "cwd": fixtures.join("hermes_home"),
6248        }));
6249        let arxiv = rows
6250            .iter()
6251            .find(|row| row["name"] == json!("arxiv-search"))
6252            .unwrap_or_else(|| panic!("no arxiv row in {rows:#?}"));
6253        assert_eq!(arxiv["harness"], json!(HarnessId::HERMES));
6254        assert_eq!(arxiv["scope"], json!("user"));
6255        assert_eq!(arxiv["version"], json!("1.4.0"));
6256        assert!(arxiv["location"]
6257            .as_str()
6258            .unwrap()
6259            .ends_with("hermes_home/skills/research/arxiv"));
6260
6261        // A directory with no SKILL.md still lists, by directory name.
6262        let bare = rows
6263            .iter()
6264            .find(|row| row["name"] == json!("bare-skill"))
6265            .unwrap_or_else(|| panic!("no bare-skill row in {rows:#?}"));
6266        assert_eq!(bare["enabled"], json!(null));
6267        assert!(bare.get("description").is_none());
6268
6269        let demo = rows
6270            .iter()
6271            .find(|row| row["name"] == json!("clawhub-demo"))
6272            .unwrap_or_else(|| panic!("no clawhub-demo row in {rows:#?}"));
6273        assert_eq!(demo["harness"], json!(HarnessId::OPENCLAW));
6274        assert_eq!(demo["scope"], json!("managed"));
6275        assert_eq!(demo["enabled"], json!(false));
6276    }
6277
6278    /// Both filters select against the same rows.
6279    #[test]
6280    fn skills_list_filters_by_harness_and_scope() {
6281        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6282        let hermes = skills_rows(json!({
6283            "homes": fixture_homes(),
6284            "cwd": fixtures.join("hermes_home"),
6285            "harness": HarnessId::HERMES,
6286        }));
6287        assert!(!hermes.is_empty());
6288        assert!(hermes
6289            .iter()
6290            .all(|row| row["harness"] == json!(HarnessId::HERMES)));
6291
6292        let managed = skills_rows(json!({
6293            "homes": fixture_homes(),
6294            "cwd": fixtures.join("openclaw_home"),
6295            "harness": HarnessId::OPENCLAW,
6296            "scope": "managed",
6297        }));
6298        assert_eq!(managed.len(), 1, "{managed:#?}");
6299        assert_eq!(managed[0]["name"], json!("clawhub-demo"));
6300
6301        let bundled = skills_rows(json!({
6302            "homes": fixture_homes(),
6303            "cwd": fixtures.join("openclaw_home"),
6304            "harness": HarnessId::OPENCLAW,
6305            "scope": "bundled",
6306        }));
6307        assert!(bundled.is_empty(), "{bundled:#?}");
6308    }
6309
6310    /// A harness supercode has no skills root for is refused by name, not
6311    /// answered with an empty list.
6312    #[test]
6313    fn skills_list_refuses_an_unknown_harness() {
6314        let response = HarnessSessionService::new().handle(request(
6315            1,
6316            "harness.v1.skills.list",
6317            json!({"harness": "not-a-harness", "homes": fixture_homes()}),
6318        ));
6319        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6320        assert!(response["error"]["message"]
6321            .as_str()
6322            .unwrap()
6323            .contains("not-a-harness"));
6324    }
6325
6326    /// The method is advertised, and its SDK operation resolves it.
6327    #[test]
6328    fn skills_list_is_an_advertised_method_and_sdk_operation() {
6329        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.list"));
6330        assert_eq!(
6331            SdkOperation::from_method("harness.v1.skills.list"),
6332            Some(SdkOperation::SkillsList)
6333        );
6334    }
6335
6336    // ---- ORCH-22: `harness.v1.skills.install|remove` ----------------------
6337
6338    /// Both controlled verbs are advertised and resolve to their operation.
6339    #[test]
6340    fn skills_install_and_remove_are_advertised_methods_and_sdk_operations() {
6341        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.install"));
6342        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.remove"));
6343        assert_eq!(
6344            SdkOperation::from_method("harness.v1.skills.install"),
6345            Some(SdkOperation::SkillsInstall)
6346        );
6347        assert_eq!(
6348            SdkOperation::from_method("harness.v1.skills.remove"),
6349            Some(SdkOperation::SkillsRemove)
6350        );
6351    }
6352
6353    /// The directory door, end to end over the RPC: a local package lands in
6354    /// Claude Code's own user root and the outcome carries the operation and
6355    /// the row the ORCH-11 loader reads back.
6356    #[test]
6357    fn skills_install_and_remove_drive_the_directory_door() {
6358        let root = std::env::temp_dir().join(format!(
6359            "supercode-orch22-rpc-{}-{}",
6360            std::process::id(),
6361            std::time::SystemTime::now()
6362                .duration_since(std::time::UNIX_EPOCH)
6363                .unwrap()
6364                .as_nanos()
6365        ));
6366        let source = root.join("probe-src");
6367        std::fs::create_dir_all(&source).unwrap();
6368        std::fs::write(
6369            source.join("SKILL.md"),
6370            "---\nname: orch22-rpc\ndescription: a probe\n---\nbody\n",
6371        )
6372        .unwrap();
6373        let homes = json!({
6374            "claude_code": root.join("claude_home"),
6375            "codex": root.join("__absent__"),
6376            "opencode": root.join("__absent__"),
6377            "pi": root.join("__absent__"),
6378            "hermes": root.join("__absent__"),
6379            "openclaw": root.join("__absent__"),
6380            "agents": root.join("__absent__"),
6381        });
6382
6383        let mut service = HarnessSessionService::new();
6384        let installed = service.handle(request(
6385            1,
6386            "harness.v1.skills.install",
6387            json!({
6388                "harness": HarnessId::CLAUDE_CODE,
6389                "source": source,
6390                "scope": "user",
6391                "cwd": root,
6392                "homes": homes,
6393            }),
6394        ));
6395        let result = &installed["result"];
6396        assert_eq!(result["name"], json!("orch22-rpc"), "{installed:#}");
6397        assert_eq!(result["verb"], json!("install"));
6398        assert!(result["ran"]
6399            .as_str()
6400            .is_some_and(|ran| ran.starts_with("cp -R ")));
6401        assert_eq!(result["skill"]["scope"], json!("user"));
6402
6403        let removed = service.handle(request(
6404            2,
6405            "harness.v1.skills.remove",
6406            json!({
6407                "harness": HarnessId::CLAUDE_CODE,
6408                "name": "orch22-rpc",
6409                "scope": "user",
6410                "cwd": root,
6411                "homes": homes,
6412            }),
6413        ));
6414        assert_eq!(removed["result"]["removed"], json!(true), "{removed:#}");
6415        assert!(!root.join("claude_home/skills/orch22-rpc").exists());
6416        std::fs::remove_dir_all(&root).ok();
6417    }
6418
6419    /// OpenClaw publishes no `skills remove` at the pin, so the uniform verb
6420    /// refuses with UnsupportedAction instead of deleting files itself.
6421    #[test]
6422    fn skills_remove_refuses_openclaw_at_the_pin() {
6423        let response = HarnessSessionService::new().handle(request(
6424            1,
6425            "harness.v1.skills.remove",
6426            json!({"harness": HarnessId::OPENCLAW, "name": "clawhub-demo"}),
6427        ));
6428        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6429        assert!(response["error"]["message"]
6430            .as_str()
6431            .unwrap()
6432            .contains("no `skills remove` verb"));
6433    }
6434
6435    /// A harness with no skills root at all is refused by name, with the
6436    /// same sentence `skills.list` gives it.
6437    #[test]
6438    fn skills_install_refuses_a_harness_without_a_skills_root() {
6439        let response = HarnessSessionService::new().handle(request(
6440            1,
6441            "harness.v1.skills.install",
6442            json!({"harness": "not-a-harness", "source": "/tmp/x"}),
6443        ));
6444        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6445        assert!(response["error"]["message"]
6446            .as_str()
6447            .unwrap()
6448            .contains("not-a-harness"));
6449    }
6450
6451    // ---- ORCH-12: `harness.v1.memory.show|search` ------------------------
6452
6453    /// `HarnessHomes` for the committed fixture homes. Every root a test does
6454    /// not name is pinned at an absent path, so a read can never fall through
6455    /// to this machine's real harness homes. Note `hermes` is the `state.db`
6456    /// PATH (its parent is HERMES_HOME) and `claude_code` is the `projects`
6457    /// directory — the same contract discovery uses.
6458    fn memory_homes() -> Value {
6459        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6460        json!({
6461            "claude_code": fixtures.join("__absent__"),
6462            "codex": fixtures.join("__absent__"),
6463            "opencode": fixtures.join("__absent__"),
6464            "pi": fixtures.join("__absent__"),
6465            "grok": fixtures.join("__absent__"),
6466            "gemini": fixtures.join("__absent__"),
6467            "goose": fixtures.join("__absent__"),
6468            "supercode": fixtures.join("__absent__"),
6469            "hermes": fixtures.join("hermes_home/state.db"),
6470            "openclaw": fixtures.join("openclaw_home"),
6471        })
6472    }
6473
6474    fn memory_call_ok(method: &str, params: Value, key: &str) -> Vec<Value> {
6475        let response = HarnessSessionService::new().handle(request(1, method, params));
6476        assert!(response.get("error").is_none(), "{response:#}");
6477        assert_eq!(response["result"]["schema"], json!("supercode.memory.v1"));
6478        response["result"][key]
6479            .as_array()
6480            .cloned()
6481            .unwrap_or_default()
6482    }
6483
6484    fn memory_documents(params: Value) -> Vec<Value> {
6485        memory_call_ok("harness.v1.memory.show", params, "documents")
6486    }
6487
6488    fn memory_matches(params: Value) -> Vec<Value> {
6489        memory_call_ok("harness.v1.memory.search", params, "matches")
6490    }
6491
6492    fn find_document<'a>(rows: &'a [Value], profile: &str, name: &str) -> &'a Value {
6493        rows.iter()
6494            .find(|row| row["profile"] == profile && row["name"] == name)
6495            .unwrap_or_else(|| panic!("no `{profile}` document `{name}` in {rows:#?}"))
6496    }
6497
6498    /// Hermes: the built-in `MEMORY.md`/`USER.md` pair and the `memories/`
6499    /// topic files, for HERMES_HOME itself and for every profile home.
6500    #[test]
6501    fn memory_show_reads_the_hermes_profile_homes() {
6502        let rows = memory_documents(json!({"harness": "hermes", "homes": memory_homes()}));
6503
6504        let notes = find_document(&rows, "default", "MEMORY.md");
6505        assert_eq!(notes["harness"], "hermes");
6506        assert_eq!(notes["scope"], "user");
6507        assert!(notes["size"].as_u64().unwrap() > 0);
6508        assert!(notes["updated_at"].is_string(), "{notes:#?}");
6509        // The default answer previews the head and never the whole body.
6510        assert!(notes.get("content").is_none(), "{notes:#?}");
6511        assert_eq!(notes["truncated"], true);
6512        assert_eq!(notes["preview"].as_array().unwrap().len(), 5);
6513
6514        let user = find_document(&rows, "default", "USER.md");
6515        assert_eq!(user["scope"], "user");
6516        assert!(user["preview"]
6517            .as_array()
6518            .unwrap()
6519            .iter()
6520            .any(|line| line.as_str().unwrap().contains("neovim")));
6521
6522        let topic = find_document(&rows, "default", "memories/2026-09-01-notes.md");
6523        assert!(topic["path"]
6524            .as_str()
6525            .unwrap()
6526            .ends_with("hermes_home/memories/2026-09-01-notes.md"));
6527
6528        // Profile mode points HERMES_HOME at `<root>/profiles/<name>`.
6529        let coder = find_document(&rows, "coder", "MEMORY.md");
6530        assert_eq!(coder["scope"], "profile");
6531        assert!(coder["path"]
6532            .as_str()
6533            .unwrap()
6534            .ends_with("hermes_home/profiles/coder/MEMORY.md"));
6535    }
6536
6537    /// `full` is the only way a body crosses the wire, and `profile` narrows
6538    /// the read to one home.
6539    #[test]
6540    fn memory_show_returns_bodies_only_under_full_and_narrows_by_profile() {
6541        let rows = memory_documents(json!({
6542            "harness": "hermes",
6543            "profile": "coder",
6544            "full": true,
6545            "homes": memory_homes(),
6546        }));
6547        assert!(
6548            rows.iter().all(|row| row["profile"] == "coder"),
6549            "{rows:#?}"
6550        );
6551        let coder = find_document(&rows, "coder", "MEMORY.md");
6552        assert!(coder["content"]
6553            .as_str()
6554            .expect("full returns the body")
6555            .contains("anthropic/claude-opus-4-8"));
6556    }
6557
6558    /// OpenClaw: memory-core's files under each agent's workspace —
6559    /// `<state>/workspace` for the default agent, `<state>/workspace-<id>`
6560    /// for any other.
6561    #[test]
6562    fn memory_show_reads_the_openclaw_agent_workspaces() {
6563        let rows = memory_documents(json!({"harness": "openclaw", "homes": memory_homes()}));
6564
6565        let main = find_document(&rows, "main", "MEMORY.md");
6566        assert_eq!(main["scope"], "agent");
6567        assert!(main["path"]
6568            .as_str()
6569            .unwrap()
6570            .ends_with("openclaw_home/workspace/MEMORY.md"));
6571
6572        let topic = find_document(&rows, "main", "memory/2026-09-01-standup.md");
6573        assert!(topic["path"]
6574            .as_str()
6575            .unwrap()
6576            .ends_with("openclaw_home/workspace/memory/2026-09-01-standup.md"));
6577
6578        let design = find_document(&rows, "design", "MEMORY.md");
6579        assert!(design["path"]
6580            .as_str()
6581            .unwrap()
6582            .ends_with("openclaw_home/workspace-design/MEMORY.md"));
6583    }
6584
6585    /// Claude Code: the auto-memory directory of the project the working tree
6586    /// belongs to, keyed by the enclosing git repository.
6587    #[test]
6588    fn memory_show_reads_a_claude_code_project_auto_memory_directory() {
6589        let scratch = std::env::temp_dir().join(format!(
6590            "supercode-orch12-cc-{}-{}",
6591            std::process::id(),
6592            std::time::SystemTime::now()
6593                .duration_since(std::time::UNIX_EPOCH)
6594                .unwrap()
6595                .as_nanos()
6596        ));
6597        let project = scratch.join("repo");
6598        std::fs::create_dir_all(project.join(".git")).unwrap();
6599        // Auto-memory is shared across a repo's worktrees, so a nested
6600        // working directory must resolve to the repo's own project dir.
6601        let worktree = project.join("crates/harness");
6602        std::fs::create_dir_all(&worktree).unwrap();
6603        let slug: String = project
6604            .to_string_lossy()
6605            .chars()
6606            .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
6607            .collect();
6608        let projects = scratch.join("claude/projects");
6609        let memory = projects.join(&slug).join("memory");
6610        std::fs::create_dir_all(&memory).unwrap();
6611        std::fs::write(
6612            memory.join("MEMORY.md"),
6613            "# index\n- [build box](build-box.md) — the pinned harnesses\n",
6614        )
6615        .unwrap();
6616        std::fs::write(
6617            memory.join("build-box.md"),
6618            "hermes 0.21.0 and openclaw 2026.7.1-2 are the pins\n",
6619        )
6620        .unwrap();
6621
6622        let mut homes = memory_homes();
6623        homes["claude_code"] = json!(projects);
6624        let rows = memory_documents(json!({
6625            "harness": "claude-code",
6626            "cwd": worktree,
6627            "homes": homes,
6628        }));
6629        let index = find_document(&rows, &slug, "MEMORY.md");
6630        assert_eq!(index["harness"], "claude-code");
6631        assert_eq!(index["scope"], "project");
6632        let topic = find_document(&rows, &slug, "build-box.md");
6633        assert!(topic["preview"]
6634            .as_array()
6635            .unwrap()
6636            .iter()
6637            .any(|line| line.as_str().unwrap().contains("2026.7.1-2")));
6638
6639        let hits = memory_matches(json!({
6640            "harness": "claude-code",
6641            "query": "pinned harnesses",
6642            "cwd": worktree,
6643            "homes": homes,
6644        }));
6645        assert_eq!(hits.len(), 1, "{hits:#?}");
6646        assert_eq!(hits[0]["name"], "MEMORY.md");
6647        assert_eq!(hits[0]["line"], 2);
6648
6649        let _ = std::fs::remove_dir_all(&scratch);
6650    }
6651
6652    /// A config-less OpenClaw install declares no default agent, but
6653    /// memory-core still resolves ONE agent to the default `workspace`
6654    /// directory — the same `main`-then-first convention the profile rows
6655    /// use. Measured against `openclaw memory status` on the pinned CLI
6656    /// (`docs/interop/research/orch12-memory-receipt-2026-09-03.json`).
6657    #[test]
6658    fn memory_show_resolves_the_default_workspace_without_an_openclaw_config() {
6659        let state = std::env::temp_dir().join(format!(
6660            "supercode-orch12-oc-{}-{}",
6661            std::process::id(),
6662            std::time::SystemTime::now()
6663                .duration_since(std::time::UNIX_EPOCH)
6664                .unwrap()
6665                .as_nanos()
6666        ));
6667        // No `openclaw.json`: only the agent home the gateway creates.
6668        std::fs::create_dir_all(state.join("agents/main/agent")).unwrap();
6669        std::fs::create_dir_all(state.join("workspace")).unwrap();
6670        std::fs::write(
6671            state.join("workspace/MEMORY.md"),
6672            "the gateway websocket needs credentials\n",
6673        )
6674        .unwrap();
6675
6676        let mut homes = memory_homes();
6677        homes["openclaw"] = json!(state);
6678        let rows = memory_documents(json!({"harness": "openclaw", "homes": homes}));
6679        assert_eq!(rows.len(), 1, "{rows:#?}");
6680        let row = find_document(&rows, "main", "MEMORY.md");
6681        assert_eq!(row["scope"], "agent");
6682        assert!(row["path"]
6683            .as_str()
6684            .unwrap()
6685            .ends_with("workspace/MEMORY.md"));
6686
6687        let _ = std::fs::remove_dir_all(&state);
6688    }
6689
6690    /// Search is a plain scan over the same documents: a hit carries the
6691    /// path, line and excerpt; a miss is an empty list, not an error.
6692    #[test]
6693    fn memory_search_reports_hits_by_line_and_misses_as_empty() {
6694        let hit = memory_matches(json!({
6695            "harness": "hermes",
6696            "query": "NEOVIM",
6697            "homes": memory_homes(),
6698        }));
6699        assert_eq!(hit.len(), 1, "{hit:#?}");
6700        assert_eq!(hit[0]["harness"], "hermes");
6701        assert_eq!(hit[0]["name"], "USER.md");
6702        assert_eq!(hit[0]["scope"], "user");
6703        assert_eq!(hit[0]["line"], 5);
6704        assert!(hit[0]["excerpt"].as_str().unwrap().contains("neovim"));
6705
6706        // A regular expression reaches the same lines.
6707        let regex = memory_matches(json!({
6708            "harness": "hermes",
6709            "query": "neo(vim|vi)",
6710            "regex": true,
6711            "homes": memory_homes(),
6712        }));
6713        assert_eq!(regex.len(), 1, "{regex:#?}");
6714
6715        let miss = memory_matches(json!({
6716            "harness": "hermes",
6717            "query": "no-memory-line-says-this",
6718            "homes": memory_homes(),
6719        }));
6720        assert!(miss.is_empty(), "{miss:#?}");
6721    }
6722
6723    /// The uniform-verb contract: a harness with no memory store at the pin
6724    /// is refused by name, and `session` only selects a Claude Code project.
6725    #[test]
6726    fn memory_refuses_harnesses_without_a_store_and_misplaced_session_scoping() {
6727        for method in ["harness.v1.memory.show", "harness.v1.memory.search"] {
6728            let response = HarnessSessionService::new().handle(request(
6729                1,
6730                method,
6731                json!({"harness": "codex", "query": "anything", "homes": memory_homes()}),
6732            ));
6733            assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6734            assert!(response["error"]["message"]
6735                .as_str()
6736                .unwrap()
6737                .contains("codex"));
6738        }
6739
6740        let response = HarnessSessionService::new().handle(request(
6741            1,
6742            "harness.v1.memory.show",
6743            json!({"harness": "hermes", "session": "abc", "homes": memory_homes()}),
6744        ));
6745        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6746
6747        // `harness` is not optional: memory documents are the user's prose.
6748        let response = HarnessSessionService::new().handle(request(
6749            1,
6750            "harness.v1.memory.show",
6751            json!({"homes": memory_homes()}),
6752        ));
6753        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
6754    }
6755
6756    /// Both methods are advertised, and their SDK operations resolve them.
6757    #[test]
6758    fn memory_methods_are_advertised_and_map_to_sdk_operations() {
6759        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.show"));
6760        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.search"));
6761        assert_eq!(
6762            SdkOperation::from_method("harness.v1.memory.show"),
6763            Some(SdkOperation::MemoryShow)
6764        );
6765        assert_eq!(
6766            SdkOperation::from_method("harness.v1.memory.search"),
6767            Some(SdkOperation::MemorySearch)
6768        );
6769    }
6770
6771    // ---- ORCH-9: `harness.v1.approvals.list` -----------------------------
6772
6773    /// A runtime that raises one protocol request and then goes quiet, so a
6774    /// single poll delivers the request without closing the connection.
6775    struct RequestingRuntime {
6776        handle: RuntimeHandle,
6777        events: std::collections::VecDeque<HarnessEvent>,
6778        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
6779    }
6780
6781    #[async_trait]
6782    impl RuntimeConnection for RequestingRuntime {
6783        fn handle(&self) -> &RuntimeHandle {
6784            &self.handle
6785        }
6786
6787        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
6788            unreachable!("this runtime only raises requests")
6789        }
6790
6791        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
6792            match self.events.pop_front() {
6793                Some(event) => Ok(Some(event)),
6794                // Quiet, not closed: `poll_sdk_events` times out and leaves
6795                // the connection open, the way a runtime blocked on a
6796                // permission request behaves.
6797                None => std::future::pending().await,
6798            }
6799        }
6800
6801        async fn interrupt(&mut self) -> crate::Result<()> {
6802            Ok(())
6803        }
6804
6805        async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
6806            // Both halves are recorded: ORCH-20 has to prove not just that the
6807            // right request was answered but that the door received its own
6808            // reply envelope.
6809            self.answered
6810                .lock()
6811                .unwrap_or_else(std::sync::PoisonError::into_inner)
6812                .push(json!({"request_id": request_id, "response": response}));
6813            Ok(())
6814        }
6815
6816        async fn close(&mut self) -> crate::Result<()> {
6817            Ok(())
6818        }
6819    }
6820
6821    fn requesting_runtime(
6822        harness: &str,
6823        events: Vec<HarnessEvent>,
6824        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
6825    ) -> Box<dyn RuntimeConnection> {
6826        requesting_runtime_named(harness, "hermes-live-session", events, answered)
6827    }
6828
6829    fn requesting_runtime_named(
6830        harness: &str,
6831        runtime_id: &str,
6832        events: Vec<HarnessEvent>,
6833        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
6834    ) -> Box<dyn RuntimeConnection> {
6835        Box::new(RequestingRuntime {
6836            handle: RuntimeHandle {
6837                harness: HarnessId::from(harness),
6838                runtime_id: runtime_id.into(),
6839                endpoint: RuntimeEndpoint::LocalProcess {
6840                    pid: None,
6841                    command: vec!["hermes-acp".into()],
6842                    protocol: "acp".into(),
6843                },
6844            },
6845            events: events.into(),
6846            answered,
6847        })
6848    }
6849
6850    fn permission_event(id: u64, title: &str) -> HarnessEvent {
6851        HarnessEvent {
6852            sequence: None,
6853            kind: "session/request_permission".into(),
6854            payload: json!({
6855                "jsonrpc": "2.0",
6856                "id": id,
6857                "method": "session/request_permission",
6858                "params": {
6859                    "sessionId": "hermes-live-session",
6860                    "toolCall": {"toolCallId": "call-1", "title": title, "kind": "execute"},
6861                    "options": [
6862                        {"optionId": "allow_once", "name": "Allow once", "kind": "allow_once"},
6863                        {"optionId": "allow_for_session", "name": "Allow for session", "kind": "allow_always"},
6864                        {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
6865                    ],
6866                },
6867            }),
6868        }
6869    }
6870
6871    fn approvals(service: &mut HarnessSessionService, params: Value) -> Value {
6872        let response = service.handle(request(1, "harness.v1.approvals.list", params));
6873        assert!(response.get("error").is_none(), "{response:#}");
6874        response["result"].clone()
6875    }
6876
6877    /// ORC-2 dev/01: the same uniform loop over the CLAUDE CODE door. The
6878    /// `can_use_tool` control request the CLI raises to its registered
6879    /// permission handler lists as one pending row, `approvals.resolve <id>
6880    /// allow_once` sends the `{behavior}` result the CLI accepts through
6881    /// `runtimes.respond`, and the row is gone. The frame is the one claude
6882    /// 2.1.258 wrote, transcribed from
6883    /// `docs/interop/research/orc2-claude-respond-receipt-2026-09-04.json`.
6884    #[tokio::test]
6885    async fn a_claude_code_permission_request_lists_and_resolves_on_the_uniform_door() {
6886        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
6887        let mut service = HarnessSessionService::new();
6888        service.runtimes.insert(
6889            "runtime-cc".into(),
6890            requesting_runtime_named(
6891                HarnessId::CLAUDE_CODE,
6892                "claude-live-session",
6893                vec![HarnessEvent {
6894                    sequence: None,
6895                    kind: "control_request".into(),
6896                    payload: json!({
6897                        "type": "control_request",
6898                        "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
6899                        "request": {
6900                            "subtype": "can_use_tool",
6901                            "tool_name": "Bash",
6902                            "display_name": "Bash",
6903                            "input": {"command": "touch probe-artifact.txt"},
6904                            "tool_use_id": "toolu_mock_1",
6905                        },
6906                    }),
6907                }],
6908                answered.clone(),
6909            ),
6910        );
6911
6912        let notifications = service.poll_runtimes().await;
6913        assert_eq!(notifications.len(), 1, "{notifications:#?}");
6914
6915        let rows = approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}));
6916        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
6917        let row = &rows[0];
6918        assert_eq!(row["id"], "runtime-cc/053f8a2d-3445-4011-a259-4261b31c7326");
6919        assert_eq!(row["harness"], HarnessId::CLAUDE_CODE);
6920        assert_eq!(row["status"], "pending");
6921        assert_eq!(row["subject"], "Bash touch probe-artifact.txt");
6922        assert_eq!(row["runtime_id"], "claude-live-session");
6923        assert_eq!(
6924            row["options"]
6925                .as_array()
6926                .unwrap()
6927                .iter()
6928                .map(|option| option["id"].as_str().unwrap())
6929                .collect::<Vec<_>>(),
6930            vec!["allow", "deny"],
6931        );
6932
6933        let response = resolve(
6934            &mut service,
6935            json!({"id": row["id"], "decision": "allow_once"}),
6936        )
6937        .await;
6938        assert!(response.get("error").is_none(), "{response:#}");
6939        assert_eq!(response["result"]["option_id"], "allow");
6940        assert_eq!(
6941            answered
6942                .lock()
6943                .unwrap_or_else(std::sync::PoisonError::into_inner)
6944                .as_slice(),
6945            &[json!({
6946                "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
6947                "response": {"behavior": "allow"},
6948            })],
6949        );
6950        assert_eq!(
6951            approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}))
6952                .as_array()
6953                .map(Vec::len),
6954            Some(0),
6955        );
6956    }
6957
6958    /// dev/01: a live ACP permission request raised on a driven runtime is
6959    /// listable while the turn is blocked on it, and stops being listable
6960    /// the moment `runtimes.respond` answers it.
6961    #[tokio::test]
6962    async fn a_live_permission_request_lists_until_it_is_answered() {
6963        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
6964        let mut service = HarnessSessionService::new();
6965        service.runtimes.insert(
6966            "runtime-1".into(),
6967            requesting_runtime(
6968                HarnessId::HERMES,
6969                vec![permission_event(7, "rm -rf build")],
6970                answered.clone(),
6971            ),
6972        );
6973
6974        let notifications = service.poll_runtimes().await;
6975        assert_eq!(notifications.len(), 1, "{notifications:#?}");
6976
6977        let rows = approvals(&mut service, json!({}));
6978        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
6979        let row = &rows[0];
6980        assert_eq!(row["id"], "runtime-1/7");
6981        assert_eq!(row["harness"], HarnessId::HERMES);
6982        assert_eq!(row["kind"], "live");
6983        assert_eq!(row["status"], "pending");
6984        assert_eq!(row["subject"], "rm -rf build");
6985        assert_eq!(row["session_id"], "hermes-live-session");
6986        assert_eq!(row["runtime_id"], "hermes-live-session");
6987        assert!(row["requested_at_ms"].as_i64().is_some(), "{row:#}");
6988        assert!(
6989            row["age_ms"].as_i64().is_some_and(|age| age >= 0),
6990            "{row:#}"
6991        );
6992        assert_eq!(
6993            row["options"]
6994                .as_array()
6995                .unwrap()
6996                .iter()
6997                .map(|option| option["id"].as_str().unwrap())
6998                .collect::<Vec<_>>(),
6999            vec!["allow_once", "allow_for_session", "deny"],
7000        );
7001
7002        // The filters select against the same rows.
7003        assert_eq!(
7004            approvals(&mut service, json!({"harness": HarnessId::HERMES}))
7005                .as_array()
7006                .map(Vec::len),
7007            Some(1),
7008        );
7009        assert_eq!(
7010            approvals(&mut service, json!({"session": "some-other-session"}))
7011                .as_array()
7012                .map(Vec::len),
7013            Some(0),
7014        );
7015
7016        let response = service
7017            .handle_async(request(
7018                2,
7019                "harness.v1.runtimes.respond",
7020                json!({
7021                    "connection": "runtime-1",
7022                    "request_id": 7,
7023                    "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7024                }),
7025            ))
7026            .await;
7027        assert!(response.get("error").is_none(), "{response:#}");
7028        assert_eq!(
7029            answered
7030                .lock()
7031                .unwrap_or_else(std::sync::PoisonError::into_inner)
7032                .as_slice(),
7033            &[json!({
7034                "request_id": 7,
7035                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7036            })],
7037        );
7038
7039        let rows = approvals(&mut service, json!({}));
7040        assert_eq!(rows.as_array().map(Vec::len), Some(0), "{rows:#}");
7041    }
7042
7043    /// dev/01: supercode's own queued subagent approvals list through the
7044    /// same door, carrying the outcome the record holds.
7045    #[test]
7046    fn queued_subagent_approvals_list_through_the_same_door() {
7047        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
7048            crate::subagents::QueuedApproval {
7049                child_agent_id: "child-7".into(),
7050                tool: "shell".into(),
7051                subject: Some("cargo publish --dry-run".into()),
7052                queued_at_ms: 1,
7053                outcome: None,
7054            },
7055            crate::subagents::QueuedApproval {
7056                child_agent_id: "child-8".into(),
7057                tool: "write_file".into(),
7058                subject: None,
7059                queued_at_ms: 2,
7060                outcome: Some(crate::subagents::QueuedApprovalOutcome::Denied),
7061            },
7062        ]));
7063        let mut service = HarnessSessionService::new();
7064        service.observe_subagent_approvals(queue);
7065
7066        let rows = approvals(&mut service, json!({}));
7067        assert_eq!(rows.as_array().map(Vec::len), Some(2), "{rows:#}");
7068        assert_eq!(rows[0]["id"], "supercode/subagent/child-7/1/0");
7069        assert_eq!(rows[0]["harness"], HarnessId::SUPERCODE);
7070        assert_eq!(rows[0]["status"], "pending");
7071        assert_eq!(rows[0]["subject"], "shell cargo publish --dry-run");
7072        assert_eq!(rows[1]["status"], "denied");
7073        assert!(rows[1]["options"].as_array().unwrap().is_empty());
7074
7075        // `--session` addresses a subagent row by its child agent id.
7076        let only = approvals(&mut service, json!({"session": "child-8"}));
7077        assert_eq!(only.as_array().map(Vec::len), Some(1), "{only:#}");
7078        assert_eq!(only[0]["id"], "supercode/subagent/child-8/2/1");
7079    }
7080
7081    /// The uniform-verb contract: an id whose runtime door cannot carry a
7082    /// protocol request is refused BY NAME rather than answered with an empty
7083    /// list. Since ORC-2 gave Claude Code a permission-response primitive
7084    /// every registered harness can carry one, so the refusal is exercised on
7085    /// an unknown id — and the registered ids are asserted to be accepted.
7086    #[test]
7087    fn approvals_list_refuses_a_harness_that_cannot_carry_a_request() {
7088        let response = HarnessSessionService::new().handle(request(
7089            1,
7090            "harness.v1.approvals.list",
7091            json!({"harness": "not-a-harness"}),
7092        ));
7093        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7094        assert!(response["error"]["message"]
7095            .as_str()
7096            .unwrap()
7097            .contains("not-a-harness"));
7098        for harness in [HarnessId::CLAUDE_CODE, HarnessId::CODEX] {
7099            let response = HarnessSessionService::new().handle(request(
7100                1,
7101                "harness.v1.approvals.list",
7102                json!({"harness": harness}),
7103            ));
7104            assert!(response.get("error").is_none(), "{harness}: {response:#}");
7105        }
7106    }
7107
7108    /// The method is advertised, its SDK operation resolves it, and the
7109    /// registry reports the concept as observed for every harness whose
7110    /// runtime door can carry a request.
7111    #[test]
7112    fn approvals_list_is_an_advertised_method_and_an_observed_tier() {
7113        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.list"));
7114        assert_eq!(
7115            SdkOperation::from_method("harness.v1.approvals.list"),
7116            Some(SdkOperation::ApprovalsList)
7117        );
7118        let registry = harness_support_registry();
7119        for id in [
7120            HarnessId::HERMES,
7121            HarnessId::OPENCLAW,
7122            HarnessId::CODEX,
7123            // ORC-2: the Claude Code door answers `can_use_tool`, so its
7124            // pending_request concept joins the other driven doors.
7125            HarnessId::CLAUDE_CODE,
7126        ] {
7127            let concept = registry
7128                .harnesses
7129                .iter()
7130                .find(|harness| harness.id.as_str() == id)
7131                .unwrap()
7132                .orchestration
7133                .concepts
7134                .iter()
7135                .find(|concept| concept.concept == "pending_request")
7136                .unwrap();
7137            assert_eq!(concept.observed, crate::ImplementationKind::BuiltIn, "{id}");
7138            assert!(concept
7139                .methods
7140                .iter()
7141                .any(|method| method == "harness.v1.approvals.list"));
7142        }
7143    }
7144
7145    // ---- ORCH-20: `harness.v1.approvals.resolve` -------------------------
7146
7147    async fn resolve(service: &mut HarnessSessionService, params: Value) -> Value {
7148        service
7149            .handle_async(request(3, "harness.v1.approvals.resolve", params))
7150            .await
7151    }
7152
7153    /// dev/01: the whole loop on a driven runtime — list one pending row,
7154    /// answer it by ROW ID with one uniform decision, and see it gone. The
7155    /// door receives its own ACP envelope carrying the option it enumerated.
7156    #[tokio::test]
7157    async fn a_listed_row_resolves_with_one_uniform_decision_and_then_is_gone() {
7158        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7159        let mut service = HarnessSessionService::new();
7160        service.runtimes.insert(
7161            "runtime-1".into(),
7162            requesting_runtime(
7163                HarnessId::HERMES,
7164                vec![permission_event(7, "rm -rf build")],
7165                answered.clone(),
7166            ),
7167        );
7168        service.poll_runtimes().await;
7169
7170        let rows = approvals(&mut service, json!({}));
7171        assert_eq!(rows[0]["id"], "runtime-1/7");
7172
7173        let response = resolve(
7174            &mut service,
7175            json!({"id": "runtime-1/7", "decision": "allow_once"}),
7176        )
7177        .await;
7178        assert!(response.get("error").is_none(), "{response:#}");
7179        assert_eq!(
7180            response["result"],
7181            json!({
7182                "id": "runtime-1/7",
7183                "decision": "allow_once",
7184                "option_id": "allow_once",
7185                "resolved": true,
7186            }),
7187        );
7188        // The harness's own door was called with its own envelope.
7189        assert_eq!(
7190            answered
7191                .lock()
7192                .unwrap_or_else(std::sync::PoisonError::into_inner)
7193                .as_slice(),
7194            &[json!({
7195                "request_id": 7,
7196                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7197            })],
7198        );
7199        // And the row is gone, the same way `runtimes.respond` drops it.
7200        assert_eq!(
7201            approvals(&mut service, json!({})).as_array().map(Vec::len),
7202            Some(0),
7203        );
7204        // Answering it twice is an honest miss, not a silent success.
7205        let response = resolve(
7206            &mut service,
7207            json!({"id": "runtime-1/7", "decision": "allow_once"}),
7208        )
7209        .await;
7210        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7211    }
7212
7213    /// dev/01: deny travels the same path and picks the option the request
7214    /// itself classified as a refusal.
7215    #[tokio::test]
7216    async fn deny_selects_the_requests_own_reject_option() {
7217        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7218        let mut service = HarnessSessionService::new();
7219        service.runtimes.insert(
7220            "runtime-1".into(),
7221            requesting_runtime(
7222                HarnessId::HERMES,
7223                vec![permission_event(11, "git push --force")],
7224                answered.clone(),
7225            ),
7226        );
7227        service.poll_runtimes().await;
7228
7229        let response = resolve(
7230            &mut service,
7231            json!({"id": "runtime-1/11", "decision": "deny"}),
7232        )
7233        .await;
7234        assert!(response.get("error").is_none(), "{response:#}");
7235        // `deny` is the optionId whose ACP `kind` is `reject_once`.
7236        assert_eq!(response["result"]["option_id"], "deny");
7237        assert_eq!(
7238            answered
7239                .lock()
7240                .unwrap_or_else(std::sync::PoisonError::into_inner)[0]["response"],
7241            json!({"outcome": {"outcome": "selected", "optionId": "deny"}}),
7242        );
7243        assert_eq!(
7244            approvals(&mut service, json!({})).as_array().map(Vec::len),
7245            Some(0),
7246        );
7247    }
7248
7249    /// dev/01: a decision this request does not offer is refused by name,
7250    /// listing the ones it does — never silently downgraded to a neighbour.
7251    #[tokio::test]
7252    async fn a_decision_the_request_does_not_offer_is_refused_with_the_offered_ones() {
7253        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7254        let mut service = HarnessSessionService::new();
7255        let mut event = permission_event(3, "rm -rf build");
7256        // A request offering only allow-once and deny, as hermes 0.21.0's
7257        // edit-approval layer raises one.
7258        event.payload["params"]["options"] = json!([
7259            {"optionId": "allow_once", "name": "Allow edit", "kind": "allow_once"},
7260            {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
7261        ]);
7262        service.runtimes.insert(
7263            "runtime-1".into(),
7264            requesting_runtime(HarnessId::HERMES, vec![event], answered.clone()),
7265        );
7266        service.poll_runtimes().await;
7267
7268        let response = resolve(
7269            &mut service,
7270            json!({"id": "runtime-1/3", "decision": "allow_always"}),
7271        )
7272        .await;
7273        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7274        let message = response["error"]["message"].as_str().unwrap();
7275        assert!(message.contains("allow_always"), "{message}");
7276        assert!(message.contains("allow_once, deny"), "{message}");
7277        // Nothing was sent, and the request is still waiting for an answer.
7278        assert!(answered
7279            .lock()
7280            .unwrap_or_else(std::sync::PoisonError::into_inner)
7281            .is_empty());
7282        assert_eq!(
7283            approvals(&mut service, json!({})).as_array().map(Vec::len),
7284            Some(1),
7285        );
7286    }
7287
7288    /// dev/01: supercode's own queued subagent row is addressable but not
7289    /// answerable through this door — it is the parent's audit copy of a
7290    /// request its own handler answers. Refused by name, never a no-op.
7291    #[tokio::test]
7292    async fn a_queued_subagent_row_is_refused_by_name_rather_than_silently_answered() {
7293        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
7294            crate::subagents::QueuedApproval {
7295                child_agent_id: "child-7".into(),
7296                tool: "shell".into(),
7297                subject: Some("cargo publish --dry-run".into()),
7298                queued_at_ms: 1,
7299                outcome: None,
7300            },
7301        ]));
7302        let mut service = HarnessSessionService::new();
7303        service.observe_subagent_approvals(queue.clone());
7304        let row = approvals(&mut service, json!({}))[0]["id"]
7305            .as_str()
7306            .unwrap()
7307            .to_string();
7308        assert_eq!(row, "supercode/subagent/child-7/1/0");
7309
7310        let response = resolve(&mut service, json!({"id": row, "decision": "allow_once"})).await;
7311        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7312        let message = response["error"]["message"].as_str().unwrap();
7313        assert!(message.contains("queued subagent record"), "{message}");
7314        assert!(message.contains("request"), "{message}");
7315        // The audit record is untouched: nothing pretended to answer it.
7316        assert!(queue
7317            .lock()
7318            .unwrap_or_else(std::sync::PoisonError::into_inner)[0]
7319            .outcome
7320            .is_none());
7321    }
7322
7323    /// An id nobody is holding, and a call that names no decision at all,
7324    /// both fail with a message that says why.
7325    #[tokio::test]
7326    async fn an_unknown_row_and_a_missing_decision_are_both_named() {
7327        let mut service = HarnessSessionService::new();
7328        let response = resolve(
7329            &mut service,
7330            json!({"id": "runtime-9/4", "decision": "deny"}),
7331        )
7332        .await;
7333        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7334        assert!(response["error"]["message"]
7335            .as_str()
7336            .unwrap()
7337            .contains("runtime-9/4"));
7338
7339        let response = resolve(&mut service, json!({"id": "runtime-9/4"})).await;
7340        let message = response["error"]["message"].as_str().unwrap();
7341        assert!(
7342            message.contains("allow_once | allow_always | deny"),
7343            "{message}"
7344        );
7345
7346        let response = resolve(
7347            &mut service,
7348            json!({"id": "runtime-9/4", "decision": "deny", "option_id": "deny"}),
7349        )
7350        .await;
7351        assert!(response["error"]["message"]
7352            .as_str()
7353            .unwrap()
7354            .contains("not both"));
7355    }
7356
7357    /// The method is advertised, its SDK operation resolves it, and every
7358    /// harness whose runtime door can carry a request reports it on the
7359    /// CONTROLLED tier beside `runtimes.respond`.
7360    #[test]
7361    fn approvals_resolve_is_an_advertised_method_and_a_controlled_tier() {
7362        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.resolve"));
7363        assert_eq!(
7364            SdkOperation::from_method("harness.v1.approvals.resolve"),
7365            Some(SdkOperation::ApprovalsResolve)
7366        );
7367        assert_eq!(
7368            SdkOperation::ApprovalsResolve.action_name(),
7369            "approvals_resolve"
7370        );
7371        let registry = harness_support_registry();
7372        for id in [
7373            HarnessId::HERMES,
7374            HarnessId::OPENCLAW,
7375            HarnessId::CODEX,
7376            // ORC-2: the Claude Code door answers `can_use_tool`, so its
7377            // pending_request concept joins the other driven doors.
7378            HarnessId::CLAUDE_CODE,
7379        ] {
7380            let concept = registry
7381                .harnesses
7382                .iter()
7383                .find(|harness| harness.id.as_str() == id)
7384                .unwrap()
7385                .orchestration
7386                .concepts
7387                .iter()
7388                .find(|concept| concept.concept == "pending_request")
7389                .unwrap();
7390            assert_eq!(
7391                concept.controlled,
7392                crate::ImplementationKind::BuiltIn,
7393                "{id}"
7394            );
7395            assert!(
7396                concept
7397                    .methods
7398                    .iter()
7399                    .any(|method| method == "harness.v1.approvals.resolve"),
7400                "{id}"
7401            );
7402        }
7403    }
7404
7405    #[test]
7406    fn capabilities_are_explicit_and_versioned() {
7407        let mut service = HarnessSessionService::new();
7408        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
7409        assert_eq!(response["result"]["version"], HARNESS_SERVICE_VERSION);
7410        assert_eq!(
7411            response["result"]["sdk"]["schema_version"],
7412            crate::SDK_SCHEMA_VERSION
7413        );
7414        assert_eq!(
7415            response["result"]["sdk"]["operations"]
7416                .as_array()
7417                .unwrap()
7418                .len(),
7419            SdkOperation::ALL.len()
7420        );
7421        assert_eq!(
7422            response["result"]["harnesses"].as_array().unwrap().len(),
7423            11
7424        );
7425        assert!(response["result"]["harnesses"]
7426            .as_array()
7427            .unwrap()
7428            .iter()
7429            .any(|harness| harness == HarnessId::GROK));
7430        assert!(response["result"]["harnesses"]
7431            .as_array()
7432            .unwrap()
7433            .iter()
7434            .any(|harness| harness == HarnessId::GOOSE));
7435    }
7436
7437    #[test]
7438    fn handshake_health_uses_protocol_liveness_not_stderr_severity() {
7439        let noisy_stderr = crate::HarnessEvent {
7440            sequence: None,
7441            kind: "transport_stderr".into(),
7442            payload: json!({"line": "ERROR optional worker AuthorizationRequired"}),
7443        };
7444        assert_eq!(handshake_event_failure(&noisy_stderr), None);
7445
7446        let closed = crate::HarnessEvent {
7447            sequence: None,
7448            kind: "transport_closed".into(),
7449            payload: json!({}),
7450        };
7451        assert!(handshake_event_failure(&closed).is_some());
7452    }
7453
7454    #[tokio::test]
7455    async fn runtime_eof_is_notified_and_removed_for_raw_and_explicit_close() {
7456        let mut service = HarnessSessionService::new();
7457        service
7458            .runtimes
7459            .insert("raw-eof".into(), ending_runtime(None));
7460        service.runtimes.insert(
7461            "explicit-close".into(),
7462            ending_runtime(Some(HarnessEvent {
7463                sequence: None,
7464                kind: "transport_closed".into(),
7465                payload: json!({"message": "native transport exited"}),
7466            })),
7467        );
7468
7469        let notifications = service.poll_runtimes().await;
7470
7471        assert_eq!(notifications.len(), 2);
7472        assert!(notifications
7473            .iter()
7474            .all(|notification| { notification["params"]["event"]["kind"] == "transport_closed" }));
7475        assert!(notifications.iter().all(|notification| {
7476            notification["params"]["session_id"] == "ending-session"
7477                && notification["params"]["connection"].is_string()
7478        }));
7479        let mut sequences = notifications
7480            .iter()
7481            .filter_map(|notification| notification["params"]["sequence"].as_u64())
7482            .collect::<Vec<_>>();
7483        sequences.sort_unstable();
7484        assert_eq!(sequences, vec![1, 2]);
7485        assert!(service.runtimes.is_empty());
7486    }
7487
7488    #[test]
7489    fn support_report_and_grok_default_binding_share_the_registry() {
7490        let mut service = HarnessSessionService::new();
7491        let response = service.handle(request(1, "harness.v1.support.report", json!({})));
7492        assert_eq!(response["result"]["schema"], crate::SUPPORT_REGISTRY_SCHEMA);
7493        let params = RuntimeBackendParams {
7494            harness: HarnessId::from(HarnessId::GROK),
7495            protocol: None,
7496            launch: None,
7497            base_url: None,
7498            policy: RuntimePolicy::Default,
7499        };
7500        let backend = match runtime_backend(&params) {
7501            Ok(backend) => backend,
7502            Err(_) => panic!("Grok should bind through its registered ACP launch"),
7503        };
7504        assert_eq!(backend.harness().as_str(), HarnessId::GROK);
7505        assert!(backend.capabilities().start_session);
7506        let registered = harness_support_registry()
7507            .harnesses
7508            .into_iter()
7509            .find(|harness| harness.id.as_str() == HarnessId::GROK)
7510            .and_then(|harness| harness.runtime.default_launch)
7511            .unwrap();
7512        assert!(!registered
7513            .arguments
7514            .iter()
7515            .any(|argument| argument == "--always-approve"));
7516        assert!(runtime_launch(&params).is_none());
7517
7518        let yolo = RuntimeBackendParams {
7519            policy: RuntimePolicy::Yolo,
7520            ..params
7521        };
7522        assert!(runtime_launch(&yolo)
7523            .unwrap()
7524            .arguments
7525            .iter()
7526            .any(|argument| argument == "--always-approve"));
7527
7528        let mismatched_protocol = RuntimeBackendParams {
7529            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
7530            protocol: Some("acp".into()),
7531            launch: None,
7532            base_url: None,
7533            policy: RuntimePolicy::Default,
7534        };
7535        assert!(runtime_backend(&mismatched_protocol).is_err());
7536    }
7537
7538    #[test]
7539    fn load_follow_and_unfollow_share_the_same_locator() {
7540        let mut service = HarnessSessionService::new();
7541        let locator = pi_locator();
7542        let loaded = service.handle(request(
7543            1,
7544            "harness.v1.sessions.load",
7545            json!({"locator": locator}),
7546        ));
7547        assert_eq!(
7548            loaded["result"]["session"]["session_id"],
7549            locator.session_id
7550        );
7551
7552        let followed = service.handle(request(
7553            2,
7554            "harness.v1.sessions.follow",
7555            json!({"locator": locator}),
7556        ));
7557        assert_eq!(followed["result"]["subscription"], "sub-1");
7558        assert_eq!(followed["result"]["initial"]["type"], "session_snapshot");
7559        assert!(service.poll().is_empty());
7560
7561        let unfollowed = service.handle(request(
7562            3,
7563            "harness.v1.sessions.unfollow",
7564            json!({"subscription": "sub-1"}),
7565        ));
7566        assert_eq!(unfollowed["result"]["removed"], true);
7567    }
7568
7569    #[test]
7570    fn bounded_read_view_excludes_subagents_and_keeps_only_the_tail() {
7571        let temp = std::env::temp_dir().join(format!(
7572            "supercode-bounded-view-{}-{}",
7573            std::process::id(),
7574            generated_session_id()
7575        ));
7576        let path = temp.join("parent.jsonl");
7577        let subagents = temp.join("parent/subagents");
7578        std::fs::create_dir_all(&subagents).unwrap();
7579        let long_last = "x".repeat(300);
7580        let parent_records = [
7581            json!({"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":"first"}}),
7582            json!({"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"middle"}]}}),
7583            json!({"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":long_last}}),
7584        ];
7585        std::fs::write(
7586            &path,
7587            format!(
7588                "{}\n",
7589                parent_records
7590                    .iter()
7591                    .map(Value::to_string)
7592                    .collect::<Vec<_>>()
7593                    .join("\n")
7594            ),
7595        )
7596        .unwrap();
7597        std::fs::write(
7598            subagents.join("agent-child.jsonl"),
7599            concat!(
7600                r#"{"type":"user","uuid":"cu","parentUuid":null,"agentId":"child","message":{"role":"user","content":"child work"}}"#,
7601                "\n",
7602            ),
7603        )
7604        .unwrap();
7605        let locator = SessionLocator {
7606            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
7607            session_id: "parent".into(),
7608            storage: StorageLocator::File { path },
7609        };
7610        let mut service = HarnessSessionService::new();
7611
7612        let complete = service.handle(request(
7613            1,
7614            "harness.v1.sessions.load",
7615            json!({"locator": locator}),
7616        ));
7617        assert_eq!(
7618            complete["result"]["session"]["subagents"]
7619                .as_array()
7620                .unwrap()
7621                .len(),
7622            1
7623        );
7624
7625        let bounded = service.handle(request(
7626            2,
7627            "harness.v1.sessions.load",
7628            json!({
7629                "locator": locator,
7630                "view": {
7631                    "tail_messages": 1,
7632                    "max_message_chars": 256,
7633                    "include_subagents": false
7634                },
7635            }),
7636        ));
7637        let session = &bounded["result"]["session"];
7638        assert!(session["subagents"].as_array().unwrap().is_empty());
7639        assert_eq!(session["messages"].as_array().unwrap().len(), 1);
7640        assert_eq!(
7641            session["messages"][0]["content"],
7642            format!("{}\n…", "x".repeat(256))
7643        );
7644
7645        let followed = service.handle(request(
7646            3,
7647            "harness.v1.sessions.follow",
7648            json!({
7649                "locator": locator,
7650                "view": {
7651                    "tail_messages": 1,
7652                    "max_message_chars": 256,
7653                    "include_subagents": false
7654                },
7655            }),
7656        ));
7657        let initial = &followed["result"]["initial"]["session"];
7658        assert!(initial["subagents"].as_array().unwrap().is_empty());
7659        assert_eq!(initial["messages"].as_array().unwrap().len(), 1);
7660
7661        let _ = std::fs::remove_dir_all(&temp);
7662    }
7663
7664    #[test]
7665    fn forty_megabyte_display_load_is_bounded_and_prompt() {
7666        let temp = std::env::temp_dir().join(format!(
7667            "supercode-large-display-view-{}-{}",
7668            std::process::id(),
7669            generated_session_id()
7670        ));
7671        std::fs::create_dir_all(&temp).unwrap();
7672        let path = temp.join("rollout.jsonl");
7673        let mut file = std::io::BufWriter::new(std::fs::File::create(&path).unwrap());
7674        writeln!(
7675            file,
7676            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
7677        )
7678        .unwrap();
7679        let padding = "x".repeat(80 * 1024);
7680        for index in 0..512 {
7681            let marker = if index == 0 {
7682                "OLDEST-SHOULD-NOT-LOAD"
7683            } else if index == 511 {
7684                "LATEST-MUST-LOAD"
7685            } else {
7686                "bulk"
7687            };
7688            writeln!(
7689                file,
7690                "{}",
7691                json!({
7692                    "timestamp": "2026-01-01T00:00:01Z",
7693                    "type": "response_item",
7694                    "payload": {
7695                        "type": "message",
7696                        "role": "assistant",
7697                        "content": [{"type": "output_text", "text": format!("{marker}:{padding}")}],
7698                    },
7699                })
7700            )
7701            .unwrap();
7702        }
7703        file.flush().unwrap();
7704        drop(file);
7705        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
7706
7707        let locator = SessionLocator {
7708            harness: HarnessId::from(HarnessId::CODEX),
7709            session_id: "large-display".into(),
7710            storage: StorageLocator::File { path },
7711        };
7712        let started = Instant::now();
7713        let response = HarnessSessionService::new().handle(request(
7714            1,
7715            "harness.v1.sessions.load",
7716            json!({
7717                "locator": locator,
7718                "view": {
7719                    "tail_messages": 500,
7720                    "max_message_chars": 1024,
7721                    "include_subagents": false,
7722                    "display_history": true,
7723                },
7724            }),
7725        ));
7726        let elapsed = started.elapsed();
7727        let wire = response.to_string();
7728        eprintln!(
7729            "bounded 40 MiB display load: {elapsed:?}, {} response bytes",
7730            wire.len()
7731        );
7732        assert!(response.get("error").is_none(), "{response:#}");
7733        assert!(wire.contains("LATEST-MUST-LOAD"));
7734        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
7735        assert!(
7736            wire.len() < 2 * 1024 * 1024,
7737            "bounded wire was {} bytes",
7738            wire.len()
7739        );
7740        assert!(
7741            elapsed.as_secs_f64() < 3.0,
7742            "bounded 40 MiB load took {elapsed:?}"
7743        );
7744
7745        let _ = std::fs::remove_dir_all(&temp);
7746    }
7747
7748    #[test]
7749    fn forty_megabyte_goose_store_display_load_reads_only_the_tail() {
7750        let temp = std::env::temp_dir().join(format!(
7751            "supercode-large-goose-view-{}-{}",
7752            std::process::id(),
7753            generated_session_id()
7754        ));
7755        std::fs::create_dir_all(&temp).unwrap();
7756        let path = temp.join("sessions.db");
7757        let connection = rusqlite::Connection::open(&path).unwrap();
7758        connection
7759            .execute_batch(
7760                "CREATE TABLE sessions (
7761                    id TEXT PRIMARY KEY, name TEXT NOT NULL, working_dir TEXT NOT NULL,
7762                    created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
7763                    session_type TEXT NOT NULL, extension_data TEXT,
7764                    goose_mode TEXT NOT NULL, provider_name TEXT, model_config_json TEXT,
7765                    archived_at TEXT
7766                 );
7767                 CREATE TABLE messages (
7768                    id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, message_id TEXT,
7769                    role TEXT NOT NULL, content_json TEXT NOT NULL,
7770                    created_timestamp INTEGER NOT NULL, metadata_json TEXT
7771                 );",
7772            )
7773            .unwrap();
7774        connection
7775            .execute(
7776                "INSERT INTO sessions VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL)",
7777                rusqlite::params![
7778                    "goose-large",
7779                    "Large Goose session",
7780                    "/tmp",
7781                    "2026-01-01 00:00:00",
7782                    "2026-01-01 00:00:02",
7783                    "user",
7784                    "{}",
7785                    "auto",
7786                    "anthropic",
7787                    r#"{"model_name":"claude-sonnet"}"#,
7788                ],
7789            )
7790            .unwrap();
7791        let old_content = serde_json::to_string(&vec![json!({
7792            "type": "text",
7793            "text": format!("OLDEST-SHOULD-NOT-LOAD:{}", "x".repeat(40 * 1024 * 1024)),
7794        })])
7795        .unwrap();
7796        connection
7797            .execute(
7798                "INSERT INTO messages VALUES (1, ?1, 'old', 'user', ?2, 1, '{}')",
7799                rusqlite::params!["goose-large", old_content],
7800            )
7801            .unwrap();
7802        connection
7803            .execute(
7804                "INSERT INTO messages VALUES (2, ?1, 'new', 'assistant', ?2, 2, '{}')",
7805                rusqlite::params![
7806                    "goose-large",
7807                    r#"[{"type":"text","text":"LATEST-MUST-LOAD"}]"#
7808                ],
7809            )
7810            .unwrap();
7811        drop(connection);
7812        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
7813
7814        let locator = SessionLocator {
7815            harness: HarnessId::from(HarnessId::GOOSE),
7816            session_id: "goose-large".into(),
7817            storage: StorageLocator::Sqlite {
7818                path,
7819                selector: "goose-large".into(),
7820            },
7821        };
7822        let started = Instant::now();
7823        let response = HarnessSessionService::new().handle(request(
7824            1,
7825            "harness.v1.sessions.load",
7826            json!({
7827                "locator": locator,
7828                "view": {
7829                    "tail_messages": 1,
7830                    "max_message_chars": 1024,
7831                    "include_subagents": false,
7832                    "display_history": true,
7833                },
7834            }),
7835        ));
7836        let elapsed = started.elapsed();
7837        let wire = response.to_string();
7838        eprintln!(
7839            "bounded 40 MiB Goose display load: {elapsed:?}, {} response bytes",
7840            wire.len()
7841        );
7842        assert!(response.get("error").is_none(), "{response:#}");
7843        assert!(wire.contains("LATEST-MUST-LOAD"));
7844        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
7845        assert!(
7846            wire.len() < 64 * 1024,
7847            "bounded wire was {} bytes",
7848            wire.len()
7849        );
7850        assert!(
7851            elapsed.as_secs_f64() < 1.0,
7852            "bounded Goose load took {elapsed:?}"
7853        );
7854
7855        let _ = std::fs::remove_dir_all(&temp);
7856    }
7857
7858    #[test]
7859    fn display_view_keeps_codex_assistant_history_across_compaction() {
7860        let temp = std::env::temp_dir().join(format!(
7861            "supercode-codex-display-view-{}-{}",
7862            std::process::id(),
7863            generated_session_id()
7864        ));
7865        std::fs::create_dir_all(&temp).unwrap();
7866        let path = temp.join("rollout.jsonl");
7867        std::fs::write(
7868            &path,
7869            concat!(
7870                r#"{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"codex-display","cwd":"/tmp"}}"#,
7871                "\n",
7872                r#"{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]}}"#,
7873                "\n",
7874                r#"{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"old answer"}]}}"#,
7875                "\n",
7876                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"}]}}"#,
7877                "\n",
7878                r#"{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"new prompt"}]}}"#,
7879                "\n",
7880                r#"{"timestamp":"2026-01-01T00:00:05Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"new answer"}]}}"#,
7881                "\n",
7882            ),
7883        )
7884        .unwrap();
7885        let locator = SessionLocator {
7886            harness: HarnessId::from(HarnessId::CODEX),
7887            session_id: "codex-display".into(),
7888            storage: StorageLocator::File { path },
7889        };
7890        let mut service = HarnessSessionService::new();
7891
7892        let continuation = service.handle(request(
7893            1,
7894            "harness.v1.sessions.load",
7895            json!({"locator": locator}),
7896        ));
7897        let continuation_text = continuation["result"]["session"]["messages"].to_string();
7898        assert!(!continuation_text.contains("old answer"));
7899
7900        let display = service.handle(request(
7901            2,
7902            "harness.v1.sessions.load",
7903            json!({
7904                "locator": locator,
7905                "view": {
7906                    "tail_messages": 10,
7907                    "include_subagents": false,
7908                    "display_history": true,
7909                },
7910            }),
7911        ));
7912        let display_text = display["result"]["session"]["messages"].to_string();
7913        assert!(display_text.contains("old prompt"));
7914        assert!(display_text.contains("old answer"));
7915        assert!(display_text.contains("new prompt"));
7916        assert!(display_text.contains("new answer"));
7917
7918        let _ = std::fs::remove_dir_all(&temp);
7919    }
7920
7921    #[test]
7922    fn indexed_claude_windows_match_the_existing_wire_projection() {
7923        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7924            .join("tests/fixtures/claude_code_session.jsonl");
7925        let locator = SessionLocator {
7926            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
7927            session_id: "fixture".into(),
7928            storage: StorageLocator::File { path },
7929        };
7930        let full = load_session(&locator).unwrap();
7931        for inline_media in [InlineMediaMode::Full, InlineMediaMode::Metadata] {
7932            for offset in [0, 1, full.messages.len(), usize::MAX] {
7933                for limit in [0, 1, 3, usize::MAX] {
7934                    let options = SessionLoadOptions {
7935                        include_subagents: Some(false),
7936                        inline_media,
7937                        message_offset: Some(offset),
7938                        message_limit: Some(limit),
7939                        ..Default::default()
7940                    };
7941                    let expected = projected_session_result(&full, &options);
7942                    assert_eq!(
7943                        indexed_claude_window(&locator, &options).unwrap().unwrap(),
7944                        expected
7945                    );
7946                }
7947            }
7948            for tail in [0, 1, 3, usize::MAX] {
7949                let options = SessionLoadOptions {
7950                    include_subagents: Some(false),
7951                    inline_media,
7952                    message_tail: Some(tail),
7953                    ..Default::default()
7954                };
7955                assert_eq!(
7956                    indexed_claude_window(&locator, &options).unwrap().unwrap(),
7957                    projected_session_result(&full, &options)
7958                );
7959            }
7960        }
7961    }
7962
7963    #[test]
7964    fn load_supports_bounded_windows_and_media_metadata() {
7965        let mut service = HarnessSessionService::new();
7966        let locator = pi_locator();
7967        let bounded = service.handle(request(
7968            1,
7969            "harness.v1.sessions.load",
7970            json!({
7971                "locator": locator,
7972                "options": {
7973                    "include_subagents": false,
7974                    "message_limit": 2,
7975                    "message_offset": 1
7976                }
7977            }),
7978        ));
7979        assert_eq!(bounded["result"]["window"]["offset"], 1);
7980        assert_eq!(bounded["result"]["window"]["returned"], 2);
7981        assert!(bounded["result"]["summary"]["first_message"].is_object());
7982        assert!(bounded["result"]["summary"]["last_message"].is_object());
7983        assert_eq!(
7984            bounded["result"]["session"]["messages"]
7985                .as_array()
7986                .unwrap()
7987                .len(),
7988            2
7989        );
7990        assert!(bounded["result"]["session"]["subagents"]
7991            .as_array()
7992            .unwrap()
7993            .is_empty());
7994
7995        let tail = service.handle(request(
7996            2,
7997            "harness.v1.sessions.load",
7998            json!({"locator": locator, "options": {"message_tail": 1}}),
7999        ));
8000        assert_eq!(tail["result"]["window"]["returned"], 1);
8001        assert_eq!(tail["result"]["window"]["has_more"], true);
8002        assert_eq!(tail["result"]["window"]["has_older"], true);
8003        assert!(tail["result"]["window"]["older_items"].as_u64().unwrap() > 0);
8004        assert!(tail["result"]["summary"]["first_message"].is_object());
8005
8006        let metadata_only = service.handle(request(
8007            3,
8008            "harness.v1.sessions.load",
8009            json!({"locator": locator, "options": {"inline_media": "metadata"}}),
8010        ));
8011        assert!(metadata_only["result"]["session"]
8012            .to_string()
8013            .contains("media_reference"));
8014        assert!(!metadata_only["result"]["session"]
8015            .to_string()
8016            .contains("data:image/"));
8017    }
8018
8019    #[test]
8020    fn import_translate_branch_and_handoff_use_typed_artifacts() {
8021        let mut service = HarnessSessionService::new();
8022        let locator = pi_locator();
8023        let translated = service.handle(request(
8024            1,
8025            "harness.v1.sessions.translate",
8026            json!({"locator": locator, "target_harness": "grok"}),
8027        ));
8028        assert_eq!(translated["result"]["artifact"]["source_harness"], "pi");
8029        assert_eq!(translated["result"]["artifact"]["target_harness"], "grok");
8030        assert!(translated["result"]["artifact"]["content"]
8031            .as_str()
8032            .is_some_and(|content| !content.is_empty()));
8033
8034        for target in ["opencode", "open-code"] {
8035            let opencode = service.handle(request(
8036                6,
8037                "harness.v1.sessions.translate",
8038                json!({"locator": locator, "target_harness": target}),
8039            ));
8040            assert_eq!(opencode["result"]["artifact"]["target_harness"], "opencode");
8041        }
8042        let goose = service.handle(request(
8043            7,
8044            "harness.v1.sessions.translate",
8045            json!({"locator": locator, "target_harness": "goose"}),
8046        ));
8047        assert_eq!(goose["result"]["artifact"]["target_harness"], "goose");
8048        assert!(serde_json::from_str::<Value>(
8049            goose["result"]["artifact"]["content"].as_str().unwrap()
8050        )
8051        .unwrap()["conversation"]
8052            .is_array());
8053
8054        let imported = service.handle(request(
8055            2,
8056            "harness.v1.sessions.import",
8057            json!({
8058                "source_harness": "grok",
8059                "content": translated["result"]["artifact"]["content"],
8060            }),
8061        ));
8062        assert_eq!(imported["result"]["session"]["source"], "grok");
8063
8064        let branched = service.handle(request(
8065            3,
8066            "harness.v1.sessions.branch",
8067            json!({"locator": locator, "target_harness": "codex"}),
8068        ));
8069        assert_eq!(branched["result"]["parent"]["harness"], "pi");
8070        assert!(branched["result"]["bootstrap_prompt"]
8071            .as_str()
8072            .unwrap()
8073            .contains("frozen parent transcript"));
8074        assert_eq!(branched["result"]["artifact"]["target_harness"], "codex");
8075
8076        let handoff = service.handle(request(
8077            4,
8078            "harness.v1.sessions.handoff",
8079            json!({"locator": locator, "target_harness": "pi", "cwd": "/tmp/project"}),
8080        ));
8081        assert_eq!(handoff["result"]["launch"]["program"], "pi");
8082        assert_eq!(handoff["result"]["launch"]["cwd"], "/tmp/project");
8083        assert_eq!(handoff["result"]["requires_materialization"], true);
8084
8085        let goose_handoff = service.handle(request(
8086            8,
8087            "harness.v1.sessions.handoff",
8088            json!({"locator": locator, "target_harness": "goose", "cwd": "/tmp/project"}),
8089        ));
8090        assert_eq!(goose_handoff["result"]["launch"]["program"], "goose");
8091        assert_eq!(
8092            goose_handoff["result"]["materialize"]["arguments"],
8093            json!(["session", "import", "{artifact_path}"])
8094        );
8095
8096        let resumed = service.handle(request(
8097            5,
8098            "harness.v1.sessions.resume_instructions",
8099            json!({"locator": locator, "cwd": "/tmp/project", "policy": "yolo"}),
8100        ));
8101        assert_eq!(resumed["result"]["launch"]["program"], "pi");
8102        assert_eq!(resumed["result"]["launch"]["arguments"][0], "--approve");
8103    }
8104
8105    #[test]
8106    fn reduce_persists_and_reloads_a_byte_exact_reversible_bundle() {
8107        let temp = std::env::temp_dir().join(format!(
8108            "supercode-service-reduce-{}-{}",
8109            std::process::id(),
8110            generated_session_id()
8111        ));
8112        let source_path = temp.join("source.jsonl");
8113        let store_root = temp.join("store");
8114        std::fs::create_dir_all(&temp).unwrap();
8115
8116        let mut records = vec![json!({
8117            "timestamp": "2026-01-01T00:00:00Z",
8118            "type": "session_meta",
8119            "payload": {"id": "codex-reduce", "cwd": "/tmp/project"},
8120        })];
8121        for turn in 0..16 {
8122            records.push(json!({
8123                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 1),
8124                "type": "response_item",
8125                "payload": {
8126                    "type": "message",
8127                    "role": "user",
8128                    "content": [{
8129                        "type": "input_text",
8130                        "text": format!("request {turn}: {}", "context ".repeat(80)),
8131                    }],
8132                },
8133            }));
8134            records.push(json!({
8135                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 2),
8136                "type": "response_item",
8137                "payload": {
8138                    "type": "message",
8139                    "role": "assistant",
8140                    "content": [{
8141                        "type": "output_text",
8142                        "text": format!("answer {turn}: {}", "implementation detail ".repeat(80)),
8143                    }],
8144                },
8145            }));
8146        }
8147        let source = format!(
8148            "{}\n",
8149            records
8150                .iter()
8151                .map(Value::to_string)
8152                .collect::<Vec<_>>()
8153                .join("\n")
8154        );
8155        std::fs::write(&source_path, &source).unwrap();
8156        let locator = SessionLocator {
8157            harness: HarnessId::from(HarnessId::CODEX),
8158            session_id: "codex-reduce".into(),
8159            storage: StorageLocator::File {
8160                path: source_path.clone(),
8161            },
8162        };
8163        let original = load_session(&locator).unwrap();
8164        let mut service =
8165            HarnessSessionService::new().with_reduction_store_root(store_root.clone());
8166
8167        let response = service.handle(request(
8168            1,
8169            "harness.v1.sessions.reduce",
8170            json!({
8171                "locator": locator,
8172                "target_harness": "claude-code",
8173                "keep_last": 4,
8174            }),
8175        ));
8176        assert!(response.get("error").is_none(), "{response:#}");
8177        let receipt = &response["result"]["receipt"];
8178        assert_eq!(receipt["source_harness"], "codex");
8179        assert_eq!(receipt["target_harness"], "claude-code");
8180        assert_eq!(receipt["verified"], true);
8181        assert_eq!(receipt["reversible"], true);
8182        assert!(receipt["reductions"].as_u64().unwrap() > 0);
8183        assert!(
8184            receipt["source_tokens"].as_u64().unwrap()
8185                > receipt["reduced_tokens"].as_u64().unwrap()
8186        );
8187        assert!(receipt["ratio"].as_f64().unwrap() > 1.0);
8188        assert!(response["result"]["bootstrap_prompt"]
8189            .as_str()
8190            .unwrap()
8191            .contains("Do not guess hidden content"));
8192
8193        let rescue_id = receipt["id"].as_str().unwrap();
8194        let store = crate::SessionStore::open(&store_root).unwrap();
8195        let sidecar =
8196            Session::from_sidecar_str(&store.load_sidecar(rescue_id).unwrap().unwrap()).unwrap();
8197        let log = store.load_reduction_log(rescue_id).unwrap().unwrap();
8198        let persisted_view = parse_messages_jsonl(&store.load(rescue_id).unwrap()).unwrap();
8199        let policy = reduce::ReductionPolicy {
8200            clear_turns_older_than: Some(4),
8201            ..Default::default()
8202        };
8203        let (restamped_view, reapplied_log) =
8204            reduce::project_messages(&sidecar.messages, &policy, &log);
8205        assert_eq!(
8206            messages_jsonl(&persisted_view).unwrap(),
8207            messages_jsonl(&restamped_view).unwrap()
8208        );
8209        assert_eq!(reapplied_log, log);
8210        reduce::verify_log(&log, &sidecar).unwrap();
8211        assert_eq!(
8212            reduce::invert(&restamped_view, &log, &sidecar).unwrap(),
8213            original.messages
8214        );
8215        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), source);
8216
8217        std::fs::remove_dir_all(temp).ok();
8218    }
8219
8220    #[test]
8221    fn read_surfaces_view_a_severed_claude_graph_while_transfer_still_refuses_it() {
8222        let temp = std::env::temp_dir().join(format!(
8223            "supercode-severed-view-{}-{}",
8224            std::process::id(),
8225            generated_session_id()
8226        ));
8227        std::fs::create_dir_all(&temp).unwrap();
8228        let path = temp.join("severed.jsonl");
8229        // A live record whose parent was pruned — what a compacted or
8230        // resumed-across-files Claude Code session looks like on disk.
8231        std::fs::write(
8232            &path,
8233            concat!(
8234                r#"{"type":"user","uuid":"orphan-u","parentUuid":null,"message":{"role":"user","content":"stranded prompt"}}"#,
8235                "\n",
8236                r#"{"type":"assistant","uuid":"live-a","parentUuid":"pruned","message":{"id":"m","role":"assistant","content":[{"type":"text","text":"live answer"}]}}"#,
8237                "\n",
8238            ),
8239        )
8240        .unwrap();
8241        let locator = SessionLocator {
8242            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8243            session_id: "severed".into(),
8244            storage: StorageLocator::File { path },
8245        };
8246        let mut service = HarnessSessionService::new();
8247
8248        let viewed = service.handle(request(
8249            1,
8250            "harness.v1.sessions.load",
8251            json!({"locator": locator}),
8252        ));
8253        let session = &viewed["result"]["session"];
8254        assert_eq!(session["fidelity"], "semantic");
8255        assert_eq!(session["messages"].as_array().unwrap().len(), 2);
8256        assert!(session["residue"].as_array().unwrap().iter().any(|entry| {
8257            entry
8258                .as_str()
8259                .is_some_and(|entry| entry.contains("live-a") && entry.contains("pruned"))
8260        }));
8261
8262        // Asking a READ surface for a lossless reconstruction gets the strict
8263        // refusal back, unchanged.
8264        let strict = service.handle(request(
8265            2,
8266            "harness.v1.sessions.load",
8267            json!({"locator": locator, "fidelity": "byte_lossless"}),
8268        ));
8269        assert!(strict["error"]["message"]
8270            .as_str()
8271            .unwrap()
8272            .contains("cannot reconstruct lossless Claude continuation"));
8273
8274        // Transfer/continuation surfaces have no view mode at all.
8275        let translated = service.handle(request(
8276            3,
8277            "harness.v1.sessions.translate",
8278            json!({"locator": locator, "target_harness": "codex"}),
8279        ));
8280        assert!(translated["error"]["message"]
8281            .as_str()
8282            .unwrap()
8283            .contains("cannot reconstruct lossless Claude continuation"));
8284        let resumed = service.handle(request(
8285            4,
8286            "harness.v1.sessions.resume_instructions",
8287            json!({"locator": locator}),
8288        ));
8289        assert!(resumed["error"]["message"]
8290            .as_str()
8291            .unwrap()
8292            .contains("cannot reconstruct lossless Claude continuation"));
8293
8294        let _ = std::fs::remove_dir_all(&temp);
8295    }
8296
8297    #[test]
8298    fn structured_resume_launches_cover_gemini_goose_and_supercode() {
8299        let codex = resume_launch(
8300            HarnessId::CODEX,
8301            "codex-session",
8302            Path::new("/tmp/project"),
8303            ResumePolicy::Yolo,
8304        )
8305        .unwrap_or_else(|_| panic!("Codex resume launch must be registered"));
8306        assert_eq!(codex.program, "codex");
8307        assert_eq!(
8308            codex.arguments,
8309            [
8310                "-c",
8311                "check_for_update_on_startup=false",
8312                "-c",
8313                "projects.\"/tmp/project\".trust_level=\"trusted\"",
8314                "--dangerously-bypass-approvals-and-sandbox",
8315                "--dangerously-bypass-hook-trust",
8316                "resume",
8317                "codex-session",
8318            ]
8319        );
8320
8321        let gemini = resume_launch(
8322            HarnessId::GEMINI,
8323            "gemini-session",
8324            Path::new("/tmp/project"),
8325            ResumePolicy::Yolo,
8326        )
8327        .unwrap_or_else(|_| panic!("Gemini resume launch must be registered"));
8328        assert_eq!(gemini.program, "gemini");
8329        assert_eq!(gemini.arguments, ["--yolo", "--resume", "gemini-session"]);
8330
8331        let goose = resume_launch(
8332            HarnessId::GOOSE,
8333            "goose-session",
8334            Path::new("/tmp/project"),
8335            ResumePolicy::Yolo,
8336        )
8337        .unwrap_or_else(|_| panic!("Goose resume launch must be registered"));
8338        assert_eq!(goose.program, "goose");
8339        assert_eq!(
8340            goose.arguments,
8341            ["session", "--resume", "--session-id", "goose-session"]
8342        );
8343
8344        let supercode = resume_launch(
8345            HarnessId::SUPERCODE,
8346            "supercode-session",
8347            Path::new("/tmp/project"),
8348            ResumePolicy::Yolo,
8349        )
8350        .unwrap_or_else(|_| panic!("Supercode resume launch must be registered"));
8351        assert_eq!(supercode.program, "supercode");
8352        assert_eq!(
8353            supercode.arguments,
8354            ["--dangerous", "resume", "supercode-session"]
8355        );
8356    }
8357
8358    #[test]
8359    fn diagonal_artifacts_preserve_claude_subagents_and_grok_bundle_members() {
8360        let temp = std::env::temp_dir().join(format!(
8361            "supercode-harness-artifact-{}-{}",
8362            std::process::id(),
8363            generated_session_id()
8364        ));
8365        let main_path = temp.join("parent.jsonl");
8366        let subagent_path = temp.join("parent/subagents/agent-child.jsonl");
8367        std::fs::create_dir_all(subagent_path.parent().unwrap()).unwrap();
8368        let fixture = std::fs::read_to_string(
8369            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8370                .join("tests/fixtures/claude_code_session.jsonl"),
8371        )
8372        .unwrap();
8373        let parent = fixture.trim_end_matches('\n');
8374        let child = fixture.trim_end_matches('\n');
8375        std::fs::write(&main_path, parent).unwrap();
8376        std::fs::write(&subagent_path, child).unwrap();
8377        let locator = SessionLocator {
8378            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8379            session_id: "213bb148-51ea-453f-9206-f8b4b1168547".into(),
8380            storage: StorageLocator::File {
8381                path: main_path.clone(),
8382            },
8383        };
8384        let mut service = HarnessSessionService::new();
8385        let claude = service.handle(request(
8386            1,
8387            "harness.v1.sessions.translate",
8388            json!({"locator": locator, "target_harness": "claude-code"}),
8389        ));
8390        let artifact = &claude["result"]["artifact"];
8391        assert_eq!(artifact["fidelity"], "byte_lossless");
8392        assert_eq!(artifact["content"], parent);
8393        let files = artifact["files"].as_array().unwrap();
8394        assert!(files.iter().any(|file| {
8395            file["role"] == "subagent"
8396                && file["path"]
8397                    .as_str()
8398                    .is_some_and(|path| path.ends_with("/subagents/agent-child.jsonl"))
8399                && file["content"] == child
8400        }));
8401        assert!(!artifact["content"].as_str().unwrap().ends_with('\n'));
8402
8403        let grok = service.handle(request(
8404            2,
8405            "harness.v1.sessions.translate",
8406            json!({"locator": grok_locator(), "target_harness": "grok"}),
8407        ));
8408        let files = grok["result"]["artifact"]["files"].as_array().unwrap();
8409        for name in ["summary.json", "updates.jsonl"] {
8410            let expected = std::fs::read_to_string(
8411                PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8412                    .join("tests/fixtures/grok_session")
8413                    .join(name),
8414            )
8415            .unwrap();
8416            assert!(files.iter().any(|file| {
8417                file["path"] == name && file["role"] == "bundle" && file["content"] == expected
8418            }));
8419        }
8420        std::fs::remove_dir_all(temp).ok();
8421    }
8422
8423    #[test]
8424    fn every_non_grok_handoff_mints_and_uses_a_fresh_target_identity() {
8425        let mut service = HarnessSessionService::new();
8426        let source = pi_locator();
8427        for (target, format) in [
8428            ("claude-code", SessionFormat::ClaudeCode),
8429            ("codex", SessionFormat::Codex),
8430            ("opencode", SessionFormat::OpenCode),
8431            ("pi", SessionFormat::Pi),
8432        ] {
8433            let result = service.handle(request(
8434                1,
8435                "harness.v1.sessions.handoff",
8436                json!({"locator": source, "target_harness": target, "cwd": "/tmp/project"}),
8437            ));
8438            let artifact = &result["result"]["artifact"];
8439            let target_id = artifact["session_id"].as_str().unwrap();
8440            assert_ne!(target_id, source.session_id, "{target}");
8441            let parsed = Session::load_str(artifact["content"].as_str().unwrap(), format).unwrap();
8442            assert_eq!(
8443                parsed.meta.session_id.as_deref(),
8444                Some(target_id),
8445                "{target}"
8446            );
8447            if target != "pi" {
8448                assert!(result["result"]["launch"]["arguments"]
8449                    .as_array()
8450                    .unwrap()
8451                    .iter()
8452                    .any(|argument| argument == target_id));
8453            }
8454            if target == "opencode" {
8455                assert!(target_id.starts_with("ses_"));
8456                fn assert_session_ids(value: &Value, target_id: &str) {
8457                    match value {
8458                        Value::Object(fields) => {
8459                            if let Some(session_id) = fields.get("sessionID") {
8460                                assert_eq!(session_id, target_id);
8461                            }
8462                            for child in fields.values() {
8463                                assert_session_ids(child, target_id);
8464                            }
8465                        }
8466                        Value::Array(values) => {
8467                            for child in values {
8468                                assert_session_ids(child, target_id);
8469                            }
8470                        }
8471                        _ => {}
8472                    }
8473                }
8474                let document: Value =
8475                    serde_json::from_str(artifact["content"].as_str().unwrap()).unwrap();
8476                assert_session_ids(&document, target_id);
8477            }
8478        }
8479
8480        let first = service.handle(request(
8481            2,
8482            "harness.v1.sessions.handoff",
8483            json!({"locator": source, "target_harness": "codex"}),
8484        ));
8485        let second = service.handle(request(
8486            3,
8487            "harness.v1.sessions.handoff",
8488            json!({"locator": source, "target_harness": "codex"}),
8489        ));
8490        assert_ne!(
8491            first["result"]["artifact"]["session_id"],
8492            second["result"]["artifact"]["session_id"]
8493        );
8494    }
8495
8496    #[test]
8497    fn grok_handoff_uses_the_official_importer_contract() {
8498        let mut service = HarnessSessionService::new();
8499        let source = opencode_locator();
8500        let response = service.handle(request(
8501            1,
8502            "harness.v1.sessions.handoff",
8503            json!({
8504                "locator": source,
8505                "target_harness": "grok",
8506                "cwd": "/tmp/grok-handoff-project",
8507            }),
8508        ));
8509        let result = &response["result"];
8510
8511        // The target is Grok, but the artifact truthfully names the Claude Code wire
8512        // format accepted by Grok's official importer. Raw Grok chat_history JSONL is
8513        // not a complete stock-resumable bundle.
8514        assert_eq!(result["artifact"]["target_harness"], "claude-code");
8515        assert!(result["artifact"]["suggested_filename"]
8516            .as_str()
8517            .unwrap()
8518            .ends_with(".grok-import.claude-code.jsonl"));
8519        let artifact = Session::load_str(
8520            result["artifact"]["content"].as_str().unwrap(),
8521            SessionFormat::ClaudeCode,
8522        )
8523        .unwrap();
8524        assert_eq!(
8525            artifact.meta.cwd.as_deref(),
8526            Some(Path::new("/tmp/grok-handoff-project"))
8527        );
8528        let target_session_id = artifact.meta.session_id.as_deref().unwrap();
8529        assert_eq!(target_session_id.len(), 36);
8530        assert_eq!(target_session_id.as_bytes()[14], b'4');
8531        assert_ne!(target_session_id, opencode_locator().session_id);
8532        assert_eq!(
8533            result["artifact"]["session_id"],
8534            artifact.meta.session_id.as_deref().unwrap()
8535        );
8536
8537        assert_eq!(
8538            result["materialize"]["arguments"],
8539            json!(["import", "--json", "{artifact_path}"])
8540        );
8541        assert_eq!(
8542            result["launch"]["arguments"],
8543            json!(["--resume", "{imported_session_id}", "--fork-session"])
8544        );
8545        assert!(result["note"]
8546            .as_str()
8547            .unwrap()
8548            .contains("outcome=imported"));
8549        assert!(!result["launch"]["arguments"]
8550            .as_array()
8551            .unwrap()
8552            .iter()
8553            .any(|argument| argument == &opencode_locator().session_id));
8554    }
8555
8556    #[tokio::test]
8557    async fn inventory_rejects_unknown_harnesses_and_runtime_attach_is_honest() {
8558        let mut service = HarnessSessionService::new();
8559        let inventory = service
8560            .handle_async(request(
8561                1,
8562                "harness.v1.harnesses.list",
8563                json!({"harnesses": ["missing"]}),
8564            ))
8565            .await;
8566        assert_eq!(inventory["error"]["code"], -32602);
8567
8568        let attached = service
8569            .handle_async(request(
8570                2,
8571                "harness.v1.runtimes.attach_existing",
8572                json!({"harness": "codex", "runtime_id": "thread-1"}),
8573            ))
8574            .await;
8575        assert_eq!(attached["error"]["code"], -32000);
8576        assert!(attached["error"]["message"]
8577            .as_str()
8578            .unwrap()
8579            .contains("runtimes.resume"));
8580    }
8581
8582    #[test]
8583    fn invalid_params_and_unknown_methods_use_json_rpc_errors() {
8584        let mut service = HarnessSessionService::new();
8585        let invalid = service.handle(request(1, "harness.v1.sessions.load", json!({})));
8586        assert_eq!(invalid["error"]["code"], -32602);
8587        let unknown = service.handle(request(2, "harness.v1.unknown", json!({})));
8588        assert_eq!(unknown["error"]["code"], -32601);
8589    }
8590
8591    #[cfg(unix)]
8592    #[tokio::test]
8593    // The test mutates process-wide harness environment and deliberately
8594    // holds the global test lock until every async runtime operation ends.
8595    #[allow(clippy::await_holding_lock)]
8596    async fn async_service_drives_a_generic_acp_runtime() {
8597        let _environment_guard = crate::live_runtime::test_environment_lock();
8598        let script = r#"
8599            i=0
8600            while IFS= read -r line; do
8601              i=$((i + 1))
8602              case "$i" in
8603                1) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{},"authMethods":[]}}' ;;
8604                2) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"svc_acp"}}' ;;
8605                3)
8606                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ok"}}}}'
8607                  printf '%s\n' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}'
8608                  ;;
8609                4)
8610                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"from terminal"}}}}'
8611                  printf '%s\n' '{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}'
8612                  ;;
8613              esac
8614            done
8615        "#;
8616        let mut service = HarnessSessionService::new();
8617        let started = service
8618            .handle_async(request(
8619                1,
8620                "harness.v1.runtimes.start",
8621                json!({
8622                    "harness": "codex",
8623                    "protocol": "acp",
8624                    "cwd": std::env::current_dir().unwrap(),
8625                    "launch": {"program": "/bin/sh", "arguments": ["-c", script], "env": {}},
8626                }),
8627            ))
8628            .await;
8629        assert_eq!(started["result"]["connection"], "runtime-1");
8630        assert_eq!(started["result"]["handle"]["runtime_id"], "svc_acp");
8631
8632        let terminal = service
8633            .handle_async(request(
8634                9,
8635                "harness.v1.runtimes.terminal_instructions",
8636                json!({"connection":"runtime-1"}),
8637            ))
8638            .await;
8639        let arguments = terminal["result"]["launch"]["arguments"]
8640            .as_array()
8641            .expect("hosted runtime should return terminal arguments");
8642        let endpoint_index = arguments
8643            .iter()
8644            .position(|value| value == "--endpoint")
8645            .expect("terminal command should use an opaque endpoint");
8646        let endpoint = LiveRuntimeEndpoint::parse(
8647            arguments[endpoint_index + 1]
8648                .as_str()
8649                .expect("endpoint argument should be text"),
8650        )
8651        .unwrap();
8652        assert!(!terminal.to_string().contains("Bearer"));
8653        let workspace = std::env::current_dir().unwrap();
8654        let receipt = resolve_live_runtime(
8655            &endpoint,
8656            &LiveRuntimeSource {
8657                harness: "codex".into(),
8658                session_id: "svc_acp".into(),
8659                workspace,
8660            },
8661        )
8662        .unwrap();
8663        let remote = crate::HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
8664            .await
8665            .unwrap();
8666        let mut attachment = crate::FrontendRuntime::attach(remote.as_ref(), 100)
8667            .await
8668            .unwrap();
8669
8670        let sent = service
8671            .handle_async(request(
8672                2,
8673                "harness.v1.runtimes.send_input",
8674                json!({"connection": "runtime-1", "text": "hi"}),
8675            ))
8676            .await;
8677        assert_eq!(sent["result"]["turn_id"], "3");
8678
8679        let mut events = Vec::new();
8680        for _ in 0..20 {
8681            events.extend(service.poll_runtimes().await);
8682            if events.len() >= 2 {
8683                break;
8684            }
8685            tokio::time::sleep(Duration::from_millis(2)).await;
8686        }
8687        assert!(events
8688            .iter()
8689            .any(|event| { event["params"]["event"]["kind"] == "session/update" }));
8690        assert!(events.iter().any(|event| {
8691            event["params"]["event"]["kind"] == "supercode/acp_request_completed"
8692        }));
8693
8694        let saw_editor_reply = tokio::time::timeout(Duration::from_secs(2), async {
8695            loop {
8696                let event = attachment.next_event().await.unwrap();
8697                if event.kind == "text_delta" && event.payload["text"] == "ok" {
8698                    break;
8699                }
8700            }
8701        })
8702        .await;
8703        assert!(
8704            saw_editor_reply.is_ok(),
8705            "terminal should observe the editor-driven turn"
8706        );
8707
8708        crate::FrontendRuntime::submit(remote.as_ref(), "DRIVE FROM TERMINAL".into())
8709            .await
8710            .unwrap();
8711        let saw_terminal_reply = tokio::time::timeout(Duration::from_secs(2), async {
8712            loop {
8713                let event = attachment.next_event().await.unwrap();
8714                if event.kind == "text_delta" && event.payload["text"] == "from terminal" {
8715                    break;
8716                }
8717            }
8718        })
8719        .await;
8720        assert!(
8721            saw_terminal_reply.is_ok(),
8722            "terminal should drive the same runtime"
8723        );
8724
8725        let closed = service
8726            .handle_async(request(
8727                3,
8728                "harness.v1.runtimes.close",
8729                json!({"connection": "runtime-1"}),
8730            ))
8731            .await;
8732        assert_eq!(closed["result"]["closed"], true);
8733    }
8734
8735    /// UNI-7 dev/02: a RUNNING mock gateway is detected through the real
8736    /// openclaw probe (config-declared endpoint, TCP connect), and an ACTIVE
8737    /// hermes WAL is detected through the real WAL-freshness probe; the
8738    /// negative sides (no listener, stale WAL, no config) stay undetected.
8739    #[test]
8740    fn running_instances_are_detected_from_mock_gateway_and_active_wal() {
8741        let home = connect_scratch_home("uni7-running");
8742
8743        // No config at all: hermes has no default endpoint, so no detection.
8744        // (openclaw's no-config behavior now probes its DOCUMENTED default
8745        // endpoint ws://127.0.0.1:18789 — see the connect launch's
8746        // `default_address` — which is real box state a hermetic test must
8747        // not assert either way; the closed-port negative below covers the
8748        // no-listener side deterministically.)
8749        assert!(probe_hermes_running(&home, 300_000).is_none());
8750
8751        // Mock gateway: a real TCP listener on an ephemeral port, declared in
8752        // the harness's own config file.
8753        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
8754        let port = listener.local_addr().unwrap().port();
8755        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
8756        std::fs::write(
8757            home.join(".openclaw/openclaw.json"),
8758            format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
8759        )
8760        .unwrap();
8761        let running = probe_openclaw_running(&home).expect("listening gateway must be detected");
8762        assert!(matches!(
8763            running.method,
8764            RunningInstanceMethod::GatewayConnect
8765        ));
8766        assert!(running.evidence.contains(&format!("127.0.0.1:{port}")));
8767        drop(listener);
8768        // Parallel tests also bind ephemeral loopback ports, so a just-freed
8769        // port can be re-bound by a NEIGHBORING test between drop and probe.
8770        // Detection on a closed port must fail — retry on a fresh port when
8771        // the freed one was recycled by someone else.
8772        let mut closed_detected = probe_openclaw_running(&home).is_some();
8773        for _ in 0..3 {
8774            if !closed_detected {
8775                break;
8776            }
8777            let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
8778            let port = listener.local_addr().unwrap().port();
8779            drop(listener);
8780            std::fs::write(
8781                home.join(".openclaw/openclaw.json"),
8782                format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
8783            )
8784            .unwrap();
8785            closed_detected = probe_openclaw_running(&home).is_some();
8786        }
8787        assert!(
8788            !closed_detected,
8789            "a closed gateway must not read as running"
8790        );
8791
8792        // gateway.url form takes precedence over port.
8793        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
8794        let port = listener.local_addr().unwrap().port();
8795        std::fs::write(
8796            home.join(".openclaw/openclaw.json"),
8797            format!(r#"{{"gateway": {{"url": "ws://127.0.0.1:{port}", "auth": {{"mode": "token", "token": "t"}}}}}}"#),
8798        )
8799        .unwrap();
8800        assert!(probe_openclaw_running(&home).is_some());
8801        drop(listener);
8802
8803        // Hermes: an ACTIVE WAL (fresh stamp) is detected; a stale one is not.
8804        std::fs::create_dir_all(home.join(".hermes")).unwrap();
8805        let wal = home.join(".hermes/state.db-wal");
8806        std::fs::write(&wal, b"wal").unwrap();
8807        let running = probe_hermes_running(&home, 300_000).expect("fresh WAL must be detected");
8808        assert!(matches!(
8809            running.method,
8810            RunningInstanceMethod::StoreWalActivity
8811        ));
8812        assert!(running.evidence.contains("state.db-wal"));
8813        let stale = std::time::SystemTime::now() - std::time::Duration::from_secs(3_600);
8814        std::fs::File::options()
8815            .append(true)
8816            .open(&wal)
8817            .unwrap()
8818            .set_modified(stale)
8819            .unwrap();
8820        assert!(
8821            probe_hermes_running(&home, 300_000).is_none(),
8822            "a stale WAL (crash leftover) must not read as running"
8823        );
8824    }
8825
8826    fn connect_scratch_home(tag: &str) -> PathBuf {
8827        let dir = std::env::temp_dir().join(format!(
8828            "supercode-connect-service-{tag}-{}-{}",
8829            std::process::id(),
8830            std::time::SystemTime::now()
8831                .duration_since(std::time::UNIX_EPOCH)
8832                .unwrap()
8833                .as_nanos()
8834        ));
8835        std::fs::create_dir_all(&dir).unwrap();
8836        dir
8837    }
8838
8839    /// Minimal HTTP responder that speaks just enough OpenCode server to
8840    /// accept a health check, create a session, and hold an SSE stream open,
8841    /// while recording each request line with its Authorization header.
8842    async fn mock_opencode_endpoint() -> (String, tokio::sync::mpsc::UnboundedReceiver<String>) {
8843        use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
8844        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
8845        let address = listener.local_addr().unwrap();
8846        let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel();
8847        tokio::spawn(async move {
8848            loop {
8849                let Ok((mut stream, _)) = listener.accept().await else {
8850                    break;
8851                };
8852                let request_sender = request_sender.clone();
8853                tokio::spawn(async move {
8854                    let (reader, mut writer) = stream.split();
8855                    let mut reader = BufReader::new(reader);
8856                    let mut request_line = String::new();
8857                    if reader.read_line(&mut request_line).await.unwrap_or(0) == 0 {
8858                        return;
8859                    }
8860                    let request_line = request_line.trim_end().to_string();
8861                    let mut authorization = String::new();
8862                    let mut content_length = 0usize;
8863                    loop {
8864                        let mut line = String::new();
8865                        if reader.read_line(&mut line).await.unwrap_or(0) == 0 {
8866                            return;
8867                        }
8868                        let line = line.trim_end();
8869                        if line.is_empty() {
8870                            break;
8871                        }
8872                        let lower = line.to_ascii_lowercase();
8873                        if let Some(value) = lower.strip_prefix("authorization:") {
8874                            authorization = value.trim().to_string();
8875                        }
8876                        if let Some(value) = lower.strip_prefix("content-length:") {
8877                            content_length = value.trim().parse().unwrap_or(0);
8878                        }
8879                    }
8880                    if content_length > 0 {
8881                        let mut body = vec![0u8; content_length];
8882                        let _ = reader.read_exact(&mut body).await;
8883                    }
8884                    let _ = request_sender.send(format!("{request_line} :: {authorization}"));
8885                    if request_line.starts_with("GET /event") {
8886                        let _ = writer
8887                            .write_all(
8888                                b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n",
8889                            )
8890                            .await;
8891                        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
8892                        return;
8893                    }
8894                    let body = if request_line.starts_with("POST /session") {
8895                        r#"{"id":"mock-session"}"#
8896                    } else {
8897                        r#"{"status":"ok"}"#
8898                    };
8899                    let response = format!(
8900                        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
8901                        body.len(),
8902                        body
8903                    );
8904                    let _ = writer.write_all(response.as_bytes()).await;
8905                });
8906            }
8907        });
8908        (format!("http://{address}"), request_receiver)
8909    }
8910
8911    fn connect_descriptor(protocol: &str) -> crate::HarnessSupportDescriptor {
8912        crate::HarnessSupportDescriptor {
8913            orchestration: Default::default(),
8914            id: HarnessId::from(HarnessId::OPENCODE),
8915            display_name: "OpenCode".into(),
8916            native: crate::NativeSupport {
8917                discover: crate::ImplementationKind::Absent,
8918                load: crate::ImplementationKind::Absent,
8919                follow: crate::ImplementationKind::Absent,
8920                import: crate::ImplementationKind::Absent,
8921                export: crate::ImplementationKind::Absent,
8922            },
8923            runtime: crate::RuntimeSupport {
8924                implementation: crate::ImplementationKind::BuiltIn,
8925                protocol: protocol.into(),
8926                default_launch: None,
8927                connect_launch: Some(crate::RuntimeConnectLaunch {
8928                    config_path: "~/opencode-tui.json".into(),
8929                    address_pointer: "/server/url".into(),
8930                    port_pointer: None,
8931                    default_address: None,
8932                    auth_pointer: Some("/server/token".into()),
8933                    protocol: protocol.into(),
8934                }),
8935                capabilities: crate::RuntimeCapabilities {
8936                    start_session: true,
8937                    resume_session: true,
8938                    attach_existing_process: true,
8939                    send_input: true,
8940                    stream_events: true,
8941                    interrupt: true,
8942                    steer: false,
8943                    respond_to_requests: true,
8944                },
8945            },
8946        }
8947    }
8948
8949    #[tokio::test]
8950    async fn connect_mode_descriptor_opens_a_running_endpoint_with_config_sourced_auth() {
8951        let (base_url, mut requests) = mock_opencode_endpoint().await;
8952        let home = connect_scratch_home("open");
8953        std::fs::write(
8954            home.join("opencode-tui.json"),
8955            format!(r#"{{"server": {{"url": "{base_url}", "token": "connect-secret"}}}}"#),
8956        )
8957        .unwrap();
8958
8959        let descriptor = connect_descriptor("opencode-http-sse");
8960        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
8961        assert!(backend.capabilities().attach_existing_process);
8962
8963        let connection = backend
8964            .start(crate::RuntimeStartRequest {
8965                cwd: home.clone(),
8966                launch: None,
8967                mcp_servers: Vec::new(),
8968            })
8969            .await
8970            .unwrap();
8971        let handle = connection.handle();
8972        assert_eq!(handle.runtime_id, "mock-session");
8973        match &handle.endpoint {
8974            crate::RuntimeEndpoint::Http {
8975                base_url: endpoint, ..
8976            } => assert_eq!(endpoint, &base_url),
8977            other => panic!("connect mode must join the running endpoint, got {other:?}"),
8978        }
8979
8980        let mut seen = Vec::new();
8981        while let Ok(line) = requests.try_recv() {
8982            seen.push(line);
8983        }
8984        assert!(seen
8985            .iter()
8986            .any(|line| line.starts_with("GET /global/health")
8987                && line.contains("bearer connect-secret")));
8988        assert!(seen.iter().any(
8989            |line| line.starts_with("POST /session") && line.contains("bearer connect-secret")
8990        ));
8991    }
8992
8993    /// UNI-5 dev/02, contract corrected by the 2026-08-31 blind walk: the
8994    /// full connect-mode attach path against a MOCK gateway bridge — no live
8995    /// gateway, no model spend. A scripted fake `openclaw` binary (a)
8996    /// asserts the REAL bridge contract — the resolved --url on argv and the
8997    /// credential via --token-file (the real bridge ignores the env var; the
8998    /// endpoint comes from openclaw-native `gateway.remote.url`, never the
8999    /// schema-invalid `gateway.url`) — then (b) speaks scripted ACP:
9000    /// initialize advertising sessionCapabilities.{list,resume},
9001    /// session/resume rebinding the requested session (join), and a
9002    /// prompted turn.
9003    #[tokio::test]
9004    async fn openclaw_connect_mode_attaches_lists_and_resumes_via_a_mock_bridge() {
9005        let home = connect_scratch_home("openclaw");
9006        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9007        std::fs::write(
9008            home.join(".openclaw/openclaw.json"),
9009            r#"{"gateway": {"remote": {"url": "ws://127.0.0.1:19789"}, "auth": {"mode": "token", "token": "mock-gateway-token"}}}"#,
9010        )
9011        .unwrap();
9012        let script = home.join("openclaw");
9013        std::fs::write(
9014            &script,
9015            r#"#!/bin/sh
9016# Fake `openclaw acp` bridge: verify the connect-mode contract, then speak ACP.
9017[ "$1" = "acp" ] || { echo "unexpected argv: $*" >&2; exit 9; }
9018[ "$2" = "--url" ] && [ "$3" = "ws://127.0.0.1:19789" ] || { echo "missing --url: $*" >&2; exit 9; }
9019[ "$4" = "--token-file" ] || { echo "missing --token-file: $*" >&2; exit 9; }
9020[ "$(cat "$5")" = "mock-gateway-token" ] || { echo "token file wrong" >&2; exit 9; }
9021while IFS= read -r line; do
9022  case "$line" in
9023    *'"initialize"'*)
9024      printf '%s
9025' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"sessionCapabilities":{"list":{},"resume":{}}},"agentInfo":{"name":"openclaw-acp","version":"2026.7.1-2"},"authMethods":[]}}' ;;
9026    *'"session/resume"'*)
9027      printf '%s
9028' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:main"}}' ;;
9029    *'"session/new"'*)
9030      printf '%s
9031' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:fresh"}}' ;;
9032    *'"session/prompt"'*)
9033      printf '%s
9034' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"agent:main:main","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"joined"}}}}'
9035      printf '%s
9036' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}' ;;
9037  esac
9038done
9039"#,
9040        )
9041        .unwrap();
9042        use std::os::unix::fs::PermissionsExt;
9043        std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
9044
9045        let mut descriptor = crate::harness_support_registry()
9046            .harnesses
9047            .into_iter()
9048            .find(|harness| harness.id.as_str() == HarnessId::OPENCLAW)
9049            .expect("openclaw must be registered");
9050        descriptor
9051            .runtime
9052            .connect_launch
9053            .as_mut()
9054            .unwrap()
9055            .config_path = "~/.openclaw/openclaw.json".into();
9056        descriptor.runtime.default_launch.as_mut().unwrap().program =
9057            script.to_string_lossy().into_owned();
9058        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
9059        assert!(backend.capabilities().resume_session);
9060
9061        let joined = backend
9062            .attach(crate::RuntimeAttachRequest {
9063                runtime_id: "agent:main:main".into(),
9064                cwd: Some(home.clone()),
9065                launch: None,
9066            })
9067            .await;
9068        let mut connection = joined.expect("mock bridge attach must succeed");
9069        assert_eq!(connection.handle().runtime_id, "agent:main:main");
9070        let turn = connection
9071            .send_input(crate::RuntimeInput {
9072                text: "hello".into(),
9073                image_urls: Vec::new(),
9074            })
9075            .await;
9076        assert!(turn.is_ok(), "prompt through the mock bridge: {turn:?}");
9077        connection.close().await.unwrap();
9078    }
9079
9080    #[tokio::test]
9081    async fn connect_mode_fails_closed_without_a_protocol_client_or_config() {
9082        let home = connect_scratch_home("fail");
9083        std::fs::write(
9084            home.join("opencode-tui.json"),
9085            r#"{"server": {"url": "http://127.0.0.1:1", "token": "connect-secret"}}"#,
9086        )
9087        .unwrap();
9088
9089        let gateway_only = connect_descriptor("acp-v1-jsonrpc");
9090        let Err(error) = open_connect_descriptor(&gateway_only, &home) else {
9091            panic!("an ACP connect endpoint has no gateway client yet");
9092        };
9093        let message = format!("{error:?}");
9094        assert!(message.contains("acp-v1-jsonrpc"));
9095        assert!(!message.contains("connect-secret"));
9096
9097        let unreadable = connect_descriptor("opencode-http-sse");
9098        let missing_home = connect_scratch_home("missing");
9099        let Err(error) = open_connect_descriptor(&unreadable, &missing_home) else {
9100            panic!("an unreadable connect config must fail closed");
9101        };
9102        let message = format!("{error:?}");
9103        assert!(message.contains("opencode-tui.json"));
9104        assert!(!message.contains("connect-secret"));
9105    }
9106
9107    // ---------------------------------------------------------------------
9108    // ORCH-7 — `harness.v1.jobs.list` / `jobs.get` over the committed fixtures
9109    // ---------------------------------------------------------------------
9110
9111    fn jobs_fixture_root() -> PathBuf {
9112        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
9113    }
9114
9115    /// Point only the three job-bearing homes at the fixtures. Nothing else is
9116    /// read, so the host machine's own harness homes cannot leak into a row.
9117    fn jobs_fixture_homes() -> Value {
9118        let root = jobs_fixture_root();
9119        json!({
9120            "claude_code": root.join("claude_jobs_home/projects"),
9121            "hermes": root.join("hermes_home/state.db"),
9122            "openclaw": root.join("openclaw_home"),
9123        })
9124    }
9125
9126    fn jobs_list(params: Value) -> Value {
9127        let mut service = HarnessSessionService::new();
9128        service.handle(request(1, "harness.v1.jobs.list", params))
9129    }
9130
9131    fn job_row<'a>(result: &'a Value, id: &str) -> &'a Value {
9132        result["jobs"]
9133            .as_array()
9134            .expect("jobs is an array")
9135            .iter()
9136            .find(|job| job["id"] == id)
9137            .unwrap_or_else(|| panic!("no job `{id}` in {result}"))
9138    }
9139
9140    #[test]
9141    fn gateway_health_derives_from_running_probe_and_install_state() {
9142        let running = RunningInstance {
9143            method: RunningInstanceMethod::GatewayConnect,
9144            evidence: "gateway endpoint 127.0.0.1:18789 accepted a TCP connect".into(),
9145            checked_at_ms: 1,
9146        };
9147        let up = gateway_health(
9148            HarnessId::OPENCLAW,
9149            true,
9150            Some(&running),
9151            Some("2026.7.1-2"),
9152        );
9153        assert_eq!(up.state, GatewayState::Up);
9154        assert!(up.endpoint.as_deref().unwrap().starts_with("ws://"));
9155        assert_eq!(up.version.as_deref(), Some("2026.7.1-2"));
9156        // Hermes consults its own `gateway status` when the WAL heuristic says
9157        // nothing; a fake binary decides the verdict (the env var is global, so
9158        // the up/down cases run inside this one test, never in parallel).
9159        let dir = std::env::temp_dir().join(format!("supercode-orch17-{}", std::process::id()));
9160        std::fs::create_dir_all(&dir).unwrap();
9161        let fake = dir.join("hermes");
9162        let write_fake = |body: &str| {
9163            std::fs::write(&fake, format!("#!/bin/sh\n{body}\n")).unwrap();
9164            #[cfg(unix)]
9165            {
9166                use std::os::unix::fs::PermissionsExt;
9167                std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o755)).unwrap();
9168            }
9169        };
9170        write_fake("echo '✗ Gateway service is not installed'");
9171        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| {
9172            *slot.borrow_mut() = Some((
9173                HarnessId::HERMES.to_string(),
9174                fake.to_string_lossy().into_owned(),
9175            ))
9176        });
9177        let down = gateway_health(HarnessId::HERMES, true, None, None);
9178        assert_eq!(down.state, GatewayState::Down, "{down:?}");
9179        assert!(down.endpoint.is_none());
9180        assert!(down.evidence.contains("not installed"));
9181        write_fake("echo 'Launchd plist: /x/ai.hermes.gateway.plist'; echo '✓ Gateway is supervised by launchd (PID 4242)'");
9182        let idle_but_up = gateway_health(HarnessId::HERMES, true, None, Some("0.21.0"));
9183        assert_eq!(idle_but_up.state, GatewayState::Up, "{idle_but_up:?}");
9184        assert!(idle_but_up.evidence.contains("PID 4242"));
9185        write_fake("echo 'something unparseable'");
9186        let no_verdict = gateway_health(HarnessId::HERMES, true, None, None);
9187        assert_eq!(no_verdict.state, GatewayState::Down);
9188        assert!(no_verdict.evidence.contains("no verdict"));
9189        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| *slot.borrow_mut() = None);
9190        let absent = gateway_health(HarnessId::HERMES, false, None, None);
9191        assert_eq!(absent.state, GatewayState::Unknown);
9192        let core = gateway_health(HarnessId::CODEX, true, None, Some("0.144.4"));
9193        assert_eq!(core.state, GatewayState::Unknown);
9194        assert!(core.evidence.contains("per session"));
9195    }
9196
9197    #[test]
9198    fn triggers_list_reads_both_stores_and_never_emits_secrets() {
9199        let response = triggers_list(json!({"homes": jobs_fixture_homes()}));
9200        let rows = response["result"]["triggers"]
9201            .as_array()
9202            .expect("triggers")
9203            .clone();
9204        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
9205        assert!(
9206            hermes.iter().any(|r| r["name"] == "deploys"
9207                && r["route"] == "/webhooks/deploys"
9208                && r["kind"] == "webhook"),
9209            "{rows:#?}"
9210        );
9211        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
9212        assert!(openclaw
9213            .iter()
9214            .any(|r| r["name"] == "wake" && r["kind"] == "builtin_wake"));
9215        assert!(openclaw.iter().any(|r| r["name"] == "gmail"
9216            && r["kind"] == "hook_mapping"
9217            && r["target"]["action"] == "agent"));
9218        let rendered = response.to_string();
9219        for secret in [
9220            "FAKE-WEBHOOK-HMAC-DO-NOT-EMIT",
9221            "FAKE-HOOK-TOKEN-DO-NOT-EMIT",
9222        ] {
9223            assert!(!rendered.contains(secret), "{rendered}");
9224        }
9225        let refused =
9226            triggers_list(json!({"harness": "claude-code", "homes": jobs_fixture_homes()}));
9227        assert_eq!(refused["error"]["code"], -32020, "{refused}");
9228    }
9229
9230    fn triggers_list(params: Value) -> Value {
9231        let mut service = HarnessSessionService::new();
9232        service.handle(request(1, "harness.v1.triggers.list", params))
9233    }
9234
9235    #[test]
9236    fn routes_list_reads_both_gateway_configs_and_flags_the_defaults() {
9237        let response = routes_list(json!({"homes": jobs_fixture_homes()}));
9238        let rows = response["result"]["routes"]
9239            .as_array()
9240            .expect("routes")
9241            .clone();
9242        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
9243        assert_eq!(hermes.len(), 2, "{rows:#?}");
9244        assert_eq!(hermes[0]["target"], "coder");
9245        assert_eq!(hermes[0]["match"]["platform"], "slack");
9246        assert_eq!(hermes[0]["match"]["chat_id"], "C0FIXTURE");
9247        assert_eq!(hermes[0]["specificity"], 4);
9248        assert_eq!(hermes[1]["default"], true);
9249        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
9250        assert!(
9251            openclaw.iter().any(|r| r["target"] == "design"
9252                && r["match"]["platform"] == "slack"
9253                && r["specificity"] == 1),
9254            "{openclaw:#?}"
9255        );
9256        assert!(openclaw.iter().any(|r| r["default"] == true));
9257        // A core harness has no routing concept and is refused, never an empty list.
9258        let refused = routes_list(json!({"harness": "codex", "homes": jobs_fixture_homes()}));
9259        assert_eq!(refused["error"]["code"], -32020, "{refused}");
9260    }
9261
9262    fn routes_list(params: Value) -> Value {
9263        let mut service = HarnessSessionService::new();
9264        service.handle(request(1, "harness.v1.routes.list", params))
9265    }
9266
9267    #[test]
9268    fn jobs_list_projects_every_fixture_store_onto_the_uniform_row() {
9269        let response = jobs_list(json!({"homes": jobs_fixture_homes()}));
9270        let result = &response["result"];
9271        let ids: Vec<&str> = result["jobs"]
9272            .as_array()
9273            .unwrap()
9274            .iter()
9275            .map(|job| job["id"].as_str().unwrap())
9276            .collect();
9277        assert_eq!(
9278            ids,
9279            vec![
9280                "release-watch",
9281                "toolu_wake_recheck",
9282                "digest-15m",
9283                "nightly-audit",
9284                "coder-standup",
9285                "ops-once-boot",
9286                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
9287                "8bb7d938-ca46-4a6d-90eb-c92331155566",
9288                "cron_standup",
9289                "cron_reindex",
9290            ],
9291            "{result}"
9292        );
9293
9294        // OpenClaw, pinned shape: rows come from `state/openclaw.sqlite`
9295        // (`cron_jobs.job_json` + runtime columns), captured from a real
9296        // 2026.7.1-2 gateway.
9297        let health = job_row(result, "85ad7832-896f-42be-af31-3e1ed2fbdc4b");
9298        assert_eq!(health["harness"], "openclaw");
9299        assert_eq!(health["schedule"]["kind"], "interval");
9300        assert_eq!(health["schedule"]["minutes"], 10.0);
9301        assert_eq!(health["session_target"], "isolated");
9302        assert_eq!(health["payload"]["kind"], "prompt");
9303        assert_eq!(health["payload"]["text"], "nightly health check");
9304        // ORCH-13: the mode word (`announce`) and the channel it announces on
9305        // (`last`) are separate facts, and the store keeps both — in
9306        // `job_json.delivery` and in the `delivery_*` columns beside it.
9307        assert_eq!(health["deliver"]["mode"], "announce");
9308        assert_eq!(health["deliver"]["target"], "last");
9309        assert_eq!(health["next_run_at"], "2026-09-03T06:52:26Z");
9310        let digest = job_row(result, "8bb7d938-ca46-4a6d-90eb-c92331155566");
9311        assert_eq!(digest["schedule"]["kind"], "cron");
9312        assert_eq!(digest["schedule"]["expr"], "0 9 * * 1");
9313        assert_eq!(digest["session_target"], "main");
9314        assert_eq!(digest["payload"]["kind"], "system_event");
9315
9316        // Claude Code: session-scoped, one recurring cron and one one-shot wakeup.
9317        let cron = job_row(result, "release-watch");
9318        assert_eq!(cron["harness"], "claude-code");
9319        assert_eq!(cron["scope"], "session");
9320        assert_eq!(cron["session_id"], "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
9321        assert_eq!(cron["schedule"]["kind"], "cron");
9322        assert_eq!(cron["schedule"]["expr"], "*/10 * * * *");
9323        assert_eq!(cron["schedule"]["display"], "*/10 * * * *");
9324        assert_eq!(cron["payload"]["kind"], "prompt");
9325        assert_eq!(cron["recurring"], true);
9326        assert_eq!(cron["deliver"]["target"], "session");
9327        let wakeup = job_row(result, "toolu_wake_recheck");
9328        assert_eq!(wakeup["payload"]["kind"], "wakeup");
9329        assert_eq!(wakeup["schedule"]["kind"], "once");
9330        assert_eq!(wakeup["recurring"], false);
9331        assert_eq!(wakeup["state"], "pending");
9332
9333        // Hermes: install-scoped, interval + origin delivery, and a paused cron.
9334        let interval = job_row(result, "digest-15m");
9335        assert_eq!(interval["harness"], "hermes");
9336        assert_eq!(interval["scope"], "install");
9337        assert_eq!(interval["profile"], Value::Null);
9338        assert_eq!(interval["schedule"]["kind"], "interval");
9339        assert_eq!(interval["schedule"]["minutes"], 15.0);
9340        assert_eq!(interval["schedule"]["display"], "every 15 min");
9341        assert_eq!(interval["deliver"]["target"], "origin");
9342        assert_eq!(interval["deliver"]["chat_id"], "-1002233445566");
9343        assert_eq!(interval["next_run_at"], "2026-09-02T11:15:00Z");
9344        assert_eq!(interval["last_status"], "ok");
9345        let nightly = job_row(result, "nightly-audit");
9346        assert_eq!(nightly["schedule"]["expr"], "0 3 * * *");
9347        assert_eq!(nightly["deliver"]["target"], "local");
9348        assert_eq!(nightly["enabled"], false);
9349        assert_eq!(nightly["state"], "paused");
9350        // The per-profile store carries the profile name from its own path.
9351        let profiled = job_row(result, "ops-once-boot");
9352        assert_eq!(profiled["profile"], "ops");
9353        assert_eq!(profiled["schedule"]["kind"], "once");
9354        assert_eq!(profiled["schedule"]["run_at"], "2026-09-03T06:00:00Z");
9355        assert_eq!(profiled["payload"]["kind"], "script");
9356        // An explicit `<platform>:<chat>` target carries the chat itself.
9357        assert_eq!(profiled["deliver"]["target"], "slack:C0429ABCD");
9358        assert_eq!(profiled["deliver"]["chat_id"], "C0429ABCD");
9359        assert_eq!(profiled["recurring"], false);
9360
9361        // ORCH-13: a job delivering to its creating conversation carries that
9362        // conversation's whole surface — platform word, chat AND thread.
9363        let standup_to_group = job_row(result, "coder-standup");
9364        assert_eq!(standup_to_group["deliver"]["target"], "origin");
9365        assert_eq!(standup_to_group["deliver"]["chat_id"], "-100777");
9366        assert_eq!(standup_to_group["deliver"]["thread_id"], "55");
9367        // Hermes has no mode word and routes by adapter profile, not account.
9368        assert!(standup_to_group["deliver"]["mode"].is_null());
9369        assert!(standup_to_group["deliver"]["account"].is_null());
9370
9371        // OpenClaw: the session target and the delivery mode are the row's own
9372        // columns, not a footnote.
9373        let standup = job_row(result, "cron_standup");
9374        assert_eq!(standup["harness"], "openclaw");
9375        assert_eq!(standup["session_target"], "isolated");
9376        assert_eq!(standup["deliver"]["mode"], "announce");
9377        assert_eq!(standup["deliver"]["target"], "slack");
9378        assert_eq!(standup["deliver"]["chat_id"], "C0429ABCD");
9379        assert_eq!(standup["payload"]["kind"], "prompt");
9380        assert_eq!(standup["profile"], "main");
9381        let reindex = job_row(result, "cron_reindex");
9382        assert_eq!(reindex["session_target"], "main");
9383        assert_eq!(reindex["payload"]["kind"], "system_event");
9384        assert_eq!(reindex["schedule"]["kind"], "interval");
9385        assert_eq!(reindex["schedule"]["display"], "every 240 min");
9386        assert_eq!(reindex["enabled"], false);
9387
9388        // Every store consulted is named, so an empty answer is never silent.
9389        let states: Vec<(&str, &str)> = result["sources"]
9390            .as_array()
9391            .unwrap()
9392            .iter()
9393            .map(|source| {
9394                (
9395                    source["harness"].as_str().unwrap(),
9396                    source["state"].as_str().unwrap(),
9397                )
9398            })
9399            .collect();
9400        // The `coder` profile home has no cron store at all: it is named as
9401        // `absent_store`, not skipped, so "this profile schedules nothing" and
9402        // "this profile was never looked at" stay distinguishable.
9403        assert_eq!(
9404            states,
9405            vec![
9406                ("claude-code", "scanned"),
9407                ("hermes", "read"),
9408                ("hermes", "absent_store"),
9409                ("hermes", "read"),
9410                ("openclaw", "read"),
9411                ("openclaw", "read"),
9412            ],
9413            "{result}"
9414        );
9415    }
9416
9417    #[test]
9418    fn jobs_list_filters_by_harness_session_and_profile() {
9419        let by_harness = jobs_list(json!({"harness": "openclaw", "homes": jobs_fixture_homes()}));
9420        let ids: Vec<&str> = by_harness["result"]["jobs"]
9421            .as_array()
9422            .unwrap()
9423            .iter()
9424            .map(|job| job["id"].as_str().unwrap())
9425            .collect();
9426        assert_eq!(
9427            ids,
9428            vec![
9429                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
9430                "8bb7d938-ca46-4a6d-90eb-c92331155566",
9431                "cron_standup",
9432                "cron_reindex",
9433            ]
9434        );
9435
9436        let by_session = jobs_list(json!({
9437            "session": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
9438            "homes": jobs_fixture_homes(),
9439        }));
9440        let jobs = by_session["result"]["jobs"].as_array().unwrap();
9441        assert_eq!(jobs.len(), 2, "{by_session}");
9442        assert!(jobs
9443            .iter()
9444            .all(|job| job["harness"] == "claude-code" && job["scope"] == "session"));
9445
9446        let by_profile = jobs_list(json!({
9447            "harness": "hermes",
9448            "profile": "ops",
9449            "homes": jobs_fixture_homes(),
9450        }));
9451        let jobs = by_profile["result"]["jobs"].as_array().unwrap();
9452        assert_eq!(jobs.len(), 1, "{by_profile}");
9453        assert_eq!(jobs[0]["id"], "ops-once-boot");
9454    }
9455
9456    #[test]
9457    fn jobs_get_answers_with_the_row_and_the_verbatim_native_record() {
9458        let mut service = HarnessSessionService::new();
9459        let hermes = service.handle(request(
9460            1,
9461            "harness.v1.jobs.get",
9462            json!({"harness": "hermes", "id": "digest-15m", "homes": jobs_fixture_homes()}),
9463        ));
9464        assert_eq!(hermes["result"]["job"]["schedule"]["kind"], "interval");
9465        // Native fields the uniform row does not carry survive on `source`.
9466        assert_eq!(hermes["result"]["source"]["provider"], "nous");
9467        assert_eq!(hermes["result"]["source"]["failure_deliver"], "local");
9468
9469        let claude = service.handle(request(
9470            2,
9471            "harness.v1.jobs.get",
9472            json!({"harness": "claude-code", "id": "release-watch", "homes": jobs_fixture_homes()}),
9473        ));
9474        assert_eq!(claude["result"]["job"]["payload"]["kind"], "prompt");
9475        assert_eq!(
9476            claude["result"]["source"]["tool_use_id"],
9477            "toolu_cron_release_watch"
9478        );
9479
9480        let missing = service.handle(request(
9481            3,
9482            "harness.v1.jobs.get",
9483            json!({"harness": "hermes", "id": "no-such-job", "homes": jobs_fixture_homes()}),
9484        ));
9485        assert!(missing["error"]["message"]
9486            .as_str()
9487            .is_some_and(|message| message.contains("no scheduled job `no-such-job`")));
9488    }
9489
9490    #[test]
9491    fn jobs_refuse_a_harness_without_a_scheduled_job_concept() {
9492        let mut service = HarnessSessionService::new();
9493        for (id, method, params) in [
9494            (
9495                1,
9496                "harness.v1.jobs.list",
9497                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
9498            ),
9499            (
9500                2,
9501                "harness.v1.jobs.get",
9502                json!({"harness": "codex", "id": "anything"}),
9503            ),
9504        ] {
9505            let response = service.handle(request(id, method, params));
9506            assert_eq!(response["error"]["code"], -32020, "{response}");
9507            assert!(response["error"]["message"]
9508                .as_str()
9509                .is_some_and(|message| message.contains("has no scheduled jobs")));
9510            assert!(response.get("result").is_none());
9511        }
9512    }
9513
9514    #[test]
9515    fn jobs_list_reports_a_migrated_openclaw_store_as_absent_instead_of_failing() {
9516        let scratch = std::env::temp_dir().join(format!(
9517            "supercode-jobs-migrated-{}-{}",
9518            std::process::id(),
9519            generated_session_id()
9520        ));
9521        std::fs::create_dir_all(&scratch).unwrap();
9522        let response = jobs_list(json!({
9523            "harness": "openclaw",
9524            "homes": {"openclaw": scratch.clone()},
9525        }));
9526        let result = &response["result"];
9527        assert_eq!(result["jobs"].as_array().unwrap().len(), 0, "{result}");
9528        assert_eq!(result["sources"][0]["state"], "absent_store");
9529        assert_eq!(result["sources"][0]["harness"], "openclaw");
9530        std::fs::remove_dir_all(&scratch).ok();
9531    }
9532
9533    // ---------------------------------------------------------------------
9534    // ORCH-8 — `harness.v1.runs.list` / `runs.get` over the committed fire
9535    // stores: Hermes's `cron/executions.db` (root home + profile home) and
9536    // OpenClaw's `cron_run_logs`. Every fixture row is written by
9537    // `tests/fixtures/gen_runs_fixtures.py` against the harnesses' own DDL.
9538    // ---------------------------------------------------------------------
9539
9540    /// The health job in the committed OpenClaw fixture, which fired twice.
9541    const OPENCLAW_HEALTH_JOB: &str = "85ad7832-896f-42be-af31-3e1ed2fbdc4b";
9542    /// The digest job, whose single fire predates run ids.
9543    const OPENCLAW_DIGEST_JOB: &str = "8bb7d938-ca46-4a6d-90eb-c92331155566";
9544
9545    fn runs_list(params: Value) -> Value {
9546        let mut service = HarnessSessionService::new();
9547        service.handle(request(1, "harness.v1.runs.list", params))
9548    }
9549
9550    fn run_row<'a>(result: &'a Value, id: &str) -> &'a Value {
9551        result["runs"]
9552            .as_array()
9553            .expect("runs is an array")
9554            .iter()
9555            .find(|run| run["id"] == id)
9556            .unwrap_or_else(|| panic!("no run `{id}` in {result}"))
9557    }
9558
9559    #[test]
9560    fn runs_list_projects_both_fixture_stores_onto_the_uniform_row() {
9561        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
9562        let result = &response["result"];
9563        let ids: Vec<&str> = result["runs"]
9564            .as_array()
9565            .expect("runs is an array")
9566            .iter()
9567            .map(|run| run["id"].as_str().unwrap())
9568            .collect();
9569        let digest_fire = format!("{OPENCLAW_DIGEST_JOB}#1");
9570        assert_eq!(
9571            ids,
9572            vec![
9573                // Hermes, newest claim first, root ledger then profile ledger.
9574                "b2c3d4e5f60718293a4b5c6d7e8f9012",
9575                "a1b2c3d4e5f60718293a4b5c6d7e8f90",
9576                "c3d4e5f60718293a4b5c6d7e8f901234",
9577                "f60718293a4b5c6d7e8f901234567890",
9578                "e5f60718293a4b5c6d7e8f9012345678",
9579                "d4e5f60718293a4b5c6d7e8f90123456",
9580                // OpenClaw, newest `ts` first.
9581                "run_health_0002",
9582                digest_fire.as_str(),
9583                "run_health_0001",
9584            ],
9585            "{result}"
9586        );
9587
9588        // The harness's OWN outcome word survives; nothing is renamed onto a
9589        // shared vocabulary.
9590        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
9591        assert_eq!(failed["harness"], "hermes");
9592        assert_eq!(failed["job_id"], "job42");
9593        assert_eq!(failed["status"], "failed");
9594        assert_eq!(failed["error"], "provider returned 500 after 3 attempts");
9595        assert_eq!(failed["claimed_at"], "2026-09-02T13:05:00.100442");
9596
9597        // Hermes's `unknown` — an attempt whose owner died before writing a
9598        // terminal state — is a fourth status, not folded into `failed`.
9599        let abandoned = run_row(result, "d4e5f60718293a4b5c6d7e8f90123456");
9600        assert_eq!(abandoned["status"], "unknown");
9601        assert_eq!(abandoned["job_id"], "ops-once-boot");
9602
9603        // An unterminated fire has no finish, and no session is invented.
9604        let running = run_row(result, "c3d4e5f60718293a4b5c6d7e8f901234");
9605        assert_eq!(running["status"], "running");
9606        assert!(running["finished_at"].is_null(), "{running}");
9607        assert!(running["session_id"].is_null(), "{running}");
9608
9609        // OpenClaw records the session on the row itself, and epoch-ms
9610        // timestamps are rendered as RFC 3339.
9611        let ok = run_row(result, "run_health_0001");
9612        assert_eq!(ok["harness"], "openclaw");
9613        assert_eq!(ok["job_id"], OPENCLAW_HEALTH_JOB);
9614        assert_eq!(ok["status"], "ok");
9615        assert_eq!(ok["started_at"], "2026-09-02T08:30:00.000Z");
9616        assert_eq!(ok["finished_at"], "2026-09-02T08:30:30.000Z");
9617        assert_eq!(ok["session_id"], "3dd577ae-a0a3-4b5b-8063-f402be4f5fd4");
9618        // OpenClaw's run log is written once, at finish: there is no claim.
9619        assert!(ok["claimed_at"].is_null(), "{ok}");
9620
9621        // A run-log row with no `run_id` falls back to the store's own
9622        // `(job_id, seq)` key rather than being dropped.
9623        assert_eq!(run_row(result, &digest_fire)["status"], "skipped");
9624
9625        // ORCH-13: a fire whose delivery nothing recorded says so, rather than
9626        // borrowing a neighbouring fire's outcome. Both of these ran on jobs
9627        // that deliver `local` (or have no job record at all), so no
9628        // obligation is addressed to a surface they could match.
9629        for id in [
9630            "b2c3d4e5f60718293a4b5c6d7e8f9012",
9631            "d4e5f60718293a4b5c6d7e8f90123456",
9632        ] {
9633            assert!(run_row(result, id)["delivery"].is_null(), "{id}");
9634        }
9635
9636        // Every store consulted is named, including the profile home that has
9637        // no ledger — an empty history and an absent store are different.
9638        let sources = result["sources"].as_array().unwrap();
9639        let states: Vec<(&str, &str)> = sources
9640            .iter()
9641            .map(|source| {
9642                (
9643                    source["harness"].as_str().unwrap(),
9644                    source["state"].as_str().unwrap(),
9645                )
9646            })
9647            .collect();
9648        assert_eq!(
9649            states,
9650            vec![
9651                ("hermes", "read"),
9652                ("hermes", "absent_store"),
9653                ("hermes", "read"),
9654                ("openclaw", "read"),
9655            ],
9656            "{result}"
9657        );
9658        assert_eq!(sources[2]["profile"], "ops");
9659        assert!(sources[3]["path"]
9660            .as_str()
9661            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
9662    }
9663
9664    #[test]
9665    fn runs_list_joins_a_hermes_fire_to_the_session_it_opened() {
9666        let response = runs_list(json!({
9667            "harness": "hermes",
9668            "job": "job42",
9669            "homes": jobs_fixture_homes(),
9670        }));
9671        let result = &response["result"];
9672        assert_eq!(result["runs"].as_array().unwrap().len(), 2, "{result}");
9673
9674        // Hermes writes NO link from an execution to its session. The fire
9675        // that ran the agent is joined to `cron_job42_<stamp>` because that
9676        // id's instant falls inside its [claimed_at, finished_at] window.
9677        let ran = run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90");
9678        assert_eq!(ran["session_id"], "cron_job42_20260902_120000");
9679
9680        // The later fire failed before opening one. Its window holds no
9681        // session, so the row says so instead of re-using the earlier fire's
9682        // — the join is per-FIRE, not per-job.
9683        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
9684        assert!(failed["session_id"].is_null(), "{failed}");
9685    }
9686
9687    /// ORCH-13: where a fire's output went, read from each harness's own
9688    /// delivery record — Hermes's `delivery_obligations` ledger inside
9689    /// `state.db`, OpenClaw's `delivery_*` run-log columns.
9690    #[test]
9691    fn runs_list_reads_the_delivery_each_harness_recorded_for_a_fire() {
9692        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
9693        let result = &response["result"];
9694
9695        // Hermes: the ledger is the GATEWAY's, keyed by conversation and
9696        // surface, so the fire's own [claimed_at, finished_at] window picks
9697        // the obligation. The fire succeeded and so did the send.
9698        let delivered = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
9699        assert_eq!(delivered["status"], "completed");
9700        assert_eq!(delivered["delivery"]["state"], "delivered");
9701        assert_eq!(delivered["delivery"]["target"], "telegram:-100777:55");
9702        assert_eq!(delivered["delivery"]["attempts"], 1);
9703        assert!(delivered["delivery"]["last_error"].is_null(), "{delivered}");
9704        assert_eq!(
9705            delivered["delivery"]["delivered_at"],
9706            "2026-09-02T09:00:30.400Z"
9707        );
9708
9709        // The next fire of the same job ALSO succeeded — and its output never
9710        // arrived. That is the fact `status` alone cannot carry.
9711        let undelivered = run_row(result, "f60718293a4b5c6d7e8f901234567890");
9712        assert_eq!(undelivered["status"], "completed");
9713        assert_eq!(undelivered["delivery"]["state"], "failed");
9714        assert_eq!(undelivered["delivery"]["attempts"], 3);
9715        assert_eq!(
9716            undelivered["delivery"]["last_error"],
9717            "telegram send failed: Bad Request: chat not found"
9718        );
9719        // Only a delivered obligation carries an instant of delivery; the
9720        // ledger's `updated_at` on a failed row dates the failure.
9721        assert!(
9722            undelivered["delivery"]["delivered_at"].is_null(),
9723            "{undelivered}"
9724        );
9725
9726        // OpenClaw writes the outcome onto the run-log row and declares the
9727        // address on the job, so the row's target is joined from `cron_jobs`.
9728        let announced = run_row(result, "run_health_0001");
9729        assert_eq!(announced["delivery"]["state"], "delivered");
9730        assert_eq!(announced["delivery"]["target"], "last");
9731        // Its run log counts no attempts and stamps no delivered-at.
9732        assert!(announced["delivery"]["attempts"].is_null(), "{announced}");
9733        assert!(
9734            announced["delivery"]["delivered_at"].is_null(),
9735            "{announced}"
9736        );
9737        let refused = run_row(result, "run_health_0002");
9738        assert_eq!(refused["delivery"]["state"], "not-delivered");
9739        assert_eq!(refused["delivery"]["last_error"], "channel_not_found");
9740
9741        // A run-log row with no delivery columns at all recorded no delivery:
9742        // the job's declared target is not evidence that anything was sent.
9743        let skipped = run_row(result, &format!("{OPENCLAW_DIGEST_JOB}#1"));
9744        assert!(skipped["delivery"].is_null(), "{skipped}");
9745    }
9746
9747    /// A Hermes fire whose session carries a `session_key` is matched on that
9748    /// key FIRST — the most specific question the ledger can answer. Proven by
9749    /// moving the obligations off the job's surface on a COPY of the fixture,
9750    /// so only the session-key question can still find them.
9751    #[test]
9752    fn runs_list_matches_a_hermes_obligation_by_the_session_key_first() {
9753        let scratch = std::env::temp_dir().join(format!(
9754            "supercode-runs-delivery-{}-{}",
9755            std::process::id(),
9756            generated_session_id()
9757        ));
9758        std::fs::create_dir_all(scratch.join("cron")).unwrap();
9759        let fixture = jobs_fixture_root().join("hermes_home");
9760        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
9761        for name in ["cron/executions.db", "cron/jobs.json"] {
9762            std::fs::copy(fixture.join(name), scratch.join(name)).unwrap();
9763        }
9764        {
9765            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
9766            // The obligations now sit on a surface no job in this store
9767            // delivers to, so the surface question cannot match them.
9768            connection
9769                .execute(
9770                    "UPDATE delivery_obligations SET platform = 'slack', chat_id = 'C0FALLBACK'",
9771                    [],
9772                )
9773                .unwrap();
9774            // A cron fire that ran inside a keyed conversation: the session
9775            // the window recovers carries `tg-coder-1`'s key.
9776            connection
9777                .execute(
9778                    "INSERT INTO sessions (id, source, session_key, started_at) VALUES \
9779                     ('cron_coder-standup_20260902_090010', 'cron', \
9780                      'agent:coder:telegram:group:-100777:55', 1788339610.0)",
9781                    [],
9782                )
9783                .unwrap();
9784        }
9785        let response = runs_list(json!({
9786            "harness": "hermes",
9787            "job": "coder-standup",
9788            "homes": {"hermes": scratch.join("state.db")},
9789        }));
9790        let result = &response["result"];
9791        let matched = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
9792        assert_eq!(
9793            matched["session_id"], "cron_coder-standup_20260902_090010",
9794            "{result}"
9795        );
9796        assert_eq!(matched["delivery"]["state"], "delivered", "{result}");
9797        assert_eq!(
9798            matched["delivery"]["target"], "slack:C0FALLBACK:55",
9799            "{result}"
9800        );
9801        std::fs::remove_dir_all(&scratch).ok();
9802    }
9803
9804    #[test]
9805    fn runs_list_follows_a_compression_chain_to_the_readable_tip() {
9806        // A fire whose session was compressed mid-run is only readable at the
9807        // continuation, so that is what the row must report. Built on a COPY
9808        // of the committed fixture: no test writes to a fixture or to a real
9809        // harness home.
9810        let scratch = std::env::temp_dir().join(format!(
9811            "supercode-runs-compressed-{}-{}",
9812            std::process::id(),
9813            generated_session_id()
9814        ));
9815        std::fs::create_dir_all(scratch.join("cron")).unwrap();
9816        let fixture = jobs_fixture_root().join("hermes_home");
9817        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
9818        std::fs::copy(
9819            fixture.join("cron/executions.db"),
9820            scratch.join("cron/executions.db"),
9821        )
9822        .unwrap();
9823        {
9824            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
9825            connection
9826                .execute(
9827                    "UPDATE sessions SET end_reason = 'compression' WHERE id = ?1",
9828                    ["cron_job42_20260902_120000"],
9829                )
9830                .unwrap();
9831            connection
9832                .execute(
9833                    "INSERT INTO sessions (id, source, parent_session_id, started_at) \
9834                     VALUES ('job42-after-compaction', 'cron', \
9835                             'cron_job42_20260902_120000', 1788350000.0)",
9836                    [],
9837                )
9838                .unwrap();
9839        }
9840        let response = runs_list(json!({
9841            "harness": "hermes",
9842            "job": "job42",
9843            "homes": {"hermes": scratch.join("state.db")},
9844        }));
9845        let result = &response["result"];
9846        assert_eq!(
9847            run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90")["session_id"],
9848            "job42-after-compaction",
9849            "{result}"
9850        );
9851        std::fs::remove_dir_all(&scratch).ok();
9852    }
9853
9854    #[test]
9855    fn runs_list_filters_by_job_and_caps_by_limit() {
9856        let by_job = runs_list(json!({
9857            "harness": "openclaw",
9858            "job": OPENCLAW_HEALTH_JOB,
9859            "homes": jobs_fixture_homes(),
9860        }));
9861        let ids: Vec<&str> = by_job["result"]["runs"]
9862            .as_array()
9863            .unwrap()
9864            .iter()
9865            .map(|run| run["id"].as_str().unwrap())
9866            .collect();
9867        assert_eq!(ids, vec!["run_health_0002", "run_health_0001"], "{by_job}");
9868
9869        let capped = runs_list(json!({
9870            "harness": "openclaw",
9871            "limit": 1,
9872            "homes": jobs_fixture_homes(),
9873        }));
9874        let runs = capped["result"]["runs"].as_array().unwrap();
9875        assert_eq!(runs.len(), 1, "{capped}");
9876        // Newest first, so the cap keeps the recent fire.
9877        assert_eq!(runs[0]["id"], "run_health_0002");
9878    }
9879
9880    #[test]
9881    fn runs_get_answers_with_the_row_and_the_verbatim_native_record() {
9882        let mut service = HarnessSessionService::new();
9883        let hermes = service.handle(request(
9884            1,
9885            "harness.v1.runs.get",
9886            json!({
9887                "harness": "hermes",
9888                "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
9889                "homes": jobs_fixture_homes(),
9890            }),
9891        ));
9892        assert_eq!(hermes["result"]["run"]["status"], "completed");
9893        assert_eq!(
9894            hermes["result"]["run"]["session_id"],
9895            "cron_job42_20260902_120000"
9896        );
9897        // Ledger columns the uniform row does not carry survive on `source`.
9898        assert_eq!(hermes["result"]["source"]["source"], "scheduler");
9899        assert_eq!(hermes["result"]["source"]["pid"], 4242);
9900        assert_eq!(hermes["result"]["source"]["process_id"], "9f1c2d");
9901
9902        let openclaw = service.handle(request(
9903            2,
9904            "harness.v1.runs.get",
9905            json!({
9906                "harness": "openclaw",
9907                "id": "run_health_0002",
9908                "homes": jobs_fixture_homes(),
9909            }),
9910        ));
9911        assert_eq!(openclaw["result"]["run"]["status"], "error");
9912        // ORCH-13: the run's delivery is projected AND the store's own columns
9913        // stay verbatim on `source`, so nothing about the fire is lost.
9914        assert_eq!(
9915            openclaw["result"]["source"]["delivery_status"],
9916            "not-delivered"
9917        );
9918        assert_eq!(
9919            openclaw["result"]["source"]["delivery_error"],
9920            "channel_not_found"
9921        );
9922        assert_eq!(openclaw["result"]["source"]["delivered"], 0);
9923        assert_eq!(
9924            openclaw["result"]["run"]["delivery"]["state"],
9925            "not-delivered"
9926        );
9927        assert_eq!(
9928            openclaw["result"]["run"]["delivery"]["last_error"],
9929            "channel_not_found"
9930        );
9931
9932        let missing = service.handle(request(
9933            3,
9934            "harness.v1.runs.get",
9935            json!({"harness": "hermes", "id": "no-such-run", "homes": jobs_fixture_homes()}),
9936        ));
9937        assert!(missing["error"]["message"]
9938            .as_str()
9939            .is_some_and(|message| message.contains("no run `no-such-run`")));
9940    }
9941
9942    #[test]
9943    fn runs_refuse_a_harness_that_keeps_no_run_store() {
9944        let mut service = HarnessSessionService::new();
9945        for (id, method, params) in [
9946            // Claude Code HAS scheduled jobs but no fire store: its fires are
9947            // ordinary turns. It must refuse, not answer with an empty list.
9948            (
9949                1,
9950                "harness.v1.runs.list",
9951                json!({"harness": "claude-code", "homes": jobs_fixture_homes()}),
9952            ),
9953            (
9954                2,
9955                "harness.v1.runs.get",
9956                json!({"harness": "claude-code", "id": "anything"}),
9957            ),
9958            (
9959                3,
9960                "harness.v1.runs.list",
9961                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
9962            ),
9963        ] {
9964            let response = service.handle(request(id, method, params));
9965            assert_eq!(response["error"]["code"], -32020, "{response}");
9966            assert!(response["error"]["message"]
9967                .as_str()
9968                .is_some_and(|message| message.contains("keeps no run store")));
9969            assert!(response.get("result").is_none());
9970        }
9971    }
9972
9973    #[test]
9974    fn runs_list_reports_an_install_with_no_run_store_as_absent() {
9975        let scratch = std::env::temp_dir().join(format!(
9976            "supercode-runs-empty-{}-{}",
9977            std::process::id(),
9978            generated_session_id()
9979        ));
9980        std::fs::create_dir_all(&scratch).unwrap();
9981        let response = runs_list(json!({
9982            "harness": "openclaw",
9983            "homes": {"openclaw": scratch.clone()},
9984        }));
9985        let result = &response["result"];
9986        assert_eq!(result["runs"].as_array().unwrap().len(), 0, "{result}");
9987        assert_eq!(result["sources"][0]["state"], "absent_store");
9988        assert!(result["sources"][0]["path"]
9989            .as_str()
9990            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
9991        std::fs::remove_dir_all(&scratch).ok();
9992    }
9993}