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(runtime) = self.runtimes.get_mut(&params.connection) else {
1635                    return Err(ServiceError::InvalidParams(format!(
1636                        "unknown runtime connection `{}`",
1637                        params.connection
1638                    )));
1639                };
1640                // A failed cleanup still has an owner and must be retryable.
1641                // Do not discard the lease, approvals, or routing hints first.
1642                let runtime_id = runtime.handle().runtime_id.clone();
1643                runtime.close().await.map_err(operation)?;
1644                self.runtimes.remove(&params.connection);
1645                self.terminal_launches.remove(&params.connection);
1646                self.runtime_sequences.remove(&runtime_id);
1647                self.approvals.forget(&params.connection);
1648                Ok(json!({"closed": true}))
1649            }
1650            _ => Err(ServiceError::MethodNotFound),
1651        }
1652    }
1653
1654    /// Deliver one message into a session that is running right now.
1655    #[cfg(feature = "adapter-api")]
1656    async fn message_call(&self, params: Value) -> std::result::Result<Value, ServiceError> {
1657        let params = decode::<MessageSessionParams>(params)?;
1658        Ok(message_live_session(&params, &crate::claude_peer::ProcessCourierRunner).await)
1659    }
1660
1661    #[cfg(feature = "adapter-api")]
1662    fn harness_settings_call(
1663        &self,
1664        method: &str,
1665        params: Value,
1666    ) -> std::result::Result<Value, ServiceError> {
1667        let homes = crate::HarnessHomes::default();
1668        match method {
1669            "harness.v1.harnesses.settings" => {
1670                let params = decode::<HarnessSettingsParams>(params)?;
1671                let report = crate::inspect_harness_interop_settings(&homes, &params.harness)
1672                    .map_err(|error| ServiceError::Operation(error.to_string()))?;
1673                serde_json::to_value(report)
1674                    .map_err(|error| ServiceError::Operation(error.to_string()))
1675            }
1676            "harness.v1.harnesses.configure" => {
1677                let params = decode::<ConfigureHarnessParams>(params)?;
1678                let report = crate::configure_harness_interop_settings(
1679                    &homes,
1680                    &params.harness,
1681                    &params.changes,
1682                    params.expected_revision.as_deref(),
1683                )
1684                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1685                serde_json::to_value(report)
1686                    .map_err(|error| ServiceError::Operation(error.to_string()))
1687            }
1688            _ => Err(ServiceError::MethodNotFound),
1689        }
1690    }
1691
1692    fn insert_runtime(
1693        &mut self,
1694        runtime: Box<dyn RuntimeConnection>,
1695    ) -> std::result::Result<Value, ServiceError> {
1696        let connection = format!("runtime-{}", self.next_runtime);
1697        self.next_runtime += 1;
1698        let handle = runtime.handle().clone();
1699        self.runtime_sequences
1700            .entry(handle.runtime_id.clone())
1701            .or_insert(0);
1702        self.runtimes.insert(connection.clone(), runtime);
1703        Ok(json!({"connection": connection, "handle": handle}))
1704    }
1705
1706    #[cfg(feature = "adapter-api")]
1707    async fn insert_hosted_runtime(
1708        &mut self,
1709        runtime: Box<dyn RuntimeConnection>,
1710        capabilities: crate::RuntimeCapabilities,
1711        workspace: PathBuf,
1712    ) -> std::result::Result<Value, ServiceError> {
1713        let (host, connection) = HostedHarnessRuntime::spawn(runtime, capabilities);
1714        let token: std::sync::Arc<str> = crate::server::generate_token().into();
1715        let server = crate::server::run_frontend_http(
1716            host.clone(),
1717            host.frontend_sender(),
1718            "127.0.0.1:0",
1719            token.clone(),
1720        )
1721        .await
1722        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1723        let source = LiveRuntimeSource {
1724            harness: connection.handle().harness.as_str().to_string(),
1725            session_id: connection.handle().runtime_id.clone(),
1726            workspace: workspace.clone(),
1727        };
1728        let registration = register_live_runtime(
1729            connection.handle().runtime_id.clone(),
1730            source.clone(),
1731            format!("http://{}", server.address()),
1732            token.to_string(),
1733        )
1734        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1735        let endpoint = registration.endpoint().to_string();
1736        let launch = StructuredLaunch {
1737            cwd: workspace,
1738            // Pin attachment to the executable hosting this runtime. A bare
1739            // `supercode` could resolve to an older global install whose CLI
1740            // does not understand the receipt it is being asked to open.
1741            program: std::env::current_exe()
1742                .ok()
1743                .map(|path| path.to_string_lossy().into_owned())
1744                .unwrap_or_else(|| "supercode".into()),
1745            arguments: vec![
1746                "harness".into(),
1747                "attach".into(),
1748                "--endpoint".into(),
1749                endpoint,
1750                "--harness".into(),
1751                source.harness,
1752                "--session".into(),
1753                source.session_id,
1754            ],
1755            env: BTreeMap::new(),
1756        };
1757        let lease = HostedRuntimeLease {
1758            connection,
1759            _host: host,
1760            _registration: registration,
1761            _server: server,
1762        };
1763        let opened = self.insert_runtime(Box::new(lease))?;
1764        let connection_id = opened["connection"]
1765            .as_str()
1766            .expect("insert_runtime returns a connection id")
1767            .to_string();
1768        self.terminal_launches.insert(connection_id, launch);
1769        Ok(opened)
1770    }
1771
1772    #[cfg(not(feature = "adapter-api"))]
1773    async fn insert_hosted_runtime(
1774        &mut self,
1775        runtime: Box<dyn RuntimeConnection>,
1776        _capabilities: crate::RuntimeCapabilities,
1777        _workspace: PathBuf,
1778    ) -> std::result::Result<Value, ServiceError> {
1779        self.insert_runtime(runtime)
1780    }
1781
1782    fn runtime_mut(
1783        &mut self,
1784        connection: &str,
1785    ) -> std::result::Result<&mut Box<dyn RuntimeConnection>, ServiceError> {
1786        self.runtimes.get_mut(connection).ok_or_else(|| {
1787            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
1788        })
1789    }
1790
1791    /// ORCH-19: run one conversation-lifecycle verb through the harness's own
1792    /// door.
1793    ///
1794    /// Two doors, one shape. A CLI / HTTP / own-store door is self-contained
1795    /// in [`crate::sessions_control`]. A LIVE door (Hermes's and OpenClaw's
1796    /// `/new` and `/reset`, which are slash commands their gateway interprets
1797    /// INSIDE a session) is performed here, because only the service owns the
1798    /// open runtime connection — the command is typed through the very same
1799    /// `send_input` path a human's message takes, so supercode invents no
1800    /// private channel.
1801    async fn mutate_session(
1802        &mut self,
1803        verb: crate::SessionVerb,
1804        params: Value,
1805    ) -> std::result::Result<Value, ServiceError> {
1806        let mutation = decode::<crate::SessionMutation>(params)?;
1807        let door = crate::sessions_control::door(&mutation.harness, verb)
1808            .map_err(session_control_error)?;
1809        let outcome = match door {
1810            // The live door types the slash command through an open hosted
1811            // runtime, which only exists with the `adapter-api` feature; the
1812            // CLI / HTTP / own-store doors below need nothing extra.
1813            #[cfg(not(feature = "adapter-api"))]
1814            crate::SessionDoor::Live(command) => {
1815                return Err(ServiceError::Operation(format!(
1816                    "`{}` performs `sessions.{}` by typing `{command}` into a live driven \
1817                     session, which needs this build's `adapter-api` feature",
1818                    mutation.harness,
1819                    verb.as_str()
1820                )));
1821            }
1822            #[cfg(feature = "adapter-api")]
1823            crate::SessionDoor::Live(command) => {
1824                let connection = mutation
1825                    .connection
1826                    .clone()
1827                    .filter(|value| !value.trim().is_empty())
1828                    .ok_or_else(|| {
1829                        ServiceError::InvalidParams(format!(
1830                            "`{}` performs `sessions.{}` by typing `{command}` into a live \
1831                             driven session: pass the `connection` of an open runtime \
1832                             (`harness.v1.runtimes.start`)",
1833                            mutation.harness,
1834                            verb.as_str()
1835                        ))
1836                    })?;
1837                let runtime = self.runtime_mut(&connection)?;
1838                let session = mutation
1839                    .session
1840                    .clone()
1841                    .filter(|value| !value.trim().is_empty())
1842                    .unwrap_or_else(|| runtime.handle().runtime_id.clone());
1843                runtime
1844                    .send_input(RuntimeInput {
1845                        text: command.to_string(),
1846                        image_urls: Vec::new(),
1847                    })
1848                    .await
1849                    .map_err(operation)?;
1850                crate::sessions_control::live_outcome(verb, &mutation, command, session)
1851                    .map_err(session_control_error)?
1852            }
1853            _ => crate::sessions_control::mutate(verb, &mutation)
1854                .await
1855                .map_err(session_control_error)?,
1856        };
1857        serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
1858    }
1859
1860    async fn inventory_call(
1861        &self,
1862        method: &str,
1863        params: Value,
1864    ) -> std::result::Result<Value, ServiceError> {
1865        let mut params = decode::<HarnessInventoryParams>(params)?;
1866        if method == "harness.v1.harnesses.probe" {
1867            let harness = params.harness.take().ok_or_else(|| {
1868                ServiceError::InvalidParams("harnesses.probe requires `harness`".into())
1869            })?;
1870            params.harnesses = vec![harness];
1871        }
1872        let selected = params
1873            .harnesses
1874            .iter()
1875            .map(HarnessId::as_str)
1876            .collect::<std::collections::BTreeSet<_>>();
1877        let supported = harness_support_registry()
1878            .harnesses
1879            .into_iter()
1880            .filter(|descriptor| selected.is_empty() || selected.contains(descriptor.id.as_str()))
1881            .collect::<Vec<_>>();
1882        if !params.harnesses.is_empty() && supported.len() != selected.len() {
1883            let known = supported
1884                .iter()
1885                .map(|harness| harness.id.as_str())
1886                .collect::<std::collections::BTreeSet<_>>();
1887            let missing = params
1888                .harnesses
1889                .iter()
1890                .filter(|id| !known.contains(id.as_str()))
1891                .map(HarnessId::as_str)
1892                .collect::<Vec<_>>();
1893            return Err(ServiceError::InvalidParams(format!(
1894                "unknown harness(es): {}",
1895                missing.join(", ")
1896            )));
1897        }
1898        let global_counts = params
1899            .include_sessions
1900            .then(|| self.session_counts(None, &params.harnesses));
1901        let workspace_counts = params.include_sessions.then(|| {
1902            params
1903                .workspace
1904                .as_deref()
1905                .map(|workspace| self.session_counts(Some(workspace), &params.harnesses))
1906        });
1907        let probes = supported.into_iter().map(|descriptor| {
1908            let global = global_counts
1909                .as_ref()
1910                .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
1911            let workspace = workspace_counts
1912                .as_ref()
1913                .and_then(Option::as_ref)
1914                .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
1915            self.probe_harness(descriptor, &params, global, workspace)
1916        });
1917        let harnesses = futures::future::join_all(probes).await;
1918        serde_json::to_value(HarnessInventoryReport {
1919            probe: params.probe,
1920            workspace: params.workspace,
1921            harnesses,
1922        })
1923        .map_err(|error| ServiceError::Operation(error.to_string()))
1924    }
1925
1926    #[cfg(feature = "adapter-api")]
1927    async fn harness_authentication_call(
1928        &self,
1929        method: &str,
1930        params: Value,
1931    ) -> std::result::Result<Value, ServiceError> {
1932        match method {
1933            "harness.v1.harnesses.auth.methods" | "harness.v1.harnesses.auth.verify" => {
1934                let params = decode::<HarnessAuthenticationParams>(params)?;
1935                serde_json::to_value(crate::inspect_harness_authentication(&params.harness).await)
1936                    .map_err(|error| ServiceError::Operation(error.to_string()))
1937            }
1938            "harness.v1.harnesses.auth.begin" => {
1939                let params = decode::<BeginHarnessAuthenticationParams>(params)?;
1940                let cwd = params
1941                    .cwd
1942                    .or_else(|| std::env::current_dir().ok())
1943                    .unwrap_or_else(|| PathBuf::from("."));
1944                let plan = crate::harness_authentication_plan(
1945                    &params.harness,
1946                    params.environment,
1947                    params.method,
1948                    &cwd,
1949                )
1950                .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
1951                serde_json::to_value(plan)
1952                    .map_err(|error| ServiceError::Operation(error.to_string()))
1953            }
1954            _ => Err(ServiceError::MethodNotFound),
1955        }
1956    }
1957
1958    async fn probe_harness(
1959        &self,
1960        descriptor: crate::HarnessSupportDescriptor,
1961        params: &HarnessInventoryParams,
1962        global: Option<usize>,
1963        workspace: Option<usize>,
1964    ) -> LocalHarness {
1965        let launch = descriptor.runtime.default_launch.as_ref();
1966        // ORC-7: the orchestrator publishes no runtime launch — it is not an
1967        // adapter supercode connects a turn to. What "installed" means for it
1968        // is that its Node daemon entry is present, so the row answers from
1969        // that instead of from a PATH lookup it could never satisfy.
1970        let orchestrator_entry = (descriptor.id.as_str() == HarnessId::ORCHESTRATOR)
1971            .then(crate::orchestrator::daemon_entry)
1972            .and_then(Result::ok);
1973        let executable = match &orchestrator_entry {
1974            Some(entry) => Some(entry.clone()),
1975            None => launch.and_then(|launch| find_executable(&launch.program)),
1976        };
1977        let installed = executable.is_some();
1978        let version = if params.skip_versions || orchestrator_entry.is_some() {
1979            // The orchestrator's "executable" is a Node module, not a CLI
1980            // with a `--version` flag; running it to ask would start a daemon.
1981            None
1982        } else {
1983            match executable.as_deref() {
1984                Some(path) => executable_version(path).await,
1985                None => None,
1986            }
1987        };
1988        let configured = auth_evidence(descriptor.id.as_str());
1989        let mut auth = if configured {
1990            HarnessAuthState::Configured
1991        } else if matches!(
1992            descriptor.id.as_str(),
1993            HarnessId::CLAUDE_CODE | HarnessId::CODEX
1994        ) {
1995            // These two adapters have explicit native status/login contracts
1996            // and complete local evidence coverage (including Claude's macOS
1997            // Keychain-backed oauthAccount marker). Treating absent evidence
1998            // as unknown advertises a start that will only fail interactively.
1999            HarnessAuthState::Required
2000        } else {
2001            HarnessAuthState::Unknown
2002        };
2003        let mut runtime = if installed {
2004            HarnessRuntimeState::Degraded
2005        } else {
2006            HarnessRuntimeState::Unavailable
2007        };
2008        let is_orchestrator = descriptor.id.as_str() == HarnessId::ORCHESTRATOR;
2009        let mut reason = (!installed).then(|| {
2010            if is_orchestrator {
2011                format!(
2012                    "{} is supported but its daemon entry `{}` was not found",
2013                    descriptor.display_name,
2014                    crate::orchestrator::DAEMON_ENTRY
2015                )
2016            } else {
2017                format!(
2018                    "{} is supported but `{}` was not found on PATH",
2019                    descriptor.display_name,
2020                    launch
2021                        .map(|launch| launch.program.as_str())
2022                        .unwrap_or("executable")
2023                )
2024            }
2025        });
2026        let mut repair = (!installed).then(|| {
2027            if is_orchestrator {
2028                format!(
2029                    "Install the `supercode-orchestrator` package so `{}` resolves.",
2030                    crate::orchestrator::DAEMON_ENTRY
2031                )
2032            } else {
2033                format!(
2034                    "Install {} and ensure `{}` is on PATH.",
2035                    descriptor.display_name,
2036                    launch
2037                        .map(|launch| launch.program.as_str())
2038                        .unwrap_or("its executable")
2039                )
2040            }
2041        });
2042
2043        if installed && params.probe == HarnessProbeLevel::Handshake {
2044            let backend_params = RuntimeBackendParams {
2045                harness: descriptor.id.clone(),
2046                protocol: None,
2047                launch: None,
2048                base_url: None,
2049                policy: RuntimePolicy::Default,
2050            };
2051            match runtime_backend(&backend_params) {
2052                Ok(backend) => {
2053                    let cwd = params
2054                        .workspace
2055                        .clone()
2056                        .or_else(|| std::env::current_dir().ok())
2057                        .unwrap_or_else(|| PathBuf::from("."));
2058                    let isolated = descriptor
2059                        .runtime
2060                        .default_launch
2061                        .clone()
2062                        .and_then(|launch| {
2063                            IsolatedProbeHome::new(descriptor.id.as_str(), launch).ok()
2064                        });
2065                    let Some(isolated) = isolated else {
2066                        reason = Some(
2067                            "No-prompt runtime handshake could not create its isolated harness home."
2068                                .into(),
2069                        );
2070                        repair = Some(
2071                            "Check temporary-directory permissions, then run the handshake probe again."
2072                                .into(),
2073                        );
2074                        let running = probe_running_instance(descriptor.id.as_str());
2075                        return LocalHarness {
2076                            gateway: gateway_health(
2077                                descriptor.id.as_str(),
2078                                installed,
2079                                running.as_ref(),
2080                                version.as_deref(),
2081                            ),
2082                            id: descriptor.id,
2083                            display_name: descriptor.display_name,
2084                            supported: true,
2085                            installed,
2086                            executable: executable.map(|path| path.to_string_lossy().into_owned()),
2087                            version,
2088                            auth,
2089                            runtime,
2090                            protocol: descriptor.runtime.protocol,
2091                            capabilities: descriptor.runtime.capabilities.clone(),
2092                            effective_capabilities: descriptor.runtime.capabilities,
2093                            sessions: HarnessSessionCounts { global, workspace },
2094                            running,
2095                            reason,
2096                            repair,
2097                        };
2098                    };
2099                    match tokio::time::timeout(
2100                        Duration::from_secs(30),
2101                        backend.start(RuntimeStartRequest {
2102                            cwd,
2103                            launch: Some(isolated.launch.clone()),
2104                            mcp_servers: Vec::new(),
2105                        }),
2106                    )
2107                    .await
2108                    {
2109                        Ok(Ok(mut connection)) => {
2110                            match stabilize_handshake(connection.as_mut()).await {
2111                                Ok(()) => {
2112                                    auth = HarnessAuthState::Ready;
2113                                    runtime = HarnessRuntimeState::Ready;
2114                                    reason = Some(
2115                                        "No-prompt runtime handshake remained healthy through the startup stabilization window; no model request was sent."
2116                                            .into(),
2117                                    );
2118                                    repair = None;
2119                                }
2120                                Err(message) => {
2121                                    auth = if looks_like_auth_error(&message) {
2122                                        HarnessAuthState::Required
2123                                    } else if configured {
2124                                        HarnessAuthState::Configured
2125                                    } else {
2126                                        HarnessAuthState::Unknown
2127                                    };
2128                                    reason = Some(format!(
2129                                        "No-prompt runtime handshake became unhealthy during startup: {message}"
2130                                    ));
2131                                    repair = Some(if auth == HarnessAuthState::Required {
2132                                        format!(
2133                                            "Run `{}` interactively once and complete sign-in, then probe again.",
2134                                            launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2135                                        )
2136                                    } else {
2137                                        "Run the harness directly to inspect its startup failure, then probe again."
2138                                            .into()
2139                                    });
2140                                }
2141                            }
2142                            let _ =
2143                                tokio::time::timeout(Duration::from_secs(3), connection.close())
2144                                    .await;
2145                        }
2146                        Ok(Err(error)) => {
2147                            let message = truncate_text(&error.to_string(), 500);
2148                            auth = if looks_like_auth_error(&message) {
2149                                HarnessAuthState::Required
2150                            } else if configured {
2151                                HarnessAuthState::Configured
2152                            } else {
2153                                HarnessAuthState::Unknown
2154                            };
2155                            reason = Some(format!("No-prompt runtime handshake failed: {message}"));
2156                            repair = Some(if auth == HarnessAuthState::Required {
2157                                format!(
2158                                    "Run `{}` interactively once and complete sign-in, then probe again.",
2159                                    launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2160                                )
2161                            } else {
2162                                "Check the harness installation and run the handshake probe again."
2163                                    .into()
2164                            });
2165                        }
2166                        Err(_) => {
2167                            reason = Some(
2168                                "No-prompt runtime handshake timed out after 30 seconds.".into(),
2169                            );
2170                            repair = Some("Run the harness directly to check startup or authentication, then probe again.".into());
2171                        }
2172                    }
2173                    // Keep the isolated home alive through process teardown.
2174                    // Otherwise the compiler may release the last meaningful
2175                    // use after cloning `launch`, and a still-starting CLI can
2176                    // recreate its state directory after Drop removed it.
2177                    // Some Node-based launchers finish a short asynchronous
2178                    // installation-id write just after their parent process
2179                    // is reaped. Remove once immediately, allow that bounded
2180                    // writer to settle, then perform the authoritative pass.
2181                    let _ = isolated.cleanup();
2182                    tokio::time::sleep(Duration::from_millis(250)).await;
2183                    if let Err(error) = isolated.cleanup() {
2184                        auth = if configured {
2185                            HarnessAuthState::Configured
2186                        } else {
2187                            HarnessAuthState::Unknown
2188                        };
2189                        runtime = HarnessRuntimeState::Degraded;
2190                        reason = Some(format!(
2191                            "No-prompt runtime handshake could not remove its isolated harness home: {error}"
2192                        ));
2193                        repair = Some(
2194                            "Check temporary-directory permissions, remove the reported disposable probe home, then run the handshake again."
2195                                .into(),
2196                        );
2197                    }
2198                }
2199                Err(error) => {
2200                    reason = Some(error_message(error));
2201                }
2202            }
2203        } else if installed && configured {
2204            reason = Some("Executable and local authentication evidence found; use a handshake probe to verify readiness.".into());
2205        } else if installed && auth == HarnessAuthState::Required {
2206            reason =
2207                Some("Executable found, but no native authentication evidence is present.".into());
2208            repair = Some(format!(
2209                "Run `supercode harness login {}` to use the harness-owned sign-in flow.",
2210                descriptor.id.as_str()
2211            ));
2212        } else if installed {
2213            reason = Some("Executable found; authentication readiness is unknown until a no-prompt handshake succeeds.".into());
2214            repair =
2215                Some(format!(
2216                "Run `{}` interactively once if sign-in is required, or use `--probe handshake`.",
2217                launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2218            ));
2219        }
2220
2221        let effective_capabilities = if installed {
2222            descriptor.runtime.capabilities.clone()
2223        } else {
2224            unavailable_capabilities()
2225        };
2226        let running = probe_running_instance(descriptor.id.as_str());
2227        LocalHarness {
2228            gateway: gateway_health(
2229                descriptor.id.as_str(),
2230                installed,
2231                running.as_ref(),
2232                version.as_deref(),
2233            ),
2234            id: descriptor.id,
2235            display_name: descriptor.display_name,
2236            supported: true,
2237            installed,
2238            executable: executable.map(|path| path.to_string_lossy().into_owned()),
2239            version,
2240            auth,
2241            runtime,
2242            protocol: descriptor.runtime.protocol,
2243            capabilities: descriptor.runtime.capabilities,
2244            effective_capabilities,
2245            sessions: HarnessSessionCounts { global, workspace },
2246            running,
2247            reason,
2248            repair,
2249        }
2250    }
2251
2252    fn session_counts(
2253        &self,
2254        workspace: Option<&Path>,
2255        harnesses: &[HarnessId],
2256    ) -> BTreeMap<String, usize> {
2257        let mut counts = BTreeMap::new();
2258        for session in self
2259            .catalog
2260            .discover(&DiscoveryQuery {
2261                workspace: workspace.map(Path::to_path_buf),
2262                harnesses: harnesses.to_vec(),
2263                ..DiscoveryQuery::default()
2264            })
2265            .unwrap_or_default()
2266        {
2267            *counts
2268                .entry(session.locator.harness.as_str().to_string())
2269                .or_insert(0) += 1;
2270        }
2271        counts
2272    }
2273}
2274
2275#[async_trait::async_trait]
2276impl SdkService for HarnessSessionService {
2277    fn capabilities(&self) -> SdkCapabilities {
2278        SdkCapabilities::default()
2279    }
2280
2281    async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError> {
2282        if request.operation == SdkOperation::Events {
2283            let events = self
2284                .poll_sdk_events()
2285                .await
2286                .into_iter()
2287                .map(|(_, event)| event)
2288                .collect::<Vec<_>>();
2289            return serde_json::to_value(events).map_err(|error| {
2290                SdkError::new(
2291                    SdkErrorCode::Execution,
2292                    request.operation,
2293                    error.to_string(),
2294                )
2295            });
2296        }
2297        if self.runtimes.is_empty()
2298            && matches!(
2299                request.operation,
2300                SdkOperation::Input
2301                    | SdkOperation::Interrupt
2302                    | SdkOperation::Steer
2303                    | SdkOperation::Respond
2304                    | SdkOperation::Close
2305            )
2306        {
2307            return Err(SdkError::unsupported(request.operation));
2308        }
2309        let method = request
2310            .operation
2311            .method()
2312            .ok_or_else(|| SdkError::unsupported(request.operation))?;
2313        let result = match request.operation {
2314            SdkOperation::Discover
2315            | SdkOperation::Load
2316            | SdkOperation::Export
2317            | SdkOperation::ProfilesList
2318            | SdkOperation::ProfilesGet
2319            | SdkOperation::ProfilesCreate
2320            | SdkOperation::ProfilesDelete
2321            | SdkOperation::SkillsList
2322            | SdkOperation::SkillsInstall
2323            | SdkOperation::SkillsRemove
2324            | SdkOperation::ChannelsList
2325            | SdkOperation::RoutesList
2326            | SdkOperation::TriggersList
2327            | SdkOperation::ChannelsStatus
2328            | SdkOperation::MemoryShow
2329            | SdkOperation::MemorySearch
2330            | SdkOperation::JobsList
2331            | SdkOperation::JobsGet
2332            | SdkOperation::JobsCreate
2333            | SdkOperation::JobsUpdate
2334            | SdkOperation::JobsPause
2335            | SdkOperation::JobsResume
2336            | SdkOperation::JobsRun
2337            | SdkOperation::JobsDelete
2338            | SdkOperation::RunsList
2339            | SdkOperation::RunsGet
2340            | SdkOperation::ApprovalsList
2341            | SdkOperation::OrchestrationLoad
2342            | SdkOperation::OrchestrationSave
2343            | SdkOperation::OrchestrationCompile
2344            | SdkOperation::OrchestrationDecompile
2345            | SdkOperation::OrchestrationImport
2346            | SdkOperation::OrchestrationExport
2347            | SdkOperation::WorkflowLoad => self.call(method, request.params),
2348            // ORCH-20: answering needs the live connection, so it takes the
2349            // async door and ends in `harness.v1.runtimes.respond`.
2350            SdkOperation::ApprovalsResolve => self.approvals_resolve(request.params).await,
2351            SdkOperation::Start
2352            | SdkOperation::Resume
2353            | SdkOperation::Input
2354            | SdkOperation::Interrupt
2355            | SdkOperation::Steer
2356            | SdkOperation::Respond
2357            | SdkOperation::Close => self.runtime_call(method, request.params).await,
2358            // ORCH-19 controlled tier. Every verb goes through the HARNESS'S
2359            // OWN door — its CLI, its HTTP API, or its slash command typed
2360            // into a live driven session — and returns the row re-read from
2361            // the harness's store afterwards.
2362            SdkOperation::SessionsNew => {
2363                self.mutate_session(crate::SessionVerb::New, request.params)
2364                    .await
2365            }
2366            SdkOperation::SessionsReset => {
2367                self.mutate_session(crate::SessionVerb::Reset, request.params)
2368                    .await
2369            }
2370            SdkOperation::SessionsArchive => {
2371                self.mutate_session(crate::SessionVerb::Archive, request.params)
2372                    .await
2373            }
2374            SdkOperation::SessionsDelete => {
2375                self.mutate_session(crate::SessionVerb::Delete, request.params)
2376                    .await
2377            }
2378            SdkOperation::Events => unreachable!("handled before method dispatch"),
2379        };
2380        result.map_err(|error| sdk_error(request.operation, error))
2381    }
2382
2383    async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError> {
2384        Ok(self
2385            .poll_sdk_events()
2386            .await
2387            .into_iter()
2388            .map(|(_, event)| event)
2389            .collect())
2390    }
2391}
2392
2393#[cfg(feature = "adapter-api")]
2394struct HostedRuntimeLease {
2395    connection: HostedHarnessConnection,
2396    _host: std::sync::Arc<HostedHarnessRuntime>,
2397    _registration: LiveRuntimeRegistration,
2398    _server: crate::server::FrontendHttpServer,
2399}
2400
2401#[async_trait::async_trait]
2402#[cfg(feature = "adapter-api")]
2403impl RuntimeConnection for HostedRuntimeLease {
2404    fn handle(&self) -> &crate::RuntimeHandle {
2405        self.connection.handle()
2406    }
2407
2408    async fn send_input(&mut self, input: RuntimeInput) -> crate::Result<Option<String>> {
2409        self.connection.send_input(input).await
2410    }
2411
2412    async fn next_event(&mut self) -> crate::Result<Option<crate::HarnessEvent>> {
2413        self.connection.next_event().await
2414    }
2415
2416    async fn interrupt(&mut self) -> crate::Result<()> {
2417        self.connection.interrupt().await
2418    }
2419
2420    async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
2421        self.connection.respond(request_id, response).await
2422    }
2423
2424    async fn close(&mut self) -> crate::Result<()> {
2425        self.connection.close().await
2426    }
2427}
2428
2429async fn stabilize_handshake(connection: &mut dyn RuntimeConnection) -> Result<(), String> {
2430    let deadline = tokio::time::Instant::now() + Duration::from_secs(3);
2431    loop {
2432        let now = tokio::time::Instant::now();
2433        if now >= deadline {
2434            return Ok(());
2435        }
2436        match tokio::time::timeout(deadline - now, connection.next_event()).await {
2437            Err(_) => return Ok(()),
2438            Ok(Ok(Some(event))) => {
2439                if let Some(message) = handshake_event_failure(&event) {
2440                    return Err(truncate_text(&message, 500));
2441                }
2442            }
2443            Ok(Ok(None)) => return Err("runtime transport closed during startup".into()),
2444            Ok(Err(error)) => return Err(error.to_string()),
2445        }
2446    }
2447}
2448
2449fn handshake_event_failure(event: &crate::HarnessEvent) -> Option<String> {
2450    let detail = event
2451        .payload
2452        .get("message")
2453        .or_else(|| event.payload.get("line"))
2454        .and_then(Value::as_str)
2455        .unwrap_or(event.kind.as_str());
2456    match event.kind.as_str() {
2457        "transport_closed" => Some("runtime transport closed during startup".into()),
2458        "transport_error" => Some(format!("runtime transport error: {detail}")),
2459        "malformed_output" => Some(format!("runtime emitted non-protocol output: {detail}")),
2460        // Stderr is retained as a runtime event, but is not transport health.
2461        // Grok, for example, can log an AuthorizationRequired error from an
2462        // optional background worker while its ACP session continues to send
2463        // updates and complete prompts normally.
2464        _ => None,
2465    }
2466}
2467
2468fn indexed_claude_window(
2469    locator: &SessionLocator,
2470    options: &SessionLoadOptions,
2471) -> std::result::Result<Option<Value>, ServiceError> {
2472    use supercode_interchange::session::ClaudeReadIndex;
2473    // Exact parent-only window: recursive/full-artifact requests retain the
2474    // existing owner. This is not a bounded display-history substitution.
2475    if locator.harness.as_str() != HarnessId::CLAUDE_CODE
2476        || options.include_subagents != Some(false)
2477    {
2478        return Ok(None);
2479    }
2480    let crate::StorageLocator::File { path } = &locator.storage else {
2481        return Ok(None);
2482    };
2483    if !ClaudeReadIndex::supports(path)
2484        .map_err(|error| ServiceError::Operation(error.to_string()))?
2485    {
2486        return Ok(None);
2487    }
2488    let mut index = ClaudeReadIndex::open(path, Fidelity::ByteLossless)
2489        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2490    let total = index.len();
2491    let (offset, end) = projected_message_window(total, options);
2492    let session = index
2493        .read_messages(offset..end)
2494        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2495    let summary = index
2496        .read_summary()
2497        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2498    let selected_options = SessionLoadOptions {
2499        message_offset: None,
2500        message_limit: None,
2501        message_tail: None,
2502        ..options.clone()
2503    };
2504    let mut selected = projected_session_json(&session, &selected_options);
2505    selected["raw_record_count"] = json!(index.raw_record_count());
2506    Ok(Some(json!({
2507        "session": selected,
2508        "summary": projected_session_summary(&summary, options),
2509        "window": {
2510            "has_more": offset > 0 || end < total, "has_newer": end < total,
2511            "has_older": offset > 0, "newer_items": index.item_count(end..total),
2512            "offset": offset, "older_items": index.item_count(0..offset),
2513            "returned": end - offset, "total_messages": total,
2514        }
2515    })))
2516}
2517
2518fn projected_session_result(session: &Session, options: &SessionLoadOptions) -> Value {
2519    let total_messages = session.messages.len();
2520    let (offset, end) = projected_message_window(total_messages, options);
2521    json!({
2522        "session": projected_session_json(session, options),
2523        "summary": projected_session_summary(session, options),
2524        "window": {
2525            "has_more": offset > 0 || end < total_messages,
2526            "has_newer": end < total_messages,
2527            "has_older": offset > 0,
2528            "newer_items": normalized_item_count(&session.messages[end..]),
2529            "offset": offset,
2530            "older_items": normalized_item_count(&session.messages[..offset]),
2531            "returned": end.saturating_sub(offset),
2532            "total_messages": total_messages,
2533        }
2534    })
2535}
2536
2537fn normalized_item_count(messages: &[crate::ChatMessage]) -> usize {
2538    messages
2539        .iter()
2540        .map(|message| {
2541            let conversation = usize::from(
2542                matches!(message.role, Role::Assistant | Role::User)
2543                    && message_has_content(message),
2544            );
2545            let tool_result =
2546                usize::from(message.role == Role::Tool && message_has_content(message));
2547            conversation + tool_result + message.tool_calls().len()
2548        })
2549        .sum()
2550}
2551
2552fn projected_session_summary(session: &Session, options: &SessionLoadOptions) -> Value {
2553    let mut conversational = session.messages.iter().filter(|message| {
2554        matches!(message.role, Role::Assistant | Role::User) && message_has_content(message)
2555    });
2556    let first_message = conversational.clone().next();
2557    let last_message = conversational.next_back();
2558    let mut assistant = session
2559        .messages
2560        .iter()
2561        .filter(|message| message.role == Role::Assistant && message_has_content(message));
2562    let first_assistant_message = assistant.clone().next();
2563    let last_assistant_message = assistant.next_back();
2564    let end_of_turn = session
2565        .messages
2566        .iter()
2567        .rev()
2568        .find(|message| message.role != Role::System)
2569        .is_some_and(|message| {
2570            message.role == Role::Assistant
2571                && message_has_content(message)
2572                && message.tool_calls().is_empty()
2573        });
2574    let project = |message: Option<&crate::ChatMessage>| {
2575        message.map(|message| project_inline_media(message_json(message), options))
2576    };
2577    json!({
2578        "end_of_turn": end_of_turn,
2579        "first_assistant_message": project(first_assistant_message),
2580        "first_message": project(first_message),
2581        "last_assistant_message": project(last_assistant_message),
2582        "last_assistant_text": last_assistant_message.map(message_text).unwrap_or_default(),
2583        "last_message": project(last_message),
2584    })
2585}
2586
2587fn message_has_content(message: &crate::ChatMessage) -> bool {
2588    message
2589        .content
2590        .as_deref()
2591        .is_some_and(|content| !content.trim().is_empty())
2592        || message
2593            .content_parts
2594            .as_ref()
2595            .is_some_and(|parts| !parts.is_empty())
2596}
2597
2598fn message_text(message: &crate::ChatMessage) -> String {
2599    if let Some(content) = &message.content {
2600        return content.clone();
2601    }
2602    message
2603        .content_parts
2604        .as_ref()
2605        .into_iter()
2606        .flatten()
2607        .filter_map(|part| part.get("text").and_then(Value::as_str))
2608        .collect::<Vec<_>>()
2609        .join("\n")
2610}
2611
2612fn projected_session_json(session: &Session, options: &SessionLoadOptions) -> Value {
2613    let (offset, end) = projected_message_window(session.messages.len(), options);
2614    let messages = session.messages[offset..end]
2615        .iter()
2616        .map(|message| project_inline_media(message_json(message), options))
2617        .collect::<Vec<_>>();
2618    let subagents = if options.include_subagents.unwrap_or(true) {
2619        // The reported window describes the top-level transcript. Applying it
2620        // recursively would silently truncate subagents without returning a
2621        // window for each child. Keep their histories complete while carrying
2622        // the caller's media policy through the tree.
2623        let subagent_options = SessionLoadOptions {
2624            message_limit: None,
2625            message_offset: None,
2626            message_tail: None,
2627            ..options.clone()
2628        };
2629        session
2630            .subagents
2631            .iter()
2632            .map(|subagent| projected_session_json(subagent, &subagent_options))
2633            .collect::<Vec<_>>()
2634    } else {
2635        Vec::new()
2636    };
2637    json!({
2638        "source": match session.meta.source {
2639            SessionSource::ClaudeCode => "claude_code",
2640            SessionSource::Codex => "codex",
2641            SessionSource::Gemini => "gemini",
2642            SessionSource::Goose => "goose",
2643            SessionSource::Grok => "grok",
2644            SessionSource::Native => "native",
2645            SessionSource::OpenClaw => "openclaw",
2646            SessionSource::Hermes => "hermes",
2647            SessionSource::OpenCode => "opencode",
2648            SessionSource::Pi => "pi",
2649        },
2650        "session_id": session.meta.session_id,
2651        "ended_at": session.meta.ended_at,
2652        "end_reason": session.meta.end_reason,
2653        "model": session.meta.model,
2654        "cwd": session.meta.cwd,
2655        "system_prompt": session.meta.system_prompt,
2656        "agent_id": session.meta.agent_id,
2657        "parent_tool_use_id": session.meta.parent_tool_use_id,
2658        "lineage": session.meta.lineage,
2659        "messages": messages,
2660        "subagents": subagents,
2661        "raw_record_count": session.raw.len(),
2662        "parse_error_lines": session.parse_error_lines,
2663    })
2664}
2665
2666fn projected_message_window(total: usize, options: &SessionLoadOptions) -> (usize, usize) {
2667    if let Some(tail) = options.message_tail {
2668        return (total.saturating_sub(tail), total);
2669    }
2670    let offset = options.message_offset.unwrap_or(0).min(total);
2671    let end = options
2672        .message_limit
2673        .map(|limit| offset.saturating_add(limit).min(total))
2674        .unwrap_or(total);
2675    (offset, end)
2676}
2677
2678fn project_inline_media(mut message: Value, options: &SessionLoadOptions) -> Value {
2679    let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else {
2680        return message;
2681    };
2682    for part in parts {
2683        let Some(url) = part
2684            .get("image_url")
2685            .and_then(|image| image.get("url"))
2686            .and_then(Value::as_str)
2687        else {
2688            continue;
2689        };
2690        let Some(rest) = url.strip_prefix("data:") else {
2691            continue;
2692        };
2693        let Some((media_type, encoded)) = rest.split_once(";base64,") else {
2694            continue;
2695        };
2696        let padding = usize::from(encoded.ends_with('=')) + usize::from(encoded.ends_with("=="));
2697        let decoded_bytes = encoded.len().saturating_mul(3) / 4;
2698        let decoded_bytes = decoded_bytes.saturating_sub(padding);
2699        let should_elide = matches!(options.inline_media, InlineMediaMode::Metadata)
2700            || options
2701                .max_inline_media_bytes
2702                .is_some_and(|limit| decoded_bytes > limit);
2703        if should_elide {
2704            *part = json!({
2705                "type": "media_reference",
2706                "media_type": media_type,
2707                "encoding": "base64",
2708                "encoded_bytes": encoded.len(),
2709                "decoded_bytes": decoded_bytes,
2710                "omitted": true,
2711            });
2712        }
2713    }
2714    message
2715}
2716
2717#[derive(Deserialize)]
2718struct LocatorParams {
2719    locator: SessionLocator,
2720    /// Optional fidelity for the READ surfaces (`sessions.load`,
2721    /// `sessions.follow`).
2722    ///
2723    /// Omitted means [`Fidelity::Semantic`]: these two methods only ever
2724    /// produce a read-only view, and a compacted or resumed-across-files
2725    /// transcript — the everyday shape of a long Claude Code session — has no
2726    /// losslessly reconstructable record graph, so refusing to render it made
2727    /// the mirror unusable rather than accurate. A caller that intends to
2728    /// CONTINUE from what it reads asks for a lossless level explicitly and
2729    /// gets the strict refusal back. Every other method (export, translate,
2730    /// branch, handoff, resume_instructions) is lossless-only and has no
2731    /// such knob.
2732    #[serde(default)]
2733    fidelity: Option<Fidelity>,
2734    /// Optional bounded frontend projection. Absent preserves the historical
2735    /// complete-session read contract.
2736    #[serde(default)]
2737    view: Option<SessionReadView>,
2738}
2739
2740#[derive(Deserialize)]
2741struct SessionReadView {
2742    /// Number of trailing normalized messages to return. Zero is treated as
2743    /// one so a caller cannot accidentally request an unbounded empty mode.
2744    #[serde(default)]
2745    tail_messages: Option<usize>,
2746    /// Whether Claude Code child transcripts belong in this view. The
2747    /// frontend default is false; the legacy no-view path remains true.
2748    #[serde(default)]
2749    include_subagents: bool,
2750    /// Preserve human-visible native history across model-context compaction.
2751    #[serde(default)]
2752    display_history: bool,
2753    /// Bound each individual text field so a single tool result cannot turn a
2754    /// small message window into a hundred-megabyte RPC response.
2755    #[serde(default)]
2756    max_message_chars: Option<usize>,
2757}
2758
2759impl LocatorParams {
2760    fn read_fidelity(&self) -> Fidelity {
2761        self.fidelity.unwrap_or(Fidelity::Semantic)
2762    }
2763
2764    fn include_subagents(&self) -> bool {
2765        self.view
2766            .as_ref()
2767            .map(|view| view.include_subagents)
2768            .unwrap_or(true)
2769    }
2770
2771    fn tail_messages(&self) -> Option<usize> {
2772        self.view
2773            .as_ref()
2774            .and_then(|view| view.tail_messages)
2775            .map(|limit| limit.clamp(1, 5_000))
2776    }
2777
2778    fn display_history(&self) -> bool {
2779        self.view.as_ref().is_some_and(|view| view.display_history)
2780    }
2781
2782    fn max_message_chars(&self) -> Option<usize> {
2783        self.view
2784            .as_ref()
2785            .and_then(|view| view.max_message_chars)
2786            .map(|limit| limit.clamp(256, 64_000))
2787    }
2788
2789    fn bound_session(&self, session: &mut Session) {
2790        bound_session_view(session, self.tail_messages(), self.max_message_chars());
2791    }
2792}
2793
2794#[derive(Debug, Clone, Copy, Default, Deserialize)]
2795#[serde(rename_all = "snake_case")]
2796enum InlineMediaMode {
2797    #[default]
2798    Full,
2799    Metadata,
2800}
2801
2802#[derive(Debug, Clone, Default, Deserialize)]
2803#[serde(default)]
2804struct SessionLoadOptions {
2805    include_subagents: Option<bool>,
2806    inline_media: InlineMediaMode,
2807    max_inline_media_bytes: Option<usize>,
2808    message_limit: Option<usize>,
2809    message_offset: Option<usize>,
2810    message_tail: Option<usize>,
2811}
2812
2813impl SessionLoadOptions {
2814    fn validate(&self) -> std::result::Result<(), ServiceError> {
2815        if self.message_tail.is_some()
2816            && (self.message_limit.is_some() || self.message_offset.is_some())
2817        {
2818            return Err(ServiceError::InvalidParams(
2819                "sessions.load options.message_tail cannot be combined with message_limit or message_offset"
2820                    .into(),
2821            ));
2822        }
2823        Ok(())
2824    }
2825}
2826
2827#[derive(Deserialize)]
2828struct LoadSessionParams {
2829    #[serde(flatten)]
2830    read: LocatorParams,
2831    #[serde(default)]
2832    options: Option<SessionLoadOptions>,
2833}
2834
2835#[derive(Deserialize)]
2836struct UnfollowParams {
2837    subscription: String,
2838}
2839
2840#[derive(Debug, Deserialize)]
2841#[serde(deny_unknown_fields)]
2842struct IndexResizeParams {
2843    subscription: String,
2844    limit: usize,
2845}
2846
2847#[derive(Deserialize)]
2848struct ActivitySubscribeParams {
2849    locators: Vec<SessionLocator>,
2850    #[serde(default)]
2851    homes: crate::HarnessHomes,
2852}
2853
2854#[derive(Deserialize)]
2855struct MessageSessionParams {
2856    locator: SessionLocator,
2857    text: String,
2858    /// Same storage roots discovery accepts, so a caller (and a test) can
2859    /// point the live-session registry somewhere other than `$HOME`.
2860    #[serde(default)]
2861    homes: crate::HarnessHomes,
2862}
2863
2864#[derive(Deserialize)]
2865#[serde(deny_unknown_fields)]
2866struct HarnessSettingsParams {
2867    harness: String,
2868}
2869
2870#[derive(Deserialize)]
2871#[serde(deny_unknown_fields)]
2872struct ConfigureHarnessParams {
2873    harness: String,
2874    #[serde(default)]
2875    changes: Vec<crate::HarnessSettingChange>,
2876    #[serde(default)]
2877    expected_revision: Option<String>,
2878}
2879
2880fn claude_inbound_controls_or_error(homes: &crate::HarnessHomes) -> (Value, Value) {
2881    match crate::inspect_harness_interop_settings(homes, HarnessId::CLAUDE_CODE) {
2882        Ok(report) => (
2883            serde_json::to_value(report).unwrap_or(Value::Null),
2884            Value::Null,
2885        ),
2886        Err(error) => (
2887            Value::Null,
2888            Value::String(format!(
2889                "Supercode could not inspect Claude Code inbound controls: {error}"
2890            )),
2891        ),
2892    }
2893}
2894
2895/// Deliver `text` into a session that is running right now, or say why not.
2896///
2897/// A refusal is a RESULT, not a JSON-RPC error: "that session is persisted
2898/// only" is an answer about the session, which a mirror renders next to the
2899/// transcript, and this service's error envelope carries no structured data
2900/// field a machine-readable reason could survive in.
2901///
2902/// `delivered_to_bus` is the honest ceiling of what the courier proves. The
2903/// message reached the receiving session's inbox; whether that session ever
2904/// reads it is governed by ITS OWN inbound controls (`crossSessionInbound`,
2905/// approval dialogs), which Supercode neither sees nor overrides.
2906#[cfg(feature = "adapter-api")]
2907async fn message_live_session(
2908    params: &MessageSessionParams,
2909    runner: &dyn crate::claude_peer::CourierRunner,
2910) -> Value {
2911    if params.locator.harness.as_str() != HarnessId::CLAUDE_CODE {
2912        return json!({
2913            "delivered_to_bus": false,
2914            "refusal": {
2915                "reason": crate::claude_peer::ClaudePeerRefusal::HarnessUnsupported.as_str(),
2916                "message": format!(
2917                    "`{}` does not publish a live-session registry; only claude-code sessions can be messaged in place",
2918                    params.locator.harness.as_str()
2919                ),
2920            },
2921        });
2922    }
2923    let (inbound_controls, inbound_controls_error) =
2924        claude_inbound_controls_or_error(&params.homes);
2925    match crate::claude_peer::message_claude_peer(
2926        &params.homes,
2927        &params.locator.session_id,
2928        &params.text,
2929        runner,
2930    )
2931    .await
2932    {
2933        Ok(delivery) => json!({
2934            "delivered_to_bus": true,
2935            "target": {
2936                "session_id": delivery.target.session_id,
2937                "name": delivery.target.name,
2938                "pid": delivery.target.pid,
2939                "cwd": delivery.target.cwd,
2940                "status": delivery.target.status.map(|status| status.as_str()),
2941            },
2942            "courier": {
2943                "model": crate::claude_peer::COURIER_MODEL,
2944                "report": delivery.courier_report,
2945            },
2946            "inbound_controls": inbound_controls,
2947            "inbound_controls_error": inbound_controls_error,
2948        }),
2949        Err(refusal) => json!({
2950            "delivered_to_bus": false,
2951            "refusal": {"reason": refusal.reason.as_str(), "message": refusal.message},
2952            "inbound_controls": inbound_controls,
2953            "inbound_controls_error": inbound_controls_error,
2954        }),
2955    }
2956}
2957
2958/// Source identity of one follow subscription, plus the last lifecycle state
2959/// already reported on it. The follower itself stays purely persistence-facing.
2960// Only the adapter-api poll reads these; the subscription bookkeeping itself is
2961// shared by both builds.
2962#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
2963struct FollowedSource {
2964    harness: String,
2965    session_id: String,
2966    reported: Option<String>,
2967}
2968
2969#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
2970struct ActivitySubscription {
2971    locators: Vec<SessionLocator>,
2972    homes: crate::HarnessHomes,
2973    reported: BTreeMap<(String, String), crate::SessionActivity>,
2974}
2975
2976fn peers_for_descriptors(
2977    descriptors: &[SessionDescriptor],
2978    homes: &HarnessHomes,
2979) -> Vec<crate::claude_peer::ClaudePeerSession> {
2980    if descriptors
2981        .iter()
2982        .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
2983    {
2984        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
2985    } else {
2986        Vec::new()
2987    }
2988}
2989
2990/// Add the live address that makes an indexed row behaviorally equivalent to a discovered row.
2991///
2992/// The durable index owns only persistence metadata. Live endpoints remain projections: every
2993/// message/attach operation revalidates its authority, so publishing one here never trusts a stale
2994/// browser-held handle. Reading the Claude registry once per batch keeps this O(peers + rows).
2995fn live_descriptor_value(
2996    session: &SessionDescriptor,
2997    peers: &[crate::claude_peer::ClaudePeerSession],
2998) -> std::result::Result<Value, ServiceError> {
2999    let mut value = serde_json::to_value(session)
3000        .map_err(|error| ServiceError::Operation(error.to_string()))?;
3001    if let Some(workspace) = &session.cwd {
3002        let source = LiveRuntimeSource {
3003            harness: session.locator.harness.as_str().to_string(),
3004            session_id: session.locator.session_id.clone(),
3005            workspace: workspace.clone(),
3006        };
3007        if let Some(endpoint) = discover_live_runtime(&source)
3008            .map_err(|error| ServiceError::Operation(error.to_string()))?
3009        {
3010            value["live_endpoint"] = json!(endpoint.as_str());
3011        }
3012    }
3013    if value.get("live_endpoint").is_none() {
3014        if let Some(peer) = peers.iter().find(|peer| {
3015            session.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3016                && peer.session_id == session.locator.session_id
3017        }) {
3018            value["live_endpoint"] = json!(peer.endpoint().as_str());
3019        }
3020    }
3021    Ok(value)
3022}
3023
3024fn live_index_changes(
3025    changes: Vec<crate::session_index::SessionIndexChange>,
3026    homes: &HarnessHomes,
3027) -> std::result::Result<Vec<Value>, ServiceError> {
3028    use crate::session_index::SessionIndexChange;
3029    let has_claude = changes.iter().any(|change| match change {
3030        SessionIndexChange::Added { descriptor } | SessionIndexChange::Updated { descriptor } => {
3031            descriptor.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3032        }
3033        SessionIndexChange::Removed { .. } => false,
3034    });
3035    let peers = if has_claude {
3036        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
3037    } else {
3038        Vec::new()
3039    };
3040    changes
3041        .into_iter()
3042        .map(|change| match change {
3043            SessionIndexChange::Added { descriptor } => Ok(json!({
3044                "kind": "added",
3045                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3046            })),
3047            SessionIndexChange::Updated { descriptor } => Ok(json!({
3048                "kind": "updated",
3049                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3050            })),
3051            SessionIndexChange::Removed { key } => Ok(json!({
3052                "kind": "removed",
3053                "key": key,
3054            })),
3055        })
3056        .collect()
3057}
3058
3059fn legacy_live_status(activity: &crate::SessionActivity) -> Option<&'static str> {
3060    use crate::{SessionPresence, SessionTurnState};
3061    match (activity.presence, activity.turn) {
3062        (SessionPresence::Persisted, _) => None,
3063        (SessionPresence::Running, SessionTurnState::Working) => Some("busy"),
3064        (SessionPresence::Running, SessionTurnState::Idle) => Some("idle"),
3065        // The normalized activity object can honestly report a live owner even
3066        // when the stock harness never published a turn status. Preserve the
3067        // older field's stricter contract instead of guessing `running`.
3068        (SessionPresence::Running, SessionTurnState::Unknown)
3069            if activity.evidence.native_state.is_none() =>
3070        {
3071            None
3072        }
3073        (SessionPresence::Running, _) | (SessionPresence::ShuttingDown, _) => Some("running"),
3074    }
3075}
3076
3077#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
3078#[serde(rename_all = "kebab-case")]
3079enum TransferFormat {
3080    ClaudeCode,
3081    Codex,
3082    #[serde(rename = "opencode", alias = "open-code")]
3083    OpenCode,
3084    Pi,
3085    Grok,
3086    Gemini,
3087    Goose,
3088    /// UNI-18: a Hermes target. Its artifact is the Codex rollout that
3089    /// `hermes sessions import --from codex` reads; `sessions.export` performs
3090    /// that import into the Hermes home.
3091    Hermes,
3092}
3093
3094impl TransferFormat {
3095    fn id(self) -> &'static str {
3096        match self {
3097            Self::ClaudeCode => HarnessId::CLAUDE_CODE,
3098            Self::Codex => HarnessId::CODEX,
3099            Self::OpenCode => HarnessId::OPENCODE,
3100            Self::Pi => HarnessId::PI,
3101            Self::Grok => HarnessId::GROK,
3102            Self::Gemini => HarnessId::GEMINI,
3103            Self::Goose => HarnessId::GOOSE,
3104            Self::Hermes => HarnessId::HERMES,
3105        }
3106    }
3107}
3108
3109impl From<TransferFormat> for SessionFormat {
3110    fn from(value: TransferFormat) -> Self {
3111        match value {
3112            TransferFormat::ClaudeCode => Self::ClaudeCode,
3113            TransferFormat::Codex => Self::Codex,
3114            TransferFormat::OpenCode => Self::OpenCode,
3115            TransferFormat::Pi => Self::Pi,
3116            TransferFormat::Grok => Self::Grok,
3117            TransferFormat::Gemini => Self::Gemini,
3118            TransferFormat::Goose => Self::Goose,
3119            // a Hermes artifact is the Codex rollout Hermes imports
3120            TransferFormat::Hermes => Self::Codex,
3121        }
3122    }
3123}
3124
3125#[derive(Deserialize)]
3126struct ImportSessionParams {
3127    source_harness: TransferFormat,
3128    content: String,
3129}
3130
3131#[derive(Deserialize)]
3132struct ExportSessionParams {
3133    locator: SessionLocator,
3134    target_harness: TransferFormat,
3135}
3136
3137#[derive(Deserialize)]
3138struct ReduceSessionParams {
3139    locator: SessionLocator,
3140    target_harness: TransferFormat,
3141    #[serde(default = "default_keep_last")]
3142    keep_last: usize,
3143}
3144
3145fn default_keep_last() -> usize {
3146    6
3147}
3148
3149#[derive(Deserialize)]
3150struct BranchSessionParams {
3151    locator: SessionLocator,
3152    #[serde(default)]
3153    target_harness: Option<TransferFormat>,
3154}
3155
3156#[derive(Deserialize)]
3157struct HandoffSessionParams {
3158    locator: SessionLocator,
3159    target_harness: TransferFormat,
3160    #[serde(default)]
3161    cwd: Option<PathBuf>,
3162}
3163
3164#[derive(Debug, Clone, Copy, Default, Deserialize)]
3165#[serde(rename_all = "snake_case")]
3166enum ResumePolicy {
3167    #[default]
3168    Default,
3169    Yolo,
3170}
3171
3172#[derive(Deserialize)]
3173struct ResumeInstructionsParams {
3174    locator: SessionLocator,
3175    #[serde(default)]
3176    cwd: Option<PathBuf>,
3177    #[serde(default)]
3178    policy: ResumePolicy,
3179}
3180
3181/// `harness.v1.workflow.load` parameters: which harness's board, and its home.
3182#[derive(Deserialize)]
3183struct WorkflowLoadParams {
3184    from: crate::workflow_doors::WorkflowHarness,
3185    home: PathBuf,
3186}
3187
3188/// ONT-4 `harness.v1.orchestration.load` parameters. `flavor` says which layout the
3189/// folder is read as; our own is the default.
3190#[derive(Deserialize)]
3191struct OrchestrationLoadParams {
3192    root: PathBuf,
3193    #[serde(default)]
3194    flavor: crate::orchestration_doors::HomeFlavor,
3195}
3196
3197/// ONT-4 `harness.v1.orchestration.save` parameters. `vault` is merged into the
3198/// home's own secrets; a caller that sends none keeps what is on disk.
3199#[derive(Deserialize)]
3200struct OrchestrationSaveParams {
3201    root: PathBuf,
3202    orchestration: crate::orchestration::Orchestration,
3203    #[serde(default)]
3204    vault: BTreeMap<String, String>,
3205}
3206
3207/// ONT-4 `harness.v1.orchestration.compile` parameters.
3208#[derive(Deserialize)]
3209struct OrchestrationCompileParams {
3210    from: crate::orchestration_doors::OrchestrationHarness,
3211    home: PathBuf,
3212}
3213
3214/// ONT-4 `harness.v1.orchestration.decompile` parameters. `source` is the home the
3215/// orchestration was compiled from: it is re-compiled to recover the io bookkeeping
3216/// that byte reuse and the live-store refusal (UNI-18) are decided from.
3217#[derive(Deserialize)]
3218struct OrchestrationDecompileParams {
3219    to: crate::orchestration_doors::OrchestrationHarness,
3220    orchestration: crate::orchestration::Orchestration,
3221    source: PathBuf,
3222    #[serde(default)]
3223    source_flavor: crate::orchestration_doors::SourceFlavor,
3224    dest: PathBuf,
3225    #[serde(default)]
3226    vault: BTreeMap<String, String>,
3227}
3228
3229/// `harness.v1.orchestration.import` parameters: another harness's home, and the
3230/// folder of ours it becomes.
3231#[derive(Deserialize)]
3232struct OrchestrationImportParams {
3233    from: crate::orchestration_doors::OrchestrationHarness,
3234    home: PathBuf,
3235    into: PathBuf,
3236}
3237
3238/// `harness.v1.orchestration.export` parameters: a folder of ours, and the home of
3239/// another harness it becomes.
3240#[derive(Deserialize)]
3241struct OrchestrationExportParams {
3242    to: crate::orchestration_doors::OrchestrationHarness,
3243    root: PathBuf,
3244    dest: PathBuf,
3245}
3246
3247/// `harness.v1.jobs.get` parameters.
3248#[derive(Deserialize)]
3249struct JobsGetParams {
3250    harness: String,
3251    id: String,
3252    #[serde(default)]
3253    homes: crate::HarnessHomes,
3254}
3255
3256/// ORCH-18: run one mutating job verb through the harness's own CLI.
3257///
3258/// The refusal ladder is deliberate: a harness with no scheduled-job concept
3259/// at all answers with the SAME sentence `jobs.list` gives it, and a harness
3260/// that has jobs but publishes no client-callable verb (Claude Code, whose
3261/// jobs are created by the model inside a session) answers with its own
3262/// reason. Neither is ever a silent no-op.
3263fn mutate_job(
3264    verb: crate::jobs_control::JobVerb,
3265    params: Value,
3266) -> std::result::Result<Value, ServiceError> {
3267    let mutation = decode::<crate::jobs_control::JobMutation>(params)?;
3268    refuse_harness_without_jobs(&mutation.harness, &format!("jobs.{}", verb.as_str()))?;
3269    let outcome = crate::jobs_control::mutate(verb, &mutation).map_err(job_control_error)?;
3270    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3271}
3272
3273/// ORCH-22: run one mutating skills verb through the harness's own door.
3274///
3275/// The refusal ladder mirrors `jobs.*`: a harness with no skills root at all
3276/// answers with the same sentence `skills.list` gives it, and a harness whose
3277/// door does not publish this verb (OpenClaw has no `skills remove` at the
3278/// pin) answers with its own reason. Neither is ever a silent no-op.
3279fn mutate_skill(
3280    verb: crate::skills_control::SkillVerb,
3281    params: Value,
3282) -> std::result::Result<Value, ServiceError> {
3283    let mutation = decode::<crate::skills_control::SkillMutation>(params)?;
3284    if !crate::skills_control::supports_skill_control(&mutation.harness) {
3285        return Err(ServiceError::UnsupportedAction(format!(
3286            "`{}` has no skills root supercode reads; `skills.{}` is supported for: {}",
3287            mutation.harness,
3288            verb.as_str(),
3289            crate::skills_control::CONTROLLED_SKILL_HARNESSES.join(", ")
3290        )));
3291    }
3292    let outcome =
3293        crate::skills_control::mutate_skill(verb, &mutation).map_err(skill_control_error)?;
3294    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3295}
3296
3297/// The skills twin of [`job_control_error`], with the same mapping rule.
3298fn skill_control_error(error: crate::skills_control::SkillControlError) -> ServiceError {
3299    match error {
3300        crate::skills_control::SkillControlError::Unsupported(message) => {
3301            ServiceError::UnsupportedAction(message)
3302        }
3303        crate::skills_control::SkillControlError::Invalid(message) => {
3304            ServiceError::InvalidParams(message)
3305        }
3306        crate::skills_control::SkillControlError::Failed(message) => {
3307            ServiceError::Operation(message)
3308        }
3309    }
3310}
3311
3312/// ORCH-21: run one mutating profile verb through the harness's own CLI.
3313///
3314/// The refusal ladder mirrors `mutate_job`'s: a harness with no profile
3315/// concept at all answers with the SAME sentence `profiles.list` gives it, and
3316/// a harness that HAS profiles but publishes no client-callable verb (Codex's
3317/// file-authored `[profiles.<name>]` tables, supercode's compiled-in presets)
3318/// answers with its own reason. Neither is ever a silent no-op.
3319fn mutate_profile(
3320    verb: crate::profiles_control::ProfileVerb,
3321    params: Value,
3322) -> std::result::Result<Value, ServiceError> {
3323    let mutation = decode::<crate::profiles_control::ProfileMutation>(params)?;
3324    let outcome =
3325        crate::profiles_control::mutate(verb, &mutation).map_err(profile_control_error)?;
3326    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3327}
3328
3329/// The same mapping `job_control_error` applies, for the profile noun.
3330fn profile_control_error(error: crate::profiles_control::ProfileControlError) -> ServiceError {
3331    match error {
3332        crate::profiles_control::ProfileControlError::Unsupported(message) => {
3333            ServiceError::UnsupportedAction(message)
3334        }
3335        crate::profiles_control::ProfileControlError::Invalid(message) => {
3336            ServiceError::InvalidParams(message)
3337        }
3338        crate::profiles_control::ProfileControlError::Failed(message) => {
3339            ServiceError::Operation(message)
3340        }
3341    }
3342}
3343
3344/// Map a controlled-tier failure onto the service's error vocabulary. A verb
3345/// the harness lacks is `UnsupportedAction`; a harness verb that RAN and
3346/// failed carries its own stderr through as the operation error.
3347fn job_control_error(error: crate::jobs_control::JobControlError) -> ServiceError {
3348    match error {
3349        crate::jobs_control::JobControlError::Unsupported(message) => {
3350            ServiceError::UnsupportedAction(message)
3351        }
3352        crate::jobs_control::JobControlError::Invalid(message) => {
3353            ServiceError::InvalidParams(message)
3354        }
3355        crate::jobs_control::JobControlError::Failed(message) => ServiceError::Operation(message),
3356    }
3357}
3358
3359/// Map an ORCH-19 controlled-tier failure onto the service's error
3360/// vocabulary. A verb the harness has no door for is `UnsupportedAction`; a
3361/// door that RAN and failed carries the harness's own stderr / HTTP body
3362/// through as the operation error.
3363fn session_control_error(error: crate::SessionControlError) -> ServiceError {
3364    match error {
3365        crate::SessionControlError::Unsupported(message) => {
3366            ServiceError::UnsupportedAction(message)
3367        }
3368        crate::SessionControlError::Invalid(message) => ServiceError::InvalidParams(message),
3369        crate::SessionControlError::Failed(message) => ServiceError::Operation(message),
3370    }
3371}
3372
3373/// A harness without a scheduled-job concept refuses the verb rather than
3374/// answering with an empty list — an absent capability and an empty inventory
3375/// are different answers (the same rule `runtimes.capabilities` applies to
3376/// `steer`).
3377fn refuse_harness_without_jobs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3378    if crate::jobs::supports_jobs(harness) {
3379        return Ok(());
3380    }
3381    Err(ServiceError::UnsupportedAction(format!(
3382        "`{harness}` has no scheduled jobs; `{verb}` is supported for: {}",
3383        crate::jobs::JOB_HARNESSES.join(", ")
3384    )))
3385}
3386
3387/// `harness.v1.runs.get` parameters.
3388#[derive(Deserialize)]
3389struct RunsGetParams {
3390    harness: String,
3391    id: String,
3392    #[serde(default)]
3393    homes: crate::HarnessHomes,
3394}
3395
3396/// A harness with no run store refuses the verb rather than answering with an
3397/// empty history — the same rule `jobs.list` applies. Claude Code lands here
3398/// on purpose: its cron fires are ordinary turns inside the session that
3399/// created the job, so there is no fire record to list.
3400fn refuse_harness_without_runs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3401    if crate::runs::supports_runs(harness) {
3402        return Ok(());
3403    }
3404    Err(ServiceError::UnsupportedAction(format!(
3405        "`{harness}` keeps no run store; `{verb}` is supported for: {}",
3406        crate::runs::RUN_HARNESSES.join(", ")
3407    )))
3408}
3409
3410#[derive(Serialize)]
3411struct SessionArtifact {
3412    source_harness: HarnessId,
3413    target_harness: &'static str,
3414    session_id: Option<String>,
3415    content: String,
3416    suggested_filename: String,
3417    files: Vec<SessionArtifactFile>,
3418    fidelity: Fidelity,
3419    residue: Vec<String>,
3420}
3421
3422#[derive(Serialize)]
3423struct SessionArtifactFile {
3424    path: String,
3425    content: String,
3426    role: ArtifactFileRole,
3427}
3428
3429#[derive(Serialize)]
3430#[serde(rename_all = "snake_case")]
3431enum ArtifactFileRole {
3432    Primary,
3433    Subagent,
3434    Bundle,
3435    SourceRecovery,
3436}
3437
3438#[derive(Serialize)]
3439struct StructuredLaunch {
3440    cwd: PathBuf,
3441    program: String,
3442    arguments: Vec<String>,
3443    env: BTreeMap<String, String>,
3444}
3445
3446struct HandoffInstructions {
3447    launch: StructuredLaunch,
3448    materialize: Option<StructuredLaunch>,
3449    requires_materialization: bool,
3450    note: String,
3451}
3452
3453#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
3454#[serde(rename_all = "snake_case")]
3455enum HarnessProbeLevel {
3456    #[default]
3457    Passive,
3458    Handshake,
3459}
3460
3461#[derive(Default, Deserialize)]
3462#[serde(default)]
3463struct HarnessInventoryParams {
3464    harness: Option<HarnessId>,
3465    harnesses: Vec<HarnessId>,
3466    workspace: Option<PathBuf>,
3467    probe: HarnessProbeLevel,
3468    include_sessions: bool,
3469    /// Omit subprocess-based `--version` calls when a latency-sensitive UI only needs readiness.
3470    skip_versions: bool,
3471}
3472
3473#[derive(Deserialize)]
3474struct HarnessAuthenticationParams {
3475    harness: HarnessId,
3476}
3477
3478#[derive(Deserialize)]
3479struct BeginHarnessAuthenticationParams {
3480    harness: HarnessId,
3481    #[serde(default = "local_browser_authentication_environment")]
3482    environment: crate::HarnessAuthenticationEnvironment,
3483    #[serde(default)]
3484    method: Option<crate::HarnessAuthenticationMethodId>,
3485    #[serde(default)]
3486    cwd: Option<PathBuf>,
3487}
3488
3489fn local_browser_authentication_environment() -> crate::HarnessAuthenticationEnvironment {
3490    crate::HarnessAuthenticationEnvironment::LocalBrowser
3491}
3492
3493#[derive(Serialize)]
3494struct HarnessInventoryReport {
3495    probe: HarnessProbeLevel,
3496    workspace: Option<PathBuf>,
3497    harnesses: Vec<LocalHarness>,
3498}
3499
3500#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3501#[serde(rename_all = "snake_case")]
3502enum HarnessAuthState {
3503    Ready,
3504    Configured,
3505    Required,
3506    Unknown,
3507}
3508
3509#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3510#[serde(rename_all = "snake_case")]
3511enum HarnessRuntimeState {
3512    Ready,
3513    Degraded,
3514    Unavailable,
3515}
3516
3517#[derive(Serialize)]
3518struct HarnessSessionCounts {
3519    global: Option<usize>,
3520    workspace: Option<usize>,
3521}
3522
3523/// Receipt-backed evidence that a harness has a RUNNING instance right now,
3524/// distinct from being merely installed (UNI-7). Detection is passive and
3525/// default-on: a gateway liveness connect for daemon harnesses, a fresh
3526/// SQLite WAL stamp for store-writer harnesses (precedent: the opencode
3527/// follower's -wal/-shm freshness). Control stays behind per-connection
3528/// grants — this reports observations only.
3529/// ORCH-17: the gateway-health noun on an inventory row. Derived from the
3530/// UNI-7 running-instance probe (Hermes: `state.db-wal` freshness; OpenClaw:
3531/// a TCP connect to the gateway endpoint resolved from its OWN config) plus
3532/// the executable version — never by starting anything.
3533#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3534#[serde(rename_all = "snake_case")]
3535pub enum GatewayState {
3536    Up,
3537    Down,
3538    Unknown,
3539}
3540
3541/// ORCH-17: `gateway` on a `harness.v1.harnesses.list` row.
3542#[derive(Debug, Clone, Serialize)]
3543pub struct GatewayHealth {
3544    pub state: GatewayState,
3545    /// The endpoint supercode would connect to (OpenClaw: the gateway
3546    /// WebSocket resolved from `openclaw.json`; core harnesses: their
3547    /// declared connect address when one exists). `None` when the harness
3548    /// has no single endpoint (Hermes multiplexes platforms).
3549    #[serde(skip_serializing_if = "Option::is_none")]
3550    pub endpoint: Option<String>,
3551    #[serde(skip_serializing_if = "Option::is_none")]
3552    pub version: Option<String>,
3553    /// What the verdict rests on, or why it is `unknown`.
3554    pub evidence: String,
3555    pub checked_at_ms: u64,
3556}
3557
3558/// OpenClaw's gateway WebSocket endpoint, resolved from its own config the
3559/// way the registry's connect descriptor prescribes (`gateway.url`, else
3560/// `gateway.port`, else the documented default).
3561fn openclaw_gateway_endpoint(home: &Path) -> String {
3562    let config_path = home.join(".openclaw/openclaw.json");
3563    let gateway = std::fs::read_to_string(&config_path)
3564        .ok()
3565        .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
3566        .and_then(|config| config.get("gateway").cloned());
3567    if let Some(url) = gateway
3568        .as_ref()
3569        .and_then(|gateway| gateway.get("url"))
3570        .and_then(serde_json::Value::as_str)
3571    {
3572        return url.to_string();
3573    }
3574    let port = gateway
3575        .as_ref()
3576        .and_then(|gateway| gateway.get("port"))
3577        .and_then(serde_json::Value::as_u64)
3578        .unwrap_or(18789);
3579    format!("ws://127.0.0.1:{port}")
3580}
3581
3582/// Ask Hermes itself (`hermes gateway status`, read-only, ~1 s) whether its
3583/// gateway is up. The command is per-host launchd/systemd text without a JSON
3584/// form at 0.19–0.21; the verdict is read from the lines it prints:
3585/// "supervised by launchd (PID …)" / "is running" → up, "not running" /
3586/// "not installed" → down, anything else → no verdict. `SUPERCODE_HERMES_BIN`
3587/// overrides the executable so a fake can stand in under test.
3588fn hermes_gateway_status() -> Option<(GatewayState, String)> {
3589    let program = crate::harness_command::harness_program(HarnessId::HERMES).ok()?;
3590    let output = std::process::Command::new(&program)
3591        .args(["gateway", "status"])
3592        .stdin(std::process::Stdio::null())
3593        .output()
3594        .ok()?;
3595    let text = format!(
3596        "{}{}",
3597        String::from_utf8_lossy(&output.stdout),
3598        String::from_utf8_lossy(&output.stderr)
3599    );
3600    let verdict = text.lines().find_map(|line| {
3601        let l = line.trim();
3602        if l.contains("supervised by launchd (PID")
3603            || l.contains("supervised by systemd (PID")
3604            || l.contains("Gateway is running")
3605            || l.contains("process is running")
3606        {
3607            Some((GatewayState::Up, format!("`hermes gateway status`: {l}")))
3608        } else if l.contains("not running") || l.contains("not installed") {
3609            Some((GatewayState::Down, format!("`hermes gateway status`: {l}")))
3610        } else {
3611            None
3612        }
3613    });
3614    verdict
3615}
3616
3617fn gateway_health(
3618    id: &str,
3619    installed: bool,
3620    running: Option<&RunningInstance>,
3621    version: Option<&str>,
3622) -> GatewayHealth {
3623    let checked_at_ms = now_epoch_ms();
3624    let home = std::env::var_os("HOME").map(PathBuf::from);
3625    match id {
3626        HarnessId::HERMES | HarnessId::OPENCLAW => {
3627            let endpoint = (id == HarnessId::OPENCLAW)
3628                .then(|| home.as_deref().map(openclaw_gateway_endpoint))
3629                .flatten();
3630            let (state, evidence) = match running {
3631                Some(instance) => (GatewayState::Up, instance.evidence.clone()),
3632                None if !installed => (
3633                    GatewayState::Unknown,
3634                    format!("`{id}` is not installed; no gateway to probe"),
3635                ),
3636                None if id == HarnessId::HERMES => match hermes_gateway_status() {
3637                    // The harness's own door outranks the WAL heuristic: an idle
3638                    // gateway writes nothing for minutes yet is up.
3639                    Some((state, evidence)) => (state, evidence),
3640                    None => (
3641                        GatewayState::Down,
3642                        "no fresh state.db-wal activity under ~/.hermes and `hermes gateway status` gave no verdict".to_string(),
3643                    ),
3644                },
3645                None => (
3646                    GatewayState::Down,
3647                    format!(
3648                        "no TCP listener at {}",
3649                        endpoint.as_deref().unwrap_or("the gateway endpoint")
3650                    ),
3651                ),
3652            };
3653            GatewayHealth {
3654                state,
3655                endpoint,
3656                version: version.map(str::to_string),
3657                evidence,
3658                checked_at_ms,
3659            }
3660        }
3661        // ORC-7: the orchestrator's gateway IS its daemon, and the daemon's
3662        // own lease file is the record of it. A lease naming a live pid is
3663        // up; a lease whose process is gone is down and says so as a STALE
3664        // lease, never as "no lease"; no lease at all is down. Nothing is
3665        // started, and no port is guessed — the daemon multiplexes adapters
3666        // the way Hermes does, so it has no single endpoint either.
3667        HarnessId::ORCHESTRATOR => {
3668            let root = crate::HarnessHomes::default().orchestrator;
3669            let (state, evidence) = match crate::orchestrator::read_lease(&root) {
3670                Some(lease) if crate::orchestrator::pid_is_live(lease.pid) => (
3671                    GatewayState::Up,
3672                    format!(
3673                        "`{}` names pid {} (started {}), which is live",
3674                        crate::orchestrator::lock_path(&root).display(),
3675                        lease.pid,
3676                        lease.started_at
3677                    ),
3678                ),
3679                Some(lease) => (
3680                    GatewayState::Down,
3681                    format!(
3682                        "stale lease `{}`: pid {} is gone",
3683                        crate::orchestrator::lock_path(&root).display(),
3684                        lease.pid
3685                    ),
3686                ),
3687                None => (
3688                    GatewayState::Down,
3689                    format!(
3690                        "no lease at `{}`; `supercode orchestrator start` writes one",
3691                        crate::orchestrator::lock_path(&root).display()
3692                    ),
3693                ),
3694            };
3695            GatewayHealth {
3696                state,
3697                endpoint: None,
3698                version: version.map(str::to_string),
3699                evidence,
3700                checked_at_ms,
3701            }
3702        }
3703        _ => GatewayHealth {
3704            state: GatewayState::Unknown,
3705            endpoint: None,
3706            version: version.map(str::to_string),
3707            evidence: format!("`{id}` runs per session, not as a gateway"),
3708            checked_at_ms,
3709        },
3710    }
3711}
3712
3713#[derive(Debug, Clone, Serialize)]
3714struct RunningInstance {
3715    /// How the instance was detected.
3716    method: RunningInstanceMethod,
3717    /// The evidence the verdict rests on (endpoint reached / WAL path+age).
3718    evidence: String,
3719    /// Epoch-ms instant the probe executed.
3720    checked_at_ms: u64,
3721}
3722
3723#[derive(Debug, Clone, Copy, Serialize)]
3724#[serde(rename_all = "snake_case")]
3725enum RunningInstanceMethod {
3726    /// A TCP connect to the harness's own configured gateway endpoint
3727    /// succeeded.
3728    GatewayConnect,
3729    /// The harness's session store has an active SQLite WAL (a live writer
3730    /// holds the store open and stamped it recently).
3731    StoreWalActivity,
3732}
3733
3734fn now_epoch_ms() -> u64 {
3735    std::time::SystemTime::now()
3736        .duration_since(std::time::UNIX_EPOCH)
3737        .map(|elapsed| elapsed.as_millis() as u64)
3738        .unwrap_or(0)
3739}
3740
3741/// OpenClaw: the gateway endpoint comes from the harness's OWN config
3742/// (`<home>/.openclaw/openclaw.json` — `gateway.url` or `gateway.port`,
3743/// default port 18789); a successful TCP connect is the running signal.
3744fn probe_openclaw_running(home: &Path) -> Option<RunningInstance> {
3745    let config_path = home.join(".openclaw/openclaw.json");
3746    let text = std::fs::read_to_string(&config_path).ok();
3747    let gateway = text
3748        .as_deref()
3749        .and_then(|raw| serde_json::from_str::<serde_json::Value>(raw).ok())
3750        .and_then(|config| config.get("gateway").cloned());
3751    let address = gateway
3752        .as_ref()
3753        .and_then(|gateway| gateway.get("url"))
3754        .and_then(serde_json::Value::as_str)
3755        .and_then(|url| {
3756            url.split("://").nth(1).map(|rest| {
3757                rest.trim_end_matches('/')
3758                    .split('/')
3759                    .next()
3760                    .unwrap_or(rest)
3761                    .to_string()
3762            })
3763        })
3764        .unwrap_or_else(|| {
3765            let port = gateway
3766                .as_ref()
3767                .and_then(|gateway| gateway.get("port"))
3768                .and_then(serde_json::Value::as_u64)
3769                .unwrap_or(18789);
3770            format!("127.0.0.1:{port}")
3771        });
3772    let reachable = std::net::TcpStream::connect_timeout(
3773        &address.parse().ok()?,
3774        std::time::Duration::from_millis(400),
3775    )
3776    .is_ok();
3777    reachable.then(|| RunningInstance {
3778        method: RunningInstanceMethod::GatewayConnect,
3779        evidence: format!(
3780            "gateway endpoint {address} accepted a TCP connect (from {})",
3781            config_path.display()
3782        ),
3783        checked_at_ms: now_epoch_ms(),
3784    })
3785}
3786
3787/// Hermes: `<home>/.hermes/state.db-wal` freshly modified means a live writer
3788/// holds the store open (SQLite WAL exists only while a connection is open;
3789/// a recent stamp distinguishes an active instance from a stale crash
3790/// leftover).
3791fn probe_hermes_running(home: &Path, max_wal_age_ms: u64) -> Option<RunningInstance> {
3792    let wal = home.join(".hermes/state.db-wal");
3793    let modified = std::fs::metadata(&wal).ok()?.modified().ok()?;
3794    let age_ms = std::time::SystemTime::now()
3795        .duration_since(modified)
3796        .map(|age| age.as_millis() as u64)
3797        .unwrap_or(u64::MAX);
3798    (age_ms <= max_wal_age_ms).then(|| RunningInstance {
3799        method: RunningInstanceMethod::StoreWalActivity,
3800        evidence: format!(
3801            "{} stamped {age_ms}ms ago (threshold {max_wal_age_ms}ms)",
3802            wal.display()
3803        ),
3804        checked_at_ms: now_epoch_ms(),
3805    })
3806}
3807
3808/// Default-on running-instance detection for the harnesses that have one.
3809fn probe_running_instance(id: &str) -> Option<RunningInstance> {
3810    let home = std::env::var_os("HOME").map(PathBuf::from)?;
3811    match id {
3812        HarnessId::OPENCLAW => probe_openclaw_running(&home),
3813        HarnessId::HERMES => probe_hermes_running(&home, 300_000),
3814        _ => None,
3815    }
3816}
3817
3818#[derive(Serialize)]
3819struct LocalHarness {
3820    id: HarnessId,
3821    display_name: String,
3822    supported: bool,
3823    installed: bool,
3824    executable: Option<String>,
3825    version: Option<String>,
3826    auth: HarnessAuthState,
3827    runtime: HarnessRuntimeState,
3828    protocol: String,
3829    capabilities: crate::RuntimeCapabilities,
3830    effective_capabilities: crate::RuntimeCapabilities,
3831    sessions: HarnessSessionCounts,
3832    /// Receipt-backed running-instance detection (None = not detected or the
3833    /// harness has no running-instance concept). Distinct from `installed`.
3834    #[serde(skip_serializing_if = "Option::is_none")]
3835    running: Option<RunningInstance>,
3836    /// ORCH-17: gateway health derived from `running` + the harness's own config.
3837    gateway: GatewayHealth,
3838    reason: Option<String>,
3839    repair: Option<String>,
3840}
3841
3842#[derive(Clone, Deserialize)]
3843struct RuntimeBackendParams {
3844    harness: HarnessId,
3845    #[serde(default)]
3846    protocol: Option<String>,
3847    #[serde(default)]
3848    launch: Option<RuntimeLaunch>,
3849    #[serde(default)]
3850    base_url: Option<String>,
3851    #[serde(default)]
3852    policy: RuntimePolicy,
3853}
3854
3855#[derive(Debug, Clone, Copy, Default, Deserialize)]
3856#[serde(rename_all = "snake_case")]
3857enum RuntimePolicy {
3858    #[default]
3859    Default,
3860    Yolo,
3861}
3862
3863#[derive(Deserialize)]
3864struct RuntimeStartParams {
3865    #[serde(flatten)]
3866    backend: RuntimeBackendParams,
3867    cwd: PathBuf,
3868    /// MCP servers to mount into the new session through the harness's own
3869    /// start door (ORC-6). Backends without such a door ignore them.
3870    #[serde(default)]
3871    mcp_servers: Vec<crate::McpServerLaunch>,
3872}
3873
3874#[derive(Deserialize)]
3875struct RuntimeAttachParams {
3876    #[serde(flatten)]
3877    backend: RuntimeBackendParams,
3878    runtime_id: String,
3879    #[serde(default)]
3880    cwd: Option<PathBuf>,
3881}
3882
3883#[derive(Deserialize)]
3884struct RuntimeConnectionParams {
3885    connection: String,
3886}
3887
3888#[derive(Deserialize)]
3889struct RuntimeInputParams {
3890    connection: String,
3891    text: String,
3892    #[serde(default)]
3893    image_urls: Vec<String>,
3894}
3895
3896const MAX_RUNTIME_IMAGES: usize = 4;
3897const MAX_RUNTIME_IMAGE_URL_BYTES: usize = 12 * 1024 * 1024;
3898const MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL: usize = 32 * 1024 * 1024;
3899
3900fn validate_runtime_image_urls(image_urls: Vec<String>) -> Result<Vec<String>, ServiceError> {
3901    if image_urls.len() > MAX_RUNTIME_IMAGES {
3902        return Err(ServiceError::InvalidParams(format!(
3903            "a runtime prompt accepts at most {MAX_RUNTIME_IMAGES} images"
3904        )));
3905    }
3906    let mut total = 0usize;
3907    for url in &image_urls {
3908        if !(url.starts_with("data:image/")
3909            || url.starts_with("https://")
3910            || url.starts_with("http://"))
3911        {
3912            return Err(ServiceError::InvalidParams(
3913                "runtime images must be image data URLs or HTTP(S) URLs".into(),
3914            ));
3915        }
3916        if url.len() > MAX_RUNTIME_IMAGE_URL_BYTES {
3917            return Err(ServiceError::InvalidParams(format!(
3918                "one runtime image exceeds the {MAX_RUNTIME_IMAGE_URL_BYTES}-byte encoded limit"
3919            )));
3920        }
3921        total = total.saturating_add(url.len());
3922    }
3923    if total > MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL {
3924        return Err(ServiceError::InvalidParams(format!(
3925            "runtime images exceed the {MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL}-byte encoded total limit"
3926        )));
3927    }
3928    Ok(image_urls)
3929}
3930
3931#[derive(Deserialize)]
3932struct RuntimeRespondParams {
3933    connection: String,
3934    request_id: Value,
3935    response: Value,
3936}
3937
3938fn default_reduction_store_root() -> PathBuf {
3939    if let Some(root) = std::env::var_os("SUPERCODE_HOME") {
3940        return PathBuf::from(root).join("sessions");
3941    }
3942    if let Some(home) = std::env::var_os("HOME") {
3943        return PathBuf::from(home).join(".supercode").join("sessions");
3944    }
3945    PathBuf::from(".supercode").join("sessions")
3946}
3947
3948fn messages_jsonl(messages: &[crate::ChatMessage]) -> std::result::Result<String, ServiceError> {
3949    let mut output = String::new();
3950    for message in messages {
3951        output.push_str(
3952            &serde_json::to_string(message)
3953                .map_err(|error| ServiceError::Operation(error.to_string()))?,
3954        );
3955        output.push('\n');
3956    }
3957    Ok(output)
3958}
3959
3960fn parse_messages_jsonl(
3961    content: &str,
3962) -> std::result::Result<Vec<crate::ChatMessage>, ServiceError> {
3963    content
3964        .lines()
3965        .enumerate()
3966        .filter(|(_, line)| !line.trim().is_empty())
3967        .map(|(index, line)| {
3968            serde_json::from_str::<crate::ChatMessage>(line).map_err(|error| {
3969                ServiceError::Operation(format!(
3970                    "reduced transcript line {} is invalid: {error}",
3971                    index + 1
3972                ))
3973            })
3974        })
3975        .collect()
3976}
3977
3978fn reduced_bootstrap_prompt(
3979    source: &SessionLocator,
3980    target: TransferFormat,
3981    view_jsonl: &str,
3982    sidecar_path: &Path,
3983    reduction_log_path: &Path,
3984) -> String {
3985    format!(
3986        "Continue the work from this losslessly reduced {source_harness} session in {target_harness}.\n\
3987         \n\
3988         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\
3989         \n\
3990         <supercode-reduced-session source-session=\"{source_id}\">\n\
3991         {view_jsonl}\
3992         </supercode-reduced-session>\n\
3993         \n\
3994         Resume from the latest unresolved user request and preserve the source session's decisions and constraints.",
3995        source_harness = source.harness.as_str(),
3996        target_harness = target.id(),
3997        sidecar = sidecar_path.display(),
3998        log = reduction_log_path.display(),
3999        source_id = source.session_id,
4000    )
4001}
4002
4003fn session_artifact(
4004    locator: &SessionLocator,
4005    session: &Session,
4006    target: TransferFormat,
4007) -> std::result::Result<SessionArtifact, ServiceError> {
4008    session_artifact_with_id(locator, session, target, None)
4009}
4010
4011fn session_artifact_with_id(
4012    locator: &SessionLocator,
4013    session: &Session,
4014    target: TransferFormat,
4015    target_session_id: Option<&str>,
4016) -> std::result::Result<SessionArtifact, ServiceError> {
4017    let format: SessionFormat = target.into();
4018    let diagonal = format.source() == session.meta.source;
4019    let has_appended_turns = session
4020        .imported_message_count
4021        .is_some_and(|imported| imported < session.messages.len());
4022    let content = if let Some(id) = target_session_id {
4023        if diagonal && format != SessionFormat::OpenCode {
4024            session
4025                .to_jsonl_spliced(format, Some(id))
4026                .map_err(operation)?
4027        } else {
4028            let mut rewritten = session.clone();
4029            rewritten.meta.session_id = Some(id.to_string());
4030            rewritten.to_jsonl(format).map_err(operation)?
4031        }
4032    } else if diagonal && session.raw_is_verbatim && !has_appended_turns {
4033        session.raw_verbatim()
4034    } else if diagonal {
4035        session.to_jsonl_spliced(format, None).map_err(operation)?
4036    } else {
4037        session.to_jsonl(format).map_err(operation)?
4038    };
4039    let stem = sanitize_filename(
4040        target_session_id
4041            .or(session.meta.session_id.as_deref())
4042            .unwrap_or(&locator.session_id),
4043    );
4044    let suggested_filename = if diagonal && target == TransferFormat::Grok {
4045        "chat_history.jsonl".to_string()
4046    } else if target == TransferFormat::Goose {
4047        format!("{stem}.goose.json")
4048    } else {
4049        format!("{stem}.{}.jsonl", target.id())
4050    };
4051    let mut files = vec![SessionArtifactFile {
4052        path: suggested_filename.clone(),
4053        content: content.clone(),
4054        role: ArtifactFileRole::Primary,
4055    }];
4056    if target == TransferFormat::ClaudeCode {
4057        let bundle_stem = Path::new(&suggested_filename)
4058            .file_stem()
4059            .and_then(|stem| stem.to_str())
4060            .unwrap_or(&stem);
4061        let mut child_paths = BTreeSet::new();
4062        for (index, subagent) in session.subagents.iter().enumerate() {
4063            let agent_id = subagent
4064                .meta
4065                .agent_id
4066                .as_deref()
4067                .map(|id| id.strip_prefix("agent-").unwrap_or(id))
4068                .map(sanitize_filename)
4069                .filter(|id| !id.is_empty())
4070                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4071            let child_has_appended_turns = subagent
4072                .imported_message_count
4073                .is_some_and(|imported| imported < subagent.messages.len());
4074            let child_content = if target_session_id.is_none()
4075                && subagent.meta.source == SessionSource::ClaudeCode
4076                && subagent.raw_is_verbatim
4077                && !child_has_appended_turns
4078            {
4079                subagent.raw_verbatim()
4080            } else if subagent.meta.source == SessionSource::ClaudeCode {
4081                subagent
4082                    .to_jsonl_spliced(SessionFormat::ClaudeCode, target_session_id)
4083                    .map_err(operation)?
4084            } else {
4085                let mut child = subagent.clone();
4086                if let Some(id) = target_session_id {
4087                    child.meta.session_id = Some(id.to_string());
4088                }
4089                child
4090                    .to_jsonl(SessionFormat::ClaudeCode)
4091                    .map_err(operation)?
4092            };
4093            let path = format!("{bundle_stem}/subagents/agent-{agent_id}.jsonl");
4094            if !child_paths.insert(path.clone()) {
4095                return Err(ServiceError::Operation(format!(
4096                    "Claude subagent ids collide at artifact path `{path}`"
4097                )));
4098            }
4099            files.push(SessionArtifactFile {
4100                path,
4101                content: child_content,
4102                role: ArtifactFileRole::Subagent,
4103            });
4104        }
4105    }
4106    if diagonal && target == TransferFormat::Grok {
4107        append_grok_bundle_files(locator, "", ArtifactFileRole::Bundle, &mut files)?;
4108    }
4109    if !diagonal || !session.raw_is_verbatim {
4110        files.push(SessionArtifactFile {
4111            path: "recovery/source.supercode.jsonl".into(),
4112            content: session.to_native_jsonl(),
4113            role: ArtifactFileRole::SourceRecovery,
4114        });
4115        for (index, subagent) in session.subagents.iter().enumerate() {
4116            let id = subagent
4117                .meta
4118                .agent_id
4119                .as_deref()
4120                .map(sanitize_filename)
4121                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4122            files.push(SessionArtifactFile {
4123                path: format!("recovery/subagents/{id}.supercode.jsonl"),
4124                content: subagent.to_native_jsonl(),
4125                role: ArtifactFileRole::SourceRecovery,
4126            });
4127        }
4128    }
4129    if !diagonal && session.meta.source == SessionSource::Grok {
4130        append_grok_bundle_files(
4131            locator,
4132            "recovery/grok/",
4133            ArtifactFileRole::SourceRecovery,
4134            &mut files,
4135        )?;
4136    }
4137    let (fidelity, residue) = if diagonal
4138        && target_session_id.is_none()
4139        && session.raw_is_verbatim
4140        && !has_appended_turns
4141    {
4142        (Fidelity::ByteLossless, Vec::new())
4143    } else if diagonal && !(target_session_id.is_some() && target == TransferFormat::OpenCode) {
4144        (
4145            Fidelity::ValueLossless,
4146            vec![if target_session_id.is_some() {
4147                "target identity was rewritten, so the artifact intentionally differs from source bytes".into()
4148            } else {
4149                "source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured".into()
4150            }],
4151        )
4152    } else {
4153        (
4154            Fidelity::Semantic,
4155            vec!["target schema has no portable slot for every source-native record and metadata field".into()],
4156        )
4157    };
4158    Ok(SessionArtifact {
4159        source_harness: locator.harness.clone(),
4160        target_harness: target.id(),
4161        session_id: target_session_id
4162            .map(str::to_string)
4163            .or_else(|| session.meta.session_id.clone()),
4164        content,
4165        suggested_filename,
4166        files,
4167        fidelity,
4168        residue,
4169    })
4170}
4171
4172fn append_grok_bundle_files(
4173    locator: &SessionLocator,
4174    prefix: &str,
4175    role: ArtifactFileRole,
4176    files: &mut Vec<SessionArtifactFile>,
4177) -> std::result::Result<(), ServiceError> {
4178    let primary = locator.storage.path();
4179    if primary.file_name().and_then(|name| name.to_str()) != Some("chat_history.jsonl") {
4180        return Err(ServiceError::Operation(format!(
4181            "Grok bundle locator must name chat_history.jsonl, got {}",
4182            primary.display()
4183        )));
4184    }
4185    let parent = primary.parent().ok_or_else(|| {
4186        ServiceError::Operation("Grok chat_history.jsonl has no session directory".into())
4187    })?;
4188    for name in ["summary.json", "updates.jsonl"] {
4189        let path = parent.join(name);
4190        let metadata = match std::fs::symlink_metadata(&path) {
4191            Ok(metadata) => metadata,
4192            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
4193            Err(error) => return Err(ServiceError::Operation(error.to_string())),
4194        };
4195        if metadata.file_type().is_symlink() || !metadata.is_file() {
4196            return Err(ServiceError::Operation(format!(
4197                "refusing non-regular Grok bundle member {}",
4198                path.display()
4199            )));
4200        }
4201        let content = std::fs::read_to_string(&path).map_err(|error| {
4202            ServiceError::Operation(format!(
4203                "Grok bundle member {} is not representable as UTF-8: {error}",
4204                path.display()
4205            ))
4206        })?;
4207        files.push(SessionArtifactFile {
4208            path: format!("{prefix}{name}"),
4209            content,
4210            role: match role {
4211                ArtifactFileRole::Bundle => ArtifactFileRole::Bundle,
4212                _ => ArtifactFileRole::SourceRecovery,
4213            },
4214        });
4215    }
4216    Ok(())
4217}
4218
4219fn handoff_artifact(
4220    locator: &SessionLocator,
4221    session: &Session,
4222    target: TransferFormat,
4223    cwd: &Path,
4224) -> std::result::Result<SessionArtifact, ServiceError> {
4225    if target != TransferFormat::Grok {
4226        let target_session_id = target_session_id(target);
4227        return session_artifact_with_id(locator, session, target, Some(&target_session_id));
4228    }
4229
4230    // Stock Grok's importer accepts Claude/Codex transcripts and materializes its own
4231    // multi-file session bundle. A synthesized Grok chat_history.jsonl alone is not a
4232    // resumable handoff because updates.jsonl is the authoritative restore log.
4233    let mut importable = session.clone();
4234    // The Claude importer validates sessionId as a UUID. Source harness identities
4235    // are not portable (OpenCode, for example, uses `ses_...`), and a handoff must
4236    // not overwrite an existing target session when the source already uses UUIDs.
4237    // Mint a distinct target identity and still bind the importer-returned ID at
4238    // launch time because the importer remains the authority on materialization.
4239    importable.meta.session_id = Some(target_session_id(TransferFormat::ClaudeCode));
4240    importable.meta.cwd = Some(if cwd.is_absolute() {
4241        cwd.to_path_buf()
4242    } else {
4243        std::env::current_dir()
4244            .map_err(|error| ServiceError::Operation(error.to_string()))?
4245            .join(cwd)
4246    });
4247    let content = importable
4248        .to_jsonl(SessionFormat::ClaudeCode)
4249        .map_err(operation)?;
4250    let stem = sanitize_filename(
4251        importable
4252            .meta
4253            .session_id
4254            .as_deref()
4255            .unwrap_or(&locator.session_id),
4256    );
4257    let suggested_filename = format!("{stem}.grok-import.claude-code.jsonl");
4258    Ok(SessionArtifact {
4259        source_harness: locator.harness.clone(),
4260        // This names the artifact's actual wire format. The requested handoff target
4261        // remains Grok; its official importer is the materialization boundary.
4262        target_harness: TransferFormat::ClaudeCode.id(),
4263        session_id: importable.meta.session_id.clone(),
4264        content: content.clone(),
4265        suggested_filename: suggested_filename.clone(),
4266        files: vec![SessionArtifactFile {
4267            path: suggested_filename,
4268            content,
4269            role: ArtifactFileRole::Primary,
4270        }],
4271        fidelity: Fidelity::Semantic,
4272        residue: vec!["Grok's stock importer accepts a Claude Code transcript, not a complete Grok updates/session bundle".into()],
4273    })
4274}
4275
4276fn target_session_id(target: TransferFormat) -> String {
4277    let uuid = generated_session_id();
4278    match target {
4279        TransferFormat::OpenCode => format!("ses_{}", uuid.replace('-', "")),
4280        TransferFormat::ClaudeCode
4281        | TransferFormat::Codex
4282        | TransferFormat::Pi
4283        | TransferFormat::Grok
4284        | TransferFormat::Gemini
4285        | TransferFormat::Goose
4286        | TransferFormat::Hermes => uuid,
4287    }
4288}
4289
4290fn sanitize_filename(value: &str) -> String {
4291    let value = value
4292        .chars()
4293        .map(|character| {
4294            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
4295                character
4296            } else {
4297                '-'
4298            }
4299        })
4300        .collect::<String>();
4301    let value = value.trim_matches('-');
4302    if value.is_empty() {
4303        "session".into()
4304    } else {
4305        value.chars().take(100).collect()
4306    }
4307}
4308
4309fn handoff_instructions(
4310    target: TransferFormat,
4311    session_id: &str,
4312    cwd: &Path,
4313) -> HandoffInstructions {
4314    let launch = |program: &str, arguments: Vec<String>| StructuredLaunch {
4315        cwd: cwd.to_path_buf(),
4316        program: program.into(),
4317        arguments,
4318        env: BTreeMap::new(),
4319    };
4320    match target {
4321        TransferFormat::ClaudeCode => HandoffInstructions {
4322            launch: launch("claude", vec!["--resume".into(), session_id.into()]),
4323            materialize: None,
4324            requires_materialization: true,
4325            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(),
4326        },
4327        TransferFormat::Hermes => HandoffInstructions {
4328            launch: launch("hermes", vec!["--resume".into(), session_id.into()]),
4329            materialize: None,
4330            requires_materialization: true,
4331            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(),
4332        },
4333        TransferFormat::Codex => HandoffInstructions {
4334            launch: launch("codex", vec!["resume".into(), session_id.into()]),
4335            materialize: None,
4336            requires_materialization: true,
4337            note: "Write the artifact into Codex's native rollout store before running the resume launch; Codex has no general transcript-import command.".into(),
4338        },
4339        TransferFormat::OpenCode => HandoffInstructions {
4340            launch: launch("opencode", vec!["--session".into(), session_id.into()]),
4341            materialize: Some(launch(
4342                "opencode",
4343                vec!["import".into(), "{artifact_path}".into()],
4344            )),
4345            requires_materialization: true,
4346            note: "Write the artifact to a file, run the materialize command with its path, then launch the imported session.".into(),
4347        },
4348        TransferFormat::Pi => HandoffInstructions {
4349            launch: launch("pi", vec!["--session".into(), "{artifact_path}".into()]),
4350            materialize: None,
4351            requires_materialization: true,
4352            note: "Write the artifact to a file and replace {artifact_path} in the launch arguments; Pi can resume that file directly.".into(),
4353        },
4354        TransferFormat::Grok => HandoffInstructions {
4355            launch: launch(
4356                "grok",
4357                vec![
4358                    "--resume".into(),
4359                    "{imported_session_id}".into(),
4360                    "--fork-session".into(),
4361                ],
4362            ),
4363            materialize: Some(launch(
4364                "grok",
4365                vec!["import".into(), "--json".into(), "{artifact_path}".into()],
4366            )),
4367            requires_materialization: true,
4368            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(),
4369        },
4370        TransferFormat::Gemini => HandoffInstructions {
4371            launch: launch(
4372                "gemini",
4373                vec!["--session-file".into(), "{artifact_path}".into()],
4374            ),
4375            materialize: None,
4376            requires_materialization: true,
4377            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(),
4378        },
4379        TransferFormat::Goose => HandoffInstructions {
4380            launch: launch(
4381                "goose",
4382                vec![
4383                    "session".into(),
4384                    "--resume".into(),
4385                    "--session-id".into(),
4386                    "{imported_session_id}".into(),
4387                ],
4388            ),
4389            materialize: Some(launch(
4390                "goose",
4391                vec!["session".into(), "import".into(), "{artifact_path}".into()],
4392            )),
4393            requires_materialization: true,
4394            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(),
4395        },
4396    }
4397}
4398
4399fn resume_launch(
4400    harness: &str,
4401    session_id: &str,
4402    cwd: &Path,
4403    policy: ResumePolicy,
4404) -> std::result::Result<StructuredLaunch, ServiceError> {
4405    let mut arguments = Vec::new();
4406    let program = match harness {
4407        HarnessId::GROK => {
4408            if matches!(policy, ResumePolicy::Yolo) {
4409                if crate::support::self_sandbox_supported() {
4410                    arguments.extend(["--sandbox".into(), "workspace".into()]);
4411                }
4412                arguments.push("--always-approve".into());
4413            }
4414            arguments.extend(["--resume".into(), session_id.into()]);
4415            "grok"
4416        }
4417        HarnessId::CODEX => {
4418            let cwd_key = serde_json::to_string(cwd.to_string_lossy().as_ref())
4419                .expect("a filesystem path always serializes as JSON text");
4420            arguments.extend([
4421                "-c".into(),
4422                "check_for_update_on_startup=false".into(),
4423                "-c".into(),
4424                format!("projects.{cwd_key}.trust_level=\"trusted\""),
4425            ]);
4426            if matches!(policy, ResumePolicy::Yolo) {
4427                arguments.extend([
4428                    "--dangerously-bypass-approvals-and-sandbox".into(),
4429                    "--dangerously-bypass-hook-trust".into(),
4430                ]);
4431            }
4432            arguments.extend(["resume".into(), session_id.into()]);
4433            "codex"
4434        }
4435        HarnessId::CLAUDE_CODE => {
4436            if matches!(policy, ResumePolicy::Yolo) {
4437                arguments.push("--dangerously-skip-permissions".into());
4438            }
4439            arguments.extend(["--resume".into(), session_id.into()]);
4440            "claude"
4441        }
4442        HarnessId::GEMINI => {
4443            if matches!(policy, ResumePolicy::Yolo) {
4444                arguments.push("--yolo".into());
4445            }
4446            arguments.extend(["--resume".into(), session_id.into()]);
4447            "gemini"
4448        }
4449        HarnessId::GOOSE => {
4450            arguments.extend([
4451                "session".into(),
4452                "--resume".into(),
4453                "--session-id".into(),
4454                session_id.into(),
4455            ]);
4456            "goose"
4457        }
4458        HarnessId::PI => {
4459            if matches!(policy, ResumePolicy::Yolo) {
4460                arguments.push("--approve".into());
4461            }
4462            arguments.extend(["--session".into(), session_id.into()]);
4463            "pi"
4464        }
4465        HarnessId::OPENCODE => {
4466            arguments.extend(["--session".into(), session_id.into()]);
4467            "opencode"
4468        }
4469        HarnessId::SUPERCODE => {
4470            if matches!(policy, ResumePolicy::Yolo) {
4471                arguments.push("--dangerous".into());
4472            }
4473            arguments.extend(["resume".into(), session_id.into()]);
4474            "supercode"
4475        }
4476        other => {
4477            return Err(ServiceError::InvalidParams(format!(
4478                "no structured resume launch is registered for harness `{other}`"
4479            )))
4480        }
4481    };
4482    Ok(StructuredLaunch {
4483        cwd: cwd.to_path_buf(),
4484        program: program.into(),
4485        arguments,
4486        env: BTreeMap::new(),
4487    })
4488}
4489
4490/// Stage the resolved gateway credential in a private (0600) file so the
4491/// bridge can read it via `--token-file` — the delivery the real `openclaw
4492/// acp` accepts. One stable file per endpoint (keyed by an address digest,
4493/// no secret material in the name), overwritten on every connect so files
4494/// never accumulate and a rotated token never goes stale on disk.
4495fn openclaw_gateway_token_file(address: &str, secret: &str) -> std::io::Result<PathBuf> {
4496    let digest = blake3::hash(address.as_bytes()).to_hex();
4497    let path = std::env::temp_dir().join(format!(
4498        "supercode-openclaw-gateway-token-{}",
4499        &digest.as_str()[..16]
4500    ));
4501    #[cfg(unix)]
4502    {
4503        use std::io::Write;
4504        use std::os::unix::fs::OpenOptionsExt;
4505        let mut file = std::fs::OpenOptions::new()
4506            .write(true)
4507            .create(true)
4508            .truncate(true)
4509            .mode(0o600)
4510            .open(&path)?;
4511        file.write_all(secret.as_bytes())?;
4512    }
4513    #[cfg(not(unix))]
4514    std::fs::write(&path, secret)?;
4515    Ok(path)
4516}
4517
4518/// Open a connect-mode descriptor: resolve the endpoint address and
4519/// credential from the harness's own config file and build the backend that
4520/// joins the already-running endpoint. Fails closed with a specific
4521/// diagnostic when the config cannot be resolved or the declared protocol has
4522/// no connect-capable client yet.
4523fn open_connect_descriptor(
4524    descriptor: &crate::HarnessSupportDescriptor,
4525    home: &Path,
4526) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
4527    let Some(connect) = &descriptor.runtime.connect_launch else {
4528        return Err(ServiceError::InvalidParams(format!(
4529            "harness `{}` has no registered connect-mode launch",
4530            descriptor.id.as_str()
4531        )));
4532    };
4533    let resolved = connect
4534        .resolve(home)
4535        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
4536    match (descriptor.id.as_str(), connect.protocol.as_str()) {
4537        (HarnessId::OPENCODE, protocol) if protocol.starts_with("opencode-http") => {
4538            let mut backend = OpenCodeRuntimeBackend::connect(&resolved.address);
4539            if let Some(token) = resolved.auth {
4540                backend = backend.with_bearer(token);
4541            }
4542            Ok(Box::new(backend))
4543        }
4544        (HarnessId::OPENCLAW, protocol) if protocol.starts_with("acp") => {
4545            // OpenClaw's own `openclaw acp` binary is the gateway client: a
4546            // stdio ACP bridge that joins the RUNNING gateway at the resolved
4547            // endpoint. Blind-walk finding 2026-08-31: the real bridge does
4548            // NOT honor OPENCLAW_GATEWAY_TOKEN from the environment — the
4549            // credential must arrive via `--token-file` (never bare `--token`
4550            // on argv, where process listings could read it). The env var is
4551            // still set for older bridges that did read it. Requires openclaw
4552            // >= 2026.7: the 2026.2 bridge drops its gateway socket
4553            // mid-prompt and advertises no session resume (executed finding,
4554            // docs/interop/research/openclaw-acp-dialect-2026-08-30.json).
4555            let mut env = BTreeMap::new();
4556            let mut arguments = vec!["acp".into(), "--url".into(), resolved.address.clone()];
4557            if let Some(token) = resolved.auth {
4558                let token_path = openclaw_gateway_token_file(&resolved.address, token.secret())
4559                    .map_err(|error| {
4560                        ServiceError::UnsupportedAction(format!(
4561                            "could not stage the gateway credential for the bridge: {error}"
4562                        ))
4563                    })?;
4564                arguments.push("--token-file".into());
4565                arguments.push(token_path.to_string_lossy().into_owned());
4566                env.insert("OPENCLAW_GATEWAY_TOKEN".to_string(), token.secret().to_string());
4567            }
4568            // The bridge program comes from the descriptor's own default
4569            // launch (the compiled registry pins `openclaw`), so tests can
4570            // substitute an absolute mock-bridge path without touching
4571            // process-global state.
4572            let program = descriptor
4573                .runtime
4574                .default_launch
4575                .as_ref()
4576                .map(|launch| launch.program.clone())
4577                .unwrap_or_else(|| "openclaw".into());
4578            let launch = RuntimeLaunch {
4579                program,
4580                arguments,
4581                env,
4582            };
4583            Ok(Box::new(
4584                crate::AcpRuntimeBackend::new(descriptor.id.clone(), launch)
4585                    .with_resume_support(descriptor.runtime.capabilities.resume_session),
4586            ))
4587        }
4588        _ => Err(ServiceError::UnsupportedAction(format!(
4589            "connect-mode endpoint for `{}` speaks `{}`; joining it needs that protocol's gateway client",
4590            descriptor.id.as_str(),
4591            connect.protocol
4592        ))),
4593    }
4594}
4595
4596/// The registry's connect-mode launch for this harness, honored only when the
4597/// caller supplied neither an explicit launch nor a base URL.
4598fn registry_connect_descriptor(
4599    params: &RuntimeBackendParams,
4600) -> Option<crate::HarnessSupportDescriptor> {
4601    if params.launch.is_some() || params.base_url.is_some() {
4602        return None;
4603    }
4604    harness_support_registry()
4605        .harnesses
4606        .into_iter()
4607        .find(|descriptor| descriptor.id == params.harness)
4608        .filter(|descriptor| descriptor.runtime.connect_launch.is_some())
4609}
4610
4611fn service_home() -> std::result::Result<PathBuf, ServiceError> {
4612    std::env::var_os("HOME").map(PathBuf::from).ok_or_else(|| {
4613        ServiceError::UnsupportedAction(
4614            "connect-mode launches need HOME to locate the harness config".into(),
4615        )
4616    })
4617}
4618
4619fn runtime_backend(
4620    params: &RuntimeBackendParams,
4621) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
4622    if let Some(descriptor) = registry_connect_descriptor(params) {
4623        return open_connect_descriptor(&descriptor, &service_home()?);
4624    }
4625    if params.protocol.as_deref() == Some("acp") {
4626        let launch = params
4627            .launch
4628            .clone()
4629            .or_else(|| {
4630                harness_support_registry()
4631                    .harnesses
4632                    .into_iter()
4633                    .find(|harness| harness.id == params.harness)
4634                    .filter(|harness| {
4635                        harness.runtime.implementation == ImplementationKind::GenericProtocol
4636                            && harness.runtime.protocol.starts_with("acp")
4637                    })
4638                    .and_then(|harness| harness.runtime.default_launch)
4639            })
4640            .ok_or_else(|| {
4641                ServiceError::InvalidParams(
4642                    "an ACP runtime requires `launch` unless the harness has a registered default"
4643                        .into(),
4644                )
4645            })?;
4646        let resume_session = harness_support_registry()
4647            .harnesses
4648            .into_iter()
4649            .find(|harness| harness.id == params.harness)
4650            .is_some_and(|harness| harness.runtime.capabilities.resume_session);
4651        return Ok(Box::new(
4652            AcpRuntimeBackend::new(params.harness.clone(), launch)
4653                .with_resume_support(resume_session),
4654        ));
4655    }
4656    let backend: Box<dyn RuntimeBackend> = match params.harness.as_str() {
4657        HarnessId::CODEX => Box::new(CodexRuntimeBackend::new()),
4658        HarnessId::CLAUDE_CODE => Box::new(ClaudeCodeRuntimeBackend::new()),
4659        HarnessId::PI => Box::new(PiRuntimeBackend::new()),
4660        HarnessId::OPENCODE => match &params.base_url {
4661            Some(url) => Box::new(OpenCodeRuntimeBackend::connect(url)),
4662            None => Box::new(OpenCodeRuntimeBackend::new()),
4663        },
4664        harness => {
4665            let descriptor = harness_support_registry()
4666                .harnesses
4667                .into_iter()
4668                .find(|descriptor| descriptor.id.as_str() == harness)
4669                .filter(|descriptor| {
4670                    descriptor.runtime.implementation == ImplementationKind::GenericProtocol
4671                        && descriptor.runtime.protocol.starts_with("acp")
4672                });
4673            let Some(descriptor) = descriptor else {
4674                return Err(ServiceError::InvalidParams(format!(
4675                    "no runtime adapter for harness `{harness}`; use protocol `acp` with a launch command"
4676                )));
4677            };
4678            let resume = descriptor.runtime.capabilities.resume_session;
4679            Box::new(
4680                AcpRuntimeBackend::new(
4681                    descriptor.id,
4682                    descriptor
4683                        .runtime
4684                        .default_launch
4685                        .expect("generic ACP registry entry includes its launch"),
4686                )
4687                .with_resume_support(resume),
4688            )
4689        }
4690    };
4691    Ok(backend)
4692}
4693
4694fn runtime_launch(params: &RuntimeBackendParams) -> Option<RuntimeLaunch> {
4695    if let Some(launch) = &params.launch {
4696        return Some(launch.clone());
4697    }
4698    if !matches!(params.policy, RuntimePolicy::Yolo) {
4699        return None;
4700    }
4701    let launch = match params.harness.as_str() {
4702        HarnessId::GROK => RuntimeLaunch {
4703            program: "grok".into(),
4704            arguments: {
4705                let mut arguments: Vec<String> = Vec::new();
4706                if crate::support::self_sandbox_supported() {
4707                    arguments.extend(["--sandbox".into(), "workspace".into()]);
4708                }
4709                arguments.extend([
4710                    "--always-approve".into(),
4711                    "agent".into(),
4712                    "--no-leader".into(),
4713                    "stdio".into(),
4714                ]);
4715                arguments
4716            },
4717            env: BTreeMap::from([("GROK_AGENT_DASHBOARD".into(), "0".into())]),
4718        },
4719        HarnessId::CODEX => RuntimeLaunch {
4720            program: "codex".into(),
4721            arguments: vec![
4722                "--dangerously-bypass-approvals-and-sandbox".into(),
4723                "--dangerously-bypass-hook-trust".into(),
4724                "app-server".into(),
4725            ],
4726            env: BTreeMap::new(),
4727        },
4728        HarnessId::CLAUDE_CODE => RuntimeLaunch {
4729            program: "claude".into(),
4730            arguments: vec![
4731                "--dangerously-skip-permissions".into(),
4732                "--print".into(),
4733                "--input-format".into(),
4734                "stream-json".into(),
4735                "--output-format".into(),
4736                "stream-json".into(),
4737                "--verbose".into(),
4738            ],
4739            env: BTreeMap::new(),
4740        },
4741        HarnessId::PI => RuntimeLaunch {
4742            program: "pi".into(),
4743            arguments: vec!["--approve".into(), "--mode".into(), "rpc".into()],
4744            env: BTreeMap::new(),
4745        },
4746        HarnessId::OPENCODE => RuntimeLaunch {
4747            program: "opencode".into(),
4748            arguments: vec!["serve".into()],
4749            env: BTreeMap::new(),
4750        },
4751        HarnessId::GEMINI => RuntimeLaunch {
4752            program: "gemini".into(),
4753            arguments: vec!["--acp".into(), "--yolo".into()],
4754            env: BTreeMap::new(),
4755        },
4756        HarnessId::GOOSE => RuntimeLaunch {
4757            program: "goose".into(),
4758            arguments: vec!["acp".into()],
4759            env: BTreeMap::new(),
4760        },
4761        HarnessId::SUPERCODE => RuntimeLaunch {
4762            program: "supercode".into(),
4763            arguments: vec!["acp".into(), "--dangerous".into()],
4764            env: BTreeMap::new(),
4765        },
4766        _ => return None,
4767    };
4768    Some(launch)
4769}
4770
4771/// Disposable harness state for a no-prompt readiness probe. Merely opening
4772/// several stock CLIs writes a session header or migrates configuration, so a
4773/// handshake must never point at the user's real home. Authentication files
4774/// are copied into the private temporary home; all writes disappear with the
4775/// guard after the connection closes.
4776struct IsolatedProbeHome {
4777    launch: RuntimeLaunch,
4778    root: PathBuf,
4779}
4780
4781impl IsolatedProbeHome {
4782    fn new(harness: &str, mut launch: RuntimeLaunch) -> std::io::Result<Self> {
4783        let root = std::env::temp_dir().join(format!(
4784            "supercode-harness-probe-{harness}-{}",
4785            generated_session_id()
4786        ));
4787        std::fs::create_dir_all(&root)?;
4788        set_private_dir_permissions(&root)?;
4789
4790        if let Some(source_home) = std::env::var_os("HOME").map(PathBuf::from) {
4791            for relative in probe_auth_files(harness) {
4792                copy_probe_file(&source_home, &root, relative)?;
4793            }
4794        }
4795        configure_isolated_probe_auth(harness, &root)?;
4796
4797        let root_text = root.to_string_lossy().into_owned();
4798        for (key, value) in [
4799            ("HOME", root_text.clone()),
4800            (
4801                "XDG_CACHE_HOME",
4802                root.join(".cache").to_string_lossy().into_owned(),
4803            ),
4804            (
4805                "XDG_CONFIG_HOME",
4806                root.join(".config").to_string_lossy().into_owned(),
4807            ),
4808            (
4809                "XDG_DATA_HOME",
4810                root.join(".local/share").to_string_lossy().into_owned(),
4811            ),
4812        ] {
4813            launch.env.insert(key.into(), value);
4814        }
4815        let scoped = match harness {
4816            HarnessId::CLAUDE_CODE => Some(("CLAUDE_CONFIG_DIR", root.join(".claude"))),
4817            HarnessId::CODEX => Some(("CODEX_HOME", root.join(".codex"))),
4818            HarnessId::GEMINI => Some(("GEMINI_CLI_HOME", root.clone())),
4819            HarnessId::GROK => Some(("GROK_HOME", root.join(".grok"))),
4820            HarnessId::PI => Some(("PI_CODING_AGENT_DIR", root.join(".pi/agent"))),
4821            HarnessId::SUPERCODE => Some(("SUPERCODE_HOME", root.join(".config/supercode"))),
4822            _ => None,
4823        };
4824        if let Some((key, value)) = scoped {
4825            launch
4826                .env
4827                .insert(key.into(), value.to_string_lossy().into_owned());
4828        }
4829        Ok(Self { launch, root })
4830    }
4831
4832    fn cleanup(&self) -> std::io::Result<()> {
4833        match std::fs::remove_dir_all(&self.root) {
4834            Ok(()) => Ok(()),
4835            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
4836            Err(error) => Err(error),
4837        }
4838    }
4839}
4840
4841impl Drop for IsolatedProbeHome {
4842    fn drop(&mut self) {
4843        let _ = self.cleanup();
4844    }
4845}
4846
4847fn probe_auth_files(harness: &str) -> &'static [&'static str] {
4848    match harness {
4849        HarnessId::CLAUDE_CODE => &[".claude/.credentials.json", ".claude.json"],
4850        // The gateway endpoint + token live in openclaw's own config; without
4851        // it the isolated probe dials the default endpoint unauthenticated
4852        // (PARITY-24 finding 2026-08-31).
4853        HarnessId::OPENCLAW => &[".openclaw/openclaw.json"],
4854        HarnessId::CODEX => &[".codex/auth.json"],
4855        HarnessId::GEMINI => &[
4856            ".gemini/google_accounts.json",
4857            ".gemini/oauth_creds.json",
4858            ".gemini/settings.json",
4859        ],
4860        HarnessId::GROK => &[".grok/auth.json", ".grok/config.toml"],
4861        HarnessId::OPENCODE => &[
4862            ".config/opencode/auth.json",
4863            ".local/share/opencode/auth.json",
4864        ],
4865        HarnessId::PI => &[".pi/agent/auth.json"],
4866        // Hermes keeps its provider selection in config.yaml, its OAuth
4867        // credential pool in auth.json, and API keys in .env; without them
4868        // the isolated probe sees "No LLM provider configured" for a
4869        // hermes that answers fine from the user's real home.
4870        HarnessId::HERMES => &[".hermes/config.yaml", ".hermes/auth.json", ".hermes/.env"],
4871        HarnessId::SUPERCODE => &[
4872            ".config/supercode/config.toml",
4873            ".config/supercode/credentials.toml",
4874        ],
4875        _ => &[],
4876    }
4877}
4878
4879fn copy_probe_file(source_home: &Path, probe_home: &Path, relative: &str) -> std::io::Result<()> {
4880    let source = source_home.join(relative);
4881    if !source.is_file() {
4882        return Ok(());
4883    }
4884    let destination = probe_home.join(relative);
4885    if let Some(parent) = destination.parent() {
4886        std::fs::create_dir_all(parent)?;
4887        set_private_dir_permissions(parent)?;
4888    }
4889    std::fs::copy(source, &destination)?;
4890    set_private_file_permissions(&destination)
4891}
4892
4893fn configure_isolated_probe_auth(harness: &str, probe_home: &Path) -> std::io::Result<()> {
4894    if harness != HarnessId::GEMINI {
4895        return Ok(());
4896    }
4897    let oauth = probe_home.join(".gemini/oauth_creds.json");
4898    if !oauth.is_file() {
4899        return Ok(());
4900    }
4901    let settings_path = probe_home.join(".gemini/settings.json");
4902    let mut settings = std::fs::read_to_string(&settings_path)
4903        .ok()
4904        .and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
4905        .unwrap_or_else(|| json!({}));
4906    settings["security"]["auth"]["selectedType"] = Value::String("oauth-personal".into());
4907    std::fs::write(
4908        &settings_path,
4909        serde_json::to_vec_pretty(&settings).map_err(std::io::Error::other)?,
4910    )?;
4911    set_private_file_permissions(&settings_path)
4912}
4913
4914#[cfg(unix)]
4915fn set_private_dir_permissions(path: &Path) -> std::io::Result<()> {
4916    use std::os::unix::fs::PermissionsExt;
4917    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
4918}
4919
4920#[cfg(not(unix))]
4921fn set_private_dir_permissions(_path: &Path) -> std::io::Result<()> {
4922    Ok(())
4923}
4924
4925#[cfg(unix)]
4926fn set_private_file_permissions(path: &Path) -> std::io::Result<()> {
4927    use std::os::unix::fs::PermissionsExt;
4928    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
4929}
4930
4931#[cfg(not(unix))]
4932fn set_private_file_permissions(_path: &Path) -> std::io::Result<()> {
4933    Ok(())
4934}
4935
4936fn find_executable(program: &str) -> Option<PathBuf> {
4937    let candidate = PathBuf::from(program);
4938    if candidate.components().count() > 1 {
4939        return candidate.is_file().then_some(candidate);
4940    }
4941    let path = std::env::var_os("PATH")?;
4942    for directory in std::env::split_paths(&path) {
4943        let candidate = directory.join(program);
4944        if candidate.is_file() {
4945            return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
4946        }
4947        #[cfg(windows)]
4948        {
4949            for extension in ["exe", "cmd", "bat"] {
4950                let candidate = directory.join(format!("{program}.{extension}"));
4951                if candidate.is_file() {
4952                    return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
4953                }
4954            }
4955        }
4956    }
4957    None
4958}
4959
4960async fn executable_version(executable: &Path) -> Option<String> {
4961    let mut command = tokio::process::Command::new(executable);
4962    command
4963        .arg("--version")
4964        .stdin(std::process::Stdio::null())
4965        .stdout(std::process::Stdio::piped())
4966        .stderr(std::process::Stdio::piped())
4967        .kill_on_drop(true);
4968    let output = tokio::time::timeout(Duration::from_secs(3), command.output())
4969        .await
4970        .ok()?
4971        .ok()?;
4972    let stdout = String::from_utf8_lossy(&output.stdout);
4973    let stderr = String::from_utf8_lossy(&output.stderr);
4974    stdout
4975        .lines()
4976        .chain(stderr.lines())
4977        .map(str::trim)
4978        .find(|line| !line.is_empty())
4979        .map(|line| truncate_text(line, 200))
4980}
4981
4982pub(crate) fn auth_evidence(harness: &str) -> bool {
4983    let env_names: &[&str] = match harness {
4984        HarnessId::CLAUDE_CODE => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
4985        HarnessId::CODEX => &["OPENAI_API_KEY"],
4986        HarnessId::OPENCODE => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
4987        HarnessId::PI => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
4988        HarnessId::GROK => &["XAI_API_KEY", "GROK_API_KEY"],
4989        HarnessId::GEMINI => &["GEMINI_API_KEY", "GOOGLE_API_KEY"],
4990        HarnessId::SUPERCODE => &["OPENROUTER_API_KEY"],
4991        _ => &[],
4992    };
4993    if env_names
4994        .iter()
4995        .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
4996    {
4997        return true;
4998    }
4999    let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else {
5000        return false;
5001    };
5002    let files: Vec<PathBuf> = match harness {
5003        HarnessId::CLAUDE_CODE => vec![home.join(".claude/.credentials.json")],
5004        HarnessId::CODEX => vec![home.join(".codex/auth.json")],
5005        HarnessId::OPENCODE => vec![
5006            home.join(".local/share/opencode/auth.json"),
5007            home.join(".config/opencode/auth.json"),
5008        ],
5009        HarnessId::PI => vec![home.join(".pi/agent/auth.json")],
5010        HarnessId::GROK => vec![home.join(".grok/auth.json")],
5011        HarnessId::GEMINI => vec![
5012            home.join(".gemini/oauth_creds.json"),
5013            home.join(".gemini/google_accounts.json"),
5014        ],
5015        HarnessId::SUPERCODE => vec![home.join(".config/supercode/credentials.toml")],
5016        HarnessId::HERMES => vec![home.join(".hermes/auth.json"), home.join(".hermes/.env")],
5017        _ => Vec::new(),
5018    };
5019    if files.into_iter().any(|path| {
5020        std::fs::metadata(path)
5021            .map(|metadata| metadata.is_file() && metadata.len() > 2)
5022            .unwrap_or(false)
5023    }) {
5024        return true;
5025    }
5026    // macOS keeps Claude Code's OAuth login in the Keychain, so
5027    // `.claude/.credentials.json` never exists there and the file probe above
5028    // reports a signed-in install as unauthenticated forever. A completed
5029    // login also writes an `oauthAccount` record into `~/.claude.json` on
5030    // every platform — file-based, prompt-free evidence (querying the
5031    // Keychain itself from an unsigned daemon can raise a UI prompt).
5032    if harness == HarnessId::CLAUDE_CODE {
5033        return std::fs::read_to_string(home.join(".claude.json"))
5034            .map(|text| text.contains("\"oauthAccount\""))
5035            .unwrap_or(false);
5036    }
5037    false
5038}
5039
5040fn looks_like_auth_error(message: &str) -> bool {
5041    let message = message.to_ascii_lowercase();
5042    [
5043        "auth",
5044        "login",
5045        "sign in",
5046        "sign-in",
5047        "credential",
5048        "unauthorized",
5049        "forbidden",
5050        "token",
5051    ]
5052    .iter()
5053    .any(|needle| message.contains(needle))
5054}
5055
5056fn unavailable_capabilities() -> crate::RuntimeCapabilities {
5057    crate::RuntimeCapabilities {
5058        start_session: false,
5059        resume_session: false,
5060        attach_existing_process: false,
5061        send_input: false,
5062        stream_events: false,
5063        interrupt: false,
5064        steer: false,
5065        respond_to_requests: false,
5066    }
5067}
5068
5069fn truncate_text(text: &str, max_chars: usize) -> String {
5070    let mut chars = text.chars();
5071    let truncated = chars.by_ref().take(max_chars).collect::<String>();
5072    if chars.next().is_some() {
5073        format!("{truncated}…")
5074    } else {
5075        truncated
5076    }
5077}
5078
5079fn error_message(error: ServiceError) -> String {
5080    match error {
5081        ServiceError::InvalidParams(message)
5082        | ServiceError::Operation(message)
5083        | ServiceError::UnsupportedAction(message) => message,
5084        ServiceError::MethodNotFound => "runtime adapter is not available".into(),
5085        ServiceError::Sdk(error) => error.to_string(),
5086    }
5087}
5088
5089#[derive(Debug)]
5090enum ServiceError {
5091    InvalidParams(String),
5092    MethodNotFound,
5093    UnsupportedAction(String),
5094    Operation(String),
5095    Sdk(SdkError),
5096}
5097
5098fn sdk_error(operation: SdkOperation, error: ServiceError) -> SdkError {
5099    match error {
5100        ServiceError::InvalidParams(message) => {
5101            SdkError::new(SdkErrorCode::InvalidArgument, operation, message)
5102        }
5103        ServiceError::MethodNotFound | ServiceError::UnsupportedAction(_) => {
5104            SdkError::unsupported(operation)
5105        }
5106        ServiceError::Operation(message) => {
5107            let code = if message.contains("already in progress") {
5108                SdkErrorCode::Busy
5109            } else if message.contains("not supported by this runtime") {
5110                SdkErrorCode::UnsupportedAction
5111            } else if message.contains("unknown runtime connection") {
5112                SdkErrorCode::NotFound
5113            } else {
5114                SdkErrorCode::Execution
5115            };
5116            SdkError::new(code, operation, message)
5117        }
5118        ServiceError::Sdk(error) => error,
5119    }
5120}
5121
5122fn sdk_rpc_error(id: Value, error: &SdkError) -> Value {
5123    let error_code = error.code();
5124    let code = match error_code {
5125        SdkErrorCode::Unauthenticated => -32030,
5126        SdkErrorCode::Unauthorized => -32031,
5127        SdkErrorCode::ControllerRequired => -32032,
5128        SdkErrorCode::LeaseExpired => -32033,
5129        SdkErrorCode::InvalidArgument => -32602,
5130        SdkErrorCode::NotFound => -32004,
5131        SdkErrorCode::Busy => -32000,
5132        SdkErrorCode::UnsupportedAction => -32020,
5133        SdkErrorCode::Execution => -32002,
5134        SdkErrorCode::Transport => -32003,
5135    };
5136    json!({
5137        "jsonrpc": "2.0",
5138        "id": id,
5139        "error": {
5140            "code": code,
5141            "name": error_code,
5142            "operation": error.operation(),
5143            "message": error.to_string(),
5144        },
5145    })
5146}
5147
5148fn decode<T: for<'de> Deserialize<'de>>(value: Value) -> std::result::Result<T, ServiceError> {
5149    serde_json::from_value(value).map_err(|error| ServiceError::InvalidParams(error.to_string()))
5150}
5151
5152fn operation(error: impl Into<crate::Error>) -> ServiceError {
5153    let error = error.into();
5154    match error {
5155        crate::Error::Sdk(error) => ServiceError::Sdk(error),
5156        error => ServiceError::Operation(error.to_string()),
5157    }
5158}
5159
5160/// ORCH-12 `harness.v1.memory.show|search` params. `homes` is the same
5161/// storage-root override every read-only method accepts, so a caller can
5162/// point the read at a fixture home without touching the real ones.
5163#[derive(Debug, Clone, Deserialize, Default)]
5164#[serde(default)]
5165struct MemoryRequest {
5166    /// Harness whose store is read. Required.
5167    harness: Option<String>,
5168    /// The needle, required by `search`.
5169    query: Option<String>,
5170    /// Hermes profile, OpenClaw agent, or Claude Code project.
5171    profile: Option<String>,
5172    /// Claude Code session id selecting a project store (`show` only).
5173    session: Option<String>,
5174    /// Include each document's whole text (`show` only).
5175    full: bool,
5176    /// Treat `query` as a regular expression (`search` only).
5177    regex: bool,
5178    /// Working tree whose project store is read.
5179    cwd: Option<std::path::PathBuf>,
5180    /// Storage roots to read.
5181    homes: crate::HarnessHomes,
5182}
5183
5184/// Read the memory noun. A harness with no memory store fails with
5185/// `UnsupportedAction` (RPC `-32020`), never an empty list.
5186fn memory_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
5187    let request = decode::<MemoryRequest>(params)?;
5188    let harness = request
5189        .harness
5190        .clone()
5191        .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
5192    let to_service = |error: crate::memory::MemoryError| match error {
5193        crate::memory::MemoryError::UnsupportedHarness { .. }
5194        | crate::memory::MemoryError::SessionNotScoped { .. } => {
5195            ServiceError::UnsupportedAction(error.to_string())
5196        }
5197        other => ServiceError::InvalidParams(other.to_string()),
5198    };
5199    match method {
5200        "harness.v1.memory.show" => {
5201            let documents = crate::memory::show_memory(&crate::memory::MemoryQuery {
5202                harness,
5203                profile: request.profile,
5204                session: request.session,
5205                full: request.full,
5206                cwd: request.cwd,
5207                homes: request.homes,
5208            })
5209            .map_err(to_service)?;
5210            Ok(json!({
5211                "schema": crate::memory::MEMORY_SCHEMA,
5212                "documents": documents,
5213            }))
5214        }
5215        "harness.v1.memory.search" => {
5216            let query = request
5217                .query
5218                .ok_or_else(|| ServiceError::InvalidParams("`query` is required".into()))?;
5219            let matches = crate::memory::search_memory(&crate::memory::MemorySearchQuery {
5220                harness,
5221                query,
5222                profile: request.profile,
5223                regex: request.regex,
5224                cwd: request.cwd,
5225                homes: request.homes,
5226            })
5227            .map_err(to_service)?;
5228            Ok(json!({
5229                "schema": crate::memory::MEMORY_SCHEMA,
5230                "matches": matches,
5231            }))
5232        }
5233        _ => Err(ServiceError::MethodNotFound),
5234    }
5235}
5236
5237/// ORCH-10 `harness.v1.profiles.list|get` params. `homes` is the same
5238/// storage-root override every read-only method accepts, so a caller can
5239/// point the read at a fixture home without touching the real ones.
5240#[derive(Debug, Clone, Deserialize)]
5241#[serde(default)]
5242struct ProfilesQuery {
5243    /// Restrict the listing to one harness. `get` requires it.
5244    harness: Option<String>,
5245    /// Profile name, required by `get`.
5246    name: Option<String>,
5247    /// Storage roots to read.
5248    homes: crate::HarnessHomes,
5249}
5250
5251impl Default for ProfilesQuery {
5252    fn default() -> Self {
5253        Self {
5254            harness: None,
5255            name: None,
5256            homes: crate::HarnessHomes::default(),
5257        }
5258    }
5259}
5260
5261/// Read the profile noun. A harness with no profile concept fails with
5262/// `UnsupportedAction` (RPC `-32020`), never an empty list.
5263fn profiles_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
5264    let query = decode::<ProfilesQuery>(params)?;
5265    let to_service = |error: crate::profiles::ProfileError| match error {
5266        crate::profiles::ProfileError::UnsupportedHarness { .. } => {
5267            ServiceError::UnsupportedAction(error.to_string())
5268        }
5269        crate::profiles::ProfileError::NotFound { .. } => {
5270            ServiceError::InvalidParams(error.to_string())
5271        }
5272    };
5273    match method {
5274        "harness.v1.profiles.list" => {
5275            let profiles = crate::profiles::list_profiles(&query.homes, query.harness.as_deref())
5276                .map_err(to_service)?;
5277            Ok(json!({
5278                "schema": crate::profiles::PROFILES_SCHEMA,
5279                "profiles": profiles,
5280            }))
5281        }
5282        "harness.v1.profiles.get" => {
5283            let harness = query
5284                .harness
5285                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
5286            let name = query
5287                .name
5288                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
5289            let profile =
5290                crate::profiles::get_profile(&query.homes, &harness, &name).map_err(to_service)?;
5291            Ok(json!({
5292                "schema": crate::profiles::PROFILES_SCHEMA,
5293                "profile": profile,
5294            }))
5295        }
5296        _ => Err(ServiceError::MethodNotFound),
5297    }
5298}
5299
5300/// ORCH-14 `harness.v1.channels.list|status` params, the same storage-root
5301/// override every read-only method accepts so a caller can point the read at
5302/// a fixture home without touching the real ones.
5303#[derive(Debug, Clone, Deserialize)]
5304#[serde(default)]
5305struct ChannelsQuery {
5306    /// Restrict the listing to one harness. `status` requires it.
5307    harness: Option<String>,
5308    /// Channel name, required by `status`.
5309    name: Option<String>,
5310    /// Storage roots to read.
5311    homes: crate::HarnessHomes,
5312}
5313
5314impl Default for ChannelsQuery {
5315    fn default() -> Self {
5316        Self {
5317            harness: None,
5318            name: None,
5319            homes: crate::HarnessHomes::default(),
5320        }
5321    }
5322}
5323
5324/// Read the channel noun. A harness with no channel concept fails with
5325/// `UnsupportedAction` (RPC `-32020`), never an empty list. No row carries a
5326/// token, key or secret — see `crate::channels` "Secrecy".
5327#[derive(Debug, Clone, Deserialize)]
5328#[serde(default)]
5329struct RoutesQuery {
5330    harness: Option<String>,
5331    /// Restrict to routes targeting one profile / agent.
5332    profile: Option<String>,
5333    homes: crate::HarnessHomes,
5334}
5335
5336impl Default for RoutesQuery {
5337    fn default() -> Self {
5338        Self {
5339            harness: None,
5340            profile: None,
5341            homes: crate::HarnessHomes::default(),
5342        }
5343    }
5344}
5345
5346#[derive(Debug, Clone, Deserialize)]
5347#[serde(default)]
5348struct TriggersQuery {
5349    harness: Option<String>,
5350    homes: crate::HarnessHomes,
5351}
5352
5353impl Default for TriggersQuery {
5354    fn default() -> Self {
5355        Self {
5356            harness: None,
5357            homes: crate::HarnessHomes::default(),
5358        }
5359    }
5360}
5361
5362fn triggers_call(params: Value) -> std::result::Result<Value, ServiceError> {
5363    let query = decode::<TriggersQuery>(params)?;
5364    let triggers = crate::triggers::list_triggers(&query.homes, query.harness.as_deref())
5365        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
5366    Ok(json!({
5367        "schema": crate::triggers::TRIGGERS_SCHEMA,
5368        "triggers": triggers,
5369    }))
5370}
5371
5372fn routes_call(params: Value) -> std::result::Result<Value, ServiceError> {
5373    let query = decode::<RoutesQuery>(params)?;
5374    let routes = crate::routes::list_routes(
5375        &query.homes,
5376        query.harness.as_deref(),
5377        query.profile.as_deref(),
5378    )
5379    .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
5380    Ok(json!({
5381        "schema": crate::routes::ROUTES_SCHEMA,
5382        "routes": routes,
5383    }))
5384}
5385
5386fn channels_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
5387    let query = decode::<ChannelsQuery>(params)?;
5388    let to_service = |error: crate::channels::ChannelError| match error {
5389        crate::channels::ChannelError::UnsupportedHarness { .. } => {
5390            ServiceError::UnsupportedAction(error.to_string())
5391        }
5392        crate::channels::ChannelError::NotFound { .. } => {
5393            ServiceError::InvalidParams(error.to_string())
5394        }
5395    };
5396    match method {
5397        "harness.v1.channels.list" => {
5398            let channels = crate::channels::list_channels(&query.homes, query.harness.as_deref())
5399                .map_err(to_service)?;
5400            Ok(json!({
5401                "schema": crate::channels::CHANNELS_SCHEMA,
5402                "channels": channels,
5403            }))
5404        }
5405        "harness.v1.channels.status" => {
5406            let harness = query
5407                .harness
5408                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
5409            let name = query
5410                .name
5411                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
5412            let channel = crate::channels::channel_status(&query.homes, &harness, &name)
5413                .map_err(to_service)?;
5414            Ok(json!({
5415                "schema": crate::channels::CHANNELS_SCHEMA,
5416                "channel": channel,
5417            }))
5418        }
5419        _ => Err(ServiceError::MethodNotFound),
5420    }
5421}
5422
5423fn rpc_error(id: Value, code: i64, message: &str) -> Value {
5424    json!({
5425        "jsonrpc": "2.0",
5426        "id": id,
5427        "error": {"code": code, "message": message},
5428    })
5429}
5430
5431#[cfg(test)]
5432mod tests {
5433    use super::*;
5434    use crate::{HarnessEvent, HarnessId, RuntimeEndpoint, RuntimeHandle, StorageLocator};
5435    use async_trait::async_trait;
5436    use std::io::Write;
5437    use std::path::PathBuf;
5438    use std::time::Instant;
5439
5440    #[test]
5441    fn indexed_claude_descriptor_keeps_the_live_peer_address() {
5442        let descriptor = SessionDescriptor {
5443            locator: SessionLocator {
5444                harness: HarnessId::new(HarnessId::CLAUDE_CODE),
5445                session_id: "live-session".into(),
5446                storage: StorageLocator::File {
5447                    path: PathBuf::from("/tmp/live-session.jsonl"),
5448                },
5449            },
5450            cwd: Some(PathBuf::from("/project")),
5451            title: None,
5452            preview_candidates: Vec::new(),
5453            latest_message_candidates: Vec::new(),
5454            updated_at_ms: Some(1),
5455            message_count: None,
5456            model: None,
5457            parent_session_id: None,
5458            child_session_count: 0,
5459            nouns: Default::default(),
5460        };
5461        let peer = crate::claude_peer::ClaudePeerSession {
5462            pid: 42,
5463            session_id: "live-session".into(),
5464            cwd: Some(PathBuf::from("/project")),
5465            name: "peer".into(),
5466            socket_path: PathBuf::from("/tmp/peer.sock"),
5467            status: Some(crate::claude_peer::ClaudePeerStatus::Busy),
5468            updated_at_ms: Some(1),
5469            version: Some("test".into()),
5470        };
5471
5472        let value = live_descriptor_value(&descriptor, &[peer]).unwrap();
5473        assert!(value["live_endpoint"]
5474            .as_str()
5475            .is_some_and(|endpoint| endpoint.starts_with("cc-peer:v1:42:peer:")));
5476    }
5477
5478    struct EndingRuntime {
5479        handle: RuntimeHandle,
5480        event: Option<HarnessEvent>,
5481        close_failures: usize,
5482    }
5483
5484    #[async_trait]
5485    impl RuntimeConnection for EndingRuntime {
5486        fn handle(&self) -> &RuntimeHandle {
5487            &self.handle
5488        }
5489
5490        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
5491            unreachable!("ending runtime does not accept input")
5492        }
5493
5494        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
5495            Ok(self.event.take())
5496        }
5497
5498        async fn interrupt(&mut self) -> crate::Result<()> {
5499            Ok(())
5500        }
5501
5502        async fn respond(&mut self, _request_id: Value, _response: Value) -> crate::Result<()> {
5503            Ok(())
5504        }
5505
5506        async fn close(&mut self) -> crate::Result<()> {
5507            if self.close_failures > 0 {
5508                self.close_failures -= 1;
5509                return Err(crate::Error::Other(
5510                    "cleanup temporarily unavailable".into(),
5511                ));
5512            }
5513            Ok(())
5514        }
5515    }
5516
5517    fn ending_runtime(event: Option<HarnessEvent>) -> Box<dyn RuntimeConnection> {
5518        Box::new(EndingRuntime {
5519            handle: RuntimeHandle {
5520                harness: HarnessId::from(HarnessId::CLAUDE_CODE),
5521                runtime_id: "ending-session".into(),
5522                endpoint: RuntimeEndpoint::LocalProcess {
5523                    pid: None,
5524                    command: vec!["ending-runtime".into()],
5525                    protocol: "test".into(),
5526                },
5527            },
5528            event,
5529            close_failures: 0,
5530        })
5531    }
5532
5533    #[tokio::test]
5534    async fn failed_runtime_close_retains_ownership_until_retry_succeeds() {
5535        let mut service = HarnessSessionService::new();
5536        let handle = ending_runtime(None).handle().clone();
5537        let runtime_id = handle.runtime_id.clone();
5538        let opened = service
5539            .insert_runtime(Box::new(EndingRuntime {
5540                handle,
5541                event: None,
5542                close_failures: 1,
5543            }))
5544            .unwrap();
5545        let connection = opened["connection"].as_str().unwrap().to_string();
5546        service.terminal_launches.insert(
5547            connection.clone(),
5548            StructuredLaunch {
5549                cwd: PathBuf::from("/fixture"),
5550                program: "fixture".into(),
5551                arguments: Vec::new(),
5552                env: BTreeMap::new(),
5553            },
5554        );
5555        let first = service
5556            .handle_async(request(
5557                1,
5558                "harness.v1.runtimes.close",
5559                json!({"connection": connection}),
5560            ))
5561            .await;
5562        assert!(first.get("error").is_some(), "{first}");
5563        assert!(service.runtimes.contains_key(&connection));
5564        assert!(service.terminal_launches.contains_key(&connection));
5565        assert!(service.runtime_sequences.contains_key(&runtime_id));
5566        let retry = service
5567            .handle_async(request(
5568                2,
5569                "harness.v1.runtimes.close",
5570                json!({"connection": connection}),
5571            ))
5572            .await;
5573        assert_eq!(retry["result"]["closed"], true, "{retry}");
5574        assert!(!service.runtimes.contains_key(&connection));
5575        assert!(!service.terminal_launches.contains_key(&connection));
5576        assert!(!service.runtime_sequences.contains_key(&runtime_id));
5577    }
5578
5579    fn request(id: u64, method: &str, params: Value) -> Value {
5580        json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
5581    }
5582
5583    // ---- ORCH-6: conversation nouns on `sessions.*` ----------------------
5584
5585    fn hermes_store() -> PathBuf {
5586        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hermes_home/state.db")
5587    }
5588
5589    /// The discovery response for the Hermes fixture home, with the one
5590    /// machine-specific value (the absolute store path) replaced so the exact
5591    /// same JSON can be committed and replayed by the UI story.
5592    fn hermes_discovery(params: Value) -> Value {
5593        let mut response =
5594            HarnessSessionService::new().handle(request(1, "harness.v1.sessions.discover", params));
5595        let store = hermes_store().display().to_string();
5596        for session in response["result"]["sessions"]
5597            .as_array_mut()
5598            .expect("sessions array")
5599        {
5600            if session["locator"]["storage"]["path"] == json!(store) {
5601                session["locator"]["storage"]["path"] = json!("<fixtures>/hermes_home/state.db");
5602            }
5603            // `activity` reports a wall-clock observation instant, not a fact
5604            // about the session; it would make this response differ on every
5605            // call. The nouns under test are all session facts.
5606            session.as_object_mut().unwrap().remove("activity");
5607        }
5608        response["result"].take()
5609    }
5610
5611    fn hermes_query() -> Value {
5612        json!({
5613            "harnesses": ["hermes"],
5614            "homes": {"hermes": hermes_store()},
5615        })
5616    }
5617
5618    fn row<'a>(result: &'a Value, id: &str) -> &'a Value {
5619        result["sessions"]
5620            .as_array()
5621            .expect("sessions array")
5622            .iter()
5623            .find(|session| session["locator"]["session_id"] == json!(id))
5624            .unwrap_or_else(|| panic!("no discovered row for `{id}` in {result:#}"))
5625    }
5626
5627    #[test]
5628    fn orch6_discover_rows_carry_the_conversation_nouns() {
5629        let result = hermes_discovery(hermes_query());
5630
5631        // A Telegram DM: reached on a channel, no repo — the workspace IS the
5632        // channel (D2 precedence), and `main` is not a profile.
5633        let dm = row(&result, "tg-dm-1");
5634        assert_eq!(dm["trigger"], json!("channel"));
5635        assert_eq!(dm["surface"]["platform"], json!("telegram"));
5636        assert_eq!(dm["surface"]["kind"], json!("dm"));
5637        assert_eq!(dm["surface"]["chat_id"], json!("123456"));
5638        assert_eq!(dm["surface"]["participant_id"], json!("u1"));
5639        assert_eq!(
5640            dm["workspace"],
5641            json!({"kind": "channel", "value": "telegram:123456"})
5642        );
5643        assert!(dm.get("profile").is_none(), "{dm:#}");
5644
5645        // A cron fire: recurring, with the job recovered from the minted id.
5646        let fire = row(&result, "cron_job42_20260902_120000");
5647        assert_eq!(fire["trigger"], json!("cron"));
5648        assert_eq!(
5649            fire["recurrence"],
5650            json!({"job_id": "job42", "kind": "cron"})
5651        );
5652        assert_eq!(fire["workspace"]["kind"], json!("repo"));
5653
5654        // A profiled group session with a pending handoff: repo workspace
5655        // wins over the channel, and the chat stays on the surface key.
5656        let coder = row(&result, "tg-coder-1");
5657        assert_eq!(coder["trigger"], json!("channel"));
5658        assert_eq!(coder["profile"], json!("coder"));
5659        assert_eq!(coder["surface"]["thread_id"], json!("55"));
5660        assert_eq!(
5661            coder["surface"]["key"],
5662            json!("agent:coder:telegram:group:-100777:55")
5663        );
5664        assert_eq!(
5665            coder["workspace"],
5666            json!({"kind": "repo", "value": "/workspace/project"})
5667        );
5668        assert_eq!(
5669            coder["cross_surface"],
5670            json!({"state": "pending", "platform": "discord"})
5671        );
5672
5673        // A plain ACP session stays human-triggered with no surface at all.
5674        let acp = row(&result, "cef97234-e8e8-428a-99ab-e8fff4e7e613");
5675        assert_eq!(acp["trigger"], json!("human"));
5676        assert!(acp.get("surface").is_none(), "{acp:#}");
5677        assert_eq!(acp["workspace"], json!({"kind": "none"}));
5678    }
5679
5680    #[test]
5681    fn orch6_discover_filters_by_harness_and_profile() {
5682        let mut params = hermes_query();
5683        params["profile"] = json!("coder");
5684        let result = hermes_discovery(params);
5685        let ids: Vec<&str> = result["sessions"]
5686            .as_array()
5687            .expect("sessions array")
5688            .iter()
5689            .map(|session| session["locator"]["session_id"].as_str().unwrap())
5690            .collect();
5691        assert_eq!(ids, vec!["tg-coder-1"]);
5692
5693        // A profile no session is routed through returns nothing rather than
5694        // silently ignoring the filter.
5695        let mut missing = hermes_query();
5696        missing["profile"] = json!("nobody");
5697        assert_eq!(hermes_discovery(missing)["sessions"], json!([]));
5698
5699        // The harness filter is `harnesses`; an id no harness answers to is
5700        // an empty page, never every store on the box.
5701        let elsewhere = json!({"harnesses": ["codex"], "homes": {"codex": hermes_store()}});
5702        assert_eq!(hermes_discovery(elsewhere)["sessions"], json!([]));
5703    }
5704
5705    #[test]
5706    fn orch6_load_reports_the_same_nouns_as_discovery() {
5707        let mut service = HarnessSessionService::new();
5708        let loaded = service.handle(request(
5709            1,
5710            "harness.v1.sessions.load",
5711            json!({"locator": {
5712                "harness": "hermes",
5713                "session_id": "tg-coder-1",
5714                "storage": {"kind": "file", "path": hermes_store()},
5715            }}),
5716        ));
5717        let session = &loaded["result"]["session"];
5718        let discovered = hermes_discovery(hermes_query());
5719        let row = row(&discovered, "tg-coder-1");
5720        for noun in [
5721            "trigger",
5722            "surface",
5723            "profile",
5724            "recurrence",
5725            "cross_surface",
5726            "workspace",
5727        ] {
5728            assert_eq!(
5729                session[noun],
5730                row.get(noun).cloned().unwrap_or(Value::Null),
5731                "`{noun}` disagrees between sessions.load and sessions.discover"
5732            );
5733        }
5734    }
5735
5736    /// ORCH-10: the fixture homes, as the RPC's `homes` override. Hermes's
5737    /// home is named by its `state.db`; OpenClaw's is the state directory.
5738    fn profile_fixture_homes() -> Value {
5739        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
5740        json!({
5741            "hermes": fixtures.join("hermes_home/state.db"),
5742            "openclaw": fixtures.join("openclaw_home"),
5743        })
5744    }
5745
5746    fn profile_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
5747        response["result"]["profiles"]
5748            .as_array()
5749            .unwrap_or_else(|| panic!("no profiles array in {response}"))
5750            .iter()
5751            .find(|row| row["harness"] == harness && row["name"] == name)
5752            .unwrap_or_else(|| panic!("no `{harness}` profile `{name}` in {response}"))
5753    }
5754
5755    /// dev/01: every source answers in one row shape, over the committed
5756    /// fixture homes — the Hermes profile directory and its `state.db`
5757    /// partition, the OpenClaw agent directories and `openclaw.json`, and
5758    /// supercode's own presets.
5759    #[test]
5760    fn profiles_list_reads_every_source_uniformly() {
5761        let mut service = HarnessSessionService::new();
5762        let response = service.handle(request(
5763            1,
5764            "harness.v1.profiles.list",
5765            json!({"homes": profile_fixture_homes()}),
5766        ));
5767        assert_eq!(
5768            response["result"]["schema"],
5769            crate::profiles::PROFILES_SCHEMA
5770        );
5771
5772        let default = profile_row(&response, "hermes", "default");
5773        assert_eq!(default["kind"], "hermes_profile");
5774        assert_eq!(default["default"], true);
5775        assert_eq!(default["routes"], 0);
5776        assert_eq!(default["sessions"], 11);
5777        assert_eq!(default["model"], "anthropic/claude-sonnet-4-5");
5778
5779        let coder = profile_row(&response, "hermes", "coder");
5780        assert_eq!(coder["kind"], "hermes_profile");
5781        assert_eq!(coder["default"], false);
5782        assert_eq!(coder["routes"], 1, "gateway.profile_routes targets coder");
5783        assert_eq!(coder["sessions"], 1, "state.db profile_name = 'coder'");
5784        assert_eq!(coder["model"], "anthropic/claude-opus-4-8");
5785        assert!(coder["home"]
5786            .as_str()
5787            .unwrap()
5788            .ends_with("hermes_home/profiles/coder"));
5789
5790        let main = profile_row(&response, "openclaw", "main");
5791        assert_eq!(main["kind"], "openclaw_agent");
5792        // No entry declares `default: true` (real configs do not), so `main`
5793        // wins on OpenClaw's own convention rather than alphabetically.
5794        assert_eq!(main["default"], true);
5795        assert_eq!(main["routes"], 0);
5796        assert_eq!(main["sessions"], 4);
5797        assert_eq!(
5798            main["model"],
5799            Value::Null,
5800            "`agents.defaults.model` is an install default, not this agent's pin"
5801        );
5802
5803        let design = profile_row(&response, "openclaw", "design");
5804        assert_eq!(design["default"], false);
5805        assert_eq!(design["routes"], 1, "one binding names agentId `design`");
5806        assert_eq!(design["sessions"], 0);
5807        assert_eq!(design["model"], "anthropic/claude-opus-4-8");
5808
5809        let preset = profile_row(&response, "supercode", "supercode-default");
5810        assert_eq!(preset["kind"], "preset");
5811        assert_eq!(preset["default"], true);
5812        assert_eq!(preset["home"], Value::Null);
5813        assert_eq!(preset["routes"], Value::Null);
5814    }
5815
5816    /// Codex's own profiles are `[profiles.<name>]` tables, with the
5817    /// top-level `profile` key naming the default.
5818    #[test]
5819    fn profiles_list_reads_codex_profile_tables() {
5820        let codex_home = std::env::temp_dir().join(format!(
5821            "supercode-orch10-codex-{}-{}",
5822            std::process::id(),
5823            std::time::SystemTime::now()
5824                .duration_since(std::time::UNIX_EPOCH)
5825                .unwrap()
5826                .as_nanos()
5827        ));
5828        std::fs::create_dir_all(codex_home.join("sessions")).unwrap();
5829        std::fs::write(
5830            codex_home.join("config.toml"),
5831            "profile = \"review\"\n\n[profiles.review]\nmodel = \"gpt-5.1-codex\"\n\n[profiles.fast]\nmodel = \"gpt-5.1-codex-mini\"\n",
5832        )
5833        .unwrap();
5834
5835        let mut service = HarnessSessionService::new();
5836        let response = service.handle(request(
5837            1,
5838            "harness.v1.profiles.list",
5839            json!({"harness": "codex", "homes": {"codex": codex_home.join("sessions")}}),
5840        ));
5841        let rows = response["result"]["profiles"].as_array().unwrap();
5842        assert_eq!(rows.len(), 2, "{response}");
5843        let review = profile_row(&response, "codex", "review");
5844        assert_eq!(review["kind"], "codex_profile");
5845        assert_eq!(review["default"], true);
5846        assert_eq!(review["model"], "gpt-5.1-codex");
5847        assert_eq!(review["home"], Value::Null);
5848        assert_eq!(profile_row(&response, "codex", "fast")["default"], false);
5849
5850        let got = service.handle(request(
5851            2,
5852            "harness.v1.profiles.get",
5853            json!({
5854                "harness": "codex",
5855                "name": "fast",
5856                "homes": {"codex": codex_home.join("sessions")},
5857            }),
5858        ));
5859        assert_eq!(got["result"]["profile"]["model"], "gpt-5.1-codex-mini");
5860        std::fs::remove_dir_all(&codex_home).ok();
5861    }
5862
5863    /// A verb a harness lacks fails with `UnsupportedAction`, never a silent
5864    /// empty list; an unknown name is an invalid argument, not an empty row.
5865    #[test]
5866    fn profiles_refuse_harnesses_without_the_concept() {
5867        let mut service = HarnessSessionService::new();
5868        let response = service.handle(request(
5869            1,
5870            "harness.v1.profiles.list",
5871            json!({"harness": "claude-code"}),
5872        ));
5873        assert_eq!(response["error"]["code"], -32020, "{response}");
5874
5875        let missing = service.handle(request(
5876            2,
5877            "harness.v1.profiles.get",
5878            json!({
5879                "harness": "hermes",
5880                "name": "no-such-profile",
5881                "homes": profile_fixture_homes(),
5882            }),
5883        ));
5884        assert_eq!(missing["error"]["code"], -32602, "{missing}");
5885    }
5886
5887    /// The two methods are advertised, so a client discovers them from
5888    /// `harness.v1.capabilities` rather than from documentation.
5889    #[test]
5890    fn profiles_methods_are_advertised() {
5891        let mut service = HarnessSessionService::new();
5892        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
5893        let methods = response["result"]["methods"].as_array().unwrap();
5894        for method in ["harness.v1.profiles.list", "harness.v1.profiles.get"] {
5895            assert!(
5896                methods.iter().any(|entry| entry == method),
5897                "{method} is not advertised"
5898            );
5899        }
5900    }
5901
5902    // -----------------------------------------------------------------
5903    // ORCH-14 — channels
5904    // -----------------------------------------------------------------
5905
5906    fn channel_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
5907        response["result"]["channels"]
5908            .as_array()
5909            .unwrap_or_else(|| panic!("no channels array in {response}"))
5910            .iter()
5911            .find(|row| row["harness"] == harness && row["name"] == name)
5912            .unwrap_or_else(|| panic!("no `{harness}` channel `{name}` in {response}"))
5913    }
5914
5915    fn channels_list(harness: Option<&str>) -> Value {
5916        let mut params = json!({"homes": profile_fixture_homes()});
5917        if let Some(harness) = harness {
5918            params["harness"] = json!(harness);
5919        }
5920        HarnessSessionService::new().handle(request(1, "harness.v1.channels.list", params))
5921    }
5922
5923    /// dev/01: both sources answer in one row shape over the committed
5924    /// fixture homes — Hermes's `platforms:` blocks with their `extra` maps,
5925    /// and OpenClaw's `channels.<name>` entries split per account.
5926    #[test]
5927    fn channels_list_reads_both_gateway_harnesses_uniformly() {
5928        let response = channels_list(None);
5929        assert_eq!(
5930            response["result"]["schema"],
5931            crate::channels::CHANNELS_SCHEMA
5932        );
5933
5934        // Hermes: a credentialed platform, a bridged `extra.key` platform,
5935        // and one the config explicitly disables.
5936        let telegram = channel_row(&response, "hermes", "telegram");
5937        assert_eq!(telegram["kind"], "telegram");
5938        assert_eq!(telegram["enabled"], true);
5939        assert_eq!(telegram["configured"], true);
5940        // The `sessions` count is the discovery rows whose surface platform
5941        // is telegram: the fixture's `agent:main:telegram:…` DM and the
5942        // `agent:coder:telegram:…` group.
5943        assert_eq!(telegram["sessions"], 2);
5944        let api = channel_row(&response, "hermes", "api_server");
5945        assert_eq!(api["configured"], true, "extra.key is a credential key");
5946        assert_eq!(api["sessions"], 0);
5947        let webhook = channel_row(&response, "hermes", "webhook");
5948        assert_eq!(webhook["enabled"], false);
5949        // Hermes lists no credential for `webhook`: declaring it is all it
5950        // needs, so a credential-less entry is still `configured`.
5951        assert_eq!(webhook["configured"], true);
5952
5953        // OpenClaw: one row per account, named `<channel>/<accountId>`.
5954        let linked = channel_row(&response, "openclaw", "slack/T0FIXTURE");
5955        assert_eq!(linked["kind"], "slack");
5956        assert_eq!(linked["account"], "T0FIXTURE");
5957        assert_eq!(linked["enabled"], true);
5958        assert_eq!(linked["configured"], true);
5959        let unlinked = channel_row(&response, "openclaw", "slack/T1FIXTURE");
5960        assert_eq!(unlinked["enabled"], false);
5961        assert_eq!(
5962            unlinked["configured"], false,
5963            "an account with no credential key is not configured"
5964        );
5965        // A single-account channel keeps its own name and names its account
5966        // inline.
5967        let telegram = channel_row(&response, "openclaw", "telegram");
5968        assert_eq!(telegram["account"], "hermes-fixture-bot");
5969        assert_eq!(telegram["configured"], true);
5970
5971        // `status` is never claimed from a config file.
5972        for row in response["result"]["channels"].as_array().unwrap() {
5973            assert_eq!(row["status"], "unknown", "{row}");
5974        }
5975    }
5976
5977    /// dev/01: no field of any emitted row carries a credential. The fixture
5978    /// homes hold four FAKE credential strings; a row that leaked one — as a
5979    /// value, an account label, or a name — fails here.
5980    #[test]
5981    fn channels_rows_never_carry_a_fixture_secret() {
5982        let secrets = [
5983            "FAKE-TOKEN-DO-NOT-EMIT",
5984            "FAKE-API-SERVER-KEY-DO-NOT-EMIT",
5985            "FAKE-SLACK-BOT-TOKEN-DO-NOT-EMIT",
5986            "FAKE-SLACK-APP-TOKEN-DO-NOT-EMIT",
5987            "FAKE-TELEGRAM-TOKEN-DO-NOT-EMIT",
5988        ];
5989        // The strings really are in the fixtures, so this test can fail.
5990        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
5991        let raw = format!(
5992            "{}{}",
5993            std::fs::read_to_string(fixtures.join("hermes_home/config.yaml")).unwrap(),
5994            std::fs::read_to_string(fixtures.join("openclaw_home/openclaw.json")).unwrap(),
5995        );
5996        for secret in secrets {
5997            assert!(raw.contains(secret), "fixture no longer holds `{secret}`");
5998        }
5999
6000        let emitted = serde_json::to_string(&channels_list(None)["result"]).unwrap();
6001        for secret in secrets {
6002            assert!(
6003                !emitted.contains(secret),
6004                "`{secret}` leaked into a channel row: {emitted}"
6005            );
6006        }
6007        // Belt and braces: no row FIELD is credential-shaped either, so a
6008        // future field cannot smuggle one past the literal scan.
6009        for row in channels_list(None)["result"]["channels"]
6010            .as_array()
6011            .unwrap()
6012        {
6013            for key in row.as_object().unwrap().keys() {
6014                let key = key.to_ascii_lowercase();
6015                assert!(
6016                    !["token", "key", "secret", "password", "credential"]
6017                        .iter()
6018                        .any(|marker| key.ends_with(marker)),
6019                    "`{key}` is a credential-shaped field on a channel row"
6020                );
6021            }
6022        }
6023    }
6024
6025    /// `status` answers one row by name, and refuses an unknown one.
6026    #[test]
6027    fn channels_status_reads_one_row_by_name() {
6028        let mut service = HarnessSessionService::new();
6029        let got = service.handle(request(
6030            1,
6031            "harness.v1.channels.status",
6032            json!({
6033                "harness": "openclaw",
6034                "name": "slack/T0FIXTURE",
6035                "homes": profile_fixture_homes(),
6036            }),
6037        ));
6038        assert_eq!(got["result"]["channel"]["kind"], "slack");
6039        assert_eq!(got["result"]["channel"]["account"], "T0FIXTURE");
6040        assert_eq!(got["result"]["channel"]["status"], "unknown");
6041
6042        let missing = service.handle(request(
6043            2,
6044            "harness.v1.channels.status",
6045            json!({
6046                "harness": "openclaw",
6047                "name": "no-such-channel",
6048                "homes": profile_fixture_homes(),
6049            }),
6050        ));
6051        assert_eq!(missing["error"]["code"], -32602, "{missing}");
6052    }
6053
6054    /// A harness with no channel concept fails with `UnsupportedAction`,
6055    /// never a silent empty list — Claude Code included, because its channels
6056    /// are MCP-protocol declarations no config file names.
6057    #[test]
6058    fn channels_refuse_harnesses_without_the_concept() {
6059        let response = channels_list(Some("claude-code"));
6060        assert_eq!(response["error"]["code"], -32020, "{response}");
6061        let codex = channels_list(Some("codex"));
6062        assert_eq!(codex["error"]["code"], -32020, "{codex}");
6063    }
6064
6065    /// The harness filter restricts the rows rather than being ignored.
6066    #[test]
6067    fn channels_list_filters_by_harness() {
6068        let response = channels_list(Some("openclaw"));
6069        let rows = response["result"]["channels"].as_array().unwrap();
6070        assert!(!rows.is_empty(), "{response}");
6071        assert!(
6072            rows.iter().all(|row| row["harness"] == "openclaw"),
6073            "harness filter leaked: {response}"
6074        );
6075    }
6076
6077    /// Both methods are advertised, so a client discovers them from
6078    /// `harness.v1.capabilities` rather than from documentation.
6079    #[test]
6080    fn channels_methods_are_advertised() {
6081        let mut service = HarnessSessionService::new();
6082        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
6083        let methods = response["result"]["methods"].as_array().unwrap();
6084        for method in ["harness.v1.channels.list", "harness.v1.channels.status"] {
6085            assert!(
6086                methods.iter().any(|entry| entry == method),
6087                "{method} is not advertised"
6088            );
6089        }
6090    }
6091
6092    /// The UI story renders REAL rows: this writes the discovery response the
6093    /// two assertions above pin into the fixture the Storybook
6094    /// `Compositions/Universal nouns` stories import, and fails when the
6095    /// committed copy has drifted from what the service now answers.
6096    #[test]
6097    fn orch6_story_fixture_matches_the_live_discovery_response() {
6098        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6099            .join("../../sdk/ui/stories/fixtures/hermes-discovery.json");
6100        let mut result = hermes_discovery(hermes_query());
6101        // `updated_at_ms` is derived from the fixture's own stored timestamps,
6102        // so the whole response is deterministic; drop only the cursor, which
6103        // is pagination state rather than a session fact.
6104        result.as_object_mut().unwrap().remove("next_cursor");
6105        let rendered = format!("{}\n", serde_json::to_string_pretty(&result).unwrap());
6106        if std::env::var_os("SUPERCODE_UPDATE_FIXTURES").is_some() {
6107            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
6108            std::fs::write(&path, &rendered).unwrap();
6109        }
6110        let committed = std::fs::read_to_string(&path).unwrap_or_default();
6111        assert_eq!(
6112            committed, rendered,
6113            "sdk/ui/stories/fixtures/hermes-discovery.json is stale — \
6114             re-run with SUPERCODE_UPDATE_FIXTURES=1"
6115        );
6116    }
6117
6118    fn pi_locator() -> SessionLocator {
6119        SessionLocator {
6120            harness: HarnessId::from(HarnessId::PI),
6121            session_id: "1e6f2a3b-0000-4000-8000-000000000001".into(),
6122            storage: StorageLocator::File {
6123                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6124                    .join("tests/fixtures/pi_session.jsonl"),
6125            },
6126        }
6127    }
6128
6129    fn opencode_locator() -> SessionLocator {
6130        let session_id = "ses_fixtureAAAAAAAAAAAAAAA1";
6131        SessionLocator {
6132            harness: HarnessId::from(HarnessId::OPENCODE),
6133            session_id: session_id.into(),
6134            storage: StorageLocator::Sqlite {
6135                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6136                    .join("tests/fixtures/opencode_fixture/opencode.db"),
6137                selector: session_id.into(),
6138            },
6139        }
6140    }
6141
6142    fn grok_locator() -> SessionLocator {
6143        SessionLocator {
6144            harness: HarnessId::from(HarnessId::GROK),
6145            session_id: "73c09283-4b33-41fa-90f1-0bcb0f7be523".into(),
6146            storage: StorageLocator::File {
6147                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6148                    .join("tests/fixtures/grok_session/chat_history.jsonl"),
6149            },
6150        }
6151    }
6152
6153    // ---- ORCH-11: `harness.v1.skills.list` -------------------------------
6154
6155    fn fixture_homes() -> Value {
6156        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6157        json!({
6158            "claude_code": fixtures.join("__absent__"),
6159            "codex": fixtures.join("__absent__"),
6160            "opencode": fixtures.join("__absent__"),
6161            "pi": fixtures.join("__absent__"),
6162            "agents": fixtures.join("__absent__"),
6163            "hermes": fixtures.join("hermes_home"),
6164            "openclaw": fixtures.join("openclaw_home"),
6165        })
6166    }
6167
6168    #[test]
6169    fn preview_search_uses_the_discovery_rpc_and_refuses_live_subscription() {
6170        let root = std::env::temp_dir().join(format!(
6171            "supercode-preview-rpc-{}-{}",
6172            std::process::id(),
6173            std::time::SystemTime::now()
6174                .duration_since(std::time::UNIX_EPOCH)
6175                .unwrap()
6176                .as_nanos()
6177        ));
6178        std::fs::create_dir_all(&root).unwrap();
6179        for id in ["first", "second"] {
6180            std::fs::write(root.join(format!("{id}.jsonl")), format!("{}\n{}\n",
6181                json!({"type": "session_meta", "payload": {"id": id, "cwd": "/workspace"}}),
6182                json!({"type": "event_msg", "payload": {"type": "agent_message", "message": "NEBULA result"}}),
6183            )).unwrap();
6184        }
6185        let mut service = HarnessSessionService::new();
6186        let query = json!({
6187            "harnesses": ["codex"], "homes": {"codex": root},
6188            "query": "nebula", "search_previews": true, "limit": 1
6189        });
6190        let first = service.handle(request(1, "harness.v1.sessions.discover", query.clone()));
6191        assert!(first.get("error").is_none(), "{first}");
6192        assert_eq!(first["result"]["receipt"]["searched_previews"], true);
6193        assert_eq!(first["result"]["receipt"]["total_matched"], 2);
6194        let mut next_query = query.clone();
6195        next_query["cursor"] = first["result"]["next_cursor"].clone();
6196        let next = service.handle(request(2, "harness.v1.sessions.discover", next_query));
6197        assert_eq!(next["result"]["receipt"]["returned"], 1);
6198        assert_eq!(next["result"]["receipt"]["total_matched"], 2);
6199        assert_eq!(next["result"]["receipt"]["truncated"], false);
6200        assert_ne!(
6201            first["result"]["sessions"][0]["locator"],
6202            next["result"]["sessions"][0]["locator"]
6203        );
6204        let refused = service.handle(request(3, "harness.v1.sessions.index.subscribe", query));
6205        assert!(
6206            refused["error"]["message"]
6207                .as_str()
6208                .unwrap()
6209                .contains("use sessions.discover"),
6210            "{refused}"
6211        );
6212        std::fs::remove_dir_all(root).unwrap();
6213    }
6214
6215    #[test]
6216    fn session_index_resize_preserves_subscription_and_rejects_invalid_requests() {
6217        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.sessions.index.resize"));
6218        let root = std::env::temp_dir().join(format!(
6219            "supercode-index-rpc-{}-{}",
6220            std::process::id(),
6221            std::time::SystemTime::now()
6222                .duration_since(std::time::UNIX_EPOCH)
6223                .unwrap()
6224                .as_nanos()
6225        ));
6226        std::fs::create_dir_all(&root).unwrap();
6227        for id in ["first", "second"] {
6228            std::fs::write(root.join(format!("{id}.jsonl")), format!(
6229                "{{\"type\":\"session_meta\",\"payload\":{{\"id\":\"{id}\",\"cwd\":\"/workspace\"}}}}\n"
6230            )).unwrap();
6231        }
6232        let mut service = HarnessSessionService::new();
6233        let opened = service.handle(request(
6234            1,
6235            "harness.v1.sessions.index.subscribe",
6236            json!({
6237                "harnesses": ["codex"], "homes": { "codex": root }, "limit": 1
6238            }),
6239        ));
6240        assert!(opened.get("error").is_none(), "{opened:#}");
6241        let subscription = opened["result"]["subscription"]
6242            .as_str()
6243            .unwrap()
6244            .to_owned();
6245        assert_eq!(opened["result"]["initial"].as_array().unwrap().len(), 1);
6246        for params in [
6247            json!({"subscription": subscription, "limit": 0}),
6248            json!({"subscription": subscription, "limit": 2049}),
6249            json!({"subscription": subscription, "limit": 2, "cursor": "not-allowed"}),
6250            json!({"subscription": "unknown", "limit": 2}),
6251        ] {
6252            let rejected = service.handle(request(2, "harness.v1.sessions.index.resize", params));
6253            assert_eq!(rejected["error"]["code"], -32602, "{rejected:#}");
6254        }
6255        for (limit, revision) in [(1, 1), (2, 2), (2, 2), (1, 3)] {
6256            let response = service.handle(request(
6257                3,
6258                "harness.v1.sessions.index.resize",
6259                json!({
6260                    "subscription": subscription, "limit": limit
6261                }),
6262            ));
6263            assert!(response.get("error").is_none(), "{response:#}");
6264            assert_eq!(response["result"]["subscription"], subscription);
6265            assert_eq!(response["result"]["revision"], revision);
6266            assert_eq!(
6267                response["result"]["initial"].as_array().unwrap().len(),
6268                limit
6269            );
6270            assert_eq!(response["result"]["receipt"]["total_matched"], 2);
6271            assert_eq!(service.index_subscriptions.len(), 1);
6272        }
6273        let removed = service.handle(request(
6274            4,
6275            "harness.v1.sessions.index.unsubscribe",
6276            json!({
6277                "subscription": subscription
6278            }),
6279        ));
6280        assert_eq!(removed["result"]["removed"], true);
6281        let stale = service.handle(request(
6282            5,
6283            "harness.v1.sessions.index.resize",
6284            json!({
6285                "subscription": subscription, "limit": 1
6286            }),
6287        ));
6288        assert_eq!(stale["error"]["code"], -32602);
6289        drop(service);
6290        std::fs::remove_dir_all(root).unwrap();
6291    }
6292
6293    fn skills_rows(params: Value) -> Vec<Value> {
6294        let response =
6295            HarnessSessionService::new().handle(request(1, "harness.v1.skills.list", params));
6296        assert!(response.get("error").is_none(), "{response:#}");
6297        response["result"].as_array().cloned().unwrap_or_default()
6298    }
6299
6300    /// The uniform row over two harnesses at once, from the harnesses' own
6301    /// skill roots: name, harness, scope, location, description, version.
6302    #[test]
6303    fn skills_list_reads_the_hermes_and_openclaw_roots() {
6304        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6305        let rows = skills_rows(json!({
6306            "homes": fixture_homes(),
6307            "cwd": fixtures.join("hermes_home"),
6308        }));
6309        let arxiv = rows
6310            .iter()
6311            .find(|row| row["name"] == json!("arxiv-search"))
6312            .unwrap_or_else(|| panic!("no arxiv row in {rows:#?}"));
6313        assert_eq!(arxiv["harness"], json!(HarnessId::HERMES));
6314        assert_eq!(arxiv["scope"], json!("user"));
6315        assert_eq!(arxiv["version"], json!("1.4.0"));
6316        assert!(arxiv["location"]
6317            .as_str()
6318            .unwrap()
6319            .ends_with("hermes_home/skills/research/arxiv"));
6320
6321        // A directory with no SKILL.md still lists, by directory name.
6322        let bare = rows
6323            .iter()
6324            .find(|row| row["name"] == json!("bare-skill"))
6325            .unwrap_or_else(|| panic!("no bare-skill row in {rows:#?}"));
6326        assert_eq!(bare["enabled"], json!(null));
6327        assert!(bare.get("description").is_none());
6328
6329        let demo = rows
6330            .iter()
6331            .find(|row| row["name"] == json!("clawhub-demo"))
6332            .unwrap_or_else(|| panic!("no clawhub-demo row in {rows:#?}"));
6333        assert_eq!(demo["harness"], json!(HarnessId::OPENCLAW));
6334        assert_eq!(demo["scope"], json!("managed"));
6335        assert_eq!(demo["enabled"], json!(false));
6336    }
6337
6338    /// Both filters select against the same rows.
6339    #[test]
6340    fn skills_list_filters_by_harness_and_scope() {
6341        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6342        let hermes = skills_rows(json!({
6343            "homes": fixture_homes(),
6344            "cwd": fixtures.join("hermes_home"),
6345            "harness": HarnessId::HERMES,
6346        }));
6347        assert!(!hermes.is_empty());
6348        assert!(hermes
6349            .iter()
6350            .all(|row| row["harness"] == json!(HarnessId::HERMES)));
6351
6352        let managed = skills_rows(json!({
6353            "homes": fixture_homes(),
6354            "cwd": fixtures.join("openclaw_home"),
6355            "harness": HarnessId::OPENCLAW,
6356            "scope": "managed",
6357        }));
6358        assert_eq!(managed.len(), 1, "{managed:#?}");
6359        assert_eq!(managed[0]["name"], json!("clawhub-demo"));
6360
6361        let bundled = skills_rows(json!({
6362            "homes": fixture_homes(),
6363            "cwd": fixtures.join("openclaw_home"),
6364            "harness": HarnessId::OPENCLAW,
6365            "scope": "bundled",
6366        }));
6367        assert!(bundled.is_empty(), "{bundled:#?}");
6368    }
6369
6370    /// A harness supercode has no skills root for is refused by name, not
6371    /// answered with an empty list.
6372    #[test]
6373    fn skills_list_refuses_an_unknown_harness() {
6374        let response = HarnessSessionService::new().handle(request(
6375            1,
6376            "harness.v1.skills.list",
6377            json!({"harness": "not-a-harness", "homes": fixture_homes()}),
6378        ));
6379        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6380        assert!(response["error"]["message"]
6381            .as_str()
6382            .unwrap()
6383            .contains("not-a-harness"));
6384    }
6385
6386    /// The method is advertised, and its SDK operation resolves it.
6387    #[test]
6388    fn skills_list_is_an_advertised_method_and_sdk_operation() {
6389        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.list"));
6390        assert_eq!(
6391            SdkOperation::from_method("harness.v1.skills.list"),
6392            Some(SdkOperation::SkillsList)
6393        );
6394    }
6395
6396    // ---- ORCH-22: `harness.v1.skills.install|remove` ----------------------
6397
6398    /// Both controlled verbs are advertised and resolve to their operation.
6399    #[test]
6400    fn skills_install_and_remove_are_advertised_methods_and_sdk_operations() {
6401        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.install"));
6402        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.remove"));
6403        assert_eq!(
6404            SdkOperation::from_method("harness.v1.skills.install"),
6405            Some(SdkOperation::SkillsInstall)
6406        );
6407        assert_eq!(
6408            SdkOperation::from_method("harness.v1.skills.remove"),
6409            Some(SdkOperation::SkillsRemove)
6410        );
6411    }
6412
6413    /// The directory door, end to end over the RPC: a local package lands in
6414    /// Claude Code's own user root and the outcome carries the operation and
6415    /// the row the ORCH-11 loader reads back.
6416    #[test]
6417    fn skills_install_and_remove_drive_the_directory_door() {
6418        let root = std::env::temp_dir().join(format!(
6419            "supercode-orch22-rpc-{}-{}",
6420            std::process::id(),
6421            std::time::SystemTime::now()
6422                .duration_since(std::time::UNIX_EPOCH)
6423                .unwrap()
6424                .as_nanos()
6425        ));
6426        let source = root.join("probe-src");
6427        std::fs::create_dir_all(&source).unwrap();
6428        std::fs::write(
6429            source.join("SKILL.md"),
6430            "---\nname: orch22-rpc\ndescription: a probe\n---\nbody\n",
6431        )
6432        .unwrap();
6433        let homes = json!({
6434            "claude_code": root.join("claude_home"),
6435            "codex": root.join("__absent__"),
6436            "opencode": root.join("__absent__"),
6437            "pi": root.join("__absent__"),
6438            "hermes": root.join("__absent__"),
6439            "openclaw": root.join("__absent__"),
6440            "agents": root.join("__absent__"),
6441        });
6442
6443        let mut service = HarnessSessionService::new();
6444        let installed = service.handle(request(
6445            1,
6446            "harness.v1.skills.install",
6447            json!({
6448                "harness": HarnessId::CLAUDE_CODE,
6449                "source": source,
6450                "scope": "user",
6451                "cwd": root,
6452                "homes": homes,
6453            }),
6454        ));
6455        let result = &installed["result"];
6456        assert_eq!(result["name"], json!("orch22-rpc"), "{installed:#}");
6457        assert_eq!(result["verb"], json!("install"));
6458        assert!(result["ran"]
6459            .as_str()
6460            .is_some_and(|ran| ran.starts_with("cp -R ")));
6461        assert_eq!(result["skill"]["scope"], json!("user"));
6462
6463        let removed = service.handle(request(
6464            2,
6465            "harness.v1.skills.remove",
6466            json!({
6467                "harness": HarnessId::CLAUDE_CODE,
6468                "name": "orch22-rpc",
6469                "scope": "user",
6470                "cwd": root,
6471                "homes": homes,
6472            }),
6473        ));
6474        assert_eq!(removed["result"]["removed"], json!(true), "{removed:#}");
6475        assert!(!root.join("claude_home/skills/orch22-rpc").exists());
6476        std::fs::remove_dir_all(&root).ok();
6477    }
6478
6479    /// OpenClaw publishes no `skills remove` at the pin, so the uniform verb
6480    /// refuses with UnsupportedAction instead of deleting files itself.
6481    #[test]
6482    fn skills_remove_refuses_openclaw_at_the_pin() {
6483        let response = HarnessSessionService::new().handle(request(
6484            1,
6485            "harness.v1.skills.remove",
6486            json!({"harness": HarnessId::OPENCLAW, "name": "clawhub-demo"}),
6487        ));
6488        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6489        assert!(response["error"]["message"]
6490            .as_str()
6491            .unwrap()
6492            .contains("no `skills remove` verb"));
6493    }
6494
6495    /// A harness with no skills root at all is refused by name, with the
6496    /// same sentence `skills.list` gives it.
6497    #[test]
6498    fn skills_install_refuses_a_harness_without_a_skills_root() {
6499        let response = HarnessSessionService::new().handle(request(
6500            1,
6501            "harness.v1.skills.install",
6502            json!({"harness": "not-a-harness", "source": "/tmp/x"}),
6503        ));
6504        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6505        assert!(response["error"]["message"]
6506            .as_str()
6507            .unwrap()
6508            .contains("not-a-harness"));
6509    }
6510
6511    // ---- ORCH-12: `harness.v1.memory.show|search` ------------------------
6512
6513    /// `HarnessHomes` for the committed fixture homes. Every root a test does
6514    /// not name is pinned at an absent path, so a read can never fall through
6515    /// to this machine's real harness homes. Note `hermes` is the `state.db`
6516    /// PATH (its parent is HERMES_HOME) and `claude_code` is the `projects`
6517    /// directory — the same contract discovery uses.
6518    fn memory_homes() -> Value {
6519        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6520        json!({
6521            "claude_code": fixtures.join("__absent__"),
6522            "codex": fixtures.join("__absent__"),
6523            "opencode": fixtures.join("__absent__"),
6524            "pi": fixtures.join("__absent__"),
6525            "grok": fixtures.join("__absent__"),
6526            "gemini": fixtures.join("__absent__"),
6527            "goose": fixtures.join("__absent__"),
6528            "supercode": fixtures.join("__absent__"),
6529            "hermes": fixtures.join("hermes_home/state.db"),
6530            "openclaw": fixtures.join("openclaw_home"),
6531        })
6532    }
6533
6534    fn memory_call_ok(method: &str, params: Value, key: &str) -> Vec<Value> {
6535        let response = HarnessSessionService::new().handle(request(1, method, params));
6536        assert!(response.get("error").is_none(), "{response:#}");
6537        assert_eq!(response["result"]["schema"], json!("supercode.memory.v1"));
6538        response["result"][key]
6539            .as_array()
6540            .cloned()
6541            .unwrap_or_default()
6542    }
6543
6544    fn memory_documents(params: Value) -> Vec<Value> {
6545        memory_call_ok("harness.v1.memory.show", params, "documents")
6546    }
6547
6548    fn memory_matches(params: Value) -> Vec<Value> {
6549        memory_call_ok("harness.v1.memory.search", params, "matches")
6550    }
6551
6552    fn find_document<'a>(rows: &'a [Value], profile: &str, name: &str) -> &'a Value {
6553        rows.iter()
6554            .find(|row| row["profile"] == profile && row["name"] == name)
6555            .unwrap_or_else(|| panic!("no `{profile}` document `{name}` in {rows:#?}"))
6556    }
6557
6558    /// Hermes: the built-in `MEMORY.md`/`USER.md` pair and the `memories/`
6559    /// topic files, for HERMES_HOME itself and for every profile home.
6560    #[test]
6561    fn memory_show_reads_the_hermes_profile_homes() {
6562        let rows = memory_documents(json!({"harness": "hermes", "homes": memory_homes()}));
6563
6564        let notes = find_document(&rows, "default", "MEMORY.md");
6565        assert_eq!(notes["harness"], "hermes");
6566        assert_eq!(notes["scope"], "user");
6567        assert!(notes["size"].as_u64().unwrap() > 0);
6568        assert!(notes["updated_at"].is_string(), "{notes:#?}");
6569        // The default answer previews the head and never the whole body.
6570        assert!(notes.get("content").is_none(), "{notes:#?}");
6571        assert_eq!(notes["truncated"], true);
6572        assert_eq!(notes["preview"].as_array().unwrap().len(), 5);
6573
6574        let user = find_document(&rows, "default", "USER.md");
6575        assert_eq!(user["scope"], "user");
6576        assert!(user["preview"]
6577            .as_array()
6578            .unwrap()
6579            .iter()
6580            .any(|line| line.as_str().unwrap().contains("neovim")));
6581
6582        let topic = find_document(&rows, "default", "memories/2026-09-01-notes.md");
6583        assert!(topic["path"]
6584            .as_str()
6585            .unwrap()
6586            .ends_with("hermes_home/memories/2026-09-01-notes.md"));
6587
6588        // Profile mode points HERMES_HOME at `<root>/profiles/<name>`.
6589        let coder = find_document(&rows, "coder", "MEMORY.md");
6590        assert_eq!(coder["scope"], "profile");
6591        assert!(coder["path"]
6592            .as_str()
6593            .unwrap()
6594            .ends_with("hermes_home/profiles/coder/MEMORY.md"));
6595    }
6596
6597    /// `full` is the only way a body crosses the wire, and `profile` narrows
6598    /// the read to one home.
6599    #[test]
6600    fn memory_show_returns_bodies_only_under_full_and_narrows_by_profile() {
6601        let rows = memory_documents(json!({
6602            "harness": "hermes",
6603            "profile": "coder",
6604            "full": true,
6605            "homes": memory_homes(),
6606        }));
6607        assert!(
6608            rows.iter().all(|row| row["profile"] == "coder"),
6609            "{rows:#?}"
6610        );
6611        let coder = find_document(&rows, "coder", "MEMORY.md");
6612        assert!(coder["content"]
6613            .as_str()
6614            .expect("full returns the body")
6615            .contains("anthropic/claude-opus-4-8"));
6616    }
6617
6618    /// OpenClaw: memory-core's files under each agent's workspace —
6619    /// `<state>/workspace` for the default agent, `<state>/workspace-<id>`
6620    /// for any other.
6621    #[test]
6622    fn memory_show_reads_the_openclaw_agent_workspaces() {
6623        let rows = memory_documents(json!({"harness": "openclaw", "homes": memory_homes()}));
6624
6625        let main = find_document(&rows, "main", "MEMORY.md");
6626        assert_eq!(main["scope"], "agent");
6627        assert!(main["path"]
6628            .as_str()
6629            .unwrap()
6630            .ends_with("openclaw_home/workspace/MEMORY.md"));
6631
6632        let topic = find_document(&rows, "main", "memory/2026-09-01-standup.md");
6633        assert!(topic["path"]
6634            .as_str()
6635            .unwrap()
6636            .ends_with("openclaw_home/workspace/memory/2026-09-01-standup.md"));
6637
6638        let design = find_document(&rows, "design", "MEMORY.md");
6639        assert!(design["path"]
6640            .as_str()
6641            .unwrap()
6642            .ends_with("openclaw_home/workspace-design/MEMORY.md"));
6643    }
6644
6645    /// Claude Code: the auto-memory directory of the project the working tree
6646    /// belongs to, keyed by the enclosing git repository.
6647    #[test]
6648    fn memory_show_reads_a_claude_code_project_auto_memory_directory() {
6649        let scratch = std::env::temp_dir().join(format!(
6650            "supercode-orch12-cc-{}-{}",
6651            std::process::id(),
6652            std::time::SystemTime::now()
6653                .duration_since(std::time::UNIX_EPOCH)
6654                .unwrap()
6655                .as_nanos()
6656        ));
6657        let project = scratch.join("repo");
6658        std::fs::create_dir_all(project.join(".git")).unwrap();
6659        // Auto-memory is shared across a repo's worktrees, so a nested
6660        // working directory must resolve to the repo's own project dir.
6661        let worktree = project.join("crates/harness");
6662        std::fs::create_dir_all(&worktree).unwrap();
6663        let slug: String = project
6664            .to_string_lossy()
6665            .chars()
6666            .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
6667            .collect();
6668        let projects = scratch.join("claude/projects");
6669        let memory = projects.join(&slug).join("memory");
6670        std::fs::create_dir_all(&memory).unwrap();
6671        std::fs::write(
6672            memory.join("MEMORY.md"),
6673            "# index\n- [build box](build-box.md) — the pinned harnesses\n",
6674        )
6675        .unwrap();
6676        std::fs::write(
6677            memory.join("build-box.md"),
6678            "hermes 0.21.0 and openclaw 2026.7.1-2 are the pins\n",
6679        )
6680        .unwrap();
6681
6682        let mut homes = memory_homes();
6683        homes["claude_code"] = json!(projects);
6684        let rows = memory_documents(json!({
6685            "harness": "claude-code",
6686            "cwd": worktree,
6687            "homes": homes,
6688        }));
6689        let index = find_document(&rows, &slug, "MEMORY.md");
6690        assert_eq!(index["harness"], "claude-code");
6691        assert_eq!(index["scope"], "project");
6692        let topic = find_document(&rows, &slug, "build-box.md");
6693        assert!(topic["preview"]
6694            .as_array()
6695            .unwrap()
6696            .iter()
6697            .any(|line| line.as_str().unwrap().contains("2026.7.1-2")));
6698
6699        let hits = memory_matches(json!({
6700            "harness": "claude-code",
6701            "query": "pinned harnesses",
6702            "cwd": worktree,
6703            "homes": homes,
6704        }));
6705        assert_eq!(hits.len(), 1, "{hits:#?}");
6706        assert_eq!(hits[0]["name"], "MEMORY.md");
6707        assert_eq!(hits[0]["line"], 2);
6708
6709        let _ = std::fs::remove_dir_all(&scratch);
6710    }
6711
6712    /// A config-less OpenClaw install declares no default agent, but
6713    /// memory-core still resolves ONE agent to the default `workspace`
6714    /// directory — the same `main`-then-first convention the profile rows
6715    /// use. Measured against `openclaw memory status` on the pinned CLI
6716    /// (`docs/interop/research/orch12-memory-receipt-2026-09-03.json`).
6717    #[test]
6718    fn memory_show_resolves_the_default_workspace_without_an_openclaw_config() {
6719        let state = std::env::temp_dir().join(format!(
6720            "supercode-orch12-oc-{}-{}",
6721            std::process::id(),
6722            std::time::SystemTime::now()
6723                .duration_since(std::time::UNIX_EPOCH)
6724                .unwrap()
6725                .as_nanos()
6726        ));
6727        // No `openclaw.json`: only the agent home the gateway creates.
6728        std::fs::create_dir_all(state.join("agents/main/agent")).unwrap();
6729        std::fs::create_dir_all(state.join("workspace")).unwrap();
6730        std::fs::write(
6731            state.join("workspace/MEMORY.md"),
6732            "the gateway websocket needs credentials\n",
6733        )
6734        .unwrap();
6735
6736        let mut homes = memory_homes();
6737        homes["openclaw"] = json!(state);
6738        let rows = memory_documents(json!({"harness": "openclaw", "homes": homes}));
6739        assert_eq!(rows.len(), 1, "{rows:#?}");
6740        let row = find_document(&rows, "main", "MEMORY.md");
6741        assert_eq!(row["scope"], "agent");
6742        assert!(row["path"]
6743            .as_str()
6744            .unwrap()
6745            .ends_with("workspace/MEMORY.md"));
6746
6747        let _ = std::fs::remove_dir_all(&state);
6748    }
6749
6750    /// Search is a plain scan over the same documents: a hit carries the
6751    /// path, line and excerpt; a miss is an empty list, not an error.
6752    #[test]
6753    fn memory_search_reports_hits_by_line_and_misses_as_empty() {
6754        let hit = memory_matches(json!({
6755            "harness": "hermes",
6756            "query": "NEOVIM",
6757            "homes": memory_homes(),
6758        }));
6759        assert_eq!(hit.len(), 1, "{hit:#?}");
6760        assert_eq!(hit[0]["harness"], "hermes");
6761        assert_eq!(hit[0]["name"], "USER.md");
6762        assert_eq!(hit[0]["scope"], "user");
6763        assert_eq!(hit[0]["line"], 5);
6764        assert!(hit[0]["excerpt"].as_str().unwrap().contains("neovim"));
6765
6766        // A regular expression reaches the same lines.
6767        let regex = memory_matches(json!({
6768            "harness": "hermes",
6769            "query": "neo(vim|vi)",
6770            "regex": true,
6771            "homes": memory_homes(),
6772        }));
6773        assert_eq!(regex.len(), 1, "{regex:#?}");
6774
6775        let miss = memory_matches(json!({
6776            "harness": "hermes",
6777            "query": "no-memory-line-says-this",
6778            "homes": memory_homes(),
6779        }));
6780        assert!(miss.is_empty(), "{miss:#?}");
6781    }
6782
6783    /// The uniform-verb contract: a harness with no memory store at the pin
6784    /// is refused by name, and `session` only selects a Claude Code project.
6785    #[test]
6786    fn memory_refuses_harnesses_without_a_store_and_misplaced_session_scoping() {
6787        for method in ["harness.v1.memory.show", "harness.v1.memory.search"] {
6788            let response = HarnessSessionService::new().handle(request(
6789                1,
6790                method,
6791                json!({"harness": "codex", "query": "anything", "homes": memory_homes()}),
6792            ));
6793            assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6794            assert!(response["error"]["message"]
6795                .as_str()
6796                .unwrap()
6797                .contains("codex"));
6798        }
6799
6800        let response = HarnessSessionService::new().handle(request(
6801            1,
6802            "harness.v1.memory.show",
6803            json!({"harness": "hermes", "session": "abc", "homes": memory_homes()}),
6804        ));
6805        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
6806
6807        // `harness` is not optional: memory documents are the user's prose.
6808        let response = HarnessSessionService::new().handle(request(
6809            1,
6810            "harness.v1.memory.show",
6811            json!({"homes": memory_homes()}),
6812        ));
6813        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
6814    }
6815
6816    /// Both methods are advertised, and their SDK operations resolve them.
6817    #[test]
6818    fn memory_methods_are_advertised_and_map_to_sdk_operations() {
6819        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.show"));
6820        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.search"));
6821        assert_eq!(
6822            SdkOperation::from_method("harness.v1.memory.show"),
6823            Some(SdkOperation::MemoryShow)
6824        );
6825        assert_eq!(
6826            SdkOperation::from_method("harness.v1.memory.search"),
6827            Some(SdkOperation::MemorySearch)
6828        );
6829    }
6830
6831    // ---- ORCH-9: `harness.v1.approvals.list` -----------------------------
6832
6833    /// A runtime that raises one protocol request and then goes quiet, so a
6834    /// single poll delivers the request without closing the connection.
6835    struct RequestingRuntime {
6836        handle: RuntimeHandle,
6837        events: std::collections::VecDeque<HarnessEvent>,
6838        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
6839    }
6840
6841    #[async_trait]
6842    impl RuntimeConnection for RequestingRuntime {
6843        fn handle(&self) -> &RuntimeHandle {
6844            &self.handle
6845        }
6846
6847        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
6848            unreachable!("this runtime only raises requests")
6849        }
6850
6851        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
6852            match self.events.pop_front() {
6853                Some(event) => Ok(Some(event)),
6854                // Quiet, not closed: `poll_sdk_events` times out and leaves
6855                // the connection open, the way a runtime blocked on a
6856                // permission request behaves.
6857                None => std::future::pending().await,
6858            }
6859        }
6860
6861        async fn interrupt(&mut self) -> crate::Result<()> {
6862            Ok(())
6863        }
6864
6865        async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
6866            // Both halves are recorded: ORCH-20 has to prove not just that the
6867            // right request was answered but that the door received its own
6868            // reply envelope.
6869            self.answered
6870                .lock()
6871                .unwrap_or_else(std::sync::PoisonError::into_inner)
6872                .push(json!({"request_id": request_id, "response": response}));
6873            Ok(())
6874        }
6875
6876        async fn close(&mut self) -> crate::Result<()> {
6877            Ok(())
6878        }
6879    }
6880
6881    fn requesting_runtime(
6882        harness: &str,
6883        events: Vec<HarnessEvent>,
6884        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
6885    ) -> Box<dyn RuntimeConnection> {
6886        requesting_runtime_named(harness, "hermes-live-session", events, answered)
6887    }
6888
6889    fn requesting_runtime_named(
6890        harness: &str,
6891        runtime_id: &str,
6892        events: Vec<HarnessEvent>,
6893        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
6894    ) -> Box<dyn RuntimeConnection> {
6895        Box::new(RequestingRuntime {
6896            handle: RuntimeHandle {
6897                harness: HarnessId::from(harness),
6898                runtime_id: runtime_id.into(),
6899                endpoint: RuntimeEndpoint::LocalProcess {
6900                    pid: None,
6901                    command: vec!["hermes-acp".into()],
6902                    protocol: "acp".into(),
6903                },
6904            },
6905            events: events.into(),
6906            answered,
6907        })
6908    }
6909
6910    fn permission_event(id: u64, title: &str) -> HarnessEvent {
6911        HarnessEvent {
6912            sequence: None,
6913            kind: "session/request_permission".into(),
6914            payload: json!({
6915                "jsonrpc": "2.0",
6916                "id": id,
6917                "method": "session/request_permission",
6918                "params": {
6919                    "sessionId": "hermes-live-session",
6920                    "toolCall": {"toolCallId": "call-1", "title": title, "kind": "execute"},
6921                    "options": [
6922                        {"optionId": "allow_once", "name": "Allow once", "kind": "allow_once"},
6923                        {"optionId": "allow_for_session", "name": "Allow for session", "kind": "allow_always"},
6924                        {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
6925                    ],
6926                },
6927            }),
6928        }
6929    }
6930
6931    fn approvals(service: &mut HarnessSessionService, params: Value) -> Value {
6932        let response = service.handle(request(1, "harness.v1.approvals.list", params));
6933        assert!(response.get("error").is_none(), "{response:#}");
6934        response["result"].clone()
6935    }
6936
6937    /// ORC-2 dev/01: the same uniform loop over the CLAUDE CODE door. The
6938    /// `can_use_tool` control request the CLI raises to its registered
6939    /// permission handler lists as one pending row, `approvals.resolve <id>
6940    /// allow_once` sends the `{behavior}` result the CLI accepts through
6941    /// `runtimes.respond`, and the row is gone. The frame is the one claude
6942    /// 2.1.258 wrote, transcribed from
6943    /// `docs/interop/research/orc2-claude-respond-receipt-2026-09-04.json`.
6944    #[tokio::test]
6945    async fn a_claude_code_permission_request_lists_and_resolves_on_the_uniform_door() {
6946        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
6947        let mut service = HarnessSessionService::new();
6948        service.runtimes.insert(
6949            "runtime-cc".into(),
6950            requesting_runtime_named(
6951                HarnessId::CLAUDE_CODE,
6952                "claude-live-session",
6953                vec![HarnessEvent {
6954                    sequence: None,
6955                    kind: "control_request".into(),
6956                    payload: json!({
6957                        "type": "control_request",
6958                        "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
6959                        "request": {
6960                            "subtype": "can_use_tool",
6961                            "tool_name": "Bash",
6962                            "display_name": "Bash",
6963                            "input": {"command": "touch probe-artifact.txt"},
6964                            "tool_use_id": "toolu_mock_1",
6965                        },
6966                    }),
6967                }],
6968                answered.clone(),
6969            ),
6970        );
6971
6972        let notifications = service.poll_runtimes().await;
6973        assert_eq!(notifications.len(), 1, "{notifications:#?}");
6974
6975        let rows = approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}));
6976        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
6977        let row = &rows[0];
6978        assert_eq!(row["id"], "runtime-cc/053f8a2d-3445-4011-a259-4261b31c7326");
6979        assert_eq!(row["harness"], HarnessId::CLAUDE_CODE);
6980        assert_eq!(row["status"], "pending");
6981        assert_eq!(row["subject"], "Bash touch probe-artifact.txt");
6982        assert_eq!(row["runtime_id"], "claude-live-session");
6983        assert_eq!(
6984            row["options"]
6985                .as_array()
6986                .unwrap()
6987                .iter()
6988                .map(|option| option["id"].as_str().unwrap())
6989                .collect::<Vec<_>>(),
6990            vec!["allow", "deny"],
6991        );
6992
6993        let response = resolve(
6994            &mut service,
6995            json!({"id": row["id"], "decision": "allow_once"}),
6996        )
6997        .await;
6998        assert!(response.get("error").is_none(), "{response:#}");
6999        assert_eq!(response["result"]["option_id"], "allow");
7000        assert_eq!(
7001            answered
7002                .lock()
7003                .unwrap_or_else(std::sync::PoisonError::into_inner)
7004                .as_slice(),
7005            &[json!({
7006                "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
7007                "response": {"behavior": "allow"},
7008            })],
7009        );
7010        assert_eq!(
7011            approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}))
7012                .as_array()
7013                .map(Vec::len),
7014            Some(0),
7015        );
7016    }
7017
7018    /// dev/01: a live ACP permission request raised on a driven runtime is
7019    /// listable while the turn is blocked on it, and stops being listable
7020    /// the moment `runtimes.respond` answers it.
7021    #[tokio::test]
7022    async fn a_live_permission_request_lists_until_it_is_answered() {
7023        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7024        let mut service = HarnessSessionService::new();
7025        service.runtimes.insert(
7026            "runtime-1".into(),
7027            requesting_runtime(
7028                HarnessId::HERMES,
7029                vec![permission_event(7, "rm -rf build")],
7030                answered.clone(),
7031            ),
7032        );
7033
7034        let notifications = service.poll_runtimes().await;
7035        assert_eq!(notifications.len(), 1, "{notifications:#?}");
7036
7037        let rows = approvals(&mut service, json!({}));
7038        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
7039        let row = &rows[0];
7040        assert_eq!(row["id"], "runtime-1/7");
7041        assert_eq!(row["harness"], HarnessId::HERMES);
7042        assert_eq!(row["kind"], "live");
7043        assert_eq!(row["status"], "pending");
7044        assert_eq!(row["subject"], "rm -rf build");
7045        assert_eq!(row["session_id"], "hermes-live-session");
7046        assert_eq!(row["runtime_id"], "hermes-live-session");
7047        assert!(row["requested_at_ms"].as_i64().is_some(), "{row:#}");
7048        assert!(
7049            row["age_ms"].as_i64().is_some_and(|age| age >= 0),
7050            "{row:#}"
7051        );
7052        assert_eq!(
7053            row["options"]
7054                .as_array()
7055                .unwrap()
7056                .iter()
7057                .map(|option| option["id"].as_str().unwrap())
7058                .collect::<Vec<_>>(),
7059            vec!["allow_once", "allow_for_session", "deny"],
7060        );
7061
7062        // The filters select against the same rows.
7063        assert_eq!(
7064            approvals(&mut service, json!({"harness": HarnessId::HERMES}))
7065                .as_array()
7066                .map(Vec::len),
7067            Some(1),
7068        );
7069        assert_eq!(
7070            approvals(&mut service, json!({"session": "some-other-session"}))
7071                .as_array()
7072                .map(Vec::len),
7073            Some(0),
7074        );
7075
7076        let response = service
7077            .handle_async(request(
7078                2,
7079                "harness.v1.runtimes.respond",
7080                json!({
7081                    "connection": "runtime-1",
7082                    "request_id": 7,
7083                    "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7084                }),
7085            ))
7086            .await;
7087        assert!(response.get("error").is_none(), "{response:#}");
7088        assert_eq!(
7089            answered
7090                .lock()
7091                .unwrap_or_else(std::sync::PoisonError::into_inner)
7092                .as_slice(),
7093            &[json!({
7094                "request_id": 7,
7095                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7096            })],
7097        );
7098
7099        let rows = approvals(&mut service, json!({}));
7100        assert_eq!(rows.as_array().map(Vec::len), Some(0), "{rows:#}");
7101    }
7102
7103    /// dev/01: supercode's own queued subagent approvals list through the
7104    /// same door, carrying the outcome the record holds.
7105    #[test]
7106    fn queued_subagent_approvals_list_through_the_same_door() {
7107        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
7108            crate::subagents::QueuedApproval {
7109                child_agent_id: "child-7".into(),
7110                tool: "shell".into(),
7111                subject: Some("cargo publish --dry-run".into()),
7112                queued_at_ms: 1,
7113                outcome: None,
7114            },
7115            crate::subagents::QueuedApproval {
7116                child_agent_id: "child-8".into(),
7117                tool: "write_file".into(),
7118                subject: None,
7119                queued_at_ms: 2,
7120                outcome: Some(crate::subagents::QueuedApprovalOutcome::Denied),
7121            },
7122        ]));
7123        let mut service = HarnessSessionService::new();
7124        service.observe_subagent_approvals(queue);
7125
7126        let rows = approvals(&mut service, json!({}));
7127        assert_eq!(rows.as_array().map(Vec::len), Some(2), "{rows:#}");
7128        assert_eq!(rows[0]["id"], "supercode/subagent/child-7/1/0");
7129        assert_eq!(rows[0]["harness"], HarnessId::SUPERCODE);
7130        assert_eq!(rows[0]["status"], "pending");
7131        assert_eq!(rows[0]["subject"], "shell cargo publish --dry-run");
7132        assert_eq!(rows[1]["status"], "denied");
7133        assert!(rows[1]["options"].as_array().unwrap().is_empty());
7134
7135        // `--session` addresses a subagent row by its child agent id.
7136        let only = approvals(&mut service, json!({"session": "child-8"}));
7137        assert_eq!(only.as_array().map(Vec::len), Some(1), "{only:#}");
7138        assert_eq!(only[0]["id"], "supercode/subagent/child-8/2/1");
7139    }
7140
7141    /// The uniform-verb contract: an id whose runtime door cannot carry a
7142    /// protocol request is refused BY NAME rather than answered with an empty
7143    /// list. Since ORC-2 gave Claude Code a permission-response primitive
7144    /// every registered harness can carry one, so the refusal is exercised on
7145    /// an unknown id — and the registered ids are asserted to be accepted.
7146    #[test]
7147    fn approvals_list_refuses_a_harness_that_cannot_carry_a_request() {
7148        let response = HarnessSessionService::new().handle(request(
7149            1,
7150            "harness.v1.approvals.list",
7151            json!({"harness": "not-a-harness"}),
7152        ));
7153        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7154        assert!(response["error"]["message"]
7155            .as_str()
7156            .unwrap()
7157            .contains("not-a-harness"));
7158        for harness in [HarnessId::CLAUDE_CODE, HarnessId::CODEX] {
7159            let response = HarnessSessionService::new().handle(request(
7160                1,
7161                "harness.v1.approvals.list",
7162                json!({"harness": harness}),
7163            ));
7164            assert!(response.get("error").is_none(), "{harness}: {response:#}");
7165        }
7166    }
7167
7168    /// The method is advertised, its SDK operation resolves it, and the
7169    /// registry reports the concept as observed for every harness whose
7170    /// runtime door can carry a request.
7171    #[test]
7172    fn approvals_list_is_an_advertised_method_and_an_observed_tier() {
7173        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.list"));
7174        assert_eq!(
7175            SdkOperation::from_method("harness.v1.approvals.list"),
7176            Some(SdkOperation::ApprovalsList)
7177        );
7178        let registry = harness_support_registry();
7179        for id in [
7180            HarnessId::HERMES,
7181            HarnessId::OPENCLAW,
7182            HarnessId::CODEX,
7183            // ORC-2: the Claude Code door answers `can_use_tool`, so its
7184            // pending_request concept joins the other driven doors.
7185            HarnessId::CLAUDE_CODE,
7186        ] {
7187            let concept = registry
7188                .harnesses
7189                .iter()
7190                .find(|harness| harness.id.as_str() == id)
7191                .unwrap()
7192                .orchestration
7193                .concepts
7194                .iter()
7195                .find(|concept| concept.concept == "pending_request")
7196                .unwrap();
7197            assert_eq!(concept.observed, crate::ImplementationKind::BuiltIn, "{id}");
7198            assert!(concept
7199                .methods
7200                .iter()
7201                .any(|method| method == "harness.v1.approvals.list"));
7202        }
7203    }
7204
7205    // ---- ORCH-20: `harness.v1.approvals.resolve` -------------------------
7206
7207    async fn resolve(service: &mut HarnessSessionService, params: Value) -> Value {
7208        service
7209            .handle_async(request(3, "harness.v1.approvals.resolve", params))
7210            .await
7211    }
7212
7213    /// dev/01: the whole loop on a driven runtime — list one pending row,
7214    /// answer it by ROW ID with one uniform decision, and see it gone. The
7215    /// door receives its own ACP envelope carrying the option it enumerated.
7216    #[tokio::test]
7217    async fn a_listed_row_resolves_with_one_uniform_decision_and_then_is_gone() {
7218        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7219        let mut service = HarnessSessionService::new();
7220        service.runtimes.insert(
7221            "runtime-1".into(),
7222            requesting_runtime(
7223                HarnessId::HERMES,
7224                vec![permission_event(7, "rm -rf build")],
7225                answered.clone(),
7226            ),
7227        );
7228        service.poll_runtimes().await;
7229
7230        let rows = approvals(&mut service, json!({}));
7231        assert_eq!(rows[0]["id"], "runtime-1/7");
7232
7233        let response = resolve(
7234            &mut service,
7235            json!({"id": "runtime-1/7", "decision": "allow_once"}),
7236        )
7237        .await;
7238        assert!(response.get("error").is_none(), "{response:#}");
7239        assert_eq!(
7240            response["result"],
7241            json!({
7242                "id": "runtime-1/7",
7243                "decision": "allow_once",
7244                "option_id": "allow_once",
7245                "resolved": true,
7246            }),
7247        );
7248        // The harness's own door was called with its own envelope.
7249        assert_eq!(
7250            answered
7251                .lock()
7252                .unwrap_or_else(std::sync::PoisonError::into_inner)
7253                .as_slice(),
7254            &[json!({
7255                "request_id": 7,
7256                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7257            })],
7258        );
7259        // And the row is gone, the same way `runtimes.respond` drops it.
7260        assert_eq!(
7261            approvals(&mut service, json!({})).as_array().map(Vec::len),
7262            Some(0),
7263        );
7264        // Answering it twice is an honest miss, not a silent success.
7265        let response = resolve(
7266            &mut service,
7267            json!({"id": "runtime-1/7", "decision": "allow_once"}),
7268        )
7269        .await;
7270        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7271    }
7272
7273    /// dev/01: deny travels the same path and picks the option the request
7274    /// itself classified as a refusal.
7275    #[tokio::test]
7276    async fn deny_selects_the_requests_own_reject_option() {
7277        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7278        let mut service = HarnessSessionService::new();
7279        service.runtimes.insert(
7280            "runtime-1".into(),
7281            requesting_runtime(
7282                HarnessId::HERMES,
7283                vec![permission_event(11, "git push --force")],
7284                answered.clone(),
7285            ),
7286        );
7287        service.poll_runtimes().await;
7288
7289        let response = resolve(
7290            &mut service,
7291            json!({"id": "runtime-1/11", "decision": "deny"}),
7292        )
7293        .await;
7294        assert!(response.get("error").is_none(), "{response:#}");
7295        // `deny` is the optionId whose ACP `kind` is `reject_once`.
7296        assert_eq!(response["result"]["option_id"], "deny");
7297        assert_eq!(
7298            answered
7299                .lock()
7300                .unwrap_or_else(std::sync::PoisonError::into_inner)[0]["response"],
7301            json!({"outcome": {"outcome": "selected", "optionId": "deny"}}),
7302        );
7303        assert_eq!(
7304            approvals(&mut service, json!({})).as_array().map(Vec::len),
7305            Some(0),
7306        );
7307    }
7308
7309    /// dev/01: a decision this request does not offer is refused by name,
7310    /// listing the ones it does — never silently downgraded to a neighbour.
7311    #[tokio::test]
7312    async fn a_decision_the_request_does_not_offer_is_refused_with_the_offered_ones() {
7313        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7314        let mut service = HarnessSessionService::new();
7315        let mut event = permission_event(3, "rm -rf build");
7316        // A request offering only allow-once and deny, as hermes 0.21.0's
7317        // edit-approval layer raises one.
7318        event.payload["params"]["options"] = json!([
7319            {"optionId": "allow_once", "name": "Allow edit", "kind": "allow_once"},
7320            {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
7321        ]);
7322        service.runtimes.insert(
7323            "runtime-1".into(),
7324            requesting_runtime(HarnessId::HERMES, vec![event], answered.clone()),
7325        );
7326        service.poll_runtimes().await;
7327
7328        let response = resolve(
7329            &mut service,
7330            json!({"id": "runtime-1/3", "decision": "allow_always"}),
7331        )
7332        .await;
7333        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7334        let message = response["error"]["message"].as_str().unwrap();
7335        assert!(message.contains("allow_always"), "{message}");
7336        assert!(message.contains("allow_once, deny"), "{message}");
7337        // Nothing was sent, and the request is still waiting for an answer.
7338        assert!(answered
7339            .lock()
7340            .unwrap_or_else(std::sync::PoisonError::into_inner)
7341            .is_empty());
7342        assert_eq!(
7343            approvals(&mut service, json!({})).as_array().map(Vec::len),
7344            Some(1),
7345        );
7346    }
7347
7348    /// dev/01: supercode's own queued subagent row is addressable but not
7349    /// answerable through this door — it is the parent's audit copy of a
7350    /// request its own handler answers. Refused by name, never a no-op.
7351    #[tokio::test]
7352    async fn a_queued_subagent_row_is_refused_by_name_rather_than_silently_answered() {
7353        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
7354            crate::subagents::QueuedApproval {
7355                child_agent_id: "child-7".into(),
7356                tool: "shell".into(),
7357                subject: Some("cargo publish --dry-run".into()),
7358                queued_at_ms: 1,
7359                outcome: None,
7360            },
7361        ]));
7362        let mut service = HarnessSessionService::new();
7363        service.observe_subagent_approvals(queue.clone());
7364        let row = approvals(&mut service, json!({}))[0]["id"]
7365            .as_str()
7366            .unwrap()
7367            .to_string();
7368        assert_eq!(row, "supercode/subagent/child-7/1/0");
7369
7370        let response = resolve(&mut service, json!({"id": row, "decision": "allow_once"})).await;
7371        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7372        let message = response["error"]["message"].as_str().unwrap();
7373        assert!(message.contains("queued subagent record"), "{message}");
7374        assert!(message.contains("request"), "{message}");
7375        // The audit record is untouched: nothing pretended to answer it.
7376        assert!(queue
7377            .lock()
7378            .unwrap_or_else(std::sync::PoisonError::into_inner)[0]
7379            .outcome
7380            .is_none());
7381    }
7382
7383    /// An id nobody is holding, and a call that names no decision at all,
7384    /// both fail with a message that says why.
7385    #[tokio::test]
7386    async fn an_unknown_row_and_a_missing_decision_are_both_named() {
7387        let mut service = HarnessSessionService::new();
7388        let response = resolve(
7389            &mut service,
7390            json!({"id": "runtime-9/4", "decision": "deny"}),
7391        )
7392        .await;
7393        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7394        assert!(response["error"]["message"]
7395            .as_str()
7396            .unwrap()
7397            .contains("runtime-9/4"));
7398
7399        let response = resolve(&mut service, json!({"id": "runtime-9/4"})).await;
7400        let message = response["error"]["message"].as_str().unwrap();
7401        assert!(
7402            message.contains("allow_once | allow_always | deny"),
7403            "{message}"
7404        );
7405
7406        let response = resolve(
7407            &mut service,
7408            json!({"id": "runtime-9/4", "decision": "deny", "option_id": "deny"}),
7409        )
7410        .await;
7411        assert!(response["error"]["message"]
7412            .as_str()
7413            .unwrap()
7414            .contains("not both"));
7415    }
7416
7417    /// The method is advertised, its SDK operation resolves it, and every
7418    /// harness whose runtime door can carry a request reports it on the
7419    /// CONTROLLED tier beside `runtimes.respond`.
7420    #[test]
7421    fn approvals_resolve_is_an_advertised_method_and_a_controlled_tier() {
7422        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.resolve"));
7423        assert_eq!(
7424            SdkOperation::from_method("harness.v1.approvals.resolve"),
7425            Some(SdkOperation::ApprovalsResolve)
7426        );
7427        assert_eq!(
7428            SdkOperation::ApprovalsResolve.action_name(),
7429            "approvals_resolve"
7430        );
7431        let registry = harness_support_registry();
7432        for id in [
7433            HarnessId::HERMES,
7434            HarnessId::OPENCLAW,
7435            HarnessId::CODEX,
7436            // ORC-2: the Claude Code door answers `can_use_tool`, so its
7437            // pending_request concept joins the other driven doors.
7438            HarnessId::CLAUDE_CODE,
7439        ] {
7440            let concept = registry
7441                .harnesses
7442                .iter()
7443                .find(|harness| harness.id.as_str() == id)
7444                .unwrap()
7445                .orchestration
7446                .concepts
7447                .iter()
7448                .find(|concept| concept.concept == "pending_request")
7449                .unwrap();
7450            assert_eq!(
7451                concept.controlled,
7452                crate::ImplementationKind::BuiltIn,
7453                "{id}"
7454            );
7455            assert!(
7456                concept
7457                    .methods
7458                    .iter()
7459                    .any(|method| method == "harness.v1.approvals.resolve"),
7460                "{id}"
7461            );
7462        }
7463    }
7464
7465    #[test]
7466    fn capabilities_are_explicit_and_versioned() {
7467        let mut service = HarnessSessionService::new();
7468        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
7469        assert_eq!(response["result"]["version"], HARNESS_SERVICE_VERSION);
7470        assert_eq!(
7471            response["result"]["sdk"]["schema_version"],
7472            crate::SDK_SCHEMA_VERSION
7473        );
7474        assert_eq!(
7475            response["result"]["sdk"]["operations"]
7476                .as_array()
7477                .unwrap()
7478                .len(),
7479            SdkOperation::ALL.len()
7480        );
7481        assert_eq!(
7482            response["result"]["harnesses"].as_array().unwrap().len(),
7483            11
7484        );
7485        assert!(response["result"]["harnesses"]
7486            .as_array()
7487            .unwrap()
7488            .iter()
7489            .any(|harness| harness == HarnessId::GROK));
7490        assert!(response["result"]["harnesses"]
7491            .as_array()
7492            .unwrap()
7493            .iter()
7494            .any(|harness| harness == HarnessId::GOOSE));
7495    }
7496
7497    #[test]
7498    fn handshake_health_uses_protocol_liveness_not_stderr_severity() {
7499        let noisy_stderr = crate::HarnessEvent {
7500            sequence: None,
7501            kind: "transport_stderr".into(),
7502            payload: json!({"line": "ERROR optional worker AuthorizationRequired"}),
7503        };
7504        assert_eq!(handshake_event_failure(&noisy_stderr), None);
7505
7506        let closed = crate::HarnessEvent {
7507            sequence: None,
7508            kind: "transport_closed".into(),
7509            payload: json!({}),
7510        };
7511        assert!(handshake_event_failure(&closed).is_some());
7512    }
7513
7514    #[tokio::test]
7515    async fn runtime_eof_is_notified_and_removed_for_raw_and_explicit_close() {
7516        let mut service = HarnessSessionService::new();
7517        service
7518            .runtimes
7519            .insert("raw-eof".into(), ending_runtime(None));
7520        service.runtimes.insert(
7521            "explicit-close".into(),
7522            ending_runtime(Some(HarnessEvent {
7523                sequence: None,
7524                kind: "transport_closed".into(),
7525                payload: json!({"message": "native transport exited"}),
7526            })),
7527        );
7528
7529        let notifications = service.poll_runtimes().await;
7530
7531        assert_eq!(notifications.len(), 2);
7532        assert!(notifications
7533            .iter()
7534            .all(|notification| { notification["params"]["event"]["kind"] == "transport_closed" }));
7535        assert!(notifications.iter().all(|notification| {
7536            notification["params"]["session_id"] == "ending-session"
7537                && notification["params"]["connection"].is_string()
7538        }));
7539        let mut sequences = notifications
7540            .iter()
7541            .filter_map(|notification| notification["params"]["sequence"].as_u64())
7542            .collect::<Vec<_>>();
7543        sequences.sort_unstable();
7544        assert_eq!(sequences, vec![1, 2]);
7545        assert!(service.runtimes.is_empty());
7546    }
7547
7548    #[test]
7549    fn support_report_and_grok_default_binding_share_the_registry() {
7550        let mut service = HarnessSessionService::new();
7551        let response = service.handle(request(1, "harness.v1.support.report", json!({})));
7552        assert_eq!(response["result"]["schema"], crate::SUPPORT_REGISTRY_SCHEMA);
7553        let params = RuntimeBackendParams {
7554            harness: HarnessId::from(HarnessId::GROK),
7555            protocol: None,
7556            launch: None,
7557            base_url: None,
7558            policy: RuntimePolicy::Default,
7559        };
7560        let backend = match runtime_backend(&params) {
7561            Ok(backend) => backend,
7562            Err(_) => panic!("Grok should bind through its registered ACP launch"),
7563        };
7564        assert_eq!(backend.harness().as_str(), HarnessId::GROK);
7565        assert!(backend.capabilities().start_session);
7566        let registered = harness_support_registry()
7567            .harnesses
7568            .into_iter()
7569            .find(|harness| harness.id.as_str() == HarnessId::GROK)
7570            .and_then(|harness| harness.runtime.default_launch)
7571            .unwrap();
7572        assert!(!registered
7573            .arguments
7574            .iter()
7575            .any(|argument| argument == "--always-approve"));
7576        assert!(runtime_launch(&params).is_none());
7577
7578        let yolo = RuntimeBackendParams {
7579            policy: RuntimePolicy::Yolo,
7580            ..params
7581        };
7582        assert!(runtime_launch(&yolo)
7583            .unwrap()
7584            .arguments
7585            .iter()
7586            .any(|argument| argument == "--always-approve"));
7587
7588        let mismatched_protocol = RuntimeBackendParams {
7589            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
7590            protocol: Some("acp".into()),
7591            launch: None,
7592            base_url: None,
7593            policy: RuntimePolicy::Default,
7594        };
7595        assert!(runtime_backend(&mismatched_protocol).is_err());
7596    }
7597
7598    #[test]
7599    fn load_follow_and_unfollow_share_the_same_locator() {
7600        let mut service = HarnessSessionService::new();
7601        let locator = pi_locator();
7602        let loaded = service.handle(request(
7603            1,
7604            "harness.v1.sessions.load",
7605            json!({"locator": locator}),
7606        ));
7607        assert_eq!(
7608            loaded["result"]["session"]["session_id"],
7609            locator.session_id
7610        );
7611
7612        let followed = service.handle(request(
7613            2,
7614            "harness.v1.sessions.follow",
7615            json!({"locator": locator}),
7616        ));
7617        assert_eq!(followed["result"]["subscription"], "sub-1");
7618        assert_eq!(followed["result"]["initial"]["type"], "session_snapshot");
7619        assert!(service.poll().is_empty());
7620
7621        let unfollowed = service.handle(request(
7622            3,
7623            "harness.v1.sessions.unfollow",
7624            json!({"subscription": "sub-1"}),
7625        ));
7626        assert_eq!(unfollowed["result"]["removed"], true);
7627    }
7628
7629    #[test]
7630    fn bounded_read_view_excludes_subagents_and_keeps_only_the_tail() {
7631        let temp = std::env::temp_dir().join(format!(
7632            "supercode-bounded-view-{}-{}",
7633            std::process::id(),
7634            generated_session_id()
7635        ));
7636        let path = temp.join("parent.jsonl");
7637        let subagents = temp.join("parent/subagents");
7638        std::fs::create_dir_all(&subagents).unwrap();
7639        let long_last = "x".repeat(300);
7640        let parent_records = [
7641            json!({"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":"first"}}),
7642            json!({"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"middle"}]}}),
7643            json!({"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":long_last}}),
7644        ];
7645        std::fs::write(
7646            &path,
7647            format!(
7648                "{}\n",
7649                parent_records
7650                    .iter()
7651                    .map(Value::to_string)
7652                    .collect::<Vec<_>>()
7653                    .join("\n")
7654            ),
7655        )
7656        .unwrap();
7657        std::fs::write(
7658            subagents.join("agent-child.jsonl"),
7659            concat!(
7660                r#"{"type":"user","uuid":"cu","parentUuid":null,"agentId":"child","message":{"role":"user","content":"child work"}}"#,
7661                "\n",
7662            ),
7663        )
7664        .unwrap();
7665        let locator = SessionLocator {
7666            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
7667            session_id: "parent".into(),
7668            storage: StorageLocator::File { path },
7669        };
7670        let mut service = HarnessSessionService::new();
7671
7672        let complete = service.handle(request(
7673            1,
7674            "harness.v1.sessions.load",
7675            json!({"locator": locator}),
7676        ));
7677        assert_eq!(
7678            complete["result"]["session"]["subagents"]
7679                .as_array()
7680                .unwrap()
7681                .len(),
7682            1
7683        );
7684
7685        let bounded = service.handle(request(
7686            2,
7687            "harness.v1.sessions.load",
7688            json!({
7689                "locator": locator,
7690                "view": {
7691                    "tail_messages": 1,
7692                    "max_message_chars": 256,
7693                    "include_subagents": false
7694                },
7695            }),
7696        ));
7697        let session = &bounded["result"]["session"];
7698        assert!(session["subagents"].as_array().unwrap().is_empty());
7699        assert_eq!(session["messages"].as_array().unwrap().len(), 1);
7700        assert_eq!(
7701            session["messages"][0]["content"],
7702            format!("{}\n…", "x".repeat(256))
7703        );
7704
7705        let followed = service.handle(request(
7706            3,
7707            "harness.v1.sessions.follow",
7708            json!({
7709                "locator": locator,
7710                "view": {
7711                    "tail_messages": 1,
7712                    "max_message_chars": 256,
7713                    "include_subagents": false
7714                },
7715            }),
7716        ));
7717        let initial = &followed["result"]["initial"]["session"];
7718        assert!(initial["subagents"].as_array().unwrap().is_empty());
7719        assert_eq!(initial["messages"].as_array().unwrap().len(), 1);
7720
7721        let _ = std::fs::remove_dir_all(&temp);
7722    }
7723
7724    #[test]
7725    fn forty_megabyte_display_load_is_bounded_and_prompt() {
7726        let temp = std::env::temp_dir().join(format!(
7727            "supercode-large-display-view-{}-{}",
7728            std::process::id(),
7729            generated_session_id()
7730        ));
7731        std::fs::create_dir_all(&temp).unwrap();
7732        let path = temp.join("rollout.jsonl");
7733        let mut file = std::io::BufWriter::new(std::fs::File::create(&path).unwrap());
7734        writeln!(
7735            file,
7736            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
7737        )
7738        .unwrap();
7739        let padding = "x".repeat(80 * 1024);
7740        for index in 0..512 {
7741            let marker = if index == 0 {
7742                "OLDEST-SHOULD-NOT-LOAD"
7743            } else if index == 511 {
7744                "LATEST-MUST-LOAD"
7745            } else {
7746                "bulk"
7747            };
7748            writeln!(
7749                file,
7750                "{}",
7751                json!({
7752                    "timestamp": "2026-01-01T00:00:01Z",
7753                    "type": "response_item",
7754                    "payload": {
7755                        "type": "message",
7756                        "role": "assistant",
7757                        "content": [{"type": "output_text", "text": format!("{marker}:{padding}")}],
7758                    },
7759                })
7760            )
7761            .unwrap();
7762        }
7763        file.flush().unwrap();
7764        drop(file);
7765        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
7766
7767        let locator = SessionLocator {
7768            harness: HarnessId::from(HarnessId::CODEX),
7769            session_id: "large-display".into(),
7770            storage: StorageLocator::File { path },
7771        };
7772        let started = Instant::now();
7773        let response = HarnessSessionService::new().handle(request(
7774            1,
7775            "harness.v1.sessions.load",
7776            json!({
7777                "locator": locator,
7778                "view": {
7779                    "tail_messages": 500,
7780                    "max_message_chars": 1024,
7781                    "include_subagents": false,
7782                    "display_history": true,
7783                },
7784            }),
7785        ));
7786        let elapsed = started.elapsed();
7787        let wire = response.to_string();
7788        eprintln!(
7789            "bounded 40 MiB display load: {elapsed:?}, {} response bytes",
7790            wire.len()
7791        );
7792        assert!(response.get("error").is_none(), "{response:#}");
7793        assert!(wire.contains("LATEST-MUST-LOAD"));
7794        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
7795        assert!(
7796            wire.len() < 2 * 1024 * 1024,
7797            "bounded wire was {} bytes",
7798            wire.len()
7799        );
7800        assert!(
7801            elapsed.as_secs_f64() < 3.0,
7802            "bounded 40 MiB load took {elapsed:?}"
7803        );
7804
7805        let _ = std::fs::remove_dir_all(&temp);
7806    }
7807
7808    #[test]
7809    fn forty_megabyte_goose_store_display_load_reads_only_the_tail() {
7810        let temp = std::env::temp_dir().join(format!(
7811            "supercode-large-goose-view-{}-{}",
7812            std::process::id(),
7813            generated_session_id()
7814        ));
7815        std::fs::create_dir_all(&temp).unwrap();
7816        let path = temp.join("sessions.db");
7817        let connection = rusqlite::Connection::open(&path).unwrap();
7818        connection
7819            .execute_batch(
7820                "CREATE TABLE sessions (
7821                    id TEXT PRIMARY KEY, name TEXT NOT NULL, working_dir TEXT NOT NULL,
7822                    created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
7823                    session_type TEXT NOT NULL, extension_data TEXT,
7824                    goose_mode TEXT NOT NULL, provider_name TEXT, model_config_json TEXT,
7825                    archived_at TEXT
7826                 );
7827                 CREATE TABLE messages (
7828                    id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, message_id TEXT,
7829                    role TEXT NOT NULL, content_json TEXT NOT NULL,
7830                    created_timestamp INTEGER NOT NULL, metadata_json TEXT
7831                 );",
7832            )
7833            .unwrap();
7834        connection
7835            .execute(
7836                "INSERT INTO sessions VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL)",
7837                rusqlite::params![
7838                    "goose-large",
7839                    "Large Goose session",
7840                    "/tmp",
7841                    "2026-01-01 00:00:00",
7842                    "2026-01-01 00:00:02",
7843                    "user",
7844                    "{}",
7845                    "auto",
7846                    "anthropic",
7847                    r#"{"model_name":"claude-sonnet"}"#,
7848                ],
7849            )
7850            .unwrap();
7851        let old_content = serde_json::to_string(&vec![json!({
7852            "type": "text",
7853            "text": format!("OLDEST-SHOULD-NOT-LOAD:{}", "x".repeat(40 * 1024 * 1024)),
7854        })])
7855        .unwrap();
7856        connection
7857            .execute(
7858                "INSERT INTO messages VALUES (1, ?1, 'old', 'user', ?2, 1, '{}')",
7859                rusqlite::params!["goose-large", old_content],
7860            )
7861            .unwrap();
7862        connection
7863            .execute(
7864                "INSERT INTO messages VALUES (2, ?1, 'new', 'assistant', ?2, 2, '{}')",
7865                rusqlite::params![
7866                    "goose-large",
7867                    r#"[{"type":"text","text":"LATEST-MUST-LOAD"}]"#
7868                ],
7869            )
7870            .unwrap();
7871        drop(connection);
7872        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
7873
7874        let locator = SessionLocator {
7875            harness: HarnessId::from(HarnessId::GOOSE),
7876            session_id: "goose-large".into(),
7877            storage: StorageLocator::Sqlite {
7878                path,
7879                selector: "goose-large".into(),
7880            },
7881        };
7882        let started = Instant::now();
7883        let response = HarnessSessionService::new().handle(request(
7884            1,
7885            "harness.v1.sessions.load",
7886            json!({
7887                "locator": locator,
7888                "view": {
7889                    "tail_messages": 1,
7890                    "max_message_chars": 1024,
7891                    "include_subagents": false,
7892                    "display_history": true,
7893                },
7894            }),
7895        ));
7896        let elapsed = started.elapsed();
7897        let wire = response.to_string();
7898        eprintln!(
7899            "bounded 40 MiB Goose display load: {elapsed:?}, {} response bytes",
7900            wire.len()
7901        );
7902        assert!(response.get("error").is_none(), "{response:#}");
7903        assert!(wire.contains("LATEST-MUST-LOAD"));
7904        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
7905        assert!(
7906            wire.len() < 64 * 1024,
7907            "bounded wire was {} bytes",
7908            wire.len()
7909        );
7910        assert!(
7911            elapsed.as_secs_f64() < 1.0,
7912            "bounded Goose load took {elapsed:?}"
7913        );
7914
7915        let _ = std::fs::remove_dir_all(&temp);
7916    }
7917
7918    #[test]
7919    fn display_view_keeps_codex_assistant_history_across_compaction() {
7920        let temp = std::env::temp_dir().join(format!(
7921            "supercode-codex-display-view-{}-{}",
7922            std::process::id(),
7923            generated_session_id()
7924        ));
7925        std::fs::create_dir_all(&temp).unwrap();
7926        let path = temp.join("rollout.jsonl");
7927        std::fs::write(
7928            &path,
7929            concat!(
7930                r#"{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"codex-display","cwd":"/tmp"}}"#,
7931                "\n",
7932                r#"{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]}}"#,
7933                "\n",
7934                r#"{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"old answer"}]}}"#,
7935                "\n",
7936                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"}]}}"#,
7937                "\n",
7938                r#"{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"new prompt"}]}}"#,
7939                "\n",
7940                r#"{"timestamp":"2026-01-01T00:00:05Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"new answer"}]}}"#,
7941                "\n",
7942            ),
7943        )
7944        .unwrap();
7945        let locator = SessionLocator {
7946            harness: HarnessId::from(HarnessId::CODEX),
7947            session_id: "codex-display".into(),
7948            storage: StorageLocator::File { path },
7949        };
7950        let mut service = HarnessSessionService::new();
7951
7952        let continuation = service.handle(request(
7953            1,
7954            "harness.v1.sessions.load",
7955            json!({"locator": locator}),
7956        ));
7957        let continuation_text = continuation["result"]["session"]["messages"].to_string();
7958        assert!(!continuation_text.contains("old answer"));
7959
7960        let display = service.handle(request(
7961            2,
7962            "harness.v1.sessions.load",
7963            json!({
7964                "locator": locator,
7965                "view": {
7966                    "tail_messages": 10,
7967                    "include_subagents": false,
7968                    "display_history": true,
7969                },
7970            }),
7971        ));
7972        let display_text = display["result"]["session"]["messages"].to_string();
7973        assert!(display_text.contains("old prompt"));
7974        assert!(display_text.contains("old answer"));
7975        assert!(display_text.contains("new prompt"));
7976        assert!(display_text.contains("new answer"));
7977
7978        let _ = std::fs::remove_dir_all(&temp);
7979    }
7980
7981    #[test]
7982    fn indexed_claude_windows_match_the_existing_wire_projection() {
7983        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7984            .join("tests/fixtures/claude_code_session.jsonl");
7985        let locator = SessionLocator {
7986            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
7987            session_id: "fixture".into(),
7988            storage: StorageLocator::File { path },
7989        };
7990        let full = load_session(&locator).unwrap();
7991        for inline_media in [InlineMediaMode::Full, InlineMediaMode::Metadata] {
7992            for offset in [0, 1, full.messages.len(), usize::MAX] {
7993                for limit in [0, 1, 3, usize::MAX] {
7994                    let options = SessionLoadOptions {
7995                        include_subagents: Some(false),
7996                        inline_media,
7997                        message_offset: Some(offset),
7998                        message_limit: Some(limit),
7999                        ..Default::default()
8000                    };
8001                    let expected = projected_session_result(&full, &options);
8002                    assert_eq!(
8003                        indexed_claude_window(&locator, &options).unwrap().unwrap(),
8004                        expected
8005                    );
8006                }
8007            }
8008            for tail in [0, 1, 3, usize::MAX] {
8009                let options = SessionLoadOptions {
8010                    include_subagents: Some(false),
8011                    inline_media,
8012                    message_tail: Some(tail),
8013                    ..Default::default()
8014                };
8015                assert_eq!(
8016                    indexed_claude_window(&locator, &options).unwrap().unwrap(),
8017                    projected_session_result(&full, &options)
8018                );
8019            }
8020        }
8021    }
8022
8023    #[test]
8024    fn load_supports_bounded_windows_and_media_metadata() {
8025        let mut service = HarnessSessionService::new();
8026        let locator = pi_locator();
8027        let bounded = service.handle(request(
8028            1,
8029            "harness.v1.sessions.load",
8030            json!({
8031                "locator": locator,
8032                "options": {
8033                    "include_subagents": false,
8034                    "message_limit": 2,
8035                    "message_offset": 1
8036                }
8037            }),
8038        ));
8039        assert_eq!(bounded["result"]["window"]["offset"], 1);
8040        assert_eq!(bounded["result"]["window"]["returned"], 2);
8041        assert!(bounded["result"]["summary"]["first_message"].is_object());
8042        assert!(bounded["result"]["summary"]["last_message"].is_object());
8043        assert_eq!(
8044            bounded["result"]["session"]["messages"]
8045                .as_array()
8046                .unwrap()
8047                .len(),
8048            2
8049        );
8050        assert!(bounded["result"]["session"]["subagents"]
8051            .as_array()
8052            .unwrap()
8053            .is_empty());
8054
8055        let tail = service.handle(request(
8056            2,
8057            "harness.v1.sessions.load",
8058            json!({"locator": locator, "options": {"message_tail": 1}}),
8059        ));
8060        assert_eq!(tail["result"]["window"]["returned"], 1);
8061        assert_eq!(tail["result"]["window"]["has_more"], true);
8062        assert_eq!(tail["result"]["window"]["has_older"], true);
8063        assert!(tail["result"]["window"]["older_items"].as_u64().unwrap() > 0);
8064        assert!(tail["result"]["summary"]["first_message"].is_object());
8065
8066        let metadata_only = service.handle(request(
8067            3,
8068            "harness.v1.sessions.load",
8069            json!({"locator": locator, "options": {"inline_media": "metadata"}}),
8070        ));
8071        assert!(metadata_only["result"]["session"]
8072            .to_string()
8073            .contains("media_reference"));
8074        assert!(!metadata_only["result"]["session"]
8075            .to_string()
8076            .contains("data:image/"));
8077    }
8078
8079    #[test]
8080    fn import_translate_branch_and_handoff_use_typed_artifacts() {
8081        let mut service = HarnessSessionService::new();
8082        let locator = pi_locator();
8083        let translated = service.handle(request(
8084            1,
8085            "harness.v1.sessions.translate",
8086            json!({"locator": locator, "target_harness": "grok"}),
8087        ));
8088        assert_eq!(translated["result"]["artifact"]["source_harness"], "pi");
8089        assert_eq!(translated["result"]["artifact"]["target_harness"], "grok");
8090        assert!(translated["result"]["artifact"]["content"]
8091            .as_str()
8092            .is_some_and(|content| !content.is_empty()));
8093
8094        for target in ["opencode", "open-code"] {
8095            let opencode = service.handle(request(
8096                6,
8097                "harness.v1.sessions.translate",
8098                json!({"locator": locator, "target_harness": target}),
8099            ));
8100            assert_eq!(opencode["result"]["artifact"]["target_harness"], "opencode");
8101        }
8102        let goose = service.handle(request(
8103            7,
8104            "harness.v1.sessions.translate",
8105            json!({"locator": locator, "target_harness": "goose"}),
8106        ));
8107        assert_eq!(goose["result"]["artifact"]["target_harness"], "goose");
8108        assert!(serde_json::from_str::<Value>(
8109            goose["result"]["artifact"]["content"].as_str().unwrap()
8110        )
8111        .unwrap()["conversation"]
8112            .is_array());
8113
8114        let imported = service.handle(request(
8115            2,
8116            "harness.v1.sessions.import",
8117            json!({
8118                "source_harness": "grok",
8119                "content": translated["result"]["artifact"]["content"],
8120            }),
8121        ));
8122        assert_eq!(imported["result"]["session"]["source"], "grok");
8123
8124        let branched = service.handle(request(
8125            3,
8126            "harness.v1.sessions.branch",
8127            json!({"locator": locator, "target_harness": "codex"}),
8128        ));
8129        assert_eq!(branched["result"]["parent"]["harness"], "pi");
8130        assert!(branched["result"]["bootstrap_prompt"]
8131            .as_str()
8132            .unwrap()
8133            .contains("frozen parent transcript"));
8134        assert_eq!(branched["result"]["artifact"]["target_harness"], "codex");
8135
8136        let handoff = service.handle(request(
8137            4,
8138            "harness.v1.sessions.handoff",
8139            json!({"locator": locator, "target_harness": "pi", "cwd": "/tmp/project"}),
8140        ));
8141        assert_eq!(handoff["result"]["launch"]["program"], "pi");
8142        assert_eq!(handoff["result"]["launch"]["cwd"], "/tmp/project");
8143        assert_eq!(handoff["result"]["requires_materialization"], true);
8144
8145        let goose_handoff = service.handle(request(
8146            8,
8147            "harness.v1.sessions.handoff",
8148            json!({"locator": locator, "target_harness": "goose", "cwd": "/tmp/project"}),
8149        ));
8150        assert_eq!(goose_handoff["result"]["launch"]["program"], "goose");
8151        assert_eq!(
8152            goose_handoff["result"]["materialize"]["arguments"],
8153            json!(["session", "import", "{artifact_path}"])
8154        );
8155
8156        let resumed = service.handle(request(
8157            5,
8158            "harness.v1.sessions.resume_instructions",
8159            json!({"locator": locator, "cwd": "/tmp/project", "policy": "yolo"}),
8160        ));
8161        assert_eq!(resumed["result"]["launch"]["program"], "pi");
8162        assert_eq!(resumed["result"]["launch"]["arguments"][0], "--approve");
8163    }
8164
8165    #[test]
8166    fn reduce_persists_and_reloads_a_byte_exact_reversible_bundle() {
8167        let temp = std::env::temp_dir().join(format!(
8168            "supercode-service-reduce-{}-{}",
8169            std::process::id(),
8170            generated_session_id()
8171        ));
8172        let source_path = temp.join("source.jsonl");
8173        let store_root = temp.join("store");
8174        std::fs::create_dir_all(&temp).unwrap();
8175
8176        let mut records = vec![json!({
8177            "timestamp": "2026-01-01T00:00:00Z",
8178            "type": "session_meta",
8179            "payload": {"id": "codex-reduce", "cwd": "/tmp/project"},
8180        })];
8181        for turn in 0..16 {
8182            records.push(json!({
8183                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 1),
8184                "type": "response_item",
8185                "payload": {
8186                    "type": "message",
8187                    "role": "user",
8188                    "content": [{
8189                        "type": "input_text",
8190                        "text": format!("request {turn}: {}", "context ".repeat(80)),
8191                    }],
8192                },
8193            }));
8194            records.push(json!({
8195                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 2),
8196                "type": "response_item",
8197                "payload": {
8198                    "type": "message",
8199                    "role": "assistant",
8200                    "content": [{
8201                        "type": "output_text",
8202                        "text": format!("answer {turn}: {}", "implementation detail ".repeat(80)),
8203                    }],
8204                },
8205            }));
8206        }
8207        let source = format!(
8208            "{}\n",
8209            records
8210                .iter()
8211                .map(Value::to_string)
8212                .collect::<Vec<_>>()
8213                .join("\n")
8214        );
8215        std::fs::write(&source_path, &source).unwrap();
8216        let locator = SessionLocator {
8217            harness: HarnessId::from(HarnessId::CODEX),
8218            session_id: "codex-reduce".into(),
8219            storage: StorageLocator::File {
8220                path: source_path.clone(),
8221            },
8222        };
8223        let original = load_session(&locator).unwrap();
8224        let mut service =
8225            HarnessSessionService::new().with_reduction_store_root(store_root.clone());
8226
8227        let response = service.handle(request(
8228            1,
8229            "harness.v1.sessions.reduce",
8230            json!({
8231                "locator": locator,
8232                "target_harness": "claude-code",
8233                "keep_last": 4,
8234            }),
8235        ));
8236        assert!(response.get("error").is_none(), "{response:#}");
8237        let receipt = &response["result"]["receipt"];
8238        assert_eq!(receipt["source_harness"], "codex");
8239        assert_eq!(receipt["target_harness"], "claude-code");
8240        assert_eq!(receipt["verified"], true);
8241        assert_eq!(receipt["reversible"], true);
8242        assert!(receipt["reductions"].as_u64().unwrap() > 0);
8243        assert!(
8244            receipt["source_tokens"].as_u64().unwrap()
8245                > receipt["reduced_tokens"].as_u64().unwrap()
8246        );
8247        assert!(receipt["ratio"].as_f64().unwrap() > 1.0);
8248        assert!(response["result"]["bootstrap_prompt"]
8249            .as_str()
8250            .unwrap()
8251            .contains("Do not guess hidden content"));
8252
8253        let rescue_id = receipt["id"].as_str().unwrap();
8254        let store = crate::SessionStore::open(&store_root).unwrap();
8255        let sidecar =
8256            Session::from_sidecar_str(&store.load_sidecar(rescue_id).unwrap().unwrap()).unwrap();
8257        let log = store.load_reduction_log(rescue_id).unwrap().unwrap();
8258        let persisted_view = parse_messages_jsonl(&store.load(rescue_id).unwrap()).unwrap();
8259        let policy = reduce::ReductionPolicy {
8260            clear_turns_older_than: Some(4),
8261            ..Default::default()
8262        };
8263        let (restamped_view, reapplied_log) =
8264            reduce::project_messages(&sidecar.messages, &policy, &log);
8265        assert_eq!(
8266            messages_jsonl(&persisted_view).unwrap(),
8267            messages_jsonl(&restamped_view).unwrap()
8268        );
8269        assert_eq!(reapplied_log, log);
8270        reduce::verify_log(&log, &sidecar).unwrap();
8271        assert_eq!(
8272            reduce::invert(&restamped_view, &log, &sidecar).unwrap(),
8273            original.messages
8274        );
8275        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), source);
8276
8277        std::fs::remove_dir_all(temp).ok();
8278    }
8279
8280    #[test]
8281    fn read_surfaces_view_a_severed_claude_graph_while_transfer_still_refuses_it() {
8282        let temp = std::env::temp_dir().join(format!(
8283            "supercode-severed-view-{}-{}",
8284            std::process::id(),
8285            generated_session_id()
8286        ));
8287        std::fs::create_dir_all(&temp).unwrap();
8288        let path = temp.join("severed.jsonl");
8289        // A live record whose parent was pruned — what a compacted or
8290        // resumed-across-files Claude Code session looks like on disk.
8291        std::fs::write(
8292            &path,
8293            concat!(
8294                r#"{"type":"user","uuid":"orphan-u","parentUuid":null,"message":{"role":"user","content":"stranded prompt"}}"#,
8295                "\n",
8296                r#"{"type":"assistant","uuid":"live-a","parentUuid":"pruned","message":{"id":"m","role":"assistant","content":[{"type":"text","text":"live answer"}]}}"#,
8297                "\n",
8298            ),
8299        )
8300        .unwrap();
8301        let locator = SessionLocator {
8302            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8303            session_id: "severed".into(),
8304            storage: StorageLocator::File { path },
8305        };
8306        let mut service = HarnessSessionService::new();
8307
8308        let viewed = service.handle(request(
8309            1,
8310            "harness.v1.sessions.load",
8311            json!({"locator": locator}),
8312        ));
8313        let session = &viewed["result"]["session"];
8314        assert_eq!(session["fidelity"], "semantic");
8315        assert_eq!(session["messages"].as_array().unwrap().len(), 2);
8316        assert!(session["residue"].as_array().unwrap().iter().any(|entry| {
8317            entry
8318                .as_str()
8319                .is_some_and(|entry| entry.contains("live-a") && entry.contains("pruned"))
8320        }));
8321
8322        // Asking a READ surface for a lossless reconstruction gets the strict
8323        // refusal back, unchanged.
8324        let strict = service.handle(request(
8325            2,
8326            "harness.v1.sessions.load",
8327            json!({"locator": locator, "fidelity": "byte_lossless"}),
8328        ));
8329        assert!(strict["error"]["message"]
8330            .as_str()
8331            .unwrap()
8332            .contains("cannot reconstruct lossless Claude continuation"));
8333
8334        // Transfer/continuation surfaces have no view mode at all.
8335        let translated = service.handle(request(
8336            3,
8337            "harness.v1.sessions.translate",
8338            json!({"locator": locator, "target_harness": "codex"}),
8339        ));
8340        assert!(translated["error"]["message"]
8341            .as_str()
8342            .unwrap()
8343            .contains("cannot reconstruct lossless Claude continuation"));
8344        let resumed = service.handle(request(
8345            4,
8346            "harness.v1.sessions.resume_instructions",
8347            json!({"locator": locator}),
8348        ));
8349        assert!(resumed["error"]["message"]
8350            .as_str()
8351            .unwrap()
8352            .contains("cannot reconstruct lossless Claude continuation"));
8353
8354        let _ = std::fs::remove_dir_all(&temp);
8355    }
8356
8357    #[test]
8358    fn structured_resume_launches_cover_gemini_goose_and_supercode() {
8359        let codex = resume_launch(
8360            HarnessId::CODEX,
8361            "codex-session",
8362            Path::new("/tmp/project"),
8363            ResumePolicy::Yolo,
8364        )
8365        .unwrap_or_else(|_| panic!("Codex resume launch must be registered"));
8366        assert_eq!(codex.program, "codex");
8367        assert_eq!(
8368            codex.arguments,
8369            [
8370                "-c",
8371                "check_for_update_on_startup=false",
8372                "-c",
8373                "projects.\"/tmp/project\".trust_level=\"trusted\"",
8374                "--dangerously-bypass-approvals-and-sandbox",
8375                "--dangerously-bypass-hook-trust",
8376                "resume",
8377                "codex-session",
8378            ]
8379        );
8380
8381        let gemini = resume_launch(
8382            HarnessId::GEMINI,
8383            "gemini-session",
8384            Path::new("/tmp/project"),
8385            ResumePolicy::Yolo,
8386        )
8387        .unwrap_or_else(|_| panic!("Gemini resume launch must be registered"));
8388        assert_eq!(gemini.program, "gemini");
8389        assert_eq!(gemini.arguments, ["--yolo", "--resume", "gemini-session"]);
8390
8391        let goose = resume_launch(
8392            HarnessId::GOOSE,
8393            "goose-session",
8394            Path::new("/tmp/project"),
8395            ResumePolicy::Yolo,
8396        )
8397        .unwrap_or_else(|_| panic!("Goose resume launch must be registered"));
8398        assert_eq!(goose.program, "goose");
8399        assert_eq!(
8400            goose.arguments,
8401            ["session", "--resume", "--session-id", "goose-session"]
8402        );
8403
8404        let supercode = resume_launch(
8405            HarnessId::SUPERCODE,
8406            "supercode-session",
8407            Path::new("/tmp/project"),
8408            ResumePolicy::Yolo,
8409        )
8410        .unwrap_or_else(|_| panic!("Supercode resume launch must be registered"));
8411        assert_eq!(supercode.program, "supercode");
8412        assert_eq!(
8413            supercode.arguments,
8414            ["--dangerous", "resume", "supercode-session"]
8415        );
8416    }
8417
8418    #[test]
8419    fn diagonal_artifacts_preserve_claude_subagents_and_grok_bundle_members() {
8420        let temp = std::env::temp_dir().join(format!(
8421            "supercode-harness-artifact-{}-{}",
8422            std::process::id(),
8423            generated_session_id()
8424        ));
8425        let main_path = temp.join("parent.jsonl");
8426        let subagent_path = temp.join("parent/subagents/agent-child.jsonl");
8427        std::fs::create_dir_all(subagent_path.parent().unwrap()).unwrap();
8428        let fixture = std::fs::read_to_string(
8429            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8430                .join("tests/fixtures/claude_code_session.jsonl"),
8431        )
8432        .unwrap();
8433        let parent = fixture.trim_end_matches('\n');
8434        let child = fixture.trim_end_matches('\n');
8435        std::fs::write(&main_path, parent).unwrap();
8436        std::fs::write(&subagent_path, child).unwrap();
8437        let locator = SessionLocator {
8438            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8439            session_id: "213bb148-51ea-453f-9206-f8b4b1168547".into(),
8440            storage: StorageLocator::File {
8441                path: main_path.clone(),
8442            },
8443        };
8444        let mut service = HarnessSessionService::new();
8445        let claude = service.handle(request(
8446            1,
8447            "harness.v1.sessions.translate",
8448            json!({"locator": locator, "target_harness": "claude-code"}),
8449        ));
8450        let artifact = &claude["result"]["artifact"];
8451        assert_eq!(artifact["fidelity"], "byte_lossless");
8452        assert_eq!(artifact["content"], parent);
8453        let files = artifact["files"].as_array().unwrap();
8454        assert!(files.iter().any(|file| {
8455            file["role"] == "subagent"
8456                && file["path"]
8457                    .as_str()
8458                    .is_some_and(|path| path.ends_with("/subagents/agent-child.jsonl"))
8459                && file["content"] == child
8460        }));
8461        assert!(!artifact["content"].as_str().unwrap().ends_with('\n'));
8462
8463        let grok = service.handle(request(
8464            2,
8465            "harness.v1.sessions.translate",
8466            json!({"locator": grok_locator(), "target_harness": "grok"}),
8467        ));
8468        let files = grok["result"]["artifact"]["files"].as_array().unwrap();
8469        for name in ["summary.json", "updates.jsonl"] {
8470            let expected = std::fs::read_to_string(
8471                PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8472                    .join("tests/fixtures/grok_session")
8473                    .join(name),
8474            )
8475            .unwrap();
8476            assert!(files.iter().any(|file| {
8477                file["path"] == name && file["role"] == "bundle" && file["content"] == expected
8478            }));
8479        }
8480        std::fs::remove_dir_all(temp).ok();
8481    }
8482
8483    #[test]
8484    fn every_non_grok_handoff_mints_and_uses_a_fresh_target_identity() {
8485        let mut service = HarnessSessionService::new();
8486        let source = pi_locator();
8487        for (target, format) in [
8488            ("claude-code", SessionFormat::ClaudeCode),
8489            ("codex", SessionFormat::Codex),
8490            ("opencode", SessionFormat::OpenCode),
8491            ("pi", SessionFormat::Pi),
8492        ] {
8493            let result = service.handle(request(
8494                1,
8495                "harness.v1.sessions.handoff",
8496                json!({"locator": source, "target_harness": target, "cwd": "/tmp/project"}),
8497            ));
8498            let artifact = &result["result"]["artifact"];
8499            let target_id = artifact["session_id"].as_str().unwrap();
8500            assert_ne!(target_id, source.session_id, "{target}");
8501            let parsed = Session::load_str(artifact["content"].as_str().unwrap(), format).unwrap();
8502            assert_eq!(
8503                parsed.meta.session_id.as_deref(),
8504                Some(target_id),
8505                "{target}"
8506            );
8507            if target != "pi" {
8508                assert!(result["result"]["launch"]["arguments"]
8509                    .as_array()
8510                    .unwrap()
8511                    .iter()
8512                    .any(|argument| argument == target_id));
8513            }
8514            if target == "opencode" {
8515                assert!(target_id.starts_with("ses_"));
8516                fn assert_session_ids(value: &Value, target_id: &str) {
8517                    match value {
8518                        Value::Object(fields) => {
8519                            if let Some(session_id) = fields.get("sessionID") {
8520                                assert_eq!(session_id, target_id);
8521                            }
8522                            for child in fields.values() {
8523                                assert_session_ids(child, target_id);
8524                            }
8525                        }
8526                        Value::Array(values) => {
8527                            for child in values {
8528                                assert_session_ids(child, target_id);
8529                            }
8530                        }
8531                        _ => {}
8532                    }
8533                }
8534                let document: Value =
8535                    serde_json::from_str(artifact["content"].as_str().unwrap()).unwrap();
8536                assert_session_ids(&document, target_id);
8537            }
8538        }
8539
8540        let first = service.handle(request(
8541            2,
8542            "harness.v1.sessions.handoff",
8543            json!({"locator": source, "target_harness": "codex"}),
8544        ));
8545        let second = service.handle(request(
8546            3,
8547            "harness.v1.sessions.handoff",
8548            json!({"locator": source, "target_harness": "codex"}),
8549        ));
8550        assert_ne!(
8551            first["result"]["artifact"]["session_id"],
8552            second["result"]["artifact"]["session_id"]
8553        );
8554    }
8555
8556    #[test]
8557    fn grok_handoff_uses_the_official_importer_contract() {
8558        let mut service = HarnessSessionService::new();
8559        let source = opencode_locator();
8560        let response = service.handle(request(
8561            1,
8562            "harness.v1.sessions.handoff",
8563            json!({
8564                "locator": source,
8565                "target_harness": "grok",
8566                "cwd": "/tmp/grok-handoff-project",
8567            }),
8568        ));
8569        let result = &response["result"];
8570
8571        // The target is Grok, but the artifact truthfully names the Claude Code wire
8572        // format accepted by Grok's official importer. Raw Grok chat_history JSONL is
8573        // not a complete stock-resumable bundle.
8574        assert_eq!(result["artifact"]["target_harness"], "claude-code");
8575        assert!(result["artifact"]["suggested_filename"]
8576            .as_str()
8577            .unwrap()
8578            .ends_with(".grok-import.claude-code.jsonl"));
8579        let artifact = Session::load_str(
8580            result["artifact"]["content"].as_str().unwrap(),
8581            SessionFormat::ClaudeCode,
8582        )
8583        .unwrap();
8584        assert_eq!(
8585            artifact.meta.cwd.as_deref(),
8586            Some(Path::new("/tmp/grok-handoff-project"))
8587        );
8588        let target_session_id = artifact.meta.session_id.as_deref().unwrap();
8589        assert_eq!(target_session_id.len(), 36);
8590        assert_eq!(target_session_id.as_bytes()[14], b'4');
8591        assert_ne!(target_session_id, opencode_locator().session_id);
8592        assert_eq!(
8593            result["artifact"]["session_id"],
8594            artifact.meta.session_id.as_deref().unwrap()
8595        );
8596
8597        assert_eq!(
8598            result["materialize"]["arguments"],
8599            json!(["import", "--json", "{artifact_path}"])
8600        );
8601        assert_eq!(
8602            result["launch"]["arguments"],
8603            json!(["--resume", "{imported_session_id}", "--fork-session"])
8604        );
8605        assert!(result["note"]
8606            .as_str()
8607            .unwrap()
8608            .contains("outcome=imported"));
8609        assert!(!result["launch"]["arguments"]
8610            .as_array()
8611            .unwrap()
8612            .iter()
8613            .any(|argument| argument == &opencode_locator().session_id));
8614    }
8615
8616    #[tokio::test]
8617    async fn inventory_rejects_unknown_harnesses_and_runtime_attach_is_honest() {
8618        let mut service = HarnessSessionService::new();
8619        let inventory = service
8620            .handle_async(request(
8621                1,
8622                "harness.v1.harnesses.list",
8623                json!({"harnesses": ["missing"]}),
8624            ))
8625            .await;
8626        assert_eq!(inventory["error"]["code"], -32602);
8627
8628        let attached = service
8629            .handle_async(request(
8630                2,
8631                "harness.v1.runtimes.attach_existing",
8632                json!({"harness": "codex", "runtime_id": "thread-1"}),
8633            ))
8634            .await;
8635        assert_eq!(attached["error"]["code"], -32000);
8636        assert!(attached["error"]["message"]
8637            .as_str()
8638            .unwrap()
8639            .contains("runtimes.resume"));
8640    }
8641
8642    #[test]
8643    fn invalid_params_and_unknown_methods_use_json_rpc_errors() {
8644        let mut service = HarnessSessionService::new();
8645        let invalid = service.handle(request(1, "harness.v1.sessions.load", json!({})));
8646        assert_eq!(invalid["error"]["code"], -32602);
8647        let unknown = service.handle(request(2, "harness.v1.unknown", json!({})));
8648        assert_eq!(unknown["error"]["code"], -32601);
8649    }
8650
8651    #[cfg(unix)]
8652    #[tokio::test]
8653    // The test mutates process-wide harness environment and deliberately
8654    // holds the global test lock until every async runtime operation ends.
8655    #[allow(clippy::await_holding_lock)]
8656    async fn async_service_drives_a_generic_acp_runtime() {
8657        let _environment_guard = crate::live_runtime::test_environment_lock();
8658        let script = r#"
8659            i=0
8660            while IFS= read -r line; do
8661              i=$((i + 1))
8662              case "$i" in
8663                1) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{},"authMethods":[]}}' ;;
8664                2) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"svc_acp"}}' ;;
8665                3)
8666                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ok"}}}}'
8667                  printf '%s\n' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}'
8668                  ;;
8669                4)
8670                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"from terminal"}}}}'
8671                  printf '%s\n' '{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}'
8672                  ;;
8673              esac
8674            done
8675        "#;
8676        let mut service = HarnessSessionService::new();
8677        let started = service
8678            .handle_async(request(
8679                1,
8680                "harness.v1.runtimes.start",
8681                json!({
8682                    "harness": "codex",
8683                    "protocol": "acp",
8684                    "cwd": std::env::current_dir().unwrap(),
8685                    "launch": {"program": "/bin/sh", "arguments": ["-c", script], "env": {}},
8686                }),
8687            ))
8688            .await;
8689        assert_eq!(started["result"]["connection"], "runtime-1");
8690        assert_eq!(started["result"]["handle"]["runtime_id"], "svc_acp");
8691
8692        let terminal = service
8693            .handle_async(request(
8694                9,
8695                "harness.v1.runtimes.terminal_instructions",
8696                json!({"connection":"runtime-1"}),
8697            ))
8698            .await;
8699        let arguments = terminal["result"]["launch"]["arguments"]
8700            .as_array()
8701            .expect("hosted runtime should return terminal arguments");
8702        let endpoint_index = arguments
8703            .iter()
8704            .position(|value| value == "--endpoint")
8705            .expect("terminal command should use an opaque endpoint");
8706        let endpoint = LiveRuntimeEndpoint::parse(
8707            arguments[endpoint_index + 1]
8708                .as_str()
8709                .expect("endpoint argument should be text"),
8710        )
8711        .unwrap();
8712        assert!(!terminal.to_string().contains("Bearer"));
8713        let workspace = std::env::current_dir().unwrap();
8714        let receipt = resolve_live_runtime(
8715            &endpoint,
8716            &LiveRuntimeSource {
8717                harness: "codex".into(),
8718                session_id: "svc_acp".into(),
8719                workspace,
8720            },
8721        )
8722        .unwrap();
8723        let remote = crate::HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
8724            .await
8725            .unwrap();
8726        let mut attachment = crate::FrontendRuntime::attach(remote.as_ref(), 100)
8727            .await
8728            .unwrap();
8729
8730        let sent = service
8731            .handle_async(request(
8732                2,
8733                "harness.v1.runtimes.send_input",
8734                json!({"connection": "runtime-1", "text": "hi"}),
8735            ))
8736            .await;
8737        assert_eq!(sent["result"]["turn_id"], "3");
8738
8739        let mut events = Vec::new();
8740        for _ in 0..20 {
8741            events.extend(service.poll_runtimes().await);
8742            if events.len() >= 2 {
8743                break;
8744            }
8745            tokio::time::sleep(Duration::from_millis(2)).await;
8746        }
8747        assert!(events
8748            .iter()
8749            .any(|event| { event["params"]["event"]["kind"] == "session/update" }));
8750        assert!(events.iter().any(|event| {
8751            event["params"]["event"]["kind"] == "supercode/acp_request_completed"
8752        }));
8753
8754        let saw_editor_reply = tokio::time::timeout(Duration::from_secs(2), async {
8755            loop {
8756                let event = attachment.next_event().await.unwrap();
8757                if event.kind == "text_delta" && event.payload["text"] == "ok" {
8758                    break;
8759                }
8760            }
8761        })
8762        .await;
8763        assert!(
8764            saw_editor_reply.is_ok(),
8765            "terminal should observe the editor-driven turn"
8766        );
8767
8768        crate::FrontendRuntime::submit(remote.as_ref(), "DRIVE FROM TERMINAL".into())
8769            .await
8770            .unwrap();
8771        let saw_terminal_reply = tokio::time::timeout(Duration::from_secs(2), async {
8772            loop {
8773                let event = attachment.next_event().await.unwrap();
8774                if event.kind == "text_delta" && event.payload["text"] == "from terminal" {
8775                    break;
8776                }
8777            }
8778        })
8779        .await;
8780        assert!(
8781            saw_terminal_reply.is_ok(),
8782            "terminal should drive the same runtime"
8783        );
8784
8785        let closed = service
8786            .handle_async(request(
8787                3,
8788                "harness.v1.runtimes.close",
8789                json!({"connection": "runtime-1"}),
8790            ))
8791            .await;
8792        assert_eq!(closed["result"]["closed"], true);
8793    }
8794
8795    /// UNI-7 dev/02: a RUNNING mock gateway is detected through the real
8796    /// openclaw probe (config-declared endpoint, TCP connect), and an ACTIVE
8797    /// hermes WAL is detected through the real WAL-freshness probe; the
8798    /// negative sides (no listener, stale WAL, no config) stay undetected.
8799    #[test]
8800    fn running_instances_are_detected_from_mock_gateway_and_active_wal() {
8801        let home = connect_scratch_home("uni7-running");
8802
8803        // No config at all: hermes has no default endpoint, so no detection.
8804        // (openclaw's no-config behavior now probes its DOCUMENTED default
8805        // endpoint ws://127.0.0.1:18789 — see the connect launch's
8806        // `default_address` — which is real box state a hermetic test must
8807        // not assert either way; the closed-port negative below covers the
8808        // no-listener side deterministically.)
8809        assert!(probe_hermes_running(&home, 300_000).is_none());
8810
8811        // Mock gateway: a real TCP listener on an ephemeral port, declared in
8812        // the harness's own config file.
8813        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
8814        let port = listener.local_addr().unwrap().port();
8815        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
8816        std::fs::write(
8817            home.join(".openclaw/openclaw.json"),
8818            format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
8819        )
8820        .unwrap();
8821        let running = probe_openclaw_running(&home).expect("listening gateway must be detected");
8822        assert!(matches!(
8823            running.method,
8824            RunningInstanceMethod::GatewayConnect
8825        ));
8826        assert!(running.evidence.contains(&format!("127.0.0.1:{port}")));
8827        drop(listener);
8828        // Parallel tests also bind ephemeral loopback ports, so a just-freed
8829        // port can be re-bound by a NEIGHBORING test between drop and probe.
8830        // Detection on a closed port must fail — retry on a fresh port when
8831        // the freed one was recycled by someone else.
8832        let mut closed_detected = probe_openclaw_running(&home).is_some();
8833        for _ in 0..3 {
8834            if !closed_detected {
8835                break;
8836            }
8837            let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
8838            let port = listener.local_addr().unwrap().port();
8839            drop(listener);
8840            std::fs::write(
8841                home.join(".openclaw/openclaw.json"),
8842                format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
8843            )
8844            .unwrap();
8845            closed_detected = probe_openclaw_running(&home).is_some();
8846        }
8847        assert!(
8848            !closed_detected,
8849            "a closed gateway must not read as running"
8850        );
8851
8852        // gateway.url form takes precedence over port.
8853        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
8854        let port = listener.local_addr().unwrap().port();
8855        std::fs::write(
8856            home.join(".openclaw/openclaw.json"),
8857            format!(r#"{{"gateway": {{"url": "ws://127.0.0.1:{port}", "auth": {{"mode": "token", "token": "t"}}}}}}"#),
8858        )
8859        .unwrap();
8860        assert!(probe_openclaw_running(&home).is_some());
8861        drop(listener);
8862
8863        // Hermes: an ACTIVE WAL (fresh stamp) is detected; a stale one is not.
8864        std::fs::create_dir_all(home.join(".hermes")).unwrap();
8865        let wal = home.join(".hermes/state.db-wal");
8866        std::fs::write(&wal, b"wal").unwrap();
8867        let running = probe_hermes_running(&home, 300_000).expect("fresh WAL must be detected");
8868        assert!(matches!(
8869            running.method,
8870            RunningInstanceMethod::StoreWalActivity
8871        ));
8872        assert!(running.evidence.contains("state.db-wal"));
8873        let stale = std::time::SystemTime::now() - std::time::Duration::from_secs(3_600);
8874        std::fs::File::options()
8875            .append(true)
8876            .open(&wal)
8877            .unwrap()
8878            .set_modified(stale)
8879            .unwrap();
8880        assert!(
8881            probe_hermes_running(&home, 300_000).is_none(),
8882            "a stale WAL (crash leftover) must not read as running"
8883        );
8884    }
8885
8886    fn connect_scratch_home(tag: &str) -> PathBuf {
8887        let dir = std::env::temp_dir().join(format!(
8888            "supercode-connect-service-{tag}-{}-{}",
8889            std::process::id(),
8890            std::time::SystemTime::now()
8891                .duration_since(std::time::UNIX_EPOCH)
8892                .unwrap()
8893                .as_nanos()
8894        ));
8895        std::fs::create_dir_all(&dir).unwrap();
8896        dir
8897    }
8898
8899    /// Minimal HTTP responder that speaks just enough OpenCode server to
8900    /// accept a health check, create a session, and hold an SSE stream open,
8901    /// while recording each request line with its Authorization header.
8902    async fn mock_opencode_endpoint() -> (String, tokio::sync::mpsc::UnboundedReceiver<String>) {
8903        use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
8904        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
8905        let address = listener.local_addr().unwrap();
8906        let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel();
8907        tokio::spawn(async move {
8908            loop {
8909                let Ok((mut stream, _)) = listener.accept().await else {
8910                    break;
8911                };
8912                let request_sender = request_sender.clone();
8913                tokio::spawn(async move {
8914                    let (reader, mut writer) = stream.split();
8915                    let mut reader = BufReader::new(reader);
8916                    let mut request_line = String::new();
8917                    if reader.read_line(&mut request_line).await.unwrap_or(0) == 0 {
8918                        return;
8919                    }
8920                    let request_line = request_line.trim_end().to_string();
8921                    let mut authorization = String::new();
8922                    let mut content_length = 0usize;
8923                    loop {
8924                        let mut line = String::new();
8925                        if reader.read_line(&mut line).await.unwrap_or(0) == 0 {
8926                            return;
8927                        }
8928                        let line = line.trim_end();
8929                        if line.is_empty() {
8930                            break;
8931                        }
8932                        let lower = line.to_ascii_lowercase();
8933                        if let Some(value) = lower.strip_prefix("authorization:") {
8934                            authorization = value.trim().to_string();
8935                        }
8936                        if let Some(value) = lower.strip_prefix("content-length:") {
8937                            content_length = value.trim().parse().unwrap_or(0);
8938                        }
8939                    }
8940                    if content_length > 0 {
8941                        let mut body = vec![0u8; content_length];
8942                        let _ = reader.read_exact(&mut body).await;
8943                    }
8944                    let _ = request_sender.send(format!("{request_line} :: {authorization}"));
8945                    if request_line.starts_with("GET /event") {
8946                        let _ = writer
8947                            .write_all(
8948                                b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n",
8949                            )
8950                            .await;
8951                        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
8952                        return;
8953                    }
8954                    let body = if request_line.starts_with("POST /session") {
8955                        r#"{"id":"mock-session"}"#
8956                    } else {
8957                        r#"{"status":"ok"}"#
8958                    };
8959                    let response = format!(
8960                        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
8961                        body.len(),
8962                        body
8963                    );
8964                    let _ = writer.write_all(response.as_bytes()).await;
8965                });
8966            }
8967        });
8968        (format!("http://{address}"), request_receiver)
8969    }
8970
8971    fn connect_descriptor(protocol: &str) -> crate::HarnessSupportDescriptor {
8972        crate::HarnessSupportDescriptor {
8973            orchestration: Default::default(),
8974            id: HarnessId::from(HarnessId::OPENCODE),
8975            display_name: "OpenCode".into(),
8976            native: crate::NativeSupport {
8977                discover: crate::ImplementationKind::Absent,
8978                load: crate::ImplementationKind::Absent,
8979                follow: crate::ImplementationKind::Absent,
8980                import: crate::ImplementationKind::Absent,
8981                export: crate::ImplementationKind::Absent,
8982            },
8983            runtime: crate::RuntimeSupport {
8984                implementation: crate::ImplementationKind::BuiltIn,
8985                protocol: protocol.into(),
8986                default_launch: None,
8987                connect_launch: Some(crate::RuntimeConnectLaunch {
8988                    config_path: "~/opencode-tui.json".into(),
8989                    address_pointer: "/server/url".into(),
8990                    port_pointer: None,
8991                    default_address: None,
8992                    auth_pointer: Some("/server/token".into()),
8993                    protocol: protocol.into(),
8994                }),
8995                capabilities: crate::RuntimeCapabilities {
8996                    start_session: true,
8997                    resume_session: true,
8998                    attach_existing_process: true,
8999                    send_input: true,
9000                    stream_events: true,
9001                    interrupt: true,
9002                    steer: false,
9003                    respond_to_requests: true,
9004                },
9005            },
9006        }
9007    }
9008
9009    #[tokio::test]
9010    async fn connect_mode_descriptor_opens_a_running_endpoint_with_config_sourced_auth() {
9011        let (base_url, mut requests) = mock_opencode_endpoint().await;
9012        let home = connect_scratch_home("open");
9013        std::fs::write(
9014            home.join("opencode-tui.json"),
9015            format!(r#"{{"server": {{"url": "{base_url}", "token": "connect-secret"}}}}"#),
9016        )
9017        .unwrap();
9018
9019        let descriptor = connect_descriptor("opencode-http-sse");
9020        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
9021        assert!(backend.capabilities().attach_existing_process);
9022
9023        let connection = backend
9024            .start(crate::RuntimeStartRequest {
9025                cwd: home.clone(),
9026                launch: None,
9027                mcp_servers: Vec::new(),
9028            })
9029            .await
9030            .unwrap();
9031        let handle = connection.handle();
9032        assert_eq!(handle.runtime_id, "mock-session");
9033        match &handle.endpoint {
9034            crate::RuntimeEndpoint::Http {
9035                base_url: endpoint, ..
9036            } => assert_eq!(endpoint, &base_url),
9037            other => panic!("connect mode must join the running endpoint, got {other:?}"),
9038        }
9039
9040        let mut seen = Vec::new();
9041        while let Ok(line) = requests.try_recv() {
9042            seen.push(line);
9043        }
9044        assert!(seen
9045            .iter()
9046            .any(|line| line.starts_with("GET /global/health")
9047                && line.contains("bearer connect-secret")));
9048        assert!(seen.iter().any(
9049            |line| line.starts_with("POST /session") && line.contains("bearer connect-secret")
9050        ));
9051    }
9052
9053    /// UNI-5 dev/02, contract corrected by the 2026-08-31 blind walk: the
9054    /// full connect-mode attach path against a MOCK gateway bridge — no live
9055    /// gateway, no model spend. A scripted fake `openclaw` binary (a)
9056    /// asserts the REAL bridge contract — the resolved --url on argv and the
9057    /// credential via --token-file (the real bridge ignores the env var; the
9058    /// endpoint comes from openclaw-native `gateway.remote.url`, never the
9059    /// schema-invalid `gateway.url`) — then (b) speaks scripted ACP:
9060    /// initialize advertising sessionCapabilities.{list,resume},
9061    /// session/resume rebinding the requested session (join), and a
9062    /// prompted turn.
9063    #[tokio::test]
9064    async fn openclaw_connect_mode_attaches_lists_and_resumes_via_a_mock_bridge() {
9065        let home = connect_scratch_home("openclaw");
9066        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9067        std::fs::write(
9068            home.join(".openclaw/openclaw.json"),
9069            r#"{"gateway": {"remote": {"url": "ws://127.0.0.1:19789"}, "auth": {"mode": "token", "token": "mock-gateway-token"}}}"#,
9070        )
9071        .unwrap();
9072        let script = home.join("openclaw");
9073        std::fs::write(
9074            &script,
9075            r#"#!/bin/sh
9076# Fake `openclaw acp` bridge: verify the connect-mode contract, then speak ACP.
9077[ "$1" = "acp" ] || { echo "unexpected argv: $*" >&2; exit 9; }
9078[ "$2" = "--url" ] && [ "$3" = "ws://127.0.0.1:19789" ] || { echo "missing --url: $*" >&2; exit 9; }
9079[ "$4" = "--token-file" ] || { echo "missing --token-file: $*" >&2; exit 9; }
9080[ "$(cat "$5")" = "mock-gateway-token" ] || { echo "token file wrong" >&2; exit 9; }
9081while IFS= read -r line; do
9082  case "$line" in
9083    *'"initialize"'*)
9084      printf '%s
9085' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"sessionCapabilities":{"list":{},"resume":{}}},"agentInfo":{"name":"openclaw-acp","version":"2026.7.1-2"},"authMethods":[]}}' ;;
9086    *'"session/resume"'*)
9087      printf '%s
9088' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:main"}}' ;;
9089    *'"session/new"'*)
9090      printf '%s
9091' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:fresh"}}' ;;
9092    *'"session/prompt"'*)
9093      printf '%s
9094' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"agent:main:main","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"joined"}}}}'
9095      printf '%s
9096' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}' ;;
9097  esac
9098done
9099"#,
9100        )
9101        .unwrap();
9102        use std::os::unix::fs::PermissionsExt;
9103        std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
9104
9105        let mut descriptor = crate::harness_support_registry()
9106            .harnesses
9107            .into_iter()
9108            .find(|harness| harness.id.as_str() == HarnessId::OPENCLAW)
9109            .expect("openclaw must be registered");
9110        descriptor
9111            .runtime
9112            .connect_launch
9113            .as_mut()
9114            .unwrap()
9115            .config_path = "~/.openclaw/openclaw.json".into();
9116        descriptor.runtime.default_launch.as_mut().unwrap().program =
9117            script.to_string_lossy().into_owned();
9118        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
9119        assert!(backend.capabilities().resume_session);
9120
9121        let joined = backend
9122            .attach(crate::RuntimeAttachRequest {
9123                runtime_id: "agent:main:main".into(),
9124                cwd: Some(home.clone()),
9125                launch: None,
9126            })
9127            .await;
9128        let mut connection = joined.expect("mock bridge attach must succeed");
9129        assert_eq!(connection.handle().runtime_id, "agent:main:main");
9130        let turn = connection
9131            .send_input(crate::RuntimeInput {
9132                text: "hello".into(),
9133                image_urls: Vec::new(),
9134            })
9135            .await;
9136        assert!(turn.is_ok(), "prompt through the mock bridge: {turn:?}");
9137        connection.close().await.unwrap();
9138    }
9139
9140    #[tokio::test]
9141    async fn connect_mode_fails_closed_without_a_protocol_client_or_config() {
9142        let home = connect_scratch_home("fail");
9143        std::fs::write(
9144            home.join("opencode-tui.json"),
9145            r#"{"server": {"url": "http://127.0.0.1:1", "token": "connect-secret"}}"#,
9146        )
9147        .unwrap();
9148
9149        let gateway_only = connect_descriptor("acp-v1-jsonrpc");
9150        let Err(error) = open_connect_descriptor(&gateway_only, &home) else {
9151            panic!("an ACP connect endpoint has no gateway client yet");
9152        };
9153        let message = format!("{error:?}");
9154        assert!(message.contains("acp-v1-jsonrpc"));
9155        assert!(!message.contains("connect-secret"));
9156
9157        let unreadable = connect_descriptor("opencode-http-sse");
9158        let missing_home = connect_scratch_home("missing");
9159        let Err(error) = open_connect_descriptor(&unreadable, &missing_home) else {
9160            panic!("an unreadable connect config must fail closed");
9161        };
9162        let message = format!("{error:?}");
9163        assert!(message.contains("opencode-tui.json"));
9164        assert!(!message.contains("connect-secret"));
9165    }
9166
9167    // ---------------------------------------------------------------------
9168    // ORCH-7 — `harness.v1.jobs.list` / `jobs.get` over the committed fixtures
9169    // ---------------------------------------------------------------------
9170
9171    fn jobs_fixture_root() -> PathBuf {
9172        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
9173    }
9174
9175    /// Point only the three job-bearing homes at the fixtures. Nothing else is
9176    /// read, so the host machine's own harness homes cannot leak into a row.
9177    fn jobs_fixture_homes() -> Value {
9178        let root = jobs_fixture_root();
9179        json!({
9180            "claude_code": root.join("claude_jobs_home/projects"),
9181            "hermes": root.join("hermes_home/state.db"),
9182            "openclaw": root.join("openclaw_home"),
9183        })
9184    }
9185
9186    fn jobs_list(params: Value) -> Value {
9187        let mut service = HarnessSessionService::new();
9188        service.handle(request(1, "harness.v1.jobs.list", params))
9189    }
9190
9191    fn job_row<'a>(result: &'a Value, id: &str) -> &'a Value {
9192        result["jobs"]
9193            .as_array()
9194            .expect("jobs is an array")
9195            .iter()
9196            .find(|job| job["id"] == id)
9197            .unwrap_or_else(|| panic!("no job `{id}` in {result}"))
9198    }
9199
9200    #[test]
9201    fn gateway_health_derives_from_running_probe_and_install_state() {
9202        let running = RunningInstance {
9203            method: RunningInstanceMethod::GatewayConnect,
9204            evidence: "gateway endpoint 127.0.0.1:18789 accepted a TCP connect".into(),
9205            checked_at_ms: 1,
9206        };
9207        let up = gateway_health(
9208            HarnessId::OPENCLAW,
9209            true,
9210            Some(&running),
9211            Some("2026.7.1-2"),
9212        );
9213        assert_eq!(up.state, GatewayState::Up);
9214        assert!(up.endpoint.as_deref().unwrap().starts_with("ws://"));
9215        assert_eq!(up.version.as_deref(), Some("2026.7.1-2"));
9216        // Hermes consults its own `gateway status` when the WAL heuristic says
9217        // nothing; a fake binary decides the verdict (the env var is global, so
9218        // the up/down cases run inside this one test, never in parallel).
9219        let dir = std::env::temp_dir().join(format!("supercode-orch17-{}", std::process::id()));
9220        std::fs::create_dir_all(&dir).unwrap();
9221        let fake = dir.join("hermes");
9222        let write_fake = |body: &str| {
9223            std::fs::write(&fake, format!("#!/bin/sh\n{body}\n")).unwrap();
9224            #[cfg(unix)]
9225            {
9226                use std::os::unix::fs::PermissionsExt;
9227                std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o755)).unwrap();
9228            }
9229        };
9230        write_fake("echo '✗ Gateway service is not installed'");
9231        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| {
9232            *slot.borrow_mut() = Some((
9233                HarnessId::HERMES.to_string(),
9234                fake.to_string_lossy().into_owned(),
9235            ))
9236        });
9237        let down = gateway_health(HarnessId::HERMES, true, None, None);
9238        assert_eq!(down.state, GatewayState::Down, "{down:?}");
9239        assert!(down.endpoint.is_none());
9240        assert!(down.evidence.contains("not installed"));
9241        write_fake("echo 'Launchd plist: /x/ai.hermes.gateway.plist'; echo '✓ Gateway is supervised by launchd (PID 4242)'");
9242        let idle_but_up = gateway_health(HarnessId::HERMES, true, None, Some("0.21.0"));
9243        assert_eq!(idle_but_up.state, GatewayState::Up, "{idle_but_up:?}");
9244        assert!(idle_but_up.evidence.contains("PID 4242"));
9245        write_fake("echo 'something unparseable'");
9246        let no_verdict = gateway_health(HarnessId::HERMES, true, None, None);
9247        assert_eq!(no_verdict.state, GatewayState::Down);
9248        assert!(no_verdict.evidence.contains("no verdict"));
9249        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| *slot.borrow_mut() = None);
9250        let absent = gateway_health(HarnessId::HERMES, false, None, None);
9251        assert_eq!(absent.state, GatewayState::Unknown);
9252        let core = gateway_health(HarnessId::CODEX, true, None, Some("0.144.4"));
9253        assert_eq!(core.state, GatewayState::Unknown);
9254        assert!(core.evidence.contains("per session"));
9255    }
9256
9257    #[test]
9258    fn triggers_list_reads_both_stores_and_never_emits_secrets() {
9259        let response = triggers_list(json!({"homes": jobs_fixture_homes()}));
9260        let rows = response["result"]["triggers"]
9261            .as_array()
9262            .expect("triggers")
9263            .clone();
9264        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
9265        assert!(
9266            hermes.iter().any(|r| r["name"] == "deploys"
9267                && r["route"] == "/webhooks/deploys"
9268                && r["kind"] == "webhook"),
9269            "{rows:#?}"
9270        );
9271        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
9272        assert!(openclaw
9273            .iter()
9274            .any(|r| r["name"] == "wake" && r["kind"] == "builtin_wake"));
9275        assert!(openclaw.iter().any(|r| r["name"] == "gmail"
9276            && r["kind"] == "hook_mapping"
9277            && r["target"]["action"] == "agent"));
9278        let rendered = response.to_string();
9279        for secret in [
9280            "FAKE-WEBHOOK-HMAC-DO-NOT-EMIT",
9281            "FAKE-HOOK-TOKEN-DO-NOT-EMIT",
9282        ] {
9283            assert!(!rendered.contains(secret), "{rendered}");
9284        }
9285        let refused =
9286            triggers_list(json!({"harness": "claude-code", "homes": jobs_fixture_homes()}));
9287        assert_eq!(refused["error"]["code"], -32020, "{refused}");
9288    }
9289
9290    fn triggers_list(params: Value) -> Value {
9291        let mut service = HarnessSessionService::new();
9292        service.handle(request(1, "harness.v1.triggers.list", params))
9293    }
9294
9295    #[test]
9296    fn routes_list_reads_both_gateway_configs_and_flags_the_defaults() {
9297        let response = routes_list(json!({"homes": jobs_fixture_homes()}));
9298        let rows = response["result"]["routes"]
9299            .as_array()
9300            .expect("routes")
9301            .clone();
9302        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
9303        assert_eq!(hermes.len(), 2, "{rows:#?}");
9304        assert_eq!(hermes[0]["target"], "coder");
9305        assert_eq!(hermes[0]["match"]["platform"], "slack");
9306        assert_eq!(hermes[0]["match"]["chat_id"], "C0FIXTURE");
9307        assert_eq!(hermes[0]["specificity"], 4);
9308        assert_eq!(hermes[1]["default"], true);
9309        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
9310        assert!(
9311            openclaw.iter().any(|r| r["target"] == "design"
9312                && r["match"]["platform"] == "slack"
9313                && r["specificity"] == 1),
9314            "{openclaw:#?}"
9315        );
9316        assert!(openclaw.iter().any(|r| r["default"] == true));
9317        // A core harness has no routing concept and is refused, never an empty list.
9318        let refused = routes_list(json!({"harness": "codex", "homes": jobs_fixture_homes()}));
9319        assert_eq!(refused["error"]["code"], -32020, "{refused}");
9320    }
9321
9322    fn routes_list(params: Value) -> Value {
9323        let mut service = HarnessSessionService::new();
9324        service.handle(request(1, "harness.v1.routes.list", params))
9325    }
9326
9327    #[test]
9328    fn jobs_list_projects_every_fixture_store_onto_the_uniform_row() {
9329        let response = jobs_list(json!({"homes": jobs_fixture_homes()}));
9330        let result = &response["result"];
9331        let ids: Vec<&str> = result["jobs"]
9332            .as_array()
9333            .unwrap()
9334            .iter()
9335            .map(|job| job["id"].as_str().unwrap())
9336            .collect();
9337        assert_eq!(
9338            ids,
9339            vec![
9340                "release-watch",
9341                "toolu_wake_recheck",
9342                "digest-15m",
9343                "nightly-audit",
9344                "coder-standup",
9345                "ops-once-boot",
9346                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
9347                "8bb7d938-ca46-4a6d-90eb-c92331155566",
9348                "cron_standup",
9349                "cron_reindex",
9350            ],
9351            "{result}"
9352        );
9353
9354        // OpenClaw, pinned shape: rows come from `state/openclaw.sqlite`
9355        // (`cron_jobs.job_json` + runtime columns), captured from a real
9356        // 2026.7.1-2 gateway.
9357        let health = job_row(result, "85ad7832-896f-42be-af31-3e1ed2fbdc4b");
9358        assert_eq!(health["harness"], "openclaw");
9359        assert_eq!(health["schedule"]["kind"], "interval");
9360        assert_eq!(health["schedule"]["minutes"], 10.0);
9361        assert_eq!(health["session_target"], "isolated");
9362        assert_eq!(health["payload"]["kind"], "prompt");
9363        assert_eq!(health["payload"]["text"], "nightly health check");
9364        // ORCH-13: the mode word (`announce`) and the channel it announces on
9365        // (`last`) are separate facts, and the store keeps both — in
9366        // `job_json.delivery` and in the `delivery_*` columns beside it.
9367        assert_eq!(health["deliver"]["mode"], "announce");
9368        assert_eq!(health["deliver"]["target"], "last");
9369        assert_eq!(health["next_run_at"], "2026-09-03T06:52:26Z");
9370        let digest = job_row(result, "8bb7d938-ca46-4a6d-90eb-c92331155566");
9371        assert_eq!(digest["schedule"]["kind"], "cron");
9372        assert_eq!(digest["schedule"]["expr"], "0 9 * * 1");
9373        assert_eq!(digest["session_target"], "main");
9374        assert_eq!(digest["payload"]["kind"], "system_event");
9375
9376        // Claude Code: session-scoped, one recurring cron and one one-shot wakeup.
9377        let cron = job_row(result, "release-watch");
9378        assert_eq!(cron["harness"], "claude-code");
9379        assert_eq!(cron["scope"], "session");
9380        assert_eq!(cron["session_id"], "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
9381        assert_eq!(cron["schedule"]["kind"], "cron");
9382        assert_eq!(cron["schedule"]["expr"], "*/10 * * * *");
9383        assert_eq!(cron["schedule"]["display"], "*/10 * * * *");
9384        assert_eq!(cron["payload"]["kind"], "prompt");
9385        assert_eq!(cron["recurring"], true);
9386        assert_eq!(cron["deliver"]["target"], "session");
9387        let wakeup = job_row(result, "toolu_wake_recheck");
9388        assert_eq!(wakeup["payload"]["kind"], "wakeup");
9389        assert_eq!(wakeup["schedule"]["kind"], "once");
9390        assert_eq!(wakeup["recurring"], false);
9391        assert_eq!(wakeup["state"], "pending");
9392
9393        // Hermes: install-scoped, interval + origin delivery, and a paused cron.
9394        let interval = job_row(result, "digest-15m");
9395        assert_eq!(interval["harness"], "hermes");
9396        assert_eq!(interval["scope"], "install");
9397        assert_eq!(interval["profile"], Value::Null);
9398        assert_eq!(interval["schedule"]["kind"], "interval");
9399        assert_eq!(interval["schedule"]["minutes"], 15.0);
9400        assert_eq!(interval["schedule"]["display"], "every 15 min");
9401        assert_eq!(interval["deliver"]["target"], "origin");
9402        assert_eq!(interval["deliver"]["chat_id"], "-1002233445566");
9403        assert_eq!(interval["next_run_at"], "2026-09-02T11:15:00Z");
9404        assert_eq!(interval["last_status"], "ok");
9405        let nightly = job_row(result, "nightly-audit");
9406        assert_eq!(nightly["schedule"]["expr"], "0 3 * * *");
9407        assert_eq!(nightly["deliver"]["target"], "local");
9408        assert_eq!(nightly["enabled"], false);
9409        assert_eq!(nightly["state"], "paused");
9410        // The per-profile store carries the profile name from its own path.
9411        let profiled = job_row(result, "ops-once-boot");
9412        assert_eq!(profiled["profile"], "ops");
9413        assert_eq!(profiled["schedule"]["kind"], "once");
9414        assert_eq!(profiled["schedule"]["run_at"], "2026-09-03T06:00:00Z");
9415        assert_eq!(profiled["payload"]["kind"], "script");
9416        // An explicit `<platform>:<chat>` target carries the chat itself.
9417        assert_eq!(profiled["deliver"]["target"], "slack:C0429ABCD");
9418        assert_eq!(profiled["deliver"]["chat_id"], "C0429ABCD");
9419        assert_eq!(profiled["recurring"], false);
9420
9421        // ORCH-13: a job delivering to its creating conversation carries that
9422        // conversation's whole surface — platform word, chat AND thread.
9423        let standup_to_group = job_row(result, "coder-standup");
9424        assert_eq!(standup_to_group["deliver"]["target"], "origin");
9425        assert_eq!(standup_to_group["deliver"]["chat_id"], "-100777");
9426        assert_eq!(standup_to_group["deliver"]["thread_id"], "55");
9427        // Hermes has no mode word and routes by adapter profile, not account.
9428        assert!(standup_to_group["deliver"]["mode"].is_null());
9429        assert!(standup_to_group["deliver"]["account"].is_null());
9430
9431        // OpenClaw: the session target and the delivery mode are the row's own
9432        // columns, not a footnote.
9433        let standup = job_row(result, "cron_standup");
9434        assert_eq!(standup["harness"], "openclaw");
9435        assert_eq!(standup["session_target"], "isolated");
9436        assert_eq!(standup["deliver"]["mode"], "announce");
9437        assert_eq!(standup["deliver"]["target"], "slack");
9438        assert_eq!(standup["deliver"]["chat_id"], "C0429ABCD");
9439        assert_eq!(standup["payload"]["kind"], "prompt");
9440        assert_eq!(standup["profile"], "main");
9441        let reindex = job_row(result, "cron_reindex");
9442        assert_eq!(reindex["session_target"], "main");
9443        assert_eq!(reindex["payload"]["kind"], "system_event");
9444        assert_eq!(reindex["schedule"]["kind"], "interval");
9445        assert_eq!(reindex["schedule"]["display"], "every 240 min");
9446        assert_eq!(reindex["enabled"], false);
9447
9448        // Every store consulted is named, so an empty answer is never silent.
9449        let states: Vec<(&str, &str)> = result["sources"]
9450            .as_array()
9451            .unwrap()
9452            .iter()
9453            .map(|source| {
9454                (
9455                    source["harness"].as_str().unwrap(),
9456                    source["state"].as_str().unwrap(),
9457                )
9458            })
9459            .collect();
9460        // The `coder` profile home has no cron store at all: it is named as
9461        // `absent_store`, not skipped, so "this profile schedules nothing" and
9462        // "this profile was never looked at" stay distinguishable.
9463        assert_eq!(
9464            states,
9465            vec![
9466                ("claude-code", "scanned"),
9467                ("hermes", "read"),
9468                ("hermes", "absent_store"),
9469                ("hermes", "read"),
9470                ("openclaw", "read"),
9471                ("openclaw", "read"),
9472            ],
9473            "{result}"
9474        );
9475    }
9476
9477    #[test]
9478    fn jobs_list_filters_by_harness_session_and_profile() {
9479        let by_harness = jobs_list(json!({"harness": "openclaw", "homes": jobs_fixture_homes()}));
9480        let ids: Vec<&str> = by_harness["result"]["jobs"]
9481            .as_array()
9482            .unwrap()
9483            .iter()
9484            .map(|job| job["id"].as_str().unwrap())
9485            .collect();
9486        assert_eq!(
9487            ids,
9488            vec![
9489                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
9490                "8bb7d938-ca46-4a6d-90eb-c92331155566",
9491                "cron_standup",
9492                "cron_reindex",
9493            ]
9494        );
9495
9496        let by_session = jobs_list(json!({
9497            "session": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
9498            "homes": jobs_fixture_homes(),
9499        }));
9500        let jobs = by_session["result"]["jobs"].as_array().unwrap();
9501        assert_eq!(jobs.len(), 2, "{by_session}");
9502        assert!(jobs
9503            .iter()
9504            .all(|job| job["harness"] == "claude-code" && job["scope"] == "session"));
9505
9506        let by_profile = jobs_list(json!({
9507            "harness": "hermes",
9508            "profile": "ops",
9509            "homes": jobs_fixture_homes(),
9510        }));
9511        let jobs = by_profile["result"]["jobs"].as_array().unwrap();
9512        assert_eq!(jobs.len(), 1, "{by_profile}");
9513        assert_eq!(jobs[0]["id"], "ops-once-boot");
9514    }
9515
9516    #[test]
9517    fn jobs_get_answers_with_the_row_and_the_verbatim_native_record() {
9518        let mut service = HarnessSessionService::new();
9519        let hermes = service.handle(request(
9520            1,
9521            "harness.v1.jobs.get",
9522            json!({"harness": "hermes", "id": "digest-15m", "homes": jobs_fixture_homes()}),
9523        ));
9524        assert_eq!(hermes["result"]["job"]["schedule"]["kind"], "interval");
9525        // Native fields the uniform row does not carry survive on `source`.
9526        assert_eq!(hermes["result"]["source"]["provider"], "nous");
9527        assert_eq!(hermes["result"]["source"]["failure_deliver"], "local");
9528
9529        let claude = service.handle(request(
9530            2,
9531            "harness.v1.jobs.get",
9532            json!({"harness": "claude-code", "id": "release-watch", "homes": jobs_fixture_homes()}),
9533        ));
9534        assert_eq!(claude["result"]["job"]["payload"]["kind"], "prompt");
9535        assert_eq!(
9536            claude["result"]["source"]["tool_use_id"],
9537            "toolu_cron_release_watch"
9538        );
9539
9540        let missing = service.handle(request(
9541            3,
9542            "harness.v1.jobs.get",
9543            json!({"harness": "hermes", "id": "no-such-job", "homes": jobs_fixture_homes()}),
9544        ));
9545        assert!(missing["error"]["message"]
9546            .as_str()
9547            .is_some_and(|message| message.contains("no scheduled job `no-such-job`")));
9548    }
9549
9550    #[test]
9551    fn jobs_refuse_a_harness_without_a_scheduled_job_concept() {
9552        let mut service = HarnessSessionService::new();
9553        for (id, method, params) in [
9554            (
9555                1,
9556                "harness.v1.jobs.list",
9557                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
9558            ),
9559            (
9560                2,
9561                "harness.v1.jobs.get",
9562                json!({"harness": "codex", "id": "anything"}),
9563            ),
9564        ] {
9565            let response = service.handle(request(id, method, params));
9566            assert_eq!(response["error"]["code"], -32020, "{response}");
9567            assert!(response["error"]["message"]
9568                .as_str()
9569                .is_some_and(|message| message.contains("has no scheduled jobs")));
9570            assert!(response.get("result").is_none());
9571        }
9572    }
9573
9574    #[test]
9575    fn jobs_list_reports_a_migrated_openclaw_store_as_absent_instead_of_failing() {
9576        let scratch = std::env::temp_dir().join(format!(
9577            "supercode-jobs-migrated-{}-{}",
9578            std::process::id(),
9579            generated_session_id()
9580        ));
9581        std::fs::create_dir_all(&scratch).unwrap();
9582        let response = jobs_list(json!({
9583            "harness": "openclaw",
9584            "homes": {"openclaw": scratch.clone()},
9585        }));
9586        let result = &response["result"];
9587        assert_eq!(result["jobs"].as_array().unwrap().len(), 0, "{result}");
9588        assert_eq!(result["sources"][0]["state"], "absent_store");
9589        assert_eq!(result["sources"][0]["harness"], "openclaw");
9590        std::fs::remove_dir_all(&scratch).ok();
9591    }
9592
9593    // ---------------------------------------------------------------------
9594    // ORCH-8 — `harness.v1.runs.list` / `runs.get` over the committed fire
9595    // stores: Hermes's `cron/executions.db` (root home + profile home) and
9596    // OpenClaw's `cron_run_logs`. Every fixture row is written by
9597    // `tests/fixtures/gen_runs_fixtures.py` against the harnesses' own DDL.
9598    // ---------------------------------------------------------------------
9599
9600    /// The health job in the committed OpenClaw fixture, which fired twice.
9601    const OPENCLAW_HEALTH_JOB: &str = "85ad7832-896f-42be-af31-3e1ed2fbdc4b";
9602    /// The digest job, whose single fire predates run ids.
9603    const OPENCLAW_DIGEST_JOB: &str = "8bb7d938-ca46-4a6d-90eb-c92331155566";
9604
9605    fn runs_list(params: Value) -> Value {
9606        let mut service = HarnessSessionService::new();
9607        service.handle(request(1, "harness.v1.runs.list", params))
9608    }
9609
9610    fn run_row<'a>(result: &'a Value, id: &str) -> &'a Value {
9611        result["runs"]
9612            .as_array()
9613            .expect("runs is an array")
9614            .iter()
9615            .find(|run| run["id"] == id)
9616            .unwrap_or_else(|| panic!("no run `{id}` in {result}"))
9617    }
9618
9619    #[test]
9620    fn runs_list_projects_both_fixture_stores_onto_the_uniform_row() {
9621        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
9622        let result = &response["result"];
9623        let ids: Vec<&str> = result["runs"]
9624            .as_array()
9625            .expect("runs is an array")
9626            .iter()
9627            .map(|run| run["id"].as_str().unwrap())
9628            .collect();
9629        let digest_fire = format!("{OPENCLAW_DIGEST_JOB}#1");
9630        assert_eq!(
9631            ids,
9632            vec![
9633                // Hermes, newest claim first, root ledger then profile ledger.
9634                "b2c3d4e5f60718293a4b5c6d7e8f9012",
9635                "a1b2c3d4e5f60718293a4b5c6d7e8f90",
9636                "c3d4e5f60718293a4b5c6d7e8f901234",
9637                "f60718293a4b5c6d7e8f901234567890",
9638                "e5f60718293a4b5c6d7e8f9012345678",
9639                "d4e5f60718293a4b5c6d7e8f90123456",
9640                // OpenClaw, newest `ts` first.
9641                "run_health_0002",
9642                digest_fire.as_str(),
9643                "run_health_0001",
9644            ],
9645            "{result}"
9646        );
9647
9648        // The harness's OWN outcome word survives; nothing is renamed onto a
9649        // shared vocabulary.
9650        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
9651        assert_eq!(failed["harness"], "hermes");
9652        assert_eq!(failed["job_id"], "job42");
9653        assert_eq!(failed["status"], "failed");
9654        assert_eq!(failed["error"], "provider returned 500 after 3 attempts");
9655        assert_eq!(failed["claimed_at"], "2026-09-02T13:05:00.100442");
9656
9657        // Hermes's `unknown` — an attempt whose owner died before writing a
9658        // terminal state — is a fourth status, not folded into `failed`.
9659        let abandoned = run_row(result, "d4e5f60718293a4b5c6d7e8f90123456");
9660        assert_eq!(abandoned["status"], "unknown");
9661        assert_eq!(abandoned["job_id"], "ops-once-boot");
9662
9663        // An unterminated fire has no finish, and no session is invented.
9664        let running = run_row(result, "c3d4e5f60718293a4b5c6d7e8f901234");
9665        assert_eq!(running["status"], "running");
9666        assert!(running["finished_at"].is_null(), "{running}");
9667        assert!(running["session_id"].is_null(), "{running}");
9668
9669        // OpenClaw records the session on the row itself, and epoch-ms
9670        // timestamps are rendered as RFC 3339.
9671        let ok = run_row(result, "run_health_0001");
9672        assert_eq!(ok["harness"], "openclaw");
9673        assert_eq!(ok["job_id"], OPENCLAW_HEALTH_JOB);
9674        assert_eq!(ok["status"], "ok");
9675        assert_eq!(ok["started_at"], "2026-09-02T08:30:00.000Z");
9676        assert_eq!(ok["finished_at"], "2026-09-02T08:30:30.000Z");
9677        assert_eq!(ok["session_id"], "3dd577ae-a0a3-4b5b-8063-f402be4f5fd4");
9678        // OpenClaw's run log is written once, at finish: there is no claim.
9679        assert!(ok["claimed_at"].is_null(), "{ok}");
9680
9681        // A run-log row with no `run_id` falls back to the store's own
9682        // `(job_id, seq)` key rather than being dropped.
9683        assert_eq!(run_row(result, &digest_fire)["status"], "skipped");
9684
9685        // ORCH-13: a fire whose delivery nothing recorded says so, rather than
9686        // borrowing a neighbouring fire's outcome. Both of these ran on jobs
9687        // that deliver `local` (or have no job record at all), so no
9688        // obligation is addressed to a surface they could match.
9689        for id in [
9690            "b2c3d4e5f60718293a4b5c6d7e8f9012",
9691            "d4e5f60718293a4b5c6d7e8f90123456",
9692        ] {
9693            assert!(run_row(result, id)["delivery"].is_null(), "{id}");
9694        }
9695
9696        // Every store consulted is named, including the profile home that has
9697        // no ledger — an empty history and an absent store are different.
9698        let sources = result["sources"].as_array().unwrap();
9699        let states: Vec<(&str, &str)> = sources
9700            .iter()
9701            .map(|source| {
9702                (
9703                    source["harness"].as_str().unwrap(),
9704                    source["state"].as_str().unwrap(),
9705                )
9706            })
9707            .collect();
9708        assert_eq!(
9709            states,
9710            vec![
9711                ("hermes", "read"),
9712                ("hermes", "absent_store"),
9713                ("hermes", "read"),
9714                ("openclaw", "read"),
9715            ],
9716            "{result}"
9717        );
9718        assert_eq!(sources[2]["profile"], "ops");
9719        assert!(sources[3]["path"]
9720            .as_str()
9721            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
9722    }
9723
9724    #[test]
9725    fn runs_list_joins_a_hermes_fire_to_the_session_it_opened() {
9726        let response = runs_list(json!({
9727            "harness": "hermes",
9728            "job": "job42",
9729            "homes": jobs_fixture_homes(),
9730        }));
9731        let result = &response["result"];
9732        assert_eq!(result["runs"].as_array().unwrap().len(), 2, "{result}");
9733
9734        // Hermes writes NO link from an execution to its session. The fire
9735        // that ran the agent is joined to `cron_job42_<stamp>` because that
9736        // id's instant falls inside its [claimed_at, finished_at] window.
9737        let ran = run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90");
9738        assert_eq!(ran["session_id"], "cron_job42_20260902_120000");
9739
9740        // The later fire failed before opening one. Its window holds no
9741        // session, so the row says so instead of re-using the earlier fire's
9742        // — the join is per-FIRE, not per-job.
9743        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
9744        assert!(failed["session_id"].is_null(), "{failed}");
9745    }
9746
9747    /// ORCH-13: where a fire's output went, read from each harness's own
9748    /// delivery record — Hermes's `delivery_obligations` ledger inside
9749    /// `state.db`, OpenClaw's `delivery_*` run-log columns.
9750    #[test]
9751    fn runs_list_reads_the_delivery_each_harness_recorded_for_a_fire() {
9752        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
9753        let result = &response["result"];
9754
9755        // Hermes: the ledger is the GATEWAY's, keyed by conversation and
9756        // surface, so the fire's own [claimed_at, finished_at] window picks
9757        // the obligation. The fire succeeded and so did the send.
9758        let delivered = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
9759        assert_eq!(delivered["status"], "completed");
9760        assert_eq!(delivered["delivery"]["state"], "delivered");
9761        assert_eq!(delivered["delivery"]["target"], "telegram:-100777:55");
9762        assert_eq!(delivered["delivery"]["attempts"], 1);
9763        assert!(delivered["delivery"]["last_error"].is_null(), "{delivered}");
9764        assert_eq!(
9765            delivered["delivery"]["delivered_at"],
9766            "2026-09-02T09:00:30.400Z"
9767        );
9768
9769        // The next fire of the same job ALSO succeeded — and its output never
9770        // arrived. That is the fact `status` alone cannot carry.
9771        let undelivered = run_row(result, "f60718293a4b5c6d7e8f901234567890");
9772        assert_eq!(undelivered["status"], "completed");
9773        assert_eq!(undelivered["delivery"]["state"], "failed");
9774        assert_eq!(undelivered["delivery"]["attempts"], 3);
9775        assert_eq!(
9776            undelivered["delivery"]["last_error"],
9777            "telegram send failed: Bad Request: chat not found"
9778        );
9779        // Only a delivered obligation carries an instant of delivery; the
9780        // ledger's `updated_at` on a failed row dates the failure.
9781        assert!(
9782            undelivered["delivery"]["delivered_at"].is_null(),
9783            "{undelivered}"
9784        );
9785
9786        // OpenClaw writes the outcome onto the run-log row and declares the
9787        // address on the job, so the row's target is joined from `cron_jobs`.
9788        let announced = run_row(result, "run_health_0001");
9789        assert_eq!(announced["delivery"]["state"], "delivered");
9790        assert_eq!(announced["delivery"]["target"], "last");
9791        // Its run log counts no attempts and stamps no delivered-at.
9792        assert!(announced["delivery"]["attempts"].is_null(), "{announced}");
9793        assert!(
9794            announced["delivery"]["delivered_at"].is_null(),
9795            "{announced}"
9796        );
9797        let refused = run_row(result, "run_health_0002");
9798        assert_eq!(refused["delivery"]["state"], "not-delivered");
9799        assert_eq!(refused["delivery"]["last_error"], "channel_not_found");
9800
9801        // A run-log row with no delivery columns at all recorded no delivery:
9802        // the job's declared target is not evidence that anything was sent.
9803        let skipped = run_row(result, &format!("{OPENCLAW_DIGEST_JOB}#1"));
9804        assert!(skipped["delivery"].is_null(), "{skipped}");
9805    }
9806
9807    /// A Hermes fire whose session carries a `session_key` is matched on that
9808    /// key FIRST — the most specific question the ledger can answer. Proven by
9809    /// moving the obligations off the job's surface on a COPY of the fixture,
9810    /// so only the session-key question can still find them.
9811    #[test]
9812    fn runs_list_matches_a_hermes_obligation_by_the_session_key_first() {
9813        let scratch = std::env::temp_dir().join(format!(
9814            "supercode-runs-delivery-{}-{}",
9815            std::process::id(),
9816            generated_session_id()
9817        ));
9818        std::fs::create_dir_all(scratch.join("cron")).unwrap();
9819        let fixture = jobs_fixture_root().join("hermes_home");
9820        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
9821        for name in ["cron/executions.db", "cron/jobs.json"] {
9822            std::fs::copy(fixture.join(name), scratch.join(name)).unwrap();
9823        }
9824        {
9825            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
9826            // The obligations now sit on a surface no job in this store
9827            // delivers to, so the surface question cannot match them.
9828            connection
9829                .execute(
9830                    "UPDATE delivery_obligations SET platform = 'slack', chat_id = 'C0FALLBACK'",
9831                    [],
9832                )
9833                .unwrap();
9834            // A cron fire that ran inside a keyed conversation: the session
9835            // the window recovers carries `tg-coder-1`'s key.
9836            connection
9837                .execute(
9838                    "INSERT INTO sessions (id, source, session_key, started_at) VALUES \
9839                     ('cron_coder-standup_20260902_090010', 'cron', \
9840                      'agent:coder:telegram:group:-100777:55', 1788339610.0)",
9841                    [],
9842                )
9843                .unwrap();
9844        }
9845        let response = runs_list(json!({
9846            "harness": "hermes",
9847            "job": "coder-standup",
9848            "homes": {"hermes": scratch.join("state.db")},
9849        }));
9850        let result = &response["result"];
9851        let matched = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
9852        assert_eq!(
9853            matched["session_id"], "cron_coder-standup_20260902_090010",
9854            "{result}"
9855        );
9856        assert_eq!(matched["delivery"]["state"], "delivered", "{result}");
9857        assert_eq!(
9858            matched["delivery"]["target"], "slack:C0FALLBACK:55",
9859            "{result}"
9860        );
9861        std::fs::remove_dir_all(&scratch).ok();
9862    }
9863
9864    #[test]
9865    fn runs_list_follows_a_compression_chain_to_the_readable_tip() {
9866        // A fire whose session was compressed mid-run is only readable at the
9867        // continuation, so that is what the row must report. Built on a COPY
9868        // of the committed fixture: no test writes to a fixture or to a real
9869        // harness home.
9870        let scratch = std::env::temp_dir().join(format!(
9871            "supercode-runs-compressed-{}-{}",
9872            std::process::id(),
9873            generated_session_id()
9874        ));
9875        std::fs::create_dir_all(scratch.join("cron")).unwrap();
9876        let fixture = jobs_fixture_root().join("hermes_home");
9877        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
9878        std::fs::copy(
9879            fixture.join("cron/executions.db"),
9880            scratch.join("cron/executions.db"),
9881        )
9882        .unwrap();
9883        {
9884            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
9885            connection
9886                .execute(
9887                    "UPDATE sessions SET end_reason = 'compression' WHERE id = ?1",
9888                    ["cron_job42_20260902_120000"],
9889                )
9890                .unwrap();
9891            connection
9892                .execute(
9893                    "INSERT INTO sessions (id, source, parent_session_id, started_at) \
9894                     VALUES ('job42-after-compaction', 'cron', \
9895                             'cron_job42_20260902_120000', 1788350000.0)",
9896                    [],
9897                )
9898                .unwrap();
9899        }
9900        let response = runs_list(json!({
9901            "harness": "hermes",
9902            "job": "job42",
9903            "homes": {"hermes": scratch.join("state.db")},
9904        }));
9905        let result = &response["result"];
9906        assert_eq!(
9907            run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90")["session_id"],
9908            "job42-after-compaction",
9909            "{result}"
9910        );
9911        std::fs::remove_dir_all(&scratch).ok();
9912    }
9913
9914    #[test]
9915    fn runs_list_filters_by_job_and_caps_by_limit() {
9916        let by_job = runs_list(json!({
9917            "harness": "openclaw",
9918            "job": OPENCLAW_HEALTH_JOB,
9919            "homes": jobs_fixture_homes(),
9920        }));
9921        let ids: Vec<&str> = by_job["result"]["runs"]
9922            .as_array()
9923            .unwrap()
9924            .iter()
9925            .map(|run| run["id"].as_str().unwrap())
9926            .collect();
9927        assert_eq!(ids, vec!["run_health_0002", "run_health_0001"], "{by_job}");
9928
9929        let capped = runs_list(json!({
9930            "harness": "openclaw",
9931            "limit": 1,
9932            "homes": jobs_fixture_homes(),
9933        }));
9934        let runs = capped["result"]["runs"].as_array().unwrap();
9935        assert_eq!(runs.len(), 1, "{capped}");
9936        // Newest first, so the cap keeps the recent fire.
9937        assert_eq!(runs[0]["id"], "run_health_0002");
9938    }
9939
9940    #[test]
9941    fn runs_get_answers_with_the_row_and_the_verbatim_native_record() {
9942        let mut service = HarnessSessionService::new();
9943        let hermes = service.handle(request(
9944            1,
9945            "harness.v1.runs.get",
9946            json!({
9947                "harness": "hermes",
9948                "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
9949                "homes": jobs_fixture_homes(),
9950            }),
9951        ));
9952        assert_eq!(hermes["result"]["run"]["status"], "completed");
9953        assert_eq!(
9954            hermes["result"]["run"]["session_id"],
9955            "cron_job42_20260902_120000"
9956        );
9957        // Ledger columns the uniform row does not carry survive on `source`.
9958        assert_eq!(hermes["result"]["source"]["source"], "scheduler");
9959        assert_eq!(hermes["result"]["source"]["pid"], 4242);
9960        assert_eq!(hermes["result"]["source"]["process_id"], "9f1c2d");
9961
9962        let openclaw = service.handle(request(
9963            2,
9964            "harness.v1.runs.get",
9965            json!({
9966                "harness": "openclaw",
9967                "id": "run_health_0002",
9968                "homes": jobs_fixture_homes(),
9969            }),
9970        ));
9971        assert_eq!(openclaw["result"]["run"]["status"], "error");
9972        // ORCH-13: the run's delivery is projected AND the store's own columns
9973        // stay verbatim on `source`, so nothing about the fire is lost.
9974        assert_eq!(
9975            openclaw["result"]["source"]["delivery_status"],
9976            "not-delivered"
9977        );
9978        assert_eq!(
9979            openclaw["result"]["source"]["delivery_error"],
9980            "channel_not_found"
9981        );
9982        assert_eq!(openclaw["result"]["source"]["delivered"], 0);
9983        assert_eq!(
9984            openclaw["result"]["run"]["delivery"]["state"],
9985            "not-delivered"
9986        );
9987        assert_eq!(
9988            openclaw["result"]["run"]["delivery"]["last_error"],
9989            "channel_not_found"
9990        );
9991
9992        let missing = service.handle(request(
9993            3,
9994            "harness.v1.runs.get",
9995            json!({"harness": "hermes", "id": "no-such-run", "homes": jobs_fixture_homes()}),
9996        ));
9997        assert!(missing["error"]["message"]
9998            .as_str()
9999            .is_some_and(|message| message.contains("no run `no-such-run`")));
10000    }
10001
10002    #[test]
10003    fn runs_refuse_a_harness_that_keeps_no_run_store() {
10004        let mut service = HarnessSessionService::new();
10005        for (id, method, params) in [
10006            // Claude Code HAS scheduled jobs but no fire store: its fires are
10007            // ordinary turns. It must refuse, not answer with an empty list.
10008            (
10009                1,
10010                "harness.v1.runs.list",
10011                json!({"harness": "claude-code", "homes": jobs_fixture_homes()}),
10012            ),
10013            (
10014                2,
10015                "harness.v1.runs.get",
10016                json!({"harness": "claude-code", "id": "anything"}),
10017            ),
10018            (
10019                3,
10020                "harness.v1.runs.list",
10021                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10022            ),
10023        ] {
10024            let response = service.handle(request(id, method, params));
10025            assert_eq!(response["error"]["code"], -32020, "{response}");
10026            assert!(response["error"]["message"]
10027                .as_str()
10028                .is_some_and(|message| message.contains("keeps no run store")));
10029            assert!(response.get("result").is_none());
10030        }
10031    }
10032
10033    #[test]
10034    fn runs_list_reports_an_install_with_no_run_store_as_absent() {
10035        let scratch = std::env::temp_dir().join(format!(
10036            "supercode-runs-empty-{}-{}",
10037            std::process::id(),
10038            generated_session_id()
10039        ));
10040        std::fs::create_dir_all(&scratch).unwrap();
10041        let response = runs_list(json!({
10042            "harness": "openclaw",
10043            "homes": {"openclaw": scratch.clone()},
10044        }));
10045        let result = &response["result"];
10046        assert_eq!(result["runs"].as_array().unwrap().len(), 0, "{result}");
10047        assert_eq!(result["sources"][0]["state"], "absent_store");
10048        assert!(result["sources"][0]["path"]
10049            .as_str()
10050            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10051        std::fs::remove_dir_all(&scratch).ok();
10052    }
10053}