File size: 4,686 Bytes
fc93158 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | import type { App } from "@slack/bolt";
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../../../config/config.js";
import type { SlackMessageEvent } from "../../types.js";
import { prepareSlackMessage } from "./prepare.js";
import { createInboundSlackTestContext, createSlackTestAccount } from "./prepare.test-helpers.js";
function buildCtx(overrides?: { replyToMode?: "all" | "first" | "off" }) {
const replyToMode = overrides?.replyToMode ?? "all";
return createInboundSlackTestContext({
cfg: {
channels: {
slack: { enabled: true, replyToMode },
},
} as OpenClawConfig,
appClient: {} as App["client"],
defaultRequireMention: false,
replyToMode,
});
}
function buildChannelMessage(overrides?: Partial<SlackMessageEvent>): SlackMessageEvent {
return {
channel: "C123",
channel_type: "channel",
user: "U1",
text: "hello",
ts: "1770408518.451689",
...overrides,
} as SlackMessageEvent;
}
describe("thread-level session keys", () => {
it("keeps top-level channel turns in one session when replyToMode=off", async () => {
const ctx = buildCtx({ replyToMode: "off" });
ctx.resolveUserName = async () => ({ name: "Alice" });
const account = createSlackTestAccount({ replyToMode: "off" });
const first = await prepareSlackMessage({
ctx,
account,
message: buildChannelMessage({ ts: "1770408518.451689" }),
opts: { source: "message" },
});
const second = await prepareSlackMessage({
ctx,
account,
message: buildChannelMessage({ ts: "1770408520.000001" }),
opts: { source: "message" },
});
expect(first).toBeTruthy();
expect(second).toBeTruthy();
const firstSessionKey = first!.ctxPayload.SessionKey as string;
const secondSessionKey = second!.ctxPayload.SessionKey as string;
expect(firstSessionKey).toBe(secondSessionKey);
expect(firstSessionKey).not.toContain(":thread:");
});
it("uses parent thread_ts for thread replies even when replyToMode=off", async () => {
const ctx = buildCtx({ replyToMode: "off" });
ctx.resolveUserName = async () => ({ name: "Bob" });
const account = createSlackTestAccount({ replyToMode: "off" });
const message = buildChannelMessage({
user: "U2",
text: "reply",
ts: "1770408522.168859",
thread_ts: "1770408518.451689",
});
const prepared = await prepareSlackMessage({
ctx,
account,
message,
opts: { source: "message" },
});
expect(prepared).toBeTruthy();
// Thread replies should use the parent thread_ts, not the reply ts
const sessionKey = prepared!.ctxPayload.SessionKey as string;
expect(sessionKey).toContain(":thread:1770408518.451689");
expect(sessionKey).not.toContain("1770408522.168859");
});
it("keeps top-level channel messages on the per-channel session regardless of replyToMode", async () => {
for (const mode of ["all", "first", "off"] as const) {
const ctx = buildCtx({ replyToMode: mode });
ctx.resolveUserName = async () => ({ name: "Carol" });
const account = createSlackTestAccount({ replyToMode: mode });
const first = await prepareSlackMessage({
ctx,
account,
message: buildChannelMessage({ ts: "1770408530.000000" }),
opts: { source: "message" },
});
const second = await prepareSlackMessage({
ctx,
account,
message: buildChannelMessage({ ts: "1770408531.000000" }),
opts: { source: "message" },
});
expect(first).toBeTruthy();
expect(second).toBeTruthy();
const firstKey = first!.ctxPayload.SessionKey as string;
const secondKey = second!.ctxPayload.SessionKey as string;
expect(firstKey).toBe(secondKey);
expect(firstKey).not.toContain(":thread:");
}
});
it("does not add thread suffix for DMs when replyToMode=off", async () => {
const ctx = buildCtx({ replyToMode: "off" });
ctx.resolveUserName = async () => ({ name: "Carol" });
const account = createSlackTestAccount({ replyToMode: "off" });
const message: SlackMessageEvent = {
channel: "D456",
channel_type: "im",
user: "U3",
text: "dm message",
ts: "1770408530.000000",
} as SlackMessageEvent;
const prepared = await prepareSlackMessage({
ctx,
account,
message,
opts: { source: "message" },
});
expect(prepared).toBeTruthy();
// DMs should NOT have :thread: in the session key
const sessionKey = prepared!.ctxPayload.SessionKey as string;
expect(sessionKey).not.toContain(":thread:");
});
});
|