v1.11.6: doom-loop guard (3 identical tool calls aborts recursion)
This commit is contained in:
43
apps/web/src/components/DoomLoopSentinel.tsx
Normal file
43
apps/web/src/components/DoomLoopSentinel.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import type { Message } from '@/api/types';
|
||||
|
||||
interface Props {
|
||||
message: Message;
|
||||
}
|
||||
|
||||
// v1.11.6: doom-loop sentinel. Renders the system row inserted by
|
||||
// services/inference.ts insertDoomLoopSentinel when the model called the
|
||||
// same tool with the same arguments threshold times in a row. Visual
|
||||
// treatment mirrors CapHitSentinel (amber card + alert icon) so users learn
|
||||
// "amber alert = the loop hit a guard rail and stopped" regardless of
|
||||
// which guard fired. Intentionally NO Continue button — retrying with the
|
||||
// same tools would just re-loop; the user needs to restate the prompt or
|
||||
// switch agents instead.
|
||||
export function DoomLoopSentinel({ message }: Props) {
|
||||
const meta = message.metadata;
|
||||
const isDoomLoop =
|
||||
meta !== null && typeof meta === 'object' && meta.kind === 'doom_loop';
|
||||
const toolName = isDoomLoop ? meta.tool_name : null;
|
||||
const threshold = isDoomLoop ? meta.threshold : null;
|
||||
|
||||
return (
|
||||
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 text-sm">
|
||||
<div className="px-3 py-2 flex items-start gap-2">
|
||||
<AlertCircle className="size-4 text-amber-500 shrink-0 mt-0.5" />
|
||||
<div className="flex-1 min-w-0 space-y-1">
|
||||
<div className="text-xs font-medium text-amber-700 dark:text-amber-300">
|
||||
Doom loop detected
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{toolName !== null && threshold !== null
|
||||
? `Stopped after ${threshold} identical calls to ${toolName}. The model was looping.`
|
||||
: message.content}
|
||||
</div>
|
||||
<div className="text-[11px] text-muted-foreground/80">
|
||||
Send a new message with a different angle, or switch agents.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { api } from '@/api/client';
|
||||
import { sessionEvents } from '@/hooks/sessionEvents';
|
||||
import { sendToTerminal, terminalsRegistry, type TerminalRegistration } from '@/lib/events';
|
||||
import { CapHitSentinel } from './CapHitSentinel';
|
||||
import { DoomLoopSentinel } from './DoomLoopSentinel';
|
||||
import { CodeBlock } from './CodeBlock';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@@ -622,6 +623,13 @@ export function MessageBubble({ message, sessionChats, capHitInfo }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
// v1.11.6: doom-loop sentinel. No Continue affordance — retrying with the
|
||||
// same tools would just re-loop. The card explains what tripped and
|
||||
// suggests next steps (new message angle / switch agents).
|
||||
if (message.role === 'system' && message.metadata?.kind === 'doom_loop') {
|
||||
return <DoomLoopSentinel message={message} />;
|
||||
}
|
||||
|
||||
// v1.8.2: tool messages and assistant tool_calls are now rendered by
|
||||
// MessageList via ToolCallLine / ToolCallGroup. Tool-role messages reach
|
||||
// this point only if MessageList didn't consume them (shouldn't happen,
|
||||
|
||||
Reference in New Issue
Block a user