refactor: split browser context/actions and unify CDP timeout policy

This commit is contained in:
Peter Steinberger
2026-03-02 16:02:21 +00:00
parent 19f5d1345c
commit 17c434f2f3
20 changed files with 1845 additions and 1273 deletions

View File

@@ -0,0 +1,72 @@
import { describe, expect, it } from "vitest";
import type { SlackMessageEvent } from "../../types.js";
import { resolveSlackMessageSubtypeHandler } from "./message-subtype-handlers.js";
describe("resolveSlackMessageSubtypeHandler", () => {
it("resolves message_changed metadata and identifiers", () => {
const event = {
type: "message",
subtype: "message_changed",
channel: "D1",
event_ts: "123.456",
message: { ts: "123.456", user: "U1" },
previous_message: { ts: "123.450", user: "U2" },
} as unknown as SlackMessageEvent;
const handler = resolveSlackMessageSubtypeHandler(event);
expect(handler?.eventKind).toBe("message_changed");
expect(handler?.resolveSenderId(event)).toBe("U1");
expect(handler?.resolveChannelId(event)).toBe("D1");
expect(handler?.resolveChannelType(event)).toBeUndefined();
expect(handler?.contextKey(event)).toBe("slack:message:changed:D1:123.456");
expect(handler?.describe("DM with @user")).toContain("edited");
});
it("resolves message_deleted metadata and identifiers", () => {
const event = {
type: "message",
subtype: "message_deleted",
channel: "C1",
deleted_ts: "123.456",
event_ts: "123.457",
previous_message: { ts: "123.450", user: "U1" },
} as unknown as SlackMessageEvent;
const handler = resolveSlackMessageSubtypeHandler(event);
expect(handler?.eventKind).toBe("message_deleted");
expect(handler?.resolveSenderId(event)).toBe("U1");
expect(handler?.resolveChannelId(event)).toBe("C1");
expect(handler?.resolveChannelType(event)).toBeUndefined();
expect(handler?.contextKey(event)).toBe("slack:message:deleted:C1:123.456");
expect(handler?.describe("general")).toContain("deleted");
});
it("resolves thread_broadcast metadata and identifiers", () => {
const event = {
type: "message",
subtype: "thread_broadcast",
channel: "C1",
event_ts: "123.456",
message: { ts: "123.456", user: "U1" },
user: "U1",
} as unknown as SlackMessageEvent;
const handler = resolveSlackMessageSubtypeHandler(event);
expect(handler?.eventKind).toBe("thread_broadcast");
expect(handler?.resolveSenderId(event)).toBe("U1");
expect(handler?.resolveChannelId(event)).toBe("C1");
expect(handler?.resolveChannelType(event)).toBeUndefined();
expect(handler?.contextKey(event)).toBe("slack:thread:broadcast:C1:123.456");
expect(handler?.describe("general")).toContain("broadcast");
});
it("returns undefined for regular messages", () => {
const event = {
type: "message",
channel: "D1",
user: "U1",
text: "hello",
} as unknown as SlackMessageEvent;
expect(resolveSlackMessageSubtypeHandler(event)).toBeUndefined();
});
});

View File

@@ -0,0 +1,98 @@
import type { SlackMessageEvent } from "../../types.js";
import type {
SlackMessageChangedEvent,
SlackMessageDeletedEvent,
SlackThreadBroadcastEvent,
} from "../types.js";
type SupportedSubtype = "message_changed" | "message_deleted" | "thread_broadcast";
export type SlackMessageSubtypeHandler = {
subtype: SupportedSubtype;
eventKind: SupportedSubtype;
describe: (channelLabel: string) => string;
contextKey: (event: SlackMessageEvent) => string;
resolveSenderId: (event: SlackMessageEvent) => string | undefined;
resolveChannelId: (event: SlackMessageEvent) => string | undefined;
resolveChannelType: (event: SlackMessageEvent) => string | null | undefined;
};
const changedHandler: SlackMessageSubtypeHandler = {
subtype: "message_changed",
eventKind: "message_changed",
describe: (channelLabel) => `Slack message edited in ${channelLabel}.`,
contextKey: (event) => {
const changed = event as SlackMessageChangedEvent;
const channelId = changed.channel ?? "unknown";
const messageId =
changed.message?.ts ?? changed.previous_message?.ts ?? changed.event_ts ?? "unknown";
return `slack:message:changed:${channelId}:${messageId}`;
},
resolveSenderId: (event) => {
const changed = event as SlackMessageChangedEvent;
return (
changed.message?.user ??
changed.previous_message?.user ??
changed.message?.bot_id ??
changed.previous_message?.bot_id
);
},
resolveChannelId: (event) => (event as SlackMessageChangedEvent).channel,
resolveChannelType: () => undefined,
};
const deletedHandler: SlackMessageSubtypeHandler = {
subtype: "message_deleted",
eventKind: "message_deleted",
describe: (channelLabel) => `Slack message deleted in ${channelLabel}.`,
contextKey: (event) => {
const deleted = event as SlackMessageDeletedEvent;
const channelId = deleted.channel ?? "unknown";
const messageId = deleted.deleted_ts ?? deleted.event_ts ?? "unknown";
return `slack:message:deleted:${channelId}:${messageId}`;
},
resolveSenderId: (event) => {
const deleted = event as SlackMessageDeletedEvent;
return deleted.previous_message?.user ?? deleted.previous_message?.bot_id;
},
resolveChannelId: (event) => (event as SlackMessageDeletedEvent).channel,
resolveChannelType: () => undefined,
};
const threadBroadcastHandler: SlackMessageSubtypeHandler = {
subtype: "thread_broadcast",
eventKind: "thread_broadcast",
describe: (channelLabel) => `Slack thread reply broadcast in ${channelLabel}.`,
contextKey: (event) => {
const thread = event as SlackThreadBroadcastEvent;
const channelId = thread.channel ?? "unknown";
const messageId = thread.message?.ts ?? thread.event_ts ?? "unknown";
return `slack:thread:broadcast:${channelId}:${messageId}`;
},
resolveSenderId: (event) => {
const thread = event as SlackThreadBroadcastEvent;
return thread.user ?? thread.message?.user ?? thread.message?.bot_id;
},
resolveChannelId: (event) => (event as SlackThreadBroadcastEvent).channel,
resolveChannelType: () => undefined,
};
const SUBTYPE_HANDLER_REGISTRY: Record<SupportedSubtype, SlackMessageSubtypeHandler> = {
message_changed: changedHandler,
message_deleted: deletedHandler,
thread_broadcast: threadBroadcastHandler,
};
export function resolveSlackMessageSubtypeHandler(
event: SlackMessageEvent,
): SlackMessageSubtypeHandler | undefined {
const subtype = event.subtype;
if (
subtype !== "message_changed" &&
subtype !== "message_deleted" &&
subtype !== "thread_broadcast"
) {
return undefined;
}
return SUBTYPE_HANDLER_REGISTRY[subtype];
}

