























@@ -198,6 +198,27 @@ vi.mock("openclaw/plugin-sdk/reply-runtime", () => ({
198198199199vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({
200200recordInboundSession: (...args: unknown[]) => recordInboundSession(...args),
201+resolvePinnedMainDmOwnerFromAllowlist: (params: {
202+dmScope?: string | null;
203+allowFrom?: Array<string | number> | null;
204+normalizeEntry: (entry: string) => string | undefined;
205+}) => {
206+if ((params.dmScope ?? "main") !== "main") {
207+return null;
208+}
209+const allowFrom = Array.isArray(params.allowFrom) ? params.allowFrom : [];
210+if (allowFrom.some((entry) => String(entry).trim() === "*")) {
211+return null;
212+}
213+const owners = Array.from(
214+new Set(
215+allowFrom
216+.map((entry) => params.normalizeEntry(String(entry)))
217+.filter((entry): entry is string => Boolean(entry)),
218+),
219+);
220+return owners.length === 1 ? owners[0] : null;
221+},
201222registerSessionBindingAdapter: vi.fn(),
202223unregisterSessionBindingAdapter: vi.fn(),
203224resolveThreadBindingConversationIdFromBindingId: (bindingId: string) =>
@@ -306,7 +327,13 @@ beforeEach(() => {
306327});
307328308329function getLastRouteUpdate():
309-| { sessionKey?: string; channel?: string; to?: string; accountId?: string }
330+| {
331+sessionKey?: string;
332+channel?: string;
333+to?: string;
334+accountId?: string;
335+mainDmOwnerPin?: { ownerRecipient?: string; senderRecipient?: string };
336+}
310337| undefined {
311338const callArgs = recordInboundSession.mock.calls.at(-1) as unknown[] | undefined;
312339const params = callArgs?.[0] as
@@ -316,6 +343,7 @@ function getLastRouteUpdate():
316343channel?: string;
317344to?: string;
318345accountId?: string;
346+mainDmOwnerPin?: { ownerRecipient?: string; senderRecipient?: string };
319347};
320348}
321349| undefined;
@@ -782,6 +810,48 @@ describe("processDiscordMessage session routing", () => {
782810});
783811});
784812813+it("pins Discord text DM main-route updates to the single configured DM owner", async () => {
814+const ctx = await createBaseContext({
815+ ...createDirectMessageContextOverrides(),
816+cfg: {
817+messages: { ackReaction: "👀" },
818+session: {
819+store: "/tmp/openclaw-discord-process-test-sessions.json",
820+dmScope: "main",
821+},
822+},
823+channelConfig: { users: ["user:111"] },
824+baseSessionKey: "agent:main:main",
825+author: {
826+id: "222",
827+username: "bob",
828+discriminator: "0",
829+globalName: "Bob",
830+},
831+sender: { id: "222", label: "bob" },
832+route: {
833+agentId: "main",
834+channel: "discord",
835+accountId: "default",
836+sessionKey: "agent:main:main",
837+mainSessionKey: "agent:main:main",
838+},
839+});
840+841+await runProcessDiscordMessage(ctx);
842+843+expect(getLastRouteUpdate()).toMatchObject({
844+sessionKey: "agent:main:main",
845+channel: "discord",
846+to: "user:222",
847+accountId: "default",
848+mainDmOwnerPin: {
849+ownerRecipient: "111",
850+senderRecipient: "222",
851+},
852+});
853+});
854+785855it("stores group lastRoute with channel target", async () => {
786856const ctx = await createBaseContext({
787857baseSessionKey: "agent:main:discord:channel:c1",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。