@nextclaw/channel-plugin-feishu 0.2.29-beta.0 → 0.2.29-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/index.d.ts +23 -0
- package/dist/index.js +45 -0
- package/dist/src/accounts.js +141 -0
- package/dist/src/app-scope-checker.js +36 -0
- package/dist/src/async.js +34 -0
- package/dist/src/auth-errors.js +72 -0
- package/dist/src/bitable.js +495 -0
- package/dist/src/bot.d.ts +35 -0
- package/dist/src/bot.js +941 -0
- package/dist/src/calendar-calendar.js +54 -0
- package/dist/src/calendar-event-attendee.js +98 -0
- package/dist/src/calendar-event.js +193 -0
- package/dist/src/calendar-freebusy.js +40 -0
- package/dist/src/calendar-shared.js +23 -0
- package/dist/src/calendar.js +16 -0
- package/dist/src/card-action.js +49 -0
- package/dist/src/channel.d.ts +7 -0
- package/dist/src/channel.js +413 -0
- package/dist/src/chat-schema.js +25 -0
- package/dist/src/chat.js +87 -0
- package/dist/src/client.d.ts +16 -0
- package/dist/src/client.js +112 -0
- package/dist/src/config-schema.d.ts +357 -0
- package/dist/src/dedup.js +126 -0
- package/dist/src/device-flow.js +109 -0
- package/dist/src/directory.js +101 -0
- package/dist/src/doc-schema.js +148 -0
- package/dist/src/docx-batch-insert.js +104 -0
- package/dist/src/docx-color-text.js +80 -0
- package/dist/src/docx-table-ops.js +197 -0
- package/dist/src/docx.js +858 -0
- package/dist/src/domains.js +14 -0
- package/dist/src/drive-schema.js +41 -0
- package/dist/src/drive.js +126 -0
- package/dist/src/dynamic-agent.js +93 -0
- package/dist/src/external-keys.js +13 -0
- package/dist/src/feishu-fetch.js +12 -0
- package/dist/src/identity.js +92 -0
- package/dist/src/lark-ticket.js +11 -0
- package/dist/src/media.d.ts +75 -0
- package/dist/src/media.js +304 -0
- package/dist/src/mention.d.ts +52 -0
- package/dist/src/mention.js +82 -0
- package/dist/src/monitor.account.d.ts +1 -0
- package/dist/src/monitor.account.js +393 -0
- package/dist/src/monitor.d.ts +11 -0
- package/dist/src/monitor.js +58 -0
- package/dist/src/monitor.startup.js +24 -0
- package/dist/src/monitor.state.d.ts +1 -0
- package/dist/src/monitor.state.js +80 -0
- package/dist/src/monitor.transport.js +167 -0
- package/dist/src/nextclaw-sdk/account-id.js +15 -0
- package/dist/src/nextclaw-sdk/core-channel.js +150 -0
- package/dist/src/nextclaw-sdk/core-pairing.js +151 -0
- package/dist/src/nextclaw-sdk/dedupe.js +164 -0
- package/dist/src/nextclaw-sdk/feishu.d.ts +1 -0
- package/dist/src/nextclaw-sdk/feishu.js +14 -0
- package/dist/src/nextclaw-sdk/history.js +69 -0
- package/dist/src/nextclaw-sdk/network-body.js +180 -0
- package/dist/src/nextclaw-sdk/network-fetch.js +63 -0
- package/dist/src/nextclaw-sdk/network-webhook.js +126 -0
- package/dist/src/nextclaw-sdk/network.js +4 -0
- package/dist/src/nextclaw-sdk/runtime-store.js +21 -0
- package/dist/src/nextclaw-sdk/secrets-config.js +65 -0
- package/dist/src/nextclaw-sdk/secrets-core.d.ts +1 -0
- package/dist/src/nextclaw-sdk/secrets-core.js +68 -0
- package/dist/src/nextclaw-sdk/secrets-prompt.js +193 -0
- package/dist/src/nextclaw-sdk/secrets.d.ts +1 -0
- package/dist/src/nextclaw-sdk/secrets.js +4 -0
- package/dist/src/nextclaw-sdk/types.d.ts +242 -0
- package/dist/src/oauth.js +171 -0
- package/dist/src/onboarding.js +381 -0
- package/dist/src/outbound.js +150 -0
- package/dist/src/perm-schema.js +49 -0
- package/dist/src/perm.js +90 -0
- package/dist/src/policy.js +61 -0
- package/dist/src/post.js +160 -0
- package/dist/src/probe.d.ts +11 -0
- package/dist/src/probe.js +85 -0
- package/dist/src/raw-request.js +24 -0
- package/dist/src/reactions.d.ts +67 -0
- package/dist/src/reactions.js +91 -0
- package/dist/src/reply-dispatcher.js +250 -0
- package/dist/src/runtime.js +5 -0
- package/dist/src/secret-input.js +3 -0
- package/dist/src/send-result.js +12 -0
- package/dist/src/send-target.js +22 -0
- package/dist/src/send.d.ts +51 -0
- package/dist/src/send.js +265 -0
- package/dist/src/sheets-shared.js +193 -0
- package/dist/src/sheets.js +95 -0
- package/dist/src/streaming-card.js +263 -0
- package/dist/src/targets.js +39 -0
- package/dist/src/task-comment.js +76 -0
- package/dist/src/task-shared.js +13 -0
- package/dist/src/task-subtask.js +79 -0
- package/dist/src/task-task.js +144 -0
- package/dist/src/task-tasklist.js +136 -0
- package/dist/src/task.js +16 -0
- package/dist/src/token-store.js +154 -0
- package/dist/src/tool-account.js +65 -0
- package/dist/src/tool-result.js +18 -0
- package/dist/src/tool-scopes.js +62 -0
- package/dist/src/tools-config.js +30 -0
- package/dist/src/types.d.ts +43 -0
- package/dist/src/typing.js +145 -0
- package/dist/src/uat-client.js +102 -0
- package/dist/src/user-tool-client.js +132 -0
- package/dist/src/user-tool-helpers.js +110 -0
- package/dist/src/user-tool-result.js +10 -0
- package/dist/src/wiki-schema.js +45 -0
- package/dist/src/wiki.js +144 -0
- package/package.json +8 -4
- package/index.ts +0 -75
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID } from "./nextclaw-sdk/account-id.js";
|
|
2
|
+
import { buildProbeChannelStatusSummary, buildRuntimeAccountStatusSnapshot, collectAllowlistProviderRestrictSendersWarnings, createDefaultChannelRuntimeState, formatAllowFromLowercase, mapAllowFromEntries } from "./nextclaw-sdk/core-channel.js";
|
|
3
|
+
import { PAIRING_APPROVED_MESSAGE } from "./nextclaw-sdk/core-pairing.js";
|
|
4
|
+
import "./nextclaw-sdk/feishu.js";
|
|
5
|
+
import { listFeishuAccountIds, resolveDefaultFeishuAccountId, resolveFeishuAccount } from "./accounts.js";
|
|
6
|
+
import { looksLikeFeishuId, normalizeFeishuTarget } from "./targets.js";
|
|
7
|
+
import { listFeishuDirectoryGroups, listFeishuDirectoryGroupsLive, listFeishuDirectoryPeers, listFeishuDirectoryPeersLive } from "./directory.js";
|
|
8
|
+
import { probeFeishu } from "./probe.js";
|
|
9
|
+
import { feishuOnboardingAdapter } from "./onboarding.js";
|
|
10
|
+
import { sendMessageFeishu } from "./send.js";
|
|
11
|
+
import { feishuOutbound } from "./outbound.js";
|
|
12
|
+
import { resolveFeishuGroupToolPolicy } from "./policy.js";
|
|
13
|
+
//#region src/channel.ts
|
|
14
|
+
const meta = {
|
|
15
|
+
id: "feishu",
|
|
16
|
+
label: "Feishu",
|
|
17
|
+
selectionLabel: "Feishu/Lark (飞书)",
|
|
18
|
+
docsPath: "/channels/feishu",
|
|
19
|
+
docsLabel: "feishu",
|
|
20
|
+
blurb: "飞书/Lark enterprise messaging.",
|
|
21
|
+
aliases: ["lark"],
|
|
22
|
+
order: 70
|
|
23
|
+
};
|
|
24
|
+
const secretInputJsonSchema = { oneOf: [{ type: "string" }, {
|
|
25
|
+
type: "object",
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
required: [
|
|
28
|
+
"source",
|
|
29
|
+
"provider",
|
|
30
|
+
"id"
|
|
31
|
+
],
|
|
32
|
+
properties: {
|
|
33
|
+
source: {
|
|
34
|
+
type: "string",
|
|
35
|
+
enum: [
|
|
36
|
+
"env",
|
|
37
|
+
"file",
|
|
38
|
+
"exec"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
provider: {
|
|
42
|
+
type: "string",
|
|
43
|
+
minLength: 1
|
|
44
|
+
},
|
|
45
|
+
id: {
|
|
46
|
+
type: "string",
|
|
47
|
+
minLength: 1
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}] };
|
|
51
|
+
function setFeishuNamedAccountEnabled(cfg, accountId, enabled) {
|
|
52
|
+
const feishuCfg = cfg.channels?.feishu;
|
|
53
|
+
return {
|
|
54
|
+
...cfg,
|
|
55
|
+
channels: {
|
|
56
|
+
...cfg.channels,
|
|
57
|
+
feishu: {
|
|
58
|
+
...feishuCfg,
|
|
59
|
+
accounts: {
|
|
60
|
+
...feishuCfg?.accounts,
|
|
61
|
+
[accountId]: {
|
|
62
|
+
...feishuCfg?.accounts?.[accountId],
|
|
63
|
+
enabled
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const feishuPlugin = {
|
|
71
|
+
id: "feishu",
|
|
72
|
+
meta: { ...meta },
|
|
73
|
+
pairing: {
|
|
74
|
+
idLabel: "feishuUserId",
|
|
75
|
+
normalizeAllowEntry: (entry) => entry.replace(/^(feishu|user|open_id):/i, ""),
|
|
76
|
+
notifyApproval: async ({ cfg, id }) => {
|
|
77
|
+
await sendMessageFeishu({
|
|
78
|
+
cfg,
|
|
79
|
+
to: id,
|
|
80
|
+
text: PAIRING_APPROVED_MESSAGE
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
capabilities: {
|
|
85
|
+
chatTypes: ["direct", "channel"],
|
|
86
|
+
polls: false,
|
|
87
|
+
threads: true,
|
|
88
|
+
media: true,
|
|
89
|
+
reactions: true,
|
|
90
|
+
edit: true,
|
|
91
|
+
reply: true
|
|
92
|
+
},
|
|
93
|
+
agentPrompt: { messageToolHints: () => [
|
|
94
|
+
"- Feishu targeting: omit `target` only when replying in the current Feishu conversation. For proactive sends from UI/CLI/another channel, pass an explicit target such as `user:open_id` or `chat:chat_id`.",
|
|
95
|
+
"- If the current session is not Feishu, never rely on `channel=feishu` alone; resolve the route first (for example from an existing Feishu session) and then send to that explicit target.",
|
|
96
|
+
"- Feishu supports interactive cards for rich messages."
|
|
97
|
+
] },
|
|
98
|
+
groups: { resolveToolPolicy: resolveFeishuGroupToolPolicy },
|
|
99
|
+
mentions: { stripPatterns: () => ["<at user_id=\"[^\"]*\">[^<]*</at>"] },
|
|
100
|
+
reload: { configPrefixes: ["channels.feishu"] },
|
|
101
|
+
configSchema: { schema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
properties: {
|
|
105
|
+
enabled: { type: "boolean" },
|
|
106
|
+
defaultAccount: { type: "string" },
|
|
107
|
+
appId: { type: "string" },
|
|
108
|
+
appSecret: secretInputJsonSchema,
|
|
109
|
+
encryptKey: secretInputJsonSchema,
|
|
110
|
+
verificationToken: secretInputJsonSchema,
|
|
111
|
+
domain: { oneOf: [{
|
|
112
|
+
type: "string",
|
|
113
|
+
enum: ["feishu", "lark"]
|
|
114
|
+
}, {
|
|
115
|
+
type: "string",
|
|
116
|
+
format: "uri",
|
|
117
|
+
pattern: "^https://"
|
|
118
|
+
}] },
|
|
119
|
+
connectionMode: {
|
|
120
|
+
type: "string",
|
|
121
|
+
enum: ["websocket", "webhook"]
|
|
122
|
+
},
|
|
123
|
+
webhookPath: { type: "string" },
|
|
124
|
+
webhookHost: { type: "string" },
|
|
125
|
+
webhookPort: {
|
|
126
|
+
type: "integer",
|
|
127
|
+
minimum: 1
|
|
128
|
+
},
|
|
129
|
+
dmPolicy: {
|
|
130
|
+
type: "string",
|
|
131
|
+
enum: [
|
|
132
|
+
"open",
|
|
133
|
+
"pairing",
|
|
134
|
+
"allowlist"
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
allowFrom: {
|
|
138
|
+
type: "array",
|
|
139
|
+
items: { oneOf: [{ type: "string" }, { type: "number" }] }
|
|
140
|
+
},
|
|
141
|
+
groupPolicy: {
|
|
142
|
+
type: "string",
|
|
143
|
+
enum: [
|
|
144
|
+
"open",
|
|
145
|
+
"allowlist",
|
|
146
|
+
"disabled"
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
groupAllowFrom: {
|
|
150
|
+
type: "array",
|
|
151
|
+
items: { oneOf: [{ type: "string" }, { type: "number" }] }
|
|
152
|
+
},
|
|
153
|
+
requireMention: { type: "boolean" },
|
|
154
|
+
groupSessionScope: {
|
|
155
|
+
type: "string",
|
|
156
|
+
enum: [
|
|
157
|
+
"group",
|
|
158
|
+
"group_sender",
|
|
159
|
+
"group_topic",
|
|
160
|
+
"group_topic_sender"
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
topicSessionMode: {
|
|
164
|
+
type: "string",
|
|
165
|
+
enum: ["disabled", "enabled"]
|
|
166
|
+
},
|
|
167
|
+
replyInThread: {
|
|
168
|
+
type: "string",
|
|
169
|
+
enum: ["disabled", "enabled"]
|
|
170
|
+
},
|
|
171
|
+
historyLimit: {
|
|
172
|
+
type: "integer",
|
|
173
|
+
minimum: 0
|
|
174
|
+
},
|
|
175
|
+
dmHistoryLimit: {
|
|
176
|
+
type: "integer",
|
|
177
|
+
minimum: 0
|
|
178
|
+
},
|
|
179
|
+
textChunkLimit: {
|
|
180
|
+
type: "integer",
|
|
181
|
+
minimum: 1
|
|
182
|
+
},
|
|
183
|
+
chunkMode: {
|
|
184
|
+
type: "string",
|
|
185
|
+
enum: ["length", "newline"]
|
|
186
|
+
},
|
|
187
|
+
mediaMaxMb: {
|
|
188
|
+
type: "number",
|
|
189
|
+
minimum: 0
|
|
190
|
+
},
|
|
191
|
+
renderMode: {
|
|
192
|
+
type: "string",
|
|
193
|
+
enum: [
|
|
194
|
+
"auto",
|
|
195
|
+
"raw",
|
|
196
|
+
"card"
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
accounts: {
|
|
200
|
+
type: "object",
|
|
201
|
+
additionalProperties: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
enabled: { type: "boolean" },
|
|
205
|
+
name: { type: "string" },
|
|
206
|
+
appId: { type: "string" },
|
|
207
|
+
appSecret: secretInputJsonSchema,
|
|
208
|
+
encryptKey: secretInputJsonSchema,
|
|
209
|
+
verificationToken: secretInputJsonSchema,
|
|
210
|
+
domain: {
|
|
211
|
+
type: "string",
|
|
212
|
+
enum: ["feishu", "lark"]
|
|
213
|
+
},
|
|
214
|
+
connectionMode: {
|
|
215
|
+
type: "string",
|
|
216
|
+
enum: ["websocket", "webhook"]
|
|
217
|
+
},
|
|
218
|
+
webhookHost: { type: "string" },
|
|
219
|
+
webhookPath: { type: "string" },
|
|
220
|
+
webhookPort: {
|
|
221
|
+
type: "integer",
|
|
222
|
+
minimum: 1
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} },
|
|
229
|
+
config: {
|
|
230
|
+
listAccountIds: (cfg) => listFeishuAccountIds(cfg),
|
|
231
|
+
resolveAccount: (cfg, accountId) => resolveFeishuAccount({
|
|
232
|
+
cfg,
|
|
233
|
+
accountId
|
|
234
|
+
}),
|
|
235
|
+
defaultAccountId: (cfg) => resolveDefaultFeishuAccountId(cfg),
|
|
236
|
+
setAccountEnabled: ({ cfg, accountId, enabled }) => {
|
|
237
|
+
resolveFeishuAccount({
|
|
238
|
+
cfg,
|
|
239
|
+
accountId
|
|
240
|
+
});
|
|
241
|
+
if (accountId === "default") return {
|
|
242
|
+
...cfg,
|
|
243
|
+
channels: {
|
|
244
|
+
...cfg.channels,
|
|
245
|
+
feishu: {
|
|
246
|
+
...cfg.channels?.feishu,
|
|
247
|
+
enabled
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
return setFeishuNamedAccountEnabled(cfg, accountId, enabled);
|
|
252
|
+
},
|
|
253
|
+
deleteAccount: ({ cfg, accountId }) => {
|
|
254
|
+
if (accountId === "default") {
|
|
255
|
+
const next = { ...cfg };
|
|
256
|
+
const nextChannels = { ...cfg.channels };
|
|
257
|
+
delete nextChannels.feishu;
|
|
258
|
+
if (Object.keys(nextChannels).length > 0) next.channels = nextChannels;
|
|
259
|
+
else delete next.channels;
|
|
260
|
+
return next;
|
|
261
|
+
}
|
|
262
|
+
const feishuCfg = cfg.channels?.feishu;
|
|
263
|
+
const accounts = { ...feishuCfg?.accounts };
|
|
264
|
+
delete accounts[accountId];
|
|
265
|
+
return {
|
|
266
|
+
...cfg,
|
|
267
|
+
channels: {
|
|
268
|
+
...cfg.channels,
|
|
269
|
+
feishu: {
|
|
270
|
+
...feishuCfg,
|
|
271
|
+
accounts: Object.keys(accounts).length > 0 ? accounts : void 0
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
},
|
|
276
|
+
isConfigured: (account) => account.configured,
|
|
277
|
+
describeAccount: (account) => ({
|
|
278
|
+
accountId: account.accountId,
|
|
279
|
+
enabled: account.enabled,
|
|
280
|
+
configured: account.configured,
|
|
281
|
+
name: account.name,
|
|
282
|
+
appId: account.appId,
|
|
283
|
+
domain: account.domain
|
|
284
|
+
}),
|
|
285
|
+
resolveAllowFrom: ({ cfg, accountId }) => {
|
|
286
|
+
return mapAllowFromEntries(resolveFeishuAccount({
|
|
287
|
+
cfg,
|
|
288
|
+
accountId
|
|
289
|
+
}).config?.allowFrom);
|
|
290
|
+
},
|
|
291
|
+
formatAllowFrom: ({ allowFrom }) => formatAllowFromLowercase({ allowFrom })
|
|
292
|
+
},
|
|
293
|
+
security: { collectWarnings: ({ cfg, accountId }) => {
|
|
294
|
+
const account = resolveFeishuAccount({
|
|
295
|
+
cfg,
|
|
296
|
+
accountId
|
|
297
|
+
});
|
|
298
|
+
const feishuCfg = account.config;
|
|
299
|
+
return collectAllowlistProviderRestrictSendersWarnings({
|
|
300
|
+
cfg,
|
|
301
|
+
providerConfigPresent: cfg.channels?.feishu !== void 0,
|
|
302
|
+
configuredGroupPolicy: feishuCfg?.groupPolicy,
|
|
303
|
+
surface: `Feishu[${account.accountId}] groups`,
|
|
304
|
+
openScope: "any member",
|
|
305
|
+
groupPolicyPath: "channels.feishu.groupPolicy",
|
|
306
|
+
groupAllowFromPath: "channels.feishu.groupAllowFrom"
|
|
307
|
+
});
|
|
308
|
+
} },
|
|
309
|
+
setup: {
|
|
310
|
+
resolveAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
311
|
+
applyAccountConfig: ({ cfg, accountId }) => {
|
|
312
|
+
if (!accountId || accountId === "default") return {
|
|
313
|
+
...cfg,
|
|
314
|
+
channels: {
|
|
315
|
+
...cfg.channels,
|
|
316
|
+
feishu: {
|
|
317
|
+
...cfg.channels?.feishu,
|
|
318
|
+
enabled: true
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
return setFeishuNamedAccountEnabled(cfg, accountId, true);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
onboarding: feishuOnboardingAdapter,
|
|
326
|
+
messaging: {
|
|
327
|
+
normalizeTarget: (raw) => normalizeFeishuTarget(raw) ?? void 0,
|
|
328
|
+
targetResolver: {
|
|
329
|
+
looksLikeId: looksLikeFeishuId,
|
|
330
|
+
hint: "<chatId|user:openId|chat:chatId>"
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
directory: {
|
|
334
|
+
self: async () => null,
|
|
335
|
+
listPeers: async ({ cfg, query, limit, accountId }) => listFeishuDirectoryPeers({
|
|
336
|
+
cfg,
|
|
337
|
+
query: query ?? void 0,
|
|
338
|
+
limit: limit ?? void 0,
|
|
339
|
+
accountId: accountId ?? void 0
|
|
340
|
+
}),
|
|
341
|
+
listGroups: async ({ cfg, query, limit, accountId }) => listFeishuDirectoryGroups({
|
|
342
|
+
cfg,
|
|
343
|
+
query: query ?? void 0,
|
|
344
|
+
limit: limit ?? void 0,
|
|
345
|
+
accountId: accountId ?? void 0
|
|
346
|
+
}),
|
|
347
|
+
listPeersLive: async ({ cfg, query, limit, accountId }) => listFeishuDirectoryPeersLive({
|
|
348
|
+
cfg,
|
|
349
|
+
query: query ?? void 0,
|
|
350
|
+
limit: limit ?? void 0,
|
|
351
|
+
accountId: accountId ?? void 0
|
|
352
|
+
}),
|
|
353
|
+
listGroupsLive: async ({ cfg, query, limit, accountId }) => listFeishuDirectoryGroupsLive({
|
|
354
|
+
cfg,
|
|
355
|
+
query: query ?? void 0,
|
|
356
|
+
limit: limit ?? void 0,
|
|
357
|
+
accountId: accountId ?? void 0
|
|
358
|
+
})
|
|
359
|
+
},
|
|
360
|
+
outbound: feishuOutbound,
|
|
361
|
+
status: {
|
|
362
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, { port: null }),
|
|
363
|
+
buildChannelSummary: ({ snapshot }) => buildProbeChannelStatusSummary(snapshot, { port: snapshot.port ?? null }),
|
|
364
|
+
probeAccount: async ({ account }) => await probeFeishu(account),
|
|
365
|
+
buildAccountSnapshot: ({ account, runtime, probe }) => ({
|
|
366
|
+
accountId: account.accountId,
|
|
367
|
+
enabled: account.enabled,
|
|
368
|
+
configured: account.configured,
|
|
369
|
+
name: account.name,
|
|
370
|
+
appId: account.appId,
|
|
371
|
+
domain: account.domain,
|
|
372
|
+
...buildRuntimeAccountStatusSnapshot({
|
|
373
|
+
runtime,
|
|
374
|
+
probe
|
|
375
|
+
}),
|
|
376
|
+
port: runtime?.port ?? null
|
|
377
|
+
})
|
|
378
|
+
},
|
|
379
|
+
gateway: { startAccount: async (ctx) => {
|
|
380
|
+
const { monitorFeishuProvider, stopFeishuMonitor } = await import("./monitor.js");
|
|
381
|
+
const account = resolveFeishuAccount({
|
|
382
|
+
cfg: ctx.cfg,
|
|
383
|
+
accountId: ctx.accountId
|
|
384
|
+
});
|
|
385
|
+
const accountId = account.accountId;
|
|
386
|
+
const port = account.config?.webhookPort ?? null;
|
|
387
|
+
ctx.setStatus({
|
|
388
|
+
accountId,
|
|
389
|
+
port
|
|
390
|
+
});
|
|
391
|
+
ctx.log?.info(`starting feishu[${accountId}] (mode: ${account.config?.connectionMode ?? "websocket"})`);
|
|
392
|
+
const monitorTask = (async () => {
|
|
393
|
+
try {
|
|
394
|
+
await monitorFeishuProvider({
|
|
395
|
+
config: ctx.cfg,
|
|
396
|
+
runtime: ctx.runtime,
|
|
397
|
+
abortSignal: ctx.abortSignal,
|
|
398
|
+
accountId
|
|
399
|
+
});
|
|
400
|
+
} catch (error) {
|
|
401
|
+
if (ctx.abortSignal?.aborted) return;
|
|
402
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
403
|
+
ctx.log?.error?.(`feishu[${accountId}]: gateway monitor stopped unexpectedly: ${message}`);
|
|
404
|
+
}
|
|
405
|
+
})();
|
|
406
|
+
return { stop: async () => {
|
|
407
|
+
stopFeishuMonitor(accountId);
|
|
408
|
+
await monitorTask;
|
|
409
|
+
} };
|
|
410
|
+
} }
|
|
411
|
+
};
|
|
412
|
+
//#endregion
|
|
413
|
+
export { feishuPlugin };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
//#region src/chat-schema.ts
|
|
3
|
+
const CHAT_ACTION_VALUES = ["members", "info"];
|
|
4
|
+
const MEMBER_ID_TYPE_VALUES = [
|
|
5
|
+
"open_id",
|
|
6
|
+
"user_id",
|
|
7
|
+
"union_id"
|
|
8
|
+
];
|
|
9
|
+
const FeishuChatSchema = Type.Object({
|
|
10
|
+
action: Type.Unsafe({
|
|
11
|
+
type: "string",
|
|
12
|
+
enum: [...CHAT_ACTION_VALUES],
|
|
13
|
+
description: "Action to run: members | info"
|
|
14
|
+
}),
|
|
15
|
+
chat_id: Type.String({ description: "Chat ID (from URL or event payload)" }),
|
|
16
|
+
page_size: Type.Optional(Type.Number({ description: "Page size (1-100, default 50)" })),
|
|
17
|
+
page_token: Type.Optional(Type.String({ description: "Pagination token" })),
|
|
18
|
+
member_id_type: Type.Optional(Type.Unsafe({
|
|
19
|
+
type: "string",
|
|
20
|
+
enum: [...MEMBER_ID_TYPE_VALUES],
|
|
21
|
+
description: "Member ID type (default: open_id)"
|
|
22
|
+
}))
|
|
23
|
+
});
|
|
24
|
+
//#endregion
|
|
25
|
+
export { FeishuChatSchema };
|
package/dist/src/chat.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createFeishuToolClient, resolveRegisteredFeishuToolsConfig } from "./tool-account.js";
|
|
2
|
+
import { FeishuChatSchema } from "./chat-schema.js";
|
|
3
|
+
//#region src/chat.ts
|
|
4
|
+
function json(data) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{
|
|
7
|
+
type: "text",
|
|
8
|
+
text: JSON.stringify(data, null, 2)
|
|
9
|
+
}],
|
|
10
|
+
details: data
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
async function getChatInfo(client, chatId) {
|
|
14
|
+
const res = await client.im.chat.get({ path: { chat_id: chatId } });
|
|
15
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
16
|
+
const chat = res.data;
|
|
17
|
+
return {
|
|
18
|
+
chat_id: chatId,
|
|
19
|
+
name: chat?.name,
|
|
20
|
+
description: chat?.description,
|
|
21
|
+
owner_id: chat?.owner_id,
|
|
22
|
+
tenant_key: chat?.tenant_key,
|
|
23
|
+
user_count: chat?.user_count,
|
|
24
|
+
chat_mode: chat?.chat_mode,
|
|
25
|
+
chat_type: chat?.chat_type,
|
|
26
|
+
join_message_visibility: chat?.join_message_visibility,
|
|
27
|
+
leave_message_visibility: chat?.leave_message_visibility,
|
|
28
|
+
membership_approval: chat?.membership_approval,
|
|
29
|
+
moderation_permission: chat?.moderation_permission,
|
|
30
|
+
avatar: chat?.avatar
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function getChatMembers(client, chatId, pageSize, pageToken, memberIdType) {
|
|
34
|
+
const page_size = pageSize ? Math.max(1, Math.min(100, pageSize)) : 50;
|
|
35
|
+
const res = await client.im.chatMembers.get({
|
|
36
|
+
path: { chat_id: chatId },
|
|
37
|
+
params: {
|
|
38
|
+
page_size,
|
|
39
|
+
page_token: pageToken,
|
|
40
|
+
member_id_type: memberIdType ?? "open_id"
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
44
|
+
return {
|
|
45
|
+
chat_id: chatId,
|
|
46
|
+
has_more: res.data?.has_more,
|
|
47
|
+
page_token: res.data?.page_token,
|
|
48
|
+
members: res.data?.items?.map((item) => ({
|
|
49
|
+
member_id: item.member_id,
|
|
50
|
+
name: item.name,
|
|
51
|
+
tenant_key: item.tenant_key,
|
|
52
|
+
member_id_type: item.member_id_type
|
|
53
|
+
})) ?? []
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function registerFeishuChatTools(api) {
|
|
57
|
+
if (!api.config) {
|
|
58
|
+
api.logger.debug?.("feishu_chat: No config available, skipping chat tools");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!resolveRegisteredFeishuToolsConfig(api.config).chat) {
|
|
62
|
+
api.logger.debug?.("feishu_chat: chat tool disabled in config");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
api.registerTool({
|
|
66
|
+
name: "feishu_chat",
|
|
67
|
+
label: "Feishu Chat",
|
|
68
|
+
description: "Feishu chat operations. Actions: members, info",
|
|
69
|
+
parameters: FeishuChatSchema,
|
|
70
|
+
async execute(_toolCallId, params) {
|
|
71
|
+
const p = params;
|
|
72
|
+
try {
|
|
73
|
+
const client = createFeishuToolClient({ api });
|
|
74
|
+
switch (p.action) {
|
|
75
|
+
case "members": return json(await getChatMembers(client, p.chat_id, p.page_size, p.page_token, p.member_id_type));
|
|
76
|
+
case "info": return json(await getChatInfo(client, p.chat_id));
|
|
77
|
+
default: return json({ error: `Unknown action: ${String(p.action)}` });
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}, { name: "feishu_chat" });
|
|
84
|
+
api.logger.info?.("feishu_chat: Registered feishu_chat tool");
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { registerFeishuChatTools };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FeishuConfig, FeishuDomain } from "./types.js";
|
|
2
|
+
//#region src/client.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Credentials needed to create a Feishu client.
|
|
5
|
+
* Both FeishuConfig and ResolvedFeishuAccount satisfy this interface.
|
|
6
|
+
*/
|
|
7
|
+
type FeishuClientCredentials = {
|
|
8
|
+
accountId?: string;
|
|
9
|
+
appId?: string;
|
|
10
|
+
appSecret?: string;
|
|
11
|
+
domain?: FeishuDomain;
|
|
12
|
+
httpTimeoutMs?: number;
|
|
13
|
+
config?: Pick<FeishuConfig, "httpTimeoutMs">;
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { FeishuClientCredentials };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
2
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
3
|
+
//#region src/client.ts
|
|
4
|
+
/** Default HTTP timeout for Feishu API requests (30 seconds). */
|
|
5
|
+
const FEISHU_HTTP_TIMEOUT_MS = 3e4;
|
|
6
|
+
const FEISHU_HTTP_TIMEOUT_MAX_MS = 3e5;
|
|
7
|
+
const FEISHU_HTTP_TIMEOUT_ENV_VAR = "OPENCLAW_FEISHU_HTTP_TIMEOUT_MS";
|
|
8
|
+
function getWsProxyAgent() {
|
|
9
|
+
const proxyUrl = process.env.https_proxy || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.HTTP_PROXY;
|
|
10
|
+
if (!proxyUrl) return void 0;
|
|
11
|
+
return new HttpsProxyAgent(proxyUrl);
|
|
12
|
+
}
|
|
13
|
+
const clientCache = /* @__PURE__ */ new Map();
|
|
14
|
+
function resolveDomain(domain) {
|
|
15
|
+
if (domain === "lark") return Lark.Domain.Lark;
|
|
16
|
+
if (domain === "feishu" || !domain) return Lark.Domain.Feishu;
|
|
17
|
+
return domain.replace(/\/+$/, "");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create an HTTP instance that delegates to the Lark SDK's default instance
|
|
21
|
+
* but injects a default request timeout to prevent indefinite hangs
|
|
22
|
+
* (e.g. when the Feishu API is slow, causing per-chat queue deadlocks).
|
|
23
|
+
*/
|
|
24
|
+
function createTimeoutHttpInstance(defaultTimeoutMs) {
|
|
25
|
+
const base = Lark.defaultHttpInstance;
|
|
26
|
+
function injectTimeout(opts) {
|
|
27
|
+
return {
|
|
28
|
+
timeout: defaultTimeoutMs,
|
|
29
|
+
...opts
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
request: (opts) => base.request(injectTimeout(opts)),
|
|
34
|
+
get: (url, opts) => base.get(url, injectTimeout(opts)),
|
|
35
|
+
post: (url, data, opts) => base.post(url, data, injectTimeout(opts)),
|
|
36
|
+
put: (url, data, opts) => base.put(url, data, injectTimeout(opts)),
|
|
37
|
+
patch: (url, data, opts) => base.patch(url, data, injectTimeout(opts)),
|
|
38
|
+
delete: (url, opts) => base.delete(url, injectTimeout(opts)),
|
|
39
|
+
head: (url, opts) => base.head(url, injectTimeout(opts)),
|
|
40
|
+
options: (url, opts) => base.options(url, injectTimeout(opts))
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function resolveConfiguredHttpTimeoutMs(creds) {
|
|
44
|
+
const clampTimeout = (value) => {
|
|
45
|
+
return Math.min(Math.max(Math.floor(value), 1), FEISHU_HTTP_TIMEOUT_MAX_MS);
|
|
46
|
+
};
|
|
47
|
+
const fromDirectField = creds.httpTimeoutMs;
|
|
48
|
+
if (typeof fromDirectField === "number" && Number.isFinite(fromDirectField) && fromDirectField > 0) return clampTimeout(fromDirectField);
|
|
49
|
+
const envRaw = process.env[FEISHU_HTTP_TIMEOUT_ENV_VAR];
|
|
50
|
+
if (envRaw) {
|
|
51
|
+
const envValue = Number(envRaw);
|
|
52
|
+
if (Number.isFinite(envValue) && envValue > 0) return clampTimeout(envValue);
|
|
53
|
+
}
|
|
54
|
+
const timeout = creds.config?.httpTimeoutMs;
|
|
55
|
+
if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) return FEISHU_HTTP_TIMEOUT_MS;
|
|
56
|
+
return clampTimeout(timeout);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create or get a cached Feishu client for an account.
|
|
60
|
+
* Accepts any object with appId, appSecret, and optional domain/accountId.
|
|
61
|
+
*/
|
|
62
|
+
function createFeishuClient(creds) {
|
|
63
|
+
const { accountId = "default", appId, appSecret, domain } = creds;
|
|
64
|
+
const defaultHttpTimeoutMs = resolveConfiguredHttpTimeoutMs(creds);
|
|
65
|
+
if (!appId || !appSecret) throw new Error(`Feishu credentials not configured for account "${accountId}"`);
|
|
66
|
+
const cached = clientCache.get(accountId);
|
|
67
|
+
if (cached && cached.config.appId === appId && cached.config.appSecret === appSecret && cached.config.domain === domain && cached.config.httpTimeoutMs === defaultHttpTimeoutMs) return cached.client;
|
|
68
|
+
const client = new Lark.Client({
|
|
69
|
+
appId,
|
|
70
|
+
appSecret,
|
|
71
|
+
appType: Lark.AppType.SelfBuild,
|
|
72
|
+
domain: resolveDomain(domain),
|
|
73
|
+
httpInstance: createTimeoutHttpInstance(defaultHttpTimeoutMs)
|
|
74
|
+
});
|
|
75
|
+
clientCache.set(accountId, {
|
|
76
|
+
client,
|
|
77
|
+
config: {
|
|
78
|
+
appId,
|
|
79
|
+
appSecret,
|
|
80
|
+
domain,
|
|
81
|
+
httpTimeoutMs: defaultHttpTimeoutMs
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return client;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create a Feishu WebSocket client for an account.
|
|
88
|
+
* Note: WSClient is not cached since each call creates a new connection.
|
|
89
|
+
*/
|
|
90
|
+
function createFeishuWSClient(account) {
|
|
91
|
+
const { accountId, appId, appSecret, domain } = account;
|
|
92
|
+
if (!appId || !appSecret) throw new Error(`Feishu credentials not configured for account "${accountId}"`);
|
|
93
|
+
const agent = getWsProxyAgent();
|
|
94
|
+
return new Lark.WSClient({
|
|
95
|
+
appId,
|
|
96
|
+
appSecret,
|
|
97
|
+
domain: resolveDomain(domain),
|
|
98
|
+
loggerLevel: Lark.LoggerLevel.info,
|
|
99
|
+
...agent ? { agent } : {}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create an event dispatcher for an account.
|
|
104
|
+
*/
|
|
105
|
+
function createEventDispatcher(account) {
|
|
106
|
+
return new Lark.EventDispatcher({
|
|
107
|
+
encryptKey: account.encryptKey,
|
|
108
|
+
verificationToken: account.verificationToken
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { createEventDispatcher, createFeishuClient, createFeishuWSClient };
|