
























@@ -776,6 +776,118 @@ describe("slack prepareSlackMessage inbound contract", () => {
776776expect(replies).toHaveBeenCalledTimes(2);
777777});
778778779+it("injects Slack DM history for new top-level DM sessions", async () => {
780+const { storePath } = storeFixture.makeTmpStorePath();
781+const history = vi.fn().mockResolvedValue({
782+messages: [
783+{ text: "current answer", user: "U1", ts: "300.000" },
784+{ text: "please choose A or B", bot_id: "B1", ts: "299.000" },
785+{ text: "earlier user context", user: "U1", ts: "298.000" },
786+],
787+});
788+const slackCtx = createInboundSlackCtx({
789+cfg: {
790+session: { store: storePath },
791+channels: { slack: { enabled: true, dmHistoryLimit: 2 } },
792+} as OpenClawConfig,
793+appClient: { conversations: { history } } as unknown as App["client"],
794+dmHistoryLimit: 2,
795+});
796+slackCtx.resolveUserName = async (id: string) => ({ name: id === "U1" ? "Alice" : id });
797+798+const prepared = await prepareMessageWith(
799+slackCtx,
800+createSlackAccount({ dmHistoryLimit: 2 }),
801+createSlackMessage({ text: "current answer", ts: "300.000" }),
802+);
803+804+expect(prepared).toBeTruthy();
805+expect(history).toHaveBeenCalledWith({
806+token: "token",
807+channel: "D123",
808+latest: "300.000",
809+inclusive: true,
810+limit: 3,
811+});
812+expect(prepared!.ctxPayload.Body).toContain("earlier user context");
813+expect(prepared!.ctxPayload.Body).toContain("please choose A or B");
814+expect(
815+Array.from(
816+(prepared!.ctxPayload.Body ?? "").matchAll(/\[slack message id: 300\.000 channel: D123\]/g),
817+),
818+).toHaveLength(1);
819+expect(prepared!.ctxPayload.InboundHistory).toEqual([
820+{
821+sender: "Alice (user)",
822+body: "earlier user context",
823+timestamp: 298000,
824+},
825+{
826+sender: "Assistant (assistant)",
827+body: "please choose A or B",
828+timestamp: 299000,
829+},
830+]);
831+});
832+833+it("uses per-DM Slack history limits and skips existing DM sessions", async () => {
834+const { storePath } = storeFixture.makeTmpStorePath();
835+const cfg = {
836+session: { store: storePath },
837+channels: {
838+slack: {
839+enabled: true,
840+dmHistoryLimit: 4,
841+dms: { U1: { historyLimit: 1 } },
842+},
843+},
844+} as OpenClawConfig;
845+const history = vi.fn().mockResolvedValue({
846+messages: [
847+{ text: "current", user: "U1", ts: "400.000" },
848+{ text: "only one previous", user: "U1", ts: "399.000" },
849+],
850+});
851+const slackCtx = createInboundSlackCtx({
852+ cfg,
853+appClient: { conversations: { history } } as unknown as App["client"],
854+dmHistoryLimit: 4,
855+});
856+slackCtx.resolveUserName = async () => ({ name: "Alice" });
857+858+const account = createSlackAccount({
859+dmHistoryLimit: 4,
860+dms: { U1: { historyLimit: 1 } },
861+});
862+const prepared = await prepareMessageWith(
863+slackCtx,
864+account,
865+createSlackMessage({ text: "current", ts: "400.000" }),
866+);
867+868+expect(prepared).toBeTruthy();
869+expect(history).toHaveBeenCalledWith(
870+expect.objectContaining({
871+limit: 2,
872+}),
873+);
874+875+history.mockClear();
876+fs.writeFileSync(
877+storePath,
878+JSON.stringify({ [prepared!.ctxPayload.SessionKey!]: { updatedAt: Date.now() } }, null, 2),
879+);
880+const existing = await prepareMessageWith(
881+slackCtx,
882+account,
883+createSlackMessage({ text: "next", ts: "401.000" }),
884+);
885+886+expect(existing).toBeTruthy();
887+expect(history).not.toHaveBeenCalled();
888+expect(existing!.ctxPayload.InboundHistory).toBeUndefined();
889+});
890+779891it("uses room users allowlist for thread context filtering", async () => {
780892const { prepared, replies } = await prepareThreadContextAllowlistCase({
781893channel: "C123",
@@ -1632,6 +1744,7 @@ describe("prepareSlackMessage sender prefix", () => {
16321744teamId: "T1",
16331745apiAppId: "A1",
16341746historyLimit: 0,
1747+dmHistoryLimit: 0,
16351748channelHistories: new Map(),
16361749sessionScope: "per-sender",
16371750mainKey: "agent:main:main",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。