Skip to content

feat: migrate to scoped bus API (forService/forSession)#145

Merged
four-bytes-robby merged 1 commit into
mainfrom
feat/scoped-bus-migration
Jun 14, 2026
Merged

feat: migrate to scoped bus API (forService/forSession)#145
four-bytes-robby merged 1 commit into
mainfrom
feat/scoped-bus-migration

Conversation

@four-bytes-robby

@four-bytes-robby four-bytes-robby commented Jun 14, 2026

Copy link
Copy Markdown
Member

Replaces manual channel string building with scoped bus API. Removes session ID filter — no longer needed.


Summary by cubic

Migrated brain status publishing and TUI subscriptions to the scoped bus API (forService/forSession), removing manual channel strings and cross-session filtering. This improves session isolation and simplifies event routing.

  • Refactors

    • Status publishing: use AsyncLocalStorage to bind session IDs per tool run and publish via bus.forService("brain").forSession(sid).publish("status") (unscoped when no session); removed _channel and session filter.
    • TUI: subscribe with BusTui.forService("brain") and optional .forSession(sessionId) on status; removed duplicate subscriptions and filters; added variant prop for sidebar/home, with cleaner indicators and compact progress.
  • Dependencies

    • Bumped @four-bytes/opencode-plugin-lib to v0.6.0 for scoped bus support.

Written for commit cf51a8f. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added session-scoped real-time status publishing capability.
  • Improvements

    • Refactored status bar UI with variant-driven layouts for sidebar and home views.
    • Simplified display logic with improved status indicators and progress visibility.
  • Chores

    • Updated plugin library dependency to version 0.6.0.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Walkthrough

src/status.ts adds AsyncLocalStorage-backed session scoping: a new withSessionId helper wraps async work in an ALS context, and write() now reads the session id from ALS to publish via forService("brain")/forSession(sid) instead of a global channel variable. src/tui.tsx refactors BrainStatusBar from a centered prop to a variant: "sidebar" | "home" prop, replaces pulse/indicator logic with signal helpers, and mirrors the scoped subscription model. package.json bumps @four-bytes/opencode-plugin-lib to v0.6.0.

Changes

Session-scoped status publishing and variant-driven TUI

Layer / File(s) Summary
ALS session context and scoped write() publishing
src/status.ts
Imports AsyncLocalStorage, introduces _sessionAls and exported withSessionId<T>, removes the old _channel assignment, and rewrites write() to derive sid from ALS and publish through forService("brain") with optional forSession(sid) scoping.
BrainStatusBar variant rendering and scoped subscriptions
src/tui.tsx
Replaces centered prop with variant: "sidebar" | "home", removes pulse/indicator/percent logic, adds connecting, showProgress, and indicatorColor signal helpers, and switches bus subscriptions to forService("brain")/forSession(sessionId). Updates slot wiring to pass variant and sessionId.
plugin-lib dependency bump
package.json
Bumps @four-bytes/opencode-plugin-lib from v0.5.1 to v0.6.0.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • four-bytes/four-opencode-brain#135: Both PRs modify src/tui.tsx's bus subscription logic around choosing between a global brain/status channel and a session-scoped brain/<sessionId> channel.
  • four-bytes/four-opencode-brain#141: Both PRs touch the status-transport layer in src/tui.tsx, with #141 removing HTTP polling in favor of bus-based updates and this PR tightening the scoped subscription model further.
Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description covers the key changes and includes a cubic summary, but the required checklist items (Conventional commit format, bun test, bun run build, version bump, HISTORY.md update, no personal paths) are not explicitly addressed. Confirm that all checklist items have been completed: verify conventional commit format, bun test passes, bun run build succeeds, package.json version was bumped, HISTORY.md was updated if needed, and no personal paths were committed.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: migrating to the scoped bus API (forService/forSession) across status publishing and TUI subscriptions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Tip: You can configure your own custom pre-merge checks in the settings.

