feat(channels): add dingtalk support

This commit is contained in:
wkgg 2026-01-29 12:59:53 +08:00
parent fcc53bcf1b
commit 80c54ba057
20 changed files with 2126 additions and 207 deletions

5
.github/labeler.yml vendored
View File

@ -3,6 +3,11 @@
- any-glob-to-any-file: - any-glob-to-any-file:
- "extensions/bluebubbles/**" - "extensions/bluebubbles/**"
- "docs/channels/bluebubbles.md" - "docs/channels/bluebubbles.md"
"channel: dingtalk":
- changed-files:
- any-glob-to-any-file:
- "extensions/dingtalk/**"
- "docs/channels/dingtalk.md"
"channel: discord": "channel: discord":
- changed-files: - changed-files:
- any-glob-to-any-file: - any-glob-to-any-file:

61
docs/channels/dingtalk.md Normal file
View File

@ -0,0 +1,61 @@
---
summary: "DingTalk bot support status, capabilities, and configuration"
read_when:
- Working on DingTalk channel features
---
# DingTalk
Status: beta; inbound via WebSocket using `dingtalk-stream` SDK.
## Quick setup
1) Create an enterprise internal robot in DingTalk Open Platform.
2) Enable "Stream Mode" in robot settings.
3) Configure `channels.dingtalk.clientId` and `channels.dingtalk.clientSecret`.
4) Start the gateway.
Minimal config:
```json5
{
channels: {
dingtalk: {
enabled: true,
clientId: "YOUR_APP_KEY",
clientSecret: "YOUR_APP_SECRET"
}
}
}
```
Multi-account example:
```json5
{
channels: {
dingtalk: {
accounts: {
work: { clientId: "APP_KEY", clientSecret: "APP_SECRET" },
personal: { clientId: "APP_KEY", clientSecret: "APP_SECRET" }
}
}
}
}
```
## How it works
- Messages are received over the DingTalk WebSocket stream (no public URL needed).
- Replies are sent back via the session webhook provided in each message.
- DMs use pairing by default (`channels.dingtalk.dm.policy`).
- Group chats can be restricted with `channels.dingtalk.groupPolicy` and `channels.dingtalk.groups`.
## Target formats
- `dingtalk:<conversation_id>` for group chats.
- `dingtalk:<user_id>` for direct messages.
## Configuration reference (DingTalk)
- `channels.dingtalk.clientId`: DingTalk AppKey (Client ID).
- `channels.dingtalk.clientSecret`: DingTalk AppSecret (Client Secret).
- `channels.dingtalk.dm.policy`: DM policy (`pairing`, `allowlist`, `open`, `disabled`).
- `channels.dingtalk.dm.allowFrom`: allowlist for DMs when policy is `allowlist` or `open`.
- `channels.dingtalk.groupPolicy`: `open`, `allowlist`, or `disabled`.
- `channels.dingtalk.groups`: per-chat overrides keyed by `conversation_id` (supports `requireMention`, `tools`, `users`).
- `channels.dingtalk.requireMention`: whether the bot must be @mentioned in groups (default: `true`).
- `channels.dingtalk.textChunkLimit`: max characters per message chunk (default: `4000`).

View File

@ -0,0 +1,18 @@
import type { MoltbotPluginApi } from "clawdbot/plugin-sdk";
import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";
import { dingtalkDock, dingtalkPlugin } from "./src/channel.js";
import { setDingTalkRuntime } from "./src/runtime.js";
const plugin = {
id: "dingtalk",
name: "DingTalk",
description: "Moltbot DingTalk channel plugin (Stream mode)",
configSchema: emptyPluginConfigSchema(),
register(api: MoltbotPluginApi) {
setDingTalkRuntime(api.runtime);
api.registerChannel({ plugin: dingtalkPlugin, dock: dingtalkDock });
},
};
export default plugin;

View File

@ -0,0 +1,11 @@
{
"id": "dingtalk",
"channels": [
"dingtalk"
],
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
}
}

View File

@ -0,0 +1,39 @@
{
"name": "@moltbot/dingtalk",
"version": "2026.1.27-beta.1",
"type": "module",
"description": "Moltbot DingTalk channel plugin",
"moltbot": {
"extensions": [
"./index.ts"
],
"channel": {
"id": "dingtalk",
"label": "DingTalk",
"selectionLabel": "DingTalk (钉钉)",
"detailLabel": "DingTalk",
"docsPath": "/channels/dingtalk",
"docsLabel": "dingtalk",
"blurb": "DingTalk enterprise robot via Stream mode.",
"aliases": [
"dingding",
"ding"
],
"order": 56
},
"install": {
"npmSpec": "@moltbot/dingtalk",
"localPath": "extensions/dingtalk",
"defaultChoice": "npm"
}
},
"dependencies": {
"dingtalk-stream": "^2.1.4"
},
"devDependencies": {
"moltbot": "workspace:*"
},
"peerDependencies": {
"moltbot": ">=2026.1.26"
}
}

View File

@ -0,0 +1,125 @@
import type { DingTalkAccountConfig, MoltbotConfig } from "clawdbot/plugin-sdk";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "clawdbot/plugin-sdk";
import type { DingTalkConfig } from "./types.js";
export type DingTalkCredentialSource = "config" | "env" | "none";
export type ResolvedDingTalkAccount = {
accountId: string;
name?: string;
enabled: boolean;
config: DingTalkAccountConfig;
credentialSource: DingTalkCredentialSource;
/** Stream mode: Client ID (AppKey) */
clientId?: string;
/** Stream mode: Client Secret (AppSecret) */
clientSecret?: string;
};
// Stream mode environment variables
const ENV_CLIENT_ID = "DINGTALK_CLIENT_ID";
const ENV_CLIENT_SECRET = "DINGTALK_CLIENT_SECRET";
function listConfiguredAccountIds(cfg: MoltbotConfig): string[] {
const accounts = (cfg.channels?.dingtalk as DingTalkConfig | undefined)?.accounts;
if (!accounts || typeof accounts !== "object") return [];
return Object.keys(accounts).filter(Boolean);
}
export function listDingTalkAccountIds(cfg: MoltbotConfig): string[] {
const ids = listConfiguredAccountIds(cfg);
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
return ids.sort((a, b) => a.localeCompare(b));
}
export function resolveDefaultDingTalkAccountId(cfg: MoltbotConfig): string {
const channel = cfg.channels?.dingtalk as DingTalkConfig | undefined;
if (channel?.defaultAccount?.trim()) return channel.defaultAccount.trim();
const ids = listDingTalkAccountIds(cfg);
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
return ids[0] ?? DEFAULT_ACCOUNT_ID;
}
function resolveAccountConfig(
cfg: MoltbotConfig,
accountId: string,
): DingTalkAccountConfig | undefined {
const accounts = (cfg.channels?.dingtalk as DingTalkConfig | undefined)?.accounts;
if (!accounts || typeof accounts !== "object") return undefined;
return accounts[accountId] as DingTalkAccountConfig | undefined;
}
function mergeDingTalkAccountConfig(
cfg: MoltbotConfig,
accountId: string,
): DingTalkAccountConfig {
const raw = (cfg.channels?.dingtalk ?? {}) as DingTalkConfig;
const { accounts: _ignored, ...base } = raw;
const account = resolveAccountConfig(cfg, accountId) ?? {};
return { ...base, ...account } as DingTalkAccountConfig;
}
function resolveCredentialsFromConfig(params: {
accountId: string;
account: DingTalkAccountConfig;
}): {
clientId?: string;
clientSecret?: string;
source: DingTalkCredentialSource;
} {
const { account, accountId } = params;
// Check Stream mode credentials from config
if (account.clientId?.trim() && account.clientSecret?.trim()) {
return {
clientId: account.clientId.trim(),
clientSecret: account.clientSecret.trim(),
source: "config",
};
}
// Check environment variables for default account
if (accountId === DEFAULT_ACCOUNT_ID) {
const envClientId = process.env[ENV_CLIENT_ID]?.trim();
const envClientSecret = process.env[ENV_CLIENT_SECRET]?.trim();
if (envClientId && envClientSecret) {
return {
clientId: envClientId,
clientSecret: envClientSecret,
source: "env",
};
}
}
return { source: "none" };
}
export function resolveDingTalkAccount(params: {
cfg: MoltbotConfig;
accountId?: string | null;
}): ResolvedDingTalkAccount {
const accountId = normalizeAccountId(params.accountId);
const baseEnabled =
(params.cfg.channels?.dingtalk as DingTalkConfig | undefined)?.enabled !== false;
const merged = mergeDingTalkAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;
const credentials = resolveCredentialsFromConfig({ accountId, account: merged });
return {
accountId,
name: merged.name?.trim() || undefined,
enabled,
config: merged,
credentialSource: credentials.source,
clientId: credentials.clientId,
clientSecret: credentials.clientSecret,
};
}
export function listEnabledDingTalkAccounts(cfg: MoltbotConfig): ResolvedDingTalkAccount[] {
return listDingTalkAccountIds(cfg)
.map((accountId) => resolveDingTalkAccount({ cfg, accountId }))
.filter((account) => account.enabled);
}

View File

