Skip to content

feat: hosted reasoning-channel delivery (pull-based, tenant-scoped)#407

Open
glassBead-tc wants to merge 3 commits into
feat/hosted-reasoning-channelfrom
feat/hosted-reasoning-channel-server
Open

feat: hosted reasoning-channel delivery (pull-based, tenant-scoped)#407
glassBead-tc wants to merge 3 commits into
feat/hosted-reasoning-channelfrom
feat/hosted-reasoning-channel-server

Conversation

@glassBead-tc

Copy link
Copy Markdown
Member

What

Delivers the hosted (multi-tenant) path for the reasoning channel: protocol lifecycle events reach the Claude Code channel from the deployed server, scoped per workspace. Implements SPEC-REASONING-CHANNEL-HOSTED:c2–c5.

Stacked on #406 (plugin wiring / c1) — review/merge that first; this targets its branch and shows only the hosted path.

Why it was needed

In hosted mode the server (a) never passed onProtocolEvent in the multi-tenant session branch and (b) only mounts /events SSE locally — and in-process SSE can't span Cloud Run replicas anyway. So a hosted channel had nothing to consume.

Changes

  • protocol_events table (c2) — tenant-scoped + RLS (claims/hub pattern), append-only, bigint identity as the pull cursor. Migration 20260615000000. A dedicated table, not protocol_history (which stores only a lossy operation-level subset — missing init/visa/complete and the ulysses/theseus taxonomy). Fed by the same onProtocolEvent emit stream, so hosted persistence == local SSE byte-for-byte.
  • Pull endpoint (c3) — GET /protocol/events?changed_since=<cursor>, workspace resolved from the API key; a key can never read another tenant's events.
  • Polling transport (c4) — PollingEventClient in the plugin; the channel selects SSE (localhost) vs polling (remote) by URL host, overridable via THOUGHTBOX_CHANNEL_MODE. Primes to the tail on connect (no replay); derives sessionId from data.session_id.
  • Tests (c5) — storage + cross-tenant negative control (local Supabase), polling client (mocked fetch). Local SSE path untouched; 83 protocol tests pass.

Design note: pull, not push

Mirrors B3's pull philosophy (SPEC-AGX-SUBSTRATE §11.1). Realtime/push delivery is deferred to B6/B8 so the realtime transport is defined once, there.

Verification

  • protocol_events migration applied to local Supabase; types regenerated.
  • Storage tests (append, changed_since paging, cross-tenant isolation): pass.
  • Polling client tests (prime-to-tail, emit-new-only, sessionId derivation, auth header): pass.
  • HTTP smoke (hosted mode): /protocol/events mounted and auth-gated (401 on missing/bogus key). Full authed e2e is deploy-gated (same posture as claims c2).
  • tsc --noEmit + oxlint clean.

Deploy note

