diff --git a/src/stores/sessionScope.test.ts b/src/stores/sessionScope.test.ts deleted file mode 100644 index e95eb0d..0000000 --- a/src/stores/sessionScope.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { test } from "node:test" -import assert from "node:assert/strict" -import { sessionScopeDirectory } from "./sessionScope.ts" - -// Bug #10 regression guard: list and create must resolve to the SAME scope. -// Bug #32 regression guard: the default scope must be "no header" (null), not -// the server's home directory. The home dir resolves to the "global" project on -// the opencode server and hides real workspace sessions. - -test("no explicit directory + known home -> default client (null), home ignored (#32)", () => { - assert.equal(sessionScopeDirectory(false, "/home/user"), null) -}) - -test("no explicit directory + unknown home -> default client (null)", () => { - assert.equal(sessionScopeDirectory(false, null), null) - assert.equal(sessionScopeDirectory(false, undefined), null) - assert.equal(sessionScopeDirectory(false, ""), null) -}) - -test("explicit directory -> default client (null), home ignored", () => { - assert.equal(sessionScopeDirectory(true, "/home/user"), null) - assert.equal(sessionScopeDirectory(true, null), null) -}) - -test("create and list resolve identically across all inputs (no drift, #10)", () => { - for (const hasDir of [true, false]) { - for (const home of ["/home/user", null, undefined, ""]) { - const listScope = sessionScopeDirectory(hasDir, home) - const createScope = sessionScopeDirectory(hasDir, home) - assert.equal(listScope, createScope) - } - } -}) - -test("server directory != home: rule returns null so server uses its CWD project (#32)", () => { - // Reproduces the real-world setup: server launched in ~/workspace/opencode - // while $HOME is /home/azureuser. Old rule returned "/home/azureuser" (global - // project, empty). New rule returns null so server uses its CWD project. - assert.equal(sessionScopeDirectory(false, "/home/azureuser"), null) -}) diff --git a/src/stores/sessionScope.ts b/src/stores/sessionScope.ts deleted file mode 100644 index 069093d..0000000 --- a/src/stores/sessionScope.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Single source of truth for which directory scope session reads/writes use. -// -// Bug #10 ("sessions tab empty after connect / create") happened because the -// list path and the create path each computed this rule independently and drifted: -// loadSessions listed sessions home-scoped while createSession wrote CWD-scoped, so -// a freshly created session was invisible. Both call sites now derive their client -// from this one function, so they cannot disagree again. -// -// Bug #32 ("recent sessions missing"): the previous rule scoped to the server's -// home directory when no explicit directory was set. opencode-server resolves the -// x-opencode-directory header to a project id by exact-match, NOT subtree prefix. -// $HOME typically maps to the synthetic "global" project, which never contains -// the user's real workspace sessions (those live under e.g. ~/workspace/foo, a -// different project id). The list looked empty even when sessions existed. -// -// Rule: when the active connection pins an explicit directory, use the default -// client (it is already scoped to that directory). Otherwise, send no scope at -// all so the server uses its own CWD — i.e. the project where opencode serve was -// launched, which is the project the user actually works in. The `home` argument -// is accepted for backward compatibility but intentionally ignored. - -export function sessionScopeDirectory(hasExplicitDirectory: boolean, _home?: string | null | undefined): string | null { - void _home - if (hasExplicitDirectory) return null - return null -} diff --git a/src/stores/sessions.ts b/src/stores/sessions.ts index 4789a98..3349f92 100644 --- a/src/stores/sessions.ts +++ b/src/stores/sessions.ts @@ -2,7 +2,6 @@ import { create } from "zustand" import type { Session, Message, Part, Event, MessageWithParts, Client } from "../lib/sdk" import { useConnections } from "./connections" import { useSettings } from "./settings" -import { sessionScopeDirectory } from "./sessionScope" import { addBreadcrumb } from "../lib/sentry" // Helper to convert API response to our internal format @@ -75,36 +74,20 @@ export const useSessions = create((set, get) => ({ loadSessions: async () => { const connState = useConnections.getState() - if (!connState.client) { + const client = connState.client + if (!client) { set({ error: "No active connection" }) return } try { set({ isLoading: true, error: null }) - const initialConnectionID = connState.activeConnection?.id || null - const hasExplicitDirectory = Boolean(connState.activeConnection?.directory) - let home = connState.serverHome - - if (!hasExplicitDirectory && !home) { - const paths = await connState.client.path.get().catch(() => null) - home = paths?.home || null - if (home) { - useConnections.setState({ serverHome: home }) - } - } - - const latestConnState = useConnections.getState() - const latestConnectionID = latestConnState.activeConnection?.id || null - if (latestConnectionID !== initialConnectionID) { - set({ isLoading: false }) - return - } - - const scopeDir = sessionScopeDirectory(Boolean(latestConnState.activeConnection?.directory), home) - const listClient = scopeDir ? latestConnState.clientForDirectory(scopeDir) : latestConnState.client - - const sessions = await (listClient || latestConnState.client!).session.list({ roots: true, limit: 50 }) + // List with the connection's default client and send no directory scope: + // opencode-server resolves the x-opencode-directory header to a project by + // exact match, and $HOME maps to the empty "global" project, which hides the + // user's real workspace sessions (bug #32). The default client already carries + // the connection's explicit directory when one is configured. + const sessions = await client.session.list({ roots: true, limit: 50 }) set({ sessions, isLoading: false }) } catch (error) { set({ error: "Failed to load sessions", isLoading: false }) @@ -185,34 +168,24 @@ export const useSessions = create((set, get) => ({ createSession: async (title) => { const connState = useConnections.getState() - if (!connState.client) { + const client = connState.client + if (!client) { set({ error: "No active connection" }) return null } - // Create the session in the SAME directory scope that loadSessions reads from - // (see sessionScopeDirectory / bug #10). Both paths derive scope from the one - // helper so a freshly created session is always visible to the list. - const scopeDir = sessionScopeDirectory(Boolean(connState.activeConnection?.directory), connState.serverHome) - const client = scopeDir ? connState.clientForDirectory(scopeDir) || connState.client : connState.client - try { const created = await client.session.create({ title }) - // Stamp the scope we created in onto the session so every downstream path - // (navigation params, selectSession, sendMessage's clientFor) addresses it - // in the SAME scope. Without this, opening/sending to a freshly created - // home-scoped session via the default client would hit the wrong scope (#10). - const session = scopeDir && !created.directory ? { ...created, directory: scopeDir } : created // Don't optimistically add to sessions list — let loadSessions() handle it // to avoid duplicate key errors from race conditions set({ - currentSession: session, + currentSession: created, messages: [], parts: {}, hasMore: false, loadingMore: false, }) - return session + return created } catch (error) { set({ error: "Failed to create session" }) return null