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::time::Duration;
10
11use serde::{Deserialize, Serialize};
12use serde_json::{json, Value};
13
14use crate::runtime::generated_session_id;
15#[cfg(feature = "adapter-api")]
16use crate::runtime::{HostedHarnessConnection, HostedHarnessRuntime};
17use crate::sdk::{
18    discover_session_page, load_session, load_session_with_fidelity, SdkCapabilities, SdkError,
19    SdkErrorCode, SdkEvent, SdkOperation, SdkRequest, SdkRuntimeEvent, SdkService,
20};
21use crate::watch::{bound_session_view, message_json, normalized_session_json};
22use crate::Fidelity;
23#[cfg(feature = "adapter-api")]
24use crate::SupercodeHttpRuntimeBackend;
25use crate::{
26    discover_live_runtime, harness_support_registry, AcpRuntimeBackend, ClaudeCodeRuntimeBackend,
27    CodexRuntimeBackend, DiscoveryQuery, HarnessCatalog, HarnessHomes, HarnessId,
28    ImplementationKind, LiveRuntimeEndpoint, LiveRuntimeSource, OpenCodeRuntimeBackend,
29    PiRuntimeBackend, Role, RuntimeAttachRequest, RuntimeBackend, RuntimeConnection, RuntimeInput,
30    RuntimeLaunch, RuntimeStartRequest, Session, SessionDescriptor, SessionFollower, SessionFormat,
31    SessionLocator, SessionSource,
32};
33use crate::{reduce, tokens};
34#[cfg(feature = "adapter-api")]
35use crate::{register_live_runtime, resolve_live_runtime, LiveRuntimeRegistration};
36
37/// Protocol namespace implemented by this service.
38pub const HARNESS_SERVICE_VERSION: &str = "harness.v1";
39/// Notification method emitted for followed-session changes.
40pub const SESSION_EVENT_METHOD: &str = "harness.v1.sessions.event";
41/// Notification method emitted for normalized session-activity transitions.
42pub const SESSION_ACTIVITY_EVENT_METHOD: &str = "harness.v1.sessions.activity_event";
43/// Notification method emitted for revisioned session-list changes.
44pub const SESSION_INDEX_EVENT_METHOD: &str = "harness.v1.sessions.index_event";
45/// Notification method emitted for live runtime events.
46pub const RUNTIME_EVENT_METHOD: &str = "harness.v1.runtimes.event";
47
48/// Stateful persisted-session service. Each instance owns its follow
49/// subscriptions; discovery and loading remain read-only.
50pub struct HarnessSessionService {
51    catalog: HarnessCatalog,
52    followers: BTreeMap<String, SessionFollower>,
53    followed_sources: BTreeMap<String, FollowedSource>,
54    activity_subscriptions: BTreeMap<String, ActivitySubscription>,
55    index_subscriptions: BTreeMap<String, crate::session_index::SessionIndexSubscription>,
56    #[cfg(feature = "adapter-api")]
57    activity_monitor: crate::session_activity::SessionActivityMonitor,
58    next_subscription: u64,
59    runtimes: BTreeMap<String, Box<dyn RuntimeConnection>>,
60    terminal_launches: BTreeMap<String, StructuredLaunch>,
61    runtime_sequences: BTreeMap<String, u64>,
62    next_runtime: u64,
63    reduction_store_root: Option<PathBuf>,
64}
65
66impl Default for HarnessSessionService {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl HarnessSessionService {
73    /// Create an empty service instance.
74    pub fn new() -> Self {
75        Self {
76            catalog: HarnessCatalog::new(),
77            followers: BTreeMap::new(),
78            followed_sources: BTreeMap::new(),
79            activity_subscriptions: BTreeMap::new(),
80            index_subscriptions: BTreeMap::new(),
81            #[cfg(feature = "adapter-api")]
82            activity_monitor: Default::default(),
83            next_subscription: 1,
84            runtimes: BTreeMap::new(),
85            terminal_launches: BTreeMap::new(),
86            runtime_sequences: BTreeMap::new(),
87            next_runtime: 1,
88            reduction_store_root: None,
89        }
90    }
91
92    /// Override the trusted, service-owned store used for durable reduction
93    /// bundles. Embedders and tests use this to keep all writes inside an
94    /// explicitly selected root; the CLI otherwise uses the normal
95    /// `$SUPERCODE_HOME/sessions` location.
96    pub fn with_reduction_store_root(mut self, root: impl Into<PathBuf>) -> Self {
97        self.reduction_store_root = Some(root.into());
98        self
99    }
100
101    /// Handle one JSON-RPC 2.0 request and return one JSON-RPC response.
102    #[cfg(feature = "adapter-api")]
103    pub fn handle(&mut self, request: Value) -> Value {
104        let id = request.get("id").cloned().unwrap_or(Value::Null);
105        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
106            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
107        }
108        let Some(method) = request.get("method").and_then(Value::as_str) else {
109            return rpc_error(id, -32600, "request is missing `method`");
110        };
111        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
112        match self.call(method, params) {
113            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
114            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
115            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
116            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
117            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
118            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
119        }
120    }
121
122    /// Handle either a persisted-session request or an asynchronous live
123    /// runtime request.
124    #[cfg(feature = "adapter-api")]
125    pub async fn handle_async(&mut self, request: Value) -> Value {
126        let method = request
127            .get("method")
128            .and_then(Value::as_str)
129            .unwrap_or_default();
130        if matches!(
131            method,
132            "harness.v1.harnesses.list" | "harness.v1.harnesses.probe"
133        ) {
134            let id = request.get("id").cloned().unwrap_or(Value::Null);
135            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
136                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
137            }
138            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
139            return match self.inventory_call(method, params).await {
140                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
141                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
142                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
143                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
144                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
145                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
146            };
147        }
148        if method == "harness.v1.sessions.message" {
149            let id = request.get("id").cloned().unwrap_or(Value::Null);
150            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
151                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
152            }
153            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
154            return match self.message_call(params).await {
155                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
156                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
157                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
158                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
159                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
160                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
161            };
162        }
163        if matches!(
164            method,
165            "harness.v1.harnesses.settings" | "harness.v1.harnesses.configure"
166        ) {
167            let id = request.get("id").cloned().unwrap_or(Value::Null);
168            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
169                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
170            }
171            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
172            return match self.harness_settings_call(method, params) {
173                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
174                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
175                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
176                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
177                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
178                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
179            };
180        }
181        if method == "harness.v1.sessions.activity.subscribe" {
182            let id = request.get("id").cloned().unwrap_or(Value::Null);
183            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
184                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
185            }
186            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
187            return match self.subscribe_session_activity(params).await {
188                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
189                Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
190                Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
191                Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
192                Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
193                Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
194            };
195        }
196        if let Some(operation) = SdkOperation::from_method(method) {
197            let id = request.get("id").cloned().unwrap_or(Value::Null);
198            if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
199                return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
200            }
201            let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
202            return match self.execute(SdkRequest { operation, params }).await {
203                Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
204                Err(error) => sdk_rpc_error(id, &error),
205            };
206        }
207        if !method.starts_with("harness.v1.runtimes.") {
208            return self.handle(request);
209        }
210        let id = request.get("id").cloned().unwrap_or(Value::Null);
211        if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
212            return rpc_error(id, -32600, "expected a JSON-RPC 2.0 request");
213        }
214        let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
215        match self.runtime_call(method, params).await {
216            Ok(result) => json!({"jsonrpc": "2.0", "id": id, "result": result}),
217            Err(ServiceError::InvalidParams(message)) => rpc_error(id, -32602, &message),
218            Err(ServiceError::MethodNotFound) => rpc_error(id, -32601, "method not found"),
219            Err(ServiceError::UnsupportedAction(message)) => rpc_error(id, -32020, &message),
220            Err(ServiceError::Operation(message)) => rpc_error(id, -32000, &message),
221            Err(ServiceError::Sdk(error)) => sdk_rpc_error(id, &error),
222        }
223    }
224
225    /// Poll all active subscriptions once and return zero or more JSON-RPC
226    /// notifications. Recoverable follower errors are delivered as events.
227    #[cfg(feature = "adapter-api")]
228    pub fn poll(&mut self) -> Vec<Value> {
229        let mut notifications = Vec::new();
230        for (subscription, follower) in &mut self.followers {
231            match follower.poll() {
232                Ok(Some(event)) => notifications.push(json!({
233                    "jsonrpc": "2.0",
234                    "method": SESSION_EVENT_METHOD,
235                    "params": {
236                        "subscription": subscription,
237                        "event": event.to_json(),
238                    }
239                })),
240                Ok(None) => {}
241                Err(error) => notifications.push(json!({
242                    "jsonrpc": "2.0",
243                    "method": SESSION_EVENT_METHOD,
244                    "params": {
245                        "subscription": subscription,
246                        "event": {
247                            "type": "watch_error",
248                            "recoverable": true,
249                            "message": error.to_string(),
250                        },
251                    }
252                })),
253            }
254        }
255        notifications
256    }
257
258    /// Report each followed session's live-runtime lifecycle state on that
259    /// session's own subscription, emitting only when the state changes.
260    ///
261    /// A growing transcript is not evidence that an agent is working, so the
262    /// state comes from the live-runtime registry and nowhere else. A followed
263    /// session with no registered Supercode runtime — a harness running outside
264    /// Supercode — reports `persisted`, which says plainly that its activity is
265    /// unknown rather than guessing at it. These events carry no sequence
266    /// number and no transcript content; they never interleave with the
267    /// content follower's sequenced stream.
268    #[cfg(feature = "adapter-api")]
269    pub async fn poll_session_runtime_states(&mut self) -> Vec<Value> {
270        let registry = crate::LocalRuntimeRegistry::new();
271        let authorization = crate::RuntimeAuthorization::observer();
272        let mut notifications = Vec::new();
273        for (subscription, source) in &mut self.followed_sources {
274            let state = match registry
275                .source_state(&source.harness, &source.session_id, &authorization)
276                .await
277            {
278                Ok(Some(state)) => state,
279                Ok(None) => crate::RuntimeRegistryState::Persisted,
280                // A failed registry read is not evidence of a state change.
281                Err(_) => continue,
282            };
283            if source.reported.as_deref() == Some(state.as_str()) {
284                continue;
285            }
286            source.reported = Some(state.as_str().to_string());
287            notifications.push(json!({
288                "jsonrpc": "2.0",
289                "method": SESSION_EVENT_METHOD,
290                "params": {
291                    "subscription": subscription,
292                    "event": {"type": "runtime_state", "state": state.as_str()},
293                },
294            }));
295        }
296        notifications
297    }
298
299    /// Poll normalized activity subscriptions, emitting only proven state
300    /// transitions. Every subscription is bulk-sampled so stock-harness
301    /// process and registry discovery happens once per UI, not once per row.
302    #[cfg(feature = "adapter-api")]
303    pub async fn poll_session_activities(&mut self) -> Vec<Value> {
304        let subscriptions = self
305            .activity_subscriptions
306            .iter()
307            .map(|(id, subscription)| {
308                (
309                    id.clone(),
310                    subscription.locators.clone(),
311                    subscription.homes.clone(),
312                )
313            })
314            .collect::<Vec<_>>();
315        let mut notifications = Vec::new();
316        for (subscription_id, locators, homes) in subscriptions {
317            let Ok(activities) = self.activity_monitor.resolve(&locators, &homes).await else {
318                // A failed evidence read proves no transition. Retain the last
319                // good state instead of flashing every row to persisted.
320                continue;
321            };
322            let Some(subscription) = self.activity_subscriptions.get_mut(&subscription_id) else {
323                continue;
324            };
325            let mut changed = Vec::new();
326            for activity in activities {
327                let key = activity.key();
328                if subscription
329                    .reported
330                    .get(&key)
331                    .is_some_and(|previous| previous.same_state(&activity))
332                {
333                    continue;
334                }
335                subscription.reported.insert(key, activity.clone());
336                changed.push(activity);
337            }
338            if !changed.is_empty() {
339                notifications.push(json!({
340                    "jsonrpc": "2.0",
341                    "method": SESSION_ACTIVITY_EVENT_METHOD,
342                    "params": {
343                        "subscription": subscription_id,
344                        "activities": changed,
345                    },
346                }));
347            }
348        }
349        notifications
350    }
351
352    /// Drain native-store invalidations and emit revisioned descriptor deltas.
353    /// An idle subscription performs no catalog or transcript reads between
354    /// its minute-scale recovery reconciliations.
355    #[cfg(feature = "adapter-api")]
356    pub fn poll_session_indexes(&mut self) -> Vec<Value> {
357        let mut notifications = Vec::new();
358        for (subscription, index) in &mut self.index_subscriptions {
359            let homes = index.homes().clone();
360            match index.poll() {
361                Ok(Some(delta)) => match live_index_changes(delta.changes, &homes) {
362                    Ok(changes) => notifications.push(json!({
363                        "jsonrpc": "2.0",
364                        "method": SESSION_INDEX_EVENT_METHOD,
365                        "params": {
366                            "subscription": subscription,
367                            "revision": delta.revision,
368                            "changes": changes,
369                        },
370                    })),
371                    Err(error) => notifications.push(json!({
372                        "jsonrpc": "2.0",
373                        "method": SESSION_INDEX_EVENT_METHOD,
374                        "params": {
375                            "subscription": subscription,
376                            "error": {"recoverable": true, "message": error_message(error)},
377                        },
378                    })),
379                },
380                Ok(None) => {}
381                Err(error) => notifications.push(json!({
382                    "jsonrpc": "2.0",
383                    "method": SESSION_INDEX_EVENT_METHOD,
384                    "params": {
385                        "subscription": subscription,
386                        "error": {"recoverable": true, "message": error},
387                    },
388                })),
389            }
390        }
391        notifications
392    }
393
394    #[cfg(feature = "adapter-api")]
395    async fn subscribe_session_activity(
396        &mut self,
397        params: Value,
398    ) -> std::result::Result<Value, ServiceError> {
399        let params = decode::<ActivitySubscribeParams>(params)?;
400        if params.locators.is_empty() {
401            return Err(ServiceError::InvalidParams(
402                "sessions.activity.subscribe requires at least one locator".into(),
403            ));
404        }
405        if params.locators.len() > 2_048 {
406            return Err(ServiceError::InvalidParams(
407                "sessions.activity.subscribe accepts at most 2048 locators".into(),
408            ));
409        }
410        let initial = self
411            .activity_monitor
412            .resolve(&params.locators, &params.homes)
413            .await
414            .map_err(ServiceError::Sdk)?;
415        let subscription = format!("activity-sub-{}", self.next_subscription);
416        self.next_subscription += 1;
417        let reported = initial
418            .iter()
419            .cloned()
420            .map(|activity| (activity.key(), activity))
421            .collect();
422        self.activity_subscriptions.insert(
423            subscription.clone(),
424            ActivitySubscription {
425                locators: params.locators,
426                homes: params.homes,
427                reported,
428            },
429        );
430        Ok(json!({"subscription": subscription, "initial": initial}))
431    }
432
433    /// Non-blockingly sample one event from every connected live runtime.
434    #[cfg(feature = "adapter-api")]
435    pub async fn poll_runtimes(&mut self) -> Vec<Value> {
436        self.poll_sdk_events()
437            .await
438            .into_iter()
439            .map(|(connection, runtime_event)| {
440                json!({
441                    "jsonrpc": "2.0",
442                    "method": RUNTIME_EVENT_METHOD,
443                    "params": {
444                        "connection": connection,
445                        "session_id": runtime_event.session_id,
446                        "sequence": runtime_event.event.sequence,
447                        "event": {
448                            "kind": runtime_event.event.kind,
449                            "payload": runtime_event.event.payload,
450                        },
451                    },
452                })
453            })
454            .collect()
455    }
456
457    async fn poll_sdk_events(&mut self) -> Vec<(String, SdkRuntimeEvent)> {
458        let mut events = Vec::new();
459        let mut closed = Vec::new();
460        for (connection, runtime) in &mut self.runtimes {
461            let session_id = runtime.handle().runtime_id.clone();
462            match tokio::time::timeout(Duration::from_millis(1), runtime.next_event()).await {
463                Ok(Ok(Some(event))) => {
464                    let terminal = event.kind == "transport_closed";
465                    let next_sequence = self
466                        .runtime_sequences
467                        .entry(session_id.clone())
468                        .or_insert(0);
469                    let sequence = event.sequence.unwrap_or_else(|| {
470                        *next_sequence = next_sequence.saturating_add(1);
471                        *next_sequence
472                    });
473                    *next_sequence = (*next_sequence).max(sequence);
474                    events.push((
475                        connection.clone(),
476                        SdkRuntimeEvent {
477                            session_id: session_id.clone(),
478                            event: SdkEvent {
479                                sequence,
480                                kind: event.kind,
481                                payload: event.payload,
482                            },
483                        },
484                    ));
485                    if terminal {
486                        closed.push(connection.clone());
487                    }
488                }
489                Ok(Ok(None)) => {
490                    let sequence = self
491                        .runtime_sequences
492                        .entry(session_id.clone())
493                        .or_insert(0);
494                    *sequence = sequence.saturating_add(1);
495                    events.push((
496                        connection.clone(),
497                        SdkRuntimeEvent {
498                            session_id,
499                            event: SdkEvent {
500                                sequence: *sequence,
501                                kind: "transport_closed".into(),
502                                payload: json!({"message": "Harness runtime transport closed."}),
503                            },
504                        },
505                    ));
506                    closed.push(connection.clone());
507                }
508                Err(_) => {}
509                Ok(Err(error)) => {
510                    let sequence = self
511                        .runtime_sequences
512                        .entry(session_id.clone())
513                        .or_insert(0);
514                    *sequence = sequence.saturating_add(1);
515                    events.push((
516                        connection.clone(),
517                        SdkRuntimeEvent {
518                            session_id,
519                            event: SdkEvent {
520                                sequence: *sequence,
521                                kind: "transport_error".into(),
522                                payload: json!({"message": error.to_string(), "terminal": true}),
523                            },
524                        },
525                    ));
526                    closed.push(connection.clone());
527                }
528            }
529        }
530        for connection in closed {
531            if let Some(runtime) = self.runtimes.remove(&connection) {
532                self.runtime_sequences.remove(&runtime.handle().runtime_id);
533            }
534            self.terminal_launches.remove(&connection);
535        }
536        events
537    }
538
539    fn call(&mut self, method: &str, params: Value) -> std::result::Result<Value, ServiceError> {
540        match method {
541            "harness.v1.capabilities" => Ok(json!({
542                "version": HARNESS_SERVICE_VERSION,
543                "sdk": self.capabilities(),
544                "methods": [
545                    "harness.v1.support.report",
546                    "harness.v1.harnesses.list",
547                    "harness.v1.harnesses.probe",
548                    "harness.v1.harnesses.settings",
549                    "harness.v1.harnesses.configure",
550                    "harness.v1.sessions.discover",
551                    "harness.v1.sessions.load",
552                    "harness.v1.sessions.follow",
553                    "harness.v1.sessions.unfollow",
554                    "harness.v1.sessions.activity.subscribe",
555                    "harness.v1.sessions.activity.unsubscribe",
556                    "harness.v1.sessions.index.subscribe",
557                    "harness.v1.sessions.index.unsubscribe",
558                    "harness.v1.sessions.message",
559                    "harness.v1.sessions.import",
560                    "harness.v1.sessions.export",
561                    "harness.v1.sessions.translate",
562                    "harness.v1.sessions.reduce",
563                    "harness.v1.sessions.branch",
564                    "harness.v1.sessions.handoff",
565                    "harness.v1.sessions.resume_instructions",
566                    "harness.v1.runtimes.capabilities",
567                    "harness.v1.runtimes.start",
568                    "harness.v1.runtimes.resume",
569                    "harness.v1.runtimes.attach_existing",
570                    "harness.v1.runtimes.attach",
571                    "harness.v1.runtimes.send_input",
572                    "harness.v1.runtimes.interrupt",
573                    "harness.v1.runtimes.steer",
574                    "harness.v1.runtimes.respond",
575                    "harness.v1.runtimes.terminal_instructions",
576                    "harness.v1.runtimes.close",
577                ],
578                "notifications": [
579                    SESSION_EVENT_METHOD,
580                    SESSION_ACTIVITY_EVENT_METHOD,
581                    SESSION_INDEX_EVENT_METHOD,
582                    RUNTIME_EVENT_METHOD
583                ],
584                "harnesses": harness_support_registry()
585                    .harnesses
586                    .into_iter()
587                    .map(|harness| harness.id)
588                    .collect::<Vec<_>>(),
589            })),
590            "harness.v1.support.report" => serde_json::to_value(harness_support_registry())
591                .map_err(|error| ServiceError::Operation(error.to_string())),
592            "harness.v1.sessions.discover" => {
593                let query = decode::<DiscoveryQuery>(params)?;
594                let page = discover_session_page(&query).map_err(operation)?;
595                // Claude Code is the one harness that publishes its RUNNING
596                // sessions. The registry is read once per discovery and joined
597                // by session id; every record in it has already survived a
598                // `kill(pid, 0)` liveness check inside `read_registry`.
599                let peers = if page
600                    .sessions
601                    .iter()
602                    .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
603                {
604                    crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(
605                        &query.homes,
606                    ))
607                } else {
608                    Vec::new()
609                };
610                let activities = crate::session_activity::resolve_stock_session_activities(
611                    &page
612                        .sessions
613                        .iter()
614                        .map(|session| session.locator.clone())
615                        .collect::<Vec<_>>(),
616                    &query.homes,
617                )
618                .into_iter()
619                .map(|activity| (activity.key(), activity))
620                .collect::<BTreeMap<_, _>>();
621                let sessions = page
622                    .sessions
623                    .into_iter()
624                    .map(|session| {
625                        let mut value = live_descriptor_value(&session, &peers)?;
626                        let activity_key = (
627                            session.locator.harness.as_str().to_string(),
628                            session.locator.session_id.clone(),
629                        );
630                        if let Some(activity) = activities.get(&activity_key) {
631                            value["activity"] = serde_json::to_value(activity)
632                                .map_err(|error| ServiceError::Operation(error.to_string()))?;
633                            if let Some(status) = legacy_live_status(activity) {
634                                value["live_status"] = json!(status);
635                            }
636                        }
637                        Ok(value)
638                    })
639                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
640                Ok(json!({"sessions": sessions, "next_cursor": page.next_cursor}))
641            }
642            "harness.v1.sessions.load" => {
643                let params = decode::<LoadSessionParams>(params)?;
644                if let Some(options) = &params.options {
645                    options.validate()?;
646                    return load_session(&params.read.locator)
647                        .map(|session| projected_session_result(&session, options))
648                        .map_err(operation);
649                }
650                let mut session = if params.read.display_history() {
651                    self.catalog
652                        .load_display_view(
653                            &params.read.locator,
654                            params.read.read_fidelity(),
655                            params.read.tail_messages().unwrap_or(500),
656                        )
657                        .map_err(crate::Error::from)
658                } else if params.read.include_subagents() {
659                    load_session_with_fidelity(&params.read.locator, params.read.read_fidelity())
660                } else {
661                    self.catalog
662                        .load_parent_with_fidelity(
663                            &params.read.locator,
664                            params.read.read_fidelity(),
665                        )
666                        .map_err(crate::Error::from)
667                }
668                .map_err(operation)?;
669                params.read.bound_session(&mut session);
670                Ok(json!({"session": normalized_session_json(&session)}))
671            }
672            "harness.v1.sessions.follow" => {
673                let params = decode::<LocatorParams>(params)?;
674                let mut follower = self
675                    .catalog
676                    .follow_read_view(
677                        &params.locator,
678                        params.read_fidelity(),
679                        params.include_subagents(),
680                        params.tail_messages(),
681                        params.max_message_chars(),
682                        params.display_history(),
683                    )
684                    .map_err(operation)?;
685                let initial = follower
686                    .poll()
687                    .map_err(operation)?
688                    .map(|event| event.to_json());
689                let subscription = format!("sub-{}", self.next_subscription);
690                self.next_subscription += 1;
691                self.followers.insert(subscription.clone(), follower);
692                self.followed_sources.insert(
693                    subscription.clone(),
694                    FollowedSource {
695                        harness: params.locator.harness.as_str().to_string(),
696                        session_id: params.locator.session_id.clone(),
697                        reported: None,
698                    },
699                );
700                Ok(json!({"subscription": subscription, "initial": initial}))
701            }
702            "harness.v1.sessions.unfollow" => {
703                let params = decode::<UnfollowParams>(params)?;
704                self.followed_sources.remove(&params.subscription);
705                Ok(json!({
706                    "removed": self.followers.remove(&params.subscription).is_some()
707                }))
708            }
709            "harness.v1.sessions.activity.unsubscribe" => {
710                let params = decode::<UnfollowParams>(params)?;
711                Ok(json!({
712                    "removed": self.activity_subscriptions.remove(&params.subscription).is_some()
713                }))
714            }
715            "harness.v1.sessions.index.subscribe" => {
716                let query = decode::<DiscoveryQuery>(params)?;
717                crate::session_index::validate_query(&query)
718                    .map_err(ServiceError::InvalidParams)?;
719                let homes = query.homes.clone();
720                let (index, initial) = crate::session_index::SessionIndexSubscription::open(query)
721                    .map_err(ServiceError::Operation)?;
722                let peers = peers_for_descriptors(&initial, &homes);
723                let initial = initial
724                    .iter()
725                    .map(|descriptor| live_descriptor_value(descriptor, &peers))
726                    .collect::<std::result::Result<Vec<_>, ServiceError>>()?;
727                let subscription = format!("index-sub-{}", self.next_subscription);
728                self.next_subscription += 1;
729                self.index_subscriptions.insert(subscription.clone(), index);
730                Ok(json!({
731                    "subscription": subscription,
732                    "revision": 1,
733                    "initial": initial,
734                }))
735            }
736            "harness.v1.sessions.index.unsubscribe" => {
737                let params = decode::<UnfollowParams>(params)?;
738                Ok(json!({
739                    "removed": self.index_subscriptions.remove(&params.subscription).is_some()
740                }))
741            }
742            "harness.v1.sessions.import" => {
743                let params = decode::<ImportSessionParams>(params)?;
744                let session = Session::load_str(&params.content, params.source_harness.into())
745                    .map_err(operation)?;
746                Ok(json!({"session": normalized_session_json(&session)}))
747            }
748            "harness.v1.sessions.export" | "harness.v1.sessions.translate" => {
749                let params = decode::<ExportSessionParams>(params)?;
750                let session = load_session(&params.locator).map_err(operation)?;
751                let artifact = session_artifact(&params.locator, &session, params.target_harness)?;
752                Ok(json!({"artifact": artifact}))
753            }
754            "harness.v1.sessions.reduce" => {
755                let params = decode::<ReduceSessionParams>(params)?;
756                self.reduce_session(params)
757            }
758            "harness.v1.sessions.branch" => {
759                let params = decode::<BranchSessionParams>(params)?;
760                let session = load_session(&params.locator).map_err(operation)?;
761                let storage = params.locator.storage.path().display().to_string();
762                let bootstrap_prompt = format!(
763                    "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.",
764                    params.locator.harness.as_str(), params.locator.session_id, storage
765                );
766                let artifact = params
767                    .target_harness
768                    .map(|target| session_artifact(&params.locator, &session, target))
769                    .transpose()?;
770                Ok(json!({
771                    "parent": params.locator,
772                    "session": normalized_session_json(&session),
773                    "bootstrap_prompt": bootstrap_prompt,
774                    "artifact": artifact,
775                }))
776            }
777            "harness.v1.sessions.handoff" => {
778                let params = decode::<HandoffSessionParams>(params)?;
779                let session = load_session(&params.locator).map_err(operation)?;
780                let cwd = params
781                    .cwd
782                    .or_else(|| session.meta.cwd.clone())
783                    .unwrap_or_else(|| PathBuf::from("."));
784                let artifact =
785                    handoff_artifact(&params.locator, &session, params.target_harness, &cwd)?;
786                let target_session_id = artifact.session_id.as_deref().ok_or_else(|| {
787                    ServiceError::Operation(
788                        "handoff artifact omitted target session identity".into(),
789                    )
790                })?;
791                let instructions =
792                    handoff_instructions(params.target_harness, target_session_id, &cwd);
793                Ok(json!({
794                    "artifact": artifact,
795                    "launch": instructions.launch,
796                    "materialize": instructions.materialize,
797                    "requires_materialization": instructions.requires_materialization,
798                    "note": instructions.note,
799                }))
800            }
801            "harness.v1.sessions.resume_instructions" => {
802                let params = decode::<ResumeInstructionsParams>(params)?;
803                let session = load_session(&params.locator).map_err(operation)?;
804                let cwd = params
805                    .cwd
806                    .or(session.meta.cwd)
807                    .unwrap_or_else(|| PathBuf::from("."));
808                let launch = resume_launch(
809                    params.locator.harness.as_str(),
810                    &params.locator.session_id,
811                    &cwd,
812                    params.policy,
813                )?;
814                Ok(json!({"launch": launch}))
815            }
816            _ => Err(ServiceError::MethodNotFound),
817        }
818    }
819
820    fn reduce_session(
821        &self,
822        params: ReduceSessionParams,
823    ) -> std::result::Result<Value, ServiceError> {
824        let session = load_session(&params.locator).map_err(operation)?;
825        if session.messages.is_empty() {
826            return Err(ServiceError::InvalidParams(
827                "cannot reduce an empty session".into(),
828            ));
829        }
830        let keep_last = params.keep_last.clamp(1, 128);
831        let policy = reduce::ReductionPolicy {
832            clear_turns_older_than: Some(keep_last),
833            ..Default::default()
834        };
835        let (view, log) =
836            reduce::project_messages(&session.messages, &policy, &reduce::ReductionLog::default());
837        if log.reductions.is_empty() {
838            return Err(ServiceError::UnsupportedAction(format!(
839                "session `{}` is already too small for a meaningful reversible reduction",
840                params.locator.session_id
841            )));
842        }
843        let source_tokens = tokens::estimate_view_tokens(&session.messages);
844        let reduced_tokens = tokens::estimate_view_tokens(&view);
845        if reduced_tokens >= source_tokens {
846            return Err(ServiceError::UnsupportedAction(format!(
847                "session `{}` has no token-reducing reversible projection",
848                params.locator.session_id
849            )));
850        }
851
852        let store_root = self
853            .reduction_store_root
854            .clone()
855            .unwrap_or_else(default_reduction_store_root);
856        let store = crate::SessionStore::open(&store_root).map_err(operation)?;
857        let rescue_id = format!("rescue-{}", generated_session_id());
858        let imported = session
859            .imported_message_count
860            .unwrap_or(session.messages.len())
861            .min(session.messages.len());
862        let sidecar_jsonl = session.to_native_jsonl_v2(&session.messages[imported..]);
863        let view_jsonl = messages_jsonl(&view)?;
864        let title = format!(
865            "Reduced {} continuation from {}",
866            params.target_harness.id(),
867            params.locator.session_id
868        );
869
870        // Durability order is intentional: the full source of truth lands
871        // before either object that can refer to it. A crash may leave an
872        // unused sidecar, but can never leave a reduced view whose originals
873        // were not durably written first.
874        store
875            .save_sidecar(&rescue_id, &sidecar_jsonl)
876            .map_err(operation)?;
877        store
878            .save_reduction_log(&rescue_id, &log)
879            .map_err(operation)?;
880        store
881            .save(&rescue_id, &title, &view_jsonl)
882            .map_err(operation)?;
883
884        let source_bytes = serde_json::to_vec(&session.messages)
885            .map_err(|error| ServiceError::Operation(error.to_string()))?
886            .len() as u64;
887        let reduced_bytes = serde_json::to_vec(&view)
888            .map_err(|error| ServiceError::Operation(error.to_string()))?
889            .len() as u64;
890        store
891            .set_reduction_stats(
892                &rescue_id,
893                &title,
894                source_bytes,
895                reduced_bytes,
896                log.reductions.len() as u32,
897            )
898            .map_err(operation)?;
899
900        // The receipt is issued only after a real disk reload. This proves
901        // the exact files another process will consume, not the convenient
902        // in-memory values that produced them.
903        let reloaded_sidecar = store
904            .load_sidecar(&rescue_id)
905            .map_err(operation)?
906            .ok_or_else(|| ServiceError::Operation("reduction sidecar disappeared".into()))?;
907        let reloaded_sidecar = Session::from_sidecar_str(&reloaded_sidecar).map_err(operation)?;
908        let reloaded_log = store
909            .load_reduction_log(&rescue_id)
910            .map_err(operation)?
911            .ok_or_else(|| ServiceError::Operation("reduction log disappeared".into()))?;
912        let reloaded_view = parse_messages_jsonl(&store.load(&rescue_id).map_err(operation)?)?;
913        reduce::verify_log(&reloaded_log, &reloaded_sidecar).map_err(operation)?;
914        // `sc.reduction` is deliberately in-memory-only metadata: it must
915        // never leak onto a provider-facing transcript. Reapplying the
916        // durable log to the durable sidecar restores those ids. Comparing
917        // its wire form with the transcript reloaded above proves that the
918        // persisted view is exactly the deterministic projection before we
919        // use the restamped form for inversion.
920        let (restamped_view, restamped_log) =
921            reduce::project_messages(&reloaded_sidecar.messages, &policy, &reloaded_log);
922        if messages_jsonl(&restamped_view)? != messages_jsonl(&reloaded_view)? {
923            return Err(ServiceError::Operation(
924                "persisted reduction view does not match its durable log and sidecar".into(),
925            ));
926        }
927        if restamped_log != reloaded_log {
928            return Err(ServiceError::Operation(
929                "reapplying the durable reduction log changed its identity".into(),
930            ));
931        }
932        let inverted =
933            reduce::invert(&restamped_view, &reloaded_log, &reloaded_sidecar).map_err(operation)?;
934        if inverted != session.messages {
935            return Err(ServiceError::Operation(
936                "reduction inversion did not restore the source messages byte-exactly".into(),
937            ));
938        }
939
940        let ratio = source_tokens as f64 / reduced_tokens.max(1) as f64;
941        let sidecar_path = store.sidecar_path(&rescue_id);
942        let reduction_log_path = store.reduction_log_path(&rescue_id).map_err(operation)?;
943        let bootstrap_prompt = reduced_bootstrap_prompt(
944            &params.locator,
945            params.target_harness,
946            &view_jsonl,
947            &sidecar_path,
948            &reduction_log_path,
949        );
950        let mut reduced_session = session.clone();
951        reduced_session.meta.session_id = Some(rescue_id.clone());
952        reduced_session.messages = view;
953
954        Ok(json!({
955            "session": normalized_session_json(&reduced_session),
956            "bootstrap_prompt": bootstrap_prompt,
957            "receipt": {
958                "id": rescue_id,
959                "sidecar_id": rescue_id,
960                "source_harness": params.locator.harness,
961                "target_harness": params.target_harness.id(),
962                "source_tokens": source_tokens,
963                "reduced_tokens": reduced_tokens,
964                "ratio": ratio,
965                "source_bytes": source_bytes,
966                "reduced_bytes": reduced_bytes,
967                "reductions": reloaded_log.reductions.len(),
968                "sidecar_path": sidecar_path,
969                "reduction_log_path": reduction_log_path,
970                "verified": true,
971                "reversible": true,
972            }
973        }))
974    }
975
976    async fn runtime_call(
977        &mut self,
978        method: &str,
979        params: Value,
980    ) -> std::result::Result<Value, ServiceError> {
981        match method {
982            "harness.v1.runtimes.capabilities" => {
983                let params = decode::<RuntimeBackendParams>(params)?;
984                let backend = runtime_backend(&params)?;
985                Ok(json!({
986                    "harness": backend.harness(),
987                    "capabilities": backend.capabilities(),
988                }))
989            }
990            "harness.v1.runtimes.start" => {
991                let params = decode::<RuntimeStartParams>(params)?;
992                let backend = runtime_backend(&params.backend)?;
993                let capabilities = backend.capabilities();
994                let workspace = params.cwd.clone();
995                let runtime = backend
996                    .start(RuntimeStartRequest {
997                        cwd: params.cwd,
998                        launch: runtime_launch(&params.backend),
999                    })
1000                    .await
1001                    .map_err(operation)?;
1002                self.insert_hosted_runtime(runtime, capabilities, workspace)
1003                    .await
1004            }
1005            "harness.v1.runtimes.resume" | "harness.v1.runtimes.attach" => {
1006                let params = decode::<RuntimeAttachParams>(params)?;
1007                let backend = runtime_backend(&params.backend)?;
1008                let capabilities = backend.capabilities();
1009                let workspace = params.cwd.clone().unwrap_or_else(|| {
1010                    std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
1011                });
1012                let runtime = backend
1013                    .attach(RuntimeAttachRequest {
1014                        runtime_id: params.runtime_id,
1015                        cwd: params.cwd,
1016                        launch: runtime_launch(&params.backend),
1017                    })
1018                    .await
1019                    .map_err(operation)?;
1020                self.insert_hosted_runtime(runtime, capabilities, workspace)
1021                    .await
1022            }
1023            "harness.v1.runtimes.attach_existing" => {
1024                let params = decode::<RuntimeAttachParams>(params)?;
1025                let backend: Box<dyn RuntimeBackend> = match params
1026                    .backend
1027                    .base_url
1028                    .as_deref()
1029                    .and_then(|value| LiveRuntimeEndpoint::parse(value).ok())
1030                {
1031                    Some(endpoint) => {
1032                        #[cfg(not(feature = "adapter-api"))]
1033                        {
1034                            let _ = endpoint;
1035                            return Err(ServiceError::UnsupportedAction(
1036                                "live HTTP attachment adapter is not compiled".into(),
1037                            ));
1038                        }
1039                        #[cfg(feature = "adapter-api")]
1040                        {
1041                            let workspace = params.cwd.clone().ok_or_else(|| {
1042                                ServiceError::InvalidParams(
1043                                    "Supercode live attach requires the project cwd".into(),
1044                                )
1045                            })?;
1046                            let source = LiveRuntimeSource {
1047                                harness: params.backend.harness.as_str().to_string(),
1048                                session_id: params.runtime_id.clone(),
1049                                workspace,
1050                            };
1051                            let receipt = resolve_live_runtime(&endpoint, &source)
1052                                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1053                            Box::new(SupercodeHttpRuntimeBackend::new(receipt))
1054                        }
1055                    }
1056                    None => runtime_backend(&params.backend)?,
1057                };
1058                if !backend.capabilities().attach_existing_process {
1059                    return Err(ServiceError::Operation(format!(
1060                        "{} cannot attach to an already-running process; use runtimes.resume for a persisted session",
1061                        backend.harness().as_str()
1062                    )));
1063                }
1064                let runtime = backend
1065                    .attach_existing(RuntimeAttachRequest {
1066                        runtime_id: params.runtime_id,
1067                        cwd: params.cwd,
1068                        launch: runtime_launch(&params.backend),
1069                    })
1070                    .await
1071                    .map_err(operation)?;
1072                self.insert_runtime(runtime)
1073            }
1074            "harness.v1.runtimes.send_input" => {
1075                let params = decode::<RuntimeInputParams>(params)?;
1076                let image_urls = validate_runtime_image_urls(params.image_urls)?;
1077                let runtime = self.runtime_mut(&params.connection)?;
1078                let turn_id = runtime
1079                    .send_input(RuntimeInput {
1080                        text: params.text,
1081                        image_urls,
1082                    })
1083                    .await
1084                    .map_err(operation)?;
1085                Ok(json!({"turn_id": turn_id}))
1086            }
1087            "harness.v1.runtimes.interrupt" => {
1088                let params = decode::<RuntimeConnectionParams>(params)?;
1089                self.runtime_mut(&params.connection)?
1090                    .interrupt()
1091                    .await
1092                    .map_err(operation)?;
1093                Ok(json!({}))
1094            }
1095            "harness.v1.runtimes.steer" => {
1096                let params = decode::<RuntimeInputParams>(params)?;
1097                if !params.image_urls.is_empty() {
1098                    return Err(ServiceError::InvalidParams(
1099                        "runtime steering accepts text only".into(),
1100                    ));
1101                }
1102                let text = params.text.trim();
1103                if text.is_empty() || text.chars().count() > 50_000 {
1104                    return Err(ServiceError::InvalidParams(
1105                        "runtime steering requires 1 to 50,000 text characters".into(),
1106                    ));
1107                }
1108                self.runtime_mut(&params.connection)?
1109                    .steer(text.to_string())
1110                    .await
1111                    .map_err(operation)?;
1112                Ok(json!({}))
1113            }
1114            "harness.v1.runtimes.respond" => {
1115                let params = decode::<RuntimeRespondParams>(params)?;
1116                self.runtime_mut(&params.connection)?
1117                    .respond(params.request_id, params.response)
1118                    .await
1119                    .map_err(operation)?;
1120                Ok(json!({}))
1121            }
1122            "harness.v1.runtimes.terminal_instructions" => {
1123                let params = decode::<RuntimeConnectionParams>(params)?;
1124                let launch = self
1125                    .terminal_launches
1126                    .get(&params.connection)
1127                    .ok_or_else(|| {
1128                        ServiceError::Operation(
1129                            "this runtime is not hosted for terminal attachment".into(),
1130                        )
1131                    })?;
1132                Ok(json!({"launch":launch}))
1133            }
1134            "harness.v1.runtimes.close" => {
1135                let params = decode::<RuntimeConnectionParams>(params)?;
1136                let Some(mut runtime) = self.runtimes.remove(&params.connection) else {
1137                    return Err(ServiceError::InvalidParams(format!(
1138                        "unknown runtime connection `{}`",
1139                        params.connection
1140                    )));
1141                };
1142                self.terminal_launches.remove(&params.connection);
1143                self.runtime_sequences.remove(&runtime.handle().runtime_id);
1144                runtime.close().await.map_err(operation)?;
1145                Ok(json!({"closed": true}))
1146            }
1147            _ => Err(ServiceError::MethodNotFound),
1148        }
1149    }
1150
1151    /// Deliver one message into a session that is running right now.
1152    #[cfg(feature = "adapter-api")]
1153    async fn message_call(&self, params: Value) -> std::result::Result<Value, ServiceError> {
1154        let params = decode::<MessageSessionParams>(params)?;
1155        Ok(message_live_session(&params, &crate::claude_peer::ProcessCourierRunner).await)
1156    }
1157
1158    #[cfg(feature = "adapter-api")]
1159    fn harness_settings_call(
1160        &self,
1161        method: &str,
1162        params: Value,
1163    ) -> std::result::Result<Value, ServiceError> {
1164        let homes = crate::HarnessHomes::default();
1165        match method {
1166            "harness.v1.harnesses.settings" => {
1167                let params = decode::<HarnessSettingsParams>(params)?;
1168                let report = crate::inspect_harness_interop_settings(&homes, &params.harness)
1169                    .map_err(|error| ServiceError::Operation(error.to_string()))?;
1170                serde_json::to_value(report)
1171                    .map_err(|error| ServiceError::Operation(error.to_string()))
1172            }
1173            "harness.v1.harnesses.configure" => {
1174                let params = decode::<ConfigureHarnessParams>(params)?;
1175                let report = crate::configure_harness_interop_settings(
1176                    &homes,
1177                    &params.harness,
1178                    &params.changes,
1179                    params.expected_revision.as_deref(),
1180                )
1181                .map_err(|error| ServiceError::Operation(error.to_string()))?;
1182                serde_json::to_value(report)
1183                    .map_err(|error| ServiceError::Operation(error.to_string()))
1184            }
1185            _ => Err(ServiceError::MethodNotFound),
1186        }
1187    }
1188
1189    fn insert_runtime(
1190        &mut self,
1191        runtime: Box<dyn RuntimeConnection>,
1192    ) -> std::result::Result<Value, ServiceError> {
1193        let connection = format!("runtime-{}", self.next_runtime);
1194        self.next_runtime += 1;
1195        let handle = runtime.handle().clone();
1196        self.runtime_sequences
1197            .entry(handle.runtime_id.clone())
1198            .or_insert(0);
1199        self.runtimes.insert(connection.clone(), runtime);
1200        Ok(json!({"connection": connection, "handle": handle}))
1201    }
1202
1203    #[cfg(feature = "adapter-api")]
1204    async fn insert_hosted_runtime(
1205        &mut self,
1206        runtime: Box<dyn RuntimeConnection>,
1207        capabilities: crate::RuntimeCapabilities,
1208        workspace: PathBuf,
1209    ) -> std::result::Result<Value, ServiceError> {
1210        let (host, connection) = HostedHarnessRuntime::spawn(runtime, capabilities);
1211        let token: std::sync::Arc<str> = crate::server::generate_token().into();
1212        let server = crate::server::run_frontend_http(
1213            host.clone(),
1214            host.frontend_sender(),
1215            "127.0.0.1:0",
1216            token.clone(),
1217        )
1218        .await
1219        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1220        let source = LiveRuntimeSource {
1221            harness: connection.handle().harness.as_str().to_string(),
1222            session_id: connection.handle().runtime_id.clone(),
1223            workspace: workspace.clone(),
1224        };
1225        let registration = register_live_runtime(
1226            connection.handle().runtime_id.clone(),
1227            source.clone(),
1228            format!("http://{}", server.address()),
1229            token.to_string(),
1230        )
1231        .map_err(|error| ServiceError::Operation(error.to_string()))?;
1232        let endpoint = registration.endpoint().to_string();
1233        let launch = StructuredLaunch {
1234            cwd: workspace,
1235            // Pin attachment to the executable hosting this runtime. A bare
1236            // `supercode` could resolve to an older global install whose CLI
1237            // does not understand the receipt it is being asked to open.
1238            program: std::env::current_exe()
1239                .ok()
1240                .map(|path| path.to_string_lossy().into_owned())
1241                .unwrap_or_else(|| "supercode".into()),
1242            arguments: vec![
1243                "harness".into(),
1244                "attach".into(),
1245                "--endpoint".into(),
1246                endpoint,
1247                "--harness".into(),
1248                source.harness,
1249                "--session".into(),
1250                source.session_id,
1251            ],
1252            env: BTreeMap::new(),
1253        };
1254        let lease = HostedRuntimeLease {
1255            connection,
1256            _host: host,
1257            _registration: registration,
1258            _server: server,
1259        };
1260        let opened = self.insert_runtime(Box::new(lease))?;
1261        let connection_id = opened["connection"]
1262            .as_str()
1263            .expect("insert_runtime returns a connection id")
1264            .to_string();
1265        self.terminal_launches.insert(connection_id, launch);
1266        Ok(opened)
1267    }
1268
1269    #[cfg(not(feature = "adapter-api"))]
1270    async fn insert_hosted_runtime(
1271        &mut self,
1272        runtime: Box<dyn RuntimeConnection>,
1273        _capabilities: crate::RuntimeCapabilities,
1274        _workspace: PathBuf,
1275    ) -> std::result::Result<Value, ServiceError> {
1276        self.insert_runtime(runtime)
1277    }
1278
1279    fn runtime_mut(
1280        &mut self,
1281        connection: &str,
1282    ) -> std::result::Result<&mut Box<dyn RuntimeConnection>, ServiceError> {
1283        self.runtimes.get_mut(connection).ok_or_else(|| {
1284            ServiceError::InvalidParams(format!("unknown runtime connection `{connection}`"))
1285        })
1286    }
1287
1288    async fn inventory_call(
1289        &self,
1290        method: &str,
1291        params: Value,
1292    ) -> std::result::Result<Value, ServiceError> {
1293        let mut params = decode::<HarnessInventoryParams>(params)?;
1294        if method == "harness.v1.harnesses.probe" {
1295            let harness = params.harness.take().ok_or_else(|| {
1296                ServiceError::InvalidParams("harnesses.probe requires `harness`".into())
1297            })?;
1298            params.harnesses = vec![harness];
1299        }
1300        let selected = params
1301            .harnesses
1302            .iter()
1303            .map(HarnessId::as_str)
1304            .collect::<std::collections::BTreeSet<_>>();
1305        let supported = harness_support_registry()
1306            .harnesses
1307            .into_iter()
1308            .filter(|descriptor| selected.is_empty() || selected.contains(descriptor.id.as_str()))
1309            .collect::<Vec<_>>();
1310        if !params.harnesses.is_empty() && supported.len() != selected.len() {
1311            let known = supported
1312                .iter()
1313                .map(|harness| harness.id.as_str())
1314                .collect::<std::collections::BTreeSet<_>>();
1315            let missing = params
1316                .harnesses
1317                .iter()
1318                .filter(|id| !known.contains(id.as_str()))
1319                .map(HarnessId::as_str)
1320                .collect::<Vec<_>>();
1321            return Err(ServiceError::InvalidParams(format!(
1322                "unknown harness(es): {}",
1323                missing.join(", ")
1324            )));
1325        }
1326        let global_counts = params
1327            .include_sessions
1328            .then(|| self.session_counts(None, &params.harnesses));
1329        let workspace_counts = params.include_sessions.then(|| {
1330            params
1331                .workspace
1332                .as_deref()
1333                .map(|workspace| self.session_counts(Some(workspace), &params.harnesses))
1334        });
1335        let probes = supported.into_iter().map(|descriptor| {
1336            let global = global_counts
1337                .as_ref()
1338                .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
1339            let workspace = workspace_counts
1340                .as_ref()
1341                .and_then(Option::as_ref)
1342                .map(|counts| counts.get(descriptor.id.as_str()).copied().unwrap_or(0));
1343            self.probe_harness(descriptor, &params, global, workspace)
1344        });
1345        let harnesses = futures::future::join_all(probes).await;
1346        serde_json::to_value(HarnessInventoryReport {
1347            probe: params.probe,
1348            workspace: params.workspace,
1349            harnesses,
1350        })
1351        .map_err(|error| ServiceError::Operation(error.to_string()))
1352    }
1353
1354    async fn probe_harness(
1355        &self,
1356        descriptor: crate::HarnessSupportDescriptor,
1357        params: &HarnessInventoryParams,
1358        global: Option<usize>,
1359        workspace: Option<usize>,
1360    ) -> LocalHarness {
1361        let launch = descriptor.runtime.default_launch.as_ref();
1362        let executable = launch.and_then(|launch| find_executable(&launch.program));
1363        let installed = executable.is_some();
1364        let version = if params.skip_versions {
1365            None
1366        } else {
1367            match executable.as_deref() {
1368                Some(path) => executable_version(path).await,
1369                None => None,
1370            }
1371        };
1372        let configured = auth_evidence(descriptor.id.as_str());
1373        let mut auth = if configured {
1374            HarnessAuthState::Configured
1375        } else {
1376            HarnessAuthState::Unknown
1377        };
1378        let mut runtime = if installed {
1379            HarnessRuntimeState::Degraded
1380        } else {
1381            HarnessRuntimeState::Unavailable
1382        };
1383        let mut reason = (!installed).then(|| {
1384            format!(
1385                "{} is supported but `{}` was not found on PATH",
1386                descriptor.display_name,
1387                launch
1388                    .map(|launch| launch.program.as_str())
1389                    .unwrap_or("executable")
1390            )
1391        });
1392        let mut repair = (!installed).then(|| {
1393            format!(
1394                "Install {} and ensure `{}` is on PATH.",
1395                descriptor.display_name,
1396                launch
1397                    .map(|launch| launch.program.as_str())
1398                    .unwrap_or("its executable")
1399            )
1400        });
1401
1402        if installed && params.probe == HarnessProbeLevel::Handshake {
1403            let backend_params = RuntimeBackendParams {
1404                harness: descriptor.id.clone(),
1405                protocol: None,
1406                launch: None,
1407                base_url: None,
1408                policy: RuntimePolicy::Default,
1409            };
1410            match runtime_backend(&backend_params) {
1411                Ok(backend) => {
1412                    let cwd = params
1413                        .workspace
1414                        .clone()
1415                        .or_else(|| std::env::current_dir().ok())
1416                        .unwrap_or_else(|| PathBuf::from("."));
1417                    let isolated = descriptor
1418                        .runtime
1419                        .default_launch
1420                        .clone()
1421                        .and_then(|launch| {
1422                            IsolatedProbeHome::new(descriptor.id.as_str(), launch).ok()
1423                        });
1424                    let Some(isolated) = isolated else {
1425                        reason = Some(
1426                            "No-prompt runtime handshake could not create its isolated harness home."
1427                                .into(),
1428                        );
1429                        repair = Some(
1430                            "Check temporary-directory permissions, then run the handshake probe again."
1431                                .into(),
1432                        );
1433                        return LocalHarness {
1434                            id: descriptor.id,
1435                            display_name: descriptor.display_name,
1436                            supported: true,
1437                            installed,
1438                            executable: executable.map(|path| path.to_string_lossy().into_owned()),
1439                            version,
1440                            auth,
1441                            runtime,
1442                            protocol: descriptor.runtime.protocol,
1443                            capabilities: descriptor.runtime.capabilities.clone(),
1444                            effective_capabilities: descriptor.runtime.capabilities,
1445                            sessions: HarnessSessionCounts { global, workspace },
1446                            reason,
1447                            repair,
1448                        };
1449                    };
1450                    match tokio::time::timeout(
1451                        Duration::from_secs(30),
1452                        backend.start(RuntimeStartRequest {
1453                            cwd,
1454                            launch: Some(isolated.launch.clone()),
1455                        }),
1456                    )
1457                    .await
1458                    {
1459                        Ok(Ok(mut connection)) => {
1460                            match stabilize_handshake(connection.as_mut()).await {
1461                                Ok(()) => {
1462                                    auth = HarnessAuthState::Ready;
1463                                    runtime = HarnessRuntimeState::Ready;
1464                                    reason = Some(
1465                                        "No-prompt runtime handshake remained healthy through the startup stabilization window; no model request was sent."
1466                                            .into(),
1467                                    );
1468                                    repair = None;
1469                                }
1470                                Err(message) => {
1471                                    auth = if looks_like_auth_error(&message) {
1472                                        HarnessAuthState::Required
1473                                    } else if configured {
1474                                        HarnessAuthState::Configured
1475                                    } else {
1476                                        HarnessAuthState::Unknown
1477                                    };
1478                                    reason = Some(format!(
1479                                        "No-prompt runtime handshake became unhealthy during startup: {message}"
1480                                    ));
1481                                    repair = Some(if auth == HarnessAuthState::Required {
1482                                        format!(
1483                                            "Run `{}` interactively once and complete sign-in, then probe again.",
1484                                            launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
1485                                        )
1486                                    } else {
1487                                        "Run the harness directly to inspect its startup failure, then probe again."
1488                                            .into()
1489                                    });
1490                                }
1491                            }
1492                            let _ =
1493                                tokio::time::timeout(Duration::from_secs(3), connection.close())
1494                                    .await;
1495                        }
1496                        Ok(Err(error)) => {
1497                            let message = truncate_text(&error.to_string(), 500);
1498                            auth = if looks_like_auth_error(&message) {
1499                                HarnessAuthState::Required
1500                            } else if configured {
1501                                HarnessAuthState::Configured
1502                            } else {
1503                                HarnessAuthState::Unknown
1504                            };
1505                            reason = Some(format!("No-prompt runtime handshake failed: {message}"));
1506                            repair = Some(if auth == HarnessAuthState::Required {
1507                                format!(
1508                                    "Run `{}` interactively once and complete sign-in, then probe again.",
1509                                    launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
1510                                )
1511                            } else {
1512                                "Check the harness installation and run the handshake probe again."
1513                                    .into()
1514                            });
1515                        }
1516                        Err(_) => {
1517                            reason = Some(
1518                                "No-prompt runtime handshake timed out after 30 seconds.".into(),
1519                            );
1520                            repair = Some("Run the harness directly to check startup or authentication, then probe again.".into());
1521                        }
1522                    }
1523                    // Keep the isolated home alive through process teardown.
1524                    // Otherwise the compiler may release the last meaningful
1525                    // use after cloning `launch`, and a still-starting CLI can
1526                    // recreate its state directory after Drop removed it.
1527                    // Some Node-based launchers finish a short asynchronous
1528                    // installation-id write just after their parent process
1529                    // is reaped. Remove once immediately, allow that bounded
1530                    // writer to settle, then perform the authoritative pass.
1531                    let _ = isolated.cleanup();
1532                    tokio::time::sleep(Duration::from_millis(250)).await;
1533                    if let Err(error) = isolated.cleanup() {
1534                        auth = if configured {
1535                            HarnessAuthState::Configured
1536                        } else {
1537                            HarnessAuthState::Unknown
1538                        };
1539                        runtime = HarnessRuntimeState::Degraded;
1540                        reason = Some(format!(
1541                            "No-prompt runtime handshake could not remove its isolated harness home: {error}"
1542                        ));
1543                        repair = Some(
1544                            "Check temporary-directory permissions, remove the reported disposable probe home, then run the handshake again."
1545                                .into(),
1546                        );
1547                    }
1548                }
1549                Err(error) => {
1550                    reason = Some(error_message(error));
1551                }
1552            }
1553        } else if installed && configured {
1554            reason = Some("Executable and local authentication evidence found; use a handshake probe to verify readiness.".into());
1555        } else if installed {
1556            reason = Some("Executable found; authentication readiness is unknown until a no-prompt handshake succeeds.".into());
1557            repair =
1558                Some(format!(
1559                "Run `{}` interactively once if sign-in is required, or use `--probe handshake`.",
1560                launch.map(|launch| launch.program.as_str()).unwrap_or("the harness")
1561            ));
1562        }
1563
1564        let effective_capabilities = if installed {
1565            descriptor.runtime.capabilities.clone()
1566        } else {
1567            unavailable_capabilities()
1568        };
1569        LocalHarness {
1570            id: descriptor.id,
1571            display_name: descriptor.display_name,
1572            supported: true,
1573            installed,
1574            executable: executable.map(|path| path.to_string_lossy().into_owned()),
1575            version,
1576            auth,
1577            runtime,
1578            protocol: descriptor.runtime.protocol,
1579            capabilities: descriptor.runtime.capabilities,
1580            effective_capabilities,
1581            sessions: HarnessSessionCounts { global, workspace },
1582            reason,
1583            repair,
1584        }
1585    }
1586
1587    fn session_counts(
1588        &self,
1589        workspace: Option<&Path>,
1590        harnesses: &[HarnessId],
1591    ) -> BTreeMap<String, usize> {
1592        let mut counts = BTreeMap::new();
1593        for session in self
1594            .catalog
1595            .discover(&DiscoveryQuery {
1596                workspace: workspace.map(Path::to_path_buf),
1597                harnesses: harnesses.to_vec(),
1598                ..DiscoveryQuery::default()
1599            })
1600            .unwrap_or_default()
1601        {
1602            *counts
1603                .entry(session.locator.harness.as_str().to_string())
1604                .or_insert(0) += 1;
1605        }
1606        counts
1607    }
1608}
1609
1610#[async_trait::async_trait]
1611impl SdkService for HarnessSessionService {
1612    fn capabilities(&self) -> SdkCapabilities {
1613        SdkCapabilities::default()
1614    }
1615
1616    async fn execute(&mut self, request: SdkRequest) -> Result<Value, SdkError> {
1617        if request.operation == SdkOperation::Events {
1618            let events = self
1619                .poll_sdk_events()
1620                .await
1621                .into_iter()
1622                .map(|(_, event)| event)
1623                .collect::<Vec<_>>();
1624            return serde_json::to_value(events).map_err(|error| {
1625                SdkError::new(
1626                    SdkErrorCode::Execution,
1627                    request.operation,
1628                    error.to_string(),
1629                )
1630            });
1631        }
1632        let method = request
1633            .operation
1634            .method()
1635            .ok_or_else(|| SdkError::unsupported(request.operation))?;
1636        let result = match request.operation {
1637            SdkOperation::Discover | SdkOperation::Load | SdkOperation::Export => {
1638                self.call(method, request.params)
1639            }
1640            SdkOperation::Start
1641            | SdkOperation::Resume
1642            | SdkOperation::Input
1643            | SdkOperation::Interrupt
1644            | SdkOperation::Steer
1645            | SdkOperation::Respond
1646            | SdkOperation::Close => self.runtime_call(method, request.params).await,
1647            SdkOperation::Events => unreachable!("handled before method dispatch"),
1648        };
1649        result.map_err(|error| sdk_error(request.operation, error))
1650    }
1651
1652    async fn events(&mut self) -> Result<Vec<SdkRuntimeEvent>, SdkError> {
1653        Ok(self
1654            .poll_sdk_events()
1655            .await
1656            .into_iter()
1657            .map(|(_, event)| event)
1658            .collect())
1659    }
1660}
1661
1662#[cfg(feature = "adapter-api")]
1663struct HostedRuntimeLease {
1664    connection: HostedHarnessConnection,
1665    _host: std::sync::Arc<HostedHarnessRuntime>,
1666    _registration: LiveRuntimeRegistration,
1667    _server: crate::server::FrontendHttpServer,
1668}
1669
1670#[async_trait::async_trait]
1671#[cfg(feature = "adapter-api")]
1672impl RuntimeConnection for HostedRuntimeLease {
1673    fn handle(&self) -> &crate::RuntimeHandle {
1674        self.connection.handle()
1675    }
1676
1677    async fn send_input(&mut self, input: RuntimeInput) -> crate::Result<Option<String>> {
1678        self.connection.send_input(input).await
1679    }
1680
1681    async fn next_event(&mut self) -> crate::Result<Option<crate::HarnessEvent>> {
1682        self.connection.next_event().await
1683    }
1684
1685    async fn interrupt(&mut self) -> crate::Result<()> {
1686        self.connection.interrupt().await
1687    }
1688
1689    async fn respond(&mut self, request_id: Value, response: Value) -> crate::Result<()> {
1690        self.connection.respond(request_id, response).await
1691    }
1692
1693    async fn close(&mut self) -> crate::Result<()> {
1694        self.connection.close().await
1695    }
1696}
1697
1698async fn stabilize_handshake(connection: &mut dyn RuntimeConnection) -> Result<(), String> {
1699    let deadline = tokio::time::Instant::now() + Duration::from_secs(3);
1700    loop {
1701        let now = tokio::time::Instant::now();
1702        if now >= deadline {
1703            return Ok(());
1704        }
1705        match tokio::time::timeout(deadline - now, connection.next_event()).await {
1706            Err(_) => return Ok(()),
1707            Ok(Ok(Some(event))) => {
1708                if let Some(message) = handshake_event_failure(&event) {
1709                    return Err(truncate_text(&message, 500));
1710                }
1711            }
1712            Ok(Ok(None)) => return Err("runtime transport closed during startup".into()),
1713            Ok(Err(error)) => return Err(error.to_string()),
1714        }
1715    }
1716}
1717
1718fn handshake_event_failure(event: &crate::HarnessEvent) -> Option<String> {
1719    let detail = event
1720        .payload
1721        .get("message")
1722        .or_else(|| event.payload.get("line"))
1723        .and_then(Value::as_str)
1724        .unwrap_or(event.kind.as_str());
1725    match event.kind.as_str() {
1726        "transport_closed" => Some("runtime transport closed during startup".into()),
1727        "transport_error" => Some(format!("runtime transport error: {detail}")),
1728        "malformed_output" => Some(format!("runtime emitted non-protocol output: {detail}")),
1729        // Stderr is retained as a runtime event, but is not transport health.
1730        // Grok, for example, can log an AuthorizationRequired error from an
1731        // optional background worker while its ACP session continues to send
1732        // updates and complete prompts normally.
1733        _ => None,
1734    }
1735}
1736
1737fn projected_session_result(session: &Session, options: &SessionLoadOptions) -> Value {
1738    let total_messages = session.messages.len();
1739    let (offset, end) = projected_message_window(total_messages, options);
1740    json!({
1741        "session": projected_session_json(session, options),
1742        "summary": projected_session_summary(session, options),
1743        "window": {
1744            "has_more": offset > 0 || end < total_messages,
1745            "has_newer": end < total_messages,
1746            "has_older": offset > 0,
1747            "newer_items": normalized_item_count(&session.messages[end..]),
1748            "offset": offset,
1749            "older_items": normalized_item_count(&session.messages[..offset]),
1750            "returned": end.saturating_sub(offset),
1751            "total_messages": total_messages,
1752        }
1753    })
1754}
1755
1756fn normalized_item_count(messages: &[crate::ChatMessage]) -> usize {
1757    messages
1758        .iter()
1759        .map(|message| {
1760            let conversation = usize::from(
1761                matches!(message.role, Role::Assistant | Role::User)
1762                    && message_has_content(message),
1763            );
1764            let tool_result =
1765                usize::from(message.role == Role::Tool && message_has_content(message));
1766            conversation + tool_result + message.tool_calls().len()
1767        })
1768        .sum()
1769}
1770
1771fn projected_session_summary(session: &Session, options: &SessionLoadOptions) -> Value {
1772    let mut conversational = session.messages.iter().filter(|message| {
1773        matches!(message.role, Role::Assistant | Role::User) && message_has_content(message)
1774    });
1775    let first_message = conversational.clone().next();
1776    let last_message = conversational.next_back();
1777    let mut assistant = session
1778        .messages
1779        .iter()
1780        .filter(|message| message.role == Role::Assistant && message_has_content(message));
1781    let first_assistant_message = assistant.clone().next();
1782    let last_assistant_message = assistant.next_back();
1783    let end_of_turn = session
1784        .messages
1785        .iter()
1786        .rev()
1787        .find(|message| message.role != Role::System)
1788        .is_some_and(|message| {
1789            message.role == Role::Assistant
1790                && message_has_content(message)
1791                && message.tool_calls().is_empty()
1792        });
1793    let project = |message: Option<&crate::ChatMessage>| {
1794        message.map(|message| project_inline_media(message_json(message), options))
1795    };
1796    json!({
1797        "end_of_turn": end_of_turn,
1798        "first_assistant_message": project(first_assistant_message),
1799        "first_message": project(first_message),
1800        "last_assistant_message": project(last_assistant_message),
1801        "last_assistant_text": last_assistant_message.map(message_text).unwrap_or_default(),
1802        "last_message": project(last_message),
1803    })
1804}
1805
1806fn message_has_content(message: &crate::ChatMessage) -> bool {
1807    message
1808        .content
1809        .as_deref()
1810        .is_some_and(|content| !content.trim().is_empty())
1811        || message
1812            .content_parts
1813            .as_ref()
1814            .is_some_and(|parts| !parts.is_empty())
1815}
1816
1817fn message_text(message: &crate::ChatMessage) -> String {
1818    if let Some(content) = &message.content {
1819        return content.clone();
1820    }
1821    message
1822        .content_parts
1823        .as_ref()
1824        .into_iter()
1825        .flatten()
1826        .filter_map(|part| part.get("text").and_then(Value::as_str))
1827        .collect::<Vec<_>>()
1828        .join("\n")
1829}
1830
1831fn projected_session_json(session: &Session, options: &SessionLoadOptions) -> Value {
1832    let (offset, end) = projected_message_window(session.messages.len(), options);
1833    let messages = session.messages[offset..end]
1834        .iter()
1835        .map(|message| project_inline_media(message_json(message), options))
1836        .collect::<Vec<_>>();
1837    let subagents = if options.include_subagents.unwrap_or(true) {
1838        // The reported window describes the top-level transcript. Applying it
1839        // recursively would silently truncate subagents without returning a
1840        // window for each child. Keep their histories complete while carrying
1841        // the caller's media policy through the tree.
1842        let subagent_options = SessionLoadOptions {
1843            message_limit: None,
1844            message_offset: None,
1845            message_tail: None,
1846            ..options.clone()
1847        };
1848        session
1849            .subagents
1850            .iter()
1851            .map(|subagent| projected_session_json(subagent, &subagent_options))
1852            .collect::<Vec<_>>()
1853    } else {
1854        Vec::new()
1855    };
1856    json!({
1857        "source": match session.meta.source {
1858            SessionSource::ClaudeCode => "claude_code",
1859            SessionSource::Codex => "codex",
1860            SessionSource::Gemini => "gemini",
1861            SessionSource::Goose => "goose",
1862            SessionSource::Grok => "grok",
1863            SessionSource::Native => "native",
1864            SessionSource::OpenCode => "opencode",
1865            SessionSource::Pi => "pi",
1866        },
1867        "session_id": session.meta.session_id,
1868        "model": session.meta.model,
1869        "cwd": session.meta.cwd,
1870        "system_prompt": session.meta.system_prompt,
1871        "agent_id": session.meta.agent_id,
1872        "parent_tool_use_id": session.meta.parent_tool_use_id,
1873        "lineage": session.meta.lineage,
1874        "messages": messages,
1875        "subagents": subagents,
1876        "raw_record_count": session.raw.len(),
1877        "parse_error_lines": session.parse_error_lines,
1878    })
1879}
1880
1881fn projected_message_window(total: usize, options: &SessionLoadOptions) -> (usize, usize) {
1882    if let Some(tail) = options.message_tail {
1883        return (total.saturating_sub(tail), total);
1884    }
1885    let offset = options.message_offset.unwrap_or(0).min(total);
1886    let end = options
1887        .message_limit
1888        .map(|limit| offset.saturating_add(limit).min(total))
1889        .unwrap_or(total);
1890    (offset, end)
1891}
1892
1893fn project_inline_media(mut message: Value, options: &SessionLoadOptions) -> Value {
1894    let Some(parts) = message.get_mut("content").and_then(Value::as_array_mut) else {
1895        return message;
1896    };
1897    for part in parts {
1898        let Some(url) = part
1899            .get("image_url")
1900            .and_then(|image| image.get("url"))
1901            .and_then(Value::as_str)
1902        else {
1903            continue;
1904        };
1905        let Some(rest) = url.strip_prefix("data:") else {
1906            continue;
1907        };
1908        let Some((media_type, encoded)) = rest.split_once(";base64,") else {
1909            continue;
1910        };
1911        let padding = usize::from(encoded.ends_with('=')) + usize::from(encoded.ends_with("=="));
1912        let decoded_bytes = encoded.len().saturating_mul(3) / 4;
1913        let decoded_bytes = decoded_bytes.saturating_sub(padding);
1914        let should_elide = matches!(options.inline_media, InlineMediaMode::Metadata)
1915            || options
1916                .max_inline_media_bytes
1917                .is_some_and(|limit| decoded_bytes > limit);
1918        if should_elide {
1919            *part = json!({
1920                "type": "media_reference",
1921                "media_type": media_type,
1922                "encoding": "base64",
1923                "encoded_bytes": encoded.len(),
1924                "decoded_bytes": decoded_bytes,
1925                "omitted": true,
1926            });
1927        }
1928    }
1929    message
1930}
1931
1932#[derive(Deserialize)]
1933struct LocatorParams {
1934    locator: SessionLocator,
1935    /// Optional fidelity for the READ surfaces (`sessions.load`,
1936    /// `sessions.follow`).
1937    ///
1938    /// Omitted means [`Fidelity::Semantic`]: these two methods only ever
1939    /// produce a read-only view, and a compacted or resumed-across-files
1940    /// transcript — the everyday shape of a long Claude Code session — has no
1941    /// losslessly reconstructable record graph, so refusing to render it made
1942    /// the mirror unusable rather than accurate. A caller that intends to
1943    /// CONTINUE from what it reads asks for a lossless level explicitly and
1944    /// gets the strict refusal back. Every other method (export, translate,
1945    /// branch, handoff, resume_instructions) is lossless-only and has no
1946    /// such knob.
1947    #[serde(default)]
1948    fidelity: Option<Fidelity>,
1949    /// Optional bounded frontend projection. Absent preserves the historical
1950    /// complete-session read contract.
1951    #[serde(default)]
1952    view: Option<SessionReadView>,
1953}
1954
1955#[derive(Deserialize)]
1956struct SessionReadView {
1957    /// Number of trailing normalized messages to return. Zero is treated as
1958    /// one so a caller cannot accidentally request an unbounded empty mode.
1959    #[serde(default)]
1960    tail_messages: Option<usize>,
1961    /// Whether Claude Code child transcripts belong in this view. The
1962    /// frontend default is false; the legacy no-view path remains true.
1963    #[serde(default)]
1964    include_subagents: bool,
1965    /// Preserve human-visible native history across model-context compaction.
1966    #[serde(default)]
1967    display_history: bool,
1968    /// Bound each individual text field so a single tool result cannot turn a
1969    /// small message window into a hundred-megabyte RPC response.
1970    #[serde(default)]
1971    max_message_chars: Option<usize>,
1972}
1973
1974impl LocatorParams {
1975    fn read_fidelity(&self) -> Fidelity {
1976        self.fidelity.unwrap_or(Fidelity::Semantic)
1977    }
1978
1979    fn include_subagents(&self) -> bool {
1980        self.view
1981            .as_ref()
1982            .map(|view| view.include_subagents)
1983            .unwrap_or(true)
1984    }
1985
1986    fn tail_messages(&self) -> Option<usize> {
1987        self.view
1988            .as_ref()
1989            .and_then(|view| view.tail_messages)
1990            .map(|limit| limit.clamp(1, 5_000))
1991    }
1992
1993    fn display_history(&self) -> bool {
1994        self.view.as_ref().is_some_and(|view| view.display_history)
1995    }
1996
1997    fn max_message_chars(&self) -> Option<usize> {
1998        self.view
1999            .as_ref()
2000            .and_then(|view| view.max_message_chars)
2001            .map(|limit| limit.clamp(256, 64_000))
2002    }
2003
2004    fn bound_session(&self, session: &mut Session) {
2005        bound_session_view(session, self.tail_messages(), self.max_message_chars());
2006    }
2007}
2008
2009#[derive(Debug, Clone, Copy, Default, Deserialize)]
2010#[serde(rename_all = "snake_case")]
2011enum InlineMediaMode {
2012    #[default]
2013    Full,
2014    Metadata,
2015}
2016
2017#[derive(Debug, Clone, Default, Deserialize)]
2018#[serde(default)]
2019struct SessionLoadOptions {
2020    include_subagents: Option<bool>,
2021    inline_media: InlineMediaMode,
2022    max_inline_media_bytes: Option<usize>,
2023    message_limit: Option<usize>,
2024    message_offset: Option<usize>,
2025    message_tail: Option<usize>,
2026}
2027
2028impl SessionLoadOptions {
2029    fn validate(&self) -> std::result::Result<(), ServiceError> {
2030        if self.message_tail.is_some()
2031            && (self.message_limit.is_some() || self.message_offset.is_some())
2032        {
2033            return Err(ServiceError::InvalidParams(
2034                "sessions.load options.message_tail cannot be combined with message_limit or message_offset"
2035                    .into(),
2036            ));
2037        }
2038        Ok(())
2039    }
2040}
2041
2042#[derive(Deserialize)]
2043struct LoadSessionParams {
2044    #[serde(flatten)]
2045    read: LocatorParams,
2046    #[serde(default)]
2047    options: Option<SessionLoadOptions>,
2048}
2049
2050#[derive(Deserialize)]
2051struct UnfollowParams {
2052    subscription: String,
2053}
2054
2055#[derive(Deserialize)]
2056struct ActivitySubscribeParams {
2057    locators: Vec<SessionLocator>,
2058    #[serde(default)]
2059    homes: crate::HarnessHomes,
2060}
2061
2062#[derive(Deserialize)]
2063struct MessageSessionParams {
2064    locator: SessionLocator,
2065    text: String,
2066    /// Same storage roots discovery accepts, so a caller (and a test) can
2067    /// point the live-session registry somewhere other than `$HOME`.
2068    #[serde(default)]
2069    homes: crate::HarnessHomes,
2070}
2071
2072#[derive(Deserialize)]
2073#[serde(deny_unknown_fields)]
2074struct HarnessSettingsParams {
2075    harness: String,
2076}
2077
2078#[derive(Deserialize)]
2079#[serde(deny_unknown_fields)]
2080struct ConfigureHarnessParams {
2081    harness: String,
2082    #[serde(default)]
2083    changes: Vec<crate::HarnessSettingChange>,
2084    #[serde(default)]
2085    expected_revision: Option<String>,
2086}
2087
2088fn claude_inbound_controls_or_error(homes: &crate::HarnessHomes) -> (Value, Value) {
2089    match crate::inspect_harness_interop_settings(homes, HarnessId::CLAUDE_CODE) {
2090        Ok(report) => (
2091            serde_json::to_value(report).unwrap_or(Value::Null),
2092            Value::Null,
2093        ),
2094        Err(error) => (
2095            Value::Null,
2096            Value::String(format!(
2097                "Supercode could not inspect Claude Code inbound controls: {error}"
2098            )),
2099        ),
2100    }
2101}
2102
2103/// Deliver `text` into a session that is running right now, or say why not.
2104///
2105/// A refusal is a RESULT, not a JSON-RPC error: "that session is persisted
2106/// only" is an answer about the session, which a mirror renders next to the
2107/// transcript, and this service's error envelope carries no structured data
2108/// field a machine-readable reason could survive in.
2109///
2110/// `delivered_to_bus` is the honest ceiling of what the courier proves. The
2111/// message reached the receiving session's inbox; whether that session ever
2112/// reads it is governed by ITS OWN inbound controls (`crossSessionInbound`,
2113/// approval dialogs), which Supercode neither sees nor overrides.
2114#[cfg(feature = "adapter-api")]
2115async fn message_live_session(
2116    params: &MessageSessionParams,
2117    runner: &dyn crate::claude_peer::CourierRunner,
2118) -> Value {
2119    if params.locator.harness.as_str() != HarnessId::CLAUDE_CODE {
2120        return json!({
2121            "delivered_to_bus": false,
2122            "refusal": {
2123                "reason": crate::claude_peer::ClaudePeerRefusal::HarnessUnsupported.as_str(),
2124                "message": format!(
2125                    "`{}` does not publish a live-session registry; only claude-code sessions can be messaged in place",
2126                    params.locator.harness.as_str()
2127                ),
2128            },
2129        });
2130    }
2131    let (inbound_controls, inbound_controls_error) =
2132        claude_inbound_controls_or_error(&params.homes);
2133    match crate::claude_peer::message_claude_peer(
2134        &params.homes,
2135        &params.locator.session_id,
2136        &params.text,
2137        runner,
2138    )
2139    .await
2140    {
2141        Ok(delivery) => json!({
2142            "delivered_to_bus": true,
2143            "target": {
2144                "session_id": delivery.target.session_id,
2145                "name": delivery.target.name,
2146                "pid": delivery.target.pid,
2147                "cwd": delivery.target.cwd,
2148                "status": delivery.target.status.map(|status| status.as_str()),
2149            },
2150            "courier": {
2151                "model": crate::claude_peer::COURIER_MODEL,
2152                "report": delivery.courier_report,
2153            },
2154            "inbound_controls": inbound_controls,
2155            "inbound_controls_error": inbound_controls_error,
2156        }),
2157        Err(refusal) => json!({
2158            "delivered_to_bus": false,
2159            "refusal": {"reason": refusal.reason.as_str(), "message": refusal.message},
2160            "inbound_controls": inbound_controls,
2161            "inbound_controls_error": inbound_controls_error,
2162        }),
2163    }
2164}
2165
2166/// Source identity of one follow subscription, plus the last lifecycle state
2167/// already reported on it. The follower itself stays purely persistence-facing.
2168// Only the adapter-api poll reads these; the subscription bookkeeping itself is
2169// shared by both builds.
2170#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
2171struct FollowedSource {
2172    harness: String,
2173    session_id: String,
2174    reported: Option<String>,
2175}
2176
2177#[cfg_attr(not(feature = "adapter-api"), allow(dead_code))]
2178struct ActivitySubscription {
2179    locators: Vec<SessionLocator>,
2180    homes: crate::HarnessHomes,
2181    reported: BTreeMap<(String, String), crate::SessionActivity>,
2182}
2183
2184fn peers_for_descriptors(
2185    descriptors: &[SessionDescriptor],
2186    homes: &HarnessHomes,
2187) -> Vec<crate::claude_peer::ClaudePeerSession> {
2188    if descriptors
2189        .iter()
2190        .any(|session| session.locator.harness.as_str() == HarnessId::CLAUDE_CODE)
2191    {
2192        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
2193    } else {
2194        Vec::new()
2195    }
2196}
2197
2198/// Add the live address that makes an indexed row behaviorally equivalent to a discovered row.
2199///
2200/// The durable index owns only persistence metadata. Live endpoints remain projections: every
2201/// message/attach operation revalidates its authority, so publishing one here never trusts a stale
2202/// browser-held handle. Reading the Claude registry once per batch keeps this O(peers + rows).
2203fn live_descriptor_value(
2204    session: &SessionDescriptor,
2205    peers: &[crate::claude_peer::ClaudePeerSession],
2206) -> std::result::Result<Value, ServiceError> {
2207    let mut value = serde_json::to_value(session)
2208        .map_err(|error| ServiceError::Operation(error.to_string()))?;
2209    if let Some(workspace) = &session.cwd {
2210        let source = LiveRuntimeSource {
2211            harness: session.locator.harness.as_str().to_string(),
2212            session_id: session.locator.session_id.clone(),
2213            workspace: workspace.clone(),
2214        };
2215        if let Some(endpoint) = discover_live_runtime(&source)
2216            .map_err(|error| ServiceError::Operation(error.to_string()))?
2217        {
2218            value["live_endpoint"] = json!(endpoint.as_str());
2219        }
2220    }
2221    if value.get("live_endpoint").is_none() {
2222        if let Some(peer) = peers.iter().find(|peer| {
2223            session.locator.harness.as_str() == HarnessId::CLAUDE_CODE
2224                && peer.session_id == session.locator.session_id
2225        }) {
2226            value["live_endpoint"] = json!(peer.endpoint().as_str());
2227        }
2228    }
2229    Ok(value)
2230}
2231
2232fn live_index_changes(
2233    changes: Vec<crate::session_index::SessionIndexChange>,
2234    homes: &HarnessHomes,
2235) -> std::result::Result<Vec<Value>, ServiceError> {
2236    use crate::session_index::SessionIndexChange;
2237    let has_claude = changes.iter().any(|change| match change {
2238        SessionIndexChange::Added { descriptor } | SessionIndexChange::Updated { descriptor } => {
2239            descriptor.locator.harness.as_str() == HarnessId::CLAUDE_CODE
2240        }
2241        SessionIndexChange::Removed { .. } => false,
2242    });
2243    let peers = if has_claude {
2244        crate::claude_peer::read_registry(&crate::claude_peer::registry_dir(homes))
2245    } else {
2246        Vec::new()
2247    };
2248    changes
2249        .into_iter()
2250        .map(|change| match change {
2251            SessionIndexChange::Added { descriptor } => Ok(json!({
2252                "kind": "added",
2253                "descriptor": live_descriptor_value(&descriptor, &peers)?,
2254            })),
2255            SessionIndexChange::Updated { descriptor } => Ok(json!({
2256                "kind": "updated",
2257                "descriptor": live_descriptor_value(&descriptor, &peers)?,
2258            })),
2259            SessionIndexChange::Removed { key } => Ok(json!({
2260                "kind": "removed",
2261                "key": key,
2262            })),
2263        })
2264        .collect()
2265}
2266
2267fn legacy_live_status(activity: &crate::SessionActivity) -> Option<&'static str> {
2268    use crate::{SessionPresence, SessionTurnState};
2269    match (activity.presence, activity.turn) {
2270        (SessionPresence::Persisted, _) => None,
2271        (SessionPresence::Running, SessionTurnState::Working) => Some("busy"),
2272        (SessionPresence::Running, SessionTurnState::Idle) => Some("idle"),
2273        (SessionPresence::Running, _) | (SessionPresence::ShuttingDown, _) => Some("running"),
2274    }
2275}
2276
2277#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
2278#[serde(rename_all = "kebab-case")]
2279enum TransferFormat {
2280    ClaudeCode,
2281    Codex,
2282    #[serde(rename = "opencode", alias = "open-code")]
2283    OpenCode,
2284    Pi,
2285    Grok,
2286    Gemini,
2287    Goose,
2288}
2289
2290impl TransferFormat {
2291    fn id(self) -> &'static str {
2292        match self {
2293            Self::ClaudeCode => HarnessId::CLAUDE_CODE,
2294            Self::Codex => HarnessId::CODEX,
2295            Self::OpenCode => HarnessId::OPENCODE,
2296            Self::Pi => HarnessId::PI,
2297            Self::Grok => HarnessId::GROK,
2298            Self::Gemini => HarnessId::GEMINI,
2299            Self::Goose => HarnessId::GOOSE,
2300        }
2301    }
2302}
2303
2304impl From<TransferFormat> for SessionFormat {
2305    fn from(value: TransferFormat) -> Self {
2306        match value {
2307            TransferFormat::ClaudeCode => Self::ClaudeCode,
2308            TransferFormat::Codex => Self::Codex,
2309            TransferFormat::OpenCode => Self::OpenCode,
2310            TransferFormat::Pi => Self::Pi,
2311            TransferFormat::Grok => Self::Grok,
2312            TransferFormat::Gemini => Self::Gemini,
2313            TransferFormat::Goose => Self::Goose,
2314        }
2315    }
2316}
2317
2318#[derive(Deserialize)]
2319struct ImportSessionParams {
2320    source_harness: TransferFormat,
2321    content: String,
2322}
2323
2324#[derive(Deserialize)]
2325struct ExportSessionParams {
2326    locator: SessionLocator,
2327    target_harness: TransferFormat,
2328}
2329
2330#[derive(Deserialize)]
2331struct ReduceSessionParams {
2332    locator: SessionLocator,
2333    target_harness: TransferFormat,
2334    #[serde(default = "default_keep_last")]
2335    keep_last: usize,
2336}
2337
2338fn default_keep_last() -> usize {
2339    6
2340}
2341
2342#[derive(Deserialize)]
2343struct BranchSessionParams {
2344    locator: SessionLocator,
2345    #[serde(default)]
2346    target_harness: Option<TransferFormat>,
2347}
2348
2349#[derive(Deserialize)]
2350struct HandoffSessionParams {
2351    locator: SessionLocator,
2352    target_harness: TransferFormat,
2353    #[serde(default)]
2354    cwd: Option<PathBuf>,
2355}
2356
2357#[derive(Debug, Clone, Copy, Default, Deserialize)]
2358#[serde(rename_all = "snake_case")]
2359enum ResumePolicy {
2360    #[default]
2361    Default,
2362    Yolo,
2363}
2364
2365#[derive(Deserialize)]
2366struct ResumeInstructionsParams {
2367    locator: SessionLocator,
2368    #[serde(default)]
2369    cwd: Option<PathBuf>,
2370    #[serde(default)]
2371    policy: ResumePolicy,
2372}
2373
2374#[derive(Serialize)]
2375struct SessionArtifact {
2376    source_harness: HarnessId,
2377    target_harness: &'static str,
2378    session_id: Option<String>,
2379    content: String,
2380    suggested_filename: String,
2381    files: Vec<SessionArtifactFile>,
2382    fidelity: Fidelity,
2383    residue: Vec<String>,
2384}
2385
2386#[derive(Serialize)]
2387struct SessionArtifactFile {
2388    path: String,
2389    content: String,
2390    role: ArtifactFileRole,
2391}
2392
2393#[derive(Serialize)]
2394#[serde(rename_all = "snake_case")]
2395enum ArtifactFileRole {
2396    Primary,
2397    Subagent,
2398    Bundle,
2399    SourceRecovery,
2400}
2401
2402#[derive(Serialize)]
2403struct StructuredLaunch {
2404    cwd: PathBuf,
2405    program: String,
2406    arguments: Vec<String>,
2407    env: BTreeMap<String, String>,
2408}
2409
2410struct HandoffInstructions {
2411    launch: StructuredLaunch,
2412    materialize: Option<StructuredLaunch>,
2413    requires_materialization: bool,
2414    note: String,
2415}
2416
2417#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
2418#[serde(rename_all = "snake_case")]
2419enum HarnessProbeLevel {
2420    #[default]
2421    Passive,
2422    Handshake,
2423}
2424
2425#[derive(Default, Deserialize)]
2426#[serde(default)]
2427struct HarnessInventoryParams {
2428    harness: Option<HarnessId>,
2429    harnesses: Vec<HarnessId>,
2430    workspace: Option<PathBuf>,
2431    probe: HarnessProbeLevel,
2432    include_sessions: bool,
2433    /// Omit subprocess-based `--version` calls when a latency-sensitive UI only needs readiness.
2434    skip_versions: bool,
2435}
2436
2437#[derive(Serialize)]
2438struct HarnessInventoryReport {
2439    probe: HarnessProbeLevel,
2440    workspace: Option<PathBuf>,
2441    harnesses: Vec<LocalHarness>,
2442}
2443
2444#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
2445#[serde(rename_all = "snake_case")]
2446enum HarnessAuthState {
2447    Ready,
2448    Configured,
2449    Required,
2450    Unknown,
2451}
2452
2453#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
2454#[serde(rename_all = "snake_case")]
2455enum HarnessRuntimeState {
2456    Ready,
2457    Degraded,
2458    Unavailable,
2459}
2460
2461#[derive(Serialize)]
2462struct HarnessSessionCounts {
2463    global: Option<usize>,
2464    workspace: Option<usize>,
2465}
2466
2467#[derive(Serialize)]
2468struct LocalHarness {
2469    id: HarnessId,
2470    display_name: String,
2471    supported: bool,
2472    installed: bool,
2473    executable: Option<String>,
2474    version: Option<String>,
2475    auth: HarnessAuthState,
2476    runtime: HarnessRuntimeState,
2477    protocol: String,
2478    capabilities: crate::RuntimeCapabilities,
2479    effective_capabilities: crate::RuntimeCapabilities,
2480    sessions: HarnessSessionCounts,
2481    reason: Option<String>,
2482    repair: Option<String>,
2483}
2484
2485#[derive(Clone, Deserialize)]
2486struct RuntimeBackendParams {
2487    harness: HarnessId,
2488    #[serde(default)]
2489    protocol: Option<String>,
2490    #[serde(default)]
2491    launch: Option<RuntimeLaunch>,
2492    #[serde(default)]
2493    base_url: Option<String>,
2494    #[serde(default)]
2495    policy: RuntimePolicy,
2496}
2497
2498#[derive(Debug, Clone, Copy, Default, Deserialize)]
2499#[serde(rename_all = "snake_case")]
2500enum RuntimePolicy {
2501    #[default]
2502    Default,
2503    Yolo,
2504}
2505
2506#[derive(Deserialize)]
2507struct RuntimeStartParams {
2508    #[serde(flatten)]
2509    backend: RuntimeBackendParams,
2510    cwd: PathBuf,
2511}
2512
2513#[derive(Deserialize)]
2514struct RuntimeAttachParams {
2515    #[serde(flatten)]
2516    backend: RuntimeBackendParams,
2517    runtime_id: String,
2518    #[serde(default)]
2519    cwd: Option<PathBuf>,
2520}
2521
2522#[derive(Deserialize)]
2523struct RuntimeConnectionParams {
2524    connection: String,
2525}
2526
2527#[derive(Deserialize)]
2528struct RuntimeInputParams {
2529    connection: String,
2530    text: String,
2531    #[serde(default)]
2532    image_urls: Vec<String>,
2533}
2534
2535const MAX_RUNTIME_IMAGES: usize = 4;
2536const MAX_RUNTIME_IMAGE_URL_BYTES: usize = 12 * 1024 * 1024;
2537const MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL: usize = 32 * 1024 * 1024;
2538
2539fn validate_runtime_image_urls(image_urls: Vec<String>) -> Result<Vec<String>, ServiceError> {
2540    if image_urls.len() > MAX_RUNTIME_IMAGES {
2541        return Err(ServiceError::InvalidParams(format!(
2542            "a runtime prompt accepts at most {MAX_RUNTIME_IMAGES} images"
2543        )));
2544    }
2545    let mut total = 0usize;
2546    for url in &image_urls {
2547        if !(url.starts_with("data:image/")
2548            || url.starts_with("https://")
2549            || url.starts_with("http://"))
2550        {
2551            return Err(ServiceError::InvalidParams(
2552                "runtime images must be image data URLs or HTTP(S) URLs".into(),
2553            ));
2554        }
2555        if url.len() > MAX_RUNTIME_IMAGE_URL_BYTES {
2556            return Err(ServiceError::InvalidParams(format!(
2557                "one runtime image exceeds the {MAX_RUNTIME_IMAGE_URL_BYTES}-byte encoded limit"
2558            )));
2559        }
2560        total = total.saturating_add(url.len());
2561    }
2562    if total > MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL {
2563        return Err(ServiceError::InvalidParams(format!(
2564            "runtime images exceed the {MAX_RUNTIME_IMAGE_URL_BYTES_TOTAL}-byte encoded total limit"
2565        )));
2566    }
2567    Ok(image_urls)
2568}
2569
2570#[derive(Deserialize)]
2571struct RuntimeRespondParams {
2572    connection: String,
2573    request_id: Value,
2574    response: Value,
2575}
2576
2577fn default_reduction_store_root() -> PathBuf {
2578    if let Some(root) = std::env::var_os("SUPERCODE_HOME") {
2579        return PathBuf::from(root).join("sessions");
2580    }
2581    if let Some(home) = std::env::var_os("HOME") {
2582        return PathBuf::from(home).join(".supercode").join("sessions");
2583    }
2584    PathBuf::from(".supercode").join("sessions")
2585}
2586
2587fn messages_jsonl(messages: &[crate::ChatMessage]) -> std::result::Result<String, ServiceError> {
2588    let mut output = String::new();
2589    for message in messages {
2590        output.push_str(
2591            &serde_json::to_string(message)
2592                .map_err(|error| ServiceError::Operation(error.to_string()))?,
2593        );
2594        output.push('\n');
2595    }
2596    Ok(output)
2597}
2598
2599fn parse_messages_jsonl(
2600    content: &str,
2601) -> std::result::Result<Vec<crate::ChatMessage>, ServiceError> {
2602    content
2603        .lines()
2604        .enumerate()
2605        .filter(|(_, line)| !line.trim().is_empty())
2606        .map(|(index, line)| {
2607            serde_json::from_str::<crate::ChatMessage>(line).map_err(|error| {
2608                ServiceError::Operation(format!(
2609                    "reduced transcript line {} is invalid: {error}",
2610                    index + 1
2611                ))
2612            })
2613        })
2614        .collect()
2615}
2616
2617fn reduced_bootstrap_prompt(
2618    source: &SessionLocator,
2619    target: TransferFormat,
2620    view_jsonl: &str,
2621    sidecar_path: &Path,
2622    reduction_log_path: &Path,
2623) -> String {
2624    format!(
2625        "Continue the work from this losslessly reduced {source_harness} session in {target_harness}.\n\
2626         \n\
2627         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\
2628         \n\
2629         <supercode-reduced-session source-session=\"{source_id}\">\n\
2630         {view_jsonl}\
2631         </supercode-reduced-session>\n\
2632         \n\
2633         Resume from the latest unresolved user request and preserve the source session's decisions and constraints.",
2634        source_harness = source.harness.as_str(),
2635        target_harness = target.id(),
2636        sidecar = sidecar_path.display(),
2637        log = reduction_log_path.display(),
2638        source_id = source.session_id,
2639    )
2640}
2641
2642fn session_artifact(
2643    locator: &SessionLocator,
2644    session: &Session,
2645    target: TransferFormat,
2646) -> std::result::Result<SessionArtifact, ServiceError> {
2647    session_artifact_with_id(locator, session, target, None)
2648}
2649
2650fn session_artifact_with_id(
2651    locator: &SessionLocator,
2652    session: &Session,
2653    target: TransferFormat,
2654    target_session_id: Option<&str>,
2655) -> std::result::Result<SessionArtifact, ServiceError> {
2656    let format: SessionFormat = target.into();
2657    let diagonal = format.source() == session.meta.source;
2658    let has_appended_turns = session
2659        .imported_message_count
2660        .is_some_and(|imported| imported < session.messages.len());
2661    let content = if let Some(id) = target_session_id {
2662        if diagonal && format != SessionFormat::OpenCode {
2663            session
2664                .to_jsonl_spliced(format, Some(id))
2665                .map_err(operation)?
2666        } else {
2667            let mut rewritten = session.clone();
2668            rewritten.meta.session_id = Some(id.to_string());
2669            rewritten.to_jsonl(format).map_err(operation)?
2670        }
2671    } else if diagonal && session.raw_is_verbatim && !has_appended_turns {
2672        session.raw_verbatim()
2673    } else if diagonal {
2674        session.to_jsonl_spliced(format, None).map_err(operation)?
2675    } else {
2676        session.to_jsonl(format).map_err(operation)?
2677    };
2678    let stem = sanitize_filename(
2679        target_session_id
2680            .or(session.meta.session_id.as_deref())
2681            .unwrap_or(&locator.session_id),
2682    );
2683    let suggested_filename = if diagonal && target == TransferFormat::Grok {
2684        "chat_history.jsonl".to_string()
2685    } else if target == TransferFormat::Goose {
2686        format!("{stem}.goose.json")
2687    } else {
2688        format!("{stem}.{}.jsonl", target.id())
2689    };
2690    let mut files = vec![SessionArtifactFile {
2691        path: suggested_filename.clone(),
2692        content: content.clone(),
2693        role: ArtifactFileRole::Primary,
2694    }];
2695    if target == TransferFormat::ClaudeCode {
2696        let bundle_stem = Path::new(&suggested_filename)
2697            .file_stem()
2698            .and_then(|stem| stem.to_str())
2699            .unwrap_or(&stem);
2700        let mut child_paths = BTreeSet::new();
2701        for (index, subagent) in session.subagents.iter().enumerate() {
2702            let agent_id = subagent
2703                .meta
2704                .agent_id
2705                .as_deref()
2706                .map(|id| id.strip_prefix("agent-").unwrap_or(id))
2707                .map(sanitize_filename)
2708                .filter(|id| !id.is_empty())
2709                .unwrap_or_else(|| format!("subagent-{}", index + 1));
2710            let child_has_appended_turns = subagent
2711                .imported_message_count
2712                .is_some_and(|imported| imported < subagent.messages.len());
2713            let child_content = if target_session_id.is_none()
2714                && subagent.meta.source == SessionSource::ClaudeCode
2715                && subagent.raw_is_verbatim
2716                && !child_has_appended_turns
2717            {
2718                subagent.raw_verbatim()
2719            } else if subagent.meta.source == SessionSource::ClaudeCode {
2720                subagent
2721                    .to_jsonl_spliced(SessionFormat::ClaudeCode, target_session_id)
2722                    .map_err(operation)?
2723            } else {
2724                let mut child = subagent.clone();
2725                if let Some(id) = target_session_id {
2726                    child.meta.session_id = Some(id.to_string());
2727                }
2728                child
2729                    .to_jsonl(SessionFormat::ClaudeCode)
2730                    .map_err(operation)?
2731            };
2732            let path = format!("{bundle_stem}/subagents/agent-{agent_id}.jsonl");
2733            if !child_paths.insert(path.clone()) {
2734                return Err(ServiceError::Operation(format!(
2735                    "Claude subagent ids collide at artifact path `{path}`"
2736                )));
2737            }
2738            files.push(SessionArtifactFile {
2739                path,
2740                content: child_content,
2741                role: ArtifactFileRole::Subagent,
2742            });
2743        }
2744    }
2745    if diagonal && target == TransferFormat::Grok {
2746        append_grok_bundle_files(locator, "", ArtifactFileRole::Bundle, &mut files)?;
2747    }
2748    if !diagonal || !session.raw_is_verbatim {
2749        files.push(SessionArtifactFile {
2750            path: "recovery/source.supercode.jsonl".into(),
2751            content: session.to_native_jsonl(),
2752            role: ArtifactFileRole::SourceRecovery,
2753        });
2754        for (index, subagent) in session.subagents.iter().enumerate() {
2755            let id = subagent
2756                .meta
2757                .agent_id
2758                .as_deref()
2759                .map(sanitize_filename)
2760                .unwrap_or_else(|| format!("subagent-{}", index + 1));
2761            files.push(SessionArtifactFile {
2762                path: format!("recovery/subagents/{id}.supercode.jsonl"),
2763                content: subagent.to_native_jsonl(),
2764                role: ArtifactFileRole::SourceRecovery,
2765            });
2766        }
2767    }
2768    if !diagonal && session.meta.source == SessionSource::Grok {
2769        append_grok_bundle_files(
2770            locator,
2771            "recovery/grok/",
2772            ArtifactFileRole::SourceRecovery,
2773            &mut files,
2774        )?;
2775    }
2776    let (fidelity, residue) = if diagonal
2777        && target_session_id.is_none()
2778        && session.raw_is_verbatim
2779        && !has_appended_turns
2780    {
2781        (Fidelity::ByteLossless, Vec::new())
2782    } else if diagonal && !(target_session_id.is_some() && target == TransferFormat::OpenCode) {
2783        (
2784            Fidelity::ValueLossless,
2785            vec![if target_session_id.is_some() {
2786                "target identity was rewritten, so the artifact intentionally differs from source bytes".into()
2787            } else {
2788                "source storage was reconstructed as a native-value-equivalent export; original container bytes were not captured".into()
2789            }],
2790        )
2791    } else {
2792        (
2793            Fidelity::Semantic,
2794            vec!["target schema has no portable slot for every source-native record and metadata field".into()],
2795        )
2796    };
2797    Ok(SessionArtifact {
2798        source_harness: locator.harness.clone(),
2799        target_harness: target.id(),
2800        session_id: target_session_id
2801            .map(str::to_string)
2802            .or_else(|| session.meta.session_id.clone()),
2803        content,
2804        suggested_filename,
2805        files,
2806        fidelity,
2807        residue,
2808    })
2809}
2810
2811fn append_grok_bundle_files(
2812    locator: &SessionLocator,
2813    prefix: &str,
2814    role: ArtifactFileRole,
2815    files: &mut Vec<SessionArtifactFile>,
2816) -> std::result::Result<(), ServiceError> {
2817    let primary = locator.storage.path();
2818    if primary.file_name().and_then(|name| name.to_str()) != Some("chat_history.jsonl") {
2819        return Err(ServiceError::Operation(format!(
2820            "Grok bundle locator must name chat_history.jsonl, got {}",
2821            primary.display()
2822        )));
2823    }
2824    let parent = primary.parent().ok_or_else(|| {
2825        ServiceError::Operation("Grok chat_history.jsonl has no session directory".into())
2826    })?;
2827    for name in ["summary.json", "updates.jsonl"] {
2828        let path = parent.join(name);
2829        let metadata = match std::fs::symlink_metadata(&path) {
2830            Ok(metadata) => metadata,
2831            Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
2832            Err(error) => return Err(ServiceError::Operation(error.to_string())),
2833        };
2834        if metadata.file_type().is_symlink() || !metadata.is_file() {
2835            return Err(ServiceError::Operation(format!(
2836                "refusing non-regular Grok bundle member {}",
2837                path.display()
2838            )));
2839        }
2840        let content = std::fs::read_to_string(&path).map_err(|error| {
2841            ServiceError::Operation(format!(
2842                "Grok bundle member {} is not representable as UTF-8: {error}",
2843                path.display()
2844            ))
2845        })?;
2846        files.push(SessionArtifactFile {
2847            path: format!("{prefix}{name}"),
2848            content,
2849            role: match role {
2850                ArtifactFileRole::Bundle => ArtifactFileRole::Bundle,
2851                _ => ArtifactFileRole::SourceRecovery,
2852            },
2853        });
2854    }
2855    Ok(())
2856}
2857
2858fn handoff_artifact(
2859    locator: &SessionLocator,
2860    session: &Session,
2861    target: TransferFormat,
2862    cwd: &Path,
2863) -> std::result::Result<SessionArtifact, ServiceError> {
2864    if target != TransferFormat::Grok {
2865        let target_session_id = target_session_id(target);
2866        return session_artifact_with_id(locator, session, target, Some(&target_session_id));
2867    }
2868
2869    // Stock Grok's importer accepts Claude/Codex transcripts and materializes its own
2870    // multi-file session bundle. A synthesized Grok chat_history.jsonl alone is not a
2871    // resumable handoff because updates.jsonl is the authoritative restore log.
2872    let mut importable = session.clone();
2873    // The Claude importer validates sessionId as a UUID. Source harness identities
2874    // are not portable (OpenCode, for example, uses `ses_...`), and a handoff must
2875    // not overwrite an existing target session when the source already uses UUIDs.
2876    // Mint a distinct target identity and still bind the importer-returned ID at
2877    // launch time because the importer remains the authority on materialization.
2878    importable.meta.session_id = Some(target_session_id(TransferFormat::ClaudeCode));
2879    importable.meta.cwd = Some(if cwd.is_absolute() {
2880        cwd.to_path_buf()
2881    } else {
2882        std::env::current_dir()
2883            .map_err(|error| ServiceError::Operation(error.to_string()))?
2884            .join(cwd)
2885    });
2886    let content = importable
2887        .to_jsonl(SessionFormat::ClaudeCode)
2888        .map_err(operation)?;
2889    let stem = sanitize_filename(
2890        importable
2891            .meta
2892            .session_id
2893            .as_deref()
2894            .unwrap_or(&locator.session_id),
2895    );
2896    let suggested_filename = format!("{stem}.grok-import.claude-code.jsonl");
2897    Ok(SessionArtifact {
2898        source_harness: locator.harness.clone(),
2899        // This names the artifact's actual wire format. The requested handoff target
2900        // remains Grok; its official importer is the materialization boundary.
2901        target_harness: TransferFormat::ClaudeCode.id(),
2902        session_id: importable.meta.session_id.clone(),
2903        content: content.clone(),
2904        suggested_filename: suggested_filename.clone(),
2905        files: vec![SessionArtifactFile {
2906            path: suggested_filename,
2907            content,
2908            role: ArtifactFileRole::Primary,
2909        }],
2910        fidelity: Fidelity::Semantic,
2911        residue: vec!["Grok's stock importer accepts a Claude Code transcript, not a complete Grok updates/session bundle".into()],
2912    })
2913}
2914
2915fn target_session_id(target: TransferFormat) -> String {
2916    let uuid = generated_session_id();
2917    match target {
2918        TransferFormat::OpenCode => format!("ses_{}", uuid.replace('-', "")),
2919        TransferFormat::ClaudeCode
2920        | TransferFormat::Codex
2921        | TransferFormat::Pi
2922        | TransferFormat::Grok
2923        | TransferFormat::Gemini
2924        | TransferFormat::Goose => uuid,
2925    }
2926}
2927
2928fn sanitize_filename(value: &str) -> String {
2929    let value = value
2930        .chars()
2931        .map(|character| {
2932            if character.is_ascii_alphanumeric() || matches!(character, '-' | '_') {
2933                character
2934            } else {
2935                '-'
2936            }
2937        })
2938        .collect::<String>();
2939    let value = value.trim_matches('-');
2940    if value.is_empty() {
2941        "session".into()
2942    } else {
2943        value.chars().take(100).collect()
2944    }
2945}
2946
2947fn handoff_instructions(
2948    target: TransferFormat,
2949    session_id: &str,
2950    cwd: &Path,
2951) -> HandoffInstructions {
2952    let launch = |program: &str, arguments: Vec<String>| StructuredLaunch {
2953        cwd: cwd.to_path_buf(),
2954        program: program.into(),
2955        arguments,
2956        env: BTreeMap::new(),
2957    };
2958    match target {
2959        TransferFormat::ClaudeCode => HandoffInstructions {
2960            launch: launch("claude", vec!["--resume".into(), session_id.into()]),
2961            materialize: None,
2962            requires_materialization: true,
2963            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(),
2964        },
2965        TransferFormat::Codex => HandoffInstructions {
2966            launch: launch("codex", vec!["resume".into(), session_id.into()]),
2967            materialize: None,
2968            requires_materialization: true,
2969            note: "Write the artifact into Codex's native rollout store before running the resume launch; Codex has no general transcript-import command.".into(),
2970        },
2971        TransferFormat::OpenCode => HandoffInstructions {
2972            launch: launch("opencode", vec!["--session".into(), session_id.into()]),
2973            materialize: Some(launch(
2974                "opencode",
2975                vec!["import".into(), "{artifact_path}".into()],
2976            )),
2977            requires_materialization: true,
2978            note: "Write the artifact to a file, run the materialize command with its path, then launch the imported session.".into(),
2979        },
2980        TransferFormat::Pi => HandoffInstructions {
2981            launch: launch("pi", vec!["--session".into(), "{artifact_path}".into()]),
2982            materialize: None,
2983            requires_materialization: true,
2984            note: "Write the artifact to a file and replace {artifact_path} in the launch arguments; Pi can resume that file directly.".into(),
2985        },
2986        TransferFormat::Grok => HandoffInstructions {
2987            launch: launch(
2988                "grok",
2989                vec![
2990                    "--resume".into(),
2991                    "{imported_session_id}".into(),
2992                    "--fork-session".into(),
2993                ],
2994            ),
2995            materialize: Some(launch(
2996                "grok",
2997                vec!["import".into(), "--json".into(), "{artifact_path}".into()],
2998            )),
2999            requires_materialization: true,
3000            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(),
3001        },
3002        TransferFormat::Gemini => HandoffInstructions {
3003            launch: launch(
3004                "gemini",
3005                vec!["--session-file".into(), "{artifact_path}".into()],
3006            ),
3007            materialize: None,
3008            requires_materialization: true,
3009            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(),
3010        },
3011        TransferFormat::Goose => HandoffInstructions {
3012            launch: launch(
3013                "goose",
3014                vec![
3015                    "session".into(),
3016                    "--resume".into(),
3017                    "--session-id".into(),
3018                    "{imported_session_id}".into(),
3019                ],
3020            ),
3021            materialize: Some(launch(
3022                "goose",
3023                vec!["session".into(), "import".into(), "{artifact_path}".into()],
3024            )),
3025            requires_materialization: true,
3026            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(),
3027        },
3028    }
3029}
3030
3031fn resume_launch(
3032    harness: &str,
3033    session_id: &str,
3034    cwd: &Path,
3035    policy: ResumePolicy,
3036) -> std::result::Result<StructuredLaunch, ServiceError> {
3037    let mut arguments = Vec::new();
3038    let program = match harness {
3039        HarnessId::GROK => {
3040            if matches!(policy, ResumePolicy::Yolo) {
3041                arguments.extend([
3042                    "--sandbox".into(),
3043                    "workspace".into(),
3044                    "--always-approve".into(),
3045                ]);
3046            }
3047            arguments.extend(["--resume".into(), session_id.into()]);
3048            "grok"
3049        }
3050        HarnessId::CODEX => {
3051            let cwd_key = serde_json::to_string(cwd.to_string_lossy().as_ref())
3052                .expect("a filesystem path always serializes as JSON text");
3053            arguments.extend([
3054                "-c".into(),
3055                "check_for_update_on_startup=false".into(),
3056                "-c".into(),
3057                format!("projects.{cwd_key}.trust_level=\"trusted\""),
3058            ]);
3059            if matches!(policy, ResumePolicy::Yolo) {
3060                arguments.extend([
3061                    "--dangerously-bypass-approvals-and-sandbox".into(),
3062                    "--dangerously-bypass-hook-trust".into(),
3063                ]);
3064            }
3065            arguments.extend(["resume".into(), session_id.into()]);
3066            "codex"
3067        }
3068        HarnessId::CLAUDE_CODE => {
3069            if matches!(policy, ResumePolicy::Yolo) {
3070                arguments.push("--dangerously-skip-permissions".into());
3071            }
3072            arguments.extend(["--resume".into(), session_id.into()]);
3073            "claude"
3074        }
3075        HarnessId::GEMINI => {
3076            if matches!(policy, ResumePolicy::Yolo) {
3077                arguments.push("--yolo".into());
3078            }
3079            arguments.extend(["--resume".into(), session_id.into()]);
3080            "gemini"
3081        }
3082        HarnessId::GOOSE => {
3083            arguments.extend([
3084                "session".into(),
3085                "--resume".into(),
3086                "--session-id".into(),
3087                session_id.into(),
3088            ]);
3089            "goose"
3090        }
3091        HarnessId::PI => {
3092            if matches!(policy, ResumePolicy::Yolo) {
3093                arguments.push("--approve".into());
3094            }
3095            arguments.extend(["--session".into(), session_id.into()]);
3096            "pi"
3097        }
3098        HarnessId::OPENCODE => {
3099            arguments.extend(["--session".into(), session_id.into()]);
3100            "opencode"
3101        }
3102        HarnessId::SUPERCODE => {
3103            if matches!(policy, ResumePolicy::Yolo) {
3104                arguments.push("--dangerous".into());
3105            }
3106            arguments.extend(["resume".into(), session_id.into()]);
3107            "supercode"
3108        }
3109        other => {
3110            return Err(ServiceError::InvalidParams(format!(
3111                "no structured resume launch is registered for harness `{other}`"
3112            )))
3113        }
3114    };
3115    Ok(StructuredLaunch {
3116        cwd: cwd.to_path_buf(),
3117        program: program.into(),
3118        arguments,
3119        env: BTreeMap::new(),
3120    })
3121}
3122
3123fn runtime_backend(
3124    params: &RuntimeBackendParams,
3125) -> std::result::Result<Box<dyn RuntimeBackend>, ServiceError> {
3126    if params.protocol.as_deref() == Some("acp") {
3127        let launch = params
3128            .launch
3129            .clone()
3130            .or_else(|| {
3131                harness_support_registry()
3132                    .harnesses
3133                    .into_iter()
3134                    .find(|harness| harness.id == params.harness)
3135                    .filter(|harness| {
3136                        harness.runtime.implementation == ImplementationKind::GenericProtocol
3137                            && harness.runtime.protocol.starts_with("acp")
3138                    })
3139                    .and_then(|harness| harness.runtime.default_launch)
3140            })
3141            .ok_or_else(|| {
3142                ServiceError::InvalidParams(
3143                    "an ACP runtime requires `launch` unless the harness has a registered default"
3144                        .into(),
3145                )
3146            })?;
3147        let resume_session = harness_support_registry()
3148            .harnesses
3149            .into_iter()
3150            .find(|harness| harness.id == params.harness)
3151            .is_some_and(|harness| harness.runtime.capabilities.resume_session);
3152        return Ok(Box::new(
3153            AcpRuntimeBackend::new(params.harness.clone(), launch)
3154                .with_resume_support(resume_session),
3155        ));
3156    }
3157    let backend: Box<dyn RuntimeBackend> = match params.harness.as_str() {
3158        HarnessId::CODEX => Box::new(CodexRuntimeBackend::new()),
3159        HarnessId::CLAUDE_CODE => Box::new(ClaudeCodeRuntimeBackend::new()),
3160        HarnessId::PI => Box::new(PiRuntimeBackend::new()),
3161        HarnessId::OPENCODE => match &params.base_url {
3162            Some(url) => Box::new(OpenCodeRuntimeBackend::connect(url)),
3163            None => Box::new(OpenCodeRuntimeBackend::new()),
3164        },
3165        harness => {
3166            let descriptor = harness_support_registry()
3167                .harnesses
3168                .into_iter()
3169                .find(|descriptor| descriptor.id.as_str() == harness)
3170                .filter(|descriptor| {
3171                    descriptor.runtime.implementation == ImplementationKind::GenericProtocol
3172                        && descriptor.runtime.protocol.starts_with("acp")
3173                });
3174            let Some(descriptor) = descriptor else {
3175                return Err(ServiceError::InvalidParams(format!(
3176                    "no runtime adapter for harness `{harness}`; use protocol `acp` with a launch command"
3177                )));
3178            };
3179            let resume = descriptor.runtime.capabilities.resume_session;
3180            Box::new(
3181                AcpRuntimeBackend::new(
3182                    descriptor.id,
3183                    descriptor
3184                        .runtime
3185                        .default_launch
3186                        .expect("generic ACP registry entry includes its launch"),
3187                )
3188                .with_resume_support(resume),
3189            )
3190        }
3191    };
3192    Ok(backend)
3193}
3194
3195fn runtime_launch(params: &RuntimeBackendParams) -> Option<RuntimeLaunch> {
3196    if let Some(launch) = &params.launch {
3197        return Some(launch.clone());
3198    }
3199    if !matches!(params.policy, RuntimePolicy::Yolo) {
3200        return None;
3201    }
3202    let launch = match params.harness.as_str() {
3203        HarnessId::GROK => RuntimeLaunch {
3204            program: "grok".into(),
3205            arguments: vec![
3206                "--sandbox".into(),
3207                "workspace".into(),
3208                "--always-approve".into(),
3209                "agent".into(),
3210                "--no-leader".into(),
3211                "stdio".into(),
3212            ],
3213            env: BTreeMap::from([("GROK_AGENT_DASHBOARD".into(), "0".into())]),
3214        },
3215        HarnessId::CODEX => RuntimeLaunch {
3216            program: "codex".into(),
3217            arguments: vec![
3218                "--dangerously-bypass-approvals-and-sandbox".into(),
3219                "--dangerously-bypass-hook-trust".into(),
3220                "app-server".into(),
3221            ],
3222            env: BTreeMap::new(),
3223        },
3224        HarnessId::CLAUDE_CODE => RuntimeLaunch {
3225            program: "claude".into(),
3226            arguments: vec![
3227                "--dangerously-skip-permissions".into(),
3228                "--print".into(),
3229                "--input-format".into(),
3230                "stream-json".into(),
3231                "--output-format".into(),
3232                "stream-json".into(),
3233                "--verbose".into(),
3234            ],
3235            env: BTreeMap::new(),
3236        },
3237        HarnessId::PI => RuntimeLaunch {
3238            program: "pi".into(),
3239            arguments: vec!["--approve".into(), "--mode".into(), "rpc".into()],
3240            env: BTreeMap::new(),
3241        },
3242        HarnessId::OPENCODE => RuntimeLaunch {
3243            program: "opencode".into(),
3244            arguments: vec!["serve".into()],
3245            env: BTreeMap::new(),
3246        },
3247        HarnessId::GEMINI => RuntimeLaunch {
3248            program: "gemini".into(),
3249            arguments: vec!["--acp".into(), "--yolo".into()],
3250            env: BTreeMap::new(),
3251        },
3252        HarnessId::GOOSE => RuntimeLaunch {
3253            program: "goose".into(),
3254            arguments: vec!["acp".into()],
3255            env: BTreeMap::new(),
3256        },
3257        HarnessId::SUPERCODE => RuntimeLaunch {
3258            program: "supercode".into(),
3259            arguments: vec!["acp".into(), "--dangerous".into()],
3260            env: BTreeMap::new(),
3261        },
3262        _ => return None,
3263    };
3264    Some(launch)
3265}
3266
3267/// Disposable harness state for a no-prompt readiness probe. Merely opening
3268/// several stock CLIs writes a session header or migrates configuration, so a
3269/// handshake must never point at the user's real home. Authentication files
3270/// are copied into the private temporary home; all writes disappear with the
3271/// guard after the connection closes.
3272struct IsolatedProbeHome {
3273    launch: RuntimeLaunch,
3274    root: PathBuf,
3275}
3276
3277impl IsolatedProbeHome {
3278    fn new(harness: &str, mut launch: RuntimeLaunch) -> std::io::Result<Self> {
3279        let root = std::env::temp_dir().join(format!(
3280            "supercode-harness-probe-{harness}-{}",
3281            generated_session_id()
3282        ));
3283        std::fs::create_dir_all(&root)?;
3284        set_private_dir_permissions(&root)?;
3285
3286        if let Some(source_home) = std::env::var_os("HOME").map(PathBuf::from) {
3287            for relative in probe_auth_files(harness) {
3288                copy_probe_file(&source_home, &root, relative)?;
3289            }
3290        }
3291        configure_isolated_probe_auth(harness, &root)?;
3292
3293        let root_text = root.to_string_lossy().into_owned();
3294        for (key, value) in [
3295            ("HOME", root_text.clone()),
3296            (
3297                "XDG_CACHE_HOME",
3298                root.join(".cache").to_string_lossy().into_owned(),
3299            ),
3300            (
3301                "XDG_CONFIG_HOME",
3302                root.join(".config").to_string_lossy().into_owned(),
3303            ),
3304            (
3305                "XDG_DATA_HOME",
3306                root.join(".local/share").to_string_lossy().into_owned(),
3307            ),
3308        ] {
3309            launch.env.insert(key.into(), value);
3310        }
3311        let scoped = match harness {
3312            HarnessId::CLAUDE_CODE => Some(("CLAUDE_CONFIG_DIR", root.join(".claude"))),
3313            HarnessId::CODEX => Some(("CODEX_HOME", root.join(".codex"))),
3314            HarnessId::GEMINI => Some(("GEMINI_CLI_HOME", root.clone())),
3315            HarnessId::GROK => Some(("GROK_HOME", root.join(".grok"))),
3316            HarnessId::PI => Some(("PI_CODING_AGENT_DIR", root.join(".pi/agent"))),
3317            HarnessId::SUPERCODE => Some(("SUPERCODE_HOME", root.join(".config/supercode"))),
3318            _ => None,
3319        };
3320        if let Some((key, value)) = scoped {
3321            launch
3322                .env
3323                .insert(key.into(), value.to_string_lossy().into_owned());
3324        }
3325        Ok(Self { launch, root })
3326    }
3327
3328    fn cleanup(&self) -> std::io::Result<()> {
3329        match std::fs::remove_dir_all(&self.root) {
3330            Ok(()) => Ok(()),
3331            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
3332            Err(error) => Err(error),
3333        }
3334    }
3335}
3336
3337impl Drop for IsolatedProbeHome {
3338    fn drop(&mut self) {
3339        let _ = self.cleanup();
3340    }
3341}
3342
3343fn probe_auth_files(harness: &str) -> &'static [&'static str] {
3344    match harness {
3345        HarnessId::CLAUDE_CODE => &[".claude/.credentials.json", ".claude.json"],
3346        HarnessId::CODEX => &[".codex/auth.json"],
3347        HarnessId::GEMINI => &[
3348            ".gemini/google_accounts.json",
3349            ".gemini/oauth_creds.json",
3350            ".gemini/settings.json",
3351        ],
3352        HarnessId::GROK => &[".grok/auth.json", ".grok/config.toml"],
3353        HarnessId::OPENCODE => &[
3354            ".config/opencode/auth.json",
3355            ".local/share/opencode/auth.json",
3356        ],
3357        HarnessId::PI => &[".pi/agent/auth.json"],
3358        HarnessId::SUPERCODE => &[
3359            ".config/supercode/config.toml",
3360            ".config/supercode/credentials.toml",
3361        ],
3362        _ => &[],
3363    }
3364}
3365
3366fn copy_probe_file(source_home: &Path, probe_home: &Path, relative: &str) -> std::io::Result<()> {
3367    let source = source_home.join(relative);
3368    if !source.is_file() {
3369        return Ok(());
3370    }
3371    let destination = probe_home.join(relative);
3372    if let Some(parent) = destination.parent() {
3373        std::fs::create_dir_all(parent)?;
3374        set_private_dir_permissions(parent)?;
3375    }
3376    std::fs::copy(source, &destination)?;
3377    set_private_file_permissions(&destination)
3378}
3379
3380fn configure_isolated_probe_auth(harness: &str, probe_home: &Path) -> std::io::Result<()> {
3381    if harness != HarnessId::GEMINI {
3382        return Ok(());
3383    }
3384    let oauth = probe_home.join(".gemini/oauth_creds.json");
3385    if !oauth.is_file() {
3386        return Ok(());
3387    }
3388    let settings_path = probe_home.join(".gemini/settings.json");
3389    let mut settings = std::fs::read_to_string(&settings_path)
3390        .ok()
3391        .and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
3392        .unwrap_or_else(|| json!({}));
3393    settings["security"]["auth"]["selectedType"] = Value::String("oauth-personal".into());
3394    std::fs::write(
3395        &settings_path,
3396        serde_json::to_vec_pretty(&settings).map_err(std::io::Error::other)?,
3397    )?;
3398    set_private_file_permissions(&settings_path)
3399}
3400
3401#[cfg(unix)]
3402fn set_private_dir_permissions(path: &Path) -> std::io::Result<()> {
3403    use std::os::unix::fs::PermissionsExt;
3404    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
3405}
3406
3407#[cfg(not(unix))]
3408fn set_private_dir_permissions(_path: &Path) -> std::io::Result<()> {
3409    Ok(())
3410}
3411
3412#[cfg(unix)]
3413fn set_private_file_permissions(path: &Path) -> std::io::Result<()> {
3414    use std::os::unix::fs::PermissionsExt;
3415    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
3416}
3417
3418#[cfg(not(unix))]
3419fn set_private_file_permissions(_path: &Path) -> std::io::Result<()> {
3420    Ok(())
3421}
3422
3423fn find_executable(program: &str) -> Option<PathBuf> {
3424    let candidate = PathBuf::from(program);
3425    if candidate.components().count() > 1 {
3426        return candidate.is_file().then_some(candidate);
3427    }
3428    let path = std::env::var_os("PATH")?;
3429    for directory in std::env::split_paths(&path) {
3430        let candidate = directory.join(program);
3431        if candidate.is_file() {
3432            return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
3433        }
3434        #[cfg(windows)]
3435        {
3436            for extension in ["exe", "cmd", "bat"] {
3437                let candidate = directory.join(format!("{program}.{extension}"));
3438                if candidate.is_file() {
3439                    return std::fs::canonicalize(&candidate).ok().or(Some(candidate));
3440                }
3441            }
3442        }
3443    }
3444    None
3445}
3446
3447async fn executable_version(executable: &Path) -> Option<String> {
3448    let mut command = tokio::process::Command::new(executable);
3449    command
3450        .arg("--version")
3451        .stdin(std::process::Stdio::null())
3452        .stdout(std::process::Stdio::piped())
3453        .stderr(std::process::Stdio::piped())
3454        .kill_on_drop(true);
3455    let output = tokio::time::timeout(Duration::from_secs(3), command.output())
3456        .await
3457        .ok()?
3458        .ok()?;
3459    let stdout = String::from_utf8_lossy(&output.stdout);
3460    let stderr = String::from_utf8_lossy(&output.stderr);
3461    stdout
3462        .lines()
3463        .chain(stderr.lines())
3464        .map(str::trim)
3465        .find(|line| !line.is_empty())
3466        .map(|line| truncate_text(line, 200))
3467}
3468
3469fn auth_evidence(harness: &str) -> bool {
3470    let env_names: &[&str] = match harness {
3471        HarnessId::CLAUDE_CODE => &["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
3472        HarnessId::CODEX => &["OPENAI_API_KEY"],
3473        HarnessId::OPENCODE => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
3474        HarnessId::PI => &["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY"],
3475        HarnessId::GROK => &["XAI_API_KEY", "GROK_API_KEY"],
3476        HarnessId::GEMINI => &["GEMINI_API_KEY", "GOOGLE_API_KEY"],
3477        HarnessId::SUPERCODE => &["OPENROUTER_API_KEY"],
3478        _ => &[],
3479    };
3480    if env_names
3481        .iter()
3482        .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()))
3483    {
3484        return true;
3485    }
3486    let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else {
3487        return false;
3488    };
3489    let files: Vec<PathBuf> = match harness {
3490        HarnessId::CLAUDE_CODE => vec![home.join(".claude/.credentials.json")],
3491        HarnessId::CODEX => vec![home.join(".codex/auth.json")],
3492        HarnessId::OPENCODE => vec![
3493            home.join(".local/share/opencode/auth.json"),
3494            home.join(".config/opencode/auth.json"),
3495        ],
3496        HarnessId::PI => vec![home.join(".pi/agent/auth.json")],
3497        HarnessId::GROK => vec![home.join(".grok/auth.json")],
3498        HarnessId::GEMINI => vec![
3499            home.join(".gemini/oauth_creds.json"),
3500            home.join(".gemini/google_accounts.json"),
3501        ],
3502        HarnessId::SUPERCODE => vec![home.join(".config/supercode/credentials.toml")],
3503        _ => Vec::new(),
3504    };
3505    if files.into_iter().any(|path| {
3506        std::fs::metadata(path)
3507            .map(|metadata| metadata.is_file() && metadata.len() > 2)
3508            .unwrap_or(false)
3509    }) {
3510        return true;
3511    }
3512    // macOS keeps Claude Code's OAuth login in the Keychain, so
3513    // `.claude/.credentials.json` never exists there and the file probe above
3514    // reports a signed-in install as unauthenticated forever. A completed
3515    // login also writes an `oauthAccount` record into `~/.claude.json` on
3516    // every platform — file-based, prompt-free evidence (querying the
3517    // Keychain itself from an unsigned daemon can raise a UI prompt).
3518    if harness == HarnessId::CLAUDE_CODE {
3519        return std::fs::read_to_string(home.join(".claude.json"))
3520            .map(|text| text.contains("\"oauthAccount\""))
3521            .unwrap_or(false);
3522    }
3523    false
3524}
3525
3526fn looks_like_auth_error(message: &str) -> bool {
3527    let message = message.to_ascii_lowercase();
3528    [
3529        "auth",
3530        "login",
3531        "sign in",
3532        "sign-in",
3533        "credential",
3534        "unauthorized",
3535        "forbidden",
3536        "token",
3537    ]
3538    .iter()
3539    .any(|needle| message.contains(needle))
3540}
3541
3542fn unavailable_capabilities() -> crate::RuntimeCapabilities {
3543    crate::RuntimeCapabilities {
3544        start_session: false,
3545        resume_session: false,
3546        attach_existing_process: false,
3547        send_input: false,
3548        stream_events: false,
3549        interrupt: false,
3550        steer: false,
3551        respond_to_requests: false,
3552    }
3553}
3554
3555fn truncate_text(text: &str, max_chars: usize) -> String {
3556    let mut chars = text.chars();
3557    let truncated = chars.by_ref().take(max_chars).collect::<String>();
3558    if chars.next().is_some() {
3559        format!("{truncated}…")
3560    } else {
3561        truncated
3562    }
3563}
3564
3565fn error_message(error: ServiceError) -> String {
3566    match error {
3567        ServiceError::InvalidParams(message)
3568        | ServiceError::Operation(message)
3569        | ServiceError::UnsupportedAction(message) => message,
3570        ServiceError::MethodNotFound => "runtime adapter is not available".into(),
3571        ServiceError::Sdk(error) => error.to_string(),
3572    }
3573}
3574
3575#[derive(Debug)]
3576enum ServiceError {
3577    InvalidParams(String),
3578    MethodNotFound,
3579    UnsupportedAction(String),
3580    Operation(String),
3581    Sdk(SdkError),
3582}
3583
3584fn sdk_error(operation: SdkOperation, error: ServiceError) -> SdkError {
3585    match error {
3586        ServiceError::InvalidParams(message) => {
3587            SdkError::new(SdkErrorCode::InvalidArgument, operation, message)
3588        }
3589        ServiceError::MethodNotFound | ServiceError::UnsupportedAction(_) => {
3590            SdkError::unsupported(operation)
3591        }
3592        ServiceError::Operation(message) => {
3593            let code = if message.contains("already in progress") {
3594                SdkErrorCode::Busy
3595            } else if message.contains("not supported by this runtime") {
3596                SdkErrorCode::UnsupportedAction
3597            } else if message.contains("unknown runtime connection") {
3598                SdkErrorCode::NotFound
3599            } else {
3600                SdkErrorCode::Execution
3601            };
3602            SdkError::new(code, operation, message)
3603        }
3604        ServiceError::Sdk(error) => error,
3605    }
3606}
3607
3608fn sdk_rpc_error(id: Value, error: &SdkError) -> Value {
3609    let error_code = error.code();
3610    let code = match error_code {
3611        SdkErrorCode::Unauthenticated => -32030,
3612        SdkErrorCode::Unauthorized => -32031,
3613        SdkErrorCode::ControllerRequired => -32032,
3614        SdkErrorCode::LeaseExpired => -32033,
3615        SdkErrorCode::InvalidArgument => -32602,
3616        SdkErrorCode::NotFound => -32004,
3617        SdkErrorCode::Busy => -32000,
3618        SdkErrorCode::UnsupportedAction => -32020,
3619        SdkErrorCode::Execution => -32002,
3620        SdkErrorCode::Transport => -32003,
3621    };
3622    json!({
3623        "jsonrpc": "2.0",
3624        "id": id,
3625        "error": {
3626            "code": code,
3627            "name": error_code,
3628            "operation": error.operation(),
3629            "message": error.to_string(),
3630        },
3631    })
3632}
3633
3634fn decode<T: for<'de> Deserialize<'de>>(value: Value) -> std::result::Result<T, ServiceError> {
3635    serde_json::from_value(value).map_err(|error| ServiceError::InvalidParams(error.to_string()))
3636}
3637
3638fn operation(error: impl Into<crate::Error>) -> ServiceError {
3639    let error = error.into();
3640    match error {
3641        crate::Error::Sdk(error) => ServiceError::Sdk(error),
3642        error => ServiceError::Operation(error.to_string()),
3643    }
3644}
3645
3646fn rpc_error(id: Value, code: i64, message: &str) -> Value {
3647    json!({
3648        "jsonrpc": "2.0",
3649        "id": id,
3650        "error": {"code": code, "message": message},
3651    })
3652}
3653
3654#[cfg(test)]
3655mod tests {
3656    use super::*;
3657    use crate::{HarnessEvent, HarnessId, RuntimeEndpoint, RuntimeHandle, StorageLocator};
3658    use async_trait::async_trait;
3659    use std::io::Write;
3660    use std::path::PathBuf;
3661    use std::time::Instant;
3662
3663    #[test]
3664    fn indexed_claude_descriptor_keeps_the_live_peer_address() {
3665        let descriptor = SessionDescriptor {
3666            locator: SessionLocator {
3667                harness: HarnessId::new(HarnessId::CLAUDE_CODE),
3668                session_id: "live-session".into(),
3669                storage: StorageLocator::File {
3670                    path: PathBuf::from("/tmp/live-session.jsonl"),
3671                },
3672            },
3673            cwd: Some(PathBuf::from("/project")),
3674            title: None,
3675            preview_candidates: Vec::new(),
3676            latest_message_candidates: Vec::new(),
3677            updated_at_ms: Some(1),
3678            message_count: None,
3679            model: None,
3680            parent_session_id: None,
3681        };
3682        let peer = crate::claude_peer::ClaudePeerSession {
3683            pid: 42,
3684            session_id: "live-session".into(),
3685            cwd: Some(PathBuf::from("/project")),
3686            name: "peer".into(),
3687            socket_path: PathBuf::from("/tmp/peer.sock"),
3688            status: Some(crate::claude_peer::ClaudePeerStatus::Busy),
3689            updated_at_ms: Some(1),
3690            version: Some("test".into()),
3691        };
3692
3693        let value = live_descriptor_value(&descriptor, &[peer]).unwrap();
3694        assert!(value["live_endpoint"]
3695            .as_str()
3696            .is_some_and(|endpoint| endpoint.starts_with("cc-peer:v1:42:peer:")));
3697    }
3698
3699    struct EndingRuntime {
3700        handle: RuntimeHandle,
3701        event: Option<HarnessEvent>,
3702    }
3703
3704    #[async_trait]
3705    impl RuntimeConnection for EndingRuntime {
3706        fn handle(&self) -> &RuntimeHandle {
3707            &self.handle
3708        }
3709
3710        async fn send_input(&mut self, _input: RuntimeInput) -> crate::Result<Option<String>> {
3711            unreachable!("ending runtime does not accept input")
3712        }
3713
3714        async fn next_event(&mut self) -> crate::Result<Option<HarnessEvent>> {
3715            Ok(self.event.take())
3716        }
3717
3718        async fn interrupt(&mut self) -> crate::Result<()> {
3719            Ok(())
3720        }
3721
3722        async fn respond(&mut self, _request_id: Value, _response: Value) -> crate::Result<()> {
3723            Ok(())
3724        }
3725
3726        async fn close(&mut self) -> crate::Result<()> {
3727            Ok(())
3728        }
3729    }
3730
3731    fn ending_runtime(event: Option<HarnessEvent>) -> Box<dyn RuntimeConnection> {
3732        Box::new(EndingRuntime {
3733            handle: RuntimeHandle {
3734                harness: HarnessId::from(HarnessId::CLAUDE_CODE),
3735                runtime_id: "ending-session".into(),
3736                endpoint: RuntimeEndpoint::LocalProcess {
3737                    pid: None,
3738                    command: vec!["ending-runtime".into()],
3739                    protocol: "test".into(),
3740                },
3741            },
3742            event,
3743        })
3744    }
3745
3746    fn request(id: u64, method: &str, params: Value) -> Value {
3747        json!({"jsonrpc": "2.0", "id": id, "method": method, "params": params})
3748    }
3749
3750    fn pi_locator() -> SessionLocator {
3751        SessionLocator {
3752            harness: HarnessId::from(HarnessId::PI),
3753            session_id: "1e6f2a3b-0000-4000-8000-000000000001".into(),
3754            storage: StorageLocator::File {
3755                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
3756                    .join("tests/fixtures/pi_session.jsonl"),
3757            },
3758        }
3759    }
3760
3761    fn opencode_locator() -> SessionLocator {
3762        let session_id = "ses_fixtureAAAAAAAAAAAAAAA1";
3763        SessionLocator {
3764            harness: HarnessId::from(HarnessId::OPENCODE),
3765            session_id: session_id.into(),
3766            storage: StorageLocator::Sqlite {
3767                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
3768                    .join("tests/fixtures/opencode_fixture/opencode.db"),
3769                selector: session_id.into(),
3770            },
3771        }
3772    }
3773
3774    fn grok_locator() -> SessionLocator {
3775        SessionLocator {
3776            harness: HarnessId::from(HarnessId::GROK),
3777            session_id: "73c09283-4b33-41fa-90f1-0bcb0f7be523".into(),
3778            storage: StorageLocator::File {
3779                path: PathBuf::from(env!("CARGO_MANIFEST_DIR"))
3780                    .join("tests/fixtures/grok_session/chat_history.jsonl"),
3781            },
3782        }
3783    }
3784
3785    #[test]
3786    fn capabilities_are_explicit_and_versioned() {
3787        let mut service = HarnessSessionService::new();
3788        let response = service.handle(request(1, "harness.v1.capabilities", json!({})));
3789        assert_eq!(response["result"]["version"], HARNESS_SERVICE_VERSION);
3790        assert_eq!(
3791            response["result"]["sdk"]["schema_version"],
3792            crate::SDK_SCHEMA_VERSION
3793        );
3794        assert_eq!(
3795            response["result"]["sdk"]["operations"]
3796                .as_array()
3797                .unwrap()
3798                .len(),
3799            SdkOperation::ALL.len()
3800        );
3801        assert_eq!(response["result"]["harnesses"].as_array().unwrap().len(), 8);
3802        assert!(response["result"]["harnesses"]
3803            .as_array()
3804            .unwrap()
3805            .iter()
3806            .any(|harness| harness == HarnessId::GROK));
3807        assert!(response["result"]["harnesses"]
3808            .as_array()
3809            .unwrap()
3810            .iter()
3811            .any(|harness| harness == HarnessId::GOOSE));
3812    }
3813
3814    #[test]
3815    fn handshake_health_uses_protocol_liveness_not_stderr_severity() {
3816        let noisy_stderr = crate::HarnessEvent {
3817            sequence: None,
3818            kind: "transport_stderr".into(),
3819            payload: json!({"line": "ERROR optional worker AuthorizationRequired"}),
3820        };
3821        assert_eq!(handshake_event_failure(&noisy_stderr), None);
3822
3823        let closed = crate::HarnessEvent {
3824            sequence: None,
3825            kind: "transport_closed".into(),
3826            payload: json!({}),
3827        };
3828        assert!(handshake_event_failure(&closed).is_some());
3829    }
3830
3831    #[tokio::test]
3832    async fn runtime_eof_is_notified_and_removed_for_raw_and_explicit_close() {
3833        let mut service = HarnessSessionService::new();
3834        service
3835            .runtimes
3836            .insert("raw-eof".into(), ending_runtime(None));
3837        service.runtimes.insert(
3838            "explicit-close".into(),
3839            ending_runtime(Some(HarnessEvent {
3840                sequence: None,
3841                kind: "transport_closed".into(),
3842                payload: json!({"message": "native transport exited"}),
3843            })),
3844        );
3845
3846        let notifications = service.poll_runtimes().await;
3847
3848        assert_eq!(notifications.len(), 2);
3849        assert!(notifications
3850            .iter()
3851            .all(|notification| { notification["params"]["event"]["kind"] == "transport_closed" }));
3852        assert!(notifications.iter().all(|notification| {
3853            notification["params"]["session_id"] == "ending-session"
3854                && notification["params"]["connection"].is_string()
3855        }));
3856        let mut sequences = notifications
3857            .iter()
3858            .filter_map(|notification| notification["params"]["sequence"].as_u64())
3859            .collect::<Vec<_>>();
3860        sequences.sort_unstable();
3861        assert_eq!(sequences, vec![1, 2]);
3862        assert!(service.runtimes.is_empty());
3863    }
3864
3865    #[test]
3866    fn support_report_and_grok_default_binding_share_the_registry() {
3867        let mut service = HarnessSessionService::new();
3868        let response = service.handle(request(1, "harness.v1.support.report", json!({})));
3869        assert_eq!(response["result"]["schema"], crate::SUPPORT_REGISTRY_SCHEMA);
3870        let params = RuntimeBackendParams {
3871            harness: HarnessId::from(HarnessId::GROK),
3872            protocol: None,
3873            launch: None,
3874            base_url: None,
3875            policy: RuntimePolicy::Default,
3876        };
3877        let backend = match runtime_backend(&params) {
3878            Ok(backend) => backend,
3879            Err(_) => panic!("Grok should bind through its registered ACP launch"),
3880        };
3881        assert_eq!(backend.harness().as_str(), HarnessId::GROK);
3882        assert!(backend.capabilities().start_session);
3883        let registered = harness_support_registry()
3884            .harnesses
3885            .into_iter()
3886            .find(|harness| harness.id.as_str() == HarnessId::GROK)
3887            .and_then(|harness| harness.runtime.default_launch)
3888            .unwrap();
3889        assert!(!registered
3890            .arguments
3891            .iter()
3892            .any(|argument| argument == "--always-approve"));
3893        assert!(runtime_launch(&params).is_none());
3894
3895        let yolo = RuntimeBackendParams {
3896            policy: RuntimePolicy::Yolo,
3897            ..params
3898        };
3899        assert!(runtime_launch(&yolo)
3900            .unwrap()
3901            .arguments
3902            .iter()
3903            .any(|argument| argument == "--always-approve"));
3904
3905        let mismatched_protocol = RuntimeBackendParams {
3906            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
3907            protocol: Some("acp".into()),
3908            launch: None,
3909            base_url: None,
3910            policy: RuntimePolicy::Default,
3911        };
3912        assert!(runtime_backend(&mismatched_protocol).is_err());
3913    }
3914
3915    #[test]
3916    fn load_follow_and_unfollow_share_the_same_locator() {
3917        let mut service = HarnessSessionService::new();
3918        let locator = pi_locator();
3919        let loaded = service.handle(request(
3920            1,
3921            "harness.v1.sessions.load",
3922            json!({"locator": locator}),
3923        ));
3924        assert_eq!(
3925            loaded["result"]["session"]["session_id"],
3926            locator.session_id
3927        );
3928
3929        let followed = service.handle(request(
3930            2,
3931            "harness.v1.sessions.follow",
3932            json!({"locator": locator}),
3933        ));
3934        assert_eq!(followed["result"]["subscription"], "sub-1");
3935        assert_eq!(followed["result"]["initial"]["type"], "session_snapshot");
3936        assert!(service.poll().is_empty());
3937
3938        let unfollowed = service.handle(request(
3939            3,
3940            "harness.v1.sessions.unfollow",
3941            json!({"subscription": "sub-1"}),
3942        ));
3943        assert_eq!(unfollowed["result"]["removed"], true);
3944    }
3945
3946    #[test]
3947    fn bounded_read_view_excludes_subagents_and_keeps_only_the_tail() {
3948        let temp = std::env::temp_dir().join(format!(
3949            "supercode-bounded-view-{}-{}",
3950            std::process::id(),
3951            generated_session_id()
3952        ));
3953        let path = temp.join("parent.jsonl");
3954        let subagents = temp.join("parent/subagents");
3955        std::fs::create_dir_all(&subagents).unwrap();
3956        let long_last = "x".repeat(300);
3957        let parent_records = [
3958            json!({"type":"user","uuid":"u1","parentUuid":null,"message":{"role":"user","content":"first"}}),
3959            json!({"type":"assistant","uuid":"a1","parentUuid":"u1","message":{"role":"assistant","content":[{"type":"text","text":"middle"}]}}),
3960            json!({"type":"user","uuid":"u2","parentUuid":"a1","message":{"role":"user","content":long_last}}),
3961        ];
3962        std::fs::write(
3963            &path,
3964            format!(
3965                "{}\n",
3966                parent_records
3967                    .iter()
3968                    .map(Value::to_string)
3969                    .collect::<Vec<_>>()
3970                    .join("\n")
3971            ),
3972        )
3973        .unwrap();
3974        std::fs::write(
3975            subagents.join("agent-child.jsonl"),
3976            concat!(
3977                r#"{"type":"user","uuid":"cu","parentUuid":null,"agentId":"child","message":{"role":"user","content":"child work"}}"#,
3978                "\n",
3979            ),
3980        )
3981        .unwrap();
3982        let locator = SessionLocator {
3983            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
3984            session_id: "parent".into(),
3985            storage: StorageLocator::File { path },
3986        };
3987        let mut service = HarnessSessionService::new();
3988
3989        let complete = service.handle(request(
3990            1,
3991            "harness.v1.sessions.load",
3992            json!({"locator": locator}),
3993        ));
3994        assert_eq!(
3995            complete["result"]["session"]["subagents"]
3996                .as_array()
3997                .unwrap()
3998                .len(),
3999            1
4000        );
4001
4002        let bounded = service.handle(request(
4003            2,
4004            "harness.v1.sessions.load",
4005            json!({
4006                "locator": locator,
4007                "view": {
4008                    "tail_messages": 1,
4009                    "max_message_chars": 256,
4010                    "include_subagents": false
4011                },
4012            }),
4013        ));
4014        let session = &bounded["result"]["session"];
4015        assert!(session["subagents"].as_array().unwrap().is_empty());
4016        assert_eq!(session["messages"].as_array().unwrap().len(), 1);
4017        assert_eq!(
4018            session["messages"][0]["content"],
4019            format!("{}\n…", "x".repeat(256))
4020        );
4021
4022        let followed = service.handle(request(
4023            3,
4024            "harness.v1.sessions.follow",
4025            json!({
4026                "locator": locator,
4027                "view": {
4028                    "tail_messages": 1,
4029                    "max_message_chars": 256,
4030                    "include_subagents": false
4031                },
4032            }),
4033        ));
4034        let initial = &followed["result"]["initial"]["session"];
4035        assert!(initial["subagents"].as_array().unwrap().is_empty());
4036        assert_eq!(initial["messages"].as_array().unwrap().len(), 1);
4037
4038        let _ = std::fs::remove_dir_all(&temp);
4039    }
4040
4041    #[test]
4042    fn forty_megabyte_display_load_is_bounded_and_prompt() {
4043        let temp = std::env::temp_dir().join(format!(
4044            "supercode-large-display-view-{}-{}",
4045            std::process::id(),
4046            generated_session_id()
4047        ));
4048        std::fs::create_dir_all(&temp).unwrap();
4049        let path = temp.join("rollout.jsonl");
4050        let mut file = std::io::BufWriter::new(std::fs::File::create(&path).unwrap());
4051        writeln!(
4052            file,
4053            r#"{{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{{"id":"large-display","cwd":"/tmp"}}}}"#
4054        )
4055        .unwrap();
4056        let padding = "x".repeat(80 * 1024);
4057        for index in 0..512 {
4058            let marker = if index == 0 {
4059                "OLDEST-SHOULD-NOT-LOAD"
4060            } else if index == 511 {
4061                "LATEST-MUST-LOAD"
4062            } else {
4063                "bulk"
4064            };
4065            writeln!(
4066                file,
4067                "{}",
4068                json!({
4069                    "timestamp": "2026-01-01T00:00:01Z",
4070                    "type": "response_item",
4071                    "payload": {
4072                        "type": "message",
4073                        "role": "assistant",
4074                        "content": [{"type": "output_text", "text": format!("{marker}:{padding}")}],
4075                    },
4076                })
4077            )
4078            .unwrap();
4079        }
4080        file.flush().unwrap();
4081        drop(file);
4082        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
4083
4084        let locator = SessionLocator {
4085            harness: HarnessId::from(HarnessId::CODEX),
4086            session_id: "large-display".into(),
4087            storage: StorageLocator::File { path },
4088        };
4089        let started = Instant::now();
4090        let response = HarnessSessionService::new().handle(request(
4091            1,
4092            "harness.v1.sessions.load",
4093            json!({
4094                "locator": locator,
4095                "view": {
4096                    "tail_messages": 500,
4097                    "max_message_chars": 1024,
4098                    "include_subagents": false,
4099                    "display_history": true,
4100                },
4101            }),
4102        ));
4103        let elapsed = started.elapsed();
4104        let wire = response.to_string();
4105        eprintln!(
4106            "bounded 40 MiB display load: {elapsed:?}, {} response bytes",
4107            wire.len()
4108        );
4109        assert!(response.get("error").is_none(), "{response:#}");
4110        assert!(wire.contains("LATEST-MUST-LOAD"));
4111        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
4112        assert!(
4113            wire.len() < 2 * 1024 * 1024,
4114            "bounded wire was {} bytes",
4115            wire.len()
4116        );
4117        assert!(
4118            elapsed.as_secs_f64() < 3.0,
4119            "bounded 40 MiB load took {elapsed:?}"
4120        );
4121
4122        let _ = std::fs::remove_dir_all(&temp);
4123    }
4124
4125    #[test]
4126    fn forty_megabyte_goose_store_display_load_reads_only_the_tail() {
4127        let temp = std::env::temp_dir().join(format!(
4128            "supercode-large-goose-view-{}-{}",
4129            std::process::id(),
4130            generated_session_id()
4131        ));
4132        std::fs::create_dir_all(&temp).unwrap();
4133        let path = temp.join("sessions.db");
4134        let connection = rusqlite::Connection::open(&path).unwrap();
4135        connection
4136            .execute_batch(
4137                "CREATE TABLE sessions (
4138                    id TEXT PRIMARY KEY, name TEXT NOT NULL, working_dir TEXT NOT NULL,
4139                    created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
4140                    session_type TEXT NOT NULL, extension_data TEXT,
4141                    goose_mode TEXT NOT NULL, provider_name TEXT, model_config_json TEXT,
4142                    archived_at TEXT
4143                 );
4144                 CREATE TABLE messages (
4145                    id INTEGER PRIMARY KEY, session_id TEXT NOT NULL, message_id TEXT,
4146                    role TEXT NOT NULL, content_json TEXT NOT NULL,
4147                    created_timestamp INTEGER NOT NULL, metadata_json TEXT
4148                 );",
4149            )
4150            .unwrap();
4151        connection
4152            .execute(
4153                "INSERT INTO sessions VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, NULL)",
4154                rusqlite::params![
4155                    "goose-large",
4156                    "Large Goose session",
4157                    "/tmp",
4158                    "2026-01-01 00:00:00",
4159                    "2026-01-01 00:00:02",
4160                    "user",
4161                    "{}",
4162                    "auto",
4163                    "anthropic",
4164                    r#"{"model_name":"claude-sonnet"}"#,
4165                ],
4166            )
4167            .unwrap();
4168        let old_content = serde_json::to_string(&vec![json!({
4169            "type": "text",
4170            "text": format!("OLDEST-SHOULD-NOT-LOAD:{}", "x".repeat(40 * 1024 * 1024)),
4171        })])
4172        .unwrap();
4173        connection
4174            .execute(
4175                "INSERT INTO messages VALUES (1, ?1, 'old', 'user', ?2, 1, '{}')",
4176                rusqlite::params!["goose-large", old_content],
4177            )
4178            .unwrap();
4179        connection
4180            .execute(
4181                "INSERT INTO messages VALUES (2, ?1, 'new', 'assistant', ?2, 2, '{}')",
4182                rusqlite::params![
4183                    "goose-large",
4184                    r#"[{"type":"text","text":"LATEST-MUST-LOAD"}]"#
4185                ],
4186            )
4187            .unwrap();
4188        drop(connection);
4189        assert!(std::fs::metadata(&path).unwrap().len() >= 40 * 1024 * 1024);
4190
4191        let locator = SessionLocator {
4192            harness: HarnessId::from(HarnessId::GOOSE),
4193            session_id: "goose-large".into(),
4194            storage: StorageLocator::Sqlite {
4195                path,
4196                selector: "goose-large".into(),
4197            },
4198        };
4199        let started = Instant::now();
4200        let response = HarnessSessionService::new().handle(request(
4201            1,
4202            "harness.v1.sessions.load",
4203            json!({
4204                "locator": locator,
4205                "view": {
4206                    "tail_messages": 1,
4207                    "max_message_chars": 1024,
4208                    "include_subagents": false,
4209                    "display_history": true,
4210                },
4211            }),
4212        ));
4213        let elapsed = started.elapsed();
4214        let wire = response.to_string();
4215        eprintln!(
4216            "bounded 40 MiB Goose display load: {elapsed:?}, {} response bytes",
4217            wire.len()
4218        );
4219        assert!(response.get("error").is_none(), "{response:#}");
4220        assert!(wire.contains("LATEST-MUST-LOAD"));
4221        assert!(!wire.contains("OLDEST-SHOULD-NOT-LOAD"));
4222        assert!(
4223            wire.len() < 64 * 1024,
4224            "bounded wire was {} bytes",
4225            wire.len()
4226        );
4227        assert!(
4228            elapsed.as_secs_f64() < 1.0,
4229            "bounded Goose load took {elapsed:?}"
4230        );
4231
4232        let _ = std::fs::remove_dir_all(&temp);
4233    }
4234
4235    #[test]
4236    fn display_view_keeps_codex_assistant_history_across_compaction() {
4237        let temp = std::env::temp_dir().join(format!(
4238            "supercode-codex-display-view-{}-{}",
4239            std::process::id(),
4240            generated_session_id()
4241        ));
4242        std::fs::create_dir_all(&temp).unwrap();
4243        let path = temp.join("rollout.jsonl");
4244        std::fs::write(
4245            &path,
4246            concat!(
4247                r#"{"timestamp":"2026-01-01T00:00:00Z","type":"session_meta","payload":{"id":"codex-display","cwd":"/tmp"}}"#,
4248                "\n",
4249                r#"{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"old prompt"}]}}"#,
4250                "\n",
4251                r#"{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"old answer"}]}}"#,
4252                "\n",
4253                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"}]}}"#,
4254                "\n",
4255                r#"{"timestamp":"2026-01-01T00:00:04Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"new prompt"}]}}"#,
4256                "\n",
4257                r#"{"timestamp":"2026-01-01T00:00:05Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"new answer"}]}}"#,
4258                "\n",
4259            ),
4260        )
4261        .unwrap();
4262        let locator = SessionLocator {
4263            harness: HarnessId::from(HarnessId::CODEX),
4264            session_id: "codex-display".into(),
4265            storage: StorageLocator::File { path },
4266        };
4267        let mut service = HarnessSessionService::new();
4268
4269        let continuation = service.handle(request(
4270            1,
4271            "harness.v1.sessions.load",
4272            json!({"locator": locator}),
4273        ));
4274        let continuation_text = continuation["result"]["session"]["messages"].to_string();
4275        assert!(!continuation_text.contains("old answer"));
4276
4277        let display = service.handle(request(
4278            2,
4279            "harness.v1.sessions.load",
4280            json!({
4281                "locator": locator,
4282                "view": {
4283                    "tail_messages": 10,
4284                    "include_subagents": false,
4285                    "display_history": true,
4286                },
4287            }),
4288        ));
4289        let display_text = display["result"]["session"]["messages"].to_string();
4290        assert!(display_text.contains("old prompt"));
4291        assert!(display_text.contains("old answer"));
4292        assert!(display_text.contains("new prompt"));
4293        assert!(display_text.contains("new answer"));
4294
4295        let _ = std::fs::remove_dir_all(&temp);
4296    }
4297
4298    #[test]
4299    fn load_supports_bounded_windows_and_media_metadata() {
4300        let mut service = HarnessSessionService::new();
4301        let locator = pi_locator();
4302        let bounded = service.handle(request(
4303            1,
4304            "harness.v1.sessions.load",
4305            json!({
4306                "locator": locator,
4307                "options": {
4308                    "include_subagents": false,
4309                    "message_limit": 2,
4310                    "message_offset": 1
4311                }
4312            }),
4313        ));
4314        assert_eq!(bounded["result"]["window"]["offset"], 1);
4315        assert_eq!(bounded["result"]["window"]["returned"], 2);
4316        assert!(bounded["result"]["summary"]["first_message"].is_object());
4317        assert!(bounded["result"]["summary"]["last_message"].is_object());
4318        assert_eq!(
4319            bounded["result"]["session"]["messages"]
4320                .as_array()
4321                .unwrap()
4322                .len(),
4323            2
4324        );
4325        assert!(bounded["result"]["session"]["subagents"]
4326            .as_array()
4327            .unwrap()
4328            .is_empty());
4329
4330        let tail = service.handle(request(
4331            2,
4332            "harness.v1.sessions.load",
4333            json!({"locator": locator, "options": {"message_tail": 1}}),
4334        ));
4335        assert_eq!(tail["result"]["window"]["returned"], 1);
4336        assert_eq!(tail["result"]["window"]["has_more"], true);
4337        assert_eq!(tail["result"]["window"]["has_older"], true);
4338        assert!(tail["result"]["window"]["older_items"].as_u64().unwrap() > 0);
4339        assert!(tail["result"]["summary"]["first_message"].is_object());
4340
4341        let metadata_only = service.handle(request(
4342            3,
4343            "harness.v1.sessions.load",
4344            json!({"locator": locator, "options": {"inline_media": "metadata"}}),
4345        ));
4346        assert!(metadata_only["result"]["session"]
4347            .to_string()
4348            .contains("media_reference"));
4349        assert!(!metadata_only["result"]["session"]
4350            .to_string()
4351            .contains("data:image/"));
4352    }
4353
4354    #[test]
4355    fn import_translate_branch_and_handoff_use_typed_artifacts() {
4356        let mut service = HarnessSessionService::new();
4357        let locator = pi_locator();
4358        let translated = service.handle(request(
4359            1,
4360            "harness.v1.sessions.translate",
4361            json!({"locator": locator, "target_harness": "grok"}),
4362        ));
4363        assert_eq!(translated["result"]["artifact"]["source_harness"], "pi");
4364        assert_eq!(translated["result"]["artifact"]["target_harness"], "grok");
4365        assert!(translated["result"]["artifact"]["content"]
4366            .as_str()
4367            .is_some_and(|content| !content.is_empty()));
4368
4369        for target in ["opencode", "open-code"] {
4370            let opencode = service.handle(request(
4371                6,
4372                "harness.v1.sessions.translate",
4373                json!({"locator": locator, "target_harness": target}),
4374            ));
4375            assert_eq!(opencode["result"]["artifact"]["target_harness"], "opencode");
4376        }
4377        let goose = service.handle(request(
4378            7,
4379            "harness.v1.sessions.translate",
4380            json!({"locator": locator, "target_harness": "goose"}),
4381        ));
4382        assert_eq!(goose["result"]["artifact"]["target_harness"], "goose");
4383        assert!(serde_json::from_str::<Value>(
4384            goose["result"]["artifact"]["content"].as_str().unwrap()
4385        )
4386        .unwrap()["conversation"]
4387            .is_array());
4388
4389        let imported = service.handle(request(
4390            2,
4391            "harness.v1.sessions.import",
4392            json!({
4393                "source_harness": "grok",
4394                "content": translated["result"]["artifact"]["content"],
4395            }),
4396        ));
4397        assert_eq!(imported["result"]["session"]["source"], "grok");
4398
4399        let branched = service.handle(request(
4400            3,
4401            "harness.v1.sessions.branch",
4402            json!({"locator": locator, "target_harness": "codex"}),
4403        ));
4404        assert_eq!(branched["result"]["parent"]["harness"], "pi");
4405        assert!(branched["result"]["bootstrap_prompt"]
4406            .as_str()
4407            .unwrap()
4408            .contains("frozen parent transcript"));
4409        assert_eq!(branched["result"]["artifact"]["target_harness"], "codex");
4410
4411        let handoff = service.handle(request(
4412            4,
4413            "harness.v1.sessions.handoff",
4414            json!({"locator": locator, "target_harness": "pi", "cwd": "/tmp/project"}),
4415        ));
4416        assert_eq!(handoff["result"]["launch"]["program"], "pi");
4417        assert_eq!(handoff["result"]["launch"]["cwd"], "/tmp/project");
4418        assert_eq!(handoff["result"]["requires_materialization"], true);
4419
4420        let goose_handoff = service.handle(request(
4421            8,
4422            "harness.v1.sessions.handoff",
4423            json!({"locator": locator, "target_harness": "goose", "cwd": "/tmp/project"}),
4424        ));
4425        assert_eq!(goose_handoff["result"]["launch"]["program"], "goose");
4426        assert_eq!(
4427            goose_handoff["result"]["materialize"]["arguments"],
4428            json!(["session", "import", "{artifact_path}"])
4429        );
4430
4431        let resumed = service.handle(request(
4432            5,
4433            "harness.v1.sessions.resume_instructions",
4434            json!({"locator": locator, "cwd": "/tmp/project", "policy": "yolo"}),
4435        ));
4436        assert_eq!(resumed["result"]["launch"]["program"], "pi");
4437        assert_eq!(resumed["result"]["launch"]["arguments"][0], "--approve");
4438    }
4439
4440    #[test]
4441    fn reduce_persists_and_reloads_a_byte_exact_reversible_bundle() {
4442        let temp = std::env::temp_dir().join(format!(
4443            "supercode-service-reduce-{}-{}",
4444            std::process::id(),
4445            generated_session_id()
4446        ));
4447        let source_path = temp.join("source.jsonl");
4448        let store_root = temp.join("store");
4449        std::fs::create_dir_all(&temp).unwrap();
4450
4451        let mut records = vec![json!({
4452            "timestamp": "2026-01-01T00:00:00Z",
4453            "type": "session_meta",
4454            "payload": {"id": "codex-reduce", "cwd": "/tmp/project"},
4455        })];
4456        for turn in 0..16 {
4457            records.push(json!({
4458                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 1),
4459                "type": "response_item",
4460                "payload": {
4461                    "type": "message",
4462                    "role": "user",
4463                    "content": [{
4464                        "type": "input_text",
4465                        "text": format!("request {turn}: {}", "context ".repeat(80)),
4466                    }],
4467                },
4468            }));
4469            records.push(json!({
4470                "timestamp": format!("2026-01-01T00:00:{:02}Z", turn * 2 + 2),
4471                "type": "response_item",
4472                "payload": {
4473                    "type": "message",
4474                    "role": "assistant",
4475                    "content": [{
4476                        "type": "output_text",
4477                        "text": format!("answer {turn}: {}", "implementation detail ".repeat(80)),
4478                    }],
4479                },
4480            }));
4481        }
4482        let source = format!(
4483            "{}\n",
4484            records
4485                .iter()
4486                .map(Value::to_string)
4487                .collect::<Vec<_>>()
4488                .join("\n")
4489        );
4490        std::fs::write(&source_path, &source).unwrap();
4491        let locator = SessionLocator {
4492            harness: HarnessId::from(HarnessId::CODEX),
4493            session_id: "codex-reduce".into(),
4494            storage: StorageLocator::File {
4495                path: source_path.clone(),
4496            },
4497        };
4498        let original = load_session(&locator).unwrap();
4499        let mut service =
4500            HarnessSessionService::new().with_reduction_store_root(store_root.clone());
4501
4502        let response = service.handle(request(
4503            1,
4504            "harness.v1.sessions.reduce",
4505            json!({
4506                "locator": locator,
4507                "target_harness": "claude-code",
4508                "keep_last": 4,
4509            }),
4510        ));
4511        assert!(response.get("error").is_none(), "{response:#}");
4512        let receipt = &response["result"]["receipt"];
4513        assert_eq!(receipt["source_harness"], "codex");
4514        assert_eq!(receipt["target_harness"], "claude-code");
4515        assert_eq!(receipt["verified"], true);
4516        assert_eq!(receipt["reversible"], true);
4517        assert!(receipt["reductions"].as_u64().unwrap() > 0);
4518        assert!(
4519            receipt["source_tokens"].as_u64().unwrap()
4520                > receipt["reduced_tokens"].as_u64().unwrap()
4521        );
4522        assert!(receipt["ratio"].as_f64().unwrap() > 1.0);
4523        assert!(response["result"]["bootstrap_prompt"]
4524            .as_str()
4525            .unwrap()
4526            .contains("Do not guess hidden content"));
4527
4528        let rescue_id = receipt["id"].as_str().unwrap();
4529        let store = crate::SessionStore::open(&store_root).unwrap();
4530        let sidecar =
4531            Session::from_sidecar_str(&store.load_sidecar(rescue_id).unwrap().unwrap()).unwrap();
4532        let log = store.load_reduction_log(rescue_id).unwrap().unwrap();
4533        let persisted_view = parse_messages_jsonl(&store.load(rescue_id).unwrap()).unwrap();
4534        let policy = reduce::ReductionPolicy {
4535            clear_turns_older_than: Some(4),
4536            ..Default::default()
4537        };
4538        let (restamped_view, reapplied_log) =
4539            reduce::project_messages(&sidecar.messages, &policy, &log);
4540        assert_eq!(
4541            messages_jsonl(&persisted_view).unwrap(),
4542            messages_jsonl(&restamped_view).unwrap()
4543        );
4544        assert_eq!(reapplied_log, log);
4545        reduce::verify_log(&log, &sidecar).unwrap();
4546        assert_eq!(
4547            reduce::invert(&restamped_view, &log, &sidecar).unwrap(),
4548            original.messages
4549        );
4550        assert_eq!(std::fs::read_to_string(&source_path).unwrap(), source);
4551
4552        std::fs::remove_dir_all(temp).ok();
4553    }
4554
4555    #[test]
4556    fn read_surfaces_view_a_severed_claude_graph_while_transfer_still_refuses_it() {
4557        let temp = std::env::temp_dir().join(format!(
4558            "supercode-severed-view-{}-{}",
4559            std::process::id(),
4560            generated_session_id()
4561        ));
4562        std::fs::create_dir_all(&temp).unwrap();
4563        let path = temp.join("severed.jsonl");
4564        // A live record whose parent was pruned — what a compacted or
4565        // resumed-across-files Claude Code session looks like on disk.
4566        std::fs::write(
4567            &path,
4568            concat!(
4569                r#"{"type":"user","uuid":"orphan-u","parentUuid":null,"message":{"role":"user","content":"stranded prompt"}}"#,
4570                "\n",
4571                r#"{"type":"assistant","uuid":"live-a","parentUuid":"pruned","message":{"id":"m","role":"assistant","content":[{"type":"text","text":"live answer"}]}}"#,
4572                "\n",
4573            ),
4574        )
4575        .unwrap();
4576        let locator = SessionLocator {
4577            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
4578            session_id: "severed".into(),
4579            storage: StorageLocator::File { path },
4580        };
4581        let mut service = HarnessSessionService::new();
4582
4583        let viewed = service.handle(request(
4584            1,
4585            "harness.v1.sessions.load",
4586            json!({"locator": locator}),
4587        ));
4588        let session = &viewed["result"]["session"];
4589        assert_eq!(session["fidelity"], "semantic");
4590        assert_eq!(session["messages"].as_array().unwrap().len(), 2);
4591        assert!(session["residue"].as_array().unwrap().iter().any(|entry| {
4592            entry
4593                .as_str()
4594                .is_some_and(|entry| entry.contains("live-a") && entry.contains("pruned"))
4595        }));
4596
4597        // Asking a READ surface for a lossless reconstruction gets the strict
4598        // refusal back, unchanged.
4599        let strict = service.handle(request(
4600            2,
4601            "harness.v1.sessions.load",
4602            json!({"locator": locator, "fidelity": "byte_lossless"}),
4603        ));
4604        assert!(strict["error"]["message"]
4605            .as_str()
4606            .unwrap()
4607            .contains("cannot reconstruct lossless Claude continuation"));
4608
4609        // Transfer/continuation surfaces have no view mode at all.
4610        let translated = service.handle(request(
4611            3,
4612            "harness.v1.sessions.translate",
4613            json!({"locator": locator, "target_harness": "codex"}),
4614        ));
4615        assert!(translated["error"]["message"]
4616            .as_str()
4617            .unwrap()
4618            .contains("cannot reconstruct lossless Claude continuation"));
4619        let resumed = service.handle(request(
4620            4,
4621            "harness.v1.sessions.resume_instructions",
4622            json!({"locator": locator}),
4623        ));
4624        assert!(resumed["error"]["message"]
4625            .as_str()
4626            .unwrap()
4627            .contains("cannot reconstruct lossless Claude continuation"));
4628
4629        let _ = std::fs::remove_dir_all(&temp);
4630    }
4631
4632    #[test]
4633    fn structured_resume_launches_cover_gemini_goose_and_supercode() {
4634        let codex = resume_launch(
4635            HarnessId::CODEX,
4636            "codex-session",
4637            Path::new("/tmp/project"),
4638            ResumePolicy::Yolo,
4639        )
4640        .unwrap_or_else(|_| panic!("Codex resume launch must be registered"));
4641        assert_eq!(codex.program, "codex");
4642        assert_eq!(
4643            codex.arguments,
4644            [
4645                "-c",
4646                "check_for_update_on_startup=false",
4647                "-c",
4648                "projects.\"/tmp/project\".trust_level=\"trusted\"",
4649                "--dangerously-bypass-approvals-and-sandbox",
4650                "--dangerously-bypass-hook-trust",
4651                "resume",
4652                "codex-session",
4653            ]
4654        );
4655
4656        let gemini = resume_launch(
4657            HarnessId::GEMINI,
4658            "gemini-session",
4659            Path::new("/tmp/project"),
4660            ResumePolicy::Yolo,
4661        )
4662        .unwrap_or_else(|_| panic!("Gemini resume launch must be registered"));
4663        assert_eq!(gemini.program, "gemini");
4664        assert_eq!(gemini.arguments, ["--yolo", "--resume", "gemini-session"]);
4665
4666        let goose = resume_launch(
4667            HarnessId::GOOSE,
4668            "goose-session",
4669            Path::new("/tmp/project"),
4670            ResumePolicy::Yolo,
4671        )
4672        .unwrap_or_else(|_| panic!("Goose resume launch must be registered"));
4673        assert_eq!(goose.program, "goose");
4674        assert_eq!(
4675            goose.arguments,
4676            ["session", "--resume", "--session-id", "goose-session"]
4677        );
4678
4679        let supercode = resume_launch(
4680            HarnessId::SUPERCODE,
4681            "supercode-session",
4682            Path::new("/tmp/project"),
4683            ResumePolicy::Yolo,
4684        )
4685        .unwrap_or_else(|_| panic!("Supercode resume launch must be registered"));
4686        assert_eq!(supercode.program, "supercode");
4687        assert_eq!(
4688            supercode.arguments,
4689            ["--dangerous", "resume", "supercode-session"]
4690        );
4691    }
4692
4693    #[test]
4694    fn diagonal_artifacts_preserve_claude_subagents_and_grok_bundle_members() {
4695        let temp = std::env::temp_dir().join(format!(
4696            "supercode-harness-artifact-{}-{}",
4697            std::process::id(),
4698            generated_session_id()
4699        ));
4700        let main_path = temp.join("parent.jsonl");
4701        let subagent_path = temp.join("parent/subagents/agent-child.jsonl");
4702        std::fs::create_dir_all(subagent_path.parent().unwrap()).unwrap();
4703        let fixture = std::fs::read_to_string(
4704            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
4705                .join("tests/fixtures/claude_code_session.jsonl"),
4706        )
4707        .unwrap();
4708        let parent = fixture.trim_end_matches('\n');
4709        let child = fixture.trim_end_matches('\n');
4710        std::fs::write(&main_path, parent).unwrap();
4711        std::fs::write(&subagent_path, child).unwrap();
4712        let locator = SessionLocator {
4713            harness: HarnessId::from(HarnessId::CLAUDE_CODE),
4714            session_id: "213bb148-51ea-453f-9206-f8b4b1168547".into(),
4715            storage: StorageLocator::File {
4716                path: main_path.clone(),
4717            },
4718        };
4719        let mut service = HarnessSessionService::new();
4720        let claude = service.handle(request(
4721            1,
4722            "harness.v1.sessions.translate",
4723            json!({"locator": locator, "target_harness": "claude-code"}),
4724        ));
4725        let artifact = &claude["result"]["artifact"];
4726        assert_eq!(artifact["fidelity"], "byte_lossless");
4727        assert_eq!(artifact["content"], parent);
4728        let files = artifact["files"].as_array().unwrap();
4729        assert!(files.iter().any(|file| {
4730            file["role"] == "subagent"
4731                && file["path"]
4732                    .as_str()
4733                    .is_some_and(|path| path.ends_with("/subagents/agent-child.jsonl"))
4734                && file["content"] == child
4735        }));
4736        assert!(!artifact["content"].as_str().unwrap().ends_with('\n'));
4737
4738        let grok = service.handle(request(
4739            2,
4740            "harness.v1.sessions.translate",
4741            json!({"locator": grok_locator(), "target_harness": "grok"}),
4742        ));
4743        let files = grok["result"]["artifact"]["files"].as_array().unwrap();
4744        for name in ["summary.json", "updates.jsonl"] {
4745            let expected = std::fs::read_to_string(
4746                PathBuf::from(env!("CARGO_MANIFEST_DIR"))
4747                    .join("tests/fixtures/grok_session")
4748                    .join(name),
4749            )
4750            .unwrap();
4751            assert!(files.iter().any(|file| {
4752                file["path"] == name && file["role"] == "bundle" && file["content"] == expected
4753            }));
4754        }
4755        std::fs::remove_dir_all(temp).ok();
4756    }
4757
4758    #[test]
4759    fn every_non_grok_handoff_mints_and_uses_a_fresh_target_identity() {
4760        let mut service = HarnessSessionService::new();
4761        let source = pi_locator();
4762        for (target, format) in [
4763            ("claude-code", SessionFormat::ClaudeCode),
4764            ("codex", SessionFormat::Codex),
4765            ("opencode", SessionFormat::OpenCode),
4766            ("pi", SessionFormat::Pi),
4767        ] {
4768            let result = service.handle(request(
4769                1,
4770                "harness.v1.sessions.handoff",
4771                json!({"locator": source, "target_harness": target, "cwd": "/tmp/project"}),
4772            ));
4773            let artifact = &result["result"]["artifact"];
4774            let target_id = artifact["session_id"].as_str().unwrap();
4775            assert_ne!(target_id, source.session_id, "{target}");
4776            let parsed = Session::load_str(artifact["content"].as_str().unwrap(), format).unwrap();
4777            assert_eq!(
4778                parsed.meta.session_id.as_deref(),
4779                Some(target_id),
4780                "{target}"
4781            );
4782            if target != "pi" {
4783                assert!(result["result"]["launch"]["arguments"]
4784                    .as_array()
4785                    .unwrap()
4786                    .iter()
4787                    .any(|argument| argument == target_id));
4788            }
4789            if target == "opencode" {
4790                assert!(target_id.starts_with("ses_"));
4791                fn assert_session_ids(value: &Value, target_id: &str) {
4792                    match value {
4793                        Value::Object(fields) => {
4794                            if let Some(session_id) = fields.get("sessionID") {
4795                                assert_eq!(session_id, target_id);
4796                            }
4797                            for child in fields.values() {
4798                                assert_session_ids(child, target_id);
4799                            }
4800                        }
4801                        Value::Array(values) => {
4802                            for child in values {
4803                                assert_session_ids(child, target_id);
4804                            }
4805                        }
4806                        _ => {}
4807                    }
4808                }
4809                let document: Value =
4810                    serde_json::from_str(artifact["content"].as_str().unwrap()).unwrap();
4811                assert_session_ids(&document, target_id);
4812            }
4813        }
4814
4815        let first = service.handle(request(
4816            2,
4817            "harness.v1.sessions.handoff",
4818            json!({"locator": source, "target_harness": "codex"}),
4819        ));
4820        let second = service.handle(request(
4821            3,
4822            "harness.v1.sessions.handoff",
4823            json!({"locator": source, "target_harness": "codex"}),
4824        ));
4825        assert_ne!(
4826            first["result"]["artifact"]["session_id"],
4827            second["result"]["artifact"]["session_id"]
4828        );
4829    }
4830
4831    #[test]
4832    fn grok_handoff_uses_the_official_importer_contract() {
4833        let mut service = HarnessSessionService::new();
4834        let source = opencode_locator();
4835        let response = service.handle(request(
4836            1,
4837            "harness.v1.sessions.handoff",
4838            json!({
4839                "locator": source,
4840                "target_harness": "grok",
4841                "cwd": "/tmp/grok-handoff-project",
4842            }),
4843        ));
4844        let result = &response["result"];
4845
4846        // The target is Grok, but the artifact truthfully names the Claude Code wire
4847        // format accepted by Grok's official importer. Raw Grok chat_history JSONL is
4848        // not a complete stock-resumable bundle.
4849        assert_eq!(result["artifact"]["target_harness"], "claude-code");
4850        assert!(result["artifact"]["suggested_filename"]
4851            .as_str()
4852            .unwrap()
4853            .ends_with(".grok-import.claude-code.jsonl"));
4854        let artifact = Session::load_str(
4855            result["artifact"]["content"].as_str().unwrap(),
4856            SessionFormat::ClaudeCode,
4857        )
4858        .unwrap();
4859        assert_eq!(
4860            artifact.meta.cwd.as_deref(),
4861            Some(Path::new("/tmp/grok-handoff-project"))
4862        );
4863        let target_session_id = artifact.meta.session_id.as_deref().unwrap();
4864        assert_eq!(target_session_id.len(), 36);
4865        assert_eq!(target_session_id.as_bytes()[14], b'4');
4866        assert_ne!(target_session_id, opencode_locator().session_id);
4867        assert_eq!(
4868            result["artifact"]["session_id"],
4869            artifact.meta.session_id.as_deref().unwrap()
4870        );
4871
4872        assert_eq!(
4873            result["materialize"]["arguments"],
4874            json!(["import", "--json", "{artifact_path}"])
4875        );
4876        assert_eq!(
4877            result["launch"]["arguments"],
4878            json!(["--resume", "{imported_session_id}", "--fork-session"])
4879        );
4880        assert!(result["note"]
4881            .as_str()
4882            .unwrap()
4883            .contains("outcome=imported"));
4884        assert!(!result["launch"]["arguments"]
4885            .as_array()
4886            .unwrap()
4887            .iter()
4888            .any(|argument| argument == &opencode_locator().session_id));
4889    }
4890
4891    #[tokio::test]
4892    async fn inventory_rejects_unknown_harnesses_and_runtime_attach_is_honest() {
4893        let mut service = HarnessSessionService::new();
4894        let inventory = service
4895            .handle_async(request(
4896                1,
4897                "harness.v1.harnesses.list",
4898                json!({"harnesses": ["missing"]}),
4899            ))
4900            .await;
4901        assert_eq!(inventory["error"]["code"], -32602);
4902
4903        let attached = service
4904            .handle_async(request(
4905                2,
4906                "harness.v1.runtimes.attach_existing",
4907                json!({"harness": "codex", "runtime_id": "thread-1"}),
4908            ))
4909            .await;
4910        assert_eq!(attached["error"]["code"], -32000);
4911        assert!(attached["error"]["message"]
4912            .as_str()
4913            .unwrap()
4914            .contains("runtimes.resume"));
4915    }
4916
4917    #[test]
4918    fn invalid_params_and_unknown_methods_use_json_rpc_errors() {
4919        let mut service = HarnessSessionService::new();
4920        let invalid = service.handle(request(1, "harness.v1.sessions.load", json!({})));
4921        assert_eq!(invalid["error"]["code"], -32602);
4922        let unknown = service.handle(request(2, "harness.v1.unknown", json!({})));
4923        assert_eq!(unknown["error"]["code"], -32601);
4924    }
4925
4926    #[cfg(unix)]
4927    #[tokio::test]
4928    // The test mutates process-wide harness environment and deliberately
4929    // holds the global test lock until every async runtime operation ends.
4930    #[allow(clippy::await_holding_lock)]
4931    async fn async_service_drives_a_generic_acp_runtime() {
4932        let _environment_guard = crate::live_runtime::test_environment_lock();
4933        let script = r#"
4934            i=0
4935            while IFS= read -r line; do
4936              i=$((i + 1))
4937              case "$i" in
4938                1) printf '%s\n' '{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{},"authMethods":[]}}' ;;
4939                2) printf '%s\n' '{"jsonrpc":"2.0","id":2,"result":{"sessionId":"svc_acp"}}' ;;
4940                3)
4941                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ok"}}}}'
4942                  printf '%s\n' '{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}'
4943                  ;;
4944                4)
4945                  printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"svc_acp","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"from terminal"}}}}'
4946                  printf '%s\n' '{"jsonrpc":"2.0","id":4,"result":{"stopReason":"end_turn"}}'
4947                  ;;
4948              esac
4949            done
4950        "#;
4951        let mut service = HarnessSessionService::new();
4952        let started = service
4953            .handle_async(request(
4954                1,
4955                "harness.v1.runtimes.start",
4956                json!({
4957                    "harness": "codex",
4958                    "protocol": "acp",
4959                    "cwd": std::env::current_dir().unwrap(),
4960                    "launch": {"program": "/bin/sh", "arguments": ["-c", script], "env": {}},
4961                }),
4962            ))
4963            .await;
4964        assert_eq!(started["result"]["connection"], "runtime-1");
4965        assert_eq!(started["result"]["handle"]["runtime_id"], "svc_acp");
4966
4967        let terminal = service
4968            .handle_async(request(
4969                9,
4970                "harness.v1.runtimes.terminal_instructions",
4971                json!({"connection":"runtime-1"}),
4972            ))
4973            .await;
4974        let arguments = terminal["result"]["launch"]["arguments"]
4975            .as_array()
4976            .expect("hosted runtime should return terminal arguments");
4977        let endpoint_index = arguments
4978            .iter()
4979            .position(|value| value == "--endpoint")
4980            .expect("terminal command should use an opaque endpoint");
4981        let endpoint = LiveRuntimeEndpoint::parse(
4982            arguments[endpoint_index + 1]
4983                .as_str()
4984                .expect("endpoint argument should be text"),
4985        )
4986        .unwrap();
4987        assert!(!terminal.to_string().contains("Bearer"));
4988        let workspace = std::env::current_dir().unwrap();
4989        let receipt = resolve_live_runtime(
4990            &endpoint,
4991            &LiveRuntimeSource {
4992                harness: "codex".into(),
4993                session_id: "svc_acp".into(),
4994                workspace,
4995            },
4996        )
4997        .unwrap();
4998        let remote = crate::HttpFrontendRuntime::connect(receipt.base_url, receipt.token)
4999            .await
5000            .unwrap();
5001        let mut attachment = crate::FrontendRuntime::attach(remote.as_ref(), 100)
5002            .await
5003            .unwrap();
5004
5005        let sent = service
5006            .handle_async(request(
5007                2,
5008                "harness.v1.runtimes.send_input",
5009                json!({"connection": "runtime-1", "text": "hi"}),
5010            ))
5011            .await;
5012        assert_eq!(sent["result"]["turn_id"], "3");
5013
5014        let mut events = Vec::new();
5015        for _ in 0..20 {
5016            events.extend(service.poll_runtimes().await);
5017            if events.len() >= 2 {
5018                break;
5019            }
5020            tokio::time::sleep(Duration::from_millis(2)).await;
5021        }
5022        assert!(events
5023            .iter()
5024            .any(|event| { event["params"]["event"]["kind"] == "session/update" }));
5025        assert!(events.iter().any(|event| {
5026            event["params"]["event"]["kind"] == "supercode/acp_request_completed"
5027        }));
5028
5029        let saw_editor_reply = tokio::time::timeout(Duration::from_secs(2), async {
5030            loop {
5031                let event = attachment.next_event().await.unwrap();
5032                if event.kind == "text_delta" && event.payload["text"] == "ok" {
5033                    break;
5034                }
5035            }
5036        })
5037        .await;
5038        assert!(
5039            saw_editor_reply.is_ok(),
5040            "terminal should observe the editor-driven turn"
5041        );
5042
5043        crate::FrontendRuntime::submit(remote.as_ref(), "DRIVE FROM TERMINAL".into())
5044            .await
5045            .unwrap();
5046        let saw_terminal_reply = tokio::time::timeout(Duration::from_secs(2), async {
5047            loop {
5048                let event = attachment.next_event().await.unwrap();
5049                if event.kind == "text_delta" && event.payload["text"] == "from terminal" {
5050                    break;
5051                }
5052            }
5053        })
5054        .await;
5055        assert!(
5056            saw_terminal_reply.is_ok(),
5057            "terminal should drive the same runtime"
5058        );
5059
5060        let closed = service
5061            .handle_async(request(
5062                3,
5063                "harness.v1.runtimes.close",
5064                json!({"connection": "runtime-1"}),
5065            ))
5066            .await;
5067        assert_eq!(closed["result"]["closed"], true);
5068    }
5069}