diff --git a/packages/ui/src/components/instance/instance-shell2.tsx b/packages/ui/src/components/instance/instance-shell2.tsx index bf10cc54..ceb95f03 100644 --- a/packages/ui/src/components/instance/instance-shell2.tsx +++ b/packages/ui/src/components/instance/instance-shell2.tsx @@ -36,7 +36,7 @@ import { serverApi } from "../../lib/api-client" import { loadBackgroundProcesses } from "../../stores/background-processes" import { BackgroundProcessOutputDialog } from "../background-process-output-dialog" import { useI18n } from "../../lib/i18n" -import { getPermissionQueueLength, getQuestionQueueLength } from "../../stores/instances" +import { getPermissionQueue, getPermissionQueueLength, getQuestionQueueLength, sendPermissionResponse } from "../../stores/instances" import SessionSidebar from "./shell/SessionSidebar" import { useSessionSidebarRequests } from "./shell/useSessionSidebarRequests" import RightPanel from "./shell/right-panel/RightPanel" @@ -57,6 +57,13 @@ import { useDrawerHostMeasure } from "./shell/useDrawerHostMeasure" import { useDrawerResize } from "./shell/useDrawerResize" import { useSessionCache } from "./shell/useSessionCache" import { useInstanceSessionContext } from "./shell/useInstanceSessionContext" +import { getPermissionSessionId } from "../../types/permission" +import { + canAutoRespondPermission, + finishAutoRespondPermission, + getPermissionAutoAcceptInFlightVersion, + isPermissionAutoAcceptEnabled, +} from "../../stores/permission-auto-accept" const log = getLogger("session") @@ -252,6 +259,33 @@ const InstanceShell2: Component = (props) => { return permissions + questions > 0 }) + const permissionQueue = createMemo(() => getPermissionQueue(props.instance.id)) + + createEffect(() => { + getPermissionAutoAcceptInFlightVersion() + + for (const permission of permissionQueue()) { + const sessionId = getPermissionSessionId(permission) + if (!sessionId) continue + if (!permission?.id) continue + if (!canAutoRespondPermission(props.instance.id, sessionId, permission.id)) continue + + void sendPermissionResponse(props.instance.id, sessionId, permission.id, "once") + .catch((error) => { + log.error("Failed to auto-accept permission", error) + }) + .finally(() => { + finishAutoRespondPermission(props.instance.id, sessionId, permission.id) + }) + } + }) + + const yoloModeEnabled = createMemo(() => { + const session = activeSessionForInstance() + if (!session) return false + return isPermissionAutoAcceptEnabled(props.instance.id, session.id) + }) + const activeSessionStatusPill = createMemo(() => { const activeSessionId = activeSessionIdForInstance() if (!activeSessionId || activeSessionId === "info") return null @@ -297,6 +331,32 @@ const InstanceShell2: Component = (props) => { ) } + const renderYoloModePill = () => { + if (!yoloModeEnabled()) return null + return ( + + + {t("instanceShell.yoloMode.badge")} + + ) + } + + const renderSessionHeaderIndicators = () => ( +
+ {renderYoloModePill()} + + setPermissionModalOpen(true)} + /> + +
+ ) + const handleCommandPaletteClick = () => { showCommandPalette(props.instance.id) } @@ -622,12 +682,7 @@ const InstanceShell2: Component = (props) => {
- - setPermissionModalOpen(true)} - /> - + {renderSessionHeaderIndicators()}
@@ -719,12 +774,7 @@ const InstanceShell2: Component = (props) => {
- - setPermissionModalOpen(true)} - /> - + {renderSessionHeaderIndicators()}
diff --git a/packages/ui/src/components/instance/shell/SessionSidebar.tsx b/packages/ui/src/components/instance/shell/SessionSidebar.tsx index 7916766d..1ece1854 100644 --- a/packages/ui/src/components/instance/shell/SessionSidebar.tsx +++ b/packages/ui/src/components/instance/shell/SessionSidebar.tsx @@ -48,104 +48,103 @@ interface SessionSidebarProps { } const SessionSidebar: Component = (props) => ( -
-
-
- - {props.t("instanceShell.leftPanel.sessionsTitle")} - -
- { - const result = props.onNewSession() - if (result instanceof Promise) { - void result.catch((error) => log.error("Failed to create session:", error)) - } - }} - > - - - - - - props.onSelectSession("info")} - > - - - +
+
+
+ + {props.t("instanceShell.leftPanel.sessionsTitle")} + +
(props.leftPinned() ? props.onUnpinLeftDrawer() : props.onPinLeftDrawer())} + aria-label={props.t("sessionList.actions.newSession.ariaLabel")} + title={props.t("sessionList.actions.newSession.title")} + onClick={() => { + const result = props.onNewSession() + if (result instanceof Promise) { + void result.catch((error) => log.error("Failed to create session:", error)) + } + }} > - {props.leftPinned() ? : } + - - - + + props.onSelectSession("info")} + > + + + + (props.leftPinned() ? props.onUnpinLeftDrawer() : props.onPinLeftDrawer())} + > + {props.leftPinned() ? : } + + + + + + + +
+
+
+ +
-
- - - -
-
-
- { - const result = props.onNewSession() - if (result instanceof Promise) { - void result.catch((error) => log.error("Failed to create session:", error)) - } - }} - enableFilterBar={props.showSearch()} - showHeader={false} - showFooter={false} - /> +
+ { + const result = props.onNewSession() + if (result instanceof Promise) { + void result.catch((error) => log.error("Failed to create session:", error)) + } + }} + enableFilterBar={props.showSearch()} + showHeader={false} + showFooter={false} + /> -
- - {(activeSession) => ( - <> +
+ + {(activeSession) => (
@@ -177,11 +176,10 @@ const SessionSidebar: Component = (props) => ( showDescription={false} />
- - )} -
+ )} + +
-
-) + ) export default SessionSidebar diff --git a/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx b/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx index 8226105f..c031ac87 100644 --- a/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx @@ -89,6 +89,7 @@ interface RightPanelProps { const RightPanel: Component = (props) => { const [rightPanelTab, setRightPanelTab] = createSignal(readStoredRightPanelTab("changes")) const [rightPanelExpandedItems, setRightPanelExpandedItems] = createSignal([ + "yolo-mode", "plan", "background-processes", "mcp", @@ -787,7 +788,7 @@ const RightPanel: Component = (props) => { setRightPanelTab("changes") } - const statusSectionIds = ["session-changes", "plan", "background-processes", "mcp", "lsp", "plugins"] + const statusSectionIds = ["yolo-mode", "session-changes", "plan", "background-processes", "mcp", "lsp", "plugins"] createEffect(() => { const currentExpanded = new Set(rightPanelExpandedItems()) diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx index b52c16d9..74741ae5 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx @@ -2,6 +2,7 @@ import { For, Show, type Accessor, type Component } from "solid-js" import type { ToolState } from "@opencode-ai/sdk/v2" import { Accordion } from "@kobalte/core" import { Tooltip } from "@kobalte/core/tooltip" +import Switch from "@suid/material/Switch" import { ChevronDown, Info, TerminalSquare, Trash2, XOctagon } from "lucide-solid" @@ -12,6 +13,7 @@ import type { Session } from "../../../../../types/session" import ContextUsagePanel from "../../../../session/context-usage-panel" import { TodoListView } from "../../../../tool-call/renderers/todo" import InstanceServiceStatus from "../../../../instance-service-status" +import { isPermissionAutoAcceptEnabled, togglePermissionAutoAccept } from "../../../../../stores/permission-auto-accept" interface StatusTabProps { t: (key: string, vars?: Record) => string @@ -39,6 +41,35 @@ interface StatusTabProps { const StatusTab: Component = (props) => { const isSectionExpanded = (id: string) => props.expandedItems().includes(id) + const renderYoloModeSection = () => { + const session = props.activeSession() + if (!session) { + return ( +
+ {props.t("instanceShell.yoloMode.noSessionSelected")} +
+ ) + } + + return ( +
+
+
+
{props.t("instanceShell.yoloMode.title")}
+

{props.t("instanceShell.yoloMode.description")}

+
+ togglePermissionAutoAccept(props.instanceId, session.id)} + /> +
+
+ ) + } + const renderStatusSessionChanges = () => { const sessionId = props.activeSessionId() if (!sessionId || sessionId === "info") { @@ -204,6 +235,12 @@ const StatusTab: Component = (props) => { } const statusSections = [ + { + id: "yolo-mode", + labelKey: "instanceShell.rightPanel.sections.yoloMode", + tooltipKey: "instanceShell.rightPanel.sections.yoloMode.tooltip", + render: renderYoloModeSection, + }, { id: "session-changes", labelKey: "instanceShell.rightPanel.sections.sessionChanges", @@ -281,29 +318,23 @@ const StatusTab: Component = (props) => { {(section) => ( - + - - - - - - + + + + + + {section.render()} diff --git a/packages/ui/src/lib/i18n/messages/en/instance.ts b/packages/ui/src/lib/i18n/messages/en/instance.ts index 0c6e5bee..a7b3baba 100644 --- a/packages/ui/src/lib/i18n/messages/en/instance.ts +++ b/packages/ui/src/lib/i18n/messages/en/instance.ts @@ -26,7 +26,6 @@ export const instanceMessages = { "instanceShell.leftPanel.sessionsTitle": "Sessions", "instanceShell.leftPanel.instanceInfo": "Instance Info", - "instanceShell.leftDrawer.pin": "Pin left drawer", "instanceShell.leftDrawer.unpin": "Unpin left drawer", "instanceShell.leftDrawer.toggle.pinned": "Left drawer pinned", @@ -107,6 +106,8 @@ export const instanceMessages = { "instanceShell.rightPanel.actions.refreshDirty.cancelLabel": "Cancel", "instanceShell.rightPanel.toast.saveSuccess": "File saved successfully", "instanceShell.rightPanel.toast.saveError": "Failed to save file", + "instanceShell.rightPanel.sections.yoloMode": "Yolo Mode", + "instanceShell.rightPanel.sections.yoloMode.tooltip": "Automatically approves permission requests for the current session. Use it only when you trust the tools being run.", "instanceShell.rightPanel.sections.sessionChanges": "Session Changes", "instanceShell.rightPanel.sections.sessionChanges.tooltip": "Files modified in the current session. Shows additions and deletions for each file.", "instanceShell.rightPanel.sections.plan": "Plan", @@ -150,6 +151,12 @@ export const instanceMessages = { "instanceShell.plan.noSessionSelected": "Select a session to view plan.", "instanceShell.plan.empty": "Nothing planned yet.", + "instanceShell.yoloMode.noSessionSelected": "Select a session to configure Yolo mode.", + "instanceShell.yoloMode.title": "Yolo mode", + "instanceShell.yoloMode.description": "Automatically approve permission requests for this session. Disabled by default.", + "instanceShell.yoloMode.badge": "Yolo mode", + "instanceShell.yoloMode.badgeAriaLabel": "Yolo mode enabled", + "instanceShell.backgroundProcesses.empty": "No background processes.", "instanceShell.backgroundProcesses.status": "Status: {status}", "instanceShell.backgroundProcesses.output": "Output: {sizeKb}KB", diff --git a/packages/ui/src/lib/i18n/messages/es/instance.ts b/packages/ui/src/lib/i18n/messages/es/instance.ts index abc05827..2eaa5532 100644 --- a/packages/ui/src/lib/i18n/messages/es/instance.ts +++ b/packages/ui/src/lib/i18n/messages/es/instance.ts @@ -26,7 +26,6 @@ export const instanceMessages = { "instanceShell.leftPanel.sessionsTitle": "Sesiones", "instanceShell.leftPanel.instanceInfo": "Info de la instancia", - "instanceShell.leftDrawer.pin": "Fijar panel izquierdo", "instanceShell.leftDrawer.unpin": "Desfijar panel izquierdo", "instanceShell.leftDrawer.toggle.pinned": "Panel izquierdo fijado", @@ -107,6 +106,8 @@ export const instanceMessages = { "instanceShell.rightPanel.actions.refreshDirty.cancelLabel": "Cancelar", "instanceShell.rightPanel.toast.saveSuccess": "Archivo guardado exitosamente", "instanceShell.rightPanel.toast.saveError": "Error al guardar el archivo", + "instanceShell.rightPanel.sections.yoloMode": "Modo yolo", + "instanceShell.rightPanel.sections.yoloMode.tooltip": "Aprueba automaticamente las solicitudes de permiso de la sesion actual. Usalo solo si confias en las herramientas que se estan ejecutando.", "instanceShell.rightPanel.sections.sessionChanges": "Cambios de sesión", "instanceShell.rightPanel.sections.sessionChanges.tooltip": "Archivos modificados en la sesión actual. Muestra las adiciones y eliminaciones de cada archivo.", "instanceShell.rightPanel.sections.plan": "Plan", @@ -140,6 +141,12 @@ export const instanceMessages = { "instanceShell.plan.noSessionSelected": "Selecciona una sesión para ver el plan.", "instanceShell.plan.empty": "Aún no hay nada planificado.", + "instanceShell.yoloMode.noSessionSelected": "Selecciona una sesion para configurar el modo yolo.", + "instanceShell.yoloMode.title": "Modo yolo", + "instanceShell.yoloMode.description": "Aprueba automaticamente las solicitudes de permiso de esta sesion. Esta desactivado por defecto.", + "instanceShell.yoloMode.badge": "Modo yolo", + "instanceShell.yoloMode.badgeAriaLabel": "Modo yolo activado", + "instanceShell.backgroundProcesses.empty": "No hay procesos en segundo plano.", "instanceShell.backgroundProcesses.status": "Estado: {status}", "instanceShell.backgroundProcesses.output": "Salida: {sizeKb} KB", diff --git a/packages/ui/src/lib/i18n/messages/fr/instance.ts b/packages/ui/src/lib/i18n/messages/fr/instance.ts index 25f250b2..dfaa3fe3 100644 --- a/packages/ui/src/lib/i18n/messages/fr/instance.ts +++ b/packages/ui/src/lib/i18n/messages/fr/instance.ts @@ -26,7 +26,6 @@ export const instanceMessages = { "instanceShell.leftPanel.sessionsTitle": "Sessions", "instanceShell.leftPanel.instanceInfo": "Infos de l'instance", - "instanceShell.leftDrawer.pin": "Épingler le tiroir gauche", "instanceShell.leftDrawer.unpin": "Désépingler le tiroir gauche", "instanceShell.leftDrawer.toggle.pinned": "Tiroir gauche épinglé", @@ -107,6 +106,8 @@ export const instanceMessages = { "instanceShell.rightPanel.actions.refreshDirty.cancelLabel": "Annuler", "instanceShell.rightPanel.toast.saveSuccess": "Fichier enregistré avec succès", "instanceShell.rightPanel.toast.saveError": "Échec de l'enregistrement du fichier", + "instanceShell.rightPanel.sections.yoloMode": "Mode yolo", + "instanceShell.rightPanel.sections.yoloMode.tooltip": "Approuve automatiquement les demandes d'autorisation pour la session actuelle. A utiliser seulement si vous faites confiance aux outils executes.", "instanceShell.rightPanel.sections.sessionChanges": "Changements de session", "instanceShell.rightPanel.sections.sessionChanges.tooltip": "Fichiers modifiés dans la session actuelle. Affiche les ajouts et suppressions pour chaque fichier.", "instanceShell.rightPanel.sections.plan": "Plan", @@ -140,6 +141,12 @@ export const instanceMessages = { "instanceShell.plan.noSessionSelected": "Sélectionnez une session pour voir le plan.", "instanceShell.plan.empty": "Aucun plan pour l'instant.", + "instanceShell.yoloMode.noSessionSelected": "Selectionnez une session pour configurer le mode yolo.", + "instanceShell.yoloMode.title": "Mode yolo", + "instanceShell.yoloMode.description": "Approuve automatiquement les demandes d'autorisation pour cette session. Desactive par defaut.", + "instanceShell.yoloMode.badge": "Mode yolo", + "instanceShell.yoloMode.badgeAriaLabel": "Mode yolo active", + "instanceShell.backgroundProcesses.empty": "Aucun processus en arrière-plan.", "instanceShell.backgroundProcesses.status": "Statut : {status}", "instanceShell.backgroundProcesses.output": "Sortie : {sizeKb}KB", diff --git a/packages/ui/src/lib/i18n/messages/he/instance.ts b/packages/ui/src/lib/i18n/messages/he/instance.ts index 1288d356..483ca767 100644 --- a/packages/ui/src/lib/i18n/messages/he/instance.ts +++ b/packages/ui/src/lib/i18n/messages/he/instance.ts @@ -26,7 +26,6 @@ export const instanceMessages = { "instanceShell.leftPanel.sessionsTitle": "סשנים", "instanceShell.leftPanel.instanceInfo": "מידע על המופע", - "instanceShell.leftDrawer.pin": "נעץ מגירה שמאלית", "instanceShell.leftDrawer.unpin": "שחרר נעיצת מגירה שמאלית", "instanceShell.leftDrawer.toggle.pinned": "המגירה השמאלית נעוצה", @@ -107,6 +106,8 @@ export const instanceMessages = { "instanceShell.rightPanel.actions.refreshDirty.cancelLabel": "בטל", "instanceShell.rightPanel.toast.saveSuccess": "הקובץ נשמר בהצלחה", "instanceShell.rightPanel.toast.saveError": "כשלון בשמירת הקובץ", + "instanceShell.rightPanel.sections.yoloMode": "מצב Yolo", + "instanceShell.rightPanel.sections.yoloMode.tooltip": "מאשר אוטומטית בקשות הרשאה עבור הסשן הנוכחי. השתמשו בזה רק אם אתם סומכים על הכלים שרצים.", "instanceShell.rightPanel.sections.sessionChanges": "שינויי סשן", "instanceShell.rightPanel.sections.sessionChanges.tooltip": "קבצים שהשתנו בסשן הנוכחי. מציג הוספות ומחיקות לכל קובץ.", "instanceShell.rightPanel.sections.plan": "תוכנית", @@ -148,6 +149,12 @@ export const instanceMessages = { "instanceShell.plan.noSessionSelected": "בחר סשן לצפייה בתוכנית.", "instanceShell.plan.empty": "עדיין לא תוכנן דבר.", + "instanceShell.yoloMode.noSessionSelected": "בחרו סשן כדי להגדיר מצב Yolo.", + "instanceShell.yoloMode.title": "מצב Yolo", + "instanceShell.yoloMode.description": "מאשר אוטומטית בקשות הרשאה עבור הסשן הזה. כבוי כברירת מחדל.", + "instanceShell.yoloMode.badge": "Yolo", + "instanceShell.yoloMode.badgeAriaLabel": "מצב Yolo פעיל", + "instanceShell.backgroundProcesses.empty": "אין תהליכי רקע.", "instanceShell.backgroundProcesses.status": "סטטוס: {status}", "instanceShell.backgroundProcesses.output": "פלט: {sizeKb}KB", diff --git a/packages/ui/src/lib/i18n/messages/ja/instance.ts b/packages/ui/src/lib/i18n/messages/ja/instance.ts index 7fbb21d0..b3fe6ded 100644 --- a/packages/ui/src/lib/i18n/messages/ja/instance.ts +++ b/packages/ui/src/lib/i18n/messages/ja/instance.ts @@ -26,7 +26,6 @@ export const instanceMessages = { "instanceShell.leftPanel.sessionsTitle": "セッション", "instanceShell.leftPanel.instanceInfo": "インスタンス情報", - "instanceShell.leftDrawer.pin": "左ドロワーを固定", "instanceShell.leftDrawer.unpin": "左ドロワーの固定を解除", "instanceShell.leftDrawer.toggle.pinned": "左ドロワーを固定しました", @@ -107,6 +106,8 @@ export const instanceMessages = { "instanceShell.rightPanel.actions.refreshDirty.cancelLabel": "キャンセル", "instanceShell.rightPanel.toast.saveSuccess": "ファイルを保存しました", "instanceShell.rightPanel.toast.saveError": "ファイルの保存に失敗しました", + "instanceShell.rightPanel.sections.yoloMode": "Yoloモード", + "instanceShell.rightPanel.sections.yoloMode.tooltip": "現在のセッションの権限リクエストを自動承認します。実行中のツールを信頼できる場合にのみ使用してください。", "instanceShell.rightPanel.sections.sessionChanges": "セッション変更", "instanceShell.rightPanel.sections.sessionChanges.tooltip": "現在のセッションで変更されたファイル。各ファイルの追加と削除を表示します。", "instanceShell.rightPanel.sections.plan": "計画", @@ -140,6 +141,12 @@ export const instanceMessages = { "instanceShell.plan.noSessionSelected": "計画を表示するにはセッションを選択してください。", "instanceShell.plan.empty": "まだ計画はありません。", + "instanceShell.yoloMode.noSessionSelected": "Yoloモードを設定するにはセッションを選択してください。", + "instanceShell.yoloMode.title": "Yoloモード", + "instanceShell.yoloMode.description": "このセッションの権限リクエストを自動承認します。デフォルトでは無効です。", + "instanceShell.yoloMode.badge": "Yolo", + "instanceShell.yoloMode.badgeAriaLabel": "Yoloモードが有効", + "instanceShell.backgroundProcesses.empty": "バックグラウンドプロセスはありません。", "instanceShell.backgroundProcesses.status": "状態: {status}", "instanceShell.backgroundProcesses.output": "出力: {sizeKb}KB", diff --git a/packages/ui/src/lib/i18n/messages/ru/instance.ts b/packages/ui/src/lib/i18n/messages/ru/instance.ts index 3d160f7c..042c7ddd 100644 --- a/packages/ui/src/lib/i18n/messages/ru/instance.ts +++ b/packages/ui/src/lib/i18n/messages/ru/instance.ts @@ -26,7 +26,6 @@ export const instanceMessages = { "instanceShell.leftPanel.sessionsTitle": "Сессии", "instanceShell.leftPanel.instanceInfo": "Информация об экземпляре", - "instanceShell.leftDrawer.pin": "Закрепить левую панель", "instanceShell.leftDrawer.unpin": "Открепить левую панель", "instanceShell.leftDrawer.toggle.pinned": "Левая панель закреплена", @@ -107,6 +106,8 @@ export const instanceMessages = { "instanceShell.rightPanel.actions.refreshDirty.cancelLabel": "Отмена", "instanceShell.rightPanel.toast.saveSuccess": "Файл успешно сохранён", "instanceShell.rightPanel.toast.saveError": "Не удалось сохранить файл", + "instanceShell.rightPanel.sections.yoloMode": "Режим Yolo", + "instanceShell.rightPanel.sections.yoloMode.tooltip": "Автоматически одобряет запросы разрешений для текущей сессии. Включайте только если доверяете запускаемым инструментам.", "instanceShell.rightPanel.sections.sessionChanges": "Изменения сессии", "instanceShell.rightPanel.sections.sessionChanges.tooltip": "Файлы, измененные в текущей сессии. Показывает добавления и удаления для каждого файла.", "instanceShell.rightPanel.sections.plan": "План", @@ -140,6 +141,12 @@ export const instanceMessages = { "instanceShell.plan.noSessionSelected": "Выберите сессию, чтобы просмотреть план.", "instanceShell.plan.empty": "Пока ничего не запланировано.", + "instanceShell.yoloMode.noSessionSelected": "Выберите сессию, чтобы настроить режим Yolo.", + "instanceShell.yoloMode.title": "Режим Yolo", + "instanceShell.yoloMode.description": "Автоматически одобряет запросы разрешений для этой сессии. По умолчанию выключен.", + "instanceShell.yoloMode.badge": "Yolo", + "instanceShell.yoloMode.badgeAriaLabel": "Режим Yolo включен", + "instanceShell.backgroundProcesses.empty": "Нет фоновых процессов.", "instanceShell.backgroundProcesses.status": "Статус: {status}", "instanceShell.backgroundProcesses.output": "Вывод: {sizeKb}KB", diff --git a/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts b/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts index 424e1fe0..f06f344c 100644 --- a/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts +++ b/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts @@ -26,7 +26,6 @@ export const instanceMessages = { "instanceShell.leftPanel.sessionsTitle": "会话", "instanceShell.leftPanel.instanceInfo": "实例信息", - "instanceShell.leftDrawer.pin": "固定左侧抽屉", "instanceShell.leftDrawer.unpin": "取消固定左侧抽屉", "instanceShell.leftDrawer.toggle.pinned": "左侧抽屉已固定", @@ -107,6 +106,8 @@ export const instanceMessages = { "instanceShell.rightPanel.actions.refreshDirty.cancelLabel": "取消", "instanceShell.rightPanel.toast.saveSuccess": "文件保存成功", "instanceShell.rightPanel.toast.saveError": "保存文件失败", + "instanceShell.rightPanel.sections.yoloMode": "Yolo 模式", + "instanceShell.rightPanel.sections.yoloMode.tooltip": "自动批准当前会话的权限请求。仅在你信任正在运行的工具时启用。", "instanceShell.rightPanel.sections.sessionChanges": "会话更改", "instanceShell.rightPanel.sections.sessionChanges.tooltip": "当前会话中修改的文件。显示每个文件的添加和删除。", "instanceShell.rightPanel.sections.plan": "计划", @@ -140,6 +141,12 @@ export const instanceMessages = { "instanceShell.plan.noSessionSelected": "选择会话以查看计划。", "instanceShell.plan.empty": "暂无计划。", + "instanceShell.yoloMode.noSessionSelected": "请选择一个会话来配置 Yolo 模式。", + "instanceShell.yoloMode.title": "Yolo 模式", + "instanceShell.yoloMode.description": "自动批准此会话的权限请求。默认关闭。", + "instanceShell.yoloMode.badge": "Yolo", + "instanceShell.yoloMode.badgeAriaLabel": "Yolo 模式已启用", + "instanceShell.backgroundProcesses.empty": "没有后台进程。", "instanceShell.backgroundProcesses.status": "状态:{status}", "instanceShell.backgroundProcesses.output": "输出:{sizeKb}KB", diff --git a/packages/ui/src/stores/permission-auto-accept.ts b/packages/ui/src/stores/permission-auto-accept.ts new file mode 100644 index 00000000..6607d3a8 --- /dev/null +++ b/packages/ui/src/stores/permission-auto-accept.ts @@ -0,0 +1,81 @@ +import { createSignal } from "solid-js" + +const STORAGE_KEY = "codenomad:permission-auto-accept:v1" + +function makeKey(instanceId: string, sessionId: string) { + return `${instanceId}:${sessionId}` +} + +function readInitialState() { + if (typeof window === "undefined" || !window.localStorage) { + return new Map() + } + + try { + const raw = window.localStorage.getItem(STORAGE_KEY) + if (!raw) return new Map() + const parsed = JSON.parse(raw) as Record + return new Map(Object.entries(parsed).filter((entry): entry is [string, boolean] => entry[1] === true)) + } catch { + return new Map() + } +} + +function persist(next: Map) { + if (typeof window === "undefined" || !window.localStorage) { + return + } + + try { + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(Object.fromEntries(next))) + } catch { + // ignore persistence failures + } +} + +const [autoAcceptState, setAutoAcceptState] = createSignal(readInitialState()) +const [inFlightVersion, setInFlightVersion] = createSignal(0) + +const inFlight = new Set() + +export function isPermissionAutoAcceptEnabled(instanceId: string, sessionId: string) { + return autoAcceptState().get(makeKey(instanceId, sessionId)) ?? false +} + +export function setPermissionAutoAcceptEnabled(instanceId: string, sessionId: string, enabled: boolean) { + const key = makeKey(instanceId, sessionId) + setAutoAcceptState((prev) => { + const next = new Map(prev) + if (enabled) { + next.set(key, true) + } else { + next.delete(key) + } + persist(next) + return next + }) +} + +export function togglePermissionAutoAccept(instanceId: string, sessionId: string) { + setPermissionAutoAcceptEnabled(instanceId, sessionId, !isPermissionAutoAcceptEnabled(instanceId, sessionId)) +} + +export function canAutoRespondPermission(instanceId: string, sessionId: string, requestId: string) { + const key = makeKey(instanceId, sessionId) + if (!autoAcceptState().get(key)) return false + const requestKey = `${key}:${requestId}` + if (inFlight.has(requestKey)) return false + inFlight.add(requestKey) + return true +} + +export function getPermissionAutoAcceptInFlightVersion() { + return inFlightVersion() +} + +export function finishAutoRespondPermission(instanceId: string, sessionId: string, requestId: string) { + if (!inFlight.delete(`${makeKey(instanceId, sessionId)}:${requestId}`)) { + return + } + setInFlightVersion((value) => value + 1) +} diff --git a/packages/ui/src/styles/panels/right-panel.css b/packages/ui/src/styles/panels/right-panel.css index 301ef0cd..8af0cac3 100644 --- a/packages/ui/src/styles/panels/right-panel.css +++ b/packages/ui/src/styles/panels/right-panel.css @@ -412,6 +412,19 @@ background-color: var(--surface-secondary); } +.right-panel-accordion-header-row { + @apply flex items-center gap-2; +} + +.right-panel-accordion-header-row .right-panel-accordion-trigger { + flex: 1 1 auto; +} + +.right-panel-accordion-header-row .section-info-trigger { + flex: 0 0 auto; + margin-inline-end: 0.75rem; +} + .right-panel-accordion-trigger { @apply w-full flex items-center justify-between px-3 py-2.5 text-[11px] font-semibold uppercase tracking-wide transition-colors duration-150; color: var(--text-secondary); @@ -452,6 +465,8 @@ @apply inline-flex items-center justify-center p-0.5 rounded transition-all duration-150; color: var(--text-muted); flex-shrink: 0; + border: none; + background-color: transparent; } .section-info-trigger:hover { @@ -459,6 +474,12 @@ background-color: var(--surface-hover); } +.section-info-trigger:focus-visible { + @apply ring-2 ring-offset-1; + ring-color: var(--accent-primary); + ring-offset-color: var(--surface-secondary); +} + .section-label { margin-inline-start: 2px; } diff --git a/packages/ui/src/styles/panels/session-layout.css b/packages/ui/src/styles/panels/session-layout.css index 479c37f7..f06db001 100644 --- a/packages/ui/src/styles/panels/session-layout.css +++ b/packages/ui/src/styles/panels/session-layout.css @@ -107,6 +107,28 @@ @apply w-full; } +.session-sidebar-toggle { + display: inline-flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + padding: 0.4rem 0.65rem; + border: 1px solid var(--border-base); + border-radius: 0.75rem; + background: var(--surface-base); + min-height: 2rem; + width: fit-content; + max-width: 100%; + margin-left: auto; +} + +.session-sidebar-toggle-title { + font-size: 0.8rem; + font-weight: 500; + color: var(--text-primary); + line-height: 1.2; +} + .session-sidebar-controls .selector-trigger, .session-sidebar-controls [data-model-selector-control], .session-sidebar-controls .selector-trigger-label, @@ -458,6 +480,16 @@ session-sidebar-controls .selector-trigger-primary { border: 1px solid transparent; } +.status-indicator.session-yolo-mode { + color: var(--accent-primary); + background-color: color-mix(in oklab, var(--accent-primary) 14%, transparent); + border-color: color-mix(in oklab, var(--accent-primary) 28%, transparent); +} + +.status-indicator.session-yolo-mode .status-dot { + background-color: var(--accent-primary); +} + @media (max-width: 768px) { .session-list-container { min-width: 200px;