@openclaw/feishu 2026.5.2 → 2026.5.3-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accounts-Ba3-WP1z.js +423 -0
- package/dist/api.js +2280 -0
- package/dist/app-registration-B8qc1MCM.js +184 -0
- package/dist/audio-preflight.runtime-BPlzkO3l.js +7 -0
- package/dist/card-interaction-BfRLgvw_.js +96 -0
- package/dist/channel-CSD_Jt8I.js +1668 -0
- package/dist/channel-entry.js +22 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-DYsXcD36.js +700 -0
- package/dist/client-DBVoQL5w.js +157 -0
- package/dist/contract-api.js +9 -0
- package/dist/conversation-id-DWS3Ep2A.js +139 -0
- package/dist/directory.static-f3EeoRJd.js +44 -0
- package/dist/drive-C5eJLJr7.js +883 -0
- package/dist/index.js +68 -0
- package/dist/monitor-CT189QfR.js +60 -0
- package/dist/monitor.account-dJV2jO8C.js +4990 -0
- package/dist/monitor.state-DYM02ipp.js +100 -0
- package/dist/policy-D6c-wMPl.js +118 -0
- package/dist/probe-BNzzU_uR.js +149 -0
- package/dist/rolldown-runtime-DUslC3ob.js +14 -0
- package/dist/runtime-CG0DuRCy.js +8 -0
- package/dist/runtime-api.js +14 -0
- package/dist/secret-contract-Dm4Z_zQN.js +119 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/security-audit-DqJdocrN.js +11 -0
- package/dist/security-audit-shared-ByuMx9cJ.js +38 -0
- package/dist/security-contract-api.js +2 -0
- package/dist/send-DowxxbpH.js +1218 -0
- package/dist/session-conversation-B4nrW-vo.js +27 -0
- package/dist/session-key-api.js +2 -0
- package/dist/setup-api.js +2 -0
- package/dist/setup-entry.js +15 -0
- package/dist/subagent-hooks-C3UhPVLV.js +227 -0
- package/dist/subagent-hooks-api.js +23 -0
- package/dist/targets-JMFJRKSe.js +48 -0
- package/dist/thread-bindings-BmS6TLes.js +222 -0
- package/package.json +15 -6
- package/api.ts +0 -31
- package/channel-entry.ts +0 -20
- package/channel-plugin-api.ts +0 -1
- package/contract-api.ts +0 -16
- package/index.ts +0 -82
- package/runtime-api.ts +0 -55
- package/secret-contract-api.ts +0 -5
- package/security-contract-api.ts +0 -1
- package/session-key-api.ts +0 -1
- package/setup-api.ts +0 -3
- package/setup-entry.test.ts +0 -14
- package/setup-entry.ts +0 -13
- package/src/accounts.test.ts +0 -459
- package/src/accounts.ts +0 -326
- package/src/app-registration.ts +0 -331
- package/src/approval-auth.test.ts +0 -24
- package/src/approval-auth.ts +0 -25
- package/src/async.test.ts +0 -35
- package/src/async.ts +0 -104
- package/src/audio-preflight.runtime.ts +0 -9
- package/src/bitable.test.ts +0 -131
- package/src/bitable.ts +0 -762
- package/src/bot-content.ts +0 -474
- package/src/bot-group-name.test.ts +0 -108
- package/src/bot-runtime-api.ts +0 -12
- package/src/bot-sender-name.ts +0 -125
- package/src/bot.broadcast.test.ts +0 -463
- package/src/bot.card-action.test.ts +0 -577
- package/src/bot.checkBotMentioned.test.ts +0 -265
- package/src/bot.helpers.test.ts +0 -118
- package/src/bot.stripBotMention.test.ts +0 -126
- package/src/bot.test.ts +0 -3040
- package/src/bot.ts +0 -1559
- package/src/card-action.ts +0 -447
- package/src/card-interaction.test.ts +0 -129
- package/src/card-interaction.ts +0 -159
- package/src/card-test-helpers.ts +0 -47
- package/src/card-ux-approval.ts +0 -65
- package/src/card-ux-launcher.test.ts +0 -99
- package/src/card-ux-launcher.ts +0 -121
- package/src/card-ux-shared.ts +0 -33
- package/src/channel-runtime-api.ts +0 -16
- package/src/channel.runtime.ts +0 -47
- package/src/channel.test.ts +0 -959
- package/src/channel.ts +0 -1313
- package/src/chat-schema.ts +0 -25
- package/src/chat.test.ts +0 -196
- package/src/chat.ts +0 -188
- package/src/client.test.ts +0 -433
- package/src/client.ts +0 -290
- package/src/comment-dispatcher-runtime-api.ts +0 -6
- package/src/comment-dispatcher.test.ts +0 -169
- package/src/comment-dispatcher.ts +0 -107
- package/src/comment-handler-runtime-api.ts +0 -3
- package/src/comment-handler.test.ts +0 -486
- package/src/comment-handler.ts +0 -309
- package/src/comment-reaction.test.ts +0 -166
- package/src/comment-reaction.ts +0 -259
- package/src/comment-shared.test.ts +0 -182
- package/src/comment-shared.ts +0 -406
- package/src/comment-target.ts +0 -44
- package/src/config-schema.test.ts +0 -309
- package/src/config-schema.ts +0 -333
- package/src/conversation-id.test.ts +0 -18
- package/src/conversation-id.ts +0 -199
- package/src/dedup-runtime-api.ts +0 -1
- package/src/dedup.ts +0 -141
- package/src/directory.static.ts +0 -61
- package/src/directory.test.ts +0 -136
- package/src/directory.ts +0 -124
- package/src/doc-schema.ts +0 -182
- package/src/docx-batch-insert.test.ts +0 -91
- package/src/docx-batch-insert.ts +0 -223
- package/src/docx-color-text.ts +0 -154
- package/src/docx-table-ops.test.ts +0 -53
- package/src/docx-table-ops.ts +0 -316
- package/src/docx-types.ts +0 -38
- package/src/docx.account-selection.test.ts +0 -79
- package/src/docx.test.ts +0 -685
- package/src/docx.ts +0 -1616
- package/src/drive-schema.ts +0 -92
- package/src/drive.test.ts +0 -1219
- package/src/drive.ts +0 -829
- package/src/dynamic-agent.ts +0 -137
- package/src/event-types.ts +0 -45
- package/src/external-keys.test.ts +0 -20
- package/src/external-keys.ts +0 -19
- package/src/lifecycle.test-support.ts +0 -220
- package/src/media.test.ts +0 -900
- package/src/media.ts +0 -861
- package/src/mention-target.types.ts +0 -5
- package/src/mention.ts +0 -114
- package/src/message-action-contract.ts +0 -13
- package/src/monitor-state-runtime-api.ts +0 -7
- package/src/monitor-transport-runtime-api.ts +0 -7
- package/src/monitor.account.ts +0 -468
- package/src/monitor.acp-init-failure.lifecycle.test-support.ts +0 -219
- package/src/monitor.bot-identity.ts +0 -86
- package/src/monitor.bot-menu-handler.ts +0 -165
- package/src/monitor.bot-menu.lifecycle.test-support.ts +0 -224
- package/src/monitor.bot-menu.test.ts +0 -178
- package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +0 -264
- package/src/monitor.card-action.lifecycle.test-support.ts +0 -373
- package/src/monitor.cleanup.test.ts +0 -376
- package/src/monitor.comment-notice-handler.ts +0 -105
- package/src/monitor.comment.test.ts +0 -937
- package/src/monitor.comment.ts +0 -1386
- package/src/monitor.lifecycle.test.ts +0 -4
- package/src/monitor.message-handler.ts +0 -339
- package/src/monitor.reaction.lifecycle.test-support.ts +0 -68
- package/src/monitor.reaction.test.ts +0 -713
- package/src/monitor.startup.test.ts +0 -192
- package/src/monitor.startup.ts +0 -74
- package/src/monitor.state.defaults.test.ts +0 -46
- package/src/monitor.state.ts +0 -170
- package/src/monitor.synthetic-error.ts +0 -18
- package/src/monitor.test-mocks.ts +0 -45
- package/src/monitor.transport.ts +0 -424
- package/src/monitor.ts +0 -100
- package/src/monitor.webhook-e2e.test.ts +0 -272
- package/src/monitor.webhook-security.test.ts +0 -264
- package/src/monitor.webhook.test-helpers.ts +0 -116
- package/src/outbound-runtime-api.ts +0 -1
- package/src/outbound.test.ts +0 -935
- package/src/outbound.ts +0 -718
- package/src/perm-schema.ts +0 -52
- package/src/perm.ts +0 -170
- package/src/pins.ts +0 -108
- package/src/policy.test.ts +0 -334
- package/src/policy.ts +0 -236
- package/src/post.test.ts +0 -105
- package/src/post.ts +0 -275
- package/src/probe.test.ts +0 -275
- package/src/probe.ts +0 -166
- package/src/processing-claims.ts +0 -59
- package/src/qr-terminal.ts +0 -1
- package/src/reactions.ts +0 -123
- package/src/reasoning-preview.test.ts +0 -59
- package/src/reasoning-preview.ts +0 -20
- package/src/reply-dispatcher-runtime-api.ts +0 -7
- package/src/reply-dispatcher.test.ts +0 -1144
- package/src/reply-dispatcher.ts +0 -650
- package/src/runtime.ts +0 -9
- package/src/secret-contract.ts +0 -145
- package/src/secret-input.ts +0 -1
- package/src/security-audit-shared.ts +0 -69
- package/src/security-audit.test.ts +0 -61
- package/src/security-audit.ts +0 -1
- package/src/send-result.ts +0 -29
- package/src/send-target.test.ts +0 -80
- package/src/send-target.ts +0 -35
- package/src/send.reply-fallback.test.ts +0 -292
- package/src/send.test.ts +0 -550
- package/src/send.ts +0 -800
- package/src/sequential-key.test.ts +0 -72
- package/src/sequential-key.ts +0 -28
- package/src/sequential-queue.test.ts +0 -92
- package/src/sequential-queue.ts +0 -16
- package/src/session-conversation.ts +0 -42
- package/src/session-route.ts +0 -48
- package/src/setup-core.ts +0 -51
- package/src/setup-surface.test.ts +0 -174
- package/src/setup-surface.ts +0 -581
- package/src/streaming-card.test.ts +0 -190
- package/src/streaming-card.ts +0 -490
- package/src/subagent-hooks.test.ts +0 -603
- package/src/subagent-hooks.ts +0 -397
- package/src/targets.ts +0 -97
- package/src/test-support/lifecycle-test-support.ts +0 -453
- package/src/thread-bindings.test.ts +0 -143
- package/src/thread-bindings.ts +0 -330
- package/src/tool-account-routing.test.ts +0 -187
- package/src/tool-account.test.ts +0 -44
- package/src/tool-account.ts +0 -93
- package/src/tool-factory-test-harness.ts +0 -79
- package/src/tool-result.test.ts +0 -32
- package/src/tool-result.ts +0 -16
- package/src/tools-config.test.ts +0 -21
- package/src/tools-config.ts +0 -22
- package/src/types.ts +0 -104
- package/src/typing.test.ts +0 -144
- package/src/typing.ts +0 -214
- package/src/wiki-schema.ts +0 -55
- package/src/wiki.ts +0 -227
- package/subagent-hooks-api.ts +0 -31
- package/tsconfig.json +0 -16
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { createNonExitingRuntimeEnv } from "openclaw/plugin-sdk/plugin-test-runtime";
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import type { ClawdbotConfig } from "../runtime-api.js";
|
|
4
|
-
import { monitorFeishuProvider, stopFeishuMonitor } from "./monitor.js";
|
|
5
|
-
|
|
6
|
-
const probeFeishuMock = vi.hoisted(() => vi.fn());
|
|
7
|
-
|
|
8
|
-
vi.mock("./probe.js", () => ({
|
|
9
|
-
probeFeishu: probeFeishuMock,
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
vi.mock("./client.js", async () => {
|
|
13
|
-
const { createFeishuClientMockModule } = await import("./monitor.test-mocks.js");
|
|
14
|
-
return createFeishuClientMockModule();
|
|
15
|
-
});
|
|
16
|
-
vi.mock("./runtime.js", async () => {
|
|
17
|
-
const { createFeishuRuntimeMockModule } = await import("./monitor.test-mocks.js");
|
|
18
|
-
return createFeishuRuntimeMockModule();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
function buildMultiAccountWebsocketConfig(accountIds: string[]): ClawdbotConfig {
|
|
22
|
-
return {
|
|
23
|
-
channels: {
|
|
24
|
-
feishu: {
|
|
25
|
-
enabled: true,
|
|
26
|
-
accounts: Object.fromEntries(
|
|
27
|
-
accountIds.map((accountId) => [
|
|
28
|
-
accountId,
|
|
29
|
-
{
|
|
30
|
-
enabled: true,
|
|
31
|
-
appId: `cli_${accountId}`,
|
|
32
|
-
appSecret: `secret_${accountId}`, // pragma: allowlist secret
|
|
33
|
-
connectionMode: "websocket",
|
|
34
|
-
},
|
|
35
|
-
]),
|
|
36
|
-
),
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
} as ClawdbotConfig;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function waitForStartedAccount(started: string[], accountId: string) {
|
|
43
|
-
await vi.waitFor(
|
|
44
|
-
() => {
|
|
45
|
-
expect(started).toContain(accountId);
|
|
46
|
-
},
|
|
47
|
-
{ timeout: 10_000 },
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
afterEach(() => {
|
|
52
|
-
stopFeishuMonitor();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe("Feishu monitor startup preflight", () => {
|
|
56
|
-
it("starts account probes sequentially to avoid startup bursts", async () => {
|
|
57
|
-
let inFlight = 0;
|
|
58
|
-
let maxInFlight = 0;
|
|
59
|
-
const started: string[] = [];
|
|
60
|
-
let releaseProbes!: () => void;
|
|
61
|
-
const probesReleased = new Promise<void>((resolve) => {
|
|
62
|
-
releaseProbes = () => resolve();
|
|
63
|
-
});
|
|
64
|
-
probeFeishuMock.mockImplementation(async (account: { accountId: string }) => {
|
|
65
|
-
started.push(account.accountId);
|
|
66
|
-
inFlight += 1;
|
|
67
|
-
maxInFlight = Math.max(maxInFlight, inFlight);
|
|
68
|
-
await probesReleased;
|
|
69
|
-
inFlight -= 1;
|
|
70
|
-
return { ok: true, botOpenId: `bot_${account.accountId}` };
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const abortController = new AbortController();
|
|
74
|
-
const monitorPromise = monitorFeishuProvider({
|
|
75
|
-
config: buildMultiAccountWebsocketConfig(["alpha", "beta", "gamma"]),
|
|
76
|
-
abortSignal: abortController.signal,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
await waitForStartedAccount(started, "alpha");
|
|
81
|
-
expect(started).toEqual(["alpha"]);
|
|
82
|
-
expect(maxInFlight).toBe(1);
|
|
83
|
-
} finally {
|
|
84
|
-
releaseProbes();
|
|
85
|
-
abortController.abort();
|
|
86
|
-
await monitorPromise;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("does not refetch bot info after a failed sequential preflight", async () => {
|
|
91
|
-
const started: string[] = [];
|
|
92
|
-
let releaseBetaProbe!: () => void;
|
|
93
|
-
const betaProbeReleased = new Promise<void>((resolve) => {
|
|
94
|
-
releaseBetaProbe = () => resolve();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
probeFeishuMock.mockImplementation(async (account: { accountId: string }) => {
|
|
98
|
-
started.push(account.accountId);
|
|
99
|
-
if (account.accountId === "alpha") {
|
|
100
|
-
return { ok: false };
|
|
101
|
-
}
|
|
102
|
-
await betaProbeReleased;
|
|
103
|
-
return { ok: true, botOpenId: `bot_${account.accountId}` };
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const abortController = new AbortController();
|
|
107
|
-
const monitorPromise = monitorFeishuProvider({
|
|
108
|
-
config: buildMultiAccountWebsocketConfig(["alpha", "beta"]),
|
|
109
|
-
abortSignal: abortController.signal,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
await waitForStartedAccount(started, "beta");
|
|
114
|
-
expect(started).toEqual(["alpha", "beta"]);
|
|
115
|
-
expect(started.filter((accountId) => accountId === "alpha")).toHaveLength(1);
|
|
116
|
-
} finally {
|
|
117
|
-
releaseBetaProbe();
|
|
118
|
-
abortController.abort();
|
|
119
|
-
await monitorPromise;
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("continues startup when probe layer reports timeout", async () => {
|
|
124
|
-
const started: string[] = [];
|
|
125
|
-
let releaseBetaProbe!: () => void;
|
|
126
|
-
const betaProbeReleased = new Promise<void>((resolve) => {
|
|
127
|
-
releaseBetaProbe = () => resolve();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
probeFeishuMock.mockImplementation((account: { accountId: string }) => {
|
|
131
|
-
started.push(account.accountId);
|
|
132
|
-
if (account.accountId === "alpha") {
|
|
133
|
-
return Promise.resolve({ ok: false, error: "probe timed out after 10000ms" });
|
|
134
|
-
}
|
|
135
|
-
return betaProbeReleased.then(() => ({ ok: true, botOpenId: `bot_${account.accountId}` }));
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const abortController = new AbortController();
|
|
139
|
-
const runtime = createNonExitingRuntimeEnv();
|
|
140
|
-
const monitorPromise = monitorFeishuProvider({
|
|
141
|
-
config: buildMultiAccountWebsocketConfig(["alpha", "beta"]),
|
|
142
|
-
runtime,
|
|
143
|
-
abortSignal: abortController.signal,
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
await waitForStartedAccount(started, "beta");
|
|
148
|
-
expect(started).toEqual(["alpha", "beta"]);
|
|
149
|
-
expect(runtime.error).toHaveBeenCalledWith(
|
|
150
|
-
expect.stringContaining("bot info probe timed out"),
|
|
151
|
-
);
|
|
152
|
-
} finally {
|
|
153
|
-
releaseBetaProbe();
|
|
154
|
-
abortController.abort();
|
|
155
|
-
await monitorPromise;
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("stops sequential preflight when aborted during probe", async () => {
|
|
160
|
-
const started: string[] = [];
|
|
161
|
-
probeFeishuMock.mockImplementation(
|
|
162
|
-
(account: { accountId: string }, options: { abortSignal?: AbortSignal }) => {
|
|
163
|
-
started.push(account.accountId);
|
|
164
|
-
return new Promise((resolve) => {
|
|
165
|
-
options.abortSignal?.addEventListener(
|
|
166
|
-
"abort",
|
|
167
|
-
() => resolve({ ok: false, error: "probe aborted" }),
|
|
168
|
-
{ once: true },
|
|
169
|
-
);
|
|
170
|
-
});
|
|
171
|
-
},
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
const abortController = new AbortController();
|
|
175
|
-
const monitorPromise = monitorFeishuProvider({
|
|
176
|
-
config: buildMultiAccountWebsocketConfig(["alpha", "beta"]),
|
|
177
|
-
abortSignal: abortController.signal,
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
await waitForStartedAccount(started, "alpha");
|
|
182
|
-
expect(started).toEqual(["alpha"]);
|
|
183
|
-
|
|
184
|
-
abortController.abort();
|
|
185
|
-
await monitorPromise;
|
|
186
|
-
|
|
187
|
-
expect(started).toEqual(["alpha"]);
|
|
188
|
-
} finally {
|
|
189
|
-
abortController.abort();
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
});
|
package/src/monitor.startup.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
2
|
-
import type { RuntimeEnv } from "../runtime-api.js";
|
|
3
|
-
import { probeFeishu } from "./probe.js";
|
|
4
|
-
import type { ResolvedFeishuAccount } from "./types.js";
|
|
5
|
-
|
|
6
|
-
const FEISHU_STARTUP_BOT_INFO_TIMEOUT_DEFAULT_MS = 30_000;
|
|
7
|
-
const FEISHU_STARTUP_BOT_INFO_TIMEOUT_ENV = "OPENCLAW_FEISHU_STARTUP_PROBE_TIMEOUT_MS";
|
|
8
|
-
|
|
9
|
-
function resolveStartupProbeTimeoutMs(): number {
|
|
10
|
-
const raw = process.env[FEISHU_STARTUP_BOT_INFO_TIMEOUT_ENV];
|
|
11
|
-
if (raw) {
|
|
12
|
-
const parsed = Number(raw);
|
|
13
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
14
|
-
return Math.floor(parsed);
|
|
15
|
-
}
|
|
16
|
-
console.warn(
|
|
17
|
-
`[feishu] ${FEISHU_STARTUP_BOT_INFO_TIMEOUT_ENV}="${raw}" is invalid; using default ${FEISHU_STARTUP_BOT_INFO_TIMEOUT_DEFAULT_MS}ms`,
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
return FEISHU_STARTUP_BOT_INFO_TIMEOUT_DEFAULT_MS;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const FEISHU_STARTUP_BOT_INFO_TIMEOUT_MS = resolveStartupProbeTimeoutMs();
|
|
24
|
-
|
|
25
|
-
type FetchBotOpenIdOptions = {
|
|
26
|
-
runtime?: RuntimeEnv;
|
|
27
|
-
abortSignal?: AbortSignal;
|
|
28
|
-
timeoutMs?: number;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type FeishuMonitorBotIdentity = {
|
|
32
|
-
botOpenId?: string;
|
|
33
|
-
botName?: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
function isTimeoutErrorMessage(message: string | undefined): boolean {
|
|
37
|
-
const lower = normalizeLowercaseStringOrEmpty(message);
|
|
38
|
-
return lower.includes("timeout") || lower.includes("timed out");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function isAbortErrorMessage(message: string | undefined): boolean {
|
|
42
|
-
return normalizeLowercaseStringOrEmpty(message).includes("aborted");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function fetchBotIdentityForMonitor(
|
|
46
|
-
account: ResolvedFeishuAccount,
|
|
47
|
-
options: FetchBotOpenIdOptions = {},
|
|
48
|
-
): Promise<FeishuMonitorBotIdentity> {
|
|
49
|
-
if (options.abortSignal?.aborted) {
|
|
50
|
-
return {};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const timeoutMs = options.timeoutMs ?? FEISHU_STARTUP_BOT_INFO_TIMEOUT_MS;
|
|
54
|
-
const result = await probeFeishu(account, {
|
|
55
|
-
timeoutMs,
|
|
56
|
-
abortSignal: options.abortSignal,
|
|
57
|
-
});
|
|
58
|
-
if (result.ok) {
|
|
59
|
-
return { botOpenId: result.botOpenId, botName: result.botName };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const probeError = result.error ?? undefined;
|
|
63
|
-
if (options.abortSignal?.aborted || isAbortErrorMessage(probeError)) {
|
|
64
|
-
return {};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (isTimeoutErrorMessage(probeError)) {
|
|
68
|
-
const error = options.runtime?.error ?? console.error;
|
|
69
|
-
error(
|
|
70
|
-
`feishu[${account.accountId}]: bot info probe timed out after ${timeoutMs}ms; continuing startup`,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
return {};
|
|
74
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
resolveFeishuWebhookAnomalyDefaultsForTest,
|
|
4
|
-
resolveFeishuWebhookRateLimitDefaultsForTest,
|
|
5
|
-
} from "./monitor.state.js";
|
|
6
|
-
|
|
7
|
-
describe("feishu monitor state defaults", () => {
|
|
8
|
-
it("falls back to hard defaults when sdk defaults are missing", () => {
|
|
9
|
-
expect(resolveFeishuWebhookRateLimitDefaultsForTest(undefined)).toEqual({
|
|
10
|
-
windowMs: 60_000,
|
|
11
|
-
maxRequests: 120,
|
|
12
|
-
maxTrackedKeys: 4_096,
|
|
13
|
-
});
|
|
14
|
-
expect(resolveFeishuWebhookAnomalyDefaultsForTest(undefined)).toEqual({
|
|
15
|
-
maxTrackedKeys: 4_096,
|
|
16
|
-
ttlMs: 21_600_000,
|
|
17
|
-
logEvery: 25,
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("keeps valid sdk values and repairs invalid fields", () => {
|
|
22
|
-
expect(
|
|
23
|
-
resolveFeishuWebhookRateLimitDefaultsForTest({
|
|
24
|
-
windowMs: 45_000,
|
|
25
|
-
maxRequests: 0,
|
|
26
|
-
maxTrackedKeys: -1,
|
|
27
|
-
}),
|
|
28
|
-
).toEqual({
|
|
29
|
-
windowMs: 45_000,
|
|
30
|
-
maxRequests: 120,
|
|
31
|
-
maxTrackedKeys: 4_096,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
expect(
|
|
35
|
-
resolveFeishuWebhookAnomalyDefaultsForTest({
|
|
36
|
-
maxTrackedKeys: 2048,
|
|
37
|
-
ttlMs: Number.NaN,
|
|
38
|
-
logEvery: 10,
|
|
39
|
-
}),
|
|
40
|
-
).toEqual({
|
|
41
|
-
maxTrackedKeys: 2048,
|
|
42
|
-
ttlMs: 21_600_000,
|
|
43
|
-
logEvery: 10,
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
});
|
package/src/monitor.state.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import * as http from "node:http";
|
|
2
|
-
import type * as Lark from "@larksuiteoapi/node-sdk";
|
|
3
|
-
import {
|
|
4
|
-
createFixedWindowRateLimiter,
|
|
5
|
-
createWebhookAnomalyTracker,
|
|
6
|
-
type RuntimeEnv,
|
|
7
|
-
WEBHOOK_ANOMALY_COUNTER_DEFAULTS as WEBHOOK_ANOMALY_COUNTER_DEFAULTS_FROM_SDK,
|
|
8
|
-
WEBHOOK_RATE_LIMIT_DEFAULTS as WEBHOOK_RATE_LIMIT_DEFAULTS_FROM_SDK,
|
|
9
|
-
} from "./monitor-state-runtime-api.js";
|
|
10
|
-
|
|
11
|
-
export const wsClients = new Map<string, Lark.WSClient>();
|
|
12
|
-
export const httpServers = new Map<string, http.Server>();
|
|
13
|
-
export const botOpenIds = new Map<string, string>();
|
|
14
|
-
export const botNames = new Map<string, string>();
|
|
15
|
-
|
|
16
|
-
export const FEISHU_WEBHOOK_MAX_BODY_BYTES = 64 * 1024;
|
|
17
|
-
export const FEISHU_WEBHOOK_BODY_TIMEOUT_MS = 5_000;
|
|
18
|
-
|
|
19
|
-
type WebhookRateLimitDefaults = {
|
|
20
|
-
windowMs: number;
|
|
21
|
-
maxRequests: number;
|
|
22
|
-
maxTrackedKeys: number;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type WebhookAnomalyDefaults = {
|
|
26
|
-
maxTrackedKeys: number;
|
|
27
|
-
ttlMs: number;
|
|
28
|
-
logEvery: number;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const FEISHU_WEBHOOK_RATE_LIMIT_FALLBACK_DEFAULTS: WebhookRateLimitDefaults = {
|
|
32
|
-
windowMs: 60_000,
|
|
33
|
-
maxRequests: 120,
|
|
34
|
-
maxTrackedKeys: 4_096,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const FEISHU_WEBHOOK_ANOMALY_FALLBACK_DEFAULTS: WebhookAnomalyDefaults = {
|
|
38
|
-
maxTrackedKeys: 4_096,
|
|
39
|
-
ttlMs: 6 * 60 * 60_000,
|
|
40
|
-
logEvery: 25,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
function coercePositiveInt(value: unknown, fallback: number): number {
|
|
44
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
45
|
-
return fallback;
|
|
46
|
-
}
|
|
47
|
-
const normalized = Math.floor(value);
|
|
48
|
-
return normalized > 0 ? normalized : fallback;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function resolveFeishuWebhookRateLimitDefaultsForTest(
|
|
52
|
-
defaults: unknown,
|
|
53
|
-
): WebhookRateLimitDefaults {
|
|
54
|
-
const resolved = defaults as Partial<WebhookRateLimitDefaults> | null | undefined;
|
|
55
|
-
return {
|
|
56
|
-
windowMs: coercePositiveInt(
|
|
57
|
-
resolved?.windowMs,
|
|
58
|
-
FEISHU_WEBHOOK_RATE_LIMIT_FALLBACK_DEFAULTS.windowMs,
|
|
59
|
-
),
|
|
60
|
-
maxRequests: coercePositiveInt(
|
|
61
|
-
resolved?.maxRequests,
|
|
62
|
-
FEISHU_WEBHOOK_RATE_LIMIT_FALLBACK_DEFAULTS.maxRequests,
|
|
63
|
-
),
|
|
64
|
-
maxTrackedKeys: coercePositiveInt(
|
|
65
|
-
resolved?.maxTrackedKeys,
|
|
66
|
-
FEISHU_WEBHOOK_RATE_LIMIT_FALLBACK_DEFAULTS.maxTrackedKeys,
|
|
67
|
-
),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function resolveFeishuWebhookAnomalyDefaultsForTest(
|
|
72
|
-
defaults: unknown,
|
|
73
|
-
): WebhookAnomalyDefaults {
|
|
74
|
-
const resolved = defaults as Partial<WebhookAnomalyDefaults> | null | undefined;
|
|
75
|
-
return {
|
|
76
|
-
maxTrackedKeys: coercePositiveInt(
|
|
77
|
-
resolved?.maxTrackedKeys,
|
|
78
|
-
FEISHU_WEBHOOK_ANOMALY_FALLBACK_DEFAULTS.maxTrackedKeys,
|
|
79
|
-
),
|
|
80
|
-
ttlMs: coercePositiveInt(resolved?.ttlMs, FEISHU_WEBHOOK_ANOMALY_FALLBACK_DEFAULTS.ttlMs),
|
|
81
|
-
logEvery: coercePositiveInt(
|
|
82
|
-
resolved?.logEvery,
|
|
83
|
-
FEISHU_WEBHOOK_ANOMALY_FALLBACK_DEFAULTS.logEvery,
|
|
84
|
-
),
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const feishuWebhookRateLimitDefaults = resolveFeishuWebhookRateLimitDefaultsForTest(
|
|
89
|
-
WEBHOOK_RATE_LIMIT_DEFAULTS_FROM_SDK,
|
|
90
|
-
);
|
|
91
|
-
const feishuWebhookAnomalyDefaults = resolveFeishuWebhookAnomalyDefaultsForTest(
|
|
92
|
-
WEBHOOK_ANOMALY_COUNTER_DEFAULTS_FROM_SDK,
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
export const feishuWebhookRateLimiter = createFixedWindowRateLimiter({
|
|
96
|
-
windowMs: feishuWebhookRateLimitDefaults.windowMs,
|
|
97
|
-
maxRequests: feishuWebhookRateLimitDefaults.maxRequests,
|
|
98
|
-
maxTrackedKeys: feishuWebhookRateLimitDefaults.maxTrackedKeys,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const feishuWebhookAnomalyTracker = createWebhookAnomalyTracker({
|
|
102
|
-
maxTrackedKeys: feishuWebhookAnomalyDefaults.maxTrackedKeys,
|
|
103
|
-
ttlMs: feishuWebhookAnomalyDefaults.ttlMs,
|
|
104
|
-
logEvery: feishuWebhookAnomalyDefaults.logEvery,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
function closeWsClient(client: Lark.WSClient | undefined): void {
|
|
108
|
-
if (!client) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
client.close();
|
|
113
|
-
} catch {
|
|
114
|
-
/* Best-effort cleanup */
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function clearFeishuWebhookRateLimitStateForTest(): void {
|
|
119
|
-
feishuWebhookRateLimiter.clear();
|
|
120
|
-
feishuWebhookAnomalyTracker.clear();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function getFeishuWebhookRateLimitStateSizeForTest(): number {
|
|
124
|
-
return feishuWebhookRateLimiter.size();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function isWebhookRateLimitedForTest(key: string, nowMs: number): boolean {
|
|
128
|
-
return feishuWebhookRateLimiter.isRateLimited(key, nowMs);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function recordWebhookStatus(
|
|
132
|
-
runtime: RuntimeEnv | undefined,
|
|
133
|
-
accountId: string,
|
|
134
|
-
path: string,
|
|
135
|
-
statusCode: number,
|
|
136
|
-
): void {
|
|
137
|
-
feishuWebhookAnomalyTracker.record({
|
|
138
|
-
key: `${accountId}:${path}:${statusCode}`,
|
|
139
|
-
statusCode,
|
|
140
|
-
log: runtime?.log ?? console.log,
|
|
141
|
-
message: (count) =>
|
|
142
|
-
`feishu[${accountId}]: webhook anomaly path=${path} status=${statusCode} count=${count}`,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function stopFeishuMonitorState(accountId?: string): void {
|
|
147
|
-
if (accountId) {
|
|
148
|
-
closeWsClient(wsClients.get(accountId));
|
|
149
|
-
wsClients.delete(accountId);
|
|
150
|
-
const server = httpServers.get(accountId);
|
|
151
|
-
if (server) {
|
|
152
|
-
server.close();
|
|
153
|
-
httpServers.delete(accountId);
|
|
154
|
-
}
|
|
155
|
-
botOpenIds.delete(accountId);
|
|
156
|
-
botNames.delete(accountId);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
for (const client of wsClients.values()) {
|
|
161
|
-
closeWsClient(client);
|
|
162
|
-
}
|
|
163
|
-
wsClients.clear();
|
|
164
|
-
for (const server of httpServers.values()) {
|
|
165
|
-
server.close();
|
|
166
|
-
}
|
|
167
|
-
httpServers.clear();
|
|
168
|
-
botOpenIds.clear();
|
|
169
|
-
botNames.clear();
|
|
170
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export class FeishuRetryableSyntheticEventError extends Error {
|
|
2
|
-
constructor(message: string, options?: ErrorOptions) {
|
|
3
|
-
super(message, options);
|
|
4
|
-
this.name = "FeishuRetryableSyntheticEventError";
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function isFeishuRetryableSyntheticEventError(
|
|
9
|
-
error: unknown,
|
|
10
|
-
): error is FeishuRetryableSyntheticEventError {
|
|
11
|
-
return (
|
|
12
|
-
error instanceof FeishuRetryableSyntheticEventError ||
|
|
13
|
-
(typeof error === "object" &&
|
|
14
|
-
error !== null &&
|
|
15
|
-
"name" in error &&
|
|
16
|
-
error.name === "FeishuRetryableSyntheticEventError")
|
|
17
|
-
);
|
|
18
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
export function createFeishuClientMockModule(): {
|
|
4
|
-
createFeishuWSClient: () => { start: () => void; close: () => void };
|
|
5
|
-
createEventDispatcher: () => { register: () => void };
|
|
6
|
-
} {
|
|
7
|
-
return {
|
|
8
|
-
createFeishuWSClient: vi.fn(() => ({ start: vi.fn(), close: vi.fn() })),
|
|
9
|
-
createEventDispatcher: vi.fn(() => ({ register: vi.fn() })),
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function createFeishuRuntimeMockModule(): {
|
|
14
|
-
getFeishuRuntime: () => {
|
|
15
|
-
channel: {
|
|
16
|
-
debounce: {
|
|
17
|
-
resolveInboundDebounceMs: () => number;
|
|
18
|
-
createInboundDebouncer: () => {
|
|
19
|
-
enqueue: () => Promise<void>;
|
|
20
|
-
flushKey: () => Promise<void>;
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
text: {
|
|
24
|
-
hasControlCommand: () => boolean;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
} {
|
|
29
|
-
return {
|
|
30
|
-
getFeishuRuntime: () => ({
|
|
31
|
-
channel: {
|
|
32
|
-
debounce: {
|
|
33
|
-
resolveInboundDebounceMs: () => 0,
|
|
34
|
-
createInboundDebouncer: () => ({
|
|
35
|
-
enqueue: async () => {},
|
|
36
|
-
flushKey: async () => {},
|
|
37
|
-
}),
|
|
38
|
-
},
|
|
39
|
-
text: {
|
|
40
|
-
hasControlCommand: () => false,
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
}),
|
|
44
|
-
};
|
|
45
|
-
}
|