@zhin.js/adapter-wecom 4.0.2 → 4.0.3

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,21 @@
1
1
  # @zhin.js/adapter-wecom
2
2
 
3
+ ## 4.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [afc0e66]
8
+ - Updated dependencies [2e41ad5]
9
+ - Updated dependencies [9f57124]
10
+ - @zhin.js/core@1.5.2
11
+ - @zhin.js/adapter@1.1.5
12
+ - @zhin.js/im-contract@1.0.1
13
+ - @zhin.js/plugin-runtime@1.1.3
14
+ - @zhin.js/command@1.0.7
15
+ - @zhin.js/host-http@1.0.6
16
+ - @zhin.js/agent@1.1.3
17
+ - zhin.js@6.0.2
18
+
3
19
  ## 4.0.2
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -23,8 +23,8 @@ pnpm add @zhin.js/adapter-wecom
23
23
  - `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
24
24
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
25
25
 
26
- 入站:`gateway.receive({ adapter, target: FromUserName, content: text, sender, metadata })`
27
- 出站:`send({ target, payload })` → 企业微信 `message/send` API
26
+ 入站:`gateway.receive({ conversation, message: { conversation, id: MsgId }, content: text, sender, metadata })`(`conversation` 为 `ConversationRef`,`@chatroom` → kind `group`,其余 → kind `private`)
27
+ 出站:`send({ conversation, payload })` → 企业微信 `message/send` API
28
28
 
29
29
  入站 `metadata.mentioned`:**未接线**。企业微信应用消息回调的 XML 事件不含 mentions/@ 字段,回调里的 `ToUserName` 是 CorpID(企业 ID)而非可比较的 bot 用户 id,配置中也没有 bot id/name 可作可靠判据,故无法可靠识别 @ 机器人。
30
30
 
package/lib/endpoint.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WecomEndpoint — lifecycle, outbound send, inbound 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 WecomEndpoint 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
  recallMessage(messageId: string): Promise<void>;
46
43
  /** Test / internal: admit a parsed message when open (non-webhook path). */
47
44
  admit(msg: WecomMessage): void;
package/lib/endpoint.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { formatCompact, getLogger } from '@zhin.js/logger';
2
2
  import { registerWecomAgentEndpoint } from './wecom-agent-deps.js';
3
3
  import { buildMediaUploadForm, readOutboundImageMedia, resolveMediaBinary, } from './media-upload.js';
4
- import { buildSendRequestBody, formatInboundContent, formatOutboundBody, resolveChatType, } from './protocol.js';
4
+ import { buildSendRequestBody, formatInboundContent, formatOutboundBody, resolveChatType, wecomInboundConversation, } from './protocol.js';
5
5
  import { registerWecomWebhookRoutes } from './webhook.js';
6
6
  const logger = getLogger('wecom');
7
7
  /**
@@ -64,10 +64,10 @@ export class WecomEndpoint {
64
64
  this.#started = false;
65
65
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
66
66
  }
67
- async send({ target, payload }) {
67
+ async send({ conversation, payload }) {
68
68
  const materialized = await this.#materializeOutboundMedia(payload);
69
69
  const content = formatOutboundBody(materialized);
70
- const body = buildSendRequestBody(target, content,
70
+ const body = buildSendRequestBody(conversation.id, content,
71
71
  // Legacy field: historically written into agentid (preserved for cutover).
72
72
  this.#options.config.agentSecret);
73
73
  const data = await this.#request('/cgi-bin/message/send', {
@@ -77,7 +77,11 @@ export class WecomEndpoint {
77
77
  if (data.errcode !== 0) {
78
78
  throw new Error(`Failed to send message: ${data.errmsg} (${data.errcode})`);
79
79
  }
80
- logger.debug(formatCompact({ op: 'send', endpoint: this.#options.config.name, to: target }));
80
+ logger.debug(formatCompact({
81
+ op: 'send',
82
+ endpoint: this.#options.config.name,
83
+ to: `${conversation.kind}:${conversation.id}`,
84
+ }));
81
85
  return data.msgid || `${Date.now()}`;
82
86
  }
83
87
  async recallMessage(messageId) {
@@ -150,12 +154,12 @@ export class WecomEndpoint {
150
154
  if (!this.#open)
151
155
  return;
152
156
  const chatType = resolveChatType(msg.FromUserName);
157
+ const conversation = wecomInboundConversation(String(this.#options.id), msg);
153
158
  void this.#options.gateway.receive({
154
- adapter: this.#options.id,
155
- target: msg.FromUserName,
159
+ conversation,
160
+ message: { conversation, id: msg.MsgId || `${msg.CreateTime}` },
156
161
  content: formatInboundContent(msg),
157
162
  sender: msg.FromUserName,
158
- id: msg.MsgId || `${msg.CreateTime}`,
159
163
  metadata: Object.freeze({
160
164
  msgType: msg.MsgType,
161
165
  event: msg.Event,
@@ -167,7 +171,7 @@ export class WecomEndpoint {
167
171
  }).catch((err) => {
168
172
  logger.warn(formatCompact({
169
173
  op: 'wecom_gateway_receive_failed',
170
- target: msg.FromUserName,
174
+ target: `${conversation.kind}:${conversation.id}`,
171
175
  error: err instanceof Error ? err.message : String(err),
172
176
  }));
173
177
  });
@@ -216,7 +220,15 @@ export class WecomEndpoint {
216
220
  }
217
221
  async sendTextMessage(userId, content) {
218
222
  try {
219
- await this.send({ target: userId, payload: content });
223
+ const endpointId = String(this.#options.id);
224
+ await this.send({
225
+ conversation: {
226
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
227
+ kind: resolveChatType(userId),
228
+ id: userId,
229
+ },
230
+ payload: content,
231
+ });
220
232
  return true;
221
233
  }
222
234
  catch (error) {
package/lib/protocol.d.ts CHANGED
@@ -3,6 +3,7 @@
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 WecomAdapterConfig {
8
9
  readonly name?: string;
@@ -86,6 +87,11 @@ export declare function parseXmlMessage(xml: string): WecomMessage | null;
86
87
  /** Build inbound text for MessageGateway.receive. */
87
88
  export declare function formatInboundContent(msg: WecomMessage): string;
88
89
  export declare function resolveChatType(fromUserName: string): 'group' | 'private';
90
+ /**
91
+ * 入站归一化 → ConversationRef:WeCom 无 guild/channel 容器概念,
92
+ * `@chatroom` 群会话 → kind 'group',其余(应用消息/单聊)→ kind 'private'。
93
+ */
94
+ export declare function wecomInboundConversation(endpointId: string, msg: WecomMessage): ConversationRef;
89
95
  /**
90
96
  * Wire-encode an already-rendered outbound payload into WeCom message/send body parts.
91
97
  */
package/lib/protocol.js CHANGED
@@ -155,6 +155,17 @@ export function formatInboundContent(msg) {
155
155
  export function resolveChatType(fromUserName) {
156
156
  return fromUserName.endsWith('@chatroom') ? 'group' : 'private';
157
157
  }
158
+ /**
159
+ * 入站归一化 → ConversationRef:WeCom 无 guild/channel 容器概念,
160
+ * `@chatroom` 群会话 → kind 'group',其余(应用消息/单聊)→ kind 'private'。
161
+ */
162
+ export function wecomInboundConversation(endpointId, msg) {
163
+ return {
164
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
165
+ kind: resolveChatType(msg.FromUserName),
166
+ id: msg.FromUserName,
167
+ };
168
+ }
158
169
  /**
159
170
  * Wire-encode an already-rendered outbound payload into WeCom message/send body parts.
160
171
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-wecom",
3
- "version": "4.0.2",
3
+ "version": "4.0.3",
4
4
  "description": "Zhin.js WeCom (企业微信) adapter for Plugin Runtime (HTTP webhook)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -34,21 +34,22 @@
34
34
  "directory": "plugins/adapters/wecom"
35
35
  },
36
36
  "dependencies": {
37
- "@zhin.js/adapter": "1.1.4",
38
- "@zhin.js/command": "1.0.6",
39
- "@zhin.js/core": "1.5.1",
40
- "@zhin.js/host-http": "1.0.5",
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",
41
42
  "@zhin.js/logger": "1.0.75",
42
- "@zhin.js/plugin-runtime": "1.1.2"
43
+ "@zhin.js/plugin-runtime": "1.1.3"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "zod": "^4.0.0",
46
- "@zhin.js/adapter": "1.1.4",
47
- "@zhin.js/agent": "1.1.2",
48
- "@zhin.js/core": "1.5.1",
49
- "@zhin.js/host-http": "1.0.5",
50
- "@zhin.js/plugin-runtime": "1.1.2",
51
- "zhin.js": "6.0.1"
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"
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.2",
70
- "zhin.js": "6.0.1"
70
+ "@zhin.js/agent": "1.1.3",
71
+ "zhin.js": "6.0.2"
71
72
  },
72
73
  "files": [
73
74
  "adapters",
package/src/endpoint.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WecomEndpoint — lifecycle, outbound send, inbound 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
7
  import { formatCompact, getLogger } from '@zhin.js/logger';
@@ -18,6 +18,7 @@ import {
18
18
  formatInboundContent,
19
19
  formatOutboundBody,
20
20
  resolveChatType,
21
+ wecomInboundConversation,
21
22
  type AccessToken,
22
23
  type ResolvedWecomConfig,
23
24
  type WecomApiResponse,
@@ -114,11 +115,11 @@ export class WecomEndpoint implements EndpointInstance {
114
115
  logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
115
116
  }
116
117
 
117
- async send({ target, payload }: { readonly target: string; readonly payload: unknown }): Promise<string> {
118
+ async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
118
119
  const materialized = await this.#materializeOutboundMedia(payload);
119
120
  const content = formatOutboundBody(materialized);
120
121
  const body = buildSendRequestBody(
121
- target,
122
+ conversation.id,
122
123
  content,
123
124
  // Legacy field: historically written into agentid (preserved for cutover).
124
125
  this.#options.config.agentSecret,
@@ -130,7 +131,11 @@ export class WecomEndpoint implements EndpointInstance {
130
131
  if (data.errcode !== 0) {
131
132
  throw new Error(`Failed to send message: ${data.errmsg} (${data.errcode})`);
132
133
  }
133
- logger.debug(formatCompact({ op: 'send', endpoint: this.#options.config.name, to: target }));
134
+ logger.debug(formatCompact({
135
+ op: 'send',
136
+ endpoint: this.#options.config.name,
137
+ to: `${conversation.kind}:${conversation.id}`,
138
+ }));
134
139
  return (data.msgid as string) || `${Date.now()}`;
135
140
  }
136
141
 
@@ -200,12 +205,12 @@ export class WecomEndpoint implements EndpointInstance {
200
205
  admit(msg: WecomMessage): void {
201
206
  if (!this.#open) return;
202
207
  const chatType = resolveChatType(msg.FromUserName);
208
+ const conversation = wecomInboundConversation(String(this.#options.id), msg);
203
209
  void this.#options.gateway.receive({
204
- adapter: this.#options.id,
205
- target: msg.FromUserName,
210
+ conversation,
211
+ message: { conversation, id: msg.MsgId || `${msg.CreateTime}` },
206
212
  content: formatInboundContent(msg),
207
213
  sender: msg.FromUserName,
208
- id: msg.MsgId || `${msg.CreateTime}`,
209
214
  metadata: Object.freeze({
210
215
  msgType: msg.MsgType,
211
216
  event: msg.Event,
@@ -217,7 +222,7 @@ export class WecomEndpoint implements EndpointInstance {
217
222
  }).catch((err) => {
218
223
  logger.warn(formatCompact({
219
224
  op: 'wecom_gateway_receive_failed',
220
- target: msg.FromUserName,
225
+ target: `${conversation.kind}:${conversation.id}`,
221
226
  error: err instanceof Error ? err.message : String(err),
222
227
  }));
223
228
  });
@@ -264,7 +269,15 @@ export class WecomEndpoint implements EndpointInstance {
264
269
 
265
270
  async sendTextMessage(userId: string, content: string): Promise<boolean> {
266
271
  try {
267
- await this.send({ target: userId, payload: content });
272
+ const endpointId = String(this.#options.id);
273
+ await this.send({
274
+ conversation: {
275
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
276
+ kind: resolveChatType(userId),
277
+ id: userId,
278
+ },
279
+ payload: content,
280
+ });
268
281
  return true;
269
282
  } catch (error) {
270
283
  logger.error('Failed to send text message:', error);
package/src/protocol.ts CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { createHash, createDecipheriv, timingSafeEqual } from 'node:crypto';
7
7
  import type { IncomingMessage } from 'node:http';
8
+ import type { ConversationRef } from '@zhin.js/im-contract';
8
9
 
9
10
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
10
11
  export interface WecomAdapterConfig {
@@ -255,6 +256,18 @@ export function resolveChatType(fromUserName: string): 'group' | 'private' {
255
256
  return fromUserName.endsWith('@chatroom') ? 'group' : 'private';
256
257
  }
257
258
 
259
+ /**
260
+ * 入站归一化 → ConversationRef:WeCom 无 guild/channel 容器概念,
261
+ * `@chatroom` 群会话 → kind 'group',其余(应用消息/单聊)→ kind 'private'。
262
+ */
263
+ export function wecomInboundConversation(endpointId: string, msg: WecomMessage): ConversationRef {
264
+ return {
265
+ endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
266
+ kind: resolveChatType(msg.FromUserName),
267
+ id: msg.FromUserName,
268
+ };
269
+ }
270
+
258
271
  /**
259
272
  * Wire-encode an already-rendered outbound payload into WeCom message/send body parts.
260
273
  */