Przeglądaj źródła

fix(tui): preserve first prompt in fresh sessions

James Long 1 miesiąc temu
rodzic
commit
8ff12590a3

+ 1 - 0
packages/tui/src/component/prompt/index.tsx

@@ -1169,6 +1169,7 @@ export function Prompt(props: PromptProps) {
         route.navigate({
           type: "session",
           sessionID,
+          fresh: true,
         })
       }, 50)
     }

+ 1 - 0
packages/tui/src/context/route.tsx

@@ -11,6 +11,7 @@ export type HomeRoute = {
 export type SessionRoute = {
   type: "session"
   sessionID: string
+  fresh?: boolean
   prompt?: PromptInfo
 }
 

+ 4 - 1
packages/tui/src/routes/session/index.tsx

@@ -237,7 +237,10 @@ export function Session() {
   const toast = useToast()
   const sdk = useSDK()
   const editor = useEditorContext()
-  const rows = createSessionRows(() => route.sessionID)
+  const rows = createSessionRows(
+    () => route.sessionID,
+    () => route.fresh !== true,
+  )
 
   createEffect(
     on(descendantSessionIDs, (sessionIDs) => {

+ 2 - 1
packages/tui/src/routes/session/rows.ts

@@ -20,7 +20,7 @@ export type SessionRow =
     }
   | { type: "assistant-footer"; messageID: string }
 
-export function createSessionRows(sessionID: Accessor<string>) {
+export function createSessionRows(sessionID: Accessor<string>, refresh: Accessor<boolean> = () => true) {
   const data = useData()
   const [rows, setRows] = createStore<SessionRow[]>([])
   const revertBoundary = () => data.session.get(sessionID())?.revert?.messageID
@@ -54,6 +54,7 @@ export function createSessionRows(sessionID: Accessor<string>) {
   createEffect(
     on(sessionID, (id) => {
       setRows(reconcile(reduce()))
+      if (!refresh()) return
       void data.session.message.refresh(id).then(
         () => {
           if (sessionID() !== id) return

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

@@ -2066,6 +2066,76 @@ test("renders admitted prompts immediately and tracks them until promoted", asyn
   }
 })
 
+test("does not refresh a fresh session timeline", async () => {
+  const events = createEventStream()
+  const sessionID = "session-refresh-race"
+  const messageID = "msg-refresh-race"
+  let refreshes = 0
+  const calls = createFetch((url) => {
+    if (url.pathname === `/api/session/${sessionID}/message`) {
+      refreshes++
+      return json({ data: [], cursor: {} })
+    }
+  }, events)
+  let data!: ReturnType<typeof useData>
+  let rows!: ReturnType<typeof createSessionRows>
+
+  function Probe() {
+    data = useData()
+    rows = createSessionRows(
+      () => sessionID,
+      () => false,
+    )
+    return <box />
+  }
+
+  const app = await testRender(() => (
+    <TestTuiContexts>
+      <SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
+        <ProjectProvider>
+          <DataProvider>
+            <Probe />
+          </DataProvider>
+        </ProjectProvider>
+      </SDKProvider>
+    </TestTuiContexts>
+  ))
+
+  try {
+    emitEvent(events, {
+      id: "evt-refresh-race-admitted",
+      created: 1,
+      type: "session.input.admitted",
+      durable: durable(sessionID),
+      data: {
+        sessionID,
+        inputID: messageID,
+        input: { type: "user", data: { text: "do not lose me" }, delivery: "steer" },
+      },
+    })
+    await wait(() => data.session.message.get(sessionID, messageID)?.type === "user")
+
+    emitEvent(events, {
+      id: "evt-refresh-race-promoted",
+      created: 2,
+      type: "session.input.promoted",
+      durable: durable(sessionID, 1),
+      data: { sessionID, inputID: messageID },
+    })
+    await wait(() => data.session.input.list(sessionID).length === 0)
+
+    expect(refreshes).toBe(0)
+    expect(rows).toContainEqual({ type: "message", messageID })
+    expect(data.session.message.get(sessionID, messageID)).toMatchObject({
+      id: messageID,
+      type: "user",
+      text: "do not lose me",
+    })
+  } finally {
+    app.renderer.destroy()
+  }
+})
+
 test("projects live instruction updates with their message ID", async () => {
   const events = createEventStream()
   const calls = createFetch(undefined, events)