From 595f68e2526c8dae4e8bc2a810f671bcdcc9f018 Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 8 May 2026 15:08:03 +0000 Subject: [PATCH] =?UTF-8?q?feat(yahweh-shell):=20primer=20m=C3=B3dulo=20br?= =?UTF-8?q?ahman=20vivo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit yahweh-shell se presenta al Init brahman como módulo Widget mediante un sidecar en thread aparte. La GUI GPUI levanta normalmente; el sidecar mantiene la sesión brahman en paralelo, desacoplado. Cambios: - crates/apps/yahweh-shell/Cargo.toml: deps brahman-card, brahman-handshake, ulid. - crates/apps/yahweh-shell/src/brahman_client.rs: thread con tokio current_thread runtime que arma una Card, llama Client::connect, y loop de pings cada 30s. Si el Init no está disponible, loggea y termina — yahweh sigue funcionando standalone. - crates/apps/yahweh-shell/src/main.rs: brahman_client::spawn() antes de Application::new(). El spawn no bloquea. Card declarada por yahweh: - label: "brahman.ui_engine" - lifecycle: Widget - payload: Virtual (yahweh no se inicia desde el Init, se presenta) - supervision: Delegate - permissions: filesystem read-write (persiste layout.json), IPC wit-v1 - flow.input: render-data (json) - flow.output: user-intent (json) Validación end-to-end: $ ente-zero & $ probe → session=...8G, init_attached=true $ yahweh → [brahman] attached: session=...Y7 Ambos clientes (probe + yahweh sidecar) se registran en el broker del Init en sesiones distintas. yahweh es el primer módulo "real" — no un tester — que vive como nodo del fractal mientras corre. Tests: 27/27 verdes. cargo check --workspace: 0 errores. Co-Authored-By: Claude Opus 4.7 (1M context) --- Cargo.lock | 3 + crates/apps/yahweh-shell/Cargo.toml | 5 + .../apps/yahweh-shell/src/brahman_client.rs | 121 ++++++++++++++++++ crates/apps/yahweh-shell/src/main.rs | 5 + 4 files changed, 134 insertions(+) create mode 100644 crates/apps/yahweh-shell/src/brahman_client.rs diff --git a/Cargo.lock b/Cargo.lock index 2c2e0b1..ae516db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11439,11 +11439,14 @@ dependencies = [ name = "yahweh-shell" version = "0.1.0" dependencies = [ + "brahman-card", + "brahman-handshake", "gpui", "notify", "serde", "serde_json", "tokio", + "ulid", "yahweh-bus", "yahweh-core", "yahweh-database-explorer", diff --git a/crates/apps/yahweh-shell/Cargo.toml b/crates/apps/yahweh-shell/Cargo.toml index 3d66cae..f42a371 100644 --- a/crates/apps/yahweh-shell/Cargo.toml +++ b/crates/apps/yahweh-shell/Cargo.toml @@ -26,6 +26,11 @@ serde = { workspace = true } serde_json = { workspace = true } notify = { workspace = true } +# Brahman protocol — sidecar thread que se presenta al Init. +brahman-card = { path = "../../core/brahman-card" } +brahman-handshake = { path = "../../core/brahman-handshake" } +ulid = { workspace = true } + [[bin]] name = "yahweh" path = "src/main.rs" diff --git a/crates/apps/yahweh-shell/src/brahman_client.rs b/crates/apps/yahweh-shell/src/brahman_client.rs new file mode 100644 index 0000000..cc87c90 --- /dev/null +++ b/crates/apps/yahweh-shell/src/brahman_client.rs @@ -0,0 +1,121 @@ +//! 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() + } +} diff --git a/crates/apps/yahweh-shell/src/main.rs b/crates/apps/yahweh-shell/src/main.rs index 9a31784..8afbea6 100644 --- a/crates/apps/yahweh-shell/src/main.rs +++ b/crates/apps/yahweh-shell/src/main.rs @@ -6,6 +6,7 @@ //! eventos tipados de los explorers (FileExplorer, DatabaseExplorer) //! traducidos a AppEvent. +mod brahman_client; mod hot_reload; mod layout_host; mod layout_model; @@ -26,6 +27,10 @@ use crate::persister::Persister; const LAYOUT_PATH: &str = "layout.json"; fn main() { + // Sidecar brahman: yahweh se presenta al Init antes de levantar GPUI. + // No bloquea: si el Init no está, el thread loggea y termina. + brahman_client::spawn(); + Application::new().run(|cx: &mut App| { Theme::install_default(cx);