@ -0,0 +1,250 @@
import crypto from "node:crypto";
import type { ResolvedDingTalkAccount } from "./accounts.js";
import type {
DingTalkApiResponse,
DingTalkOutboundMessage,
DingTalkTextMessage,
DingTalkMarkdownMessage,
} from "./types.js";
const DINGTALK_ROBOT_SEND_URL = "https://oapi.dingtalk.com/robot/send";
/**
* Generate HMAC-SHA256 signature for DingTalk webhook authentication.
* Format: timestamp + "\n" + secret, then HMAC-SHA256, base64, URL encode
* @deprecated Legacy webhook mode - use Stream mode instead
*/
export function generateSignature(timestamp: number, secret: string): string {
const stringToSign = `${timestamp}\n${secret}`;
const hmac = crypto.createHmac("sha256", secret);
hmac.update(stringToSign, "utf8");
const digest = hmac.digest("base64");
return encodeURIComponent(digest);
}
/**
* Verify incoming webhook signature from DingTalk.
* The signature is in request headers: timestamp and sign.
* @deprecated Legacy webhook mode - use Stream mode instead
*/
export function verifySignature(params: {
timestamp: string;
sign: string;
secret: string;
}): { ok: boolean; error?: string } {
const { timestamp, sign, secret } = params;
// Check timestamp is within 1 hour
const ts = Number.parseInt(timestamp, 10);
if (!Number.isFinite(ts)) {
return { ok: false, error: "invalid timestamp" };
}
const now = Date.now();
if (Math.abs(now - ts) > 60 * 60 * 1000) {
return { ok: false, error: "timestamp expired" };
}
// Verify signature
const expected = generateSignature(ts, secret);
// URL decode the incoming sign for comparison
const decodedSign = decodeURIComponent(sign);
const decodedExpected = decodeURIComponent(expected);
if (decodedSign !== decodedExpected) {
return { ok: false, error: "signature mismatch" };
}
return { ok: true };
}
/**
* Build the full webhook URL with authentication parameters.
* @deprecated Legacy webhook mode - use Stream mode instead
*/
export function buildWebhookUrl(params: {
accessToken: string;
secret?: string;
}): string {
const { accessToken, secret } = params;
const url = new URL(DINGTALK_ROBOT_SEND_URL);
url.searchParams.set("access_token", accessToken);
if (secret) {
const timestamp = Date.now();
const sign = generateSignature(timestamp, secret);
url.searchParams.set("timestamp", String(timestamp));
url.searchParams.set("sign", sign);
}
return url.toString();
}
/**
* Send a message to DingTalk robot webhook.
*/
export async function sendDingTalkMessage(params: {
accessToken: string;
secret?: string;
message: DingTalkOutboundMessage;
}): Promise<{ ok: boolean; response?: DingTalkApiResponse; error?: string }> {
const { accessToken, secret, message } = params;
const url = buildWebhookUrl({ accessToken, secret });
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(message),
});
const data = (await response.json()) as DingTalkApiResponse;
if (data.errcode !== 0) {
return {
ok: false,
response: data,
error: `DingTalk API error: ${data.errmsg} (code: ${data.errcode})`,
};
}
return { ok: true, response: data };
} catch (err) {
return {
ok: false,
error: err instanceof Error ? err.message : String(err),
};
}
}
/**
* Send a text message to DingTalk.
*/
export async function sendTextMessage(params: {
accessToken: string;
secret?: string;
content: string;
atUserIds?: string[];
atMobiles?: string[];
isAtAll?: boolean;
}): Promise<{ ok: boolean; error?: string }> {
const { accessToken, secret, content, atUserIds, atMobiles, isAtAll } = params;
const message: DingTalkTextMessage = {
msgtype: "text",
text: {
content,
},
};
if (atUserIds?.length || atMobiles?.length || isAtAll) {
message.at = {
atUserIds,
atMobiles,
isAtAll,
};
}
return sendDingTalkMessage({ accessToken, secret, message });
}
/**
* Send a markdown message to DingTalk.
*/
export async function sendMarkdownMessage(params: {
accessToken: string;
secret?: string;
title: string;
text: string;
atUserIds?: string[];
atMobiles?: string[];
isAtAll?: boolean;
}): Promise<{ ok: boolean; error?: string }> {
const { accessToken, secret, title, text, atUserIds, atMobiles, isAtAll } = params;
const message: DingTalkMarkdownMessage = {
msgtype: "markdown",
markdown: {
title,
text,
},
};
if (atUserIds?.length || atMobiles?.length || isAtAll) {
message.at = {
atUserIds,
atMobiles,
isAtAll,
};
}
return sendDingTalkMessage({ accessToken, secret, message });
}
/**
* Reply to a message using the session webhook.
* This is the preferred method when replying to incoming messages.
*/
export async function replyWithSessionWebhook(params: {
sessionWebhook: string;
message: DingTalkOutboundMessage;
}): Promise<{ ok: boolean; response?: DingTalkApiResponse; error?: string }> {
const { sessionWebhook, message } = params;
try {
const response = await fetch(sessionWebhook, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(message),
});
const data = (await response.json()) as DingTalkApiResponse;
if (data.errcode !== 0) {
return {
ok: false,
response: data,
error: `DingTalk API error: ${data.errmsg} (code: ${data.errcode})`,
};
}
return { ok: true, response: data };
} catch (err) {
return {
ok: false,
error: err instanceof Error ? err.message : String(err),
};
}
}
/**
* Probe DingTalk connection.
* For Stream mode, we validate clientId and clientSecret are configured.
*/
export async function probeDingTalk(account: ResolvedDingTalkAccount): Promise<{
ok: boolean;
error?: string;
configured: boolean;
mode: "stream" | "none";
}> {
// Check Stream mode credentials
if (account.clientId?.trim() && account.clientSecret?.trim()) {
return {
ok: true,
configured: true,
mode: "stream",
};
}
return {
ok: false,
configured: false,
mode: "none",
error: "DingTalk credentials not configured. Set clientId and clientSecret for Stream mode.",
};
}

View File

@ -0,0 +1,430 @@
import {
applyAccountNameToChannelSection,
DEFAULT_ACCOUNT_ID,
deleteAccountFromConfigSection,
emptyPluginConfigSchema,
formatPairingApproveHint,
getChatChannelMeta,
migrateBaseNameToDefaultAccount,
normalizeAccountId,
setAccountEnabledInConfigSection,
type ChannelDock,
type ChannelPlugin,
type MoltbotConfig,
} from "clawdbot/plugin-sdk";
import {
listDingTalkAccountIds,
resolveDefaultDingTalkAccountId,
resolveDingTalkAccount,
type ResolvedDingTalkAccount,
} from "./accounts.js";
import { probeDingTalk } from "./api.js";
import { dingtalkOnboardingAdapter } from "./onboarding.js";
import { getDingTalkRuntime } from "./runtime.js";
import { startDingTalkMonitor } from "./monitor.js";
const meta = getChatChannelMeta("dingtalk");
const formatAllowFromEntry = (entry: string) =>
entry
.trim()
.replace(/^(dingtalk|dingding|ding):/i, "")
.toLowerCase();
export const dingtalkDock: ChannelDock = {
id: "dingtalk",
capabilities: {
chatTypes: ["direct", "group"],
reactions: false,
media: false, // DingTalk robot API has limited media support
threads: false,
blockStreaming: true,
},
outbound: { textChunkLimit: 4000 },
config: {
resolveAllowFrom: ({ cfg, accountId }) =>
(
resolveDingTalkAccount({ cfg: cfg as MoltbotConfig, accountId }).config.dm?.allowFrom ?? []
).map((entry) => String(entry)),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry))
.filter(Boolean)
.map(formatAllowFromEntry),
},
groups: {
resolveRequireMention: ({ cfg, accountId }) => {
const account = resolveDingTalkAccount({ cfg: cfg as MoltbotConfig, accountId });
return account.config.requireMention ?? true;
},
},
threading: {
resolveReplyToMode: ({ cfg }) => cfg.channels?.["dingtalk"]?.replyToMode ?? "off",
buildToolContext: ({ context, hasRepliedRef }) => ({
currentChannelId: context.To?.trim() || undefined,
hasRepliedRef,
}),
},
};
export const dingtalkPlugin: ChannelPlugin<ResolvedDingTalkAccount> = {
id: "dingtalk",
meta: { ...meta },
onboarding: dingtalkOnboardingAdapter,
pairing: {
idLabel: "dingtalkUserId",
normalizeAllowEntry: (entry) => formatAllowFromEntry(entry),
notifyApproval: async ({ cfg, id }) => {
const account = resolveDingTalkAccount({ cfg: cfg as MoltbotConfig });
if (account.credentialSource === "none" || !account.clientId) return;
// Note: DingTalk doesn't support sending DMs directly via robot API
// The user will need to @mention the robot first to establish a session
// This is a limitation of the DingTalk platform
},
},
capabilities: {
chatTypes: ["direct", "group"],
reactions: false,
threads: false,
media: false,
nativeCommands: false,
blockStreaming: true,
},
streaming: {
blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
},
reload: { configPrefixes: ["channels.dingtalk"] },
configSchema: emptyPluginConfigSchema(),
config: {
listAccountIds: (cfg) => listDingTalkAccountIds(cfg as MoltbotConfig),
resolveAccount: (cfg, accountId) =>
resolveDingTalkAccount({ cfg: cfg as MoltbotConfig, accountId }),
defaultAccountId: (cfg) => resolveDefaultDingTalkAccountId(cfg as MoltbotConfig),
setAccountEnabled: ({ cfg, accountId, enabled }) =>
setAccountEnabledInConfigSection({
cfg: cfg as MoltbotConfig,
sectionKey: "dingtalk",
accountId,
enabled,
allowTopLevel: true,
}),
deleteAccount: ({ cfg, accountId }) =>
deleteAccountFromConfigSection({
cfg: cfg as MoltbotConfig,
sectionKey: "dingtalk",
accountId,
clearBaseFields: ["accessToken", "accessTokenFile", "secret", "secretFile", "webhookPath", "webhookUrl", "name"],
}),
isConfigured: (account) => account.credentialSource !== "none",
describeAccount: (account) => ({
accountId: account.accountId,
name: account.name,
enabled: account.enabled,
configured: account.credentialSource !== "none",
credentialSource: account.credentialSource,
}),
resolveAllowFrom: ({ cfg, accountId }) =>
(
resolveDingTalkAccount({
cfg: cfg as MoltbotConfig,
accountId,
}).config.dm?.allowFrom ?? []
).map((entry) => String(entry)),
formatAllowFrom: ({ allowFrom }) =>
allowFrom
.map((entry) => String(entry))
.filter(Boolean)
.map(formatAllowFromEntry),
},
security: {
resolveDmPolicy: ({ cfg, accountId, account }) => {
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
const useAccountPath = Boolean(
(cfg as MoltbotConfig).channels?.["dingtalk"]?.accounts?.[resolvedAccountId],
);
const allowFromPath = useAccountPath
? `channels.dingtalk.accounts.${resolvedAccountId}.dm.`
: "channels.dingtalk.dm.";
return {
policy: account.config.dm?.policy ?? "pairing",
allowFrom: account.config.dm?.allowFrom ?? [],
allowFromPath,
approveHint: formatPairingApproveHint("dingtalk"),
normalizeEntry: (raw) => formatAllowFromEntry(raw),
};
},
collectWarnings: ({ account, cfg }) => {
const warnings: string[] = [];
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
if (groupPolicy === "open") {
warnings.push(
`- DingTalk groups: groupPolicy="open" allows any group to trigger (mention-gated). Set channels.dingtalk.groupPolicy="allowlist" and configure channels.dingtalk.groups.`,
);
}
if (account.config.dm?.policy === "open") {
warnings.push(
`- DingTalk DMs are open to anyone. Set channels.dingtalk.dm.policy="pairing" or "allowlist".`,
);
}
return warnings;
},
},
groups: {
resolveRequireMention: ({ cfg, accountId }) => {
const account = resolveDingTalkAccount({ cfg: cfg as MoltbotConfig, accountId });
return account.config.requireMention ?? true;
},
},
threading: {
resolveReplyToMode: ({ cfg }) => cfg.channels?.["dingtalk"]?.replyToMode ?? "off",
},
messaging: {
normalizeTarget: (raw) => {
const trimmed = raw?.trim();
if (!trimmed) return null;
return trimmed.replace(/^(dingtalk|dingding|ding):/i, "");
},
targetResolver: {
looksLikeId: (raw, normalized) => {
const value = normalized ?? raw.trim();
return Boolean(value);
},
hint: "<conversationId>",
},
},
directory: {
self: async () => null,
listPeers: async ({ cfg, accountId, query, limit }) => {
const account = resolveDingTalkAccount({
cfg: cfg as MoltbotConfig,
accountId,
});
const q = query?.trim().toLowerCase() || "";
const allowFrom = account.config.dm?.allowFrom ?? [];
const peers = Array.from(
new Set(
allowFrom
.map((entry) => String(entry).trim())
.filter((entry) => Boolean(entry) && entry !== "*"),
),
)
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
.slice(0, limit && limit > 0 ? limit : undefined)
.map((id) => ({ kind: "user", id }) as const);
return peers;
},
listGroups: async ({ cfg, accountId, query, limit }) => {
const account = resolveDingTalkAccount({
cfg: cfg as MoltbotConfig,
accountId,
});
const groups = account.config.groups ?? {};
const q = query?.trim().toLowerCase() || "";
const entries = Object.keys(groups)
.filter((key) => key && key !== "*")
.filter((key) => (q ? key.toLowerCase().includes(q) : true))
.slice(0, limit && limit > 0 ? limit : undefined)
.map((id) => ({ kind: "group", id }) as const);
return entries;
},
},
setup: {
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
applyAccountName: ({ cfg, accountId, name }) =>
applyAccountNameToChannelSection({
cfg: cfg as MoltbotConfig,
channelKey: "dingtalk",
accountId,
name,
}),
validateInput: ({ accountId, input }) => {
if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) {
return "DINGTALK_ACCESS_TOKEN env var can only be used for the default account.";
}
if (!input.useEnv && !input.token && !input.tokenFile) {
return "DingTalk requires access token or --token-file (or --use-env).";
}
return null;
},
applyAccountConfig: ({ cfg, accountId, input }) => {
const namedConfig = applyAccountNameToChannelSection({
cfg: cfg as MoltbotConfig,
channelKey: "dingtalk",
accountId,
name: input.name,
});
const next =
accountId !== DEFAULT_ACCOUNT_ID
? migrateBaseNameToDefaultAccount({
cfg: namedConfig as MoltbotConfig,
channelKey: "dingtalk",
})
: namedConfig;
const patch = input.useEnv
? {}
: input.tokenFile
? { accessTokenFile: input.tokenFile }
: input.token
? { accessToken: input.token }
: {};
const webhookPath = input.webhookPath?.trim();
const webhookUrl = input.webhookUrl?.trim();
const configPatch = {
...patch,
...(webhookPath ? { webhookPath } : {}),
...(webhookUrl ? { webhookUrl } : {}),
};
if (accountId === DEFAULT_ACCOUNT_ID) {
return {
...next,
channels: {
...next.channels,
dingtalk: {
...(next.channels?.["dingtalk"] ?? {}),
enabled: true,
...configPatch,
},
},
} as MoltbotConfig;
}
return {
...next,
channels: {
...next.channels,
dingtalk: {
...(next.channels?.["dingtalk"] ?? {}),
enabled: true,
accounts: {
...(next.channels?.["dingtalk"]?.accounts ?? {}),
[accountId]: {
...(next.channels?.["dingtalk"]?.accounts?.[accountId] ?? {}),
enabled: true,
...configPatch,
},
},
},
},
} as MoltbotConfig;
},
},
outbound: {
deliveryMode: "direct",
chunker: (text: string, limit: number) =>
getDingTalkRuntime().channel.text.chunkMarkdownText(text, limit),
chunkerMode: "markdown",
textChunkLimit: 4000,
sendText: async ({ cfg, to, text, accountId }) => {
const account = resolveDingTalkAccount({
cfg: cfg as MoltbotConfig,
accountId,
});
// Stream mode doesn't support outbound messages via accessToken
// Messages are sent via sessionWebhook in response to inbound messages
if (!account.clientId) {
throw new Error("DingTalk clientId not configured");
}
// For outbound messages, we need to use a different API
// This is a limitation of DingTalk's robot API
throw new Error(
"DingTalk Stream mode does not support proactive outbound messages. Messages can only be sent in response to user messages.",
);
},
sendMedia: async () => {
// DingTalk robot API has very limited media support
// Media messages typically require different API endpoints
throw new Error("DingTalk robot API does not support media attachments");
},
},
status: {
defaultRuntime: {
accountId: DEFAULT_ACCOUNT_ID,
running: false,
lastStartAt: null,
lastStopAt: null,
lastError: null,
},
collectStatusIssues: (accounts: Array<{ accountId?: string; enabled?: boolean; configured?: boolean; clientId?: string }>) =>
accounts.flatMap((entry) => {
const accountId = String(entry.accountId ?? DEFAULT_ACCOUNT_ID);
const enabled = entry.enabled !== false;
const configured = entry.configured === true;
if (!enabled || !configured) return [];
const issues: Array<{ channel: string; accountId: string; kind: string; message: string; fix: string }> = [];
if (!entry.clientId) {
issues.push({
channel: "dingtalk",
accountId,
kind: "config",
message: "DingTalk clientId (AppKey) is missing.",
fix: "Set channels.dingtalk.clientId.",
});
}
return issues;
}),
buildChannelSummary: ({ snapshot }) => ({
configured: snapshot.configured ?? false,
credentialSource: snapshot.credentialSource ?? "none",
mode: "stream",
running: snapshot.running ?? false,
lastStartAt: snapshot.lastStartAt ?? null,
lastStopAt: snapshot.lastStopAt ?? null,
lastError: snapshot.lastError ?? null,
probe: snapshot.probe,
lastProbeAt: snapshot.lastProbeAt ?? null,
}),
probeAccount: async ({ account }) => probeDingTalk(account),
buildAccountSnapshot: ({ account, runtime, probe }) => ({
accountId: account.accountId,
name: account.name,
enabled: account.enabled,
configured: account.credentialSource !== "none",
credentialSource: account.credentialSource,
mode: "stream",
clientId: account.clientId,
running: runtime?.running ?? false,
lastStartAt: runtime?.lastStartAt ?? null,
lastStopAt: runtime?.lastStopAt ?? null,
lastError: runtime?.lastError ?? null,
lastInboundAt: runtime?.lastInboundAt ?? null,
lastOutboundAt: runtime?.lastOutboundAt ?? null,
dmPolicy: account.config.dm?.policy ?? "pairing",
probe,
}),
},
gateway: {
startAccount: async (ctx: {
account: ResolvedDingTalkAccount;
cfg: unknown;
runtime: { log?: (msg: string) => void; error?: (msg: string) => void };
abortSignal: AbortSignal;
log?: { info: (msg: string) => void };
setStatus: (patch: Record<string, unknown>) => void;
}) => {
const account = ctx.account;
ctx.log?.info(`[${account.accountId}] starting DingTalk Stream mode`);
ctx.setStatus({
accountId: account.accountId,
running: true,
lastStartAt: Date.now(),
mode: "stream",
});
const unregister = await startDingTalkMonitor({
account,
config: ctx.cfg as MoltbotConfig,
runtime: ctx.runtime,
abortSignal: ctx.abortSignal,
statusSink: (patch) => ctx.setStatus({ accountId: account.accountId, ...patch }),
});
return () => {
unregister?.();
ctx.setStatus({
accountId: account.accountId,
running: false,
lastStopAt: Date.now(),
});
};
},
},
};

