@tencent-weixin/openclaw-weixin 2.1.9 → 2.1.10

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "openclaw-weixin",
3
- "version": "2.1.9",
3
+ "version": "2.1.10",
4
4
  "channels": [
5
5
  "openclaw-weixin"
6
6
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-weixin/openclaw-weixin",
3
- "version": "2.1.9",
3
+ "version": "2.1.10",
4
4
  "description": "OpenClaw Weixin channel",
5
5
  "license": "MIT",
6
6
  "author": "Tencent",
package/src/api/api.ts CHANGED
@@ -13,6 +13,8 @@ import type {
13
13
  GetUploadUrlResp,
14
14
  GetUpdatesReq,
15
15
  GetUpdatesResp,
16
+ NotifyStopResp,
17
+ NotifyStartResp,
16
18
  SendMessageReq,
17
19
  SendTypingReq,
18
20
  GetConfigResp,
@@ -316,3 +318,35 @@ export async function sendTyping(
316
318
  label: "sendTyping",
317
319
  });
318
320
  }
321
+
322
+ /**
323
+ * Notify Weixin that this channel client is stopping (gateway shutdown / channel stop).
324
+ * Uses a standalone timeout (not the gateway abort signal) so the request can finish
325
+ * after OpenClaw has already aborted the long-poll.
326
+ */
327
+ export async function notifyStop(params: WeixinApiOptions): Promise<NotifyStopResp> {
328
+ const rawText = await apiPostFetch({
329
+ baseUrl: params.baseUrl,
330
+ endpoint: "ilink/bot/msg/notifystop",
331
+ body: JSON.stringify({ base_info: buildBaseInfo() }),
332
+ token: params.token,
333
+ timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,
334
+ label: "notifyStop",
335
+ });
336
+ return JSON.parse(rawText) as NotifyStopResp;
337
+ }
338
+
339
+ /**
340
+ * Notify Weixin that this channel client is starting (gateway startup / channel start).
341
+ */
342
+ export async function notifyStart(params: WeixinApiOptions): Promise<NotifyStartResp> {
343
+ const rawText = await apiPostFetch({
344
+ baseUrl: params.baseUrl,
345
+ endpoint: "ilink/bot/msg/notifystart",
346
+ body: JSON.stringify({ base_info: buildBaseInfo() }),
347
+ token: params.token,
348
+ timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,
349
+ label: "notifyStart",
350
+ });
351
+ return JSON.parse(rawText) as NotifyStartResp;
352
+ }
package/src/api/types.ts CHANGED
@@ -224,3 +224,25 @@ export interface GetConfigResp {
224
224
  /** Base64-encoded typing ticket for sendTyping. */
225
225
  typing_ticket?: string;
226
226
  }
227
+
228
+ /** proto: NotifyStopReq — notify server when the channel client is stopping. */
229
+ export interface NotifyStopReq {
230
+ base_info?: BaseInfo;
231
+ }
232
+
233
+ /** proto: NotifyStopResp */
234
+ export interface NotifyStopResp {
235
+ ret?: number;
236
+ errmsg?: string;
237
+ }
238
+
239
+ /** proto: NotifyStartReq — notify server when the channel client is starting. */
240
+ export interface NotifyStartReq {
241
+ base_info?: BaseInfo;
242
+ }
243
+
244
+ /** proto: NotifyStartResp */
245
+ export interface NotifyStartResp {
246
+ ret?: number;
247
+ errmsg?: string;
248
+ }
package/src/channel.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  DEFAULT_BASE_URL,
16
16
  } from "./auth/accounts.js";
17
17
  import type { ResolvedWeixinAccount } from "./auth/accounts.js";
18
+ import { notifyStop, notifyStart } from "./api/api.js";
18
19
  import { assertSessionActive } from "./api/session-guard.js";
19
20
  import { getContextToken, findAccountIdsByContextToken, restoreContextTokens, clearContextTokensForAccount } from "./messaging/inbound.js";
20
21
  import { logger } from "./util/logger.js";
@@ -427,6 +428,18 @@ export const weixinPlugin: ChannelPlugin<ResolvedWeixinAccount> = {
427
428
 
428
429
  ctx.log?.info?.(`[${account.accountId}] starting weixin provider (${DEFAULT_BASE_URL})`);
429
430
 
431
+ try {
432
+ const resp = await notifyStart({
433
+ baseUrl: account.baseUrl,
434
+ token: account.token,
435
+ });
436
+ if (resp.ret !== undefined && resp.ret !== 0) {
437
+ aLog.warn(`notifyStart: ret=${resp.ret} errmsg=${resp.errmsg ?? ""}`);
438
+ }
439
+ } catch (err) {
440
+ aLog.warn(`notifyStart failed during startup (ignored): ${String(err)}`);
441
+ }
442
+
430
443
  const logPath = aLog.getLogFilePath();
431
444
  ctx.log?.info?.(`[${account.accountId}] weixin logs: ${logPath}`);
432
445
 
@@ -442,6 +455,25 @@ export const weixinPlugin: ChannelPlugin<ResolvedWeixinAccount> = {
442
455
  setStatus: ctx.setStatus,
443
456
  });
444
457
  },
458
+ stopAccount: async (ctx) => {
459
+ const account = ctx.account;
460
+ const aLog = logger.withAccount(account.accountId);
461
+ if (!account.configured || !account.token?.trim()) {
462
+ aLog.debug(`gateway.stopAccount: skip notifyStop (not configured or no token)`);
463
+ return;
464
+ }
465
+ try {
466
+ const resp = await notifyStop({
467
+ baseUrl: account.baseUrl,
468
+ token: account.token,
469
+ });
470
+ if (resp.ret !== undefined && resp.ret !== 0) {
471
+ aLog.warn(`notifyStop: ret=${resp.ret} errmsg=${resp.errmsg ?? ""}`);
472
+ }
473
+ } catch (err) {
474
+ aLog.warn(`notifyStop failed during shutdown (ignored): ${String(err)}`);
475
+ }
476
+ },
445
477
  loginWithQrStart: async ({ accountId, force, timeoutMs, verbose }) => {
446
478
  // For re-login: use saved baseUrl from account data; fall back to default for new accounts.
447
479
  const savedBaseUrl = accountId ? loadWeixinAccount(accountId)?.baseUrl?.trim() : "";