liangzimixin 0.3.43 → 0.3.44

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
@@ -17441,6 +17441,9 @@ function parseChannelAddress(address) {
17441
17441
  const prefix = `${CHANNEL_ID}:`;
17442
17442
  return address.startsWith(prefix) ? address.slice(prefix.length) : address;
17443
17443
  }
17444
+ var GUIDE_URL = "https://www.kdocs.cn/l/ca5LX4wxpSSE";
17445
+ var HINT_NO_QUANTUM_ACCOUNT = "\u60A8\u672A\u914D\u7F6E\u91CF\u5B50\u52A0\u5BC6\u53C2\u6570\uFF0C\u8BF7\u53C2\u8003[\u300A\u63A5\u5165\u6307\u5357\u300B\u5B89\u88C5\u91CF\u5B50\u5BC6\u4FE1\u63D2\u4EF6](" + GUIDE_URL + ") \u4FEE\u6539\u914D\u7F6E";
17446
+ var HINT_PLAINTEXT_NOT_SUPPORTED = "\u5F53\u524D\u6A21\u5F0F\u4E0D\u652F\u6301\u666E\u901A\u6D88\u606F\uFF0C\u8BF7\u53C2\u8003[\u300A\u63A5\u5165\u6307\u5357\u300B\u5B89\u88C5\u91CF\u5B50\u5BC6\u4FE1\u63D2\u4EF6](" + GUIDE_URL + ") \u4FEE\u6539\u914D\u7F6E";
17444
17447
 
17445
17448
  // src/config.ts