Touches supabase/migrations/** → staging auto-apply on push. Out-of-order timestamp is tolerated (set-based applier + sorted-set drift check).

🤖 Generated with Claude Code

glassBead-tc and others added 2 commits June 15, 2026 23:28
Hosted (multi-tenant) Cloud Run can't deliver the in-process /events SSE
stream across replicas, so the reasoning channel had nothing to consume.

Add a tenant-scoped, append-only protocol_events table (claims/hub RLS
pattern) fed by the same onProtocolEvent emit stream, so hosted
persistence equals local SSE byte-for-byte across all nine lifecycle
types. Distinct from protocol_history, which stores a lossy operation-
level subset. Expose it via GET /protocol/events?changed_since=, scoped
to the caller's workspace by API key so no key reads another tenant's
events.

Implements SPEC-REASONING-CHANNEL-HOSTED:c2, c3. Storage + cross-tenant
negative-control tests pass against local Supabase; the authed HTTP e2e
is deploy-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Against a hosted server the channel can't hold an SSE connection (multi-
replica), so add a PollingEventClient that pulls GET /protocol/events
with a changed_since cursor. It primes to the tail on connect so a fresh
channel reacts to new events instead of replaying old sessions, and
derives the top-level sessionId from data.session_id (the shape
EventFilter expects).

The channel selects transport by URL host (localhost -> SSE, remote ->
poll), overridable via THOUGHTBOX_CHANNEL_MODE. Local SSE delivery is
unchanged.

Implements SPEC-REASONING-CHANNEL-HOSTED:c4, c5. Polling unit tests run
under the root vitest (plugin test path added); plugin build excludes
__tests__ from the shipped dist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
thoughtbox-web-two Ready Ready Preview, Comment Jun 24, 2026 7:09am

@supabase

supabase Bot commented Jun 16, 2026

Copy link
Copy Markdown

Updates to Preview Branch (feat/hosted-reasoning-channel-server) ↗︎

Deployments Status Updated
Database Wed, 24 Jun 2026 07:09:12 UTC
Services Wed, 24 Jun 2026 07:09:12 UTC
APIs Wed, 24 Jun 2026 07:09:12 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Wed, 24 Jun 2026 07:09:14 UTC
Migrations Wed, 24 Jun 2026 07:09:15 UTC
Seeding Wed, 24 Jun 2026 07:09:15 UTC
Edge Functions Wed, 24 Jun 2026 07:09:22 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 754533f28f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.from('protocol_events')
.select('id, source, type, event_timestamp, data')
.eq('tenant_workspace_id', this.tenantWorkspaceId)
.gt('id', cursor)

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 Badge Use a commit-safe cursor for protocol_events

In hosted workspaces with concurrent protocol event appends, this high-water mark can silently skip rows. PostgreSQL identity/sequence values are allocated before commit, so if event id 11 commits and is returned before an earlier transaction with id 10 becomes visible, the client advances its cursor to 11 and the later-visible id 10 will never match id > cursor; Cloud Run replicas/fire-and-forget appends make that interleaving possible. Use a cursor based on commit-visible ordering or poll with overlap/deduping so committed rows cannot be skipped.

Useful? React with 👍 / 👎.

Comment on lines +68 to +71
} catch (error) {
this.reportError(error);
}
this.scheduleNext(this.config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);

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 Badge Do not emit backlog after failed priming

When the initial prime-to-tail request fails transiently, cursor remains at 0 but the client still enters the normal emitting poll loop. The next successful poll will therefore deliver the existing backlog that priming was supposed to suppress, which can replay completed Ulysses/Theseus sessions into the user's channel after a startup blip. Keep retrying the non-emitting prime path, or otherwise mark priming incomplete, before scheduling normal polls.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Implements the hosted (multi-tenant) reasoning-channel delivery path: a protocol_events table (tenant-scoped, RLS, bigint identity cursor), a GET /protocol/events?changed_since= pull endpoint on the server, and a PollingEventClient in the plugin that selects SSE for local and polling for remote servers. The local SSE path is untouched.

  • Server persistence (c2): onProtocolEvent is wired in the multi-tenant session branch to fire-and-forget append to SupabaseProtocolEventStorage; the route and storage provider are correctly gated behind if (isMultiTenant), making the ! assertions safe.
  • Pull endpoint (c3): GET /protocol/events resolves workspace from the API key, pages by cursor, and returns {events, cursor}; cross-tenant reads are structurally impossible because every query is scoped by tenant_workspace_id.
  • Transport selection (c4): selectTransport() checks the URL hostname (localhost → SSE, remote → poll) with a THOUGHTBOX_CHANNEL_MODE override; PollingEventClient primes to the current tail on connect to avoid replaying completed sessions, then polls with backoff.

Confidence Score: 4/5

Safe to merge; all multi-tenant guards and tenant-isolation properties are correctly enforced. The issues found are in the polling client's session-filter handling and observability, not in data isolation or correctness.

The migration, storage layer, pull endpoint, and transport-selection logic are all solid. The main concern is in PollingEventClient: it accepts sessionId in config but never forwards it to the server query — so when THOUGHTBOX_SESSION is set in hosted mode, every poll fetches all workspace events rather than only the configured session's events. EventFilter rescues correctness downstream, but the server-side session scoping that the SSE path provides is silently absent. There is also a subtle prime-failure edge case where onConnect is not called yet polling begins, leaving operators with no 'Connected' log line.

plugins/thoughtbox-claude-code/src/polling-event-client.ts — the session-filter gap and prime-failure behavior warrant a second look before this becomes a higher-traffic path.

Important Files Changed

Filename Overview
plugins/thoughtbox-claude-code/src/polling-event-client.ts New pull-based event transport for hosted mode; functional and well-structured but silently drops sessionId from queries (server-side session filter never applied), and partial prime failures leave the cursor mid-history while polling starts without calling onConnect.
plugins/thoughtbox-claude-code/src/thoughtbox-channel.ts Transport selection logic is clean; selectTransport() silently ignores unrecognised THOUGHTBOX_CHANNEL_MODE values with no warning log.
src/protocol/protocol-event-storage.ts Clean tenant-scoped append + cursor-paged read; uses service-role key with explicit tenant_workspace_id filter on every query; provider caches one client per tenant.
src/index.ts Pull endpoint and onProtocolEvent wiring are correctly guarded inside if (isMultiTenant); the ! assertions on tenantProtocolEventStorage are safe given the guard; fire-and-forget append pattern is appropriate.
supabase/migrations/20260615000000_add_protocol_events_table.sql Correct IF NOT EXISTS guards, bigint identity cursor, composite index on (tenant_workspace_id, id) matching the query pattern, RLS with service-role full-access + workspace-membership SELECT policy.
src/protocol/tests/protocol-event-storage.test.ts Good cross-tenant negative control test; graceful skip when local Supabase is unavailable; covers append ordering and cursor pagination.
plugins/thoughtbox-claude-code/src/tests/polling-event-client.test.ts Covers prime-to-tail, new-event emission, auth header, and sessionId derivation; uses real timers with a 10 ms window, which could be flaky in slow CI — fake timers would make this deterministic.
src/database.types.ts Generated types for protocol_events correctly reflect the migration schema; id is never on Insert/Update (GENERATED ALWAYS identity).

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CC as Claude Code
    participant CH as thoughtbox-channel (plugin)
    participant SRV as Thoughtbox Server (Cloud Run)
    participant DB as Supabase (protocol_events)

    Note over CH: selectTransport(baseUrl)<br/>localhost → SSE, remote → poll

    alt Local mode (SSE)
        CC->>CH: MCP session start
        CH->>SRV: "GET /events?session_id=... (SSE, Bearer key)"
        SRV-->>CH: text/event-stream (in-process broadcast)
        SRV->>CH: protocol event (ThoughtboxEvent)
        CH->>CC: notifications/claude/channel
    else Hosted mode (poll)
        CC->>CH: MCP session start
        CH->>SRV: GET /protocol/events (prime to tail, no emit)
        SRV->>DB: "SELECT id, ... WHERE tenant=T AND id > 0"
        DB-->>SRV: existing events
        SRV-->>CH: "{events, cursor}"
        Note over CH: cursor advanced to tail, onConnect fired
        loop every 3 s
            CH->>SRV: "GET /protocol/events?changed_since=cursor"
            SRV->>DB: "SELECT ... WHERE tenant=T AND id > cursor"
            DB-->>SRV: new events
            SRV-->>CH: "{events, nextCursor}"
            CH->>CC: notifications/claude/channel (per new event)
        end
        Note over SRV: onProtocolEvent (MCP session)
        SRV->>DB: INSERT INTO protocol_events (tenant, type, data, ...)
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CC as Claude Code
    participant CH as thoughtbox-channel (plugin)
    participant SRV as Thoughtbox Server (Cloud Run)
    participant DB as Supabase (protocol_events)

    Note over CH: selectTransport(baseUrl)<br/>localhost → SSE, remote → poll

    alt Local mode (SSE)
        CC->>CH: MCP session start
        CH->>SRV: "GET /events?session_id=... (SSE, Bearer key)"
        SRV-->>CH: text/event-stream (in-process broadcast)
        SRV->>CH: protocol event (ThoughtboxEvent)
        CH->>CC: notifications/claude/channel
    else Hosted mode (poll)
        CC->>CH: MCP session start
        CH->>SRV: GET /protocol/events (prime to tail, no emit)
        SRV->>DB: "SELECT id, ... WHERE tenant=T AND id > 0"
        DB-->>SRV: existing events
        SRV-->>CH: "{events, cursor}"
        Note over CH: cursor advanced to tail, onConnect fired
        loop every 3 s
            CH->>SRV: "GET /protocol/events?changed_since=cursor"
            SRV->>DB: "SELECT ... WHERE tenant=T AND id > cursor"
            DB-->>SRV: new events
            SRV-->>CH: "{events, nextCursor}"
            CH->>CC: notifications/claude/channel (per new event)
        end
        Note over SRV: onProtocolEvent (MCP session)
        SRV->>DB: INSERT INTO protocol_events (tenant, type, data, ...)
    end
Loading

Comments Outside Diff (2)

  1. plugins/thoughtbox-claude-code/src/polling-event-client.ts, line 497-513 (link)

    P2 sessionId accepted but silently unused in polling requests

    PollingEventClientConfig accepts sessionId for interface parity with EventClient, but fetchPage() never adds it as a query parameter. EventClient.doConnect() sends ?session_id=<id> to the SSE endpoint for server-side filtering (line 55 of event-client.ts). When THOUGHTBOX_SESSION is set and hosted mode is selected, the polling transport fetches all workspace events on every cycle instead of just events for that session — EventFilter rescues correctness downstream, but the session-scoping contract that callers expect from the config field is silently ignored. In a high-event workspace this also means unnecessary data transfer on every poll.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: plugins/thoughtbox-claude-code/src/polling-event-client.ts
    Line: 497-513
    
    Comment:
    **`sessionId` accepted but silently unused in polling requests**
    
    `PollingEventClientConfig` accepts `sessionId` for interface parity with `EventClient`, but `fetchPage()` never adds it as a query parameter. `EventClient.doConnect()` sends `?session_id=<id>` to the SSE endpoint for server-side filtering (line 55 of `event-client.ts`). When `THOUGHTBOX_SESSION` is set and hosted mode is selected, the polling transport fetches all workspace events on every cycle instead of just events for that session — `EventFilter` rescues correctness downstream, but the session-scoping contract that callers expect from the config field is silently ignored. In a high-event workspace this also means unnecessary data transfer on every poll.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. plugins/thoughtbox-claude-code/src/polling-event-client.ts, line 431-446 (link)

    P2 onConnect not called and cursor left mid-history after prime failure

    If fetchPage throws during the prime loop, the catch block calls reportError but onConnect is not called, yet scheduleNext still runs and polling begins. Two consequences: (1) the "Connected" log line never appears despite the client actively polling — an operator monitoring stderr would see no confirmation that the channel is live; (2) if the prime had already advanced this.cursor past 0 before the error, the first successful poll cycle will emit those historical events that the prime was specifically designed to skip.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: plugins/thoughtbox-claude-code/src/polling-event-client.ts
    Line: 431-446
    
    Comment:
    **`onConnect` not called and cursor left mid-history after prime failure**
    
    If `fetchPage` throws during the prime loop, the catch block calls `reportError` but `onConnect` is not called, yet `scheduleNext` still runs and polling begins. Two consequences: (1) the "Connected" log line never appears despite the client actively polling — an operator monitoring stderr would see no confirmation that the channel is live; (2) if the prime had already advanced `this.cursor` past 0 before the error, the first successful poll cycle will emit those historical events that the prime was specifically designed to skip.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
plugins/thoughtbox-claude-code/src/polling-event-client.ts:497-513
**`sessionId` accepted but silently unused in polling requests**

`PollingEventClientConfig` accepts `sessionId` for interface parity with `EventClient`, but `fetchPage()` never adds it as a query parameter. `EventClient.doConnect()` sends `?session_id=<id>` to the SSE endpoint for server-side filtering (line 55 of `event-client.ts`). When `THOUGHTBOX_SESSION` is set and hosted mode is selected, the polling transport fetches all workspace events on every cycle instead of just events for that session — `EventFilter` rescues correctness downstream, but the session-scoping contract that callers expect from the config field is silently ignored. In a high-event workspace this also means unnecessary data transfer on every poll.

### Issue 2 of 3
plugins/thoughtbox-claude-code/src/thoughtbox-channel.ts:35-37
**Invalid `THOUGHTBOX_CHANNEL_MODE` value silently falls through**

If `THOUGHTBOX_CHANNEL_MODE` is set to anything other than `"sse"` or `"poll"` (e.g., a typo like `"SSE"` or `"polling"`), the function silently falls back to URL-based detection with no log output. This makes misconfiguration hard to diagnose. A one-line warning before the fallback would surface the bad value immediately.

```suggestion
  const override = process.env.THOUGHTBOX_CHANNEL_MODE;
  if (override === "sse" || override === "poll") return override;
  if (override !== undefined) {
    console.error(`[Channel] Unknown THOUGHTBOX_CHANNEL_MODE="${override}"; falling back to URL-based detection`);
  }
  try {
```

### Issue 3 of 3
plugins/thoughtbox-claude-code/src/polling-event-client.ts:431-446
**`onConnect` not called and cursor left mid-history after prime failure**

If `fetchPage` throws during the prime loop, the catch block calls `reportError` but `onConnect` is not called, yet `scheduleNext` still runs and polling begins. Two consequences: (1) the "Connected" log line never appears despite the client actively polling — an operator monitoring stderr would see no confirmation that the channel is live; (2) if the prime had already advanced `this.cursor` past 0 before the error, the first successful poll cycle will emit those historical events that the prime was specifically designed to skip.

Reviews (1): Last reviewed commit: "feat(plugin): add hosted polling transpo..." | Re-trigger Greptile

Comment on lines +35 to +37
const override = process.env.THOUGHTBOX_CHANNEL_MODE;
if (override === "sse" || override === "poll") return override;
try {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Invalid THOUGHTBOX_CHANNEL_MODE value silently falls through

If THOUGHTBOX_CHANNEL_MODE is set to anything other than "sse" or "poll" (e.g., a typo like "SSE" or "polling"), the function silently falls back to URL-based detection with no log output. This makes misconfiguration hard to diagnose. A one-line warning before the fallback would surface the bad value immediately.

Suggested change
const override = process.env.THOUGHTBOX_CHANNEL_MODE;
if (override === "sse" || override === "poll") return override;
try {
const override = process.env.THOUGHTBOX_CHANNEL_MODE;
if (override === "sse" || override === "poll") return override;
if (override !== undefined) {
console.error(`[Channel] Unknown THOUGHTBOX_CHANNEL_MODE="${override}"; falling back to URL-based detection`);
}
try {
Prompt To Fix With AI
This is a comment left during a code review.
Path: plugins/thoughtbox-claude-code/src/thoughtbox-channel.ts
Line: 35-37

Comment:
**Invalid `THOUGHTBOX_CHANNEL_MODE` value silently falls through**

If `THOUGHTBOX_CHANNEL_MODE` is set to anything other than `"sse"` or `"poll"` (e.g., a typo like `"SSE"` or `"polling"`), the function silently falls back to URL-based detection with no log output. This makes misconfiguration hard to diagnose. A one-line warning before the fallback would surface the bad value immediately.

```suggestion
  const override = process.env.THOUGHTBOX_CHANNEL_MODE;
  if (override === "sse" || override === "poll") return override;
  if (override !== undefined) {
    console.error(`[Channel] Unknown THOUGHTBOX_CHANNEL_MODE="${override}"; falling back to URL-based detection`);
  }
  try {
```

How can I resolve this? If you propose a fix, please make it concise.

@glassBead-tc

Copy link
Copy Markdown
Member Author

@cursoragent Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.


Issue 1 of 3

plugins/thoughtbox-claude-code/src/polling-event-client.ts:497-513
sessionId accepted but silently unused in polling requests

PollingEventClientConfig accepts sessionId for interface parity with EventClient, but fetchPage() never adds it as a query parameter. EventClient.doConnect() sends ?session_id=<id> to the SSE endpoint for server-side filtering (line 55 of event-client.ts). When THOUGHTBOX_SESSION is set and hosted mode is selected, the polling transport fetches all workspace events on every cycle instead of just events for that session — EventFilter rescues correctness downstream, but the session-scoping contract that callers expect from the config field is silently ignored. In a high-event workspace this also means unnecessary data transfer on every poll.

Issue 2 of 3

plugins/thoughtbox-claude-code/src/thoughtbox-channel.ts:35-37
Invalid THOUGHTBOX_CHANNEL_MODE value silently falls through

If THOUGHTBOX_CHANNEL_MODE is set to anything other than "sse" or "poll" (e.g., a typo like "SSE" or "polling"), the function silently falls back to URL-based detection with no log output. This makes misconfiguration hard to diagnose. A one-line warning before the fallback would surface the bad value immediately.

  const override = process.env.THOUGHTBOX_CHANNEL_MODE;
  if (override === "sse" || override === "poll") return override;
  if (override !== undefined) {
    console.error(`[Channel] Unknown THOUGHTBOX_CHANNEL_MODE="${override}"; falling back to URL-based detection`);
  }
  try {

Issue 3 of 3

plugins/thoughtbox-claude-code/src/polling-event-client.ts:431-446
onConnect not called and cursor left mid-history after prime failure

If fetchPage throws during the prime loop, the catch block calls reportError but onConnect is not called, yet scheduleNext still runs and polling begins. Two consequences: (1) the "Connected" log line never appears despite the client actively polling — an operator monitoring stderr would see no confirmation that the channel is live; (2) if the prime had already advanced this.cursor past 0 before the error, the first successful poll cycle will emit those historical events that the prime was specifically designed to skip.

@cursor

cursor Bot commented Jun 24, 2026

Copy link
Copy Markdown

Summary:

  • Updated hosted polling to include session_id in /protocol/events requests, added a warning for invalid THOUGHTBOX_CHANNEL_MODE, and made priming retry until success before logging “Connected.”
  • Added/updated polling client tests and aligned the hosted reasoning channel spec claim wording to reflect the new behavior.

Tests:

  • pnpm test plugins/thoughtbox-claude-code/src/__tests__/polling-event-client.test.ts

Notes:

  • A pattern review flagged two optional warnings in thoughtbox-channel.ts (missing exhaustive switch guard, message-only error logging). I left them unchanged to keep scope tight—tell me if you want those addressed.
Open in Web Open in Cursor 

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.

2 participants