diff --git a/plans/opencode-v1.17.12-sdk/handovers/session-1.md b/plans/opencode-v1.17.12-sdk/handovers/session-1.md deleted file mode 100644 index ad04a4e9..00000000 --- a/plans/opencode-v1.17.12-sdk/handovers/session-1.md +++ /dev/null @@ -1,34 +0,0 @@ -# Handover — Session 1 (2026-07-01) - -## Current state - -Plan created for OpenCode v1.17.12 SDK migration. No code changes made yet. - -## What was done - -1. Verified SDK v1.17.9 types against v1.17.12 release notes -2. Identified 3 actually-new methods: `session.interrupt()`, `session.events()`, `session.permission` -3. Identified 2 existing-but-unused: `Session3.messages()` with cursor, `Session3.message()` -4. Created 4-phase plan with implementation details -5. Created 4 GitHub issues (#1968, #1969, #1971, #1972) -6. Created 4 draft PRs (#1973, #1974, #1976, #1977) -7. Answered bot questions on all issues - -## Key findings - -- `session.interrupt()` is the highest-impact change — fixes abort propagation to upstream provider -- `session.events()` and `session.permission` must be verified in SDK types before implementation -- `Session3` API has different parameter shape than `Session2` — `directory` is client-scoped, `before` replaced by `cursor` -- `global.event()` is already used correctly — no changes needed -- Custom WebSocket/SSE in `event-pipeline.ts` is OpenChamber-specific (coalescing, routing, backpressure) — not a replacement for SDK - -## Next safe action - -1. Bump `@opencode-ai/sdk` to `^1.17.12` and run `bun install` -2. Verify new SDK types exist (`session.interrupt`, `session.events`, `session.permission`) -3. Start Phase 1: replace `session.abort()` → `session.interrupt()` in 3 call sites - -## Blockers - -- SDK v1.17.12 must be published and installable -- `session.events()` and `session.permission` existence unconfirmed diff --git a/plans/opencode-v1.17.12-sdk/implementation/phase-5-impl.md b/plans/opencode-v1.17.12-sdk/implementation/phase-5-impl.md deleted file mode 100644 index 0aa42346..00000000 --- a/plans/opencode-v1.17.12-sdk/implementation/phase-5-impl.md +++ /dev/null @@ -1,75 +0,0 @@ -# Phase 4 Implementation: `session.permission` — Programmatic Endpoints - -## Prerequisite - -Verify `session.permission.create` and `session.permission.fetch` exist in SDK v1.17.12 types. Check `node_modules/@opencode-ai/sdk/dist/v2/gen/sdk.gen.d.ts` for `Permission2` class (referenced by `Session3` at line 1672). - -If absent, skip this phase. - -## Step 1: Add wrappers to client.ts - -File: `packages/ui/src/lib/opencode/client.ts` - -Add after `replyToPermission()` (line 1108): - -```typescript -async createPermission( - sessionID: string, - permission: string, - options?: { message?: string; directory?: string | null } -): Promise { - const requestDirectory = this.normalizeCandidatePath(options?.directory ?? null) ?? this.currentDirectory; - const response = await this.client.session.permission.create({ - sessionID, - permission, - ...(requestDirectory ? { directory: requestDirectory } : {}), - ...(options?.message ? { message: options.message } : {}), - }); - return (response.data as PermissionRequest) ?? null; -} - -async fetchPermission( - sessionID: string, - requestID: string, - directory?: string | null -): Promise { - const requestDirectory = this.normalizeCandidatePath(directory) ?? this.currentDirectory; - const response = await this.client.session.permission.fetch({ - sessionID, - requestID, - ...(requestDirectory ? { directory: requestDirectory } : {}), - }); - return (response.data as PermissionRequest) ?? null; -} -``` - -## Step 2: Use in auto-accept flow (optional) - -File: `packages/ui/src/sync/sync-context.tsx`, line 1118-1143 - -The auto-accept flow currently iterates `grouped` permissions and calls `respondToPermission()`. The new `fetchPermission()` could be used to verify a permission still exists before auto-accepting: - -```typescript -await Promise.all(autoAcceptingSessionIds.flatMap((sessionId) => - (grouped[sessionId] ?? []).map(async (permission) => { - try { - const fresh = await opencodeClient.fetchPermission( - permission.sessionID, - permission.id - ); - if (!fresh) return; // Permission already resolved - await sessionActions.respondToPermission(permission.sessionID, permission.id, "once") - } catch { - // Keep failed auto-accept permissions in UI state - } - }), -)) -``` - -## Step 3: Validation - -```bash -cd packages/ui && bun run type-check -``` - -Manual: trigger a permission request, verify auto-accept flow works. diff --git a/plans/opencode-v1.17.12-sdk/phases/phase-5-permission.md b/plans/opencode-v1.17.12-sdk/phases/phase-5-permission.md deleted file mode 100644 index 5272d2b5..00000000 --- a/plans/opencode-v1.17.12-sdk/phases/phase-5-permission.md +++ /dev/null @@ -1,24 +0,0 @@ -# Phase 4: `session.permission` — Programmatic Endpoints - -## Summary - -Use `session.permission.create` and `session.permission.fetch` (new in OpenCode SDK v1.17.12) for programmatic permission handling — verify a permission is still relevant before auto-accepting it. - -## Files - -| File | Change | -|------|--------| -| `packages/ui/src/lib/opencode/client.ts` | Add `createPermission()`, `fetchPermission()` wrappers | -| `packages/ui/src/sync/sync-context.tsx` | Use in auto-accept flow (L1118-1143) | - -## Risk - -**Low.** Must verify `session.permission` exists in SDK v1.17.12. If absent, skip this phase. - -## Validation - -```bash -bun run type-check --filter @openchamber/ui -``` - -Manual: trigger a permission request, verify auto-accept flow works. diff --git a/plans/opencode-v1.17.12-sdk/plan.md b/plans/opencode-v1.17.12-sdk/plan.md deleted file mode 100644 index 223bb957..00000000 --- a/plans/opencode-v1.17.12-sdk/plan.md +++ /dev/null @@ -1,88 +0,0 @@ -# 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` diff --git a/plans/opencode-v1.17.12-sdk/todo.md b/plans/opencode-v1.17.12-sdk/todo.md deleted file mode 100644 index 564d7090..00000000 --- a/plans/opencode-v1.17.12-sdk/todo.md +++ /dev/null @@ -1,61 +0,0 @@ -# Todo — OpenCode v1.17.12 SDK Migration - -## Phase 1: `session.interrupt()` — server-side abort propagation - -- [ ] Bump `@opencode-ai/sdk` to `^1.17.12` in all `package.json` files -- [ ] Add `session.interrupt()` mock to `session-actions.test.ts` -- [ ] Replace `session.abort()` → `session.interrupt()` in `abortCurrentOperation()` (line 717) -- [ ] Replace `session.abort()` → `session.interrupt()` in `revertToMessage()` (line 910) -- [ ] Replace `session.abort()` → `session.interrupt()` in `unrevertSession()` (line 1042) -- [ ] Add `interruptSession()` wrapper to `client.ts` (optional) -- [ ] Run `bun run type-check` in `packages/ui` -- [ ] Run `bun test packages/ui/src/sync/session-actions.test.ts` -- [ ] Manual: verify STOP button aborts session and propagates to provider - -## Phase 2: `session.events()` — per-session event stream - -- [ ] Verify `session.events()` exists in SDK v1.17.12 types -- [ ] Add `subscribeSessionEvents()` to `client.ts` -- [ ] Use per-session stream in ChatContainer (reduce global firehose routing) -- [ ] Keep global pipeline as fallback for non-active sessions -- [ ] Run `bun run type-check` in `packages/ui` -- [ ] Manual: verify events arrive for active session only - -## Phase 3: Migrate all message operations to Session3 API - -### Step 1: Switch `session.messages()` calls from `Session2` to `Session3` API - -- [ ] Pass `cursor` param in `fetchMessages()` (`use-sync.ts` line 326) -- [ ] Pass `cursor` param in `materializeSessionFromServer()` (`sync-context.tsx` line 240) -- [ ] Pass `cursor` param in `resyncDirectoryAfterReconnect()` (`sync-context.tsx` line 1207) -- [ ] Pass `cursor` param in `refetchSessionMessages()` (`session-actions.ts` line 1010) -- [ ] Pass `cursor` param in `fetchMessagesForSession()` (`session-actions.ts` line 1147) -- [ ] Pass `cursor` param in `getSessionMessages()` (`client.ts` line 547) -- [ ] Remove `directory` param from per-call args (set at client creation via scoped client) -- [ ] Replace `before` param with `cursor` - -### Step 2: Add `session.message()` on the same Session3 API - -- [ ] Add `getMessage()` wrapper to `client.ts` using `Session3.message()` -- [ ] Use in `revertToMessage()` as fallback when message not in store -- [ ] Use in `forkFromMessage()` as fallback when message not in store - -### Validation - -- [ ] Run `bun run type-check` in `packages/ui` -- [ ] Manual: load a session with 100+ messages, scroll up — smooth, no jank -- [ ] Manual: revert an evicted session — works without full history load - -## Phase 4: `session.permission` — programmatic endpoints - -- [ ] Verify `session.permission.create` / `fetch` exist in SDK v1.17.12 -- [ ] Add wrappers to `client.ts` -- [ ] Use in permission auto-accept flow (`sync-context.tsx` line 1118-1143) -- [ ] Run `bun run type-check` in `packages/ui` -- [ ] Manual: verify permission create/fetch flow - -## Blockers - -- SDK v1.17.12 must be published and installable -- `session.events()` and `session.permission` must be confirmed in SDK types -- `Session3` API compatibility with current `Session2` usage must be verified