Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions src/tui.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @jsxImportSource @opentui/solid */

import { createSignal, onMount, onCleanup } from "solid-js";
import { createSignal, createEffect, onMount, onCleanup } from "solid-js";
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui";
import type { RGBA } from "@opentui/core";
import { BusTui } from "@four-bytes/opencode-plugin-lib/tui";
Expand Down Expand Up @@ -66,28 +66,34 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI
};

onMount(() => {
let bus: BusTui | null = null;
// bus is a signal so createEffect below can react when BusTui resolves.
const [bus, setBus] = createSignal<BusTui | null>(null);
let unsub: (() => void) | null = null;
let sessionUnsub: (() => void) | null = null;
let timer: ReturnType<typeof setInterval> | null = null;
let unmounted = false;
let busConnected = false;

onCleanup(() => {
unmounted = true;
unsub?.();
bus?.close();
sessionUnsub?.();
bus()?.close();
if (timer) clearInterval(timer);
});

// Real-time WebSocket subscription via plugin bus
BusTui.connect()
.then((b) => {
if (unmounted) { b.close(); return; }
bus = b;
setBus(b);
busConnected = true;
if (timer) { clearInterval(timer); timer = null; }
const channel = props.sessionId ? `brain/${props.sessionId}` : "brain/status";
unsub = b.subscribe(channel, (envelope) => {
// Always subscribe to brain/status — server publishes here during ingest,
// before any chat message creates a session. Once sessionId is known,
// also subscribe to the per-session channel (server switches to it
// after first chat.message). Prevents missing pre-session status updates.
unsub = b.subscribe("brain/status", (envelope) => {
handleStatus(envelope.payload as BrainStatusEvent);
});
})
Expand All @@ -97,6 +103,19 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI
if (!timer) timer = setInterval(poll, 200);
});

// React to props.sessionId changes — sessionId is set later by the host
// (after first chat.message), so it is often undefined at mount time.
// Tracks bus() so the effect re-runs once BusTui.connect() resolves.
createEffect(() => {
const sid = props.sessionId;
const b = bus();
sessionUnsub?.();
if (!sid || !b) return;
sessionUnsub = b.subscribe(`brain/${sid}`, (envelope) => {
handleStatus(envelope.payload as BrainStatusEvent);
});
});

// HTTP fallback for when bus is unavailable (cross-process)
let statusUrl = "";
const hash = createHash("md5").update(props.api.state.path.directory).digest("hex").slice(0, 12);
Expand Down
Loading