Skip to content

Commit 1f8e6bf

Browse files
committed
fix: preserve slack direct monitor thread fallback
1 parent 0575a27 commit 1f8e6bf

2 files changed

Lines changed: 63 additions & 2 deletions

File tree

extensions/slack/src/monitor/message-handler/prepare-thread-context.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import type { ResolvedSlackAccount } from "../../accounts.js";
1111
import type { SlackMessageEvent } from "../../types.js";
1212
import { resolveSlackAllowListMatch } from "../allow-list.js";
13-
import { resolveChannelResetConfig } from "../config.runtime.js";
13+
import { readSessionUpdatedAt, resolveChannelResetConfig } from "../config.runtime.js";
1414
import type { SlackMonitorContext } from "../context.js";
1515
import type { SlackMediaResult } from "../media-types.js";
1616
import { resolveSlackThreadHistory, type SlackThreadStarter } from "../thread.js";
@@ -169,10 +169,19 @@ export async function resolveSlackThreadContextData(params: {
169169
sessionKey: params.sessionKey,
170170
})
171171
: undefined;
172+
const threadSessionPreviousTimestamp =
173+
params.isThreadReply && params.threadTs && !threadSessionFreshness
174+
? readSessionUpdatedAt({
175+
storePath: params.storePath,
176+
sessionKey: params.sessionKey,
177+
})
178+
: undefined;
172179
const shouldSeedInitialThreadContext = Boolean(
173180
params.isThreadReply &&
174181
params.threadTs &&
175-
(!threadSessionFreshness || threadSessionFreshness.state !== "fresh"),
182+
(threadSessionFreshness
183+
? threadSessionFreshness.state !== "fresh"
184+
: threadSessionPreviousTimestamp === undefined),
176185
);
177186
const shouldLoadInitialThreadHistory =
178187
shouldSeedInitialThreadContext || params.forceInitialHistory === true;

extensions/slack/src/monitor/message-handler/prepare.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1908,6 +1908,58 @@ Second paragraph should still reach the agent after Slack's preview cutoff.`;
19081908
expect(replies).toHaveBeenCalledTimes(1);
19091909
});
19101910

1911+
it("preserves existing thread fallback when channel runtime is omitted", async () => {
1912+
const { storePath } = storeFixture.makeTmpStorePath();
1913+
const cfg = {
1914+
session: { store: storePath },
1915+
channels: { slack: { enabled: true, replyToMode: "all", groupPolicy: "open" } },
1916+
} as OpenClawConfig;
1917+
const route = resolveAgentRoute({
1918+
cfg,
1919+
channel: "slack",
1920+
accountId: "default",
1921+
teamId: "T1",
1922+
peer: { kind: "channel", id: "C123" },
1923+
});
1924+
const threadKeys = resolveThreadSessionKeys({
1925+
baseSessionKey: route.sessionKey,
1926+
threadId: "250.000",
1927+
});
1928+
const now = Date.now();
1929+
await saveSessionStore(
1930+
storePath,
1931+
{
1932+
[threadKeys.sessionKey]: {
1933+
sessionId: "direct-monitor-existing-thread-session",
1934+
updatedAt: now - 2 * 24 * 60 * 60 * 1000,
1935+
sessionStartedAt: now - 2 * 24 * 60 * 60 * 1000,
1936+
lastInteractionAt: now - 2 * 24 * 60 * 60 * 1000,
1937+
},
1938+
},
1939+
{ skipMaintenance: true },
1940+
);
1941+
1942+
const replies = vi.fn().mockResolvedValueOnce({
1943+
messages: [{ text: "starter", user: "U2", ts: "250.000" }],
1944+
});
1945+
const slackCtx = createThreadSlackCtx({ cfg, replies });
1946+
slackCtx.channelRuntime = undefined;
1947+
slackCtx.resolveUserName = async () => ({ name: "Alice" });
1948+
slackCtx.resolveChannelName = async () => ({ name: "general", type: "channel" });
1949+
1950+
const prepared = await prepareThreadMessage(slackCtx, {
1951+
text: "direct monitor reply in old thread",
1952+
ts: "251.000",
1953+
thread_ts: "250.000",
1954+
});
1955+
1956+
assertPrepared(prepared);
1957+
expect(prepared.ctxPayload.IsFirstThreadTurn).toBeUndefined();
1958+
expect(prepared.ctxPayload.ThreadHistoryBody).toBeUndefined();
1959+
expect(prepared.ctxPayload.ThreadStarterBody).toBeUndefined();
1960+
expect(replies).toHaveBeenCalledTimes(1);
1961+
});
1962+
19111963
it("loads bounded thread history for existing thread sessions stale under reset policy", async () => {
19121964
const { storePath } = storeFixture.makeTmpStorePath();
19131965
const now = Date.now();

0 commit comments

Comments
 (0)