fix(app): seed session status before warming session info (#34864)

This commit is contained in:
Luke Parker
2026-07-02 16:32:25 +10:00
committed by GitHub
parent 40233595b0
commit fbb95a6ee3
2 changed files with 121 additions and 52 deletions
@@ -1,18 +1,17 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { createStore } from "solid-js/store" import { createStore } from "solid-js/store"
import { QueryClient } from "@tanstack/solid-query" import { QueryClient } from "@tanstack/solid-query"
import type { Config, OpencodeClient, Project } from "@opencode-ai/sdk/v2/client" import type { Config, OpencodeClient, Project, Session } from "@opencode-ai/sdk/v2/client"
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
import { bootstrapDirectory, loadPathQuery, loadProvidersQuery } from "./bootstrap" import { bootstrapDirectory, loadPathQuery, loadProvidersQuery } from "./bootstrap"
import type { State, VcsCache } from "./types" import type { State, VcsCache } from "./types"
import { createServerSession } from "../server-session"
import { ServerScope } from "@/utils/server-scope" import { ServerScope } from "@/utils/server-scope"
const provider = { all: new Map(), connected: [], default: {} } satisfies NormalizedProviderListResponse const provider = { all: new Map(), connected: [], default: {} } satisfies NormalizedProviderListResponse
describe("bootstrapDirectory", () => { function directoryState() {
test("marks a loading directory partial during bootstrap and complete after success", async () => { return createStore<State>({
const mcpReads: string[] = []
const [store, setStore] = createStore<State>({
status: "loading", status: "loading",
agent: [], agent: [],
command: [], command: [],
@@ -45,6 +44,12 @@ describe("bootstrapDirectory", () => {
part: {}, part: {},
part_text_accum_delta: {}, part_text_accum_delta: {},
}) })
}
describe("bootstrapDirectory", () => {
test("marks a loading directory partial during bootstrap and complete after success", async () => {
const mcpReads: string[] = []
const [store, setStore] = directoryState()
await bootstrapDirectory({ await bootstrapDirectory({
directory: "/project", directory: "/project",
@@ -93,6 +98,66 @@ describe("bootstrapDirectory", () => {
expect(store.status).toBe("complete") expect(store.status).toBe("complete")
expect(mcpReads).toEqual([]) expect(mcpReads).toEqual([])
}) })
test("seeds session status even while warming session info stalls", async () => {
const [store, setStore] = directoryState()
const stalled = Promise.withResolvers<never>()
const client = {
app: { agents: async () => ({ data: [{ name: "build", mode: "primary" }] }) },
config: { get: async () => ({ data: {} }) },
session: {
status: async () => ({ data: { ses_busy: { type: "busy" } } }),
get: () => stalled.promise,
},
vcs: { get: async () => ({ data: undefined }) },
command: { list: async () => ({ data: [] }) },
permission: { list: async () => ({ data: [] }) },
question: { list: async () => ({ data: [] }) },
v2: { reference: { list: async () => ({ data: { data: [] } }) } },
mcp: { status: async () => ({ data: {} }) },
provider: { list: async () => ({ data: { all: [], connected: [], default: {} } }) },
} as unknown as OpencodeClient
const session = createServerSession(client)
const stale: Session = {
id: "ses_stale",
slug: "ses_stale",
projectID: "project",
directory: "/project",
title: "stale",
version: "1",
time: { created: 1, updated: 1 },
}
session.remember(stale)
session.set("session_status", stale.id, { type: "busy" })
await bootstrapDirectory({
directory: "/project",
scope: ServerScope.local,
mcp: false,
global: {
config: {} satisfies Config,
path: { state: "", config: "", worktree: "/project", directory: "/project", home: "/home" },
project: [{ id: "project", worktree: "/project" } as Project],
provider,
},
sdk: client,
store,
setStore,
vcsCache: { setStore() {} } as unknown as VcsCache,
loadSessions() {},
translate: (key) => key,
queryClient: new QueryClient(),
session,
})
const deadline = Date.now() + 500
while (!session.data.session_working("ses_busy") && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 10))
}
expect(session.data.session_status["ses_busy"]?.type).toBe("busy")
expect(session.data.session_status[stale.id]).toBeUndefined()
})
}) })
describe("query keys", () => { describe("query keys", () => {
@@ -247,11 +247,11 @@ export async function bootstrapDirectory(input: {
() => () =>
retry(() => retry(() =>
input.sdk.session.status().then(async (x) => { input.sdk.session.status().then(async (x) => {
if (input.session) { if (!input.session) {
input.setStore("session_status", x.data!)
return
}
const statuses = x.data ?? {} const statuses = x.data ?? {}
await Promise.all(
Object.keys(statuses).map((sessionID) => input.session!.resolve(sessionID).catch(() => undefined)),
)
input.session.set( input.session.set(
"session_status", "session_status",
produce((draft) => { produce((draft) => {
@@ -264,8 +264,12 @@ export async function bootstrapDirectory(input: {
for (const [sessionID, status] of Object.entries(statuses)) { for (const [sessionID, status] of Object.entries(statuses)) {
input.session.set("session_status", sessionID, reconcile(status)) input.session.set("session_status", sessionID, reconcile(status))
} }
} // Warm session info only after seeding statuses so a stalled session
if (!input.session) input.setStore("session_status", x.data!) // fetch cannot park busy indicators behind it, mirroring how live
// session.status events apply first and resolve info in the background.
await Promise.all(
Object.keys(statuses).map((sessionID) => input.session!.resolve(sessionID).catch(() => undefined)),
)
}), }),
), ),
!seededProject && !seededProject &&