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
34 changes: 30 additions & 4 deletions src/tui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { useServiceBus } 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 { readFileSync, existsSync } from "fs";
import { join } from "path";
import { homedir } from "os";
import { createHash } from "crypto";
Comment on lines +10 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Use the fixed polling endpoint instead of cache-file port discovery.

This adds ~/.cache/.../status-port-*.json discovery and makes polling conditional on a one-time cache hit. That conflicts with the fixed port contract and can still leave the TUI without HTTP polling when the file is missing or stale at mount. Prefer wiring the fixed localhost endpoint; if the status server is not already bound to 4099, update that side in the same stack.

Proposed fix
-import { readFileSync, existsSync } from "fs";
-import { join } from "path";
-import { homedir } from "os";
-import { createHash } from "crypto";
@@
-  // Discover the server's HTTP status port from the cache file
-  const discoverPollEndpoint = (): string | undefined => {
-    try {
-      const scopes = [props.sessionId, props.api.state.path.directory].filter(Boolean) as string[];
-      for (const scope of scopes) {
-        const hash = createHash("sha256").update(scope).digest("hex").slice(0, 12);
-        const portFile = join(homedir(), ".cache", "opencode", "brain", `status-port-${hash}.json`);
-        if (existsSync(portFile)) {
-          const data = JSON.parse(readFileSync(portFile, "utf-8"));
-          if (data.port) return `http://127.0.0.1:${data.port}`;
-        }
-      }
-    } catch { /* port file not found or not ready yet */ }
-    return undefined;
-  };
-
-  const pollEndpoint = discoverPollEndpoint();
+  const pollEndpoint = "http://127.0.0.1:4099";
@@
-    pollEndpoint ? { pollEndpoint } : undefined,
+    { pollEndpoint },
   );

As per coding guidelines, Set fixed bus port to 4099 (no port discovery or port.json configuration).

Also applies to: 82-107

Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tui.tsx` around lines 10 - 13, Replace the cache-file based port
discovery mechanism (that reads from ~/.cache/.../status-port-*.json files) with
a fixed port configuration. Remove the file-reading logic that uses readFileSync
and existsSync for port discovery and instead configure the HTTP polling to use
a fixed localhost endpoint on port 4099. Update any references to dynamically
discovered ports throughout the code (particularly in the polling logic around
lines 82-107) to use this fixed port instead. Remove any imports that are no
longer needed after eliminating the file-based discovery, such as readFileSync,
existsSync, join, homedir, and createHash if they're not used elsewhere.

Source: Coding guidelines


/**
* Derives a stable project ID from a directory path.
Expand Down Expand Up @@ -73,12 +77,34 @@ function BrainStatusBar(props: { variant: "sidebar" | "home"; api: TuiPluginApi;
setHasError(true);
}

};
};

// Discover the server's HTTP status port from the cache file
const discoverPollEndpoint = (): string | undefined => {
try {
const scopes = [props.sessionId, props.api.state.path.directory].filter(Boolean) as string[];
for (const scope of scopes) {
const hash = createHash("sha256").update(scope).digest("hex").slice(0, 12);
const portFile = join(homedir(), ".cache", "opencode", "brain", `status-port-${hash}.json`);
if (existsSync(portFile)) {
const data = JSON.parse(readFileSync(portFile, "utf-8"));
if (data.port) return `http://127.0.0.1:${data.port}`;
}
}
} catch { /* port file not found or not ready yet */ }
return undefined;
};

const pollEndpoint = discoverPollEndpoint();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: pollEndpoint is resolved only once at mount, so late creation of the status-port file leaves polling fallback permanently disabled. This can keep the UI stuck on "connecting…" in the same race this PR is fixing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tui.tsx, line 98:

<comment>`pollEndpoint` is resolved only once at mount, so late creation of the status-port file leaves polling fallback permanently disabled. This can keep the UI stuck on "connecting…" in the same race this PR is fixing.</comment>

<file context>
@@ -73,12 +77,34 @@ function BrainStatusBar(props: { variant: "sidebar" | "home"; api: TuiPluginApi;
+    return undefined;
+  };
+
+  const pollEndpoint = discoverPollEndpoint();
 
   // Reactive bus subscription — project-scoped (brain status is project-wide, not session-scoped).
</file context>


// Reactive bus subscription — project-scoped (brain status is project-wide, not session-scoped).
useServiceBus("brain", () => deriveProjectId(props.api.state.path.directory), "status", (payload) => {
handleStatus(payload as BrainStatusEvent);
});
useServiceBus(
"brain",
() => deriveProjectId(props.api.state.path.directory),
"status",
(payload) => { handleStatus(payload as BrainStatusEvent); },
pollEndpoint ? { pollEndpoint } : undefined,
);

const indicatorColor = () => connecting() ? theme().error : (hasError() ? theme().error : fg());
const textColor = () => connecting() ? theme().error : theme().textMuted;
Expand Down
Loading