@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
package/src/monitor.transport.ts
DELETED
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import * as http from "node:http";
|
|
3
|
-
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
4
|
-
import { waitForAbortableDelay } from "./async.js";
|
|
5
|
-
import { createFeishuWSClient } from "./client.js";
|
|
6
|
-
import {
|
|
7
|
-
applyBasicWebhookRequestGuards,
|
|
8
|
-
type RuntimeEnv,
|
|
9
|
-
installRequestBodyLimitGuard,
|
|
10
|
-
readWebhookBodyOrReject,
|
|
11
|
-
safeEqualSecret,
|
|
12
|
-
} from "./monitor-transport-runtime-api.js";
|
|
13
|
-
import {
|
|
14
|
-
botNames,
|
|
15
|
-
botOpenIds,
|
|
16
|
-
FEISHU_WEBHOOK_BODY_TIMEOUT_MS,
|
|
17
|
-
FEISHU_WEBHOOK_MAX_BODY_BYTES,
|
|
18
|
-
feishuWebhookRateLimiter,
|
|
19
|
-
httpServers,
|
|
20
|
-
recordWebhookStatus,
|
|
21
|
-
wsClients,
|
|
22
|
-
} from "./monitor.state.js";
|
|
23
|
-
import type { ResolvedFeishuAccount } from "./types.js";
|
|
24
|
-
|
|
25
|
-
type MonitorTransportParams = {
|
|
26
|
-
account: ResolvedFeishuAccount;
|
|
27
|
-
accountId: string;
|
|
28
|
-
runtime?: RuntimeEnv;
|
|
29
|
-
abortSignal?: AbortSignal;
|
|
30
|
-
eventDispatcher: Lark.EventDispatcher;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const FEISHU_WS_RECONNECT_INITIAL_DELAY_MS = 1_000;
|
|
34
|
-
const FEISHU_WS_RECONNECT_MAX_DELAY_MS = 30_000;
|
|
35
|
-
const FEISHU_WS_LOG_ERROR_MAX_LENGTH = 500;
|
|
36
|
-
const FEISHU_WS_RECONNECT_EXHAUSTED_RE = /^WebSocket reconnect exhausted after \d+ attempts?/;
|
|
37
|
-
const FEISHU_WS_AUTORECONNECT_DISABLED_ERROR =
|
|
38
|
-
"WebSocket connect failed and autoReconnect is disabled";
|
|
39
|
-
|
|
40
|
-
function isFeishuWebhookPayload(value: unknown): value is Record<string, unknown> {
|
|
41
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function buildFeishuWebhookEnvelope(
|
|
45
|
-
req: http.IncomingMessage,
|
|
46
|
-
payload: Record<string, unknown>,
|
|
47
|
-
): Record<string, unknown> {
|
|
48
|
-
return Object.assign(Object.create({ headers: req.headers }), payload) as Record<string, unknown>;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function parseFeishuWebhookPayload(rawBody: string): Record<string, unknown> | null {
|
|
52
|
-
try {
|
|
53
|
-
const parsed = JSON.parse(rawBody) as unknown;
|
|
54
|
-
return isFeishuWebhookPayload(parsed) ? parsed : null;
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function isFeishuWebhookSignatureValid(params: {
|
|
61
|
-
headers: http.IncomingHttpHeaders;
|
|
62
|
-
rawBody: string;
|
|
63
|
-
encryptKey?: string;
|
|
64
|
-
}): boolean {
|
|
65
|
-
const encryptKey = params.encryptKey?.trim();
|
|
66
|
-
if (!encryptKey) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const timestampHeader = params.headers["x-lark-request-timestamp"];
|
|
71
|
-
const nonceHeader = params.headers["x-lark-request-nonce"];
|
|
72
|
-
const signatureHeader = params.headers["x-lark-signature"];
|
|
73
|
-
const timestamp = Array.isArray(timestampHeader) ? timestampHeader[0] : timestampHeader;
|
|
74
|
-
const nonce = Array.isArray(nonceHeader) ? nonceHeader[0] : nonceHeader;
|
|
75
|
-
const signature = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
|
|
76
|
-
if (!timestamp || !nonce || !signature) {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const computedSignature = crypto
|
|
81
|
-
.createHash("sha256")
|
|
82
|
-
.update(timestamp + nonce + encryptKey + params.rawBody)
|
|
83
|
-
.digest("hex");
|
|
84
|
-
return safeEqualSecret(computedSignature, signature);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function respondText(res: http.ServerResponse, statusCode: number, body: string): void {
|
|
88
|
-
res.statusCode = statusCode;
|
|
89
|
-
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
90
|
-
res.end(body);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function getFeishuWsReconnectDelayMs(attempt: number): number {
|
|
94
|
-
return Math.min(
|
|
95
|
-
FEISHU_WS_RECONNECT_INITIAL_DELAY_MS * 2 ** Math.max(0, attempt - 1),
|
|
96
|
-
FEISHU_WS_RECONNECT_MAX_DELAY_MS,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function formatFeishuWsErrorForLog(err: unknown): string {
|
|
101
|
-
const raw = err instanceof Error ? err.message || err.name : String(err);
|
|
102
|
-
const singleLine = Array.from(raw, (char) => {
|
|
103
|
-
const code = char.charCodeAt(0);
|
|
104
|
-
return code <= 31 || code === 127 ? " " : char;
|
|
105
|
-
}).join("");
|
|
106
|
-
const redacted = singleLine
|
|
107
|
-
.replace(/:\/\/[^:@/\s]+:[^@/\s]+@/g, "://[redacted]@")
|
|
108
|
-
.replace(/\b(authorization\s*[:=]\s*Bearer\s+)[^\s,;]+/gi, "$1[redacted]")
|
|
109
|
-
.replace(/\b(Bearer\s+)[A-Za-z0-9._~+/-]+=*/g, "$1[redacted]")
|
|
110
|
-
.replace(
|
|
111
|
-
/\b((?:app[_-]?secret|tenant[_-]?access[_-]?token|access[_-]?token|refresh[_-]?token|token|secret|password)\s*[:=]\s*)[^\s&;,]+/gi,
|
|
112
|
-
"$1[redacted]",
|
|
113
|
-
)
|
|
114
|
-
.replace(/\s+/g, " ")
|
|
115
|
-
.trim();
|
|
116
|
-
|
|
117
|
-
if (!redacted) {
|
|
118
|
-
return "unknown error";
|
|
119
|
-
}
|
|
120
|
-
if (redacted.length <= FEISHU_WS_LOG_ERROR_MAX_LENGTH) {
|
|
121
|
-
return redacted;
|
|
122
|
-
}
|
|
123
|
-
return `${redacted.slice(0, FEISHU_WS_LOG_ERROR_MAX_LENGTH)}...`;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function isFeishuWsTerminalError(err: Error): boolean {
|
|
127
|
-
const message = err.message.trim();
|
|
128
|
-
return (
|
|
129
|
-
FEISHU_WS_RECONNECT_EXHAUSTED_RE.test(message) ||
|
|
130
|
-
message.startsWith(FEISHU_WS_AUTORECONNECT_DISABLED_ERROR)
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function cleanupFeishuWsClient(params: {
|
|
135
|
-
accountId: string;
|
|
136
|
-
wsClient?: Lark.WSClient;
|
|
137
|
-
error: (message: string) => void;
|
|
138
|
-
clearIdentity: boolean;
|
|
139
|
-
}): void {
|
|
140
|
-
const { accountId, wsClient, error, clearIdentity } = params;
|
|
141
|
-
if (wsClient) {
|
|
142
|
-
try {
|
|
143
|
-
wsClient.close();
|
|
144
|
-
} catch (err) {
|
|
145
|
-
error(
|
|
146
|
-
`feishu[${accountId}]: error closing WebSocket client: ${formatFeishuWsErrorForLog(err)}`,
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
wsClients.delete(accountId);
|
|
151
|
-
if (clearIdentity) {
|
|
152
|
-
botOpenIds.delete(accountId);
|
|
153
|
-
botNames.delete(accountId);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function waitForFeishuWsCycleEnd(params: {
|
|
158
|
-
abortSignal?: AbortSignal;
|
|
159
|
-
terminalError: Promise<Error>;
|
|
160
|
-
}): Promise<"abort" | Error> {
|
|
161
|
-
if (params.abortSignal?.aborted) {
|
|
162
|
-
return Promise.resolve("abort");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return new Promise((resolve) => {
|
|
166
|
-
let settled = false;
|
|
167
|
-
let handleAbort: (() => void) | undefined;
|
|
168
|
-
|
|
169
|
-
const finish = (result: "abort" | Error) => {
|
|
170
|
-
if (settled) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
settled = true;
|
|
174
|
-
if (handleAbort) {
|
|
175
|
-
params.abortSignal?.removeEventListener("abort", handleAbort);
|
|
176
|
-
}
|
|
177
|
-
resolve(result);
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
handleAbort = () => finish("abort");
|
|
181
|
-
params.abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
|
182
|
-
if (params.abortSignal?.aborted) {
|
|
183
|
-
finish("abort");
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
void params.terminalError.then(finish);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export async function monitorWebSocket({
|
|
192
|
-
account,
|
|
193
|
-
accountId,
|
|
194
|
-
runtime,
|
|
195
|
-
abortSignal,
|
|
196
|
-
eventDispatcher,
|
|
197
|
-
}: MonitorTransportParams): Promise<void> {
|
|
198
|
-
const log = runtime?.log ?? console.log;
|
|
199
|
-
const error = runtime?.error ?? console.error;
|
|
200
|
-
|
|
201
|
-
let attempt = 0;
|
|
202
|
-
while (true) {
|
|
203
|
-
if (abortSignal?.aborted) {
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
let wsClient: Lark.WSClient | undefined;
|
|
208
|
-
try {
|
|
209
|
-
let reportTerminalError: (err: Error) => void = () => {};
|
|
210
|
-
const terminalError = new Promise<Error>((resolve) => {
|
|
211
|
-
reportTerminalError = resolve;
|
|
212
|
-
});
|
|
213
|
-
const handleWsError = (err: Error) => {
|
|
214
|
-
if (isFeishuWsTerminalError(err)) {
|
|
215
|
-
reportTerminalError(err);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
error(
|
|
220
|
-
`feishu[${accountId}]: WebSocket SDK reported recoverable error: ${formatFeishuWsErrorForLog(err)}`,
|
|
221
|
-
);
|
|
222
|
-
};
|
|
223
|
-
log(`feishu[${accountId}]: starting WebSocket connection...`);
|
|
224
|
-
wsClient = await createFeishuWSClient(account, {
|
|
225
|
-
onError: handleWsError,
|
|
226
|
-
});
|
|
227
|
-
if (abortSignal?.aborted) {
|
|
228
|
-
cleanupFeishuWsClient({ accountId, wsClient, error, clearIdentity: true });
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
wsClients.set(accountId, wsClient);
|
|
232
|
-
await wsClient.start({ eventDispatcher });
|
|
233
|
-
attempt = 0;
|
|
234
|
-
log(`feishu[${accountId}]: WebSocket client started`);
|
|
235
|
-
const cycleEnd = await waitForFeishuWsCycleEnd({ abortSignal, terminalError });
|
|
236
|
-
if (cycleEnd === "abort") {
|
|
237
|
-
log(`feishu[${accountId}]: abort signal received, stopping`);
|
|
238
|
-
cleanupFeishuWsClient({ accountId, wsClient, error, clearIdentity: true });
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
cleanupFeishuWsClient({ accountId, wsClient, error, clearIdentity: false });
|
|
243
|
-
if (abortSignal?.aborted) {
|
|
244
|
-
break;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
attempt += 1;
|
|
248
|
-
const delayMs = getFeishuWsReconnectDelayMs(attempt);
|
|
249
|
-
error(
|
|
250
|
-
`feishu[${accountId}]: WebSocket connection ended, recreating client in ${delayMs}ms: ${formatFeishuWsErrorForLog(cycleEnd)}`,
|
|
251
|
-
);
|
|
252
|
-
const shouldRetry = await waitForAbortableDelay(delayMs, abortSignal);
|
|
253
|
-
if (!shouldRetry) {
|
|
254
|
-
break;
|
|
255
|
-
}
|
|
256
|
-
} catch (err) {
|
|
257
|
-
cleanupFeishuWsClient({ accountId, wsClient, error, clearIdentity: false });
|
|
258
|
-
if (abortSignal?.aborted) {
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
attempt += 1;
|
|
263
|
-
const delayMs = getFeishuWsReconnectDelayMs(attempt);
|
|
264
|
-
error(
|
|
265
|
-
`feishu[${accountId}]: WebSocket start failed, retrying in ${delayMs}ms: ${formatFeishuWsErrorForLog(err)}`,
|
|
266
|
-
);
|
|
267
|
-
const shouldRetry = await waitForAbortableDelay(delayMs, abortSignal);
|
|
268
|
-
if (!shouldRetry) {
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
cleanupFeishuWsClient({ accountId, wsClient: undefined, error, clearIdentity: true });
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export async function monitorWebhook({
|
|
277
|
-
account,
|
|
278
|
-
accountId,
|
|
279
|
-
runtime,
|
|
280
|
-
abortSignal,
|
|
281
|
-
eventDispatcher,
|
|
282
|
-
}: MonitorTransportParams): Promise<void> {
|
|
283
|
-
const log = runtime?.log ?? console.log;
|
|
284
|
-
const error = runtime?.error ?? console.error;
|
|
285
|
-
const encryptKey = account.encryptKey?.trim();
|
|
286
|
-
if (!encryptKey) {
|
|
287
|
-
throw new Error(`Feishu account "${accountId}" webhook mode requires encryptKey`);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const port = account.config.webhookPort ?? 3000;
|
|
291
|
-
const path = account.config.webhookPath ?? "/feishu/events";
|
|
292
|
-
const host = account.config.webhookHost ?? "127.0.0.1";
|
|
293
|
-
|
|
294
|
-
log(`feishu[${accountId}]: starting Webhook server on ${host}:${port}, path ${path}...`);
|
|
295
|
-
|
|
296
|
-
const server = http.createServer();
|
|
297
|
-
|
|
298
|
-
server.on("request", (req, res) => {
|
|
299
|
-
res.on("finish", () => {
|
|
300
|
-
recordWebhookStatus(runtime, accountId, path, res.statusCode);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
const rateLimitKey = `${accountId}:${path}:${req.socket.remoteAddress ?? "unknown"}`;
|
|
304
|
-
if (
|
|
305
|
-
!applyBasicWebhookRequestGuards({
|
|
306
|
-
req,
|
|
307
|
-
res,
|
|
308
|
-
rateLimiter: feishuWebhookRateLimiter,
|
|
309
|
-
rateLimitKey,
|
|
310
|
-
nowMs: Date.now(),
|
|
311
|
-
requireJsonContentType: true,
|
|
312
|
-
})
|
|
313
|
-
) {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const guard = installRequestBodyLimitGuard(req, res, {
|
|
318
|
-
maxBytes: FEISHU_WEBHOOK_MAX_BODY_BYTES,
|
|
319
|
-
timeoutMs: FEISHU_WEBHOOK_BODY_TIMEOUT_MS,
|
|
320
|
-
responseFormat: "text",
|
|
321
|
-
});
|
|
322
|
-
if (guard.isTripped()) {
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
void (async () => {
|
|
327
|
-
try {
|
|
328
|
-
const body = await readWebhookBodyOrReject({
|
|
329
|
-
req,
|
|
330
|
-
res,
|
|
331
|
-
maxBytes: FEISHU_WEBHOOK_MAX_BODY_BYTES,
|
|
332
|
-
timeoutMs: FEISHU_WEBHOOK_BODY_TIMEOUT_MS,
|
|
333
|
-
profile: "pre-auth",
|
|
334
|
-
});
|
|
335
|
-
if (!body.ok || res.writableEnded) {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
if (guard.isTripped()) {
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
const rawBody = body.value;
|
|
342
|
-
|
|
343
|
-
// Reject invalid signatures before any JSON parsing to keep the auth boundary strict.
|
|
344
|
-
if (
|
|
345
|
-
!isFeishuWebhookSignatureValid({
|
|
346
|
-
headers: req.headers,
|
|
347
|
-
rawBody,
|
|
348
|
-
encryptKey,
|
|
349
|
-
})
|
|
350
|
-
) {
|
|
351
|
-
respondText(res, 401, "Invalid signature");
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const payload = parseFeishuWebhookPayload(rawBody);
|
|
356
|
-
if (!payload) {
|
|
357
|
-
respondText(res, 400, "Invalid JSON");
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const { isChallenge, challenge } = Lark.generateChallenge(payload, {
|
|
362
|
-
encryptKey,
|
|
363
|
-
});
|
|
364
|
-
if (isChallenge) {
|
|
365
|
-
res.statusCode = 200;
|
|
366
|
-
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
367
|
-
res.end(JSON.stringify(challenge));
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const value = await eventDispatcher.invoke(buildFeishuWebhookEnvelope(req, payload), {
|
|
372
|
-
needCheck: false,
|
|
373
|
-
});
|
|
374
|
-
if (!res.headersSent) {
|
|
375
|
-
res.statusCode = 200;
|
|
376
|
-
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
377
|
-
res.end(JSON.stringify(value));
|
|
378
|
-
}
|
|
379
|
-
} catch (err) {
|
|
380
|
-
error(`feishu[${accountId}]: webhook handler error: ${String(err)}`);
|
|
381
|
-
if (!res.headersSent) {
|
|
382
|
-
respondText(res, 500, "Internal Server Error");
|
|
383
|
-
}
|
|
384
|
-
} finally {
|
|
385
|
-
guard.dispose();
|
|
386
|
-
}
|
|
387
|
-
})();
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
httpServers.set(accountId, server);
|
|
391
|
-
|
|
392
|
-
return new Promise((resolve, reject) => {
|
|
393
|
-
const cleanup = () => {
|
|
394
|
-
server.close();
|
|
395
|
-
httpServers.delete(accountId);
|
|
396
|
-
botOpenIds.delete(accountId);
|
|
397
|
-
botNames.delete(accountId);
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
const handleAbort = () => {
|
|
401
|
-
log(`feishu[${accountId}]: abort signal received, stopping Webhook server`);
|
|
402
|
-
cleanup();
|
|
403
|
-
resolve();
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
if (abortSignal?.aborted) {
|
|
407
|
-
cleanup();
|
|
408
|
-
resolve();
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
|
413
|
-
|
|
414
|
-
server.listen(port, host, () => {
|
|
415
|
-
log(`feishu[${accountId}]: Webhook server listening on ${host}:${port}`);
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
server.on("error", (err) => {
|
|
419
|
-
error(`feishu[${accountId}]: Webhook server error: ${err}`);
|
|
420
|
-
abortSignal?.removeEventListener("abort", handleAbort);
|
|
421
|
-
reject(err);
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
}
|
package/src/monitor.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
|
2
|
-
import { listEnabledFeishuAccounts, resolveFeishuRuntimeAccount } from "./accounts.js";
|
|
3
|
-
import { fetchBotIdentityForMonitor } from "./monitor.startup.js";
|
|
4
|
-
import {
|
|
5
|
-
clearFeishuWebhookRateLimitStateForTest,
|
|
6
|
-
getFeishuWebhookRateLimitStateSizeForTest,
|
|
7
|
-
isWebhookRateLimitedForTest,
|
|
8
|
-
stopFeishuMonitorState,
|
|
9
|
-
} from "./monitor.state.js";
|
|
10
|
-
|
|
11
|
-
export type MonitorFeishuOpts = {
|
|
12
|
-
config?: ClawdbotConfig;
|
|
13
|
-
runtime?: RuntimeEnv;
|
|
14
|
-
abortSignal?: AbortSignal;
|
|
15
|
-
accountId?: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
let monitorAccountRuntimePromise: Promise<typeof import("./monitor.account.js")> | undefined;
|
|
19
|
-
|
|
20
|
-
async function loadMonitorAccountRuntime() {
|
|
21
|
-
monitorAccountRuntimePromise ??= import("./monitor.account.js");
|
|
22
|
-
return await monitorAccountRuntimePromise;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export {
|
|
26
|
-
clearFeishuWebhookRateLimitStateForTest,
|
|
27
|
-
getFeishuWebhookRateLimitStateSizeForTest,
|
|
28
|
-
isWebhookRateLimitedForTest,
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promise<void> {
|
|
32
|
-
const cfg = opts.config;
|
|
33
|
-
if (!cfg) {
|
|
34
|
-
throw new Error("Config is required for Feishu monitor");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const log = opts.runtime?.log ?? console.log;
|
|
38
|
-
|
|
39
|
-
if (opts.accountId) {
|
|
40
|
-
const account = resolveFeishuRuntimeAccount(
|
|
41
|
-
{ cfg, accountId: opts.accountId },
|
|
42
|
-
{ requireEventSecrets: true },
|
|
43
|
-
);
|
|
44
|
-
if (!account.enabled || !account.configured) {
|
|
45
|
-
throw new Error(`Feishu account "${opts.accountId}" not configured or disabled`);
|
|
46
|
-
}
|
|
47
|
-
const { monitorSingleAccount } = await loadMonitorAccountRuntime();
|
|
48
|
-
return monitorSingleAccount({
|
|
49
|
-
cfg,
|
|
50
|
-
account,
|
|
51
|
-
runtime: opts.runtime,
|
|
52
|
-
abortSignal: opts.abortSignal,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const accounts = listEnabledFeishuAccounts(cfg);
|
|
57
|
-
if (accounts.length === 0) {
|
|
58
|
-
throw new Error("No enabled Feishu accounts configured");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
log(
|
|
62
|
-
`feishu: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(", ")}`,
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
const { monitorSingleAccount } = await loadMonitorAccountRuntime();
|
|
66
|
-
const monitorPromises: Promise<void>[] = [];
|
|
67
|
-
for (const account of accounts) {
|
|
68
|
-
if (opts.abortSignal?.aborted) {
|
|
69
|
-
log("feishu: abort signal received during startup preflight; stopping startup");
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Probe sequentially so large multi-account startups do not burst Feishu's bot-info endpoint.
|
|
74
|
-
const { botOpenId, botName } = await fetchBotIdentityForMonitor(account, {
|
|
75
|
-
runtime: opts.runtime,
|
|
76
|
-
abortSignal: opts.abortSignal,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
if (opts.abortSignal?.aborted) {
|
|
80
|
-
log("feishu: abort signal received during startup preflight; stopping startup");
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
monitorPromises.push(
|
|
85
|
-
monitorSingleAccount({
|
|
86
|
-
cfg,
|
|
87
|
-
account,
|
|
88
|
-
runtime: opts.runtime,
|
|
89
|
-
abortSignal: opts.abortSignal,
|
|
90
|
-
botOpenIdSource: { kind: "prefetched", botOpenId, botName },
|
|
91
|
-
}),
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
await Promise.all(monitorPromises);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function stopFeishuMonitor(accountId?: string): void {
|
|
99
|
-
stopFeishuMonitorState(accountId);
|
|
100
|
-
}
|