|
1 | 1 | import { beforeAll, beforeEach, describe, expect, it, vi, type Mock } from "vitest"; |
2 | 2 | import { clearAgentHarnesses, registerAgentHarness } from "../../agents/harness/registry.js"; |
| 3 | +import type { ChannelMessagingAdapter } from "../../channels/plugins/types.core.js"; |
3 | 4 | import type { OpenClawConfig } from "../../config/config.js"; |
4 | 5 | import { |
5 | 6 | clearApprovalNativeRouteStateForTest, |
@@ -38,6 +39,9 @@ import { resolveRoutedDeliveryThreadId } from "./routed-delivery-thread.js"; |
38 | 39 | import { buildTestCtx } from "./test-ctx.js"; |
39 | 40 |
|
40 | 41 | type AbortResult = { handled: boolean; aborted: boolean; stoppedSubagents?: number }; |
| 42 | +type ResolveInboundConversationParams = Parameters< |
| 43 | + NonNullable<ChannelMessagingAdapter["resolveInboundConversation"]> |
| 44 | +>[0]; |
41 | 45 |
|
42 | 46 | const mocks = vi.hoisted(() => ({ |
43 | 47 | routeReply: vi.fn(async (_params: unknown) => ({ ok: true, messageId: "mock" })), |
@@ -5210,6 +5214,126 @@ describe("dispatchReplyFromConfig", () => { |
5210 | 5214 | expect(replyResolver).not.toHaveBeenCalled(); |
5211 | 5215 | }); |
5212 | 5216 |
|
| 5217 | + 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 | + |
5213 | 5337 | it("does not run plugin-owned binding delivery when the caller already aborted", async () => { |
5214 | 5338 | setNoAbort(); |
5215 | 5339 | hookMocks.runner.hasHooks.mockImplementation( |
|
0 commit comments