fix(tui): prevent stale shell counts
This commit is contained in:
parent
e1abf59c59
commit
9609670544
2 changed files with 70 additions and 5 deletions
|
|
@ -114,6 +114,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
const sessionRefreshApplied = new Map<string, number>()
|
||||
const sessionUsage = new Map<string, { generation: number; cost: number; tokens: SessionV2Info["tokens"] }>()
|
||||
let connectionGeneration = 0
|
||||
let shellGeneration = 0
|
||||
let statusChanges: Set<string> | undefined
|
||||
let bootstrapping: Promise<void> | undefined
|
||||
|
||||
|
|
@ -758,6 +759,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
)
|
||||
break
|
||||
case "shell.created":
|
||||
shellGeneration++
|
||||
setStore("location", locationKey(event.location ?? defaultLocation()), (data) => ({
|
||||
...data,
|
||||
shell: { ...data?.shell, [event.data.info.id]: event.data.info },
|
||||
|
|
@ -765,6 +767,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
break
|
||||
case "shell.exited":
|
||||
case "shell.deleted":
|
||||
shellGeneration++
|
||||
if (event.location) {
|
||||
setStore("location", locationKey(event.location), (data) => ({
|
||||
...data,
|
||||
|
|
@ -932,12 +935,14 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
|||
.map((data) => data.shell?.[id])
|
||||
.find((shell) => shell !== undefined)
|
||||
},
|
||||
async refresh(ref?: LocationRef) {
|
||||
const result = await sdk.api.shell.list({ location: locationQuery(ref) })
|
||||
const key = locationKey(result.location)
|
||||
async refresh(ref?: LocationRef): Promise<void> {
|
||||
const generation = shellGeneration
|
||||
const response = await sdk.api.shell.list({ location: locationQuery(ref) })
|
||||
if (generation !== shellGeneration) return result.shell.refresh(ref)
|
||||
const key = locationKey(response.location)
|
||||
setStore("location", key, {
|
||||
...store.location[key],
|
||||
shell: Object.fromEntries(mutable(result.data).map((info) => [info.id, info])),
|
||||
shell: Object.fromEntries(mutable(response.data).map((info) => [info.id, info])),
|
||||
})
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { ProjectProvider } from "../../../src/context/project"
|
|||
import { SDKProvider } from "../../../src/context/sdk"
|
||||
import { DataProvider, useData } from "../../../src/context/data"
|
||||
import { createSessionRows, type SessionRow } from "../../../src/routes/session/rows"
|
||||
import { createApi, createClient, createEventStream, createFetch, directory, json } from "../../fixture/tui-sdk"
|
||||
import { createApi, createClient, createEventStream, createFetch, directory, json, worktree } from "../../fixture/tui-sdk"
|
||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||
|
||||
async function wait(fn: () => boolean, timeout = 2000) {
|
||||
|
|
@ -1452,6 +1452,66 @@ test("keeps shell state scoped to location", async () => {
|
|||
}
|
||||
})
|
||||
|
||||
test("retries a shell refresh when a lifecycle event races its response", async () => {
|
||||
const events = createEventStream()
|
||||
let requests = 0
|
||||
let resolveFirst!: (response: Response) => void
|
||||
const first = new Promise<Response>((resolve) => {
|
||||
resolveFirst = resolve
|
||||
})
|
||||
const location = { directory, project: { id: "proj_test", directory: worktree } }
|
||||
const shell = {
|
||||
id: "sh_stale",
|
||||
status: "running" as const,
|
||||
command: "bun test",
|
||||
cwd: directory,
|
||||
shell: "/bin/sh",
|
||||
file: "/tmp/opencode-shell",
|
||||
metadata: { sessionID: "ses_default" },
|
||||
time: { started: 1 },
|
||||
}
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname !== "/api/shell") return
|
||||
requests++
|
||||
if (requests === 1) return first
|
||||
return json({ location, data: [] })
|
||||
}, events)
|
||||
let data!: ReturnType<typeof useData>
|
||||
|
||||
function Probe() {
|
||||
data = useData()
|
||||
return <box />
|
||||
}
|
||||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
</DataProvider>
|
||||
</ProjectProvider>
|
||||
</SDKProvider>
|
||||
</TestTuiContexts>
|
||||
))
|
||||
|
||||
try {
|
||||
await wait(() => requests === 1)
|
||||
emitEvent(events, {
|
||||
id: "evt_shell_exited",
|
||||
created: 0,
|
||||
type: "shell.exited",
|
||||
data: { id: "sh_stale", exit: 0, status: "exited" },
|
||||
})
|
||||
resolveFirst(json({ location, data: [shell] }))
|
||||
|
||||
await wait(() => requests === 2)
|
||||
expect(data.shell.list()).toEqual([])
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("adds and dismisses permission requests from live events", async () => {
|
||||
const events = createEventStream()
|
||||
const calls = createFetch(undefined, events)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue