diff --git a/src/renderer/src/components/native-chat/NativeChatPermissionModePicker.test.tsx b/src/renderer/src/components/native-chat/NativeChatPermissionModePicker.test.tsx index a1114715533..97ffa01e530 100644 --- a/src/renderer/src/components/native-chat/NativeChatPermissionModePicker.test.tsx +++ b/src/renderer/src/components/native-chat/NativeChatPermissionModePicker.test.tsx @@ -26,6 +26,7 @@ vi.mock('@/components/ui/tooltip', () => ({ vi.mock('@/components/ui/dropdown-menu', async () => { const React = await import('react') + const Selection = React.createContext(undefined) return { DropdownMenu: ({ children }: { children: React.ReactNode }) =>
{children}
, DropdownMenuTrigger: ({ @@ -45,48 +46,38 @@ vi.mock('@/components/ui/dropdown-menu', async () => { DropdownMenuRadioGroup: ({ children, value, - onValueChange, 'aria-label': ariaLabel }: { children: React.ReactNode value?: string - onValueChange?: (value: string) => void 'aria-label'?: string }) => ( -
- {React.Children.map(children, (child) => { - if ( - !React.isValidElement<{ - value?: string - disabled?: boolean - children?: React.ReactNode - onSelect?: () => void - }>(child) - ) { - return child - } - const props = child.props - return ( - - ) - })} -
+ +
+ {children} +
+
), - DropdownMenuRadioItem: ({ children }: { children: React.ReactNode; value: string }) => ( - {children} + DropdownMenuRadioItem: ({ + children, + value, + disabled, + onSelect + }: { + children: React.ReactNode + value: string + disabled?: boolean + onSelect?: () => void + }) => ( + ) } }) diff --git a/src/renderer/src/components/native-chat/NativeChatPermissionModePicker.tsx b/src/renderer/src/components/native-chat/NativeChatPermissionModePicker.tsx index a986a0e267c..bda5e2e1427 100644 --- a/src/renderer/src/components/native-chat/NativeChatPermissionModePicker.tsx +++ b/src/renderer/src/components/native-chat/NativeChatPermissionModePicker.tsx @@ -27,7 +27,10 @@ import { } from './native-chat-permission-mode-labels' import { runSessionOptionSurfaceCall } from './native-chat-session-option-surface-call' -function ModeName(props: { mode: AgentChatPermissionMode; truncate?: boolean }): React.JSX.Element { +export function NativeChatPermissionModeName(props: { + mode: AgentChatPermissionMode + truncate?: boolean +}): React.JSX.Element { const Icon = NATIVE_CHAT_PERMISSION_MODE_ICONS[props.mode] const label = nativeChatPermissionModeLabel(props.mode) const body = ( @@ -43,6 +46,36 @@ function ModeName(props: { mode: AgentChatPermissionMode; truncate?: boolean }): ) } +export function NativeChatPermissionModeItems({ + modes, + agent, + disabled, + onSelect +}: Pick< + NativeChatPermissionModePickerProps, + 'modes' | 'agent' | 'disabled' | 'onSelect' +>): React.JSX.Element { + return ( + <> + {modes.map((mode) => ( + onSelect(mode)} + > +
+ +
+ {nativeChatPermissionModeDescription(mode, agent)} +
+
+
+ ))} + + ) +} + export type NativeChatPermissionModePickerProps = { current: AgentChatPermissionMode modes: readonly AgentChatPermissionMode[] @@ -77,7 +110,7 @@ function NativeChatPermissionModePickerInner({ )} className="max-w-40 min-w-0 shrink" > - + @@ -88,21 +121,12 @@ function NativeChatPermissionModePickerInner({ - {modes.map((mode) => ( - onSelect(mode)} - > -
- -
- {nativeChatPermissionModeDescription(mode, agent)} -
-
-
- ))} +
diff --git a/src/renderer/src/components/settings/AgentCatalogRow.tsx b/src/renderer/src/components/settings/AgentCatalogRow.tsx index 1d8b253d4c5..0fd823b2146 100644 --- a/src/renderer/src/components/settings/AgentCatalogRow.tsx +++ b/src/renderer/src/components/settings/AgentCatalogRow.tsx @@ -10,6 +10,7 @@ import type { AgentSessionSourceHomeControl } from './codex-session-source-home- import { AgentSessionSourceHomeInput } from './codex-session-source-home-control' import { resolveTuiAgentPermissionMode } from '../../../../shared/tui-agent-permissions' import { stringifyAgentDefaultEnvDraft } from './agent-default-env-draft' +import { liftAgentPermissionArgs } from '../../../../shared/tui-agent-permission-lift' import { AgentPermissionExceptionBadge, AgentRowPermissionControl, @@ -21,7 +22,6 @@ import { AgentDefaultEnvInput } from './AgentLaunchDefaultsEditor' import { - AgentPermissionArgsChip, agentPermissionChoiceLabel, AgentPermissionMenu, type AgentPermissionChoiceRow @@ -299,13 +299,17 @@ export function AgentCatalogRow({ argsOverride={argsOverride} onSaveArgs={onSaveArgs} permissionChip={ - permissionChoice?.flag && !permissionChoice.argsSetPermission ? ( - permissionChoice.onSelect('own')} - /> - ) : undefined + permissionChoice?.flag && !permissionChoice.argsSetPermission + ? { + label, + flag: permissionChoice.flag, + removeRecipe: (text) => { + const lifted = liftAgentPermissionArgs(agentId, text) + return lifted.choice !== null ? lifted.args : null + }, + onRemove: () => permissionChoice.onSelect('own') + } + : undefined } note={ permissionChoice?.argsSetPermission diff --git a/src/renderer/src/components/settings/AgentLaunchDefaultsEditor.tsx b/src/renderer/src/components/settings/AgentLaunchDefaultsEditor.tsx index 933e0e7ef14..f402891bbc3 100644 --- a/src/renderer/src/components/settings/AgentLaunchDefaultsEditor.tsx +++ b/src/renderer/src/components/settings/AgentLaunchDefaultsEditor.tsx @@ -1,6 +1,7 @@ -import { useId, useState } from 'react' +import { useId, useLayoutEffect, useRef, useState } from 'react' import { Button } from '../ui/button' import { Input } from '../ui/input' +import { Textarea } from '../ui/textarea' import { translate } from '@/i18n/i18n' import { parseAgentDefaultEnvDraft, @@ -8,6 +9,7 @@ import { type AgentDefaultEnvDraftInvalidReason } from './agent-default-env-draft' import { useAgentLaunchFieldWrite } from './use-agent-launch-field-write' +import { AgentPermissionArgsChip } from './AgentPermissionChoiceControls' export function AgentCommandOverrideInput({ defaultCmd, @@ -91,13 +93,38 @@ export function AgentDefaultArgsInput({ defaultArgs: string argsOverride: string onSaveArgs: (value: string) => void | Promise - /** The permission flag terminals add ahead of the text, drawn just before the box. */ - permissionChip?: React.ReactNode + permissionChip?: { + label: string + flag: string + removeRecipe: (text: string) => string | null + onRemove: () => void + } note?: string }): React.JSX.Element { const [draft, setDraft] = useState(null) + const inputRef = useRef(null) + const chipRef = useRef(null) + const chipButtonRef = useRef(null) const argsDraft = draft ?? argsOverride const { pending, write } = useAgentLaunchFieldWrite() + useLayoutEffect(() => { + const input = inputRef.current + const chip = chipRef.current + if (!input) { + return + } + input.style.textIndent = '' + if (!chip) { + return + } + const measure = (): void => { + input.style.textIndent = `calc(${chip.getBoundingClientRect().width}px + var(--spacing) * 2)` + } + measure() + const observer = new ResizeObserver(measure) + observer.observe(chip) + return () => observer.disconnect() + }, [permissionChip?.flag]) const commitArgs = (): void => { if (pending || draft === null) { return @@ -120,28 +147,72 @@ export function AgentDefaultArgsInput({ {translate('auto.components.settings.AgentsPane.cfb3f35775', 'Arguments')}
- {permissionChip} - setDraft(event.target.value)} - onBlur={commitArgs} - onKeyDown={(event) => { - if (event.key === 'Enter') { +
{ + if (!event.currentTarget.contains(event.relatedTarget)) { commitArgs() - event.currentTarget.blur() - } - if (event.key === 'Escape') { - setDraft(null) } }} - placeholder={ - defaultArgs || - translate('auto.components.settings.AgentsPane.6f99bf5dd0', 'No default arguments') - } - spellCheck={false} - className="h-7 flex-1 font-mono text-xs" - /> + onClick={(event) => { + if (event.target === event.currentTarget) { + inputRef.current?.focus() + } + }} + > + {permissionChip && ( + { + if (draft !== null) { + setDraft(permissionChip.removeRecipe(draft) ?? draft) + } + inputRef.current?.focus() + permissionChip.onRemove() + }} + /> + )} +