mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
feat(ui): enable tool-loop detection from Labs (#115983)
* feat(ui): add tool loop detection lab * docs(tools): mention loop detection lab
This commit is contained in:
committed by
GitHub
parent
e20a0039b3
commit
d7d3fc5dad
@@ -64,6 +64,8 @@ Per-agent override (optional, at `agents.entries.*.tools.loopDetection`):
|
||||
|
||||
The per-agent setting overrides the global setting.
|
||||
|
||||
You can also enable the global rolling-history detectors in **Settings -> Labs** in the Control UI.
|
||||
|
||||
### Field behavior
|
||||
|
||||
| Field | Default | Effect |
|
||||
|
||||
@@ -2695,6 +2695,11 @@ export const en: TranslationMap = {
|
||||
description:
|
||||
"Keep a bounded tool directory visible and defer the rest behind search, so large MCP and plugin catalogs stop crowding the prompt.",
|
||||
},
|
||||
loopDetection: {
|
||||
title: "Tool-loop detection",
|
||||
description:
|
||||
"Enable rolling-history guards that warn or block repeated tool calls when an agent stops making progress.",
|
||||
},
|
||||
localModelLean: {
|
||||
title: "Lean tools for local models",
|
||||
description:
|
||||
|
||||
@@ -159,8 +159,15 @@ describe("LabsPage", () => {
|
||||
note: "labs: update toolSearch",
|
||||
},
|
||||
{
|
||||
label: "Lean tools for local models",
|
||||
label: "Tool-loop detection",
|
||||
index: 3,
|
||||
sourceConfig: { tools: { loopDetection: { enabled: false } } },
|
||||
expectedPatch: { tools: { loopDetection: { enabled: true } } },
|
||||
note: "labs: update loopDetection",
|
||||
},
|
||||
{
|
||||
label: "Lean tools for local models",
|
||||
index: 4,
|
||||
sourceConfig: {},
|
||||
expectedPatch: { agents: { defaults: { experimental: { localModelLean: true } } } },
|
||||
note: "labs: update localModelLean",
|
||||
@@ -169,7 +176,7 @@ describe("LabsPage", () => {
|
||||
// Not a boolean gate: the on state is the conservative `direct` mode, so
|
||||
// enabling here cannot start recording group or unknown conversations.
|
||||
label: "Message audit metadata",
|
||||
index: 4,
|
||||
index: 5,
|
||||
sourceConfig: { logging: { audit: { messages: "off" } } },
|
||||
expectedPatch: { logging: { audit: { messages: "direct" } } },
|
||||
note: "labs: update auditMessages",
|
||||
@@ -324,3 +331,44 @@ describe("LabsPage tool search enablement", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("LabsPage tool loop detection enablement", () => {
|
||||
const loopDetectionIndex = LAB_FEATURES.findIndex((feature) => feature.id === "loopDetection");
|
||||
|
||||
// Mirrors resolveToolLoopDetectionConfig and the detector default: only an
|
||||
// explicit true enables the rolling-history detectors.
|
||||
it.each([
|
||||
{ label: "unset", config: {}, expected: false },
|
||||
{
|
||||
label: "explicit enabled",
|
||||
config: { tools: { loopDetection: { enabled: true } } },
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
label: "explicit disabled",
|
||||
config: { tools: { loopDetection: { enabled: false } } },
|
||||
expected: false,
|
||||
},
|
||||
])("reads $label as $expected", async ({ config, expected }) => {
|
||||
const { page, provider } = await mountPage(config);
|
||||
|
||||
expect(labToggle(page, loopDetectionIndex, "Tool-loop detection").checked).toBe(expected);
|
||||
provider.remove();
|
||||
});
|
||||
|
||||
it("patches only enabled so sibling settings remain untouched", async () => {
|
||||
const { page, runtimeConfig } = await mountPage({
|
||||
tools: { loopDetection: { enabled: false, warningThreshold: 12 } },
|
||||
});
|
||||
const toggle = labToggle(page, loopDetectionIndex, "Tool-loop detection");
|
||||
|
||||
toggle.checked = true;
|
||||
toggle.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
|
||||
|
||||
await vi.waitFor(() => expect(runtimeConfig.patch).toHaveBeenCalledOnce());
|
||||
expect(runtimeConfig.patch).toHaveBeenCalledWith({
|
||||
raw: { tools: { loopDetection: { enabled: true } } },
|
||||
note: "labs: update loopDetection",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -111,6 +111,21 @@ export const LAB_FEATURES = [
|
||||
enableAlso: { mode: "directory" },
|
||||
restartHint: null,
|
||||
},
|
||||
{
|
||||
id: "loopDetection",
|
||||
title: () => t("labsPage.loopDetection.title"),
|
||||
description: () => t("labsPage.loopDetection.description"),
|
||||
docsUrl: "https://docs.openclaw.ai/tools/loop-detection",
|
||||
configPath: ["tools", "loopDetection", "enabled"],
|
||||
onValue: true,
|
||||
offValue: false,
|
||||
activeValues: [true],
|
||||
// ToolLoopDetectionSchema accepts object form only, and
|
||||
// resolveToolLoopDetectionConfig reads this enabled leaf directly.
|
||||
readEnabled: null,
|
||||
enableAlso: null,
|
||||
restartHint: null,
|
||||
},
|
||||
{
|
||||
id: "localModelLean",
|
||||
title: () => t("labsPage.localModelLean.title"),
|
||||
|
||||
Reference in New Issue
Block a user