fix(ui): read exec policy from tools config (#79119) thanks @BunsDev

Co-authored-by: Nova <nova@openclaw.local>
This commit is contained in:
Val Alexander
2026-05-07 22:58:47 -05:00
committed by GitHub
parent b1eedb2fc8
commit 30214a40cb
3 changed files with 73 additions and 10 deletions

View File

@@ -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)

View File

@@ -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<string, unknown>): 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");
});
});

View File

@@ -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<string, unknown>).defaults;
if (defaults && typeof defaults === "object") {
const exec = (defaults as Record<string, unknown>).exec;
if (exec && typeof exec === "object") {
const security = (exec as Record<string, unknown>).security;
if (typeof security === "string") {
execPolicy = security;
const tools = cfg.tools;
if (tools && typeof tools === "object") {
const exec = (tools as Record<string, unknown>).exec;
if (exec && typeof exec === "object") {
const security = (exec as Record<string, unknown>).security;
if (typeof security === "string") {
const trimmedSecurity = security.trim();
if (trimmedSecurity) {
execPolicy = trimmedSecurity;
}
}
}