mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-21 05:32:53 +00:00
feat(approvals): auto-enable native chat approvals
This commit is contained in:
@@ -11566,11 +11566,11 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
},
|
||||
execApprovals: {
|
||||
label: "Slack Exec Approvals",
|
||||
help: "Slack-native exec approval routing and approver authorization. Enable this only when Slack should act as an explicit exec-approval client for the selected workspace account.",
|
||||
help: "Slack-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for this workspace account.",
|
||||
},
|
||||
"execApprovals.enabled": {
|
||||
label: "Slack Exec Approvals Enabled",
|
||||
help: "Enable Slack exec approvals for this account. When false or unset, Slack messages/buttons cannot approve exec requests.",
|
||||
help: 'Controls Slack native exec approvals for this account: unset or "auto" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them.',
|
||||
},
|
||||
"execApprovals.approvers": {
|
||||
label: "Slack Exec Approval Approvers",
|
||||
@@ -13721,11 +13721,11 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
},
|
||||
execApprovals: {
|
||||
label: "Telegram Exec Approvals",
|
||||
help: "Telegram-native exec approval routing and approver authorization. Enable this only when Telegram should act as an explicit exec-approval client for the selected bot account.",
|
||||
help: "Telegram-native exec approval routing and approver authorization. When unset, OpenClaw auto-enables DM-first native approvals if approvers can be resolved for the selected bot account.",
|
||||
},
|
||||
"execApprovals.enabled": {
|
||||
label: "Telegram Exec Approvals Enabled",
|
||||
help: "Enable Telegram exec approvals for this account. When false or unset, Telegram messages/buttons cannot approve exec requests.",
|
||||
help: 'Controls Telegram native exec approvals for this account: unset or "auto" enables DM-first native approvals when approvers can be resolved, true forces native approvals on, and false disables them.',
|
||||
},
|
||||
"execApprovals.approvers": {
|
||||
label: "Telegram Exec Approval Approvers",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export type NativeExecApprovalEnableMode = boolean | "auto";
|
||||
|
||||
export type ExecApprovalForwardingMode = "session" | "targets" | "both";
|
||||
|
||||
export type ExecApprovalForwardTarget = {
|
||||
|
||||
@@ -140,8 +140,8 @@ export type DiscordVoiceConfig = {
|
||||
};
|
||||
|
||||
export type DiscordExecApprovalConfig = {
|
||||
/** Enable exec approval forwarding to Discord DMs. Default: false. */
|
||||
enabled?: boolean;
|
||||
/** Enable mode for Discord exec approvals on this account. Default: auto when approvers can be resolved; false disables. */
|
||||
enabled?: import("./types.approvals.js").NativeExecApprovalEnableMode;
|
||||
/** Discord user IDs to receive approval prompts. Optional: falls back to commands.ownerAllowFrom when possible. */
|
||||
approvers?: string[];
|
||||
/** Only forward approvals for these agent IDs. Omit = all agents. */
|
||||
|
||||
@@ -52,8 +52,8 @@ export type SlackStreamingMode = "off" | "partial" | "block" | "progress";
|
||||
export type SlackLegacyStreamMode = "replace" | "status_final" | "append";
|
||||
export type SlackExecApprovalTarget = "dm" | "channel" | "both";
|
||||
export type SlackExecApprovalConfig = {
|
||||
/** Enable Slack exec approvals for this account. Default: false. */
|
||||
enabled?: boolean;
|
||||
/** Enable mode for Slack exec approvals on this account. Default: auto when approvers can be resolved; false disables. */
|
||||
enabled?: import("./types.approvals.js").NativeExecApprovalEnableMode;
|
||||
/** Slack user IDs allowed to approve exec requests. Optional: falls back to commands.ownerAllowFrom when possible. */
|
||||
approvers?: Array<string | number>;
|
||||
/** Only forward approvals for these agent IDs. Omit = all agents. */
|
||||
|
||||
@@ -59,8 +59,8 @@ export type TelegramStreamingMode = "off" | "partial" | "block" | "progress";
|
||||
export type TelegramExecApprovalTarget = "dm" | "channel" | "both";
|
||||
|
||||
export type TelegramExecApprovalConfig = {
|
||||
/** Enable Telegram exec approvals for this account. Default: false. */
|
||||
enabled?: boolean;
|
||||
/** Enable mode for Telegram exec approvals on this account. Default: auto when approvers can be resolved; false disables. */
|
||||
enabled?: import("./types.approvals.js").NativeExecApprovalEnableMode;
|
||||
/** Telegram user IDs allowed to approve exec requests. Optional: falls back to numeric owner IDs inferred from allowFrom/defaultTo when possible. */
|
||||
approvers?: Array<string | number>;
|
||||
/** Only forward approvals for these agent IDs. Omit = all agents. */
|
||||
|
||||
@@ -54,12 +54,12 @@ describe("exec approval reply helpers", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("mentions Slack in the fallback approval-client guidance", () => {
|
||||
it("mentions native chat approval clients in the fallback guidance", () => {
|
||||
expect(
|
||||
buildExecApprovalUnavailableReplyPayload({
|
||||
reason: "no-approval-route",
|
||||
}).text,
|
||||
).toContain("Discord, Slack, or Telegram exec approvals");
|
||||
).toContain("native chat approval client such as Discord, Slack, or Telegram");
|
||||
});
|
||||
|
||||
it.each(invalidReplyMetadataCases)(
|
||||
|
||||
@@ -324,21 +324,21 @@ export function buildExecApprovalUnavailableReplyPayload(
|
||||
`Exec approval is required, but chat exec approvals are not enabled on ${params.channelLabel ?? "this platform"}.`,
|
||||
);
|
||||
lines.push(
|
||||
"Approve it from the Web UI or terminal UI, or enable Discord, Slack, or Telegram exec approvals. If those accounts already know your owner ID via allowFrom, OpenClaw can infer approvers automatically.",
|
||||
"Approve it from the Web UI or terminal UI, or enable a native chat approval client such as Discord, Slack, or Telegram. If those accounts already know your owner ID via allowFrom or owner config, OpenClaw can often infer approvers automatically.",
|
||||
);
|
||||
} else if (params.reason === "initiating-platform-unsupported") {
|
||||
lines.push(
|
||||
`Exec approval is required, but ${params.channelLabel ?? "this platform"} does not support chat exec approvals.`,
|
||||
);
|
||||
lines.push(
|
||||
"Approve it from the Web UI or terminal UI, or enable Discord, Slack, or Telegram exec approvals. If those accounts already know your owner ID via allowFrom, OpenClaw can infer approvers automatically.",
|
||||
"Approve it from the Web UI or terminal UI, or enable a native chat approval client such as Discord, Slack, or Telegram. If those accounts already know your owner ID via allowFrom or owner config, OpenClaw can often infer approvers automatically.",
|
||||
);
|
||||
} else {
|
||||
lines.push(
|
||||
"Exec approval is required, but no interactive approval client is currently available.",
|
||||
);
|
||||
lines.push(
|
||||
"Open the Web UI or terminal UI, or enable Discord, Slack, or Telegram exec approvals, then retry the command. If those accounts already know your owner ID via allowFrom, you can usually leave execApprovals.approvers unset.",
|
||||
"Open the Web UI or terminal UI, or enable a native chat approval client such as Discord, Slack, or Telegram, then retry the command. If those accounts already know your owner ID via allowFrom or owner config, you can usually leave execApprovals.approvers unset.",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@ export {
|
||||
export { createResolvedApproverActionAuthAdapter } from "./approval-auth-helpers.js";
|
||||
export {
|
||||
createChannelExecApprovalProfile,
|
||||
isChannelExecApprovalClientEnabledFromConfig,
|
||||
isChannelExecApprovalTargetRecipient,
|
||||
} from "./approval-client-helpers.js";
|
||||
export {
|
||||
|
||||
Reference in New Issue
Block a user