moltbot-dingtalk-stream 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,293 +1,391 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dingtalkPlugin = void 0;
6
4
  const dingtalk_stream_1 = require("dingtalk-stream");
7
- const axios_1 = __importDefault(require("axios"));
8
- // Store plugin runtime
5
+ const runtime_js_1 = require("./runtime.js");
6
+ const schema_js_1 = require("./schema.js");
7
+ // ============================================================================
8
+ // Channel Meta
9
+ // ============================================================================
10
+ const meta = {
11
+ id: schema_js_1.CHANNEL_ID,
12
+ label: "DingTalk",
13
+ selectionLabel: "DingTalk Bot (Stream)",
14
+ docsPath: "/channels/dingtalk",
15
+ docsLabel: "dingtalk",
16
+ blurb: "DingTalk bot channel plugin (Stream mode)",
17
+ order: 100,
18
+ aliases: ["dt", "ding", "dingtalk"],
19
+ };
20
+ // ============================================================================
21
+ // Store plugin runtime reference
22
+ // ============================================================================
9
23
  let pluginRuntime = null;
10
- // Store session webhooks for reply
11
- const sessionWebhooks = new Map();
12
- // Store active clients for each account
13
- const activeClients = new Map();
14
- // Helper functions
15
- function listDingTalkAccountIds(cfg) {
16
- const accounts = cfg.channels?.['moltbot-dingtalk-stream']?.accounts;
17
- return accounts ? Object.keys(accounts) : [];
18
- }
19
- function resolveDingTalkAccount(opts) {
20
- const { cfg, accountId = 'default' } = opts;
21
- const account = cfg.channels?.['moltbot-dingtalk-stream']?.accounts?.[accountId];
22
- return {
23
- accountId,
24
- name: account?.name,
25
- enabled: account?.enabled ?? false,
26
- configured: Boolean(account?.clientId && account?.clientSecret),
27
- config: account || { clientId: '', clientSecret: '' }
28
- };
29
- }
24
+ // ============================================================================
30
25
  // DingTalk Channel Plugin
