liangzimixin 0.3.69 → 0.3.70

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/dist/index.cjs CHANGED
@@ -18488,12 +18488,25 @@ async function resolveAndUploadMedia(params) {
18488
18488
  timeoutMs
18489
18489
  });
18490
18490
  } catch (err) {
18491
- log5.warn("media:upload failed, \u964D\u7EA7\u5904\u7406", { error: err.message });
18491
+ const errMsg = err.message;
18492
+ log5.warn("media:upload failed, \u964D\u7EA7\u5904\u7406", { error: errMsg });
18493
+ try {
18494
+ const tipText = "\u26A0\uFE0F \u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
18495
+ await messagePipe.sendMessage({
18496
+ chatId,
18497
+ senderId: chatId,
18498
+ msgType: "text",
18499
+ content: JSON.stringify({ content: tipText }),
18500
+ skipEncrypt: true
18501
+ });
18502
+ } catch (tipErr) {
18503
+ log5.warn("media:upload-failed tip send error", { error: tipErr.message });
18504
+ }
18492
18505
  return {
18493
18506
  channel: CHANNEL_ID,
18494
18507
  messageId: "",
18495
18508
  chatId,
18496
- warning: `\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25: ${err.message}`
18509
+ warning: `\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25: ${errMsg}`
18497
18510
  };
18498
18511
  }
18499
18512
  const msgType = fileType;
