diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index 30802b446c..ebc925588b 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -1312,7 +1312,7 @@ export class AIChatManager { if (message.role === 'tool' && message.tool_call_id === toolId && message.userQuestion) { return { ...message, - content: `User answered question: ${answerSummary}`, + content: `Asked: ${message.userQuestion.question} — ${answerSummary}`, isLoading: false, userQuestion: { ...message.userQuestion, @@ -2996,7 +2996,11 @@ export class AIChatManager { return { ...message, isLoading: false, - content: messageText, + // A question's card disappears once canceled, so keep the question + // itself readable in the collapsed header. + content: message.userQuestion + ? `Asked: ${message.userQuestion.question} — ${messageText}` + : messageText, error: messageText, userQuestion: message.userQuestion ? { ...message.userQuestion, canceled: true } diff --git a/frontend/src/lib/components/copilot/chat/anthropic.ts b/frontend/src/lib/components/copilot/chat/anthropic.ts index b6e7e57a17..2c19a611a0 100644 --- a/frontend/src/lib/components/copilot/chat/anthropic.ts +++ b/frontend/src/lib/components/copilot/chat/anthropic.ts @@ -143,7 +143,7 @@ export async function parseAnthropicCompletion( callbacks.setToolStatus(toolId, { isLoading: true, - content: `Calling ${toolName}...`, + content: tool?.streamingLabel ?? `Calling ${toolName}...`, toolName, isStreamingArguments: shouldStream, showFade: tool?.showFade, diff --git a/frontend/src/lib/components/copilot/chat/global/core.test.ts b/frontend/src/lib/components/copilot/chat/global/core.test.ts index 849f462701..407870433d 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.test.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.test.ts @@ -3250,7 +3250,7 @@ describe('global AI tools', () => { expect(callbacks.setToolStatus).toHaveBeenLastCalledWith( 'test-askUserQuestion', expect.objectContaining({ - content: 'User answered question: python3', + content: 'Asked: Which script language should be used? — python3', isLoading: false, result: 'python3', userQuestion: expect.objectContaining({ selectedChoices: ['python3'] }) @@ -3288,7 +3288,7 @@ describe('global AI tools', () => { expect(callbacks.setToolStatus).toHaveBeenLastCalledWith( 'test-askUserQuestion', expect.objectContaining({ - content: 'User answered question: bun, go', + content: 'Asked: Which languages should be supported? — bun, go', isLoading: false, result: '- bun\n- go', userQuestion: expect.objectContaining({ selectedChoices: ['bun', 'go'] }) @@ -3362,7 +3362,7 @@ describe('global AI tools', () => { expect(callbacks.setToolStatus).toHaveBeenLastCalledWith( 'test-askUserQuestion', expect.objectContaining({ - content: 'User answered question: use deno instead', + content: 'Asked: Which script language should be used? — use deno instead', result: 'use deno instead', userQuestion: expect.objectContaining({ selectedChoices: ['use deno instead'] }) }) diff --git a/frontend/src/lib/components/copilot/chat/global/core.ts b/frontend/src/lib/components/copilot/chat/global/core.ts index 3254f555f8..f9ba044f0a 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.ts @@ -2133,6 +2133,7 @@ export const globalTools: Tool<{}>[] = [ 'askUserQuestion', 'Ask the user a question with proposed answers and wait for their selected or custom answer before continuing.' ), + streamingLabel: 'Asking the user a question...', fn: async ({ args, toolId, toolCallbacks }) => { const parsed = askUserQuestionSchema.parse(args) const userQuestion = { @@ -2142,7 +2143,7 @@ export const globalTools: Tool<{}>[] = [ } toolCallbacks.setToolStatus(toolId, { - content: parsed.question, + content: `Asking user: ${parsed.question}`, userQuestion, isLoading: true }) @@ -2162,7 +2163,7 @@ export const globalTools: Tool<{}>[] = [ if (!selected?.length) { const message = 'Question cancelled by user' toolCallbacks.setToolStatus(toolId, { - content: message, + content: `Asked: ${parsed.question} — cancelled by user`, userQuestion: { ...userQuestion, canceled: true }, isLoading: false, error: message @@ -2176,12 +2177,13 @@ export const globalTools: Tool<{}>[] = [ // ("Yes, immediately") stays unambiguous to the model reading it back. const answerText = selected.length === 1 ? selected[0] : selected.map((c) => `- ${c}`).join('\n') - // The collapsed tool-header is a human glance, not model input, so the picks - // read as a compact comma list there instead of a stacked bullet list. + // The collapsed tool-header is a human glance, not model input, so it carries + // the question plus the picks as a compact comma list (not a bullet list) — + // it is the only place the exchange stays readable in the transcript. const answerSummary = selected.join(', ') toolCallbacks.setToolStatus(toolId, { - content: `User answered question: ${answerSummary}`, + content: `Asked: ${parsed.question} — ${answerSummary}`, userQuestion: { ...userQuestion, selectedChoices: selected diff --git a/frontend/src/lib/components/copilot/chat/openai-responses.ts b/frontend/src/lib/components/copilot/chat/openai-responses.ts index f66d50bb09..3a83595f8f 100644 --- a/frontend/src/lib/components/copilot/chat/openai-responses.ts +++ b/frontend/src/lib/components/copilot/chat/openai-responses.ts @@ -337,7 +337,7 @@ export async function parseOpenAIResponsesCompletion( callbacks.onMessageEnd() callbacks.setToolStatus(`${item.id}`, { isLoading: true, - content: `Calling ${item.name}...`, + content: tool?.streamingLabel ?? `Calling ${item.name}...`, toolName: item.name, isStreamingArguments: shouldStream, showFade: tool?.showFade, diff --git a/frontend/src/lib/components/copilot/chat/shared.ts b/frontend/src/lib/components/copilot/chat/shared.ts index 60b85e16de..02f92519b7 100644 --- a/frontend/src/lib/components/copilot/chat/shared.ts +++ b/frontend/src/lib/components/copilot/chat/shared.ts @@ -820,6 +820,9 @@ export interface Tool { autoCollapseDetails?: boolean streamArguments?: boolean showFade?: boolean + /** Header shown while the model is still streaming this call's arguments, + * before `fn` runs and sets a real status. Defaults to "Calling ...". */ + streamingLabel?: string } /** Status of a job the chat started and tracks in the jobs tray. Mirrors the diff --git a/frontend/src/lib/components/copilot/lib.ts b/frontend/src/lib/components/copilot/lib.ts index 88afa58b7d..2b261a21e0 100644 --- a/frontend/src/lib/components/copilot/lib.ts +++ b/frontend/src/lib/components/copilot/lib.ts @@ -1176,7 +1176,7 @@ export async function parseOpenAICompletion( // Display tool call with streaming parameters if enabled callbacks.setToolStatus(toolCallId, { isLoading: true, - content: `Calling ${funcName}...`, + content: tool?.streamingLabel ?? `Calling ${funcName}...`, toolName: funcName, isStreamingArguments: shouldStream, showFade: tool?.showFade,