View File

@@ -4,11 +4,7 @@ import { enqueueSystemEvent } from "../../../infra/system-events.js";
import type { SlackAppMentionEvent, SlackMessageEvent } from "../../types.js";
import type { SlackMonitorContext } from "../context.js";
import type { SlackMessageHandler } from "../message-handler.js";
import type {
SlackMessageChangedEvent,
SlackMessageDeletedEvent,
SlackThreadBroadcastEvent,
} from "../types.js";
import { resolveSlackMessageSubtypeHandler } from "./message-subtype-handlers.js";
import { authorizeAndResolveSlackSystemEventContext } from "./system-event-context.js";
export function registerSlackMessageEvents(params: {
@@ -17,16 +13,6 @@ export function registerSlackMessageEvents(params: {
}) {
const { ctx, handleSlackMessage } = params;
const resolveChangedSenderId = (changed: SlackMessageChangedEvent): string | undefined =>
changed.message?.user ??
changed.previous_message?.user ??
changed.message?.bot_id ??
changed.previous_message?.bot_id;
const resolveDeletedSenderId = (deleted: SlackMessageDeletedEvent): string | undefined =>
deleted.previous_message?.user ?? deleted.previous_message?.bot_id;
const resolveThreadBroadcastSenderId = (thread: SlackThreadBroadcastEvent): string | undefined =>
thread.user ?? thread.message?.user ?? thread.message?.bot_id;
const handleIncomingMessageEvent = async ({ event, body }: { event: unknown; body: unknown }) => {
try {
if (ctx.shouldDropMismatchedSlackEvent(body)) {
@@ -34,59 +20,22 @@ export function registerSlackMessageEvents(params: {
}
const message = event as SlackMessageEvent;
if (message.subtype === "message_changed") {
const changed = event as SlackMessageChangedEvent;
const channelId = changed.channel;
const subtypeHandler = resolveSlackMessageSubtypeHandler(message);
if (subtypeHandler) {
const channelId = subtypeHandler.resolveChannelId(message);
const ingressContext = await authorizeAndResolveSlackSystemEventContext({
ctx,
senderId: resolveChangedSenderId(changed),
senderId: subtypeHandler.resolveSenderId(message),
channelId,
eventKind: "message_changed",
channelType: subtypeHandler.resolveChannelType(message),
eventKind: subtypeHandler.eventKind,
});
if (!ingressContext) {
return;
}
const messageId = changed.message?.ts ?? changed.previous_message?.ts;
enqueueSystemEvent(`Slack message edited in ${ingressContext.channelLabel}.`, {
enqueueSystemEvent(subtypeHandler.describe(ingressContext.channelLabel), {
sessionKey: ingressContext.sessionKey,
contextKey: `slack:message:changed:${channelId ?? "unknown"}:${messageId ?? changed.event_ts ?? "unknown"}`,
});
return;
}
if (message.subtype === "message_deleted") {
const deleted = event as SlackMessageDeletedEvent;
const channelId = deleted.channel;
const ingressContext = await authorizeAndResolveSlackSystemEventContext({
ctx,
senderId: resolveDeletedSenderId(deleted),
channelId,
eventKind: "message_deleted",
});
if (!ingressContext) {
return;
}
enqueueSystemEvent(`Slack message deleted in ${ingressContext.channelLabel}.`, {
sessionKey: ingressContext.sessionKey,
contextKey: `slack:message:deleted:${channelId ?? "unknown"}:${deleted.deleted_ts ?? deleted.event_ts ?? "unknown"}`,
});
return;
}
if (message.subtype === "thread_broadcast") {
const thread = event as SlackThreadBroadcastEvent;
const channelId = thread.channel;
const ingressContext = await authorizeAndResolveSlackSystemEventContext({
ctx,
senderId: resolveThreadBroadcastSenderId(thread),
channelId,
eventKind: "thread_broadcast",
});
if (!ingressContext) {
return;
}
const messageId = thread.message?.ts ?? thread.event_ts;
enqueueSystemEvent(`Slack thread reply broadcast in ${ingressContext.channelLabel}.`, {
sessionKey: ingressContext.sessionKey,
contextKey: `slack:thread:broadcast:${channelId ?? "unknown"}:${messageId ?? "unknown"}`,
contextKey: subtypeHandler.contextKey(message),
});
return;
}