mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:29:34 +00:00
Feishu: serialize startup bot-info probes
This commit is contained in:
committed by
Peter Steinberger
parent
02b1958760
commit
bdca44693c
@@ -267,6 +267,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Agents/Ollama discovery: skip Ollama discovery when explicit models are configured. (#28827) Thanks @Kansodata and @vincentkoc.
|
- Agents/Ollama discovery: skip Ollama discovery when explicit models are configured. (#28827) Thanks @Kansodata and @vincentkoc.
|
||||||
- Issues/triage labeling: consolidate bug intake to a single bug issue form with required bug-type classification (regression/crash/behavior), auto-apply matching subtype labels from issue form content, and retire the separate regression template to reduce misfiled issue types and improve queue filtering. Thanks @vincentkoc.
|
- Issues/triage labeling: consolidate bug intake to a single bug issue form with required bug-type classification (regression/crash/behavior), auto-apply matching subtype labels from issue form content, and retire the separate regression template to reduce misfiled issue types and improve queue filtering. Thanks @vincentkoc.
|
||||||
- Android/Onboarding + voice reliability: request per-toggle onboarding permissions, update pairing guidance to `openclaw devices list/approve`, restore assistant speech playback in mic capture flow, cancel superseded in-flight speech (mute + per-reply token rotation), and keep `talk.config` loads retryable after transient failures. (#29796) Thanks @obviyus.
|
- Android/Onboarding + voice reliability: request per-toggle onboarding permissions, update pairing guidance to `openclaw devices list/approve`, restore assistant speech playback in mic capture flow, cancel superseded in-flight speech (mute + per-reply token rotation), and keep `talk.config` loads retryable after transient failures. (#29796) Thanks @obviyus.
|
||||||
|
- Feishu/Startup probes: serialize multi-account bot-info probes during monitor startup so large Feishu account sets do not burst `/open-apis/bot/v3/info` and trigger avoidable rate limits. (#26685)
|
||||||
- FS/Sandbox workspace boundaries: add a dedicated `outside-workspace` safe-open error code for root-escape checks, and propagate specific outside-workspace messages across edit/browser/media consumers instead of generic not-found/invalid-path fallbacks. (#29715) Thanks @YuzuruS.
|
- FS/Sandbox workspace boundaries: add a dedicated `outside-workspace` safe-open error code for root-escape checks, and propagate specific outside-workspace messages across edit/browser/media consumers instead of generic not-found/invalid-path fallbacks. (#29715) Thanks @YuzuruS.
|
||||||
- Config/Doctor group allowlist diagnostics: align `groupPolicy: "allowlist"` warnings with per-channel runtime semantics by excluding Google Chat sender-list checks and by warning when no-fallback channels (for example iMessage) omit `groupAllowFrom`, with regression coverage. (#28477) Thanks @tonydehnke.
|
- Config/Doctor group allowlist diagnostics: align `groupPolicy: "allowlist"` warnings with per-channel runtime semantics by excluding Google Chat sender-list checks and by warning when no-fallback channels (for example iMessage) omit `groupAllowFrom`, with regression coverage. (#28477) Thanks @tonydehnke.
|
||||||
- Slack/Disabled channel startup: skip Slack monitor socket startup entirely when `channels.slack.enabled=false` (including configs that still contain valid tokens), preventing disabled accounts from opening websocket connections. (#30586) Thanks @liuxiaopai-ai.
|
- Slack/Disabled channel startup: skip Slack monitor socket startup entirely when `channels.slack.enabled=false` (including configs that still contain valid tokens), preventing disabled accounts from opening websocket connections. (#30586) Thanks @liuxiaopai-ai.
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ type MonitorAccountParams = {
|
|||||||
account: ResolvedFeishuAccount;
|
account: ResolvedFeishuAccount;
|
||||||
runtime?: RuntimeEnv;
|
runtime?: RuntimeEnv;
|
||||||
abortSignal?: AbortSignal;
|
abortSignal?: AbortSignal;
|
||||||
|
botOpenId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -345,7 +346,7 @@ async function monitorSingleAccount(params: MonitorAccountParams): Promise<void>
|
|||||||
const log = runtime?.log ?? console.log;
|
const log = runtime?.log ?? console.log;
|
||||||
|
|
||||||
// Fetch bot open_id
|
// Fetch bot open_id
|
||||||
const botOpenId = await fetchBotOpenId(account);
|
const botOpenId = params.botOpenId ?? (await fetchBotOpenId(account));
|
||||||
botOpenIds.set(accountId, botOpenId ?? "");
|
botOpenIds.set(accountId, botOpenId ?? "");
|
||||||
log(`feishu[${accountId}]: bot open_id resolved: ${botOpenId ?? "unknown"}`);
|
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(", ")}`,
|
`feishu: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(", ")}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start all accounts in parallel
|
const monitorPromises: Promise<void>[] = [];
|
||||||
await Promise.all(
|
for (const account of accounts) {
|
||||||
accounts.map((account) =>
|
// Probe sequentially so large multi-account startups do not burst Feishu's bot-info endpoint.
|
||||||
|
const botOpenId = await fetchBotOpenId(account);
|
||||||
|
monitorPromises.push(
|
||||||
monitorSingleAccount({
|
monitorSingleAccount({
|
||||||
cfg,
|
cfg,
|
||||||
account,
|
account,
|
||||||
runtime: opts.runtime,
|
runtime: opts.runtime,
|
||||||
abortSignal: opts.abortSignal,
|
abortSignal: opts.abortSignal,
|
||||||
|
botOpenId,
|
||||||
}),
|
}),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
await Promise.all(monitorPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -84,6 +84,27 @@ function buildConfig(params: {
|
|||||||
} as ClawdbotConfig;
|
} 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(
|
async function withRunningWebhookMonitor(
|
||||||
params: {
|
params: {
|
||||||
accountId: string;
|
accountId: string;
|
||||||
@@ -206,4 +227,40 @@ describe("Feishu webhook security hardening", () => {
|
|||||||
isWebhookRateLimitedForTest("/feishu-rate-limit-stale:fresh", now + 60_001);
|
isWebhookRateLimitedForTest("/feishu-rate-limit-stale:fresh", now + 60_001);
|
||||||
expect(getFeishuWebhookRateLimitStateSizeForTest()).toBe(1);
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user