fix(h5): show AskUserQuestion before tool stream completes #1220

Materialize the question card directly from a permission request when the
streamed tool block has not arrived yet. Upsert by tool-use id so either event
order produces one visible, answerable card without requiring a refresh.
This commit is contained in:
程序员阿江(Relakkes)
2026-08-13 23:12:08 +08:00
parent 6307af0736
commit 8703165e01
2 changed files with 102 additions and 2 deletions
+86
View File
@@ -4054,6 +4054,92 @@ describe('chatStore history mapping', () => {
expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.runtimeConfigReadyCount).toBe(1)
})
it('shows AskUserQuestion when permission arrives before the streamed tool block', () => {
const input = {
questions: [
{
question: 'Should we persist data?',
options: [{ label: 'No' }, { label: 'Yes' }],
},
],
}
useChatStore.setState({
sessions: { [TEST_SESSION_ID]: makeSession() },
})
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
type: 'permission_request',
requestId: 'perm-ask-permission-first',
toolName: 'AskUserQuestion',
toolUseId: 'tool-ask-permission-first',
input,
})
expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toContainEqual(
expect.objectContaining({
type: 'tool_use',
toolName: 'AskUserQuestion',
toolUseId: 'tool-ask-permission-first',
input,
}),
)
useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
type: 'tool_use_complete',
toolName: 'AskUserQuestion',
toolUseId: 'tool-ask-permission-first',
input,
})
expect(
useChatStore.getState().sessions[TEST_SESSION_ID]?.messages.filter(
(message) => message.type === 'tool_use' &&
message.toolUseId === 'tool-ask-permission-first',
),
).toHaveLength(1)
})
it('keeps one AskUserQuestion when the streamed tool block arrives first', () => {
const input = {
questions: [
{
question: 'Which scope?',
options: [{ label: 'Current file' }, { label: 'Repository' }],
},
],
}
useChatStore.setState({
sessions: { [TEST_SESSION_ID]: makeSession() },
})
const store = useChatStore.getState()
store.handleServerMessage(TEST_SESSION_ID, {
type: 'tool_use_complete',
toolName: 'AskUserQuestion',
toolUseId: 'tool-ask-stream-first',
input,
})
store.handleServerMessage(TEST_SESSION_ID, {
type: 'permission_request',
requestId: 'perm-ask-stream-first',
toolName: 'AskUserQuestion',
toolUseId: 'tool-ask-stream-first',
input,
})
const session = useChatStore.getState().sessions[TEST_SESSION_ID]
expect(session?.pendingPermission).toMatchObject({
requestId: 'perm-ask-stream-first',
toolUseId: 'tool-ask-stream-first',
})
expect(
session?.messages.filter(
(message) => message.type === 'tool_use' &&
message.toolUseId === 'tool-ask-stream-first',
),
).toHaveLength(1)
})
it('keeps AskUserQuestion permission requests out of the message list while tracking the pending request', () => {
useChatStore.setState({
sessions: {
+16 -2
View File
@@ -3340,6 +3340,7 @@ export const useChatStore = create<ChatStore>((set, get) => ({
}
const hasPermissionMessage = s.messages.some((message) =>
message.type === 'permission_request' && message.requestId === msg.requestId)
const isAskUserQuestion = msg.toolName === 'AskUserQuestion'
return {
pendingPermission,
@@ -3349,8 +3350,21 @@ export const useChatStore = create<ChatStore>((set, get) => ({
apiRetry: null,
streamingFallback: null,
messages:
msg.toolName === 'AskUserQuestion' || hasPermissionMessage
? s.messages
isAskUserQuestion && msg.toolUseId
? upsertToolUseMessage(s.messages, msg.toolUseId, (existing) => ({
id: existing?.id ?? nextId(),
type: 'tool_use',
toolName: msg.toolName,
toolUseId: msg.toolUseId!,
originalToolUseId: existing?.originalToolUseId,
input: msg.input,
timestamp: existing?.timestamp ?? Date.now(),
parentToolUseId: existing?.parentToolUseId,
isPending: false,
partialInput: existing?.partialInput,
}))
: isAskUserQuestion || hasPermissionMessage
? s.messages
: [...s.messages, {
id: nextId(),
type: 'permission_request',