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