finish phase 0 of the opencode parity list

Three defects and one honest removal. Each was reproduced before being changed, as the doc asks.

B4 — images were offered and silently discarded. Every OpenCode model advertised `images: true`, the
composer gates on that flag, the bubble rendered the attachment, and `handleOpenCodeChat`'s message type
has no `images` field, so it never left officer. Flipped to false: 61 OpenCode models now decline, the
three Claude ones still accept. Plumbing them through OpenCodeRunParams stays Phase 4; advertising a
capability that does not exist is the part worth fixing today.

B5 — every OpenCode turn overwrote the previous turn's subscription handle without detaching it, so the
old session-scoped listener stayed attached and delivery doubled, tripled, and so on for any termination
that is not result/error/stopped. Deliberately NOT the Claude guard: Claude keeps one persistent session
and skips re-subscribing, while OpenCode spawns a fresh `opencode run` per turn, so a new subscription
each time is correct — detaching the old one is what was missing.

B6 — the sessionKey → `ses_…` map had no writer of deletions, so it grew for the process lifetime and a
reused key resumed a stale OpenCode session. Cleared in `deleteSession` only, never in `releaseSession`:
releasing means "let go, leave it running", and a returning browser must find the same `ses_…` again.

Phase 0 item 1 — the thinking toggle is removed rather than fixed. `thinking` is accepted on the wire
and forwarded by neither channel, so the control changed its own label and nothing else. Out of scope
for both harnesses by decision. The inert plumbing beneath it is left for a follow-up that touches the
socket contract; the props stay accepted-and-unread so no call site had to change.

Phase 0 is complete: B1, B2, B3 landed earlier; B4, B5, B6 and the selector here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 02:31:27 +00:00
co-authored by Claude Opus 5
parent 29d99127bd
commit 013e6296f1
4 changed files with 65 additions and 37 deletions
+7
View File
@@ -1,5 +1,6 @@
import type { UserSession } from './types';
import { logger } from './logger';
import { clearOpenCodeSession } from './opencode/state';
class SessionManager {
private sessions = new Map<string, UserSession>();
@@ -104,6 +105,12 @@ class SessionManager {
session._claudeKill();
}
// The OpenCode sessionKey → `ses_…` map is keyed on our session id and nothing else cleared it, so
// it grew for the process lifetime and a REUSED key silently resumed a stale OpenCode session.
// Only on deleteSession, never on releaseSession: releasing is "let go, leave it running", and a
// returning browser has to be able to find the same `ses_…` again.
clearOpenCodeSession(sessionId);
this.forget(session);
}