From 8a185731cbc83b47caa77e72d3d76d12d786561c Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:44:54 -0700 Subject: [PATCH] Support editable draft mode for agent quick commands Agent prompts can now defer execution by setting submitPrompt: false, allowing them to insert as editable drafts like terminal commands do. Older clients don't receive this field via capability negotiation, ensuring backward compatibility with mixed-version paired hosts. --- .../runtime/rpc/methods/client-ui.test.ts | 2 + src/main/runtime/rpc/methods/client-ui.ts | 15 ++-- ...al-quick-command-client-projection.test.ts | 38 +++++++++++ ...erminal-quick-command-client-projection.ts | 24 +++++++ .../terminal-quick-command-rpc-schema.ts | 1 + .../components/settings/QuickCommandsPane.tsx | 1 + .../tab-bar/TabBarQuickCommandsButton.tsx | 2 + .../components/terminal-pane/TerminalPane.tsx | 1 + .../TerminalQuickCommandAppendEnterSwitch.tsx | 62 ----------------- .../TerminalQuickCommandContentSection.tsx | 22 +++--- .../TerminalQuickCommandDialog.test.tsx | 68 +++++++++++++++++-- .../TerminalQuickCommandDialog.tsx | 50 ++++++++++---- .../TerminalQuickCommandSubmissionSwitch.tsx | 42 ++++++++++++ ...erminal-quick-command-dialog-draft.test.ts | 8 ++- .../terminal-quick-command-dialog-draft.ts | 13 ++-- .../lib/run-quick-command-in-new-tab.test.ts | 31 +++++++++ .../src/lib/run-quick-command-in-new-tab.ts | 1 + src/shared/protocol-version.ts | 5 ++ src/shared/terminal-quick-command-types.ts | 2 + src/shared/terminal-quick-commands.test.ts | 24 ++++++- src/shared/terminal-quick-commands.ts | 12 +++- 21 files changed, 321 insertions(+), 103 deletions(-) create mode 100644 src/main/runtime/rpc/methods/terminal-quick-command-client-projection.test.ts create mode 100644 src/main/runtime/rpc/methods/terminal-quick-command-client-projection.ts delete mode 100644 src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAppendEnterSwitch.tsx create mode 100644 src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandSubmissionSwitch.tsx diff --git a/src/main/runtime/rpc/methods/client-ui.test.ts b/src/main/runtime/rpc/methods/client-ui.test.ts index 3bfb7303bd0..5d30ceac500 100644 --- a/src/main/runtime/rpc/methods/client-ui.test.ts +++ b/src/main/runtime/rpc/methods/client-ui.test.ts @@ -184,6 +184,7 @@ describe('client UI RPC methods', () => { action: 'agent-prompt' as const, agent: 'codex' as const, prompt: 'Review this diff', + submitPrompt: false as const, scope: { type: 'global' as const } } ] @@ -205,6 +206,7 @@ describe('client UI RPC methods', () => { action: 'agent-prompt', agent: 'codex', prompt: 'Review this diff\n', + submitPrompt: false, scope: { type: 'global' } } } diff --git a/src/main/runtime/rpc/methods/client-ui.ts b/src/main/runtime/rpc/methods/client-ui.ts index 4161064be01..6bd8f803b71 100644 --- a/src/main/runtime/rpc/methods/client-ui.ts +++ b/src/main/runtime/rpc/methods/client-ui.ts @@ -7,6 +7,7 @@ import { FeatureInteractionIdParam, UiUpdate } from './client-ui-schemas' // the typecheck graph so drift fails the build instead of a paired client. import { TerminalQuickCommandsUpdate } from './terminal-quick-command-rpc-schema' +import { projectTerminalQuickCommandsForClient } from './terminal-quick-command-client-projection' export const CLIENT_UI_METHODS: RpcMethod[] = [ defineMethod({ @@ -26,15 +27,21 @@ export const CLIENT_UI_METHODS: RpcMethod[] = [ params: null, // Why: command bodies can total ~240 KB, so keep unrelated settings reads // from carrying them over every paired/relay connection. - handler: (_params, { runtime }) => ({ - terminalQuickCommands: runtime.getClientTerminalQuickCommands() + handler: (_params, { runtime, clientCapabilities }) => ({ + terminalQuickCommands: projectTerminalQuickCommandsForClient( + runtime.getClientTerminalQuickCommands(), + clientCapabilities + ) }) }), defineMethod({ name: 'settings.updateTerminalQuickCommands', params: TerminalQuickCommandsUpdate, - handler: (params, { runtime }) => ({ - terminalQuickCommands: runtime.updateClientTerminalQuickCommands(params.mutation) + handler: (params, { runtime, clientCapabilities }) => ({ + terminalQuickCommands: projectTerminalQuickCommandsForClient( + runtime.updateClientTerminalQuickCommands(params.mutation), + clientCapabilities + ) }) }), defineMethod({ diff --git a/src/main/runtime/rpc/methods/terminal-quick-command-client-projection.test.ts b/src/main/runtime/rpc/methods/terminal-quick-command-client-projection.test.ts new file mode 100644 index 00000000000..ab78c9e9215 --- /dev/null +++ b/src/main/runtime/rpc/methods/terminal-quick-command-client-projection.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest' +import { TERMINAL_QUICK_COMMAND_AGENT_DRAFTS_RUNTIME_CAPABILITY } from '../../../../shared/protocol-version' +import type { TerminalQuickCommand } from '../../../../shared/terminal-quick-command-types' +import { projectTerminalQuickCommandsForClient } from './terminal-quick-command-client-projection' + +const commands: TerminalQuickCommand[] = [ + { + id: 'review', + label: 'Review', + action: 'agent-prompt', + agent: 'codex', + prompt: 'Review this diff', + submitPrompt: false + } +] + +describe('projectTerminalQuickCommandsForClient', () => { + it('preserves Agent draft state for current and capable clients', () => { + expect(projectTerminalQuickCommandsForClient(commands, undefined)).toEqual(commands) + expect( + projectTerminalQuickCommandsForClient(commands, [ + TERMINAL_QUICK_COMMAND_AGENT_DRAFTS_RUNTIME_CAPABILITY + ]) + ).toEqual(commands) + }) + + it('omits Agent draft state for older exact-shape clients', () => { + expect(projectTerminalQuickCommandsForClient(commands, [])).toEqual([ + { + id: 'review', + label: 'Review', + action: 'agent-prompt', + agent: 'codex', + prompt: 'Review this diff' + } + ]) + }) +}) diff --git a/src/main/runtime/rpc/methods/terminal-quick-command-client-projection.ts b/src/main/runtime/rpc/methods/terminal-quick-command-client-projection.ts new file mode 100644 index 00000000000..6f6efa897b9 --- /dev/null +++ b/src/main/runtime/rpc/methods/terminal-quick-command-client-projection.ts @@ -0,0 +1,24 @@ +import { TERMINAL_QUICK_COMMAND_AGENT_DRAFTS_RUNTIME_CAPABILITY } from '../../../../shared/protocol-version' +import type { RuntimeCapability } from '../../../../shared/protocol-version' +import type { TerminalQuickCommand } from '../../../../shared/terminal-quick-command-types' +import { isTerminalAgentQuickCommand } from '../../../../shared/terminal-quick-commands' + +export function projectTerminalQuickCommandsForClient( + commands: readonly TerminalQuickCommand[], + clientCapabilities: readonly RuntimeCapability[] | undefined +): TerminalQuickCommand[] { + if ( + clientCapabilities === undefined || + clientCapabilities.includes(TERMINAL_QUICK_COMMAND_AGENT_DRAFTS_RUNTIME_CAPABILITY) + ) { + return [...commands] + } + return commands.map((command) => { + if (!isTerminalAgentQuickCommand(command)) { + return command + } + const { submitPrompt: _submitPrompt, ...legacyCommand } = command + void _submitPrompt + return legacyCommand + }) +} diff --git a/src/main/runtime/rpc/methods/terminal-quick-command-rpc-schema.ts b/src/main/runtime/rpc/methods/terminal-quick-command-rpc-schema.ts index 18661f10b57..311cba94cad 100644 --- a/src/main/runtime/rpc/methods/terminal-quick-command-rpc-schema.ts +++ b/src/main/runtime/rpc/methods/terminal-quick-command-rpc-schema.ts @@ -40,6 +40,7 @@ const TerminalQuickCommandUpdateItem = z.union([ message: 'Agent does not support prompt commands' }), prompt: z.string().max(MAX_QUICK_COMMAND_AGENT_PROMPT_LENGTH), + submitPrompt: z.boolean().optional(), scope: TerminalQuickCommandScopeUpdate.optional() }) .strict() diff --git a/src/renderer/src/components/settings/QuickCommandsPane.tsx b/src/renderer/src/components/settings/QuickCommandsPane.tsx index 2305a034227..351c70c11dc 100644 --- a/src/renderer/src/components/settings/QuickCommandsPane.tsx +++ b/src/renderer/src/components/settings/QuickCommandsPane.tsx @@ -394,6 +394,7 @@ export function QuickCommandsPane({ open mode={editor.mode} command={editor.command} + hostId={editor.hostId} repos={hostRepos} defaultAdvancedOpen onOpenChange={(open) => !open && setEditor(null)} diff --git a/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx b/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx index ae831a418f9..378a0dd7066 100644 --- a/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx +++ b/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx @@ -203,6 +203,7 @@ export function TabBarQuickCommandsButton({ open={editor !== null} mode={editor?.mode ?? 'add'} command={editor?.command ?? createTerminalQuickCommandDraft({ type: 'repo', repoId })} + hostId={editor?.hostId ?? defaultHostId} repos={editorRepos} onOpenChange={(open) => !open && setEditor(null)} onSave={handleSaveCommand} @@ -232,6 +233,7 @@ export function TabBarQuickCommandsButton({ open={editor !== null} mode={editor?.mode ?? 'add'} command={editor?.command ?? createTerminalQuickCommandDraft({ type: 'repo', repoId })} + hostId={editor?.hostId ?? defaultHostId} repos={editorRepos} onOpenChange={(open) => !open && setEditor(null)} onSave={handleSaveCommand} diff --git a/src/renderer/src/components/terminal-pane/TerminalPane.tsx b/src/renderer/src/components/terminal-pane/TerminalPane.tsx index 2b58732f0ea..acaf592fd35 100644 --- a/src/renderer/src/components/terminal-pane/TerminalPane.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalPane.tsx @@ -294,6 +294,7 @@ function TerminalQuickCommandEditorDialog({ open mode="add" command={command} + hostId={hostId} repos={hostRepos} onOpenChange={onOpenChange} onSave={onSave} diff --git a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAppendEnterSwitch.tsx b/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAppendEnterSwitch.tsx deleted file mode 100644 index 187fa402207..00000000000 --- a/src/renderer/src/components/terminal-quick-commands/TerminalQuickCommandAppendEnterSwitch.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { translate } from '@/i18n/i18n' -import { Switch } from '@/components/ui/switch' - -type TerminalQuickCommandAppendEnterSwitchProps = { - appendEnter: boolean - onToggle: () => void - compact?: boolean -} - -export function TerminalQuickCommandAppendEnterSwitch({ - appendEnter, - onToggle, - compact = false -}: TerminalQuickCommandAppendEnterSwitchProps): React.JSX.Element { - if (compact) { - return ( - - ) - } - - return ( -