





























@@ -1388,6 +1388,80 @@ describe("slack prepareSlackMessage inbound contract", () => {
13881388expect(new Set([root!.ctxPayload.SessionKey, followUp!.ctxPayload.SessionKey]).size).toBe(1);
13891389});
139013901391+it("keeps an implicit-conversation root and its Slack thread follow-up on one parent session in `requireMention: false` channels (#78505)", async () => {
1392+const { storePath } = storeFixture.makeTmpStorePath();
1393+const rootTs = "1778073105.769279";
1394+const expectedSessionKey = `agent:main:slack:channel:c0agg76cp1s:thread:${rootTs}`;
1395+const replies = vi.fn().mockResolvedValue({
1396+messages: [
1397+{
1398+text: "What day is it?",
1399+user: "U_TRAJCHE",
1400+ts: rootTs,
1401+},
1402+],
1403+response_metadata: { next_cursor: "" },
1404+});
1405+const slackCtx = createInboundSlackCtx({
1406+cfg: {
1407+session: { store: storePath },
1408+channels: {
1409+slack: {
1410+enabled: true,
1411+replyToMode: "first",
1412+groupPolicy: "open",
1413+channels: { C0AGG76CP1S: { enabled: true, requireMention: false } },
1414+},
1415+},
1416+} as OpenClawConfig,
1417+appClient: { conversations: { replies } } as unknown as App["client"],
1418+defaultRequireMention: true,
1419+replyToMode: "first",
1420+channelsConfig: { C0AGG76CP1S: { enabled: true, requireMention: false } },
1421+});
1422+slackCtx.resolveChannelName = async () => ({ name: "genai", type: "channel" });
1423+slackCtx.resolveUserName = async () => ({ name: "Trajche" });
1424+1425+const root = await prepareSlackMessage({
1426+ctx: slackCtx,
1427+account: createSlackAccount({ replyToMode: "first" }),
1428+message: {
1429+type: "message",
1430+channel: "C0AGG76CP1S",
1431+channel_type: "channel",
1432+user: "U_TRAJCHE",
1433+text: "What day is it?",
1434+ts: rootTs,
1435+} as SlackMessageEvent,
1436+opts: { source: "message" },
1437+});
1438+recordSlackThreadParticipation("default", "C0AGG76CP1S", rootTs);
1439+1440+const followUp = await prepareSlackMessage({
1441+ctx: slackCtx,
1442+account: createSlackAccount({ replyToMode: "first" }),
1443+message: {
1444+type: "message",
1445+channel: "C0AGG76CP1S",
1446+channel_type: "channel",
1447+user: "U_TRAJCHE",
1448+text: "and the time?",
1449+ts: "1778073128.229409",
1450+thread_ts: rootTs,
1451+} as SlackMessageEvent,
1452+opts: { source: "message" },
1453+});
1454+1455+expect(root).toBeTruthy();
1456+expect(followUp).toBeTruthy();
1457+// Without the seeding fix, root would land on `agent:main:slack:channel:c0agg76cp1s`
1458+// while followUp would land on `:thread:<rootTs>`, splitting the conversation
1459+// across two sessions. Both must share one session key.
1460+expect(root!.ctxPayload.SessionKey).toBe(expectedSessionKey);
1461+expect(followUp!.ctxPayload.SessionKey).toBe(expectedSessionKey);
1462+expect(new Set([root!.ctxPayload.SessionKey, followUp!.ctxPayload.SessionKey]).size).toBe(1);
1463+});
1464+13911465it("treats Slack user-group mentions as explicit mentions when the bot is a member", async () => {
13921466const usergroupsUsersList = vi.fn().mockResolvedValue({
13931467ok: true,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。