opencode-agent[bot] 2 месяцев назад
Родитель
Сommit
f6197cefe1

+ 6 - 4
packages/app/src/components/prompt-input/submit.ts

@@ -547,10 +547,12 @@ export function createPromptSubmit(input: PromptSubmitInput) {
         }, timeoutMs)
       })
 
-      const result = await Promise.race([WorktreeState.wait(sdk.scope, sessionDirectory), abortWait, timeout]).finally(() => {
-        if (timer.id === undefined) return
-        clearTimeout(timer.id)
-      })
+      const result = await Promise.race([WorktreeState.wait(sdk.scope, sessionDirectory), abortWait, timeout]).finally(
+        () => {
+          if (timer.id === undefined) return
+          clearTimeout(timer.id)
+        },
+      )
       pending.delete(pendingKey(session.id))
       if (controller.signal.aborted) return false
       if (result.status === "failed") throw new Error(result.message)

+ 5 - 1
packages/app/src/context/comments.tsx

@@ -207,7 +207,11 @@ export const { use: useComments, provider: CommentsProvider } = createSimpleCont
       (key) => {
         const decoded = decodeSessionKey(key)
         return createRoot((dispose) => ({
-          value: createCommentSession(serverSDK.scope, decoded.dir, decoded.id === WORKSPACE_KEY ? undefined : decoded.id),
+          value: createCommentSession(
+            serverSDK.scope,
+            decoded.dir,
+            decoded.id === WORKSPACE_KEY ? undefined : decoded.id,
+          ),
           dispose,
         }))
       },

+ 3 - 1
packages/app/src/context/file.tsx

@@ -64,7 +64,9 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
 
     const scope = createMemo(() => sdk.directory)
     const path = createPathHelpers(scope)
-    const tabs = layout.tabs(() => SessionStateKey.from(serverSDK.scope, SessionRouteKey.fromRoute(params.dir, params.id)))
+    const tabs = layout.tabs(() =>
+      SessionStateKey.from(serverSDK.scope, SessionRouteKey.fromRoute(params.dir, params.id)),
+    )
 
     const inflight = new Map<string, Promise<void>>()
     const [store, setStore] = createStore<{

+ 5 - 1
packages/app/src/context/global-sync/bootstrap.test.ts

@@ -99,6 +99,10 @@ describe("query keys", () => {
 
     expect([...loadPathQuery(ServerScope.local, "/repo", client).queryKey]).toEqual(["local", "/repo", "path"])
     expect([...loadPathQuery(remote, "/repo", client).queryKey]).toEqual(["https://debian.example", "/repo", "path"])
-    expect([...loadProvidersQuery(remote, null, client).queryKey]).toEqual(["https://debian.example", null, "providers"])
+    expect([...loadProvidersQuery(remote, null, client).queryKey]).toEqual([
+      "https://debian.example",
+      null,
+      "providers",
+    ])
   })
 })

+ 33 - 4
packages/app/src/context/global-sync/session-prefetch.test.ts

@@ -26,7 +26,12 @@ describe("session prefetch", () => {
       at: 123,
     })
 
-    expect(getSessionPrefetch(scope, "/tmp/a", "ses_1")).toEqual({ limit: 200, cursor: "abc", complete: false, at: 123 })
+    expect(getSessionPrefetch(scope, "/tmp/a", "ses_1")).toEqual({
+      limit: 200,
+      cursor: "abc",
+      complete: false,
+      at: 123,
+    })
     expect(getSessionPrefetch(scope, "/tmp/b", "ses_1")).toBeUndefined()
 
     clearSessionPrefetch(scope, "/tmp/a", ["ses_1"])
