























@@ -803,20 +803,21 @@ describe("resolveIMessageInboundDecision command auth", () => {
803803storeAllowFrom: string[];
804804dmPolicy?: "open" | "pairing" | "allowlist" | "disabled";
805805allowFrom?: string[];
806+text?: string;
806807}) =>
807808resolveIMessageInboundDecision({
808809 cfg,
809810accountId: "default",
810811message: {
811812id: params.messageId,
812813sender: "+15555550123",
813-text: "/status",
814+text: params.text ?? "/status",
814815is_from_me: false,
815816is_group: false,
816817},
817818opts: undefined,
818-messageText: "/status",
819-bodyText: "/status",
819+messageText: params.text ?? "/status",
820+bodyText: params.text ?? "/status",
820821allowFrom: params.allowFrom ?? [],
821822groupAllowFrom: [],
822823groupPolicy: "open",
@@ -849,6 +850,84 @@ describe("resolveIMessageInboundDecision command auth", () => {
849850return;
850851}
851852expect(decision.commandAuthorized).toBe(true);
853+expect(decision.hasControlCommand).toBe(true);
854+});
855+856+it("marks authorized iMessage control commands as text command turns", async () => {
857+const decision = await resolveDmCommandDecision({
858+messageId: 102,
859+dmPolicy: "pairing",
860+storeAllowFrom: ["+15555550123"],
861+text: "/new",
862+});
863+864+expect(decision.kind).toBe("dispatch");
865+if (decision.kind !== "dispatch") {
866+return;
867+}
868+869+const { ctxPayload } = buildIMessageInboundContext({
870+ cfg,
871+ decision,
872+message: {
873+id: 102,
874+guid: "p:0/GUID-command",
875+sender: "+15555550123",
876+text: "/new",
877+is_from_me: false,
878+is_group: false,
879+},
880+historyLimit: 0,
881+groupHistories: new Map(),
882+});
883+884+expect(ctxPayload.CommandAuthorized).toBe(true);
885+expect(ctxPayload.CommandSource).toBe("text");
886+expect(ctxPayload.CommandTurn).toMatchObject({
887+kind: "text-slash",
888+source: "text",
889+authorized: true,
890+commandName: "new",
891+});
892+});
893+894+it("does not mark authorized non-command iMessage DMs as text command turns", async () => {
895+const decision = await resolveDmCommandDecision({
896+messageId: 103,
897+dmPolicy: "pairing",
898+storeAllowFrom: ["+15555550123"],
899+text: "hello there",
900+});
901+902+expect(decision.kind).toBe("dispatch");
903+if (decision.kind !== "dispatch") {
904+return;
905+}
906+expect(decision.commandAuthorized).toBe(true);
907+expect(decision.hasControlCommand).toBe(false);
908+909+const { ctxPayload } = buildIMessageInboundContext({
910+ cfg,
911+ decision,
912+message: {
913+id: 103,
914+guid: "p:0/GUID-non-command",
915+sender: "+15555550123",
916+text: "hello there",
917+is_from_me: false,
918+is_group: false,
919+},
920+historyLimit: 0,
921+groupHistories: new Map(),
922+});
923+924+expect(ctxPayload.CommandAuthorized).toBe(true);
925+expect(ctxPayload.CommandSource).toBeUndefined();
926+expect(ctxPayload.CommandTurn).toMatchObject({
927+kind: "normal",
928+source: "message",
929+commandName: undefined,
930+});
852931});
853932});
854933@@ -892,6 +971,7 @@ describe("buildIMessageInboundContext MessageSid handling (rowid-leak regression
892971replyContext: undefined,
893972isCommand: false,
894973commandAuthorized: false,
974+hasControlCommand: false,
895975};
896976return {
897977cfg: {} as OpenClawConfig,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。