@zhin.js/adapter-dingtalk 6.0.2 → 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,100 @@
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
+
82
+ ## 6.0.3
83
+
84
+ ### Patch Changes
85
+
86
+ - Updated dependencies [afc0e66]
87
+ - Updated dependencies [2e41ad5]
88
+ - Updated dependencies [9f57124]
89
+ - @zhin.js/core@1.5.2
90
+ - @zhin.js/adapter@1.1.5
91
+ - @zhin.js/im-contract@1.0.1
92
+ - @zhin.js/plugin-runtime@1.1.3
93
+ - @zhin.js/command@1.0.7
94
+ - @zhin.js/host-http@1.0.6
95
+ - @zhin.js/agent@1.1.3
96
+ - zhin.js@6.0.2
97
+
3
98
  ## 6.0.2
4
99
 
5
100
  ### Patch Changes
package/README.md CHANGED
@@ -24,8 +24,8 @@ pnpm add @zhin.js/adapter-dingtalk
24
24
  - `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
25
25
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
26
 
27
- 入站:`gateway.receive({ adapter, target: conversationId, content: text, sender, metadata })`
28
- 出站:`send({ target, payload })` → sessionWebhook 或 `/robot/send`
27
+ 入站:`gateway.receive({ conversation: ConversationRef, message: { conversation, id }, content: text, sender, metadata })`
28
+ 出站:`send({ conversation, payload })` → sessionWebhook 或 `/robot/send`
29
29
 
30
30
  ## 前置条件
31
31
 
@@ -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.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * DingTalkEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
3
3
  */
4
- import type { EndpointInstance } from '@zhin.js/adapter';
4
+ import type { EndpointInstance, EndpointSendRequest } from '@zhin.js/adapter';
5
5
  import type { MessageGateway } from '@zhin.js/core/runtime';
6
6
  import type { HttpHost } from '@zhin.js/host-http';
7
7
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
@@ -38,10 +38,7 @@ export declare class DingTalkEndpoint implements EndpointInstance {
38
38
  open(): void;
39
39
  close(): void;
40
40
  stop(): Promise<void>;
41
- send({ target, payload }: {
42
- readonly target: string;
43
- readonly payload: unknown;
44
- }): Promise<string>;
41
+ send({ conversation, payload }: EndpointSendRequest): Promise<string>;
45
42
  /** Test / internal: admit a parsed event when open (non-webhook path). */
46
43
  admit(event: DingTalkEvent | DingTalkMessage): void;
47
44
  getUserInfo(userId: string): Promise<unknown>;
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
- import { formatInboundContent, formatOutboundBody, generateMessageId, isDingtalkBotMentioned, resolveChatType, resolveSender, resolveTarget, } from './protocol.js';
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,11 +65,11 @@ 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
- async send({ target, payload }) {
70
+ async send({ conversation, payload }) {
70
71
  const content = formatOutboundBody(payload);
71
- const sessionWebhook = this.#sessionWebhooks.get(target);
72
+ const sessionWebhook = this.#sessionWebhooks.get(conversation.id);
72
73
  if (sessionWebhook) {
73
74
  const response = await this.#fetch(sessionWebhook, {
74
75
  method: 'POST',
@@ -79,11 +80,11 @@ 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
- to: target,
87
+ to: conversation.id,
87
88
  }));
88
89
  return data.msgId || `${Date.now()}`;
89
90
  }
@@ -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: target }));
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). */
@@ -110,29 +111,32 @@ export class DingTalkEndpoint {
110
111
  if (event.sessionWebhook && event.conversationId) {
111
112
  this.#sessionWebhooks.set(event.conversationId, event.sessionWebhook);
112
113
  }
113
- const target = resolveTarget(event);
114
+ const conversation = dingtalkInboundConversation(String(this.#options.id), event);
114
115
  const chatType = resolveChatType(event.conversationType);
115
116
  const permit = normalizeDingtalkSenderForPermit({ isAdmin: event.isAdmin === true });
116
117
  void this.#options.gateway.receive({
117
- adapter: this.#options.id,
118
- target,
118
+ conversation,
119
+ message: { conversation, id: generateMessageId(event) },
119
120
  content: formatInboundContent(event),
120
- sender: resolveSender(event),
121
- id: generateMessageId(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 } : {}),
122
128
  metadata: Object.freeze({
123
129
  msgtype: event.msgtype,
124
130
  chatType,
125
- endpoint: this.#options.config.name,
126
131
  senderNick: event.senderNick,
127
132
  role: permit.role,
128
133
  permissions: permit.permissions,
129
134
  conversationType: event.conversationType,
130
- ...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
131
135
  }),
132
136
  }).catch((err) => {
133
- logger.warn(formatCompact({
137
+ this.#logger.warn(formatCompact({
134
138
  op: 'dingtalk_gateway_receive_failed',
135
- target,
139
+ conversationId: conversation.id,
136
140
  error: err instanceof Error ? err.message : String(err),
137
141
  }));
138
142
  });
@@ -148,7 +152,7 @@ export class DingTalkEndpoint {
148
152
  throw new Error(`Failed to get user info: ${data.errmsg}`);
149
153
  }
150
154
  catch (error) {
151
- logger.error('Failed to get user info:', error);
155
+ this.#logger.error('Failed to get user info:', error);
152
156
  return null;
153
157
  }
154
158
  }
@@ -165,7 +169,7 @@ export class DingTalkEndpoint {
165
169
  throw new Error(`Failed to get department users: ${data.errmsg}`);
166
170
  }
167
171
  catch (error) {
168
- logger.error('Failed to get department users:', error);
172
+ this.#logger.error('Failed to get department users:', error);
169
173
  return [];
170
174
  }
171
175
  }
@@ -184,7 +188,7 @@ export class DingTalkEndpoint {
184
188
  throw new Error(`Failed to send work notice: ${data.errmsg}`);
185
189
  }
186
190
  catch (error) {
187
- logger.error('Failed to send work notice:', error);
191
+ this.#logger.error('Failed to send work notice:', error);
188
192
  return false;
189
193
  }
190
194
  }
@@ -199,7 +203,7 @@ export class DingTalkEndpoint {
199
203
  throw new Error(`Failed to get department list: ${data.errmsg}`);
200
204
  }
201
205
  catch (error) {
202
- logger.error('Failed to get department list:', error);
206
+ this.#logger.error('Failed to get department list:', error);
203
207
  return [];
204
208
  }
205
209
  }
@@ -214,7 +218,7 @@ export class DingTalkEndpoint {
214
218
  throw new Error(`Failed to get department info: ${data.errmsg}`);
215
219
  }
216
220
  catch (error) {
217
- logger.error('Failed to get department info:', error);
221
+ this.#logger.error('Failed to get department info:', error);
218
222
  return null;
219
223
  }
220
224
  }
@@ -229,7 +233,7 @@ export class DingTalkEndpoint {
229
233
  throw new Error(`Failed to create chat: ${data.errmsg}`);
230
234
  }
231
235
  catch (error) {
232
- logger.error('Failed to create chat:', error);
236
+ this.#logger.error('Failed to create chat:', error);
233
237
  return null;
234
238
  }
235
239
  }
@@ -244,7 +248,7 @@ export class DingTalkEndpoint {
244
248
  throw new Error(`Failed to get chat info: ${data.errmsg}`);
245
249
  }
246
250
  catch (error) {
247
- logger.error('Failed to get chat info:', error);
251
+ this.#logger.error('Failed to get chat info:', error);
248
252
  return null;
249
253
  }
250
254
  }
@@ -259,7 +263,7 @@ export class DingTalkEndpoint {
259
263
  throw new Error(`Failed to update chat: ${data.errmsg}`);
260
264
  }
261
265
  catch (error) {
262
- logger.error('Failed to update chat:', error);
266
+ this.#logger.error('Failed to update chat:', error);
263
267
  return false;
264
268
  }
265
269
  }
@@ -309,7 +313,7 @@ export class DingTalkEndpoint {
309
313
  expires_in: data.expires_in ?? 7200,
310
314
  timestamp: Date.now(),
311
315
  };
312
- logger.debug('Access token refreshed successfully');
316
+ this.#logger.debug('Access token refreshed successfully');
313
317
  return;
314
318
  }
315
319
  throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
package/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender, resolveTarget, verifySignature, type AccessToken, type DingTalkAdapterConfig, type DingTalkApiResponse, type DingTalkEvent, type DingTalkMessage, type DingTalkSendBody, type DingTalkWireSegment, type ResolvedDingTalkConfig, } from './protocol.js';
1
+ export { dingtalkInboundConversation, formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender, verifySignature, type AccessToken, type DingTalkAdapterConfig, type DingTalkApiResponse, type DingTalkEvent, type DingTalkMessage, type DingTalkSendBody, type DingTalkWireSegment, type ResolvedDingTalkConfig, } from './protocol.js';
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
@@ -1,5 +1,5 @@
1
- export { formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender, resolveTarget, verifySignature, } from './protocol.js';
1
+ export { dingtalkInboundConversation, formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender, verifySignature, } from './protocol.js';
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
@@ -3,9 +3,10 @@
3
3
  * Canonicalization is owned by gateway/core before endpoint.send.
4
4
  */
5
5
  import type { IncomingMessage } from 'node:http';
6
+ import type { ConversationRef } from '@zhin.js/im-contract';
6
7
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
7
8
  export interface DingTalkAdapterConfig {
8
- readonly name?: string;
9
+ readonly id?: string;
9
10
  readonly appKey?: string;
10
11
  readonly appSecret?: string;
11
12
  readonly webhookPath?: string;
@@ -18,7 +19,7 @@ export interface DingTalkAdapterConfig {
18
19
  }
19
20
  export interface ResolvedDingTalkConfig {
20
21
  readonly context: 'dingtalk';
21
- readonly name: string;
22
+ readonly id: string;
22
23
  readonly appKey: string;
23
24
  readonly appSecret: string;
24
25
  readonly webhookPath: string;
@@ -104,7 +105,12 @@ export interface DingTalkSendBody {
104
105
  export declare function resolveDingTalkConfig(config?: DingTalkAdapterConfig): ResolvedDingTalkConfig;
105
106
  export declare function normalizeWebhookPath(path: string): string;
106
107
  export declare function resolveChatType(conversationType?: string): 'group' | 'private';
107
- export declare function resolveTarget(msg: DingTalkMessage): string;
108
+ /**
109
+ * 入站归一化 → ConversationRef:`conversationType: '2'` 为群会话(kind 'group'),
110
+ * 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
111
+ * 钉钉无 guild/频道容器概念,不产生 parent。
112
+ */
113
+ export declare function dingtalkInboundConversation(endpointKey: string, msg: DingTalkMessage): ConversationRef;
108
114
  export declare function resolveSender(msg: DingTalkMessage): string;
109
115
  export declare function generateMessageId(msg: DingTalkMessage): string;
110
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,
@@ -53,8 +53,17 @@ export function normalizeWebhookPath(path) {
53
53
  export function resolveChatType(conversationType) {
54
54
  return conversationType === '2' ? 'group' : 'private';
55
55
  }
56
- export function resolveTarget(msg) {
57
- return msg.conversationId || msg.senderId || 'unknown';
56
+ /**
57
+ * 入站归一化 ConversationRef:`conversationType: '2'` 为群会话(kind 'group'),
58
+ * 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
59
+ * 钉钉无 guild/频道容器概念,不产生 parent。
60
+ */
61
+ export function dingtalkInboundConversation(endpointKey, msg) {
62
+ return {
63
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
64
+ kind: resolveChatType(msg.conversationType),
65
+ id: msg.conversationId || msg.senderId || 'unknown',
66
+ };
58
67
  }
59
68
  export function resolveSender(msg) {
60
69
  return msg.senderId || msg.senderStaffId || 'unknown';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-dingtalk",
3
- "version": "6.0.2",
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,21 +33,23 @@
33
33
  "directory": "plugins/adapters/dingtalk"
34
34
  },
35
35
  "dependencies": {
36
- "@zhin.js/adapter": "1.1.4",
37
- "@zhin.js/command": "1.0.6",
38
- "@zhin.js/core": "1.5.1",
39
- "@zhin.js/host-http": "1.0.5",
40
- "@zhin.js/logger": "1.0.75",
41
- "@zhin.js/plugin-runtime": "1.1.2"
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"
42
43
  },
43
44
  "peerDependencies": {
44
45
  "zod": "^4.0.0",
45
- "@zhin.js/adapter": "1.1.4",
46
- "@zhin.js/agent": "1.1.2",
47
- "@zhin.js/core": "1.5.1",
48
- "@zhin.js/host-http": "1.0.5",
49
- "@zhin.js/plugin-runtime": "1.1.2",
50
- "zhin.js": "6.0.1"
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"
51
53
  },
52
54
  "peerDependenciesMeta": {
53
55
  "zhin.js": {
@@ -65,8 +67,8 @@
65
67
  "typescript": "^6.0.3",
66
68
  "vitest": "^4.1.10",
67
69
  "zod": "^4.4.3",
68
- "@zhin.js/agent": "1.1.2",
69
- "zhin.js": "6.0.1"
70
+ "@zhin.js/agent": "1.1.5",
71
+ "zhin.js": "6.0.4"
70
72
  },
71
73
  "files": [
72
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
@@ -1,21 +1,21 @@
1
1
  /**
2
2
  * DingTalkEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
3
3
  */
4
- import type { EndpointInstance } from '@zhin.js/adapter';
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';
11
11
  import {
12
+ dingtalkInboundConversation,
12
13
  formatInboundContent,
13
14
  formatOutboundBody,
14
15
  generateMessageId,
15
16
  isDingtalkBotMentioned,
16
17
  resolveChatType,
17
18
  resolveSender,
18
- resolveTarget,
19
19
  type AccessToken,
20
20
  type DingTalkApiResponse,
21
21
  type DingTalkEvent,
@@ -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,12 +114,12 @@ 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
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
120
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
120
121
  const content = formatOutboundBody(payload);
121
- const sessionWebhook = this.#sessionWebhooks.get(target);
122
+ const sessionWebhook = this.#sessionWebhooks.get(conversation.id);
122
123
  if (sessionWebhook) {
123
124
  const response = await this.#fetch(sessionWebhook, {
124
125
  method: 'POST',
@@ -129,11 +130,11 @@ 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
- to: target,
137
+ to: conversation.id,
137
138
  }));
138
139
  return (data.msgId as string) || `${Date.now()}`;
139
140
  }
@@ -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: target }));
155
+ this.#logger.debug(formatCompact({ op: 'send', to: conversation.id }));
155
156
  return (data.msgId as string) || `${Date.now()}`;
156
157
  }
157
158
 
@@ -161,29 +162,32 @@ export class DingTalkEndpoint implements EndpointInstance {
161
162
  if (event.sessionWebhook && event.conversationId) {
162
163
  this.#sessionWebhooks.set(event.conversationId, event.sessionWebhook);
163
164
  }
164
- const target = resolveTarget(event);
165
+ const conversation = dingtalkInboundConversation(String(this.#options.id), event);
165
166
  const chatType = resolveChatType(event.conversationType);
166
167
  const permit = normalizeDingtalkSenderForPermit({ isAdmin: event.isAdmin === true });
167
168
  void this.#options.gateway.receive({
168
- adapter: this.#options.id,
169
- target,
169
+ conversation,
170
+ message: { conversation, id: generateMessageId(event) },
170
171
  content: formatInboundContent(event),
171
- sender: resolveSender(event),
172
- id: generateMessageId(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 } : {}),
173
179
  metadata: Object.freeze({
174
180
  msgtype: event.msgtype,
175
181
  chatType,
176
- endpoint: this.#options.config.name,
177
182
  senderNick: event.senderNick,
178
183
  role: permit.role,
179
184
  permissions: permit.permissions,
180
185
  conversationType: event.conversationType,
181
- ...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
182
186
  }),
183
187
  }).catch((err) => {
184
- logger.warn(formatCompact({
188
+ this.#logger.warn(formatCompact({
185
189
  op: 'dingtalk_gateway_receive_failed',
186
- target,
190
+ conversationId: conversation.id,
187
191
  error: err instanceof Error ? err.message : String(err),
188
192
  }));
189
193
  });
@@ -198,7 +202,7 @@ export class DingTalkEndpoint implements EndpointInstance {
198
202
  if (data.errcode === 0) return data.result;
199
203
  throw new Error(`Failed to get user info: ${data.errmsg}`);
200
204
  } catch (error) {
201
- logger.error('Failed to get user info:', error);
205
+ this.#logger.error('Failed to get user info:', error);
202
206
  return null;
203
207
  }
204
208
  }
@@ -215,7 +219,7 @@ export class DingTalkEndpoint implements EndpointInstance {
215
219
  }
216
220
  throw new Error(`Failed to get department users: ${data.errmsg}`);
217
221
  } catch (error) {
218
- logger.error('Failed to get department users:', error);
222
+ this.#logger.error('Failed to get department users:', error);
219
223
  return [];
220
224
  }
221
225
  }
@@ -233,7 +237,7 @@ export class DingTalkEndpoint implements EndpointInstance {
233
237
  if (data.errcode === 0) return true;
234
238
  throw new Error(`Failed to send work notice: ${data.errmsg}`);
235
239
  } catch (error) {
236
- logger.error('Failed to send work notice:', error);
240
+ this.#logger.error('Failed to send work notice:', error);
237
241
  return false;
238
242
  }
239
243
  }
@@ -247,7 +251,7 @@ export class DingTalkEndpoint implements EndpointInstance {
247
251
  if (data.errcode === 0) return (data.result as unknown[]) || [];
248
252
  throw new Error(`Failed to get department list: ${data.errmsg}`);
249
253
  } catch (error) {
250
- logger.error('Failed to get department list:', error);
254
+ this.#logger.error('Failed to get department list:', error);
251
255
  return [];
252
256
  }
253
257
  }
@@ -261,7 +265,7 @@ export class DingTalkEndpoint implements EndpointInstance {
261
265
  if (data.errcode === 0) return data.result;
262
266
  throw new Error(`Failed to get department info: ${data.errmsg}`);
263
267
  } catch (error) {
264
- logger.error('Failed to get department info:', error);
268
+ this.#logger.error('Failed to get department info:', error);
265
269
  return null;
266
270
  }
267
271
  }
@@ -279,7 +283,7 @@ export class DingTalkEndpoint implements EndpointInstance {
279
283
  if (data.errcode === 0) return (data.chatid as string) || null;
280
284
  throw new Error(`Failed to create chat: ${data.errmsg}`);
281
285
  } catch (error) {
282
- logger.error('Failed to create chat:', error);
286
+ this.#logger.error('Failed to create chat:', error);
283
287
  return null;
284
288
  }
285
289
  }
@@ -293,7 +297,7 @@ export class DingTalkEndpoint implements EndpointInstance {
293
297
  if (data.errcode === 0) return data.chat_info;
294
298
  throw new Error(`Failed to get chat info: ${data.errmsg}`);
295
299
  } catch (error) {
296
- logger.error('Failed to get chat info:', error);
300
+ this.#logger.error('Failed to get chat info:', error);
297
301
  return null;
298
302
  }
299
303
  }
@@ -315,7 +319,7 @@ export class DingTalkEndpoint implements EndpointInstance {
315
319
  if (data.errcode === 0) return true;
316
320
  throw new Error(`Failed to update chat: ${data.errmsg}`);
317
321
  } catch (error) {
318
- logger.error('Failed to update chat:', error);
322
+ this.#logger.error('Failed to update chat:', error);
319
323
  return false;
320
324
  }
321
325
  }
@@ -379,7 +383,7 @@ export class DingTalkEndpoint implements EndpointInstance {
379
383
  expires_in: data.expires_in ?? 7200,
380
384
  timestamp: Date.now(),
381
385
  };
382
- logger.debug('Access token refreshed successfully');
386
+ this.#logger.debug('Access token refreshed successfully');
383
387
  return;
384
388
  }
385
389
  throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export {
2
+ dingtalkInboundConversation,
2
3
  formatInboundContent,
3
4
  formatOutboundBody,
4
5
  generateMessageId,
@@ -8,7 +9,6 @@ export {
8
9
  resolveChatType,
9
10
  resolveDingTalkConfig,
10
11
  resolveSender,
11
- resolveTarget,
12
12
  verifySignature,
13
13
  type AccessToken,
14
14
  type DingTalkAdapterConfig,
@@ -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
@@ -7,13 +7,14 @@ import { createHmac, timingSafeEqual } from 'node:crypto';
7
7
  import type { IncomingMessage } from 'node:http';
8
8
 
9
9
  import { isMediaRef } from '@zhin.js/core';
10
+ import type { ConversationRef } from '@zhin.js/im-contract';
10
11
  import { formatCompact, getLogger } from '@zhin.js/logger';
11
12
 
12
13
  const logger = getLogger('dingtalk');
13
14
 
14
15
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
15
16
  export interface DingTalkAdapterConfig {
16
- readonly name?: string;
17
+ readonly id?: string;
17
18
  readonly appKey?: string;
18
19
  readonly appSecret?: string;
19
20
  readonly webhookPath?: string;
@@ -27,7 +28,7 @@ export interface DingTalkAdapterConfig {
27
28
 
28
29
  export interface ResolvedDingTalkConfig {
29
30
  readonly context: 'dingtalk';
30
- readonly name: string;
31
+ readonly id: string;
31
32
  readonly appKey: string;
32
33
  readonly appSecret: string;
33
34
  readonly webhookPath: string;
@@ -123,8 +124,8 @@ export function resolveDingTalkConfig(config: DingTalkAdapterConfig = {}): Resol
123
124
  'DingTalk adapter requires appKey + appSecret (plugins.<key> or endpoints with context: dingtalk)',
124
125
  );
125
126
  }
126
- const name = (typeof config.name === 'string' && config.name)
127
- || (typeof entry?.name === 'string' && entry.name)
127
+ const id = (typeof config.id === 'string' && config.id)
128
+ || (typeof entry?.id === 'string' && entry.id)
128
129
  || process.env.DINGTALK_BOT_NAME
129
130
  || 'dingtalk-bot';
130
131
  const webhookPath = normalizeWebhookPath(
@@ -136,7 +137,7 @@ export function resolveDingTalkConfig(config: DingTalkAdapterConfig = {}): Resol
136
137
  const robotCode = config.robotCode ?? entry?.robotCode;
137
138
  return {
138
139
  context: 'dingtalk',
139
- name,
140
+ id,
140
141
  appKey,
141
142
  appSecret,
142
143
  webhookPath,
@@ -154,8 +155,17 @@ export function resolveChatType(conversationType?: string): 'group' | 'private'
154
155
  return conversationType === '2' ? 'group' : 'private';
155
156
  }
156
157
 
157
- export function resolveTarget(msg: DingTalkMessage): string {
158
- return msg.conversationId || msg.senderId || 'unknown';
158
+ /**
159
+ * 入站归一化 ConversationRef:`conversationType: '2'` 为群会话(kind 'group'),
160
+ * 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
161
+ * 钉钉无 guild/频道容器概念,不产生 parent。
162
+ */
163
+ export function dingtalkInboundConversation(endpointKey: string, msg: DingTalkMessage): ConversationRef {
164
+ return {
165
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
166
+ kind: resolveChatType(msg.conversationType),
167
+ id: msg.conversationId || msg.senderId || 'unknown',
168
+ };
159
169
  }
160
170
 
161
171
  export function resolveSender(msg: DingTalkMessage): string {