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