diff --git a/docs/channels/feishu.md b/docs/channels/feishu.md index 41fde58aa..5c6debcf9 100644 --- a/docs/channels/feishu.md +++ b/docs/channels/feishu.md @@ -1,12 +1,12 @@ --- summary: "Feishu (Lark) bot support, capabilities, and configuration" read_when: - - Working on Feishu features or webhooks + - Working on Feishu features - Integrating with Chinese enterprise messaging --- # Feishu (飞书/Lark) -Status: experimental. Supports direct messages and groups via Bot API. +Status: experimental. Supports direct messages and groups via Bot API using WebSocket long connection. ## Plugin required Feishu ships as a plugin and is not bundled with the core install. @@ -23,7 +23,9 @@ Feishu ships as a plugin and is not bundled with the core install. 3) Set the credentials: - Env: `FEISHU_APP_ID=...` and `FEISHU_APP_SECRET=...` - Or config: `channels.feishu.appId` and `channels.feishu.appSecret` -4) Configure event subscription in Feishu console with your webhook URL +4) Configure event subscription in Feishu console: + - Set subscription method to **"Long Connection"** + - Add `im.message.receive_v1` event 5) Restart the gateway (or finish onboarding) 6) DM access is pairing by default; approve the pairing code on first contact @@ -35,8 +37,6 @@ Minimal config: enabled: true, appId: "cli_xxxxxxxxxx", appSecret: "xxxxxxxxxxxxxxxxxxxxxxxx", - verificationToken: "xxxxxxxxxxxxxxxxxxxxxxxx", - webhookPath: "/feishu/callback", dmPolicy: "pairing" } } @@ -46,6 +46,7 @@ Minimal config: ## What it is Feishu (飞书) is an enterprise collaboration platform by ByteDance, also known as Lark internationally. Its Bot API allows the Gateway to run a bot for 1:1 conversations and group chats. - A Feishu Bot API channel owned by the Gateway +- Uses WebSocket long connection for receiving events (no public IP needed) - Deterministic routing: replies go back to Feishu; the model never chooses channels - DMs share the agent's main session - Groups require @mention by default @@ -70,11 +71,9 @@ Feishu (飞书) is an enterprise collaboration platform by ByteDance, also known ### 3) Configure event subscription 1) Go to "Events and Callbacks" page -2) Choose "Request URL Mode" (webhook) -3) Enter your webhook URL: `https://your-gateway-host/feishu/callback` -4) Copy the **Verification Token** and optionally the **Encrypt Key** -5) Add event subscription: `im.message.receive_v1` (Receive messages) -6) Click Save +2) Set subscription method to **"Long Connection"** (WebSocket) +3) Add event subscription: `im.message.receive_v1` (Receive messages) +4) Click Save ### 4) Configure the token (env or config) Example: @@ -86,9 +85,6 @@ Example: enabled: true, appId: "cli_xxxxxxxxxx", appSecret: "xxxxxxxxxxxxxxxxxxxxxxxx", - verificationToken: "xxxxxxxxxxxxxxxxxxxxxxxx", - encryptKey: "xxxxxxxxxxxxxxxxxxxxxxxx", // optional, for encrypted events - webhookPath: "/feishu/callback", dmPolicy: "pairing" } } @@ -98,8 +94,6 @@ Example: Env option (works for the default account only): - `FEISHU_APP_ID=cli_xxxxxxxxxx` - `FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx` -- `FEISHU_VERIFICATION_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxx` -- `FEISHU_ENCRYPT_KEY=xxxxxxxxxxxxxxxxxxxxxxxx` Multi-account support: use `channels.feishu.accounts` with per-account credentials and optional `name`. @@ -114,8 +108,8 @@ Restart the gateway. Feishu starts when credentials are resolved. DM access defaults to pairing. Approve the code when the bot is first contacted. ## How it works (behavior) -- Inbound messages arrive via webhook (Feishu sends HTTP POST to your configured path) -- The gateway verifies the request using the verification token +- The gateway establishes a WebSocket long connection to Feishu servers +- Events are pushed directly through the WebSocket (no public IP needed) - Messages are normalized into the shared channel envelope - Replies always route back to the same Feishu chat - Long responses are chunked to 4000 characters (Feishu API limit) @@ -123,7 +117,6 @@ DM access defaults to pairing. Approve the code when the bot is first contacted. ## Limits - Outbound text is chunked to 4000 characters (Feishu API limit) - Media downloads/uploads are capped by `channels.feishu.mediaMaxMb` (default 20) -- Streaming is blocked by default due to webhook-based architecture - Rate limits apply per the Feishu API documentation ## Access control (DMs) @@ -141,33 +134,33 @@ DM access defaults to pairing. Approve the code when the bot is first contacted. - Configure allowed groups via `channels.feishu.groupAllowFrom` or `channels.feishu.groups` - Groups require @mention by default; configure per-group via `channels.feishu.groups..requireMention` -## Webhook verification -Feishu uses two verification mechanisms: -1) **URL Verification**: When you first configure the webhook, Feishu sends a challenge request. The gateway responds automatically. -2) **Event Verification**: Each event includes a `token` field that must match your verification token. - -Optional encryption: -- If you enable encryption in Feishu console, set `channels.feishu.encryptKey` -- Events are encrypted with AES-256-CBC; the gateway decrypts them automatically - ## Supported message types -- **Text messages**: Full support with 4000 character chunking -- **Image messages**: Logged but media upload requires image_key (planned) +- **Interactive cards**: Full markdown support via card messages (default for replies) +- **Text messages**: Plain text fallback +- **Image messages**: Requires image_key (pre-uploaded images) - **Rich text (post)**: Planned support -- **Interactive cards**: Planned support + +### Markdown support +All outbound messages use Feishu interactive card format, which supports markdown syntax: +- **Bold**: `**text**` +- *Italic*: `*text*` +- ~~Strikethrough~~: `~~text~~` +- `Code`: `` `code` `` +- Links: `[text](url)` +- Lists and more ## Capabilities | Feature | Status | |---------|--------| | Direct messages | Supported | | Groups | Supported | -| Media (images) | Partial (text only for now) | +| Markdown formatting | Supported (via card messages) | +| Media (images) | Partial (requires image_key) | | Reactions | Not supported | | Threads | Not supported | | Polls | Not supported | | Native commands | Not supported | -| Streaming | Blocked (webhook-based) | -| Rich cards | Planned | +| Streaming | Blocked | ## Delivery targets (CLI/cron) - Use an open_id, user_id, or chat_id as the target @@ -178,15 +171,14 @@ Optional encryption: **Bot does not respond:** - Check that the app credentials are valid: `clawdbot channels status --probe` -- Verify the webhook URL is correctly configured in Feishu console -- Check that the verification token matches +- Verify the event subscription is set to "Long Connection" mode in Feishu console - Verify the sender is approved (pairing or allowFrom) - Check gateway logs: `clawdbot logs --follow` -**Webhook verification fails:** -- Ensure your gateway is publicly accessible at the configured webhook path -- Check that `channels.feishu.verificationToken` matches the token in Feishu console -- If using encryption, verify `channels.feishu.encryptKey` is correct +**WebSocket connection fails:** +- Ensure the gateway has network access to Feishu servers +- Check that the App ID and App Secret are correct +- Verify the app is published and active **Permission errors:** - Ensure the app has required permissions (`im:message`, `im:message.receive_v1`) @@ -206,9 +198,6 @@ Provider options: - `channels.feishu.appId`: App ID from Feishu Open Platform - `channels.feishu.appSecret`: App Secret from Feishu Open Platform - `channels.feishu.appSecretFile`: read app secret from file path -- `channels.feishu.verificationToken`: verification token for webhook validation -- `channels.feishu.encryptKey`: encrypt key for event decryption (optional) -- `channels.feishu.webhookPath`: webhook path on the gateway HTTP server (default: /feishu/callback) - `channels.feishu.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing) - `channels.feishu.allowFrom`: DM allowlist (open_id or user_id). `open` requires `"*"` - `channels.feishu.groupPolicy`: `open | allowlist` (default: allowlist) @@ -224,9 +213,6 @@ Multi-account options: - `channels.feishu.accounts..enabled`: enable/disable account - `channels.feishu.accounts..dmPolicy`: per-account DM policy - `channels.feishu.accounts..allowFrom`: per-account allowlist -- `channels.feishu.accounts..verificationToken`: per-account verification token -- `channels.feishu.accounts..encryptKey`: per-account encrypt key -- `channels.feishu.accounts..webhookPath`: per-account webhook path ## International users (Lark) For Lark (international version), use the same configuration. The API endpoints are compatible. diff --git a/extensions/feishu/index.ts b/extensions/feishu/index.ts index f0b4c925a..8374d321e 100644 --- a/extensions/feishu/index.ts +++ b/extensions/feishu/index.ts @@ -2,7 +2,6 @@ import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk"; import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk"; import { feishuDock, feishuPlugin } from "./src/channel.js"; -import { handleFeishuWebhookRequest } from "./src/monitor.js"; import { setFeishuRuntime } from "./src/runtime.js"; const plugin = { @@ -13,7 +12,7 @@ const plugin = { register(api: ClawdbotPluginApi) { setFeishuRuntime(api.runtime); api.registerChannel({ plugin: feishuPlugin, dock: feishuDock }); - api.registerHttpHandler(handleFeishuWebhookRequest); + // WebSocket mode - no HTTP handler needed }, }; diff --git a/extensions/feishu/package.json b/extensions/feishu/package.json index 9a848a035..23426a845 100644 --- a/extensions/feishu/package.json +++ b/extensions/feishu/package.json @@ -27,6 +27,9 @@ "defaultChoice": "npm" } }, + "dependencies": { + "@larksuiteoapi/node-sdk": "^1.56.1" + }, "devDependencies": { "clawdbot": "workspace:*" }, diff --git a/extensions/feishu/src/api.ts b/extensions/feishu/src/api.ts index e9ed5bb49..ffb11173d 100644 --- a/extensions/feishu/src/api.ts +++ b/extensions/feishu/src/api.ts @@ -3,8 +3,6 @@ * @see https://open.feishu.cn/document */ -import { createHash, createDecipheriv } from "node:crypto"; - import type { FeishuApiResponse, FeishuBotInfo, @@ -12,7 +10,6 @@ import type { FeishuSendMessageParams, FeishuSendMessageResponse, FeishuTokenResponse, - FeishuWebhookPayload, } from "./types.js"; const FEISHU_API_BASE = "https://open.feishu.cn/open-apis"; @@ -214,74 +211,3 @@ export async function replyMessage( fetch: options?.fetch, }); } - -// ───────────────────────────────────────────────────────────────────────────── -// Webhook Verification -// ───────────────────────────────────────────────────────────────────────────── - -/** - * Decrypt an encrypted webhook payload using the encrypt key. - * Feishu uses AES-256-CBC with the key derived from SHA256 of the encrypt key. - */ -export function decryptWebhookPayload(encrypted: string, encryptKey: string): string { - // Derive key from encrypt key using SHA256 - const key = createHash("sha256").update(encryptKey).digest(); - - // Decode base64 - const ciphertext = Buffer.from(encrypted, "base64"); - - // First 16 bytes are the IV - const iv = ciphertext.subarray(0, 16); - const encryptedData = ciphertext.subarray(16); - - // Decrypt using AES-256-CBC - const decipher = createDecipheriv("aes-256-cbc", key, iv); - let decrypted = decipher.update(encryptedData); - decrypted = Buffer.concat([decrypted, decipher.final()]); - - return decrypted.toString("utf8"); -} - -/** - * Verify webhook signature using verification token. - */ -export function verifyWebhookToken(payload: FeishuWebhookPayload, verificationToken: string): boolean { - // For challenge verification, token is at top level - if (payload.token) { - return payload.token === verificationToken; - } - // For events, token is in header - if (payload.header?.token) { - return payload.header.token === verificationToken; - } - return false; -} - -/** - * Parse webhook payload, decrypting if necessary. - */ -export function parseWebhookPayload( - rawBody: string, - encryptKey?: string, -): FeishuWebhookPayload | null { - try { - const parsed = JSON.parse(rawBody) as FeishuWebhookPayload; - - // If encrypted, decrypt first - if (parsed.encrypt && encryptKey) { - const decrypted = decryptWebhookPayload(parsed.encrypt, encryptKey); - return JSON.parse(decrypted) as FeishuWebhookPayload; - } - - return parsed; - } catch { - return null; - } -} - -/** - * Build challenge response for URL verification. - */ -export function buildChallengeResponse(challenge: string): string { - return JSON.stringify({ challenge }); -} diff --git a/extensions/feishu/src/monitor.ts b/extensions/feishu/src/monitor.ts index 003182cfa..147335862 100644 --- a/extensions/feishu/src/monitor.ts +++ b/extensions/feishu/src/monitor.ts @@ -1,19 +1,21 @@ -import type { IncomingMessage, ServerResponse } from "node:http"; +/** + * WebSocket-based monitor for Feishu events using long connection mode. + * Uses the official @larksuiteoapi/node-sdk WSClient for receiving events + * via WebSocket - no public IP or webhook setup needed. + * + * @see https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/event-subscription-guide/long-connection-mode + */ + +import * as Lark from "@larksuiteoapi/node-sdk"; import type { ClawdbotConfig, MarkdownTableMode } from "clawdbot/plugin-sdk"; import type { FeishuMessageEvent, FeishuReceiveIdType, - FeishuWebhookPayload, ResolvedFeishuAccount, } from "./types.js"; -import { - buildChallengeResponse, - parseWebhookPayload, - sendMessage, - verifyWebhookToken, -} from "./api.js"; +import { sendMessage } from "./api.js"; import { getFeishuRuntime } from "./runtime.js"; export type FeishuRuntimeEnv = { @@ -26,7 +28,6 @@ export type FeishuMonitorOptions = { config: ClawdbotConfig; runtime: FeishuRuntimeEnv; abortSignal: AbortSignal; - webhookPath?: string; statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void; }; @@ -34,9 +35,29 @@ export type FeishuMonitorResult = { stop: () => void; }; -const FEISHU_TEXT_LIMIT = 4000; +// Feishu card markdown content limit (slightly lower than text to account for JSON overhead) +const FEISHU_CARD_CONTENT_LIMIT = 3800; const DEFAULT_MEDIA_MAX_MB = 20; -const DEFAULT_WEBHOOK_PATH = "/feishu/callback"; + +/** + * Build a Feishu interactive card with markdown content. + * Card messages support rich markdown formatting in Feishu. + */ +function buildMarkdownCard(content: string): string { + const card = { + config: { + wide_screen_mode: true, + enable_forward: true, + }, + elements: [ + { + tag: "markdown", + content, + }, + ], + }; + return JSON.stringify(card); +} type FeishuCoreRuntime = ReturnType; @@ -46,11 +67,6 @@ function logVerbose(core: FeishuCoreRuntime, runtime: FeishuRuntimeEnv, message: } } -function logWebhookEvent(message: string): void { - // Use console to ensure visibility even when runtime isn't available. - console.warn(`[feishu] ${message}`); -} - type FeishuReplyTarget = { id: string; type: FeishuReceiveIdType; @@ -73,218 +89,8 @@ function isSenderAllowed(senderId: string, allowFrom: string[]): boolean { }); } -async function readJsonBody(req: IncomingMessage, maxBytes: number) { - const chunks: Buffer[] = []; - let total = 0; - return await new Promise<{ ok: boolean; value?: string; error?: string }>((resolve) => { - req.on("data", (chunk: Buffer) => { - total += chunk.length; - if (total > maxBytes) { - resolve({ ok: false, error: "payload too large" }); - req.destroy(); - return; - } - chunks.push(chunk); - }); - req.on("end", () => { - try { - const raw = Buffer.concat(chunks).toString("utf8"); - if (!raw.trim()) { - resolve({ ok: false, error: "empty payload" }); - return; - } - resolve({ ok: true, value: raw }); - } catch (err) { - resolve({ ok: false, error: err instanceof Error ? err.message : String(err) }); - } - }); - req.on("error", (err) => { - resolve({ ok: false, error: err instanceof Error ? err.message : String(err) }); - }); - }); -} - -type WebhookTarget = { - account: ResolvedFeishuAccount; - config: ClawdbotConfig; - runtime: FeishuRuntimeEnv; - core: FeishuCoreRuntime; - path: string; - encryptKey: string; - verificationToken: string; - mediaMaxMb: number; - statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void; -}; - -const webhookTargets = new Map(); - -function normalizeWebhookPath(raw: string): string { - const trimmed = raw.trim(); - if (!trimmed) return "/feishu/callback"; - const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`; - if (withSlash.length > 1 && withSlash.endsWith("/")) { - return withSlash.slice(0, -1); - } - return withSlash; -} - -export function registerFeishuWebhookTarget(target: WebhookTarget): () => void { - const key = normalizeWebhookPath(target.path); - const normalizedTarget = { ...target, path: key }; - const existing = webhookTargets.get(key) ?? []; - const next = [...existing, normalizedTarget]; - webhookTargets.set(key, next); - return () => { - const updated = (webhookTargets.get(key) ?? []).filter( - (entry) => entry !== normalizedTarget, - ); - if (updated.length > 0) { - webhookTargets.set(key, updated); - } else { - webhookTargets.delete(key); - } - }; -} - /** - * Handle incoming Feishu webhook requests. - */ -export async function handleFeishuWebhookRequest( - req: IncomingMessage, - res: ServerResponse, -): Promise { - const url = new URL(req.url ?? "/", "http://localhost"); - const path = normalizeWebhookPath(url.pathname); - const targets = webhookTargets.get(path); - if (!targets || targets.length === 0) { - if (path === DEFAULT_WEBHOOK_PATH || path.startsWith("/feishu/")) { - logWebhookEvent( - `webhook request received for ${path}, but no active Feishu accounts are registered`, - ); - } - return false; - } - - if (req.method !== "POST") { - res.statusCode = 405; - res.setHeader("Allow", "POST"); - res.end("Method Not Allowed"); - return true; - } - - const body = await readJsonBody(req, 1024 * 1024); - if (!body.ok || !body.value) { - res.statusCode = body.error === "payload too large" ? 413 : 400; - res.end(body.error ?? "invalid payload"); - return true; - } - - let rawPayload: FeishuWebhookPayload | null = null; - try { - rawPayload = JSON.parse(body.value) as FeishuWebhookPayload; - } catch { - rawPayload = null; - } - const isEncryptedRequest = Boolean(rawPayload?.encrypt); - const rawEventType = rawPayload?.header?.event_type ?? rawPayload?.type ?? "unknown"; - logWebhookEvent( - `webhook hit: path=${path} method=${req.method} targets=${targets.length} encrypted=${isEncryptedRequest} event=${rawEventType}`, - ); - - let sawTokenMismatch = false; - let sawEncryptedWithoutKey = false; - let sawDecryptFailure = false; - - // Try each target to find one that can handle this request - for (const target of targets) { - const payload = parseWebhookPayload(body.value, target.encryptKey); - if (!payload) { - if (isEncryptedRequest && target.encryptKey) { - sawDecryptFailure = true; - target.runtime.error?.( - `[${target.account.accountId}] Feishu webhook decrypt failed. Check encryptKey.`, - ); - } - continue; - } - - if (payload.encrypt && !target.encryptKey) { - sawEncryptedWithoutKey = true; - target.runtime.error?.( - `[${target.account.accountId}] Feishu webhook is encrypted but encryptKey is not configured.`, - ); - continue; - } - - // Verify token if configured - if (target.verificationToken && !verifyWebhookToken(payload, target.verificationToken)) { - sawTokenMismatch = true; - target.runtime.error?.( - `[${target.account.accountId}] Feishu webhook token mismatch. Check verificationToken.`, - ); - continue; - } - - // Handle URL verification challenge - if (payload.type === "url_verification" && payload.challenge) { - res.statusCode = 200; - res.setHeader("Content-Type", "application/json"); - res.end(buildChallengeResponse(payload.challenge)); - return true; - } - - // Handle message events - if (payload.header?.event_type === "im.message.receive_v1" && payload.event) { - target.statusSink?.({ lastInboundAt: Date.now() }); - processMessageEvent( - payload.event as FeishuMessageEvent, - target.account, - target.config, - target.runtime, - target.core, - target.mediaMaxMb, - target.statusSink, - ).catch((err) => { - target.runtime.error?.(`[${target.account.accountId}] Feishu webhook failed: ${String(err)}`); - }); - - res.statusCode = 200; - res.end("ok"); - return true; - } - - // Other event types - acknowledge but don't process - if (payload.header?.event_type) { - target.runtime.log?.(`[feishu] ignoring event type: ${payload.header.event_type}`); - res.statusCode = 200; - res.end("ok"); - return true; - } - } - - // No target could handle the request - if (sawEncryptedWithoutKey) { - res.statusCode = 400; - res.end("encrypt_key required"); - return true; - } - if (sawDecryptFailure) { - res.statusCode = 400; - res.end("decrypt failed"); - return true; - } - if (sawTokenMismatch) { - res.statusCode = 401; - res.end("unauthorized"); - return true; - } - res.statusCode = 401; - res.end("unauthorized"); - return true; -} - -/** - * Process a message event from Feishu webhook. + * Process a message event from Feishu WebSocket. */ async function processMessageEvent( event: FeishuMessageEvent, @@ -543,18 +349,19 @@ async function deliverFeishuReply(params: { const chunkMode = core.channel.text.resolveChunkMode(config, "feishu", account.accountId); const chunks = core.channel.text.chunkMarkdownTextWithMode( text, - FEISHU_TEXT_LIMIT, + FEISHU_CARD_CONTENT_LIMIT, chunkMode, ); for (const chunk of chunks) { try { + // Send as interactive card message with markdown support await sendMessage( account.appId, account.appSecret, { receive_id: receiveId, - msg_type: "text", - content: JSON.stringify({ text: chunk }), + msg_type: "interactive", + content: buildMarkdownCard(chunk), }, receiveIdType, ); @@ -567,48 +374,70 @@ async function deliverFeishuReply(params: { } /** - * Start monitoring Feishu webhook events. + * Start monitoring Feishu events using WebSocket long connection. */ export async function monitorFeishuProvider( options: FeishuMonitorOptions, ): Promise { - const { account, config, runtime, abortSignal, webhookPath, statusSink } = options; + const { account, config, runtime, abortSignal, statusSink } = options; const core = getFeishuRuntime(); const effectiveMediaMaxMb = account.config.mediaMaxMb ?? DEFAULT_MEDIA_MAX_MB; - const path = normalizeWebhookPath(webhookPath ?? account.config.webhookPath ?? "/feishu/callback"); - - const encryptKey = account.config.encryptKey ?? process.env.FEISHU_ENCRYPT_KEY ?? ""; - const verificationToken = account.config.verificationToken ?? process.env.FEISHU_VERIFICATION_TOKEN ?? ""; let stopped = false; - const stopHandlers: Array<() => void> = []; + let wsClient: Lark.WSClient | null = null; const stop = () => { stopped = true; - for (const handler of stopHandlers) { - handler(); + if (wsClient) { + wsClient = null; } }; - // Register webhook target - const unregister = registerFeishuWebhookTarget({ - account, - config, - runtime, - core, - path, - encryptKey, - verificationToken, - mediaMaxMb: effectiveMediaMaxMb, - statusSink, + // Create WebSocket client for receiving events + wsClient = new Lark.WSClient({ + appId: account.appId, + appSecret: account.appSecret, + domain: Lark.Domain.Feishu, + loggerLevel: Lark.LoggerLevel.warn, + }); + + // Create event dispatcher with message handler + const eventDispatcher = new Lark.EventDispatcher({}).register({ + "im.message.receive_v1": async (data) => { + if (stopped) return; + + statusSink?.({ lastInboundAt: Date.now() }); + + // Convert SDK event data to our internal type + const event: FeishuMessageEvent = { + sender: data.sender as FeishuMessageEvent["sender"], + message: data.message as FeishuMessageEvent["message"], + }; + + try { + await processMessageEvent( + event, + account, + config, + runtime, + core, + effectiveMediaMaxMb, + statusSink, + ); + } catch (err) { + runtime.error?.(`[${account.accountId}] Feishu event handler failed: ${String(err)}`); + } + }, + }); + + // Start WebSocket connection + wsClient.start({ + eventDispatcher, }); - stopHandlers.push(unregister); runtime.log?.( - `[feishu] webhook ready: path=${path} encrypted=${Boolean(encryptKey)} token=${Boolean( - verificationToken, - )}`, + `[feishu] WebSocket connection started for account=${account.accountId}`, ); abortSignal.addEventListener("abort", stop, { once: true }); diff --git a/extensions/feishu/src/send.ts b/extensions/feishu/src/send.ts index d9ba1c25f..60b6293ed 100644 --- a/extensions/feishu/src/send.ts +++ b/extensions/feishu/src/send.ts @@ -62,7 +62,28 @@ function inferReceiveIdType(target: string): FeishuReceiveIdType { } /** - * Send a text message to a Feishu user or chat. + * Build a Feishu interactive card with markdown content. + * Card messages support rich markdown formatting in Feishu. + */ +function buildMarkdownCard(content: string): string { + const card = { + config: { + wide_screen_mode: true, + enable_forward: true, + }, + elements: [ + { + tag: "markdown", + content, + }, + ], + }; + return JSON.stringify(card); +} + +/** + * Send a message to a Feishu user or chat. + * Uses interactive card format for markdown support. */ export async function sendMessageFeishu( to: string, @@ -81,8 +102,8 @@ export async function sendMessageFeishu( const receiveIdType = options.receiveIdType ?? inferReceiveIdType(to); - // Build message content - const content = JSON.stringify({ text }); + // Build interactive card message content for markdown support + const content = buildMarkdownCard(text); try { // If replying to a specific message @@ -91,7 +112,7 @@ export async function sendMessageFeishu( appId, appSecret, options.replyToMessageId, - { msg_type: "text", content }, + { msg_type: "interactive", content }, { fetch: fetcher }, ); @@ -101,11 +122,11 @@ export async function sendMessageFeishu( return { ok: false, error: response.msg ?? "Failed to reply" }; } - // Send new message + // Send new message as interactive card const response = await sendMessage( appId, appSecret, - { receive_id: to.trim(), msg_type: "text", content }, + { receive_id: to.trim(), msg_type: "interactive", content }, receiveIdType, { fetch: fetcher }, ); diff --git a/extensions/feishu/src/types.ts b/extensions/feishu/src/types.ts index dacf96462..953ade600 100644 --- a/extensions/feishu/src/types.ts +++ b/extensions/feishu/src/types.ts @@ -10,12 +10,6 @@ export type FeishuAccountConfig = { appSecret?: string; /** Path to file containing the app secret. */ appSecretFile?: string; - /** Encrypt key for event decryption (from Events and Callbacks page). */ - encryptKey?: string; - /** Verification token for webhook validation. */ - verificationToken?: string; - /** Webhook path for the gateway HTTP server (defaults to /feishu/callback). */ - webhookPath?: string; /** Direct message access policy (default: pairing). */ dmPolicy?: "pairing" | "allowlist" | "open" | "disabled"; /** Allowlist for DM senders (Feishu open_id or user_id). */ @@ -124,39 +118,27 @@ export type FeishuMention = { tenant_key?: string; }; -/** Event header from webhook payload. */ -export type FeishuEventHeader = { - event_id: string; - event_type: string; - create_time: string; - token: string; - app_id: string; - tenant_key: string; -}; - /** Message receive event (im.message.receive_v1). */ export type FeishuMessageEvent = { sender: FeishuSender; message: FeishuMessageContent; }; -/** Webhook event payload. */ -export type FeishuWebhookPayload = { - schema?: string; - header?: FeishuEventHeader; - event?: FeishuMessageEvent | Record; - // URL verification challenge - challenge?: string; - token?: string; - type?: string; - // Encrypted payload - encrypt?: string; -}; +/** Feishu message type. */ +export type FeishuMessageType = + | "text" + | "post" + | "image" + | "file" + | "audio" + | "media" + | "sticker" + | "interactive"; // Card message with markdown support /** Send message request body. */ export type FeishuSendMessageParams = { receive_id: string; - msg_type: "text" | "post" | "image" | "file" | "audio" | "media" | "sticker" | "interactive"; + msg_type: FeishuMessageType; content: string; // JSON string uuid?: string; }; diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index af6a3f9cd..e9a682855 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -9,6 +9,6 @@ ] }, "peerDependencies": { - "clawdbot": ">=2026.1.24-3" + "clawdbot": ">=2026.1.24" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9dbd4fb5c..821fd1c66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -306,6 +306,10 @@ importers: extensions/feishu: dependencies: + '@larksuiteoapi/node-sdk': + specifier: ^1.56.1 + version: 1.56.1 + devDependencies: clawdbot: specifier: workspace:* version: link:../.. @@ -363,8 +367,8 @@ importers: extensions/memory-core: dependencies: clawdbot: - specifier: workspace:* - version: link:../.. + specifier: '>=2026.1.24' + version: 2026.1.24(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3) extensions/memory-lancedb: dependencies: @@ -1074,105 +1078,89 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1241,6 +1229,48 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@lancedb/lancedb-darwin-arm64@0.23.0': + resolution: {integrity: sha512-8w0sMCNMwBv2kv5+fczGeSVlNOL+BOKChSsO4usM0hMw3PmxasONPctQBsESDuPS8lQ6/AKAQc2HT/ddd5Mg5w==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [darwin] + + '@lancedb/lancedb-linux-arm64-gnu@0.23.0': + resolution: {integrity: sha512-+xse2IspO7hbuHT4H62q8Ct00fTojnuBxXp1X1I3/27dDvW8E+/itFiJuTZ0YMaJc7nNr9qh9YFXZ9hZdEmReg==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [linux] + + '@lancedb/lancedb-linux-arm64-musl@0.23.0': + resolution: {integrity: sha512-c2UCtGoYjA3oDdw5y3RLK7J2th3rSjYBng+1I03vU9g092y8KATAJO/lV2AtyxSC+esSuyY1dMEaj8ADcXjZAA==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [linux] + + '@lancedb/lancedb-linux-x64-gnu@0.23.0': + resolution: {integrity: sha512-OPL7tK3JCTx43ZxvbVs+CljfCer0KrojANQbcJ2V4VAp6XBhKx1sBAlIVGuCrd93pA8UOUP3iHsM7aglPo6rCg==} + engines: {node: '>= 18'} + cpu: [x64] + os: [linux] + + '@lancedb/lancedb-linux-x64-musl@0.23.0': + resolution: {integrity: sha512-1ZEoQDwOrKvwPyAG+95/r1NYqX8Ca5bRek8Vr62CzWCEmHd/pFeEGWZ5STrkh+Bt3GLdi2JOivFtRbmuBAJypQ==} + engines: {node: '>= 18'} + cpu: [x64] + os: [linux] + + '@lancedb/lancedb-win32-arm64-msvc@0.23.0': + resolution: {integrity: sha512-OuD1mkrgXvijRlXdbx3LvfuorO04FD5qHegnTOWGXh1sIwwrvvhcJAvXUGBNLY4n/lsWvA+xTjtMwRjUitvPKg==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [win32] + + '@lancedb/lancedb-win32-x64-msvc@0.23.0': + resolution: {integrity: sha512-5ve1hvVtp8zWxSE9A+MOQaicXl2Rn0ZG/NUaMTjTD3/CQHPKFmtrqDnM5khoPICTj2O2b10F6mn4cUzl5PASgA==} + engines: {node: '>= 18'} + cpu: [x64] + os: [win32] + '@lancedb/lancedb@0.23.0': resolution: {integrity: sha512-aYrIoEG24AC+wILCL57Ius/Y4yU+xFHDPKLvmjzzN4byAjzeIGF0TC86S5RBt4Ji+dxS7yIWV5Q/gE5/fybIFQ==} engines: {node: '>= 18'} @@ -1249,6 +1279,9 @@ packages: peerDependencies: apache-arrow: '>=15.0.0 <=18.1.0' + '@larksuiteoapi/node-sdk@1.56.1': + resolution: {integrity: sha512-/ixtyJnWOmcupKgDXz+6G6qTLMi3cNrR+LGOuq2PMwcJ6hhXTUJNyAF+ADY7ah9OoeDniGU/UJwMb2gqKdxwcA==} + '@line/bot-sdk@10.6.0': resolution: {integrity: sha512-4hSpglL/G/cW2JCcohaYz/BS0uOSJNV9IEYdMm0EiPEvDLayoI2hGq2D86uYPQFD2gvgkyhmzdShpWLG3P5r3w==} engines: {node: '>=20'} @@ -1320,28 +1353,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@mariozechner/clipboard-linux-riscv64-gnu@0.3.0': resolution: {integrity: sha512-4BC08CIaOXSSAGRZLEjqJmQfioED8ohAzwt0k2amZPEbH96YKoBNorq5EdwPf5VT+odS0DeyCwhwtxokRLZIvQ==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@mariozechner/clipboard-linux-x64-gnu@0.3.0': resolution: {integrity: sha512-GpNY5Y9nOzr0Vt0Qi5U88qwe6piiIHk44kSMexl8ns90LluN5UTNYmyfi7Xq3/lmPZCpnB2xvBTYbsXCxnopIA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@mariozechner/clipboard-linux-x64-musl@0.3.0': resolution: {integrity: sha512-+PnR48/x9GMY5Kh8BLjzHMx6trOegMtxAuqTM9X/bhV3QuW6sLLd7nojDHSGj/ZueK6i0tcQxvOrgNLozVtNDA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@mariozechner/clipboard-win32-arm64-msvc@0.3.0': resolution: {integrity: sha512-+dy2vZ1Ph4EYj0cotB+bVUVk/uKl2bh9LOp/zlnFqoCCYDN6sm+L0VyIOPPo3hjoEVdGpHe1MUxp3qG/OLwXgg==} @@ -1442,35 +1471,30 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@napi-rs/canvas-linux-arm64-musl@0.1.88': resolution: {integrity: sha512-kYyNrUsHLkoGHBc77u4Unh067GrfiCUMbGHC2+OTxbeWfZkPt2o32UOQkhnSswKd9Fko/wSqqGkY956bIUzruA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@napi-rs/canvas-linux-riscv64-gnu@0.1.88': resolution: {integrity: sha512-HVuH7QgzB0yavYdNZDRyAsn/ejoXB0hn8twwFnOqUbCCdkV+REna7RXjSR7+PdfW0qMQ2YYWsLvVBT5iL/mGpw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@napi-rs/canvas-linux-x64-gnu@0.1.88': resolution: {integrity: sha512-hvcvKIcPEQrvvJtJnwD35B3qk6umFJ8dFIr8bSymfrSMem0EQsfn1ztys8ETIFndTwdNWJKWluvxztA41ivsEw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@napi-rs/canvas-linux-x64-musl@0.1.88': resolution: {integrity: sha512-eSMpGYY2xnZSQ6UxYJ6plDboxq4KeJ4zT5HaVkUnbObNN6DlbJe0Mclh3wifAmquXfrlgTZt6zhHsUgz++AK6g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@napi-rs/canvas-win32-arm64-msvc@0.1.88': resolution: {integrity: sha512-qcIFfEgHrchyYqRrxsCeTQgpJZ/GqHiqPcU/Fvw/ARVlQeDX1VyFH+X+0gCR2tca6UJrq96vnW+5o7buCq+erA==} @@ -1516,28 +1540,36 @@ packages: engines: {node: '>=20.0.0'} cpu: [arm64, x64] os: [linux] - libc: [glibc] '@node-llama-cpp/linux-armv7l@3.15.0': resolution: {integrity: sha512-ZuZ3q6mejQnEP4o22la7zBv7jNR+IZfgItDm3KjAl04HUXTKJ43HpNwjnf9GyYYd+dEgtoX0MESvWz4RnGH8Jw==} engines: {node: '>=20.0.0'} cpu: [arm, x64] os: [linux] - libc: [glibc] + + '@node-llama-cpp/linux-x64-cuda-ext@3.15.0': + resolution: {integrity: sha512-wQwgSl7Qm8vH56oBt7IuWWDNNsDECkVMS000C92wl3PkbzjwZFiWzehwa+kF8Lr2BBMiCJNkI5nEabhYH3RN2Q==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] + + '@node-llama-cpp/linux-x64-cuda@3.15.0': + resolution: {integrity: sha512-mDjyVulCTRYilm9Emm3lDMx7dbI1vzGqk28Pj28shartjERTUu8aUNDYOmVKNMLpUKS1akw7vy0lMF8t4qswxQ==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [linux] '@node-llama-cpp/linux-x64-vulkan@3.15.0': resolution: {integrity: sha512-htVIthQKq/rr8v5e7NiVtcHsstqTBAAC50kUymmDMbrzAu6d/EHacCJpNbU57b1UUa1nKN5cBqr6Jr+QqEalMA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@node-llama-cpp/linux-x64@3.15.0': resolution: {integrity: sha512-etUuTqSyNefRObqc5+JZviNTkuef2XEtHcQLaamEIWwjI1dj7nTD2YMZPBP7H3M3E55HSIY82vqCQ1bp6ZILiA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@node-llama-cpp/mac-arm64-metal@3.15.0': resolution: {integrity: sha512-3Vkq6bpyQZaIzoaLLP7H2Tt8ty5BS0zxUY2pX0ox2S9P4fp8Au0CCJuUJF4V+EKi+/PTn70A6R1QCkRMfMQJig==} @@ -1557,6 +1589,24 @@ packages: cpu: [arm64, x64] os: [win32] + '@node-llama-cpp/win-x64-cuda-ext@3.15.0': + resolution: {integrity: sha512-KQoNH9KsVtqGVXaRdPrnHPrg5w3KOM7CfynPmG1m16gmjmDSIspdPg/Dbg6DgHBfkdAzt+duRZEBk8Bg8KbDHw==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@node-llama-cpp/win-x64-cuda@3.15.0': + resolution: {integrity: sha512-2Kyu1roDwXwFLaJgGZQISIXP9lCDZtJCx/DRcmrYRHcSUFCzo5ikOuAECyliSSQmRUAvvlRCuD+GrTcegbhojA==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + + '@node-llama-cpp/win-x64-vulkan@3.15.0': + resolution: {integrity: sha512-sH+K7lO49WrUvCCC3RPddCBrn2ZQwKCXKL90P/NZicMRgxTPFZEVSU2jXR/bu1K8B+4lNN+z5OEbjSYs7cKEcA==} + engines: {node: '>=20.0.0'} + cpu: [x64] + os: [win32] + '@node-llama-cpp/win-x64@3.15.0': resolution: {integrity: sha512-gWhtc8l3HOry5guO46YfFohLQnF0NfL4On0GAO8E27JiYYxHO9nHSCfFif4+U03+FfHquZXL0znJ1qPVOiwOPw==} engines: {node: '>=20.0.0'} @@ -1867,25 +1917,21 @@ packages: resolution: {integrity: sha512-GubkQeQT5d3B/Jx/IiR7NMkSmXrCZcVI0BPh1i7mpFi8HgD1hQ/LbhiBKAMsMqs5bbugdQOgBEl8bOhe8JhW1g==} cpu: [arm64] os: [linux] - libc: [glibc] '@oxfmt/linux-arm64-musl@0.26.0': resolution: {integrity: sha512-OEypUwK69bFPj+aa3/LYCnlIUPgoOLu//WNcriwpnWNmt47808Ht7RJSg+MNK8a7pSZHpXJ5/E6CRK/OTwFdaQ==} cpu: [arm64] os: [linux] - libc: [musl] '@oxfmt/linux-x64-gnu@0.26.0': resolution: {integrity: sha512-xO6iEW2bC6ZHyOTPmPWrg/nM6xgzyRPaS84rATy6F8d79wz69LdRdJ3l/PXlkqhi7XoxhvX4ExysA0Nf10ZZEQ==} cpu: [x64] os: [linux] - libc: [glibc] '@oxfmt/linux-x64-musl@0.26.0': resolution: {integrity: sha512-Z3KuZFC+MIuAyFCXBHY71kCsdRq1ulbsbzTe71v+hrEv7zVBn6yzql+/AZcgfIaKzWO9OXNuz5WWLWDmVALwow==} cpu: [x64] os: [linux] - libc: [musl] '@oxfmt/win32-arm64@0.26.0': resolution: {integrity: sha512-3zRbqwVWK1mDhRhTknlQFpRFL9GhEB5GfU6U7wawnuEwpvi39q91kJ+SRJvJnhyPCARkjZBd1V8XnweN5IFd1g==} @@ -1941,25 +1987,21 @@ packages: resolution: {integrity: sha512-Fow7H84Bs8XxuaK1yfSEWBC8HI7rfEQB9eR2A0J61un1WgCas7jNrt1HbT6+p6KmUH2bhR+r/RDu/6JFAvvj4g==} cpu: [arm64] os: [linux] - libc: [glibc] '@oxlint/linux-arm64-musl@1.41.0': resolution: {integrity: sha512-WoRRDNwgP5W3rjRh42Zdx8ferYnqpKoYCv2QQLenmdrLjRGYwAd52uywfkcS45mKEWHeY1RPwPkYCSROXiGb2w==} cpu: [arm64] os: [linux] - libc: [musl] '@oxlint/linux-x64-gnu@1.41.0': resolution: {integrity: sha512-75k3CKj3fOc/a/2aSgO81s3HsTZOFROthPJ+UI2Oatic1LhvH6eKjKfx3jDDyVpzeDS2qekPlc/y3N33iZz5Og==} cpu: [x64] os: [linux] - libc: [glibc] '@oxlint/linux-x64-musl@1.41.0': resolution: {integrity: sha512-8r82eBwGPoAPn67ZvdxTlX/Z3gVb+ZtN6nbkyFzwwHWAh8yGutX+VBcVkyrePSl6XgBP4QAaddPnHmkvJjqY0g==} cpu: [x64] os: [linux] - libc: [musl] '@oxlint/win32-arm64@1.41.0': resolution: {integrity: sha512-aK+DAcckQsNCOXKruatyYuY/ROjNiRejQB1PeJtkZwM21+8rV9ODYbvKNvt0pW+YCws7svftBSFMCpl3ke2unw==} @@ -2031,28 +2073,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@reflink/reflink-linux-arm64-musl@0.1.19': resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@reflink/reflink-linux-x64-gnu@0.1.19': resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@reflink/reflink-linux-x64-musl@0.1.19': resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@reflink/reflink-win32-arm64-msvc@0.1.19': resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==} @@ -2105,28 +2143,24 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==} @@ -2188,79 +2222,66 @@ packages: resolution: {integrity: sha512-YeGUhkN1oA+iSPzzhEjVPS29YbViOr8s4lSsFaZKLHswgqP911xx25fPOyE9+khmN6W4VeM0aevbDp4kkEoHiA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.55.3': resolution: {integrity: sha512-eo0iOIOvcAlWB3Z3eh8pVM8hZ0oVkK3AjEM9nSrkSug2l15qHzF3TOwT0747omI6+CJJvl7drwZepT+re6Fy/w==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.55.3': resolution: {integrity: sha512-DJay3ep76bKUDImmn//W5SvpjRN5LmK/ntWyeJs/dcnwiiHESd3N4uteK9FDLf0S0W8E6Y0sVRXpOCoQclQqNg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.55.3': resolution: {integrity: sha512-BKKWQkY2WgJ5MC/ayvIJTHjy0JUGb5efaHCUiG/39sSUvAYRBaO3+/EK0AZT1RF3pSj86O24GLLik9mAYu0IJg==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.55.3': resolution: {integrity: sha512-Q9nVlWtKAG7ISW80OiZGxTr6rYtyDSkauHUtvkQI6TNOJjFvpj4gcH+KaJihqYInnAzEEUetPQubRwHef4exVg==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.55.3': resolution: {integrity: sha512-2H5LmhzrpC4fFRNwknzmmTvvyJPHwESoJgyReXeFoYYuIDfBhP29TEXOkCJE/KxHi27mj7wDUClNq78ue3QEBQ==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.55.3': resolution: {integrity: sha512-9S542V0ie9LCTznPYlvaeySwBeIEa7rDBgLHKZ5S9DBgcqdJYburabm8TqiqG6mrdTzfV5uttQRHcbKff9lWtA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.55.3': resolution: {integrity: sha512-ukxw+YH3XXpcezLgbJeasgxyTbdpnNAkrIlFGDl7t+pgCxZ89/6n1a+MxlY7CegU+nDgrgdqDelPRNQ/47zs0g==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.55.3': resolution: {integrity: sha512-Iauw9UsTTvlF++FhghFJjqYxyXdggXsOqGpFBylaRopVpcbfyIIsNvkf9oGwfgIcf57z3m8+/oSYTo6HutBFNw==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.55.3': resolution: {integrity: sha512-3OqKAHSEQXKdq9mQ4eajqUgNIK27VZPW3I26EP8miIzuKzCJ3aW3oEn2pzF+4/Hj/Moc0YDsOtBgT5bZ56/vcA==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.55.3': resolution: {integrity: sha512-0CM8dSVzVIaqMcXIFej8zZrSFLnGrAE8qlNbbHfTw1EEPnFTg1U1ekI0JdzjPyzSfUsHWtodilQQG/RA55berA==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.55.3': resolution: {integrity: sha512-+fgJE12FZMIgBaKIAGd45rxf+5ftcycANJRWk8Vz0NnMTM5rADPGuRFTYar+Mqs560xuART7XsX2lSACa1iOmQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.55.3': resolution: {integrity: sha512-tMD7NnbAolWPzQlJQJjVFh/fNH3K/KnA7K8gv2dJWCwwnaK6DFCYST1QXYWfu5V0cDwarWC8Sf/cfMHniNq21A==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.55.3': resolution: {integrity: sha512-u5KsqxOxjEeIbn7bUK1MPM34jrnPwjeqgyin4/N6e/KzXKfpE9Mi0nCxcQjaM9lLmPcHmn/xx1yOjgTMtu1jWQ==} @@ -2973,6 +2994,9 @@ packages: aws4@1.13.2: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} + axios@0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + axios@1.13.2: resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} @@ -3116,6 +3140,11 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + clawdbot@2026.1.24: + resolution: {integrity: sha512-foszbNXzk743kQBx2Nfc2KNlStZyBAVAYLQJ+KaONh009r8oB1a74kQ8wTnKX+SDNaQ9MvaE9tOisVUi+H3F+Q==} + engines: {node: '>=22.12.0'} + hasBin: true + cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -3983,28 +4012,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -4055,6 +4080,9 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.identity@3.0.0: + resolution: {integrity: sha512-AupTIzdLQxJS5wIYUQlgGyk2XRTfGXA+MCghDHqZk0pzUNYvd3EESS6dkChNauNYVIutcb0dfHw1ri9Q1yPV8Q==} + lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} @@ -4073,9 +4101,15 @@ packages: lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.pickby@4.6.0: + resolution: {integrity: sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==} + lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} @@ -6694,10 +6728,53 @@ snapshots: '@kwsites/promise-deferred@1.1.1': optional: true + '@lancedb/lancedb-darwin-arm64@0.23.0': + optional: true + + '@lancedb/lancedb-linux-arm64-gnu@0.23.0': + optional: true + + '@lancedb/lancedb-linux-arm64-musl@0.23.0': + optional: true + + '@lancedb/lancedb-linux-x64-gnu@0.23.0': + optional: true + + '@lancedb/lancedb-linux-x64-musl@0.23.0': + optional: true + + '@lancedb/lancedb-win32-arm64-msvc@0.23.0': + optional: true + + '@lancedb/lancedb-win32-x64-msvc@0.23.0': + optional: true + '@lancedb/lancedb@0.23.0(apache-arrow@18.1.0)': dependencies: apache-arrow: 18.1.0 reflect-metadata: 0.2.2 + optionalDependencies: + '@lancedb/lancedb-darwin-arm64': 0.23.0 + '@lancedb/lancedb-linux-arm64-gnu': 0.23.0 + '@lancedb/lancedb-linux-arm64-musl': 0.23.0 + '@lancedb/lancedb-linux-x64-gnu': 0.23.0 + '@lancedb/lancedb-linux-x64-musl': 0.23.0 + '@lancedb/lancedb-win32-arm64-msvc': 0.23.0 + '@lancedb/lancedb-win32-x64-msvc': 0.23.0 + + '@larksuiteoapi/node-sdk@1.56.1': + dependencies: + axios: 0.27.2 + lodash.identity: 3.0.0 + lodash.merge: 4.6.2 + lodash.pickby: 4.6.0 + protobufjs: 7.5.4 + qs: 6.14.1 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate '@line/bot-sdk@10.6.0': dependencies: @@ -7006,6 +7083,12 @@ snapshots: '@node-llama-cpp/linux-armv7l@3.15.0': optional: true + '@node-llama-cpp/linux-x64-cuda-ext@3.15.0': + optional: true + + '@node-llama-cpp/linux-x64-cuda@3.15.0': + optional: true + '@node-llama-cpp/linux-x64-vulkan@3.15.0': optional: true @@ -7021,6 +7104,15 @@ snapshots: '@node-llama-cpp/win-arm64@3.15.0': optional: true + '@node-llama-cpp/win-x64-cuda-ext@3.15.0': + optional: true + + '@node-llama-cpp/win-x64-cuda@3.15.0': + optional: true + + '@node-llama-cpp/win-x64-vulkan@3.15.0': + optional: true + '@node-llama-cpp/win-x64@3.15.0': optional: true @@ -8645,6 +8737,13 @@ snapshots: aws4@1.13.2: {} + axios@0.27.2: + dependencies: + follow-redirects: 1.15.11(debug@4.4.3) + form-data: 4.0.5 + transitivePeerDependencies: + - debug + axios@1.13.2(debug@4.4.3): dependencies: follow-redirects: 1.15.11(debug@4.4.3) @@ -8814,6 +8913,84 @@ snapshots: dependencies: clsx: 2.1.1 + clawdbot@2026.1.24(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3): + dependencies: + '@agentclientprotocol/sdk': 0.13.1(zod@4.3.6) + '@aws-sdk/client-bedrock': 3.975.0 + '@buape/carbon': 0.14.0(hono@4.11.4) + '@clack/prompts': 0.11.0 + '@grammyjs/runner': 2.0.3(grammy@1.39.3) + '@grammyjs/transformer-throttler': 1.2.1(grammy@1.39.3) + '@homebridge/ciao': 1.3.4 + '@line/bot-sdk': 10.6.0 + '@lydell/node-pty': 1.2.0-beta.3 + '@mariozechner/pi-agent-core': 0.49.3(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-ai': 0.49.3(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-coding-agent': 0.49.3(ws@8.19.0)(zod@4.3.6) + '@mariozechner/pi-tui': 0.49.3 + '@mozilla/readability': 0.6.0 + '@sinclair/typebox': 0.34.47 + '@slack/bolt': 4.6.0(@types/express@5.0.6) + '@slack/web-api': 7.13.0 + '@whiskeysockets/baileys': 7.0.0-rc.9(audio-decode@2.2.3)(sharp@0.34.5) + ajv: 8.17.1 + body-parser: 2.2.2 + chalk: 5.6.2 + chokidar: 5.0.0 + chromium-bidi: 13.0.1(devtools-protocol@0.0.1561482) + cli-highlight: 2.1.11 + commander: 14.0.2 + croner: 9.1.0 + detect-libc: 2.1.2 + discord-api-types: 0.38.37 + dotenv: 17.2.3 + express: 5.2.1 + file-type: 21.3.0 + grammy: 1.39.3 + hono: 4.11.4 + jiti: 2.6.1 + json5: 2.2.3 + jszip: 3.10.1 + linkedom: 0.18.12 + long: 5.3.2 + markdown-it: 14.1.0 + node-edge-tts: 1.2.9 + osc-progress: 0.3.0 + pdfjs-dist: 5.4.530 + playwright-core: 1.58.0 + proper-lockfile: 4.1.2 + qrcode-terminal: 0.12.0 + sharp: 0.34.5 + sqlite-vec: 0.1.7-alpha.2 + tar: 7.5.4 + tslog: 4.10.2 + undici: 7.19.0 + ws: 8.19.0 + yaml: 2.8.2 + zod: 4.3.6 + optionalDependencies: + '@napi-rs/canvas': 0.1.88 + node-llama-cpp: 3.15.0(typescript@5.9.3) + transitivePeerDependencies: + - '@discordjs/opus' + - '@modelcontextprotocol/sdk' + - '@types/express' + - audio-decode + - aws-crt + - bufferutil + - canvas + - debug + - devtools-protocol + - encoding + - ffmpeg-static + - jimp + - link-preview-js + - node-opus + - opusscript + - supports-color + - typescript + - utf-8-validate + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -9865,6 +10042,8 @@ snapshots: lodash.debounce@4.0.8: optional: true + lodash.identity@3.0.0: {} + lodash.includes@4.3.0: {} lodash.isboolean@3.0.3: {} @@ -9877,8 +10056,12 @@ snapshots: lodash.isstring@4.0.1: {} + lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash.pickby@4.6.0: {} + lodash@4.17.23: {} log-symbols@6.0.0: @@ -10163,11 +10346,16 @@ snapshots: '@node-llama-cpp/linux-arm64': 3.15.0 '@node-llama-cpp/linux-armv7l': 3.15.0 '@node-llama-cpp/linux-x64': 3.15.0 + '@node-llama-cpp/linux-x64-cuda': 3.15.0 + '@node-llama-cpp/linux-x64-cuda-ext': 3.15.0 '@node-llama-cpp/linux-x64-vulkan': 3.15.0 '@node-llama-cpp/mac-arm64-metal': 3.15.0 '@node-llama-cpp/mac-x64': 3.15.0 '@node-llama-cpp/win-arm64': 3.15.0 '@node-llama-cpp/win-x64': 3.15.0 + '@node-llama-cpp/win-x64-cuda': 3.15.0 + '@node-llama-cpp/win-x64-cuda-ext': 3.15.0 + '@node-llama-cpp/win-x64-vulkan': 3.15.0 typescript: 5.9.3 transitivePeerDependencies: - supports-color