fix(search): keep duplicate-search loop guard armed (#2117)

This commit is contained in:
mastertyko 2026-03-23 16:52:34 +01:00 committed by GitHub
parent 968815cd22
commit 06901f1c76
2 changed files with 32 additions and 7 deletions

View file

@ -398,16 +398,16 @@ export function registerSearchTool(pi: ExtensionAPI) {
// with brief interruptions every MAX_CONSECUTIVE_DUPES+1 calls.
if (cacheKey === lastSearchKey) {
consecutiveDupeCount++;
if (consecutiveDupeCount >= MAX_CONSECUTIVE_DUPES) {
if (consecutiveDupeCount > MAX_CONSECUTIVE_DUPES) {
return {
content: [{ type: "text" as const, text: `⚠️ Search loop detected: the query "${params.query}" has been searched ${consecutiveDupeCount + 1} times consecutively with identical results. The information you need is already in the previous search results above. Stop searching and use those results to proceed with your task.` }],
content: [{ type: "text" as const, text: `⚠️ Search loop detected: the query "${params.query}" has been searched ${consecutiveDupeCount} times consecutively with identical results. The information you need is already in the previous search results above. Stop searching and use those results to proceed with your task.` }],
isError: true,
details: { errorKind: "search_loop", error: "Consecutive duplicate search detected" } satisfies Partial<SearchDetails>,
};
}
} else {
lastSearchKey = cacheKey;
consecutiveDupeCount = 0;
consecutiveDupeCount = 1;
}
const cached = searchCache.get(cacheKey);

View file

@ -14,6 +14,23 @@ import assert from "node:assert/strict";
import { registerSearchTool } from "../resources/extensions/search-the-web/tool-search.ts";
import searchExtension from "../resources/extensions/search-the-web/index.ts";
const ORIGINAL_ENV = {
BRAVE_API_KEY: process.env.BRAVE_API_KEY,
TAVILY_API_KEY: process.env.TAVILY_API_KEY,
OLLAMA_API_KEY: process.env.OLLAMA_API_KEY,
};
function restoreSearchEnv() {
if (ORIGINAL_ENV.BRAVE_API_KEY === undefined) delete process.env.BRAVE_API_KEY;
else process.env.BRAVE_API_KEY = ORIGINAL_ENV.BRAVE_API_KEY;
if (ORIGINAL_ENV.TAVILY_API_KEY === undefined) delete process.env.TAVILY_API_KEY;
else process.env.TAVILY_API_KEY = ORIGINAL_ENV.TAVILY_API_KEY;
if (ORIGINAL_ENV.OLLAMA_API_KEY === undefined) delete process.env.OLLAMA_API_KEY;
else process.env.OLLAMA_API_KEY = ORIGINAL_ENV.OLLAMA_API_KEY;
}
// =============================================================================
// Mock helpers
// =============================================================================
@ -101,6 +118,8 @@ async function callSearch(
test("search loop guard fires after MAX_CONSECUTIVE_DUPES duplicates", async () => {
process.env.BRAVE_API_KEY = "test-key-loop-guard";
delete process.env.TAVILY_API_KEY;
delete process.env.OLLAMA_API_KEY;
const restoreFetch = mockFetch(makeBraveResponse());
try {
@ -127,12 +146,14 @@ test("search loop guard fires after MAX_CONSECUTIVE_DUPES duplicates", async ()
);
} finally {
restoreFetch();
delete process.env.BRAVE_API_KEY;
restoreSearchEnv();
}
});
test("search loop guard resets at session_start boundary", async () => {
process.env.BRAVE_API_KEY = "test-key-loop-guard-session";
delete process.env.TAVILY_API_KEY;
delete process.env.OLLAMA_API_KEY;
const restoreFetch = mockFetch(makeBraveResponse());
const query = "session boundary query";
@ -167,12 +188,14 @@ test("search loop guard resets at session_start boundary", async () => {
);
} finally {
restoreFetch();
delete process.env.BRAVE_API_KEY;
restoreSearchEnv();
}
});
test("search loop guard stays armed after firing — subsequent duplicates immediately re-trigger (#1671)", async () => {
process.env.BRAVE_API_KEY = "test-key-loop-guard-2";
delete process.env.TAVILY_API_KEY;
delete process.env.OLLAMA_API_KEY;
const restoreFetch = mockFetch(makeBraveResponse());
// Use a unique query so module-level state from previous test doesn't interfere
@ -209,12 +232,14 @@ test("search loop guard stays armed after firing — subsequent duplicates immed
);
} finally {
restoreFetch();
delete process.env.BRAVE_API_KEY;
restoreSearchEnv();
}
});
test("search loop guard resets cleanly when a different query is issued", async () => {
process.env.BRAVE_API_KEY = "test-key-loop-guard-3";
delete process.env.TAVILY_API_KEY;
delete process.env.OLLAMA_API_KEY;
const restoreFetch = mockFetch(makeBraveResponse());
const queryA = "query alpha reset test";
@ -239,6 +264,6 @@ test("search loop guard resets cleanly when a different query is issued", async
);
} finally {
restoreFetch();
delete process.env.BRAVE_API_KEY;
restoreSearchEnv();
}
});