diff options
| author | Adam <[email protected]> | 2026-03-12 11:32:05 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-12 11:32:05 -0500 |
| commit | dce7eceb2855bc36a41bc49d9c56d5dcc92a8eb2 (patch) | |
| tree | 31cd7d7aa33733579134e9a6cf3a61762599d8e0 /packages/app/src/pages/session/use-session-commands.tsx | |
| parent | 0e077f748352df6d44c811829baff3c26b3436ac (diff) | |
| download | opencode-dce7eceb2855bc36a41bc49d9c56d5dcc92a8eb2.tar.gz opencode-dce7eceb2855bc36a41bc49d9c56d5dcc92a8eb2.zip | |
chore: cleanup (#17197)
Diffstat (limited to 'packages/app/src/pages/session/use-session-commands.tsx')
| -rw-r--r-- | packages/app/src/pages/session/use-session-commands.tsx | 756 |
1 files changed, 374 insertions, 382 deletions
diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 6799504ca..f5a4c0576 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -1,4 +1,3 @@ -import { createMemo } from "solid-js" import { useNavigate } from "@solidjs/router" import { useCommand, type CommandOption } from "@/context/command" import { useDialog } from "@opencode-ai/ui/context/dialog" @@ -18,6 +17,7 @@ import { DialogSelectMcp } from "@/components/dialog-select-mcp" import { DialogFork } from "@/components/dialog-fork" import { showToast } from "@opencode-ai/ui/toast" import { findLast } from "@opencode-ai/util/array" +import { createSessionTabs } from "@/pages/session/helpers" import { extractPromptFromParts } from "@/utils/prompt" import { UserMessage } from "@opencode-ai/sdk/v2" import { useSessionLayout } from "@/pages/session/session-layout" @@ -26,6 +26,7 @@ export type SessionCommandContext = { navigateMessageByOffset: (offset: number) => void setActiveMessage: (message: UserMessage | undefined) => void focusInput: () => void + review?: () => boolean } const withCategory = (category: string) => { @@ -50,17 +51,43 @@ export const useSessionCommands = (actions: SessionCommandContext) => { const navigate = useNavigate() const { params, tabs, view } = useSessionLayout() - const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) + const info = () => { + const id = params.id + if (!id) return + return sync.session.get(id) + } + const hasReview = () => { + const id = params.id + if (!id) return false + return Math.max(info()?.summary?.files ?? 0, (sync.data.session_diff[id] ?? []).length) > 0 + } + const normalizeTab = (tab: string) => { + if (!tab.startsWith("file://")) return tab + return file.tab(tab) + } + const tabState = createSessionTabs({ + tabs, + pathFromTab: file.pathFromTab, + normalizeTab, + review: actions.review, + hasReview, + }) + const activeFileTab = tabState.activeFileTab + const closableTab = tabState.closableTab 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 status = () => sync.data.session_status[params.id ?? ""] ?? idle + const messages = () => { + const id = params.id + if (!id) return [] + return sync.data.message[id] ?? [] + } + const userMessages = () => messages().filter((m) => m.role === "user") as UserMessage[] + const visibleUserMessages = () => { const revert = info()?.revert?.messageID if (!revert) return userMessages() return userMessages().filter((m) => m.id < revert) - }) + } const showAllFiles = () => { if (layout.fileTree.tab() !== "changes") return @@ -79,9 +106,9 @@ export const useSessionCommands = (actions: SessionCommandContext) => { } const canAddSelectionContext = () => { - const active = tabs().active() - if (!active) return false - const path = file.pathFromTab(active) + const tab = activeFileTab() + if (!tab) return false + const path = file.pathFromTab(tab) if (!path) return false return file.selectedLines(path) != null } @@ -100,404 +127,369 @@ export const useSessionCommands = (actions: SessionCommandContext) => { const agentCommand = withCategory(language.t("command.category.agent")) const permissionsCommand = withCategory(language.t("command.category.permissions")) - const sessionCommands = createMemo(() => [ - sessionCommand({ - id: "session.new", - title: language.t("command.session.new"), - keybind: "mod+shift+s", - slash: "new", - onSelect: () => navigate(`/${params.dir}/session`), - }), - ]) - - const fileCommands = createMemo(() => [ - fileCommand({ - id: "file.open", - title: language.t("command.file.open"), - description: language.t("palette.search.placeholder"), - keybind: "mod+p", - slash: "open", - onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={showAllFiles} />), - }), - fileCommand({ - id: "tab.close", - title: language.t("command.tab.close"), - keybind: "mod+w", - disabled: !tabs().active(), - onSelect: () => { - const active = tabs().active() - if (!active) return - tabs().close(active) - }, - }), - ]) - - const contextCommands = createMemo(() => [ - contextCommand({ - id: "context.addSelection", - title: language.t("command.context.addSelection"), - description: language.t("command.context.addSelection.description"), - keybind: "mod+shift+l", - disabled: !canAddSelectionContext(), - onSelect: () => { - const active = tabs().active() - if (!active) return - const path = file.pathFromTab(active) - if (!path) return - - const range = file.selectedLines(path) as SelectedLineRange | null | undefined - if (!range) { - showToast({ - title: language.t("toast.context.noLineSelection.title"), - description: language.t("toast.context.noLineSelection.description"), - }) - return - } - - addSelectionToContext(path, selectionFromLines(range)) - }, - }), - ]) - - const viewCommands = createMemo(() => [ - viewCommand({ - id: "terminal.toggle", - title: language.t("command.terminal.toggle"), - keybind: "ctrl+`", - slash: "terminal", - onSelect: () => view().terminal.toggle(), - }), - viewCommand({ - id: "review.toggle", - title: language.t("command.review.toggle"), - keybind: "mod+shift+r", - onSelect: () => view().reviewPanel.toggle(), - }), - viewCommand({ - id: "fileTree.toggle", - title: language.t("command.fileTree.toggle"), - keybind: "mod+\\", - onSelect: () => layout.fileTree.toggle(), - }), - viewCommand({ - id: "input.focus", - title: language.t("command.input.focus"), - keybind: "ctrl+l", - onSelect: () => focusInput(), - }), - terminalCommand({ - id: "terminal.new", - title: language.t("command.terminal.new"), - description: language.t("command.terminal.new.description"), - keybind: "ctrl+alt+t", - onSelect: () => { - if (terminal.all().length > 0) terminal.new() - view().terminal.open() - }, - }), - ]) - - const messageCommands = createMemo(() => [ - sessionCommand({ - id: "message.previous", - title: language.t("command.message.previous"), - description: language.t("command.message.previous.description"), - keybind: "mod+arrowup", - disabled: !params.id, - onSelect: () => navigateMessageByOffset(-1), - }), - sessionCommand({ - id: "message.next", - title: language.t("command.message.next"), - description: language.t("command.message.next.description"), - keybind: "mod+arrowdown", - disabled: !params.id, - onSelect: () => navigateMessageByOffset(1), - }), - ]) - - const agentCommands = createMemo(() => [ - modelCommand({ - id: "model.choose", - title: language.t("command.model.choose"), - description: language.t("command.model.choose.description"), - keybind: "mod+'", - slash: "model", - onSelect: () => dialog.show(() => <DialogSelectModel />), - }), - mcpCommand({ - id: "mcp.toggle", - title: language.t("command.mcp.toggle"), - description: language.t("command.mcp.toggle.description"), - keybind: "mod+;", - slash: "mcp", - onSelect: () => dialog.show(() => <DialogSelectMcp />), - }), - agentCommand({ - id: "agent.cycle", - title: language.t("command.agent.cycle"), - description: language.t("command.agent.cycle.description"), - keybind: "mod+.", - slash: "agent", - onSelect: () => local.agent.move(1), - }), - agentCommand({ - id: "agent.cycle.reverse", - title: language.t("command.agent.cycle.reverse"), - description: language.t("command.agent.cycle.reverse.description"), - keybind: "shift+mod+.", - onSelect: () => local.agent.move(-1), - }), - modelCommand({ - id: "model.variant.cycle", - title: language.t("command.model.variant.cycle"), - description: language.t("command.model.variant.cycle.description"), - keybind: "shift+mod+d", - onSelect: () => { - local.model.variant.cycle() - }, - }), - ]) - const isAutoAcceptActive = () => { const sessionID = params.id if (sessionID) return permission.isAutoAccepting(sessionID, sdk.directory) return permission.isAutoAcceptingDirectory(sdk.directory) } + command.register("session", () => { + const share = + sync.data.config.share === "disabled" + ? [] + : [ + sessionCommand({ + id: "session.share", + 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: !params.id, + onSelect: async () => { + if (!params.id) return - const permissionCommands = createMemo(() => [ - permissionsCommand({ - id: "permissions.autoaccept", - title: isAutoAcceptActive() - ? language.t("command.permissions.autoaccept.disable") - : language.t("command.permissions.autoaccept.enable"), - keybind: "mod+shift+a", - disabled: false, - onSelect: () => { - const sessionID = params.id - if (sessionID) { - permission.toggleAutoAccept(sessionID, sdk.directory) - } else { - permission.toggleAutoAcceptDirectory(sdk.directory) - } - const active = sessionID - ? permission.isAutoAccepting(sessionID, sdk.directory) - : permission.isAutoAcceptingDirectory(sdk.directory) - showToast({ - title: active - ? language.t("toast.permissions.autoaccept.on.title") - : language.t("toast.permissions.autoaccept.off.title"), - description: active - ? language.t("toast.permissions.autoaccept.on.description") - : language.t("toast.permissions.autoaccept.off.description"), - }) - }, - }), - ]) + const write = (value: string) => { + const body = typeof document === "undefined" ? undefined : document.body + if (body) { + const textarea = document.createElement("textarea") + textarea.value = value + textarea.setAttribute("readonly", "") + textarea.style.position = "fixed" + textarea.style.opacity = "0" + textarea.style.pointerEvents = "none" + body.appendChild(textarea) + textarea.select() + const copied = document.execCommand("copy") + body.removeChild(textarea) + if (copied) return Promise.resolve(true) + } - const sessionActionCommands = createMemo(() => [ - sessionCommand({ - id: "session.undo", - title: language.t("command.session.undo"), - description: language.t("command.session.undo.description"), - slash: "undo", - disabled: !params.id || visibleUserMessages().length === 0, - onSelect: async () => { - const sessionID = params.id - if (!sessionID) return - if (status()?.type !== "idle") { - await sdk.client.session.abort({ sessionID }).catch(() => {}) - } - const revert = info()?.revert?.messageID - const message = findLast(userMessages(), (x) => !revert || x.id < revert) - if (!message) return - await sdk.client.session.revert({ sessionID, messageID: message.id }) - const parts = sync.data.part[message.id] - if (parts) { - const restored = extractPromptFromParts(parts, { directory: sdk.directory }) - prompt.set(restored) - } - const priorMessage = findLast(userMessages(), (x) => x.id < message.id) - setActiveMessage(priorMessage) - }, - }), - sessionCommand({ - id: "session.redo", - title: language.t("command.session.redo"), - description: language.t("command.session.redo.description"), - slash: "redo", - disabled: !params.id || !info()?.revert?.messageID, - onSelect: async () => { - const sessionID = params.id - if (!sessionID) return - const revertMessageID = info()?.revert?.messageID - if (!revertMessageID) return - const nextMessage = userMessages().find((x) => x.id > revertMessageID) - if (!nextMessage) { - await sdk.client.session.unrevert({ sessionID }) - prompt.reset() - const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID) - setActiveMessage(lastMsg) - return - } - await sdk.client.session.revert({ sessionID, messageID: nextMessage.id }) - const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id) - setActiveMessage(priorMsg) - }, - }), - sessionCommand({ - id: "session.compact", - title: language.t("command.session.compact"), - description: language.t("command.session.compact.description"), - slash: "compact", - disabled: !params.id || visibleUserMessages().length === 0, - onSelect: async () => { - const sessionID = params.id - if (!sessionID) return - const model = local.model.current() - if (!model) { - showToast({ - title: language.t("toast.model.none.title"), - description: language.t("toast.model.none.description"), - }) - return - } - await sdk.client.session.summarize({ - sessionID, - modelID: model.id, - providerID: model.provider.id, - }) - }, - }), - sessionCommand({ - id: "session.fork", - title: language.t("command.session.fork"), - description: language.t("command.session.fork.description"), - slash: "fork", - disabled: !params.id || visibleUserMessages().length === 0, - onSelect: () => dialog.show(() => <DialogFork />), - }), - ]) + const clipboard = typeof navigator === "undefined" ? undefined : navigator.clipboard + if (!clipboard?.writeText) return Promise.resolve(false) + return clipboard.writeText(value).then( + () => true, + () => false, + ) + } - const shareCommands = createMemo(() => { - if (sync.data.config.share === "disabled") return [] - return [ - sessionCommand({ - id: "session.share", - 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: !params.id, - onSelect: async () => { - if (!params.id) return + const copy = async (url: string, existing: boolean) => { + const ok = await write(url) + if (!ok) { + showToast({ + title: language.t("toast.session.share.copyFailed.title"), + variant: "error", + }) + return + } - const write = (value: string) => { - const body = typeof document === "undefined" ? undefined : document.body - if (body) { - const textarea = document.createElement("textarea") - textarea.value = value - textarea.setAttribute("readonly", "") - textarea.style.position = "fixed" - textarea.style.opacity = "0" - textarea.style.pointerEvents = "none" - body.appendChild(textarea) - textarea.select() - const copied = document.execCommand("copy") - body.removeChild(textarea) - if (copied) return Promise.resolve(true) - } + showToast({ + title: existing + ? language.t("session.share.copy.copied") + : language.t("toast.session.share.success.title"), + description: language.t("toast.session.share.success.description"), + variant: "success", + }) + } - const clipboard = typeof navigator === "undefined" ? undefined : navigator.clipboard - if (!clipboard?.writeText) return Promise.resolve(false) - return clipboard.writeText(value).then( - () => true, - () => false, - ) - } + const existing = info()?.share?.url + if (existing) { + await copy(existing, true) + return + } - const copy = async (url: string, existing: boolean) => { - const ok = await write(url) - if (!ok) { - showToast({ - title: language.t("toast.session.share.copyFailed.title"), - variant: "error", - }) - return - } + const url = await sdk.client.session + .share({ sessionID: params.id }) + .then((res) => res.data?.share?.url) + .catch(() => undefined) + if (!url) { + showToast({ + title: language.t("toast.session.share.failed.title"), + description: language.t("toast.session.share.failed.description"), + variant: "error", + }) + return + } + await copy(url, false) + }, + }), + sessionCommand({ + id: "session.unshare", + title: language.t("command.session.unshare"), + description: language.t("command.session.unshare.description"), + slash: "unshare", + disabled: !params.id || !info()?.share?.url, + onSelect: async () => { + if (!params.id) return + await sdk.client.session + .unshare({ sessionID: params.id }) + .then(() => + showToast({ + title: language.t("toast.session.unshare.success.title"), + description: language.t("toast.session.unshare.success.description"), + variant: "success", + }), + ) + .catch(() => + showToast({ + title: language.t("toast.session.unshare.failed.title"), + description: language.t("toast.session.unshare.failed.description"), + variant: "error", + }), + ) + }, + }), + ] + + return [ + sessionCommand({ + id: "session.new", + title: language.t("command.session.new"), + keybind: "mod+shift+s", + slash: "new", + onSelect: () => navigate(`/${params.dir}/session`), + }), + fileCommand({ + id: "file.open", + title: language.t("command.file.open"), + description: language.t("palette.search.placeholder"), + keybind: "mod+p", + slash: "open", + onSelect: () => dialog.show(() => <DialogSelectFile onOpenFile={showAllFiles} />), + }), + fileCommand({ + id: "tab.close", + title: language.t("command.tab.close"), + keybind: "mod+w", + disabled: !closableTab(), + onSelect: () => { + const tab = closableTab() + if (!tab) return + tabs().close(tab) + }, + }), + contextCommand({ + id: "context.addSelection", + title: language.t("command.context.addSelection"), + description: language.t("command.context.addSelection.description"), + keybind: "mod+shift+l", + disabled: !canAddSelectionContext(), + onSelect: () => { + const tab = activeFileTab() + if (!tab) return + const path = file.pathFromTab(tab) + if (!path) return + + const range = file.selectedLines(path) as SelectedLineRange | null | undefined + if (!range) { showToast({ - title: existing - ? language.t("session.share.copy.copied") - : language.t("toast.session.share.success.title"), - description: language.t("toast.session.share.success.description"), - variant: "success", + title: language.t("toast.context.noLineSelection.title"), + description: language.t("toast.context.noLineSelection.description"), }) + return } - const existing = info()?.share?.url - if (existing) { - await copy(existing, true) + addSelectionToContext(path, selectionFromLines(range)) + }, + }), + viewCommand({ + id: "terminal.toggle", + title: language.t("command.terminal.toggle"), + keybind: "ctrl+`", + slash: "terminal", + onSelect: () => view().terminal.toggle(), + }), + viewCommand({ + id: "review.toggle", + title: language.t("command.review.toggle"), + keybind: "mod+shift+r", + onSelect: () => view().reviewPanel.toggle(), + }), + viewCommand({ + id: "fileTree.toggle", + title: language.t("command.fileTree.toggle"), + keybind: "mod+\\", + onSelect: () => layout.fileTree.toggle(), + }), + viewCommand({ + id: "input.focus", + title: language.t("command.input.focus"), + keybind: "ctrl+l", + onSelect: focusInput, + }), + terminalCommand({ + id: "terminal.new", + title: language.t("command.terminal.new"), + description: language.t("command.terminal.new.description"), + keybind: "ctrl+alt+t", + onSelect: () => { + if (terminal.all().length > 0) terminal.new() + view().terminal.open() + }, + }), + sessionCommand({ + id: "message.previous", + title: language.t("command.message.previous"), + description: language.t("command.message.previous.description"), + keybind: "mod+arrowup", + disabled: !params.id, + onSelect: () => navigateMessageByOffset(-1), + }), + sessionCommand({ + id: "message.next", + title: language.t("command.message.next"), + description: language.t("command.message.next.description"), + keybind: "mod+arrowdown", + disabled: !params.id, + onSelect: () => navigateMessageByOffset(1), + }), + modelCommand({ + id: "model.choose", + title: language.t("command.model.choose"), + description: language.t("command.model.choose.description"), + keybind: "mod+'", + slash: "model", + onSelect: () => dialog.show(() => <DialogSelectModel />), + }), + mcpCommand({ + id: "mcp.toggle", + title: language.t("command.mcp.toggle"), + description: language.t("command.mcp.toggle.description"), + keybind: "mod+;", + slash: "mcp", + onSelect: () => dialog.show(() => <DialogSelectMcp />), + }), + agentCommand({ + id: "agent.cycle", + title: language.t("command.agent.cycle"), + description: language.t("command.agent.cycle.description"), + keybind: "mod+.", + slash: "agent", + onSelect: () => local.agent.move(1), + }), + agentCommand({ + id: "agent.cycle.reverse", + title: language.t("command.agent.cycle.reverse"), + description: language.t("command.agent.cycle.reverse.description"), + keybind: "shift+mod+.", + onSelect: () => local.agent.move(-1), + }), + modelCommand({ + id: "model.variant.cycle", + title: language.t("command.model.variant.cycle"), + description: language.t("command.model.variant.cycle.description"), + keybind: "shift+mod+d", + onSelect: () => local.model.variant.cycle(), + }), + permissionsCommand({ + id: "permissions.autoaccept", + title: isAutoAcceptActive() + ? language.t("command.permissions.autoaccept.disable") + : language.t("command.permissions.autoaccept.enable"), + keybind: "mod+shift+a", + disabled: false, + onSelect: () => { + const sessionID = params.id + if (sessionID) permission.toggleAutoAccept(sessionID, sdk.directory) + else permission.toggleAutoAcceptDirectory(sdk.directory) + + const active = sessionID + ? permission.isAutoAccepting(sessionID, sdk.directory) + : permission.isAutoAcceptingDirectory(sdk.directory) + showToast({ + title: active + ? language.t("toast.permissions.autoaccept.on.title") + : language.t("toast.permissions.autoaccept.off.title"), + description: active + ? language.t("toast.permissions.autoaccept.on.description") + : language.t("toast.permissions.autoaccept.off.description"), + }) + }, + }), + sessionCommand({ + id: "session.undo", + title: language.t("command.session.undo"), + description: language.t("command.session.undo.description"), + slash: "undo", + disabled: !params.id || visibleUserMessages().length === 0, + onSelect: async () => { + const sessionID = params.id + if (!sessionID) return + if (status().type !== "idle") { + await sdk.client.session.abort({ sessionID }).catch(() => {}) + } + const revert = info()?.revert?.messageID + const message = findLast(userMessages(), (x) => !revert || x.id < revert) + if (!message) return + await sdk.client.session.revert({ sessionID, messageID: message.id }) + const parts = sync.data.part[message.id] + if (parts) { + const restored = extractPromptFromParts(parts, { directory: sdk.directory }) + prompt.set(restored) + } + const priorMessage = findLast(userMessages(), (x) => x.id < message.id) + setActiveMessage(priorMessage) + }, + }), + sessionCommand({ + id: "session.redo", + title: language.t("command.session.redo"), + description: language.t("command.session.redo.description"), + slash: "redo", + disabled: !params.id || !info()?.revert?.messageID, + onSelect: async () => { + const sessionID = params.id + if (!sessionID) return + const revertMessageID = info()?.revert?.messageID + if (!revertMessageID) return + const nextMessage = userMessages().find((x) => x.id > revertMessageID) + if (!nextMessage) { + await sdk.client.session.unrevert({ sessionID }) + prompt.reset() + const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID) + setActiveMessage(lastMsg) return } - - const url = await sdk.client.session - .share({ sessionID: params.id }) - .then((res) => res.data?.share?.url) - .catch(() => undefined) - if (!url) { + await sdk.client.session.revert({ sessionID, messageID: nextMessage.id }) + const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id) + setActiveMessage(priorMsg) + }, + }), + sessionCommand({ + id: "session.compact", + title: language.t("command.session.compact"), + description: language.t("command.session.compact.description"), + slash: "compact", + disabled: !params.id || visibleUserMessages().length === 0, + onSelect: async () => { + const sessionID = params.id + if (!sessionID) return + const model = local.model.current() + if (!model) { showToast({ - title: language.t("toast.session.share.failed.title"), - description: language.t("toast.session.share.failed.description"), - variant: "error", + title: language.t("toast.model.none.title"), + description: language.t("toast.model.none.description"), }) return } - - await copy(url, false) + await sdk.client.session.summarize({ + sessionID, + modelID: model.id, + providerID: model.provider.id, + }) }, }), sessionCommand({ - id: "session.unshare", - title: language.t("command.session.unshare"), - description: language.t("command.session.unshare.description"), - slash: "unshare", - disabled: !params.id || !info()?.share?.url, - onSelect: async () => { - if (!params.id) return - await sdk.client.session - .unshare({ sessionID: params.id }) - .then(() => - showToast({ - title: language.t("toast.session.unshare.success.title"), - description: language.t("toast.session.unshare.success.description"), - variant: "success", - }), - ) - .catch(() => - showToast({ - title: language.t("toast.session.unshare.failed.title"), - description: language.t("toast.session.unshare.failed.description"), - variant: "error", - }), - ) - }, + id: "session.fork", + title: language.t("command.session.fork"), + description: language.t("command.session.fork.description"), + slash: "fork", + disabled: !params.id || visibleUserMessages().length === 0, + onSelect: () => dialog.show(() => <DialogFork />), }), + ...share, ] }) - - command.register("session", () => - [ - sessionCommands(), - fileCommands(), - contextCommands(), - viewCommands(), - messageCommands(), - agentCommands(), - permissionCommands(), - sessionActionCommands(), - shareCommands(), - ].flatMap((x) => x), - ) } |
