@zhin.js/adapter-weixin-ilink 5.0.2 → 5.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,52 @@
1
1
  # @zhin.js/adapter-weixin-ilink
2
2
 
3
+ ## 5.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [2d0a622]
8
+ - @zhin.js/command@1.0.10
9
+ - @zhin.js/adapter@1.1.7
10
+ - @zhin.js/core@1.5.5
11
+ - zhin.js@6.0.5
12
+
13
+ ## 5.0.4
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [c106ecc]
18
+ - Updated dependencies [ca92e03]
19
+ - Updated dependencies [b0f37ae]
20
+ - Updated dependencies [ba08a2f]
21
+ - Updated dependencies [daffd4c]
22
+ - Updated dependencies [36c7400]
23
+ - Updated dependencies [162fa34]
24
+ - Updated dependencies [e40b048]
25
+ - Updated dependencies [f1708c3]
26
+ - Updated dependencies [e53444f]
27
+ - Updated dependencies [92b0dd7]
28
+ - Updated dependencies [a7df753]
29
+ - @zhin.js/im-contract@1.0.3
30
+ - @zhin.js/adapter@1.1.7
31
+ - @zhin.js/plugin-runtime@1.1.5
32
+ - @zhin.js/command@1.0.9
33
+ - @zhin.js/core@1.5.4
34
+ - zhin.js@6.0.4
35
+
36
+ ## 5.0.3
37
+
38
+ ### Patch Changes
39
+
40
+ - f8c7a54: fix: im
41
+ - Updated dependencies [f8c7a54]
42
+ - @zhin.js/logger@1.0.76
43
+ - @zhin.js/adapter@1.1.6
44
+ - @zhin.js/command@1.0.8
45
+ - @zhin.js/core@1.5.3
46
+ - @zhin.js/im-contract@1.0.2
47
+ - @zhin.js/plugin-runtime@1.1.4
48
+ - zhin.js@6.0.3
49
+
3
50
  ## 5.0.2
4
51
 
5
52
  ### Patch Changes
package/README.md CHANGED
@@ -71,4 +71,3 @@ data/weixin-ilink/<bot-name>.json
71
71
  ## 文档
72
72
 
73
73
  - 适配器索引:[`plugins/adapters/README.md`](../README.md)
