feat(approvals): auto-enable native chat approvals

This commit is contained in:
Peter Steinberger
2026-04-02 17:28:32 +01:00
parent 721cab2b8d
commit 17f6626ffe
26 changed files with 197 additions and 113 deletions

View File

@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import {
createChannelExecApprovalProfile,
isChannelExecApprovalClientEnabledFromConfig,
isChannelExecApprovalTargetRecipient,
} from "./approval-client-helpers.js";
import type { OpenClawConfig } from "./config-runtime.js";
@@ -76,6 +77,37 @@ describe("createChannelExecApprovalProfile", () => {
matchesRequestAccount: ({ accountId }) => accountId !== "other",
});
it("treats unset enabled as auto and false as disabled", () => {
expect(
isChannelExecApprovalClientEnabledFromConfig({
approverCount: 1,
}),
).toBe(true);
expect(
isChannelExecApprovalClientEnabledFromConfig({
enabled: "auto",
approverCount: 1,
}),
).toBe(true);
expect(
isChannelExecApprovalClientEnabledFromConfig({
enabled: true,
approverCount: 1,
}),
).toBe(true);
expect(
isChannelExecApprovalClientEnabledFromConfig({
enabled: false,
approverCount: 1,
}),
).toBe(false);
expect(
isChannelExecApprovalClientEnabledFromConfig({
approverCount: 0,
}),
).toBe(false);
});
it("reuses shared client, auth, and request-filter logic", () => {
expect(profile.isClientEnabled({ cfg: {} })).toBe(true);
expect(profile.isApprover({ cfg: {}, senderId: "owner" })).toBe(true);

View File

@@ -9,9 +9,10 @@ import { normalizeAccountId } from "./routing.js";
type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
type ApprovalTarget = "dm" | "channel" | "both";
type ChannelExecApprovalEnableMode = boolean | "auto";
type ChannelApprovalConfig = {
enabled?: boolean;
enabled?: ChannelExecApprovalEnableMode;
target?: ApprovalTarget;
agentFilter?: string[];
sessionFilter?: string[];
@@ -35,6 +36,16 @@ function isApprovalTargetsMode(cfg: OpenClawConfig): boolean {
return execApprovals.mode === "targets" || execApprovals.mode === "both";
}
export function isChannelExecApprovalClientEnabledFromConfig(params: {
enabled?: ChannelExecApprovalEnableMode;
approverCount: number;
}): boolean {
if (params.approverCount <= 0) {
return false;
}
return params.enabled !== false;
}
export function isChannelExecApprovalTargetRecipient(params: {
cfg: OpenClawConfig;
senderId?: string | null;
@@ -91,7 +102,10 @@ export function createChannelExecApprovalProfile(params: {
const isClientEnabled = (input: ApprovalProfileParams): boolean => {
const config = params.resolveConfig(input);
return Boolean(config?.enabled && params.resolveApprovers(input).length > 0);
return isChannelExecApprovalClientEnabledFromConfig({
enabled: config?.enabled,
approverCount: params.resolveApprovers(input).length,
});
};
const isApprover = (input: ApprovalProfileParams & { senderId?: string | null }): boolean => {
@@ -119,16 +133,19 @@ export function createChannelExecApprovalProfile(params: {
return false;
}
const config = params.resolveConfig(input);
if (!config?.enabled) {
return false;
}
if (params.resolveApprovers(input).length === 0) {
const approverCount = params.resolveApprovers(input).length;
if (
!isChannelExecApprovalClientEnabledFromConfig({
enabled: config?.enabled,
approverCount,
})
) {
return false;
}
return matchesApprovalRequestFilters({
request: input.request.request,
agentFilter: config.agentFilter,
sessionFilter: config.sessionFilter,
agentFilter: config?.agentFilter,
sessionFilter: config?.sessionFilter,
fallbackAgentIdFromSessionKey: params.fallbackAgentIdFromSessionKey === true,
});
};

View File

@@ -40,6 +40,7 @@ export {
export { createResolvedApproverActionAuthAdapter } from "./approval-auth-helpers.js";
export {
createChannelExecApprovalProfile,
isChannelExecApprovalClientEnabledFromConfig,
isChannelExecApprovalTargetRecipient,
} from "./approval-client-helpers.js";
export {