- Sync latest changes from clawdbot-feishu including multi-account support - Add eslint-disable comments for SDK-related any types - Remove unused imports - Fix no-floating-promises in monitor.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
173 lines
6.3 KiB
TypeScript
173 lines
6.3 KiB
TypeScript
import { z } from "zod";
|
|
export { z };
|
|
|
|
const DmPolicySchema = z.enum(["open", "pairing", "allowlist"]);
|
|
const GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]);
|
|
const FeishuDomainSchema = z.union([
|
|
z.enum(["feishu", "lark"]),
|
|
z.string().url().startsWith("https://"),
|
|
]);
|
|
const FeishuConnectionModeSchema = z.enum(["websocket", "webhook"]);
|
|
|
|
const ToolPolicySchema = z
|
|
.object({
|
|
allow: z.array(z.string()).optional(),
|
|
deny: z.array(z.string()).optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
const DmConfigSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
systemPrompt: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
const MarkdownConfigSchema = z
|
|
.object({
|
|
mode: z.enum(["native", "escape", "strip"]).optional(),
|
|
tableMode: z.enum(["native", "ascii", "simple"]).optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
// Message render mode: auto (default) = detect markdown, raw = plain text, card = always card
|
|
const RenderModeSchema = z.enum(["auto", "raw", "card"]).optional();
|
|
|
|
const BlockStreamingCoalesceSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
minDelayMs: z.number().int().positive().optional(),
|
|
maxDelayMs: z.number().int().positive().optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
const ChannelHeartbeatVisibilitySchema = z
|
|
.object({
|
|
visibility: z.enum(["visible", "hidden"]).optional(),
|
|
intervalMs: z.number().int().positive().optional(),
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
/**
|
|
* Feishu tools configuration.
|
|
* Controls which tool categories are enabled.
|
|
*
|
|
* Dependencies:
|
|
* - wiki requires doc (wiki content is edited via doc tools)
|
|
* - perm can work independently but is typically used with drive
|
|
*/
|
|
const FeishuToolsConfigSchema = z
|
|
.object({
|
|
doc: z.boolean().optional(), // Document operations (default: true)
|
|
wiki: z.boolean().optional(), // Knowledge base operations (default: true, requires doc)
|
|
drive: z.boolean().optional(), // Cloud storage operations (default: true)
|
|
perm: z.boolean().optional(), // Permission management (default: false, sensitive)
|
|
scopes: z.boolean().optional(), // App scopes diagnostic (default: true)
|
|
})
|
|
.strict()
|
|
.optional();
|
|
|
|
export const FeishuGroupSchema = z
|
|
.object({
|
|
requireMention: z.boolean().optional(),
|
|
tools: ToolPolicySchema,
|
|
skills: z.array(z.string()).optional(),
|
|
enabled: z.boolean().optional(),
|
|
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
systemPrompt: z.string().optional(),
|
|
})
|
|
.strict();
|
|
|
|
/**
|
|
* Per-account configuration.
|
|
* All fields are optional - missing fields inherit from top-level config.
|
|
*/
|
|
export const FeishuAccountConfigSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
name: z.string().optional(), // Display name for this account
|
|
appId: z.string().optional(),
|
|
appSecret: z.string().optional(),
|
|
encryptKey: z.string().optional(),
|
|
verificationToken: z.string().optional(),
|
|
domain: FeishuDomainSchema.optional(),
|
|
connectionMode: FeishuConnectionModeSchema.optional(),
|
|
webhookPath: z.string().optional(),
|
|
webhookPort: z.number().int().positive().optional(),
|
|
capabilities: z.array(z.string()).optional(),
|
|
markdown: MarkdownConfigSchema,
|
|
configWrites: z.boolean().optional(),
|
|
dmPolicy: DmPolicySchema.optional(),
|
|
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
groupPolicy: GroupPolicySchema.optional(),
|
|
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
requireMention: z.boolean().optional(),
|
|
groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
|
|
historyLimit: z.number().int().min(0).optional(),
|
|
dmHistoryLimit: z.number().int().min(0).optional(),
|
|
dms: z.record(z.string(), DmConfigSchema).optional(),
|
|
textChunkLimit: z.number().int().positive().optional(),
|
|
chunkMode: z.enum(["length", "newline"]).optional(),
|
|
blockStreamingCoalesce: BlockStreamingCoalesceSchema,
|
|
mediaMaxMb: z.number().positive().optional(),
|
|
heartbeat: ChannelHeartbeatVisibilitySchema,
|
|
renderMode: RenderModeSchema,
|
|
tools: FeishuToolsConfigSchema,
|
|
})
|
|
.strict();
|
|
|
|
export const FeishuConfigSchema = z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
// Top-level credentials (backward compatible for single-account mode)
|
|
appId: z.string().optional(),
|
|
appSecret: z.string().optional(),
|
|
encryptKey: z.string().optional(),
|
|
verificationToken: z.string().optional(),
|
|
domain: FeishuDomainSchema.optional().default("feishu"),
|
|
connectionMode: FeishuConnectionModeSchema.optional().default("websocket"),
|
|
webhookPath: z.string().optional().default("/feishu/events"),
|
|
webhookPort: z.number().int().positive().optional(),
|
|
capabilities: z.array(z.string()).optional(),
|
|
markdown: MarkdownConfigSchema,
|
|
configWrites: z.boolean().optional(),
|
|
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
|
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
|
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
requireMention: z.boolean().optional().default(true),
|
|
groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
|
|
historyLimit: z.number().int().min(0).optional(),
|
|
dmHistoryLimit: z.number().int().min(0).optional(),
|
|
dms: z.record(z.string(), DmConfigSchema).optional(),
|
|
textChunkLimit: z.number().int().positive().optional(),
|
|
chunkMode: z.enum(["length", "newline"]).optional(),
|
|
blockStreamingCoalesce: BlockStreamingCoalesceSchema,
|
|
mediaMaxMb: z.number().positive().optional(),
|
|
heartbeat: ChannelHeartbeatVisibilitySchema,
|
|
renderMode: RenderModeSchema, // raw = plain text (default), card = interactive card with markdown
|
|
tools: FeishuToolsConfigSchema,
|
|
// Multi-account configuration
|
|
accounts: z.record(z.string(), FeishuAccountConfigSchema.optional()).optional(),
|
|
})
|
|
.strict()
|
|
.superRefine((value, ctx) => {
|
|
if (value.dmPolicy === "open") {
|
|
const allowFrom = value.allowFrom ?? [];
|
|
const hasWildcard = allowFrom.some((entry) => String(entry).trim() === "*");
|
|
if (!hasWildcard) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["allowFrom"],
|
|
message:
|
|
'channels.feishu.dmPolicy="open" requires channels.feishu.allowFrom to include "*"',
|
|
});
|
|
}
|
|
}
|
|
});
|