From 7d01d252b526987f8f0b86695de2f9c90de64519 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Mon, 19 Oct 2026 23:13:53 +0200 Subject: [PATCH] feat: name the action on chat tool confirmation buttons (#11795) * feat: name the action on chat tool confirmation buttons Co-Authored-By: Claude Opus 5.5 (1M context) * fix: size chat confirmation footer buttons with unifiedSize and the UI font Co-Authored-By: Claude Opus 5.5 (1M context) --------- Co-authored-by: Claude Opus 5.5 (1M context) --- .../copilot/chat/AIChatDisplay.svelte | 4 ++-- .../copilot/chat/ToolConfirmationFooter.svelte | 7 ++++--- .../copilot/chat/ToolExecutionDisplay.svelte | 12 +++++++----- .../components/copilot/chat/api/apiTools.ts | 1 + .../copilot/chat/datatableMigrationTools.ts | 3 +++ .../components/copilot/chat/datatableTools.ts | 1 + .../lib/components/copilot/chat/flow/core.ts | 2 ++ .../lib/components/copilot/chat/global/core.ts | 6 ++++++ .../components/copilot/chat/global/mcpTools.ts | 1 + .../chat/global/workspaceSettingsTools.ts | 1 + .../components/copilot/chat/pipeline/core.ts | 1 + .../lib/components/copilot/chat/script/core.ts | 1 + .../src/lib/components/copilot/chat/shared.ts | 18 +++++++++++++++++- .../copilot/chat/toolConfirmAction.ts | 13 +++++++++++++ .../components/copilot/chat/workspaceTools.ts | 2 ++ 15 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 frontend/src/lib/components/copilot/chat/toolConfirmAction.ts diff --git a/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte b/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte index 1060a64110..b233728a19 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte +++ b/frontend/src/lib/components/copilot/chat/AIChatDisplay.svelte @@ -977,7 +977,7 @@ the panel, or the Escape-to-stop focus check would wrongly reject them. -->
{:else if message.needsConfirmation} - {@render confirmFooter('py-1 font-main')} + {@render confirmFooter('py-1')} {/if} {:else} @@ -373,8 +375,8 @@ toolCallId={message.tool_call_id} rejectIcon={XCircle} rejectDestructive - confirmLabel="Run" - confirmIcon={Play} + confirmLabel={confirmAction.label} + confirmIcon={confirmAction.icon} /> diff --git a/frontend/src/lib/components/copilot/chat/api/apiTools.ts b/frontend/src/lib/components/copilot/chat/api/apiTools.ts index 1063bd97e3..b0466f77de 100644 --- a/frontend/src/lib/components/copilot/chat/api/apiTools.ts +++ b/frontend/src/lib/components/copilot/chat/api/apiTools.ts @@ -109,6 +109,7 @@ export function createApiTools( def: chatTool, planModeSafe: !!endpoint && ['GET', 'HEAD', 'OPTIONS'].includes(method), requiresConfirmation: needsConfirmation, + confirmAction: method === 'DELETE' ? 'delete' : 'confirm', confirmationMessage: `Run ${toolName}`, showDetails: true, showFade: true, diff --git a/frontend/src/lib/components/copilot/chat/datatableMigrationTools.ts b/frontend/src/lib/components/copilot/chat/datatableMigrationTools.ts index c093b450c3..6e5ec4f8a7 100644 --- a/frontend/src/lib/components/copilot/chat/datatableMigrationTools.ts +++ b/frontend/src/lib/components/copilot/chat/datatableMigrationTools.ts @@ -213,6 +213,7 @@ export function getDatatableMigrationTools(): SessionTool<{}>[] { requires: RUN_PREVIEW, def: getCreateMigrationToolDef(), requiresConfirmation: true, + confirmAction: (args) => (args?.apply === false ? 'create' : 'apply'), confirmationMessage: (args) => `${args?.version === undefined ? 'Create' : 'Rewrite'}${args?.apply === false ? '' : ' and apply'} data table migration`, queuedLabel: (args) => `Write migration "${args?.name ?? ''}"`, @@ -341,6 +342,7 @@ export function getDatatableMigrationTools(): SessionTool<{}>[] { requires: RUN_PREVIEW, def: getRunMigrationsToolDef(), requiresConfirmation: true, + confirmAction: 'apply', confirmationMessage: 'Apply pending data table migrations', queuedLabel: (args) => `Apply migrations on "${args?.datatable_name ?? 'datatable'}"`, showDetails: true, @@ -376,6 +378,7 @@ export function getDatatableMigrationTools(): SessionTool<{}>[] { requires: RUN_PREVIEW, def: getRollbackMigrationToolDef(), requiresConfirmation: true, + confirmAction: 'rollback', confirmationMessage: (args) => `Roll back data table migration ${args?.version ?? ''}`, queuedLabel: (args) => `Roll back migration ${args?.version ?? ''}`, showDetails: true, diff --git a/frontend/src/lib/components/copilot/chat/datatableTools.ts b/frontend/src/lib/components/copilot/chat/datatableTools.ts index e05b2e4325..097d64d06e 100644 --- a/frontend/src/lib/components/copilot/chat/datatableTools.ts +++ b/frontend/src/lib/components/copilot/chat/datatableTools.ts @@ -344,6 +344,7 @@ export function getDatatableTools(): SessionTool<{}>[] { requires: RUN_PREVIEW, def: getExecDatatableSqlToolDef(), requiresConfirmation: true, + confirmAction: 'run', confirmationMessage: 'Execute SQL on datatable', queuedLabel: (args) => `Execute SQL on "${args?.datatable_name ?? 'datatable'}"`, showDetails: true, diff --git a/frontend/src/lib/components/copilot/chat/flow/core.ts b/frontend/src/lib/components/copilot/chat/flow/core.ts index 4adbe1634b..39be790f3e 100644 --- a/frontend/src/lib/components/copilot/chat/flow/core.ts +++ b/frontend/src/lib/components/copilot/chat/flow/core.ts @@ -441,6 +441,7 @@ export const flowTools: Tool[] = [ return def }, requiresConfirmation: true, + confirmAction: 'run', confirmationMessage: 'Run a test of the current flow', showDetails: true, autoCollapseDetails: false @@ -486,6 +487,7 @@ export const flowTools: Tool[] = [ }) }, requiresConfirmation: true, + confirmAction: 'run', confirmationMessage: (args) => `Run a test of step "${args?.stepId ?? ''}"`, showDetails: true, autoCollapseDetails: false diff --git a/frontend/src/lib/components/copilot/chat/global/core.ts b/frontend/src/lib/components/copilot/chat/global/core.ts index a830d4d404..3414ef6185 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.ts @@ -4134,6 +4134,7 @@ export const globalTools: SessionTool<{}>[] = [ 'Create a new shared folder (addressable as f//) in the workspace. The current user is added as an owner.' ), requiresConfirmation: true, + confirmAction: 'create', confirmationMessage: 'Create folder', showDetails: true, fn: async ({ args, workspace, toolId, toolCallbacks }) => { @@ -4568,6 +4569,7 @@ export const globalTools: SessionTool<{}>[] = [ showDetails: true, showFade: true, requiresConfirmation: true, + confirmAction: 'deploy', confirmationMessage: 'Deploy draft to workspace', fn: async (ctx) => { const parsed = deployWorkspaceItemSchema.parse(ctx.args) @@ -4650,6 +4652,7 @@ export const globalTools: SessionTool<{}>[] = [ showDetails: true, showFade: true, requiresConfirmation: true, + confirmAction: 'delete', confirmationMessage: 'Delete workspace item', validateBeforeConfirmation: validateDeleteWorkspaceItemTarget, fn: async (ctx) => { @@ -4669,6 +4672,7 @@ export const globalTools: SessionTool<{}>[] = [ showDetails: true, showFade: true, requiresConfirmation: true, + confirmAction: 'discard', confirmationMessage: 'Discard draft', fn: async (ctx) => { const parsed = discardLocalDraftSchema.parse(ctx.args) @@ -4764,6 +4768,7 @@ export const globalTools: SessionTool<{}>[] = [ ), showDetails: true, requiresConfirmation: true, + confirmAction: 'create', confirmationMessage: 'Create resource type', fn: async ({ args, workspace, toolId, toolCallbacks }) => { const parsed = createResourceTypeSchema.parse(args) @@ -4944,6 +4949,7 @@ export const globalTools: SessionTool<{}>[] = [ return testRunAppRunnable(parsed, ctx) }, requiresConfirmation: true, + confirmAction: 'run', confirmationMessage: (args) => `Run the backend runnable "${args?.key ?? ''}" of ${pathLeaf(args?.path, 'the app')}`, queuedLabel: (args) => `Test runnable "${args?.key ?? ''}" of ${args?.path ?? 'the app'}`, diff --git a/frontend/src/lib/components/copilot/chat/global/mcpTools.ts b/frontend/src/lib/components/copilot/chat/global/mcpTools.ts index 620e5a86ea..e1da0fb624 100644 --- a/frontend/src/lib/components/copilot/chat/global/mcpTools.ts +++ b/frontend/src/lib/components/copilot/chat/global/mcpTools.ts @@ -366,6 +366,7 @@ function createCallTool(servers: McpServer[], mode: 'read' | 'write'): SessionTo ? { planModeSafe: true } : { requiresConfirmation: true, + confirmAction: 'run', confirmationMessage: (args: any) => `Call ${args?.tool ?? ''} on ${args?.server ?? ''}` }), fn: async ({ args, workspace, toolId, toolCallbacks }) => { diff --git a/frontend/src/lib/components/copilot/chat/global/workspaceSettingsTools.ts b/frontend/src/lib/components/copilot/chat/global/workspaceSettingsTools.ts index d63b75343f..1f37eb9300 100644 --- a/frontend/src/lib/components/copilot/chat/global/workspaceSettingsTools.ts +++ b/frontend/src/lib/components/copilot/chat/global/workspaceSettingsTools.ts @@ -105,6 +105,7 @@ export const workspaceSettingsTools: SessionTool<{}>[] = [ 'Change workspace settings. Keys the yaml omits are left as they are. The user reviews the changes before they apply. Read get_instructions subject "workspace_settings" before your first change.' ), requiresConfirmation: true, + confirmAction: 'apply', confirmationMessage: 'Change workspace settings', cancellationMessage: 'The user declined: no setting was changed.', showDetails: true, diff --git a/frontend/src/lib/components/copilot/chat/pipeline/core.ts b/frontend/src/lib/components/copilot/chat/pipeline/core.ts index 8d71b00279..abd403206d 100644 --- a/frontend/src/lib/components/copilot/chat/pipeline/core.ts +++ b/frontend/src/lib/components/copilot/chat/pipeline/core.ts @@ -568,6 +568,7 @@ export const pipelineTools: SessionTool[] = [ requires: RUN_PREVIEW, def: testPipelineNodeToolDef, requiresConfirmation: true, + confirmAction: 'run', confirmationMessage: 'Run pipeline node', showDetails: true, autoCollapseDetails: false, diff --git a/frontend/src/lib/components/copilot/chat/script/core.ts b/frontend/src/lib/components/copilot/chat/script/core.ts index b732e00c05..93ad12ff67 100644 --- a/frontend/src/lib/components/copilot/chat/script/core.ts +++ b/frontend/src/lib/components/copilot/chat/script/core.ts @@ -1007,6 +1007,7 @@ export const testRunScriptTool: Tool = { }) }, requiresConfirmation: true, + confirmAction: 'run', confirmationMessage: 'Run a test of the current script', showDetails: true, autoCollapseDetails: false diff --git a/frontend/src/lib/components/copilot/chat/shared.ts b/frontend/src/lib/components/copilot/chat/shared.ts index a6ce735230..71ef91a0b7 100644 --- a/frontend/src/lib/components/copilot/chat/shared.ts +++ b/frontend/src/lib/components/copilot/chat/shared.ts @@ -642,6 +642,16 @@ export type ToolCodeDiff = { beforeLang?: string } +export type ToolConfirmAction = + | 'run' + | 'deploy' + | 'create' + | 'apply' + | 'delete' + | 'discard' + | 'rollback' + | 'confirm' + export type ToolDisplayMessage = { role: 'tool' tool_call_id: string @@ -657,6 +667,8 @@ export type ToolDisplayMessage = { isQueued?: boolean error?: string needsConfirmation?: boolean + /** What the confirm button says the call will do. */ + confirmAction?: ToolConfirmAction showDetails?: boolean autoCollapseDetails?: boolean isStreamingArguments?: boolean @@ -1103,6 +1115,8 @@ export async function processToolCall({ typeof tool?.confirmationMessage === 'function' ? tool.confirmationMessage(args) : tool?.confirmationMessage + const confirmAction = + typeof tool?.confirmAction === 'function' ? tool.confirmAction(args) : tool?.confirmAction // preAction fires at promotion, not stream time, so its "-ing" label covers // only the execution window — queued cards keep their imperative header. @@ -1111,7 +1125,7 @@ export async function processToolCall({ toolCallbacks.setToolStatus(toolCall.id, { ...(requiresConfirmation - ? { content: confirmationContent ?? 'Waiting for confirmation...' } + ? { content: confirmationContent ?? 'Waiting for confirmation...', confirmAction } : {}), parameters: args, isLoading: true, @@ -1287,6 +1301,8 @@ export interface Tool { /** Header shown on the confirmation card before the tool runs. Pass a function * to derive it from the parsed arguments (e.g. name the script being tested). */ confirmationMessage?: string | ((args: any) => string) + /** The verb on the confirm button; `confirm` when omitted. */ + confirmAction?: ToolConfirmAction | ((args: any) => ToolConfirmAction) /** Only when a card gates the call, so `fn` must not rely on it. Not awaited, so it may * not throw, and must be safe for a call the user then declines. */ onConfirmationRequested?: (p: { args: any; toolCallbacks: ToolCallbacks; toolId: string }) => void diff --git a/frontend/src/lib/components/copilot/chat/toolConfirmAction.ts b/frontend/src/lib/components/copilot/chat/toolConfirmAction.ts new file mode 100644 index 0000000000..52f5d546e8 --- /dev/null +++ b/frontend/src/lib/components/copilot/chat/toolConfirmAction.ts @@ -0,0 +1,13 @@ +import { Check, Play, Plus, Save, Trash2, Undo2 } from 'lucide-svelte' +import type { ToolConfirmAction } from './shared' + +export const TOOL_CONFIRM_ACTIONS: Record = { + run: { label: 'Run', icon: Play }, + deploy: { label: 'Deploy', icon: Save }, + create: { label: 'Create', icon: Plus }, + apply: { label: 'Apply', icon: Check }, + delete: { label: 'Delete', icon: Trash2 }, + discard: { label: 'Discard', icon: Trash2 }, + rollback: { label: 'Roll back', icon: Undo2 }, + confirm: { label: 'Confirm', icon: Check } +} diff --git a/frontend/src/lib/components/copilot/chat/workspaceTools.ts b/frontend/src/lib/components/copilot/chat/workspaceTools.ts index 18c3687701..7590f3b686 100644 --- a/frontend/src/lib/components/copilot/chat/workspaceTools.ts +++ b/frontend/src/lib/components/copilot/chat/workspaceTools.ts @@ -309,6 +309,7 @@ function setToolError(toolCallbacks: ToolCallbacks, toolId: string, error: unkno const createScheduleTool: Tool = { def: createScheduleToolDef, requiresConfirmation: true, + confirmAction: 'create', confirmationMessage: 'Create schedule', showDetails: true, showFade: true, @@ -418,6 +419,7 @@ async function resolveEmailTriggerAvailability(): Promise = { def: createTriggerToolDef, requiresConfirmation: true, + confirmAction: 'create', confirmationMessage: 'Create trigger', showDetails: true, showFade: true,