@@ -19121,19 +19134,27 @@ function createQuantumImDeliverFn(deps) {
19121
19134
  const hasMedia = mediaUrls.length > 0;
19122
19135
  if (!hasText && !hasMedia) return;
19123
19136
  if (hasText) {
19124
- await messagePipe.sendMessage({
19125
- chatId,
19126
- senderId,
19127
- msgType: resolveTextMsgType(payload.text),
19128
- content: JSON.stringify({ content: payload.text }),
19129
- replyToMessageId,
19130
- skipEncrypt: shouldSkipEncrypt
19131
- });
19132
- log14.info("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u5DF2\u53D1\u9001", {
19133
- chatId,
19134
- \u957F\u5EA6: payload.text.length,
19135
- \u56DE\u590D\u9884\u89C8: payload.text.slice(0, 200)
19136
- });
19137
+ try {
19138
+ await messagePipe.sendMessage({
19139
+ chatId,
19140
+ senderId,
19141
+ msgType: resolveTextMsgType(payload.text),
19142
+ content: JSON.stringify({ content: payload.text }),
19143
+ replyToMessageId,
19144
+ skipEncrypt: shouldSkipEncrypt
19145
+ });
19146
+ log14.info("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u5DF2\u53D1\u9001", {
19147
+ chatId,
19148
+ \u957F\u5EA6: payload.text.length,
19149
+ \u56DE\u590D\u9884\u89C8: payload.text.slice(0, 200)
19150
+ });
19151
+ } catch (err) {
19152
+ log14.error("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u53D1\u9001\u5931\u8D25", {
19153
+ chatId,
19154
+ error: err.message
19155
+ });
19156
+ if (!hasMedia) throw err;
19157
+ }
19137
19158
  }
19138
19159
  if (hasMedia) {
19139
19160
  for (const mediaUrl of mediaUrls) {
@@ -19425,6 +19446,21 @@ var InboundPipeline = class {
19425
19446
  stack: err.stack,
19426
19447
  durationMs
19427
19448
  });
19449
+ try {
19450
+ const errorTip = "\u26A0\uFE0F \u6D88\u606F\u5904\u7406\u9047\u5230\u95EE\u9898\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002\u5982\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002";
19451
+ await this.deps.messagePipe.sendMessage({
19452
+ chatId: msg.chatId,
19453
+ senderId: msg.senderId,
19454
+ msgType: resolveTextMsgType(errorTip),
19455
+ content: JSON.stringify({ content: errorTip }),
19456
+ skipEncrypt: true
19457
+ // 系统提示消息不加密,避免加密失败导致的死循环
19458
+ });
19459
+ } catch (tipErr) {
19460
+ log15.warn("\u26A0\uFE0F \u9519\u8BEF\u63D0\u793A\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF08\u907F\u514D\u6B7B\u5FAA\u73AF\uFF0C\u4E0D\u518D\u91CD\u8BD5\uFF09", {
19461
+ error: tipErr.message
19462
+ });
19463
+ }
19428
19464
  clearInboundEncryptionStatus(msg.chatId, msg.messageId);
19429
19465
  } finally {
19430
19466
  semaphore.release();
@@ -21143,7 +21179,9 @@ var MessagePipe = class _MessagePipe {
21143
21179
  body.reply_msg_id = msg.replyToMessageId;
21144
21180
  }
21145
21181
  const result = await this.callMessageApi("/messages/v1/send", body, "outbound");
21146
- if (!result) return;
21182
+ if (!result) {
21183
+ throw new Error(`\u6D88\u606F\u53D1\u9001\u5931\u8D25: POST ${this.messageServiceBaseUrl}/messages/v1/send \u672A\u8FD4\u56DE\u6709\u6548\u54CD\u5E94`);
21184
+ }
21147
21185
  log25.info("\u{1F4E4} outbound:sent \u2192 IM \u670D\u52A1\u5668", {
21148
21186
  chatId: msg.chatId,
21149
21187
  encrypted: Boolean(extra),
@@ -21167,7 +21205,10 @@ var MessagePipe = class _MessagePipe {
21167
21205
  conversation_type: ""
21168
21206
  };
21169
21207
  const result = await this.callMessageApi("/messages/v1/recall", body, "recall");
21170
- if (!result) return;
21208
+ if (!result) {
21209
+ log25.warn("recall:failed \u2014 callMessageApi returned null");
21210
+ return;
21211
+ }
21171
21212
  log25.info("recall:sent", {
21172
21213
  messageId: params.messageId,
21173
21214
  requestId: result.request_id
@@ -21182,6 +21223,43 @@ var MessagePipe = class _MessagePipe {
21182
21223
  * @param body - 请求体
21183
21224
  * @param logTag - 日志标签前缀 (如 'outbound' / 'recall')
21184
21225
  */
21226
+ /** 单次 HTTP 请求封装 — callMessageApi 内部使用 */
21227
+ async _callMessageApiOnce(url2, body, logTag) {
21228
+ const apiStartMs = Date.now();
21229
+ const token = await this.tokenFn();
21230
+ const resp = await fetch(url2, {
21231
+ method: "POST",
21232
+ headers: {
21233
+ "Authorization": `Bearer ${token}`,
21234
+ "Content-Type": "application/json"
21235
+ },
21236
+ body: JSON.stringify(body)
21237
+ });
21238
+ if (!resp.ok) {
21239
+ log25.error(`${logTag}:http-error`, {
21240
+ status: resp.status,
21241
+ statusText: resp.statusText,
21242
+ url: url2
21243
+ });
21244
+ return { code: -1, msg: `HTTP ${resp.status}`, _retryable: resp.status >= 500 };
21245
+ }
21246
+ const result = await resp.json();
21247
+ if (result.code !== 0 && result.code !== 200) {
21248
+ log25.error(`${logTag}:api-error`, {
21249
+ code: result.code,
21250
+ msg: result.msg,
21251
+ requestId: result.request_id
21252
+ });
21253
+ return null;
21254
+ }
21255
+ metrics.increment("outbound.success");
21256
+ metrics.recordLatency("outbound.latency", Date.now() - apiStartMs);
21257
+ return result;
21258
+ }
21259
+ /**
21260
+ * 消息服务 API 公共调用方法 — 含 1 次重试 (仅限 5xx / 网络错误)。
21261
+ * 失败返回 null(不抛异常),错误通过 log 记录。
21262
+ */
21185
21263
  async callMessageApi(path3, body, logTag) {
21186
21264
  const url2 = `${this.messageServiceBaseUrl}${path3}`;
21187
21265
  const rateLimitResult = await this.rateLimiter.acquire();
@@ -21194,44 +21272,42 @@ var MessagePipe = class _MessagePipe {
21194
21272
  return null;
21195
21273
  }
21196
21274
  try {
21197
- const apiStartMs = Date.now();
21198
- const token = await this.tokenFn();
21199
- const resp = await fetch(url2, {
21200
- method: "POST",
21201
- headers: {
21202
- "Authorization": `Bearer ${token}`,
21203
- "Content-Type": "application/json"
21204
- },
21205
- body: JSON.stringify(body)
21206
- });
21207
- if (!resp.ok) {
21208
- log25.error(`${logTag}:http-error`, {
21209
- status: resp.status,
21210
- statusText: resp.statusText,
21211
- url: url2
21212
- });
21275
+ const result = await this._callMessageApiOnce(url2, body, logTag);
21276
+ if (result && "_retryable" in result) {
21277
+ if (result._retryable) {
21278
+ log25.warn(`${logTag}:retrying after 5xx`, { url: url2 });
21279
+ await new Promise((r) => setTimeout(r, 1e3));
21280
+ try {
21281
+ const retryResult = await this._callMessageApiOnce(url2, body, `${logTag}:retry`);
21282
+ if (retryResult && !("_retryable" in retryResult)) {
21283
+ return retryResult;
21284
+ }
21285
+ } catch (retryErr) {
21286
+ log25.error(`${logTag}:retry-failed`, { error: retryErr.message });
21287
+ }
21288
+ }
21213
21289
  metrics.increment("outbound.failed");
21214
21290
  return null;
21215
21291
  }
21216
- const result = await resp.json();
21217
- if (result.code !== 0 && result.code !== 200) {
21218
- log25.error(`${logTag}:api-error`, {
21219
- code: result.code,
21220
- msg: result.msg,
21221
- requestId: result.request_id
21292
+ return result;
21293
+ } catch (err) {
21294
+ log25.warn(`${logTag}:network-error, retrying`, { url: url2, error: err.message });
21295
+ await new Promise((r) => setTimeout(r, 1e3));
21296
+ try {
21297
+ const retryResult = await this._callMessageApiOnce(url2, body, `${logTag}:retry`);
21298
+ if (retryResult && !("_retryable" in retryResult)) {
21299
+ return retryResult;
21300
+ }
21301
+ metrics.increment("outbound.failed");
21302
+ return null;
21303
+ } catch (retryErr) {
21304
+ metrics.increment("outbound.failed");
21305
+ log25.error(`${logTag}:retry-network-error`, {
21306
+ url: url2,
21307
+ error: retryErr.message
21222
21308
  });
21223
21309
  return null;
21224
21310
  }
21225
- metrics.increment("outbound.success");
21226
- metrics.recordLatency("outbound.latency", Date.now() - apiStartMs);
21227
- return result;
21228
- } catch (err) {
21229
- metrics.increment("outbound.failed");
21230
- log25.error(`${logTag}:network-error`, {
21231
- url: url2,
21232
- error: err.message
21233
- });
21234
- return null;
21235
21311
  }
21236
21312
  }
21237
21313
  /**
@@ -21340,6 +21416,10 @@ var MessagePipe = class _MessagePipe {
21340
21416
  msgUid: callbackData.msgUid,
21341
21417
  error: err.message
21342
21418
  });
21419
+ await this.sendHintMessage(
21420
+ callbackData,
21421
+ "\u26A0\uFE0F \u6D88\u606F\u89E3\u5BC6\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u3002\u5982\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002"
21422
+ );
21343
21423
  return;
21344
21424
  }
21345
21425
  } else {
@@ -21361,6 +21441,10 @@ var MessagePipe = class _MessagePipe {
21361
21441
  msgUid: callbackData.msgUid,
21362
21442
  error: err.message
21363
21443
  });
21444
+ await this.sendHintMessage(
21445
+ callbackData,
21446
+ "\u26A0\uFE0F \u6D88\u606F\u89E3\u5BC6\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u3002\u5982\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002"
21447
+ );
21364
21448
  return;
21365
21449
  }
21366
21450
  } else {
package/dist/index.d.cts CHANGED
@@ -966,6 +966,12 @@ declare class MessagePipe {
966
966
  * @param body - 请求体
967
967
  * @param logTag - 日志标签前缀 (如 'outbound' / 'recall')
968
968
  */
969
+ /** 单次 HTTP 请求封装 — callMessageApi 内部使用 */
970
+ private _callMessageApiOnce;
971
+ /**
972
+ * 消息服务 API 公共调用方法 — 含 1 次重试 (仅限 5xx / 网络错误)。
973
+ * 失败返回 null(不抛异常),错误通过 log 记录。
974
+ */
969
975
  private callMessageApi;
970
976
  /**
971
977
  * 入站处理流水线 (7 步):
@@ -4559,12 +4559,25 @@ async function resolveAndUploadMedia(params) {
4559
4559
  timeoutMs
4560
4560
  });
4561
4561
  } catch (err) {
4562
- log5.warn("media:upload failed, \u964D\u7EA7\u5904\u7406", { error: err.message });
4562
+ const errMsg = err.message;
4563
+ log5.warn("media:upload failed, \u964D\u7EA7\u5904\u7406", { error: errMsg });
4564
+ try {
4565
+ const tipText = "\u26A0\uFE0F \u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
4566
+ await messagePipe.sendMessage({
4567
+ chatId,
4568
+ senderId: chatId,
4569
+ msgType: "text",
4570
+ content: JSON.stringify({ content: tipText }),
4571
+ skipEncrypt: true
4572
+ });
4573
+ } catch (tipErr) {
4574
+ log5.warn("media:upload-failed tip send error", { error: tipErr.message });
4575
+ }
4563
4576
  return {
4564
4577
  channel: CHANNEL_ID,
4565
4578
  messageId: "",
4566
4579
  chatId,
4567
- warning: `\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25: ${err.message}`
4580
+ warning: `\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25: ${errMsg}`
4568
4581
  };
4569
4582
  }
4570
4583
  const msgType = fileType;
@@ -20064,7 +20077,9 @@ var MessagePipe = class _MessagePipe {
20064
20077
  body.reply_msg_id = msg.replyToMessageId;
20065
20078
  }
20066
20079
  const result = await this.callMessageApi("/messages/v1/send", body, "outbound");
20067
- if (!result) return;
20080
+ if (!result) {
20081
+ throw new Error(`\u6D88\u606F\u53D1\u9001\u5931\u8D25: POST ${this.messageServiceBaseUrl}/messages/v1/send \u672A\u8FD4\u56DE\u6709\u6548\u54CD\u5E94`);
20082
+ }
20068
20083
  log16.info("\u{1F4E4} outbound:sent \u2192 IM \u670D\u52A1\u5668", {
20069
20084
  chatId: msg.chatId,
20070
20085
  encrypted: Boolean(extra),
@@ -20088,7 +20103,10 @@ var MessagePipe = class _MessagePipe {
20088
20103
  conversation_type: ""
20089
20104
  };
20090
20105
  const result = await this.callMessageApi("/messages/v1/recall", body, "recall");
20091
- if (!result) return;
20106
+ if (!result) {
20107
+ log16.warn("recall:failed \u2014 callMessageApi returned null");
20108
+ return;
20109
+ }
20092
20110
  log16.info("recall:sent", {
20093
20111
  messageId: params.messageId,
20094
20112
  requestId: result.request_id
@@ -20103,6 +20121,43 @@ var MessagePipe = class _MessagePipe {
20103
20121
  * @param body - 请求体
20104
20122
  * @param logTag - 日志标签前缀 (如 'outbound' / 'recall')
20105
20123
  */
20124
+ /** 单次 HTTP 请求封装 — callMessageApi 内部使用 */
20125
+ async _callMessageApiOnce(url2, body, logTag) {
20126
+ const apiStartMs = Date.now();
20127
+ const token = await this.tokenFn();
20128
+ const resp = await fetch(url2, {
20129
+ method: "POST",
20130
+ headers: {
20131
+ "Authorization": `Bearer ${token}`,
20132
+ "Content-Type": "application/json"
20133
+ },
20134
+ body: JSON.stringify(body)
20135
+ });
20136
+ if (!resp.ok) {
20137
+ log16.error(`${logTag}:http-error`, {
20138
+ status: resp.status,
20139
+ statusText: resp.statusText,
20140
+ url: url2
20141
+ });
20142
+ return { code: -1, msg: `HTTP ${resp.status}`, _retryable: resp.status >= 500 };
20143
+ }
20144
+ const result = await resp.json();
20145
+ if (result.code !== 0 && result.code !== 200) {
20146
+ log16.error(`${logTag}:api-error`, {
20147
+ code: result.code,
20148
+ msg: result.msg,
20149
+ requestId: result.request_id
20150
+ });
20151
+ return null;
20152
+ }
20153
+ metrics.increment("outbound.success");
20154
+ metrics.recordLatency("outbound.latency", Date.now() - apiStartMs);
20155
+ return result;
20156
+ }
20157
+ /**
20158
+ * 消息服务 API 公共调用方法 — 含 1 次重试 (仅限 5xx / 网络错误)。
20159
+ * 失败返回 null(不抛异常),错误通过 log 记录。
20160
+ */
20106
20161
  async callMessageApi(path3, body, logTag) {
20107
20162
  const url2 = `${this.messageServiceBaseUrl}${path3}`;
20108
20163
  const rateLimitResult = await this.rateLimiter.acquire();
@@ -20115,44 +20170,42 @@ var MessagePipe = class _MessagePipe {
20115
20170
  return null;
20116
20171
  }
20117
20172
  try {
20118
- const apiStartMs = Date.now();
20119
- const token = await this.tokenFn();
20120
- const resp = await fetch(url2, {
20121
- method: "POST",
20122
- headers: {
20123
- "Authorization": `Bearer ${token}`,
20124
- "Content-Type": "application/json"
20125
- },
20126
- body: JSON.stringify(body)
20127
- });
20128
- if (!resp.ok) {
20129
- log16.error(`${logTag}:http-error`, {
20130
- status: resp.status,
20131
- statusText: resp.statusText,
20132
- url: url2
20133
- });
20173
+ const result = await this._callMessageApiOnce(url2, body, logTag);
20174
+ if (result && "_retryable" in result) {
20175
+ if (result._retryable) {
20176
+ log16.warn(`${logTag}:retrying after 5xx`, { url: url2 });
20177
+ await new Promise((r) => setTimeout(r, 1e3));
20178
+ try {
20179
+ const retryResult = await this._callMessageApiOnce(url2, body, `${logTag}:retry`);
20180
+ if (retryResult && !("_retryable" in retryResult)) {
20181
+ return retryResult;
20182
+ }
20183
+ } catch (retryErr) {
20184
+ log16.error(`${logTag}:retry-failed`, { error: retryErr.message });
20185
+ }
20186
+ }
20134
20187
  metrics.increment("outbound.failed");
20135
20188
  return null;
20136
20189
  }
20137
- const result = await resp.json();
20138
- if (result.code !== 0 && result.code !== 200) {
20139
- log16.error(`${logTag}:api-error`, {
20140
- code: result.code,
20141
- msg: result.msg,
20142
- requestId: result.request_id
20190
+ return result;
20191
+ } catch (err) {
20192
+ log16.warn(`${logTag}:network-error, retrying`, { url: url2, error: err.message });
20193
+ await new Promise((r) => setTimeout(r, 1e3));
20194
+ try {
20195
+ const retryResult = await this._callMessageApiOnce(url2, body, `${logTag}:retry`);
20196
+ if (retryResult && !("_retryable" in retryResult)) {
20197
+ return retryResult;
20198
+ }
20199
+ metrics.increment("outbound.failed");
20200
+ return null;
20201
+ } catch (retryErr) {
20202
+ metrics.increment("outbound.failed");
20203
+ log16.error(`${logTag}:retry-network-error`, {
20204
+ url: url2,
20205
+ error: retryErr.message
20143
20206
  });
20144
20207
  return null;
20145
20208
  }
20146
- metrics.increment("outbound.success");
20147
- metrics.recordLatency("outbound.latency", Date.now() - apiStartMs);
20148
- return result;
20149
- } catch (err) {
20150
- metrics.increment("outbound.failed");
20151
- log16.error(`${logTag}:network-error`, {
20152
- url: url2,
20153
- error: err.message
20154
- });
20155
- return null;
20156
20209
  }
20157
20210
  }
20158
20211
  /**
@@ -20261,6 +20314,10 @@ var MessagePipe = class _MessagePipe {
20261
20314
  msgUid: callbackData.msgUid,
20262
20315
  error: err.message
20263
20316
  });
20317
+ await this.sendHintMessage(
20318
+ callbackData,
20319
+ "\u26A0\uFE0F \u6D88\u606F\u89E3\u5BC6\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u3002\u5982\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002"
20320
+ );
20264
20321
  return;
20265
20322
  }
20266
20323
  } else {
@@ -20282,6 +20339,10 @@ var MessagePipe = class _MessagePipe {
20282
20339
  msgUid: callbackData.msgUid,
20283
20340
  error: err.message
20284
20341
  });
20342
+ await this.sendHintMessage(
20343
+ callbackData,
20344
+ "\u26A0\uFE0F \u6D88\u606F\u89E3\u5BC6\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u3002\u5982\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002"
20345
+ );
20285
20346
  return;
20286
20347
  }
20287
20348
  } else {
@@ -21459,19 +21520,27 @@ function createQuantumImDeliverFn(deps) {
21459
21520
  const hasMedia = mediaUrls.length > 0;
21460
21521
  if (!hasText && !hasMedia) return;
21461
21522
  if (hasText) {
21462
- await messagePipe.sendMessage({
21463
- chatId,
21464
- senderId,
21465
- msgType: resolveTextMsgType(payload.text),
21466
- content: JSON.stringify({ content: payload.text }),
21467
- replyToMessageId,
21468
- skipEncrypt: shouldSkipEncrypt
21469
- });
21470
- log27.info("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u5DF2\u53D1\u9001", {
21471
- chatId,
21472
- \u957F\u5EA6: payload.text.length,
21473
- \u56DE\u590D\u9884\u89C8: payload.text.slice(0, 200)
21474
- });
21523
+ try {
21524
+ await messagePipe.sendMessage({
21525
+ chatId,
21526
+ senderId,
21527
+ msgType: resolveTextMsgType(payload.text),
21528
+ content: JSON.stringify({ content: payload.text }),
21529
+ replyToMessageId,
21530
+ skipEncrypt: shouldSkipEncrypt
21531
+ });
21532
+ log27.info("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u5DF2\u53D1\u9001", {
21533
+ chatId,
21534
+ \u957F\u5EA6: payload.text.length,
21535
+ \u56DE\u590D\u9884\u89C8: payload.text.slice(0, 200)
21536
+ });
21537
+ } catch (err) {
21538
+ log27.error("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u53D1\u9001\u5931\u8D25", {
21539
+ chatId,
21540
+ error: err.message
21541
+ });
21542
+ if (!hasMedia) throw err;
21543
+ }
21475
21544
  }
21476
21545
  if (hasMedia) {
21477
21546
  for (const mediaUrl of mediaUrls) {
@@ -21751,6 +21820,21 @@ var InboundPipeline = class {
21751
21820
  stack: err.stack,
21752
21821
  durationMs
21753
21822
  });
21823
+ try {
21824
+ const errorTip = "\u26A0\uFE0F \u6D88\u606F\u5904\u7406\u9047\u5230\u95EE\u9898\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002\u5982\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002";
21825
+ await this.deps.messagePipe.sendMessage({
21826
+ chatId: msg.chatId,
21827
+ senderId: msg.senderId,
21828
+ msgType: resolveTextMsgType(errorTip),
21829
+ content: JSON.stringify({ content: errorTip }),
21830
+ skipEncrypt: true
21831
+ // 系统提示消息不加密,避免加密失败导致的死循环
21832
+ });
21833
+ } catch (tipErr) {
21834
+ log28.warn("\u26A0\uFE0F \u9519\u8BEF\u63D0\u793A\u6D88\u606F\u53D1\u9001\u5931\u8D25\uFF08\u907F\u514D\u6B7B\u5FAA\u73AF\uFF0C\u4E0D\u518D\u91CD\u8BD5\uFF09", {
21835
+ error: tipErr.message
21836
+ });
21837
+ }
21754
21838
  clearInboundEncryptionStatus(msg.chatId, msg.messageId);
21755
21839
  } finally {
21756
21840
  semaphore.release();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liangzimixin",
3
- "version": "0.3.69",
3
+ "version": "0.3.70",
4
4
  "description": "Quantum-encrypted IM channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -3,24 +3,26 @@ setlocal enabledelayedexpansion
3
3
  chcp 65001 >nul 2>&1
4
4
 
5
5
  REM ============================================================
6
- REM liangzimixin 一键部署脚本 (Windows)
7
- REM 用法: liangzimixin_install.bat <appId> <appSecret> <quantumAccount>
6
+ REM liangzimixin install script (Windows)
7
+ REM Usage: liangzimixin_install.bat <appId> <appSecret> [quantumAccount]
8
8
  REM ============================================================
9
9
 
10
- set "SCRIPT_VERSION=0.3.69"
10
+ set "SCRIPT_VERSION=0.3.70"
11
11
  set "NPM_PACKAGE=liangzimixin"
12
- set "REMOTE_SCRIPT_URL=https://unpkg.com/%NPM_PACKAGE%@latest/scripts/liangzimixin_install.bat"
13
12
 
14
13
  set "SKIP_SELF_UPDATE=0"
14
+ set "USE_BETA=0"
15
15
  set "APP_ID="
16
16
  set "APP_SECRET="
17
17
  set "QUANTUM_ACCOUNT="
18
18
 
19
- REM ── 参数解析 ────────────────────────────────────────────────
19
+ REM -- Parse arguments -------------------------------------------------
20
20
  set "IDX=0"
21
21
  for %%a in (%*) do (
22
22
  if "%%a"=="--skip-self-update" (
23
23
  set "SKIP_SELF_UPDATE=1"
24
+ ) else if "%%a"=="--beta" (
25
+ set "USE_BETA=1"
24
26
  ) else (
25
27
  set /a IDX+=1
26
28
  if !IDX!==1 set "APP_ID=%%a"
@@ -29,68 +31,61 @@ for %%a in (%*) do (
29
31
  )
30
32
  )
31
33
 
34
+ set "TAG=latest"
35
+ if "%USE_BETA%"=="1" set "TAG=beta"
36
+ set "REMOTE_SCRIPT_URL=https://unpkg.com/%NPM_PACKAGE%@%TAG%/scripts/liangzimixin_install.bat"
37
+
32
38
  if "%APP_ID%"=="" goto :usage
33
39
  if "%APP_SECRET%"=="" goto :usage
34
- if "%QUANTUM_ACCOUNT%"=="" goto :usage
35
40
  goto :main
36
41
 
37
42
  :usage
38
43
  echo.
39
- echo liangzimixin 一键部署脚本 v%SCRIPT_VERSION%
44
+ echo liangzimixin install script v%SCRIPT_VERSION%
40
45
  echo.
41
- echo 用法: %~nx0 ^<appId^> ^<appSecret^> ^<quantumAccount^> [--skip-self-update]
46
+ echo Usage: %~nx0 ^<appId^> ^<appSecret^> [quantumAccount] [--skip-self-update] [--beta]
42
47
  echo.
43
- echo 参数:
44
- echo appId 平台应用 ID
45
- echo appSecret 应用密钥
46
- echo quantumAccount 量子账户标识
48
+ echo Arguments:
49
+ echo appId App ID (required)
50
+ echo appSecret App Secret (required)
51
+ echo quantumAccount Quantum Account ID (optional)
47
52
  echo.
48
- echo 选项:
49
- echo --skip-self-update 跳过脚本自更新检查
53
+ echo Options:
54
+ echo --skip-self-update Skip script self-update check
55
+ echo --beta Install beta version
50
56
  echo.
51
57
  exit /b 1
52
58
 
53
- REM ════════════════════════════════════════════════════════════
59
+ REM ============================================================
54
60
  :main
55
61
  echo.
56
- echo ═══════════════════════════════════════════════════════
57
- echo liangzimixin 一键部署 v%SCRIPT_VERSION%
58
- echo ═══════════════════════════════════════════════════════
62
+ echo =======================================================
63
+ echo liangzimixin deploy v%SCRIPT_VERSION%
64
+ echo =======================================================
59
65
  echo.
60
66
 
61
- REM ════════════════════════════════════════════════════════════
62
- REM Step 0: 脚本自更新检查
63
- REM ════════════════════════════════════════════════════════════
67
+ REM ============================================================
68
+ REM Step 0: Script self-update check
69
+ REM ============================================================
64
70
  if "%SKIP_SELF_UPDATE%"=="1" (
65
- echo [INFO] 跳过脚本自更新检查 ^(--skip-self-update^)
71
+ echo [INFO] Skip self-update ^(--skip-self-update^)
66
72
  goto :step1
67
73
  )
68
74
 
69
- echo [INFO] Step 0/4 检查脚本更新...
75
+ echo [INFO] Step 0/4 - Checking for script updates...
70
76
 
71
77
  set "REMOTE_VERSION="
72
- REM npm registry 获取最新版本号
73
- curl -fsSL --connect-timeout 5 --max-time 10 "https://registry.npmjs.org/%NPM_PACKAGE%/latest" > "%TEMP%\lzmx_pkg.json" 2>nul
74
- if %errorlevel%==0 (
75
- for /f "tokens=2 delims=:," %%a in ('findstr /c:"version" "%TEMP%\lzmx_pkg.json"') do (
76
- set "REMOTE_VERSION=%%~a"
77
- goto :got_version
78
- )
79
- )
80
- :got_version
81
- REM 去除可能的前后空格和引号
82
- if defined REMOTE_VERSION (
83
- set "REMOTE_VERSION=!REMOTE_VERSION: =!"
84
- set "REMOTE_VERSION=!REMOTE_VERSION:"=!"
78
+ REM Fetch latest version from npm registry (use PowerShell for JSON parsing)
79
+ for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "try { (Invoke-RestMethod -Uri 'https://registry.npmjs.org/%NPM_PACKAGE%/%TAG%' -TimeoutSec 10).version } catch { }" 2^>nul`) do (
80
+ set "REMOTE_VERSION=%%a"
85
81
  )
86
- del /q "%TEMP%\lzmx_pkg.json" 2>nul
87
82
 
88
83
  if not defined REMOTE_VERSION (
89
- echo [WARN] 无法检查远端版本,使用当前版本继续执行
84
+ echo [WARN] Cannot check remote version, continuing with current version
90
85
  goto :step1
91
86
  )
92
87
 
93
- REM 简单版本比较:解析语义化版本,防止 prerelease 版本被回滚到较旧的 latest
88
+ REM Semver comparison: prevent prerelease from rolling back to older latest
94
89
  for /f "delims=- tokens=1" %%a in ("!REMOTE_VERSION!") do set "CLEAN_REMOTE=%%a"
95
90
  for /f "delims=- tokens=1" %%a in ("!SCRIPT_VERSION!") do set "CLEAN_LOCAL=%%a"
96
91
 
@@ -111,86 +106,105 @@ if !R1! EQU !L1! (
111
106
  )
112
107
 
113
108
  if "%REMOTE_VERSION%"=="%SCRIPT_VERSION%" (
114
- echo [OK] 脚本已是最新版本 ^(v%SCRIPT_VERSION%^)
109
+ echo [OK] Script is up to date ^(v%SCRIPT_VERSION%^)
115
110
  goto :step1
116
111
  )
117
112
 
118
113
  if "!SHOULD_UPDATE!"=="0" (
119
- echo [OK] 当前脚本 ^(v%SCRIPT_VERSION%^) 更新或等同于远端 ^(v%REMOTE_VERSION%^)
114
+ echo [OK] Current script ^(v%SCRIPT_VERSION%^) is newer or equal to remote ^(v%REMOTE_VERSION%^)
120
115
  goto :step1
121
116
  )
122
117
 
123
- echo [INFO] 发现新版本: %SCRIPT_VERSION% -^> %REMOTE_VERSION%,正在更新脚本...
118
+ echo [INFO] New version found: %SCRIPT_VERSION% -^> %REMOTE_VERSION%, updating...
124
119
  set "TEMP_SCRIPT=%TEMP%\liangzimixin_install_new.bat"
125
120
  curl -fsSL --connect-timeout 10 --max-time 30 "!REMOTE_SCRIPT_URL!" -o "%TEMP_SCRIPT%" 2>nul
126
121
  if %errorlevel%==0 (
127
- echo [OK] 脚本已更新到 v%REMOTE_VERSION%
128
- call "%TEMP_SCRIPT%" --skip-self-update %APP_ID% %APP_SECRET% %QUANTUM_ACCOUNT%
122
+ echo [OK] Script updated to v%REMOTE_VERSION%
123
+ if "%USE_BETA%"=="1" (
124
+ call "%TEMP_SCRIPT%" --skip-self-update --beta %APP_ID% %APP_SECRET% %QUANTUM_ACCOUNT%
125
+ ) else (
126
+ call "%TEMP_SCRIPT%" --skip-self-update %APP_ID% %APP_SECRET% %QUANTUM_ACCOUNT%
127
+ )
129
128
  exit /b %errorlevel%
130
129
  ) else (
131
- echo [WARN] 脚本下载失败,使用当前版本继续执行
130
+ echo [WARN] Script download failed, continuing with current version
132
131
  del /q "%TEMP_SCRIPT%" 2>nul
133
132
  )
134
133
 
135
134
  echo.
136
135
 
137
- REM ════════════════════════════════════════════════════════════
138
- REM Step 1: 安装/更新插件
139
- REM ════════════════════════════════════════════════════════════
136
+ REM ============================================================
137
+ REM Step 1: Install/update plugin
138
+ REM ============================================================
140
139
  :step1
141
- echo [INFO] Step 1/4 安装/更新 liangzimixin 插件...
140
+ echo [INFO] Step 1/4 - Install/update liangzimixin plugin (%TAG%)...
142
141
 
143
- openclaw plugins list 2>nul | findstr /i "liangzimixin" >nul
142
+ call openclaw plugins list 2>nul | findstr /i "liangzimixin" >nul
144
143
  if %errorlevel%==0 (
145
- echo [INFO] 插件已安装,正在更新到最新版本...
146
- openclaw plugins update liangzimixin
144
+ echo [INFO] Plugin installed, updating...
145
+ if "%USE_BETA%"=="1" (
146
+ call openclaw plugins install liangzimixin@beta
147
+ ) else (
148
+ call openclaw plugins update liangzimixin
149
+ )
147
150
  ) else (
148
- echo [INFO] 插件未安装,正在安装最新版本...
149
- openclaw plugins install liangzimixin@latest
151
+ echo [INFO] Plugin not installed, installing...
152
+ call openclaw plugins install liangzimixin@%TAG%
150
153
  )
151
154
 
155
+ REM Verify plugin is actually installed (don't rely on exit code, as
156
+ REM "already at latest" may return non-zero)
157
+ call openclaw plugins list 2>nul | findstr /i "liangzimixin" >nul
152
158
  if %errorlevel% neq 0 (
153
- echo [ERROR] 插件安装/更新失败,请检查网络或权限
159
+ echo [ERROR] Plugin install/update failed, check network or permissions
154
160
  exit /b 1
155
161
  )
156
162
 
157
- echo [OK] 插件就绪
163
+ echo [OK] Plugin ready
158
164
  echo.
159
165
 
160
- REM ════════════════════════════════════════════════════════════
161
- REM Step 2: 写入配置
162
- REM ════════════════════════════════════════════════════════════
163
- echo [INFO] Step 2/4 写入配置...
166
+ REM ============================================================
167
+ REM Step 2: Write configuration
168
+ REM ============================================================
169
+ echo [INFO] Step 2/4 - Writing configuration...
164
170
 
165
- openclaw channels add --channel liangzimixin --token %APP_ID% --password %APP_SECRET% --user-id %QUANTUM_ACCOUNT%
171
+ if not "%QUANTUM_ACCOUNT%"=="" (
172
+ call openclaw channels add --channel liangzimixin --token %APP_ID% --password %APP_SECRET% --user-id %QUANTUM_ACCOUNT%
173
+ ) else (
174
+ call openclaw channels add --channel liangzimixin --token %APP_ID% --password %APP_SECRET%
175
+ )
166
176
 
167
177
  if %errorlevel% neq 0 (
168
- echo [ERROR] 配置写入失败,请检查参数是否正确
178
+ echo [ERROR] Configuration write failed, check parameters
169
179
  exit /b 1
170
180
  )
171
181
 
172
- echo [OK] 配置写入成功 ^(appId / appSecret / quantumAccount^)
182
+ if not "%QUANTUM_ACCOUNT%"=="" (
183
+ echo [OK] Configuration saved (appId / appSecret / quantumAccount)
184
+ ) else (
185
+ echo [OK] Configuration saved (appId / appSecret)
186
+ )
173
187
  echo.
174
188
 
175
- REM ════════════════════════════════════════════════════════════
176
- REM Step 3: 重启网关
177
- REM ════════════════════════════════════════════════════════════
178
- echo [INFO] Step 3/4 重启 OpenClaw 网关...
189
+ REM ============================================================
190
+ REM Step 3: Restart gateway
191
+ REM ============================================================
192
+ echo [INFO] Step 3/4 - Restarting OpenClaw gateway...
179
193
 
180
- openclaw gateway restart
194
+ call openclaw gateway restart
181
195
 
182
196
  if %errorlevel% neq 0 (
183
- echo [ERROR] 网关重启失败,请手动执行: openclaw gateway restart
184
- exit /b 1
197
+ echo [WARN] Gateway restart command returned an error or timed out
198
+ echo [WARN] Continuing to health check anyway...
199
+ ) else (
200
+ echo [OK] Gateway restart command sent
185
201
  )
186
-
187
- echo [OK] 网关重启指令已发送
188
202
  echo.
189
203
 
190
- REM ════════════════════════════════════════════════════════════
191
- REM Step 4: 健康检查
192
- REM ════════════════════════════════════════════════════════════
193
- echo [INFO] Step 4/4 健康检查...
204
+ REM ============================================================
205
+ REM Step 4: Health check
206
+ REM ============================================================
207
+ echo [INFO] Step 4/4 - Health check...
194
208
 
195
209
  set "MAX_RETRIES=3"
196
210
  set "RETRY_INTERVAL=5"
@@ -198,21 +212,21 @@ set "HEALTH_OK=0"
198
212
 
199
213
  for /l %%i in (1,1,%MAX_RETRIES%) do (
200
214
  if !HEALTH_OK!==0 (
201
- echo [INFO] 等待服务就绪 ^(%RETRY_INTERVAL%s^)...
215
+ echo [INFO] Waiting for service ^(%RETRY_INTERVAL%s^)...
202
216
  timeout /t %RETRY_INTERVAL% /nobreak >nul
203
217
 
204
- echo [INFO] %%i/%MAX_RETRIES% 次检查...
218
+ echo [INFO] Check %%i/%MAX_RETRIES%...
205
219
 
206
220
  set "GATEWAY_OK=0"
207
221
  set "CHANNEL_OK=0"
208
222
 
209
- openclaw status > "%TEMP%\lzmx_status.txt" 2>&1
223
+ call openclaw status > "%TEMP%\lzmx_status.txt" 2>&1
210
224
 
211
- REM 检查 gateway 状态 (reachable 在表格折行中)
225
+ REM Check gateway status
212
226
  findstr /i /c:"reachable" "%TEMP%\lzmx_status.txt" >nul 2>&1
213
227
  if !errorlevel!==0 set "GATEWAY_OK=1"
214
228
 
215
- REM 检查 QuantumIM 渠道状态 (同一行: │ QuantumIM │ ON │ OK │)
229
+ REM Check QuantumIM channel status
216
230
  findstr /i /c:"QuantumIM" "%TEMP%\lzmx_status.txt" | findstr /i /c:"OK" >nul 2>&1
217
231
  if !errorlevel!==0 set "CHANNEL_OK=1"
218
232
 
@@ -222,9 +236,9 @@ for /l %%i in (1,1,%MAX_RETRIES%) do (
222
236
  set "HEALTH_OK=1"
223
237
  ) else (
224
238
  if !GATEWAY_OK!==1 (
225
- echo [WARN] 网关已就绪,但渠道尚未连接...
239
+ echo [WARN] Gateway ready, but channel not connected...
226
240
  ) else (
227
- echo [WARN] 服务尚未就绪...
241
+ echo [WARN] Service not ready...
228
242
  )
229
243
  )
230
244
  )
@@ -234,26 +248,26 @@ echo.
234
248
  echo ---------------------------------------------------
235
249
 
236
250
  if %HEALTH_OK%==1 (
237
- echo [SUCCESS] 部署完成!
251
+ echo [SUCCESS] Deploy complete!
238
252
  echo.
239
253
  echo Health:
240
254
  echo gateway = reachable
241
255
  echo QuantumIM = OK
242
256
  echo.
243
- echo 查看详情: openclaw status
257
+ echo Details: openclaw status
244
258
  echo.
245
259
  ) else (
246
- echo [WARN] 部署已完成,但健康检查未通过
260
+ echo [WARN] Deploy finished, but health check failed
247
261
  echo.
248
- echo 可能原因:
249
- echo - 服务启动较慢,请稍后手动检查
250
- echo - 网络连接问题
251
- echo - 凭据配置有误
262
+ echo Possible causes:
263
+ echo - Service is still starting, check later
264
+ echo - Network connectivity issue
265
+ echo - Invalid credentials
252
266
  echo.
253
- echo 手动检查: openclaw status
254
- echo 查看日志: openclaw gateway logs
267
+ echo Manual check: openclaw status
268
+ echo View logs: openclaw gateway logs
255
269
  echo.
256
270
  exit /b 1
257
271
  )
258
272
 
259
- endlocal
273
+ endlocal
@@ -3,12 +3,11 @@ set -euo pipefail
3
3
 
4
4
  # ============================================================
5
5
  # liangzimixin — 一键部署脚本
6
- # 用法: ./liangzimixin_install.sh <appId> <appSecret> <quantumAccount>
6
+ # 用法: ./liangzimixin_install.sh <appId> <appSecret> [quantumAccount]
7
7
  # ============================================================
8
8
 
9
- SCRIPT_VERSION="0.3.69"
9
+ SCRIPT_VERSION="0.3.70"
10
10
  NPM_PACKAGE="liangzimixin"
11
- REMOTE_SCRIPT_URL="https://unpkg.com/${NPM_PACKAGE}@latest/scripts/liangzimixin_install.sh"
12
11
 
13
12
  # ── 颜色 ──────────────────────────────────────────────────────
14
13
  RED='\033[0;31m'
@@ -24,11 +23,13 @@ fail() { echo -e "${RED}✗${NC} $*"; exit 1; }
24
23
 
25
24
  # ── 参数解析 ──────────────────────────────────────────────────
26
25
  SKIP_SELF_UPDATE=false
26
+ USE_BETA=false
27
27
  POSITIONAL_ARGS=()
28
28
 
29
29
  for arg in "$@"; do
30
30
  case $arg in
31
31
  --skip-self-update) SKIP_SELF_UPDATE=true ;;
32
+ --beta) USE_BETA=true ;;
32
33
  *) POSITIONAL_ARGS+=("$arg") ;;
33
34
  esac
34
35
  done
@@ -37,19 +38,27 @@ APP_ID="${POSITIONAL_ARGS[0]:-}"
37
38
  APP_SECRET="${POSITIONAL_ARGS[1]:-}"
38
39
  QUANTUM_ACCOUNT="${POSITIONAL_ARGS[2]:-}"
39
40
 
40
- if [ -z "$APP_ID" ] || [ -z "$APP_SECRET" ] || [ -z "$QUANTUM_ACCOUNT" ]; then
41
+ TAG="latest"
42
+ if [ "$USE_BETA" = true ]; then
43
+ TAG="beta"
44
+ fi
45
+
46
+ REMOTE_SCRIPT_URL="https://unpkg.com/${NPM_PACKAGE}@${TAG}/scripts/liangzimixin_install.sh"
47
+
48
+ if [ -z "$APP_ID" ] || [ -z "$APP_SECRET" ]; then
41
49
  echo ""
42
50
  echo "liangzimixin 一键部署脚本 v${SCRIPT_VERSION}"
43
51
  echo ""
44
- echo "用法: $0 <appId> <appSecret> <quantumAccount> [--skip-self-update]"
52
+ echo "用法: $0 <appId> <appSecret> [quantumAccount] [--skip-self-update] [--beta]"
45
53
  echo ""
46
54
  echo "参数:"
47
- echo " appId 平台应用 ID"
48
- echo " appSecret 应用密钥"
49
- echo " quantumAccount 量子账户标识"
55
+ echo " appId 平台应用 ID (必填)"
56
+ echo " appSecret 应用密钥 (必填)"
57
+ echo " quantumAccount 量子账户标识 (可选)"
50
58
  echo ""
51
59
  echo "选项:"
52
60
  echo " --skip-self-update 跳过脚本自更新检查"
61
+ echo " --beta 安装 beta 测试版本"
53
62
  echo ""
54
63
  exit 1
55
64
  fi
@@ -101,7 +110,7 @@ if [ "$SKIP_SELF_UPDATE" = false ]; then
101
110
  if command -v curl &>/dev/null; then
102
111
  # 从 npm registry 获取最新版本号
103
112
  REMOTE_VERSION=$(curl -fsSL --connect-timeout 5 --max-time 10 \
104
- "https://registry.npmjs.org/${NPM_PACKAGE}/latest" 2>/dev/null \
113
+ "https://registry.npmjs.org/${NPM_PACKAGE}/${TAG}" 2>/dev/null \
105
114
  | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4) || true
106
115
  fi
107
116
 
@@ -117,7 +126,15 @@ if [ "$SKIP_SELF_UPDATE" = false ]; then
117
126
  if curl -fsSL --connect-timeout 10 --max-time 30 "$REMOTE_SCRIPT_URL" -o "$TEMP_SCRIPT" 2>/dev/null; then
118
127
  chmod +x "$TEMP_SCRIPT"
119
128
  success "脚本已更新到 v${REMOTE_VERSION}"
120
- exec "$TEMP_SCRIPT" --skip-self-update "$APP_ID" "$APP_SECRET" "$QUANTUM_ACCOUNT"
129
+ EXEC_ARGS=(--skip-self-update)
130
+ if [ "$USE_BETA" = true ]; then
131
+ EXEC_ARGS+=(--beta)
132
+ fi
133
+ EXEC_ARGS+=("$APP_ID" "$APP_SECRET")
134
+ if [ -n "$QUANTUM_ACCOUNT" ]; then
135
+ EXEC_ARGS+=("$QUANTUM_ACCOUNT")
136
+ fi
137
+ exec "$TEMP_SCRIPT" "${EXEC_ARGS[@]}"
121
138
  else
122
139
  warn "脚本下载失败,使用当前版本继续执行"
123
140
  rm -f "$TEMP_SCRIPT"
@@ -137,17 +154,17 @@ echo ""
137
154
  # ══════════════════════════════════════════════════════════════
138
155
  # Step 1: 安装 / 更新插件
139
156
  # ══════════════════════════════════════════════════════════════
140
- info "Step 1/4 — 安装/更新 liangzimixin 插件..."
157
+ info "Step 1/4 — 安装/更新 liangzimixin 插件 (${TAG})..."
141
158
 
142
159
  if openclaw plugins list 2>/dev/null | grep -q "liangzimixin"; then
143
- info "插件已安装,正在更新到最新版本..."
160
+ info "插件已安装,正在更新..."
144
161
  if ! openclaw plugins update liangzimixin; then
145
162
  fail "插件更新失败,请检查网络或权限"
146
163
  fi
147
164
  success "插件更新完成"
148
165
  else
149
- info "插件未安装,正在安装最新版本..."
150
- if ! openclaw plugins install liangzimixin@latest; then
166
+ info "插件未安装,正在安装..."
167
+ if ! openclaw plugins install liangzimixin@${TAG}; then
151
168
  fail "插件安装失败,请检查网络或权限"
152
169
  fi
153
170
  success "插件安装完成"
@@ -160,15 +177,20 @@ echo ""
160
177
  # ══════════════════════════════════════════════════════════════
161
178
  info "Step 2/4 — 写入配置..."
162
179
 
163
- if ! openclaw channels add \
164
- --channel liangzimixin \
165
- --token "$APP_ID" \
166
- --password "$APP_SECRET" \
167
- --user-id "$QUANTUM_ACCOUNT"; then
180
+ CHANNEL_ARGS=(--channel liangzimixin --token "$APP_ID" --password "$APP_SECRET")
181
+ if [ -n "$QUANTUM_ACCOUNT" ]; then
182
+ CHANNEL_ARGS+=(--user-id "$QUANTUM_ACCOUNT")
183
+ fi
184
+
185
+ if ! openclaw channels add "${CHANNEL_ARGS[@]}"; then
168
186
  fail "配置写入失败,请检查参数是否正确"
169
187
  fi
170
188
 
171
- success "配置写入成功 (appId / appSecret / quantumAccount)"
189
+ if [ -n "$QUANTUM_ACCOUNT" ]; then
190
+ success "配置写入成功 (appId / appSecret / quantumAccount)"
191
+ else
192
+ success "配置写入成功 (appId / appSecret)"
193
+ fi
172
194
  echo ""
173
195
 
174
196
  # ══════════════════════════════════════════════════════════════
@@ -177,10 +199,10 @@ echo ""
177
199
  info "Step 3/4 — 重启 OpenClaw 网关..."
178
200
 
179
201
  if ! openclaw gateway restart; then
180
- fail "网关重启失败,请手动执行: openclaw gateway restart"
202
+ warn "网关重启指令返回错误或超时,将继续进行健康检查..."
203
+ else
204
+ success "网关重启指令已发送"
181
205
  fi
182
-
183
- success "网关重启指令已发送"
184
206
  echo ""
185
207
 
186
208
  # ══════════════════════════════════════════════════════════════