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