mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(feishu): use probed botName for mention checks (#36391)
This commit is contained in:
@@ -19,8 +19,8 @@ import {
|
||||
warmupDedupFromDisk,
|
||||
} from "./dedup.js";
|
||||
import { isMentionForwardRequest } from "./mention.js";
|
||||
import { fetchBotOpenIdForMonitor } from "./monitor.startup.js";
|
||||
import { botOpenIds } from "./monitor.state.js";
|
||||
import { fetchBotIdentityForMonitor } from "./monitor.startup.js";
|
||||
import { botNames, botOpenIds } from "./monitor.state.js";
|
||||
import { monitorWebhook, monitorWebSocket } from "./monitor.transport.js";
|
||||
import { getFeishuRuntime } from "./runtime.js";
|
||||
import { getMessageFeishu } from "./send.js";
|
||||
@@ -247,6 +247,7 @@ function registerEventHandlers(
|
||||
cfg,
|
||||
event,
|
||||
botOpenId: botOpenIds.get(accountId),
|
||||
botName: botNames.get(accountId),
|
||||
runtime,
|
||||
chatHistories,
|
||||
accountId,
|
||||
@@ -260,7 +261,7 @@ function registerEventHandlers(
|
||||
};
|
||||
const resolveDebounceText = (event: FeishuMessageEvent): string => {
|
||||
const botOpenId = botOpenIds.get(accountId);
|
||||
const parsed = parseFeishuMessageEvent(event, botOpenId);
|
||||
const parsed = parseFeishuMessageEvent(event, botOpenId, botNames.get(accountId));
|
||||
return parsed.content.trim();
|
||||
};
|
||||
const recordSuppressedMessageIds = async (
|
||||
@@ -430,6 +431,7 @@ function registerEventHandlers(
|
||||
cfg,
|
||||
event: syntheticEvent,
|
||||
botOpenId: myBotId,
|
||||
botName: botNames.get(accountId),
|
||||
runtime,
|
||||
chatHistories,
|
||||
accountId,
|
||||
@@ -483,7 +485,9 @@ function registerEventHandlers(
|
||||
});
|
||||
}
|
||||
|
||||
export type BotOpenIdSource = { kind: "prefetched"; botOpenId?: string } | { kind: "fetch" };
|
||||
export type BotOpenIdSource =
|
||||
| { kind: "prefetched"; botOpenId?: string; botName?: string }
|
||||
| { kind: "fetch" };
|
||||
|
||||
export type MonitorSingleAccountParams = {
|
||||
cfg: ClawdbotConfig;
|
||||
@@ -499,11 +503,18 @@ export async function monitorSingleAccount(params: MonitorSingleAccountParams):
|
||||
const log = runtime?.log ?? console.log;
|
||||
|
||||
const botOpenIdSource = params.botOpenIdSource ?? { kind: "fetch" };
|
||||
const botOpenId =
|
||||
const botIdentity =
|
||||
botOpenIdSource.kind === "prefetched"
|
||||
? botOpenIdSource.botOpenId
|
||||
: await fetchBotOpenIdForMonitor(account, { runtime, abortSignal });
|
||||
? { botOpenId: botOpenIdSource.botOpenId, botName: botOpenIdSource.botName }
|
||||
: await fetchBotIdentityForMonitor(account, { runtime, abortSignal });
|
||||
const botOpenId = botIdentity.botOpenId;
|
||||
const botName = botIdentity.botName?.trim();
|
||||
botOpenIds.set(accountId, botOpenId ?? "");
|
||||
if (botName) {
|
||||
botNames.set(accountId, botName);
|
||||
} else {
|
||||
botNames.delete(accountId);
|
||||
}
|
||||
log(`feishu[${accountId}]: bot open_id resolved: ${botOpenId ?? "unknown"}`);
|
||||
|
||||
const connectionMode = account.config.connectionMode ?? "websocket";
|
||||
|
||||
@@ -109,7 +109,10 @@ function createTextEvent(params: {
|
||||
};
|
||||
}
|
||||
|
||||
async function setupDebounceMonitor(): Promise<(data: unknown) => Promise<void>> {
|
||||
async function setupDebounceMonitor(params?: {
|
||||
botOpenId?: string;
|
||||
botName?: string;
|
||||
}): Promise<(data: unknown) => Promise<void>> {
|
||||
const register = vi.fn((registered: Record<string, (data: unknown) => Promise<void>>) => {
|
||||
handlers = registered;
|
||||
});
|
||||
@@ -123,7 +126,11 @@ async function setupDebounceMonitor(): Promise<(data: unknown) => Promise<void>>
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
} as RuntimeEnv,
|
||||
botOpenIdSource: { kind: "prefetched", botOpenId: "ou_bot" },
|
||||
botOpenIdSource: {
|
||||
kind: "prefetched",
|
||||
botOpenId: params?.botOpenId ?? "ou_bot",
|
||||
botName: params?.botName,
|
||||
},
|
||||
});
|
||||
|
||||
const onMessage = handlers["im.message.receive_v1"];
|
||||
@@ -434,6 +441,37 @@ describe("Feishu inbound debounce regressions", () => {
|
||||
expect(mergedMentions.some((mention) => mention.id.open_id === "ou_user_a")).toBe(false);
|
||||
});
|
||||
|
||||
it("passes prefetched botName through to handleFeishuMessage", async () => {
|
||||
vi.spyOn(dedup, "tryRecordMessage").mockReturnValue(true);
|
||||
vi.spyOn(dedup, "tryRecordMessagePersistent").mockResolvedValue(true);
|
||||
vi.spyOn(dedup, "hasRecordedMessage").mockReturnValue(false);
|
||||
vi.spyOn(dedup, "hasRecordedMessagePersistent").mockResolvedValue(false);
|
||||
const onMessage = await setupDebounceMonitor({ botName: "OpenClaw Bot" });
|
||||
|
||||
await onMessage(
|
||||
createTextEvent({
|
||||
messageId: "om_name_passthrough",
|
||||
text: "@bot hello",
|
||||
mentions: [
|
||||
{
|
||||
key: "@_user_1",
|
||||
id: { open_id: "ou_bot" },
|
||||
name: "OpenClaw Bot",
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
await vi.advanceTimersByTimeAsync(25);
|
||||
|
||||
expect(handleFeishuMessageMock).toHaveBeenCalledTimes(1);
|
||||
const firstParams = handleFeishuMessageMock.mock.calls[0]?.[0] as
|
||||
| { botName?: string }
|
||||
| undefined;
|
||||
expect(firstParams?.botName).toBe("OpenClaw Bot");
|
||||
});
|
||||
|
||||
it("does not synthesize mention-forward intent across separate messages", async () => {
|
||||
vi.spyOn(dedup, "tryRecordMessage").mockReturnValue(true);
|
||||
vi.spyOn(dedup, "tryRecordMessagePersistent").mockResolvedValue(true);
|
||||
|
||||
@@ -10,6 +10,11 @@ type FetchBotOpenIdOptions = {
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
export type FeishuMonitorBotIdentity = {
|
||||
botOpenId?: string;
|
||||
botName?: string;
|
||||
};
|
||||
|
||||
function isTimeoutErrorMessage(message: string | undefined): boolean {
|
||||
return message?.toLowerCase().includes("timeout") || message?.toLowerCase().includes("timed out")
|
||||
? true
|
||||
@@ -20,12 +25,12 @@ function isAbortErrorMessage(message: string | undefined): boolean {
|
||||
return message?.toLowerCase().includes("aborted") ?? false;
|
||||
}
|
||||
|
||||
export async function fetchBotOpenIdForMonitor(
|
||||
export async function fetchBotIdentityForMonitor(
|
||||
account: ResolvedFeishuAccount,
|
||||
options: FetchBotOpenIdOptions = {},
|
||||
): Promise<string | undefined> {
|
||||
): Promise<FeishuMonitorBotIdentity> {
|
||||
if (options.abortSignal?.aborted) {
|
||||
return undefined;
|
||||
return {};
|
||||
}
|
||||
|
||||
const timeoutMs = options.timeoutMs ?? FEISHU_STARTUP_BOT_INFO_TIMEOUT_MS;
|
||||
@@ -34,11 +39,11 @@ export async function fetchBotOpenIdForMonitor(
|
||||
abortSignal: options.abortSignal,
|
||||
});
|
||||
if (result.ok) {
|
||||
return result.botOpenId;
|
||||
return { botOpenId: result.botOpenId, botName: result.botName };
|
||||
}
|
||||
|
||||
if (options.abortSignal?.aborted || isAbortErrorMessage(result.error)) {
|
||||
return undefined;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (isTimeoutErrorMessage(result.error)) {
|
||||
@@ -47,5 +52,13 @@ export async function fetchBotOpenIdForMonitor(
|
||||
`feishu[${account.accountId}]: bot info probe timed out after ${timeoutMs}ms; continuing startup`,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
return {};
|
||||
}
|
||||
|
||||
export async function fetchBotOpenIdForMonitor(
|
||||
account: ResolvedFeishuAccount,
|
||||
options: FetchBotOpenIdOptions = {},
|
||||
): Promise<string | undefined> {
|
||||
const identity = await fetchBotIdentityForMonitor(account, options);
|
||||
return identity.botOpenId;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
export const wsClients = new Map<string, Lark.WSClient>();
|
||||
export const httpServers = new Map<string, http.Server>();
|
||||
export const botOpenIds = new Map<string, string>();
|
||||
export const botNames = new Map<string, string>();
|
||||
|
||||
export const FEISHU_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
|
||||
export const FEISHU_WEBHOOK_BODY_TIMEOUT_MS = 30_000;
|
||||
@@ -140,6 +141,7 @@ export function stopFeishuMonitorState(accountId?: string): void {
|
||||
httpServers.delete(accountId);
|
||||
}
|
||||
botOpenIds.delete(accountId);
|
||||
botNames.delete(accountId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,4 +151,5 @@ export function stopFeishuMonitorState(accountId?: string): void {
|
||||
}
|
||||
httpServers.clear();
|
||||
botOpenIds.clear();
|
||||
botNames.clear();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/feishu";
|
||||
import { createFeishuWSClient } from "./client.js";
|
||||
import {
|
||||
botNames,
|
||||
botOpenIds,
|
||||
FEISHU_WEBHOOK_BODY_TIMEOUT_MS,
|
||||
FEISHU_WEBHOOK_MAX_BODY_BYTES,
|
||||
@@ -42,6 +43,7 @@ export async function monitorWebSocket({
|
||||
const cleanup = () => {
|
||||
wsClients.delete(accountId);
|
||||
botOpenIds.delete(accountId);
|
||||
botNames.delete(accountId);
|
||||
};
|
||||
|
||||
const handleAbort = () => {
|
||||
@@ -134,6 +136,7 @@ export async function monitorWebhook({
|
||||
server.close();
|
||||
httpServers.delete(accountId);
|
||||
botOpenIds.delete(accountId);
|
||||
botNames.delete(accountId);
|
||||
};
|
||||
|
||||
const handleAbort = () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
resolveReactionSyntheticEvent,
|
||||
type FeishuReactionCreatedEvent,
|
||||
} from "./monitor.account.js";
|
||||
import { fetchBotOpenIdForMonitor } from "./monitor.startup.js";
|
||||
import { fetchBotIdentityForMonitor } from "./monitor.startup.js";
|
||||
import {
|
||||
clearFeishuWebhookRateLimitStateForTest,
|
||||
getFeishuWebhookRateLimitStateSizeForTest,
|
||||
@@ -66,7 +66,7 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi
|
||||
}
|
||||
|
||||
// Probe sequentially so large multi-account startups do not burst Feishu's bot-info endpoint.
|
||||
const botOpenId = await fetchBotOpenIdForMonitor(account, {
|
||||
const { botOpenId, botName } = await fetchBotIdentityForMonitor(account, {
|
||||
runtime: opts.runtime,
|
||||
abortSignal: opts.abortSignal,
|
||||
});
|
||||
@@ -82,7 +82,7 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi
|
||||
account,
|
||||
runtime: opts.runtime,
|
||||
abortSignal: opts.abortSignal,
|
||||
botOpenIdSource: { kind: "prefetched", botOpenId },
|
||||
botOpenIdSource: { kind: "prefetched", botOpenId, botName },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user