Fix tool call allow still showing initial state in chat after navigating back (#3498)

This commit is contained in:
Zane
2025-07-17 19:43:35 -07:00
committed by GitHub
parent 1e0e72cdb8
commit b7057b1eda
2 changed files with 84 additions and 16 deletions
+1 -1
View File
@@ -220,7 +220,7 @@ export default function GooseMessage({
{hasToolConfirmation && ( {hasToolConfirmation && (
<ToolCallConfirmation <ToolCallConfirmation
isCancelledMessage={messageIndex == messageHistoryIndex - 1} isCancelledMessage={messageIndex == messageHistoryIndex - 1}
isClicked={messageIndex < messageHistoryIndex - 1} isClicked={messageIndex < messageHistoryIndex}
toolConfirmationId={toolConfirmationContent.id} toolConfirmationId={toolConfirmationContent.id}
toolName={toolConfirmationContent.toolName} toolName={toolConfirmationContent.toolName}
/> />
@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { snakeToTitleCase } from '../utils'; import { snakeToTitleCase } from '../utils';
import PermissionModal from './settings/permission/PermissionModal'; import PermissionModal from './settings/permission/PermissionModal';
import { ChevronRight } from 'lucide-react'; import { ChevronRight } from 'lucide-react';
@@ -9,6 +9,17 @@ const ALWAYS_ALLOW = 'always_allow';
const ALLOW_ONCE = 'allow_once'; const ALLOW_ONCE = 'allow_once';
const DENY = 'deny'; const DENY = 'deny';
// Global state to track tool confirmation decisions
// This persists across navigation within the same session
const toolConfirmationState = new Map<
string,
{
clicked: boolean;
status: string;
actionDisplay: string;
}
>();
interface ToolConfirmationProps { interface ToolConfirmationProps {
isCancelledMessage: boolean; isCancelledMessage: boolean;
isClicked: boolean; isClicked: boolean;
@@ -22,30 +33,75 @@ export default function ToolConfirmation({
toolConfirmationId, toolConfirmationId,
toolName, toolName,
}: ToolConfirmationProps) { }: ToolConfirmationProps) {
const [clicked, setClicked] = useState(isClicked); // Check if we have a stored state for this tool confirmation
const [status, setStatus] = useState('unknown'); const storedState = toolConfirmationState.get(toolConfirmationId);
const [actionDisplay, setActionDisplay] = useState('');
// Initialize state from stored state if available, otherwise use props/defaults
const [clicked, setClicked] = useState(storedState?.clicked ?? isClicked);
const [status, setStatus] = useState(storedState?.status ?? 'unknown');
const [actionDisplay, setActionDisplay] = useState(storedState?.actionDisplay ?? '');
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const handleButtonClick = async (action: string) => { // Sync internal state with stored state and props
setClicked(true); useEffect(() => {
setStatus(action); const currentStoredState = toolConfirmationState.get(toolConfirmationId);
if (action === ALWAYS_ALLOW) {
setActionDisplay('always allowed'); // If we have stored state, use it
} else if (action === ALLOW_ONCE) { if (currentStoredState) {
setActionDisplay('allowed once'); setClicked(currentStoredState.clicked);
} else { setStatus(currentStoredState.status);
setActionDisplay('denied'); setActionDisplay(currentStoredState.actionDisplay);
} else if (isClicked && !clicked) {
// Fallback to prop-based logic for historical confirmations
setClicked(isClicked);
if (status === 'unknown') {
setStatus('confirmed');
setActionDisplay('confirmed');
// Store this state for future renders
toolConfirmationState.set(toolConfirmationId, {
clicked: true,
status: 'confirmed',
actionDisplay: 'confirmed',
});
}
} }
}, [isClicked, clicked, status, toolName, toolConfirmationId]);
const handleButtonClick = async (action: string) => {
const newClicked = true;
const newStatus = action;
let newActionDisplay = '';
if (action === ALWAYS_ALLOW) {
newActionDisplay = 'always allowed';
} else if (action === ALLOW_ONCE) {
newActionDisplay = 'allowed once';
} else {
newActionDisplay = 'denied';
}
// Update local state
setClicked(newClicked);
setStatus(newStatus);
setActionDisplay(newActionDisplay);
// Store in global state for persistence across navigation
toolConfirmationState.set(toolConfirmationId, {
clicked: newClicked,
status: newStatus,
actionDisplay: newActionDisplay,
});
try { try {
const response = await confirmPermission({ const response = await confirmPermission({
body: { id: toolConfirmationId, action, principal_type: 'Tool' }, body: { id: toolConfirmationId, action, principal_type: 'Tool' },
}); });
if (response.error) { if (response.error) {
console.error('Failed to confirm permission: ', response.error); console.error('Failed to confirm permission:', response.error);
} }
} catch (err) { } catch (err) {
console.error('Error fetching tools:', err); console.error('Error confirming permission:', err);
} }
}; };
@@ -106,6 +162,18 @@ export default function ToolConfirmation({
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
)} )}
{status === 'confirmed' && (
<svg
className="w-5 h-5 text-gray-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
)}
<span className="ml-2 text-textStandard"> <span className="ml-2 text-textStandard">
{isClicked {isClicked
? 'Tool confirmation is not available' ? 'Tool confirmation is not available'