diff --git a/docs/chat-panes-next-session.md b/docs/chat-panes-next-session.md new file mode 100644 index 00000000..a1d8a4b0 --- /dev/null +++ b/docs/chat-panes-next-session.md @@ -0,0 +1,74 @@ +# Multi-pane chat: where it stands, and the one open bug + +Written 2026-08-10 at the end of a long session, so the next one starts from evidence instead of +re-deriving it. + +## The open bug + +**A pane pointed at a REMOTE server shows `Disconnected` and never gets a reply.** The pane opens, lists +and reads that server's conversations fine (HTTP with the API key works). Sending produces nothing. + +Console, repeatedly: + +``` +WebSocket connection to 'wss://officer.pastilhas.dev/api/chat/ws?token=ofk_…' failed: +WebSocket is closed before the connection is established. +``` + +The same message also appears for the LOCAL pane's `ws://localhost:9010/...`, yet the local pane works +and shows connected. So the message alone is not the bug — something closes the remote one and it never +comes back. + +## Ruled out, each by direct test — do not re-test these + +- **The server.** `wss://officer.pastilhas.dev/api/chat/ws?token=` opens on the first + try from outside the browser. Tested twice: with no `Origin` header, and with + `Origin: http://localhost:9010` (what the browser sends). Both `OPEN`. +- **The key.** Same key, works over HTTP for `/chat/sessions`, and opens the socket above. +- **Websocket auth not understanding API keys.** `upgradeWs` in `server.tsx` uses `resolveAuthToken`, the + same resolver as the HTTP doors, and comments say so explicitly. `chat` is an `execution` capability + and the owner passes `isWsProviderAllowed`. +- **Officer being stale on alpha.** Pulled and restarted; the failure persists. +- **Send being dropped.** Fixed in `243bd04` — `useChatWebSocket.send` used to `return` silently when the + socket was not `OPEN`. It now queues and flushes on open. That fix is real and worth keeping, but it + did not resolve this: a socket that never opens never flushes. + +## Where to look next + +`src/workspaces/hooks/src/useChatWebSocket.ts`, and specifically what happens with SEVERAL instances +mounted at once — one per pane. + +The handlers look individually correct: `close` returns early when `socketRef.current !== socket`, the +effect depends on `[url]` alone, and `isCleaningUpRef` is reset on mount. What has NOT been established +is how those interact across three simultaneous instances plus React's dev StrictMode double-invoke, +which creates, closes and recreates every socket on mount. + +Concrete things to try, cheapest first: + +1. **Instrument before theorising.** Log `url`, `readyState` and instance identity on every create, + open, close and retry. The console message says a close arrived during CONNECTING; it does not say + who called it. That is the whole question and it is one log line away. +2. **Check whether the effect re-runs.** `url` is a string, so it should be stable — but + `chatSocketUrl(serverId, localStorage.getItem('BEARER_TOKEN'))` is recomputed every render, and if + anything makes `serverId` flip (say, a pane re-resolving its target) the URL changes and the socket + is torn down mid-handshake, forever. +3. **Suspect the transcript resolver in `ChatPane`.** It calls `onTargetChange` on success, which + rewrites the tab state, which produces a new `target` object for every pane in that tab. If that ever + loops, every pane's socket is rebuilt on every pass and none survives the handshake. +4. **Try one pane pointed at the remote server, alone.** If it connects, the bug is about multiplicity + rather than about the remote server, which would be the single most useful fact to have. + +## What works, so it is not re-litigated + +- Adding a second server by URL + `ofk_` key, verified against `/api/auth/me` before storing. +- Per-pane server chips; the list, the directory picker and the transcript all follow the pane's server. +- Tabs, splitting to three panes, closing, renaming a tab, and the tab name winning the page title. +- Reading a remote conversation end to end. + +## Setup facts worth not rediscovering + +- Andre's Mac key lives in agent memory. Alpha needs its own; the one in the console above is alpha's. +- `bun dev` on the Mac serves a STALE bundle whenever a new file is added to a workspace package — the + symptom is `X is not a function` for an export that plainly exists. Restart it; do not debug it. +- The Mac is `https://macbook.pastilhas.dev` through NPM on alpha (`100.64.0.1` → Mac `100.64.0.9:9010`), + and the cert is valid under strict TLS.