@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,381 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID } from "./nextclaw-sdk/account-id.js";
|
|
2
|
+
import { formatDocsLink } from "./nextclaw-sdk/core-pairing.js";
|
|
3
|
+
import { hasConfiguredSecretInput } from "./nextclaw-sdk/secrets-core.js";
|
|
4
|
+
import { buildSingleChannelSecretPromptState, mergeAllowFromEntries, setTopLevelChannelAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelGroupPolicy, splitOnboardingEntries } from "./nextclaw-sdk/secrets-config.js";
|
|
5
|
+
import { promptSingleChannelSecretInput } from "./nextclaw-sdk/secrets-prompt.js";
|
|
6
|
+
import "./nextclaw-sdk/feishu.js";
|
|
7
|
+
import { resolveFeishuCredentials } from "./accounts.js";
|
|
8
|
+
import { probeFeishu } from "./probe.js";
|
|
9
|
+
//#region src/onboarding.ts
|
|
10
|
+
const channel = "feishu";
|
|
11
|
+
function normalizeString(value) {
|
|
12
|
+
if (typeof value !== "string") return;
|
|
13
|
+
return value.trim() || void 0;
|
|
14
|
+
}
|
|
15
|
+
function setFeishuDmPolicy(cfg, dmPolicy) {
|
|
16
|
+
return setTopLevelChannelDmPolicyWithAllowFrom({
|
|
17
|
+
cfg,
|
|
18
|
+
channel: "feishu",
|
|
19
|
+
dmPolicy
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function setFeishuAllowFrom(cfg, allowFrom) {
|
|
23
|
+
return setTopLevelChannelAllowFrom({
|
|
24
|
+
cfg,
|
|
25
|
+
channel: "feishu",
|
|
26
|
+
allowFrom
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function promptFeishuAllowFrom(params) {
|
|
30
|
+
const existing = params.cfg.channels?.feishu?.allowFrom ?? [];
|
|
31
|
+
await params.prompter.note([
|
|
32
|
+
"Allowlist Feishu DMs by open_id or user_id.",
|
|
33
|
+
"You can find user open_id in Feishu admin console or via API.",
|
|
34
|
+
"Examples:",
|
|
35
|
+
"- ou_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
36
|
+
"- on_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
37
|
+
].join("\n"), "Feishu allowlist");
|
|
38
|
+
while (true) {
|
|
39
|
+
const entry = await params.prompter.text({
|
|
40
|
+
message: "Feishu allowFrom (user open_ids)",
|
|
41
|
+
placeholder: "ou_xxxxx, ou_yyyyy",
|
|
42
|
+
initialValue: existing[0] ? String(existing[0]) : void 0,
|
|
43
|
+
validate: (value) => String(value ?? "").trim() ? void 0 : "Required"
|
|
44
|
+
});
|
|
45
|
+
const parts = splitOnboardingEntries(String(entry));
|
|
46
|
+
if (parts.length === 0) {
|
|
47
|
+
await params.prompter.note("Enter at least one user.", "Feishu allowlist");
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const unique = mergeAllowFromEntries(existing, parts);
|
|
51
|
+
return setFeishuAllowFrom(params.cfg, unique);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function noteFeishuCredentialHelp(prompter) {
|
|
55
|
+
await prompter.note([
|
|
56
|
+
"1) Go to Feishu Open Platform (open.feishu.cn)",
|
|
57
|
+
"2) Create a self-built app",
|
|
58
|
+
"3) Get App ID and App Secret from Credentials page",
|
|
59
|
+
"4) Enable required permissions: im:message, im:chat, contact:user.base:readonly",
|
|
60
|
+
"5) Publish the app or add it to a test group",
|
|
61
|
+
"Tip: you can also set FEISHU_APP_ID / FEISHU_APP_SECRET env vars.",
|
|
62
|
+
`Docs: ${formatDocsLink("/channels/feishu", "feishu")}`
|
|
63
|
+
].join("\n"), "Feishu credentials");
|
|
64
|
+
}
|
|
65
|
+
async function promptFeishuAppId(params) {
|
|
66
|
+
return String(await params.prompter.text({
|
|
67
|
+
message: "Enter Feishu App ID",
|
|
68
|
+
initialValue: params.initialValue,
|
|
69
|
+
validate: (value) => value?.trim() ? void 0 : "Required"
|
|
70
|
+
})).trim();
|
|
71
|
+
}
|
|
72
|
+
function setFeishuGroupPolicy(cfg, groupPolicy) {
|
|
73
|
+
return setTopLevelChannelGroupPolicy({
|
|
74
|
+
cfg,
|
|
75
|
+
channel: "feishu",
|
|
76
|
+
groupPolicy,
|
|
77
|
+
enabled: true
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function setFeishuGroupAllowFrom(cfg, groupAllowFrom) {
|
|
81
|
+
return {
|
|
82
|
+
...cfg,
|
|
83
|
+
channels: {
|
|
84
|
+
...cfg.channels,
|
|
85
|
+
feishu: {
|
|
86
|
+
...cfg.channels?.feishu,
|
|
87
|
+
groupAllowFrom
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const feishuOnboardingAdapter = {
|
|
93
|
+
channel,
|
|
94
|
+
getStatus: async ({ cfg }) => {
|
|
95
|
+
const feishuCfg = cfg.channels?.feishu;
|
|
96
|
+
const isAppIdConfigured = (value) => {
|
|
97
|
+
if (normalizeString(value)) return true;
|
|
98
|
+
if (!value || typeof value !== "object") return false;
|
|
99
|
+
const rec = value;
|
|
100
|
+
const source = normalizeString(rec.source)?.toLowerCase();
|
|
101
|
+
const id = normalizeString(rec.id);
|
|
102
|
+
if (source === "env" && id) return Boolean(normalizeString(process.env[id]));
|
|
103
|
+
return hasConfiguredSecretInput(value);
|
|
104
|
+
};
|
|
105
|
+
const topLevelConfigured = Boolean(isAppIdConfigured(feishuCfg?.appId) && hasConfiguredSecretInput(feishuCfg?.appSecret));
|
|
106
|
+
const accountConfigured = Object.values(feishuCfg?.accounts ?? {}).some((account) => {
|
|
107
|
+
if (!account || typeof account !== "object") return false;
|
|
108
|
+
const hasOwnAppId = Object.prototype.hasOwnProperty.call(account, "appId");
|
|
109
|
+
const hasOwnAppSecret = Object.prototype.hasOwnProperty.call(account, "appSecret");
|
|
110
|
+
const accountAppIdConfigured = hasOwnAppId ? isAppIdConfigured(account.appId) : isAppIdConfigured(feishuCfg?.appId);
|
|
111
|
+
const accountSecretConfigured = hasOwnAppSecret ? hasConfiguredSecretInput(account.appSecret) : hasConfiguredSecretInput(feishuCfg?.appSecret);
|
|
112
|
+
return Boolean(accountAppIdConfigured && accountSecretConfigured);
|
|
113
|
+
});
|
|
114
|
+
const configured = topLevelConfigured || accountConfigured;
|
|
115
|
+
const resolvedCredentials = resolveFeishuCredentials(feishuCfg, { allowUnresolvedSecretRef: true });
|
|
116
|
+
let probeResult = null;
|
|
117
|
+
if (configured && resolvedCredentials) try {
|
|
118
|
+
probeResult = await probeFeishu(resolvedCredentials);
|
|
119
|
+
} catch {}
|
|
120
|
+
const statusLines = [];
|
|
121
|
+
if (!configured) statusLines.push("Feishu: needs app credentials");
|
|
122
|
+
else if (probeResult?.ok) statusLines.push(`Feishu: connected as ${probeResult.botName ?? probeResult.botOpenId ?? "bot"}`);
|
|
123
|
+
else statusLines.push("Feishu: configured (connection not verified)");
|
|
124
|
+
return {
|
|
125
|
+
channel,
|
|
126
|
+
configured,
|
|
127
|
+
statusLines,
|
|
128
|
+
selectionHint: configured ? "configured" : "needs app creds",
|
|
129
|
+
quickstartScore: configured ? 2 : 0
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
configure: async ({ cfg, prompter }) => {
|
|
133
|
+
const feishuCfg = cfg.channels?.feishu;
|
|
134
|
+
const resolved = resolveFeishuCredentials(feishuCfg, { allowUnresolvedSecretRef: true });
|
|
135
|
+
const hasConfigSecret = hasConfiguredSecretInput(feishuCfg?.appSecret);
|
|
136
|
+
const hasConfigCreds = Boolean(typeof feishuCfg?.appId === "string" && feishuCfg.appId.trim() && hasConfigSecret);
|
|
137
|
+
const appSecretPromptState = buildSingleChannelSecretPromptState({
|
|
138
|
+
accountConfigured: Boolean(resolved),
|
|
139
|
+
hasConfigToken: hasConfigSecret,
|
|
140
|
+
allowEnv: !hasConfigCreds && Boolean(process.env.FEISHU_APP_ID?.trim()),
|
|
141
|
+
envValue: process.env.FEISHU_APP_SECRET
|
|
142
|
+
});
|
|
143
|
+
let next = cfg;
|
|
144
|
+
let appId = null;
|
|
145
|
+
let appSecret = null;
|
|
146
|
+
let appSecretProbeValue = null;
|
|
147
|
+
if (!resolved) await noteFeishuCredentialHelp(prompter);
|
|
148
|
+
const appSecretResult = await promptSingleChannelSecretInput({
|
|
149
|
+
cfg: next,
|
|
150
|
+
prompter,
|
|
151
|
+
providerHint: "feishu",
|
|
152
|
+
credentialLabel: "App Secret",
|
|
153
|
+
accountConfigured: appSecretPromptState.accountConfigured,
|
|
154
|
+
canUseEnv: appSecretPromptState.canUseEnv,
|
|
155
|
+
hasConfigToken: appSecretPromptState.hasConfigToken,
|
|
156
|
+
envPrompt: "FEISHU_APP_ID + FEISHU_APP_SECRET detected. Use env vars?",
|
|
157
|
+
keepPrompt: "Feishu App Secret already configured. Keep it?",
|
|
158
|
+
inputPrompt: "Enter Feishu App Secret",
|
|
159
|
+
preferredEnvVar: "FEISHU_APP_SECRET"
|
|
160
|
+
});
|
|
161
|
+
if (appSecretResult.action === "use-env") next = {
|
|
162
|
+
...next,
|
|
163
|
+
channels: {
|
|
164
|
+
...next.channels,
|
|
165
|
+
feishu: {
|
|
166
|
+
...next.channels?.feishu,
|
|
167
|
+
enabled: true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
else if (appSecretResult.action === "set") {
|
|
172
|
+
appSecret = appSecretResult.value;
|
|
173
|
+
appSecretProbeValue = appSecretResult.resolvedValue;
|
|
174
|
+
appId = await promptFeishuAppId({
|
|
175
|
+
prompter,
|
|
176
|
+
initialValue: normalizeString(feishuCfg?.appId) ?? normalizeString(process.env.FEISHU_APP_ID)
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (appId && appSecret) {
|
|
180
|
+
next = {
|
|
181
|
+
...next,
|
|
182
|
+
channels: {
|
|
183
|
+
...next.channels,
|
|
184
|
+
feishu: {
|
|
185
|
+
...next.channels?.feishu,
|
|
186
|
+
enabled: true,
|
|
187
|
+
appId,
|
|
188
|
+
appSecret
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
try {
|
|
193
|
+
const probe = await probeFeishu({
|
|
194
|
+
appId,
|
|
195
|
+
appSecret: appSecretProbeValue ?? void 0,
|
|
196
|
+
domain: (next.channels?.feishu)?.domain
|
|
197
|
+
});
|
|
198
|
+
if (probe.ok) await prompter.note(`Connected as ${probe.botName ?? probe.botOpenId ?? "bot"}`, "Feishu connection test");
|
|
199
|
+
else await prompter.note(`Connection failed: ${probe.error ?? "unknown error"}`, "Feishu connection test");
|
|
200
|
+
} catch (err) {
|
|
201
|
+
await prompter.note(`Connection test failed: ${String(err)}`, "Feishu connection test");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const currentMode = (next.channels?.feishu)?.connectionMode ?? "websocket";
|
|
205
|
+
const connectionMode = await prompter.select({
|
|
206
|
+
message: "Feishu connection mode",
|
|
207
|
+
options: [{
|
|
208
|
+
value: "websocket",
|
|
209
|
+
label: "WebSocket (default)"
|
|
210
|
+
}, {
|
|
211
|
+
value: "webhook",
|
|
212
|
+
label: "Webhook"
|
|
213
|
+
}],
|
|
214
|
+
initialValue: currentMode
|
|
215
|
+
});
|
|
216
|
+
next = {
|
|
217
|
+
...next,
|
|
218
|
+
channels: {
|
|
219
|
+
...next.channels,
|
|
220
|
+
feishu: {
|
|
221
|
+
...next.channels?.feishu,
|
|
222
|
+
connectionMode
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
if (connectionMode === "webhook") {
|
|
227
|
+
const currentVerificationToken = (next.channels?.feishu)?.verificationToken;
|
|
228
|
+
const verificationTokenPromptState = buildSingleChannelSecretPromptState({
|
|
229
|
+
accountConfigured: hasConfiguredSecretInput(currentVerificationToken),
|
|
230
|
+
hasConfigToken: hasConfiguredSecretInput(currentVerificationToken),
|
|
231
|
+
allowEnv: false
|
|
232
|
+
});
|
|
233
|
+
const verificationTokenResult = await promptSingleChannelSecretInput({
|
|
234
|
+
cfg: next,
|
|
235
|
+
prompter,
|
|
236
|
+
providerHint: "feishu-webhook",
|
|
237
|
+
credentialLabel: "verification token",
|
|
238
|
+
accountConfigured: verificationTokenPromptState.accountConfigured,
|
|
239
|
+
canUseEnv: verificationTokenPromptState.canUseEnv,
|
|
240
|
+
hasConfigToken: verificationTokenPromptState.hasConfigToken,
|
|
241
|
+
envPrompt: "",
|
|
242
|
+
keepPrompt: "Feishu verification token already configured. Keep it?",
|
|
243
|
+
inputPrompt: "Enter Feishu verification token",
|
|
244
|
+
preferredEnvVar: "FEISHU_VERIFICATION_TOKEN"
|
|
245
|
+
});
|
|
246
|
+
if (verificationTokenResult.action === "set") next = {
|
|
247
|
+
...next,
|
|
248
|
+
channels: {
|
|
249
|
+
...next.channels,
|
|
250
|
+
feishu: {
|
|
251
|
+
...next.channels?.feishu,
|
|
252
|
+
verificationToken: verificationTokenResult.value
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const currentEncryptKey = (next.channels?.feishu)?.encryptKey;
|
|
257
|
+
const encryptKeyPromptState = buildSingleChannelSecretPromptState({
|
|
258
|
+
accountConfigured: hasConfiguredSecretInput(currentEncryptKey),
|
|
259
|
+
hasConfigToken: hasConfiguredSecretInput(currentEncryptKey),
|
|
260
|
+
allowEnv: false
|
|
261
|
+
});
|
|
262
|
+
const encryptKeyResult = await promptSingleChannelSecretInput({
|
|
263
|
+
cfg: next,
|
|
264
|
+
prompter,
|
|
265
|
+
providerHint: "feishu-webhook",
|
|
266
|
+
credentialLabel: "encrypt key",
|
|
267
|
+
accountConfigured: encryptKeyPromptState.accountConfigured,
|
|
268
|
+
canUseEnv: encryptKeyPromptState.canUseEnv,
|
|
269
|
+
hasConfigToken: encryptKeyPromptState.hasConfigToken,
|
|
270
|
+
envPrompt: "",
|
|
271
|
+
keepPrompt: "Feishu encrypt key already configured. Keep it?",
|
|
272
|
+
inputPrompt: "Enter Feishu encrypt key",
|
|
273
|
+
preferredEnvVar: "FEISHU_ENCRYPT_KEY"
|
|
274
|
+
});
|
|
275
|
+
if (encryptKeyResult.action === "set") next = {
|
|
276
|
+
...next,
|
|
277
|
+
channels: {
|
|
278
|
+
...next.channels,
|
|
279
|
+
feishu: {
|
|
280
|
+
...next.channels?.feishu,
|
|
281
|
+
encryptKey: encryptKeyResult.value
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
const currentWebhookPath = (next.channels?.feishu)?.webhookPath;
|
|
286
|
+
const webhookPath = String(await prompter.text({
|
|
287
|
+
message: "Feishu webhook path",
|
|
288
|
+
initialValue: currentWebhookPath ?? "/feishu/events",
|
|
289
|
+
validate: (value) => String(value ?? "").trim() ? void 0 : "Required"
|
|
290
|
+
})).trim();
|
|
291
|
+
next = {
|
|
292
|
+
...next,
|
|
293
|
+
channels: {
|
|
294
|
+
...next.channels,
|
|
295
|
+
feishu: {
|
|
296
|
+
...next.channels?.feishu,
|
|
297
|
+
webhookPath
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const currentDomain = (next.channels?.feishu)?.domain ?? "feishu";
|
|
303
|
+
const domain = await prompter.select({
|
|
304
|
+
message: "Which Feishu domain?",
|
|
305
|
+
options: [{
|
|
306
|
+
value: "feishu",
|
|
307
|
+
label: "Feishu (feishu.cn) - China"
|
|
308
|
+
}, {
|
|
309
|
+
value: "lark",
|
|
310
|
+
label: "Lark (larksuite.com) - International"
|
|
311
|
+
}],
|
|
312
|
+
initialValue: currentDomain
|
|
313
|
+
});
|
|
314
|
+
if (domain) next = {
|
|
315
|
+
...next,
|
|
316
|
+
channels: {
|
|
317
|
+
...next.channels,
|
|
318
|
+
feishu: {
|
|
319
|
+
...next.channels?.feishu,
|
|
320
|
+
domain
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
const groupPolicy = await prompter.select({
|
|
325
|
+
message: "Group chat policy",
|
|
326
|
+
options: [
|
|
327
|
+
{
|
|
328
|
+
value: "allowlist",
|
|
329
|
+
label: "Allowlist - only respond in specific groups"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
value: "open",
|
|
333
|
+
label: "Open - respond in all groups (requires mention)"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
value: "disabled",
|
|
337
|
+
label: "Disabled - don't respond in groups"
|
|
338
|
+
}
|
|
339
|
+
],
|
|
340
|
+
initialValue: (next.channels?.feishu)?.groupPolicy ?? "allowlist"
|
|
341
|
+
});
|
|
342
|
+
if (groupPolicy) next = setFeishuGroupPolicy(next, groupPolicy);
|
|
343
|
+
if (groupPolicy === "allowlist") {
|
|
344
|
+
const existing = (next.channels?.feishu)?.groupAllowFrom ?? [];
|
|
345
|
+
const entry = await prompter.text({
|
|
346
|
+
message: "Group chat allowlist (chat_ids)",
|
|
347
|
+
placeholder: "oc_xxxxx, oc_yyyyy",
|
|
348
|
+
initialValue: existing.length > 0 ? existing.map(String).join(", ") : void 0
|
|
349
|
+
});
|
|
350
|
+
if (entry) {
|
|
351
|
+
const parts = splitOnboardingEntries(String(entry));
|
|
352
|
+
if (parts.length > 0) next = setFeishuGroupAllowFrom(next, parts);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
cfg: next,
|
|
357
|
+
accountId: DEFAULT_ACCOUNT_ID
|
|
358
|
+
};
|
|
359
|
+
},
|
|
360
|
+
dmPolicy: {
|
|
361
|
+
label: "Feishu",
|
|
362
|
+
channel,
|
|
363
|
+
policyKey: "channels.feishu.dmPolicy",
|
|
364
|
+
allowFromKey: "channels.feishu.allowFrom",
|
|
365
|
+
getCurrent: (cfg) => (cfg.channels?.feishu)?.dmPolicy ?? "pairing",
|
|
366
|
+
setPolicy: (cfg, policy) => setFeishuDmPolicy(cfg, policy),
|
|
367
|
+
promptAllowFrom: promptFeishuAllowFrom
|
|
368
|
+
},
|
|
369
|
+
disable: (cfg) => ({
|
|
370
|
+
...cfg,
|
|
371
|
+
channels: {
|
|
372
|
+
...cfg.channels,
|
|
373
|
+
feishu: {
|
|
374
|
+
...cfg.channels?.feishu,
|
|
375
|
+
enabled: false
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
};
|
|
380
|
+
//#endregion
|
|
381
|
+
export { feishuOnboardingAdapter };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { resolveFeishuAccount } from "./accounts.js";
|
|
2
|
+
import { getFeishuRuntime } from "./runtime.js";
|
|
3
|
+
import { sendMediaFeishu } from "./media.js";
|
|
4
|
+
import { sendMarkdownCardFeishu, sendMessageFeishu } from "./send.js";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
//#region src/outbound.ts
|
|
8
|
+
function normalizePossibleLocalImagePath(text) {
|
|
9
|
+
const raw = text?.trim();
|
|
10
|
+
if (!raw) return null;
|
|
11
|
+
if (/\s/.test(raw)) return null;
|
|
12
|
+
if (/^(https?:\/\/|data:|file:\/\/)/i.test(raw)) return null;
|
|
13
|
+
const ext = path.extname(raw).toLowerCase();
|
|
14
|
+
if (![
|
|
15
|
+
".jpg",
|
|
16
|
+
".jpeg",
|
|
17
|
+
".png",
|
|
18
|
+
".gif",
|
|
19
|
+
".webp",
|
|
20
|
+
".bmp",
|
|
21
|
+
".ico",
|
|
22
|
+
".tiff"
|
|
23
|
+
].includes(ext)) return null;
|
|
24
|
+
if (!path.isAbsolute(raw)) return null;
|
|
25
|
+
if (!fs.existsSync(raw)) return null;
|
|
26
|
+
try {
|
|
27
|
+
if (!fs.statSync(raw).isFile()) return null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
function shouldUseCard(text) {
|
|
34
|
+
return /```[\s\S]*?```/.test(text) || /\|.+\|[\r\n]+\|[-:| ]+\|/.test(text);
|
|
35
|
+
}
|
|
36
|
+
function resolveReplyToMessageId(params) {
|
|
37
|
+
const replyToId = params.replyToId?.trim();
|
|
38
|
+
if (replyToId) return replyToId;
|
|
39
|
+
if (params.threadId == null) return;
|
|
40
|
+
return String(params.threadId).trim() || void 0;
|
|
41
|
+
}
|
|
42
|
+
async function sendOutboundText(params) {
|
|
43
|
+
const { cfg, to, text, accountId, replyToMessageId } = params;
|
|
44
|
+
const renderMode = resolveFeishuAccount({
|
|
45
|
+
cfg,
|
|
46
|
+
accountId
|
|
47
|
+
}).config?.renderMode ?? "auto";
|
|
48
|
+
if (renderMode === "card" || renderMode === "auto" && shouldUseCard(text)) return sendMarkdownCardFeishu({
|
|
49
|
+
cfg,
|
|
50
|
+
to,
|
|
51
|
+
text,
|
|
52
|
+
accountId,
|
|
53
|
+
replyToMessageId
|
|
54
|
+
});
|
|
55
|
+
return sendMessageFeishu({
|
|
56
|
+
cfg,
|
|
57
|
+
to,
|
|
58
|
+
text,
|
|
59
|
+
accountId,
|
|
60
|
+
replyToMessageId
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const feishuOutbound = {
|
|
64
|
+
deliveryMode: "direct",
|
|
65
|
+
chunker: (text, limit) => getFeishuRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
66
|
+
chunkerMode: "markdown",
|
|
67
|
+
textChunkLimit: 4e3,
|
|
68
|
+
sendText: async ({ cfg, to, text, accountId, replyToId, threadId, mediaLocalRoots }) => {
|
|
69
|
+
const replyToMessageId = resolveReplyToMessageId({
|
|
70
|
+
replyToId,
|
|
71
|
+
threadId
|
|
72
|
+
});
|
|
73
|
+
const localImagePath = normalizePossibleLocalImagePath(text);
|
|
74
|
+
if (localImagePath) try {
|
|
75
|
+
return {
|
|
76
|
+
channel: "feishu",
|
|
77
|
+
...await sendMediaFeishu({
|
|
78
|
+
cfg,
|
|
79
|
+
to,
|
|
80
|
+
mediaUrl: localImagePath,
|
|
81
|
+
accountId: accountId ?? void 0,
|
|
82
|
+
replyToMessageId,
|
|
83
|
+
mediaLocalRoots
|
|
84
|
+
})
|
|
85
|
+
};
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error(`[feishu] local image path auto-send failed:`, err);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
channel: "feishu",
|
|
91
|
+
...await sendOutboundText({
|
|
92
|
+
cfg,
|
|
93
|
+
to,
|
|
94
|
+
text,
|
|
95
|
+
accountId: accountId ?? void 0,
|
|
96
|
+
replyToMessageId
|
|
97
|
+
})
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
sendMedia: async ({ cfg, to, text, mediaUrl, accountId, mediaLocalRoots, replyToId, threadId }) => {
|
|
101
|
+
const replyToMessageId = resolveReplyToMessageId({
|
|
102
|
+
replyToId,
|
|
103
|
+
threadId
|
|
104
|
+
});
|
|
105
|
+
if (text?.trim()) await sendOutboundText({
|
|
106
|
+
cfg,
|
|
107
|
+
to,
|
|
108
|
+
text,
|
|
109
|
+
accountId: accountId ?? void 0,
|
|
110
|
+
replyToMessageId
|
|
111
|
+
});
|
|
112
|
+
if (mediaUrl) try {
|
|
113
|
+
return {
|
|
114
|
+
channel: "feishu",
|
|
115
|
+
...await sendMediaFeishu({
|
|
116
|
+
cfg,
|
|
117
|
+
to,
|
|
118
|
+
mediaUrl,
|
|
119
|
+
accountId: accountId ?? void 0,
|
|
120
|
+
mediaLocalRoots,
|
|
121
|
+
replyToMessageId
|
|
122
|
+
})
|
|
123
|
+
};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error(`[feishu] sendMediaFeishu failed:`, err);
|
|
126
|
+
return {
|
|
127
|
+
channel: "feishu",
|
|
128
|
+
...await sendOutboundText({
|
|
129
|
+
cfg,
|
|
130
|
+
to,
|
|
131
|
+
text: `📎 ${mediaUrl}`,
|
|
132
|
+
accountId: accountId ?? void 0,
|
|
133
|
+
replyToMessageId
|
|
134
|
+
})
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
channel: "feishu",
|
|
139
|
+
...await sendOutboundText({
|
|
140
|
+
cfg,
|
|
141
|
+
to,
|
|
142
|
+
text: text ?? "",
|
|
143
|
+
accountId: accountId ?? void 0,
|
|
144
|
+
replyToMessageId
|
|
145
|
+
})
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
//#endregion
|
|
150
|
+
export { feishuOutbound };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
//#region src/perm-schema.ts
|
|
3
|
+
const TokenType = Type.Union([
|
|
4
|
+
Type.Literal("doc"),
|
|
5
|
+
Type.Literal("docx"),
|
|
6
|
+
Type.Literal("sheet"),
|
|
7
|
+
Type.Literal("bitable"),
|
|
8
|
+
Type.Literal("folder"),
|
|
9
|
+
Type.Literal("file"),
|
|
10
|
+
Type.Literal("wiki"),
|
|
11
|
+
Type.Literal("mindnote")
|
|
12
|
+
]);
|
|
13
|
+
const MemberType = Type.Union([
|
|
14
|
+
Type.Literal("email"),
|
|
15
|
+
Type.Literal("openid"),
|
|
16
|
+
Type.Literal("userid"),
|
|
17
|
+
Type.Literal("unionid"),
|
|
18
|
+
Type.Literal("openchat"),
|
|
19
|
+
Type.Literal("opendepartmentid")
|
|
20
|
+
]);
|
|
21
|
+
const Permission = Type.Union([
|
|
22
|
+
Type.Literal("view"),
|
|
23
|
+
Type.Literal("edit"),
|
|
24
|
+
Type.Literal("full_access")
|
|
25
|
+
]);
|
|
26
|
+
const FeishuPermSchema = Type.Union([
|
|
27
|
+
Type.Object({
|
|
28
|
+
action: Type.Literal("list"),
|
|
29
|
+
token: Type.String({ description: "File token" }),
|
|
30
|
+
type: TokenType
|
|
31
|
+
}),
|
|
32
|
+
Type.Object({
|
|
33
|
+
action: Type.Literal("add"),
|
|
34
|
+
token: Type.String({ description: "File token" }),
|
|
35
|
+
type: TokenType,
|
|
36
|
+
member_type: MemberType,
|
|
37
|
+
member_id: Type.String({ description: "Member ID (email, open_id, user_id, etc.)" }),
|
|
38
|
+
perm: Permission
|
|
39
|
+
}),
|
|
40
|
+
Type.Object({
|
|
41
|
+
action: Type.Literal("remove"),
|
|
42
|
+
token: Type.String({ description: "File token" }),
|
|
43
|
+
type: TokenType,
|
|
44
|
+
member_type: MemberType,
|
|
45
|
+
member_id: Type.String({ description: "Member ID to remove" })
|
|
46
|
+
})
|
|
47
|
+
]);
|
|
48
|
+
//#endregion
|
|
49
|
+
export { FeishuPermSchema };
|
package/dist/src/perm.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsonToolResult, toolExecutionErrorResult, unknownToolActionResult } from "./tool-result.js";
|
|
2
|
+
import { createFeishuToolClient, resolveRegisteredFeishuToolsConfig } from "./tool-account.js";
|
|
3
|
+
import { FeishuPermSchema } from "./perm-schema.js";
|
|
4
|
+
//#region src/perm.ts
|
|
5
|
+
async function listMembers(client, token, type) {
|
|
6
|
+
const res = await client.drive.permissionMember.list({
|
|
7
|
+
path: { token },
|
|
8
|
+
params: { type }
|
|
9
|
+
});
|
|
10
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
11
|
+
return { members: res.data?.items?.map((m) => ({
|
|
12
|
+
member_type: m.member_type,
|
|
13
|
+
member_id: m.member_id,
|
|
14
|
+
perm: m.perm,
|
|
15
|
+
name: m.name
|
|
16
|
+
})) ?? [] };
|
|
17
|
+
}
|
|
18
|
+
async function addMember(client, token, type, memberType, memberId, perm) {
|
|
19
|
+
const res = await client.drive.permissionMember.create({
|
|
20
|
+
path: { token },
|
|
21
|
+
params: {
|
|
22
|
+
type,
|
|
23
|
+
need_notification: false
|
|
24
|
+
},
|
|
25
|
+
data: {
|
|
26
|
+
member_type: memberType,
|
|
27
|
+
member_id: memberId,
|
|
28
|
+
perm
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
32
|
+
return {
|
|
33
|
+
success: true,
|
|
34
|
+
member: res.data?.member
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function removeMember(client, token, type, memberType, memberId) {
|
|
38
|
+
const res = await client.drive.permissionMember.delete({
|
|
39
|
+
path: {
|
|
40
|
+
token,
|
|
41
|
+
member_id: memberId
|
|
42
|
+
},
|
|
43
|
+
params: {
|
|
44
|
+
type,
|
|
45
|
+
member_type: memberType
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
49
|
+
return { success: true };
|
|
50
|
+
}
|
|
51
|
+
function registerFeishuPermTools(api) {
|
|
52
|
+
if (!api.config) {
|
|
53
|
+
api.logger.debug?.("feishu_perm: No config available, skipping perm tools");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!resolveRegisteredFeishuToolsConfig(api.config).perm) {
|
|
57
|
+
api.logger.debug?.("feishu_perm: perm tool disabled in config (default: false)");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
api.registerTool((ctx) => {
|
|
61
|
+
const defaultAccountId = ctx.agentAccountId;
|
|
62
|
+
return {
|
|
63
|
+
name: "feishu_perm",
|
|
64
|
+
label: "Feishu Perm",
|
|
65
|
+
description: "Feishu permission management. Actions: list, add, remove",
|
|
66
|
+
parameters: FeishuPermSchema,
|
|
67
|
+
async execute(_toolCallId, params) {
|
|
68
|
+
const p = params;
|
|
69
|
+
try {
|
|
70
|
+
const client = createFeishuToolClient({
|
|
71
|
+
api,
|
|
72
|
+
executeParams: p,
|
|
73
|
+
defaultAccountId
|
|
74
|
+
});
|
|
75
|
+
switch (p.action) {
|
|
76
|
+
case "list": return jsonToolResult(await listMembers(client, p.token, p.type));
|
|
77
|
+
case "add": return jsonToolResult(await addMember(client, p.token, p.type, p.member_type, p.member_id, p.perm));
|
|
78
|
+
case "remove": return jsonToolResult(await removeMember(client, p.token, p.type, p.member_type, p.member_id));
|
|
79
|
+
default: return unknownToolActionResult(p.action);
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
return toolExecutionErrorResult(err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}, { name: "feishu_perm" });
|
|
87
|
+
api.logger.info?.(`feishu_perm: Registered feishu_perm tool`);
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { registerFeishuPermTools };
|