mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 10:50:44 +00:00
perf(slack): trim thread context allocation
This commit is contained in:
@@ -13,6 +13,7 @@ type SlackResolvedMessageContent = {
|
||||
|
||||
const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
|
||||
const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
|
||||
const SLACK_USER_MENTION_RE = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
|
||||
|
||||
type SlackTextObject = {
|
||||
text?: unknown;
|
||||
@@ -54,7 +55,8 @@ function collectUniqueSlackMentionIds(texts: Array<string | undefined>): string[
|
||||
if (!text) {
|
||||
continue;
|
||||
}
|
||||
for (const match of text.matchAll(/<@([A-Z0-9]+)(?:\|[^>]+)?>/gi)) {
|
||||
SLACK_USER_MENTION_RE.lastIndex = 0;
|
||||
for (const match of text.matchAll(SLACK_USER_MENTION_RE)) {
|
||||
const userId = match[1];
|
||||
if (!userId || seen.has(userId)) {
|
||||
continue;
|
||||
@@ -73,7 +75,8 @@ function renderSlackUserMentions(
|
||||
if (!text || renderedMentions.size === 0) {
|
||||
return text;
|
||||
}
|
||||
return text.replace(/<@([A-Z0-9]+)(?:\|[^>]+)?>/gi, (full, userId: string) => {
|
||||
SLACK_USER_MENTION_RE.lastIndex = 0;
|
||||
return text.replace(SLACK_USER_MENTION_RE, (full, userId: string) => {
|
||||
const rendered = renderedMentions.get(userId);
|
||||
return rendered ?? full;
|
||||
});
|
||||
@@ -139,16 +142,19 @@ function renderSlackRichTextElements(elements: unknown): string {
|
||||
break;
|
||||
}
|
||||
case "rich_text_list": {
|
||||
const listText = Array.isArray(element.elements)
|
||||
? element.elements
|
||||
.map((child) =>
|
||||
child && typeof child === "object"
|
||||
? renderSlackRichTextElements((child as SlackRichTextElement).elements)
|
||||
: "",
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
: "";
|
||||
const listParts: string[] = [];
|
||||
if (Array.isArray(element.elements)) {
|
||||
for (const child of element.elements) {
|
||||
if (!child || typeof child !== "object") {
|
||||
continue;
|
||||
}
|
||||
const rendered = renderSlackRichTextElements((child as SlackRichTextElement).elements);
|
||||
if (rendered) {
|
||||
listParts.push(rendered);
|
||||
}
|
||||
}
|
||||
}
|
||||
const listText = listParts.join("\n");
|
||||
parts.push(listText);
|
||||
break;
|
||||
}
|
||||
@@ -174,7 +180,13 @@ function readSlackBlockText(block: unknown): string | undefined {
|
||||
return text;
|
||||
}
|
||||
if (Array.isArray(blockLike.fields)) {
|
||||
const fields = blockLike.fields.map(readTextObject).filter(Boolean);
|
||||
const fields: string[] = [];
|
||||
for (const field of blockLike.fields) {
|
||||
const fieldText = readTextObject(field);
|
||||
if (fieldText) {
|
||||
fields.push(fieldText);
|
||||
}
|
||||
}
|
||||
return fields.length > 0 ? fields.join("\n") : undefined;
|
||||
}
|
||||
return undefined;
|
||||
@@ -185,7 +197,13 @@ function readSlackBlockText(block: unknown): string | undefined {
|
||||
if (!Array.isArray(blockLike.elements)) {
|
||||
return undefined;
|
||||
}
|
||||
const parts = blockLike.elements.map(readTextObject).filter(Boolean);
|
||||
const parts: string[] = [];
|
||||
for (const element of blockLike.elements) {
|
||||
const text = readTextObject(element);
|
||||
if (text) {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
return parts.length > 0 ? parts.join(" ") : undefined;
|
||||
}
|
||||
case "image":
|
||||
@@ -205,7 +223,13 @@ function resolveSlackBlocksText(blocks: unknown[] | undefined): string | undefin
|
||||
if (!blocks?.length) {
|
||||
return undefined;
|
||||
}
|
||||
const parts = blocks.map(readSlackBlockText).filter(Boolean);
|
||||
const parts: string[] = [];
|
||||
for (const block of blocks) {
|
||||
const text = readSlackBlockText(block);
|
||||
if (text) {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
return parts.length > 0 ? parts.join("\n") : undefined;
|
||||
}
|
||||
|
||||
@@ -302,17 +326,19 @@ export async function resolveSlackMessageContent(params: {
|
||||
: undefined;
|
||||
const fileOnlyPlaceholder = fileOnlyFallback ? `[Slack file: ${fileOnlyFallback}]` : undefined;
|
||||
|
||||
const botAttachmentText =
|
||||
params.isBotMessage && !attachmentContent?.text
|
||||
? (params.message.attachments ?? [])
|
||||
.map(
|
||||
(attachment) =>
|
||||
normalizeOptionalString(attachment.text) ??
|
||||
normalizeOptionalString(attachment.fallback),
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
: undefined;
|
||||
let botAttachmentText: string | undefined;
|
||||
if (params.isBotMessage && !attachmentContent?.text) {
|
||||
const botAttachmentTextParts: string[] = [];
|
||||
for (const attachment of params.message.attachments ?? []) {
|
||||
const text =
|
||||
normalizeOptionalString(attachment.text) ?? normalizeOptionalString(attachment.fallback);
|
||||
if (text) {
|
||||
botAttachmentTextParts.push(text);
|
||||
}
|
||||
}
|
||||
botAttachmentText =
|
||||
botAttachmentTextParts.length > 0 ? botAttachmentTextParts.join("\n") : undefined;
|
||||
}
|
||||
|
||||
const blocksText = resolveSlackBlocksText(params.message.blocks);
|
||||
const primaryText = chooseSlackPrimaryText({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { formatInboundEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
|
||||
import type { ContextVisibilityMode } from "openclaw/plugin-sdk/config-types";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import {
|
||||
@@ -29,6 +30,8 @@ type SlackThreadContextData = {
|
||||
threadStarterMedia: SlackMediaResult[] | null;
|
||||
};
|
||||
|
||||
const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
|
||||
|
||||
function isSlackThreadContextSenderAllowed(params: {
|
||||
allowFromLower: string[];
|
||||
allowNameMatching: boolean;
|
||||
@@ -50,6 +53,38 @@ function isSlackThreadContextSenderAllowed(params: {
|
||||
}).allowed;
|
||||
}
|
||||
|
||||
async function resolveSlackThreadUserMap(params: {
|
||||
ctx: SlackMonitorContext;
|
||||
messages: SlackThreadStarter[];
|
||||
}): Promise<Map<string, { name?: string }>> {
|
||||
const uniqueUserIds: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const item of params.messages) {
|
||||
if (!item.userId || seen.has(item.userId)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(item.userId);
|
||||
uniqueUserIds.push(item.userId);
|
||||
}
|
||||
const userMap = new Map<string, { name?: string }>();
|
||||
if (uniqueUserIds.length === 0) {
|
||||
return userMap;
|
||||
}
|
||||
const { results } = await runTasksWithConcurrency({
|
||||
tasks: uniqueUserIds.map((id) => async () => {
|
||||
const user = await params.ctx.resolveUserName(id);
|
||||
return user ? { id, user } : null;
|
||||
}),
|
||||
limit: SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY,
|
||||
});
|
||||
for (const result of results) {
|
||||
if (result) {
|
||||
userMap.set(result.id, result.user);
|
||||
}
|
||||
}
|
||||
return userMap;
|
||||
}
|
||||
|
||||
export async function resolveSlackThreadContextData(params: {
|
||||
ctx: SlackMonitorContext;
|
||||
account: ResolvedSlackAccount;
|
||||
@@ -92,7 +127,7 @@ export async function resolveSlackThreadContextData(params: {
|
||||
|
||||
const starter = params.threadStarter;
|
||||
const starterSenderName =
|
||||
params.allowNameMatching && starter?.userId
|
||||
params.allowNameMatching && params.allowFromLower.length > 0 && starter?.userId
|
||||
? (await params.ctx.resolveUserName(starter.userId))?.name
|
||||
: undefined;
|
||||
const starterIsCurrentBot = Boolean(
|
||||
@@ -174,39 +209,37 @@ export async function resolveSlackThreadContextData(params: {
|
||||
const omittedCurrentBotHistoryCount =
|
||||
threadHistory.length - threadHistoryWithoutCurrentBot.length;
|
||||
|
||||
const uniqueUserIds = [
|
||||
...new Set(
|
||||
threadHistoryWithoutCurrentBot
|
||||
.map((item) => item.userId)
|
||||
.filter((id): id is string => Boolean(id)),
|
||||
),
|
||||
];
|
||||
const userMap = new Map<string, { name?: string }>();
|
||||
await Promise.all(
|
||||
uniqueUserIds.map(async (id) => {
|
||||
const user = await params.ctx.resolveUserName(id);
|
||||
if (user) {
|
||||
userMap.set(id, user);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const userMapForFilter =
|
||||
params.contextVisibilityMode !== "all" &&
|
||||
params.allowNameMatching &&
|
||||
params.allowFromLower.length > 0
|
||||
? await resolveSlackThreadUserMap({
|
||||
ctx: params.ctx,
|
||||
messages: threadHistoryWithoutCurrentBot,
|
||||
})
|
||||
: new Map<string, { name?: string }>();
|
||||
const { items: filteredThreadHistory, omitted: omittedHistoryCount } =
|
||||
filterSupplementalContextItems({
|
||||
items: threadHistoryWithoutCurrentBot,
|
||||
mode: params.contextVisibilityMode,
|
||||
kind: "thread",
|
||||
isSenderAllowed: (historyMsg) => {
|
||||
const msgUser = historyMsg.userId ? userMap.get(historyMsg.userId) : null;
|
||||
return isSlackThreadContextSenderAllowed({
|
||||
allowFromLower: params.allowFromLower,
|
||||
allowNameMatching: params.allowNameMatching,
|
||||
userId: historyMsg.userId,
|
||||
userName: msgUser?.name,
|
||||
botId: historyMsg.botId,
|
||||
params.contextVisibilityMode === "all"
|
||||
? { items: threadHistoryWithoutCurrentBot, omitted: 0 }
|
||||
: filterSupplementalContextItems({
|
||||
items: threadHistoryWithoutCurrentBot,
|
||||
mode: params.contextVisibilityMode,
|
||||
kind: "thread",
|
||||
isSenderAllowed: (historyMsg) => {
|
||||
const msgUser = historyMsg.userId ? userMapForFilter.get(historyMsg.userId) : null;
|
||||
return isSlackThreadContextSenderAllowed({
|
||||
allowFromLower: params.allowFromLower,
|
||||
allowNameMatching: params.allowNameMatching,
|
||||
userId: historyMsg.userId,
|
||||
userName: msgUser?.name,
|
||||
botId: historyMsg.botId,
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
const userMap = await resolveSlackThreadUserMap({
|
||||
ctx: params.ctx,
|
||||
messages: filteredThreadHistory,
|
||||
});
|
||||
if (omittedHistoryCount > 0 || omittedCurrentBotHistoryCount > 0) {
|
||||
logVerbose(
|
||||
`slack: omitted ${omittedHistoryCount + omittedCurrentBotHistoryCount} thread message(s) from context (mode=${params.contextVisibilityMode})`,
|
||||
|
||||
@@ -163,9 +163,9 @@ export async function resolveSlackThreadHistory(params: {
|
||||
continue;
|
||||
}
|
||||
retained.push(msg);
|
||||
if (retained.length > maxMessages) {
|
||||
retained.shift();
|
||||
}
|
||||
}
|
||||
if (retained.length > maxMessages) {
|
||||
retained.splice(0, retained.length - maxMessages);
|
||||
}
|
||||
|
||||
const next = response.response_metadata?.next_cursor;
|
||||
|
||||
Reference in New Issue
Block a user