View File

@ -0,0 +1,40 @@
/**
* Type declarations for dingtalk-stream SDK
*/
declare module "dingtalk-stream" {
export interface DWClientOptions {
clientId: string;
clientSecret: string;
}
export interface StreamResponse {
headers: {
messageId: string;
[key: string]: string;
};
data: string;
}
export class DWClient {
constructor(options: DWClientOptions);
registerCallbackListener(
path: string,
callback: (res: StreamResponse) => Promise<void>,
): DWClient;
connect(): Promise<void>;
disconnect?(): void;
socketCallBackResponse(messageId: string, response: string): void;
}
export const EventAck: {
SUCCESS: string;
LATER: string;
};
export const GATEWAY_URL: string;
export const GET_TOKEN_URL: string;
export const TOPIC_AI_GRAPH_API: string;
export const TOPIC_CARD: string;
export const TOPIC_ROBOT: string;
}

View File

@ -0,0 +1,443 @@
import type { DingTalkAccountConfig, MoltbotConfig } from "clawdbot/plugin-sdk";
import type { ResolvedDingTalkAccount } from "./accounts.js";
import { replyWithSessionWebhook } from "./api.js";
import { getDingTalkRuntime } from "./runtime.js";
import type {
DingTalkStreamMessage,
DingTalkMarkdownMessage,
DingTalkGroupConfig,
} from "./types.js";
export type DingTalkRuntimeEnv = {
log?: (message: string) => void;
error?: (message: string) => void;
};
export type DingTalkMonitorOptions = {
account: ResolvedDingTalkAccount;
config: MoltbotConfig;
runtime: DingTalkRuntimeEnv;
abortSignal: AbortSignal;
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
};
type DingTalkCoreRuntime = ReturnType<typeof getDingTalkRuntime>;
function logVerbose(core: DingTalkCoreRuntime, runtime: DingTalkRuntimeEnv, message: string) {
if (core.logging.shouldLogVerbose()) {
runtime.log?.(`[dingtalk] ${message}`);
}
}
/**
* Resolve group entry from config for allowlist checking
*/
function resolveGroupEntry(
config: DingTalkAccountConfig,
conversationId: string,
): {
entry: DingTalkGroupConfig | undefined;
wildcard: DingTalkGroupConfig | undefined;
allowlistConfigured: boolean;
} {
const groups = config.groups ?? {};
const entry = groups[conversationId];
const wildcard = groups["*"];
const allowlistConfigured = Object.keys(groups).length > 0;
return { entry, wildcard, allowlistConfigured };
}
/**
* Start DingTalk Stream mode monitor using dingtalk-stream SDK.
* Stream mode uses WebSocket connection, no public URL needed.
*/
export async function startDingTalkMonitor(params: DingTalkMonitorOptions): Promise<() => void> {
const { account, config, runtime, abortSignal, statusSink } = params;
const core = getDingTalkRuntime();
// Validate Stream mode credentials
if (!account.clientId || !account.clientSecret) {
runtime.error?.(
`[${account.accountId}] DingTalk Stream mode requires clientId and clientSecret`,
);
return () => {};
}
runtime.log?.(`[${account.accountId}] Starting DingTalk Stream mode connection...`);
let client: DingTalkStreamClient | null = null;
let isConnected = false;
try {
// Dynamic import of dingtalk-stream SDK
const { DWClient, EventAck } = await import("dingtalk-stream");
// Create DingTalk Stream client
client = new DWClient({
clientId: account.clientId,
clientSecret: account.clientSecret,
}) as DingTalkStreamClient;
// Register robot message callback listener
// The callback path for robot messages is '/v1.0/im/bot/messages/get'
client.registerCallbackListener(
"/v1.0/im/bot/messages/get",
async (res: DingTalkStreamResponse) => {
try {
statusSink?.({ lastInboundAt: Date.now() });
const message = JSON.parse(res.data) as DingTalkStreamMessage;
logVerbose(core, runtime, `received stream message: ${JSON.stringify(message)}`);
await processStreamMessage({
message,
account,
config,
runtime,
core,
statusSink,
});
// Acknowledge the message was processed
client?.socketCallBackResponse(res.headers.messageId, EventAck.SUCCESS);
} catch (err) {
runtime.error?.(`[${account.accountId}] Stream message processing error: ${String(err)}`);
// Still acknowledge to prevent redelivery
client?.socketCallBackResponse(res.headers.messageId, EventAck.SUCCESS);
}
},
);
// Connect to DingTalk Stream
await client.connect();
isConnected = true;
runtime.log?.(`[${account.accountId}] DingTalk Stream mode connected successfully`);
// Handle abort signal
const handleAbort = () => {
if (client && isConnected) {
runtime.log?.(`[${account.accountId}] Disconnecting DingTalk Stream...`);
try {
client.disconnect?.();
} catch {
// Ignore disconnect errors
}
isConnected = false;
}
};
abortSignal.addEventListener("abort", handleAbort);
return () => {
abortSignal.removeEventListener("abort", handleAbort);
handleAbort();
};
} catch (err) {
runtime.error?.(`[${account.accountId}] DingTalk Stream connection failed: ${String(err)}`);
return () => {};
}
}
// Type definitions for dingtalk-stream SDK
interface DingTalkStreamClient {
registerCallbackListener: (
path: string,
callback: (res: DingTalkStreamResponse) => Promise<void>,
) => DingTalkStreamClient;
connect: () => Promise<void>;
disconnect?: () => void;
socketCallBackResponse: (messageId: string, response: string) => void;
}
interface DingTalkStreamResponse {
headers: {
messageId: string;
[key: string]: string;
};
data: string;
}
/**
* Process incoming DingTalk Stream message
*/
async function processStreamMessage(params: {
message: DingTalkStreamMessage;
account: ResolvedDingTalkAccount;
config: MoltbotConfig;
runtime: DingTalkRuntimeEnv;
core: DingTalkCoreRuntime;
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
}): Promise<void> {
const { message, account, config, runtime, core, statusSink } = params;
// Only handle text messages for now
if (message.msgtype !== "text") {
logVerbose(core, runtime, `skip non-text message (type=${message.msgtype})`);
return;
}
const messageText = message.text?.content?.trim() ?? "";
if (!messageText) {
logVerbose(core, runtime, "skip empty message");
return;
}
const isGroup = message.conversationType === "2";
const senderId = message.senderStaffId;
const senderName = message.senderNick ?? "";
const conversationId = message.conversationId;
const conversationTitle = message.conversationTitle ?? "";
// Check if sender is allowed (simplified DM policy check)
const dmPolicy = account.config.dm?.policy ?? "pairing";
const configAllowFrom = (account.config.dm?.allowFrom ?? []).map((v) => String(v));
if (!isGroup && dmPolicy !== "open") {
const allowed = isSenderAllowed(senderId, configAllowFrom);
if (!allowed) {
if (dmPolicy === "pairing") {
const { code, created } = await core.channel.pairing.upsertPairingRequest({
channel: "dingtalk",
id: senderId,
meta: { name: senderName || undefined },
});
if (created) {
logVerbose(core, runtime, `dingtalk pairing request sender=${senderId}`);
try {
await replyWithSessionWebhook({
sessionWebhook: message.sessionWebhook,
message: {
msgtype: "text",
text: {
content: core.channel.pairing.buildPairingReply({
channel: "dingtalk",
idLine: `Your DingTalk user ID: ${senderId}`,
code,
}),
},
},
});
statusSink?.({ lastOutboundAt: Date.now() });
} catch (err) {
logVerbose(core, runtime, `pairing reply failed for ${senderId}: ${String(err)}`);
}
}
} else {
logVerbose(core, runtime, `Blocked unauthorized DingTalk sender ${senderId}`);
}
return;
}
}
// Group message handling
if (isGroup) {
const groupPolicy = account.config.groupPolicy ?? "allowlist";
if (groupPolicy === "disabled") {
logVerbose(core, runtime, `drop group message (groupPolicy=disabled)`);
return;
}
const groupInfo = resolveGroupEntry(account.config, conversationId);
const groupEntry = groupInfo.entry;
// Allowlist check
if (groupPolicy === "allowlist") {
if (!groupInfo.allowlistConfigured) {
logVerbose(
core,
runtime,
`drop group message (allowlist empty, chat=${conversationId})`,
);
return;
}
const groupAllowed = Boolean(groupEntry) || Boolean(groupInfo.wildcard);
if (!groupAllowed) {
logVerbose(
core,
runtime,
`drop group message (not allowlisted, chat=${conversationId})`,
);
return;
}
}
// Check if group is disabled
if (groupEntry?.enabled === false || groupEntry?.allow === false) {
logVerbose(core, runtime, `drop group message (disabled, chat=${conversationId})`);
return;
}
// Check group user allowlist
if (groupEntry?.users && groupEntry.users.length > 0) {
const ok = isSenderAllowed(
senderId,
groupEntry.users.map((v) => String(v)),
);
if (!ok) {
logVerbose(core, runtime, `drop group message (sender not allowed, ${senderId})`);
return;
}
}
// @mention check
const requireMention =
groupEntry?.requireMention ?? account.config.requireMention ?? true;
const wasMentioned = message.isInAtList === true;
if (requireMention && !wasMentioned) {
logVerbose(core, runtime, `drop group message (mention required)`);
return;
}
}
// Build route and session
const route = core.channel.routing.resolveAgentRoute({
cfg: config,
channel: "dingtalk",
accountId: account.accountId,
peer: {
kind: isGroup ? "group" : "dm",
id: conversationId,
},
});
const fromLabel = isGroup
? conversationTitle || `group:${conversationId}`
: senderName || `user:${senderId}`;
const storePath = core.channel.session.resolveStorePath(config.session?.store, {
agentId: route.agentId,
});
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(config);
const previousTimestamp = core.channel.session.readSessionUpdatedAt({
storePath,
sessionKey: route.sessionKey,
});
const body = core.channel.reply.formatAgentEnvelope({
channel: "DingTalk",
from: fromLabel,
timestamp: message.createAt ?? Date.now(),
previousTimestamp,
envelope: envelopeOptions,
body: messageText,
});
const ctxPayload = core.channel.reply.finalizeInboundContext({
Body: body,
RawBody: messageText,
CommandBody: messageText,
From: `dingtalk:${senderId}`,
To: `dingtalk:${conversationId}`,
SessionKey: route.sessionKey,
AccountId: route.accountId,
ChatType: isGroup ? "channel" : "direct",
ConversationLabel: fromLabel,
SenderName: senderName || undefined,
SenderId: senderId,
WasMentioned: isGroup ? message.isInAtList : undefined,
Provider: "dingtalk",
Surface: "dingtalk",
MessageSid: message.msgId,
MessageSidFull: message.msgId,
GroupSpace: isGroup ? conversationTitle ?? undefined : undefined,
OriginatingChannel: "dingtalk",
OriginatingTo: `dingtalk:${conversationId}`,
});
void core.channel.session
.recordSessionMetaFromInbound({
storePath,
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
ctx: ctxPayload,
})
.catch((err: unknown) => {
runtime.error?.(`dingtalk: failed updating session meta: ${String(err)}`);
});
// Dispatch reply
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
ctx: ctxPayload,
cfg: config,
dispatcherOptions: {
deliver: async (payload) => {
await deliverDingTalkReply({
payload,
sessionWebhook: message.sessionWebhook,
runtime,
core,
config,
account,
statusSink,
});
},
onError: (err, info) => {
runtime.error?.(
`[${account.accountId}] DingTalk ${info.kind} reply failed: ${String(err)}`,
);
},
},
});
}
function isSenderAllowed(senderId: string, allowFrom: string[]): boolean {
if (allowFrom.includes("*")) return true;
const normalizedSenderId = senderId.toLowerCase();
return allowFrom.some((entry) => {
const normalized = String(entry).trim().toLowerCase();
if (!normalized) return false;
if (normalized === normalizedSenderId) return true;
if (normalized.replace(/^dingtalk:/i, "") === normalizedSenderId) return true;
return false;
});
}
async function deliverDingTalkReply(params: {
payload: { text?: string; mediaUrls?: string[]; mediaUrl?: string };
sessionWebhook: string;
runtime: DingTalkRuntimeEnv;
core: DingTalkCoreRuntime;
config: MoltbotConfig;
account: ResolvedDingTalkAccount;
statusSink?: (patch: { lastOutboundAt?: number }) => void;
}): Promise<void> {
const { payload, sessionWebhook, runtime, core, config, account, statusSink } = params;
// DingTalk doesn't support media attachments via session webhook in the same way
// We'll send text messages and note media limitations
if (payload.text) {
const chunkLimit = account.config.textChunkLimit ?? 4000;
const chunkMode = core.channel.text.resolveChunkMode(config, "dingtalk", account.accountId);
const chunks = core.channel.text.chunkMarkdownTextWithMode(payload.text, chunkLimit, chunkMode);
for (const chunk of chunks) {
try {
// Use markdown format for better formatting
const message: DingTalkMarkdownMessage = {
msgtype: "markdown",
markdown: {
title: "Reply",
text: chunk,
},
};
await replyWithSessionWebhook({ sessionWebhook, message });
statusSink?.({ lastOutboundAt: Date.now() });
} catch (err) {
runtime.error?.(`DingTalk message send failed: ${String(err)}`);
}
}
}
// Note: Media handling would require additional API endpoints
// DingTalk's robot API has limited media support compared to other platforms
}
// Legacy exports for backward compatibility
export function resolveDingTalkWebhookPath(_params: {
account: ResolvedDingTalkAccount;
}): string {
// Stream mode doesn't use webhooks, but return a placeholder for compatibility
return "/dingtalk-stream";
}

