瀏覽代碼

chore: generate

opencode-agent[bot] 3 月之前
父節點
當前提交
9c7de47031

+ 1 - 1
bun.lock

@@ -719,10 +719,10 @@
   "patchedDependencies": {
     "solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
     "@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch",
+    "virtua@0.49.1": "patches/virtua@0.49.1.patch",
     "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
     "@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch",
     "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
-    "virtua@0.49.1": "patches/virtua@0.49.1.patch",
   },
   "overrides": {
     "@opentui/core": "catalog:",

+ 83 - 41
packages/app/e2e/regression/session-timeline-collapse-state.spec.ts

@@ -168,7 +168,9 @@ test.describe("regression: session timeline local row state", () => {
       },
     })
 
-    await expect(wrapper.locator('[data-slot="diff-changes-additions"]').filter({ hasText: "+2" }).first()).toBeVisible({ timeout: 10_000 })
+    await expect(wrapper.locator('[data-slot="diff-changes-additions"]').filter({ hasText: "+2" }).first()).toBeVisible(
+      { timeout: 10_000 },
+    )
     expect(await readDiffProbe(page)).toEqual({ fileMarker: "before", shadowRoots: 0, toolMarker: "before" })
   })
 })
@@ -194,23 +196,29 @@ async function expectExpanded(locator: Locator, expected: boolean) {
 }
 
 async function readToolState(page: Page) {
-  return page.locator(`[data-timeline-part-id="${editPartID}"]`).first().evaluate((element, textPartID) => ({
-    expanded: (() => {
-      const trigger = element.querySelector('[data-slot="collapsible-trigger"]')
-      const aria = trigger?.getAttribute("aria-expanded")
-      if (aria === "true") return true
-      if (aria === "false") return false
-
-      const root = element.querySelector('[data-component="collapsible"]')
-      if (root?.hasAttribute("data-expanded")) return true
-      if (root?.hasAttribute("data-closed")) return false
-
-      const content = element.querySelector<HTMLElement>('[data-slot="collapsible-content"]')
-      return !!content && content.getBoundingClientRect().height > 0
-    })(),
-    row: element.closest("[data-timeline-row]")?.getAttribute("data-timeline-row"),
-    streamedTextVisible: !!document.querySelector(`[data-timeline-part-id="${textPartID}"]`),
-  }), textPartID)
+  return page
+    .locator(`[data-timeline-part-id="${editPartID}"]`)
+    .first()
+    .evaluate(
+      (element, textPartID) => ({
+        expanded: (() => {
+          const trigger = element.querySelector('[data-slot="collapsible-trigger"]')
+          const aria = trigger?.getAttribute("aria-expanded")
+          if (aria === "true") return true
+          if (aria === "false") return false
+
+          const root = element.querySelector('[data-component="collapsible"]')
+          if (root?.hasAttribute("data-expanded")) return true
+          if (root?.hasAttribute("data-closed")) return false
+
+          const content = element.querySelector<HTMLElement>('[data-slot="collapsible-content"]')
+          return !!content && content.getBoundingClientRect().height > 0
+        })(),
+        row: element.closest("[data-timeline-row]")?.getAttribute("data-timeline-row"),
+        streamedTextVisible: !!document.querySelector(`[data-timeline-part-id="${textPartID}"]`),
+      }),
+      textPartID,
+    )
 }
 
 async function installDiffProbe(page: Page) {
@@ -231,27 +239,33 @@ async function installDiffProbe(page: Page) {
 }
 
 async function markDiffProbe(page: Page) {
-  await page.locator(`[data-timeline-part-id="${editPartID}"]`).first().evaluate((element) => {
-    const tool = element as HTMLElement
-    const file = tool.querySelector<HTMLElement>('[data-component="file"][data-mode="diff"]')
-    if (!file) throw new Error("missing edit diff file")
-
-    tool.dataset.timelineProbe = "before"
-    file.dataset.timelineProbe = "before"
-    window.__timelineDiffProbe.reset()
-  })
+  await page
+    .locator(`[data-timeline-part-id="${editPartID}"]`)
+    .first()
+    .evaluate((element) => {
+      const tool = element as HTMLElement
+      const file = tool.querySelector<HTMLElement>('[data-component="file"][data-mode="diff"]')
+      if (!file) throw new Error("missing edit diff file")
+
+      tool.dataset.timelineProbe = "before"
+      file.dataset.timelineProbe = "before"
+      window.__timelineDiffProbe.reset()
+    })
 }
 
 async function readDiffProbe(page: Page) {
-  return page.locator(`[data-timeline-part-id="${editPartID}"]`).first().evaluate((element) => {
-    const tool = element as HTMLElement
-    const file = tool.querySelector<HTMLElement>('[data-component="file"][data-mode="diff"]')
-    return {
-      fileMarker: file?.dataset.timelineProbe,
-      shadowRoots: window.__timelineDiffProbe.shadowRoots(),
-      toolMarker: tool.dataset.timelineProbe,
-    }
-  })
+  return page
+    .locator(`[data-timeline-part-id="${editPartID}"]`)
+    .first()
+    .evaluate((element) => {
+      const tool = element as HTMLElement
+      const file = tool.querySelector<HTMLElement>('[data-component="file"][data-mode="diff"]')
+      return {
+        fileMarker: file?.dataset.timelineProbe,
+        shadowRoots: window.__timelineDiffProbe.shadowRoots(),
+        toolMarker: tool.dataset.timelineProbe,
+      }
+    })
 }
 
 function editPartWithAdditions(additions: number) {
@@ -292,10 +306,23 @@ async function mockServer(page: Page, events: EventPayload[]) {
 
     const path = url.pathname
     if (path === "/global/event") return sse(route, events.splice(0))
-    if (path === "/global/config" || path === "/config" || path === "/provider/auth" || path === "/mcp" || path === "/session/status") return json(route, {})
-    if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/status", "/vcs/diff"].includes(path)) return json(route, [])
+    if (
+      path === "/global/config" ||
+      path === "/config" ||
+      path === "/provider/auth" ||
+      path === "/mcp" ||
+      path === "/session/status"
+    )
+      return json(route, {})
+    if (
+      ["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/status", "/vcs/diff"].includes(
+        path,
+      )
+    )
+      return json(route, [])
     if (path === "/provider") return json(route, provider())
-    if (path === "/path") return json(route, { state: directory, config: directory, worktree: directory, directory, home: "C:/OpenCode" })
+    if (path === "/path")
+      return json(route, { state: directory, config: directory, worktree: directory, directory, home: "C:/OpenCode" })
     if (path === "/project") return json(route, [project()])
     if (path === "/project/current") return json(route, project())
     if (path === "/agent") return json(route, [{ name: "build", mode: "primary" }])
@@ -309,11 +336,26 @@ async function mockServer(page: Page, events: EventPayload[]) {
 }
 
 function project() {
-  return { id: projectID, worktree: directory, vcs: "git", name: "timeline-state-regression", time: { created: 1700000000000, updated: 1700000000000 }, sandboxes: [] }
+  return {
+    id: projectID,
+    worktree: directory,
+    vcs: "git",
+    name: "timeline-state-regression",
+    time: { created: 1700000000000, updated: 1700000000000 },
+    sandboxes: [],
+  }
 }
 
 function session() {
-  return { id: sessionID, slug: "timeline-state-regression", projectID, directory, title, version: "dev", time: { created: 1700000000000, updated: 1700000000000 } }
+  return {
+    id: sessionID,
+    slug: "timeline-state-regression",
+    projectID,
+    directory,
+    title,
+    version: "dev",
+    time: { created: 1700000000000, updated: 1700000000000 },
+  }
 }
 
 function provider() {

+ 34 - 11
packages/app/e2e/regression/session-timeline-context-resize.spec.ts

@@ -8,12 +8,12 @@ const model = { providerID: "opencode", modelID: "claude-opus-4-6", variant: "ma
 const contextIDs = ["prt_0100_read", "prt_0101_glob", "prt_0102_grep", "prt_0103_list"]
 const followingTextID = "prt_0104_text"
 
-type Message = { info: Record<string, unknown> & { id: string; role: "user" | "assistant" }; parts: Record<string, unknown>[] }
+type Message = {
+  info: Record<string, unknown> & { id: string; role: "user" | "assistant" }
+  parts: Record<string, unknown>[]
+}
 
-const messages = [
-  ...Array.from({ length: 8 }, (_, index) => turn(index, false)).flat(),
-  ...turn(10, true),
-]
+const messages = [...Array.from({ length: 8 }, (_, index) => turn(index, false)).flat(), ...turn(10, true)]
 
 test.describe("regression: session timeline context group resize", () => {
   test("remeasures a recent explored context group before the next paint", async ({ page }) => {
@@ -76,7 +76,8 @@ async function sampleExpansion(page: Page) {
         const trigger = context?.querySelector<HTMLElement>('[data-slot="collapsible-trigger"]')
         const contextRow = context?.closest<HTMLElement>('[data-timeline-row="AssistantPart"]')
         const textRow = text?.closest<HTMLElement>('[data-timeline-row="AssistantPart"]')
-        if (!context || !text || !scroller || !trigger || !contextRow || !textRow) throw new Error("missing regression nodes")
+        if (!context || !text || !scroller || !trigger || !contextRow || !textRow)
+          throw new Error("missing regression nodes")
 
         scroller.scrollTop = scroller.scrollHeight
         const samples: {
@@ -213,10 +214,17 @@ async function mockServer(page: Page) {
 
     const path = url.pathname
     if (path === "/global/event" || path === "/event") return sse(route)
-    if (["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"].includes(path)) return json(route, {})
-    if (["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/status", "/vcs/diff"].includes(path)) return json(route, [])
+    if (["/global/config", "/config", "/provider/auth", "/mcp", "/session/status"].includes(path))
+      return json(route, {})
+    if (
+      ["/skill", "/command", "/lsp", "/formatter", "/permission", "/question", "/vcs/status", "/vcs/diff"].includes(
+        path,
+      )
+    )
+      return json(route, [])
     if (path === "/provider") return json(route, provider())
-    if (path === "/path") return json(route, { state: directory, config: directory, worktree: directory, directory, home: "C:/OpenCode" })
+    if (path === "/path")
+      return json(route, { state: directory, config: directory, worktree: directory, directory, home: "C:/OpenCode" })
     if (path === "/project") return json(route, [project()])
     if (path === "/project/current") return json(route, project())
     if (path === "/agent") return json(route, [{ name: "build", mode: "primary" }])
@@ -238,11 +246,26 @@ function id(prefix: string, index: number) {
 }
 
 function project() {
-  return { id: projectID, worktree: directory, vcs: "git", name: "context-resize-regression", time: { created: 1700000000000, updated: 1700000000000 }, sandboxes: [] }
+  return {
+    id: projectID,
+    worktree: directory,
+    vcs: "git",
+    name: "context-resize-regression",
+    time: { created: 1700000000000, updated: 1700000000000 },
+    sandboxes: [],
+  }
 }
 
 function session() {
-  return { id: sessionID, slug: "context-resize-regression", projectID, directory, title, version: "dev", time: { created: 1700000000000, updated: 1700000000000 } }
+  return {
+    id: sessionID,
+    slug: "context-resize-regression",
+    projectID,
+    directory,
+    title,
+    version: "dev",
+    time: { created: 1700000000000, updated: 1700000000000 },
+  }
 }
 
 function provider() {

+ 12 - 3
packages/app/src/pages/session/message-timeline.tsx

@@ -1018,7 +1018,9 @@ export function MessageTimeline(props: {
       return (
         <ContextToolGroup
           parts={parts()}
-          busy={workingTurn(row().userMessageID) && lastAssistantGroupKey().get(row().userMessageID) === row().group.key}
+          busy={
+            workingTurn(row().userMessageID) && lastAssistantGroupKey().get(row().userMessageID) === row().group.key
+          }
           onSizeChange={measureTimeline}
         />
       )
@@ -1149,7 +1151,11 @@ export function MessageTimeline(props: {
               {(message) => (
                 <div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
                   <div data-slot="session-turn-message-content" aria-live="off">
-                    <Message message={message()} parts={getMsgParts(userMessageRow().userMessageID)} actions={props.actions} />
+                    <Message
+                      message={message()}
+                      parts={getMsgParts(userMessageRow().userMessageID)}
+                      actions={props.actions}
+                    />
                   </div>
                 </div>
               )}
@@ -1178,7 +1184,10 @@ export function MessageTimeline(props: {
         return (
           <TimelineRowFrame row={assistantPartRow}>
             <div data-slot="session-turn-message-container" class="w-full px-4 md:px-5">
-              <div data-slot="session-turn-assistant-content" aria-hidden={workingTurn(assistantPartRow().userMessageID)}>
+              <div
+                data-slot="session-turn-assistant-content"
+                aria-hidden={workingTurn(assistantPartRow().userMessageID)}
+              >
                 {renderAssistantPartGroup(assistantPartRow)}
               </div>
             </div>

+ 6 - 2
packages/ui/src/components/file.tsx

@@ -1089,8 +1089,12 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
       return sampledChecksum(contents)
     }
 
-    const before = local.before ? { ...local.before, contents: beforeContents, cacheKey: cacheKey(beforeContents) } : undefined
-    const after = local.after ? { ...local.after, contents: afterContents, cacheKey: cacheKey(afterContents) } : undefined
+    const before = local.before
+      ? { ...local.before, contents: beforeContents, cacheKey: cacheKey(beforeContents) }
+      : undefined
+    const after = local.after
+      ? { ...local.after, contents: afterContents, cacheKey: cacheKey(afterContents) }
+      : undefined
     const targetChanged =
       local.fileDiff !== undefined
         ? instanceFileDiff !== local.fileDiff

+ 6 - 1
packages/ui/src/components/message-part.tsx

@@ -2278,7 +2278,12 @@ ToolRegistry.register({
               }
             >
               <div data-component="apply-patch-file-diff">
-                <Dynamic component={fileComponent} mode="diff" virtualize={props.virtualizeDiff} fileDiff={single()!.view.fileDiff} />
+                <Dynamic
+                  component={fileComponent}
+                  mode="diff"
+                  virtualize={props.virtualizeDiff}
+                  fileDiff={single()!.view.fileDiff}
+                />
               </div>
             </ToolFileAccordion>
           </BasicTool>

+ 2 - 1
packages/ui/src/components/session-diff.test.ts

@@ -71,7 +71,8 @@ describe("session diff", () => {
   test("keeps capped header-only patches partial", () => {
     const fileDiff = resolveFileDiff({
       file: "a.ts",
-      patch: "Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n",
+      patch:
+        "Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n",
     })
 
     expect(fileDiff.name).toBe("a.ts")

+ 1 - 6
packages/ui/src/components/tool-error-card.tsx

@@ -92,12 +92,7 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
 
   return (
     <Card {...rest} data-kind="tool-error-card" data-open={open() ? "true" : "false"} variant="error">
-      <Collapsible
-        class="tool-collapsible"
-        data-open={open() ? "true" : "false"}
-        open={open()}
-        onOpenChange={setOpen}
-      >
+      <Collapsible class="tool-collapsible" data-open={open() ? "true" : "false"} open={open()} onOpenChange={setOpen}>
         <Collapsible.Trigger>
           <div data-component="tool-trigger">
             <div data-slot="basic-tool-tool-trigger-content">