1use std::{
11 sync::{Arc, Mutex},
12 time::Duration,
13};
14
15use hyphae::{Gettable, Watchable};
16use myko::{
17 client::{ConnectionStatus, MykoClient},
18 command::{CommandContext, CommandHandlerRegistration},
19 query::QueryRegistration,
20 report::ReportRegistration,
21 request::RequestContext,
22 server::CellServerCtx,
23 view::ViewRegistration,
24 wire::{WrappedCommand, WrappedQuery, WrappedReport, WrappedView},
25};
26use serde_json::{Value, json};
27use tokio::sync::oneshot;
28use uuid::Uuid;
29
30const QUERY_TIMEOUT: Duration = Duration::from_secs(5);
31const REPORT_TIMEOUT: Duration = Duration::from_secs(5);
32const COMMAND_TIMEOUT: Duration = Duration::from_secs(10);
33const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
34
35#[derive(Clone)]
37pub enum Executor {
38 Client(Arc<MykoClient>),
40 InProcess(Arc<CellServerCtx>),
42}
43
44impl Executor {
45 pub async fn execute_query(&self, query_id: &str, args: Value) -> Result<Value, String> {
47 match self {
48 Executor::Client(client) => client_execute_query(client.clone(), query_id, args).await,
49 Executor::InProcess(ctx) => in_process_execute_query(ctx.clone(), query_id, args),
50 }
51 }
52
53 pub async fn execute_report(&self, report_id: &str, args: Value) -> Result<Value, String> {
55 match self {
56 Executor::Client(client) => {
57 client_execute_report(client.clone(), report_id, args).await
58 }
59 Executor::InProcess(ctx) => {
60 in_process_execute_report(ctx.clone(), report_id, args).await
61 }
62 }
63 }
64
65 pub async fn execute_view(&self, view_id: &str, args: Value) -> Result<Value, String> {
67 match self {
68 Executor::Client(client) => client_execute_view(client.clone(), view_id, args).await,
69 Executor::InProcess(ctx) => in_process_execute_view(ctx.clone(), view_id, args),
70 }
71 }
72
73 pub async fn execute_command(&self, command_id: &str, args: Value) -> Result<Value, String> {
75 match self {
76 Executor::Client(client) => {
77 client_execute_command(client.clone(), command_id, args).await
78 }
79 Executor::InProcess(ctx) => in_process_execute_command(ctx.clone(), command_id, args),
80 }
81 }
82
83 pub fn connection_status(&self, info: &super::dispatch::ServerInfo) -> Value {
87 match self {
88 Executor::Client(client) => {
89 let status = client.connection_status().get();
90 let text = match &status {
91 ConnectionStatus::Connected(addr) => format!("Connected to {}", addr),
92 ConnectionStatus::Connecting(addr) => format!("Connecting to {}", addr),
93 ConnectionStatus::Reconnecting(addr) => format!("Reconnecting to {}", addr),
94 ConnectionStatus::Idle => "Idle".to_string(),
95 ConnectionStatus::Disconnected => "Disconnected".to_string(),
96 };
97 json!({ "status": text, "name": info.name, "version": info.version })
98 }
99 Executor::InProcess(ctx) => json!({
100 "status": "In-process (always connected)",
101 "name": info.name,
102 "version": info.version,
103 "hostId": ctx.host_id,
104 }),
105 }
106 }
107}
108
109async fn client_execute_query(
114 client: Arc<MykoClient>,
115 query_id: &str,
116 arguments: Value,
117) -> Result<Value, String> {
118 for reg in inventory::iter::<QueryRegistration> {
119 if reg.query_id == query_id {
120 let tx = Uuid::new_v4().to_string();
121 let mut query_json = arguments_object(arguments);
122 if let Some(obj) = query_json.as_object_mut() {
123 obj.insert("tx".to_string(), json!(tx));
124 obj.insert(
125 "createdAt".to_string(),
126 json!(chrono::Utc::now().to_rfc3339()),
127 );
128 }
129
130 let wrapped = WrappedQuery {
131 query: query_json,
132 query_id: reg.query_id.into(),
133 query_item_type: reg.query_item_type.into(),
134 window: None,
135 };
136
137 let cell = client.watch_query_raw(wrapped);
138 let (result_tx, result_rx) = oneshot::channel::<Vec<Value>>();
139 let result_tx = Arc::new(Mutex::new(Some(result_tx)));
140 let seen_initial = Arc::new(Mutex::new(false));
141 let result_tx_sub = result_tx.clone();
142 let seen_initial_sub = seen_initial.clone();
143 let _guard = cell.subscribe(move |signal| {
144 if let hyphae::Signal::Value(items) = signal {
145 let mut seen = seen_initial_sub.lock().unwrap();
146 if !*seen {
147 *seen = true;
148 return;
149 }
150 if let Some(tx) = result_tx_sub.lock().unwrap().take() {
151 let _ = tx.send((**items).clone());
152 }
153 }
154 });
155
156 return match tokio::time::timeout(QUERY_TIMEOUT, result_rx).await {
157 Ok(Ok(items)) => Ok(json!({
158 "query_id": query_id,
159 "item_type": reg.query_item_type,
160 "count": items.len(),
161 "items": items,
162 })),
163 Ok(Err(_)) => Err("Query channel closed".to_string()),
164 Err(_) => Err("Timeout waiting for query response".to_string()),
165 };
166 }
167 }
168 Err(format!("Query not found: {}", query_id))
169}
170
171async fn client_execute_view(
172 client: Arc<MykoClient>,
173 view_id: &str,
174 arguments: Value,
175) -> Result<Value, String> {
176 for reg in inventory::iter::<ViewRegistration> {
177 if reg.view_id == view_id {
178 let tx = Uuid::new_v4().to_string();
179 let mut view_json = arguments_object(arguments);
180 if let Some(obj) = view_json.as_object_mut() {
181 obj.insert("tx".to_string(), json!(tx));
182 obj.insert(
183 "createdAt".to_string(),
184 json!(chrono::Utc::now().to_rfc3339()),
185 );
186 }
187
188 let wrapped = WrappedView {
189 view: view_json,
190 view_id: reg.view_id.into(),
191 view_item_type: reg.view_item_type.into(),
192 window: None,
193 };
194
195 let cell = client.watch_view_raw(wrapped);
196 let (result_tx, result_rx) = oneshot::channel::<Vec<Value>>();
197 let result_tx = Arc::new(Mutex::new(Some(result_tx)));
198 let seen_initial = Arc::new(Mutex::new(false));
199 let result_tx_sub = result_tx.clone();
200 let seen_initial_sub = seen_initial.clone();
201 let _guard = cell.subscribe(move |signal| {
202 if let hyphae::Signal::Value(items) = signal {
203 let mut seen = seen_initial_sub.lock().unwrap();
204 if !*seen {
205 *seen = true;
206 return;
207 }
208 if let Some(tx) = result_tx_sub.lock().unwrap().take() {
209 let _ = tx.send((**items).clone());
210 }
211 }
212 });
213
214 return match tokio::time::timeout(QUERY_TIMEOUT, result_rx).await {
215 Ok(Ok(items)) => Ok(json!({
216 "view_id": view_id,
217 "item_type": reg.view_item_type,
218 "count": items.len(),
219 "items": items,
220 })),
221 Ok(Err(_)) => Err("View channel closed".to_string()),
222 Err(_) => Err("Timeout waiting for view response".to_string()),
223 };
224 }
225 }
226 Err(format!("View not found: {}", view_id))
227}
228
229async fn client_execute_report(
230 client: Arc<MykoClient>,
231 report_id: &str,
232 arguments: Value,
233) -> Result<Value, String> {
234 for reg in inventory::iter::<ReportRegistration> {
235 if reg.report_id == report_id {
236 let tx = Uuid::new_v4().to_string();
237 let mut report_json = arguments_object(arguments);
238 if let Some(obj) = report_json.as_object_mut() {
239 obj.insert("tx".to_string(), json!(tx));
240 }
241
242 let wrapped = WrappedReport {
243 report: report_json,
244 report_id: reg.report_id.to_string(),
245 };
246
247 let cell = client.watch_report_raw(wrapped);
248 let (result_tx, result_rx) = oneshot::channel::<Value>();
249 let result_tx = Arc::new(Mutex::new(Some(result_tx)));
250 let _guard = cell.subscribe(move |signal| {
251 if let hyphae::Signal::Value(value_opt) = signal
252 && let Some(value) = &**value_opt
253 && let Some(tx) = result_tx.lock().unwrap().take()
254 {
255 let _ = tx.send(value.clone());
256 }
257 });
258
259 return match tokio::time::timeout(REPORT_TIMEOUT, result_rx).await {
260 Ok(Ok(value)) => Ok(json!({
261 "report_id": report_id,
262 "output_type": reg.output_type,
263 "result": value,
264 })),
265 Ok(Err(_)) => Err("Report channel closed".to_string()),
266 Err(_) => Err("Timeout waiting for report response".to_string()),
267 };
268 }
269 }
270 Err(format!("Report not found: {}", report_id))
271}
272
273async fn client_execute_command(
274 client: Arc<MykoClient>,
275 command_id: &str,
276 arguments: Value,
277) -> Result<Value, String> {
278 let status = client.connection_status().get();
279 if !matches!(status, ConnectionStatus::Connected(_)) {
280 let (tx_connected, rx_connected) = oneshot::channel::<bool>();
281 let tx_connected = Mutex::new(Some(tx_connected));
282 let guard = client.connection_status().subscribe(move |signal| {
283 if let hyphae::Signal::Value(status) = signal
284 && let ConnectionStatus::Connected(_) = &**status
285 && let Some(sender) = tx_connected.lock().unwrap().take()
286 {
287 let _ = sender.send(true);
288 }
289 });
290
291 let connected = tokio::time::timeout(CONNECT_TIMEOUT, rx_connected)
292 .await
293 .unwrap_or(Ok(false))
294 .unwrap_or(false);
295 drop(guard);
296
297 if !connected {
298 return Err("Not connected to Myko server".to_string());
299 }
300 }
301
302 let tx = Uuid::new_v4().to_string();
303 let mut command_json = arguments_object(arguments);
304 if let Some(obj) = command_json.as_object_mut() {
305 obj.insert("tx".to_string(), json!(tx));
306 }
307
308 let wrapped = WrappedCommand {
309 command: command_json,
310 command_id: command_id.to_string(),
311 };
312
313 let result_cell = client.send_command_raw_result(wrapped);
314 let (resp_tx, resp_rx) = oneshot::channel::<Result<Value, String>>();
315 let resp_tx = Arc::new(Mutex::new(Some(resp_tx)));
316 let _guard = result_cell.subscribe(move |signal| {
317 if let hyphae::Signal::Value(result_opt) = signal
318 && let Some(result) = &**result_opt
319 && let Some(sender) = resp_tx.lock().unwrap().take()
320 {
321 let _ = sender.send(result.clone());
322 }
323 });
324
325 match tokio::time::timeout(COMMAND_TIMEOUT, resp_rx).await {
326 Ok(Ok(Ok(response))) => Ok(json!({
327 "command_id": command_id,
328 "success": true,
329 "result": response,
330 })),
331 Ok(Ok(Err(e))) => Err(e),
332 _ => Err("Timeout waiting for response".to_string()),
333 }
334}
335
336fn in_process_execute_query(
341 ctx: Arc<CellServerCtx>,
342 query_id: &str,
343 arguments: Value,
344) -> Result<Value, String> {
345 let registration = inventory::iter::<QueryRegistration>
346 .into_iter()
347 .find(|r| r.query_id == query_id)
348 .ok_or_else(|| format!("Query not found: {}", query_id))?;
349
350 let query_data = ctx
351 .handler_registry
352 .get_query(query_id)
353 .ok_or_else(|| format!("Query handler not registered: {}", query_id))?;
354
355 let mut query_json = arguments_object(arguments);
356 let tx: Arc<str> = Uuid::new_v4().to_string().into();
357 if let Some(obj) = query_json.as_object_mut() {
358 obj.insert("tx".to_string(), json!(tx.as_ref()));
359 obj.insert(
360 "createdAt".to_string(),
361 json!(chrono::Utc::now().to_rfc3339()),
362 );
363 }
364
365 let parsed = (query_data.parse)(query_json)
366 .map_err(|e| format!("Failed to parse query {}: {}", query_id, e))?;
367
368 let request_context = Arc::new(RequestContext::internal(tx, ctx.host_id, "mcp"));
369
370 let cellmap = (query_data.cell_factory)(
371 parsed,
372 ctx.registry.clone(),
373 request_context,
374 Some(ctx.clone()),
375 )
376 .map_err(|e| format!("Failed to build query cell: {}", e))?;
377
378 let items: Vec<Value> = cellmap
379 .snapshot()
380 .into_iter()
381 .map(|(_, item)| serde_json::to_value(&*item).unwrap_or(Value::Null))
382 .collect();
383
384 Ok(json!({
385 "query_id": query_id,
386 "item_type": registration.query_item_type,
387 "count": items.len(),
388 "items": items,
389 }))
390}
391
392fn in_process_execute_view(
393 ctx: Arc<CellServerCtx>,
394 view_id: &str,
395 arguments: Value,
396) -> Result<Value, String> {
397 let registration = inventory::iter::<ViewRegistration>
398 .into_iter()
399 .find(|r| r.view_id == view_id)
400 .ok_or_else(|| format!("View not found: {}", view_id))?;
401
402 let view_data = ctx
403 .handler_registry
404 .get_view(view_id)
405 .ok_or_else(|| format!("View handler not registered: {}", view_id))?;
406
407 let mut view_json = arguments_object(arguments);
408 let tx: Arc<str> = Uuid::new_v4().to_string().into();
409 if let Some(obj) = view_json.as_object_mut() {
410 obj.insert("tx".to_string(), json!(tx.as_ref()));
411 obj.insert(
412 "createdAt".to_string(),
413 json!(chrono::Utc::now().to_rfc3339()),
414 );
415 }
416
417 let parsed = (view_data.parse)(view_json)
418 .map_err(|e| format!("Failed to parse view {}: {}", view_id, e))?;
419
420 let request_context = Arc::new(RequestContext::internal(tx, ctx.host_id, "mcp"));
421
422 let cellmap =
423 (view_data.cell_factory)(parsed, ctx.registry.clone(), request_context, ctx.clone())
424 .map_err(|e| format!("Failed to build view cell: {}", e))?;
425
426 let items: Vec<Value> = cellmap
427 .snapshot()
428 .into_iter()
429 .map(|(_, item)| serde_json::to_value(&*item).unwrap_or(Value::Null))
430 .collect();
431
432 Ok(json!({
433 "view_id": view_id,
434 "item_type": registration.view_item_type,
435 "count": items.len(),
436 "items": items,
437 }))
438}
439
440async fn in_process_execute_report(
441 ctx: Arc<CellServerCtx>,
442 report_id: &str,
443 arguments: Value,
444) -> Result<Value, String> {
445 let registration = inventory::iter::<ReportRegistration>
446 .into_iter()
447 .find(|r| r.report_id == report_id)
448 .ok_or_else(|| format!("Report not found: {}", report_id))?;
449
450 let report_data = ctx
451 .handler_registry
452 .get_report(report_id)
453 .ok_or_else(|| format!("Report handler not registered: {}", report_id))?;
454
455 let mut report_json = arguments_object(arguments);
456 let tx: Arc<str> = Uuid::new_v4().to_string().into();
457 if let Some(obj) = report_json.as_object_mut() {
458 obj.insert("tx".to_string(), json!(tx.as_ref()));
459 }
460
461 let parsed = (report_data.parse)(report_json)
462 .map_err(|e| format!("Failed to parse report {}: {}", report_id, e))?;
463
464 let request_context = Arc::new(RequestContext::internal(tx, ctx.host_id, "mcp"));
465
466 let cell = (report_data.cell_factory)(parsed, request_context, ctx)
467 .map_err(|e| format!("Failed to build report cell: {}", e))?;
468
469 let (tx_resp, rx_resp) = oneshot::channel::<Value>();
471 let tx_resp = Arc::new(Mutex::new(Some(tx_resp)));
472 let tx_resp_sub = tx_resp.clone();
473 let _guard = cell.subscribe(move |signal| {
474 if let hyphae::Signal::Value(output) = signal
475 && let Some(sender) = tx_resp_sub.lock().unwrap().take()
476 {
477 let _ = sender.send(output.to_value());
478 }
479 });
480
481 match tokio::time::timeout(REPORT_TIMEOUT, rx_resp).await {
482 Ok(Ok(value)) => Ok(json!({
483 "report_id": report_id,
484 "output_type": registration.output_type,
485 "result": value,
486 })),
487 Ok(Err(_)) => Err("Report cell dropped before emitting".to_string()),
488 Err(_) => Err("Timeout waiting for report value".to_string()),
489 }
490}
491
492fn in_process_execute_command(
493 ctx: Arc<CellServerCtx>,
494 command_id: &str,
495 arguments: Value,
496) -> Result<Value, String> {
497 let mut command_json = arguments_object(arguments);
498 let tx: Arc<str> = Uuid::new_v4().to_string().into();
499 if let Some(obj) = command_json.as_object_mut() {
500 obj.insert("tx".to_string(), json!(tx.as_ref()));
501 }
502
503 for registration in inventory::iter::<CommandHandlerRegistration> {
504 if registration.command_id == command_id {
505 let executor = (registration.factory)();
506 let req = Arc::new(RequestContext::internal(tx.clone(), ctx.host_id, "mcp"));
507 let cmd_id: Arc<str> = Arc::from(command_id);
508 let cmd_ctx = CommandContext::new(cmd_id, req, ctx.clone());
509
510 return match executor.execute_from_value(command_json, cmd_ctx) {
511 Ok(result) => Ok(json!({
512 "command_id": command_id,
513 "success": true,
514 "result": result,
515 })),
516 Err(err) => Err(err.message),
517 };
518 }
519 }
520
521 Err(format!("Command handler not found: {}", command_id))
522}
523
524fn arguments_object(arguments: Value) -> Value {
529 if arguments.is_object() {
530 arguments
531 } else {
532 json!({})
533 }
534}