






























@@ -5427,7 +5427,7 @@ describe("dispatchReplyFromConfig", () => {
54275427expect(replyResolver).not.toHaveBeenCalled();
54285428});
542954295430-it("keeps unauthorized plugin-owned binding slash text routed to the bound plugin", async () => {
5430+it("keeps unauthorized plugin-owned binding slash replies suppressed while routed to the bound plugin", async () => {
54315431setNoAbort();
54325432hookMocks.runner.hasHooks.mockImplementation(
54335433((hookName?: string) =>
@@ -5436,7 +5436,7 @@ describe("dispatchReplyFromConfig", () => {
54365436hookMocks.registry.plugins = [{ id: "openclaw-codex-app-server", status: "loaded" }];
54375437hookMocks.runner.runInboundClaimForPluginOutcome.mockResolvedValue({
54385438status: "handled",
5439-result: { handled: true },
5439+result: { handled: true, reply: { text: "do not leak slash reply" } },
54405440});
54415441sessionBindingMocks.resolveByConversation.mockReturnValue({
54425442bindingId: "binding-command-escape-denied",
@@ -5467,6 +5467,7 @@ describe("dispatchReplyFromConfig", () => {
54675467AccountId: "default",
54685468SenderId: "user-9",
54695469SenderUsername: "ada",
5470+ChatType: "channel",
54705471CommandSource: "text",
54715472CommandAuthorized: false,
54725473WasMentioned: false,
@@ -5480,7 +5481,11 @@ describe("dispatchReplyFromConfig", () => {
5480548154815482const result = await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
548254835483-expect(result).toEqual({ queuedFinal: false, counts: { tool: 0, block: 0, final: 0 } });
5484+expect(result).toEqual({
5485+queuedFinal: false,
5486+counts: { tool: 0, block: 0, final: 0 },
5487+sourceReplyDeliveryMode: "message_tool_only",
5488+});
54845489expect(sessionBindingMocks.touch).toHaveBeenCalledWith("binding-command-escape-denied");
54855490expect(hookMocks.runner.runInboundClaimForPluginOutcome).toHaveBeenCalledWith(
54865491"openclaw-codex-app-server",
@@ -5491,6 +5496,7 @@ describe("dispatchReplyFromConfig", () => {
54915496);
54925497expect(hookMocks.runner.runInboundClaim).not.toHaveBeenCalled();
54935498expect(replyResolver).not.toHaveBeenCalled();
5499+expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
54945500});
5495550154965502it("delivers plugin-owned binding replies returned by the owning inbound claim hook", async () => {
@@ -7049,7 +7055,100 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () =>
70497055expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
70507056});
705170577052-it("routes plugin-owned bindings under message-tool-only source delivery", async () => {
7058+it.each(
7059+[
7060+{
7061+name: "handled without a plugin reply",
7062+bindingId: "binding-message-tool-only",
7063+conversation: {
7064+channel: "telegram",
7065+accountId: "default",
7066+conversationId: "-1001234567890:topic:11",
7067+parentConversationId: "-1001234567890",
7068+},
7069+ctx: {
7070+Provider: "telegram",
7071+Surface: "telegram",
7072+OriginatingChannel: "telegram",
7073+OriginatingTo: "telegram:-1001234567890",
7074+To: "telegram:-1001234567890",
7075+AccountId: "default",
7076+MessageThreadId: 11,
7077+ChatType: "group",
7078+GroupSubject: "Dev",
7079+Body: "observed message",
7080+},
7081+cfg: emptyConfig,
7082+replyOptions: {
7083+sourceReplyDeliveryMode: "message_tool_only" as const,
7084+},
7085+expectedClaim: { channel: "telegram", threadId: 11 },
7086+},
7087+{
7088+name: "handled with a plugin reply",
7089+bindingId: "binding-message-tool-reply",
7090+conversation: {
7091+channel: "discord",
7092+accountId: "default",
7093+conversationId: "channel:reply-test",
7094+},
7095+ctx: {
7096+Provider: "discord",
7097+Surface: "discord",
7098+OriginatingChannel: "discord",
7099+OriginatingTo: "discord:channel:reply-test",
7100+To: "discord:channel:reply-test",
7101+AccountId: "default",
7102+ChatType: "channel",
7103+Body: "observed message",
7104+SessionKey: "agent:main:discord:channel:reply-test",
7105+},
7106+cfg: {
7107+messages: {
7108+groupChat: { visibleReplies: "message_tool" },
7109+},
7110+} as OpenClawConfig,
7111+expectedClaim: { channel: "discord" },
7112+pluginReply: { text: "Codex native reply" },
7113+expectPluginReplyDelivered: true,
7114+},
7115+{
7116+name: "suppresses ambient room_event plugin reply",
7117+bindingId: "binding-message-tool-room-event",
7118+conversation: {
7119+channel: "discord",
7120+accountId: "default",
7121+conversationId: "channel:room-event-test",
7122+},
7123+ctx: {
7124+Provider: "discord",
7125+Surface: "discord",
7126+OriginatingChannel: "discord",
7127+OriginatingTo: "discord:channel:room-event-test",
7128+To: "discord:channel:room-event-test",
7129+AccountId: "default",
7130+ChatType: "group",
7131+InboundEventKind: "room_event",
7132+Body: "observed message",
7133+SessionKey: "agent:main:discord:channel:room-event-test",
7134+},
7135+cfg: emptyConfig,
7136+expectedClaim: { channel: "discord" },
7137+pluginReply: { text: "Codex ambient room event reply" },
7138+expectPluginReplyDelivered: false,
7139+},
7140+] satisfies Array<{
7141+name: string;
7142+bindingId: string;
7143+conversation: SessionBindingRecord["conversation"];
7144+ctx: Partial<MsgContext>;
7145+cfg: OpenClawConfig;
7146+replyOptions?: { sourceReplyDeliveryMode: "message_tool_only" };
7147+expectedClaim: Record<string, unknown>;
7148+pluginReply?: ReplyPayload;
7149+expectPluginReplyDelivered?: boolean;
7150+}>,
7151+)("routes plugin-owned bindings under message-tool-only source delivery: $name", async (params) => {
70537152setNoAbort();
70547153hookMocks.runner.hasHooks.mockImplementation(
70557154((hookName?: string) =>
@@ -7058,18 +7157,15 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () =>
70587157hookMocks.registry.plugins = [{ id: "openclaw-codex-app-server", status: "loaded" }];
70597158hookMocks.runner.runInboundClaimForPluginOutcome.mockResolvedValue({
70607159status: "handled",
7061-result: { handled: true },
7160+result: params.pluginReply
7161+ ? { handled: true, reply: params.pluginReply }
7162+ : { handled: true },
70627163});
70637164sessionBindingMocks.resolveByConversation.mockReturnValue({
7064-bindingId: "binding-message-tool-only",
7165+bindingId: params.bindingId,
70657166targetSessionKey: "plugin-binding:codex:abc123",
70667167targetKind: "session",
7067-conversation: {
7068-channel: "telegram",
7069-accountId: "default",
7070-conversationId: "-1001234567890:topic:11",
7071-parentConversationId: "-1001234567890",
7072-},
7168+conversation: params.conversation,
70737169status: "active",
70747170boundAt: 1710000000000,
70757171metadata: {
@@ -7085,49 +7181,37 @@ describe("sendPolicy deny — suppress delivery, not processing (#53328)", () =>
70857181};
70867182const dispatcher = createDispatcher();
70877183const replyResolver = vi.fn(async () => ({ text: "agent reply" }) satisfies ReplyPayload);
7088-const ctx = buildTestCtx({
7089-Provider: "telegram",
7090-Surface: "telegram",
7091-OriginatingChannel: "telegram",
7092-OriginatingTo: "telegram:-1001234567890",
7093-To: "telegram:-1001234567890",
7094-AccountId: "default",
7095-MessageThreadId: 11,
7096-ChatType: "group",
7097-GroupSubject: "Dev",
7098-Body: "observed message",
7099-});
7100718471017185const result = await dispatchReplyFromConfig({
7102- ctx,
7103-cfg: emptyConfig,
7186+ctx: buildTestCtx(params.ctx),
7187+cfg: params.cfg,
71047188 dispatcher,
71057189 replyResolver,
7106-replyOptions: {
7107-sourceReplyDeliveryMode: "message_tool_only",
7108-},
7190+replyOptions: params.replyOptions,
71097191});
7110719271117193expect(result).toEqual({
71127194queuedFinal: false,
71137195counts: { tool: 0, block: 0, final: 0 },
71147196sourceReplyDeliveryMode: "message_tool_only",
71157197});
7116-expect(sessionBindingMocks.touch).toHaveBeenCalledWith("binding-message-tool-only");
7117-const claimCall = firstMockCall(
7118-hookMocks.runner.runInboundClaimForPluginOutcome,
7119-"plugin inbound claim",
7198+expect(sessionBindingMocks.touch).toHaveBeenCalledWith(params.bindingId);
7199+expect(hookMocks.runner.runInboundClaimForPluginOutcome).toHaveBeenCalledWith(
7200+"openclaw-codex-app-server",
7201+expect.objectContaining({
7202+content: "observed message",
7203+ ...params.expectedClaim,
7204+}),
7205+expect.objectContaining({
7206+pluginBinding: expect.objectContaining({ bindingId: params.bindingId }),
7207+}),
71207208);
7121-expect(claimCall[0]).toBe("openclaw-codex-app-server");
7122-expect(claimCall[1]).toMatchObject({
7123-channel: "telegram",
7124-content: "observed message",
7125-threadId: 11,
7126-});
7127-const claimContext = claimCall[2] as { pluginBinding?: { bindingId?: string } };
7128-expect(claimContext.pluginBinding).toMatchObject({ bindingId: "binding-message-tool-only" });
71297209expect(replyResolver).not.toHaveBeenCalled();
7130-expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
7210+if (params.expectPluginReplyDelivered) {
7211+expect(dispatcher.sendFinalReply).toHaveBeenCalledWith(params.pluginReply);
7212+} else {
7213+expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
7214+}
71317215});
7132721671337217it("keeps unmentioned plugin-bound fallback from ordinary group agent dispatch", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。