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