Skip to main content

supercode_harness/
harness_service.rs

1//! Versioned, language-neutral service over persisted harness sessions.
2//!
3//! The service is transport-agnostic: [`HarnessSessionService::handle`] accepts
4//! one JSON-RPC value and [`HarnessSessionService::poll`] produces subscription
5//! notifications. The CLI exposes those primitives as NDJSON over stdio.
6
7use std::collections::{BTreeMap, BTreeSet};
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use std::time::Duration;
11
12use serde::{Deserialize, Serialize};
13use serde_json::{json, Value};
14use tokio::sync::Notify;
15
16use crate::runtime::generated_session_id;
17#[cfg(feature = "adapter-api")]
18use crate::runtime::{HostedHarnessConnection, HostedHarnessRuntime};
19use crate::sdk::{
20    discover_session_page, load_session, load_session_with_fidelity, SdkCapabilities, SdkError,
21    SdkErrorCode, SdkEvent, SdkOperation, SdkRequest, SdkRuntimeEvent, SdkService,
22};
23use crate::watch::{bound_session_view, message_json, normalized_session_json};
24use crate::Fidelity;
25#[cfg(feature = "adapter-api")]
26use crate::SupercodeHttpRuntimeBackend;
27use crate::{
28    discover_live_runtime, harness_support_registry, AcpRuntimeBackend, ClaudeCodeRuntimeBackend,
29    CodexRuntimeBackend, DiscoveryQuery, HarnessCatalog, HarnessHomes, HarnessId,
30    ImplementationKind, LiveRuntimeEndpoint, LiveRuntimeSource, OpenCodeRuntimeBackend,
31    PiRuntimeBackend, Role, RuntimeAttachRequest, RuntimeBackend, RuntimeConnection, RuntimeInput,
32    RuntimeLaunch, RuntimeStartRequest, Session, SessionDescriptor, SessionFollower, SessionFormat,
33    SessionLocator, SessionSource,
34};
35use crate::{reduce, tokens};
36#[cfg(feature = "adapter-api")]
37use crate::{register_live_runtime, resolve_live_runtime, LiveRuntimeRegistration};
38
39/// Every JSON-RPC method the harness service dispatches (`harness.v1.capabilities`
40/// reports it; ORCH-4 registry tiers must cite entries of it).
41pub const HARNESS_SERVICE_METHODS: &[&str] = &[
42    "harness.v1.support.report",
43    "harness.v1.harnesses.list",
44    "harness.v1.harnesses.probe",
45    "harness.v1.harnesses.settings",
46    "harness.v1.harnesses.configure",
47    "harness.v1.harnesses.auth.methods",
48    "harness.v1.harnesses.auth.begin",
49    "harness.v1.harnesses.auth.verify",
50    "harness.v1.sessions.discover",
51    "harness.v1.sessions.load",
52    "harness.v1.sessions.follow",
53    "harness.v1.sessions.unfollow",
54    "harness.v1.sessions.activity.subscribe",
55    "harness.v1.sessions.activity.unsubscribe",
56    "harness.v1.sessions.index.subscribe",
57    "harness.v1.sessions.index.resize",
58    "harness.v1.sessions.index.unsubscribe",
59    "harness.v1.sessions.message",
60    "harness.v1.sessions.import",
61    "harness.v1.sessions.export",
62    "harness.v1.sessions.translate",
63    "harness.v1.sessions.reduce",
64    "harness.v1.sessions.branch",
65    "harness.v1.sessions.handoff",
66    "harness.v1.sessions.materialize",
67    "harness.v1.sessions.resume_instructions",
68    "harness.v1.skills.list",
69    "harness.v1.skills.install",
70    "harness.v1.skills.remove",
71    "harness.v1.memory.show",
72    "harness.v1.memory.search",
73    "harness.v1.jobs.list",
74    "harness.v1.jobs.get",
75    "harness.v1.jobs.create",
76    "harness.v1.jobs.update",
77    "harness.v1.jobs.pause",
78    "harness.v1.jobs.resume",
79    "harness.v1.jobs.run",
80    "harness.v1.jobs.delete",
81    "harness.v1.jobs.notepad",
82    "harness.v1.jobs.notepad_set",
83    "harness.v1.jobs.notepad_delete",
84    "harness.v1.sessions.new",
85    "harness.v1.sessions.reset",
86    "harness.v1.sessions.archive",
87    "harness.v1.sessions.delete",
88    "harness.v1.runs.list",
89    "harness.v1.runs.get",
90    "harness.v1.approvals.list",
91    "harness.v1.approvals.resolve",
92    "harness.v1.runtimes.capabilities",
93    "harness.v1.runtimes.start",
94    "harness.v1.runtimes.resume",
95    "harness.v1.runtimes.attach_existing",
96    "harness.v1.runtimes.attach",
97    "harness.v1.runtimes.send_input",
98    "harness.v1.runtimes.interrupt",
99    "harness.v1.runtimes.steer",
100    "harness.v1.runtimes.respond",
101    "harness.v1.runtimes.terminal_instructions",
102    "harness.v1.runtimes.acquire_control",
103    "harness.v1.runtimes.heartbeat",
104    "harness.v1.runtimes.detach",
105    "harness.v1.runtimes.close",
106    "harness.v1.profiles.list",
107    "harness.v1.profiles.get",
108    "harness.v1.profiles.create",
109    "harness.v1.profiles.delete",
110    "harness.v1.channels.list",
111    "harness.v1.routes.list",
112    "harness.v1.triggers.list",
113    "harness.v1.channels.status",
114    "harness.v1.orchestration.load",
115    "harness.v1.orchestration.save",
116    "harness.v1.orchestration.compile",
117    "harness.v1.orchestration.decompile",
118    "harness.v1.orchestration.import",
119    "harness.v1.orchestration.export",
120    "harness.v1.workflow.load",
121];
122
123/// Protocol namespace implemented by this service.
124pub const HARNESS_SERVICE_VERSION: &str = "harness.v1";
125/// Notification method emitted for followed-session changes.
126pub const SESSION_EVENT_METHOD: &str = "harness.v1.sessions.event";
127/// Notification method emitted for normalized session-activity transitions.
128pub const SESSION_ACTIVITY_EVENT_METHOD: &str = "harness.v1.sessions.activity_event";
129/// Notification method emitted for revisioned session-list changes.
130pub const SESSION_INDEX_EVENT_METHOD: &str = "harness.v1.sessions.index_event";
131/// Notification method emitted for live runtime events.
132pub const RUNTIME_EVENT_METHOD: &str = "harness.v1.runtimes.event";
133
134/// Stateful persisted-session service. Each instance owns its follow
135/// subscriptions; discovery and loading remain read-only.
136pub struct HarnessSessionService {
137    catalog: HarnessCatalog,
138    followers: BTreeMap<String, SessionFollower>,
139    followed_sources: BTreeMap<String, FollowedSource>,
140    activity_subscriptions: BTreeMap<String, ActivitySubscription>,
141    index_subscriptions: BTreeMap<String, crate::session_index::SessionIndexSubscription>,
142    index_notifier: Arc<Notify>,
143    #[cfg(feature = "adapter-api")]
144    activity_monitor: crate::session_activity::SessionActivityMonitor,
145    next_subscription: u64,
146    runtimes: BTreeMap<String, Box<dyn RuntimeConnection>>,
147    /// Connections lent to a detached call that is running right now. The
148    /// runtime itself is OUT of `runtimes` for that whole call, and these
149    /// names are how a second caller is told the connection is busy rather
150    /// than unknown.
151    runtimes_in_flight: BTreeSet<String>,
152    terminal_launches: BTreeMap<String, StructuredLaunch>,
153    runtime_sequences: BTreeMap<String, u64>,
154    next_runtime: u64,
155    reduction_store_root: Option<PathBuf>,
156    /// ORCH-9: live permission/approval requests outstanding on the open
157    /// runtime connections above, fed by the same event pump that publishes
158    /// `harness.v1.runtimes.event`.
159    approvals: crate::approvals::ApprovalRegistry,
160    /// ORCH-9: supercode's own queued subagent approvals, when the host that
161    /// owns this service publishes its parent queue here.
162    subagent_approvals: Option<Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>>,
163}
164
165impl Default for HarnessSessionService {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171impl HarnessSessionService {
172    /// Create an empty service instance.
173    pub fn new() -> Self {
174        Self {
175            catalog: HarnessCatalog::new(),
176            followers: BTreeMap::new(),
177            followed_sources: BTreeMap::new(),
178            activity_subscriptions: BTreeMap::new(),
179            index_subscriptions: BTreeMap::new(),
180            index_notifier: Arc::new(Notify::new()),
181            #[cfg(feature = "adapter-api")]
182            activity_monitor: Default::default(),
183            next_subscription: 1,
184            runtimes: BTreeMap::new(),
185            runtimes_in_flight: BTreeSet::new(),
186            terminal_launches: BTreeMap::new(),
187            runtime_sequences: BTreeMap::new(),
188            next_runtime: 1,
189            reduction_store_root: None,
190            approvals: crate::approvals::ApprovalRegistry::new(),
191            subagent_approvals: None,
192        }
193    }
194
195    /// Override the trusted, service-owned store used for durable reduction
196    /// bundles. Embedders and tests use this to keep all writes inside an
197    /// explicitly selected root; the CLI otherwise uses the normal
198    /// `$SUPERCODE_HOME/sessions` location.
199    pub fn with_reduction_store_root(mut self, root: impl Into<PathBuf>) -> Self {
200        self.reduction_store_root = Some(root.into());
201        self
202    }
203
204    /// ORCH-9: publish the parent's own subagent-approval queue into
205    /// `harness.v1.approvals.list`.
206    ///
207    /// This is the SAME `Arc` an [`crate::Agent`] pushes into
208    /// (`Agent::pending_child_approvals`), so a host that runs supercode's own
209    /// loop beside this service surfaces those requests through the uniform
210    /// door without copying them anywhere.
211    pub fn observe_subagent_approvals(
212        &mut self,
213        queue: Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>,
214    ) {
215        self.subagent_approvals = Some(queue);
216    }
217
218    /// ORCH-9: every approval request this service can see, newest last.
219    ///
220    /// Two sources, both live: the requests outstanding on the open runtime
221    /// connections, and supercode's own queued subagent approvals. There is
222    /// no file or database source at the pinned harness versions (see
223    /// [`crate::approvals`]), so a stored or proposal row is never produced.
224    pub fn approvals(&self, query: &crate::approvals::ApprovalsQuery) -> Vec<crate::ApprovalRow> {
225        let now = crate::approvals::now_ms();
226        let mut rows = self.approvals.rows(now);
227        if let Some(queue) = self.subagent_approvals.as_ref() {
228            let queued = queue
229                .lock()
230                .unwrap_or_else(std::sync::PoisonError::into_inner)
231                .clone();
232            rows.extend(crate::approvals::subagent_rows(&queued, now));
233        }
234        rows.retain(|row| query.matches(row));
235        rows.sort_by(|left, right| {
236            left.requested_at_ms
237                .cmp(&right.requested_at_ms)
238                .then_with(|| left.id.cmp(&right.id))
239        });
240        rows
241    }
242
243    /// ORCH-20 (controlled tier): answer one listed approval request by its
244    /// row id and one uniform decision.
245    ///
246    /// The decision is translated into the option token and reply envelope
247    /// the door that raised the request already accepts
248    /// ([`crate::approvals::plan_reply`]), and the answer is then sent by
249    /// calling `harness.v1.runtimes.respond` itself — the same code path, the
250    /// same adapter, the same bookkeeping that drops the row. This verb adds
251    /// a translation and nothing else.
252    async fn approvals_resolve(
253        &mut self,
254        params: Value,
255    ) -> std::result::Result<Value, ServiceError> {
256        let params = decode::<crate::approvals::ApprovalsResolveParams>(params)?;
257        if params.id.trim().is_empty() {
258            return Err(ServiceError::InvalidParams(
259                "approvals resolve requires the `id` of a listed approval row".into(),
260            ));
261        }
262        let choice = match (params.decision, params.option_id.as_deref()) {
263            (Some(_), Some(_)) => {
264                return Err(ServiceError::InvalidParams(
265                    "approvals resolve takes either `decision` or `option_id`, not both".into(),
266                ))
267            }
268            (Some(decision), None) => crate::approvals::ApprovalChoice::Decision(decision),
269            (None, Some(option)) => crate::approvals::ApprovalChoice::Option(option.to_string()),
270            (None, None) => {
271                return Err(ServiceError::InvalidParams(format!(
272                    "approvals resolve requires `decision` ({}) or an explicit `option_id`",
273                    crate::approvals::ApprovalDecision::ALL
274                        .map(|decision| decision.as_str())
275                        .join(" | "),
276                )))
277            }
278        };
279        let resolution = self
280            .approvals
281            .resolution(&params.id, &choice)
282            .map_err(|error| ServiceError::InvalidParams(error.to_string()))?;
283        // The harness's own door, unchanged: this is the identical call
284        // `harness.v1.runtimes.respond` performs for a caller who built the
285        // envelope by hand, including dropping the answered row.
286        self.runtime_call(
287            "harness.v1.runtimes.respond",
288            json!({
289                "connection": resolution.connection,
290                "request_id": resolution.request_id,
291                "response": resolution.response,
292            }),
293        )
294        .await?;
295        Ok(json!({
296            "id": params.id,
297            "decision": params.decision.map(|decision| decision.as_str()),
298            "option_id": resolution.option_id,
299            "resolved": true,
300        }))
301    }
302
303    /// Return the edge-triggered wakeup used by session-index filesystem
304    /// subscriptions. Transports can await this instead of polling indexes.
305    #[cfg(feature = "adapter-api")]
306    pub fn session_index_notifier(&self) -> Arc<Notify> {
307        Arc::clone(&self.index_notifier)
308    }
309
310    /// Handle one JSON-RPC 2.0 request and return one JSON-RPC response.
311    #[cfg(feature = "adapter-api")]
312    pub fn handle(&mut self, request: Value) -> Value {
313        let id = request.get("id").cloned().unwrap_or(Value::Null);
314        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
315            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
316        }
317        let Some(method) = request.get("method").and_then(Value::as_str) else {
318            return rpc_error(id, -32600, "request is missing `method`");
319        };
320        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
321        match self.call(method, params) {
322            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
323            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
324            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
325            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
326            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
327            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
328        }
329    }
330
331    /// Handle either a persisted-session request or an asynchronous live
332    /// runtime request.
333    #[cfg(feature = "adapter-api")]
334    pub async fn handle_async(&mut self, request: Value) -> Value {
335        let method = request
336            .get("method")
337            .and_then(Value::as_str)
338            .unwrap_or_default();
339        if matches!(
340            method,
341            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe"
342        ) {
343            let id = request.get("id").cloned().unwrap_or(Value::Null);
344            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
345                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
346            }
347            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
348            return match self.inventory_call(method, params).await {
349                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
350                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
351                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
352                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
353                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
354                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
355            };
356        }
357        if matches!(
358            method,
359            "harness.v1.harnesses.auth.methods"
360                | "harness.v1.harnesses.auth.begin"
361                | "harness.v1.harnesses.auth.verify"
362        ) {
363            let id = request.get("id").cloned().unwrap_or(Value::Null);
364            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
365                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
366            }
367            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
368            return match self.harness_authentication_call(method, params).await {
369                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
370                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
371                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
372                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
373                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
374                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
375            };
376        }
377        // ORCH-19 controlled tier. Answered here rather than through the SDK
378        // operation dispatch below so the harness's OWN refusal reaches the
379        // caller: `sdk_error` collapses every `UnsupportedAction` to one
380        // generic sentence, and the whole point of this tier is that a
381        // refusal names which door the harness does have.
382        if matches!(
383            method,
384            "harness.v1.sessions.new"
385                | "harness.v1.sessions.reset"
386                | "harness.v1.sessions.archive"
387                | "harness.v1.sessions.delete"
388        ) {
389            let id = request.get("id").cloned().unwrap_or(Value::Null);
390            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
391                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
392            }
393            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
394            let verb = match method {
395                "harness.v1.sessions.new" => crate::SessionVerb::New,
396                "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
397                "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
398                _ => crate::SessionVerb::Delete,
399            };
400            return match self.mutate_session(verb, params).await {
401                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
402                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
403                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
404                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
405                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
406                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
407            };
408        }
409        if method == "harness.v1.sessions.message" {
410            let id = request.get("id").cloned().unwrap_or(Value::Null);
411            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
412                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
413            }
414            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
415            return match self.message_call(params).await {
416                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
417                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
418                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
419                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
420                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
421                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
422            };
423        }
424        if matches!(
425            method,
426            "harness.v1.harnesses.settings" | "harness.v1.harnesses.configure"
427        ) {
428            let id = request.get("id").cloned().unwrap_or(Value::Null);
429            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
430                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
431            }
432            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
433            return match self.harness_settings_call(method, params) {
434                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
435                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
436                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
437                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
438                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
439                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
440            };
441        }
442        if method == "harness.v1.sessions.activity.subscribe" {
443            let id = request.get("id").cloned().unwrap_or(Value::Null);
444            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
445                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
446            }
447            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
448            return match self.subscribe_session_activity(params).await {
449                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
450                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
451                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
452                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
453                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
454                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
455            };
456        }
457        if let Some(operation) = SdkOperation::from_method(method) {
458            let id = request.get("id").cloned().unwrap_or(Value::Null);
459            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
460                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
461            }
462            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
463            return match self.execute(SdkRequest { operation, params }).await {
464                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
465                Err(error) => sdk_rpc_error(id, &error),
466            };
467        }
468        if !method.starts_with("harness.v1.runtimes.") {
469            return self.handle(request);
470        }
471        let id = request.get("id").cloned().unwrap_or(Value::Null);
472        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
473            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
474        }
475        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
476        match self.runtime_call(method, params).await {
477            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
478            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
479            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
480            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
481            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
482            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
483        }
484    }
485
486    /// Poll all active subscriptions once and return zero or more JSON-RPC
487    /// notifications. Recoverable follower errors are delivered as events.
488    #[cfg(feature = "adapter-api")]
489    pub fn poll(&mut self) -> Vec<Value> {
490        let mut notifications = Vec::new();
491        for (subscription, follower) in &mut self.followers {
492            match follower.poll() {
493                Ok(Some(event)) => notifications.push(json!({
494                    "jsonrpc": "2.0",
495                    "method": SESSION_EVENT_METHOD,
496                    "params": {
497                        "subscription": subscription,
498                        "event": event.to_json(),
499                    }
500                })),
501                Ok(None) => {}
502                Err(error) => notifications.push(json!({
503                    "jsonrpc": "2.0",
504                    "method": SESSION_EVENT_METHOD,
505                    "params": {
506                        "subscription": subscription,
507                        "event": {
508                            "type": "watch_error",
509                            "recoverable": true,
510                            "message": error.to_string(),
511                        },
512                    }
513                })),
514            }
515        }
516        notifications
517    }
518
519    /// Report each followed session's live-runtime lifecycle state on that
520    /// session's own subscription, emitting only when the state changes.
521    ///
522    /// A growing transcript is not evidence that an agent is working, so the
523    /// state comes from the live-runtime registry and nowhere else. A followed
524    /// session with no registered Supercode runtime — a harness running outside
525    /// Supercode — reports `persisted`, which says plainly that its activity is
526    /// unknown rather than guessing at it. These events carry no sequence
527    /// number and no transcript content; they never interleave with the
528    /// content follower's sequenced stream.
529    #[cfg(feature = "adapter-api")]
530    pub async fn poll_session_runtime_states(&mut self) -> Vec<Value> {
531        let registry = crate::LocalRuntimeRegistry::new();
532        let authorization = crate::RuntimeAuthorization::observer();
533        let mut notifications = Vec::new();
534        for (subscription, source) in &mut self.followed_sources {
535            let state = match registry
536                .source_state(&source.harness, &source.session_id, &authorization)
537                .await
538            {
539                Ok(Some(state)) => state,
540                Ok(None) => crate::RuntimeRegistryState::Persisted,
541                // A failed registry read is not evidence of a state change.
542                Err(_) => continue,
543            };
544            if source.reported.as_deref() == Some(state.as_str()) {
545                continue;
546            }
547            source.reported = Some(state.as_str().to_string());
548            notifications.push(json!({
549                "jsonrpc": "2.0",
550                "method": SESSION_EVENT_METHOD,
551                "params": {
552                    "subscription": subscription,
553                    "event": {"type": "runtime_state", "state": state.as_str()},
554                },
555            }));
556        }
557        notifications
558    }
559
560    /// Poll normalized activity subscriptions, emitting only proven state
561    /// transitions. Every subscription is bulk-sampled so stock-harness
562    /// process and registry discovery happens once per UI, not once per row.
563    #[cfg(feature = "adapter-api")]
564    pub async fn poll_session_activities(&mut self) -> Vec<Value> {
565        let subscriptions = self
566            .activity_subscriptions
567            .iter()
568            .map(|(id, subscription)| {
569                (
570                    id.clone(),
571                    subscription.locators.clone(),
572                    subscription.homes.clone(),
573                )
574            })
575            .collect::<Vec<_>>();
576        let mut notifications = Vec::new();
577        for (subscription_id, locators, homes) in subscriptions {
578            let Ok(activities) = self.activity_monitor.resolve(&locators, &homes).await else {
579                // A failed evidence read proves no transition. Retain the last
580                // good state instead of flashing every row to persisted.
581                continue;
582            };
583            let Some(subscription) = self.activity_subscriptions.get_mut(&subscription_id) else {
584                continue;
585            };
586            let mut changed = Vec::new();
587            for activity in activities {
588                let key = activity.key();
589                if subscription
590                    .reported
591                    .get(&key)
592                    .is_some_and(|previous| previous.same_state(&activity))
593                {
594                    continue;
595                }
596                subscription.reported.insert(key, activity.clone());
597                changed.push(activity);
598            }
599            if !changed.is_empty() {
600                notifications.push(json!({
601                    "jsonrpc": "2.0",
602                    "method": SESSION_ACTIVITY_EVENT_METHOD,
603                    "params": {
604                        "subscription": subscription_id,
605                        "activities": changed,
606                    },
607                }));
608            }
609        }
610        notifications
611    }
612
613    /// Drain native-store invalidations and emit revisioned descriptor deltas.
614    /// An idle subscription performs no catalog or transcript reads between
615    /// its minute-scale recovery reconciliations.
616    #[cfg(feature = "adapter-api")]
617    pub fn poll_session_indexes(&mut self) -> Vec<Value> {
618        let mut notifications = Vec::new();
619        for (subscription, index) in &mut self.index_subscriptions {
620            let homes = index.homes().clone();
621            match index.poll() {
622                Ok(Some(delta)) => match live_index_changes(delta.changes, &homes) {
623                    Ok(changes) => notifications.push(json!({
624                        "jsonrpc": "2.0",
625                        "method": SESSION_INDEX_EVENT_METHOD,
626                        "params": {
627                            "subscription": subscription,
628                            "revision": delta.revision,
629                            "changes": changes,
630                        },
631                    })),
632                    Err(error) => notifications.push(json!({
633                        "jsonrpc": "2.0",
634                        "method": SESSION_INDEX_EVENT_METHOD,
635                        "params": {
636                            "subscription": subscription,
637                            "error": {"recoverable": true, "message": error_message(error)},
638                        },
639                    })),
640                },
641                Ok(None) => {}
642                Err(error) => notifications.push(json!({
643                    "jsonrpc": "2.0",
644                    "method": SESSION_INDEX_EVENT_METHOD,
645                    "params": {
646                        "subscription": subscription,
647                        "error": {"recoverable": true, "message": error},
648                    },
649                })),
650            }
651        }
652        notifications
653    }
654
655    #[cfg(feature = "adapter-api")]
656    async fn subscribe_session_activity(
657        &mut self,
658        params: Value,
659    ) -> std::result::Result<Value, ServiceError> {
660        let params = decode::<ActivitySubscribeParams>(params)?;
661        if params.locators.is_empty() {
662            return Err(ServiceError::InvalidParams(
663                "sessions.activity.subscribe requires at least one locator".into(),
664            ));
665        }
666        if params.locators.len() > 2_048 {
667            return Err(ServiceError::InvalidParams(
668                "sessions.activity.subscribe accepts at most 2048 locators".into(),
669            ));
670        }
671        let initial = self
672            .activity_monitor
673            .resolve(&params.locators, &params.homes)
674            .await
675            .map_err(ServiceError::Sdk)?;
676        let subscription = format!("activity-sub-{}", self.next_subscription);
677        self.next_subscription += 1;
678        let reported = initial
679            .iter()
680            .cloned()
681            .map(|activity| (activity.key(), activity))
682            .collect();
683        self.activity_subscriptions.insert(
684            subscription.clone(),
685            ActivitySubscription {
686                locators: params.locators,
687                homes: params.homes,
688                reported,
689            },
690        );
691        Ok(json!({"subscription": subscription, "initial": initial}))
692    }
693
694    /// Non-blockingly sample one event from every connected live runtime.
695    #[cfg(feature = "adapter-api")]
696    pub async fn poll_runtimes(&mut self) -> Vec<Value> {
697        self.poll_sdk_events()
698            .await
699            .into_iter()
700            .map(|(connection, runtime_event)| {
701                json!({
702                    "jsonrpc": "2.0",
703                    "method": RUNTIME_EVENT_METHOD,
704                    "params": {
705                        "connection": connection,
706                        "session_id": runtime_event.session_id,
707                        "sequence": runtime_event.event.sequence,
708                        "event": {
709                            "kind": runtime_event.event.kind,
710                            "payload": runtime_event.event.payload,
711                        },
712                    },
713                })
714            })
715            .collect()
716    }
717
718    async fn poll_sdk_events(&mut self) -> Vec<(String, SdkRuntimeEvent)> {
719        let mut events = Vec::new();
720        let mut closed = Vec::new();
721        let now_ms = crate::approvals::now_ms();
722        for (connection, runtime) in &mut self.runtimes {
723            let session_id = runtime.handle().runtime_id.clone();
724            let harness = runtime.handle().harness.clone();
725            // Drain what the runtime already has: a turn is several events
726            // (updates, then the protocol's completion), and delivering one
727            // per poll would cost a poll interval each. A zero timeout takes
728            // only what is ready — an idle runtime costs nothing.
729            for _ in 0..256 {
730                match tokio::time::timeout(Duration::ZERO, runtime.next_event()).await {
731                    Ok(Ok(Some(event))) => {
732                        let terminal = event.kind == "transport_closed";
733                        // ORCH-9: a permission/approval request arrives as an
734                        // ordinary event; it becomes listable here and stops
735                        // being listable when `runtimes.respond` answers it.
736                        self.approvals
737                            .observe(connection, &harness, &session_id, &event, now_ms);
738                        let next_sequence = self
739                            .runtime_sequences
740                            .entry(session_id.clone())
741                            .or_insert(0);
742                        let sequence = event.sequence.unwrap_or_else(|| {
743                            *next_sequence = next_sequence.saturating_add(1);
744                            *next_sequence
745                        });
746                        *next_sequence = (*next_sequence).max(sequence);
747                        events.push((
748                            connection.clone(),
749                            SdkRuntimeEvent {
750                                session_id: session_id.clone(),
751                                event: SdkEvent {
752                                    sequence,
753                                    kind: event.kind,
754                                    payload: event.payload,
755                                },
756                            },
757                        ));
758                        if terminal {
759                            closed.push(connection.clone());
760                            break;
761                        }
762                    }
763                    Ok(Ok(None)) => {
764                        let sequence = self
765                            .runtime_sequences
766                            .entry(session_id.clone())
767                            .or_insert(0);
768                        *sequence = sequence.saturating_add(1);
769                        events.push((
770                        connection.clone(),
771                        SdkRuntimeEvent {
772                            session_id,
773                            event: SdkEvent {
774                                sequence: *sequence,
775                                kind: "transport_closed".into(),
776                                payload: json!({"message": "Harness runtime transport closed."}),
777                            },
778                        },
779                    ));
780                        closed.push(connection.clone());
781                        break;
782                    }
783                    Err(_) => break,
784                    Ok(Err(error)) => {
785                        let sequence = self
786                            .runtime_sequences
787                            .entry(session_id.clone())
788                            .or_insert(0);
789                        *sequence = sequence.saturating_add(1);
790                        events.push((
791                        connection.clone(),
792                        SdkRuntimeEvent {
793                            session_id,
794                            event: SdkEvent {
795                                sequence: *sequence,
796                                kind: "transport_error".into(),
797                                payload: json!({"message": error.to_string(), "terminal": true}),
798                            },
799                        },
800                    ));
801                        closed.push(connection.clone());
802                        break;
803                    }
804                }
805            }
806        }
807        for connection in closed {
808            if let Some(runtime) = self.runtimes.remove(&connection) {
809                self.runtime_sequences.remove(&runtime.handle().runtime_id);
810            }
811            self.terminal_launches.remove(&connection);
812            // A connection that is gone cannot answer anything it was
813            // holding; those requests stop being listable with it.
814            self.approvals.forget(&connection);
815        }
816        events
817    }
818
819    fn call(&mut self, method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
820        match method {
821            "harness.v1.capabilities" => Ok(json!({
822                "version": HARNESS_SERVICE_VERSION,
823                "sdk": self.capabilities(),
824                "methods": HARNESS_SERVICE_METHODS,
825                "notifications": [
826                    SESSION_EVENT_METHOD,
827                    SESSION_ACTIVITY_EVENT_METHOD,
828                    SESSION_INDEX_EVENT_METHOD,
829                    RUNTIME_EVENT_METHOD
830                ],
831                "harnesses": harness_support_registry()
832                    .harnesses
833                    .into_iter()
834                    .map(|harness| harness.id)
835                    .collect::<Vec<_>>(),
836            })),
837            "harness.v1.support.report" => serde_json::to_value(harness_support_registry())
838                .map_err(|error| ServiceError::Operation(error.to_string())),
839            "harness.v1.profiles.list" | "harness.v1.profiles.get" => profiles_call(method, params),
840            // ORCH-21 controlled tier. Each verb translates to the HARNESS'S
841            // OWN profile verb and runs it (`crate::profiles_control`);
842            // supercode makes and removes nothing itself. The row returned is
843            // re-read through the ORCH-10 loader afterwards, and `ran`
844            // narrates the exact command.
845            "harness.v1.profiles.create" => {
846                mutate_profile(crate::profiles_control::ProfileVerb::Create, params)
847            }
848            "harness.v1.profiles.delete" => {
849                mutate_profile(crate::profiles_control::ProfileVerb::Delete, params)
850            }
851            "harness.v1.channels.list" | "harness.v1.channels.status" => {
852                channels_call(method, params)
853            }
854            // ORCH-15 observed tier: which profile / agent a surface tuple
855            // resolves to, read from each gateway harness's own config.
856            "harness.v1.routes.list" => routes_call(params),
857            // ORCH-16 observed tier: inbound webhook routes / hook mappings.
858            "harness.v1.triggers.list" => triggers_call(params),
859            // ONT-4: the orchestration doors. One home folder in, one typed orchestration
860            // value out (and back). Every one of the four is
861            // `crate::orchestration_doors`, which the `supercode orchestration` verbs call
862            // too — the RPC adds nothing but the envelope. A vault VALUE
863            // never crosses this wire: a load or a compile answers with the
864            // `.env` KEY NAMES, and a caller that needs a value reads the
865            // home's own `.env`.
866            // the workflow layer's read door: a harness's board as one typed value,
867            // the same code the `supercode workflow load` verb calls
868            "harness.v1.workflow.load" => {
869                let params = decode::<WorkflowLoadParams>(params)?;
870                let read =
871                    crate::workflow_doors::load(params.from, &params.home).map_err(operation)?;
872                serde_json::to_value(read)
873                    .map_err(|error| ServiceError::Operation(error.to_string()))
874            }
875            "harness.v1.orchestration.load" => {
876                let params = decode::<OrchestrationLoadParams>(params)?;
877                let read = crate::orchestration_doors::load(&params.root, params.flavor)
878                    .map_err(operation)?;
879                serde_json::to_value(read)
880                    .map_err(|error| ServiceError::Operation(error.to_string()))
881            }
882            "harness.v1.orchestration.save" => {
883                let params = decode::<OrchestrationSaveParams>(params)?;
884                let saved = crate::orchestration_doors::save(
885                    &params.root,
886                    params.orchestration,
887                    params.vault,
888                )
889                .map_err(operation)?;
890                serde_json::to_value(saved)
891                    .map_err(|error| ServiceError::Operation(error.to_string()))
892            }
893            "harness.v1.orchestration.compile" => {
894                let params = decode::<OrchestrationCompileParams>(params)?;
895                let read = crate::orchestration_doors::compile(params.from, &params.home)
896                    .map_err(operation)?;
897                serde_json::to_value(read)
898                    .map_err(|error| ServiceError::Operation(error.to_string()))
899            }
900            "harness.v1.orchestration.decompile" => {
901                let params = decode::<OrchestrationDecompileParams>(params)?;
902                let report = crate::orchestration_doors::decompile(
903                    params.to,
904                    params.orchestration,
905                    &params.source,
906                    params.source_flavor,
907                    &params.dest,
908                    params.vault,
909                )
910                .map_err(operation)?;
911                serde_json::to_value(report)
912                    .map_err(|error| ServiceError::Operation(error.to_string()))
913            }
914            // a migration keeps the credential in this process: a compile and
915            // a save (import), a load and a decompile (export), composed here
916            // because composed by a client the secret would have to cross
917            // the wire
918            "harness.v1.orchestration.import" => {
919                let params = decode::<OrchestrationImportParams>(params)?;
920                let imported =
921                    crate::orchestration_doors::import(params.from, &params.home, &params.into)
922                        .map_err(operation)?;
923                serde_json::to_value(imported)
924                    .map_err(|error| ServiceError::Operation(error.to_string()))
925            }
926            "harness.v1.orchestration.export" => {
927                let params = decode::<OrchestrationExportParams>(params)?;
928                let report =
929                    crate::orchestration_doors::export(params.to, &params.root, &params.dest)
930                        .map_err(operation)?;
931                serde_json::to_value(report)
932                    .map_err(|error| ServiceError::Operation(error.to_string()))
933            }
934            // ORCH-12 observed tier: read and search the persistent memory
935            // documents a harness keeps on disk. Read-only — every write
936            // (`hermes memory off`, `openclaw memory forget|reset`, Claude
937            // Code's `/memory`) stays the harness's own verb. A harness with
938            // no memory store is refused with UnsupportedAction.
939            "harness.v1.memory.show" | "harness.v1.memory.search" => memory_call(method, params),
940            // ORCH-11 observed tier: read-only enumeration of every harness's
941            // installed skill packages. An unknown harness id is refused with
942            // UnsupportedAction — every harness supports skills, so a filter
943            // that matches nothing is a caller error, never an empty listing.
944            "harness.v1.skills.list" => {
945                let query = decode::<crate::skills::SkillsQuery>(params)?;
946                if let Some(harness) = query.harness.as_deref() {
947                    if !crate::skills::SKILL_HARNESSES.contains(&harness) {
948                        return Err(ServiceError::UnsupportedAction(format!(
949                            "`{harness}` has no skills root supercode reads"
950                        )));
951                    }
952                }
953                serde_json::to_value(crate::skills::list_skills(&query))
954                    .map_err(|error| ServiceError::Operation(error.to_string()))
955            }
956            // ORCH-22 controlled tier: each verb goes through the door the
957            // HARNESS publishes — `hermes skills install|uninstall`,
958            // `openclaw skills install`, and for the core four the loader's
959            // own directory, which is the only skills door those harnesses
960            // have. supercode resolves no registry and unpacks no archive.
961            // The row returned is re-read through the ORCH-11 loader
962            // afterwards, and `ran` narrates exactly what was performed.
963            "harness.v1.skills.install" => {
964                mutate_skill(crate::skills_control::SkillVerb::Install, params)
965            }
966            "harness.v1.skills.remove" => {
967                mutate_skill(crate::skills_control::SkillVerb::Remove, params)
968            }
969            // ORCH-9 observed tier: the approval requests waiting for an
970            // answer. At the pinned harness versions the only uniform source
971            // is a LIVE request held by an open runtime connection, plus
972            // supercode's own queued subagent approvals — neither Hermes
973            // 0.21.0 nor OpenClaw 2026.7.1-2 has an approvals door to read
974            // (see `crate::approvals`). A harness whose runtime cannot carry
975            // a protocol request at all is refused by name.
976            "harness.v1.approvals.list" => {
977                let query = decode::<crate::approvals::ApprovalsQuery>(params)?;
978                if let Some(harness) = query.harness.as_deref() {
979                    if !crate::approvals::lists_approvals(harness) {
980                        return Err(ServiceError::UnsupportedAction(format!(
981                            "`{harness}` has no runtime door that carries an approval request"
982                        )));
983                    }
984                }
985                serde_json::to_value(self.approvals(&query))
986                    .map_err(|error| ServiceError::Operation(error.to_string()))
987            }
988            "harness.v1.sessions.discover" => {
989                let query = decode::<DiscoveryQuery>(params)?;
990                let page = discover_session_page(&query).map_err(operation)?;
991                // Claude Code is the one harness that publishes its RUNNING
992                // sessions. The registry is read once per discovery and joined
993                // by session id; every record in it has already survived a
994                // `kill(pid, 0)` liveness check inside `read_registry`.
995                let peers = if page
996                    .sessions
997                    .iter()
998                    .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
999                {
1000                    crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(
1001                        &query.homes,
1002                    ))
1003                } else {
1004                    Vec::new()
1005                };
1006                let activities = crate::session_activity::resolve_stock_session_activities(
1007                    &page
1008                        .sessions
1009                        .iter()
1010                        .map(|session| session.locator.clone())
1011                        .collect::<Vec<_>>(),
1012                    &query.homes,
1013                )
1014                .into_iter()
1015                .map(|activity| (activity.key(), activity))
1016                .collect::<BTreeMap<_, _>>();
1017                let sessions = page
1018                    .sessions
1019                    .into_iter()
1020                    .map(|session| {
1021                        let mut value = live_descriptor_value(&session, &peers)?;
1022                        let activity_key = (
1023                            session.locator.harness.as_str().to_string(),
1024                            session.locator.session_id.clone(),
1025                        );
1026                        if let Some(activity) = activities.get(&activity_key) {
1027                            value["activity"] = serde_json::to_value(activity)
1028                                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1029                            if let Some(status) = legacy_live_status(activity) {
1030                                value["live_status"] = json!(status);
1031                            }
1032                        }
1033                        Ok(value)
1034                    })
1035                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1036                let mut result = json!({"sessions": sessions, "next_cursor": page.next_cursor});
1037                // Preserve the metadata-only wire shape, but carry the catalog's
1038                // proof/counts when the caller explicitly requests preview search.
1039                if query.search_previews {
1040                    result["receipt"] = serde_json::to_value(page.receipt)
1041                        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1042                }
1043                Ok(result)
1044            }
1045            "harness.v1.sessions.load" => {
1046                let params = decode::<LoadSessionParams>(params)?;
1047                if let Some(options) = &params.options {
1048                    options.validate()?;
1049                    if let Some(result) = indexed_claude_window(&params.read.locator, options)? {
1050                        return Ok(result);
1051                    }
1052                    return load_session(&params.read.locator)
1053                        .map(|session| projected_session_result(&session, options))
1054                        .map_err(operation);
1055                }
1056                let mut session = if params.read.display_history() {
1057                    self.catalog
1058                        .load_display_view(
1059                            &params.read.locator,
1060                            params.read.read_fidelity(),
1061                            params.read.tail_messages().unwrap_or(500),
1062                        )
1063                        .map_err(crate::Error::from)
1064                } else if params.read.include_subagents() {
1065                    load_session_with_fidelity(&params.read.locator, params.read.read_fidelity())
1066                } else {
1067                    self.catalog
1068                        .load_parent_with_fidelity(
1069                            &params.read.locator,
1070                            params.read.read_fidelity(),
1071                        )
1072                        .map_err(crate::Error::from)
1073                }
1074                .map_err(operation)?;
1075                params.read.bound_session(&mut session);
1076                Ok(json!({"session": normalized_session_json(&session)}))
1077            }
1078            "harness.v1.sessions.follow" => {
1079                let params = decode::<LocatorParams>(params)?;
1080                let mut follower = self
1081                    .catalog
1082                    .follow_read_view(
1083                        &params.locator,
1084                        params.read_fidelity(),
1085                        params.include_subagents(),
1086                        params.tail_messages(),
1087                        params.max_message_chars(),
1088                        params.display_history(),
1089                    )
1090                    .map_err(operation)?;
1091                let initial = follower
1092                    .poll()
1093                    .map_err(operation)?
1094                    .map(|event| event.to_json());
1095                let subscription = format!("sub-{}", self.next_subscription);
1096                self.next_subscription += 1;
1097                self.followers.insert(subscription.clone(), follower);
1098                self.followed_sources.insert(
1099                    subscription.clone(),
1100                    FollowedSource {
1101                        harness: params.locator.harness.as_str().to_string(),
1102                        session_id: params.locator.session_id.clone(),
1103                        reported: None,
1104                    },
1105                );
1106                Ok(json!({"subscription": subscription, "initial": initial}))
1107            }
1108            "harness.v1.sessions.unfollow" => {
1109                let params = decode::<UnfollowParams>(params)?;
1110                self.followed_sources.remove(&params.subscription);
1111                Ok(json!({
1112                    "removed": self.followers.remove(&params.subscription).is_some()
1113                }))
1114            }
1115            "harness.v1.sessions.activity.unsubscribe" => {
1116                let params = decode::<UnfollowParams>(params)?;
1117                Ok(json!({
1118                    "removed": self.activity_subscriptions.remove(&params.subscription).is_some()
1119                }))
1120            }
1121            "harness.v1.sessions.index.subscribe" => {
1122                let query = decode::<DiscoveryQuery>(params)?;
1123                crate::session_index::validate_query(&query)
1124                    .map_err(ServiceError::InvalidParams)?;
1125                let homes = query.homes.clone();
1126                let (index, initial) = crate::session_index::SessionIndexSubscription::open(
1127                    query,
1128                    Arc::clone(&self.index_notifier),
1129                )
1130                .map_err(ServiceError::Operation)?;
1131                let peers = peers_for_descriptors(&initial, &homes);
1132                let initial = initial
1133                    .iter()
1134                    .map(|descriptor| live_descriptor_value(descriptor, &peers))
1135                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1136                let subscription = format!("index-sub-{}", self.next_subscription);
1137                self.next_subscription += 1;
1138                self.index_subscriptions.insert(subscription.clone(), index);
1139                Ok(json!({
1140                    "subscription": subscription,
1141                    "revision": 1,
1142                    "initial": initial,
1143                }))
1144            }
1145            "harness.v1.sessions.index.resize" => {
1146                let params = decode::<IndexResizeParams>(params)?;
1147                crate::session_index::validate_limit(params.limit)
1148                    .map_err(ServiceError::InvalidParams)?;
1149                let index = self
1150                    .index_subscriptions
1151                    .get_mut(&params.subscription)
1152                    .ok_or_else(|| {
1153                        ServiceError::InvalidParams("unknown session index subscription".into())
1154                    })?;
1155                let prepared = index
1156                    .prepare_resize(params.limit)
1157                    .map_err(ServiceError::Operation)?;
1158                let peers = peers_for_descriptors(&prepared.page.sessions, index.homes());
1159                let initial = prepared
1160                    .page
1161                    .sessions
1162                    .iter()
1163                    .map(|descriptor| live_descriptor_value(descriptor, &peers))
1164                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1165                let response = json!({
1166                    "subscription": params.subscription,
1167                    "revision": prepared.revision,
1168                    "initial": initial,
1169                    "receipt": prepared.page.receipt,
1170                });
1171                index.commit_resize(prepared);
1172                Ok(response)
1173            }
1174            "harness.v1.sessions.index.unsubscribe" => {
1175                let params = decode::<UnfollowParams>(params)?;
1176                Ok(json!({
1177                    "removed": self.index_subscriptions.remove(&params.subscription).is_some()
1178                }))
1179            }
1180            "harness.v1.sessions.import" => {
1181                let params = decode::<ImportSessionParams>(params)?;
1182                let session = Session::load_str(&params.content, params.source_harness.into())
1183                    .map_err(operation)?;
1184                Ok(json!({"session": normalized_session_json(&session)}))
1185            }
1186            "harness.v1.sessions.export" | "harness.v1.sessions.translate" => {
1187                let params = decode::<ExportSessionParams>(params)?;
1188                let session = load_session(&params.locator).map_err(operation)?;
1189                let artifact = session_artifact(&params.locator, &session, params.target_harness)?;
1190                if method == "harness.v1.sessions.export"
1191                    && params.target_harness == TransferFormat::Hermes
1192                {
1193                    // UNI-18: write through Hermes's own door, never into its store
1194                    let imported = crate::hermes_import::import_into_hermes(&session, None)
1195                        .map_err(operation)?;
1196                    return Ok(json!({"artifact": artifact, "imported": imported}));
1197                }
1198                Ok(json!({"artifact": artifact}))
1199            }
1200            "harness.v1.sessions.reduce" => {
1201                let params = decode::<ReduceSessionParams>(params)?;
1202                self.reduce_session(params)
1203            }
1204            "harness.v1.sessions.branch" => {
1205                let params = decode::<BranchSessionParams>(params)?;
1206                let session = load_session(&params.locator).map_err(operation)?;
1207                let storage = params.locator.storage.path().display().to_string();
1208                let bootstrap_prompt = format!(
1209                    "Continue as a new branch from {} session {}. The frozen parent transcript is at {}. Read or load that parent for context, summarize the relevant state, then continue independently without mutating the parent session.",
1210                    params.locator.harness.as_str(), params.locator.session_id, storage
1211                );
1212                let artifact = params
1213                    .target_harness
1214                    .map(|target| session_artifact(&params.locator, &session, target))
1215                    .transpose()?;
1216                Ok(json!({
1217                    "parent": params.locator,
1218                    "session": normalized_session_json(&session),
1219                    "bootstrap_prompt": bootstrap_prompt,
1220                    "artifact": artifact,
1221                }))
1222            }
1223            "harness.v1.sessions.handoff" => {
1224                let params = decode::<HandoffSessionParams>(params)?;
1225                let session = load_session(&params.locator).map_err(operation)?;
1226                let cwd = params
1227                    .cwd
1228                    .or_else(|| session.meta.cwd.clone())
1229                    .unwrap_or_else(|| PathBuf::from("."));
1230                let artifact = handoff_artifact(&params.locator, &session, params.target_harness)?;
1231                let target_session_id = artifact.session_id.as_deref().ok_or_else(|| {
1232                    ServiceError::Operation(
1233                        "handoff artifact omitted target session identity".into(),
1234                    )
1235                })?;
1236                let instructions =
1237                    handoff_instructions(params.target_harness, target_session_id, &cwd);
1238                Ok(json!({
1239                    "artifact": artifact,
1240                    "launch": instructions.launch,
1241                    "materialize": instructions.materialize,
1242                    "requires_materialization": instructions.requires_materialization,
1243                    "note": instructions.note,
1244                }))
1245            }
1246            "harness.v1.sessions.materialize" => {
1247                let params = decode::<MaterializeSessionParams>(params)?;
1248                // An artifact from another machine carries its whole source as a recovery file;
1249                // keeping its segments here lets a later write back to that format restore it
1250                // byte for byte on this machine too (docs/plans/portable-residue.md).
1251                for file in &params.artifact.files {
1252                    if file.role == "source_recovery"
1253                        && file.path == "recovery/source.supercode.jsonl"
1254                    {
1255                        if let Ok(source) = Session::from_native_str(&file.content) {
1256                            crate::residue_store::store_segments(&source);
1257                        }
1258                    }
1259                }
1260                let locator = crate::native_materialize::materialize_native_artifact(
1261                    params.artifact,
1262                    &params.cwd,
1263                    &params.homes,
1264                )
1265                .map_err(ServiceError::Operation)?;
1266                Ok(json!({"locator": locator}))
1267            }
1268            // ORCH-7 observed tier. Read-only: the handlers open the harness's
1269            // own job store (Claude Code's session JSONL, Hermes's and
1270            // OpenClaw's `cron/jobs.json`) and never write, fire, or schedule.
1271            "harness.v1.jobs.list" => {
1272                let query = decode::<crate::jobs::JobsQuery>(params)?;
1273                if let Some(harness) = query.harness.as_deref() {
1274                    refuse_harness_without_jobs(harness, "jobs.list")?;
1275                }
1276                let listing = crate::jobs::list_jobs(&query).map_err(operation)?;
1277                serde_json::to_value(listing)
1278                    .map_err(|error| ServiceError::Operation(error.to_string()))
1279            }
1280            "harness.v1.jobs.get" => {
1281                let params = decode::<JobsGetParams>(params)?;
1282                refuse_harness_without_jobs(&params.harness, "jobs.get")?;
1283                match crate::jobs::get_job(&params.harness, &params.id, &params.homes)
1284                    .map_err(operation)?
1285                {
1286                    Some((job, source)) => Ok(json!({"job": job, "source": source})),
1287                    None => Err(ServiceError::Operation(format!(
1288                        "`{}` has no scheduled job `{}`",
1289                        params.harness, params.id
1290                    ))),
1291                }
1292            }
1293            // ORCH-18 controlled tier. Each verb translates to the HARNESS'S
1294            // OWN cron verb and runs it (`crate::jobs_control`); supercode
1295            // schedules nothing. The row returned is re-read from the
1296            // harness's store afterwards, and `ran` narrates the exact command
1297            // with any credential redacted.
1298            "harness.v1.jobs.create" => mutate_job(crate::jobs_control::JobVerb::Create, params),
1299            "harness.v1.jobs.update" => mutate_job(crate::jobs_control::JobVerb::Update, params),
1300            "harness.v1.jobs.pause" => mutate_job(crate::jobs_control::JobVerb::Pause, params),
1301            "harness.v1.jobs.resume" => mutate_job(crate::jobs_control::JobVerb::Resume, params),
1302            "harness.v1.jobs.run" => mutate_job(crate::jobs_control::JobVerb::Run, params),
1303            "harness.v1.jobs.delete" => mutate_job(crate::jobs_control::JobVerb::Delete, params),
1304            "harness.v1.jobs.notepad"
1305            | "harness.v1.jobs.notepad_set"
1306            | "harness.v1.jobs.notepad_delete" => {
1307                let request = decode::<crate::jobs_notepad::JobNotepadRequest>(params)?;
1308                refuse_harness_without_jobs(&request.harness, "jobs.notepad")?;
1309                let answer = match method {
1310                    "harness.v1.jobs.notepad_set" => crate::jobs_notepad::set(&request),
1311                    "harness.v1.jobs.notepad_delete" => crate::jobs_notepad::delete(&request),
1312                    _ => crate::jobs_notepad::read(&request),
1313                }
1314                .map_err(job_control_error)?;
1315                serde_json::to_value(answer)
1316                    .map_err(|error| ServiceError::Operation(error.to_string()))
1317            }
1318            // ORCH-8 observed tier. Read-only: the handlers open the harness's
1319            // own run store (Hermes's `cron/executions.db`, OpenClaw's
1320            // `cron_run_logs`) and never claim, retry, or prune a fire.
1321            "harness.v1.runs.list" => {
1322                let query = decode::<crate::runs::RunsQuery>(params)?;
1323                if let Some(harness) = query.harness.as_deref() {
1324                    refuse_harness_without_runs(harness, "runs.list")?;
1325                }
1326                let listing = crate::runs::list_runs(&query).map_err(operation)?;
1327                serde_json::to_value(listing)
1328                    .map_err(|error| ServiceError::Operation(error.to_string()))
1329            }
1330            "harness.v1.runs.get" => {
1331                let params = decode::<RunsGetParams>(params)?;
1332                refuse_harness_without_runs(&params.harness, "runs.get")?;
1333                match crate::runs::get_run(&params.harness, &params.id, &params.homes)
1334                    .map_err(operation)?
1335                {
1336                    Some((run, source)) => Ok(json!({"run": run, "source": source})),
1337                    None => Err(ServiceError::Operation(format!(
1338                        "`{}` has no run `{}`",
1339                        params.harness, params.id
1340                    ))),
1341                }
1342            }
1343            "harness.v1.sessions.resume_instructions" => {
1344                let params = decode::<ResumeInstructionsParams>(params)?;
1345                let session = load_session(&params.locator).map_err(operation)?;
1346                let cwd = params
1347                    .cwd
1348                    .or(session.meta.cwd)
1349                    .unwrap_or_else(|| PathBuf::from("."));
1350                let launch = resume_launch(
1351                    params.locator.harness.as_str(),
1352                    &params.locator.session_id,
1353                    &cwd,
1354                    params.policy,
1355                )?;
1356                Ok(json!({"launch": launch}))
1357            }
1358            _ => Err(ServiceError::MethodNotFound),
1359        }
1360    }
1361
1362    fn reduce_session(
1363        &self,
1364        params: ReduceSessionParams,
1365    ) -> std::result::Result<Value, ServiceError> {
1366        let session = load_session(&params.locator).map_err(operation)?;
1367        if session.messages.is_empty() {
1368            return Err(ServiceError::InvalidParams(
1369                "cannot reduce an empty session".into(),
1370            ));
1371        }
1372        let keep_last = params.keep_last.clamp(1, 128);
1373        let policy = reduce::ReductionPolicy {
1374            clear_turns_older_than: Some(keep_last),
1375            ..Default::default()
1376        };
1377        let (view, log) =
1378            reduce::project_messages(&session.messages, &policy, &reduce::ReductionLog::default());
1379        if log.reductions.is_empty() {
1380            return Err(ServiceError::UnsupportedAction(format!(
1381                "session `{}` is already too small for a meaningful reversible reduction",
1382                params.locator.session_id
1383            )));
1384        }
1385        let source_tokens = tokens::estimate_view_tokens(&session.messages);
1386        let reduced_tokens = tokens::estimate_view_tokens(&view);
1387        if reduced_tokens >= source_tokens {
1388            return Err(ServiceError::UnsupportedAction(format!(
1389                "session `{}` has no token-reducing reversible projection",
1390                params.locator.session_id
1391            )));
1392        }
1393
1394        let store_root = self
1395            .reduction_store_root
1396            .clone()
1397            .unwrap_or_else(default_reduction_store_root);
1398        let store = crate::SessionStore::open(&store_root).map_err(operation)?;
1399        let rescue_id = format!("rescue-{}", generated_session_id());
1400        let imported = session
1401            .imported_message_count
1402            .unwrap_or(session.messages.len())
1403            .min(session.messages.len());
1404        let sidecar_jsonl = session.to_native_jsonl_v2(&session.messages[imported..]);
1405        let view_jsonl = messages_jsonl(&view)?;
1406        let title = format!(
1407            "Reduced {} continuation from {}",
1408            params.target_harness.id(),
1409            params.locator.session_id
1410        );
1411
1412        // Durability order is intentional: the full source of truth lands
1413        // before either object that can refer to it. A crash may leave an
1414        // unused sidecar, but can never leave a reduced view whose originals
1415        // were not durably written first.
1416        store
1417            .save_sidecar(&rescue_id, &sidecar_jsonl)
1418            .map_err(operation)?;
1419        store
1420            .save_reduction_log(&rescue_id, &log)
1421            .map_err(operation)?;
1422        store
1423            .save(&rescue_id, &title, &view_jsonl)
1424            .map_err(operation)?;
1425
1426        let source_bytes = serde_json::to_vec(&session.messages)
1427            .map_err(|error| ServiceError::Operation(error.to_string()))?
1428            .len() as u64;
1429        let reduced_bytes = serde_json::to_vec(&view)
1430            .map_err(|error| ServiceError::Operation(error.to_string()))?
1431            .len() as u64;
1432        store
1433            .set_reduction_stats(
1434                &rescue_id,
1435                &title,
1436                source_bytes,
1437                reduced_bytes,
1438                log.reductions.len() as u32,
1439            )
1440            .map_err(operation)?;
1441
1442        // The receipt is issued only after a real disk reload. This proves
1443        // the exact files another process will consume, not the convenient
1444        // in-memory values that produced them.
1445        let reloaded_sidecar = store
1446            .load_sidecar(&rescue_id)
1447            .map_err(operation)?
1448            .ok_or_else(|| ServiceError::Operation("reduction sidecar disappeared".into()))?;
1449        let reloaded_sidecar = Session::from_sidecar_str(&reloaded_sidecar).map_err(operation)?;
1450        let reloaded_log = store
1451            .load_reduction_log(&rescue_id)
1452            .map_err(operation)?
1453            .ok_or_else(|| ServiceError::Operation("reduction log disappeared".into()))?;
1454        let reloaded_view = parse_messages_jsonl(&store.load(&rescue_id).map_err(operation)?)?;
1455        reduce::verify_log(&reloaded_log, &reloaded_sidecar).map_err(operation)?;
1456        // `sc.reduction` is deliberately in-memory-only metadata: it must
1457        // never leak onto a provider-facing transcript. Reapplying the
1458        // durable log to the durable sidecar restores those ids. Comparing
1459        // its wire form with the transcript reloaded above proves that the
1460        // persisted view is exactly the deterministic projection before we
1461        // use the restamped form for inversion.
1462        let (restamped_view, restamped_log) =
1463            reduce::project_messages(&reloaded_sidecar.messages, &policy, &reloaded_log);
1464        if messages_jsonl(&restamped_view)? != messages_jsonl(&reloaded_view)? {
1465            return Err(ServiceError::Operation(
1466                "persisted reduction view does not match its durable log and sidecar".into(),
1467            ));
1468        }
1469        if restamped_log != reloaded_log {
1470            return Err(ServiceError::Operation(
1471                "reapplying the durable reduction log changed its identity".into(),
1472            ));
1473        }
1474        let inverted =
1475            reduce::invert(&restamped_view, &reloaded_log, &reloaded_sidecar).map_err(operation)?;
1476        if inverted != session.messages {
1477            return Err(ServiceError::Operation(
1478                "reduction inversion did not restore the source messages byte-exactly".into(),
1479            ));
1480        }
1481
1482        let ratio = source_tokens as f64 / reduced_tokens.max(1) as f64;
1483        let sidecar_path = store.sidecar_path(&rescue_id);
1484        let reduction_log_path = store.reduction_log_path(&rescue_id).map_err(operation)?;
1485        let bootstrap_prompt = reduced_bootstrap_prompt(
1486            &params.locator,
1487            params.target_harness,
1488            &view_jsonl,
1489            &sidecar_path,
1490            &reduction_log_path,
1491        );
1492        let mut reduced_session = session.clone();
1493        reduced_session.meta.session_id = Some(rescue_id.clone());
1494        reduced_session.messages = view;
1495
1496        Ok(json!({
1497            "session": normalized_session_json(&reduced_session),
1498            "bootstrap_prompt": bootstrap_prompt,
1499            "receipt": {
1500                "id": rescue_id,
1501                "sidecar_id": rescue_id,
1502                "source_harness": params.locator.harness,
1503                "target_harness": params.target_harness.id(),
1504                "source_tokens": source_tokens,
1505                "reduced_tokens": reduced_tokens,
1506                "ratio": ratio,
1507                "source_bytes": source_bytes,
1508                "reduced_bytes": reduced_bytes,
1509                "reductions": reloaded_log.reductions.len(),
1510                "sidecar_path": sidecar_path,
1511                "reduction_log_path": reduction_log_path,
1512                "verified": true,
1513                "reversible": true,
1514            }
1515        }))
1516    }
1517
1518    /// Recognize the one request family whose waiting happens entirely
1519    /// outside this service's state, and hand a transport the half it can run
1520    /// off the task that owns the service.
1521    ///
1522    /// Opening a runtime is the only door here that waits on a foreign
1523    /// program: it spawns the harness's own binary and completes that
1524    /// program's protocol handshake, which takes as long as the program takes
1525    /// to answer. A transport that awaited the whole request inline would
1526    /// stop reading its own input for that whole time, so ONE slow launch
1527    /// would queue every later request on the same server — including reads
1528    /// like `sessions.discover` that touch no runtime at all. Splitting the
1529    /// request lets the transport spawn [`RuntimeOpen::open`] and keep
1530    /// reading, then pay only the short bookkeeping half
1531    /// ([`Self::register_open_runtime`]) when the runtime is up.
1532    ///
1533    /// `None` for every other method: those are answered by
1534    /// [`Self::handle_async`] as before.
1535    pub fn runtime_open(request: &Value) -> Option<RuntimeOpen> {
1536        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1537            return None;
1538        }
1539        let method = request.get("method").and_then(Value::as_str)?;
1540        if !RUNTIME_OPEN_METHODS.contains(&method) {
1541            return None;
1542        }
1543        Some(RuntimeOpen {
1544            id: request.get("id").cloned().unwrap_or(Value::Null),
1545            method: method.to_string(),
1546            params: request.get("params").cloned().unwrap_or_else(|| json!({})),
1547        })
1548    }
1549
1550    /// Recognize a [`DETACHED_METHODS`] request and hand a transport the
1551    /// whole of it: the service-state half is read here and now, and what
1552    /// remains waits on a foreign program with nothing of this service's in
1553    /// hand.
1554    ///
1555    /// Same reason as [`Self::runtime_open`], different doors. Probing a
1556    /// harness starts it and completes its handshake; couriering a message
1557    /// runs a `claude` process to completion; a conversation verb runs the
1558    /// harness's own CLI or calls its HTTP API. A transport that awaited any
1559    /// of those inline would stop reading its own input for that whole time,
1560    /// so one probe of an unhealthy harness would queue every later request
1561    /// on the same server.
1562    ///
1563    /// Unlike an opening runtime there is no bookkeeping half: the answer
1564    /// [`DetachedCall::run`] produces is the caller's complete response, so a
1565    /// transport writes it without coming back here.
1566    ///
1567    /// `None` for every other method — including the LIVE `sessions.new` /
1568    /// `sessions.reset` door and `runtimes.close`, which wait on a runtime
1569    /// connection this service owns and so are split off by
1570    /// [`Self::detach_runtime`] instead.
1571    pub fn detach(&self, request: &Value) -> Option<DetachedCall> {
1572        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1573            return None;
1574        }
1575        let method = request.get("method").and_then(Value::as_str)?;
1576        if !DETACHED_METHODS.contains(&method) {
1577            return None;
1578        }
1579        let id = request.get("id").cloned().unwrap_or(Value::Null);
1580        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1581        let work = match method {
1582            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe" => self
1583                .inventory_work(method, params)
1584                .map(DetachedWork::Inventory),
1585            "harness.v1.sessions.message" => {
1586                decode::<MessageSessionParams>(params).map(DetachedWork::Message)
1587            }
1588            _ => {
1589                let verb = match method {
1590                    "harness.v1.sessions.new" => crate::SessionVerb::New,
1591                    "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
1592                    "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
1593                    _ => crate::SessionVerb::Delete,
1594                };
1595                match decode::<crate::SessionMutation>(params) {
1596                    Ok(mutation) => {
1597                        match crate::sessions_control::door(&mutation.harness, verb) {
1598                            // The live door needs the open runtime connection
1599                            // this service owns; it stays inline.
1600                            Ok(crate::SessionDoor::Live(_)) => return None,
1601                            Ok(_) => Ok(DetachedWork::SessionMutation { verb, mutation }),
1602                            Err(error) => Err(session_control_error(error)),
1603                        }
1604                    }
1605                    Err(error) => Err(error),
1606                }
1607            }
1608        };
1609        Some(DetachedCall {
1610            id,
1611            method: method.to_string(),
1612            work: work.map(Work::Free),
1613        })
1614    }
1615
1616    /// Recognize the two doors that wait on a runtime THIS SERVICE OWNS, and
1617    /// hand a transport the whole of each by lending the connection out.
1618    ///
1619    /// `runtimes.close` surrenders its runtime for good; the LIVE
1620    /// `sessions.new` / `sessions.reset` door borrows one for the length of
1621    /// the slash command and gives it back through
1622    /// [`Self::finish_detached`]. Both are bounded by
1623    /// [`RUNTIME_CONTROL_DEADLINE`], and a wedged runtime spends all of it —
1624    /// which is exactly as long as a transport that awaited them inline would
1625    /// stop reading its own input.
1626    ///
1627    /// `None` for every other method, and for the `sessions.new` /
1628    /// `sessions.reset` doors that are not live: [`Self::detach`] owns those.
1629    pub fn detach_runtime(&mut self, request: &Value) -> Option<DetachedCall> {
1630        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1631            return None;
1632        }
1633        let method = request.get("method").and_then(Value::as_str)?;
1634        let id = request.get("id").cloned().unwrap_or(Value::Null);
1635        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1636        let work = match method {
1637            "harness.v1.runtimes.close" => decode::<RuntimeConnectionParams>(params)
1638                .and_then(|params| self.surrender_runtime(&params.connection))
1639                .map(|(runtime, process_group)| {
1640                    Work::Runtime(RuntimeWork::Close {
1641                        runtime,
1642                        process_group,
1643                    })
1644                }),
1645            "harness.v1.sessions.new" | "harness.v1.sessions.reset" => {
1646                let verb = if method == "harness.v1.sessions.new" {
1647                    crate::SessionVerb::New
1648                } else {
1649                    crate::SessionVerb::Reset
1650                };
1651                let mutation = decode::<crate::SessionMutation>(params).ok()?;
1652                // Everything but the live door — including a refusal and a
1653                // request naming no connection — is `detach`'s or
1654                // `handle_async`'s to answer.
1655                let Ok(crate::SessionDoor::Live(command)) =
1656                    crate::sessions_control::door(&mutation.harness, verb)
1657                else {
1658                    return None;
1659                };
1660                let connection = mutation
1661                    .connection
1662                    .clone()
1663                    .filter(|value| !value.trim().is_empty())?;
1664                self.lend_runtime(&connection).map(|runtime| {
1665                    let session = live_session_name(runtime.as_ref(), &mutation);
1666                    Work::Runtime(RuntimeWork::LiveCommand {
1667                        connection,
1668                        runtime,
1669                        verb,
1670                        mutation,
1671                        command,
1672                        session,
1673                    })
1674                })
1675            }
1676            _ => return None,
1677        };
1678        Some(DetachedCall {
1679            id,
1680            method: method.to_string(),
1681            work,
1682        })
1683    }
1684
1685    /// Take back whatever a detached call borrowed and hand over the caller's
1686    /// response. Every answer from [`DetachedCall::run`] comes through here,
1687    /// so a lent-out connection is back in the service before the response
1688    /// that used it is written.
1689    pub fn finish_detached(&mut self, answer: DetachedAnswer) -> Value {
1690        let DetachedAnswer { response, returned } = answer;
1691        if let Some(ReturnedRuntime {
1692            connection,
1693            runtime,
1694        }) = returned
1695        {
1696            self.runtimes_in_flight.remove(&connection);
1697            self.runtimes.insert(connection, runtime);
1698        }
1699        response
1700    }
1701
1702    /// Answer a request split out by [`Self::runtime_open`] and already
1703    /// awaited by [`RuntimeOpen::open`]: register the runtime this service now
1704    /// owns and build its JSON-RPC response.
1705    pub async fn finish_runtime_open(&mut self, opened: OpenedRuntime) -> Value {
1706        let OpenedRuntime { id, outcome } = opened;
1707        let result = match outcome {
1708            Ok(open) => self.register_open_runtime(open).await,
1709            Err(error) => Err(error),
1710        };
1711        service_response(id, result)
1712    }
1713
1714    /// Take ownership of an opened runtime.
1715    async fn register_open_runtime(
1716        &mut self,
1717        open: OpenRuntime,
1718    ) -> std::result::Result<Value, ServiceError> {
1719        match open {
1720            OpenRuntime::Hosted {
1721                runtime,
1722                capabilities,
1723                workspace,
1724            } => {
1725                self.insert_hosted_runtime(runtime, capabilities, workspace)
1726                    .await
1727            }
1728            OpenRuntime::Joined { runtime } => self.insert_runtime(runtime),
1729        }
1730    }
1731
1732    async fn runtime_call(
1733        &mut self,
1734        method: &str,
1735        params: Value,
1736    ) -> std::result::Result<Value, ServiceError> {
1737        match method {
1738            "harness.v1.runtimes.capabilities" => {
1739                let params = decode::<RuntimeBackendParams>(params)?;
1740                let backend = runtime_backend(&params)?;
1741                Ok(json!({
1742                    "harness": backend.harness(),
1743                    "capabilities": backend.capabilities(),
1744                }))
1745            }
1746            method if RUNTIME_OPEN_METHODS.contains(&method) => {
1747                self.register_open_runtime(open_runtime(method, params).await?)
1748                    .await
1749            }
1750            "harness.v1.runtimes.send_input" => {
1751                let params = decode::<RuntimeInputParams>(params)?;
1752                let image_urls = validate_runtime_image_urls(params.image_urls)?;
1753                let runtime = self.runtime_mut(&params.connection)?;
1754                let turn_id = within_control_deadline(
1755                    method,
1756                    runtime.send_input(RuntimeInput {
1757                        text: params.text,
1758                        image_urls,
1759                    }),
1760                )
1761                .await?
1762                .map_err(operation)?;
1763                Ok(json!({"turn_id": turn_id}))
1764            }
1765            "harness.v1.runtimes.interrupt" => {
1766                let params = decode::<RuntimeConnectionParams>(params)?;
1767                within_control_deadline(method, self.runtime_mut(&params.connection)?.interrupt())
1768                    .await?
1769                    .map_err(operation)?;
1770                Ok(json!({}))
1771            }
1772            "harness.v1.runtimes.steer" => {
1773                let params = decode::<RuntimeInputParams>(params)?;
1774                if !params.image_urls.is_empty() {
1775                    return Err(ServiceError::InvalidParams(
1776                        "runtime steering accepts text only".into(),
1777                    ));
1778                }
1779                let text = params.text.trim();
1780                if text.is_empty() || text.chars().count() > 50_000 {
1781                    return Err(ServiceError::InvalidParams(
1782                        "runtime steering requires 1 to 50,000 text characters".into(),
1783                    ));
1784                }
1785                within_control_deadline(
1786                    method,
1787                    self.runtime_mut(&params.connection)?
1788                        .steer(text.to_string()),
1789                )
1790                .await?
1791                .map_err(operation)?;
1792                Ok(json!({}))
1793            }
1794            "harness.v1.runtimes.respond" => {
1795                let params = decode::<RuntimeRespondParams>(params)?;
1796                let request_id = params.request_id.clone();
1797                within_control_deadline(
1798                    method,
1799                    self.runtime_mut(&params.connection)?
1800                        .respond(params.request_id, params.response),
1801                )
1802                .await?
1803                .map_err(operation)?;
1804                // ORCH-9: an answered request is no longer waiting for one.
1805                self.approvals.answered(&params.connection, &request_id);
1806                Ok(json!({}))
1807            }
1808            "harness.v1.runtimes.acquire_control" => {
1809                let params = decode::<RuntimeConnectionParams>(params)?;
1810                let snapshot = within_control_deadline(
1811                    method,
1812                    self.runtime_mut(&params.connection)?.acquire_control(),
1813                )
1814                .await?
1815                .map_err(operation)?;
1816                serde_json::to_value(snapshot)
1817                    .map_err(|error| ServiceError::Operation(error.to_string()))
1818            }
1819            "harness.v1.runtimes.heartbeat" => {
1820                let params = decode::<RuntimeConnectionParams>(params)?;
1821                let snapshot = within_control_deadline(
1822                    method,
1823                    self.runtime_mut(&params.connection)?.heartbeat(),
1824                )
1825                .await?
1826                .map_err(operation)?;
1827                serde_json::to_value(snapshot)
1828                    .map_err(|error| ServiceError::Operation(error.to_string()))
1829            }
1830            "harness.v1.runtimes.detach" => {
1831                let params = decode::<RuntimeConnectionParams>(params)?;
1832                let snapshot =
1833                    within_control_deadline(method, self.runtime_mut(&params.connection)?.detach())
1834                        .await?
1835                        .map_err(operation)?;
1836                serde_json::to_value(snapshot)
1837                    .map_err(|error| ServiceError::Operation(error.to_string()))
1838            }
1839            "harness.v1.runtimes.terminal_instructions" => {
1840                let params = decode::<RuntimeConnectionParams>(params)?;
1841                let launch = self
1842                    .terminal_launches
1843                    .get(&params.connection)
1844                    .ok_or_else(|| {
1845                        ServiceError::Operation(
1846                            "this runtime is not hosted for terminal attachment".into(),
1847                        )
1848                    })?;
1849                Ok(json!({"launch":launch}))
1850            }
1851            "harness.v1.runtimes.close" => {
1852                let params = decode::<RuntimeConnectionParams>(params)?;
1853                let (runtime, process_group) = self.surrender_runtime(&params.connection)?;
1854                close_runtime(runtime, process_group).await
1855            }
1856            _ => Err(ServiceError::MethodNotFound),
1857        }
1858    }
1859
1860    /// Deliver one message into a session that is running right now.
1861    #[cfg(feature = "adapter-api")]
1862    async fn message_call(&self, params: Value) -> std::result::Result<Value, ServiceError> {
1863        let params = decode::<MessageSessionParams>(params)?;
1864        Ok(message_live_session(&params, &crate::claude_peer::ProcessCourierRunner).await)
1865    }
1866
1867    #[cfg(feature = "adapter-api")]
1868    fn harness_settings_call(
1869        &self,
1870        method: &str,
1871        params: Value,
1872    ) -> std::result::Result<Value, ServiceError> {
1873        let homes = crate::HarnessHomes::default();
1874        match method {
1875            "harness.v1.harnesses.settings" => {
1876                let params = decode::<HarnessSettingsParams>(params)?;
1877                let report = crate::inspect_harness_interop_settings(&homes, &params.harness)
1878                    .map_err(|error| ServiceError::Operation(error.to_string()))?;
1879                serde_json::to_value(report)
1880                    .map_err(|error| ServiceError::Operation(error.to_string()))
1881            }
1882            "harness.v1.harnesses.configure" => {
1883                let params = decode::<ConfigureHarnessParams>(params)?;
1884                let report = crate::configure_harness_interop_settings(
1885                    &homes,
1886                    &params.harness,
1887                    &params.changes,
1888                    params.expected_revision.as_deref(),
1889                )
1890                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1891                serde_json::to_value(report)
1892                    .map_err(|error| ServiceError::Operation(error.to_string()))
1893            }
1894            _ => Err(ServiceError::MethodNotFound),
1895        }
1896    }
1897
1898    fn insert_runtime(
1899        &mut self,
1900        runtime: Box<dyn RuntimeConnection>,
1901    ) -> std::result::Result<Value, ServiceError> {
1902        let connection = format!("runtime-{}", self.next_runtime);
1903        self.next_runtime += 1;
1904        let handle = runtime.handle().clone();
1905        self.runtime_sequences
1906            .entry(handle.runtime_id.clone())
1907            .or_insert(0);
1908        self.runtimes.insert(connection.clone(), runtime);
1909        Ok(json!({"connection": connection, "handle": handle}))
1910    }
1911
1912    #[cfg(feature = "adapter-api")]
1913    async fn insert_hosted_runtime(
1914        &mut self,
1915        runtime: Box<dyn RuntimeConnection>,
1916        capabilities: crate::RuntimeCapabilities,
1917        workspace: PathBuf,
1918    ) -> std::result::Result<Value, ServiceError> {
1919        let (host, connection) = HostedHarnessRuntime::spawn(runtime, capabilities);
1920        let token: std::sync::Arc<str> = crate::server::generate_token().into();
1921        let server = crate::server::run_frontend_http(
1922            host.clone(),
1923            host.frontend_sender(),
1924            "127.0.0.1:0",
1925            token.clone(),
1926            connection.handle().runtime_id.clone(),
1927        )
1928        .await
1929        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1930        let source = LiveRuntimeSource {
1931            harness: connection.handle().harness.as_str().to_string(),
1932            session_id: connection.handle().runtime_id.clone(),
1933            workspace: workspace.clone(),
1934        };
1935        let registration = register_live_runtime(
1936            connection.handle().runtime_id.clone(),
1937            source.clone(),
1938            format!("http://{}", server.address()),
1939            token.to_string(),
1940        )
1941        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1942        let endpoint = registration.endpoint().to_string();
1943        let launch = StructuredLaunch {
1944            cwd: workspace,
1945            // Pin attachment to the executable hosting this runtime. A bare
1946            // `supercode` could resolve to an older global install whose CLI
1947            // does not understand the receipt it is being asked to open.
1948            program: std::env::current_exe()
1949                .ok()
1950                .map(|path| path.to_string_lossy().into_owned())
1951                .unwrap_or_else(|| "supercode".into()),
1952            arguments: vec![
1953                "harness".into(),
1954                "attach".into(),
1955                "--endpoint".into(),
1956                endpoint,
1957                "--harness".into(),
1958                source.harness,
1959                "--session".into(),
1960                source.session_id,
1961            ],
1962            env: BTreeMap::new(),
1963        };
1964        let lease = HostedRuntimeLease {
1965            connection,
1966            _host: host,
1967            _registration: registration,
1968            _server: server,
1969        };
1970        let opened = self.insert_runtime(Box::new(lease))?;
1971        let connection_id = opened["connection"]
1972            .as_str()
1973            .expect("insert_runtime returns a connection id")
1974            .to_string();
1975        self.terminal_launches.insert(connection_id, launch);
1976        Ok(opened)
1977    }
1978
1979    #[cfg(not(feature = "adapter-api"))]
1980    async fn insert_hosted_runtime(
1981        &mut self,
1982        runtime: Box<dyn RuntimeConnection>,
1983        _capabilities: crate::RuntimeCapabilities,
1984        _workspace: PathBuf,
1985    ) -> std::result::Result<Value, ServiceError> {
1986        self.insert_runtime(runtime)
1987    }
1988
1989    fn runtime_mut(
1990        &mut self,
1991        connection: &str,
1992    ) -> std::result::Result<&mut Box<dyn RuntimeConnection>, ServiceError> {
1993        if self.runtimes_in_flight.contains(connection) {
1994            return Err(self.lent_out(connection));
1995        }
1996        self.runtimes.get_mut(connection).ok_or_else(|| {
1997            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
1998        })
1999    }
2000
2001    /// What a caller is told about a connection that is out on a detached
2002    /// call. It is not gone and it is not free: it is mid-call, which is the
2003    /// same answer the runtime itself gives a second turn.
2004    fn lent_out(&self, connection: &str) -> ServiceError {
2005        ServiceError::Operation(format!(
2006            "runtime connection `{connection}`: a harness turn is already in progress"
2007        ))
2008    }
2009
2010    /// Take a runtime OUT of the service for the duration of one detached
2011    /// call, leaving its name marked as lent out.
2012    fn lend_runtime(
2013        &mut self,
2014        connection: &str,
2015    ) -> std::result::Result<Box<dyn RuntimeConnection>, ServiceError> {
2016        if self.runtimes_in_flight.contains(connection) {
2017            return Err(self.lent_out(connection));
2018        }
2019        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
2020            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2021        })?;
2022        self.runtimes_in_flight.insert(connection.to_string());
2023        Ok(runtime)
2024    }
2025
2026    /// Surrender a runtime for good: the connection and everything the
2027    /// service hung off it are gone before its teardown is even attempted.
2028    ///
2029    /// `close` is what a caller reaches for when a runtime has stopped
2030    /// answering, and a runtime that has stopped answering is exactly the one
2031    /// whose graceful close cannot complete: a hosted runtime's own loop
2032    /// parks on the call the runtime never answered, so it never dequeues the
2033    /// shutdown either. Keeping the entry until teardown succeeded made a
2034    /// wedged runtime permanent — every later call on that connection, and
2035    /// every new turn, answered "a harness turn is already in progress" with
2036    /// no way to take the connection back.
2037    fn surrender_runtime(
2038        &mut self,
2039        connection: &str,
2040    ) -> std::result::Result<(Box<dyn RuntimeConnection>, Option<u32>), ServiceError> {
2041        if self.runtimes_in_flight.contains(connection) {
2042            return Err(self.lent_out(connection));
2043        }
2044        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
2045            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
2046        })?;
2047        let process_group = runtime_process_group(runtime.handle());
2048        let runtime_id = runtime.handle().runtime_id.clone();
2049        self.terminal_launches.remove(connection);
2050        self.runtime_sequences.remove(&runtime_id);
2051        self.approvals.forget(connection);
2052        Ok((runtime, process_group))
2053    }
2054
2055    /// SIGKILL the process group of every runtime this service owns, without
2056    /// waiting on any of them.
2057    ///
2058    /// A host leaving for good calls this BEFORE dropping the service. The
2059    /// handle this service holds is not the runtime's connection: a hosted
2060    /// runtime's real transport lives in the task driving it, so neither
2061    /// exiting the process nor dropping these handles reaches the harness
2062    /// process — while dropping them does remove each runtime's live-runtime
2063    /// receipt. Signalling first is what keeps a removed receipt from
2064    /// advertising a harness that is still running.
2065    pub fn kill_all_runtime_groups(&self) -> usize {
2066        self.runtimes
2067            .values()
2068            .filter(|runtime| kill_runtime_process_group(runtime_process_group(runtime.handle())))
2069            .count()
2070    }
2071
2072    /// ORCH-19: run one conversation-lifecycle verb through the harness's own
2073    /// door.
2074    ///
2075    /// Two doors, one shape. A CLI / HTTP / own-store door is self-contained
2076    /// in [`crate::sessions_control`]. A LIVE door (Hermes's and OpenClaw's
2077    /// `/new` and `/reset`, which are slash commands their gateway interprets
2078    /// INSIDE a session) is performed here, because only the service owns the
2079    /// open runtime connection — the command is typed through the very same
2080    /// `send_input` path a human's message takes, so supercode invents no
2081    /// private channel.
2082    async fn mutate_session(
2083        &mut self,
2084        verb: crate::SessionVerb,
2085        params: Value,
2086    ) -> std::result::Result<Value, ServiceError> {
2087        let mutation = decode::<crate::SessionMutation>(params)?;
2088        let door = crate::sessions_control::door(&mutation.harness, verb)
2089            .map_err(session_control_error)?;
2090        let outcome = match door {
2091            // The live door types the slash command through an open hosted
2092            // runtime, which only exists with the `adapter-api` feature; the
2093            // CLI / HTTP / own-store doors below need nothing extra.
2094            #[cfg(not(feature = "adapter-api"))]
2095            crate::SessionDoor::Live(command) => {
2096                return Err(ServiceError::Operation(format!(
2097                    "`{}` performs `sessions.{}` by typing `{command}` into a live driven \
2098                     session, which needs this build's `adapter-api` feature",
2099                    mutation.harness,
2100                    verb.as_str()
2101                )));
2102            }
2103            #[cfg(feature = "adapter-api")]
2104            crate::SessionDoor::Live(command) => {
2105                let connection = mutation
2106                    .connection
2107                    .clone()
2108                    .filter(|value| !value.trim().is_empty())
2109                    .ok_or_else(|| {
2110                        ServiceError::InvalidParams(format!(
2111                            "`{}` performs `sessions.{}` by typing `{command}` into a live \
2112                             driven session: pass the `connection` of an open runtime \
2113                             (`harness.v1.runtimes.start`)",
2114                            mutation.harness,
2115                            verb.as_str()
2116                        ))
2117                    })?;
2118                let runtime = self.runtime_mut(&connection)?;
2119                let session = live_session_name(runtime.as_ref(), &mutation);
2120                // Typing into a live session is a control call on an open
2121                // runtime, and a wedged runtime never accepts one, so it is
2122                // bounded exactly like the other control verbs. A transport
2123                // with a loop of its own lends the connection out instead of
2124                // waiting here: see [`Self::detach_runtime`].
2125                return type_live_command(runtime.as_mut(), verb, &mutation, command, session)
2126                    .await;
2127            }
2128            _ => run_session_mutation(verb, &mutation).await?,
2129        };
2130        serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
2131    }
2132
2133    /// Answer an inventory request whole, for callers that have nowhere to
2134    /// put the waiting half. A transport with a loop of its own splits it
2135    /// instead: see [`Self::detach`].
2136    async fn inventory_call(
2137        &self,
2138        method: &str,
2139        params: Value,
2140    ) -> std::result::Result<Value, ServiceError> {
2141        run_inventory(self.inventory_work(method, params)?).await
2142    }
2143
2144    /// The half of an inventory request that reads this service's state:
2145    /// resolve the selection and count the persisted sessions each row
2146    /// reports. What remains — finding executables, asking them their
2147    /// version, and (at `probe: handshake`) starting each harness and
2148    /// completing its protocol handshake — touches no service state at all.
2149    fn inventory_work(
2150        &self,
2151        method: &str,
2152        params: Value,
2153    ) -> std::result::Result<InventoryWork, ServiceError> {
2154        let mut params = decode::<HarnessInventoryParams>(params)?;
2155        if method == "harness.v1.harnesses.probe" {
2156            let harness = params.harness.take().ok_or_else(|| {
2157                ServiceError::InvalidParams("harnesses.probe requires `harness`".into())
2158            })?;
2159            params.harnesses = vec![harness];
2160        }
2161        let selected = params
2162            .harnesses
2163            .iter()
2164            .map(HarnessId::as_str)
2165            .collect::<std::collections::BTreeSet<_>>();
2166        let supported = harness_support_registry()
2167            .harnesses
2168            .into_iter()
2169            .filter(|descriptor| selected.is_empty() || selected.contains(descriptor.id.as_str()))
2170            .collect::<Vec<_>>();
2171        if !params.harnesses.is_empty() && supported.len() != selected.len() {
2172            let known = supported
2173                .iter()
2174                .map(|harness| harness.id.as_str())
2175                .collect::<std::collections::BTreeSet<_>>();
2176            let missing = params
2177                .harnesses
2178                .iter()
2179                .filter(|id| !known.contains(id.as_str()))
2180                .map(HarnessId::as_str)
2181                .collect::<Vec<_>>();
2182            return Err(ServiceError::InvalidParams(format!(
2183                "unknown harness(es): {}",
2184                missing.join(", ")
2185            )));
2186        }
2187        let global_counts = params
2188            .include_sessions
2189            .then(|| self.session_counts(None, &params.harnesses));
2190        let workspace_counts = params
2191            .include_sessions
2192            .then(|| {
2193                params
2194                    .workspace
2195                    .as_deref()
2196                    .map(|workspace| self.session_counts(Some(workspace), &params.harnesses))
2197            })
2198            .flatten();
2199        Ok(InventoryWork {
2200            params,
2201            supported,
2202            global_counts,
2203            workspace_counts,
2204        })
2205    }
2206
2207    #[cfg(feature = "adapter-api")]
2208    async fn harness_authentication_call(
2209        &self,
2210        method: &str,
2211        params: Value,
2212    ) -> std::result::Result<Value, ServiceError> {
2213        match method {
2214            "harness.v1.harnesses.auth.methods" | "harness.v1.harnesses.auth.verify" => {
2215                let params = decode::<HarnessAuthenticationParams>(params)?;
2216                serde_json::to_value(crate::inspect_harness_authentication(&params.harness).await)
2217                    .map_err(|error| ServiceError::Operation(error.to_string()))
2218            }
2219            "harness.v1.harnesses.auth.begin" => {
2220                let params = decode::<BeginHarnessAuthenticationParams>(params)?;
2221                let cwd = params
2222                    .cwd
2223                    .or_else(|| std::env::current_dir().ok())
2224                    .unwrap_or_else(|| PathBuf::from("."));
2225                let plan = crate::harness_authentication_plan(
2226                    &params.harness,
2227                    params.environment,
2228                    params.method,
2229                    &cwd,
2230                )
2231                .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
2232                serde_json::to_value(plan)
2233                    .map_err(|error| ServiceError::Operation(error.to_string()))
2234            }
2235            _ => Err(ServiceError::MethodNotFound),
2236        }
2237    }
2238
2239    fn session_counts(
2240        &self,
2241        workspace: Option<&Path>,
2242        harnesses: &[HarnessId],
2243    ) -> BTreeMap<String, usize> {
2244        let mut counts = BTreeMap::new();
2245        for session in self
2246            .catalog
2247            .discover(&DiscoveryQuery {
2248                workspace: workspace.map(Path::to_path_buf),
2249                harnesses: harnesses.to_vec(),
2250                ..DiscoveryQuery::default()
2251            })
2252            .unwrap_or_default()
2253        {
2254            *counts
2255                .entry(session.locator.harness.as_str().to_string())
2256                .or_insert(0) += 1;
2257        }
2258        counts
2259    }
2260}
2261
2262#[async_trait::async_trait]
2263impl SdkService for HarnessSessionService {
2264    fn capabilities(&self) -> SdkCapabilities {
2265        SdkCapabilities::default()
2266    }
2267
2268    async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError> {
2269        if request.operation == SdkOperation::Events {
2270            let events = self
2271                .poll_sdk_events()
2272                .await
2273                .into_iter()
2274                .map(|(_, event)| event)
2275                .collect::<Vec<_>>();
2276            return serde_json::to_value(events).map_err(|error| {
2277                SdkError::new(
2278                    SdkErrorCode::Execution,
2279                    request.operation,
2280                    error.to_string(),
2281                )
2282            });
2283        }
2284        if self.runtimes.is_empty()
2285            && matches!(
2286                request.operation,
2287                SdkOperation::Input
2288                    | SdkOperation::Interrupt
2289                    | SdkOperation::Steer
2290                    | SdkOperation::Respond
2291                    | SdkOperation::Close
2292            )
2293        {
2294            return Err(SdkError::unsupported(request.operation));
2295        }
2296        let method = request
2297            .operation
2298            .method()
2299            .ok_or_else(|| SdkError::unsupported(request.operation))?;
2300        let result = match request.operation {
2301            SdkOperation::Discover
2302            | SdkOperation::Load
2303            | SdkOperation::Export
2304            | SdkOperation::ProfilesList
2305            | SdkOperation::ProfilesGet
2306            | SdkOperation::ProfilesCreate
2307            | SdkOperation::ProfilesDelete
2308            | SdkOperation::SkillsList
2309            | SdkOperation::SkillsInstall
2310            | SdkOperation::SkillsRemove
2311            | SdkOperation::ChannelsList
2312            | SdkOperation::RoutesList
2313            | SdkOperation::TriggersList
2314            | SdkOperation::ChannelsStatus
2315            | SdkOperation::MemoryShow
2316            | SdkOperation::MemorySearch
2317            | SdkOperation::JobsList
2318            | SdkOperation::JobsGet
2319            | SdkOperation::JobsCreate
2320            | SdkOperation::JobsUpdate
2321            | SdkOperation::JobsPause
2322            | SdkOperation::JobsResume
2323            | SdkOperation::JobsRun
2324            | SdkOperation::JobsDelete
2325            | SdkOperation::JobsNotepad
2326            | SdkOperation::JobsNotepadSet
2327            | SdkOperation::JobsNotepadDelete
2328            | SdkOperation::RunsList
2329            | SdkOperation::RunsGet
2330            | SdkOperation::ApprovalsList
2331            | SdkOperation::OrchestrationLoad
2332            | SdkOperation::OrchestrationSave
2333            | SdkOperation::OrchestrationCompile
2334            | SdkOperation::OrchestrationDecompile
2335            | SdkOperation::OrchestrationImport
2336            | SdkOperation::OrchestrationExport
2337            | SdkOperation::WorkflowLoad => self.call(method, request.params),
2338            // ORCH-20: answering needs the live connection, so it takes the
2339            // async door and ends in `harness.v1.runtimes.respond`.
2340            SdkOperation::ApprovalsResolve => self.approvals_resolve(request.params).await,
2341            SdkOperation::Start
2342            | SdkOperation::Resume
2343            | SdkOperation::Input
2344            | SdkOperation::Interrupt
2345            | SdkOperation::Steer
2346            | SdkOperation::Respond
2347            | SdkOperation::Close => self.runtime_call(method, request.params).await,
2348            // ORCH-19 controlled tier. Every verb goes through the HARNESS'S
2349            // OWN door — its CLI, its HTTP API, or its slash command typed
2350            // into a live driven session — and returns the row re-read from
2351            // the harness's store afterwards.
2352            SdkOperation::SessionsNew => {
2353                self.mutate_session(crate::SessionVerb::New, request.params)
2354                    .await
2355            }
2356            SdkOperation::SessionsReset => {
2357                self.mutate_session(crate::SessionVerb::Reset, request.params)
2358                    .await
2359            }
2360            SdkOperation::SessionsArchive => {
2361                self.mutate_session(crate::SessionVerb::Archive, request.params)
2362                    .await
2363            }
2364            SdkOperation::SessionsDelete => {
2365                self.mutate_session(crate::SessionVerb::Delete, request.params)
2366                    .await
2367            }
2368            SdkOperation::Events => unreachable!("handled before method dispatch"),
2369        };
2370        result.map_err(|error| sdk_error(request.operation, error))
2371    }
2372
2373    async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError> {
2374        Ok(self
2375            .poll_sdk_events()
2376            .await
2377            .into_iter()
2378            .map(|(_, event)| event)
2379            .collect())
2380    }
2381}
2382
2383#[cfg(feature = "adapter-api")]
2384struct HostedRuntimeLease {
2385    connection: HostedHarnessConnection,
2386    _host: std::sync::Arc<HostedHarnessRuntime>,
2387    _registration: LiveRuntimeRegistration,
2388    _server: crate::server::FrontendHttpServer,
2389}
2390
2391#[async_trait::async_trait]
2392#[cfg(feature = "adapter-api")]
2393impl RuntimeConnection for HostedRuntimeLease {
2394    fn handle(&self) -> &crate::RuntimeHandle {
2395        self.connection.handle()
2396    }
2397
2398    async fn send_input(&mut self, input: RuntimeInput) -> crate::Result<Option<String>> {
2399        self.connection.send_input(input).await
2400    }
2401
2402    async fn next_event(&mut self) -> crate::Result<Option<crate::HarnessEvent>> {
2403        self.connection.next_event().await
2404    }
2405
2406    async fn interrupt(&mut self) -> crate::Result<()> {
2407        self.connection.interrupt().await
2408    }
2409
2410    // the lease must forward every verb its capabilities advertise; without
2411    // this, steer fell to the trait default and refused a turn it claimed
2412    async fn steer(&mut self, text: String) -> crate::Result<()> {
2413        self.connection.steer(text).await
2414    }
2415
2416    async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
2417        self.connection.respond(request_id, response).await
2418    }
2419
2420    async fn close(&mut self) -> crate::Result<()> {
2421        self.connection.close().await
2422    }
2423}
2424
2425/// One inventory request's waiting half, already separated from the service
2426/// state it reads. See [`HarnessSessionService::inventory_work`].
2427struct InventoryWork {
2428    params: HarnessInventoryParams,
2429    supported: Vec<crate::HarnessSupportDescriptor>,
2430    global_counts: Option<BTreeMap<String, usize>>,
2431    workspace_counts: Option<BTreeMap<String, usize>>,
2432}
2433
2434/// Perform one conversation-lifecycle verb through a door that is
2435/// self-contained in [`crate::sessions_control`]: the harness's own CLI, its
2436/// HTTP API, the orchestrator daemon's socket, or supercode's own store.
2437/// Touches no service state, so this runs on any task. The LIVE door is not
2438/// here — it types its slash command through a runtime connection the service
2439/// owns, and is performed by [`HarnessSessionService::mutate_session`].
2440async fn run_session_mutation(
2441    verb: crate::SessionVerb,
2442    mutation: &crate::SessionMutation,
2443) -> std::result::Result<crate::SessionMutationOutcome, ServiceError> {
2444    // Only the HTTP door actually awaits anything. The CLI, store and daemon
2445    // doors run the harness's own program, or its store, with calls that
2446    // block the calling THREAD from start to finish — a future that never
2447    // yields, which no timeout around it can interrupt and which would hold a
2448    // runtime worker for as long as the harness takes. They go to a blocking
2449    // task, where blocking is what the thread is for.
2450    let door =
2451        crate::sessions_control::door(&mutation.harness, verb).map_err(session_control_error)?;
2452    if let crate::SessionDoor::Http = door {
2453        return crate::sessions_control::mutate(verb, mutation)
2454            .await
2455            .map_err(session_control_error);
2456    }
2457    let mutation = mutation.clone();
2458    tokio::task::spawn_blocking(move || crate::sessions_control::mutate_blocking(verb, &mutation))
2459        .await
2460        .map_err(|error| {
2461            ServiceError::Operation(format!("the conversation verb could not be run: {error}"))
2462        })?
2463        .map_err(session_control_error)
2464}
2465
2466/// Probe every selected harness and assemble the report. Touches no service
2467/// state, so this runs on any task.
2468async fn run_inventory(work: InventoryWork) -> std::result::Result<Value, ServiceError> {
2469    let InventoryWork {
2470        params,
2471        supported,
2472        global_counts,
2473        workspace_counts,
2474    } = work;
2475    let probes = supported.into_iter().map(|descriptor| {
2476        let global = global_counts
2477            .as_ref()
2478            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2479        let workspace = workspace_counts
2480            .as_ref()
2481            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2482        probe_harness(descriptor, &params, global, workspace)
2483    });
2484    let harnesses = futures::future::join_all(probes).await;
2485    serde_json::to_value(HarnessInventoryReport {
2486        probe: params.probe,
2487        workspace: params.workspace,
2488        harnesses,
2489    })
2490    .map_err(|error| ServiceError::Operation(error.to_string()))
2491}
2492
2493async fn probe_harness(
2494    descriptor: crate::HarnessSupportDescriptor,
2495    params: &HarnessInventoryParams,
2496    global: Option<usize>,
2497    workspace: Option<usize>,
2498) -> LocalHarness {
2499    let launch = descriptor.runtime.default_launch.as_ref();
2500    // ORC-7: the orchestrator publishes no runtime launch — it is not an
2501    // adapter supercode connects a turn to. What "installed" means for it
2502    // is that its Node daemon entry is present, so the row answers from
2503    // that instead of from a PATH lookup it could never satisfy.
2504    let orchestrator_entry = (descriptor.id.as_str() == HarnessId::ORCHESTRATOR)
2505        .then(crate::orchestrator::daemon_entry)
2506        .and_then(Result::ok);
2507    let executable = match &orchestrator_entry {
2508        Some(entry) => Some(entry.clone()),
2509        None => launch.and_then(|launch| find_executable(&launch.program)),
2510    };
2511    let installed = executable.is_some();
2512    let version = if params.skip_versions || orchestrator_entry.is_some() {
2513        // The orchestrator's "executable" is a Node module, not a CLI
2514        // with a `--version` flag; running it to ask would start a daemon.
2515        None
2516    } else {
2517        match executable.as_deref() {
2518            Some(path) => executable_version(path).await,
2519            None => None,
2520        }
2521    };
2522    let configured = auth_evidence(descriptor.id.as_str());
2523    let mut auth = if configured {
2524        HarnessAuthState::Configured
2525    } else if matches!(
2526        descriptor.id.as_str(),
2527        HarnessId::CLAUDE_CODE | HarnessId::CODEX
2528    ) {
2529        // These two adapters have explicit native status/login contracts
2530        // and complete local evidence coverage (including Claude's macOS
2531        // Keychain-backed oauthAccount marker). Treating absent evidence
2532        // as unknown advertises a start that will only fail interactively.
2533        HarnessAuthState::Required
2534    } else {
2535        HarnessAuthState::Unknown
2536    };
2537    let mut runtime = if installed {
2538        HarnessRuntimeState::Degraded
2539    } else {
2540        HarnessRuntimeState::Unavailable
2541    };
2542    let is_orchestrator = descriptor.id.as_str() == HarnessId::ORCHESTRATOR;
2543    let mut reason = (!installed).then(|| {
2544        if is_orchestrator {
2545            format!(
2546                "{} is supported but its daemon entry `{}` was not found",
2547                descriptor.display_name,
2548                crate::orchestrator::DAEMON_ENTRY
2549            )
2550        } else {
2551            format!(
2552                "{} is supported but `{}` was not found on PATH",
2553                descriptor.display_name,
2554                launch
2555                    .map(|launch| launch.program.as_str())
2556                    .unwrap_or("executable")
2557            )
2558        }
2559    });
2560    let mut repair = (!installed).then(|| {
2561        if is_orchestrator {
2562            format!(
2563                "Install the `supercode-orchestrator` package so `{}` resolves.",
2564                crate::orchestrator::DAEMON_ENTRY
2565            )
2566        } else {
2567            format!(
2568                "Install {} and ensure `{}` is on PATH.",
2569                descriptor.display_name,
2570                launch
2571                    .map(|launch| launch.program.as_str())
2572                    .unwrap_or("its executable")
2573            )
2574        }
2575    });
2576
2577    if installed && params.probe == HarnessProbeLevel::Handshake {
2578        let backend_params = RuntimeBackendParams {
2579            harness: descriptor.id.clone(),
2580            protocol: None,
2581            launch: None,
2582            base_url: None,
2583            policy: RuntimePolicy::Default,
2584        };
2585        match runtime_backend(&backend_params) {
2586            Ok(backend) => {
2587                let cwd = params
2588                    .workspace
2589                    .clone()
2590                    .or_else(|| std::env::current_dir().ok())
2591                    .unwrap_or_else(|| PathBuf::from("."));
2592                let isolated = descriptor
2593                    .runtime
2594                    .default_launch
2595                    .clone()
2596                    .and_then(|launch| IsolatedProbeHome::new(descriptor.id.as_str(), launch).ok());
2597                let Some(isolated) = isolated else {
2598                    reason = Some(
2599                        "No-prompt runtime handshake could not create its isolated harness home."
2600                            .into(),
2601                    );
2602                    repair = Some(
2603                        "Check temporary-directory permissions, then run the handshake probe again."
2604                            .into(),
2605                    );
2606                    let running = probe_running_instance(descriptor.id.as_str());
2607                    return LocalHarness {
2608                        gateway: gateway_health(
2609                            descriptor.id.as_str(),
2610                            installed,
2611                            running.as_ref(),
2612                            version.as_deref(),
2613                        ),
2614                        id: descriptor.id,
2615                        display_name: descriptor.display_name,
2616                        supported: true,
2617                        installed,
2618                        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2619                        version,
2620                        auth,
2621                        runtime,
2622                        protocol: descriptor.runtime.protocol,
2623                        capabilities: descriptor.runtime.capabilities.clone(),
2624                        effective_capabilities: descriptor.runtime.capabilities,
2625                        sessions: HarnessSessionCounts { global, workspace },
2626                        running,
2627                        reason,
2628                        repair,
2629                    };
2630                };
2631                match tokio::time::timeout(
2632                    Duration::from_secs(30),
2633                    backend.start(RuntimeStartRequest {
2634                        cwd,
2635                        launch: Some(isolated.launch.clone()),
2636                        mcp_servers: Vec::new(),
2637                        approval_policy: None,
2638                    }),
2639                )
2640                .await
2641                {
2642                    Ok(Ok(mut connection)) => {
2643                        match stabilize_handshake(connection.as_mut()).await {
2644                            Ok(()) => {
2645                                auth = HarnessAuthState::Ready;
2646                                runtime = HarnessRuntimeState::Ready;
2647                                reason = Some(
2648                                    "No-prompt runtime handshake remained healthy through the startup stabilization window; no model request was sent."
2649                                        .into(),
2650                                );
2651                                repair = None;
2652                            }
2653                            Err(message) => {
2654                                auth = if looks_like_auth_error(&message) {
2655                                    HarnessAuthState::Required
2656                                } else if configured {
2657                                    HarnessAuthState::Configured
2658                                } else {
2659                                    HarnessAuthState::Unknown
2660                                };
2661                                reason = Some(format!(
2662                                    "No-prompt runtime handshake became unhealthy during startup: {message}"
2663                                ));
2664                                repair = Some(if auth == HarnessAuthState::Required {
2665                                    format!(
2666                                        "Run `{}` interactively once and complete sign-in, then probe again.",
2667                                        launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2668                                    )
2669                                } else {
2670                                    "Run the harness directly to inspect its startup failure, then probe again."
2671                                        .into()
2672                                });
2673                            }
2674                        }
2675                        let _ =
2676                            tokio::time::timeout(Duration::from_secs(3), connection.close()).await;
2677                    }
2678                    Ok(Err(error)) => {
2679                        let message = truncate_text(&error.to_string(), 500);
2680                        auth = if looks_like_auth_error(&message) {
2681                            HarnessAuthState::Required
2682                        } else if configured {
2683                            HarnessAuthState::Configured
2684                        } else {
2685                            HarnessAuthState::Unknown
2686                        };
2687                        reason = Some(format!("No-prompt runtime handshake failed: {message}"));
2688                        repair = Some(if auth == HarnessAuthState::Required {
2689                            format!(
2690                                "Run `{}` interactively once and complete sign-in, then probe again.",
2691                                launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2692                            )
2693                        } else {
2694                            "Check the harness installation and run the handshake probe again."
2695                                .into()
2696                        });
2697                    }
2698                    Err(_) => {
2699                        reason =
2700                            Some("No-prompt runtime handshake timed out after 30 seconds.".into());
2701                        repair = Some("Run the harness directly to check startup or authentication, then probe again.".into());
2702                    }
2703                }
2704                // Keep the isolated home alive through process teardown.
2705                // Otherwise the compiler may release the last meaningful
2706                // use after cloning `launch`, and a still-starting CLI can
2707                // recreate its state directory after Drop removed it.
2708                // Some Node-based launchers finish a short asynchronous
2709                // installation-id write just after their parent process
2710                // is reaped. Remove once immediately, allow that bounded
2711                // writer to settle, then perform the authoritative pass.
2712                let _ = isolated.cleanup();
2713                tokio::time::sleep(Duration::from_millis(250)).await;
2714                if let Err(error) = isolated.cleanup() {
2715                    auth = if configured {
2716                        HarnessAuthState::Configured
2717                    } else {
2718                        HarnessAuthState::Unknown
2719                    };
2720                    runtime = HarnessRuntimeState::Degraded;
2721                    reason = Some(format!(
2722                        "No-prompt runtime handshake could not remove its isolated harness home: {error}"
2723                    ));
2724                    repair = Some(
2725                        "Check temporary-directory permissions, remove the reported disposable probe home, then run the handshake again."
2726                            .into(),
2727                    );
2728                }
2729            }
2730            Err(error) => {
2731                reason = Some(error_message(error));
2732            }
2733        }
2734    } else if installed && configured {
2735        reason = Some("Executable and local authentication evidence found; use a handshake probe to verify readiness.".into());
2736    } else if installed && auth == HarnessAuthState::Required {
2737        reason = Some("Executable found, but no native authentication evidence is present.".into());
2738        repair = Some(format!(
2739            "Run `supercode harness login {}` to use the harness-owned sign-in flow.",
2740            descriptor.id.as_str()
2741        ));
2742    } else if installed {
2743        reason = Some("Executable found; authentication readiness is unknown until a no-prompt handshake succeeds.".into());
2744        repair = Some(format!(
2745            "Run `{}` interactively once if sign-in is required, or use `--probe handshake`.",
2746            launch
2747                .map(|launch| launch.program.as_str())
2748                .unwrap_or("the harness")
2749        ));
2750    }
2751
2752    let effective_capabilities = if installed {
2753        descriptor.runtime.capabilities.clone()
2754    } else {
2755        unavailable_capabilities()
2756    };
2757    let running = probe_running_instance(descriptor.id.as_str());
2758    LocalHarness {
2759        gateway: gateway_health(
2760            descriptor.id.as_str(),
2761            installed,
2762            running.as_ref(),
2763            version.as_deref(),
2764        ),
2765        id: descriptor.id,
2766        display_name: descriptor.display_name,
2767        supported: true,
2768        installed,
2769        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2770        version,
2771        auth,
2772        runtime,
2773        protocol: descriptor.runtime.protocol,
2774        capabilities: descriptor.runtime.capabilities,
2775        effective_capabilities,
2776        sessions: HarnessSessionCounts { global, workspace },
2777        running,
2778        reason,
2779        repair,
2780    }
2781}
2782
2783async fn stabilize_handshake(connection: &mut dyn RuntimeConnection) -> Result<(), String> {
2784    let deadline = tokio::time::Instant::now() + Duration::from_secs(3);
2785    loop {
2786        let now = tokio::time::Instant::now();
2787        if now >= deadline {
2788            return Ok(());
2789        }
2790        match tokio::time::timeout(deadline - now, connection.next_event()).await {
2791            Err(_) => return Ok(()),
2792            Ok(Ok(Some(event))) => {
2793                if let Some(message) = handshake_event_failure(&event) {
2794                    return Err(truncate_text(&message, 500));
2795                }
2796            }
2797            Ok(Ok(None)) => return Err("runtime transport closed during startup".into()),
2798            Ok(Err(error)) => return Err(error.to_string()),
2799        }
2800    }
2801}
2802
2803fn handshake_event_failure(event: &crate::HarnessEvent) -> Option<String> {
2804    let detail = event
2805        .payload
2806        .get("message")
2807        .or_else(|| event.payload.get("line"))
2808        .and_then(Value::as_str)
2809        .unwrap_or(event.kind.as_str());
2810    match event.kind.as_str() {
2811        "transport_closed" => Some("runtime transport closed during startup".into()),
2812        "transport_error" => Some(format!("runtime transport error: {detail}")),
2813        "malformed_output" => Some(format!("runtime emitted non-protocol output: {detail}")),
2814        // Stderr is retained as a runtime event, but is not transport health.
2815        // Grok, for example, can log an AuthorizationRequired error from an
2816        // optional background worker while its ACP session continues to send
2817        // updates and complete prompts normally.
2818        _ => None,
2819    }
2820}
2821
2822fn indexed_claude_window(
2823    locator: &SessionLocator,
2824    options: &SessionLoadOptions,
2825) -> std::result::Result<Option<Value>, ServiceError> {
2826    use supercode_interchange::session::ClaudeReadIndex;
2827    // Exact parent-only window: recursive/full-artifact requests retain the
2828    // existing owner. This is not a bounded display-history substitution.
2829    if locator.harness.as_str() != HarnessId::CLAUDE_CODE
2830        || options.include_subagents != Some(false)
2831    {
2832        return Ok(None);
2833    }
2834    let crate::StorageLocator::File { path } = &locator.storage else {
2835        return Ok(None);
2836    };
2837    if !ClaudeReadIndex::supports(path)
2838        .map_err(|error| ServiceError::Operation(error.to_string()))?
2839    {
2840        return Ok(None);
2841    }
2842    let mut index = ClaudeReadIndex::open(path, Fidelity::ByteLossless)
2843        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2844    let total = index.len();
2845    let (offset, end) = projected_message_window(total, options);
2846    let session = index
2847        .read_messages(offset..end)
2848        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2849    let summary = index
2850        .read_summary()
2851        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2852    let selected_options = SessionLoadOptions {
2853        message_offset: None,
2854        message_limit: None,
2855        message_tail: None,
2856        ..options.clone()
2857    };
2858    let mut selected = projected_session_json(&session, &selected_options);
2859    selected["raw_record_count"] = json!(index.raw_record_count());
2860    Ok(Some(json!({
2861        "session": selected,
2862        "summary": projected_session_summary(&summary, options),
2863        "window": {
2864            "has_more": offset > 0 || end < total, "has_newer": end < total,
2865            "has_older": offset > 0, "newer_items": index.item_count(end..total),
2866            "offset": offset, "older_items": index.item_count(0..offset),
2867            "returned": end - offset, "total_messages": total,
2868        }
2869    })))
2870}
2871
2872fn projected_session_result(session: &Session, options: &SessionLoadOptions) -> Value {
2873    let total_messages = session.messages.len();
2874    let (offset, end) = projected_message_window(total_messages, options);
2875    json!({
2876        "session": projected_session_json(session, options),
2877        "summary": projected_session_summary(session, options),
2878        "window": {
2879            "has_more": offset > 0 || end < total_messages,
2880            "has_newer": end < total_messages,
2881            "has_older": offset > 0,
2882            "newer_items": normalized_item_count(&session.messages[end..]),
2883            "offset": offset,
2884            "older_items": normalized_item_count(&session.messages[..offset]),
2885            "returned": end.saturating_sub(offset),
2886            "total_messages": total_messages,
2887        }
2888    })
2889}
2890
2891fn normalized_item_count(messages: &[crate::ChatMessage]) -> usize {
2892    messages
2893        .iter()
2894        .map(|message| {
2895            let conversation = usize::from(
2896                matches!(message.role, Role::Assistant | Role::User)
2897                    && message_has_content(message),
2898            );
2899            let tool_result =
2900                usize::from(message.role == Role::Tool && message_has_content(message));
2901            conversation + tool_result + message.tool_calls().len()
2902        })
2903        .sum()
2904}
2905
2906fn projected_session_summary(session: &Session, options: &SessionLoadOptions) -> Value {
2907    let mut conversational = session.messages.iter().filter(|message| {
2908        matches!(message.role, Role::Assistant | Role::User) && message_has_content(message)
2909    });
2910    let first_message = conversational.clone().next();
2911    let last_message = conversational.next_back();
2912    let mut assistant = session
2913        .messages
2914        .iter()
2915        .filter(|message| message.role == Role::Assistant && message_has_content(message));
2916    let first_assistant_message = assistant.clone().next();
2917    let last_assistant_message = assistant.next_back();
2918    let end_of_turn = session
2919        .messages
2920        .iter()
2921        .rev()
2922        .find(|message| message.role != Role::System)
2923        .is_some_and(|message| {
2924            message.role == Role::Assistant
2925                && message_has_content(message)
2926                && message.tool_calls().is_empty()
2927                // Codex narrates while it works (`phase: commentary`); only its `final_answer` ends a turn
2928                && message.metadata.get("phase").map(String::as_str) != Some("commentary")
2929        });
2930    let project = |message: Option<&crate::ChatMessage>| {
2931        message.map(|message| project_inline_media(message_json(message), options))
2932    };
2933    json!({
2934        "end_of_turn": end_of_turn,
2935        "first_assistant_message": project(first_assistant_message),
2936        "first_message": project(first_message),
2937        "last_assistant_message": project(last_assistant_message),
2938        "last_assistant_text": last_assistant_message.map(message_text).unwrap_or_default(),
2939        "last_message": project(last_message),
2940    })
2941}
2942
2943fn message_has_content(message: &crate::ChatMessage) -> bool {
2944    message
2945        .content
2946        .as_deref()
2947        .is_some_and(|content| !content.trim().is_empty())
2948        || message
2949            .content_parts
2950            .as_ref()
2951            .is_some_and(|parts| !parts.is_empty())
2952}
2953
2954fn message_text(message: &crate::ChatMessage) -> String {
2955    if let Some(content) = &message.content {
2956        return content.clone();
2957    }
2958    message
2959        .content_parts
2960        .as_ref()
2961        .into_iter()
2962        .flatten()
2963        .filter_map(|part| part.get("text").and_then(Value::as_str))
2964        .collect::<Vec<_>>()
2965        .join("\n")
2966}
2967
2968fn projected_session_json(session: &Session, options: &SessionLoadOptions) -> Value {
2969    let (offset, end) = projected_message_window(session.messages.len(), options);
2970    let messages = session.messages[offset..end]
2971        .iter()
2972        .map(|message| project_inline_media(message_json(message), options))
2973        .collect::<Vec<_>>();
2974    let subagents = if options.include_subagents.unwrap_or(true) {
2975        // The reported window describes the top-level transcript. Applying it
2976        // recursively would silently truncate subagents without returning a
2977        // window for each child. Keep their histories complete while carrying
2978        // the caller's media policy through the tree.
2979        let subagent_options = SessionLoadOptions {
2980            message_limit: None,
2981            message_offset: None,
2982            message_tail: None,
2983            ..options.clone()
2984        };
2985        session
2986            .subagents
2987            .iter()
2988            .map(|subagent| projected_session_json(subagent, &subagent_options))
2989            .collect::<Vec<_>>()
2990    } else {
2991        Vec::new()
2992    };
2993    json!({
2994        "source": match session.meta.source {
2995            SessionSource::ClaudeCode => "claude_code",
2996            SessionSource::Codex => "codex",
2997            SessionSource::Gemini => "gemini",
2998            SessionSource::Goose => "goose",
2999            SessionSource::Grok => "grok",
3000            SessionSource::Native => "native",
3001            SessionSource::OpenClaw => "openclaw",
3002            SessionSource::Hermes => "hermes",
3003            SessionSource::OpenCode => "opencode",
3004            SessionSource::Pi => "pi",
3005        },
3006        "session_id": session.meta.session_id,
3007        "ended_at": session.meta.ended_at,
3008        "end_reason": session.meta.end_reason,
3009        "model": session.meta.model,
3010        "cwd": session.meta.cwd,
3011        "system_prompt": session.meta.system_prompt,
3012        "agent_id": session.meta.agent_id,
3013        "parent_tool_use_id": session.meta.parent_tool_use_id,
3014        "lineage": session.meta.lineage,
3015        "messages": messages,
3016        "subagents": subagents,
3017        "raw_record_count": session.raw.len(),
3018        "parse_error_lines": session.parse_error_lines,
3019    })
3020}
3021
3022fn projected_message_window(total: usize, options: &SessionLoadOptions) -> (usize, usize) {
3023    if let Some(tail) = options.message_tail {
3024        return (total.saturating_sub(tail), total);
3025    }
3026    let offset = options.message_offset.unwrap_or(0).min(total);
3027    let end = options
3028        .message_limit
3029        .map(|limit| offset.saturating_add(limit).min(total))
3030        .unwrap_or(total);
3031    (offset, end)
3032}
3033
3034fn project_inline_media(mut message: Value, options: &SessionLoadOptions) -> Value {
3035    let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else {
3036        return message;
3037    };
3038    for part in parts {
3039        let Some(url) = part
3040            .get("image_url")
3041            .and_then(|image| image.get("url"))
3042            .and_then(Value::as_str)
3043        else {
3044            continue;
3045        };
3046        let Some(rest) = url.strip_prefix("data:") else {
3047            continue;
3048        };
3049        let Some((media_type, encoded)) = rest.split_once(";base64,") else {
3050            continue;
3051        };
3052        let padding = usize::from(encoded.ends_with('=')) + usize::from(encoded.ends_with("=="));
3053        let decoded_bytes = encoded.len().saturating_mul(3) / 4;
3054        let decoded_bytes = decoded_bytes.saturating_sub(padding);
3055        let should_elide = matches!(options.inline_media, InlineMediaMode::Metadata)
3056            || options
3057                .max_inline_media_bytes
3058                .is_some_and(|limit| decoded_bytes > limit);
3059        if should_elide {
3060            *part = json!({
3061                "type": "media_reference",
3062                "media_type": media_type,
3063                "encoding": "base64",
3064                "encoded_bytes": encoded.len(),
3065                "decoded_bytes": decoded_bytes,
3066                "omitted": true,
3067            });
3068        }
3069    }
3070    message
3071}
3072
3073#[derive(Deserialize)]
3074struct LocatorParams {
3075    locator: SessionLocator,
3076    /// Optional fidelity for the READ surfaces (`sessions.load`,
3077    /// `sessions.follow`).
3078    ///
3079    /// Omitted means [`Fidelity::Semantic`]: these two methods only ever
3080    /// produce a read-only view, and a compacted or resumed-across-files
3081    /// transcript — the everyday shape of a long Claude Code session — has no
3082    /// losslessly reconstructable record graph, so refusing to render it made
3083    /// the mirror unusable rather than accurate. A caller that intends to
3084    /// CONTINUE from what it reads asks for a lossless level explicitly and
3085    /// gets the strict refusal back. Every other method (export, translate,
3086    /// branch, handoff, resume_instructions) is lossless-only and has no
3087    /// such knob.
3088    #[serde(default)]
3089    fidelity: Option<Fidelity>,
3090    /// Optional bounded frontend projection. Absent preserves the historical
3091    /// complete-session read contract.
3092    #[serde(default)]
3093    view: Option<SessionReadView>,
3094}
3095
3096#[derive(Deserialize)]
3097struct SessionReadView {
3098    /// Number of trailing normalized messages to return. Zero is treated as
3099    /// one so a caller cannot accidentally request an unbounded empty mode.
3100    #[serde(default)]
3101    tail_messages: Option<usize>,
3102    /// Whether Claude Code child transcripts belong in this view. The
3103    /// frontend default is false; the legacy no-view path remains true.
3104    #[serde(default)]
3105    include_subagents: bool,
3106    /// Preserve human-visible native history across model-context compaction.
3107    #[serde(default)]
3108    display_history: bool,
3109    /// Bound each individual text field so a single tool result cannot turn a
3110    /// small message window into a hundred-megabyte RPC response.
3111    #[serde(default)]
3112    max_message_chars: Option<usize>,
3113}
3114
3115impl LocatorParams {
3116    fn read_fidelity(&self) -> Fidelity {
3117        self.fidelity.unwrap_or(Fidelity::Semantic)
3118    }
3119
3120    fn include_subagents(&self) -> bool {
3121        self.view
3122            .as_ref()
3123            .map(|view| view.include_subagents)
3124            .unwrap_or(true)
3125    }
3126
3127    fn tail_messages(&self) -> Option<usize> {
3128        self.view
3129            .as_ref()
3130            .and_then(|view| view.tail_messages)
3131            .map(|limit| limit.clamp(1, 5_000))
3132    }
3133
3134    fn display_history(&self) -> bool {
3135        self.view.as_ref().is_some_and(|view| view.display_history)
3136    }
3137
3138    fn max_message_chars(&self) -> Option<usize> {
3139        self.view
3140            .as_ref()
3141            .and_then(|view| view.max_message_chars)
3142            .map(|limit| limit.clamp(256, 64_000))
3143    }
3144
3145    fn bound_session(&self, session: &mut Session) {
3146        bound_session_view(session, self.tail_messages(), self.max_message_chars());
3147    }
3148}
3149
3150#[derive(Debug, Clone, Copy, Default, Deserialize)]
3151#[serde(rename_all = "snake_case")]
3152enum InlineMediaMode {
3153    #[default]
3154    Full,
3155    Metadata,
3156}
3157
3158#[derive(Debug, Clone, Default, Deserialize)]
3159#[serde(default)]
3160struct SessionLoadOptions {
3161    include_subagents: Option<bool>,
3162    inline_media: InlineMediaMode,
3163    max_inline_media_bytes: Option<usize>,
3164    message_limit: Option<usize>,
3165    message_offset: Option<usize>,
3166    message_tail: Option<usize>,
3167}
3168
3169impl SessionLoadOptions {
3170    fn validate(&self) -> std::result::Result<(), ServiceError> {
3171        if self.message_tail.is_some()
3172            && (self.message_limit.is_some() || self.message_offset.is_some())
3173        {
3174            return Err(ServiceError::InvalidParams(
3175                "sessions.load options.message_tail cannot be combined with message_limit or message_offset"
3176                    .into(),
3177            ));
3178        }
3179        Ok(())
3180    }
3181}
3182
3183#[derive(Deserialize)]
3184struct LoadSessionParams {
3185    #[serde(flatten)]
3186    read: LocatorParams,
3187    #[serde(default)]
3188    options: Option<SessionLoadOptions>,
3189}
3190
3191#[derive(Deserialize)]
3192struct UnfollowParams {
3193    subscription: String,
3194}
3195
3196#[derive(Debug, Deserialize)]
3197#[serde(deny_unknown_fields)]
3198struct IndexResizeParams {
3199    subscription: String,
3200    limit: usize,
3201}
3202
3203#[derive(Deserialize)]
3204struct ActivitySubscribeParams {
3205    locators: Vec<SessionLocator>,
3206    #[serde(default)]
3207    homes: crate::HarnessHomes,
3208}
3209
3210#[derive(Deserialize)]
3211struct MessageSessionParams {
3212    locator: SessionLocator,
3213    text: String,
3214    /// Same storage roots discovery accepts, so a caller (and a test) can
3215    /// point the live-session registry somewhere other than `$HOME`.
3216    #[serde(default)]
3217    homes: crate::HarnessHomes,
3218}
3219
3220#[derive(Deserialize)]
3221#[serde(deny_unknown_fields)]
3222struct HarnessSettingsParams {
3223    harness: String,
3224}
3225
3226#[derive(Deserialize)]
3227#[serde(deny_unknown_fields)]
3228struct ConfigureHarnessParams {
3229    harness: String,
3230    #[serde(default)]
3231    changes: Vec<crate::HarnessSettingChange>,
3232    #[serde(default)]
3233    expected_revision: Option<String>,
3234}
3235
3236fn claude_inbound_controls_or_error(homes: &crate::HarnessHomes) -> (Value, Value) {
3237    match crate::inspect_harness_interop_settings(homes, HarnessId::CLAUDE_CODE) {
3238        Ok(report) => (
3239            serde_json::to_value(report).unwrap_or(Value::Null),
3240            Value::Null,
3241        ),
3242        Err(error) => (
3243            Value::Null,
3244            Value::String(format!(
3245                "Supercode could not inspect Claude Code inbound controls: {error}"
3246            )),
3247        ),
3248    }
3249}
3250
3251/// Deliver `text` into a session that is running right now, or say why not.
3252///
3253/// A refusal is a RESULT, not a JSON-RPC error: "that session is persisted
3254/// only" is an answer about the session, which a mirror renders next to the
3255/// transcript, and this service's error envelope carries no structured data
3256/// field a machine-readable reason could survive in.
3257///
3258/// `delivered_to_bus` is the honest ceiling of what the courier proves. The
3259/// message reached the receiving session's inbox; whether that session ever
3260/// reads it is governed by ITS OWN inbound controls (`crossSessionInbound`,
3261/// approval dialogs), which Supercode neither sees nor overrides.
3262async fn message_live_session(
3263    params: &MessageSessionParams,
3264    runner: &dyn crate::claude_peer::CourierRunner,
3265) -> Value {
3266    if params.locator.harness.as_str() != HarnessId::CLAUDE_CODE {
3267        return json!({
3268            "delivered_to_bus": false,
3269            "refusal": {
3270                "reason": crate::claude_peer::ClaudePeerRefusal::HarnessUnsupported.as_str(),
3271                "message": format!(
3272                    "`{}` does not publish a live-session registry; only claude-code sessions can be messaged in place",
3273                    params.locator.harness.as_str()
3274                ),
3275            },
3276        });
3277    }
3278    let (inbound_controls, inbound_controls_error) =
3279        claude_inbound_controls_or_error(&params.homes);
3280    match crate::claude_peer::message_claude_peer(
3281        &params.homes,
3282        &params.locator.session_id,
3283        &params.text,
3284        runner,
3285    )
3286    .await
3287    {
3288        Ok(delivery) => json!({
3289            "delivered_to_bus": true,
3290            "target": {
3291                "session_id": delivery.target.session_id,
3292                "name": delivery.target.name,
3293                "pid": delivery.target.pid,
3294                "cwd": delivery.target.cwd,
3295                "status": delivery.target.status.map(|status| status.as_str()),
3296            },
3297            "courier": {
3298                "model": crate::claude_peer::COURIER_MODEL,
3299                "report": delivery.courier_report,
3300            },
3301            "inbound_controls": inbound_controls,
3302            "inbound_controls_error": inbound_controls_error,
3303        }),
3304        Err(refusal) => json!({
3305            "delivered_to_bus": false,
3306            "refusal": {"reason": refusal.reason.as_str(), "message": refusal.message},
3307            "inbound_controls": inbound_controls,
3308            "inbound_controls_error": inbound_controls_error,
3309        }),
3310    }
3311}
3312
3313/// Source identity of one follow subscription, plus the last lifecycle state
3314/// already reported on it. The follower itself stays purely persistence-facing.
3315// Only the adapter-api poll reads these; the subscription bookkeeping itself is
3316// shared by both builds.
3317#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3318struct FollowedSource {
3319    harness: String,
3320    session_id: String,
3321    reported: Option<String>,
3322}
3323
3324#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3325struct ActivitySubscription {
3326    locators: Vec<SessionLocator>,
3327    homes: crate::HarnessHomes,
3328    reported: BTreeMap<(String, String), crate::SessionActivity>,
3329}
3330
3331fn peers_for_descriptors(
3332    descriptors: &[SessionDescriptor],
3333    homes: &HarnessHomes,
3334) -> Vec<crate::claude_peer::ClaudePeerSession> {
3335    if descriptors
3336        .iter()
3337        .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
3338    {
3339        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
3340    } else {
3341        Vec::new()
3342    }
3343}
3344
3345/// Add the live address that makes an indexed row behaviorally equivalent to a discovered row.
3346///
3347/// The durable index owns only persistence metadata. Live endpoints remain projections: every
3348/// message/attach operation revalidates its authority, so publishing one here never trusts a stale
3349/// browser-held handle. Reading the Claude registry once per batch keeps this O(peers + rows).
3350fn live_descriptor_value(
3351    session: &SessionDescriptor,
3352    peers: &[crate::claude_peer::ClaudePeerSession],
3353) -> std::result::Result<Value, ServiceError> {
3354    let mut value = serde_json::to_value(session)
3355        .map_err(|error| ServiceError::Operation(error.to_string()))?;
3356    if let Some(workspace) = &session.cwd {
3357        let source = LiveRuntimeSource {
3358            harness: session.locator.harness.as_str().to_string(),
3359            session_id: session.locator.session_id.clone(),
3360            workspace: workspace.clone(),
3361        };
3362        if let Some(endpoint) = discover_live_runtime(&source)
3363            .map_err(|error| ServiceError::Operation(error.to_string()))?
3364        {
3365            value["live_endpoint"] = json!(endpoint.as_str());
3366        }
3367    }
3368    if value.get("live_endpoint").is_none() {
3369        if let Some(peer) = peers.iter().find(|peer| {
3370            session.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3371                && peer.session_id == session.locator.session_id
3372        }) {
3373            value["live_endpoint"] = json!(peer.endpoint().as_str());
3374        }
3375    }
3376    Ok(value)
3377}
3378
3379fn live_index_changes(
3380    changes: Vec<crate::session_index::SessionIndexChange>,
3381    homes: &HarnessHomes,
3382) -> std::result::Result<Vec<Value>, ServiceError> {
3383    use crate::session_index::SessionIndexChange;
3384    let has_claude = changes.iter().any(|change| match change {
3385        SessionIndexChange::Added { descriptor } | SessionIndexChange::Updated { descriptor } => {
3386            descriptor.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3387        }
3388        SessionIndexChange::Removed { .. } => false,
3389    });
3390    let peers = if has_claude {
3391        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
3392    } else {
3393        Vec::new()
3394    };
3395    changes
3396        .into_iter()
3397        .map(|change| match change {
3398            SessionIndexChange::Added { descriptor } => Ok(json!({
3399                "kind": "added",
3400                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3401            })),
3402            SessionIndexChange::Updated { descriptor } => Ok(json!({
3403                "kind": "updated",
3404                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3405            })),
3406            SessionIndexChange::Removed { key } => Ok(json!({
3407                "kind": "removed",
3408                "key": key,
3409            })),
3410        })
3411        .collect()
3412}
3413
3414fn legacy_live_status(activity: &crate::SessionActivity) -> Option<&'static str> {
3415    use crate::{SessionPresence, SessionTurnState};
3416    match (activity.presence, activity.turn) {
3417        (SessionPresence::Persisted, _) => None,
3418        (SessionPresence::Running, SessionTurnState::Working) => Some("busy"),
3419        (SessionPresence::Running, SessionTurnState::Idle) => Some("idle"),
3420        // The normalized activity object can honestly report a live owner even
3421        // when the stock harness never published a turn status. Preserve the
3422        // older field's stricter contract instead of guessing `running`.
3423        (SessionPresence::Running, SessionTurnState::Unknown)
3424            if activity.evidence.native_state.is_none() =>
3425        {
3426            None
3427        }
3428        (SessionPresence::Running, _) | (SessionPresence::ShuttingDown, _) => Some("running"),
3429    }
3430}
3431
3432#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
3433#[serde(rename_all = "kebab-case")]
3434enum TransferFormat {
3435    ClaudeCode,
3436    Codex,
3437    #[serde(rename = "opencode", alias = "open-code")]
3438    OpenCode,
3439    Pi,
3440    Grok,
3441    Gemini,
3442    Goose,
3443    /// UNI-18: a Hermes target. Its artifact is the Codex rollout that
3444    /// `hermes sessions import --from codex` reads; `sessions.export` performs
3445    /// that import into the Hermes home.
3446    Hermes,
3447}
3448
3449impl TransferFormat {
3450    fn id(self) -> &'static str {
3451        match self {
3452            Self::ClaudeCode => HarnessId::CLAUDE_CODE,
3453            Self::Codex => HarnessId::CODEX,
3454            Self::OpenCode => HarnessId::OPENCODE,
3455            Self::Pi => HarnessId::PI,
3456            Self::Grok => HarnessId::GROK,
3457            Self::Gemini => HarnessId::GEMINI,
3458            Self::Goose => HarnessId::GOOSE,
3459            Self::Hermes => HarnessId::HERMES,
3460        }
3461    }
3462}
3463
3464impl From<TransferFormat> for SessionFormat {
3465    fn from(value: TransferFormat) -> Self {
3466        match value {
3467            TransferFormat::ClaudeCode => Self::ClaudeCode,
3468            TransferFormat::Codex => Self::Codex,
3469            TransferFormat::OpenCode => Self::OpenCode,
3470            TransferFormat::Pi => Self::Pi,
3471            TransferFormat::Grok => Self::Grok,
3472            TransferFormat::Gemini => Self::Gemini,
3473            TransferFormat::Goose => Self::Goose,
3474            // a Hermes artifact is the Codex rollout Hermes imports
3475            TransferFormat::Hermes => Self::Codex,
3476        }
3477    }
3478}
3479
3480#[derive(Deserialize)]
3481struct ImportSessionParams {
3482    source_harness: TransferFormat,
3483    content: String,
3484}
3485
3486#[derive(Deserialize)]
3487struct ExportSessionParams {
3488    locator: SessionLocator,
3489    target_harness: TransferFormat,
3490}
3491
3492#[derive(Deserialize)]
3493struct ReduceSessionParams {
3494    locator: SessionLocator,
3495    target_harness: TransferFormat,
3496    #[serde(default = "default_keep_last")]
3497    keep_last: usize,
3498}
3499
3500fn default_keep_last() -> usize {
3501    6
3502}
3503
3504#[derive(Deserialize)]
3505struct BranchSessionParams {
3506    locator: SessionLocator,
3507    #[serde(default)]
3508    target_harness: Option<TransferFormat>,
3509}
3510
3511#[derive(Deserialize)]
3512struct HandoffSessionParams {
3513    locator: SessionLocator,
3514    target_harness: TransferFormat,
3515    #[serde(default)]
3516    cwd: Option<PathBuf>,
3517}
3518
3519#[derive(Deserialize)]
3520struct MaterializeSessionParams {
3521    artifact: crate::native_materialize::MaterializeArtifact,
3522    cwd: PathBuf,
3523    /// Where the continuation is written; unset roots are the environment's own, as discovery reads them.
3524    #[serde(default)]
3525    homes: HarnessHomes,
3526}
3527
3528#[derive(Debug, Clone, Copy, Default, Deserialize)]
3529#[serde(rename_all = "snake_case")]
3530enum ResumePolicy {
3531    #[default]
3532    Default,
3533    Yolo,
3534}
3535
3536#[derive(Deserialize)]
3537struct ResumeInstructionsParams {
3538    locator: SessionLocator,
3539    #[serde(default)]
3540    cwd: Option<PathBuf>,
3541    #[serde(default)]
3542    policy: ResumePolicy,
3543}
3544
3545/// `harness.v1.workflow.load` parameters: which harness's board, and its home.
3546#[derive(Deserialize)]
3547struct WorkflowLoadParams {
3548    from: crate::workflow_doors::WorkflowHarness,
3549    home: PathBuf,
3550}
3551
3552/// ONT-4 `harness.v1.orchestration.load` parameters. `flavor` says which layout the
3553/// folder is read as; our own is the default.
3554#[derive(Deserialize)]
3555struct OrchestrationLoadParams {
3556    root: PathBuf,
3557    #[serde(default)]
3558    flavor: crate::orchestration_doors::HomeFlavor,
3559}
3560
3561/// ONT-4 `harness.v1.orchestration.save` parameters. `vault` is merged into the
3562/// home's own secrets; a caller that sends none keeps what is on disk.
3563#[derive(Deserialize)]
3564struct OrchestrationSaveParams {
3565    root: PathBuf,
3566    orchestration: crate::orchestration::Orchestration,
3567    #[serde(default)]
3568    vault: BTreeMap<String, String>,
3569}
3570
3571/// ONT-4 `harness.v1.orchestration.compile` parameters.
3572#[derive(Deserialize)]
3573struct OrchestrationCompileParams {
3574    from: crate::orchestration_doors::OrchestrationHarness,
3575    home: PathBuf,
3576}
3577
3578/// ONT-4 `harness.v1.orchestration.decompile` parameters. `source` is the home the
3579/// orchestration was compiled from: it is re-compiled to recover the io bookkeeping
3580/// that byte reuse and the live-store refusal (UNI-18) are decided from.
3581#[derive(Deserialize)]
3582struct OrchestrationDecompileParams {
3583    to: crate::orchestration_doors::OrchestrationHarness,
3584    orchestration: crate::orchestration::Orchestration,
3585    source: PathBuf,
3586    #[serde(default)]
3587    source_flavor: crate::orchestration_doors::SourceFlavor,
3588    dest: PathBuf,
3589    #[serde(default)]
3590    vault: BTreeMap<String, String>,
3591}
3592
3593/// `harness.v1.orchestration.import` parameters: another harness's home, and the
3594/// folder of ours it becomes.
3595#[derive(Deserialize)]
3596struct OrchestrationImportParams {
3597    from: crate::orchestration_doors::OrchestrationHarness,
3598    home: PathBuf,
3599    into: PathBuf,
3600}
3601
3602/// `harness.v1.orchestration.export` parameters: a folder of ours, and the home of
3603/// another harness it becomes.
3604#[derive(Deserialize)]
3605struct OrchestrationExportParams {
3606    to: crate::orchestration_doors::OrchestrationHarness,
3607    root: PathBuf,
3608    dest: PathBuf,
3609}
3610
3611/// `harness.v1.jobs.get` parameters.
3612#[derive(Deserialize)]
3613struct JobsGetParams {
3614    harness: String,
3615    id: String,
3616    #[serde(default)]
3617    homes: crate::HarnessHomes,
3618}
3619
3620/// ORCH-18: run one mutating job verb through the harness's own CLI.
3621///
3622/// The refusal ladder is deliberate: a harness with no scheduled-job concept
3623/// at all answers with the SAME sentence `jobs.list` gives it, and a harness
3624/// that has jobs but publishes no client-callable verb (Claude Code, whose
3625/// jobs are created by the model inside a session) answers with its own
3626/// reason. Neither is ever a silent no-op.
3627fn mutate_job(
3628    verb: crate::jobs_control::JobVerb,
3629    params: Value,
3630) -> std::result::Result<Value, ServiceError> {
3631    let mutation = decode::<crate::jobs_control::JobMutation>(params)?;
3632    refuse_harness_without_jobs(&mutation.harness, &format!("jobs.{}", verb.as_str()))?;
3633    let outcome = crate::jobs_control::mutate(verb, &mutation).map_err(job_control_error)?;
3634    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3635}
3636
3637/// ORCH-22: run one mutating skills verb through the harness's own door.
3638///
3639/// The refusal ladder mirrors `jobs.*`: a harness with no skills root at all
3640/// answers with the same sentence `skills.list` gives it, and a harness whose
3641/// door does not publish this verb (OpenClaw has no `skills remove` at the
3642/// pin) answers with its own reason. Neither is ever a silent no-op.
3643fn mutate_skill(
3644    verb: crate::skills_control::SkillVerb,
3645    params: Value,
3646) -> std::result::Result<Value, ServiceError> {
3647    let mutation = decode::<crate::skills_control::SkillMutation>(params)?;
3648    if !crate::skills_control::supports_skill_control(&mutation.harness) {
3649        return Err(ServiceError::UnsupportedAction(format!(
3650            "`{}` has no skills root supercode reads; `skills.{}` is supported for: {}",
3651            mutation.harness,
3652            verb.as_str(),
3653            crate::skills_control::CONTROLLED_SKILL_HARNESSES.join(", ")
3654        )));
3655    }
3656    let outcome =
3657        crate::skills_control::mutate_skill(verb, &mutation).map_err(skill_control_error)?;
3658    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3659}
3660
3661/// The skills twin of [`job_control_error`], with the same mapping rule.
3662fn skill_control_error(error: crate::skills_control::SkillControlError) -> ServiceError {
3663    match error {
3664        crate::skills_control::SkillControlError::Unsupported(message) => {
3665            ServiceError::UnsupportedAction(message)
3666        }
3667        crate::skills_control::SkillControlError::Invalid(message) => {
3668            ServiceError::InvalidParams(message)
3669        }
3670        crate::skills_control::SkillControlError::Failed(message) => {
3671            ServiceError::Operation(message)
3672        }
3673    }
3674}
3675
3676/// ORCH-21: run one mutating profile verb through the harness's own CLI.
3677///
3678/// The refusal ladder mirrors `mutate_job`'s: a harness with no profile
3679/// concept at all answers with the SAME sentence `profiles.list` gives it, and
3680/// a harness that HAS profiles but publishes no client-callable verb (Codex's
3681/// file-authored `[profiles.<name>]` tables, supercode's compiled-in presets)
3682/// answers with its own reason. Neither is ever a silent no-op.
3683fn mutate_profile(
3684    verb: crate::profiles_control::ProfileVerb,
3685    params: Value,
3686) -> std::result::Result<Value, ServiceError> {
3687    let mutation = decode::<crate::profiles_control::ProfileMutation>(params)?;
3688    let outcome =
3689        crate::profiles_control::mutate(verb, &mutation).map_err(profile_control_error)?;
3690    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3691}
3692
3693/// The same mapping `job_control_error` applies, for the profile noun.
3694fn profile_control_error(error: crate::profiles_control::ProfileControlError) -> ServiceError {
3695    match error {
3696        crate::profiles_control::ProfileControlError::Unsupported(message) => {
3697            ServiceError::UnsupportedAction(message)
3698        }
3699        crate::profiles_control::ProfileControlError::Invalid(message) => {
3700            ServiceError::InvalidParams(message)
3701        }
3702        crate::profiles_control::ProfileControlError::Failed(message) => {
3703            ServiceError::Operation(message)
3704        }
3705    }
3706}
3707
3708/// Map a controlled-tier failure onto the service's error vocabulary. A verb
3709/// the harness lacks is `UnsupportedAction`; a harness verb that RAN and
3710/// failed carries its own stderr through as the operation error.
3711fn job_control_error(error: crate::jobs_control::JobControlError) -> ServiceError {
3712    match error {
3713        crate::jobs_control::JobControlError::Unsupported(message) => {
3714            ServiceError::UnsupportedAction(message)
3715        }
3716        crate::jobs_control::JobControlError::Invalid(message) => {
3717            ServiceError::InvalidParams(message)
3718        }
3719        crate::jobs_control::JobControlError::Failed(message) => ServiceError::Operation(message),
3720    }
3721}
3722
3723/// Map an ORCH-19 controlled-tier failure onto the service's error
3724/// vocabulary. A verb the harness has no door for is `UnsupportedAction`; a
3725/// door that RAN and failed carries the harness's own stderr / HTTP body
3726/// through as the operation error.
3727fn session_control_error(error: crate::SessionControlError) -> ServiceError {
3728    match error {
3729        crate::SessionControlError::Unsupported(message) => {
3730            ServiceError::UnsupportedAction(message)
3731        }
3732        crate::SessionControlError::Invalid(message) => ServiceError::InvalidParams(message),
3733        crate::SessionControlError::Failed(message) => ServiceError::Operation(message),
3734    }
3735}
3736
3737/// A harness without a scheduled-job concept refuses the verb rather than
3738/// answering with an empty list — an absent capability and an empty inventory
3739/// are different answers (the same rule `runtimes.capabilities` applies to
3740/// `steer`).
3741fn refuse_harness_without_jobs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3742    if crate::jobs::supports_jobs(harness) {
3743        return Ok(());
3744    }
3745    Err(ServiceError::UnsupportedAction(format!(
3746        "`{harness}` has no scheduled jobs; `{verb}` is supported for: {}",
3747        crate::jobs::JOB_HARNESSES.join(", ")
3748    )))
3749}
3750
3751/// `harness.v1.runs.get` parameters.
3752#[derive(Deserialize)]
3753struct RunsGetParams {
3754    harness: String,
3755    id: String,
3756    #[serde(default)]
3757    homes: crate::HarnessHomes,
3758}
3759
3760/// A harness with no run store refuses the verb rather than answering with an
3761/// empty history — the same rule `jobs.list` applies. Claude Code lands here
3762/// on purpose: its cron fires are ordinary turns inside the session that
3763/// created the job, so there is no fire record to list.
3764fn refuse_harness_without_runs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3765    if crate::runs::supports_runs(harness) {
3766        return Ok(());
3767    }
3768    Err(ServiceError::UnsupportedAction(format!(
3769        "`{harness}` keeps no run store; `{verb}` is supported for: {}",
3770        crate::runs::RUN_HARNESSES.join(", ")
3771    )))
3772}
3773
3774#[derive(Serialize)]
3775struct SessionArtifact {
3776    source_harness: HarnessId,
3777    target_harness: &'static str,
3778    session_id: Option<String>,
3779    content: String,
3780    suggested_filename: String,
3781    files: Vec<SessionArtifactFile>,
3782    fidelity: Fidelity,
3783    residue: Vec<String>,
3784}
3785
3786#[derive(Serialize)]
3787struct SessionArtifactFile {
3788    path: String,
3789    content: String,
3790    role: ArtifactFileRole,
3791}
3792
3793#[derive(Serialize)]
3794#[serde(rename_all = "snake_case")]
3795enum ArtifactFileRole {
3796    Primary,
3797    Subagent,
3798    Bundle,
3799    SourceRecovery,
3800}
3801
3802#[derive(Serialize)]
3803struct StructuredLaunch {
3804    cwd: PathBuf,
3805    program: String,
3806    arguments: Vec<String>,
3807    env: BTreeMap<String, String>,
3808}
3809
3810struct HandoffInstructions {
3811    launch: StructuredLaunch,
3812    materialize: Option<StructuredLaunch>,
3813    requires_materialization: bool,
3814    note: String,
3815}
3816
3817#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
3818#[serde(rename_all = "snake_case")]
3819enum HarnessProbeLevel {
3820    #[default]
3821    Passive,
3822    Handshake,
3823}
3824
3825#[derive(Default, Deserialize)]
3826#[serde(default)]
3827struct HarnessInventoryParams {
3828    harness: Option<HarnessId>,
3829    harnesses: Vec<HarnessId>,
3830    workspace: Option<PathBuf>,
3831    probe: HarnessProbeLevel,
3832    include_sessions: bool,
3833    /// Omit subprocess-based `--version` calls when a latency-sensitive UI only needs readiness.
3834    skip_versions: bool,
3835}
3836
3837#[derive(Deserialize)]
3838struct HarnessAuthenticationParams {
3839    harness: HarnessId,
3840}
3841
3842#[derive(Deserialize)]
3843struct BeginHarnessAuthenticationParams {
3844    harness: HarnessId,
3845    #[serde(default = "local_browser_authentication_environment")]
3846    environment: crate::HarnessAuthenticationEnvironment,
3847    #[serde(default)]
3848    method: Option<crate::HarnessAuthenticationMethodId>,
3849    #[serde(default)]
3850    cwd: Option<PathBuf>,
3851}
3852
3853fn local_browser_authentication_environment() -> crate::HarnessAuthenticationEnvironment {
3854    crate::HarnessAuthenticationEnvironment::LocalBrowser
3855}
3856
3857#[derive(Serialize)]
3858struct HarnessInventoryReport {
3859    probe: HarnessProbeLevel,
3860    workspace: Option<PathBuf>,
3861    harnesses: Vec<LocalHarness>,
3862}
3863
3864#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3865#[serde(rename_all = "snake_case")]
3866enum HarnessAuthState {
3867    Ready,
3868    Configured,
3869    Required,
3870    Unknown,
3871}
3872
3873#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3874#[serde(rename_all = "snake_case")]
3875enum HarnessRuntimeState {
3876    Ready,
3877    Degraded,
3878    Unavailable,
3879}
3880
3881#[derive(Serialize)]
3882struct HarnessSessionCounts {
3883    global: Option<usize>,
3884    workspace: Option<usize>,
3885}
3886
3887/// Receipt-backed evidence that a harness has a RUNNING instance right now,
3888/// distinct from being merely installed (UNI-7). Detection is passive and
3889/// default-on: a gateway liveness connect for daemon harnesses, a fresh
3890/// SQLite WAL stamp for store-writer harnesses (precedent: the opencode
3891/// follower's -wal/-shm freshness). Control stays behind per-connection
3892/// grants — this reports observations only.
3893/// ORCH-17: the gateway-health noun on an inventory row. Derived from the
3894/// UNI-7 running-instance probe (Hermes: `state.db-wal` freshness; OpenClaw:
3895/// a TCP connect to the gateway endpoint resolved from its OWN config) plus
3896/// the executable version — never by starting anything.
3897#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3898#[serde(rename_all = "snake_case")]
3899pub enum GatewayState {
3900    Up,
3901    Down,
3902    Unknown,
3903}
3904
3905/// ORCH-17: `gateway` on a `harness.v1.harnesses.list` row.
3906#[derive(Debug, Clone, Serialize)]
3907pub struct GatewayHealth {
3908    pub state: GatewayState,
3909    /// The endpoint supercode would connect to (OpenClaw: the gateway
3910    /// WebSocket resolved from `openclaw.json`; core harnesses: their
3911    /// declared connect address when one exists). `None` when the harness
3912    /// has no single endpoint (Hermes multiplexes platforms).
3913    #[serde(skip_serializing_if = "Option::is_none")]
3914    pub endpoint: Option<String>,
3915    #[serde(skip_serializing_if = "Option::is_none")]
3916    pub version: Option<String>,
3917    /// What the verdict rests on, or why it is `unknown`.
3918    pub evidence: String,
3919    pub checked_at_ms: u64,
3920}
3921
3922/// OpenClaw's gateway WebSocket endpoint, resolved from its own config the
3923/// way the registry's connect descriptor prescribes (`gateway.url`, else
3924/// `gateway.port`, else the documented default).
3925fn openclaw_gateway_endpoint(home: &Path) -> String {
3926    let config_path = home.join(".openclaw/openclaw.json");
3927    let gateway = std::fs::read_to_string(&config_path)
3928        .ok()
3929        .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
3930        .and_then(|config| config.get("gateway").cloned());
3931    if let Some(url) = gateway
3932        .as_ref()
3933        .and_then(|gateway| gateway.get("url"))
3934        .and_then(serde_json::Value::as_str)
3935    {
3936        return url.to_string();
3937    }
3938    let port = gateway
3939        .as_ref()
3940        .and_then(|gateway| gateway.get("port"))
3941        .and_then(serde_json::Value::as_u64)
3942        .unwrap_or(18789);
3943    format!("ws://127.0.0.1:{port}")
3944}
3945
3946/// Ask Hermes itself (`hermes gateway status`, read-only, ~1 s) whether its
3947/// gateway is up. The command is per-host launchd/systemd text without a JSON
3948/// form at 0.19–0.21; the verdict is read from the lines it prints:
3949/// "supervised by launchd (PID …)" / "is running" → up, "not running" /
3950/// "not installed" → down, anything else → no verdict. `SUPERCODE_HERMES_BIN`
3951/// overrides the executable so a fake can stand in under test.
3952fn hermes_gateway_status() -> Option<(GatewayState, String)> {
3953    let program = crate::harness_command::harness_program(HarnessId::HERMES).ok()?;
3954    let output = std::process::Command::new(&program)
3955        .args(["gateway", "status"])
3956        .stdin(std::process::Stdio::null())
3957        .output()
3958        .ok()?;
3959    let text = format!(
3960        "{}{}",
3961        String::from_utf8_lossy(&output.stdout),
3962        String::from_utf8_lossy(&output.stderr)
3963    );
3964    let verdict = text.lines().find_map(|line| {
3965        let l = line.trim();
3966        if l.contains("supervised by launchd (PID")
3967            || l.contains("supervised by systemd (PID")
3968            || l.contains("Gateway is running")
3969            || l.contains("process is running")
3970        {
3971            Some((GatewayState::Up, format!("`hermes gateway status`: {l}")))
3972        } else if l.contains("not running") || l.contains("not installed") {
3973            Some((GatewayState::Down, format!("`hermes gateway status`: {l}")))
3974        } else {
3975            None
3976        }
3977    });
3978    verdict
3979}
3980
3981fn gateway_health(
3982    id: &str,
3983    installed: bool,
3984    running: Option<&RunningInstance>,
3985    version: Option<&str>,
3986) -> GatewayHealth {
3987    let checked_at_ms = now_epoch_ms();
3988    let home = std::env::var_os("HOME").map(PathBuf::from);
3989    match id {
3990        HarnessId::HERMES | HarnessId::OPENCLAW => {
3991            let endpoint = (id == HarnessId::OPENCLAW)
3992                .then(|| home.as_deref().map(openclaw_gateway_endpoint))
3993                .flatten();
3994            let (state, evidence) = match running {
3995                Some(instance) => (GatewayState::Up, instance.evidence.clone()),
3996                None if !installed => (
3997                    GatewayState::Unknown,
3998                    format!("`{id}` is not installed; no gateway to probe"),
3999                ),
4000                None if id == HarnessId::HERMES => match hermes_gateway_status() {
4001                    // The harness's own door outranks the WAL heuristic: an idle
4002                    // gateway writes nothing for minutes yet is up.
4003                    Some((state, evidence)) => (state, evidence),
4004                    None => (
4005                        GatewayState::Down,
4006                        "no fresh state.db-wal activity under ~/.hermes and `hermes gateway status` gave no verdict".to_string(),
4007                    ),
4008                },
4009                None => (
4010                    GatewayState::Down,
4011                    format!(
4012                        "no TCP listener at {}",
4013                        endpoint.as_deref().unwrap_or("the gateway endpoint")
4014                    ),
4015                ),
4016            };
4017            GatewayHealth {
4018                state,
4019                endpoint,
4020                version: version.map(str::to_string),
4021                evidence,
4022                checked_at_ms,
4023            }
4024        }
4025        // ORC-7: the orchestrator's gateway IS its daemon, and the daemon's
4026        // own lease file is the record of it. A lease naming a live pid is
4027        // up; a lease whose process is gone is down and says so as a STALE
4028        // lease, never as "no lease"; no lease at all is down. Nothing is
4029        // started, and no port is guessed — the daemon multiplexes adapters
4030        // the way Hermes does, so it has no single endpoint either.
4031        HarnessId::ORCHESTRATOR => {
4032            let root = crate::HarnessHomes::default().orchestrator;
4033            let (state, evidence) = match crate::orchestrator::read_lease(&root) {
4034                Some(lease) if lease.is_live() => (
4035                    GatewayState::Up,
4036                    format!(
4037                        "`{}` names pid {} (started {}), which is live",
4038                        crate::orchestrator::lock_path(&root).display(),
4039                        lease.pid,
4040                        lease.started_at
4041                    ),
4042                ),
4043                Some(lease) => (
4044                    GatewayState::Down,
4045                    format!(
4046                        "stale lease `{}`: pid {} is gone",
4047                        crate::orchestrator::lock_path(&root).display(),
4048                        lease.pid
4049                    ),
4050                ),
4051                None => (
4052                    GatewayState::Down,
4053                    format!(
4054                        "no lease at `{}`; `supercode orchestrator start` writes one",
4055                        crate::orchestrator::lock_path(&root).display()
4056                    ),
4057                ),
4058            };
4059            GatewayHealth {
4060                state,
4061                endpoint: None,
4062                version: version.map(str::to_string),
4063                evidence,
4064                checked_at_ms,
4065            }
4066        }
4067        _ => GatewayHealth {
4068            state: GatewayState::Unknown,
4069            endpoint: None,
4070            version: version.map(str::to_string),
4071            evidence: format!("`{id}` runs per session, not as a gateway"),
4072            checked_at_ms,
4073        },
4074    }
4075}
4076
4077#[derive(Debug, Clone, Serialize)]
4078struct RunningInstance {
4079    /// How the instance was detected.
4080    method: RunningInstanceMethod,
4081    /// The evidence the verdict rests on (endpoint reached / WAL path+age).
4082    evidence: String,
4083    /// Epoch-ms instant the probe executed.
4084    checked_at_ms: u64,
4085}
4086
4087#[derive(Debug, Clone, Copy, Serialize)]
4088#[serde(rename_all = "snake_case")]
4089enum RunningInstanceMethod {
4090    /// A TCP connect to the harness's own configured gateway endpoint
4091    /// succeeded.
4092    GatewayConnect,
4093    /// The harness's session store has an active SQLite WAL (a live writer
4094    /// holds the store open and stamped it recently).
4095    StoreWalActivity,
4096}
4097
4098fn now_epoch_ms() -> u64 {
4099    std::time::SystemTime::now()
4100        .duration_since(std::time::UNIX_EPOCH)
4101        .map(|elapsed| elapsed.as_millis() as u64)
4102        .unwrap_or(0)
4103}
4104
4105/// OpenClaw: the gateway endpoint comes from the harness's OWN config
4106/// (`<home>/.openclaw/openclaw.json` — `gateway.url` or `gateway.port`,
4107/// default port 18789); a successful TCP connect is the running signal.
4108fn probe_openclaw_running(home: &Path) -> Option<RunningInstance> {
4109    let config_path = home.join(".openclaw/openclaw.json");
4110    let text = std::fs::read_to_string(&config_path).ok();
4111    let gateway = text
4112        .as_deref()
4113        .and_then(|raw| serde_json::from_str::<serde_json::Value>(raw).ok())
4114        .and_then(|config| config.get("gateway").cloned());
4115    let address = gateway
4116        .as_ref()
4117        .and_then(|gateway| gateway.get("url"))
4118        .and_then(serde_json::Value::as_str)
4119        .and_then(|url| {
4120            url.split("://").nth(1).map(|rest| {
4121                rest.trim_end_matches('/')
4122                    .split('/')
4123                    .next()
4124                    .unwrap_or(rest)
4125                    .to_string()
4126            })
4127        })
4128        .unwrap_or_else(|| {
4129            let port = gateway
4130                .as_ref()
4131                .and_then(|gateway| gateway.get("port"))
4132                .and_then(serde_json::Value::as_u64)
4133                .unwrap_or(18789);
4134            format!("127.0.0.1:{port}")
4135        });
4136    let reachable = std::net::TcpStream::connect_timeout(
4137        &address.parse().ok()?,
4138        std::time::Duration::from_millis(400),
4139    )
4140    .is_ok();
4141    reachable.then(|| RunningInstance {
4142        method: RunningInstanceMethod::GatewayConnect,
4143        evidence: format!(
4144            "gateway endpoint {address} accepted a TCP connect (from {})",
4145            config_path.display()
4146        ),
4147        checked_at_ms: now_epoch_ms(),
4148    })
4149}
4150
4151/// Hermes: `<home>/.hermes/state.db-wal` freshly modified means a live writer
4152/// holds the store open (SQLite WAL exists only while a connection is open;
4153/// a recent stamp distinguishes an active instance from a stale crash
4154/// leftover).
4155fn probe_hermes_running(home: &Path, max_wal_age_ms: u64) -> Option<RunningInstance> {
4156    let wal = home.join(".hermes/state.db-wal");
4157    let modified = std::fs::metadata(&wal).ok()?.modified().ok()?;
4158    let age_ms = std::time::SystemTime::now()
4159        .duration_since(modified)
4160        .map(|age| age.as_millis() as u64)
4161        .unwrap_or(u64::MAX);
4162    (age_ms <= max_wal_age_ms).then(|| RunningInstance {
4163        method: RunningInstanceMethod::StoreWalActivity,
4164        evidence: format!(
4165            "{} stamped {age_ms}ms ago (threshold {max_wal_age_ms}ms)",
4166            wal.display()
4167        ),
4168        checked_at_ms: now_epoch_ms(),
4169    })
4170}
4171
4172/// Default-on running-instance detection for the harnesses that have one.
4173fn probe_running_instance(id: &str) -> Option<RunningInstance> {
4174    let home = std::env::var_os("HOME").map(PathBuf::from)?;
4175    match id {
4176        HarnessId::OPENCLAW => probe_openclaw_running(&home),
4177        HarnessId::HERMES => probe_hermes_running(&home, 300_000),
4178        _ => None,
4179    }
4180}
4181
4182#[derive(Serialize)]
4183struct LocalHarness {
4184    id: HarnessId,
4185    display_name: String,
4186    supported: bool,
4187    installed: bool,
4188    executable: Option<String>,
4189    version: Option<String>,
4190    auth: HarnessAuthState,
4191    runtime: HarnessRuntimeState,
4192    protocol: String,
4193    capabilities: crate::RuntimeCapabilities,
4194    effective_capabilities: crate::RuntimeCapabilities,
4195    sessions: HarnessSessionCounts,
4196    /// Receipt-backed running-instance detection (None = not detected or the
4197    /// harness has no running-instance concept). Distinct from `installed`.
4198    #[serde(skip_serializing_if = "Option::is_none")]
4199    running: Option<RunningInstance>,
4200    /// ORCH-17: gateway health derived from `running` + the harness's own config.
4201    gateway: GatewayHealth,
4202    reason: Option<String>,
4203    repair: Option<String>,
4204}
4205
4206#[derive(Clone, Deserialize)]
4207struct RuntimeBackendParams {
4208    harness: HarnessId,
4209    #[serde(default)]
4210    protocol: Option<String>,
4211    #[serde(default)]
4212    launch: Option<RuntimeLaunch>,
4213    #[serde(default)]
4214    base_url: Option<String>,
4215    #[serde(default)]
4216    policy: RuntimePolicy,
4217}
4218
4219#[derive(Debug, Clone, Copy, Default, Deserialize)]
4220#[serde(rename_all = "snake_case")]
4221enum RuntimePolicy {
4222    #[default]
4223    Default,
4224    Yolo,
4225}
4226
4227#[derive(Deserialize)]
4228struct RuntimeStartParams {
4229    #[serde(flatten)]
4230    backend: RuntimeBackendParams,
4231    cwd: PathBuf,
4232    /// MCP servers to mount into the new session through the harness's own
4233    /// start door (ORC-6). Backends without such a door ignore them.
4234    #[serde(default)]
4235    mcp_servers: Vec<crate::McpServerLaunch>,
4236    /// The session's approval policy, where the harness's start door takes one (Codex).
4237    #[serde(default)]
4238    approval_policy: Option<String>,
4239}
4240
4241#[derive(Deserialize)]
4242struct RuntimeAttachParams {
4243    #[serde(flatten)]
4244    backend: RuntimeBackendParams,
4245    runtime_id: String,
4246    #[serde(default)]
4247    cwd: Option<PathBuf>,
4248    /// MCP servers to mount into the resumed session (the start door's own
4249    /// field, carried again because a session's tools die with its process).
4250    #[serde(default)]
4251    mcp_servers: Vec<crate::McpServerLaunch>,
4252    /// The session's approval policy, carried again on resume as on start (Codex).
4253    #[serde(default)]
4254    approval_policy: Option<String>,
4255}
4256
4257#[derive(Deserialize)]
4258struct RuntimeConnectionParams {
4259    connection: String,
4260}
4261
4262#[derive(Deserialize)]
4263struct RuntimeInputParams {
4264    connection: String,
4265    text: String,
4266    #[serde(default)]
4267    image_urls: Vec<String>,
4268}
4269
4270const MAX_RUNTIME_IMAGES: usize = 4;
4271const MAX_RUNTIME_IMAGE_URL_BYTES: usize = 12 * 1024 * 1024;
4272const MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL: usize = 32 * 1024 * 1024;
4273
4274fn validate_runtime_image_urls(image_urls: Vec<String>) -> Result<Vec<String>, ServiceError> {
4275    if image_urls.len() > MAX_RUNTIME_IMAGES {
4276        return Err(ServiceError::InvalidParams(format!(
4277            "a runtime prompt accepts at most {MAX_RUNTIME_IMAGES} images"
4278        )));
4279    }
4280    let mut total = 0usize;
4281    for url in &image_urls {
4282        if !(url.starts_with("data:image/")
4283            || url.starts_with("https://")
4284            || url.starts_with("http://"))
4285        {
4286            return Err(ServiceError::InvalidParams(
4287                "runtime images must be image data URLs or HTTP(S) URLs".into(),
4288            ));
4289        }
4290        if url.len() > MAX_RUNTIME_IMAGE_URL_BYTES {
4291            return Err(ServiceError::InvalidParams(format!(
4292                "one runtime image exceeds the {MAX_RUNTIME_IMAGE_URL_BYTES}-byte encoded limit"
4293            )));
4294        }
4295        total = total.saturating_add(url.len());
4296    }
4297    if total > MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL {
4298        return Err(ServiceError::InvalidParams(format!(
4299            "runtime images exceed the {MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL}-byte encoded total limit"
4300        )));
4301    }
4302    Ok(image_urls)
4303}
4304
4305#[derive(Deserialize)]
4306struct RuntimeRespondParams {
4307    connection: String,
4308    request_id: Value,
4309    response: Value,
4310}
4311
4312fn default_reduction_store_root() -> PathBuf {
4313    if let Some(root) = std::env::var_os("SUPERCODE_HOME") {
4314        return PathBuf::from(root).join("sessions");
4315    }
4316    if let Some(home) = std::env::var_os("HOME") {
4317        return PathBuf::from(home).join(".supercode").join("sessions");
4318    }
4319    PathBuf::from(".supercode").join("sessions")
4320}
4321
4322fn messages_jsonl(messages: &[crate::ChatMessage]) -> std::result::Result<String, ServiceError> {
4323    let mut output = String::new();
4324    for message in messages {
4325        output.push_str(
4326            &serde_json::to_string(message)
4327                .map_err(|error| ServiceError::Operation(error.to_string()))?,
4328        );
4329        output.push('\n');
4330    }
4331    Ok(output)
4332}
4333
4334fn parse_messages_jsonl(
4335    content: &str,
4336) -> std::result::Result<Vec<crate::ChatMessage>, ServiceError> {
4337    content
4338        .lines()
4339        .enumerate()
4340        .filter(|(_, line)| !line.trim().is_empty())
4341        .map(|(index, line)| {
4342            serde_json::from_str::<crate::ChatMessage>(line).map_err(|error| {
4343                ServiceError::Operation(format!(
4344                    "reduced transcript line {} is invalid: {error}",
4345                    index + 1
4346                ))
4347            })
4348        })
4349        .collect()
4350}
4351
4352fn reduced_bootstrap_prompt(
4353    source: &SessionLocator,
4354    target: TransferFormat,
4355    view_jsonl: &str,
4356    sidecar_path: &Path,
4357    reduction_log_path: &Path,
4358) -> String {
4359    format!(
4360        "Continue the work from this losslessly reduced {source_harness} session in {target_harness}.\n\
4361         \n\
4362         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\
4363         \n\
4364         <supercode-reduced-session source-session=\"{source_id}\">\n\
4365         {view_jsonl}\
4366         </supercode-reduced-session>\n\
4367         \n\
4368         Resume from the latest unresolved user request and preserve the source session's decisions and constraints.",
4369        source_harness = source.harness.as_str(),
4370        target_harness = target.id(),
4371        sidecar = sidecar_path.display(),
4372        log = reduction_log_path.display(),
4373        source_id = source.session_id,
4374    )
4375}
4376
4377fn session_artifact(
4378    locator: &SessionLocator,
4379    session: &Session,
4380    target: TransferFormat,
4381) -> std::result::Result<SessionArtifact, ServiceError> {
4382    session_artifact_with_id(locator, session, target, None)
4383}
4384
4385fn session_artifact_with_id(
4386    locator: &SessionLocator,
4387    session: &Session,
4388    target: TransferFormat,
4389    target_session_id: Option<&str>,
4390) -> std::result::Result<SessionArtifact, ServiceError> {
4391    let format: SessionFormat = target.into();
4392    let diagonal = format.source() == session.meta.source;
4393    crate::residue_store::store_segments(session);
4394    let has_appended_turns = session
4395        .imported_message_count
4396        .is_some_and(|imported| imported < session.messages.len());
4397    let mut restoration = None;
4398    let content = if let Some(id) = target_session_id {
4399        if diagonal && format != SessionFormat::OpenCode {
4400            session
4401                .to_jsonl_spliced(format, Some(id))
4402                .map_err(operation)?
4403        } else {
4404            let mut rewritten = session.clone();
4405            rewritten.meta.session_id = Some(id.to_string());
4406            rewritten.to_jsonl(format).map_err(operation)?
4407        }
4408    } else if diagonal && session.raw_is_verbatim && !has_appended_turns {
4409        session.raw_verbatim()
4410    } else if diagonal {
4411        session.to_jsonl_spliced(format, None).map_err(operation)?
4412    } else {
4413        // A session that came from `format` before returns its source records verbatim for the
4414        // prefix the residue store holds (docs/plans/portable-residue.md).
4415        match session
4416            .restore_residue(format, crate::residue_store::lookup)
4417            .map_err(operation)?
4418        {
4419            Some((content, report)) => {
4420                restoration = Some(report);
4421                content
4422            }
4423            None => session.to_jsonl(format).map_err(operation)?,
4424        }
4425    };
4426    let stem = sanitize_filename(
4427        target_session_id
4428            .or(session.meta.session_id.as_deref())
4429            .unwrap_or(&locator.session_id),
4430    );
4431    let suggested_filename = if diagonal && target == TransferFormat::Grok {
4432        "chat_history.jsonl".to_string()
4433    } else if target == TransferFormat::Goose {
4434        format!("{stem}.goose.json")
4435    } else {
4436        format!("{stem}.{}.jsonl", target.id())
4437    };
4438    let mut files = vec![SessionArtifactFile {
4439        path: suggested_filename.clone(),
4440        content: content.clone(),
4441        role: ArtifactFileRole::Primary,
4442    }];
4443    if target == TransferFormat::ClaudeCode {
4444        let bundle_stem = Path::new(&suggested_filename)
4445            .file_stem()
4446            .and_then(|stem| stem.to_str())
4447            .unwrap_or(&stem);
4448        let mut child_paths = BTreeSet::new();
4449        for (index, subagent) in session.subagents.iter().enumerate() {
4450            let agent_id = subagent
4451                .meta
4452                .agent_id
4453                .as_deref()
4454                .map(|id| id.strip_prefix("agent-").unwrap_or(id))
4455                .map(sanitize_filename)
4456                .filter(|id| !id.is_empty())
4457                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4458            let child_has_appended_turns = subagent
4459                .imported_message_count
4460                .is_some_and(|imported| imported < subagent.messages.len());
4461            let child_content = if target_session_id.is_none()
4462                && subagent.meta.source == SessionSource::ClaudeCode
4463                && subagent.raw_is_verbatim
4464                && !child_has_appended_turns
4465            {
4466                subagent.raw_verbatim()
4467            } else if subagent.meta.source == SessionSource::ClaudeCode {
4468                subagent
4469                    .to_jsonl_spliced(SessionFormat::ClaudeCode, target_session_id)
4470                    .map_err(operation)?
4471            } else {
4472                let mut child = subagent.clone();
4473                if let Some(id) = target_session_id {
4474                    child.meta.session_id = Some(id.to_string());
4475                }
4476                child
4477                    .to_jsonl(SessionFormat::ClaudeCode)
4478                    .map_err(operation)?
4479            };
4480            let path = format!("{bundle_stem}/subagents/agent-{agent_id}.jsonl");
4481            if !child_paths.insert(path.clone()) {
4482                return Err(ServiceError::Operation(format!(
4483                    "Claude subagent ids collide at artifact path `{path}`"
4484                )));
4485            }
4486            files.push(SessionArtifactFile {
4487                path,
4488                content: child_content,
4489                role: ArtifactFileRole::Subagent,
4490            });
4491        }
4492    }
4493    if diagonal && target == TransferFormat::Grok {
4494        append_grok_bundle_files(locator, "", ArtifactFileRole::Bundle, &mut files)?;
4495    }
4496    if !diagonal || !session.raw_is_verbatim {
4497        files.push(SessionArtifactFile {
4498            path: "recovery/source.supercode.jsonl".into(),
4499            content: session.to_native_jsonl(),
4500            role: ArtifactFileRole::SourceRecovery,
4501        });
4502        for (index, subagent) in session.subagents.iter().enumerate() {
4503            let id = subagent
4504                .meta
4505                .agent_id
4506                .as_deref()
4507                .map(sanitize_filename)
4508                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4509            files.push(SessionArtifactFile {
4510                path: format!("recovery/subagents/{id}.supercode.jsonl"),
4511                content: subagent.to_native_jsonl(),
4512                role: ArtifactFileRole::SourceRecovery,
4513            });
4514        }
4515    }
4516    if !diagonal && session.meta.source == SessionSource::Grok {
4517        append_grok_bundle_files(
4518            locator,
4519            "recovery/grok/",
4520            ArtifactFileRole::SourceRecovery,
4521            &mut files,
4522        )?;
4523    }
4524    let (fidelity, residue) = if diagonal
4525        && target_session_id.is_none()
4526        && session.raw_is_verbatim
4527        && !has_appended_turns
4528    {
4529        (Fidelity::ByteLossless, Vec::new())
4530    } else if diagonal && !(target_session_id.is_some() && target == TransferFormat::OpenCode) {
4531        (
4532            Fidelity::ValueLossless,
4533            vec![if target_session_id.is_some() {
4534                "target identity was rewritten, so the artifact intentionally differs from source bytes".into()
4535            } else {
4536                "source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured".into()
4537            }],
4538        )
4539    } else {
4540        match restoration {
4541            Some(report) if report.rendered_messages == 0 => (
4542                Fidelity::ByteLossless,
4543                vec![format!(
4544                    "restored verbatim from this conversation's {} source records in the residue store",
4545                    target.id()
4546                )],
4547            ),
4548            Some(report) => (
4549                Fidelity::Semantic,
4550                vec![format!(
4551                    "{} of {} messages restored verbatim from the residue store; the other {} written by the {} writer",
4552                    report.restored_messages,
4553                    report.restored_messages + report.rendered_messages,
4554                    report.rendered_messages,
4555                    target.id()
4556                )],
4557            ),
4558            None => (
4559                Fidelity::Semantic,
4560                vec!["target schema has no portable slot for every source-native record and metadata field".into()],
4561            ),
4562        }
4563    };
4564    Ok(SessionArtifact {
4565        source_harness: locator.harness.clone(),
4566        target_harness: target.id(),
4567        session_id: target_session_id
4568            .map(str::to_string)
4569            .or_else(|| session.meta.session_id.clone()),
4570        content,
4571        suggested_filename,
4572        files,
4573        fidelity,
4574        residue,
4575    })
4576}
4577
4578fn append_grok_bundle_files(
4579    locator: &SessionLocator,
4580    prefix: &str,
4581    role: ArtifactFileRole,
4582    files: &mut Vec<SessionArtifactFile>,
4583) -> std::result::Result<(), ServiceError> {
4584    let primary = locator.storage.path();
4585    if primary.file_name().and_then(|name| name.to_str()) != Some("chat_history.jsonl") {
4586        return Err(ServiceError::Operation(format!(
4587            "Grok bundle locator must name chat_history.jsonl, got {}",
4588            primary.display()
4589        )));
4590    }
4591    let parent = primary.parent().ok_or_else(|| {
4592        ServiceError::Operation("Grok chat_history.jsonl has no session directory".into())
4593    })?;
4594    for name in ["summary.json", "updates.jsonl"] {
4595        let path = parent.join(name);
4596        let metadata = match std::fs::symlink_metadata(&path) {
4597            Ok(metadata) => metadata,
4598            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
4599            Err(error) => return Err(ServiceError::Operation(error.to_string())),
4600        };
4601        if metadata.file_type().is_symlink() || !metadata.is_file() {
4602            return Err(ServiceError::Operation(format!(
4603                "refusing non-regular Grok bundle member {}",
4604                path.display()
4605            )));
4606        }
4607        let content = std::fs::read_to_string(&path).map_err(|error| {
4608            ServiceError::Operation(format!(
4609                "Grok bundle member {} is not representable as UTF-8: {error}",
4610                path.display()
4611            ))
4612        })?;
4613        files.push(SessionArtifactFile {
4614            path: format!("{prefix}{name}"),
4615            content,
4616            role: match role {
4617                ArtifactFileRole::Bundle => ArtifactFileRole::Bundle,
4618                _ => ArtifactFileRole::SourceRecovery,
4619            },
4620        });
4621    }
4622    Ok(())
4623}
4624
4625fn handoff_artifact(
4626    locator: &SessionLocator,
4627    session: &Session,
4628    target: TransferFormat,
4629) -> std::result::Result<SessionArtifact, ServiceError> {
4630    let target_session_id = target_session_id(target);
4631    session_artifact_with_id(locator, session, target, Some(&target_session_id))
4632}
4633
4634fn target_session_id(target: TransferFormat) -> String {
4635    let uuid = generated_session_id();
4636    match target {
4637        TransferFormat::OpenCode => format!("ses_{}", uuid.replace('-', "")),
4638        TransferFormat::ClaudeCode
4639        | TransferFormat::Codex
4640        | TransferFormat::Pi
4641        | TransferFormat::Grok
4642        | TransferFormat::Gemini
4643        | TransferFormat::Goose
4644        | TransferFormat::Hermes => uuid,
4645    }
4646}
4647
4648fn sanitize_filename(value: &str) -> String {
4649    let value = value
4650        .chars()
4651        .map(|character| {
4652            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
4653                character
4654            } else {
4655                '-'
4656            }
4657        })
4658        .collect::<String>();
4659    let value = value.trim_matches('-');
4660    if value.is_empty() {
4661        "session".into()
4662    } else {
4663        value.chars().take(100).collect()
4664    }
4665}
4666
4667fn handoff_instructions(
4668    target: TransferFormat,
4669    session_id: &str,
4670    cwd: &Path,
4671) -> HandoffInstructions {
4672    let launch = |program: &str, arguments: Vec<String>| StructuredLaunch {
4673        cwd: cwd.to_path_buf(),
4674        program: program.into(),
4675        arguments,
4676        env: BTreeMap::new(),
4677    };
4678    match target {
4679        TransferFormat::ClaudeCode => HandoffInstructions {
4680            launch: launch("claude", vec!["--resume".into(), session_id.into()]),
4681            materialize: None,
4682            requires_materialization: true,
4683            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(),
4684        },
4685        TransferFormat::Hermes => HandoffInstructions {
4686            launch: launch("hermes", vec!["--resume".into(), session_id.into()]),
4687            materialize: None,
4688            requires_materialization: true,
4689            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(),
4690        },
4691        TransferFormat::Codex => HandoffInstructions {
4692            launch: launch("codex", vec!["resume".into(), session_id.into()]),
4693            materialize: None,
4694            requires_materialization: true,
4695            note: "Write the artifact into Codex's native rollout store before running the resume launch; Codex has no general transcript-import command.".into(),
4696        },
4697        TransferFormat::OpenCode => HandoffInstructions {
4698            launch: launch("opencode", vec!["--session".into(), session_id.into()]),
4699            materialize: Some(launch(
4700                "opencode",
4701                vec!["import".into(), "{artifact_path}".into()],
4702            )),
4703            requires_materialization: true,
4704            note: "Write the artifact to a file, run the materialize command with its path, then launch the imported session.".into(),
4705        },
4706        TransferFormat::Pi => HandoffInstructions {
4707            launch: launch("pi", vec!["--session".into(), "{artifact_path}".into()]),
4708            materialize: None,
4709            requires_materialization: true,
4710            note: "Write the artifact to a file and replace {artifact_path} in the launch arguments; Pi can resume that file directly.".into(),
4711        },
4712        TransferFormat::Grok => HandoffInstructions {
4713            launch: launch(
4714                "grok",
4715                vec!["--resume".into(), "{materialized_session_id}".into()],
4716            ),
4717            materialize: None,
4718            requires_materialization: true,
4719            note: "Grok has no import command. Materialize the artifact through `harness.v1.sessions.materialize` (target `grok`, `value_lossless`, the destination cwd): it writes Grok's store entry (`chat_history.jsonl` and the `summary.json` `--resume` requires) under a fresh id; replace {materialized_session_id} with the id it returns.".into(),
4720        },
4721        TransferFormat::Gemini => HandoffInstructions {
4722            launch: launch(
4723                "gemini",
4724                vec!["--session-file".into(), "{artifact_path}".into()],
4725            ),
4726            materialize: None,
4727            requires_materialization: true,
4728            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(),
4729        },
4730        TransferFormat::Goose => HandoffInstructions {
4731            launch: launch(
4732                "goose",
4733                vec![
4734                    "session".into(),
4735                    "--resume".into(),
4736                    "--session-id".into(),
4737                    "{imported_session_id}".into(),
4738                ],
4739            ),
4740            materialize: Some(launch(
4741                "goose",
4742                vec!["session".into(), "import".into(), "{artifact_path}".into()],
4743            )),
4744            requires_materialization: true,
4745            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(),
4746        },
4747    }
4748}
4749
4750fn resume_launch(
4751    harness: &str,
4752    session_id: &str,
4753    cwd: &Path,
4754    policy: ResumePolicy,
4755) -> std::result::Result<StructuredLaunch, ServiceError> {
4756    let mut arguments = Vec::new();
4757    let program = match harness {
4758        HarnessId::GROK => {
4759            if matches!(policy, ResumePolicy::Yolo) {
4760                if crate::support::self_sandbox_supported() {
4761                    arguments.extend(["--sandbox".into(), "workspace".into()]);
4762                }
4763                arguments.push("--always-approve".into());
4764            }
4765            arguments.extend(["--resume".into(), session_id.into()]);
4766            "grok"
4767        }
4768        HarnessId::CODEX => {
4769            let cwd_key = serde_json::to_string(cwd.to_string_lossy().as_ref())
4770                .expect("a filesystem path always serializes as JSON text");
4771            arguments.extend([
4772                "-c".into(),
4773                "check_for_update_on_startup=false".into(),
4774                "-c".into(),
4775                format!("projects.{cwd_key}.trust_level=\"trusted\""),
4776            ]);
4777            if matches!(policy, ResumePolicy::Yolo) {
4778                arguments.extend([
4779                    "--dangerously-bypass-approvals-and-sandbox".into(),
4780                    "--dangerously-bypass-hook-trust".into(),
4781                ]);
4782            }
4783            arguments.extend(["resume".into(), session_id.into()]);
4784            "codex"
4785        }
4786        HarnessId::CLAUDE_CODE => {
4787            if matches!(policy, ResumePolicy::Yolo) {
4788                arguments.push("--dangerously-skip-permissions".into());
4789            }
4790            arguments.extend(["--resume".into(), session_id.into()]);
4791            "claude"
4792        }
4793        HarnessId::GEMINI => {
4794            if matches!(policy, ResumePolicy::Yolo) {
4795                arguments.push("--yolo".into());
4796            }
4797            arguments.extend(["--resume".into(), session_id.into()]);
4798            "gemini"
4799        }
4800        HarnessId::GOOSE => {
4801            arguments.extend([
4802                "session".into(),
4803                "--resume".into(),
4804                "--session-id".into(),
4805                session_id.into(),
4806            ]);
4807            "goose"
4808        }
4809        HarnessId::PI => {
4810            if matches!(policy, ResumePolicy::Yolo) {
4811                arguments.push("--approve".into());
4812            }
4813            arguments.extend(["--session".into(), session_id.into()]);
4814            "pi"
4815        }
4816        HarnessId::OPENCODE => {
4817            arguments.extend(["--session".into(), session_id.into()]);
4818            "opencode"
4819        }
4820        HarnessId::SUPERCODE => {
4821            if matches!(policy, ResumePolicy::Yolo) {
4822                arguments.push("--dangerous".into());
4823            }
4824            arguments.extend(["resume".into(), session_id.into()]);
4825            "supercode"
4826        }
4827        other => {
4828            return Err(ServiceError::InvalidParams(format!(
4829                "no structured resume launch is registered for harness `{other}`"
4830            )))
4831        }
4832    };
4833    Ok(StructuredLaunch {
4834        cwd: cwd.to_path_buf(),
4835        env: if program == "grok" {
4836            crate::support::grok_home_env()
4837        } else {
4838            BTreeMap::new()
4839        },
4840        program: program.into(),
4841        arguments,
4842    })
4843}
4844
4845/// Stage the resolved gateway credential in a private (0600) file so the
4846/// bridge can read it via `--token-file` — the delivery the real `openclaw
4847/// acp` accepts. One stable file per endpoint (keyed by an address digest,
4848/// no secret material in the name), overwritten on every connect so files
4849/// never accumulate and a rotated token never goes stale on disk.
4850fn openclaw_gateway_token_file(address: &str, secret: &str) -> std::io::Result<PathBuf> {
4851    let digest = blake3::hash(address.as_bytes()).to_hex();
4852    let path = std::env::temp_dir().join(format!(
4853        "supercode-openclaw-gateway-token-{}",
4854        &digest.as_str()[..16]
4855    ));
4856    #[cfg(unix)]
4857    {
4858        use std::io::Write;
4859        use std::os::unix::fs::OpenOptionsExt;
4860        let mut file = std::fs::OpenOptions::new()
4861            .write(true)
4862            .create(true)
4863            .truncate(true)
4864            .mode(0o600)
4865            .open(&path)?;
4866        file.write_all(secret.as_bytes())?;
4867    }
4868    #[cfg(not(unix))]
4869    std::fs::write(&path, secret)?;
4870    Ok(path)
4871}
4872
4873/// Open a connect-mode descriptor: resolve the endpoint address and
4874/// credential from the harness's own config file and build the backend that
4875/// joins the already-running endpoint. Fails closed with a specific
4876/// diagnostic when the config cannot be resolved or the declared protocol has
4877/// no connect-capable client yet.
4878fn open_connect_descriptor(
4879    descriptor: &crate::HarnessSupportDescriptor,
4880    home: &Path,
4881) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
4882    let Some(connect) = &descriptor.runtime.connect_launch else {
4883        return Err(ServiceError::InvalidParams(format!(
4884            "harness `{}` has no registered connect-mode launch",
4885            descriptor.id.as_str()
4886        )));
4887    };
4888    let resolved = connect
4889        .resolve(home)
4890        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
4891    match (descriptor.id.as_str(), connect.protocol.as_str()) {
4892        (HarnessId::OPENCODE, protocol) if protocol.starts_with("opencode-http") => {
4893            let mut backend = OpenCodeRuntimeBackend::connect(&resolved.address);
4894            if let Some(token) = resolved.auth {
4895                backend = backend.with_bearer(token);
4896            }
4897            Ok(Box::new(backend))
4898        }
4899        (HarnessId::OPENCLAW, protocol) if protocol.starts_with("acp") => {
4900            // OpenClaw's own `openclaw acp` binary is the gateway client: a
4901            // stdio ACP bridge that joins the RUNNING gateway at the resolved
4902            // endpoint. Blind-walk finding 2026-08-31: the real bridge does
4903            // NOT honor OPENCLAW_GATEWAY_TOKEN from the environment — the
4904            // credential must arrive via `--token-file` (never bare `--token`
4905            // on argv, where process listings could read it). The env var is
4906            // still set for older bridges that did read it. Requires openclaw
4907            // >= 2026.7: the 2026.2 bridge drops its gateway socket
4908            // mid-prompt and advertises no session resume (executed finding,
4909            // docs/interop/research/openclaw-acp-dialect-2026-08-30.json).
4910            let mut env = BTreeMap::new();
4911            let mut arguments = vec!["acp".into(), "--url".into(), resolved.address.clone()];
4912            if let Some(token) = resolved.auth {
4913                let token_path = openclaw_gateway_token_file(&resolved.address, token.secret())
4914                    .map_err(|error| {
4915                        ServiceError::UnsupportedAction(format!(
4916                            "could not stage the gateway credential for the bridge: {error}"
4917                        ))
4918                    })?;
4919                arguments.push("--token-file".into());
4920                arguments.push(token_path.to_string_lossy().into_owned());
4921                env.insert("OPENCLAW_GATEWAY_TOKEN".to_string(), token.secret().to_string());
4922            }
4923            // The bridge program comes from the descriptor's own default
4924            // launch (the compiled registry pins `openclaw`), so tests can
4925            // substitute an absolute mock-bridge path without touching
4926            // process-global state.
4927            let program = descriptor
4928                .runtime
4929                .default_launch
4930                .as_ref()
4931                .map(|launch| launch.program.clone())
4932                .unwrap_or_else(|| "openclaw".into());
4933            let launch = RuntimeLaunch {
4934                program,
4935                arguments,
4936                env,
4937            };
4938            Ok(Box::new(
4939                crate::AcpRuntimeBackend::new(descriptor.id.clone(), launch)
4940                    .with_resume_support(descriptor.runtime.capabilities.resume_session),
4941            ))
4942        }
4943        _ => Err(ServiceError::UnsupportedAction(format!(
4944            "connect-mode endpoint for `{}` speaks `{}`; joining it needs that protocol's gateway client",
4945            descriptor.id.as_str(),
4946            connect.protocol
4947        ))),
4948    }
4949}
4950
4951/// The registry's connect-mode launch for this harness, honored only when the
4952/// caller supplied neither an explicit launch nor a base URL.
4953fn registry_connect_descriptor(
4954    params: &RuntimeBackendParams,
4955) -> Option<crate::HarnessSupportDescriptor> {
4956    if params.launch.is_some() || params.base_url.is_some() {
4957        return None;
4958    }
4959    harness_support_registry()
4960        .harnesses
4961        .into_iter()
4962        .find(|descriptor| descriptor.id == params.harness)
4963        .filter(|descriptor| descriptor.runtime.connect_launch.is_some())
4964}
4965
4966fn service_home() -> std::result::Result<PathBuf, ServiceError> {
4967    std::env::var_os("HOME").map(PathBuf::from).ok_or_else(|| {
4968        ServiceError::UnsupportedAction(
4969            "connect-mode launches need HOME to locate the harness config".into(),
4970        )
4971    })
4972}
4973
4974/// The doors that open a runtime: each spawns or joins a program and waits on
4975/// that program's protocol handshake before it can answer.
4976pub const RUNTIME_OPEN_METHODS: &[&str] = &[
4977    "harness.v1.runtimes.start",
4978    "harness.v1.runtimes.resume",
4979    "harness.v1.runtimes.attach",
4980    "harness.v1.runtimes.attach_existing",
4981];
4982
4983/// How long a runtime gets to finish opening before its caller is answered an
4984/// error instead. A program that never speaks the protocol at all — the wrong
4985/// binary, a shim that prints usage and waits — never answers the handshake,
4986/// so the wait is unbounded without this.
4987pub const RUNTIME_OPEN_DEADLINE: Duration = Duration::from_secs(60);
4988
4989/// How long a control call on an ALREADY-open runtime — send input, interrupt,
4990/// steer, respond, close — gets before its caller is answered an error
4991/// instead. A live runtime answers these in milliseconds; a wedged one never
4992/// answers at all, and `close` is exactly what a caller reaches for when it
4993/// suspects that.
4994pub const RUNTIME_CONTROL_DEADLINE: Duration = Duration::from_secs(30);
4995
4996/// The doors whose work happens entirely OUTSIDE this service's state once
4997/// its state has been read: probing harnesses, couriering a message into a
4998/// live session, and performing a conversation verb through a harness's own
4999/// CLI / HTTP / store door. Every one of them waits on a child process or a
5000/// network peer. See [`HarnessSessionService::detach`].
5001pub const DETACHED_METHODS: &[&str] = &[
5002    "harness.v1.harnesses.list",
5003    "harness.v1.harnesses.probe",
5004    "harness.v1.sessions.message",
5005    "harness.v1.sessions.new",
5006    "harness.v1.sessions.reset",
5007    "harness.v1.sessions.archive",
5008    "harness.v1.sessions.delete",
5009];
5010
5011/// How long a request moved off a transport's loop gets before its caller is
5012/// answered an error instead. Each of these already bounds its own inner
5013/// waits (a probe's handshake, the courier's run); this is the backstop for
5014/// the ones that do not — a harness CLI that never exits — so no caller waits
5015/// forever on a detached task no one is watching.
5016pub const DETACHED_CALL_DEADLINE: Duration = Duration::from_secs(120);
5017
5018/// How long `sessions.discover` gets before its caller is answered an error
5019/// instead. Discovery reads each harness's own store, and a store on a cold
5020/// or unavailable mount answers at the filesystem's pace rather than its own.
5021///
5022/// Deliberately shorter than the clients' own request deadline (30s): the
5023/// server's answer names the store that did not answer, and it is only read
5024/// if it lands before the client stops listening.
5025pub const SESSION_DISCOVER_DEADLINE: Duration = Duration::from_secs(25);
5026
5027/// Bound one control call on an open runtime by [`RUNTIME_CONTROL_DEADLINE`],
5028/// naming the method and the bound when it blows.
5029async fn within_control_deadline<F: std::future::Future>(
5030    method: &str,
5031    call: F,
5032) -> std::result::Result<F::Output, ServiceError> {
5033    tokio::time::timeout(RUNTIME_CONTROL_DEADLINE, call)
5034        .await
5035        .map_err(|_| {
5036            ServiceError::Operation(format!(
5037                "`{method}` gave up after {}s: the runtime did not answer",
5038                RUNTIME_CONTROL_DEADLINE.as_secs()
5039            ))
5040        })
5041}
5042
5043/// One [`RUNTIME_OPEN_METHODS`] request, parsed but not yet started. See
5044/// [`HarnessSessionService::runtime_open`] for why it exists apart from
5045/// [`HarnessSessionService::handle_async`].
5046pub struct RuntimeOpen {
5047    id: Value,
5048    method: String,
5049    params: Value,
5050}
5051
5052impl RuntimeOpen {
5053    /// Do the waiting: spawn or join the program and complete its handshake,
5054    /// bounded by [`RUNTIME_OPEN_DEADLINE`]. Touches no service state, so this
5055    /// runs on any task.
5056    pub async fn open(self) -> OpenedRuntime {
5057        let Self { id, method, params } = self;
5058        let outcome = open_runtime(&method, params).await;
5059        OpenedRuntime { id, outcome }
5060    }
5061}
5062
5063/// The result of [`RuntimeOpen::open`], ready for
5064/// [`HarnessSessionService::finish_runtime_open`].
5065pub struct OpenedRuntime {
5066    id: Value,
5067    outcome: std::result::Result<OpenRuntime, ServiceError>,
5068}
5069
5070/// One detached request: the half that reads this service's state already
5071/// done, and the half that waits not yet started. See
5072/// [`HarnessSessionService::detach`] and
5073/// [`HarnessSessionService::detach_runtime`].
5074pub struct DetachedCall {
5075    id: Value,
5076    method: String,
5077    work: std::result::Result<Work, ServiceError>,
5078}
5079
5080impl DetachedCall {
5081    /// Do the waiting and answer. Runs on any task: whatever this call needed
5082    /// from the service was taken before it left.
5083    pub async fn run(self) -> DetachedAnswer {
5084        let Self { id, method, work } = self;
5085        match work {
5086            // A call holding a runtime is already bounded by
5087            // RUNTIME_CONTROL_DEADLINE, and its future OWNS that connection:
5088            // a second timeout around it would drop the connection mid-call
5089            // and take down a runtime its caller still has.
5090            Ok(Work::Runtime(work)) => {
5091                let (result, returned) = work.run().await;
5092                DetachedAnswer {
5093                    response: service_response(id, result),
5094                    returned,
5095                }
5096            }
5097            Ok(Work::Free(work)) => {
5098                let result = match tokio::time::timeout(DETACHED_CALL_DEADLINE, work.run()).await {
5099                    Ok(result) => result,
5100                    Err(_) => Err(ServiceError::Operation(format!(
5101                        "`{method}` gave up after {}s: the harness it waits on did not answer",
5102                        DETACHED_CALL_DEADLINE.as_secs()
5103                    ))),
5104                };
5105                DetachedAnswer {
5106                    response: service_response(id, result),
5107                    returned: None,
5108                }
5109            }
5110            Err(error) => DetachedAnswer {
5111                response: service_response(id, Err(error)),
5112                returned: None,
5113            },
5114        }
5115    }
5116}
5117
5118/// One detached call's complete answer, plus whatever it must hand back to
5119/// the service before that answer is written. See
5120/// [`HarnessSessionService::finish_detached`].
5121pub struct DetachedAnswer {
5122    response: Value,
5123    returned: Option<ReturnedRuntime>,
5124}
5125
5126impl DetachedAnswer {
5127    /// The caller's JSON-RPC response, for a transport that owns no service
5128    /// to give a borrowed connection back to.
5129    pub fn into_response(self) -> Value {
5130        self.response
5131    }
5132}
5133
5134/// A connection lent to a detached call, on its way back to the service that
5135/// owns it.
5136pub struct ReturnedRuntime {
5137    connection: String,
5138    runtime: Box<dyn RuntimeConnection>,
5139}
5140
5141/// The waiting half of one detached request: with nothing of the service's
5142/// in hand, or holding a connection the service lent out for the call.
5143enum Work {
5144    Free(DetachedWork),
5145    Runtime(RuntimeWork),
5146}
5147
5148/// The waiting half of one detached request that holds nothing of the
5149/// service's.
5150enum DetachedWork {
5151    /// Probe the selected harnesses: find their executables, ask each its
5152    /// version, and at `probe: handshake` start each one and complete its
5153    /// protocol handshake.
5154    Inventory(InventoryWork),
5155    /// Run the courier that delivers one message into a live session.
5156    Message(MessageSessionParams),
5157    /// Perform one conversation verb through the harness's own CLI, HTTP API,
5158    /// daemon socket, or supercode's own store.
5159    SessionMutation {
5160        verb: crate::SessionVerb,
5161        mutation: crate::SessionMutation,
5162    },
5163}
5164
5165impl DetachedWork {
5166    async fn run(self) -> std::result::Result<Value, ServiceError> {
5167        match self {
5168            Self::Inventory(work) => run_inventory(work).await,
5169            Self::Message(params) => {
5170                Ok(message_live_session(&params, &crate::claude_peer::ProcessCourierRunner).await)
5171            }
5172            Self::SessionMutation { verb, mutation } => {
5173                let outcome = run_session_mutation(verb, &mutation).await?;
5174                serde_json::to_value(outcome)
5175                    .map_err(|error| ServiceError::Operation(error.to_string()))
5176            }
5177        }
5178    }
5179}
5180
5181/// One detached call that holds a runtime connection for its whole run.
5182enum RuntimeWork {
5183    /// Tear down a runtime the service has already surrendered.
5184    Close {
5185        runtime: Box<dyn RuntimeConnection>,
5186        process_group: Option<u32>,
5187    },
5188    /// Type one live slash command through a borrowed connection, then give
5189    /// the connection back.
5190    LiveCommand {
5191        connection: String,
5192        runtime: Box<dyn RuntimeConnection>,
5193        verb: crate::SessionVerb,
5194        mutation: crate::SessionMutation,
5195        command: &'static str,
5196        session: String,
5197    },
5198}
5199
5200/// What one [`RuntimeWork`] answers with: the caller's result, and the
5201/// connection to give back when the call only borrowed one.
5202type RuntimeWorkAnswer = (
5203    std::result::Result<Value, ServiceError>,
5204    Option<ReturnedRuntime>,
5205);
5206
5207impl RuntimeWork {
5208    async fn run(self) -> RuntimeWorkAnswer {
5209        match self {
5210            Self::Close {
5211                runtime,
5212                process_group,
5213            } => (close_runtime(runtime, process_group).await, None),
5214            Self::LiveCommand {
5215                connection,
5216                mut runtime,
5217                verb,
5218                mutation,
5219                command,
5220                session,
5221            } => {
5222                let result =
5223                    type_live_command(runtime.as_mut(), verb, &mutation, command, session).await;
5224                (
5225                    result,
5226                    Some(ReturnedRuntime {
5227                        connection,
5228                        runtime,
5229                    }),
5230                )
5231            }
5232        }
5233    }
5234}
5235
5236/// Tear down a runtime already out of the service, within
5237/// [`RUNTIME_CONTROL_DEADLINE`].
5238async fn close_runtime(
5239    mut runtime: Box<dyn RuntimeConnection>,
5240    process_group: Option<u32>,
5241) -> std::result::Result<Value, ServiceError> {
5242    match within_control_deadline("harness.v1.runtimes.close", runtime.close()).await {
5243        Ok(result) => {
5244            result.map_err(operation)?;
5245            Ok(json!({"closed": true}))
5246        }
5247        Err(deadline) => {
5248            // Dropping the handle is not enough: the process that stopped
5249            // answering is held by a task parked on it, so nothing here runs
5250            // its Drop. Signal the group the graceful path would have
5251            // signalled, then say so.
5252            let killed = kill_runtime_process_group(process_group);
5253            drop(runtime);
5254            Ok(json!({
5255                "closed": true,
5256                "killed": killed,
5257                "detail": error_message(deadline),
5258            }))
5259        }
5260    }
5261}
5262
5263/// The conversation a live `sessions.new` / `sessions.reset` acts on: the one
5264/// the request named, or the runtime's own session.
5265fn live_session_name(runtime: &dyn RuntimeConnection, mutation: &crate::SessionMutation) -> String {
5266    mutation
5267        .session
5268        .clone()
5269        .filter(|value| !value.trim().is_empty())
5270        .unwrap_or_else(|| runtime.handle().runtime_id.clone())
5271}
5272
5273/// Type one harness slash command into a live session through the very same
5274/// `send_input` path a human's message takes, within
5275/// [`RUNTIME_CONTROL_DEADLINE`].
5276async fn type_live_command(
5277    runtime: &mut dyn RuntimeConnection,
5278    verb: crate::SessionVerb,
5279    mutation: &crate::SessionMutation,
5280    command: &str,
5281    session: String,
5282) -> std::result::Result<Value, ServiceError> {
5283    within_control_deadline(
5284        &format!("sessions.{}", verb.as_str()),
5285        runtime.send_input(RuntimeInput {
5286            text: command.to_string(),
5287            image_urls: Vec::new(),
5288        }),
5289    )
5290    .await?
5291    .map_err(operation)?;
5292    let outcome = crate::sessions_control::live_outcome(verb, mutation, command, session)
5293        .map_err(session_control_error)?;
5294    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
5295}
5296
5297/// A runtime that is up and whose handshake completed, with what the service
5298/// needs to take ownership of it.
5299enum OpenRuntime {
5300    /// supercode spawned this process, so it also hosts it: a frontend server,
5301    /// a live-runtime registration and a terminal launch of its own.
5302    Hosted {
5303        runtime: Box<dyn RuntimeConnection>,
5304        capabilities: crate::RuntimeCapabilities,
5305        workspace: PathBuf,
5306    },
5307    /// `attach_existing` joined a process supercode does not own. It is
5308    /// registered as a bare connection and hosts nothing.
5309    Joined { runtime: Box<dyn RuntimeConnection> },
5310}
5311
5312/// Open the runtime one [`RUNTIME_OPEN_METHODS`] request asks for, within
5313/// [`RUNTIME_OPEN_DEADLINE`]. The error a blown deadline answers names the
5314/// method and the bound, so a caller reads why it was cut loose instead of
5315/// waiting on a handshake that is never coming.
5316async fn open_runtime(
5317    method: &str,
5318    params: Value,
5319) -> std::result::Result<OpenRuntime, ServiceError> {
5320    match tokio::time::timeout(
5321        RUNTIME_OPEN_DEADLINE,
5322        open_runtime_unbounded(method, params),
5323    )
5324    .await
5325    {
5326        Ok(result) => result,
5327        Err(_) => Err(ServiceError::Operation(format!(
5328            "`{method}` gave up after {}s: the runtime never finished its protocol handshake",
5329            RUNTIME_OPEN_DEADLINE.as_secs()
5330        ))),
5331    }
5332}
5333
5334async fn open_runtime_unbounded(
5335    method: &str,
5336    params: Value,
5337) -> std::result::Result<OpenRuntime, ServiceError> {
5338    match method {
5339        "harness.v1.runtimes.start" => {
5340            let params = decode::<RuntimeStartParams>(params)?;
5341            let backend = runtime_backend(&params.backend)?;
5342            let capabilities = backend.capabilities();
5343            let workspace = params.cwd.clone();
5344            let runtime = backend
5345                .start(RuntimeStartRequest {
5346                    cwd: params.cwd,
5347                    launch: runtime_launch(&params.backend),
5348                    mcp_servers: params.mcp_servers,
5349                    approval_policy: params.approval_policy,
5350                })
5351                .await
5352                .map_err(operation)?;
5353            Ok(OpenRuntime::Hosted {
5354                runtime,
5355                capabilities,
5356                workspace,
5357            })
5358        }
5359        "harness.v1.runtimes.resume" | "harness.v1.runtimes.attach" => {
5360            let params = decode::<RuntimeAttachParams>(params)?;
5361            let backend = runtime_backend(&params.backend)?;
5362            let capabilities = backend.capabilities();
5363            let workspace = params
5364                .cwd
5365                .clone()
5366                .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
5367            let runtime = backend
5368                .attach(RuntimeAttachRequest {
5369                    runtime_id: params.runtime_id,
5370                    cwd: params.cwd,
5371                    launch: runtime_launch(&params.backend),
5372                    mcp_servers: params.mcp_servers,
5373                    approval_policy: params.approval_policy,
5374                })
5375                .await
5376                .map_err(operation)?;
5377            Ok(OpenRuntime::Hosted {
5378                runtime,
5379                capabilities,
5380                workspace,
5381            })
5382        }
5383        "harness.v1.runtimes.attach_existing" => {
5384            let params = decode::<RuntimeAttachParams>(params)?;
5385            let backend: Box<dyn RuntimeBackend> = match params
5386                .backend
5387                .base_url
5388                .as_deref()
5389                .and_then(|value| LiveRuntimeEndpoint::parse(value).ok())
5390            {
5391                Some(endpoint) => {
5392                    #[cfg(not(feature = "adapter-api"))]
5393                    {
5394                        let _ = endpoint;
5395                        return Err(ServiceError::UnsupportedAction(
5396                            "live HTTP attachment adapter is not compiled".into(),
5397                        ));
5398                    }
5399                    #[cfg(feature = "adapter-api")]
5400                    {
5401                        let workspace = params.cwd.clone().ok_or_else(|| {
5402                            ServiceError::InvalidParams(
5403                                "Supercode live attach requires the project cwd".into(),
5404                            )
5405                        })?;
5406                        let source = LiveRuntimeSource {
5407                            harness: params.backend.harness.as_str().to_string(),
5408                            session_id: params.runtime_id.clone(),
5409                            workspace,
5410                        };
5411                        let receipt = resolve_live_runtime(&endpoint, &source)
5412                            .map_err(|error| ServiceError::Operation(error.to_string()))?;
5413                        Box::new(SupercodeHttpRuntimeBackend::new(receipt))
5414                    }
5415                }
5416                None => runtime_backend(&params.backend)?,
5417            };
5418            let capabilities = backend.capabilities();
5419            if !capabilities.attach_existing_process {
5420                return Err(ServiceError::Operation(format!(
5421                    "{} cannot attach to an already-running process; use runtimes.resume for a persisted session",
5422                    backend.harness().as_str()
5423                )));
5424            }
5425            let runtime = backend
5426                .attach_existing(RuntimeAttachRequest {
5427                    runtime_id: params.runtime_id,
5428                    cwd: params.cwd,
5429                    launch: runtime_launch(&params.backend),
5430                    mcp_servers: params.mcp_servers,
5431                    approval_policy: params.approval_policy,
5432                })
5433                .await
5434                .map_err(operation)?;
5435            Ok(OpenRuntime::Joined { runtime })
5436        }
5437        _ => Err(ServiceError::MethodNotFound),
5438    }
5439}
5440
5441/// Wrap one service outcome in its JSON-RPC 2.0 envelope.
5442fn service_response(id: Value, result: std::result::Result<Value, ServiceError>) -> Value {
5443    match result {
5444        Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
5445        Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
5446        Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
5447        Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
5448        Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
5449        Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
5450    }
5451}
5452
5453fn runtime_backend(
5454    params: &RuntimeBackendParams,
5455) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
5456    if let Some(descriptor) = registry_connect_descriptor(params) {
5457        return open_connect_descriptor(&descriptor, &service_home()?);
5458    }
5459    if params.protocol.as_deref() == Some("acp") {
5460        let launch = params
5461            .launch
5462            .clone()
5463            .or_else(|| {
5464                harness_support_registry()
5465                    .harnesses
5466                    .into_iter()
5467                    .find(|harness| harness.id == params.harness)
5468                    .filter(|harness| {
5469                        harness.runtime.implementation == ImplementationKind::GenericProtocol
5470                            && harness.runtime.protocol.starts_with("acp")
5471                    })
5472                    .and_then(|harness| harness.runtime.default_launch)
5473            })
5474            .ok_or_else(|| {
5475                ServiceError::InvalidParams(
5476                    "an ACP runtime requires `launch` unless the harness has a registered default"
5477                        .into(),
5478                )
5479            })?;
5480        let resume_session = harness_support_registry()
5481            .harnesses
5482            .into_iter()
5483            .find(|harness| harness.id == params.harness)
5484            .is_some_and(|harness| harness.runtime.capabilities.resume_session);
5485        return Ok(Box::new(
5486            AcpRuntimeBackend::new(params.harness.clone(), launch)
5487                .with_resume_support(resume_session),
5488        ));
5489    }
5490    let backend: Box<dyn RuntimeBackend> = match params.harness.as_str() {
5491        HarnessId::CODEX => Box::new(CodexRuntimeBackend::new()),
5492        HarnessId::CLAUDE_CODE => Box::new(ClaudeCodeRuntimeBackend::new()),
5493        HarnessId::PI => Box::new(PiRuntimeBackend::new()),
5494        HarnessId::OPENCODE => match &params.base_url {
5495            Some(url) => Box::new(OpenCodeRuntimeBackend::connect(url)),
5496            None => Box::new(OpenCodeRuntimeBackend::new()),
5497        },
5498        harness => {
5499            let descriptor = harness_support_registry()
5500                .harnesses
5501                .into_iter()
5502                .find(|descriptor| descriptor.id.as_str() == harness)
5503                .filter(|descriptor| {
5504                    descriptor.runtime.implementation == ImplementationKind::GenericProtocol
5505                        && descriptor.runtime.protocol.starts_with("acp")
5506                });
5507            let Some(descriptor) = descriptor else {
5508                return Err(ServiceError::InvalidParams(format!(
5509                    "no runtime adapter for harness `{harness}`; use protocol `acp` with a launch command"
5510                )));
5511            };
5512            let resume = descriptor.runtime.capabilities.resume_session;
5513            Box::new(
5514                AcpRuntimeBackend::new(
5515                    descriptor.id,
5516                    descriptor
5517                        .runtime
5518                        .default_launch
5519                        .expect("generic ACP registry entry includes its launch"),
5520                )
5521                .with_resume_support(resume),
5522            )
5523        }
5524    };
5525    Ok(backend)
5526}
5527
5528fn runtime_launch(params: &RuntimeBackendParams) -> Option<RuntimeLaunch> {
5529    if let Some(launch) = &params.launch {
5530        return Some(launch.clone());
5531    }
5532    if !matches!(params.policy, RuntimePolicy::Yolo) {
5533        return None;
5534    }
5535    let launch = match params.harness.as_str() {
5536        HarnessId::GROK => RuntimeLaunch {
5537            program: "grok".into(),
5538            arguments: {
5539                let mut arguments: Vec<String> = Vec::new();
5540                if crate::support::self_sandbox_supported() {
5541                    arguments.extend(["--sandbox".into(), "workspace".into()]);
5542                }
5543                arguments.extend([
5544                    "--always-approve".into(),
5545                    "agent".into(),
5546                    "--no-leader".into(),
5547                    "stdio".into(),
5548                ]);
5549                arguments
5550            },
5551            env: crate::support::grok_env(),
5552        },
5553        HarnessId::CODEX => RuntimeLaunch {
5554            program: "codex".into(),
5555            arguments: vec![
5556                "--dangerously-bypass-approvals-and-sandbox".into(),
5557                "--dangerously-bypass-hook-trust".into(),
5558                "app-server".into(),
5559            ],
5560            env: BTreeMap::new(),
5561        },
5562        HarnessId::CLAUDE_CODE => RuntimeLaunch {
5563            program: "claude".into(),
5564            arguments: vec![
5565                "--dangerously-skip-permissions".into(),
5566                "--print".into(),
5567                "--input-format".into(),
5568                "stream-json".into(),
5569                "--output-format".into(),
5570                "stream-json".into(),
5571                "--verbose".into(),
5572            ],
5573            env: BTreeMap::new(),
5574        },
5575        HarnessId::PI => RuntimeLaunch {
5576            program: "pi".into(),
5577            arguments: vec!["--approve".into(), "--mode".into(), "rpc".into()],
5578            env: BTreeMap::new(),
5579        },
5580        HarnessId::OPENCODE => RuntimeLaunch {
5581            program: "opencode".into(),
5582            arguments: vec!["serve".into()],
5583            env: BTreeMap::new(),
5584        },
5585        HarnessId::GEMINI => RuntimeLaunch {
5586            program: "gemini".into(),
5587            arguments: vec!["--acp".into(), "--yolo".into()],
5588            env: BTreeMap::new(),
5589        },
5590        HarnessId::GOOSE => RuntimeLaunch {
5591            program: "goose".into(),
5592            arguments: vec!["acp".into()],
5593            env: BTreeMap::new(),
5594        },
5595        HarnessId::SUPERCODE => RuntimeLaunch {
5596            program: "supercode".into(),
5597            arguments: vec!["acp".into(), "--dangerous".into()],
5598            env: BTreeMap::new(),
5599        },
5600        _ => return None,
5601    };
5602    Some(launch)
5603}
5604
5605/// Disposable harness state for a no-prompt readiness probe. Merely opening
5606/// several stock CLIs writes a session header or migrates configuration, so a
5607/// handshake must never point at the user's real home. Authentication files
5608/// are copied into the private temporary home; all writes disappear with the
5609/// guard after the connection closes.
5610struct IsolatedProbeHome {
5611    launch: RuntimeLaunch,
5612    root: PathBuf,
5613}
5614
5615impl IsolatedProbeHome {
5616    fn new(harness: &str, mut launch: RuntimeLaunch) -> std::io::Result<Self> {
5617        let root = std::env::temp_dir().join(format!(
5618            "supercode-harness-probe-{harness}-{}",
5619            generated_session_id()
5620        ));
5621        std::fs::create_dir_all(&root)?;
5622        set_private_dir_permissions(&root)?;
5623
5624        if let Some(source_home) = std::env::var_os("HOME").map(PathBuf::from) {
5625            for relative in probe_auth_files(harness) {
5626                copy_probe_file(&source_home, &root, relative)?;
5627            }
5628        }
5629        // supercode reads its own config home ($SUPERCODE_HOME, else
5630        // $XDG_CONFIG_HOME/supercode, else ~/.config/supercode), not a fixed
5631        // place under HOME: a login kept under XDG_CONFIG_HOME probed as
5632        // "no API key found" while `supercode run` answered.
5633        if harness == HarnessId::SUPERCODE {
5634            let config_home = crate::agent::global_instructions_dir();
5635            for file in ["config.toml", "credentials.toml"] {
5636                copy_probe_path(
5637                    &config_home.join(file),
5638                    &root.join(".config/supercode").join(file),
5639                )?;
5640            }
5641        }
5642        configure_isolated_probe_auth(harness, &root)?;
5643
5644        let root_text = root.to_string_lossy().into_owned();
5645        for (key, value) in [
5646            ("HOME", root_text.clone()),
5647            (
5648                "XDG_CACHE_HOME",
5649                root.join(".cache").to_string_lossy().into_owned(),
5650            ),
5651            (
5652                "XDG_CONFIG_HOME",
5653                root.join(".config").to_string_lossy().into_owned(),
5654            ),
5655            (
5656                "XDG_DATA_HOME",
5657                root.join(".local/share").to_string_lossy().into_owned(),
5658            ),
5659        ] {
5660            launch.env.insert(key.into(), value);
5661        }
5662        let scoped = match harness {
5663            HarnessId::CLAUDE_CODE => Some(("CLAUDE_CONFIG_DIR", root.join(".claude"))),
5664            HarnessId::CODEX => Some(("CODEX_HOME", root.join(".codex"))),
5665            HarnessId::GEMINI => Some(("GEMINI_CLI_HOME", root.clone())),
5666            HarnessId::GROK => Some(("GROK_HOME", root.join(".grok"))),
5667            HarnessId::PI => Some(("PI_CODING_AGENT_DIR", root.join(".pi/agent"))),
5668            HarnessId::SUPERCODE => Some(("SUPERCODE_HOME", root.join(".config/supercode"))),
5669            _ => None,
5670        };
5671        if let Some((key, value)) = scoped {
5672            launch
5673                .env
5674                .insert(key.into(), value.to_string_lossy().into_owned());
5675        }
5676        Ok(Self { launch, root })
5677    }
5678
5679    fn cleanup(&self) -> std::io::Result<()> {
5680        match std::fs::remove_dir_all(&self.root) {
5681            Ok(()) => Ok(()),
5682            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
5683            Err(error) => Err(error),
5684        }
5685    }
5686}
5687
5688impl Drop for IsolatedProbeHome {
5689    fn drop(&mut self) {
5690        let _ = self.cleanup();
5691    }
5692}
5693
5694fn probe_auth_files(harness: &str) -> &'static [&'static str] {
5695    match harness {
5696        HarnessId::CLAUDE_CODE => &[".claude/.credentials.json", ".claude.json"],
5697        // The gateway endpoint + token live in openclaw's own config; without
5698        // it the isolated probe dials the default endpoint unauthenticated
5699        // (PARITY-24 finding 2026-08-31).
5700        HarnessId::OPENCLAW => &[".openclaw/openclaw.json"],
5701        HarnessId::CODEX => &[".codex/auth.json"],
5702        HarnessId::GEMINI => &[
5703            ".gemini/google_accounts.json",
5704            ".gemini/oauth_creds.json",
5705            ".gemini/settings.json",
5706        ],
5707        HarnessId::GROK => &[".grok/auth.json", ".grok/config.toml"],
5708        HarnessId::OPENCODE => &[
5709            ".config/opencode/auth.json",
5710            ".local/share/opencode/auth.json",
5711        ],
5712        HarnessId::PI => &[".pi/agent/auth.json"],
5713        // Hermes keeps its provider selection in config.yaml, its OAuth
5714        // credential pool in auth.json, and API keys in .env; without them
5715        // the isolated probe sees "No LLM provider configured" for a
5716        // hermes that answers fine from the user's real home.
5717        HarnessId::HERMES => &[".hermes/config.yaml", ".hermes/auth.json", ".hermes/.env"],
5718        _ => &[],
5719    }
5720}
5721
5722fn copy_probe_file(source_home: &Path, probe_home: &Path, relative: &str) -> std::io::Result<()> {
5723    copy_probe_path(&source_home.join(relative), &probe_home.join(relative))
5724}
5725
5726fn copy_probe_path(source: &Path, destination: &Path) -> std::io::Result<()> {
5727    if !source.is_file() {
5728        return Ok(());
5729    }
5730    if let Some(parent) = destination.parent() {
5731        std::fs::create_dir_all(parent)?;
5732        set_private_dir_permissions(parent)?;
5733    }
5734    std::fs::copy(source, destination)?;
5735    set_private_file_permissions(destination)
5736}
5737
5738fn configure_isolated_probe_auth(harness: &str, probe_home: &Path) -> std::io::Result<()> {
5739    if harness != HarnessId::GEMINI {
5740        return Ok(());
5741    }
5742    let oauth = probe_home.join(".gemini/oauth_creds.json");
5743    if !oauth.is_file() {
5744        return Ok(());
5745    }
5746    let settings_path = probe_home.join(".gemini/settings.json");
5747    let mut settings = std::fs::read_to_string(&settings_path)
5748        .ok()
5749        .and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
5750        .unwrap_or_else(|| json!({}));
5751    settings["security"]["auth"]["selectedType"] = Value::String("oauth-personal".into());
5752    std::fs::write(
5753        &settings_path,
5754        serde_json::to_vec_pretty(&settings).map_err(std::io::Error::other)?,
5755    )?;
5756    set_private_file_permissions(&settings_path)
5757}
5758
5759#[cfg(unix)]
5760fn set_private_dir_permissions(path: &Path) -> std::io::Result<()> {
5761    use std::os::unix::fs::PermissionsExt;
5762    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
5763}
5764
5765#[cfg(not(unix))]
5766fn set_private_dir_permissions(_path: &Path) -> std::io::Result<()> {
5767    Ok(())
5768}
5769
5770#[cfg(unix)]
5771fn set_private_file_permissions(path: &Path) -> std::io::Result<()> {
5772    use std::os::unix::fs::PermissionsExt;
5773    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
5774}
5775
5776#[cfg(not(unix))]
5777fn set_private_file_permissions(_path: &Path) -> std::io::Result<()> {
5778    Ok(())
5779}
5780
5781fn find_executable(program: &str) -> Option<PathBuf> {
5782    let candidate = PathBuf::from(program);
5783    if candidate.components().count() > 1 {
5784        return candidate.is_file().then_some(candidate);
5785    }
5786    let path = std::env::var_os("PATH")?;
5787    for directory in std::env::split_paths(&path) {
5788        let candidate = directory.join(program);
5789        if candidate.is_file() {
5790            return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
5791        }
5792        #[cfg(windows)]
5793        {
5794            for extension in ["exe", "cmd", "bat"] {
5795                let candidate = directory.join(format!("{program}.{extension}"));
5796                if candidate.is_file() {
5797                    return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
5798                }
5799            }
5800        }
5801    }
5802    None
5803}
5804
5805async fn executable_version(executable: &Path) -> Option<String> {
5806    let mut command = tokio::process::Command::new(executable);
5807    command
5808        .arg("--version")
5809        .stdin(std::process::Stdio::null())
5810        .stdout(std::process::Stdio::piped())
5811        .stderr(std::process::Stdio::piped())
5812        .kill_on_drop(true);
5813    let output = tokio::time::timeout(Duration::from_secs(3), command.output())
5814        .await
5815        .ok()?
5816        .ok()?;
5817    let stdout = String::from_utf8_lossy(&output.stdout);
5818    let stderr = String::from_utf8_lossy(&output.stderr);
5819    stdout
5820        .lines()
5821        .chain(stderr.lines())
5822        .map(str::trim)
5823        .find(|line| !line.is_empty())
5824        .map(|line| truncate_text(line, 200))
5825}
5826
5827pub(crate) fn auth_evidence(harness: &str) -> bool {
5828    let env_names: &[&str] = match harness {
5829        HarnessId::CLAUDE_CODE => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
5830        HarnessId::CODEX => &["OPENAI_API_KEY"],
5831        HarnessId::OPENCODE => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
5832        HarnessId::PI => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
5833        HarnessId::GROK => &["XAI_API_KEY", "GROK_API_KEY"],
5834        HarnessId::GEMINI => &["GEMINI_API_KEY", "GOOGLE_API_KEY"],
5835        HarnessId::SUPERCODE => &["OPENROUTER_API_KEY"],
5836        _ => &[],
5837    };
5838    if env_names
5839        .iter()
5840        .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
5841    {
5842        return true;
5843    }
5844    let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else {
5845        return false;
5846    };
5847    let files: Vec<PathBuf> = match harness {
5848        HarnessId::CLAUDE_CODE => vec![home.join(".claude/.credentials.json")],
5849        HarnessId::CODEX => vec![home.join(".codex/auth.json")],
5850        HarnessId::OPENCODE => vec![
5851            home.join(".local/share/opencode/auth.json"),
5852            home.join(".config/opencode/auth.json"),
5853        ],
5854        HarnessId::PI => vec![home.join(".pi/agent/auth.json")],
5855        HarnessId::GROK => vec![home.join(".grok/auth.json")],
5856        HarnessId::GEMINI => vec![
5857            home.join(".gemini/oauth_creds.json"),
5858            home.join(".gemini/google_accounts.json"),
5859        ],
5860        HarnessId::SUPERCODE => vec![home.join(".config/supercode/credentials.toml")],
5861        HarnessId::HERMES => vec![home.join(".hermes/auth.json"), home.join(".hermes/.env")],
5862        _ => Vec::new(),
5863    };
5864    if files.into_iter().any(|path| {
5865        std::fs::metadata(path)
5866            .map(|metadata| metadata.is_file() && metadata.len() > 2)
5867            .unwrap_or(false)
5868    }) {
5869        return true;
5870    }
5871    // macOS keeps Claude Code's OAuth login in the Keychain, so
5872    // `.claude/.credentials.json` never exists there and the file probe above
5873    // reports a signed-in install as unauthenticated forever. A completed
5874    // login also writes an `oauthAccount` record into `~/.claude.json` on
5875    // every platform — file-based, prompt-free evidence (querying the
5876    // Keychain itself from an unsigned daemon can raise a UI prompt).
5877    if harness == HarnessId::CLAUDE_CODE {
5878        return std::fs::read_to_string(home.join(".claude.json"))
5879            .map(|text| text.contains("\"oauthAccount\""))
5880            .unwrap_or(false);
5881    }
5882    false
5883}
5884
5885fn looks_like_auth_error(message: &str) -> bool {
5886    let message = message.to_ascii_lowercase();
5887    [
5888        "auth",
5889        "login",
5890        "sign in",
5891        "sign-in",
5892        "credential",
5893        "unauthorized",
5894        "forbidden",
5895        "token",
5896    ]
5897    .iter()
5898    .any(|needle| message.contains(needle))
5899}
5900
5901fn unavailable_capabilities() -> crate::RuntimeCapabilities {
5902    crate::RuntimeCapabilities {
5903        start_session: false,
5904        resume_session: false,
5905        attach_existing_process: false,
5906        send_input: false,
5907        stream_events: false,
5908        interrupt: false,
5909        steer: false,
5910        respond_to_requests: false,
5911    }
5912}
5913
5914fn truncate_text(text: &str, max_chars: usize) -> String {
5915    let mut chars = text.chars();
5916    let truncated = chars.by_ref().take(max_chars).collect::<String>();
5917    if chars.next().is_some() {
5918        format!("{truncated}…")
5919    } else {
5920        truncated
5921    }
5922}
5923
5924/// The process group a runtime's own handle names, when it names one.
5925///
5926/// Every adapter that spawns a local process spawns it as its own group
5927/// leader (`Command::process_group(0)`), so the endpoint's pid IS the group
5928/// id. A runtime reached over HTTP, or one supercode joined rather than
5929/// spawned, names no group here and is left alone.
5930fn runtime_process_group(handle: &crate::RuntimeHandle) -> Option<u32> {
5931    match &handle.endpoint {
5932        crate::RuntimeEndpoint::LocalProcess { pid, .. } => *pid,
5933        crate::RuntimeEndpoint::Http { .. } => None,
5934    }
5935}
5936
5937/// SIGKILL a wedged runtime's whole process group, reporting whether there
5938/// was one to signal. This is the same group teardown a graceful `close`
5939/// performs; it runs here only when the graceful path blew its deadline,
5940/// because the task parked on the unanswered call still owns the process
5941/// handle and so no `Drop` of ours can reach it.
5942fn kill_runtime_process_group(process_group: Option<u32>) -> bool {
5943    match process_group {
5944        #[cfg(unix)]
5945        Some(pid) => {
5946            crate::lsp::kill_process_group(pid);
5947            true
5948        }
5949        #[cfg(not(unix))]
5950        Some(_) => false,
5951        None => false,
5952    }
5953}
5954
5955fn error_message(error: ServiceError) -> String {
5956    match error {
5957        ServiceError::InvalidParams(message)
5958        | ServiceError::Operation(message)
5959        | ServiceError::UnsupportedAction(message) => message,
5960        ServiceError::MethodNotFound => "runtime adapter is not available".into(),
5961        ServiceError::Sdk(error) => error.to_string(),
5962    }
5963}
5964
5965#[derive(Debug)]
5966enum ServiceError {
5967    InvalidParams(String),
5968    MethodNotFound,
5969    UnsupportedAction(String),
5970    Operation(String),
5971    Sdk(SdkError),
5972}
5973
5974fn sdk_error(operation: SdkOperation, error: ServiceError) -> SdkError {
5975    match error {
5976        ServiceError::InvalidParams(message) => {
5977            SdkError::new(SdkErrorCode::InvalidArgument, operation, message)
5978        }
5979        ServiceError::MethodNotFound | ServiceError::UnsupportedAction(_) => {
5980            SdkError::unsupported(operation)
5981        }
5982        ServiceError::Operation(message) => {
5983            let code = if message.contains("already in progress") {
5984                SdkErrorCode::Busy
5985            } else if message.contains("not supported by this runtime") {
5986                SdkErrorCode::UnsupportedAction
5987            } else if message.contains("unknown runtime connection") {
5988                SdkErrorCode::NotFound
5989            } else {
5990                SdkErrorCode::Execution
5991            };
5992            SdkError::new(code, operation, message)
5993        }
5994        ServiceError::Sdk(error) => error,
5995    }
5996}
5997
5998fn sdk_rpc_error(id: Value, error: &SdkError) -> Value {
5999    let error_code = error.code();
6000    let code = match error_code {
6001        SdkErrorCode::Unauthenticated => -32030,
6002        SdkErrorCode::Unauthorized => -32031,
6003        SdkErrorCode::ControllerRequired => -32032,
6004        SdkErrorCode::LeaseExpired => -32033,
6005        SdkErrorCode::InvalidArgument => -32602,
6006        SdkErrorCode::NotFound => -32004,
6007        SdkErrorCode::Busy => -32000,
6008        SdkErrorCode::UnsupportedAction => -32020,
6009        SdkErrorCode::Execution => -32002,
6010        SdkErrorCode::Transport => -32003,
6011    };
6012    json!({
6013        "jsonrpc": "2.0",
6014        "id": id,
6015        "error": {
6016            "code": code,
6017            "name": error_code,
6018            "operation": error.operation(),
6019            "message": error.to_string(),
6020        },
6021    })
6022}
6023
6024fn decode<T: for<'de> Deserialize<'de>>(value: Value) -> std::result::Result<T, ServiceError> {
6025    serde_json::from_value(value).map_err(|error| ServiceError::InvalidParams(error.to_string()))
6026}
6027
6028fn operation(error: impl Into<crate::Error>) -> ServiceError {
6029    let error = error.into();
6030    match error {
6031        crate::Error::Sdk(error) => ServiceError::Sdk(error),
6032        error => ServiceError::Operation(error.to_string()),
6033    }
6034}
6035
6036/// ORCH-12 `harness.v1.memory.show|search` params. `homes` is the same
6037/// storage-root override every read-only method accepts, so a caller can
6038/// point the read at a fixture home without touching the real ones.
6039#[derive(Debug, Clone, Deserialize, Default)]
6040#[serde(default)]
6041struct MemoryRequest {
6042    /// Harness whose store is read. Required.
6043    harness: Option<String>,
6044    /// The needle, required by `search`.
6045    query: Option<String>,
6046    /// Hermes profile, OpenClaw agent, or Claude Code project.
6047    profile: Option<String>,
6048    /// Claude Code session id selecting a project store (`show` only).
6049    session: Option<String>,
6050    /// Include each document's whole text (`show` only).
6051    full: bool,
6052    /// Treat `query` as a regular expression (`search` only).
6053    regex: bool,
6054    /// Working tree whose project store is read.
6055    cwd: Option<std::path::PathBuf>,
6056    /// Storage roots to read.
6057    homes: crate::HarnessHomes,
6058}
6059
6060/// Read the memory noun. A harness with no memory store fails with
6061/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6062fn memory_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6063    let request = decode::<MemoryRequest>(params)?;
6064    let harness = request
6065        .harness
6066        .clone()
6067        .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6068    let to_service = |error: crate::memory::MemoryError| match error {
6069        crate::memory::MemoryError::UnsupportedHarness { .. }
6070        | crate::memory::MemoryError::SessionNotScoped { .. } => {
6071            ServiceError::UnsupportedAction(error.to_string())
6072        }
6073        other => ServiceError::InvalidParams(other.to_string()),
6074    };
6075    match method {
6076        "harness.v1.memory.show" => {
6077            let documents = crate::memory::show_memory(&crate::memory::MemoryQuery {
6078                harness,
6079                profile: request.profile,
6080                session: request.session,
6081                full: request.full,
6082                cwd: request.cwd,
6083                homes: request.homes,
6084            })
6085            .map_err(to_service)?;
6086            Ok(json!({
6087                "schema": crate::memory::MEMORY_SCHEMA,
6088                "documents": documents,
6089            }))
6090        }
6091        "harness.v1.memory.search" => {
6092            let query = request
6093                .query
6094                .ok_or_else(|| ServiceError::InvalidParams("`query` is required".into()))?;
6095            let matches = crate::memory::search_memory(&crate::memory::MemorySearchQuery {
6096                harness,
6097                query,
6098                profile: request.profile,
6099                regex: request.regex,
6100                cwd: request.cwd,
6101                homes: request.homes,
6102            })
6103            .map_err(to_service)?;
6104            Ok(json!({
6105                "schema": crate::memory::MEMORY_SCHEMA,
6106                "matches": matches,
6107            }))
6108        }
6109        _ => Err(ServiceError::MethodNotFound),
6110    }
6111}
6112
6113/// ORCH-10 `harness.v1.profiles.list|get` params. `homes` is the same
6114/// storage-root override every read-only method accepts, so a caller can
6115/// point the read at a fixture home without touching the real ones.
6116#[derive(Debug, Clone, Deserialize)]
6117#[serde(default)]
6118struct ProfilesQuery {
6119    /// Restrict the listing to one harness. `get` requires it.
6120    harness: Option<String>,
6121    /// Profile name, required by `get`.
6122    name: Option<String>,
6123    /// Storage roots to read.
6124    homes: crate::HarnessHomes,
6125}
6126
6127impl Default for ProfilesQuery {
6128    fn default() -> Self {
6129        Self {
6130            harness: None,
6131            name: None,
6132            homes: crate::HarnessHomes::default(),
6133        }
6134    }
6135}
6136
6137/// Read the profile noun. A harness with no profile concept fails with
6138/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6139fn profiles_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6140    let query = decode::<ProfilesQuery>(params)?;
6141    let to_service = |error: crate::profiles::ProfileError| match error {
6142        crate::profiles::ProfileError::UnsupportedHarness { .. } => {
6143            ServiceError::UnsupportedAction(error.to_string())
6144        }
6145        crate::profiles::ProfileError::NotFound { .. } => {
6146            ServiceError::InvalidParams(error.to_string())
6147        }
6148    };
6149    match method {
6150        "harness.v1.profiles.list" => {
6151            let profiles = crate::profiles::list_profiles(&query.homes, query.harness.as_deref())
6152                .map_err(to_service)?;
6153            Ok(json!({
6154                "schema": crate::profiles::PROFILES_SCHEMA,
6155                "profiles": profiles,
6156            }))
6157        }
6158        "harness.v1.profiles.get" => {
6159            let harness = query
6160                .harness
6161                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6162            let name = query
6163                .name
6164                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6165            let profile =
6166                crate::profiles::get_profile(&query.homes, &harness, &name).map_err(to_service)?;
6167            Ok(json!({
6168                "schema": crate::profiles::PROFILES_SCHEMA,
6169                "profile": profile,
6170            }))
6171        }
6172        _ => Err(ServiceError::MethodNotFound),
6173    }
6174}
6175
6176/// ORCH-14 `harness.v1.channels.list|status` params, the same storage-root
6177/// override every read-only method accepts so a caller can point the read at
6178/// a fixture home without touching the real ones.
6179#[derive(Debug, Clone, Deserialize)]
6180#[serde(default)]
6181struct ChannelsQuery {
6182    /// Restrict the listing to one harness. `status` requires it.
6183    harness: Option<String>,
6184    /// Channel name, required by `status`.
6185    name: Option<String>,
6186    /// Storage roots to read.
6187    homes: crate::HarnessHomes,
6188}
6189
6190impl Default for ChannelsQuery {
6191    fn default() -> Self {
6192        Self {
6193            harness: None,
6194            name: None,
6195            homes: crate::HarnessHomes::default(),
6196        }
6197    }
6198}
6199
6200/// Read the channel noun. A harness with no channel concept fails with
6201/// `UnsupportedAction` (RPC `-32020`), never an empty list. No row carries a
6202/// token, key or secret — see `crate::channels` "Secrecy".
6203#[derive(Debug, Clone, Deserialize)]
6204#[serde(default)]
6205struct RoutesQuery {
6206    harness: Option<String>,
6207    /// Restrict to routes targeting one profile / agent.
6208    profile: Option<String>,
6209    homes: crate::HarnessHomes,
6210}
6211
6212impl Default for RoutesQuery {
6213    fn default() -> Self {
6214        Self {
6215            harness: None,
6216            profile: None,
6217            homes: crate::HarnessHomes::default(),
6218        }
6219    }
6220}
6221
6222#[derive(Debug, Clone, Deserialize)]
6223#[serde(default)]
6224struct TriggersQuery {
6225    harness: Option<String>,
6226    homes: crate::HarnessHomes,
6227}
6228
6229impl Default for TriggersQuery {
6230    fn default() -> Self {
6231        Self {
6232            harness: None,
6233            homes: crate::HarnessHomes::default(),
6234        }
6235    }
6236}
6237
6238fn triggers_call(params: Value) -> std::result::Result<Value, ServiceError> {
6239    let query = decode::<TriggersQuery>(params)?;
6240    let triggers = crate::triggers::list_triggers(&query.homes, query.harness.as_deref())
6241        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6242    Ok(json!({
6243        "schema": crate::triggers::TRIGGERS_SCHEMA,
6244        "triggers": triggers,
6245    }))
6246}
6247
6248fn routes_call(params: Value) -> std::result::Result<Value, ServiceError> {
6249    let query = decode::<RoutesQuery>(params)?;
6250    let routes = crate::routes::list_routes(
6251        &query.homes,
6252        query.harness.as_deref(),
6253        query.profile.as_deref(),
6254    )
6255    .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6256    Ok(json!({
6257        "schema": crate::routes::ROUTES_SCHEMA,
6258        "routes": routes,
6259    }))
6260}
6261
6262fn channels_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6263    let query = decode::<ChannelsQuery>(params)?;
6264    let to_service = |error: crate::channels::ChannelError| match error {
6265        crate::channels::ChannelError::UnsupportedHarness { .. } => {
6266            ServiceError::UnsupportedAction(error.to_string())
6267        }
6268        crate::channels::ChannelError::NotFound { .. } => {
6269            ServiceError::InvalidParams(error.to_string())
6270        }
6271    };
6272    match method {
6273        "harness.v1.channels.list" => {
6274            let channels = crate::channels::list_channels(&query.homes, query.harness.as_deref())
6275                .map_err(to_service)?;
6276            Ok(json!({
6277                "schema": crate::channels::CHANNELS_SCHEMA,
6278                "channels": channels,
6279            }))
6280        }
6281        "harness.v1.channels.status" => {
6282            let harness = query
6283                .harness
6284                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6285            let name = query
6286                .name
6287                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6288            let channel = crate::channels::channel_status(&query.homes, &harness, &name)
6289                .map_err(to_service)?;
6290            Ok(json!({
6291                "schema": crate::channels::CHANNELS_SCHEMA,
6292                "channel": channel,
6293            }))
6294        }
6295        _ => Err(ServiceError::MethodNotFound),
6296    }
6297}
6298
6299fn rpc_error(id: Value, code: i64, message: &str) -> Value {
6300    json!({
6301        "jsonrpc": "2.0",
6302        "id": id,
6303        "error": {"code": code, "message": message},
6304    })
6305}
6306
6307#[cfg(test)]
6308mod tests {
6309    use super::*;
6310    use crate::{HarnessEvent, HarnessId, RuntimeEndpoint, RuntimeHandle, StorageLocator};
6311    use async_trait::async_trait;
6312    use std::io::Write;
6313    use std::path::PathBuf;
6314    use std::time::Instant;
6315
6316    #[test]
6317    fn indexed_claude_descriptor_keeps_the_live_peer_address() {
6318        let descriptor = SessionDescriptor {
6319            locator: SessionLocator {
6320                harness: HarnessId::new(HarnessId::CLAUDE_CODE),
6321                session_id: "live-session".into(),
6322                storage: StorageLocator::File {
6323                    path: PathBuf::from("/tmp/live-session.jsonl"),
6324                },
6325            },
6326            cwd: Some(PathBuf::from("/project")),
6327            title: None,
6328            preview_candidates: Vec::new(),
6329            latest_message_candidates: Vec::new(),
6330            updated_at_ms: Some(1),
6331            message_count: None,
6332            model: None,
6333            parent_session_id: None,
6334            child_session_count: 0,
6335            nouns: Default::default(),
6336        };
6337        let peer = crate::claude_peer::ClaudePeerSession {
6338            pid: 42,
6339            session_id: "live-session".into(),
6340            cwd: Some(PathBuf::from("/project")),
6341            name: "peer".into(),
6342            socket_path: PathBuf::from("/tmp/peer.sock"),
6343            status: Some(crate::claude_peer::ClaudePeerStatus::Busy),
6344            updated_at_ms: Some(1),
6345            version: Some("test".into()),
6346        };
6347
6348        let value = live_descriptor_value(&descriptor, &[peer]).unwrap();
6349        assert!(value["live_endpoint"]
6350            .as_str()
6351            .is_some_and(|endpoint| endpoint.starts_with("cc-peer:v1:42:peer:")));
6352    }
6353
6354    struct EndingRuntime {
6355        handle: RuntimeHandle,
6356        event: Option<HarnessEvent>,
6357        close_failures: usize,
6358    }
6359
6360    #[async_trait]
6361    impl RuntimeConnection for EndingRuntime {
6362        fn handle(&self) -> &RuntimeHandle {
6363            &self.handle
6364        }
6365
6366        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
6367            unreachable!("ending runtime does not accept input")
6368        }
6369
6370        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
6371            Ok(self.event.take())
6372        }
6373
6374        async fn interrupt(&mut self) -> crate::Result<()> {
6375            Ok(())
6376        }
6377
6378        async fn respond(&mut self, _request_id: Value, _response: Value) -> crate::Result<()> {
6379            Ok(())
6380        }
6381
6382        async fn close(&mut self) -> crate::Result<()> {
6383            if self.close_failures > 0 {
6384                self.close_failures -= 1;
6385                return Err(crate::Error::Other(
6386                    "cleanup temporarily unavailable".into(),
6387                ));
6388            }
6389            Ok(())
6390        }
6391    }
6392
6393    fn ending_runtime(event: Option<HarnessEvent>) -> Box<dyn RuntimeConnection> {
6394        Box::new(EndingRuntime {
6395            handle: RuntimeHandle {
6396                harness: HarnessId::from(HarnessId::CLAUDE_CODE),
6397                runtime_id: "ending-session".into(),
6398                endpoint: RuntimeEndpoint::LocalProcess {
6399                    pid: None,
6400                    command: vec!["ending-runtime".into()],
6401                    protocol: "test".into(),
6402                },
6403            },
6404            event,
6405            close_failures: 0,
6406        })
6407    }
6408
6409    #[tokio::test]
6410    async fn closing_a_runtime_surrenders_the_connection_even_when_teardown_fails() {
6411        let mut service = HarnessSessionService::new();
6412        let handle = ending_runtime(None).handle().clone();
6413        let runtime_id = handle.runtime_id.clone();
6414        let opened = service
6415            .insert_runtime(Box::new(EndingRuntime {
6416                handle,
6417                event: None,
6418                close_failures: 1,
6419            }))
6420            .unwrap();
6421        let connection = opened["connection"].as_str().unwrap().to_string();
6422        service.terminal_launches.insert(
6423            connection.clone(),
6424            StructuredLaunch {
6425                cwd: PathBuf::from("/fixture"),
6426                program: "fixture".into(),
6427                arguments: Vec::new(),
6428                env: BTreeMap::new(),
6429            },
6430        );
6431        let first = service
6432            .handle_async(request(
6433                1,
6434                "harness.v1.runtimes.close",
6435                json!({"connection": connection}),
6436            ))
6437            .await;
6438        // The harness's own teardown failed and the caller is told so...
6439        assert!(first.get("error").is_some(), "{first}");
6440        // ...but the connection is gone all the same. A connection whose close
6441        // cannot complete is exactly the one that must not stay registered:
6442        // holding it would answer every later call on this node with a turn
6443        // that is never going to end.
6444        assert!(!service.runtimes.contains_key(&connection));
6445        assert!(!service.terminal_launches.contains_key(&connection));
6446        assert!(!service.runtime_sequences.contains_key(&runtime_id));
6447        let again = service
6448            .handle_async(request(
6449                2,
6450                "harness.v1.runtimes.close",
6451                json!({"connection": connection}),
6452            ))
6453            .await;
6454        assert_eq!(again["error"]["code"], -32602, "{again}");
6455    }
6456
6457    fn request(id: u64, method: &str, params: Value) -> Value {
6458        json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
6459    }
6460
6461    // ---- ORCH-6: conversation nouns on `sessions.*` ----------------------
6462
6463    fn hermes_store() -> PathBuf {
6464        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hermes_home/state.db")
6465    }
6466
6467    /// The discovery response for the Hermes fixture home, with the one
6468    /// machine-specific value (the absolute store path) replaced so the exact
6469    /// same JSON can be committed and replayed by the UI story.
6470    fn hermes_discovery(params: Value) -> Value {
6471        let mut response =
6472            HarnessSessionService::new().handle(request(1, "harness.v1.sessions.discover", params));
6473        let store = hermes_store().display().to_string();
6474        for session in response["result"]["sessions"]
6475            .as_array_mut()
6476            .expect("sessions array")
6477        {
6478            if session["locator"]["storage"]["path"] == json!(store) {
6479                session["locator"]["storage"]["path"] = json!("<fixtures>/hermes_home/state.db");
6480            }
6481            // `activity` reports a wall-clock observation instant, not a fact
6482            // about the session; it would make this response differ on every
6483            // call. The nouns under test are all session facts.
6484            session.as_object_mut().unwrap().remove("activity");
6485        }
6486        response["result"].take()
6487    }
6488
6489    fn hermes_query() -> Value {
6490        json!({
6491            "harnesses": ["hermes"],
6492            "homes": {"hermes": hermes_store()},
6493        })
6494    }
6495
6496    fn row<'a>(result: &'a Value, id: &str) -> &'a Value {
6497        result["sessions"]
6498            .as_array()
6499            .expect("sessions array")
6500            .iter()
6501            .find(|session| session["locator"]["session_id"] == json!(id))
6502            .unwrap_or_else(|| panic!("no discovered row for `{id}` in {result:#}"))
6503    }
6504
6505    #[test]
6506    fn orch6_discover_rows_carry_the_conversation_nouns() {
6507        let result = hermes_discovery(hermes_query());
6508
6509        // A Telegram DM: reached on a channel, no repo — the workspace IS the
6510        // channel (D2 precedence), and `main` is not a profile.
6511        let dm = row(&result, "tg-dm-1");
6512        assert_eq!(dm["trigger"], json!("channel"));
6513        assert_eq!(dm["surface"]["platform"], json!("telegram"));
6514        assert_eq!(dm["surface"]["kind"], json!("dm"));
6515        assert_eq!(dm["surface"]["chat_id"], json!("123456"));
6516        assert_eq!(dm["surface"]["participant_id"], json!("u1"));
6517        assert_eq!(
6518            dm["workspace"],
6519            json!({"kind": "channel", "value": "telegram:123456"})
6520        );
6521        assert!(dm.get("profile").is_none(), "{dm:#}");
6522
6523        // A cron fire: recurring, with the job recovered from the minted id.
6524        let fire = row(&result, "cron_job42_20260902_120000");
6525        assert_eq!(fire["trigger"], json!("cron"));
6526        assert_eq!(
6527            fire["recurrence"],
6528            json!({"job_id": "job42", "kind": "cron"})
6529        );
6530        assert_eq!(fire["workspace"]["kind"], json!("repo"));
6531
6532        // A profiled group session with a pending handoff: repo workspace
6533        // wins over the channel, and the chat stays on the surface key.
6534        let coder = row(&result, "tg-coder-1");
6535        assert_eq!(coder["trigger"], json!("channel"));
6536        assert_eq!(coder["profile"], json!("coder"));
6537        assert_eq!(coder["surface"]["thread_id"], json!("55"));
6538        assert_eq!(
6539            coder["surface"]["key"],
6540            json!("agent:coder:telegram:group:-100777:55")
6541        );
6542        assert_eq!(
6543            coder["workspace"],
6544            json!({"kind": "repo", "value": "/workspace/project"})
6545        );
6546        assert_eq!(
6547            coder["cross_surface"],
6548            json!({"state": "pending", "platform": "discord"})
6549        );
6550
6551        // A plain ACP session stays human-triggered with no surface at all.
6552        let acp = row(&result, "cef97234-e8e8-428a-99ab-e8fff4e7e613");
6553        assert_eq!(acp["trigger"], json!("human"));
6554        assert!(acp.get("surface").is_none(), "{acp:#}");
6555        assert_eq!(acp["workspace"], json!({"kind": "none"}));
6556    }
6557
6558    #[test]
6559    fn orch6_discover_filters_by_harness_and_profile() {
6560        let mut params = hermes_query();
6561        params["profile"] = json!("coder");
6562        let result = hermes_discovery(params);
6563        let ids: Vec<&str> = result["sessions"]
6564            .as_array()
6565            .expect("sessions array")
6566            .iter()
6567            .map(|session| session["locator"]["session_id"].as_str().unwrap())
6568            .collect();
6569        assert_eq!(ids, vec!["tg-coder-1"]);
6570
6571        // A profile no session is routed through returns nothing rather than
6572        // silently ignoring the filter.
6573        let mut missing = hermes_query();
6574        missing["profile"] = json!("nobody");
6575        assert_eq!(hermes_discovery(missing)["sessions"], json!([]));
6576
6577        // The harness filter is `harnesses`; an id no harness answers to is
6578        // an empty page, never every store on the box.
6579        let elsewhere = json!({"harnesses": ["codex"], "homes": {"codex": hermes_store()}});
6580        assert_eq!(hermes_discovery(elsewhere)["sessions"], json!([]));
6581    }
6582
6583    #[test]
6584    fn orch6_load_reports_the_same_nouns_as_discovery() {
6585        let mut service = HarnessSessionService::new();
6586        let loaded = service.handle(request(
6587            1,
6588            "harness.v1.sessions.load",
6589            json!({"locator": {
6590                "harness": "hermes",
6591                "session_id": "tg-coder-1",
6592                "storage": {"kind": "file", "path": hermes_store()},
6593            }}),
6594        ));
6595        let session = &loaded["result"]["session"];
6596        let discovered = hermes_discovery(hermes_query());
6597        let row = row(&discovered, "tg-coder-1");
6598        for noun in [
6599            "trigger",
6600            "surface",
6601            "profile",
6602            "recurrence",
6603            "cross_surface",
6604            "workspace",
6605        ] {
6606            assert_eq!(
6607                session[noun],
6608                row.get(noun).cloned().unwrap_or(Value::Null),
6609                "`{noun}` disagrees between sessions.load and sessions.discover"
6610            );
6611        }
6612    }
6613
6614    /// ORCH-10: the fixture homes, as the RPC's `homes` override. Hermes's
6615    /// home is named by its `state.db`; OpenClaw's is the state directory.
6616    fn profile_fixture_homes() -> Value {
6617        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6618        json!({
6619            "hermes": fixtures.join("hermes_home/state.db"),
6620            "openclaw": fixtures.join("openclaw_home"),
6621        })
6622    }
6623
6624    fn profile_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
6625        response["result"]["profiles"]
6626            .as_array()
6627            .unwrap_or_else(|| panic!("no profiles array in {response}"))
6628            .iter()
6629            .find(|row| row["harness"] == harness && row["name"] == name)
6630            .unwrap_or_else(|| panic!("no `{harness}` profile `{name}` in {response}"))
6631    }
6632
6633    /// dev/01: every source answers in one row shape, over the committed
6634    /// fixture homes — the Hermes profile directory and its `state.db`
6635    /// partition, the OpenClaw agent directories and `openclaw.json`, and
6636    /// supercode's own presets.
6637    #[test]
6638    fn profiles_list_reads_every_source_uniformly() {
6639        let mut service = HarnessSessionService::new();
6640        let response = service.handle(request(
6641            1,
6642            "harness.v1.profiles.list",
6643            json!({"homes": profile_fixture_homes()}),
6644        ));
6645        assert_eq!(
6646            response["result"]["schema"],
6647            crate::profiles::PROFILES_SCHEMA
6648        );
6649
6650        let default = profile_row(&response, "hermes", "default");
6651        assert_eq!(default["kind"], "hermes_profile");
6652        assert_eq!(default["default"], true);
6653        assert_eq!(default["routes"], 0);
6654        assert_eq!(default["sessions"], 11);
6655        assert_eq!(default["model"], "anthropic/claude-sonnet-4-5");
6656
6657        let coder = profile_row(&response, "hermes", "coder");
6658        assert_eq!(coder["kind"], "hermes_profile");
6659        assert_eq!(coder["default"], false);
6660        assert_eq!(coder["routes"], 1, "gateway.profile_routes targets coder");
6661        assert_eq!(coder["sessions"], 1, "state.db profile_name = 'coder'");
6662        assert_eq!(coder["model"], "anthropic/claude-opus-4-8");
6663        assert!(coder["home"]
6664            .as_str()
6665            .unwrap()
6666            .ends_with("hermes_home/profiles/coder"));
6667
6668        let main = profile_row(&response, "openclaw", "main");
6669        assert_eq!(main["kind"], "openclaw_agent");
6670        // No entry declares `default: true` (real configs do not), so `main`
6671        // wins on OpenClaw's own convention rather than alphabetically.
6672        assert_eq!(main["default"], true);
6673        assert_eq!(main["routes"], 0);
6674        assert_eq!(main["sessions"], 4);
6675        assert_eq!(
6676            main["model"],
6677            Value::Null,
6678            "`agents.defaults.model` is an install default, not this agent's pin"
6679        );
6680
6681        let design = profile_row(&response, "openclaw", "design");
6682        assert_eq!(design["default"], false);
6683        assert_eq!(design["routes"], 1, "one binding names agentId `design`");
6684        assert_eq!(design["sessions"], 0);
6685        assert_eq!(design["model"], "anthropic/claude-opus-4-8");
6686
6687        let preset = profile_row(&response, "supercode", "supercode-default");
6688        assert_eq!(preset["kind"], "preset");
6689        assert_eq!(preset["default"], true);
6690        assert_eq!(preset["home"], Value::Null);
6691        assert_eq!(preset["routes"], Value::Null);
6692    }
6693
6694    /// Codex's own profiles are `[profiles.<name>]` tables, with the
6695    /// top-level `profile` key naming the default.
6696    #[test]
6697    fn profiles_list_reads_codex_profile_tables() {
6698        let codex_home = std::env::temp_dir().join(format!(
6699            "supercode-orch10-codex-{}-{}",
6700            std::process::id(),
6701            std::time::SystemTime::now()
6702                .duration_since(std::time::UNIX_EPOCH)
6703                .unwrap()
6704                .as_nanos()
6705        ));
6706        std::fs::create_dir_all(codex_home.join("sessions")).unwrap();
6707        std::fs::write(
6708            codex_home.join("config.toml"),
6709            "profile = \"review\"\n\n[profiles.review]\nmodel = \"gpt-5.1-codex\"\n\n[profiles.fast]\nmodel = \"gpt-5.1-codex-mini\"\n",
6710        )
6711        .unwrap();
6712
6713        let mut service = HarnessSessionService::new();
6714        let response = service.handle(request(
6715            1,
6716            "harness.v1.profiles.list",
6717            json!({"harness": "codex", "homes": {"codex": codex_home.join("sessions")}}),
6718        ));
6719        let rows = response["result"]["profiles"].as_array().unwrap();
6720        assert_eq!(rows.len(), 2, "{response}");
6721        let review = profile_row(&response, "codex", "review");
6722        assert_eq!(review["kind"], "codex_profile");
6723        assert_eq!(review["default"], true);
6724        assert_eq!(review["model"], "gpt-5.1-codex");
6725        assert_eq!(review["home"], Value::Null);
6726        assert_eq!(profile_row(&response, "codex", "fast")["default"], false);
6727
6728        let got = service.handle(request(
6729            2,
6730            "harness.v1.profiles.get",
6731            json!({
6732                "harness": "codex",
6733                "name": "fast",
6734                "homes": {"codex": codex_home.join("sessions")},
6735            }),
6736        ));
6737        assert_eq!(got["result"]["profile"]["model"], "gpt-5.1-codex-mini");
6738        std::fs::remove_dir_all(&codex_home).ok();
6739    }
6740
6741    /// A verb a harness lacks fails with `UnsupportedAction`, never a silent
6742    /// empty list; an unknown name is an invalid argument, not an empty row.
6743    #[test]
6744    fn profiles_refuse_harnesses_without_the_concept() {
6745        let mut service = HarnessSessionService::new();
6746        let response = service.handle(request(
6747            1,
6748            "harness.v1.profiles.list",
6749            json!({"harness": "claude-code"}),
6750        ));
6751        assert_eq!(response["error"]["code"], -32020, "{response}");
6752
6753        let missing = service.handle(request(
6754            2,
6755            "harness.v1.profiles.get",
6756            json!({
6757                "harness": "hermes",
6758                "name": "no-such-profile",
6759                "homes": profile_fixture_homes(),
6760            }),
6761        ));
6762        assert_eq!(missing["error"]["code"], -32602, "{missing}");
6763    }
6764
6765    /// The two methods are advertised, so a client discovers them from
6766    /// `harness.v1.capabilities` rather than from documentation.
6767    #[test]
6768    fn profiles_methods_are_advertised() {
6769        let mut service = HarnessSessionService::new();
6770        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
6771        let methods = response["result"]["methods"].as_array().unwrap();
6772        for method in ["harness.v1.profiles.list", "harness.v1.profiles.get"] {
6773            assert!(
6774                methods.iter().any(|entry| entry == method),
6775                "{method} is not advertised"
6776            );
6777        }
6778    }
6779
6780    // -----------------------------------------------------------------
6781    // ORCH-14 — channels
6782    // -----------------------------------------------------------------
6783
6784    fn channel_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
6785        response["result"]["channels"]
6786            .as_array()
6787            .unwrap_or_else(|| panic!("no channels array in {response}"))
6788            .iter()
6789            .find(|row| row["harness"] == harness && row["name"] == name)
6790            .unwrap_or_else(|| panic!("no `{harness}` channel `{name}` in {response}"))
6791    }
6792
6793    fn channels_list(harness: Option<&str>) -> Value {
6794        let mut params = json!({"homes": profile_fixture_homes()});
6795        if let Some(harness) = harness {
6796            params["harness"] = json!(harness);
6797        }
6798        HarnessSessionService::new().handle(request(1, "harness.v1.channels.list", params))
6799    }
6800
6801    /// dev/01: both sources answer in one row shape over the committed
6802    /// fixture homes — Hermes's `platforms:` blocks with their `extra` maps,
6803    /// and OpenClaw's `channels.<name>` entries split per account.
6804    #[test]
6805    fn channels_list_reads_both_gateway_harnesses_uniformly() {
6806        let response = channels_list(None);
6807        assert_eq!(
6808            response["result"]["schema"],
6809            crate::channels::CHANNELS_SCHEMA
6810        );
6811
6812        // Hermes: a credentialed platform, a bridged `extra.key` platform,
6813        // and one the config explicitly disables.
6814        let telegram = channel_row(&response, "hermes", "telegram");
6815        assert_eq!(telegram["kind"], "telegram");
6816        assert_eq!(telegram["enabled"], true);
6817        assert_eq!(telegram["configured"], true);
6818        // The `sessions` count is the discovery rows whose surface platform
6819        // is telegram: the fixture's `agent:main:telegram:…` DM and the
6820        // `agent:coder:telegram:…` group.
6821        assert_eq!(telegram["sessions"], 2);
6822        let api = channel_row(&response, "hermes", "api_server");
6823        assert_eq!(api["configured"], true, "extra.key is a credential key");
6824        assert_eq!(api["sessions"], 0);
6825        let webhook = channel_row(&response, "hermes", "webhook");
6826        assert_eq!(webhook["enabled"], false);
6827        // Hermes lists no credential for `webhook`: declaring it is all it
6828        // needs, so a credential-less entry is still `configured`.
6829        assert_eq!(webhook["configured"], true);
6830
6831        // OpenClaw: one row per account, named `<channel>/<accountId>`.
6832        let linked = channel_row(&response, "openclaw", "slack/T0FIXTURE");
6833        assert_eq!(linked["kind"], "slack");
6834        assert_eq!(linked["account"], "T0FIXTURE");
6835        assert_eq!(linked["enabled"], true);
6836        assert_eq!(linked["configured"], true);
6837        let unlinked = channel_row(&response, "openclaw", "slack/T1FIXTURE");
6838        assert_eq!(unlinked["enabled"], false);
6839        assert_eq!(
6840            unlinked["configured"], false,
6841            "an account with no credential key is not configured"
6842        );
6843        // A single-account channel keeps its own name and names its account
6844        // inline.
6845        let telegram = channel_row(&response, "openclaw", "telegram");
6846        assert_eq!(telegram["account"], "hermes-fixture-bot");
6847        assert_eq!(telegram["configured"], true);
6848
6849        // `status` is never claimed from a config file.
6850        for row in response["result"]["channels"].as_array().unwrap() {
6851            assert_eq!(row["status"], "unknown", "{row}");
6852        }
6853    }
6854
6855    /// dev/01: no field of any emitted row carries a credential. The fixture
6856    /// homes hold four FAKE credential strings; a row that leaked one — as a
6857    /// value, an account label, or a name — fails here.
6858    #[test]
6859    fn channels_rows_never_carry_a_fixture_secret() {
6860        let secrets = [
6861            "FAKE-TOKEN-DO-NOT-EMIT",
6862            "FAKE-API-SERVER-KEY-DO-NOT-EMIT",
6863            "FAKE-SLACK-BOT-TOKEN-DO-NOT-EMIT",
6864            "FAKE-SLACK-APP-TOKEN-DO-NOT-EMIT",
6865            "FAKE-TELEGRAM-TOKEN-DO-NOT-EMIT",
6866        ];
6867        // The strings really are in the fixtures, so this test can fail.
6868        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6869        let raw = format!(
6870            "{}{}",
6871            std::fs::read_to_string(fixtures.join("hermes_home/config.yaml")).unwrap(),
6872            std::fs::read_to_string(fixtures.join("openclaw_home/openclaw.json")).unwrap(),
6873        );
6874        for secret in secrets {
6875            assert!(raw.contains(secret), "fixture no longer holds `{secret}`");
6876        }
6877
6878        let emitted = serde_json::to_string(&channels_list(None)["result"]).unwrap();
6879        for secret in secrets {
6880            assert!(
6881                !emitted.contains(secret),
6882                "`{secret}` leaked into a channel row: {emitted}"
6883            );
6884        }
6885        // Belt and braces: no row FIELD is credential-shaped either, so a
6886        // future field cannot smuggle one past the literal scan.
6887        for row in channels_list(None)["result"]["channels"]
6888            .as_array()
6889            .unwrap()
6890        {
6891            for key in row.as_object().unwrap().keys() {
6892                let key = key.to_ascii_lowercase();
6893                assert!(
6894                    !["token", "key", "secret", "password", "credential"]
6895                        .iter()
6896                        .any(|marker| key.ends_with(marker)),
6897                    "`{key}` is a credential-shaped field on a channel row"
6898                );
6899            }
6900        }
6901    }
6902
6903    /// `status` answers one row by name, and refuses an unknown one.
6904    #[test]
6905    fn channels_status_reads_one_row_by_name() {
6906        let mut service = HarnessSessionService::new();
6907        let got = service.handle(request(
6908            1,
6909            "harness.v1.channels.status",
6910            json!({
6911                "harness": "openclaw",
6912                "name": "slack/T0FIXTURE",
6913                "homes": profile_fixture_homes(),
6914            }),
6915        ));
6916        assert_eq!(got["result"]["channel"]["kind"], "slack");
6917        assert_eq!(got["result"]["channel"]["account"], "T0FIXTURE");
6918        assert_eq!(got["result"]["channel"]["status"], "unknown");
6919
6920        let missing = service.handle(request(
6921            2,
6922            "harness.v1.channels.status",
6923            json!({
6924                "harness": "openclaw",
6925                "name": "no-such-channel",
6926                "homes": profile_fixture_homes(),
6927            }),
6928        ));
6929        assert_eq!(missing["error"]["code"], -32602, "{missing}");
6930    }
6931
6932    /// A harness with no channel concept fails with `UnsupportedAction`,
6933    /// never a silent empty list — Claude Code included, because its channels
6934    /// are MCP-protocol declarations no config file names.
6935    #[test]
6936    fn channels_refuse_harnesses_without_the_concept() {
6937        let response = channels_list(Some("claude-code"));
6938        assert_eq!(response["error"]["code"], -32020, "{response}");
6939        let codex = channels_list(Some("codex"));
6940        assert_eq!(codex["error"]["code"], -32020, "{codex}");
6941    }
6942
6943    /// The harness filter restricts the rows rather than being ignored.
6944    #[test]
6945    fn channels_list_filters_by_harness() {
6946        let response = channels_list(Some("openclaw"));
6947        let rows = response["result"]["channels"].as_array().unwrap();
6948        assert!(!rows.is_empty(), "{response}");
6949        assert!(
6950            rows.iter().all(|row| row["harness"] == "openclaw"),
6951            "harness filter leaked: {response}"
6952        );
6953    }
6954
6955    /// Both methods are advertised, so a client discovers them from
6956    /// `harness.v1.capabilities` rather than from documentation.
6957    #[test]
6958    fn channels_methods_are_advertised() {
6959        let mut service = HarnessSessionService::new();
6960        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
6961        let methods = response["result"]["methods"].as_array().unwrap();
6962        for method in ["harness.v1.channels.list", "harness.v1.channels.status"] {
6963            assert!(
6964                methods.iter().any(|entry| entry == method),
6965                "{method} is not advertised"
6966            );
6967        }
6968    }
6969
6970    /// The UI story renders REAL rows: this writes the discovery response the
6971    /// two assertions above pin into the fixture the Storybook
6972    /// `Compositions/Universal nouns` stories import, and fails when the
6973    /// committed copy has drifted from what the service now answers.
6974    #[test]
6975    fn orch6_story_fixture_matches_the_live_discovery_response() {
6976        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6977            .join("../../sdk/ui/stories/fixtures/hermes-discovery.json");
6978        let mut result = hermes_discovery(hermes_query());
6979        // `updated_at_ms` is derived from the fixture's own stored timestamps,
6980        // so the whole response is deterministic; drop only the cursor, which
6981        // is pagination state rather than a session fact.
6982        result.as_object_mut().unwrap().remove("next_cursor");
6983        let rendered = format!("{}\n", serde_json::to_string_pretty(&result).unwrap());
6984        if std::env::var_os("SUPERCODE_UPDATE_FIXTURES").is_some() {
6985            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
6986            std::fs::write(&path, &rendered).unwrap();
6987        }
6988        let committed = std::fs::read_to_string(&path).unwrap_or_default();
6989        assert_eq!(
6990            committed, rendered,
6991            "sdk/ui/stories/fixtures/hermes-discovery.json is stale — \
6992             re-run with SUPERCODE_UPDATE_FIXTURES=1"
6993        );
6994    }
6995
6996    fn pi_locator() -> SessionLocator {
6997        SessionLocator {
6998            harness: HarnessId::from(HarnessId::PI),
6999            session_id: "1e6f2a3b-0000-4000-8000-000000000001".into(),
7000            storage: StorageLocator::File {
7001                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7002                    .join("tests/fixtures/pi_session.jsonl"),
7003            },
7004        }
7005    }
7006
7007    fn opencode_locator() -> SessionLocator {
7008        let session_id = "ses_fixtureAAAAAAAAAAAAAAA1";
7009        SessionLocator {
7010            harness: HarnessId::from(HarnessId::OPENCODE),
7011            session_id: session_id.into(),
7012            storage: StorageLocator::Sqlite {
7013                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7014                    .join("tests/fixtures/opencode_fixture/opencode.db"),
7015                selector: session_id.into(),
7016            },
7017        }
7018    }
7019
7020    fn grok_locator() -> SessionLocator {
7021        SessionLocator {
7022            harness: HarnessId::from(HarnessId::GROK),
7023            session_id: "73c09283-4b33-41fa-90f1-0bcb0f7be523".into(),
7024            storage: StorageLocator::File {
7025                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7026                    .join("tests/fixtures/grok_session/chat_history.jsonl"),
7027            },
7028        }
7029    }
7030
7031    // ---- ORCH-11: `harness.v1.skills.list` -------------------------------
7032
7033    fn fixture_homes() -> Value {
7034        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7035        json!({
7036            "claude_code": fixtures.join("__absent__"),
7037            "codex": fixtures.join("__absent__"),
7038            "opencode": fixtures.join("__absent__"),
7039            "pi": fixtures.join("__absent__"),
7040            "agents": fixtures.join("__absent__"),
7041            "hermes": fixtures.join("hermes_home"),
7042            "openclaw": fixtures.join("openclaw_home"),
7043        })
7044    }
7045
7046    #[test]
7047    fn preview_search_uses_the_discovery_rpc_and_refuses_live_subscription() {
7048        let root = std::env::temp_dir().join(format!(
7049            "supercode-preview-rpc-{}-{}",
7050            std::process::id(),
7051            std::time::SystemTime::now()
7052                .duration_since(std::time::UNIX_EPOCH)
7053                .unwrap()
7054                .as_nanos()
7055        ));
7056        std::fs::create_dir_all(&root).unwrap();
7057        for id in ["first", "second"] {
7058            std::fs::write(root.join(format!("{id}.jsonl")), format!("{}\n{}\n",
7059                json!({"type": "session_meta", "payload": {"id": id, "cwd": "/workspace"}}),
7060                json!({"type": "event_msg", "payload": {"type": "agent_message", "message": "NEBULA result"}}),
7061            )).unwrap();
7062        }
7063        let mut service = HarnessSessionService::new();
7064        let query = json!({
7065            "harnesses": ["codex"], "homes": {"codex": root},
7066            "query": "nebula", "search_previews": true, "limit": 1
7067        });
7068        let first = service.handle(request(1, "harness.v1.sessions.discover", query.clone()));
7069        assert!(first.get("error").is_none(), "{first}");
7070        assert_eq!(first["result"]["receipt"]["searched_previews"], true);
7071        assert_eq!(first["result"]["receipt"]["total_matched"], 2);
7072        let mut next_query = query.clone();
7073        next_query["cursor"] = first["result"]["next_cursor"].clone();
7074        let next = service.handle(request(2, "harness.v1.sessions.discover", next_query));
7075        assert_eq!(next["result"]["receipt"]["returned"], 1);
7076        assert_eq!(next["result"]["receipt"]["total_matched"], 2);
7077        assert_eq!(next["result"]["receipt"]["truncated"], false);
7078        assert_ne!(
7079            first["result"]["sessions"][0]["locator"],
7080            next["result"]["sessions"][0]["locator"]
7081        );
7082        let refused = service.handle(request(3, "harness.v1.sessions.index.subscribe", query));
7083        assert!(
7084            refused["error"]["message"]
7085                .as_str()
7086                .unwrap()
7087                .contains("use sessions.discover"),
7088            "{refused}"
7089        );
7090        std::fs::remove_dir_all(root).unwrap();
7091    }
7092
7093    #[test]
7094    fn session_index_resize_preserves_subscription_and_rejects_invalid_requests() {
7095        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.sessions.index.resize"));
7096        let root = std::env::temp_dir().join(format!(
7097            "supercode-index-rpc-{}-{}",
7098            std::process::id(),
7099            std::time::SystemTime::now()
7100                .duration_since(std::time::UNIX_EPOCH)
7101                .unwrap()
7102                .as_nanos()
7103        ));
7104        std::fs::create_dir_all(&root).unwrap();
7105        for id in ["first", "second"] {
7106            std::fs::write(root.join(format!("{id}.jsonl")), format!(
7107                "{{\"type\":\"session_meta\",\"payload\":{{\"id\":\"{id}\",\"cwd\":\"/workspace\"}}}}\n"
7108            )).unwrap();
7109        }
7110        let mut service = HarnessSessionService::new();
7111        let opened = service.handle(request(
7112            1,
7113            "harness.v1.sessions.index.subscribe",
7114            json!({
7115                "harnesses": ["codex"], "homes": { "codex": root }, "limit": 1
7116            }),
7117        ));
7118        assert!(opened.get("error").is_none(), "{opened:#}");
7119        let subscription = opened["result"]["subscription"]
7120            .as_str()
7121            .unwrap()
7122            .to_owned();
7123        assert_eq!(opened["result"]["initial"].as_array().unwrap().len(), 1);
7124        for params in [
7125            json!({"subscription": subscription, "limit": 0}),
7126            json!({"subscription": subscription, "limit": 2049}),
7127            json!({"subscription": subscription, "limit": 2, "cursor": "not-allowed"}),
7128            json!({"subscription": "unknown", "limit": 2}),
7129        ] {
7130            let rejected = service.handle(request(2, "harness.v1.sessions.index.resize", params));
7131            assert_eq!(rejected["error"]["code"], -32602, "{rejected:#}");
7132        }
7133        for (limit, revision) in [(1, 1), (2, 2), (2, 2), (1, 3)] {
7134            let response = service.handle(request(
7135                3,
7136                "harness.v1.sessions.index.resize",
7137                json!({
7138                    "subscription": subscription, "limit": limit
7139                }),
7140            ));
7141            assert!(response.get("error").is_none(), "{response:#}");
7142            assert_eq!(response["result"]["subscription"], subscription);
7143            assert_eq!(response["result"]["revision"], revision);
7144            assert_eq!(
7145                response["result"]["initial"].as_array().unwrap().len(),
7146                limit
7147            );
7148            assert_eq!(response["result"]["receipt"]["total_matched"], 2);
7149            assert_eq!(service.index_subscriptions.len(), 1);
7150        }
7151        let removed = service.handle(request(
7152            4,
7153            "harness.v1.sessions.index.unsubscribe",
7154            json!({
7155                "subscription": subscription
7156            }),
7157        ));
7158        assert_eq!(removed["result"]["removed"], true);
7159        let stale = service.handle(request(
7160            5,
7161            "harness.v1.sessions.index.resize",
7162            json!({
7163                "subscription": subscription, "limit": 1
7164            }),
7165        ));
7166        assert_eq!(stale["error"]["code"], -32602);
7167        drop(service);
7168        std::fs::remove_dir_all(root).unwrap();
7169    }
7170
7171    fn skills_rows(params: Value) -> Vec<Value> {
7172        let response =
7173            HarnessSessionService::new().handle(request(1, "harness.v1.skills.list", params));
7174        assert!(response.get("error").is_none(), "{response:#}");
7175        response["result"].as_array().cloned().unwrap_or_default()
7176    }
7177
7178    /// The uniform row over two harnesses at once, from the harnesses' own
7179    /// skill roots: name, harness, scope, location, description, version.
7180    #[test]
7181    fn skills_list_reads_the_hermes_and_openclaw_roots() {
7182        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7183        let rows = skills_rows(json!({
7184            "homes": fixture_homes(),
7185            "cwd": fixtures.join("hermes_home"),
7186        }));
7187        let arxiv = rows
7188            .iter()
7189            .find(|row| row["name"] == json!("arxiv-search"))
7190            .unwrap_or_else(|| panic!("no arxiv row in {rows:#?}"));
7191        assert_eq!(arxiv["harness"], json!(HarnessId::HERMES));
7192        assert_eq!(arxiv["scope"], json!("user"));
7193        assert_eq!(arxiv["version"], json!("1.4.0"));
7194        assert!(arxiv["location"]
7195            .as_str()
7196            .unwrap()
7197            .ends_with("hermes_home/skills/research/arxiv"));
7198
7199        // A directory with no SKILL.md still lists, by directory name.
7200        let bare = rows
7201            .iter()
7202            .find(|row| row["name"] == json!("bare-skill"))
7203            .unwrap_or_else(|| panic!("no bare-skill row in {rows:#?}"));
7204        assert_eq!(bare["enabled"], json!(null));
7205        assert!(bare.get("description").is_none());
7206
7207        let demo = rows
7208            .iter()
7209            .find(|row| row["name"] == json!("clawhub-demo"))
7210            .unwrap_or_else(|| panic!("no clawhub-demo row in {rows:#?}"));
7211        assert_eq!(demo["harness"], json!(HarnessId::OPENCLAW));
7212        assert_eq!(demo["scope"], json!("managed"));
7213        assert_eq!(demo["enabled"], json!(false));
7214    }
7215
7216    /// Both filters select against the same rows.
7217    #[test]
7218    fn skills_list_filters_by_harness_and_scope() {
7219        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7220        let hermes = skills_rows(json!({
7221            "homes": fixture_homes(),
7222            "cwd": fixtures.join("hermes_home"),
7223            "harness": HarnessId::HERMES,
7224        }));
7225        assert!(!hermes.is_empty());
7226        assert!(hermes
7227            .iter()
7228            .all(|row| row["harness"] == json!(HarnessId::HERMES)));
7229
7230        let managed = skills_rows(json!({
7231            "homes": fixture_homes(),
7232            "cwd": fixtures.join("openclaw_home"),
7233            "harness": HarnessId::OPENCLAW,
7234            "scope": "managed",
7235        }));
7236        assert_eq!(managed.len(), 1, "{managed:#?}");
7237        assert_eq!(managed[0]["name"], json!("clawhub-demo"));
7238
7239        let bundled = skills_rows(json!({
7240            "homes": fixture_homes(),
7241            "cwd": fixtures.join("openclaw_home"),
7242            "harness": HarnessId::OPENCLAW,
7243            "scope": "bundled",
7244        }));
7245        assert!(bundled.is_empty(), "{bundled:#?}");
7246    }
7247
7248    /// A harness supercode has no skills root for is refused by name, not
7249    /// answered with an empty list.
7250    #[test]
7251    fn skills_list_refuses_an_unknown_harness() {
7252        let response = HarnessSessionService::new().handle(request(
7253            1,
7254            "harness.v1.skills.list",
7255            json!({"harness": "not-a-harness", "homes": fixture_homes()}),
7256        ));
7257        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7258        assert!(response["error"]["message"]
7259            .as_str()
7260            .unwrap()
7261            .contains("not-a-harness"));
7262    }
7263
7264    /// The method is advertised, and its SDK operation resolves it.
7265    #[test]
7266    fn skills_list_is_an_advertised_method_and_sdk_operation() {
7267        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.list"));
7268        assert_eq!(
7269            SdkOperation::from_method("harness.v1.skills.list"),
7270            Some(SdkOperation::SkillsList)
7271        );
7272    }
7273
7274    // ---- ORCH-22: `harness.v1.skills.install|remove` ----------------------
7275
7276    /// Both controlled verbs are advertised and resolve to their operation.
7277    #[test]
7278    fn skills_install_and_remove_are_advertised_methods_and_sdk_operations() {
7279        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.install"));
7280        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.remove"));
7281        assert_eq!(
7282            SdkOperation::from_method("harness.v1.skills.install"),
7283            Some(SdkOperation::SkillsInstall)
7284        );
7285        assert_eq!(
7286            SdkOperation::from_method("harness.v1.skills.remove"),
7287            Some(SdkOperation::SkillsRemove)
7288        );
7289    }
7290
7291    /// The directory door, end to end over the RPC: a local package lands in
7292    /// Claude Code's own user root and the outcome carries the operation and
7293    /// the row the ORCH-11 loader reads back.
7294    #[test]
7295    fn skills_install_and_remove_drive_the_directory_door() {
7296        let root = std::env::temp_dir().join(format!(
7297            "supercode-orch22-rpc-{}-{}",
7298            std::process::id(),
7299            std::time::SystemTime::now()
7300                .duration_since(std::time::UNIX_EPOCH)
7301                .unwrap()
7302                .as_nanos()
7303        ));
7304        let source = root.join("probe-src");
7305        std::fs::create_dir_all(&source).unwrap();
7306        std::fs::write(
7307            source.join("SKILL.md"),
7308            "---\nname: orch22-rpc\ndescription: a probe\n---\nbody\n",
7309        )
7310        .unwrap();
7311        let homes = json!({
7312            "claude_code": root.join("claude_home"),
7313            "codex": root.join("__absent__"),
7314            "opencode": root.join("__absent__"),
7315            "pi": root.join("__absent__"),
7316            "hermes": root.join("__absent__"),
7317            "openclaw": root.join("__absent__"),
7318            "agents": root.join("__absent__"),
7319        });
7320
7321        let mut service = HarnessSessionService::new();
7322        let installed = service.handle(request(
7323            1,
7324            "harness.v1.skills.install",
7325            json!({
7326                "harness": HarnessId::CLAUDE_CODE,
7327                "source": source,
7328                "scope": "user",
7329                "cwd": root,
7330                "homes": homes,
7331            }),
7332        ));
7333        let result = &installed["result"];
7334        assert_eq!(result["name"], json!("orch22-rpc"), "{installed:#}");
7335        assert_eq!(result["verb"], json!("install"));
7336        assert!(result["ran"]
7337            .as_str()
7338            .is_some_and(|ran| ran.starts_with("cp -R ")));
7339        assert_eq!(result["skill"]["scope"], json!("user"));
7340
7341        let removed = service.handle(request(
7342            2,
7343            "harness.v1.skills.remove",
7344            json!({
7345                "harness": HarnessId::CLAUDE_CODE,
7346                "name": "orch22-rpc",
7347                "scope": "user",
7348                "cwd": root,
7349                "homes": homes,
7350            }),
7351        ));
7352        assert_eq!(removed["result"]["removed"], json!(true), "{removed:#}");
7353        assert!(!root.join("claude_home/skills/orch22-rpc").exists());
7354        std::fs::remove_dir_all(&root).ok();
7355    }
7356
7357    /// OpenClaw publishes no `skills remove` at the pin, so the uniform verb
7358    /// refuses with UnsupportedAction instead of deleting files itself.
7359    #[test]
7360    fn skills_remove_refuses_openclaw_at_the_pin() {
7361        let response = HarnessSessionService::new().handle(request(
7362            1,
7363            "harness.v1.skills.remove",
7364            json!({"harness": HarnessId::OPENCLAW, "name": "clawhub-demo"}),
7365        ));
7366        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7367        assert!(response["error"]["message"]
7368            .as_str()
7369            .unwrap()
7370            .contains("no `skills remove` verb"));
7371    }
7372
7373    /// A harness with no skills root at all is refused by name, with the
7374    /// same sentence `skills.list` gives it.
7375    #[test]
7376    fn skills_install_refuses_a_harness_without_a_skills_root() {
7377        let response = HarnessSessionService::new().handle(request(
7378            1,
7379            "harness.v1.skills.install",
7380            json!({"harness": "not-a-harness", "source": "/tmp/x"}),
7381        ));
7382        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7383        assert!(response["error"]["message"]
7384            .as_str()
7385            .unwrap()
7386            .contains("not-a-harness"));
7387    }
7388
7389    // ---- ORCH-12: `harness.v1.memory.show|search` ------------------------
7390
7391    /// `HarnessHomes` for the committed fixture homes. Every root a test does
7392    /// not name is pinned at an absent path, so a read can never fall through
7393    /// to this machine's real harness homes. Note `hermes` is the `state.db`
7394    /// PATH (its parent is HERMES_HOME) and `claude_code` is the `projects`
7395    /// directory — the same contract discovery uses.
7396    fn memory_homes() -> Value {
7397        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7398        json!({
7399            "claude_code": fixtures.join("__absent__"),
7400            "codex": fixtures.join("__absent__"),
7401            "opencode": fixtures.join("__absent__"),
7402            "pi": fixtures.join("__absent__"),
7403            "grok": fixtures.join("__absent__"),
7404            "gemini": fixtures.join("__absent__"),
7405            "goose": fixtures.join("__absent__"),
7406            "supercode": fixtures.join("__absent__"),
7407            "hermes": fixtures.join("hermes_home/state.db"),
7408            "openclaw": fixtures.join("openclaw_home"),
7409        })
7410    }
7411
7412    fn memory_call_ok(method: &str, params: Value, key: &str) -> Vec<Value> {
7413        let response = HarnessSessionService::new().handle(request(1, method, params));
7414        assert!(response.get("error").is_none(), "{response:#}");
7415        assert_eq!(response["result"]["schema"], json!("supercode.memory.v1"));
7416        response["result"][key]
7417            .as_array()
7418            .cloned()
7419            .unwrap_or_default()
7420    }
7421
7422    fn memory_documents(params: Value) -> Vec<Value> {
7423        memory_call_ok("harness.v1.memory.show", params, "documents")
7424    }
7425
7426    fn memory_matches(params: Value) -> Vec<Value> {
7427        memory_call_ok("harness.v1.memory.search", params, "matches")
7428    }
7429
7430    fn find_document<'a>(rows: &'a [Value], profile: &str, name: &str) -> &'a Value {
7431        rows.iter()
7432            .find(|row| row["profile"] == profile && row["name"] == name)
7433            .unwrap_or_else(|| panic!("no `{profile}` document `{name}` in {rows:#?}"))
7434    }
7435
7436    /// Hermes: the built-in `MEMORY.md`/`USER.md` pair and the `memories/`
7437    /// topic files, for HERMES_HOME itself and for every profile home.
7438    #[test]
7439    fn memory_show_reads_the_hermes_profile_homes() {
7440        let rows = memory_documents(json!({"harness": "hermes", "homes": memory_homes()}));
7441
7442        let notes = find_document(&rows, "default", "MEMORY.md");
7443        assert_eq!(notes["harness"], "hermes");
7444        assert_eq!(notes["scope"], "user");
7445        assert!(notes["size"].as_u64().unwrap() > 0);
7446        assert!(notes["updated_at"].is_string(), "{notes:#?}");
7447        // The default answer previews the head and never the whole body.
7448        assert!(notes.get("content").is_none(), "{notes:#?}");
7449        assert_eq!(notes["truncated"], true);
7450        assert_eq!(notes["preview"].as_array().unwrap().len(), 5);
7451
7452        let user = find_document(&rows, "default", "USER.md");
7453        assert_eq!(user["scope"], "user");
7454        assert!(user["preview"]
7455            .as_array()
7456            .unwrap()
7457            .iter()
7458            .any(|line| line.as_str().unwrap().contains("neovim")));
7459
7460        let topic = find_document(&rows, "default", "memories/2026-09-01-notes.md");
7461        assert!(topic["path"]
7462            .as_str()
7463            .unwrap()
7464            .ends_with("hermes_home/memories/2026-09-01-notes.md"));
7465
7466        // Profile mode points HERMES_HOME at `<root>/profiles/<name>`.
7467        let coder = find_document(&rows, "coder", "MEMORY.md");
7468        assert_eq!(coder["scope"], "profile");
7469        assert!(coder["path"]
7470            .as_str()
7471            .unwrap()
7472            .ends_with("hermes_home/profiles/coder/MEMORY.md"));
7473    }
7474
7475    /// `full` is the only way a body crosses the wire, and `profile` narrows
7476    /// the read to one home.
7477    #[test]
7478    fn memory_show_returns_bodies_only_under_full_and_narrows_by_profile() {
7479        let rows = memory_documents(json!({
7480            "harness": "hermes",
7481            "profile": "coder",
7482            "full": true,
7483            "homes": memory_homes(),
7484        }));
7485        assert!(
7486            rows.iter().all(|row| row["profile"] == "coder"),
7487            "{rows:#?}"
7488        );
7489        let coder = find_document(&rows, "coder", "MEMORY.md");
7490        assert!(coder["content"]
7491            .as_str()
7492            .expect("full returns the body")
7493            .contains("anthropic/claude-opus-4-8"));
7494    }
7495
7496    /// OpenClaw: memory-core's files under each agent's workspace —
7497    /// `<state>/workspace` for the default agent, `<state>/workspace-<id>`
7498    /// for any other.
7499    #[test]
7500    fn memory_show_reads_the_openclaw_agent_workspaces() {
7501        let rows = memory_documents(json!({"harness": "openclaw", "homes": memory_homes()}));
7502
7503        let main = find_document(&rows, "main", "MEMORY.md");
7504        assert_eq!(main["scope"], "agent");
7505        assert!(main["path"]
7506            .as_str()
7507            .unwrap()
7508            .ends_with("openclaw_home/workspace/MEMORY.md"));
7509
7510        let topic = find_document(&rows, "main", "memory/2026-09-01-standup.md");
7511        assert!(topic["path"]
7512            .as_str()
7513            .unwrap()
7514            .ends_with("openclaw_home/workspace/memory/2026-09-01-standup.md"));
7515
7516        let design = find_document(&rows, "design", "MEMORY.md");
7517        assert!(design["path"]
7518            .as_str()
7519            .unwrap()
7520            .ends_with("openclaw_home/workspace-design/MEMORY.md"));
7521    }
7522
7523    /// Claude Code: the auto-memory directory of the project the working tree
7524    /// belongs to, keyed by the enclosing git repository.
7525    #[test]
7526    fn memory_show_reads_a_claude_code_project_auto_memory_directory() {
7527        let scratch = std::env::temp_dir().join(format!(
7528            "supercode-orch12-cc-{}-{}",
7529            std::process::id(),
7530            std::time::SystemTime::now()
7531                .duration_since(std::time::UNIX_EPOCH)
7532                .unwrap()
7533                .as_nanos()
7534        ));
7535        let project = scratch.join("repo");
7536        std::fs::create_dir_all(project.join(".git")).unwrap();
7537        // Auto-memory is shared across a repo's worktrees, so a nested
7538        // working directory must resolve to the repo's own project dir.
7539        let worktree = project.join("crates/harness");
7540        std::fs::create_dir_all(&worktree).unwrap();
7541        let slug: String = project
7542            .to_string_lossy()
7543            .chars()
7544            .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
7545            .collect();
7546        let projects = scratch.join("claude/projects");
7547        let memory = projects.join(&slug).join("memory");
7548        std::fs::create_dir_all(&memory).unwrap();
7549        std::fs::write(
7550            memory.join("MEMORY.md"),
7551            "# index\n- [build box](build-box.md) — the pinned harnesses\n",
7552        )
7553        .unwrap();
7554        std::fs::write(
7555            memory.join("build-box.md"),
7556            "hermes 0.21.0 and openclaw 2026.7.1-2 are the pins\n",
7557        )
7558        .unwrap();
7559
7560        let mut homes = memory_homes();
7561        homes["claude_code"] = json!(projects);
7562        let rows = memory_documents(json!({
7563            "harness": "claude-code",
7564            "cwd": worktree,
7565            "homes": homes,
7566        }));
7567        let index = find_document(&rows, &slug, "MEMORY.md");
7568        assert_eq!(index["harness"], "claude-code");
7569        assert_eq!(index["scope"], "project");
7570        let topic = find_document(&rows, &slug, "build-box.md");
7571        assert!(topic["preview"]
7572            .as_array()
7573            .unwrap()
7574            .iter()
7575            .any(|line| line.as_str().unwrap().contains("2026.7.1-2")));
7576
7577        let hits = memory_matches(json!({
7578            "harness": "claude-code",
7579            "query": "pinned harnesses",
7580            "cwd": worktree,
7581            "homes": homes,
7582        }));
7583        assert_eq!(hits.len(), 1, "{hits:#?}");
7584        assert_eq!(hits[0]["name"], "MEMORY.md");
7585        assert_eq!(hits[0]["line"], 2);
7586
7587        let _ = std::fs::remove_dir_all(&scratch);
7588    }
7589
7590    /// A config-less OpenClaw install declares no default agent, but
7591    /// memory-core still resolves ONE agent to the default `workspace`
7592    /// directory — the same `main`-then-first convention the profile rows
7593    /// use. Measured against `openclaw memory status` on the pinned CLI
7594    /// (`docs/interop/research/orch12-memory-receipt-2026-09-03.json`).
7595    #[test]
7596    fn memory_show_resolves_the_default_workspace_without_an_openclaw_config() {
7597        let state = std::env::temp_dir().join(format!(
7598            "supercode-orch12-oc-{}-{}",
7599            std::process::id(),
7600            std::time::SystemTime::now()
7601                .duration_since(std::time::UNIX_EPOCH)
7602                .unwrap()
7603                .as_nanos()
7604        ));
7605        // No `openclaw.json`: only the agent home the gateway creates.
7606        std::fs::create_dir_all(state.join("agents/main/agent")).unwrap();
7607        std::fs::create_dir_all(state.join("workspace")).unwrap();
7608        std::fs::write(
7609            state.join("workspace/MEMORY.md"),
7610            "the gateway websocket needs credentials\n",
7611        )
7612        .unwrap();
7613
7614        let mut homes = memory_homes();
7615        homes["openclaw"] = json!(state);
7616        let rows = memory_documents(json!({"harness": "openclaw", "homes": homes}));
7617        assert_eq!(rows.len(), 1, "{rows:#?}");
7618        let row = find_document(&rows, "main", "MEMORY.md");
7619        assert_eq!(row["scope"], "agent");
7620        assert!(row["path"]
7621            .as_str()
7622            .unwrap()
7623            .ends_with("workspace/MEMORY.md"));
7624
7625        let _ = std::fs::remove_dir_all(&state);
7626    }
7627
7628    /// Search is a plain scan over the same documents: a hit carries the
7629    /// path, line and excerpt; a miss is an empty list, not an error.
7630    #[test]
7631    fn memory_search_reports_hits_by_line_and_misses_as_empty() {
7632        let hit = memory_matches(json!({
7633            "harness": "hermes",
7634            "query": "NEOVIM",
7635            "homes": memory_homes(),
7636        }));
7637        assert_eq!(hit.len(), 1, "{hit:#?}");
7638        assert_eq!(hit[0]["harness"], "hermes");
7639        assert_eq!(hit[0]["name"], "USER.md");
7640        assert_eq!(hit[0]["scope"], "user");
7641        assert_eq!(hit[0]["line"], 5);
7642        assert!(hit[0]["excerpt"].as_str().unwrap().contains("neovim"));
7643
7644        // A regular expression reaches the same lines.
7645        let regex = memory_matches(json!({
7646            "harness": "hermes",
7647            "query": "neo(vim|vi)",
7648            "regex": true,
7649            "homes": memory_homes(),
7650        }));
7651        assert_eq!(regex.len(), 1, "{regex:#?}");
7652
7653        let miss = memory_matches(json!({
7654            "harness": "hermes",
7655            "query": "no-memory-line-says-this",
7656            "homes": memory_homes(),
7657        }));
7658        assert!(miss.is_empty(), "{miss:#?}");
7659    }
7660
7661    /// The uniform-verb contract: a harness with no memory store at the pin
7662    /// is refused by name, and `session` only selects a Claude Code project.
7663    #[test]
7664    fn memory_refuses_harnesses_without_a_store_and_misplaced_session_scoping() {
7665        for method in ["harness.v1.memory.show", "harness.v1.memory.search"] {
7666            let response = HarnessSessionService::new().handle(request(
7667                1,
7668                method,
7669                json!({"harness": "codex", "query": "anything", "homes": memory_homes()}),
7670            ));
7671            assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7672            assert!(response["error"]["message"]
7673                .as_str()
7674                .unwrap()
7675                .contains("codex"));
7676        }
7677
7678        let response = HarnessSessionService::new().handle(request(
7679            1,
7680            "harness.v1.memory.show",
7681            json!({"harness": "hermes", "session": "abc", "homes": memory_homes()}),
7682        ));
7683        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7684
7685        // `harness` is not optional: memory documents are the user's prose.
7686        let response = HarnessSessionService::new().handle(request(
7687            1,
7688            "harness.v1.memory.show",
7689            json!({"homes": memory_homes()}),
7690        ));
7691        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7692    }
7693
7694    /// Both methods are advertised, and their SDK operations resolve them.
7695    #[test]
7696    fn memory_methods_are_advertised_and_map_to_sdk_operations() {
7697        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.show"));
7698        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.search"));
7699        assert_eq!(
7700            SdkOperation::from_method("harness.v1.memory.show"),
7701            Some(SdkOperation::MemoryShow)
7702        );
7703        assert_eq!(
7704            SdkOperation::from_method("harness.v1.memory.search"),
7705            Some(SdkOperation::MemorySearch)
7706        );
7707    }
7708
7709    // ---- ORCH-9: `harness.v1.approvals.list` -----------------------------
7710
7711    /// A runtime that raises one protocol request and then goes quiet, so a
7712    /// single poll delivers the request without closing the connection.
7713    struct RequestingRuntime {
7714        handle: RuntimeHandle,
7715        events: std::collections::VecDeque<HarnessEvent>,
7716        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7717    }
7718
7719    #[async_trait]
7720    impl RuntimeConnection for RequestingRuntime {
7721        fn handle(&self) -> &RuntimeHandle {
7722            &self.handle
7723        }
7724
7725        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
7726            unreachable!("this runtime only raises requests")
7727        }
7728
7729        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
7730            match self.events.pop_front() {
7731                Some(event) => Ok(Some(event)),
7732                // Quiet, not closed: `poll_sdk_events` times out and leaves
7733                // the connection open, the way a runtime blocked on a
7734                // permission request behaves.
7735                None => std::future::pending().await,
7736            }
7737        }
7738
7739        async fn interrupt(&mut self) -> crate::Result<()> {
7740            Ok(())
7741        }
7742
7743        async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
7744            // Both halves are recorded: ORCH-20 has to prove not just that the
7745            // right request was answered but that the door received its own
7746            // reply envelope.
7747            self.answered
7748                .lock()
7749                .unwrap_or_else(std::sync::PoisonError::into_inner)
7750                .push(json!({"request_id": request_id, "response": response}));
7751            Ok(())
7752        }
7753
7754        async fn close(&mut self) -> crate::Result<()> {
7755            Ok(())
7756        }
7757    }
7758
7759    fn requesting_runtime(
7760        harness: &str,
7761        events: Vec<HarnessEvent>,
7762        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7763    ) -> Box<dyn RuntimeConnection> {
7764        requesting_runtime_named(harness, "hermes-live-session", events, answered)
7765    }
7766
7767    fn requesting_runtime_named(
7768        harness: &str,
7769        runtime_id: &str,
7770        events: Vec<HarnessEvent>,
7771        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7772    ) -> Box<dyn RuntimeConnection> {
7773        Box::new(RequestingRuntime {
7774            handle: RuntimeHandle {
7775                harness: HarnessId::from(harness),
7776                runtime_id: runtime_id.into(),
7777                endpoint: RuntimeEndpoint::LocalProcess {
7778                    pid: None,
7779                    command: vec!["hermes-acp".into()],
7780                    protocol: "acp".into(),
7781                },
7782            },
7783            events: events.into(),
7784            answered,
7785        })
7786    }
7787
7788    fn permission_event(id: u64, title: &str) -> HarnessEvent {
7789        HarnessEvent {
7790            sequence: None,
7791            kind: "session/request_permission".into(),
7792            payload: json!({
7793                "jsonrpc": "2.0",
7794                "id": id,
7795                "method": "session/request_permission",
7796                "params": {
7797                    "sessionId": "hermes-live-session",
7798                    "toolCall": {"toolCallId": "call-1", "title": title, "kind": "execute"},
7799                    "options": [
7800                        {"optionId": "allow_once", "name": "Allow once", "kind": "allow_once"},
7801                        {"optionId": "allow_for_session", "name": "Allow for session", "kind": "allow_always"},
7802                        {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
7803                    ],
7804                },
7805            }),
7806        }
7807    }
7808
7809    fn approvals(service: &mut HarnessSessionService, params: Value) -> Value {
7810        let response = service.handle(request(1, "harness.v1.approvals.list", params));
7811        assert!(response.get("error").is_none(), "{response:#}");
7812        response["result"].clone()
7813    }
7814
7815    /// ORC-2 dev/01: the same uniform loop over the CLAUDE CODE door. The
7816    /// `can_use_tool` control request the CLI raises to its registered
7817    /// permission handler lists as one pending row, `approvals.resolve <id>
7818    /// allow_once` sends the `{behavior}` result the CLI accepts through
7819    /// `runtimes.respond`, and the row is gone. The frame is the one claude
7820    /// 2.1.258 wrote, transcribed from
7821    /// `docs/interop/research/orc2-claude-respond-receipt-2026-09-04.json`.
7822    #[tokio::test]
7823    async fn a_claude_code_permission_request_lists_and_resolves_on_the_uniform_door() {
7824        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7825        let mut service = HarnessSessionService::new();
7826        service.runtimes.insert(
7827            "runtime-cc".into(),
7828            requesting_runtime_named(
7829                HarnessId::CLAUDE_CODE,
7830                "claude-live-session",
7831                vec![HarnessEvent {
7832                    sequence: None,
7833                    kind: "control_request".into(),
7834                    payload: json!({
7835                        "type": "control_request",
7836                        "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
7837                        "request": {
7838                            "subtype": "can_use_tool",
7839                            "tool_name": "Bash",
7840                            "display_name": "Bash",
7841                            "input": {"command": "touch probe-artifact.txt"},
7842                            "tool_use_id": "toolu_mock_1",
7843                        },
7844                    }),
7845                }],
7846                answered.clone(),
7847            ),
7848        );
7849
7850        let notifications = service.poll_runtimes().await;
7851        assert_eq!(notifications.len(), 1, "{notifications:#?}");
7852
7853        let rows = approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}));
7854        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
7855        let row = &rows[0];
7856        assert_eq!(row["id"], "runtime-cc/053f8a2d-3445-4011-a259-4261b31c7326");
7857        assert_eq!(row["harness"], HarnessId::CLAUDE_CODE);
7858        assert_eq!(row["status"], "pending");
7859        assert_eq!(row["subject"], "Bash touch probe-artifact.txt");
7860        assert_eq!(row["runtime_id"], "claude-live-session");
7861        assert_eq!(
7862            row["options"]
7863                .as_array()
7864                .unwrap()
7865                .iter()
7866                .map(|option| option["id"].as_str().unwrap())
7867                .collect::<Vec<_>>(),
7868            vec!["allow", "deny"],
7869        );
7870
7871        let response = resolve(
7872            &mut service,
7873            json!({"id": row["id"], "decision": "allow_once"}),
7874        )
7875        .await;
7876        assert!(response.get("error").is_none(), "{response:#}");
7877        assert_eq!(response["result"]["option_id"], "allow");
7878        assert_eq!(
7879            answered
7880                .lock()
7881                .unwrap_or_else(std::sync::PoisonError::into_inner)
7882                .as_slice(),
7883            &[json!({
7884                "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
7885                "response": {"behavior": "allow"},
7886            })],
7887        );
7888        assert_eq!(
7889            approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}))
7890                .as_array()
7891                .map(Vec::len),
7892            Some(0),
7893        );
7894    }
7895
7896    /// dev/01: a live ACP permission request raised on a driven runtime is
7897    /// listable while the turn is blocked on it, and stops being listable
7898    /// the moment `runtimes.respond` answers it.
7899    #[tokio::test]
7900    async fn a_live_permission_request_lists_until_it_is_answered() {
7901        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7902        let mut service = HarnessSessionService::new();
7903        service.runtimes.insert(
7904            "runtime-1".into(),
7905            requesting_runtime(
7906                HarnessId::HERMES,
7907                vec![permission_event(7, "rm -rf build")],
7908                answered.clone(),
7909            ),
7910        );
7911
7912        let notifications = service.poll_runtimes().await;
7913        assert_eq!(notifications.len(), 1, "{notifications:#?}");
7914
7915        let rows = approvals(&mut service, json!({}));
7916        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
7917        let row = &rows[0];
7918        assert_eq!(row["id"], "runtime-1/7");
7919        assert_eq!(row["harness"], HarnessId::HERMES);
7920        assert_eq!(row["kind"], "live");
7921        assert_eq!(row["status"], "pending");
7922        assert_eq!(row["subject"], "rm -rf build");
7923        assert_eq!(row["session_id"], "hermes-live-session");
7924        assert_eq!(row["runtime_id"], "hermes-live-session");
7925        assert!(row["requested_at_ms"].as_i64().is_some(), "{row:#}");
7926        assert!(
7927            row["age_ms"].as_i64().is_some_and(|age| age >= 0),
7928            "{row:#}"
7929        );
7930        assert_eq!(
7931            row["options"]
7932                .as_array()
7933                .unwrap()
7934                .iter()
7935                .map(|option| option["id"].as_str().unwrap())
7936                .collect::<Vec<_>>(),
7937            vec!["allow_once", "allow_for_session", "deny"],
7938        );
7939
7940        // The filters select against the same rows.
7941        assert_eq!(
7942            approvals(&mut service, json!({"harness": HarnessId::HERMES}))
7943                .as_array()
7944                .map(Vec::len),
7945            Some(1),
7946        );
7947        assert_eq!(
7948            approvals(&mut service, json!({"session": "some-other-session"}))
7949                .as_array()
7950                .map(Vec::len),
7951            Some(0),
7952        );
7953
7954        let response = service
7955            .handle_async(request(
7956                2,
7957                "harness.v1.runtimes.respond",
7958                json!({
7959                    "connection": "runtime-1",
7960                    "request_id": 7,
7961                    "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7962                }),
7963            ))
7964            .await;
7965        assert!(response.get("error").is_none(), "{response:#}");
7966        assert_eq!(
7967            answered
7968                .lock()
7969                .unwrap_or_else(std::sync::PoisonError::into_inner)
7970                .as_slice(),
7971            &[json!({
7972                "request_id": 7,
7973                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7974            })],
7975        );
7976
7977        let rows = approvals(&mut service, json!({}));
7978        assert_eq!(rows.as_array().map(Vec::len), Some(0), "{rows:#}");
7979    }
7980
7981    /// dev/01: supercode's own queued subagent approvals list through the
7982    /// same door, carrying the outcome the record holds.
7983    #[test]
7984    fn queued_subagent_approvals_list_through_the_same_door() {
7985        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
7986            crate::subagents::QueuedApproval {
7987                child_agent_id: "child-7".into(),
7988                tool: "shell".into(),
7989                subject: Some("cargo publish --dry-run".into()),
7990                queued_at_ms: 1,
7991                outcome: None,
7992            },
7993            crate::subagents::QueuedApproval {
7994                child_agent_id: "child-8".into(),
7995                tool: "write_file".into(),
7996                subject: None,
7997                queued_at_ms: 2,
7998                outcome: Some(crate::subagents::QueuedApprovalOutcome::Denied),
7999            },
8000        ]));
8001        let mut service = HarnessSessionService::new();
8002        service.observe_subagent_approvals(queue);
8003
8004        let rows = approvals(&mut service, json!({}));
8005        assert_eq!(rows.as_array().map(Vec::len), Some(2), "{rows:#}");
8006        assert_eq!(rows[0]["id"], "supercode/subagent/child-7/1/0");
8007        assert_eq!(rows[0]["harness"], HarnessId::SUPERCODE);
8008        assert_eq!(rows[0]["status"], "pending");
8009        assert_eq!(rows[0]["subject"], "shell cargo publish --dry-run");
8010        assert_eq!(rows[1]["status"], "denied");
8011        assert!(rows[1]["options"].as_array().unwrap().is_empty());
8012
8013        // `--session` addresses a subagent row by its child agent id.
8014        let only = approvals(&mut service, json!({"session": "child-8"}));
8015        assert_eq!(only.as_array().map(Vec::len), Some(1), "{only:#}");
8016        assert_eq!(only[0]["id"], "supercode/subagent/child-8/2/1");
8017    }
8018
8019    /// The uniform-verb contract: an id whose runtime door cannot carry a
8020    /// protocol request is refused BY NAME rather than answered with an empty
8021    /// list. Since ORC-2 gave Claude Code a permission-response primitive
8022    /// every registered harness can carry one, so the refusal is exercised on
8023    /// an unknown id — and the registered ids are asserted to be accepted.
8024    #[test]
8025    fn approvals_list_refuses_a_harness_that_cannot_carry_a_request() {
8026        let response = HarnessSessionService::new().handle(request(
8027            1,
8028            "harness.v1.approvals.list",
8029            json!({"harness": "not-a-harness"}),
8030        ));
8031        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
8032        assert!(response["error"]["message"]
8033            .as_str()
8034            .unwrap()
8035            .contains("not-a-harness"));
8036        for harness in [HarnessId::CLAUDE_CODE, HarnessId::CODEX] {
8037            let response = HarnessSessionService::new().handle(request(
8038                1,
8039                "harness.v1.approvals.list",
8040                json!({"harness": harness}),
8041            ));
8042            assert!(response.get("error").is_none(), "{harness}: {response:#}");
8043        }
8044    }
8045
8046    /// The method is advertised, its SDK operation resolves it, and the
8047    /// registry reports the concept as observed for every harness whose
8048    /// runtime door can carry a request.
8049    #[test]
8050    fn approvals_list_is_an_advertised_method_and_an_observed_tier() {
8051        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.list"));
8052        assert_eq!(
8053            SdkOperation::from_method("harness.v1.approvals.list"),
8054            Some(SdkOperation::ApprovalsList)
8055        );
8056        let registry = harness_support_registry();
8057        for id in [
8058            HarnessId::HERMES,
8059            HarnessId::OPENCLAW,
8060            HarnessId::CODEX,
8061            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8062            // pending_request concept joins the other driven doors.
8063            HarnessId::CLAUDE_CODE,
8064        ] {
8065            let concept = registry
8066                .harnesses
8067                .iter()
8068                .find(|harness| harness.id.as_str() == id)
8069                .unwrap()
8070                .orchestration
8071                .concepts
8072                .iter()
8073                .find(|concept| concept.concept == "pending_request")
8074                .unwrap();
8075            assert_eq!(concept.observed, crate::ImplementationKind::BuiltIn, "{id}");
8076            assert!(concept
8077                .methods
8078                .iter()
8079                .any(|method| method == "harness.v1.approvals.list"));
8080        }
8081    }
8082
8083    // ---- ORCH-20: `harness.v1.approvals.resolve` -------------------------
8084
8085    async fn resolve(service: &mut HarnessSessionService, params: Value) -> Value {
8086        service
8087            .handle_async(request(3, "harness.v1.approvals.resolve", params))
8088            .await
8089    }
8090
8091    /// dev/01: the whole loop on a driven runtime — list one pending row,
8092    /// answer it by ROW ID with one uniform decision, and see it gone. The
8093    /// door receives its own ACP envelope carrying the option it enumerated.
8094    #[tokio::test]
8095    async fn a_listed_row_resolves_with_one_uniform_decision_and_then_is_gone() {
8096        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8097        let mut service = HarnessSessionService::new();
8098        service.runtimes.insert(
8099            "runtime-1".into(),
8100            requesting_runtime(
8101                HarnessId::HERMES,
8102                vec![permission_event(7, "rm -rf build")],
8103                answered.clone(),
8104            ),
8105        );
8106        service.poll_runtimes().await;
8107
8108        let rows = approvals(&mut service, json!({}));
8109        assert_eq!(rows[0]["id"], "runtime-1/7");
8110
8111        let response = resolve(
8112            &mut service,
8113            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8114        )
8115        .await;
8116        assert!(response.get("error").is_none(), "{response:#}");
8117        assert_eq!(
8118            response["result"],
8119            json!({
8120                "id": "runtime-1/7",
8121                "decision": "allow_once",
8122                "option_id": "allow_once",
8123                "resolved": true,
8124            }),
8125        );
8126        // The harness's own door was called with its own envelope.
8127        assert_eq!(
8128            answered
8129                .lock()
8130                .unwrap_or_else(std::sync::PoisonError::into_inner)
8131                .as_slice(),
8132            &[json!({
8133                "request_id": 7,
8134                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8135            })],
8136        );
8137        // And the row is gone, the same way `runtimes.respond` drops it.
8138        assert_eq!(
8139            approvals(&mut service, json!({})).as_array().map(Vec::len),
8140            Some(0),
8141        );
8142        // Answering it twice is an honest miss, not a silent success.
8143        let response = resolve(
8144            &mut service,
8145            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8146        )
8147        .await;
8148        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8149    }
8150
8151    /// dev/01: deny travels the same path and picks the option the request
8152    /// itself classified as a refusal.
8153    #[tokio::test]
8154    async fn deny_selects_the_requests_own_reject_option() {
8155        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8156        let mut service = HarnessSessionService::new();
8157        service.runtimes.insert(
8158            "runtime-1".into(),
8159            requesting_runtime(
8160                HarnessId::HERMES,
8161                vec![permission_event(11, "git push --force")],
8162                answered.clone(),
8163            ),
8164        );
8165        service.poll_runtimes().await;
8166
8167        let response = resolve(
8168            &mut service,
8169            json!({"id": "runtime-1/11", "decision": "deny"}),
8170        )
8171        .await;
8172        assert!(response.get("error").is_none(), "{response:#}");
8173        // `deny` is the optionId whose ACP `kind` is `reject_once`.
8174        assert_eq!(response["result"]["option_id"], "deny");
8175        assert_eq!(
8176            answered
8177                .lock()
8178                .unwrap_or_else(std::sync::PoisonError::into_inner)[0]["response"],
8179            json!({"outcome": {"outcome": "selected", "optionId": "deny"}}),
8180        );
8181        assert_eq!(
8182            approvals(&mut service, json!({})).as_array().map(Vec::len),
8183            Some(0),
8184        );
8185    }
8186
8187    /// dev/01: a decision this request does not offer is refused by name,
8188    /// listing the ones it does — never silently downgraded to a neighbour.
8189    #[tokio::test]
8190    async fn a_decision_the_request_does_not_offer_is_refused_with_the_offered_ones() {
8191        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8192        let mut service = HarnessSessionService::new();
8193        let mut event = permission_event(3, "rm -rf build");
8194        // A request offering only allow-once and deny, as hermes 0.21.0's
8195        // edit-approval layer raises one.
8196        event.payload["params"]["options"] = json!([
8197            {"optionId": "allow_once", "name": "Allow edit", "kind": "allow_once"},
8198            {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
8199        ]);
8200        service.runtimes.insert(
8201            "runtime-1".into(),
8202            requesting_runtime(HarnessId::HERMES, vec![event], answered.clone()),
8203        );
8204        service.poll_runtimes().await;
8205
8206        let response = resolve(
8207            &mut service,
8208            json!({"id": "runtime-1/3", "decision": "allow_always"}),
8209        )
8210        .await;
8211        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8212        let message = response["error"]["message"].as_str().unwrap();
8213        assert!(message.contains("allow_always"), "{message}");
8214        assert!(message.contains("allow_once, deny"), "{message}");
8215        // Nothing was sent, and the request is still waiting for an answer.
8216        assert!(answered
8217            .lock()
8218            .unwrap_or_else(std::sync::PoisonError::into_inner)
8219            .is_empty());
8220        assert_eq!(
8221            approvals(&mut service, json!({})).as_array().map(Vec::len),
8222            Some(1),
8223        );
8224    }
8225
8226    /// dev/01: supercode's own queued subagent row is addressable but not
8227    /// answerable through this door — it is the parent's audit copy of a
8228    /// request its own handler answers. Refused by name, never a no-op.
8229    #[tokio::test]
8230    async fn a_queued_subagent_row_is_refused_by_name_rather_than_silently_answered() {
8231        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
8232            crate::subagents::QueuedApproval {
8233                child_agent_id: "child-7".into(),
8234                tool: "shell".into(),
8235                subject: Some("cargo publish --dry-run".into()),
8236                queued_at_ms: 1,
8237                outcome: None,
8238            },
8239        ]));
8240        let mut service = HarnessSessionService::new();
8241        service.observe_subagent_approvals(queue.clone());
8242        let row = approvals(&mut service, json!({}))[0]["id"]
8243            .as_str()
8244            .unwrap()
8245            .to_string();
8246        assert_eq!(row, "supercode/subagent/child-7/1/0");
8247
8248        let response = resolve(&mut service, json!({"id": row, "decision": "allow_once"})).await;
8249        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8250        let message = response["error"]["message"].as_str().unwrap();
8251        assert!(message.contains("queued subagent record"), "{message}");
8252        assert!(message.contains("request"), "{message}");
8253        // The audit record is untouched: nothing pretended to answer it.
8254        assert!(queue
8255            .lock()
8256            .unwrap_or_else(std::sync::PoisonError::into_inner)[0]
8257            .outcome
8258            .is_none());
8259    }
8260
8261    /// An id nobody is holding, and a call that names no decision at all,
8262    /// both fail with a message that says why.
8263    #[tokio::test]
8264    async fn an_unknown_row_and_a_missing_decision_are_both_named() {
8265        let mut service = HarnessSessionService::new();
8266        let response = resolve(
8267            &mut service,
8268            json!({"id": "runtime-9/4", "decision": "deny"}),
8269        )
8270        .await;
8271        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8272        assert!(response["error"]["message"]
8273            .as_str()
8274            .unwrap()
8275            .contains("runtime-9/4"));
8276
8277        let response = resolve(&mut service, json!({"id": "runtime-9/4"})).await;
8278        let message = response["error"]["message"].as_str().unwrap();
8279        assert!(
8280            message.contains("allow_once | allow_always | deny"),
8281            "{message}"
8282        );
8283
8284        let response = resolve(
8285            &mut service,
8286            json!({"id": "runtime-9/4", "decision": "deny", "option_id": "deny"}),
8287        )
8288        .await;
8289        assert!(response["error"]["message"]
8290            .as_str()
8291            .unwrap()
8292            .contains("not both"));
8293    }
8294
8295    /// The method is advertised, its SDK operation resolves it, and every
8296    /// harness whose runtime door can carry a request reports it on the
8297    /// CONTROLLED tier beside `runtimes.respond`.
8298    #[test]
8299    fn approvals_resolve_is_an_advertised_method_and_a_controlled_tier() {
8300        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.resolve"));
8301        assert_eq!(
8302            SdkOperation::from_method("harness.v1.approvals.resolve"),
8303            Some(SdkOperation::ApprovalsResolve)
8304        );
8305        assert_eq!(
8306            SdkOperation::ApprovalsResolve.action_name(),
8307            "approvals_resolve"
8308        );
8309        let registry = harness_support_registry();
8310        for id in [
8311            HarnessId::HERMES,
8312            HarnessId::OPENCLAW,
8313            HarnessId::CODEX,
8314            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8315            // pending_request concept joins the other driven doors.
8316            HarnessId::CLAUDE_CODE,
8317        ] {
8318            let concept = registry
8319                .harnesses
8320                .iter()
8321                .find(|harness| harness.id.as_str() == id)
8322                .unwrap()
8323                .orchestration
8324                .concepts
8325                .iter()
8326                .find(|concept| concept.concept == "pending_request")
8327                .unwrap();
8328            assert_eq!(
8329                concept.controlled,
8330                crate::ImplementationKind::BuiltIn,
8331                "{id}"
8332            );
8333            assert!(
8334                concept
8335                    .methods
8336                    .iter()
8337                    .any(|method| method == "harness.v1.approvals.resolve"),
8338                "{id}"
8339            );
8340        }
8341    }
8342
8343    #[test]
8344    fn capabilities_are_explicit_and_versioned() {
8345        let mut service = HarnessSessionService::new();
8346        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
8347        assert_eq!(response["result"]["version"], HARNESS_SERVICE_VERSION);
8348        assert_eq!(
8349            response["result"]["sdk"]["schema_version"],
8350            crate::SDK_SCHEMA_VERSION
8351        );
8352        assert_eq!(
8353            response["result"]["sdk"]["operations"]
8354                .as_array()
8355                .unwrap()
8356                .len(),
8357            SdkOperation::ALL.len()
8358        );
8359        assert_eq!(
8360            response["result"]["harnesses"].as_array().unwrap().len(),
8361            11
8362        );
8363        assert!(response["result"]["harnesses"]
8364            .as_array()
8365            .unwrap()
8366            .iter()
8367            .any(|harness| harness == HarnessId::GROK));
8368        assert!(response["result"]["harnesses"]
8369            .as_array()
8370            .unwrap()
8371            .iter()
8372            .any(|harness| harness == HarnessId::GOOSE));
8373    }
8374
8375    #[test]
8376    fn handshake_health_uses_protocol_liveness_not_stderr_severity() {
8377        let noisy_stderr = crate::HarnessEvent {
8378            sequence: None,
8379            kind: "transport_stderr".into(),
8380            payload: json!({"line": "ERROR optional worker AuthorizationRequired"}),
8381        };
8382        assert_eq!(handshake_event_failure(&noisy_stderr), None);
8383
8384        let closed = crate::HarnessEvent {
8385            sequence: None,
8386            kind: "transport_closed".into(),
8387            payload: json!({}),
8388        };
8389        assert!(handshake_event_failure(&closed).is_some());
8390    }
8391
8392    #[tokio::test]
8393    async fn runtime_eof_is_notified_and_removed_for_raw_and_explicit_close() {
8394        let mut service = HarnessSessionService::new();
8395        service
8396            .runtimes
8397            .insert("raw-eof".into(), ending_runtime(None));
8398        service.runtimes.insert(
8399            "explicit-close".into(),
8400            ending_runtime(Some(HarnessEvent {
8401                sequence: None,
8402                kind: "transport_closed".into(),
8403                payload: json!({"message": "native transport exited"}),
8404            })),
8405        );
8406
8407        let notifications = service.poll_runtimes().await;
8408
8409        assert_eq!(notifications.len(), 2);
8410        assert!(notifications
8411            .iter()
8412            .all(|notification| { notification["params"]["event"]["kind"] == "transport_closed" }));
8413        assert!(notifications.iter().all(|notification| {
8414            notification["params"]["session_id"] == "ending-session"
8415                && notification["params"]["connection"].is_string()
8416        }));
8417        let mut sequences = notifications
8418            .iter()
8419            .filter_map(|notification| notification["params"]["sequence"].as_u64())
8420            .collect::<Vec<_>>();
8421        sequences.sort_unstable();
8422        assert_eq!(sequences, vec![1, 2]);
8423        assert!(service.runtimes.is_empty());
8424    }
8425
8426    #[test]
8427    fn support_report_and_grok_default_binding_share_the_registry() {
8428        let mut service = HarnessSessionService::new();
8429        let response = service.handle(request(1, "harness.v1.support.report", json!({})));
8430        assert_eq!(response["result"]["schema"], crate::SUPPORT_REGISTRY_SCHEMA);
8431        let params = RuntimeBackendParams {
8432            harness: HarnessId::from(HarnessId::GROK),
8433            protocol: None,
8434            launch: None,
8435            base_url: None,
8436            policy: RuntimePolicy::Default,
8437        };
8438        let backend = match runtime_backend(&params) {
8439            Ok(backend) => backend,
8440            Err(_) => panic!("Grok should bind through its registered ACP launch"),
8441        };
8442        assert_eq!(backend.harness().as_str(), HarnessId::GROK);
8443        assert!(backend.capabilities().start_session);
8444        let registered = harness_support_registry()
8445            .harnesses
8446            .into_iter()
8447            .find(|harness| harness.id.as_str() == HarnessId::GROK)
8448            .and_then(|harness| harness.runtime.default_launch)
8449            .unwrap();
8450        assert!(!registered
8451            .arguments
8452            .iter()
8453            .any(|argument| argument == "--always-approve"));
8454        assert!(runtime_launch(&params).is_none());
8455
8456        let yolo = RuntimeBackendParams {
8457            policy: RuntimePolicy::Yolo,
8458            ..params
8459        };
8460        assert!(runtime_launch(&yolo)
8461            .unwrap()
8462            .arguments
8463            .iter()
8464            .any(|argument| argument == "--always-approve"));
8465
8466        let mismatched_protocol = RuntimeBackendParams {
8467            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8468            protocol: Some("acp".into()),
8469            launch: None,
8470            base_url: None,
8471            policy: RuntimePolicy::Default,
8472        };
8473        assert!(runtime_backend(&mismatched_protocol).is_err());
8474    }
8475
8476    #[test]
8477    fn load_follow_and_unfollow_share_the_same_locator() {
8478        let mut service = HarnessSessionService::new();
8479        let locator = pi_locator();
8480        let loaded = service.handle(request(
8481            1,
8482            "harness.v1.sessions.load",
8483            json!({"locator": locator}),
8484        ));
8485        assert_eq!(
8486            loaded["result"]["session"]["session_id"],
8487            locator.session_id
8488        );
8489
8490        let followed = service.handle(request(
8491            2,
8492            "harness.v1.sessions.follow",
8493            json!({"locator": locator}),
8494        ));
8495        assert_eq!(followed["result"]["subscription"], "sub-1");
8496        assert_eq!(followed["result"]["initial"]["type"], "session_snapshot");
8497        assert!(service.poll().is_empty());
8498
8499        let unfollowed = service.handle(request(
8500            3,
8501            "harness.v1.sessions.unfollow",
8502            json!({"subscription": "sub-1"}),
8503        ));
8504        assert_eq!(unfollowed["result"]["removed"], true);
8505    }
8506
8507    #[test]
8508    fn bounded_read_view_excludes_subagents_and_keeps_only_the_tail() {
8509        let temp = std::env::temp_dir().join(format!(
8510            "supercode-bounded-view-{}-{}",
8511            std::process::id(),
8512            generated_session_id()
8513        ));
8514        let path = temp.join("parent.jsonl");
8515        let subagents = temp.join("parent/subagents");
8516        std::fs::create_dir_all(&subagents).unwrap();
8517        let long_last = "x".repeat(300);
8518        let parent_records = [
8519            json!({"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":"first"}}),
8520            json!({"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"middle"}]}}),
8521            json!({"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":long_last}}),
8522        ];
8523        std::fs::write(
8524            &path,
8525            format!(
8526                "{}\n",
8527                parent_records
8528                    .iter()
8529                    .map(Value::to_string)
8530                    .collect::<Vec<_>>()
8531                    .join("\n")
8532            ),
8533        )
8534        .unwrap();
8535        std::fs::write(
8536            subagents.join("agent-child.jsonl"),
8537            concat!(
8538                r#"{"type":"user","uuid":"cu","parentUuid":null,"agentId":"child","message":{"role":"user","content":"child work"}}"#,
8539                "\n",
8540            ),
8541        )
8542        .unwrap();
8543        let locator = SessionLocator {
8544            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8545            session_id: "parent".into(),
8546            storage: StorageLocator::File { path },
8547        };
8548        let mut service = HarnessSessionService::new();
8549
8550        let complete = service.handle(request(
8551            1,
8552            "harness.v1.sessions.load",
8553            json!({"locator": locator}),
8554        ));
8555        assert_eq!(
8556            complete["result"]["session"]["subagents"]
8557                .as_array()
8558                .unwrap()
8559                .len(),
8560            1
8561        );
8562
8563        let bounded = service.handle(request(
8564            2,
8565            "harness.v1.sessions.load",
8566            json!({
8567                "locator": locator,
8568                "view": {
8569                    "tail_messages": 1,
8570                    "max_message_chars": 256,
8571                    "include_subagents": false
8572                },
8573            }),
8574        ));
8575        let session = &bounded["result"]["session"];
8576        assert!(session["subagents"].as_array().unwrap().is_empty());
8577        assert_eq!(session["messages"].as_array().unwrap().len(), 1);
8578        assert_eq!(
8579            session["messages"][0]["content"],
8580            format!("{}\n…", "x".repeat(256))
8581        );
8582
8583        let followed = service.handle(request(
8584            3,
8585            "harness.v1.sessions.follow",
8586            json!({
8587                "locator": locator,
8588                "view": {
8589                    "tail_messages": 1,
8590                    "max_message_chars": 256,
8591                    "include_subagents": false
8592                },
8593            }),
8594        ));
8595        let initial = &followed["result"]["initial"]["session"];
8596        assert!(initial["subagents"].as_array().unwrap().is_empty());
8597        assert_eq!(initial["messages"].as_array().unwrap().len(), 1);
8598
8599        let _ = std::fs::remove_dir_all(&temp);
8600    }
8601
8602    #[test]
8603    fn forty_megabyte_display_load_is_bounded_and_prompt() {
8604        let temp = std::env::temp_dir().join(format!(
8605            "supercode-large-display-view-{}-{}",
8606            std::process::id(),
8607            generated_session_id()
8608        ));
8609        std::fs::create_dir_all(&temp).unwrap();
8610        let path = temp.join("rollout.jsonl");
8611        let mut file = std::io::BufWriter::new(std::fs::File::create(&path).unwrap());
8612        writeln!(
8613            file,
8614            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8615        )
8616        .unwrap();
8617        let padding = "x".repeat(80 * 1024);
8618        for index in 0..512 {
8619            let marker = if index == 0 {
8620                "OLDEST-SHOULD-NOT-LOAD"
8621            } else if index == 511 {
8622                "LATEST-MUST-LOAD"
8623            } else {
8624                "bulk"
8625            };
8626            writeln!(
8627                file,
8628                "{}",
8629                json!({
8630                    "timestamp": "2026-01-01T00:00:01Z",
8631                    "type": "response_item",
8632                    "payload": {
8633                        "type": "message",
8634                        "role": "assistant",
8635                        "content": [{"type": "output_text", "text": format!("{marker}:{padding}")}],
8636                    },
8637                })
8638            )
8639            .unwrap();
8640        }
8641        file.flush().unwrap();
8642        drop(file);
8643        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
8644
8645        let locator = SessionLocator {
8646            harness: HarnessId::from(HarnessId::CODEX),
8647            session_id: "large-display".into(),
8648            storage: StorageLocator::File { path },
8649        };
8650        let started = Instant::now();
8651        let response = HarnessSessionService::new().handle(request(
8652            1,
8653            "harness.v1.sessions.load",
8654            json!({
8655                "locator": locator,
8656                "view": {
8657                    "tail_messages": 500,
8658                    "max_message_chars": 1024,
8659                    "include_subagents": false,
8660                    "display_history": true,
8661                },
8662            }),
8663        ));
8664        let elapsed = started.elapsed();
8665        let wire = response.to_string();
8666        eprintln!(
8667            "bounded 40 MiB display load: {elapsed:?}, {} response bytes",
8668            wire.len()
8669        );
8670        assert!(response.get("error").is_none(), "{response:#}");
8671        assert!(wire.contains("LATEST-MUST-LOAD"));
8672        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
8673        assert!(
8674            wire.len() < 2 * 1024 * 1024,
8675            "bounded wire was {} bytes",
8676            wire.len()
8677        );
8678        assert!(
8679            elapsed.as_secs_f64() < 3.0,
8680            "bounded 40 MiB load took {elapsed:?}"
8681        );
8682
8683        // Timing-free: a store with no human turn widens its window to the 64 MiB ceiling
8684        // looking for anchors, so the bounded read shows on one with a human turn every eight
8685        // records: a short view stops well short of the first record and says so.
8686        let anchored = temp.join("anchored.jsonl");
8687        let mut file = std::io::BufWriter::new(std::fs::File::create(&anchored).unwrap());
8688        writeln!(
8689            file,
8690            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8691        )
8692        .unwrap();
8693        for index in 0..512 {
8694            let marker = if index == 0 {
8695                "OLDEST-SHOULD-NOT-LOAD"
8696            } else if index == 511 {
8697                "LATEST-MUST-LOAD"
8698            } else {
8699                "bulk"
8700            };
8701            let (role, kind) = if index % 8 == 0 {
8702                ("user", "input_text")
8703            } else {
8704                ("assistant", "output_text")
8705            };
8706            writeln!(
8707                file,
8708                "{}",
8709                json!({
8710                    "timestamp": "2026-01-01T00:00:01Z",
8711                    "type": "response_item",
8712                    "payload": {
8713                        "type": "message",
8714                        "role": role,
8715                        "content": [{"type": kind, "text": format!("{marker}:{padding}")}],
8716                    },
8717                })
8718            )
8719            .unwrap();
8720        }
8721        file.flush().unwrap();
8722        drop(file);
8723        let short = HarnessSessionService::new().handle(request(
8724            2,
8725            "harness.v1.sessions.load",
8726            json!({
8727                "locator": SessionLocator {
8728                    harness: HarnessId::from(HarnessId::CODEX),
8729                    session_id: "large-display".into(),
8730                    storage: StorageLocator::File { path: anchored },
8731                },
8732                "view": {
8733                    "tail_messages": 20,
8734                    "max_message_chars": 1024,
8735                    "include_subagents": false,
8736                    "display_history": true,
8737                },
8738            }),
8739        ));
8740        let records = short["result"]["session"]["raw_record_count"].as_u64();
8741        assert!(
8742            records.is_some_and(|records| records < 128),
8743            "{records:?} records read"
8744        );
8745        let short = short.to_string();
8746        assert!(short.contains("LATEST-MUST-LOAD"));
8747        assert!(short.contains("older native records remain outside this bounded display window"));
8748
8749        let _ = std::fs::remove_dir_all(&temp);
8750    }
8751
8752    #[test]
8753    fn forty_megabyte_goose_store_display_load_reads_only_the_tail() {
8754        let temp = std::env::temp_dir().join(format!(
8755            "supercode-large-goose-view-{}-{}",
8756            std::process::id(),
8757            generated_session_id()
8758        ));
8759        std::fs::create_dir_all(&temp).unwrap();
8760        let path = temp.join("sessions.db");
8761        let connection = rusqlite::Connection::open(&path).unwrap();
8762        connection
8763            .execute_batch(
8764                "CREATE TABLE sessions (
8765                    id TEXT PRIMARY KEY, name TEXT NOT NULL, working_dir TEXT NOT NULL,
8766                    created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
8767                    session_type TEXT NOT NULL, extension_data TEXT,
8768                    goose_mode TEXT NOT NULL, provider_name TEXT, model_config_json TEXT,
8769                    archived_at TEXT
8770                 );
8771                 CREATE TABLE messages (
8772                    id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, message_id TEXT,
8773                    role TEXT NOT NULL, content_json TEXT NOT NULL,
8774                    created_timestamp INTEGER NOT NULL, metadata_json TEXT
8775                 );",
8776            )
8777            .unwrap();
8778        connection
8779            .execute(
8780                "INSERT INTO sessions VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL)",
8781                rusqlite::params![
8782                    "goose-large",
8783                    "Large Goose session",
8784                    "/tmp",
8785                    "2026-01-01 00:00:00",
8786                    "2026-01-01 00:00:02",
8787                    "user",
8788                    "{}",
8789                    "auto",
8790                    "anthropic",
8791                    r#"{"model_name":"claude-sonnet"}"#,
8792                ],
8793            )
8794            .unwrap();
8795        let old_content = serde_json::to_string(&vec![json!({
8796            "type": "text",
8797            "text": format!("OLDEST-SHOULD-NOT-LOAD:{}", "x".repeat(40 * 1024 * 1024)),
8798        })])
8799        .unwrap();
8800        connection
8801            .execute(
8802                "INSERT INTO messages VALUES (1, ?1, 'old', 'user', ?2, 1, '{}')",
8803                rusqlite::params!["goose-large", old_content],
8804            )
8805            .unwrap();
8806        connection
8807            .execute(
8808                "INSERT INTO messages VALUES (2, ?1, 'new', 'assistant', ?2, 2, '{}')",
8809                rusqlite::params![
8810                    "goose-large",
8811                    r#"[{"type":"text","text":"LATEST-MUST-LOAD"}]"#
8812                ],
8813            )
8814            .unwrap();
8815        drop(connection);
8816        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
8817
8818        let locator = SessionLocator {
8819            harness: HarnessId::from(HarnessId::GOOSE),
8820            session_id: "goose-large".into(),
8821            storage: StorageLocator::Sqlite {
8822                path,
8823                selector: "goose-large".into(),
8824            },
8825        };
8826        let started = Instant::now();
8827        let response = HarnessSessionService::new().handle(request(
8828            1,
8829            "harness.v1.sessions.load",
8830            json!({
8831                "locator": locator,
8832                "view": {
8833                    "tail_messages": 1,
8834                    "max_message_chars": 1024,
8835                    "include_subagents": false,
8836                    "display_history": true,
8837                },
8838            }),
8839        ));
8840        let elapsed = started.elapsed();
8841        let wire = response.to_string();
8842        eprintln!(
8843            "bounded 40 MiB Goose display load: {elapsed:?}, {} response bytes",
8844            wire.len()
8845        );
8846        assert!(response.get("error").is_none(), "{response:#}");
8847        assert!(wire.contains("LATEST-MUST-LOAD"));
8848        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
8849        assert!(
8850            wire.len() < 64 * 1024,
8851            "bounded wire was {} bytes",
8852            wire.len()
8853        );
8854        assert!(
8855            elapsed.as_secs_f64() < 1.0,
8856            "bounded Goose load took {elapsed:?}"
8857        );
8858
8859        let _ = std::fs::remove_dir_all(&temp);
8860    }
8861
8862    #[test]
8863    fn display_view_keeps_codex_assistant_history_across_compaction() {
8864        let temp = std::env::temp_dir().join(format!(
8865            "supercode-codex-display-view-{}-{}",
8866            std::process::id(),
8867            generated_session_id()
8868        ));
8869        std::fs::create_dir_all(&temp).unwrap();
8870        let path = temp.join("rollout.jsonl");
8871        std::fs::write(
8872            &path,
8873            concat!(
8874                r#"{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"codex-display","cwd":"/tmp"}}"#,
8875                "\n",
8876                r#"{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]}}"#,
8877                "\n",
8878                r#"{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"old answer"}]}}"#,
8879                "\n",
8880                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"}]}}"#,
8881                "\n",
8882                r#"{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"new prompt"}]}}"#,
8883                "\n",
8884                r#"{"timestamp":"2026-01-01T00:00:05Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"new answer"}]}}"#,
8885                "\n",
8886            ),
8887        )
8888        .unwrap();
8889        let locator = SessionLocator {
8890            harness: HarnessId::from(HarnessId::CODEX),
8891            session_id: "codex-display".into(),
8892            storage: StorageLocator::File { path },
8893        };
8894        let mut service = HarnessSessionService::new();
8895
8896        let continuation = service.handle(request(
8897            1,
8898            "harness.v1.sessions.load",
8899            json!({"locator": locator}),
8900        ));
8901        let continuation_text = continuation["result"]["session"]["messages"].to_string();
8902        assert!(!continuation_text.contains("old answer"));
8903
8904        let display = service.handle(request(
8905            2,
8906            "harness.v1.sessions.load",
8907            json!({
8908                "locator": locator,
8909                "view": {
8910                    "tail_messages": 10,
8911                    "include_subagents": false,
8912                    "display_history": true,
8913                },
8914            }),
8915        ));
8916        let display_text = display["result"]["session"]["messages"].to_string();
8917        assert!(display_text.contains("old prompt"));
8918        assert!(display_text.contains("old answer"));
8919        assert!(display_text.contains("new prompt"));
8920        assert!(display_text.contains("new answer"));
8921
8922        let _ = std::fs::remove_dir_all(&temp);
8923    }
8924
8925    #[test]
8926    fn indexed_claude_windows_match_the_existing_wire_projection() {
8927        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8928            .join("tests/fixtures/claude_code_session.jsonl");
8929        let locator = SessionLocator {
8930            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8931            session_id: "fixture".into(),
8932            storage: StorageLocator::File { path },
8933        };
8934        let full = load_session(&locator).unwrap();
8935        for inline_media in [InlineMediaMode::Full, InlineMediaMode::Metadata] {
8936            for offset in [0, 1, full.messages.len(), usize::MAX] {
8937                for limit in [0, 1, 3, usize::MAX] {
8938                    let options = SessionLoadOptions {
8939                        include_subagents: Some(false),
8940                        inline_media,
8941                        message_offset: Some(offset),
8942                        message_limit: Some(limit),
8943                        ..Default::default()
8944                    };
8945                    let expected = projected_session_result(&full, &options);
8946                    assert_eq!(
8947                        indexed_claude_window(&locator, &options).unwrap().unwrap(),
8948                        expected
8949                    );
8950                }
8951            }
8952            for tail in [0, 1, 3, usize::MAX] {
8953                let options = SessionLoadOptions {
8954                    include_subagents: Some(false),
8955                    inline_media,
8956                    message_tail: Some(tail),
8957                    ..Default::default()
8958                };
8959                assert_eq!(
8960                    indexed_claude_window(&locator, &options).unwrap().unwrap(),
8961                    projected_session_result(&full, &options)
8962                );
8963            }
8964        }
8965    }
8966
8967    #[test]
8968    fn load_supports_bounded_windows_and_media_metadata() {
8969        let mut service = HarnessSessionService::new();
8970        let locator = pi_locator();
8971        let bounded = service.handle(request(
8972            1,
8973            "harness.v1.sessions.load",
8974            json!({
8975                "locator": locator,
8976                "options": {
8977                    "include_subagents": false,
8978                    "message_limit": 2,
8979                    "message_offset": 1
8980                }
8981            }),
8982        ));
8983        assert_eq!(bounded["result"]["window"]["offset"], 1);
8984        assert_eq!(bounded["result"]["window"]["returned"], 2);
8985        assert!(bounded["result"]["summary"]["first_message"].is_object());
8986        assert!(bounded["result"]["summary"]["last_message"].is_object());
8987        assert_eq!(
8988            bounded["result"]["session"]["messages"]
8989                .as_array()
8990                .unwrap()
8991                .len(),
8992            2
8993        );
8994        assert!(bounded["result"]["session"]["subagents"]
8995            .as_array()
8996            .unwrap()
8997            .is_empty());
8998
8999        let tail = service.handle(request(
9000            2,
9001            "harness.v1.sessions.load",
9002            json!({"locator": locator, "options": {"message_tail": 1}}),
9003        ));
9004        assert_eq!(tail["result"]["window"]["returned"], 1);
9005        assert_eq!(tail["result"]["window"]["has_more"], true);
9006        assert_eq!(tail["result"]["window"]["has_older"], true);
9007        assert!(tail["result"]["window"]["older_items"].as_u64().unwrap() > 0);
9008        assert!(tail["result"]["summary"]["first_message"].is_object());
9009
9010        let metadata_only = service.handle(request(
9011            3,
9012            "harness.v1.sessions.load",
9013            json!({"locator": locator, "options": {"inline_media": "metadata"}}),
9014        ));
9015        assert!(metadata_only["result"]["session"]
9016            .to_string()
9017            .contains("media_reference"));
9018        assert!(!metadata_only["result"]["session"]
9019            .to_string()
9020            .contains("data:image/"));
9021    }
9022
9023    #[test]
9024    fn import_translate_branch_and_handoff_use_typed_artifacts() {
9025        let mut service = HarnessSessionService::new();
9026        let locator = pi_locator();
9027        let translated = service.handle(request(
9028            1,
9029            "harness.v1.sessions.translate",
9030            json!({"locator": locator, "target_harness": "grok"}),
9031        ));
9032        assert_eq!(translated["result"]["artifact"]["source_harness"], "pi");
9033        assert_eq!(translated["result"]["artifact"]["target_harness"], "grok");
9034        assert!(translated["result"]["artifact"]["content"]
9035            .as_str()
9036            .is_some_and(|content| !content.is_empty()));
9037
9038        for target in ["opencode", "open-code"] {
9039            let opencode = service.handle(request(
9040                6,
9041                "harness.v1.sessions.translate",
9042                json!({"locator": locator, "target_harness": target}),
9043            ));
9044            assert_eq!(opencode["result"]["artifact"]["target_harness"], "opencode");
9045        }
9046        let goose = service.handle(request(
9047            7,
9048            "harness.v1.sessions.translate",
9049            json!({"locator": locator, "target_harness": "goose"}),
9050        ));
9051        assert_eq!(goose["result"]["artifact"]["target_harness"], "goose");
9052        assert!(serde_json::from_str::<Value>(
9053            goose["result"]["artifact"]["content"].as_str().unwrap()
9054        )
9055        .unwrap()["conversation"]
9056            .is_array());
9057
9058        let imported = service.handle(request(
9059            2,
9060            "harness.v1.sessions.import",
9061            json!({
9062                "source_harness": "grok",
9063                "content": translated["result"]["artifact"]["content"],
9064            }),
9065        ));
9066        assert_eq!(imported["result"]["session"]["source"], "grok");
9067
9068        let branched = service.handle(request(
9069            3,
9070            "harness.v1.sessions.branch",
9071            json!({"locator": locator, "target_harness": "codex"}),
9072        ));
9073        assert_eq!(branched["result"]["parent"]["harness"], "pi");
9074        assert!(branched["result"]["bootstrap_prompt"]
9075            .as_str()
9076            .unwrap()
9077            .contains("frozen parent transcript"));
9078        assert_eq!(branched["result"]["artifact"]["target_harness"], "codex");
9079
9080        let handoff = service.handle(request(
9081            4,
9082            "harness.v1.sessions.handoff",
9083            json!({"locator": locator, "target_harness": "pi", "cwd": "/tmp/project"}),
9084        ));
9085        assert_eq!(handoff["result"]["launch"]["program"], "pi");
9086        assert_eq!(handoff["result"]["launch"]["cwd"], "/tmp/project");
9087        assert_eq!(handoff["result"]["requires_materialization"], true);
9088
9089        let goose_handoff = service.handle(request(
9090            8,
9091            "harness.v1.sessions.handoff",
9092            json!({"locator": locator, "target_harness": "goose", "cwd": "/tmp/project"}),
9093        ));
9094        assert_eq!(goose_handoff["result"]["launch"]["program"], "goose");
9095        assert_eq!(
9096            goose_handoff["result"]["materialize"]["arguments"],
9097            json!(["session", "import", "{artifact_path}"])
9098        );
9099
9100        let resumed = service.handle(request(
9101            5,
9102            "harness.v1.sessions.resume_instructions",
9103            json!({"locator": locator, "cwd": "/tmp/project", "policy": "yolo"}),
9104        ));
9105        assert_eq!(resumed["result"]["launch"]["program"], "pi");
9106        assert_eq!(resumed["result"]["launch"]["arguments"][0], "--approve");
9107    }
9108
9109    #[test]
9110    fn reduce_persists_and_reloads_a_byte_exact_reversible_bundle() {
9111        let temp = std::env::temp_dir().join(format!(
9112            "supercode-service-reduce-{}-{}",
9113            std::process::id(),
9114            generated_session_id()
9115        ));
9116        let source_path = temp.join("source.jsonl");
9117        let store_root = temp.join("store");
9118        std::fs::create_dir_all(&temp).unwrap();
9119
9120        let mut records = vec![json!({
9121            "timestamp": "2026-01-01T00:00:00Z",
9122            "type": "session_meta",
9123            "payload": {"id": "codex-reduce", "cwd": "/tmp/project"},
9124        })];
9125        for turn in 0..16 {
9126            records.push(json!({
9127                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 1),
9128                "type": "response_item",
9129                "payload": {
9130                    "type": "message",
9131                    "role": "user",
9132                    "content": [{
9133                        "type": "input_text",
9134                        "text": format!("request {turn}: {}", "context ".repeat(80)),
9135                    }],
9136                },
9137            }));
9138            records.push(json!({
9139                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 2),
9140                "type": "response_item",
9141                "payload": {
9142                    "type": "message",
9143                    "role": "assistant",
9144                    "content": [{
9145                        "type": "output_text",
9146                        "text": format!("answer {turn}: {}", "implementation detail ".repeat(80)),
9147                    }],
9148                },
9149            }));
9150        }
9151        let source = format!(
9152            "{}\n",
9153            records
9154                .iter()
9155                .map(Value::to_string)
9156                .collect::<Vec<_>>()
9157                .join("\n")
9158        );
9159        std::fs::write(&source_path, &source).unwrap();
9160        let locator = SessionLocator {
9161            harness: HarnessId::from(HarnessId::CODEX),
9162            session_id: "codex-reduce".into(),
9163            storage: StorageLocator::File {
9164                path: source_path.clone(),
9165            },
9166        };
9167        let original = load_session(&locator).unwrap();
9168        let mut service =
9169            HarnessSessionService::new().with_reduction_store_root(store_root.clone());
9170
9171        let response = service.handle(request(
9172            1,
9173            "harness.v1.sessions.reduce",
9174            json!({
9175                "locator": locator,
9176                "target_harness": "claude-code",
9177                "keep_last": 4,
9178            }),
9179        ));
9180        assert!(response.get("error").is_none(), "{response:#}");
9181        let receipt = &response["result"]["receipt"];
9182        assert_eq!(receipt["source_harness"], "codex");
9183        assert_eq!(receipt["target_harness"], "claude-code");
9184        assert_eq!(receipt["verified"], true);
9185        assert_eq!(receipt["reversible"], true);
9186        assert!(receipt["reductions"].as_u64().unwrap() > 0);
9187        assert!(
9188            receipt["source_tokens"].as_u64().unwrap()
9189                > receipt["reduced_tokens"].as_u64().unwrap()
9190        );
9191        assert!(receipt["ratio"].as_f64().unwrap() > 1.0);
9192        assert!(response["result"]["bootstrap_prompt"]
9193            .as_str()
9194            .unwrap()
9195            .contains("Do not guess hidden content"));
9196
9197        let rescue_id = receipt["id"].as_str().unwrap();
9198        let store = crate::SessionStore::open(&store_root).unwrap();
9199        let sidecar =
9200            Session::from_sidecar_str(&store.load_sidecar(rescue_id).unwrap().unwrap()).unwrap();
9201        let log = store.load_reduction_log(rescue_id).unwrap().unwrap();
9202        let persisted_view = parse_messages_jsonl(&store.load(rescue_id).unwrap()).unwrap();
9203        let policy = reduce::ReductionPolicy {
9204            clear_turns_older_than: Some(4),
9205            ..Default::default()
9206        };
9207        let (restamped_view, reapplied_log) =
9208            reduce::project_messages(&sidecar.messages, &policy, &log);
9209        assert_eq!(
9210            messages_jsonl(&persisted_view).unwrap(),
9211            messages_jsonl(&restamped_view).unwrap()
9212        );
9213        assert_eq!(reapplied_log, log);
9214        reduce::verify_log(&log, &sidecar).unwrap();
9215        assert_eq!(
9216            reduce::invert(&restamped_view, &log, &sidecar).unwrap(),
9217            original.messages
9218        );
9219        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), source);
9220
9221        std::fs::remove_dir_all(temp).ok();
9222    }
9223
9224    #[test]
9225    fn read_surfaces_view_a_severed_claude_graph_while_transfer_still_refuses_it() {
9226        let temp = std::env::temp_dir().join(format!(
9227            "supercode-severed-view-{}-{}",
9228            std::process::id(),
9229            generated_session_id()
9230        ));
9231        std::fs::create_dir_all(&temp).unwrap();
9232        let path = temp.join("severed.jsonl");
9233        // A live record whose parent was pruned — what a compacted or
9234        // resumed-across-files Claude Code session looks like on disk.
9235        std::fs::write(
9236            &path,
9237            concat!(
9238                r#"{"type":"user","uuid":"orphan-u","parentUuid":null,"message":{"role":"user","content":"stranded prompt"}}"#,
9239                "\n",
9240                r#"{"type":"assistant","uuid":"live-a","parentUuid":"pruned","message":{"id":"m","role":"assistant","content":[{"type":"text","text":"live answer"}]}}"#,
9241                "\n",
9242            ),
9243        )
9244        .unwrap();
9245        let locator = SessionLocator {
9246            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9247            session_id: "severed".into(),
9248            storage: StorageLocator::File { path },
9249        };
9250        let mut service = HarnessSessionService::new();
9251
9252        let viewed = service.handle(request(
9253            1,
9254            "harness.v1.sessions.load",
9255            json!({"locator": locator}),
9256        ));
9257        let session = &viewed["result"]["session"];
9258        assert_eq!(session["fidelity"], "semantic");
9259        assert_eq!(session["messages"].as_array().unwrap().len(), 2);
9260        assert!(session["residue"].as_array().unwrap().iter().any(|entry| {
9261            entry
9262                .as_str()
9263                .is_some_and(|entry| entry.contains("live-a") && entry.contains("pruned"))
9264        }));
9265
9266        // Asking a READ surface for a lossless reconstruction gets the strict
9267        // refusal back, unchanged.
9268        let strict = service.handle(request(
9269            2,
9270            "harness.v1.sessions.load",
9271            json!({"locator": locator, "fidelity": "byte_lossless"}),
9272        ));
9273        assert!(strict["error"]["message"]
9274            .as_str()
9275            .unwrap()
9276            .contains("cannot reconstruct lossless Claude continuation"));
9277
9278        // Transfer/continuation surfaces have no view mode at all.
9279        let translated = service.handle(request(
9280            3,
9281            "harness.v1.sessions.translate",
9282            json!({"locator": locator, "target_harness": "codex"}),
9283        ));
9284        assert!(translated["error"]["message"]
9285            .as_str()
9286            .unwrap()
9287            .contains("cannot reconstruct lossless Claude continuation"));
9288        let resumed = service.handle(request(
9289            4,
9290            "harness.v1.sessions.resume_instructions",
9291            json!({"locator": locator}),
9292        ));
9293        assert!(resumed["error"]["message"]
9294            .as_str()
9295            .unwrap()
9296            .contains("cannot reconstruct lossless Claude continuation"));
9297
9298        let _ = std::fs::remove_dir_all(&temp);
9299    }
9300
9301    #[test]
9302    fn structured_resume_launches_cover_gemini_goose_and_supercode() {
9303        let codex = resume_launch(
9304            HarnessId::CODEX,
9305            "codex-session",
9306            Path::new("/tmp/project"),
9307            ResumePolicy::Yolo,
9308        )
9309        .unwrap_or_else(|_| panic!("Codex resume launch must be registered"));
9310        assert_eq!(codex.program, "codex");
9311        assert_eq!(
9312            codex.arguments,
9313            [
9314                "-c",
9315                "check_for_update_on_startup=false",
9316                "-c",
9317                "projects.\"/tmp/project\".trust_level=\"trusted\"",
9318                "--dangerously-bypass-approvals-and-sandbox",
9319                "--dangerously-bypass-hook-trust",
9320                "resume",
9321                "codex-session",
9322            ]
9323        );
9324
9325        let gemini = resume_launch(
9326            HarnessId::GEMINI,
9327            "gemini-session",
9328            Path::new("/tmp/project"),
9329            ResumePolicy::Yolo,
9330        )
9331        .unwrap_or_else(|_| panic!("Gemini resume launch must be registered"));
9332        assert_eq!(gemini.program, "gemini");
9333        assert_eq!(gemini.arguments, ["--yolo", "--resume", "gemini-session"]);
9334
9335        let goose = resume_launch(
9336            HarnessId::GOOSE,
9337            "goose-session",
9338            Path::new("/tmp/project"),
9339            ResumePolicy::Yolo,
9340        )
9341        .unwrap_or_else(|_| panic!("Goose resume launch must be registered"));
9342        assert_eq!(goose.program, "goose");
9343        assert_eq!(
9344            goose.arguments,
9345            ["session", "--resume", "--session-id", "goose-session"]
9346        );
9347
9348        let supercode = resume_launch(
9349            HarnessId::SUPERCODE,
9350            "supercode-session",
9351            Path::new("/tmp/project"),
9352            ResumePolicy::Yolo,
9353        )
9354        .unwrap_or_else(|_| panic!("Supercode resume launch must be registered"));
9355        assert_eq!(supercode.program, "supercode");
9356        assert_eq!(
9357            supercode.arguments,
9358            ["--dangerous", "resume", "supercode-session"]
9359        );
9360    }
9361
9362    #[test]
9363    fn diagonal_artifacts_preserve_claude_subagents_and_grok_bundle_members() {
9364        let temp = std::env::temp_dir().join(format!(
9365            "supercode-harness-artifact-{}-{}",
9366            std::process::id(),
9367            generated_session_id()
9368        ));
9369        let main_path = temp.join("parent.jsonl");
9370        let subagent_path = temp.join("parent/subagents/agent-child.jsonl");
9371        std::fs::create_dir_all(subagent_path.parent().unwrap()).unwrap();
9372        let fixture = std::fs::read_to_string(
9373            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9374                .join("tests/fixtures/claude_code_session.jsonl"),
9375        )
9376        .unwrap();
9377        let parent = fixture.trim_end_matches('\n');
9378        let child = fixture.trim_end_matches('\n');
9379        std::fs::write(&main_path, parent).unwrap();
9380        std::fs::write(&subagent_path, child).unwrap();
9381        let locator = SessionLocator {
9382            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9383            session_id: "213bb148-51ea-453f-9206-f8b4b1168547".into(),
9384            storage: StorageLocator::File {
9385                path: main_path.clone(),
9386            },
9387        };
9388        let mut service = HarnessSessionService::new();
9389        let claude = service.handle(request(
9390            1,
9391            "harness.v1.sessions.translate",
9392            json!({"locator": locator, "target_harness": "claude-code"}),
9393        ));
9394        let artifact = &claude["result"]["artifact"];
9395        assert_eq!(artifact["fidelity"], "byte_lossless");
9396        assert_eq!(artifact["content"], parent);
9397        let files = artifact["files"].as_array().unwrap();
9398        assert!(files.iter().any(|file| {
9399            file["role"] == "subagent"
9400                && file["path"]
9401                    .as_str()
9402                    .is_some_and(|path| path.ends_with("/subagents/agent-child.jsonl"))
9403                && file["content"] == child
9404        }));
9405        assert!(!artifact["content"].as_str().unwrap().ends_with('\n'));
9406
9407        let grok = service.handle(request(
9408            2,
9409            "harness.v1.sessions.translate",
9410            json!({"locator": grok_locator(), "target_harness": "grok"}),
9411        ));
9412        let files = grok["result"]["artifact"]["files"].as_array().unwrap();
9413        for name in ["summary.json", "updates.jsonl"] {
9414            let expected = std::fs::read_to_string(
9415                PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9416                    .join("tests/fixtures/grok_session")
9417                    .join(name),
9418            )
9419            .unwrap();
9420            assert!(files.iter().any(|file| {
9421                file["path"] == name && file["role"] == "bundle" && file["content"] == expected
9422            }));
9423        }
9424        std::fs::remove_dir_all(temp).ok();
9425    }
9426
9427    #[test]
9428    fn every_non_grok_handoff_mints_and_uses_a_fresh_target_identity() {
9429        let mut service = HarnessSessionService::new();
9430        let source = pi_locator();
9431        for (target, format) in [
9432            ("claude-code", SessionFormat::ClaudeCode),
9433            ("codex", SessionFormat::Codex),
9434            ("opencode", SessionFormat::OpenCode),
9435            ("pi", SessionFormat::Pi),
9436        ] {
9437            let result = service.handle(request(
9438                1,
9439                "harness.v1.sessions.handoff",
9440                json!({"locator": source, "target_harness": target, "cwd": "/tmp/project"}),
9441            ));
9442            let artifact = &result["result"]["artifact"];
9443            let target_id = artifact["session_id"].as_str().unwrap();
9444            assert_ne!(target_id, source.session_id, "{target}");
9445            let parsed = Session::load_str(artifact["content"].as_str().unwrap(), format).unwrap();
9446            assert_eq!(
9447                parsed.meta.session_id.as_deref(),
9448                Some(target_id),
9449                "{target}"
9450            );
9451            if target != "pi" {
9452                assert!(result["result"]["launch"]["arguments"]
9453                    .as_array()
9454                    .unwrap()
9455                    .iter()
9456                    .any(|argument| argument == target_id));
9457            }
9458            if target == "opencode" {
9459                assert!(target_id.starts_with("ses_"));
9460                fn assert_session_ids(value: &Value, target_id: &str) {
9461                    match value {
9462                        Value::Object(fields) => {
9463                            if let Some(session_id) = fields.get("sessionID") {
9464                                assert_eq!(session_id, target_id);
9465                            }
9466                            for child in fields.values() {
9467                                assert_session_ids(child, target_id);
9468                            }
9469                        }
9470                        Value::Array(values) => {
9471                            for child in values {
9472                                assert_session_ids(child, target_id);
9473                            }
9474                        }
9475                        _ => {}
9476                    }
9477                }
9478                let document: Value =
9479                    serde_json::from_str(artifact["content"].as_str().unwrap()).unwrap();
9480                assert_session_ids(&document, target_id);
9481            }
9482        }
9483
9484        let first = service.handle(request(
9485            2,
9486            "harness.v1.sessions.handoff",
9487            json!({"locator": source, "target_harness": "codex"}),
9488        ));
9489        let second = service.handle(request(
9490            3,
9491            "harness.v1.sessions.handoff",
9492            json!({"locator": source, "target_harness": "codex"}),
9493        ));
9494        assert_ne!(
9495            first["result"]["artifact"]["session_id"],
9496            second["result"]["artifact"]["session_id"]
9497        );
9498    }
9499
9500    #[test]
9501    fn grok_handoff_materializes_through_the_core_door() {
9502        let mut service = HarnessSessionService::new();
9503        let source = opencode_locator();
9504        let response = service.handle(request(
9505            1,
9506            "harness.v1.sessions.handoff",
9507            json!({
9508                "locator": source,
9509                "target_harness": "grok",
9510                "cwd": "/tmp/grok-handoff-project",
9511            }),
9512        ));
9513        let result = &response["result"];
9514
9515        // Grok has no import command: the artifact is Grok's own transcript under a fresh
9516        // identity, and `harness.v1.sessions.materialize` writes its store entry.
9517        assert_eq!(result["artifact"]["target_harness"], "grok");
9518        let artifact = Session::load_str(
9519            result["artifact"]["content"].as_str().unwrap(),
9520            SessionFormat::Grok,
9521        )
9522        .unwrap();
9523        assert!(!artifact.messages.is_empty());
9524        let target_session_id = result["artifact"]["session_id"].as_str().unwrap();
9525        assert_eq!(target_session_id.len(), 36);
9526        assert_ne!(target_session_id, opencode_locator().session_id);
9527        assert!(result["materialize"].is_null());
9528        assert_eq!(
9529            result["launch"]["arguments"],
9530            json!(["--resume", "{materialized_session_id}"])
9531        );
9532        assert!(result["note"]
9533            .as_str()
9534            .unwrap()
9535            .contains("harness.v1.sessions.materialize"));
9536    }
9537
9538    #[tokio::test]
9539    async fn inventory_rejects_unknown_harnesses_and_runtime_attach_is_honest() {
9540        let mut service = HarnessSessionService::new();
9541        let inventory = service
9542            .handle_async(request(
9543                1,
9544                "harness.v1.harnesses.list",
9545                json!({"harnesses": ["missing"]}),
9546            ))
9547            .await;
9548        assert_eq!(inventory["error"]["code"], -32602);
9549
9550        let attached = service
9551            .handle_async(request(
9552                2,
9553                "harness.v1.runtimes.attach_existing",
9554                json!({"harness": "codex", "runtime_id": "thread-1"}),
9555            ))
9556            .await;
9557        assert_eq!(attached["error"]["code"], -32000);
9558        assert!(attached["error"]["message"]
9559            .as_str()
9560            .unwrap()
9561            .contains("runtimes.resume"));
9562    }
9563
9564    #[test]
9565    fn invalid_params_and_unknown_methods_use_json_rpc_errors() {
9566        let mut service = HarnessSessionService::new();
9567        let invalid = service.handle(request(1, "harness.v1.sessions.load", json!({})));
9568        assert_eq!(invalid["error"]["code"], -32602);
9569        let unknown = service.handle(request(2, "harness.v1.unknown", json!({})));
9570        assert_eq!(unknown["error"]["code"], -32601);
9571    }
9572
9573    #[cfg(unix)]
9574    #[tokio::test]
9575    // The test mutates process-wide harness environment and deliberately
9576    // holds the global test lock until every async runtime operation ends.
9577    #[allow(clippy::await_holding_lock)]
9578    async fn async_service_drives_a_generic_acp_runtime() {
9579        let _environment_guard = crate::live_runtime::test_environment_lock();
9580        let script = r#"
9581            i=0
9582            while IFS= read -r line; do
9583              i=$((i + 1))
9584              case "$i" in
9585                1) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{},"authMethods":[]}}' ;;
9586                2) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"svc_acp"}}' ;;
9587                3)
9588                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ok"}}}}'
9589                  printf '%s\n' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}'
9590                  ;;
9591                4)
9592                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"from terminal"}}}}'
9593                  printf '%s\n' '{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}'
9594                  ;;
9595              esac
9596            done
9597        "#;
9598        let mut service = HarnessSessionService::new();
9599        let started = service
9600            .handle_async(request(
9601                1,
9602                "harness.v1.runtimes.start",
9603                json!({
9604                    "harness": "codex",
9605                    "protocol": "acp",
9606                    "cwd": std::env::current_dir().unwrap(),
9607                    "launch": {"program": "/bin/sh", "arguments": ["-c", script], "env": {}},
9608                }),
9609            ))
9610            .await;
9611        assert_eq!(started["result"]["connection"], "runtime-1");
9612        assert_eq!(started["result"]["handle"]["runtime_id"], "svc_acp");
9613
9614        let terminal = service
9615            .handle_async(request(
9616                9,
9617                "harness.v1.runtimes.terminal_instructions",
9618                json!({"connection":"runtime-1"}),
9619            ))
9620            .await;
9621        let arguments = terminal["result"]["launch"]["arguments"]
9622            .as_array()
9623            .expect("hosted runtime should return terminal arguments");
9624        let endpoint_index = arguments
9625            .iter()
9626            .position(|value| value == "--endpoint")
9627            .expect("terminal command should use an opaque endpoint");
9628        let endpoint = LiveRuntimeEndpoint::parse(
9629            arguments[endpoint_index + 1]
9630                .as_str()
9631                .expect("endpoint argument should be text"),
9632        )
9633        .unwrap();
9634        assert!(!terminal.to_string().contains("Bearer"));
9635        let workspace = std::env::current_dir().unwrap();
9636        let receipt = resolve_live_runtime(
9637            &endpoint,
9638            &LiveRuntimeSource {
9639                harness: "codex".into(),
9640                session_id: "svc_acp".into(),
9641                workspace,
9642            },
9643        )
9644        .unwrap();
9645        let remote = crate::HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
9646            .await
9647            .unwrap();
9648        let mut attachment = crate::FrontendRuntime::attach(remote.as_ref(), 100)
9649            .await
9650            .unwrap();
9651
9652        let sent = service
9653            .handle_async(request(
9654                2,
9655                "harness.v1.runtimes.send_input",
9656                json!({"connection": "runtime-1", "text": "hi"}),
9657            ))
9658            .await;
9659        assert_eq!(sent["result"]["turn_id"], "3");
9660
9661        let mut events = Vec::new();
9662        for _ in 0..20 {
9663            events.extend(service.poll_runtimes().await);
9664            if events.len() >= 2 {
9665                break;
9666            }
9667            tokio::time::sleep(Duration::from_millis(2)).await;
9668        }
9669        assert!(events
9670            .iter()
9671            .any(|event| { event["params"]["event"]["kind"] == "session/update" }));
9672        assert!(events.iter().any(|event| {
9673            event["params"]["event"]["kind"] == "supercode/acp_request_completed"
9674        }));
9675
9676        let saw_editor_reply = tokio::time::timeout(Duration::from_secs(2), async {
9677            loop {
9678                let event = attachment.next_event().await.unwrap();
9679                if event.kind == "text_delta" && event.payload["text"] == "ok" {
9680                    break;
9681                }
9682            }
9683        })
9684        .await;
9685        assert!(
9686            saw_editor_reply.is_ok(),
9687            "terminal should observe the editor-driven turn"
9688        );
9689
9690        crate::FrontendRuntime::submit(remote.as_ref(), "DRIVE FROM TERMINAL".into())
9691            .await
9692            .unwrap();
9693        let saw_terminal_reply = tokio::time::timeout(Duration::from_secs(2), async {
9694            loop {
9695                let event = attachment.next_event().await.unwrap();
9696                if event.kind == "text_delta" && event.payload["text"] == "from terminal" {
9697                    break;
9698                }
9699            }
9700        })
9701        .await;
9702        assert!(
9703            saw_terminal_reply.is_ok(),
9704            "terminal should drive the same runtime"
9705        );
9706
9707        let closed = service
9708            .handle_async(request(
9709                3,
9710                "harness.v1.runtimes.close",
9711                json!({"connection": "runtime-1"}),
9712            ))
9713            .await;
9714        assert_eq!(closed["result"]["closed"], true);
9715    }
9716
9717    /// UNI-7 dev/02: a RUNNING mock gateway is detected through the real
9718    /// openclaw probe (config-declared endpoint, TCP connect), and an ACTIVE
9719    /// hermes WAL is detected through the real WAL-freshness probe; the
9720    /// negative sides (no listener, stale WAL, no config) stay undetected.
9721    #[test]
9722    fn running_instances_are_detected_from_mock_gateway_and_active_wal() {
9723        let home = connect_scratch_home("uni7-running");
9724
9725        // No config at all: hermes has no default endpoint, so no detection.
9726        // (openclaw's no-config behavior now probes its DOCUMENTED default
9727        // endpoint ws://127.0.0.1:18789 — see the connect launch's
9728        // `default_address` — which is real box state a hermetic test must
9729        // not assert either way; the closed-port negative below covers the
9730        // no-listener side deterministically.)
9731        assert!(probe_hermes_running(&home, 300_000).is_none());
9732
9733        // Mock gateway: a real TCP listener on an ephemeral port, declared in
9734        // the harness's own config file.
9735        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9736        let port = listener.local_addr().unwrap().port();
9737        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9738        std::fs::write(
9739            home.join(".openclaw/openclaw.json"),
9740            format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9741        )
9742        .unwrap();
9743        let running = probe_openclaw_running(&home).expect("listening gateway must be detected");
9744        assert!(matches!(
9745            running.method,
9746            RunningInstanceMethod::GatewayConnect
9747        ));
9748        assert!(running.evidence.contains(&format!("127.0.0.1:{port}")));
9749        drop(listener);
9750        // Parallel tests also bind ephemeral loopback ports, so a just-freed
9751        // port can be re-bound by a NEIGHBORING test between drop and probe.
9752        // Detection on a closed port must fail — retry on a fresh port when
9753        // the freed one was recycled by someone else.
9754        let mut closed_detected = probe_openclaw_running(&home).is_some();
9755        for _ in 0..3 {
9756            if !closed_detected {
9757                break;
9758            }
9759            let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9760            let port = listener.local_addr().unwrap().port();
9761            drop(listener);
9762            std::fs::write(
9763                home.join(".openclaw/openclaw.json"),
9764                format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9765            )
9766            .unwrap();
9767            closed_detected = probe_openclaw_running(&home).is_some();
9768        }
9769        assert!(
9770            !closed_detected,
9771            "a closed gateway must not read as running"
9772        );
9773
9774        // gateway.url form takes precedence over port.
9775        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9776        let port = listener.local_addr().unwrap().port();
9777        std::fs::write(
9778            home.join(".openclaw/openclaw.json"),
9779            format!(r#"{{"gateway": {{"url": "ws://127.0.0.1:{port}", "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9780        )
9781        .unwrap();
9782        assert!(probe_openclaw_running(&home).is_some());
9783        drop(listener);
9784
9785        // Hermes: an ACTIVE WAL (fresh stamp) is detected; a stale one is not.
9786        std::fs::create_dir_all(home.join(".hermes")).unwrap();
9787        let wal = home.join(".hermes/state.db-wal");
9788        std::fs::write(&wal, b"wal").unwrap();
9789        let running = probe_hermes_running(&home, 300_000).expect("fresh WAL must be detected");
9790        assert!(matches!(
9791            running.method,
9792            RunningInstanceMethod::StoreWalActivity
9793        ));
9794        assert!(running.evidence.contains("state.db-wal"));
9795        let stale = std::time::SystemTime::now() - std::time::Duration::from_secs(3_600);
9796        std::fs::File::options()
9797            .append(true)
9798            .open(&wal)
9799            .unwrap()
9800            .set_modified(stale)
9801            .unwrap();
9802        assert!(
9803            probe_hermes_running(&home, 300_000).is_none(),
9804            "a stale WAL (crash leftover) must not read as running"
9805        );
9806    }
9807
9808    fn connect_scratch_home(tag: &str) -> PathBuf {
9809        let dir = std::env::temp_dir().join(format!(
9810            "supercode-connect-service-{tag}-{}-{}",
9811            std::process::id(),
9812            std::time::SystemTime::now()
9813                .duration_since(std::time::UNIX_EPOCH)
9814                .unwrap()
9815                .as_nanos()
9816        ));
9817        std::fs::create_dir_all(&dir).unwrap();
9818        dir
9819    }
9820
9821    /// Minimal HTTP responder that speaks just enough OpenCode server to
9822    /// accept a health check, create a session, and hold an SSE stream open,
9823    /// while recording each request line with its Authorization header.
9824    async fn mock_opencode_endpoint() -> (String, tokio::sync::mpsc::UnboundedReceiver<String>) {
9825        use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
9826        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
9827        let address = listener.local_addr().unwrap();
9828        let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel();
9829        tokio::spawn(async move {
9830            loop {
9831                let Ok((mut stream, _)) = listener.accept().await else {
9832                    break;
9833                };
9834                let request_sender = request_sender.clone();
9835                tokio::spawn(async move {
9836                    let (reader, mut writer) = stream.split();
9837                    let mut reader = BufReader::new(reader);
9838                    let mut request_line = String::new();
9839                    if reader.read_line(&mut request_line).await.unwrap_or(0) == 0 {
9840                        return;
9841                    }
9842                    let request_line = request_line.trim_end().to_string();
9843                    let mut authorization = String::new();
9844                    let mut content_length = 0usize;
9845                    loop {
9846                        let mut line = String::new();
9847                        if reader.read_line(&mut line).await.unwrap_or(0) == 0 {
9848                            return;
9849                        }
9850                        let line = line.trim_end();
9851                        if line.is_empty() {
9852                            break;
9853                        }
9854                        let lower = line.to_ascii_lowercase();
9855                        if let Some(value) = lower.strip_prefix("authorization:") {
9856                            authorization = value.trim().to_string();
9857                        }
9858                        if let Some(value) = lower.strip_prefix("content-length:") {
9859                            content_length = value.trim().parse().unwrap_or(0);
9860                        }
9861                    }
9862                    if content_length > 0 {
9863                        let mut body = vec![0u8; content_length];
9864                        let _ = reader.read_exact(&mut body).await;
9865                    }
9866                    let _ = request_sender.send(format!("{request_line} :: {authorization}"));
9867                    if request_line.starts_with("GET /event") {
9868                        let _ = writer
9869                            .write_all(
9870                                b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n",
9871                            )
9872                            .await;
9873                        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
9874                        return;
9875                    }
9876                    let body = if request_line.starts_with("POST /session") {
9877                        r#"{"id":"mock-session"}"#
9878                    } else {
9879                        r#"{"status":"ok"}"#
9880                    };
9881                    let response = format!(
9882                        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
9883                        body.len(),
9884                        body
9885                    );
9886                    let _ = writer.write_all(response.as_bytes()).await;
9887                });
9888            }
9889        });
9890        (format!("http://{address}"), request_receiver)
9891    }
9892
9893    fn connect_descriptor(protocol: &str) -> crate::HarnessSupportDescriptor {
9894        crate::HarnessSupportDescriptor {
9895            orchestration: Default::default(),
9896            id: HarnessId::from(HarnessId::OPENCODE),
9897            display_name: "OpenCode".into(),
9898            native: crate::NativeSupport {
9899                discover: crate::ImplementationKind::Absent,
9900                load: crate::ImplementationKind::Absent,
9901                follow: crate::ImplementationKind::Absent,
9902                import: crate::ImplementationKind::Absent,
9903                export: crate::ImplementationKind::Absent,
9904            },
9905            runtime: crate::RuntimeSupport {
9906                implementation: crate::ImplementationKind::BuiltIn,
9907                protocol: protocol.into(),
9908                default_launch: None,
9909                connect_launch: Some(crate::RuntimeConnectLaunch {
9910                    config_path: "~/opencode-tui.json".into(),
9911                    address_pointer: "/server/url".into(),
9912                    port_pointer: None,
9913                    default_address: None,
9914                    auth_pointer: Some("/server/token".into()),
9915                    protocol: protocol.into(),
9916                }),
9917                capabilities: crate::RuntimeCapabilities {
9918                    start_session: true,
9919                    resume_session: true,
9920                    attach_existing_process: true,
9921                    send_input: true,
9922                    stream_events: true,
9923                    interrupt: true,
9924                    steer: false,
9925                    respond_to_requests: true,
9926                },
9927            },
9928        }
9929    }
9930
9931    #[tokio::test]
9932    async fn connect_mode_descriptor_opens_a_running_endpoint_with_config_sourced_auth() {
9933        let (base_url, mut requests) = mock_opencode_endpoint().await;
9934        let home = connect_scratch_home("open");
9935        std::fs::write(
9936            home.join("opencode-tui.json"),
9937            format!(r#"{{"server": {{"url": "{base_url}", "token": "connect-secret"}}}}"#),
9938        )
9939        .unwrap();
9940
9941        let descriptor = connect_descriptor("opencode-http-sse");
9942        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
9943        assert!(backend.capabilities().attach_existing_process);
9944
9945        let connection = backend
9946            .start(crate::RuntimeStartRequest {
9947                cwd: home.clone(),
9948                launch: None,
9949                mcp_servers: Vec::new(),
9950                approval_policy: None,
9951            })
9952            .await
9953            .unwrap();
9954        let handle = connection.handle();
9955        assert_eq!(handle.runtime_id, "mock-session");
9956        match &handle.endpoint {
9957            crate::RuntimeEndpoint::Http {
9958                base_url: endpoint, ..
9959            } => assert_eq!(endpoint, &base_url),
9960            other => panic!("connect mode must join the running endpoint, got {other:?}"),
9961        }
9962
9963        let mut seen = Vec::new();
9964        while let Ok(line) = requests.try_recv() {
9965            seen.push(line);
9966        }
9967        assert!(seen
9968            .iter()
9969            .any(|line| line.starts_with("GET /global/health")
9970                && line.contains("bearer connect-secret")));
9971        assert!(seen.iter().any(
9972            |line| line.starts_with("POST /session") && line.contains("bearer connect-secret")
9973        ));
9974    }
9975
9976    /// UNI-5 dev/02, contract corrected by the 2026-08-31 blind walk: the
9977    /// full connect-mode attach path against a MOCK gateway bridge — no live
9978    /// gateway, no model spend. A scripted fake `openclaw` binary (a)
9979    /// asserts the REAL bridge contract — the resolved --url on argv and the
9980    /// credential via --token-file (the real bridge ignores the env var; the
9981    /// endpoint comes from openclaw-native `gateway.remote.url`, never the
9982    /// schema-invalid `gateway.url`) — then (b) speaks scripted ACP:
9983    /// initialize advertising sessionCapabilities.{list,resume},
9984    /// session/resume rebinding the requested session (join), and a
9985    /// prompted turn.
9986    #[tokio::test]
9987    async fn openclaw_connect_mode_attaches_lists_and_resumes_via_a_mock_bridge() {
9988        let home = connect_scratch_home("openclaw");
9989        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9990        std::fs::write(
9991            home.join(".openclaw/openclaw.json"),
9992            r#"{"gateway": {"remote": {"url": "ws://127.0.0.1:19789"}, "auth": {"mode": "token", "token": "mock-gateway-token"}}}"#,
9993        )
9994        .unwrap();
9995        let script = home.join("openclaw");
9996        std::fs::write(
9997            &script,
9998            r#"#!/bin/sh
9999# Fake `openclaw acp` bridge: verify the connect-mode contract, then speak ACP.
10000[ "$1" = "acp" ] || { echo "unexpected argv: $*" >&2; exit 9; }
10001[ "$2" = "--url" ] && [ "$3" = "ws://127.0.0.1:19789" ] || { echo "missing --url: $*" >&2; exit 9; }
10002[ "$4" = "--token-file" ] || { echo "missing --token-file: $*" >&2; exit 9; }
10003[ "$(cat "$5")" = "mock-gateway-token" ] || { echo "token file wrong" >&2; exit 9; }
10004while IFS= read -r line; do
10005  case "$line" in
10006    *'"initialize"'*)
10007      printf '%s
10008' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"sessionCapabilities":{"list":{},"resume":{}}},"agentInfo":{"name":"openclaw-acp","version":"2026.7.1-2"},"authMethods":[]}}' ;;
10009    *'"session/resume"'*)
10010      printf '%s
10011' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:main"}}' ;;
10012    *'"session/new"'*)
10013      printf '%s
10014' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:fresh"}}' ;;
10015    *'"session/prompt"'*)
10016      printf '%s
10017' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"agent:main:main","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"joined"}}}}'
10018      printf '%s
10019' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}' ;;
10020  esac
10021done
10022"#,
10023        )
10024        .unwrap();
10025        use std::os::unix::fs::PermissionsExt;
10026        std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
10027
10028        let mut descriptor = crate::harness_support_registry()
10029            .harnesses
10030            .into_iter()
10031            .find(|harness| harness.id.as_str() == HarnessId::OPENCLAW)
10032            .expect("openclaw must be registered");
10033        descriptor
10034            .runtime
10035            .connect_launch
10036            .as_mut()
10037            .unwrap()
10038            .config_path = "~/.openclaw/openclaw.json".into();
10039        descriptor.runtime.default_launch.as_mut().unwrap().program =
10040            script.to_string_lossy().into_owned();
10041        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
10042        assert!(backend.capabilities().resume_session);
10043
10044        let joined = backend
10045            .attach(crate::RuntimeAttachRequest {
10046                runtime_id: "agent:main:main".into(),
10047                cwd: Some(home.clone()),
10048                launch: None,
10049                mcp_servers: Vec::new(),
10050                approval_policy: None,
10051            })
10052            .await;
10053        let mut connection = joined.expect("mock bridge attach must succeed");
10054        assert_eq!(connection.handle().runtime_id, "agent:main:main");
10055        let turn = connection
10056            .send_input(crate::RuntimeInput {
10057                text: "hello".into(),
10058                image_urls: Vec::new(),
10059            })
10060            .await;
10061        assert!(turn.is_ok(), "prompt through the mock bridge: {turn:?}");
10062        connection.close().await.unwrap();
10063    }
10064
10065    #[tokio::test]
10066    async fn connect_mode_fails_closed_without_a_protocol_client_or_config() {
10067        let home = connect_scratch_home("fail");
10068        std::fs::write(
10069            home.join("opencode-tui.json"),
10070            r#"{"server": {"url": "http://127.0.0.1:1", "token": "connect-secret"}}"#,
10071        )
10072        .unwrap();
10073
10074        let gateway_only = connect_descriptor("acp-v1-jsonrpc");
10075        let Err(error) = open_connect_descriptor(&gateway_only, &home) else {
10076            panic!("an ACP connect endpoint has no gateway client yet");
10077        };
10078        let message = format!("{error:?}");
10079        assert!(message.contains("acp-v1-jsonrpc"));
10080        assert!(!message.contains("connect-secret"));
10081
10082        let unreadable = connect_descriptor("opencode-http-sse");
10083        let missing_home = connect_scratch_home("missing");
10084        let Err(error) = open_connect_descriptor(&unreadable, &missing_home) else {
10085            panic!("an unreadable connect config must fail closed");
10086        };
10087        let message = format!("{error:?}");
10088        assert!(message.contains("opencode-tui.json"));
10089        assert!(!message.contains("connect-secret"));
10090    }
10091
10092    // ---------------------------------------------------------------------
10093    // ORCH-7 — `harness.v1.jobs.list` / `jobs.get` over the committed fixtures
10094    // ---------------------------------------------------------------------
10095
10096    fn jobs_fixture_root() -> PathBuf {
10097        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
10098    }
10099
10100    /// Point only the three job-bearing homes at the fixtures. Nothing else is
10101    /// read, so the host machine's own harness homes cannot leak into a row.
10102    fn jobs_fixture_homes() -> Value {
10103        let root = jobs_fixture_root();
10104        json!({
10105            "claude_code": root.join("claude_jobs_home/projects"),
10106            "hermes": root.join("hermes_home/state.db"),
10107            "openclaw": root.join("openclaw_home"),
10108        })
10109    }
10110
10111    fn jobs_list(params: Value) -> Value {
10112        let mut service = HarnessSessionService::new();
10113        service.handle(request(1, "harness.v1.jobs.list", params))
10114    }
10115
10116    fn job_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10117        result["jobs"]
10118            .as_array()
10119            .expect("jobs is an array")
10120            .iter()
10121            .find(|job| job["id"] == id)
10122            .unwrap_or_else(|| panic!("no job `{id}` in {result}"))
10123    }
10124
10125    #[test]
10126    fn gateway_health_derives_from_running_probe_and_install_state() {
10127        let running = RunningInstance {
10128            method: RunningInstanceMethod::GatewayConnect,
10129            evidence: "gateway endpoint 127.0.0.1:18789 accepted a TCP connect".into(),
10130            checked_at_ms: 1,
10131        };
10132        let up = gateway_health(
10133            HarnessId::OPENCLAW,
10134            true,
10135            Some(&running),
10136            Some("2026.7.1-2"),
10137        );
10138        assert_eq!(up.state, GatewayState::Up);
10139        assert!(up.endpoint.as_deref().unwrap().starts_with("ws://"));
10140        assert_eq!(up.version.as_deref(), Some("2026.7.1-2"));
10141        // Hermes consults its own `gateway status` when the WAL heuristic says
10142        // nothing; a fake binary decides the verdict (the env var is global, so
10143        // the up/down cases run inside this one test, never in parallel).
10144        let dir = std::env::temp_dir().join(format!("supercode-orch17-{}", std::process::id()));
10145        std::fs::create_dir_all(&dir).unwrap();
10146        let fake = dir.join("hermes");
10147        let write_fake = |body: &str| {
10148            std::fs::write(&fake, format!("#!/bin/sh\n{body}\n")).unwrap();
10149            #[cfg(unix)]
10150            {
10151                use std::os::unix::fs::PermissionsExt;
10152                std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o755)).unwrap();
10153            }
10154        };
10155        write_fake("echo '✗ Gateway service is not installed'");
10156        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| {
10157            *slot.borrow_mut() = Some((
10158                HarnessId::HERMES.to_string(),
10159                fake.to_string_lossy().into_owned(),
10160            ))
10161        });
10162        let down = gateway_health(HarnessId::HERMES, true, None, None);
10163        assert_eq!(down.state, GatewayState::Down, "{down:?}");
10164        assert!(down.endpoint.is_none());
10165        assert!(down.evidence.contains("not installed"));
10166        write_fake("echo 'Launchd plist: /x/ai.hermes.gateway.plist'; echo '✓ Gateway is supervised by launchd (PID 4242)'");
10167        let idle_but_up = gateway_health(HarnessId::HERMES, true, None, Some("0.21.0"));
10168        assert_eq!(idle_but_up.state, GatewayState::Up, "{idle_but_up:?}");
10169        assert!(idle_but_up.evidence.contains("PID 4242"));
10170        write_fake("echo 'something unparseable'");
10171        let no_verdict = gateway_health(HarnessId::HERMES, true, None, None);
10172        assert_eq!(no_verdict.state, GatewayState::Down);
10173        assert!(no_verdict.evidence.contains("no verdict"));
10174        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| *slot.borrow_mut() = None);
10175        let absent = gateway_health(HarnessId::HERMES, false, None, None);
10176        assert_eq!(absent.state, GatewayState::Unknown);
10177        let core = gateway_health(HarnessId::CODEX, true, None, Some("0.144.4"));
10178        assert_eq!(core.state, GatewayState::Unknown);
10179        assert!(core.evidence.contains("per session"));
10180    }
10181
10182    #[test]
10183    fn triggers_list_reads_both_stores_and_never_emits_secrets() {
10184        let response = triggers_list(json!({"homes": jobs_fixture_homes()}));
10185        let rows = response["result"]["triggers"]
10186            .as_array()
10187            .expect("triggers")
10188            .clone();
10189        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10190        assert!(
10191            hermes.iter().any(|r| r["name"] == "deploys"
10192                && r["route"] == "/webhooks/deploys"
10193                && r["kind"] == "webhook"),
10194            "{rows:#?}"
10195        );
10196        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10197        assert!(openclaw
10198            .iter()
10199            .any(|r| r["name"] == "wake" && r["kind"] == "builtin_wake"));
10200        assert!(openclaw.iter().any(|r| r["name"] == "gmail"
10201            && r["kind"] == "hook_mapping"
10202            && r["target"]["action"] == "agent"));
10203        let rendered = response.to_string();
10204        for secret in [
10205            "FAKE-WEBHOOK-HMAC-DO-NOT-EMIT",
10206            "FAKE-HOOK-TOKEN-DO-NOT-EMIT",
10207        ] {
10208            assert!(!rendered.contains(secret), "{rendered}");
10209        }
10210        let refused =
10211            triggers_list(json!({"harness": "claude-code", "homes": jobs_fixture_homes()}));
10212        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10213    }
10214
10215    fn triggers_list(params: Value) -> Value {
10216        let mut service = HarnessSessionService::new();
10217        service.handle(request(1, "harness.v1.triggers.list", params))
10218    }
10219
10220    #[test]
10221    fn routes_list_reads_both_gateway_configs_and_flags_the_defaults() {
10222        let response = routes_list(json!({"homes": jobs_fixture_homes()}));
10223        let rows = response["result"]["routes"]
10224            .as_array()
10225            .expect("routes")
10226            .clone();
10227        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10228        assert_eq!(hermes.len(), 2, "{rows:#?}");
10229        assert_eq!(hermes[0]["target"], "coder");
10230        assert_eq!(hermes[0]["match"]["platform"], "slack");
10231        assert_eq!(hermes[0]["match"]["chat_id"], "C0FIXTURE");
10232        assert_eq!(hermes[0]["specificity"], 4);
10233        assert_eq!(hermes[1]["default"], true);
10234        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10235        assert!(
10236            openclaw.iter().any(|r| r["target"] == "design"
10237                && r["match"]["platform"] == "slack"
10238                && r["specificity"] == 1),
10239            "{openclaw:#?}"
10240        );
10241        assert!(openclaw.iter().any(|r| r["default"] == true));
10242        // A core harness has no routing concept and is refused, never an empty list.
10243        let refused = routes_list(json!({"harness": "codex", "homes": jobs_fixture_homes()}));
10244        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10245    }
10246
10247    fn routes_list(params: Value) -> Value {
10248        let mut service = HarnessSessionService::new();
10249        service.handle(request(1, "harness.v1.routes.list", params))
10250    }
10251
10252    #[test]
10253    fn jobs_list_projects_every_fixture_store_onto_the_uniform_row() {
10254        let response = jobs_list(json!({"homes": jobs_fixture_homes()}));
10255        let result = &response["result"];
10256        let ids: Vec<&str> = result["jobs"]
10257            .as_array()
10258            .unwrap()
10259            .iter()
10260            .map(|job| job["id"].as_str().unwrap())
10261            .collect();
10262        assert_eq!(
10263            ids,
10264            vec![
10265                "release-watch",
10266                "toolu_wake_recheck",
10267                "digest-15m",
10268                "nightly-audit",
10269                "coder-standup",
10270                "ops-once-boot",
10271                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10272                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10273                "cron_standup",
10274                "cron_reindex",
10275            ],
10276            "{result}"
10277        );
10278
10279        // OpenClaw, pinned shape: rows come from `state/openclaw.sqlite`
10280        // (`cron_jobs.job_json` + runtime columns), captured from a real
10281        // 2026.7.1-2 gateway.
10282        let health = job_row(result, "85ad7832-896f-42be-af31-3e1ed2fbdc4b");
10283        assert_eq!(health["harness"], "openclaw");
10284        assert_eq!(health["schedule"]["kind"], "interval");
10285        assert_eq!(health["schedule"]["minutes"], 10.0);
10286        assert_eq!(health["session_target"], "isolated");
10287        assert_eq!(health["payload"]["kind"], "prompt");
10288        assert_eq!(health["payload"]["text"], "nightly health check");
10289        // ORCH-13: the mode word (`announce`) and the channel it announces on
10290        // (`last`) are separate facts, and the store keeps both — in
10291        // `job_json.delivery` and in the `delivery_*` columns beside it.
10292        assert_eq!(health["deliver"]["mode"], "announce");
10293        assert_eq!(health["deliver"]["target"], "last");
10294        assert_eq!(health["next_run_at"], "2026-09-03T06:52:26Z");
10295        let digest = job_row(result, "8bb7d938-ca46-4a6d-90eb-c92331155566");
10296        assert_eq!(digest["schedule"]["kind"], "cron");
10297        assert_eq!(digest["schedule"]["expr"], "0 9 * * 1");
10298        assert_eq!(digest["session_target"], "main");
10299        assert_eq!(digest["payload"]["kind"], "system_event");
10300
10301        // Claude Code: session-scoped, one recurring cron and one one-shot wakeup.
10302        let cron = job_row(result, "release-watch");
10303        assert_eq!(cron["harness"], "claude-code");
10304        assert_eq!(cron["scope"], "session");
10305        assert_eq!(cron["session_id"], "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
10306        assert_eq!(cron["schedule"]["kind"], "cron");
10307        assert_eq!(cron["schedule"]["expr"], "*/10 * * * *");
10308        assert_eq!(cron["schedule"]["display"], "*/10 * * * *");
10309        assert_eq!(cron["payload"]["kind"], "prompt");
10310        assert_eq!(cron["recurring"], true);
10311        assert_eq!(cron["deliver"]["target"], "session");
10312        let wakeup = job_row(result, "toolu_wake_recheck");
10313        assert_eq!(wakeup["payload"]["kind"], "wakeup");
10314        assert_eq!(wakeup["schedule"]["kind"], "once");
10315        assert_eq!(wakeup["recurring"], false);
10316        assert_eq!(wakeup["state"], "pending");
10317
10318        // Hermes: install-scoped, interval + origin delivery, and a paused cron.
10319        let interval = job_row(result, "digest-15m");
10320        assert_eq!(interval["harness"], "hermes");
10321        assert_eq!(interval["scope"], "install");
10322        assert_eq!(interval["profile"], Value::Null);
10323        assert_eq!(interval["schedule"]["kind"], "interval");
10324        assert_eq!(interval["schedule"]["minutes"], 15.0);
10325        assert_eq!(interval["schedule"]["display"], "every 15 min");
10326        assert_eq!(interval["deliver"]["target"], "origin");
10327        assert_eq!(interval["deliver"]["chat_id"], "-1002233445566");
10328        assert_eq!(interval["next_run_at"], "2026-09-02T11:15:00Z");
10329        assert_eq!(interval["last_status"], "ok");
10330        let nightly = job_row(result, "nightly-audit");
10331        assert_eq!(nightly["schedule"]["expr"], "0 3 * * *");
10332        assert_eq!(nightly["deliver"]["target"], "local");
10333        assert_eq!(nightly["enabled"], false);
10334        assert_eq!(nightly["state"], "paused");
10335        // The per-profile store carries the profile name from its own path.
10336        let profiled = job_row(result, "ops-once-boot");
10337        assert_eq!(profiled["profile"], "ops");
10338        assert_eq!(profiled["schedule"]["kind"], "once");
10339        assert_eq!(profiled["schedule"]["run_at"], "2026-09-03T06:00:00Z");
10340        assert_eq!(profiled["payload"]["kind"], "script");
10341        // An explicit `<platform>:<chat>` target carries the chat itself.
10342        assert_eq!(profiled["deliver"]["target"], "slack:C0429ABCD");
10343        assert_eq!(profiled["deliver"]["chat_id"], "C0429ABCD");
10344        assert_eq!(profiled["recurring"], false);
10345
10346        // ORCH-13: a job delivering to its creating conversation carries that
10347        // conversation's whole surface — platform word, chat AND thread.
10348        let standup_to_group = job_row(result, "coder-standup");
10349        assert_eq!(standup_to_group["deliver"]["target"], "origin");
10350        assert_eq!(standup_to_group["deliver"]["chat_id"], "-100777");
10351        assert_eq!(standup_to_group["deliver"]["thread_id"], "55");
10352        // Hermes has no mode word and routes by adapter profile, not account.
10353        assert!(standup_to_group["deliver"]["mode"].is_null());
10354        assert!(standup_to_group["deliver"]["account"].is_null());
10355
10356        // OpenClaw: the session target and the delivery mode are the row's own
10357        // columns, not a footnote.
10358        let standup = job_row(result, "cron_standup");
10359        assert_eq!(standup["harness"], "openclaw");
10360        assert_eq!(standup["session_target"], "isolated");
10361        assert_eq!(standup["deliver"]["mode"], "announce");
10362        assert_eq!(standup["deliver"]["target"], "slack");
10363        assert_eq!(standup["deliver"]["chat_id"], "C0429ABCD");
10364        assert_eq!(standup["payload"]["kind"], "prompt");
10365        assert_eq!(standup["profile"], "main");
10366        let reindex = job_row(result, "cron_reindex");
10367        assert_eq!(reindex["session_target"], "main");
10368        assert_eq!(reindex["payload"]["kind"], "system_event");
10369        assert_eq!(reindex["schedule"]["kind"], "interval");
10370        assert_eq!(reindex["schedule"]["display"], "every 240 min");
10371        assert_eq!(reindex["enabled"], false);
10372
10373        // Every store consulted is named, so an empty answer is never silent.
10374        let states: Vec<(&str, &str)> = result["sources"]
10375            .as_array()
10376            .unwrap()
10377            .iter()
10378            .map(|source| {
10379                (
10380                    source["harness"].as_str().unwrap(),
10381                    source["state"].as_str().unwrap(),
10382                )
10383            })
10384            .collect();
10385        // The `coder` profile home has no cron store at all: it is named as
10386        // `absent_store`, not skipped, so "this profile schedules nothing" and
10387        // "this profile was never looked at" stay distinguishable.
10388        assert_eq!(
10389            states,
10390            vec![
10391                ("claude-code", "scanned"),
10392                ("hermes", "read"),
10393                ("hermes", "absent_store"),
10394                ("hermes", "read"),
10395                ("openclaw", "read"),
10396                ("openclaw", "read"),
10397            ],
10398            "{result}"
10399        );
10400    }
10401
10402    #[test]
10403    fn jobs_list_filters_by_harness_session_and_profile() {
10404        let by_harness = jobs_list(json!({"harness": "openclaw", "homes": jobs_fixture_homes()}));
10405        let ids: Vec<&str> = by_harness["result"]["jobs"]
10406            .as_array()
10407            .unwrap()
10408            .iter()
10409            .map(|job| job["id"].as_str().unwrap())
10410            .collect();
10411        assert_eq!(
10412            ids,
10413            vec![
10414                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10415                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10416                "cron_standup",
10417                "cron_reindex",
10418            ]
10419        );
10420
10421        let by_session = jobs_list(json!({
10422            "session": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
10423            "homes": jobs_fixture_homes(),
10424        }));
10425        let jobs = by_session["result"]["jobs"].as_array().unwrap();
10426        assert_eq!(jobs.len(), 2, "{by_session}");
10427        assert!(jobs
10428            .iter()
10429            .all(|job| job["harness"] == "claude-code" && job["scope"] == "session"));
10430
10431        let by_profile = jobs_list(json!({
10432            "harness": "hermes",
10433            "profile": "ops",
10434            "homes": jobs_fixture_homes(),
10435        }));
10436        let jobs = by_profile["result"]["jobs"].as_array().unwrap();
10437        assert_eq!(jobs.len(), 1, "{by_profile}");
10438        assert_eq!(jobs[0]["id"], "ops-once-boot");
10439    }
10440
10441    #[test]
10442    fn jobs_get_answers_with_the_row_and_the_verbatim_native_record() {
10443        let mut service = HarnessSessionService::new();
10444        let hermes = service.handle(request(
10445            1,
10446            "harness.v1.jobs.get",
10447            json!({"harness": "hermes", "id": "digest-15m", "homes": jobs_fixture_homes()}),
10448        ));
10449        assert_eq!(hermes["result"]["job"]["schedule"]["kind"], "interval");
10450        // Native fields the uniform row does not carry survive on `source`.
10451        assert_eq!(hermes["result"]["source"]["provider"], "nous");
10452        assert_eq!(hermes["result"]["source"]["failure_deliver"], "local");
10453
10454        let claude = service.handle(request(
10455            2,
10456            "harness.v1.jobs.get",
10457            json!({"harness": "claude-code", "id": "release-watch", "homes": jobs_fixture_homes()}),
10458        ));
10459        assert_eq!(claude["result"]["job"]["payload"]["kind"], "prompt");
10460        assert_eq!(
10461            claude["result"]["source"]["tool_use_id"],
10462            "toolu_cron_release_watch"
10463        );
10464
10465        let missing = service.handle(request(
10466            3,
10467            "harness.v1.jobs.get",
10468            json!({"harness": "hermes", "id": "no-such-job", "homes": jobs_fixture_homes()}),
10469        ));
10470        assert!(missing["error"]["message"]
10471            .as_str()
10472            .is_some_and(|message| message.contains("no scheduled job `no-such-job`")));
10473    }
10474
10475    #[test]
10476    fn jobs_refuse_a_harness_without_a_scheduled_job_concept() {
10477        let mut service = HarnessSessionService::new();
10478        for (id, method, params) in [
10479            (
10480                1,
10481                "harness.v1.jobs.list",
10482                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10483            ),
10484            (
10485                2,
10486                "harness.v1.jobs.get",
10487                json!({"harness": "codex", "id": "anything"}),
10488            ),
10489        ] {
10490            let response = service.handle(request(id, method, params));
10491            assert_eq!(response["error"]["code"], -32020, "{response}");
10492            assert!(response["error"]["message"]
10493                .as_str()
10494                .is_some_and(|message| message.contains("has no scheduled jobs")));
10495            assert!(response.get("result").is_none());
10496        }
10497    }
10498
10499    #[test]
10500    fn jobs_list_reports_a_migrated_openclaw_store_as_absent_instead_of_failing() {
10501        let scratch = std::env::temp_dir().join(format!(
10502            "supercode-jobs-migrated-{}-{}",
10503            std::process::id(),
10504            generated_session_id()
10505        ));
10506        std::fs::create_dir_all(&scratch).unwrap();
10507        let response = jobs_list(json!({
10508            "harness": "openclaw",
10509            "homes": {"openclaw": scratch.clone()},
10510        }));
10511        let result = &response["result"];
10512        assert_eq!(result["jobs"].as_array().unwrap().len(), 0, "{result}");
10513        assert_eq!(result["sources"][0]["state"], "absent_store");
10514        assert_eq!(result["sources"][0]["harness"], "openclaw");
10515        std::fs::remove_dir_all(&scratch).ok();
10516    }
10517
10518    // ---------------------------------------------------------------------
10519    // ORCH-8 — `harness.v1.runs.list` / `runs.get` over the committed fire
10520    // stores: Hermes's `cron/executions.db` (root home + profile home) and
10521    // OpenClaw's `cron_run_logs`. Every fixture row is written by
10522    // `tests/fixtures/gen_runs_fixtures.py` against the harnesses' own DDL.
10523    // ---------------------------------------------------------------------
10524
10525    /// The health job in the committed OpenClaw fixture, which fired twice.
10526    const OPENCLAW_HEALTH_JOB: &str = "85ad7832-896f-42be-af31-3e1ed2fbdc4b";
10527    /// The digest job, whose single fire predates run ids.
10528    const OPENCLAW_DIGEST_JOB: &str = "8bb7d938-ca46-4a6d-90eb-c92331155566";
10529
10530    fn runs_list(params: Value) -> Value {
10531        let mut service = HarnessSessionService::new();
10532        service.handle(request(1, "harness.v1.runs.list", params))
10533    }
10534
10535    fn run_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10536        result["runs"]
10537            .as_array()
10538            .expect("runs is an array")
10539            .iter()
10540            .find(|run| run["id"] == id)
10541            .unwrap_or_else(|| panic!("no run `{id}` in {result}"))
10542    }
10543
10544    #[test]
10545    fn runs_list_projects_both_fixture_stores_onto_the_uniform_row() {
10546        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10547        let result = &response["result"];
10548        let ids: Vec<&str> = result["runs"]
10549            .as_array()
10550            .expect("runs is an array")
10551            .iter()
10552            .map(|run| run["id"].as_str().unwrap())
10553            .collect();
10554        let digest_fire = format!("{OPENCLAW_DIGEST_JOB}#1");
10555        assert_eq!(
10556            ids,
10557            vec![
10558                // Hermes, newest claim first, root ledger then profile ledger.
10559                "b2c3d4e5f60718293a4b5c6d7e8f9012",
10560                "a1b2c3d4e5f60718293a4b5c6d7e8f90",
10561                "c3d4e5f60718293a4b5c6d7e8f901234",
10562                "f60718293a4b5c6d7e8f901234567890",
10563                "e5f60718293a4b5c6d7e8f9012345678",
10564                "d4e5f60718293a4b5c6d7e8f90123456",
10565                // OpenClaw, newest `ts` first.
10566                "run_health_0002",
10567                digest_fire.as_str(),
10568                "run_health_0001",
10569            ],
10570            "{result}"
10571        );
10572
10573        // The harness's OWN outcome word survives; nothing is renamed onto a
10574        // shared vocabulary.
10575        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10576        assert_eq!(failed["harness"], "hermes");
10577        assert_eq!(failed["job_id"], "job42");
10578        assert_eq!(failed["status"], "failed");
10579        assert_eq!(failed["error"], "provider returned 500 after 3 attempts");
10580        assert_eq!(failed["claimed_at"], "2026-09-02T13:05:00.100442");
10581
10582        // Hermes's `unknown` — an attempt whose owner died before writing a
10583        // terminal state — is a fourth status, not folded into `failed`.
10584        let abandoned = run_row(result, "d4e5f60718293a4b5c6d7e8f90123456");
10585        assert_eq!(abandoned["status"], "unknown");
10586        assert_eq!(abandoned["job_id"], "ops-once-boot");
10587
10588        // An unterminated fire has no finish, and no session is invented.
10589        let running = run_row(result, "c3d4e5f60718293a4b5c6d7e8f901234");
10590        assert_eq!(running["status"], "running");
10591        assert!(running["finished_at"].is_null(), "{running}");
10592        assert!(running["session_id"].is_null(), "{running}");
10593
10594        // OpenClaw records the session on the row itself, and epoch-ms
10595        // timestamps are rendered as RFC 3339.
10596        let ok = run_row(result, "run_health_0001");
10597        assert_eq!(ok["harness"], "openclaw");
10598        assert_eq!(ok["job_id"], OPENCLAW_HEALTH_JOB);
10599        assert_eq!(ok["status"], "ok");
10600        assert_eq!(ok["started_at"], "2026-09-02T08:30:00.000Z");
10601        assert_eq!(ok["finished_at"], "2026-09-02T08:30:30.000Z");
10602        assert_eq!(ok["session_id"], "3dd577ae-a0a3-4b5b-8063-f402be4f5fd4");
10603        // OpenClaw's run log is written once, at finish: there is no claim.
10604        assert!(ok["claimed_at"].is_null(), "{ok}");
10605
10606        // A run-log row with no `run_id` falls back to the store's own
10607        // `(job_id, seq)` key rather than being dropped.
10608        assert_eq!(run_row(result, &digest_fire)["status"], "skipped");
10609
10610        // ORCH-13: a fire whose delivery nothing recorded says so, rather than
10611        // borrowing a neighbouring fire's outcome. Both of these ran on jobs
10612        // that deliver `local` (or have no job record at all), so no
10613        // obligation is addressed to a surface they could match.
10614        for id in [
10615            "b2c3d4e5f60718293a4b5c6d7e8f9012",
10616            "d4e5f60718293a4b5c6d7e8f90123456",
10617        ] {
10618            assert!(run_row(result, id)["delivery"].is_null(), "{id}");
10619        }
10620
10621        // Every store consulted is named, including the profile home that has
10622        // no ledger — an empty history and an absent store are different.
10623        let sources = result["sources"].as_array().unwrap();
10624        let states: Vec<(&str, &str)> = sources
10625            .iter()
10626            .map(|source| {
10627                (
10628                    source["harness"].as_str().unwrap(),
10629                    source["state"].as_str().unwrap(),
10630                )
10631            })
10632            .collect();
10633        assert_eq!(
10634            states,
10635            vec![
10636                ("hermes", "read"),
10637                ("hermes", "absent_store"),
10638                ("hermes", "read"),
10639                ("openclaw", "read"),
10640            ],
10641            "{result}"
10642        );
10643        assert_eq!(sources[2]["profile"], "ops");
10644        assert!(sources[3]["path"]
10645            .as_str()
10646            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10647    }
10648
10649    #[test]
10650    fn runs_list_joins_a_hermes_fire_to_the_session_it_opened() {
10651        let response = runs_list(json!({
10652            "harness": "hermes",
10653            "job": "job42",
10654            "homes": jobs_fixture_homes(),
10655        }));
10656        let result = &response["result"];
10657        assert_eq!(result["runs"].as_array().unwrap().len(), 2, "{result}");
10658
10659        // Hermes writes NO link from an execution to its session. The fire
10660        // that ran the agent is joined to `cron_job42_<stamp>` because that
10661        // id's instant falls inside its [claimed_at, finished_at] window.
10662        let ran = run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90");
10663        assert_eq!(ran["session_id"], "cron_job42_20260902_120000");
10664
10665        // The later fire failed before opening one. Its window holds no
10666        // session, so the row says so instead of re-using the earlier fire's
10667        // — the join is per-FIRE, not per-job.
10668        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10669        assert!(failed["session_id"].is_null(), "{failed}");
10670    }
10671
10672    /// ORCH-13: where a fire's output went, read from each harness's own
10673    /// delivery record — Hermes's `delivery_obligations` ledger inside
10674    /// `state.db`, OpenClaw's `delivery_*` run-log columns.
10675    #[test]
10676    fn runs_list_reads_the_delivery_each_harness_recorded_for_a_fire() {
10677        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10678        let result = &response["result"];
10679
10680        // Hermes: the ledger is the GATEWAY's, keyed by conversation and
10681        // surface, so the fire's own [claimed_at, finished_at] window picks
10682        // the obligation. The fire succeeded and so did the send.
10683        let delivered = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
10684        assert_eq!(delivered["status"], "completed");
10685        assert_eq!(delivered["delivery"]["state"], "delivered");
10686        assert_eq!(delivered["delivery"]["target"], "telegram:-100777:55");
10687        assert_eq!(delivered["delivery"]["attempts"], 1);
10688        assert!(delivered["delivery"]["last_error"].is_null(), "{delivered}");
10689        assert_eq!(
10690            delivered["delivery"]["delivered_at"],
10691            "2026-09-02T09:00:30.400Z"
10692        );
10693
10694        // The next fire of the same job ALSO succeeded — and its output never
10695        // arrived. That is the fact `status` alone cannot carry.
10696        let undelivered = run_row(result, "f60718293a4b5c6d7e8f901234567890");
10697        assert_eq!(undelivered["status"], "completed");
10698        assert_eq!(undelivered["delivery"]["state"], "failed");
10699        assert_eq!(undelivered["delivery"]["attempts"], 3);
10700        assert_eq!(
10701            undelivered["delivery"]["last_error"],
10702            "telegram send failed: Bad Request: chat not found"
10703        );
10704        // Only a delivered obligation carries an instant of delivery; the
10705        // ledger's `updated_at` on a failed row dates the failure.
10706        assert!(
10707            undelivered["delivery"]["delivered_at"].is_null(),
10708            "{undelivered}"
10709        );
10710
10711        // OpenClaw writes the outcome onto the run-log row and declares the
10712        // address on the job, so the row's target is joined from `cron_jobs`.
10713        let announced = run_row(result, "run_health_0001");
10714        assert_eq!(announced["delivery"]["state"], "delivered");
10715        assert_eq!(announced["delivery"]["target"], "last");
10716        // Its run log counts no attempts and stamps no delivered-at.
10717        assert!(announced["delivery"]["attempts"].is_null(), "{announced}");
10718        assert!(
10719            announced["delivery"]["delivered_at"].is_null(),
10720            "{announced}"
10721        );
10722        let refused = run_row(result, "run_health_0002");
10723        assert_eq!(refused["delivery"]["state"], "not-delivered");
10724        assert_eq!(refused["delivery"]["last_error"], "channel_not_found");
10725
10726        // A run-log row with no delivery columns at all recorded no delivery:
10727        // the job's declared target is not evidence that anything was sent.
10728        let skipped = run_row(result, &format!("{OPENCLAW_DIGEST_JOB}#1"));
10729        assert!(skipped["delivery"].is_null(), "{skipped}");
10730    }
10731
10732    /// A Hermes fire whose session carries a `session_key` is matched on that
10733    /// key FIRST — the most specific question the ledger can answer. Proven by
10734    /// moving the obligations off the job's surface on a COPY of the fixture,
10735    /// so only the session-key question can still find them.
10736    #[test]
10737    fn runs_list_matches_a_hermes_obligation_by_the_session_key_first() {
10738        let scratch = std::env::temp_dir().join(format!(
10739            "supercode-runs-delivery-{}-{}",
10740            std::process::id(),
10741            generated_session_id()
10742        ));
10743        std::fs::create_dir_all(scratch.join("cron")).unwrap();
10744        let fixture = jobs_fixture_root().join("hermes_home");
10745        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
10746        for name in ["cron/executions.db", "cron/jobs.json"] {
10747            std::fs::copy(fixture.join(name), scratch.join(name)).unwrap();
10748        }
10749        {
10750            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
10751            // The obligations now sit on a surface no job in this store
10752            // delivers to, so the surface question cannot match them.
10753            connection
10754                .execute(
10755                    "UPDATE delivery_obligations SET platform = 'slack', chat_id = 'C0FALLBACK'",
10756                    [],
10757                )
10758                .unwrap();
10759            // A cron fire that ran inside a keyed conversation: the session
10760            // the window recovers carries `tg-coder-1`'s key.
10761            connection
10762                .execute(
10763                    "INSERT INTO sessions (id, source, session_key, started_at) VALUES \
10764                     ('cron_coder-standup_20260902_090010', 'cron', \
10765                      'agent:coder:telegram:group:-100777:55', 1788339610.0)",
10766                    [],
10767                )
10768                .unwrap();
10769        }
10770        let response = runs_list(json!({
10771            "harness": "hermes",
10772            "job": "coder-standup",
10773            "homes": {"hermes": scratch.join("state.db")},
10774        }));
10775        let result = &response["result"];
10776        let matched = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
10777        assert_eq!(
10778            matched["session_id"], "cron_coder-standup_20260902_090010",
10779            "{result}"
10780        );
10781        assert_eq!(matched["delivery"]["state"], "delivered", "{result}");
10782        assert_eq!(
10783            matched["delivery"]["target"], "slack:C0FALLBACK:55",
10784            "{result}"
10785        );
10786        std::fs::remove_dir_all(&scratch).ok();
10787    }
10788
10789    #[test]
10790    fn runs_list_follows_a_compression_chain_to_the_readable_tip() {
10791        // A fire whose session was compressed mid-run is only readable at the
10792        // continuation, so that is what the row must report. Built on a COPY
10793        // of the committed fixture: no test writes to a fixture or to a real
10794        // harness home.
10795        let scratch = std::env::temp_dir().join(format!(
10796            "supercode-runs-compressed-{}-{}",
10797            std::process::id(),
10798            generated_session_id()
10799        ));
10800        std::fs::create_dir_all(scratch.join("cron")).unwrap();
10801        let fixture = jobs_fixture_root().join("hermes_home");
10802        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
10803        std::fs::copy(
10804            fixture.join("cron/executions.db"),
10805            scratch.join("cron/executions.db"),
10806        )
10807        .unwrap();
10808        {
10809            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
10810            connection
10811                .execute(
10812                    "UPDATE sessions SET end_reason = 'compression' WHERE id = ?1",
10813                    ["cron_job42_20260902_120000"],
10814                )
10815                .unwrap();
10816            connection
10817                .execute(
10818                    "INSERT INTO sessions (id, source, parent_session_id, started_at) \
10819                     VALUES ('job42-after-compaction', 'cron', \
10820                             'cron_job42_20260902_120000', 1788350000.0)",
10821                    [],
10822                )
10823                .unwrap();
10824        }
10825        let response = runs_list(json!({
10826            "harness": "hermes",
10827            "job": "job42",
10828            "homes": {"hermes": scratch.join("state.db")},
10829        }));
10830        let result = &response["result"];
10831        assert_eq!(
10832            run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90")["session_id"],
10833            "job42-after-compaction",
10834            "{result}"
10835        );
10836        std::fs::remove_dir_all(&scratch).ok();
10837    }
10838
10839    #[test]
10840    fn runs_list_filters_by_job_and_caps_by_limit() {
10841        let by_job = runs_list(json!({
10842            "harness": "openclaw",
10843            "job": OPENCLAW_HEALTH_JOB,
10844            "homes": jobs_fixture_homes(),
10845        }));
10846        let ids: Vec<&str> = by_job["result"]["runs"]
10847            .as_array()
10848            .unwrap()
10849            .iter()
10850            .map(|run| run["id"].as_str().unwrap())
10851            .collect();
10852        assert_eq!(ids, vec!["run_health_0002", "run_health_0001"], "{by_job}");
10853
10854        let capped = runs_list(json!({
10855            "harness": "openclaw",
10856            "limit": 1,
10857            "homes": jobs_fixture_homes(),
10858        }));
10859        let runs = capped["result"]["runs"].as_array().unwrap();
10860        assert_eq!(runs.len(), 1, "{capped}");
10861        // Newest first, so the cap keeps the recent fire.
10862        assert_eq!(runs[0]["id"], "run_health_0002");
10863    }
10864
10865    #[test]
10866    fn runs_get_answers_with_the_row_and_the_verbatim_native_record() {
10867        let mut service = HarnessSessionService::new();
10868        let hermes = service.handle(request(
10869            1,
10870            "harness.v1.runs.get",
10871            json!({
10872                "harness": "hermes",
10873                "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
10874                "homes": jobs_fixture_homes(),
10875            }),
10876        ));
10877        assert_eq!(hermes["result"]["run"]["status"], "completed");
10878        assert_eq!(
10879            hermes["result"]["run"]["session_id"],
10880            "cron_job42_20260902_120000"
10881        );
10882        // Ledger columns the uniform row does not carry survive on `source`.
10883        assert_eq!(hermes["result"]["source"]["source"], "scheduler");
10884        assert_eq!(hermes["result"]["source"]["pid"], 4242);
10885        assert_eq!(hermes["result"]["source"]["process_id"], "9f1c2d");
10886
10887        let openclaw = service.handle(request(
10888            2,
10889            "harness.v1.runs.get",
10890            json!({
10891                "harness": "openclaw",
10892                "id": "run_health_0002",
10893                "homes": jobs_fixture_homes(),
10894            }),
10895        ));
10896        assert_eq!(openclaw["result"]["run"]["status"], "error");
10897        // ORCH-13: the run's delivery is projected AND the store's own columns
10898        // stay verbatim on `source`, so nothing about the fire is lost.
10899        assert_eq!(
10900            openclaw["result"]["source"]["delivery_status"],
10901            "not-delivered"
10902        );
10903        assert_eq!(
10904            openclaw["result"]["source"]["delivery_error"],
10905            "channel_not_found"
10906        );
10907        assert_eq!(openclaw["result"]["source"]["delivered"], 0);
10908        assert_eq!(
10909            openclaw["result"]["run"]["delivery"]["state"],
10910            "not-delivered"
10911        );
10912        assert_eq!(
10913            openclaw["result"]["run"]["delivery"]["last_error"],
10914            "channel_not_found"
10915        );
10916
10917        let missing = service.handle(request(
10918            3,
10919            "harness.v1.runs.get",
10920            json!({"harness": "hermes", "id": "no-such-run", "homes": jobs_fixture_homes()}),
10921        ));
10922        assert!(missing["error"]["message"]
10923            .as_str()
10924            .is_some_and(|message| message.contains("no run `no-such-run`")));
10925    }
10926
10927    #[test]
10928    fn runs_refuse_a_harness_that_keeps_no_run_store() {
10929        let mut service = HarnessSessionService::new();
10930        for (id, method, params) in [
10931            // Claude Code HAS scheduled jobs but no fire store: its fires are
10932            // ordinary turns. It must refuse, not answer with an empty list.
10933            (
10934                1,
10935                "harness.v1.runs.list",
10936                json!({"harness": "claude-code", "homes": jobs_fixture_homes()}),
10937            ),
10938            (
10939                2,
10940                "harness.v1.runs.get",
10941                json!({"harness": "claude-code", "id": "anything"}),
10942            ),
10943            (
10944                3,
10945                "harness.v1.runs.list",
10946                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10947            ),
10948        ] {
10949            let response = service.handle(request(id, method, params));
10950            assert_eq!(response["error"]["code"], -32020, "{response}");
10951            assert!(response["error"]["message"]
10952                .as_str()
10953                .is_some_and(|message| message.contains("keeps no run store")));
10954            assert!(response.get("result").is_none());
10955        }
10956    }
10957
10958    #[test]
10959    fn runs_list_reports_an_install_with_no_run_store_as_absent() {
10960        let scratch = std::env::temp_dir().join(format!(
10961            "supercode-runs-empty-{}-{}",
10962            std::process::id(),
10963            generated_session_id()
10964        ));
10965        std::fs::create_dir_all(&scratch).unwrap();
10966        let response = runs_list(json!({
10967            "harness": "openclaw",
10968            "homes": {"openclaw": scratch.clone()},
10969        }));
10970        let result = &response["result"];
10971        assert_eq!(result["runs"].as_array().unwrap().len(), 0, "{result}");
10972        assert_eq!(result["sources"][0]["state"], "absent_store");
10973        assert!(result["sources"][0]["path"]
10974            .as_str()
10975            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10976        std::fs::remove_dir_all(&scratch).ok();
10977    }
10978}