feat:support feshu via websocket

This commit is contained in:
ie2718 2026-01-26 23:10:45 +08:00
parent b560bdeab6
commit bd23d04ce2
9 changed files with 409 additions and 475 deletions

View File

@ -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.<chat_id>.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.<id>.enabled`: enable/disable account
- `channels.feishu.accounts.<id>.dmPolicy`: per-account DM policy
- `channels.feishu.accounts.<id>.allowFrom`: per-account allowlist
- `channels.feishu.accounts.<id>.verificationToken`: per-account verification token
- `channels.feishu.accounts.<id>.encryptKey`: per-account encrypt key
- `channels.feishu.accounts.<id>.webhookPath`: per-account webhook path
## International users (Lark)
For Lark (international version), use the same configuration. The API endpoints are compatible.

View File

@ -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
},
};

View File

@ -27,6 +27,9 @@
"defaultChoice": "npm"
}
},
"dependencies": {
"@larksuiteoapi/node-sdk": "^1.56.1"
},
"devDependencies": {
"clawdbot": "workspace:*"
},

View File

@ -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 });
}

View File

@ -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<typeof getFeishuRuntime>;
@ -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<string, WebhookTarget[]>();
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<boolean> {
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<FeishuMonitorResult> {
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 });

View File

@ -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 },
);

View File

@ -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<string, unknown>;
// 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;
};

View File

@ -9,6 +9,6 @@
]
},
"peerDependencies": {
"clawdbot": ">=2026.1.24-3"
"clawdbot": ">=2026.1.24"
}
}

316
pnpm-lock.yaml generated
View File

@ -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