






















11import { beforeAll, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
22import { clearAgentHarnesses, registerAgentHarness } from "../../agents/harness/registry.js";
3+import type { ChannelMessagingAdapter } from "../../channels/plugins/types.core.js";
34import type { OpenClawConfig } from "../../config/config.js";
45import {
56clearApprovalNativeRouteStateForTest,
@@ -38,6 +39,9 @@ import { resolveRoutedDeliveryThreadId } from "./routed-delivery-thread.js";
3839import { buildTestCtx } from "./test-ctx.js";
39404041type AbortResult = { handled: boolean; aborted: boolean; stoppedSubagents?: number };
42+type ResolveInboundConversationParams = Parameters<
43+NonNullable<ChannelMessagingAdapter["resolveInboundConversation"]>
44+>[0];
41454246const mocks = vi.hoisted(() => ({
4347routeReply: vi.fn(async (_params: unknown) => ({ ok: true, messageId: "mock" })),
@@ -5210,6 +5214,126 @@ describe("dispatchReplyFromConfig", () => {
52105214expect(replyResolver).not.toHaveBeenCalled();
52115215});
521252165217+it("routes Discord thread plugin-owned bindings by raw thread id", async () => {
5218+setNoAbort();
5219+setActivePluginRegistry(
5220+createTestRegistry([
5221+{
5222+pluginId: "discord",
5223+source: "test",
5224+plugin: {
5225+ ...createChannelTestPluginBase({ id: "discord" }),
5226+messaging: {
5227+resolveInboundConversation: ({
5228+ to,
5229+ conversationId,
5230+ threadId,
5231+ threadParentId,
5232+}: ResolveInboundConversationParams) =>
5233+threadId
5234+ ? {
5235+conversationId: String(threadId),
5236+ ...(threadParentId
5237+ ? { parentConversationId: `channel:${threadParentId}` }
5238+ : {}),
5239+}
5240+ : { conversationId: to ?? conversationId },
5241+},
5242+},
5243+},
5244+]),
5245+);
5246+hookMocks.runner.hasHooks.mockImplementation(
5247+((hookName?: string) =>
5248+hookName === "inbound_claim" || hookName === "message_received") as () => boolean,
5249+);
5250+hookMocks.registry.plugins = [{ id: "openclaw-codex-app-server", status: "loaded" }];
5251+hookMocks.runner.runInboundClaimForPluginOutcome.mockResolvedValue({
5252+status: "handled",
5253+result: { handled: true },
5254+});
5255+sessionBindingMocks.resolveByConversation.mockImplementation(
5256+(ref: {
5257+channel: string;
5258+accountId: string;
5259+conversationId: string;
5260+parentConversationId?: string;
5261+}) =>
5262+ref.channel === "discord" &&
5263+ref.accountId === "default" &&
5264+ref.conversationId === "1510164477642014740"
5265+ ? ({
5266+bindingId: "binding-discord-thread",
5267+targetSessionKey: "plugin-binding:codex:thread",
5268+targetKind: "session",
5269+conversation: {
5270+channel: "discord",
5271+accountId: "default",
5272+conversationId: "1510164477642014740",
5273+parentConversationId: "channel:1510164477642014999",
5274+},
5275+status: "active",
5276+boundAt: 1710000000000,
5277+metadata: {
5278+pluginBindingOwner: "plugin",
5279+pluginId: "openclaw-codex-app-server",
5280+pluginRoot: "/plugins/openclaw-codex-app-server",
5281+},
5282+} satisfies SessionBindingRecord)
5283+ : null,
5284+);
5285+const dispatcher = createDispatcher();
5286+const ctx = buildTestCtx({
5287+Provider: "discord",
5288+Surface: "discord",
5289+OriginatingChannel: "discord",
5290+OriginatingTo: "channel:1510164477642014740",
5291+To: "channel:1510164477642014740",
5292+AccountId: "default",
5293+SenderId: "user-9",
5294+CommandAuthorized: true,
5295+WasMentioned: false,
5296+RawBody: "continue",
5297+Body: "continue",
5298+MessageSid: "msg-claim-discord-thread",
5299+MessageThreadId: "1510164477642014740",
5300+ThreadParentId: "1510164477642014999",
5301+SessionKey: "agent:main:discord:channel:1510164477642014740",
5302+});
5303+const replyResolver = vi.fn(async () => ({ text: "should not run" }) satisfies ReplyPayload);
5304+5305+const result = await dispatchReplyFromConfig({
5306+ ctx,
5307+cfg: emptyConfig,
5308+ dispatcher,
5309+ replyResolver,
5310+});
5311+5312+expect(result).toEqual({ queuedFinal: false, counts: { tool: 0, block: 0, final: 0 } });
5313+expect(sessionBindingMocks.resolveByConversation).toHaveBeenCalledWith(
5314+expect.objectContaining({
5315+channel: "discord",
5316+accountId: "default",
5317+conversationId: "1510164477642014740",
5318+parentConversationId: "channel:1510164477642014999",
5319+}),
5320+);
5321+expect(hookMocks.runner.runInboundClaimForPluginOutcome).toHaveBeenCalledWith(
5322+"openclaw-codex-app-server",
5323+expect.objectContaining({
5324+channel: "discord",
5325+conversationId: "1510164477642014740",
5326+parentConversationId: "channel:1510164477642014999",
5327+}),
5328+expect.objectContaining({
5329+conversationId: "1510164477642014740",
5330+parentConversationId: "channel:1510164477642014999",
5331+pluginBinding: expect.objectContaining({ bindingId: "binding-discord-thread" }),
5332+}),
5333+);
5334+expect(replyResolver).not.toHaveBeenCalled();
5335+});
5336+52135337it("does not run plugin-owned binding delivery when the caller already aborted", async () => {
52145338setNoAbort();
52155339hookMocks.runner.hasHooks.mockImplementation(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。