17446
17449
  var AccountConfigSchema = external_exports.object({
@@ -17462,7 +17465,9 @@ var AccountConfigSchema = external_exports.object({
17462
17465
  */
17463
17466
  botUserId: external_exports.string().optional(),
17464
17467
  /** 🟡 部署环境 — staging=联调, production=线上 */
17465
- env: external_exports.enum(["staging", "production"]).optional()
17468
+ env: external_exports.enum(["staging", "production"]).optional(),
17469
+ /** 🟡 消息加密模式 — quantum_only=仅加密, quantum_and_plain=加密+普通(默认) */
17470
+ encryptionMode: external_exports.enum(["quantum_only", "quantum_and_plain"]).default("quantum_and_plain")
17466
17471
  }).transform((raw) => ({
17467
17472
  appId: raw.appId,
17468
17473
  appSecret: raw.appSecret,
@@ -17471,7 +17476,8 @@ var AccountConfigSchema = external_exports.object({
17471
17476
  // quantumAppId: raw.quantumAppId,
17472
17477
  // quantumAppSecret: raw.quantumAppSecret,
17473
17478
  botUserId: raw.botUserId,
17474
- env: raw.env
17479
+ env: raw.env,
17480
+ encryptionMode: raw.encryptionMode
17475
17481
  }));
17476
17482
  var ENV_PRESETS = {
17477
17483
  /** 联调环境 */
@@ -18180,13 +18186,15 @@ var quantumImOutbound = {
18180
18186
  }
18181
18187
  const messagePipe = messagePipeGetter();
18182
18188
  const chatId = parseChannelAddress(to);
18189
+ const encryptionMode = pluginConfigGetter?.()?.credentials?.encryptionMode;
18183
18190
  const isEncrypted = getOutboundEncryptionStatus(chatId);
18191
+ const skipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted !== true;
18184
18192
  await messagePipe.sendMessage({
18185
18193
  chatId,
18186
18194
  senderId: chatId,
18187
18195
  msgType: "markdown",
18188
18196
  content: JSON.stringify({ content: text }),
18189
- skipEncrypt: isEncrypted !== true
18197
+ skipEncrypt
18190
18198
  });
18191
18199
  return { channel: CHANNEL_ID, messageId: "", chatId };
18192
18200
  },
@@ -18201,7 +18209,9 @@ var quantumImOutbound = {
18201
18209
  const sdkRuntime = sdkRuntimeGetter();
18202
18210
  const chatId = parseChannelAddress(to);
18203
18211
  const fileCfg = pluginConfigGetter().file;
18212
+ const encryptionMode = pluginConfigGetter?.()?.credentials?.encryptionMode;
18204
18213
  const isEncrypted = getOutboundEncryptionStatus(chatId);
18214
+ const skipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted !== true;
18205
18215
  const result = await resolveAndUploadMedia({
18206
18216
  mediaUrl: mediaUrl ?? void 0,
18207
18217
  tokenManager: tokenManagerGetter(),
@@ -18214,7 +18224,7 @@ var quantumImOutbound = {
18214
18224
  maxFileSizeMb: fileCfg.maxFileSizeMb,
18215
18225
  chunkSizeMb: fileCfg.chunkSizeMb,
18216
18226
  timeoutMs: fileCfg.fetchTimeoutMs,
18217
- skipEncrypt: isEncrypted !== true
18227
+ skipEncrypt
18218
18228
  });
18219
18229
  if (result.warning) {
18220
18230
  log4.warn("sendMedia:degraded", { chatId, warning: result.warning });
@@ -18515,8 +18525,10 @@ function createQuantumImDeliverFn(deps) {
18515
18525
  maxFileSizeMb,
18516
18526
  chunkSizeMb,
18517
18527
  timeoutMs,
18518
- isEncrypted
18528
+ isEncrypted,
18529
+ encryptionMode
18519
18530
  } = deps;
18531
+ const shouldSkipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted === false;
18520
18532
  const deliver = async (payload) => {
18521
18533
  const mediaUrls = [];
18522
18534
  if (payload.mediaUrls?.length) {
@@ -18534,7 +18546,7 @@ function createQuantumImDeliverFn(deps) {
18534
18546
  msgType: "markdown",
18535
18547
  content: JSON.stringify({ content: payload.text }),
18536
18548
  replyToMessageId,
18537
- skipEncrypt: isEncrypted === false
18549
+ skipEncrypt: shouldSkipEncrypt
18538
18550
  });
18539
18551
  log11.info("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u5DF2\u53D1\u9001", {
18540
18552
  chatId,
@@ -18557,7 +18569,7 @@ function createQuantumImDeliverFn(deps) {
18557
18569
  maxFileSizeMb,
18558
18570
  chunkSizeMb,
18559
18571
  timeoutMs,
18560
- skipEncrypt: isEncrypted === false
18572
+ skipEncrypt: shouldSkipEncrypt
18561
18573
  });
18562
18574
  if (result.warning) {
18563
18575
  log11.warn("\u{1F4E4} \u5A92\u4F53\u53D1\u9001\u964D\u7EA7", { chatId, mediaUrl, warning: result.warning });
@@ -18576,7 +18588,7 @@ function createQuantumImDeliverFn(deps) {
18576
18588
  msgType: "markdown",
18577
18589
  content: JSON.stringify({ content: `\u{1F4CE} ${mediaUrl}` }),
18578
18590
  replyToMessageId,
18579
- skipEncrypt: isEncrypted === false
18591
+ skipEncrypt: shouldSkipEncrypt
18580
18592
  });
18581
18593
  }
18582
18594
  }
@@ -18626,12 +18638,13 @@ var InboundPipeline = class {
18626
18638
  });
18627
18639
  return;
18628
18640
  }
18641
+ const feedbackSkipEncrypt = this.deps.pluginConfig.credentials.encryptionMode === "quantum_only" ? false : true;
18629
18642
  this.deps.messagePipe.sendMessage({
18630
18643
  chatId: msg.chatId,
18631
18644
  senderId: msg.senderId,
18632
18645
  msgType: "text",
18633
18646
  content: JSON.stringify({ content: "\u4EFB\u52A1\u5DF2\u6536\u5230\u{1F44C}\uFF0C\u6B63\u5728\u5904\u7406\u4E2D..." }),
18634
- skipEncrypt: true
18647
+ skipEncrypt: feedbackSkipEncrypt
18635
18648
  }).catch((err) => {
18636
18649
  log12.warn("\u26A0\uFE0F \u5373\u65F6\u53CD\u9988\u53D1\u9001\u5931\u8D25", { error: err.message });
18637
18650
  });
@@ -18659,7 +18672,8 @@ var InboundPipeline = class {
18659
18672
  allowPrivateNetwork: this.deps.pluginConfig.file.allowPrivateNetwork,
18660
18673
  maxFileSizeMb: this.deps.pluginConfig.file.maxFileSizeMb,
18661
18674
  timeoutMs: this.deps.pluginConfig.file.fetchTimeoutMs,
18662
- isEncrypted: msg.isEncrypted
18675
+ isEncrypted: msg.isEncrypted,
18676
+ encryptionMode: this.deps.pluginConfig.credentials.encryptionMode
18663
18677
  });
18664
18678
  const { dispatcher, replyOptions } = core.channel.reply.createReplyDispatcherWithTyping({
18665
18679
  deliver
@@ -18759,6 +18773,13 @@ var QUANTUM_IM_CONFIG_JSON_SCHEMA = {
18759
18773
  description: "\u9009\u62E9\u5BF9\u63A5\u7684\u670D\u52A1\u5668\u73AF\u5883\u3002staging = \u8054\u8C03\u73AF\u5883\uFF0Cproduction = \u7EBF\u4E0A\u73AF\u5883",
18760
18774
  enum: ["staging", "production"],
18761
18775
  default: "production"
18776
+ },
18777
+ encryptionMode: {
18778
+ type: "string",
18779
+ title: "\u6D88\u606F\u52A0\u5BC6\u6A21\u5F0F",
18780
+ description: "quantum_only = \u4EC5\u91CF\u5B50\u52A0\u5BC6\u6D88\u606F\uFF08\u6240\u6709\u6D88\u606F\u5FC5\u987B\u52A0\u5BC6\uFF09\uFF1Bquantum_and_plain = \u540C\u65F6\u652F\u6301\u52A0\u5BC6\u548C\u666E\u901A\u6D88\u606F",
18781
+ enum: ["quantum_only", "quantum_and_plain"],
18782
+ default: "quantum_and_plain"
18762
18783
  }
18763
18784
  }
18764
18785
  };
@@ -18853,33 +18874,39 @@ var quantumImOnboarding = {
18853
18874
  ].join("\n"),
18854
18875
  "\u91CF\u5B50\u5BC6\u4FE1 \u2014 \u51ED\u636E\u914D\u7F6E"
18855
18876
  );
18856
- const appId = await prompter.text({
18857
- message: "\u5E94\u7528 ID (appId)",
18858
- initialValue: existing.appId ?? void 0,
18859
- validate: required2
18860
- });
18861
- const appSecret = await prompter.text({
18862
- message: "\u5E94\u7528\u5BC6\u94A5 (appSecret)",
18863
- initialValue: existing.appSecret ?? void 0,
18864
- validate: required2
18865
- });
18866
- const encryptionMode = await prompter.select({
18867
- message: "\u6D88\u606F\u4F20\u8F93\u6A21\u5F0F",
18877
+ const encryptionModeChoice = await prompter.select({
18878
+ message: "\u8BF7\u9009\u62E9\u6D88\u606F\u4EA4\u4E92\u65B9\u5F0F",
18868
18879
  options: [
18869
- { value: "plain", label: "\u660E\u6587\u6A21\u5F0F\uFF08\u4E0D\u52A0\u5BC6\uFF09", hint: "\u9002\u5408\u6D4B\u8BD5\u6216\u4E0D\u9700\u8981\u52A0\u5BC6\u7684\u573A\u666F" },
18870
- { value: "quantum", label: "\u91CF\u5B50\u52A0\u5BC6\u6A21\u5F0F", hint: "\u9700\u8981\u91CF\u5B50\u8D26\u6237\u6807\u8BC6" }
18880
+ { value: "quantum_only", label: "\u4EC5\u91CF\u5B50\u52A0\u5BC6\u6D88\u606F" },
18881
+ { value: "quantum_and_plain", label: "\u91CF\u5B50\u52A0\u5BC6+\u666E\u901A\u6D88\u606F\uFF08\u82E5\u91CF\u5B50\u52A0\u5BC6\u7981\u7528\uFF0C\u5219\u7559\u7A7A\uFF09" }
18871
18882
  ],
18872
- initialValue: existing.quantumAccount?.trim() ? "quantum" : "plain"
18883
+ initialValue: existing.encryptionMode ?? "quantum_and_plain"
18873
18884
  });
18874
18885
  let quantumAccount = "";
18875
- if (encryptionMode === "quantum") {
18886
+ if (encryptionModeChoice === "quantum_only") {
18876
18887
  quantumAccount = await prompter.text({
18877
18888
  message: "\u91CF\u5B50\u8D26\u6237\u6807\u8BC6 (quantumAccount)",
18878
18889
  placeholder: "\u5982: 17100001111",
18879
18890
  initialValue: existing.quantumAccount ?? void 0,
18880
18891
  validate: required2
18881
18892
  });
18893
+ } else {
18894
+ quantumAccount = await prompter.text({
18895
+ message: "\u91CF\u5B50\u8D26\u6237\u6807\u8BC6\uFF08\u53EF\u9009\uFF0C\u4E0D\u586B\u5219\u4EC5\u652F\u6301\u660E\u6587\uFF09",
18896
+ placeholder: "\u5982: 17100001111",
18897
+ initialValue: existing.quantumAccount ?? void 0
18898
+ });
18882
18899
  }
18900
+ const appId = await prompter.text({
18901
+ message: "\u5E94\u7528 ID (appId)",
18902
+ initialValue: existing.appId ?? void 0,
18903
+ validate: required2
18904
+ });
18905
+ const appSecret = await prompter.text({
18906
+ message: "\u5E94\u7528\u5BC6\u94A5 (appSecret)",
18907
+ initialValue: existing.appSecret ?? void 0,
18908
+ validate: required2
18909
+ });
18883
18910
  const channels = cfg.channels ?? {};
18884
18911
  const channelCfg = channels[CHANNEL_ID] ?? {};
18885
18912
  const accounts = channelCfg.accounts ?? {};
@@ -18895,10 +18922,8 @@ var quantumImOnboarding = {
18895
18922
  default: {
18896
18923
  appId: appId.trim(),
18897
18924
  appSecret: appSecret.trim(),
18898
- // pin: pin.trim(),
18925
+ encryptionMode: encryptionModeChoice,
18899
18926
  ...quantumAccount.trim() ? { quantumAccount: quantumAccount.trim() } : {}
18900
- // quantumAppId: quantumAppId.trim(),
18901
- // quantumAppSecret: quantumAppSecret.trim(),
18902
18927
  }
18903
18928
  }
18904
18929
  }
@@ -20042,6 +20067,8 @@ var MessagePipe = class {
20042
20067
  messageCallback = null;
20043
20068
  /** 量子账户标识 — 用于判断是否具备解密能力 */
20044
20069
  quantumAccount;
20070
+ /** 消息加密模式 */
20071
+ encryptionMode;
20045
20072
  constructor(deps) {
20046
20073
  this.wsClient = deps.wsClient;
20047
20074
  this.dedup = deps.dedup;
@@ -20049,6 +20076,7 @@ var MessagePipe = class {
20049
20076
  this.tokenFn = deps.tokenFn;
20050
20077
  this.messageServiceBaseUrl = deps.messageServiceBaseUrl;
20051
20078
  this.quantumAccount = deps.quantumAccount;
20079
+ this.encryptionMode = deps.encryptionMode;
20052
20080
  this.wsClient.on("message", (rawData) => {
20053
20081
  this.handleInbound(rawData).catch((err) => {
20054
20082
  log21.error("\u274C \u5165\u7AD9\u5904\u7406\u5F02\u5E38", { step: "handleInbound", error: err.message });
@@ -20074,32 +20102,38 @@ var MessagePipe = class {
20074
20102
  * POST {messageServiceBaseUrl}/messages/v1/send
20075
20103
  * 字段映射: senderId → receive_id, msgType → msg_type
20076
20104
  *
20077
- * 加密模式 (quantumAccount 已配置):
20078
- * - 明文 content 加密后放入 extra.encryptMsg
20079
- * - extra.sessionId / extra.cryptoIv 由加密函数返回
20080
- * - 原始 content 置为空字符串 {content: ''}
20081
- * - extra 序列化为 JSON 字符串
20082
- *
20083
- * 失败不抛异常,避免阻塞 SDK deliver 回调。
20105
+ * 加密行为取决于 encryptionMode:
20106
+ * - quantum_only: 强制加密,失败直接抛错(不降级明文)
20107
+ * - quantum_and_plain: 加密失败降级明文(保持兼容)
20084
20108
  */
20085
20109
  async sendMessage(msg) {
20086
20110
  let content = msg.content;
20087
20111
  let extra = "";
20088
- if (this.quantumAccount && !msg.skipEncrypt) {
20089
- try {
20112
+ if (!msg.skipEncrypt && this.quantumAccount) {
20113
+ if (this.encryptionMode === "quantum_only") {
20090
20114
  const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
20091
20115
  extra = JSON.stringify({
20092
20116
  cryptoIv: iv,
20093
- // 随机生成的 IV,解密时需要回传
20094
20117
  encryptMsg: ciphertext,
20095
20118
  sessionId: keyId
20096
20119
  });
20097
20120
  content = JSON.stringify({ content: "" });
20098
- log21.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6", { sessionId: keyId });
20099
- } catch (err) {
20100
- log21.error("\u274C \u51FA\u7AD9\u6D88\u606F\u52A0\u5BC6\u5931\u8D25\uFF0C\u5C06\u53D1\u9001\u660E\u6587", {
20101
- error: err.message
20102
- });
20121
+ log21.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_only)", { sessionId: keyId });
20122
+ } else {
20123
+ try {
20124
+ const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
20125
+ extra = JSON.stringify({
20126
+ cryptoIv: iv,
20127
+ encryptMsg: ciphertext,
20128
+ sessionId: keyId
20129
+ });
20130
+ content = JSON.stringify({ content: "" });
20131
+ log21.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_and_plain)", { sessionId: keyId });
20132
+ } catch (err) {
20133
+ log21.error("\u274C \u51FA\u7AD9\u6D88\u606F\u52A0\u5BC6\u5931\u8D25\uFF0C\u5C06\u53D1\u9001\u660E\u6587", {
20134
+ error: err.message
20135
+ });
20136
+ }
20103
20137
  }
20104
20138
  }
20105
20139
  const body = {
@@ -20234,44 +20268,45 @@ var MessagePipe = class {
20234
20268
  const extra = callbackData.extra;
20235
20269
  const isEncrypted = typeof extra === "string" && extra.length > 0;
20236
20270
  let contentObj;
20237
- if (isEncrypted) {
20271
+ if (this.encryptionMode === "quantum_only") {
20238
20272
  if (!this.quantumAccount) {
20239
- log21.error("\u274C \u6536\u5230\u52A0\u5BC6\u6D88\u606F\u4F46\u672A\u914D\u7F6E\u91CF\u5B50\u5BC6\u94A5\uFF0C\u89E3\u5BC6\u5931\u8D25\u3002\u8BF7\u5728 openclaw.json \u4E2D\u914D\u7F6E quantumAccount", {
20240
- msgUid: callbackData.msgUid,
20241
- eventType: callbackData.eventType
20242
- });
20273
+ log21.info("\u26A0\uFE0F quantum_only: \u672A\u914D\u7F6E quantumAccount\uFF0C\u53D1\u9001\u5F15\u5BFC\u63D0\u793A", { chatId: callbackData.userId });
20274
+ await this.sendHintMessage(callbackData, HINT_NO_QUANTUM_ACCOUNT);
20275
+ return;
20276
+ }
20277
+ if (!isEncrypted) {
20278
+ log21.info("\u26A0\uFE0F quantum_only: \u6536\u5230\u660E\u6587\u6D88\u606F\uFF0C\u62D2\u7EDD\u5904\u7406", { msgUid: callbackData.msgUid });
20279
+ await this.sendHintMessage(callbackData, HINT_PLAINTEXT_NOT_SUPPORTED);
20243
20280
  return;
20244
20281
  }
20245
20282
  try {
20246
- const extraData = JSON.parse(extra);
20247
- const encryptMsg = extraData.encryptMsg ?? "";
20248
- const sessionId = extraData.sessionId ?? "";
20249
- if (!encryptMsg) {
20250
- log21.warn("\u26A0\uFE0F extra \u4E2D encryptMsg \u4E3A\u7A7A\uFF0C\u8DF3\u8FC7\u89E3\u5BC6", { msgUid: callbackData.msgUid });
20251
- contentObj = callbackData.content;
20252
- } else {
20253
- const cryptoIv = extraData.cryptoIv ?? "";
20254
- const decrypted = await this.crypto.decrypt(encryptMsg, sessionId, cryptoIv);
20255
- try {
20256
- contentObj = JSON.parse(decrypted);
20257
- } catch {
20258
- contentObj = { content: decrypted };
20259
- }
20260
- log21.debug("\u{1F513} \u6D88\u606F\u89E3\u5BC6\u6210\u529F", {
20261
- msgUid: callbackData.msgUid,
20262
- sessionId,
20263
- hasIv: Boolean(extraData.cryptoIv)
20264
- });
20265
- }
20283
+ contentObj = await this.decryptExtra(callbackData);
20266
20284
  } catch (err) {
20267
- log21.error("\u274C \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
20285
+ log21.error("\u274C quantum_only: \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
20268
20286
  msgUid: callbackData.msgUid,
20269
20287
  error: err.message
20270
20288
  });
20271
20289
  return;
20272
20290
  }
20273
20291
  } else {
20274
- contentObj = callbackData.content;
20292
+ if (isEncrypted) {
20293
+ if (!this.quantumAccount) {
20294
+ log21.info("\u26A0\uFE0F quantum_and_plain: \u6536\u5230\u52A0\u5BC6\u6D88\u606F\u4F46\u672A\u914D\u7F6E quantumAccount", { msgUid: callbackData.msgUid });
20295
+ await this.sendHintMessage(callbackData, HINT_NO_QUANTUM_ACCOUNT);
20296
+ return;
20297
+ }
20298
+ try {
20299
+ contentObj = await this.decryptExtra(callbackData);
20300
+ } catch (err) {
20301
+ log21.error("\u274C quantum_and_plain: \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
20302
+ msgUid: callbackData.msgUid,
20303
+ error: err.message
20304
+ });
20305
+ return;
20306
+ }
20307
+ } else {
20308
+ contentObj = callbackData.content;
20309
+ }
20275
20310
  }
20276
20311
  if (callbackData.eventType !== "callback:direct") {
20277
20312
  log21.debug("\u2139\uFE0F \u5FFD\u7565\u975E\u76EE\u6807\u4E8B\u4EF6", { eventType: callbackData.eventType });
@@ -20298,6 +20333,48 @@ var MessagePipe = class {
20298
20333
  log21.warn("\u26A0\uFE0F \u6D88\u606F\u56DE\u8C03\u672A\u6CE8\u518C\uFF0C\u65E0\u6CD5\u5904\u7406", { messageId: msg.messageId });
20299
20334
  }
20300
20335
  }
20336
+ // ── 私有辅助方法 ──
20337
+ /**
20338
+ * 发送系统提示消息(明文,不加密)。
20339
+ * 用于在模式不匹配时向用户发送引导信息。
20340
+ */
20341
+ async sendHintMessage(callbackData, hintText) {
20342
+ const chatId = callbackData.groupId || callbackData.userId;
20343
+ const senderId = callbackData.userId;
20344
+ try {
20345
+ await this.sendMessage({
20346
+ chatId,
20347
+ senderId,
20348
+ msgType: "markdown",
20349
+ content: JSON.stringify({ content: hintText }),
20350
+ skipEncrypt: true
20351
+ });
20352
+ log21.info("\u{1F4A1} \u5DF2\u53D1\u9001\u6A21\u5F0F\u63D0\u793A\u6D88\u606F", { chatId });
20353
+ } catch (err) {
20354
+ log21.error("\u274C \u63D0\u793A\u6D88\u606F\u53D1\u9001\u5931\u8D25", { error: err.message });
20355
+ }
20356
+ }
20357
+ /**
20358
+ * 解密 extra 中的加密内容。
20359
+ * @throws 解密失败时抛出异常
20360
+ */
20361
+ async decryptExtra(callbackData) {
20362
+ const extraData = JSON.parse(callbackData.extra);
20363
+ const encryptMsg = extraData.encryptMsg ?? "";
20364
+ const sessionId = extraData.sessionId ?? "";
20365
+ if (!encryptMsg) {
20366
+ log21.warn("\u26A0\uFE0F extra \u4E2D encryptMsg \u4E3A\u7A7A\uFF0C\u4F7F\u7528\u539F\u59CB content", { msgUid: callbackData.msgUid });
20367
+ return callbackData.content;
20368
+ }
20369
+ const cryptoIv = extraData.cryptoIv ?? "";
20370
+ const decrypted = await this.crypto.decrypt(encryptMsg, sessionId, cryptoIv);
20371
+ log21.debug("\u{1F513} \u6D88\u606F\u89E3\u5BC6\u6210\u529F", { msgUid: callbackData.msgUid, sessionId, hasIv: Boolean(cryptoIv) });
20372
+ try {
20373
+ return JSON.parse(decrypted);
20374
+ } catch {
20375
+ return { content: decrypted };
20376
+ }
20377
+ }
20301
20378
  };
20302
20379
 
20303
20380
  // src/transport/connection-manager.ts
@@ -20933,7 +21010,8 @@ async function startPlugin(accountConfig, internalOverrides) {
20933
21010
  crypto: cryptoEngine,
20934
21011
  tokenFn: () => tokenManager.getValidToken(),
20935
21012
  messageServiceBaseUrl: config2.message.messageServiceBaseUrl,
20936
- quantumAccount: accountConfig.quantumAccount
21013
+ quantumAccount: accountConfig.quantumAccount,
21014
+ encryptionMode: accountConfig.encryptionMode
20937
21015
  });
20938
21016
  const connectionManager = new ConnectionManager(wsClient, {
20939
21017
  heartbeatIntervalMs: config2.transport.heartbeatIntervalMs,
package/dist/index.d.cts CHANGED
@@ -23,15 +23,21 @@ declare const AccountConfigSchema: z.ZodPipe<z.ZodObject<{
23
23
  staging: "staging";
24
24
  production: "production";
25
25
  }>>;
26
+ encryptionMode: z.ZodDefault<z.ZodEnum<{
27
+ quantum_only: "quantum_only";
28
+ quantum_and_plain: "quantum_and_plain";
29
+ }>>;
26
30
  }, z.core.$strip>, z.ZodTransform<{
27
31
  appId: string;
28
32
  appSecret: string;
29
33
  quantumAccount: string | undefined;
30
34
  botUserId: string | undefined;
31
35
  env: "staging" | "production" | undefined;
36
+ encryptionMode: "quantum_only" | "quantum_and_plain";
32
37
  }, {
33
38
  appId: string;
34
39
  appSecret: string;
40
+ encryptionMode: "quantum_only" | "quantum_and_plain";
35
41
  quantumAccount?: string | undefined;
36
42
  botUserId?: string | undefined;
37
43
  env?: "staging" | "production" | undefined;
@@ -757,6 +763,8 @@ declare class MessagePipe {
757
763
  private messageCallback;
758
764
  /** 量子账户标识 — 用于判断是否具备解密能力 */
759
765
  private readonly quantumAccount?;
766
+ /** 消息加密模式 */
767
+ private readonly encryptionMode;
760
768
  constructor(deps: {
761
769
  wsClient: WSClient;
762
770
  dedup: MessageDedup;
@@ -767,6 +775,8 @@ declare class MessagePipe {
767
775
  messageServiceBaseUrl: string;
768
776
  /** 量子账户标识 — 用于判断是否具备解密能力 */
769
777
  quantumAccount?: string;
778
+ /** 消息加密模式 */
779
+ encryptionMode: 'quantum_only' | 'quantum_and_plain';
770
780
  });
771
781
  /** 注册入站消息回调 — 解密后的消息会通过此回调传给 L4 层 */
772
782
  onMessage(callback: (msg: InboundMessage) => void): void;
@@ -781,13 +791,9 @@ declare class MessagePipe {
781
791
  * POST {messageServiceBaseUrl}/messages/v1/send
782
792
  * 字段映射: senderId → receive_id, msgType → msg_type
783
793
  *
784
- * 加密模式 (quantumAccount 已配置):
785
- * - 明文 content 加密后放入 extra.encryptMsg
786
- * - extra.sessionId / extra.cryptoIv 由加密函数返回
787
- * - 原始 content 置为空字符串 {content: ''}
788
- * - extra 序列化为 JSON 字符串
789
- *
790
- * 失败不抛异常,避免阻塞 SDK deliver 回调。
794
+ * 加密行为取决于 encryptionMode:
795
+ * - quantum_only: 强制加密,失败直接抛错(不降级明文)
796
+ * - quantum_and_plain: 加密失败降级明文(保持兼容)
791
797
  */
792
798
  sendMessage(msg: OutboundMessage): Promise<void>;
793
799
  /**
@@ -822,6 +828,16 @@ declare class MessagePipe {
822
828
  * 7. 回调 L4
823
829
  */
824
830
  private handleInbound;
831
+ /**
832
+ * 发送系统提示消息(明文,不加密)。
833
+ * 用于在模式不匹配时向用户发送引导信息。
834
+ */
835
+ private sendHintMessage;
836
+ /**
837
+ * 解密 extra 中的加密内容。
838
+ * @throws 解密失败时抛出异常
839
+ */
840
+ private decryptExtra;
825
841
  }
826
842
 
827
843
  /**
@@ -1141,6 +1157,13 @@ declare const QUANTUM_IM_CONFIG_JSON_SCHEMA: {
1141
1157
  readonly enum: readonly ["staging", "production"];
1142
1158
  readonly default: "production";
1143
1159
  };
1160
+ readonly encryptionMode: {
1161
+ readonly type: "string";
1162
+ readonly title: "消息加密模式";
1163
+ readonly description: "quantum_only = 仅量子加密消息(所有消息必须加密);quantum_and_plain = 同时支持加密和普通消息";
1164
+ readonly enum: readonly ["quantum_only", "quantum_and_plain"];
1165
+ readonly default: "quantum_and_plain";
1166
+ };
1144
1167
  };
1145
1168
  };
1146
1169
 
@@ -3688,6 +3688,9 @@ function parseChannelAddress(address) {
3688
3688
  const prefix = `${CHANNEL_ID}:`;
3689
3689
  return address.startsWith(prefix) ? address.slice(prefix.length) : address;
3690
3690
  }
3691
+ var GUIDE_URL = "https://www.kdocs.cn/l/ca5LX4wxpSSE";
3692
+ var HINT_NO_QUANTUM_ACCOUNT = "\u60A8\u672A\u914D\u7F6E\u91CF\u5B50\u52A0\u5BC6\u53C2\u6570\uFF0C\u8BF7\u53C2\u8003[\u300A\u63A5\u5165\u6307\u5357\u300B\u5B89\u88C5\u91CF\u5B50\u5BC6\u4FE1\u63D2\u4EF6](" + GUIDE_URL + ") \u4FEE\u6539\u914D\u7F6E";
3693
+ var HINT_PLAINTEXT_NOT_SUPPORTED = "\u5F53\u524D\u6A21\u5F0F\u4E0D\u652F\u6301\u666E\u901A\u6D88\u606F\uFF0C\u8BF7\u53C2\u8003[\u300A\u63A5\u5165\u6307\u5357\u300B\u5B89\u88C5\u91CF\u5B50\u5BC6\u4FE1\u63D2\u4EF6](" + GUIDE_URL + ") \u4FEE\u6539\u914D\u7F6E";
3691
3694
 
3692
3695
  // src/logger.ts
3693
3696
  var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
@@ -4275,13 +4278,15 @@ var quantumImOutbound = {
4275
4278
  }
4276
4279
  const messagePipe = messagePipeGetter();
4277
4280
  const chatId = parseChannelAddress(to);
4281
+ const encryptionMode = pluginConfigGetter?.()?.credentials?.encryptionMode;
4278
4282
  const isEncrypted = getOutboundEncryptionStatus(chatId);
4283
+ const skipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted !== true;
4279
4284
  await messagePipe.sendMessage({
4280
4285
  chatId,
4281
4286
  senderId: chatId,
4282
4287
  msgType: "markdown",
4283
4288
  content: JSON.stringify({ content: text }),
4284
- skipEncrypt: isEncrypted !== true
4289
+ skipEncrypt
4285
4290
  });
4286
4291
  return { channel: CHANNEL_ID, messageId: "", chatId };
4287
4292
  },
@@ -4296,7 +4301,9 @@ var quantumImOutbound = {
4296
4301
  const sdkRuntime = sdkRuntimeGetter();
4297
4302
  const chatId = parseChannelAddress(to);
4298
4303
  const fileCfg = pluginConfigGetter().file;
4304
+ const encryptionMode = pluginConfigGetter?.()?.credentials?.encryptionMode;
4299
4305
  const isEncrypted = getOutboundEncryptionStatus(chatId);
4306
+ const skipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted !== true;
4300
4307
  const result = await resolveAndUploadMedia({
4301
4308
  mediaUrl: mediaUrl ?? void 0,
4302
4309
  tokenManager: tokenManagerGetter(),
@@ -4309,7 +4316,7 @@ var quantumImOutbound = {
4309
4316
  maxFileSizeMb: fileCfg.maxFileSizeMb,
4310
4317
  chunkSizeMb: fileCfg.chunkSizeMb,
4311
4318
  timeoutMs: fileCfg.fetchTimeoutMs,
4312
- skipEncrypt: isEncrypted !== true
4319
+ skipEncrypt
4313
4320
  });
4314
4321
  if (result.warning) {
4315
4322
  log4.warn("sendMedia:degraded", { chatId, warning: result.warning });
@@ -18110,7 +18117,9 @@ var AccountConfigSchema = external_exports.object({
18110
18117
  */
18111
18118
  botUserId: external_exports.string().optional(),
18112
18119
  /** 🟡 部署环境 — staging=联调, production=线上 */
18113
- env: external_exports.enum(["staging", "production"]).optional()
18120
+ env: external_exports.enum(["staging", "production"]).optional(),
18121
+ /** 🟡 消息加密模式 — quantum_only=仅加密, quantum_and_plain=加密+普通(默认) */
18122
+ encryptionMode: external_exports.enum(["quantum_only", "quantum_and_plain"]).default("quantum_and_plain")
18114
18123
  }).transform((raw) => ({
18115
18124
  appId: raw.appId,
18116
18125
  appSecret: raw.appSecret,
@@ -18119,7 +18128,8 @@ var AccountConfigSchema = external_exports.object({
18119
18128
  // quantumAppId: raw.quantumAppId,
18120
18129
  // quantumAppSecret: raw.quantumAppSecret,
18121
18130
  botUserId: raw.botUserId,
18122
- env: raw.env
18131
+ env: raw.env,
18132
+ encryptionMode: raw.encryptionMode
18123
18133
  }));
18124
18134
  var ENV_PRESETS = {
18125
18135
  /** 联调环境 */
@@ -19101,6 +19111,8 @@ var MessagePipe = class {
19101
19111
  messageCallback = null;
19102
19112
  /** 量子账户标识 — 用于判断是否具备解密能力 */
19103
19113
  quantumAccount;
19114
+ /** 消息加密模式 */
19115
+ encryptionMode;
19104
19116
  constructor(deps) {
19105
19117
  this.wsClient = deps.wsClient;
19106
19118
  this.dedup = deps.dedup;
@@ -19108,6 +19120,7 @@ var MessagePipe = class {
19108
19120
  this.tokenFn = deps.tokenFn;
19109
19121
  this.messageServiceBaseUrl = deps.messageServiceBaseUrl;
19110
19122
  this.quantumAccount = deps.quantumAccount;
19123
+ this.encryptionMode = deps.encryptionMode;
19111
19124
  this.wsClient.on("message", (rawData) => {
19112
19125
  this.handleInbound(rawData).catch((err) => {
19113
19126
  log12.error("\u274C \u5165\u7AD9\u5904\u7406\u5F02\u5E38", { step: "handleInbound", error: err.message });
@@ -19133,32 +19146,38 @@ var MessagePipe = class {
19133
19146
  * POST {messageServiceBaseUrl}/messages/v1/send
19134
19147
  * 字段映射: senderId → receive_id, msgType → msg_type
19135
19148
  *
19136
- * 加密模式 (quantumAccount 已配置):
19137
- * - 明文 content 加密后放入 extra.encryptMsg
19138
- * - extra.sessionId / extra.cryptoIv 由加密函数返回
19139
- * - 原始 content 置为空字符串 {content: ''}
19140
- * - extra 序列化为 JSON 字符串
19141
- *
19142
- * 失败不抛异常,避免阻塞 SDK deliver 回调。
19149
+ * 加密行为取决于 encryptionMode:
19150
+ * - quantum_only: 强制加密,失败直接抛错(不降级明文)
19151
+ * - quantum_and_plain: 加密失败降级明文(保持兼容)
19143
19152
  */
19144
19153
  async sendMessage(msg) {
19145
19154
  let content = msg.content;
19146
19155
  let extra = "";
19147
- if (this.quantumAccount && !msg.skipEncrypt) {
19148
- try {
19156
+ if (!msg.skipEncrypt && this.quantumAccount) {
19157
+ if (this.encryptionMode === "quantum_only") {
19149
19158
  const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
19150
19159
  extra = JSON.stringify({
19151
19160
  cryptoIv: iv,
19152
- // 随机生成的 IV,解密时需要回传
19153
19161
  encryptMsg: ciphertext,
19154
19162
  sessionId: keyId
19155
19163
  });
19156
19164
  content = JSON.stringify({ content: "" });
19157
- log12.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6", { sessionId: keyId });
19158
- } catch (err) {
19159
- log12.error("\u274C \u51FA\u7AD9\u6D88\u606F\u52A0\u5BC6\u5931\u8D25\uFF0C\u5C06\u53D1\u9001\u660E\u6587", {
19160
- error: err.message
19161
- });
19165
+ log12.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_only)", { sessionId: keyId });
19166
+ } else {
19167
+ try {
19168
+ const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
19169
+ extra = JSON.stringify({
19170
+ cryptoIv: iv,
19171
+ encryptMsg: ciphertext,
19172
+ sessionId: keyId
19173
+ });
19174
+ content = JSON.stringify({ content: "" });
19175
+ log12.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_and_plain)", { sessionId: keyId });
19176
+ } catch (err) {
19177
+ log12.error("\u274C \u51FA\u7AD9\u6D88\u606F\u52A0\u5BC6\u5931\u8D25\uFF0C\u5C06\u53D1\u9001\u660E\u6587", {
19178
+ error: err.message
19179
+ });
19180
+ }
19162
19181
  }
19163
19182
  }
19164
19183
  const body = {
@@ -19293,44 +19312,45 @@ var MessagePipe = class {
19293
19312
  const extra = callbackData.extra;
19294
19313
  const isEncrypted = typeof extra === "string" && extra.length > 0;
19295
19314
  let contentObj;
19296
- if (isEncrypted) {
19315
+ if (this.encryptionMode === "quantum_only") {
19297
19316
  if (!this.quantumAccount) {
19298
- log12.error("\u274C \u6536\u5230\u52A0\u5BC6\u6D88\u606F\u4F46\u672A\u914D\u7F6E\u91CF\u5B50\u5BC6\u94A5\uFF0C\u89E3\u5BC6\u5931\u8D25\u3002\u8BF7\u5728 openclaw.json \u4E2D\u914D\u7F6E quantumAccount", {
19299
- msgUid: callbackData.msgUid,
19300
- eventType: callbackData.eventType
19301
- });
19317
+ log12.info("\u26A0\uFE0F quantum_only: \u672A\u914D\u7F6E quantumAccount\uFF0C\u53D1\u9001\u5F15\u5BFC\u63D0\u793A", { chatId: callbackData.userId });
19318
+ await this.sendHintMessage(callbackData, HINT_NO_QUANTUM_ACCOUNT);
19319
+ return;
19320
+ }
19321
+ if (!isEncrypted) {
19322
+ log12.info("\u26A0\uFE0F quantum_only: \u6536\u5230\u660E\u6587\u6D88\u606F\uFF0C\u62D2\u7EDD\u5904\u7406", { msgUid: callbackData.msgUid });
19323
+ await this.sendHintMessage(callbackData, HINT_PLAINTEXT_NOT_SUPPORTED);
19302
19324
  return;
19303
19325
  }
19304
19326
  try {
19305
- const extraData = JSON.parse(extra);
19306
- const encryptMsg = extraData.encryptMsg ?? "";
19307
- const sessionId = extraData.sessionId ?? "";
19308
- if (!encryptMsg) {
19309
- log12.warn("\u26A0\uFE0F extra \u4E2D encryptMsg \u4E3A\u7A7A\uFF0C\u8DF3\u8FC7\u89E3\u5BC6", { msgUid: callbackData.msgUid });
19310
- contentObj = callbackData.content;
19311
- } else {
19312
- const cryptoIv = extraData.cryptoIv ?? "";
19313
- const decrypted = await this.crypto.decrypt(encryptMsg, sessionId, cryptoIv);
19314
- try {
19315
- contentObj = JSON.parse(decrypted);
19316
- } catch {
19317
- contentObj = { content: decrypted };
19318
- }
19319
- log12.debug("\u{1F513} \u6D88\u606F\u89E3\u5BC6\u6210\u529F", {
19320
- msgUid: callbackData.msgUid,
19321
- sessionId,
19322
- hasIv: Boolean(extraData.cryptoIv)
19323
- });
19324
- }
19327
+ contentObj = await this.decryptExtra(callbackData);
19325
19328
  } catch (err) {
19326
- log12.error("\u274C \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
19329
+ log12.error("\u274C quantum_only: \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
19327
19330
  msgUid: callbackData.msgUid,
19328
19331
  error: err.message
19329
19332
  });
19330
19333
  return;
19331
19334
  }
19332
19335
  } else {
19333
- contentObj = callbackData.content;
19336
+ if (isEncrypted) {
19337
+ if (!this.quantumAccount) {
19338
+ log12.info("\u26A0\uFE0F quantum_and_plain: \u6536\u5230\u52A0\u5BC6\u6D88\u606F\u4F46\u672A\u914D\u7F6E quantumAccount", { msgUid: callbackData.msgUid });
19339
+ await this.sendHintMessage(callbackData, HINT_NO_QUANTUM_ACCOUNT);
19340
+ return;
19341
+ }
19342
+ try {
19343
+ contentObj = await this.decryptExtra(callbackData);
19344
+ } catch (err) {
19345
+ log12.error("\u274C quantum_and_plain: \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
19346
+ msgUid: callbackData.msgUid,
19347
+ error: err.message
19348
+ });
19349
+ return;
19350
+ }
19351
+ } else {
19352
+ contentObj = callbackData.content;
19353
+ }
19334
19354
  }
19335
19355
  if (callbackData.eventType !== "callback:direct") {
19336
19356
  log12.debug("\u2139\uFE0F \u5FFD\u7565\u975E\u76EE\u6807\u4E8B\u4EF6", { eventType: callbackData.eventType });
@@ -19357,6 +19377,48 @@ var MessagePipe = class {
19357
19377
  log12.warn("\u26A0\uFE0F \u6D88\u606F\u56DE\u8C03\u672A\u6CE8\u518C\uFF0C\u65E0\u6CD5\u5904\u7406", { messageId: msg.messageId });
19358
19378
  }
19359
19379
  }
19380
+ // ── 私有辅助方法 ──
19381
+ /**
19382
+ * 发送系统提示消息(明文,不加密)。
19383
+ * 用于在模式不匹配时向用户发送引导信息。
19384
+ */
19385
+ async sendHintMessage(callbackData, hintText) {
19386
+ const chatId = callbackData.groupId || callbackData.userId;
19387
+ const senderId = callbackData.userId;
19388
+ try {
19389
+ await this.sendMessage({
19390
+ chatId,
19391
+ senderId,
19392
+ msgType: "markdown",
19393
+ content: JSON.stringify({ content: hintText }),
19394
+ skipEncrypt: true
19395
+ });
19396
+ log12.info("\u{1F4A1} \u5DF2\u53D1\u9001\u6A21\u5F0F\u63D0\u793A\u6D88\u606F", { chatId });
19397
+ } catch (err) {
19398
+ log12.error("\u274C \u63D0\u793A\u6D88\u606F\u53D1\u9001\u5931\u8D25", { error: err.message });
19399
+ }
19400
+ }
19401
+ /**
19402
+ * 解密 extra 中的加密内容。
19403
+ * @throws 解密失败时抛出异常
19404
+ */
19405
+ async decryptExtra(callbackData) {
19406
+ const extraData = JSON.parse(callbackData.extra);
19407
+ const encryptMsg = extraData.encryptMsg ?? "";
19408
+ const sessionId = extraData.sessionId ?? "";
19409
+ if (!encryptMsg) {
19410
+ log12.warn("\u26A0\uFE0F extra \u4E2D encryptMsg \u4E3A\u7A7A\uFF0C\u4F7F\u7528\u539F\u59CB content", { msgUid: callbackData.msgUid });
19411
+ return callbackData.content;
19412
+ }
19413
+ const cryptoIv = extraData.cryptoIv ?? "";
19414
+ const decrypted = await this.crypto.decrypt(encryptMsg, sessionId, cryptoIv);
19415
+ log12.debug("\u{1F513} \u6D88\u606F\u89E3\u5BC6\u6210\u529F", { msgUid: callbackData.msgUid, sessionId, hasIv: Boolean(cryptoIv) });
19416
+ try {
19417
+ return JSON.parse(decrypted);
19418
+ } catch {
19419
+ return { content: decrypted };
19420
+ }
19421
+ }
19360
19422
  };
19361
19423
 
19362
19424
  // src/transport/connection-manager.ts
@@ -19993,6 +20055,13 @@ var QUANTUM_IM_CONFIG_JSON_SCHEMA = {
19993
20055
  description: "\u9009\u62E9\u5BF9\u63A5\u7684\u670D\u52A1\u5668\u73AF\u5883\u3002staging = \u8054\u8C03\u73AF\u5883\uFF0Cproduction = \u7EBF\u4E0A\u73AF\u5883",
19994
20056
  enum: ["staging", "production"],
19995
20057
  default: "production"
20058
+ },
20059
+ encryptionMode: {
20060
+ type: "string",
20061
+ title: "\u6D88\u606F\u52A0\u5BC6\u6A21\u5F0F",
20062
+ description: "quantum_only = \u4EC5\u91CF\u5B50\u52A0\u5BC6\u6D88\u606F\uFF08\u6240\u6709\u6D88\u606F\u5FC5\u987B\u52A0\u5BC6\uFF09\uFF1Bquantum_and_plain = \u540C\u65F6\u652F\u6301\u52A0\u5BC6\u548C\u666E\u901A\u6D88\u606F",
20063
+ enum: ["quantum_only", "quantum_and_plain"],
20064
+ default: "quantum_and_plain"
19996
20065
  }
19997
20066
  }
19998
20067
  };
@@ -20062,7 +20131,8 @@ async function startPlugin(accountConfig, internalOverrides) {
20062
20131
  crypto: cryptoEngine,
20063
20132
  tokenFn: () => tokenManager.getValidToken(),
20064
20133
  messageServiceBaseUrl: config2.message.messageServiceBaseUrl,
20065
- quantumAccount: accountConfig.quantumAccount
20134
+ quantumAccount: accountConfig.quantumAccount,
20135
+ encryptionMode: accountConfig.encryptionMode
20066
20136
  });
20067
20137
  const connectionManager = new ConnectionManager(wsClient, {
20068
20138
  heartbeatIntervalMs: config2.transport.heartbeatIntervalMs,
@@ -20382,8 +20452,10 @@ function createQuantumImDeliverFn(deps) {
20382
20452
  maxFileSizeMb,
20383
20453
  chunkSizeMb,
20384
20454
  timeoutMs,
20385
- isEncrypted
20455
+ isEncrypted,
20456
+ encryptionMode
20386
20457
  } = deps;
20458
+ const shouldSkipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted === false;
20387
20459
  const deliver = async (payload) => {
20388
20460
  const mediaUrls = [];
20389
20461
  if (payload.mediaUrls?.length) {
@@ -20401,7 +20473,7 @@ function createQuantumImDeliverFn(deps) {
20401
20473
  msgType: "markdown",
20402
20474
  content: JSON.stringify({ content: payload.text }),
20403
20475
  replyToMessageId,
20404
- skipEncrypt: isEncrypted === false
20476
+ skipEncrypt: shouldSkipEncrypt
20405
20477
  });
20406
20478
  log23.info("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u5DF2\u53D1\u9001", {
20407
20479
  chatId,
@@ -20424,7 +20496,7 @@ function createQuantumImDeliverFn(deps) {
20424
20496
  maxFileSizeMb,
20425
20497
  chunkSizeMb,
20426
20498
  timeoutMs,
20427
- skipEncrypt: isEncrypted === false
20499
+ skipEncrypt: shouldSkipEncrypt
20428
20500
  });
20429
20501
  if (result.warning) {
20430
20502
  log23.warn("\u{1F4E4} \u5A92\u4F53\u53D1\u9001\u964D\u7EA7", { chatId, mediaUrl, warning: result.warning });
@@ -20443,7 +20515,7 @@ function createQuantumImDeliverFn(deps) {
20443
20515
  msgType: "markdown",
20444
20516
  content: JSON.stringify({ content: `\u{1F4CE} ${mediaUrl}` }),
20445
20517
  replyToMessageId,
20446
- skipEncrypt: isEncrypted === false
20518
+ skipEncrypt: shouldSkipEncrypt
20447
20519
  });
20448
20520
  }
20449
20521
  }
@@ -20481,12 +20553,13 @@ var InboundPipeline = class {
20481
20553
  });
20482
20554
  return;
20483
20555
  }
20556
+ const feedbackSkipEncrypt = this.deps.pluginConfig.credentials.encryptionMode === "quantum_only" ? false : true;
20484
20557
  this.deps.messagePipe.sendMessage({
20485
20558
  chatId: msg.chatId,
20486
20559
  senderId: msg.senderId,
20487
20560
  msgType: "text",
20488
20561
  content: JSON.stringify({ content: "\u4EFB\u52A1\u5DF2\u6536\u5230\u{1F44C}\uFF0C\u6B63\u5728\u5904\u7406\u4E2D..." }),
20489
- skipEncrypt: true
20562
+ skipEncrypt: feedbackSkipEncrypt
20490
20563
  }).catch((err) => {
20491
20564
  log24.warn("\u26A0\uFE0F \u5373\u65F6\u53CD\u9988\u53D1\u9001\u5931\u8D25", { error: err.message });
20492
20565
  });
@@ -20514,7 +20587,8 @@ var InboundPipeline = class {
20514
20587
  allowPrivateNetwork: this.deps.pluginConfig.file.allowPrivateNetwork,
20515
20588
  maxFileSizeMb: this.deps.pluginConfig.file.maxFileSizeMb,
20516
20589
  timeoutMs: this.deps.pluginConfig.file.fetchTimeoutMs,
20517
- isEncrypted: msg.isEncrypted
20590
+ isEncrypted: msg.isEncrypted,
20591
+ encryptionMode: this.deps.pluginConfig.credentials.encryptionMode
20518
20592
  });
20519
20593
  const { dispatcher, replyOptions } = core.channel.reply.createReplyDispatcherWithTyping({
20520
20594
  deliver
@@ -20647,33 +20721,39 @@ var quantumImOnboarding = {
20647
20721
  ].join("\n"),
20648
20722
  "\u91CF\u5B50\u5BC6\u4FE1 \u2014 \u51ED\u636E\u914D\u7F6E"
20649
20723
  );
20650
- const appId = await prompter.text({
20651
- message: "\u5E94\u7528 ID (appId)",
20652
- initialValue: existing.appId ?? void 0,
20653
- validate: required2
20654
- });
20655
- const appSecret = await prompter.text({
20656
- message: "\u5E94\u7528\u5BC6\u94A5 (appSecret)",
20657
- initialValue: existing.appSecret ?? void 0,
20658
- validate: required2
20659
- });
20660
- const encryptionMode = await prompter.select({
20661
- message: "\u6D88\u606F\u4F20\u8F93\u6A21\u5F0F",
20724
+ const encryptionModeChoice = await prompter.select({
20725
+ message: "\u8BF7\u9009\u62E9\u6D88\u606F\u4EA4\u4E92\u65B9\u5F0F",
20662
20726
  options: [
20663
- { value: "plain", label: "\u660E\u6587\u6A21\u5F0F\uFF08\u4E0D\u52A0\u5BC6\uFF09", hint: "\u9002\u5408\u6D4B\u8BD5\u6216\u4E0D\u9700\u8981\u52A0\u5BC6\u7684\u573A\u666F" },
20664
- { value: "quantum", label: "\u91CF\u5B50\u52A0\u5BC6\u6A21\u5F0F", hint: "\u9700\u8981\u91CF\u5B50\u8D26\u6237\u6807\u8BC6" }
20727
+ { value: "quantum_only", label: "\u4EC5\u91CF\u5B50\u52A0\u5BC6\u6D88\u606F" },
20728
+ { value: "quantum_and_plain", label: "\u91CF\u5B50\u52A0\u5BC6+\u666E\u901A\u6D88\u606F\uFF08\u82E5\u91CF\u5B50\u52A0\u5BC6\u7981\u7528\uFF0C\u5219\u7559\u7A7A\uFF09" }
20665
20729
  ],
20666
- initialValue: existing.quantumAccount?.trim() ? "quantum" : "plain"
20730
+ initialValue: existing.encryptionMode ?? "quantum_and_plain"
20667
20731
  });
20668
20732
  let quantumAccount = "";
20669
- if (encryptionMode === "quantum") {
20733
+ if (encryptionModeChoice === "quantum_only") {
20670
20734
  quantumAccount = await prompter.text({
20671
20735
  message: "\u91CF\u5B50\u8D26\u6237\u6807\u8BC6 (quantumAccount)",
20672
20736
  placeholder: "\u5982: 17100001111",
20673
20737
  initialValue: existing.quantumAccount ?? void 0,
20674
20738
  validate: required2
20675
20739
  });
20740
+ } else {
20741
+ quantumAccount = await prompter.text({
20742
+ message: "\u91CF\u5B50\u8D26\u6237\u6807\u8BC6\uFF08\u53EF\u9009\uFF0C\u4E0D\u586B\u5219\u4EC5\u652F\u6301\u660E\u6587\uFF09",
20743
+ placeholder: "\u5982: 17100001111",
20744
+ initialValue: existing.quantumAccount ?? void 0
20745
+ });
20676
20746
  }
20747
+ const appId = await prompter.text({
20748
+ message: "\u5E94\u7528 ID (appId)",
20749
+ initialValue: existing.appId ?? void 0,
20750
+ validate: required2
20751
+ });
20752
+ const appSecret = await prompter.text({
20753
+ message: "\u5E94\u7528\u5BC6\u94A5 (appSecret)",
20754
+ initialValue: existing.appSecret ?? void 0,
20755
+ validate: required2
20756
+ });
20677
20757
  const channels = cfg.channels ?? {};
20678
20758
  const channelCfg = channels[CHANNEL_ID] ?? {};
20679
20759
  const accounts = channelCfg.accounts ?? {};
@@ -20689,10 +20769,8 @@ var quantumImOnboarding = {
20689
20769
  default: {
20690
20770
  appId: appId.trim(),
20691
20771
  appSecret: appSecret.trim(),
20692
- // pin: pin.trim(),
20772
+ encryptionMode: encryptionModeChoice,
20693
20773
  ...quantumAccount.trim() ? { quantumAccount: quantumAccount.trim() } : {}
20694
- // quantumAppId: quantumAppId.trim(),
20695
- // quantumAppSecret: quantumAppSecret.trim(),
20696
20774
  }
20697
20775
  }
20698
20776
  }
@@ -33,6 +33,13 @@
33
33
  "description": "选择对接的服务器环境。staging = 联调环境,production = 线上环境",
34
34
  "enum": ["staging", "production"],
35
35
  "default": "production"
36
+ },
37
+ "encryptionMode": {
38
+ "type": "string",
39
+ "title": "消息加密模式",
40
+ "description": "quantum_only = 仅量子加密消息(所有消息必须加密);quantum_and_plain = 同时支持加密和普通消息",
41
+ "enum": ["quantum_only", "quantum_and_plain"],
42
+ "default": "quantum_and_plain"
36
43
  }
37
44
  }
38
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liangzimixin",
3
- "version": "0.3.43",
3
+ "version": "0.3.44",
4
4
  "description": "Quantum-encrypted IM channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",