





























@@ -3,12 +3,15 @@ import os from "node:os";
33import path from "node:path";
44import { expect } from "vitest";
55import { createChannelConversationBindingManager } from "../../../src/channels/plugins/conversation-bindings.js";
6+import type { ChannelPlugin } from "../../../src/channels/plugins/types.js";
67import type { OpenClawConfig } from "../../../src/config/config.js";
78import {
89getSessionBindingService,
910type SessionBindingCapabilities,
1011type SessionBindingRecord,
1112} from "../../../src/infra/outbound/session-binding-service.js";
13+import { setActivePluginRegistry } from "../../../src/plugins/runtime.js";
14+import { createTestRegistry } from "../../../src/test-utils/channel-plugins.js";
1215import {
1316sessionBindingContractChannelIds,
1417type SessionBindingContractChannelId,
@@ -23,6 +26,7 @@ type SessionBindingContractEntry = {
2326bindAndResolve: () => Promise<SessionBindingRecord>;
2427unbindAndVerify: (binding: SessionBindingRecord) => Promise<void>;
2528cleanup: () => Promise<void> | void;
29+beforeEach?: () => Promise<void> | void;
2630};
2731const contractApiPromises = new Map<string, Promise<Record<string, unknown>>>();
2832@@ -96,17 +100,8 @@ function resetMatrixSessionBindingStateDir() {
9610097101async function createContractMatrixThreadBindingManager() {
98102resetMatrixSessionBindingStateDir();
99-const { setMatrixRuntime, createMatrixThreadBindingManager } = await getContractApi<{
100-setMatrixRuntime: (runtime: unknown) => void;
101-createMatrixThreadBindingManager: (params: {
102-accountId: string;
103-auth: typeof matrixSessionBindingAuth;
104-client: unknown;
105-idleTimeoutMs: number;
106-maxAgeMs: number;
107-enableSweeper: boolean;
108-}) => Promise<unknown>;
109-}>("matrix");
103+const { setMatrixRuntime, createMatrixThreadBindingManager } =
104+await getContractApi<MatrixContractApi>("matrix");
110105setMatrixRuntime({
111106state: {
112107resolveStateDir: () => matrixSessionBindingStateDir,
@@ -126,11 +121,145 @@ const baseSessionBindingCfg = {
126121session: { mainKey: "main", scope: "per-sender" },
127122} satisfies OpenClawConfig;
128123124+type ChannelConversationBindingManagerFactory = NonNullable<
125+NonNullable<ChannelPlugin["conversationBindings"]>["createManager"]
126+>;
127+128+type BlueBubblesContractApi = {
129+blueBubblesConversationBindingTesting: {
130+resetBlueBubblesConversationBindingsForTests: () => void;
131+};
132+createBlueBubblesConversationBindingManager: ChannelConversationBindingManagerFactory;
133+};
134+135+type DiscordContractApi = {
136+createThreadBindingManager: (params: {
137+accountId: string;
138+cfg?: OpenClawConfig;
139+persist: boolean;
140+enableSweeper: boolean;
141+}) => unknown;
142+discordThreadBindingTesting: {
143+resetThreadBindingsForTests: () => void;
144+};
145+};
146+147+type FeishuContractApi = {
148+createFeishuThreadBindingManager: (params: {
149+accountId?: string;
150+cfg: OpenClawConfig;
151+}) => unknown;
152+feishuThreadBindingTesting: {
153+resetFeishuThreadBindingsForTests: () => void;
154+};
155+};
156+157+type IMessageContractApi = {
158+createIMessageConversationBindingManager: ChannelConversationBindingManagerFactory;
159+imessageConversationBindingTesting: {
160+resetIMessageConversationBindingsForTests: () => void;
161+};
162+};
163+164+type MatrixContractApi = {
165+createMatrixThreadBindingManager: (params: {
166+accountId: string;
167+auth: typeof matrixSessionBindingAuth;
168+client: unknown;
169+idleTimeoutMs: number;
170+maxAgeMs: number;
171+enableSweeper: boolean;
172+}) => Promise<unknown>;
173+resetMatrixThreadBindingsForTests: () => void;
174+setMatrixRuntime: (runtime: unknown) => void;
175+};
176+177+type TelegramContractApi = {
178+createTelegramThreadBindingManager: (params: {
179+accountId: string;
180+persist: boolean;
181+enableSweeper: boolean;
182+}) => unknown;
183+resetTelegramThreadBindingsForTests: () => Promise<void>;
184+};
185+186+function setRegistryBackedConversationBindingPlugin(params: {
187+id: SessionBindingContractChannelId;
188+createManager: ChannelConversationBindingManagerFactory;
189+}) {
190+const plugin = {
191+id: params.id,
192+meta: {
193+id: params.id,
194+label: params.id,
195+selectionLabel: params.id,
196+blurb: "session binding contract fixture",
197+},
198+capabilities: { chatTypes: ["direct"] },
199+config: {
200+listAccountIds: () => ["default"],
201+resolveAccount: () => ({}),
202+},
203+conversationBindings: {
204+supportsCurrentConversationBinding: true,
205+createManager: params.createManager,
206+},
207+} as unknown as ChannelPlugin;
208+setActivePluginRegistry(
209+createTestRegistry([
210+{
211+pluginId: params.id,
212+ plugin,
213+source: "test",
214+},
215+]),
216+);
217+}
218+219+async function prepareBlueBubblesSessionBindingContract() {
220+const api = await getContractApi<BlueBubblesContractApi>("bluebubbles");
221+api.blueBubblesConversationBindingTesting.resetBlueBubblesConversationBindingsForTests();
222+setRegistryBackedConversationBindingPlugin({
223+id: "bluebubbles",
224+createManager: api.createBlueBubblesConversationBindingManager,
225+});
226+}
227+228+async function prepareDiscordSessionBindingContract() {
229+const api = await getContractApi<DiscordContractApi>("discord");
230+api.discordThreadBindingTesting.resetThreadBindingsForTests();
231+}
232+233+async function prepareFeishuSessionBindingContract() {
234+const api = await getContractApi<FeishuContractApi>("feishu");
235+api.feishuThreadBindingTesting.resetFeishuThreadBindingsForTests();
236+}
237+238+async function prepareIMessageSessionBindingContract() {
239+const api = await getContractApi<IMessageContractApi>("imessage");
240+api.imessageConversationBindingTesting.resetIMessageConversationBindingsForTests();
241+setRegistryBackedConversationBindingPlugin({
242+id: "imessage",
243+createManager: api.createIMessageConversationBindingManager,
244+});
245+}
246+247+async function prepareMatrixSessionBindingContract() {
248+const api = await getContractApi<MatrixContractApi>("matrix");
249+api.resetMatrixThreadBindingsForTests();
250+}
251+252+async function prepareTelegramSessionBindingContract() {
253+const api = await getContractApi<TelegramContractApi>("telegram");
254+await api.resetTelegramThreadBindingsForTests();
255+}
256+129257const sessionBindingContractEntries: Record<
130258SessionBindingContractChannelId,
131259Omit<SessionBindingContractEntry, "id">
132260> = {
133261bluebubbles: {
262+beforeEach: prepareBlueBubblesSessionBindingContract,
134263expectedCapabilities: {
135264adapterAvailable: true,
136265bindSupported: true,
@@ -193,21 +322,15 @@ const sessionBindingContractEntries: Record<
193322},
194323},
195324discord: {
325+beforeEach: prepareDiscordSessionBindingContract,
196326expectedCapabilities: {
197327adapterAvailable: true,
198328bindSupported: true,
199329unbindSupported: true,
200330placements: ["current", "child"],
201331},
202332getCapabilities: async () => {
203-const { createThreadBindingManager } = await getContractApi<{
204-createThreadBindingManager: (params: {
205-accountId: string;
206-cfg?: OpenClawConfig;
207-persist: boolean;
208-enableSweeper: boolean;
209-}) => unknown;
210-}>("discord");
333+const { createThreadBindingManager } = await getContractApi<DiscordContractApi>("discord");
211334createThreadBindingManager({
212335accountId: "default",
213336cfg: baseSessionBindingCfg,
@@ -220,14 +343,7 @@ const sessionBindingContractEntries: Record<
220343});
221344},
222345bindAndResolve: async () => {
223-const { createThreadBindingManager } = await getContractApi<{
224-createThreadBindingManager: (params: {
225-accountId: string;
226-cfg?: OpenClawConfig;
227-persist: boolean;
228-enableSweeper: boolean;
229-}) => unknown;
230-}>("discord");
346+const { createThreadBindingManager } = await getContractApi<DiscordContractApi>("discord");
231347createThreadBindingManager({
232348accountId: "default",
233349cfg: baseSessionBindingCfg,
@@ -267,19 +383,16 @@ const sessionBindingContractEntries: Record<
267383},
268384},
269385feishu: {
386+beforeEach: prepareFeishuSessionBindingContract,
270387expectedCapabilities: {
271388adapterAvailable: true,
272389bindSupported: true,
273390unbindSupported: true,
274391placements: ["current"],
275392},
276393getCapabilities: async () => {
277-const { createFeishuThreadBindingManager } = await getContractApi<{
278-createFeishuThreadBindingManager: (params: {
279-accountId?: string;
280-cfg: OpenClawConfig;
281-}) => unknown;
282-}>("feishu");
394+const { createFeishuThreadBindingManager } =
395+await getContractApi<FeishuContractApi>("feishu");
283396createFeishuThreadBindingManager({
284397accountId: "default",
285398cfg: baseSessionBindingCfg,
@@ -290,12 +403,8 @@ const sessionBindingContractEntries: Record<
290403});
291404},
292405bindAndResolve: async () => {
293-const { createFeishuThreadBindingManager } = await getContractApi<{
294-createFeishuThreadBindingManager: (params: {
295-accountId?: string;
296-cfg: OpenClawConfig;
297-}) => unknown;
298-}>("feishu");
406+const { createFeishuThreadBindingManager } =
407+await getContractApi<FeishuContractApi>("feishu");
299408createFeishuThreadBindingManager({
300409accountId: "default",
301410cfg: baseSessionBindingCfg,
@@ -335,6 +444,7 @@ const sessionBindingContractEntries: Record<
335444},
336445},
337446imessage: {
447+beforeEach: prepareIMessageSessionBindingContract,
338448expectedCapabilities: {
339449adapterAvailable: true,
340450bindSupported: true,
@@ -397,6 +507,7 @@ const sessionBindingContractEntries: Record<
397507},
398508},
399509matrix: {
510+beforeEach: prepareMatrixSessionBindingContract,
400511expectedCapabilities: {
401512adapterAvailable: true,
402513bindSupported: true,
@@ -447,20 +558,16 @@ const sessionBindingContractEntries: Record<
447558},
448559},
449560telegram: {
561+beforeEach: prepareTelegramSessionBindingContract,
450562expectedCapabilities: {
451563adapterAvailable: true,
452564bindSupported: true,
453565unbindSupported: true,
454566placements: ["current", "child"],
455567},
456568getCapabilities: async () => {
457-const { createTelegramThreadBindingManager } = await getContractApi<{
458-createTelegramThreadBindingManager: (params: {
459-accountId: string;
460-persist: boolean;
461-enableSweeper: boolean;
462-}) => unknown;
463-}>("telegram");
569+const { createTelegramThreadBindingManager } =
570+await getContractApi<TelegramContractApi>("telegram");
464571createTelegramThreadBindingManager({
465572accountId: "default",
466573persist: false,
@@ -472,13 +579,8 @@ const sessionBindingContractEntries: Record<
472579});
473580},
474581bindAndResolve: async () => {
475-const { createTelegramThreadBindingManager } = await getContractApi<{
476-createTelegramThreadBindingManager: (params: {
477-accountId: string;
478-persist: boolean;
479-enableSweeper: boolean;
480-}) => unknown;
481-}>("telegram");
582+const { createTelegramThreadBindingManager } =
583+await getContractApi<TelegramContractApi>("telegram");
482584createTelegramThreadBindingManager({
483585accountId: "default",
484586persist: false,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。