Quellcode durchsuchen

fix(tui): restore forms on reconnect

Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Kit Langton vor 1 Monat
Ursprung
Commit
9c54291ae8

+ 10 - 0
packages/tui/src/context/data.tsx

@@ -1074,6 +1074,16 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           )
           setStore("session", "permission", reconcile(permissions))
         }),
+        sdk.api.form.listRequests({ location: locationQuery(defaultLocation()) }).then((response) => {
+          const forms = mutable(response.data).reduce<Record<string, FormInfo[]>>(
+            (result, form) => ({
+              ...result,
+              [form.sessionID]: [...(result[form.sessionID] ?? []), form],
+            }),
+            {},
+          )
+          setStore("session", "form", reconcile(forms))
+        }),
         result.location.refresh(),
         result.location.agent.refresh(),
         result.location.integration.refresh(),

+ 46 - 0
packages/tui/test/cli/tui/data.test.tsx

@@ -1637,6 +1637,52 @@ test("adds, dismisses, and refreshes form requests", async () => {
   }
 })
 
+test("reconciles all pending form requests when the event stream reconnects", async () => {
+  const events = createEventStream()
+  let requests = [
+    { id: "frm_old", sessionID: "ses_old", mode: "form" as const, fields: [] },
+    { id: "frm_keep", sessionID: "ses_keep", mode: "url" as const, url: "https://example.com" },
+  ]
+  let calls = 0
+  const fetch = createFetch((url) => {
+    if (url.pathname !== "/api/form/request") return
+    calls++
+    return json({ location: { directory, project: { id: "proj_test", directory } }, data: requests })
+  }, events)
+  let data!: ReturnType<typeof useData>
+
+  function Probe() {
+    data = useData()
+    return <box />
+  }
+
+  const app = await testRender(() => (
+    <TestTuiContexts>
+      <SDKProvider client={createClient(fetch.fetch)} api={createApi(fetch.fetch)}>
+        <ProjectProvider>
+          <DataProvider>
+            <Probe />
+          </DataProvider>
+        </ProjectProvider>
+      </SDKProvider>
+    </TestTuiContexts>
+  ))
+
+  try {
+    await wait(() => data.session.form.list("ses_old")?.[0]?.id === "frm_old")
+    expect(data.session.form.list("ses_keep")?.[0]?.id).toBe("frm_keep")
+
+    requests = [{ id: "frm_new", sessionID: "ses_new", mode: "form" as const, fields: [] }]
+    events.disconnect()
+
+    await wait(() => calls === 2 && data.session.form.list("ses_new")?.[0]?.id === "frm_new")
+    expect(data.session.form.list("ses_old")).toBeUndefined()
+    expect(data.session.form.list("ses_keep")).toBeUndefined()
+  } finally {
+    app.renderer.destroy()
+  }
+})
+
 test("settles pending tools when a live failure arrives", async () => {
   const events = createEventStream()
   const calls = createFetch((url) => {

+ 2 - 0
packages/tui/test/fixture/tui-sdk.ts

@@ -103,6 +103,8 @@ export function createFetch(override?: FetchHandler, events?: ReturnType<typeof
     if (url.pathname === "/api/session/active") return json({ data: {} })
     if (url.pathname === "/api/permission/request")
       return json({ location: { directory, project: { id: "proj_test", directory: worktree } }, data: [] })
+    if (url.pathname === "/api/form/request")
+      return json({ location: { directory, project: { id: "proj_test", directory: worktree } }, data: [] })
     if (/^\/api\/session\/[^/]+\/form$/.test(url.pathname)) return json({ data: [] })
     if (
       ["/api/agent", "/api/model", "/api/provider", "/api/integration", "/api/command", "/api/skill"].includes(