31
- const dingTalkChannelPlugin = {
32
- id: "moltbot-dingtalk-stream",
33
- meta: {
34
- id: "moltbot-dingtalk-stream",
35
- label: "钉钉",
36
- selectionLabel: "DingTalk Bot (Stream)",
37
- docsPath: "/channels/moltbot-dingtalk-stream",
38
- docsLabel: "dingtalk",
39
- blurb: "钉钉机器人通道插件 (Stream模式)",
40
- order: 100,
41
- aliases: ["dt", "ding"],
42
- },
26
+ // ============================================================================
27
+ exports.dingtalkPlugin = {
28
+ id: schema_js_1.CHANNEL_ID,
29
+ meta,
43
30
  capabilities: {
44
31
  chatTypes: ["direct", "group"],
32
+ media: true,
33
+ threads: false,
34
+ },
35
+ reload: { configPrefixes: [`channels.${schema_js_1.CHANNEL_ID}`] },
36
+ configSchema: schema_js_1.DingTalkConfigSchema,
37
+ // ============================================================================
38
+ // Config Management
39
+ // ============================================================================
40
+ config: {
41
+ listAccountIds: (cfg) => (0, schema_js_1.listDingTalkAccountIds)(cfg),
42
+ resolveAccount: (cfg, accountId) => (0, schema_js_1.resolveDingTalkAccount)({ cfg, accountId }),
43
+ defaultAccountId: (cfg) => (0, schema_js_1.resolveDefaultDingTalkAccountId)(cfg),
44
+ setAccountEnabled: ({ cfg, accountId, enabled }) => (0, schema_js_1.setAccountEnabledInConfig)({ cfg, accountId, enabled }),
45
+ deleteAccount: ({ cfg, accountId }) => (0, schema_js_1.deleteAccountFromConfig)({ cfg, accountId }),
46
+ isConfigured: (account) => account.configured,
47
+ describeAccount: (account) => ({
48
+ accountId: account.accountId,
49
+ name: account.name,
50
+ enabled: account.enabled,
51
+ configured: account.configured,
52
+ tokenSource: account.tokenSource,
53
+ }),
54
+ },
55
+ // ============================================================================
56
+ // Security (DM Policy)
57
+ // ============================================================================
58
+ security: {
59
+ resolveDmPolicy: ({ cfg, accountId, account }) => {
60
+ const resolvedAccountId = accountId ?? account.accountId ?? schema_js_1.DEFAULT_ACCOUNT_ID;
61
+ const channelConfig = cfg.channels?.[schema_js_1.CHANNEL_ID];
62
+ const useAccountPath = Boolean(channelConfig?.accounts?.[resolvedAccountId]);
63
+ const allowFromPath = useAccountPath
64
+ ? `channels.${schema_js_1.CHANNEL_ID}.accounts.${resolvedAccountId}.dm.`
65
+ : `channels.${schema_js_1.CHANNEL_ID}.dm.`;
66
+ return {
67
+ policy: account.config.dm?.policy ?? "open",
68
+ allowFrom: account.config.dm?.allowFrom ?? [],
69
+ allowFromPath,
70
+ normalizeEntry: (raw) => raw.replace(/^dingtalk:/i, ""),
71
+ };
72
+ },
73
+ },
74
+ // ============================================================================
75
+ // Mentions
76
+ // ============================================================================
77
+ mentions: {
78
+ stripPatterns: () => ["@\\S+\\s*"],
79
+ },
80
+ // ============================================================================
81
+ // Groups
82
+ // ============================================================================
83
+ groups: {
84
+ resolveRequireMention: ({ cfg, accountId }) => {
85
+ const account = (0, schema_js_1.resolveDingTalkAccount)({ cfg, accountId });
86
+ return account.config.requireMention ?? true;
87
+ },
88
+ },
89
+ // ============================================================================
90
+ // Messaging
91
+ // ============================================================================
92
+ messaging: {
93
+ normalizeTarget: (target) => {
94
+ if (target.startsWith("dingtalk:"))
95
+ return target;
96
+ if (target.startsWith("group:"))
97
+ return `dingtalk:${target}`;
98
+ if (target.startsWith("user:"))
99
+ return `dingtalk:${target}`;
100
+ return `dingtalk:${target}`;
101
+ },
102
+ targetResolver: {
103
+ looksLikeId: (id) => /^[a-zA-Z0-9_-]+$/.test(id),
104
+ hint: "<conversationId|user:ID>",
105
+ },
45
106
  },
46
- reload: { configPrefixes: ["channels.moltbot-dingtalk-stream"] },
47
- configSchema: {
48
- type: "object",
49
- properties: {
50
- channels: {
51
- type: "object",
52
- properties: {
53
- 'moltbot-dingtalk-stream': {
54
- type: "object",
55
- properties: {
56
- accounts: {
57
- type: "object",
58
- additionalProperties: {
59
- type: "object",
60
- properties: {
61
- enabled: { type: "boolean" },
62
- clientId: { type: "string" },
63
- clientSecret: { type: "string" },
64
- webhookUrl: { type: "string" },
65
- name: { type: "string" },
66
- },
67
- required: ["clientId", "clientSecret"],
68
- },
107
+ // ============================================================================
108
+ // Setup (Account Configuration)
109
+ // ============================================================================
110
+ setup: {
111
+ resolveAccountId: ({ accountId }) => (0, schema_js_1.normalizeAccountId)(accountId),
112
+ applyAccountName: ({ cfg, accountId, name }) => (0, schema_js_1.applyAccountNameToConfig)({ cfg, accountId, name }),
113
+ validateInput: ({ accountId, input }) => {
114
+ if (input.useEnv && accountId !== schema_js_1.DEFAULT_ACCOUNT_ID) {
115
+ return "Environment variables can only be used for the default account";
116
+ }
117
+ if (!input.useEnv && (!input.clientId || !input.clientSecret)) {
118
+ return "DingTalk requires clientId and clientSecret";
119
+ }
120
+ return null;
121
+ },
122
+ applyAccountConfig: ({ cfg, accountId, input }) => {
123
+ const namedConfig = (0, schema_js_1.applyAccountNameToConfig)({
124
+ cfg,
125
+ accountId,
126
+ name: input.name,
127
+ });
128
+ if (accountId === schema_js_1.DEFAULT_ACCOUNT_ID) {
129
+ return {
130
+ ...namedConfig,
131
+ channels: {
132
+ ...namedConfig.channels,
133
+ [schema_js_1.CHANNEL_ID]: {
134
+ ...namedConfig.channels?.[schema_js_1.CHANNEL_ID],
135
+ enabled: true,
136
+ ...(input.useEnv ? {} : { clientId: input.clientId, clientSecret: input.clientSecret }),
137
+ },
138
+ },
139
+ };
140
+ }
141
+ return {
142
+ ...namedConfig,
143
+ channels: {
144
+ ...namedConfig.channels,
145
+ [schema_js_1.CHANNEL_ID]: {
146
+ ...namedConfig.channels?.[schema_js_1.CHANNEL_ID],
147
+ enabled: true,
148
+ accounts: {
149
+ ...namedConfig.channels?.[schema_js_1.CHANNEL_ID]?.accounts,
150
+ [accountId]: {
151
+ ...namedConfig.channels?.[schema_js_1.CHANNEL_ID]?.accounts?.[accountId],
152
+ enabled: true,
153
+ clientId: input.clientId,
154
+ clientSecret: input.clientSecret,
69
155
  },
70
156
  },
71
157
  },
72
158
  },
73
- },
159
+ };
74
160
  },
75
161
  },
76
- config: {
77
- listAccountIds: (cfg) => listDingTalkAccountIds(cfg),
78
- resolveAccount: (cfg, accountId) => resolveDingTalkAccount({ cfg, accountId }),
79
- defaultAccountId: (_cfg) => 'default',
80
- isConfigured: (account) => account.configured,
81
- describeAccount: (account) => ({
162
+ // ============================================================================
163
+ // Outbound (Send Messages)
164
+ // ============================================================================
165
+ outbound: {
166
+ deliveryMode: "direct",
167
+ textChunkLimit: 2000,
168
+ sendText: async ({ to, text, accountId }) => {
169
+ const result = await (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.sendMessage(to, text, {
170
+ accountId,
171
+ });
172
+ return { channel: schema_js_1.CHANNEL_ID, ...result };
173
+ },
174
+ sendMedia: async ({ to, text, mediaUrl, accountId }) => {
175
+ const result = await (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.sendMessage(to, text, {
176
+ accountId,
177
+ mediaUrl,
178
+ });
179
+ return { channel: schema_js_1.CHANNEL_ID, ...result };
180
+ },
181
+ },
182
+ // ============================================================================
183
+ // Status (Probe & Monitoring)
184
+ // ============================================================================
185
+ status: {
186
+ defaultRuntime: {
187
+ accountId: schema_js_1.DEFAULT_ACCOUNT_ID,
188
+ running: false,
189
+ lastStartAt: null,
190
+ lastStopAt: null,
191
+ lastError: null,
192
+ },
193
+ probeAccount: async ({ account, timeoutMs }) => {
194
+ if (!account.clientId || !account.clientSecret) {
195
+ return { ok: false, error: "Missing clientId or clientSecret" };
196
+ }
197
+ return (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.probe(account.clientId, account.clientSecret, timeoutMs);
198
+ },
199
+ buildAccountSnapshot: ({ account, runtime, probe }) => ({
82
200
  accountId: account.accountId,
83
201
  name: account.name,
84
202
  enabled: account.enabled,
85
203
  configured: account.configured,
204
+ tokenSource: account.tokenSource,
205
+ running: runtime?.running ?? false,
206
+ lastStartAt: runtime?.lastStartAt ?? null,
207
+ lastStopAt: runtime?.lastStopAt ?? null,
208
+ lastError: runtime?.lastError ?? null,
209
+ probe,
86
210
  }),
87
211
  },
212
+ // ============================================================================
213
+ // Gateway (Start/Stop Bot)
214
+ // ============================================================================
88
215
  gateway: {
89
216
  startAccount: async (ctx) => {
90
- const account = ctx.account;
91
- const config = account.config;
217
+ const { account, cfg, abortSignal, log, statusSink } = ctx;
92
218
  const accountId = account.accountId;
93
- if (!config.clientId || !config.clientSecret) {
94
- ctx.log?.warn?.(`[${accountId}] missing clientId or clientSecret`);
219
+ const core = pluginRuntime?.runtime;
220
+ if (!account.clientId || !account.clientSecret) {
221
+ log?.warn?.(`[${accountId}] Missing clientId or clientSecret`);
95
222
  return;
96
223
  }
97
- ctx.log?.info?.(`[${accountId}] starting DingTalk Stream client`);
224
+ if (!core?.channel?.reply) {
225
+ log?.error?.(`[${accountId}] runtime.channel.reply not available`);
226
+ return;
227
+ }
228
+ log?.info?.(`[${accountId}] Starting DingTalk Stream client`);
229
+ // Probe 检测凭据
98
230
  try {
99
- const client = new dingtalk_stream_1.DWClient({
100
- clientId: config.clientId,
101
- clientSecret: config.clientSecret,
102
- });
103
- // Helper to safely handle messages
104
- const handleMessage = async (res) => {
105
- try {
106
- const message = JSON.parse(res.data);
107
- const textContent = message.text?.content || "";
108
- const senderId = message.senderId;
109
- const convoId = message.conversationId;
110
- const msgId = message.msgId;
111
- // Store session webhook if provided (DingTalk Stream mode provides this for replies)
112
- if (message.sessionWebhook) {
113
- sessionWebhooks.set(convoId, message.sessionWebhook);
231
+ const probe = await (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.probe(account.clientId, account.clientSecret, 2500);
232
+ if (probe.ok) {
233
+ log?.info?.(`[${accountId}] Credentials verified successfully`);
234
+ ctx.setStatus?.({ accountId, probe });
235
+ }
236
+ else {
237
+ log?.warn?.(`[${accountId}] Credential verification failed: ${probe.error}`);
238
+ }
239
+ }
240
+ catch (err) {
241
+ log?.debug?.(`[${accountId}] Probe failed: ${String(err)}`);
242
+ }
243
+ const client = new dingtalk_stream_1.DWClient({
244
+ clientId: account.clientId,
245
+ clientSecret: account.clientSecret,
246
+ });
247
+ const handleMessage = async (res) => {
248
+ try {
249
+ const message = JSON.parse(res.data);
250
+ const textContent = message.text?.content || "";
251
+ const senderId = message.senderId;
252
+ const convoId = message.conversationId;
253
+ log?.info?.(`[${accountId}] Received message from ${message.senderNick || senderId}: ${textContent}`);
254
+ statusSink?.({ lastInboundAt: Date.now() });
255
+ if (!textContent)
256
+ return;
257
+ const rawBody = textContent;
258
+ const cleanedText = textContent.replace(/@\S+\s*/g, "").trim();
259
+ const chatType = String(message.conversationType) === "2" ? "group" : "direct";
260
+ // Store session webhook with multiple keys for flexible lookup
261
+ if (message.sessionWebhook) {
262
+ (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.setSessionWebhook(convoId, message.sessionWebhook);
263
+ if (chatType === "direct" && senderId) {
264
+ (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.setSessionWebhook(senderId, message.sessionWebhook);
265
+ (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.setSessionWebhook(`dingtalk:user:${senderId}`, message.sessionWebhook);
266
+ }
267
+ if (chatType === "group" && convoId) {
268
+ (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.setSessionWebhook(`dingtalk:channel:${convoId}`, message.sessionWebhook);
114
269
  }
115
- // Log reception
116
- ctx.log?.info?.(`[${accountId}] received message from ${message.senderNick || senderId}: ${textContent}`);
117
- // Filter out empty messages
118
- if (!textContent)
270
+ }
271
+ const route = core.channel.routing?.resolveAgentRoute?.({
272
+ cfg,
273
+ channel: schema_js_1.CHANNEL_ID,
274
+ accountId,
275
+ peer: {
276
+ kind: chatType === "group" ? "group" : "direct",
277
+ id: chatType === "group" ? convoId : senderId,
278
+ },
279
+ }) ?? { agentId: "main", sessionKey: `dingtalk:${convoId}`, accountId };
280
+ const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions?.(cfg) ?? {};
281
+ const body = core.channel.reply.formatAgentEnvelope?.({
282
+ channel: "DingTalk",
283
+ from: message.senderNick ?? message.senderId,
284
+ timestamp: message.createAt,
285
+ envelope: envelopeOptions,
286
+ body: cleanedText,
287
+ }) ?? cleanedText;
288
+ const ctxPayload = {
289
+ Body: body,
290
+ RawBody: rawBody,
291
+ CommandBody: cleanedText,
292
+ From: `dingtalk:user:${senderId}`,
293
+ To: chatType === "group" ? `dingtalk:channel:${convoId}` : `dingtalk:user:${senderId}`,
294
+ SessionKey: route.sessionKey,
295
+ AccountId: route.accountId,
296
+ ChatType: chatType,
297
+ ConversationLabel: chatType === "group" ? convoId : undefined,
298
+ SenderName: message.senderNick,
299
+ SenderId: senderId,
300
+ SenderUsername: message.senderNick,
301
+ Provider: "dingtalk",
302
+ Surface: "dingtalk",
303
+ MessageSid: message.msgId,
304
+ Timestamp: message.createAt,
305
+ GroupSubject: chatType === "group" ? convoId : undefined,
306
+ OriginatingChannel: schema_js_1.CHANNEL_ID,
307
+ OriginatingTo: chatType === "group" ? `dingtalk:channel:${convoId}` : `dingtalk:user:${senderId}`,
308
+ };
309
+ const finalizedCtx = core.channel.reply.finalizeInboundContext(ctxPayload);
310
+ const storePath = core.channel.session?.resolveStorePath?.(cfg.session, { agentId: route.agentId }) ?? "";
311
+ if (core.channel.session?.recordInboundSession) {
312
+ await core.channel.session.recordInboundSession({
313
+ storePath,
314
+ sessionKey: route.sessionKey,
315
+ ctx: finalizedCtx,
316
+ onRecordError: (err) => {
317
+ log?.error?.(`[${accountId}] Failed to record session: ${String(err)}`);
318
+ },
319
+ });
320
+ }
321
+ if (res.headers?.messageId) {
322
+ client.socketCallBackResponse(res.headers.messageId, { status: "SUCCEED" });
323
+ }
324
+ const DINGTALK_TEXT_LIMIT = 2000;
325
+ const deliverDingTalkReply = async (payload) => {
326
+ const text = payload.text || payload.content || "";
327
+ if (!text) {
328
+ log?.warn?.(`[${accountId}] Received empty payload`);
119
329
  return;
120
- // Simple text cleaning (remove @bot mentions if possible, though DingTalk usually gives clean content or we might need to parse entities)
121
- const cleanedText = textContent.replace(/@\w+\s*/g, '').trim();
122
- // Forward the message to Clawdbot for processing
123
- if (pluginRuntime?.runtime?.channel?.reply) {
124
- const replyModule = pluginRuntime.runtime.channel.reply;
125
- const chatType = String(message.conversationType) === '2' ? 'group' : 'direct';
126
- const fromAddress = chatType === 'group' ? `dingtalk:group:${convoId}` : `dingtalk:${senderId}`;
127
- const ctxPayload = {
128
- Body: cleanedText,
129
- RawBody: textContent,
130
- CommandBody: cleanedText,
131
- From: fromAddress,
132
- To: 'bot',
133
- SessionKey: `dingtalk:${convoId}`,
134
- AccountId: accountId,
135
- ChatType: chatType,
136
- SenderName: message.senderNick,
137
- SenderId: senderId,
138
- Provider: 'dingtalk',
139
- Surface: 'dingtalk',
140
- MessageSid: message.msgId,
141
- Timestamp: message.createAt,
142
- // Required for some logic
143
- GroupSubject: chatType === 'group' ? (message.conversationId) : undefined,
144
- };
145
- const finalizedCtx = replyModule.finalizeInboundContext(ctxPayload);
146
- let replyBuffer = "";
147
- let replySent = false;
148
- const sendToDingTalk = async (text) => {
149
- if (!text)
150
- return;
151
- if (replySent) {
152
- ctx.log?.info?.(`[${accountId}] Reply already sent, skipping buffer flush.`);
153
- return;
154
- }
155
- const replyWebhook = sessionWebhooks.get(convoId) || config.webhookUrl;
156
- if (!replyWebhook) {
157
- ctx.log?.error?.(`[${accountId}] No webhook to reply to ${convoId}`);
158
- return;
159
- }
160
- try {
161
- await axios_1.default.post(replyWebhook, {
162
- msgtype: "text",
163
- text: { content: text }
164
- }, { headers: { 'Content-Type': 'application/json' } });
165
- replySent = true;
166
- ctx.log?.info?.(`[${accountId}] Reply sent successfully.`);
167
- }
168
- catch (e) {
169
- ctx.log?.error?.(`[${accountId}] Failed to send reply: ${e}`);
170
- }
171
- };
172
- const dispatcher = {
173
- sendFinalReply: (payload) => {
174
- const text = payload.text || payload.content || '';
175
- sendToDingTalk(text).catch(e => ctx.log?.error?.(`[${accountId}] sendToDingTalk failed: ${e}`));
176
- return true;
177
- },
178
- typing: async () => { },
179
- reaction: async () => { },
180
- isSynchronous: () => false,
181
- waitForIdle: async () => { },
182
- sendBlockReply: async (block) => {
183
- // Accumulate text from blocks
184
- const text = block.text || block.delta || block.content || '';
185
- if (text) {
186
- replyBuffer += text;
187
- }
188
- },
189
- getQueuedCounts: () => ({ active: 0, queued: 0, final: 0 })
190
- };
191
- // Internal dispatch
192
- const dispatchPromise = replyModule.dispatchReplyFromConfig({
193
- ctx: finalizedCtx,
194
- cfg: pluginRuntime.config,
195
- dispatcher: dispatcher,
196
- replyOptions: {}
330
+ }
331
+ log?.info?.(`[${accountId}] Sending reply: ${text.substring(0, 50)}...`);
332
+ const chunkMode = core.channel.text?.resolveChunkMode?.(cfg, schema_js_1.CHANNEL_ID, accountId) ?? "smart";
333
+ const chunks = core.channel.text?.chunkMarkdownTextWithMode?.(text, DINGTALK_TEXT_LIMIT, chunkMode) ?? [text];
334
+ for (const chunk of chunks.length > 0 ? chunks : [text]) {
335
+ if (!chunk)
336
+ continue;
337
+ const result = await (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.sendMessage(convoId, chunk, {
338
+ accountId,
197
339
  });
198
- // ACK immediately to prevent retries
199
- if (res.headers && res.headers.messageId) {
200
- client.socketCallBackResponse(res.headers.messageId, { status: "SUCCEED" });
340
+ if (result.ok) {
341
+ log?.info?.(`[${accountId}] Reply sent successfully`);
342
+ statusSink?.({ lastOutboundAt: Date.now() });
201
343
  }
202
- // Wait for run to finish
203
- await dispatchPromise;
204
- // If final reply wasn't called but we have buffer (streaming case where agent didn't return final payload?)
205
- if (!replySent && replyBuffer) {
206
- ctx.log?.info?.(`[${accountId}] Sending accumulated buffer from blocks (len=${replyBuffer.length}).`);
207
- await sendToDingTalk(replyBuffer);
344
+ else {
345
+ log?.error?.(`[${accountId}] Failed to send reply: ${result.error}`);
208
346
  }
209
347
  }
210
- else {
211
- ctx.log?.error?.(`[${accountId}] runtime.channel.reply not available`);
212
- }
213
- }
214
- catch (error) {
215
- ctx.log?.error?.(`[${accountId}] error processing message: ${error instanceof Error ? error.message : String(error)}`);
216
- console.error('DingTalk Handler Error:', error);
217
- }
218
- };
219
- // Register callback for robot messages
220
- client.registerCallbackListener('/v1.0/im/bot/messages/get', handleMessage);
221
- // Connect to DingTalk Stream
222
- await client.connect();
223
- activeClients.set(accountId, client);
224
- ctx.log?.info?.(`[${accountId}] DingTalk Stream client connected`);
225
- // Handle abort signal for cleanup
226
- ctx.abortSignal?.addEventListener('abort', () => {
227
- ctx.log?.info?.(`[${accountId}] stopping DingTalk Stream client`);
228
- client.disconnect();
229
- activeClients.delete(accountId);
230
- });
231
- }
232
- catch (error) {
233
- ctx.log?.error?.(`[${accountId}] failed to start: ${error instanceof Error ? error.message : String(error)}`);
234
- throw error;
235
- }
236
- },
237
- },
238
- outbound: {
239
- deliveryMode: "direct",
240
- sendText: async (opts) => {
241
- const { text, account, target } = opts;
242
- const config = account.config;
243
- // Try session webhook first (for replies)
244
- const sessionWebhook = sessionWebhooks.get(target);
245
- if (sessionWebhook) {
246
- try {
247
- await axios_1.default.post(sessionWebhook, {
248
- msgtype: "text",
249
- text: { content: text }
250
- }, {
251
- headers: { 'Content-Type': 'application/json' }
252
- });
253
- return { ok: true };
254
- }
255
- catch (error) {
256
- // Fall through to webhookUrl
257
- }
258
- }
259
- // Fallback to webhookUrl for proactive messages
260
- if (config?.webhookUrl) {
261
- try {
262
- await axios_1.default.post(config.webhookUrl, {
263
- msgtype: "text",
264
- text: { content: text }
265
- }, {
266
- headers: { 'Content-Type': 'application/json' }
348
+ };
349
+ log?.info?.(`[${accountId}] Using dispatchReplyWithBufferedBlockDispatcher`);
350
+ await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
351
+ ctx: finalizedCtx,
352
+ cfg,
353
+ dispatcherOptions: {
354
+ deliver: deliverDingTalkReply,
355
+ onError: (err, info) => {
356
+ log?.error?.(`[${accountId}] DingTalk ${info.kind} reply failed: ${String(err)}`);
357
+ },
358
+ },
267
359
  });
268
- return { ok: true };
360
+ log?.info?.(`[${accountId}] dispatchReplyWithBufferedBlockDispatcher completed`);
269
361
  }
270
362
  catch (error) {
271
- return { ok: false, error: error instanceof Error ? error.message : String(error) };
363
+ log?.error?.(`[${accountId}] Error processing message: ${error instanceof Error ? error.message : String(error)}`);
272
364
  }
273
- }
274
- return { ok: false, error: "No webhook available for sending messages" };
275
- }
276
- }
365
+ };
366
+ client.registerCallbackListener("/v1.0/im/bot/messages/get", handleMessage);
367
+ await client.connect();
368
+ (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.setClient(accountId, client);
369
+ log?.info?.(`[${accountId}] DingTalk Stream client connected`);
370
+ abortSignal?.addEventListener("abort", () => {
371
+ log?.info?.(`[${accountId}] Stopping DingTalk Stream client`);
372
+ client.disconnect();
373
+ (0, runtime_js_1.getDingTalkRuntime)().channel.dingtalk.removeClient(accountId);
374
+ });
375
+ },
376
+ },
277
377
  };
278
- // Plugin object format required by Clawdbot
378
+ // ============================================================================
379
+ // Plugin Export
380
+ // ============================================================================
279
381
  const plugin = {
280
- id: "moltbot-dingtalk-stream",
382
+ id: schema_js_1.CHANNEL_ID,
281
383
  name: "DingTalk Channel",
282
- description: "DingTalk channel plugin using Stream mode",
283
- configSchema: {
284
- type: "object",
285
- properties: {}
286
- },
384
+ description: "DingTalk channel plugin (Stream mode)",
287
385
  register(api) {
288
386
  pluginRuntime = api;
289
- api.registerChannel({ plugin: dingTalkChannelPlugin });
290
- }
387
+ api.registerChannel({ plugin: exports.dingtalkPlugin });
388
+ },
291
389
  };
292
390
  exports.default = plugin;
293
391
  //# sourceMappingURL=index.js.map