74
- - 原位迁移:[`docs/architecture/target-implementation/in-place-migration.md`](../../../docs/architecture/target-implementation/in-place-migration.md)
@@ -19,9 +19,10 @@ export default defineAdapter({
19
19
  create(context) {
20
20
  const config = resolveWeixinIlinkConfig(context.config);
21
21
  // 注册到插件运行时状态(weixin-ilink.endpoint list 的"运行中"数据源)
22
- context.use(weixinIlinkRuntimeStateToken).endpoints.set(config.name, {
23
- name: config.name,
24
- mode: config.connection,
22
+ context.use(weixinIlinkRuntimeStateToken).endpoints.set(config.id, {
23
+ id: config.id,
24
+ // iLink 仅长轮询一种接入方式,无 config.connection 字段
25
+ mode: 'long-poll',
25
26
  });
26
27
  return new WeixinIlinkEndpoint({
27
28
  id: context.id,
@@ -30,9 +30,10 @@ export default defineAdapter<WeixinIlinkAdapterConfig>({
30
30
  create(context) {
31
31
  const config = resolveWeixinIlinkConfig(context.config);
32
32
  // 注册到插件运行时状态(weixin-ilink.endpoint list 的"运行中"数据源)
33
- context.use(weixinIlinkRuntimeStateToken).endpoints.set(config.name, {
34
- name: config.name,
35
- mode: config.connection,
33
+ context.use(weixinIlinkRuntimeStateToken).endpoints.set(config.id, {
34
+ id: config.id,
35
+ // iLink 仅长轮询一种接入方式,无 config.connection 字段
36
+ mode: 'long-poll',
36
37
  });
37
38
  return new WeixinIlinkEndpoint({
38
39
  id: context.id,
@@ -6,9 +6,9 @@ export interface WeixinIlinkCredentials {
6
6
  updatedAt?: string;
7
7
  }
8
8
  export declare function resolveStateDir(): string;
9
- export declare function credentialsPath(endpointName: string): string;
10
- export declare function loadCredentials(endpointName: string): WeixinIlinkCredentials | null;
11
- export declare function saveCredentials(endpointName: string, creds: WeixinIlinkCredentials): void;
12
- export declare function syncBufPath(endpointName: string): string;
13
- export declare function loadSyncBuf(endpointName: string): string;
14
- export declare function saveSyncBuf(endpointName: string, buf: string): void;
9
+ export declare function credentialsPath(endpointId: string): string;
10
+ export declare function loadCredentials(endpointId: string): WeixinIlinkCredentials | null;
11
+ export declare function saveCredentials(endpointId: string, creds: WeixinIlinkCredentials): void;
12
+ export declare function syncBufPath(endpointId: string): string;
13
+ export declare function loadSyncBuf(endpointId: string): string;
14
+ export declare function saveSyncBuf(endpointId: string, buf: string): void;
@@ -6,11 +6,11 @@ export function resolveStateDir() {
6
6
  ? path.join(process.env.ZHIN_DATA_DIR, "weixin-ilink")
7
7
  : DEFAULT_DATA_DIR;
8
8
  }
9
- export function credentialsPath(endpointName) {
10
- return path.join(resolveStateDir(), `${endpointName}.json`);
9
+ export function credentialsPath(endpointId) {
10
+ return path.join(resolveStateDir(), `${endpointId}.json`);
11
11
  }
12
- export function loadCredentials(endpointName) {
13
- const file = credentialsPath(endpointName);
12
+ export function loadCredentials(endpointId) {
13
+ const file = credentialsPath(endpointId);
14
14
  try {
15
15
  if (!fs.existsSync(file))
16
16
  return null;
@@ -24,16 +24,16 @@ export function loadCredentials(endpointName) {
24
24
  return null;
25
25
  }
26
26
  }
27
- export function saveCredentials(endpointName, creds) {
28
- const file = credentialsPath(endpointName);
27
+ export function saveCredentials(endpointId, creds) {
28
+ const file = credentialsPath(endpointId);
29
29
  fs.mkdirSync(path.dirname(file), { recursive: true });
30
30
  fs.writeFileSync(file, JSON.stringify({ ...creds, updatedAt: new Date().toISOString() }, null, 2), "utf-8");
31
31
  }
32
- export function syncBufPath(endpointName) {
33
- return path.join(resolveStateDir(), `${endpointName}.sync-buf`);
32
+ export function syncBufPath(endpointId) {
33
+ return path.join(resolveStateDir(), `${endpointId}.sync-buf`);
34
34
  }
35
- export function loadSyncBuf(endpointName) {
36
- const file = syncBufPath(endpointName);
35
+ export function loadSyncBuf(endpointId) {
36
+ const file = syncBufPath(endpointId);
37
37
  try {
38
38
  return fs.existsSync(file) ? fs.readFileSync(file, "utf-8") : "";
39
39
  }
@@ -41,8 +41,8 @@ export function loadSyncBuf(endpointName) {
41
41
  return "";
42
42
  }
43
43
  }
44
- export function saveSyncBuf(endpointName, buf) {
45
- const file = syncBufPath(endpointName);
44
+ export function saveSyncBuf(endpointId, buf) {
45
+ const file = syncBufPath(endpointId);
46
46
  fs.mkdirSync(path.dirname(file), { recursive: true });
47
47
  fs.writeFileSync(file, buf, "utf-8");
48
48
  }
package/lib/endpoint.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import crypto from 'node:crypto';
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
- import { formatCompact, getLogger } from '@zhin.js/logger';
7
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
8
8
  import { getUpdates, notifyStart, notifyStop, sendTyping } from './ilink-api.js';
9
9
  import { configureIlinkMeta } from './ilink-meta.js';
10
10
  import { loadSyncBuf, resolveStateDir, saveSyncBuf, } from './credentials.js';
@@ -19,7 +19,6 @@ import { materializeOutboundMedia } from './outbound-media.js';
19
19
  import { MessageItemType } from './ilink-types.js';
20
20
  import { getExtensionFromMime, sniffMimeFromBuffer } from './mime.js';
21
21
  import { formatInboundContent, formatOutboundSegments, inboundMessageId, segmentLocalPath, sleep, weixinIlinkInboundConversation, } from './protocol.js';
22
- const logger = getLogger('weixin-ilink');
23
22
  const MAX_CONSECUTIVE_FAILURES = 3;
24
23
  const BACKOFF_DELAY_MS = 30_000;
25
24
  const RETRY_DELAY_MS = 2_000;
@@ -28,6 +27,7 @@ const WEIXIN_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
28
27
  const INBOUND_MEDIA_TTL_MS = 24 * 60 * 60 * 1000;
29
28
  const MEDIA_SWEEP_INTERVAL_MS = 60 * 60 * 1000;
30
29
  export class WeixinIlinkEndpoint {
30
+ #logger;
31
31
  #options;
32
32
  #resolveCredentials;
33
33
  #notifyStart;
@@ -44,6 +44,7 @@ export class WeixinIlinkEndpoint {
44
44
  #started = false;
45
45
  management = createWeixinIlinkEndpointManagement(this);
46
46
  constructor(options) {
47
+ this.#logger = getAdapterLogger('weixin-ilink', options.config.id);
47
48
  this.#options = options;
48
49
  this.#resolveCredentials = options.resolveCredentials ?? resolveCredentials;
49
50
  this.#notifyStart = options.notifyStart ?? notifyStart;
@@ -56,7 +57,7 @@ export class WeixinIlinkEndpoint {
56
57
  }
57
58
  /** Live endpoint 名(context token 按 account 名分桶,management 推导对端用)。 */
58
59
  get configName() {
59
- return this.#options.config.name;
60
+ return this.#options.config.id;
60
61
  }
61
62
  apiBaseUrl() {
62
63
  return this.#creds?.baseUrl ?? this.#options.config.baseUrl;
@@ -73,23 +74,23 @@ export class WeixinIlinkEndpoint {
73
74
  // QR 登录最长 8 分钟:stop() 必须能打断
74
75
  this.#loginAbort = new AbortController();
75
76
  this.#creds = await this.#resolveCredentials(this.#options.config, this.#loginAbort.signal);
76
- restoreContextTokens(this.#options.config.name);
77
+ restoreContextTokens(this.#options.config.id);
77
78
  await this.#notifyStart({
78
79
  baseUrl: this.apiBaseUrl(),
79
80
  token: this.#creds.botToken,
80
81
  });
81
- this.#configManager = new WeixinConfigManager({ baseUrl: this.apiBaseUrl(), token: this.#creds.botToken }, (msg) => logger.debug(msg));
82
+ this.#configManager = new WeixinConfigManager({ baseUrl: this.apiBaseUrl(), token: this.#creds.botToken }, (msg) => this.#logger.debug(msg));
82
83
  this.#pollAbort = new AbortController();
83
84
  this.#pollPromise = this.#pollLoop(this.#pollAbort.signal);
84
85
  this.#startMediaSweep();
85
- logger.info(formatCompact({
86
+ this.#logger.info(formatCompact({
86
87
  op: 'connect',
87
- endpoint: this.#options.config.name,
88
+ endpoint: this.#options.config.id,
88
89
  }));
89
90
  }
90
91
  catch (error) {
91
92
  await this.stop();
92
- logger.error('Failed to connect weixin-ilink bot:', error);
93
+ this.#logger.error('Failed to connect weixin-ilink bot:', error);
93
94
  throw error;
94
95
  }
95
96
  }
@@ -111,13 +112,13 @@ export class WeixinIlinkEndpoint {
111
112
  }
112
113
  this.#stopMediaSweep();
113
114
  // 防抖中的 context token 落盘,避免 stop 丢尾部写入
114
- flushContextTokenPersist(this.#options.config.name);
115
+ flushContextTokenPersist(this.#options.config.id);
115
116
  if (this.#creds?.botToken) {
116
117
  try {
117
118
  await this.#notifyStop({ baseUrl: this.apiBaseUrl(), token: this.#creds.botToken });
118
119
  }
119
120
  catch (err) {
120
- logger.warn(formatCompact({
121
+ this.#logger.warn(formatCompact({
121
122
  op: 'notify_stop',
122
123
  ok: false,
123
124
  error: err instanceof Error ? err.message : String(err),
@@ -125,9 +126,8 @@ export class WeixinIlinkEndpoint {
125
126
  }
126
127
  }
127
128
  this.#started = false;
128
- logger.debug(formatCompact({
129
+ this.#logger.debug(formatCompact({
129
130
  op: 'disconnect',
130
- endpoint: this.#options.config.name,
131
131
  }));
132
132
  }
133
133
  async send({ conversation, payload }) {
@@ -136,9 +136,9 @@ export class WeixinIlinkEndpoint {
136
136
  }
137
137
  // 个人微信仅 private 会话,对端 user_id 即 conversation.id
138
138
  const target = conversation.id;
139
- const contextToken = getContextToken(this.#options.config.name, target);
139
+ const contextToken = getContextToken(this.#options.config.id, target);
140
140
  if (!contextToken) {
141
- logger.warn(formatCompact({
141
+ this.#logger.warn(formatCompact({
142
142
  op: 'send',
143
143
  ok: false,
144
144
  reason: 'missing_context_token',
@@ -192,7 +192,7 @@ export class WeixinIlinkEndpoint {
192
192
  continue;
193
193
  }
194
194
  if (seg.type === 'image' || seg.type === 'video' || seg.type === 'file' || seg.type === 'record') {
195
- logger.warn(formatCompact({
195
+ this.#logger.warn(formatCompact({
196
196
  op: 'send',
197
197
  skip: 'no_local_file',
198
198
  type: seg.type,
@@ -215,21 +215,21 @@ export class WeixinIlinkEndpoint {
215
215
  return;
216
216
  const userId = msg.from_user_id ?? '';
217
217
  if (msg.context_token && userId) {
218
- setContextToken(this.#options.config.name, userId, msg.context_token);
218
+ setContextToken(this.#options.config.id, userId, msg.context_token);
219
219
  }
220
220
  const conversation = weixinIlinkInboundConversation(String(this.#options.id), userId);
221
221
  void this.#options.gateway.receive({
222
222
  conversation,
223
223
  message: { conversation, id: inboundMessageId(msg) },
224
224
  content: formatInboundContent(msg),
225
- sender: userId,
225
+ sender: { id: userId },
226
+ endpointId: this.#options.config.id,
226
227
  metadata: Object.freeze({
227
- endpoint: this.#options.config.name,
228
228
  messageType: msg.message_type,
229
229
  createTimeMs: msg.create_time_ms,
230
230
  }),
231
231
  }).catch((err) => {
232
- logger.warn(formatCompact({
232
+ this.#logger.warn(formatCompact({
233
233
  op: 'weixin_ilink_gateway_receive_failed',
234
234
  target: userId,
235
235
  error: err instanceof Error ? err.message : String(err),
@@ -239,10 +239,10 @@ export class WeixinIlinkEndpoint {
239
239
  async sendTypingToUser(userId, status) {
240
240
  if (!this.#creds?.botToken || !this.#configManager)
241
241
  return;
242
- const contextToken = getContextToken(this.#options.config.name, userId);
242
+ const contextToken = getContextToken(this.#options.config.id, userId);
243
243
  const cfg = await this.#configManager.getForUser(userId, contextToken);
244
244
  if (!cfg.typingTicket) {
245
- logger.debug(formatCompact({
245
+ this.#logger.debug(formatCompact({
246
246
  op: 'send_typing',
247
247
  skip: 'no_typing_ticket',
248
248
  userId,
@@ -262,13 +262,13 @@ export class WeixinIlinkEndpoint {
262
262
  async #pollLoop(abortSignal) {
263
263
  if (!this.#creds?.botToken)
264
264
  return;
265
- const endpointName = this.#options.config.name;
266
- let getUpdatesBuf = loadSyncBuf(endpointName);
265
+ const endpointId = this.#options.config.id;
266
+ let getUpdatesBuf = loadSyncBuf(endpointId);
267
267
  let nextTimeoutMs = this.#options.config.longPollTimeoutMs;
268
268
  let consecutiveFailures = 0;
269
269
  while (!abortSignal.aborted) {
270
- if (isSessionPaused(endpointName)) {
271
- await sleep(getRemainingPauseMs(endpointName), abortSignal);
270
+ if (isSessionPaused(endpointId)) {
271
+ await sleep(getRemainingPauseMs(endpointId), abortSignal);
272
272
  continue;
273
273
  }
274
274
  try {
@@ -287,9 +287,9 @@ export class WeixinIlinkEndpoint {
287
287
  if (isApiError) {
288
288
  const sessionExpired = resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
289
289
  if (sessionExpired) {
290
- pauseSession(endpointName);
290
+ pauseSession(endpointId);
291
291
  consecutiveFailures = 0;
292
- await sleep(getRemainingPauseMs(endpointName), abortSignal);
292
+ await sleep(getRemainingPauseMs(endpointId), abortSignal);
293
293
  continue;
294
294
  }
295
295
  consecutiveFailures += 1;
@@ -310,14 +310,14 @@ export class WeixinIlinkEndpoint {
310
310
  }
311
311
  if (nextBuf) {
312
312
  getUpdatesBuf = nextBuf;
313
- saveSyncBuf(endpointName, getUpdatesBuf);
313
+ saveSyncBuf(endpointId, getUpdatesBuf);
314
314
  }
315
315
  }
316
316
  catch (err) {
317
317
  if (abortSignal.aborted)
318
318
  return;
319
319
  consecutiveFailures += 1;
320
- logger.error(formatCompact({
320
+ this.#logger.error(formatCompact({
321
321
  op: 'poll',
322
322
  ok: false,
323
323
  error: err instanceof Error ? err.message : String(err),
@@ -334,13 +334,13 @@ export class WeixinIlinkEndpoint {
334
334
  async #handleInboundMessage(full) {
335
335
  const fromUserId = full.from_user_id ?? '';
336
336
  if (full.context_token && fromUserId) {
337
- setContextToken(this.#options.config.name, fromUserId, full.context_token);
337
+ setContextToken(this.#options.config.id, fromUserId, full.context_token);
338
338
  }
339
339
  const mediaOpts = await this.#downloadInboundMedia(full);
340
340
  this.admit({ ...full, _media: mediaOpts });
341
- logger.debug(formatCompact({
341
+ this.#logger.debug(formatCompact({
342
342
  op: 'recv',
343
- endpoint: this.#options.config.name,
343
+ endpoint: this.#options.config.id,
344
344
  target: fromUserId,
345
345
  preview: formatInboundContent({ ...full, _media: mediaOpts }).slice(0, 80),
346
346
  }));
@@ -356,8 +356,8 @@ export class WeixinIlinkEndpoint {
356
356
  return downloadMediaFromItem(mediaItem, {
357
357
  cdnBaseUrl: this.cdnBaseUrl(),
358
358
  saveMedia: (buffer, contentType, subdir, maxBytes, originalFilename) => this.#saveInboundMedia(buffer, contentType, subdir, maxBytes, originalFilename),
359
- log: (msg) => logger.debug(msg),
360
- errLog: (msg) => logger.warn(msg),
359
+ log: (msg) => this.#logger.debug(msg),
360
+ errLog: (msg) => this.#logger.warn(msg),
361
361
  label: `inbound:${full.from_user_id ?? 'unknown'}`,
362
362
  });
363
363
  }
@@ -414,9 +414,9 @@ export class WeixinIlinkEndpoint {
414
414
  if (now - stat.mtimeMs <= INBOUND_MEDIA_TTL_MS)
415
415
  continue;
416
416
  fs.unlinkSync(filePath);
417
- logger.debug(formatCompact({
417
+ this.#logger.debug(formatCompact({
418
418
  op: 'media_sweep',
419
- endpoint: this.#options.config.name,
419
+ endpoint: this.#options.config.id,
420
420
  file: entry.name,
421
421
  }));
422
422
  }
package/lib/login.js CHANGED
@@ -67,7 +67,7 @@ function sleepWithAbort(ms, signal) {
67
67
  export async function resolveCredentials(config, signal) {
68
68
  const envToken = process.env.WEIXIN_ILINK_TOKEN?.trim() || config.botToken?.trim();
69
69
  if (envToken) {
70
- const stored = loadCredentials(config.name);
70
+ const stored = loadCredentials(config.id);
71
71
  return {
72
72
  botToken: envToken,
73
73
  ilinkUserId: stored?.ilinkUserId,
@@ -75,7 +75,7 @@ export async function resolveCredentials(config, signal) {
75
75
  baseUrl: config.baseUrl ?? stored?.baseUrl ?? DEFAULT_API_BASE_URL,
76
76
  };
77
77
  }
78
- const stored = loadCredentials(config.name);
78
+ const stored = loadCredentials(config.id);
79
79
  if (stored?.botToken) {
80
80
  return {
81
81
  ...stored,
@@ -88,7 +88,7 @@ export async function loginWithQr(config, signal) {
88
88
  const apiBaseUrl = config.baseUrl ?? DEFAULT_API_BASE_URL;
89
89
  const qr = await fetchQRCode(apiBaseUrl, DEFAULT_ILINK_BOT_TYPE);
90
90
  const qrcodeUrl = qr.qrcode_img_content || qr.qrcode;
91
- logger.info(`weixin-ilink QR login for ${config.name}: scan with WeChat ClawBot entry — ${qrcodeUrl.slice(0, 120)}${qrcodeUrl.length > 120 ? '…' : ''}`);
91
+ logger.info(`weixin-ilink QR login for ${config.id}: scan with WeChat ClawBot entry — ${qrcodeUrl.slice(0, 120)}${qrcodeUrl.length > 120 ? '…' : ''}`);
92
92
  const deadline = Date.now() + LOGIN_TIMEOUT_MS;
93
93
  let currentBaseUrl = apiBaseUrl;
94
94
  while (Date.now() < deadline) {
@@ -106,7 +106,7 @@ export async function loginWithQr(config, signal) {
106
106
  throw new Error('扫码成功但未返回 bot_token');
107
107
  }
108
108
  const creds = {
109
- botToken: status.bot_token ?? loadCredentials(config.name)?.botToken ?? '',
109
+ botToken: status.bot_token ?? loadCredentials(config.id)?.botToken ?? '',
110
110
  ilinkUserId: status.ilink_user_id,
111
111
  ilinkBotId: status.ilink_endpoint_id,
112
112
  baseUrl: status.baseurl ?? currentBaseUrl,
@@ -114,7 +114,7 @@ export async function loginWithQr(config, signal) {
114
114
  if (!creds.botToken) {
115
115
  throw new Error('binded_redirect 但本地无有效 bot_token');
116
116
  }
117
- saveCredentials(config.name, creds);
117
+ saveCredentials(config.id, creds);
118
118
  return creds;
119
119
  }
120
120
  if (status.status === 'expired') {
package/lib/protocol.d.ts CHANGED
@@ -6,7 +6,7 @@ import type { ConversationRef } from '@zhin.js/im-contract';
6
6
  import type { WeixinMessage } from './ilink-types.js';
7
7
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
8
8
  export interface WeixinIlinkAdapterConfig {
9
- readonly name?: string;
9
+ readonly id?: string;
10
10
  readonly botAgent?: string;
11
11
  readonly baseUrl?: string;
12
12
  readonly cdnBaseUrl?: string;
@@ -19,7 +19,7 @@ export interface WeixinIlinkAdapterConfig {
19
19
  }
20
20
  export interface ResolvedWeixinIlinkConfig {
21
21
  readonly context: 'weixin-ilink';
22
- readonly name: string;
22
+ readonly id: string;
23
23
  readonly botAgent?: string;
24
24
  readonly baseUrl: string;
25
25
  readonly cdnBaseUrl: string;
@@ -48,7 +48,7 @@ export declare function inboundMessageId(msg: WeixinMessage): string;
48
48
  * 入站归一化 → ConversationRef:个人微信无群/频道概念,全部会话都是
49
49
  * 与 `from_user_id` 的 private 会话(无 parent)。
50
50
  */
51
- export declare function weixinIlinkInboundConversation(endpointId: string, userId: string): ConversationRef;
51
+ export declare function weixinIlinkInboundConversation(endpointKey: string, userId: string): ConversationRef;
52
52
  /**
53
53
  * Wire-encode an already-rendered outbound payload into Weixin wire segments.
54
54
  * Segment canonicalization is intentionally not done here.
package/lib/protocol.js CHANGED
@@ -9,8 +9,8 @@ import { DEFAULT_API_BASE_URL, DEFAULT_CDN_BASE_URL } from './ilink-meta.js';
9
9
  const DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;
10
10
  export function resolveWeixinIlinkConfig(config = {}) {
11
11
  const entry = config.endpoints?.find((item) => item.context === 'weixin-ilink');
12
- const name = (typeof config.name === 'string' && config.name)
13
- || (typeof entry?.name === 'string' && entry.name)
12
+ const id = (typeof config.id === 'string' && config.id)
13
+ || (typeof entry?.id === 'string' && entry.id)
14
14
  || process.env.WEIXIN_ILINK_BOT_NAME
15
15
  || 'weixin-ilink-bot';
16
16
  const botToken = pickCredential(typeof config.botToken === 'string' ? config.botToken.trim() : config.botToken, typeof entry?.botToken === 'string' ? entry.botToken.trim() : entry?.botToken, process.env.WEIXIN_ILINK_TOKEN?.trim());
@@ -18,7 +18,7 @@ export function resolveWeixinIlinkConfig(config = {}) {
18
18
  // (sidecar credentials file / QR login) during endpoint.start().
19
19
  return {
20
20
  context: 'weixin-ilink',
21
- name,
21
+ id,
22
22
  botAgent: config.botAgent ?? entry?.botAgent,
23
23
  baseUrl: config.baseUrl ?? entry?.baseUrl ?? DEFAULT_API_BASE_URL,
24
24
  cdnBaseUrl: config.cdnBaseUrl ?? entry?.cdnBaseUrl ?? DEFAULT_CDN_BASE_URL,
@@ -59,9 +59,9 @@ export function inboundMessageId(msg) {
59
59
  * 入站归一化 → ConversationRef:个人微信无群/频道概念,全部会话都是
60
60
  * 与 `from_user_id` 的 private 会话(无 parent)。
61
61
  */
62
- export function weixinIlinkInboundConversation(endpointId, userId) {
62
+ export function weixinIlinkInboundConversation(endpointKey, userId) {
63
63
  return {
64
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
64
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
65
65
  kind: 'private',
66
66
  id: userId,
67
67
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-weixin-ilink",
3
- "version": "5.0.2",
3
+ "version": "5.0.5",
4
4
  "type": "module",
5
5
  "description": "Zhin.js Weixin iLink (ClawBot) adapter for Plugin Runtime (long-poll)",
6
6
  "main": "./lib/index.js",
@@ -48,18 +48,18 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@zhin.js/adapter": "1.1.5",
52
- "@zhin.js/command": "1.0.7",
53
- "@zhin.js/core": "1.5.2",
54
- "@zhin.js/im-contract": "1.0.1",
55
- "@zhin.js/logger": "1.0.75",
56
- "@zhin.js/plugin-runtime": "1.1.3"
51
+ "@zhin.js/adapter": "1.1.7",
52
+ "@zhin.js/command": "1.0.10",
53
+ "@zhin.js/core": "1.5.5",
54
+ "@zhin.js/im-contract": "1.0.3",
55
+ "@zhin.js/logger": "1.0.76",
56
+ "@zhin.js/plugin-runtime": "1.1.5"
57
57
  },
58
58
  "peerDependencies": {
59
- "@zhin.js/adapter": "1.1.5",
60
- "@zhin.js/core": "1.5.2",
61
- "@zhin.js/plugin-runtime": "1.1.3",
62
- "zhin.js": "6.0.2"
59
+ "@zhin.js/adapter": "1.1.7",
60
+ "@zhin.js/core": "1.5.5",
61
+ "@zhin.js/plugin-runtime": "1.1.5",
62
+ "zhin.js": "6.0.5"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "zhin.js": {
package/schema.json CHANGED
@@ -18,24 +18,60 @@
18
18
  "type": "number",
19
19
  "default": 35000
20
20
  },
21
+ "master": {
22
+ "type": [
23
+ "string",
24
+ "number"
25
+ ],
26
+ "description": "框架 master(Weixin user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
27
+ },
28
+ "trusted": {
29
+ "type": "array",
30
+ "items": {
31
+ "type": [
32
+ "string",
33
+ "number"
34
+ ],
35
+ "description": "Trusted Weixin user id"
36
+ },
37
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
38
+ },
21
39
  "endpoints": {
22
40
  "type": "array",
23
- "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
41
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
24
42
  "items": {
25
43
  "type": "object",
26
44
  "additionalProperties": true,
27
45
  "properties": {
28
- "name": {
29
- "type": "string",
30
- "description": "Weixin iLink bot name"
46
+ "master": {
47
+ "type": [
48
+ "string",
49
+ "number"
50
+ ],
51
+ "description": "本 endpoint 的框架 master(Weixin user id);覆盖顶层 master"
52
+ },
53
+ "trusted": {
54
+ "type": "array",
55
+ "items": {
56
+ "type": [
57
+ "string",
58
+ "number"
59
+ ],
60
+ "description": "Trusted Weixin user id"
61
+ },
62
+ "description": "本 endpoint 的 trusted 列表"
31
63
  },
32
64
  "botToken": {
33
65
  "type": "string",
34
66
  "description": "iLink bot token (prefer env WEIXIN_ILINK_TOKEN or sidecar credential file)"
67
+ },
68
+ "id": {
69
+ "type": "string",
70
+ "description": "Weixin iLink bot name"
35
71
  }
36
72
  },
37
73
  "required": [
38
- "name",
74
+ "id",
39
75
  "botToken"
40
76
  ]
41
77
  }
@@ -17,12 +17,12 @@ export function resolveStateDir(): string {
17
17
  : DEFAULT_DATA_DIR;
18
18
  }
19
19
 
20
- export function credentialsPath(endpointName: string): string {
21
- return path.join(resolveStateDir(), `${endpointName}.json`);
20
+ export function credentialsPath(endpointId: string): string {
21
+ return path.join(resolveStateDir(), `${endpointId}.json`);
22
22
  }
23
23
 
24
- export function loadCredentials(endpointName: string): WeixinIlinkCredentials | null {
25
- const file = credentialsPath(endpointName);
24
+ export function loadCredentials(endpointId: string): WeixinIlinkCredentials | null {
25
+ const file = credentialsPath(endpointId);
26
26
  try {
27
27
  if (!fs.existsSync(file)) return null;
28
28
  const raw = fs.readFileSync(file, "utf-8");
@@ -34,8 +34,8 @@ export function loadCredentials(endpointName: string): WeixinIlinkCredentials |
34
34
  }
35
35
  }
36
36
 
37
- export function saveCredentials(endpointName: string, creds: WeixinIlinkCredentials): void {
38
- const file = credentialsPath(endpointName);
37
+ export function saveCredentials(endpointId: string, creds: WeixinIlinkCredentials): void {
38
+ const file = credentialsPath(endpointId);
39
39
  fs.mkdirSync(path.dirname(file), { recursive: true });
40
40
  fs.writeFileSync(
41
41
  file,
@@ -44,12 +44,12 @@ export function saveCredentials(endpointName: string, creds: WeixinIlinkCredenti
44
44
  );
45
45
  }
46
46
 
47
- export function syncBufPath(endpointName: string): string {
48
- return path.join(resolveStateDir(), `${endpointName}.sync-buf`);
47
+ export function syncBufPath(endpointId: string): string {
48
+ return path.join(resolveStateDir(), `${endpointId}.sync-buf`);
49
49
  }
50
50
 
51
- export function loadSyncBuf(endpointName: string): string {
52
- const file = syncBufPath(endpointName);
51
+ export function loadSyncBuf(endpointId: string): string {
52
+ const file = syncBufPath(endpointId);
53
53
  try {
54
54
  return fs.existsSync(file) ? fs.readFileSync(file, "utf-8") : "";
55
55
  } catch {
@@ -57,8 +57,8 @@ export function loadSyncBuf(endpointName: string): string {
57
57
  }
58
58
  }
59
59
 
60
- export function saveSyncBuf(endpointName: string, buf: string): void {
61
- const file = syncBufPath(endpointName);
60
+ export function saveSyncBuf(endpointId: string, buf: string): void {
61
+ const file = syncBufPath(endpointId);
62
62
  fs.mkdirSync(path.dirname(file), { recursive: true });
63
63
  fs.writeFileSync(file, buf, "utf-8");
64
64
  }
package/src/endpoint.ts CHANGED
@@ -11,7 +11,7 @@ import type {
11
11
  EndpointSendRequest,
12
12
  } from '@zhin.js/adapter';
13
13
  import type { MessageGateway } from '@zhin.js/core/runtime';
14
- import { formatCompact, getLogger } from '@zhin.js/logger';
14
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
15
15
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
16
16
  import { getUpdates, notifyStart, notifyStop, sendTyping } from './ilink-api.js';
17
17
  import { configureIlinkMeta } from './ilink-meta.js';
@@ -55,8 +55,6 @@ import {
55
55
  type WeixinWireSegment,
56
56
  } from './protocol.js';
57
57
 
58
- const logger = getLogger('weixin-ilink');
59
-
60
58
  const MAX_CONSECUTIVE_FAILURES = 3;
61
59
  const BACKOFF_DELAY_MS = 30_000;
62
60
  const RETRY_DELAY_MS = 2_000;
@@ -86,6 +84,8 @@ export interface WeixinIlinkEndpointOptions {
86
84
  }
87
85
 
88
86
  export class WeixinIlinkEndpoint implements EndpointInstance {
87
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
88
+
89
89
  readonly #options: WeixinIlinkEndpointOptions;
90
90
  readonly #resolveCredentials: (
91
91
  config: ResolvedWeixinIlinkConfig,
@@ -106,6 +106,7 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
106
106
  readonly management: EndpointManagement = createWeixinIlinkEndpointManagement(this);
107
107
 
108
108
  constructor(options: WeixinIlinkEndpointOptions) {
109
+ this.#logger = getAdapterLogger('weixin-ilink', options.config.id);
109
110
  this.#options = options;
110
111
  this.#resolveCredentials = options.resolveCredentials ?? resolveCredentials;
111
112
  this.#notifyStart = options.notifyStart ?? notifyStart;
@@ -120,7 +121,7 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
120
121
 
121
122
  /** Live endpoint 名(context token 按 account 名分桶,management 推导对端用)。 */
122
123
  get configName(): string {
123
- return this.#options.config.name;
124
+ return this.#options.config.id;
124
125
  }
125
126
 
126
127
  apiBaseUrl(): string {
@@ -139,7 +140,7 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
139
140
  // QR 登录最长 8 分钟:stop() 必须能打断
140
141
  this.#loginAbort = new AbortController();
141
142
  this.#creds = await this.#resolveCredentials(this.#options.config, this.#loginAbort.signal);
142
- restoreContextTokens(this.#options.config.name);
143
+ restoreContextTokens(this.#options.config.id);
143
144
 
144
145
  await this.#notifyStart({
145
146
  baseUrl: this.apiBaseUrl(),
@@ -148,19 +149,19 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
148
149
 
149
150
  this.#configManager = new WeixinConfigManager(
150
151
  { baseUrl: this.apiBaseUrl(), token: this.#creds.botToken },
151
- (msg) => logger.debug(msg),
152
+ (msg) => this.#logger.debug(msg),
152
153
  );
153
154
 
154
155
  this.#pollAbort = new AbortController();
155
156
  this.#pollPromise = this.#pollLoop(this.#pollAbort.signal);
156
157
  this.#startMediaSweep();
157
- logger.info(formatCompact({
158
+ this.#logger.info(formatCompact({
158
159
  op: 'connect',
159
- endpoint: this.#options.config.name,
160
+ endpoint: this.#options.config.id,
160
161
  }));
161
162
  } catch (error) {
162
163
  await this.stop();
163
- logger.error('Failed to connect weixin-ilink bot:', error);
164
+ this.#logger.error('Failed to connect weixin-ilink bot:', error);
164
165
  throw error;
165
166
  }
166
167
  }
@@ -184,12 +185,12 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
184
185
  }
185
186
  this.#stopMediaSweep();
186
187
  // 防抖中的 context token 落盘,避免 stop 丢尾部写入
187
- flushContextTokenPersist(this.#options.config.name);
188
+ flushContextTokenPersist(this.#options.config.id);
188
189
  if (this.#creds?.botToken) {
189
190
  try {
190
191
  await this.#notifyStop({ baseUrl: this.apiBaseUrl(), token: this.#creds.botToken });
191
192
  } catch (err) {
192
- logger.warn(formatCompact({
193
+ this.#logger.warn(formatCompact({
193
194
  op: 'notify_stop',
194
195
  ok: false,
195
196
  error: err instanceof Error ? err.message : String(err),
@@ -197,9 +198,8 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
197
198
  }
198
199
  }
199
200
  this.#started = false;
200
- logger.debug(formatCompact({
201
- op: 'disconnect',
202
- endpoint: this.#options.config.name,
201
+ this.#logger.debug(formatCompact({
202
+ op: 'disconnect',
203
203
  }));
204
204
  }
205
205
 
@@ -209,9 +209,9 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
209
209
  }
210
210
  // 个人微信仅 private 会话,对端 user_id 即 conversation.id
211
211
  const target = conversation.id;
212
- const contextToken = getContextToken(this.#options.config.name, target);
212
+ const contextToken = getContextToken(this.#options.config.id, target);
213
213
  if (!contextToken) {
214
- logger.warn(formatCompact({
214
+ this.#logger.warn(formatCompact({
215
215
  op: 'send',
216
216
  ok: false,
217
217
  reason: 'missing_context_token',
@@ -269,7 +269,7 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
269
269
  }
270
270
 
271
271
  if (seg.type === 'image' || seg.type === 'video' || seg.type === 'file' || seg.type === 'record') {
272
- logger.warn(formatCompact({
272
+ this.#logger.warn(formatCompact({
273
273
  op: 'send',
274
274
  skip: 'no_local_file',
275
275
  type: seg.type,
@@ -294,21 +294,21 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
294
294
  if (!this.#open) return;
295
295
  const userId = msg.from_user_id ?? '';
296
296
  if (msg.context_token && userId) {
297
- setContextToken(this.#options.config.name, userId, msg.context_token);
297
+ setContextToken(this.#options.config.id, userId, msg.context_token);
298
298
  }
299
299
  const conversation = weixinIlinkInboundConversation(String(this.#options.id), userId);
300
300
  void this.#options.gateway.receive({
301
301
  conversation,
302
302
  message: { conversation, id: inboundMessageId(msg) },
303
303
  content: formatInboundContent(msg),
304
- sender: userId,
304
+ sender: { id: userId },
305
+ endpointId: this.#options.config.id,
305
306
  metadata: Object.freeze({
306
- endpoint: this.#options.config.name,
307
307
  messageType: msg.message_type,
308
308
  createTimeMs: msg.create_time_ms,
309
309
  }),
310
310
  }).catch((err) => {
311
- logger.warn(formatCompact({
311
+ this.#logger.warn(formatCompact({
312
312
  op: 'weixin_ilink_gateway_receive_failed',
313
313
  target: userId,
314
314
  error: err instanceof Error ? err.message : String(err),
@@ -318,10 +318,10 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
318
318
 
319
319
  async sendTypingToUser(userId: string, status: number): Promise<void> {
320
320
  if (!this.#creds?.botToken || !this.#configManager) return;
321
- const contextToken = getContextToken(this.#options.config.name, userId);
321
+ const contextToken = getContextToken(this.#options.config.id, userId);
322
322
  const cfg = await this.#configManager.getForUser(userId, contextToken);
323
323
  if (!cfg.typingTicket) {
324
- logger.debug(formatCompact({
324
+ this.#logger.debug(formatCompact({
325
325
  op: 'send_typing',
326
326
  skip: 'no_typing_ticket',
327
327
  userId,
@@ -342,14 +342,14 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
342
342
  async #pollLoop(abortSignal: AbortSignal): Promise<void> {
343
343
  if (!this.#creds?.botToken) return;
344
344
 
345
- const endpointName = this.#options.config.name;
346
- let getUpdatesBuf = loadSyncBuf(endpointName);
345
+ const endpointId = this.#options.config.id;
346
+ let getUpdatesBuf = loadSyncBuf(endpointId);
347
347
  let nextTimeoutMs = this.#options.config.longPollTimeoutMs;
348
348
  let consecutiveFailures = 0;
349
349
 
350
350
  while (!abortSignal.aborted) {
351
- if (isSessionPaused(endpointName)) {
352
- await sleep(getRemainingPauseMs(endpointName), abortSignal);
351
+ if (isSessionPaused(endpointId)) {
352
+ await sleep(getRemainingPauseMs(endpointId), abortSignal);
353
353
  continue;
354
354
  }
355
355
 
@@ -374,9 +374,9 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
374
374
  const sessionExpired =
375
375
  resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
376
376
  if (sessionExpired) {
377
- pauseSession(endpointName);
377
+ pauseSession(endpointId);
378
378
  consecutiveFailures = 0;
379
- await sleep(getRemainingPauseMs(endpointName), abortSignal);
379
+ await sleep(getRemainingPauseMs(endpointId), abortSignal);
380
380
  continue;
381
381
  }
382
382
  consecutiveFailures += 1;
@@ -397,12 +397,12 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
397
397
  }
398
398
  if (nextBuf) {
399
399
  getUpdatesBuf = nextBuf;
400
- saveSyncBuf(endpointName, getUpdatesBuf);
400
+ saveSyncBuf(endpointId, getUpdatesBuf);
401
401
  }
402
402
  } catch (err) {
403
403
  if (abortSignal.aborted) return;
404
404
  consecutiveFailures += 1;
405
- logger.error(formatCompact({
405
+ this.#logger.error(formatCompact({
406
406
  op: 'poll',
407
407
  ok: false,
408
408
  error: err instanceof Error ? err.message : String(err),
@@ -419,14 +419,14 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
419
419
  async #handleInboundMessage(full: WeixinMessage): Promise<void> {
420
420
  const fromUserId = full.from_user_id ?? '';
421
421
  if (full.context_token && fromUserId) {
422
- setContextToken(this.#options.config.name, fromUserId, full.context_token);
422
+ setContextToken(this.#options.config.id, fromUserId, full.context_token);
423
423
  }
424
424
 
425
425
  const mediaOpts = await this.#downloadInboundMedia(full);
426
426
  this.admit({ ...full, _media: mediaOpts });
427
- logger.debug(formatCompact({
427
+ this.#logger.debug(formatCompact({
428
428
  op: 'recv',
429
- endpoint: this.#options.config.name,
429
+ endpoint: this.#options.config.id,
430
430
  target: fromUserId,
431
431
  preview: formatInboundContent({ ...full, _media: mediaOpts }).slice(0, 80),
432
432
  }));
@@ -456,8 +456,8 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
456
456
  cdnBaseUrl: this.cdnBaseUrl(),
457
457
  saveMedia: (buffer, contentType, subdir, maxBytes, originalFilename) =>
458
458
  this.#saveInboundMedia(buffer, contentType, subdir, maxBytes, originalFilename),
459
- log: (msg) => logger.debug(msg),
460
- errLog: (msg) => logger.warn(msg),
459
+ log: (msg) => this.#logger.debug(msg),
460
+ errLog: (msg) => this.#logger.warn(msg),
461
461
  label: `inbound:${full.from_user_id ?? 'unknown'}`,
462
462
  });
463
463
  }
@@ -521,9 +521,9 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
521
521
  const stat = fs.statSync(filePath);
522
522
  if (now - stat.mtimeMs <= INBOUND_MEDIA_TTL_MS) continue;
523
523
  fs.unlinkSync(filePath);
524
- logger.debug(formatCompact({
524
+ this.#logger.debug(formatCompact({
525
525
  op: 'media_sweep',
526
- endpoint: this.#options.config.name,
526
+ endpoint: this.#options.config.id,
527
527
  file: entry.name,
528
528
  }));
529
529
  } catch {
package/src/login.ts CHANGED
@@ -106,7 +106,7 @@ export async function resolveCredentials(
106
106
  ): Promise<WeixinIlinkCredentials> {
107
107
  const envToken = process.env.WEIXIN_ILINK_TOKEN?.trim() || config.botToken?.trim();
108
108
  if (envToken) {
109
- const stored = loadCredentials(config.name);
109
+ const stored = loadCredentials(config.id);
110
110
  return {
111
111
  botToken: envToken,
112
112
  ilinkUserId: stored?.ilinkUserId,
@@ -115,7 +115,7 @@ export async function resolveCredentials(
115
115
  };
116
116
  }
117
117
 
118
- const stored = loadCredentials(config.name);
118
+ const stored = loadCredentials(config.id);
119
119
  if (stored?.botToken) {
120
120
  return {
121
121
  ...stored,
@@ -135,7 +135,7 @@ export async function loginWithQr(
135
135
  const qrcodeUrl = qr.qrcode_img_content || qr.qrcode;
136
136
 
137
137
  logger.info(
138
- `weixin-ilink QR login for ${config.name}: scan with WeChat ClawBot entry — ${qrcodeUrl.slice(0, 120)}${qrcodeUrl.length > 120 ? '…' : ''}`,
138
+ `weixin-ilink QR login for ${config.id}: scan with WeChat ClawBot entry — ${qrcodeUrl.slice(0, 120)}${qrcodeUrl.length > 120 ? '…' : ''}`,
139
139
  );
140
140
 
141
141
  const deadline = Date.now() + LOGIN_TIMEOUT_MS;
@@ -157,7 +157,7 @@ export async function loginWithQr(
157
157
  throw new Error('扫码成功但未返回 bot_token');
158
158
  }
159
159
  const creds: WeixinIlinkCredentials = {
160
- botToken: status.bot_token ?? loadCredentials(config.name)?.botToken ?? '',
160
+ botToken: status.bot_token ?? loadCredentials(config.id)?.botToken ?? '',
161
161
  ilinkUserId: status.ilink_user_id,
162
162
  ilinkBotId: status.ilink_endpoint_id,
163
163
  baseUrl: status.baseurl ?? currentBaseUrl,
@@ -165,7 +165,7 @@ export async function loginWithQr(
165
165
  if (!creds.botToken) {
166
166
  throw new Error('binded_redirect 但本地无有效 bot_token');
167
167
  }
168
- saveCredentials(config.name, creds);
168
+ saveCredentials(config.id, creds);
169
169
  return creds;
170
170
  }
171
171
 
package/src/protocol.ts CHANGED
@@ -12,7 +12,7 @@ import type { WeixinMessage } from './ilink-types.js';
12
12
 
13
13
  /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
14
14
  export interface WeixinIlinkAdapterConfig {
15
- readonly name?: string;
15
+ readonly id?: string;
16
16
  readonly botAgent?: string;
17
17
  readonly baseUrl?: string;
18
18
  readonly cdnBaseUrl?: string;
@@ -26,7 +26,7 @@ export interface WeixinIlinkAdapterConfig {
26
26
 
27
27
  export interface ResolvedWeixinIlinkConfig {
28
28
  readonly context: 'weixin-ilink';
29
- readonly name: string;
29
+ readonly id: string;
30
30
  readonly botAgent?: string;
31
31
  readonly baseUrl: string;
32
32
  readonly cdnBaseUrl: string;
@@ -58,8 +58,8 @@ export function resolveWeixinIlinkConfig(
58
58
  config: WeixinIlinkAdapterConfig = {},
59
59
  ): ResolvedWeixinIlinkConfig {
60
60
  const entry = config.endpoints?.find((item) => item.context === 'weixin-ilink');
61
- const name = (typeof config.name === 'string' && config.name)
62
- || (typeof entry?.name === 'string' && entry.name)
61
+ const id = (typeof config.id === 'string' && config.id)
62
+ || (typeof entry?.id === 'string' && entry.id)
63
63
  || process.env.WEIXIN_ILINK_BOT_NAME
64
64
  || 'weixin-ilink-bot';
65
65
  const botToken = pickCredential(
@@ -71,7 +71,7 @@ export function resolveWeixinIlinkConfig(
71
71
  // (sidecar credentials file / QR login) during endpoint.start().
72
72
  return {
73
73
  context: 'weixin-ilink',
74
- name,
74
+ id,
75
75
  botAgent: config.botAgent ?? entry?.botAgent,
76
76
  baseUrl: config.baseUrl ?? entry?.baseUrl ?? DEFAULT_API_BASE_URL,
77
77
  cdnBaseUrl: config.cdnBaseUrl ?? entry?.cdnBaseUrl ?? DEFAULT_CDN_BASE_URL,
@@ -112,9 +112,9 @@ export function inboundMessageId(msg: WeixinMessage): string {
112
112
  * 入站归一化 → ConversationRef:个人微信无群/频道概念,全部会话都是
113
113
  * 与 `from_user_id` 的 private 会话(无 parent)。
114
114
  */
115
- export function weixinIlinkInboundConversation(endpointId: string, userId: string): ConversationRef {
115
+ export function weixinIlinkInboundConversation(endpointKey: string, userId: string): ConversationRef {
116
116
  return {
117
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
117
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
118
118
  kind: 'private',
119
119
  id: userId,
120
120
  };
@@ -1,6 +1,10 @@
1
- declare module "silk-wasm" {
1
+ /**
2
+ * silk-wasm 是纯可选运行时依赖(不入 package.json):silk-transcode.ts 在
3
+ * try/catch 里动态 import,缺失时降级返回 null。这里只声明用到的最小表面。
4
+ */
5
+ declare module 'silk-wasm' {
2
6
  export function decode(
3
- buf: Buffer,
7
+ input: Uint8Array,
4
8
  sampleRate: number,
5
9
  ): Promise<{ data: Uint8Array; duration: number }>;
6
10
  }
File without changes
File without changes
File without changes
File without changes