refactor(sessions): remove dead serverHome/path.get() plumbing
sessionScopeDirectory() always returned null and ignored its home arg, so the scope plumbing in loadSessions/createSession was dead: scopeDir was always null, listClient always the default client, and the lazy path.get() fetch fed only that dead branch. Collapse both call sites to use the connection's default client directly and delete the now-orphaned sessionScope.ts helper and its test. serverHome is intentionally KEPT in connections.ts: it is still consumed by the directory switcher UI (DirectorySwitcher.tsx, app/(tabs)/index.tsx) for ~ path expansion, so it is not dead code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<SessionsState>((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<SessionsState>((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
|
||||
|
||||
Reference in New Issue
Block a user