opencode_ctl/tools/
pair.rs1use serde_json::{json, Value};
4
5use crate::clock::Clock;
6use crate::engine::Engine;
7use crate::error::{ErrorCode, ToolError, ToolResult};
8use crate::liveness::{side_liveness, Liveness, Side};
9use crate::pairing::read_record;
10use crate::transport::Transport;
11
12use super::PairStatusArgs;
13
14impl<T: Transport, C: Clock> Engine<T, C> {
15 pub fn pair_liveness(&self) -> (Liveness, Liveness) {
18 let Some(id) = self.env.pair.as_deref() else {
19 return (Liveness::None, Liveness::None);
20 };
21 match read_record(&self.env.home, id) {
22 Ok(Some(r)) => {
23 let now = self.clock().now_ms();
24 let side = |s| side_liveness(s, &r, &self.env.home, &*self.probe, now);
25 (side(Side::Tui), side(Side::Controller))
26 }
27 _ => (Liveness::Unknown, Liveness::Unknown),
28 }
29 }
30
31 pub async fn pair_status(&self, _a: PairStatusArgs) -> ToolResult {
32 let Some(id) = self.env.pair.as_deref() else {
33 return Ok(json!({"paired": null}));
34 };
35 let record = read_record(&self.env.home, id)?.ok_or_else(|| {
36 ToolError::new(
37 ErrorCode::PairNotFound,
38 format!("no pair record for {id}"),
39 )
40 .hint("restart the controller with `opencode-pair resume <pair-id>`, or pair again with `opencode-pair`")
41 })?;
42 let now = self.clock().now_ms();
43 let mut out = serde_json::to_value(&record)
44 .map_err(|e| ToolError::new(ErrorCode::IoError, e.to_string()))?;
45 let tui = side_liveness(Side::Tui, &record, &self.env.home, &*self.probe, now);
46 let claude = side_liveness(Side::Controller, &record, &self.env.home, &*self.probe, now);
47 out["tui"] = Value::String(tui.as_str().into());
48 out["claude"] = Value::String(claude.as_str().into());
49 Ok(out)
50 }
51}