View File

@ -0,0 +1,230 @@
import type { MoltbotConfig, DmPolicy } from "clawdbot/plugin-sdk";
import {
addWildcardAllowFrom,
formatDocsLink,
promptAccountId,
type ChannelOnboardingAdapter,
type ChannelOnboardingDmPolicy,
type WizardPrompter,
DEFAULT_ACCOUNT_ID,
normalizeAccountId,
migrateBaseNameToDefaultAccount,
} from "clawdbot/plugin-sdk";
import {
listDingTalkAccountIds,
resolveDefaultDingTalkAccountId,
resolveDingTalkAccount,
} from "./accounts.js";
const channel = "dingtalk" as const;
// Stream mode environment variables
const ENV_CLIENT_ID = "DINGTALK_CLIENT_ID";
const ENV_CLIENT_SECRET = "DINGTALK_CLIENT_SECRET";
function setDingTalkDmPolicy(cfg: MoltbotConfig, policy: DmPolicy) {
const allowFrom =
policy === "open"
? addWildcardAllowFrom(cfg.channels?.dingtalk?.dm?.allowFrom)
: undefined;
return {
...cfg,
channels: {
...cfg.channels,
dingtalk: {
...(cfg.channels?.dingtalk ?? {}),
dm: {
...(cfg.channels?.dingtalk?.dm ?? {}),
policy,
...(allowFrom ? { allowFrom } : {}),
},
},
},
};
}
function parseAllowFromInput(raw: string): string[] {
return raw
.split(/[\n,;]+/g)
.map((entry) => entry.trim())
.filter(Boolean);
}
async function promptAllowFrom(params: {
cfg: MoltbotConfig;
prompter: WizardPrompter;
}): Promise<MoltbotConfig> {
const current = params.cfg.channels?.dingtalk?.dm?.allowFrom ?? [];
const entry = await params.prompter.text({
message: "DingTalk allowFrom (user ID)",
placeholder: "user123, user456",
initialValue: current[0] ? String(current[0]) : undefined,
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
});
const parts = parseAllowFromInput(String(entry));
const unique = [...new Set(parts)];
return {
...params.cfg,
channels: {
...params.cfg.channels,
dingtalk: {
...(params.cfg.channels?.dingtalk ?? {}),
enabled: true,
dm: {
...(params.cfg.channels?.dingtalk?.dm ?? {}),
policy: "allowlist",
allowFrom: unique,
},
},
},
};
}
const dmPolicy: ChannelOnboardingDmPolicy = {
label: "DingTalk",
channel,
policyKey: "channels.dingtalk.dm.policy",
allowFromKey: "channels.dingtalk.dm.allowFrom",
getCurrent: (cfg) => cfg.channels?.dingtalk?.dm?.policy ?? "pairing",
setPolicy: (cfg, policy) => setDingTalkDmPolicy(cfg, policy),
promptAllowFrom,
};
function applyAccountConfig(params: {
cfg: MoltbotConfig;
accountId: string;
patch: Record<string, unknown>;
}): MoltbotConfig {
const { cfg, accountId, patch } = params;
if (accountId === DEFAULT_ACCOUNT_ID) {
return {
...cfg,
channels: {
...cfg.channels,
dingtalk: {
...(cfg.channels?.dingtalk ?? {}),
enabled: true,
...patch,
},
},
};
}
return {
...cfg,
channels: {
...cfg.channels,
dingtalk: {
...(cfg.channels?.dingtalk ?? {}),
enabled: true,
accounts: {
...(cfg.channels?.dingtalk?.accounts ?? {}),
[accountId]: {
...(cfg.channels?.dingtalk?.accounts?.[accountId] ?? {}),
enabled: true,
...patch,
},
},
},
},
};
}
async function promptCredentials(params: {
cfg: MoltbotConfig;
prompter: WizardPrompter;
accountId: string;
}): Promise<MoltbotConfig> {
const { cfg, prompter, accountId } = params;
const envReady =
accountId === DEFAULT_ACCOUNT_ID &&
Boolean(process.env[ENV_CLIENT_ID]) &&
Boolean(process.env[ENV_CLIENT_SECRET]);
if (envReady) {
const useEnv = await prompter.confirm({
message: "Use DINGTALK_CLIENT_ID/DINGTALK_CLIENT_SECRET env vars?",
initialValue: true,
});
if (useEnv) {
return applyAccountConfig({ cfg, accountId, patch: {} });
}
}
const clientId = await prompter.text({
message: "DingTalk AppKey (Client ID)",
placeholder: "dingxxxxxxxx",
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
});
const clientSecret = await prompter.text({
message: "DingTalk AppSecret (Client Secret)",
placeholder: "xxxxxxxx",
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
});
return applyAccountConfig({
cfg,
accountId,
patch: {
clientId: String(clientId).trim(),
clientSecret: String(clientSecret).trim(),
},
});
}
async function noteDingTalkSetup(prompter: WizardPrompter) {
await prompter.note(
[
"DingTalk Stream mode uses WebSocket connection (no public URL needed).",
"1. Create an enterprise internal robot in DingTalk Open Platform",
"2. Enable 'Stream Mode' in robot settings",
"3. Copy the AppKey (Client ID) and AppSecret (Client Secret)",
`Docs: ${formatDocsLink("/channels/dingtalk", "channels/dingtalk")}`,
].join("\n"),
"DingTalk setup",
);
}
export const dingtalkOnboardingAdapter: ChannelOnboardingAdapter = {
channel,
dmPolicy,
getStatus: async ({ cfg }) => {
const configured = listDingTalkAccountIds(cfg).some(
(accountId) => resolveDingTalkAccount({ cfg, accountId }).credentialSource !== "none",
);
return {
channel,
configured,
statusLines: [`DingTalk: ${configured ? "configured" : "needs credentials"}`],
selectionHint: configured ? "configured" : "needs auth",
};
},
configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => {
const override = accountOverrides["dingtalk"]?.trim();
const defaultAccountId = resolveDefaultDingTalkAccountId(cfg);
let accountId = override ? normalizeAccountId(override) : defaultAccountId;
if (shouldPromptAccountIds && !override) {
accountId = await promptAccountId({
cfg,
prompter,
label: "DingTalk",
currentId: accountId,
listAccountIds: listDingTalkAccountIds,
defaultAccountId,
});
}
let next = cfg;
await noteDingTalkSetup(prompter);
next = await promptCredentials({ cfg: next, prompter, accountId });
const namedConfig = migrateBaseNameToDefaultAccount({
cfg: next,
channelKey: "dingtalk",
});
return { cfg: namedConfig, accountId };
},
};

