@zhin.js/adapter-dingtalk 1.0.81 → 1.1.2

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +709 -3
  2. package/PERMITS.md +19 -0
  3. package/README.md +67 -333
  4. package/adapters/dingtalk/index.js +33 -0
  5. package/adapters/dingtalk/index.ts +38 -0
  6. package/agents/dingtalk/agent.json +20 -0
  7. package/agents/dingtalk/boundaries.md +3 -0
  8. package/agents/dingtalk/conventions.md +3 -0
  9. package/agents/dingtalk/skills/dingtalk/SKILL.md +26 -0
  10. package/agents/dingtalk/skills/dingtalk/tools/add_chat_members/index.js +19 -0
  11. package/agents/dingtalk/skills/dingtalk/tools/add_chat_members/index.ts +20 -0
  12. package/agents/dingtalk/skills/dingtalk/tools/create_chat/index.js +20 -0
  13. package/agents/dingtalk/skills/dingtalk/tools/create_chat/index.ts +21 -0
  14. package/agents/dingtalk/skills/dingtalk/tools/dept_info/index.js +15 -0
  15. package/agents/dingtalk/skills/dingtalk/tools/dept_info/index.ts +15 -0
  16. package/agents/dingtalk/skills/dingtalk/tools/get_dept_users/index.js +16 -0
  17. package/agents/dingtalk/skills/dingtalk/tools/get_dept_users/index.ts +16 -0
  18. package/agents/dingtalk/skills/dingtalk/tools/get_user/index.js +15 -0
  19. package/agents/dingtalk/skills/dingtalk/tools/get_user/index.ts +15 -0
  20. package/agents/dingtalk/skills/dingtalk/tools/list_departments/index.js +16 -0
  21. package/agents/dingtalk/skills/dingtalk/tools/list_departments/index.ts +16 -0
  22. package/agents/dingtalk/skills/dingtalk/tools/send_work_notice/index.js +18 -0
  23. package/agents/dingtalk/skills/dingtalk/tools/send_work_notice/index.ts +18 -0
  24. package/agents/dingtalk/skills/dingtalk/tools/update_chat/index.js +29 -0
  25. package/agents/dingtalk/skills/dingtalk/tools/update_chat/index.ts +25 -0
  26. package/agents/dingtalk/system.md +3 -0
  27. package/commands/dingtalk/endpoint/add/[id]/index.js +3 -0
  28. package/commands/dingtalk/endpoint/add/[id]/index.ts +3 -0
  29. package/commands/dingtalk/endpoint/definition.js +19 -0
  30. package/commands/dingtalk/endpoint/definition.ts +19 -0
  31. package/commands/dingtalk/endpoint/list/index.js +3 -0
  32. package/commands/dingtalk/endpoint/list/index.ts +3 -0
  33. package/commands/dingtalk/endpoint/remove/[id]/index.js +3 -0
  34. package/commands/dingtalk/endpoint/remove/[id]/index.ts +3 -0
  35. package/lib/client.d.ts +12 -0
  36. package/lib/client.js +2 -0
  37. package/lib/dingtalk-runtime-state.d.ts +1 -0
  38. package/lib/dingtalk-runtime-state.js +6 -0
  39. package/lib/endpoint.d.ts +72 -0
  40. package/lib/endpoint.js +353 -0
  41. package/lib/index.d.ts +5 -15
  42. package/lib/index.js +5 -214
  43. package/lib/platform-permit.d.ts +14 -0
  44. package/lib/platform-permit.js +33 -0
  45. package/lib/protocol.d.ts +130 -0
  46. package/lib/protocol.js +264 -0
  47. package/lib/webhook.d.ts +13 -0
  48. package/lib/webhook.js +47 -0
  49. package/package.json +76 -19
  50. package/plugin.js +19 -0
  51. package/schema.json +97 -0
  52. package/src/client.ts +16 -0
  53. package/src/dingtalk-runtime-state.ts +7 -0
  54. package/src/endpoint.ts +439 -0
  55. package/src/index.ts +44 -228
  56. package/src/platform-permit.ts +48 -0
  57. package/src/protocol.ts +377 -0
  58. package/src/webhook.ts +75 -0
  59. package/lib/adapter.d.ts +0 -16
  60. package/lib/adapter.d.ts.map +0 -1
  61. package/lib/adapter.js +0 -37
  62. package/lib/adapter.js.map +0 -1
  63. package/lib/bot.d.ts +0 -46
  64. package/lib/bot.d.ts.map +0 -1
  65. package/lib/bot.js +0 -539
  66. package/lib/bot.js.map +0 -1
  67. package/lib/index.d.ts.map +0 -1
  68. package/lib/index.js.map +0 -1
  69. package/lib/types.d.ts +0 -58
  70. package/lib/types.d.ts.map +0 -1
  71. package/lib/types.js +0 -5
  72. package/lib/types.js.map +0 -1
  73. package/plugin.yml +0 -3
  74. package/skills/dingtalk/SKILL.md +0 -91
  75. package/src/adapter.ts +0 -44
  76. package/src/bot.ts +0 -590
  77. package/src/types.ts +0 -56
@@ -0,0 +1,130 @@
1
+ /**
2
+ * DingTalk protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
+ * Canonicalization is owned by gateway/core before endpoint.send.
4
+ */
5
+ import type { IncomingMessage } from 'node:http';
6
+ import { type ConversationRef } from '@zhin.js/im-contract';
7
+ /** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
8
+ export interface DingTalkEndpointConfig {
9
+ readonly id: string;
10
+ readonly appKey: string;
11
+ readonly appSecret: string;
12
+ readonly webhookPath?: string;
13
+ readonly robotCode?: string;
14
+ readonly apiBaseUrl?: string;
15
+ }
16
+ export interface ResolvedDingTalkConfig {
17
+ readonly context: 'dingtalk';
18
+ readonly id: string;
19
+ readonly appKey: string;
20
+ readonly appSecret: string;
21
+ readonly webhookPath: string;
22
+ readonly robotCode?: string;
23
+ readonly apiBaseUrl: string;
24
+ }
25
+ export interface DingTalkMessage {
26
+ readonly msgtype?: string;
27
+ readonly text?: {
28
+ readonly content?: string;
29
+ };
30
+ readonly msgId?: string;
31
+ readonly createAt?: number;
32
+ readonly conversationType?: string;
33
+ readonly conversationId?: string;
34
+ readonly senderId?: string;
35
+ readonly senderNick?: string;
36
+ readonly senderCorpId?: string;
37
+ readonly sessionWebhook?: string;
38
+ readonly chatbotCorpId?: string;
39
+ readonly chatbotUserId?: string;
40
+ readonly isAdmin?: boolean;
41
+ readonly senderStaffId?: string;
42
+ readonly atUsers?: ReadonlyArray<{
43
+ readonly dingtalkId?: string;
44
+ readonly staffId?: string;
45
+ }>;
46
+ readonly content?: Record<string, unknown>;
47
+ }
48
+ export interface DingTalkEvent extends DingTalkMessage {
49
+ readonly [key: string]: unknown;
50
+ }
51
+ /**
52
+ * 钉钉回调消息 @ 机器人判定:机器人被 @ 时回调带 `isInAtList: true`;
53
+ * 部分回调形态的 `atUserIds` / `atUsers[].dingtalkId` 会包含机器人 robotCode(来自配置)。
54
+ * 两者都不满足则不标注。
55
+ */
56
+ export declare function isDingtalkBotMentioned(event: DingTalkMessage, robotCode?: string): boolean;
57
+ export interface AccessToken {
58
+ token: string;
59
+ expires_in: number;
60
+ timestamp: number;
61
+ }
62
+ export interface DingTalkApiResponse {
63
+ readonly errcode: number;
64
+ readonly errmsg?: string;
65
+ readonly access_token?: string;
66
+ readonly expires_in?: number;
67
+ readonly msgId?: string;
68
+ readonly chatid?: string;
69
+ readonly result?: unknown;
70
+ readonly chat_info?: unknown;
71
+ readonly [key: string]: unknown;
72
+ }
73
+ export interface DingTalkWireSegment {
74
+ readonly type: string;
75
+ readonly data?: Record<string, unknown>;
76
+ }
77
+ export interface DingTalkSendBody {
78
+ readonly msgtype: string;
79
+ readonly text?: {
80
+ readonly content: string;
81
+ };
82
+ readonly picture?: {
83
+ readonly picURL: string;
84
+ };
85
+ readonly markdown?: {
86
+ readonly title: string;
87
+ readonly text: string;
88
+ };
89
+ readonly link?: {
90
+ readonly title: string;
91
+ readonly text: string;
92
+ readonly messageUrl?: string;
93
+ readonly picUrl?: string;
94
+ };
95
+ readonly at?: {
96
+ readonly atUserIds: string[];
97
+ readonly isAtAll: boolean;
98
+ };
99
+ readonly robotCode?: string;
100
+ }
101
+ export declare function resolveDingTalkConfig(config: DingTalkEndpointConfig): ResolvedDingTalkConfig;
102
+ export declare function normalizeWebhookPath(path: string): string;
103
+ export declare function resolveChatType(conversationType?: string): 'group' | 'private';
104
+ /**
105
+ * 入站归一化 → ConversationRef:`conversationType: '2'` 为群会话(kind 'group'),
106
+ * 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
107
+ * 钉钉无 guild/频道容器概念,不产生 parent。
108
+ */
109
+ export declare function dingtalkInboundConversation(endpointKey: string, msg: DingTalkMessage): ConversationRef;
110
+ export declare function resolveSender(msg: DingTalkMessage): string;
111
+ export declare function generateMessageId(msg: DingTalkMessage): string;
112
+ /** Build inbound text for OutboundMessageService.receive. */
113
+ export declare function formatInboundContent(msg: DingTalkMessage): string;
114
+ /** DingTalk timestamps are epoch milliseconds; reject replays outside ±1 hour. */
115
+ export declare const MAX_TIMESTAMP_DRIFT_MS: number;
116
+ export declare function verifySignature(appSecret: string, timestamp: string, sign: string): boolean;
117
+ /**
118
+ * Wire-encode an already-rendered outbound payload into DingTalk robot body.
119
+ * Segment canonicalization is intentionally not done here: media segments carry
120
+ * the canonical `data.media` MediaRef and nothing else is consulted.
121
+ *
122
+ * 钉钉机器人媒体消息仅支持远程 URL(picture.picURL):
123
+ * - image 段 kind=url → 直发 picture;
124
+ * - 其余 kind / audio / video / file 段无投递面,warn + 丢弃。
125
+ */
126
+ export declare function formatOutboundBody(payload: unknown): DingTalkSendBody;
127
+ export declare function headerValue(headers: IncomingMessage['headers'], name: string): string;
128
+ export declare function readTextBody(request: IncomingMessage, options?: {
129
+ readonly limit?: number;
130
+ }): Promise<string>;
@@ -0,0 +1,264 @@
1
+ /**
2
+ * DingTalk protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
3
+ * Canonicalization is owned by gateway/core before endpoint.send.
4
+ */
5
+ import { createHmac, timingSafeEqual } from 'node:crypto';
6
+ import { isMediaRef } from '@zhin.js/im-contract';
7
+ import { formatCompact, getLogger } from '@zhin.js/logger';
8
+ const logger = getLogger('dingtalk');
9
+ /**
10
+ * 钉钉回调消息 @ 机器人判定:机器人被 @ 时回调带 `isInAtList: true`;
11
+ * 部分回调形态的 `atUserIds` / `atUsers[].dingtalkId` 会包含机器人 robotCode(来自配置)。
12
+ * 两者都不满足则不标注。
13
+ */
14
+ export function isDingtalkBotMentioned(event, robotCode) {
15
+ const extra = event;
16
+ if (extra.isInAtList === true)
17
+ return true;
18
+ if (!robotCode)
19
+ return false;
20
+ if (Array.isArray(extra.atUserIds) && extra.atUserIds.some((id) => String(id) === robotCode)) {
21
+ return true;
22
+ }
23
+ return (event.atUsers ?? []).some((user) => user.dingtalkId === robotCode);
24
+ }
25
+ export function resolveDingTalkConfig(config) {
26
+ const id = requiredEndpointField(config.id, 'id');
27
+ const appKey = requiredEndpointField(config.appKey, 'appKey');
28
+ const appSecret = requiredEndpointField(config.appSecret, 'appSecret');
29
+ const webhookPath = normalizeWebhookPath(config.webhookPath ?? '/dingtalk/webhook');
30
+ const apiBaseUrl = (config.apiBaseUrl ?? 'https://oapi.dingtalk.com').replace(/\/$/, '');
31
+ const robotCode = config.robotCode;
32
+ return {
33
+ context: 'dingtalk',
34
+ id,
35
+ appKey,
36
+ appSecret,
37
+ webhookPath,
38
+ ...(robotCode ? { robotCode } : {}),
39
+ apiBaseUrl,
40
+ };
41
+ }
42
+ function requiredEndpointField(value, field) {
43
+ if (typeof value !== 'string' || !value.trim()) {
44
+ throw new TypeError(`DingTalk endpoint requires a non-empty ${field}`);
45
+ }
46
+ return value.trim();
47
+ }
48
+ export function normalizeWebhookPath(path) {
49
+ const trimmed = path.trim() || '/dingtalk/webhook';
50
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
51
+ }
52
+ export function resolveChatType(conversationType) {
53
+ return conversationType === '2' ? 'group' : 'private';
54
+ }
55
+ /**
56
+ * 入站归一化 → ConversationRef:`conversationType: '2'` 为群会话(kind 'group'),
57
+ * 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
58
+ * 钉钉无 guild/频道容器概念,不产生 parent。
59
+ */
60
+ export function dingtalkInboundConversation(endpointKey, msg) {
61
+ return {
62
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
63
+ kind: resolveChatType(msg.conversationType),
64
+ id: msg.conversationId || msg.senderId || 'unknown',
65
+ };
66
+ }
67
+ export function resolveSender(msg) {
68
+ return msg.senderId || msg.senderStaffId || 'unknown';
69
+ }
70
+ export function generateMessageId(msg) {
71
+ return msg.msgId || `${msg.createAt ?? Date.now()}`;
72
+ }
73
+ /** Build inbound text for OutboundMessageService.receive. */
74
+ export function formatInboundContent(msg) {
75
+ if (!msg.msgtype)
76
+ return '';
77
+ switch (msg.msgtype) {
78
+ case 'text':
79
+ return msg.text?.content || '';
80
+ case 'picture':
81
+ return '[image]';
82
+ case 'file': {
83
+ const name = typeof msg.content?.fileName === 'string' ? msg.content.fileName : '';
84
+ return name ? `[file: ${name}]` : '[file]';
85
+ }
86
+ case 'audio':
87
+ return '[audio]';
88
+ case 'video':
89
+ return '[video]';
90
+ case 'richText': {
91
+ const rich = msg.content?.richText;
92
+ if (Array.isArray(rich)) {
93
+ return rich
94
+ .map((item) => (item && typeof item === 'object' && 'text' in item
95
+ ? String(item.text || '')
96
+ : ''))
97
+ .join('');
98
+ }
99
+ return '[richText]';
100
+ }
101
+ case 'markdown':
102
+ return typeof msg.content?.text === 'string' ? msg.content.text : '[markdown]';
103
+ default:
104
+ return `[${msg.msgtype}]`;
105
+ }
106
+ }
107
+ /** DingTalk timestamps are epoch milliseconds; reject replays outside ±1 hour. */
108
+ export const MAX_TIMESTAMP_DRIFT_MS = 60 * 60 * 1000;
109
+ export function verifySignature(appSecret, timestamp, sign) {
110
+ try {
111
+ const ts = Number(timestamp);
112
+ if (!Number.isFinite(ts) || Math.abs(Date.now() - ts) > MAX_TIMESTAMP_DRIFT_MS) {
113
+ return false;
114
+ }
115
+ const stringToSign = `${timestamp}\n${appSecret}`;
116
+ const hmac = createHmac('sha256', appSecret);
117
+ hmac.update(stringToSign);
118
+ const calculated = hmac.digest('base64');
119
+ const a = Buffer.from(calculated);
120
+ const b = Buffer.from(sign);
121
+ if (a.length !== b.length)
122
+ return false;
123
+ return timingSafeEqual(a, b);
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ }
129
+ /**
130
+ * Wire-encode an already-rendered outbound payload into DingTalk robot body.
131
+ * Segment canonicalization is intentionally not done here: media segments carry
132
+ * the canonical `data.media` MediaRef and nothing else is consulted.
133
+ *
134
+ * 钉钉机器人媒体消息仅支持远程 URL(picture.picURL):
135
+ * - image 段 kind=url → 直发 picture;
136
+ * - 其余 kind / audio / video / file 段无投递面,warn + 丢弃。
137
+ */
138
+ export function formatOutboundBody(payload) {
139
+ if (typeof payload === 'string') {
140
+ return { msgtype: 'text', text: { content: payload } };
141
+ }
142
+ const items = Array.isArray(payload)
143
+ ? payload
144
+ : payload && typeof payload === 'object' && 'type' in payload
145
+ ? [payload]
146
+ : [];
147
+ if (items.length === 0) {
148
+ const text = payload == null
149
+ ? ''
150
+ : typeof payload === 'object'
151
+ ? JSON.stringify(payload)
152
+ : String(payload);
153
+ return { msgtype: 'text', text: { content: text } };
154
+ }
155
+ const textParts = [];
156
+ const atUserIds = [];
157
+ let media = null;
158
+ let markdownTitle;
159
+ for (const item of items) {
160
+ if (typeof item === 'string') {
161
+ textParts.push(item);
162
+ continue;
163
+ }
164
+ const data = item.data ?? {};
165
+ switch (item.type) {
166
+ case 'text':
167
+ textParts.push(String(data.content ?? data.text ?? ''));
168
+ break;
169
+ case 'at': {
170
+ const userId = data.id ?? data.userId;
171
+ if (userId) {
172
+ atUserIds.push(String(userId));
173
+ textParts.push(`@${String(data.name || userId)} `);
174
+ }
175
+ break;
176
+ }
177
+ case 'image': {
178
+ if (media)
179
+ break;
180
+ const ref = data.media;
181
+ if (isMediaRef(ref) && ref.kind === 'url') {
182
+ media = {
183
+ msgtype: 'picture',
184
+ picture: { picURL: ref.value },
185
+ };
186
+ break;
187
+ }
188
+ logger.warn(formatCompact({
189
+ op: 'dingtalk_outbound_media_dropped',
190
+ type: item.type,
191
+ reason: isMediaRef(ref) ? `unsupported_kind:${ref.kind}` : 'missing_media_ref',
192
+ }));
193
+ break;
194
+ }
195
+ case 'audio':
196
+ case 'video':
197
+ case 'file':
198
+ // 钉钉机器人无音频/视频/文件投递面,warn + 丢弃
199
+ logger.warn(formatCompact({
200
+ op: 'dingtalk_outbound_media_dropped',
201
+ type: item.type,
202
+ reason: 'undeliverable_msgtype',
203
+ }));
204
+ break;
205
+ case 'markdown':
206
+ markdownTitle ??= String(data.title || '消息');
207
+ textParts.push(String(data.content ?? data.text ?? ''));
208
+ break;
209
+ case 'link':
210
+ if (!media) {
211
+ media = {
212
+ msgtype: 'link',
213
+ link: {
214
+ title: String(data.title || '链接'),
215
+ text: String(data.text ?? data.content ?? ''),
216
+ messageUrl: typeof data.url === 'string' ? data.url : undefined,
217
+ picUrl: typeof data.picUrl === 'string' ? data.picUrl : undefined,
218
+ },
219
+ };
220
+ }
221
+ break;
222
+ default:
223
+ textParts.push(`[${item.type}]`);
224
+ }
225
+ }
226
+ if (media)
227
+ return media;
228
+ if (markdownTitle !== undefined) {
229
+ return {
230
+ msgtype: 'markdown',
231
+ markdown: { title: markdownTitle, text: textParts.join('') },
232
+ ...(atUserIds.length > 0 ? { at: { atUserIds, isAtAll: false } } : {}),
233
+ };
234
+ }
235
+ const result = {
236
+ msgtype: 'text',
237
+ text: { content: textParts.join('') },
238
+ };
239
+ if (atUserIds.length > 0) {
240
+ return { ...result, at: { atUserIds, isAtAll: false } };
241
+ }
242
+ return result;
243
+ }
244
+ export function headerValue(headers, name) {
245
+ const value = headers[name] ?? headers[name.toLowerCase()];
246
+ if (Array.isArray(value))
247
+ return value[0] ?? '';
248
+ return value ?? '';
249
+ }
250
+ export async function readTextBody(request, options = {}) {
251
+ const limit = options.limit ?? 1_048_576;
252
+ const chunks = [];
253
+ let size = 0;
254
+ for await (const chunk of request) {
255
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
256
+ size += buffer.length;
257
+ if (size > limit) {
258
+ request.destroy();
259
+ throw new Error(`Request body exceeds ${limit} bytes`);
260
+ }
261
+ chunks.push(buffer);
262
+ }
263
+ return Buffer.concat(chunks).toString('utf8');
264
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * DingTalk webhook HTTP: signature → parse → admit.
3
+ */
4
+ import type { IncomingMessage, ServerResponse } from 'node:http';
5
+ import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
6
+ import { type DingTalkEvent, type ResolvedDingTalkConfig } from './protocol.js';
7
+ export interface DingTalkWebhookHandler {
8
+ readonly config: ResolvedDingTalkConfig;
9
+ readonly isOpen: boolean;
10
+ admit(event: DingTalkEvent): void;
11
+ }
12
+ export declare function registerDingTalkWebhookRoutes(http: HttpHost, handler: DingTalkWebhookHandler): HttpRouteRegistration[];
13
+ export declare function handleDingTalkWebhookRequest(request: IncomingMessage, response: ServerResponse, handler: DingTalkWebhookHandler): Promise<void>;
package/lib/webhook.js ADDED
@@ -0,0 +1,47 @@
1
+ import { formatCompact, getLogger } from '@zhin.js/logger';
2
+ import { headerValue, readTextBody, verifySignature, } from './protocol.js';
3
+ const logger = getLogger('dingtalk');
4
+ export function registerDingTalkWebhookRoutes(http, handler) {
5
+ const path = handler.config.webhookPath;
6
+ return [
7
+ http.route('POST', path, async (request, response) => {
8
+ await handleDingTalkWebhookRequest(request, response, handler);
9
+ }, { summary: 'DingTalk robot webhook', tags: ['dingtalk'] }),
10
+ ];
11
+ }
12
+ export async function handleDingTalkWebhookRequest(request, response, handler) {
13
+ try {
14
+ // DingTalk outgoing callbacks put timestamp/sign on the URL query;
15
+ // headers are accepted as a fallback for legacy senders.
16
+ const query = new URL(request.url ?? '/', 'http://localhost').searchParams;
17
+ const timestamp = query.get('timestamp') || headerValue(request.headers, 'timestamp');
18
+ const sign = query.get('sign') || headerValue(request.headers, 'sign');
19
+ // Missing credentials must never be admitted: verify unconditionally.
20
+ if (!timestamp || !sign || !verifySignature(handler.config.appSecret, timestamp, sign)) {
21
+ logger.warn(formatCompact({ op: 'webhook', ok: false, error: 'invalid signature' }));
22
+ response.writeHead(403, { 'Content-Type': 'application/json' });
23
+ response.end(JSON.stringify({ code: -1, msg: 'Forbidden' }));
24
+ return;
25
+ }
26
+ const rawBody = await readTextBody(request);
27
+ let event;
28
+ try {
29
+ event = JSON.parse(rawBody);
30
+ }
31
+ catch {
32
+ response.writeHead(200, { 'Content-Type': 'application/json' });
33
+ response.end(JSON.stringify({ code: 0, msg: 'success' }));
34
+ return;
35
+ }
36
+ if (event.msgtype && handler.isOpen) {
37
+ handler.admit(event);
38
+ }
39
+ response.writeHead(200, { 'Content-Type': 'application/json' });
40
+ response.end(JSON.stringify({ code: 0, msg: 'success' }));
41
+ }
42
+ catch (error) {
43
+ logger.error('Webhook error:', error);
44
+ response.writeHead(500, { 'Content-Type': 'application/json' });
45
+ response.end(JSON.stringify({ code: -1, msg: 'Internal Server Error' }));
46
+ }
47
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-dingtalk",
3
- "version": "1.0.81",
4
- "description": "Zhin.js adapter for DingTalk (钉钉)",
3
+ "version": "1.1.2",
4
+ "description": "Zhin.js DingTalk (钉钉) adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -32,39 +32,96 @@
32
32
  "type": "git",
33
33
  "directory": "plugins/adapters/dingtalk"
34
34
  },
35
- "devDependencies": {
36
- "@types/koa": "^3.0.3",
37
- "@types/node": "^25.9.1",
38
- "typescript": "^6.0.3",
39
- "zhin.js": "1.0.93"
35
+ "dependencies": {
36
+ "@zhin.js/adapter": "1.1.14",
37
+ "@zhin.js/agent-feature": "1.1.1",
38
+ "@zhin.js/core": "1.1.37",
39
+ "@zhin.js/feature-kit": "1.1.1",
40
+ "@zhin.js/host-http": "1.1.1",
41
+ "@zhin.js/im-contract": "1.1.1",
42
+ "@zhin.js/logger": "1.1.1",
43
+ "@zhin.js/skill": "1.1.1",
44
+ "@zhin.js/tool": "1.1.1"
40
45
  },
41
46
  "peerDependencies": {
42
- "@zhin.js/host-router": "0.0.3",
43
- "zhin.js": "1.0.93"
47
+ "zod": "^4.0.0",
48
+ "@zhin.js/adapter": "^1.1.14",
49
+ "@zhin.js/command": "^1.1.1",
50
+ "@zhin.js/core": "^1.1.37",
51
+ "@zhin.js/host-http": "^1.1.1",
52
+ "@zhin.js/permission": "^1.1.1",
53
+ "zhin.js": "^1.1.2"
44
54
  },
45
55
  "peerDependenciesMeta": {
46
- "@zhin.js/host-router": {
47
- "optional": false
56
+ "@zhin.js/command": {
57
+ "optional": true
58
+ },
59
+ "zhin.js": {
60
+ "optional": true
61
+ },
62
+ "zod": {
63
+ "optional": true
48
64
  }
49
65
  },
66
+ "devDependencies": {
67
+ "@types/node": "^26.1.2",
68
+ "typescript": "^6.0.3",
69
+ "vitest": "^4.1.10",
70
+ "zod": "^4.4.3",
71
+ "zhin.js": "1.1.2"
72
+ },
50
73
  "files": [
74
+ "adapters",
75
+ "commands",
76
+ "plugin.js",
77
+ "schema.json",
51
78
  "src",
52
79
  "lib",
53
- "client",
54
- "dist",
55
- "skills",
56
- "plugin.yml",
80
+ "agents",
57
81
  "README.md",
58
- "node",
59
- "CHANGELOG.md"
82
+ "CHANGELOG.md",
83
+ "PERMITS.md"
60
84
  ],
61
85
  "publishConfig": {
62
86
  "access": "public",
63
87
  "registry": "https://registry.npmjs.org"
64
88
  },
89
+ "engines": {
90
+ "node": "^20.19.0 || >=22.12.0"
91
+ },
92
+ "zhin": {
93
+ "protocol": 1,
94
+ "type": "plugin",
95
+ "entry": "./plugin.js",
96
+ "engine": "^1.0.0",
97
+ "runtime": "trusted",
98
+ "features": [
99
+ {
100
+ "package": "@zhin.js/adapter",
101
+ "api": "^1.0.0"
102
+ },
103
+ {
104
+ "package": "@zhin.js/command",
105
+ "api": "^1.0.0"
106
+ },
107
+ {
108
+ "package": "@zhin.js/tool",
109
+ "api": "^1.0.0"
110
+ },
111
+ {
112
+ "package": "@zhin.js/skill",
113
+ "api": "^1.0.0"
114
+ },
115
+ {
116
+ "package": "@zhin.js/agent-feature",
117
+ "api": "^1.0.0"
118
+ }
119
+ ],
120
+ "plugins": []
121
+ },
65
122
  "scripts": {
66
- "build": "pnpm build:node",
123
+ "build": "tsc",
67
124
  "clean": "rimraf lib",
68
- "build:node": "tsc"
125
+ "test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/adapters/dingtalk/tests"
69
126
  }
70
127
  }
package/plugin.js ADDED
@@ -0,0 +1,19 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { createEndpointRuntimeState } from 'zhin.js/adapter';
3
+ import { definePlugin } from 'zhin.js';
4
+ import { permissionHostToken } from '@zhin.js/permission';
5
+ import { checkDingtalkPlatformPermit } from "./lib/platform-permit.js";
6
+ import { dingtalkRuntimeStateToken } from "./lib/dingtalk-runtime-state.js";
7
+ export default definePlugin({
8
+ name: 'dingtalk',
9
+ metadata: {
10
+ displayName: 'DingTalk (钉钉) Adapter',
11
+ },
12
+ setup(context) {
13
+ context.resources.provide(dingtalkRuntimeStateToken, createEndpointRuntimeState());
14
+ if (context.resources.has(permissionHostToken)) {
15
+ const host = context.resources.use(permissionHostToken);
16
+ return host.registerPlatform('dingtalk', checkDingtalkPlatformPermit);
17
+ }
18
+ },
19
+ });