feat(ui): Add confirmation dialog for disabling loop detection for current session (#8231)

This commit is contained in:
Sandy Tao
2025-09-10 22:20:13 -07:00
committed by GitHub
parent 5b2176770e
commit 78744cfbca
12 changed files with 498 additions and 39 deletions

View File

@@ -186,6 +186,10 @@ export class GeminiClient {
return this.chat?.getChatRecordingService();
}
getLoopDetectionService(): LoopDetectionService {
return this.loopDetector;
}
async addDirectoryContext(): Promise<void> {
if (!this.chat) {
return;

View File

@@ -130,6 +130,15 @@ describe('LoopDetectionService', () => {
expect(service.addAndCheck(toolCallEvent)).toBe(true);
expect(loggers.logLoopDetected).toHaveBeenCalledTimes(1);
});
it('should not detect a loop when disabled for session', () => {
service.disableForSession();
const event = createToolCallRequestEvent('testTool', { param: 'value' });
for (let i = 0; i < TOOL_CALL_LOOP_THRESHOLD; i++) {
expect(service.addAndCheck(event)).toBe(false);
}
expect(loggers.logLoopDetected).not.toHaveBeenCalled();
});
});
describe('Content Loop Detection', () => {
@@ -719,4 +728,12 @@ describe('LoopDetectionService LLM Checks', () => {
expect(result).toBe(false);
expect(loggers.logLoopDetected).not.toHaveBeenCalled();
});
it('should not trigger LLM check when disabled for session', async () => {
service.disableForSession();
await advanceTurns(30);
const result = await service.turnStarted(abortController.signal);
expect(result).toBe(false);
expect(mockGeminiClient.generateJson).not.toHaveBeenCalled();
});
});

View File

@@ -74,10 +74,20 @@ export class LoopDetectionService {
private llmCheckInterval = DEFAULT_LLM_CHECK_INTERVAL;
private lastCheckTurn = 0;
// Session-level disable flag
private disabledForSession = false;
constructor(config: Config) {
this.config = config;
}
/**
* Disables loop detection for the current session.
*/
disableForSession(): void {
this.disabledForSession = true;
}
private getToolCallKey(toolCall: { name: string; args: object }): string {
const argsString = JSON.stringify(toolCall.args);
const keyString = `${toolCall.name}:${argsString}`;
@@ -90,8 +100,8 @@ export class LoopDetectionService {
* @returns true if a loop is detected, false otherwise
*/
addAndCheck(event: ServerGeminiStreamEvent): boolean {
if (this.loopDetected) {
return true;
if (this.loopDetected || this.disabledForSession) {
return this.loopDetected;
}
switch (event.type) {
@@ -121,6 +131,9 @@ export class LoopDetectionService {
* @returns A promise that resolves to `true` if a loop is detected, and `false` otherwise.
*/
async turnStarted(signal: AbortSignal) {
if (this.disabledForSession) {
return false;
}
this.turnsInCurrentPrompt++;
if (