From 266b5a19f128799d2c604a965872608902836ceb Mon Sep 17 00:00:00 2001 From: xxxigm Date: Fri, 12 Jun 2026 18:27:14 +0700 Subject: [PATCH] feat(desktop): expand the full command inline from the approval bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The native desktop approval bar deliberately omits the command because the pending tool row "already shows it" — but that row only renders a single truncated line, and a pending row can't be expanded (it has no result yet). So the full command was only reachable by opening the "Always allow" dropdown, reading the modal, cancelling, then clicking Run — 4-5 clicks just to see what you're approving. Add a "Command" toggle to the approval bar that reveals the full `request.command` inline (reusing the dialog's pre styling), default collapsed. Approving a long command is now "expand, Run". Gated on a non-empty command so zero-command approvals are unaffected. --- .../components/assistant-ui/tool-approval.tsx | 128 +++++++++++------- apps/desktop/src/i18n/en.ts | 1 + apps/desktop/src/i18n/ja.ts | 1 + apps/desktop/src/i18n/types.ts | 1 + apps/desktop/src/i18n/zh-hant.ts | 1 + apps/desktop/src/i18n/zh.ts | 1 + 6 files changed, 82 insertions(+), 51 deletions(-) diff --git a/apps/desktop/src/components/assistant-ui/tool-approval.tsx b/apps/desktop/src/components/assistant-ui/tool-approval.tsx index 6a3dc6c0d9c..d355fda77fc 100644 --- a/apps/desktop/src/components/assistant-ui/tool-approval.tsx +++ b/apps/desktop/src/components/assistant-ui/tool-approval.tsx @@ -16,6 +16,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge import { useI18n } from '@/i18n' import { triggerHaptic } from '@/lib/haptics' import { ChevronDown, Loader2 } from '@/lib/icons' +import { cn } from '@/lib/utils' import { $gateway } from '@/store/gateway' import { notifyError } from '@/store/notifications' import { $approvalRequest, type ApprovalRequest, clearApprovalRequest } from '@/store/prompts' @@ -60,9 +61,15 @@ const ApprovalBar: FC<{ request: ApprovalRequest }> = ({ request }) => { // "Always allow" persists the pattern to ~/.hermes/config.yaml permanently, so // it goes through a confirm step rather than firing straight from the menu. const [confirmAlways, setConfirmAlways] = useState(false) + // The pending tool row only shows a single truncated line of the command, and + // a pending row can't be expanded (no result yet), so the full command was + // previously only reachable via the "Always allow" modal. Let the user reveal + // it inline instead — "expand, Run" (2 clicks) rather than the modal dance. + const [showCommand, setShowCommand] = useState(false) const busy = submitting !== null // false when the backend won't honor a permanent allow (tirith warning) → hide "Always allow". const allowPermanent = request.allowPermanent !== false + const hasCommand = request.command.trim().length > 0 const respond = useCallback( async (choice: ApprovalChoice) => { @@ -119,70 +126,89 @@ const ApprovalBar: FC<{ request: ApprovalRequest }> = ({ request }) => { }, [confirmAlways, respond]) return ( -
-
+
+
+
+ + + + + + + + void respond('session')}>{copy.allowSession} + {allowPermanent && ( + { + // Defer one tick so the menu fully unmounts before the dialog + // mounts — otherwise Radix's focus-return races the dialog and + // dismisses it via onInteractOutside. + setTimeout(() => setConfirmAlways(true), 0) + }} + > + {copy.alwaysAllowMenu} + + )} + void respond('deny')} variant="destructive"> + {copy.reject} + + + +
+ - - - - - - - void respond('session')}>{copy.allowSession} - {allowPermanent && ( - { - // Defer one tick so the menu fully unmounts before the dialog - // mounts — otherwise Radix's focus-return races the dialog and - // dismisses it via onInteractOutside. - setTimeout(() => setConfirmAlways(true), 0) - }} - > - {copy.alwaysAllowMenu} - - )} - void respond('deny')} variant="destructive"> - {copy.reject} - - - + + {hasCommand && ( + + )}
- + {showCommand && hasCommand && ( +
+          {request.command.trim()}
+        
+ )} {copy.alwaysTitle} - - {copy.alwaysDescription(request.description)} - + {copy.alwaysDescription(request.description)} {request.command.trim() && ( diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts index 269af0c3cf4..2ab95b3e61f 100644 --- a/apps/desktop/src/i18n/en.ts +++ b/apps/desktop/src/i18n/en.ts @@ -1687,6 +1687,7 @@ export const en: Translations = { gatewayDisconnected: 'Hermes gateway is not connected', sendFailed: 'Could not send approval response', run: 'Run', + command: 'Command', moreOptions: 'More approval options', allowSession: 'Allow this session', alwaysAllowMenu: 'Always allow…', diff --git a/apps/desktop/src/i18n/ja.ts b/apps/desktop/src/i18n/ja.ts index 17e7d0076b2..a44019045fe 100644 --- a/apps/desktop/src/i18n/ja.ts +++ b/apps/desktop/src/i18n/ja.ts @@ -1827,6 +1827,7 @@ export const ja = defineLocale({ gatewayDisconnected: 'Hermes ゲートウェイが接続されていません', sendFailed: '承認応答を送信できませんでした', run: '実行', + command: 'コマンド', moreOptions: 'その他の承認オプション', allowSession: 'このセッションで許可', alwaysAllowMenu: '常に許可…', diff --git a/apps/desktop/src/i18n/types.ts b/apps/desktop/src/i18n/types.ts index d877567b578..1f65dc57287 100644 --- a/apps/desktop/src/i18n/types.ts +++ b/apps/desktop/src/i18n/types.ts @@ -1346,6 +1346,7 @@ export interface Translations { gatewayDisconnected: string sendFailed: string run: string + command: string moreOptions: string allowSession: string alwaysAllowMenu: string diff --git a/apps/desktop/src/i18n/zh-hant.ts b/apps/desktop/src/i18n/zh-hant.ts index 4b60f4242ca..c7bdf3ba6da 100644 --- a/apps/desktop/src/i18n/zh-hant.ts +++ b/apps/desktop/src/i18n/zh-hant.ts @@ -1771,6 +1771,7 @@ export const zhHant = defineLocale({ gatewayDisconnected: 'Hermes 閘道未連線', sendFailed: '無法傳送核准回應', run: '執行', + command: '指令', moreOptions: '更多核准選項', allowSession: '允許本工作階段', alwaysAllowMenu: '一律允許…', diff --git a/apps/desktop/src/i18n/zh.ts b/apps/desktop/src/i18n/zh.ts index 4daab207d08..a047c0d44cd 100644 --- a/apps/desktop/src/i18n/zh.ts +++ b/apps/desktop/src/i18n/zh.ts @@ -1867,6 +1867,7 @@ export const zh: Translations = { gatewayDisconnected: 'Hermes 网关未连接', sendFailed: '无法发送审批响应', run: '运行', + command: '命令', moreOptions: '更多审批选项', allowSession: '允许本会话', alwaysAllowMenu: '始终允许…',