generated from nhcarrigan/template
feat: add AskUserQuestion tool support (#60)
## Summary Implements support for Claude's `AskUserQuestion` tool, allowing Claude to ask the user questions with multiple choice options during a conversation. ## Changes - Add `UserQuestionEvent` and `QuestionOption` types (Rust and TypeScript) - Detect `AskUserQuestion` in permission denials and emit `claude:question` event - Create `UserQuestionModal` component with option selection and custom answer input - Use stop/reconnect approach (same as `PermissionModal`) since Claude API doesn't accept tool_result for permission-denied tools - Add `pendingQuestion` to conversation store and `hasQuestionPending` derived store ## Technical Notes We discovered that Claude Code's permission denial system doesn't allow sending tool results back directly - the API rejects them with "unexpected tool_use_id found in tool_result blocks". The solution was to use the same stop/reconnect pattern that permissions use: stop the session, reconnect with context, and include the user's answer in the context restoration message. ## Test Plan - [x] Build compiles without errors (Rust + TypeScript) - [x] Question modal appears when Claude uses `AskUserQuestion` - [x] Can select options and submit answer - [x] Answer is properly restored to Claude after reconnect Closes #51 --- ✨ This PR was created with help from Hikari~ 🌸 Co-authored-by: Hikari <hikari@nhcarrigan.com> Reviewed-on: #60
This commit was merged in pull request #60.
This commit is contained in:
+24
-1
@@ -6,7 +6,11 @@ import { characterState } from "$lib/stores/character";
|
||||
import { configStore } from "$lib/stores/config";
|
||||
import { initStatsListener, resetSessionStats } from "$lib/stores/stats";
|
||||
import { initAchievementsListener } from "$lib/stores/achievements";
|
||||
import type { ConnectionStatus, PermissionPromptEvent } from "$lib/types/messages";
|
||||
import type {
|
||||
ConnectionStatus,
|
||||
PermissionPromptEvent,
|
||||
UserQuestionEvent,
|
||||
} from "$lib/types/messages";
|
||||
import type { CharacterState } from "$lib/types/states";
|
||||
import {
|
||||
initializeNotificationRules,
|
||||
@@ -317,6 +321,25 @@ export async function initializeTauriListeners() {
|
||||
}
|
||||
});
|
||||
unlisteners.push(permissionUnlisten);
|
||||
|
||||
const questionUnlisten = await listen<UserQuestionEvent>("claude:question", (event) => {
|
||||
const questionEvent = event.payload;
|
||||
|
||||
// Store question request for the specific conversation
|
||||
if (questionEvent.conversation_id) {
|
||||
claudeStore.requestQuestionForConversation(questionEvent.conversation_id, questionEvent);
|
||||
claudeStore.addLineToConversation(
|
||||
questionEvent.conversation_id,
|
||||
"system",
|
||||
`Question: ${questionEvent.question}`
|
||||
);
|
||||
} else {
|
||||
// Fallback to active conversation if no conversation_id
|
||||
claudeStore.requestQuestion(questionEvent);
|
||||
claudeStore.addLine("system", `Question: ${questionEvent.question}`);
|
||||
}
|
||||
});
|
||||
unlisteners.push(questionUnlisten);
|
||||
}
|
||||
|
||||
export function cleanupTauriListeners() {
|
||||
|
||||
Reference in New Issue
Block a user