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.resume_instructions",
67    "harness.v1.skills.list",
68    "harness.v1.skills.install",
69    "harness.v1.skills.remove",
70    "harness.v1.memory.show",
71    "harness.v1.memory.search",
72    "harness.v1.jobs.list",
73    "harness.v1.jobs.get",
74    "harness.v1.jobs.create",
75    "harness.v1.jobs.update",
76    "harness.v1.jobs.pause",
77    "harness.v1.jobs.resume",
78    "harness.v1.jobs.run",
79    "harness.v1.jobs.delete",
80    "harness.v1.sessions.new",
81    "harness.v1.sessions.reset",
82    "harness.v1.sessions.archive",
83    "harness.v1.sessions.delete",
84    "harness.v1.runs.list",
85    "harness.v1.runs.get",
86    "harness.v1.approvals.list",
87    "harness.v1.approvals.resolve",
88    "harness.v1.runtimes.capabilities",
89    "harness.v1.runtimes.start",
90    "harness.v1.runtimes.resume",
91    "harness.v1.runtimes.attach_existing",
92    "harness.v1.runtimes.attach",
93    "harness.v1.runtimes.send_input",
94    "harness.v1.runtimes.interrupt",
95    "harness.v1.runtimes.steer",
96    "harness.v1.runtimes.respond",
97    "harness.v1.runtimes.terminal_instructions",
98    "harness.v1.runtimes.close",
99    "harness.v1.profiles.list",
100    "harness.v1.profiles.get",
101    "harness.v1.profiles.create",
102    "harness.v1.profiles.delete",
103    "harness.v1.channels.list",
104    "harness.v1.routes.list",
105    "harness.v1.triggers.list",
106    "harness.v1.channels.status",
107    "harness.v1.orchestration.load",
108    "harness.v1.orchestration.save",
109    "harness.v1.orchestration.compile",
110    "harness.v1.orchestration.decompile",
111    "harness.v1.orchestration.import",
112    "harness.v1.orchestration.export",
113    "harness.v1.workflow.load",
114];
115
116/// Protocol namespace implemented by this service.
117pub const HARNESS_SERVICE_VERSION: &str = "harness.v1";
118/// Notification method emitted for followed-session changes.
119pub const SESSION_EVENT_METHOD: &str = "harness.v1.sessions.event";
120/// Notification method emitted for normalized session-activity transitions.
121pub const SESSION_ACTIVITY_EVENT_METHOD: &str = "harness.v1.sessions.activity_event";
122/// Notification method emitted for revisioned session-list changes.
123pub const SESSION_INDEX_EVENT_METHOD: &str = "harness.v1.sessions.index_event";
124/// Notification method emitted for live runtime events.
125pub const RUNTIME_EVENT_METHOD: &str = "harness.v1.runtimes.event";
126
127/// Stateful persisted-session service. Each instance owns its follow
128/// subscriptions; discovery and loading remain read-only.
129pub struct HarnessSessionService {
130    catalog: HarnessCatalog,
131    followers: BTreeMap<String, SessionFollower>,
132    followed_sources: BTreeMap<String, FollowedSource>,
133    activity_subscriptions: BTreeMap<String, ActivitySubscription>,
134    index_subscriptions: BTreeMap<String, crate::session_index::SessionIndexSubscription>,
135    index_notifier: Arc<Notify>,
136    #[cfg(feature = "adapter-api")]
137    activity_monitor: crate::session_activity::SessionActivityMonitor,
138    next_subscription: u64,
139    runtimes: BTreeMap<String, Box<dyn RuntimeConnection>>,
140    /// Connections lent to a detached call that is running right now. The
141    /// runtime itself is OUT of `runtimes` for that whole call, and these
142    /// names are how a second caller is told the connection is busy rather
143    /// than unknown.
144    runtimes_in_flight: BTreeSet<String>,
145    terminal_launches: BTreeMap<String, StructuredLaunch>,
146    runtime_sequences: BTreeMap<String, u64>,
147    next_runtime: u64,
148    reduction_store_root: Option<PathBuf>,
149    /// ORCH-9: live permission/approval requests outstanding on the open
150    /// runtime connections above, fed by the same event pump that publishes
151    /// `harness.v1.runtimes.event`.
152    approvals: crate::approvals::ApprovalRegistry,
153    /// ORCH-9: supercode's own queued subagent approvals, when the host that
154    /// owns this service publishes its parent queue here.
155    subagent_approvals: Option<Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>>,
156}
157
158impl Default for HarnessSessionService {
159    fn default() -> Self {
160        Self::new()
161    }
162}
163
164impl HarnessSessionService {
165    /// Create an empty service instance.
166    pub fn new() -> Self {
167        Self {
168            catalog: HarnessCatalog::new(),
169            followers: BTreeMap::new(),
170            followed_sources: BTreeMap::new(),
171            activity_subscriptions: BTreeMap::new(),
172            index_subscriptions: BTreeMap::new(),
173            index_notifier: Arc::new(Notify::new()),
174            #[cfg(feature = "adapter-api")]
175            activity_monitor: Default::default(),
176            next_subscription: 1,
177            runtimes: BTreeMap::new(),
178            runtimes_in_flight: BTreeSet::new(),
179            terminal_launches: BTreeMap::new(),
180            runtime_sequences: BTreeMap::new(),
181            next_runtime: 1,
182            reduction_store_root: None,
183            approvals: crate::approvals::ApprovalRegistry::new(),
184            subagent_approvals: None,
185        }
186    }
187
188    /// Override the trusted, service-owned store used for durable reduction
189    /// bundles. Embedders and tests use this to keep all writes inside an
190    /// explicitly selected root; the CLI otherwise uses the normal
191    /// `$SUPERCODE_HOME/sessions` location.
192    pub fn with_reduction_store_root(mut self, root: impl Into<PathBuf>) -> Self {
193        self.reduction_store_root = Some(root.into());
194        self
195    }
196
197    /// ORCH-9: publish the parent's own subagent-approval queue into
198    /// `harness.v1.approvals.list`.
199    ///
200    /// This is the SAME `Arc` an [`crate::Agent`] pushes into
201    /// (`Agent::pending_child_approvals`), so a host that runs supercode's own
202    /// loop beside this service surfaces those requests through the uniform
203    /// door without copying them anywhere.
204    pub fn observe_subagent_approvals(
205        &mut self,
206        queue: Arc<std::sync::Mutex<Vec<crate::subagents::QueuedApproval>>>,
207    ) {
208        self.subagent_approvals = Some(queue);
209    }
210
211    /// ORCH-9: every approval request this service can see, newest last.
212    ///
213    /// Two sources, both live: the requests outstanding on the open runtime
214    /// connections, and supercode's own queued subagent approvals. There is
215    /// no file or database source at the pinned harness versions (see
216    /// [`crate::approvals`]), so a stored or proposal row is never produced.
217    pub fn approvals(&self, query: &crate::approvals::ApprovalsQuery) -> Vec<crate::ApprovalRow> {
218        let now = crate::approvals::now_ms();
219        let mut rows = self.approvals.rows(now);
220        if let Some(queue) = self.subagent_approvals.as_ref() {
221            let queued = queue
222                .lock()
223                .unwrap_or_else(std::sync::PoisonError::into_inner)
224                .clone();
225            rows.extend(crate::approvals::subagent_rows(&queued, now));
226        }
227        rows.retain(|row| query.matches(row));
228        rows.sort_by(|left, right| {
229            left.requested_at_ms
230                .cmp(&right.requested_at_ms)
231                .then_with(|| left.id.cmp(&right.id))
232        });
233        rows
234    }
235
236    /// ORCH-20 (controlled tier): answer one listed approval request by its
237    /// row id and one uniform decision.
238    ///
239    /// The decision is translated into the option token and reply envelope
240    /// the door that raised the request already accepts
241    /// ([`crate::approvals::plan_reply`]), and the answer is then sent by
242    /// calling `harness.v1.runtimes.respond` itself — the same code path, the
243    /// same adapter, the same bookkeeping that drops the row. This verb adds
244    /// a translation and nothing else.
245    async fn approvals_resolve(
246        &mut self,
247        params: Value,
248    ) -> std::result::Result<Value, ServiceError> {
249        let params = decode::<crate::approvals::ApprovalsResolveParams>(params)?;
250        if params.id.trim().is_empty() {
251            return Err(ServiceError::InvalidParams(
252                "approvals resolve requires the `id` of a listed approval row".into(),
253            ));
254        }
255        let choice = match (params.decision, params.option_id.as_deref()) {
256            (Some(_), Some(_)) => {
257                return Err(ServiceError::InvalidParams(
258                    "approvals resolve takes either `decision` or `option_id`, not both".into(),
259                ))
260            }
261            (Some(decision), None) => crate::approvals::ApprovalChoice::Decision(decision),
262            (None, Some(option)) => crate::approvals::ApprovalChoice::Option(option.to_string()),
263            (None, None) => {
264                return Err(ServiceError::InvalidParams(format!(
265                    "approvals resolve requires `decision` ({}) or an explicit `option_id`",
266                    crate::approvals::ApprovalDecision::ALL
267                        .map(|decision| decision.as_str())
268                        .join(" | "),
269                )))
270            }
271        };
272        let resolution = self
273            .approvals
274            .resolution(&params.id, &choice)
275            .map_err(|error| ServiceError::InvalidParams(error.to_string()))?;
276        // The harness's own door, unchanged: this is the identical call
277        // `harness.v1.runtimes.respond` performs for a caller who built the
278        // envelope by hand, including dropping the answered row.
279        self.runtime_call(
280            "harness.v1.runtimes.respond",
281            json!({
282                "connection": resolution.connection,
283                "request_id": resolution.request_id,
284                "response": resolution.response,
285            }),
286        )
287        .await?;
288        Ok(json!({
289            "id": params.id,
290            "decision": params.decision.map(|decision| decision.as_str()),
291            "option_id": resolution.option_id,
292            "resolved": true,
293        }))
294    }
295
296    /// Return the edge-triggered wakeup used by session-index filesystem
297    /// subscriptions. Transports can await this instead of polling indexes.
298    #[cfg(feature = "adapter-api")]
299    pub fn session_index_notifier(&self) -> Arc<Notify> {
300        Arc::clone(&self.index_notifier)
301    }
302
303    /// Handle one JSON-RPC 2.0 request and return one JSON-RPC response.
304    #[cfg(feature = "adapter-api")]
305    pub fn handle(&mut self, request: Value) -> Value {
306        let id = request.get("id").cloned().unwrap_or(Value::Null);
307        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
308            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
309        }
310        let Some(method) = request.get("method").and_then(Value::as_str) else {
311            return rpc_error(id, -32600, "request is missing `method`");
312        };
313        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
314        match self.call(method, params) {
315            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
316            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
317            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
318            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
319            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
320            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
321        }
322    }
323
324    /// Handle either a persisted-session request or an asynchronous live
325    /// runtime request.
326    #[cfg(feature = "adapter-api")]
327    pub async fn handle_async(&mut self, request: Value) -> Value {
328        let method = request
329            .get("method")
330            .and_then(Value::as_str)
331            .unwrap_or_default();
332        if matches!(
333            method,
334            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe"
335        ) {
336            let id = request.get("id").cloned().unwrap_or(Value::Null);
337            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
338                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
339            }
340            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
341            return match self.inventory_call(method, params).await {
342                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
343                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
344                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
345                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
346                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
347                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
348            };
349        }
350        if matches!(
351            method,
352            "harness.v1.harnesses.auth.methods"
353                | "harness.v1.harnesses.auth.begin"
354                | "harness.v1.harnesses.auth.verify"
355        ) {
356            let id = request.get("id").cloned().unwrap_or(Value::Null);
357            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
358                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
359            }
360            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
361            return match self.harness_authentication_call(method, params).await {
362                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
363                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
364                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
365                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
366                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
367                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
368            };
369        }
370        // ORCH-19 controlled tier. Answered here rather than through the SDK
371        // operation dispatch below so the harness's OWN refusal reaches the
372        // caller: `sdk_error` collapses every `UnsupportedAction` to one
373        // generic sentence, and the whole point of this tier is that a
374        // refusal names which door the harness does have.
375        if matches!(
376            method,
377            "harness.v1.sessions.new"
378                | "harness.v1.sessions.reset"
379                | "harness.v1.sessions.archive"
380                | "harness.v1.sessions.delete"
381        ) {
382            let id = request.get("id").cloned().unwrap_or(Value::Null);
383            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
384                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
385            }
386            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
387            let verb = match method {
388                "harness.v1.sessions.new" => crate::SessionVerb::New,
389                "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
390                "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
391                _ => crate::SessionVerb::Delete,
392            };
393            return match self.mutate_session(verb, params).await {
394                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
395                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
396                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
397                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
398                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
399                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
400            };
401        }
402        if method == "harness.v1.sessions.message" {
403            let id = request.get("id").cloned().unwrap_or(Value::Null);
404            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
405                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
406            }
407            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
408            return match self.message_call(params).await {
409                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
410                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
411                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
412                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
413                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
414                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
415            };
416        }
417        if matches!(
418            method,
419            "harness.v1.harnesses.settings" | "harness.v1.harnesses.configure"
420        ) {
421            let id = request.get("id").cloned().unwrap_or(Value::Null);
422            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
423                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
424            }
425            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
426            return match self.harness_settings_call(method, params) {
427                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
428                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
429                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
430                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
431                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
432                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
433            };
434        }
435        if method == "harness.v1.sessions.activity.subscribe" {
436            let id = request.get("id").cloned().unwrap_or(Value::Null);
437            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
438                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
439            }
440            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
441            return match self.subscribe_session_activity(params).await {
442                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
443                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
444                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
445                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
446                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
447                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
448            };
449        }
450        if let Some(operation) = SdkOperation::from_method(method) {
451            let id = request.get("id").cloned().unwrap_or(Value::Null);
452            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
453                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
454            }
455            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
456            return match self.execute(SdkRequest { operation, params }).await {
457                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
458                Err(error) => sdk_rpc_error(id, &error),
459            };
460        }
461        if !method.starts_with("harness.v1.runtimes.") {
462            return self.handle(request);
463        }
464        let id = request.get("id").cloned().unwrap_or(Value::Null);
465        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
466            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
467        }
468        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
469        match self.runtime_call(method, params).await {
470            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
471            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
472            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
473            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
474            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
475            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
476        }
477    }
478
479    /// Poll all active subscriptions once and return zero or more JSON-RPC
480    /// notifications. Recoverable follower errors are delivered as events.
481    #[cfg(feature = "adapter-api")]
482    pub fn poll(&mut self) -> Vec<Value> {
483        let mut notifications = Vec::new();
484        for (subscription, follower) in &mut self.followers {
485            match follower.poll() {
486                Ok(Some(event)) => notifications.push(json!({
487                    "jsonrpc": "2.0",
488                    "method": SESSION_EVENT_METHOD,
489                    "params": {
490                        "subscription": subscription,
491                        "event": event.to_json(),
492                    }
493                })),
494                Ok(None) => {}
495                Err(error) => notifications.push(json!({
496                    "jsonrpc": "2.0",
497                    "method": SESSION_EVENT_METHOD,
498                    "params": {
499                        "subscription": subscription,
500                        "event": {
501                            "type": "watch_error",
502                            "recoverable": true,
503                            "message": error.to_string(),
504                        },
505                    }
506                })),
507            }
508        }
509        notifications
510    }
511
512    /// Report each followed session's live-runtime lifecycle state on that
513    /// session's own subscription, emitting only when the state changes.
514    ///
515    /// A growing transcript is not evidence that an agent is working, so the
516    /// state comes from the live-runtime registry and nowhere else. A followed
517    /// session with no registered Supercode runtime — a harness running outside
518    /// Supercode — reports `persisted`, which says plainly that its activity is
519    /// unknown rather than guessing at it. These events carry no sequence
520    /// number and no transcript content; they never interleave with the
521    /// content follower's sequenced stream.
522    #[cfg(feature = "adapter-api")]
523    pub async fn poll_session_runtime_states(&mut self) -> Vec<Value> {
524        let registry = crate::LocalRuntimeRegistry::new();
525        let authorization = crate::RuntimeAuthorization::observer();
526        let mut notifications = Vec::new();
527        for (subscription, source) in &mut self.followed_sources {
528            let state = match registry
529                .source_state(&source.harness, &source.session_id, &authorization)
530                .await
531            {
532                Ok(Some(state)) => state,
533                Ok(None) => crate::RuntimeRegistryState::Persisted,
534                // A failed registry read is not evidence of a state change.
535                Err(_) => continue,
536            };
537            if source.reported.as_deref() == Some(state.as_str()) {
538                continue;
539            }
540            source.reported = Some(state.as_str().to_string());
541            notifications.push(json!({
542                "jsonrpc": "2.0",
543                "method": SESSION_EVENT_METHOD,
544                "params": {
545                    "subscription": subscription,
546                    "event": {"type": "runtime_state", "state": state.as_str()},
547                },
548            }));
549        }
550        notifications
551    }
552
553    /// Poll normalized activity subscriptions, emitting only proven state
554    /// transitions. Every subscription is bulk-sampled so stock-harness
555    /// process and registry discovery happens once per UI, not once per row.
556    #[cfg(feature = "adapter-api")]
557    pub async fn poll_session_activities(&mut self) -> Vec<Value> {
558        let subscriptions = self
559            .activity_subscriptions
560            .iter()
561            .map(|(id, subscription)| {
562                (
563                    id.clone(),
564                    subscription.locators.clone(),
565                    subscription.homes.clone(),
566                )
567            })
568            .collect::<Vec<_>>();
569        let mut notifications = Vec::new();
570        for (subscription_id, locators, homes) in subscriptions {
571            let Ok(activities) = self.activity_monitor.resolve(&locators, &homes).await else {
572                // A failed evidence read proves no transition. Retain the last
573                // good state instead of flashing every row to persisted.
574                continue;
575            };
576            let Some(subscription) = self.activity_subscriptions.get_mut(&subscription_id) else {
577                continue;
578            };
579            let mut changed = Vec::new();
580            for activity in activities {
581                let key = activity.key();
582                if subscription
583                    .reported
584                    .get(&key)
585                    .is_some_and(|previous| previous.same_state(&activity))
586                {
587                    continue;
588                }
589                subscription.reported.insert(key, activity.clone());
590                changed.push(activity);
591            }
592            if !changed.is_empty() {
593                notifications.push(json!({
594                    "jsonrpc": "2.0",
595                    "method": SESSION_ACTIVITY_EVENT_METHOD,
596                    "params": {
597                        "subscription": subscription_id,
598                        "activities": changed,
599                    },
600                }));
601            }
602        }
603        notifications
604    }
605
606    /// Drain native-store invalidations and emit revisioned descriptor deltas.
607    /// An idle subscription performs no catalog or transcript reads between
608    /// its minute-scale recovery reconciliations.
609    #[cfg(feature = "adapter-api")]
610    pub fn poll_session_indexes(&mut self) -> Vec<Value> {
611        let mut notifications = Vec::new();
612        for (subscription, index) in &mut self.index_subscriptions {
613            let homes = index.homes().clone();
614            match index.poll() {
615                Ok(Some(delta)) => match live_index_changes(delta.changes, &homes) {
616                    Ok(changes) => notifications.push(json!({
617                        "jsonrpc": "2.0",
618                        "method": SESSION_INDEX_EVENT_METHOD,
619                        "params": {
620                            "subscription": subscription,
621                            "revision": delta.revision,
622                            "changes": changes,
623                        },
624                    })),
625                    Err(error) => notifications.push(json!({
626                        "jsonrpc": "2.0",
627                        "method": SESSION_INDEX_EVENT_METHOD,
628                        "params": {
629                            "subscription": subscription,
630                            "error": {"recoverable": true, "message": error_message(error)},
631                        },
632                    })),
633                },
634                Ok(None) => {}
635                Err(error) => notifications.push(json!({
636                    "jsonrpc": "2.0",
637                    "method": SESSION_INDEX_EVENT_METHOD,
638                    "params": {
639                        "subscription": subscription,
640                        "error": {"recoverable": true, "message": error},
641                    },
642                })),
643            }
644        }
645        notifications
646    }
647
648    #[cfg(feature = "adapter-api")]
649    async fn subscribe_session_activity(
650        &mut self,
651        params: Value,
652    ) -> std::result::Result<Value, ServiceError> {
653        let params = decode::<ActivitySubscribeParams>(params)?;
654        if params.locators.is_empty() {
655            return Err(ServiceError::InvalidParams(
656                "sessions.activity.subscribe requires at least one locator".into(),
657            ));
658        }
659        if params.locators.len() > 2_048 {
660            return Err(ServiceError::InvalidParams(
661                "sessions.activity.subscribe accepts at most 2048 locators".into(),
662            ));
663        }
664        let initial = self
665            .activity_monitor
666            .resolve(&params.locators, &params.homes)
667            .await
668            .map_err(ServiceError::Sdk)?;
669        let subscription = format!("activity-sub-{}", self.next_subscription);
670        self.next_subscription += 1;
671        let reported = initial
672            .iter()
673            .cloned()
674            .map(|activity| (activity.key(), activity))
675            .collect();
676        self.activity_subscriptions.insert(
677            subscription.clone(),
678            ActivitySubscription {
679                locators: params.locators,
680                homes: params.homes,
681                reported,
682            },
683        );
684        Ok(json!({"subscription": subscription, "initial": initial}))
685    }
686
687    /// Non-blockingly sample one event from every connected live runtime.
688    #[cfg(feature = "adapter-api")]
689    pub async fn poll_runtimes(&mut self) -> Vec<Value> {
690        self.poll_sdk_events()
691            .await
692            .into_iter()
693            .map(|(connection, runtime_event)| {
694                json!({
695                    "jsonrpc": "2.0",
696                    "method": RUNTIME_EVENT_METHOD,
697                    "params": {
698                        "connection": connection,
699                        "session_id": runtime_event.session_id,
700                        "sequence": runtime_event.event.sequence,
701                        "event": {
702                            "kind": runtime_event.event.kind,
703                            "payload": runtime_event.event.payload,
704                        },
705                    },
706                })
707            })
708            .collect()
709    }
710
711    async fn poll_sdk_events(&mut self) -> Vec<(String, SdkRuntimeEvent)> {
712        let mut events = Vec::new();
713        let mut closed = Vec::new();
714        let now_ms = crate::approvals::now_ms();
715        for (connection, runtime) in &mut self.runtimes {
716            let session_id = runtime.handle().runtime_id.clone();
717            let harness = runtime.handle().harness.clone();
718            // Drain what the runtime already has: a turn is several events
719            // (updates, then the protocol's completion), and delivering one
720            // per poll would cost a poll interval each. A zero timeout takes
721            // only what is ready — an idle runtime costs nothing.
722            for _ in 0..256 {
723                match tokio::time::timeout(Duration::ZERO, runtime.next_event()).await {
724                    Ok(Ok(Some(event))) => {
725                        let terminal = event.kind == "transport_closed";
726                        // ORCH-9: a permission/approval request arrives as an
727                        // ordinary event; it becomes listable here and stops
728                        // being listable when `runtimes.respond` answers it.
729                        self.approvals
730                            .observe(connection, &harness, &session_id, &event, now_ms);
731                        let next_sequence = self
732                            .runtime_sequences
733                            .entry(session_id.clone())
734                            .or_insert(0);
735                        let sequence = event.sequence.unwrap_or_else(|| {
736                            *next_sequence = next_sequence.saturating_add(1);
737                            *next_sequence
738                        });
739                        *next_sequence = (*next_sequence).max(sequence);
740                        events.push((
741                            connection.clone(),
742                            SdkRuntimeEvent {
743                                session_id: session_id.clone(),
744                                event: SdkEvent {
745                                    sequence,
746                                    kind: event.kind,
747                                    payload: event.payload,
748                                },
749                            },
750                        ));
751                        if terminal {
752                            closed.push(connection.clone());
753                            break;
754                        }
755                    }
756                    Ok(Ok(None)) => {
757                        let sequence = self
758                            .runtime_sequences
759                            .entry(session_id.clone())
760                            .or_insert(0);
761                        *sequence = sequence.saturating_add(1);
762                        events.push((
763                        connection.clone(),
764                        SdkRuntimeEvent {
765                            session_id,
766                            event: SdkEvent {
767                                sequence: *sequence,
768                                kind: "transport_closed".into(),
769                                payload: json!({"message": "Harness runtime transport closed."}),
770                            },
771                        },
772                    ));
773                        closed.push(connection.clone());
774                        break;
775                    }
776                    Err(_) => break,
777                    Ok(Err(error)) => {
778                        let sequence = self
779                            .runtime_sequences
780                            .entry(session_id.clone())
781                            .or_insert(0);
782                        *sequence = sequence.saturating_add(1);
783                        events.push((
784                        connection.clone(),
785                        SdkRuntimeEvent {
786                            session_id,
787                            event: SdkEvent {
788                                sequence: *sequence,
789                                kind: "transport_error".into(),
790                                payload: json!({"message": error.to_string(), "terminal": true}),
791                            },
792                        },
793                    ));
794                        closed.push(connection.clone());
795                        break;
796                    }
797                }
798            }
799        }
800        for connection in closed {
801            if let Some(runtime) = self.runtimes.remove(&connection) {
802                self.runtime_sequences.remove(&runtime.handle().runtime_id);
803            }
804            self.terminal_launches.remove(&connection);
805            // A connection that is gone cannot answer anything it was
806            // holding; those requests stop being listable with it.
807            self.approvals.forget(&connection);
808        }
809        events
810    }
811
812    fn call(&mut self, method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
813        match method {
814            "harness.v1.capabilities" => Ok(json!({
815                "version": HARNESS_SERVICE_VERSION,
816                "sdk": self.capabilities(),
817                "methods": HARNESS_SERVICE_METHODS,
818                "notifications": [
819                    SESSION_EVENT_METHOD,
820                    SESSION_ACTIVITY_EVENT_METHOD,
821                    SESSION_INDEX_EVENT_METHOD,
822                    RUNTIME_EVENT_METHOD
823                ],
824                "harnesses": harness_support_registry()
825                    .harnesses
826                    .into_iter()
827                    .map(|harness| harness.id)
828                    .collect::<Vec<_>>(),
829            })),
830            "harness.v1.support.report" => serde_json::to_value(harness_support_registry())
831                .map_err(|error| ServiceError::Operation(error.to_string())),
832            "harness.v1.profiles.list" | "harness.v1.profiles.get" => profiles_call(method, params),
833            // ORCH-21 controlled tier. Each verb translates to the HARNESS'S
834            // OWN profile verb and runs it (`crate::profiles_control`);
835            // supercode makes and removes nothing itself. The row returned is
836            // re-read through the ORCH-10 loader afterwards, and `ran`
837            // narrates the exact command.
838            "harness.v1.profiles.create" => {
839                mutate_profile(crate::profiles_control::ProfileVerb::Create, params)
840            }
841            "harness.v1.profiles.delete" => {
842                mutate_profile(crate::profiles_control::ProfileVerb::Delete, params)
843            }
844            "harness.v1.channels.list" | "harness.v1.channels.status" => {
845                channels_call(method, params)
846            }
847            // ORCH-15 observed tier: which profile / agent a surface tuple
848            // resolves to, read from each gateway harness's own config.
849            "harness.v1.routes.list" => routes_call(params),
850            // ORCH-16 observed tier: inbound webhook routes / hook mappings.
851            "harness.v1.triggers.list" => triggers_call(params),
852            // ONT-4: the orchestration doors. One home folder in, one typed orchestration
853            // value out (and back). Every one of the four is
854            // `crate::orchestration_doors`, which the `supercode orchestration` verbs call
855            // too — the RPC adds nothing but the envelope. A vault VALUE
856            // never crosses this wire: a load or a compile answers with the
857            // `.env` KEY NAMES, and a caller that needs a value reads the
858            // home's own `.env`.
859            // the workflow layer's read door: a harness's board as one typed value,
860            // the same code the `supercode workflow load` verb calls
861            "harness.v1.workflow.load" => {
862                let params = decode::<WorkflowLoadParams>(params)?;
863                let read =
864                    crate::workflow_doors::load(params.from, &params.home).map_err(operation)?;
865                serde_json::to_value(read)
866                    .map_err(|error| ServiceError::Operation(error.to_string()))
867            }
868            "harness.v1.orchestration.load" => {
869                let params = decode::<OrchestrationLoadParams>(params)?;
870                let read = crate::orchestration_doors::load(&params.root, params.flavor)
871                    .map_err(operation)?;
872                serde_json::to_value(read)
873                    .map_err(|error| ServiceError::Operation(error.to_string()))
874            }
875            "harness.v1.orchestration.save" => {
876                let params = decode::<OrchestrationSaveParams>(params)?;
877                let saved = crate::orchestration_doors::save(
878                    &params.root,
879                    params.orchestration,
880                    params.vault,
881                )
882                .map_err(operation)?;
883                serde_json::to_value(saved)
884                    .map_err(|error| ServiceError::Operation(error.to_string()))
885            }
886            "harness.v1.orchestration.compile" => {
887                let params = decode::<OrchestrationCompileParams>(params)?;
888                let read = crate::orchestration_doors::compile(params.from, &params.home)
889                    .map_err(operation)?;
890                serde_json::to_value(read)
891                    .map_err(|error| ServiceError::Operation(error.to_string()))
892            }
893            "harness.v1.orchestration.decompile" => {
894                let params = decode::<OrchestrationDecompileParams>(params)?;
895                let report = crate::orchestration_doors::decompile(
896                    params.to,
897                    params.orchestration,
898                    &params.source,
899                    params.source_flavor,
900                    &params.dest,
901                    params.vault,
902                )
903                .map_err(operation)?;
904                serde_json::to_value(report)
905                    .map_err(|error| ServiceError::Operation(error.to_string()))
906            }
907            // a migration keeps the credential in this process: a compile and
908            // a save (import), a load and a decompile (export), composed here
909            // because composed by a client the secret would have to cross
910            // the wire
911            "harness.v1.orchestration.import" => {
912                let params = decode::<OrchestrationImportParams>(params)?;
913                let imported =
914                    crate::orchestration_doors::import(params.from, &params.home, &params.into)
915                        .map_err(operation)?;
916                serde_json::to_value(imported)
917                    .map_err(|error| ServiceError::Operation(error.to_string()))
918            }
919            "harness.v1.orchestration.export" => {
920                let params = decode::<OrchestrationExportParams>(params)?;
921                let report =
922                    crate::orchestration_doors::export(params.to, &params.root, &params.dest)
923                        .map_err(operation)?;
924                serde_json::to_value(report)
925                    .map_err(|error| ServiceError::Operation(error.to_string()))
926            }
927            // ORCH-12 observed tier: read and search the persistent memory
928            // documents a harness keeps on disk. Read-only — every write
929            // (`hermes memory off`, `openclaw memory forget|reset`, Claude
930            // Code's `/memory`) stays the harness's own verb. A harness with
931            // no memory store is refused with UnsupportedAction.
932            "harness.v1.memory.show" | "harness.v1.memory.search" => memory_call(method, params),
933            // ORCH-11 observed tier: read-only enumeration of every harness's
934            // installed skill packages. An unknown harness id is refused with
935            // UnsupportedAction — every harness supports skills, so a filter
936            // that matches nothing is a caller error, never an empty listing.
937            "harness.v1.skills.list" => {
938                let query = decode::<crate::skills::SkillsQuery>(params)?;
939                if let Some(harness) = query.harness.as_deref() {
940                    if !crate::skills::SKILL_HARNESSES.contains(&harness) {
941                        return Err(ServiceError::UnsupportedAction(format!(
942                            "`{harness}` has no skills root supercode reads"
943                        )));
944                    }
945                }
946                serde_json::to_value(crate::skills::list_skills(&query))
947                    .map_err(|error| ServiceError::Operation(error.to_string()))
948            }
949            // ORCH-22 controlled tier: each verb goes through the door the
950            // HARNESS publishes — `hermes skills install|uninstall`,
951            // `openclaw skills install`, and for the core four the loader's
952            // own directory, which is the only skills door those harnesses
953            // have. supercode resolves no registry and unpacks no archive.
954            // The row returned is re-read through the ORCH-11 loader
955            // afterwards, and `ran` narrates exactly what was performed.
956            "harness.v1.skills.install" => {
957                mutate_skill(crate::skills_control::SkillVerb::Install, params)
958            }
959            "harness.v1.skills.remove" => {
960                mutate_skill(crate::skills_control::SkillVerb::Remove, params)
961            }
962            // ORCH-9 observed tier: the approval requests waiting for an
963            // answer. At the pinned harness versions the only uniform source
964            // is a LIVE request held by an open runtime connection, plus
965            // supercode's own queued subagent approvals — neither Hermes
966            // 0.21.0 nor OpenClaw 2026.7.1-2 has an approvals door to read
967            // (see `crate::approvals`). A harness whose runtime cannot carry
968            // a protocol request at all is refused by name.
969            "harness.v1.approvals.list" => {
970                let query = decode::<crate::approvals::ApprovalsQuery>(params)?;
971                if let Some(harness) = query.harness.as_deref() {
972                    if !crate::approvals::lists_approvals(harness) {
973                        return Err(ServiceError::UnsupportedAction(format!(
974                            "`{harness}` has no runtime door that carries an approval request"
975                        )));
976                    }
977                }
978                serde_json::to_value(self.approvals(&query))
979                    .map_err(|error| ServiceError::Operation(error.to_string()))
980            }
981            "harness.v1.sessions.discover" => {
982                let query = decode::<DiscoveryQuery>(params)?;
983                let page = discover_session_page(&query).map_err(operation)?;
984                // Claude Code is the one harness that publishes its RUNNING
985                // sessions. The registry is read once per discovery and joined
986                // by session id; every record in it has already survived a
987                // `kill(pid, 0)` liveness check inside `read_registry`.
988                let peers = if page
989                    .sessions
990                    .iter()
991                    .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
992                {
993                    crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(
994                        &query.homes,
995                    ))
996                } else {
997                    Vec::new()
998                };
999                let activities = crate::session_activity::resolve_stock_session_activities(
1000                    &page
1001                        .sessions
1002                        .iter()
1003                        .map(|session| session.locator.clone())
1004                        .collect::<Vec<_>>(),
1005                    &query.homes,
1006                )
1007                .into_iter()
1008                .map(|activity| (activity.key(), activity))
1009                .collect::<BTreeMap<_, _>>();
1010                let sessions = page
1011                    .sessions
1012                    .into_iter()
1013                    .map(|session| {
1014                        let mut value = live_descriptor_value(&session, &peers)?;
1015                        let activity_key = (
1016                            session.locator.harness.as_str().to_string(),
1017                            session.locator.session_id.clone(),
1018                        );
1019                        if let Some(activity) = activities.get(&activity_key) {
1020                            value["activity"] = serde_json::to_value(activity)
1021                                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1022                            if let Some(status) = legacy_live_status(activity) {
1023                                value["live_status"] = json!(status);
1024                            }
1025                        }
1026                        Ok(value)
1027                    })
1028                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1029                let mut result = json!({"sessions": sessions, "next_cursor": page.next_cursor});
1030                // Preserve the metadata-only wire shape, but carry the catalog's
1031                // proof/counts when the caller explicitly requests preview search.
1032                if query.search_previews {
1033                    result["receipt"] = serde_json::to_value(page.receipt)
1034                        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1035                }
1036                Ok(result)
1037            }
1038            "harness.v1.sessions.load" => {
1039                let params = decode::<LoadSessionParams>(params)?;
1040                if let Some(options) = &params.options {
1041                    options.validate()?;
1042                    if let Some(result) = indexed_claude_window(&params.read.locator, options)? {
1043                        return Ok(result);
1044                    }
1045                    return load_session(&params.read.locator)
1046                        .map(|session| projected_session_result(&session, options))
1047                        .map_err(operation);
1048                }
1049                let mut session = if params.read.display_history() {
1050                    self.catalog
1051                        .load_display_view(
1052                            &params.read.locator,
1053                            params.read.read_fidelity(),
1054                            params.read.tail_messages().unwrap_or(500),
1055                        )
1056                        .map_err(crate::Error::from)
1057                } else if params.read.include_subagents() {
1058                    load_session_with_fidelity(&params.read.locator, params.read.read_fidelity())
1059                } else {
1060                    self.catalog
1061                        .load_parent_with_fidelity(
1062                            &params.read.locator,
1063                            params.read.read_fidelity(),
1064                        )
1065                        .map_err(crate::Error::from)
1066                }
1067                .map_err(operation)?;
1068                params.read.bound_session(&mut session);
1069                Ok(json!({"session": normalized_session_json(&session)}))
1070            }
1071            "harness.v1.sessions.follow" => {
1072                let params = decode::<LocatorParams>(params)?;
1073                let mut follower = self
1074                    .catalog
1075                    .follow_read_view(
1076                        &params.locator,
1077                        params.read_fidelity(),
1078                        params.include_subagents(),
1079                        params.tail_messages(),
1080                        params.max_message_chars(),
1081                        params.display_history(),
1082                    )
1083                    .map_err(operation)?;
1084                let initial = follower
1085                    .poll()
1086                    .map_err(operation)?
1087                    .map(|event| event.to_json());
1088                let subscription = format!("sub-{}", self.next_subscription);
1089                self.next_subscription += 1;
1090                self.followers.insert(subscription.clone(), follower);
1091                self.followed_sources.insert(
1092                    subscription.clone(),
1093                    FollowedSource {
1094                        harness: params.locator.harness.as_str().to_string(),
1095                        session_id: params.locator.session_id.clone(),
1096                        reported: None,
1097                    },
1098                );
1099                Ok(json!({"subscription": subscription, "initial": initial}))
1100            }
1101            "harness.v1.sessions.unfollow" => {
1102                let params = decode::<UnfollowParams>(params)?;
1103                self.followed_sources.remove(&params.subscription);
1104                Ok(json!({
1105                    "removed": self.followers.remove(&params.subscription).is_some()
1106                }))
1107            }
1108            "harness.v1.sessions.activity.unsubscribe" => {
1109                let params = decode::<UnfollowParams>(params)?;
1110                Ok(json!({
1111                    "removed": self.activity_subscriptions.remove(&params.subscription).is_some()
1112                }))
1113            }
1114            "harness.v1.sessions.index.subscribe" => {
1115                let query = decode::<DiscoveryQuery>(params)?;
1116                crate::session_index::validate_query(&query)
1117                    .map_err(ServiceError::InvalidParams)?;
1118                let homes = query.homes.clone();
1119                let (index, initial) = crate::session_index::SessionIndexSubscription::open(
1120                    query,
1121                    Arc::clone(&self.index_notifier),
1122                )
1123                .map_err(ServiceError::Operation)?;
1124                let peers = peers_for_descriptors(&initial, &homes);
1125                let initial = initial
1126                    .iter()
1127                    .map(|descriptor| live_descriptor_value(descriptor, &peers))
1128                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1129                let subscription = format!("index-sub-{}", self.next_subscription);
1130                self.next_subscription += 1;
1131                self.index_subscriptions.insert(subscription.clone(), index);
1132                Ok(json!({
1133                    "subscription": subscription,
1134                    "revision": 1,
1135                    "initial": initial,
1136                }))
1137            }
1138            "harness.v1.sessions.index.resize" => {
1139                let params = decode::<IndexResizeParams>(params)?;
1140                crate::session_index::validate_limit(params.limit)
1141                    .map_err(ServiceError::InvalidParams)?;
1142                let index = self
1143                    .index_subscriptions
1144                    .get_mut(&params.subscription)
1145                    .ok_or_else(|| {
1146                        ServiceError::InvalidParams("unknown session index subscription".into())
1147                    })?;
1148                let prepared = index
1149                    .prepare_resize(params.limit)
1150                    .map_err(ServiceError::Operation)?;
1151                let peers = peers_for_descriptors(&prepared.page.sessions, index.homes());
1152                let initial = prepared
1153                    .page
1154                    .sessions
1155                    .iter()
1156                    .map(|descriptor| live_descriptor_value(descriptor, &peers))
1157                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
1158                let response = json!({
1159                    "subscription": params.subscription,
1160                    "revision": prepared.revision,
1161                    "initial": initial,
1162                    "receipt": prepared.page.receipt,
1163                });
1164                index.commit_resize(prepared);
1165                Ok(response)
1166            }
1167            "harness.v1.sessions.index.unsubscribe" => {
1168                let params = decode::<UnfollowParams>(params)?;
1169                Ok(json!({
1170                    "removed": self.index_subscriptions.remove(&params.subscription).is_some()
1171                }))
1172            }
1173            "harness.v1.sessions.import" => {
1174                let params = decode::<ImportSessionParams>(params)?;
1175                let session = Session::load_str(&params.content, params.source_harness.into())
1176                    .map_err(operation)?;
1177                Ok(json!({"session": normalized_session_json(&session)}))
1178            }
1179            "harness.v1.sessions.export" | "harness.v1.sessions.translate" => {
1180                let params = decode::<ExportSessionParams>(params)?;
1181                let session = load_session(&params.locator).map_err(operation)?;
1182                let artifact = session_artifact(&params.locator, &session, params.target_harness)?;
1183                if method == "harness.v1.sessions.export"
1184                    && params.target_harness == TransferFormat::Hermes
1185                {
1186                    // UNI-18: write through Hermes's own door, never into its store
1187                    let imported = crate::hermes_import::import_into_hermes(&session, None)
1188                        .map_err(operation)?;
1189                    return Ok(json!({"artifact": artifact, "imported": imported}));
1190                }
1191                Ok(json!({"artifact": artifact}))
1192            }
1193            "harness.v1.sessions.reduce" => {
1194                let params = decode::<ReduceSessionParams>(params)?;
1195                self.reduce_session(params)
1196            }
1197            "harness.v1.sessions.branch" => {
1198                let params = decode::<BranchSessionParams>(params)?;
1199                let session = load_session(&params.locator).map_err(operation)?;
1200                let storage = params.locator.storage.path().display().to_string();
1201                let bootstrap_prompt = format!(
1202                    "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.",
1203                    params.locator.harness.as_str(), params.locator.session_id, storage
1204                );
1205                let artifact = params
1206                    .target_harness
1207                    .map(|target| session_artifact(&params.locator, &session, target))
1208                    .transpose()?;
1209                Ok(json!({
1210                    "parent": params.locator,
1211                    "session": normalized_session_json(&session),
1212                    "bootstrap_prompt": bootstrap_prompt,
1213                    "artifact": artifact,
1214                }))
1215            }
1216            "harness.v1.sessions.handoff" => {
1217                let params = decode::<HandoffSessionParams>(params)?;
1218                let session = load_session(&params.locator).map_err(operation)?;
1219                let cwd = params
1220                    .cwd
1221                    .or_else(|| session.meta.cwd.clone())
1222                    .unwrap_or_else(|| PathBuf::from("."));
1223                let artifact =
1224                    handoff_artifact(&params.locator, &session, params.target_harness, &cwd)?;
1225                let target_session_id = artifact.session_id.as_deref().ok_or_else(|| {
1226                    ServiceError::Operation(
1227                        "handoff artifact omitted target session identity".into(),
1228                    )
1229                })?;
1230                let instructions =
1231                    handoff_instructions(params.target_harness, target_session_id, &cwd);
1232                Ok(json!({
1233                    "artifact": artifact,
1234                    "launch": instructions.launch,
1235                    "materialize": instructions.materialize,
1236                    "requires_materialization": instructions.requires_materialization,
1237                    "note": instructions.note,
1238                }))
1239            }
1240            // ORCH-7 observed tier. Read-only: the handlers open the harness's
1241            // own job store (Claude Code's session JSONL, Hermes's and
1242            // OpenClaw's `cron/jobs.json`) and never write, fire, or schedule.
1243            "harness.v1.jobs.list" => {
1244                let query = decode::<crate::jobs::JobsQuery>(params)?;
1245                if let Some(harness) = query.harness.as_deref() {
1246                    refuse_harness_without_jobs(harness, "jobs.list")?;
1247                }
1248                let listing = crate::jobs::list_jobs(&query).map_err(operation)?;
1249                serde_json::to_value(listing)
1250                    .map_err(|error| ServiceError::Operation(error.to_string()))
1251            }
1252            "harness.v1.jobs.get" => {
1253                let params = decode::<JobsGetParams>(params)?;
1254                refuse_harness_without_jobs(&params.harness, "jobs.get")?;
1255                match crate::jobs::get_job(&params.harness, &params.id, &params.homes)
1256                    .map_err(operation)?
1257                {
1258                    Some((job, source)) => Ok(json!({"job": job, "source": source})),
1259                    None => Err(ServiceError::Operation(format!(
1260                        "`{}` has no scheduled job `{}`",
1261                        params.harness, params.id
1262                    ))),
1263                }
1264            }
1265            // ORCH-18 controlled tier. Each verb translates to the HARNESS'S
1266            // OWN cron verb and runs it (`crate::jobs_control`); supercode
1267            // schedules nothing. The row returned is re-read from the
1268            // harness's store afterwards, and `ran` narrates the exact command
1269            // with any credential redacted.
1270            "harness.v1.jobs.create" => mutate_job(crate::jobs_control::JobVerb::Create, params),
1271            "harness.v1.jobs.update" => mutate_job(crate::jobs_control::JobVerb::Update, params),
1272            "harness.v1.jobs.pause" => mutate_job(crate::jobs_control::JobVerb::Pause, params),
1273            "harness.v1.jobs.resume" => mutate_job(crate::jobs_control::JobVerb::Resume, params),
1274            "harness.v1.jobs.run" => mutate_job(crate::jobs_control::JobVerb::Run, params),
1275            "harness.v1.jobs.delete" => mutate_job(crate::jobs_control::JobVerb::Delete, params),
1276            // ORCH-8 observed tier. Read-only: the handlers open the harness's
1277            // own run store (Hermes's `cron/executions.db`, OpenClaw's
1278            // `cron_run_logs`) and never claim, retry, or prune a fire.
1279            "harness.v1.runs.list" => {
1280                let query = decode::<crate::runs::RunsQuery>(params)?;
1281                if let Some(harness) = query.harness.as_deref() {
1282                    refuse_harness_without_runs(harness, "runs.list")?;
1283                }
1284                let listing = crate::runs::list_runs(&query).map_err(operation)?;
1285                serde_json::to_value(listing)
1286                    .map_err(|error| ServiceError::Operation(error.to_string()))
1287            }
1288            "harness.v1.runs.get" => {
1289                let params = decode::<RunsGetParams>(params)?;
1290                refuse_harness_without_runs(&params.harness, "runs.get")?;
1291                match crate::runs::get_run(&params.harness, &params.id, &params.homes)
1292                    .map_err(operation)?
1293                {
1294                    Some((run, source)) => Ok(json!({"run": run, "source": source})),
1295                    None => Err(ServiceError::Operation(format!(
1296                        "`{}` has no run `{}`",
1297                        params.harness, params.id
1298                    ))),
1299                }
1300            }
1301            "harness.v1.sessions.resume_instructions" => {
1302                let params = decode::<ResumeInstructionsParams>(params)?;
1303                let session = load_session(&params.locator).map_err(operation)?;
1304                let cwd = params
1305                    .cwd
1306                    .or(session.meta.cwd)
1307                    .unwrap_or_else(|| PathBuf::from("."));
1308                let launch = resume_launch(
1309                    params.locator.harness.as_str(),
1310                    &params.locator.session_id,
1311                    &cwd,
1312                    params.policy,
1313                )?;
1314                Ok(json!({"launch": launch}))
1315            }
1316            _ => Err(ServiceError::MethodNotFound),
1317        }
1318    }
1319
1320    fn reduce_session(
1321        &self,
1322        params: ReduceSessionParams,
1323    ) -> std::result::Result<Value, ServiceError> {
1324        let session = load_session(&params.locator).map_err(operation)?;
1325        if session.messages.is_empty() {
1326            return Err(ServiceError::InvalidParams(
1327                "cannot reduce an empty session".into(),
1328            ));
1329        }
1330        let keep_last = params.keep_last.clamp(1, 128);
1331        let policy = reduce::ReductionPolicy {
1332            clear_turns_older_than: Some(keep_last),
1333            ..Default::default()
1334        };
1335        let (view, log) =
1336            reduce::project_messages(&session.messages, &policy, &reduce::ReductionLog::default());
1337        if log.reductions.is_empty() {
1338            return Err(ServiceError::UnsupportedAction(format!(
1339                "session `{}` is already too small for a meaningful reversible reduction",
1340                params.locator.session_id
1341            )));
1342        }
1343        let source_tokens = tokens::estimate_view_tokens(&session.messages);
1344        let reduced_tokens = tokens::estimate_view_tokens(&view);
1345        if reduced_tokens >= source_tokens {
1346            return Err(ServiceError::UnsupportedAction(format!(
1347                "session `{}` has no token-reducing reversible projection",
1348                params.locator.session_id
1349            )));
1350        }
1351
1352        let store_root = self
1353            .reduction_store_root
1354            .clone()
1355            .unwrap_or_else(default_reduction_store_root);
1356        let store = crate::SessionStore::open(&store_root).map_err(operation)?;
1357        let rescue_id = format!("rescue-{}", generated_session_id());
1358        let imported = session
1359            .imported_message_count
1360            .unwrap_or(session.messages.len())
1361            .min(session.messages.len());
1362        let sidecar_jsonl = session.to_native_jsonl_v2(&session.messages[imported..]);
1363        let view_jsonl = messages_jsonl(&view)?;
1364        let title = format!(
1365            "Reduced {} continuation from {}",
1366            params.target_harness.id(),
1367            params.locator.session_id
1368        );
1369
1370        // Durability order is intentional: the full source of truth lands
1371        // before either object that can refer to it. A crash may leave an
1372        // unused sidecar, but can never leave a reduced view whose originals
1373        // were not durably written first.
1374        store
1375            .save_sidecar(&rescue_id, &sidecar_jsonl)
1376            .map_err(operation)?;
1377        store
1378            .save_reduction_log(&rescue_id, &log)
1379            .map_err(operation)?;
1380        store
1381            .save(&rescue_id, &title, &view_jsonl)
1382            .map_err(operation)?;
1383
1384        let source_bytes = serde_json::to_vec(&session.messages)
1385            .map_err(|error| ServiceError::Operation(error.to_string()))?
1386            .len() as u64;
1387        let reduced_bytes = serde_json::to_vec(&view)
1388            .map_err(|error| ServiceError::Operation(error.to_string()))?
1389            .len() as u64;
1390        store
1391            .set_reduction_stats(
1392                &rescue_id,
1393                &title,
1394                source_bytes,
1395                reduced_bytes,
1396                log.reductions.len() as u32,
1397            )
1398            .map_err(operation)?;
1399
1400        // The receipt is issued only after a real disk reload. This proves
1401        // the exact files another process will consume, not the convenient
1402        // in-memory values that produced them.
1403        let reloaded_sidecar = store
1404            .load_sidecar(&rescue_id)
1405            .map_err(operation)?
1406            .ok_or_else(|| ServiceError::Operation("reduction sidecar disappeared".into()))?;
1407        let reloaded_sidecar = Session::from_sidecar_str(&reloaded_sidecar).map_err(operation)?;
1408        let reloaded_log = store
1409            .load_reduction_log(&rescue_id)
1410            .map_err(operation)?
1411            .ok_or_else(|| ServiceError::Operation("reduction log disappeared".into()))?;
1412        let reloaded_view = parse_messages_jsonl(&store.load(&rescue_id).map_err(operation)?)?;
1413        reduce::verify_log(&reloaded_log, &reloaded_sidecar).map_err(operation)?;
1414        // `sc.reduction` is deliberately in-memory-only metadata: it must
1415        // never leak onto a provider-facing transcript. Reapplying the
1416        // durable log to the durable sidecar restores those ids. Comparing
1417        // its wire form with the transcript reloaded above proves that the
1418        // persisted view is exactly the deterministic projection before we
1419        // use the restamped form for inversion.
1420        let (restamped_view, restamped_log) =
1421            reduce::project_messages(&reloaded_sidecar.messages, &policy, &reloaded_log);
1422        if messages_jsonl(&restamped_view)? != messages_jsonl(&reloaded_view)? {
1423            return Err(ServiceError::Operation(
1424                "persisted reduction view does not match its durable log and sidecar".into(),
1425            ));
1426        }
1427        if restamped_log != reloaded_log {
1428            return Err(ServiceError::Operation(
1429                "reapplying the durable reduction log changed its identity".into(),
1430            ));
1431        }
1432        let inverted =
1433            reduce::invert(&restamped_view, &reloaded_log, &reloaded_sidecar).map_err(operation)?;
1434        if inverted != session.messages {
1435            return Err(ServiceError::Operation(
1436                "reduction inversion did not restore the source messages byte-exactly".into(),
1437            ));
1438        }
1439
1440        let ratio = source_tokens as f64 / reduced_tokens.max(1) as f64;
1441        let sidecar_path = store.sidecar_path(&rescue_id);
1442        let reduction_log_path = store.reduction_log_path(&rescue_id).map_err(operation)?;
1443        let bootstrap_prompt = reduced_bootstrap_prompt(
1444            &params.locator,
1445            params.target_harness,
1446            &view_jsonl,
1447            &sidecar_path,
1448            &reduction_log_path,
1449        );
1450        let mut reduced_session = session.clone();
1451        reduced_session.meta.session_id = Some(rescue_id.clone());
1452        reduced_session.messages = view;
1453
1454        Ok(json!({
1455            "session": normalized_session_json(&reduced_session),
1456            "bootstrap_prompt": bootstrap_prompt,
1457            "receipt": {
1458                "id": rescue_id,
1459                "sidecar_id": rescue_id,
1460                "source_harness": params.locator.harness,
1461                "target_harness": params.target_harness.id(),
1462                "source_tokens": source_tokens,
1463                "reduced_tokens": reduced_tokens,
1464                "ratio": ratio,
1465                "source_bytes": source_bytes,
1466                "reduced_bytes": reduced_bytes,
1467                "reductions": reloaded_log.reductions.len(),
1468                "sidecar_path": sidecar_path,
1469                "reduction_log_path": reduction_log_path,
1470                "verified": true,
1471                "reversible": true,
1472            }
1473        }))
1474    }
1475
1476    /// Recognize the one request family whose waiting happens entirely
1477    /// outside this service's state, and hand a transport the half it can run
1478    /// off the task that owns the service.
1479    ///
1480    /// Opening a runtime is the only door here that waits on a foreign
1481    /// program: it spawns the harness's own binary and completes that
1482    /// program's protocol handshake, which takes as long as the program takes
1483    /// to answer. A transport that awaited the whole request inline would
1484    /// stop reading its own input for that whole time, so ONE slow launch
1485    /// would queue every later request on the same server — including reads
1486    /// like `sessions.discover` that touch no runtime at all. Splitting the
1487    /// request lets the transport spawn [`RuntimeOpen::open`] and keep
1488    /// reading, then pay only the short bookkeeping half
1489    /// ([`Self::register_open_runtime`]) when the runtime is up.
1490    ///
1491    /// `None` for every other method: those are answered by
1492    /// [`Self::handle_async`] as before.
1493    pub fn runtime_open(request: &Value) -> Option<RuntimeOpen> {
1494        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1495            return None;
1496        }
1497        let method = request.get("method").and_then(Value::as_str)?;
1498        if !RUNTIME_OPEN_METHODS.contains(&method) {
1499            return None;
1500        }
1501        Some(RuntimeOpen {
1502            id: request.get("id").cloned().unwrap_or(Value::Null),
1503            method: method.to_string(),
1504            params: request.get("params").cloned().unwrap_or_else(|| json!({})),
1505        })
1506    }
1507
1508    /// Recognize a [`DETACHED_METHODS`] request and hand a transport the
1509    /// whole of it: the service-state half is read here and now, and what
1510    /// remains waits on a foreign program with nothing of this service's in
1511    /// hand.
1512    ///
1513    /// Same reason as [`Self::runtime_open`], different doors. Probing a
1514    /// harness starts it and completes its handshake; couriering a message
1515    /// runs a `claude` process to completion; a conversation verb runs the
1516    /// harness's own CLI or calls its HTTP API. A transport that awaited any
1517    /// of those inline would stop reading its own input for that whole time,
1518    /// so one probe of an unhealthy harness would queue every later request
1519    /// on the same server.
1520    ///
1521    /// Unlike an opening runtime there is no bookkeeping half: the answer
1522    /// [`DetachedCall::run`] produces is the caller's complete response, so a
1523    /// transport writes it without coming back here.
1524    ///
1525    /// `None` for every other method — including the LIVE `sessions.new` /
1526    /// `sessions.reset` door and `runtimes.close`, which wait on a runtime
1527    /// connection this service owns and so are split off by
1528    /// [`Self::detach_runtime`] instead.
1529    pub fn detach(&self, request: &Value) -> Option<DetachedCall> {
1530        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1531            return None;
1532        }
1533        let method = request.get("method").and_then(Value::as_str)?;
1534        if !DETACHED_METHODS.contains(&method) {
1535            return None;
1536        }
1537        let id = request.get("id").cloned().unwrap_or(Value::Null);
1538        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1539        let work = match method {
1540            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe" => self
1541                .inventory_work(method, params)
1542                .map(DetachedWork::Inventory),
1543            "harness.v1.sessions.message" => {
1544                decode::<MessageSessionParams>(params).map(DetachedWork::Message)
1545            }
1546            _ => {
1547                let verb = match method {
1548                    "harness.v1.sessions.new" => crate::SessionVerb::New,
1549                    "harness.v1.sessions.reset" => crate::SessionVerb::Reset,
1550                    "harness.v1.sessions.archive" => crate::SessionVerb::Archive,
1551                    _ => crate::SessionVerb::Delete,
1552                };
1553                match decode::<crate::SessionMutation>(params) {
1554                    Ok(mutation) => {
1555                        match crate::sessions_control::door(&mutation.harness, verb) {
1556                            // The live door needs the open runtime connection
1557                            // this service owns; it stays inline.
1558                            Ok(crate::SessionDoor::Live(_)) => return None,
1559                            Ok(_) => Ok(DetachedWork::SessionMutation { verb, mutation }),
1560                            Err(error) => Err(session_control_error(error)),
1561                        }
1562                    }
1563                    Err(error) => Err(error),
1564                }
1565            }
1566        };
1567        Some(DetachedCall {
1568            id,
1569            method: method.to_string(),
1570            work: work.map(Work::Free),
1571        })
1572    }
1573
1574    /// Recognize the two doors that wait on a runtime THIS SERVICE OWNS, and
1575    /// hand a transport the whole of each by lending the connection out.
1576    ///
1577    /// `runtimes.close` surrenders its runtime for good; the LIVE
1578    /// `sessions.new` / `sessions.reset` door borrows one for the length of
1579    /// the slash command and gives it back through
1580    /// [`Self::finish_detached`]. Both are bounded by
1581    /// [`RUNTIME_CONTROL_DEADLINE`], and a wedged runtime spends all of it —
1582    /// which is exactly as long as a transport that awaited them inline would
1583    /// stop reading its own input.
1584    ///
1585    /// `None` for every other method, and for the `sessions.new` /
1586    /// `sessions.reset` doors that are not live: [`Self::detach`] owns those.
1587    pub fn detach_runtime(&mut self, request: &Value) -> Option<DetachedCall> {
1588        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
1589            return None;
1590        }
1591        let method = request.get("method").and_then(Value::as_str)?;
1592        let id = request.get("id").cloned().unwrap_or(Value::Null);
1593        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
1594        let work = match method {
1595            "harness.v1.runtimes.close" => decode::<RuntimeConnectionParams>(params)
1596                .and_then(|params| self.surrender_runtime(&params.connection))
1597                .map(|(runtime, process_group)| {
1598                    Work::Runtime(RuntimeWork::Close {
1599                        runtime,
1600                        process_group,
1601                    })
1602                }),
1603            "harness.v1.sessions.new" | "harness.v1.sessions.reset" => {
1604                let verb = if method == "harness.v1.sessions.new" {
1605                    crate::SessionVerb::New
1606                } else {
1607                    crate::SessionVerb::Reset
1608                };
1609                let mutation = decode::<crate::SessionMutation>(params).ok()?;
1610                // Everything but the live door — including a refusal and a
1611                // request naming no connection — is `detach`'s or
1612                // `handle_async`'s to answer.
1613                let Ok(crate::SessionDoor::Live(command)) =
1614                    crate::sessions_control::door(&mutation.harness, verb)
1615                else {
1616                    return None;
1617                };
1618                let connection = mutation
1619                    .connection
1620                    .clone()
1621                    .filter(|value| !value.trim().is_empty())?;
1622                self.lend_runtime(&connection).map(|runtime| {
1623                    let session = live_session_name(runtime.as_ref(), &mutation);
1624                    Work::Runtime(RuntimeWork::LiveCommand {
1625                        connection,
1626                        runtime,
1627                        verb,
1628                        mutation,
1629                        command,
1630                        session,
1631                    })
1632                })
1633            }
1634            _ => return None,
1635        };
1636        Some(DetachedCall {
1637            id,
1638            method: method.to_string(),
1639            work,
1640        })
1641    }
1642
1643    /// Take back whatever a detached call borrowed and hand over the caller's
1644    /// response. Every answer from [`DetachedCall::run`] comes through here,
1645    /// so a lent-out connection is back in the service before the response
1646    /// that used it is written.
1647    pub fn finish_detached(&mut self, answer: DetachedAnswer) -> Value {
1648        let DetachedAnswer { response, returned } = answer;
1649        if let Some(ReturnedRuntime {
1650            connection,
1651            runtime,
1652        }) = returned
1653        {
1654            self.runtimes_in_flight.remove(&connection);
1655            self.runtimes.insert(connection, runtime);
1656        }
1657        response
1658    }
1659
1660    /// Answer a request split out by [`Self::runtime_open`] and already
1661    /// awaited by [`RuntimeOpen::open`]: register the runtime this service now
1662    /// owns and build its JSON-RPC response.
1663    pub async fn finish_runtime_open(&mut self, opened: OpenedRuntime) -> Value {
1664        let OpenedRuntime { id, outcome } = opened;
1665        let result = match outcome {
1666            Ok(open) => self.register_open_runtime(open).await,
1667            Err(error) => Err(error),
1668        };
1669        service_response(id, result)
1670    }
1671
1672    /// Take ownership of an opened runtime.
1673    async fn register_open_runtime(
1674        &mut self,
1675        open: OpenRuntime,
1676    ) -> std::result::Result<Value, ServiceError> {
1677        match open {
1678            OpenRuntime::Hosted {
1679                runtime,
1680                capabilities,
1681                workspace,
1682            } => {
1683                self.insert_hosted_runtime(runtime, capabilities, workspace)
1684                    .await
1685            }
1686            OpenRuntime::Joined { runtime } => self.insert_runtime(runtime),
1687        }
1688    }
1689
1690    async fn runtime_call(
1691        &mut self,
1692        method: &str,
1693        params: Value,
1694    ) -> std::result::Result<Value, ServiceError> {
1695        match method {
1696            "harness.v1.runtimes.capabilities" => {
1697                let params = decode::<RuntimeBackendParams>(params)?;
1698                let backend = runtime_backend(&params)?;
1699                Ok(json!({
1700                    "harness": backend.harness(),
1701                    "capabilities": backend.capabilities(),
1702                }))
1703            }
1704            method if RUNTIME_OPEN_METHODS.contains(&method) => {
1705                self.register_open_runtime(open_runtime(method, params).await?)
1706                    .await
1707            }
1708            "harness.v1.runtimes.send_input" => {
1709                let params = decode::<RuntimeInputParams>(params)?;
1710                let image_urls = validate_runtime_image_urls(params.image_urls)?;
1711                let runtime = self.runtime_mut(&params.connection)?;
1712                let turn_id = within_control_deadline(
1713                    method,
1714                    runtime.send_input(RuntimeInput {
1715                        text: params.text,
1716                        image_urls,
1717                    }),
1718                )
1719                .await?
1720                .map_err(operation)?;
1721                Ok(json!({"turn_id": turn_id}))
1722            }
1723            "harness.v1.runtimes.interrupt" => {
1724                let params = decode::<RuntimeConnectionParams>(params)?;
1725                within_control_deadline(method, self.runtime_mut(&params.connection)?.interrupt())
1726                    .await?
1727                    .map_err(operation)?;
1728                Ok(json!({}))
1729            }
1730            "harness.v1.runtimes.steer" => {
1731                let params = decode::<RuntimeInputParams>(params)?;
1732                if !params.image_urls.is_empty() {
1733                    return Err(ServiceError::InvalidParams(
1734                        "runtime steering accepts text only".into(),
1735                    ));
1736                }
1737                let text = params.text.trim();
1738                if text.is_empty() || text.chars().count() > 50_000 {
1739                    return Err(ServiceError::InvalidParams(
1740                        "runtime steering requires 1 to 50,000 text characters".into(),
1741                    ));
1742                }
1743                within_control_deadline(
1744                    method,
1745                    self.runtime_mut(&params.connection)?
1746                        .steer(text.to_string()),
1747                )
1748                .await?
1749                .map_err(operation)?;
1750                Ok(json!({}))
1751            }
1752            "harness.v1.runtimes.respond" => {
1753                let params = decode::<RuntimeRespondParams>(params)?;
1754                let request_id = params.request_id.clone();
1755                within_control_deadline(
1756                    method,
1757                    self.runtime_mut(&params.connection)?
1758                        .respond(params.request_id, params.response),
1759                )
1760                .await?
1761                .map_err(operation)?;
1762                // ORCH-9: an answered request is no longer waiting for one.
1763                self.approvals.answered(&params.connection, &request_id);
1764                Ok(json!({}))
1765            }
1766            "harness.v1.runtimes.terminal_instructions" => {
1767                let params = decode::<RuntimeConnectionParams>(params)?;
1768                let launch = self
1769                    .terminal_launches
1770                    .get(&params.connection)
1771                    .ok_or_else(|| {
1772                        ServiceError::Operation(
1773                            "this runtime is not hosted for terminal attachment".into(),
1774                        )
1775                    })?;
1776                Ok(json!({"launch":launch}))
1777            }
1778            "harness.v1.runtimes.close" => {
1779                let params = decode::<RuntimeConnectionParams>(params)?;
1780                let (runtime, process_group) = self.surrender_runtime(&params.connection)?;
1781                close_runtime(runtime, process_group).await
1782            }
1783            _ => Err(ServiceError::MethodNotFound),
1784        }
1785    }
1786
1787    /// Deliver one message into a session that is running right now.
1788    #[cfg(feature = "adapter-api")]
1789    async fn message_call(&self, params: Value) -> std::result::Result<Value, ServiceError> {
1790        let params = decode::<MessageSessionParams>(params)?;
1791        Ok(message_live_session(&params, &crate::claude_peer::ProcessCourierRunner).await)
1792    }
1793
1794    #[cfg(feature = "adapter-api")]
1795    fn harness_settings_call(
1796        &self,
1797        method: &str,
1798        params: Value,
1799    ) -> std::result::Result<Value, ServiceError> {
1800        let homes = crate::HarnessHomes::default();
1801        match method {
1802            "harness.v1.harnesses.settings" => {
1803                let params = decode::<HarnessSettingsParams>(params)?;
1804                let report = crate::inspect_harness_interop_settings(&homes, &params.harness)
1805                    .map_err(|error| ServiceError::Operation(error.to_string()))?;
1806                serde_json::to_value(report)
1807                    .map_err(|error| ServiceError::Operation(error.to_string()))
1808            }
1809            "harness.v1.harnesses.configure" => {
1810                let params = decode::<ConfigureHarnessParams>(params)?;
1811                let report = crate::configure_harness_interop_settings(
1812                    &homes,
1813                    &params.harness,
1814                    &params.changes,
1815                    params.expected_revision.as_deref(),
1816                )
1817                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1818                serde_json::to_value(report)
1819                    .map_err(|error| ServiceError::Operation(error.to_string()))
1820            }
1821            _ => Err(ServiceError::MethodNotFound),
1822        }
1823    }
1824
1825    fn insert_runtime(
1826        &mut self,
1827        runtime: Box<dyn RuntimeConnection>,
1828    ) -> std::result::Result<Value, ServiceError> {
1829        let connection = format!("runtime-{}", self.next_runtime);
1830        self.next_runtime += 1;
1831        let handle = runtime.handle().clone();
1832        self.runtime_sequences
1833            .entry(handle.runtime_id.clone())
1834            .or_insert(0);
1835        self.runtimes.insert(connection.clone(), runtime);
1836        Ok(json!({"connection": connection, "handle": handle}))
1837    }
1838
1839    #[cfg(feature = "adapter-api")]
1840    async fn insert_hosted_runtime(
1841        &mut self,
1842        runtime: Box<dyn RuntimeConnection>,
1843        capabilities: crate::RuntimeCapabilities,
1844        workspace: PathBuf,
1845    ) -> std::result::Result<Value, ServiceError> {
1846        let (host, connection) = HostedHarnessRuntime::spawn(runtime, capabilities);
1847        let token: std::sync::Arc<str> = crate::server::generate_token().into();
1848        let server = crate::server::run_frontend_http(
1849            host.clone(),
1850            host.frontend_sender(),
1851            "127.0.0.1:0",
1852            token.clone(),
1853        )
1854        .await
1855        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1856        let source = LiveRuntimeSource {
1857            harness: connection.handle().harness.as_str().to_string(),
1858            session_id: connection.handle().runtime_id.clone(),
1859            workspace: workspace.clone(),
1860        };
1861        let registration = register_live_runtime(
1862            connection.handle().runtime_id.clone(),
1863            source.clone(),
1864            format!("http://{}", server.address()),
1865            token.to_string(),
1866        )
1867        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1868        let endpoint = registration.endpoint().to_string();
1869        let launch = StructuredLaunch {
1870            cwd: workspace,
1871            // Pin attachment to the executable hosting this runtime. A bare
1872            // `supercode` could resolve to an older global install whose CLI
1873            // does not understand the receipt it is being asked to open.
1874            program: std::env::current_exe()
1875                .ok()
1876                .map(|path| path.to_string_lossy().into_owned())
1877                .unwrap_or_else(|| "supercode".into()),
1878            arguments: vec![
1879                "harness".into(),
1880                "attach".into(),
1881                "--endpoint".into(),
1882                endpoint,
1883                "--harness".into(),
1884                source.harness,
1885                "--session".into(),
1886                source.session_id,
1887            ],
1888            env: BTreeMap::new(),
1889        };
1890        let lease = HostedRuntimeLease {
1891            connection,
1892            _host: host,
1893            _registration: registration,
1894            _server: server,
1895        };
1896        let opened = self.insert_runtime(Box::new(lease))?;
1897        let connection_id = opened["connection"]
1898            .as_str()
1899            .expect("insert_runtime returns a connection id")
1900            .to_string();
1901        self.terminal_launches.insert(connection_id, launch);
1902        Ok(opened)
1903    }
1904
1905    #[cfg(not(feature = "adapter-api"))]
1906    async fn insert_hosted_runtime(
1907        &mut self,
1908        runtime: Box<dyn RuntimeConnection>,
1909        _capabilities: crate::RuntimeCapabilities,
1910        _workspace: PathBuf,
1911    ) -> std::result::Result<Value, ServiceError> {
1912        self.insert_runtime(runtime)
1913    }
1914
1915    fn runtime_mut(
1916        &mut self,
1917        connection: &str,
1918    ) -> std::result::Result<&mut Box<dyn RuntimeConnection>, ServiceError> {
1919        if self.runtimes_in_flight.contains(connection) {
1920            return Err(self.lent_out(connection));
1921        }
1922        self.runtimes.get_mut(connection).ok_or_else(|| {
1923            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
1924        })
1925    }
1926
1927    /// What a caller is told about a connection that is out on a detached
1928    /// call. It is not gone and it is not free: it is mid-call, which is the
1929    /// same answer the runtime itself gives a second turn.
1930    fn lent_out(&self, connection: &str) -> ServiceError {
1931        ServiceError::Operation(format!(
1932            "runtime connection `{connection}`: a harness turn is already in progress"
1933        ))
1934    }
1935
1936    /// Take a runtime OUT of the service for the duration of one detached
1937    /// call, leaving its name marked as lent out.
1938    fn lend_runtime(
1939        &mut self,
1940        connection: &str,
1941    ) -> std::result::Result<Box<dyn RuntimeConnection>, ServiceError> {
1942        if self.runtimes_in_flight.contains(connection) {
1943            return Err(self.lent_out(connection));
1944        }
1945        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
1946            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
1947        })?;
1948        self.runtimes_in_flight.insert(connection.to_string());
1949        Ok(runtime)
1950    }
1951
1952    /// Surrender a runtime for good: the connection and everything the
1953    /// service hung off it are gone before its teardown is even attempted.
1954    ///
1955    /// `close` is what a caller reaches for when a runtime has stopped
1956    /// answering, and a runtime that has stopped answering is exactly the one
1957    /// whose graceful close cannot complete: a hosted runtime's own loop
1958    /// parks on the call the runtime never answered, so it never dequeues the
1959    /// shutdown either. Keeping the entry until teardown succeeded made a
1960    /// wedged runtime permanent — every later call on that connection, and
1961    /// every new turn, answered "a harness turn is already in progress" with
1962    /// no way to take the connection back.
1963    fn surrender_runtime(
1964        &mut self,
1965        connection: &str,
1966    ) -> std::result::Result<(Box<dyn RuntimeConnection>, Option<u32>), ServiceError> {
1967        if self.runtimes_in_flight.contains(connection) {
1968            return Err(self.lent_out(connection));
1969        }
1970        let runtime = self.runtimes.remove(connection).ok_or_else(|| {
1971            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
1972        })?;
1973        let process_group = runtime_process_group(runtime.handle());
1974        let runtime_id = runtime.handle().runtime_id.clone();
1975        self.terminal_launches.remove(connection);
1976        self.runtime_sequences.remove(&runtime_id);
1977        self.approvals.forget(connection);
1978        Ok((runtime, process_group))
1979    }
1980
1981    /// SIGKILL the process group of every runtime this service owns, without
1982    /// waiting on any of them.
1983    ///
1984    /// A host leaving for good calls this BEFORE dropping the service. The
1985    /// handle this service holds is not the runtime's connection: a hosted
1986    /// runtime's real transport lives in the task driving it, so neither
1987    /// exiting the process nor dropping these handles reaches the harness
1988    /// process — while dropping them does remove each runtime's live-runtime
1989    /// receipt. Signalling first is what keeps a removed receipt from
1990    /// advertising a harness that is still running.
1991    pub fn kill_all_runtime_groups(&self) -> usize {
1992        self.runtimes
1993            .values()
1994            .filter(|runtime| kill_runtime_process_group(runtime_process_group(runtime.handle())))
1995            .count()
1996    }
1997
1998    /// ORCH-19: run one conversation-lifecycle verb through the harness's own
1999    /// door.
2000    ///
2001    /// Two doors, one shape. A CLI / HTTP / own-store door is self-contained
2002    /// in [`crate::sessions_control`]. A LIVE door (Hermes's and OpenClaw's
2003    /// `/new` and `/reset`, which are slash commands their gateway interprets
2004    /// INSIDE a session) is performed here, because only the service owns the
2005    /// open runtime connection — the command is typed through the very same
2006    /// `send_input` path a human's message takes, so supercode invents no
2007    /// private channel.
2008    async fn mutate_session(
2009        &mut self,
2010        verb: crate::SessionVerb,
2011        params: Value,
2012    ) -> std::result::Result<Value, ServiceError> {
2013        let mutation = decode::<crate::SessionMutation>(params)?;
2014        let door = crate::sessions_control::door(&mutation.harness, verb)
2015            .map_err(session_control_error)?;
2016        let outcome = match door {
2017            // The live door types the slash command through an open hosted
2018            // runtime, which only exists with the `adapter-api` feature; the
2019            // CLI / HTTP / own-store doors below need nothing extra.
2020            #[cfg(not(feature = "adapter-api"))]
2021            crate::SessionDoor::Live(command) => {
2022                return Err(ServiceError::Operation(format!(
2023                    "`{}` performs `sessions.{}` by typing `{command}` into a live driven \
2024                     session, which needs this build's `adapter-api` feature",
2025                    mutation.harness,
2026                    verb.as_str()
2027                )));
2028            }
2029            #[cfg(feature = "adapter-api")]
2030            crate::SessionDoor::Live(command) => {
2031                let connection = mutation
2032                    .connection
2033                    .clone()
2034                    .filter(|value| !value.trim().is_empty())
2035                    .ok_or_else(|| {
2036                        ServiceError::InvalidParams(format!(
2037                            "`{}` performs `sessions.{}` by typing `{command}` into a live \
2038                             driven session: pass the `connection` of an open runtime \
2039                             (`harness.v1.runtimes.start`)",
2040                            mutation.harness,
2041                            verb.as_str()
2042                        ))
2043                    })?;
2044                let runtime = self.runtime_mut(&connection)?;
2045                let session = live_session_name(runtime.as_ref(), &mutation);
2046                // Typing into a live session is a control call on an open
2047                // runtime, and a wedged runtime never accepts one, so it is
2048                // bounded exactly like the other control verbs. A transport
2049                // with a loop of its own lends the connection out instead of
2050                // waiting here: see [`Self::detach_runtime`].
2051                return type_live_command(runtime.as_mut(), verb, &mutation, command, session)
2052                    .await;
2053            }
2054            _ => run_session_mutation(verb, &mutation).await?,
2055        };
2056        serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
2057    }
2058
2059    /// Answer an inventory request whole, for callers that have nowhere to
2060    /// put the waiting half. A transport with a loop of its own splits it
2061    /// instead: see [`Self::detach`].
2062    async fn inventory_call(
2063        &self,
2064        method: &str,
2065        params: Value,
2066    ) -> std::result::Result<Value, ServiceError> {
2067        run_inventory(self.inventory_work(method, params)?).await
2068    }
2069
2070    /// The half of an inventory request that reads this service's state:
2071    /// resolve the selection and count the persisted sessions each row
2072    /// reports. What remains — finding executables, asking them their
2073    /// version, and (at `probe: handshake`) starting each harness and
2074    /// completing its protocol handshake — touches no service state at all.
2075    fn inventory_work(
2076        &self,
2077        method: &str,
2078        params: Value,
2079    ) -> std::result::Result<InventoryWork, ServiceError> {
2080        let mut params = decode::<HarnessInventoryParams>(params)?;
2081        if method == "harness.v1.harnesses.probe" {
2082            let harness = params.harness.take().ok_or_else(|| {
2083                ServiceError::InvalidParams("harnesses.probe requires `harness`".into())
2084            })?;
2085            params.harnesses = vec![harness];
2086        }
2087        let selected = params
2088            .harnesses
2089            .iter()
2090            .map(HarnessId::as_str)
2091            .collect::<std::collections::BTreeSet<_>>();
2092        let supported = harness_support_registry()
2093            .harnesses
2094            .into_iter()
2095            .filter(|descriptor| selected.is_empty() || selected.contains(descriptor.id.as_str()))
2096            .collect::<Vec<_>>();
2097        if !params.harnesses.is_empty() && supported.len() != selected.len() {
2098            let known = supported
2099                .iter()
2100                .map(|harness| harness.id.as_str())
2101                .collect::<std::collections::BTreeSet<_>>();
2102            let missing = params
2103                .harnesses
2104                .iter()
2105                .filter(|id| !known.contains(id.as_str()))
2106                .map(HarnessId::as_str)
2107                .collect::<Vec<_>>();
2108            return Err(ServiceError::InvalidParams(format!(
2109                "unknown harness(es): {}",
2110                missing.join(", ")
2111            )));
2112        }
2113        let global_counts = params
2114            .include_sessions
2115            .then(|| self.session_counts(None, &params.harnesses));
2116        let workspace_counts = params
2117            .include_sessions
2118            .then(|| {
2119                params
2120                    .workspace
2121                    .as_deref()
2122                    .map(|workspace| self.session_counts(Some(workspace), &params.harnesses))
2123            })
2124            .flatten();
2125        Ok(InventoryWork {
2126            params,
2127            supported,
2128            global_counts,
2129            workspace_counts,
2130        })
2131    }
2132
2133    #[cfg(feature = "adapter-api")]
2134    async fn harness_authentication_call(
2135        &self,
2136        method: &str,
2137        params: Value,
2138    ) -> std::result::Result<Value, ServiceError> {
2139        match method {
2140            "harness.v1.harnesses.auth.methods" | "harness.v1.harnesses.auth.verify" => {
2141                let params = decode::<HarnessAuthenticationParams>(params)?;
2142                serde_json::to_value(crate::inspect_harness_authentication(&params.harness).await)
2143                    .map_err(|error| ServiceError::Operation(error.to_string()))
2144            }
2145            "harness.v1.harnesses.auth.begin" => {
2146                let params = decode::<BeginHarnessAuthenticationParams>(params)?;
2147                let cwd = params
2148                    .cwd
2149                    .or_else(|| std::env::current_dir().ok())
2150                    .unwrap_or_else(|| PathBuf::from("."));
2151                let plan = crate::harness_authentication_plan(
2152                    &params.harness,
2153                    params.environment,
2154                    params.method,
2155                    &cwd,
2156                )
2157                .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
2158                serde_json::to_value(plan)
2159                    .map_err(|error| ServiceError::Operation(error.to_string()))
2160            }
2161            _ => Err(ServiceError::MethodNotFound),
2162        }
2163    }
2164
2165    fn session_counts(
2166        &self,
2167        workspace: Option<&Path>,
2168        harnesses: &[HarnessId],
2169    ) -> BTreeMap<String, usize> {
2170        let mut counts = BTreeMap::new();
2171        for session in self
2172            .catalog
2173            .discover(&DiscoveryQuery {
2174                workspace: workspace.map(Path::to_path_buf),
2175                harnesses: harnesses.to_vec(),
2176                ..DiscoveryQuery::default()
2177            })
2178            .unwrap_or_default()
2179        {
2180            *counts
2181                .entry(session.locator.harness.as_str().to_string())
2182                .or_insert(0) += 1;
2183        }
2184        counts
2185    }
2186}
2187
2188#[async_trait::async_trait]
2189impl SdkService for HarnessSessionService {
2190    fn capabilities(&self) -> SdkCapabilities {
2191        SdkCapabilities::default()
2192    }
2193
2194    async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError> {
2195        if request.operation == SdkOperation::Events {
2196            let events = self
2197                .poll_sdk_events()
2198                .await
2199                .into_iter()
2200                .map(|(_, event)| event)
2201                .collect::<Vec<_>>();
2202            return serde_json::to_value(events).map_err(|error| {
2203                SdkError::new(
2204                    SdkErrorCode::Execution,
2205                    request.operation,
2206                    error.to_string(),
2207                )
2208            });
2209        }
2210        if self.runtimes.is_empty()
2211            && matches!(
2212                request.operation,
2213                SdkOperation::Input
2214                    | SdkOperation::Interrupt
2215                    | SdkOperation::Steer
2216                    | SdkOperation::Respond
2217                    | SdkOperation::Close
2218            )
2219        {
2220            return Err(SdkError::unsupported(request.operation));
2221        }
2222        let method = request
2223            .operation
2224            .method()
2225            .ok_or_else(|| SdkError::unsupported(request.operation))?;
2226        let result = match request.operation {
2227            SdkOperation::Discover
2228            | SdkOperation::Load
2229            | SdkOperation::Export
2230            | SdkOperation::ProfilesList
2231            | SdkOperation::ProfilesGet
2232            | SdkOperation::ProfilesCreate
2233            | SdkOperation::ProfilesDelete
2234            | SdkOperation::SkillsList
2235            | SdkOperation::SkillsInstall
2236            | SdkOperation::SkillsRemove
2237            | SdkOperation::ChannelsList
2238            | SdkOperation::RoutesList
2239            | SdkOperation::TriggersList
2240            | SdkOperation::ChannelsStatus
2241            | SdkOperation::MemoryShow
2242            | SdkOperation::MemorySearch
2243            | SdkOperation::JobsList
2244            | SdkOperation::JobsGet
2245            | SdkOperation::JobsCreate
2246            | SdkOperation::JobsUpdate
2247            | SdkOperation::JobsPause
2248            | SdkOperation::JobsResume
2249            | SdkOperation::JobsRun
2250            | SdkOperation::JobsDelete
2251            | SdkOperation::RunsList
2252            | SdkOperation::RunsGet
2253            | SdkOperation::ApprovalsList
2254            | SdkOperation::OrchestrationLoad
2255            | SdkOperation::OrchestrationSave
2256            | SdkOperation::OrchestrationCompile
2257            | SdkOperation::OrchestrationDecompile
2258            | SdkOperation::OrchestrationImport
2259            | SdkOperation::OrchestrationExport
2260            | SdkOperation::WorkflowLoad => self.call(method, request.params),
2261            // ORCH-20: answering needs the live connection, so it takes the
2262            // async door and ends in `harness.v1.runtimes.respond`.
2263            SdkOperation::ApprovalsResolve => self.approvals_resolve(request.params).await,
2264            SdkOperation::Start
2265            | SdkOperation::Resume
2266            | SdkOperation::Input
2267            | SdkOperation::Interrupt
2268            | SdkOperation::Steer
2269            | SdkOperation::Respond
2270            | SdkOperation::Close => self.runtime_call(method, request.params).await,
2271            // ORCH-19 controlled tier. Every verb goes through the HARNESS'S
2272            // OWN door — its CLI, its HTTP API, or its slash command typed
2273            // into a live driven session — and returns the row re-read from
2274            // the harness's store afterwards.
2275            SdkOperation::SessionsNew => {
2276                self.mutate_session(crate::SessionVerb::New, request.params)
2277                    .await
2278            }
2279            SdkOperation::SessionsReset => {
2280                self.mutate_session(crate::SessionVerb::Reset, request.params)
2281                    .await
2282            }
2283            SdkOperation::SessionsArchive => {
2284                self.mutate_session(crate::SessionVerb::Archive, request.params)
2285                    .await
2286            }
2287            SdkOperation::SessionsDelete => {
2288                self.mutate_session(crate::SessionVerb::Delete, request.params)
2289                    .await
2290            }
2291            SdkOperation::Events => unreachable!("handled before method dispatch"),
2292        };
2293        result.map_err(|error| sdk_error(request.operation, error))
2294    }
2295
2296    async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError> {
2297        Ok(self
2298            .poll_sdk_events()
2299            .await
2300            .into_iter()
2301            .map(|(_, event)| event)
2302            .collect())
2303    }
2304}
2305
2306#[cfg(feature = "adapter-api")]
2307struct HostedRuntimeLease {
2308    connection: HostedHarnessConnection,
2309    _host: std::sync::Arc<HostedHarnessRuntime>,
2310    _registration: LiveRuntimeRegistration,
2311    _server: crate::server::FrontendHttpServer,
2312}
2313
2314#[async_trait::async_trait]
2315#[cfg(feature = "adapter-api")]
2316impl RuntimeConnection for HostedRuntimeLease {
2317    fn handle(&self) -> &crate::RuntimeHandle {
2318        self.connection.handle()
2319    }
2320
2321    async fn send_input(&mut self, input: RuntimeInput) -> crate::Result<Option<String>> {
2322        self.connection.send_input(input).await
2323    }
2324
2325    async fn next_event(&mut self) -> crate::Result<Option<crate::HarnessEvent>> {
2326        self.connection.next_event().await
2327    }
2328
2329    async fn interrupt(&mut self) -> crate::Result<()> {
2330        self.connection.interrupt().await
2331    }
2332
2333    async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
2334        self.connection.respond(request_id, response).await
2335    }
2336
2337    async fn close(&mut self) -> crate::Result<()> {
2338        self.connection.close().await
2339    }
2340}
2341
2342/// One inventory request's waiting half, already separated from the service
2343/// state it reads. See [`HarnessSessionService::inventory_work`].
2344struct InventoryWork {
2345    params: HarnessInventoryParams,
2346    supported: Vec<crate::HarnessSupportDescriptor>,
2347    global_counts: Option<BTreeMap<String, usize>>,
2348    workspace_counts: Option<BTreeMap<String, usize>>,
2349}
2350
2351/// Perform one conversation-lifecycle verb through a door that is
2352/// self-contained in [`crate::sessions_control`]: the harness's own CLI, its
2353/// HTTP API, the orchestrator daemon's socket, or supercode's own store.
2354/// Touches no service state, so this runs on any task. The LIVE door is not
2355/// here — it types its slash command through a runtime connection the service
2356/// owns, and is performed by [`HarnessSessionService::mutate_session`].
2357async fn run_session_mutation(
2358    verb: crate::SessionVerb,
2359    mutation: &crate::SessionMutation,
2360) -> std::result::Result<crate::SessionMutationOutcome, ServiceError> {
2361    // Only the HTTP door actually awaits anything. The CLI, store and daemon
2362    // doors run the harness's own program, or its store, with calls that
2363    // block the calling THREAD from start to finish — a future that never
2364    // yields, which no timeout around it can interrupt and which would hold a
2365    // runtime worker for as long as the harness takes. They go to a blocking
2366    // task, where blocking is what the thread is for.
2367    let door =
2368        crate::sessions_control::door(&mutation.harness, verb).map_err(session_control_error)?;
2369    if let crate::SessionDoor::Http = door {
2370        return crate::sessions_control::mutate(verb, mutation)
2371            .await
2372            .map_err(session_control_error);
2373    }
2374    let mutation = mutation.clone();
2375    tokio::task::spawn_blocking(move || crate::sessions_control::mutate_blocking(verb, &mutation))
2376        .await
2377        .map_err(|error| {
2378            ServiceError::Operation(format!("the conversation verb could not be run: {error}"))
2379        })?
2380        .map_err(session_control_error)
2381}
2382
2383/// Probe every selected harness and assemble the report. Touches no service
2384/// state, so this runs on any task.
2385async fn run_inventory(work: InventoryWork) -> std::result::Result<Value, ServiceError> {
2386    let InventoryWork {
2387        params,
2388        supported,
2389        global_counts,
2390        workspace_counts,
2391    } = work;
2392    let probes = supported.into_iter().map(|descriptor| {
2393        let global = global_counts
2394            .as_ref()
2395            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2396        let workspace = workspace_counts
2397            .as_ref()
2398            .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
2399        probe_harness(descriptor, &params, global, workspace)
2400    });
2401    let harnesses = futures::future::join_all(probes).await;
2402    serde_json::to_value(HarnessInventoryReport {
2403        probe: params.probe,
2404        workspace: params.workspace,
2405        harnesses,
2406    })
2407    .map_err(|error| ServiceError::Operation(error.to_string()))
2408}
2409
2410async fn probe_harness(
2411    descriptor: crate::HarnessSupportDescriptor,
2412    params: &HarnessInventoryParams,
2413    global: Option<usize>,
2414    workspace: Option<usize>,
2415) -> LocalHarness {
2416    let launch = descriptor.runtime.default_launch.as_ref();
2417    // ORC-7: the orchestrator publishes no runtime launch — it is not an
2418    // adapter supercode connects a turn to. What "installed" means for it
2419    // is that its Node daemon entry is present, so the row answers from
2420    // that instead of from a PATH lookup it could never satisfy.
2421    let orchestrator_entry = (descriptor.id.as_str() == HarnessId::ORCHESTRATOR)
2422        .then(crate::orchestrator::daemon_entry)
2423        .and_then(Result::ok);
2424    let executable = match &orchestrator_entry {
2425        Some(entry) => Some(entry.clone()),
2426        None => launch.and_then(|launch| find_executable(&launch.program)),
2427    };
2428    let installed = executable.is_some();
2429    let version = if params.skip_versions || orchestrator_entry.is_some() {
2430        // The orchestrator's "executable" is a Node module, not a CLI
2431        // with a `--version` flag; running it to ask would start a daemon.
2432        None
2433    } else {
2434        match executable.as_deref() {
2435            Some(path) => executable_version(path).await,
2436            None => None,
2437        }
2438    };
2439    let configured = auth_evidence(descriptor.id.as_str());
2440    let mut auth = if configured {
2441        HarnessAuthState::Configured
2442    } else if matches!(
2443        descriptor.id.as_str(),
2444        HarnessId::CLAUDE_CODE | HarnessId::CODEX
2445    ) {
2446        // These two adapters have explicit native status/login contracts
2447        // and complete local evidence coverage (including Claude's macOS
2448        // Keychain-backed oauthAccount marker). Treating absent evidence
2449        // as unknown advertises a start that will only fail interactively.
2450        HarnessAuthState::Required
2451    } else {
2452        HarnessAuthState::Unknown
2453    };
2454    let mut runtime = if installed {
2455        HarnessRuntimeState::Degraded
2456    } else {
2457        HarnessRuntimeState::Unavailable
2458    };
2459    let is_orchestrator = descriptor.id.as_str() == HarnessId::ORCHESTRATOR;
2460    let mut reason = (!installed).then(|| {
2461        if is_orchestrator {
2462            format!(
2463                "{} is supported but its daemon entry `{}` was not found",
2464                descriptor.display_name,
2465                crate::orchestrator::DAEMON_ENTRY
2466            )
2467        } else {
2468            format!(
2469                "{} is supported but `{}` was not found on PATH",
2470                descriptor.display_name,
2471                launch
2472                    .map(|launch| launch.program.as_str())
2473                    .unwrap_or("executable")
2474            )
2475        }
2476    });
2477    let mut repair = (!installed).then(|| {
2478        if is_orchestrator {
2479            format!(
2480                "Install the `supercode-orchestrator` package so `{}` resolves.",
2481                crate::orchestrator::DAEMON_ENTRY
2482            )
2483        } else {
2484            format!(
2485                "Install {} and ensure `{}` is on PATH.",
2486                descriptor.display_name,
2487                launch
2488                    .map(|launch| launch.program.as_str())
2489                    .unwrap_or("its executable")
2490            )
2491        }
2492    });
2493
2494    if installed && params.probe == HarnessProbeLevel::Handshake {
2495        let backend_params = RuntimeBackendParams {
2496            harness: descriptor.id.clone(),
2497            protocol: None,
2498            launch: None,
2499            base_url: None,
2500            policy: RuntimePolicy::Default,
2501        };
2502        match runtime_backend(&backend_params) {
2503            Ok(backend) => {
2504                let cwd = params
2505                    .workspace
2506                    .clone()
2507                    .or_else(|| std::env::current_dir().ok())
2508                    .unwrap_or_else(|| PathBuf::from("."));
2509                let isolated = descriptor
2510                    .runtime
2511                    .default_launch
2512                    .clone()
2513                    .and_then(|launch| IsolatedProbeHome::new(descriptor.id.as_str(), launch).ok());
2514                let Some(isolated) = isolated else {
2515                    reason = Some(
2516                        "No-prompt runtime handshake could not create its isolated harness home."
2517                            .into(),
2518                    );
2519                    repair = Some(
2520                        "Check temporary-directory permissions, then run the handshake probe again."
2521                            .into(),
2522                    );
2523                    let running = probe_running_instance(descriptor.id.as_str());
2524                    return LocalHarness {
2525                        gateway: gateway_health(
2526                            descriptor.id.as_str(),
2527                            installed,
2528                            running.as_ref(),
2529                            version.as_deref(),
2530                        ),
2531                        id: descriptor.id,
2532                        display_name: descriptor.display_name,
2533                        supported: true,
2534                        installed,
2535                        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2536                        version,
2537                        auth,
2538                        runtime,
2539                        protocol: descriptor.runtime.protocol,
2540                        capabilities: descriptor.runtime.capabilities.clone(),
2541                        effective_capabilities: descriptor.runtime.capabilities,
2542                        sessions: HarnessSessionCounts { global, workspace },
2543                        running,
2544                        reason,
2545                        repair,
2546                    };
2547                };
2548                match tokio::time::timeout(
2549                    Duration::from_secs(30),
2550                    backend.start(RuntimeStartRequest {
2551                        cwd,
2552                        launch: Some(isolated.launch.clone()),
2553                        mcp_servers: Vec::new(),
2554                    }),
2555                )
2556                .await
2557                {
2558                    Ok(Ok(mut connection)) => {
2559                        match stabilize_handshake(connection.as_mut()).await {
2560                            Ok(()) => {
2561                                auth = HarnessAuthState::Ready;
2562                                runtime = HarnessRuntimeState::Ready;
2563                                reason = Some(
2564                                    "No-prompt runtime handshake remained healthy through the startup stabilization window; no model request was sent."
2565                                        .into(),
2566                                );
2567                                repair = None;
2568                            }
2569                            Err(message) => {
2570                                auth = if looks_like_auth_error(&message) {
2571                                    HarnessAuthState::Required
2572                                } else if configured {
2573                                    HarnessAuthState::Configured
2574                                } else {
2575                                    HarnessAuthState::Unknown
2576                                };
2577                                reason = Some(format!(
2578                                    "No-prompt runtime handshake became unhealthy during startup: {message}"
2579                                ));
2580                                repair = Some(if auth == HarnessAuthState::Required {
2581                                    format!(
2582                                        "Run `{}` interactively once and complete sign-in, then probe again.",
2583                                        launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2584                                    )
2585                                } else {
2586                                    "Run the harness directly to inspect its startup failure, then probe again."
2587                                        .into()
2588                                });
2589                            }
2590                        }
2591                        let _ =
2592                            tokio::time::timeout(Duration::from_secs(3), connection.close()).await;
2593                    }
2594                    Ok(Err(error)) => {
2595                        let message = truncate_text(&error.to_string(), 500);
2596                        auth = if looks_like_auth_error(&message) {
2597                            HarnessAuthState::Required
2598                        } else if configured {
2599                            HarnessAuthState::Configured
2600                        } else {
2601                            HarnessAuthState::Unknown
2602                        };
2603                        reason = Some(format!("No-prompt runtime handshake failed: {message}"));
2604                        repair = Some(if auth == HarnessAuthState::Required {
2605                            format!(
2606                                "Run `{}` interactively once and complete sign-in, then probe again.",
2607                                launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
2608                            )
2609                        } else {
2610                            "Check the harness installation and run the handshake probe again."
2611                                .into()
2612                        });
2613                    }
2614                    Err(_) => {
2615                        reason =
2616                            Some("No-prompt runtime handshake timed out after 30 seconds.".into());
2617                        repair = Some("Run the harness directly to check startup or authentication, then probe again.".into());
2618                    }
2619                }
2620                // Keep the isolated home alive through process teardown.
2621                // Otherwise the compiler may release the last meaningful
2622                // use after cloning `launch`, and a still-starting CLI can
2623                // recreate its state directory after Drop removed it.
2624                // Some Node-based launchers finish a short asynchronous
2625                // installation-id write just after their parent process
2626                // is reaped. Remove once immediately, allow that bounded
2627                // writer to settle, then perform the authoritative pass.
2628                let _ = isolated.cleanup();
2629                tokio::time::sleep(Duration::from_millis(250)).await;
2630                if let Err(error) = isolated.cleanup() {
2631                    auth = if configured {
2632                        HarnessAuthState::Configured
2633                    } else {
2634                        HarnessAuthState::Unknown
2635                    };
2636                    runtime = HarnessRuntimeState::Degraded;
2637                    reason = Some(format!(
2638                        "No-prompt runtime handshake could not remove its isolated harness home: {error}"
2639                    ));
2640                    repair = Some(
2641                        "Check temporary-directory permissions, remove the reported disposable probe home, then run the handshake again."
2642                            .into(),
2643                    );
2644                }
2645            }
2646            Err(error) => {
2647                reason = Some(error_message(error));
2648            }
2649        }
2650    } else if installed && configured {
2651        reason = Some("Executable and local authentication evidence found; use a handshake probe to verify readiness.".into());
2652    } else if installed && auth == HarnessAuthState::Required {
2653        reason = Some("Executable found, but no native authentication evidence is present.".into());
2654        repair = Some(format!(
2655            "Run `supercode harness login {}` to use the harness-owned sign-in flow.",
2656            descriptor.id.as_str()
2657        ));
2658    } else if installed {
2659        reason = Some("Executable found; authentication readiness is unknown until a no-prompt handshake succeeds.".into());
2660        repair = Some(format!(
2661            "Run `{}` interactively once if sign-in is required, or use `--probe handshake`.",
2662            launch
2663                .map(|launch| launch.program.as_str())
2664                .unwrap_or("the harness")
2665        ));
2666    }
2667
2668    let effective_capabilities = if installed {
2669        descriptor.runtime.capabilities.clone()
2670    } else {
2671        unavailable_capabilities()
2672    };
2673    let running = probe_running_instance(descriptor.id.as_str());
2674    LocalHarness {
2675        gateway: gateway_health(
2676            descriptor.id.as_str(),
2677            installed,
2678            running.as_ref(),
2679            version.as_deref(),
2680        ),
2681        id: descriptor.id,
2682        display_name: descriptor.display_name,
2683        supported: true,
2684        installed,
2685        executable: executable.map(|path| path.to_string_lossy().into_owned()),
2686        version,
2687        auth,
2688        runtime,
2689        protocol: descriptor.runtime.protocol,
2690        capabilities: descriptor.runtime.capabilities,
2691        effective_capabilities,
2692        sessions: HarnessSessionCounts { global, workspace },
2693        running,
2694        reason,
2695        repair,
2696    }
2697}
2698
2699async fn stabilize_handshake(connection: &mut dyn RuntimeConnection) -> Result<(), String> {
2700    let deadline = tokio::time::Instant::now() + Duration::from_secs(3);
2701    loop {
2702        let now = tokio::time::Instant::now();
2703        if now >= deadline {
2704            return Ok(());
2705        }
2706        match tokio::time::timeout(deadline - now, connection.next_event()).await {
2707            Err(_) => return Ok(()),
2708            Ok(Ok(Some(event))) => {
2709                if let Some(message) = handshake_event_failure(&event) {
2710                    return Err(truncate_text(&message, 500));
2711                }
2712            }
2713            Ok(Ok(None)) => return Err("runtime transport closed during startup".into()),
2714            Ok(Err(error)) => return Err(error.to_string()),
2715        }
2716    }
2717}
2718
2719fn handshake_event_failure(event: &crate::HarnessEvent) -> Option<String> {
2720    let detail = event
2721        .payload
2722        .get("message")
2723        .or_else(|| event.payload.get("line"))
2724        .and_then(Value::as_str)
2725        .unwrap_or(event.kind.as_str());
2726    match event.kind.as_str() {
2727        "transport_closed" => Some("runtime transport closed during startup".into()),
2728        "transport_error" => Some(format!("runtime transport error: {detail}")),
2729        "malformed_output" => Some(format!("runtime emitted non-protocol output: {detail}")),
2730        // Stderr is retained as a runtime event, but is not transport health.
2731        // Grok, for example, can log an AuthorizationRequired error from an
2732        // optional background worker while its ACP session continues to send
2733        // updates and complete prompts normally.
2734        _ => None,
2735    }
2736}
2737
2738fn indexed_claude_window(
2739    locator: &SessionLocator,
2740    options: &SessionLoadOptions,
2741) -> std::result::Result<Option<Value>, ServiceError> {
2742    use supercode_interchange::session::ClaudeReadIndex;
2743    // Exact parent-only window: recursive/full-artifact requests retain the
2744    // existing owner. This is not a bounded display-history substitution.
2745    if locator.harness.as_str() != HarnessId::CLAUDE_CODE
2746        || options.include_subagents != Some(false)
2747    {
2748        return Ok(None);
2749    }
2750    let crate::StorageLocator::File { path } = &locator.storage else {
2751        return Ok(None);
2752    };
2753    if !ClaudeReadIndex::supports(path)
2754        .map_err(|error| ServiceError::Operation(error.to_string()))?
2755    {
2756        return Ok(None);
2757    }
2758    let mut index = ClaudeReadIndex::open(path, Fidelity::ByteLossless)
2759        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2760    let total = index.len();
2761    let (offset, end) = projected_message_window(total, options);
2762    let session = index
2763        .read_messages(offset..end)
2764        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2765    let summary = index
2766        .read_summary()
2767        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2768    let selected_options = SessionLoadOptions {
2769        message_offset: None,
2770        message_limit: None,
2771        message_tail: None,
2772        ..options.clone()
2773    };
2774    let mut selected = projected_session_json(&session, &selected_options);
2775    selected["raw_record_count"] = json!(index.raw_record_count());
2776    Ok(Some(json!({
2777        "session": selected,
2778        "summary": projected_session_summary(&summary, options),
2779        "window": {
2780            "has_more": offset > 0 || end < total, "has_newer": end < total,
2781            "has_older": offset > 0, "newer_items": index.item_count(end..total),
2782            "offset": offset, "older_items": index.item_count(0..offset),
2783            "returned": end - offset, "total_messages": total,
2784        }
2785    })))
2786}
2787
2788fn projected_session_result(session: &Session, options: &SessionLoadOptions) -> Value {
2789    let total_messages = session.messages.len();
2790    let (offset, end) = projected_message_window(total_messages, options);
2791    json!({
2792        "session": projected_session_json(session, options),
2793        "summary": projected_session_summary(session, options),
2794        "window": {
2795            "has_more": offset > 0 || end < total_messages,
2796            "has_newer": end < total_messages,
2797            "has_older": offset > 0,
2798            "newer_items": normalized_item_count(&session.messages[end..]),
2799            "offset": offset,
2800            "older_items": normalized_item_count(&session.messages[..offset]),
2801            "returned": end.saturating_sub(offset),
2802            "total_messages": total_messages,
2803        }
2804    })
2805}
2806
2807fn normalized_item_count(messages: &[crate::ChatMessage]) -> usize {
2808    messages
2809        .iter()
2810        .map(|message| {
2811            let conversation = usize::from(
2812                matches!(message.role, Role::Assistant | Role::User)
2813                    && message_has_content(message),
2814            );
2815            let tool_result =
2816                usize::from(message.role == Role::Tool && message_has_content(message));
2817            conversation + tool_result + message.tool_calls().len()
2818        })
2819        .sum()
2820}
2821
2822fn projected_session_summary(session: &Session, options: &SessionLoadOptions) -> Value {
2823    let mut conversational = session.messages.iter().filter(|message| {
2824        matches!(message.role, Role::Assistant | Role::User) && message_has_content(message)
2825    });
2826    let first_message = conversational.clone().next();
2827    let last_message = conversational.next_back();
2828    let mut assistant = session
2829        .messages
2830        .iter()
2831        .filter(|message| message.role == Role::Assistant && message_has_content(message));
2832    let first_assistant_message = assistant.clone().next();
2833    let last_assistant_message = assistant.next_back();
2834    let end_of_turn = session
2835        .messages
2836        .iter()
2837        .rev()
2838        .find(|message| message.role != Role::System)
2839        .is_some_and(|message| {
2840            message.role == Role::Assistant
2841                && message_has_content(message)
2842                && message.tool_calls().is_empty()
2843        });
2844    let project = |message: Option<&crate::ChatMessage>| {
2845        message.map(|message| project_inline_media(message_json(message), options))
2846    };
2847    json!({
2848        "end_of_turn": end_of_turn,
2849        "first_assistant_message": project(first_assistant_message),
2850        "first_message": project(first_message),
2851        "last_assistant_message": project(last_assistant_message),
2852        "last_assistant_text": last_assistant_message.map(message_text).unwrap_or_default(),
2853        "last_message": project(last_message),
2854    })
2855}
2856
2857fn message_has_content(message: &crate::ChatMessage) -> bool {
2858    message
2859        .content
2860        .as_deref()
2861        .is_some_and(|content| !content.trim().is_empty())
2862        || message
2863            .content_parts
2864            .as_ref()
2865            .is_some_and(|parts| !parts.is_empty())
2866}
2867
2868fn message_text(message: &crate::ChatMessage) -> String {
2869    if let Some(content) = &message.content {
2870        return content.clone();
2871    }
2872    message
2873        .content_parts
2874        .as_ref()
2875        .into_iter()
2876        .flatten()
2877        .filter_map(|part| part.get("text").and_then(Value::as_str))
2878        .collect::<Vec<_>>()
2879        .join("\n")
2880}
2881
2882fn projected_session_json(session: &Session, options: &SessionLoadOptions) -> Value {
2883    let (offset, end) = projected_message_window(session.messages.len(), options);
2884    let messages = session.messages[offset..end]
2885        .iter()
2886        .map(|message| project_inline_media(message_json(message), options))
2887        .collect::<Vec<_>>();
2888    let subagents = if options.include_subagents.unwrap_or(true) {
2889        // The reported window describes the top-level transcript. Applying it
2890        // recursively would silently truncate subagents without returning a
2891        // window for each child. Keep their histories complete while carrying
2892        // the caller's media policy through the tree.
2893        let subagent_options = SessionLoadOptions {
2894            message_limit: None,
2895            message_offset: None,
2896            message_tail: None,
2897            ..options.clone()
2898        };
2899        session
2900            .subagents
2901            .iter()
2902            .map(|subagent| projected_session_json(subagent, &subagent_options))
2903            .collect::<Vec<_>>()
2904    } else {
2905        Vec::new()
2906    };
2907    json!({
2908        "source": match session.meta.source {
2909            SessionSource::ClaudeCode => "claude_code",
2910            SessionSource::Codex => "codex",
2911            SessionSource::Gemini => "gemini",
2912            SessionSource::Goose => "goose",
2913            SessionSource::Grok => "grok",
2914            SessionSource::Native => "native",
2915            SessionSource::OpenClaw => "openclaw",
2916            SessionSource::Hermes => "hermes",
2917            SessionSource::OpenCode => "opencode",
2918            SessionSource::Pi => "pi",
2919        },
2920        "session_id": session.meta.session_id,
2921        "ended_at": session.meta.ended_at,
2922        "end_reason": session.meta.end_reason,
2923        "model": session.meta.model,
2924        "cwd": session.meta.cwd,
2925        "system_prompt": session.meta.system_prompt,
2926        "agent_id": session.meta.agent_id,
2927        "parent_tool_use_id": session.meta.parent_tool_use_id,
2928        "lineage": session.meta.lineage,
2929        "messages": messages,
2930        "subagents": subagents,
2931        "raw_record_count": session.raw.len(),
2932        "parse_error_lines": session.parse_error_lines,
2933    })
2934}
2935
2936fn projected_message_window(total: usize, options: &SessionLoadOptions) -> (usize, usize) {
2937    if let Some(tail) = options.message_tail {
2938        return (total.saturating_sub(tail), total);
2939    }
2940    let offset = options.message_offset.unwrap_or(0).min(total);
2941    let end = options
2942        .message_limit
2943        .map(|limit| offset.saturating_add(limit).min(total))
2944        .unwrap_or(total);
2945    (offset, end)
2946}
2947
2948fn project_inline_media(mut message: Value, options: &SessionLoadOptions) -> Value {
2949    let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else {
2950        return message;
2951    };
2952    for part in parts {
2953        let Some(url) = part
2954            .get("image_url")
2955            .and_then(|image| image.get("url"))
2956            .and_then(Value::as_str)
2957        else {
2958            continue;
2959        };
2960        let Some(rest) = url.strip_prefix("data:") else {
2961            continue;
2962        };
2963        let Some((media_type, encoded)) = rest.split_once(";base64,") else {
2964            continue;
2965        };
2966        let padding = usize::from(encoded.ends_with('=')) + usize::from(encoded.ends_with("=="));
2967        let decoded_bytes = encoded.len().saturating_mul(3) / 4;
2968        let decoded_bytes = decoded_bytes.saturating_sub(padding);
2969        let should_elide = matches!(options.inline_media, InlineMediaMode::Metadata)
2970            || options
2971                .max_inline_media_bytes
2972                .is_some_and(|limit| decoded_bytes > limit);
2973        if should_elide {
2974            *part = json!({
2975                "type": "media_reference",
2976                "media_type": media_type,
2977                "encoding": "base64",
2978                "encoded_bytes": encoded.len(),
2979                "decoded_bytes": decoded_bytes,
2980                "omitted": true,
2981            });
2982        }
2983    }
2984    message
2985}
2986
2987#[derive(Deserialize)]
2988struct LocatorParams {
2989    locator: SessionLocator,
2990    /// Optional fidelity for the READ surfaces (`sessions.load`,
2991    /// `sessions.follow`).
2992    ///
2993    /// Omitted means [`Fidelity::Semantic`]: these two methods only ever
2994    /// produce a read-only view, and a compacted or resumed-across-files
2995    /// transcript — the everyday shape of a long Claude Code session — has no
2996    /// losslessly reconstructable record graph, so refusing to render it made
2997    /// the mirror unusable rather than accurate. A caller that intends to
2998    /// CONTINUE from what it reads asks for a lossless level explicitly and
2999    /// gets the strict refusal back. Every other method (export, translate,
3000    /// branch, handoff, resume_instructions) is lossless-only and has no
3001    /// such knob.
3002    #[serde(default)]
3003    fidelity: Option<Fidelity>,
3004    /// Optional bounded frontend projection. Absent preserves the historical
3005    /// complete-session read contract.
3006    #[serde(default)]
3007    view: Option<SessionReadView>,
3008}
3009
3010#[derive(Deserialize)]
3011struct SessionReadView {
3012    /// Number of trailing normalized messages to return. Zero is treated as
3013    /// one so a caller cannot accidentally request an unbounded empty mode.
3014    #[serde(default)]
3015    tail_messages: Option<usize>,
3016    /// Whether Claude Code child transcripts belong in this view. The
3017    /// frontend default is false; the legacy no-view path remains true.
3018    #[serde(default)]
3019    include_subagents: bool,
3020    /// Preserve human-visible native history across model-context compaction.
3021    #[serde(default)]
3022    display_history: bool,
3023    /// Bound each individual text field so a single tool result cannot turn a
3024    /// small message window into a hundred-megabyte RPC response.
3025    #[serde(default)]
3026    max_message_chars: Option<usize>,
3027}
3028
3029impl LocatorParams {
3030    fn read_fidelity(&self) -> Fidelity {
3031        self.fidelity.unwrap_or(Fidelity::Semantic)
3032    }
3033
3034    fn include_subagents(&self) -> bool {
3035        self.view
3036            .as_ref()
3037            .map(|view| view.include_subagents)
3038            .unwrap_or(true)
3039    }
3040
3041    fn tail_messages(&self) -> Option<usize> {
3042        self.view
3043            .as_ref()
3044            .and_then(|view| view.tail_messages)
3045            .map(|limit| limit.clamp(1, 5_000))
3046    }
3047
3048    fn display_history(&self) -> bool {
3049        self.view.as_ref().is_some_and(|view| view.display_history)
3050    }
3051
3052    fn max_message_chars(&self) -> Option<usize> {
3053        self.view
3054            .as_ref()
3055            .and_then(|view| view.max_message_chars)
3056            .map(|limit| limit.clamp(256, 64_000))
3057    }
3058
3059    fn bound_session(&self, session: &mut Session) {
3060        bound_session_view(session, self.tail_messages(), self.max_message_chars());
3061    }
3062}
3063
3064#[derive(Debug, Clone, Copy, Default, Deserialize)]
3065#[serde(rename_all = "snake_case")]
3066enum InlineMediaMode {
3067    #[default]
3068    Full,
3069    Metadata,
3070}
3071
3072#[derive(Debug, Clone, Default, Deserialize)]
3073#[serde(default)]
3074struct SessionLoadOptions {
3075    include_subagents: Option<bool>,
3076    inline_media: InlineMediaMode,
3077    max_inline_media_bytes: Option<usize>,
3078    message_limit: Option<usize>,
3079    message_offset: Option<usize>,
3080    message_tail: Option<usize>,
3081}
3082
3083impl SessionLoadOptions {
3084    fn validate(&self) -> std::result::Result<(), ServiceError> {
3085        if self.message_tail.is_some()
3086            && (self.message_limit.is_some() || self.message_offset.is_some())
3087        {
3088            return Err(ServiceError::InvalidParams(
3089                "sessions.load options.message_tail cannot be combined with message_limit or message_offset"
3090                    .into(),
3091            ));
3092        }
3093        Ok(())
3094    }
3095}
3096
3097#[derive(Deserialize)]
3098struct LoadSessionParams {
3099    #[serde(flatten)]
3100    read: LocatorParams,
3101    #[serde(default)]
3102    options: Option<SessionLoadOptions>,
3103}
3104
3105#[derive(Deserialize)]
3106struct UnfollowParams {
3107    subscription: String,
3108}
3109
3110#[derive(Debug, Deserialize)]
3111#[serde(deny_unknown_fields)]
3112struct IndexResizeParams {
3113    subscription: String,
3114    limit: usize,
3115}
3116
3117#[derive(Deserialize)]
3118struct ActivitySubscribeParams {
3119    locators: Vec<SessionLocator>,
3120    #[serde(default)]
3121    homes: crate::HarnessHomes,
3122}
3123
3124#[derive(Deserialize)]
3125struct MessageSessionParams {
3126    locator: SessionLocator,
3127    text: String,
3128    /// Same storage roots discovery accepts, so a caller (and a test) can
3129    /// point the live-session registry somewhere other than `$HOME`.
3130    #[serde(default)]
3131    homes: crate::HarnessHomes,
3132}
3133
3134#[derive(Deserialize)]
3135#[serde(deny_unknown_fields)]
3136struct HarnessSettingsParams {
3137    harness: String,
3138}
3139
3140#[derive(Deserialize)]
3141#[serde(deny_unknown_fields)]
3142struct ConfigureHarnessParams {
3143    harness: String,
3144    #[serde(default)]
3145    changes: Vec<crate::HarnessSettingChange>,
3146    #[serde(default)]
3147    expected_revision: Option<String>,
3148}
3149
3150fn claude_inbound_controls_or_error(homes: &crate::HarnessHomes) -> (Value, Value) {
3151    match crate::inspect_harness_interop_settings(homes, HarnessId::CLAUDE_CODE) {
3152        Ok(report) => (
3153            serde_json::to_value(report).unwrap_or(Value::Null),
3154            Value::Null,
3155        ),
3156        Err(error) => (
3157            Value::Null,
3158            Value::String(format!(
3159                "Supercode could not inspect Claude Code inbound controls: {error}"
3160            )),
3161        ),
3162    }
3163}
3164
3165/// Deliver `text` into a session that is running right now, or say why not.
3166///
3167/// A refusal is a RESULT, not a JSON-RPC error: "that session is persisted
3168/// only" is an answer about the session, which a mirror renders next to the
3169/// transcript, and this service's error envelope carries no structured data
3170/// field a machine-readable reason could survive in.
3171///
3172/// `delivered_to_bus` is the honest ceiling of what the courier proves. The
3173/// message reached the receiving session's inbox; whether that session ever
3174/// reads it is governed by ITS OWN inbound controls (`crossSessionInbound`,
3175/// approval dialogs), which Supercode neither sees nor overrides.
3176async fn message_live_session(
3177    params: &MessageSessionParams,
3178    runner: &dyn crate::claude_peer::CourierRunner,
3179) -> Value {
3180    if params.locator.harness.as_str() != HarnessId::CLAUDE_CODE {
3181        return json!({
3182            "delivered_to_bus": false,
3183            "refusal": {
3184                "reason": crate::claude_peer::ClaudePeerRefusal::HarnessUnsupported.as_str(),
3185                "message": format!(
3186                    "`{}` does not publish a live-session registry; only claude-code sessions can be messaged in place",
3187                    params.locator.harness.as_str()
3188                ),
3189            },
3190        });
3191    }
3192    let (inbound_controls, inbound_controls_error) =
3193        claude_inbound_controls_or_error(&params.homes);
3194    match crate::claude_peer::message_claude_peer(
3195        &params.homes,
3196        &params.locator.session_id,
3197        &params.text,
3198        runner,
3199    )
3200    .await
3201    {
3202        Ok(delivery) => json!({
3203            "delivered_to_bus": true,
3204            "target": {
3205                "session_id": delivery.target.session_id,
3206                "name": delivery.target.name,
3207                "pid": delivery.target.pid,
3208                "cwd": delivery.target.cwd,
3209                "status": delivery.target.status.map(|status| status.as_str()),
3210            },
3211            "courier": {
3212                "model": crate::claude_peer::COURIER_MODEL,
3213                "report": delivery.courier_report,
3214            },
3215            "inbound_controls": inbound_controls,
3216            "inbound_controls_error": inbound_controls_error,
3217        }),
3218        Err(refusal) => json!({
3219            "delivered_to_bus": false,
3220            "refusal": {"reason": refusal.reason.as_str(), "message": refusal.message},
3221            "inbound_controls": inbound_controls,
3222            "inbound_controls_error": inbound_controls_error,
3223        }),
3224    }
3225}
3226
3227/// Source identity of one follow subscription, plus the last lifecycle state
3228/// already reported on it. The follower itself stays purely persistence-facing.
3229// Only the adapter-api poll reads these; the subscription bookkeeping itself is
3230// shared by both builds.
3231#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3232struct FollowedSource {
3233    harness: String,
3234    session_id: String,
3235    reported: Option<String>,
3236}
3237
3238#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
3239struct ActivitySubscription {
3240    locators: Vec<SessionLocator>,
3241    homes: crate::HarnessHomes,
3242    reported: BTreeMap<(String, String), crate::SessionActivity>,
3243}
3244
3245fn peers_for_descriptors(
3246    descriptors: &[SessionDescriptor],
3247    homes: &HarnessHomes,
3248) -> Vec<crate::claude_peer::ClaudePeerSession> {
3249    if descriptors
3250        .iter()
3251        .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
3252    {
3253        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
3254    } else {
3255        Vec::new()
3256    }
3257}
3258
3259/// Add the live address that makes an indexed row behaviorally equivalent to a discovered row.
3260///
3261/// The durable index owns only persistence metadata. Live endpoints remain projections: every
3262/// message/attach operation revalidates its authority, so publishing one here never trusts a stale
3263/// browser-held handle. Reading the Claude registry once per batch keeps this O(peers + rows).
3264fn live_descriptor_value(
3265    session: &SessionDescriptor,
3266    peers: &[crate::claude_peer::ClaudePeerSession],
3267) -> std::result::Result<Value, ServiceError> {
3268    let mut value = serde_json::to_value(session)
3269        .map_err(|error| ServiceError::Operation(error.to_string()))?;
3270    if let Some(workspace) = &session.cwd {
3271        let source = LiveRuntimeSource {
3272            harness: session.locator.harness.as_str().to_string(),
3273            session_id: session.locator.session_id.clone(),
3274            workspace: workspace.clone(),
3275        };
3276        if let Some(endpoint) = discover_live_runtime(&source)
3277            .map_err(|error| ServiceError::Operation(error.to_string()))?
3278        {
3279            value["live_endpoint"] = json!(endpoint.as_str());
3280        }
3281    }
3282    if value.get("live_endpoint").is_none() {
3283        if let Some(peer) = peers.iter().find(|peer| {
3284            session.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3285                && peer.session_id == session.locator.session_id
3286        }) {
3287            value["live_endpoint"] = json!(peer.endpoint().as_str());
3288        }
3289    }
3290    Ok(value)
3291}
3292
3293fn live_index_changes(
3294    changes: Vec<crate::session_index::SessionIndexChange>,
3295    homes: &HarnessHomes,
3296) -> std::result::Result<Vec<Value>, ServiceError> {
3297    use crate::session_index::SessionIndexChange;
3298    let has_claude = changes.iter().any(|change| match change {
3299        SessionIndexChange::Added { descriptor } | SessionIndexChange::Updated { descriptor } => {
3300            descriptor.locator.harness.as_str() == HarnessId::CLAUDE_CODE
3301        }
3302        SessionIndexChange::Removed { .. } => false,
3303    });
3304    let peers = if has_claude {
3305        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
3306    } else {
3307        Vec::new()
3308    };
3309    changes
3310        .into_iter()
3311        .map(|change| match change {
3312            SessionIndexChange::Added { descriptor } => Ok(json!({
3313                "kind": "added",
3314                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3315            })),
3316            SessionIndexChange::Updated { descriptor } => Ok(json!({
3317                "kind": "updated",
3318                "descriptor": live_descriptor_value(&descriptor, &peers)?,
3319            })),
3320            SessionIndexChange::Removed { key } => Ok(json!({
3321                "kind": "removed",
3322                "key": key,
3323            })),
3324        })
3325        .collect()
3326}
3327
3328fn legacy_live_status(activity: &crate::SessionActivity) -> Option<&'static str> {
3329    use crate::{SessionPresence, SessionTurnState};
3330    match (activity.presence, activity.turn) {
3331        (SessionPresence::Persisted, _) => None,
3332        (SessionPresence::Running, SessionTurnState::Working) => Some("busy"),
3333        (SessionPresence::Running, SessionTurnState::Idle) => Some("idle"),
3334        // The normalized activity object can honestly report a live owner even
3335        // when the stock harness never published a turn status. Preserve the
3336        // older field's stricter contract instead of guessing `running`.
3337        (SessionPresence::Running, SessionTurnState::Unknown)
3338            if activity.evidence.native_state.is_none() =>
3339        {
3340            None
3341        }
3342        (SessionPresence::Running, _) | (SessionPresence::ShuttingDown, _) => Some("running"),
3343    }
3344}
3345
3346#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
3347#[serde(rename_all = "kebab-case")]
3348enum TransferFormat {
3349    ClaudeCode,
3350    Codex,
3351    #[serde(rename = "opencode", alias = "open-code")]
3352    OpenCode,
3353    Pi,
3354    Grok,
3355    Gemini,
3356    Goose,
3357    /// UNI-18: a Hermes target. Its artifact is the Codex rollout that
3358    /// `hermes sessions import --from codex` reads; `sessions.export` performs
3359    /// that import into the Hermes home.
3360    Hermes,
3361}
3362
3363impl TransferFormat {
3364    fn id(self) -> &'static str {
3365        match self {
3366            Self::ClaudeCode => HarnessId::CLAUDE_CODE,
3367            Self::Codex => HarnessId::CODEX,
3368            Self::OpenCode => HarnessId::OPENCODE,
3369            Self::Pi => HarnessId::PI,
3370            Self::Grok => HarnessId::GROK,
3371            Self::Gemini => HarnessId::GEMINI,
3372            Self::Goose => HarnessId::GOOSE,
3373            Self::Hermes => HarnessId::HERMES,
3374        }
3375    }
3376}
3377
3378impl From<TransferFormat> for SessionFormat {
3379    fn from(value: TransferFormat) -> Self {
3380        match value {
3381            TransferFormat::ClaudeCode => Self::ClaudeCode,
3382            TransferFormat::Codex => Self::Codex,
3383            TransferFormat::OpenCode => Self::OpenCode,
3384            TransferFormat::Pi => Self::Pi,
3385            TransferFormat::Grok => Self::Grok,
3386            TransferFormat::Gemini => Self::Gemini,
3387            TransferFormat::Goose => Self::Goose,
3388            // a Hermes artifact is the Codex rollout Hermes imports
3389            TransferFormat::Hermes => Self::Codex,
3390        }
3391    }
3392}
3393
3394#[derive(Deserialize)]
3395struct ImportSessionParams {
3396    source_harness: TransferFormat,
3397    content: String,
3398}
3399
3400#[derive(Deserialize)]
3401struct ExportSessionParams {
3402    locator: SessionLocator,
3403    target_harness: TransferFormat,
3404}
3405
3406#[derive(Deserialize)]
3407struct ReduceSessionParams {
3408    locator: SessionLocator,
3409    target_harness: TransferFormat,
3410    #[serde(default = "default_keep_last")]
3411    keep_last: usize,
3412}
3413
3414fn default_keep_last() -> usize {
3415    6
3416}
3417
3418#[derive(Deserialize)]
3419struct BranchSessionParams {
3420    locator: SessionLocator,
3421    #[serde(default)]
3422    target_harness: Option<TransferFormat>,
3423}
3424
3425#[derive(Deserialize)]
3426struct HandoffSessionParams {
3427    locator: SessionLocator,
3428    target_harness: TransferFormat,
3429    #[serde(default)]
3430    cwd: Option<PathBuf>,
3431}
3432
3433#[derive(Debug, Clone, Copy, Default, Deserialize)]
3434#[serde(rename_all = "snake_case")]
3435enum ResumePolicy {
3436    #[default]
3437    Default,
3438    Yolo,
3439}
3440
3441#[derive(Deserialize)]
3442struct ResumeInstructionsParams {
3443    locator: SessionLocator,
3444    #[serde(default)]
3445    cwd: Option<PathBuf>,
3446    #[serde(default)]
3447    policy: ResumePolicy,
3448}
3449
3450/// `harness.v1.workflow.load` parameters: which harness's board, and its home.
3451#[derive(Deserialize)]
3452struct WorkflowLoadParams {
3453    from: crate::workflow_doors::WorkflowHarness,
3454    home: PathBuf,
3455}
3456
3457/// ONT-4 `harness.v1.orchestration.load` parameters. `flavor` says which layout the
3458/// folder is read as; our own is the default.
3459#[derive(Deserialize)]
3460struct OrchestrationLoadParams {
3461    root: PathBuf,
3462    #[serde(default)]
3463    flavor: crate::orchestration_doors::HomeFlavor,
3464}
3465
3466/// ONT-4 `harness.v1.orchestration.save` parameters. `vault` is merged into the
3467/// home's own secrets; a caller that sends none keeps what is on disk.
3468#[derive(Deserialize)]
3469struct OrchestrationSaveParams {
3470    root: PathBuf,
3471    orchestration: crate::orchestration::Orchestration,
3472    #[serde(default)]
3473    vault: BTreeMap<String, String>,
3474}
3475
3476/// ONT-4 `harness.v1.orchestration.compile` parameters.
3477#[derive(Deserialize)]
3478struct OrchestrationCompileParams {
3479    from: crate::orchestration_doors::OrchestrationHarness,
3480    home: PathBuf,
3481}
3482
3483/// ONT-4 `harness.v1.orchestration.decompile` parameters. `source` is the home the
3484/// orchestration was compiled from: it is re-compiled to recover the io bookkeeping
3485/// that byte reuse and the live-store refusal (UNI-18) are decided from.
3486#[derive(Deserialize)]
3487struct OrchestrationDecompileParams {
3488    to: crate::orchestration_doors::OrchestrationHarness,
3489    orchestration: crate::orchestration::Orchestration,
3490    source: PathBuf,
3491    #[serde(default)]
3492    source_flavor: crate::orchestration_doors::SourceFlavor,
3493    dest: PathBuf,
3494    #[serde(default)]
3495    vault: BTreeMap<String, String>,
3496}
3497
3498/// `harness.v1.orchestration.import` parameters: another harness's home, and the
3499/// folder of ours it becomes.
3500#[derive(Deserialize)]
3501struct OrchestrationImportParams {
3502    from: crate::orchestration_doors::OrchestrationHarness,
3503    home: PathBuf,
3504    into: PathBuf,
3505}
3506
3507/// `harness.v1.orchestration.export` parameters: a folder of ours, and the home of
3508/// another harness it becomes.
3509#[derive(Deserialize)]
3510struct OrchestrationExportParams {
3511    to: crate::orchestration_doors::OrchestrationHarness,
3512    root: PathBuf,
3513    dest: PathBuf,
3514}
3515
3516/// `harness.v1.jobs.get` parameters.
3517#[derive(Deserialize)]
3518struct JobsGetParams {
3519    harness: String,
3520    id: String,
3521    #[serde(default)]
3522    homes: crate::HarnessHomes,
3523}
3524
3525/// ORCH-18: run one mutating job verb through the harness's own CLI.
3526///
3527/// The refusal ladder is deliberate: a harness with no scheduled-job concept
3528/// at all answers with the SAME sentence `jobs.list` gives it, and a harness
3529/// that has jobs but publishes no client-callable verb (Claude Code, whose
3530/// jobs are created by the model inside a session) answers with its own
3531/// reason. Neither is ever a silent no-op.
3532fn mutate_job(
3533    verb: crate::jobs_control::JobVerb,
3534    params: Value,
3535) -> std::result::Result<Value, ServiceError> {
3536    let mutation = decode::<crate::jobs_control::JobMutation>(params)?;
3537    refuse_harness_without_jobs(&mutation.harness, &format!("jobs.{}", verb.as_str()))?;
3538    let outcome = crate::jobs_control::mutate(verb, &mutation).map_err(job_control_error)?;
3539    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3540}
3541
3542/// ORCH-22: run one mutating skills verb through the harness's own door.
3543///
3544/// The refusal ladder mirrors `jobs.*`: a harness with no skills root at all
3545/// answers with the same sentence `skills.list` gives it, and a harness whose
3546/// door does not publish this verb (OpenClaw has no `skills remove` at the
3547/// pin) answers with its own reason. Neither is ever a silent no-op.
3548fn mutate_skill(
3549    verb: crate::skills_control::SkillVerb,
3550    params: Value,
3551) -> std::result::Result<Value, ServiceError> {
3552    let mutation = decode::<crate::skills_control::SkillMutation>(params)?;
3553    if !crate::skills_control::supports_skill_control(&mutation.harness) {
3554        return Err(ServiceError::UnsupportedAction(format!(
3555            "`{}` has no skills root supercode reads; `skills.{}` is supported for: {}",
3556            mutation.harness,
3557            verb.as_str(),
3558            crate::skills_control::CONTROLLED_SKILL_HARNESSES.join(", ")
3559        )));
3560    }
3561    let outcome =
3562        crate::skills_control::mutate_skill(verb, &mutation).map_err(skill_control_error)?;
3563    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3564}
3565
3566/// The skills twin of [`job_control_error`], with the same mapping rule.
3567fn skill_control_error(error: crate::skills_control::SkillControlError) -> ServiceError {
3568    match error {
3569        crate::skills_control::SkillControlError::Unsupported(message) => {
3570            ServiceError::UnsupportedAction(message)
3571        }
3572        crate::skills_control::SkillControlError::Invalid(message) => {
3573            ServiceError::InvalidParams(message)
3574        }
3575        crate::skills_control::SkillControlError::Failed(message) => {
3576            ServiceError::Operation(message)
3577        }
3578    }
3579}
3580
3581/// ORCH-21: run one mutating profile verb through the harness's own CLI.
3582///
3583/// The refusal ladder mirrors `mutate_job`'s: a harness with no profile
3584/// concept at all answers with the SAME sentence `profiles.list` gives it, and
3585/// a harness that HAS profiles but publishes no client-callable verb (Codex's
3586/// file-authored `[profiles.<name>]` tables, supercode's compiled-in presets)
3587/// answers with its own reason. Neither is ever a silent no-op.
3588fn mutate_profile(
3589    verb: crate::profiles_control::ProfileVerb,
3590    params: Value,
3591) -> std::result::Result<Value, ServiceError> {
3592    let mutation = decode::<crate::profiles_control::ProfileMutation>(params)?;
3593    let outcome =
3594        crate::profiles_control::mutate(verb, &mutation).map_err(profile_control_error)?;
3595    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
3596}
3597
3598/// The same mapping `job_control_error` applies, for the profile noun.
3599fn profile_control_error(error: crate::profiles_control::ProfileControlError) -> ServiceError {
3600    match error {
3601        crate::profiles_control::ProfileControlError::Unsupported(message) => {
3602            ServiceError::UnsupportedAction(message)
3603        }
3604        crate::profiles_control::ProfileControlError::Invalid(message) => {
3605            ServiceError::InvalidParams(message)
3606        }
3607        crate::profiles_control::ProfileControlError::Failed(message) => {
3608            ServiceError::Operation(message)
3609        }
3610    }
3611}
3612
3613/// Map a controlled-tier failure onto the service's error vocabulary. A verb
3614/// the harness lacks is `UnsupportedAction`; a harness verb that RAN and
3615/// failed carries its own stderr through as the operation error.
3616fn job_control_error(error: crate::jobs_control::JobControlError) -> ServiceError {
3617    match error {
3618        crate::jobs_control::JobControlError::Unsupported(message) => {
3619            ServiceError::UnsupportedAction(message)
3620        }
3621        crate::jobs_control::JobControlError::Invalid(message) => {
3622            ServiceError::InvalidParams(message)
3623        }
3624        crate::jobs_control::JobControlError::Failed(message) => ServiceError::Operation(message),
3625    }
3626}
3627
3628/// Map an ORCH-19 controlled-tier failure onto the service's error
3629/// vocabulary. A verb the harness has no door for is `UnsupportedAction`; a
3630/// door that RAN and failed carries the harness's own stderr / HTTP body
3631/// through as the operation error.
3632fn session_control_error(error: crate::SessionControlError) -> ServiceError {
3633    match error {
3634        crate::SessionControlError::Unsupported(message) => {
3635            ServiceError::UnsupportedAction(message)
3636        }
3637        crate::SessionControlError::Invalid(message) => ServiceError::InvalidParams(message),
3638        crate::SessionControlError::Failed(message) => ServiceError::Operation(message),
3639    }
3640}
3641
3642/// A harness without a scheduled-job concept refuses the verb rather than
3643/// answering with an empty list — an absent capability and an empty inventory
3644/// are different answers (the same rule `runtimes.capabilities` applies to
3645/// `steer`).
3646fn refuse_harness_without_jobs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3647    if crate::jobs::supports_jobs(harness) {
3648        return Ok(());
3649    }
3650    Err(ServiceError::UnsupportedAction(format!(
3651        "`{harness}` has no scheduled jobs; `{verb}` is supported for: {}",
3652        crate::jobs::JOB_HARNESSES.join(", ")
3653    )))
3654}
3655
3656/// `harness.v1.runs.get` parameters.
3657#[derive(Deserialize)]
3658struct RunsGetParams {
3659    harness: String,
3660    id: String,
3661    #[serde(default)]
3662    homes: crate::HarnessHomes,
3663}
3664
3665/// A harness with no run store refuses the verb rather than answering with an
3666/// empty history — the same rule `jobs.list` applies. Claude Code lands here
3667/// on purpose: its cron fires are ordinary turns inside the session that
3668/// created the job, so there is no fire record to list.
3669fn refuse_harness_without_runs(harness: &str, verb: &str) -> std::result::Result<(), ServiceError> {
3670    if crate::runs::supports_runs(harness) {
3671        return Ok(());
3672    }
3673    Err(ServiceError::UnsupportedAction(format!(
3674        "`{harness}` keeps no run store; `{verb}` is supported for: {}",
3675        crate::runs::RUN_HARNESSES.join(", ")
3676    )))
3677}
3678
3679#[derive(Serialize)]
3680struct SessionArtifact {
3681    source_harness: HarnessId,
3682    target_harness: &'static str,
3683    session_id: Option<String>,
3684    content: String,
3685    suggested_filename: String,
3686    files: Vec<SessionArtifactFile>,
3687    fidelity: Fidelity,
3688    residue: Vec<String>,
3689}
3690
3691#[derive(Serialize)]
3692struct SessionArtifactFile {
3693    path: String,
3694    content: String,
3695    role: ArtifactFileRole,
3696}
3697
3698#[derive(Serialize)]
3699#[serde(rename_all = "snake_case")]
3700enum ArtifactFileRole {
3701    Primary,
3702    Subagent,
3703    Bundle,
3704    SourceRecovery,
3705}
3706
3707#[derive(Serialize)]
3708struct StructuredLaunch {
3709    cwd: PathBuf,
3710    program: String,
3711    arguments: Vec<String>,
3712    env: BTreeMap<String, String>,
3713}
3714
3715struct HandoffInstructions {
3716    launch: StructuredLaunch,
3717    materialize: Option<StructuredLaunch>,
3718    requires_materialization: bool,
3719    note: String,
3720}
3721
3722#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
3723#[serde(rename_all = "snake_case")]
3724enum HarnessProbeLevel {
3725    #[default]
3726    Passive,
3727    Handshake,
3728}
3729
3730#[derive(Default, Deserialize)]
3731#[serde(default)]
3732struct HarnessInventoryParams {
3733    harness: Option<HarnessId>,
3734    harnesses: Vec<HarnessId>,
3735    workspace: Option<PathBuf>,
3736    probe: HarnessProbeLevel,
3737    include_sessions: bool,
3738    /// Omit subprocess-based `--version` calls when a latency-sensitive UI only needs readiness.
3739    skip_versions: bool,
3740}
3741
3742#[derive(Deserialize)]
3743struct HarnessAuthenticationParams {
3744    harness: HarnessId,
3745}
3746
3747#[derive(Deserialize)]
3748struct BeginHarnessAuthenticationParams {
3749    harness: HarnessId,
3750    #[serde(default = "local_browser_authentication_environment")]
3751    environment: crate::HarnessAuthenticationEnvironment,
3752    #[serde(default)]
3753    method: Option<crate::HarnessAuthenticationMethodId>,
3754    #[serde(default)]
3755    cwd: Option<PathBuf>,
3756}
3757
3758fn local_browser_authentication_environment() -> crate::HarnessAuthenticationEnvironment {
3759    crate::HarnessAuthenticationEnvironment::LocalBrowser
3760}
3761
3762#[derive(Serialize)]
3763struct HarnessInventoryReport {
3764    probe: HarnessProbeLevel,
3765    workspace: Option<PathBuf>,
3766    harnesses: Vec<LocalHarness>,
3767}
3768
3769#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3770#[serde(rename_all = "snake_case")]
3771enum HarnessAuthState {
3772    Ready,
3773    Configured,
3774    Required,
3775    Unknown,
3776}
3777
3778#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3779#[serde(rename_all = "snake_case")]
3780enum HarnessRuntimeState {
3781    Ready,
3782    Degraded,
3783    Unavailable,
3784}
3785
3786#[derive(Serialize)]
3787struct HarnessSessionCounts {
3788    global: Option<usize>,
3789    workspace: Option<usize>,
3790}
3791
3792/// Receipt-backed evidence that a harness has a RUNNING instance right now,
3793/// distinct from being merely installed (UNI-7). Detection is passive and
3794/// default-on: a gateway liveness connect for daemon harnesses, a fresh
3795/// SQLite WAL stamp for store-writer harnesses (precedent: the opencode
3796/// follower's -wal/-shm freshness). Control stays behind per-connection
3797/// grants — this reports observations only.
3798/// ORCH-17: the gateway-health noun on an inventory row. Derived from the
3799/// UNI-7 running-instance probe (Hermes: `state.db-wal` freshness; OpenClaw:
3800/// a TCP connect to the gateway endpoint resolved from its OWN config) plus
3801/// the executable version — never by starting anything.
3802#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
3803#[serde(rename_all = "snake_case")]
3804pub enum GatewayState {
3805    Up,
3806    Down,
3807    Unknown,
3808}
3809
3810/// ORCH-17: `gateway` on a `harness.v1.harnesses.list` row.
3811#[derive(Debug, Clone, Serialize)]
3812pub struct GatewayHealth {
3813    pub state: GatewayState,
3814    /// The endpoint supercode would connect to (OpenClaw: the gateway
3815    /// WebSocket resolved from `openclaw.json`; core harnesses: their
3816    /// declared connect address when one exists). `None` when the harness
3817    /// has no single endpoint (Hermes multiplexes platforms).
3818    #[serde(skip_serializing_if = "Option::is_none")]
3819    pub endpoint: Option<String>,
3820    #[serde(skip_serializing_if = "Option::is_none")]
3821    pub version: Option<String>,
3822    /// What the verdict rests on, or why it is `unknown`.
3823    pub evidence: String,
3824    pub checked_at_ms: u64,
3825}
3826
3827/// OpenClaw's gateway WebSocket endpoint, resolved from its own config the
3828/// way the registry's connect descriptor prescribes (`gateway.url`, else
3829/// `gateway.port`, else the documented default).
3830fn openclaw_gateway_endpoint(home: &Path) -> String {
3831    let config_path = home.join(".openclaw/openclaw.json");
3832    let gateway = std::fs::read_to_string(&config_path)
3833        .ok()
3834        .and_then(|raw| serde_json::from_str::<serde_json::Value>(&raw).ok())
3835        .and_then(|config| config.get("gateway").cloned());
3836    if let Some(url) = gateway
3837        .as_ref()
3838        .and_then(|gateway| gateway.get("url"))
3839        .and_then(serde_json::Value::as_str)
3840    {
3841        return url.to_string();
3842    }
3843    let port = gateway
3844        .as_ref()
3845        .and_then(|gateway| gateway.get("port"))
3846        .and_then(serde_json::Value::as_u64)
3847        .unwrap_or(18789);
3848    format!("ws://127.0.0.1:{port}")
3849}
3850
3851/// Ask Hermes itself (`hermes gateway status`, read-only, ~1 s) whether its
3852/// gateway is up. The command is per-host launchd/systemd text without a JSON
3853/// form at 0.19–0.21; the verdict is read from the lines it prints:
3854/// "supervised by launchd (PID …)" / "is running" → up, "not running" /
3855/// "not installed" → down, anything else → no verdict. `SUPERCODE_HERMES_BIN`
3856/// overrides the executable so a fake can stand in under test.
3857fn hermes_gateway_status() -> Option<(GatewayState, String)> {
3858    let program = crate::harness_command::harness_program(HarnessId::HERMES).ok()?;
3859    let output = std::process::Command::new(&program)
3860        .args(["gateway", "status"])
3861        .stdin(std::process::Stdio::null())
3862        .output()
3863        .ok()?;
3864    let text = format!(
3865        "{}{}",
3866        String::from_utf8_lossy(&output.stdout),
3867        String::from_utf8_lossy(&output.stderr)
3868    );
3869    let verdict = text.lines().find_map(|line| {
3870        let l = line.trim();
3871        if l.contains("supervised by launchd (PID")
3872            || l.contains("supervised by systemd (PID")
3873            || l.contains("Gateway is running")
3874            || l.contains("process is running")
3875        {
3876            Some((GatewayState::Up, format!("`hermes gateway status`: {l}")))
3877        } else if l.contains("not running") || l.contains("not installed") {
3878            Some((GatewayState::Down, format!("`hermes gateway status`: {l}")))
3879        } else {
3880            None
3881        }
3882    });
3883    verdict
3884}
3885
3886fn gateway_health(
3887    id: &str,
3888    installed: bool,
3889    running: Option<&RunningInstance>,
3890    version: Option<&str>,
3891) -> GatewayHealth {
3892    let checked_at_ms = now_epoch_ms();
3893    let home = std::env::var_os("HOME").map(PathBuf::from);
3894    match id {
3895        HarnessId::HERMES | HarnessId::OPENCLAW => {
3896            let endpoint = (id == HarnessId::OPENCLAW)
3897                .then(|| home.as_deref().map(openclaw_gateway_endpoint))
3898                .flatten();
3899            let (state, evidence) = match running {
3900                Some(instance) => (GatewayState::Up, instance.evidence.clone()),
3901                None if !installed => (
3902                    GatewayState::Unknown,
3903                    format!("`{id}` is not installed; no gateway to probe"),
3904                ),
3905                None if id == HarnessId::HERMES => match hermes_gateway_status() {
3906                    // The harness's own door outranks the WAL heuristic: an idle
3907                    // gateway writes nothing for minutes yet is up.
3908                    Some((state, evidence)) => (state, evidence),
3909                    None => (
3910                        GatewayState::Down,
3911                        "no fresh state.db-wal activity under ~/.hermes and `hermes gateway status` gave no verdict".to_string(),
3912                    ),
3913                },
3914                None => (
3915                    GatewayState::Down,
3916                    format!(
3917                        "no TCP listener at {}",
3918                        endpoint.as_deref().unwrap_or("the gateway endpoint")
3919                    ),
3920                ),
3921            };
3922            GatewayHealth {
3923                state,
3924                endpoint,
3925                version: version.map(str::to_string),
3926                evidence,
3927                checked_at_ms,
3928            }
3929        }
3930        // ORC-7: the orchestrator's gateway IS its daemon, and the daemon's
3931        // own lease file is the record of it. A lease naming a live pid is
3932        // up; a lease whose process is gone is down and says so as a STALE
3933        // lease, never as "no lease"; no lease at all is down. Nothing is
3934        // started, and no port is guessed — the daemon multiplexes adapters
3935        // the way Hermes does, so it has no single endpoint either.
3936        HarnessId::ORCHESTRATOR => {
3937            let root = crate::HarnessHomes::default().orchestrator;
3938            let (state, evidence) = match crate::orchestrator::read_lease(&root) {
3939                Some(lease) if crate::orchestrator::pid_is_live(lease.pid) => (
3940                    GatewayState::Up,
3941                    format!(
3942                        "`{}` names pid {} (started {}), which is live",
3943                        crate::orchestrator::lock_path(&root).display(),
3944                        lease.pid,
3945                        lease.started_at
3946                    ),
3947                ),
3948                Some(lease) => (
3949                    GatewayState::Down,
3950                    format!(
3951                        "stale lease `{}`: pid {} is gone",
3952                        crate::orchestrator::lock_path(&root).display(),
3953                        lease.pid
3954                    ),
3955                ),
3956                None => (
3957                    GatewayState::Down,
3958                    format!(
3959                        "no lease at `{}`; `supercode orchestrator start` writes one",
3960                        crate::orchestrator::lock_path(&root).display()
3961                    ),
3962                ),
3963            };
3964            GatewayHealth {
3965                state,
3966                endpoint: None,
3967                version: version.map(str::to_string),
3968                evidence,
3969                checked_at_ms,
3970            }
3971        }
3972        _ => GatewayHealth {
3973            state: GatewayState::Unknown,
3974            endpoint: None,
3975            version: version.map(str::to_string),
3976            evidence: format!("`{id}` runs per session, not as a gateway"),
3977            checked_at_ms,
3978        },
3979    }
3980}
3981
3982#[derive(Debug, Clone, Serialize)]
3983struct RunningInstance {
3984    /// How the instance was detected.
3985    method: RunningInstanceMethod,
3986    /// The evidence the verdict rests on (endpoint reached / WAL path+age).
3987    evidence: String,
3988    /// Epoch-ms instant the probe executed.
3989    checked_at_ms: u64,
3990}
3991
3992#[derive(Debug, Clone, Copy, Serialize)]
3993#[serde(rename_all = "snake_case")]
3994enum RunningInstanceMethod {
3995    /// A TCP connect to the harness's own configured gateway endpoint
3996    /// succeeded.
3997    GatewayConnect,
3998    /// The harness's session store has an active SQLite WAL (a live writer
3999    /// holds the store open and stamped it recently).
4000    StoreWalActivity,
4001}
4002
4003fn now_epoch_ms() -> u64 {
4004    std::time::SystemTime::now()
4005        .duration_since(std::time::UNIX_EPOCH)
4006        .map(|elapsed| elapsed.as_millis() as u64)
4007        .unwrap_or(0)
4008}
4009
4010/// OpenClaw: the gateway endpoint comes from the harness's OWN config
4011/// (`<home>/.openclaw/openclaw.json` — `gateway.url` or `gateway.port`,
4012/// default port 18789); a successful TCP connect is the running signal.
4013fn probe_openclaw_running(home: &Path) -> Option<RunningInstance> {
4014    let config_path = home.join(".openclaw/openclaw.json");
4015    let text = std::fs::read_to_string(&config_path).ok();
4016    let gateway = text
4017        .as_deref()
4018        .and_then(|raw| serde_json::from_str::<serde_json::Value>(raw).ok())
4019        .and_then(|config| config.get("gateway").cloned());
4020    let address = gateway
4021        .as_ref()
4022        .and_then(|gateway| gateway.get("url"))
4023        .and_then(serde_json::Value::as_str)
4024        .and_then(|url| {
4025            url.split("://").nth(1).map(|rest| {
4026                rest.trim_end_matches('/')
4027                    .split('/')
4028                    .next()
4029                    .unwrap_or(rest)
4030                    .to_string()
4031            })
4032        })
4033        .unwrap_or_else(|| {
4034            let port = gateway
4035                .as_ref()
4036                .and_then(|gateway| gateway.get("port"))
4037                .and_then(serde_json::Value::as_u64)
4038                .unwrap_or(18789);
4039            format!("127.0.0.1:{port}")
4040        });
4041    let reachable = std::net::TcpStream::connect_timeout(
4042        &address.parse().ok()?,
4043        std::time::Duration::from_millis(400),
4044    )
4045    .is_ok();
4046    reachable.then(|| RunningInstance {
4047        method: RunningInstanceMethod::GatewayConnect,
4048        evidence: format!(
4049            "gateway endpoint {address} accepted a TCP connect (from {})",
4050            config_path.display()
4051        ),
4052        checked_at_ms: now_epoch_ms(),
4053    })
4054}
4055
4056/// Hermes: `<home>/.hermes/state.db-wal` freshly modified means a live writer
4057/// holds the store open (SQLite WAL exists only while a connection is open;
4058/// a recent stamp distinguishes an active instance from a stale crash
4059/// leftover).
4060fn probe_hermes_running(home: &Path, max_wal_age_ms: u64) -> Option<RunningInstance> {
4061    let wal = home.join(".hermes/state.db-wal");
4062    let modified = std::fs::metadata(&wal).ok()?.modified().ok()?;
4063    let age_ms = std::time::SystemTime::now()
4064        .duration_since(modified)
4065        .map(|age| age.as_millis() as u64)
4066        .unwrap_or(u64::MAX);
4067    (age_ms <= max_wal_age_ms).then(|| RunningInstance {
4068        method: RunningInstanceMethod::StoreWalActivity,
4069        evidence: format!(
4070            "{} stamped {age_ms}ms ago (threshold {max_wal_age_ms}ms)",
4071            wal.display()
4072        ),
4073        checked_at_ms: now_epoch_ms(),
4074    })
4075}
4076
4077/// Default-on running-instance detection for the harnesses that have one.
4078fn probe_running_instance(id: &str) -> Option<RunningInstance> {
4079    let home = std::env::var_os("HOME").map(PathBuf::from)?;
4080    match id {
4081        HarnessId::OPENCLAW => probe_openclaw_running(&home),
4082        HarnessId::HERMES => probe_hermes_running(&home, 300_000),
4083        _ => None,
4084    }
4085}
4086
4087#[derive(Serialize)]
4088struct LocalHarness {
4089    id: HarnessId,
4090    display_name: String,
4091    supported: bool,
4092    installed: bool,
4093    executable: Option<String>,
4094    version: Option<String>,
4095    auth: HarnessAuthState,
4096    runtime: HarnessRuntimeState,
4097    protocol: String,
4098    capabilities: crate::RuntimeCapabilities,
4099    effective_capabilities: crate::RuntimeCapabilities,
4100    sessions: HarnessSessionCounts,
4101    /// Receipt-backed running-instance detection (None = not detected or the
4102    /// harness has no running-instance concept). Distinct from `installed`.
4103    #[serde(skip_serializing_if = "Option::is_none")]
4104    running: Option<RunningInstance>,
4105    /// ORCH-17: gateway health derived from `running` + the harness's own config.
4106    gateway: GatewayHealth,
4107    reason: Option<String>,
4108    repair: Option<String>,
4109}
4110
4111#[derive(Clone, Deserialize)]
4112struct RuntimeBackendParams {
4113    harness: HarnessId,
4114    #[serde(default)]
4115    protocol: Option<String>,
4116    #[serde(default)]
4117    launch: Option<RuntimeLaunch>,
4118    #[serde(default)]
4119    base_url: Option<String>,
4120    #[serde(default)]
4121    policy: RuntimePolicy,
4122}
4123
4124#[derive(Debug, Clone, Copy, Default, Deserialize)]
4125#[serde(rename_all = "snake_case")]
4126enum RuntimePolicy {
4127    #[default]
4128    Default,
4129    Yolo,
4130}
4131
4132#[derive(Deserialize)]
4133struct RuntimeStartParams {
4134    #[serde(flatten)]
4135    backend: RuntimeBackendParams,
4136    cwd: PathBuf,
4137    /// MCP servers to mount into the new session through the harness's own
4138    /// start door (ORC-6). Backends without such a door ignore them.
4139    #[serde(default)]
4140    mcp_servers: Vec<crate::McpServerLaunch>,
4141}
4142
4143#[derive(Deserialize)]
4144struct RuntimeAttachParams {
4145    #[serde(flatten)]
4146    backend: RuntimeBackendParams,
4147    runtime_id: String,
4148    #[serde(default)]
4149    cwd: Option<PathBuf>,
4150    /// MCP servers to mount into the resumed session (the start door's own
4151    /// field, carried again because a session's tools die with its process).
4152    #[serde(default)]
4153    mcp_servers: Vec<crate::McpServerLaunch>,
4154}
4155
4156#[derive(Deserialize)]
4157struct RuntimeConnectionParams {
4158    connection: String,
4159}
4160
4161#[derive(Deserialize)]
4162struct RuntimeInputParams {
4163    connection: String,
4164    text: String,
4165    #[serde(default)]
4166    image_urls: Vec<String>,
4167}
4168
4169const MAX_RUNTIME_IMAGES: usize = 4;
4170const MAX_RUNTIME_IMAGE_URL_BYTES: usize = 12 * 1024 * 1024;
4171const MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL: usize = 32 * 1024 * 1024;
4172
4173fn validate_runtime_image_urls(image_urls: Vec<String>) -> Result<Vec<String>, ServiceError> {
4174    if image_urls.len() > MAX_RUNTIME_IMAGES {
4175        return Err(ServiceError::InvalidParams(format!(
4176            "a runtime prompt accepts at most {MAX_RUNTIME_IMAGES} images"
4177        )));
4178    }
4179    let mut total = 0usize;
4180    for url in &image_urls {
4181        if !(url.starts_with("data:image/")
4182            || url.starts_with("https://")
4183            || url.starts_with("http://"))
4184        {
4185            return Err(ServiceError::InvalidParams(
4186                "runtime images must be image data URLs or HTTP(S) URLs".into(),
4187            ));
4188        }
4189        if url.len() > MAX_RUNTIME_IMAGE_URL_BYTES {
4190            return Err(ServiceError::InvalidParams(format!(
4191                "one runtime image exceeds the {MAX_RUNTIME_IMAGE_URL_BYTES}-byte encoded limit"
4192            )));
4193        }
4194        total = total.saturating_add(url.len());
4195    }
4196    if total > MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL {
4197        return Err(ServiceError::InvalidParams(format!(
4198            "runtime images exceed the {MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL}-byte encoded total limit"
4199        )));
4200    }
4201    Ok(image_urls)
4202}
4203
4204#[derive(Deserialize)]
4205struct RuntimeRespondParams {
4206    connection: String,
4207    request_id: Value,
4208    response: Value,
4209}
4210
4211fn default_reduction_store_root() -> PathBuf {
4212    if let Some(root) = std::env::var_os("SUPERCODE_HOME") {
4213        return PathBuf::from(root).join("sessions");
4214    }
4215    if let Some(home) = std::env::var_os("HOME") {
4216        return PathBuf::from(home).join(".supercode").join("sessions");
4217    }
4218    PathBuf::from(".supercode").join("sessions")
4219}
4220
4221fn messages_jsonl(messages: &[crate::ChatMessage]) -> std::result::Result<String, ServiceError> {
4222    let mut output = String::new();
4223    for message in messages {
4224        output.push_str(
4225            &serde_json::to_string(message)
4226                .map_err(|error| ServiceError::Operation(error.to_string()))?,
4227        );
4228        output.push('\n');
4229    }
4230    Ok(output)
4231}
4232
4233fn parse_messages_jsonl(
4234    content: &str,
4235) -> std::result::Result<Vec<crate::ChatMessage>, ServiceError> {
4236    content
4237        .lines()
4238        .enumerate()
4239        .filter(|(_, line)| !line.trim().is_empty())
4240        .map(|(index, line)| {
4241            serde_json::from_str::<crate::ChatMessage>(line).map_err(|error| {
4242                ServiceError::Operation(format!(
4243                    "reduced transcript line {} is invalid: {error}",
4244                    index + 1
4245                ))
4246            })
4247        })
4248        .collect()
4249}
4250
4251fn reduced_bootstrap_prompt(
4252    source: &SessionLocator,
4253    target: TransferFormat,
4254    view_jsonl: &str,
4255    sidecar_path: &Path,
4256    reduction_log_path: &Path,
4257) -> String {
4258    format!(
4259        "Continue the work from this losslessly reduced {source_harness} session in {target_harness}.\n\
4260         \n\
4261         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\
4262         \n\
4263         <supercode-reduced-session source-session=\"{source_id}\">\n\
4264         {view_jsonl}\
4265         </supercode-reduced-session>\n\
4266         \n\
4267         Resume from the latest unresolved user request and preserve the source session's decisions and constraints.",
4268        source_harness = source.harness.as_str(),
4269        target_harness = target.id(),
4270        sidecar = sidecar_path.display(),
4271        log = reduction_log_path.display(),
4272        source_id = source.session_id,
4273    )
4274}
4275
4276fn session_artifact(
4277    locator: &SessionLocator,
4278    session: &Session,
4279    target: TransferFormat,
4280) -> std::result::Result<SessionArtifact, ServiceError> {
4281    session_artifact_with_id(locator, session, target, None)
4282}
4283
4284fn session_artifact_with_id(
4285    locator: &SessionLocator,
4286    session: &Session,
4287    target: TransferFormat,
4288    target_session_id: Option<&str>,
4289) -> std::result::Result<SessionArtifact, ServiceError> {
4290    let format: SessionFormat = target.into();
4291    let diagonal = format.source() == session.meta.source;
4292    let has_appended_turns = session
4293        .imported_message_count
4294        .is_some_and(|imported| imported < session.messages.len());
4295    let content = if let Some(id) = target_session_id {
4296        if diagonal && format != SessionFormat::OpenCode {
4297            session
4298                .to_jsonl_spliced(format, Some(id))
4299                .map_err(operation)?
4300        } else {
4301            let mut rewritten = session.clone();
4302            rewritten.meta.session_id = Some(id.to_string());
4303            rewritten.to_jsonl(format).map_err(operation)?
4304        }
4305    } else if diagonal && session.raw_is_verbatim && !has_appended_turns {
4306        session.raw_verbatim()
4307    } else if diagonal {
4308        session.to_jsonl_spliced(format, None).map_err(operation)?
4309    } else {
4310        session.to_jsonl(format).map_err(operation)?
4311    };
4312    let stem = sanitize_filename(
4313        target_session_id
4314            .or(session.meta.session_id.as_deref())
4315            .unwrap_or(&locator.session_id),
4316    );
4317    let suggested_filename = if diagonal && target == TransferFormat::Grok {
4318        "chat_history.jsonl".to_string()
4319    } else if target == TransferFormat::Goose {
4320        format!("{stem}.goose.json")
4321    } else {
4322        format!("{stem}.{}.jsonl", target.id())
4323    };
4324    let mut files = vec![SessionArtifactFile {
4325        path: suggested_filename.clone(),
4326        content: content.clone(),
4327        role: ArtifactFileRole::Primary,
4328    }];
4329    if target == TransferFormat::ClaudeCode {
4330        let bundle_stem = Path::new(&suggested_filename)
4331            .file_stem()
4332            .and_then(|stem| stem.to_str())
4333            .unwrap_or(&stem);
4334        let mut child_paths = BTreeSet::new();
4335        for (index, subagent) in session.subagents.iter().enumerate() {
4336            let agent_id = subagent
4337                .meta
4338                .agent_id
4339                .as_deref()
4340                .map(|id| id.strip_prefix("agent-").unwrap_or(id))
4341                .map(sanitize_filename)
4342                .filter(|id| !id.is_empty())
4343                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4344            let child_has_appended_turns = subagent
4345                .imported_message_count
4346                .is_some_and(|imported| imported < subagent.messages.len());
4347            let child_content = if target_session_id.is_none()
4348                && subagent.meta.source == SessionSource::ClaudeCode
4349                && subagent.raw_is_verbatim
4350                && !child_has_appended_turns
4351            {
4352                subagent.raw_verbatim()
4353            } else if subagent.meta.source == SessionSource::ClaudeCode {
4354                subagent
4355                    .to_jsonl_spliced(SessionFormat::ClaudeCode, target_session_id)
4356                    .map_err(operation)?
4357            } else {
4358                let mut child = subagent.clone();
4359                if let Some(id) = target_session_id {
4360                    child.meta.session_id = Some(id.to_string());
4361                }
4362                child
4363                    .to_jsonl(SessionFormat::ClaudeCode)
4364                    .map_err(operation)?
4365            };
4366            let path = format!("{bundle_stem}/subagents/agent-{agent_id}.jsonl");
4367            if !child_paths.insert(path.clone()) {
4368                return Err(ServiceError::Operation(format!(
4369                    "Claude subagent ids collide at artifact path `{path}`"
4370                )));
4371            }
4372            files.push(SessionArtifactFile {
4373                path,
4374                content: child_content,
4375                role: ArtifactFileRole::Subagent,
4376            });
4377        }
4378    }
4379    if diagonal && target == TransferFormat::Grok {
4380        append_grok_bundle_files(locator, "", ArtifactFileRole::Bundle, &mut files)?;
4381    }
4382    if !diagonal || !session.raw_is_verbatim {
4383        files.push(SessionArtifactFile {
4384            path: "recovery/source.supercode.jsonl".into(),
4385            content: session.to_native_jsonl(),
4386            role: ArtifactFileRole::SourceRecovery,
4387        });
4388        for (index, subagent) in session.subagents.iter().enumerate() {
4389            let id = subagent
4390                .meta
4391                .agent_id
4392                .as_deref()
4393                .map(sanitize_filename)
4394                .unwrap_or_else(|| format!("subagent-{}", index + 1));
4395            files.push(SessionArtifactFile {
4396                path: format!("recovery/subagents/{id}.supercode.jsonl"),
4397                content: subagent.to_native_jsonl(),
4398                role: ArtifactFileRole::SourceRecovery,
4399            });
4400        }
4401    }
4402    if !diagonal && session.meta.source == SessionSource::Grok {
4403        append_grok_bundle_files(
4404            locator,
4405            "recovery/grok/",
4406            ArtifactFileRole::SourceRecovery,
4407            &mut files,
4408        )?;
4409    }
4410    let (fidelity, residue) = if diagonal
4411        && target_session_id.is_none()
4412        && session.raw_is_verbatim
4413        && !has_appended_turns
4414    {
4415        (Fidelity::ByteLossless, Vec::new())
4416    } else if diagonal && !(target_session_id.is_some() && target == TransferFormat::OpenCode) {
4417        (
4418            Fidelity::ValueLossless,
4419            vec![if target_session_id.is_some() {
4420                "target identity was rewritten, so the artifact intentionally differs from source bytes".into()
4421            } else {
4422                "source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured".into()
4423            }],
4424        )
4425    } else {
4426        (
4427            Fidelity::Semantic,
4428            vec!["target schema has no portable slot for every source-native record and metadata field".into()],
4429        )
4430    };
4431    Ok(SessionArtifact {
4432        source_harness: locator.harness.clone(),
4433        target_harness: target.id(),
4434        session_id: target_session_id
4435            .map(str::to_string)
4436            .or_else(|| session.meta.session_id.clone()),
4437        content,
4438        suggested_filename,
4439        files,
4440        fidelity,
4441        residue,
4442    })
4443}
4444
4445fn append_grok_bundle_files(
4446    locator: &SessionLocator,
4447    prefix: &str,
4448    role: ArtifactFileRole,
4449    files: &mut Vec<SessionArtifactFile>,
4450) -> std::result::Result<(), ServiceError> {
4451    let primary = locator.storage.path();
4452    if primary.file_name().and_then(|name| name.to_str()) != Some("chat_history.jsonl") {
4453        return Err(ServiceError::Operation(format!(
4454            "Grok bundle locator must name chat_history.jsonl, got {}",
4455            primary.display()
4456        )));
4457    }
4458    let parent = primary.parent().ok_or_else(|| {
4459        ServiceError::Operation("Grok chat_history.jsonl has no session directory".into())
4460    })?;
4461    for name in ["summary.json", "updates.jsonl"] {
4462        let path = parent.join(name);
4463        let metadata = match std::fs::symlink_metadata(&path) {
4464            Ok(metadata) => metadata,
4465            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
4466            Err(error) => return Err(ServiceError::Operation(error.to_string())),
4467        };
4468        if metadata.file_type().is_symlink() || !metadata.is_file() {
4469            return Err(ServiceError::Operation(format!(
4470                "refusing non-regular Grok bundle member {}",
4471                path.display()
4472            )));
4473        }
4474        let content = std::fs::read_to_string(&path).map_err(|error| {
4475            ServiceError::Operation(format!(
4476                "Grok bundle member {} is not representable as UTF-8: {error}",
4477                path.display()
4478            ))
4479        })?;
4480        files.push(SessionArtifactFile {
4481            path: format!("{prefix}{name}"),
4482            content,
4483            role: match role {
4484                ArtifactFileRole::Bundle => ArtifactFileRole::Bundle,
4485                _ => ArtifactFileRole::SourceRecovery,
4486            },
4487        });
4488    }
4489    Ok(())
4490}
4491
4492fn handoff_artifact(
4493    locator: &SessionLocator,
4494    session: &Session,
4495    target: TransferFormat,
4496    cwd: &Path,
4497) -> std::result::Result<SessionArtifact, ServiceError> {
4498    if target != TransferFormat::Grok {
4499        let target_session_id = target_session_id(target);
4500        return session_artifact_with_id(locator, session, target, Some(&target_session_id));
4501    }
4502
4503    // Stock Grok's importer accepts Claude/Codex transcripts and materializes its own
4504    // multi-file session bundle. A synthesized Grok chat_history.jsonl alone is not a
4505    // resumable handoff because updates.jsonl is the authoritative restore log.
4506    let mut importable = session.clone();
4507    // The Claude importer validates sessionId as a UUID. Source harness identities
4508    // are not portable (OpenCode, for example, uses `ses_...`), and a handoff must
4509    // not overwrite an existing target session when the source already uses UUIDs.
4510    // Mint a distinct target identity and still bind the importer-returned ID at
4511    // launch time because the importer remains the authority on materialization.
4512    importable.meta.session_id = Some(target_session_id(TransferFormat::ClaudeCode));
4513    importable.meta.cwd = Some(if cwd.is_absolute() {
4514        cwd.to_path_buf()
4515    } else {
4516        std::env::current_dir()
4517            .map_err(|error| ServiceError::Operation(error.to_string()))?
4518            .join(cwd)
4519    });
4520    let content = importable
4521        .to_jsonl(SessionFormat::ClaudeCode)
4522        .map_err(operation)?;
4523    let stem = sanitize_filename(
4524        importable
4525            .meta
4526            .session_id
4527            .as_deref()
4528            .unwrap_or(&locator.session_id),
4529    );
4530    let suggested_filename = format!("{stem}.grok-import.claude-code.jsonl");
4531    Ok(SessionArtifact {
4532        source_harness: locator.harness.clone(),
4533        // This names the artifact's actual wire format. The requested handoff target
4534        // remains Grok; its official importer is the materialization boundary.
4535        target_harness: TransferFormat::ClaudeCode.id(),
4536        session_id: importable.meta.session_id.clone(),
4537        content: content.clone(),
4538        suggested_filename: suggested_filename.clone(),
4539        files: vec![SessionArtifactFile {
4540            path: suggested_filename,
4541            content,
4542            role: ArtifactFileRole::Primary,
4543        }],
4544        fidelity: Fidelity::Semantic,
4545        residue: vec!["Grok's stock importer accepts a Claude Code transcript, not a complete Grok updates/session bundle".into()],
4546    })
4547}
4548
4549fn target_session_id(target: TransferFormat) -> String {
4550    let uuid = generated_session_id();
4551    match target {
4552        TransferFormat::OpenCode => format!("ses_{}", uuid.replace('-', "")),
4553        TransferFormat::ClaudeCode
4554        | TransferFormat::Codex
4555        | TransferFormat::Pi
4556        | TransferFormat::Grok
4557        | TransferFormat::Gemini
4558        | TransferFormat::Goose
4559        | TransferFormat::Hermes => uuid,
4560    }
4561}
4562
4563fn sanitize_filename(value: &str) -> String {
4564    let value = value
4565        .chars()
4566        .map(|character| {
4567            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
4568                character
4569            } else {
4570                '-'
4571            }
4572        })
4573        .collect::<String>();
4574    let value = value.trim_matches('-');
4575    if value.is_empty() {
4576        "session".into()
4577    } else {
4578        value.chars().take(100).collect()
4579    }
4580}
4581
4582fn handoff_instructions(
4583    target: TransferFormat,
4584    session_id: &str,
4585    cwd: &Path,
4586) -> HandoffInstructions {
4587    let launch = |program: &str, arguments: Vec<String>| StructuredLaunch {
4588        cwd: cwd.to_path_buf(),
4589        program: program.into(),
4590        arguments,
4591        env: BTreeMap::new(),
4592    };
4593    match target {
4594        TransferFormat::ClaudeCode => HandoffInstructions {
4595            launch: launch("claude", vec!["--resume".into(), session_id.into()]),
4596            materialize: None,
4597            requires_materialization: true,
4598            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(),
4599        },
4600        TransferFormat::Hermes => HandoffInstructions {
4601            launch: launch("hermes", vec!["--resume".into(), session_id.into()]),
4602            materialize: None,
4603            requires_materialization: true,
4604            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(),
4605        },
4606        TransferFormat::Codex => HandoffInstructions {
4607            launch: launch("codex", vec!["resume".into(), session_id.into()]),
4608            materialize: None,
4609            requires_materialization: true,
4610            note: "Write the artifact into Codex's native rollout store before running the resume launch; Codex has no general transcript-import command.".into(),
4611        },
4612        TransferFormat::OpenCode => HandoffInstructions {
4613            launch: launch("opencode", vec!["--session".into(), session_id.into()]),
4614            materialize: Some(launch(
4615                "opencode",
4616                vec!["import".into(), "{artifact_path}".into()],
4617            )),
4618            requires_materialization: true,
4619            note: "Write the artifact to a file, run the materialize command with its path, then launch the imported session.".into(),
4620        },
4621        TransferFormat::Pi => HandoffInstructions {
4622            launch: launch("pi", vec!["--session".into(), "{artifact_path}".into()]),
4623            materialize: None,
4624            requires_materialization: true,
4625            note: "Write the artifact to a file and replace {artifact_path} in the launch arguments; Pi can resume that file directly.".into(),
4626        },
4627        TransferFormat::Grok => HandoffInstructions {
4628            launch: launch(
4629                "grok",
4630                vec![
4631                    "--resume".into(),
4632                    "{imported_session_id}".into(),
4633                    "--fork-session".into(),
4634                ],
4635            ),
4636            materialize: Some(launch(
4637                "grok",
4638                vec!["import".into(), "--json".into(), "{artifact_path}".into()],
4639            )),
4640            requires_materialization: true,
4641            note: "The artifact is Claude Code JSONL for Grok's official importer. Write it to a file, run the materialize command, read sessionId from its NDJSON outcome=imported record, replace {imported_session_id} in the launch arguments, then launch a writable fork of the imported session.".into(),
4642        },
4643        TransferFormat::Gemini => HandoffInstructions {
4644            launch: launch(
4645                "gemini",
4646                vec!["--session-file".into(), "{artifact_path}".into()],
4647            ),
4648            materialize: None,
4649            requires_materialization: true,
4650            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(),
4651        },
4652        TransferFormat::Goose => HandoffInstructions {
4653            launch: launch(
4654                "goose",
4655                vec![
4656                    "session".into(),
4657                    "--resume".into(),
4658                    "--session-id".into(),
4659                    "{imported_session_id}".into(),
4660                ],
4661            ),
4662            materialize: Some(launch(
4663                "goose",
4664                vec!["session".into(), "import".into(), "{artifact_path}".into()],
4665            )),
4666            requires_materialization: true,
4667            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(),
4668        },
4669    }
4670}
4671
4672fn resume_launch(
4673    harness: &str,
4674    session_id: &str,
4675    cwd: &Path,
4676    policy: ResumePolicy,
4677) -> std::result::Result<StructuredLaunch, ServiceError> {
4678    let mut arguments = Vec::new();
4679    let program = match harness {
4680        HarnessId::GROK => {
4681            if matches!(policy, ResumePolicy::Yolo) {
4682                if crate::support::self_sandbox_supported() {
4683                    arguments.extend(["--sandbox".into(), "workspace".into()]);
4684                }
4685                arguments.push("--always-approve".into());
4686            }
4687            arguments.extend(["--resume".into(), session_id.into()]);
4688            "grok"
4689        }
4690        HarnessId::CODEX => {
4691            let cwd_key = serde_json::to_string(cwd.to_string_lossy().as_ref())
4692                .expect("a filesystem path always serializes as JSON text");
4693            arguments.extend([
4694                "-c".into(),
4695                "check_for_update_on_startup=false".into(),
4696                "-c".into(),
4697                format!("projects.{cwd_key}.trust_level=\"trusted\""),
4698            ]);
4699            if matches!(policy, ResumePolicy::Yolo) {
4700                arguments.extend([
4701                    "--dangerously-bypass-approvals-and-sandbox".into(),
4702                    "--dangerously-bypass-hook-trust".into(),
4703                ]);
4704            }
4705            arguments.extend(["resume".into(), session_id.into()]);
4706            "codex"
4707        }
4708        HarnessId::CLAUDE_CODE => {
4709            if matches!(policy, ResumePolicy::Yolo) {
4710                arguments.push("--dangerously-skip-permissions".into());
4711            }
4712            arguments.extend(["--resume".into(), session_id.into()]);
4713            "claude"
4714        }
4715        HarnessId::GEMINI => {
4716            if matches!(policy, ResumePolicy::Yolo) {
4717                arguments.push("--yolo".into());
4718            }
4719            arguments.extend(["--resume".into(), session_id.into()]);
4720            "gemini"
4721        }
4722        HarnessId::GOOSE => {
4723            arguments.extend([
4724                "session".into(),
4725                "--resume".into(),
4726                "--session-id".into(),
4727                session_id.into(),
4728            ]);
4729            "goose"
4730        }
4731        HarnessId::PI => {
4732            if matches!(policy, ResumePolicy::Yolo) {
4733                arguments.push("--approve".into());
4734            }
4735            arguments.extend(["--session".into(), session_id.into()]);
4736            "pi"
4737        }
4738        HarnessId::OPENCODE => {
4739            arguments.extend(["--session".into(), session_id.into()]);
4740            "opencode"
4741        }
4742        HarnessId::SUPERCODE => {
4743            if matches!(policy, ResumePolicy::Yolo) {
4744                arguments.push("--dangerous".into());
4745            }
4746            arguments.extend(["resume".into(), session_id.into()]);
4747            "supercode"
4748        }
4749        other => {
4750            return Err(ServiceError::InvalidParams(format!(
4751                "no structured resume launch is registered for harness `{other}`"
4752            )))
4753        }
4754    };
4755    Ok(StructuredLaunch {
4756        cwd: cwd.to_path_buf(),
4757        program: program.into(),
4758        arguments,
4759        env: BTreeMap::new(),
4760    })
4761}
4762
4763/// Stage the resolved gateway credential in a private (0600) file so the
4764/// bridge can read it via `--token-file` — the delivery the real `openclaw
4765/// acp` accepts. One stable file per endpoint (keyed by an address digest,
4766/// no secret material in the name), overwritten on every connect so files
4767/// never accumulate and a rotated token never goes stale on disk.
4768fn openclaw_gateway_token_file(address: &str, secret: &str) -> std::io::Result<PathBuf> {
4769    let digest = blake3::hash(address.as_bytes()).to_hex();
4770    let path = std::env::temp_dir().join(format!(
4771        "supercode-openclaw-gateway-token-{}",
4772        &digest.as_str()[..16]
4773    ));
4774    #[cfg(unix)]
4775    {
4776        use std::io::Write;
4777        use std::os::unix::fs::OpenOptionsExt;
4778        let mut file = std::fs::OpenOptions::new()
4779            .write(true)
4780            .create(true)
4781            .truncate(true)
4782            .mode(0o600)
4783            .open(&path)?;
4784        file.write_all(secret.as_bytes())?;
4785    }
4786    #[cfg(not(unix))]
4787    std::fs::write(&path, secret)?;
4788    Ok(path)
4789}
4790
4791/// Open a connect-mode descriptor: resolve the endpoint address and
4792/// credential from the harness's own config file and build the backend that
4793/// joins the already-running endpoint. Fails closed with a specific
4794/// diagnostic when the config cannot be resolved or the declared protocol has
4795/// no connect-capable client yet.
4796fn open_connect_descriptor(
4797    descriptor: &crate::HarnessSupportDescriptor,
4798    home: &Path,
4799) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
4800    let Some(connect) = &descriptor.runtime.connect_launch else {
4801        return Err(ServiceError::InvalidParams(format!(
4802            "harness `{}` has no registered connect-mode launch",
4803            descriptor.id.as_str()
4804        )));
4805    };
4806    let resolved = connect
4807        .resolve(home)
4808        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
4809    match (descriptor.id.as_str(), connect.protocol.as_str()) {
4810        (HarnessId::OPENCODE, protocol) if protocol.starts_with("opencode-http") => {
4811            let mut backend = OpenCodeRuntimeBackend::connect(&resolved.address);
4812            if let Some(token) = resolved.auth {
4813                backend = backend.with_bearer(token);
4814            }
4815            Ok(Box::new(backend))
4816        }
4817        (HarnessId::OPENCLAW, protocol) if protocol.starts_with("acp") => {
4818            // OpenClaw's own `openclaw acp` binary is the gateway client: a
4819            // stdio ACP bridge that joins the RUNNING gateway at the resolved
4820            // endpoint. Blind-walk finding 2026-08-31: the real bridge does
4821            // NOT honor OPENCLAW_GATEWAY_TOKEN from the environment — the
4822            // credential must arrive via `--token-file` (never bare `--token`
4823            // on argv, where process listings could read it). The env var is
4824            // still set for older bridges that did read it. Requires openclaw
4825            // >= 2026.7: the 2026.2 bridge drops its gateway socket
4826            // mid-prompt and advertises no session resume (executed finding,
4827            // docs/interop/research/openclaw-acp-dialect-2026-08-30.json).
4828            let mut env = BTreeMap::new();
4829            let mut arguments = vec!["acp".into(), "--url".into(), resolved.address.clone()];
4830            if let Some(token) = resolved.auth {
4831                let token_path = openclaw_gateway_token_file(&resolved.address, token.secret())
4832                    .map_err(|error| {
4833                        ServiceError::UnsupportedAction(format!(
4834                            "could not stage the gateway credential for the bridge: {error}"
4835                        ))
4836                    })?;
4837                arguments.push("--token-file".into());
4838                arguments.push(token_path.to_string_lossy().into_owned());
4839                env.insert("OPENCLAW_GATEWAY_TOKEN".to_string(), token.secret().to_string());
4840            }
4841            // The bridge program comes from the descriptor's own default
4842            // launch (the compiled registry pins `openclaw`), so tests can
4843            // substitute an absolute mock-bridge path without touching
4844            // process-global state.
4845            let program = descriptor
4846                .runtime
4847                .default_launch
4848                .as_ref()
4849                .map(|launch| launch.program.clone())
4850                .unwrap_or_else(|| "openclaw".into());
4851            let launch = RuntimeLaunch {
4852                program,
4853                arguments,
4854                env,
4855            };
4856            Ok(Box::new(
4857                crate::AcpRuntimeBackend::new(descriptor.id.clone(), launch)
4858                    .with_resume_support(descriptor.runtime.capabilities.resume_session),
4859            ))
4860        }
4861        _ => Err(ServiceError::UnsupportedAction(format!(
4862            "connect-mode endpoint for `{}` speaks `{}`; joining it needs that protocol's gateway client",
4863            descriptor.id.as_str(),
4864            connect.protocol
4865        ))),
4866    }
4867}
4868
4869/// The registry's connect-mode launch for this harness, honored only when the
4870/// caller supplied neither an explicit launch nor a base URL.
4871fn registry_connect_descriptor(
4872    params: &RuntimeBackendParams,
4873) -> Option<crate::HarnessSupportDescriptor> {
4874    if params.launch.is_some() || params.base_url.is_some() {
4875        return None;
4876    }
4877    harness_support_registry()
4878        .harnesses
4879        .into_iter()
4880        .find(|descriptor| descriptor.id == params.harness)
4881        .filter(|descriptor| descriptor.runtime.connect_launch.is_some())
4882}
4883
4884fn service_home() -> std::result::Result<PathBuf, ServiceError> {
4885    std::env::var_os("HOME").map(PathBuf::from).ok_or_else(|| {
4886        ServiceError::UnsupportedAction(
4887            "connect-mode launches need HOME to locate the harness config".into(),
4888        )
4889    })
4890}
4891
4892/// The doors that open a runtime: each spawns or joins a program and waits on
4893/// that program's protocol handshake before it can answer.
4894pub const RUNTIME_OPEN_METHODS: &[&str] = &[
4895    "harness.v1.runtimes.start",
4896    "harness.v1.runtimes.resume",
4897    "harness.v1.runtimes.attach",
4898    "harness.v1.runtimes.attach_existing",
4899];
4900
4901/// How long a runtime gets to finish opening before its caller is answered an
4902/// error instead. A program that never speaks the protocol at all — the wrong
4903/// binary, a shim that prints usage and waits — never answers the handshake,
4904/// so the wait is unbounded without this.
4905pub const RUNTIME_OPEN_DEADLINE: Duration = Duration::from_secs(60);
4906
4907/// How long a control call on an ALREADY-open runtime — send input, interrupt,
4908/// steer, respond, close — gets before its caller is answered an error
4909/// instead. A live runtime answers these in milliseconds; a wedged one never
4910/// answers at all, and `close` is exactly what a caller reaches for when it
4911/// suspects that.
4912pub const RUNTIME_CONTROL_DEADLINE: Duration = Duration::from_secs(30);
4913
4914/// The doors whose work happens entirely OUTSIDE this service's state once
4915/// its state has been read: probing harnesses, couriering a message into a
4916/// live session, and performing a conversation verb through a harness's own
4917/// CLI / HTTP / store door. Every one of them waits on a child process or a
4918/// network peer. See [`HarnessSessionService::detach`].
4919pub const DETACHED_METHODS: &[&str] = &[
4920    "harness.v1.harnesses.list",
4921    "harness.v1.harnesses.probe",
4922    "harness.v1.sessions.message",
4923    "harness.v1.sessions.new",
4924    "harness.v1.sessions.reset",
4925    "harness.v1.sessions.archive",
4926    "harness.v1.sessions.delete",
4927];
4928
4929/// How long a request moved off a transport's loop gets before its caller is
4930/// answered an error instead. Each of these already bounds its own inner
4931/// waits (a probe's handshake, the courier's run); this is the backstop for
4932/// the ones that do not — a harness CLI that never exits — so no caller waits
4933/// forever on a detached task no one is watching.
4934pub const DETACHED_CALL_DEADLINE: Duration = Duration::from_secs(120);
4935
4936/// How long `sessions.discover` gets before its caller is answered an error
4937/// instead. Discovery reads each harness's own store, and a store on a cold
4938/// or unavailable mount answers at the filesystem's pace rather than its own.
4939///
4940/// Deliberately shorter than the clients' own request deadline (30s): the
4941/// server's answer names the store that did not answer, and it is only read
4942/// if it lands before the client stops listening.
4943pub const SESSION_DISCOVER_DEADLINE: Duration = Duration::from_secs(25);
4944
4945/// Bound one control call on an open runtime by [`RUNTIME_CONTROL_DEADLINE`],
4946/// naming the method and the bound when it blows.
4947async fn within_control_deadline<F: std::future::Future>(
4948    method: &str,
4949    call: F,
4950) -> std::result::Result<F::Output, ServiceError> {
4951    tokio::time::timeout(RUNTIME_CONTROL_DEADLINE, call)
4952        .await
4953        .map_err(|_| {
4954            ServiceError::Operation(format!(
4955                "`{method}` gave up after {}s: the runtime did not answer",
4956                RUNTIME_CONTROL_DEADLINE.as_secs()
4957            ))
4958        })
4959}
4960
4961/// One [`RUNTIME_OPEN_METHODS`] request, parsed but not yet started. See
4962/// [`HarnessSessionService::runtime_open`] for why it exists apart from
4963/// [`HarnessSessionService::handle_async`].
4964pub struct RuntimeOpen {
4965    id: Value,
4966    method: String,
4967    params: Value,
4968}
4969
4970impl RuntimeOpen {
4971    /// Do the waiting: spawn or join the program and complete its handshake,
4972    /// bounded by [`RUNTIME_OPEN_DEADLINE`]. Touches no service state, so this
4973    /// runs on any task.
4974    pub async fn open(self) -> OpenedRuntime {
4975        let Self { id, method, params } = self;
4976        let outcome = open_runtime(&method, params).await;
4977        OpenedRuntime { id, outcome }
4978    }
4979}
4980
4981/// The result of [`RuntimeOpen::open`], ready for
4982/// [`HarnessSessionService::finish_runtime_open`].
4983pub struct OpenedRuntime {
4984    id: Value,
4985    outcome: std::result::Result<OpenRuntime, ServiceError>,
4986}
4987
4988/// One detached request: the half that reads this service's state already
4989/// done, and the half that waits not yet started. See
4990/// [`HarnessSessionService::detach`] and
4991/// [`HarnessSessionService::detach_runtime`].
4992pub struct DetachedCall {
4993    id: Value,
4994    method: String,
4995    work: std::result::Result<Work, ServiceError>,
4996}
4997
4998impl DetachedCall {
4999    /// Do the waiting and answer. Runs on any task: whatever this call needed
5000    /// from the service was taken before it left.
5001    pub async fn run(self) -> DetachedAnswer {
5002        let Self { id, method, work } = self;
5003        match work {
5004            // A call holding a runtime is already bounded by
5005            // RUNTIME_CONTROL_DEADLINE, and its future OWNS that connection:
5006            // a second timeout around it would drop the connection mid-call
5007            // and take down a runtime its caller still has.
5008            Ok(Work::Runtime(work)) => {
5009                let (result, returned) = work.run().await;
5010                DetachedAnswer {
5011                    response: service_response(id, result),
5012                    returned,
5013                }
5014            }
5015            Ok(Work::Free(work)) => {
5016                let result = match tokio::time::timeout(DETACHED_CALL_DEADLINE, work.run()).await {
5017                    Ok(result) => result,
5018                    Err(_) => Err(ServiceError::Operation(format!(
5019                        "`{method}` gave up after {}s: the harness it waits on did not answer",
5020                        DETACHED_CALL_DEADLINE.as_secs()
5021                    ))),
5022                };
5023                DetachedAnswer {
5024                    response: service_response(id, result),
5025                    returned: None,
5026                }
5027            }
5028            Err(error) => DetachedAnswer {
5029                response: service_response(id, Err(error)),
5030                returned: None,
5031            },
5032        }
5033    }
5034}
5035
5036/// One detached call's complete answer, plus whatever it must hand back to
5037/// the service before that answer is written. See
5038/// [`HarnessSessionService::finish_detached`].
5039pub struct DetachedAnswer {
5040    response: Value,
5041    returned: Option<ReturnedRuntime>,
5042}
5043
5044impl DetachedAnswer {
5045    /// The caller's JSON-RPC response, for a transport that owns no service
5046    /// to give a borrowed connection back to.
5047    pub fn into_response(self) -> Value {
5048        self.response
5049    }
5050}
5051
5052/// A connection lent to a detached call, on its way back to the service that
5053/// owns it.
5054pub struct ReturnedRuntime {
5055    connection: String,
5056    runtime: Box<dyn RuntimeConnection>,
5057}
5058
5059/// The waiting half of one detached request: with nothing of the service's
5060/// in hand, or holding a connection the service lent out for the call.
5061enum Work {
5062    Free(DetachedWork),
5063    Runtime(RuntimeWork),
5064}
5065
5066/// The waiting half of one detached request that holds nothing of the
5067/// service's.
5068enum DetachedWork {
5069    /// Probe the selected harnesses: find their executables, ask each its
5070    /// version, and at `probe: handshake` start each one and complete its
5071    /// protocol handshake.
5072    Inventory(InventoryWork),
5073    /// Run the courier that delivers one message into a live session.
5074    Message(MessageSessionParams),
5075    /// Perform one conversation verb through the harness's own CLI, HTTP API,
5076    /// daemon socket, or supercode's own store.
5077    SessionMutation {
5078        verb: crate::SessionVerb,
5079        mutation: crate::SessionMutation,
5080    },
5081}
5082
5083impl DetachedWork {
5084    async fn run(self) -> std::result::Result<Value, ServiceError> {
5085        match self {
5086            Self::Inventory(work) => run_inventory(work).await,
5087            Self::Message(params) => {
5088                Ok(message_live_session(&params, &crate::claude_peer::ProcessCourierRunner).await)
5089            }
5090            Self::SessionMutation { verb, mutation } => {
5091                let outcome = run_session_mutation(verb, &mutation).await?;
5092                serde_json::to_value(outcome)
5093                    .map_err(|error| ServiceError::Operation(error.to_string()))
5094            }
5095        }
5096    }
5097}
5098
5099/// One detached call that holds a runtime connection for its whole run.
5100enum RuntimeWork {
5101    /// Tear down a runtime the service has already surrendered.
5102    Close {
5103        runtime: Box<dyn RuntimeConnection>,
5104        process_group: Option<u32>,
5105    },
5106    /// Type one live slash command through a borrowed connection, then give
5107    /// the connection back.
5108    LiveCommand {
5109        connection: String,
5110        runtime: Box<dyn RuntimeConnection>,
5111        verb: crate::SessionVerb,
5112        mutation: crate::SessionMutation,
5113        command: &'static str,
5114        session: String,
5115    },
5116}
5117
5118/// What one [`RuntimeWork`] answers with: the caller's result, and the
5119/// connection to give back when the call only borrowed one.
5120type RuntimeWorkAnswer = (
5121    std::result::Result<Value, ServiceError>,
5122    Option<ReturnedRuntime>,
5123);
5124
5125impl RuntimeWork {
5126    async fn run(self) -> RuntimeWorkAnswer {
5127        match self {
5128            Self::Close {
5129                runtime,
5130                process_group,
5131            } => (close_runtime(runtime, process_group).await, None),
5132            Self::LiveCommand {
5133                connection,
5134                mut runtime,
5135                verb,
5136                mutation,
5137                command,
5138                session,
5139            } => {
5140                let result =
5141                    type_live_command(runtime.as_mut(), verb, &mutation, command, session).await;
5142                (
5143                    result,
5144                    Some(ReturnedRuntime {
5145                        connection,
5146                        runtime,
5147                    }),
5148                )
5149            }
5150        }
5151    }
5152}
5153
5154/// Tear down a runtime already out of the service, within
5155/// [`RUNTIME_CONTROL_DEADLINE`].
5156async fn close_runtime(
5157    mut runtime: Box<dyn RuntimeConnection>,
5158    process_group: Option<u32>,
5159) -> std::result::Result<Value, ServiceError> {
5160    match within_control_deadline("harness.v1.runtimes.close", runtime.close()).await {
5161        Ok(result) => {
5162            result.map_err(operation)?;
5163            Ok(json!({"closed": true}))
5164        }
5165        Err(deadline) => {
5166            // Dropping the handle is not enough: the process that stopped
5167            // answering is held by a task parked on it, so nothing here runs
5168            // its Drop. Signal the group the graceful path would have
5169            // signalled, then say so.
5170            let killed = kill_runtime_process_group(process_group);
5171            drop(runtime);
5172            Ok(json!({
5173                "closed": true,
5174                "killed": killed,
5175                "detail": error_message(deadline),
5176            }))
5177        }
5178    }
5179}
5180
5181/// The conversation a live `sessions.new` / `sessions.reset` acts on: the one
5182/// the request named, or the runtime's own session.
5183fn live_session_name(runtime: &dyn RuntimeConnection, mutation: &crate::SessionMutation) -> String {
5184    mutation
5185        .session
5186        .clone()
5187        .filter(|value| !value.trim().is_empty())
5188        .unwrap_or_else(|| runtime.handle().runtime_id.clone())
5189}
5190
5191/// Type one harness slash command into a live session through the very same
5192/// `send_input` path a human's message takes, within
5193/// [`RUNTIME_CONTROL_DEADLINE`].
5194async fn type_live_command(
5195    runtime: &mut dyn RuntimeConnection,
5196    verb: crate::SessionVerb,
5197    mutation: &crate::SessionMutation,
5198    command: &str,
5199    session: String,
5200) -> std::result::Result<Value, ServiceError> {
5201    within_control_deadline(
5202        &format!("sessions.{}", verb.as_str()),
5203        runtime.send_input(RuntimeInput {
5204            text: command.to_string(),
5205            image_urls: Vec::new(),
5206        }),
5207    )
5208    .await?
5209    .map_err(operation)?;
5210    let outcome = crate::sessions_control::live_outcome(verb, mutation, command, session)
5211        .map_err(session_control_error)?;
5212    serde_json::to_value(outcome).map_err(|error| ServiceError::Operation(error.to_string()))
5213}
5214
5215/// A runtime that is up and whose handshake completed, with what the service
5216/// needs to take ownership of it.
5217enum OpenRuntime {
5218    /// supercode spawned this process, so it also hosts it: a frontend server,
5219    /// a live-runtime registration and a terminal launch of its own.
5220    Hosted {
5221        runtime: Box<dyn RuntimeConnection>,
5222        capabilities: crate::RuntimeCapabilities,
5223        workspace: PathBuf,
5224    },
5225    /// `attach_existing` joined a process supercode does not own. It is
5226    /// registered as a bare connection and hosts nothing.
5227    Joined { runtime: Box<dyn RuntimeConnection> },
5228}
5229
5230/// Open the runtime one [`RUNTIME_OPEN_METHODS`] request asks for, within
5231/// [`RUNTIME_OPEN_DEADLINE`]. The error a blown deadline answers names the
5232/// method and the bound, so a caller reads why it was cut loose instead of
5233/// waiting on a handshake that is never coming.
5234async fn open_runtime(
5235    method: &str,
5236    params: Value,
5237) -> std::result::Result<OpenRuntime, ServiceError> {
5238    match tokio::time::timeout(
5239        RUNTIME_OPEN_DEADLINE,
5240        open_runtime_unbounded(method, params),
5241    )
5242    .await
5243    {
5244        Ok(result) => result,
5245        Err(_) => Err(ServiceError::Operation(format!(
5246            "`{method}` gave up after {}s: the runtime never finished its protocol handshake",
5247            RUNTIME_OPEN_DEADLINE.as_secs()
5248        ))),
5249    }
5250}
5251
5252async fn open_runtime_unbounded(
5253    method: &str,
5254    params: Value,
5255) -> std::result::Result<OpenRuntime, ServiceError> {
5256    match method {
5257        "harness.v1.runtimes.start" => {
5258            let params = decode::<RuntimeStartParams>(params)?;
5259            let backend = runtime_backend(&params.backend)?;
5260            let capabilities = backend.capabilities();
5261            let workspace = params.cwd.clone();
5262            let runtime = backend
5263                .start(RuntimeStartRequest {
5264                    cwd: params.cwd,
5265                    launch: runtime_launch(&params.backend),
5266                    mcp_servers: params.mcp_servers,
5267                })
5268                .await
5269                .map_err(operation)?;
5270            Ok(OpenRuntime::Hosted {
5271                runtime,
5272                capabilities,
5273                workspace,
5274            })
5275        }
5276        "harness.v1.runtimes.resume" | "harness.v1.runtimes.attach" => {
5277            let params = decode::<RuntimeAttachParams>(params)?;
5278            let backend = runtime_backend(&params.backend)?;
5279            let capabilities = backend.capabilities();
5280            let workspace = params
5281                .cwd
5282                .clone()
5283                .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
5284            let runtime = backend
5285                .attach(RuntimeAttachRequest {
5286                    runtime_id: params.runtime_id,
5287                    cwd: params.cwd,
5288                    launch: runtime_launch(&params.backend),
5289                    mcp_servers: params.mcp_servers,
5290                })
5291                .await
5292                .map_err(operation)?;
5293            Ok(OpenRuntime::Hosted {
5294                runtime,
5295                capabilities,
5296                workspace,
5297            })
5298        }
5299        "harness.v1.runtimes.attach_existing" => {
5300            let params = decode::<RuntimeAttachParams>(params)?;
5301            let backend: Box<dyn RuntimeBackend> = match params
5302                .backend
5303                .base_url
5304                .as_deref()
5305                .and_then(|value| LiveRuntimeEndpoint::parse(value).ok())
5306            {
5307                Some(endpoint) => {
5308                    #[cfg(not(feature = "adapter-api"))]
5309                    {
5310                        let _ = endpoint;
5311                        return Err(ServiceError::UnsupportedAction(
5312                            "live HTTP attachment adapter is not compiled".into(),
5313                        ));
5314                    }
5315                    #[cfg(feature = "adapter-api")]
5316                    {
5317                        let workspace = params.cwd.clone().ok_or_else(|| {
5318                            ServiceError::InvalidParams(
5319                                "Supercode live attach requires the project cwd".into(),
5320                            )
5321                        })?;
5322                        let source = LiveRuntimeSource {
5323                            harness: params.backend.harness.as_str().to_string(),
5324                            session_id: params.runtime_id.clone(),
5325                            workspace,
5326                        };
5327                        let receipt = resolve_live_runtime(&endpoint, &source)
5328                            .map_err(|error| ServiceError::Operation(error.to_string()))?;
5329                        Box::new(SupercodeHttpRuntimeBackend::new(receipt))
5330                    }
5331                }
5332                None => runtime_backend(&params.backend)?,
5333            };
5334            let capabilities = backend.capabilities();
5335            if !capabilities.attach_existing_process {
5336                return Err(ServiceError::Operation(format!(
5337                    "{} cannot attach to an already-running process; use runtimes.resume for a persisted session",
5338                    backend.harness().as_str()
5339                )));
5340            }
5341            let runtime = backend
5342                .attach_existing(RuntimeAttachRequest {
5343                    runtime_id: params.runtime_id,
5344                    cwd: params.cwd,
5345                    launch: runtime_launch(&params.backend),
5346                    mcp_servers: params.mcp_servers,
5347                })
5348                .await
5349                .map_err(operation)?;
5350            Ok(OpenRuntime::Joined { runtime })
5351        }
5352        _ => Err(ServiceError::MethodNotFound),
5353    }
5354}
5355
5356/// Wrap one service outcome in its JSON-RPC 2.0 envelope.
5357fn service_response(id: Value, result: std::result::Result<Value, ServiceError>) -> Value {
5358    match result {
5359        Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
5360        Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
5361        Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
5362        Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
5363        Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
5364        Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
5365    }
5366}
5367
5368fn runtime_backend(
5369    params: &RuntimeBackendParams,
5370) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
5371    if let Some(descriptor) = registry_connect_descriptor(params) {
5372        return open_connect_descriptor(&descriptor, &service_home()?);
5373    }
5374    if params.protocol.as_deref() == Some("acp") {
5375        let launch = params
5376            .launch
5377            .clone()
5378            .or_else(|| {
5379                harness_support_registry()
5380                    .harnesses
5381                    .into_iter()
5382                    .find(|harness| harness.id == params.harness)
5383                    .filter(|harness| {
5384                        harness.runtime.implementation == ImplementationKind::GenericProtocol
5385                            && harness.runtime.protocol.starts_with("acp")
5386                    })
5387                    .and_then(|harness| harness.runtime.default_launch)
5388            })
5389            .ok_or_else(|| {
5390                ServiceError::InvalidParams(
5391                    "an ACP runtime requires `launch` unless the harness has a registered default"
5392                        .into(),
5393                )
5394            })?;
5395        let resume_session = harness_support_registry()
5396            .harnesses
5397            .into_iter()
5398            .find(|harness| harness.id == params.harness)
5399            .is_some_and(|harness| harness.runtime.capabilities.resume_session);
5400        return Ok(Box::new(
5401            AcpRuntimeBackend::new(params.harness.clone(), launch)
5402                .with_resume_support(resume_session),
5403        ));
5404    }
5405    let backend: Box<dyn RuntimeBackend> = match params.harness.as_str() {
5406        HarnessId::CODEX => Box::new(CodexRuntimeBackend::new()),
5407        HarnessId::CLAUDE_CODE => Box::new(ClaudeCodeRuntimeBackend::new()),
5408        HarnessId::PI => Box::new(PiRuntimeBackend::new()),
5409        HarnessId::OPENCODE => match &params.base_url {
5410            Some(url) => Box::new(OpenCodeRuntimeBackend::connect(url)),
5411            None => Box::new(OpenCodeRuntimeBackend::new()),
5412        },
5413        harness => {
5414            let descriptor = harness_support_registry()
5415                .harnesses
5416                .into_iter()
5417                .find(|descriptor| descriptor.id.as_str() == harness)
5418                .filter(|descriptor| {
5419                    descriptor.runtime.implementation == ImplementationKind::GenericProtocol
5420                        && descriptor.runtime.protocol.starts_with("acp")
5421                });
5422            let Some(descriptor) = descriptor else {
5423                return Err(ServiceError::InvalidParams(format!(
5424                    "no runtime adapter for harness `{harness}`; use protocol `acp` with a launch command"
5425                )));
5426            };
5427            let resume = descriptor.runtime.capabilities.resume_session;
5428            Box::new(
5429                AcpRuntimeBackend::new(
5430                    descriptor.id,
5431                    descriptor
5432                        .runtime
5433                        .default_launch
5434                        .expect("generic ACP registry entry includes its launch"),
5435                )
5436                .with_resume_support(resume),
5437            )
5438        }
5439    };
5440    Ok(backend)
5441}
5442
5443fn runtime_launch(params: &RuntimeBackendParams) -> Option<RuntimeLaunch> {
5444    if let Some(launch) = &params.launch {
5445        return Some(launch.clone());
5446    }
5447    if !matches!(params.policy, RuntimePolicy::Yolo) {
5448        return None;
5449    }
5450    let launch = match params.harness.as_str() {
5451        HarnessId::GROK => RuntimeLaunch {
5452            program: "grok".into(),
5453            arguments: {
5454                let mut arguments: Vec<String> = Vec::new();
5455                if crate::support::self_sandbox_supported() {
5456                    arguments.extend(["--sandbox".into(), "workspace".into()]);
5457                }
5458                arguments.extend([
5459                    "--always-approve".into(),
5460                    "agent".into(),
5461                    "--no-leader".into(),
5462                    "stdio".into(),
5463                ]);
5464                arguments
5465            },
5466            env: BTreeMap::from([("GROK_AGENT_DASHBOARD".into(), "0".into())]),
5467        },
5468        HarnessId::CODEX => RuntimeLaunch {
5469            program: "codex".into(),
5470            arguments: vec![
5471                "--dangerously-bypass-approvals-and-sandbox".into(),
5472                "--dangerously-bypass-hook-trust".into(),
5473                "app-server".into(),
5474            ],
5475            env: BTreeMap::new(),
5476        },
5477        HarnessId::CLAUDE_CODE => RuntimeLaunch {
5478            program: "claude".into(),
5479            arguments: vec![
5480                "--dangerously-skip-permissions".into(),
5481                "--print".into(),
5482                "--input-format".into(),
5483                "stream-json".into(),
5484                "--output-format".into(),
5485                "stream-json".into(),
5486                "--verbose".into(),
5487            ],
5488            env: BTreeMap::new(),
5489        },
5490        HarnessId::PI => RuntimeLaunch {
5491            program: "pi".into(),
5492            arguments: vec!["--approve".into(), "--mode".into(), "rpc".into()],
5493            env: BTreeMap::new(),
5494        },
5495        HarnessId::OPENCODE => RuntimeLaunch {
5496            program: "opencode".into(),
5497            arguments: vec!["serve".into()],
5498            env: BTreeMap::new(),
5499        },
5500        HarnessId::GEMINI => RuntimeLaunch {
5501            program: "gemini".into(),
5502            arguments: vec!["--acp".into(), "--yolo".into()],
5503            env: BTreeMap::new(),
5504        },
5505        HarnessId::GOOSE => RuntimeLaunch {
5506            program: "goose".into(),
5507            arguments: vec!["acp".into()],
5508            env: BTreeMap::new(),
5509        },
5510        HarnessId::SUPERCODE => RuntimeLaunch {
5511            program: "supercode".into(),
5512            arguments: vec!["acp".into(), "--dangerous".into()],
5513            env: BTreeMap::new(),
5514        },
5515        _ => return None,
5516    };
5517    Some(launch)
5518}
5519
5520/// Disposable harness state for a no-prompt readiness probe. Merely opening
5521/// several stock CLIs writes a session header or migrates configuration, so a
5522/// handshake must never point at the user's real home. Authentication files
5523/// are copied into the private temporary home; all writes disappear with the
5524/// guard after the connection closes.
5525struct IsolatedProbeHome {
5526    launch: RuntimeLaunch,
5527    root: PathBuf,
5528}
5529
5530impl IsolatedProbeHome {
5531    fn new(harness: &str, mut launch: RuntimeLaunch) -> std::io::Result<Self> {
5532        let root = std::env::temp_dir().join(format!(
5533            "supercode-harness-probe-{harness}-{}",
5534            generated_session_id()
5535        ));
5536        std::fs::create_dir_all(&root)?;
5537        set_private_dir_permissions(&root)?;
5538
5539        if let Some(source_home) = std::env::var_os("HOME").map(PathBuf::from) {
5540            for relative in probe_auth_files(harness) {
5541                copy_probe_file(&source_home, &root, relative)?;
5542            }
5543        }
5544        configure_isolated_probe_auth(harness, &root)?;
5545
5546        let root_text = root.to_string_lossy().into_owned();
5547        for (key, value) in [
5548            ("HOME", root_text.clone()),
5549            (
5550                "XDG_CACHE_HOME",
5551                root.join(".cache").to_string_lossy().into_owned(),
5552            ),
5553            (
5554                "XDG_CONFIG_HOME",
5555                root.join(".config").to_string_lossy().into_owned(),
5556            ),
5557            (
5558                "XDG_DATA_HOME",
5559                root.join(".local/share").to_string_lossy().into_owned(),
5560            ),
5561        ] {
5562            launch.env.insert(key.into(), value);
5563        }
5564        let scoped = match harness {
5565            HarnessId::CLAUDE_CODE => Some(("CLAUDE_CONFIG_DIR", root.join(".claude"))),
5566            HarnessId::CODEX => Some(("CODEX_HOME", root.join(".codex"))),
5567            HarnessId::GEMINI => Some(("GEMINI_CLI_HOME", root.clone())),
5568            HarnessId::GROK => Some(("GROK_HOME", root.join(".grok"))),
5569            HarnessId::PI => Some(("PI_CODING_AGENT_DIR", root.join(".pi/agent"))),
5570            HarnessId::SUPERCODE => Some(("SUPERCODE_HOME", root.join(".config/supercode"))),
5571            _ => None,
5572        };
5573        if let Some((key, value)) = scoped {
5574            launch
5575                .env
5576                .insert(key.into(), value.to_string_lossy().into_owned());
5577        }
5578        Ok(Self { launch, root })
5579    }
5580
5581    fn cleanup(&self) -> std::io::Result<()> {
5582        match std::fs::remove_dir_all(&self.root) {
5583            Ok(()) => Ok(()),
5584            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
5585            Err(error) => Err(error),
5586        }
5587    }
5588}
5589
5590impl Drop for IsolatedProbeHome {
5591    fn drop(&mut self) {
5592        let _ = self.cleanup();
5593    }
5594}
5595
5596fn probe_auth_files(harness: &str) -> &'static [&'static str] {
5597    match harness {
5598        HarnessId::CLAUDE_CODE => &[".claude/.credentials.json", ".claude.json"],
5599        // The gateway endpoint + token live in openclaw's own config; without
5600        // it the isolated probe dials the default endpoint unauthenticated
5601        // (PARITY-24 finding 2026-08-31).
5602        HarnessId::OPENCLAW => &[".openclaw/openclaw.json"],
5603        HarnessId::CODEX => &[".codex/auth.json"],
5604        HarnessId::GEMINI => &[
5605            ".gemini/google_accounts.json",
5606            ".gemini/oauth_creds.json",
5607            ".gemini/settings.json",
5608        ],
5609        HarnessId::GROK => &[".grok/auth.json", ".grok/config.toml"],
5610        HarnessId::OPENCODE => &[
5611            ".config/opencode/auth.json",
5612            ".local/share/opencode/auth.json",
5613        ],
5614        HarnessId::PI => &[".pi/agent/auth.json"],
5615        // Hermes keeps its provider selection in config.yaml, its OAuth
5616        // credential pool in auth.json, and API keys in .env; without them
5617        // the isolated probe sees "No LLM provider configured" for a
5618        // hermes that answers fine from the user's real home.
5619        HarnessId::HERMES => &[".hermes/config.yaml", ".hermes/auth.json", ".hermes/.env"],
5620        HarnessId::SUPERCODE => &[
5621            ".config/supercode/config.toml",
5622            ".config/supercode/credentials.toml",
5623        ],
5624        _ => &[],
5625    }
5626}
5627
5628fn copy_probe_file(source_home: &Path, probe_home: &Path, relative: &str) -> std::io::Result<()> {
5629    let source = source_home.join(relative);
5630    if !source.is_file() {
5631        return Ok(());
5632    }
5633    let destination = probe_home.join(relative);
5634    if let Some(parent) = destination.parent() {
5635        std::fs::create_dir_all(parent)?;
5636        set_private_dir_permissions(parent)?;
5637    }
5638    std::fs::copy(source, &destination)?;
5639    set_private_file_permissions(&destination)
5640}
5641
5642fn configure_isolated_probe_auth(harness: &str, probe_home: &Path) -> std::io::Result<()> {
5643    if harness != HarnessId::GEMINI {
5644        return Ok(());
5645    }
5646    let oauth = probe_home.join(".gemini/oauth_creds.json");
5647    if !oauth.is_file() {
5648        return Ok(());
5649    }
5650    let settings_path = probe_home.join(".gemini/settings.json");
5651    let mut settings = std::fs::read_to_string(&settings_path)
5652        .ok()
5653        .and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
5654        .unwrap_or_else(|| json!({}));
5655    settings["security"]["auth"]["selectedType"] = Value::String("oauth-personal".into());
5656    std::fs::write(
5657        &settings_path,
5658        serde_json::to_vec_pretty(&settings).map_err(std::io::Error::other)?,
5659    )?;
5660    set_private_file_permissions(&settings_path)
5661}
5662
5663#[cfg(unix)]
5664fn set_private_dir_permissions(path: &Path) -> std::io::Result<()> {
5665    use std::os::unix::fs::PermissionsExt;
5666    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
5667}
5668
5669#[cfg(not(unix))]
5670fn set_private_dir_permissions(_path: &Path) -> std::io::Result<()> {
5671    Ok(())
5672}
5673
5674#[cfg(unix)]
5675fn set_private_file_permissions(path: &Path) -> std::io::Result<()> {
5676    use std::os::unix::fs::PermissionsExt;
5677    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
5678}
5679
5680#[cfg(not(unix))]
5681fn set_private_file_permissions(_path: &Path) -> std::io::Result<()> {
5682    Ok(())
5683}
5684
5685fn find_executable(program: &str) -> Option<PathBuf> {
5686    let candidate = PathBuf::from(program);
5687    if candidate.components().count() > 1 {
5688        return candidate.is_file().then_some(candidate);
5689    }
5690    let path = std::env::var_os("PATH")?;
5691    for directory in std::env::split_paths(&path) {
5692        let candidate = directory.join(program);
5693        if candidate.is_file() {
5694            return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
5695        }
5696        #[cfg(windows)]
5697        {
5698            for extension in ["exe", "cmd", "bat"] {
5699                let candidate = directory.join(format!("{program}.{extension}"));
5700                if candidate.is_file() {
5701                    return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
5702                }
5703            }
5704        }
5705    }
5706    None
5707}
5708
5709async fn executable_version(executable: &Path) -> Option<String> {
5710    let mut command = tokio::process::Command::new(executable);
5711    command
5712        .arg("--version")
5713        .stdin(std::process::Stdio::null())
5714        .stdout(std::process::Stdio::piped())
5715        .stderr(std::process::Stdio::piped())
5716        .kill_on_drop(true);
5717    let output = tokio::time::timeout(Duration::from_secs(3), command.output())
5718        .await
5719        .ok()?
5720        .ok()?;
5721    let stdout = String::from_utf8_lossy(&output.stdout);
5722    let stderr = String::from_utf8_lossy(&output.stderr);
5723    stdout
5724        .lines()
5725        .chain(stderr.lines())
5726        .map(str::trim)
5727        .find(|line| !line.is_empty())
5728        .map(|line| truncate_text(line, 200))
5729}
5730
5731pub(crate) fn auth_evidence(harness: &str) -> bool {
5732    let env_names: &[&str] = match harness {
5733        HarnessId::CLAUDE_CODE => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
5734        HarnessId::CODEX => &["OPENAI_API_KEY"],
5735        HarnessId::OPENCODE => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
5736        HarnessId::PI => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
5737        HarnessId::GROK => &["XAI_API_KEY", "GROK_API_KEY"],
5738        HarnessId::GEMINI => &["GEMINI_API_KEY", "GOOGLE_API_KEY"],
5739        HarnessId::SUPERCODE => &["OPENROUTER_API_KEY"],
5740        _ => &[],
5741    };
5742    if env_names
5743        .iter()
5744        .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
5745    {
5746        return true;
5747    }
5748    let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else {
5749        return false;
5750    };
5751    let files: Vec<PathBuf> = match harness {
5752        HarnessId::CLAUDE_CODE => vec![home.join(".claude/.credentials.json")],
5753        HarnessId::CODEX => vec![home.join(".codex/auth.json")],
5754        HarnessId::OPENCODE => vec![
5755            home.join(".local/share/opencode/auth.json"),
5756            home.join(".config/opencode/auth.json"),
5757        ],
5758        HarnessId::PI => vec![home.join(".pi/agent/auth.json")],
5759        HarnessId::GROK => vec![home.join(".grok/auth.json")],
5760        HarnessId::GEMINI => vec![
5761            home.join(".gemini/oauth_creds.json"),
5762            home.join(".gemini/google_accounts.json"),
5763        ],
5764        HarnessId::SUPERCODE => vec![home.join(".config/supercode/credentials.toml")],
5765        HarnessId::HERMES => vec![home.join(".hermes/auth.json"), home.join(".hermes/.env")],
5766        _ => Vec::new(),
5767    };
5768    if files.into_iter().any(|path| {
5769        std::fs::metadata(path)
5770            .map(|metadata| metadata.is_file() && metadata.len() > 2)
5771            .unwrap_or(false)
5772    }) {
5773        return true;
5774    }
5775    // macOS keeps Claude Code's OAuth login in the Keychain, so
5776    // `.claude/.credentials.json` never exists there and the file probe above
5777    // reports a signed-in install as unauthenticated forever. A completed
5778    // login also writes an `oauthAccount` record into `~/.claude.json` on
5779    // every platform — file-based, prompt-free evidence (querying the
5780    // Keychain itself from an unsigned daemon can raise a UI prompt).
5781    if harness == HarnessId::CLAUDE_CODE {
5782        return std::fs::read_to_string(home.join(".claude.json"))
5783            .map(|text| text.contains("\"oauthAccount\""))
5784            .unwrap_or(false);
5785    }
5786    false
5787}
5788
5789fn looks_like_auth_error(message: &str) -> bool {
5790    let message = message.to_ascii_lowercase();
5791    [
5792        "auth",
5793        "login",
5794        "sign in",
5795        "sign-in",
5796        "credential",
5797        "unauthorized",
5798        "forbidden",
5799        "token",
5800    ]
5801    .iter()
5802    .any(|needle| message.contains(needle))
5803}
5804
5805fn unavailable_capabilities() -> crate::RuntimeCapabilities {
5806    crate::RuntimeCapabilities {
5807        start_session: false,
5808        resume_session: false,
5809        attach_existing_process: false,
5810        send_input: false,
5811        stream_events: false,
5812        interrupt: false,
5813        steer: false,
5814        respond_to_requests: false,
5815    }
5816}
5817
5818fn truncate_text(text: &str, max_chars: usize) -> String {
5819    let mut chars = text.chars();
5820    let truncated = chars.by_ref().take(max_chars).collect::<String>();
5821    if chars.next().is_some() {
5822        format!("{truncated}…")
5823    } else {
5824        truncated
5825    }
5826}
5827
5828/// The process group a runtime's own handle names, when it names one.
5829///
5830/// Every adapter that spawns a local process spawns it as its own group
5831/// leader (`Command::process_group(0)`), so the endpoint's pid IS the group
5832/// id. A runtime reached over HTTP, or one supercode joined rather than
5833/// spawned, names no group here and is left alone.
5834fn runtime_process_group(handle: &crate::RuntimeHandle) -> Option<u32> {
5835    match &handle.endpoint {
5836        crate::RuntimeEndpoint::LocalProcess { pid, .. } => *pid,
5837        crate::RuntimeEndpoint::Http { .. } => None,
5838    }
5839}
5840
5841/// SIGKILL a wedged runtime's whole process group, reporting whether there
5842/// was one to signal. This is the same group teardown a graceful `close`
5843/// performs; it runs here only when the graceful path blew its deadline,
5844/// because the task parked on the unanswered call still owns the process
5845/// handle and so no `Drop` of ours can reach it.
5846fn kill_runtime_process_group(process_group: Option<u32>) -> bool {
5847    match process_group {
5848        #[cfg(unix)]
5849        Some(pid) => {
5850            crate::lsp::kill_process_group(pid);
5851            true
5852        }
5853        #[cfg(not(unix))]
5854        Some(_) => false,
5855        None => false,
5856    }
5857}
5858
5859fn error_message(error: ServiceError) -> String {
5860    match error {
5861        ServiceError::InvalidParams(message)
5862        | ServiceError::Operation(message)
5863        | ServiceError::UnsupportedAction(message) => message,
5864        ServiceError::MethodNotFound => "runtime adapter is not available".into(),
5865        ServiceError::Sdk(error) => error.to_string(),
5866    }
5867}
5868
5869#[derive(Debug)]
5870enum ServiceError {
5871    InvalidParams(String),
5872    MethodNotFound,
5873    UnsupportedAction(String),
5874    Operation(String),
5875    Sdk(SdkError),
5876}
5877
5878fn sdk_error(operation: SdkOperation, error: ServiceError) -> SdkError {
5879    match error {
5880        ServiceError::InvalidParams(message) => {
5881            SdkError::new(SdkErrorCode::InvalidArgument, operation, message)
5882        }
5883        ServiceError::MethodNotFound | ServiceError::UnsupportedAction(_) => {
5884            SdkError::unsupported(operation)
5885        }
5886        ServiceError::Operation(message) => {
5887            let code = if message.contains("already in progress") {
5888                SdkErrorCode::Busy
5889            } else if message.contains("not supported by this runtime") {
5890                SdkErrorCode::UnsupportedAction
5891            } else if message.contains("unknown runtime connection") {
5892                SdkErrorCode::NotFound
5893            } else {
5894                SdkErrorCode::Execution
5895            };
5896            SdkError::new(code, operation, message)
5897        }
5898        ServiceError::Sdk(error) => error,
5899    }
5900}
5901
5902fn sdk_rpc_error(id: Value, error: &SdkError) -> Value {
5903    let error_code = error.code();
5904    let code = match error_code {
5905        SdkErrorCode::Unauthenticated => -32030,
5906        SdkErrorCode::Unauthorized => -32031,
5907        SdkErrorCode::ControllerRequired => -32032,
5908        SdkErrorCode::LeaseExpired => -32033,
5909        SdkErrorCode::InvalidArgument => -32602,
5910        SdkErrorCode::NotFound => -32004,
5911        SdkErrorCode::Busy => -32000,
5912        SdkErrorCode::UnsupportedAction => -32020,
5913        SdkErrorCode::Execution => -32002,
5914        SdkErrorCode::Transport => -32003,
5915    };
5916    json!({
5917        "jsonrpc": "2.0",
5918        "id": id,
5919        "error": {
5920            "code": code,
5921            "name": error_code,
5922            "operation": error.operation(),
5923            "message": error.to_string(),
5924        },
5925    })
5926}
5927
5928fn decode<T: for<'de> Deserialize<'de>>(value: Value) -> std::result::Result<T, ServiceError> {
5929    serde_json::from_value(value).map_err(|error| ServiceError::InvalidParams(error.to_string()))
5930}
5931
5932fn operation(error: impl Into<crate::Error>) -> ServiceError {
5933    let error = error.into();
5934    match error {
5935        crate::Error::Sdk(error) => ServiceError::Sdk(error),
5936        error => ServiceError::Operation(error.to_string()),
5937    }
5938}
5939
5940/// ORCH-12 `harness.v1.memory.show|search` params. `homes` is the same
5941/// storage-root override every read-only method accepts, so a caller can
5942/// point the read at a fixture home without touching the real ones.
5943#[derive(Debug, Clone, Deserialize, Default)]
5944#[serde(default)]
5945struct MemoryRequest {
5946    /// Harness whose store is read. Required.
5947    harness: Option<String>,
5948    /// The needle, required by `search`.
5949    query: Option<String>,
5950    /// Hermes profile, OpenClaw agent, or Claude Code project.
5951    profile: Option<String>,
5952    /// Claude Code session id selecting a project store (`show` only).
5953    session: Option<String>,
5954    /// Include each document's whole text (`show` only).
5955    full: bool,
5956    /// Treat `query` as a regular expression (`search` only).
5957    regex: bool,
5958    /// Working tree whose project store is read.
5959    cwd: Option<std::path::PathBuf>,
5960    /// Storage roots to read.
5961    homes: crate::HarnessHomes,
5962}
5963
5964/// Read the memory noun. A harness with no memory store fails with
5965/// `UnsupportedAction` (RPC `-32020`), never an empty list.
5966fn memory_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
5967    let request = decode::<MemoryRequest>(params)?;
5968    let harness = request
5969        .harness
5970        .clone()
5971        .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
5972    let to_service = |error: crate::memory::MemoryError| match error {
5973        crate::memory::MemoryError::UnsupportedHarness { .. }
5974        | crate::memory::MemoryError::SessionNotScoped { .. } => {
5975            ServiceError::UnsupportedAction(error.to_string())
5976        }
5977        other => ServiceError::InvalidParams(other.to_string()),
5978    };
5979    match method {
5980        "harness.v1.memory.show" => {
5981            let documents = crate::memory::show_memory(&crate::memory::MemoryQuery {
5982                harness,
5983                profile: request.profile,
5984                session: request.session,
5985                full: request.full,
5986                cwd: request.cwd,
5987                homes: request.homes,
5988            })
5989            .map_err(to_service)?;
5990            Ok(json!({
5991                "schema": crate::memory::MEMORY_SCHEMA,
5992                "documents": documents,
5993            }))
5994        }
5995        "harness.v1.memory.search" => {
5996            let query = request
5997                .query
5998                .ok_or_else(|| ServiceError::InvalidParams("`query` is required".into()))?;
5999            let matches = crate::memory::search_memory(&crate::memory::MemorySearchQuery {
6000                harness,
6001                query,
6002                profile: request.profile,
6003                regex: request.regex,
6004                cwd: request.cwd,
6005                homes: request.homes,
6006            })
6007            .map_err(to_service)?;
6008            Ok(json!({
6009                "schema": crate::memory::MEMORY_SCHEMA,
6010                "matches": matches,
6011            }))
6012        }
6013        _ => Err(ServiceError::MethodNotFound),
6014    }
6015}
6016
6017/// ORCH-10 `harness.v1.profiles.list|get` params. `homes` is the same
6018/// storage-root override every read-only method accepts, so a caller can
6019/// point the read at a fixture home without touching the real ones.
6020#[derive(Debug, Clone, Deserialize)]
6021#[serde(default)]
6022struct ProfilesQuery {
6023    /// Restrict the listing to one harness. `get` requires it.
6024    harness: Option<String>,
6025    /// Profile name, required by `get`.
6026    name: Option<String>,
6027    /// Storage roots to read.
6028    homes: crate::HarnessHomes,
6029}
6030
6031impl Default for ProfilesQuery {
6032    fn default() -> Self {
6033        Self {
6034            harness: None,
6035            name: None,
6036            homes: crate::HarnessHomes::default(),
6037        }
6038    }
6039}
6040
6041/// Read the profile noun. A harness with no profile concept fails with
6042/// `UnsupportedAction` (RPC `-32020`), never an empty list.
6043fn profiles_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6044    let query = decode::<ProfilesQuery>(params)?;
6045    let to_service = |error: crate::profiles::ProfileError| match error {
6046        crate::profiles::ProfileError::UnsupportedHarness { .. } => {
6047            ServiceError::UnsupportedAction(error.to_string())
6048        }
6049        crate::profiles::ProfileError::NotFound { .. } => {
6050            ServiceError::InvalidParams(error.to_string())
6051        }
6052    };
6053    match method {
6054        "harness.v1.profiles.list" => {
6055            let profiles = crate::profiles::list_profiles(&query.homes, query.harness.as_deref())
6056                .map_err(to_service)?;
6057            Ok(json!({
6058                "schema": crate::profiles::PROFILES_SCHEMA,
6059                "profiles": profiles,
6060            }))
6061        }
6062        "harness.v1.profiles.get" => {
6063            let harness = query
6064                .harness
6065                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6066            let name = query
6067                .name
6068                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6069            let profile =
6070                crate::profiles::get_profile(&query.homes, &harness, &name).map_err(to_service)?;
6071            Ok(json!({
6072                "schema": crate::profiles::PROFILES_SCHEMA,
6073                "profile": profile,
6074            }))
6075        }
6076        _ => Err(ServiceError::MethodNotFound),
6077    }
6078}
6079
6080/// ORCH-14 `harness.v1.channels.list|status` params, the same storage-root
6081/// override every read-only method accepts so a caller can point the read at
6082/// a fixture home without touching the real ones.
6083#[derive(Debug, Clone, Deserialize)]
6084#[serde(default)]
6085struct ChannelsQuery {
6086    /// Restrict the listing to one harness. `status` requires it.
6087    harness: Option<String>,
6088    /// Channel name, required by `status`.
6089    name: Option<String>,
6090    /// Storage roots to read.
6091    homes: crate::HarnessHomes,
6092}
6093
6094impl Default for ChannelsQuery {
6095    fn default() -> Self {
6096        Self {
6097            harness: None,
6098            name: None,
6099            homes: crate::HarnessHomes::default(),
6100        }
6101    }
6102}
6103
6104/// Read the channel noun. A harness with no channel concept fails with
6105/// `UnsupportedAction` (RPC `-32020`), never an empty list. No row carries a
6106/// token, key or secret — see `crate::channels` "Secrecy".
6107#[derive(Debug, Clone, Deserialize)]
6108#[serde(default)]
6109struct RoutesQuery {
6110    harness: Option<String>,
6111    /// Restrict to routes targeting one profile / agent.
6112    profile: Option<String>,
6113    homes: crate::HarnessHomes,
6114}
6115
6116impl Default for RoutesQuery {
6117    fn default() -> Self {
6118        Self {
6119            harness: None,
6120            profile: None,
6121            homes: crate::HarnessHomes::default(),
6122        }
6123    }
6124}
6125
6126#[derive(Debug, Clone, Deserialize)]
6127#[serde(default)]
6128struct TriggersQuery {
6129    harness: Option<String>,
6130    homes: crate::HarnessHomes,
6131}
6132
6133impl Default for TriggersQuery {
6134    fn default() -> Self {
6135        Self {
6136            harness: None,
6137            homes: crate::HarnessHomes::default(),
6138        }
6139    }
6140}
6141
6142fn triggers_call(params: Value) -> std::result::Result<Value, ServiceError> {
6143    let query = decode::<TriggersQuery>(params)?;
6144    let triggers = crate::triggers::list_triggers(&query.homes, query.harness.as_deref())
6145        .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6146    Ok(json!({
6147        "schema": crate::triggers::TRIGGERS_SCHEMA,
6148        "triggers": triggers,
6149    }))
6150}
6151
6152fn routes_call(params: Value) -> std::result::Result<Value, ServiceError> {
6153    let query = decode::<RoutesQuery>(params)?;
6154    let routes = crate::routes::list_routes(
6155        &query.homes,
6156        query.harness.as_deref(),
6157        query.profile.as_deref(),
6158    )
6159    .map_err(|error| ServiceError::UnsupportedAction(error.to_string()))?;
6160    Ok(json!({
6161        "schema": crate::routes::ROUTES_SCHEMA,
6162        "routes": routes,
6163    }))
6164}
6165
6166fn channels_call(method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
6167    let query = decode::<ChannelsQuery>(params)?;
6168    let to_service = |error: crate::channels::ChannelError| match error {
6169        crate::channels::ChannelError::UnsupportedHarness { .. } => {
6170            ServiceError::UnsupportedAction(error.to_string())
6171        }
6172        crate::channels::ChannelError::NotFound { .. } => {
6173            ServiceError::InvalidParams(error.to_string())
6174        }
6175    };
6176    match method {
6177        "harness.v1.channels.list" => {
6178            let channels = crate::channels::list_channels(&query.homes, query.harness.as_deref())
6179                .map_err(to_service)?;
6180            Ok(json!({
6181                "schema": crate::channels::CHANNELS_SCHEMA,
6182                "channels": channels,
6183            }))
6184        }
6185        "harness.v1.channels.status" => {
6186            let harness = query
6187                .harness
6188                .ok_or_else(|| ServiceError::InvalidParams("`harness` is required".into()))?;
6189            let name = query
6190                .name
6191                .ok_or_else(|| ServiceError::InvalidParams("`name` is required".into()))?;
6192            let channel = crate::channels::channel_status(&query.homes, &harness, &name)
6193                .map_err(to_service)?;
6194            Ok(json!({
6195                "schema": crate::channels::CHANNELS_SCHEMA,
6196                "channel": channel,
6197            }))
6198        }
6199        _ => Err(ServiceError::MethodNotFound),
6200    }
6201}
6202
6203fn rpc_error(id: Value, code: i64, message: &str) -> Value {
6204    json!({
6205        "jsonrpc": "2.0",
6206        "id": id,
6207        "error": {"code": code, "message": message},
6208    })
6209}
6210
6211#[cfg(test)]
6212mod tests {
6213    use super::*;
6214    use crate::{HarnessEvent, HarnessId, RuntimeEndpoint, RuntimeHandle, StorageLocator};
6215    use async_trait::async_trait;
6216    use std::io::Write;
6217    use std::path::PathBuf;
6218    use std::time::Instant;
6219
6220    #[test]
6221    fn indexed_claude_descriptor_keeps_the_live_peer_address() {
6222        let descriptor = SessionDescriptor {
6223            locator: SessionLocator {
6224                harness: HarnessId::new(HarnessId::CLAUDE_CODE),
6225                session_id: "live-session".into(),
6226                storage: StorageLocator::File {
6227                    path: PathBuf::from("/tmp/live-session.jsonl"),
6228                },
6229            },
6230            cwd: Some(PathBuf::from("/project")),
6231            title: None,
6232            preview_candidates: Vec::new(),
6233            latest_message_candidates: Vec::new(),
6234            updated_at_ms: Some(1),
6235            message_count: None,
6236            model: None,
6237            parent_session_id: None,
6238            child_session_count: 0,
6239            nouns: Default::default(),
6240        };
6241        let peer = crate::claude_peer::ClaudePeerSession {
6242            pid: 42,
6243            session_id: "live-session".into(),
6244            cwd: Some(PathBuf::from("/project")),
6245            name: "peer".into(),
6246            socket_path: PathBuf::from("/tmp/peer.sock"),
6247            status: Some(crate::claude_peer::ClaudePeerStatus::Busy),
6248            updated_at_ms: Some(1),
6249            version: Some("test".into()),
6250        };
6251
6252        let value = live_descriptor_value(&descriptor, &[peer]).unwrap();
6253        assert!(value["live_endpoint"]
6254            .as_str()
6255            .is_some_and(|endpoint| endpoint.starts_with("cc-peer:v1:42:peer:")));
6256    }
6257
6258    struct EndingRuntime {
6259        handle: RuntimeHandle,
6260        event: Option<HarnessEvent>,
6261        close_failures: usize,
6262    }
6263
6264    #[async_trait]
6265    impl RuntimeConnection for EndingRuntime {
6266        fn handle(&self) -> &RuntimeHandle {
6267            &self.handle
6268        }
6269
6270        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
6271            unreachable!("ending runtime does not accept input")
6272        }
6273
6274        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
6275            Ok(self.event.take())
6276        }
6277
6278        async fn interrupt(&mut self) -> crate::Result<()> {
6279            Ok(())
6280        }
6281
6282        async fn respond(&mut self, _request_id: Value, _response: Value) -> crate::Result<()> {
6283            Ok(())
6284        }
6285
6286        async fn close(&mut self) -> crate::Result<()> {
6287            if self.close_failures > 0 {
6288                self.close_failures -= 1;
6289                return Err(crate::Error::Other(
6290                    "cleanup temporarily unavailable".into(),
6291                ));
6292            }
6293            Ok(())
6294        }
6295    }
6296
6297    fn ending_runtime(event: Option<HarnessEvent>) -> Box<dyn RuntimeConnection> {
6298        Box::new(EndingRuntime {
6299            handle: RuntimeHandle {
6300                harness: HarnessId::from(HarnessId::CLAUDE_CODE),
6301                runtime_id: "ending-session".into(),
6302                endpoint: RuntimeEndpoint::LocalProcess {
6303                    pid: None,
6304                    command: vec!["ending-runtime".into()],
6305                    protocol: "test".into(),
6306                },
6307            },
6308            event,
6309            close_failures: 0,
6310        })
6311    }
6312
6313    #[tokio::test]
6314    async fn closing_a_runtime_surrenders_the_connection_even_when_teardown_fails() {
6315        let mut service = HarnessSessionService::new();
6316        let handle = ending_runtime(None).handle().clone();
6317        let runtime_id = handle.runtime_id.clone();
6318        let opened = service
6319            .insert_runtime(Box::new(EndingRuntime {
6320                handle,
6321                event: None,
6322                close_failures: 1,
6323            }))
6324            .unwrap();
6325        let connection = opened["connection"].as_str().unwrap().to_string();
6326        service.terminal_launches.insert(
6327            connection.clone(),
6328            StructuredLaunch {
6329                cwd: PathBuf::from("/fixture"),
6330                program: "fixture".into(),
6331                arguments: Vec::new(),
6332                env: BTreeMap::new(),
6333            },
6334        );
6335        let first = service
6336            .handle_async(request(
6337                1,
6338                "harness.v1.runtimes.close",
6339                json!({"connection": connection}),
6340            ))
6341            .await;
6342        // The harness's own teardown failed and the caller is told so...
6343        assert!(first.get("error").is_some(), "{first}");
6344        // ...but the connection is gone all the same. A connection whose close
6345        // cannot complete is exactly the one that must not stay registered:
6346        // holding it would answer every later call on this node with a turn
6347        // that is never going to end.
6348        assert!(!service.runtimes.contains_key(&connection));
6349        assert!(!service.terminal_launches.contains_key(&connection));
6350        assert!(!service.runtime_sequences.contains_key(&runtime_id));
6351        let again = service
6352            .handle_async(request(
6353                2,
6354                "harness.v1.runtimes.close",
6355                json!({"connection": connection}),
6356            ))
6357            .await;
6358        assert_eq!(again["error"]["code"], -32602, "{again}");
6359    }
6360
6361    fn request(id: u64, method: &str, params: Value) -> Value {
6362        json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
6363    }
6364
6365    // ---- ORCH-6: conversation nouns on `sessions.*` ----------------------
6366
6367    fn hermes_store() -> PathBuf {
6368        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hermes_home/state.db")
6369    }
6370
6371    /// The discovery response for the Hermes fixture home, with the one
6372    /// machine-specific value (the absolute store path) replaced so the exact
6373    /// same JSON can be committed and replayed by the UI story.
6374    fn hermes_discovery(params: Value) -> Value {
6375        let mut response =
6376            HarnessSessionService::new().handle(request(1, "harness.v1.sessions.discover", params));
6377        let store = hermes_store().display().to_string();
6378        for session in response["result"]["sessions"]
6379            .as_array_mut()
6380            .expect("sessions array")
6381        {
6382            if session["locator"]["storage"]["path"] == json!(store) {
6383                session["locator"]["storage"]["path"] = json!("<fixtures>/hermes_home/state.db");
6384            }
6385            // `activity` reports a wall-clock observation instant, not a fact
6386            // about the session; it would make this response differ on every
6387            // call. The nouns under test are all session facts.
6388            session.as_object_mut().unwrap().remove("activity");
6389        }
6390        response["result"].take()
6391    }
6392
6393    fn hermes_query() -> Value {
6394        json!({
6395            "harnesses": ["hermes"],
6396            "homes": {"hermes": hermes_store()},
6397        })
6398    }
6399
6400    fn row<'a>(result: &'a Value, id: &str) -> &'a Value {
6401        result["sessions"]
6402            .as_array()
6403            .expect("sessions array")
6404            .iter()
6405            .find(|session| session["locator"]["session_id"] == json!(id))
6406            .unwrap_or_else(|| panic!("no discovered row for `{id}` in {result:#}"))
6407    }
6408
6409    #[test]
6410    fn orch6_discover_rows_carry_the_conversation_nouns() {
6411        let result = hermes_discovery(hermes_query());
6412
6413        // A Telegram DM: reached on a channel, no repo — the workspace IS the
6414        // channel (D2 precedence), and `main` is not a profile.
6415        let dm = row(&result, "tg-dm-1");
6416        assert_eq!(dm["trigger"], json!("channel"));
6417        assert_eq!(dm["surface"]["platform"], json!("telegram"));
6418        assert_eq!(dm["surface"]["kind"], json!("dm"));
6419        assert_eq!(dm["surface"]["chat_id"], json!("123456"));
6420        assert_eq!(dm["surface"]["participant_id"], json!("u1"));
6421        assert_eq!(
6422            dm["workspace"],
6423            json!({"kind": "channel", "value": "telegram:123456"})
6424        );
6425        assert!(dm.get("profile").is_none(), "{dm:#}");
6426
6427        // A cron fire: recurring, with the job recovered from the minted id.
6428        let fire = row(&result, "cron_job42_20260902_120000");
6429        assert_eq!(fire["trigger"], json!("cron"));
6430        assert_eq!(
6431            fire["recurrence"],
6432            json!({"job_id": "job42", "kind": "cron"})
6433        );
6434        assert_eq!(fire["workspace"]["kind"], json!("repo"));
6435
6436        // A profiled group session with a pending handoff: repo workspace
6437        // wins over the channel, and the chat stays on the surface key.
6438        let coder = row(&result, "tg-coder-1");
6439        assert_eq!(coder["trigger"], json!("channel"));
6440        assert_eq!(coder["profile"], json!("coder"));
6441        assert_eq!(coder["surface"]["thread_id"], json!("55"));
6442        assert_eq!(
6443            coder["surface"]["key"],
6444            json!("agent:coder:telegram:group:-100777:55")
6445        );
6446        assert_eq!(
6447            coder["workspace"],
6448            json!({"kind": "repo", "value": "/workspace/project"})
6449        );
6450        assert_eq!(
6451            coder["cross_surface"],
6452            json!({"state": "pending", "platform": "discord"})
6453        );
6454
6455        // A plain ACP session stays human-triggered with no surface at all.
6456        let acp = row(&result, "cef97234-e8e8-428a-99ab-e8fff4e7e613");
6457        assert_eq!(acp["trigger"], json!("human"));
6458        assert!(acp.get("surface").is_none(), "{acp:#}");
6459        assert_eq!(acp["workspace"], json!({"kind": "none"}));
6460    }
6461
6462    #[test]
6463    fn orch6_discover_filters_by_harness_and_profile() {
6464        let mut params = hermes_query();
6465        params["profile"] = json!("coder");
6466        let result = hermes_discovery(params);
6467        let ids: Vec<&str> = result["sessions"]
6468            .as_array()
6469            .expect("sessions array")
6470            .iter()
6471            .map(|session| session["locator"]["session_id"].as_str().unwrap())
6472            .collect();
6473        assert_eq!(ids, vec!["tg-coder-1"]);
6474
6475        // A profile no session is routed through returns nothing rather than
6476        // silently ignoring the filter.
6477        let mut missing = hermes_query();
6478        missing["profile"] = json!("nobody");
6479        assert_eq!(hermes_discovery(missing)["sessions"], json!([]));
6480
6481        // The harness filter is `harnesses`; an id no harness answers to is
6482        // an empty page, never every store on the box.
6483        let elsewhere = json!({"harnesses": ["codex"], "homes": {"codex": hermes_store()}});
6484        assert_eq!(hermes_discovery(elsewhere)["sessions"], json!([]));
6485    }
6486
6487    #[test]
6488    fn orch6_load_reports_the_same_nouns_as_discovery() {
6489        let mut service = HarnessSessionService::new();
6490        let loaded = service.handle(request(
6491            1,
6492            "harness.v1.sessions.load",
6493            json!({"locator": {
6494                "harness": "hermes",
6495                "session_id": "tg-coder-1",
6496                "storage": {"kind": "file", "path": hermes_store()},
6497            }}),
6498        ));
6499        let session = &loaded["result"]["session"];
6500        let discovered = hermes_discovery(hermes_query());
6501        let row = row(&discovered, "tg-coder-1");
6502        for noun in [
6503            "trigger",
6504            "surface",
6505            "profile",
6506            "recurrence",
6507            "cross_surface",
6508            "workspace",
6509        ] {
6510            assert_eq!(
6511                session[noun],
6512                row.get(noun).cloned().unwrap_or(Value::Null),
6513                "`{noun}` disagrees between sessions.load and sessions.discover"
6514            );
6515        }
6516    }
6517
6518    /// ORCH-10: the fixture homes, as the RPC's `homes` override. Hermes's
6519    /// home is named by its `state.db`; OpenClaw's is the state directory.
6520    fn profile_fixture_homes() -> Value {
6521        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6522        json!({
6523            "hermes": fixtures.join("hermes_home/state.db"),
6524            "openclaw": fixtures.join("openclaw_home"),
6525        })
6526    }
6527
6528    fn profile_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
6529        response["result"]["profiles"]
6530            .as_array()
6531            .unwrap_or_else(|| panic!("no profiles array in {response}"))
6532            .iter()
6533            .find(|row| row["harness"] == harness && row["name"] == name)
6534            .unwrap_or_else(|| panic!("no `{harness}` profile `{name}` in {response}"))
6535    }
6536
6537    /// dev/01: every source answers in one row shape, over the committed
6538    /// fixture homes — the Hermes profile directory and its `state.db`
6539    /// partition, the OpenClaw agent directories and `openclaw.json`, and
6540    /// supercode's own presets.
6541    #[test]
6542    fn profiles_list_reads_every_source_uniformly() {
6543        let mut service = HarnessSessionService::new();
6544        let response = service.handle(request(
6545            1,
6546            "harness.v1.profiles.list",
6547            json!({"homes": profile_fixture_homes()}),
6548        ));
6549        assert_eq!(
6550            response["result"]["schema"],
6551            crate::profiles::PROFILES_SCHEMA
6552        );
6553
6554        let default = profile_row(&response, "hermes", "default");
6555        assert_eq!(default["kind"], "hermes_profile");
6556        assert_eq!(default["default"], true);
6557        assert_eq!(default["routes"], 0);
6558        assert_eq!(default["sessions"], 11);
6559        assert_eq!(default["model"], "anthropic/claude-sonnet-4-5");
6560
6561        let coder = profile_row(&response, "hermes", "coder");
6562        assert_eq!(coder["kind"], "hermes_profile");
6563        assert_eq!(coder["default"], false);
6564        assert_eq!(coder["routes"], 1, "gateway.profile_routes targets coder");
6565        assert_eq!(coder["sessions"], 1, "state.db profile_name = 'coder'");
6566        assert_eq!(coder["model"], "anthropic/claude-opus-4-8");
6567        assert!(coder["home"]
6568            .as_str()
6569            .unwrap()
6570            .ends_with("hermes_home/profiles/coder"));
6571
6572        let main = profile_row(&response, "openclaw", "main");
6573        assert_eq!(main["kind"], "openclaw_agent");
6574        // No entry declares `default: true` (real configs do not), so `main`
6575        // wins on OpenClaw's own convention rather than alphabetically.
6576        assert_eq!(main["default"], true);
6577        assert_eq!(main["routes"], 0);
6578        assert_eq!(main["sessions"], 4);
6579        assert_eq!(
6580            main["model"],
6581            Value::Null,
6582            "`agents.defaults.model` is an install default, not this agent's pin"
6583        );
6584
6585        let design = profile_row(&response, "openclaw", "design");
6586        assert_eq!(design["default"], false);
6587        assert_eq!(design["routes"], 1, "one binding names agentId `design`");
6588        assert_eq!(design["sessions"], 0);
6589        assert_eq!(design["model"], "anthropic/claude-opus-4-8");
6590
6591        let preset = profile_row(&response, "supercode", "supercode-default");
6592        assert_eq!(preset["kind"], "preset");
6593        assert_eq!(preset["default"], true);
6594        assert_eq!(preset["home"], Value::Null);
6595        assert_eq!(preset["routes"], Value::Null);
6596    }
6597
6598    /// Codex's own profiles are `[profiles.<name>]` tables, with the
6599    /// top-level `profile` key naming the default.
6600    #[test]
6601    fn profiles_list_reads_codex_profile_tables() {
6602        let codex_home = std::env::temp_dir().join(format!(
6603            "supercode-orch10-codex-{}-{}",
6604            std::process::id(),
6605            std::time::SystemTime::now()
6606                .duration_since(std::time::UNIX_EPOCH)
6607                .unwrap()
6608                .as_nanos()
6609        ));
6610        std::fs::create_dir_all(codex_home.join("sessions")).unwrap();
6611        std::fs::write(
6612            codex_home.join("config.toml"),
6613            "profile = \"review\"\n\n[profiles.review]\nmodel = \"gpt-5.1-codex\"\n\n[profiles.fast]\nmodel = \"gpt-5.1-codex-mini\"\n",
6614        )
6615        .unwrap();
6616
6617        let mut service = HarnessSessionService::new();
6618        let response = service.handle(request(
6619            1,
6620            "harness.v1.profiles.list",
6621            json!({"harness": "codex", "homes": {"codex": codex_home.join("sessions")}}),
6622        ));
6623        let rows = response["result"]["profiles"].as_array().unwrap();
6624        assert_eq!(rows.len(), 2, "{response}");
6625        let review = profile_row(&response, "codex", "review");
6626        assert_eq!(review["kind"], "codex_profile");
6627        assert_eq!(review["default"], true);
6628        assert_eq!(review["model"], "gpt-5.1-codex");
6629        assert_eq!(review["home"], Value::Null);
6630        assert_eq!(profile_row(&response, "codex", "fast")["default"], false);
6631
6632        let got = service.handle(request(
6633            2,
6634            "harness.v1.profiles.get",
6635            json!({
6636                "harness": "codex",
6637                "name": "fast",
6638                "homes": {"codex": codex_home.join("sessions")},
6639            }),
6640        ));
6641        assert_eq!(got["result"]["profile"]["model"], "gpt-5.1-codex-mini");
6642        std::fs::remove_dir_all(&codex_home).ok();
6643    }
6644
6645    /// A verb a harness lacks fails with `UnsupportedAction`, never a silent
6646    /// empty list; an unknown name is an invalid argument, not an empty row.
6647    #[test]
6648    fn profiles_refuse_harnesses_without_the_concept() {
6649        let mut service = HarnessSessionService::new();
6650        let response = service.handle(request(
6651            1,
6652            "harness.v1.profiles.list",
6653            json!({"harness": "claude-code"}),
6654        ));
6655        assert_eq!(response["error"]["code"], -32020, "{response}");
6656
6657        let missing = service.handle(request(
6658            2,
6659            "harness.v1.profiles.get",
6660            json!({
6661                "harness": "hermes",
6662                "name": "no-such-profile",
6663                "homes": profile_fixture_homes(),
6664            }),
6665        ));
6666        assert_eq!(missing["error"]["code"], -32602, "{missing}");
6667    }
6668
6669    /// The two methods are advertised, so a client discovers them from
6670    /// `harness.v1.capabilities` rather than from documentation.
6671    #[test]
6672    fn profiles_methods_are_advertised() {
6673        let mut service = HarnessSessionService::new();
6674        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
6675        let methods = response["result"]["methods"].as_array().unwrap();
6676        for method in ["harness.v1.profiles.list", "harness.v1.profiles.get"] {
6677            assert!(
6678                methods.iter().any(|entry| entry == method),
6679                "{method} is not advertised"
6680            );
6681        }
6682    }
6683
6684    // -----------------------------------------------------------------
6685    // ORCH-14 — channels
6686    // -----------------------------------------------------------------
6687
6688    fn channel_row<'a>(response: &'a Value, harness: &str, name: &str) -> &'a Value {
6689        response["result"]["channels"]
6690            .as_array()
6691            .unwrap_or_else(|| panic!("no channels array in {response}"))
6692            .iter()
6693            .find(|row| row["harness"] == harness && row["name"] == name)
6694            .unwrap_or_else(|| panic!("no `{harness}` channel `{name}` in {response}"))
6695    }
6696
6697    fn channels_list(harness: Option<&str>) -> Value {
6698        let mut params = json!({"homes": profile_fixture_homes()});
6699        if let Some(harness) = harness {
6700            params["harness"] = json!(harness);
6701        }
6702        HarnessSessionService::new().handle(request(1, "harness.v1.channels.list", params))
6703    }
6704
6705    /// dev/01: both sources answer in one row shape over the committed
6706    /// fixture homes — Hermes's `platforms:` blocks with their `extra` maps,
6707    /// and OpenClaw's `channels.<name>` entries split per account.
6708    #[test]
6709    fn channels_list_reads_both_gateway_harnesses_uniformly() {
6710        let response = channels_list(None);
6711        assert_eq!(
6712            response["result"]["schema"],
6713            crate::channels::CHANNELS_SCHEMA
6714        );
6715
6716        // Hermes: a credentialed platform, a bridged `extra.key` platform,
6717        // and one the config explicitly disables.
6718        let telegram = channel_row(&response, "hermes", "telegram");
6719        assert_eq!(telegram["kind"], "telegram");
6720        assert_eq!(telegram["enabled"], true);
6721        assert_eq!(telegram["configured"], true);
6722        // The `sessions` count is the discovery rows whose surface platform
6723        // is telegram: the fixture's `agent:main:telegram:…` DM and the
6724        // `agent:coder:telegram:…` group.
6725        assert_eq!(telegram["sessions"], 2);
6726        let api = channel_row(&response, "hermes", "api_server");
6727        assert_eq!(api["configured"], true, "extra.key is a credential key");
6728        assert_eq!(api["sessions"], 0);
6729        let webhook = channel_row(&response, "hermes", "webhook");
6730        assert_eq!(webhook["enabled"], false);
6731        // Hermes lists no credential for `webhook`: declaring it is all it
6732        // needs, so a credential-less entry is still `configured`.
6733        assert_eq!(webhook["configured"], true);
6734
6735        // OpenClaw: one row per account, named `<channel>/<accountId>`.
6736        let linked = channel_row(&response, "openclaw", "slack/T0FIXTURE");
6737        assert_eq!(linked["kind"], "slack");
6738        assert_eq!(linked["account"], "T0FIXTURE");
6739        assert_eq!(linked["enabled"], true);
6740        assert_eq!(linked["configured"], true);
6741        let unlinked = channel_row(&response, "openclaw", "slack/T1FIXTURE");
6742        assert_eq!(unlinked["enabled"], false);
6743        assert_eq!(
6744            unlinked["configured"], false,
6745            "an account with no credential key is not configured"
6746        );
6747        // A single-account channel keeps its own name and names its account
6748        // inline.
6749        let telegram = channel_row(&response, "openclaw", "telegram");
6750        assert_eq!(telegram["account"], "hermes-fixture-bot");
6751        assert_eq!(telegram["configured"], true);
6752
6753        // `status` is never claimed from a config file.
6754        for row in response["result"]["channels"].as_array().unwrap() {
6755            assert_eq!(row["status"], "unknown", "{row}");
6756        }
6757    }
6758
6759    /// dev/01: no field of any emitted row carries a credential. The fixture
6760    /// homes hold four FAKE credential strings; a row that leaked one — as a
6761    /// value, an account label, or a name — fails here.
6762    #[test]
6763    fn channels_rows_never_carry_a_fixture_secret() {
6764        let secrets = [
6765            "FAKE-TOKEN-DO-NOT-EMIT",
6766            "FAKE-API-SERVER-KEY-DO-NOT-EMIT",
6767            "FAKE-SLACK-BOT-TOKEN-DO-NOT-EMIT",
6768            "FAKE-SLACK-APP-TOKEN-DO-NOT-EMIT",
6769            "FAKE-TELEGRAM-TOKEN-DO-NOT-EMIT",
6770        ];
6771        // The strings really are in the fixtures, so this test can fail.
6772        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6773        let raw = format!(
6774            "{}{}",
6775            std::fs::read_to_string(fixtures.join("hermes_home/config.yaml")).unwrap(),
6776            std::fs::read_to_string(fixtures.join("openclaw_home/openclaw.json")).unwrap(),
6777        );
6778        for secret in secrets {
6779            assert!(raw.contains(secret), "fixture no longer holds `{secret}`");
6780        }
6781
6782        let emitted = serde_json::to_string(&channels_list(None)["result"]).unwrap();
6783        for secret in secrets {
6784            assert!(
6785                !emitted.contains(secret),
6786                "`{secret}` leaked into a channel row: {emitted}"
6787            );
6788        }
6789        // Belt and braces: no row FIELD is credential-shaped either, so a
6790        // future field cannot smuggle one past the literal scan.
6791        for row in channels_list(None)["result"]["channels"]
6792            .as_array()
6793            .unwrap()
6794        {
6795            for key in row.as_object().unwrap().keys() {
6796                let key = key.to_ascii_lowercase();
6797                assert!(
6798                    !["token", "key", "secret", "password", "credential"]
6799                        .iter()
6800                        .any(|marker| key.ends_with(marker)),
6801                    "`{key}` is a credential-shaped field on a channel row"
6802                );
6803            }
6804        }
6805    }
6806
6807    /// `status` answers one row by name, and refuses an unknown one.
6808    #[test]
6809    fn channels_status_reads_one_row_by_name() {
6810        let mut service = HarnessSessionService::new();
6811        let got = service.handle(request(
6812            1,
6813            "harness.v1.channels.status",
6814            json!({
6815                "harness": "openclaw",
6816                "name": "slack/T0FIXTURE",
6817                "homes": profile_fixture_homes(),
6818            }),
6819        ));
6820        assert_eq!(got["result"]["channel"]["kind"], "slack");
6821        assert_eq!(got["result"]["channel"]["account"], "T0FIXTURE");
6822        assert_eq!(got["result"]["channel"]["status"], "unknown");
6823
6824        let missing = service.handle(request(
6825            2,
6826            "harness.v1.channels.status",
6827            json!({
6828                "harness": "openclaw",
6829                "name": "no-such-channel",
6830                "homes": profile_fixture_homes(),
6831            }),
6832        ));
6833        assert_eq!(missing["error"]["code"], -32602, "{missing}");
6834    }
6835
6836    /// A harness with no channel concept fails with `UnsupportedAction`,
6837    /// never a silent empty list — Claude Code included, because its channels
6838    /// are MCP-protocol declarations no config file names.
6839    #[test]
6840    fn channels_refuse_harnesses_without_the_concept() {
6841        let response = channels_list(Some("claude-code"));
6842        assert_eq!(response["error"]["code"], -32020, "{response}");
6843        let codex = channels_list(Some("codex"));
6844        assert_eq!(codex["error"]["code"], -32020, "{codex}");
6845    }
6846
6847    /// The harness filter restricts the rows rather than being ignored.
6848    #[test]
6849    fn channels_list_filters_by_harness() {
6850        let response = channels_list(Some("openclaw"));
6851        let rows = response["result"]["channels"].as_array().unwrap();
6852        assert!(!rows.is_empty(), "{response}");
6853        assert!(
6854            rows.iter().all(|row| row["harness"] == "openclaw"),
6855            "harness filter leaked: {response}"
6856        );
6857    }
6858
6859    /// Both methods are advertised, so a client discovers them from
6860    /// `harness.v1.capabilities` rather than from documentation.
6861    #[test]
6862    fn channels_methods_are_advertised() {
6863        let mut service = HarnessSessionService::new();
6864        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
6865        let methods = response["result"]["methods"].as_array().unwrap();
6866        for method in ["harness.v1.channels.list", "harness.v1.channels.status"] {
6867            assert!(
6868                methods.iter().any(|entry| entry == method),
6869                "{method} is not advertised"
6870            );
6871        }
6872    }
6873
6874    /// The UI story renders REAL rows: this writes the discovery response the
6875    /// two assertions above pin into the fixture the Storybook
6876    /// `Compositions/Universal nouns` stories import, and fails when the
6877    /// committed copy has drifted from what the service now answers.
6878    #[test]
6879    fn orch6_story_fixture_matches_the_live_discovery_response() {
6880        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6881            .join("../../sdk/ui/stories/fixtures/hermes-discovery.json");
6882        let mut result = hermes_discovery(hermes_query());
6883        // `updated_at_ms` is derived from the fixture's own stored timestamps,
6884        // so the whole response is deterministic; drop only the cursor, which
6885        // is pagination state rather than a session fact.
6886        result.as_object_mut().unwrap().remove("next_cursor");
6887        let rendered = format!("{}\n", serde_json::to_string_pretty(&result).unwrap());
6888        if std::env::var_os("SUPERCODE_UPDATE_FIXTURES").is_some() {
6889            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
6890            std::fs::write(&path, &rendered).unwrap();
6891        }
6892        let committed = std::fs::read_to_string(&path).unwrap_or_default();
6893        assert_eq!(
6894            committed, rendered,
6895            "sdk/ui/stories/fixtures/hermes-discovery.json is stale — \
6896             re-run with SUPERCODE_UPDATE_FIXTURES=1"
6897        );
6898    }
6899
6900    fn pi_locator() -> SessionLocator {
6901        SessionLocator {
6902            harness: HarnessId::from(HarnessId::PI),
6903            session_id: "1e6f2a3b-0000-4000-8000-000000000001".into(),
6904            storage: StorageLocator::File {
6905                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6906                    .join("tests/fixtures/pi_session.jsonl"),
6907            },
6908        }
6909    }
6910
6911    fn opencode_locator() -> SessionLocator {
6912        let session_id = "ses_fixtureAAAAAAAAAAAAAAA1";
6913        SessionLocator {
6914            harness: HarnessId::from(HarnessId::OPENCODE),
6915            session_id: session_id.into(),
6916            storage: StorageLocator::Sqlite {
6917                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6918                    .join("tests/fixtures/opencode_fixture/opencode.db"),
6919                selector: session_id.into(),
6920            },
6921        }
6922    }
6923
6924    fn grok_locator() -> SessionLocator {
6925        SessionLocator {
6926            harness: HarnessId::from(HarnessId::GROK),
6927            session_id: "73c09283-4b33-41fa-90f1-0bcb0f7be523".into(),
6928            storage: StorageLocator::File {
6929                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6930                    .join("tests/fixtures/grok_session/chat_history.jsonl"),
6931            },
6932        }
6933    }
6934
6935    // ---- ORCH-11: `harness.v1.skills.list` -------------------------------
6936
6937    fn fixture_homes() -> Value {
6938        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
6939        json!({
6940            "claude_code": fixtures.join("__absent__"),
6941            "codex": fixtures.join("__absent__"),
6942            "opencode": fixtures.join("__absent__"),
6943            "pi": fixtures.join("__absent__"),
6944            "agents": fixtures.join("__absent__"),
6945            "hermes": fixtures.join("hermes_home"),
6946            "openclaw": fixtures.join("openclaw_home"),
6947        })
6948    }
6949
6950    #[test]
6951    fn preview_search_uses_the_discovery_rpc_and_refuses_live_subscription() {
6952        let root = std::env::temp_dir().join(format!(
6953            "supercode-preview-rpc-{}-{}",
6954            std::process::id(),
6955            std::time::SystemTime::now()
6956                .duration_since(std::time::UNIX_EPOCH)
6957                .unwrap()
6958                .as_nanos()
6959        ));
6960        std::fs::create_dir_all(&root).unwrap();
6961        for id in ["first", "second"] {
6962            std::fs::write(root.join(format!("{id}.jsonl")), format!("{}\n{}\n",
6963                json!({"type": "session_meta", "payload": {"id": id, "cwd": "/workspace"}}),
6964                json!({"type": "event_msg", "payload": {"type": "agent_message", "message": "NEBULA result"}}),
6965            )).unwrap();
6966        }
6967        let mut service = HarnessSessionService::new();
6968        let query = json!({
6969            "harnesses": ["codex"], "homes": {"codex": root},
6970            "query": "nebula", "search_previews": true, "limit": 1
6971        });
6972        let first = service.handle(request(1, "harness.v1.sessions.discover", query.clone()));
6973        assert!(first.get("error").is_none(), "{first}");
6974        assert_eq!(first["result"]["receipt"]["searched_previews"], true);
6975        assert_eq!(first["result"]["receipt"]["total_matched"], 2);
6976        let mut next_query = query.clone();
6977        next_query["cursor"] = first["result"]["next_cursor"].clone();
6978        let next = service.handle(request(2, "harness.v1.sessions.discover", next_query));
6979        assert_eq!(next["result"]["receipt"]["returned"], 1);
6980        assert_eq!(next["result"]["receipt"]["total_matched"], 2);
6981        assert_eq!(next["result"]["receipt"]["truncated"], false);
6982        assert_ne!(
6983            first["result"]["sessions"][0]["locator"],
6984            next["result"]["sessions"][0]["locator"]
6985        );
6986        let refused = service.handle(request(3, "harness.v1.sessions.index.subscribe", query));
6987        assert!(
6988            refused["error"]["message"]
6989                .as_str()
6990                .unwrap()
6991                .contains("use sessions.discover"),
6992            "{refused}"
6993        );
6994        std::fs::remove_dir_all(root).unwrap();
6995    }
6996
6997    #[test]
6998    fn session_index_resize_preserves_subscription_and_rejects_invalid_requests() {
6999        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.sessions.index.resize"));
7000        let root = std::env::temp_dir().join(format!(
7001            "supercode-index-rpc-{}-{}",
7002            std::process::id(),
7003            std::time::SystemTime::now()
7004                .duration_since(std::time::UNIX_EPOCH)
7005                .unwrap()
7006                .as_nanos()
7007        ));
7008        std::fs::create_dir_all(&root).unwrap();
7009        for id in ["first", "second"] {
7010            std::fs::write(root.join(format!("{id}.jsonl")), format!(
7011                "{{\"type\":\"session_meta\",\"payload\":{{\"id\":\"{id}\",\"cwd\":\"/workspace\"}}}}\n"
7012            )).unwrap();
7013        }
7014        let mut service = HarnessSessionService::new();
7015        let opened = service.handle(request(
7016            1,
7017            "harness.v1.sessions.index.subscribe",
7018            json!({
7019                "harnesses": ["codex"], "homes": { "codex": root }, "limit": 1
7020            }),
7021        ));
7022        assert!(opened.get("error").is_none(), "{opened:#}");
7023        let subscription = opened["result"]["subscription"]
7024            .as_str()
7025            .unwrap()
7026            .to_owned();
7027        assert_eq!(opened["result"]["initial"].as_array().unwrap().len(), 1);
7028        for params in [
7029            json!({"subscription": subscription, "limit": 0}),
7030            json!({"subscription": subscription, "limit": 2049}),
7031            json!({"subscription": subscription, "limit": 2, "cursor": "not-allowed"}),
7032            json!({"subscription": "unknown", "limit": 2}),
7033        ] {
7034            let rejected = service.handle(request(2, "harness.v1.sessions.index.resize", params));
7035            assert_eq!(rejected["error"]["code"], -32602, "{rejected:#}");
7036        }
7037        for (limit, revision) in [(1, 1), (2, 2), (2, 2), (1, 3)] {
7038            let response = service.handle(request(
7039                3,
7040                "harness.v1.sessions.index.resize",
7041                json!({
7042                    "subscription": subscription, "limit": limit
7043                }),
7044            ));
7045            assert!(response.get("error").is_none(), "{response:#}");
7046            assert_eq!(response["result"]["subscription"], subscription);
7047            assert_eq!(response["result"]["revision"], revision);
7048            assert_eq!(
7049                response["result"]["initial"].as_array().unwrap().len(),
7050                limit
7051            );
7052            assert_eq!(response["result"]["receipt"]["total_matched"], 2);
7053            assert_eq!(service.index_subscriptions.len(), 1);
7054        }
7055        let removed = service.handle(request(
7056            4,
7057            "harness.v1.sessions.index.unsubscribe",
7058            json!({
7059                "subscription": subscription
7060            }),
7061        ));
7062        assert_eq!(removed["result"]["removed"], true);
7063        let stale = service.handle(request(
7064            5,
7065            "harness.v1.sessions.index.resize",
7066            json!({
7067                "subscription": subscription, "limit": 1
7068            }),
7069        ));
7070        assert_eq!(stale["error"]["code"], -32602);
7071        drop(service);
7072        std::fs::remove_dir_all(root).unwrap();
7073    }
7074
7075    fn skills_rows(params: Value) -> Vec<Value> {
7076        let response =
7077            HarnessSessionService::new().handle(request(1, "harness.v1.skills.list", params));
7078        assert!(response.get("error").is_none(), "{response:#}");
7079        response["result"].as_array().cloned().unwrap_or_default()
7080    }
7081
7082    /// The uniform row over two harnesses at once, from the harnesses' own
7083    /// skill roots: name, harness, scope, location, description, version.
7084    #[test]
7085    fn skills_list_reads_the_hermes_and_openclaw_roots() {
7086        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7087        let rows = skills_rows(json!({
7088            "homes": fixture_homes(),
7089            "cwd": fixtures.join("hermes_home"),
7090        }));
7091        let arxiv = rows
7092            .iter()
7093            .find(|row| row["name"] == json!("arxiv-search"))
7094            .unwrap_or_else(|| panic!("no arxiv row in {rows:#?}"));
7095        assert_eq!(arxiv["harness"], json!(HarnessId::HERMES));
7096        assert_eq!(arxiv["scope"], json!("user"));
7097        assert_eq!(arxiv["version"], json!("1.4.0"));
7098        assert!(arxiv["location"]
7099            .as_str()
7100            .unwrap()
7101            .ends_with("hermes_home/skills/research/arxiv"));
7102
7103        // A directory with no SKILL.md still lists, by directory name.
7104        let bare = rows
7105            .iter()
7106            .find(|row| row["name"] == json!("bare-skill"))
7107            .unwrap_or_else(|| panic!("no bare-skill row in {rows:#?}"));
7108        assert_eq!(bare["enabled"], json!(null));
7109        assert!(bare.get("description").is_none());
7110
7111        let demo = rows
7112            .iter()
7113            .find(|row| row["name"] == json!("clawhub-demo"))
7114            .unwrap_or_else(|| panic!("no clawhub-demo row in {rows:#?}"));
7115        assert_eq!(demo["harness"], json!(HarnessId::OPENCLAW));
7116        assert_eq!(demo["scope"], json!("managed"));
7117        assert_eq!(demo["enabled"], json!(false));
7118    }
7119
7120    /// Both filters select against the same rows.
7121    #[test]
7122    fn skills_list_filters_by_harness_and_scope() {
7123        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7124        let hermes = skills_rows(json!({
7125            "homes": fixture_homes(),
7126            "cwd": fixtures.join("hermes_home"),
7127            "harness": HarnessId::HERMES,
7128        }));
7129        assert!(!hermes.is_empty());
7130        assert!(hermes
7131            .iter()
7132            .all(|row| row["harness"] == json!(HarnessId::HERMES)));
7133
7134        let managed = skills_rows(json!({
7135            "homes": fixture_homes(),
7136            "cwd": fixtures.join("openclaw_home"),
7137            "harness": HarnessId::OPENCLAW,
7138            "scope": "managed",
7139        }));
7140        assert_eq!(managed.len(), 1, "{managed:#?}");
7141        assert_eq!(managed[0]["name"], json!("clawhub-demo"));
7142
7143        let bundled = skills_rows(json!({
7144            "homes": fixture_homes(),
7145            "cwd": fixtures.join("openclaw_home"),
7146            "harness": HarnessId::OPENCLAW,
7147            "scope": "bundled",
7148        }));
7149        assert!(bundled.is_empty(), "{bundled:#?}");
7150    }
7151
7152    /// A harness supercode has no skills root for is refused by name, not
7153    /// answered with an empty list.
7154    #[test]
7155    fn skills_list_refuses_an_unknown_harness() {
7156        let response = HarnessSessionService::new().handle(request(
7157            1,
7158            "harness.v1.skills.list",
7159            json!({"harness": "not-a-harness", "homes": fixture_homes()}),
7160        ));
7161        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7162        assert!(response["error"]["message"]
7163            .as_str()
7164            .unwrap()
7165            .contains("not-a-harness"));
7166    }
7167
7168    /// The method is advertised, and its SDK operation resolves it.
7169    #[test]
7170    fn skills_list_is_an_advertised_method_and_sdk_operation() {
7171        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.list"));
7172        assert_eq!(
7173            SdkOperation::from_method("harness.v1.skills.list"),
7174            Some(SdkOperation::SkillsList)
7175        );
7176    }
7177
7178    // ---- ORCH-22: `harness.v1.skills.install|remove` ----------------------
7179
7180    /// Both controlled verbs are advertised and resolve to their operation.
7181    #[test]
7182    fn skills_install_and_remove_are_advertised_methods_and_sdk_operations() {
7183        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.install"));
7184        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.skills.remove"));
7185        assert_eq!(
7186            SdkOperation::from_method("harness.v1.skills.install"),
7187            Some(SdkOperation::SkillsInstall)
7188        );
7189        assert_eq!(
7190            SdkOperation::from_method("harness.v1.skills.remove"),
7191            Some(SdkOperation::SkillsRemove)
7192        );
7193    }
7194
7195    /// The directory door, end to end over the RPC: a local package lands in
7196    /// Claude Code's own user root and the outcome carries the operation and
7197    /// the row the ORCH-11 loader reads back.
7198    #[test]
7199    fn skills_install_and_remove_drive_the_directory_door() {
7200        let root = std::env::temp_dir().join(format!(
7201            "supercode-orch22-rpc-{}-{}",
7202            std::process::id(),
7203            std::time::SystemTime::now()
7204                .duration_since(std::time::UNIX_EPOCH)
7205                .unwrap()
7206                .as_nanos()
7207        ));
7208        let source = root.join("probe-src");
7209        std::fs::create_dir_all(&source).unwrap();
7210        std::fs::write(
7211            source.join("SKILL.md"),
7212            "---\nname: orch22-rpc\ndescription: a probe\n---\nbody\n",
7213        )
7214        .unwrap();
7215        let homes = json!({
7216            "claude_code": root.join("claude_home"),
7217            "codex": root.join("__absent__"),
7218            "opencode": root.join("__absent__"),
7219            "pi": root.join("__absent__"),
7220            "hermes": root.join("__absent__"),
7221            "openclaw": root.join("__absent__"),
7222            "agents": root.join("__absent__"),
7223        });
7224
7225        let mut service = HarnessSessionService::new();
7226        let installed = service.handle(request(
7227            1,
7228            "harness.v1.skills.install",
7229            json!({
7230                "harness": HarnessId::CLAUDE_CODE,
7231                "source": source,
7232                "scope": "user",
7233                "cwd": root,
7234                "homes": homes,
7235            }),
7236        ));
7237        let result = &installed["result"];
7238        assert_eq!(result["name"], json!("orch22-rpc"), "{installed:#}");
7239        assert_eq!(result["verb"], json!("install"));
7240        assert!(result["ran"]
7241            .as_str()
7242            .is_some_and(|ran| ran.starts_with("cp -R ")));
7243        assert_eq!(result["skill"]["scope"], json!("user"));
7244
7245        let removed = service.handle(request(
7246            2,
7247            "harness.v1.skills.remove",
7248            json!({
7249                "harness": HarnessId::CLAUDE_CODE,
7250                "name": "orch22-rpc",
7251                "scope": "user",
7252                "cwd": root,
7253                "homes": homes,
7254            }),
7255        ));
7256        assert_eq!(removed["result"]["removed"], json!(true), "{removed:#}");
7257        assert!(!root.join("claude_home/skills/orch22-rpc").exists());
7258        std::fs::remove_dir_all(&root).ok();
7259    }
7260
7261    /// OpenClaw publishes no `skills remove` at the pin, so the uniform verb
7262    /// refuses with UnsupportedAction instead of deleting files itself.
7263    #[test]
7264    fn skills_remove_refuses_openclaw_at_the_pin() {
7265        let response = HarnessSessionService::new().handle(request(
7266            1,
7267            "harness.v1.skills.remove",
7268            json!({"harness": HarnessId::OPENCLAW, "name": "clawhub-demo"}),
7269        ));
7270        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7271        assert!(response["error"]["message"]
7272            .as_str()
7273            .unwrap()
7274            .contains("no `skills remove` verb"));
7275    }
7276
7277    /// A harness with no skills root at all is refused by name, with the
7278    /// same sentence `skills.list` gives it.
7279    #[test]
7280    fn skills_install_refuses_a_harness_without_a_skills_root() {
7281        let response = HarnessSessionService::new().handle(request(
7282            1,
7283            "harness.v1.skills.install",
7284            json!({"harness": "not-a-harness", "source": "/tmp/x"}),
7285        ));
7286        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7287        assert!(response["error"]["message"]
7288            .as_str()
7289            .unwrap()
7290            .contains("not-a-harness"));
7291    }
7292
7293    // ---- ORCH-12: `harness.v1.memory.show|search` ------------------------
7294
7295    /// `HarnessHomes` for the committed fixture homes. Every root a test does
7296    /// not name is pinned at an absent path, so a read can never fall through
7297    /// to this machine's real harness homes. Note `hermes` is the `state.db`
7298    /// PATH (its parent is HERMES_HOME) and `claude_code` is the `projects`
7299    /// directory — the same contract discovery uses.
7300    fn memory_homes() -> Value {
7301        let fixtures = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
7302        json!({
7303            "claude_code": fixtures.join("__absent__"),
7304            "codex": fixtures.join("__absent__"),
7305            "opencode": fixtures.join("__absent__"),
7306            "pi": fixtures.join("__absent__"),
7307            "grok": fixtures.join("__absent__"),
7308            "gemini": fixtures.join("__absent__"),
7309            "goose": fixtures.join("__absent__"),
7310            "supercode": fixtures.join("__absent__"),
7311            "hermes": fixtures.join("hermes_home/state.db"),
7312            "openclaw": fixtures.join("openclaw_home"),
7313        })
7314    }
7315
7316    fn memory_call_ok(method: &str, params: Value, key: &str) -> Vec<Value> {
7317        let response = HarnessSessionService::new().handle(request(1, method, params));
7318        assert!(response.get("error").is_none(), "{response:#}");
7319        assert_eq!(response["result"]["schema"], json!("supercode.memory.v1"));
7320        response["result"][key]
7321            .as_array()
7322            .cloned()
7323            .unwrap_or_default()
7324    }
7325
7326    fn memory_documents(params: Value) -> Vec<Value> {
7327        memory_call_ok("harness.v1.memory.show", params, "documents")
7328    }
7329
7330    fn memory_matches(params: Value) -> Vec<Value> {
7331        memory_call_ok("harness.v1.memory.search", params, "matches")
7332    }
7333
7334    fn find_document<'a>(rows: &'a [Value], profile: &str, name: &str) -> &'a Value {
7335        rows.iter()
7336            .find(|row| row["profile"] == profile && row["name"] == name)
7337            .unwrap_or_else(|| panic!("no `{profile}` document `{name}` in {rows:#?}"))
7338    }
7339
7340    /// Hermes: the built-in `MEMORY.md`/`USER.md` pair and the `memories/`
7341    /// topic files, for HERMES_HOME itself and for every profile home.
7342    #[test]
7343    fn memory_show_reads_the_hermes_profile_homes() {
7344        let rows = memory_documents(json!({"harness": "hermes", "homes": memory_homes()}));
7345
7346        let notes = find_document(&rows, "default", "MEMORY.md");
7347        assert_eq!(notes["harness"], "hermes");
7348        assert_eq!(notes["scope"], "user");
7349        assert!(notes["size"].as_u64().unwrap() > 0);
7350        assert!(notes["updated_at"].is_string(), "{notes:#?}");
7351        // The default answer previews the head and never the whole body.
7352        assert!(notes.get("content").is_none(), "{notes:#?}");
7353        assert_eq!(notes["truncated"], true);
7354        assert_eq!(notes["preview"].as_array().unwrap().len(), 5);
7355
7356        let user = find_document(&rows, "default", "USER.md");
7357        assert_eq!(user["scope"], "user");
7358        assert!(user["preview"]
7359            .as_array()
7360            .unwrap()
7361            .iter()
7362            .any(|line| line.as_str().unwrap().contains("neovim")));
7363
7364        let topic = find_document(&rows, "default", "memories/2026-09-01-notes.md");
7365        assert!(topic["path"]
7366            .as_str()
7367            .unwrap()
7368            .ends_with("hermes_home/memories/2026-09-01-notes.md"));
7369
7370        // Profile mode points HERMES_HOME at `<root>/profiles/<name>`.
7371        let coder = find_document(&rows, "coder", "MEMORY.md");
7372        assert_eq!(coder["scope"], "profile");
7373        assert!(coder["path"]
7374            .as_str()
7375            .unwrap()
7376            .ends_with("hermes_home/profiles/coder/MEMORY.md"));
7377    }
7378
7379    /// `full` is the only way a body crosses the wire, and `profile` narrows
7380    /// the read to one home.
7381    #[test]
7382    fn memory_show_returns_bodies_only_under_full_and_narrows_by_profile() {
7383        let rows = memory_documents(json!({
7384            "harness": "hermes",
7385            "profile": "coder",
7386            "full": true,
7387            "homes": memory_homes(),
7388        }));
7389        assert!(
7390            rows.iter().all(|row| row["profile"] == "coder"),
7391            "{rows:#?}"
7392        );
7393        let coder = find_document(&rows, "coder", "MEMORY.md");
7394        assert!(coder["content"]
7395            .as_str()
7396            .expect("full returns the body")
7397            .contains("anthropic/claude-opus-4-8"));
7398    }
7399
7400    /// OpenClaw: memory-core's files under each agent's workspace —
7401    /// `<state>/workspace` for the default agent, `<state>/workspace-<id>`
7402    /// for any other.
7403    #[test]
7404    fn memory_show_reads_the_openclaw_agent_workspaces() {
7405        let rows = memory_documents(json!({"harness": "openclaw", "homes": memory_homes()}));
7406
7407        let main = find_document(&rows, "main", "MEMORY.md");
7408        assert_eq!(main["scope"], "agent");
7409        assert!(main["path"]
7410            .as_str()
7411            .unwrap()
7412            .ends_with("openclaw_home/workspace/MEMORY.md"));
7413
7414        let topic = find_document(&rows, "main", "memory/2026-09-01-standup.md");
7415        assert!(topic["path"]
7416            .as_str()
7417            .unwrap()
7418            .ends_with("openclaw_home/workspace/memory/2026-09-01-standup.md"));
7419
7420        let design = find_document(&rows, "design", "MEMORY.md");
7421        assert!(design["path"]
7422            .as_str()
7423            .unwrap()
7424            .ends_with("openclaw_home/workspace-design/MEMORY.md"));
7425    }
7426
7427    /// Claude Code: the auto-memory directory of the project the working tree
7428    /// belongs to, keyed by the enclosing git repository.
7429    #[test]
7430    fn memory_show_reads_a_claude_code_project_auto_memory_directory() {
7431        let scratch = std::env::temp_dir().join(format!(
7432            "supercode-orch12-cc-{}-{}",
7433            std::process::id(),
7434            std::time::SystemTime::now()
7435                .duration_since(std::time::UNIX_EPOCH)
7436                .unwrap()
7437                .as_nanos()
7438        ));
7439        let project = scratch.join("repo");
7440        std::fs::create_dir_all(project.join(".git")).unwrap();
7441        // Auto-memory is shared across a repo's worktrees, so a nested
7442        // working directory must resolve to the repo's own project dir.
7443        let worktree = project.join("crates/harness");
7444        std::fs::create_dir_all(&worktree).unwrap();
7445        let slug: String = project
7446            .to_string_lossy()
7447            .chars()
7448            .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
7449            .collect();
7450        let projects = scratch.join("claude/projects");
7451        let memory = projects.join(&slug).join("memory");
7452        std::fs::create_dir_all(&memory).unwrap();
7453        std::fs::write(
7454            memory.join("MEMORY.md"),
7455            "# index\n- [build box](build-box.md) — the pinned harnesses\n",
7456        )
7457        .unwrap();
7458        std::fs::write(
7459            memory.join("build-box.md"),
7460            "hermes 0.21.0 and openclaw 2026.7.1-2 are the pins\n",
7461        )
7462        .unwrap();
7463
7464        let mut homes = memory_homes();
7465        homes["claude_code"] = json!(projects);
7466        let rows = memory_documents(json!({
7467            "harness": "claude-code",
7468            "cwd": worktree,
7469            "homes": homes,
7470        }));
7471        let index = find_document(&rows, &slug, "MEMORY.md");
7472        assert_eq!(index["harness"], "claude-code");
7473        assert_eq!(index["scope"], "project");
7474        let topic = find_document(&rows, &slug, "build-box.md");
7475        assert!(topic["preview"]
7476            .as_array()
7477            .unwrap()
7478            .iter()
7479            .any(|line| line.as_str().unwrap().contains("2026.7.1-2")));
7480
7481        let hits = memory_matches(json!({
7482            "harness": "claude-code",
7483            "query": "pinned harnesses",
7484            "cwd": worktree,
7485            "homes": homes,
7486        }));
7487        assert_eq!(hits.len(), 1, "{hits:#?}");
7488        assert_eq!(hits[0]["name"], "MEMORY.md");
7489        assert_eq!(hits[0]["line"], 2);
7490
7491        let _ = std::fs::remove_dir_all(&scratch);
7492    }
7493
7494    /// A config-less OpenClaw install declares no default agent, but
7495    /// memory-core still resolves ONE agent to the default `workspace`
7496    /// directory — the same `main`-then-first convention the profile rows
7497    /// use. Measured against `openclaw memory status` on the pinned CLI
7498    /// (`docs/interop/research/orch12-memory-receipt-2026-09-03.json`).
7499    #[test]
7500    fn memory_show_resolves_the_default_workspace_without_an_openclaw_config() {
7501        let state = std::env::temp_dir().join(format!(
7502            "supercode-orch12-oc-{}-{}",
7503            std::process::id(),
7504            std::time::SystemTime::now()
7505                .duration_since(std::time::UNIX_EPOCH)
7506                .unwrap()
7507                .as_nanos()
7508        ));
7509        // No `openclaw.json`: only the agent home the gateway creates.
7510        std::fs::create_dir_all(state.join("agents/main/agent")).unwrap();
7511        std::fs::create_dir_all(state.join("workspace")).unwrap();
7512        std::fs::write(
7513            state.join("workspace/MEMORY.md"),
7514            "the gateway websocket needs credentials\n",
7515        )
7516        .unwrap();
7517
7518        let mut homes = memory_homes();
7519        homes["openclaw"] = json!(state);
7520        let rows = memory_documents(json!({"harness": "openclaw", "homes": homes}));
7521        assert_eq!(rows.len(), 1, "{rows:#?}");
7522        let row = find_document(&rows, "main", "MEMORY.md");
7523        assert_eq!(row["scope"], "agent");
7524        assert!(row["path"]
7525            .as_str()
7526            .unwrap()
7527            .ends_with("workspace/MEMORY.md"));
7528
7529        let _ = std::fs::remove_dir_all(&state);
7530    }
7531
7532    /// Search is a plain scan over the same documents: a hit carries the
7533    /// path, line and excerpt; a miss is an empty list, not an error.
7534    #[test]
7535    fn memory_search_reports_hits_by_line_and_misses_as_empty() {
7536        let hit = memory_matches(json!({
7537            "harness": "hermes",
7538            "query": "NEOVIM",
7539            "homes": memory_homes(),
7540        }));
7541        assert_eq!(hit.len(), 1, "{hit:#?}");
7542        assert_eq!(hit[0]["harness"], "hermes");
7543        assert_eq!(hit[0]["name"], "USER.md");
7544        assert_eq!(hit[0]["scope"], "user");
7545        assert_eq!(hit[0]["line"], 5);
7546        assert!(hit[0]["excerpt"].as_str().unwrap().contains("neovim"));
7547
7548        // A regular expression reaches the same lines.
7549        let regex = memory_matches(json!({
7550            "harness": "hermes",
7551            "query": "neo(vim|vi)",
7552            "regex": true,
7553            "homes": memory_homes(),
7554        }));
7555        assert_eq!(regex.len(), 1, "{regex:#?}");
7556
7557        let miss = memory_matches(json!({
7558            "harness": "hermes",
7559            "query": "no-memory-line-says-this",
7560            "homes": memory_homes(),
7561        }));
7562        assert!(miss.is_empty(), "{miss:#?}");
7563    }
7564
7565    /// The uniform-verb contract: a harness with no memory store at the pin
7566    /// is refused by name, and `session` only selects a Claude Code project.
7567    #[test]
7568    fn memory_refuses_harnesses_without_a_store_and_misplaced_session_scoping() {
7569        for method in ["harness.v1.memory.show", "harness.v1.memory.search"] {
7570            let response = HarnessSessionService::new().handle(request(
7571                1,
7572                method,
7573                json!({"harness": "codex", "query": "anything", "homes": memory_homes()}),
7574            ));
7575            assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7576            assert!(response["error"]["message"]
7577                .as_str()
7578                .unwrap()
7579                .contains("codex"));
7580        }
7581
7582        let response = HarnessSessionService::new().handle(request(
7583            1,
7584            "harness.v1.memory.show",
7585            json!({"harness": "hermes", "session": "abc", "homes": memory_homes()}),
7586        ));
7587        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7588
7589        // `harness` is not optional: memory documents are the user's prose.
7590        let response = HarnessSessionService::new().handle(request(
7591            1,
7592            "harness.v1.memory.show",
7593            json!({"homes": memory_homes()}),
7594        ));
7595        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
7596    }
7597
7598    /// Both methods are advertised, and their SDK operations resolve them.
7599    #[test]
7600    fn memory_methods_are_advertised_and_map_to_sdk_operations() {
7601        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.show"));
7602        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.memory.search"));
7603        assert_eq!(
7604            SdkOperation::from_method("harness.v1.memory.show"),
7605            Some(SdkOperation::MemoryShow)
7606        );
7607        assert_eq!(
7608            SdkOperation::from_method("harness.v1.memory.search"),
7609            Some(SdkOperation::MemorySearch)
7610        );
7611    }
7612
7613    // ---- ORCH-9: `harness.v1.approvals.list` -----------------------------
7614
7615    /// A runtime that raises one protocol request and then goes quiet, so a
7616    /// single poll delivers the request without closing the connection.
7617    struct RequestingRuntime {
7618        handle: RuntimeHandle,
7619        events: std::collections::VecDeque<HarnessEvent>,
7620        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7621    }
7622
7623    #[async_trait]
7624    impl RuntimeConnection for RequestingRuntime {
7625        fn handle(&self) -> &RuntimeHandle {
7626            &self.handle
7627        }
7628
7629        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
7630            unreachable!("this runtime only raises requests")
7631        }
7632
7633        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
7634            match self.events.pop_front() {
7635                Some(event) => Ok(Some(event)),
7636                // Quiet, not closed: `poll_sdk_events` times out and leaves
7637                // the connection open, the way a runtime blocked on a
7638                // permission request behaves.
7639                None => std::future::pending().await,
7640            }
7641        }
7642
7643        async fn interrupt(&mut self) -> crate::Result<()> {
7644            Ok(())
7645        }
7646
7647        async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
7648            // Both halves are recorded: ORCH-20 has to prove not just that the
7649            // right request was answered but that the door received its own
7650            // reply envelope.
7651            self.answered
7652                .lock()
7653                .unwrap_or_else(std::sync::PoisonError::into_inner)
7654                .push(json!({"request_id": request_id, "response": response}));
7655            Ok(())
7656        }
7657
7658        async fn close(&mut self) -> crate::Result<()> {
7659            Ok(())
7660        }
7661    }
7662
7663    fn requesting_runtime(
7664        harness: &str,
7665        events: Vec<HarnessEvent>,
7666        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7667    ) -> Box<dyn RuntimeConnection> {
7668        requesting_runtime_named(harness, "hermes-live-session", events, answered)
7669    }
7670
7671    fn requesting_runtime_named(
7672        harness: &str,
7673        runtime_id: &str,
7674        events: Vec<HarnessEvent>,
7675        answered: std::sync::Arc<std::sync::Mutex<Vec<Value>>>,
7676    ) -> Box<dyn RuntimeConnection> {
7677        Box::new(RequestingRuntime {
7678            handle: RuntimeHandle {
7679                harness: HarnessId::from(harness),
7680                runtime_id: runtime_id.into(),
7681                endpoint: RuntimeEndpoint::LocalProcess {
7682                    pid: None,
7683                    command: vec!["hermes-acp".into()],
7684                    protocol: "acp".into(),
7685                },
7686            },
7687            events: events.into(),
7688            answered,
7689        })
7690    }
7691
7692    fn permission_event(id: u64, title: &str) -> HarnessEvent {
7693        HarnessEvent {
7694            sequence: None,
7695            kind: "session/request_permission".into(),
7696            payload: json!({
7697                "jsonrpc": "2.0",
7698                "id": id,
7699                "method": "session/request_permission",
7700                "params": {
7701                    "sessionId": "hermes-live-session",
7702                    "toolCall": {"toolCallId": "call-1", "title": title, "kind": "execute"},
7703                    "options": [
7704                        {"optionId": "allow_once", "name": "Allow once", "kind": "allow_once"},
7705                        {"optionId": "allow_for_session", "name": "Allow for session", "kind": "allow_always"},
7706                        {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
7707                    ],
7708                },
7709            }),
7710        }
7711    }
7712
7713    fn approvals(service: &mut HarnessSessionService, params: Value) -> Value {
7714        let response = service.handle(request(1, "harness.v1.approvals.list", params));
7715        assert!(response.get("error").is_none(), "{response:#}");
7716        response["result"].clone()
7717    }
7718
7719    /// ORC-2 dev/01: the same uniform loop over the CLAUDE CODE door. The
7720    /// `can_use_tool` control request the CLI raises to its registered
7721    /// permission handler lists as one pending row, `approvals.resolve <id>
7722    /// allow_once` sends the `{behavior}` result the CLI accepts through
7723    /// `runtimes.respond`, and the row is gone. The frame is the one claude
7724    /// 2.1.258 wrote, transcribed from
7725    /// `docs/interop/research/orc2-claude-respond-receipt-2026-09-04.json`.
7726    #[tokio::test]
7727    async fn a_claude_code_permission_request_lists_and_resolves_on_the_uniform_door() {
7728        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7729        let mut service = HarnessSessionService::new();
7730        service.runtimes.insert(
7731            "runtime-cc".into(),
7732            requesting_runtime_named(
7733                HarnessId::CLAUDE_CODE,
7734                "claude-live-session",
7735                vec![HarnessEvent {
7736                    sequence: None,
7737                    kind: "control_request".into(),
7738                    payload: json!({
7739                        "type": "control_request",
7740                        "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
7741                        "request": {
7742                            "subtype": "can_use_tool",
7743                            "tool_name": "Bash",
7744                            "display_name": "Bash",
7745                            "input": {"command": "touch probe-artifact.txt"},
7746                            "tool_use_id": "toolu_mock_1",
7747                        },
7748                    }),
7749                }],
7750                answered.clone(),
7751            ),
7752        );
7753
7754        let notifications = service.poll_runtimes().await;
7755        assert_eq!(notifications.len(), 1, "{notifications:#?}");
7756
7757        let rows = approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}));
7758        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
7759        let row = &rows[0];
7760        assert_eq!(row["id"], "runtime-cc/053f8a2d-3445-4011-a259-4261b31c7326");
7761        assert_eq!(row["harness"], HarnessId::CLAUDE_CODE);
7762        assert_eq!(row["status"], "pending");
7763        assert_eq!(row["subject"], "Bash touch probe-artifact.txt");
7764        assert_eq!(row["runtime_id"], "claude-live-session");
7765        assert_eq!(
7766            row["options"]
7767                .as_array()
7768                .unwrap()
7769                .iter()
7770                .map(|option| option["id"].as_str().unwrap())
7771                .collect::<Vec<_>>(),
7772            vec!["allow", "deny"],
7773        );
7774
7775        let response = resolve(
7776            &mut service,
7777            json!({"id": row["id"], "decision": "allow_once"}),
7778        )
7779        .await;
7780        assert!(response.get("error").is_none(), "{response:#}");
7781        assert_eq!(response["result"]["option_id"], "allow");
7782        assert_eq!(
7783            answered
7784                .lock()
7785                .unwrap_or_else(std::sync::PoisonError::into_inner)
7786                .as_slice(),
7787            &[json!({
7788                "request_id": "053f8a2d-3445-4011-a259-4261b31c7326",
7789                "response": {"behavior": "allow"},
7790            })],
7791        );
7792        assert_eq!(
7793            approvals(&mut service, json!({"harness": HarnessId::CLAUDE_CODE}))
7794                .as_array()
7795                .map(Vec::len),
7796            Some(0),
7797        );
7798    }
7799
7800    /// dev/01: a live ACP permission request raised on a driven runtime is
7801    /// listable while the turn is blocked on it, and stops being listable
7802    /// the moment `runtimes.respond` answers it.
7803    #[tokio::test]
7804    async fn a_live_permission_request_lists_until_it_is_answered() {
7805        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
7806        let mut service = HarnessSessionService::new();
7807        service.runtimes.insert(
7808            "runtime-1".into(),
7809            requesting_runtime(
7810                HarnessId::HERMES,
7811                vec![permission_event(7, "rm -rf build")],
7812                answered.clone(),
7813            ),
7814        );
7815
7816        let notifications = service.poll_runtimes().await;
7817        assert_eq!(notifications.len(), 1, "{notifications:#?}");
7818
7819        let rows = approvals(&mut service, json!({}));
7820        assert_eq!(rows.as_array().map(Vec::len), Some(1), "{rows:#}");
7821        let row = &rows[0];
7822        assert_eq!(row["id"], "runtime-1/7");
7823        assert_eq!(row["harness"], HarnessId::HERMES);
7824        assert_eq!(row["kind"], "live");
7825        assert_eq!(row["status"], "pending");
7826        assert_eq!(row["subject"], "rm -rf build");
7827        assert_eq!(row["session_id"], "hermes-live-session");
7828        assert_eq!(row["runtime_id"], "hermes-live-session");
7829        assert!(row["requested_at_ms"].as_i64().is_some(), "{row:#}");
7830        assert!(
7831            row["age_ms"].as_i64().is_some_and(|age| age >= 0),
7832            "{row:#}"
7833        );
7834        assert_eq!(
7835            row["options"]
7836                .as_array()
7837                .unwrap()
7838                .iter()
7839                .map(|option| option["id"].as_str().unwrap())
7840                .collect::<Vec<_>>(),
7841            vec!["allow_once", "allow_for_session", "deny"],
7842        );
7843
7844        // The filters select against the same rows.
7845        assert_eq!(
7846            approvals(&mut service, json!({"harness": HarnessId::HERMES}))
7847                .as_array()
7848                .map(Vec::len),
7849            Some(1),
7850        );
7851        assert_eq!(
7852            approvals(&mut service, json!({"session": "some-other-session"}))
7853                .as_array()
7854                .map(Vec::len),
7855            Some(0),
7856        );
7857
7858        let response = service
7859            .handle_async(request(
7860                2,
7861                "harness.v1.runtimes.respond",
7862                json!({
7863                    "connection": "runtime-1",
7864                    "request_id": 7,
7865                    "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7866                }),
7867            ))
7868            .await;
7869        assert!(response.get("error").is_none(), "{response:#}");
7870        assert_eq!(
7871            answered
7872                .lock()
7873                .unwrap_or_else(std::sync::PoisonError::into_inner)
7874                .as_slice(),
7875            &[json!({
7876                "request_id": 7,
7877                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
7878            })],
7879        );
7880
7881        let rows = approvals(&mut service, json!({}));
7882        assert_eq!(rows.as_array().map(Vec::len), Some(0), "{rows:#}");
7883    }
7884
7885    /// dev/01: supercode's own queued subagent approvals list through the
7886    /// same door, carrying the outcome the record holds.
7887    #[test]
7888    fn queued_subagent_approvals_list_through_the_same_door() {
7889        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
7890            crate::subagents::QueuedApproval {
7891                child_agent_id: "child-7".into(),
7892                tool: "shell".into(),
7893                subject: Some("cargo publish --dry-run".into()),
7894                queued_at_ms: 1,
7895                outcome: None,
7896            },
7897            crate::subagents::QueuedApproval {
7898                child_agent_id: "child-8".into(),
7899                tool: "write_file".into(),
7900                subject: None,
7901                queued_at_ms: 2,
7902                outcome: Some(crate::subagents::QueuedApprovalOutcome::Denied),
7903            },
7904        ]));
7905        let mut service = HarnessSessionService::new();
7906        service.observe_subagent_approvals(queue);
7907
7908        let rows = approvals(&mut service, json!({}));
7909        assert_eq!(rows.as_array().map(Vec::len), Some(2), "{rows:#}");
7910        assert_eq!(rows[0]["id"], "supercode/subagent/child-7/1/0");
7911        assert_eq!(rows[0]["harness"], HarnessId::SUPERCODE);
7912        assert_eq!(rows[0]["status"], "pending");
7913        assert_eq!(rows[0]["subject"], "shell cargo publish --dry-run");
7914        assert_eq!(rows[1]["status"], "denied");
7915        assert!(rows[1]["options"].as_array().unwrap().is_empty());
7916
7917        // `--session` addresses a subagent row by its child agent id.
7918        let only = approvals(&mut service, json!({"session": "child-8"}));
7919        assert_eq!(only.as_array().map(Vec::len), Some(1), "{only:#}");
7920        assert_eq!(only[0]["id"], "supercode/subagent/child-8/2/1");
7921    }
7922
7923    /// The uniform-verb contract: an id whose runtime door cannot carry a
7924    /// protocol request is refused BY NAME rather than answered with an empty
7925    /// list. Since ORC-2 gave Claude Code a permission-response primitive
7926    /// every registered harness can carry one, so the refusal is exercised on
7927    /// an unknown id — and the registered ids are asserted to be accepted.
7928    #[test]
7929    fn approvals_list_refuses_a_harness_that_cannot_carry_a_request() {
7930        let response = HarnessSessionService::new().handle(request(
7931            1,
7932            "harness.v1.approvals.list",
7933            json!({"harness": "not-a-harness"}),
7934        ));
7935        assert_eq!(response["error"]["code"], json!(-32020), "{response:#}");
7936        assert!(response["error"]["message"]
7937            .as_str()
7938            .unwrap()
7939            .contains("not-a-harness"));
7940        for harness in [HarnessId::CLAUDE_CODE, HarnessId::CODEX] {
7941            let response = HarnessSessionService::new().handle(request(
7942                1,
7943                "harness.v1.approvals.list",
7944                json!({"harness": harness}),
7945            ));
7946            assert!(response.get("error").is_none(), "{harness}: {response:#}");
7947        }
7948    }
7949
7950    /// The method is advertised, its SDK operation resolves it, and the
7951    /// registry reports the concept as observed for every harness whose
7952    /// runtime door can carry a request.
7953    #[test]
7954    fn approvals_list_is_an_advertised_method_and_an_observed_tier() {
7955        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.list"));
7956        assert_eq!(
7957            SdkOperation::from_method("harness.v1.approvals.list"),
7958            Some(SdkOperation::ApprovalsList)
7959        );
7960        let registry = harness_support_registry();
7961        for id in [
7962            HarnessId::HERMES,
7963            HarnessId::OPENCLAW,
7964            HarnessId::CODEX,
7965            // ORC-2: the Claude Code door answers `can_use_tool`, so its
7966            // pending_request concept joins the other driven doors.
7967            HarnessId::CLAUDE_CODE,
7968        ] {
7969            let concept = registry
7970                .harnesses
7971                .iter()
7972                .find(|harness| harness.id.as_str() == id)
7973                .unwrap()
7974                .orchestration
7975                .concepts
7976                .iter()
7977                .find(|concept| concept.concept == "pending_request")
7978                .unwrap();
7979            assert_eq!(concept.observed, crate::ImplementationKind::BuiltIn, "{id}");
7980            assert!(concept
7981                .methods
7982                .iter()
7983                .any(|method| method == "harness.v1.approvals.list"));
7984        }
7985    }
7986
7987    // ---- ORCH-20: `harness.v1.approvals.resolve` -------------------------
7988
7989    async fn resolve(service: &mut HarnessSessionService, params: Value) -> Value {
7990        service
7991            .handle_async(request(3, "harness.v1.approvals.resolve", params))
7992            .await
7993    }
7994
7995    /// dev/01: the whole loop on a driven runtime — list one pending row,
7996    /// answer it by ROW ID with one uniform decision, and see it gone. The
7997    /// door receives its own ACP envelope carrying the option it enumerated.
7998    #[tokio::test]
7999    async fn a_listed_row_resolves_with_one_uniform_decision_and_then_is_gone() {
8000        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8001        let mut service = HarnessSessionService::new();
8002        service.runtimes.insert(
8003            "runtime-1".into(),
8004            requesting_runtime(
8005                HarnessId::HERMES,
8006                vec![permission_event(7, "rm -rf build")],
8007                answered.clone(),
8008            ),
8009        );
8010        service.poll_runtimes().await;
8011
8012        let rows = approvals(&mut service, json!({}));
8013        assert_eq!(rows[0]["id"], "runtime-1/7");
8014
8015        let response = resolve(
8016            &mut service,
8017            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8018        )
8019        .await;
8020        assert!(response.get("error").is_none(), "{response:#}");
8021        assert_eq!(
8022            response["result"],
8023            json!({
8024                "id": "runtime-1/7",
8025                "decision": "allow_once",
8026                "option_id": "allow_once",
8027                "resolved": true,
8028            }),
8029        );
8030        // The harness's own door was called with its own envelope.
8031        assert_eq!(
8032            answered
8033                .lock()
8034                .unwrap_or_else(std::sync::PoisonError::into_inner)
8035                .as_slice(),
8036            &[json!({
8037                "request_id": 7,
8038                "response": {"outcome": {"outcome": "selected", "optionId": "allow_once"}},
8039            })],
8040        );
8041        // And the row is gone, the same way `runtimes.respond` drops it.
8042        assert_eq!(
8043            approvals(&mut service, json!({})).as_array().map(Vec::len),
8044            Some(0),
8045        );
8046        // Answering it twice is an honest miss, not a silent success.
8047        let response = resolve(
8048            &mut service,
8049            json!({"id": "runtime-1/7", "decision": "allow_once"}),
8050        )
8051        .await;
8052        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8053    }
8054
8055    /// dev/01: deny travels the same path and picks the option the request
8056    /// itself classified as a refusal.
8057    #[tokio::test]
8058    async fn deny_selects_the_requests_own_reject_option() {
8059        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8060        let mut service = HarnessSessionService::new();
8061        service.runtimes.insert(
8062            "runtime-1".into(),
8063            requesting_runtime(
8064                HarnessId::HERMES,
8065                vec![permission_event(11, "git push --force")],
8066                answered.clone(),
8067            ),
8068        );
8069        service.poll_runtimes().await;
8070
8071        let response = resolve(
8072            &mut service,
8073            json!({"id": "runtime-1/11", "decision": "deny"}),
8074        )
8075        .await;
8076        assert!(response.get("error").is_none(), "{response:#}");
8077        // `deny` is the optionId whose ACP `kind` is `reject_once`.
8078        assert_eq!(response["result"]["option_id"], "deny");
8079        assert_eq!(
8080            answered
8081                .lock()
8082                .unwrap_or_else(std::sync::PoisonError::into_inner)[0]["response"],
8083            json!({"outcome": {"outcome": "selected", "optionId": "deny"}}),
8084        );
8085        assert_eq!(
8086            approvals(&mut service, json!({})).as_array().map(Vec::len),
8087            Some(0),
8088        );
8089    }
8090
8091    /// dev/01: a decision this request does not offer is refused by name,
8092    /// listing the ones it does — never silently downgraded to a neighbour.
8093    #[tokio::test]
8094    async fn a_decision_the_request_does_not_offer_is_refused_with_the_offered_ones() {
8095        let answered = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
8096        let mut service = HarnessSessionService::new();
8097        let mut event = permission_event(3, "rm -rf build");
8098        // A request offering only allow-once and deny, as hermes 0.21.0's
8099        // edit-approval layer raises one.
8100        event.payload["params"]["options"] = json!([
8101            {"optionId": "allow_once", "name": "Allow edit", "kind": "allow_once"},
8102            {"optionId": "deny", "name": "Deny", "kind": "reject_once"},
8103        ]);
8104        service.runtimes.insert(
8105            "runtime-1".into(),
8106            requesting_runtime(HarnessId::HERMES, vec![event], answered.clone()),
8107        );
8108        service.poll_runtimes().await;
8109
8110        let response = resolve(
8111            &mut service,
8112            json!({"id": "runtime-1/3", "decision": "allow_always"}),
8113        )
8114        .await;
8115        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8116        let message = response["error"]["message"].as_str().unwrap();
8117        assert!(message.contains("allow_always"), "{message}");
8118        assert!(message.contains("allow_once, deny"), "{message}");
8119        // Nothing was sent, and the request is still waiting for an answer.
8120        assert!(answered
8121            .lock()
8122            .unwrap_or_else(std::sync::PoisonError::into_inner)
8123            .is_empty());
8124        assert_eq!(
8125            approvals(&mut service, json!({})).as_array().map(Vec::len),
8126            Some(1),
8127        );
8128    }
8129
8130    /// dev/01: supercode's own queued subagent row is addressable but not
8131    /// answerable through this door — it is the parent's audit copy of a
8132    /// request its own handler answers. Refused by name, never a no-op.
8133    #[tokio::test]
8134    async fn a_queued_subagent_row_is_refused_by_name_rather_than_silently_answered() {
8135        let queue = std::sync::Arc::new(std::sync::Mutex::new(vec![
8136            crate::subagents::QueuedApproval {
8137                child_agent_id: "child-7".into(),
8138                tool: "shell".into(),
8139                subject: Some("cargo publish --dry-run".into()),
8140                queued_at_ms: 1,
8141                outcome: None,
8142            },
8143        ]));
8144        let mut service = HarnessSessionService::new();
8145        service.observe_subagent_approvals(queue.clone());
8146        let row = approvals(&mut service, json!({}))[0]["id"]
8147            .as_str()
8148            .unwrap()
8149            .to_string();
8150        assert_eq!(row, "supercode/subagent/child-7/1/0");
8151
8152        let response = resolve(&mut service, json!({"id": row, "decision": "allow_once"})).await;
8153        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8154        let message = response["error"]["message"].as_str().unwrap();
8155        assert!(message.contains("queued subagent record"), "{message}");
8156        assert!(message.contains("request"), "{message}");
8157        // The audit record is untouched: nothing pretended to answer it.
8158        assert!(queue
8159            .lock()
8160            .unwrap_or_else(std::sync::PoisonError::into_inner)[0]
8161            .outcome
8162            .is_none());
8163    }
8164
8165    /// An id nobody is holding, and a call that names no decision at all,
8166    /// both fail with a message that says why.
8167    #[tokio::test]
8168    async fn an_unknown_row_and_a_missing_decision_are_both_named() {
8169        let mut service = HarnessSessionService::new();
8170        let response = resolve(
8171            &mut service,
8172            json!({"id": "runtime-9/4", "decision": "deny"}),
8173        )
8174        .await;
8175        assert_eq!(response["error"]["code"], json!(-32602), "{response:#}");
8176        assert!(response["error"]["message"]
8177            .as_str()
8178            .unwrap()
8179            .contains("runtime-9/4"));
8180
8181        let response = resolve(&mut service, json!({"id": "runtime-9/4"})).await;
8182        let message = response["error"]["message"].as_str().unwrap();
8183        assert!(
8184            message.contains("allow_once | allow_always | deny"),
8185            "{message}"
8186        );
8187
8188        let response = resolve(
8189            &mut service,
8190            json!({"id": "runtime-9/4", "decision": "deny", "option_id": "deny"}),
8191        )
8192        .await;
8193        assert!(response["error"]["message"]
8194            .as_str()
8195            .unwrap()
8196            .contains("not both"));
8197    }
8198
8199    /// The method is advertised, its SDK operation resolves it, and every
8200    /// harness whose runtime door can carry a request reports it on the
8201    /// CONTROLLED tier beside `runtimes.respond`.
8202    #[test]
8203    fn approvals_resolve_is_an_advertised_method_and_a_controlled_tier() {
8204        assert!(HARNESS_SERVICE_METHODS.contains(&"harness.v1.approvals.resolve"));
8205        assert_eq!(
8206            SdkOperation::from_method("harness.v1.approvals.resolve"),
8207            Some(SdkOperation::ApprovalsResolve)
8208        );
8209        assert_eq!(
8210            SdkOperation::ApprovalsResolve.action_name(),
8211            "approvals_resolve"
8212        );
8213        let registry = harness_support_registry();
8214        for id in [
8215            HarnessId::HERMES,
8216            HarnessId::OPENCLAW,
8217            HarnessId::CODEX,
8218            // ORC-2: the Claude Code door answers `can_use_tool`, so its
8219            // pending_request concept joins the other driven doors.
8220            HarnessId::CLAUDE_CODE,
8221        ] {
8222            let concept = registry
8223                .harnesses
8224                .iter()
8225                .find(|harness| harness.id.as_str() == id)
8226                .unwrap()
8227                .orchestration
8228                .concepts
8229                .iter()
8230                .find(|concept| concept.concept == "pending_request")
8231                .unwrap();
8232            assert_eq!(
8233                concept.controlled,
8234                crate::ImplementationKind::BuiltIn,
8235                "{id}"
8236            );
8237            assert!(
8238                concept
8239                    .methods
8240                    .iter()
8241                    .any(|method| method == "harness.v1.approvals.resolve"),
8242                "{id}"
8243            );
8244        }
8245    }
8246
8247    #[test]
8248    fn capabilities_are_explicit_and_versioned() {
8249        let mut service = HarnessSessionService::new();
8250        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
8251        assert_eq!(response["result"]["version"], HARNESS_SERVICE_VERSION);
8252        assert_eq!(
8253            response["result"]["sdk"]["schema_version"],
8254            crate::SDK_SCHEMA_VERSION
8255        );
8256        assert_eq!(
8257            response["result"]["sdk"]["operations"]
8258                .as_array()
8259                .unwrap()
8260                .len(),
8261            SdkOperation::ALL.len()
8262        );
8263        assert_eq!(
8264            response["result"]["harnesses"].as_array().unwrap().len(),
8265            11
8266        );
8267        assert!(response["result"]["harnesses"]
8268            .as_array()
8269            .unwrap()
8270            .iter()
8271            .any(|harness| harness == HarnessId::GROK));
8272        assert!(response["result"]["harnesses"]
8273            .as_array()
8274            .unwrap()
8275            .iter()
8276            .any(|harness| harness == HarnessId::GOOSE));
8277    }
8278
8279    #[test]
8280    fn handshake_health_uses_protocol_liveness_not_stderr_severity() {
8281        let noisy_stderr = crate::HarnessEvent {
8282            sequence: None,
8283            kind: "transport_stderr".into(),
8284            payload: json!({"line": "ERROR optional worker AuthorizationRequired"}),
8285        };
8286        assert_eq!(handshake_event_failure(&noisy_stderr), None);
8287
8288        let closed = crate::HarnessEvent {
8289            sequence: None,
8290            kind: "transport_closed".into(),
8291            payload: json!({}),
8292        };
8293        assert!(handshake_event_failure(&closed).is_some());
8294    }
8295
8296    #[tokio::test]
8297    async fn runtime_eof_is_notified_and_removed_for_raw_and_explicit_close() {
8298        let mut service = HarnessSessionService::new();
8299        service
8300            .runtimes
8301            .insert("raw-eof".into(), ending_runtime(None));
8302        service.runtimes.insert(
8303            "explicit-close".into(),
8304            ending_runtime(Some(HarnessEvent {
8305                sequence: None,
8306                kind: "transport_closed".into(),
8307                payload: json!({"message": "native transport exited"}),
8308            })),
8309        );
8310
8311        let notifications = service.poll_runtimes().await;
8312
8313        assert_eq!(notifications.len(), 2);
8314        assert!(notifications
8315            .iter()
8316            .all(|notification| { notification["params"]["event"]["kind"] == "transport_closed" }));
8317        assert!(notifications.iter().all(|notification| {
8318            notification["params"]["session_id"] == "ending-session"
8319                && notification["params"]["connection"].is_string()
8320        }));
8321        let mut sequences = notifications
8322            .iter()
8323            .filter_map(|notification| notification["params"]["sequence"].as_u64())
8324            .collect::<Vec<_>>();
8325        sequences.sort_unstable();
8326        assert_eq!(sequences, vec![1, 2]);
8327        assert!(service.runtimes.is_empty());
8328    }
8329
8330    #[test]
8331    fn support_report_and_grok_default_binding_share_the_registry() {
8332        let mut service = HarnessSessionService::new();
8333        let response = service.handle(request(1, "harness.v1.support.report", json!({})));
8334        assert_eq!(response["result"]["schema"], crate::SUPPORT_REGISTRY_SCHEMA);
8335        let params = RuntimeBackendParams {
8336            harness: HarnessId::from(HarnessId::GROK),
8337            protocol: None,
8338            launch: None,
8339            base_url: None,
8340            policy: RuntimePolicy::Default,
8341        };
8342        let backend = match runtime_backend(&params) {
8343            Ok(backend) => backend,
8344            Err(_) => panic!("Grok should bind through its registered ACP launch"),
8345        };
8346        assert_eq!(backend.harness().as_str(), HarnessId::GROK);
8347        assert!(backend.capabilities().start_session);
8348        let registered = harness_support_registry()
8349            .harnesses
8350            .into_iter()
8351            .find(|harness| harness.id.as_str() == HarnessId::GROK)
8352            .and_then(|harness| harness.runtime.default_launch)
8353            .unwrap();
8354        assert!(!registered
8355            .arguments
8356            .iter()
8357            .any(|argument| argument == "--always-approve"));
8358        assert!(runtime_launch(&params).is_none());
8359
8360        let yolo = RuntimeBackendParams {
8361            policy: RuntimePolicy::Yolo,
8362            ..params
8363        };
8364        assert!(runtime_launch(&yolo)
8365            .unwrap()
8366            .arguments
8367            .iter()
8368            .any(|argument| argument == "--always-approve"));
8369
8370        let mismatched_protocol = RuntimeBackendParams {
8371            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8372            protocol: Some("acp".into()),
8373            launch: None,
8374            base_url: None,
8375            policy: RuntimePolicy::Default,
8376        };
8377        assert!(runtime_backend(&mismatched_protocol).is_err());
8378    }
8379
8380    #[test]
8381    fn load_follow_and_unfollow_share_the_same_locator() {
8382        let mut service = HarnessSessionService::new();
8383        let locator = pi_locator();
8384        let loaded = service.handle(request(
8385            1,
8386            "harness.v1.sessions.load",
8387            json!({"locator": locator}),
8388        ));
8389        assert_eq!(
8390            loaded["result"]["session"]["session_id"],
8391            locator.session_id
8392        );
8393
8394        let followed = service.handle(request(
8395            2,
8396            "harness.v1.sessions.follow",
8397            json!({"locator": locator}),
8398        ));
8399        assert_eq!(followed["result"]["subscription"], "sub-1");
8400        assert_eq!(followed["result"]["initial"]["type"], "session_snapshot");
8401        assert!(service.poll().is_empty());
8402
8403        let unfollowed = service.handle(request(
8404            3,
8405            "harness.v1.sessions.unfollow",
8406            json!({"subscription": "sub-1"}),
8407        ));
8408        assert_eq!(unfollowed["result"]["removed"], true);
8409    }
8410
8411    #[test]
8412    fn bounded_read_view_excludes_subagents_and_keeps_only_the_tail() {
8413        let temp = std::env::temp_dir().join(format!(
8414            "supercode-bounded-view-{}-{}",
8415            std::process::id(),
8416            generated_session_id()
8417        ));
8418        let path = temp.join("parent.jsonl");
8419        let subagents = temp.join("parent/subagents");
8420        std::fs::create_dir_all(&subagents).unwrap();
8421        let long_last = "x".repeat(300);
8422        let parent_records = [
8423            json!({"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":"first"}}),
8424            json!({"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"middle"}]}}),
8425            json!({"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":long_last}}),
8426        ];
8427        std::fs::write(
8428            &path,
8429            format!(
8430                "{}\n",
8431                parent_records
8432                    .iter()
8433                    .map(Value::to_string)
8434                    .collect::<Vec<_>>()
8435                    .join("\n")
8436            ),
8437        )
8438        .unwrap();
8439        std::fs::write(
8440            subagents.join("agent-child.jsonl"),
8441            concat!(
8442                r#"{"type":"user","uuid":"cu","parentUuid":null,"agentId":"child","message":{"role":"user","content":"child work"}}"#,
8443                "\n",
8444            ),
8445        )
8446        .unwrap();
8447        let locator = SessionLocator {
8448            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8449            session_id: "parent".into(),
8450            storage: StorageLocator::File { path },
8451        };
8452        let mut service = HarnessSessionService::new();
8453
8454        let complete = service.handle(request(
8455            1,
8456            "harness.v1.sessions.load",
8457            json!({"locator": locator}),
8458        ));
8459        assert_eq!(
8460            complete["result"]["session"]["subagents"]
8461                .as_array()
8462                .unwrap()
8463                .len(),
8464            1
8465        );
8466
8467        let bounded = service.handle(request(
8468            2,
8469            "harness.v1.sessions.load",
8470            json!({
8471                "locator": locator,
8472                "view": {
8473                    "tail_messages": 1,
8474                    "max_message_chars": 256,
8475                    "include_subagents": false
8476                },
8477            }),
8478        ));
8479        let session = &bounded["result"]["session"];
8480        assert!(session["subagents"].as_array().unwrap().is_empty());
8481        assert_eq!(session["messages"].as_array().unwrap().len(), 1);
8482        assert_eq!(
8483            session["messages"][0]["content"],
8484            format!("{}\n…", "x".repeat(256))
8485        );
8486
8487        let followed = service.handle(request(
8488            3,
8489            "harness.v1.sessions.follow",
8490            json!({
8491                "locator": locator,
8492                "view": {
8493                    "tail_messages": 1,
8494                    "max_message_chars": 256,
8495                    "include_subagents": false
8496                },
8497            }),
8498        ));
8499        let initial = &followed["result"]["initial"]["session"];
8500        assert!(initial["subagents"].as_array().unwrap().is_empty());
8501        assert_eq!(initial["messages"].as_array().unwrap().len(), 1);
8502
8503        let _ = std::fs::remove_dir_all(&temp);
8504    }
8505
8506    #[test]
8507    fn forty_megabyte_display_load_is_bounded_and_prompt() {
8508        let temp = std::env::temp_dir().join(format!(
8509            "supercode-large-display-view-{}-{}",
8510            std::process::id(),
8511            generated_session_id()
8512        ));
8513        std::fs::create_dir_all(&temp).unwrap();
8514        let path = temp.join("rollout.jsonl");
8515        let mut file = std::io::BufWriter::new(std::fs::File::create(&path).unwrap());
8516        writeln!(
8517            file,
8518            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
8519        )
8520        .unwrap();
8521        let padding = "x".repeat(80 * 1024);
8522        for index in 0..512 {
8523            let marker = if index == 0 {
8524                "OLDEST-SHOULD-NOT-LOAD"
8525            } else if index == 511 {
8526                "LATEST-MUST-LOAD"
8527            } else {
8528                "bulk"
8529            };
8530            writeln!(
8531                file,
8532                "{}",
8533                json!({
8534                    "timestamp": "2026-01-01T00:00:01Z",
8535                    "type": "response_item",
8536                    "payload": {
8537                        "type": "message",
8538                        "role": "assistant",
8539                        "content": [{"type": "output_text", "text": format!("{marker}:{padding}")}],
8540                    },
8541                })
8542            )
8543            .unwrap();
8544        }
8545        file.flush().unwrap();
8546        drop(file);
8547        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
8548
8549        let locator = SessionLocator {
8550            harness: HarnessId::from(HarnessId::CODEX),
8551            session_id: "large-display".into(),
8552            storage: StorageLocator::File { path },
8553        };
8554        let started = Instant::now();
8555        let response = HarnessSessionService::new().handle(request(
8556            1,
8557            "harness.v1.sessions.load",
8558            json!({
8559                "locator": locator,
8560                "view": {
8561                    "tail_messages": 500,
8562                    "max_message_chars": 1024,
8563                    "include_subagents": false,
8564                    "display_history": true,
8565                },
8566            }),
8567        ));
8568        let elapsed = started.elapsed();
8569        let wire = response.to_string();
8570        eprintln!(
8571            "bounded 40 MiB display load: {elapsed:?}, {} response bytes",
8572            wire.len()
8573        );
8574        assert!(response.get("error").is_none(), "{response:#}");
8575        assert!(wire.contains("LATEST-MUST-LOAD"));
8576        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
8577        assert!(
8578            wire.len() < 2 * 1024 * 1024,
8579            "bounded wire was {} bytes",
8580            wire.len()
8581        );
8582        assert!(
8583            elapsed.as_secs_f64() < 3.0,
8584            "bounded 40 MiB load took {elapsed:?}"
8585        );
8586
8587        let _ = std::fs::remove_dir_all(&temp);
8588    }
8589
8590    #[test]
8591    fn forty_megabyte_goose_store_display_load_reads_only_the_tail() {
8592        let temp = std::env::temp_dir().join(format!(
8593            "supercode-large-goose-view-{}-{}",
8594            std::process::id(),
8595            generated_session_id()
8596        ));
8597        std::fs::create_dir_all(&temp).unwrap();
8598        let path = temp.join("sessions.db");
8599        let connection = rusqlite::Connection::open(&path).unwrap();
8600        connection
8601            .execute_batch(
8602                "CREATE TABLE sessions (
8603                    id TEXT PRIMARY KEY, name TEXT NOT NULL, working_dir TEXT NOT NULL,
8604                    created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
8605                    session_type TEXT NOT NULL, extension_data TEXT,
8606                    goose_mode TEXT NOT NULL, provider_name TEXT, model_config_json TEXT,
8607                    archived_at TEXT
8608                 );
8609                 CREATE TABLE messages (
8610                    id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, message_id TEXT,
8611                    role TEXT NOT NULL, content_json TEXT NOT NULL,
8612                    created_timestamp INTEGER NOT NULL, metadata_json TEXT
8613                 );",
8614            )
8615            .unwrap();
8616        connection
8617            .execute(
8618                "INSERT INTO sessions VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL)",
8619                rusqlite::params![
8620                    "goose-large",
8621                    "Large Goose session",
8622                    "/tmp",
8623                    "2026-01-01 00:00:00",
8624                    "2026-01-01 00:00:02",
8625                    "user",
8626                    "{}",
8627                    "auto",
8628                    "anthropic",
8629                    r#"{"model_name":"claude-sonnet"}"#,
8630                ],
8631            )
8632            .unwrap();
8633        let old_content = serde_json::to_string(&vec![json!({
8634            "type": "text",
8635            "text": format!("OLDEST-SHOULD-NOT-LOAD:{}", "x".repeat(40 * 1024 * 1024)),
8636        })])
8637        .unwrap();
8638        connection
8639            .execute(
8640                "INSERT INTO messages VALUES (1, ?1, 'old', 'user', ?2, 1, '{}')",
8641                rusqlite::params!["goose-large", old_content],
8642            )
8643            .unwrap();
8644        connection
8645            .execute(
8646                "INSERT INTO messages VALUES (2, ?1, 'new', 'assistant', ?2, 2, '{}')",
8647                rusqlite::params![
8648                    "goose-large",
8649                    r#"[{"type":"text","text":"LATEST-MUST-LOAD"}]"#
8650                ],
8651            )
8652            .unwrap();
8653        drop(connection);
8654        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
8655
8656        let locator = SessionLocator {
8657            harness: HarnessId::from(HarnessId::GOOSE),
8658            session_id: "goose-large".into(),
8659            storage: StorageLocator::Sqlite {
8660                path,
8661                selector: "goose-large".into(),
8662            },
8663        };
8664        let started = Instant::now();
8665        let response = HarnessSessionService::new().handle(request(
8666            1,
8667            "harness.v1.sessions.load",
8668            json!({
8669                "locator": locator,
8670                "view": {
8671                    "tail_messages": 1,
8672                    "max_message_chars": 1024,
8673                    "include_subagents": false,
8674                    "display_history": true,
8675                },
8676            }),
8677        ));
8678        let elapsed = started.elapsed();
8679        let wire = response.to_string();
8680        eprintln!(
8681            "bounded 40 MiB Goose display load: {elapsed:?}, {} response bytes",
8682            wire.len()
8683        );
8684        assert!(response.get("error").is_none(), "{response:#}");
8685        assert!(wire.contains("LATEST-MUST-LOAD"));
8686        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
8687        assert!(
8688            wire.len() < 64 * 1024,
8689            "bounded wire was {} bytes",
8690            wire.len()
8691        );
8692        assert!(
8693            elapsed.as_secs_f64() < 1.0,
8694            "bounded Goose load took {elapsed:?}"
8695        );
8696
8697        let _ = std::fs::remove_dir_all(&temp);
8698    }
8699
8700    #[test]
8701    fn display_view_keeps_codex_assistant_history_across_compaction() {
8702        let temp = std::env::temp_dir().join(format!(
8703            "supercode-codex-display-view-{}-{}",
8704            std::process::id(),
8705            generated_session_id()
8706        ));
8707        std::fs::create_dir_all(&temp).unwrap();
8708        let path = temp.join("rollout.jsonl");
8709        std::fs::write(
8710            &path,
8711            concat!(
8712                r#"{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"codex-display","cwd":"/tmp"}}"#,
8713                "\n",
8714                r#"{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]}}"#,
8715                "\n",
8716                r#"{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"old answer"}]}}"#,
8717                "\n",
8718                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"}]}}"#,
8719                "\n",
8720                r#"{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"new prompt"}]}}"#,
8721                "\n",
8722                r#"{"timestamp":"2026-01-01T00:00:05Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"new answer"}]}}"#,
8723                "\n",
8724            ),
8725        )
8726        .unwrap();
8727        let locator = SessionLocator {
8728            harness: HarnessId::from(HarnessId::CODEX),
8729            session_id: "codex-display".into(),
8730            storage: StorageLocator::File { path },
8731        };
8732        let mut service = HarnessSessionService::new();
8733
8734        let continuation = service.handle(request(
8735            1,
8736            "harness.v1.sessions.load",
8737            json!({"locator": locator}),
8738        ));
8739        let continuation_text = continuation["result"]["session"]["messages"].to_string();
8740        assert!(!continuation_text.contains("old answer"));
8741
8742        let display = service.handle(request(
8743            2,
8744            "harness.v1.sessions.load",
8745            json!({
8746                "locator": locator,
8747                "view": {
8748                    "tail_messages": 10,
8749                    "include_subagents": false,
8750                    "display_history": true,
8751                },
8752            }),
8753        ));
8754        let display_text = display["result"]["session"]["messages"].to_string();
8755        assert!(display_text.contains("old prompt"));
8756        assert!(display_text.contains("old answer"));
8757        assert!(display_text.contains("new prompt"));
8758        assert!(display_text.contains("new answer"));
8759
8760        let _ = std::fs::remove_dir_all(&temp);
8761    }
8762
8763    #[test]
8764    fn indexed_claude_windows_match_the_existing_wire_projection() {
8765        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8766            .join("tests/fixtures/claude_code_session.jsonl");
8767        let locator = SessionLocator {
8768            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
8769            session_id: "fixture".into(),
8770            storage: StorageLocator::File { path },
8771        };
8772        let full = load_session(&locator).unwrap();
8773        for inline_media in [InlineMediaMode::Full, InlineMediaMode::Metadata] {
8774            for offset in [0, 1, full.messages.len(), usize::MAX] {
8775                for limit in [0, 1, 3, usize::MAX] {
8776                    let options = SessionLoadOptions {
8777                        include_subagents: Some(false),
8778                        inline_media,
8779                        message_offset: Some(offset),
8780                        message_limit: Some(limit),
8781                        ..Default::default()
8782                    };
8783                    let expected = projected_session_result(&full, &options);
8784                    assert_eq!(
8785                        indexed_claude_window(&locator, &options).unwrap().unwrap(),
8786                        expected
8787                    );
8788                }
8789            }
8790            for tail in [0, 1, 3, usize::MAX] {
8791                let options = SessionLoadOptions {
8792                    include_subagents: Some(false),
8793                    inline_media,
8794                    message_tail: Some(tail),
8795                    ..Default::default()
8796                };
8797                assert_eq!(
8798                    indexed_claude_window(&locator, &options).unwrap().unwrap(),
8799                    projected_session_result(&full, &options)
8800                );
8801            }
8802        }
8803    }
8804
8805    #[test]
8806    fn load_supports_bounded_windows_and_media_metadata() {
8807        let mut service = HarnessSessionService::new();
8808        let locator = pi_locator();
8809        let bounded = service.handle(request(
8810            1,
8811            "harness.v1.sessions.load",
8812            json!({
8813                "locator": locator,
8814                "options": {
8815                    "include_subagents": false,
8816                    "message_limit": 2,
8817                    "message_offset": 1
8818                }
8819            }),
8820        ));
8821        assert_eq!(bounded["result"]["window"]["offset"], 1);
8822        assert_eq!(bounded["result"]["window"]["returned"], 2);
8823        assert!(bounded["result"]["summary"]["first_message"].is_object());
8824        assert!(bounded["result"]["summary"]["last_message"].is_object());
8825        assert_eq!(
8826            bounded["result"]["session"]["messages"]
8827                .as_array()
8828                .unwrap()
8829                .len(),
8830            2
8831        );
8832        assert!(bounded["result"]["session"]["subagents"]
8833            .as_array()
8834            .unwrap()
8835            .is_empty());
8836
8837        let tail = service.handle(request(
8838            2,
8839            "harness.v1.sessions.load",
8840            json!({"locator": locator, "options": {"message_tail": 1}}),
8841        ));
8842        assert_eq!(tail["result"]["window"]["returned"], 1);
8843        assert_eq!(tail["result"]["window"]["has_more"], true);
8844        assert_eq!(tail["result"]["window"]["has_older"], true);
8845        assert!(tail["result"]["window"]["older_items"].as_u64().unwrap() > 0);
8846        assert!(tail["result"]["summary"]["first_message"].is_object());
8847
8848        let metadata_only = service.handle(request(
8849            3,
8850            "harness.v1.sessions.load",
8851            json!({"locator": locator, "options": {"inline_media": "metadata"}}),
8852        ));
8853        assert!(metadata_only["result"]["session"]
8854            .to_string()
8855            .contains("media_reference"));
8856        assert!(!metadata_only["result"]["session"]
8857            .to_string()
8858            .contains("data:image/"));
8859    }
8860
8861    #[test]
8862    fn import_translate_branch_and_handoff_use_typed_artifacts() {
8863        let mut service = HarnessSessionService::new();
8864        let locator = pi_locator();
8865        let translated = service.handle(request(
8866            1,
8867            "harness.v1.sessions.translate",
8868            json!({"locator": locator, "target_harness": "grok"}),
8869        ));
8870        assert_eq!(translated["result"]["artifact"]["source_harness"], "pi");
8871        assert_eq!(translated["result"]["artifact"]["target_harness"], "grok");
8872        assert!(translated["result"]["artifact"]["content"]
8873            .as_str()
8874            .is_some_and(|content| !content.is_empty()));
8875
8876        for target in ["opencode", "open-code"] {
8877            let opencode = service.handle(request(
8878                6,
8879                "harness.v1.sessions.translate",
8880                json!({"locator": locator, "target_harness": target}),
8881            ));
8882            assert_eq!(opencode["result"]["artifact"]["target_harness"], "opencode");
8883        }
8884        let goose = service.handle(request(
8885            7,
8886            "harness.v1.sessions.translate",
8887            json!({"locator": locator, "target_harness": "goose"}),
8888        ));
8889        assert_eq!(goose["result"]["artifact"]["target_harness"], "goose");
8890        assert!(serde_json::from_str::<Value>(
8891            goose["result"]["artifact"]["content"].as_str().unwrap()
8892        )
8893        .unwrap()["conversation"]
8894            .is_array());
8895
8896        let imported = service.handle(request(
8897            2,
8898            "harness.v1.sessions.import",
8899            json!({
8900                "source_harness": "grok",
8901                "content": translated["result"]["artifact"]["content"],
8902            }),
8903        ));
8904        assert_eq!(imported["result"]["session"]["source"], "grok");
8905
8906        let branched = service.handle(request(
8907            3,
8908            "harness.v1.sessions.branch",
8909            json!({"locator": locator, "target_harness": "codex"}),
8910        ));
8911        assert_eq!(branched["result"]["parent"]["harness"], "pi");
8912        assert!(branched["result"]["bootstrap_prompt"]
8913            .as_str()
8914            .unwrap()
8915            .contains("frozen parent transcript"));
8916        assert_eq!(branched["result"]["artifact"]["target_harness"], "codex");
8917
8918        let handoff = service.handle(request(
8919            4,
8920            "harness.v1.sessions.handoff",
8921            json!({"locator": locator, "target_harness": "pi", "cwd": "/tmp/project"}),
8922        ));
8923        assert_eq!(handoff["result"]["launch"]["program"], "pi");
8924        assert_eq!(handoff["result"]["launch"]["cwd"], "/tmp/project");
8925        assert_eq!(handoff["result"]["requires_materialization"], true);
8926
8927        let goose_handoff = service.handle(request(
8928            8,
8929            "harness.v1.sessions.handoff",
8930            json!({"locator": locator, "target_harness": "goose", "cwd": "/tmp/project"}),
8931        ));
8932        assert_eq!(goose_handoff["result"]["launch"]["program"], "goose");
8933        assert_eq!(
8934            goose_handoff["result"]["materialize"]["arguments"],
8935            json!(["session", "import", "{artifact_path}"])
8936        );
8937
8938        let resumed = service.handle(request(
8939            5,
8940            "harness.v1.sessions.resume_instructions",
8941            json!({"locator": locator, "cwd": "/tmp/project", "policy": "yolo"}),
8942        ));
8943        assert_eq!(resumed["result"]["launch"]["program"], "pi");
8944        assert_eq!(resumed["result"]["launch"]["arguments"][0], "--approve");
8945    }
8946
8947    #[test]
8948    fn reduce_persists_and_reloads_a_byte_exact_reversible_bundle() {
8949        let temp = std::env::temp_dir().join(format!(
8950            "supercode-service-reduce-{}-{}",
8951            std::process::id(),
8952            generated_session_id()
8953        ));
8954        let source_path = temp.join("source.jsonl");
8955        let store_root = temp.join("store");
8956        std::fs::create_dir_all(&temp).unwrap();
8957
8958        let mut records = vec![json!({
8959            "timestamp": "2026-01-01T00:00:00Z",
8960            "type": "session_meta",
8961            "payload": {"id": "codex-reduce", "cwd": "/tmp/project"},
8962        })];
8963        for turn in 0..16 {
8964            records.push(json!({
8965                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 1),
8966                "type": "response_item",
8967                "payload": {
8968                    "type": "message",
8969                    "role": "user",
8970                    "content": [{
8971                        "type": "input_text",
8972                        "text": format!("request {turn}: {}", "context ".repeat(80)),
8973                    }],
8974                },
8975            }));
8976            records.push(json!({
8977                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 2),
8978                "type": "response_item",
8979                "payload": {
8980                    "type": "message",
8981                    "role": "assistant",
8982                    "content": [{
8983                        "type": "output_text",
8984                        "text": format!("answer {turn}: {}", "implementation detail ".repeat(80)),
8985                    }],
8986                },
8987            }));
8988        }
8989        let source = format!(
8990            "{}\n",
8991            records
8992                .iter()
8993                .map(Value::to_string)
8994                .collect::<Vec<_>>()
8995                .join("\n")
8996        );
8997        std::fs::write(&source_path, &source).unwrap();
8998        let locator = SessionLocator {
8999            harness: HarnessId::from(HarnessId::CODEX),
9000            session_id: "codex-reduce".into(),
9001            storage: StorageLocator::File {
9002                path: source_path.clone(),
9003            },
9004        };
9005        let original = load_session(&locator).unwrap();
9006        let mut service =
9007            HarnessSessionService::new().with_reduction_store_root(store_root.clone());
9008
9009        let response = service.handle(request(
9010            1,
9011            "harness.v1.sessions.reduce",
9012            json!({
9013                "locator": locator,
9014                "target_harness": "claude-code",
9015                "keep_last": 4,
9016            }),
9017        ));
9018        assert!(response.get("error").is_none(), "{response:#}");
9019        let receipt = &response["result"]["receipt"];
9020        assert_eq!(receipt["source_harness"], "codex");
9021        assert_eq!(receipt["target_harness"], "claude-code");
9022        assert_eq!(receipt["verified"], true);
9023        assert_eq!(receipt["reversible"], true);
9024        assert!(receipt["reductions"].as_u64().unwrap() > 0);
9025        assert!(
9026            receipt["source_tokens"].as_u64().unwrap()
9027                > receipt["reduced_tokens"].as_u64().unwrap()
9028        );
9029        assert!(receipt["ratio"].as_f64().unwrap() > 1.0);
9030        assert!(response["result"]["bootstrap_prompt"]
9031            .as_str()
9032            .unwrap()
9033            .contains("Do not guess hidden content"));
9034
9035        let rescue_id = receipt["id"].as_str().unwrap();
9036        let store = crate::SessionStore::open(&store_root).unwrap();
9037        let sidecar =
9038            Session::from_sidecar_str(&store.load_sidecar(rescue_id).unwrap().unwrap()).unwrap();
9039        let log = store.load_reduction_log(rescue_id).unwrap().unwrap();
9040        let persisted_view = parse_messages_jsonl(&store.load(rescue_id).unwrap()).unwrap();
9041        let policy = reduce::ReductionPolicy {
9042            clear_turns_older_than: Some(4),
9043            ..Default::default()
9044        };
9045        let (restamped_view, reapplied_log) =
9046            reduce::project_messages(&sidecar.messages, &policy, &log);
9047        assert_eq!(
9048            messages_jsonl(&persisted_view).unwrap(),
9049            messages_jsonl(&restamped_view).unwrap()
9050        );
9051        assert_eq!(reapplied_log, log);
9052        reduce::verify_log(&log, &sidecar).unwrap();
9053        assert_eq!(
9054            reduce::invert(&restamped_view, &log, &sidecar).unwrap(),
9055            original.messages
9056        );
9057        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), source);
9058
9059        std::fs::remove_dir_all(temp).ok();
9060    }
9061
9062    #[test]
9063    fn read_surfaces_view_a_severed_claude_graph_while_transfer_still_refuses_it() {
9064        let temp = std::env::temp_dir().join(format!(
9065            "supercode-severed-view-{}-{}",
9066            std::process::id(),
9067            generated_session_id()
9068        ));
9069        std::fs::create_dir_all(&temp).unwrap();
9070        let path = temp.join("severed.jsonl");
9071        // A live record whose parent was pruned — what a compacted or
9072        // resumed-across-files Claude Code session looks like on disk.
9073        std::fs::write(
9074            &path,
9075            concat!(
9076                r#"{"type":"user","uuid":"orphan-u","parentUuid":null,"message":{"role":"user","content":"stranded prompt"}}"#,
9077                "\n",
9078                r#"{"type":"assistant","uuid":"live-a","parentUuid":"pruned","message":{"id":"m","role":"assistant","content":[{"type":"text","text":"live answer"}]}}"#,
9079                "\n",
9080            ),
9081        )
9082        .unwrap();
9083        let locator = SessionLocator {
9084            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9085            session_id: "severed".into(),
9086            storage: StorageLocator::File { path },
9087        };
9088        let mut service = HarnessSessionService::new();
9089
9090        let viewed = service.handle(request(
9091            1,
9092            "harness.v1.sessions.load",
9093            json!({"locator": locator}),
9094        ));
9095        let session = &viewed["result"]["session"];
9096        assert_eq!(session["fidelity"], "semantic");
9097        assert_eq!(session["messages"].as_array().unwrap().len(), 2);
9098        assert!(session["residue"].as_array().unwrap().iter().any(|entry| {
9099            entry
9100                .as_str()
9101                .is_some_and(|entry| entry.contains("live-a") && entry.contains("pruned"))
9102        }));
9103
9104        // Asking a READ surface for a lossless reconstruction gets the strict
9105        // refusal back, unchanged.
9106        let strict = service.handle(request(
9107            2,
9108            "harness.v1.sessions.load",
9109            json!({"locator": locator, "fidelity": "byte_lossless"}),
9110        ));
9111        assert!(strict["error"]["message"]
9112            .as_str()
9113            .unwrap()
9114            .contains("cannot reconstruct lossless Claude continuation"));
9115
9116        // Transfer/continuation surfaces have no view mode at all.
9117        let translated = service.handle(request(
9118            3,
9119            "harness.v1.sessions.translate",
9120            json!({"locator": locator, "target_harness": "codex"}),
9121        ));
9122        assert!(translated["error"]["message"]
9123            .as_str()
9124            .unwrap()
9125            .contains("cannot reconstruct lossless Claude continuation"));
9126        let resumed = service.handle(request(
9127            4,
9128            "harness.v1.sessions.resume_instructions",
9129            json!({"locator": locator}),
9130        ));
9131        assert!(resumed["error"]["message"]
9132            .as_str()
9133            .unwrap()
9134            .contains("cannot reconstruct lossless Claude continuation"));
9135
9136        let _ = std::fs::remove_dir_all(&temp);
9137    }
9138
9139    #[test]
9140    fn structured_resume_launches_cover_gemini_goose_and_supercode() {
9141        let codex = resume_launch(
9142            HarnessId::CODEX,
9143            "codex-session",
9144            Path::new("/tmp/project"),
9145            ResumePolicy::Yolo,
9146        )
9147        .unwrap_or_else(|_| panic!("Codex resume launch must be registered"));
9148        assert_eq!(codex.program, "codex");
9149        assert_eq!(
9150            codex.arguments,
9151            [
9152                "-c",
9153                "check_for_update_on_startup=false",
9154                "-c",
9155                "projects.\"/tmp/project\".trust_level=\"trusted\"",
9156                "--dangerously-bypass-approvals-and-sandbox",
9157                "--dangerously-bypass-hook-trust",
9158                "resume",
9159                "codex-session",
9160            ]
9161        );
9162
9163        let gemini = resume_launch(
9164            HarnessId::GEMINI,
9165            "gemini-session",
9166            Path::new("/tmp/project"),
9167            ResumePolicy::Yolo,
9168        )
9169        .unwrap_or_else(|_| panic!("Gemini resume launch must be registered"));
9170        assert_eq!(gemini.program, "gemini");
9171        assert_eq!(gemini.arguments, ["--yolo", "--resume", "gemini-session"]);
9172
9173        let goose = resume_launch(
9174            HarnessId::GOOSE,
9175            "goose-session",
9176            Path::new("/tmp/project"),
9177            ResumePolicy::Yolo,
9178        )
9179        .unwrap_or_else(|_| panic!("Goose resume launch must be registered"));
9180        assert_eq!(goose.program, "goose");
9181        assert_eq!(
9182            goose.arguments,
9183            ["session", "--resume", "--session-id", "goose-session"]
9184        );
9185
9186        let supercode = resume_launch(
9187            HarnessId::SUPERCODE,
9188            "supercode-session",
9189            Path::new("/tmp/project"),
9190            ResumePolicy::Yolo,
9191        )
9192        .unwrap_or_else(|_| panic!("Supercode resume launch must be registered"));
9193        assert_eq!(supercode.program, "supercode");
9194        assert_eq!(
9195            supercode.arguments,
9196            ["--dangerous", "resume", "supercode-session"]
9197        );
9198    }
9199
9200    #[test]
9201    fn diagonal_artifacts_preserve_claude_subagents_and_grok_bundle_members() {
9202        let temp = std::env::temp_dir().join(format!(
9203            "supercode-harness-artifact-{}-{}",
9204            std::process::id(),
9205            generated_session_id()
9206        ));
9207        let main_path = temp.join("parent.jsonl");
9208        let subagent_path = temp.join("parent/subagents/agent-child.jsonl");
9209        std::fs::create_dir_all(subagent_path.parent().unwrap()).unwrap();
9210        let fixture = std::fs::read_to_string(
9211            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9212                .join("tests/fixtures/claude_code_session.jsonl"),
9213        )
9214        .unwrap();
9215        let parent = fixture.trim_end_matches('\n');
9216        let child = fixture.trim_end_matches('\n');
9217        std::fs::write(&main_path, parent).unwrap();
9218        std::fs::write(&subagent_path, child).unwrap();
9219        let locator = SessionLocator {
9220            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
9221            session_id: "213bb148-51ea-453f-9206-f8b4b1168547".into(),
9222            storage: StorageLocator::File {
9223                path: main_path.clone(),
9224            },
9225        };
9226        let mut service = HarnessSessionService::new();
9227        let claude = service.handle(request(
9228            1,
9229            "harness.v1.sessions.translate",
9230            json!({"locator": locator, "target_harness": "claude-code"}),
9231        ));
9232        let artifact = &claude["result"]["artifact"];
9233        assert_eq!(artifact["fidelity"], "byte_lossless");
9234        assert_eq!(artifact["content"], parent);
9235        let files = artifact["files"].as_array().unwrap();
9236        assert!(files.iter().any(|file| {
9237            file["role"] == "subagent"
9238                && file["path"]
9239                    .as_str()
9240                    .is_some_and(|path| path.ends_with("/subagents/agent-child.jsonl"))
9241                && file["content"] == child
9242        }));
9243        assert!(!artifact["content"].as_str().unwrap().ends_with('\n'));
9244
9245        let grok = service.handle(request(
9246            2,
9247            "harness.v1.sessions.translate",
9248            json!({"locator": grok_locator(), "target_harness": "grok"}),
9249        ));
9250        let files = grok["result"]["artifact"]["files"].as_array().unwrap();
9251        for name in ["summary.json", "updates.jsonl"] {
9252            let expected = std::fs::read_to_string(
9253                PathBuf::from(env!("CARGO_MANIFEST_DIR"))
9254                    .join("tests/fixtures/grok_session")
9255                    .join(name),
9256            )
9257            .unwrap();
9258            assert!(files.iter().any(|file| {
9259                file["path"] == name && file["role"] == "bundle" && file["content"] == expected
9260            }));
9261        }
9262        std::fs::remove_dir_all(temp).ok();
9263    }
9264
9265    #[test]
9266    fn every_non_grok_handoff_mints_and_uses_a_fresh_target_identity() {
9267        let mut service = HarnessSessionService::new();
9268        let source = pi_locator();
9269        for (target, format) in [
9270            ("claude-code", SessionFormat::ClaudeCode),
9271            ("codex", SessionFormat::Codex),
9272            ("opencode", SessionFormat::OpenCode),
9273            ("pi", SessionFormat::Pi),
9274        ] {
9275            let result = service.handle(request(
9276                1,
9277                "harness.v1.sessions.handoff",
9278                json!({"locator": source, "target_harness": target, "cwd": "/tmp/project"}),
9279            ));
9280            let artifact = &result["result"]["artifact"];
9281            let target_id = artifact["session_id"].as_str().unwrap();
9282            assert_ne!(target_id, source.session_id, "{target}");
9283            let parsed = Session::load_str(artifact["content"].as_str().unwrap(), format).unwrap();
9284            assert_eq!(
9285                parsed.meta.session_id.as_deref(),
9286                Some(target_id),
9287                "{target}"
9288            );
9289            if target != "pi" {
9290                assert!(result["result"]["launch"]["arguments"]
9291                    .as_array()
9292                    .unwrap()
9293                    .iter()
9294                    .any(|argument| argument == target_id));
9295            }
9296            if target == "opencode" {
9297                assert!(target_id.starts_with("ses_"));
9298                fn assert_session_ids(value: &Value, target_id: &str) {
9299                    match value {
9300                        Value::Object(fields) => {
9301                            if let Some(session_id) = fields.get("sessionID") {
9302                                assert_eq!(session_id, target_id);
9303                            }
9304                            for child in fields.values() {
9305                                assert_session_ids(child, target_id);
9306                            }
9307                        }
9308                        Value::Array(values) => {
9309                            for child in values {
9310                                assert_session_ids(child, target_id);
9311                            }
9312                        }
9313                        _ => {}
9314                    }
9315                }
9316                let document: Value =
9317                    serde_json::from_str(artifact["content"].as_str().unwrap()).unwrap();
9318                assert_session_ids(&document, target_id);
9319            }
9320        }
9321
9322        let first = service.handle(request(
9323            2,
9324            "harness.v1.sessions.handoff",
9325            json!({"locator": source, "target_harness": "codex"}),
9326        ));
9327        let second = service.handle(request(
9328            3,
9329            "harness.v1.sessions.handoff",
9330            json!({"locator": source, "target_harness": "codex"}),
9331        ));
9332        assert_ne!(
9333            first["result"]["artifact"]["session_id"],
9334            second["result"]["artifact"]["session_id"]
9335        );
9336    }
9337
9338    #[test]
9339    fn grok_handoff_uses_the_official_importer_contract() {
9340        let mut service = HarnessSessionService::new();
9341        let source = opencode_locator();
9342        let response = service.handle(request(
9343            1,
9344            "harness.v1.sessions.handoff",
9345            json!({
9346                "locator": source,
9347                "target_harness": "grok",
9348                "cwd": "/tmp/grok-handoff-project",
9349            }),
9350        ));
9351        let result = &response["result"];
9352
9353        // The target is Grok, but the artifact truthfully names the Claude Code wire
9354        // format accepted by Grok's official importer. Raw Grok chat_history JSONL is
9355        // not a complete stock-resumable bundle.
9356        assert_eq!(result["artifact"]["target_harness"], "claude-code");
9357        assert!(result["artifact"]["suggested_filename"]
9358            .as_str()
9359            .unwrap()
9360            .ends_with(".grok-import.claude-code.jsonl"));
9361        let artifact = Session::load_str(
9362            result["artifact"]["content"].as_str().unwrap(),
9363            SessionFormat::ClaudeCode,
9364        )
9365        .unwrap();
9366        assert_eq!(
9367            artifact.meta.cwd.as_deref(),
9368            Some(Path::new("/tmp/grok-handoff-project"))
9369        );
9370        let target_session_id = artifact.meta.session_id.as_deref().unwrap();
9371        assert_eq!(target_session_id.len(), 36);
9372        assert_eq!(target_session_id.as_bytes()[14], b'4');
9373        assert_ne!(target_session_id, opencode_locator().session_id);
9374        assert_eq!(
9375            result["artifact"]["session_id"],
9376            artifact.meta.session_id.as_deref().unwrap()
9377        );
9378
9379        assert_eq!(
9380            result["materialize"]["arguments"],
9381            json!(["import", "--json", "{artifact_path}"])
9382        );
9383        assert_eq!(
9384            result["launch"]["arguments"],
9385            json!(["--resume", "{imported_session_id}", "--fork-session"])
9386        );
9387        assert!(result["note"]
9388            .as_str()
9389            .unwrap()
9390            .contains("outcome=imported"));
9391        assert!(!result["launch"]["arguments"]
9392            .as_array()
9393            .unwrap()
9394            .iter()
9395            .any(|argument| argument == &opencode_locator().session_id));
9396    }
9397
9398    #[tokio::test]
9399    async fn inventory_rejects_unknown_harnesses_and_runtime_attach_is_honest() {
9400        let mut service = HarnessSessionService::new();
9401        let inventory = service
9402            .handle_async(request(
9403                1,
9404                "harness.v1.harnesses.list",
9405                json!({"harnesses": ["missing"]}),
9406            ))
9407            .await;
9408        assert_eq!(inventory["error"]["code"], -32602);
9409
9410        let attached = service
9411            .handle_async(request(
9412                2,
9413                "harness.v1.runtimes.attach_existing",
9414                json!({"harness": "codex", "runtime_id": "thread-1"}),
9415            ))
9416            .await;
9417        assert_eq!(attached["error"]["code"], -32000);
9418        assert!(attached["error"]["message"]
9419            .as_str()
9420            .unwrap()
9421            .contains("runtimes.resume"));
9422    }
9423
9424    #[test]
9425    fn invalid_params_and_unknown_methods_use_json_rpc_errors() {
9426        let mut service = HarnessSessionService::new();
9427        let invalid = service.handle(request(1, "harness.v1.sessions.load", json!({})));
9428        assert_eq!(invalid["error"]["code"], -32602);
9429        let unknown = service.handle(request(2, "harness.v1.unknown", json!({})));
9430        assert_eq!(unknown["error"]["code"], -32601);
9431    }
9432
9433    #[cfg(unix)]
9434    #[tokio::test]
9435    // The test mutates process-wide harness environment and deliberately
9436    // holds the global test lock until every async runtime operation ends.
9437    #[allow(clippy::await_holding_lock)]
9438    async fn async_service_drives_a_generic_acp_runtime() {
9439        let _environment_guard = crate::live_runtime::test_environment_lock();
9440        let script = r#"
9441            i=0
9442            while IFS= read -r line; do
9443              i=$((i + 1))
9444              case "$i" in
9445                1) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{},"authMethods":[]}}' ;;
9446                2) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"svc_acp"}}' ;;
9447                3)
9448                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ok"}}}}'
9449                  printf '%s\n' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}'
9450                  ;;
9451                4)
9452                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"from terminal"}}}}'
9453                  printf '%s\n' '{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}'
9454                  ;;
9455              esac
9456            done
9457        "#;
9458        let mut service = HarnessSessionService::new();
9459        let started = service
9460            .handle_async(request(
9461                1,
9462                "harness.v1.runtimes.start",
9463                json!({
9464                    "harness": "codex",
9465                    "protocol": "acp",
9466                    "cwd": std::env::current_dir().unwrap(),
9467                    "launch": {"program": "/bin/sh", "arguments": ["-c", script], "env": {}},
9468                }),
9469            ))
9470            .await;
9471        assert_eq!(started["result"]["connection"], "runtime-1");
9472        assert_eq!(started["result"]["handle"]["runtime_id"], "svc_acp");
9473
9474        let terminal = service
9475            .handle_async(request(
9476                9,
9477                "harness.v1.runtimes.terminal_instructions",
9478                json!({"connection":"runtime-1"}),
9479            ))
9480            .await;
9481        let arguments = terminal["result"]["launch"]["arguments"]
9482            .as_array()
9483            .expect("hosted runtime should return terminal arguments");
9484        let endpoint_index = arguments
9485            .iter()
9486            .position(|value| value == "--endpoint")
9487            .expect("terminal command should use an opaque endpoint");
9488        let endpoint = LiveRuntimeEndpoint::parse(
9489            arguments[endpoint_index + 1]
9490                .as_str()
9491                .expect("endpoint argument should be text"),
9492        )
9493        .unwrap();
9494        assert!(!terminal.to_string().contains("Bearer"));
9495        let workspace = std::env::current_dir().unwrap();
9496        let receipt = resolve_live_runtime(
9497            &endpoint,
9498            &LiveRuntimeSource {
9499                harness: "codex".into(),
9500                session_id: "svc_acp".into(),
9501                workspace,
9502            },
9503        )
9504        .unwrap();
9505        let remote = crate::HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
9506            .await
9507            .unwrap();
9508        let mut attachment = crate::FrontendRuntime::attach(remote.as_ref(), 100)
9509            .await
9510            .unwrap();
9511
9512        let sent = service
9513            .handle_async(request(
9514                2,
9515                "harness.v1.runtimes.send_input",
9516                json!({"connection": "runtime-1", "text": "hi"}),
9517            ))
9518            .await;
9519        assert_eq!(sent["result"]["turn_id"], "3");
9520
9521        let mut events = Vec::new();
9522        for _ in 0..20 {
9523            events.extend(service.poll_runtimes().await);
9524            if events.len() >= 2 {
9525                break;
9526            }
9527            tokio::time::sleep(Duration::from_millis(2)).await;
9528        }
9529        assert!(events
9530            .iter()
9531            .any(|event| { event["params"]["event"]["kind"] == "session/update" }));
9532        assert!(events.iter().any(|event| {
9533            event["params"]["event"]["kind"] == "supercode/acp_request_completed"
9534        }));
9535
9536        let saw_editor_reply = tokio::time::timeout(Duration::from_secs(2), async {
9537            loop {
9538                let event = attachment.next_event().await.unwrap();
9539                if event.kind == "text_delta" && event.payload["text"] == "ok" {
9540                    break;
9541                }
9542            }
9543        })
9544        .await;
9545        assert!(
9546            saw_editor_reply.is_ok(),
9547            "terminal should observe the editor-driven turn"
9548        );
9549
9550        crate::FrontendRuntime::submit(remote.as_ref(), "DRIVE FROM TERMINAL".into())
9551            .await
9552            .unwrap();
9553        let saw_terminal_reply = tokio::time::timeout(Duration::from_secs(2), async {
9554            loop {
9555                let event = attachment.next_event().await.unwrap();
9556                if event.kind == "text_delta" && event.payload["text"] == "from terminal" {
9557                    break;
9558                }
9559            }
9560        })
9561        .await;
9562        assert!(
9563            saw_terminal_reply.is_ok(),
9564            "terminal should drive the same runtime"
9565        );
9566
9567        let closed = service
9568            .handle_async(request(
9569                3,
9570                "harness.v1.runtimes.close",
9571                json!({"connection": "runtime-1"}),
9572            ))
9573            .await;
9574        assert_eq!(closed["result"]["closed"], true);
9575    }
9576
9577    /// UNI-7 dev/02: a RUNNING mock gateway is detected through the real
9578    /// openclaw probe (config-declared endpoint, TCP connect), and an ACTIVE
9579    /// hermes WAL is detected through the real WAL-freshness probe; the
9580    /// negative sides (no listener, stale WAL, no config) stay undetected.
9581    #[test]
9582    fn running_instances_are_detected_from_mock_gateway_and_active_wal() {
9583        let home = connect_scratch_home("uni7-running");
9584
9585        // No config at all: hermes has no default endpoint, so no detection.
9586        // (openclaw's no-config behavior now probes its DOCUMENTED default
9587        // endpoint ws://127.0.0.1:18789 — see the connect launch's
9588        // `default_address` — which is real box state a hermetic test must
9589        // not assert either way; the closed-port negative below covers the
9590        // no-listener side deterministically.)
9591        assert!(probe_hermes_running(&home, 300_000).is_none());
9592
9593        // Mock gateway: a real TCP listener on an ephemeral port, declared in
9594        // the harness's own config file.
9595        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9596        let port = listener.local_addr().unwrap().port();
9597        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9598        std::fs::write(
9599            home.join(".openclaw/openclaw.json"),
9600            format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9601        )
9602        .unwrap();
9603        let running = probe_openclaw_running(&home).expect("listening gateway must be detected");
9604        assert!(matches!(
9605            running.method,
9606            RunningInstanceMethod::GatewayConnect
9607        ));
9608        assert!(running.evidence.contains(&format!("127.0.0.1:{port}")));
9609        drop(listener);
9610        // Parallel tests also bind ephemeral loopback ports, so a just-freed
9611        // port can be re-bound by a NEIGHBORING test between drop and probe.
9612        // Detection on a closed port must fail — retry on a fresh port when
9613        // the freed one was recycled by someone else.
9614        let mut closed_detected = probe_openclaw_running(&home).is_some();
9615        for _ in 0..3 {
9616            if !closed_detected {
9617                break;
9618            }
9619            let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9620            let port = listener.local_addr().unwrap().port();
9621            drop(listener);
9622            std::fs::write(
9623                home.join(".openclaw/openclaw.json"),
9624                format!(r#"{{"gateway": {{"mode": "local", "port": {port}, "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9625            )
9626            .unwrap();
9627            closed_detected = probe_openclaw_running(&home).is_some();
9628        }
9629        assert!(
9630            !closed_detected,
9631            "a closed gateway must not read as running"
9632        );
9633
9634        // gateway.url form takes precedence over port.
9635        let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
9636        let port = listener.local_addr().unwrap().port();
9637        std::fs::write(
9638            home.join(".openclaw/openclaw.json"),
9639            format!(r#"{{"gateway": {{"url": "ws://127.0.0.1:{port}", "auth": {{"mode": "token", "token": "t"}}}}}}"#),
9640        )
9641        .unwrap();
9642        assert!(probe_openclaw_running(&home).is_some());
9643        drop(listener);
9644
9645        // Hermes: an ACTIVE WAL (fresh stamp) is detected; a stale one is not.
9646        std::fs::create_dir_all(home.join(".hermes")).unwrap();
9647        let wal = home.join(".hermes/state.db-wal");
9648        std::fs::write(&wal, b"wal").unwrap();
9649        let running = probe_hermes_running(&home, 300_000).expect("fresh WAL must be detected");
9650        assert!(matches!(
9651            running.method,
9652            RunningInstanceMethod::StoreWalActivity
9653        ));
9654        assert!(running.evidence.contains("state.db-wal"));
9655        let stale = std::time::SystemTime::now() - std::time::Duration::from_secs(3_600);
9656        std::fs::File::options()
9657            .append(true)
9658            .open(&wal)
9659            .unwrap()
9660            .set_modified(stale)
9661            .unwrap();
9662        assert!(
9663            probe_hermes_running(&home, 300_000).is_none(),
9664            "a stale WAL (crash leftover) must not read as running"
9665        );
9666    }
9667
9668    fn connect_scratch_home(tag: &str) -> PathBuf {
9669        let dir = std::env::temp_dir().join(format!(
9670            "supercode-connect-service-{tag}-{}-{}",
9671            std::process::id(),
9672            std::time::SystemTime::now()
9673                .duration_since(std::time::UNIX_EPOCH)
9674                .unwrap()
9675                .as_nanos()
9676        ));
9677        std::fs::create_dir_all(&dir).unwrap();
9678        dir
9679    }
9680
9681    /// Minimal HTTP responder that speaks just enough OpenCode server to
9682    /// accept a health check, create a session, and hold an SSE stream open,
9683    /// while recording each request line with its Authorization header.
9684    async fn mock_opencode_endpoint() -> (String, tokio::sync::mpsc::UnboundedReceiver<String>) {
9685        use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
9686        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
9687        let address = listener.local_addr().unwrap();
9688        let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel();
9689        tokio::spawn(async move {
9690            loop {
9691                let Ok((mut stream, _)) = listener.accept().await else {
9692                    break;
9693                };
9694                let request_sender = request_sender.clone();
9695                tokio::spawn(async move {
9696                    let (reader, mut writer) = stream.split();
9697                    let mut reader = BufReader::new(reader);
9698                    let mut request_line = String::new();
9699                    if reader.read_line(&mut request_line).await.unwrap_or(0) == 0 {
9700                        return;
9701                    }
9702                    let request_line = request_line.trim_end().to_string();
9703                    let mut authorization = String::new();
9704                    let mut content_length = 0usize;
9705                    loop {
9706                        let mut line = String::new();
9707                        if reader.read_line(&mut line).await.unwrap_or(0) == 0 {
9708                            return;
9709                        }
9710                        let line = line.trim_end();
9711                        if line.is_empty() {
9712                            break;
9713                        }
9714                        let lower = line.to_ascii_lowercase();
9715                        if let Some(value) = lower.strip_prefix("authorization:") {
9716                            authorization = value.trim().to_string();
9717                        }
9718                        if let Some(value) = lower.strip_prefix("content-length:") {
9719                            content_length = value.trim().parse().unwrap_or(0);
9720                        }
9721                    }
9722                    if content_length > 0 {
9723                        let mut body = vec![0u8; content_length];
9724                        let _ = reader.read_exact(&mut body).await;
9725                    }
9726                    let _ = request_sender.send(format!("{request_line} :: {authorization}"));
9727                    if request_line.starts_with("GET /event") {
9728                        let _ = writer
9729                            .write_all(
9730                                b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n",
9731                            )
9732                            .await;
9733                        tokio::time::sleep(std::time::Duration::from_secs(5)).await;
9734                        return;
9735                    }
9736                    let body = if request_line.starts_with("POST /session") {
9737                        r#"{"id":"mock-session"}"#
9738                    } else {
9739                        r#"{"status":"ok"}"#
9740                    };
9741                    let response = format!(
9742                        "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
9743                        body.len(),
9744                        body
9745                    );
9746                    let _ = writer.write_all(response.as_bytes()).await;
9747                });
9748            }
9749        });
9750        (format!("http://{address}"), request_receiver)
9751    }
9752
9753    fn connect_descriptor(protocol: &str) -> crate::HarnessSupportDescriptor {
9754        crate::HarnessSupportDescriptor {
9755            orchestration: Default::default(),
9756            id: HarnessId::from(HarnessId::OPENCODE),
9757            display_name: "OpenCode".into(),
9758            native: crate::NativeSupport {
9759                discover: crate::ImplementationKind::Absent,
9760                load: crate::ImplementationKind::Absent,
9761                follow: crate::ImplementationKind::Absent,
9762                import: crate::ImplementationKind::Absent,
9763                export: crate::ImplementationKind::Absent,
9764            },
9765            runtime: crate::RuntimeSupport {
9766                implementation: crate::ImplementationKind::BuiltIn,
9767                protocol: protocol.into(),
9768                default_launch: None,
9769                connect_launch: Some(crate::RuntimeConnectLaunch {
9770                    config_path: "~/opencode-tui.json".into(),
9771                    address_pointer: "/server/url".into(),
9772                    port_pointer: None,
9773                    default_address: None,
9774                    auth_pointer: Some("/server/token".into()),
9775                    protocol: protocol.into(),
9776                }),
9777                capabilities: crate::RuntimeCapabilities {
9778                    start_session: true,
9779                    resume_session: true,
9780                    attach_existing_process: true,
9781                    send_input: true,
9782                    stream_events: true,
9783                    interrupt: true,
9784                    steer: false,
9785                    respond_to_requests: true,
9786                },
9787            },
9788        }
9789    }
9790
9791    #[tokio::test]
9792    async fn connect_mode_descriptor_opens_a_running_endpoint_with_config_sourced_auth() {
9793        let (base_url, mut requests) = mock_opencode_endpoint().await;
9794        let home = connect_scratch_home("open");
9795        std::fs::write(
9796            home.join("opencode-tui.json"),
9797            format!(r#"{{"server": {{"url": "{base_url}", "token": "connect-secret"}}}}"#),
9798        )
9799        .unwrap();
9800
9801        let descriptor = connect_descriptor("opencode-http-sse");
9802        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
9803        assert!(backend.capabilities().attach_existing_process);
9804
9805        let connection = backend
9806            .start(crate::RuntimeStartRequest {
9807                cwd: home.clone(),
9808                launch: None,
9809                mcp_servers: Vec::new(),
9810            })
9811            .await
9812            .unwrap();
9813        let handle = connection.handle();
9814        assert_eq!(handle.runtime_id, "mock-session");
9815        match &handle.endpoint {
9816            crate::RuntimeEndpoint::Http {
9817                base_url: endpoint, ..
9818            } => assert_eq!(endpoint, &base_url),
9819            other => panic!("connect mode must join the running endpoint, got {other:?}"),
9820        }
9821
9822        let mut seen = Vec::new();
9823        while let Ok(line) = requests.try_recv() {
9824            seen.push(line);
9825        }
9826        assert!(seen
9827            .iter()
9828            .any(|line| line.starts_with("GET /global/health")
9829                && line.contains("bearer connect-secret")));
9830        assert!(seen.iter().any(
9831            |line| line.starts_with("POST /session") && line.contains("bearer connect-secret")
9832        ));
9833    }
9834
9835    /// UNI-5 dev/02, contract corrected by the 2026-08-31 blind walk: the
9836    /// full connect-mode attach path against a MOCK gateway bridge — no live
9837    /// gateway, no model spend. A scripted fake `openclaw` binary (a)
9838    /// asserts the REAL bridge contract — the resolved --url on argv and the
9839    /// credential via --token-file (the real bridge ignores the env var; the
9840    /// endpoint comes from openclaw-native `gateway.remote.url`, never the
9841    /// schema-invalid `gateway.url`) — then (b) speaks scripted ACP:
9842    /// initialize advertising sessionCapabilities.{list,resume},
9843    /// session/resume rebinding the requested session (join), and a
9844    /// prompted turn.
9845    #[tokio::test]
9846    async fn openclaw_connect_mode_attaches_lists_and_resumes_via_a_mock_bridge() {
9847        let home = connect_scratch_home("openclaw");
9848        std::fs::create_dir_all(home.join(".openclaw")).unwrap();
9849        std::fs::write(
9850            home.join(".openclaw/openclaw.json"),
9851            r#"{"gateway": {"remote": {"url": "ws://127.0.0.1:19789"}, "auth": {"mode": "token", "token": "mock-gateway-token"}}}"#,
9852        )
9853        .unwrap();
9854        let script = home.join("openclaw");
9855        std::fs::write(
9856            &script,
9857            r#"#!/bin/sh
9858# Fake `openclaw acp` bridge: verify the connect-mode contract, then speak ACP.
9859[ "$1" = "acp" ] || { echo "unexpected argv: $*" >&2; exit 9; }
9860[ "$2" = "--url" ] && [ "$3" = "ws://127.0.0.1:19789" ] || { echo "missing --url: $*" >&2; exit 9; }
9861[ "$4" = "--token-file" ] || { echo "missing --token-file: $*" >&2; exit 9; }
9862[ "$(cat "$5")" = "mock-gateway-token" ] || { echo "token file wrong" >&2; exit 9; }
9863while IFS= read -r line; do
9864  case "$line" in
9865    *'"initialize"'*)
9866      printf '%s
9867' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"sessionCapabilities":{"list":{},"resume":{}}},"agentInfo":{"name":"openclaw-acp","version":"2026.7.1-2"},"authMethods":[]}}' ;;
9868    *'"session/resume"'*)
9869      printf '%s
9870' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:main"}}' ;;
9871    *'"session/new"'*)
9872      printf '%s
9873' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"agent:main:fresh"}}' ;;
9874    *'"session/prompt"'*)
9875      printf '%s
9876' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"agent:main:main","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"joined"}}}}'
9877      printf '%s
9878' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}' ;;
9879  esac
9880done
9881"#,
9882        )
9883        .unwrap();
9884        use std::os::unix::fs::PermissionsExt;
9885        std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
9886
9887        let mut descriptor = crate::harness_support_registry()
9888            .harnesses
9889            .into_iter()
9890            .find(|harness| harness.id.as_str() == HarnessId::OPENCLAW)
9891            .expect("openclaw must be registered");
9892        descriptor
9893            .runtime
9894            .connect_launch
9895            .as_mut()
9896            .unwrap()
9897            .config_path = "~/.openclaw/openclaw.json".into();
9898        descriptor.runtime.default_launch.as_mut().unwrap().program =
9899            script.to_string_lossy().into_owned();
9900        let backend = open_connect_descriptor(&descriptor, &home).unwrap();
9901        assert!(backend.capabilities().resume_session);
9902
9903        let joined = backend
9904            .attach(crate::RuntimeAttachRequest {
9905                runtime_id: "agent:main:main".into(),
9906                cwd: Some(home.clone()),
9907                launch: None,
9908                mcp_servers: Vec::new(),
9909            })
9910            .await;
9911        let mut connection = joined.expect("mock bridge attach must succeed");
9912        assert_eq!(connection.handle().runtime_id, "agent:main:main");
9913        let turn = connection
9914            .send_input(crate::RuntimeInput {
9915                text: "hello".into(),
9916                image_urls: Vec::new(),
9917            })
9918            .await;
9919        assert!(turn.is_ok(), "prompt through the mock bridge: {turn:?}");
9920        connection.close().await.unwrap();
9921    }
9922
9923    #[tokio::test]
9924    async fn connect_mode_fails_closed_without_a_protocol_client_or_config() {
9925        let home = connect_scratch_home("fail");
9926        std::fs::write(
9927            home.join("opencode-tui.json"),
9928            r#"{"server": {"url": "http://127.0.0.1:1", "token": "connect-secret"}}"#,
9929        )
9930        .unwrap();
9931
9932        let gateway_only = connect_descriptor("acp-v1-jsonrpc");
9933        let Err(error) = open_connect_descriptor(&gateway_only, &home) else {
9934            panic!("an ACP connect endpoint has no gateway client yet");
9935        };
9936        let message = format!("{error:?}");
9937        assert!(message.contains("acp-v1-jsonrpc"));
9938        assert!(!message.contains("connect-secret"));
9939
9940        let unreadable = connect_descriptor("opencode-http-sse");
9941        let missing_home = connect_scratch_home("missing");
9942        let Err(error) = open_connect_descriptor(&unreadable, &missing_home) else {
9943            panic!("an unreadable connect config must fail closed");
9944        };
9945        let message = format!("{error:?}");
9946        assert!(message.contains("opencode-tui.json"));
9947        assert!(!message.contains("connect-secret"));
9948    }
9949
9950    // ---------------------------------------------------------------------
9951    // ORCH-7 — `harness.v1.jobs.list` / `jobs.get` over the committed fixtures
9952    // ---------------------------------------------------------------------
9953
9954    fn jobs_fixture_root() -> PathBuf {
9955        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
9956    }
9957
9958    /// Point only the three job-bearing homes at the fixtures. Nothing else is
9959    /// read, so the host machine's own harness homes cannot leak into a row.
9960    fn jobs_fixture_homes() -> Value {
9961        let root = jobs_fixture_root();
9962        json!({
9963            "claude_code": root.join("claude_jobs_home/projects"),
9964            "hermes": root.join("hermes_home/state.db"),
9965            "openclaw": root.join("openclaw_home"),
9966        })
9967    }
9968
9969    fn jobs_list(params: Value) -> Value {
9970        let mut service = HarnessSessionService::new();
9971        service.handle(request(1, "harness.v1.jobs.list", params))
9972    }
9973
9974    fn job_row<'a>(result: &'a Value, id: &str) -> &'a Value {
9975        result["jobs"]
9976            .as_array()
9977            .expect("jobs is an array")
9978            .iter()
9979            .find(|job| job["id"] == id)
9980            .unwrap_or_else(|| panic!("no job `{id}` in {result}"))
9981    }
9982
9983    #[test]
9984    fn gateway_health_derives_from_running_probe_and_install_state() {
9985        let running = RunningInstance {
9986            method: RunningInstanceMethod::GatewayConnect,
9987            evidence: "gateway endpoint 127.0.0.1:18789 accepted a TCP connect".into(),
9988            checked_at_ms: 1,
9989        };
9990        let up = gateway_health(
9991            HarnessId::OPENCLAW,
9992            true,
9993            Some(&running),
9994            Some("2026.7.1-2"),
9995        );
9996        assert_eq!(up.state, GatewayState::Up);
9997        assert!(up.endpoint.as_deref().unwrap().starts_with("ws://"));
9998        assert_eq!(up.version.as_deref(), Some("2026.7.1-2"));
9999        // Hermes consults its own `gateway status` when the WAL heuristic says
10000        // nothing; a fake binary decides the verdict (the env var is global, so
10001        // the up/down cases run inside this one test, never in parallel).
10002        let dir = std::env::temp_dir().join(format!("supercode-orch17-{}", std::process::id()));
10003        std::fs::create_dir_all(&dir).unwrap();
10004        let fake = dir.join("hermes");
10005        let write_fake = |body: &str| {
10006            std::fs::write(&fake, format!("#!/bin/sh\n{body}\n")).unwrap();
10007            #[cfg(unix)]
10008            {
10009                use std::os::unix::fs::PermissionsExt;
10010                std::fs::set_permissions(&fake, std::fs::Permissions::from_mode(0o755)).unwrap();
10011            }
10012        };
10013        write_fake("echo '✗ Gateway service is not installed'");
10014        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| {
10015            *slot.borrow_mut() = Some((
10016                HarnessId::HERMES.to_string(),
10017                fake.to_string_lossy().into_owned(),
10018            ))
10019        });
10020        let down = gateway_health(HarnessId::HERMES, true, None, None);
10021        assert_eq!(down.state, GatewayState::Down, "{down:?}");
10022        assert!(down.endpoint.is_none());
10023        assert!(down.evidence.contains("not installed"));
10024        write_fake("echo 'Launchd plist: /x/ai.hermes.gateway.plist'; echo '✓ Gateway is supervised by launchd (PID 4242)'");
10025        let idle_but_up = gateway_health(HarnessId::HERMES, true, None, Some("0.21.0"));
10026        assert_eq!(idle_but_up.state, GatewayState::Up, "{idle_but_up:?}");
10027        assert!(idle_but_up.evidence.contains("PID 4242"));
10028        write_fake("echo 'something unparseable'");
10029        let no_verdict = gateway_health(HarnessId::HERMES, true, None, None);
10030        assert_eq!(no_verdict.state, GatewayState::Down);
10031        assert!(no_verdict.evidence.contains("no verdict"));
10032        crate::harness_command::TEST_PROGRAM_OVERRIDE.with(|slot| *slot.borrow_mut() = None);
10033        let absent = gateway_health(HarnessId::HERMES, false, None, None);
10034        assert_eq!(absent.state, GatewayState::Unknown);
10035        let core = gateway_health(HarnessId::CODEX, true, None, Some("0.144.4"));
10036        assert_eq!(core.state, GatewayState::Unknown);
10037        assert!(core.evidence.contains("per session"));
10038    }
10039
10040    #[test]
10041    fn triggers_list_reads_both_stores_and_never_emits_secrets() {
10042        let response = triggers_list(json!({"homes": jobs_fixture_homes()}));
10043        let rows = response["result"]["triggers"]
10044            .as_array()
10045            .expect("triggers")
10046            .clone();
10047        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10048        assert!(
10049            hermes.iter().any(|r| r["name"] == "deploys"
10050                && r["route"] == "/webhooks/deploys"
10051                && r["kind"] == "webhook"),
10052            "{rows:#?}"
10053        );
10054        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10055        assert!(openclaw
10056            .iter()
10057            .any(|r| r["name"] == "wake" && r["kind"] == "builtin_wake"));
10058        assert!(openclaw.iter().any(|r| r["name"] == "gmail"
10059            && r["kind"] == "hook_mapping"
10060            && r["target"]["action"] == "agent"));
10061        let rendered = response.to_string();
10062        for secret in [
10063            "FAKE-WEBHOOK-HMAC-DO-NOT-EMIT",
10064            "FAKE-HOOK-TOKEN-DO-NOT-EMIT",
10065        ] {
10066            assert!(!rendered.contains(secret), "{rendered}");
10067        }
10068        let refused =
10069            triggers_list(json!({"harness": "claude-code", "homes": jobs_fixture_homes()}));
10070        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10071    }
10072
10073    fn triggers_list(params: Value) -> Value {
10074        let mut service = HarnessSessionService::new();
10075        service.handle(request(1, "harness.v1.triggers.list", params))
10076    }
10077
10078    #[test]
10079    fn routes_list_reads_both_gateway_configs_and_flags_the_defaults() {
10080        let response = routes_list(json!({"homes": jobs_fixture_homes()}));
10081        let rows = response["result"]["routes"]
10082            .as_array()
10083            .expect("routes")
10084            .clone();
10085        let hermes: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "hermes").collect();
10086        assert_eq!(hermes.len(), 2, "{rows:#?}");
10087        assert_eq!(hermes[0]["target"], "coder");
10088        assert_eq!(hermes[0]["match"]["platform"], "slack");
10089        assert_eq!(hermes[0]["match"]["chat_id"], "C0FIXTURE");
10090        assert_eq!(hermes[0]["specificity"], 4);
10091        assert_eq!(hermes[1]["default"], true);
10092        let openclaw: Vec<&Value> = rows.iter().filter(|r| r["harness"] == "openclaw").collect();
10093        assert!(
10094            openclaw.iter().any(|r| r["target"] == "design"
10095                && r["match"]["platform"] == "slack"
10096                && r["specificity"] == 1),
10097            "{openclaw:#?}"
10098        );
10099        assert!(openclaw.iter().any(|r| r["default"] == true));
10100        // A core harness has no routing concept and is refused, never an empty list.
10101        let refused = routes_list(json!({"harness": "codex", "homes": jobs_fixture_homes()}));
10102        assert_eq!(refused["error"]["code"], -32020, "{refused}");
10103    }
10104
10105    fn routes_list(params: Value) -> Value {
10106        let mut service = HarnessSessionService::new();
10107        service.handle(request(1, "harness.v1.routes.list", params))
10108    }
10109
10110    #[test]
10111    fn jobs_list_projects_every_fixture_store_onto_the_uniform_row() {
10112        let response = jobs_list(json!({"homes": jobs_fixture_homes()}));
10113        let result = &response["result"];
10114        let ids: Vec<&str> = result["jobs"]
10115            .as_array()
10116            .unwrap()
10117            .iter()
10118            .map(|job| job["id"].as_str().unwrap())
10119            .collect();
10120        assert_eq!(
10121            ids,
10122            vec![
10123                "release-watch",
10124                "toolu_wake_recheck",
10125                "digest-15m",
10126                "nightly-audit",
10127                "coder-standup",
10128                "ops-once-boot",
10129                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10130                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10131                "cron_standup",
10132                "cron_reindex",
10133            ],
10134            "{result}"
10135        );
10136
10137        // OpenClaw, pinned shape: rows come from `state/openclaw.sqlite`
10138        // (`cron_jobs.job_json` + runtime columns), captured from a real
10139        // 2026.7.1-2 gateway.
10140        let health = job_row(result, "85ad7832-896f-42be-af31-3e1ed2fbdc4b");
10141        assert_eq!(health["harness"], "openclaw");
10142        assert_eq!(health["schedule"]["kind"], "interval");
10143        assert_eq!(health["schedule"]["minutes"], 10.0);
10144        assert_eq!(health["session_target"], "isolated");
10145        assert_eq!(health["payload"]["kind"], "prompt");
10146        assert_eq!(health["payload"]["text"], "nightly health check");
10147        // ORCH-13: the mode word (`announce`) and the channel it announces on
10148        // (`last`) are separate facts, and the store keeps both — in
10149        // `job_json.delivery` and in the `delivery_*` columns beside it.
10150        assert_eq!(health["deliver"]["mode"], "announce");
10151        assert_eq!(health["deliver"]["target"], "last");
10152        assert_eq!(health["next_run_at"], "2026-09-03T06:52:26Z");
10153        let digest = job_row(result, "8bb7d938-ca46-4a6d-90eb-c92331155566");
10154        assert_eq!(digest["schedule"]["kind"], "cron");
10155        assert_eq!(digest["schedule"]["expr"], "0 9 * * 1");
10156        assert_eq!(digest["session_target"], "main");
10157        assert_eq!(digest["payload"]["kind"], "system_event");
10158
10159        // Claude Code: session-scoped, one recurring cron and one one-shot wakeup.
10160        let cron = job_row(result, "release-watch");
10161        assert_eq!(cron["harness"], "claude-code");
10162        assert_eq!(cron["scope"], "session");
10163        assert_eq!(cron["session_id"], "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f");
10164        assert_eq!(cron["schedule"]["kind"], "cron");
10165        assert_eq!(cron["schedule"]["expr"], "*/10 * * * *");
10166        assert_eq!(cron["schedule"]["display"], "*/10 * * * *");
10167        assert_eq!(cron["payload"]["kind"], "prompt");
10168        assert_eq!(cron["recurring"], true);
10169        assert_eq!(cron["deliver"]["target"], "session");
10170        let wakeup = job_row(result, "toolu_wake_recheck");
10171        assert_eq!(wakeup["payload"]["kind"], "wakeup");
10172        assert_eq!(wakeup["schedule"]["kind"], "once");
10173        assert_eq!(wakeup["recurring"], false);
10174        assert_eq!(wakeup["state"], "pending");
10175
10176        // Hermes: install-scoped, interval + origin delivery, and a paused cron.
10177        let interval = job_row(result, "digest-15m");
10178        assert_eq!(interval["harness"], "hermes");
10179        assert_eq!(interval["scope"], "install");
10180        assert_eq!(interval["profile"], Value::Null);
10181        assert_eq!(interval["schedule"]["kind"], "interval");
10182        assert_eq!(interval["schedule"]["minutes"], 15.0);
10183        assert_eq!(interval["schedule"]["display"], "every 15 min");
10184        assert_eq!(interval["deliver"]["target"], "origin");
10185        assert_eq!(interval["deliver"]["chat_id"], "-1002233445566");
10186        assert_eq!(interval["next_run_at"], "2026-09-02T11:15:00Z");
10187        assert_eq!(interval["last_status"], "ok");
10188        let nightly = job_row(result, "nightly-audit");
10189        assert_eq!(nightly["schedule"]["expr"], "0 3 * * *");
10190        assert_eq!(nightly["deliver"]["target"], "local");
10191        assert_eq!(nightly["enabled"], false);
10192        assert_eq!(nightly["state"], "paused");
10193        // The per-profile store carries the profile name from its own path.
10194        let profiled = job_row(result, "ops-once-boot");
10195        assert_eq!(profiled["profile"], "ops");
10196        assert_eq!(profiled["schedule"]["kind"], "once");
10197        assert_eq!(profiled["schedule"]["run_at"], "2026-09-03T06:00:00Z");
10198        assert_eq!(profiled["payload"]["kind"], "script");
10199        // An explicit `<platform>:<chat>` target carries the chat itself.
10200        assert_eq!(profiled["deliver"]["target"], "slack:C0429ABCD");
10201        assert_eq!(profiled["deliver"]["chat_id"], "C0429ABCD");
10202        assert_eq!(profiled["recurring"], false);
10203
10204        // ORCH-13: a job delivering to its creating conversation carries that
10205        // conversation's whole surface — platform word, chat AND thread.
10206        let standup_to_group = job_row(result, "coder-standup");
10207        assert_eq!(standup_to_group["deliver"]["target"], "origin");
10208        assert_eq!(standup_to_group["deliver"]["chat_id"], "-100777");
10209        assert_eq!(standup_to_group["deliver"]["thread_id"], "55");
10210        // Hermes has no mode word and routes by adapter profile, not account.
10211        assert!(standup_to_group["deliver"]["mode"].is_null());
10212        assert!(standup_to_group["deliver"]["account"].is_null());
10213
10214        // OpenClaw: the session target and the delivery mode are the row's own
10215        // columns, not a footnote.
10216        let standup = job_row(result, "cron_standup");
10217        assert_eq!(standup["harness"], "openclaw");
10218        assert_eq!(standup["session_target"], "isolated");
10219        assert_eq!(standup["deliver"]["mode"], "announce");
10220        assert_eq!(standup["deliver"]["target"], "slack");
10221        assert_eq!(standup["deliver"]["chat_id"], "C0429ABCD");
10222        assert_eq!(standup["payload"]["kind"], "prompt");
10223        assert_eq!(standup["profile"], "main");
10224        let reindex = job_row(result, "cron_reindex");
10225        assert_eq!(reindex["session_target"], "main");
10226        assert_eq!(reindex["payload"]["kind"], "system_event");
10227        assert_eq!(reindex["schedule"]["kind"], "interval");
10228        assert_eq!(reindex["schedule"]["display"], "every 240 min");
10229        assert_eq!(reindex["enabled"], false);
10230
10231        // Every store consulted is named, so an empty answer is never silent.
10232        let states: Vec<(&str, &str)> = result["sources"]
10233            .as_array()
10234            .unwrap()
10235            .iter()
10236            .map(|source| {
10237                (
10238                    source["harness"].as_str().unwrap(),
10239                    source["state"].as_str().unwrap(),
10240                )
10241            })
10242            .collect();
10243        // The `coder` profile home has no cron store at all: it is named as
10244        // `absent_store`, not skipped, so "this profile schedules nothing" and
10245        // "this profile was never looked at" stay distinguishable.
10246        assert_eq!(
10247            states,
10248            vec![
10249                ("claude-code", "scanned"),
10250                ("hermes", "read"),
10251                ("hermes", "absent_store"),
10252                ("hermes", "read"),
10253                ("openclaw", "read"),
10254                ("openclaw", "read"),
10255            ],
10256            "{result}"
10257        );
10258    }
10259
10260    #[test]
10261    fn jobs_list_filters_by_harness_session_and_profile() {
10262        let by_harness = jobs_list(json!({"harness": "openclaw", "homes": jobs_fixture_homes()}));
10263        let ids: Vec<&str> = by_harness["result"]["jobs"]
10264            .as_array()
10265            .unwrap()
10266            .iter()
10267            .map(|job| job["id"].as_str().unwrap())
10268            .collect();
10269        assert_eq!(
10270            ids,
10271            vec![
10272                "85ad7832-896f-42be-af31-3e1ed2fbdc4b",
10273                "8bb7d938-ca46-4a6d-90eb-c92331155566",
10274                "cron_standup",
10275                "cron_reindex",
10276            ]
10277        );
10278
10279        let by_session = jobs_list(json!({
10280            "session": "7c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
10281            "homes": jobs_fixture_homes(),
10282        }));
10283        let jobs = by_session["result"]["jobs"].as_array().unwrap();
10284        assert_eq!(jobs.len(), 2, "{by_session}");
10285        assert!(jobs
10286            .iter()
10287            .all(|job| job["harness"] == "claude-code" && job["scope"] == "session"));
10288
10289        let by_profile = jobs_list(json!({
10290            "harness": "hermes",
10291            "profile": "ops",
10292            "homes": jobs_fixture_homes(),
10293        }));
10294        let jobs = by_profile["result"]["jobs"].as_array().unwrap();
10295        assert_eq!(jobs.len(), 1, "{by_profile}");
10296        assert_eq!(jobs[0]["id"], "ops-once-boot");
10297    }
10298
10299    #[test]
10300    fn jobs_get_answers_with_the_row_and_the_verbatim_native_record() {
10301        let mut service = HarnessSessionService::new();
10302        let hermes = service.handle(request(
10303            1,
10304            "harness.v1.jobs.get",
10305            json!({"harness": "hermes", "id": "digest-15m", "homes": jobs_fixture_homes()}),
10306        ));
10307        assert_eq!(hermes["result"]["job"]["schedule"]["kind"], "interval");
10308        // Native fields the uniform row does not carry survive on `source`.
10309        assert_eq!(hermes["result"]["source"]["provider"], "nous");
10310        assert_eq!(hermes["result"]["source"]["failure_deliver"], "local");
10311
10312        let claude = service.handle(request(
10313            2,
10314            "harness.v1.jobs.get",
10315            json!({"harness": "claude-code", "id": "release-watch", "homes": jobs_fixture_homes()}),
10316        ));
10317        assert_eq!(claude["result"]["job"]["payload"]["kind"], "prompt");
10318        assert_eq!(
10319            claude["result"]["source"]["tool_use_id"],
10320            "toolu_cron_release_watch"
10321        );
10322
10323        let missing = service.handle(request(
10324            3,
10325            "harness.v1.jobs.get",
10326            json!({"harness": "hermes", "id": "no-such-job", "homes": jobs_fixture_homes()}),
10327        ));
10328        assert!(missing["error"]["message"]
10329            .as_str()
10330            .is_some_and(|message| message.contains("no scheduled job `no-such-job`")));
10331    }
10332
10333    #[test]
10334    fn jobs_refuse_a_harness_without_a_scheduled_job_concept() {
10335        let mut service = HarnessSessionService::new();
10336        for (id, method, params) in [
10337            (
10338                1,
10339                "harness.v1.jobs.list",
10340                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10341            ),
10342            (
10343                2,
10344                "harness.v1.jobs.get",
10345                json!({"harness": "codex", "id": "anything"}),
10346            ),
10347        ] {
10348            let response = service.handle(request(id, method, params));
10349            assert_eq!(response["error"]["code"], -32020, "{response}");
10350            assert!(response["error"]["message"]
10351                .as_str()
10352                .is_some_and(|message| message.contains("has no scheduled jobs")));
10353            assert!(response.get("result").is_none());
10354        }
10355    }
10356
10357    #[test]
10358    fn jobs_list_reports_a_migrated_openclaw_store_as_absent_instead_of_failing() {
10359        let scratch = std::env::temp_dir().join(format!(
10360            "supercode-jobs-migrated-{}-{}",
10361            std::process::id(),
10362            generated_session_id()
10363        ));
10364        std::fs::create_dir_all(&scratch).unwrap();
10365        let response = jobs_list(json!({
10366            "harness": "openclaw",
10367            "homes": {"openclaw": scratch.clone()},
10368        }));
10369        let result = &response["result"];
10370        assert_eq!(result["jobs"].as_array().unwrap().len(), 0, "{result}");
10371        assert_eq!(result["sources"][0]["state"], "absent_store");
10372        assert_eq!(result["sources"][0]["harness"], "openclaw");
10373        std::fs::remove_dir_all(&scratch).ok();
10374    }
10375
10376    // ---------------------------------------------------------------------
10377    // ORCH-8 — `harness.v1.runs.list` / `runs.get` over the committed fire
10378    // stores: Hermes's `cron/executions.db` (root home + profile home) and
10379    // OpenClaw's `cron_run_logs`. Every fixture row is written by
10380    // `tests/fixtures/gen_runs_fixtures.py` against the harnesses' own DDL.
10381    // ---------------------------------------------------------------------
10382
10383    /// The health job in the committed OpenClaw fixture, which fired twice.
10384    const OPENCLAW_HEALTH_JOB: &str = "85ad7832-896f-42be-af31-3e1ed2fbdc4b";
10385    /// The digest job, whose single fire predates run ids.
10386    const OPENCLAW_DIGEST_JOB: &str = "8bb7d938-ca46-4a6d-90eb-c92331155566";
10387
10388    fn runs_list(params: Value) -> Value {
10389        let mut service = HarnessSessionService::new();
10390        service.handle(request(1, "harness.v1.runs.list", params))
10391    }
10392
10393    fn run_row<'a>(result: &'a Value, id: &str) -> &'a Value {
10394        result["runs"]
10395            .as_array()
10396            .expect("runs is an array")
10397            .iter()
10398            .find(|run| run["id"] == id)
10399            .unwrap_or_else(|| panic!("no run `{id}` in {result}"))
10400    }
10401
10402    #[test]
10403    fn runs_list_projects_both_fixture_stores_onto_the_uniform_row() {
10404        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10405        let result = &response["result"];
10406        let ids: Vec<&str> = result["runs"]
10407            .as_array()
10408            .expect("runs is an array")
10409            .iter()
10410            .map(|run| run["id"].as_str().unwrap())
10411            .collect();
10412        let digest_fire = format!("{OPENCLAW_DIGEST_JOB}#1");
10413        assert_eq!(
10414            ids,
10415            vec![
10416                // Hermes, newest claim first, root ledger then profile ledger.
10417                "b2c3d4e5f60718293a4b5c6d7e8f9012",
10418                "a1b2c3d4e5f60718293a4b5c6d7e8f90",
10419                "c3d4e5f60718293a4b5c6d7e8f901234",
10420                "f60718293a4b5c6d7e8f901234567890",
10421                "e5f60718293a4b5c6d7e8f9012345678",
10422                "d4e5f60718293a4b5c6d7e8f90123456",
10423                // OpenClaw, newest `ts` first.
10424                "run_health_0002",
10425                digest_fire.as_str(),
10426                "run_health_0001",
10427            ],
10428            "{result}"
10429        );
10430
10431        // The harness's OWN outcome word survives; nothing is renamed onto a
10432        // shared vocabulary.
10433        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10434        assert_eq!(failed["harness"], "hermes");
10435        assert_eq!(failed["job_id"], "job42");
10436        assert_eq!(failed["status"], "failed");
10437        assert_eq!(failed["error"], "provider returned 500 after 3 attempts");
10438        assert_eq!(failed["claimed_at"], "2026-09-02T13:05:00.100442");
10439
10440        // Hermes's `unknown` — an attempt whose owner died before writing a
10441        // terminal state — is a fourth status, not folded into `failed`.
10442        let abandoned = run_row(result, "d4e5f60718293a4b5c6d7e8f90123456");
10443        assert_eq!(abandoned["status"], "unknown");
10444        assert_eq!(abandoned["job_id"], "ops-once-boot");
10445
10446        // An unterminated fire has no finish, and no session is invented.
10447        let running = run_row(result, "c3d4e5f60718293a4b5c6d7e8f901234");
10448        assert_eq!(running["status"], "running");
10449        assert!(running["finished_at"].is_null(), "{running}");
10450        assert!(running["session_id"].is_null(), "{running}");
10451
10452        // OpenClaw records the session on the row itself, and epoch-ms
10453        // timestamps are rendered as RFC 3339.
10454        let ok = run_row(result, "run_health_0001");
10455        assert_eq!(ok["harness"], "openclaw");
10456        assert_eq!(ok["job_id"], OPENCLAW_HEALTH_JOB);
10457        assert_eq!(ok["status"], "ok");
10458        assert_eq!(ok["started_at"], "2026-09-02T08:30:00.000Z");
10459        assert_eq!(ok["finished_at"], "2026-09-02T08:30:30.000Z");
10460        assert_eq!(ok["session_id"], "3dd577ae-a0a3-4b5b-8063-f402be4f5fd4");
10461        // OpenClaw's run log is written once, at finish: there is no claim.
10462        assert!(ok["claimed_at"].is_null(), "{ok}");
10463
10464        // A run-log row with no `run_id` falls back to the store's own
10465        // `(job_id, seq)` key rather than being dropped.
10466        assert_eq!(run_row(result, &digest_fire)["status"], "skipped");
10467
10468        // ORCH-13: a fire whose delivery nothing recorded says so, rather than
10469        // borrowing a neighbouring fire's outcome. Both of these ran on jobs
10470        // that deliver `local` (or have no job record at all), so no
10471        // obligation is addressed to a surface they could match.
10472        for id in [
10473            "b2c3d4e5f60718293a4b5c6d7e8f9012",
10474            "d4e5f60718293a4b5c6d7e8f90123456",
10475        ] {
10476            assert!(run_row(result, id)["delivery"].is_null(), "{id}");
10477        }
10478
10479        // Every store consulted is named, including the profile home that has
10480        // no ledger — an empty history and an absent store are different.
10481        let sources = result["sources"].as_array().unwrap();
10482        let states: Vec<(&str, &str)> = sources
10483            .iter()
10484            .map(|source| {
10485                (
10486                    source["harness"].as_str().unwrap(),
10487                    source["state"].as_str().unwrap(),
10488                )
10489            })
10490            .collect();
10491        assert_eq!(
10492            states,
10493            vec![
10494                ("hermes", "read"),
10495                ("hermes", "absent_store"),
10496                ("hermes", "read"),
10497                ("openclaw", "read"),
10498            ],
10499            "{result}"
10500        );
10501        assert_eq!(sources[2]["profile"], "ops");
10502        assert!(sources[3]["path"]
10503            .as_str()
10504            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10505    }
10506
10507    #[test]
10508    fn runs_list_joins_a_hermes_fire_to_the_session_it_opened() {
10509        let response = runs_list(json!({
10510            "harness": "hermes",
10511            "job": "job42",
10512            "homes": jobs_fixture_homes(),
10513        }));
10514        let result = &response["result"];
10515        assert_eq!(result["runs"].as_array().unwrap().len(), 2, "{result}");
10516
10517        // Hermes writes NO link from an execution to its session. The fire
10518        // that ran the agent is joined to `cron_job42_<stamp>` because that
10519        // id's instant falls inside its [claimed_at, finished_at] window.
10520        let ran = run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90");
10521        assert_eq!(ran["session_id"], "cron_job42_20260902_120000");
10522
10523        // The later fire failed before opening one. Its window holds no
10524        // session, so the row says so instead of re-using the earlier fire's
10525        // — the join is per-FIRE, not per-job.
10526        let failed = run_row(result, "b2c3d4e5f60718293a4b5c6d7e8f9012");
10527        assert!(failed["session_id"].is_null(), "{failed}");
10528    }
10529
10530    /// ORCH-13: where a fire's output went, read from each harness's own
10531    /// delivery record — Hermes's `delivery_obligations` ledger inside
10532    /// `state.db`, OpenClaw's `delivery_*` run-log columns.
10533    #[test]
10534    fn runs_list_reads_the_delivery_each_harness_recorded_for_a_fire() {
10535        let response = runs_list(json!({"homes": jobs_fixture_homes()}));
10536        let result = &response["result"];
10537
10538        // Hermes: the ledger is the GATEWAY's, keyed by conversation and
10539        // surface, so the fire's own [claimed_at, finished_at] window picks
10540        // the obligation. The fire succeeded and so did the send.
10541        let delivered = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
10542        assert_eq!(delivered["status"], "completed");
10543        assert_eq!(delivered["delivery"]["state"], "delivered");
10544        assert_eq!(delivered["delivery"]["target"], "telegram:-100777:55");
10545        assert_eq!(delivered["delivery"]["attempts"], 1);
10546        assert!(delivered["delivery"]["last_error"].is_null(), "{delivered}");
10547        assert_eq!(
10548            delivered["delivery"]["delivered_at"],
10549            "2026-09-02T09:00:30.400Z"
10550        );
10551
10552        // The next fire of the same job ALSO succeeded — and its output never
10553        // arrived. That is the fact `status` alone cannot carry.
10554        let undelivered = run_row(result, "f60718293a4b5c6d7e8f901234567890");
10555        assert_eq!(undelivered["status"], "completed");
10556        assert_eq!(undelivered["delivery"]["state"], "failed");
10557        assert_eq!(undelivered["delivery"]["attempts"], 3);
10558        assert_eq!(
10559            undelivered["delivery"]["last_error"],
10560            "telegram send failed: Bad Request: chat not found"
10561        );
10562        // Only a delivered obligation carries an instant of delivery; the
10563        // ledger's `updated_at` on a failed row dates the failure.
10564        assert!(
10565            undelivered["delivery"]["delivered_at"].is_null(),
10566            "{undelivered}"
10567        );
10568
10569        // OpenClaw writes the outcome onto the run-log row and declares the
10570        // address on the job, so the row's target is joined from `cron_jobs`.
10571        let announced = run_row(result, "run_health_0001");
10572        assert_eq!(announced["delivery"]["state"], "delivered");
10573        assert_eq!(announced["delivery"]["target"], "last");
10574        // Its run log counts no attempts and stamps no delivered-at.
10575        assert!(announced["delivery"]["attempts"].is_null(), "{announced}");
10576        assert!(
10577            announced["delivery"]["delivered_at"].is_null(),
10578            "{announced}"
10579        );
10580        let refused = run_row(result, "run_health_0002");
10581        assert_eq!(refused["delivery"]["state"], "not-delivered");
10582        assert_eq!(refused["delivery"]["last_error"], "channel_not_found");
10583
10584        // A run-log row with no delivery columns at all recorded no delivery:
10585        // the job's declared target is not evidence that anything was sent.
10586        let skipped = run_row(result, &format!("{OPENCLAW_DIGEST_JOB}#1"));
10587        assert!(skipped["delivery"].is_null(), "{skipped}");
10588    }
10589
10590    /// A Hermes fire whose session carries a `session_key` is matched on that
10591    /// key FIRST — the most specific question the ledger can answer. Proven by
10592    /// moving the obligations off the job's surface on a COPY of the fixture,
10593    /// so only the session-key question can still find them.
10594    #[test]
10595    fn runs_list_matches_a_hermes_obligation_by_the_session_key_first() {
10596        let scratch = std::env::temp_dir().join(format!(
10597            "supercode-runs-delivery-{}-{}",
10598            std::process::id(),
10599            generated_session_id()
10600        ));
10601        std::fs::create_dir_all(scratch.join("cron")).unwrap();
10602        let fixture = jobs_fixture_root().join("hermes_home");
10603        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
10604        for name in ["cron/executions.db", "cron/jobs.json"] {
10605            std::fs::copy(fixture.join(name), scratch.join(name)).unwrap();
10606        }
10607        {
10608            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
10609            // The obligations now sit on a surface no job in this store
10610            // delivers to, so the surface question cannot match them.
10611            connection
10612                .execute(
10613                    "UPDATE delivery_obligations SET platform = 'slack', chat_id = 'C0FALLBACK'",
10614                    [],
10615                )
10616                .unwrap();
10617            // A cron fire that ran inside a keyed conversation: the session
10618            // the window recovers carries `tg-coder-1`'s key.
10619            connection
10620                .execute(
10621                    "INSERT INTO sessions (id, source, session_key, started_at) VALUES \
10622                     ('cron_coder-standup_20260902_090010', 'cron', \
10623                      'agent:coder:telegram:group:-100777:55', 1788339610.0)",
10624                    [],
10625                )
10626                .unwrap();
10627        }
10628        let response = runs_list(json!({
10629            "harness": "hermes",
10630            "job": "coder-standup",
10631            "homes": {"hermes": scratch.join("state.db")},
10632        }));
10633        let result = &response["result"];
10634        let matched = run_row(result, "e5f60718293a4b5c6d7e8f9012345678");
10635        assert_eq!(
10636            matched["session_id"], "cron_coder-standup_20260902_090010",
10637            "{result}"
10638        );
10639        assert_eq!(matched["delivery"]["state"], "delivered", "{result}");
10640        assert_eq!(
10641            matched["delivery"]["target"], "slack:C0FALLBACK:55",
10642            "{result}"
10643        );
10644        std::fs::remove_dir_all(&scratch).ok();
10645    }
10646
10647    #[test]
10648    fn runs_list_follows_a_compression_chain_to_the_readable_tip() {
10649        // A fire whose session was compressed mid-run is only readable at the
10650        // continuation, so that is what the row must report. Built on a COPY
10651        // of the committed fixture: no test writes to a fixture or to a real
10652        // harness home.
10653        let scratch = std::env::temp_dir().join(format!(
10654            "supercode-runs-compressed-{}-{}",
10655            std::process::id(),
10656            generated_session_id()
10657        ));
10658        std::fs::create_dir_all(scratch.join("cron")).unwrap();
10659        let fixture = jobs_fixture_root().join("hermes_home");
10660        std::fs::copy(fixture.join("state.db"), scratch.join("state.db")).unwrap();
10661        std::fs::copy(
10662            fixture.join("cron/executions.db"),
10663            scratch.join("cron/executions.db"),
10664        )
10665        .unwrap();
10666        {
10667            let connection = rusqlite::Connection::open(scratch.join("state.db")).unwrap();
10668            connection
10669                .execute(
10670                    "UPDATE sessions SET end_reason = 'compression' WHERE id = ?1",
10671                    ["cron_job42_20260902_120000"],
10672                )
10673                .unwrap();
10674            connection
10675                .execute(
10676                    "INSERT INTO sessions (id, source, parent_session_id, started_at) \
10677                     VALUES ('job42-after-compaction', 'cron', \
10678                             'cron_job42_20260902_120000', 1788350000.0)",
10679                    [],
10680                )
10681                .unwrap();
10682        }
10683        let response = runs_list(json!({
10684            "harness": "hermes",
10685            "job": "job42",
10686            "homes": {"hermes": scratch.join("state.db")},
10687        }));
10688        let result = &response["result"];
10689        assert_eq!(
10690            run_row(result, "a1b2c3d4e5f60718293a4b5c6d7e8f90")["session_id"],
10691            "job42-after-compaction",
10692            "{result}"
10693        );
10694        std::fs::remove_dir_all(&scratch).ok();
10695    }
10696
10697    #[test]
10698    fn runs_list_filters_by_job_and_caps_by_limit() {
10699        let by_job = runs_list(json!({
10700            "harness": "openclaw",
10701            "job": OPENCLAW_HEALTH_JOB,
10702            "homes": jobs_fixture_homes(),
10703        }));
10704        let ids: Vec<&str> = by_job["result"]["runs"]
10705            .as_array()
10706            .unwrap()
10707            .iter()
10708            .map(|run| run["id"].as_str().unwrap())
10709            .collect();
10710        assert_eq!(ids, vec!["run_health_0002", "run_health_0001"], "{by_job}");
10711
10712        let capped = runs_list(json!({
10713            "harness": "openclaw",
10714            "limit": 1,
10715            "homes": jobs_fixture_homes(),
10716        }));
10717        let runs = capped["result"]["runs"].as_array().unwrap();
10718        assert_eq!(runs.len(), 1, "{capped}");
10719        // Newest first, so the cap keeps the recent fire.
10720        assert_eq!(runs[0]["id"], "run_health_0002");
10721    }
10722
10723    #[test]
10724    fn runs_get_answers_with_the_row_and_the_verbatim_native_record() {
10725        let mut service = HarnessSessionService::new();
10726        let hermes = service.handle(request(
10727            1,
10728            "harness.v1.runs.get",
10729            json!({
10730                "harness": "hermes",
10731                "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
10732                "homes": jobs_fixture_homes(),
10733            }),
10734        ));
10735        assert_eq!(hermes["result"]["run"]["status"], "completed");
10736        assert_eq!(
10737            hermes["result"]["run"]["session_id"],
10738            "cron_job42_20260902_120000"
10739        );
10740        // Ledger columns the uniform row does not carry survive on `source`.
10741        assert_eq!(hermes["result"]["source"]["source"], "scheduler");
10742        assert_eq!(hermes["result"]["source"]["pid"], 4242);
10743        assert_eq!(hermes["result"]["source"]["process_id"], "9f1c2d");
10744
10745        let openclaw = service.handle(request(
10746            2,
10747            "harness.v1.runs.get",
10748            json!({
10749                "harness": "openclaw",
10750                "id": "run_health_0002",
10751                "homes": jobs_fixture_homes(),
10752            }),
10753        ));
10754        assert_eq!(openclaw["result"]["run"]["status"], "error");
10755        // ORCH-13: the run's delivery is projected AND the store's own columns
10756        // stay verbatim on `source`, so nothing about the fire is lost.
10757        assert_eq!(
10758            openclaw["result"]["source"]["delivery_status"],
10759            "not-delivered"
10760        );
10761        assert_eq!(
10762            openclaw["result"]["source"]["delivery_error"],
10763            "channel_not_found"
10764        );
10765        assert_eq!(openclaw["result"]["source"]["delivered"], 0);
10766        assert_eq!(
10767            openclaw["result"]["run"]["delivery"]["state"],
10768            "not-delivered"
10769        );
10770        assert_eq!(
10771            openclaw["result"]["run"]["delivery"]["last_error"],
10772            "channel_not_found"
10773        );
10774
10775        let missing = service.handle(request(
10776            3,
10777            "harness.v1.runs.get",
10778            json!({"harness": "hermes", "id": "no-such-run", "homes": jobs_fixture_homes()}),
10779        ));
10780        assert!(missing["error"]["message"]
10781            .as_str()
10782            .is_some_and(|message| message.contains("no run `no-such-run`")));
10783    }
10784
10785    #[test]
10786    fn runs_refuse_a_harness_that_keeps_no_run_store() {
10787        let mut service = HarnessSessionService::new();
10788        for (id, method, params) in [
10789            // Claude Code HAS scheduled jobs but no fire store: its fires are
10790            // ordinary turns. It must refuse, not answer with an empty list.
10791            (
10792                1,
10793                "harness.v1.runs.list",
10794                json!({"harness": "claude-code", "homes": jobs_fixture_homes()}),
10795            ),
10796            (
10797                2,
10798                "harness.v1.runs.get",
10799                json!({"harness": "claude-code", "id": "anything"}),
10800            ),
10801            (
10802                3,
10803                "harness.v1.runs.list",
10804                json!({"harness": "codex", "homes": jobs_fixture_homes()}),
10805            ),
10806        ] {
10807            let response = service.handle(request(id, method, params));
10808            assert_eq!(response["error"]["code"], -32020, "{response}");
10809            assert!(response["error"]["message"]
10810                .as_str()
10811                .is_some_and(|message| message.contains("keeps no run store")));
10812            assert!(response.get("result").is_none());
10813        }
10814    }
10815
10816    #[test]
10817    fn runs_list_reports_an_install_with_no_run_store_as_absent() {
10818        let scratch = std::env::temp_dir().join(format!(
10819            "supercode-runs-empty-{}-{}",
10820            std::process::id(),
10821            generated_session_id()
10822        ));
10823        std::fs::create_dir_all(&scratch).unwrap();
10824        let response = runs_list(json!({
10825            "harness": "openclaw",
10826            "homes": {"openclaw": scratch.clone()},
10827        }));
10828        let result = &response["result"];
10829        assert_eq!(result["runs"].as_array().unwrap().len(), 0, "{result}");
10830        assert_eq!(result["sources"][0]["state"], "absent_store");
10831        assert!(result["sources"][0]["path"]
10832            .as_str()
10833            .is_some_and(|path| path.ends_with("state/openclaw.sqlite")));
10834        std::fs::remove_dir_all(&scratch).ok();
10835    }
10836}