* docs: add SDK v1.17.12 migration plan — phase 4 (session.permission) * feat(permissions): verify pending permission before auto-accept via SDK v1.17.12 Adds createPermission() and fetchPermission() wrappers on OpencodeService for the new v2.session.permission endpoints (OpenCode SDK 1.17.12). fetchPermission() is used by the auto-accept sweep in resyncBlockingRequestsForDirectory to verify a permission is still pending before replying. The auto-accept flow now skips permissions that are already resolved, returning a null from fetchPermission() rather than blindly calling respondToPermission on a stale entry. createPermission() is exposed for future programmatic permission creation; the V1 list/reply path used by the UI is unchanged. The plan doc at plans/opencode-v1.17.12-sdk/ was rebased onto origin/main in the prior commit to keep the PR diff focused on this change. Closes #1972 * fix(permissions): drop confirmed-resolved permissions from auto-accept resync fetchPermission() now returns a tagged FetchPermissionResult so the auto-accept loop can distinguish a server-confirmed 404 (the permission is no longer pending) from a fetch failure (network error or pre-v1.17.12 server). Previously both cases collapsed to null, so a permission the server had already answered would still appear in the resync output and trigger a spurious 'Permission needed' toast. The auto-accept loop in resyncBlockingRequestsForDirectory now tracks both accepted and resolved permissions, then drops both from the 'grouped' map before it falls through to the toast path. On a pre-v1.17.12 server (no V2 endpoint) the call still returns 'unknown' and the permission stays in the resync output so the user can answer manually — fail-closed, no false-resolved signals. Adds a focused unit test for fetchPermission (4 cases: 200 ok, 404 resolved, 500 unknown, network throw) mocking the V2 SDK client shape. --------- Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
6.0 KiB
OpenCode v1.17.12 SDK Migration
Why this matters
OpenChamber currently runs on SDK v1.17.9. Version 1.17.12 adds several methods that directly fix real user-facing problems. Plus there are methods already in the SDK that we simply aren't using — and we should.
In short: this is not a "bump the version" chore. It's concrete fixes for hangs, lag, and wasted tokens.
What actually improves for the user
1. The STOP button will actually stop generation
Today: when the user presses STOP, OpenChamber sends session.abort() to the server. The server marks the session as idle, but does not cancel the HTTP request to the LLM provider (OpenAI, Anthropic, etc.). The provider keeps generating, tokens keep burning, and the user thinks everything stopped. Worse — sometimes the abort itself hangs and never reaches the server (known OpenCode bug #29975).
After the update: session.interrupt() — new in v1.17.12 — doesn't just mark the session idle. It actually tears down the HTTP request to the provider. The provider stops generating, tokens stop burning, the session frees up immediately. This is the direct fix from OpenCode PR #34467.
Where it gets better: no more "I pressed STOP but it's still thinking for 10 more seconds."
2. Chat stops lagging when switching sessions
Today: all events from all sessions flow into a single global stream (global.event()). OpenChamber has to manually parse this firehose — 300+ lines of code in sync-context.tsx exist solely to figure out "which chat gets which event." This is slow, complex, and events occasionally leak into the wrong chat.
After the update: session.events() — new in v1.17.12 — lets us subscribe to events for a single session. The chat listens only to its own session. No global firehose parsing, no guessing "whose event is this."
Where it gets better:
- Session switching is instant, no lag
- Events don't "leak" between chats
- Lower CPU from event parsing (especially noticeable with 5+ open sessions)
3. Long sessions load without freezing; revert/fork without full history load
Today (pagination): when loading message history, OpenChamber requests "the last N messages" via session.messages({ limit: 50 }). To load older messages, it requests "everything before message X" via before. No cursor — the server rescans history from the beginning every time.
Today (message lookup): when revert or fork needs one specific message, the code searches the already-loaded history. If the message isn't cached — the entire session history is loaded just to find one message.
After the update: Session3.messages({ cursor }) — the V2 API with cursor-based pagination (already in the SDK, but we use the old Session2). Works like flipping pages: load page 1 → get a cursor → request page 2 by cursor → server returns the continuation without rescanning. Plus Session3.message({ messageID }) for targeted single-message fetch.
Where it gets better:
- Long sessions open faster
- Scrolling up for history — smooth, no jank or redundant loads
- Less server load (no rescanning from scratch)
- Revert and fork complete instantly, even when session is evicted from cache
4. Permissions — programmatic create and fetch
Today: permissions are handled reactively — an SSE event permission.asked arrives, the UI shows a dialog, the user responds. Auto-accept works by iterating all pending permissions and calling reply() without checking if the permission is still valid.
After the update: session.permission.create and session.permission.fetch — new in v1.17.12. Enables programmatic permission creation and status checks before responding. Useful for auto-accept: before auto-approving, we can verify the permission is still relevant.
Where it gets better: fewer false auto-accepts on already-answered permissions.
What will NOT change (and why)
Tool timeout hangs (issue #1950) — not fixable via SDK
The problem "tool hangs for 5 minutes → session silently dies → UI stuck on thinking" is an OpenCode server bug, not an SDK issue. No SDK method can force the server to emit session.idle when it doesn't. This can only be fixed in OpenCode itself (default timeout, stream watchdog, correct session.idle emission).
What the SDK does help with: session.interrupt() lets the user manually kill a hung tool via STOP. Previously STOP didn't guarantee provider cancellation — now it does.
Phase plan
| # | What | Priority | Effort | User impact |
|---|---|---|---|---|
| 1 | session.interrupt() replaces session.abort() |
🔴 High | Small | STOP actually stops generation |
| 2 | session.events() — per-session event subscription |
🟡 Medium | Medium | No lag on session switch, no event leaks |
| 3 | Migrate all message ops to Session3 API (cursor pagination + targeted message lookup) | 🟡 Medium | Medium | Long sessions load without freezing; revert/fork without full history load |
| 4 | session.permission — programmatic endpoints |
🟢 Low | Small | More reliable auto-accept |
What already works — don't touch
global.event()— global event stream. Used, works, no changes needed.- Custom WebSocket/SSE in
event-pipeline.ts— this is OpenChamber-specific wrapping (coalescing, routing, backpressure), not an SDK replacement. Stays as-is.
Validation
| Phase | Command |
|---|---|
| 1-4 | bun run type-check in affected packages |
| 1 | bun test packages/ui/src/sync/session-actions.test.ts |
| 2 | Manual: open a session, send a message, switch sessions — events don't leak |
| 3 | Manual: load a session with 100+ messages, scroll up — smooth, no jank; revert an evicted session — works without full history load |
| 4 | Manual: verify auto-accept permissions |
References
- OpenCode v1.17.12 release: https://github.com/anomalyco/opencode/releases/tag/v1.17.12
- OpenChamber issue #1950: https://github.com/openchamber/openchamber/issues/1950
- SDK types:
node_modules/@opencode-ai/sdk/dist/v2/gen/sdk.gen.d.ts