Feishu: serialize startup bot-info probes

This commit is contained in:
bmendonca3
2026-02-28 10:22:02 -07:00
committed by Peter Steinberger
parent 02b1958760
commit bdca44693c
3 changed files with 69 additions and 6 deletions

View File

@@ -334,6 +334,7 @@ type MonitorAccountParams = {
account: ResolvedFeishuAccount;
runtime?: RuntimeEnv;
abortSignal?: AbortSignal;
botOpenId?: string;
};
/**
@@ -345,7 +346,7 @@ async function monitorSingleAccount(params: MonitorAccountParams): Promise<void>
const log = runtime?.log ?? console.log;
// Fetch bot open_id
const botOpenId = await fetchBotOpenId(account);
const botOpenId = params.botOpenId ?? (await fetchBotOpenId(account));
botOpenIds.set(accountId, botOpenId ?? "");
log(`feishu[${accountId}]: bot open_id resolved: ${botOpenId ?? "unknown"}`);
@@ -546,17 +547,21 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi
`feishu: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(", ")}`,
);
// Start all accounts in parallel
await Promise.all(
accounts.map((account) =>
const monitorPromises: Promise<void>[] = [];
for (const account of accounts) {
// Probe sequentially so large multi-account startups do not burst Feishu's bot-info endpoint.
const botOpenId = await fetchBotOpenId(account);
monitorPromises.push(
monitorSingleAccount({
cfg,
account,
runtime: opts.runtime,
abortSignal: opts.abortSignal,
botOpenId,
}),
),
);
);
}
await Promise.all(monitorPromises);
}
/**

View File

@@ -84,6 +84,27 @@ function buildConfig(params: {
} as ClawdbotConfig;
}
function buildMultiAccountWebsocketConfig(accountIds: string[]): ClawdbotConfig {
return {
channels: {
feishu: {
enabled: true,
accounts: Object.fromEntries(
accountIds.map((accountId) => [
accountId,
{
enabled: true,
appId: `cli_${accountId}`,
appSecret: `secret_${accountId}`,
connectionMode: "websocket",
},
]),
),
},
},
} as ClawdbotConfig;
}
async function withRunningWebhookMonitor(
params: {
accountId: string;
@@ -206,4 +227,40 @@ describe("Feishu webhook security hardening", () => {
isWebhookRateLimitedForTest("/feishu-rate-limit-stale:fresh", now + 60_001);
expect(getFeishuWebhookRateLimitStateSizeForTest()).toBe(1);
});
it("starts account probes sequentially to avoid startup bursts", async () => {
let inFlight = 0;
let maxInFlight = 0;
const started: string[] = [];
let releaseProbes!: () => void;
const probesReleased = new Promise<void>((resolve) => {
releaseProbes = () => resolve();
});
probeFeishuMock.mockImplementation(async (account: { accountId: string }) => {
started.push(account.accountId);
inFlight += 1;
maxInFlight = Math.max(maxInFlight, inFlight);
await probesReleased;
inFlight -= 1;
return { ok: true, botOpenId: `bot_${account.accountId}` };
});
const abortController = new AbortController();
const monitorPromise = monitorFeishuProvider({
config: buildMultiAccountWebsocketConfig(["alpha", "beta", "gamma"]),
abortSignal: abortController.signal,
});
try {
await Promise.resolve();
await Promise.resolve();
expect(started).toEqual(["alpha"]);
expect(maxInFlight).toBe(1);
} finally {
releaseProbes();
abortController.abort();
await monitorPromise;
}
});
});