@@ -57,9 +62,33 @@ describe("session prefetch", () => {
   })
 
   test("clears a whole directory", () => {
-    setSessionPrefetch({ scope, directory: "/tmp/d", sessionID: "ses_1", limit: 10, cursor: "a", complete: true, at: 1 })
-    setSessionPrefetch({ scope, directory: "/tmp/d", sessionID: "ses_2", limit: 20, cursor: "b", complete: false, at: 2 })
-    setSessionPrefetch({ scope, directory: "/tmp/e", sessionID: "ses_1", limit: 30, cursor: "c", complete: true, at: 3 })
+    setSessionPrefetch({
+      scope,
+      directory: "/tmp/d",
+      sessionID: "ses_1",
+      limit: 10,
+      cursor: "a",
+      complete: true,
+      at: 1,
+    })
+    setSessionPrefetch({
+      scope,
+      directory: "/tmp/d",
+      sessionID: "ses_2",
+      limit: 20,
+      cursor: "b",
+      complete: false,
+      at: 2,
+    })
+    setSessionPrefetch({
+      scope,
+      directory: "/tmp/e",
+      sessionID: "ses_1",
+      limit: 30,
+      cursor: "c",
+      complete: true,
+      at: 3,
+    })
 
     clearSessionPrefetchDirectory(scope, "/tmp/d")
 

+ 8 - 2
packages/app/src/context/server-sync.tsx

@@ -63,7 +63,11 @@ export const loadLspQuery = (scope: ServerScope, directory: string, sdk: Opencod
     queryFn: () => sdk.lsp.status().then((r) => r.data ?? []),
   })
 
-function makeQueryOptionsApi(scope: ServerScope, serverSDK: () => OpencodeClient, sdkFor: (dir: PathKey) => OpencodeClient) {
+function makeQueryOptionsApi(
+  scope: ServerScope,
+  serverSDK: () => OpencodeClient,
+  sdkFor: (dir: PathKey) => OpencodeClient,
+) {
   return {
     globalConfig: () => loadGlobalConfigQuery(scope, serverSDK()),
     projects: () => loadProjectsQuery(scope, serverSDK()),
@@ -447,7 +451,9 @@ export function createServerSyncContextInner(_serverSDK?: ServerSDK) {
       // Invalidate all provider queries so newly configured custom providers
       // appear immediately in the available provider list across all directories.
       queryClient.invalidateQueries({ queryKey: [serverSDK.scope, null, "providers"] })
-      queryClient.invalidateQueries({ predicate: (query) => query.queryKey[0] === serverSDK.scope && query.queryKey[2] === "providers" })
+      queryClient.invalidateQueries({
+        predicate: (query) => query.queryKey[0] === serverSDK.scope && query.queryKey[2] === "providers",
+      })
     },
   }))
 

+ 5 - 1
packages/app/src/context/server.tsx

@@ -80,7 +80,11 @@ export function createServerProjects<T extends ServerProjectState>(input: {
       setStore("projects", scope, [{ worktree: directory, expanded: true }, ...current()])
     },
     close(directory: string) {
-      setStore("projects", input.scope(), current().filter((project) => project.worktree !== directory))
+      setStore(
+        "projects",
+        input.scope(),
+        current().filter((project) => project.worktree !== directory),
+      )
     },
     expand(directory: string) {
       const index = current().findIndex((project) => project.worktree === directory)

+ 40 - 15
packages/app/src/pages/home.tsx

@@ -118,14 +118,23 @@ function createHomeSessionStatus(input: {
   const notification = useNotification()
   const permission = usePermission()
   const sessionStore = createMemo(() => input.sync().child(input.record().session.directory, { bootstrap: false })[0])
-  const unseenCount = createMemo(() => (input.activeServer() ? notification.session.unseenCount(input.record().session.id) : 0))
-  const hasError = createMemo(() => input.activeServer() && notification.session.unseenHasError(input.record().session.id))
+  const unseenCount = createMemo(() =>
+    input.activeServer() ? notification.session.unseenCount(input.record().session.id) : 0,
+  )
+  const hasError = createMemo(
+    () => input.activeServer() && notification.session.unseenHasError(input.record().session.id),
+  )
   const hasPermissions = createMemo(
     () =>
       input.activeServer() &&
-      !!sessionPermissionRequest(sessionStore().session, sessionStore().permission, input.record().session.id, (item) => {
-        return !permission.autoResponds(item, input.record().session.directory)
-      }),
+      !!sessionPermissionRequest(
+        sessionStore().session,
+        sessionStore().permission,
+        input.record().session.id,
+        (item) => {
+          return !permission.autoResponds(item, input.record().session.directory)
+        },
+      ),
   )
   const serverStatus = createMemo(() =>
     homeSessionServerStatus(input.activeServer(), () => ({
@@ -278,7 +287,13 @@ function HomeDesign() {
 
   function selectProject(conn: ServerConnection.Any, directory: string) {
     const key = ServerConnection.key(conn)
-    if (!global.createServerCtx(conn).projects.list().some((project) => project.worktree === directory)) return
+    if (
+      !global
+        .createServerCtx(conn)
+        .projects.list()
+        .some((project) => project.worktree === directory)
+    )
+      return
     setSelection(toggleHomeProjectSelection(state.selection, key, directory))
   }
 
@@ -534,11 +549,7 @@ function HomeProjectColumn(props: {
                 </div>
                 <Show when={healthy()}>
                   <div class="mx-3 h-px bg-v2-border-border-base" />
-                  <HomeProjectList
-                    {...props}
-                    server={item}
-                    projects={serverCtx.projects.list()}
-                  />
+                  <HomeProjectList {...props} server={item} projects={serverCtx.projects.list()} />
                 </Show>
               </div>
             )
@@ -586,7 +597,10 @@ function HomeProjectList(props: {
           <HomeProjectRow
             project={project}
             server={props.server}
-            selected={props.selected.server === ServerConnection.key(props.server) && props.selected.directory === project.worktree}
+            selected={
+              props.selected.server === ServerConnection.key(props.server) &&
+              props.selected.directory === project.worktree
+            }
             unseenCount={props.unseenCount(props.server, project)}
             selectProject={props.selectProject}
             openNewSession={props.openNewSession}
@@ -662,7 +676,10 @@ function HomeProjectRow(props: {
               <MenuV2.Item onSelect={() => props.editProject(props.server, props.project)}>
                 {props.language.t("common.edit")}
               </MenuV2.Item>
-              <MenuV2.Item disabled={props.unseenCount === 0} onSelect={() => props.clearNotifications(props.server, props.project)}>
+              <MenuV2.Item
+                disabled={props.unseenCount === 0}
+                onSelect={() => props.clearNotifications(props.server, props.project)}
+              >
                 {props.language.t("sidebar.project.clearNotifications")}
               </MenuV2.Item>
               <MenuV2.Separator />
@@ -902,7 +919,11 @@ function HomeSessionSearchResultRow(props: {
   onHighlight: () => void
   onSelect: (session: Session) => void
 }) {
-  const status = createHomeSessionStatus({ record: () => props.record, sync: () => props.sync, activeServer: () => props.activeServer })
+  const status = createHomeSessionStatus({
+    record: () => props.record,
+    sync: () => props.sync,
+    activeServer: () => props.activeServer,
+  })
   const title = createMemo(() => sessionTitle(props.record.session.title) || props.record.session.id)
 
   const key = () => homeSessionSearchKey(props.record)
@@ -993,7 +1014,11 @@ function HomeSessionRow(props: {
   activeServer: boolean
   openSession: (session: Session) => void
 }) {
-  const status = createHomeSessionStatus({ record: () => props.record, sync: () => props.sync, activeServer: () => props.activeServer })
+  const status = createHomeSessionStatus({
+    record: () => props.record,
+    sync: () => props.sync,
+    activeServer: () => props.activeServer,
+  })
   const title = createMemo(() => sessionTitle(props.record.session.title) || props.record.session.id)
 
   return (

+ 13 - 3
packages/app/src/pages/layout/helpers.test.ts

@@ -227,7 +227,9 @@ describe("layout workspace helpers", () => {
   })
 
   test("scopes home project selection by server", () => {
-    expect(toggleHomeProjectSelection(undefined, serverKey("https://debian.example"), "/home/luke/repos/amazon")).toEqual({
+    expect(
+      toggleHomeProjectSelection(undefined, serverKey("https://debian.example"), "/home/luke/repos/amazon"),
+    ).toEqual({
       server: serverKey("https://debian.example"),
       directory: "/home/luke/repos/amazon",
     })
@@ -270,11 +272,19 @@ describe("layout workspace helpers", () => {
   })
 
   test("defers home project navigation until its server is active", () => {
-    expect(homeProjectNavigation(serverKey("sidecar"), serverKey("https://debian.example"), "/YW1hem9u/session")).toEqual({
+    expect(
+      homeProjectNavigation(serverKey("sidecar"), serverKey("https://debian.example"), "/YW1hem9u/session"),
+    ).toEqual({
       server: serverKey("https://debian.example"),
       href: "/YW1hem9u/session",
     })
-    expect(homeProjectNavigation(serverKey("https://debian.example"), serverKey("https://debian.example"), "/YW1hem9u/session")).toEqual({
+    expect(
+      homeProjectNavigation(
+        serverKey("https://debian.example"),
+        serverKey("https://debian.example"),
+        "/YW1hem9u/session",
+      ),
+    ).toEqual({
       href: "/YW1hem9u/session",
     })
   })

+ 5 - 1
packages/app/src/pages/layout/project-avatar-state.ts

@@ -4,7 +4,11 @@ import { useNotification } from "@/context/notification"
 import { usePermission } from "@/context/permission"
 import { sessionPermissionRequest } from "@/pages/session/composer/session-request-tree"
 
-export function useSessionTabAvatarState(directory: Accessor<string>, sessionId: Accessor<string>, active: Accessor<boolean> = () => true) {
+export function useSessionTabAvatarState(
+  directory: Accessor<string>,
+  sessionId: Accessor<string>,
+  active: Accessor<boolean> = () => true,
+) {
   const globalSync = useServerSync()
   const notification = useNotification()
   const permission = usePermission()

+ 9 - 5
packages/app/src/utils/server-scope.test.ts

@@ -3,13 +3,15 @@ import { ScopedKey, ServerScope, SessionRouteKey, SessionStateKey, migrateLegacy
 
 describe("ServerScope", () => {
   test("uses a stable local scope for the canonical sidecar", () => {
-    expect(String(ServerScope.fromServerKey("sidecar" as Parameters<typeof ServerScope.fromServerKey>[0]))).toBe("local")
+    expect(String(ServerScope.fromServerKey("sidecar" as Parameters<typeof ServerScope.fromServerKey>[0]))).toBe(
+      "local",
+    )
   })
 
   test("keeps configured loopback servers distinct from the canonical sidecar", () => {
-    expect(String(ServerScope.fromServerKey("http://localhost:4096" as Parameters<typeof ServerScope.fromServerKey>[0]))).toBe(
-      "http://localhost:4096",
-    )
+    expect(
+      String(ServerScope.fromServerKey("http://localhost:4096" as Parameters<typeof ServerScope.fromServerKey>[0])),
+    ).toBe("http://localhost:4096")
   })
 
   test("uses a stable local scope for an explicit canonical web server", () => {
@@ -52,6 +54,8 @@ describe("migrateLegacySessionStateKeys", () => {
   })
 
   test("rejects invalid identity fragments", () => {
-    expect(() => ScopedKey.from(ServerScope.local, "bad\0directory")).toThrow("Scoped key part cannot contain null bytes")
+    expect(() => ScopedKey.from(ServerScope.local, "bad\0directory")).toThrow(
+      "Scoped key part cannot contain null bytes",
+    )
   })
 })

+ 4 - 1
packages/app/src/utils/server-scope.ts

@@ -19,7 +19,10 @@ function compose(scope: ServerScope, parts: string[]) {
 export const ServerScope = {
   local: "local" as ServerScope,
   fromServerKey(key: ServerConnection.Key, canonicalLocalServer?: ServerConnection.Key) {
-    return fragment("Server scope", key === "sidecar" || key === canonicalLocalServer ? ServerScope.local : key) as ServerScope
+    return fragment(
+      "Server scope",
+      key === "sidecar" || key === canonicalLocalServer ? ServerScope.local : key,
+    ) as ServerScope
   },
 }