fix(tui): stabilize reload connection state

This commit is contained in:
Dax Raad 2026-06-30 22:31:51 -04:00
commit 8dd993d25a
16 changed files with 224 additions and 19 deletions

View file

@ -1148,7 +1148,7 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
<Show when={!startup.skipInitialLoading}>
<StartupLoading ready={ready} />
</Show>
<Show when={sdk.connection.status() === "reconnecting"}>
<Show when={sdk.connection.status() === "connecting"}>
<Reconnecting attempt={sdk.connection.attempt()} error={sdk.connection.error()} />
</Show>
</box>

View file

@ -93,6 +93,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
directory: process.cwd(),
})
const messageIndex = new Map<string, Map<string, number>>()
let bootstrapping: Promise<void> | undefined
const message = {
update(sessionID: string, fn: (messages: SessionMessage[], index: Map<string, number>) => void) {
@ -543,6 +544,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
// Authenticating an MCP integration reconnects its server, which emits mcp.status.changed,
// so the mcp list refreshes here rather than off integration.updated.
case "mcp.status.changed":
if (bootstrapping) break
void result.location.mcp.refresh(event.location)
break
}
@ -750,7 +752,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
}
async function bootstrap() {
const settled = await Promise.allSettled([
if (bootstrapping) return bootstrapping
bootstrapping = Promise.allSettled([
sdk.api.session
.list({
limit: 50,
@ -787,14 +790,23 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
result.location.skill.refresh(),
result.shell.refresh(),
])
for (const failure of settled.filter((item) => item.status === "rejected"))
console.error("Failed to refresh default location data", failure.reason)
.then((settled) => {
for (const failure of settled.filter((item) => item.status === "rejected"))
console.error("Failed to refresh default location data", failure.reason)
})
.finally(() => {
bootstrapping = undefined
})
return bootstrapping
}
onCleanup(
sdk.event.listen(({ details }) => {
if (details.type === "server.connected") {
void bootstrap()
return
}
handleEvent(details)
if (details.type === "server.connected") void bootstrap()
}),
)

View file

@ -5,7 +5,7 @@ import { onCleanup, onMount } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "./helper"
export type SDKConnectionStatus = "connecting" | "connected" | "reconnecting"
export type SDKConnectionStatus = "connected" | "connecting"
type SDKEventMap = { [Type in V2Event["type"]]: Extract<V2Event, { type: Type }> }
const connectTimeout = 2_000
@ -64,10 +64,11 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
return connection.signal.reason instanceof Error
? connection.signal.reason
: new Error("Event stream disconnected")
if (first.value.type !== "server.connected") return new Error("Event stream did not start with server.connected")
clearTimeout(timeout)
attempt = 0
setConnection({ status: "connected", attempt: 0, error: undefined })
events.emit(first.value.type, first.value)
setConnection({ status: "connected", attempt: 0, error: undefined })
connected()
while (!abort.signal.aborted && !controller.signal.aborted) {
const event = await iterator.next()
@ -84,7 +85,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
if (abort.signal.aborted || controller.signal.aborted) return
attempt += 1
setConnection({
status: "reconnecting",
status: "connecting",
attempt,
error: error instanceof Error ? error.message : String(error),
})