Skip to main content

supercode_harness/
harness_service.rs

1//! Versioned, language-neutral service over persisted harness sessions.
2//!
3//! The service is transport-agnostic: [`HarnessSessionService::handle`] accepts
4//! one JSON-RPC value and [`HarnessSessionService::poll`] produces subscription
5//! notifications. The CLI exposes those primitives as NDJSON over stdio.
6
7use std::collections::{BTreeMap, BTreeSet};
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use std::time::Duration;
11
12use serde::{Deserialize, Serialize};
13use serde_json::{json, Value};
14use tokio::sync::Notify;
15
16use crate::runtime::generated_session_id;
17#[cfg(feature = "adapter-api")]
18use crate::runtime::{HostedHarnessConnection, HostedHarnessRuntime};
19use crate::sdk::{
20    discover_session_page, load_session, load_session_with_fidelity, SdkCapabilities, SdkError,
21    SdkErrorCode, SdkEvent, SdkOperation, SdkRequest, SdkRuntimeEvent, SdkService,
22};
23use crate::watch::{bound_session_view, message_json, normalized_session_json};
24use crate::Fidelity;
25#[cfg(feature = "adapter-api")]
26use crate::SupercodeHttpRuntimeBackend;
27use crate::{
28    discover_live_runtime, harness_support_registry, AcpRuntimeBackend, ClaudeCodeRuntimeBackend,
29    CodexRuntimeBackend, DiscoveryQuery, HarnessCatalog, HarnessHomes, HarnessId,
30    ImplementationKind, LiveRuntimeEndpoint, LiveRuntimeSource, OpenCodeRuntimeBackend,
31    PiRuntimeBackend, Role, RuntimeAttachRequest, RuntimeBackend, RuntimeConnection, RuntimeInput,
32    RuntimeLaunch, RuntimeStartRequest, Session, SessionDescriptor, SessionFollower, SessionFormat,
33    SessionLocator, SessionSource,
34};
35use crate::{reduce, tokens};
36#[cfg(feature = "adapter-api")]
37use crate::{register_live_runtime, resolve_live_runtime, LiveRuntimeRegistration};
38
39/// Every JSON-RPC method the harness service dispatches (`harness.v1.capabilities`
40/// reports it; ORCH-4 registry tiers must cite entries of it).
41pub const HARNESS_SERVICE_METHODS: &[&str] = &[
42    "harness.v1.support.report",
43    "harness.v1.harnesses.list",
44    "harness.v1.harnesses.probe",
45    "harness.v1.harnesses.settings",
46    "harness.v1.harnesses.configure",
47    "harness.v1.harnesses.auth.methods",
48    "harness.v1.harnesses.auth.begin",
49    "harness.v1.harnesses.auth.verify",
50    "harness.v1.sessions.discover",
51    "harness.v1.sessions.load",
52    "harness.v1.sessions.follow",
53    "harness.v1.sessions.unfollow",
54    "harness.v1.sessions.activity.subscribe",
55    "harness.v1.sessions.activity.unsubscribe",
56    "harness.v1.sessions.index.subscribe",
57    "harness.v1.sessions.index.resize",
58    "harness.v1.sessions.index.unsubscribe",
59    "harness.v1.sessions.message",
60    "harness.v1.sessions.import",
61    "harness.v1.sessions.export",
62    "harness.v1.sessions.translate",
63    "harness.v1.sessions.reduce",
64    "harness.v1.sessions.branch",
65    "harness.v1.sessions.handoff",
66    "harness.v1.sessions.materialize",
67    "harness.v1.sessions.resume_instructions",
68    "harness.v1.skills.list",
69    "harness.v1.skills.install",
70    "harness.v1.skills.remove",
71    "harness.v1.memory.show",
72    "harness.v1.memory.search",
73    "harness.v1.jobs.list",
74    "harness.v1.jobs.get",
75    "harness.v1.jobs.create",
76    "harness.v1.jobs.update",
77    "harness.v1.jobs.pause",
78    "harness.v1.jobs.resume",
79    "harness.v1.jobs.run",
80    "harness.v1.jobs.delete",
81    "harness.v1.jobs.notepad",
82    "harness.v1.jobs.notepad_set",
83    "harness.v1.jobs.notepad_delete",
84    "harness.v1.sessions.new",
85    "harness.v1.sessions.reset",
86    "harness.v1.sessions.archive",
87    "harness.v1.sessions.delete",
88    "harness.v1.runs.list",
89    "harness.v1.runs.get",
90    "harness.v1.approvals.list",
91    "harness.v1.approvals.resolve",
92    "harness.v1.runtimes.capabilities",
93    "harness.v1.runtimes.start",
94    "harness.v1.runtimes.resume",
95    "harness.v1.runtimes.attach_existing",
96    "harness.v1.runtimes.attach",
97    "harness.v1.runtimes.send_input",
98    "harness.v1.runtimes.interrupt",
99    "harness.v1.runtimes.steer",
100    "harness.v1.runtimes.respond",
101    "harness.v1.runtimes.terminal_instructions",
102    "harness.v1.runtimes.acquire_control",
103    "harness.v1.runtimes.heartbeat",
104    "harness.v1.runtimes.detach",
105    "harness.v1.runtimes.close",
106    "harness.v1.profiles.list",
107    "harness.v1.profiles.get",
108    "harness.v1.profiles.create",
109    "harness.v1.profiles.delete",
110    "harness.v1.channels.list",
111    "harness.v1.routes.list",
112    "harness.v1.triggers.list",
113    "harness.v1.channels.status",
114    "harness.v1.orchestration.load",
115    "harness.v1.orchestration.save",
116    "harness.v1.orchestration.compile",
117    "harness.v1.orchestration.decompile",
118    "harness.v1.orchestration.import",
119    "harness.v1.orchestration.export",
120    "harness.v1.workflow.load",
121];
122
123/// Protocol namespace implemented by this service.
124pub const HARNESS_SERVICE_VERSION: &str = "harness.v1";
125/// Notification method emitted for followed-session changes.
126pub const SESSION_EVENT_METHOD: &str = "harness.v1.sessions.event";
127/// Notification method emitted for normalized session-activity transitions.
128pub const SESSION_ACTIVITY_EVENT_METHOD: &str = "harness.v1.sessions.activity_event";
129/// Notification method emitted for revisioned session-list changes.
130pub const SESSION_INDEX_EVENT_METHOD: &str = "harness.v1.sessions.index_event";
131/// Notification method emitted for live runtime events.
132pub const RUNTIME_EVENT_METHOD: &str = "harness.v1.runtimes.event";
133
134/// Stateful persisted-session service. Each instance owns its follow
135/// subscriptions; discovery and loading remain read-only.
136pub struct HarnessSessionService {
137    catalog: HarnessCatalog,
138    followers: BTreeMap<String, SessionFollower>,
139    followed_sources: BTreeMap<String, FollowedSource>,
140    activity_subscriptions: BTreeMap<String, ActivitySubscription>,
141    index_subscriptions: BTreeMap<String, crate::session_index::SessionIndexSubscription>,
142    index_notifier: Arc<Notify>,
143    #[cfg(feature = "adapter-api")]
144    activity_monitor: crate::session_activity::SessionActivityMonitor,
145    next_subscription: u64,
146    runtimes: BTreeMap<String, Box<dyn RuntimeConnection>>,
147    /// Connections lent to a detached call that is running right now. The
148    /// runtime itself is OUT of `runtimes` for that whole call, and these
149    /// names are how a second caller is told the connection is busy rather
150    /// than unknown.
151    runtimes_in_flight: BTreeSet<String>,
152    terminal_launches: BTreeMap<String, StructuredLaunch>,
153    runtime_sequences: BTreeMap<String, u64>,
154    next_runtime: u64,
155    reduction_store_root: Option<PathBuf>,
156    /// ORCH-9: live permission/approval requests outstanding on the open
157    /// runtime connections above, fed by the same event pump that publishes
158    /// `harness.v1.runtimes.event`.
159    approvals: crate::approvals::ApprovalRegistry,
160    /// ORCH-9: supercode's own queued subagent approvals, when the host that
161    /// owns this service publishes its parent queue here.
162    subagent_approvals: Option<Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>>,
163}
164
165impl Default for HarnessSessionService {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171impl HarnessSessionService {
172    /// Create an empty service instance.
173    pub fn new() -> Self {
174        Self {
175            catalog: HarnessCatalog::new(),
176            followers: BTreeMap::new(),
177            followed_sources: BTreeMap::new(),
178            activity_subscriptions: BTreeMap::new(),
179            index_subscriptions: BTreeMap::new(),
180            index_notifier: Arc::new(Notify::new()),
181            #[cfg(feature = "adapter-api")]
182            activity_monitor: Default::default(),
183            next_subscription: 1,
184            runtimes: BTreeMap::new(),
185            runtimes_in_flight: BTreeSet::new(),
186            terminal_launches: BTreeMap::new(),
187            runtime_sequences: BTreeMap::new(),
188            next_runtime: 1,
189            reduction_store_root: None,
190            approvals: crate::approvals::ApprovalRegistry::new(),
191            subagent_approvals: None,
192        }
193    }
194
195    /// Override the trusted, service-owned store used for durable reduction
196    /// bundles. Embedders and tests use this to keep all writes inside an
197    /// explicitly selected root; the CLI otherwise uses the normal
198    /// `$SUPERCODE_HOME/sessions` location.
199    pub fn with_reduction_store_root(mut self, root: impl Into<PathBuf>) -> Self {
200        self.reduction_store_root = Some(root.into());
201        self
202    }
203
204    /// ORCH-9: publish the parent's own subagent-approval queue into
205    /// `harness.v1.approvals.list`.
206    ///
207    /// This is the SAME `Arc` an [`crate::Agent`] pushes into
208    /// (`Agent::pending_child_approvals`), so a host that runs supercode's own
209    /// loop beside this service surfaces those requests through the uniform
210    /// door without copying them anywhere.
211    pub fn observe_subagent_approvals(
212        &mut self,
213        queue: Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>,
214    ) {
215        self.subagent_approvals = Some(queue);
216    }
217
218    /// ORCH-9: every approval request this service can see, newest last.
219    ///
220    /// Two sources, both live: the requests outstanding on the open runtime
221    /// connections, and supercode's own queued subagent approvals. There is
222    /// no file or database source at the pinned harness versions (see
223    /// [`crate::approvals`]), so a stored or proposal row is never produced.
224    pub fn approvals(&self, query: &crate::approvals::ApprovalsQuery) -> Vec<crate::ApprovalRow> {
225        let now = crate::approvals::now_ms();
226        let mut rows = self.approvals.rows(now);
227        if let Some(queue) = self.subagent_approvals.as_ref() {
228            let queued = queue
229                .lock()
230                .unwrap_or_else(std::sync::PoisonError::into_inner)
231                .clone();
232            rows.extend(crate::approvals::subagent_rows(&queued, now));
233        }
234        rows.retain(|row| query.matches(row));
235        rows.sort_by(|left, right| {
236            left.requested_at_ms
237                .cmp(&right.requested_at_ms)
238                .then_with(|| left.id.cmp(&right.id))
239        });
240        rows
241    }
242
243    /// ORCH-20 (controlled tier): answer one listed approval request by its
244    /// row id and one uniform decision.
245    ///
246    /// The decision is translated into the option token and reply envelope
247    /// the door that raised the request already accepts
248    /// ([`crate::approvals::plan_reply`]), and the answer is then sent by
249    /// calling `harness.v1.runtimes.respond` itself — the same code path, the
250    /// same adapter, the same bookkeeping that drops the row. This verb adds
251    /// a translation and nothing else.
252    async fn approvals_resolve(
253        &mut self,
254        params: Value,
255    ) -> std::result::Result<Value, ServiceError> {
256        let params = decode::<crate::approvals::ApprovalsResolveParams>(params)?;
257        if params.id.trim().is_empty() {
258            return Err(ServiceError::InvalidParams(
259                "approvals resolve requires the `id` of a listed approval row".into(),
260            ));
261        }
262        let choice = match (params.decision, params.option_id.as_deref()) {
263            (Some(_), Some(_)) => {
264                return Err(ServiceError::InvalidParams(
265                    "approvals resolve takes either `decision` or `option_id`, not both".into(),
266                ))
267            }
268            (Some(decision), None) => crate::approvals::ApprovalChoice::Decision(decision),
269            (None, Some(option)) => crate::approvals::ApprovalChoice::Option(option.to_string()),
270            (None, None) => {
271                return Err(ServiceError::InvalidParams(format!(
272                    "approvals resolve requires `decision` ({}) or an explicit `option_id`",
273                    crate::approvals::ApprovalDecision::ALL
274                        .map(|decision| decision.as_str())
275                        .join(" | "),
276                )))
277            }
278        };
279        let resolution = self
280            .approvals
281            .resolution(&params.id, &choice)
282            .map_err(|error| ServiceError::InvalidParams(error.to_string()))?;
283        // The harness's own door, unchanged: this is the identical call
284        // `harness.v1.runtimes.respond` performs for a caller who built the
285        // envelope by hand, including dropping the answered row.
286        self.runtime_call(
287            "harness.v1.runtimes.respond",
288            json!({
289                "connection": resolution.connection,
290                "request_id": resolution.request_id,
291                "response": resolution.response,
292            }),
293        )
294        .await?;
295        Ok(json!({
296            "id": params.id,
297            "decision": params.decision.map(|decision| decision.as_str()),
298            "option_id": resolution.option_id,
299            "resolved": true,
300        }))
301    }
302
303    /// Return the edge-triggered wakeup used by session-index filesystem
304    /// subscriptions. Transports can await this instead of polling indexes.
305    #[cfg(feature = "adapter-api")]
306    pub fn session_index_notifier(&self) -> Arc<Notify> {
307        Arc::clone(&self.index_notifier)
308    }
309
310    /// Handle one JSON-RPC 2.0 request and return one JSON-RPC response.
311    #[cfg(feature = "adapter-api")]
312    pub fn handle(&mut self, request: Value) -> Value {
313        let id = request.get("id").cloned().unwrap_or(Value::Null);
314        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
315            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
316        }
317        let Some(method) = request.get("method").and_then(Value::as_str) else {
318            return rpc_error(id, -32600, "request is missing `method`");
319        };
320        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
321        match self.call(method, params) {
322            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
323            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
324            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
325            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
326            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
327            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
328        }
329    }
330
331    /// Handle either a persisted-session request or an asynchronous live
332    /// runtime request.
333    #[cfg(feature = "adapter-api")]
334    pub async fn handle_async(&mut self, request: Value) -> Value {
335        let method = request
336            .get("method")
337            .and_then(Value::as_str)
338            .unwrap_or_default();
339        if matches!(
340            method,
341            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe"
342        ) {
343            let id = request.get("id").cloned().unwrap_or(Value::Null);
344            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
345                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
346            }
347            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
348            return match self.inventory_call(method, params).await {
349                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
350                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
351                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
352                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
353                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
354                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
355            };
356        }
357        if matches!(
358            method,
359            "harness.v1.harnesses.auth.methods"
360                | "harness.v1.harnesses.auth.begin"
361                | "harness.v1.harnesses.auth.verify"
362        ) {
363            let id = request.get("id").cloned().unwrap_or(Value::Null);
364            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
365                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
366            }
367            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
368            return match self.harness_authentication_call(method, params).await {
369                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
370                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
371                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
372                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
373                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
374                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
375            };
376        }
377        // ORCH-19 controlled tier. Answered here rather than through the SDK
378        // operation dispatch below so the harness's OWN refusal reaches the
379        // caller: `sdk_error` collapses every `UnsupportedAction` to one
380        // generic sentence, and the whole point of this tier is that a
381        // refusal names which door the harness does have.
382        if matches!(
383            method,
384            "harness.v1.sessions.new"
385                | "harness.v1.sessions.reset"
386                | "harness.v1.sessions.archive"
387                | "harness.v1.sessions.delete"
388        ) {
389            let id = request.get("id").cloned().unwrap_or(Value::Null);
390            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
391                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
392            }
393            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
394            let verb = match method {
395                "harness.v1.sessions.new" => crate::SessionVerb::New,
396                "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
397                "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
398                _ => crate::SessionVerb::Delete,
399            };
400            return match self.mutate_session(verb, params).await {
401                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
402                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
403                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
404                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
405                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
406                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
407            };
408        }
409        if method == "harness.v1.sessions.message" {
410            let id = request.get("id").cloned().unwrap_or(Value::Null);
411            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
412                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
413            }
414            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
415            return match self.message_call(params).await {
416                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
417                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
418                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
419                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
420                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
421                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
422            };
423        }
424        if matches!(
425            method,
426            "harness.v1.harnesses.settings" | "harness.v1.harnesses.configure"
427        ) {
428            let id = request.get("id").cloned().unwrap_or(Value::Null);
429            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
430                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
431            }
432            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
433            return match self.harness_settings_call(method, params) {
434                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
435                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
436                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
437                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
438                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
439                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
440            };
441        }
442        if method == "harness.v1.sessions.activity.subscribe" {
443            let id = request.get("id").cloned().unwrap_or(Value::Null);
444            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
445                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
446            }
447            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
448            return match self.subscribe_session_activity(params).await {
449                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
450                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
451                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
452                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
453                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
454                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
455            };
456        }
457        if let Some(operation) = SdkOperation::from_method(method) {
458            let id = request.get("id").cloned().unwrap_or(Value::Null);
459            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
460                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
461            }
462            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
463            return match self.execute(SdkRequest { operation, params }).await {
464                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
465                Err(error) => sdk_rpc_error(id, &error),
466            };
467        }
468        if !method.starts_with("harness.v1.runtimes.") {
469            return self.handle(request);
470        }
471        let id = request.get("id").cloned().unwrap_or(Value::Null);
472        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
473            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
474        }
475        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
476        match self.runtime_call(method, params).await {
477            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
478            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
479            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
480            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
481            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
482            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
483        }
484    }
485
486    /// Poll all active subscriptions once and return zero or more JSON-RPC
487    /// notifications. Recoverable follower errors are delivered as events.
488    #[cfg(feature = "adapter-api")]
489    pub fn poll(&mut self) -> Vec<Value> {
490        let mut notifications = Vec::new();
491        for (subscription, follower) in &mut self.followers {
492            match follower.poll() {
493                Ok(Some(event)) => notifications.push(json!({
494                    "jsonrpc": "2.0",
495                    "method": SESSION_EVENT_METHOD,
496                    "params": {
497                        "subscription": subscription,
498                        "event": event.to_json(),
499                    }
500                })),
501                Ok(None) => {}
502                Err(error) => notifications.push(json!({
503                    "jsonrpc": "2.0",
504                    "method": SESSION_EVENT_METHOD,
505                    "params": {
506                        "subscription": subscription,
507                        "event": {
508                            "type": "watch_error",
509                            "recoverable": true,
510                            "message": error.to_string(),
511                        },
512                    }
513                })),
514            }
515        }
516        notifications
517    }
518
519    /// Report each followed session's live-runtime lifecycle state on that
520    /// session's own subscription, emitting only when the state changes.
521    ///
522    /// A growing transcript is not evidence that an agent is working, so the
523    /// state comes from the live-runtime registry and nowhere else. A followed
524    /// session with no registered Supercode runtime — a harness running outside
525    /// Supercode — reports `persisted`, which says plainly that its activity is
526    /// unknown rather than guessing at it. These events carry no sequence
527    /// number and no transcript content; they never interleave with the
528    /// content follower's sequenced stream.
529    #[cfg(feature = "adapter-api")]
530    pub async fn poll_session_runtime_states(&mut self) -> Vec<Value> {
531        let registry = crate::LocalRuntimeRegistry::new();
532        let authorization = crate::RuntimeAuthorization::observer();
533        let mut notifications = Vec::new();
534        for (subscription, source) in &mut self.followed_sources {
535            let state = match registry
536                .source_state(&source.harness, &source.session_id, &authorization)
537                .await
538            {
539                Ok(Some(state)) => state,
540                Ok(None) => crate::RuntimeRegistryState::Persisted,
541                // A failed registry read is not evidence of a state change.
542                Err(_) => continue,
543            };
544            if source.reported.as_deref() == Some(state.as_str()) {
545                continue;
546            }
547            source.reported = Some(state.as_str().to_string());
548            notifications.push(json!({
549                "jsonrpc": "2.0",
550                "method": SESSION_EVENT_METHOD,
551                "params": {
552                    "subscription": subscription,
553                    "event": {"type": "runtime_state", "state": state.as_str()},
554                },
555            }));
556        }
557        notifications
558    }
559
560    /// Poll normalized activity subscriptions, emitting only proven state
561    /// transitions. Every subscription is bulk-sampled so stock-harness
562    /// process and registry discovery happens once per UI, not once per row.
563    #[cfg(feature = "adapter-api")]
564    pub async fn poll_session_activities(&mut self) -> Vec<Value> {
565        let subscriptions = self
566            .activity_subscriptions
567            .iter()
568            .map(|(id, subscription)| {
569                (
570                    id.clone(),
571                    subscription.locators.clone(),
572                    subscription.homes.clone(),
573                )
574            })
575            .collect::<Vec<_>>();
576        let mut notifications = Vec::new();
577        for (subscription_id, locators, homes) in subscriptions {
578            let Ok(activities) = self.activity_monitor.resolve(&locators, &homes).await else {
579                // A failed evidence read proves no transition. Retain the last
580                // good state instead of flashing every row to persisted.
581                continue;
582            };
583            let Some(subscription) = self.activity_subscriptions.get_mut(&subscription_id) else {
584                continue;
585            };
586            let mut changed = Vec::new();
587            for activity in activities {
588                let key = activity.key();
589                if subscription
590                    .reported
591                    .get(&key)
592                    .is_some_and(|previous| previous.same_state(&activity))
593                {
594                    continue;
595                }
596                subscription.reported.insert(key, activity.clone());
597                changed.push(activity);
598            }
599            if !changed.is_empty() {
600                notifications.push(json!({
601                    "jsonrpc": "2.0",
602                    "method": SESSION_ACTIVITY_EVENT_METHOD,
603                    "params": {
604                        "subscription": subscription_id,
605                        "activities": changed,
606                    },
607                }));
608            }
609        }
610        notifications
611    }
612
613    /// Drain native-store invalidations and emit revisioned descriptor deltas.
614    /// An idle subscription performs no catalog or transcript reads between
615    /// its minute-scale recovery reconciliations.
616    #[cfg(feature = "adapter-api")]
617    pub fn poll_session_indexes(&mut self) -> Vec<Value> {
618        let mut notifications = Vec::new();
619        for (subscription, index) in &mut self.index_subscriptions {
620            let homes = index.homes().clone();
621            match index.poll() {
622                Ok(Some(delta)) => match live_index_changes(delta.changes, &homes) {
623                    Ok(changes) => notifications.push(json!({
624                        "jsonrpc": "2.0",
625                        "method": SESSION_INDEX_EVENT_METHOD,
626                        "params": {
627                            "subscription": subscription,
628                            "revision": delta.revision,
629                            "changes": changes,
630                        },
631                    })),
632                    Err(error) => notifications.push(json!({
633                        "jsonrpc": "2.0",
634                        "method": SESSION_INDEX_EVENT_METHOD,
635                        "params": {
636                            "subscription": subscription,
637                            "error": {"recoverable": true, "message": error_message(error)},
638                        },
639                    })),
640                },
641                Ok(None) => {}
642                Err(error) => notifications.push(json!({
643                    "jsonrpc": "2.0",
644                    "method": SESSION_INDEX_EVENT_METHOD,
645                    "params": {
646                        "subscription": subscription,
647                        "error": {"recoverable": true, "message": error},
648                    },
649                })),
650            }
651        }
652        notifications
653    }
654
655    #[cfg(feature = "adapter-api")]
656    async fn subscribe_session_activity(
657        &mut self,
658        params: Value,
659    ) -> std::result::Result<Value, ServiceError> {
660        let params = decode::<ActivitySubscribeParams>(params)?;
661        if params.locators.is_empty() {
662            return Err(ServiceError::InvalidParams(
663                "sessions.activity.subscribe requires at least one locator".into(),
664            ));
665        }
666        if params.locators.len() > 2_048 {
667            return Err(ServiceError::InvalidParams(
668                "sessions.activity.subscribe accepts at most 2048 locators".into(),
669            ));
670        }
671        let initial = self
672            .activity_monitor
673            .resolve(&params.locators, &params.homes)
674            .await
675            .map_err(ServiceError::Sdk)?;
676        let subscription = format!("activity-sub-{}", self.next_subscription);
677        self.next_subscription += 1;
678        let reported = initial
679            .iter()
680            .cloned()
681            .map(|activity| (activity.key(), activity))
682            .collect();
683        self.activity_subscriptions.insert(
684            subscription.clone(),
685            ActivitySubscription {
686                locators: params.locators,
687                homes: params.homes,
688                reported,
689            },
690        );
691        Ok(json!({"subscription": subscription, "initial": initial}))
692    }
693
694    /// Non-blockingly sample one event from every connected live runtime.
695    #[cfg(feature = "adapter-api")]
696    pub async fn poll_runtimes(&mut self) -> Vec<Value> {
697        self.poll_sdk_events()
698            .await
699            .into_iter()
700            .map(|(connection, runtime_event)| {
701                json!({
702                    "jsonrpc": "2.0",
703                    "method": RUNTIME_EVENT_METHOD,
704                    "params": {
705                        "connection": connection,
706                        "session_id": runtime_event.session_id,
707                        "sequence": runtime_event.event.sequence,
708                        "event": {
709                            "kind": runtime_event.event.kind,
710                            "payload": runtime_event.event.payload,
711                        },
712                    },
713                })
714            })
715            .collect()
716    }
717
718    async fn poll_sdk_events(&mut self) -> Vec<(String, SdkRuntimeEvent)> {
719        let mut events = Vec::new();
720        let mut closed = Vec::new();
721        let now_ms = crate::approvals::now_ms();
722        for (connection, runtime) in &mut self.runtimes {
723            let session_id = runtime.handle().runtime_id.clone();
724            let harness = runtime.handle().harness.clone();
725            // Drain what the runtime already has: a turn is several events
726            // (updates, then the protocol's completion), and delivering one
727            // per poll would cost a poll interval each. A zero timeout takes
728            // only what is ready — an idle runtime costs nothing.
729            for _ in 0..256 {
730                match tokio::time::timeout(Duration::ZERO, runtime.next_event()).await {
731                    Ok(Ok(Some(event))) => {
732                        let terminal = event.kind == "transport_closed";
733                        // ORCH-9: a permission/approval request arrives as an
734                        // ordinary event; it becomes listable here and stops
735                        // being listable when `runtimes.respond` answers it.
736                        self.approvals
737                            .observe(connection, &harness, &session_id, &event, now_ms);
738                        let next_sequence = self
739                            .runtime_sequences
740                            .entry(session_id.clone())
741                            .or_insert(0);
742                        let sequence = event.sequence.unwrap_or_else(|| {
743                            *next_sequence = next_sequence.saturating_add(1);
744                            *next_sequence
745                        });
746                        *next_sequence = (*next_sequence).max(sequence);
747                        events.push((
748                            connection.clone(),
749                            SdkRuntimeEvent {
750                                session_id: session_id.clone(),
751                                event: SdkEvent {
752                                    sequence,
753                                    kind: event.kind,
754                                    payload: event.payload,
755                                },
756                            },
757                        ));
758                        if terminal {
759                            closed.push(connection.clone());
760                            break;
761                        }
762                    }
763                    Ok(Ok(None)) => {
764                        let sequence = self
765                            .runtime_sequences
766                            .entry(session_id.clone())
767                            .or_insert(0);
768                        *sequence = sequence.saturating_add(1);
769                        events.push((
770                        connection.clone(),
771                        SdkRuntimeEvent {
772                            session_id,
773                            event: SdkEvent {
774                                sequence: *sequence,
775                                kind: "transport_closed".into(),
776                                payload: json!({"message": "Harness runtime transport closed."}),
777                            },
778                        },
779                    ));
780                        closed.push(connection.clone());
781                        break;
782                    }
783                    Err(_) => break,
784                    Ok(Err(error)) => {
785                        let sequence = self
786                            .runtime_sequences
787                            .entry(session_id.clone())
788                            .or_insert(0);
789                        *sequence = sequence.saturating_add(1);
790                        events.push((
791                        connection.clone(),
792                        SdkRuntimeEvent {
793                            session_id,
794                            event: SdkEvent {
795                                sequence: *sequence,
796                                kind: "transport_error".into(),
797                                payload: json!({"message": error.to_string(), "terminal": true}),
798                            },
799                        },
800                    ));
801                        closed.push(connection.clone());
802                        break;
803                    }
804                }
805            }
806        }
807        for connection in closed {
808            if let Some(runtime) = self.runtimes.remove(&connection) {
809                self.runtime_sequences.remove(&runtime.handle().runtime_id);
810            }
811            self.terminal_launches.remove(&connection);
812            // A connection that is gone cannot answer anything it was
813            // holding; those requests stop being listable with it.
814            self.approvals.forget(&connection);
815        }
816        events
817    }
818
819    fn call(&mut self, method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
820        match method {
821            "harness.v1.capabilities" => Ok(json!({
822                "version": HARNESS_SERVICE_VERSION,
823                "sdk": self.capabilities(),
824                "methods": HARNESS_SERVICE_METHODS,
825                "notifications": [
826                    SESSION_EVENT_METHOD,
827                    SESSION_ACTIVITY_EVENT_METHOD,
828                    SESSION_INDEX_EVENT_METHOD,
829                    RUNTIME_EVENT_METHOD
830                ],
831                "harnesses": harness_support_registry()
832                    .harnesses
833                    .into_iter()
834                    .map(|harness| harness.id)
835                    .collect::<Vec<_>>(),
836            })),
837            "harness.v1.support.report" => serde_json::to_value(harness_support_registry())
838                .map_err(|error| ServiceError::Operation(error.to_string())),
839            "harness.v1.profiles.list" | "harness.v1.profiles.get" => profiles_call(method, params),
840            // ORCH-21 controlled tier. Each verb translates to the HARNESS'S
841            // OWN profile verb and runs it (`crate::profiles_control`);
842            // supercode makes and removes nothing itself. The row returned is
843            // re-read through the ORCH-10 loader afterwards, and `ran`
844            // narrates the exact command.
845            "harness.v1.profiles.create" => {
846                mutate_profile(crate::profiles_control::ProfileVerb::Create, params)
847            }
848            "harness.v1.profiles.delete" => {
849                mutate_profile(crate::profiles_control::ProfileVerb::Delete, params)
850            }
851            "harness.v1.channels.list" | "harness.v1.channels.status" => {
852                channels_call(method, params)
853            }
854            // ORCH-15 observed tier: which profile / agent a surface tuple
855            // resolves to, read from each gateway harness's own config.
856            "harness.v1.routes.list" => routes_call(params),
857            // ORCH-16 observed tier: inbound webhook routes / hook mappings.
858            "harness.v1.triggers.list" => triggers_call(params),
859            // ONT-4: the orchestration doors. One home folder in, one typed orchestration
860            // value out (and back). Every one of the four is
861            // `crate::orchestration_doors`, which the `supercode orchestration` verbs call
862            // too — the RPC adds nothing but the envelope. A vault VALUE
863            // never crosses this wire: a load or a compile answers with the
864            // `.env` KEY NAMES, and a caller that needs a value reads the
865            // home's own `.env`.
866            // the workflow layer's read door: a harness's board as one typed value,
867            // the same code the `supercode workflow load` verb calls
868            "harness.v1.workflow.load" => {
869                let params = decode::<WorkflowLoadParams>(params)?;
870                let read =
871                    crate::workflow_doors::load(params.from, &params.home).map_err(operation)?;
872                serde_json::to_value(read)
873                    .map_err(|error| ServiceError::Operation(error.to_string()))
874            }
875            "harness.v1.orchestration.load" => {
876                let params = decode::<OrchestrationLoadParams>(params)?;
877                let read = crate::orchestration_doors::load(&params.root, params.flavor)
878                    .map_err(operation)?;
879                serde_json::to_value(read)
880                    .map_err(|error| ServiceError::Operation(error.to_string()))
881            }
882            "harness.v1.orchestration.save" => {
883                let params = decode::<OrchestrationSaveParams>(params)?;
884                let saved = crate::orchestration_doors::save(
885                    &params.root,
886                    params.orchestration,
887                    params.vault,
888                )
889                .map_err(operation)?;
890                serde_json::to_value(saved)
891                    .map_err(|error| ServiceError::Operation(error.to_string()))
892            }
893            "harness.v1.orchestration.compile" => {
894                let params = decode::<OrchestrationCompileParams>(params)?;
895                let read = crate::orchestration_doors::compile(params.from, &params.home)
896                    .map_err(operation)?;
897                serde_json::to_value(read)
898                    .map_err(|error| ServiceError::Operation(error.to_string()))
899            }
900            "harness.v1.orchestration.decompile" => {
901                let params = decode::<OrchestrationDecompileParams>(params)?;
902                let report = crate::orchestration_doors::decompile(
903                    params.to,
904                    params.orchestration,
905                    &params.source,
906                    params.source_flavor,
907                    &params.dest,
908                    params.vault,
909                )
910                .map_err(operation)?;
911                serde_json::to_value(report)
912                    .map_err(|error| ServiceError::Operation(error.to_string()))
913            }
914            // a migration keeps the credential in this process: a compile and
915            // a save (import), a load and a decompile (export), composed here
916            // because composed by a client the secret would have to cross
917            // the wire
918            "harness.v1.orchestration.import" => {
919                let params = decode::<OrchestrationImportParams>(params)?;
920                let imported =
921                    crate::orchestration_doors::import(params.from, &params.home, &params.into)
922                        .map_err(operation)?;
923                serde_json::to_value(imported)
924                    .map_err(|error| ServiceError::Operation(error.to_string()))
925            }
926            "harness.v1.orchestration.export" => {
927                let params = decode::<OrchestrationExportParams>(params)?;
928                let report =
929                    crate::orchestration_doors::export(params.to, &params.root, &params.dest)
930                        .map_err(operation)?;
931                serde_json::to_value(report)
932                    .map_err(|error| ServiceError::Operation(error.to_string()))
933            }
934            // ORCH-12 observed tier: read and search the persistent memory
935            // documents a harness keeps on disk. Read-only — every write
936            // (`hermes memory off`, `openclaw memory forget|reset`, Claude
937            // Code's `/memory`) stays the harness's own verb. A harness with
938            // no memory store is refused with UnsupportedAction.
939            "harness.v1.memory.show" | "harness.v1.memory.search" => memory_call(method, params),
940            // ORCH-11 observed tier: read-only enumeration of every harness's
941            // installed skill packages. An unknown harness id is refused with
942            // UnsupportedAction — every harness supports skills, so a filter
943            // that matches nothing is a caller error, never an empty listing.
944            "harness.v1.skills.list" => {
945                let query = decode::<crate::skills::SkillsQuery>(params)?;
946                if let Some(harness) = query.harness.as_deref() {
947                    if !crate::skills::SKILL_HARNESSES.contains(&harness) {
948                        return Err(ServiceError::UnsupportedAction(format!(
949                            "`{harness}` has no skills root supercode reads"
950                        )));
951                    }
952                }
953                serde_json::to_value(crate::skills::list_skills(&query))
954                    .map_err(|error| ServiceError::Operation(error.to_string()))
955            }
956            // ORCH-22 controlled tier: each verb goes through the door the
957            // HARNESS publishes — `hermes skills install|uninstall`,
958            // `openclaw skills install`, and for the core four the loader's
959            // own directory, which is the only skills door those harnesses
960            // have. supercode resolves no registry and unpacks no archive.
961            // The row returned is re-read through the ORCH-11 loader
962            // afterwards, and `ran` narrates exactly what was performed.
963            "harness.v1.skills.install" => {
964                mutate_skill(crate::skills_control::SkillVerb::Install, params)
965            }
966            "harness.v1.skills.remove" => {
967                mutate_skill(crate::skills_control::SkillVerb::Remove, params)
968            }
969            // ORCH-9 observed tier: the approval requests waiting for an
970            // answer. At the pinned harness versions the only uniform source
971            // is a LIVE request held by an open runtime connection, plus
972            // supercode's own queued subagent approvals — neither Hermes
973            // 0.21.0 nor OpenClaw 2026.7.1-2 has an approvals door to read
974            // (see `crate::approvals`). A harness whose runtime cannot carry
975            // a protocol request at all is refused by name.
976            "harness.v1.approvals.list" => {
977                let query = decode::<crate::approvals::ApprovalsQuery>(params)?;
978                if let Some(harness) = query.harness.as_deref() {
979                    if !crate::approvals::lists_approvals(harness) {
980                        return Err(ServiceError::UnsupportedAction(format!(
981                            "`{harness}` has no runtime door that carries an approval request"
982                        )));
983                    }
984                }
985                serde_json::to_value(self.approvals(&query))
986                    .map_err(|error| ServiceError::Operation(error.to_string()))
987            }
988            "harness.v1.sessions.discover" => {
989                let query = decode::<DiscoveryQuery>(params)?;
990                let page = discover_session_page(&query).map_err(operation)?;
991                // Claude Code is the one harness that publishes its RUNNING
992                // sessions. The registry is read once per discovery and joined
993                // by session id; every record in it has already survived a
994                // `kill(pid, 0)` liveness check inside `read_registry`.
995                let peers = if page
996                    .sessions
997                    .iter()
998                    .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
999                {
1000                    crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(
1001                        &query.homes,
1002                    ))
1003                } else {
1004                    Vec::new()
1005                };
1006                let activities = crate::session_activity::resolve_stock_session_activities(
1007                    &page
1008                        .sessions
1009                        .iter()
1010                        .map(|session| session.locator.clone())
1011                        .collect::<Vec<_>>(),
1012                    &query.homes,
1013                )
1014                .into_iter()
1015                .map(|activity| (activity.key(), activity))
1016                .collect::<BTreeMap<_, _>>();
1017                let sessions = page
1018                    .sessions
1019                    .into_iter()
1020                    .map(|session| {
1021                        let mut value = live_descriptor_value(&session, &peers)?;
1022                        let activity_key = (
1023                            session.locator.harness.as_str().to_string(),
1024                            session.locator.session_id.clone(),
1025                        );
1026                        if let Some(activity) = activities.get(&activity_key) {
1027                            value["activity"] = serde_json::to_value(activity)
1028                                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1029                            if let Some(status) = legacy_live_status(activity) {
1030                                value["live_status"] = json!(status);
1031                            }
1032                        }
1033                        Ok(value)
1034                    })
1035                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1036                let mut result = json!({"sessions": sessions, "next_cursor": page.next_cursor});
1037                // Preserve the metadata-only wire shape, but carry the catalog's
1038                // proof/counts when the caller explicitly requests preview search.
1039                if query.search_previews {
1040                    result["receipt"] = serde_json::to_value(page.receipt)
1041                        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1042                }
1043                Ok(result)
1044            }
1045            "harness.v1.sessions.load" => {
1046                let params = decode::<LoadSessionParams>(params)?;
1047                if let Some(options) = &params.options {
1048                    options.validate()?;
1049                    if let Some(result) = indexed_claude_window(&params.read.locator, options)? {
1050                        return Ok(result);
1051                    }
1052                    return load_session(&params.read.locator)
1053                        .map(|session| projected_session_result(&session, options))
1054                        .map_err(operation);
1055                }
1056                let mut session = if params.read.display_history() {
1057                    self.catalog
1058                        .load_display_view(
1059                            &params.read.locator,
1060                            params.read.read_fidelity(),
1061                            params.read.tail_messages().unwrap_or(500),
1062                        )
1063                        .map_err(crate::Error::from)
1064                } else if params.read.include_subagents() {
1065                    load_session_with_fidelity(&params.read.locator, params.read.read_fidelity())
1066                } else {
1067                    self.catalog
1068                        .load_parent_with_fidelity(
1069                            &params.read.locator,
1070                            params.read.read_fidelity(),
1071                        )
1072                        .map_err(crate::Error::from)
1073                }
1074                .map_err(operation)?;
1075                params.read.bound_session(&mut session);
1076                Ok(json!({"session": normalized_session_json(&session)}))
1077            }
1078            "harness.v1.sessions.follow" => {
1079                let params = decode::<LocatorParams>(params)?;
1080                let mut follower = self
1081                    .catalog
1082                    .follow_read_view(
1083                        &params.locator,
1084                        params.read_fidelity(),
1085                        params.include_subagents(),
1086                        params.tail_messages(),
1087                        params.max_message_chars(),
1088                        params.display_history(),
1089                    )
1090                    .map_err(operation)?;
1091                let initial = follower
1092                    .poll()
1093                    .map_err(operation)?
1094                    .map(|event| event.to_json());
1095                let subscription = format!("sub-{}", self.next_subscription);
1096                self.next_subscription += 1;
1097                self.followers.insert(subscription.clone(), follower);
1098                self.followed_sources.insert(
1099                    subscription.clone(),
1100                    FollowedSource {
1101                        harness: params.locator.harness.as_str().to_string(),
1102                        session_id: params.locator.session_id.clone(),
1103                        reported: None,
1104                    },
1105                );
1106                Ok(json!({"subscription": subscription, "initial": initial}))
1107            }
1108            "harness.v1.sessions.unfollow" => {
1109                let params = decode::<UnfollowParams>(params)?;
1110                self.followed_sources.remove(&params.subscription);
1111                Ok(json!({
1112                    "removed": self.followers.remove(&params.subscription).is_some()
1113                }))
1114            }
1115            "harness.v1.sessions.activity.unsubscribe" => {
1116                let params = decode::<UnfollowParams>(params)?;
1117                Ok(json!({
1118                    "removed": self.activity_subscriptions.remove(&params.subscription).is_some()
1119                }))
1120            }
1121            "harness.v1.sessions.index.subscribe" => {
1122                let query = decode::<DiscoveryQuery>(params)?;
1123                crate::session_index::validate_query(&query)
1124                    .map_err(ServiceError::InvalidParams)?;
1125                let homes = query.homes.clone();
1126                let (index, initial) = crate::session_index::SessionIndexSubscription::open(
1127                    query,
1128                    Arc::clone(&self.index_notifier),
1129                )
1130                .map_err(ServiceError::Operation)?;
1131                let peers = peers_for_descriptors(&initial, &homes);
1132                let initial = initial
1133                    .iter()
1134                    .map(|descriptor| live_descriptor_value(descriptor, &peers))
1135                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1136                let subscription = format!("index-sub-{}", self.next_subscription);
1137                self.next_subscription += 1;
1138                self.index_subscriptions.insert(subscription.clone(), index);
1139                Ok(json!({
1140                    "subscription": subscription,
1141                    "revision": 1,
1142                    "initial": initial,
1143                }))
1144            }
1145            "harness.v1.sessions.index.resize" => {
1146                let params = decode::<IndexResizeParams>(params)?;
1147                crate::session_index::validate_limit(params.limit)
1148                    .map_err(ServiceError::InvalidParams)?;
1149                let index = self
1150                    .index_subscriptions
1151                    .get_mut(&params.subscription)
1152                    .ok_or_else(|| {
1153                        ServiceError::InvalidParams("unknown session index subscription".into())
1154                    })?;
1155                let prepared = index
1156                    .prepare_resize(params.limit)
1157                    .map_err(ServiceError::Operation)?;
1158                let peers = peers_for_descriptors(&prepared.page.sessions, index.homes());
1159                let initial = prepared
1160                    .page
1161                    .sessions
1162                    .iter()
1163                    .map(|descriptor| live_descriptor_value(descriptor, &peers))
1164                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1165                let response = json!({
1166                    "subscription": params.subscription,
1167                    "revision": prepared.revision,
1168                    "initial": initial,
1169                    "receipt": prepared.page.receipt,
1170                });
1171                index.commit_resize(prepared);
1172                Ok(response)
1173            }
1174            "harness.v1.sessions.index.unsubscribe" => {
1175                let params = decode::<UnfollowParams>(params)?;
1176                Ok(json!({
1177                    "removed": self.index_subscriptions.remove(&params.subscription).is_some()
1178                }))
1179            }
1180            "harness.v1.sessions.import" => {
1181                let params = decode::<ImportSessionParams>(params)?;
1182                let session = Session::load_str(&params.content, params.source_harness.into())
1183                    .map_err(operation)?;
1184                Ok(json!({"session": normalized_session_json(&session)}))
1185            }
1186            "harness.v1.sessions.export" | "harness.v1.sessions.translate" => {
1187                let params = decode::<ExportSessionParams>(params)?;
1188                let session = load_session(&params.locator).map_err(operation)?;
1189                let artifact = session_artifact(&params.locator, &session, params.target_harness)?;
1190                if method == "harness.v1.sessions.export"
1191                    && params.target_harness == TransferFormat::Hermes
1192                {
1193                    // UNI-18: write through Hermes's own door, never into its store
1194                    let imported = crate::hermes_import::import_into_hermes(&session, None)
1195                        .map_err(operation)?;
1196                    return Ok(json!({"artifact": artifact, "imported": imported}));
1197                }
1198                Ok(json!({"artifact": artifact}))
1199            }
1200            "harness.v1.sessions.reduce" => {
1201                let params = decode::<ReduceSessionParams>(params)?;
1202                self.reduce_session(params)
1203            }
1204            "harness.v1.sessions.branch" => {
1205                let params = decode::<BranchSessionParams>(params)?;
1206                let session = load_session(&params.locator).map_err(operation)?;
1207                let storage = params.locator.storage.path().display().to_string();
1208                let bootstrap_prompt = format!(
1209                    "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.",
1210                    params.locator.harness.as_str(), params.locator.session_id, storage
1211                );
1212                let artifact = params
1213                    .target_harness
1214                    .map(|target| session_artifact(&params.locator, &session, target))
1215                    .transpose()?;
1216                Ok(json!({
1217                    "parent": params.locator,
1218                    "session": normalized_session_json(&session),
1219                    "bootstrap_prompt": bootstrap_prompt,
1220                    "artifact": artifact,
1221                }))
1222            }
1223            "harness.v1.sessions.handoff" => {
1224                let params = decode::<HandoffSessionParams>(params)?;
1225                let session = load_session(&params.locator).map_err(operation)?;
1226                let cwd = params
1227                    .cwd
1228                    .or_else(|| session.meta.cwd.clone())
1229                    .unwrap_or_else(|| PathBuf::from("."));
1230                let artifact = handoff_artifact(&params.locator, &session, params.target_harness)?;
1231                let target_session_id = artifact.session_id.as_deref().ok_or_else(|| {
1232                    ServiceError::Operation(
1233                        "handoff artifact omitted target session identity".into(),
1234                    )
1235                })?;
1236                let instructions =
1237                    handoff_instructions(params.target_harness, target_session_id, &cwd);
1238                Ok(json!({
1239                    "artifact": artifact,
1240                    "launch": instructions.launch,
1241                    "materialize": instructions.materialize,
1242                    "requires_materialization": instructions.requires_materialization,
1243                    "note": instructions.note,
1244                }))
1245            }
1246            "harness.v1.sessions.materialize" => {
1247                let params = decode::<MaterializeSessionParams>(params)?;
1248                // An artifact from another machine carries its whole source as a recovery file;
1249                // keeping its segments here lets a later write back to that format restore it
1250                // byte for byte on this machine too (docs/plans/portable-residue.md).
1251                for file in &params.artifact.files {
1252                    if file.role == "source_recovery"
1253                        && file.path == "recovery/source.supercode.jsonl"
1254                    {
1255                        if let Ok(source) = Session::from_native_str(&file.content) {
1256                            crate::residue_store::store_segments(&source);
1257                        }
1258                    }
1259                }
1260                let locator = crate::native_materialize::materialize_native_artifact(
1261                    params.artifact,
1262                    &params.cwd,
1263                    &params.homes,
1264                )
1265                .map_err(ServiceError::Operation)?;
1266                Ok(json!({"locator": locator}))
1267            }
1268            // ORCH-7 observed tier. Read-only: the handlers open the harness's
1269            // own job store (Claude Code's session JSONL, Hermes's and
1270            // OpenClaw's `cron/jobs.json`) and never write, fire, or schedule.
1271            "harness.v1.jobs.list" => {
1272                let query = decode::<crate::jobs::JobsQuery>(params)?;
1273                if let Some(harness) = query.harness.as_deref() {
1274                    refuse_harness_without_jobs(harness, "jobs.list")?;
1275                }
1276                let listing = crate::jobs::list_jobs(&query).map_err(operation)?;
1277                serde_json::to_value(listing)
1278                    .map_err(|error| ServiceError::Operation(error.to_string()))
1279            }
1280            "harness.v1.jobs.get" => {
1281                let params = decode::<JobsGetParams>(params)?;
1282                refuse_harness_without_jobs(&params.harness, "jobs.get")?;
1283                match crate::jobs::get_job(&params.harness, &params.id, &params.homes)
1284                    .map_err(operation)?
1285                {
1286                    Some((job, source)) => Ok(json!({"job": job, "source": source})),
1287                    None => Err(ServiceError::Operation(format!(
1288                        "`{}` has no scheduled job `{}`",
1289                        params.harness, params.id
1290                    ))),
1291                }
1292            }
1293            // ORCH-18 controlled tier. Each verb translates to the HARNESS'S
1294            // OWN cron verb and runs it (`crate::jobs_control`); supercode
1295            // schedules nothing. The row returned is re-read from the
1296            // harness's store afterwards, and `ran` narrates the exact command
1297            // with any credential redacted.
1298            "harness.v1.jobs.create" => mutate_job(crate::jobs_control::JobVerb::Create, params),
1299            "harness.v1.jobs.update" => mutate_job(crate::jobs_control::JobVerb::Update, params),
1300            "harness.v1.jobs.pause" => mutate_job(crate::jobs_control::JobVerb::Pause, params),
1301            "harness.v1.jobs.resume" => mutate_job(crate::jobs_control::JobVerb::Resume, params),
1302            "harness.v1.jobs.run" => mutate_job(crate::jobs_control::JobVerb::Run, params),
1303            "harness.v1.jobs.delete" => mutate_job(crate::jobs_control::JobVerb::Delete, params),
1304            "harness.v1.jobs.notepad"
1305            | "harness.v1.jobs.notepad_set"
1306            | "harness.v1.jobs.notepad_delete" => {
1307                let request = decode::<crate::jobs_notepad::JobNotepadRequest>(params)?;
1308                refuse_harness_without_jobs(&request.harness, "jobs.notepad")?;
1309                let answer = match method {
1310                    "harness.v1.jobs.notepad_set" => crate::jobs_notepad::set(&request),
1311                    "harness.v1.jobs.notepad_delete" => crate::jobs_notepad::delete(&request),
1312                    _ => crate::jobs_notepad::read(&request),
1313                }
1314                .map_err(job_control_error)?;
1315                serde_json::to_value(answer)
1316                    .map_err(|error| ServiceError::Operation(error.to_string()))
1317            }
1318            // ORCH-8 observed tier. Read-only: the handlers open the harness's
1319            // own run store (Hermes's `cron/executions.db`, OpenClaw's
1320            // `cron_run_logs`) and never claim, retry, or prune a fire.
1321            "harness.v1.runs.list" => {
1322                let query = decode::<crate::runs::RunsQuery>(params)?;
1323                if let Some(harness) = query.harness.as_deref() {
1324                    refuse_harness_without_runs(harness, "runs.list")?;
1325                }
1326                let listing = crate::runs::list_runs(&query).map_err(operation)?;
1327                serde_json::to_value(listing)
1328                    .map_err(|error| ServiceError::Operation(error.to_string()))
1329            }
1330            "harness.v1.runs.get" => {
1331                let params = decode::<RunsGetParams>(params)?;
1332                refuse_harness_without_runs(&params.harness, "runs.get")?;
1333                match crate::runs::get_run(&params.harness, &params.id, &params.homes)
1334                    .map_err(operation)?
1335                {
1336                    Some((run, source)) => Ok(json!({"run": run, "source": source})),
1337                    None => Err(ServiceError::Operation(format!(
1338                        "`{}` has no run `{}`",
1339                        params.harness, params.id
1340                    ))),
1341                }
1342            }
1343            "harness.v1.sessions.resume_instructions" => {
1344                let params = decode::<ResumeInstructionsParams>(params)?;
1345                let session = load_session(&params.locator).map_err(operation)?;
1346                let cwd = params
1347                    .cwd
1348                    .or(session.meta.cwd)
1349                    .unwrap_or_else(|| PathBuf::from("."));
1350                let launch = resume_launch(
1351                    params.locator.harness.as_str(),
1352                    &params.locator.session_id,
1353                    &cwd,
1354                    params.policy,
1355                )?;
1356                Ok(json!({"launch": launch}))
1357            }
1358            _ => Err(ServiceError::MethodNotFound),
1359        }
1360    }
1361
1362    fn reduce_session(
1363        &self,
1364        params: ReduceSessionParams,
1365    ) -> std::result::Result<Value, ServiceError> {
1366        let session = load_session(&params.locator).map_err(operation)?;
1367        if session.messages.is_empty() {
1368            return Err(ServiceError::InvalidParams(
1369                "cannot reduce an empty session".into(),
1370            ));
1371        }
1372        let keep_last = params.keep_last.clamp(1, 128);
1373        let policy = reduce::ReductionPolicy {
1374            clear_turns_older_than: Some(keep_last),
1375            ..Default::default()
1376        };
1377        let (view, log) =
1378            reduce::project_messages(&session.messages, &policy, &reduce::ReductionLog::default());
1379        if log.reductions.is_empty() {
1380            return Err(ServiceError::UnsupportedAction(format!(
1381                "session `{}` is already too small for a meaningful reversible reduction",
1382                params.locator.session_id
1383            )));
1384        }
1385        let source_tokens = tokens::estimate_view_tokens(&session.messages);
1386        let reduced_tokens = tokens::estimate_view_tokens(&view);
1387        if reduced_tokens >= source_tokens {
1388            return Err(ServiceError::UnsupportedAction(format!(
1389                "session `{}` has no token-reducing reversible projection",
1390                params.locator.session_id
1391            )));
1392        }
1393
1394        let store_root = self
1395            .reduction_store_root
1396            .clone()
1397            .unwrap_or_else(default_reduction_store_root);
1398        let store = crate::SessionStore::open(&store_root).map_err(operation)?;
1399        let rescue_id = format!("rescue-{}", generated_session_id());
1400        let imported = session
1401            .imported_message_count
1402            .unwrap_or(session.messages.len())
1403            .min(session.messages.len());
1404        let sidecar_jsonl = session.to_native_jsonl_v2(&session.messages[imported..]);
1405        let view_jsonl = messages_jsonl(&view)?;
1406        let title = format!(
1407            "Reduced {} continuation from {}",
1408            params.target_harness.id(),
1409            params.locator.session_id
1410        );
1411
1412        // Durability order is intentional: the full source of truth lands
1413        // before either object that can refer to it. A crash may leave an
1414        // unused sidecar, but can never leave a reduced view whose originals
1415        // were not durably written first.
1416        store
1417            .save_sidecar(&rescue_id, &sidecar_jsonl)
1418            .map_err(operation)?;
1419        store
1420            .save_reduction_log(&rescue_id, &log)
1421            .map_err(operation)?;
1422        store
1423            .save(&rescue_id, &title, &view_jsonl)
1424            .map_err(operation)?;
1425
1426        let source_bytes = serde_json::to_vec(&session.messages)
1427            .map_err(|error| ServiceError::Operation(error.to_string()))?
1428            .len() as u64;
1429        let reduced_bytes = serde_json::to_vec(&view)
1430            .map_err(|error| ServiceError::Operation(error.to_string()))?
1431            .len() as u64;
1432        store
1433            .set_reduction_stats(
1434                &rescue_id,
1435                &title,
1436                source_bytes,
1437                reduced_bytes,
1438                log.reductions.len() as u32,
1439            )
1440            .map_err(operation)?;
1441
1442        // The receipt is issued only after a real disk reload. This proves
1443        // the exact files another process will consume, not the convenient
1444        // in-memory values that produced them.
1445        let reloaded_sidecar = store
1446            .load_sidecar(&rescue_id)
1447            .map_err(operation)?
1448            .ok_or_else(|| ServiceError::Operation("reduction sidecar disappeared".into()))?;
1449        let reloaded_sidecar = Session::from_sidecar_str(&reloaded_sidecar).map_err(operation)?;
1450        let reloaded_log = store
1451            .load_reduction_log(&rescue_id)
1452            .map_err(operation)?
1453            .ok_or_else(|| ServiceError::Operation("reduction log disappeared".into()))?;
1454        let reloaded_view = parse_messages_jsonl(&store.load(&rescue_id).map_err(operation)?)?;
1455        reduce::verify_log(&reloaded_log, &reloaded_sidecar).map_err(operation)?;
1456        // `sc.reduction` is deliberately in-memory-only metadata: it must
1457        // never leak onto a provider-facing transcript. Reapplying the
1458        // durable log to the durable sidecar restores those ids. Comparing
1459        // its wire form with the transcript reloaded above proves that the
1460        // persisted view is exactly the deterministic projection before we
1461        // use the restamped form for inversion.
1462        let (restamped_view, restamped_log) =
1463            reduce::project_messages(&reloaded_sidecar.messages, &policy, &reloaded_log);
1464        if messages_jsonl(&restamped_view)? != messages_jsonl(&reloaded_view)? {
1465            return Err(ServiceError::Operation(
1466                "persisted reduction view does not match its durable log and sidecar".into(),
1467            ));
1468        }
1469        if restamped_log != reloaded_log {
1470            return Err(ServiceError::Operation(
1471                "reapplying the durable reduction log changed its identity".into(),
1472            ));
1473        }
1474        let inverted =
1475            reduce::invert(&restamped_view, &reloaded_log, &reloaded_sidecar).map_err(operation)?;
1476        if inverted != session.messages {
1477            return Err(ServiceError::Operation(
1478                "reduction inversion did not restore the source messages byte-exactly".into(),
1479            ));
1480        }
1481
1482        let ratio = source_tokens as f64 / reduced_tokens.max(1) as f64;
1483        let sidecar_path = store.sidecar_path(&rescue_id);
1484        let reduction_log_path = store.reduction_log_path(&rescue_id).map_err(operation)?;
1485        let bootstrap_prompt = reduced_bootstrap_prompt(
1486            &params.locator,
1487            params.target_harness,
1488            &view_jsonl,
1489            &sidecar_path,
1490            &reduction_log_path,
1491        );
1492        let mut reduced_session = session.clone();
1493        reduced_session.meta.session_id = Some(rescue_id.clone());
1494        reduced_session.messages = view;
1495
1496        Ok(json!({
1497            "session": normalized_session_json(&reduced_session),
1498            "bootstrap_prompt": bootstrap_prompt,
1499            "receipt": {
1500                "id": rescue_id,
1501                "sidecar_id": rescue_id,
1502                "source_harness": params.locator.harness,
1503                "target_harness": params.target_harness.id(),
1504                "source_tokens": source_tokens,
1505                "reduced_tokens": reduced_tokens,
1506                "ratio": ratio,
1507                "source_bytes": source_bytes,
1508                "reduced_bytes": reduced_bytes,
1509                "reductions": reloaded_log.reductions.len(),
1510                "sidecar_path": sidecar_path,
1511                "reduction_log_path": reduction_log_path,
1512                "verified": true,
1513                "reversible": true,
1514            }
1515        }))
1516    }
1517
1518    /// Recognize the one request family whose waiting happens entirely
1519    /// outside this service's state, and hand a transport the half it can run
1520    /// off the task that owns the service.
1521    ///
1522    /// Opening a runtime is the only door here that waits on a foreign
1523    /// program: it spawns the harness's own binary and completes that
1524    /// program's protocol handshake, which takes as long as the program takes
1525    /// to answer. A transport that awaited the whole request inline would
1526    /// stop reading its own input for that whole time, so ONE slow launch
1527    /// would queue every later request on the same server — including reads
1528    /// like `sessions.discover` that touch no runtime at all. Splitting the
1529    /// request lets the transport spawn [`RuntimeOpen::open`] and keep
1530    /// reading, then pay only the short bookkeeping half
1531    /// ([`Self::register_open_runtime`]) when the runtime is up.
1532    ///
1533    /// `None` for every other method: those are answered by
1534    /// [`Self::handle_async`] as before.
1535    pub fn runtime_open(request: &Value) -> Option<RuntimeOpen> {
1536        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1537            return None;
1538        }
1539        let method = request.get("method").and_then(Value::as_str)?;
1540        if !RUNTIME_OPEN_METHODS.contains(&method) {
1541            return None;
1542        }
1543        Some(RuntimeOpen {
1544            id: request.get("id").cloned().unwrap_or(Value::Null),
1545            method: method.to_string(),
1546            params: request.get("params").cloned().unwrap_or_else(|| json!({})),
1547        })
1548    }
1549
1550    /// Recognize a [`DETACHED_METHODS`] request and hand a transport the
1551    /// whole of it: the service-state half is read here and now, and what
1552    /// remains waits on a foreign program with nothing of this service's in
1553    /// hand.
1554    ///
1555    /// Same reason as [`Self::runtime_open`], different doors. Probing a
1556    /// harness starts it and completes its handshake; couriering a message
1557    /// runs a `claude` process to completion; a conversation verb runs the
1558    /// harness's own CLI or calls its HTTP API. A transport that awaited any
1559    /// of those inline would stop reading its own input for that whole time,
1560    /// so one probe of an unhealthy harness would queue every later request
1561    /// on the same server.
1562    ///
1563    /// Unlike an opening runtime there is no bookkeeping half: the answer
1564    /// [`DetachedCall::run`] produces is the caller's complete response, so a
1565    /// transport writes it without coming back here.
1566    ///
1567    /// `None` for every other method — including the LIVE `sessions.new` /
1568    /// `sessions.reset` door and `runtimes.close`, which wait on a runtime
1569    /// connection this service owns and so are split off by
1570    /// [`Self::detach_runtime`] instead.
1571    pub fn detach(&self, request: &Value) -> Option<DetachedCall> {
1572        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1573            return None;
1574        }
1575        let method = request.get("method").and_then(Value::as_str)?;
1576        if !DETACHED_METHODS.contains(&method) {
1577            return None;
1578        }
1579        let id = request.get("id").cloned().unwrap_or(Value::Null);
1580        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1581        let work = match method {
1582            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe" => self
1583                .inventory_work(method, params)
1584                .map(DetachedWork::Inventory),
1585            "harness.v1.sessions.message" => {
1586                decode::<MessageSessionParams>(params).map(DetachedWork::Message)
1587            }
1588            _ => {
1589                let verb = match method {
1590                    "harness.v1.sessions.new" => crate::SessionVerb::New,
1591                    "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
1592                    "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
1593                    _ => crate::SessionVerb::Delete,
1594                };
1595                match decode::<crate::SessionMutation>(params) {
1596                    Ok(mutation) => {
1597                        match crate::sessions_control::door(&mutation.harness, verb) {
1598                            // The live door needs the open runtime connection
1599                            // this service owns; it stays inline.
1600                            Ok(crate::SessionDoor::Live(_)) => return None,
1601                            Ok(_) => Ok(DetachedWork::SessionMutation { verb, mutation }),
1602                            Err(error) => Err(session_control_error(error)),
1603                        }
1604                    }
1605                    Err(error) => Err(error),
1606                }
1607            }
1608        };
1609        Some(DetachedCall {
1610            id,
1611            method: method.to_string(),
1612            work: work.map(Work::Free),
1613        })
1614    }
1615
1616    /// Recognize the two doors that wait on a runtime THIS SERVICE OWNS, and
1617    /// hand a transport the whole of each by lending the connection out.
1618    ///
1619    /// `runtimes.close` surrenders its runtime for good; the LIVE
1620    /// `sessions.new` / `sessions.reset` door borrows one for the length of
1621    /// the slash command and gives it back through
1622    /// [`Self::finish_detached`]. Both are bounded by
1623    /// [`RUNTIME_CONTROL_DEADLINE`], and a wedged runtime spends all of it —
1624    /// which is exactly as long as a transport that awaited them inline would
1625    /// stop reading its own input.
1626    ///
1627    /// `None` for every other method, and for the `sessions.new` /
1628    /// `sessions.reset` doors that are not live: [`Self::detach`] owns those.
1629    pub fn detach_runtime(&mut self, request: &Value) -> Option<DetachedCall> {
1630        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1631            return None;
1632        }
1633        let method = request.get("method").and_then(Value::as_str)?;
1634        let id = request.get("id").cloned().unwrap_or(Value::Null);
1635        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1636        let work = match method {
1637            "harness.v1.runtimes.close" => decode::<RuntimeConnectionParams>(params)
1638                .and_then(|params| self.surrender_runtime(&params.connection))
1639                .map(|(runtime, process_group)| {
1640                    Work::Runtime(RuntimeWork::Close {
1641                        runtime,
1642                        process_group,
1643                    })
1644                }),
1645            "harness.v1.sessions.new" | "harness.v1.sessions.reset" => {
1646                let verb = if method == "harness.v1.sessions.new" {
1647                    crate::SessionVerb::New
1648                } else {
1649                    crate::SessionVerb::Reset
1650                };
1651                let mutation = decode::<crate::SessionMutation>(params).ok()?;
1652                // Everything but the live door — including a refusal and a
1653                // request naming no connection — is `detach`'s or
1654                // `handle_async`'s to answer.
1655                let Ok(crate::SessionDoor::Live(command)) =
1656                    crate::sessions_control::door(&mutation.harness, verb)
1657                else {
1658                    return None;
1659                };
1660                let connection = mutation
1661                    .connection
1662                    .clone()
1663                    .filter(|value| !value.trim().is_empty())?;
1664                self.lend_runtime(&connection).map(|runtime| {
1665                    let session = live_session_name(runtime.as_ref(), &mutation);
1666                    Work::Runtime(RuntimeWork::LiveCommand {
1667                        connection,
1668                        runtime,
1669                        verb,
1670                        mutation,
1671                        command,
1672                        session,
1673                    })
1674                })
1675            }
1676            _ => return None,
1677        };
1678        Some(DetachedCall {
1679            id,
1680            method: method.to_string(),
1681            work,
1682        })
1683    }
1684
1685    /// Take back whatever a detached call borrowed and hand over the caller's
1686    /// response. Every answer from [`DetachedCall::run`] comes through here,
1687    /// so a lent-out connection is back in the service before the response
1688    /// that used it is written.
1689    pub fn finish_detached(&mut self, answer: DetachedAnswer) -> Value {
1690        let DetachedAnswer { response, returned } = answer;
1691        if let Some(ReturnedRuntime {
1692            connection,
1693            runtime,
1694        }) = returned
1695        {
1696            self.runtimes_in_flight.remove(&connection);
1697            self.runtimes.insert(connection, runtime);
1698        }
1699        response
1700    }
1701
1702    /// Answer a request split out by [`Self::runtime_open`] and already
1703    /// awaited by [`RuntimeOpen::open`]: register the runtime this service now
1704    /// owns and build its JSON-RPC response.
1705    pub async fn finish_runtime_open(&mut self, opened: OpenedRuntime) -> Value {
1706        let OpenedRuntime { id, outcome } = opened;
1707        let result = match outcome {
1708            Ok(open) => self.register_open_runtime(open).await,
1709            Err(error) => Err(error),
1710        };
1711        service_response(id, result)
1712    }
1713
1714    /// Take ownership of an opened runtime.
1715    async fn register_open_runtime(
1716        &mut self,
1717        open: OpenRuntime,
1718    ) -> std::result::Result<Value, ServiceError> {
1719        match open {
1720            OpenRuntime::Hosted {
1721                runtime,
1722                capabilities,
1723                workspace,
1724            } => {
1725                self.insert_hosted_runtime(runtime, capabilities, workspace)
1726                    .await
1727            }
1728            OpenRuntime::Joined { runtime } => self.insert_runtime(runtime),
1729        }
1730    }
1731
1732    async fn runtime_call(
1733        &mut self,
1734        method: &str,
1735        params: Value,
1736    ) -> std::result::Result<Value, ServiceError> {
1737        match method {
1738            "harness.v1.runtimes.capabilities" => {
1739                let params = decode::<RuntimeBackendParams>(params)?;
1740                let backend = runtime_backend(&params)?;
1741                Ok(json!({
1742                    "harness": backend.harness(),
1743                    "capabilities": backend.capabilities(),
1744                }))
1745            }
1746            method if RUNTIME_OPEN_METHODS.contains(&method) => {
1747                self.register_open_runtime(open_runtime(method, params).await?)
1748                    .await
1749            }
1750            "harness.v1.runtimes.send_input" => {
1751                let params = decode::<RuntimeInputParams>(params)?;
1752                let image_urls = validate_runtime_image_urls(params.image_urls)?;
1753                let runtime = self.runtime_mut(&params.connection)?;
1754                let turn_id = within_control_deadline(
1755                    method,
1756                    runtime.send_input(RuntimeInput {
1757                        text: params.text,
1758                        image_urls,
1759                    }),
1760                )
1761                .await?
1762                .map_err(operation)?;
1763                Ok(json!({"turn_id": turn_id}))
1764            }
1765            "harness.v1.runtimes.interrupt" => {
1766                let params = decode::<RuntimeConnectionParams>(params)?;
1767                within_control_deadline(method, self.runtime_mut(&params.connection)?.interrupt())
1768                    .await?
1769                    .map_err(operation)?;
1770                Ok(json!({}))
1771            }
1772            "harness.v1.runtimes.steer" => {
1773                let params = decode::<RuntimeInputParams>(params)?;
1774                if !params.image_urls.is_empty() {
1775                    return Err(ServiceError::InvalidParams(
1776                        "runtime steering accepts text only".into(),
1777                    ));
1778                }
1779                let text = params.text.trim();
1780                if text.is_empty() || text.chars().count() > 50_000 {
1781                    return Err(ServiceError::InvalidParams(
1782                        "runtime steering requires 1 to 50,000 text characters".into(),
1783                    ));
1784                }
1785                within_control_deadline(
1786                    method,
1787                    self.runtime_mut(&params.connection)?
1788                        .steer(text.to_string()),
1789                )
1790                .await?
1791                .map_err(operation)?;
1792                Ok(json!({}))
1793            }
1794            "harness.v1.runtimes.respond" => {
1795                let params = decode::<RuntimeRespondParams>(params)?;
1796                let request_id = params.request_id.clone();
1797                within_control_deadline(
1798                    method,
1799                    self.runtime_mut(&params.connection)?
1800                        .respond(params.request_id, params.response),
1801                )
1802                .await?
1803                .map_err(operation)?;
1804                // ORCH-9: an answered request is no longer waiting for one.
1805                self.approvals.answered(&params.connection, &request_id);
1806                Ok(json!({}))
1807            }
1808            "harness.v1.runtimes.acquire_control" => {
1809                let params = decode::<RuntimeConnectionParams>(params)?;
1810                let snapshot = within_control_deadline(
1811                    method,
1812                    self.runtime_mut(&params.connection)?.acquire_control(),
1813                )
1814                .await?
1815                .map_err(operation)?;
1816                serde_json::to_value(snapshot)
1817                    .map_err(|error| ServiceError::Operation(error.to_string()))
1818            }
1819            "harness.v1.runtimes.heartbeat" => {
1820                let params = decode::<RuntimeConnectionParams>(params)?;
1821                let snapshot = within_control_deadline(
1822                    method,
1823                    self.runtime_mut(&params.connection)?.heartbeat(),
1824                )
1825                .await?
1826                .map_err(operation)?;
1827                serde_json::to_value(snapshot)
1828                    .map_err(|error| ServiceError::Operation(error.to_string()))
1829            }
1830            "harness.v1.runtimes.detach" => {
1831                let params = decode::<RuntimeConnectionParams>(params)?;
1832                let snapshot =
1833                    within_control_deadline(method, self.runtime_mut(&params.connection)?.detach())
1834                        .await?
1835                        .map_err(operation)?;
1836                serde_json::to_value(snapshot)
1837                    .map_err(|error| ServiceError::Operation(error.to_string()))
1838            }
1839            "harness.v1.runtimes.terminal_instructions" => {
1840                let params = decode::<RuntimeConnectionParams>(params)?;
1841                let launch = self
1842                    .terminal_launches
1843                    .get(&params.connection)
1844                    .ok_or_else(|| {
1845                        ServiceError::Operation(
1846                            "this runtime is not hosted for terminal attachment".into(),
1847                        )
1848                    })?;
1849                Ok(json!({"launch":launch}))
1850            }
1851            "harness.v1.runtimes.close" => {
1852                let params = decode::<RuntimeConnectionParams>(params)?;
1853                let (runtime, process_group) = self.surrender_runtime(&params.connection)?;
1854                close_runtime(runtime, process_group).await
1855            }
1856            _ => Err(ServiceError::MethodNotFound),
1857        }
1858    }
1859
1860    /// Deliver one message into a session that is running right now.
1861    #[cfg(feature = "adapter-api")]
1862    async fn message_call(&self, params: Value) -> std::result::Result<Value, ServiceError> {
1863        let params = decode::<MessageSessionParams>(params)?;
1864        Ok(message_live_session(&params, &crate::claude_peer::ProcessCourierRunner).await)
1865    }
1866
1867    #[cfg(feature = "adapter-api")]
1868    fn harness_settings_call(
1869        &self,
1870        method: &str,
1871        params: Value,
1872    ) -> std::result::Result<Value, ServiceError> {
1873        let homes = crate::HarnessHomes::default();
1874        match method {
1875            "harness.v1.harnesses.settings" => {
1876                let params = decode::<HarnessSettingsParams>(params)?;
1877                let report = crate::inspect_harness_interop_settings(&homes, &params.harness)
1878                    .map_err(|error| ServiceError::Operation(error.to_string()))?;
1879                serde_json::to_value(report)
1880                    .map_err(|error| ServiceError::Operation(error.to_string()))
1881            }
1882            "harness.v1.harnesses.configure" => {
1883                let params = decode::<ConfigureHarnessParams>(params)?;
1884                let report = crate::configure_harness_interop_settings(
1885                    &homes,
1886                    &params.harness,
1887                    &params.changes,
1888                    params.expected_revision.as_deref(),
1889                )
1890                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1891                serde_json::to_value(report)
1892                    .map_err(|error| ServiceError::Operation(error.to_string()))
1893            }
1894            _ => Err(ServiceError::MethodNotFound),
1895        }
1896    }
1897
1898    fn insert_runtime(
1899        &mut self,
1900        runtime: Box<dyn RuntimeConnection>,
1901    ) -> std::result::Result<Value, ServiceError> {
1902        let connection = format!("runtime-{}", self.next_runtime);
1903        self.next_runtime += 1;
1904        let handle = runtime.handle().clone();
1905        self.runtime_sequences
1906            .entry(handle.runtime_id.clone())
1907            .or_insert(0);
1908        self.runtimes.insert(connection.clone(), runtime);
1909        Ok(json!({"connection": connection, "handle": handle}))
1910    }
1911
1912    #[cfg(feature = "adapter-api")]
1913    async fn insert_hosted_runtime(
1914        &mut self,
1915        runtime: Box<dyn RuntimeConnection>,
1916        capabilities: crate::RuntimeCapabilities,
1917        workspace: PathBuf,
1918    ) -> std::result::Result<Value, ServiceError> {
1919        let (host, connection) = HostedHarnessRuntime::spawn(runtime, capabilities);
1920        let token: std::sync::Arc<str> = crate::server::generate_token().into();
1921        let server = crate::server::run_frontend_http(
1922            host.clone(),
1923            host.frontend_sender(),
1924            "127.0.0.1:0",
1925            token.clone(),
1926            connection.handle().runtime_id.clone(),
1927        )
1928        .await
1929        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1930        let source = LiveRuntimeSource {
1931            harness: connection.handle().harness.as_str().to_string(),
1932            session_id: connection.handle().runtime_id.clone(),
1933            workspace: workspace.clone(),
1934        };
1935        let registration = register_live_runtime(
1936            connection.handle().runtime_id.clone(),
1937            source.clone(),
1938            format!("http://{}", server.address()),
1939            token.to_string(),
1940        )
1941        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1942        let endpoint = registration.endpoint().to_string();
1943        let launch = StructuredLaunch {
1944            cwd: workspace,
1945            // Pin attachment to the executable hosting this runtime. A bare
1946            // `supercode` could resolve to an older global install whose CLI
1947            // does not understand the receipt it is being asked to open.
1948            program: std::env::current_exe()
1949                .ok()
1950                .map(|path| path.to_string_lossy().into_owned())
1951                .unwrap_or_else(|| "supercode".into()),
1952            arguments: vec![
1953                "harness".into(),
1954                "attach".into(),
1955                "--endpoint".into(),
1956                endpoint,
1957                "--harness".into(),
1958                source.harness,
1959                "--session".into(),
1960                source.session_id,
1961            ],
1962            env: BTreeMap::new(),
1963        };
1964        let lease = HostedRuntimeLease {
1965            connection,
1966            _host: host,
1967            _registration: registration,
1968            _server: server,
1969        };
1970        let opened = self.insert_runtime(Box::new(lease))?;
1971        let connection_id = opened["connection"]
1972            .as_str()
1973            .expect("insert_runtime returns a connection id")
1974            .to_string();
1975        self.terminal_launches.insert(connection_id, launch);
1976        Ok(opened)
1977    }
1978
1979    #[cfg(not(feature = "adapter-api"))]
1980    async fn insert_hosted_runtime(
1981        &mut self,
1982        runtime: Box<dyn RuntimeConnection>,
1983        _capabilities: crate::RuntimeCapabilities,
1984        _workspace: PathBuf,
1985    ) -> std::result::Result<Value, ServiceError> {
1986        self.insert_runtime(runtime)
1987    }
1988
1989    fn runtime_mut(
1990        &mut self,
1991        connection: &str,
1992    ) -> std::result::Result<&mut Box<dyn RuntimeConnection>, ServiceError> {
1993        if self.runtimes_in_flight.contains(connection) {
1994            return Err(self.lent_out(connection));
1995        }
1996        self.runtimes.get_mut(connection).ok_or_else(|| {
1997            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
1998        })
1999    }
2000
2001    /// What a caller is told about a connection that is out on a detached
2002    /// call. It is not gone and it is not free: it is mid-call, which is the
2003    /// same answer the runtime itself gives a second turn.
2004    fn lent_out(&self, connection: &str) -> ServiceError {
2005        ServiceError::Operation(format!(
2006            "runtime connection `{connection}`: a harness turn is already in progress"
2007        ))
2008    }
2009
2010    /// Take a runtime OUT of the service for the duration of one detached
2011    /// call, leaving its name marked as lent out.
2012    fn lend_runtime(
2013        &mut self,
2014        connection: &str,
2015    ) -> std::result::Result<Box<dyn RuntimeConnection>, ServiceError> {
2016        if self.runtimes_in_flight.contains(connection) {
2017            return Err(self.lent_out(connection));
2018        }
2019        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
2020            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2021        })?;
2022        self.runtimes_in_flight.insert(connection.to_string());
2023        Ok(runtime)
2024    }
2025
2026    /// Surrender a runtime for good: the connection and everything the
2027    /// service hung off it are gone before its teardown is even attempted.
2028    ///
2029    /// `close` is what a caller reaches for when a runtime has stopped
2030    /// answering, and a runtime that has stopped answering is exactly the one
2031    /// whose graceful close cannot complete: a hosted runtime's own loop
2032    /// parks on the call the runtime never answered, so it never dequeues the
2033    /// shutdown either. Keeping the entry until teardown succeeded made a
2034    /// wedged runtime permanent — every later call on that connection, and
2035    /// every new turn, answered "a harness turn is already in progress" with
2036    /// no way to take the connection back.
2037    fn surrender_runtime(
2038        &mut self,
2039        connection: &str,
2040    ) -> std::result::Result<(Box<dyn RuntimeConnection>, Option<u32>), ServiceError> {
2041        if self.runtimes_in_flight.contains(connection) {
2042            return Err(self.lent_out(connection));
2043        }
2044        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
2045            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2046        })?;
2047        let process_group = runtime_process_group(runtime.handle());
2048        let runtime_id = runtime.handle().runtime_id.clone();
2049        self.terminal_launches.remove(connection);
2050        self.runtime_sequences.remove(&runtime_id);
2051        self.approvals.forget(connection);
2052        Ok((runtime, process_group))
2053    }
2054
2055    /// SIGKILL the process group of every runtime this service owns, without
2056    /// waiting on any of them.
2057    ///
2058    /// A host leaving for good calls this BEFORE dropping the service. The
2059    /// handle this service holds is not the runtime's connection: a hosted
2060    /// runtime's real transport lives in the task driving it, so neither
2061    /// exiting the process nor dropping these handles reaches the harness
2062    /// process — while dropping them does remove each runtime's live-runtime
2063    /// receipt. Signalling first is what keeps a removed receipt from
2064    /// advertising a harness that is still running.
2065    pub fn kill_all_runtime_groups(&self) -> usize {
2066        self.runtimes
2067            .values()
2068            .filter(|runtime| kill_runtime_process_group(runtime_process_group(runtime.handle())))
2069            .count()
2070    }
2071
2072    /// ORCH-19: run one conversation-lifecycle verb through the harness's own
2073    /// door.
2074    ///
2075    /// Two doors, one shape. A CLI / HTTP / own-store door is self-contained
2076    /// in [`crate::sessions_control`]. A LIVE door (Hermes's and OpenClaw's
2077    /// `/new` and `/reset`, which are slash commands their gateway interprets
2078    /// INSIDE a session) is performed here, because only the service owns the
2079    /// open runtime connection — the command is typed through the very same
2080    /// `send_input` path a human's message takes, so supercode invents no
2081    /// private channel.
2082    async fn mutate_session(
2083        &mut self,
2084        verb: crate::SessionVerb,
2085        params: Value,
2086    ) -> std::result::Result<Value, ServiceError> {
2087        let mutation = decode::<crate::SessionMutation>(params)?;
2088        let door = crate::sessions_control::door(&mutation.harness, verb)
2089            .map_err(session_control_error)?;
2090        let outcome = match door {
2091            // The live door types the slash command through an open hosted
2092            // runtime, which only exists with the `adapter-api` feature; the
2093            // CLI / HTTP / own-store doors below need nothing extra.
2094            #[cfg(not(feature = "adapter-api"))]
2095            crate::SessionDoor::Live(command) => {
2096                return Err(ServiceError::Operation(format!(
2097                    "`{}` performs `sessions.{}` by typing `{command}` into a live driven \
2098                     session, which needs this build's `adapter-api` feature",
2099                    mutation.harness,
2100                    verb.as_str()
2101                )));
2102            }
2103            #[cfg(feature = "adapter-api")]
2104            crate::SessionDoor::Live(command) => {
2105                let connection = mutation
2106                    .connection
2107                    .clone()
2108                    .filter(|value| !value.trim().is_empty())
2109                    .ok_or_else(|| {
2110                        ServiceError::InvalidParams(format!(
2111                            "`{}` performs `sessions.{}` by typing `{command}` into a live \
2112                             driven session: pass the `connection` of an open runtime \
2113                             (`harness.v1.runtimes.start`)",
2114                            mutation.harness,
2115                            verb.as_str()
2116                        ))
2117                    })?;
2118                let runtime = self.runtime_mut(&connection)?;
2119                let session = live_session_name(runtime.as_ref(), &mutation);
2120                // Typing into a live session is a control call on an open
2121                // runtime, and a wedged runtime never accepts one, so it is
2122                // bounded exactly like the other control verbs. A transport
2123                // with a loop of its own lends the connection out instead of
2124                // waiting here: see [`Self::detach_runtime`].
2125                return type_live_command(runtime.as_mut(), verb, &mutation, command, session)
2126                    .await;
2127            }
2128            _ => run_session_mutation(verb, &mutation).await?,
2129        };
2130        serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
2131    }
2132
2133    /// Answer an inventory request whole, for callers that have nowhere to
2134    /// put the waiting half. A transport with a loop of its own splits it
2135    /// instead: see [`Self::detach`].
2136    async fn inventory_call(
2137        &self,
2138        method: &str,
2139        params: Value,
2140    ) -> std::result::Result<Value, ServiceError> {
2141        run_inventory(self.inventory_work(method, params)?).await
2142    }
2143
2144    /// The half of an inventory request that reads this service's state:
2145    /// resolve the selection and count the persisted sessions each row
2146    /// reports. What remains — finding executables, asking them their
2147    /// version, and (at `probe: handshake`) starting each harness and
2148    /// completing its protocol handshake — touches no service state at all.
2149    fn inventory_work(
2150        &self,
2151        method: &str,
2152        params: Value,
2153    ) -> std::result::Result<InventoryWork, ServiceError> {
2154        let mut params = decode::<HarnessInventoryParams>(params)?;
2155        if method == "harness.v1.harnesses.probe" {
2156            let harness = params.harness.take().ok_or_else(|| {
2157                ServiceError::InvalidParams("harnesses.probe requires `harness`".into())
2158            })?;
2159            params.harnesses = vec![harness];
2160        }
2161        let selected = params
2162            .harnesses
2163            .iter()
2164            .map(HarnessId::as_str)
2165            .collect::<std::collections::BTreeSet<_>>();
2166        let supported = harness_support_registry()
2167            .harnesses
2168            .into_iter()
2169            .filter(|descriptor| selected.is_empty() || selected.contains(descriptor.id.as_str()))
2170            .collect::<Vec<_>>();
2171        if !params.harnesses.is_empty() && supported.len() != selected.len() {
2172            let known = supported
2173                .iter()
2174                .map(|harness| harness.id.as_str())
2175                .collect::<std::collections::BTreeSet<_>>();
2176            let missing = params
2177                .harnesses
2178                .iter()
2179                .filter(|id| !known.contains(id.as_str()))
2180                .map(HarnessId::as_str)
2181                .collect::<Vec<_>>();
2182            return Err(ServiceError::InvalidParams(format!(
2183                "unknown harness(es): {}",
2184                missing.join(", ")
2185            )));
2186        }
2187        let global_counts = params
2188            .include_sessions
2189            .then(|| self.session_counts(None, &params.harnesses));
2190        let workspace_counts = params
2191            .include_sessions
2192            .then(|| {
2193                params
2194                    .workspace
2195                    .as_deref()
2196                    .map(|workspace| self.session_counts(Some(workspace), &params.harnesses))
2197            })
2198            .flatten();
2199        Ok(InventoryWork {
2200            params,
2201            supported,
2202            global_counts,
2203            workspace_counts,
2204        })
2205    }
2206
2207    #[cfg(feature = "adapter-api")]
2208    async fn harness_authentication_call(
2209        &self,
2210        method: &str,
2211        params: Value,
2212    ) -> std::result::Result<Value, ServiceError> {
2213        match method {
2214            "harness.v1.harnesses.auth.methods" | "harness.v1.harnesses.auth.verify" => {
2215                let params = decode::<HarnessAuthenticationParams>(params)?;
2216                serde_json::to_value(crate::inspect_harness_authentication(&params.harness).await)
2217                    .map_err(|error| ServiceError::Operation(error.to_string()))
2218            }
2219            "harness.v1.harnesses.auth.begin" => {
2220                let params = decode::<BeginHarnessAuthenticationParams>(params)?;
2221                let cwd = params
2222                    .cwd
2223                    .or_else(|| std::env::current_dir().ok())
2224                    .unwrap_or_else(|| PathBuf::from("."));
2225                let plan = crate::harness_authentication_plan(
2226                    &params.harness,
2227                    params.environment,
2228                    params.method,
2229                    &cwd,
2230                )
2231                .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
2232                serde_json::to_value(plan)
2233                    .map_err(|error| ServiceError::Operation(error.to_string()))
2234            }
2235            _ => Err(ServiceError::MethodNotFound),
2236        }
2237    }
2238
2239    fn session_counts(
2240        &self,
2241        workspace: Option<&Path>,
2242        harnesses: &[HarnessId],
2243    ) -> BTreeMap<String, usize> {
2244        let mut counts = BTreeMap::new();
2245        for session in self
2246            .catalog
2247            .discover(&DiscoveryQuery {
2248                workspace: workspace.map(Path::to_path_buf),
2249                harnesses: harnesses.to_vec(),
2250                ..DiscoveryQuery::default()
2251            })
2252            .unwrap_or_default()
2253        {
2254            *counts
2255                .entry(session.locator.harness.as_str().to_string())
2256                .or_insert(0) += 1;
2257        }
2258        counts
2259    }
2260}
2261
2262#[async_trait::async_trait]
2263impl SdkService for HarnessSessionService {
2264    fn capabilities(&self) -> SdkCapabilities {
2265        SdkCapabilities::default()
2266    }
2267
2268    async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError> {
2269        if request.operation == SdkOperation::Events {
2270            let events = self
2271                .poll_sdk_events()
2272                .await
2273                .into_iter()
2274                .map(|(_, event)| event)
2275                .collect::<Vec<_>>();
2276            return serde_json::to_value(events).map_err(|error| {
2277                SdkError::new(
2278                    SdkErrorCode::Execution,
2279                    request.operation,
2280                    error.to_string(),
2281                )
2282            });
2283        }
2284        if self.runtimes.is_empty()
2285            && matches!(
2286                request.operation,
2287                SdkOperation::Input
2288                    | SdkOperation::Interrupt
2289                    | SdkOperation::Steer
2290                    | SdkOperation::Respond
2291                    | SdkOperation::Close
2292            )
2293        {
2294            return Err(SdkError::unsupported(request.operation));
2295        }
2296        let method = request
2297            .operation
2298            .method()
2299            .ok_or_else(|| SdkError::unsupported(request.operation))?;
2300        let result = match request.operation {
2301            SdkOperation::Discover
2302            | SdkOperation::Load
2303            | SdkOperation::Export
2304            | SdkOperation::ProfilesList
2305            | SdkOperation::ProfilesGet
2306            | SdkOperation::ProfilesCreate
2307            | SdkOperation::ProfilesDelete
2308            | SdkOperation::SkillsList
2309            | SdkOperation::SkillsInstall
2310            | SdkOperation::SkillsRemove
2311            | SdkOperation::ChannelsList
2312            | SdkOperation::RoutesList
2313            | SdkOperation::TriggersList
2314            | SdkOperation::ChannelsStatus
2315            | SdkOperation::MemoryShow
2316            | SdkOperation::MemorySearch
2317            | SdkOperation::JobsList
2318            | SdkOperation::JobsGet
2319            | SdkOperation::JobsCreate
2320            | SdkOperation::JobsUpdate
2321            | SdkOperation::JobsPause
2322            | SdkOperation::JobsResume
2323            | SdkOperation::JobsRun
2324            | SdkOperation::JobsDelete
2325            | SdkOperation::JobsNotepad
2326            | SdkOperation::JobsNotepadSet
2327            | SdkOperation::JobsNotepadDelete
2328            | SdkOperation::RunsList
2329            | SdkOperation::RunsGet
2330            | SdkOperation::ApprovalsList
2331            | SdkOperation::OrchestrationLoad
2332            | SdkOperation::OrchestrationSave
2333            | SdkOperation::OrchestrationCompile
2334            | SdkOperation::OrchestrationDecompile
2335            | SdkOperation::OrchestrationImport
2336            | SdkOperation::OrchestrationExport
2337            | SdkOperation::WorkflowLoad => self.call(method, request.params),
2338            // ORCH-20: answering needs the live connection, so it takes the
2339            // async door and ends in `harness.v1.runtimes.respond`.
2340            SdkOperation::ApprovalsResolve => self.approvals_resolve(request.params).await,
2341            SdkOperation::Start
2342            | SdkOperation::Resume
2343            | SdkOperation::Input
2344            | SdkOperation::Interrupt
2345            | SdkOperation::Steer
2346            | SdkOperation::Respond
2347            | SdkOperation::Close => self.runtime_call(method, request.params).await,
2348            // ORCH-19 controlled tier. Every verb goes through the HARNESS'S
2349            // OWN door — its CLI, its HTTP API, or its slash command typed
2350            // into a live driven session — and returns the row re-read from
2351            // the harness's store afterwards.
2352            SdkOperation::SessionsNew => {
2353                self.mutate_session(crate::SessionVerb::New, request.params)
2354                    .await
2355            }
2356            SdkOperation::SessionsReset => {
2357                self.mutate_session(crate::SessionVerb::Reset, request.params)
2358                    .await
2359            }
2360            SdkOperation::SessionsArchive => {
2361                self.mutate_session(crate::SessionVerb::Archive, request.params)
2362                    .await
2363            }
2364            SdkOperation::SessionsDelete => {
2365                self.mutate_session(crate::SessionVerb::Delete, request.params)
2366                    .await
2367            }
2368            SdkOperation::Events => unreachable!("handled before method dispatch"),
2369        };
2370        result.map_err(|error| sdk_error(request.operation, error))
2371    }
2372
2373    async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError> {
2374        Ok(self
2375            .poll_sdk_events()
2376            .await
2377            .into_iter()
2378            .map(|(_, event)| event)
2379            .collect())
2380    }
2381}
2382
2383#[cfg(feature = "adapter-api")]
2384struct HostedRuntimeLease {
2385    connection: HostedHarnessConnection,
2386    _host: std::sync::Arc<HostedHarnessRuntime>,
2387    _registration: LiveRuntimeRegistration,
2388    _server: crate::server::FrontendHttpServer,
2389}
2390
2391#[async_trait::async_trait]
2392#[cfg(feature = "adapter-api")]
2393impl RuntimeConnection for HostedRuntimeLease {
2394    fn handle(&self) -> &crate::RuntimeHandle {
2395        self.connection.handle()
2396    }
2397
2398    async fn send_input(&mut self, input: RuntimeInput) -> crate::Result<Option<String>> {
2399        self.connection.send_input(input).await
2400    }
2401
2402    async fn next_event(&mut self) -> crate::Result<Option<crate::HarnessEvent>> {
2403        self.connection.next_event().await
2404    }
2405
2406    async fn interrupt(&mut self) -> crate::Result<()> {
2407        self.connection.interrupt().await
2408    }
2409
2410    // the lease must forward every verb its capabilities advertise; without
2411    // this, steer fell to the trait default and refused a turn it claimed
2412    async fn steer(&mut self, text: String) -> crate::Result<()> {
2413        self.connection.steer(text).await
2414    }
2415
2416    async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
2417        self.connection.respond(request_id, response).await
2418    }
2419
2420    async fn close(&mut self) -> crate::Result<()> {
2421        self.connection.close().await
2422    }
2423}
2424
2425/// One inventory request's waiting half, already separated from the service
2426/// state it reads. See [`HarnessSessionService::inventory_work`].
2427struct InventoryWork {
2428    params: HarnessInventoryParams,
2429    supported: Vec<crate::HarnessSupportDescriptor>,
2430    global_counts: Option<BTreeMap<String, usize>>,
2431    workspace_counts: Option<BTreeMap<String, usize>>,
2432}
2433
2434/// Perform one conversation-lifecycle verb through a door that is
2435/// self-contained in [`crate::sessions_control`]: the harness's own CLI, its
2436/// HTTP API, the orchestrator daemon's socket, or supercode's own store.
2437/// Touches no service state, so this runs on any task. The LIVE door is not
2438/// here — it types its slash command through a runtime connection the service
2439/// owns, and is performed by [`HarnessSessionService::mutate_session`].
2440async fn run_session_mutation(
2441    verb: crate::SessionVerb,
2442    mutation: &crate::SessionMutation,
2443) -> std::result::Result<crate::SessionMutationOutcome, ServiceError> {
2444    // Only the HTTP door actually awaits anything. The CLI, store and daemon
2445    // doors run the harness's own program, or its store, with calls that
2446    // block the calling THREAD from start to finish — a future that never
2447    // yields, which no timeout around it can interrupt and which would hold a
2448    // runtime worker for as long as the harness takes. They go to a blocking
2449    // task, where blocking is what the thread is for.
2450    let door =
2451        crate::sessions_control::door(&mutation.harness, verb).map_err(session_control_error)?;
2452    if let crate::SessionDoor::Http = door {
2453        return crate::sessions_control::mutate(verb, mutation)
2454            .await
2455            .map_err(session_control_error);
2456    }
2457    let mutation = mutation.clone();
2458    tokio::task::spawn_blocking(move || crate::sessions_control::mutate_blocking(verb, &mutation))
2459        .await
2460        .map_err(|error| {
2461            ServiceError::Operation(format!("the conversation verb could not be run: {error}"))
2462        })?
2463        .map_err(session_control_error)
2464}
2465
2466/// Probe every selected harness and assemble the report. Touches no service
2467/// state, so this runs on any task.
2468async fn run_inventory(work: InventoryWork) -> std::result::Result<Value, ServiceError> {
2469    let InventoryWork {
2470        params,
2471        supported,
2472        global_counts,
2473        workspace_counts,
2474    } = work;
2475    let probes = supported.into_iter().map(|descriptor| {
2476        let global = global_counts
2477            .as_ref()
2478            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2479        let workspace = workspace_counts
2480            .as_ref()
2481            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2482        probe_harness(descriptor, &params, global, workspace)
2483    });
2484    let harnesses = futures::future::join_all(probes).await;
2485    serde_json::to_value(HarnessInventoryReport {
2486        probe: params.probe,
2487        workspace: params.workspace,
2488        harnesses,
2489    })
2490    .map_err(|error| ServiceError::Operation(error.to_string()))
2491}
2492
2493async fn probe_harness(
2494    descriptor: crate::HarnessSupportDescriptor,
2495    params: &HarnessInventoryParams,
2496    global: Option<usize>,
2497    workspace: Option<usize>,
2498) -> LocalHarness {
2499    let launch = descriptor.runtime.default_launch.as_ref();
2500    // ORC-7: the orchestrator publishes no runtime launch — it is not an
2501    // adapter supercode connects a turn to. What "installed" means for it
2502    // is that its Node daemon entry is present, so the row answers from
2503    // that instead of from a PATH lookup it could never satisfy.
2504    let orchestrator_entry = (descriptor.id.as_str() == HarnessId::ORCHESTRATOR)
2505        .then(crate::orchestrator::daemon_entry)
2506        .and_then(Result::ok);
2507    let executable = match &orchestrator_entry {
2508        Some(entry) => Some(entry.clone()),
2509        None => launch.and_then(|launch| find_executable(&launch.program)),
2510    };
2511    let installed = executable.is_some();
2512    let version = if params.skip_versions || orchestrator_entry.is_some() {
2513        // The orchestrator's "executable" is a Node module, not a CLI
2514        // with a `--version` flag; running it to ask would start a daemon.
2515        None
2516    } else {
2517        match executable.as_deref() {
2518            Some(path) => executable_version(path).await,
2519            None => None,
2520        }
2521    };
2522    let configured = auth_evidence(descriptor.id.as_str());
2523    let mut auth = if configured {
2524        HarnessAuthState::Configured
2525    } else if matches!(
2526        descriptor.id.as_str(),
2527        HarnessId::CLAUDE_CODE | HarnessId::CODEX
2528    ) {
2529        // These two adapters have explicit native status/login contracts
2530        // and complete local evidence coverage (including Claude's macOS
2531        // Keychain-backed oauthAccount marker). Treating absent evidence
2532        // as unknown advertises a start that will only fail interactively.
2533        HarnessAuthState::Required
2534    } else {
2535        HarnessAuthState::Unknown
2536    };
2537    let mut runtime = if installed {
2538        HarnessRuntimeState::Degraded
2539    } else {
2540        HarnessRuntimeState::Unavailable
2541    };
2542    let is_orchestrator = descriptor.id.as_str() == HarnessId::ORCHESTRATOR;
2543    let mut reason = (!installed).then(|| {
2544        if is_orchestrator {
2545            format!(
2546                "{} is supported but its daemon entry `{}` was not found",
2547                descriptor.display_name,
2548                crate::orchestrator::DAEMON_ENTRY
2549            )
2550        } else {
2551            format!(
2552                "{} is supported but `{}` was not found on PATH",
2553                descriptor.display_name,
2554                launch
2555                    .map(|launch| launch.program.as_str())
2556                    .unwrap_or("executable")
2557            )
2558        }
2559    });
2560    let mut repair = (!installed).then(|| {
2561        if is_orchestrator {
2562            format!(
2563                "Install the `supercode-orchestrator` package so `{}` resolves.",
2564                crate::orchestrator::DAEMON_ENTRY
2565            )
2566        } else {
2567            format!(
2568                "Install {} and ensure `{}` is on PATH.",
2569                descriptor.display_name,
2570                launch
2571                    .map(|launch| launch.program.as_str())
2572                    .unwrap_or("its executable")
2573            )
2574        }
2575    });
2576
2577    if installed && params.probe == HarnessProbeLevel::Handshake {
2578        let backend_params = RuntimeBackendParams {
2579            harness: descriptor.id.clone(),
2580            protocol: None,
2581            launch: None,
2582            base_url: None,
2583            policy: RuntimePolicy::Default,
2584        };
2585        match runtime_backend(&backend_params) {
2586            Ok(backend) => {
2587                let cwd = params
2588                    .workspace
2589                    .clone()
2590                    .or_else(|| std::env::current_dir().ok())
2591                    .unwrap_or_else(|| PathBuf::from("."));
2592                let isolated = descriptor
2593                    .runtime
2594                    .default_launch
2595                    .clone()
2596                    .and_then(|launch| IsolatedProbeHome::new(descriptor.id.as_str(), launch).ok());
2597                let Some(isolated) = isolated else {
2598                    reason = Some(
2599                        "No-prompt runtime handshake could not create its isolated harness home."
2600                            .into(),
2601                    );
2602                    repair = Some(
2603                        "Check temporary-directory permissions, then run the handshake probe again."
2604                            .into(),
2605                    );
2606                    let running = probe_running_instance(descriptor.id.as_str());
2607                    return LocalHarness {
2608                        gateway: gateway_health(
2609                            descriptor.id.as_str(),
2610                            installed,
2611                            running.as_ref(),
2612                            version.as_deref(),
2613                        ),
2614                        id: descriptor.id,
2615                        display_name: descriptor.display_name,
2616                        supported: true,
2617                        installed,
2618                        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2619                        version,
2620                        auth,
2621                        runtime,
2622                        protocol: descriptor.runtime.protocol,
2623                        capabilities: descriptor.runtime.capabilities.clone(),
2624                        effective_capabilities: descriptor.runtime.capabilities,
2625                        sessions: HarnessSessionCounts { global, workspace },
2626                        running,
2627                        reason,
2628                        repair,
2629                    };
2630                };
2631                match tokio::time::timeout(
2632                    Duration::from_secs(30),
2633                    backend.start(RuntimeStartRequest {
2634                        cwd,
2635                        launch: Some(isolated.launch.clone()),
2636                        mcp_servers: Vec::new(),
2637                    }),
2638                )
2639                .await
2640                {
2641                    Ok(Ok(mut connection)) => {
2642                        match stabilize_handshake(connection.as_mut()).await {
2643                            Ok(()) => {
2644                                auth = HarnessAuthState::Ready;
2645                                runtime = HarnessRuntimeState::Ready;
2646                                reason = Some(
2647                                    "No-prompt runtime handshake remained healthy through the startup stabilization window; no model request was sent."
2648                                        .into(),
2649                                );
2650                                repair = None;
2651                            }
2652                            Err(message) => {
2653                                auth = if looks_like_auth_error(&message) {
2654                                    HarnessAuthState::Required
2655                                } else if configured {
2656                                    HarnessAuthState::Configured
2657                                } else {
2658                                    HarnessAuthState::Unknown
2659                                };
2660                                reason = Some(format!(
2661                                    "No-prompt runtime handshake became unhealthy during startup: {message}"
2662                                ));
2663                                repair = Some(if auth == HarnessAuthState::Required {
2664                                    format!(
2665                                        "Run `{}` interactively once and complete sign-in, then probe again.",
2666                                        launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2667                                    )
2668                                } else {
2669                                    "Run the harness directly to inspect its startup failure, then probe again."
2670                                        .into()
2671                                });
2672                            }
2673                        }
2674                        let _ =
2675                            tokio::time::timeout(Duration::from_secs(3), connection.close()).await;
2676                    }
2677                    Ok(Err(error)) => {
2678                        let message = truncate_text(&error.to_string(), 500);
2679                        auth = if looks_like_auth_error(&message) {
2680                            HarnessAuthState::Required
2681                        } else if configured {
2682                            HarnessAuthState::Configured
2683                        } else {
2684                            HarnessAuthState::Unknown
2685                        };
2686                        reason = Some(format!("No-prompt runtime handshake failed: {message}"));
2687                        repair = Some(if auth == HarnessAuthState::Required {
2688                            format!(
2689                                "Run `{}` interactively once and complete sign-in, then probe again.",
2690                                launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2691                            )
2692                        } else {
2693                            "Check the harness installation and run the handshake probe again."
2694                                .into()
2695                        });
2696                    }
2697                    Err(_) => {
2698                        reason =
2699                            Some("No-prompt runtime handshake timed out after 30 seconds.".into());
2700                        repair = Some("Run the harness directly to check startup or authentication, then probe again.".into());
2701                    }
2702                }
2703                // Keep the isolated home alive through process teardown.
2704                // Otherwise the compiler may release the last meaningful
2705                // use after cloning `launch`, and a still-starting CLI can
2706                // recreate its state directory after Drop removed it.
2707                // Some Node-based launchers finish a short asynchronous
2708                // installation-id write just after their parent process
2709                // is reaped. Remove once immediately, allow that bounded
2710                // writer to settle, then perform the authoritative pass.
2711                let _ = isolated.cleanup();
2712                tokio::time::sleep(Duration::from_millis(250)).await;
2713                if let Err(error) = isolated.cleanup() {
2714                    auth = if configured {
2715                        HarnessAuthState::Configured
2716                    } else {
2717                        HarnessAuthState::Unknown
2718                    };
2719                    runtime = HarnessRuntimeState::Degraded;
2720                    reason = Some(format!(
2721                        "No-prompt runtime handshake could not remove its isolated harness home: {error}"
2722                    ));
2723                    repair = Some(
2724                        "Check temporary-directory permissions, remove the reported disposable probe home, then run the handshake again."
2725                            .into(),
2726                    );
2727                }
2728            }
2729            Err(error) => {
2730                reason = Some(error_message(error));
2731            }
2732        }
2733    } else if installed && configured {
2734        reason = Some("Executable and local authentication evidence found; use a handshake probe to verify readiness.".into());
2735    } else if installed && auth == HarnessAuthState::Required {
2736        reason = Some("Executable found, but no native authentication evidence is present.".into());
2737        repair = Some(format!(
2738            "Run `supercode harness login {}` to use the harness-owned sign-in flow.",
2739            descriptor.id.as_str()
2740        ));
2741    } else if installed {
2742        reason = Some("Executable found; authentication readiness is unknown until a no-prompt handshake succeeds.".into());
2743        repair = Some(format!(
2744            "Run `{}` interactively once if sign-in is required, or use `--probe handshake`.",
2745            launch
2746                .map(|launch| launch.program.as_str())
2747                .unwrap_or("the harness")
2748        ));
2749    }
2750
2751    let effective_capabilities = if installed {
2752        descriptor.runtime.capabilities.clone()
2753    } else {
2754        unavailable_capabilities()
2755    };
2756    let running = probe_running_instance(descriptor.id.as_str());
2757    LocalHarness {
2758        gateway: gateway_health(
2759            descriptor.id.as_str(),
2760            installed,
2761            running.as_ref(),
2762            version.as_deref(),
2763        ),
2764        id: descriptor.id,
2765        display_name: descriptor.display_name,
2766        supported: true,
2767        installed,
2768        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2769        version,
2770        auth,
2771        runtime,
2772        protocol: descriptor.runtime.protocol,
2773        capabilities: descriptor.runtime.capabilities,
2774        effective_capabilities,
2775        sessions: HarnessSessionCounts { global, workspace },
2776        running,
2777        reason,
2778        repair,
2779    }
2780}
2781
2782async fn stabilize_handshake(connection: &mut dyn RuntimeConnection) -> Result<(), String> {
2783    let deadline = tokio::time::Instant::now() + Duration::from_secs(3);
2784    loop {
2785        let now = tokio::time::Instant::now();
2786        if now >= deadline {
2787            return Ok(());
2788        }
2789        match tokio::time::timeout(deadline - now, connection.next_event()).await {
2790            Err(_) => return Ok(()),
2791            Ok(Ok(Some(event))) => {
2792                if let Some(message) = handshake_event_failure(&event) {
2793                    return Err(truncate_text(&message, 500));
2794                }
2795            }
2796            Ok(Ok(None)) => return Err("runtime transport closed during startup".into()),
2797            Ok(Err(error)) => return Err(error.to_string()),
2798        }
2799    }
2800}
2801
2802fn handshake_event_failure(event: &crate::HarnessEvent) -> Option<String> {
2803    let detail = event
2804        .payload
2805        .get("message")
2806        .or_else(|| event.payload.get("line"))
2807        .and_then(Value::as_str)
2808        .unwrap_or(event.kind.as_str());
2809    match event.kind.as_str() {
2810        "transport_closed" => Some("runtime transport closed during startup".into()),
2811        "transport_error" => Some(format!("runtime transport error: {detail}")),
2812        "malformed_output" => Some(format!("runtime emitted non-protocol output: {detail}")),
2813        // Stderr is retained as a runtime event, but is not transport health.
2814        // Grok, for example, can log an AuthorizationRequired error from an
2815        // optional background worker while its ACP session continues to send
2816        // updates and complete prompts normally.
2817        _ => None,
2818    }
2819}
2820
2821fn indexed_claude_window(
2822    locator: &SessionLocator,
2823    options: &SessionLoadOptions,
2824) -> std::result::Result<Option<Value>, ServiceError> {
2825    use supercode_interchange::session::ClaudeReadIndex;
2826    // Exact parent-only window: recursive/full-artifact requests retain the
2827    // existing owner. This is not a bounded display-history substitution.
2828    if locator.harness.as_str() != HarnessId::CLAUDE_CODE
2829        || options.include_subagents != Some(false)
2830    {
2831        return Ok(None);
2832    }
2833    let crate::StorageLocator::File { path } = &locator.storage else {
2834        return Ok(None);
2835    };
2836    if !ClaudeReadIndex::supports(path)
2837        .map_err(|error| ServiceError::Operation(error.to_string()))?
2838    {
2839        return Ok(None);
2840    }
2841    let mut index = ClaudeReadIndex::open(path, Fidelity::ByteLossless)
2842        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2843    let total = index.len();
2844    let (offset, end) = projected_message_window(total, options);
2845    let session = index
2846        .read_messages(offset..end)
2847        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2848    let summary = index
2849        .read_summary()
2850        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2851    let selected_options = SessionLoadOptions {
2852        message_offset: None,
2853        message_limit: None,
2854        message_tail: None,
2855        ..options.clone()
2856    };
2857    let mut selected = projected_session_json(&session, &selected_options);
2858    selected["raw_record_count"] = json!(index.raw_record_count());
2859    Ok(Some(json!({
2860        "session": selected,
2861        "summary": projected_session_summary(&summary, options),
2862        "window": {
2863            "has_more": offset > 0 || end < total, "has_newer": end < total,
2864            "has_older": offset > 0, "newer_items": index.item_count(end..total),
2865            "offset": offset, "older_items": index.item_count(0..offset),
2866            "returned": end - offset, "total_messages": total,
2867        }
2868    })))
2869}
2870
2871fn projected_session_result(session: &Session, options: &SessionLoadOptions) -> Value {
2872    let total_messages = session.messages.len();
2873    let (offset, end) = projected_message_window(total_messages, options);
2874    json!({
2875        "session": projected_session_json(session, options),
2876        "summary": projected_session_summary(session, options),
2877        "window": {
2878            "has_more": offset > 0 || end < total_messages,
2879            "has_newer": end < total_messages,
2880            "has_older": offset > 0,
2881            "newer_items": normalized_item_count(&session.messages[end..]),
2882            "offset": offset,
2883            "older_items": normalized_item_count(&session.messages[..offset]),
2884            "returned": end.saturating_sub(offset),
2885            "total_messages": total_messages,
2886        }
2887    })
2888}
2889
2890fn normalized_item_count(messages: &[crate::ChatMessage]) -> usize {
2891    messages
2892        .iter()
2893        .map(|message| {
2894            let conversation = usize::from(
2895                matches!(message.role, Role::Assistant | Role::User)
2896                    && message_has_content(message),
2897            );
2898            let tool_result =
2899                usize::from(message.role == Role::Tool && message_has_content(message));
2900            conversation + tool_result + message.tool_calls().len()
2901        })
2902        .sum()
2903}
2904
2905fn projected_session_summary(session: &Session, options: &SessionLoadOptions) -> Value {
2906    let mut conversational = session.messages.iter().filter(|message| {
2907        matches!(message.role, Role::Assistant | Role::User) && message_has_content(message)
2908    });
2909    let first_message = conversational.clone().next();
2910    let last_message = conversational.next_back();
2911    let mut assistant = session
2912        .messages
2913        .iter()
2914        .filter(|message| message.role == Role::Assistant && message_has_content(message));
2915    let first_assistant_message = assistant.clone().next();
2916    let last_assistant_message = assistant.next_back();
2917    let end_of_turn = session
2918        .messages
2919        .iter()
2920        .rev()
2921        .find(|message| message.role != Role::System)
2922        .is_some_and(|message| {
2923            message.role == Role::Assistant
2924                && message_has_content(message)
2925                && message.tool_calls().is_empty()
2926        });
2927    let project = |message: Option<&crate::ChatMessage>| {
2928        message.map(|message| project_inline_media(message_json(message), options))
2929    };
2930    json!({
2931        "end_of_turn": end_of_turn,
2932        "first_assistant_message": project(first_assistant_message),
2933        "first_message": project(first_message),
2934        "last_assistant_message": project(last_assistant_message),
2935        "last_assistant_text": last_assistant_message.map(message_text).unwrap_or_default(),
2936        "last_message": project(last_message),
2937    })
2938}
2939
2940fn message_has_content(message: &crate::ChatMessage) -> bool {
2941    message
2942        .content
2943        .as_deref()
2944        .is_some_and(|content| !content.trim().is_empty())
2945        || message
2946            .content_parts
2947            .as_ref()
2948            .is_some_and(|parts| !parts.is_empty())
2949}
2950
2951fn message_text(message: &crate::ChatMessage) -> String {
2952    if let Some(content) = &message.content {
2953        return content.clone();
2954    }
2955    message
2956        .content_parts
2957        .as_ref()
2958        .into_iter()
2959        .flatten()
2960        .filter_map(|part| part.get("text").and_then(Value::as_str))
2961        .collect::<Vec<_>>()
2962        .join("\n")
2963}
2964
2965fn projected_session_json(session: &Session, options: &SessionLoadOptions) -> Value {
2966    let (offset, end) = projected_message_window(session.messages.len(), options);
2967    let messages = session.messages[offset..end]
2968        .iter()
2969        .map(|message| project_inline_media(message_json(message), options))
2970        .collect::<Vec<_>>();
2971    let subagents = if options.include_subagents.unwrap_or(true) {
2972        // The reported window describes the top-level transcript. Applying it
2973        // recursively would silently truncate subagents without returning a
2974        // window for each child. Keep their histories complete while carrying
2975        // the caller's media policy through the tree.
2976        let subagent_options = SessionLoadOptions {
2977            message_limit: None,
2978            message_offset: None,
2979            message_tail: None,
2980            ..options.clone()
2981        };
2982        session
2983            .subagents
2984            .iter()
2985            .map(|subagent| projected_session_json(subagent, &subagent_options))
2986            .collect::<Vec<_>>()
2987    } else {
2988        Vec::new()
2989    };
2990    json!({
2991        "source": match session.meta.source {
2992            SessionSource::ClaudeCode => "claude_code",
2993            SessionSource::Codex => "codex",
2994            SessionSource::Gemini => "gemini",
2995            SessionSource::Goose => "goose",
2996            SessionSource::Grok => "grok",
2997            SessionSource::Native => "native",
2998            SessionSource::OpenClaw => "openclaw",
2999            SessionSource::Hermes => "hermes",
3000            SessionSource::OpenCode => "opencode",
3001            SessionSource::Pi => "pi",
3002        },
3003        "session_id": session.meta.session_id,
3004        "ended_at": session.meta.ended_at,
3005        "end_reason": session.meta.end_reason,
3006        "model": session.meta.model,
3007        "cwd": session.meta.cwd,
3008        "system_prompt": session.meta.system_prompt,
3009        "agent_id": session.meta.agent_id,
3010        "parent_tool_use_id": session.meta.parent_tool_use_id,
3011        "lineage": session.meta.lineage,
3012        "messages": messages,
3013        "subagents": subagents,
3014        "raw_record_count": session.raw.len(),
3015        "parse_error_lines": session.parse_error_lines,
3016    })
3017}
3018
3019fn projected_message_window(total: usize, options: &SessionLoadOptions) -> (usize, usize) {
3020    if let Some(tail) = options.message_tail {
3021        return (total.saturating_sub(tail), total);
3022    }
3023    let offset = options.message_offset.unwrap_or(0).min(total);
3024    let end = options
3025        .message_limit
3026        .map(|limit| offset.saturating_add(limit).min(total))
3027        .unwrap_or(total);
3028    (offset, end)
3029}
3030
3031fn project_inline_media(mut message: Value, options: &SessionLoadOptions) -> Value {
3032    let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else {
3033        return message;
3034    };
3035    for part in parts {
3036        let Some(url) = part
3037            .get("image_url")
3038            .and_then(|image| image.get("url"))
3039            .and_then(Value::as_str)
3040        else {
3041            continue;
3042        };
3043        let Some(rest) = url.strip_prefix("data:") else {
3044            continue;
3045        };
3046        let Some((media_type, encoded)) = rest.split_once(";base64,") else {
3047            continue;
3048        };
3049        let padding = usize::from(encoded.ends_with('=')) + usize::from(encoded.ends_with("=="));
3050        let decoded_bytes = encoded.len().saturating_mul(3) / 4;
3051        let decoded_bytes = decoded_bytes.saturating_sub(padding);
3052        let should_elide = matches!(options.inline_media, InlineMediaMode::Metadata)
3053            || options
3054                .max_inline_media_bytes
3055                .is_some_and(|limit| decoded_bytes > limit);
3056        if should_elide {
3057            *part = json!({
3058                "type": "media_reference",
3059                "media_type": media_type,
3060                "encoding": "base64",
3061                "encoded_bytes": encoded.len(),
3062                "decoded_bytes": decoded_bytes,
3063                "omitted": true,
3064            });
3065        }
3066    }
3067    message
3068}
3069
3070#[derive(Deserialize)]
3071struct LocatorParams {
3072    locator: SessionLocator,
3073    /// Optional fidelity for the READ surfaces (`sessions.load`,
3074    /// `sessions.follow`).
3075    ///
3076    /// Omitted means [`Fidelity::Semantic`]: these two methods only ever
3077    /// produce a read-only view, and a compacted or resumed-across-files
3078    /// transcript — the everyday shape of a long Claude Code session — has no
3079    /// losslessly reconstructable record graph, so refusing to render it made
3080    /// the mirror unusable rather than accurate. A caller that intends to
3081    /// CONTINUE from what it reads asks for a lossless level explicitly and
3082    /// gets the strict refusal back. Every other method (export, translate,
3083    /// branch, handoff, resume_instructions) is lossless-only and has no
3084    /// such knob.
3085    #[serde(default)]
3086    fidelity: Option<Fidelity>,
3087    /// Optional bounded frontend projection. Absent preserves the historical
3088    /// complete-session read contract.
3089    #[serde(default)]
3090    view: Option<SessionReadView>,
3091}
3092
3093#[derive(Deserialize)]
3094struct SessionReadView {
3095    /// Number of trailing normalized messages to return. Zero is treated as
3096    /// one so a caller cannot accidentally request an unbounded empty mode.
3097    #[serde(default)]
3098    tail_messages: Option<usize>,
3099    /// Whether Claude Code child transcripts belong in this view. The
3100    /// frontend default is false; the legacy no-view path remains true.
3101    #[serde(default)]
3102    include_subagents: bool,
3103    /// Preserve human-visible native history across model-context compaction.
3104    #[serde(default)]
3105    display_history: bool,
3106    /// Bound each individual text field so a single tool result cannot turn a
3107    /// small message window into a hundred-megabyte RPC response.
3108    #[serde(default)]
3109    max_message_chars: Option<usize>,
3110}
3111
3112impl LocatorParams {
3113    fn read_fidelity(&self) -> Fidelity {
3114        self.fidelity.unwrap_or(Fidelity::Semantic)
3115    }
3116
3117    fn include_subagents(&self) -> bool {
3118        self.view
3119            .as_ref()
3120            .map(|view| view.include_subagents)
3121            .unwrap_or(true)
3122    }
3123
3124    fn tail_messages(&self) -> Option<usize> {
3125        self.view
3126            .as_ref()
3127            .and_then(|view| view.tail_messages)
3128            .map(|limit| limit.clamp(1, 5_000))
3129    }
3130
3131    fn display_history(&self) -> bool {
3132        self.view.as_ref().is_some_and(|view| view.display_history)
3133    }
3134
3135    fn max_message_chars(&self) -> Option<usize> {
3136        self.view
3137            .as_ref()
3138            .and_then(|view| view.max_message_chars)
3139            .map(|limit| limit.clamp(256, 64_000))
3140    }
3141
3142    fn bound_session(&self, session: &mut Session) {
3143        bound_session_view(session, self.tail_messages(), self.max_message_chars());
3144    }
3145}
3146
3147#[derive(Debug, Clone, Copy, Default, Deserialize)]
3148#[serde(rename_all = "snake_case")]
3149enum InlineMediaMode {
3150    #[default]
3151    Full,
3152    Metadata,
3153}
3154
3155#[derive(Debug, Clone, Default, Deserialize)]
3156#[serde(default)]
3157struct SessionLoadOptions {
3158    include_subagents: Option<bool>,
3159    inline_media: InlineMediaMode,
3160    max_inline_media_bytes: Option<usize>,
3161    message_limit: Option<usize>,
3162    message_offset: Option<usize>,
3163    message_tail: Option<usize>,
3164}
3165
3166impl SessionLoadOptions {
3167    fn validate(&self) -> std::result::Result<(), ServiceError> {
3168        if self.message_tail.is_some()
3169            && (self.message_limit.is_some() || self.message_offset.is_some())
3170        {
3171            return Err(ServiceError::InvalidParams(
3172                "sessions.load options.message_tail cannot be combined with message_limit or message_offset"
3173                    .into(),
3174            ));
3175        }
3176        Ok(())
3177    }
3178}
3179
3180#[derive(Deserialize)]
3181struct LoadSessionParams {
3182    #[serde(flatten)]
3183    read: LocatorParams,
3184    #[serde(default)]
3185    options: Option<SessionLoadOptions>,
3186}
3187
3188#[derive(Deserialize)]
3189struct UnfollowParams {
3190    subscription: String,
3191}
3192
3193#[derive(Debug, Deserialize)]
3194#[serde(deny_unknown_fields)]
3195struct IndexResizeParams {
3196    subscription: String,
3197    limit: usize,
3198}
3199
3200#[derive(Deserialize)]
3201struct ActivitySubscribeParams {
3202    locators: Vec<SessionLocator>,
3203    #[serde(default)]
3204    homes: crate::HarnessHomes,
3205}
3206
3207#[derive(Deserialize)]
3208struct MessageSessionParams {
3209    locator: SessionLocator,
3210    text: String,
3211    /// Same storage roots discovery accepts, so a caller (and a test) can
3212    /// point the live-session registry somewhere other than `$HOME`.
3213    #[serde(default)]
3214    homes: crate::HarnessHomes,
3215}
3216
3217#[derive(Deserialize)]
3218#[serde(deny_unknown_fields)]
3219struct HarnessSettingsParams {
3220    harness: String,
3221}
3222
3223#[derive(Deserialize)]
3224#[serde(deny_unknown_fields)]
3225struct ConfigureHarnessParams {
3226    harness: String,
3227    #[serde(default)]
3228    changes: Vec<crate::HarnessSettingChange>,
3229    #[serde(default)]
3230    expected_revision: Option<String>,
3231}
3232
3233fn claude_inbound_controls_or_error(homes: &crate::HarnessHomes) -> (Value, Value) {
3234    match crate::inspect_harness_interop_settings(homes, HarnessId::CLAUDE_CODE) {
3235        Ok(report) => (
3236            serde_json::to_value(report).unwrap_or(Value::Null),
3237            Value::Null,
3238        ),
3239        Err(error) => (
3240            Value::Null,
3241            Value::String(format!(
3242                "Supercode could not inspect Claude Code inbound controls: {error}"
3243            )),
3244        ),
3245    }
3246}
3247
3248/// Deliver `text` into a session that is running right now, or say why not.
3249///
3250/// A refusal is a RESULT, not a JSON-RPC error: "that session is persisted
3251/// only" is an answer about the session, which a mirror renders next to the
3252/// transcript, and this service's error envelope carries no structured data
3253/// field a machine-readable reason could survive in.
3254///
3255/// `delivered_to_bus` is the honest ceiling of what the courier proves. The
3256/// message reached the receiving session's inbox; whether that session ever
3257/// reads it is governed by ITS OWN inbound controls (`crossSessionInbound`,
3258/// approval dialogs), which Supercode neither sees nor overrides.
3259async fn message_live_session(
3260    params: &MessageSessionParams,
3261    runner: &dyn crate::claude_peer::CourierRunner,
3262) -> Value {
3263    if params.locator.harness.as_str() != HarnessId::CLAUDE_CODE {
3264        return json!({
3265            "delivered_to_bus": false,
3266            "refusal": {
3267                "reason": crate::claude_peer::ClaudePeerRefusal::HarnessUnsupported.as_str(),
3268                "message": format!(
3269                    "`{}` does not publish a live-session registry; only claude-code sessions can be messaged in place",
3270                    params.locator.harness.as_str()
3271                ),
3272            },
3273        });
3274    }
3275    let (inbound_controls, inbound_controls_error) =
3276        claude_inbound_controls_or_error(&params.homes);
3277    match crate::claude_peer::message_claude_peer(
3278        &params.homes,
3279        &params.locator.session_id,
3280        &params.text,
3281        runner,
3282    )
3283    .await
3284    {
3285        Ok(delivery) => json!({
3286            "delivered_to_bus": true,
3287            "target": {
3288                "session_id": delivery.target.session_id,
3289                "name": delivery.target.name,
3290                "pid": delivery.target.pid,
3291                "cwd": delivery.target.cwd,
3292                "status": delivery.target.status.map(|status| status.as_str()),
3293            },
3294            "courier": {
3295                "model": crate::claude_peer::COURIER_MODEL,
3296                "report": delivery.courier_report,
3297            },
3298            "inbound_controls": inbound_controls,
3299            "inbound_controls_error": inbound_controls_error,
3300        }),
3301        Err(refusal) => json!({
3302            "delivered_to_bus": false,
3303            "refusal": {"reason": refusal.reason.as_str(), "message": refusal.message},
3304            "inbound_controls": inbound_controls,
3305            "inbound_controls_error": inbound_controls_error,
3306        }),
3307    }
3308}
3309
3310/// Source identity of one follow subscription, plus the last lifecycle state
3311/// already reported on it. The follower itself stays purely persistence-facing.
3312// Only the adapter-api poll reads these; the subscription bookkeeping itself is
3313// shared by both builds.
3314#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3315struct FollowedSource {
3316    harness: String,
3317    session_id: String,
3318    reported: Option<String>,
3319}
3320
3321#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3322struct ActivitySubscription {
3323    locators: Vec<SessionLocator>,
3324    homes: crate::HarnessHomes,
3325    reported: BTreeMap<(String, String), crate::SessionActivity>,
3326}
3327
3328fn peers_for_descriptors(
3329    descriptors: &[SessionDescriptor],
3330    homes: &HarnessHomes,
3331) -> Vec<crate::claude_peer::ClaudePeerSession> {
3332    if descriptors
3333        .iter()
3334        .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
3335    {
3336        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
3337    } else {
3338        Vec::new()
3339    }
3340}
3341
3342/// Add the live address that makes an indexed row behaviorally equivalent to a discovered row.
3343///
3344/// The durable index owns only persistence metadata. Live endpoints remain projections: every
3345/// message/attach operation revalidates its authority, so publishing one here never trusts a stale
3346/// browser-held handle. Reading the Claude registry once per batch keeps this O(peers + rows).
3347fn live_descriptor_value(
3348    session: &SessionDescriptor,
3349    peers: &[crate::claude_peer::ClaudePeerSession],
3350) -> std::result::Result<Value, ServiceError> {
3351    let mut value = serde_json::to_value(session)
3352        .map_err(|error| ServiceError::Operation(error.to_string()))?;
3353    if let Some(workspace) = &session.cwd {
3354        let source = LiveRuntimeSource {
3355            harness: session.locator.harness.as_str().to_string(),
3356            session_id: session.locator.session_id.clone(),
3357            workspace: workspace.clone(),
3358        };
3359        if let Some(endpoint) = discover_live_runtime(&source)
3360            .map_err(|error| ServiceError::Operation(error.to_string()))?
3361        {
3362            value["live_endpoint"] = json!(endpoint.as_str());
3363        }
3364    }
3365    if value.get("live_endpoint").is_none() {
3366        if let Some(peer) = peers.iter().find(|peer| {
3367            session.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3368                && peer.session_id == session.locator.session_id
3369        }) {
3370            value["live_endpoint"] = json!(peer.endpoint().as_str());
3371        }
3372    }
3373    Ok(value)
3374}
3375
3376fn live_index_changes(
3377    changes: Vec<crate::session_index::SessionIndexChange>,
3378    homes: &HarnessHomes,
3379) -> std::result::Result<Vec<Value>, ServiceError> {
3380    use crate::session_index::SessionIndexChange;
3381    let has_claude = changes.iter().any(|change| match change {
3382        SessionIndexChange::Added { descriptor } | SessionIndexChange::Updated { descriptor } => {
3383            descriptor.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3384        }
3385        SessionIndexChange::Removed { .. } => false,
3386    });
3387    let peers = if has_claude {
3388        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
3389    } else {
3390        Vec::new()
3391    };
3392    changes
3393        .into_iter()
3394        .map(|change| match change {
3395            SessionIndexChange::Added { descriptor } => Ok(json!({
3396                "kind": "added",
3397                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3398            })),
3399            SessionIndexChange::Updated { descriptor } => Ok(json!({
3400                "kind": "updated",
3401                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3402            })),
3403            SessionIndexChange::Removed { key } => Ok(json!({
3404                "kind": "removed",
3405                "key": key,
3406            })),
3407        })
3408        .collect()
3409}
3410
3411fn legacy_live_status(activity: &crate::SessionActivity) -> Option<&'static str> {
3412    use crate::{SessionPresence, SessionTurnState};
3413    match (activity.presence, activity.turn) {
3414        (SessionPresence::Persisted, _) => None,
3415        (SessionPresence::Running, SessionTurnState::Working) => Some("busy"),
3416        (SessionPresence::Running, SessionTurnState::Idle) => Some("idle"),
3417        // The normalized activity object can honestly report a live owner even
3418        // when the stock harness never published a turn status. Preserve the
3419        // older field's stricter contract instead of guessing `running`.
3420        (SessionPresence::Running, SessionTurnState::Unknown)
3421            if activity.evidence.native_state.is_none() =>
3422        {
3423            None
3424        }
3425        (SessionPresence::Running, _) | (SessionPresence::ShuttingDown, _) => Some("running"),
3426    }
3427}
3428
3429#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
3430#[serde(rename_all = "kebab-case")]
3431enum TransferFormat {
3432    ClaudeCode,
3433    Codex,
3434    #[serde(rename = "opencode", alias = "open-code")]
3435    OpenCode,
3436    Pi,
3437    Grok,
3438    Gemini,
3439    Goose,
3440    /// UNI-18: a Hermes target. Its artifact is the Codex rollout that
3441    /// `hermes sessions import --from codex` reads; `sessions.export` performs
3442    /// that import into the Hermes home.
3443    Hermes,
3444}
3445
3446impl TransferFormat {
3447    fn id(self) -> &'static str {
3448        match self {
3449            Self::ClaudeCode => HarnessId::CLAUDE_CODE,
3450            Self::Codex => HarnessId::CODEX,
3451            Self::OpenCode => HarnessId::OPENCODE,
3452            Self::Pi => HarnessId::PI,
3453            Self::Grok => HarnessId::GROK,
3454            Self::Gemini => HarnessId::GEMINI,
3455            Self::Goose => HarnessId::GOOSE,
3456            Self::Hermes => HarnessId::HERMES,
3457        }
3458    }
3459}
3460
3461impl From<TransferFormat> for SessionFormat {
3462    fn from(value: TransferFormat) -> Self {
3463        match value {
3464            TransferFormat::ClaudeCode => Self::ClaudeCode,
3465            TransferFormat::Codex => Self::Codex,
3466            TransferFormat::OpenCode => Self::OpenCode,
3467            TransferFormat::Pi => Self::Pi,
3468            TransferFormat::Grok => Self::Grok,
3469            TransferFormat::Gemini => Self::Gemini,
3470            TransferFormat::Goose => Self::Goose,
3471            // a Hermes artifact is the Codex rollout Hermes imports
3472            TransferFormat::Hermes => Self::Codex,
3473        }
3474    }
3475}
3476
3477#[derive(Deserialize)]
3478struct ImportSessionParams {
3479    source_harness: TransferFormat,
3480    content: String,
3481}
3482
3483#[derive(Deserialize)]
3484struct ExportSessionParams {
3485    locator: SessionLocator,
3486    target_harness: TransferFormat,
3487}
3488
3489#[derive(Deserialize)]
3490struct ReduceSessionParams {
3491    locator: SessionLocator,
3492    target_harness: TransferFormat,
3493    #[serde(default = "default_keep_last")]
3494    keep_last: usize,
3495}
3496
3497fn default_keep_last() -> usize {
3498    6
3499}
3500
3501#[derive(Deserialize)]
3502struct BranchSessionParams {
3503    locator: SessionLocator,
3504    #[serde(default)]
3505    target_harness: Option<TransferFormat>,
3506}
3507
3508#[derive(Deserialize)]
3509struct HandoffSessionParams {
3510    locator: SessionLocator,
3511    target_harness: TransferFormat,
3512    #[serde(default)]
3513    cwd: Option<PathBuf>,
3514}
3515
3516#[derive(Deserialize)]
3517struct MaterializeSessionParams {
3518    artifact: crate::native_materialize::MaterializeArtifact,
3519    cwd: PathBuf,
3520    /// Where the continuation is written; unset roots are the environment's own, as discovery reads them.
3521    #[serde(default)]
3522    homes: HarnessHomes,
3523}
3524
3525#[derive(Debug, Clone, Copy, Default, Deserialize)]
3526#[serde(rename_all = "snake_case")]
3527enum ResumePolicy {
3528    #[default]
3529    Default,
3530    Yolo,
3531}
3532
3533#[derive(Deserialize)]
3534struct ResumeInstructionsParams {
3535    locator: SessionLocator,
3536    #[serde(default)]
3537    cwd: Option<PathBuf>,
3538    #[serde(default)]
3539    policy: ResumePolicy,
3540}
3541
3542/// `harness.v1.workflow.load` parameters: which harness's board, and its home.
3543#[derive(Deserialize)]
3544struct WorkflowLoadParams {
3545    from: crate::workflow_doors::WorkflowHarness,
3546    home: PathBuf,
3547}
3548
3549/// ONT-4 `harness.v1.orchestration.load` parameters. `flavor` says which layout the
3550/// folder is read as; our own is the default.
3551#[derive(Deserialize)]
3552struct OrchestrationLoadParams {
3553    root: PathBuf,
3554    #[serde(default)]
3555    flavor: crate::orchestration_doors::HomeFlavor,
3556}
3557
3558/// ONT-4 `harness.v1.orchestration.save` parameters. `vault` is merged into the
3559/// home's own secrets; a caller that sends none keeps what is on disk.
3560#[derive(Deserialize)]
3561struct OrchestrationSaveParams {
3562    root: PathBuf,
3563    orchestration: crate::orchestration::Orchestration,
3564    #[serde(default)]
3565    vault: BTreeMap<String, String>,
3566}
3567
3568/// ONT-4 `harness.v1.orchestration.compile` parameters.
3569#[derive(Deserialize)]
3570struct OrchestrationCompileParams {
3571    from: crate::orchestration_doors::OrchestrationHarness,
3572    home: PathBuf,
3573}
3574
3575/// ONT-4 `harness.v1.orchestration.decompile` parameters. `source` is the home the
3576/// orchestration was compiled from: it is re-compiled to recover the io bookkeeping
3577/// that byte reuse and the live-store refusal (UNI-18) are decided from.
3578#[derive(Deserialize)]
3579struct OrchestrationDecompileParams {
3580    to: crate::orchestration_doors::OrchestrationHarness,
3581    orchestration: crate::orchestration::Orchestration,
3582    source: PathBuf,
3583    #[serde(default)]
3584    source_flavor: crate::orchestration_doors::SourceFlavor,
3585    dest: PathBuf,
3586    #[serde(default)]
3587    vault: BTreeMap<String, String>,
3588}
3589
3590/// `harness.v1.orchestration.import` parameters: another harness's home, and the
3591/// folder of ours it becomes.
3592#[derive(Deserialize)]
3593struct OrchestrationImportParams {
3594    from: crate::orchestration_doors::OrchestrationHarness,
3595    home: PathBuf,
3596    into: PathBuf,
3597}
3598
3599/// `harness.v1.orchestration.export` parameters: a folder of ours, and the home of
3600/// another harness it becomes.
3601#[derive(Deserialize)]
3602struct OrchestrationExportParams {
3603    to: crate::orchestration_doors::OrchestrationHarness,
3604    root: PathBuf,
3605    dest: PathBuf,
3606}
3607
3608/// `harness.v1.jobs.get` parameters.
3609#[derive(Deserialize)]
3610struct JobsGetParams {
3611    harness: String,
3612    id: String,
3613    #[serde(default)]
3614    homes: crate::HarnessHomes,
3615}
3616
3617/// ORCH-18: run one mutating job verb through the harness's own CLI.
3618///
3619/// The refusal ladder is deliberate: a harness with no scheduled-job concept
3620/// at all answers with the SAME sentence `jobs.list` gives it, and a harness
3621/// that has jobs but publishes no client-callable verb (Claude Code, whose
3622/// jobs are created by the model inside a session) answers with its own
3623/// reason. Neither is ever a silent no-op.
3624fn mutate_job(
3625    verb: crate::jobs_control::JobVerb,
3626    params: Value,
3627) -> std::result::Result<Value, ServiceError> {
3628    let mutation = decode::<crate::jobs_control::JobMutation>(params)?;
3629    refuse_harness_without_jobs(&mutation.harness, &format!("jobs.{}", verb.as_str()))?;
3630    let outcome = crate::jobs_control::mutate(verb, &mutation).map_err(job_control_error)?;
3631    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3632}
3633
3634/// ORCH-22: run one mutating skills verb through the harness's own door.
3635///
3636/// The refusal ladder mirrors `jobs.*`: a harness with no skills root at all
3637/// answers with the same sentence `skills.list` gives it, and a harness whose
3638/// door does not publish this verb (OpenClaw has no `skills remove` at the
3639/// pin) answers with its own reason. Neither is ever a silent no-op.
3640fn mutate_skill(
3641    verb: crate::skills_control::SkillVerb,
3642    params: Value,
3643) -> std::result::Result<Value, ServiceError> {
3644    let mutation = decode::<crate::skills_control::SkillMutation>(params)?;
3645    if !crate::skills_control::supports_skill_control(&mutation.harness) {
3646        return Err(ServiceError::UnsupportedAction(format!(
3647            "`{}` has no skills root supercode reads; `skills.{}` is supported for: {}",
3648            mutation.harness,
3649            verb.as_str(),
3650            crate::skills_control::CONTROLLED_SKILL_HARNESSES.join(", ")
3651        )));
3652    }
3653    let outcome =
3654        crate::skills_control::mutate_skill(verb, &mutation).map_err(skill_control_error)?;
3655    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3656}
3657
3658/// The skills twin of [`job_control_error`], with the same mapping rule.
3659fn skill_control_error(error: crate::skills_control::SkillControlError) -> ServiceError {
3660    match error {
3661        crate::skills_control::SkillControlError::Unsupported(message) => {
3662            ServiceError::UnsupportedAction(message)
3663        }
3664        crate::skills_control::SkillControlError::Invalid(message) => {
3665            ServiceError::InvalidParams(message)
3666        }
3667        crate::skills_control::SkillControlError::Failed(message) => {
3668            ServiceError::Operation(message)
3669        }
3670    }
3671}
3672
3673/// ORCH-21: run one mutating profile verb through the harness's own CLI.
3674///
3675/// The refusal ladder mirrors `mutate_job`'s: a harness with no profile
3676/// concept at all answers with the SAME sentence `profiles.list` gives it, and
3677/// a harness that HAS profiles but publishes no client-callable verb (Codex's
3678/// file-authored `[profiles.<name>]` tables, supercode's compiled-in presets)
3679/// answers with its own reason. Neither is ever a silent no-op.
3680fn mutate_profile(
3681    verb: crate::profiles_control::ProfileVerb,
3682    params: Value,
3683) -> std::result::Result<Value, ServiceError> {
3684    let mutation = decode::<crate::profiles_control::ProfileMutation>(params)?;
3685    let outcome =
3686        crate::profiles_control::mutate(verb, &mutation).map_err(profile_control_error)?;
3687    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3688}
3689
3690/// The same mapping `job_control_error` applies, for the profile noun.
3691fn profile_control_error(error: crate::profiles_control::ProfileControlError) -> ServiceError {
3692    match error {
3693        crate::profiles_control::ProfileControlError::Unsupported(message) => {
3694            ServiceError::UnsupportedAction(message)
3695        }
3696        crate::profiles_control::ProfileControlError::Invalid(message) => {
3697            ServiceError::InvalidParams(message)
3698        }
3699        crate::profiles_control::ProfileControlError::Failed(message) => {
3700            ServiceError::Operation(message)
3701        }
3702    }
3703}
3704
3705/// Map a controlled-tier failure onto the service's error vocabulary. A verb
3706/// the harness lacks is `UnsupportedAction`; a harness verb that RAN and
3707/// failed carries its own stderr through as the operation error.
3708fn job_control_error(error: crate::jobs_control::JobControlError) -> ServiceError {
3709    match error {
3710        crate::jobs_control::JobControlError::Unsupported(message) => {
3711            ServiceError::UnsupportedAction(message)
3712        }
3713        crate::jobs_control::JobControlError::Invalid(message) => {
3714            ServiceError::InvalidParams(message)
3715        }
3716        crate::jobs_control::JobControlError::Failed(message) => ServiceError::Operation(message),
3717    }
3718}
3719
3720/// Map an ORCH-19 controlled-tier failure onto the service's error
3721/// vocabulary. A verb the harness has no door for is `UnsupportedAction`; a
3722/// door that RAN and failed carries the harness's own stderr / HTTP body
3723/// through as the operation error.
3724fn session_control_error(error: crate::SessionControlError) -> ServiceError {
3725    match error {
3726        crate::SessionControlError::Unsupported(message) => {
3727            ServiceError::UnsupportedAction(message)
3728        }
3729        crate::SessionControlError::Invalid(message) => ServiceError::InvalidParams(message),
3730        crate::SessionControlError::Failed(message) => ServiceError::Operation(message),
3731    }
3732}
3733
3734/// A harness without a scheduled-job concept refuses the verb rather than
3735/// answering with an empty list — an absent capability and an empty inventory
3736/// are different answers (the same rule `runtimes.capabilities` applies to
3737/// `steer`).
3738fn refuse_harness_without_jobs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3739    if crate::jobs::supports_jobs(harness) {
3740        return Ok(());
3741    }
3742    Err(ServiceError::UnsupportedAction(format!(
3743        "`{harness}` has no scheduled jobs; `{verb}` is supported for: {}",
3744        crate::jobs::JOB_HARNESSES.join(", ")
3745    )))
3746}
3747
3748/// `harness.v1.runs.get` parameters.
3749#[derive(Deserialize)]
3750struct RunsGetParams {
3751    harness: String,
3752    id: String,
3753    #[serde(default)]
3754    homes: crate::HarnessHomes,
3755}
3756
3757/// A harness with no run store refuses the verb rather than answering with an
3758/// empty history — the same rule `jobs.list` applies. Claude Code lands here
3759/// on purpose: its cron fires are ordinary turns inside the session that
3760/// created the job, so there is no fire record to list.
3761fn refuse_harness_without_runs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3762    if crate::runs::supports_runs(harness) {
3763        return Ok(());
3764    }
3765    Err(ServiceError::UnsupportedAction(format!(
3766        "`{harness}` keeps no run store; `{verb}` is supported for: {}",
3767        crate::runs::RUN_HARNESSES.join(", ")
3768    )))
3769}
3770
3771#[derive(Serialize)]
3772struct SessionArtifact {
3773    source_harness: HarnessId,
3774    target_harness: &'static str,
3775    session_id: Option<String>,
3776    content: String,
3777    suggested_filename: String,
3778    files: Vec<SessionArtifactFile>,
3779    fidelity: Fidelity,
3780    residue: Vec<String>,
3781}
3782
3783#[derive(Serialize)]
3784struct SessionArtifactFile {
3785    path: String,
3786    content: String,
3787    role: ArtifactFileRole,
3788}
3789
3790#[derive(Serialize)]
3791#[serde(rename_all = "snake_case")]
3792enum ArtifactFileRole {
3793    Primary,
3794    Subagent,
3795    Bundle,
3796    SourceRecovery,
3797}
3798
3799#[derive(Serialize)]
3800struct StructuredLaunch {
3801    cwd: PathBuf,
3802    program: String,
3803    arguments: Vec<String>,
3804    env: BTreeMap<String, String>,
3805}
3806
3807struct HandoffInstructions {
3808    launch: StructuredLaunch,
3809    materialize: Option<StructuredLaunch>,
3810    requires_materialization: bool,
3811    note: String,
3812}
3813
3814#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
3815#[serde(rename_all = "snake_case")]
3816enum HarnessProbeLevel {
3817    #[default]
3818    Passive,
3819    Handshake,
3820}
3821
3822#[derive(Default, Deserialize)]
3823#[serde(default)]
3824struct HarnessInventoryParams {
3825    harness: Option<HarnessId>,
3826    harnesses: Vec<HarnessId>,
3827    workspace: Option<PathBuf>,
3828    probe: HarnessProbeLevel,
3829    include_sessions: bool,
3830    /// Omit subprocess-based `--version` calls when a latency-sensitive UI only needs readiness.
3831    skip_versions: bool,
3832}
3833
3834#[derive(Deserialize)]
3835struct HarnessAuthenticationParams {
3836    harness: HarnessId,
3837}
3838
3839#[derive(Deserialize)]
3840struct BeginHarnessAuthenticationParams {
3841    harness: HarnessId,
3842    #[serde(default = "local_browser_authentication_environment")]
3843    environment: crate::HarnessAuthenticationEnvironment,
3844    #[serde(default)]
3845    method: Option<crate::HarnessAuthenticationMethodId>,
3846    #[serde(default)]
3847    cwd: Option<PathBuf>,
3848}
3849
3850fn local_browser_authentication_environment() -> crate::HarnessAuthenticationEnvironment {
3851    crate::HarnessAuthenticationEnvironment::LocalBrowser
3852}
3853
3854#[derive(Serialize)]
3855struct HarnessInventoryReport {
3856    probe: HarnessProbeLevel,
3857    workspace: Option<PathBuf>,
3858    harnesses: Vec<LocalHarness>,
3859}
3860
3861#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3862#[serde(rename_all = "snake_case")]
3863enum HarnessAuthState {
3864    Ready,
3865    Configured,
3866    Required,
3867    Unknown,
3868}
3869
3870#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3871#[serde(rename_all = "snake_case")]
3872enum HarnessRuntimeState {
3873    Ready,
3874    Degraded,
3875    Unavailable,
3876}
3877
3878#[derive(Serialize)]
3879struct HarnessSessionCounts {
3880    global: Option<usize>,
3881    workspace: Option<usize>,
3882}
3883
3884/// Receipt-backed evidence that a harness has a RUNNING instance right now,
3885/// distinct from being merely installed (UNI-7). Detection is passive and
3886/// default-on: a gateway liveness connect for daemon harnesses, a fresh
3887/// SQLite WAL stamp for store-writer harnesses (precedent: the opencode
3888/// follower's -wal/-shm freshness). Control stays behind per-connection
3889/// grants — this reports observations only.
3890/// ORCH-17: the gateway-health noun on an inventory row. Derived from the
3891/// UNI-7 running-instance probe (Hermes: `state.db-wal` freshness; OpenClaw:
3892/// a TCP connect to the gateway endpoint resolved from its OWN config) plus
3893/// the executable version — never by starting anything.
3894#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3895#[serde(rename_all = "snake_case")]
3896pub enum GatewayState {
3897    Up,
3898    Down,
3899    Unknown,
3900}
3901
3902/// ORCH-17: `gateway` on a `harness.v1.harnesses.list` row.
3903#[derive(Debug, Clone, Serialize)]
3904pub struct GatewayHealth {
3905    pub state: GatewayState,
3906    /// The endpoint supercode would connect to (OpenClaw: the gateway
3907    /// WebSocket resolved from `openclaw.json`; core harnesses: their
3908    /// declared connect address when one exists). `None` when the harness
3909    /// has no single endpoint (Hermes multiplexes platforms).
3910    #[serde(skip_serializing_if = "Option::is_none")]
3911    pub endpoint: Option<String>,
3912    #[serde(skip_serializing_if = "Option::is_none")]
3913    pub version: Option<String>,
3914    /// What the verdict rests on, or why it is `unknown`.
3915    pub evidence: String,
3916    pub checked_at_ms: u64,
3917}
3918
3919/// OpenClaw's gateway WebSocket endpoint, resolved from its own config the
3920/// way the registry's connect descriptor prescribes (`gateway.url`, else
3921/// `gateway.port`, else the documented default).
3922fn openclaw_gateway_endpoint(home: &Path) -> String {
3923    let config_path = home.join(".openclaw/openclaw.json");
3924    let gateway = std::fs::read_to_string(&config_path)
3925        .ok()
3926        .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
3927        .and_then(|config| config.get("gateway").cloned());
3928    if let Some(url) = gateway
3929        .as_ref()
3930        .and_then(|gateway| gateway.get("url"))
3931        .and_then(serde_json::Value::as_str)
3932    {
3933        return url.to_string();
3934    }
3935    let port = gateway
3936        .as_ref()
3937        .and_then(|gateway| gateway.get("port"))
3938        .and_then(serde_json::Value::as_u64)
3939        .unwrap_or(18789);
3940    format!("ws://127.0.0.1:{port}")
3941}
3942
3943/// Ask Hermes itself (`hermes gateway status`, read-only, ~1 s) whether its
3944/// gateway is up. The command is per-host launchd/systemd text without a JSON
3945/// form at 0.19–0.21; the verdict is read from the lines it prints:
3946/// "supervised by launchd (PID …)" / "is running" → up, "not running" /
3947/// "not installed" → down, anything else → no verdict. `SUPERCODE_HERMES_BIN`
3948/// overrides the executable so a fake can stand in under test.
3949fn hermes_gateway_status() -> Option<(GatewayState, String)> {
3950    let program = crate::harness_command::harness_program(HarnessId::HERMES).ok()?;
3951    let output = std::process::Command::new(&program)
3952        .args(["gateway", "status"])
3953        .stdin(std::process::Stdio::null())
3954        .output()
3955        .ok()?;
3956    let text = format!(
3957        "{}{}",
3958        String::from_utf8_lossy(&output.stdout),
3959        String::from_utf8_lossy(&output.stderr)
3960    );
3961    let verdict = text.lines().find_map(|line| {
3962        let l = line.trim();
3963        if l.contains("supervised by launchd (PID")
3964            || l.contains("supervised by systemd (PID")
3965            || l.contains("Gateway is running")
3966            || l.contains("process is running")
3967        {
3968            Some((GatewayState::Up, format!("`hermes gateway status`: {l}")))
3969        } else if l.contains("not running") || l.contains("not installed") {
3970            Some((GatewayState::Down, format!("`hermes gateway status`: {l}")))
3971        } else {
3972            None
3973        }
3974    });
3975    verdict
3976}
3977
3978fn gateway_health(
3979    id: &str,
3980    installed: bool,
3981    running: Option<&RunningInstance>,
3982    version: Option<&str>,
3983) -> GatewayHealth {
3984    let checked_at_ms = now_epoch_ms();
3985    let home = std::env::var_os("HOME").map(PathBuf::from);
3986    match id {
3987        HarnessId::HERMES | HarnessId::OPENCLAW => {
3988            let endpoint = (id == HarnessId::OPENCLAW)
3989                .then(|| home.as_deref().map(openclaw_gateway_endpoint))
3990                .flatten();
3991            let (state, evidence) = match running {
3992                Some(instance) => (GatewayState::Up, instance.evidence.clone()),
3993                None if !installed => (
3994                    GatewayState::Unknown,
3995                    format!("`{id}` is not installed; no gateway to probe"),
3996                ),
3997                None if id == HarnessId::HERMES => match hermes_gateway_status() {
3998                    // The harness's own door outranks the WAL heuristic: an idle
3999                    // gateway writes nothing for minutes yet is up.
4000                    Some((state, evidence)) => (state, evidence),
4001                    None => (
4002                        GatewayState::Down,
4003                        "no fresh state.db-wal activity under ~/.hermes and `hermes gateway status` gave no verdict".to_string(),
4004                    ),
4005                },
4006                None => (
4007                    GatewayState::Down,
4008                    format!(
4009                        "no TCP listener at {}",
4010                        endpoint.as_deref().unwrap_or("the gateway endpoint")
4011                    ),
4012                ),
4013            };
4014            GatewayHealth {
4015                state,
4016                endpoint,
4017                version: version.map(str::to_string),
4018                evidence,
4019                checked_at_ms,
4020            }
4021        }
4022        // ORC-7: the orchestrator's gateway IS its daemon, and the daemon's
4023        // own lease file is the record of it. A lease naming a live pid is
4024        // up; a lease whose process is gone is down and says so as a STALE
4025        // lease, never as "no lease"; no lease at all is down. Nothing is
4026        // started, and no port is guessed — the daemon multiplexes adapters
4027        // the way Hermes does, so it has no single endpoint either.
4028        HarnessId::ORCHESTRATOR => {
4029            let root = crate::HarnessHomes::default().orchestrator;
4030            let (state, evidence) = match crate::orchestrator::read_lease(&root) {
4031                Some(lease) if lease.is_live() => (
4032                    GatewayState::Up,
4033                    format!(
4034                        "`{}` names pid {} (started {}), which is live",
4035                        crate::orchestrator::lock_path(&root).display(),
4036                        lease.pid,
4037                        lease.started_at
4038                    ),
4039                ),
4040                Some(lease) => (
4041                    GatewayState::Down,
4042                    format!(
4043                        "stale lease `{}`: pid {} is gone",
4044                        crate::orchestrator::lock_path(&root).display(),
4045                        lease.pid
4046                    ),
4047                ),
4048                None => (
4049                    GatewayState::Down,
4050                    format!(
4051                        "no lease at `{}`; `supercode orchestrator start` writes one",
4052                        crate::orchestrator::lock_path(&root).display()
4053                    ),
4054                ),
4055            };
4056            GatewayHealth {
4057                state,
4058                endpoint: None,
4059                version: version.map(str::to_string),
4060                evidence,
4061                checked_at_ms,
4062            }
4063        }
4064        _ => GatewayHealth {
4065            state: GatewayState::Unknown,
4066            endpoint: None,
4067            version: version.map(str::to_string),
4068            evidence: format!("`{id}` runs per session, not as a gateway"),
4069            checked_at_ms,
4070        },
4071    }
4072}
4073
4074#[derive(Debug, Clone, Serialize)]
4075struct RunningInstance {
4076    /// How the instance was detected.
4077    method: RunningInstanceMethod,
4078    /// The evidence the verdict rests on (endpoint reached / WAL path+age).
4079    evidence: String,
4080    /// Epoch-ms instant the probe executed.
4081    checked_at_ms: u64,
4082}
4083
4084#[derive(Debug, Clone, Copy, Serialize)]
4085#[serde(rename_all = "snake_case")]
4086enum RunningInstanceMethod {
4087    /// A TCP connect to the harness's own configured gateway endpoint
4088    /// succeeded.
4089    GatewayConnect,
4090    /// The harness's session store has an active SQLite WAL (a live writer
4091    /// holds the store open and stamped it recently).
4092    StoreWalActivity,
4093}
4094
4095fn now_epoch_ms() -> u64 {
4096    std::time::SystemTime::now()
4097        .duration_since(std::time::UNIX_EPOCH)
4098        .map(|elapsed| elapsed.as_millis() as u64)
4099        .unwrap_or(0)
4100}
4101
4102/// OpenClaw: the gateway endpoint comes from the harness's OWN config
4103/// (`<home>/.openclaw/openclaw.json` — `gateway.url` or `gateway.port`,
4104/// default port 18789); a successful TCP connect is the running signal.
4105fn probe_openclaw_running(home: &Path) -> Option<RunningInstance> {
4106    let config_path = home.join(".openclaw/openclaw.json");
4107    let text = std::fs::read_to_string(&config_path).ok();
4108    let gateway = text
4109        .as_deref()
4110        .and_then(|raw| serde_json::from_str::<serde_json::Value>(raw).ok())
4111        .and_then(|config| config.get("gateway").cloned());
4112    let address = gateway
4113        .as_ref()
4114        .and_then(|gateway| gateway.get("url"))
4115        .and_then(serde_json::Value::as_str)
4116        .and_then(|url| {
4117            url.split("://").nth(1).map(|rest| {
4118                rest.trim_end_matches('/')
4119                    .split('/')
4120                    .next()
4121                    .unwrap_or(rest)
4122                    .to_string()
4123            })
4124        })
4125        .unwrap_or_else(|| {
4126            let port = gateway
4127                .as_ref()
4128                .and_then(|gateway| gateway.get("port"))
4129                .and_then(serde_json::Value::as_u64)
4130                .unwrap_or(18789);
4131            format!("127.0.0.1:{port}")
4132        });
4133    let reachable = std::net::TcpStream::connect_timeout(
4134        &address.parse().ok()?,
4135        std::time::Duration::from_millis(400),
4136    )
4137    .is_ok();
4138    reachable.then(|| RunningInstance {
4139        method: RunningInstanceMethod::GatewayConnect,
4140        evidence: format!(
4141            "gateway endpoint {address} accepted a TCP connect (from {})",
4142            config_path.display()
4143        ),
4144        checked_at_ms: now_epoch_ms(),
4145    })
4146}
4147
4148/// Hermes: `<home>/.hermes/state.db-wal` freshly modified means a live writer
4149/// holds the store open (SQLite WAL exists only while a connection is open;
4150/// a recent stamp distinguishes an active instance from a stale crash
4151/// leftover).
4152fn probe_hermes_running(home: &Path, max_wal_age_ms: u64) -> Option<RunningInstance> {
4153    let wal = home.join(".hermes/state.db-wal");
4154    let modified = std::fs::metadata(&wal).ok()?.modified().ok()?;
4155    let age_ms = std::time::SystemTime::now()
4156        .duration_since(modified)
4157        .map(|age| age.as_millis() as u64)
4158        .unwrap_or(u64::MAX);
4159    (age_ms <= max_wal_age_ms).then(|| RunningInstance {
4160        method: RunningInstanceMethod::StoreWalActivity,
4161        evidence: format!(
4162            "{} stamped {age_ms}ms ago (threshold {max_wal_age_ms}ms)",
4163            wal.display()
4164        ),
4165        checked_at_ms: now_epoch_ms(),
4166    })
4167}
4168
4169/// Default-on running-instance detection for the harnesses that have one.
4170fn probe_running_instance(id: &str) -> Option<RunningInstance> {
4171    let home = std::env::var_os("HOME").map(PathBuf::from)?;
4172    match id {
4173        HarnessId::OPENCLAW => probe_openclaw_running(&home),
4174        HarnessId::HERMES => probe_hermes_running(&home, 300_000),
4175        _ => None,
4176    }
4177}
4178
4179#[derive(Serialize)]
4180struct LocalHarness {
4181    id: HarnessId,
4182    display_name: String,
4183    supported: bool,
4184    installed: bool,
4185    executable: Option<String>,
4186    version: Option<String>,
4187    auth: HarnessAuthState,
4188    runtime: HarnessRuntimeState,
4189    protocol: String,
4190    capabilities: crate::RuntimeCapabilities,
4191    effective_capabilities: crate::RuntimeCapabilities,
4192    sessions: HarnessSessionCounts,
4193    /// Receipt-backed running-instance detection (None = not detected or the
4194    /// harness has no running-instance concept). Distinct from `installed`.
4195    #[serde(skip_serializing_if = "Option::is_none")]
4196    running: Option<RunningInstance>,
4197    /// ORCH-17: gateway health derived from `running` + the harness's own config.
4198    gateway: GatewayHealth,
4199    reason: Option<String>,
4200    repair: Option<String>,
4201}
4202
4203#[derive(Clone, Deserialize)]
4204struct RuntimeBackendParams {
4205    harness: HarnessId,
4206    #[serde(default)]
4207    protocol: Option<String>,
4208    #[serde(default)]
4209    launch: Option<RuntimeLaunch>,
4210    #[serde(default)]
4211    base_url: Option<String>,
4212    #[serde(default)]
4213    policy: RuntimePolicy,
4214}
4215
4216#[derive(Debug, Clone, Copy, Default, Deserialize)]
4217#[serde(rename_all = "snake_case")]
4218enum RuntimePolicy {
4219    #[default]
4220    Default,
4221    Yolo,
4222}
4223
4224#[derive(Deserialize)]
4225struct RuntimeStartParams {
4226    #[serde(flatten)]
4227    backend: RuntimeBackendParams,
4228    cwd: PathBuf,
4229    /// MCP servers to mount into the new session through the harness's own
4230    /// start door (ORC-6). Backends without such a door ignore them.
4231    #[serde(default)]
4232    mcp_servers: Vec<crate::McpServerLaunch>,
4233}
4234
4235#[derive(Deserialize)]
4236struct RuntimeAttachParams {
4237    #[serde(flatten)]
4238    backend: RuntimeBackendParams,
4239    runtime_id: String,
4240    #[serde(default)]
4241    cwd: Option<PathBuf>,
4242    /// MCP servers to mount into the resumed session (the start door's own
4243    /// field, carried again because a session's tools die with its process).
4244    #[serde(default)]
4245    mcp_servers: Vec<crate::McpServerLaunch>,
4246}
4247
4248#[derive(Deserialize)]
4249struct RuntimeConnectionParams {
4250    connection: String,
4251}
4252
4253#[derive(Deserialize)]
4254struct RuntimeInputParams {
4255    connection: String,
4256    text: String,
4257    #[serde(default)]
4258    image_urls: Vec<String>,
4259}
4260
4261const MAX_RUNTIME_IMAGES: usize = 4;
4262const MAX_RUNTIME_IMAGE_URL_BYTES: usize = 12 * 1024 * 1024;
4263const MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL: usize = 32 * 1024 * 1024;
4264
4265fn validate_runtime_image_urls(image_urls: Vec<String>) -> Result<Vec<String>, ServiceError> {
4266    if image_urls.len() > MAX_RUNTIME_IMAGES {
4267        return Err(ServiceError::InvalidParams(format!(
4268            "a runtime prompt accepts at most {MAX_RUNTIME_IMAGES} images"
4269        )));
4270    }
4271    let mut total = 0usize;
4272    for url in &image_urls {
4273        if !(url.starts_with("data:image/")
4274            || url.starts_with("https://")
4275            || url.starts_with("http://"))
4276        {
4277            return Err(ServiceError::InvalidParams(
4278                "runtime images must be image data URLs or HTTP(S) URLs".into(),
4279            ));
4280        }
4281        if url.len() > MAX_RUNTIME_IMAGE_URL_BYTES {
4282            return Err(ServiceError::InvalidParams(format!(
4283                "one runtime image exceeds the {MAX_RUNTIME_IMAGE_URL_BYTES}-byte encoded limit"
4284            )));
4285        }
4286        total = total.saturating_add(url.len());
4287    }
4288    if total > MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL {
4289        return Err(ServiceError::InvalidParams(format!(
4290            "runtime images exceed the {MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL}-byte encoded total limit"
4291        )));
4292    }
4293    Ok(image_urls)
4294}
4295
4296#[derive(Deserialize)]
4297struct RuntimeRespondParams {
4298    connection: String,
4299    request_id: Value,
4300    response: Value,
4301}
4302
4303fn default_reduction_store_root() -> PathBuf {
4304    if let Some(root) = std::env::var_os("SUPERCODE_HOME") {
4305        return PathBuf::from(root).join("sessions");
4306    }
4307    if let Some(home) = std::env::var_os("HOME") {
4308        return PathBuf::from(home).join(".supercode").join("sessions");
4309    }
4310    PathBuf::from(".supercode").join("sessions")
4311}
4312
4313fn messages_jsonl(messages: &[crate::ChatMessage]) -> std::result::Result<String, ServiceError> {
4314    let mut output = String::new();
4315    for message in messages {
4316        output.push_str(
4317            &serde_json::to_string(message)
4318                .map_err(|error| ServiceError::Operation(error.to_string()))?,
4319        );
4320        output.push('\n');
4321    }
4322    Ok(output)
4323}
4324
4325fn parse_messages_jsonl(
4326    content: &str,
4327) -> std::result::Result<Vec<crate::ChatMessage>, ServiceError> {
4328    content
4329        .lines()
4330        .enumerate()
4331        .filter(|(_, line)| !line.trim().is_empty())
4332        .map(|(index, line)| {
4333            serde_json::from_str::<crate::ChatMessage>(line).map_err(|error| {
4334                ServiceError::Operation(format!(
4335                    "reduced transcript line {} is invalid: {error}",
4336                    index + 1
4337                ))
4338            })
4339        })
4340        .collect()
4341}
4342
4343fn reduced_bootstrap_prompt(
4344    source: &SessionLocator,
4345    target: TransferFormat,
4346    view_jsonl: &str,
4347    sidecar_path: &Path,
4348    reduction_log_path: &Path,
4349) -> String {
4350    format!(
4351        "Continue the work from this losslessly reduced {source_harness} session in {target_harness}.\n\
4352         \n\
4353         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\
4354         \n\
4355         <supercode-reduced-session source-session=\"{source_id}\">\n\
4356         {view_jsonl}\
4357         </supercode-reduced-session>\n\
4358         \n\
4359         Resume from the latest unresolved user request and preserve the source session's decisions and constraints.",
4360        source_harness = source.harness.as_str(),
4361        target_harness = target.id(),
4362        sidecar = sidecar_path.display(),
4363        log = reduction_log_path.display(),
4364        source_id = source.session_id,
4365    )
4366}
4367
4368fn session_artifact(
4369    locator: &SessionLocator,
4370    session: &Session,
4371    target: TransferFormat,
4372) -> std::result::Result<SessionArtifact, ServiceError> {
4373    session_artifact_with_id(locator, session, target, None)
4374}
4375
4376fn session_artifact_with_id(
4377    locator: &SessionLocator,
4378    session: &Session,
4379    target: TransferFormat,
4380    target_session_id: Option<&str>,
4381) -> std::result::Result<SessionArtifact, ServiceError> {
4382    let format: SessionFormat = target.into();
4383    let diagonal = format.source() == session.meta.source;
4384    crate::residue_store::store_segments(session);
4385    let has_appended_turns = session
4386        .imported_message_count
4387        .is_some_and(|imported| imported < session.messages.len());
4388    let mut restoration = None;
4389    let content = if let Some(id) = target_session_id {
4390        if diagonal && format != SessionFormat::OpenCode {
4391            session
4392                .to_jsonl_spliced(format, Some(id))
4393                .map_err(operation)?
4394        } else {
4395            let mut rewritten = session.clone();
4396            rewritten.meta.session_id = Some(id.to_string());
4397            rewritten.to_jsonl(format).map_err(operation)?
4398        }
4399    } else if diagonal && session.raw_is_verbatim && !has_appended_turns {
4400        session.raw_verbatim()
4401    } else if diagonal {
4402        session.to_jsonl_spliced(format, None).map_err(operation)?
4403    } else {
4404        // A session that came from `format` before returns its source records verbatim for the
4405        // prefix the residue store holds (docs/plans/portable-residue.md).
4406        match session
4407            .restore_residue(format, crate::residue_store::lookup)
4408            .map_err(operation)?
4409        {
4410            Some((content, report)) => {
4411                restoration = Some(report);
4412                content
4413            }
4414            None => session.to_jsonl(format).map_err(operation)?,
4415        }
4416    };
4417    let stem = sanitize_filename(
4418        target_session_id
4419            .or(session.meta.session_id.as_deref())
4420            .unwrap_or(&locator.session_id),
4421    );
4422    let suggested_filename = if diagonal && target == TransferFormat::Grok {
4423        "chat_history.jsonl".to_string()
4424    } else if target == TransferFormat::Goose {
4425        format!("{stem}.goose.json")
4426    } else {
4427        format!("{stem}.{}.jsonl", target.id())
4428    };
4429    let mut files = vec![SessionArtifactFile {
4430        path: suggested_filename.clone(),
4431        content: content.clone(),
4432        role: ArtifactFileRole::Primary,
4433    }];
4434    if target == TransferFormat::ClaudeCode {
4435        let bundle_stem = Path::new(&suggested_filename)
4436            .file_stem()
4437            .and_then(|stem| stem.to_str())
4438            .unwrap_or(&stem);
4439        let mut child_paths = BTreeSet::new();
4440        for (index, subagent) in session.subagents.iter().enumerate() {
4441            let agent_id = subagent
4442                .meta
4443                .agent_id
4444                .as_deref()
4445                .map(|id| id.strip_prefix("agent-").unwrap_or(id))
4446                .map(sanitize_filename)
4447                .filter(|id| !id.is_empty())
4448                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4449            let child_has_appended_turns = subagent
4450                .imported_message_count
4451                .is_some_and(|imported| imported < subagent.messages.len());
4452            let child_content = if target_session_id.is_none()
4453                && subagent.meta.source == SessionSource::ClaudeCode
4454                && subagent.raw_is_verbatim
4455                && !child_has_appended_turns
4456            {
4457                subagent.raw_verbatim()
4458            } else if subagent.meta.source == SessionSource::ClaudeCode {
4459                subagent
4460                    .to_jsonl_spliced(SessionFormat::ClaudeCode, target_session_id)
4461                    .map_err(operation)?
4462            } else {
4463                let mut child = subagent.clone();
4464                if let Some(id) = target_session_id {
4465                    child.meta.session_id = Some(id.to_string());
4466                }
4467                child
4468                    .to_jsonl(SessionFormat::ClaudeCode)
4469                    .map_err(operation)?
4470            };
4471            let path = format!("{bundle_stem}/subagents/agent-{agent_id}.jsonl");
4472            if !child_paths.insert(path.clone()) {
4473                return Err(ServiceError::Operation(format!(
4474                    "Claude subagent ids collide at artifact path `{path}`"
4475                )));
4476            }
4477            files.push(SessionArtifactFile {
4478                path,
4479                content: child_content,
4480                role: ArtifactFileRole::Subagent,
4481            });
4482        }
4483    }
4484    if diagonal && target == TransferFormat::Grok {
4485        append_grok_bundle_files(locator, "", ArtifactFileRole::Bundle, &mut files)?;
4486    }
4487    if !diagonal || !session.raw_is_verbatim {
4488        files.push(SessionArtifactFile {
4489            path: "recovery/source.supercode.jsonl".into(),
4490            content: session.to_native_jsonl(),
4491            role: ArtifactFileRole::SourceRecovery,
4492        });
4493        for (index, subagent) in session.subagents.iter().enumerate() {
4494            let id = subagent
4495                .meta
4496                .agent_id
4497                .as_deref()
4498                .map(sanitize_filename)
4499                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4500            files.push(SessionArtifactFile {
4501                path: format!("recovery/subagents/{id}.supercode.jsonl"),
4502                content: subagent.to_native_jsonl(),
4503                role: ArtifactFileRole::SourceRecovery,
4504            });
4505        }
4506    }
4507    if !diagonal && session.meta.source == SessionSource::Grok {
4508        append_grok_bundle_files(
4509            locator,
4510            "recovery/grok/",
4511            ArtifactFileRole::SourceRecovery,
4512            &mut files,
4513        )?;
4514    }
4515    let (fidelity, residue) = if diagonal
4516        && target_session_id.is_none()
4517        && session.raw_is_verbatim
4518        && !has_appended_turns
4519    {
4520        (Fidelity::ByteLossless, Vec::new())
4521    } else if diagonal && !(target_session_id.is_some() && target == TransferFormat::OpenCode) {
4522        (
4523            Fidelity::ValueLossless,
4524            vec![if target_session_id.is_some() {
4525                "target identity was rewritten, so the artifact intentionally differs from source bytes".into()
4526            } else {
4527                "source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured".into()
4528            }],
4529        )
4530    } else {
4531        match restoration {
4532            Some(report) if report.rendered_messages == 0 => (
4533                Fidelity::ByteLossless,
4534                vec![format!(
4535                    "restored verbatim from this conversation's {} source records in the residue store",
4536                    target.id()
4537                )],
4538            ),
4539            Some(report) => (
4540                Fidelity::Semantic,
4541                vec![format!(
4542                    "{} of {} messages restored verbatim from the residue store; the other {} written by the {} writer",
4543                    report.restored_messages,
4544                    report.restored_messages + report.rendered_messages,
4545                    report.rendered_messages,
4546                    target.id()
4547                )],
4548            ),
4549            None => (
4550                Fidelity::Semantic,
4551                vec!["target schema has no portable slot for every source-native record and metadata field".into()],
4552            ),
4553        }
4554    };
4555    Ok(SessionArtifact {
4556        source_harness: locator.harness.clone(),
4557        target_harness: target.id(),
4558        session_id: target_session_id
4559            .map(str::to_string)
4560            .or_else(|| session.meta.session_id.clone()),
4561        content,
4562        suggested_filename,
4563        files,
4564        fidelity,
4565        residue,
4566    })
4567}
4568
4569fn append_grok_bundle_files(
4570    locator: &SessionLocator,
4571    prefix: &str,
4572    role: ArtifactFileRole,
4573    files: &mut Vec<SessionArtifactFile>,
4574) -> std::result::Result<(), ServiceError> {
4575    let primary = locator.storage.path();
4576    if primary.file_name().and_then(|name| name.to_str()) != Some("chat_history.jsonl") {
4577        return Err(ServiceError::Operation(format!(
4578            "Grok bundle locator must name chat_history.jsonl, got {}",
4579            primary.display()
4580        )));
4581    }
4582    let parent = primary.parent().ok_or_else(|| {
4583        ServiceError::Operation("Grok chat_history.jsonl has no session directory".into())
4584    })?;
4585    for name in ["summary.json", "updates.jsonl"] {
4586        let path = parent.join(name);
4587        let metadata = match std::fs::symlink_metadata(&path) {
4588            Ok(metadata) => metadata,
4589            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
4590            Err(error) => return Err(ServiceError::Operation(error.to_string())),
4591        };
4592        if metadata.file_type().is_symlink() || !metadata.is_file() {
4593            return Err(ServiceError::Operation(format!(
4594                "refusing non-regular Grok bundle member {}",
4595                path.display()
4596            )));
4597        }
4598        let content = std::fs::read_to_string(&path).map_err(|error| {
4599            ServiceError::Operation(format!(
4600                "Grok bundle member {} is not representable as UTF-8: {error}",
4601                path.display()
4602            ))
4603        })?;
4604        files.push(SessionArtifactFile {
4605            path: format!("{prefix}{name}"),
4606            content,
4607            role: match role {
4608                ArtifactFileRole::Bundle => ArtifactFileRole::Bundle,
4609                _ => ArtifactFileRole::SourceRecovery,
4610            },
4611        });
4612    }
4613    Ok(())
4614}
4615
4616fn handoff_artifact(
4617    locator: &SessionLocator,
4618    session: &Session,
4619    target: TransferFormat,
4620) -> std::result::Result<SessionArtifact, ServiceError> {
4621    let target_session_id = target_session_id(target);
4622    session_artifact_with_id(locator, session, target, Some(&target_session_id))
4623}
4624
4625fn target_session_id(target: TransferFormat) -> String {
4626    let uuid = generated_session_id();
4627    match target {
4628        TransferFormat::OpenCode => format!("ses_{}", uuid.replace('-', "")),
4629        TransferFormat::ClaudeCode
4630        | TransferFormat::Codex
4631        | TransferFormat::Pi
4632        | TransferFormat::Grok
4633        | TransferFormat::Gemini
4634        | TransferFormat::Goose
4635        | TransferFormat::Hermes => uuid,
4636    }
4637}
4638
4639fn sanitize_filename(value: &str) -> String {
4640    let value = value
4641        .chars()
4642        .map(|character| {
4643            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
4644                character
4645            } else {
4646                '-'
4647            }
4648        })
4649        .collect::<String>();
4650    let value = value.trim_matches('-');
4651    if value.is_empty() {
4652        "session".into()
4653    } else {
4654        value.chars().take(100).collect()
4655    }
4656}
4657
4658fn handoff_instructions(
4659    target: TransferFormat,
4660    session_id: &str,
4661    cwd: &Path,
4662) -> HandoffInstructions {
4663    let launch = |program: &str, arguments: Vec<String>| StructuredLaunch {
4664        cwd: cwd.to_path_buf(),
4665        program: program.into(),
4666        arguments,
4667        env: BTreeMap::new(),
4668    };
4669    match target {
4670        TransferFormat::ClaudeCode => HandoffInstructions {
4671            launch: launch("claude", vec!["--resume".into(), session_id.into()]),
4672            materialize: None,
4673            requires_materialization: true,
4674            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(),
4675        },
4676        TransferFormat::Hermes => HandoffInstructions {
4677            launch: launch("hermes", vec!["--resume".into(), session_id.into()]),
4678            materialize: None,
4679            requires_materialization: true,
4680            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(),
4681        },
4682        TransferFormat::Codex => HandoffInstructions {
4683            launch: launch("codex", vec!["resume".into(), session_id.into()]),
4684            materialize: None,
4685            requires_materialization: true,
4686            note: "Write the artifact into Codex's native rollout store before running the resume launch; Codex has no general transcript-import command.".into(),
4687        },
4688        TransferFormat::OpenCode => HandoffInstructions {
4689            launch: launch("opencode", vec!["--session".into(), session_id.into()]),
4690            materialize: Some(launch(
4691                "opencode",
4692                vec!["import".into(), "{artifact_path}".into()],
4693            )),
4694            requires_materialization: true,
4695            note: "Write the artifact to a file, run the materialize command with its path, then launch the imported session.".into(),
4696        },
4697        TransferFormat::Pi => HandoffInstructions {
4698            launch: launch("pi", vec!["--session".into(), "{artifact_path}".into()]),
4699            materialize: None,
4700            requires_materialization: true,
4701            note: "Write the artifact to a file and replace {artifact_path} in the launch arguments; Pi can resume that file directly.".into(),
4702        },
4703        TransferFormat::Grok => HandoffInstructions {
4704            launch: launch(
4705                "grok",
4706                vec!["--resume".into(), "{materialized_session_id}".into()],
4707            ),
4708            materialize: None,
4709            requires_materialization: true,
4710            note: "Grok has no import command. Materialize the artifact through `harness.v1.sessions.materialize` (target `grok`, `value_lossless`, the destination cwd): it writes Grok's store entry (`chat_history.jsonl` and the `summary.json` `--resume` requires) under a fresh id; replace {materialized_session_id} with the id it returns.".into(),
4711        },
4712        TransferFormat::Gemini => HandoffInstructions {
4713            launch: launch(
4714                "gemini",
4715                vec!["--session-file".into(), "{artifact_path}".into()],
4716            ),
4717            materialize: None,
4718            requires_materialization: true,
4719            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(),
4720        },
4721        TransferFormat::Goose => HandoffInstructions {
4722            launch: launch(
4723                "goose",
4724                vec![
4725                    "session".into(),
4726                    "--resume".into(),
4727                    "--session-id".into(),
4728                    "{imported_session_id}".into(),
4729                ],
4730            ),
4731            materialize: Some(launch(
4732                "goose",
4733                vec!["session".into(), "import".into(), "{artifact_path}".into()],
4734            )),
4735            requires_materialization: true,
4736            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(),
4737        },
4738    }
4739}
4740
4741fn resume_launch(
4742    harness: &str,
4743    session_id: &str,
4744    cwd: &Path,
4745    policy: ResumePolicy,
4746) -> std::result::Result<StructuredLaunch, ServiceError> {
4747    let mut arguments = Vec::new();
4748    let program = match harness {
4749        HarnessId::GROK => {
4750            if matches!(policy, ResumePolicy::Yolo) {
4751                if crate::support::self_sandbox_supported() {
4752                    arguments.extend(["--sandbox".into(), "workspace".into()]);
4753                }
4754                arguments.push("--always-approve".into());
4755            }
4756            arguments.extend(["--resume".into(), session_id.into()]);
4757            "grok"
4758        }
4759        HarnessId::CODEX => {
4760            let cwd_key = serde_json::to_string(cwd.to_string_lossy().as_ref())
4761                .expect("a filesystem path always serializes as JSON text");
4762            arguments.extend([
4763                "-c".into(),
4764                "check_for_update_on_startup=false".into(),
4765                "-c".into(),
4766                format!("projects.{cwd_key}.trust_level=\"trusted\""),
4767            ]);
4768            if matches!(policy, ResumePolicy::Yolo) {
4769                arguments.extend([
4770                    "--dangerously-bypass-approvals-and-sandbox".into(),
4771                    "--dangerously-bypass-hook-trust".into(),
4772                ]);
4773            }
4774            arguments.extend(["resume".into(), session_id.into()]);
4775            "codex"
4776        }
4777        HarnessId::CLAUDE_CODE => {
4778            if matches!(policy, ResumePolicy::Yolo) {
4779                arguments.push("--dangerously-skip-permissions".into());
4780            }
4781            arguments.extend(["--resume".into(), session_id.into()]);
4782            "claude"
4783        }
4784        HarnessId::GEMINI => {
4785            if matches!(policy, ResumePolicy::Yolo) {
4786                arguments.push("--yolo".into());
4787            }
4788            arguments.extend(["--resume".into(), session_id.into()]);
4789            "gemini"
4790        }
4791        HarnessId::GOOSE => {
4792            arguments.extend([
4793                "session".into(),
4794                "--resume".into(),
4795                "--session-id".into(),
4796                session_id.into(),
4797            ]);
4798            "goose"
4799        }
4800        HarnessId::PI => {
4801            if matches!(policy, ResumePolicy::Yolo) {
4802                arguments.push("--approve".into());
4803            }
4804            arguments.extend(["--session".into(), session_id.into()]);
4805            "pi"
4806        }
4807        HarnessId::OPENCODE => {
4808            arguments.extend(["--session".into(), session_id.into()]);
4809            "opencode"
4810        }
4811        HarnessId::SUPERCODE => {
4812            if matches!(policy, ResumePolicy::Yolo) {
4813                arguments.push("--dangerous".into());
4814            }
4815            arguments.extend(["resume".into(), session_id.into()]);
4816            "supercode"
4817        }
4818        other => {
4819            return Err(ServiceError::InvalidParams(format!(
4820                "no structured resume launch is registered for harness `{other}`"
4821            )))
4822        }
4823    };
4824    Ok(StructuredLaunch {
4825        cwd: cwd.to_path_buf(),
4826        env: if program == "grok" {
4827            crate::support::grok_home_env()
4828        } else {
4829            BTreeMap::new()
4830        },
4831        program: program.into(),
4832        arguments,
4833    })
4834}
4835
4836/// Stage the resolved gateway credential in a private (0600) file so the
4837/// bridge can read it via `--token-file` — the delivery the real `openclaw
4838/// acp` accepts. One stable file per endpoint (keyed by an address digest,
4839/// no secret material in the name), overwritten on every connect so files
4840/// never accumulate and a rotated token never goes stale on disk.
4841fn openclaw_gateway_token_file(address: &str, secret: &str) -> std::io::Result<PathBuf> {
4842    let digest = blake3::hash(address.as_bytes()).to_hex();
4843    let path = std::env::temp_dir().join(format!(
4844        "supercode-openclaw-gateway-token-{}",
4845        &digest.as_str()[..16]
4846    ));
4847    #[cfg(unix)]
4848    {
4849        use std::io::Write;
4850        use std::os::unix::fs::OpenOptionsExt;
4851        let mut file = std::fs::OpenOptions::new()
4852            .write(true)
4853            .create(true)
4854            .truncate(true)
4855            .mode(0o600)
4856            .open(&path)?;
4857        file.write_all(secret.as_bytes())?;
4858    }
4859    #[cfg(not(unix))]
4860    std::fs::write(&path, secret)?;
4861    Ok(path)
4862}
4863
4864/// Open a connect-mode descriptor: resolve the endpoint address and
4865/// credential from the harness's own config file and build the backend that
4866/// joins the already-running endpoint. Fails closed with a specific
4867/// diagnostic when the config cannot be resolved or the declared protocol has
4868/// no connect-capable client yet.
4869fn open_connect_descriptor(
4870    descriptor: &crate::HarnessSupportDescriptor,
4871    home: &Path,
4872) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
4873    let Some(connect) = &descriptor.runtime.connect_launch else {
4874        return Err(ServiceError::InvalidParams(format!(
4875            "harness `{}` has no registered connect-mode launch",
4876            descriptor.id.as_str()
4877        )));
4878    };
4879    let resolved = connect
4880        .resolve(home)
4881        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
4882    match (descriptor.id.as_str(), connect.protocol.as_str()) {
4883        (HarnessId::OPENCODE, protocol) if protocol.starts_with("opencode-http") => {
4884            let mut backend = OpenCodeRuntimeBackend::connect(&resolved.address);
4885            if let Some(token) = resolved.auth {
4886                backend = backend.with_bearer(token);
4887            }
4888            Ok(Box::new(backend))
4889        }
4890        (HarnessId::OPENCLAW, protocol) if protocol.starts_with("acp") => {
4891            // OpenClaw's own `openclaw acp` binary is the gateway client: a
4892            // stdio ACP bridge that joins the RUNNING gateway at the resolved
4893            // endpoint. Blind-walk finding 2026-08-31: the real bridge does
4894            // NOT honor OPENCLAW_GATEWAY_TOKEN from the environment — the
4895            // credential must arrive via `--token-file` (never bare `--token`
4896            // on argv, where process listings could read it). The env var is
4897            // still set for older bridges that did read it. Requires openclaw
4898            // >= 2026.7: the 2026.2 bridge drops its gateway socket
4899            // mid-prompt and advertises no session resume (executed finding,
4900            // docs/interop/research/openclaw-acp-dialect-2026-08-30.json).
4901            let mut env = BTreeMap::new();
4902            let mut arguments = vec!["acp".into(), "--url".into(), resolved.address.clone()];
4903            if let Some(token) = resolved.auth {
4904                let token_path = openclaw_gateway_token_file(&resolved.address, token.secret())
4905                    .map_err(|error| {
4906                        ServiceError::UnsupportedAction(format!(
4907                            "could not stage the gateway credential for the bridge: {error}"
4908                        ))
4909                    })?;
4910                arguments.push("--token-file".into());
4911                arguments.push(token_path.to_string_lossy().into_owned());
4912                env.insert("OPENCLAW_GATEWAY_TOKEN".to_string(), token.secret().to_string());
4913            }
4914            // The bridge program comes from the descriptor's own default
4915            // launch (the compiled registry pins `openclaw`), so tests can
4916            // substitute an absolute mock-bridge path without touching
4917            // process-global state.
4918            let program = descriptor
4919                .runtime
4920                .default_launch
4921                .as_ref()
4922                .map(|launch| launch.program.clone())
4923                .unwrap_or_else(|| "openclaw".into());
4924            let launch = RuntimeLaunch {
4925                program,
4926                arguments,
4927                env,
4928            };
4929            Ok(Box::new(
4930                crate::AcpRuntimeBackend::new(descriptor.id.clone(), launch)
4931                    .with_resume_support(descriptor.runtime.capabilities.resume_session),
4932            ))
4933        }
4934        _ => Err(ServiceError::UnsupportedAction(format!(
4935            "connect-mode endpoint for `{}` speaks `{}`; joining it needs that protocol's gateway client",
4936            descriptor.id.as_str(),
4937            connect.protocol
4938        ))),
4939    }
4940}
4941
4942/// The registry's connect-mode launch for this harness, honored only when the
4943/// caller supplied neither an explicit launch nor a base URL.
4944fn registry_connect_descriptor(
4945    params: &RuntimeBackendParams,
4946) -> Option<crate::HarnessSupportDescriptor> {
4947    if params.launch.is_some() || params.base_url.is_some() {
4948        return None;
4949    }
4950    harness_support_registry()
4951        .harnesses
4952        .into_iter()
4953        .find(|descriptor| descriptor.id == params.harness)
4954        .filter(|descriptor| descriptor.runtime.connect_launch.is_some())
4955}
4956
4957fn service_home() -> std::result::Result<PathBuf, ServiceError> {
4958    std::env::var_os("HOME").map(PathBuf::from).ok_or_else(|| {
4959        ServiceError::UnsupportedAction(
4960            "connect-mode launches need HOME to locate the harness config".into(),
4961        )
4962    })
4963}
4964
4965/// The doors that open a runtime: each spawns or joins a program and waits on
4966/// that program's protocol handshake before it can answer.
4967pub const RUNTIME_OPEN_METHODS: &[&str] = &[
4968    "harness.v1.runtimes.start",
4969    "harness.v1.runtimes.resume",
4970    "harness.v1.runtimes.attach",
4971    "harness.v1.runtimes.attach_existing",
4972];
4973
4974/// How long a runtime gets to finish opening before its caller is answered an
4975/// error instead. A program that never speaks the protocol at all — the wrong
4976/// binary, a shim that prints usage and waits — never answers the handshake,
4977/// so the wait is unbounded without this.
4978pub const RUNTIME_OPEN_DEADLINE: Duration = Duration::from_secs(60);
4979
4980/// How long a control call on an ALREADY-open runtime — send input, interrupt,
4981/// steer, respond, close — gets before its caller is answered an error
4982/// instead. A live runtime answers these in milliseconds; a wedged one never
4983/// answers at all, and `close` is exactly what a caller reaches for when it
4984/// suspects that.
4985pub const RUNTIME_CONTROL_DEADLINE: Duration = Duration::from_secs(30);
4986
4987/// The doors whose work happens entirely OUTSIDE this service's state once
4988/// its state has been read: probing harnesses, couriering a message into a
4989/// live session, and performing a conversation verb through a harness's own
4990/// CLI / HTTP / store door. Every one of them waits on a child process or a
4991/// network peer. See [`HarnessSessionService::detach`].
4992pub const DETACHED_METHODS: &[&str] = &[
4993    "harness.v1.harnesses.list",
4994    "harness.v1.harnesses.probe",
4995    "harness.v1.sessions.message",
4996    "harness.v1.sessions.new",
4997    "harness.v1.sessions.reset",
4998    "harness.v1.sessions.archive",
4999    "harness.v1.sessions.delete",
5000];
5001
5002/// How long a request moved off a transport's loop gets before its caller is
5003/// answered an error instead. Each of these already bounds its own inner
5004/// waits (a probe's handshake, the courier's run); this is the backstop for
5005/// the ones that do not — a harness CLI that never exits — so no caller waits
5006/// forever on a detached task no one is watching.
5007pub const DETACHED_CALL_DEADLINE: Duration = Duration::from_secs(120);
5008
5009/// How long `sessions.discover` gets before its caller is answered an error
5010/// instead. Discovery reads each harness's own store, and a store on a cold
5011/// or unavailable mount answers at the filesystem's pace rather than its own.
5012///
5013/// Deliberately shorter than the clients' own request deadline (30s): the
5014/// server's answer names the store that did not answer, and it is only read
5015/// if it lands before the client stops listening.
5016pub const SESSION_DISCOVER_DEADLINE: Duration = Duration::from_secs(25);
5017
5018/// Bound one control call on an open runtime by [`RUNTIME_CONTROL_DEADLINE`],
5019/// naming the method and the bound when it blows.
5020async fn within_control_deadline<F: std::future::Future>(
5021    method: &str,
5022    call: F,
5023) -> std::result::Result<F::Output, ServiceError> {
5024    tokio::time::timeout(RUNTIME_CONTROL_DEADLINE, call)
5025        .await
5026        .map_err(|_| {
5027            ServiceError::Operation(format!(
5028                "`{method}` gave up after {}s: the runtime did not answer",
5029                RUNTIME_CONTROL_DEADLINE.as_secs()
5030            ))
5031        })
5032}
5033
5034/// One [`RUNTIME_OPEN_METHODS`] request, parsed but not yet started. See
5035/// [`HarnessSessionService::runtime_open`] for why it exists apart from
5036/// [`HarnessSessionService::handle_async`].
5037pub struct RuntimeOpen {
5038    id: Value,
5039    method: String,
5040    params: Value,
5041}
5042
5043impl RuntimeOpen {
5044    /// Do the waiting: spawn or join the program and complete its handshake,
5045    /// bounded by [`RUNTIME_OPEN_DEADLINE`]. Touches no service state, so this
5046    /// runs on any task.
5047    pub async fn open(self) -> OpenedRuntime {
5048        let Self { id, method, params } = self;
5049        let outcome = open_runtime(&method, params).await;
5050        OpenedRuntime { id, outcome }
5051    }
5052}
5053
5054/// The result of [`RuntimeOpen::open`], ready for
5055/// [`HarnessSessionService::finish_runtime_open`].
5056pub struct OpenedRuntime {
5057    id: Value,
5058    outcome: std::result::Result<OpenRuntime, ServiceError>,
5059}
5060
5061/// One detached request: the half that reads this service's state already
5062/// done, and the half that waits not yet started. See
5063/// [`HarnessSessionService::detach`] and
5064/// [`HarnessSessionService::detach_runtime`].
5065pub struct DetachedCall {
5066    id: Value,
5067    method: String,
5068    work: std::result::Result<Work, ServiceError>,
5069}
5070
5071impl DetachedCall {
5072    /// Do the waiting and answer. Runs on any task: whatever this call needed
5073    /// from the service was taken before it left.
5074    pub async fn run(self) -> DetachedAnswer {
5075        let Self { id, method, work } = self;
5076        match work {
5077            // A call holding a runtime is already bounded by
5078            // RUNTIME_CONTROL_DEADLINE, and its future OWNS that connection:
5079            // a second timeout around it would drop the connection mid-call
5080            // and take down a runtime its caller still has.
5081            Ok(Work::Runtime(work)) => {
5082                let (result, returned) = work.run().await;
5083                DetachedAnswer {
5084                    response: service_response(id, result),
5085                    returned,
5086                }
5087            }
5088            Ok(Work::Free(work)) => {
5089                let result = match tokio::time::timeout(DETACHED_CALL_DEADLINE, work.run()).await {
5090                    Ok(result) => result,
5091                    Err(_) => Err(ServiceError::Operation(format!(
5092                        "`{method}` gave up after {}s: the harness it waits on did not answer",
5093                        DETACHED_CALL_DEADLINE.as_secs()
5094                    ))),
5095                };
5096                DetachedAnswer {
5097                    response: service_response(id, result),
5098                    returned: None,
5099                }
5100            }
5101            Err(error) => DetachedAnswer {
5102                response: service_response(id, Err(error)),
5103                returned: None,
5104            },
5105        }
5106    }
5107}
5108
5109/// One detached call's complete answer, plus whatever it must hand back to
5110/// the service before that answer is written. See
5111/// [`HarnessSessionService::finish_detached`].
5112pub struct DetachedAnswer {
5113    response: Value,
5114    returned: Option<ReturnedRuntime>,
5115}
5116
5117impl DetachedAnswer {
5118    /// The caller's JSON-RPC response, for a transport that owns no service
5119    /// to give a borrowed connection back to.
5120    pub fn into_response(self) -> Value {
5121        self.response
5122    }
5123}
5124
5125/// A connection lent to a detached call, on its way back to the service that
5126/// owns it.
5127pub struct ReturnedRuntime {
5128    connection: String,
5129    runtime: Box<dyn RuntimeConnection>,
5130}
5131
5132/// The waiting half of one detached request: with nothing of the service's
5133/// in hand, or holding a connection the service lent out for the call.
5134enum Work {
5135    Free(DetachedWork),
5136    Runtime(RuntimeWork),
5137}
5138
5139/// The waiting half of one detached request that holds nothing of the
5140/// service's.
5141enum DetachedWork {
5142    /// Probe the selected harnesses: find their executables, ask each its
5143    /// version, and at `probe: handshake` start each one and complete its
5144    /// protocol handshake.
5145    Inventory(InventoryWork),
5146    /// Run the courier that delivers one message into a live session.
5147    Message(MessageSessionParams),
5148    /// Perform one conversation verb through the harness's own CLI, HTTP API,
5149    /// daemon socket, or supercode's own store.
5150    SessionMutation {
5151        verb: crate::SessionVerb,
5152        mutation: crate::SessionMutation,
5153    },
5154}
5155
5156impl DetachedWork {
5157    async fn run(self) -> std::result::Result<Value, ServiceError> {
5158        match self {
5159            Self::Inventory(work) => run_inventory(work).await,
5160            Self::Message(params) => {
5161                Ok(message_live_session(&params, &crate::claude_peer::ProcessCourierRunner).await)
5162            }
5163            Self::SessionMutation { verb, mutation } => {
5164                let outcome = run_session_mutation(verb, &mutation).await?;
5165                serde_json::to_value(outcome)
5166                    .map_err(|error| ServiceError::Operation(error.to_string()))
5167            }
5168        }
5169    }
5170}
5171
5172/// One detached call that holds a runtime connection for its whole run.
5173enum RuntimeWork {
5174    /// Tear down a runtime the service has already surrendered.
5175    Close {
5176        runtime: Box<dyn RuntimeConnection>,
5177        process_group: Option<u32>,
5178    },
5179    /// Type one live slash command through a borrowed connection, then give
5180    /// the connection back.
5181    LiveCommand {
5182        connection: String,
5183        runtime: Box<dyn RuntimeConnection>,
5184        verb: crate::SessionVerb,
5185        mutation: crate::SessionMutation,
5186        command: &'static str,
5187        session: String,
5188    },
5189}
5190
5191/// What one [`RuntimeWork`] answers with: the caller's result, and the
5192/// connection to give back when the call only borrowed one.
5193type RuntimeWorkAnswer = (
5194    std::result::Result<Value, ServiceError>,
5195    Option<ReturnedRuntime>,
5196);
5197
5198impl RuntimeWork {
5199    async fn run(self) -> RuntimeWorkAnswer {
5200        match self {
5201            Self::Close {
5202                runtime,
5203                process_group,
5204            } => (close_runtime(runtime, process_group).await, None),
5205            Self::LiveCommand {
5206                connection,
5207                mut runtime,
5208                verb,
5209                mutation,
5210                command,
5211                session,
5212            } => {
5213                let result =
5214                    type_live_command(runtime.as_mut(), verb, &mutation, command, session).await;
5215                (
5216                    result,
5217                    Some(ReturnedRuntime {
5218                        connection,
5219                        runtime,
5220                    }),
5221                )
5222            }
5223        }
5224    }
5225}
5226
5227/// Tear down a runtime already out of the service, within
5228/// [`RUNTIME_CONTROL_DEADLINE`].
5229async fn close_runtime(
5230    mut runtime: Box<dyn RuntimeConnection>,
5231    process_group: Option<u32>,
5232) -> std::result::Result<Value, ServiceError> {
5233    match within_control_deadline("harness.v1.runtimes.close", runtime.close()).await {
5234        Ok(result) => {
5235            result.map_err(operation)?;
5236            Ok(json!({"closed": true}))
5237        }
5238        Err(deadline) => {
5239            // Dropping the handle is not enough: the process that stopped
5240            // answering is held by a task parked on it, so nothing here runs
5241            // its Drop. Signal the group the graceful path would have
5242            // signalled, then say so.
5243            let killed = kill_runtime_process_group(process_group);
5244            drop(runtime);
5245            Ok(json!({
5246                "closed": true,
5247                "killed": killed,
5248                "detail": error_message(deadline),
5249            }))
5250        }
5251    }
5252}
5253
5254/// The conversation a live `sessions.new` / `sessions.reset` acts on: the one
5255/// the request named, or the runtime's own session.
5256fn live_session_name(runtime: &dyn RuntimeConnection, mutation: &crate::SessionMutation) -> String {
5257    mutation
5258        .session
5259        .clone()
5260        .filter(|value| !value.trim().is_empty())
5261        .unwrap_or_else(|| runtime.handle().runtime_id.clone())
5262}
5263
5264/// Type one harness slash command into a live session through the very same
5265/// `send_input` path a human's message takes, within
5266/// [`RUNTIME_CONTROL_DEADLINE`].
5267async fn type_live_command(
5268    runtime: &mut dyn RuntimeConnection,
5269    verb: crate::SessionVerb,
5270    mutation: &crate::SessionMutation,
5271    command: &str,
5272    session: String,
5273) -> std::result::Result<Value, ServiceError> {
5274    within_control_deadline(
5275        &format!("sessions.{}", verb.as_str()),
5276        runtime.send_input(RuntimeInput {
5277            text: command.to_string(),
5278            image_urls: Vec::new(),
5279        }),
5280    )
5281    .await?
5282    .map_err(operation)?;
5283    let outcome = crate::sessions_control::live_outcome(verb, mutation, command, session)
5284        .map_err(session_control_error)?;
5285    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
5286}
5287
5288/// A runtime that is up and whose handshake completed, with what the service
5289/// needs to take ownership of it.
5290enum OpenRuntime {
5291    /// supercode spawned this process, so it also hosts it: a frontend server,
5292    /// a live-runtime registration and a terminal launch of its own.
5293    Hosted {
5294        runtime: Box<dyn RuntimeConnection>,
5295        capabilities: crate::RuntimeCapabilities,
5296        workspace: PathBuf,
5297    },
5298    /// `attach_existing` joined a process supercode does not own. It is
5299    /// registered as a bare connection and hosts nothing.
5300    Joined { runtime: Box<dyn RuntimeConnection> },
5301}
5302
5303/// Open the runtime one [`RUNTIME_OPEN_METHODS`] request asks for, within
5304/// [`RUNTIME_OPEN_DEADLINE`]. The error a blown deadline answers names the
5305/// method and the bound, so a caller reads why it was cut loose instead of
5306/// waiting on a handshake that is never coming.
5307async fn open_runtime(
5308    method: &str,
5309    params: Value,
5310) -> std::result::Result<OpenRuntime, ServiceError> {
5311    match tokio::time::timeout(
5312        RUNTIME_OPEN_DEADLINE,
5313        open_runtime_unbounded(method, params),
5314    )
5315    .await
5316    {
5317        Ok(result) => result,
5318        Err(_) => Err(ServiceError::Operation(format!(
5319            "`{method}` gave up after {}s: the runtime never finished its protocol handshake",
5320            RUNTIME_OPEN_DEADLINE.as_secs()
5321        ))),
5322    }
5323}
5324
5325async fn open_runtime_unbounded(
5326    method: &str,
5327    params: Value,
5328) -> std::result::Result<OpenRuntime, ServiceError> {
5329    match method {
5330        "harness.v1.runtimes.start" => {
5331            let params = decode::<RuntimeStartParams>(params)?;
5332            let backend = runtime_backend(&params.backend)?;
5333            let capabilities = backend.capabilities();
5334            let workspace = params.cwd.clone();
5335            let runtime = backend
5336                .start(RuntimeStartRequest {
5337                    cwd: params.cwd,
5338                    launch: runtime_launch(&params.backend),
5339                    mcp_servers: params.mcp_servers,
5340                })
5341                .await
5342                .map_err(operation)?;
5343            Ok(OpenRuntime::Hosted {
5344                runtime,
5345                capabilities,
5346                workspace,
5347            })
5348        }
5349        "harness.v1.runtimes.resume" | "harness.v1.runtimes.attach" => {
5350            let params = decode::<RuntimeAttachParams>(params)?;
5351            let backend = runtime_backend(&params.backend)?;
5352            let capabilities = backend.capabilities();
5353            let workspace = params
5354                .cwd
5355                .clone()
5356                .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
5357            let runtime = backend
5358                .attach(RuntimeAttachRequest {
5359                    runtime_id: params.runtime_id,
5360                    cwd: params.cwd,
5361                    launch: runtime_launch(&params.backend),
5362                    mcp_servers: params.mcp_servers,
5363                })
5364                .await
5365                .map_err(operation)?;
5366            Ok(OpenRuntime::Hosted {
5367                runtime,
5368                capabilities,
5369                workspace,
5370            })
5371        }
5372        "harness.v1.runtimes.attach_existing" => {
5373            let params = decode::<RuntimeAttachParams>(params)?;
5374            let backend: Box<dyn RuntimeBackend> = match params
5375                .backend
5376                .base_url
5377                .as_deref()
5378                .and_then(|value| LiveRuntimeEndpoint::parse(value).ok())
5379            {
5380                Some(endpoint) => {
5381                    #[cfg(not(feature = "adapter-api"))]
5382                    {
5383                        let _ = endpoint;
5384                        return Err(ServiceError::UnsupportedAction(
5385                            "live HTTP attachment adapter is not compiled".into(),
5386                        ));
5387                    }
5388                    #[cfg(feature = "adapter-api")]
5389                    {
5390                        let workspace = params.cwd.clone().ok_or_else(|| {
5391                            ServiceError::InvalidParams(
5392                                "Supercode live attach requires the project cwd".into(),
5393                            )
5394                        })?;
5395                        let source = LiveRuntimeSource {
5396                            harness: params.backend.harness.as_str().to_string(),
5397                            session_id: params.runtime_id.clone(),
5398                            workspace,
5399                        };
5400                        let receipt = resolve_live_runtime(&endpoint, &source)
5401                            .map_err(|error| ServiceError::Operation(error.to_string()))?;
5402                        Box::new(SupercodeHttpRuntimeBackend::new(receipt))
5403                    }
5404                }
5405                None => runtime_backend(&params.backend)?,
5406            };
5407            let capabilities = backend.capabilities();
5408            if !capabilities.attach_existing_process {
5409                return Err(ServiceError::Operation(format!(
5410                    "{} cannot attach to an already-running process; use runtimes.resume for a persisted session",
5411                    backend.harness().as_str()
5412                )));
5413            }
5414            let runtime = backend
5415                .attach_existing(RuntimeAttachRequest {
5416                    runtime_id: params.runtime_id,
5417                    cwd: params.cwd,
5418                    launch: runtime_launch(&params.backend),
5419                    mcp_servers: params.mcp_servers,
5420                })
5421                .await
5422                .map_err(operation)?;
5423            Ok(OpenRuntime::Joined { runtime })
5424        }
5425        _ => Err(ServiceError::MethodNotFound),
5426    }
5427}
5428
5429/// Wrap one service outcome in its JSON-RPC 2.0 envelope.
5430fn service_response(id: Value, result: std::result::Result<Value, ServiceError>) -> Value {
5431    match result {
5432        Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
5433        Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
5434        Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
5435        Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
5436        Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
5437        Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
5438    }
5439}
5440
5441fn runtime_backend(
5442    params: &RuntimeBackendParams,
5443) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
5444    if let Some(descriptor) = registry_connect_descriptor(params) {
5445        return open_connect_descriptor(&descriptor, &service_home()?);
5446    }
5447    if params.protocol.as_deref() == Some("acp") {
5448        let launch = params
5449            .launch
5450            .clone()
5451            .or_else(|| {
5452                harness_support_registry()
5453                    .harnesses
5454                    .into_iter()
5455                    .find(|harness| harness.id == params.harness)
5456                    .filter(|harness| {
5457                        harness.runtime.implementation == ImplementationKind::GenericProtocol
5458                            && harness.runtime.protocol.starts_with("acp")
5459                    })
5460                    .and_then(|harness| harness.runtime.default_launch)
5461            })
5462            .ok_or_else(|| {
5463                ServiceError::InvalidParams(
5464                    "an ACP runtime requires `launch` unless the harness has a registered default"
5465                        .into(),
5466                )
5467            })?;
5468        let resume_session = harness_support_registry()
5469            .harnesses
5470            .into_iter()
5471            .find(|harness| harness.id == params.harness)
5472            .is_some_and(|harness| harness.runtime.capabilities.resume_session);
5473        return Ok(Box::new(
5474            AcpRuntimeBackend::new(params.harness.clone(), launch)
5475                .with_resume_support(resume_session),
5476        ));
5477    }
5478    let backend: Box<dyn RuntimeBackend> = match params.harness.as_str() {
5479        HarnessId::CODEX => Box::new(CodexRuntimeBackend::new()),
5480        HarnessId::CLAUDE_CODE => Box::new(ClaudeCodeRuntimeBackend::new()),
5481        HarnessId::PI => Box::new(PiRuntimeBackend::new()),
5482        HarnessId::OPENCODE => match &params.base_url {
5483            Some(url) => Box::new(OpenCodeRuntimeBackend::connect(url)),
5484            None => Box::new(OpenCodeRuntimeBackend::new()),
5485        },
5486        harness => {
5487            let descriptor = harness_support_registry()
5488                .harnesses
5489                .into_iter()
5490                .find(|descriptor| descriptor.id.as_str() == harness)
5491                .filter(|descriptor| {
5492                    descriptor.runtime.implementation == ImplementationKind::GenericProtocol
5493                        && descriptor.runtime.protocol.starts_with("acp")
5494                });
5495            let Some(descriptor) = descriptor else {
5496                return Err(ServiceError::InvalidParams(format!(
5497                    "no runtime adapter for harness `{harness}`; use protocol `acp` with a launch command"
5498                )));
5499            };
5500            let resume = descriptor.runtime.capabilities.resume_session;
5501            Box::new(
5502                AcpRuntimeBackend::new(
5503                    descriptor.id,
5504                    descriptor
5505                        .runtime
5506                        .default_launch
5507                        .expect("generic ACP registry entry includes its launch"),
5508                )
5509                .with_resume_support(resume),
5510            )
5511        }
5512    };
5513    Ok(backend)
5514}
5515
5516fn runtime_launch(params: &RuntimeBackendParams) -> Option<RuntimeLaunch> {
5517    if let Some(launch) = &params.launch {
5518        return Some(launch.clone());
5519    }
5520    if !matches!(params.policy, RuntimePolicy::Yolo) {
5521        return None;
5522    }
5523    let launch = match params.harness.as_str() {
5524        HarnessId::GROK => RuntimeLaunch {
5525            program: "grok".into(),
5526            arguments: {
5527                let mut arguments: Vec<String> = Vec::new();
5528                if crate::support::self_sandbox_supported() {
5529                    arguments.extend(["--sandbox".into(), "workspace".into()]);
5530                }
5531                arguments.extend([
5532                    "--always-approve".into(),
5533                    "agent".into(),
5534                    "--no-leader".into(),
5535                    "stdio".into(),
5536                ]);
5537                arguments
5538            },
5539            env: crate::support::grok_env(),
5540        },
5541        HarnessId::CODEX => RuntimeLaunch {
5542            program: "codex".into(),
5543            arguments: vec![
5544                "--dangerously-bypass-approvals-and-sandbox".into(),
5545                "--dangerously-bypass-hook-trust".into(),
5546                "app-server".into(),
5547            ],
5548            env: BTreeMap::new(),
5549        },
5550        HarnessId::CLAUDE_CODE => RuntimeLaunch {
5551            program: "claude".into(),
5552            arguments: vec![
5553                "--dangerously-skip-permissions".into(),
5554                "--print".into(),
5555                "--input-format".into(),
5556                "stream-json".into(),
5557                "--output-format".into(),
5558                "stream-json".into(),
5559                "--verbose".into(),
5560            ],
5561            env: BTreeMap::new(),
5562        },
5563        HarnessId::PI => RuntimeLaunch {
5564            program: "pi".into(),
5565            arguments: vec!["--approve".into(), "--mode".into(), "rpc".into()],
5566            env: BTreeMap::new(),
5567        },
5568        HarnessId::OPENCODE => RuntimeLaunch {
5569            program: "opencode".into(),
5570            arguments: vec!["serve".into()],
5571            env: BTreeMap::new(),
5572        },
5573        HarnessId::GEMINI => RuntimeLaunch {
5574            program: "gemini".into(),
5575            arguments: vec!["--acp".into(), "--yolo".into()],
5576            env: BTreeMap::new(),
5577        },
5578        HarnessId::GOOSE => RuntimeLaunch {
5579            program: "goose".into(),
5580            arguments: vec!["acp".into()],
5581            env: BTreeMap::new(),
5582        },
5583        HarnessId::SUPERCODE => RuntimeLaunch {
5584            program: "supercode".into(),
5585            arguments: vec!["acp".into(), "--dangerous".into()],
5586            env: BTreeMap::new(),
5587        },
5588        _ => return None,
5589    };
5590    Some(launch)
5591}
5592
5593/// Disposable harness state for a no-prompt readiness probe. Merely opening
5594/// several stock CLIs writes a session header or migrates configuration, so a
5595/// handshake must never point at the user's real home. Authentication files
5596/// are copied into the private temporary home; all writes disappear with the
5597/// guard after the connection closes.
5598struct IsolatedProbeHome {
5599    launch: RuntimeLaunch,
5600    root: PathBuf,
5601}
5602
5603impl IsolatedProbeHome {
5604    fn new(harness: &str, mut launch: RuntimeLaunch) -> std::io::Result<Self> {
5605        let root = std::env::temp_dir().join(format!(
5606            "supercode-harness-probe-{harness}-{}",
5607            generated_session_id()
5608        ));
5609        std::fs::create_dir_all(&root)?;
5610        set_private_dir_permissions(&root)?;
5611
5612        if let Some(source_home) = std::env::var_os("HOME").map(PathBuf::from) {
5613            for relative in probe_auth_files(harness) {
5614                copy_probe_file(&source_home, &root, relative)?;
5615            }
5616        }
5617        // supercode reads its own config home ($SUPERCODE_HOME, else
5618        // $XDG_CONFIG_HOME/supercode, else ~/.config/supercode), not a fixed
5619        // place under HOME: a login kept under XDG_CONFIG_HOME probed as
5620        // "no API key found" while `supercode run` answered.
5621        if harness == HarnessId::SUPERCODE {
5622            let config_home = crate::agent::global_instructions_dir();
5623            for file in ["config.toml", "credentials.toml"] {
5624                copy_probe_path(
5625                    &config_home.join(file),
5626                    &root.join(".config/supercode").join(file),
5627                )?;
5628            }
5629        }
5630        configure_isolated_probe_auth(harness, &root)?;
5631
5632        let root_text = root.to_string_lossy().into_owned();
5633        for (key, value) in [
5634            ("HOME", root_text.clone()),
5635            (
5636                "XDG_CACHE_HOME",
5637                root.join(".cache").to_string_lossy().into_owned(),
5638            ),
5639            (
5640                "XDG_CONFIG_HOME",
5641                root.join(".config").to_string_lossy().into_owned(),
5642            ),
5643            (
5644                "XDG_DATA_HOME",
5645                root.join(".local/share").to_string_lossy().into_owned(),
5646            ),
5647        ] {
5648            launch.env.insert(key.into(), value);
5649        }
5650        let scoped = match harness {
5651            HarnessId::CLAUDE_CODE => Some(("CLAUDE_CONFIG_DIR", root.join(".claude"))),
5652            HarnessId::CODEX => Some(("CODEX_HOME", root.join(".codex"))),
5653            HarnessId::GEMINI => Some(("GEMINI_CLI_HOME", root.clone())),
5654            HarnessId::GROK => Some(("GROK_HOME", root.join(".grok"))),
5655            HarnessId::PI => Some(("PI_CODING_AGENT_DIR", root.join(".pi/agent"))),
5656            HarnessId::SUPERCODE => Some(("SUPERCODE_HOME", root.join(".config/supercode"))),
5657            _ => None,
5658        };
5659        if let Some((key, value)) = scoped {
5660            launch
5661                .env
5662                .insert(key.into(), value.to_string_lossy().into_owned());
5663        }
5664        Ok(Self { launch, root })
5665    }
5666
5667    fn cleanup(&self) -> std::io::Result<()> {
5668        match std::fs::remove_dir_all(&self.root) {
5669            Ok(()) => Ok(()),
5670            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
5671            Err(error) => Err(error),
5672        }
5673    }
5674}
5675
5676impl Drop for IsolatedProbeHome {
5677    fn drop(&mut self) {
5678        let _ = self.cleanup();
5679    }
5680}
5681
5682fn probe_auth_files(harness: &str) -> &'static [&'static str] {
5683    match harness {
5684        HarnessId::CLAUDE_CODE => &[".claude/.credentials.json", ".claude.json"],
5685        // The gateway endpoint + token live in openclaw's own config; without
5686        // it the isolated probe dials the default endpoint unauthenticated
5687        // (PARITY-24 finding 2026-08-31).
5688        HarnessId::OPENCLAW => &[".openclaw/openclaw.json"],
5689        HarnessId::CODEX => &[".codex/auth.json"],
5690        HarnessId::GEMINI => &[
5691            ".gemini/google_accounts.json",
5692            ".gemini/oauth_creds.json",
5693            ".gemini/settings.json",
5694        ],
5695        HarnessId::GROK => &[".grok/auth.json", ".grok/config.toml"],
5696        HarnessId::OPENCODE => &[
5697            ".config/opencode/auth.json",
5698            ".local/share/opencode/auth.json",
5699        ],
5700        HarnessId::PI => &[".pi/agent/auth.json"],
5701        // Hermes keeps its provider selection in config.yaml, its OAuth
5702        // credential pool in auth.json, and API keys in .env; without them
5703        // the isolated probe sees "No LLM provider configured" for a
5704        // hermes that answers fine from the user's real home.
5705        HarnessId::HERMES => &[".hermes/config.yaml", ".hermes/auth.json", ".hermes/.env"],
5706        _ => &[],
5707    }
5708}
5709
5710fn copy_probe_file(source_home: &Path, probe_home: &Path, relative: &str) -> std::io::Result<()> {
5711    copy_probe_path(&source_home.join(relative), &probe_home.join(relative))
5712}
5713
5714fn copy_probe_path(source: &Path, destination: &Path) -> std::io::Result<()> {
5715    if !source.is_file() {
5716        return Ok(());
5717    }
5718    if let Some(parent) = destination.parent() {
5719        std::fs::create_dir_all(parent)?;
5720        set_private_dir_permissions(parent)?;
5721    }
5722    std::fs::copy(source, destination)?;
5723    set_private_file_permissions(destination)
5724}
5725
5726fn configure_isolated_probe_auth(harness: &str, probe_home: &Path) -> std::io::Result<()> {
5727    if harness != HarnessId::GEMINI {
5728        return Ok(());
5729    }
5730    let oauth = probe_home.join(".gemini/oauth_creds.json");
5731    if !oauth.is_file() {
5732        return Ok(());
5733    }
5734    let settings_path = probe_home.join(".gemini/settings.json");
5735    let mut settings = std::fs::read_to_string(&settings_path)
5736        .ok()
5737        .and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
5738        .unwrap_or_else(|| json!({}));
5739    settings["security"]["auth"]["selectedType"] = Value::String("oauth-personal".into());
5740    std::fs::write(
5741        &settings_path,
5742        serde_json::to_vec_pretty(&settings).map_err(std::io::Error::other)?,
5743    )?;
5744    set_private_file_permissions(&settings_path)
5745}
5746
5747#[cfg(unix)]
5748fn set_private_dir_permissions(path: &Path) -> std::io::Result<()> {
5749    use std::os::unix::fs::PermissionsExt;
5750    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
5751}
5752
5753#[cfg(not(unix))]
5754fn set_private_dir_permissions(_path: &Path) -> std::io::Result<()> {
5755    Ok(())
5756}
5757
5758#[cfg(unix)]
5759fn set_private_file_permissions(path: &Path) -> std::io::Result<()> {
5760    use std::os::unix::fs::PermissionsExt;
5761    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
5762}
5763
5764#[cfg(not(unix))]
5765fn set_private_file_permissions(_path: &Path) -> std::io::Result<()> {
5766    Ok(())
5767}
5768
5769fn find_executable(program: &str) -> Option<PathBuf> {
5770    let candidate = PathBuf::from(program);
5771    if candidate.components().count() > 1 {
5772        return candidate.is_file().then_some(candidate);
5773    }
5774    let path = std::env::var_os("PATH")?;
5775    for directory in std::env::split_paths(&path) {
5776        let candidate = directory.join(program);
5777        if candidate.is_file() {
5778            return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
5779        }
5780        #[cfg(windows)]
5781        {
5782            for extension in ["exe", "cmd", "bat"] {
5783                let candidate = directory.join(format!("{program}.{extension}"));
5784                if candidate.is_file() {
5785                    return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
5786                }
5787            }
5788        }
5789    }
5790    None
5791}
5792
5793async fn executable_version(executable: &Path) -> Option<String> {
5794    let mut command = tokio::process::Command::new(executable);
5795    command
5796        .arg("--version")
5797        .stdin(std::process::Stdio::null())
5798        .stdout(std::process::Stdio::piped())
5799        .stderr(std::process::Stdio::piped())
5800        .kill_on_drop(true);
5801    let output = tokio::time::timeout(Duration::from_secs(3), command.output())
5802        .await
5803        .ok()?
5804        .ok()?;
5805    let stdout = String::from_utf8_lossy(&output.stdout);
5806    let stderr = String::from_utf8_lossy(&output.stderr);
5807    stdout
5808        .lines()
5809        .chain(stderr.lines())
5810        .map(str::trim)
5811        .find(|line| !line.is_empty())
5812        .map(|line| truncate_text(line, 200))
5813}
5814
5815pub(crate) fn auth_evidence(harness: &str) -> bool {
5816    let env_names: &[&str] = match harness {
5817        HarnessId::CLAUDE_CODE => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
5818        HarnessId::CODEX => &["OPENAI_API_KEY"],
5819        HarnessId::OPENCODE => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
5820        HarnessId::PI => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
5821        HarnessId::GROK => &["XAI_API_KEY", "GROK_API_KEY"],
5822        HarnessId::GEMINI => &["GEMINI_API_KEY", "GOOGLE_API_KEY"],
5823        HarnessId::SUPERCODE => &["OPENROUTER_API_KEY"],
5824        _ => &[],
5825    };
5826    if env_names
5827        .iter()
5828        .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
5829    {
5830        return true;
5831    }
5832    let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else {
5833        return false;
5834    };
5835    let files: Vec<PathBuf> = match harness {
5836        HarnessId::CLAUDE_CODE => vec![home.join(".claude/.credentials.json")],
5837        HarnessId::CODEX => vec![home.join(".codex/auth.json")],
5838        HarnessId::OPENCODE => vec![
5839            home.join(".local/share/opencode/auth.json"),
5840            home.join(".config/opencode/auth.json"),
5841        ],
5842        HarnessId::PI => vec![home.join(".pi/agent/auth.json")],
5843        HarnessId::GROK => vec![home.join(".grok/auth.json")],
5844        HarnessId::GEMINI => vec![
5845            home.join(".gemini/oauth_creds.json"),
5846            home.join(".gemini/google_accounts.json"),
5847        ],
5848        HarnessId::SUPERCODE => vec![home.join(".config/supercode/credentials.toml")],
5849        HarnessId::HERMES => vec![home.join(".hermes/auth.json"), home.join(".hermes/.env")],
5850        _ => Vec::new(),
5851    };
5852    if files.into_iter().any(|path| {
5853        std::fs::metadata(path)
5854            .map(|metadata| metadata.is_file() && metadata.len() > 2)
5855            .unwrap_or(false)
5856    }) {
5857        return true;
5858    }
5859    // macOS keeps Claude Code's OAuth login in the Keychain, so
5860    // `.claude/.credentials.json` never exists there and the file probe above
5861    // reports a signed-in install as unauthenticated forever. A completed
5862    // login also writes an `oauthAccount` record into `~/.claude.json` on
5863    // every platform — file-based, prompt-free evidence (querying the
5864    // Keychain itself from an unsigned daemon can raise a UI prompt).
5865    if harness == HarnessId::CLAUDE_CODE {
5866        return std::fs::read_to_string(home.join(".claude.json"))
5867            .map(|text| text.contains("\"oauthAccount\""))
5868            .unwrap_or(false);
5869    }
5870    false
5871}
5872
5873fn looks_like_auth_error(message: &str) -> bool {
5874    let message = message.to_ascii_lowercase();
5875    [
5876        "auth",
5877        "login",
5878        "sign in",
5879        "sign-in",
5880        "credential",
5881        "unauthorized",
5882        "forbidden",
5883        "token",
5884    ]
5885    .iter()
5886    .any(|needle| message.contains(needle))
5887}
5888
5889fn unavailable_capabilities() -> crate::RuntimeCapabilities {
5890    crate::RuntimeCapabilities {
5891        start_session: false,
5892        resume_session: false,
5893        attach_existing_process: false,
5894        send_input: false,
5895        stream_events: false,
5896        interrupt: false,
5897        steer: false,
5898        respond_to_requests: false,
5899    }
5900}
5901
5902fn truncate_text(text: &str, max_chars: usize) -> String {
5903    let mut chars = text.chars();
5904    let truncated = chars.by_ref().take(max_chars).collect::<String>();
5905    if chars.next().is_some() {
5906        format!("{truncated}…")
5907    } else {
5908        truncated
5909    }
5910}
5911
5912/// The process group a runtime's own handle names, when it names one.
5913///
5914/// Every adapter that spawns a local process spawns it as its own group
5915/// leader (`Command::process_group(0)`), so the endpoint's pid IS the group
5916/// id. A runtime reached over HTTP, or one supercode joined rather than
5917/// spawned, names no group here and is left alone.
5918fn runtime_process_group(handle: &crate::RuntimeHandle) -> Option<u32> {
5919    match &handle.endpoint {
5920        crate::RuntimeEndpoint::LocalProcess { pid, .. } => *pid,
5921        crate::RuntimeEndpoint::Http { .. } => None,
5922    }
5923}
5924
5925/// SIGKILL a wedged runtime's whole process group, reporting whether there
5926/// was one to signal. This is the same group teardown a graceful `close`
5927/// performs; it runs here only when the graceful path blew its deadline,
5928/// because the task parked on the unanswered call still owns the process
5929/// handle and so no `Drop` of ours can reach it.
5930fn kill_runtime_process_group(process_group: Option<u32>) -> bool {
5931    match process_group {
5932        #[cfg(unix)]
5933        Some(pid) => {
5934            crate::lsp::kill_process_group(pid);
5935            true
5936        }
5937        #[cfg(not(unix))]
5938        Some(_) => false,
5939        None => false,
5940    }
5941}
5942
5943fn error_message(error: ServiceError) -> String {
5944    match error {
5945        ServiceError::InvalidParams(message)
5946        | ServiceError::Operation(message)
5947        | ServiceError::UnsupportedAction(message) => message,
5948        ServiceError::MethodNotFound => "runtime adapter is not available".into(),
5949        ServiceError::Sdk(error) => error.to_string(),
5950    }
5951}
5952
5953#[derive(Debug)]
5954enum ServiceError {
5955    InvalidParams(String),
5956    MethodNotFound,
5957    UnsupportedAction(String),
5958    Operation(String),
5959    Sdk(SdkError),
5960}
5961
5962fn sdk_error(operation: SdkOperation, error: ServiceError) -> SdkError {
5963    match error {
5964        ServiceError::InvalidParams(message) => {
5965            SdkError::new(SdkErrorCode::InvalidArgument, operation, message)
5966        }
5967        ServiceError::MethodNotFound | ServiceError::UnsupportedAction(_) => {
5968            SdkError::unsupported(operation)
5969        }
5970        ServiceError::Operation(message) => {
5971            let code = if message.contains("already in progress") {
5972                SdkErrorCode::Busy
5973            } else if message.contains("not supported by this runtime") {
5974                SdkErrorCode::UnsupportedAction
5975            } else if message.contains("unknown runtime connection") {
5976                SdkErrorCode::NotFound
5977            } else {
5978                SdkErrorCode::Execution
5979            };
5980            SdkError::new(code, operation, message)
5981        }
5982        ServiceError::Sdk(error) => error,
5983    }
5984}
5985
5986fn sdk_rpc_error(id: Value, error: &SdkError) -> Value {
5987    let error_code = error.code();
5988    let code = match error_code {
5989        SdkErrorCode::Unauthenticated => -32030,
5990        SdkErrorCode::Unauthorized => -32031,
5991        SdkErrorCode::ControllerRequired => -32032,
5992        SdkErrorCode::LeaseExpired => -32033,
5993        SdkErrorCode::InvalidArgument => -32602,
5994        SdkErrorCode::NotFound => -32004,
5995        SdkErrorCode::Busy => -32000,
5996        SdkErrorCode::UnsupportedAction => -32020,
5997        SdkErrorCode::Execution => -32002,
5998        SdkErrorCode::Transport => -32003,
5999    };
6000    json!({
6001        "jsonrpc": "2.0",
6002        "id": id,
6003        "error": {
6004            "code": code,
6005            "name": error_code,
6006            "operation": error.operation(),
6007            "message": error.to_string(),
6008        },
6009    })
6010}
6011
6012fn decode<T: for<'de> Deserialize<'de>>(value: Value) -> std::result::Result<T, ServiceError> {
6013    serde_json::from_value(value).map_err(|error| ServiceError::InvalidParams(error.to_string()))
6014}
6015
6016fn operation(error: impl Into<crate::Error>) -> ServiceError {
6017    let error = error.into();
6018    match error {
6019        crate::Error::Sdk(error) => ServiceError::Sdk(error),
6020        error => ServiceError::Operation(error.to_string()),
6021    }
6022}
6023
6024/// ORCH-12 `harness.v1.memory.show|search` params. `homes` is the same
6025/// storage-root override every read-only method accepts, so a caller can
6026/// point the read at a fixture home without touching the real ones.
6027#[derive(Debug, Clone, Deserialize, Default)]
6028#[serde(default)]
6029struct MemoryRequest {
6030    /// Harness whose store is read. Required.
6031    harness: Option<String>,
6032    /// The needle, required by `search`.
6033    query: Option<String>,
6034    /// Hermes profile, OpenClaw agent, or Claude Code project.
6035    profile: Option<String>,
6036    /// Claude Code session id selecting a project store (`show` only).
6037    session: Option<String>,
6038    /// Include each document's whole text (`show` only).
6039    full: bool,
6040    /// Treat `query` as a regular expression (`search` only).
6041    regex: bool,
6042    /// Working tree whose project store is read.
6043    cwd: Option<std::path::PathBuf>,
6044    /// Storage roots to read.
6045    homes: crate::HarnessHomes,
6046}
6047
6048/// Read the memory noun. A harness with no memory store fails with
6049/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6050fn memory_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6051    let request = decode::<MemoryRequest>(params)?;
6052    let harness = request
6053        .harness
6054        .clone()
6055        .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6056    let to_service = |error: crate::memory::MemoryError| match error {
6057        crate::memory::MemoryError::UnsupportedHarness { .. }
6058        | crate::memory::MemoryError::SessionNotScoped { .. } => {
6059            ServiceError::UnsupportedAction(error.to_string())
6060        }
6061        other => ServiceError::InvalidParams(other.to_string()),
6062    };
6063    match method {
6064        "harness.v1.memory.show" => {
6065            let documents = crate::memory::show_memory(&crate::memory::MemoryQuery {
6066                harness,
6067                profile: request.profile,
6068                session: request.session,
6069                full: request.full,
6070                cwd: request.cwd,
6071                homes: request.homes,
6072            })
6073            .map_err(to_service)?;
6074            Ok(json!({
6075                "schema": crate::memory::MEMORY_SCHEMA,
6076                "documents": documents,
6077            }))
6078        }
6079        "harness.v1.memory.search" => {
6080            let query = request
6081                .query
6082                .ok_or_else(|| ServiceError::InvalidParams("`query` is required".into()))?;
6083            let matches = crate::memory::search_memory(&crate::memory::MemorySearchQuery {
6084                harness,
6085                query,
6086                profile: request.profile,
6087                regex: request.regex,
6088                cwd: request.cwd,
6089                homes: request.homes,
6090            })
6091            .map_err(to_service)?;
6092            Ok(json!({
6093                "schema": crate::memory::MEMORY_SCHEMA,
6094                "matches": matches,
6095            }))
6096        }
6097        _ => Err(ServiceError::MethodNotFound),
6098    }
6099}
6100
6101/// ORCH-10 `harness.v1.profiles.list|get` params. `homes` is the same
6102/// storage-root override every read-only method accepts, so a caller can
6103/// point the read at a fixture home without touching the real ones.
6104#[derive(Debug, Clone, Deserialize)]
6105#[serde(default)]
6106struct ProfilesQuery {
6107    /// Restrict the listing to one harness. `get` requires it.
6108    harness: Option<String>,
6109    /// Profile name, required by `get`.
6110    name: Option<String>,
6111    /// Storage roots to read.
6112    homes: crate::HarnessHomes,
6113}
6114
6115impl Default for ProfilesQuery {
6116    fn default() -> Self {
6117        Self {
6118            harness: None,
6119            name: None,
6120            homes: crate::HarnessHomes::default(),
6121        }
6122    }
6123}
6124
6125/// Read the profile noun. A harness with no profile concept fails with
6126/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6127fn profiles_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6128    let query = decode::<ProfilesQuery>(params)?;
6129    let to_service = |error: crate::profiles::ProfileError| match error {
6130        crate::profiles::ProfileError::UnsupportedHarness { .. } => {
6131            ServiceError::UnsupportedAction(error.to_string())
6132        }
6133        crate::profiles::ProfileError::NotFound { .. } => {
6134            ServiceError::InvalidParams(error.to_string())
6135        }
6136    };
6137    match method {
6138        "harness.v1.profiles.list" => {
6139            let profiles = crate::profiles::list_profiles(&query.homes, query.harness.as_deref())
6140                .map_err(to_service)?;
6141            Ok(json!({
6142                "schema": crate::profiles::PROFILES_SCHEMA,
6143                "profiles": profiles,
6144            }))
6145        }
6146        "harness.v1.profiles.get" => {
6147            let harness = query
6148                .harness
6149                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6150            let name = query
6151                .name
6152                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6153            let profile =
6154                crate::profiles::get_profile(&query.homes, &harness, &name).map_err(to_service)?;
6155            Ok(json!({
6156                "schema": crate::profiles::PROFILES_SCHEMA,
6157                "profile": profile,
6158            }))
6159        }
6160        _ => Err(ServiceError::MethodNotFound),
6161    }
6162}
6163
6164/// ORCH-14 `harness.v1.channels.list|status` params, the same storage-root
6165/// override every read-only method accepts so a caller can point the read at
6166/// a fixture home without touching the real ones.
6167#[derive(Debug, Clone, Deserialize)]
6168#[serde(default)]
6169struct ChannelsQuery {
6170    /// Restrict the listing to one harness. `status` requires it.
6171    harness: Option<String>,
6172    /// Channel name, required by `status`.
6173    name: Option<String>,
6174    /// Storage roots to read.
6175    homes: crate::HarnessHomes,
6176}
6177
6178impl Default for ChannelsQuery {
6179    fn default() -> Self {
6180        Self {
6181            harness: None,
6182            name: None,
6183            homes: crate::HarnessHomes::default(),
6184        }
6185    }
6186}
6187
6188/// Read the channel noun. A harness with no channel concept fails with
6189/// `UnsupportedAction` (RPC `-32020`), never an empty list. No row carries a
6190/// token, key or secret — see `crate::channels` "Secrecy".
6191#[derive(Debug, Clone, Deserialize)]
6192#[serde(default)]
6193struct RoutesQuery {
6194    harness: Option<String>,
6195    /// Restrict to routes targeting one profile / agent.
6196    profile: Option<String>,
6197    homes: crate::HarnessHomes,
6198}
6199
6200impl Default for RoutesQuery {
6201    fn default() -> Self {
6202        Self {
6203            harness: None,
6204            profile: None,
6205            homes: crate::HarnessHomes::default(),
6206        }
6207    }
6208}
6209
6210#[derive(Debug, Clone, Deserialize)]
6211#[serde(default)]
6212struct TriggersQuery {
6213    harness: Option<String>,
6214    homes: crate::HarnessHomes,
6215}
6216
6217impl Default for TriggersQuery {
6218    fn default() -> Self {
6219        Self {
6220            harness: None,
6221            homes: crate::HarnessHomes::default(),
6222        }
6223    }
6224}
6225
6226fn triggers_call(params: Value) -> std::result::Result<Value, ServiceError> {
6227    let query = decode::<TriggersQuery>(params)?;
6228    let triggers = crate::triggers::list_triggers(&query.homes, query.harness.as_deref())
6229        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6230    Ok(json!({
6231        "schema": crate::triggers::TRIGGERS_SCHEMA,
6232        "triggers": triggers,
6233    }))
6234}
6235
6236fn routes_call(params: Value) -> std::result::Result<Value, ServiceError> {
6237    let query = decode::<RoutesQuery>(params)?;
6238    let routes = crate::routes::list_routes(
6239        &query.homes,
6240        query.harness.as_deref(),
6241        query.profile.as_deref(),
6242    )
6243    .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6244    Ok(json!({
6245        "schema": crate::routes::ROUTES_SCHEMA,
6246        "routes": routes,
6247    }))
6248}
6249
6250fn channels_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6251    let query = decode::<ChannelsQuery>(params)?;
6252    let to_service = |error: crate::channels::ChannelError| match error {
6253        crate::channels::ChannelError::UnsupportedHarness { .. } => {
6254            ServiceError::UnsupportedAction(error.to_string())
6255        }
6256        crate::channels::ChannelError::NotFound { .. } => {
6257            ServiceError::InvalidParams(error.to_string())
6258        }
6259    };
6260    match method {
6261        "harness.v1.channels.list" => {
6262            let channels = crate::channels::list_channels(&query.homes, query.harness.as_deref())
6263                .map_err(to_service)?;
6264            Ok(json!({
6265                "schema": crate::channels::CHANNELS_SCHEMA,
6266                "channels": channels,
6267            }))
6268        }
6269        "harness.v1.channels.status" => {
6270            let harness = query
6271                .harness
6272                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6273            let name = query
6274                .name
6275                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6276            let channel = crate::channels::channel_status(&query.homes, &harness, &name)
6277                .map_err(to_service)?;
6278            Ok(json!({
6279                "schema": crate::channels::CHANNELS_SCHEMA,
6280                "channel": channel,
6281            }))
6282        }
6283        _ => Err(ServiceError::MethodNotFound),
6284    }
6285}
6286
6287fn rpc_error(id: Value, code: i64, message: &str) -> Value {
6288    json!({
6289        "jsonrpc": "2.0",
6290        "id": id,
6291        "error": {"code": code, "message": message},
6292    })
6293}
6294
6295#[cfg(test)]
6296mod tests {
6297    use super::*;
6298    use crate::{HarnessEvent, HarnessId, RuntimeEndpoint, RuntimeHandle, StorageLocator};
6299    use async_trait::async_trait;
6300    use std::io::Write;
6301    use std::path::PathBuf;
6302    use std::time::Instant;
6303
6304    #[test]
6305    fn indexed_claude_descriptor_keeps_the_live_peer_address() {
6306        let descriptor = SessionDescriptor {
6307            locator: SessionLocator {
6308                harness: HarnessId::new(HarnessId::CLAUDE_CODE),
6309                session_id: "live-session".into(),
6310                storage: StorageLocator::File {
6311                    path: PathBuf::from("/tmp/live-session.jsonl"),
6312                },
6313            },
6314            cwd: Some(PathBuf::from("/project")),
6315            title: None,
6316            preview_candidates: Vec::new(),
6317            latest_message_candidates: Vec::new(),
6318            updated_at_ms: Some(1),
6319            message_count: None,
6320            model: None,
6321            parent_session_id: None,
6322            child_session_count: 0,
6323            nouns: Default::default(),
6324        };
6325        let peer = crate::claude_peer::ClaudePeerSession {
6326            pid: 42,
6327            session_id: "live-session".into(),
6328            cwd: Some(PathBuf::from("/project")),
6329            name: "peer".into(),
6330            socket_path: PathBuf::from("/tmp/peer.sock"),
6331            status: Some(crate::claude_peer::ClaudePeerStatus::Busy),
6332            updated_at_ms: Some(1),
6333            version: Some("test".into()),
6334        };
6335
6336        let value = live_descriptor_value(&descriptor, &[peer]).unwrap();
6337        assert!(value["live_endpoint"]
6338            .as_str()
6339            .is_some_and(|endpoint| endpoint.starts_with("cc-peer:v1:42:peer:")));
6340    }
6341
6342    struct EndingRuntime {
6343        handle: RuntimeHandle,
6344        event: Option<HarnessEvent>,
6345        close_failures: usize,
6346    }
6347
6348    #[async_trait]
6349    impl RuntimeConnection for EndingRuntime {
6350        fn handle(&self) -> &RuntimeHandle {
6351            &self.handle
6352        }
6353
6354        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
6355            unreachable!("ending runtime does not accept input")
6356        }
6357
6358        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
6359            Ok(self.event.take())
6360        }
6361
6362        async fn interrupt(&mut self) -> crate::Result<()> {
6363            Ok(())
6364        }
6365
6366        async fn respond(&mut self, _request_id: Value, _response: Value) -> crate::Result<()> {
6367            Ok(())
6368        }
6369
6370        async fn close(&mut self) -> crate::Result<()> {
6371            if self.close_failures > 0 {
6372                self.close_failures -= 1;
6373                return Err(crate::Error::Other(
6374                    "cleanup temporarily unavailable".into(),
6375                ));
6376            }
6377            Ok(())
6378        }
6379    }
6380
6381    fn ending_runtime(event: Option<HarnessEvent>) -> Box<dyn RuntimeConnection> {
6382        Box::new(EndingRuntime {
6383            handle: RuntimeHandle {
6384                harness: HarnessId::from(HarnessId::CLAUDE_CODE),
6385                runtime_id: "ending-session".into(),
6386                endpoint: RuntimeEndpoint::LocalProcess {
6387                    pid: None,
6388                    command: vec!["ending-runtime".into()],
6389                    protocol: "test".into(),
6390                },
6391            },
6392            event,
6393            close_failures: 0,
6394        })
6395    }
6396
6397    #[tokio::test]
6398    async fn closing_a_runtime_surrenders_the_connection_even_when_teardown_fails() {
6399        let mut service = HarnessSessionService::new();
6400        let handle = ending_runtime(None).handle().clone();
6401        let runtime_id = handle.runtime_id.clone();
6402        let opened = service
6403            .insert_runtime(Box::new(EndingRuntime {
6404                handle,
6405                event: None,
6406                close_failures: 1,
6407            }))
6408            .unwrap();
6409        let connection = opened["connection"].as_str().unwrap().to_string();
6410        service.terminal_launches.insert(
6411            connection.clone(),
6412            StructuredLaunch {
6413                cwd: PathBuf::from("/fixture"),
6414                program: "fixture".into(),
6415                arguments: Vec::new(),
6416                env: BTreeMap::new(),
6417            },
6418        );
6419        let first = service
6420            .handle_async(request(
6421                1,
6422                "harness.v1.runtimes.close",
6423                json!({"connection": connection}),
6424            ))
6425            .await;
6426        // The harness's own teardown failed and the caller is told so...
6427        assert!(first.get("error").is_some(), "{first}");
6428        // ...but the connection is gone all the same. A connection whose close
6429        // cannot complete is exactly the one that must not stay registered:
6430        // holding it would answer every later call on this node with a turn
6431        // that is never going to end.
6432        assert!(!service.runtimes.contains_key(&connection));
6433        assert!(!service.terminal_launches.contains_key(&connection));
6434        assert!(!service.runtime_sequences.contains_key(&runtime_id));
6435        let again = service
6436            .handle_async(request(
6437                2,
6438                "harness.v1.runtimes.close",
6439                json!({"connection": connection}),
6440            ))
6441            .await;
6442        assert_eq!(again["error"]["code"], -32602, "{again}");
6443    }
6444
6445    fn request(id: u64, method: &str, params: Value) -> Value {
6446        json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
6447    }
6448
6449    // ---- ORCH-6: conversation nouns on `sessions.*` ----------------------
6450
6451    fn hermes_store() -> PathBuf {
6452        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hermes_home/state.db")
6453    }
6454
6455    /// The discovery response for the Hermes fixture home, with the one
6456    /// machine-specific value (the absolute store path) replaced so the exact
6457    /// same JSON can be committed and replayed by the UI story.
6458    fn hermes_discovery(params: Value) -> Value {
6459        let mut response =
6460            HarnessSessionService::new().handle(request(1, "harness.v1.sessions.discover", params));
6461        let store = hermes_store().display().to_string();
6462        for session in response["result"]["sessions"]
6463            .as_array_mut()
6464            .expect("sessions array")
6465        {
6466            if session["locator"]["storage"]["path"] == json!(store) {
6467                session["locator"]["storage"]["path"] = json!("<fixtures>/hermes_home/state.db");
6468            }
6469            // `activity` reports a wall-clock observation instant, not a fact
6470            // about the session; it would make this response differ on every
6471            // call. The nouns under test are all session facts.
6472            session.as_object_mut().unwrap().remove("activity");
6473        }
6474        response["result"].take()
6475    }
6476
6477    fn hermes_query() -> Value {
6478        json!({
6479            "harnesses": ["hermes"],
6480            "homes": {"hermes": hermes_store()},
6481        })
6482    }
6483
6484    fn row<'a>(result: &'a Value, id: &str) -> &'a Value {
6485        result["sessions"]
6486            .as_array()
6487            .expect("sessions array")
6488            .iter()
6489            .find(|session| session["locator"]["session_id"] == json!(id))
6490            .unwrap_or_else(|| panic!("no discovered row for `{id}` in {result:#}"))
6491    }
6492
6493    #[test]
6494    fn orch6_discover_rows_carry_the_conversation_nouns() {
6495        let result = hermes_discovery(hermes_query());
6496
6497        // A Telegram DM: reached on a channel, no repo — the workspace IS the
6498        // channel (D2 precedence), and `main` is not a profile.
6499        let dm = row(&result, "tg-dm-1");
6500        assert_eq!(dm["trigger"], json!("channel"));
6501        assert_eq!(dm["surface"]["platform"], json!("telegram"));
6502        assert_eq!(dm["surface"]["kind"], json!("dm"));
6503        assert_eq!(dm["surface"]["chat_id"], json!("123456"));
6504        assert_eq!(dm["surface"]["participant_id"], json!("u1"));
6505        assert_eq!(
6506            dm["workspace"],
6507            json!({"kind": "channel", "value": "telegram:123456"})
6508        );
6509        assert!(dm.get("profile").is_none(), "{dm:#}");
6510
6511        // A cron fire: recurring, with the job recovered from the minted id.
6512        let fire = row(&result, "cron_job42_20260902_120000");
6513        assert_eq!(fire["trigger"], json!("cron"));
6514        assert_eq!(
6515            fire["recurrence"],
6516            json!({"job_id": "job42", "kind": "cron"})
6517        );
6518        assert_eq!(fire["workspace"]["kind"], json!("repo"));
6519
6520        // A profiled group session with a pending handoff: repo workspace
6521        // wins over the channel, and the chat stays on the surface key.
6522        let coder = row(&result, "tg-coder-1");
6523        assert_eq!(coder["trigger"], json!("channel"));
6524        assert_eq!(coder["profile"], json!("coder"));
6525        assert_eq!(coder["surface"]["thread_id"], json!("55"));
6526        assert_eq!(
6527            coder["surface"]["key"],
6528            json!("agent:coder:telegram:group:-100777:55")
6529        );
6530        assert_eq!(
6531            coder["workspace"],
6532            json!({"kind": "repo", "value": "/workspace/project"})
6533        );
6534        assert_eq!(
6535            coder["cross_surface"],
6536            json!({"state": "pending", "platform": "discord"})
6537        );
6538
6539        // A plain ACP session stays human-triggered with no surface at all.
6540        let acp = row(&result, "cef97234-e8e8-428a-99ab-e8fff4e7e613");
6541        assert_eq!(acp["trigger"], json!("human"));
6542        assert!(acp.get("surface").is_none(), "{acp:#}");
6543        assert_eq!(acp["workspace"], json!({"kind": "none"}));
6544    }
6545
6546    #[test]
6547    fn orch6_discover_filters_by_harness_and_profile() {
6548        let mut params = hermes_query();
6549        params["profile"] = json!("coder");
6550        let result = hermes_discovery(params);
6551        let ids: Vec<&str> = result["sessions"]
6552            .as_array()
6553            .expect("sessions array")
6554            .iter()
6555            .map(|session| session["locator"]["session_id"].as_str().unwrap())
6556            .collect();
6557        assert_eq!(ids, vec!["tg-coder-1"]);
6558
6559        // A profile no session is routed through returns nothing rather than
6560        // silently ignoring the filter.
6561        let mut missing = hermes_query();
6562        missing["profile"] = json!("nobody");
6563        assert_eq!(hermes_discovery(missing)["sessions"], json!([]));
6564
6565        // The harness filter is `harnesses`; an id no harness answers to is
6566        // an empty page, never every store on the box.
6567        let elsewhere = json!({"harnesses": ["codex"], "homes": {"codex": hermes_store()}});
6568        assert_eq!(hermes_discovery(elsewhere)["sessions"], json!([]));
6569    }
6570
6571    #[test]
6572    fn orch6_load_reports_the_same_nouns_as_discovery() {
6573        let mut service = HarnessSessionService::new();
6574        let loaded = service.handle(request(
6575            1,
6576            "harness.v1.sessions.load",
6577            json!({"locator": {
6578                "harness": "hermes",
6579                "session_id": "tg-coder-1",
6580                "storage": {"kind": "file", "path": hermes_store()},
6581            }}),
6582        ));
6583        let session = &loaded["result"]["session"];
6584        let discovered = hermes_discovery(hermes_query());
6585        let row = row(&discovered, "tg-coder-1");
6586        for noun in [
6587            "trigger",
6588            "surface",
6589            "profile",
6590            "recurrence",
6591            "cross_surface",
6592            "workspace",
6593        ] {
6594            assert_eq!(
6595                session[noun],
6596                row.get(noun).cloned().unwrap_or(Value::Null),
6597                "`{noun}` disagrees between sessions.load and sessions.discover"
6598            );
6599        }
6600    }
6601
6602    /// ORCH-10: the fixture homes, as the RPC's `homes` override. Hermes's
6603    /// home is named by its `state.db`; OpenClaw's is the state directory.
6604    fn profile_fixture_homes() -> Value {
6605        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6606        json!({
6607            "hermes": fixtures.join("hermes_home/state.db"),
6608            "openclaw": fixtures.join("openclaw_home"),
6609        })
6610    }
6611
6612    fn profile_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
6613        response["result"]["profiles"]
6614            .as_array()
6615            .unwrap_or_else(|| panic!("no profiles array in {response}"))
6616            .iter()
6617            .find(|row| row["harness"] == harness && row["name"] == name)
6618            .unwrap_or_else(|| panic!("no `{harness}` profile `{name}` in {response}"))
6619    }
6620
6621    /// dev/01: every source answers in one row shape, over the committed
6622    /// fixture homes — the Hermes profile directory and its `state.db`
6623    /// partition, the OpenClaw agent directories and `openclaw.json`, and
6624    /// supercode's own presets.
6625    #[test]
6626    fn profiles_list_reads_every_source_uniformly() {
6627        let mut service = HarnessSessionService::new();
6628        let response = service.handle(request(
6629            1,
6630            "harness.v1.profiles.list",
6631            json!({"homes": profile_fixture_homes()}),
6632        ));
6633        assert_eq!(
6634            response["result"]["schema"],
6635            crate::profiles::PROFILES_SCHEMA
6636        );
6637
6638        let default = profile_row(&response, "hermes", "default");
6639        assert_eq!(default["kind"], "hermes_profile");
6640        assert_eq!(default["default"], true);
6641        assert_eq!(default["routes"], 0);
6642        assert_eq!(default["sessions"], 11);
6643        assert_eq!(default["model"], "anthropic/claude-sonnet-4-5");
6644
6645        let coder = profile_row(&response, "hermes", "coder");
6646        assert_eq!(coder["kind"], "hermes_profile");
6647        assert_eq!(coder["default"], false);
6648        assert_eq!(coder["routes"], 1, "gateway.profile_routes targets coder");
6649        assert_eq!(coder["sessions"], 1, "state.db profile_name = 'coder'");
6650        assert_eq!(coder["model"], "anthropic/claude-opus-4-8");
6651        assert!(coder["home"]
6652            .as_str()
6653            .unwrap()
6654            .ends_with("hermes_home/profiles/coder"));
6655
6656        let main = profile_row(&response, "openclaw", "main");
6657        assert_eq!(main["kind"], "openclaw_agent");
6658        // No entry declares `default: true` (real configs do not), so `main`
6659        // wins on OpenClaw's own convention rather than alphabetically.
6660        assert_eq!(main["default"], true);
6661        assert_eq!(main["routes"], 0);
6662        assert_eq!(main["sessions"], 4);
6663        assert_eq!(
6664            main["model"],
6665            Value::Null,
6666            "`agents.defaults.model` is an install default, not this agent's pin"
6667        );
6668
6669        let design = profile_row(&response, "openclaw", "design");
6670        assert_eq!(design["default"], false);
6671        assert_eq!(design["routes"], 1, "one binding names agentId `design`");
6672        assert_eq!(design["sessions"], 0);
6673        assert_eq!(design["model"], "anthropic/claude-opus-4-8");
6674
6675        let preset = profile_row(&response, "supercode", "supercode-default");
6676        assert_eq!(preset["kind"], "preset");
6677        assert_eq!(preset["default"], true);
6678        assert_eq!(preset["home"], Value::Null);
6679        assert_eq!(preset["routes"], Value::Null);
6680    }
6681
6682    /// Codex's own profiles are `[profiles.<name>]` tables, with the
6683    /// top-level `profile` key naming the default.
6684    #[test]
6685    fn profiles_list_reads_codex_profile_tables() {
6686        let codex_home = std::env::temp_dir().join(format!(
6687            "supercode-orch10-codex-{}-{}",
6688            std::process::id(),
6689            std::time::SystemTime::now()
6690                .duration_since(std::time::UNIX_EPOCH)
6691                .unwrap()
6692                .as_nanos()
6693        ));
6694        std::fs::create_dir_all(codex_home.join("sessions")).unwrap();
6695        std::fs::write(
6696            codex_home.join("config.toml"),
6697            "profile = \"review\"\n\n[profiles.review]\nmodel = \"gpt-5.1-codex\"\n\n[profiles.fast]\nmodel = \"gpt-5.1-codex-mini\"\n",
6698        )
6699        .unwrap();
6700
6701        let mut service = HarnessSessionService::new();
6702        let response = service.handle(request(
6703            1,
6704            "harness.v1.profiles.list",
6705            json!({"harness": "codex", "homes": {"codex": codex_home.join("sessions")}}),
6706        ));
6707        let rows = response["result"]["profiles"].as_array().unwrap();
6708        assert_eq!(rows.len(), 2, "{response}");
6709        let review = profile_row(&response, "codex", "review");
6710        assert_eq!(review["kind"], "codex_profile");
6711        assert_eq!(review["default"], true);
6712        assert_eq!(review["model"], "gpt-5.1-codex");
6713        assert_eq!(review["home"], Value::Null);
6714        assert_eq!(profile_row(&response, "codex", "fast")["default"], false);
6715
6716        let got = service.handle(request(
6717            2,
6718            "harness.v1.profiles.get",
6719            json!({
6720                "harness": "codex",
6721                "name": "fast",
6722                "homes": {"codex": codex_home.join("sessions")},
6723            }),
6724        ));
6725        assert_eq!(got["result"]["profile"]["model"], "gpt-5.1-codex-mini");
6726        std::fs::remove_dir_all(&codex_home).ok();
6727    }
6728
6729    /// A verb a harness lacks fails with `UnsupportedAction`, never a silent
6730    /// empty list; an unknown name is an invalid argument, not an empty row.
6731    #[test]
6732    fn profiles_refuse_harnesses_without_the_concept() {
6733        let mut service = HarnessSessionService::new();
6734        let response = service.handle(request(
6735            1,
6736            "harness.v1.profiles.list",
6737            json!({"harness": "claude-code"}),
6738        ));
6739        assert_eq!(response["error"]["code"], -32020, "{response}");
6740
6741        let missing = service.handle(request(
6742            2,
6743            "harness.v1.profiles.get",
6744            json!({
6745                "harness": "hermes",
6746                "name": "no-such-profile",
6747                "homes": profile_fixture_homes(),
6748            }),
6749        ));
6750        assert_eq!(missing["error"]["code"], -32602, "{missing}");
6751    }
6752
6753    /// The two methods are advertised, so a client discovers them from
6754    /// `harness.v1.capabilities` rather than from documentation.
6755    #[test]
6756    fn profiles_methods_are_advertised() {
6757        let mut service = HarnessSessionService::new();
6758        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
6759        let methods = response["result"]["methods"].as_array().unwrap();
6760        for method in ["harness.v1.profiles.list", "harness.v1.profiles.get"] {
6761            assert!(
6762                methods.iter().any(|entry| entry == method),
6763                "{method} is not advertised"
6764            );
6765        }
6766    }
6767
6768    // -----------------------------------------------------------------
6769    // ORCH-14 — channels
6770    // -----------------------------------------------------------------
6771
6772    fn channel_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
6773        response["result"]["channels"]
6774            .as_array()
6775            .unwrap_or_else(|| panic!("no channels array in {response}"))
6776            .iter()
6777            .find(|row| row["harness"] == harness && row["name"] == name)
6778            .unwrap_or_else(|| panic!("no `{harness}` channel `{name}` in {response}"))
6779    }
6780
6781    fn channels_list(harness: Option<&str>) -> Value {
6782        let mut params = json!({"homes": profile_fixture_homes()});
6783        if let Some(harness) = harness {
6784            params["harness"] = json!(harness);
6785        }
6786        HarnessSessionService::new().handle(request(1, "harness.v1.channels.list", params))
6787    }
6788
6789    /// dev/01: both sources answer in one row shape over the committed
6790    /// fixture homes — Hermes's `platforms:` blocks with their `extra` maps,
6791    /// and OpenClaw's `channels.<name>` entries split per account.
6792    #[test]
6793    fn channels_list_reads_both_gateway_harnesses_uniformly() {
6794        let response = channels_list(None);
6795        assert_eq!(
6796            response["result"]["schema"],
6797            crate::channels::CHANNELS_SCHEMA
6798        );
6799
6800        // Hermes: a credentialed platform, a bridged `extra.key` platform,
6801        // and one the config explicitly disables.
6802        let telegram = channel_row(&response, "hermes", "telegram");
6803        assert_eq!(telegram["kind"], "telegram");
6804        assert_eq!(telegram["enabled"], true);
6805        assert_eq!(telegram["configured"], true);
6806        // The `sessions` count is the discovery rows whose surface platform
6807        // is telegram: the fixture's `agent:main:telegram:…` DM and the
6808        // `agent:coder:telegram:…` group.
6809        assert_eq!(telegram["sessions"], 2);
6810        let api = channel_row(&response, "hermes", "api_server");
6811        assert_eq!(api["configured"], true, "extra.key is a credential key");
6812        assert_eq!(api["sessions"], 0);
6813        let webhook = channel_row(&response, "hermes", "webhook");
6814        assert_eq!(webhook["enabled"], false);
6815        // Hermes lists no credential for `webhook`: declaring it is all it
6816        // needs, so a credential-less entry is still `configured`.
6817        assert_eq!(webhook["configured"], true);
6818
6819        // OpenClaw: one row per account, named `<channel>/<accountId>`.
6820        let linked = channel_row(&response, "openclaw", "slack/T0FIXTURE");
6821        assert_eq!(linked["kind"], "slack");
6822        assert_eq!(linked["account"], "T0FIXTURE");
6823        assert_eq!(linked["enabled"], true);
6824        assert_eq!(linked["configured"], true);
6825        let unlinked = channel_row(&response, "openclaw", "slack/T1FIXTURE");
6826        assert_eq!(unlinked["enabled"], false);
6827        assert_eq!(
6828            unlinked["configured"], false,
6829            "an account with no credential key is not configured"
6830        );
6831        // A single-account channel keeps its own name and names its account
6832        // inline.
6833        let telegram = channel_row(&response, "openclaw", "telegram");
6834        assert_eq!(telegram["account"], "hermes-fixture-bot");
6835        assert_eq!(telegram["configured"], true);
6836
6837        // `status` is never claimed from a config file.
6838        for row in response["result"]["channels"].as_array().unwrap() {
6839            assert_eq!(row["status"], "unknown", "{row}");
6840        }
6841    }
6842
6843    /// dev/01: no field of any emitted row carries a credential. The fixture
6844    /// homes hold four FAKE credential strings; a row that leaked one — as a
6845    /// value, an account label, or a name — fails here.
6846    #[test]
6847    fn channels_rows_never_carry_a_fixture_secret() {
6848        let secrets = [
6849            "FAKE-TOKEN-DO-NOT-EMIT",
6850            "FAKE-API-SERVER-KEY-DO-NOT-EMIT",
6851            "FAKE-SLACK-BOT-TOKEN-DO-NOT-EMIT",
6852            "FAKE-SLACK-APP-TOKEN-DO-NOT-EMIT",
6853            "FAKE-TELEGRAM-TOKEN-DO-NOT-EMIT",
6854        ];
6855        // The strings really are in the fixtures, so this test can fail.
6856        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6857        let raw = format!(
6858            "{}{}",
6859            std::fs::read_to_string(fixtures.join("hermes_home/config.yaml")).unwrap(),
6860            std::fs::read_to_string(fixtures.join("openclaw_home/openclaw.json")).unwrap(),
6861        );
6862        for secret in secrets {
6863            assert!(raw.contains(secret), "fixture no longer holds `{secret}`");
6864        }
6865
6866        let emitted = serde_json::to_string(&channels_list(None)["result"]).unwrap();
6867        for secret in secrets {
6868            assert!(
6869                !emitted.contains(secret),
6870                "`{secret}` leaked into a channel row: {emitted}"
6871            );
6872        }
6873        // Belt and braces: no row FIELD is credential-shaped either, so a
6874        // future field cannot smuggle one past the literal scan.
6875        for row in channels_list(None)["result"]["channels"]
6876            .as_array()
6877            .unwrap()
6878        {
6879            for key in row.as_object().unwrap().keys() {
6880                let key = key.to_ascii_lowercase();
6881                assert!(
6882                    !["token", "key", "secret", "password", "credential"]
6883                        .iter()
6884                        .any(|marker| key.ends_with(marker)),
6885                    "`{key}` is a credential-shaped field on a channel row"
6886                );
6887            }
6888        }
6889    }
6890
6891    /// `status` answers one row by name, and refuses an unknown one.
6892    #[test]
6893    fn channels_status_reads_one_row_by_name() {
6894        let mut service = HarnessSessionService::new();
6895        let got = service.handle(request(
6896            1,
6897            "harness.v1.channels.status",
6898            json!({
6899                "harness": "openclaw",
6900                "name": "slack/T0FIXTURE",
6901                "homes": profile_fixture_homes(),
6902            }),
6903        ));
6904        assert_eq!(got["result"]["channel"]["kind"], "slack");
6905        assert_eq!(got["result"]["channel"]["account"], "T0FIXTURE");
6906        assert_eq!(got["result"]["channel"]["status"], "unknown");
6907
6908        let missing = service.handle(request(
6909            2,
6910            "harness.v1.channels.status",
6911            json!({
6912                "harness": "openclaw",
6913                "name": "no-such-channel",
6914                "homes": profile_fixture_homes(),
6915            }),
6916        ));
6917        assert_eq!(missing["error"]["code"], -32602, "{missing}");
6918    }
6919
6920    /// A harness with no channel concept fails with `UnsupportedAction`,
6921    /// never a silent empty list — Claude Code included, because its channels
6922    /// are MCP-protocol declarations no config file names.
6923    #[test]
6924    fn channels_refuse_harnesses_without_the_concept() {
6925        let response = channels_list(Some("claude-code"));
6926        assert_eq!(response["error"]["code"], -32020, "{response}");
6927        let codex = channels_list(Some("codex"));
6928        assert_eq!(codex["error"]["code"], -32020, "{codex}");
6929    }
6930
6931    /// The harness filter restricts the rows rather than being ignored.
6932    #[test]
6933    fn channels_list_filters_by_harness() {
6934        let response = channels_list(Some("openclaw"));
6935        let rows = response["result"]["channels"].as_array().unwrap();
6936        assert!(!rows.is_empty(), "{response}");
6937        assert!(
6938            rows.iter().all(|row| row["harness"] == "openclaw"),
6939            "harness filter leaked: {response}"
6940        );
6941    }
6942
6943    /// Both methods are advertised, so a client discovers them from
6944    /// `harness.v1.capabilities` rather than from documentation.
6945    #[test]
6946    fn channels_methods_are_advertised() {
6947        let mut service = HarnessSessionService::new();
6948        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
6949        let methods = response["result"]["methods"].as_array().unwrap();
6950        for method in ["harness.v1.channels.list", "harness.v1.channels.status"] {
6951            assert!(
6952                methods.iter().any(|entry| entry == method),
6953                "{method} is not advertised"
6954            );
6955        }
6956    }
6957
6958    /// The UI story renders REAL rows: this writes the discovery response the
6959    /// two assertions above pin into the fixture the Storybook
6960    /// `Compositions/Universal nouns` stories import, and fails when the
6961    /// committed copy has drifted from what the service now answers.
6962    #[test]
6963    fn orch6_story_fixture_matches_the_live_discovery_response() {
6964        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6965            .join("../../sdk/ui/stories/fixtures/hermes-discovery.json");
6966        let mut result = hermes_discovery(hermes_query());
6967        // `updated_at_ms` is derived from the fixture's own stored timestamps,
6968        // so the whole response is deterministic; drop only the cursor, which
6969        // is pagination state rather than a session fact.
6970        result.as_object_mut().unwrap().remove("next_cursor");
6971        let rendered = format!("{}\n", serde_json::to_string_pretty(&result).unwrap());
6972        if std::env::var_os("SUPERCODE_UPDATE_FIXTURES").is_some() {
6973            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
6974            std::fs::write(&path, &rendered).unwrap();
6975        }
6976        let committed = std::fs::read_to_string(&path).unwrap_or_default();
6977        assert_eq!(
6978            committed, rendered,
6979            "sdk/ui/stories/fixtures/hermes-discovery.json is stale — \
6980             re-run with SUPERCODE_UPDATE_FIXTURES=1"
6981        );
6982    }
6983
6984    fn pi_locator() -> SessionLocator {
6985        SessionLocator {
6986            harness: HarnessId::from(HarnessId::PI),
6987            session_id: "1e6f2a3b-0000-4000-8000-000000000001".into(),
6988            storage: StorageLocator::File {
6989                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6990                    .join("tests/fixtures/pi_session.jsonl"),
6991            },
6992        }
6993    }
6994
6995    fn opencode_locator() -> SessionLocator {
6996        let session_id = "ses_fixtureAAAAAAAAAAAAAAA1";
6997        SessionLocator {
6998            harness: HarnessId::from(HarnessId::OPENCODE),
6999            session_id: session_id.into(),
7000            storage: StorageLocator::Sqlite {
7001                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7002                    .join("tests/fixtures/opencode_fixture/opencode.db"),
7003                selector: session_id.into(),
7004            },
7005        }
7006    }
7007
7008    fn grok_locator() -> SessionLocator {
7009        SessionLocator {
7010            harness: HarnessId::from(HarnessId::GROK),
7011            session_id: "73c09283-4b33-41fa-90f1-0bcb0f7be523".into(),
7012            storage: StorageLocator::File {
7013                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7014                    .join("tests/fixtures/grok_session/chat_history.jsonl"),
7015            },
7016        }
7017    }
7018
7019    // ---- ORCH-11: `harness.v1.skills.list` -------------------------------
7020
7021    fn fixture_homes() -> Value {
7022        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7023        json!({
7024            "claude_code": fixtures.join("__absent__"),
7025            "codex": fixtures.join("__absent__"),
7026            "opencode": fixtures.join("__absent__"),
7027            "pi": fixtures.join("__absent__"),
7028            "agents": fixtures.join("__absent__"),
7029            "hermes": fixtures.join("hermes_home"),
7030            "openclaw": fixtures.join("openclaw_home"),
7031        })
7032    }
7033
7034    #[test]
7035    fn preview_search_uses_the_discovery_rpc_and_refuses_live_subscription() {
7036        let root = std::env::temp_dir().join(format!(
7037            "supercode-preview-rpc-{}-{}",
7038            std::process::id(),
7039            std::time::SystemTime::now()
7040                .duration_since(std::time::UNIX_EPOCH)
7041                .unwrap()
7042                .as_nanos()
7043        ));
7044        std::fs::create_dir_all(&root).unwrap();
7045        for id in ["first", "second"] {
7046            std::fs::write(root.join(format!("{id}.jsonl")), format!("{}\n{}\n",
7047                json!({"type": "session_meta", "payload": {"id": id, "cwd": "/workspace"}}),
7048                json!({"type": "event_msg", "payload": {"type": "agent_message", "message": "NEBULA result"}}),
7049            )).unwrap();
7050        }
7051        let mut service = HarnessSessionService::new();
7052        let query = json!({
7053            "harnesses": ["codex"], "homes": {"codex": root},
7054            "query": "nebula", "search_previews": true, "limit": 1
7055        });
7056        let first = service.handle(request(1, "harness.v1.sessions.discover", query.clone()));
7057        assert!(first.get("error").is_none(), "{first}");
7058        assert_eq!(first["result"]["receipt"]["searched_previews"], true);
7059        assert_eq!(first["result"]["receipt"]["total_matched"], 2);
7060        let mut next_query = query.clone();
7061        next_query["cursor"] = first["result"]["next_cursor"].clone();
7062        let next = service.handle(request(2, "harness.v1.sessions.discover", next_query));
7063        assert_eq!(next["result"]["receipt"]["returned"], 1);
7064        assert_eq!(next["result"]["receipt"]["total_matched"], 2);
7065        assert_eq!(next["result"]["receipt"]["truncated"], false);
7066        assert_ne!(
7067            first["result"]["sessions"][0]["locator"],
7068            next["result"]["sessions"][0]["locator"]
7069        );
7070        let refused = service.handle(request(3, "harness.v1.sessions.index.subscribe", query));
7071        assert!(
7072            refused["error"]["message"]
7073                .as_str()
7074                .unwrap()
7075                .contains("use sessions.discover"),
7076            "{refused}"
7077        );
7078        std::fs::remove_dir_all(root).unwrap();
7079    }
7080
7081    #[test]
7082    fn session_index_resize_preserves_subscription_and_rejects_invalid_requests() {
7083        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.sessions.index.resize"));
7084        let root = std::env::temp_dir().join(format!(
7085            "supercode-index-rpc-{}-{}",
7086            std::process::id(),
7087            std::time::SystemTime::now()
7088                .duration_since(std::time::UNIX_EPOCH)
7089                .unwrap()
7090                .as_nanos()
7091        ));
7092        std::fs::create_dir_all(&root).unwrap();
7093        for id in ["first", "second"] {
7094            std::fs::write(root.join(format!("{id}.jsonl")), format!(
7095                "{{\"type\":\"session_meta\",\"payload\":{{\"id\":\"{id}\",\"cwd\":\"/workspace\"}}}}\n"
7096            )).unwrap();
7097        }
7098        let mut service = HarnessSessionService::new();
7099        let opened = service.handle(request(
7100            1,
7101            "harness.v1.sessions.index.subscribe",
7102            json!({
7103                "harnesses": ["codex"], "homes": { "codex": root }, "limit": 1
7104            }),
7105        ));
7106        assert!(opened.get("error").is_none(), "{opened:#}");
7107        let subscription = opened["result"]["subscription"]
7108            .as_str()
7109            .unwrap()
7110            .to_owned();
7111        assert_eq!(opened["result"]["initial"].as_array().unwrap().len(), 1);
7112        for params in [
7113            json!({"subscription": subscription, "limit": 0}),
7114            json!({"subscription": subscription, "limit": 2049}),
7115            json!({"subscription": subscription, "limit": 2, "cursor": "not-allowed"}),
7116            json!({"subscription": "unknown", "limit": 2}),
7117        ] {
7118            let rejected = service.handle(request(2, "harness.v1.sessions.index.resize", params));
7119            assert_eq!(rejected["error"]["code"], -32602, "{rejected:#}");
7120        }
7121        for (limit, revision) in [(1, 1), (2, 2), (2, 2), (1, 3)] {
7122            let response = service.handle(request(
7123                3,
7124                "harness.v1.sessions.index.resize",
7125                json!({
7126                    "subscription": subscription, "limit": limit
7127                }),
7128            ));
7129            assert!(response.get("error").is_none(), "{response:#}");
7130            assert_eq!(response["result"]["subscription"], subscription);
7131            assert_eq!(response["result"]["revision"], revision);
7132            assert_eq!(
7133                response["result"]["initial"].as_array().unwrap().len(),
7134                limit
7135            );
7136            assert_eq!(response["result"]["receipt"]["total_matched"], 2);
7137            assert_eq!(service.index_subscriptions.len(), 1);
7138        }
7139        let removed = service.handle(request(
7140            4,
7141            "harness.v1.sessions.index.unsubscribe",
7142            json!({
7143                "subscription": subscription
7144            }),
7145        ));
7146        assert_eq!(removed["result"]["removed"], true);
7147        let stale = service.handle(request(
7148            5,
7149            "harness.v1.sessions.index.resize",
7150            json!({
7151                "subscription": subscription, "limit": 1
7152            }),
7153        ));
7154        assert_eq!(stale["error"]["code"], -32602);
7155        drop(service);
7156        std::fs::remove_dir_all(root).unwrap();
7157    }
7158
7159    fn skills_rows(params: Value) -> Vec<Value> {
7160        let response =
7161            HarnessSessionService::new().handle(request(1, "harness.v1.skills.list", params));
7162        assert!(response.get("error").is_none(), "{response:#}");
7163        response["result"].as_array().cloned().unwrap_or_default()
7164    }
7165
7166    /// The uniform row over two harnesses at once, from the harnesses' own
7167    /// skill roots: name, harness, scope, location, description, version.
7168    #[test]
7169    fn skills_list_reads_the_hermes_and_openclaw_roots() {
7170        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7171        let rows = skills_rows(json!({
7172            "homes": fixture_homes(),
7173            "cwd": fixtures.join("hermes_home"),
7174        }));
7175        let arxiv = rows
7176            .iter()
7177            .find(|row| row["name"] == json!("arxiv-search"))
7178            .unwrap_or_else(|| panic!("no arxiv row in {rows:#?}"));
7179        assert_eq!(arxiv["harness"], json!(HarnessId::HERMES));
7180        assert_eq!(arxiv["scope"], json!("user"));
7181        assert_eq!(arxiv["version"], json!("1.4.0"));
7182        assert!(arxiv["location"]
7183            .as_str()
7184            .unwrap()
7185            .ends_with("hermes_home/skills/research/arxiv"));
7186
7187        // A directory with no SKILL.md still lists, by directory name.
7188        let bare = rows
7189            .iter()
7190            .find(|row| row["name"] == json!("bare-skill"))
7191            .unwrap_or_else(|| panic!("no bare-skill row in {rows:#?}"));
7192        assert_eq!(bare["enabled"], json!(null));
7193        assert!(bare.get("description").is_none());
7194
7195        let demo = rows
7196            .iter()
7197            .find(|row| row["name"] == json!("clawhub-demo"))
7198            .unwrap_or_else(|| panic!("no clawhub-demo row in {rows:#?}"));
7199        assert_eq!(demo["harness"], json!(HarnessId::OPENCLAW));
7200        assert_eq!(demo["scope"], json!("managed"));
7201        assert_eq!(demo["enabled"], json!(false));
7202    }
7203
7204    /// Both filters select against the same rows.
7205    #[test]
7206    fn skills_list_filters_by_harness_and_scope() {
7207        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7208        let hermes = skills_rows(json!({
7209            "homes": fixture_homes(),
7210            "cwd": fixtures.join("hermes_home"),
7211            "harness": HarnessId::HERMES,
7212        }));
7213        assert!(!hermes.is_empty());
7214        assert!(hermes
7215            .iter()
7216            .all(|row| row["harness"] == json!(HarnessId::HERMES)));
7217
7218        let managed = skills_rows(json!({
7219            "homes": fixture_homes(),
7220            "cwd": fixtures.join("openclaw_home"),
7221            "harness": HarnessId::OPENCLAW,
7222            "scope": "managed",
7223        }));
7224        assert_eq!(managed.len(), 1, "{managed:#?}");
7225        assert_eq!(managed[0]["name"], json!("clawhub-demo"));
7226
7227        let bundled = skills_rows(json!({
7228            "homes": fixture_homes(),
7229            "cwd": fixtures.join("openclaw_home"),
7230            "harness": HarnessId::OPENCLAW,
7231            "scope": "bundled",
7232        }));
7233        assert!(bundled.is_empty(), "{bundled:#?}");
7234    }
7235
7236    /// A harness supercode has no skills root for is refused by name, not
7237    /// answered with an empty list.
7238    #[test]
7239    fn skills_list_refuses_an_unknown_harness() {
7240        let response = HarnessSessionService::new().handle(request(
7241            1,
7242            "harness.v1.skills.list",
7243            json!({"harness": "not-a-harness", "homes": fixture_homes()}),
7244        ));
7245        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7246        assert!(response["error"]["message"]
7247            .as_str()
7248            .unwrap()
7249            .contains("not-a-harness"));
7250    }
7251
7252    /// The method is advertised, and its SDK operation resolves it.
7253    #[test]
7254    fn skills_list_is_an_advertised_method_and_sdk_operation() {
7255        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.list"));
7256        assert_eq!(
7257            SdkOperation::from_method("harness.v1.skills.list"),
7258            Some(SdkOperation::SkillsList)
7259        );
7260    }
7261
7262    // ---- ORCH-22: `harness.v1.skills.install|remove` ----------------------
7263
7264    /// Both controlled verbs are advertised and resolve to their operation.
7265    #[test]
7266    fn skills_install_and_remove_are_advertised_methods_and_sdk_operations() {
7267        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.install"));
7268        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.remove"));
7269        assert_eq!(
7270            SdkOperation::from_method("harness.v1.skills.install"),
7271            Some(SdkOperation::SkillsInstall)
7272        );
7273        assert_eq!(
7274            SdkOperation::from_method("harness.v1.skills.remove"),
7275            Some(SdkOperation::SkillsRemove)
7276        );
7277    }
7278
7279    /// The directory door, end to end over the RPC: a local package lands in
7280    /// Claude Code's own user root and the outcome carries the operation and
7281    /// the row the ORCH-11 loader reads back.
7282    #[test]
7283    fn skills_install_and_remove_drive_the_directory_door() {
7284        let root = std::env::temp_dir().join(format!(
7285            "supercode-orch22-rpc-{}-{}",
7286            std::process::id(),
7287            std::time::SystemTime::now()
7288                .duration_since(std::time::UNIX_EPOCH)
7289                .unwrap()
7290                .as_nanos()
7291        ));
7292        let source = root.join("probe-src");
7293        std::fs::create_dir_all(&source).unwrap();
7294        std::fs::write(
7295            source.join("SKILL.md"),
7296            "---\nname: orch22-rpc\ndescription: a probe\n---\nbody\n",
7297        )
7298        .unwrap();
7299        let homes = json!({
7300            "claude_code": root.join("claude_home"),
7301            "codex": root.join("__absent__"),
7302            "opencode": root.join("__absent__"),
7303            "pi": root.join("__absent__"),
7304            "hermes": root.join("__absent__"),
7305            "openclaw": root.join("__absent__"),
7306            "agents": root.join("__absent__"),
7307        });
7308
7309        let mut service = HarnessSessionService::new();
7310        let installed = service.handle(request(
7311            1,
7312            "harness.v1.skills.install",
7313            json!({
7314                "harness": HarnessId::CLAUDE_CODE,
7315                "source": source,
7316                "scope": "user",
7317                "cwd": root,
7318                "homes": homes,
7319            }),
7320        ));
7321        let result = &installed["result"];
7322        assert_eq!(result["name"], json!("orch22-rpc"), "{installed:#}");
7323        assert_eq!(result["verb"], json!("install"));
7324        assert!(result["ran"]
7325            .as_str()
7326            .is_some_and(|ran| ran.starts_with("cp -R ")));
7327        assert_eq!(result["skill"]["scope"], json!("user"));
7328
7329        let removed = service.handle(request(
7330            2,
7331            "harness.v1.skills.remove",
7332            json!({
7333                "harness": HarnessId::CLAUDE_CODE,
7334                "name": "orch22-rpc",
7335                "scope": "user",
7336                "cwd": root,
7337                "homes": homes,
7338            }),
7339        ));
7340        assert_eq!(removed["result"]["removed"], json!(true), "{removed:#}");
7341        assert!(!root.join("claude_home/skills/orch22-rpc").exists());
7342        std::fs::remove_dir_all(&root).ok();
7343    }
7344
7345    /// OpenClaw publishes no `skills remove` at the pin, so the uniform verb
7346    /// refuses with UnsupportedAction instead of deleting files itself.
7347    #[test]
7348    fn skills_remove_refuses_openclaw_at_the_pin() {
7349        let response = HarnessSessionService::new().handle(request(
7350            1,
7351            "harness.v1.skills.remove",
7352            json!({"harness": HarnessId::OPENCLAW, "name": "clawhub-demo"}),
7353        ));
7354        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7355        assert!(response["error"]["message"]
7356            .as_str()
7357            .unwrap()
7358            .contains("no `skills remove` verb"));
7359    }
7360
7361    /// A harness with no skills root at all is refused by name, with the
7362    /// same sentence `skills.list` gives it.
7363    #[test]
7364    fn skills_install_refuses_a_harness_without_a_skills_root() {
7365        let response = HarnessSessionService::new().handle(request(
7366            1,
7367            "harness.v1.skills.install",
7368            json!({"harness": "not-a-harness", "source": "/tmp/x"}),
7369        ));
7370        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7371        assert!(response["error"]["message"]
7372            .as_str()
7373            .unwrap()
7374            .contains("not-a-harness"));
7375    }
7376
7377    // ---- ORCH-12: `harness.v1.memory.show|search` ------------------------
7378
7379    /// `HarnessHomes` for the committed fixture homes. Every root a test does
7380    /// not name is pinned at an absent path, so a read can never fall through
7381    /// to this machine's real harness homes. Note `hermes` is the `state.db`
7382    /// PATH (its parent is HERMES_HOME) and `claude_code` is the `projects`
7383    /// directory — the same contract discovery uses.
7384    fn memory_homes() -> Value {
7385        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7386        json!({
7387            "claude_code": fixtures.join("__absent__"),
7388            "codex": fixtures.join("__absent__"),
7389            "opencode": fixtures.join("__absent__"),
7390            "pi": fixtures.join("__absent__"),
7391            "grok": fixtures.join("__absent__"),
7392            "gemini": fixtures.join("__absent__"),
7393            "goose": fixtures.join("__absent__"),
7394            "supercode": fixtures.join("__absent__"),
7395            "hermes": fixtures.join("hermes_home/state.db"),
7396            "openclaw": fixtures.join("openclaw_home"),
7397        })
7398    }
7399
7400    fn memory_call_ok(method: &str, params: Value, key: &str) -> Vec<Value> {
7401        let response = HarnessSessionService::new().handle(request(1, method, params));
7402        assert!(response.get("error").is_none(), "{response:#}");
7403        assert_eq!(response["result"]["schema"], json!("supercode.memory.v1"));
7404        response["result"][key]
7405            .as_array()
7406            .cloned()
7407            .unwrap_or_default()
7408    }
7409
7410    fn memory_documents(params: Value) -> Vec<Value> {
7411        memory_call_ok("harness.v1.memory.show", params, "documents")
7412    }
7413
7414    fn memory_matches(params: Value) -> Vec<Value> {
7415        memory_call_ok("harness.v1.memory.search", params, "matches")
7416    }
7417
7418    fn find_document<'a>(rows: &'a [Value], profile: &str, name: &str) -> &'a Value {
7419        rows.iter()
7420            .find(|row| row["profile"] == profile && row["name"] == name)
7421            .unwrap_or_else(|| panic!("no `{profile}` document `{name}` in {rows:#?}"))
7422    }
7423
7424    /// Hermes: the built-in `MEMORY.md`/`USER.md` pair and the `memories/`
7425    /// topic files, for HERMES_HOME itself and for every profile home.
7426    #[test]
7427    fn memory_show_reads_the_hermes_profile_homes() {
7428        let rows = memory_documents(json!({"harness": "hermes", "homes": memory_homes()}));
7429
7430        let notes = find_document(&rows, "default", "MEMORY.md");
7431        assert_eq!(notes["harness"], "hermes");
7432        assert_eq!(notes["scope"], "user");
7433        assert!(notes["size"].as_u64().unwrap() > 0);
7434        assert!(notes["updated_at"].is_string(), "{notes:#?}");
7435        // The default answer previews the head and never the whole body.
7436        assert!(notes.get("content").is_none(), "{notes:#?}");
7437        assert_eq!(notes["truncated"], true);
7438        assert_eq!(notes["preview"].as_array().unwrap().len(), 5);
7439
7440        let user = find_document(&rows, "default", "USER.md");
7441        assert_eq!(user["scope"], "user");
7442        assert!(user["preview"]
7443            .as_array()
7444            .unwrap()
7445            .iter()
7446            .any(|line| line.as_str().unwrap().contains("neovim")));
7447
7448        let topic = find_document(&rows, "default", "memories/2026-09-01-notes.md");
7449        assert!(topic["path"]
7450            .as_str()
7451            .unwrap()
7452            .ends_with("hermes_home/memories/2026-09-01-notes.md"));
7453
7454        // Profile mode points HERMES_HOME at `<root>/profiles/<name>`.
7455        let coder = find_document(&rows, "coder", "MEMORY.md");
7456        assert_eq!(coder["scope"], "profile");
7457        assert!(coder["path"]
7458            .as_str()
7459            .unwrap()
7460            .ends_with("hermes_home/profiles/coder/MEMORY.md"));
7461    }
7462
7463    /// `full` is the only way a body crosses the wire, and `profile` narrows
7464    /// the read to one home.
7465    #[test]
7466    fn memory_show_returns_bodies_only_under_full_and_narrows_by_profile() {
7467        let rows = memory_documents(json!({
7468            "harness": "hermes",
7469            "profile": "coder",
7470            "full": true,
7471            "homes": memory_homes(),
7472        }));
7473        assert!(
7474            rows.iter().all(|row| row["profile"] == "coder"),
7475            "{rows:#?}"
7476        );
7477        let coder = find_document(&rows, "coder", "MEMORY.md");
7478        assert!(coder["content"]
7479            .as_str()
7480            .expect("full returns the body")
7481            .contains("anthropic/claude-opus-4-8"));
7482    }
7483
7484    /// OpenClaw: memory-core's files under each agent's workspace —
7485    /// `<state>/workspace` for the default agent, `<state>/workspace-<id>`
7486    /// for any other.
7487    #[test]
7488    fn memory_show_reads_the_openclaw_agent_workspaces() {
7489        let rows = memory_documents(json!({"harness": "openclaw", "homes": memory_homes()}));
7490
7491        let main = find_document(&rows, "main", "MEMORY.md");
7492        assert_eq!(main["scope"], "agent");
7493        assert!(main["path"]
7494            .as_str()
7495            .unwrap()
7496            .ends_with("openclaw_home/workspace/MEMORY.md"));
7497
7498        let topic = find_document(&rows, "main", "memory/2026-09-01-standup.md");
7499        assert!(topic["path"]
7500            .as_str()
7501            .unwrap()
7502            .ends_with("openclaw_home/workspace/memory/2026-09-01-standup.md"));
7503
7504        let design = find_document(&rows, "design", "MEMORY.md");
7505        assert!(design["path"]
7506            .as_str()
7507            .unwrap()
7508            .ends_with("openclaw_home/workspace-design/MEMORY.md"));
7509    }
7510
7511    /// Claude Code: the auto-memory directory of the project the working tree
7512    /// belongs to, keyed by the enclosing git repository.
7513    #[test]
7514    fn memory_show_reads_a_claude_code_project_auto_memory_directory() {
7515        let scratch = std::env::temp_dir().join(format!(
7516            "supercode-orch12-cc-{}-{}",
7517            std::process::id(),
7518            std::time::SystemTime::now()
7519                .duration_since(std::time::UNIX_EPOCH)
7520                .unwrap()
7521                .as_nanos()
7522        ));
7523        let project = scratch.join("repo");
7524        std::fs::create_dir_all(project.join(".git")).unwrap();
7525        // Auto-memory is shared across a repo's worktrees, so a nested
7526        // working directory must resolve to the repo's own project dir.
7527        let worktree = project.join("crates/harness");
7528        std::fs::create_dir_all(&worktree).unwrap();
7529        let slug: String = project
7530            .to_string_lossy()
7531            .chars()
7532            .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
7533            .collect();
7534        let projects = scratch.join("claude/projects");
7535        let memory = projects.join(&slug).join("memory");
7536        std::fs::create_dir_all(&memory).unwrap();
7537        std::fs::write(
7538            memory.join("MEMORY.md"),
7539            "# index\n- [build box](build-box.md) — the pinned harnesses\n",
7540        )
7541        .unwrap();
7542        std::fs::write(
7543            memory.join("build-box.md"),
7544            "hermes 0.21.0 and openclaw 2026.7.1-2 are the pins\n",
7545        )
7546        .unwrap();
7547
7548        let mut homes = memory_homes();
7549        homes["claude_code"] = json!(projects);
7550        let rows = memory_documents(json!({
7551            "harness": "claude-code",
7552            "cwd": worktree,
7553            "homes": homes,
7554        }));
7555        let index = find_document(&rows, &slug, "MEMORY.md");
7556        assert_eq!(index["harness"], "claude-code");
7557        assert_eq!(index["scope"], "project");
7558        let topic = find_document(&rows, &slug, "build-box.md");
7559        assert!(topic["preview"]
7560            .as_array()
7561            .unwrap()
7562            .iter()
7563            .any(|line| line.as_str().unwrap().contains("2026.7.1-2")));
7564
7565        let hits = memory_matches(json!({
7566            "harness": "claude-code",
7567            "query": "pinned harnesses",
7568            "cwd": worktree,
7569            "homes": homes,
7570        }));
7571        assert_eq!(hits.len(), 1, "{hits:#?}");
7572        assert_eq!(hits[0]["name"], "MEMORY.md");
7573        assert_eq!(hits[0]["line"], 2);
7574
7575        let _ = std::fs::remove_dir_all(&scratch);
7576    }
7577
7578    /// A config-less OpenClaw install declares no default agent, but
7579    /// memory-core still resolves ONE agent to the default `workspace`
7580    /// directory — the same `main`-then-first convention the profile rows
7581    /// use. Measured against `openclaw memory status` on the pinned CLI
7582    /// (`docs/interop/research/orch12-memory-receipt-2026-09-03.json`).
7583    #[test]
7584    fn memory_show_resolves_the_default_workspace_without_an_openclaw_config() {
7585        let state = std::env::temp_dir().join(format!(
7586            "supercode-orch12-oc-{}-{}",
7587            std::process::id(),
7588            std::time::SystemTime::now()
7589                .duration_since(std::time::UNIX_EPOCH)
7590                .unwrap()
7591                .as_nanos()
7592        ));
7593        // No `openclaw.json`: only the agent home the gateway creates.
7594        std::fs::create_dir_all(state.join("agents/main/agent")).unwrap();
7595        std::fs::create_dir_all(state.join("workspace")).unwrap();
7596        std::fs::write(
7597            state.join("workspace/MEMORY.md"),
7598            "the gateway websocket needs credentials\n",
7599        )
7600        .unwrap();
7601
7602        let mut homes = memory_homes();
7603        homes["openclaw"] = json!(state);
7604        let rows = memory_documents(json!({"harness": "openclaw", "homes": homes}));
7605        assert_eq!(rows.len(), 1, "{rows:#?}");
7606        let row = find_document(&rows, "main", "MEMORY.md");
7607        assert_eq!(row["scope"], "agent");
7608        assert!(row["path"]
7609            .as_str()
7610            .unwrap()
7611            .ends_with("workspace/MEMORY.md"));
7612
7613        let _ = std::fs::remove_dir_all(&state);
7614    }
7615
7616    /// Search is a plain scan over the same documents: a hit carries the
7617    /// path, line and excerpt; a miss is an empty list, not an error.
7618    #[test]
7619    fn memory_search_reports_hits_by_line_and_misses_as_empty() {
7620        let hit = memory_matches(json!({
7621            "harness": "hermes",
7622            "query": "NEOVIM",
7623            "homes": memory_homes(),
7624        }));
7625        assert_eq!(hit.len(), 1, "{hit:#?}");
7626        assert_eq!(hit[0]["harness"], "hermes");
7627        assert_eq!(hit[0]["name"], "USER.md");
7628        assert_eq!(hit[0]["scope"], "user");
7629        assert_eq!(hit[0]["line"], 5);
7630        assert!(hit[0]["excerpt"].as_str().unwrap().contains("neovim"));
7631
7632        // A regular expression reaches the same lines.
7633        let regex = memory_matches(json!({
7634            "harness": "hermes",
7635            "query": "neo(vim|vi)",
7636            "regex": true,
7637            "homes": memory_homes(),
7638        }));
7639        assert_eq!(regex.len(), 1, "{regex:#?}");
7640
7641        let miss = memory_matches(json!({
7642            "harness": "hermes",
7643            "query": "no-memory-line-says-this",
7644            "homes": memory_homes(),
7645        }));
7646        assert!(miss.is_empty(), "{miss:#?}");
7647    }
7648
7649    /// The uniform-verb contract: a harness with no memory store at the pin
7650    /// is refused by name, and `session` only selects a Claude Code project.
7651    #[test]
7652    fn memory_refuses_harnesses_without_a_store_and_misplaced_session_scoping() {
7653        for method in ["harness.v1.memory.show", "harness.v1.memory.search"] {
7654            let response = HarnessSessionService::new().handle(request(
7655                1,
7656                method,
7657                json!({"harness": "codex", "query": "anything", "homes": memory_homes()}),
7658            ));
7659            assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7660            assert!(response["error"]["message"]
7661                .as_str()
7662                .unwrap()
7663                .contains("codex"));
7664        }
7665
7666        let response = HarnessSessionService::new().handle(request(
7667            1,
7668            "harness.v1.memory.show",
7669            json!({"harness": "hermes", "session": "abc", "homes": memory_homes()}),
7670        ));
7671        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7672
7673        // `harness` is not optional: memory documents are the user's prose.
7674        let response = HarnessSessionService::new().handle(request(
7675            1,
7676            "harness.v1.memory.show",
7677            json!({"homes": memory_homes()}),
7678        ));
7679        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7680    }
7681
7682    /// Both methods are advertised, and their SDK operations resolve them.
7683    #[test]
7684    fn memory_methods_are_advertised_and_map_to_sdk_operations() {
7685        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.show"));
7686        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.search"));
7687        assert_eq!(
7688            SdkOperation::from_method("harness.v1.memory.show"),
7689            Some(SdkOperation::MemoryShow)
7690        );
7691        assert_eq!(
7692            SdkOperation::from_method("harness.v1.memory.search"),
7693            Some(SdkOperation::MemorySearch)
7694        );
7695    }
7696
7697    // ---- ORCH-9: `harness.v1.approvals.list` -----------------------------
7698
7699    /// A runtime that raises one protocol request and then goes quiet, so a
7700    /// single poll delivers the request without closing the connection.
7701    struct RequestingRuntime {
7702        handle: RuntimeHandle,
7703        events: std::collections::VecDeque<HarnessEvent>,
7704        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7705    }
7706
7707    #[async_trait]
7708    impl RuntimeConnection for RequestingRuntime {
7709        fn handle(&self) -> &RuntimeHandle {
7710            &self.handle
7711        }
7712
7713        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
7714            unreachable!("this runtime only raises requests")
7715        }
7716
7717        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
7718            match self.events.pop_front() {
7719                Some(event) => Ok(Some(event)),
7720                // Quiet, not closed: `poll_sdk_events` times out and leaves
7721                // the connection open, the way a runtime blocked on a
7722                // permission request behaves.
7723                None => std::future::pending().await,
7724            }
7725        }
7726
7727        async fn interrupt(&mut self) -> crate::Result<()> {
7728            Ok(())
7729        }
7730
7731        async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
7732            // Both halves are recorded: ORCH-20 has to prove not just that the
7733            // right request was answered but that the door received its own
7734            // reply envelope.
7735            self.answered
7736                .lock()
7737                .unwrap_or_else(std::sync::PoisonError::into_inner)
7738                .push(json!({"request_id": request_id, "response": response}));
7739            Ok(())
7740        }
7741
7742        async fn close(&mut self) -> crate::Result<()> {
7743            Ok(())
7744        }
7745    }
7746
7747    fn requesting_runtime(
7748        harness: &str,
7749        events: Vec<HarnessEvent>,
7750        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7751    ) -> Box<dyn RuntimeConnection> {
7752        requesting_runtime_named(harness, "hermes-live-session", events, answered)
7753    }
7754
7755    fn requesting_runtime_named(
7756        harness: &str,
7757        runtime_id: &str,
7758        events: Vec<HarnessEvent>,
7759        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7760    ) -> Box<dyn RuntimeConnection> {
7761        Box::new(RequestingRuntime {
7762            handle: RuntimeHandle {
7763                harness: HarnessId::from(harness),
7764                runtime_id: runtime_id.into(),
7765                endpoint: RuntimeEndpoint::LocalProcess {
7766                    pid: None,
7767                    command: vec!["hermes-acp".into()],
7768                    protocol: "acp".into(),
7769                },
7770            },
7771            events: events.into(),
7772            answered,
7773        })
7774    }
7775
7776    fn permission_event(id: u64, title: &str) -> HarnessEvent {
7777        HarnessEvent {
7778            sequence: None,
7779            kind: "session/request_permission".into(),
7780            payload: json!({
7781                "jsonrpc": "2.0",
7782                "id": id,
7783                "method": "session/request_permission",
7784                "params": {
7785                    "sessionId": "hermes-live-session",
7786                    "toolCall": {"toolCallId": "call-1", "title": title, "kind": "execute"},
7787                    "options": [
7788                        {"optionId": "allow_once", "name": "Allow once", "kind": "allow_once"},
7789                        {"optionId": "allow_for_session", "name": "Allow for session", "kind": "allow_always"},
7790                        {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
7791                    ],
7792                },
7793            }),
7794        }
7795    }
7796
7797    fn approvals(service: &mut HarnessSessionService, params: Value) -> Value {
7798        let response = service.handle(request(1, "harness.v1.approvals.list", params));
7799        assert!(response.get("error").is_none(), "{response:#}");
7800        response["result"].clone()
7801    }
7802
7803    /// ORC-2 dev/01: the same uniform loop over the CLAUDE CODE door. The
7804    /// `can_use_tool` control request the CLI raises to its registered
7805    /// permission handler lists as one pending row, `approvals.resolve <id>
7806    /// allow_once` sends the `{behavior}` result the CLI accepts through
7807    /// `runtimes.respond`, and the row is gone. The frame is the one claude
7808    /// 2.1.258 wrote, transcribed from
7809    /// `docs/interop/research/orc2-claude-respond-receipt-2026-09-04.json`.
7810    #[tokio::test]
7811    async fn a_claude_code_permission_request_lists_and_resolves_on_the_uniform_door() {
7812        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7813        let mut service = HarnessSessionService::new();
7814        service.runtimes.insert(
7815            "runtime-cc".into(),
7816            requesting_runtime_named(
7817                HarnessId::CLAUDE_CODE,
7818                "claude-live-session",
7819                vec![HarnessEvent {
7820                    sequence: None,
7821                    kind: "control_request".into(),
7822                    payload: json!({
7823                        "type": "control_request",
7824                        "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
7825                        "request": {
7826                            "subtype": "can_use_tool",
7827                            "tool_name": "Bash",
7828                            "display_name": "Bash",
7829                            "input": {"command": "touch probe-artifact.txt"},
7830                            "tool_use_id": "toolu_mock_1",
7831                        },
7832                    }),
7833                }],
7834                answered.clone(),
7835            ),
7836        );
7837
7838        let notifications = service.poll_runtimes().await;
7839        assert_eq!(notifications.len(), 1, "{notifications:#?}");
7840
7841        let rows = approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}));
7842        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
7843        let row = &rows[0];
7844        assert_eq!(row["id"], "runtime-cc/053f8a2d-3445-4011-a259-4261b31c7326");
7845        assert_eq!(row["harness"], HarnessId::CLAUDE_CODE);
7846        assert_eq!(row["status"], "pending");
7847        assert_eq!(row["subject"], "Bash touch probe-artifact.txt");
7848        assert_eq!(row["runtime_id"], "claude-live-session");
7849        assert_eq!(
7850            row["options"]
7851                .as_array()
7852                .unwrap()
7853                .iter()
7854                .map(|option| option["id"].as_str().unwrap())
7855                .collect::<Vec<_>>(),
7856            vec!["allow", "deny"],
7857        );
7858
7859        let response = resolve(
7860            &mut service,
7861            json!({"id": row["id"], "decision": "allow_once"}),
7862        )
7863        .await;
7864        assert!(response.get("error").is_none(), "{response:#}");
7865        assert_eq!(response["result"]["option_id"], "allow");
7866        assert_eq!(
7867            answered
7868                .lock()
7869                .unwrap_or_else(std::sync::PoisonError::into_inner)
7870                .as_slice(),
7871            &[json!({
7872                "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
7873                "response": {"behavior": "allow"},
7874            })],
7875        );
7876        assert_eq!(
7877            approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}))
7878                .as_array()
7879                .map(Vec::len),
7880            Some(0),
7881        );
7882    }
7883
7884    /// dev/01: a live ACP permission request raised on a driven runtime is
7885    /// listable while the turn is blocked on it, and stops being listable
7886    /// the moment `runtimes.respond` answers it.
7887    #[tokio::test]
7888    async fn a_live_permission_request_lists_until_it_is_answered() {
7889        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7890        let mut service = HarnessSessionService::new();
7891        service.runtimes.insert(
7892            "runtime-1".into(),
7893            requesting_runtime(
7894                HarnessId::HERMES,
7895                vec![permission_event(7, "rm -rf build")],
7896                answered.clone(),
7897            ),
7898        );
7899
7900        let notifications = service.poll_runtimes().await;
7901        assert_eq!(notifications.len(), 1, "{notifications:#?}");
7902
7903        let rows = approvals(&mut service, json!({}));
7904        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
7905        let row = &rows[0];
7906        assert_eq!(row["id"], "runtime-1/7");
7907        assert_eq!(row["harness"], HarnessId::HERMES);
7908        assert_eq!(row["kind"], "live");
7909        assert_eq!(row["status"], "pending");
7910        assert_eq!(row["subject"], "rm -rf build");
7911        assert_eq!(row["session_id"], "hermes-live-session");
7912        assert_eq!(row["runtime_id"], "hermes-live-session");
7913        assert!(row["requested_at_ms"].as_i64().is_some(), "{row:#}");
7914        assert!(
7915            row["age_ms"].as_i64().is_some_and(|age| age >= 0),
7916            "{row:#}"
7917        );
7918        assert_eq!(
7919            row["options"]
7920                .as_array()
7921                .unwrap()
7922                .iter()
7923                .map(|option| option["id"].as_str().unwrap())
7924                .collect::<Vec<_>>(),
7925            vec!["allow_once", "allow_for_session", "deny"],
7926        );
7927
7928        // The filters select against the same rows.
7929        assert_eq!(
7930            approvals(&mut service, json!({"harness": HarnessId::HERMES}))
7931                .as_array()
7932                .map(Vec::len),
7933            Some(1),
7934        );
7935        assert_eq!(
7936            approvals(&mut service, json!({"session": "some-other-session"}))
7937                .as_array()
7938                .map(Vec::len),
7939            Some(0),
7940        );
7941
7942        let response = service
7943            .handle_async(request(
7944                2,
7945                "harness.v1.runtimes.respond",
7946                json!({
7947                    "connection": "runtime-1",
7948                    "request_id": 7,
7949                    "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7950                }),
7951            ))
7952            .await;
7953        assert!(response.get("error").is_none(), "{response:#}");
7954        assert_eq!(
7955            answered
7956                .lock()
7957                .unwrap_or_else(std::sync::PoisonError::into_inner)
7958                .as_slice(),
7959            &[json!({
7960                "request_id": 7,
7961                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7962            })],
7963        );
7964
7965        let rows = approvals(&mut service, json!({}));
7966        assert_eq!(rows.as_array().map(Vec::len), Some(0), "{rows:#}");
7967    }
7968
7969    /// dev/01: supercode's own queued subagent approvals list through the
7970    /// same door, carrying the outcome the record holds.
7971    #[test]
7972    fn queued_subagent_approvals_list_through_the_same_door() {
7973        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
7974            crate::subagents::QueuedApproval {
7975                child_agent_id: "child-7".into(),
7976                tool: "shell".into(),
7977                subject: Some("cargo publish --dry-run".into()),
7978                queued_at_ms: 1,
7979                outcome: None,
7980            },
7981            crate::subagents::QueuedApproval {
7982                child_agent_id: "child-8".into(),
7983                tool: "write_file".into(),
7984                subject: None,
7985                queued_at_ms: 2,
7986                outcome: Some(crate::subagents::QueuedApprovalOutcome::Denied),
7987            },
7988        ]));
7989        let mut service = HarnessSessionService::new();
7990        service.observe_subagent_approvals(queue);
7991
7992        let rows = approvals(&mut service, json!({}));
7993        assert_eq!(rows.as_array().map(Vec::len), Some(2), "{rows:#}");
7994        assert_eq!(rows[0]["id"], "supercode/subagent/child-7/1/0");
7995        assert_eq!(rows[0]["harness"], HarnessId::SUPERCODE);
7996        assert_eq!(rows[0]["status"], "pending");
7997        assert_eq!(rows[0]["subject"], "shell cargo publish --dry-run");
7998        assert_eq!(rows[1]["status"], "denied");
7999        assert!(rows[1]["options"].as_array().unwrap().is_empty());
8000
8001        // `--session` addresses a subagent row by its child agent id.
8002        let only = approvals(&mut service, json!({"session": "child-8"}));
8003        assert_eq!(only.as_array().map(Vec::len), Some(1), "{only:#}");
8004        assert_eq!(only[0]["id"], "supercode/subagent/child-8/2/1");
8005    }
8006
8007    /// The uniform-verb contract: an id whose runtime door cannot carry a
8008    /// protocol request is refused BY NAME rather than answered with an empty
8009    /// list. Since ORC-2 gave Claude Code a permission-response primitive
8010    /// every registered harness can carry one, so the refusal is exercised on
8011    /// an unknown id — and the registered ids are asserted to be accepted.
8012    #[test]
8013    fn approvals_list_refuses_a_harness_that_cannot_carry_a_request() {
8014        let response = HarnessSessionService::new().handle(request(
8015            1,
8016            "harness.v1.approvals.list",
8017            json!({"harness": "not-a-harness"}),
8018        ));
8019        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
8020        assert!(response["error"]["message"]
8021            .as_str()
8022            .unwrap()
8023            .contains("not-a-harness"));
8024        for harness in [HarnessId::CLAUDE_CODE, HarnessId::CODEX] {
8025            let response = HarnessSessionService::new().handle(request(
8026                1,
8027                "harness.v1.approvals.list",
8028                json!({"harness": harness}),
8029            ));
8030            assert!(response.get("error").is_none(), "{harness}: {response:#}");
8031        }
8032    }
8033
8034    /// The method is advertised, its SDK operation resolves it, and the
8035    /// registry reports the concept as observed for every harness whose
8036    /// runtime door can carry a request.
8037    #[test]
8038    fn approvals_list_is_an_advertised_method_and_an_observed_tier() {
8039        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.list"));
8040        assert_eq!(
8041            SdkOperation::from_method("harness.v1.approvals.list"),
8042            Some(SdkOperation::ApprovalsList)
8043        );
8044        let registry = harness_support_registry();
8045        for id in [
8046            HarnessId::HERMES,
8047            HarnessId::OPENCLAW,
8048            HarnessId::CODEX,
8049            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8050            // pending_request concept joins the other driven doors.
8051            HarnessId::CLAUDE_CODE,
8052        ] {
8053            let concept = registry
8054                .harnesses
8055                .iter()
8056                .find(|harness| harness.id.as_str() == id)
8057                .unwrap()
8058                .orchestration
8059                .concepts
8060                .iter()
8061                .find(|concept| concept.concept == "pending_request")
8062                .unwrap();
8063            assert_eq!(concept.observed, crate::ImplementationKind::BuiltIn, "{id}");
8064            assert!(concept
8065                .methods
8066                .iter()
8067                .any(|method| method == "harness.v1.approvals.list"));
8068        }
8069    }
8070
8071    // ---- ORCH-20: `harness.v1.approvals.resolve` -------------------------
8072
8073    async fn resolve(service: &mut HarnessSessionService, params: Value) -> Value {
8074        service
8075            .handle_async(request(3, "harness.v1.approvals.resolve", params))
8076            .await
8077    }
8078
8079    /// dev/01: the whole loop on a driven runtime — list one pending row,
8080    /// answer it by ROW ID with one uniform decision, and see it gone. The
8081    /// door receives its own ACP envelope carrying the option it enumerated.
8082    #[tokio::test]
8083    async fn a_listed_row_resolves_with_one_uniform_decision_and_then_is_gone() {
8084        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8085        let mut service = HarnessSessionService::new();
8086        service.runtimes.insert(
8087            "runtime-1".into(),
8088            requesting_runtime(
8089                HarnessId::HERMES,
8090                vec![permission_event(7, "rm -rf build")],
8091                answered.clone(),
8092            ),
8093        );
8094        service.poll_runtimes().await;
8095
8096        let rows = approvals(&mut service, json!({}));
8097        assert_eq!(rows[0]["id"], "runtime-1/7");
8098
8099        let response = resolve(
8100            &mut service,
8101            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8102        )
8103        .await;
8104        assert!(response.get("error").is_none(), "{response:#}");
8105        assert_eq!(
8106            response["result"],
8107            json!({
8108                "id": "runtime-1/7",
8109                "decision": "allow_once",
8110                "option_id": "allow_once",
8111                "resolved": true,
8112            }),
8113        );
8114        // The harness's own door was called with its own envelope.
8115        assert_eq!(
8116            answered
8117                .lock()
8118                .unwrap_or_else(std::sync::PoisonError::into_inner)
8119                .as_slice(),
8120            &[json!({
8121                "request_id": 7,
8122                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8123            })],
8124        );
8125        // And the row is gone, the same way `runtimes.respond` drops it.
8126        assert_eq!(
8127            approvals(&mut service, json!({})).as_array().map(Vec::len),
8128            Some(0),
8129        );
8130        // Answering it twice is an honest miss, not a silent success.
8131        let response = resolve(
8132            &mut service,
8133            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8134        )
8135        .await;
8136        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8137    }
8138
8139    /// dev/01: deny travels the same path and picks the option the request
8140    /// itself classified as a refusal.
8141    #[tokio::test]
8142    async fn deny_selects_the_requests_own_reject_option() {
8143        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8144        let mut service = HarnessSessionService::new();
8145        service.runtimes.insert(
8146            "runtime-1".into(),
8147            requesting_runtime(
8148                HarnessId::HERMES,
8149                vec![permission_event(11, "git push --force")],
8150                answered.clone(),
8151            ),
8152        );
8153        service.poll_runtimes().await;
8154
8155        let response = resolve(
8156            &mut service,
8157            json!({"id": "runtime-1/11", "decision": "deny"}),
8158        )
8159        .await;
8160        assert!(response.get("error").is_none(), "{response:#}");
8161        // `deny` is the optionId whose ACP `kind` is `reject_once`.
8162        assert_eq!(response["result"]["option_id"], "deny");
8163        assert_eq!(
8164            answered
8165                .lock()
8166                .unwrap_or_else(std::sync::PoisonError::into_inner)[0]["response"],
8167            json!({"outcome": {"outcome": "selected", "optionId": "deny"}}),
8168        );
8169        assert_eq!(
8170            approvals(&mut service, json!({})).as_array().map(Vec::len),
8171            Some(0),
8172        );
8173    }
8174
8175    /// dev/01: a decision this request does not offer is refused by name,
8176    /// listing the ones it does — never silently downgraded to a neighbour.
8177    #[tokio::test]
8178    async fn a_decision_the_request_does_not_offer_is_refused_with_the_offered_ones() {
8179        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8180        let mut service = HarnessSessionService::new();
8181        let mut event = permission_event(3, "rm -rf build");
8182        // A request offering only allow-once and deny, as hermes 0.21.0's
8183        // edit-approval layer raises one.
8184        event.payload["params"]["options"] = json!([
8185            {"optionId": "allow_once", "name": "Allow edit", "kind": "allow_once"},
8186            {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
8187        ]);
8188        service.runtimes.insert(
8189            "runtime-1".into(),
8190            requesting_runtime(HarnessId::HERMES, vec![event], answered.clone()),
8191        );
8192        service.poll_runtimes().await;
8193
8194        let response = resolve(
8195            &mut service,
8196            json!({"id": "runtime-1/3", "decision": "allow_always"}),
8197        )
8198        .await;
8199        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8200        let message = response["error"]["message"].as_str().unwrap();
8201        assert!(message.contains("allow_always"), "{message}");
8202        assert!(message.contains("allow_once, deny"), "{message}");
8203        // Nothing was sent, and the request is still waiting for an answer.
8204        assert!(answered
8205            .lock()
8206            .unwrap_or_else(std::sync::PoisonError::into_inner)
8207            .is_empty());
8208        assert_eq!(
8209            approvals(&mut service, json!({})).as_array().map(Vec::len),
8210            Some(1),
8211        );
8212    }
8213
8214    /// dev/01: supercode's own queued subagent row is addressable but not
8215    /// answerable through this door — it is the parent's audit copy of a
8216    /// request its own handler answers. Refused by name, never a no-op.
8217    #[tokio::test]
8218    async fn a_queued_subagent_row_is_refused_by_name_rather_than_silently_answered() {
8219        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
8220            crate::subagents::QueuedApproval {
8221                child_agent_id: "child-7".into(),
8222                tool: "shell".into(),
8223                subject: Some("cargo publish --dry-run".into()),
8224                queued_at_ms: 1,
8225                outcome: None,
8226            },
8227        ]));
8228        let mut service = HarnessSessionService::new();
8229        service.observe_subagent_approvals(queue.clone());
8230        let row = approvals(&mut service, json!({}))[0]["id"]
8231            .as_str()
8232            .unwrap()
8233            .to_string();
8234        assert_eq!(row, "supercode/subagent/child-7/1/0");
8235
8236        let response = resolve(&mut service, json!({"id": row, "decision": "allow_once"})).await;
8237        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8238        let message = response["error"]["message"].as_str().unwrap();
8239        assert!(message.contains("queued subagent record"), "{message}");
8240        assert!(message.contains("request"), "{message}");
8241        // The audit record is untouched: nothing pretended to answer it.
8242        assert!(queue
8243            .lock()
8244            .unwrap_or_else(std::sync::PoisonError::into_inner)[0]
8245            .outcome
8246            .is_none());
8247    }
8248
8249    /// An id nobody is holding, and a call that names no decision at all,
8250    /// both fail with a message that says why.
8251    #[tokio::test]
8252    async fn an_unknown_row_and_a_missing_decision_are_both_named() {
8253        let mut service = HarnessSessionService::new();
8254        let response = resolve(
8255            &mut service,
8256            json!({"id": "runtime-9/4", "decision": "deny"}),
8257        )
8258        .await;
8259        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8260        assert!(response["error"]["message"]
8261            .as_str()
8262            .unwrap()
8263            .contains("runtime-9/4"));
8264
8265        let response = resolve(&mut service, json!({"id": "runtime-9/4"})).await;
8266        let message = response["error"]["message"].as_str().unwrap();
8267        assert!(
8268            message.contains("allow_once | allow_always | deny"),
8269            "{message}"
8270        );
8271
8272        let response = resolve(
8273            &mut service,
8274            json!({"id": "runtime-9/4", "decision": "deny", "option_id": "deny"}),
8275        )
8276        .await;
8277        assert!(response["error"]["message"]
8278            .as_str()
8279            .unwrap()
8280            .contains("not both"));
8281    }
8282
8283    /// The method is advertised, its SDK operation resolves it, and every
8284    /// harness whose runtime door can carry a request reports it on the
8285    /// CONTROLLED tier beside `runtimes.respond`.
8286    #[test]
8287    fn approvals_resolve_is_an_advertised_method_and_a_controlled_tier() {
8288        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.resolve"));
8289        assert_eq!(
8290            SdkOperation::from_method("harness.v1.approvals.resolve"),
8291            Some(SdkOperation::ApprovalsResolve)
8292        );
8293        assert_eq!(
8294            SdkOperation::ApprovalsResolve.action_name(),
8295            "approvals_resolve"
8296        );
8297        let registry = harness_support_registry();
8298        for id in [
8299            HarnessId::HERMES,
8300            HarnessId::OPENCLAW,
8301            HarnessId::CODEX,
8302            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8303            // pending_request concept joins the other driven doors.
8304            HarnessId::CLAUDE_CODE,
8305        ] {
8306            let concept = registry
8307                .harnesses
8308                .iter()
8309                .find(|harness| harness.id.as_str() == id)
8310                .unwrap()
8311                .orchestration
8312                .concepts
8313                .iter()
8314                .find(|concept| concept.concept == "pending_request")
8315                .unwrap();
8316            assert_eq!(
8317                concept.controlled,
8318                crate::ImplementationKind::BuiltIn,
8319                "{id}"
8320            );
8321            assert!(
8322                concept
8323                    .methods
8324                    .iter()
8325                    .any(|method| method == "harness.v1.approvals.resolve"),
8326                "{id}"
8327            );
8328        }
8329    }
8330
8331    #[test]
8332    fn capabilities_are_explicit_and_versioned() {
8333        let mut service = HarnessSessionService::new();
8334        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
8335        assert_eq!(response["result"]["version"], HARNESS_SERVICE_VERSION);
8336        assert_eq!(
8337            response["result"]["sdk"]["schema_version"],
8338            crate::SDK_SCHEMA_VERSION
8339        );
8340        assert_eq!(
8341            response["result"]["sdk"]["operations"]
8342                .as_array()
8343                .unwrap()
8344                .len(),
8345            SdkOperation::ALL.len()
8346        );
8347        assert_eq!(
8348            response["result"]["harnesses"].as_array().unwrap().len(),
8349            11
8350        );
8351        assert!(response["result"]["harnesses"]
8352            .as_array()
8353            .unwrap()
8354            .iter()
8355            .any(|harness| harness == HarnessId::GROK));
8356        assert!(response["result"]["harnesses"]
8357            .as_array()
8358            .unwrap()
8359            .iter()
8360            .any(|harness| harness == HarnessId::GOOSE));
8361    }
8362
8363    #[test]
8364    fn handshake_health_uses_protocol_liveness_not_stderr_severity() {
8365        let noisy_stderr = crate::HarnessEvent {
8366            sequence: None,
8367            kind: "transport_stderr".into(),
8368            payload: json!({"line": "ERROR optional worker AuthorizationRequired"}),
8369        };
8370        assert_eq!(handshake_event_failure(&noisy_stderr), None);
8371
8372        let closed = crate::HarnessEvent {
8373            sequence: None,
8374            kind: "transport_closed".into(),
8375            payload: json!({}),
8376        };
8377        assert!(handshake_event_failure(&closed).is_some());
8378    }
8379
8380    #[tokio::test]
8381    async fn runtime_eof_is_notified_and_removed_for_raw_and_explicit_close() {
8382        let mut service = HarnessSessionService::new();
8383        service
8384            .runtimes
8385            .insert("raw-eof".into(), ending_runtime(None));
8386        service.runtimes.insert(
8387            "explicit-close".into(),
8388            ending_runtime(Some(HarnessEvent {
8389                sequence: None,
8390                kind: "transport_closed".into(),
8391                payload: json!({"message": "native transport exited"}),
8392            })),
8393        );
8394
8395        let notifications = service.poll_runtimes().await;
8396
8397        assert_eq!(notifications.len(), 2);
8398        assert!(notifications
8399            .iter()
8400            .all(|notification| { notification["params"]["event"]["kind"] == "transport_closed" }));
8401        assert!(notifications.iter().all(|notification| {
8402            notification["params"]["session_id"] == "ending-session"
8403                && notification["params"]["connection"].is_string()
8404        }));
8405        let mut sequences = notifications
8406            .iter()
8407            .filter_map(|notification| notification["params"]["sequence"].as_u64())
8408            .collect::<Vec<_>>();
8409        sequences.sort_unstable();
8410        assert_eq!(sequences, vec![1, 2]);
8411        assert!(service.runtimes.is_empty());
8412    }
8413
8414    #[test]
8415    fn support_report_and_grok_default_binding_share_the_registry() {
8416        let mut service = HarnessSessionService::new();
8417        let response = service.handle(request(1, "harness.v1.support.report", json!({})));
8418        assert_eq!(response["result"]["schema"], crate::SUPPORT_REGISTRY_SCHEMA);
8419        let params = RuntimeBackendParams {
8420            harness: HarnessId::from(HarnessId::GROK),
8421            protocol: None,
8422            launch: None,
8423            base_url: None,
8424            policy: RuntimePolicy::Default,
8425        };
8426        let backend = match runtime_backend(&params) {
8427            Ok(backend) => backend,
8428            Err(_) => panic!("Grok should bind through its registered ACP launch"),
8429        };
8430        assert_eq!(backend.harness().as_str(), HarnessId::GROK);
8431        assert!(backend.capabilities().start_session);
8432        let registered = harness_support_registry()
8433            .harnesses
8434            .into_iter()
8435            .find(|harness| harness.id.as_str() == HarnessId::GROK)
8436            .and_then(|harness| harness.runtime.default_launch)
8437            .unwrap();
8438        assert!(!registered
8439            .arguments
8440            .iter()
8441            .any(|argument| argument == "--always-approve"));
8442        assert!(runtime_launch(&params).is_none());
8443
8444        let yolo = RuntimeBackendParams {
8445            policy: RuntimePolicy::Yolo,
8446            ..params
8447        };
8448        assert!(runtime_launch(&yolo)
8449            .unwrap()
8450            .arguments
8451            .iter()
8452            .any(|argument| argument == "--always-approve"));
8453
8454        let mismatched_protocol = RuntimeBackendParams {
8455            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8456            protocol: Some("acp".into()),
8457            launch: None,
8458            base_url: None,
8459            policy: RuntimePolicy::Default,
8460        };
8461        assert!(runtime_backend(&mismatched_protocol).is_err());
8462    }
8463
8464    #[test]
8465    fn load_follow_and_unfollow_share_the_same_locator() {
8466        let mut service = HarnessSessionService::new();
8467        let locator = pi_locator();
8468        let loaded = service.handle(request(
8469            1,
8470            "harness.v1.sessions.load",
8471            json!({"locator": locator}),
8472        ));
8473        assert_eq!(
8474            loaded["result"]["session"]["session_id"],
8475            locator.session_id
8476        );
8477
8478        let followed = service.handle(request(
8479            2,
8480            "harness.v1.sessions.follow",
8481            json!({"locator": locator}),
8482        ));
8483        assert_eq!(followed["result"]["subscription"], "sub-1");
8484        assert_eq!(followed["result"]["initial"]["type"], "session_snapshot");
8485        assert!(service.poll().is_empty());
8486
8487        let unfollowed = service.handle(request(
8488            3,
8489            "harness.v1.sessions.unfollow",
8490            json!({"subscription": "sub-1"}),
8491        ));
8492        assert_eq!(unfollowed["result"]["removed"], true);
8493    }
8494
8495    #[test]
8496    fn bounded_read_view_excludes_subagents_and_keeps_only_the_tail() {
8497        let temp = std::env::temp_dir().join(format!(
8498            "supercode-bounded-view-{}-{}",
8499            std::process::id(),
8500            generated_session_id()
8501        ));
8502        let path = temp.join("parent.jsonl");
8503        let subagents = temp.join("parent/subagents");
8504        std::fs::create_dir_all(&subagents).unwrap();
8505        let long_last = "x".repeat(300);
8506        let parent_records = [
8507            json!({"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":"first"}}),
8508            json!({"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"middle"}]}}),
8509            json!({"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":long_last}}),
8510        ];
8511        std::fs::write(
8512            &path,
8513            format!(
8514                "{}\n",
8515                parent_records
8516                    .iter()
8517                    .map(Value::to_string)
8518                    .collect::<Vec<_>>()
8519                    .join("\n")
8520            ),
8521        )
8522        .unwrap();
8523        std::fs::write(
8524            subagents.join("agent-child.jsonl"),
8525            concat!(
8526                r#"{"type":"user","uuid":"cu","parentUuid":null,"agentId":"child","message":{"role":"user","content":"child work"}}"#,
8527                "\n",
8528            ),
8529        )
8530        .unwrap();
8531        let locator = SessionLocator {
8532            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8533            session_id: "parent".into(),
8534            storage: StorageLocator::File { path },
8535        };
8536        let mut service = HarnessSessionService::new();
8537
8538        let complete = service.handle(request(
8539            1,
8540            "harness.v1.sessions.load",
8541            json!({"locator": locator}),
8542        ));
8543        assert_eq!(
8544            complete["result"]["session"]["subagents"]
8545                .as_array()
8546                .unwrap()
8547                .len(),
8548            1
8549        );
8550
8551        let bounded = service.handle(request(
8552            2,
8553            "harness.v1.sessions.load",
8554            json!({
8555                "locator": locator,
8556                "view": {
8557                    "tail_messages": 1,
8558                    "max_message_chars": 256,
8559                    "include_subagents": false
8560                },
8561            }),
8562        ));
8563        let session = &bounded["result"]["session"];
8564        assert!(session["subagents"].as_array().unwrap().is_empty());
8565        assert_eq!(session["messages"].as_array().unwrap().len(), 1);
8566        assert_eq!(
8567            session["messages"][0]["content"],
8568            format!("{}\n…", "x".repeat(256))
8569        );
8570
8571        let followed = service.handle(request(
8572            3,
8573            "harness.v1.sessions.follow",
8574            json!({
8575                "locator": locator,
8576                "view": {
8577                    "tail_messages": 1,
8578                    "max_message_chars": 256,
8579                    "include_subagents": false
8580                },
8581            }),
8582        ));
8583        let initial = &followed["result"]["initial"]["session"];
8584        assert!(initial["subagents"].as_array().unwrap().is_empty());
8585        assert_eq!(initial["messages"].as_array().unwrap().len(), 1);
8586
8587        let _ = std::fs::remove_dir_all(&temp);
8588    }
8589
8590    #[test]
8591    fn forty_megabyte_display_load_is_bounded_and_prompt() {
8592        let temp = std::env::temp_dir().join(format!(
8593            "supercode-large-display-view-{}-{}",
8594            std::process::id(),
8595            generated_session_id()
8596        ));
8597        std::fs::create_dir_all(&temp).unwrap();
8598        let path = temp.join("rollout.jsonl");
8599        let mut file = std::io::BufWriter::new(std::fs::File::create(&path).unwrap());
8600        writeln!(
8601            file,
8602            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8603        )
8604        .unwrap();
8605        let padding = "x".repeat(80 * 1024);
8606        for index in 0..512 {
8607            let marker = if index == 0 {
8608                "OLDEST-SHOULD-NOT-LOAD"
8609            } else if index == 511 {
8610                "LATEST-MUST-LOAD"
8611            } else {
8612                "bulk"
8613            };
8614            writeln!(
8615                file,
8616                "{}",
8617                json!({
8618                    "timestamp": "2026-01-01T00:00:01Z",
8619                    "type": "response_item",
8620                    "payload": {
8621                        "type": "message",
8622                        "role": "assistant",
8623                        "content": [{"type": "output_text", "text": format!("{marker}:{padding}")}],
8624                    },
8625                })
8626            )
8627            .unwrap();
8628        }
8629        file.flush().unwrap();
8630        drop(file);
8631        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
8632
8633        let locator = SessionLocator {
8634            harness: HarnessId::from(HarnessId::CODEX),
8635            session_id: "large-display".into(),
8636            storage: StorageLocator::File { path },
8637        };
8638        let started = Instant::now();
8639        let response = HarnessSessionService::new().handle(request(
8640            1,
8641            "harness.v1.sessions.load",
8642            json!({
8643                "locator": locator,
8644                "view": {
8645                    "tail_messages": 500,
8646                    "max_message_chars": 1024,
8647                    "include_subagents": false,
8648                    "display_history": true,
8649                },
8650            }),
8651        ));
8652        let elapsed = started.elapsed();
8653        let wire = response.to_string();
8654        eprintln!(
8655            "bounded 40 MiB display load: {elapsed:?}, {} response bytes",
8656            wire.len()
8657        );
8658        assert!(response.get("error").is_none(), "{response:#}");
8659        assert!(wire.contains("LATEST-MUST-LOAD"));
8660        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
8661        assert!(
8662            wire.len() < 2 * 1024 * 1024,
8663            "bounded wire was {} bytes",
8664            wire.len()
8665        );
8666        assert!(
8667            elapsed.as_secs_f64() < 3.0,
8668            "bounded 40 MiB load took {elapsed:?}"
8669        );
8670
8671        // Timing-free: a store with no human turn widens its window to the 64 MiB ceiling
8672        // looking for anchors, so the bounded read shows on one with a human turn every eight
8673        // records: a short view stops well short of the first record and says so.
8674        let anchored = temp.join("anchored.jsonl");
8675        let mut file = std::io::BufWriter::new(std::fs::File::create(&anchored).unwrap());
8676        writeln!(
8677            file,
8678            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8679        )
8680        .unwrap();
8681        for index in 0..512 {
8682            let marker = if index == 0 {
8683                "OLDEST-SHOULD-NOT-LOAD"
8684            } else if index == 511 {
8685                "LATEST-MUST-LOAD"
8686            } else {
8687                "bulk"
8688            };
8689            let (role, kind) = if index % 8 == 0 {
8690                ("user", "input_text")
8691            } else {
8692                ("assistant", "output_text")
8693            };
8694            writeln!(
8695                file,
8696                "{}",
8697                json!({
8698                    "timestamp": "2026-01-01T00:00:01Z",
8699                    "type": "response_item",
8700                    "payload": {
8701                        "type": "message",
8702                        "role": role,
8703                        "content": [{"type": kind, "text": format!("{marker}:{padding}")}],
8704                    },
8705                })
8706            )
8707            .unwrap();
8708        }
8709        file.flush().unwrap();
8710        drop(file);
8711        let short = HarnessSessionService::new().handle(request(
8712            2,
8713            "harness.v1.sessions.load",
8714            json!({
8715                "locator": SessionLocator {
8716                    harness: HarnessId::from(HarnessId::CODEX),
8717                    session_id: "large-display".into(),
8718                    storage: StorageLocator::File { path: anchored },
8719                },
8720                "view": {
8721                    "tail_messages": 20,
8722                    "max_message_chars": 1024,
8723                    "include_subagents": false,
8724                    "display_history": true,
8725                },
8726            }),
8727        ));
8728        let records = short["result"]["session"]["raw_record_count"].as_u64();
8729        assert!(
8730            records.is_some_and(|records| records < 128),
8731            "{records:?} records read"
8732        );
8733        let short = short.to_string();
8734        assert!(short.contains("LATEST-MUST-LOAD"));
8735        assert!(short.contains("older native records remain outside this bounded display window"));
8736
8737        let _ = std::fs::remove_dir_all(&temp);
8738    }
8739
8740    #[test]
8741    fn forty_megabyte_goose_store_display_load_reads_only_the_tail() {
8742        let temp = std::env::temp_dir().join(format!(
8743            "supercode-large-goose-view-{}-{}",
8744            std::process::id(),
8745            generated_session_id()
8746        ));
8747        std::fs::create_dir_all(&temp).unwrap();
8748        let path = temp.join("sessions.db");
8749        let connection = rusqlite::Connection::open(&path).unwrap();
8750        connection
8751            .execute_batch(
8752                "CREATE TABLE sessions (
8753                    id TEXT PRIMARY KEY, name TEXT NOT NULL, working_dir TEXT NOT NULL,
8754                    created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
8755                    session_type TEXT NOT NULL, extension_data TEXT,
8756                    goose_mode TEXT NOT NULL, provider_name TEXT, model_config_json TEXT,
8757                    archived_at TEXT
8758                 );
8759                 CREATE TABLE messages (
8760                    id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, message_id TEXT,
8761                    role TEXT NOT NULL, content_json TEXT NOT NULL,
8762                    created_timestamp INTEGER NOT NULL, metadata_json TEXT
8763                 );",
8764            )
8765            .unwrap();
8766        connection
8767            .execute(
8768                "INSERT INTO sessions VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL)",
8769                rusqlite::params![
8770                    "goose-large",
8771                    "Large Goose session",
8772                    "/tmp",
8773                    "2026-01-01 00:00:00",
8774                    "2026-01-01 00:00:02",
8775                    "user",
8776                    "{}",
8777                    "auto",
8778                    "anthropic",
8779                    r#"{"model_name":"claude-sonnet"}"#,
8780                ],
8781            )
8782            .unwrap();
8783        let old_content = serde_json::to_string(&vec![json!({
8784            "type": "text",
8785            "text": format!("OLDEST-SHOULD-NOT-LOAD:{}", "x".repeat(40 * 1024 * 1024)),
8786        })])
8787        .unwrap();
8788        connection
8789            .execute(
8790                "INSERT INTO messages VALUES (1, ?1, 'old', 'user', ?2, 1, '{}')",
8791                rusqlite::params!["goose-large", old_content],
8792            )
8793            .unwrap();
8794        connection
8795            .execute(
8796                "INSERT INTO messages VALUES (2, ?1, 'new', 'assistant', ?2, 2, '{}')",
8797                rusqlite::params![
8798                    "goose-large",
8799                    r#"[{"type":"text","text":"LATEST-MUST-LOAD"}]"#
8800                ],
8801            )
8802            .unwrap();
8803        drop(connection);
8804        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
8805
8806        let locator = SessionLocator {
8807            harness: HarnessId::from(HarnessId::GOOSE),
8808            session_id: "goose-large".into(),
8809            storage: StorageLocator::Sqlite {
8810                path,
8811                selector: "goose-large".into(),
8812            },
8813        };
8814        let started = Instant::now();
8815        let response = HarnessSessionService::new().handle(request(
8816            1,
8817            "harness.v1.sessions.load",
8818            json!({
8819                "locator": locator,
8820                "view": {
8821                    "tail_messages": 1,
8822                    "max_message_chars": 1024,
8823                    "include_subagents": false,
8824                    "display_history": true,
8825                },
8826            }),
8827        ));
8828        let elapsed = started.elapsed();
8829        let wire = response.to_string();
8830        eprintln!(
8831            "bounded 40 MiB Goose display load: {elapsed:?}, {} response bytes",
8832            wire.len()
8833        );
8834        assert!(response.get("error").is_none(), "{response:#}");
8835        assert!(wire.contains("LATEST-MUST-LOAD"));
8836        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
8837        assert!(
8838            wire.len() < 64 * 1024,
8839            "bounded wire was {} bytes",
8840            wire.len()
8841        );
8842        assert!(
8843            elapsed.as_secs_f64() < 1.0,
8844            "bounded Goose load took {elapsed:?}"
8845        );
8846
8847        let _ = std::fs::remove_dir_all(&temp);
8848    }
8849
8850    #[test]
8851    fn display_view_keeps_codex_assistant_history_across_compaction() {
8852        let temp = std::env::temp_dir().join(format!(
8853            "supercode-codex-display-view-{}-{}",
8854            std::process::id(),
8855            generated_session_id()
8856        ));
8857        std::fs::create_dir_all(&temp).unwrap();
8858        let path = temp.join("rollout.jsonl");
8859        std::fs::write(
8860            &path,
8861            concat!(
8862                r#"{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"codex-display","cwd":"/tmp"}}"#,
8863                "\n",
8864                r#"{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]}}"#,
8865                "\n",
8866                r#"{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"old answer"}]}}"#,
8867                "\n",
8868                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"}]}}"#,
8869                "\n",
8870                r#"{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"new prompt"}]}}"#,
8871                "\n",
8872                r#"{"timestamp":"2026-01-01T00:00:05Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"new answer"}]}}"#,
8873                "\n",
8874            ),
8875        )
8876        .unwrap();
8877        let locator = SessionLocator {
8878            harness: HarnessId::from(HarnessId::CODEX),
8879            session_id: "codex-display".into(),
8880            storage: StorageLocator::File { path },
8881        };
8882        let mut service = HarnessSessionService::new();
8883
8884        let continuation = service.handle(request(
8885            1,
8886            "harness.v1.sessions.load",
8887            json!({"locator": locator}),
8888        ));
8889        let continuation_text = continuation["result"]["session"]["messages"].to_string();
8890        assert!(!continuation_text.contains("old answer"));
8891
8892        let display = service.handle(request(
8893            2,
8894            "harness.v1.sessions.load",
8895            json!({
8896                "locator": locator,
8897                "view": {
8898                    "tail_messages": 10,
8899                    "include_subagents": false,
8900                    "display_history": true,
8901                },
8902            }),
8903        ));
8904        let display_text = display["result"]["session"]["messages"].to_string();
8905        assert!(display_text.contains("old prompt"));
8906        assert!(display_text.contains("old answer"));
8907        assert!(display_text.contains("new prompt"));
8908        assert!(display_text.contains("new answer"));
8909
8910        let _ = std::fs::remove_dir_all(&temp);
8911    }
8912
8913    #[test]
8914    fn indexed_claude_windows_match_the_existing_wire_projection() {
8915        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8916            .join("tests/fixtures/claude_code_session.jsonl");
8917        let locator = SessionLocator {
8918            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8919            session_id: "fixture".into(),
8920            storage: StorageLocator::File { path },
8921        };
8922        let full = load_session(&locator).unwrap();
8923        for inline_media in [InlineMediaMode::Full, InlineMediaMode::Metadata] {
8924            for offset in [0, 1, full.messages.len(), usize::MAX] {
8925                for limit in [0, 1, 3, usize::MAX] {
8926                    let options = SessionLoadOptions {
8927                        include_subagents: Some(false),
8928                        inline_media,
8929                        message_offset: Some(offset),
8930                        message_limit: Some(limit),
8931                        ..Default::default()
8932                    };
8933                    let expected = projected_session_result(&full, &options);
8934                    assert_eq!(
8935                        indexed_claude_window(&locator, &options).unwrap().unwrap(),
8936                        expected
8937                    );
8938                }
8939            }
8940            for tail in [0, 1, 3, usize::MAX] {
8941                let options = SessionLoadOptions {
8942                    include_subagents: Some(false),
8943                    inline_media,
8944                    message_tail: Some(tail),
8945                    ..Default::default()
8946                };
8947                assert_eq!(
8948                    indexed_claude_window(&locator, &options).unwrap().unwrap(),
8949                    projected_session_result(&full, &options)
8950                );
8951            }
8952        }
8953    }
8954
8955    #[test]
8956    fn load_supports_bounded_windows_and_media_metadata() {
8957        let mut service = HarnessSessionService::new();
8958        let locator = pi_locator();
8959        let bounded = service.handle(request(
8960            1,
8961            "harness.v1.sessions.load",
8962            json!({
8963                "locator": locator,
8964                "options": {
8965                    "include_subagents": false,
8966                    "message_limit": 2,
8967                    "message_offset": 1
8968                }
8969            }),
8970        ));
8971        assert_eq!(bounded["result"]["window"]["offset"], 1);
8972        assert_eq!(bounded["result"]["window"]["returned"], 2);
8973        assert!(bounded["result"]["summary"]["first_message"].is_object());
8974        assert!(bounded["result"]["summary"]["last_message"].is_object());
8975        assert_eq!(
8976            bounded["result"]["session"]["messages"]
8977                .as_array()
8978                .unwrap()
8979                .len(),
8980            2
8981        );
8982        assert!(bounded["result"]["session"]["subagents"]
8983            .as_array()
8984            .unwrap()
8985            .is_empty());
8986
8987        let tail = service.handle(request(
8988            2,
8989            "harness.v1.sessions.load",
8990            json!({"locator": locator, "options": {"message_tail": 1}}),
8991        ));
8992        assert_eq!(tail["result"]["window"]["returned"], 1);
8993        assert_eq!(tail["result"]["window"]["has_more"], true);
8994        assert_eq!(tail["result"]["window"]["has_older"], true);
8995        assert!(tail["result"]["window"]["older_items"].as_u64().unwrap() > 0);
8996        assert!(tail["result"]["summary"]["first_message"].is_object());
8997
8998        let metadata_only = service.handle(request(
8999            3,
9000            "harness.v1.sessions.load",
9001            json!({"locator": locator, "options": {"inline_media": "metadata"}}),
9002        ));
9003        assert!(metadata_only["result"]["session"]
9004            .to_string()
9005            .contains("media_reference"));
9006        assert!(!metadata_only["result"]["session"]
9007            .to_string()
9008            .contains("data:image/"));
9009    }
9010
9011    #[test]
9012    fn import_translate_branch_and_handoff_use_typed_artifacts() {
9013        let mut service = HarnessSessionService::new();
9014        let locator = pi_locator();
9015        let translated = service.handle(request(
9016            1,
9017            "harness.v1.sessions.translate",
9018            json!({"locator": locator, "target_harness": "grok"}),
9019        ));
9020        assert_eq!(translated["result"]["artifact"]["source_harness"], "pi");
9021        assert_eq!(translated["result"]["artifact"]["target_harness"], "grok");
9022        assert!(translated["result"]["artifact"]["content"]
9023            .as_str()
9024            .is_some_and(|content| !content.is_empty()));
9025
9026        for target in ["opencode", "open-code"] {
9027            let opencode = service.handle(request(
9028                6,
9029                "harness.v1.sessions.translate",
9030                json!({"locator": locator, "target_harness": target}),
9031            ));
9032            assert_eq!(opencode["result"]["artifact"]["target_harness"], "opencode");
9033        }
9034        let goose = service.handle(request(
9035            7,
9036            "harness.v1.sessions.translate",
9037            json!({"locator": locator, "target_harness": "goose"}),
9038        ));
9039        assert_eq!(goose["result"]["artifact"]["target_harness"], "goose");
9040        assert!(serde_json::from_str::<Value>(
9041            goose["result"]["artifact"]["content"].as_str().unwrap()
9042        )
9043        .unwrap()["conversation"]
9044            .is_array());
9045
9046        let imported = service.handle(request(
9047            2,
9048            "harness.v1.sessions.import",
9049            json!({
9050                "source_harness": "grok",
9051                "content": translated["result"]["artifact"]["content"],
9052            }),
9053        ));
9054        assert_eq!(imported["result"]["session"]["source"], "grok");
9055
9056        let branched = service.handle(request(
9057            3,
9058            "harness.v1.sessions.branch",
9059            json!({"locator": locator, "target_harness": "codex"}),
9060        ));
9061        assert_eq!(branched["result"]["parent"]["harness"], "pi");
9062        assert!(branched["result"]["bootstrap_prompt"]
9063            .as_str()
9064            .unwrap()
9065            .contains("frozen parent transcript"));
9066        assert_eq!(branched["result"]["artifact"]["target_harness"], "codex");
9067
9068        let handoff = service.handle(request(
9069            4,
9070            "harness.v1.sessions.handoff",
9071            json!({"locator": locator, "target_harness": "pi", "cwd": "/tmp/project"}),
9072        ));
9073        assert_eq!(handoff["result"]["launch"]["program"], "pi");
9074        assert_eq!(handoff["result"]["launch"]["cwd"], "/tmp/project");
9075        assert_eq!(handoff["result"]["requires_materialization"], true);
9076
9077        let goose_handoff = service.handle(request(
9078            8,
9079            "harness.v1.sessions.handoff",
9080            json!({"locator": locator, "target_harness": "goose", "cwd": "/tmp/project"}),
9081        ));
9082        assert_eq!(goose_handoff["result"]["launch"]["program"], "goose");
9083        assert_eq!(
9084            goose_handoff["result"]["materialize"]["arguments"],
9085            json!(["session", "import", "{artifact_path}"])
9086        );
9087
9088        let resumed = service.handle(request(
9089            5,
9090            "harness.v1.sessions.resume_instructions",
9091            json!({"locator": locator, "cwd": "/tmp/project", "policy": "yolo"}),
9092        ));
9093        assert_eq!(resumed["result"]["launch"]["program"], "pi");
9094        assert_eq!(resumed["result"]["launch"]["arguments"][0], "--approve");
9095    }
9096
9097    #[test]
9098    fn reduce_persists_and_reloads_a_byte_exact_reversible_bundle() {
9099        let temp = std::env::temp_dir().join(format!(
9100            "supercode-service-reduce-{}-{}",
9101            std::process::id(),
9102            generated_session_id()
9103        ));
9104        let source_path = temp.join("source.jsonl");
9105        let store_root = temp.join("store");
9106        std::fs::create_dir_all(&temp).unwrap();
9107
9108        let mut records = vec![json!({
9109            "timestamp": "2026-01-01T00:00:00Z",
9110            "type": "session_meta",
9111            "payload": {"id": "codex-reduce", "cwd": "/tmp/project"},
9112        })];
9113        for turn in 0..16 {
9114            records.push(json!({
9115                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 1),
9116                "type": "response_item",
9117                "payload": {
9118                    "type": "message",
9119                    "role": "user",
9120                    "content": [{
9121                        "type": "input_text",
9122                        "text": format!("request {turn}: {}", "context ".repeat(80)),
9123                    }],
9124                },
9125            }));
9126            records.push(json!({
9127                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 2),
9128                "type": "response_item",
9129                "payload": {
9130                    "type": "message",
9131                    "role": "assistant",
9132                    "content": [{
9133                        "type": "output_text",
9134                        "text": format!("answer {turn}: {}", "implementation detail ".repeat(80)),
9135                    }],
9136                },
9137            }));
9138        }
9139        let source = format!(
9140            "{}\n",
9141            records
9142                .iter()
9143                .map(Value::to_string)
9144                .collect::<Vec<_>>()
9145                .join("\n")
9146        );
9147        std::fs::write(&source_path, &source).unwrap();
9148        let locator = SessionLocator {
9149            harness: HarnessId::from(HarnessId::CODEX),
9150            session_id: "codex-reduce".into(),
9151            storage: StorageLocator::File {
9152                path: source_path.clone(),
9153            },
9154        };
9155        let original = load_session(&locator).unwrap();
9156        let mut service =
9157            HarnessSessionService::new().with_reduction_store_root(store_root.clone());
9158
9159        let response = service.handle(request(
9160            1,
9161            "harness.v1.sessions.reduce",
9162            json!({
9163                "locator": locator,
9164                "target_harness": "claude-code",
9165                "keep_last": 4,
9166            }),
9167        ));
9168        assert!(response.get("error").is_none(), "{response:#}");
9169        let receipt = &response["result"]["receipt"];
9170        assert_eq!(receipt["source_harness"], "codex");
9171        assert_eq!(receipt["target_harness"], "claude-code");
9172        assert_eq!(receipt["verified"], true);
9173        assert_eq!(receipt["reversible"], true);
9174        assert!(receipt["reductions"].as_u64().unwrap() > 0);
9175        assert!(
9176            receipt["source_tokens"].as_u64().unwrap()
9177                > receipt["reduced_tokens"].as_u64().unwrap()
9178        );
9179        assert!(receipt["ratio"].as_f64().unwrap() > 1.0);
9180        assert!(response["result"]["bootstrap_prompt"]
9181            .as_str()
9182            .unwrap()
9183            .contains("Do not guess hidden content"));
9184
9185        let rescue_id = receipt["id"].as_str().unwrap();
9186        let store = crate::SessionStore::open(&store_root).unwrap();
9187        let sidecar =
9188            Session::from_sidecar_str(&store.load_sidecar(rescue_id).unwrap().unwrap()).unwrap();
9189        let log = store.load_reduction_log(rescue_id).unwrap().unwrap();
9190        let persisted_view = parse_messages_jsonl(&store.load(rescue_id).unwrap()).unwrap();
9191        let policy = reduce::ReductionPolicy {
9192            clear_turns_older_than: Some(4),
9193            ..Default::default()
9194        };
9195        let (restamped_view, reapplied_log) =
9196            reduce::project_messages(&sidecar.messages, &policy, &log);
9197        assert_eq!(
9198            messages_jsonl(&persisted_view).unwrap(),
9199            messages_jsonl(&restamped_view).unwrap()
9200        );
9201        assert_eq!(reapplied_log, log);
9202        reduce::verify_log(&log, &sidecar).unwrap();
9203        assert_eq!(
9204            reduce::invert(&restamped_view, &log, &sidecar).unwrap(),
9205            original.messages
9206        );
9207        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), source);
9208
9209        std::fs::remove_dir_all(temp).ok();
9210    }
9211
9212    #[test]
9213    fn read_surfaces_view_a_severed_claude_graph_while_transfer_still_refuses_it() {
9214        let temp = std::env::temp_dir().join(format!(
9215            "supercode-severed-view-{}-{}",
9216            std::process::id(),
9217            generated_session_id()
9218        ));
9219        std::fs::create_dir_all(&temp).unwrap();
9220        let path = temp.join("severed.jsonl");
9221        // A live record whose parent was pruned — what a compacted or
9222        // resumed-across-files Claude Code session looks like on disk.
9223        std::fs::write(
9224            &path,
9225            concat!(
9226                r#"{"type":"user","uuid":"orphan-u","parentUuid":null,"message":{"role":"user","content":"stranded prompt"}}"#,
9227                "\n",
9228                r#"{"type":"assistant","uuid":"live-a","parentUuid":"pruned","message":{"id":"m","role":"assistant","content":[{"type":"text","text":"live answer"}]}}"#,
9229                "\n",
9230            ),
9231        )
9232        .unwrap();
9233        let locator = SessionLocator {
9234            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9235            session_id: "severed".into(),
9236            storage: StorageLocator::File { path },
9237        };
9238        let mut service = HarnessSessionService::new();
9239
9240        let viewed = service.handle(request(
9241            1,
9242            "harness.v1.sessions.load",
9243            json!({"locator": locator}),
9244        ));
9245        let session = &viewed["result"]["session"];
9246        assert_eq!(session["fidelity"], "semantic");
9247        assert_eq!(session["messages"].as_array().unwrap().len(), 2);
9248        assert!(session["residue"].as_array().unwrap().iter().any(|entry| {
9249            entry
9250                .as_str()
9251                .is_some_and(|entry| entry.contains("live-a") && entry.contains("pruned"))
9252        }));
9253
9254        // Asking a READ surface for a lossless reconstruction gets the strict
9255        // refusal back, unchanged.
9256        let strict = service.handle(request(
9257            2,
9258            "harness.v1.sessions.load",
9259            json!({"locator": locator, "fidelity": "byte_lossless"}),
9260        ));
9261        assert!(strict["error"]["message"]
9262            .as_str()
9263            .unwrap()
9264            .contains("cannot reconstruct lossless Claude continuation"));
9265
9266        // Transfer/continuation surfaces have no view mode at all.
9267        let translated = service.handle(request(
9268            3,
9269            "harness.v1.sessions.translate",
9270            json!({"locator": locator, "target_harness": "codex"}),
9271        ));
9272        assert!(translated["error"]["message"]
9273            .as_str()
9274            .unwrap()
9275            .contains("cannot reconstruct lossless Claude continuation"));
9276        let resumed = service.handle(request(
9277            4,
9278            "harness.v1.sessions.resume_instructions",
9279            json!({"locator": locator}),
9280        ));
9281        assert!(resumed["error"]["message"]
9282            .as_str()
9283            .unwrap()
9284            .contains("cannot reconstruct lossless Claude continuation"));
9285
9286        let _ = std::fs::remove_dir_all(&temp);
9287    }
9288
9289    #[test]
9290    fn structured_resume_launches_cover_gemini_goose_and_supercode() {
9291        let codex = resume_launch(
9292            HarnessId::CODEX,
9293            "codex-session",
9294            Path::new("/tmp/project"),
9295            ResumePolicy::Yolo,
9296        )
9297        .unwrap_or_else(|_| panic!("Codex resume launch must be registered"));
9298        assert_eq!(codex.program, "codex");
9299        assert_eq!(
9300            codex.arguments,
9301            [
9302                "-c",
9303                "check_for_update_on_startup=false",
9304                "-c",
9305                "projects.\"/tmp/project\".trust_level=\"trusted\"",
9306                "--dangerously-bypass-approvals-and-sandbox",
9307                "--dangerously-bypass-hook-trust",
9308                "resume",
9309                "codex-session",
9310            ]
9311        );
9312
9313        let gemini = resume_launch(
9314            HarnessId::GEMINI,
9315            "gemini-session",
9316            Path::new("/tmp/project"),
9317            ResumePolicy::Yolo,
9318        )
9319        .unwrap_or_else(|_| panic!("Gemini resume launch must be registered"));
9320        assert_eq!(gemini.program, "gemini");
9321        assert_eq!(gemini.arguments, ["--yolo", "--resume", "gemini-session"]);
9322
9323        let goose = resume_launch(
9324            HarnessId::GOOSE,
9325            "goose-session",
9326            Path::new("/tmp/project"),
9327            ResumePolicy::Yolo,
9328        )
9329        .unwrap_or_else(|_| panic!("Goose resume launch must be registered"));
9330        assert_eq!(goose.program, "goose");
9331        assert_eq!(
9332            goose.arguments,
9333            ["session", "--resume", "--session-id", "goose-session"]
9334        );
9335
9336        let supercode = resume_launch(
9337            HarnessId::SUPERCODE,
9338            "supercode-session",
9339            Path::new("/tmp/project"),
9340            ResumePolicy::Yolo,
9341        )
9342        .unwrap_or_else(|_| panic!("Supercode resume launch must be registered"));
9343        assert_eq!(supercode.program, "supercode");
9344        assert_eq!(
9345            supercode.arguments,
9346            ["--dangerous", "resume", "supercode-session"]
9347        );
9348    }
9349
9350    #[test]
9351    fn diagonal_artifacts_preserve_claude_subagents_and_grok_bundle_members() {
9352        let temp = std::env::temp_dir().join(format!(
9353            "supercode-harness-artifact-{}-{}",
9354            std::process::id(),
9355            generated_session_id()
9356        ));
9357        let main_path = temp.join("parent.jsonl");
9358        let subagent_path = temp.join("parent/subagents/agent-child.jsonl");
9359        std::fs::create_dir_all(subagent_path.parent().unwrap()).unwrap();
9360        let fixture = std::fs::read_to_string(
9361            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9362                .join("tests/fixtures/claude_code_session.jsonl"),
9363        )
9364        .unwrap();
9365        let parent = fixture.trim_end_matches('\n');
9366        let child = fixture.trim_end_matches('\n');
9367        std::fs::write(&main_path, parent).unwrap();
9368        std::fs::write(&subagent_path, child).unwrap();
9369        let locator = SessionLocator {
9370            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9371            session_id: "213bb148-51ea-453f-9206-f8b4b1168547".into(),
9372            storage: StorageLocator::File {
9373                path: main_path.clone(),
9374            },
9375        };
9376        let mut service = HarnessSessionService::new();
9377        let claude = service.handle(request(
9378            1,
9379            "harness.v1.sessions.translate",
9380            json!({"locator": locator, "target_harness": "claude-code"}),
9381        ));
9382        let artifact = &claude["result"]["artifact"];
9383        assert_eq!(artifact["fidelity"], "byte_lossless");
9384        assert_eq!(artifact["content"], parent);
9385        let files = artifact["files"].as_array().unwrap();
9386        assert!(files.iter().any(|file| {
9387            file["role"] == "subagent"
9388                && file["path"]
9389                    .as_str()
9390                    .is_some_and(|path| path.ends_with("/subagents/agent-child.jsonl"))
9391                && file["content"] == child
9392        }));
9393        assert!(!artifact["content"].as_str().unwrap().ends_with('\n'));
9394
9395        let grok = service.handle(request(
9396            2,
9397            "harness.v1.sessions.translate",
9398            json!({"locator": grok_locator(), "target_harness": "grok"}),
9399        ));
9400        let files = grok["result"]["artifact"]["files"].as_array().unwrap();
9401        for name in ["summary.json", "updates.jsonl"] {
9402            let expected = std::fs::read_to_string(
9403                PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9404                    .join("tests/fixtures/grok_session")
9405                    .join(name),
9406            )
9407            .unwrap();
9408            assert!(files.iter().any(|file| {
9409                file["path"] == name && file["role"] == "bundle" && file["content"] == expected
9410            }));
9411        }
9412        std::fs::remove_dir_all(temp).ok();
9413    }
9414
9415    #[test]
9416    fn every_non_grok_handoff_mints_and_uses_a_fresh_target_identity() {
9417        let mut service = HarnessSessionService::new();
9418        let source = pi_locator();
9419        for (target, format) in [
9420            ("claude-code", SessionFormat::ClaudeCode),
9421            ("codex", SessionFormat::Codex),
9422            ("opencode", SessionFormat::OpenCode),
9423            ("pi", SessionFormat::Pi),
9424        ] {
9425            let result = service.handle(request(
9426                1,
9427                "harness.v1.sessions.handoff",
9428                json!({"locator": source, "target_harness": target, "cwd": "/tmp/project"}),
9429            ));
9430            let artifact = &result["result"]["artifact"];
9431            let target_id = artifact["session_id"].as_str().unwrap();
9432            assert_ne!(target_id, source.session_id, "{target}");
9433            let parsed = Session::load_str(artifact["content"].as_str().unwrap(), format).unwrap();
9434            assert_eq!(
9435                parsed.meta.session_id.as_deref(),
9436                Some(target_id),
9437                "{target}"
9438            );
9439            if target != "pi" {
9440                assert!(result["result"]["launch"]["arguments"]
9441                    .as_array()
9442                    .unwrap()
9443                    .iter()
9444                    .any(|argument| argument == target_id));
9445            }
9446            if target == "opencode" {
9447                assert!(target_id.starts_with("ses_"));
9448                fn assert_session_ids(value: &Value, target_id: &str) {
9449                    match value {
9450                        Value::Object(fields) => {
9451                            if let Some(session_id) = fields.get("sessionID") {
9452                                assert_eq!(session_id, target_id);
9453                            }
9454                            for child in fields.values() {
9455                                assert_session_ids(child, target_id);
9456                            }
9457                        }
9458                        Value::Array(values) => {
9459                            for child in values {
9460                                assert_session_ids(child, target_id);
9461                            }
9462                        }
9463                        _ => {}
9464                    }
9465                }
9466                let document: Value =
9467                    serde_json::from_str(artifact["content"].as_str().unwrap()).unwrap();
9468                assert_session_ids(&document, target_id);
9469            }
9470        }
9471
9472        let first = service.handle(request(
9473            2,
9474            "harness.v1.sessions.handoff",
9475            json!({"locator": source, "target_harness": "codex"}),
9476        ));
9477        let second = service.handle(request(
9478            3,
9479            "harness.v1.sessions.handoff",
9480            json!({"locator": source, "target_harness": "codex"}),
9481        ));
9482        assert_ne!(
9483            first["result"]["artifact"]["session_id"],
9484            second["result"]["artifact"]["session_id"]
9485        );
9486    }
9487
9488    #[test]
9489    fn grok_handoff_materializes_through_the_core_door() {
9490        let mut service = HarnessSessionService::new();
9491        let source = opencode_locator();
9492        let response = service.handle(request(
9493            1,
9494            "harness.v1.sessions.handoff",
9495            json!({
9496                "locator": source,
9497                "target_harness": "grok",
9498                "cwd": "/tmp/grok-handoff-project",
9499            }),
9500        ));
9501        let result = &response["result"];
9502
9503        // Grok has no import command: the artifact is Grok's own transcript under a fresh
9504        // identity, and `harness.v1.sessions.materialize` writes its store entry.
9505        assert_eq!(result["artifact"]["target_harness"], "grok");
9506        let artifact = Session::load_str(
9507            result["artifact"]["content"].as_str().unwrap(),
9508            SessionFormat::Grok,
9509        )
9510        .unwrap();
9511        assert!(!artifact.messages.is_empty());
9512        let target_session_id = result["artifact"]["session_id"].as_str().unwrap();
9513        assert_eq!(target_session_id.len(), 36);
9514        assert_ne!(target_session_id, opencode_locator().session_id);
9515        assert!(result["materialize"].is_null());
9516        assert_eq!(
9517            result["launch"]["arguments"],
9518            json!(["--resume", "{materialized_session_id}"])
9519        );
9520        assert!(result["note"]
9521            .as_str()
9522            .unwrap()
9523            .contains("harness.v1.sessions.materialize"));
9524    }
9525
9526    #[tokio::test]
9527    async fn inventory_rejects_unknown_harnesses_and_runtime_attach_is_honest() {
9528        let mut service = HarnessSessionService::new();
9529        let inventory = service
9530            .handle_async(request(
9531                1,
9532                "harness.v1.harnesses.list",
9533                json!({"harnesses": ["missing"]}),
9534            ))
9535            .await;
9536        assert_eq!(inventory["error"]["code"], -32602);
9537
9538        let attached = service
9539            .handle_async(request(
9540                2,
9541                "harness.v1.runtimes.attach_existing",
9542                json!({"harness": "codex", "runtime_id": "thread-1"}),
9543            ))
9544            .await;
9545        assert_eq!(attached["error"]["code"], -32000);
9546        assert!(attached["error"]["message"]
9547            .as_str()
9548            .unwrap()
9549            .contains("runtimes.resume"));
9550    }
9551
9552    #[test]
9553    fn invalid_params_and_unknown_methods_use_json_rpc_errors() {
9554        let mut service = HarnessSessionService::new();
9555        let invalid = service.handle(request(1, "harness.v1.sessions.load", json!({})));
9556        assert_eq!(invalid["error"]["code"], -32602);
9557        let unknown = service.handle(request(2, "harness.v1.unknown", json!({})));
9558        assert_eq!(unknown["error"]["code"], -32601);
9559    }
9560
9561    #[cfg(unix)]
9562    #[tokio::test]
9563    // The test mutates process-wide harness environment and deliberately
9564    // holds the global test lock until every async runtime operation ends.
9565    #[allow(clippy::await_holding_lock)]
9566    async fn async_service_drives_a_generic_acp_runtime() {
9567        let _environment_guard = crate::live_runtime::test_environment_lock();
9568        let script = r#"
9569            i=0
9570            while IFS= read -r line; do
9571              i=$((i + 1))
9572              case "$i" in
9573                1) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{},"authMethods":[]}}' ;;
9574                2) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"svc_acp"}}' ;;
9575                3)
9576                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ok"}}}}'
9577                  printf '%s\n' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}'
9578                  ;;
9579                4)
9580                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"from terminal"}}}}'
9581                  printf '%s\n' '{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}'
9582                  ;;
9583              esac
9584            done
9585        "#;
9586        let mut service = HarnessSessionService::new();
9587        let started = service
9588            .handle_async(request(
9589                1,
9590                "harness.v1.runtimes.start",
9591                json!({
9592                    "harness": "codex",
9593                    "protocol": "acp",
9594                    "cwd": std::env::current_dir().unwrap(),
9595                    "launch": {"program": "/bin/sh", "arguments": ["-c", script], "env": {}},
9596                }),
9597            ))
9598            .await;
9599        assert_eq!(started["result"]["connection"], "runtime-1");
9600        assert_eq!(started["result"]["handle"]["runtime_id"], "svc_acp");
9601
9602        let terminal = service
9603            .handle_async(request(
9604                9,
9605                "harness.v1.runtimes.terminal_instructions",
9606                json!({"connection":"runtime-1"}),
9607            ))
9608            .await;
9609        let arguments = terminal["result"]["launch"]["arguments"]
9610            .as_array()
9611            .expect("hosted runtime should return terminal arguments");
9612        let endpoint_index = arguments
9613            .iter()
9614            .position(|value| value == "--endpoint")
9615            .expect("terminal command should use an opaque endpoint");
9616        let endpoint = LiveRuntimeEndpoint::parse(
9617            arguments[endpoint_index + 1]
9618                .as_str()
9619                .expect("endpoint argument should be text"),
9620        )
9621        .unwrap();
9622        assert!(!terminal.to_string().contains("Bearer"));
9623        let workspace = std::env::current_dir().unwrap();
9624        let receipt = resolve_live_runtime(
9625            &endpoint,
9626            &LiveRuntimeSource {
9627                harness: "codex".into(),
9628                session_id: "svc_acp".into(),
9629                workspace,
9630            },
9631        )
9632        .unwrap();
9633        let remote = crate::HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
9634            .await
9635            .unwrap();
9636        let mut attachment = crate::FrontendRuntime::attach(remote.as_ref(), 100)
9637            .await
9638            .unwrap();
9639
9640        let sent = service
9641            .handle_async(request(
9642                2,
9643                "harness.v1.runtimes.send_input",
9644                json!({"connection": "runtime-1", "text": "hi"}),
9645            ))
9646            .await;
9647        assert_eq!(sent["result"]["turn_id"], "3");
9648
9649        let mut events = Vec::new();
9650        for _ in 0..20 {
9651            events.extend(service.poll_runtimes().await);
9652            if events.len() >= 2 {
9653                break;
9654            }
9655            tokio::time::sleep(Duration::from_millis(2)).await;
9656        }
9657        assert!(events
9658            .iter()
9659            .any(|event| { event["params"]["event"]["kind"] == "session/update" }));
9660        assert!(events.iter().any(|event| {
9661            event["params"]["event"]["kind"] == "supercode/acp_request_completed"
9662        }));
9663
9664        let saw_editor_reply = tokio::time::timeout(Duration::from_secs(2), async {
9665            loop {
9666                let event = attachment.next_event().await.unwrap();
9667                if event.kind == "text_delta" && event.payload["text"] == "ok" {
9668                    break;
9669                }
9670            }
9671        })
9672        .await;
9673        assert!(
9674            saw_editor_reply.is_ok(),
9675            "terminal should observe the editor-driven turn"
9676        );
9677
9678        crate::FrontendRuntime::submit(remote.as_ref(), "DRIVE FROM TERMINAL".into())
9679            .await
9680            .unwrap();
9681        let saw_terminal_reply = tokio::time::timeout(Duration::from_secs(2), async {
9682            loop {
9683                let event = attachment.next_event().await.unwrap();
9684                if event.kind == "text_delta" && event.payload["text"] == "from terminal" {
9685                    break;
9686                }
9687            }
9688        })
9689        .await;
9690        assert!(
9691            saw_terminal_reply.is_ok(),
9692            "terminal should drive the same runtime"
9693        );
9694
9695        let closed = service
9696            .handle_async(request(
9697                3,
9698                "harness.v1.runtimes.close",
9699                json!({"connection": "runtime-1"}),
9700            ))
9701            .await;
9702        assert_eq!(closed["result"]["closed"], true);
9703    }
9704
9705    /// UNI-7 dev/02: a RUNNING mock gateway is detected through the real
9706    /// openclaw probe (config-declared endpoint, TCP connect), and an ACTIVE
9707    /// hermes WAL is detected through the real WAL-freshness probe; the
9708    /// negative sides (no listener, stale WAL, no config) stay undetected.
9709    #[test]
9710    fn running_instances_are_detected_from_mock_gateway_and_active_wal() {
9711        let home = connect_scratch_home("uni7-running");
9712
9713        // No config at all: hermes has no default endpoint, so no detection.
9714        // (openclaw's no-config behavior now probes its DOCUMENTED default
9715        // endpoint ws://127.0.0.1:18789 — see the connect launch's
9716        // `default_address` — which is real box state a hermetic test must
9717        // not assert either way; the closed-port negative below covers the
9718        // no-listener side deterministically.)
9719        assert!(probe_hermes_running(&home, 300_000).is_none());
9720
9721        // Mock gateway: a real TCP listener on an ephemeral port, declared in
9722        // the harness's own config file.
9723        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9724        let port = listener.local_addr().unwrap().port();
9725        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9726        std::fs::write(
9727            home.join(".openclaw/openclaw.json"),
9728            format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9729        )
9730        .unwrap();
9731        let running = probe_openclaw_running(&home).expect("listening gateway must be detected");
9732        assert!(matches!(
9733            running.method,
9734            RunningInstanceMethod::GatewayConnect
9735        ));
9736        assert!(running.evidence.contains(&format!("127.0.0.1:{port}")));
9737        drop(listener);
9738        // Parallel tests also bind ephemeral loopback ports, so a just-freed
9739        // port can be re-bound by a NEIGHBORING test between drop and probe.
9740        // Detection on a closed port must fail — retry on a fresh port when
9741        // the freed one was recycled by someone else.
9742        let mut closed_detected = probe_openclaw_running(&home).is_some();
9743        for _ in 0..3 {
9744            if !closed_detected {
9745                break;
9746            }
9747            let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9748            let port = listener.local_addr().unwrap().port();
9749            drop(listener);
9750            std::fs::write(
9751                home.join(".openclaw/openclaw.json"),
9752                format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9753            )
9754            .unwrap();
9755            closed_detected = probe_openclaw_running(&home).is_some();
9756        }
9757        assert!(
9758            !closed_detected,
9759            "a closed gateway must not read as running"
9760        );
9761
9762        // gateway.url form takes precedence over port.
9763        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9764        let port = listener.local_addr().unwrap().port();
9765        std::fs::write(
9766            home.join(".openclaw/openclaw.json"),
9767            format!(r#"{{"gateway": {{"url": "ws://127.0.0.1:{port}", "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9768        )
9769        .unwrap();
9770        assert!(probe_openclaw_running(&home).is_some());
9771        drop(listener);
9772
9773        // Hermes: an ACTIVE WAL (fresh stamp) is detected; a stale one is not.
9774        std::fs::create_dir_all(home.join(".hermes")).unwrap();
9775        let wal = home.join(".hermes/state.db-wal");
9776        std::fs::write(&wal, b"wal").unwrap();
9777        let running = probe_hermes_running(&home, 300_000).expect("fresh WAL must be detected");
9778        assert!(matches!(
9779            running.method,
9780            RunningInstanceMethod::StoreWalActivity
9781        ));
9782        assert!(running.evidence.contains("state.db-wal"));
9783        let stale = std::time::SystemTime::now() - std::time::Duration::from_secs(3_600);
9784        std::fs::File::options()
9785            .append(true)
9786            .open(&wal)
9787            .unwrap()
9788            .set_modified(stale)
9789            .unwrap();
9790        assert!(
9791            probe_hermes_running(&home, 300_000).is_none(),
9792            "a stale WAL (crash leftover) must not read as running"
9793        );
9794    }
9795
9796    fn connect_scratch_home(tag: &str) -> PathBuf {
9797        let dir = std::env::temp_dir().join(format!(
9798            "supercode-connect-service-{tag}-{}-{}",
9799            std::process::id(),
9800            std::time::SystemTime::now()
9801                .duration_since(std::time::UNIX_EPOCH)
9802                .unwrap()
9803                .as_nanos()
9804        ));
9805        std::fs::create_dir_all(&dir).unwrap();
9806        dir
9807    }
9808
9809    /// Minimal HTTP responder that speaks just enough OpenCode server to
9810    /// accept a health check, create a session, and hold an SSE stream open,
9811    /// while recording each request line with its Authorization header.
9812    async fn mock_opencode_endpoint() -> (String, tokio::sync::mpsc::UnboundedReceiver<String>) {
9813        use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
9814        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
9815        let address = listener.local_addr().unwrap();
9816        let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel();
9817        tokio::spawn(async move {
9818            loop {
9819                let Ok((mut stream, _)) = listener.accept().await else {
9820                    break;
9821                };
9822                let request_sender = request_sender.clone();
9823                tokio::spawn(async move {
9824                    let (reader, mut writer) = stream.split();
9825                    let mut reader = BufReader::new(reader);
9826                    let mut request_line = String::new();
9827                    if reader.read_line(&mut request_line).await.unwrap_or(0) == 0 {
9828                        return;
9829                    }
9830                    let request_line = request_line.trim_end().to_string();
9831                    let mut authorization = String::new();
9832                    let mut content_length = 0usize;
9833                    loop {
9834                        let mut line = String::new();
9835                        if reader.read_line(&mut line).await.unwrap_or(0) == 0 {
9836                            return;
9837                        }
9838                        let line = line.trim_end();
9839                        if line.is_empty() {
9840                            break;
9841                        }
9842                        let lower = line.to_ascii_lowercase();
9843                        if let Some(value) = lower.strip_prefix("authorization:") {
9844                            authorization = value.trim().to_string();
9845                        }
9846                        if let Some(value) = lower.strip_prefix("content-length:") {
9847                            content_length = value.trim().parse().unwrap_or(0);
9848                        }
9849                    }
9850                    if content_length > 0 {
9851                        let mut body = vec![0u8; content_length];
9852                        let _ = reader.read_exact(&mut body).await;
9853                    }
9854                    let _ = request_sender.send(format!("{request_line} :: {authorization}"));
9855                    if request_line.starts_with("GET /event") {
9856                        let _ = writer
9857                            .write_all(
9858                                b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n",
9859                            )
9860                            .await;
9861                        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
9862                        return;
9863                    }
9864                    let body = if request_line.starts_with("POST /session") {
9865                        r#"{"id":"mock-session"}"#
9866                    } else {
9867                        r#"{"status":"ok"}"#
9868                    };
9869                    let response = format!(
9870                        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
9871                        body.len(),
9872                        body
9873                    );
9874                    let _ = writer.write_all(response.as_bytes()).await;
9875                });
9876            }
9877        });
9878        (format!("http://{address}"), request_receiver)
9879    }
9880
9881    fn connect_descriptor(protocol: &str) -> crate::HarnessSupportDescriptor {
9882        crate::HarnessSupportDescriptor {
9883            orchestration: Default::default(),
9884            id: HarnessId::from(HarnessId::OPENCODE),
9885            display_name: "OpenCode".into(),
9886            native: crate::NativeSupport {
9887                discover: crate::ImplementationKind::Absent,
9888                load: crate::ImplementationKind::Absent,
9889                follow: crate::ImplementationKind::Absent,
9890                import: crate::ImplementationKind::Absent,
9891                export: crate::ImplementationKind::Absent,
9892            },
9893            runtime: crate::RuntimeSupport {
9894                implementation: crate::ImplementationKind::BuiltIn,
9895                protocol: protocol.into(),
9896                default_launch: None,
9897                connect_launch: Some(crate::RuntimeConnectLaunch {
9898                    config_path: "~/opencode-tui.json".into(),
9899                    address_pointer: "/server/url".into(),
9900                    port_pointer: None,
9901                    default_address: None,
9902                    auth_pointer: Some("/server/token".into()),
9903                    protocol: protocol.into(),
9904                }),
9905                capabilities: crate::RuntimeCapabilities {
9906                    start_session: true,
9907                    resume_session: true,
9908                    attach_existing_process: true,
9909                    send_input: true,
9910                    stream_events: true,
9911                    interrupt: true,
9912                    steer: false,
9913                    respond_to_requests: true,
9914                },
9915            },
9916        }
9917    }
9918
9919    #[tokio::test]
9920    async fn connect_mode_descriptor_opens_a_running_endpoint_with_config_sourced_auth() {
9921        let (base_url, mut requests) = mock_opencode_endpoint().await;
9922        let home = connect_scratch_home("open");
9923        std::fs::write(
9924            home.join("opencode-tui.json"),
9925            format!(r#"{{"server": {{"url": "{base_url}", "token": "connect-secret"}}}}"#),
9926        )
9927        .unwrap();
9928
9929        let descriptor = connect_descriptor("opencode-http-sse");
9930        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
9931        assert!(backend.capabilities().attach_existing_process);
9932
9933        let connection = backend
9934            .start(crate::RuntimeStartRequest {
9935                cwd: home.clone(),
9936                launch: None,
9937                mcp_servers: Vec::new(),
9938            })
9939            .await
9940            .unwrap();
9941        let handle = connection.handle();
9942        assert_eq!(handle.runtime_id, "mock-session");
9943        match &handle.endpoint {
9944            crate::RuntimeEndpoint::Http {
9945                base_url: endpoint, ..
9946            } => assert_eq!(endpoint, &base_url),
9947            other => panic!("connect mode must join the running endpoint, got {other:?}"),
9948        }
9949
9950        let mut seen = Vec::new();
9951        while let Ok(line) = requests.try_recv() {
9952            seen.push(line);
9953        }
9954        assert!(seen
9955            .iter()
9956            .any(|line| line.starts_with("GET /global/health")
9957                && line.contains("bearer connect-secret")));
9958        assert!(seen.iter().any(
9959            |line| line.starts_with("POST /session") && line.contains("bearer connect-secret")
9960        ));
9961    }
9962
9963    /// UNI-5 dev/02, contract corrected by the 2026-08-31 blind walk: the
9964    /// full connect-mode attach path against a MOCK gateway bridge — no live
9965    /// gateway, no model spend. A scripted fake `openclaw` binary (a)
9966    /// asserts the REAL bridge contract — the resolved --url on argv and the
9967    /// credential via --token-file (the real bridge ignores the env var; the
9968    /// endpoint comes from openclaw-native `gateway.remote.url`, never the
9969    /// schema-invalid `gateway.url`) — then (b) speaks scripted ACP:
9970    /// initialize advertising sessionCapabilities.{list,resume},
9971    /// session/resume rebinding the requested session (join), and a
9972    /// prompted turn.
9973    #[tokio::test]
9974    async fn openclaw_connect_mode_attaches_lists_and_resumes_via_a_mock_bridge() {
9975        let home = connect_scratch_home("openclaw");
9976        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9977        std::fs::write(
9978            home.join(".openclaw/openclaw.json"),
9979            r#"{"gateway": {"remote": {"url": "ws://127.0.0.1:19789"}, "auth": {"mode": "token", "token": "mock-gateway-token"}}}"#,
9980        )
9981        .unwrap();
9982        let script = home.join("openclaw");
9983        std::fs::write(
9984            &script,
9985            r#"#!/bin/sh
9986# Fake `openclaw acp` bridge: verify the connect-mode contract, then speak ACP.
9987[ "$1" = "acp" ] || { echo "unexpected argv: $*" >&2; exit 9; }
9988[ "$2" = "--url" ] && [ "$3" = "ws://127.0.0.1:19789" ] || { echo "missing --url: $*" >&2; exit 9; }
9989[ "$4" = "--token-file" ] || { echo "missing --token-file: $*" >&2; exit 9; }
9990[ "$(cat "$5")" = "mock-gateway-token" ] || { echo "token file wrong" >&2; exit 9; }
9991while IFS= read -r line; do
9992  case "$line" in
9993    *'"initialize"'*)
9994      printf '%s
9995' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"sessionCapabilities":{"list":{},"resume":{}}},"agentInfo":{"name":"openclaw-acp","version":"2026.7.1-2"},"authMethods":[]}}' ;;
9996    *'"session/resume"'*)
9997      printf '%s
9998' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:main"}}' ;;
9999    *'"session/new"'*)
10000      printf '%s
10001' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:fresh"}}' ;;
10002    *'"session/prompt"'*)
10003      printf '%s
10004' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"agent:main:main","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"joined"}}}}'
10005      printf '%s
10006' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}' ;;
10007  esac
10008done
10009"#,
10010        )
10011        .unwrap();
10012        use std::os::unix::fs::PermissionsExt;
10013        std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
10014
10015        let mut descriptor = crate::harness_support_registry()
10016            .harnesses
10017            .into_iter()
10018            .find(|harness| harness.id.as_str() == HarnessId::OPENCLAW)
10019            .expect("openclaw must be registered");
10020        descriptor
10021            .runtime
10022            .connect_launch
10023            .as_mut()
10024            .unwrap()
10025            .config_path = "~/.openclaw/openclaw.json".into();
10026        descriptor.runtime.default_launch.as_mut().unwrap().program =
10027            script.to_string_lossy().into_owned();
10028        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
10029        assert!(backend.capabilities().resume_session);
10030
10031        let joined = backend
10032            .attach(crate::RuntimeAttachRequest {
10033                runtime_id: "agent:main:main".into(),
10034                cwd: Some(home.clone()),
10035                launch: None,
10036                mcp_servers: Vec::new(),
10037            })
10038            .await;
10039        let mut connection = joined.expect("mock bridge attach must succeed");
10040        assert_eq!(connection.handle().runtime_id, "agent:main:main");
10041        let turn = connection
10042            .send_input(crate::RuntimeInput {
10043                text: "hello".into(),
10044                image_urls: Vec::new(),
10045            })
10046            .await;
10047        assert!(turn.is_ok(), "prompt through the mock bridge: {turn:?}");
10048        connection.close().await.unwrap();
10049    }
10050
10051    #[tokio::test]
10052    async fn connect_mode_fails_closed_without_a_protocol_client_or_config() {
10053        let home = connect_scratch_home("fail");
10054        std::fs::write(
10055            home.join("opencode-tui.json"),
10056            r#"{"server": {"url": "http://127.0.0.1:1", "token": "connect-secret"}}"#,
10057        )
10058        .unwrap();
10059
10060        let gateway_only = connect_descriptor("acp-v1-jsonrpc");
10061        let Err(error) = open_connect_descriptor(&gateway_only, &home) else {
10062            panic!("an ACP connect endpoint has no gateway client yet");
10063        };
10064        let message = format!("{error:?}");
10065        assert!(message.contains("acp-v1-jsonrpc"));
10066        assert!(!message.contains("connect-secret"));
10067
10068        let unreadable = connect_descriptor("opencode-http-sse");
10069        let missing_home = connect_scratch_home("missing");
10070        let Err(error) = open_connect_descriptor(&unreadable, &missing_home) else {
10071            panic!("an unreadable connect config must fail closed");
10072        };
10073        let message = format!("{error:?}");
10074        assert!(message.contains("opencode-tui.json"));
10075        assert!(!message.contains("connect-secret"));
10076    }
10077
10078    // ---------------------------------------------------------------------
10079    // ORCH-7 — `harness.v1.jobs.list` / `jobs.get` over the committed fixtures
10080    // ---------------------------------------------------------------------
10081
10082    fn jobs_fixture_root() -> PathBuf {
10083        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
10084    }
10085
10086    /// Point only the three job-bearing homes at the fixtures. Nothing else is
10087    /// read, so the host machine's own harness homes cannot leak into a row.
10088    fn jobs_fixture_homes() -> Value {
10089        let root = jobs_fixture_root();
10090        json!({
10091            "claude_code": root.join("claude_jobs_home/projects"),
10092            "hermes": root.join("hermes_home/state.db"),
10093            "openclaw": root.join("openclaw_home"),
10094        })
10095    }
10096
10097    fn jobs_list(params: Value) -> Value {
10098        let mut service = HarnessSessionService::new();
10099        service.handle(request(1, "harness.v1.jobs.list", params))
10100    }
10101
10102    fn job_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10103        result["jobs"]
10104            .as_array()
10105            .expect("jobs is an array")
10106            .iter()
10107            .find(|job| job["id"] == id)
10108            .unwrap_or_else(|| panic!("no job `{id}` in {result}"))
10109    }
10110
10111    #[test]
10112    fn gateway_health_derives_from_running_probe_and_install_state() {
10113        let running = RunningInstance {
10114            method: RunningInstanceMethod::GatewayConnect,
10115            evidence: "gateway endpoint 127.0.0.1:18789 accepted a TCP connect".into(),
10116            checked_at_ms: 1,
10117        };
10118        let up = gateway_health(
10119            HarnessId::OPENCLAW,
10120            true,
10121            Some(&running),
10122            Some("2026.7.1-2"),
10123        );
10124        assert_eq!(up.state, GatewayState::Up);
10125        assert!(up.endpoint.as_deref().unwrap().starts_with("ws://"));
10126        assert_eq!(up.version.as_deref(), Some("2026.7.1-2"));
10127        // Hermes consults its own `gateway status` when the WAL heuristic says
10128        // nothing; a fake binary decides the verdict (the env var is global, so
10129        // the up/down cases run inside this one test, never in parallel).
10130        let dir = std::env::temp_dir().join(format!("supercode-orch17-{}", std::process::id()));
10131        std::fs::create_dir_all(&dir).unwrap();
10132        let fake = dir.join("hermes");
10133        let write_fake = |body: &str| {
10134            std::fs::write(&fake, format!("#!/bin/sh\n{body}\n")).unwrap();
10135            #[cfg(unix)]
10136            {
10137                use std::os::unix::fs::PermissionsExt;
10138                std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o755)).unwrap();
10139            }
10140        };
10141        write_fake("echo '✗ Gateway service is not installed'");
10142        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| {
10143            *slot.borrow_mut() = Some((
10144                HarnessId::HERMES.to_string(),
10145                fake.to_string_lossy().into_owned(),
10146            ))
10147        });
10148        let down = gateway_health(HarnessId::HERMES, true, None, None);
10149        assert_eq!(down.state, GatewayState::Down, "{down:?}");
10150        assert!(down.endpoint.is_none());
10151        assert!(down.evidence.contains("not installed"));
10152        write_fake("echo 'Launchd plist: /x/ai.hermes.gateway.plist'; echo '✓ Gateway is supervised by launchd (PID 4242)'");
10153        let idle_but_up = gateway_health(HarnessId::HERMES, true, None, Some("0.21.0"));
10154        assert_eq!(idle_but_up.state, GatewayState::Up, "{idle_but_up:?}");
10155        assert!(idle_but_up.evidence.contains("PID 4242"));
10156        write_fake("echo 'something unparseable'");
10157        let no_verdict = gateway_health(HarnessId::HERMES, true, None, None);
10158        assert_eq!(no_verdict.state, GatewayState::Down);
10159        assert!(no_verdict.evidence.contains("no verdict"));
10160        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| *slot.borrow_mut() = None);
10161        let absent = gateway_health(HarnessId::HERMES, false, None, None);
10162        assert_eq!(absent.state, GatewayState::Unknown);
10163        let core = gateway_health(HarnessId::CODEX, true, None, Some("0.144.4"));
10164        assert_eq!(core.state, GatewayState::Unknown);
10165        assert!(core.evidence.contains("per session"));
10166    }
10167
10168    #[test]
10169    fn triggers_list_reads_both_stores_and_never_emits_secrets() {
10170        let response = triggers_list(json!({"homes": jobs_fixture_homes()}));
10171        let rows = response["result"]["triggers"]
10172            .as_array()
10173            .expect("triggers")
10174            .clone();
10175        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10176        assert!(
10177            hermes.iter().any(|r| r["name"] == "deploys"
10178                && r["route"] == "/webhooks/deploys"
10179                && r["kind"] == "webhook"),
10180            "{rows:#?}"
10181        );
10182        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10183        assert!(openclaw
10184            .iter()
10185            .any(|r| r["name"] == "wake" && r["kind"] == "builtin_wake"));
10186        assert!(openclaw.iter().any(|r| r["name"] == "gmail"
10187            && r["kind"] == "hook_mapping"
10188            && r["target"]["action"] == "agent"));
10189        let rendered = response.to_string();
10190        for secret in [
10191            "FAKE-WEBHOOK-HMAC-DO-NOT-EMIT",
10192            "FAKE-HOOK-TOKEN-DO-NOT-EMIT",
10193        ] {
10194            assert!(!rendered.contains(secret), "{rendered}");
10195        }
10196        let refused =
10197            triggers_list(json!({"harness": "claude-code", "homes": jobs_fixture_homes()}));
10198        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10199    }
10200
10201    fn triggers_list(params: Value) -> Value {
10202        let mut service = HarnessSessionService::new();
10203        service.handle(request(1, "harness.v1.triggers.list", params))
10204    }
10205
10206    #[test]
10207    fn routes_list_reads_both_gateway_configs_and_flags_the_defaults() {
10208        let response = routes_list(json!({"homes": jobs_fixture_homes()}));
10209        let rows = response["result"]["routes"]
10210            .as_array()
10211            .expect("routes")
10212            .clone();
10213        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10214        assert_eq!(hermes.len(), 2, "{rows:#?}");
10215        assert_eq!(hermes[0]["target"], "coder");
10216        assert_eq!(hermes[0]["match"]["platform"], "slack");
10217        assert_eq!(hermes[0]["match"]["chat_id"], "C0FIXTURE");
10218        assert_eq!(hermes[0]["specificity"], 4);
10219        assert_eq!(hermes[1]["default"], true);
10220        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10221        assert!(
10222            openclaw.iter().any(|r| r["target"] == "design"
10223                && r["match"]["platform"] == "slack"
10224                && r["specificity"] == 1),
10225            "{openclaw:#?}"
10226        );
10227        assert!(openclaw.iter().any(|r| r["default"] == true));
10228        // A core harness has no routing concept and is refused, never an empty list.
10229        let refused = routes_list(json!({"harness": "codex", "homes": jobs_fixture_homes()}));
10230        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10231    }
10232
10233    fn routes_list(params: Value) -> Value {
10234        let mut service = HarnessSessionService::new();
10235        service.handle(request(1, "harness.v1.routes.list", params))
10236    }
10237
10238    #[test]
10239    fn jobs_list_projects_every_fixture_store_onto_the_uniform_row() {
10240        let response = jobs_list(json!({"homes": jobs_fixture_homes()}));
10241        let result = &response["result"];
10242        let ids: Vec<&str> = result["jobs"]
10243            .as_array()
10244            .unwrap()
10245            .iter()
10246            .map(|job| job["id"].as_str().unwrap())
10247            .collect();
10248        assert_eq!(
10249            ids,
10250            vec![
10251                "release-watch",
10252                "toolu_wake_recheck",
10253                "digest-15m",
10254                "nightly-audit",
10255                "coder-standup",
10256                "ops-once-boot",
10257                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10258                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10259                "cron_standup",
10260                "cron_reindex",
10261            ],
10262            "{result}"
10263        );
10264
10265        // OpenClaw, pinned shape: rows come from `state/openclaw.sqlite`
10266        // (`cron_jobs.job_json` + runtime columns), captured from a real
10267        // 2026.7.1-2 gateway.
10268        let health = job_row(result, "85ad7832-896f-42be-af31-3e1ed2fbdc4b");
10269        assert_eq!(health["harness"], "openclaw");
10270        assert_eq!(health["schedule"]["kind"], "interval");
10271        assert_eq!(health["schedule"]["minutes"], 10.0);
10272        assert_eq!(health["session_target"], "isolated");
10273        assert_eq!(health["payload"]["kind"], "prompt");
10274        assert_eq!(health["payload"]["text"], "nightly health check");
10275        // ORCH-13: the mode word (`announce`) and the channel it announces on
10276        // (`last`) are separate facts, and the store keeps both — in
10277        // `job_json.delivery` and in the `delivery_*` columns beside it.
10278        assert_eq!(health["deliver"]["mode"], "announce");
10279        assert_eq!(health["deliver"]["target"], "last");
10280        assert_eq!(health["next_run_at"], "2026-09-03T06:52:26Z");
10281        let digest = job_row(result, "8bb7d938-ca46-4a6d-90eb-c92331155566");
10282        assert_eq!(digest["schedule"]["kind"], "cron");
10283        assert_eq!(digest["schedule"]["expr"], "0 9 * * 1");
10284        assert_eq!(digest["session_target"], "main");
10285        assert_eq!(digest["payload"]["kind"], "system_event");
10286
10287        // Claude Code: session-scoped, one recurring cron and one one-shot wakeup.
10288        let cron = job_row(result, "release-watch");
10289        assert_eq!(cron["harness"], "claude-code");
10290        assert_eq!(cron["scope"], "session");
10291        assert_eq!(cron["session_id"], "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
10292        assert_eq!(cron["schedule"]["kind"], "cron");
10293        assert_eq!(cron["schedule"]["expr"], "*/10 * * * *");
10294        assert_eq!(cron["schedule"]["display"], "*/10 * * * *");
10295        assert_eq!(cron["payload"]["kind"], "prompt");
10296        assert_eq!(cron["recurring"], true);
10297        assert_eq!(cron["deliver"]["target"], "session");
10298        let wakeup = job_row(result, "toolu_wake_recheck");
10299        assert_eq!(wakeup["payload"]["kind"], "wakeup");
10300        assert_eq!(wakeup["schedule"]["kind"], "once");
10301        assert_eq!(wakeup["recurring"], false);
10302        assert_eq!(wakeup["state"], "pending");
10303
10304        // Hermes: install-scoped, interval + origin delivery, and a paused cron.
10305        let interval = job_row(result, "digest-15m");
10306        assert_eq!(interval["harness"], "hermes");
10307        assert_eq!(interval["scope"], "install");
10308        assert_eq!(interval["profile"], Value::Null);
10309        assert_eq!(interval["schedule"]["kind"], "interval");
10310        assert_eq!(interval["schedule"]["minutes"], 15.0);
10311        assert_eq!(interval["schedule"]["display"], "every 15 min");
10312        assert_eq!(interval["deliver"]["target"], "origin");
10313        assert_eq!(interval["deliver"]["chat_id"], "-1002233445566");
10314        assert_eq!(interval["next_run_at"], "2026-09-02T11:15:00Z");
10315        assert_eq!(interval["last_status"], "ok");
10316        let nightly = job_row(result, "nightly-audit");
10317        assert_eq!(nightly["schedule"]["expr"], "0 3 * * *");
10318        assert_eq!(nightly["deliver"]["target"], "local");
10319        assert_eq!(nightly["enabled"], false);
10320        assert_eq!(nightly["state"], "paused");
10321        // The per-profile store carries the profile name from its own path.
10322        let profiled = job_row(result, "ops-once-boot");
10323        assert_eq!(profiled["profile"], "ops");
10324        assert_eq!(profiled["schedule"]["kind"], "once");
10325        assert_eq!(profiled["schedule"]["run_at"], "2026-09-03T06:00:00Z");
10326        assert_eq!(profiled["payload"]["kind"], "script");
10327        // An explicit `<platform>:<chat>` target carries the chat itself.
10328        assert_eq!(profiled["deliver"]["target"], "slack:C0429ABCD");
10329        assert_eq!(profiled["deliver"]["chat_id"], "C0429ABCD");
10330        assert_eq!(profiled["recurring"], false);
10331
10332        // ORCH-13: a job delivering to its creating conversation carries that
10333        // conversation's whole surface — platform word, chat AND thread.
10334        let standup_to_group = job_row(result, "coder-standup");
10335        assert_eq!(standup_to_group["deliver"]["target"], "origin");
10336        assert_eq!(standup_to_group["deliver"]["chat_id"], "-100777");
10337        assert_eq!(standup_to_group["deliver"]["thread_id"], "55");
10338        // Hermes has no mode word and routes by adapter profile, not account.
10339        assert!(standup_to_group["deliver"]["mode"].is_null());
10340        assert!(standup_to_group["deliver"]["account"].is_null());
10341
10342        // OpenClaw: the session target and the delivery mode are the row's own
10343        // columns, not a footnote.
10344        let standup = job_row(result, "cron_standup");
10345        assert_eq!(standup["harness"], "openclaw");
10346        assert_eq!(standup["session_target"], "isolated");
10347        assert_eq!(standup["deliver"]["mode"], "announce");
10348        assert_eq!(standup["deliver"]["target"], "slack");
10349        assert_eq!(standup["deliver"]["chat_id"], "C0429ABCD");
10350        assert_eq!(standup["payload"]["kind"], "prompt");
10351        assert_eq!(standup["profile"], "main");
10352        let reindex = job_row(result, "cron_reindex");
10353        assert_eq!(reindex["session_target"], "main");
10354        assert_eq!(reindex["payload"]["kind"], "system_event");
10355        assert_eq!(reindex["schedule"]["kind"], "interval");
10356        assert_eq!(reindex["schedule"]["display"], "every 240 min");
10357        assert_eq!(reindex["enabled"], false);
10358
10359        // Every store consulted is named, so an empty answer is never silent.
10360        let states: Vec<(&str, &str)> = result["sources"]
10361            .as_array()
10362            .unwrap()
10363            .iter()
10364            .map(|source| {
10365                (
10366                    source["harness"].as_str().unwrap(),
10367                    source["state"].as_str().unwrap(),
10368                )
10369            })
10370            .collect();
10371        // The `coder` profile home has no cron store at all: it is named as
10372        // `absent_store`, not skipped, so "this profile schedules nothing" and
10373        // "this profile was never looked at" stay distinguishable.
10374        assert_eq!(
10375            states,
10376            vec![
10377                ("claude-code", "scanned"),
10378                ("hermes", "read"),
10379                ("hermes", "absent_store"),
10380                ("hermes", "read"),
10381                ("openclaw", "read"),
10382                ("openclaw", "read"),
10383            ],
10384            "{result}"
10385        );
10386    }
10387
10388    #[test]
10389    fn jobs_list_filters_by_harness_session_and_profile() {
10390        let by_harness = jobs_list(json!({"harness": "openclaw", "homes": jobs_fixture_homes()}));
10391        let ids: Vec<&str> = by_harness["result"]["jobs"]
10392            .as_array()
10393            .unwrap()
10394            .iter()
10395            .map(|job| job["id"].as_str().unwrap())
10396            .collect();
10397        assert_eq!(
10398            ids,
10399            vec![
10400                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10401                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10402                "cron_standup",
10403                "cron_reindex",
10404            ]
10405        );
10406
10407        let by_session = jobs_list(json!({
10408            "session": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
10409            "homes": jobs_fixture_homes(),
10410        }));
10411        let jobs = by_session["result"]["jobs"].as_array().unwrap();
10412        assert_eq!(jobs.len(), 2, "{by_session}");
10413        assert!(jobs
10414            .iter()
10415            .all(|job| job["harness"] == "claude-code" && job["scope"] == "session"));
10416
10417        let by_profile = jobs_list(json!({
10418            "harness": "hermes",
10419            "profile": "ops",
10420            "homes": jobs_fixture_homes(),
10421        }));
10422        let jobs = by_profile["result"]["jobs"].as_array().unwrap();
10423        assert_eq!(jobs.len(), 1, "{by_profile}");
10424        assert_eq!(jobs[0]["id"], "ops-once-boot");
10425    }
10426
10427    #[test]
10428    fn jobs_get_answers_with_the_row_and_the_verbatim_native_record() {
10429        let mut service = HarnessSessionService::new();
10430        let hermes = service.handle(request(
10431            1,
10432            "harness.v1.jobs.get",
10433            json!({"harness": "hermes", "id": "digest-15m", "homes": jobs_fixture_homes()}),
10434        ));
10435        assert_eq!(hermes["result"]["job"]["schedule"]["kind"], "interval");
10436        // Native fields the uniform row does not carry survive on `source`.
10437        assert_eq!(hermes["result"]["source"]["provider"], "nous");
10438        assert_eq!(hermes["result"]["source"]["failure_deliver"], "local");
10439
10440        let claude = service.handle(request(
10441            2,
10442            "harness.v1.jobs.get",
10443            json!({"harness": "claude-code", "id": "release-watch", "homes": jobs_fixture_homes()}),
10444        ));
10445        assert_eq!(claude["result"]["job"]["payload"]["kind"], "prompt");
10446        assert_eq!(
10447            claude["result"]["source"]["tool_use_id"],
10448            "toolu_cron_release_watch"
10449        );
10450
10451        let missing = service.handle(request(
10452            3,
10453            "harness.v1.jobs.get",
10454            json!({"harness": "hermes", "id": "no-such-job", "homes": jobs_fixture_homes()}),
10455        ));
10456        assert!(missing["error"]["message"]
10457            .as_str()
10458            .is_some_and(|message| message.contains("no scheduled job `no-such-job`")));
10459    }
10460
10461    #[test]
10462    fn jobs_refuse_a_harness_without_a_scheduled_job_concept() {
10463        let mut service = HarnessSessionService::new();
10464        for (id, method, params) in [
10465            (
10466                1,
10467                "harness.v1.jobs.list",
10468                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10469            ),
10470            (
10471                2,
10472                "harness.v1.jobs.get",
10473                json!({"harness": "codex", "id": "anything"}),
10474            ),
10475        ] {
10476            let response = service.handle(request(id, method, params));
10477            assert_eq!(response["error"]["code"], -32020, "{response}");
10478            assert!(response["error"]["message"]
10479                .as_str()
10480                .is_some_and(|message| message.contains("has no scheduled jobs")));
10481            assert!(response.get("result").is_none());
10482        }
10483    }
10484
10485    #[test]
10486    fn jobs_list_reports_a_migrated_openclaw_store_as_absent_instead_of_failing() {
10487        let scratch = std::env::temp_dir().join(format!(
10488            "supercode-jobs-migrated-{}-{}",
10489            std::process::id(),
10490            generated_session_id()
10491        ));
10492        std::fs::create_dir_all(&scratch).unwrap();
10493        let response = jobs_list(json!({
10494            "harness": "openclaw",
10495            "homes": {"openclaw": scratch.clone()},
10496        }));
10497        let result = &response["result"];
10498        assert_eq!(result["jobs"].as_array().unwrap().len(), 0, "{result}");
10499        assert_eq!(result["sources"][0]["state"], "absent_store");
10500        assert_eq!(result["sources"][0]["harness"], "openclaw");
10501        std::fs::remove_dir_all(&scratch).ok();
10502    }
10503
10504    // ---------------------------------------------------------------------
10505    // ORCH-8 — `harness.v1.runs.list` / `runs.get` over the committed fire
10506    // stores: Hermes's `cron/executions.db` (root home + profile home) and
10507    // OpenClaw's `cron_run_logs`. Every fixture row is written by
10508    // `tests/fixtures/gen_runs_fixtures.py` against the harnesses' own DDL.
10509    // ---------------------------------------------------------------------
10510
10511    /// The health job in the committed OpenClaw fixture, which fired twice.
10512    const OPENCLAW_HEALTH_JOB: &str = "85ad7832-896f-42be-af31-3e1ed2fbdc4b";
10513    /// The digest job, whose single fire predates run ids.
10514    const OPENCLAW_DIGEST_JOB: &str = "8bb7d938-ca46-4a6d-90eb-c92331155566";
10515
10516    fn runs_list(params: Value) -> Value {
10517        let mut service = HarnessSessionService::new();
10518        service.handle(request(1, "harness.v1.runs.list", params))
10519    }
10520
10521    fn run_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10522        result["runs"]
10523            .as_array()
10524            .expect("runs is an array")
10525            .iter()
10526            .find(|run| run["id"] == id)
10527            .unwrap_or_else(|| panic!("no run `{id}` in {result}"))
10528    }
10529
10530    #[test]
10531    fn runs_list_projects_both_fixture_stores_onto_the_uniform_row() {
10532        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10533        let result = &response["result"];
10534        let ids: Vec<&str> = result["runs"]
10535            .as_array()
10536            .expect("runs is an array")
10537            .iter()
10538            .map(|run| run["id"].as_str().unwrap())
10539            .collect();
10540        let digest_fire = format!("{OPENCLAW_DIGEST_JOB}#1");
10541        assert_eq!(
10542            ids,
10543            vec![
10544                // Hermes, newest claim first, root ledger then profile ledger.
10545                "b2c3d4e5f60718293a4b5c6d7e8f9012",
10546                "a1b2c3d4e5f60718293a4b5c6d7e8f90",
10547                "c3d4e5f60718293a4b5c6d7e8f901234",
10548                "f60718293a4b5c6d7e8f901234567890",
10549                "e5f60718293a4b5c6d7e8f9012345678",
10550                "d4e5f60718293a4b5c6d7e8f90123456",
10551                // OpenClaw, newest `ts` first.
10552                "run_health_0002",
10553                digest_fire.as_str(),
10554                "run_health_0001",
10555            ],
10556            "{result}"
10557        );
10558
10559        // The harness's OWN outcome word survives; nothing is renamed onto a
10560        // shared vocabulary.
10561        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10562        assert_eq!(failed["harness"], "hermes");
10563        assert_eq!(failed["job_id"], "job42");
10564        assert_eq!(failed["status"], "failed");
10565        assert_eq!(failed["error"], "provider returned 500 after 3 attempts");
10566        assert_eq!(failed["claimed_at"], "2026-09-02T13:05:00.100442");
10567
10568        // Hermes's `unknown` — an attempt whose owner died before writing a
10569        // terminal state — is a fourth status, not folded into `failed`.
10570        let abandoned = run_row(result, "d4e5f60718293a4b5c6d7e8f90123456");
10571        assert_eq!(abandoned["status"], "unknown");
10572        assert_eq!(abandoned["job_id"], "ops-once-boot");
10573
10574        // An unterminated fire has no finish, and no session is invented.
10575        let running = run_row(result, "c3d4e5f60718293a4b5c6d7e8f901234");
10576        assert_eq!(running["status"], "running");
10577        assert!(running["finished_at"].is_null(), "{running}");
10578        assert!(running["session_id"].is_null(), "{running}");
10579
10580        // OpenClaw records the session on the row itself, and epoch-ms
10581        // timestamps are rendered as RFC 3339.
10582        let ok = run_row(result, "run_health_0001");
10583        assert_eq!(ok["harness"], "openclaw");
10584        assert_eq!(ok["job_id"], OPENCLAW_HEALTH_JOB);
10585        assert_eq!(ok["status"], "ok");
10586        assert_eq!(ok["started_at"], "2026-09-02T08:30:00.000Z");
10587        assert_eq!(ok["finished_at"], "2026-09-02T08:30:30.000Z");
10588        assert_eq!(ok["session_id"], "3dd577ae-a0a3-4b5b-8063-f402be4f5fd4");
10589        // OpenClaw's run log is written once, at finish: there is no claim.
10590        assert!(ok["claimed_at"].is_null(), "{ok}");
10591
10592        // A run-log row with no `run_id` falls back to the store's own
10593        // `(job_id, seq)` key rather than being dropped.
10594        assert_eq!(run_row(result, &digest_fire)["status"], "skipped");
10595
10596        // ORCH-13: a fire whose delivery nothing recorded says so, rather than
10597        // borrowing a neighbouring fire's outcome. Both of these ran on jobs
10598        // that deliver `local` (or have no job record at all), so no
10599        // obligation is addressed to a surface they could match.
10600        for id in [
10601            "b2c3d4e5f60718293a4b5c6d7e8f9012",
10602            "d4e5f60718293a4b5c6d7e8f90123456",
10603        ] {
10604            assert!(run_row(result, id)["delivery"].is_null(), "{id}");
10605        }
10606
10607        // Every store consulted is named, including the profile home that has
10608        // no ledger — an empty history and an absent store are different.
10609        let sources = result["sources"].as_array().unwrap();
10610        let states: Vec<(&str, &str)> = sources
10611            .iter()
10612            .map(|source| {
10613                (
10614                    source["harness"].as_str().unwrap(),
10615                    source["state"].as_str().unwrap(),
10616                )
10617            })
10618            .collect();
10619        assert_eq!(
10620            states,
10621            vec![
10622                ("hermes", "read"),
10623                ("hermes", "absent_store"),
10624                ("hermes", "read"),
10625                ("openclaw", "read"),
10626            ],
10627            "{result}"
10628        );
10629        assert_eq!(sources[2]["profile"], "ops");
10630        assert!(sources[3]["path"]
10631            .as_str()
10632            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10633    }
10634
10635    #[test]
10636    fn runs_list_joins_a_hermes_fire_to_the_session_it_opened() {
10637        let response = runs_list(json!({
10638            "harness": "hermes",
10639            "job": "job42",
10640            "homes": jobs_fixture_homes(),
10641        }));
10642        let result = &response["result"];
10643        assert_eq!(result["runs"].as_array().unwrap().len(), 2, "{result}");
10644
10645        // Hermes writes NO link from an execution to its session. The fire
10646        // that ran the agent is joined to `cron_job42_<stamp>` because that
10647        // id's instant falls inside its [claimed_at, finished_at] window.
10648        let ran = run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90");
10649        assert_eq!(ran["session_id"], "cron_job42_20260902_120000");
10650
10651        // The later fire failed before opening one. Its window holds no
10652        // session, so the row says so instead of re-using the earlier fire's
10653        // — the join is per-FIRE, not per-job.
10654        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10655        assert!(failed["session_id"].is_null(), "{failed}");
10656    }
10657
10658    /// ORCH-13: where a fire's output went, read from each harness's own
10659    /// delivery record — Hermes's `delivery_obligations` ledger inside
10660    /// `state.db`, OpenClaw's `delivery_*` run-log columns.
10661    #[test]
10662    fn runs_list_reads_the_delivery_each_harness_recorded_for_a_fire() {
10663        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10664        let result = &response["result"];
10665
10666        // Hermes: the ledger is the GATEWAY's, keyed by conversation and
10667        // surface, so the fire's own [claimed_at, finished_at] window picks
10668        // the obligation. The fire succeeded and so did the send.
10669        let delivered = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
10670        assert_eq!(delivered["status"], "completed");
10671        assert_eq!(delivered["delivery"]["state"], "delivered");
10672        assert_eq!(delivered["delivery"]["target"], "telegram:-100777:55");
10673        assert_eq!(delivered["delivery"]["attempts"], 1);
10674        assert!(delivered["delivery"]["last_error"].is_null(), "{delivered}");
10675        assert_eq!(
10676            delivered["delivery"]["delivered_at"],
10677            "2026-09-02T09:00:30.400Z"
10678        );
10679
10680        // The next fire of the same job ALSO succeeded — and its output never
10681        // arrived. That is the fact `status` alone cannot carry.
10682        let undelivered = run_row(result, "f60718293a4b5c6d7e8f901234567890");
10683        assert_eq!(undelivered["status"], "completed");
10684        assert_eq!(undelivered["delivery"]["state"], "failed");
10685        assert_eq!(undelivered["delivery"]["attempts"], 3);
10686        assert_eq!(
10687            undelivered["delivery"]["last_error"],
10688            "telegram send failed: Bad Request: chat not found"
10689        );
10690        // Only a delivered obligation carries an instant of delivery; the
10691        // ledger's `updated_at` on a failed row dates the failure.
10692        assert!(
10693            undelivered["delivery"]["delivered_at"].is_null(),
10694            "{undelivered}"
10695        );
10696
10697        // OpenClaw writes the outcome onto the run-log row and declares the
10698        // address on the job, so the row's target is joined from `cron_jobs`.
10699        let announced = run_row(result, "run_health_0001");
10700        assert_eq!(announced["delivery"]["state"], "delivered");
10701        assert_eq!(announced["delivery"]["target"], "last");
10702        // Its run log counts no attempts and stamps no delivered-at.
10703        assert!(announced["delivery"]["attempts"].is_null(), "{announced}");
10704        assert!(
10705            announced["delivery"]["delivered_at"].is_null(),
10706            "{announced}"
10707        );
10708        let refused = run_row(result, "run_health_0002");
10709        assert_eq!(refused["delivery"]["state"], "not-delivered");
10710        assert_eq!(refused["delivery"]["last_error"], "channel_not_found");
10711
10712        // A run-log row with no delivery columns at all recorded no delivery:
10713        // the job's declared target is not evidence that anything was sent.
10714        let skipped = run_row(result, &format!("{OPENCLAW_DIGEST_JOB}#1"));
10715        assert!(skipped["delivery"].is_null(), "{skipped}");
10716    }
10717
10718    /// A Hermes fire whose session carries a `session_key` is matched on that
10719    /// key FIRST — the most specific question the ledger can answer. Proven by
10720    /// moving the obligations off the job's surface on a COPY of the fixture,
10721    /// so only the session-key question can still find them.
10722    #[test]
10723    fn runs_list_matches_a_hermes_obligation_by_the_session_key_first() {
10724        let scratch = std::env::temp_dir().join(format!(
10725            "supercode-runs-delivery-{}-{}",
10726            std::process::id(),
10727            generated_session_id()
10728        ));
10729        std::fs::create_dir_all(scratch.join("cron")).unwrap();
10730        let fixture = jobs_fixture_root().join("hermes_home");
10731        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
10732        for name in ["cron/executions.db", "cron/jobs.json"] {
10733            std::fs::copy(fixture.join(name), scratch.join(name)).unwrap();
10734        }
10735        {
10736            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
10737            // The obligations now sit on a surface no job in this store
10738            // delivers to, so the surface question cannot match them.
10739            connection
10740                .execute(
10741                    "UPDATE delivery_obligations SET platform = 'slack', chat_id = 'C0FALLBACK'",
10742                    [],
10743                )
10744                .unwrap();
10745            // A cron fire that ran inside a keyed conversation: the session
10746            // the window recovers carries `tg-coder-1`'s key.
10747            connection
10748                .execute(
10749                    "INSERT INTO sessions (id, source, session_key, started_at) VALUES \
10750                     ('cron_coder-standup_20260902_090010', 'cron', \
10751                      'agent:coder:telegram:group:-100777:55', 1788339610.0)",
10752                    [],
10753                )
10754                .unwrap();
10755        }
10756        let response = runs_list(json!({
10757            "harness": "hermes",
10758            "job": "coder-standup",
10759            "homes": {"hermes": scratch.join("state.db")},
10760        }));
10761        let result = &response["result"];
10762        let matched = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
10763        assert_eq!(
10764            matched["session_id"], "cron_coder-standup_20260902_090010",
10765            "{result}"
10766        );
10767        assert_eq!(matched["delivery"]["state"], "delivered", "{result}");
10768        assert_eq!(
10769            matched["delivery"]["target"], "slack:C0FALLBACK:55",
10770            "{result}"
10771        );
10772        std::fs::remove_dir_all(&scratch).ok();
10773    }
10774
10775    #[test]
10776    fn runs_list_follows_a_compression_chain_to_the_readable_tip() {
10777        // A fire whose session was compressed mid-run is only readable at the
10778        // continuation, so that is what the row must report. Built on a COPY
10779        // of the committed fixture: no test writes to a fixture or to a real
10780        // harness home.
10781        let scratch = std::env::temp_dir().join(format!(
10782            "supercode-runs-compressed-{}-{}",
10783            std::process::id(),
10784            generated_session_id()
10785        ));
10786        std::fs::create_dir_all(scratch.join("cron")).unwrap();
10787        let fixture = jobs_fixture_root().join("hermes_home");
10788        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
10789        std::fs::copy(
10790            fixture.join("cron/executions.db"),
10791            scratch.join("cron/executions.db"),
10792        )
10793        .unwrap();
10794        {
10795            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
10796            connection
10797                .execute(
10798                    "UPDATE sessions SET end_reason = 'compression' WHERE id = ?1",
10799                    ["cron_job42_20260902_120000"],
10800                )
10801                .unwrap();
10802            connection
10803                .execute(
10804                    "INSERT INTO sessions (id, source, parent_session_id, started_at) \
10805                     VALUES ('job42-after-compaction', 'cron', \
10806                             'cron_job42_20260902_120000', 1788350000.0)",
10807                    [],
10808                )
10809                .unwrap();
10810        }
10811        let response = runs_list(json!({
10812            "harness": "hermes",
10813            "job": "job42",
10814            "homes": {"hermes": scratch.join("state.db")},
10815        }));
10816        let result = &response["result"];
10817        assert_eq!(
10818            run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90")["session_id"],
10819            "job42-after-compaction",
10820            "{result}"
10821        );
10822        std::fs::remove_dir_all(&scratch).ok();
10823    }
10824
10825    #[test]
10826    fn runs_list_filters_by_job_and_caps_by_limit() {
10827        let by_job = runs_list(json!({
10828            "harness": "openclaw",
10829            "job": OPENCLAW_HEALTH_JOB,
10830            "homes": jobs_fixture_homes(),
10831        }));
10832        let ids: Vec<&str> = by_job["result"]["runs"]
10833            .as_array()
10834            .unwrap()
10835            .iter()
10836            .map(|run| run["id"].as_str().unwrap())
10837            .collect();
10838        assert_eq!(ids, vec!["run_health_0002", "run_health_0001"], "{by_job}");
10839
10840        let capped = runs_list(json!({
10841            "harness": "openclaw",
10842            "limit": 1,
10843            "homes": jobs_fixture_homes(),
10844        }));
10845        let runs = capped["result"]["runs"].as_array().unwrap();
10846        assert_eq!(runs.len(), 1, "{capped}");
10847        // Newest first, so the cap keeps the recent fire.
10848        assert_eq!(runs[0]["id"], "run_health_0002");
10849    }
10850
10851    #[test]
10852    fn runs_get_answers_with_the_row_and_the_verbatim_native_record() {
10853        let mut service = HarnessSessionService::new();
10854        let hermes = service.handle(request(
10855            1,
10856            "harness.v1.runs.get",
10857            json!({
10858                "harness": "hermes",
10859                "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
10860                "homes": jobs_fixture_homes(),
10861            }),
10862        ));
10863        assert_eq!(hermes["result"]["run"]["status"], "completed");
10864        assert_eq!(
10865            hermes["result"]["run"]["session_id"],
10866            "cron_job42_20260902_120000"
10867        );
10868        // Ledger columns the uniform row does not carry survive on `source`.
10869        assert_eq!(hermes["result"]["source"]["source"], "scheduler");
10870        assert_eq!(hermes["result"]["source"]["pid"], 4242);
10871        assert_eq!(hermes["result"]["source"]["process_id"], "9f1c2d");
10872
10873        let openclaw = service.handle(request(
10874            2,
10875            "harness.v1.runs.get",
10876            json!({
10877                "harness": "openclaw",
10878                "id": "run_health_0002",
10879                "homes": jobs_fixture_homes(),
10880            }),
10881        ));
10882        assert_eq!(openclaw["result"]["run"]["status"], "error");
10883        // ORCH-13: the run's delivery is projected AND the store's own columns
10884        // stay verbatim on `source`, so nothing about the fire is lost.
10885        assert_eq!(
10886            openclaw["result"]["source"]["delivery_status"],
10887            "not-delivered"
10888        );
10889        assert_eq!(
10890            openclaw["result"]["source"]["delivery_error"],
10891            "channel_not_found"
10892        );
10893        assert_eq!(openclaw["result"]["source"]["delivered"], 0);
10894        assert_eq!(
10895            openclaw["result"]["run"]["delivery"]["state"],
10896            "not-delivered"
10897        );
10898        assert_eq!(
10899            openclaw["result"]["run"]["delivery"]["last_error"],
10900            "channel_not_found"
10901        );
10902
10903        let missing = service.handle(request(
10904            3,
10905            "harness.v1.runs.get",
10906            json!({"harness": "hermes", "id": "no-such-run", "homes": jobs_fixture_homes()}),
10907        ));
10908        assert!(missing["error"]["message"]
10909            .as_str()
10910            .is_some_and(|message| message.contains("no run `no-such-run`")));
10911    }
10912
10913    #[test]
10914    fn runs_refuse_a_harness_that_keeps_no_run_store() {
10915        let mut service = HarnessSessionService::new();
10916        for (id, method, params) in [
10917            // Claude Code HAS scheduled jobs but no fire store: its fires are
10918            // ordinary turns. It must refuse, not answer with an empty list.
10919            (
10920                1,
10921                "harness.v1.runs.list",
10922                json!({"harness": "claude-code", "homes": jobs_fixture_homes()}),
10923            ),
10924            (
10925                2,
10926                "harness.v1.runs.get",
10927                json!({"harness": "claude-code", "id": "anything"}),
10928            ),
10929            (
10930                3,
10931                "harness.v1.runs.list",
10932                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10933            ),
10934        ] {
10935            let response = service.handle(request(id, method, params));
10936            assert_eq!(response["error"]["code"], -32020, "{response}");
10937            assert!(response["error"]["message"]
10938                .as_str()
10939                .is_some_and(|message| message.contains("keeps no run store")));
10940            assert!(response.get("result").is_none());
10941        }
10942    }
10943
10944    #[test]
10945    fn runs_list_reports_an_install_with_no_run_store_as_absent() {
10946        let scratch = std::env::temp_dir().join(format!(
10947            "supercode-runs-empty-{}-{}",
10948            std::process::id(),
10949            generated_session_id()
10950        ));
10951        std::fs::create_dir_all(&scratch).unwrap();
10952        let response = runs_list(json!({
10953            "harness": "openclaw",
10954            "homes": {"openclaw": scratch.clone()},
10955        }));
10956        let result = &response["result"];
10957        assert_eq!(result["runs"].as_array().unwrap().len(), 0, "{result}");
10958        assert_eq!(result["sources"][0]["state"], "absent_store");
10959        assert!(result["sources"][0]["path"]
10960            .as_str()
10961            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10962        std::fs::remove_dir_all(&scratch).ok();
10963    }
10964}