






















@@ -302,6 +302,7 @@ vi.mock("../../plugins/conversation-binding.js", () => ({
302302pluginId?: string;
303303pluginName?: string;
304304pluginRoot?: string;
305+data?: Record<string, unknown>;
305306};
306307return {
307308bindingId: record.bindingId,
@@ -312,6 +313,7 @@ vi.mock("../../plugins/conversation-binding.js", () => ({
312313accountId: record.conversation.accountId,
313314conversationId: record.conversation.conversationId,
314315parentConversationId: record.conversation.parentConversationId,
316+data: metadata.data,
315317};
316318},
317319}));
@@ -2545,6 +2547,12 @@ describe("dispatchReplyFromConfig", () => {
25452547pluginBindingOwner: "plugin",
25462548pluginId: "openclaw-codex-app-server",
25472549pluginRoot: "/Users/huntharo/github/openclaw-app-server",
2550+data: {
2551+kind: "codex-app-server-session",
2552+version: 1,
2553+sessionFile: "/tmp/session.jsonl",
2554+workspaceDir: "/workspace/openclaw",
2555+},
25482556},
25492557} satisfies SessionBindingRecord);
25502558const cfg = emptyConfig;
@@ -2584,12 +2592,74 @@ describe("dispatchReplyFromConfig", () => {
25842592channelId: "discord",
25852593accountId: "default",
25862594conversationId: "channel:1481858418548412579",
2595+pluginBinding: expect.objectContaining({
2596+data: expect.objectContaining({
2597+kind: "codex-app-server-session",
2598+sessionFile: "/tmp/session.jsonl",
2599+}),
2600+}),
25872601}),
25882602);
25892603expect(hookMocks.runner.runInboundClaim).not.toHaveBeenCalled();
25902604expect(replyResolver).not.toHaveBeenCalled();
25912605});
259226062607+it("delivers plugin-owned binding replies returned by the owning inbound claim hook", async () => {
2608+setNoAbort();
2609+hookMocks.runner.hasHooks.mockImplementation(
2610+((hookName?: string) =>
2611+hookName === "inbound_claim" || hookName === "message_received") as () => boolean,
2612+);
2613+hookMocks.registry.plugins = [{ id: "codex", status: "loaded" }];
2614+hookMocks.runner.runInboundClaimForPluginOutcome.mockResolvedValue({
2615+status: "handled",
2616+result: { handled: true, reply: { text: "Codex native reply" } },
2617+});
2618+sessionBindingMocks.resolveByConversation.mockReturnValue({
2619+bindingId: "binding-reply-1",
2620+targetSessionKey: "plugin-binding:codex:reply123",
2621+targetKind: "session",
2622+conversation: {
2623+channel: "discord",
2624+accountId: "default",
2625+conversationId: "channel:1481858418548412579",
2626+},
2627+status: "active",
2628+boundAt: 1710000000000,
2629+metadata: {
2630+pluginBindingOwner: "plugin",
2631+pluginId: "codex",
2632+pluginRoot: "/plugins/codex",
2633+},
2634+} satisfies SessionBindingRecord);
2635+const cfg = emptyConfig;
2636+const dispatcher = createDispatcher();
2637+const ctx = buildTestCtx({
2638+Provider: "discord",
2639+Surface: "discord",
2640+OriginatingChannel: "discord",
2641+OriginatingTo: "discord:channel:1481858418548412579",
2642+To: "discord:channel:1481858418548412579",
2643+AccountId: "default",
2644+SenderId: "user-9",
2645+SenderUsername: "ada",
2646+CommandAuthorized: true,
2647+WasMentioned: false,
2648+CommandBody: "who are you",
2649+RawBody: "who are you",
2650+Body: "who are you",
2651+MessageSid: "msg-claim-plugin-reply",
2652+SessionKey: "agent:main:discord:channel:1481858418548412579",
2653+});
2654+const replyResolver = vi.fn(async () => ({ text: "should not run" }) satisfies ReplyPayload);
2655+2656+const result = await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
2657+2658+expect(result).toEqual({ queuedFinal: false, counts: { tool: 0, block: 0, final: 0 } });
2659+expect(dispatcher.sendFinalReply).toHaveBeenCalledWith({ text: "Codex native reply" });
2660+expect(replyResolver).not.toHaveBeenCalled();
2661+});
2662+25932663it("routes plugin-owned Discord DM bindings to the owning plugin before generic inbound claim broadcast", async () => {
25942664setNoAbort();
25952665hookMocks.runner.hasHooks.mockImplementation(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。