//! Sidecar brahman: yahweh se presenta al Init como módulo `Widget`. //! //! Vive en un thread aparte con tokio runtime current_thread, desacoplado //! de GPUI. Si el Init no está disponible, loggea y termina — yahweh //! sigue funcionando standalone. Si conecta, mantiene la sesión viva //! con pings periódicos hasta que la GUI termine o el server caiga. //! //! Card declarada: //! - label: `brahman.ui_engine` //! - lifecycle: `Widget` //! - flow.input: `render-data` (json) //! - flow.output: `user-intent` (json) //! - permissions: filesystem read-write (yahweh persiste `layout.json`), //! IPC `wit-v1`. use std::collections::BTreeSet; use std::time::Duration; use brahman_card::{ Card, Flow, Flows, FsPolicy, IpcPolicy, Lifecycle, Payload, Permissions, Priority, Supervision, TypeRef, CARD_SCHEMA_VERSION, }; use brahman_handshake::{client::Client, transport}; use ulid::Ulid; /// Período entre pings al Init. const PING_INTERVAL: Duration = Duration::from_secs(30); /// Spawn del sidecar brahman. No-op si el thread no se puede crear. /// Devuelve inmediatamente; la conexión se establece en background. pub fn spawn() { let result = std::thread::Builder::new() .name("brahman-client".into()) .spawn(run_thread); if let Err(e) = result { eprintln!("[brahman] no se pudo spawnear el sidecar: {e}"); } } fn run_thread() { let rt = match tokio::runtime::Builder::new_current_thread() .enable_io() .enable_time() .build() { Ok(rt) => rt, Err(e) => { eprintln!("[brahman] tokio runtime falló: {e}"); return; } }; rt.block_on(run_client()); } async fn run_client() { let path = transport::default_socket_path(); let card = build_card(); let mut client = match Client::connect(&path, card).await { Ok(c) => { eprintln!( "[brahman] attached: session={} init_attached={} server={}", c.session(), c.server_info().init_attached, c.server_info().server_version ); c } Err(e) => { eprintln!("[brahman] no conectado a {} ({e})", path.display()); return; } }; loop { tokio::time::sleep(PING_INTERVAL).await; if let Err(e) = client.ping().await { eprintln!("[brahman] ping falló: {e}"); return; } } } fn build_card() -> Card { Card { schema_version: CARD_SCHEMA_VERSION, id: Ulid::new(), lineage: None, label: "brahman.ui_engine".into(), provides: BTreeSet::new(), requires: BTreeSet::new(), payload: Payload::Virtual, supervision: Supervision::Delegate, lifecycle: Lifecycle::Widget, priority: Priority::Normal, permissions: Permissions { filesystem: FsPolicy::ReadWrite, ipc: IpcPolicy { allow: vec!["wit-v1".into()], }, ..Default::default() }, flow: Flows { input: vec![Flow { name: "render-data".into(), ty: TypeRef::Primitive { name: "json".into(), }, pin_to: None, }], output: vec![Flow { name: "user-intent".into(), ty: TypeRef::Primitive { name: "json".into(), }, pin_to: None, }], }, ..Default::default() } }