



























@@ -4,7 +4,11 @@ import type {
44} from "openclaw/plugin-sdk/approval-runtime";
55import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
66import { describe, expect, it } from "vitest";
7-import { imessageApprovalCapability, imessageNativeApprovalAdapter } from "./approval-native.js";
7+import {
8+imessageApprovalCapability,
9+imessageNativeApprovalAdapter,
10+shouldSuppressLocalIMessageExecApprovalPrompt,
11+} from "./approval-native.js";
812913type IMessageConfig = NonNullable<NonNullable<OpenClawConfig["channels"]>["imessage"]>;
1014@@ -79,6 +83,27 @@ function nativeShouldHandle(params: {
7983});
8084}
818586+function buildLocalApprovalPayload(
87+params: {
88+approvalKind?: "exec" | "plugin";
89+agentId?: string | null;
90+sessionKey?: string | null;
91+} = {},
92+) {
93+return {
94+text: "Approval required.",
95+channelData: {
96+execApproval: {
97+approvalId: params.approvalKind === "plugin" ? "plugin:approval-1" : "exec-1",
98+approvalSlug: params.approvalKind === "plugin" ? "plugin:approval-1" : "exec-1",
99+approvalKind: params.approvalKind ?? "exec",
100+agentId: params.agentId,
101+sessionKey: params.sessionKey,
102+},
103+},
104+};
105+}
106+82107describe("imessage approval capability", () => {
83108it("disables native approvals when no top-level approvals config is set", () => {
84109const cfg = buildConfig();
@@ -570,3 +595,232 @@ describe("imessage approval capability", () => {
570595});
571596});
572597});
598+599+describe("shouldSuppressLocalIMessageExecApprovalPrompt", () => {
600+const activeExecHint = {
601+kind: "approval-pending",
602+approvalKind: "exec",
603+nativeRouteActive: true,
604+} as const;
605+606+it("suppresses eligible session-mode exec approval prompts", () => {
607+const cfg = buildConfig({
608+imessage: { allowFrom: ["+15551230000"] },
609+approvals: {
610+exec: {
611+enabled: true,
612+agentFilter: ["main"],
613+},
614+},
615+});
616+617+expect(
618+shouldSuppressLocalIMessageExecApprovalPrompt({
619+ cfg,
620+accountId: "default",
621+payload: buildLocalApprovalPayload({
622+agentId: null,
623+sessionKey: "agent:main:imessage:+15551230000",
624+}),
625+hint: activeExecHint,
626+}),
627+).toBe(true);
628+});
629+630+it("keeps local prompts for disabled, target-only, inactive, or non-exec cases", () => {
631+const enabledConfig = buildConfig({
632+imessage: { allowFrom: ["+15551230000"] },
633+approvals: { exec: { enabled: true } },
634+});
635+const payload = buildLocalApprovalPayload({
636+agentId: "main",
637+sessionKey: "agent:main:imessage:+15551230000",
638+});
639+640+expect(
641+shouldSuppressLocalIMessageExecApprovalPrompt({
642+cfg: buildConfig(),
643+ payload,
644+hint: activeExecHint,
645+}),
646+).toBe(false);
647+expect(
648+shouldSuppressLocalIMessageExecApprovalPrompt({
649+cfg: buildConfig({
650+imessage: { allowFrom: ["+15551230000"] },
651+approvals: { exec: { enabled: false } },
652+}),
653+ payload,
654+hint: activeExecHint,
655+}),
656+).toBe(false);
657+expect(
658+shouldSuppressLocalIMessageExecApprovalPrompt({
659+cfg: buildConfig({
660+imessage: { allowFrom: ["+15551230000"] },
661+approvals: {
662+exec: {
663+enabled: true,
664+mode: "targets",
665+targets: [{ channel: "imessage", to: "+15551230000" }],
666+},
667+},
668+}),
669+ payload,
670+hint: activeExecHint,
671+}),
672+).toBe(false);
673+expect(
674+shouldSuppressLocalIMessageExecApprovalPrompt({
675+cfg: enabledConfig,
676+ payload,
677+hint: { ...activeExecHint, nativeRouteActive: false },
678+}),
679+).toBe(false);
680+expect(
681+shouldSuppressLocalIMessageExecApprovalPrompt({
682+cfg: enabledConfig,
683+payload: buildLocalApprovalPayload({ approvalKind: "plugin" }),
684+hint: activeExecHint,
685+}),
686+).toBe(false);
687+expect(
688+shouldSuppressLocalIMessageExecApprovalPrompt({
689+cfg: enabledConfig,
690+payload: { text: "Approval required." },
691+hint: activeExecHint,
692+}),
693+).toBe(false);
694+});
695+696+it("suppresses direct same-chat iMessage prompts without explicit approvers", () => {
697+const cfg = buildConfig({
698+approvals: { exec: { enabled: true } },
699+});
700+701+expect(
702+shouldSuppressLocalIMessageExecApprovalPrompt({
703+ cfg,
704+payload: buildLocalApprovalPayload({
705+agentId: "main",
706+sessionKey: "agent:main:imessage:+15551230000",
707+}),
708+hint: activeExecHint,
709+}),
710+).toBe(true);
711+expect(
712+shouldSuppressLocalIMessageExecApprovalPrompt({
713+ cfg,
714+accountId: "default",
715+payload: buildLocalApprovalPayload({
716+agentId: "main",
717+sessionKey: "agent:main:imessage:direct:+15551230000",
718+}),
719+hint: activeExecHint,
720+}),
721+).toBe(true);
722+expect(
723+shouldSuppressLocalIMessageExecApprovalPrompt({
724+ cfg,
725+accountId: "default",
726+payload: buildLocalApprovalPayload({
727+agentId: "main",
728+sessionKey: "agent:main:imessage:default:direct:+15551230000",
729+}),
730+hint: activeExecHint,
731+}),
732+).toBe(true);
733+});
734+735+it("keeps no-approver local prompts for ambiguous or group iMessage sessions", () => {
736+const cfg = buildConfig({
737+approvals: { exec: { enabled: true } },
738+});
739+740+expect(
741+shouldSuppressLocalIMessageExecApprovalPrompt({
742+ cfg,
743+payload: buildLocalApprovalPayload({
744+agentId: "main",
745+sessionKey: "agent:main:imessage:group:test-group",
746+}),
747+hint: activeExecHint,
748+}),
749+).toBe(false);
750+expect(
751+shouldSuppressLocalIMessageExecApprovalPrompt({
752+ cfg,
753+payload: buildLocalApprovalPayload({
754+agentId: "main",
755+sessionKey: "agent:main:imessage:chat_guid:iMessage;+;chat42",
756+}),
757+hint: activeExecHint,
758+}),
759+).toBe(false);
760+expect(
761+shouldSuppressLocalIMessageExecApprovalPrompt({
762+ cfg,
763+payload: buildLocalApprovalPayload({
764+agentId: "main",
765+sessionKey: "agent:main:slack:C123",
766+}),
767+hint: activeExecHint,
768+}),
769+).toBe(false);
770+expect(
771+shouldSuppressLocalIMessageExecApprovalPrompt({
772+ cfg,
773+accountId: "work",
774+payload: buildLocalApprovalPayload({
775+agentId: "main",
776+sessionKey: "agent:main:imessage:default:direct:+15551230000",
777+}),
778+hint: activeExecHint,
779+}),
780+).toBe(false);
781+});
782+783+it("applies top-level approval filters with agent fallback from session key", () => {
784+const cfg = buildConfig({
785+imessage: { allowFrom: ["+15551230000"] },
786+approvals: {
787+exec: {
788+enabled: true,
789+agentFilter: ["ops"],
790+sessionFilter: ["imessage"],
791+},
792+},
793+});
794+795+expect(
796+shouldSuppressLocalIMessageExecApprovalPrompt({
797+ cfg,
798+payload: buildLocalApprovalPayload({
799+agentId: null,
800+sessionKey: "agent:ops:imessage:+15551230000",
801+}),
802+hint: activeExecHint,
803+}),
804+).toBe(true);
805+expect(
806+shouldSuppressLocalIMessageExecApprovalPrompt({
807+ cfg,
808+payload: buildLocalApprovalPayload({
809+agentId: null,
810+sessionKey: "agent:main:imessage:+15551230000",
811+}),
812+hint: activeExecHint,
813+}),
814+).toBe(false);
815+expect(
816+shouldSuppressLocalIMessageExecApprovalPrompt({
817+ cfg,
818+payload: buildLocalApprovalPayload({
819+agentId: null,
820+sessionKey: "agent:ops:slack:C123",
821+}),
822+hint: activeExecHint,
823+}),
824+).toBe(false);
825+});
826+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。