Skip to content
Merged
Show file tree
Hide file tree
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
11 changes: 3 additions & 8 deletions src/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@ export function setSessionId(id: string): void {
_channel = `brain/${id}`;
}

export function setSessionId(id: string): void {
if (id === _sessionId) return;
_sessionId = id;
_channel = `brain/${id}`;
}

export function initStatus(client: PluginInput["client"], directory: string): void {
_client = client;
startStatusServer(directory);
Expand Down Expand Up @@ -91,9 +85,10 @@ export function startStatusServer(directory: string): void {

_port = _server.port;

// Write port to session-scoped discovery file
// Write port to session-scoped discovery file (session id when known, else directory)
try {
const hash = createHash("md5").update(directory).digest("hex").slice(0, 12);
const scope = _sessionId || directory;
const hash = createHash("sha256").update(scope).digest("hex").slice(0, 12);
const portFile = join(homedir(), ".cache", "opencode", "brain", `status-port-${hash}.json`);
const dir = portFile.replace(/\/[^/]+$/, "");
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
Expand Down
57 changes: 8 additions & 49 deletions src/tui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import { BusTui } from "@four-bytes/opencode-plugin-lib/tui";
import { ProgressBar } from "@four-bytes/opencode-plugin-lib/tui-components";
import type { BrainStatusEvent } from "./event-bus";
import { Spinner } from "./spinner";
import { createHash } from "crypto";
import { homedir } from "os";
import { join } from "path";

function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionId?: string }) {
const [indicator, setIndicator] = createSignal("•");
const [status, setStatus] = createSignal("connecting…");
Expand Down Expand Up @@ -66,29 +62,26 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI
};

onMount(() => {
// bus is a signal so createEffect below can react when BusTui resolves.
// 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?.();
sessionUnsub?.();
bus()?.close();
if (timer) clearInterval(timer);
});

// Real-time WebSocket subscription via plugin bus
// Real-time WebSocket subscription via plugin bus.
// HTTP fallback removed — bus is the only transport; status server stays
// for any external probes (e.g. /status endpoint) but TUI never polls it.
BusTui.connect()
.then((b) => {
if (unmounted) { b.close(); return; }
setBus(b);
busConnected = true;
if (timer) { clearInterval(timer); timer = null; }
// 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
Expand All @@ -99,8 +92,6 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI
})
.catch((err) => {
console.warn("[brain TUI] BusTui connect failed:", (err as Error).message);
busConnected = false;
if (!timer) timer = setInterval(poll, 200);
});

// React to props.sessionId changes — sessionId is set later by the host
Expand All @@ -115,42 +106,10 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI
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);
const portFile = join(homedir(), ".cache", "opencode", "brain", `status-port-${hash}.json`);

const resolvePort = async () => {
try {
const file = Bun.file(portFile);
if (!(await file.exists())) return;
const data = await file.json();
if (data.port) {
statusUrl = `http://127.0.0.1:${data.port}/status`;
}
} catch { /* port file not ready yet */ }
};

const poll = async () => {
if (!statusUrl) {
await resolvePort();
if (!statusUrl) return;
}
try {
const res = await fetch(statusUrl);
if (!res.ok) return;
const data = await res.json() as BrainStatusEvent;
handleStatus(data);
} catch { /* server not ready */ }
};

poll();
timer = setInterval(poll, 200);
});

const StatusRow = () => (
<box flexDirection="row">
<box width="100%" flexDirection="row">
<text fg={theme().textMuted}>🧠 {version()} </text>
{busy() ? <Spinner fg={fg()} /> : <text fg={connecting() ? theme().error : fg()}>{indicator()}</text>}
<text fg={connecting() ? theme().error : theme().textMuted}> {connecting() ? "connecting…" : status()}</text>
Expand All @@ -164,7 +123,7 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI
);

return (
<box>
<box width="100%">
{props.centered ? (
<box>
<text> </text>
Expand All @@ -175,12 +134,12 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionI
</box>
</box>
) : (
<box>
<box width="100%">
<box flexDirection="row">
<text fg={theme().text}><b>Brain</b></text>
<text fg={theme().textMuted}> 🧠 {version()}</text>
</box>
<box flexDirection="row">
<box width="100%" flexDirection="row">
{busy() ? <Spinner fg={fg()} /> : <text fg={connecting() ? theme().error : fg()}>{indicator()}</text>}
<text fg={connecting() ? theme().error : theme().textMuted}> {connecting() ? "connecting…" : status()}</text>
{busy() && current() > 0 && total() > 0 && (
Expand Down
Loading