1use std::collections::{HashMap, VecDeque};
4use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
5use std::sync::Arc;
6use std::time::Duration;
7
8use futures_util::{SinkExt, StreamExt};
9use serde_json::{json, Map, Value};
10use tokio::sync::{mpsc, oneshot, Mutex, Notify};
11use tokio::time::{sleep, timeout};
12use tokio_tungstenite::{connect_async, tungstenite::Message};
13
14use crate::errors::{
15 ConnectionError, DaemonError, DisconnectCause, Error, Result, StaleLoopError, TimeoutError,
16};
17use crate::heartbeat::HeartbeatTracker;
18use crate::inbound_priority::{
19 inbound_frame_drop_priority, DEFAULT_INBOUND_MAX_SIZE, DROP_PRIORITY_NORMAL,
20};
21use crate::protocol::{
22 decode_message, expand_wire_messages, new_connection_init, new_notification, new_ping,
23 new_pong, new_request, new_subscribe, new_unsubscribe, Envelope,
24};
25use crate::stream_terminal::{
26 extract_loop_id_from_inbound, inbound_needs_delivery_ack, stale_pending_frame_label,
27};
28const DEFAULT_MAX_FRAME: usize = 10 * 1024 * 1024;
29
30#[derive(Debug, Clone)]
32pub struct ClientConfig {
33 pub url: String,
35 pub max_inbound: usize,
37 pub max_frame_size: usize,
39}
40
41impl Default for ClientConfig {
42 fn default() -> Self {
43 Self {
44 url: "ws://127.0.0.1:8765".into(),
45 max_inbound: DEFAULT_INBOUND_MAX_SIZE,
46 max_frame_size: DEFAULT_MAX_FRAME,
47 }
48 }
49}
50
51#[derive(Debug, Clone, Default)]
53pub struct SendInputOptions {
54 pub loop_id: Option<String>,
56 pub autonomous: bool,
58 pub max_iterations: Option<u32>,
60 pub preferred_subagent: Option<String>,
62 pub model: Option<String>,
64 pub model_params: Option<Value>,
66 pub router_profile: Option<String>,
68 pub attachments: Option<Value>,
70 pub intent_hint: Option<String>,
72 pub response_schema: Option<Value>,
74 pub response_schema_name: Option<String>,
76 pub response_schema_strict: Option<bool>,
78 pub clarification_mode: Option<String>,
80 pub clarification_answer: bool,
82 pub clarification_answers: Option<Value>,
84}
85
86type RpcWaiter = oneshot::Sender<std::result::Result<Value, DaemonError>>;
87type StreamDegradedCallback = Arc<dyn Fn(u64, String) + Send + Sync>;
88
89struct Shared {
90 url: String,
91 max_inbound: AtomicUsize,
92 write_tx: Mutex<Option<mpsc::UnboundedSender<Message>>>,
93 rpc_waiters: Mutex<HashMap<String, RpcWaiter>>,
94 inbound: Mutex<VecDeque<Value>>,
95 inbound_notify: Notify,
96 connected: AtomicBool,
97 reader_alive: AtomicBool,
98 handshake_complete: AtomicBool,
99 readiness_state: Mutex<String>,
100 disconnect_cause: Mutex<Option<DisconnectCause>>,
101 disconnect_notify: Notify,
102 inbound_dropped: AtomicU64,
103 delivery_seq: Mutex<HashMap<String, u64>>,
104 heartbeat_interval_ms: Mutex<Option<u64>>,
105 stream_degraded_cb: std::sync::Mutex<Option<StreamDegradedCallback>>,
106 degraded_notified: AtomicBool,
107 heartbeat_tracker: std::sync::Mutex<Option<Arc<HeartbeatTracker>>>,
108}
109
110#[derive(Clone)]
112pub struct Client {
113 shared: Arc<Shared>,
114}
115
116impl Client {
117 pub fn new(url: impl Into<String>) -> Self {
119 Self::with_config(ClientConfig {
120 url: url.into(),
121 ..Default::default()
122 })
123 }
124
125 pub fn with_config(cfg: ClientConfig) -> Self {
127 Self {
128 shared: Arc::new(Shared {
129 url: cfg.url,
130 max_inbound: AtomicUsize::new(cfg.max_inbound),
131 write_tx: Mutex::new(None),
132 rpc_waiters: Mutex::new(HashMap::new()),
133 inbound: Mutex::new(VecDeque::new()),
134 inbound_notify: Notify::new(),
135 connected: AtomicBool::new(false),
136 reader_alive: AtomicBool::new(false),
137 handshake_complete: AtomicBool::new(false),
138 readiness_state: Mutex::new(String::new()),
139 disconnect_cause: Mutex::new(None),
140 disconnect_notify: Notify::new(),
141 inbound_dropped: AtomicU64::new(0),
142 delivery_seq: Mutex::new(HashMap::new()),
143 heartbeat_interval_ms: Mutex::new(None),
144 stream_degraded_cb: std::sync::Mutex::new(None),
145 degraded_notified: AtomicBool::new(false),
146 heartbeat_tracker: std::sync::Mutex::new(None),
147 }),
148 }
149 }
150
151 pub fn url(&self) -> &str {
153 &self.shared.url
154 }
155
156 pub fn is_connected(&self) -> bool {
158 self.shared.connected.load(Ordering::SeqCst)
159 }
160
161 pub fn is_connection_alive(&self) -> bool {
163 self.shared.reader_alive.load(Ordering::SeqCst)
164 && self.shared.connected.load(Ordering::SeqCst)
165 }
166
167 pub fn is_handshake_complete(&self) -> bool {
169 self.shared.handshake_complete.load(Ordering::SeqCst)
170 }
171
172 pub async fn readiness_state(&self) -> String {
174 self.shared.readiness_state.lock().await.clone()
175 }
176
177 pub fn inbound_dropped(&self) -> u64 {
179 self.shared.inbound_dropped.load(Ordering::SeqCst)
180 }
181
182 pub fn set_stream_degraded_callback(&self, cb: Option<Arc<dyn Fn(u64, String) + Send + Sync>>) {
184 *self
185 .shared
186 .stream_degraded_cb
187 .lock()
188 .expect("stream_degraded lock") = cb;
189 self.shared.degraded_notified.store(false, Ordering::SeqCst);
190 }
191
192 pub fn enable_heartbeat_tracking(&self) -> Arc<HeartbeatTracker> {
194 self.enable_heartbeat_tracking_with_threshold(Duration::from_secs(15))
195 }
196
197 pub fn enable_heartbeat_tracking_with_threshold(
199 &self,
200 threshold: Duration,
201 ) -> Arc<HeartbeatTracker> {
202 let tracker = Arc::new(HeartbeatTracker::with_threshold(threshold));
203 *self
204 .shared
205 .heartbeat_tracker
206 .lock()
207 .expect("heartbeat lock") = Some(tracker.clone());
208 tracker
209 }
210
211 pub fn disable_heartbeat_tracking(&self) {
213 *self
214 .shared
215 .heartbeat_tracker
216 .lock()
217 .expect("heartbeat lock") = None;
218 }
219
220 pub fn heartbeat_tracker(&self) -> Option<Arc<HeartbeatTracker>> {
222 self.shared
223 .heartbeat_tracker
224 .lock()
225 .expect("heartbeat lock")
226 .clone()
227 }
228
229 pub fn is_daemon_alive(&self) -> bool {
231 match self
232 .shared
233 .heartbeat_tracker
234 .lock()
235 .expect("heartbeat lock")
236 .as_ref()
237 {
238 Some(t) => t.get_health().is_alive,
239 None => true,
240 }
241 }
242
243 pub async fn disconnect_cause(&self) -> Option<DisconnectCause> {
245 *self.shared.disconnect_cause.lock().await
246 }
247
248 pub async fn wait_disconnected(&self) -> DisconnectCause {
250 loop {
251 if let Some(c) = *self.shared.disconnect_cause.lock().await {
252 return c;
253 }
254 self.shared.disconnect_notify.notified().await;
255 }
256 }
257
258 pub async fn connect(&self) -> Result<()> {
260 if self.is_connected() {
261 return Ok(());
262 }
263 let (ws, _) = connect_async(&self.shared.url).await.map_err(|e| {
264 ConnectionError::new(
265 &self.shared.url,
266 1,
267 Box::new(e) as Box<dyn std::error::Error + Send + Sync>,
268 )
269 })?;
270 let (mut write, mut read) = ws.split();
271 let (tx, mut rx) = mpsc::unbounded_channel::<Message>();
272 {
273 let mut slot = self.shared.write_tx.lock().await;
274 *slot = Some(tx);
275 }
276 self.shared.connected.store(true, Ordering::SeqCst);
277 self.shared.reader_alive.store(true, Ordering::SeqCst);
278 *self.shared.disconnect_cause.lock().await = None;
279
280 let shared_r2 = self.shared.clone();
281 let client_for_hb = self.clone();
282 tokio::spawn(async move {
283 while let Some(msg) = rx.recv().await {
284 if write.send(msg).await.is_err() {
285 break;
286 }
287 }
288 let _ = write.close().await;
289 });
290
291 tokio::spawn(async move {
292 while let Some(item) = read.next().await {
293 match item {
294 Ok(Message::Text(text)) => {
295 if let Ok(raw) = decode_message(&text) {
296 for msg in expand_wire_messages(raw) {
297 shared_r2.route_inbound(msg).await;
298 }
299 }
300 }
301 Ok(Message::Ping(data)) => {
302 let _ = shared_r2.send_raw(Message::Pong(data)).await;
303 }
304 Ok(Message::Close(_)) => break,
305 Ok(_) => {}
306 Err(_) => break,
307 }
308 }
309 shared_r2.mark_disconnected(DisconnectCause::Unclean).await;
310 });
311
312 self.send_envelope(new_connection_init()).await?;
314 let ack = self.wait_connection_ack(Duration::from_secs(15)).await?;
315 if let Some(interval) = ack
316 .get("result")
317 .and_then(|r| r.get("heartbeat_interval_ms"))
318 .and_then(|v| v.as_u64())
319 {
320 *self.shared.heartbeat_interval_ms.lock().await = Some(interval);
321 if interval > 0 {
322 let hb_client = client_for_hb;
323 tokio::spawn(async move {
324 let period = Duration::from_millis(interval.max(5_000));
325 while hb_client.is_connection_alive() {
326 sleep(period).await;
327 if !hb_client.is_connection_alive() {
328 break;
329 }
330 let _ = hb_client.send_envelope(new_ping()).await;
331 }
332 });
333 }
334 }
335 Ok(())
336 }
337
338 async fn wait_connection_ack(&self, overall: Duration) -> Result<Value> {
339 let deadline = tokio::time::Instant::now() + overall;
340 loop {
341 let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
342 if remaining.is_zero() {
343 return Err(TimeoutError::new("connection_ack", format!("{overall:?}")).into());
344 }
345 let msg = self
346 .read_event_with_timeout(remaining.min(Duration::from_secs(2)))
347 .await?;
348 let Some(msg) = msg else {
349 continue;
350 };
351 let msg_type = msg.get("type").and_then(|v| v.as_str()).unwrap_or("");
352 if msg_type == "status" {
353 continue;
354 }
355 if msg_type == "error" {
356 let err = parse_daemon_error(&msg);
357 if matches!(err.code, -32003..=-32001) {
359 sleep(Duration::from_millis(50)).await;
360 self.send_envelope(new_connection_init()).await?;
361 continue;
362 }
363 return Err(err.into());
364 }
365 if msg_type == "connection_ack" {
366 let state = msg
367 .get("result")
368 .and_then(|r| r.get("readiness_state"))
369 .and_then(|v| v.as_str())
370 .unwrap_or("");
371 if state == "starting" || state == "warming" {
372 sleep(Duration::from_millis(50)).await;
373 self.send_envelope(new_connection_init()).await?;
374 continue;
375 }
376 if state == "ready" || state.is_empty() {
377 *self.shared.readiness_state.lock().await = if state.is_empty() {
378 "ready".into()
379 } else {
380 state.into()
381 };
382 self.shared.handshake_complete.store(true, Ordering::SeqCst);
383 return Ok(msg);
384 }
385 return Err(Error::protocol(format!(
386 "connection_ack readiness_state={state}"
387 )));
388 }
389 }
390 }
391
392 pub async fn close(&self) -> Result<()> {
394 if self.is_connected() {
395 let _ = self.notify("disconnect", Map::new()).await;
396 }
397 {
398 let mut tx = self.shared.write_tx.lock().await;
399 *tx = None;
400 }
401 self.shared
402 .handshake_complete
403 .store(false, Ordering::SeqCst);
404 *self.shared.readiness_state.lock().await = String::new();
405 self.shared.mark_disconnected(DisconnectCause::Clean).await;
406 Ok(())
407 }
408
409 pub async fn reconnect(&self) -> Result<()> {
414 {
415 let mut tx = self.shared.write_tx.lock().await;
416 *tx = None;
417 }
418 self.shared.connected.store(false, Ordering::SeqCst);
419 self.shared.reader_alive.store(false, Ordering::SeqCst);
420 self.shared
421 .handshake_complete
422 .store(false, Ordering::SeqCst);
423 *self.shared.readiness_state.lock().await = String::new();
424 *self.shared.disconnect_cause.lock().await = None;
425 self.shared.rpc_waiters.lock().await.clear();
426 self.shared.inbound.lock().await.clear();
427 self.shared.degraded_notified.store(false, Ordering::SeqCst);
428 self.connect().await
429 }
430
431 pub async fn send_envelope(&self, env: Envelope) -> Result<()> {
433 let text = env.to_wire_json()?;
434 self.shared.send_raw(Message::Text(text.into())).await
435 }
436
437 pub async fn notify(&self, method: &str, params: Map<String, Value>) -> Result<()> {
439 self.send_envelope(new_notification(method, params)).await
440 }
441
442 pub async fn request(
444 &self,
445 method: &str,
446 params: Map<String, Value>,
447 req_timeout: Duration,
448 ) -> Result<Map<String, Value>> {
449 let env = new_request(method, params);
450 let id = env.id.clone().unwrap_or_default();
451 let (tx, rx) = oneshot::channel();
452 self.shared.rpc_waiters.lock().await.insert(id.clone(), tx);
453 if let Err(e) = self.send_envelope(env).await {
454 self.shared.rpc_waiters.lock().await.remove(&id);
455 return Err(e);
456 }
457 match timeout(req_timeout, rx).await {
458 Ok(Ok(Ok(Value::Object(m)))) => Ok(m),
459 Ok(Ok(Ok(other))) => {
460 let mut m = Map::new();
461 m.insert("result".into(), other);
462 Ok(m)
463 }
464 Ok(Ok(Err(de))) => Err(de.into()),
465 Ok(Err(_)) => Err(Error::protocol("rpc waiter dropped")),
466 Err(_) => {
467 self.shared.rpc_waiters.lock().await.remove(&id);
468 Err(TimeoutError::new(method, format!("{req_timeout:?}")).into())
469 }
470 }
471 }
472
473 pub async fn request_response(
476 &self,
477 payload: Map<String, Value>,
478 fallback_method: &str,
479 req_timeout: Duration,
480 ) -> Result<Map<String, Value>> {
481 let method = payload
482 .get("type")
483 .and_then(|v| v.as_str())
484 .unwrap_or(fallback_method)
485 .to_string();
486 let mut params = Map::new();
487 for (k, v) in payload {
488 if k == "type" || k == "request_id" {
489 continue;
490 }
491 params.insert(k, v);
492 }
493 self.request(&method, params, req_timeout).await
494 }
495
496 pub async fn subscribe(
498 &self,
499 method: &str,
500 params: Map<String, Value>,
501 req_timeout: Duration,
502 ) -> Result<String> {
503 let env = new_subscribe(method, params);
504 let id = env.id.clone().unwrap_or_default();
505 let (tx, rx) = oneshot::channel();
508 self.shared.rpc_waiters.lock().await.insert(id.clone(), tx);
509 self.send_envelope(env).await?;
510 match timeout(req_timeout.min(Duration::from_millis(500)), rx).await {
512 Ok(Ok(Err(de))) => return Err(de.into()),
513 Ok(Ok(Ok(_))) => {}
514 _ => {
515 self.shared.rpc_waiters.lock().await.remove(&id);
516 }
517 }
518 Ok(id)
519 }
520
521 pub async fn unsubscribe(&self, id: &str) -> Result<()> {
523 self.send_envelope(new_unsubscribe(id)).await
524 }
525
526 pub async fn read_event(&self) -> Result<Option<Value>> {
528 loop {
529 {
530 let mut q = self.shared.inbound.lock().await;
531 if let Some(v) = q.pop_front() {
532 return Ok(Some(v));
533 }
534 }
535 if !self.is_connection_alive() {
536 return Ok(None);
537 }
538 self.shared.inbound_notify.notified().await;
539 }
540 }
541
542 pub async fn read_event_with_timeout(&self, dur: Duration) -> Result<Option<Value>> {
544 match timeout(dur, self.read_event()).await {
545 Ok(r) => r,
546 Err(_) => Ok(None),
547 }
548 }
549
550 pub async fn clear_pending_events(&self) {
552 self.shared.inbound.lock().await.clear();
553 }
554
555 pub async fn peel_stale_pending_control_events(&self) -> Vec<String> {
557 let mut labels = Vec::new();
558 let mut kept = VecDeque::new();
559 let mut q = self.shared.inbound.lock().await;
560 while let Some(ev) = q.pop_front() {
561 if let Some(label) = stale_pending_frame_label(&ev) {
562 labels.push(label);
563 } else {
564 kept.push_back(ev);
565 }
566 }
567 *q = kept;
568 labels
569 }
570
571 pub async fn push_pending_event(&self, ev: Value) {
576 if !ev.is_object() {
577 return;
578 }
579 let mut q = self.shared.inbound.lock().await;
580 self.shared.enqueue_pending_locked(&mut q, ev);
581 self.shared.inbound_notify.notify_one();
582 }
583
584 pub fn set_inbound_max_size(&self, n: usize) {
586 if n > 0 {
587 self.shared.max_inbound.store(n, Ordering::SeqCst);
588 }
589 }
590
591 pub fn receive_messages(&self, buffer: usize) -> mpsc::Receiver<Value> {
602 let (tx, rx) = mpsc::channel(if buffer == 0 { 100 } else { buffer });
603 let shared = self.shared.clone();
604 tokio::spawn(async move {
605 loop {
606 let ev = {
607 let mut q = shared.inbound.lock().await;
608 q.pop_front()
609 };
610 if let Some(ev) = ev {
611 if tx.send(ev).await.is_err() {
612 return;
613 }
614 continue;
615 }
616 if !shared.is_connection_alive() {
617 return;
618 }
619 shared.inbound_notify.notified().await;
620 }
621 });
622 rx
623 }
624
625 pub async fn send_input(&self, text: &str, opts: SendInputOptions) -> Result<()> {
627 let mut params = Map::new();
628 params.insert("content".into(), json!(text));
629 if let Some(lid) = opts.loop_id {
630 params.insert("loop_id".into(), json!(lid));
631 }
632 if opts.autonomous {
633 params.insert("autonomous".into(), json!(true));
634 }
635 if let Some(n) = opts.max_iterations {
636 params.insert("max_iterations".into(), json!(n));
637 }
638 if let Some(v) = opts.preferred_subagent {
639 params.insert("preferred_subagent".into(), json!(v));
640 }
641 if let Some(v) = opts.model {
642 params.insert("model".into(), json!(v));
643 }
644 if let Some(v) = opts.model_params {
645 params.insert("model_params".into(), v);
646 }
647 if let Some(v) = opts.router_profile {
648 params.insert("router_profile".into(), json!(v));
649 }
650 if let Some(v) = opts.attachments {
651 params.insert("attachments".into(), v);
652 }
653 if let Some(v) = opts.intent_hint {
654 params.insert("intent_hint".into(), json!(v));
655 }
656 if let Some(v) = opts.response_schema {
657 params.insert("response_schema".into(), v);
658 }
659 if let Some(v) = opts.response_schema_name {
660 params.insert("response_schema_name".into(), json!(v));
661 }
662 if let Some(v) = opts.response_schema_strict {
663 params.insert("response_schema_strict".into(), json!(v));
664 }
665 if let Some(v) = opts.clarification_mode {
666 params.insert("clarification_mode".into(), json!(v));
667 }
668 if opts.clarification_answer {
669 params.insert("clarification_answer".into(), json!(true));
670 }
671 if let Some(v) = opts.clarification_answers {
672 params.insert("clarification_answers".into(), v);
673 }
674 self.notify("loop_input", params).await
675 }
676
677 pub async fn loop_new(&self, params: Map<String, Value>) -> Result<Map<String, Value>> {
679 self.request("loop_new", params, Duration::from_secs(30))
680 .await
681 }
682
683 pub async fn loop_list(&self, limit: u32) -> Result<Map<String, Value>> {
685 let mut params = Map::new();
686 params.insert("limit".into(), json!(limit));
687 self.request("loop_list", params, Duration::from_secs(15))
688 .await
689 }
690
691 pub async fn loop_get(&self, loop_id: &str) -> Result<Map<String, Value>> {
693 let mut params = Map::new();
694 params.insert("loop_id".into(), json!(loop_id));
695 self.request("loop_get", params, Duration::from_secs(15))
696 .await
697 }
698
699 pub async fn loop_reattach(&self, loop_id: &str) -> Result<Map<String, Value>> {
701 let mut params = Map::new();
702 params.insert("loop_id".into(), json!(loop_id));
703 self.request("loop_reattach", params, Duration::from_secs(15))
704 .await
705 }
706
707 pub async fn loop_subscribe(&self, loop_id: &str, stream_delivery: &str) -> Result<String> {
709 let mut params = Map::new();
710 params.insert("loop_id".into(), json!(loop_id));
711 params.insert("stream_delivery".into(), json!(stream_delivery));
712 params.insert("wire_tier".into(), json!("full"));
713 self.subscribe("loop_events", params, Duration::from_secs(30))
714 .await
715 }
716
717 pub async fn loop_cards_fetch(&self, loop_id: &str) -> Result<Map<String, Value>> {
719 let mut params = Map::new();
720 params.insert("loop_id".into(), json!(loop_id));
721 self.request("loop_cards_fetch", params, Duration::from_secs(30))
722 .await
723 }
724
725 pub async fn loop_history_fetch(&self, loop_id: &str) -> Result<Map<String, Value>> {
727 let mut params = Map::new();
728 params.insert("loop_id".into(), json!(loop_id));
729 self.request("loop_history_fetch", params, Duration::from_secs(30))
730 .await
731 }
732
733 pub async fn loop_messages(
735 &self,
736 loop_id: &str,
737 limit: u32,
738 offset: u32,
739 ) -> Result<Map<String, Value>> {
740 let mut params = Map::new();
741 params.insert("loop_id".into(), json!(loop_id));
742 params.insert("limit".into(), json!(limit));
743 params.insert("offset".into(), json!(offset));
744 self.request("loop_messages", params, Duration::from_secs(10))
745 .await
746 }
747
748 pub async fn loop_state_get(&self, loop_id: &str) -> Result<Map<String, Value>> {
750 let mut params = Map::new();
751 params.insert("loop_id".into(), json!(loop_id));
752 self.request("loop_state_get", params, Duration::from_secs(30))
753 .await
754 }
755
756 pub async fn loop_state_update(
758 &self,
759 loop_id: &str,
760 state: Map<String, Value>,
761 ) -> Result<Map<String, Value>> {
762 let mut params = Map::new();
763 params.insert("loop_id".into(), json!(loop_id));
764 params.insert("state".into(), Value::Object(state));
765 self.request("loop_state_update", params, Duration::from_secs(30))
766 .await
767 }
768
769 pub async fn loop_tree(
771 &self,
772 loop_id: &str,
773 format: Option<&str>,
774 ) -> Result<Map<String, Value>> {
775 let mut params = Map::new();
776 params.insert("loop_id".into(), json!(loop_id));
777 if let Some(f) = format {
778 if !f.is_empty() {
779 params.insert("format".into(), json!(f));
780 }
781 }
782 self.request("loop_tree", params, Duration::from_secs(15))
783 .await
784 }
785
786 pub async fn loop_prune(
788 &self,
789 loop_id: &str,
790 retention_days: Option<i32>,
791 dry_run: bool,
792 ) -> Result<Map<String, Value>> {
793 let mut params = Map::new();
794 params.insert("loop_id".into(), json!(loop_id));
795 if let Some(d) = retention_days {
796 if d > 0 {
797 params.insert("retention_days".into(), json!(d));
798 }
799 }
800 if dry_run {
801 params.insert("dry_run".into(), json!(true));
802 }
803 self.request("loop_prune", params, Duration::from_secs(30))
804 .await
805 }
806
807 pub async fn loop_delete(&self, loop_id: &str) -> Result<Map<String, Value>> {
809 let mut params = Map::new();
810 params.insert("loop_id".into(), json!(loop_id));
811 self.request("loop_delete", params, Duration::from_secs(10))
812 .await
813 }
814
815 pub async fn loop_detach(&self, subscription_id: &str) -> Result<()> {
817 self.unsubscribe(subscription_id).await
818 }
819
820 pub async fn authenticate(
822 &self,
823 access_key: &str,
824 secret_key: &str,
825 ) -> Result<Map<String, Value>> {
826 let mut params = Map::new();
827 params.insert("access_key".into(), json!(access_key));
828 params.insert("secret_key".into(), json!(secret_key));
829 self.request("auth", params, Duration::from_secs(15)).await
830 }
831
832 pub async fn refresh_auth_token(&self, refresh_token: &str) -> Result<Map<String, Value>> {
834 let mut params = Map::new();
835 params.insert("refresh_token".into(), json!(refresh_token));
836 self.request("auth_refresh", params, Duration::from_secs(15))
837 .await
838 }
839
840 pub async fn job_create(
842 &self,
843 goal: &str,
844 workspace: Option<&str>,
845 ) -> Result<Map<String, Value>> {
846 let mut params = Map::new();
847 params.insert("goal".into(), json!(goal));
848 if let Some(ws) = workspace {
849 params.insert("workspace".into(), json!(ws));
850 }
851 self.request("job_create", params, Duration::from_secs(30))
852 .await
853 }
854
855 pub async fn job_status(&self, job_id: &str) -> Result<Map<String, Value>> {
857 let mut params = Map::new();
858 params.insert("job_id".into(), json!(job_id));
859 self.request("job_status", params, Duration::from_secs(15))
860 .await
861 }
862
863 pub async fn job_pause(&self, job_id: &str) -> Result<Map<String, Value>> {
865 let mut params = Map::new();
866 params.insert("job_id".into(), json!(job_id));
867 self.request("job_pause", params, Duration::from_secs(15))
868 .await
869 }
870
871 pub async fn job_resume(&self, job_id: &str) -> Result<Map<String, Value>> {
873 let mut params = Map::new();
874 params.insert("job_id".into(), json!(job_id));
875 self.request("job_resume", params, Duration::from_secs(15))
876 .await
877 }
878
879 pub async fn job_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
881 let mut params = Map::new();
882 params.insert("job_id".into(), json!(job_id));
883 self.request("job_cancel", params, Duration::from_secs(15))
884 .await
885 }
886
887 pub async fn job_dag(&self, job_id: &str) -> Result<Map<String, Value>> {
889 let mut params = Map::new();
890 params.insert("job_id".into(), json!(job_id));
891 self.request("job_dag", params, Duration::from_secs(15))
892 .await
893 }
894
895 pub async fn job_guidance(
897 &self,
898 job_id: &str,
899 content: &str,
900 goal_id: Option<&str>,
901 ) -> Result<Map<String, Value>> {
902 let mut params = Map::new();
903 params.insert("job_id".into(), json!(job_id));
904 params.insert("content".into(), json!(content));
905 if let Some(g) = goal_id {
906 params.insert("goal_id".into(), json!(g));
907 }
908 self.request("job_guidance", params, Duration::from_secs(30))
909 .await
910 }
911
912 pub async fn autopilot_status(&self) -> Result<Map<String, Value>> {
914 self.request("autopilot_status", Map::new(), Duration::from_secs(15))
915 .await
916 }
917
918 pub async fn autopilot_submit(
920 &self,
921 description: &str,
922 priority: i32,
923 workspace: Option<&str>,
924 ) -> Result<Map<String, Value>> {
925 let mut params = Map::new();
926 params.insert("description".into(), json!(description));
927 params.insert("priority".into(), json!(priority));
928 if let Some(ws) = workspace {
929 params.insert("workspace".into(), json!(ws));
930 }
931 self.request("autopilot_submit", params, Duration::from_secs(30))
932 .await
933 }
934
935 pub async fn autopilot_list_goals(&self) -> Result<Map<String, Value>> {
937 self.request("autopilot_list_goals", Map::new(), Duration::from_secs(15))
938 .await
939 }
940
941 pub async fn autopilot_get_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
943 let mut params = Map::new();
944 params.insert("goal_id".into(), json!(goal_id));
945 self.request("autopilot_get_goal", params, Duration::from_secs(15))
946 .await
947 }
948
949 pub async fn autopilot_cancel_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
951 let mut params = Map::new();
952 params.insert("goal_id".into(), json!(goal_id));
953 self.request("autopilot_cancel_goal", params, Duration::from_secs(15))
954 .await
955 }
956
957 pub async fn autopilot_cancel_all(&self) -> Result<Map<String, Value>> {
959 self.request("autopilot_cancel_all", Map::new(), Duration::from_secs(15))
960 .await
961 }
962
963 pub async fn autopilot_wake(&self) -> Result<Map<String, Value>> {
965 self.request("autopilot_wake", Map::new(), Duration::from_secs(15))
966 .await
967 }
968
969 pub async fn autopilot_dream(&self) -> Result<Map<String, Value>> {
971 self.request("autopilot_dream", Map::new(), Duration::from_secs(15))
972 .await
973 }
974
975 pub async fn autopilot_resume(&self, goal_id: &str) -> Result<Map<String, Value>> {
977 let mut params = Map::new();
978 params.insert("goal_id".into(), json!(goal_id));
979 self.request("autopilot_resume", params, Duration::from_secs(15))
980 .await
981 }
982
983 pub async fn autopilot_list_jobs(&self) -> Result<Map<String, Value>> {
985 self.request("autopilot_list_jobs", Map::new(), Duration::from_secs(15))
986 .await
987 }
988
989 pub async fn autopilot_get_job(&self, job_id: &str) -> Result<Map<String, Value>> {
991 let mut params = Map::new();
992 params.insert("job_id".into(), json!(job_id));
993 self.request("autopilot_get_job", params, Duration::from_secs(15))
994 .await
995 }
996
997 pub async fn autopilot_subscribe(&self) -> Result<String> {
999 self.subscribe("autopilot_events", Map::new(), Duration::from_secs(15))
1000 .await
1001 }
1002
1003 pub async fn autopilot_unsubscribe(&self, subscription_id: &str) -> Result<()> {
1005 self.unsubscribe(subscription_id).await
1006 }
1007
1008 pub async fn cron_add(&self, text: &str, priority: Option<i32>) -> Result<Map<String, Value>> {
1010 let mut params = Map::new();
1011 params.insert("text".into(), json!(text));
1012 if let Some(p) = priority {
1013 params.insert("priority".into(), json!(p));
1014 }
1015 self.request("cron_add", params, Duration::from_secs(15))
1016 .await
1017 }
1018
1019 pub async fn cron_list(&self, status: Option<&str>) -> Result<Map<String, Value>> {
1021 let mut params = Map::new();
1022 if let Some(s) = status {
1023 params.insert("status".into(), json!(s));
1024 }
1025 self.request("cron_list", params, Duration::from_secs(15))
1026 .await
1027 }
1028
1029 pub async fn cron_show(&self, job_id: &str) -> Result<Map<String, Value>> {
1031 let mut params = Map::new();
1032 params.insert("job_id".into(), json!(job_id));
1033 self.request("cron_show", params, Duration::from_secs(15))
1034 .await
1035 }
1036
1037 pub async fn cron_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
1039 let mut params = Map::new();
1040 params.insert("job_id".into(), json!(job_id));
1041 self.request("cron_cancel", params, Duration::from_secs(15))
1042 .await
1043 }
1044
1045 pub async fn memory_stats(&self, mode: &str) -> Result<Map<String, Value>> {
1047 let mut params = Map::new();
1048 params.insert("mode".into(), json!(mode));
1049 self.request("memory_stats", params, Duration::from_secs(15))
1050 .await
1051 }
1052
1053 pub async fn list_skills(&self) -> Result<Map<String, Value>> {
1055 self.request("skills_list", Map::new(), Duration::from_secs(15))
1056 .await
1057 }
1058
1059 pub async fn list_models(&self) -> Result<Map<String, Value>> {
1061 self.request("models_list", Map::new(), Duration::from_secs(15))
1062 .await
1063 }
1064
1065 pub async fn mcp_status(&self) -> Result<Map<String, Value>> {
1067 self.request("mcp_status", Map::new(), Duration::from_secs(15))
1068 .await
1069 }
1070
1071 pub async fn fetch_daemon_status(&self) -> Result<Map<String, Value>> {
1073 self.request("daemon_status", Map::new(), Duration::from_secs(10))
1074 .await
1075 }
1076
1077 pub async fn invoke_skill(&self, skill: &str, args: &str) -> Result<Map<String, Value>> {
1079 let mut params = Map::new();
1080 params.insert("skill".into(), json!(skill));
1081 if !args.is_empty() {
1082 params.insert("args".into(), json!(args));
1083 }
1084 self.request("invoke_skill", params, Duration::from_secs(120))
1085 .await
1086 }
1087
1088 pub async fn reattach_and_probe(&self, loop_id: &str) -> Result<()> {
1090 self.loop_reattach(loop_id).await?;
1091 self.loop_subscribe(loop_id, "adaptive").await?;
1092 match self.loop_get(loop_id).await {
1093 Ok(_) => Ok(()),
1094 Err(Error::Daemon(de)) if de.code == -32200 => {
1095 Err(StaleLoopError::new(loop_id, Some(Box::new(de))).into())
1096 }
1097 Err(e) => Err(StaleLoopError::new(loop_id, Some(Box::new(e))).into()),
1098 }
1099 }
1100
1101 pub async fn command_request(
1107 &self,
1108 command: &str,
1109 loop_id: Option<&str>,
1110 params: Option<Map<String, Value>>,
1111 ) -> Result<Map<String, Value>> {
1112 let mut p = Map::new();
1113 p.insert("command".into(), json!(command));
1114 if let Some(lid) = loop_id {
1115 p.insert("loop_id".into(), json!(lid));
1116 }
1117 if let Some(extra) = params {
1118 p.insert("params".into(), Value::Object(extra));
1119 }
1120 self.request("command_request", p, Duration::from_secs(30))
1121 .await
1122 }
1123
1124 pub async fn command_clear(&self, loop_id: &str) -> Result<Map<String, Value>> {
1126 self.command_request("clear", Some(loop_id), None).await
1127 }
1128
1129 pub async fn command_exit(&self, loop_id: &str) -> Result<Map<String, Value>> {
1131 self.command_request("exit", Some(loop_id), None).await
1132 }
1133
1134 pub async fn command_quit(&self, loop_id: &str) -> Result<Map<String, Value>> {
1136 self.command_request("quit", Some(loop_id), None).await
1137 }
1138
1139 pub async fn command_detach(&self, loop_id: &str) -> Result<Map<String, Value>> {
1141 self.command_request("detach", Some(loop_id), None).await
1142 }
1143
1144 pub async fn command_cancel(&self, loop_id: &str) -> Result<Map<String, Value>> {
1146 self.command_request("cancel", Some(loop_id), None).await
1147 }
1148
1149 pub async fn command_memory(&self, loop_id: &str) -> Result<Map<String, Value>> {
1151 self.command_request("memory", Some(loop_id), None).await
1152 }
1153
1154 pub async fn command_policy(&self) -> Result<Map<String, Value>> {
1156 self.command_request("policy", None, None).await
1157 }
1158
1159 pub async fn command_history(&self, loop_id: &str) -> Result<Map<String, Value>> {
1161 self.command_request("history", Some(loop_id), None).await
1162 }
1163
1164 pub async fn command_config(&self) -> Result<Map<String, Value>> {
1166 self.command_request("config", None, None).await
1167 }
1168
1169 pub async fn command_review(&self, loop_id: &str) -> Result<Map<String, Value>> {
1171 self.command_request("review", Some(loop_id), None).await
1172 }
1173
1174 pub async fn command_plan(&self, loop_id: &str) -> Result<Map<String, Value>> {
1176 self.command_request("plan", Some(loop_id), None).await
1177 }
1178
1179 pub async fn command_autopilot_dashboard(&self, loop_id: &str) -> Result<Map<String, Value>> {
1181 self.command_request("autopilot_dashboard", Some(loop_id), None)
1182 .await
1183 }
1184
1185 pub async fn wait_for_daemon_ready(&self, timeout: Duration) -> Result<Map<String, Value>> {
1193 let timeout = if timeout.is_zero() {
1194 Duration::from_secs(10)
1195 } else {
1196 timeout
1197 };
1198 if self.is_handshake_complete() {
1201 let state = self.readiness_state().await;
1202 if state.is_empty() || state == "ready" {
1203 let mut m = Map::new();
1204 m.insert("readiness_state".into(), json!("ready"));
1205 return Ok(m);
1206 }
1207 return Err(Error::protocol(format!(
1208 "daemon not ready: readiness_state={state}"
1209 )));
1210 }
1211 let deadline = tokio::time::Instant::now() + timeout;
1212 loop {
1213 let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
1214 if remaining.is_zero() {
1215 return Err(TimeoutError::new("connection_ack", format!("{timeout:?}")).into());
1216 }
1217 let msg = self
1218 .read_event_with_timeout(remaining.min(Duration::from_secs(2)))
1219 .await?;
1220 let Some(msg) = msg else {
1221 continue;
1222 };
1223 let msg_type = msg.get("type").and_then(|v| v.as_str()).unwrap_or("");
1224 if msg_type != "connection_ack" {
1225 continue;
1226 }
1227 let state = msg
1228 .get("result")
1229 .and_then(|r| r.get("readiness_state"))
1230 .and_then(|v| v.as_str())
1231 .unwrap_or("");
1232 if state == "ready" {
1233 return Ok(map_from_value(msg));
1234 }
1235 return Err(Error::protocol(format!(
1236 "daemon not ready: readiness_state={state}"
1237 )));
1238 }
1239 }
1240}
1241
1242fn map_from_value(v: Value) -> Map<String, Value> {
1244 match v {
1245 Value::Object(m) => m,
1246 other => {
1247 let mut m = Map::new();
1248 m.insert("result".into(), other);
1249 m
1250 }
1251 }
1252}
1253
1254impl Shared {
1255 fn is_connection_alive(&self) -> bool {
1256 self.reader_alive.load(Ordering::SeqCst) && self.connected.load(Ordering::SeqCst)
1257 }
1258
1259 async fn send_raw(&self, msg: Message) -> Result<()> {
1260 let tx = self.write_tx.lock().await;
1261 let Some(tx) = tx.as_ref() else {
1262 return Err(Error::protocol("not connected"));
1263 };
1264 tx.send(msg)
1265 .map_err(|_| Error::protocol("write channel closed"))?;
1266 Ok(())
1267 }
1268
1269 async fn mark_disconnected(&self, cause: DisconnectCause) {
1270 self.connected.store(false, Ordering::SeqCst);
1271 self.reader_alive.store(false, Ordering::SeqCst);
1272 {
1273 let mut slot = self.write_tx.lock().await;
1274 *slot = None;
1275 }
1276 {
1277 let mut c = self.disconnect_cause.lock().await;
1278 if c.is_none() {
1279 *c = Some(cause);
1280 }
1281 }
1282 let mut waiters = self.rpc_waiters.lock().await;
1284 for (_, tx) in waiters.drain() {
1285 let _ = tx.send(Err(DaemonError::new(-1, "disconnected")));
1286 }
1287 self.inbound_notify.notify_waiters();
1288 self.disconnect_notify.notify_waiters();
1289 }
1290
1291 async fn route_inbound(&self, msg: Value) {
1292 if msg.get("type").and_then(|v| v.as_str()) == Some("ping") {
1294 let _ = self
1295 .send_raw(Message::Text(
1296 new_pong().to_wire_json().unwrap_or_default().into(),
1297 ))
1298 .await;
1299 return;
1300 }
1301
1302 if msg.get("type").and_then(|v| v.as_str()) == Some("pong") {
1303 if let Some(tracker) = self.heartbeat_tracker.lock().expect("hb").as_ref() {
1304 tracker.note_pong();
1305 }
1306 return;
1307 }
1308
1309 let ns = msg
1311 .get("namespace")
1312 .or_else(|| msg.get("type"))
1313 .and_then(|v| v.as_str())
1314 .unwrap_or("");
1315 if ns.contains("heartbeat") {
1316 if let Some(tracker) = self.heartbeat_tracker.lock().expect("hb").as_ref() {
1317 tracker.update(msg.get("data"));
1318 }
1319 }
1320
1321 if inbound_needs_delivery_ack(&msg) {
1322 let loop_id = extract_loop_id_from_inbound(&msg);
1323 if !loop_id.is_empty() {
1324 let seq = {
1325 let mut map = self.delivery_seq.lock().await;
1326 let e = map.entry(loop_id.clone()).or_insert(0);
1327 *e += 1;
1328 *e
1329 };
1330 let mut params = Map::new();
1331 params.insert("loop_id".into(), json!(loop_id));
1332 params.insert("seq".into(), json!(seq));
1333 let _ = self
1334 .send_raw(Message::Text(
1335 new_notification("delivery_ack", params)
1336 .to_wire_json()
1337 .unwrap_or_default()
1338 .into(),
1339 ))
1340 .await;
1341 }
1342 }
1343
1344 let msg_type = msg.get("type").and_then(|v| v.as_str()).unwrap_or("");
1345 let id = msg
1346 .get("id")
1347 .and_then(|v| v.as_str())
1348 .unwrap_or("")
1349 .to_string();
1350
1351 if matches!(msg_type, "response" | "error") && !id.is_empty() {
1352 if let Some(waiter) = self.rpc_waiters.lock().await.remove(&id) {
1353 let result = if msg_type == "error" {
1354 Err(parse_daemon_error(&msg))
1355 } else {
1356 Ok(msg
1357 .get("result")
1358 .cloned()
1359 .unwrap_or(Value::Object(Map::new())))
1360 };
1361 let _ = waiter.send(result);
1362 return;
1363 }
1364 }
1365
1366 {
1368 let mut q = self.inbound.lock().await;
1369 self.enqueue_pending_locked(&mut q, msg);
1370 }
1371 self.inbound_notify.notify_one();
1372 }
1373
1374 fn enqueue_pending_locked(&self, q: &mut VecDeque<Value>, ev: Value) {
1375 let max = self.max_inbound.load(Ordering::SeqCst);
1376 if q.len() < max {
1377 q.push_back(ev);
1378 return;
1379 }
1380 let mut drop_idx = None;
1382 let mut drop_pri = -1;
1383 for (i, item) in q.iter().enumerate() {
1384 let p = inbound_frame_drop_priority(item.as_object());
1385 if p > drop_pri {
1386 drop_pri = p;
1387 drop_idx = Some(i);
1388 }
1389 }
1390 let incoming_pri = inbound_frame_drop_priority(ev.as_object());
1391 if let Some(idx) = drop_idx {
1392 if drop_pri >= DROP_PRIORITY_NORMAL {
1393 q.remove(idx);
1394 q.push_back(ev);
1395 self.note_inbound_drop("priority_evict");
1396 return;
1397 }
1398 }
1399 if incoming_pri >= DROP_PRIORITY_NORMAL {
1400 self.note_inbound_drop("queue_full");
1401 return;
1402 }
1403 if !q.is_empty() {
1405 q.pop_front();
1406 self.note_inbound_drop("priority_evict");
1407 }
1408 q.push_back(ev);
1409 }
1410
1411 fn note_inbound_drop(&self, reason: &str) {
1412 let n = self.inbound_dropped.fetch_add(1, Ordering::SeqCst) + 1;
1413 if !self.degraded_notified.swap(true, Ordering::SeqCst) {
1414 if let Some(cb) = self.stream_degraded_cb.lock().expect("cb").as_ref() {
1415 cb(n, reason.to_string());
1416 }
1417 }
1418 }
1419}
1420
1421fn parse_daemon_error(msg: &Value) -> DaemonError {
1422 if let Some(err) = msg.get("error") {
1423 let code = err.get("code").and_then(|c| c.as_i64()).unwrap_or(-1);
1424 let message = err
1425 .get("message")
1426 .and_then(|m| m.as_str())
1427 .unwrap_or("daemon error")
1428 .to_string();
1429 let data = err.get("data").cloned();
1430 let mut de = DaemonError::new(code, message);
1431 if let Some(d) = data {
1432 de = de.with_data(d);
1433 }
1434 return de;
1435 }
1436 DaemonError::new(
1437 -1,
1438 msg.get("message")
1439 .and_then(|m| m.as_str())
1440 .unwrap_or("daemon error"),
1441 )
1442}
1443
1444pub use crate::protocol::unwrap_next as unwrap_next_frame;
1446
1447#[cfg(test)]
1448mod tests {
1449 use super::*;
1450
1451 #[test]
1452 fn send_input_options_default() {
1453 let o = SendInputOptions::default();
1454 assert!(!o.autonomous);
1455 assert!(o.loop_id.is_none());
1456 }
1457}