From 30214a40cb7c5fa5d6833f57fd4884bc17ffb787 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Thu, 7 May 2026 22:58:47 -0500 Subject: [PATCH] fix(ui): read exec policy from tools config (#79119) thanks @BunsDev Co-authored-by: Nova --- CHANGELOG.md | 1 + ui/src/ui/app-render.exec-policy.test.ts | 62 ++++++++++++++++++++++++ ui/src/ui/app-render.ts | 20 ++++---- 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 ui/src/ui/app-render.exec-policy.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 182aa1f74ec..4553aa6afe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai ### Changes +- Control UI: read the Quick Settings exec policy badge from `tools.exec.security` instead of the non-schema `agents.defaults.exec.security` path, so configured `full`/`deny` values render accurately. Fixes #78311. Thanks @FriedBack. - Control UI/usage: add transcript-backed historical lineage rollups for rotated logical sessions, with current-instance vs historical-lineage scope controls and long-range presets so usage history stays visible after restarts and updates. Fixes #50701. Thanks @dev-gideon-llc and @BunsDev. - Agents/failover: harden state-aware lane suspension by persisting quota resume transitions, restoring configured lane concurrency, preserving non-quota failure reasons, and exporting model failover events through diagnostics OTLP. Thanks @BunsDev. - Channels/streaming: make progress draft labels scroll away with other progress lines, render structured tool rows as compact emoji/title/details, show web-search queries from provider-native argument shapes, and skip empty Discord apply-patch starts until a patch summary exists. (#79146) diff --git a/ui/src/ui/app-render.exec-policy.test.ts b/ui/src/ui/app-render.exec-policy.test.ts new file mode 100644 index 00000000000..5414b566d46 --- /dev/null +++ b/ui/src/ui/app-render.exec-policy.test.ts @@ -0,0 +1,62 @@ +// @vitest-environment jsdom +import { describe, expect, it } from "vitest"; +import { extractQuickSettingsSecurity } from "./app-render.ts"; +import type { AppViewState } from "./app-view-state.ts"; + +function makeState(config: Record): AppViewState { + return { configForm: config } as unknown as AppViewState; +} + +describe("extractQuickSettingsSecurity", () => { + it("reads execPolicy from the canonical tools.exec.security path", () => { + const result = extractQuickSettingsSecurity( + makeState({ tools: { exec: { security: "full" } } }), + ); + + expect(result.execPolicy).toBe("full"); + }); + + it("reads execPolicy from tools.exec.security when set to deny", () => { + const result = extractQuickSettingsSecurity( + makeState({ tools: { exec: { security: "deny" } } }), + ); + + expect(result.execPolicy).toBe("deny"); + }); + + it("falls back to allowlist when tools.exec.security is missing", () => { + expect(extractQuickSettingsSecurity(makeState({})).execPolicy).toBe("allowlist"); + expect(extractQuickSettingsSecurity(makeState({ tools: { exec: {} } })).execPolicy).toBe( + "allowlist", + ); + }); + + it("ignores agents.defaults.exec.security because it is not a schema path", () => { + const result = extractQuickSettingsSecurity( + makeState({ + tools: { exec: { security: "full" } }, + agents: { defaults: { exec: { security: "deny" } } }, + }), + ); + + expect(result.execPolicy).toBe("full"); + }); + + it("does not treat agents.defaults.exec.security as a fallback", () => { + const result = extractQuickSettingsSecurity( + makeState({ agents: { defaults: { exec: { security: "full" } } } }), + ); + + expect(result.execPolicy).toBe("allowlist"); + }); + + it("trims whitespace and ignores empty strings", () => { + expect( + extractQuickSettingsSecurity(makeState({ tools: { exec: { security: " full " } } })) + .execPolicy, + ).toBe("full"); + expect( + extractQuickSettingsSecurity(makeState({ tools: { exec: { security: " " } } })).execPolicy, + ).toBe("allowlist"); + }); +}); diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index 345822a2503..98c8ce79abe 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -545,7 +545,7 @@ function extractMcpServerCount(state: AppViewState): number { return Object.keys(servers).length; } -function extractQuickSettingsSecurity(state: AppViewState): { +export function extractQuickSettingsSecurity(state: AppViewState): { gatewayAuth: string; execPolicy: string; deviceAuth: boolean; @@ -578,16 +578,16 @@ function extractQuickSettingsSecurity(state: AppViewState): { gatewayAuth = "none"; } } - const agents = cfg.agents; let execPolicy = "allowlist"; - if (agents && typeof agents === "object") { - const defaults = (agents as Record).defaults; - if (defaults && typeof defaults === "object") { - const exec = (defaults as Record).exec; - if (exec && typeof exec === "object") { - const security = (exec as Record).security; - if (typeof security === "string") { - execPolicy = security; + const tools = cfg.tools; + if (tools && typeof tools === "object") { + const exec = (tools as Record).exec; + if (exec && typeof exec === "object") { + const security = (exec as Record).security; + if (typeof security === "string") { + const trimmedSecurity = security.trim(); + if (trimmedSecurity) { + execPolicy = trimmedSecurity; } } }