瀏覽代碼

chore: generate

opencode-agent[bot] 2 月之前
父節點
當前提交
5ca19947d9

+ 11 - 5
packages/app/src/context/directory-sync.ts

@@ -52,7 +52,10 @@ export const createDirSyncContext = (
       setStore("session", result.index, reconcile(session))
       return
     }
-    setStore("session", produce((draft) => void draft.splice(result.index, 0, session)))
+    setStore(
+      "session",
+      produce((draft) => void draft.splice(result.index, 0, session)),
+    )
   }
 
   return {
@@ -127,10 +130,13 @@ export const createDirSyncContext = (
       more: createMemo(() => current()[0].session.length >= current()[0].limit),
       archive: async (sessionID: string) => {
         await serverSDK.client.session.update({ sessionID, time: { archived: Date.now() } })
-        current()[1]("session", produce((draft) => {
-          const match = Binary.search(draft, sessionID, (session) => session.id)
-          if (match.found) draft.splice(match.index, 1)
-        }))
+        current()[1](
+          "session",
+          produce((draft) => {
+            const match = Binary.search(draft, sessionID, (session) => session.id)
+            if (match.found) draft.splice(match.index, 1)
+          }),
+        )
       },
     },
     mcp: {

+ 12 - 7
packages/app/src/context/global-sync/bootstrap.ts

@@ -241,13 +241,18 @@ export async function bootstrapDirectory(input: {
           input.sdk.session.status().then(async (x) => {
             if (input.session) {
               const statuses = x.data ?? {}
-              await Promise.all(Object.keys(statuses).map((sessionID) => input.session!.resolve(sessionID).catch(() => undefined)))
-              input.session.set("session_status", produce((draft) => {
-                for (const sessionID of Object.keys(draft)) {
-                  if (statuses[sessionID]) continue
-                  if (input.session?.get(sessionID)?.directory === input.directory) delete draft[sessionID]
-                }
-              }))
+              await Promise.all(
+                Object.keys(statuses).map((sessionID) => input.session!.resolve(sessionID).catch(() => undefined)),
+              )
+              input.session.set(
+                "session_status",
+                produce((draft) => {
+                  for (const sessionID of Object.keys(draft)) {
+                    if (statuses[sessionID]) continue
+                    if (input.session?.get(sessionID)?.directory === input.directory) delete draft[sessionID]
+                  }
+                }),
+              )
               for (const [sessionID, status] of Object.entries(statuses)) {
                 input.session.set("session_status", sessionID, reconcile(status))
               }

+ 18 - 2
packages/app/src/context/server-session.test.ts

@@ -69,12 +69,28 @@ describe("server session", () => {
     ctx.store.pin("active")
     ctx.store.optimistic.add({
       sessionID: "active",
-      message: { id: "message", sessionID: "active", role: "assistant", time: { created: 1 }, parentID: "parent", modelID: "model", providerID: "provider", mode: "build", agent: "agent", path: { cwd: "/repo", root: "/repo" }, cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } },
+      message: {
+        id: "message",
+        sessionID: "active",
+        role: "assistant",
+        time: { created: 1 },
+        parentID: "parent",
+        modelID: "model",
+        providerID: "provider",
+        mode: "build",
+        agent: "agent",
+        path: { cwd: "/repo", root: "/repo" },
+        cost: 0,
+        tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
+      },
       parts: [],
     })
 
     for (let index = 0; index < 50; index++) {
-      ctx.store.apply({ type: "session.status", properties: { sessionID: `session-${index}`, status: { type: "busy" } } })
+      ctx.store.apply({
+        type: "session.status",
+        properties: { sessionID: `session-${index}`, status: { type: "busy" } },
+      })
     }
 
     expect(ctx.store.data.message.active?.map((message) => message.id)).toEqual(["message"])

+ 159 - 71
packages/app/src/context/server-session.ts

@@ -115,9 +115,15 @@ export function createServerSession(client: OpencodeClient) {
       const preserve = new Set([
         ...pinned.keys(),
         ...requests.keys(),
-        ...Object.entries(data.permission).filter(([, items]) => items.length > 0).map(([sessionID]) => sessionID),
-        ...Object.entries(data.question).filter(([, items]) => items.length > 0).map(([sessionID]) => sessionID),
-        ...Object.entries(data.session_status).filter(([, status]) => status.type !== "idle").map(([sessionID]) => sessionID),
+        ...Object.entries(data.permission)
+          .filter(([, items]) => items.length > 0)
+          .map(([sessionID]) => sessionID),
+        ...Object.entries(data.question)
+          .filter(([, items]) => items.length > 0)
+          .map(([sessionID]) => sessionID),
+        ...Object.entries(data.session_status)
+          .filter(([, status]) => status.type !== "idle")
+          .map(([sessionID]) => sessionID),
       ])
       for (const sessionID of preserve) {
         let current = data.info[sessionID]
@@ -132,7 +138,10 @@ export function createServerSession(client: OpencodeClient) {
         if (!preserve.has(sessionID)) stale.push(sessionID)
       }
       stale.forEach((sessionID) => infoSeen.delete(sessionID))
-      setData("info", produce((draft) => stale.forEach((sessionID) => delete draft[sessionID])))
+      setData(
+        "info",
+        produce((draft) => stale.forEach((sessionID) => delete draft[sessionID])),
+      )
     }
     return session
   }
@@ -214,27 +223,39 @@ export function createServerSession(client: OpencodeClient) {
     )
   }
 
-  const protectedSessions = () => new Set([
-    ...pinned.keys(),
-    ...requests.keys(),
-    ...inflight.keys(),
-    ...inflightDiff.keys(),
-    ...inflightTodo.keys(),
-    ...optimistic.keys(),
-    ...Object.entries(data.permission).filter(([, items]) => items.length > 0).map(([sessionID]) => sessionID),
-    ...Object.entries(data.question).filter(([, items]) => items.length > 0).map(([sessionID]) => sessionID),
-    ...Object.entries(data.session_status).filter(([, status]) => status.type !== "idle").map(([sessionID]) => sessionID),
-  ])
+  const protectedSessions = () =>
+    new Set([
+      ...pinned.keys(),
+      ...requests.keys(),
+      ...inflight.keys(),
+      ...inflightDiff.keys(),
+      ...inflightTodo.keys(),
+      ...optimistic.keys(),
+      ...Object.entries(data.permission)
+        .filter(([, items]) => items.length > 0)
+        .map(([sessionID]) => sessionID),
+      ...Object.entries(data.question)
+        .filter(([, items]) => items.length > 0)
+        .map(([sessionID]) => sessionID),
+      ...Object.entries(data.session_status)
+        .filter(([, status]) => status.type !== "idle")
+        .map(([sessionID]) => sessionID),
+    ])
 
   const touch = (sessionID: string) =>
-    evict(pickSessionCacheEvictions({ seen, keep: sessionID, limit: SESSION_CACHE_LIMIT, preserve: protectedSessions() }))
+    evict(
+      pickSessionCacheEvictions({ seen, keep: sessionID, limit: SESSION_CACHE_LIMIT, preserve: protectedSessions() }),
+    )
 
   const fetchMessages = async (sessionID: string, limit: number, before?: string) => {
     const response = await retry(() => client.session.messages({ sessionID, limit, before }))
     const items = (response.data ?? []).filter((item) => !!item?.info?.id)
     return {
       session: items.map((item) => cleanMessage(item.info)).sort((a, b) => cmp(a.id, b.id)),
-      part: items.map((item) => ({ id: item.info.id, part: item.parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id)) })),
+      part: items.map((item) => ({
+        id: item.info.id,
+        part: item.parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id)),
+      })),
       cursor: response.response.headers.get("x-next-cursor") ?? undefined,
       complete: !response.response.headers.get("x-next-cursor"),
     }
@@ -284,7 +305,11 @@ export function createServerSession(client: OpencodeClient) {
   const prefetch = async (sessionID: string, limit: number) => {
     touch(sessionID)
     await inflight.get(sessionID)
-    if (Date.now() - (meta.at[sessionID] ?? 0) <= 15_000 && (meta.complete[sessionID] || (data.message[sessionID]?.length ?? 0) >= limit)) return
+    if (
+      Date.now() - (meta.at[sessionID] ?? 0) <= 15_000 &&
+      (meta.complete[sessionID] || (data.message[sessionID]?.length ?? 0) >= limit)
+    )
+      return
     await runInflight(inflight, sessionID, () => loadMessages(sessionID, limit))
   }
 
@@ -292,8 +317,22 @@ export function createServerSession(client: OpencodeClient) {
     const properties = event.properties
     if (!properties || typeof properties !== "object") return
     if ("sessionID" in properties && typeof properties.sessionID === "string") return properties.sessionID
-    if ("info" in properties && properties.info && typeof properties.info === "object" && "sessionID" in properties.info && typeof properties.info.sessionID === "string") return properties.info.sessionID
-    if ("part" in properties && properties.part && typeof properties.part === "object" && "sessionID" in properties.part && typeof properties.part.sessionID === "string") return properties.part.sessionID
+    if (
+      "info" in properties &&
+      properties.info &&
+      typeof properties.info === "object" &&
+      "sessionID" in properties.info &&
+      typeof properties.info.sessionID === "string"
+    )
+      return properties.info.sessionID
+    if (
+      "part" in properties &&
+      properties.part &&
+      typeof properties.part === "object" &&
+      "sessionID" in properties.part &&
+      typeof properties.part.sessionID === "string"
+    )
+      return properties.part.sessionID
   }
 
   const apply = (event: { type: string; properties?: unknown }) => {
@@ -315,7 +354,10 @@ export function createServerSession(client: OpencodeClient) {
       case "session.deleted": {
         const sessionID = (event.properties as { info: Session }).info.id
         infoSeen.delete(sessionID)
-        setData("info", produce((draft) => void delete draft[sessionID]))
+        setData(
+          "info",
+          produce((draft) => void delete draft[sessionID]),
+        )
         evict([sessionID])
         return
       }
@@ -343,30 +385,36 @@ export function createServerSession(client: OpencodeClient) {
         }
         const result = Binary.search(messages, info.id, (message) => message.id)
         if (result.found) setData("message", info.sessionID, result.index, reconcile(info))
-        if (!result.found) setData("message", info.sessionID, (value = []) => {
-          const next = value.slice()
-          next.splice(result.index, 0, info)
-          return next
-        })
+        if (!result.found)
+          setData("message", info.sessionID, (value = []) => {
+            const next = value.slice()
+            next.splice(result.index, 0, info)
+            return next
+          })
         return
       }
       case "message.removed": {
         const props = event.properties as { sessionID: string; messageID: string }
-        setData(produce((draft) => {
-          const messages = draft.message[props.sessionID]
-          if (messages) {
-            const result = Binary.search(messages, props.messageID, (message) => message.id)
-            if (result.found) messages.splice(result.index, 1)
-          }
-          for (const part of draft.part[props.messageID] ?? []) delete draft.part_text_accum_delta[part.id]
-          delete draft.part[props.messageID]
-        }))
+        setData(
+          produce((draft) => {
+            const messages = draft.message[props.sessionID]
+            if (messages) {
+              const result = Binary.search(messages, props.messageID, (message) => message.id)
+              if (result.found) messages.splice(result.index, 1)
+            }
+            for (const part of draft.part[props.messageID] ?? []) delete draft.part_text_accum_delta[part.id]
+            delete draft.part[props.messageID]
+          }),
+        )
         return
       }
       case "message.part.updated": {
         const part = (event.properties as { part: Part }).part
         if (SKIP_PARTS.has(part.type)) return
-        setData("part_text_accum_delta", produce((draft) => void delete draft[part.id]))
+        setData(
+          "part_text_accum_delta",
+          produce((draft) => void delete draft[part.id]),
+        )
         const parts = data.part[part.messageID]
         if (!parts) {
           setData("part", part.messageID, [part])
@@ -374,23 +422,26 @@ export function createServerSession(client: OpencodeClient) {
         }
         const result = Binary.search(parts, part.id, (item) => item.id)
         if (result.found) setData("part", part.messageID, result.index, reconcile(part))
-        if (!result.found) setData("part", part.messageID, (value = []) => {
-          const next = value.slice()
-          next.splice(result.index, 0, part)
-          return next
-        })
+        if (!result.found)
+          setData("part", part.messageID, (value = []) => {
+            const next = value.slice()
+            next.splice(result.index, 0, part)
+            return next
+          })
         return
       }
       case "message.part.removed": {
         const props = event.properties as { messageID: string; partID: string }
-        setData(produce((draft) => {
-          delete draft.part_text_accum_delta[props.partID]
-          const parts = draft.part[props.messageID]
-          if (!parts) return
-          const result = Binary.search(parts, props.partID, (part) => part.id)
-          if (result.found) parts.splice(result.index, 1)
-          if (parts.length === 0) delete draft.part[props.messageID]
-        }))
+        setData(
+          produce((draft) => {
+            delete draft.part_text_accum_delta[props.partID]
+            const parts = draft.part[props.messageID]
+            if (!parts) return
+            const result = Binary.search(parts, props.partID, (part) => part.id)
+            if (result.found) parts.splice(result.index, 1)
+            if (parts.length === 0) delete draft.part[props.messageID]
+          }),
+        )
         return
       }
       case "message.part.delta": {
@@ -401,13 +452,21 @@ export function createServerSession(client: OpencodeClient) {
         if (!result.found) return
         const field = props.field as keyof (typeof parts)[number]
         const current = parts[result.index]?.[field]
-        setData("part_text_accum_delta", props.partID, (value) => (value ?? (typeof current === "string" ? current : "")) + props.delta)
-        setData("part", props.messageID, produce((draft) => {
-          if (!draft) return
-          const part = draft[result.index]
-          const field = props.field as keyof typeof part
-          ;(part[field] as string) = ((part[field] as string | undefined) ?? "") + props.delta
-        }))
+        setData(
+          "part_text_accum_delta",
+          props.partID,
+          (value) => (value ?? (typeof current === "string" ? current : "")) + props.delta,
+        )
+        setData(
+          "part",
+          props.messageID,
+          produce((draft) => {
+            if (!draft) return
+            const part = draft[result.index]
+            const field = props.field as keyof typeof part
+            ;(part[field] as string) = ((part[field] as string | undefined) ?? "") + props.delta
+          }),
+        )
         return
       }
       case "permission.asked": {
@@ -415,16 +474,25 @@ export function createServerSession(client: OpencodeClient) {
         const permissions = data.permission[permission.sessionID] ?? []
         const result = Binary.search(permissions, permission.id, (item) => item.id)
         if (result.found) setData("permission", permission.sessionID, result.index, reconcile(permission))
-        if (!result.found) setData("permission", permission.sessionID, produce((draft = []) => void draft.splice(result.index, 0, permission)))
+        if (!result.found)
+          setData(
+            "permission",
+            permission.sessionID,
+            produce((draft = []) => void draft.splice(result.index, 0, permission)),
+          )
         return
       }
       case "permission.replied": {
         const props = event.properties as { sessionID: string; requestID: string }
-        setData("permission", props.sessionID, produce((draft) => {
-          if (!draft) return
-          const result = Binary.search(draft, props.requestID, (item) => item.id)
-          if (result.found) draft.splice(result.index, 1)
-        }))
+        setData(
+          "permission",
+          props.sessionID,
+          produce((draft) => {
+            if (!draft) return
+            const result = Binary.search(draft, props.requestID, (item) => item.id)
+            if (result.found) draft.splice(result.index, 1)
+          }),
+        )
         return
       }
       case "question.asked": {
@@ -432,17 +500,26 @@ export function createServerSession(client: OpencodeClient) {
         const questions = data.question[question.sessionID] ?? []
         const result = Binary.search(questions, question.id, (item) => item.id)
         if (result.found) setData("question", question.sessionID, result.index, reconcile(question))
-        if (!result.found) setData("question", question.sessionID, produce((draft = []) => void draft.splice(result.index, 0, question)))
+        if (!result.found)
+          setData(
+            "question",
+            question.sessionID,
+            produce((draft = []) => void draft.splice(result.index, 0, question)),
+          )
         return
       }
       case "question.replied":
       case "question.rejected": {
         const props = event.properties as { sessionID: string; requestID: string }
-        setData("question", props.sessionID, produce((draft) => {
-          if (!draft) return
-          const result = Binary.search(draft, props.requestID, (item) => item.id)
-          if (result.found) draft.splice(result.index, 1)
-        }))
+        setData(
+          "question",
+          props.sessionID,
+          produce((draft) => {
+            if (!draft) return
+            const result = Binary.search(draft, props.requestID, (item) => item.id)
+            if (result.found) draft.splice(result.index, 1)
+          }),
+        )
       }
     }
   }
@@ -478,12 +555,19 @@ export function createServerSession(client: OpencodeClient) {
         if (items) items.set(input.message.id, input)
         if (!items) optimistic.set(input.sessionID, new Map([[input.message.id, input]]))
         setData("message", input.sessionID, (messages = []) => merge(messages, [input.message]))
-        setData("part", input.message.id, input.parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id)))
+        setData(
+          "part",
+          input.message.id,
+          input.parts.filter((part) => !!part?.id).sort((a, b) => cmp(a.id, b.id)),
+        )
       },
       remove(input: { sessionID: string; messageID: string }) {
         clearOptimistic(input.sessionID, input.messageID)
         setData("message", input.sessionID, (messages) => messages?.filter((message) => message.id !== input.messageID))
-        setData("part", produce((draft) => void delete draft[input.messageID]))
+        setData(
+          "part",
+          produce((draft) => void delete draft[input.messageID]),
+        )
       },
     },
     diff(sessionID: string, options?: { force?: boolean }) {
@@ -509,7 +593,11 @@ export function createServerSession(client: OpencodeClient) {
       })
     },
     history: {
-      more: (sessionID: string) => data.message[sessionID] !== undefined && meta.limit[sessionID] !== undefined && !meta.complete[sessionID] && !!meta.cursor[sessionID],
+      more: (sessionID: string) =>
+        data.message[sessionID] !== undefined &&
+        meta.limit[sessionID] !== undefined &&
+        !meta.complete[sessionID] &&
+        !!meta.cursor[sessionID],
       loading: (sessionID: string) => meta.loading[sessionID] ?? false,
       async loadMore(sessionID: string, count = historyMessagePageSize) {
         touch(sessionID)

+ 3 - 1
packages/app/src/pages/layout.tsx

@@ -702,7 +702,9 @@ export default function LegacyLayout(props: ParentProps) {
   }
 
   async function prefetchMessages(directory: string, sessionID: string, token: number) {
-    await serverSync().session.prefetch(sessionID, prefetchChunk).catch(() => {})
+    await serverSync()
+      .session.prefetch(sessionID, prefetchChunk)
+      .catch(() => {})
     if (prefetchToken.value !== token) return
     for (const stale of markPrefetched(directory, sessionID)) serverSync().session.evict(stale)
   }

+ 11 - 4
packages/app/src/pages/layout/sidebar-items.tsx

@@ -153,16 +153,23 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
   const hasError = createMemo(() => notification.session.unseenHasError(props.session.id))
   const [sessionStore] = serverSync().child(props.session.directory)
   const hasPermissions = createMemo(() => {
-    return !!sessionPermissionRequest(sessionStore.session, serverSync().session.data.permission, props.session.id, (item) => {
-      return !permission.autoResponds(item, props.session.directory)
-    })
+    return !!sessionPermissionRequest(
+      sessionStore.session,
+      serverSync().session.data.permission,
+      props.session.id,
+      (item) => {
+        return !permission.autoResponds(item, props.session.directory)
+      },
+    )
   })
   const isWorking = createMemo(() => {
     if (hasPermissions()) return false
     return serverSync().session.data.session_working(props.session.id)
   })
 
-  const tint = createMemo(() => messageAgentColor(serverSync().session.data.message[props.session.id], sessionStore.agent))
+  const tint = createMemo(() =>
+    messageAgentColor(serverSync().session.data.message[props.session.id], sessionStore.agent),
+  )
   const tooltip = createMemo(() => props.showTooltip ?? (props.mobile || !props.sidebarExpanded()))
   const currentChild = createMemo(() => {
     if (!props.showChild) return

+ 1 - 3
packages/app/src/pages/session.tsx

@@ -516,9 +516,7 @@ export default function Page() {
         todoTimer = undefined
         if (!id) return
         if (status === "idle" && !blocked) return
-        const cached = untrack(
-          () => sync().data.todo[id] !== undefined,
-        )
+        const cached = untrack(() => sync().data.todo[id] !== undefined)
 
         todoFrame = requestAnimationFrame(() => {
           todoFrame = undefined