⎇ 595f68e2526c8dae4e8bc2a810f671bcdcc9f018
crates/apps/yahweh-shell/src/brahman_client.rs
Historia
Quién tocó qué
crudo
feat(yahweh-shell): primer módulo brahman vivo
1//! Sidecar brahman: yahweh se presenta al Init como módulo `Widget`.
2//!
3//! Vive en un thread aparte con tokio runtime current_thread, desacoplado
4//! de GPUI. Si el Init no está disponible, loggea y termina — yahweh
5//! sigue funcionando standalone. Si conecta, mantiene la sesión viva
6//! con pings periódicos hasta que la GUI termine o el server caiga.
7//!
8//! Card declarada:
9//! - label: `brahman.ui_engine`
10//! - lifecycle: `Widget`
11//! - flow.input: `render-data` (json)
12//! - flow.output: `user-intent` (json)
13//! - permissions: filesystem read-write (yahweh persiste `layout.json`),
14//! IPC `wit-v1`.
15
16use std::collections::BTreeSet;
17use std::time::Duration;
18
19use brahman_card::{
20 Card, Flow, Flows, FsPolicy, IpcPolicy, Lifecycle, Payload, Permissions, Priority, Supervision,
21 TypeRef, CARD_SCHEMA_VERSION,
22};
23use brahman_handshake::{client::Client, transport};
24use ulid::Ulid;
25
26/// Período entre pings al Init.
27const PING_INTERVAL: Duration = Duration::from_secs(30);
28
29/// Spawn del sidecar brahman. No-op si el thread no se puede crear.
30/// Devuelve inmediatamente; la conexión se establece en background.
31pub fn spawn() {
32 let result = std::thread::Builder::new()
33 .name("brahman-client".into())
34 .spawn(run_thread);
35 if let Err(e) = result {
36 eprintln!("[brahman] no se pudo spawnear el sidecar: {e}");
37 }
38}
39
40fn run_thread() {
41 let rt = match tokio::runtime::Builder::new_current_thread()
42 .enable_io()
43 .enable_time()
44 .build()
45 {
46 Ok(rt) => rt,
47 Err(e) => {
48 eprintln!("[brahman] tokio runtime falló: {e}");
49 return;
50 }
51 };
52 rt.block_on(run_client());
53}
54
55async fn run_client() {
56 let path = transport::default_socket_path();
57 let card = build_card();
58
59 let mut client = match Client::connect(&path, card).await {
60 Ok(c) => {
61 eprintln!(
62 "[brahman] attached: session={} init_attached={} server={}",
63 c.session(),
64 c.server_info().init_attached,
65 c.server_info().server_version
66 );
67 c
68 }
69 Err(e) => {
70 eprintln!("[brahman] no conectado a {} ({e})", path.display());
71 return;
72 }
73 };
74
75 loop {
76 tokio::time::sleep(PING_INTERVAL).await;
77 if let Err(e) = client.ping().await {
78 eprintln!("[brahman] ping falló: {e}");
79 return;
80 }
81 }
82}
83
84fn build_card() -> Card {
85 Card {
86 schema_version: CARD_SCHEMA_VERSION,
87 id: Ulid::new(),
88 lineage: None,
89 label: "brahman.ui_engine".into(),
90 provides: BTreeSet::new(),
91 requires: BTreeSet::new(),
92 payload: Payload::Virtual,
93 supervision: Supervision::Delegate,
94 lifecycle: Lifecycle::Widget,
95 priority: Priority::Normal,
96 permissions: Permissions {
97 filesystem: FsPolicy::ReadWrite,
98 ipc: IpcPolicy {
99 allow: vec!["wit-v1".into()],
100 },
101 ..Default::default()
102 },
103 flow: Flows {
104 input: vec![Flow {
105 name: "render-data".into(),
106 ty: TypeRef::Primitive {
107 name: "json".into(),
108 },
109 pin_to: None,
110 }],
111 output: vec![Flow {
112 name: "user-intent".into(),
113 ty: TypeRef::Primitive {
114 name: "json".into(),
115 },
116 pin_to: None,
117 }],
118 },
119 ..Default::default()
120 }
121}