fix(slack): keep cached monitor identity and allowlists live (#123403)

* fix(slack): keep cached monitor context state live

* test(slack): type cached monitor context mock
This commit is contained in:
Kimi Yu
2026-08-13 19:15:38 -07:00
committed by GitHub
parent a645d13d88
commit d3ff5583d3
2 changed files with 57 additions and 1 deletions
@@ -19,6 +19,7 @@ const onFlushCallbacks: Array<
> = [];
const prepareSlackMessageMock = vi.fn(
async (_params?: {
ctx: Parameters<typeof createSlackMessageHandler>[0]["ctx"];
opts: { onVisibleDrop?: () => void };
}): Promise<{ ctxPayload: Record<string, unknown> } | null> => ({ ctxPayload: {} }),
);
@@ -174,6 +175,59 @@ describe("createSlackMessageHandler", () => {
expect(context.cfg).toBe(startupConfig);
});
it("keeps cached runtime contexts synchronized with mutable monitor state", async () => {
const startupConfig: OpenClawConfig = { agents: { defaults: { thinkingDefault: "max" } } };
const runtimeConfig: OpenClawConfig = { agents: { defaults: { thinkingDefault: "ultra" } } };
const initialChannels = { C_OLD: { enabled: true } };
const resolvedChannels = { C_RESOLVED: { enabled: true } };
const context = createContext({ cfg: startupConfig });
context.botUserId = "U_STALE";
context.channelsConfig = initialChannels;
const handler = createSlackMessageHandler({
ctx: context,
account: { accountId: "default" } as Parameters<
typeof createSlackMessageHandler
>[0]["account"],
});
setRuntimeConfigSnapshot(runtimeConfig, runtimeConfig);
const handleMessage = async (ts: string) => {
await handler(
{
type: "message",
channel: "D1",
user: "U1",
ts,
text: "hello",
} as never,
{ source: "message" },
);
const entry = enqueueMock.mock.calls.at(-1)?.[0] as Record<string, unknown>;
await runOnFlush([entry]);
};
await handleMessage("1709000000.009007");
const initialRuntimeContext = prepareSlackMessageMock.mock.calls[0]?.[0]?.ctx;
expect(initialRuntimeContext).toMatchObject({
cfg: runtimeConfig,
botUserId: "U_STALE",
channelsConfig: initialChannels,
});
context.botUserId = "U_RECOVERED";
context.channelsConfig = resolvedChannels;
await handleMessage("1709000000.009008");
const reusedRuntimeContext = prepareSlackMessageMock.mock.calls[1]?.[0]?.ctx;
expect(reusedRuntimeContext).toBe(initialRuntimeContext);
expect(reusedRuntimeContext).toMatchObject({
cfg: runtimeConfig,
botUserId: "U_RECOVERED",
channelsConfig: resolvedChannels,
});
expect(context.cfg).toBe(startupConfig);
});
it.each([
{
label: "without a source snapshot",
@@ -132,7 +132,9 @@ export function createSlackMessageHandler(params: {
if (cached) {
return cached;
}
const runtimeContext = { ...ctx, cfg: runtimeConfig };
// Keep identity, allowlists, and other mutable monitor state live while pinning this config.
const runtimeContext = Object.create(ctx) as SlackMonitorContext;
runtimeContext.cfg = runtimeConfig;
runtimeContexts.set(runtimeConfig, runtimeContext);
return runtimeContext;
};