





















@@ -1,5 +1,19 @@
11import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
2-import { describe, expect, it } from "vitest";
2+import { beforeEach, describe, expect, it, vi } from "vitest";
3+4+const resolveConfiguredBindingRouteMock = vi.hoisted(() => vi.fn());
5+6+vi.mock("openclaw/plugin-sdk/conversation-runtime", async () => {
7+const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/conversation-runtime")>(
8+"openclaw/plugin-sdk/conversation-runtime",
9+);
10+return {
11+ ...actual,
12+resolveConfiguredBindingRoute: (...args: unknown[]) =>
13+resolveConfiguredBindingRouteMock(...args),
14+};
15+});
16+317import type { ResolvedSlackAccount } from "../../accounts.js";
418import type { SlackMessageEvent } from "../../types.js";
519import { resolveSlackRoutingContext, type SlackRoutingContextDeps } from "./prepare-routing.js";
@@ -46,6 +60,131 @@ function buildChannelMessage(overrides?: Partial<SlackMessageEvent>): SlackMessa
4660}
47614862describe("thread-level session keys", () => {
63+beforeEach(() => {
64+resolveConfiguredBindingRouteMock.mockReset();
65+resolveConfiguredBindingRouteMock.mockImplementation(({ route }) => ({
66+bindingResolution: null,
67+ route,
68+}));
69+});
70+71+it("routes configured ACP bindings for top-level Slack channels", () => {
72+const ctx = buildCtx({ replyToMode: "off" });
73+const account = buildAccount("off");
74+const targetSessionKey = "agent:codex:acp:binding:slack:default:c123";
75+resolveConfiguredBindingRouteMock.mockImplementation(({ route, conversation }) => ({
76+bindingResolution: {
77+ conversation,
78+record: {
79+bindingId: "config:acp:slack:default:c123",
80+ targetSessionKey,
81+targetKind: "session",
82+conversation: {
83+channel: "slack",
84+accountId: "default",
85+conversationId: "c123",
86+},
87+status: "active",
88+boundAt: 0,
89+metadata: {
90+source: "config",
91+mode: "persistent",
92+agentId: "codex",
93+},
94+},
95+},
96+boundSessionKey: targetSessionKey,
97+boundAgentId: "codex",
98+route: {
99+ ...route,
100+agentId: "codex",
101+sessionKey: targetSessionKey,
102+mainSessionKey: "agent:codex:main",
103+matchedBy: "binding.channel",
104+lastRoutePolicy: "session",
105+},
106+}));
107+108+const routing = resolveSlackRoutingContext({
109+ ctx,
110+ account,
111+message: buildChannelMessage({ channel: "C123" }),
112+isDirectMessage: false,
113+isGroupDm: false,
114+isRoom: true,
115+isRoomish: true,
116+});
117+118+expect(resolveConfiguredBindingRouteMock).toHaveBeenCalledWith(
119+expect.objectContaining({
120+conversation: {
121+channel: "slack",
122+accountId: "default",
123+conversationId: "C123",
124+},
125+}),
126+);
127+expect(routing.route.agentId).toBe("codex");
128+expect(routing.sessionKey).toBe(targetSessionKey);
129+expect(routing.configuredBindingSessionKey).toBe(targetSessionKey);
130+expect(routing.runtimeBinding).toBeNull();
131+});
132+133+it("does not append Slack thread suffixes to configured ACP binding sessions", () => {
134+const ctx = buildCtx({ replyToMode: "all" });
135+const account = buildAccount("all");
136+const targetSessionKey = "agent:codex:acp:binding:slack:default:c123";
137+resolveConfiguredBindingRouteMock.mockImplementation(({ route, conversation }) => ({
138+bindingResolution: {
139+ conversation,
140+record: {
141+bindingId: "config:acp:slack:default:c123",
142+ targetSessionKey,
143+targetKind: "session",
144+conversation: {
145+channel: "slack",
146+accountId: "default",
147+conversationId: "c123",
148+},
149+status: "active",
150+boundAt: 0,
151+metadata: {
152+source: "config",
153+mode: "persistent",
154+agentId: "codex",
155+},
156+},
157+},
158+boundSessionKey: targetSessionKey,
159+boundAgentId: "codex",
160+route: {
161+ ...route,
162+agentId: "codex",
163+sessionKey: targetSessionKey,
164+mainSessionKey: "agent:codex:main",
165+matchedBy: "binding.channel",
166+lastRoutePolicy: "session",
167+},
168+}));
169+170+const routing = resolveSlackRoutingContext({
171+ ctx,
172+ account,
173+message: buildChannelMessage({
174+channel: "C123",
175+ts: "1770408522.168859",
176+thread_ts: "1770408518.451689",
177+}),
178+isDirectMessage: false,
179+isGroupDm: false,
180+isRoom: true,
181+isRoomish: true,
182+});
183+184+expect(routing.sessionKey).toBe(targetSessionKey);
185+expect(routing.sessionKey).not.toContain(":thread:");
186+});
187+49188it("keeps top-level channel turns in one session when replyToMode=off", () => {
50189const ctx = buildCtx({ replyToMode: "off" });
51190const account = buildAccount("off");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。