@zhin.js/adapter-wecom 4.0.3 → 4.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-wecom
2
2
 
3
+ ## 4.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
+ ## 4.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
  ## 4.0.3
4
83
 
5
84
  ### Patch Changes
package/adapters/wecom.js CHANGED
@@ -21,8 +21,8 @@ export default defineAdapter({
21
21
  create(context) {
22
22
  const config = resolveWecomConfig(context.config);
23
23
  // 注册到插件运行时状态(wecom.endpoint list 的"运行中"数据源)
24
- context.use(wecomRuntimeStateToken).endpoints.set(config.name, {
25
- name: config.name,
24
+ context.use(wecomRuntimeStateToken).endpoints.set(config.id, {
25
+ id: config.id,
26
26
  mode: 'webhook',
27
27
  });
28
28
  return new WecomEndpoint({
package/adapters/wecom.ts CHANGED
@@ -26,8 +26,8 @@ export default defineAdapter<WecomAdapterConfig>({
26
26
  create(context) {
27
27
  const config = resolveWecomConfig(context.config);
28
28
  // 注册到插件运行时状态(wecom.endpoint list 的"运行中"数据源)
29
- context.use(wecomRuntimeStateToken).endpoints.set(config.name, {
30
- name: config.name,
29
+ context.use(wecomRuntimeStateToken).endpoints.set(config.id, {
30
+ id: config.id,
31
31
  mode: 'webhook',
32
32
  });
33
33
  return new WecomEndpoint({
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 { registerWecomAgentEndpoint } from './wecom-agent-deps.js';
3
3
  import { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, } from './media-upload.js';
4
4
  import { buildSendRequestBody, formatInboundContent, formatOutboundBody, resolveChatType, wecomInboundConversation, } from './protocol.js';
5
5
  import { registerWecomWebhookRoutes } from './webhook.js';
6
- const logger = getLogger('wecom');
7
6
  /**
8
7
  * 企业微信服务端 API 无 bot 群列表/群成员列表接口(客户群接口属「客户联系」
9
8
  * 独立授权域,非 bot 社交面),好友/频道概念亦不存在;
10
9
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
11
10
  */
12
11
  export class WecomEndpoint {
12
+ #logger;
13
13
  #options;
14
14
  #fetch;
15
15
  #routeReleases = [];
@@ -19,6 +19,7 @@ export class WecomEndpoint {
19
19
  #started = false;
20
20
  #unregisterAgent;
21
21
  constructor(options) {
22
+ this.#logger = getAdapterLogger('wecom', options.config.id);
22
23
  this.#options = options;
23
24
  this.#fetch = options.fetch ?? globalThis.fetch;
24
25
  }
@@ -35,17 +36,17 @@ export class WecomEndpoint {
35
36
  this.#started = true;
36
37
  try {
37
38
  await this.#refreshAccessToken();
38
- this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.name, this);
39
+ this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.id, this);
39
40
  this.#routeReleases.push(...registerWecomWebhookRoutes(this.#options.http, this));
40
- logger.debug(formatCompact({
41
- endpoint: this.#options.config.name,
41
+ this.#logger.debug(formatCompact({
42
+ endpoint: this.#options.config.id,
42
43
  op: 'webhook',
43
44
  path: this.#options.config.webhookPath,
44
45
  }));
45
46
  }
46
47
  catch (error) {
47
48
  await this.stop();
48
- logger.error('Failed to connect WeCom endpoint:', error);
49
+ this.#logger.error('Failed to connect WeCom endpoint:', error);
49
50
  throw error;
50
51
  }
51
52
  }
@@ -62,7 +63,7 @@ export class WecomEndpoint {
62
63
  this.#unregisterAgent?.();
63
64
  this.#unregisterAgent = undefined;
64
65
  this.#started = false;
65
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
66
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
66
67
  }
67
68
  async send({ conversation, payload }) {
68
69
  const materialized = await this.#materializeOutboundMedia(payload);
@@ -77,10 +78,7 @@ export class WecomEndpoint {
77
78
  if (data.errcode !== 0) {
78
79
  throw new Error(`Failed to send message: ${data.errmsg} (${data.errcode})`);
79
80
  }
80
- logger.debug(formatCompact({
81
- op: 'send',
82
- endpoint: this.#options.config.name,
83
- to: `${conversation.kind}:${conversation.id}`,
81
+ this.#logger.debug(formatCompact({ op: 'send', to: `${conversation.kind}:${conversation.id}`,
84
82
  }));
85
83
  return data.msgid || `${Date.now()}`;
86
84
  }
@@ -110,9 +108,9 @@ export class WecomEndpoint {
110
108
  const data = seg.data ?? {};
111
109
  const media = readOutboundImageMedia(data);
112
110
  if (!media) {
113
- logger.warn(formatCompact({
111
+ this.#logger.warn(formatCompact({
114
112
  op: 'wecom_outbound_media_dropped',
115
- endpoint: this.#options.config.name,
113
+ endpoint: this.#options.config.id,
116
114
  type: 'image',
117
115
  reason: 'missing_media_ref',
118
116
  }));
@@ -126,9 +124,9 @@ export class WecomEndpoint {
126
124
  return { type: 'image', data: { media_id: mediaId } };
127
125
  }
128
126
  catch (error) {
129
- logger.warn(formatCompact({
127
+ this.#logger.warn(formatCompact({
130
128
  op: 'wecom_media_upload_failed',
131
- endpoint: this.#options.config.name,
129
+ endpoint: this.#options.config.id,
132
130
  error: error instanceof Error ? error.message : String(error),
133
131
  }));
134
132
  const alt = typeof data.alt === 'string' && data.alt ? data.alt : '[image]';
@@ -159,17 +157,17 @@ export class WecomEndpoint {
159
157
  conversation,
160
158
  message: { conversation, id: msg.MsgId || `${msg.CreateTime}` },
161
159
  content: formatInboundContent(msg),
162
- sender: msg.FromUserName,
160
+ sender: { id: msg.FromUserName },
161
+ endpointId: this.#options.config.id,
163
162
  metadata: Object.freeze({
164
163
  msgType: msg.MsgType,
165
164
  event: msg.Event,
166
165
  chatType,
167
- endpoint: this.#options.config.name,
168
166
  toUserName: msg.ToUserName,
169
167
  agentId: msg.AgentID,
170
168
  }),
171
169
  }).catch((err) => {
172
- logger.warn(formatCompact({
170
+ this.#logger.warn(formatCompact({
173
171
  op: 'wecom_gateway_receive_failed',
174
172
  target: `${conversation.kind}:${conversation.id}`,
175
173
  error: err instanceof Error ? err.message : String(err),
@@ -186,7 +184,7 @@ export class WecomEndpoint {
186
184
  throw new Error(`Failed to get user info: ${data.errmsg}`);
187
185
  }
188
186
  catch (error) {
189
- logger.error('Failed to get user info:', error);
187
+ this.#logger.error('Failed to get user info:', error);
190
188
  return null;
191
189
  }
192
190
  }
@@ -200,7 +198,7 @@ export class WecomEndpoint {
200
198
  throw new Error(`Failed to get department users: ${data.errmsg}`);
201
199
  }
202
200
  catch (error) {
203
- logger.error('Failed to get department users:', error);
201
+ this.#logger.error('Failed to get department users:', error);
204
202
  return [];
205
203
  }
206
204
  }
@@ -214,16 +212,16 @@ export class WecomEndpoint {
214
212
  throw new Error(`Failed to get department list: ${data.errmsg}`);
215
213
  }
216
214
  catch (error) {
217
- logger.error('Failed to get department list:', error);
215
+ this.#logger.error('Failed to get department list:', error);
218
216
  return [];
219
217
  }
220
218
  }
221
219
  async sendTextMessage(userId, content) {
222
220
  try {
223
- const endpointId = String(this.#options.id);
221
+ const endpointKey = String(this.#options.id);
224
222
  await this.send({
225
223
  conversation: {
226
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
224
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
227
225
  kind: resolveChatType(userId),
228
226
  id: userId,
229
227
  },
@@ -232,7 +230,7 @@ export class WecomEndpoint {
232
230
  return true;
233
231
  }
234
232
  catch (error) {
235
- logger.error('Failed to send text message:', error);
233
+ this.#logger.error('Failed to send text message:', error);
236
234
  return false;
237
235
  }
238
236
  }
@@ -281,7 +279,7 @@ export class WecomEndpoint {
281
279
  expires_in: data.expires_in ?? 7200,
282
280
  timestamp: Date.now(),
283
281
  };
284
- logger.debug('Access token refreshed successfully');
282
+ this.#logger.debug('Access token refreshed successfully');
285
283
  return;
286
284
  }
287
285
  throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { buildSendRequestBody, decryptMessage, extractEncryptFromXml, formatInboundContent, formatOutboundBody, getAesKey, normalizeEchostrParam, normalizeWebhookPath, parseXmlMessage, queryParam, readTextBody, resolveChatType, resolveWecomConfig, verifySignature, type AccessToken, type ResolvedWecomConfig, type WecomAdapterConfig, type WecomApiResponse, type WecomMessage, type WecomSendBody, type WecomWireSegment, } from './protocol.js';
2
2
  export { getWecomAgentDeps, registerWecomAgentEndpoint, setWecomAgentDeps, type WecomAgentDeps, type WecomAgentEndpoint, } from './wecom-agent-deps.js';
3
- export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit, registerWecomPlatformPermitChecker, wecomGroupPermitResolver, } from './platform-permit.js';
3
+ export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit, wecomGroupPermitResolver, } from './platform-permit.js';
4
4
  export { WecomEndpoint, type WecomEndpointOptions, type WecomFetch, } from './endpoint.js';
5
5
  export { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, type MediaBinary, type WecomMediaUploadResult, } from './media-upload.js';
6
6
  export { registerWecomWebhookRoutes, handleWecomVerificationRequest, handleWecomWebhookRequest, type WecomWebhookHandler, } from './webhook.js';
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { buildSendRequestBody, decryptMessage, extractEncryptFromXml, formatInboundContent, formatOutboundBody, getAesKey, normalizeEchostrParam, normalizeWebhookPath, parseXmlMessage, queryParam, readTextBody, resolveChatType, resolveWecomConfig, verifySignature, } from './protocol.js';
2
2
  export { getWecomAgentDeps, registerWecomAgentEndpoint, setWecomAgentDeps, } from './wecom-agent-deps.js';
3
- export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit, registerWecomPlatformPermitChecker, wecomGroupPermitResolver, } from './platform-permit.js';
3
+ export { checkWecomPlatformPermit, normalizeWecomSenderForPermit, platformPermit, wecomGroupPermitResolver, } from './platform-permit.js';
4
4
  export { WecomEndpoint, } from './endpoint.js';
5
5
  export { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, } from './media-upload.js';
6
6
  export { registerWecomWebhookRoutes, handleWecomVerificationRequest, handleWecomWebhookRequest, } from './webhook.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 企业微信 WeCom 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 wecomGroupPermitResolver(logicalPerm: string): string;
7
7
  export declare function normalizeWecomSenderForPermit(input: {
@@ -11,5 +11,4 @@ export declare function normalizeWecomSenderForPermit(input: {
11
11
  role?: string;
12
12
  permissions?: string[];
13
13
  };
14
- export declare function checkWecomPlatformPermit(perm: string, message: Message<any>): boolean;
15
- export declare function registerWecomPlatformPermitChecker(): () => void;
14
+ export declare function checkWecomPlatformPermit(perm: string, subject: PermissionSubject): boolean;
@@ -1,7 +1,3 @@
1
- /**
2
- * 企业微信 WeCom platform permit
3
- */
4
- import { registerPlatformPermitChecker } from '@zhin.js/core';
5
1
  const ADAPTER = 'wecom';
6
2
  export function platformPermit(perm) {
7
3
  return `platform(${ADAPTER},${perm})`;
@@ -22,10 +18,9 @@ export function normalizeWecomSenderForPermit(input) {
22
18
  }
23
19
  return { role: 'member', permissions: [] };
24
20
  }
25
- export function checkWecomPlatformPermit(perm, message) {
26
- const sender = message.$sender;
27
- const permissions = sender.permissions ?? [];
28
- const role = sender.role;
21
+ export function checkWecomPlatformPermit(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 checkWecomPlatformPermit(perm, message) {
36
31
  return false;
37
32
  }
38
33
  }
39
- export function registerWecomPlatformPermitChecker() {
40
- return registerPlatformPermitChecker(ADAPTER, checkWecomPlatformPermit);
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 WecomAdapterConfig {
9
- readonly name?: string;
9
+ readonly id?: string;
10
10
  readonly corpId?: string;
11
11
  readonly agentSecret?: string;
12
12
  readonly token?: string;
@@ -20,7 +20,7 @@ export interface WecomAdapterConfig {
20
20
  }
21
21
  export interface ResolvedWecomConfig {
22
22
  readonly context: 'wecom';
23
- readonly name: string;
23
+ readonly id: string;
24
24
  readonly corpId: string;
25
25
  readonly agentSecret: string;
26
26
  readonly token: string;
@@ -91,7 +91,7 @@ export declare function resolveChatType(fromUserName: string): 'group' | 'privat
91
91
  * 入站归一化 → ConversationRef:WeCom 无 guild/channel 容器概念,
92
92
  * `@chatroom` 群会话 → kind 'group',其余(应用消息/单聊)→ kind 'private'。
93
93
  */
94
- export declare function wecomInboundConversation(endpointId: string, msg: WecomMessage): ConversationRef;
94
+ export declare function wecomInboundConversation(endpointKey: string, msg: WecomMessage): ConversationRef;
95
95
  /**
96
96
  * Wire-encode an already-rendered outbound payload into WeCom message/send body parts.
97
97
  */
package/lib/protocol.js CHANGED
@@ -14,13 +14,13 @@ export function resolveWecomConfig(config = {}) {
14
14
  if (!corpId || !agentSecret || !token || !encodingAESKey) {
15
15
  throw new TypeError('WeCom adapter requires corpId + agentSecret + token + encodingAESKey (plugins.<key> or endpoints with context: wecom)');
16
16
  }
17
- const name = (typeof config.name === 'string' && config.name)
18
- || (typeof entry?.name === 'string' && entry.name)
17
+ const id = (typeof config.id === 'string' && config.id)
18
+ || (typeof entry?.id === 'string' && entry.id)
19
19
  || process.env.WECOM_BOT_NAME
20
20
  || 'wecom-bot';
21
21
  return {
22
22
  context: 'wecom',
23
- name,
23
+ id,
24
24
  corpId,
25
25
  agentSecret,
26
26
  token,
@@ -159,9 +159,9 @@ export function resolveChatType(fromUserName) {
159
159
  * 入站归一化 → ConversationRef:WeCom 无 guild/channel 容器概念,
160
160
  * `@chatroom` 群会话 → kind 'group',其余(应用消息/单聊)→ kind 'private'。
161
161
  */
162
- export function wecomInboundConversation(endpointId, msg) {
162
+ export function wecomInboundConversation(endpointKey, msg) {
163
163
  return {
164
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
164
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
165
165
  kind: resolveChatType(msg.FromUserName),
166
166
  id: msg.FromUserName,
167
167
  };
@@ -9,9 +9,9 @@ export interface WecomAgentEndpoint {
9
9
  sendTextMessage(userId: string, content: string): Promise<boolean>;
10
10
  }
11
11
  export interface WecomAgentDeps {
12
- getEndpoint: (endpointId: string) => WecomAgentEndpoint;
12
+ getEndpoint: (endpointKey: string) => WecomAgentEndpoint;
13
13
  }
14
- export declare function registerWecomAgentEndpoint(endpointId: string, endpoint: WecomAgentEndpoint): () => void;
14
+ export declare function registerWecomAgentEndpoint(endpointKey: string, endpoint: WecomAgentEndpoint): () => void;
15
15
  /** Optional override used by tests / transitional callers. Pass `null` to clear. */
16
16
  export declare function setWecomAgentDeps(deps: WecomAgentDeps | null): void;
17
17
  export declare function getWecomAgentDeps(): WecomAgentDeps;
@@ -4,11 +4,11 @@
4
4
  */
5
5
  const endpoints = new Map();
6
6
  let override = null;
7
- export function registerWecomAgentEndpoint(endpointId, endpoint) {
8
- endpoints.set(endpointId, endpoint);
7
+ export function registerWecomAgentEndpoint(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 getWecomAgentDeps() {
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-wecom",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "description": "Zhin.js WeCom (企业微信) adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -34,22 +34,23 @@
34
34
  "directory": "plugins/adapters/wecom"
35
35
  },
36
36
  "dependencies": {
37
- "@zhin.js/adapter": "1.1.5",
38
- "@zhin.js/command": "1.0.7",
39
- "@zhin.js/core": "1.5.2",
40
- "@zhin.js/host-http": "1.0.6",
41
- "@zhin.js/im-contract": "1.0.1",
42
- "@zhin.js/logger": "1.0.75",
43
- "@zhin.js/plugin-runtime": "1.1.3"
37
+ "@zhin.js/adapter": "1.1.7",
38
+ "@zhin.js/command": "1.0.9",
39
+ "@zhin.js/core": "1.5.4",
40
+ "@zhin.js/host-http": "1.0.8",
41
+ "@zhin.js/im-contract": "1.0.3",
42
+ "@zhin.js/logger": "1.0.76",
43
+ "@zhin.js/plugin-runtime": "1.1.5"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "zod": "^4.0.0",
47
- "@zhin.js/adapter": "1.1.5",
48
- "@zhin.js/agent": "1.1.3",
49
- "@zhin.js/core": "1.5.2",
50
- "@zhin.js/host-http": "1.0.6",
51
- "@zhin.js/plugin-runtime": "1.1.3",
52
- "zhin.js": "6.0.2"
47
+ "@zhin.js/adapter": "1.1.7",
48
+ "@zhin.js/agent": "1.1.5",
49
+ "@zhin.js/core": "1.5.4",
50
+ "@zhin.js/host-http": "1.0.8",
51
+ "@zhin.js/permission": "1.0.1",
52
+ "@zhin.js/plugin-runtime": "1.1.5",
53
+ "zhin.js": "6.0.4"
53
54
  },
54
55
  "peerDependenciesMeta": {
55
56
  "zhin.js": {
@@ -67,8 +68,8 @@
67
68
  "typescript": "^6.0.3",
68
69
  "vitest": "^4.1.10",
69
70
  "zod": "^4.4.3",
70
- "@zhin.js/agent": "1.1.3",
71
- "zhin.js": "6.0.2"
71
+ "@zhin.js/agent": "1.1.5",
72
+ "zhin.js": "6.0.4"
72
73
  },
73
74
  "files": [
74
75
  "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 { registerWecomPlatformPermitChecker } from "./lib/platform-permit.js";
4
+ import { permissionHostToken } from '@zhin.js/permission';
5
+ import { checkWecomPlatformPermit } from "./lib/platform-permit.js";
5
6
  import { wecomRuntimeStateToken } from "./lib/wecom-runtime-state.js";
6
7
  export default definePlugin({
7
8
  name: 'wecom',
@@ -9,8 +10,10 @@ export default definePlugin({
9
10
  displayName: 'WeCom (企业微信) Adapter',
10
11
  },
11
12
  setup(context) {
12
- // 运行中 endpoint 注册表(wecom.endpoint list 的"运行中"数据源)
13
13
  context.resources.provide(wecomRuntimeStateToken, createEndpointRuntimeState());
14
- return registerWecomPlatformPermitChecker();
14
+ if (context.resources.has(permissionHostToken)) {
15
+ const host = context.resources.use(permissionHostToken);
16
+ return host.registerPlatform('wecom', checkWecomPlatformPermit);
17
+ }
15
18
  },
16
19
  });
package/schema.json CHANGED
@@ -11,16 +11,48 @@
11
11
  "type": "string",
12
12
  "default": "https://qyapi.weixin.qq.com"
13
13
  },
14
+ "master": {
15
+ "type": [
16
+ "string",
17
+ "number"
18
+ ],
19
+ "description": "框架 master(WeCom userid;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
20
+ },
21
+ "trusted": {
22
+ "type": "array",
23
+ "items": {
24
+ "type": [
25
+ "string",
26
+ "number"
27
+ ],
28
+ "description": "Trusted WeCom userid"
29
+ },
30
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
31
+ },
14
32
  "endpoints": {
15
33
  "type": "array",
16
- "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
34
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
17
35
  "items": {
18
36
  "type": "object",
19
37
  "additionalProperties": true,
20
38
  "properties": {
21
- "name": {
22
- "type": "string",
23
- "description": "WeCom bot name"
39
+ "master": {
40
+ "type": [
41
+ "string",
42
+ "number"
43
+ ],
44
+ "description": "本 endpoint 的框架 master(WeCom userid);覆盖顶层 master"
45
+ },
46
+ "trusted": {
47
+ "type": "array",
48
+ "items": {
49
+ "type": [
50
+ "string",
51
+ "number"
52
+ ],
53
+ "description": "Trusted WeCom userid"
54
+ },
55
+ "description": "本 endpoint 的 trusted 列表"
24
56
  },
25
57
  "corpId": {
26
58
  "type": "string",
@@ -37,10 +69,14 @@
37
69
  "encodingAESKey": {
38
70
  "type": "string",
39
71
  "description": "WeCom encoding AES key"
72
+ },
73
+ "id": {
74
+ "type": "string",
75
+ "description": "WeCom bot name"
40
76
  }
41
77
  },
42
78
  "required": [
43
- "name",
79
+ "id",
44
80
  "corpId",
45
81
  "agentSecret",
46
82
  "token",
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 { registerWecomAgentEndpoint } from './wecom-agent-deps.js';
10
10
  import {
@@ -26,8 +26,6 @@ import {
26
26
  } from './protocol.js';
27
27
  import { registerWecomWebhookRoutes } from './webhook.js';
28
28
 
29
- const logger = getLogger('wecom');
30
-
31
29
  export type WecomFetch = (
32
30
  url: string,
33
31
  init?: {
@@ -56,6 +54,8 @@ export interface WecomEndpointOptions {
56
54
  * 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
57
55
  */
58
56
  export class WecomEndpoint implements EndpointInstance {
57
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
58
+
59
59
  readonly #options: WecomEndpointOptions;
60
60
  readonly #fetch: WecomFetch;
61
61
  #routeReleases: HttpRouteRegistration[] = [];
@@ -66,6 +66,7 @@ export class WecomEndpoint implements EndpointInstance {
66
66
  #unregisterAgent?: () => void;
67
67
 
68
68
  constructor(options: WecomEndpointOptions) {
69
+ this.#logger = getAdapterLogger('wecom', options.config.id);
69
70
  this.#options = options;
70
71
  this.#fetch = options.fetch ?? globalThis.fetch;
71
72
  }
@@ -84,16 +85,16 @@ export class WecomEndpoint implements EndpointInstance {
84
85
  this.#started = true;
85
86
  try {
86
87
  await this.#refreshAccessToken();
87
- this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.name, this);
88
+ this.#unregisterAgent = registerWecomAgentEndpoint(this.#options.config.id, this);
88
89
  this.#routeReleases.push(...registerWecomWebhookRoutes(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 WeCom endpoint:', error);
97
+ this.#logger.error('Failed to connect WeCom endpoint:', error);
97
98
  throw error;
98
99
  }
99
100
  }
@@ -112,7 +113,7 @@ export class WecomEndpoint implements EndpointInstance {
112
113
  this.#unregisterAgent?.();
113
114
  this.#unregisterAgent = undefined;
114
115
  this.#started = false;
115
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
116
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
116
117
  }
117
118
 
118
119
  async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
@@ -131,10 +132,7 @@ export class WecomEndpoint implements EndpointInstance {
131
132
  if (data.errcode !== 0) {
132
133
  throw new Error(`Failed to send message: ${data.errmsg} (${data.errcode})`);
133
134
  }
134
- logger.debug(formatCompact({
135
- op: 'send',
136
- endpoint: this.#options.config.name,
137
- to: `${conversation.kind}:${conversation.id}`,
135
+ this.#logger.debug(formatCompact({ op: 'send', to: `${conversation.kind}:${conversation.id}`,
138
136
  }));
139
137
  return (data.msgid as string) || `${Date.now()}`;
140
138
  }
@@ -162,9 +160,9 @@ export class WecomEndpoint implements EndpointInstance {
162
160
  const data = seg.data ?? {};
163
161
  const media = readOutboundImageMedia(data);
164
162
  if (!media) {
165
- logger.warn(formatCompact({
163
+ this.#logger.warn(formatCompact({
166
164
  op: 'wecom_outbound_media_dropped',
167
- endpoint: this.#options.config.name,
165
+ endpoint: this.#options.config.id,
168
166
  type: 'image',
169
167
  reason: 'missing_media_ref',
170
168
  }));
@@ -177,9 +175,9 @@ export class WecomEndpoint implements EndpointInstance {
177
175
  const mediaId = await this.#uploadMedia('image', media);
178
176
  return { type: 'image', data: { media_id: mediaId } };
179
177
  } catch (error) {
180
- logger.warn(formatCompact({
178
+ this.#logger.warn(formatCompact({
181
179
  op: 'wecom_media_upload_failed',
182
- endpoint: this.#options.config.name,
180
+ endpoint: this.#options.config.id,
183
181
  error: error instanceof Error ? error.message : String(error),
184
182
  }));
185
183
  const alt = typeof data.alt === 'string' && data.alt ? data.alt : '[image]';
@@ -210,17 +208,17 @@ export class WecomEndpoint implements EndpointInstance {
210
208
  conversation,
211
209
  message: { conversation, id: msg.MsgId || `${msg.CreateTime}` },
212
210
  content: formatInboundContent(msg),
213
- sender: msg.FromUserName,
211
+ sender: { id: msg.FromUserName },
212
+ endpointId: this.#options.config.id,
214
213
  metadata: Object.freeze({
215
214
  msgType: msg.MsgType,
216
215
  event: msg.Event,
217
216
  chatType,
218
- endpoint: this.#options.config.name,
219
217
  toUserName: msg.ToUserName,
220
218
  agentId: msg.AgentID,
221
219
  }),
222
220
  }).catch((err) => {
223
- logger.warn(formatCompact({
221
+ this.#logger.warn(formatCompact({
224
222
  op: 'wecom_gateway_receive_failed',
225
223
  target: `${conversation.kind}:${conversation.id}`,
226
224
  error: err instanceof Error ? err.message : String(err),
@@ -236,7 +234,7 @@ export class WecomEndpoint implements EndpointInstance {
236
234
  if (data.errcode === 0) return data;
237
235
  throw new Error(`Failed to get user info: ${data.errmsg}`);
238
236
  } catch (error) {
239
- logger.error('Failed to get user info:', error);
237
+ this.#logger.error('Failed to get user info:', error);
240
238
  return null;
241
239
  }
242
240
  }
@@ -249,7 +247,7 @@ export class WecomEndpoint implements EndpointInstance {
249
247
  if (data.errcode === 0) return (data.userlist as unknown[]) || [];
250
248
  throw new Error(`Failed to get department users: ${data.errmsg}`);
251
249
  } catch (error) {
252
- logger.error('Failed to get department users:', error);
250
+ this.#logger.error('Failed to get department users:', error);
253
251
  return [];
254
252
  }
255
253
  }
@@ -262,17 +260,17 @@ export class WecomEndpoint implements EndpointInstance {
262
260
  if (data.errcode === 0) return (data.department as unknown[]) || [];
263
261
  throw new Error(`Failed to get department list: ${data.errmsg}`);
264
262
  } catch (error) {
265
- logger.error('Failed to get department list:', error);
263
+ this.#logger.error('Failed to get department list:', error);
266
264
  return [];
267
265
  }
268
266
  }
269
267
 
270
268
  async sendTextMessage(userId: string, content: string): Promise<boolean> {
271
269
  try {
272
- const endpointId = String(this.#options.id);
270
+ const endpointKey = String(this.#options.id);
273
271
  await this.send({
274
272
  conversation: {
275
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
273
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
276
274
  kind: resolveChatType(userId),
277
275
  id: userId,
278
276
  },
@@ -280,7 +278,7 @@ export class WecomEndpoint implements EndpointInstance {
280
278
  });
281
279
  return true;
282
280
  } catch (error) {
283
- logger.error('Failed to send text message:', error);
281
+ this.#logger.error('Failed to send text message:', error);
284
282
  return false;
285
283
  }
286
284
  }
@@ -343,7 +341,7 @@ export class WecomEndpoint implements EndpointInstance {
343
341
  expires_in: data.expires_in ?? 7200,
344
342
  timestamp: Date.now(),
345
343
  };
346
- logger.debug('Access token refreshed successfully');
344
+ this.#logger.debug('Access token refreshed successfully');
347
345
  return;
348
346
  }
349
347
  throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
package/src/index.ts CHANGED
@@ -34,7 +34,6 @@ export {
34
34
  checkWecomPlatformPermit,
35
35
  normalizeWecomSenderForPermit,
36
36
  platformPermit,
37
- registerWecomPlatformPermitChecker,
38
37
  wecomGroupPermitResolver,
39
38
  } from './platform-permit.js';
40
39
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 企业微信 WeCom 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 = 'wecom';
7
7
 
@@ -31,10 +31,9 @@ export function normalizeWecomSenderForPermit(input: {
31
31
  return { role: 'member', permissions: [] };
32
32
  }
33
33
 
34
- export function checkWecomPlatformPermit(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 checkWecomPlatformPermit(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 checkWecomPlatformPermit(perm: string, message: Message<any>): b
47
46
  }
48
47
  }
49
48
 
50
- export function registerWecomPlatformPermitChecker(): () => void {
51
- return registerPlatformPermitChecker(ADAPTER, checkWecomPlatformPermit);
52
- }
package/src/protocol.ts CHANGED
@@ -9,7 +9,7 @@ import type { ConversationRef } from '@zhin.js/im-contract';
9
9
 
10
10
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
11
11
  export interface WecomAdapterConfig {
12
- readonly name?: string;
12
+ readonly id?: string;
13
13
  readonly corpId?: string;
14
14
  readonly agentSecret?: string;
15
15
  readonly token?: string;
@@ -24,7 +24,7 @@ export interface WecomAdapterConfig {
24
24
 
25
25
  export interface ResolvedWecomConfig {
26
26
  readonly context: 'wecom';
27
- readonly name: string;
27
+ readonly id: string;
28
28
  readonly corpId: string;
29
29
  readonly agentSecret: string;
30
30
  readonly token: string;
@@ -97,13 +97,13 @@ export function resolveWecomConfig(config: WecomAdapterConfig = {}): ResolvedWec
97
97
  'WeCom adapter requires corpId + agentSecret + token + encodingAESKey (plugins.<key> or endpoints with context: wecom)',
98
98
  );
99
99
  }
100
- const name = (typeof config.name === 'string' && config.name)
101
- || (typeof entry?.name === 'string' && entry.name)
100
+ const id = (typeof config.id === 'string' && config.id)
101
+ || (typeof entry?.id === 'string' && entry.id)
102
102
  || process.env.WECOM_BOT_NAME
103
103
  || 'wecom-bot';
104
104
  return {
105
105
  context: 'wecom',
106
- name,
106
+ id,
107
107
  corpId,
108
108
  agentSecret,
109
109
  token,
@@ -260,9 +260,9 @@ export function resolveChatType(fromUserName: string): 'group' | 'private' {
260
260
  * 入站归一化 → ConversationRef:WeCom 无 guild/channel 容器概念,
261
261
  * `@chatroom` 群会话 → kind 'group',其余(应用消息/单聊)→ kind 'private'。
262
262
  */
263
- export function wecomInboundConversation(endpointId: string, msg: WecomMessage): ConversationRef {
263
+ export function wecomInboundConversation(endpointKey: string, msg: WecomMessage): ConversationRef {
264
264
  return {
265
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
265
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
266
266
  kind: resolveChatType(msg.FromUserName),
267
267
  id: msg.FromUserName,
268
268
  };
@@ -11,20 +11,20 @@ export interface WecomAgentEndpoint {
11
11
  }
12
12
 
13
13
  export interface WecomAgentDeps {
14
- getEndpoint: (endpointId: string) => WecomAgentEndpoint;
14
+ getEndpoint: (endpointKey: string) => WecomAgentEndpoint;
15
15
  }
16
16
 
17
17
  const endpoints = new Map<string, WecomAgentEndpoint>();
18
18
  let override: WecomAgentDeps | null = null;
19
19
 
20
20
  export function registerWecomAgentEndpoint(
21
- endpointId: string,
21
+ endpointKey: string,
22
22
  endpoint: WecomAgentEndpoint,
23
23
  ): () => void {
24
- endpoints.set(endpointId, endpoint);
24
+ endpoints.set(endpointKey, endpoint);
25
25
  return () => {
26
- if (endpoints.get(endpointId) === endpoint) {
27
- endpoints.delete(endpointId);
26
+ if (endpoints.get(endpointKey) === endpoint) {
27
+ endpoints.delete(endpointKey);
28
28
  }
29
29
  };
30
30
  }
@@ -37,9 +37,9 @@ export function setWecomAgentDeps(deps: WecomAgentDeps | null): void {
37
37
  export function getWecomAgentDeps(): WecomAgentDeps {
38
38
  if (override) return override;
39
39
  return {
40
- getEndpoint(endpointId) {
41
- const endpoint = endpoints.get(endpointId);
42
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
40
+ getEndpoint(endpointKey) {
41
+ const endpoint = endpoints.get(endpointKey);
42
+ if (!endpoint) throw new Error(`Endpoint ${endpointKey} 不存在`);
43
43
  return endpoint;
44
44
  },
45
45
  };
File without changes
File without changes
File without changes
File without changes