Browse Source

chore: cleanup

Adam 1 tháng trước cách đây
mục cha
commit
b055f973df

+ 0 - 28
packages/app/src/pages/session.tsx

@@ -23,7 +23,6 @@ import { useSync } from "@/context/sync"
 import { useTerminal, type LocalPTY } from "@/context/terminal"
 import { useLayout } from "@/context/layout"
 import { checksum, base64Encode } from "@opencode-ai/util/encode"
-import { findLast } from "@opencode-ai/util/array"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { DialogSelectFile } from "@/components/dialog-select-file"
 import FileTree from "@/components/file-tree"
@@ -35,7 +34,6 @@ import { useSDK } from "@/context/sdk"
 import { usePrompt } from "@/context/prompt"
 import { useComments } from "@/context/comments"
 import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
-import { usePermission } from "@/context/permission"
 import { showToast } from "@opencode-ai/ui/toast"
 import { SessionHeader, SessionContextTab, SortableTab, FileVisual, NewSessionView } from "@/components/session"
 import { navMark, navParams } from "@/utils/perf"
@@ -101,7 +99,6 @@ export default function Page() {
   const sdk = useSDK()
   const prompt = usePrompt()
   const comments = useComments()
-  const permission = usePermission()
 
   const permRequest = createMemo(() => {
     const sessionID = params.id
@@ -770,11 +767,6 @@ export default function Page() {
     return lines.slice(0, 2).join("\n")
   }
 
-  const addSelectionToContext = (path: string, selection: FileSelection) => {
-    const preview = selectionPreview(path, selection)
-    prompt.context.add({ type: "file", path, selection, preview })
-  }
-
   const addCommentToContext = (input: {
     file: string
     selection: SelectedLineRange
@@ -913,31 +905,11 @@ export default function Page() {
   const focusInput = () => inputRef?.focus()
 
   useSessionCommands({
-    command,
-    dialog,
-    file,
-    language,
-    local,
-    permission,
-    prompt,
-    sdk,
-    sync,
-    terminal,
-    layout,
-    params,
-    navigate,
-    tabs,
-    view,
-    info,
-    status,
-    userMessages,
-    visibleUserMessages,
     activeMessage,
     showAllFiles,
     navigateMessageByOffset,
     setExpanded: (id, fn) => setStore("expanded", id, fn),
     setActiveMessage,
-    addSelectionToContext,
     focusInput,
   })
 

+ 1 - 15
packages/app/src/pages/session/helpers.test.ts

@@ -1,5 +1,5 @@
 import { describe, expect, test } from "bun:test"
-import { combineCommandSections, createOpenReviewFile, focusTerminalById, getTabReorderIndex } from "./helpers"
+import { createOpenReviewFile, focusTerminalById, getTabReorderIndex } from "./helpers"
 
 describe("createOpenReviewFile", () => {
   test("opens and loads selected review file", () => {
@@ -46,20 +46,6 @@ describe("focusTerminalById", () => {
   })
 })
 
-describe("combineCommandSections", () => {
-  test("keeps section order stable", () => {
-    const result = combineCommandSections([
-      [{ id: "a", title: "A" }],
-      [
-        { id: "b", title: "B" },
-        { id: "c", title: "C" },
-      ],
-    ])
-
-    expect(result.map((item) => item.id)).toEqual(["a", "b", "c"])
-  })
-})
-
 describe("getTabReorderIndex", () => {
   test("returns target index for valid drag reorder", () => {
     expect(getTabReorderIndex(["a", "b", "c"], "a", "c")).toBe(2)

+ 0 - 5
packages/app/src/pages/session/helpers.ts

@@ -1,4 +1,3 @@
-import type { CommandOption } from "@/context/command"
 import { batch } from "solid-js"
 
 export const focusTerminalById = (id: string) => {
@@ -36,10 +35,6 @@ export const createOpenReviewFile = (input: {
   }
 }
 
-export const combineCommandSections = (sections: readonly (readonly CommandOption[])[]) => {
-  return sections.flatMap((section) => section)
-}
-
 export const getTabReorderIndex = (tabs: readonly string[], from: string, to: string) => {
   const fromIndex = tabs.indexOf(from)
   const toIndex = tabs.indexOf(to)

+ 193 - 173
packages/app/src/pages/session/use-session-commands.tsx

@@ -19,35 +19,14 @@ import { showToast } from "@opencode-ai/ui/toast"
 import { findLast } from "@opencode-ai/util/array"
 import { extractPromptFromParts } from "@/utils/prompt"
 import { UserMessage } from "@opencode-ai/sdk/v2"
-import { combineCommandSections } from "@/pages/session/helpers"
 import { canAddSelectionContext } from "@/pages/session/session-command-helpers"
 
 export type SessionCommandContext = {
-  command: ReturnType<typeof useCommand>
-  dialog: ReturnType<typeof useDialog>
-  file: ReturnType<typeof useFile>
-  language: ReturnType<typeof useLanguage>
-  local: ReturnType<typeof useLocal>
-  permission: ReturnType<typeof usePermission>
-  prompt: ReturnType<typeof usePrompt>
-  sdk: ReturnType<typeof useSDK>
-  sync: ReturnType<typeof useSync>
-  terminal: ReturnType<typeof useTerminal>
-  layout: ReturnType<typeof useLayout>
-  params: ReturnType<typeof useParams>
-  navigate: ReturnType<typeof useNavigate>
-  tabs: () => ReturnType<ReturnType<typeof useLayout>["tabs"]>
-  view: () => ReturnType<ReturnType<typeof useLayout>["view"]>
-  info: () => { revert?: { messageID?: string }; share?: { url?: string } } | undefined
-  status: () => { type: string }
-  userMessages: () => UserMessage[]
-  visibleUserMessages: () => UserMessage[]
   activeMessage: () => UserMessage | undefined
   showAllFiles: () => void
   navigateMessageByOffset: (offset: number) => void
   setExpanded: (id: string, fn: (open: boolean | undefined) => boolean) => void
   setActiveMessage: (message: UserMessage | undefined) => void
-  addSelectionToContext: (path: string, selection: FileSelection) => void
   focusInput: () => void
 }
 
@@ -58,45 +37,88 @@ const withCategory = (category: string) => {
   })
 }
 
-export const useSessionCommands = (input: SessionCommandContext) => {
-  const sessionCommand = withCategory(input.language.t("command.category.session"))
-  const fileCommand = withCategory(input.language.t("command.category.file"))
-  const contextCommand = withCategory(input.language.t("command.category.context"))
-  const viewCommand = withCategory(input.language.t("command.category.view"))
-  const terminalCommand = withCategory(input.language.t("command.category.terminal"))
-  const modelCommand = withCategory(input.language.t("command.category.model"))
-  const mcpCommand = withCategory(input.language.t("command.category.mcp"))
-  const agentCommand = withCategory(input.language.t("command.category.agent"))
-  const permissionsCommand = withCategory(input.language.t("command.category.permissions"))
+export const useSessionCommands = (args: SessionCommandContext) => {
+  const command = useCommand()
+  const dialog = useDialog()
+  const file = useFile()
+  const language = useLanguage()
+  const local = useLocal()
+  const permission = usePermission()
+  const prompt = usePrompt()
+  const sdk = useSDK()
+  const sync = useSync()
+  const terminal = useTerminal()
+  const layout = useLayout()
+  const params = useParams()
+  const navigate = useNavigate()
+
+  const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
+  const tabs = createMemo(() => layout.tabs(sessionKey))
+  const view = createMemo(() => layout.view(sessionKey))
+  const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
+  const idle = { type: "idle" as const }
+  const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle)
+  const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
+  const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[])
+  const visibleUserMessages = createMemo(() => {
+    const revert = info()?.revert?.messageID
+    if (!revert) return userMessages()
+    return userMessages().filter((m) => m.id < revert)
+  })
+
+  const selectionPreview = (path: string, selection: FileSelection) => {
+    const content = file.get(path)?.content?.content
+    if (!content) return undefined
+    const start = Math.max(1, Math.min(selection.startLine, selection.endLine))
+    const end = Math.max(selection.startLine, selection.endLine)
+    const lines = content.split("\n").slice(start - 1, end)
+    if (lines.length === 0) return undefined
+    return lines.slice(0, 2).join("\n")
+  }
+
+  const addSelectionToContext = (path: string, selection: FileSelection) => {
+    const preview = selectionPreview(path, selection)
+    prompt.context.add({ type: "file", path, selection, preview })
+  }
+
+  const sessionCommand = withCategory(language.t("command.category.session"))
+  const fileCommand = withCategory(language.t("command.category.file"))
+  const contextCommand = withCategory(language.t("command.category.context"))
+  const viewCommand = withCategory(language.t("command.category.view"))
+  const terminalCommand = withCategory(language.t("command.category.terminal"))
+  const modelCommand = withCategory(language.t("command.category.model"))
+  const mcpCommand = withCategory(language.t("command.category.mcp"))
+  const agentCommand = withCategory(language.t("command.category.agent"))
+  const permissionsCommand = withCategory(language.t("command.category.permissions"))
 
   const sessionCommands = createMemo(() => [
     sessionCommand({
       id: "session.new",
-      title: input.language.t("command.session.new"),
+      title: language.t("command.session.new"),
       keybind: "mod+shift+s",
       slash: "new",
-      onSelect: () => input.navigate(`/${input.params.dir}/session`),
+      onSelect: () => navigate(`/${params.dir}/session`),
     }),
   ])
 
   const fileCommands = createMemo(() => [
     fileCommand({
       id: "file.open",
-      title: input.language.t("command.file.open"),
-      description: input.language.t("palette.search.placeholder"),
+      title: language.t("command.file.open"),
+      description: language.t("palette.search.placeholder"),
       keybind: "mod+p",
       slash: "open",
-      onSelect: () => input.dialog.show(() => <DialogSelectFile onOpenFile={input.showAllFiles} />),
+      onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={args.showAllFiles} />),
     }),
     fileCommand({
       id: "tab.close",
-      title: input.language.t("command.tab.close"),
+      title: language.t("command.tab.close"),
       keybind: "mod+w",
-      disabled: !input.tabs().active(),
+      disabled: !tabs().active(),
       onSelect: () => {
-        const active = input.tabs().active()
+        const active = tabs().active()
         if (!active) return
-        input.tabs().close(active)
+        tabs().close(active)
       },
     }),
   ])
@@ -104,30 +126,30 @@ export const useSessionCommands = (input: SessionCommandContext) => {
   const contextCommands = createMemo(() => [
     contextCommand({
       id: "context.addSelection",
-      title: input.language.t("command.context.addSelection"),
-      description: input.language.t("command.context.addSelection.description"),
+      title: language.t("command.context.addSelection"),
+      description: language.t("command.context.addSelection.description"),
       keybind: "mod+shift+l",
       disabled: !canAddSelectionContext({
-        active: input.tabs().active(),
-        pathFromTab: input.file.pathFromTab,
-        selectedLines: input.file.selectedLines,
+        active: tabs().active(),
+        pathFromTab: file.pathFromTab,
+        selectedLines: file.selectedLines,
       }),
       onSelect: () => {
-        const active = input.tabs().active()
+        const active = tabs().active()
         if (!active) return
-        const path = input.file.pathFromTab(active)
+        const path = file.pathFromTab(active)
         if (!path) return
 
-        const range = input.file.selectedLines(path) as SelectedLineRange | null | undefined
+        const range = file.selectedLines(path) as SelectedLineRange | null | undefined
         if (!range) {
           showToast({
-            title: input.language.t("toast.context.noLineSelection.title"),
-            description: input.language.t("toast.context.noLineSelection.description"),
+            title: language.t("toast.context.noLineSelection.title"),
+            description: language.t("toast.context.noLineSelection.description"),
           })
           return
         }
 
-        input.addSelectionToContext(path, selectionFromLines(range))
+        addSelectionToContext(path, selectionFromLines(range))
       },
     }),
   ])
@@ -135,50 +157,50 @@ export const useSessionCommands = (input: SessionCommandContext) => {
   const viewCommands = createMemo(() => [
     viewCommand({
       id: "terminal.toggle",
-      title: input.language.t("command.terminal.toggle"),
+      title: language.t("command.terminal.toggle"),
       keybind: "ctrl+`",
       slash: "terminal",
-      onSelect: () => input.view().terminal.toggle(),
+      onSelect: () => view().terminal.toggle(),
     }),
     viewCommand({
       id: "review.toggle",
-      title: input.language.t("command.review.toggle"),
+      title: language.t("command.review.toggle"),
       keybind: "mod+shift+r",
-      onSelect: () => input.view().reviewPanel.toggle(),
+      onSelect: () => view().reviewPanel.toggle(),
     }),
     viewCommand({
       id: "fileTree.toggle",
-      title: input.language.t("command.fileTree.toggle"),
+      title: language.t("command.fileTree.toggle"),
       keybind: "mod+\\",
-      onSelect: () => input.layout.fileTree.toggle(),
+      onSelect: () => layout.fileTree.toggle(),
     }),
     viewCommand({
       id: "input.focus",
-      title: input.language.t("command.input.focus"),
+      title: language.t("command.input.focus"),
       keybind: "ctrl+l",
-      onSelect: () => input.focusInput(),
+      onSelect: () => args.focusInput(),
     }),
     terminalCommand({
       id: "terminal.new",
-      title: input.language.t("command.terminal.new"),
-      description: input.language.t("command.terminal.new.description"),
+      title: language.t("command.terminal.new"),
+      description: language.t("command.terminal.new.description"),
       keybind: "ctrl+alt+t",
       onSelect: () => {
-        if (input.terminal.all().length > 0) input.terminal.new()
-        input.view().terminal.open()
+        if (terminal.all().length > 0) terminal.new()
+        view().terminal.open()
       },
     }),
     viewCommand({
       id: "steps.toggle",
-      title: input.language.t("command.steps.toggle"),
-      description: input.language.t("command.steps.toggle.description"),
+      title: language.t("command.steps.toggle"),
+      description: language.t("command.steps.toggle.description"),
       keybind: "mod+e",
       slash: "steps",
-      disabled: !input.params.id,
+      disabled: !params.id,
       onSelect: () => {
-        const msg = input.activeMessage()
+        const msg = args.activeMessage()
         if (!msg) return
-        input.setExpanded(msg.id, (open: boolean | undefined) => !open)
+        args.setExpanded(msg.id, (open: boolean | undefined) => !open)
       },
     }),
   ])
@@ -186,61 +208,61 @@ export const useSessionCommands = (input: SessionCommandContext) => {
   const messageCommands = createMemo(() => [
     sessionCommand({
       id: "message.previous",
-      title: input.language.t("command.message.previous"),
-      description: input.language.t("command.message.previous.description"),
+      title: language.t("command.message.previous"),
+      description: language.t("command.message.previous.description"),
       keybind: "mod+arrowup",
-      disabled: !input.params.id,
-      onSelect: () => input.navigateMessageByOffset(-1),
+      disabled: !params.id,
+      onSelect: () => args.navigateMessageByOffset(-1),
     }),
     sessionCommand({
       id: "message.next",
-      title: input.language.t("command.message.next"),
-      description: input.language.t("command.message.next.description"),
+      title: language.t("command.message.next"),
+      description: language.t("command.message.next.description"),
       keybind: "mod+arrowdown",
-      disabled: !input.params.id,
-      onSelect: () => input.navigateMessageByOffset(1),
+      disabled: !params.id,
+      onSelect: () => args.navigateMessageByOffset(1),
     }),
   ])
 
   const agentCommands = createMemo(() => [
     modelCommand({
       id: "model.choose",
-      title: input.language.t("command.model.choose"),
-      description: input.language.t("command.model.choose.description"),
+      title: language.t("command.model.choose"),
+      description: language.t("command.model.choose.description"),
       keybind: "mod+'",
       slash: "model",
-      onSelect: () => input.dialog.show(() => <DialogSelectModel />),
+      onSelect: () => dialog.show(() => <DialogSelectModel />),
     }),
     mcpCommand({
       id: "mcp.toggle",
-      title: input.language.t("command.mcp.toggle"),
-      description: input.language.t("command.mcp.toggle.description"),
+      title: language.t("command.mcp.toggle"),
+      description: language.t("command.mcp.toggle.description"),
       keybind: "mod+;",
       slash: "mcp",
-      onSelect: () => input.dialog.show(() => <DialogSelectMcp />),
+      onSelect: () => dialog.show(() => <DialogSelectMcp />),
     }),
     agentCommand({
       id: "agent.cycle",
-      title: input.language.t("command.agent.cycle"),
-      description: input.language.t("command.agent.cycle.description"),
+      title: language.t("command.agent.cycle"),
+      description: language.t("command.agent.cycle.description"),
       keybind: "mod+.",
       slash: "agent",
-      onSelect: () => input.local.agent.move(1),
+      onSelect: () => local.agent.move(1),
     }),
     agentCommand({
       id: "agent.cycle.reverse",
-      title: input.language.t("command.agent.cycle.reverse"),
-      description: input.language.t("command.agent.cycle.reverse.description"),
+      title: language.t("command.agent.cycle.reverse"),
+      description: language.t("command.agent.cycle.reverse.description"),
       keybind: "shift+mod+.",
-      onSelect: () => input.local.agent.move(-1),
+      onSelect: () => local.agent.move(-1),
     }),
     modelCommand({
       id: "model.variant.cycle",
-      title: input.language.t("command.model.variant.cycle"),
-      description: input.language.t("command.model.variant.cycle.description"),
+      title: language.t("command.model.variant.cycle"),
+      description: language.t("command.model.variant.cycle.description"),
       keybind: "shift+mod+d",
       onSelect: () => {
-        input.local.model.variant.cycle()
+        local.model.variant.cycle()
       },
     }),
   ])
@@ -249,22 +271,22 @@ export const useSessionCommands = (input: SessionCommandContext) => {
     permissionsCommand({
       id: "permissions.autoaccept",
       title:
-        input.params.id && input.permission.isAutoAccepting(input.params.id, input.sdk.directory)
-          ? input.language.t("command.permissions.autoaccept.disable")
-          : input.language.t("command.permissions.autoaccept.enable"),
+        params.id && permission.isAutoAccepting(params.id, sdk.directory)
+          ? language.t("command.permissions.autoaccept.disable")
+          : language.t("command.permissions.autoaccept.enable"),
       keybind: "mod+shift+a",
-      disabled: !input.params.id || !input.permission.permissionsEnabled(),
+      disabled: !params.id || !permission.permissionsEnabled(),
       onSelect: () => {
-        const sessionID = input.params.id
+        const sessionID = params.id
         if (!sessionID) return
-        input.permission.toggleAutoAccept(sessionID, input.sdk.directory)
+        permission.toggleAutoAccept(sessionID, sdk.directory)
         showToast({
-          title: input.permission.isAutoAccepting(sessionID, input.sdk.directory)
-            ? input.language.t("toast.permissions.autoaccept.on.title")
-            : input.language.t("toast.permissions.autoaccept.off.title"),
-          description: input.permission.isAutoAccepting(sessionID, input.sdk.directory)
-            ? input.language.t("toast.permissions.autoaccept.on.description")
-            : input.language.t("toast.permissions.autoaccept.off.description"),
+          title: permission.isAutoAccepting(sessionID, sdk.directory)
+            ? language.t("toast.permissions.autoaccept.on.title")
+            : language.t("toast.permissions.autoaccept.off.title"),
+          description: permission.isAutoAccepting(sessionID, sdk.directory)
+            ? language.t("toast.permissions.autoaccept.on.description")
+            : language.t("toast.permissions.autoaccept.off.description"),
         })
       },
     }),
@@ -273,71 +295,71 @@ export const useSessionCommands = (input: SessionCommandContext) => {
   const sessionActionCommands = createMemo(() => [
     sessionCommand({
       id: "session.undo",
-      title: input.language.t("command.session.undo"),
-      description: input.language.t("command.session.undo.description"),
+      title: language.t("command.session.undo"),
+      description: language.t("command.session.undo.description"),
       slash: "undo",
-      disabled: !input.params.id || input.visibleUserMessages().length === 0,
+      disabled: !params.id || visibleUserMessages().length === 0,
       onSelect: async () => {
-        const sessionID = input.params.id
+        const sessionID = params.id
         if (!sessionID) return
-        if (input.status()?.type !== "idle") {
-          await input.sdk.client.session.abort({ sessionID }).catch(() => {})
+        if (status()?.type !== "idle") {
+          await sdk.client.session.abort({ sessionID }).catch(() => {})
         }
-        const revert = input.info()?.revert?.messageID
-        const message = findLast(input.userMessages(), (x) => !revert || x.id < revert)
+        const revert = info()?.revert?.messageID
+        const message = findLast(userMessages(), (x) => !revert || x.id < revert)
         if (!message) return
-        await input.sdk.client.session.revert({ sessionID, messageID: message.id })
-        const parts = input.sync.data.part[message.id]
+        await sdk.client.session.revert({ sessionID, messageID: message.id })
+        const parts = sync.data.part[message.id]
         if (parts) {
-          const restored = extractPromptFromParts(parts, { directory: input.sdk.directory })
-          input.prompt.set(restored)
+          const restored = extractPromptFromParts(parts, { directory: sdk.directory })
+          prompt.set(restored)
         }
-        const priorMessage = findLast(input.userMessages(), (x) => x.id < message.id)
-        input.setActiveMessage(priorMessage)
+        const priorMessage = findLast(userMessages(), (x) => x.id < message.id)
+        args.setActiveMessage(priorMessage)
       },
     }),
     sessionCommand({
       id: "session.redo",
-      title: input.language.t("command.session.redo"),
-      description: input.language.t("command.session.redo.description"),
+      title: language.t("command.session.redo"),
+      description: language.t("command.session.redo.description"),
       slash: "redo",
-      disabled: !input.params.id || !input.info()?.revert?.messageID,
+      disabled: !params.id || !info()?.revert?.messageID,
       onSelect: async () => {
-        const sessionID = input.params.id
+        const sessionID = params.id
         if (!sessionID) return
-        const revertMessageID = input.info()?.revert?.messageID
+        const revertMessageID = info()?.revert?.messageID
         if (!revertMessageID) return
-        const nextMessage = input.userMessages().find((x) => x.id > revertMessageID)
+        const nextMessage = userMessages().find((x) => x.id > revertMessageID)
         if (!nextMessage) {
-          await input.sdk.client.session.unrevert({ sessionID })
-          input.prompt.reset()
-          const lastMsg = findLast(input.userMessages(), (x) => x.id >= revertMessageID)
-          input.setActiveMessage(lastMsg)
+          await sdk.client.session.unrevert({ sessionID })
+          prompt.reset()
+          const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID)
+          args.setActiveMessage(lastMsg)
           return
         }
-        await input.sdk.client.session.revert({ sessionID, messageID: nextMessage.id })
-        const priorMsg = findLast(input.userMessages(), (x) => x.id < nextMessage.id)
-        input.setActiveMessage(priorMsg)
+        await sdk.client.session.revert({ sessionID, messageID: nextMessage.id })
+        const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id)
+        args.setActiveMessage(priorMsg)
       },
     }),
     sessionCommand({
       id: "session.compact",
-      title: input.language.t("command.session.compact"),
-      description: input.language.t("command.session.compact.description"),
+      title: language.t("command.session.compact"),
+      description: language.t("command.session.compact.description"),
       slash: "compact",
-      disabled: !input.params.id || input.visibleUserMessages().length === 0,
+      disabled: !params.id || visibleUserMessages().length === 0,
       onSelect: async () => {
-        const sessionID = input.params.id
+        const sessionID = params.id
         if (!sessionID) return
-        const model = input.local.model.current()
+        const model = local.model.current()
         if (!model) {
           showToast({
-            title: input.language.t("toast.model.none.title"),
-            description: input.language.t("toast.model.none.description"),
+            title: language.t("toast.model.none.title"),
+            description: language.t("toast.model.none.description"),
           })
           return
         }
-        await input.sdk.client.session.summarize({
+        await sdk.client.session.summarize({
           sessionID,
           modelID: model.id,
           providerID: model.provider.id,
@@ -346,29 +368,27 @@ export const useSessionCommands = (input: SessionCommandContext) => {
     }),
     sessionCommand({
       id: "session.fork",
-      title: input.language.t("command.session.fork"),
-      description: input.language.t("command.session.fork.description"),
+      title: language.t("command.session.fork"),
+      description: language.t("command.session.fork.description"),
       slash: "fork",
-      disabled: !input.params.id || input.visibleUserMessages().length === 0,
-      onSelect: () => input.dialog.show(() => <DialogFork />),
+      disabled: !params.id || visibleUserMessages().length === 0,
+      onSelect: () => dialog.show(() => <DialogFork />),
     }),
   ])
 
   const shareCommands = createMemo(() => {
-    if (input.sync.data.config.share === "disabled") return []
+    if (sync.data.config.share === "disabled") return []
     return [
       sessionCommand({
         id: "session.share",
-        title: input.info()?.share?.url
-          ? input.language.t("session.share.copy.copyLink")
-          : input.language.t("command.session.share"),
-        description: input.info()?.share?.url
-          ? input.language.t("toast.session.share.success.description")
-          : input.language.t("command.session.share.description"),
+        title: info()?.share?.url ? language.t("session.share.copy.copyLink") : language.t("command.session.share"),
+        description: info()?.share?.url
+          ? language.t("toast.session.share.success.description")
+          : language.t("command.session.share.description"),
         slash: "share",
-        disabled: !input.params.id,
+        disabled: !params.id,
         onSelect: async () => {
-          if (!input.params.id) return
+          if (!params.id) return
 
           const write = (value: string) => {
             const body = typeof document === "undefined" ? undefined : document.body
@@ -398,7 +418,7 @@ export const useSessionCommands = (input: SessionCommandContext) => {
             const ok = await write(url)
             if (!ok) {
               showToast({
-                title: input.language.t("toast.session.share.copyFailed.title"),
+                title: language.t("toast.session.share.copyFailed.title"),
                 variant: "error",
               })
               return
@@ -406,27 +426,27 @@ export const useSessionCommands = (input: SessionCommandContext) => {
 
             showToast({
               title: existing
-                ? input.language.t("session.share.copy.copied")
-                : input.language.t("toast.session.share.success.title"),
-              description: input.language.t("toast.session.share.success.description"),
+                ? language.t("session.share.copy.copied")
+                : language.t("toast.session.share.success.title"),
+              description: language.t("toast.session.share.success.description"),
               variant: "success",
             })
           }
 
-          const existing = input.info()?.share?.url
+          const existing = info()?.share?.url
           if (existing) {
             await copy(existing, true)
             return
           }
 
-          const url = await input.sdk.client.session
-            .share({ sessionID: input.params.id })
+          const url = await sdk.client.session
+            .share({ sessionID: params.id })
             .then((res) => res.data?.share?.url)
             .catch(() => undefined)
           if (!url) {
             showToast({
-              title: input.language.t("toast.session.share.failed.title"),
-              description: input.language.t("toast.session.share.failed.description"),
+              title: language.t("toast.session.share.failed.title"),
+              description: language.t("toast.session.share.failed.description"),
               variant: "error",
             })
             return
@@ -437,25 +457,25 @@ export const useSessionCommands = (input: SessionCommandContext) => {
       }),
       sessionCommand({
         id: "session.unshare",
-        title: input.language.t("command.session.unshare"),
-        description: input.language.t("command.session.unshare.description"),
+        title: language.t("command.session.unshare"),
+        description: language.t("command.session.unshare.description"),
         slash: "unshare",
-        disabled: !input.params.id || !input.info()?.share?.url,
+        disabled: !params.id || !info()?.share?.url,
         onSelect: async () => {
-          if (!input.params.id) return
-          await input.sdk.client.session
-            .unshare({ sessionID: input.params.id })
+          if (!params.id) return
+          await sdk.client.session
+            .unshare({ sessionID: params.id })
             .then(() =>
               showToast({
-                title: input.language.t("toast.session.unshare.success.title"),
-                description: input.language.t("toast.session.unshare.success.description"),
+                title: language.t("toast.session.unshare.success.title"),
+                description: language.t("toast.session.unshare.success.description"),
                 variant: "success",
               }),
             )
             .catch(() =>
               showToast({
-                title: input.language.t("toast.session.unshare.failed.title"),
-                description: input.language.t("toast.session.unshare.failed.description"),
+                title: language.t("toast.session.unshare.failed.title"),
+                description: language.t("toast.session.unshare.failed.description"),
                 variant: "error",
               }),
             )
@@ -464,8 +484,8 @@ export const useSessionCommands = (input: SessionCommandContext) => {
     ]
   })
 
-  input.command.register("session", () =>
-    combineCommandSections([
+  command.register("session", () =>
+    [
       sessionCommands(),
       fileCommands(),
       contextCommands(),
@@ -475,6 +495,6 @@ export const useSessionCommands = (input: SessionCommandContext) => {
       permissionCommands(),
       sessionActionCommands(),
       shareCommands(),
-    ]),
+    ].flatMap((section) => section),
   )
 }