Skip to content

Commit 0aea77c

Browse files
fix(Chat): don't ask for tool call after rejection
Rejecting a tool call now displays a rejection message and returns directly to idle β€” no re-stream, no repeated approval prompt.
1 parent 07f9fe9 commit 0aea77c

3 files changed

Lines changed: 26 additions & 20 deletions

File tree

β€Žsrc/components/Chat/Chat.test.tsxβ€Ž

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,11 +1040,9 @@ describe('Chat with tool calls', () => {
10401040
rerender(chat);
10411041

10421042
expect(lastFrame()).not.toContain('Tool requires approval');
1043-
const calls = vi.mocked(ollama.streamChat).mock.calls;
1044-
const lastCallMessages = calls[calls.length - 1][0];
1045-
expect(
1046-
lastCallMessages.some((m) => m.content.includes('<turn_aborted>')),
1047-
).toBe(true);
1043+
expect(lastFrame()).toContain('❗ Tool call rejected.');
1044+
expect(lastFrame()).toContain('>');
1045+
expect(vi.mocked(ollama.streamChat)).toHaveBeenCalledOnce();
10481046
});
10491047

10501048
it('handles tool approval acceptance', async () => {

β€Žsrc/components/Chat/Chat.tsxβ€Ž

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PlanApproval } from '../PlanApproval';
99
import { ToolApproval } from '../ToolApproval';
1010
import {
1111
ACTION_NOT_PERFORMED,
12+
INTERRUPT_REASON,
1213
PLAN_CHECKLIST_REMINDER,
1314
PLAN_EXECUTION_REMINDER,
1415
} from './constants';
@@ -33,14 +34,18 @@ export function Chat({
3334
const [messages, setMessages] = useState<ollama.Message[]>([]);
3435
const [streamingMessage, setStreamingMessage] =
3536
useState<ollama.Message | null>(null);
37+
3638
const [isLoading, setIsLoading] = useState(false);
39+
3740
const [pendingToolCall, setPendingToolCall] =
3841
useState<ollama.ToolCall | null>(null);
3942
const [pendingPlan, setPendingPlan] = useState<{
4043
planContent: string;
4144
messages: ollama.Message[];
4245
} | null>(null);
43-
const [wasInterrupted, setWasInterrupted] = useState(false);
46+
47+
const [interruptReason, setInterruptReason] =
48+
useState<INTERRUPT_REASON | null>(null);
4449
const abortControllerRef = useRef<AbortController | null>(null);
4550

4651
useEffect(() => {
@@ -49,7 +54,7 @@ export function Chat({
4954
setIsLoading(false);
5055
setPendingToolCall(null);
5156
setPendingPlan(null);
52-
setWasInterrupted(false);
57+
setInterruptReason(null);
5358
}, [sessionId]);
5459

5560
const buildToolResultMessage = useCallback(
@@ -93,7 +98,7 @@ export function Chat({
9398
abortControllerRef.current = null;
9499
setIsLoading(false);
95100
setStreamingMessage(null);
96-
setWasInterrupted(true);
101+
setInterruptReason(INTERRUPT_REASON.INTERRUPTED);
97102
setMessages((prev) => [
98103
...prev,
99104
{ role: ROLE.USER, content: TURN_ABORTED_MESSAGE },
@@ -477,18 +482,12 @@ export function Chat({
477482
}
478483

479484
case DECISION.REJECT: {
480-
const rejectionMessage: ollama.Message = {
481-
role: ROLE.USER,
482-
content: TURN_ABORTED_MESSAGE,
483-
};
484-
485-
const newMessages = [...messages, rejectionMessage];
486485
setMessages((previousMessages) => [
487486
...previousMessages,
488-
rejectionMessage,
487+
{ role: ROLE.USER, content: TURN_ABORTED_MESSAGE },
489488
]);
490-
491-
await processStream(newMessages);
489+
setIsLoading(false);
490+
setInterruptReason(INTERRUPT_REASON.REJECTED);
492491
break;
493492
}
494493
}
@@ -498,7 +497,7 @@ export function Chat({
498497

499498
const handleSubmit = useCallback(
500499
async (value: string) => {
501-
setWasInterrupted(false);
500+
setInterruptReason(null);
502501
const userContent = value.trim();
503502

504503
if (!userContent) {
@@ -553,9 +552,13 @@ export function Chat({
553552
/>
554553
)}
555554

556-
{wasInterrupted && !isLoading && (
555+
{interruptReason && !isLoading && (
557556
<Box marginBottom={1}>
558-
<Text color="red">❗ Execution interrupted.</Text>
557+
<Text color="red">
558+
{interruptReason === INTERRUPT_REASON.REJECTED
559+
? '❗ Tool call rejected.'
560+
: '❗ Execution interrupted.'}
561+
</Text>
559562
</Box>
560563
)}
561564

β€Žsrc/components/Chat/constants.tsβ€Ž

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ export const PLAN_CHECKLIST_REMINDER =
55

66
export const PLAN_EXECUTION_REMINDER =
77
'Do not claim success and do not call write_file or run_shell until the user approves execution';
8+
9+
export enum INTERRUPT_REASON {
10+
INTERRUPTED = 'interrupted',
11+
REJECTED = 'rejected',
12+
}

0 commit comments

Comments
Β (0)