View File

@ -0,0 +1,14 @@
import type { PluginRuntime } from "clawdbot/plugin-sdk";
let runtime: PluginRuntime | null = null;
export function setDingTalkRuntime(next: PluginRuntime) {
runtime = next;
}
export function getDingTalkRuntime(): PluginRuntime {
if (!runtime) {
throw new Error("DingTalk runtime not initialized");
}
return runtime;
}

View File

@ -0,0 +1,209 @@
/**
* DingTalk Bot API Types
* Based on: https://open.dingtalk.com/document/orgapp/robot-message-types-and-data-format
*/
// Re-export core types for convenience
export type {
DingTalkAccountConfig,
DingTalkConfig,
DingTalkDmConfig,
DingTalkGroupConfig,
} from "clawdbot/plugin-sdk";
// Inbound message types (received from DingTalk webhook callback)
export type DingTalkUser = {
/** User ID in DingTalk */
dingtalkId: string;
/** Staff ID in enterprise */
staffId?: string;
/** User nickname */
nick?: string;
};
export type DingTalkAtUser = {
/** DingTalk ID of the mentioned user */
dingtalkId: string;
/** Staff ID of the mentioned user */
staffId?: string;
};
/**
* @deprecated Legacy webhook callback event from DingTalk
* Use Stream mode instead (DingTalkStreamMessage)
*/
export type DingTalkCallbackEvent = {
/** Message ID */
msgId: string;
/** Message type: text, richText, picture, audio, video, file */
msgtype: string;
/** Conversation ID */
conversationId: string;
/** Conversation type: "1" = single chat, "2" = group chat */
conversationType: "1" | "2";
/** Conversation title (group name for group chat) */
conversationTitle?: string;
/** Chat bot corp ID */
chatbotCorpId: string;
/** Chat bot user ID */
chatbotUserId: string;
/** Sender ID */
senderId: string;
/** Sender corp ID */
senderCorpId?: string;
/** Sender nickname */
senderNick: string;
/** Sender staff ID */
senderStaffId?: string;
/** Whether sender is admin */
isAdmin?: boolean;
/** Session webhook URL for replying */
sessionWebhook: string;
/** Session webhook expiration timestamp */
sessionWebhookExpiredTime: number;
/** Message creation timestamp */
createAt: number;
/** Robot code */
robotCode?: string;
/** Text content for text messages */
text?: {
content: string;
};
/** Content for rich text messages */
content?: string;
/** At users list */
atUsers?: DingTalkAtUser[];
/** Whether the robot was @mentioned */
isInAtList?: boolean;
};
// Outbound message types (sent to DingTalk)
export type DingTalkTextMessage = {
msgtype: "text";
text: {
content: string;
};
at?: {
atMobiles?: string[];
atUserIds?: string[];
isAtAll?: boolean;
};
};
export type DingTalkLinkMessage = {
msgtype: "link";
link: {
title: string;
text: string;
messageUrl: string;
picUrl?: string;
};
};
export type DingTalkMarkdownMessage = {
msgtype: "markdown";
markdown: {
title: string;
text: string;
};
at?: {
atMobiles?: string[];
atUserIds?: string[];
isAtAll?: boolean;
};
};
export type DingTalkActionCardMessage = {
msgtype: "actionCard";
actionCard: {
title: string;
text: string;
singleTitle?: string;
singleURL?: string;
btnOrientation?: "0" | "1";
btns?: Array<{
title: string;
actionURL: string;
}>;
};
};
export type DingTalkFeedCardMessage = {
msgtype: "feedCard";
feedCard: {
links: Array<{
title: string;
messageURL: string;
picURL: string;
}>;
};
};
export type DingTalkOutboundMessage =
| DingTalkTextMessage
| DingTalkLinkMessage
| DingTalkMarkdownMessage
| DingTalkActionCardMessage
| DingTalkFeedCardMessage;
// API response types
export type DingTalkApiResponse = {
errcode: number;
errmsg: string;
};
// Stream mode message types (from dingtalk-stream SDK)
export type DingTalkStreamMessage = {
/** Platform of sender */
senderPlatform?: string;
/** Conversation ID */
conversationId: string;
/** At users list */
atUsers?: Array<{
dingtalkId: string;
staffId?: string;
}>;
/** Chat bot corp ID */
chatbotCorpId?: string;
/** Message ID */
msgId: string;
/** Sender nickname */
senderNick?: string;
/** Whether sender is admin */
isAdmin?: boolean;
/** Sender staff ID (user ID) */
senderStaffId: string;
/** Session webhook URL for replying */
sessionWebhook: string;
/** Session webhook expiration timestamp */
sessionWebhookExpiredTime?: number;
/** Conversation type: "1" = single chat, "2" = group chat */
conversationType?: "1" | "2";
/** Conversation title (group name for group chat) */
conversationTitle?: string;
/** Text content */
text?: {
content: string;
};
/** Robot code */
robotCode?: string;
/** Message type: text, image, voice, file, link, markdown, etc. */
msgtype: string;
/** Whether robot was @mentioned (in the atUsers list) */
isInAtList?: boolean;
/** Sender corp ID */
senderCorpId?: string;
/** Message creation timestamp */
createAt?: number;
};
// Local config type with additional legacy fields (for backward compatibility)
export type DingTalkLocalConfig = {
accounts?: Record<string, unknown>;
defaultAccount?: string;
replyToMode?: "off" | "first" | "all";
};

295
pnpm-lock.yaml generated
View File

