@m1heng-clawd/feishu 0.1.8 → 0.1.10

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/src/onboarding.ts CHANGED
@@ -5,45 +5,99 @@ import type {
5
5
  DmPolicy,
6
6
  WizardPrompter,
7
7
  } from "openclaw/plugin-sdk";
8
- import { addWildcardAllowFrom, DEFAULT_ACCOUNT_ID, formatDocsLink } from "openclaw/plugin-sdk";
8
+ import {
9
+ addWildcardAllowFrom,
10
+ DEFAULT_ACCOUNT_ID,
11
+ formatDocsLink,
12
+ normalizeAccountId,
13
+ promptAccountId,
14
+ } from "openclaw/plugin-sdk";
9
15
 
10
- import { resolveFeishuCredentials } from "./accounts.js";
16
+ import {
17
+ listFeishuAccountIds,
18
+ resolveDefaultFeishuAccountId,
19
+ resolveFeishuAccount,
20
+ resolveFeishuCredentials,
21
+ } from "./accounts.js";
11
22
  import { probeFeishu } from "./probe.js";
12
23
  import type { FeishuConfig } from "./types.js";
13
24
 
14
25
  const channel = "feishu" as const;
15
26
 
16
- function setFeishuDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy): ClawdbotConfig {
17
- const allowFrom =
18
- dmPolicy === "open"
19
- ? addWildcardAllowFrom(cfg.channels?.feishu?.allowFrom)?.map((entry) => String(entry))
20
- : undefined;
21
- return {
22
- ...cfg,
23
- channels: {
24
- ...cfg.channels,
25
- feishu: {
26
- ...cfg.channels?.feishu,
27
- dmPolicy,
28
- ...(allowFrom ? { allowFrom } : {}),
29
- },
30
- },
31
- };
27
+ function resolveOnboardingAccountId(cfg: ClawdbotConfig, accountId?: string | null): string {
28
+ const raw = accountId?.trim();
29
+ if (raw) {
30
+ return normalizeAccountId(raw);
31
+ }
32
+ return resolveDefaultFeishuAccountId(cfg);
32
33
  }
33
34
 
34
- function setFeishuAllowFrom(cfg: ClawdbotConfig, allowFrom: string[]): ClawdbotConfig {
35
+ function upsertFeishuAccountConfig(
36
+ cfg: ClawdbotConfig,
37
+ accountId: string,
38
+ patch: Partial<FeishuConfig>,
39
+ ): ClawdbotConfig {
40
+ const normalizedAccountId = normalizeAccountId(accountId);
41
+ const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
42
+
43
+ if (normalizedAccountId === DEFAULT_ACCOUNT_ID) {
44
+ return {
45
+ ...cfg,
46
+ channels: {
47
+ ...cfg.channels,
48
+ feishu: {
49
+ ...feishuCfg,
50
+ ...patch,
51
+ },
52
+ },
53
+ };
54
+ }
55
+
56
+ const existingAccount = feishuCfg?.accounts?.[normalizedAccountId];
35
57
  return {
36
58
  ...cfg,
37
59
  channels: {
38
60
  ...cfg.channels,
39
61
  feishu: {
40
- ...cfg.channels?.feishu,
41
- allowFrom,
62
+ ...feishuCfg,
63
+ enabled: true,
64
+ accounts: {
65
+ ...feishuCfg?.accounts,
66
+ [normalizedAccountId]: {
67
+ ...existingAccount,
68
+ enabled: existingAccount?.enabled ?? true,
69
+ ...patch,
70
+ },
71
+ },
42
72
  },
43
73
  },
44
74
  };
45
75
  }
46
76
 
77
+ function setFeishuDmPolicy(
78
+ cfg: ClawdbotConfig,
79
+ dmPolicy: DmPolicy,
80
+ accountId?: string,
81
+ ): ClawdbotConfig {
82
+ const resolvedAccountId = resolveOnboardingAccountId(cfg, accountId);
83
+ const account = resolveFeishuAccount({ cfg, accountId: resolvedAccountId });
84
+ // Feishu channel config does not support "disabled" as a dmPolicy value.
85
+ const effectiveDmPolicy = dmPolicy === "disabled" ? "pairing" : dmPolicy;
86
+ const allowFrom =
87
+ effectiveDmPolicy === "open"
88
+ ? addWildcardAllowFrom(account.config.allowFrom)?.map((entry) => String(entry))
89
+ : undefined;
90
+ return upsertFeishuAccountConfig(cfg, resolvedAccountId, {
91
+ dmPolicy: effectiveDmPolicy,
92
+ ...(allowFrom ? { allowFrom } : {}),
93
+ });
94
+ }
95
+
96
+ function setFeishuAllowFrom(cfg: ClawdbotConfig, allowFrom: string[], accountId?: string): ClawdbotConfig {
97
+ const resolvedAccountId = resolveOnboardingAccountId(cfg, accountId);
98
+ return upsertFeishuAccountConfig(cfg, resolvedAccountId, { allowFrom });
99
+ }
100
+
47
101
  function parseAllowFromInput(raw: string): string[] {
48
102
  return raw
49
103
  .split(/[\n,;]+/g)
@@ -54,10 +108,14 @@ function parseAllowFromInput(raw: string): string[] {
54
108
  async function promptFeishuAllowFrom(params: {
55
109
  cfg: ClawdbotConfig;
56
110
  prompter: WizardPrompter;
111
+ accountId?: string;
57
112
  }): Promise<ClawdbotConfig> {
58
- const existing = params.cfg.channels?.feishu?.allowFrom ?? [];
113
+ const accountId = resolveOnboardingAccountId(params.cfg, params.accountId);
114
+ const existing = resolveFeishuAccount({ cfg: params.cfg, accountId }).config.allowFrom ?? [];
115
+ const accountLabel = accountId === DEFAULT_ACCOUNT_ID ? "default" : accountId;
59
116
  await params.prompter.note(
60
117
  [
118
+ `Account: ${accountLabel}`,
61
119
  "Allowlist Feishu DMs by open_id or user_id.",
62
120
  "You can find user open_id in Feishu admin console or via API.",
63
121
  "Examples:",
@@ -83,7 +141,7 @@ async function promptFeishuAllowFrom(params: {
83
141
  const unique = [
84
142
  ...new Set([...existing.map((v) => String(v).trim()).filter(Boolean), ...parts]),
85
143
  ];
86
- return setFeishuAllowFrom(params.cfg, unique);
144
+ return setFeishuAllowFrom(params.cfg, unique, accountId);
87
145
  }
88
146
  }
89
147
 
@@ -105,31 +163,24 @@ async function noteFeishuCredentialHelp(prompter: WizardPrompter): Promise<void>
105
163
  function setFeishuGroupPolicy(
106
164
  cfg: ClawdbotConfig,
107
165
  groupPolicy: "open" | "allowlist" | "disabled",
166
+ accountId?: string,
108
167
  ): ClawdbotConfig {
109
- return {
110
- ...cfg,
111
- channels: {
112
- ...cfg.channels,
113
- feishu: {
114
- ...cfg.channels?.feishu,
115
- enabled: true,
116
- groupPolicy,
117
- },
118
- },
119
- };
168
+ const resolvedAccountId = resolveOnboardingAccountId(cfg, accountId);
169
+ return upsertFeishuAccountConfig(cfg, resolvedAccountId, { enabled: true, groupPolicy });
120
170
  }
121
171
 
122
- function setFeishuGroupAllowFrom(cfg: ClawdbotConfig, groupAllowFrom: string[]): ClawdbotConfig {
123
- return {
124
- ...cfg,
125
- channels: {
126
- ...cfg.channels,
127
- feishu: {
128
- ...cfg.channels?.feishu,
129
- groupAllowFrom,
130
- },
131
- },
132
- };
172
+ function setFeishuGroupAllowFrom(
173
+ cfg: ClawdbotConfig,
174
+ groupAllowFrom: string[],
175
+ accountId?: string,
176
+ ): ClawdbotConfig {
177
+ const resolvedAccountId = resolveOnboardingAccountId(cfg, accountId);
178
+ return upsertFeishuAccountConfig(cfg, resolvedAccountId, { groupAllowFrom });
179
+ }
180
+
181
+ function setFeishuDomain(cfg: ClawdbotConfig, domain: "feishu" | "lark", accountId?: string): ClawdbotConfig {
182
+ const resolvedAccountId = resolveOnboardingAccountId(cfg, accountId);
183
+ return upsertFeishuAccountConfig(cfg, resolvedAccountId, { domain });
133
184
  }
134
185
 
135
186
  const dmPolicy: ChannelOnboardingDmPolicy = {
@@ -137,22 +188,30 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
137
188
  channel,
138
189
  policyKey: "channels.feishu.dmPolicy",
139
190
  allowFromKey: "channels.feishu.allowFrom",
140
- getCurrent: (cfg) => (cfg.channels?.feishu as FeishuConfig | undefined)?.dmPolicy ?? "pairing",
141
- setPolicy: (cfg, policy) => setFeishuDmPolicy(cfg, policy),
191
+ getCurrent: (cfg) => {
192
+ const accountId = resolveDefaultFeishuAccountId(cfg);
193
+ return resolveFeishuAccount({ cfg, accountId }).config.dmPolicy ?? "pairing";
194
+ },
195
+ setPolicy: (cfg, policy, accountId?: string) => setFeishuDmPolicy(cfg, policy, accountId),
142
196
  promptAllowFrom: promptFeishuAllowFrom,
143
197
  };
144
198
 
145
199
  export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
146
200
  channel,
147
- getStatus: async ({ cfg }) => {
148
- const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
149
- const configured = Boolean(resolveFeishuCredentials(feishuCfg));
201
+ getStatus: async ({ cfg, accountOverrides }) => {
202
+ const override = accountOverrides?.feishu?.trim();
203
+ const accountIds = override
204
+ ? [resolveOnboardingAccountId(cfg, override)]
205
+ : listFeishuAccountIds(cfg);
206
+ const accounts = accountIds.map((accountId) => resolveFeishuAccount({ cfg, accountId }));
207
+ const configuredAccounts = accounts.filter((account) => account.configured);
208
+ const configured = configuredAccounts.length > 0;
150
209
 
151
210
  // Try to probe if configured
152
211
  let probeResult = null;
153
- if (configured && feishuCfg) {
212
+ if (configuredAccounts[0]) {
154
213
  try {
155
- probeResult = await probeFeishu(feishuCfg);
214
+ probeResult = await probeFeishu(configuredAccounts[0]);
156
215
  } catch {
157
216
  // Ignore probe errors
158
217
  }
@@ -162,7 +221,16 @@ export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
162
221
  if (!configured) {
163
222
  statusLines.push("Feishu: needs app credentials");
164
223
  } else if (probeResult?.ok) {
165
- statusLines.push(`Feishu: connected as ${probeResult.botName ?? probeResult.botOpenId ?? "bot"}`);
224
+ const probeAccountId = configuredAccounts[0]?.accountId ?? DEFAULT_ACCOUNT_ID;
225
+ const accountNote = probeAccountId === DEFAULT_ACCOUNT_ID ? "" : ` [${probeAccountId}]`;
226
+ statusLines.push(
227
+ `Feishu${accountNote}: connected as ${probeResult.botName ?? probeResult.botOpenId ?? "bot"}`,
228
+ );
229
+ if (!override && configuredAccounts.length > 1) {
230
+ statusLines.push(`Feishu: ${configuredAccounts.length} account(s) configured`);
231
+ }
232
+ } else if (!override && configuredAccounts.length > 1) {
233
+ statusLines.push(`Feishu: configured (${configuredAccounts.length} account(s), connection not verified)`);
166
234
  } else {
167
235
  statusLines.push("Feishu: configured (connection not verified)");
168
236
  }
@@ -176,11 +244,30 @@ export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
176
244
  };
177
245
  },
178
246
 
179
- configure: async ({ cfg, prompter }) => {
180
- const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
181
- const resolved = resolveFeishuCredentials(feishuCfg);
182
- const hasConfigCreds = Boolean(feishuCfg?.appId?.trim() && feishuCfg?.appSecret?.trim());
247
+ configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => {
248
+ const feishuOverride = accountOverrides?.feishu?.trim();
249
+ const defaultFeishuAccountId = resolveDefaultFeishuAccountId(cfg);
250
+ let feishuAccountId = feishuOverride
251
+ ? normalizeAccountId(feishuOverride)
252
+ : defaultFeishuAccountId;
253
+ if (shouldPromptAccountIds && !feishuOverride) {
254
+ feishuAccountId = await promptAccountId({
255
+ cfg,
256
+ prompter,
257
+ label: "Feishu",
258
+ currentId: feishuAccountId,
259
+ listAccountIds: listFeishuAccountIds,
260
+ defaultAccountId: defaultFeishuAccountId,
261
+ });
262
+ }
263
+ const accountLabel = feishuAccountId === DEFAULT_ACCOUNT_ID ? "default" : feishuAccountId;
264
+ const currentAccount = resolveFeishuAccount({ cfg, accountId: feishuAccountId });
265
+ const resolved = resolveFeishuCredentials(currentAccount.config);
266
+ const hasConfigCreds = Boolean(
267
+ currentAccount.config.appId?.trim() && currentAccount.config.appSecret?.trim(),
268
+ );
183
269
  const canUseEnv = Boolean(
270
+ feishuAccountId === DEFAULT_ACCOUNT_ID &&
184
271
  !hasConfigCreds &&
185
272
  process.env.FEISHU_APP_ID?.trim() &&
186
273
  process.env.FEISHU_APP_SECRET?.trim(),
@@ -189,6 +276,14 @@ export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
189
276
  let next = cfg;
190
277
  let appId: string | null = null;
191
278
  let appSecret: string | null = null;
279
+ const appIdPrompt =
280
+ feishuAccountId === DEFAULT_ACCOUNT_ID
281
+ ? "Enter Feishu App ID"
282
+ : `Enter Feishu App ID for account "${accountLabel}"`;
283
+ const appSecretPrompt =
284
+ feishuAccountId === DEFAULT_ACCOUNT_ID
285
+ ? "Enter Feishu App Secret"
286
+ : `Enter Feishu App Secret for account "${accountLabel}"`;
192
287
 
193
288
  if (!resolved) {
194
289
  await noteFeishuCredentialHelp(prompter);
@@ -200,42 +295,36 @@ export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
200
295
  initialValue: true,
201
296
  });
202
297
  if (keepEnv) {
203
- next = {
204
- ...next,
205
- channels: {
206
- ...next.channels,
207
- feishu: { ...next.channels?.feishu, enabled: true },
208
- },
209
- };
298
+ next = upsertFeishuAccountConfig(next, feishuAccountId, { enabled: true });
210
299
  } else {
211
300
  appId = String(
212
301
  await prompter.text({
213
- message: "Enter Feishu App ID",
302
+ message: appIdPrompt,
214
303
  validate: (value) => (value?.trim() ? undefined : "Required"),
215
304
  }),
216
305
  ).trim();
217
306
  appSecret = String(
218
307
  await prompter.text({
219
- message: "Enter Feishu App Secret",
308
+ message: appSecretPrompt,
220
309
  validate: (value) => (value?.trim() ? undefined : "Required"),
221
310
  }),
222
311
  ).trim();
223
312
  }
224
313
  } else if (hasConfigCreds) {
225
314
  const keep = await prompter.confirm({
226
- message: "Feishu credentials already configured. Keep them?",
315
+ message: `Feishu credentials already configured for account "${accountLabel}". Keep them?`,
227
316
  initialValue: true,
228
317
  });
229
318
  if (!keep) {
230
319
  appId = String(
231
320
  await prompter.text({
232
- message: "Enter Feishu App ID",
321
+ message: appIdPrompt,
233
322
  validate: (value) => (value?.trim() ? undefined : "Required"),
234
323
  }),
235
324
  ).trim();
236
325
  appSecret = String(
237
326
  await prompter.text({
238
- message: "Enter Feishu App Secret",
327
+ message: appSecretPrompt,
239
328
  validate: (value) => (value?.trim() ? undefined : "Required"),
240
329
  }),
241
330
  ).trim();
@@ -243,54 +332,50 @@ export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
243
332
  } else {
244
333
  appId = String(
245
334
  await prompter.text({
246
- message: "Enter Feishu App ID",
335
+ message: appIdPrompt,
247
336
  validate: (value) => (value?.trim() ? undefined : "Required"),
248
337
  }),
249
338
  ).trim();
250
339
  appSecret = String(
251
340
  await prompter.text({
252
- message: "Enter Feishu App Secret",
341
+ message: appSecretPrompt,
253
342
  validate: (value) => (value?.trim() ? undefined : "Required"),
254
343
  }),
255
344
  ).trim();
256
345
  }
257
346
 
258
347
  if (appId && appSecret) {
259
- next = {
260
- ...next,
261
- channels: {
262
- ...next.channels,
263
- feishu: {
264
- ...next.channels?.feishu,
265
- enabled: true,
266
- appId,
267
- appSecret,
268
- },
269
- },
270
- };
348
+ next = upsertFeishuAccountConfig(next, feishuAccountId, {
349
+ enabled: true,
350
+ appId,
351
+ appSecret,
352
+ });
271
353
 
272
354
  // Test connection
273
- const testCfg = next.channels?.feishu as FeishuConfig;
355
+ const testAccount = resolveFeishuAccount({ cfg: next, accountId: feishuAccountId });
274
356
  try {
275
- const probe = await probeFeishu(testCfg);
357
+ const probe = await probeFeishu(testAccount);
276
358
  if (probe.ok) {
277
359
  await prompter.note(
278
360
  `Connected as ${probe.botName ?? probe.botOpenId ?? "bot"}`,
279
- "Feishu connection test",
361
+ `Feishu connection test (${accountLabel})`,
280
362
  );
281
363
  } else {
282
364
  await prompter.note(
283
365
  `Connection failed: ${probe.error ?? "unknown error"}`,
284
- "Feishu connection test",
366
+ `Feishu connection test (${accountLabel})`,
285
367
  );
286
368
  }
287
369
  } catch (err) {
288
- await prompter.note(`Connection test failed: ${String(err)}`, "Feishu connection test");
370
+ await prompter.note(
371
+ `Connection test failed: ${String(err)}`,
372
+ `Feishu connection test (${accountLabel})`,
373
+ );
289
374
  }
290
375
  }
291
376
 
292
377
  // Domain selection
293
- const currentDomain = (next.channels?.feishu as FeishuConfig | undefined)?.domain ?? "feishu";
378
+ const currentDomain = resolveFeishuAccount({ cfg: next, accountId: feishuAccountId }).config.domain ?? "feishu";
294
379
  const domain = await prompter.select({
295
380
  message: "Which Feishu domain?",
296
381
  options: [
@@ -300,19 +385,11 @@ export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
300
385
  initialValue: currentDomain,
301
386
  });
302
387
  if (domain) {
303
- next = {
304
- ...next,
305
- channels: {
306
- ...next.channels,
307
- feishu: {
308
- ...next.channels?.feishu,
309
- domain: domain as "feishu" | "lark",
310
- },
311
- },
312
- };
388
+ next = setFeishuDomain(next, domain as "feishu" | "lark", feishuAccountId);
313
389
  }
314
390
 
315
391
  // Group policy
392
+ const groupPolicyAccount = resolveFeishuAccount({ cfg: next, accountId: feishuAccountId });
316
393
  const groupPolicy = await prompter.select({
317
394
  message: "Group chat policy",
318
395
  options: [
@@ -320,16 +397,20 @@ export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
320
397
  { value: "open", label: "Open - respond in all groups (requires mention)" },
321
398
  { value: "disabled", label: "Disabled - don't respond in groups" },
322
399
  ],
323
- initialValue:
324
- (next.channels?.feishu as FeishuConfig | undefined)?.groupPolicy ?? "allowlist",
400
+ initialValue: groupPolicyAccount.config.groupPolicy ?? "allowlist",
325
401
  });
326
402
  if (groupPolicy) {
327
- next = setFeishuGroupPolicy(next, groupPolicy as "open" | "allowlist" | "disabled");
403
+ next = setFeishuGroupPolicy(
404
+ next,
405
+ groupPolicy as "open" | "allowlist" | "disabled",
406
+ feishuAccountId,
407
+ );
328
408
  }
329
409
 
330
410
  // Group allowlist if needed
331
411
  if (groupPolicy === "allowlist") {
332
- const existing = (next.channels?.feishu as FeishuConfig | undefined)?.groupAllowFrom ?? [];
412
+ const existing =
413
+ resolveFeishuAccount({ cfg: next, accountId: feishuAccountId }).config.groupAllowFrom ?? [];
333
414
  const entry = await prompter.text({
334
415
  message: "Group chat allowlist (chat_ids)",
335
416
  placeholder: "oc_xxxxx, oc_yyyyy",
@@ -338,12 +419,12 @@ export const feishuOnboardingAdapter: ChannelOnboardingAdapter = {
338
419
  if (entry) {
339
420
  const parts = parseAllowFromInput(String(entry));
340
421
  if (parts.length > 0) {
341
- next = setFeishuGroupAllowFrom(next, parts);
422
+ next = setFeishuGroupAllowFrom(next, parts, feishuAccountId);
342
423
  }
343
424
  }
344
425
  }
345
426
 
346
- return { cfg: next, accountId: DEFAULT_ACCOUNT_ID };
427
+ return { cfg: next, accountId: feishuAccountId };
347
428
  },
348
429
 
349
430
  dmPolicy,
package/src/perm.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- import { createFeishuClient } from "./client.js";
3
- import { listEnabledFeishuAccounts } from "./accounts.js";
4
2
  import type * as Lark from "@larksuiteoapi/node-sdk";
5
3
  import { FeishuPermSchema, type FeishuPermParams } from "./perm-schema.js";
6
- import { resolveToolsConfig } from "./tools-config.js";
4
+ import { hasFeishuToolEnabledForAnyAccount, withFeishuToolClient } from "./tools-common/tool-exec.js";
7
5
 
8
6
  // ============ Helpers ============
9
7
 
@@ -117,21 +115,16 @@ export function registerFeishuPermTools(api: OpenClawPluginApi) {
117
115
  return;
118
116
  }
119
117
 
120
- const accounts = listEnabledFeishuAccounts(api.config);
121
- if (accounts.length === 0) {
118
+ if (!hasFeishuToolEnabledForAnyAccount(api.config)) {
122
119
  api.logger.debug?.("feishu_perm: No Feishu accounts configured, skipping perm tools");
123
120
  return;
124
121
  }
125
122
 
126
- const firstAccount = accounts[0];
127
- const toolsCfg = resolveToolsConfig(firstAccount.config.tools);
128
- if (!toolsCfg.perm) {
123
+ if (!hasFeishuToolEnabledForAnyAccount(api.config, "perm")) {
129
124
  api.logger.debug?.("feishu_perm: perm tool disabled in config (default: false)");
130
125
  return;
131
126
  }
132
127
 
133
- const getClient = () => createFeishuClient(firstAccount);
134
-
135
128
  api.registerTool(
136
129
  {
137
130
  name: "feishu_perm",
@@ -141,19 +134,25 @@ export function registerFeishuPermTools(api: OpenClawPluginApi) {
141
134
  async execute(_toolCallId, params) {
142
135
  const p = params as FeishuPermParams;
143
136
  try {
144
- const client = getClient();
145
- switch (p.action) {
146
- case "list":
147
- return json(await listMembers(client, p.token, p.type));
148
- case "add":
149
- return json(
150
- await addMember(client, p.token, p.type, p.member_type, p.member_id, p.perm),
151
- );
152
- case "remove":
153
- return json(await removeMember(client, p.token, p.type, p.member_type, p.member_id));
154
- default:
155
- return json({ error: `Unknown action: ${(p as any).action}` });
156
- }
137
+ return await withFeishuToolClient({
138
+ api,
139
+ toolName: "feishu_perm",
140
+ requiredTool: "perm",
141
+ run: async ({ client }) => {
142
+ switch (p.action) {
143
+ case "list":
144
+ return json(await listMembers(client, p.token, p.type));
145
+ case "add":
146
+ return json(
147
+ await addMember(client, p.token, p.type, p.member_type, p.member_id, p.perm),
148
+ );
149
+ case "remove":
150
+ return json(await removeMember(client, p.token, p.type, p.member_type, p.member_id));
151
+ default:
152
+ return json({ error: `Unknown action: ${(p as any).action}` });
153
+ }
154
+ },
155
+ });
157
156
  } catch (err) {
158
157
  return json({ error: err instanceof Error ? err.message : String(err) });
159
158
  }
@@ -162,5 +161,5 @@ export function registerFeishuPermTools(api: OpenClawPluginApi) {
162
161
  { name: "feishu_perm" },
163
162
  );
164
163
 
165
- api.logger.info?.(`feishu_perm: Registered feishu_perm tool`);
164
+ api.logger.debug?.("feishu_perm: Registered feishu_perm tool");
166
165
  }