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