@ -302,6 +302,16 @@ importers:
specifier: ^1.39.0 specifier: ^1.39.0
version: 1.39.0 version: 1.39.0
extensions/dingtalk:
dependencies:
dingtalk-stream:
specifier: ^2.1.4
version: 2.1.4
devDependencies:
moltbot:
specifier: workspace:*
version: link:../..
extensions/discord: {} extensions/discord: {}
extensions/google-antigravity-auth: {} extensions/google-antigravity-auth: {}
@ -383,12 +393,12 @@ importers:
'@microsoft/agents-hosting-extensions-teams': '@microsoft/agents-hosting-extensions-teams':
specifier: ^1.2.2 specifier: ^1.2.2
version: 1.2.2 version: 1.2.2
moltbot:
specifier: workspace:*
version: link:../..
express: express:
specifier: ^5.2.1 specifier: ^5.2.1
version: 5.2.1 version: 5.2.1
moltbot:
specifier: workspace:*
version: link:../..
proper-lockfile: proper-lockfile:
specifier: ^4.1.2 specifier: ^4.1.2
version: 4.1.2 version: 4.1.2
@ -1126,89 +1136,105 @@ packages:
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.2.4': '@img/sharp-libvips-linux-arm@1.2.4':
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.2.4': '@img/sharp-libvips-linux-ppc64@1.2.4':
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.2.4': '@img/sharp-libvips-linux-riscv64@1.2.4':
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.2.4': '@img/sharp-libvips-linux-s390x@1.2.4':
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.2.4': '@img/sharp-libvips-linux-x64@1.2.4':
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.2.4': '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.2.4': '@img/sharp-libvips-linuxmusl-x64@1.2.4':
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.34.5': '@img/sharp-linux-arm64@0.34.5':
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.5': '@img/sharp-linux-arm@0.34.5':
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.34.5': '@img/sharp-linux-ppc64@0.34.5':
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.34.5': '@img/sharp-linux-riscv64@0.34.5':
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.5': '@img/sharp-linux-s390x@0.34.5':
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.5': '@img/sharp-linux-x64@0.34.5':
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.34.5': '@img/sharp-linuxmusl-arm64@0.34.5':
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.5': '@img/sharp-linuxmusl-x64@0.34.5':
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.34.5': '@img/sharp-wasm32@0.34.5':
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
@ -1277,48 +1303,6 @@ packages:
'@kwsites/promise-deferred@1.1.1': '@kwsites/promise-deferred@1.1.1':
resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} 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': '@lancedb/lancedb@0.23.0':
resolution: {integrity: sha512-aYrIoEG24AC+wILCL57Ius/Y4yU+xFHDPKLvmjzzN4byAjzeIGF0TC86S5RBt4Ji+dxS7yIWV5Q/gE5/fybIFQ==} resolution: {integrity: sha512-aYrIoEG24AC+wILCL57Ius/Y4yU+xFHDPKLvmjzzN4byAjzeIGF0TC86S5RBt4Ji+dxS7yIWV5Q/gE5/fybIFQ==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
@ -1398,24 +1382,28 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@mariozechner/clipboard-linux-riscv64-gnu@0.3.0': '@mariozechner/clipboard-linux-riscv64-gnu@0.3.0':
resolution: {integrity: sha512-4BC08CIaOXSSAGRZLEjqJmQfioED8ohAzwt0k2amZPEbH96YKoBNorq5EdwPf5VT+odS0DeyCwhwtxokRLZIvQ==} resolution: {integrity: sha512-4BC08CIaOXSSAGRZLEjqJmQfioED8ohAzwt0k2amZPEbH96YKoBNorq5EdwPf5VT+odS0DeyCwhwtxokRLZIvQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@mariozechner/clipboard-linux-x64-gnu@0.3.0': '@mariozechner/clipboard-linux-x64-gnu@0.3.0':
resolution: {integrity: sha512-GpNY5Y9nOzr0Vt0Qi5U88qwe6piiIHk44kSMexl8ns90LluN5UTNYmyfi7Xq3/lmPZCpnB2xvBTYbsXCxnopIA==} resolution: {integrity: sha512-GpNY5Y9nOzr0Vt0Qi5U88qwe6piiIHk44kSMexl8ns90LluN5UTNYmyfi7Xq3/lmPZCpnB2xvBTYbsXCxnopIA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@mariozechner/clipboard-linux-x64-musl@0.3.0': '@mariozechner/clipboard-linux-x64-musl@0.3.0':
resolution: {integrity: sha512-+PnR48/x9GMY5Kh8BLjzHMx6trOegMtxAuqTM9X/bhV3QuW6sLLd7nojDHSGj/ZueK6i0tcQxvOrgNLozVtNDA==} resolution: {integrity: sha512-+PnR48/x9GMY5Kh8BLjzHMx6trOegMtxAuqTM9X/bhV3QuW6sLLd7nojDHSGj/ZueK6i0tcQxvOrgNLozVtNDA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@mariozechner/clipboard-win32-arm64-msvc@0.3.0': '@mariozechner/clipboard-win32-arm64-msvc@0.3.0':
resolution: {integrity: sha512-+dy2vZ1Ph4EYj0cotB+bVUVk/uKl2bh9LOp/zlnFqoCCYDN6sm+L0VyIOPPo3hjoEVdGpHe1MUxp3qG/OLwXgg==} resolution: {integrity: sha512-+dy2vZ1Ph4EYj0cotB+bVUVk/uKl2bh9LOp/zlnFqoCCYDN6sm+L0VyIOPPo3hjoEVdGpHe1MUxp3qG/OLwXgg==}
@ -1516,30 +1504,35 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-arm64-musl@0.1.88': '@napi-rs/canvas-linux-arm64-musl@0.1.88':
resolution: {integrity: sha512-kYyNrUsHLkoGHBc77u4Unh067GrfiCUMbGHC2+OTxbeWfZkPt2o32UOQkhnSswKd9Fko/wSqqGkY956bIUzruA==} resolution: {integrity: sha512-kYyNrUsHLkoGHBc77u4Unh067GrfiCUMbGHC2+OTxbeWfZkPt2o32UOQkhnSswKd9Fko/wSqqGkY956bIUzruA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@napi-rs/canvas-linux-riscv64-gnu@0.1.88': '@napi-rs/canvas-linux-riscv64-gnu@0.1.88':
resolution: {integrity: sha512-HVuH7QgzB0yavYdNZDRyAsn/ejoXB0hn8twwFnOqUbCCdkV+REna7RXjSR7+PdfW0qMQ2YYWsLvVBT5iL/mGpw==} resolution: {integrity: sha512-HVuH7QgzB0yavYdNZDRyAsn/ejoXB0hn8twwFnOqUbCCdkV+REna7RXjSR7+PdfW0qMQ2YYWsLvVBT5iL/mGpw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-gnu@0.1.88': '@napi-rs/canvas-linux-x64-gnu@0.1.88':
resolution: {integrity: sha512-hvcvKIcPEQrvvJtJnwD35B3qk6umFJ8dFIr8bSymfrSMem0EQsfn1ztys8ETIFndTwdNWJKWluvxztA41ivsEw==} resolution: {integrity: sha512-hvcvKIcPEQrvvJtJnwD35B3qk6umFJ8dFIr8bSymfrSMem0EQsfn1ztys8ETIFndTwdNWJKWluvxztA41ivsEw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-musl@0.1.88': '@napi-rs/canvas-linux-x64-musl@0.1.88':
resolution: {integrity: sha512-eSMpGYY2xnZSQ6UxYJ6plDboxq4KeJ4zT5HaVkUnbObNN6DlbJe0Mclh3wifAmquXfrlgTZt6zhHsUgz++AK6g==} resolution: {integrity: sha512-eSMpGYY2xnZSQ6UxYJ6plDboxq4KeJ4zT5HaVkUnbObNN6DlbJe0Mclh3wifAmquXfrlgTZt6zhHsUgz++AK6g==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@napi-rs/canvas-win32-arm64-msvc@0.1.88': '@napi-rs/canvas-win32-arm64-msvc@0.1.88':
resolution: {integrity: sha512-qcIFfEgHrchyYqRrxsCeTQgpJZ/GqHiqPcU/Fvw/ARVlQeDX1VyFH+X+0gCR2tca6UJrq96vnW+5o7buCq+erA==} resolution: {integrity: sha512-qcIFfEgHrchyYqRrxsCeTQgpJZ/GqHiqPcU/Fvw/ARVlQeDX1VyFH+X+0gCR2tca6UJrq96vnW+5o7buCq+erA==}
@ -1585,36 +1578,28 @@ packages:
engines: {node: '>=20.0.0'} engines: {node: '>=20.0.0'}
cpu: [arm64, x64] cpu: [arm64, x64]
os: [linux] os: [linux]
libc: [glibc]
'@node-llama-cpp/linux-armv7l@3.15.0': '@node-llama-cpp/linux-armv7l@3.15.0':
resolution: {integrity: sha512-ZuZ3q6mejQnEP4o22la7zBv7jNR+IZfgItDm3KjAl04HUXTKJ43HpNwjnf9GyYYd+dEgtoX0MESvWz4RnGH8Jw==} resolution: {integrity: sha512-ZuZ3q6mejQnEP4o22la7zBv7jNR+IZfgItDm3KjAl04HUXTKJ43HpNwjnf9GyYYd+dEgtoX0MESvWz4RnGH8Jw==}
engines: {node: '>=20.0.0'} engines: {node: '>=20.0.0'}
cpu: [arm, x64] cpu: [arm, x64]
os: [linux] 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': '@node-llama-cpp/linux-x64-vulkan@3.15.0':
resolution: {integrity: sha512-htVIthQKq/rr8v5e7NiVtcHsstqTBAAC50kUymmDMbrzAu6d/EHacCJpNbU57b1UUa1nKN5cBqr6Jr+QqEalMA==} resolution: {integrity: sha512-htVIthQKq/rr8v5e7NiVtcHsstqTBAAC50kUymmDMbrzAu6d/EHacCJpNbU57b1UUa1nKN5cBqr6Jr+QqEalMA==}
engines: {node: '>=20.0.0'} engines: {node: '>=20.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@node-llama-cpp/linux-x64@3.15.0': '@node-llama-cpp/linux-x64@3.15.0':
resolution: {integrity: sha512-etUuTqSyNefRObqc5+JZviNTkuef2XEtHcQLaamEIWwjI1dj7nTD2YMZPBP7H3M3E55HSIY82vqCQ1bp6ZILiA==} resolution: {integrity: sha512-etUuTqSyNefRObqc5+JZviNTkuef2XEtHcQLaamEIWwjI1dj7nTD2YMZPBP7H3M3E55HSIY82vqCQ1bp6ZILiA==}
engines: {node: '>=20.0.0'} engines: {node: '>=20.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@node-llama-cpp/mac-arm64-metal@3.15.0': '@node-llama-cpp/mac-arm64-metal@3.15.0':
resolution: {integrity: sha512-3Vkq6bpyQZaIzoaLLP7H2Tt8ty5BS0zxUY2pX0ox2S9P4fp8Au0CCJuUJF4V+EKi+/PTn70A6R1QCkRMfMQJig==} resolution: {integrity: sha512-3Vkq6bpyQZaIzoaLLP7H2Tt8ty5BS0zxUY2pX0ox2S9P4fp8Au0CCJuUJF4V+EKi+/PTn70A6R1QCkRMfMQJig==}
@ -1634,24 +1619,6 @@ packages:
cpu: [arm64, x64] cpu: [arm64, x64]
os: [win32] 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': '@node-llama-cpp/win-x64@3.15.0':
resolution: {integrity: sha512-gWhtc8l3HOry5guO46YfFohLQnF0NfL4On0GAO8E27JiYYxHO9nHSCfFif4+U03+FfHquZXL0znJ1qPVOiwOPw==} resolution: {integrity: sha512-gWhtc8l3HOry5guO46YfFohLQnF0NfL4On0GAO8E27JiYYxHO9nHSCfFif4+U03+FfHquZXL0znJ1qPVOiwOPw==}
engines: {node: '>=20.0.0'} engines: {node: '>=20.0.0'}
@ -1962,21 +1929,25 @@ packages:
resolution: {integrity: sha512-GubkQeQT5d3B/Jx/IiR7NMkSmXrCZcVI0BPh1i7mpFi8HgD1hQ/LbhiBKAMsMqs5bbugdQOgBEl8bOhe8JhW1g==} resolution: {integrity: sha512-GubkQeQT5d3B/Jx/IiR7NMkSmXrCZcVI0BPh1i7mpFi8HgD1hQ/LbhiBKAMsMqs5bbugdQOgBEl8bOhe8JhW1g==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@oxfmt/linux-arm64-musl@0.26.0': '@oxfmt/linux-arm64-musl@0.26.0':
resolution: {integrity: sha512-OEypUwK69bFPj+aa3/LYCnlIUPgoOLu//WNcriwpnWNmt47808Ht7RJSg+MNK8a7pSZHpXJ5/E6CRK/OTwFdaQ==} resolution: {integrity: sha512-OEypUwK69bFPj+aa3/LYCnlIUPgoOLu//WNcriwpnWNmt47808Ht7RJSg+MNK8a7pSZHpXJ5/E6CRK/OTwFdaQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@oxfmt/linux-x64-gnu@0.26.0': '@oxfmt/linux-x64-gnu@0.26.0':
resolution: {integrity: sha512-xO6iEW2bC6ZHyOTPmPWrg/nM6xgzyRPaS84rATy6F8d79wz69LdRdJ3l/PXlkqhi7XoxhvX4ExysA0Nf10ZZEQ==} resolution: {integrity: sha512-xO6iEW2bC6ZHyOTPmPWrg/nM6xgzyRPaS84rATy6F8d79wz69LdRdJ3l/PXlkqhi7XoxhvX4ExysA0Nf10ZZEQ==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@oxfmt/linux-x64-musl@0.26.0': '@oxfmt/linux-x64-musl@0.26.0':
resolution: {integrity: sha512-Z3KuZFC+MIuAyFCXBHY71kCsdRq1ulbsbzTe71v+hrEv7zVBn6yzql+/AZcgfIaKzWO9OXNuz5WWLWDmVALwow==} resolution: {integrity: sha512-Z3KuZFC+MIuAyFCXBHY71kCsdRq1ulbsbzTe71v+hrEv7zVBn6yzql+/AZcgfIaKzWO9OXNuz5WWLWDmVALwow==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@oxfmt/win32-arm64@0.26.0': '@oxfmt/win32-arm64@0.26.0':
resolution: {integrity: sha512-3zRbqwVWK1mDhRhTknlQFpRFL9GhEB5GfU6U7wawnuEwpvi39q91kJ+SRJvJnhyPCARkjZBd1V8XnweN5IFd1g==} resolution: {integrity: sha512-3zRbqwVWK1mDhRhTknlQFpRFL9GhEB5GfU6U7wawnuEwpvi39q91kJ+SRJvJnhyPCARkjZBd1V8XnweN5IFd1g==}
@ -2032,21 +2003,25 @@ packages:
resolution: {integrity: sha512-Fow7H84Bs8XxuaK1yfSEWBC8HI7rfEQB9eR2A0J61un1WgCas7jNrt1HbT6+p6KmUH2bhR+r/RDu/6JFAvvj4g==} resolution: {integrity: sha512-Fow7H84Bs8XxuaK1yfSEWBC8HI7rfEQB9eR2A0J61un1WgCas7jNrt1HbT6+p6KmUH2bhR+r/RDu/6JFAvvj4g==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@oxlint/linux-arm64-musl@1.41.0': '@oxlint/linux-arm64-musl@1.41.0':
resolution: {integrity: sha512-WoRRDNwgP5W3rjRh42Zdx8ferYnqpKoYCv2QQLenmdrLjRGYwAd52uywfkcS45mKEWHeY1RPwPkYCSROXiGb2w==} resolution: {integrity: sha512-WoRRDNwgP5W3rjRh42Zdx8ferYnqpKoYCv2QQLenmdrLjRGYwAd52uywfkcS45mKEWHeY1RPwPkYCSROXiGb2w==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@oxlint/linux-x64-gnu@1.41.0': '@oxlint/linux-x64-gnu@1.41.0':
resolution: {integrity: sha512-75k3CKj3fOc/a/2aSgO81s3HsTZOFROthPJ+UI2Oatic1LhvH6eKjKfx3jDDyVpzeDS2qekPlc/y3N33iZz5Og==} resolution: {integrity: sha512-75k3CKj3fOc/a/2aSgO81s3HsTZOFROthPJ+UI2Oatic1LhvH6eKjKfx3jDDyVpzeDS2qekPlc/y3N33iZz5Og==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@oxlint/linux-x64-musl@1.41.0': '@oxlint/linux-x64-musl@1.41.0':
resolution: {integrity: sha512-8r82eBwGPoAPn67ZvdxTlX/Z3gVb+ZtN6nbkyFzwwHWAh8yGutX+VBcVkyrePSl6XgBP4QAaddPnHmkvJjqY0g==} resolution: {integrity: sha512-8r82eBwGPoAPn67ZvdxTlX/Z3gVb+ZtN6nbkyFzwwHWAh8yGutX+VBcVkyrePSl6XgBP4QAaddPnHmkvJjqY0g==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@oxlint/win32-arm64@1.41.0': '@oxlint/win32-arm64@1.41.0':
resolution: {integrity: sha512-aK+DAcckQsNCOXKruatyYuY/ROjNiRejQB1PeJtkZwM21+8rV9ODYbvKNvt0pW+YCws7svftBSFMCpl3ke2unw==} resolution: {integrity: sha512-aK+DAcckQsNCOXKruatyYuY/ROjNiRejQB1PeJtkZwM21+8rV9ODYbvKNvt0pW+YCws7svftBSFMCpl3ke2unw==}
@ -2118,24 +2093,28 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@reflink/reflink-linux-arm64-musl@0.1.19': '@reflink/reflink-linux-arm64-musl@0.1.19':
resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==} resolution: {integrity: sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@reflink/reflink-linux-x64-gnu@0.1.19': '@reflink/reflink-linux-x64-gnu@0.1.19':
resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==} resolution: {integrity: sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@reflink/reflink-linux-x64-musl@0.1.19': '@reflink/reflink-linux-x64-musl@0.1.19':
resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==} resolution: {integrity: sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@reflink/reflink-win32-arm64-msvc@0.1.19': '@reflink/reflink-win32-arm64-msvc@0.1.19':
resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==} resolution: {integrity: sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==}
@ -2188,24 +2167,28 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1':
resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==} resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1':
resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==} resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.0.0-rc.1': '@rolldown/binding-linux-x64-musl@1.0.0-rc.1':
resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==} resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.0.0-rc.1': '@rolldown/binding-openharmony-arm64@1.0.0-rc.1':
resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==} resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==}
@ -2267,66 +2250,79 @@ packages:
resolution: {integrity: sha512-YeGUhkN1oA+iSPzzhEjVPS29YbViOr8s4lSsFaZKLHswgqP911xx25fPOyE9+khmN6W4VeM0aevbDp4kkEoHiA==} resolution: {integrity: sha512-YeGUhkN1oA+iSPzzhEjVPS29YbViOr8s4lSsFaZKLHswgqP911xx25fPOyE9+khmN6W4VeM0aevbDp4kkEoHiA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.55.3': '@rollup/rollup-linux-arm-musleabihf@4.55.3':
resolution: {integrity: sha512-eo0iOIOvcAlWB3Z3eh8pVM8hZ0oVkK3AjEM9nSrkSug2l15qHzF3TOwT0747omI6+CJJvl7drwZepT+re6Fy/w==} resolution: {integrity: sha512-eo0iOIOvcAlWB3Z3eh8pVM8hZ0oVkK3AjEM9nSrkSug2l15qHzF3TOwT0747omI6+CJJvl7drwZepT+re6Fy/w==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.55.3': '@rollup/rollup-linux-arm64-gnu@4.55.3':
resolution: {integrity: sha512-DJay3ep76bKUDImmn//W5SvpjRN5LmK/ntWyeJs/dcnwiiHESd3N4uteK9FDLf0S0W8E6Y0sVRXpOCoQclQqNg==} resolution: {integrity: sha512-DJay3ep76bKUDImmn//W5SvpjRN5LmK/ntWyeJs/dcnwiiHESd3N4uteK9FDLf0S0W8E6Y0sVRXpOCoQclQqNg==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.55.3': '@rollup/rollup-linux-arm64-musl@4.55.3':
resolution: {integrity: sha512-BKKWQkY2WgJ5MC/ayvIJTHjy0JUGb5efaHCUiG/39sSUvAYRBaO3+/EK0AZT1RF3pSj86O24GLLik9mAYu0IJg==} resolution: {integrity: sha512-BKKWQkY2WgJ5MC/ayvIJTHjy0JUGb5efaHCUiG/39sSUvAYRBaO3+/EK0AZT1RF3pSj86O24GLLik9mAYu0IJg==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.55.3': '@rollup/rollup-linux-loong64-gnu@4.55.3':
resolution: {integrity: sha512-Q9nVlWtKAG7ISW80OiZGxTr6rYtyDSkauHUtvkQI6TNOJjFvpj4gcH+KaJihqYInnAzEEUetPQubRwHef4exVg==} resolution: {integrity: sha512-Q9nVlWtKAG7ISW80OiZGxTr6rYtyDSkauHUtvkQI6TNOJjFvpj4gcH+KaJihqYInnAzEEUetPQubRwHef4exVg==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.55.3': '@rollup/rollup-linux-loong64-musl@4.55.3':
resolution: {integrity: sha512-2H5LmhzrpC4fFRNwknzmmTvvyJPHwESoJgyReXeFoYYuIDfBhP29TEXOkCJE/KxHi27mj7wDUClNq78ue3QEBQ==} resolution: {integrity: sha512-2H5LmhzrpC4fFRNwknzmmTvvyJPHwESoJgyReXeFoYYuIDfBhP29TEXOkCJE/KxHi27mj7wDUClNq78ue3QEBQ==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.55.3': '@rollup/rollup-linux-ppc64-gnu@4.55.3':
resolution: {integrity: sha512-9S542V0ie9LCTznPYlvaeySwBeIEa7rDBgLHKZ5S9DBgcqdJYburabm8TqiqG6mrdTzfV5uttQRHcbKff9lWtA==} resolution: {integrity: sha512-9S542V0ie9LCTznPYlvaeySwBeIEa7rDBgLHKZ5S9DBgcqdJYburabm8TqiqG6mrdTzfV5uttQRHcbKff9lWtA==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.55.3': '@rollup/rollup-linux-ppc64-musl@4.55.3':
resolution: {integrity: sha512-ukxw+YH3XXpcezLgbJeasgxyTbdpnNAkrIlFGDl7t+pgCxZ89/6n1a+MxlY7CegU+nDgrgdqDelPRNQ/47zs0g==} resolution: {integrity: sha512-ukxw+YH3XXpcezLgbJeasgxyTbdpnNAkrIlFGDl7t+pgCxZ89/6n1a+MxlY7CegU+nDgrgdqDelPRNQ/47zs0g==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.55.3': '@rollup/rollup-linux-riscv64-gnu@4.55.3':
resolution: {integrity: sha512-Iauw9UsTTvlF++FhghFJjqYxyXdggXsOqGpFBylaRopVpcbfyIIsNvkf9oGwfgIcf57z3m8+/oSYTo6HutBFNw==} resolution: {integrity: sha512-Iauw9UsTTvlF++FhghFJjqYxyXdggXsOqGpFBylaRopVpcbfyIIsNvkf9oGwfgIcf57z3m8+/oSYTo6HutBFNw==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.55.3': '@rollup/rollup-linux-riscv64-musl@4.55.3':
resolution: {integrity: sha512-3OqKAHSEQXKdq9mQ4eajqUgNIK27VZPW3I26EP8miIzuKzCJ3aW3oEn2pzF+4/Hj/Moc0YDsOtBgT5bZ56/vcA==} resolution: {integrity: sha512-3OqKAHSEQXKdq9mQ4eajqUgNIK27VZPW3I26EP8miIzuKzCJ3aW3oEn2pzF+4/Hj/Moc0YDsOtBgT5bZ56/vcA==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.55.3': '@rollup/rollup-linux-s390x-gnu@4.55.3':
resolution: {integrity: sha512-0CM8dSVzVIaqMcXIFej8zZrSFLnGrAE8qlNbbHfTw1EEPnFTg1U1ekI0JdzjPyzSfUsHWtodilQQG/RA55berA==} resolution: {integrity: sha512-0CM8dSVzVIaqMcXIFej8zZrSFLnGrAE8qlNbbHfTw1EEPnFTg1U1ekI0JdzjPyzSfUsHWtodilQQG/RA55berA==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.55.3': '@rollup/rollup-linux-x64-gnu@4.55.3':
resolution: {integrity: sha512-+fgJE12FZMIgBaKIAGd45rxf+5ftcycANJRWk8Vz0NnMTM5rADPGuRFTYar+Mqs560xuART7XsX2lSACa1iOmQ==} resolution: {integrity: sha512-+fgJE12FZMIgBaKIAGd45rxf+5ftcycANJRWk8Vz0NnMTM5rADPGuRFTYar+Mqs560xuART7XsX2lSACa1iOmQ==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.55.3': '@rollup/rollup-linux-x64-musl@4.55.3':
resolution: {integrity: sha512-tMD7NnbAolWPzQlJQJjVFh/fNH3K/KnA7K8gv2dJWCwwnaK6DFCYST1QXYWfu5V0cDwarWC8Sf/cfMHniNq21A==} resolution: {integrity: sha512-tMD7NnbAolWPzQlJQJjVFh/fNH3K/KnA7K8gv2dJWCwwnaK6DFCYST1QXYWfu5V0cDwarWC8Sf/cfMHniNq21A==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.55.3': '@rollup/rollup-openbsd-x64@4.55.3':
resolution: {integrity: sha512-u5KsqxOxjEeIbn7bUK1MPM34jrnPwjeqgyin4/N6e/KzXKfpE9Mi0nCxcQjaM9lLmPcHmn/xx1yOjgTMtu1jWQ==} resolution: {integrity: sha512-u5KsqxOxjEeIbn7bUK1MPM34jrnPwjeqgyin4/N6e/KzXKfpE9Mi0nCxcQjaM9lLmPcHmn/xx1yOjgTMtu1jWQ==}
@ -3214,11 +3210,6 @@ packages:
class-variance-authority@0.7.1: class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
clawdbot@2026.1.24-3:
resolution: {integrity: sha512-zt9BzhWXduq8ZZR4rfzQDurQWAgmijTTyPZCQGrn5ew6wCEwhxxEr2/NHG7IlCwcfRsKymsY4se9KMhoNz0JtQ==}
engines: {node: '>=22.12.0'}
hasBin: true
cli-cursor@5.0.0: cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -3407,6 +3398,9 @@ packages:
resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==}
engines: {node: '>=0.3.1'} engines: {node: '>=0.3.1'}
dingtalk-stream@2.1.4:
resolution: {integrity: sha512-rgQbXLGWfASuB9onFcqXTnRSj4ZotimhBOnzrB4kS19AaU9lshXiuofs1GAYcKh5uzPWCAuEs3tMtiadTQWP4A==}
discord-api-types@0.38.37: discord-api-types@0.38.37:
resolution: {integrity: sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==} resolution: {integrity: sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==}
@ -4097,24 +4091,28 @@ packages:
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.30.2: lightningcss-linux-arm64-musl@1.30.2:
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.30.2: lightningcss-linux-x64-gnu@1.30.2:
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.30.2: lightningcss-linux-x64-musl@1.30.2:
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.30.2: lightningcss-win32-arm64-msvc@1.30.2:
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
@ -6848,39 +6846,10 @@ snapshots:
'@kwsites/promise-deferred@1.1.1': '@kwsites/promise-deferred@1.1.1':
optional: true 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)': '@lancedb/lancedb@0.23.0(apache-arrow@18.1.0)':
dependencies: dependencies:
apache-arrow: 18.1.0 apache-arrow: 18.1.0
reflect-metadata: 0.2.2 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
'@line/bot-sdk@10.6.0': '@line/bot-sdk@10.6.0':
dependencies: dependencies:
@ -7189,12 +7158,6 @@ snapshots:
'@node-llama-cpp/linux-armv7l@3.15.0': '@node-llama-cpp/linux-armv7l@3.15.0':
optional: true 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': '@node-llama-cpp/linux-x64-vulkan@3.15.0':
optional: true optional: true
@ -7210,15 +7173,6 @@ snapshots:
'@node-llama-cpp/win-arm64@3.15.0': '@node-llama-cpp/win-arm64@3.15.0':
optional: true 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': '@node-llama-cpp/win-x64@3.15.0':
optional: true optional: true
@ -9098,84 +9052,6 @@ snapshots:
dependencies: dependencies:
clsx: 2.1.1 clsx: 2.1.1
clawdbot@2026.1.24-3(@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: cli-cursor@5.0.0:
dependencies: dependencies:
restore-cursor: 5.1.0 restore-cursor: 5.1.0
@ -9349,6 +9225,16 @@ snapshots:
diff@8.0.3: {} diff@8.0.3: {}
dingtalk-stream@2.1.4:
dependencies:
axios: 1.13.2(debug@4.4.3)
debug: 4.4.3
ws: 8.19.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
discord-api-types@0.38.37: {} discord-api-types@0.38.37: {}
docx-preview@0.3.7: docx-preview@0.3.7:
@ -10526,16 +10412,11 @@ snapshots:
'@node-llama-cpp/linux-arm64': 3.15.0 '@node-llama-cpp/linux-arm64': 3.15.0
'@node-llama-cpp/linux-armv7l': 3.15.0 '@node-llama-cpp/linux-armv7l': 3.15.0
'@node-llama-cpp/linux-x64': 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/linux-x64-vulkan': 3.15.0
'@node-llama-cpp/mac-arm64-metal': 3.15.0 '@node-llama-cpp/mac-arm64-metal': 3.15.0
'@node-llama-cpp/mac-x64': 3.15.0 '@node-llama-cpp/mac-x64': 3.15.0
'@node-llama-cpp/win-arm64': 3.15.0 '@node-llama-cpp/win-arm64': 3.15.0
'@node-llama-cpp/win-x64': 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 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color

View File

@ -1,3 +1,4 @@
import type { DingTalkConfig } from "./types.dingtalk.js";
import type { DiscordConfig } from "./types.discord.js"; import type { DiscordConfig } from "./types.discord.js";
import type { GoogleChatConfig } from "./types.googlechat.js"; import type { GoogleChatConfig } from "./types.googlechat.js";
import type { IMessageConfig } from "./types.imessage.js"; import type { IMessageConfig } from "./types.imessage.js";
@ -25,6 +26,7 @@ export type ChannelDefaultsConfig = {
export type ChannelsConfig = { export type ChannelsConfig = {
defaults?: ChannelDefaultsConfig; defaults?: ChannelDefaultsConfig;
dingtalk?: DingTalkConfig;
whatsapp?: WhatsAppConfig; whatsapp?: WhatsAppConfig;
telegram?: TelegramConfig; telegram?: TelegramConfig;
discord?: DiscordConfig; discord?: DiscordConfig;

View File

@ -0,0 +1,90 @@
import type {
BlockStreamingCoalesceConfig,
DmPolicy,
GroupPolicy,
MarkdownConfig,
ReplyToMode,
} from "./types.base.js";
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
import type { DmConfig } from "./types.messages.js";
import type { GroupToolPolicyBySenderConfig, GroupToolPolicyConfig } from "./types.tools.js";
export type DingTalkDmConfig = {
/** If false, ignore all incoming DingTalk DMs. Default: true. */
enabled?: boolean;
/** Direct message access policy (default: pairing). */
policy?: DmPolicy;
/** Allowlist for DM senders (user IDs). */
allowFrom?: Array<string | number>;
};
export type DingTalkGroupConfig = {
/** If false, disable the bot in this group. (Alias for allow: false.) */
enabled?: boolean;
/** Legacy group allow toggle; prefer enabled. */
allow?: boolean;
/** Require mentioning the bot to trigger replies. */
requireMention?: boolean;
/** Optional tool policy overrides for this group. */
tools?: GroupToolPolicyConfig;
toolsBySender?: GroupToolPolicyBySenderConfig;
/** Allowlist of users that can invoke the bot in this group. */
users?: Array<string | number>;
/** Optional system prompt for this group. */
systemPrompt?: string;
};
export type DingTalkAccountConfig = {
/** Optional display name for this account (used in CLI/UI lists). */
name?: string;
/** Optional provider capability tags used for agent/runtime guidance. */
capabilities?: string[];
/** Markdown formatting overrides (tables). */
markdown?: MarkdownConfig;
/** Allow channel-initiated config writes (default: true). */
configWrites?: boolean;
/** If false, do not start this DingTalk account. Default: true. */
enabled?: boolean;
/** Stream mode: Client ID (AppKey). */
clientId?: string;
/** Stream mode: Client Secret (AppSecret). */
clientSecret?: string;
/** Allow bot-authored messages to trigger replies (default: false). */
allowBots?: boolean;
/** Default mention requirement for group chats (default: true). */
requireMention?: boolean;
/**
* Controls how group chats are handled:
* - "open": groups bypass allowlists; mention-gating applies
* - "disabled": block all group chats
* - "allowlist": only allow groups present in channels.dingtalk.groups
*/
groupPolicy?: GroupPolicy;
/** Max group messages to keep as history context (0 disables). */
historyLimit?: number;
/** Max DM turns to keep as history context. */
dmHistoryLimit?: number;
/** Per-DM config overrides keyed by user ID. */
dms?: Record<string, DmConfig>;
/** Outbound text chunk size (chars). Default: 4000. */
textChunkLimit?: number;
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
chunkMode?: "length" | "newline";
/** Merge streamed block replies before sending. */
blockStreaming?: boolean;
/** Merge streamed block replies before sending. */
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
/** Max media size in MB. */
mediaMaxMb?: number;
/** Control reply threading when reply tags are present (off|first|all). */
replyToMode?: ReplyToMode;
dm?: DingTalkDmConfig;
groups?: Record<string, DingTalkGroupConfig>;
/** Heartbeat visibility settings for this channel. */
heartbeat?: ChannelHeartbeatVisibilityConfig;
};
export type DingTalkConfig = {
/** Optional per-account DingTalk configuration (multi-account). */
accounts?: Record<string, DingTalkAccountConfig>;
} & DingTalkAccountConfig;

View File

@ -9,6 +9,7 @@ export * from "./types.browser.js";
export * from "./types.channels.js"; export * from "./types.channels.js";
export * from "./types.clawdbot.js"; export * from "./types.clawdbot.js";
export * from "./types.cron.js"; export * from "./types.cron.js";
export * from "./types.dingtalk.js";
export * from "./types.discord.js"; export * from "./types.discord.js";
export * from "./types.googlechat.js"; export * from "./types.googlechat.js";
export * from "./types.gateway.js"; export * from "./types.gateway.js";

View File

@ -770,3 +770,65 @@ export const MSTeamsConfigSchema = z
'channels.msteams.dmPolicy="open" requires channels.msteams.allowFrom to include "*"', 'channels.msteams.dmPolicy="open" requires channels.msteams.allowFrom to include "*"',
}); });
}); });
// DingTalk schemas
export const DingTalkDmSchema = z
.object({
enabled: z.boolean().optional(),
policy: DmPolicySchema.optional().default("pairing"),
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
})
.strict()
.superRefine((value, ctx) => {
requireOpenAllowFrom({
policy: value.policy,
allowFrom: value.allowFrom,
ctx,
path: ["allowFrom"],
message:
'channels.dingtalk.dm.policy="open" requires channels.dingtalk.dm.allowFrom to include "*"',
});
});
export const DingTalkGroupSchema = z
.object({
enabled: z.boolean().optional(),
allow: z.boolean().optional(),
requireMention: z.boolean().optional(),
tools: ToolPolicySchema,
toolsBySender: ToolPolicyBySenderSchema,
users: z.array(z.union([z.string(), z.number()])).optional(),
systemPrompt: z.string().optional(),
})
.strict();
export const DingTalkAccountSchema = z
.object({
name: z.string().optional(),
capabilities: z.array(z.string()).optional(),
markdown: MarkdownConfigSchema.optional(),
configWrites: z.boolean().optional(),
enabled: z.boolean().optional(),
clientId: z.string().optional(),
clientSecret: z.string().optional(),
allowBots: z.boolean().optional(),
requireMention: z.boolean().optional(),
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
historyLimit: z.number().int().min(0).optional(),
dmHistoryLimit: z.number().int().min(0).optional(),
dms: z.record(z.string(), DmConfigSchema.optional()).optional(),
textChunkLimit: z.number().int().positive().optional(),
chunkMode: z.enum(["length", "newline"]).optional(),
blockStreaming: z.boolean().optional(),
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
mediaMaxMb: z.number().positive().optional(),
replyToMode: ReplyToModeSchema.optional(),
dm: DingTalkDmSchema.optional(),
groups: z.record(z.string(), DingTalkGroupSchema.optional()).optional(),
heartbeat: ChannelHeartbeatVisibilitySchema,
})
.strict();
export const DingTalkConfigSchema = DingTalkAccountSchema.extend({
accounts: z.record(z.string(), DingTalkAccountSchema.optional()).optional(),
});

View File

@ -2,6 +2,7 @@ import { z } from "zod";
import { import {
BlueBubblesConfigSchema, BlueBubblesConfigSchema,
DingTalkConfigSchema,
DiscordConfigSchema, DiscordConfigSchema,
GoogleChatConfigSchema, GoogleChatConfigSchema,
IMessageConfigSchema, IMessageConfigSchema,
@ -27,6 +28,7 @@ export const ChannelsSchema = z
}) })
.strict() .strict()
.optional(), .optional(),
dingtalk: DingTalkConfigSchema.optional(),
whatsapp: WhatsAppConfigSchema.optional(), whatsapp: WhatsAppConfigSchema.optional(),
telegram: TelegramConfigSchema.optional(), telegram: TelegramConfigSchema.optional(),
discord: DiscordConfigSchema.optional(), discord: DiscordConfigSchema.optional(),

View File

@ -84,6 +84,10 @@ export type {
GroupToolPolicyBySenderConfig, GroupToolPolicyBySenderConfig,
MarkdownConfig, MarkdownConfig,
MarkdownTableMode, MarkdownTableMode,
DingTalkAccountConfig,
DingTalkConfig,
DingTalkDmConfig,
DingTalkGroupConfig,
GoogleChatAccountConfig, GoogleChatAccountConfig,
GoogleChatConfig, GoogleChatConfig,
GoogleChatDmConfig, GoogleChatDmConfig,
@ -95,6 +99,8 @@ export type {
MSTeamsTeamConfig, MSTeamsTeamConfig,
} from "../config/types.js"; } from "../config/types.js";
export { export {
DingTalkConfigSchema,
DingTalkAccountSchema,
DiscordConfigSchema, DiscordConfigSchema,
GoogleChatConfigSchema, GoogleChatConfigSchema,
IMessageConfigSchema, IMessageConfigSchema,