Finishing Touches
Generate docstrings
  • Create stacked PR
  • Commit on current branch
Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/scoped-bus-migration
Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/scoped-bus-migration

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Prompt for all review comments with 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.

Inline comments:
In `@src/status.ts`:
- Line 135: The `BrainStatusEvent` interface does not include a `sessionId`
field, but the payload construction in the status module is attempting to add
`sessionId` and casting it to `BrainStatusEvent`, which violates type safety.
Update the `BrainStatusEvent` interface definition to include the `sessionId`
field with an appropriate type (likely `string | undefined` based on the usage
pattern), so that the `sessionId` property is properly declared and the type
cast on line 135 becomes type-safe without needing to bypass TypeScript's type
checking.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

Review info
Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e536bff-6486-4e74-be02-ac121c475178

Commits

Reviewing files that changed from the base of the PR and between 5645592 and cf51a8f.

Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
Files selected for processing (3)
  • package.json
  • src/status.ts
  • src/tui.tsx

Comment thread src/status.ts
// ALS-stored session ID wins over global (prevents cross-session channel overwrite).
// If no ALS context (startup, auto-ingest fire-and-forget), use the unscoped "brain" service.
const sid = _sessionAls.getStore() ?? "";
const payload = { ..._state.current, version: _version, sessionId: sid || undefined } as BrainStatusEvent;

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 | ⚡ Quick win

Type safety violation: sessionId is not in BrainStatusEvent interface.

Line 135 adds sessionId to the payload and casts it to BrainStatusEvent, but the interface (defined in src/event-bus.ts:5-12) does not include a sessionId field. This bypasses TypeScript's type checking and could cause runtime issues if consumers expect only the declared fields.

Proposed fix: Add sessionId to BrainStatusEvent interface

Update src/event-bus.ts to include the sessionId field:

 export interface BrainStatusEvent {
   status?: "init" | "busy" | "ready" | "error";
   statusText?: string;
   current?: number;
   total?: number;
   version?: string;
   error?: string;
+  sessionId?: string;
 }

Then the cast on line 135 will be type-safe.

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/status.ts` at line 135, The `BrainStatusEvent` interface does not include
a `sessionId` field, but the payload construction in the status module is
attempting to add `sessionId` and casting it to `BrainStatusEvent`, which
violates type safety. Update the `BrainStatusEvent` interface definition to
include the `sessionId` field with an appropriate type (likely `string |
undefined` based on the usage pattern), so that the `sessionId` property is
properly declared and the type cast on line 135 becomes type-safe without
needing to bypass TypeScript's type checking.

@four-bytes-robby four-bytes-robby merged commit 55d308c into main Jun 14, 2026
6 checks passed

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/tui.tsx">

<violation number="1" location="src/tui.tsx:82">
P1: Subscription scope is fixed at mount and does not track later `sessionId` updates. Sidebar can miss status updates after session creation because publisher switches to session-scoped channel.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/tui.tsx
// the old brain/{sid} channel. No sessionId filter needed — the bus
// only delivers events for the scoped session (or unscoped when sid missing).
const scoped = b.forService("brain");
const brainBus = props.sessionId ? scoped.forSession(props.sessionId) : scoped;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Subscription scope is fixed at mount and does not track later sessionId updates. Sidebar can miss status updates after session creation because publisher switches to session-scoped channel.

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

<comment>Subscription scope is fixed at mount and does not track later `sessionId` updates. Sidebar can miss status updates after session creation because publisher switches to session-scoped channel.</comment>

<file context>
@@ -1,161 +1,134 @@
+        // the old brain/{sid} channel. No sessionId filter needed — the bus
+        // only delivers events for the scoped session (or unscoped when sid missing).
+        const scoped = b.forService("brain");
+        const brainBus = props.sessionId ? scoped.forSession(props.sessionId) : scoped;
+        unsub = brainBus.subscribe("status", (envelope) => {
+          handleStatus(envelope.payload as BrainStatusEvent);
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant