@zhin.js/adapter-dingtalk 6.0.3 → 6.0.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,84 @@
1
1
  # @zhin.js/adapter-dingtalk
2
2
 
3
+ ## 6.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
8
+
9
+ All adapter platform-permit checkers now accept PermissionSubject
10
+ (duck-typed from Message/CommandSession) instead of Message directly.
11
+ registerPlatformPermitChecker is replaced by host.registerPlatform in
12
+ the adapter plugin setup. activity-feedback service updated for new
13
+ PermissionHost integration.
14
+
15
+ - Updated dependencies [6f9c366]
16
+ - Updated dependencies [373a56b]
17
+ - Updated dependencies [0de46a8]
18
+ - Updated dependencies [c106ecc]
19
+ - Updated dependencies [ca92e03]
20
+ - Updated dependencies [b0f37ae]
21
+ - Updated dependencies [ba08a2f]
22
+ - Updated dependencies [b08f7fe]
23
+ - Updated dependencies [daffd4c]
24
+ - Updated dependencies [36c7400]
25
+ - Updated dependencies [a9fa72e]
26
+ - Updated dependencies [c8de3ef]
27
+ - Updated dependencies [60f0fc8]
28
+ - Updated dependencies [574c990]
29
+ - Updated dependencies [d096f16]
30
+ - Updated dependencies [3f29623]
31
+ - Updated dependencies [2916852]
32
+ - Updated dependencies [d047869]
33
+ - Updated dependencies [162fa34]
34
+ - Updated dependencies [e62561e]
35
+ - Updated dependencies [3eeeb46]
36
+ - Updated dependencies [85d0f82]
37
+ - Updated dependencies [61bfc1c]
38
+ - Updated dependencies [04bad47]
39
+ - Updated dependencies [e40b048]
40
+ - Updated dependencies [5a5b1bb]
41
+ - Updated dependencies [f1708c3]
42
+ - Updated dependencies [d254a81]
43
+ - Updated dependencies [7123c47]
44
+ - Updated dependencies [05befc1]
45
+ - Updated dependencies [0663b6a]
46
+ - Updated dependencies [f28f9b3]
47
+ - Updated dependencies [9f340f7]
48
+ - Updated dependencies [e53444f]
49
+ - Updated dependencies [5eedd26]
50
+ - Updated dependencies [92b0dd7]
51
+ - Updated dependencies [f919b6f]
52
+ - Updated dependencies [098e411]
53
+ - Updated dependencies [e1b7c01]
54
+ - Updated dependencies [9b94f87]
55
+ - Updated dependencies [a7df753]
56
+ - @zhin.js/agent@1.1.5
57
+ - @zhin.js/host-http@1.0.8
58
+ - @zhin.js/permission@1.0.1
59
+ - @zhin.js/im-contract@1.0.3
60
+ - @zhin.js/adapter@1.1.7
61
+ - @zhin.js/plugin-runtime@1.1.5
62
+ - @zhin.js/command@1.0.9
63
+ - @zhin.js/core@1.5.4
64
+ - zhin.js@6.0.4
65
+
66
+ ## 6.0.4
67
+
68
+ ### Patch Changes
69
+
70
+ - f8c7a54: fix: im
71
+ - Updated dependencies [f8c7a54]
72
+ - @zhin.js/logger@1.0.76
73
+ - @zhin.js/host-http@1.0.7
74
+ - @zhin.js/adapter@1.1.6
75
+ - @zhin.js/agent@1.1.4
76
+ - @zhin.js/command@1.0.8
77
+ - @zhin.js/core@1.5.3
78
+ - @zhin.js/im-contract@1.0.2
79
+ - @zhin.js/plugin-runtime@1.1.4
80
+ - zhin.js@6.0.3
81
+
3
82
  ## 6.0.3
4
83
 
5
84
  ### Patch Changes
@@ -19,8 +19,8 @@ export default defineAdapter({
19
19
  create(context) {
20
20
  const config = resolveDingTalkConfig(context.config);
21
21
  // 注册到插件运行时状态(dingtalk.endpoint list 的"运行中"数据源)
22
- context.use(dingtalkRuntimeStateToken).endpoints.set(config.name, {
23
- name: config.name,
22
+ context.use(dingtalkRuntimeStateToken).endpoints.set(config.id, {
23
+ id: config.id,
24
24
  mode: 'webhook',
25
25
  });
26
26
  return new DingTalkEndpoint({
@@ -24,8 +24,8 @@ export default defineAdapter<DingTalkAdapterConfig>({
24
24
  create(context) {
25
25
  const config = resolveDingTalkConfig(context.config);
26
26
  // 注册到插件运行时状态(dingtalk.endpoint list 的"运行中"数据源)
27
- context.use(dingtalkRuntimeStateToken).endpoints.set(config.name, {
28
- name: config.name,
27
+ context.use(dingtalkRuntimeStateToken).endpoints.set(config.id, {
28
+ id: config.id,
29
29
  mode: 'webhook',
30
30
  });
31
31
  return new DingTalkEndpoint({
@@ -18,9 +18,9 @@ export interface DingtalkAgentEndpoint {
18
18
  }): Promise<boolean>;
19
19
  }
20
20
  export interface DingtalkAgentDeps {
21
- getEndpoint: (endpointId: string) => DingtalkAgentEndpoint;
21
+ getEndpoint: (endpointKey: string) => DingtalkAgentEndpoint;
22
22
  }
23
- export declare function registerDingtalkAgentEndpoint(endpointId: string, endpoint: DingtalkAgentEndpoint): () => void;
23
+ export declare function registerDingtalkAgentEndpoint(endpointKey: string, endpoint: DingtalkAgentEndpoint): () => void;
24
24
  /** Optional override used by tests / transitional callers. Pass `null` to clear. */
25
25
  export declare function setDingtalkAgentDeps(deps: DingtalkAgentDeps | null): void;
26
26
  export declare function getDingtalkAgentDeps(): DingtalkAgentDeps;
@@ -4,11 +4,11 @@
4
4
  */
5
5
  const endpoints = new Map();
6
6
  let override = null;
7
- export function registerDingtalkAgentEndpoint(endpointId, endpoint) {
8
- endpoints.set(endpointId, endpoint);
7
+ export function registerDingtalkAgentEndpoint(endpointKey, endpoint) {
8
+ endpoints.set(endpointKey, endpoint);
9
9
  return () => {
10
- if (endpoints.get(endpointId) === endpoint) {
11
- endpoints.delete(endpointId);
10
+ if (endpoints.get(endpointKey) === endpoint) {
11
+ endpoints.delete(endpointKey);
12
12
  }
13
13
  };
14
14
  }
@@ -20,10 +20,10 @@ export function getDingtalkAgentDeps() {
20
20
  if (override)
21
21
  return override;
22
22
  return {
23
- getEndpoint(endpointId) {
24
- const endpoint = endpoints.get(endpointId);
23
+ getEndpoint(endpointKey) {
24
+ const endpoint = endpoints.get(endpointKey);
25
25
  if (!endpoint)
26
- throw new Error(`Endpoint ${endpointId} 不存在`);
26
+ throw new Error(`Endpoint ${endpointKey} 不存在`);
27
27
  return endpoint;
28
28
  },
29
29
  };
package/lib/endpoint.js CHANGED
@@ -1,15 +1,15 @@
1
- import { formatCompact, getLogger } from '@zhin.js/logger';
1
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
2
2
  import { registerDingtalkAgentEndpoint } from './dingtalk-agent-deps.js';
3
3
  import { normalizeDingtalkSenderForPermit } from './platform-permit.js';
4
4
  import { dingtalkInboundConversation, formatInboundContent, formatOutboundBody, generateMessageId, isDingtalkBotMentioned, resolveChatType, resolveSender, } from './protocol.js';
5
5
  import { registerDingTalkWebhookRoutes } from './webhook.js';
6
- const logger = getLogger('dingtalk');
7
6
  /**
8
7
  * 钉钉机器人(webhook/stream 模式)无常规群列表 API——机器人不持有
9
8
  * 「我所在的群」枚举面,仅能收发消息;
10
9
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
11
10
  */
12
11
  export class DingTalkEndpoint {
12
+ #logger;
13
13
  #options;
14
14
  #fetch;
15
15
  #routeReleases = [];
@@ -20,6 +20,7 @@ export class DingTalkEndpoint {
20
20
  #started = false;
21
21
  #unregisterAgent;
22
22
  constructor(options) {
23
+ this.#logger = getAdapterLogger('dingtalk', options.config.id);
23
24
  this.#options = options;
24
25
  this.#fetch = options.fetch ?? globalThis.fetch;
25
26
  }
@@ -36,17 +37,17 @@ export class DingTalkEndpoint {
36
37
  this.#started = true;
37
38
  try {
38
39
  await this.#refreshAccessToken();
39
- this.#unregisterAgent = registerDingtalkAgentEndpoint(this.#options.config.name, this);
40
+ this.#unregisterAgent = registerDingtalkAgentEndpoint(this.#options.config.id, this);
40
41
  this.#routeReleases.push(...registerDingTalkWebhookRoutes(this.#options.http, this));
41
- logger.debug(formatCompact({
42
- endpoint: this.#options.config.name,
42
+ this.#logger.debug(formatCompact({
43
+ endpoint: this.#options.config.id,
43
44
  op: 'webhook',
44
45
  path: this.#options.config.webhookPath,
45
46
  }));
46
47
  }
47
48
  catch (error) {
48
49
  await this.stop();
49
- logger.error('Failed to connect DingTalk endpoint:', error);
50
+ this.#logger.error('Failed to connect DingTalk endpoint:', error);
50
51
  throw error;
51
52
  }
52
53
  }
@@ -64,7 +65,7 @@ export class DingTalkEndpoint {
64
65
  this.#unregisterAgent?.();
65
66
  this.#unregisterAgent = undefined;
66
67
  this.#started = false;
67
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
68
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
68
69
  }
69
70
  async send({ conversation, payload }) {
70
71
  const content = formatOutboundBody(payload);
@@ -79,9 +80,9 @@ export class DingTalkEndpoint {
79
80
  if (data.errcode !== 0) {
80
81
  throw new Error(`Failed to send message via session webhook: ${data.errmsg}`);
81
82
  }
82
- logger.debug(formatCompact({
83
+ this.#logger.debug(formatCompact({
83
84
  op: 'send',
84
- endpoint: this.#options.config.name,
85
+ endpoint: this.#options.config.id,
85
86
  via: 'sessionWebhook',
86
87
  to: conversation.id,
87
88
  }));
@@ -100,7 +101,7 @@ export class DingTalkEndpoint {
100
101
  if (data.errcode !== 0) {
101
102
  throw new Error(`Failed to send message: ${data.errmsg}`);
102
103
  }
103
- logger.debug(formatCompact({ op: 'send', endpoint: this.#options.config.name, to: conversation.id }));
104
+ this.#logger.debug(formatCompact({ op: 'send', to: conversation.id }));
104
105
  return data.msgId || `${Date.now()}`;
105
106
  }
106
107
  /** Test / internal: admit a parsed event when open (non-webhook path). */
@@ -117,19 +118,23 @@ export class DingTalkEndpoint {
117
118
  conversation,
118
119
  message: { conversation, id: generateMessageId(event) },
119
120
  content: formatInboundContent(event),
120
- sender: resolveSender(event),
121
+ sender: {
122
+ id: resolveSender(event),
123
+ name: event.senderNick || undefined,
124
+ ...(permit.role ? { roles: [permit.role] } : {}),
125
+ },
126
+ endpointId: this.#options.config.id,
127
+ ...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
121
128
  metadata: Object.freeze({
122
129
  msgtype: event.msgtype,
123
130
  chatType,
124
- endpoint: this.#options.config.name,
125
131
  senderNick: event.senderNick,
126
132
  role: permit.role,
127
133
  permissions: permit.permissions,
128
134
  conversationType: event.conversationType,
129
- ...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
130
135
  }),
131
136
  }).catch((err) => {
132
- logger.warn(formatCompact({
137
+ this.#logger.warn(formatCompact({
133
138
  op: 'dingtalk_gateway_receive_failed',
134
139
  conversationId: conversation.id,
135
140
  error: err instanceof Error ? err.message : String(err),
@@ -147,7 +152,7 @@ export class DingTalkEndpoint {
147
152
  throw new Error(`Failed to get user info: ${data.errmsg}`);
148
153
  }
149
154
  catch (error) {
150
- logger.error('Failed to get user info:', error);
155
+ this.#logger.error('Failed to get user info:', error);
151
156
  return null;
152
157
  }
153
158
  }
@@ -164,7 +169,7 @@ export class DingTalkEndpoint {
164
169
  throw new Error(`Failed to get department users: ${data.errmsg}`);
165
170
  }
166
171
  catch (error) {
167
- logger.error('Failed to get department users:', error);
172
+ this.#logger.error('Failed to get department users:', error);
168
173
  return [];
169
174
  }
170
175
  }
@@ -183,7 +188,7 @@ export class DingTalkEndpoint {
183
188
  throw new Error(`Failed to send work notice: ${data.errmsg}`);
184
189
  }
185
190
  catch (error) {
186
- logger.error('Failed to send work notice:', error);
191
+ this.#logger.error('Failed to send work notice:', error);
187
192
  return false;
188
193
  }
189
194
  }
@@ -198,7 +203,7 @@ export class DingTalkEndpoint {
198
203
  throw new Error(`Failed to get department list: ${data.errmsg}`);
199
204
  }
200
205
  catch (error) {
201
- logger.error('Failed to get department list:', error);
206
+ this.#logger.error('Failed to get department list:', error);
202
207
  return [];
203
208
  }
204
209
  }
@@ -213,7 +218,7 @@ export class DingTalkEndpoint {
213
218
  throw new Error(`Failed to get department info: ${data.errmsg}`);
214
219
  }
215
220
  catch (error) {
216
- logger.error('Failed to get department info:', error);
221
+ this.#logger.error('Failed to get department info:', error);
217
222
  return null;
218
223
  }
219
224
  }
@@ -228,7 +233,7 @@ export class DingTalkEndpoint {
228
233
  throw new Error(`Failed to create chat: ${data.errmsg}`);
229
234
  }
230
235
  catch (error) {
231
- logger.error('Failed to create chat:', error);
236
+ this.#logger.error('Failed to create chat:', error);
232
237
  return null;
233
238
  }
234
239
  }
@@ -243,7 +248,7 @@ export class DingTalkEndpoint {
243
248
  throw new Error(`Failed to get chat info: ${data.errmsg}`);
244
249
  }
245
250
  catch (error) {
246
- logger.error('Failed to get chat info:', error);
251
+ this.#logger.error('Failed to get chat info:', error);
247
252
  return null;
248
253
  }
249
254
  }
@@ -258,7 +263,7 @@ export class DingTalkEndpoint {
258
263
  throw new Error(`Failed to update chat: ${data.errmsg}`);
259
264
  }
260
265
  catch (error) {
261
- logger.error('Failed to update chat:', error);
266
+ this.#logger.error('Failed to update chat:', error);
262
267
  return false;
263
268
  }
264
269
  }
@@ -308,7 +313,7 @@ export class DingTalkEndpoint {
308
313
  expires_in: data.expires_in ?? 7200,
309
314
  timestamp: Date.now(),
310
315
  };
311
- logger.debug('Access token refreshed successfully');
316
+ this.#logger.debug('Access token refreshed successfully');
312
317
  return;
313
318
  }
314
319
  throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
package/lib/index.d.ts CHANGED
@@ -2,4 +2,4 @@ export { dingtalkInboundConversation, formatInboundContent, formatOutboundBody,
2
2
  export { DingTalkEndpoint, type DingTalkEndpointOptions, type DingTalkFetch, } from './endpoint.js';
3
3
  export { registerDingTalkWebhookRoutes, handleDingTalkWebhookRequest, type DingTalkWebhookHandler, } from './webhook.js';
4
4
  export { getDingtalkAgentDeps, registerDingtalkAgentEndpoint, setDingtalkAgentDeps, type DingtalkAgentDeps, type DingtalkAgentEndpoint, } from './dingtalk-agent-deps.js';
5
- export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit, registerDingtalkPlatformPermitChecker, } from './platform-permit.js';
5
+ export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit, } from './platform-permit.js';
package/lib/index.js CHANGED
@@ -2,4 +2,4 @@ export { dingtalkInboundConversation, formatInboundContent, formatOutboundBody,
2
2
  export { DingTalkEndpoint, } from './endpoint.js';
3
3
  export { registerDingTalkWebhookRoutes, handleDingTalkWebhookRequest, } from './webhook.js';
4
4
  export { getDingtalkAgentDeps, registerDingtalkAgentEndpoint, setDingtalkAgentDeps, } from './dingtalk-agent-deps.js';
5
- export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit, registerDingtalkPlatformPermitChecker, } from './platform-permit.js';
5
+ export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit, } from './platform-permit.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 钉钉 DingTalk platform permit
3
3
  */
4
- import { type Message } from '@zhin.js/core';
4
+ import type { PermissionSubject } from '@zhin.js/permission';
5
5
  export declare function platformPermit(perm: string): string;
6
6
  export declare function dingtalkGroupPermitResolver(logicalPerm: string): string;
7
7
  export declare function normalizeDingtalkSenderForPermit(input: {
@@ -11,5 +11,4 @@ export declare function normalizeDingtalkSenderForPermit(input: {
11
11
  role?: string;
12
12
  permissions?: string[];
13
13
  };
14
- export declare function checkDingtalkPlatformPermit(perm: string, message: Message<any>): boolean;
15
- export declare function registerDingtalkPlatformPermitChecker(): () => void;
14
+ export declare function checkDingtalkPlatformPermit(perm: string, subject: PermissionSubject): boolean;
@@ -1,7 +1,3 @@
1
- /**
2
- * 钉钉 DingTalk platform permit
3
- */
4
- import { registerPlatformPermitChecker } from '@zhin.js/core';
5
1
  const ADAPTER = 'dingtalk';
6
2
  export function platformPermit(perm) {
7
3
  return `platform(${ADAPTER},${perm})`;
@@ -22,10 +18,9 @@ export function normalizeDingtalkSenderForPermit(input) {
22
18
  }
23
19
  return { role: 'member', permissions: [] };
24
20
  }
25
- export function checkDingtalkPlatformPermit(perm, message) {
26
- const sender = message.$sender;
27
- const permissions = sender.permissions ?? [];
28
- const role = sender.role;
21
+ export function checkDingtalkPlatformPermit(perm, subject) {
22
+ const role = subject.sender?.role?.[0];
23
+ const permissions = subject.sender?.permissions ?? [];
29
24
  const has = (t) => permissions.includes(t);
30
25
  switch (perm) {
31
26
  case 'chat_owner':
@@ -36,6 +31,3 @@ export function checkDingtalkPlatformPermit(perm, message) {
36
31
  return false;
37
32
  }
38
33
  }
39
- export function registerDingtalkPlatformPermitChecker() {
40
- return registerPlatformPermitChecker(ADAPTER, checkDingtalkPlatformPermit);
41
- }
package/lib/protocol.d.ts CHANGED
@@ -6,7 +6,7 @@ import type { IncomingMessage } from 'node:http';
6
6
  import type { ConversationRef } from '@zhin.js/im-contract';
7
7
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
8
8
  export interface DingTalkAdapterConfig {
9
- readonly name?: string;
9
+ readonly id?: string;
10
10
  readonly appKey?: string;
11
11
  readonly appSecret?: string;
12
12
  readonly webhookPath?: string;
@@ -19,7 +19,7 @@ export interface DingTalkAdapterConfig {
19
19
  }
20
20
  export interface ResolvedDingTalkConfig {
21
21
  readonly context: 'dingtalk';
22
- readonly name: string;
22
+ readonly id: string;
23
23
  readonly appKey: string;
24
24
  readonly appSecret: string;
25
25
  readonly webhookPath: string;
@@ -110,7 +110,7 @@ export declare function resolveChatType(conversationType?: string): 'group' | 'p
110
110
  * 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
111
111
  * 钉钉无 guild/频道容器概念,不产生 parent。
112
112
  */
113
- export declare function dingtalkInboundConversation(endpointId: string, msg: DingTalkMessage): ConversationRef;
113
+ export declare function dingtalkInboundConversation(endpointKey: string, msg: DingTalkMessage): ConversationRef;
114
114
  export declare function resolveSender(msg: DingTalkMessage): string;
115
115
  export declare function generateMessageId(msg: DingTalkMessage): string;
116
116
  /** Build inbound text for MessageGateway.receive. */
package/lib/protocol.js CHANGED
@@ -29,8 +29,8 @@ export function resolveDingTalkConfig(config = {}) {
29
29
  if (!appKey || !appSecret) {
30
30
  throw new TypeError('DingTalk adapter requires appKey + appSecret (plugins.<key> or endpoints with context: dingtalk)');
31
31
  }
32
- const name = (typeof config.name === 'string' && config.name)
33
- || (typeof entry?.name === 'string' && entry.name)
32
+ const id = (typeof config.id === 'string' && config.id)
33
+ || (typeof entry?.id === 'string' && entry.id)
34
34
  || process.env.DINGTALK_BOT_NAME
35
35
  || 'dingtalk-bot';
36
36
  const webhookPath = normalizeWebhookPath(config.webhookPath ?? entry?.webhookPath ?? '/dingtalk/webhook');
@@ -38,7 +38,7 @@ export function resolveDingTalkConfig(config = {}) {
38
38
  const robotCode = config.robotCode ?? entry?.robotCode;
39
39
  return {
40
40
  context: 'dingtalk',
41
- name,
41
+ id,
42
42
  appKey,
43
43
  appSecret,
44
44
  webhookPath,
@@ -58,9 +58,9 @@ export function resolveChatType(conversationType) {
58
58
  * 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
59
59
  * 钉钉无 guild/频道容器概念,不产生 parent。
60
60
  */
61
- export function dingtalkInboundConversation(endpointId, msg) {
61
+ export function dingtalkInboundConversation(endpointKey, msg) {
62
62
  return {
63
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
63
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
64
64
  kind: resolveChatType(msg.conversationType),
65
65
  id: msg.conversationId || msg.senderId || 'unknown',
66
66
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-dingtalk",
3
- "version": "6.0.3",
3
+ "version": "6.0.5",
4
4
  "description": "Zhin.js DingTalk (钉钉) adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -33,22 +33,23 @@
33
33
  "directory": "plugins/adapters/dingtalk"
34
34
  },
35
35
  "dependencies": {
36
- "@zhin.js/adapter": "1.1.5",
37
- "@zhin.js/command": "1.0.7",
38
- "@zhin.js/core": "1.5.2",
39
- "@zhin.js/host-http": "1.0.6",
40
- "@zhin.js/im-contract": "1.0.1",
41
- "@zhin.js/logger": "1.0.75",
42
- "@zhin.js/plugin-runtime": "1.1.3"
36
+ "@zhin.js/adapter": "1.1.7",
37
+ "@zhin.js/command": "1.0.9",
38
+ "@zhin.js/core": "1.5.4",
39
+ "@zhin.js/host-http": "1.0.8",
40
+ "@zhin.js/im-contract": "1.0.3",
41
+ "@zhin.js/logger": "1.0.76",
42
+ "@zhin.js/plugin-runtime": "1.1.5"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "zod": "^4.0.0",
46
- "@zhin.js/adapter": "1.1.5",
47
- "@zhin.js/agent": "1.1.3",
48
- "@zhin.js/core": "1.5.2",
49
- "@zhin.js/host-http": "1.0.6",
50
- "@zhin.js/plugin-runtime": "1.1.3",
51
- "zhin.js": "6.0.2"
46
+ "@zhin.js/adapter": "1.1.7",
47
+ "@zhin.js/agent": "1.1.5",
48
+ "@zhin.js/core": "1.5.4",
49
+ "@zhin.js/host-http": "1.0.8",
50
+ "@zhin.js/permission": "1.0.1",
51
+ "@zhin.js/plugin-runtime": "1.1.5",
52
+ "zhin.js": "6.0.4"
52
53
  },
53
54
  "peerDependenciesMeta": {
54
55
  "zhin.js": {
@@ -66,8 +67,8 @@
66
67
  "typescript": "^6.0.3",
67
68
  "vitest": "^4.1.10",
68
69
  "zod": "^4.4.3",
69
- "@zhin.js/agent": "1.1.3",
70
- "zhin.js": "6.0.2"
70
+ "@zhin.js/agent": "1.1.5",
71
+ "zhin.js": "6.0.4"
71
72
  },
72
73
  "files": [
73
74
  "adapters",
package/plugin.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { createEndpointRuntimeState } from '@zhin.js/adapter';
3
3
  import { definePlugin } from '@zhin.js/plugin-runtime';
4
- import { registerDingtalkPlatformPermitChecker } from "./lib/platform-permit.js";
4
+ import { permissionHostToken } from '@zhin.js/permission';
5
+ import { checkDingtalkPlatformPermit } from "./lib/platform-permit.js";
5
6
  import { dingtalkRuntimeStateToken } from "./lib/dingtalk-runtime-state.js";
6
7
  export default definePlugin({
7
8
  name: 'dingtalk',
@@ -9,8 +10,10 @@ export default definePlugin({
9
10
  displayName: 'DingTalk (钉钉) Adapter',
10
11
  },
11
12
  setup(context) {
12
- // 运行中 endpoint 注册表(dingtalk.endpoint list 的"运行中"数据源)
13
13
  context.resources.provide(dingtalkRuntimeStateToken, createEndpointRuntimeState());
14
- return registerDingtalkPlatformPermitChecker();
14
+ if (context.resources.has(permissionHostToken)) {
15
+ const host = context.resources.use(permissionHostToken);
16
+ return host.registerPlatform('dingtalk', checkDingtalkPlatformPermit);
17
+ }
15
18
  },
16
19
  });
package/schema.json CHANGED
@@ -7,16 +7,48 @@
7
7
  "type": "string",
8
8
  "default": "https://oapi.dingtalk.com"
9
9
  },
10
+ "master": {
11
+ "type": [
12
+ "string",
13
+ "number"
14
+ ],
15
+ "description": "框架 master(DingTalk userid;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
16
+ },
17
+ "trusted": {
18
+ "type": "array",
19
+ "items": {
20
+ "type": [
21
+ "string",
22
+ "number"
23
+ ],
24
+ "description": "Trusted DingTalk userid"
25
+ },
26
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
27
+ },
10
28
  "endpoints": {
11
29
  "type": "array",
12
- "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
30
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
13
31
  "items": {
14
32
  "type": "object",
15
33
  "additionalProperties": true,
16
34
  "properties": {
17
- "name": {
18
- "type": "string",
19
- "description": "Dingtalk bot name"
35
+ "master": {
36
+ "type": [
37
+ "string",
38
+ "number"
39
+ ],
40
+ "description": "本 endpoint 的框架 master(DingTalk userid);覆盖顶层 master"
41
+ },
42
+ "trusted": {
43
+ "type": "array",
44
+ "items": {
45
+ "type": [
46
+ "string",
47
+ "number"
48
+ ],
49
+ "description": "Trusted DingTalk userid"
50
+ },
51
+ "description": "本 endpoint 的 trusted 列表"
20
52
  },
21
53
  "appKey": {
22
54
  "type": "string",
@@ -33,10 +65,14 @@
33
65
  "robotCode": {
34
66
  "type": "string",
35
67
  "description": "Dingtalk robot code"
68
+ },
69
+ "id": {
70
+ "type": "string",
71
+ "description": "Dingtalk bot name"
36
72
  }
37
73
  },
38
74
  "required": [
39
- "name",
75
+ "id",
40
76
  "appKey",
41
77
  "appSecret",
42
78
  "webhookPath",
@@ -23,20 +23,20 @@ export interface DingtalkAgentEndpoint {
23
23
  }
24
24
 
25
25
  export interface DingtalkAgentDeps {
26
- getEndpoint: (endpointId: string) => DingtalkAgentEndpoint;
26
+ getEndpoint: (endpointKey: string) => DingtalkAgentEndpoint;
27
27
  }
28
28
 
29
29
  const endpoints = new Map<string, DingtalkAgentEndpoint>();
30
30
  let override: DingtalkAgentDeps | null = null;
31
31
 
32
32
  export function registerDingtalkAgentEndpoint(
33
- endpointId: string,
33
+ endpointKey: string,
34
34
  endpoint: DingtalkAgentEndpoint,
35
35
  ): () => void {
36
- endpoints.set(endpointId, endpoint);
36
+ endpoints.set(endpointKey, endpoint);
37
37
  return () => {
38
- if (endpoints.get(endpointId) === endpoint) {
39
- endpoints.delete(endpointId);
38
+ if (endpoints.get(endpointKey) === endpoint) {
39
+ endpoints.delete(endpointKey);
40
40
  }
41
41
  };
42
42
  }
@@ -49,9 +49,9 @@ export function setDingtalkAgentDeps(deps: DingtalkAgentDeps | null): void {
49
49
  export function getDingtalkAgentDeps(): DingtalkAgentDeps {
50
50
  if (override) return override;
51
51
  return {
52
- getEndpoint(endpointId) {
53
- const endpoint = endpoints.get(endpointId);
54
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
52
+ getEndpoint(endpointKey) {
53
+ const endpoint = endpoints.get(endpointKey);
54
+ if (!endpoint) throw new Error(`Endpoint ${endpointKey} 不存在`);
55
55
  return endpoint;
56
56
  },
57
57
  };
package/src/endpoint.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import type { EndpointInstance, EndpointSendRequest } from '@zhin.js/adapter';
5
5
  import type { MessageGateway } from '@zhin.js/core/runtime';
6
6
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
7
- import { formatCompact, getLogger } from '@zhin.js/logger';
7
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
8
8
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
9
9
  import { registerDingtalkAgentEndpoint } from './dingtalk-agent-deps.js';
10
10
  import { normalizeDingtalkSenderForPermit } from './platform-permit.js';
@@ -25,8 +25,6 @@ import {
25
25
  } from './protocol.js';
26
26
  import { registerDingTalkWebhookRoutes } from './webhook.js';
27
27
 
28
- const logger = getLogger('dingtalk');
29
-
30
28
  export type DingTalkFetch = (
31
29
  url: string,
32
30
  init?: {
@@ -55,6 +53,8 @@ export interface DingTalkEndpointOptions {
55
53
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
56
54
  */
57
55
  export class DingTalkEndpoint implements EndpointInstance {
56
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
57
+
58
58
  readonly #options: DingTalkEndpointOptions;
59
59
  readonly #fetch: DingTalkFetch;
60
60
  #routeReleases: HttpRouteRegistration[] = [];
@@ -66,6 +66,7 @@ export class DingTalkEndpoint implements EndpointInstance {
66
66
  #unregisterAgent?: () => void;
67
67
 
68
68
  constructor(options: DingTalkEndpointOptions) {
69
+ this.#logger = getAdapterLogger('dingtalk', options.config.id);
69
70
  this.#options = options;
70
71
  this.#fetch = options.fetch ?? globalThis.fetch;
71
72
  }
@@ -84,16 +85,16 @@ export class DingTalkEndpoint implements EndpointInstance {
84
85
  this.#started = true;
85
86
  try {
86
87
  await this.#refreshAccessToken();
87
- this.#unregisterAgent = registerDingtalkAgentEndpoint(this.#options.config.name, this);
88
+ this.#unregisterAgent = registerDingtalkAgentEndpoint(this.#options.config.id, this);
88
89
  this.#routeReleases.push(...registerDingTalkWebhookRoutes(this.#options.http, this));
89
- logger.debug(formatCompact({
90
- endpoint: this.#options.config.name,
90
+ this.#logger.debug(formatCompact({
91
+ endpoint: this.#options.config.id,
91
92
  op: 'webhook',
92
93
  path: this.#options.config.webhookPath,
93
94
  }));
94
95
  } catch (error) {
95
96
  await this.stop();
96
- logger.error('Failed to connect DingTalk endpoint:', error);
97
+ this.#logger.error('Failed to connect DingTalk endpoint:', error);
97
98
  throw error;
98
99
  }
99
100
  }
@@ -113,7 +114,7 @@ export class DingTalkEndpoint implements EndpointInstance {
113
114
  this.#unregisterAgent?.();
114
115
  this.#unregisterAgent = undefined;
115
116
  this.#started = false;
116
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
117
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
117
118
  }
118
119
 
119
120
  async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
@@ -129,9 +130,9 @@ export class DingTalkEndpoint implements EndpointInstance {
129
130
  if (data.errcode !== 0) {
130
131
  throw new Error(`Failed to send message via session webhook: ${data.errmsg}`);
131
132
  }
132
- logger.debug(formatCompact({
133
+ this.#logger.debug(formatCompact({
133
134
  op: 'send',
134
- endpoint: this.#options.config.name,
135
+ endpoint: this.#options.config.id,
135
136
  via: 'sessionWebhook',
136
137
  to: conversation.id,
137
138
  }));
@@ -151,7 +152,7 @@ export class DingTalkEndpoint implements EndpointInstance {
151
152
  if (data.errcode !== 0) {
152
153
  throw new Error(`Failed to send message: ${data.errmsg}`);
153
154
  }
154
- logger.debug(formatCompact({ op: 'send', endpoint: this.#options.config.name, to: conversation.id }));
155
+ this.#logger.debug(formatCompact({ op: 'send', to: conversation.id }));
155
156
  return (data.msgId as string) || `${Date.now()}`;
156
157
  }
157
158
 
@@ -168,19 +169,23 @@ export class DingTalkEndpoint implements EndpointInstance {
168
169
  conversation,
169
170
  message: { conversation, id: generateMessageId(event) },
170
171
  content: formatInboundContent(event),
171
- sender: resolveSender(event),
172
+ sender: {
173
+ id: resolveSender(event),
174
+ name: event.senderNick || undefined,
175
+ ...(permit.role ? { roles: [permit.role] } : {}),
176
+ },
177
+ endpointId: this.#options.config.id,
178
+ ...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
172
179
  metadata: Object.freeze({
173
180
  msgtype: event.msgtype,
174
181
  chatType,
175
- endpoint: this.#options.config.name,
176
182
  senderNick: event.senderNick,
177
183
  role: permit.role,
178
184
  permissions: permit.permissions,
179
185
  conversationType: event.conversationType,
180
- ...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
181
186
  }),
182
187
  }).catch((err) => {
183
- logger.warn(formatCompact({
188
+ this.#logger.warn(formatCompact({
184
189
  op: 'dingtalk_gateway_receive_failed',
185
190
  conversationId: conversation.id,
186
191
  error: err instanceof Error ? err.message : String(err),
@@ -197,7 +202,7 @@ export class DingTalkEndpoint implements EndpointInstance {
197
202
  if (data.errcode === 0) return data.result;
198
203
  throw new Error(`Failed to get user info: ${data.errmsg}`);
199
204
  } catch (error) {
200
- logger.error('Failed to get user info:', error);
205
+ this.#logger.error('Failed to get user info:', error);
201
206
  return null;
202
207
  }
203
208
  }
@@ -214,7 +219,7 @@ export class DingTalkEndpoint implements EndpointInstance {
214
219
  }
215
220
  throw new Error(`Failed to get department users: ${data.errmsg}`);
216
221
  } catch (error) {
217
- logger.error('Failed to get department users:', error);
222
+ this.#logger.error('Failed to get department users:', error);
218
223
  return [];
219
224
  }
220
225
  }
@@ -232,7 +237,7 @@ export class DingTalkEndpoint implements EndpointInstance {
232
237
  if (data.errcode === 0) return true;
233
238
  throw new Error(`Failed to send work notice: ${data.errmsg}`);
234
239
  } catch (error) {
235
- logger.error('Failed to send work notice:', error);
240
+ this.#logger.error('Failed to send work notice:', error);
236
241
  return false;
237
242
  }
238
243
  }
@@ -246,7 +251,7 @@ export class DingTalkEndpoint implements EndpointInstance {
246
251
  if (data.errcode === 0) return (data.result as unknown[]) || [];
247
252
  throw new Error(`Failed to get department list: ${data.errmsg}`);
248
253
  } catch (error) {
249
- logger.error('Failed to get department list:', error);
254
+ this.#logger.error('Failed to get department list:', error);
250
255
  return [];
251
256
  }
252
257
  }
@@ -260,7 +265,7 @@ export class DingTalkEndpoint implements EndpointInstance {
260
265
  if (data.errcode === 0) return data.result;
261
266
  throw new Error(`Failed to get department info: ${data.errmsg}`);
262
267
  } catch (error) {
263
- logger.error('Failed to get department info:', error);
268
+ this.#logger.error('Failed to get department info:', error);
264
269
  return null;
265
270
  }
266
271
  }
@@ -278,7 +283,7 @@ export class DingTalkEndpoint implements EndpointInstance {
278
283
  if (data.errcode === 0) return (data.chatid as string) || null;
279
284
  throw new Error(`Failed to create chat: ${data.errmsg}`);
280
285
  } catch (error) {
281
- logger.error('Failed to create chat:', error);
286
+ this.#logger.error('Failed to create chat:', error);
282
287
  return null;
283
288
  }
284
289
  }
@@ -292,7 +297,7 @@ export class DingTalkEndpoint implements EndpointInstance {
292
297
  if (data.errcode === 0) return data.chat_info;
293
298
  throw new Error(`Failed to get chat info: ${data.errmsg}`);
294
299
  } catch (error) {
295
- logger.error('Failed to get chat info:', error);
300
+ this.#logger.error('Failed to get chat info:', error);
296
301
  return null;
297
302
  }
298
303
  }
@@ -314,7 +319,7 @@ export class DingTalkEndpoint implements EndpointInstance {
314
319
  if (data.errcode === 0) return true;
315
320
  throw new Error(`Failed to update chat: ${data.errmsg}`);
316
321
  } catch (error) {
317
- logger.error('Failed to update chat:', error);
322
+ this.#logger.error('Failed to update chat:', error);
318
323
  return false;
319
324
  }
320
325
  }
@@ -378,7 +383,7 @@ export class DingTalkEndpoint implements EndpointInstance {
378
383
  expires_in: data.expires_in ?? 7200,
379
384
  timestamp: Date.now(),
380
385
  };
381
- logger.debug('Access token refreshed successfully');
386
+ this.#logger.debug('Access token refreshed successfully');
382
387
  return;
383
388
  }
384
389
  throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
package/src/index.ts CHANGED
@@ -45,5 +45,4 @@ export {
45
45
  dingtalkGroupPermitResolver,
46
46
  normalizeDingtalkSenderForPermit,
47
47
  platformPermit,
48
- registerDingtalkPlatformPermitChecker,
49
48
  } from './platform-permit.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 钉钉 DingTalk platform permit
3
3
  */
4
- import { registerPlatformPermitChecker, type Message } from '@zhin.js/core';
4
+ import type { PermissionSubject } from '@zhin.js/permission';
5
5
 
6
6
  const ADAPTER = 'dingtalk';
7
7
 
@@ -31,10 +31,9 @@ export function normalizeDingtalkSenderForPermit(input: {
31
31
  return { role: 'member', permissions: [] };
32
32
  }
33
33
 
34
- export function checkDingtalkPlatformPermit(perm: string, message: Message<any>): boolean {
35
- const sender = message.$sender as { role?: string; permissions?: string[] };
36
- const permissions = sender.permissions ?? [];
37
- const role = sender.role;
34
+ export function checkDingtalkPlatformPermit(perm: string, subject: PermissionSubject): boolean {
35
+ const role = subject.sender?.role?.[0];
36
+ const permissions = subject.sender?.permissions ?? [];
38
37
  const has = (t: string) => permissions.includes(t);
39
38
 
40
39
  switch (perm) {
@@ -47,6 +46,3 @@ export function checkDingtalkPlatformPermit(perm: string, message: Message<any>)
47
46
  }
48
47
  }
49
48
 
50
- export function registerDingtalkPlatformPermitChecker(): () => void {
51
- return registerPlatformPermitChecker(ADAPTER, checkDingtalkPlatformPermit);
52
- }
package/src/protocol.ts CHANGED
@@ -14,7 +14,7 @@ const logger = getLogger('dingtalk');
14
14
 
15
15
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
16
16
  export interface DingTalkAdapterConfig {
17
- readonly name?: string;
17
+ readonly id?: string;
18
18
  readonly appKey?: string;
19
19
  readonly appSecret?: string;
20
20
  readonly webhookPath?: string;
@@ -28,7 +28,7 @@ export interface DingTalkAdapterConfig {
28
28
 
29
29
  export interface ResolvedDingTalkConfig {
30
30
  readonly context: 'dingtalk';
31
- readonly name: string;
31
+ readonly id: string;
32
32
  readonly appKey: string;
33
33
  readonly appSecret: string;
34
34
  readonly webhookPath: string;
@@ -124,8 +124,8 @@ export function resolveDingTalkConfig(config: DingTalkAdapterConfig = {}): Resol
124
124
  'DingTalk adapter requires appKey + appSecret (plugins.<key> or endpoints with context: dingtalk)',
125
125
  );
126
126
  }
127
- const name = (typeof config.name === 'string' && config.name)
128
- || (typeof entry?.name === 'string' && entry.name)
127
+ const id = (typeof config.id === 'string' && config.id)
128
+ || (typeof entry?.id === 'string' && entry.id)
129
129
  || process.env.DINGTALK_BOT_NAME
130
130
  || 'dingtalk-bot';
131
131
  const webhookPath = normalizeWebhookPath(
@@ -137,7 +137,7 @@ export function resolveDingTalkConfig(config: DingTalkAdapterConfig = {}): Resol
137
137
  const robotCode = config.robotCode ?? entry?.robotCode;
138
138
  return {
139
139
  context: 'dingtalk',
140
- name,
140
+ id,
141
141
  appKey,
142
142
  appSecret,
143
143
  webhookPath,
@@ -160,9 +160,9 @@ export function resolveChatType(conversationType?: string): 'group' | 'private'
160
160
  * 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
161
161
  * 钉钉无 guild/频道容器概念,不产生 parent。
162
162
  */
163
- export function dingtalkInboundConversation(endpointId: string, msg: DingTalkMessage): ConversationRef {
163
+ export function dingtalkInboundConversation(endpointKey: string, msg: DingTalkMessage): ConversationRef {
164
164
  return {
165
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
165
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
166
166
  kind: resolveChatType(msg.conversationType),
167
167
  id: msg.conversationId || msg.senderId || 'unknown',
168
168
  };
File without changes
File without changes
File without changes
File without changes