@ycodium-ai/plugin-im 0.2.3202 → 0.2.3210

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.js CHANGED
@@ -137,7 +137,7 @@ var require_punycode = __commonJS({
137
137
  }
138
138
  return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
139
139
  };
140
- var decode2 = function(input) {
140
+ var decode3 = function(input) {
141
141
  const output = [];
142
142
  const inputLength = input.length;
143
143
  let i = 0;
@@ -249,7 +249,7 @@ var require_punycode = __commonJS({
249
249
  };
250
250
  var toUnicode = function(input) {
251
251
  return mapDomain(input, function(string) {
252
- return regexPunycode.test(string) ? decode2(string.slice(4).toLowerCase()) : string;
252
+ return regexPunycode.test(string) ? decode3(string.slice(4).toLowerCase()) : string;
253
253
  });
254
254
  };
255
255
  var toASCII = function(input) {
@@ -275,7 +275,7 @@ var require_punycode = __commonJS({
275
275
  "decode": ucs2decode,
276
276
  "encode": ucs2encode
277
277
  },
278
- "decode": decode2,
278
+ "decode": decode3,
279
279
  "encode": encode2,
280
280
  "toASCII": toASCII,
281
281
  "toUnicode": toUnicode
@@ -18475,7 +18475,7 @@ var require_utils = __commonJS({
18475
18475
  return acc;
18476
18476
  }, target2);
18477
18477
  };
18478
- var decode2 = function(str2, defaultDecoder, charset) {
18478
+ var decode3 = function(str2, defaultDecoder, charset) {
18479
18479
  var strWithoutPlus = str2.replace(/\+/g, " ");
18480
18480
  if (charset === "iso-8859-1") {
18481
18481
  return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
@@ -18601,7 +18601,7 @@ var require_utils = __commonJS({
18601
18601
  assign,
18602
18602
  combine,
18603
18603
  compact,
18604
- decode: decode2,
18604
+ decode: decode3,
18605
18605
  encode: encode2,
18606
18606
  isBuffer,
18607
18607
  isOverflow,
@@ -23360,7 +23360,7 @@ var require_websocket = __commonJS({
23360
23360
  var http = __require("http");
23361
23361
  var net = __require("net");
23362
23362
  var tls = __require("tls");
23363
- var { randomBytes, createHash: createHash6 } = __require("crypto");
23363
+ var { randomBytes, createHash: createHash8 } = __require("crypto");
23364
23364
  var { Duplex, Readable } = __require("stream");
23365
23365
  var { URL: URL2 } = __require("url");
23366
23366
  var PerMessageDeflate = require_permessage_deflate();
@@ -24028,7 +24028,7 @@ var require_websocket = __commonJS({
24028
24028
  abortHandshake(websocket, socket, "Invalid Upgrade header");
24029
24029
  return;
24030
24030
  }
24031
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
24031
+ const digest = createHash8("sha1").update(key + GUID).digest("base64");
24032
24032
  if (res.headers["sec-websocket-accept"] !== digest) {
24033
24033
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
24034
24034
  return;
@@ -24397,7 +24397,7 @@ var require_websocket_server = __commonJS({
24397
24397
  var EventEmitter = __require("events");
24398
24398
  var http = __require("http");
24399
24399
  var { Duplex } = __require("stream");
24400
- var { createHash: createHash6 } = __require("crypto");
24400
+ var { createHash: createHash8 } = __require("crypto");
24401
24401
  var extension = require_extension();
24402
24402
  var PerMessageDeflate = require_permessage_deflate();
24403
24403
  var subprotocol = require_subprotocol();
@@ -24704,7 +24704,7 @@ var require_websocket_server = __commonJS({
24704
24704
  );
24705
24705
  }
24706
24706
  if (this._state > RUNNING) return abortHandshake(socket, 503);
24707
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
24707
+ const digest = createHash8("sha1").update(key + GUID).digest("base64");
24708
24708
  const headers = [
24709
24709
  "HTTP/1.1 101 Switching Protocols",
24710
24710
  "Upgrade: websocket",
@@ -24912,7 +24912,7 @@ var require_base64 = __commonJS({
24912
24912
  return String.fromCharCode.apply(String, chunk.slice(0, i2));
24913
24913
  };
24914
24914
  var invalidEncoding = "invalid encoding";
24915
- base64.decode = function decode2(string, buffer, offset) {
24915
+ base64.decode = function decode3(string, buffer, offset) {
24916
24916
  var start = offset;
24917
24917
  var j = 0, t;
24918
24918
  for (var i2 = 0; i2 < string.length; ) {
@@ -121305,7 +121305,7 @@ var require_lib2 = __commonJS({
121305
121305
  Header.encodeDelimited = function encodeDelimited(message, writer) {
121306
121306
  return this.encode(message, writer).ldelim();
121307
121307
  };
121308
- Header.decode = function decode3(reader, length) {
121308
+ Header.decode = function decode4(reader, length) {
121309
121309
  if (!(reader instanceof $Reader))
121310
121310
  reader = $Reader.create(reader);
121311
121311
  let end = length === void 0 ? reader.len : reader.pos + length, message = new $root.pbbp2.Header();
@@ -121451,7 +121451,7 @@ var require_lib2 = __commonJS({
121451
121451
  Frame.encodeDelimited = function encodeDelimited(message, writer) {
121452
121452
  return this.encode(message, writer).ldelim();
121453
121453
  };
121454
- Frame.decode = function decode3(reader, length) {
121454
+ Frame.decode = function decode4(reader, length) {
121455
121455
  if (!(reader instanceof $Reader))
121456
121456
  reader = $Reader.create(reader);
121457
121457
  let end = length === void 0 ? reader.len : reader.pos + length, message = new $root.pbbp2.Frame();
@@ -121678,7 +121678,7 @@ var require_lib2 = __commonJS({
121678
121678
  })();
121679
121679
  return pbbp22;
121680
121680
  })();
121681
- var decode2 = (buffer) => {
121681
+ var decode3 = (buffer) => {
121682
121682
  return pbbp2.Frame.decode(buffer);
121683
121683
  };
121684
121684
  var WSConfig = class {
@@ -122177,7 +122177,7 @@ var require_lib2 = __commonJS({
122177
122177
  const wsInstance = this.wsConfig.getWSInstance();
122178
122178
  wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.on("message", (buffer) => __awaiter(this, void 0, void 0, function* () {
122179
122179
  this.clearLiveness();
122180
- const data = decode2(buffer);
122180
+ const data = decode3(buffer);
122181
122181
  const { method } = data;
122182
122182
  if (method === FrameType.control) {
122183
122183
  yield this.handleControlData(data);
@@ -125575,14 +125575,14 @@ ${lines.join("\n")}
125575
125575
  const senderOpenId = event.sender.sender_id.open_id;
125576
125576
  const senderFallbackId = (_d = (_c = event.sender.sender_id.user_id) !== null && _c !== void 0 ? _c : event.sender.sender_id.union_id) !== null && _d !== void 0 ? _d : "";
125577
125577
  const senderId = senderOpenId !== null && senderOpenId !== void 0 ? senderOpenId : senderFallbackId;
125578
- const senderName = senderOpenId ? (_e = opts.resolveSenderName) === null || _e === void 0 ? void 0 : _e.call(opts, senderOpenId) : void 0;
125578
+ const senderName2 = senderOpenId ? (_e = opts.resolveSenderName) === null || _e === void 0 ? void 0 : _e.call(opts, senderOpenId) : void 0;
125579
125579
  const createMs = msg.create_time ? parseInt(msg.create_time, 10) : 0;
125580
125580
  return {
125581
125581
  messageId: msg.message_id,
125582
125582
  chatId: msg.chat_id,
125583
125583
  chatType: msg.chat_type,
125584
125584
  senderId,
125585
- senderName,
125585
+ senderName: senderName2,
125586
125586
  content,
125587
125587
  rawContentType: msg.message_type,
125588
125588
  resources,
@@ -126850,6 +126850,7 @@ var SETTINGS_REMOVE_TARGET_COMMAND = "settings.removeTarget";
126850
126850
  var LARK_SETUP_SESSION_COMMAND = "larkSetup.session";
126851
126851
  var BOT_ACTION_COMMANDS = {
126852
126852
  checkConnection: "im.settings.checkConnection",
126853
+ reconnect: "im.settings.reconnect",
126853
126854
  copyDiagnostics: "im.settings.copyDiagnostics",
126854
126855
  larkRepair: "im.settings.larkRepair"
126855
126856
  };
@@ -127416,6 +127417,31 @@ var makeChatQueue = (core) => {
127416
127417
  // src/chatSetup.ts
127417
127418
  import { threadOrNull } from "@ycodium-ai/plugin-api";
127418
127419
 
127420
+ // src/cooldown.ts
127421
+ var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
127422
+ const lastAt = /* @__PURE__ */ new Map();
127423
+ return {
127424
+ tryAcquire: (key) => {
127425
+ const current = now();
127426
+ const previous = lastAt.get(key);
127427
+ if (previous !== void 0 && current - previous < windowMs) return false;
127428
+ lastAt.delete(key);
127429
+ lastAt.set(key, current);
127430
+ if (lastAt.size > maxKeys) {
127431
+ for (const [candidate, at] of lastAt) {
127432
+ if (current - at >= windowMs) lastAt.delete(candidate);
127433
+ }
127434
+ while (lastAt.size > maxKeys) {
127435
+ const oldest = lastAt.keys().next().value;
127436
+ if (oldest === void 0) break;
127437
+ lastAt.delete(oldest);
127438
+ }
127439
+ }
127440
+ return true;
127441
+ }
127442
+ };
127443
+ };
127444
+
127419
127445
  // src/projectList.ts
127420
127446
  var PROJECT_LIST_MAX = 20;
127421
127447
  var projectLine = (row, position) => `${String(position)}. ${row.title.replace(/\s+/g, " ").trim()}${row.current ? " \u2605" : ""}`;
@@ -127449,6 +127475,14 @@ var pickProject = (arg, listed, rows) => {
127449
127475
  return prefixed.length === 1 ? prefixed[0]?.projectId ?? null : null;
127450
127476
  };
127451
127477
 
127478
+ // src/messageIdentityFormat.ts
127479
+ var IDENTITY_VERSION = "im1";
127480
+ var identityPrefix = (operation) => `${IDENTITY_VERSION}-${operation}-`;
127481
+ var identityDigest = (id, operation) => {
127482
+ const prefix = identityPrefix(operation);
127483
+ return id.startsWith(prefix) ? id.slice(prefix.length) : void 0;
127484
+ };
127485
+
127452
127486
  // src/sessionList.ts
127453
127487
  var SESSION_LIST_MAX = 20;
127454
127488
  var isSessionRunning = (status) => status === "running";
@@ -127463,7 +127497,10 @@ var ageLabel = (updatedAt, now, t) => {
127463
127497
  if (hours < 24) return t("sessions.age.hours", { count: hours });
127464
127498
  return t("sessions.age.days", { count: Math.floor(hours / 24) });
127465
127499
  };
127466
- var shortId = (threadId) => threadId.slice(0, SESSION_ID_SHORT_LENGTH);
127500
+ var shortId = (threadId) => threadId.slice(
127501
+ 0,
127502
+ (identityDigest(threadId, "thread") === void 0 ? 0 : identityPrefix("thread").length) + SESSION_ID_SHORT_LENGTH
127503
+ );
127467
127504
  var sessionLine = (row, position, now, t) => {
127468
127505
  const title = row.title.replace(/\s+/g, " ").trim() || t("sessions.untitled");
127469
127506
  const age = ageLabel(row.updatedAt, now, t);
@@ -127504,16 +127541,21 @@ var pickSession = (arg, listed, rows) => {
127504
127541
  var makeChatSetup = (core, deps) => {
127505
127542
  const { host, t, adapterFor, commit, deliver, bindingOf: bindingOf2, hasPanel } = core;
127506
127543
  const { projectRowsFor, listedProjectsByChat } = deps.rows;
127507
- const promptProject = async (binding) => {
127508
- const chatKey = chatKeyOf(binding);
127509
- let rows;
127544
+ const pendingMessages = makeBoundedMap(100);
127545
+ const projectPromptCooldown = makeCooldown(3e4, () => Date.now());
127546
+ const loadProjectRows = async (binding) => {
127510
127547
  try {
127511
- rows = await projectRowsFor(chatKey);
127548
+ return await projectRowsFor(chatKeyOf(binding));
127512
127549
  } catch (error) {
127513
127550
  host.log.warn("chat could not list the projects", { error: describeError(error) });
127514
127551
  await deliver(binding, t("panel.loadFailed"));
127515
- return;
127552
+ return null;
127516
127553
  }
127554
+ };
127555
+ const promptProject = async (binding, available) => {
127556
+ const chatKey = chatKeyOf(binding);
127557
+ const rows = available ?? await loadProjectRows(binding);
127558
+ if (rows === null) return;
127517
127559
  listedProjectsByChat.set(chatKey, listedProjectIds(rows));
127518
127560
  if (rows.length > 0 && hasPanel(binding)) {
127519
127561
  try {
@@ -127533,14 +127575,8 @@ var makeChatSetup = (core, deps) => {
127533
127575
  return;
127534
127576
  }
127535
127577
  const chatKey = chatKeyOf(binding);
127536
- let rows;
127537
- try {
127538
- rows = await projectRowsFor(chatKey);
127539
- } catch (error) {
127540
- host.log.warn("chat could not list the projects", { error: describeError(error) });
127541
- await deliver(binding, t("panel.loadFailed"));
127542
- return;
127543
- }
127578
+ const rows = await loadProjectRows(binding);
127579
+ if (rows === null) return;
127544
127580
  const picked = pickProject(arg, listedProjectsByChat.get(chatKey), rows);
127545
127581
  if (picked === null) {
127546
127582
  await deliver(binding, t("repo.notFound", { query: arg }));
@@ -127572,6 +127608,9 @@ var makeChatSetup = (core, deps) => {
127572
127608
  listedProjectsByChat.delete(chatKey);
127573
127609
  host.log.info("chat picked a project", { bot: binding.displayName, projectId: picked });
127574
127610
  await deliver(binding, t("repo.done", { title: target2.title }));
127611
+ const pending = pendingMessages.get(chatKey);
127612
+ pendingMessages.forget((key) => key === chatKey);
127613
+ if (pending !== void 0) await deps.resumeMessage(binding, pending);
127575
127614
  };
127576
127615
  const handleGroup = async (binding, name, senderId) => {
127577
127616
  const adapter = adapterFor(binding.platform);
@@ -127599,7 +127638,27 @@ var makeChatSetup = (core, deps) => {
127599
127638
  await deliver(binding, t("group.created", { name }));
127600
127639
  await promptProject(bindingOf2(binding, chatId));
127601
127640
  };
127602
- return { promptProject, handleRepo, handleGroup };
127641
+ const prepareMessage = async (binding, message) => {
127642
+ const chatKey = chatKeyOf(binding);
127643
+ const asked = message.text.trim();
127644
+ if (/^[1-9]\d{0,2}$/u.test(asked) && listedProjectsByChat.has(chatKey)) {
127645
+ await handleRepo(binding, asked);
127646
+ return false;
127647
+ }
127648
+ const rows = await loadProjectRows(binding);
127649
+ if (rows === null) return false;
127650
+ const only = rows.length === 1 ? rows[0] : void 0;
127651
+ if (only !== void 0) {
127652
+ commit("chat-updated", { chatKey, projectId: only.projectId });
127653
+ pendingMessages.forget((key) => key === chatKey);
127654
+ await deliver(binding, t("repo.autoSelected", { title: only.title }));
127655
+ return true;
127656
+ }
127657
+ pendingMessages.set(chatKey, message);
127658
+ if (projectPromptCooldown.tryAcquire(chatKey)) await promptProject(binding, rows);
127659
+ return false;
127660
+ };
127661
+ return { promptProject, handleRepo, handleGroup, prepareMessage };
127603
127662
  };
127604
127663
 
127605
127664
  // src/commands.ts
@@ -127692,7 +127751,6 @@ var parseChatCommand = (input) => {
127692
127751
  return query === "" ? { kind: "selectTeam" } : { kind: "help" };
127693
127752
  }
127694
127753
  if (isThreadCommand(input.text, input.chinese)) return { kind: "selectThread" };
127695
- if (/^\/[a-z][a-z0-9_-]*/i.test(trimmed)) return { kind: "help" };
127696
127754
  if (input.chinese) {
127697
127755
  const alias = ZH_ALIASES[trimmed];
127698
127756
  if (alias !== void 0) return { kind: alias };
@@ -128084,30 +128142,36 @@ var startConnections = (core, inboundHandlersFor) => {
128084
128142
  }
128085
128143
  };
128086
128144
  };
128087
- const reconcile = () => {
128145
+ const plan = () => {
128088
128146
  const bots = disposed ? [] : core.bots();
128089
128147
  const wanted = /* @__PURE__ */ new Map();
128090
- const credentials3 = /* @__PURE__ */ new Map();
128148
+ const credentials2 = /* @__PURE__ */ new Map();
128091
128149
  const duplicates = /* @__PURE__ */ new Map();
128092
128150
  for (const bot of bots) {
128093
128151
  const adapter = adapterFor(bot.platform);
128094
128152
  if (adapter === void 0) continue;
128095
128153
  const fingerprint = `${bot.platform}:${adapter.columns.map((column) => bot.values[column.key] ?? "").join(" ")}`;
128096
- const duplicateOf = credentials3.get(fingerprint);
128154
+ const duplicateOf = credentials2.get(fingerprint);
128097
128155
  if (duplicateOf !== void 0) {
128098
128156
  duplicates.set(bot.rowId, duplicateOf);
128099
- if (statuses.get(bot.rowId)?.duplicateOf !== duplicateOf) {
128100
- record3(bot.rowId, { state: "duplicate", duplicateOf });
128101
- }
128102
- host.log.warn("chat did not connect a bot that repeats another bot's credentials", {
128103
- bot: bot.displayName,
128104
- platform: bot.platform
128105
- });
128106
128157
  continue;
128107
128158
  }
128108
- credentials3.set(fingerprint, bot.rowId);
128159
+ credentials2.set(fingerprint, bot.rowId);
128109
128160
  wanted.set(bot.rowId, { bot, key: connectionKey(adapter, bot) });
128110
128161
  }
128162
+ return { bots, wanted, duplicates };
128163
+ };
128164
+ const reconcile = () => {
128165
+ const { bots, wanted, duplicates } = plan();
128166
+ for (const bot of bots) {
128167
+ const duplicateOf = duplicates.get(bot.rowId);
128168
+ if (duplicateOf === void 0) continue;
128169
+ record3(bot.rowId, { state: "duplicate", duplicateOf });
128170
+ host.log.warn("chat did not connect a bot that repeats another bot's credentials", {
128171
+ bot: bot.displayName,
128172
+ platform: bot.platform
128173
+ });
128174
+ }
128111
128175
  for (const [rowId, connection] of live) {
128112
128176
  if (wanted.get(rowId)?.key === connection.key) continue;
128113
128177
  connection.close();
@@ -128128,6 +128192,23 @@ var startConnections = (core, inboundHandlersFor) => {
128128
128192
  });
128129
128193
  return {
128130
128194
  reconcile,
128195
+ reconnect: (rowId) => {
128196
+ live.get(rowId)?.close();
128197
+ live.delete(rowId);
128198
+ const { wanted, duplicates } = plan();
128199
+ const duplicateOf = duplicates.get(rowId);
128200
+ if (duplicateOf !== void 0) {
128201
+ record3(rowId, { state: "duplicate", duplicateOf });
128202
+ return { started: false, reason: "duplicate" };
128203
+ }
128204
+ const target2 = wanted.get(rowId);
128205
+ if (target2 === void 0) {
128206
+ remove(rowId);
128207
+ return { started: false, reason: disposed ? "inactive" : "notConfigured" };
128208
+ }
128209
+ live.set(rowId, open(target2.bot, target2.key));
128210
+ return { started: true };
128211
+ },
128131
128212
  subscribe: (listener) => {
128132
128213
  if (!disposed) listeners.add(listener);
128133
128214
  listener(Object.fromEntries(statuses));
@@ -128396,7 +128477,7 @@ var makeImCore = (input) => {
128396
128477
  init: emptyState,
128397
128478
  apply: foldPluginEvent(input.pluginId)
128398
128479
  });
128399
- const ready = stateHandle.get().then(
128480
+ const chatReady = stateHandle.get().then(
128400
128481
  (restored) => {
128401
128482
  state = restored;
128402
128483
  },
@@ -128404,6 +128485,7 @@ var makeImCore = (input) => {
128404
128485
  host.log.warn("chat could not restore its saved state", { error: describeError(error) });
128405
128486
  }
128406
128487
  );
128488
+ const ready = Promise.all([chatReady, input.inboundReady]).then(() => void 0);
128407
128489
  let persisting = Promise.resolve();
128408
128490
  const commit = (name, data) => {
128409
128491
  state = foldImEvent(state, name, data);
@@ -128936,79 +129018,51 @@ var makeInboundAttachments = (core) => {
128936
129018
  // src/inbound.ts
128937
129019
  import { projectOrNull } from "@ycodium-ai/plugin-api";
128938
129020
 
128939
- // src/boundedSeen.ts
128940
- var makeBoundedSeen = (limit) => {
128941
- const seen = /* @__PURE__ */ new Set();
128942
- return {
128943
- has: (key) => seen.has(key),
128944
- /** 记下这个键:第一次见返回 true;已经记着返回 false(不重排,最早记的先被忘)。 */
128945
- claim: (key) => {
128946
- if (seen.has(key)) return false;
128947
- seen.add(key);
128948
- if (seen.size > limit) {
128949
- const oldest = seen.values().next().value;
128950
- if (oldest !== void 0) seen.delete(oldest);
128951
- }
128952
- return true;
128953
- }
128954
- };
128955
- };
128956
-
128957
- // src/cooldown.ts
128958
- var makeCooldown = (windowMs, now, maxKeys = Number.POSITIVE_INFINITY) => {
128959
- const lastAt = /* @__PURE__ */ new Map();
128960
- return {
128961
- tryAcquire: (key) => {
128962
- const current = now();
128963
- const previous = lastAt.get(key);
128964
- if (previous !== void 0 && current - previous < windowMs) return false;
128965
- lastAt.delete(key);
128966
- lastAt.set(key, current);
128967
- if (lastAt.size > maxKeys) {
128968
- for (const [candidate, at] of lastAt) {
128969
- if (current - at >= windowMs) lastAt.delete(candidate);
128970
- }
128971
- while (lastAt.size > maxKeys) {
128972
- const oldest = lastAt.keys().next().value;
128973
- if (oldest === void 0) break;
128974
- lastAt.delete(oldest);
128975
- }
128976
- }
128977
- return true;
128978
- }
129021
+ // src/greetings.ts
129022
+ var GREETING_COOLDOWN_MS = 12 * 60 * 6e4;
129023
+ var GREETING_KEY_LIMIT = 1e3;
129024
+ var makeGreetings = (core, enqueueForChat, now) => {
129025
+ const cooldown = makeCooldown(GREETING_COOLDOWN_MS, now, GREETING_KEY_LIMIT);
129026
+ return (rowId, chatId, senderId) => {
129027
+ const bot = core.bots().find((candidate) => candidate.rowId === rowId);
129028
+ if (bot === void 0 || !isAllowedSender(bot, senderId)) return;
129029
+ if (!cooldown.tryAcquire(`${rowId}:${senderId}`)) return;
129030
+ enqueueForChat(chatKeyOf({ rowId, chatId }), async () => {
129031
+ const current = core.bots().find((candidate) => candidate.rowId === rowId);
129032
+ if (current === void 0 || !isAllowedSender(current, senderId)) return;
129033
+ await core.deliver(core.bindingOf(current, chatId), core.t("reply.welcome"));
129034
+ });
128979
129035
  };
128980
129036
  };
128981
129037
 
128982
129038
  // src/inbound.ts
128983
- var SEEN_MESSAGE_LIMIT = 256;
128984
129039
  var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
128985
129040
  var UNSUPPORTED_REPLY_COOLDOWN_MS = 6e4;
128986
129041
  var NOT_ALLOWED_REPLY_COOLDOWN_MS = 10 * 6e4;
128987
129042
  var NOT_ALLOWED_REPLY_KEY_LIMIT = 1e3;
128988
- var PROJECT_PROMPT_COOLDOWN_MS = 3e4;
128989
129043
  var makeInbound = (core, deps) => {
128990
129044
  const { host, t, adapterFor, commit, deliver, bindingOf: bindingOf2, bots } = core;
128991
129045
  const { enqueueForChat } = deps;
129046
+ const now = deps.now ?? (() => Date.now());
129047
+ const greet = makeGreetings(core, enqueueForChat, now);
128992
129048
  const { handleCommand, targetOf } = deps.commands;
128993
- const { promptProject, handleRepo } = deps.chatSetup;
128994
- const { listedProjectsByChat } = deps.rows;
129049
+ const { prepareMessage } = deps.chatSetup;
128995
129050
  const { questionFlow, handleButtonClick } = deps.interactions;
128996
129051
  const { answerByToken } = deps.approvals;
128997
129052
  const { handlePanelClick } = deps.panelWiring;
128998
129053
  const { startTurn, receiveAttachmentsOnly, hasWaitingAttachments } = deps.turns;
128999
129054
  const { startTeamGoal } = deps.teams;
129000
129055
  const { markWorking } = deps.acknowledgements;
129001
- const archivedReplyCooldown = makeCooldown(ARCHIVED_REPLY_COOLDOWN_MS, () => Date.now());
129002
- const unsupportedReplyCooldown = makeCooldown(UNSUPPORTED_REPLY_COOLDOWN_MS, () => Date.now());
129056
+ const archivedReplyCooldown = makeCooldown(ARCHIVED_REPLY_COOLDOWN_MS, now);
129057
+ const unsupportedReplyCooldown = makeCooldown(UNSUPPORTED_REPLY_COOLDOWN_MS, now);
129003
129058
  const notAllowedReplyCooldown = makeCooldown(
129004
129059
  NOT_ALLOWED_REPLY_COOLDOWN_MS,
129005
- () => Date.now(),
129060
+ now,
129006
129061
  NOT_ALLOWED_REPLY_KEY_LIMIT
129007
129062
  );
129008
- const projectPromptCooldown = makeCooldown(PROJECT_PROMPT_COOLDOWN_MS, () => Date.now());
129009
- const handledMessages = makeBoundedSeen(SEEN_MESSAGE_LIMIT);
129010
- const alreadyHandled = (key) => !handledMessages.claim(key);
129011
- const claimCallback = (rowId, callbackId) => callbackId === void 0 || callbackId === "" || !alreadyHandled(`c:${rowId}:${callbackId}`);
129063
+ const { inboundState } = deps;
129064
+ const alreadyHandled = (rowId, key) => !inboundState.claim(rowId, key);
129065
+ const claimCallback = (rowId, callbackId) => callbackId === void 0 || callbackId === "" || !alreadyHandled(rowId, `c:${callbackId}`);
129012
129066
  const turnAwayStranger = (target2, sender) => {
129013
129067
  host.log.warn("chat ignored a message from someone not on the allow list", {
129014
129068
  bot: target2.displayName,
@@ -129039,6 +129093,7 @@ var makeInbound = (core, deps) => {
129039
129093
  commit("chat-aliased", { rowId: bot.rowId, alias, chatId });
129040
129094
  };
129041
129095
  const settleChat = (bot, message) => {
129096
+ message = { ...message, originalChatId: message.originalChatId ?? message.chatId };
129042
129097
  if (message.chatAlias === void 0) return message;
129043
129098
  const known = chatAliasOf(core.state, bot.rowId, message.chatAlias);
129044
129099
  if (known === void 0) noteChatAlias(bot, message.chatAlias, message.chatId);
@@ -129048,6 +129103,61 @@ var makeInbound = (core, deps) => {
129048
129103
  const threadId = await startTurn(speaker, message);
129049
129104
  if (threadId !== null) await markWorking(speaker, threadId, message.messageId);
129050
129105
  };
129106
+ const resumeMessage = async (current, message) => {
129107
+ inheritFromParent(current, message.chatId, message.parentChatId);
129108
+ let target2 = {
129109
+ ...bindingOf2(current, message.chatId),
129110
+ reply: anchorOfMessage(adapterFor(current.platform), message)
129111
+ };
129112
+ const chatKey = chatKeyOf(target2);
129113
+ if (await handleCommand(target2, message.text, message.senderId)) return;
129114
+ if (target2.projectId === "") {
129115
+ if (!await prepareMessage(target2, message)) return;
129116
+ target2 = { ...bindingOf2(current, message.chatId), reply: target2.reply };
129117
+ }
129118
+ const boundThread = chatOf(core.state, chatKey).threadId;
129119
+ if (boundThread !== null && message.text !== "" && await questionFlow.answerByText(chatKey, boundThread, message.text)) {
129120
+ return;
129121
+ }
129122
+ const project = await projectOrNull(host.projects, target2.projectId);
129123
+ if (project === null || project.archivedAt != null) {
129124
+ if (project === null) {
129125
+ host.log.warn("chat cannot start work: the chat's project is gone", {
129126
+ bot: target2.displayName,
129127
+ projectId: target2.projectId
129128
+ });
129129
+ }
129130
+ if (archivedReplyCooldown.tryAcquire(chatKey)) {
129131
+ await deliver(
129132
+ target2,
129133
+ project === null ? t("reply.projectMissing") : t("reply.projectArchived", { title: project.title })
129134
+ );
129135
+ }
129136
+ return;
129137
+ }
129138
+ const routed = targetOf(target2);
129139
+ const attachmentsOnly = message.text.trim() === "" && message.resources.length > 0;
129140
+ if (routed.kind === "team") {
129141
+ if (message.resources.length > 0) await deliver(target2, t("reply.attachmentsNoTeam"));
129142
+ if (attachmentsOnly) return;
129143
+ const leadThreadId = await startTeamGoal(target2, message);
129144
+ if (leadThreadId !== null) await markWorking(target2, leadThreadId, message.messageId);
129145
+ return;
129146
+ }
129147
+ if (attachmentsOnly) {
129148
+ await receiveAttachmentsOnly(target2, message);
129149
+ return;
129150
+ }
129151
+ const branchChatId = hasWaitingAttachments(chatKey) ? void 0 : message.branchChatId;
129152
+ if (branchChatId === void 0) {
129153
+ await openTurn(target2, message);
129154
+ return;
129155
+ }
129156
+ enqueueForChat(chatKeyOf({ rowId: current.rowId, chatId: branchChatId }), async () => {
129157
+ inheritFromParent(current, branchChatId, message.chatId);
129158
+ await openTurn({ ...bindingOf2(current, branchChatId), reply: target2.reply }, message);
129159
+ });
129160
+ };
129051
129161
  const inboundHandlersFor = (bot) => ({
129052
129162
  onError: (message) => host.log.warn("chat inbound connection failed", {
129053
129163
  platform: bot.platform,
@@ -129065,12 +129175,13 @@ var makeInbound = (core, deps) => {
129065
129175
  },
129066
129176
  noteChatAlias: (alias, chatId) => noteChatAlias(bot, alias, chatId),
129067
129177
  knowsChatAlias: (alias) => chatAliasOf(core.state, bot.rowId, alias) !== void 0,
129178
+ onGreeting: (chatId, senderId) => greet(bot.rowId, chatId, senderId),
129068
129179
  onMessage: (received) => {
129069
129180
  const current = currentBot(bot.rowId);
129070
129181
  if (current === void 0) return;
129071
129182
  const message = settleChat(current, received);
129072
129183
  const arrived = bindingOf2(current, message.chatId);
129073
- if (alreadyHandled(`m:${chatKeyOf(arrived)}:${message.messageId}`)) return;
129184
+ if (alreadyHandled(current.rowId, `m:${message.chatId}:${message.messageId}`)) return;
129074
129185
  if (message.chatKind !== void 0 && chatOf(core.state, chatKeyOf(arrived)).chatKind !== message.chatKind) {
129075
129186
  commit("chat-updated", { chatKey: chatKeyOf(arrived), chatKind: message.chatKind });
129076
129187
  }
@@ -129084,7 +129195,7 @@ var makeInbound = (core, deps) => {
129084
129195
  if (!unsupportedReplyCooldown.tryAcquire(chatKeyOf(arrived))) return;
129085
129196
  await deliver(
129086
129197
  arrived,
129087
- unsupported === "folder" ? t("reply.unsupportedFolder") : t("reply.unsupportedType", { type: unsupported })
129198
+ unsupported === "folder" ? t("reply.unsupportedFolder") : unsupported === "voice" ? t("reply.unsupportedVoice") : t("reply.unsupportedType", { type: unsupported })
129088
129199
  );
129089
129200
  });
129090
129201
  return;
@@ -129097,69 +129208,129 @@ var makeInbound = (core, deps) => {
129097
129208
  });
129098
129209
  return;
129099
129210
  }
129100
- enqueueForChat(chatKeyOf(arrived), async () => {
129101
- inheritFromParent(current, message.chatId, message.parentChatId);
129102
- const target2 = {
129103
- ...bindingOf2(current, message.chatId),
129104
- reply: anchorOfMessage(adapterFor(current.platform), message)
129105
- };
129106
- const chatKey = chatKeyOf(target2);
129107
- if (await handleCommand(target2, message.text, message.senderId)) return;
129108
- if (target2.projectId === "") {
129109
- const asked = message.text.trim();
129110
- if (/^[1-9]\d{0,2}$/u.test(asked) && listedProjectsByChat.has(chatKey)) {
129111
- await handleRepo(target2, asked);
129112
- } else if (projectPromptCooldown.tryAcquire(chatKey)) {
129113
- await promptProject(target2);
129114
- }
129115
- return;
129116
- }
129117
- const boundThread = chatOf(core.state, chatKey).threadId;
129118
- if (boundThread !== null && message.text !== "" && await questionFlow.answerByText(chatKey, boundThread, message.text)) {
129119
- return;
129211
+ enqueueForChat(chatKeyOf(arrived), () => resumeMessage(current, message));
129212
+ }
129213
+ });
129214
+ return { inboundHandlersFor, resumeMessage };
129215
+ };
129216
+
129217
+ // src/inboundStore.ts
129218
+ import * as NodeFSP from "node:fs/promises";
129219
+ import * as NodePath from "node:path";
129220
+ var SEEN_LIMIT = 1e3;
129221
+ var fileIo = {
129222
+ readFile: (path) => NodeFSP.readFile(path, "utf8"),
129223
+ mkdir: async (path) => {
129224
+ await NodeFSP.mkdir(path, { recursive: true });
129225
+ },
129226
+ writeFile: (path, contents) => NodeFSP.writeFile(path, contents, "utf8"),
129227
+ rename: (from, to) => NodeFSP.rename(from, to)
129228
+ };
129229
+ var isRecord2 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
129230
+ var decode = (contents) => {
129231
+ const value2 = JSON.parse(contents);
129232
+ if (!isRecord2(value2) || value2["version"] !== 1 || !isRecord2(value2["bots"]))
129233
+ throw new Error("invalid inbound store");
129234
+ const bots = /* @__PURE__ */ new Map();
129235
+ for (const [rowId, row] of Object.entries(value2["bots"])) {
129236
+ if (!isRecord2(row) || !Array.isArray(row["seenIds"]) || !row["seenIds"].every((id) => typeof id === "string"))
129237
+ throw new Error("invalid inbound bot row");
129238
+ const telegram = row["telegram"];
129239
+ if (telegram !== void 0 && (!isRecord2(telegram) || typeof telegram["tokenHash"] !== "string" || telegram["tokenHash"] === "" || typeof telegram["offset"] !== "number" || !Number.isSafeInteger(telegram["offset"]) || telegram["offset"] < 0))
129240
+ throw new Error("invalid telegram cursor");
129241
+ bots.set(rowId, {
129242
+ seenIds: row["seenIds"].slice(-SEEN_LIMIT),
129243
+ ...telegram === void 0 ? {} : {
129244
+ telegram: {
129245
+ tokenHash: telegram["tokenHash"],
129246
+ offset: telegram["offset"]
129120
129247
  }
129121
- const project = await projectOrNull(host.projects, target2.projectId);
129122
- if (project === null || project.archivedAt != null) {
129123
- if (project === null) {
129124
- host.log.warn("chat cannot start work: the chat's project is gone", {
129125
- bot: target2.displayName,
129126
- projectId: target2.projectId
129127
- });
129128
- }
129129
- if (archivedReplyCooldown.tryAcquire(chatKey)) {
129130
- await deliver(
129131
- target2,
129132
- project === null ? t("reply.projectMissing") : t("reply.projectArchived", { title: project.title })
129133
- );
129248
+ }
129249
+ });
129250
+ }
129251
+ return bots;
129252
+ };
129253
+ var makeInboundStore = (input) => {
129254
+ const io = input.io ?? fileIo;
129255
+ const path = NodePath.join(input.dataDir, "inbound.json");
129256
+ const temporary = `${path}.tmp`;
129257
+ let bots = /* @__PURE__ */ new Map();
129258
+ let dirty = false;
129259
+ let writing;
129260
+ const warn = (message, error) => input.log.warn(message, { error: describeError(error) });
129261
+ const ready = io.readFile(path).then(
129262
+ (contents) => {
129263
+ try {
129264
+ bots = decode(contents);
129265
+ } catch (error) {
129266
+ warn("chat could not restore inbound state", error);
129267
+ }
129268
+ },
129269
+ (error) => {
129270
+ if (!isRecord2(error) || error["code"] !== "ENOENT")
129271
+ warn("chat could not restore inbound state", error);
129272
+ }
129273
+ );
129274
+ const write = () => {
129275
+ writing ??= Promise.resolve().then(async () => {
129276
+ try {
129277
+ while (dirty) {
129278
+ dirty = false;
129279
+ const contents = JSON.stringify({ version: 1, bots: Object.fromEntries(bots) });
129280
+ try {
129281
+ await io.mkdir(input.dataDir);
129282
+ await io.writeFile(temporary, contents);
129283
+ await io.rename(temporary, path);
129284
+ } catch (error) {
129285
+ dirty = true;
129286
+ warn("chat could not save inbound state", error);
129287
+ break;
129134
129288
  }
129135
- return;
129136
- }
129137
- const routed = targetOf(target2);
129138
- const attachmentsOnly = message.text.trim() === "" && message.resources.length > 0;
129139
- if (routed.kind === "team") {
129140
- if (message.resources.length > 0) await deliver(target2, t("reply.attachmentsNoTeam"));
129141
- if (attachmentsOnly) return;
129142
- const leadThreadId = await startTeamGoal(target2, message);
129143
- if (leadThreadId !== null) await markWorking(target2, leadThreadId, message.messageId);
129144
- return;
129145
- }
129146
- if (attachmentsOnly) {
129147
- await receiveAttachmentsOnly(target2, message);
129148
- return;
129149
- }
129150
- const branchChatId = hasWaitingAttachments(chatKey) ? void 0 : message.branchChatId;
129151
- if (branchChatId === void 0) {
129152
- await openTurn(target2, message);
129153
- return;
129154
129289
  }
129155
- enqueueForChat(chatKeyOf({ rowId: current.rowId, chatId: branchChatId }), async () => {
129156
- inheritFromParent(current, branchChatId, message.chatId);
129157
- await openTurn({ ...bindingOf2(current, branchChatId), reply: target2.reply }, message);
129158
- });
129159
- });
129290
+ } finally {
129291
+ writing = void 0;
129292
+ }
129293
+ });
129294
+ return writing;
129295
+ };
129296
+ const changed = () => {
129297
+ dirty = true;
129298
+ void write();
129299
+ };
129300
+ const rowOf = (rowId) => {
129301
+ let row = bots.get(rowId);
129302
+ if (row === void 0) {
129303
+ row = { seenIds: [] };
129304
+ bots.set(rowId, row);
129160
129305
  }
129161
- });
129162
- return { inboundHandlersFor };
129306
+ return row;
129307
+ };
129308
+ return {
129309
+ ready,
129310
+ claim: (rowId, id) => {
129311
+ const row = rowOf(rowId);
129312
+ if (row.seenIds.includes(id)) return false;
129313
+ row.seenIds.push(id);
129314
+ if (row.seenIds.length > SEEN_LIMIT) row.seenIds.shift();
129315
+ changed();
129316
+ return true;
129317
+ },
129318
+ readCursor: (rowId, tokenHash) => {
129319
+ const cursor = bots.get(rowId)?.telegram;
129320
+ return cursor?.tokenHash === tokenHash ? cursor.offset : void 0;
129321
+ },
129322
+ saveCursor: (rowId, tokenHash, offset) => {
129323
+ const row = rowOf(rowId);
129324
+ if (row.telegram?.tokenHash === tokenHash && row.telegram.offset === offset) return;
129325
+ if (row.telegram?.tokenHash !== tokenHash) row.seenIds = [];
129326
+ row.telegram = { tokenHash, offset };
129327
+ changed();
129328
+ },
129329
+ flush: async () => {
129330
+ await ready;
129331
+ if (dirty || writing) await write();
129332
+ }
129333
+ };
129163
129334
  };
129164
129335
 
129165
129336
  // src/interactions.ts
@@ -129925,6 +130096,8 @@ var consolePath = {
129925
130096
  var APP_EVENTS = ["im.message.receive_v1"];
129926
130097
  var CARD_CALLBACKS = ["card.action.trigger"];
129927
130098
  var SCOPES = [
130099
+ // 让「补全权限」查询应用自身已开通的权限,并列出缺项。
130100
+ "application:application:self_manage",
129928
130101
  // 收发消息、改卡片、读消息。
129929
130102
  "im:message",
129930
130103
  // 群里 @ 机器人的消息(入站的主路径)。
@@ -129949,7 +130122,7 @@ var SCOPES = [
129949
130122
  // 「在弄了」表情回执(`larkApi.ts` 的 `addReaction`)。unconfirmed
129950
130123
  "im:message.reactions:write_only"
129951
130124
  ];
129952
- var isTenantScope = (name) => name.startsWith("im:");
130125
+ var isTenantScope = (name) => name.startsWith("im:") || name === "application:application:self_manage";
129953
130126
  var appMarker = (environmentId) => `[ycodium:${environmentId.slice(0, 8)}]`;
129954
130127
  var DEFAULT_APP_NAME = "Ycodium Agent";
129955
130128
  var DEFAULT_APP_DESCRIPTION = "Ycodium chat bot";
@@ -130000,6 +130173,16 @@ var makeFlowItemSchema = (shape) => ({
130000
130173
  },
130001
130174
  fills: shape.fills,
130002
130175
  messageKey: { type: "string" },
130176
+ tone: { type: "string", enum: ["caution"] },
130177
+ link: {
130178
+ type: "object",
130179
+ additionalProperties: false,
130180
+ required: ["url", "labelKey"],
130181
+ properties: {
130182
+ url: { type: "string", pattern: "^https://" },
130183
+ labelKey: { type: "string" }
130184
+ }
130185
+ },
130003
130186
  warnings: {
130004
130187
  type: "array",
130005
130188
  items: {
@@ -130013,6 +130196,7 @@ var makeFlowItemSchema = (shape) => ({
130013
130196
  }
130014
130197
  },
130015
130198
  reasonKey: { type: "string" },
130199
+ noteKey: { type: "string" },
130016
130200
  detail: { type: "string" }
130017
130201
  },
130018
130202
  oneOf: [
@@ -131656,8 +131840,8 @@ var resourceOf = (element, index) => {
131656
131840
  }
131657
131841
  return null;
131658
131842
  };
131659
- var placeholderOf = (element, index) => `<${mediaOf(element)} ${String(index)}>`;
131660
- var serializeElement = (element, index, botIds) => {
131843
+ var placeholderOf = (resource, index) => `<${resource.media} ${String(index)}>`;
131844
+ var serializeElement = (element, resourceText, botIds) => {
131661
131845
  switch (element.tag) {
131662
131846
  case "text":
131663
131847
  return styledText(element.text ?? "", element.style);
@@ -131680,12 +131864,12 @@ ${element.text ?? ""}
131680
131864
  case "img":
131681
131865
  case "media":
131682
131866
  case "file":
131683
- return index === null ? "" : placeholderOf(element, index);
131867
+ return resourceText ?? "";
131684
131868
  default:
131685
131869
  return "";
131686
131870
  }
131687
131871
  };
131688
- var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
131872
+ var serializePost = (content, botIds = /* @__PURE__ */ new Set(), renderResource = placeholderOf) => {
131689
131873
  let parsed;
131690
131874
  try {
131691
131875
  parsed = JSON.parse(content);
@@ -131694,31 +131878,31 @@ var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
131694
131878
  }
131695
131879
  const elements = (parsed.content ?? []).flat();
131696
131880
  const resources = [];
131697
- const indexOf = /* @__PURE__ */ new Map();
131881
+ const resourceTextOf = /* @__PURE__ */ new Map();
131698
131882
  for (const element of elements) {
131699
131883
  const resource = resourceOf(element, resources.length + 1);
131700
131884
  if (resource === null) continue;
131701
131885
  resources.push(resource);
131702
- indexOf.set(element, resources.length);
131886
+ resourceTextOf.set(element, renderResource(resource, resources.length));
131703
131887
  }
131704
131888
  const md2 = parsed.md;
131705
131889
  if (typeof md2 === "string" && md2.trim() !== "") {
131706
- const placeholderByKey = /* @__PURE__ */ new Map();
131890
+ const resourceTextByKey = /* @__PURE__ */ new Map();
131707
131891
  for (const element of elements) {
131708
- const index = indexOf.get(element);
131892
+ const resourceText = resourceTextOf.get(element);
131709
131893
  const key = element.tag === "img" ? element.image_key : element.file_key;
131710
- if (index !== void 0 && nonEmpty4(key)) {
131711
- placeholderByKey.set(key, placeholderOf(element, index));
131894
+ if (resourceText !== void 0 && nonEmpty4(key)) {
131895
+ resourceTextByKey.set(key, resourceText);
131712
131896
  }
131713
131897
  }
131714
131898
  const text5 = md2.replace(
131715
131899
  /!\[(?:img|video|file)\]\(([^)]+)\)/g,
131716
- (whole, rawKey) => placeholderByKey.get(rawKey.trim()) ?? whole
131900
+ (whole, rawKey) => resourceTextByKey.get(rawKey.trim()) ?? whole
131717
131901
  );
131718
131902
  return { text: text5, resources };
131719
131903
  }
131720
131904
  const paragraphs = (parsed.content ?? []).map(
131721
- (row) => row.map((element) => serializeElement(element, indexOf.get(element) ?? null, botIds)).join("")
131905
+ (row) => row.map((element) => serializeElement(element, resourceTextOf.get(element), botIds)).join("")
131722
131906
  );
131723
131907
  const text4 = paragraphs.map((line) => line.trim()).filter((line) => line !== "").join("\n");
131724
131908
  if (text4 === "" && resources.length === 0) return void 0;
@@ -131859,6 +132043,7 @@ var LARK_TEXT_MAX_CHARS = 3500;
131859
132043
  var LarkApiError = class extends Error {
131860
132044
  operation;
131861
132045
  code;
132046
+ msg;
131862
132047
  status;
131863
132048
  retryAfterMs;
131864
132049
  constructor(operation, code2, message, http = {}) {
@@ -131866,11 +132051,13 @@ var LarkApiError = class extends Error {
131866
132051
  this.name = "LarkApiError";
131867
132052
  this.operation = operation;
131868
132053
  this.code = code2;
132054
+ this.msg = message;
131869
132055
  this.status = http.status;
131870
132056
  this.retryAfterMs = http.retryAfterMs;
131871
132057
  }
131872
132058
  };
131873
- var isLarkPermissionError = (error) => error instanceof LarkApiError && (error.code === 99991672 || error.code === 99991679 || error.status === 401 || error.status === 403);
132059
+ var isLarkPermissionCode = (code2) => code2 === 99991672 || code2 === 99991679;
132060
+ var isLarkPermissionError = (error) => error instanceof LarkApiError && (isLarkPermissionCode(error.code) || error.status === 401 || error.status === 403);
131874
132061
  var LARK_RATE_LIMITED = 99991400;
131875
132062
  var isReplyRejected = (error) => error instanceof LarkApiError && error.status === void 0 && error.code !== LARK_RATE_LIMITED;
131876
132063
  var larkSendVerdict = (error) => {
@@ -131885,14 +132072,20 @@ var larkSendVerdict = (error) => {
131885
132072
  };
131886
132073
  var readEnvelope = async (operation, response) => {
131887
132074
  if (!response.ok) {
131888
- await response.body?.cancel().catch(() => void 0);
131889
- throw new LarkApiError(operation, void 0, `HTTP ${String(response.status)}`, {
131890
- status: response.status,
131891
- retryAfterMs: parseRetryAfterMs(
131892
- response.headers.get("retry-after") ?? response.headers.get("x-ogw-ratelimit-reset"),
131893
- Date.now()
131894
- ) ?? void 0
131895
- });
132075
+ const body2 = await response.json().catch(() => void 0);
132076
+ const envelope2 = typeof body2 === "object" && body2 !== null ? body2 : void 0;
132077
+ throw new LarkApiError(
132078
+ operation,
132079
+ typeof envelope2?.code === "number" ? envelope2.code : void 0,
132080
+ typeof envelope2?.msg === "string" ? envelope2.msg : `HTTP ${String(response.status)}`,
132081
+ {
132082
+ status: response.status,
132083
+ retryAfterMs: parseRetryAfterMs(
132084
+ response.headers.get("retry-after") ?? response.headers.get("x-ogw-ratelimit-reset"),
132085
+ Date.now()
132086
+ ) ?? void 0
132087
+ }
132088
+ );
131896
132089
  }
131897
132090
  const body = await response.json();
131898
132091
  const envelope = body;
@@ -131913,6 +132106,19 @@ var fetchTenantToken = async (input) => {
131913
132106
  }
131914
132107
  return { token: body.tenant_access_token, expiresInSeconds: body.expire ?? 0 };
131915
132108
  };
132109
+ var fetchAppTenantScopes = async (input) => {
132110
+ const response = await input.http(`${LARK_BASE}/application/v6/applications/me?lang=zh_cn`, {
132111
+ headers: { authorization: `Bearer ${input.token}` }
132112
+ });
132113
+ const body = await readEnvelope("applications/me", response);
132114
+ const scopes = body.data?.app?.scopes;
132115
+ if (!Array.isArray(scopes)) return void 0;
132116
+ if (scopes.some(
132117
+ (entry) => entry === null || typeof entry !== "object" || typeof entry.scope !== "string" || !Array.isArray(entry.token_types)
132118
+ ))
132119
+ return void 0;
132120
+ return scopes.filter((entry) => entry.token_types.includes("tenant")).map((entry) => entry.scope);
132121
+ };
131916
132122
  var postMessage = async (input) => {
131917
132123
  const headers = {
131918
132124
  authorization: `Bearer ${input.token}`,
@@ -131950,16 +132156,31 @@ var postMessage = async (input) => {
131950
132156
  });
131951
132157
  return sent?.message_id ?? null;
131952
132158
  };
131953
- var readMessage = async (input) => {
132159
+ var readMessageItems = async (input) => {
131954
132160
  const response = await input.http(
131955
- `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}`,
132161
+ `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}${input.withSenderName ? "?with_sender_name=true" : ""}`,
131956
132162
  {
131957
132163
  headers: { authorization: `Bearer ${input.token}` },
131958
132164
  ...input.signal === void 0 ? {} : { signal: input.signal }
131959
132165
  }
131960
132166
  );
131961
- const body = await readEnvelope("message.get", response);
131962
- return body.data?.items?.[0];
132167
+ const body = await readEnvelope(input.operation, response);
132168
+ return body.data?.items;
132169
+ };
132170
+ var readMessage = async (input) => (await readMessageItems({
132171
+ ...input,
132172
+ operation: "message.get"
132173
+ }))?.[0];
132174
+ var fetchMergeForwardMessages = async (input) => {
132175
+ const items = await readMessageItems({
132176
+ ...input,
132177
+ operation: "merge_forward.get",
132178
+ withSenderName: true
132179
+ });
132180
+ if (!items?.length) {
132181
+ throw new LarkApiError("merge_forward.get", 0, "the response carried no messages");
132182
+ }
132183
+ return items;
131963
132184
  };
131964
132185
  var fetchMessageThreadId = async (input) => (await readMessage(input))?.thread_id || void 0;
131965
132186
  var fetchMessageReference = async (input) => {
@@ -132203,9 +132424,9 @@ function getDecodeCache(exclude) {
132203
132424
  }
132204
132425
  return cache;
132205
132426
  }
132206
- function decode(string, exclude) {
132427
+ function decode2(string, exclude) {
132207
132428
  if (typeof exclude !== "string") {
132208
- exclude = decode.defaultChars;
132429
+ exclude = decode2.defaultChars;
132209
132430
  }
132210
132431
  const cache = getDecodeCache(exclude);
132211
132432
  return string.replace(/(%[a-f0-9]{2})+/gi, function(seq) {
@@ -132264,9 +132485,9 @@ function decode(string, exclude) {
132264
132485
  return result;
132265
132486
  });
132266
132487
  }
132267
- decode.defaultChars = ";/?:@&=+$,#";
132268
- decode.componentChars = "";
132269
- var decode_default = decode;
132488
+ decode2.defaultChars = ";/?:@&=+$,#";
132489
+ decode2.componentChars = "";
132490
+ var decode_default = decode2;
132270
132491
 
132271
132492
  // ../../node_modules/.pnpm/mdurl@2.1.0/node_modules/mdurl/lib/encode.mjs
132272
132493
  var encodeCache = {};
@@ -133287,12 +133508,12 @@ var LinkifyIt = class {
133287
133508
  *
133288
133509
  * See [twitter mentions example](https://github.com/markdown-it/linkify-it/blob/master/examples/twitter.mjs).
133289
133510
  */
133290
- add(schema, definition = null) {
133291
- if (!definition) delete this.__schemas__[schema];
133511
+ add(schema, definition2 = null) {
133512
+ if (!definition2) delete this.__schemas__[schema];
133292
133513
  else {
133293
133514
  const def = {
133294
133515
  normalize: (match, self2) => self2.normalize(match),
133295
- ...definition
133516
+ ...definition2
133296
133517
  };
133297
133518
  this.__schemas__[schema] = def;
133298
133519
  }
@@ -137533,6 +137754,50 @@ var buildChoiceCard = (card) => {
137533
137754
 
137534
137755
  // src/larkInbound.ts
137535
137756
  var import_node_sdk = __toESM(require_lib2(), 1);
137757
+
137758
+ // src/larkConnectionHints.ts
137759
+ var larkConnectionHint = (error) => {
137760
+ if (error instanceof LarkApiError) {
137761
+ if (error.operation === "auth") {
137762
+ if (error.code === 10003 || error.code === 20002) return "larkCredentials";
137763
+ if (error.code === 10014) {
137764
+ const msg = error.msg.toLowerCase();
137765
+ if (msg.includes("app id not exists") || msg.includes("app secret invalid")) {
137766
+ return "larkCredentials";
137767
+ }
137768
+ return msg.includes("unauthorized") ? "larkAppUnavailable" : void 0;
137769
+ }
137770
+ }
137771
+ switch (error.code) {
137772
+ case 99991672:
137773
+ return "larkScope";
137774
+ case 230006:
137775
+ case 232025:
137776
+ return "larkBotOff";
137777
+ case 232034:
137778
+ return "larkAppUnavailable";
137779
+ default:
137780
+ return void 0;
137781
+ }
137782
+ }
137783
+ const code2 = error instanceof Error ? /^pullConnectConfig failed: code=(\d+), msg=/u.exec(error.message)?.[1] : void 0;
137784
+ return code2 === "1000040344" || code2 === "1000040345" || code2 === "1000040346" ? "larkCredentials" : void 0;
137785
+ };
137786
+
137787
+ // src/larkCredentials.ts
137788
+ var isLarkAppId = (appId) => /^cli_[0-9a-fA-F]{16}$/u.test(appId);
137789
+ var larkCredentials = (bot) => {
137790
+ const appId = bot.values["larkAppId"] ?? "";
137791
+ const appSecret = bot.values["larkAppSecret"] ?? "";
137792
+ const issues = [];
137793
+ if (appId === "") issues.push({ column: "larkAppId", code: "required" });
137794
+ else if (!isLarkAppId(appId)) issues.push({ column: "larkAppId", code: "format" });
137795
+ if (appSecret === "") issues.push({ column: "larkAppSecret", code: "required" });
137796
+ const error = issues.some((issue) => issue.code === "required") ? "missing app id or secret in saved config" : issues.length > 0 ? "malformed app id in saved config" : void 0;
137797
+ return { appId, appSecret, issues, error };
137798
+ };
137799
+
137800
+ // src/larkInbound.ts
137536
137801
  var isOwnMention = (mention, appId) => mention.id?.app_id === appId || mention.mentioned_type === "bot";
137537
137802
  var mentionsApp = (event, appId) => (event.message?.mentions ?? []).some((mention) => isOwnMention(mention, appId));
137538
137803
  var mentionsSomeoneElse = (event, appId) => (event.message?.mentions ?? []).some(
@@ -137546,12 +137811,12 @@ var passesGroupGate = (event, appId, groupMention, messageType) => {
137546
137811
  };
137547
137812
  var RESOURCE_MESSAGE_TYPES = /* @__PURE__ */ new Set(["image", "file", "audio", "media"]);
137548
137813
  var UNSUPPORTED_MESSAGE_TYPES = /* @__PURE__ */ new Set([
137814
+ "audio",
137549
137815
  "sticker",
137550
137816
  "folder",
137551
137817
  "share_chat",
137552
137818
  "share_user",
137553
137819
  "system",
137554
- "merge_forward",
137555
137820
  "interactive"
137556
137821
  ]);
137557
137822
  var botIdsOf = (mentions, appId) => {
@@ -137589,10 +137854,6 @@ var readResource = (messageType, content) => {
137589
137854
  const name = key("file_name") ?? "file";
137590
137855
  return { fileKey, kind: "file", name, mimeType: mimeFromName(name) };
137591
137856
  }
137592
- if (messageType === "audio") {
137593
- const fileKey = key("file_key");
137594
- return fileKey === null ? null : { fileKey, kind: "file", name: "voice.opus", mimeType: "audio/ogg" };
137595
- }
137596
137857
  if (messageType === "media") {
137597
137858
  const fileKey = key("file_key");
137598
137859
  if (fileKey === null) return null;
@@ -137621,7 +137882,11 @@ var parseInboundMessage = (event, appId, groupMention = "always", topicOnMention
137621
137882
  const rootGiven = typeof message.root_id === "string" && message.root_id !== "";
137622
137883
  const rootId = rootGiven && message.root_id !== void 0 ? message.root_id : messageId;
137623
137884
  const topic = chatType === "p2p" || !inThread ? {} : threadId === void 0 ? { topic: { rootMessageId: messageId } } : { topic: { rootMessageId: rootId, threadId, rootGiven } };
137624
- if (UNSUPPORTED_MESSAGE_TYPES.has(messageType)) {
137885
+ if (messageType === "audio") {
137886
+ const fileKey = readJson(message.content)?.["file_key"];
137887
+ if (typeof fileKey !== "string" || fileKey === "") return null;
137888
+ }
137889
+ if (messageType === "merge_forward" || UNSUPPORTED_MESSAGE_TYPES.has(messageType)) {
137625
137890
  return {
137626
137891
  messageId,
137627
137892
  chatId,
@@ -137631,7 +137896,7 @@ var parseInboundMessage = (event, appId, groupMention = "always", topicOnMention
137631
137896
  resources: [],
137632
137897
  inThread,
137633
137898
  ...topic,
137634
- unsupportedType: messageType
137899
+ ...messageType === "merge_forward" ? { mergeForward: true, ...parentMessageId ? { parentMessageId } : {} } : { unsupportedType: messageType === "audio" ? "voice" : messageType }
137635
137900
  };
137636
137901
  }
137637
137902
  const mentions = message.mentions ?? [];
@@ -137723,7 +137988,6 @@ var cardActionOperator = (event) => {
137723
137988
  }
137724
137989
  return null;
137725
137990
  };
137726
- var isLarkAppId = (appId) => /^cli_[0-9a-fA-F]{16}$/u.test(appId);
137727
137991
  var startLarkInbound = (input) => {
137728
137992
  const dispatcher = new import_node_sdk.EventDispatcher({}).register({
137729
137993
  "im.message.receive_v1": (event) => {
@@ -137766,7 +138030,11 @@ var startLarkInbound = (input) => {
137766
138030
  }
137767
138031
  });
137768
138032
  if (!isLarkAppId(input.appId)) {
137769
- input.onConnectionState?.({ state: "failed", reason: "invalid-app-id" });
138033
+ input.onConnectionState?.({
138034
+ state: "failed",
138035
+ reason: "invalid-app-id",
138036
+ hint: "larkCredentials"
138037
+ });
137770
138038
  return { close: () => void 0 };
137771
138039
  }
137772
138040
  let closed = false;
@@ -137775,7 +138043,13 @@ var startLarkInbound = (input) => {
137775
138043
  };
137776
138044
  const failed = (error) => {
137777
138045
  if (!closed) {
137778
- input.onConnectionState?.({ state: "failed", reason: "disconnected", error });
138046
+ const hint = larkConnectionHint(error);
138047
+ input.onConnectionState?.({
138048
+ state: "failed",
138049
+ reason: hint === "larkCredentials" ? "credentials-rejected" : "disconnected",
138050
+ error,
138051
+ ...hint === void 0 ? {} : { hint }
138052
+ });
137779
138053
  }
137780
138054
  };
137781
138055
  const client = new import_node_sdk.WSClient({
@@ -137801,6 +138075,24 @@ var startLarkInbound = (input) => {
137801
138075
  };
137802
138076
  };
137803
138077
 
138078
+ // src/boundedSeen.ts
138079
+ var makeBoundedSeen = (limit) => {
138080
+ const seen = /* @__PURE__ */ new Set();
138081
+ return {
138082
+ has: (key) => seen.has(key),
138083
+ /** 记下这个键:第一次见返回 true;已经记着返回 false(不重排,最早记的先被忘)。 */
138084
+ claim: (key) => {
138085
+ if (seen.has(key)) return false;
138086
+ seen.add(key);
138087
+ if (seen.size > limit) {
138088
+ const oldest = seen.values().next().value;
138089
+ if (oldest !== void 0) seen.delete(oldest);
138090
+ }
138091
+ return true;
138092
+ }
138093
+ };
138094
+ };
138095
+
137804
138096
  // src/deadline.ts
137805
138097
  var withDeadline2 = async (timeoutMs, load) => {
137806
138098
  const signal = AbortSignal.timeout(timeoutMs);
@@ -137993,15 +138285,157 @@ var makeLarkMessageContext = (deps) => {
137993
138285
  if (message.chatType !== "group" || message.senderOpenId === null) return {};
137994
138286
  const name = contactDenied ? null : userName(message.senderOpenId);
137995
138287
  const chat = chatInfo(message.chatId);
137996
- const fields = (senderName, info) => ({
137997
- ...senderName ? { senderName } : {},
138288
+ const fields = (senderName2, info) => ({
138289
+ ...senderName2 ? { senderName: senderName2 } : {},
137998
138290
  ...info?.name ? { chatTitle: info.name } : {}
137999
138291
  });
138000
- return name instanceof Promise || chat instanceof Promise ? Promise.all([name, chat]).then(([senderName, info]) => fields(senderName, info)) : fields(name, chat);
138292
+ return name instanceof Promise || chat instanceof Promise ? Promise.all([name, chat]).then(([senderName2, info]) => fields(senderName2, info)) : fields(name, chat);
138001
138293
  };
138002
138294
  return { chatInfo, forMessage, loadReference, topicRootForSession };
138003
138295
  };
138004
138296
 
138297
+ // src/larkMergeForward.ts
138298
+ var MAX_MESSAGES = 100;
138299
+ var MAX_MESSAGE_CHARS = 2e3;
138300
+ var MAX_BODY_CHARS = 3e4;
138301
+ var clip2 = (text4, limit, suffix = "\u2026") => {
138302
+ if (text4.length <= limit) return text4;
138303
+ const end = limit - suffix.length;
138304
+ const safeEnd = /[\uD800-\uDBFF]/u.test(text4[end - 1] ?? "") ? end - 1 : end;
138305
+ return text4.slice(0, safeEnd) + suffix;
138306
+ };
138307
+ var senderName = (message, t) => {
138308
+ const names = message.sender?.sender_i18n_names;
138309
+ const name = [
138310
+ t.language === "zh-CN" ? names?.zh_cn : names?.en_us,
138311
+ names?.zh_cn,
138312
+ names?.en_us,
138313
+ names?.ja_jp
138314
+ ].find((value2) => value2?.trim());
138315
+ return clip2(
138316
+ name?.trim() ?? t(message.sender?.sender_type === "app" ? "mergeForward.bot" : "mergeForward.someone"),
138317
+ 120
138318
+ ).replace(/\s+/gu, " ");
138319
+ };
138320
+ var localTime = (value2, t) => {
138321
+ const date = new Date(Number(value2 ?? NaN));
138322
+ if (Number.isNaN(date.getTime())) return t("mergeForward.unknownTime");
138323
+ const pad = (n) => String(n).padStart(2, "0");
138324
+ return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
138325
+ };
138326
+ var resourceLabel = (kind, name, t) => {
138327
+ const type = t.optional(`mergeForward.resource.${kind}`) ?? kind;
138328
+ return typeof name === "string" && name.trim() ? t("mergeForward.unavailableNamed", { type, name: name.trim() }) : t("mergeForward.unavailable", { type });
138329
+ };
138330
+ var messageText = (message, t) => {
138331
+ const type = message.msg_type ?? "unknown";
138332
+ const content = message.body?.content;
138333
+ let text4;
138334
+ if (type === "post") {
138335
+ const post = serializePost(
138336
+ content ?? "{}",
138337
+ void 0,
138338
+ (resource) => resourceLabel(resource.media, resource.fileName, t)
138339
+ );
138340
+ text4 = post?.text ?? "[post]";
138341
+ } else if (type === "text") {
138342
+ const value2 = readJson(content)?.["text"];
138343
+ text4 = typeof value2 === "string" ? value2 : "[text]";
138344
+ } else if (["image", "file", "audio", "media", "folder"].includes(type)) {
138345
+ text4 = resourceLabel(type === "media" ? "video" : type, readJson(content)?.["file_name"], t);
138346
+ } else {
138347
+ text4 = `[${type}]`;
138348
+ }
138349
+ const mentions = new Map((message.mentions ?? []).map((mention) => [mention.key, mention.name]));
138350
+ text4 = text4.replace(
138351
+ /@_user_\d+|@_all/gu,
138352
+ (key) => `@${mentions.get(key)?.trim() || t("mergeForward.someone")}`
138353
+ );
138354
+ return clip2(text4.trim(), MAX_MESSAGE_CHARS, t("mergeForward.truncated"));
138355
+ };
138356
+ var orderedMessages = (rootId, items) => {
138357
+ const byId = new Map(
138358
+ items.filter((item) => item.message_id !== rootId).map((item) => [item.message_id, item])
138359
+ );
138360
+ const children = /* @__PURE__ */ new Map();
138361
+ for (const item of byId.values()) {
138362
+ const parent = item.upper_message_id ?? rootId;
138363
+ const siblings = children.get(parent) ?? [];
138364
+ siblings.push(item);
138365
+ children.set(parent, siblings);
138366
+ }
138367
+ const compare = (a, b) => Number(a.create_time ?? 0) - Number(b.create_time ?? 0);
138368
+ for (const siblings of children.values()) siblings.sort(compare);
138369
+ const rows = [];
138370
+ const seen = /* @__PURE__ */ new Set([rootId]);
138371
+ const walk = (first) => {
138372
+ const stack = [{ message: first, depth: 0 }];
138373
+ while (stack.length) {
138374
+ const frame = stack.pop();
138375
+ if (frame === void 0) break;
138376
+ if ("end" in frame) {
138377
+ const row = rows[frame.end];
138378
+ if (row) row.descendants = rows.length - frame.end - 1;
138379
+ continue;
138380
+ }
138381
+ if (seen.has(frame.message.message_id)) continue;
138382
+ seen.add(frame.message.message_id);
138383
+ const end = rows.length;
138384
+ rows.push({ ...frame, descendants: 0 });
138385
+ stack.push({ end });
138386
+ for (const child of (children.get(frame.message.message_id) ?? []).toReversed()) {
138387
+ stack.push({ message: child, depth: frame.depth + 1 });
138388
+ }
138389
+ }
138390
+ };
138391
+ for (const item of children.get(rootId) ?? []) walk(item);
138392
+ for (const item of [...byId.values()].sort(compare)) walk(item);
138393
+ return rows;
138394
+ };
138395
+ var serializeMergeForward = (rootId, items, t) => {
138396
+ const rows = orderedMessages(rootId, items);
138397
+ const lines = [t("mergeForward.title", { count: rows.length })];
138398
+ let length = lines[0]?.length ?? 0;
138399
+ let included = 0;
138400
+ for (const { message, depth, descendants } of rows) {
138401
+ if (included >= MAX_MESSAGES) break;
138402
+ const indent = " ".repeat(depth);
138403
+ const body = message.msg_type === "merge_forward" ? t("mergeForward.nested", { count: descendants }) : messageText(message, t);
138404
+ const line = `${indent}${senderName(message, t)} ${localTime(message.create_time, t)}: ${body.replaceAll("\n", `
138405
+ ${indent} `)}`;
138406
+ const remaining = rows.length - included - 1;
138407
+ const tail = remaining > 0 ? t("mergeForward.omitted", { count: remaining }).length + 1 : 0;
138408
+ if (length + line.length + 1 + tail > MAX_BODY_CHARS) break;
138409
+ lines.push(line);
138410
+ length += line.length + 1;
138411
+ included += 1;
138412
+ }
138413
+ if (included < rows.length)
138414
+ lines.push(t("mergeForward.omitted", { count: rows.length - included }));
138415
+ return lines.join("\n");
138416
+ };
138417
+ var loadMergeForward = async (message, deps) => {
138418
+ try {
138419
+ const items = await withDeadline2(1e4, async (signal) => {
138420
+ const token = await deps.tenantToken();
138421
+ signal.throwIfAborted();
138422
+ return fetchMergeForwardMessages({
138423
+ http: deps.http,
138424
+ token,
138425
+ messageId: message.messageId,
138426
+ signal
138427
+ });
138428
+ });
138429
+ return { ...message, text: serializeMergeForward(message.messageId, items, deps.translate) };
138430
+ } catch (error) {
138431
+ deps.warn("lark could not read merged forward messages", {
138432
+ messageId: message.messageId,
138433
+ error: describeError(error)
138434
+ });
138435
+ return { ...message, unsupportedType: "merge_forward" };
138436
+ }
138437
+ };
138438
+
138005
138439
  // src/larkAdapter.ts
138006
138440
  var TOKEN_CACHE_MAX_AGE_MS = 30 * 6e4;
138007
138441
  var COLUMNS = [
@@ -138033,21 +138467,9 @@ var splitTopicChatId = (chatId) => {
138033
138467
  return at < 0 ? { chatId } : { chatId: chatId.slice(0, at), rootMessageId: chatId.slice(at + 1) };
138034
138468
  };
138035
138469
  var topicAliasOf = (chatId, threadId) => `${chatId}:${threadId}`;
138036
- var credentials = (binding) => ({
138037
- appId: binding.values["larkAppId"] ?? "",
138038
- appSecret: binding.values["larkAppSecret"] ?? ""
138039
- });
138040
- var larkConfigIssues = (binding) => {
138041
- const { appId, appSecret } = credentials(binding);
138042
- const issues = [];
138043
- if (appId === "") issues.push({ column: "larkAppId", code: "required" });
138044
- else if (!isLarkAppId(appId)) issues.push({ column: "larkAppId", code: "format" });
138045
- if (appSecret === "") issues.push({ column: "larkAppSecret", code: "required" });
138046
- return issues;
138047
- };
138048
138470
  var makeLarkAdapter = (deps) => {
138049
138471
  const tenantToken = async (binding) => {
138050
- const { appId, appSecret } = credentials(binding);
138472
+ const { appId, appSecret } = larkCredentials(binding);
138051
138473
  const key = `lark-tenant-token.${appId}`;
138052
138474
  const cached = await deps.cacheGet(key, TOKEN_CACHE_MAX_AGE_MS);
138053
138475
  if (typeof cached?.token === "string") return cached.token;
@@ -138120,6 +138542,7 @@ var makeLarkAdapter = (deps) => {
138120
138542
  };
138121
138543
  return {
138122
138544
  platform: "lark",
138545
+ botIdentity: (bot) => larkCredentials(bot).appId,
138123
138546
  icon: "lark",
138124
138547
  label: "Lark",
138125
138548
  columns: COLUMNS,
@@ -138128,10 +138551,10 @@ var makeLarkAdapter = (deps) => {
138128
138551
  const { chatId: groupId, rootMessageId } = splitTopicChatId(chatId);
138129
138552
  return rootMessageId === void 0 ? void 0 : groupId;
138130
138553
  },
138131
- validate: larkConfigIssues,
138132
- isConfigured: (binding) => larkConfigIssues(binding).every((issue) => issue.code !== "required"),
138554
+ validate: (binding) => larkCredentials(binding).issues,
138555
+ isConfigured: (binding) => larkCredentials(binding).issues.every((issue) => issue.code !== "required"),
138133
138556
  connect: (binding, handlers) => {
138134
- const { appId, appSecret } = credentials(binding);
138557
+ const { appId, appSecret } = larkCredentials(binding);
138135
138558
  const port = {
138136
138559
  noteChatAlias: handlers.noteChatAlias,
138137
138560
  knowsChatAlias: handlers.knowsChatAlias
@@ -138168,11 +138591,17 @@ var makeLarkAdapter = (deps) => {
138168
138591
  arg: click.arg
138169
138592
  }) ?? null,
138170
138593
  onMessage: (message) => {
138594
+ const prepared = message.mergeForward ? loadMergeForward(message, {
138595
+ http: deps.http,
138596
+ tenantToken: () => tenantToken(binding),
138597
+ translate: deps.translate,
138598
+ warn: deps.log.warn
138599
+ }) : message;
138171
138600
  const { topic } = message;
138172
138601
  const source = context.forMessage(message);
138173
138602
  const parentMessageId = message.parentMessageId;
138174
138603
  const reference2 = parentMessageId === void 0 ? null : context.loadReference(parentMessageId);
138175
- const deliver = (names, replyTo) => handlers.onMessage({
138604
+ const deliver = (ready, names, replyTo) => handlers.onMessage({
138176
138605
  messageId: message.messageId,
138177
138606
  // 已经开出来的话题就是它自己那个聊天;将要开出来的(主窗口里 @ 机器人)先算群的,
138178
138607
  // 由核心决定要不要分出去。
@@ -138186,7 +138615,7 @@ var makeLarkAdapter = (deps) => {
138186
138615
  },
138187
138616
  senderId: message.senderOpenId,
138188
138617
  ...names,
138189
- text: message.text,
138618
+ text: ready.text,
138190
138619
  topicRootForSession: context.topicRootForSession(message),
138191
138620
  ...replyTo === void 0 ? {} : { replyTo },
138192
138621
  chatKind: message.chatType === "p2p" ? "private" : "group",
@@ -138197,7 +138626,7 @@ var makeLarkAdapter = (deps) => {
138197
138626
  mimeType: resource.mimeType,
138198
138627
  kind: resource.kind
138199
138628
  })),
138200
- ...message.unsupportedType === void 0 ? {} : { unsupportedType: message.unsupportedType }
138629
+ ...ready.unsupportedType === void 0 ? {} : { unsupportedType: ready.unsupportedType }
138201
138630
  });
138202
138631
  const pending = topic?.threadId === void 0 ? null : topicUnsettled(binding.rowId, message.chatId, {
138203
138632
  threadId: topic.threadId,
@@ -138205,12 +138634,12 @@ var makeLarkAdapter = (deps) => {
138205
138634
  });
138206
138635
  const key = JSON.stringify([message.chatId, topic?.threadId]);
138207
138636
  const earlier = pendingDeliveries.get(key);
138208
- if (earlier === void 0 && pending === null && reference2 === null && !(source instanceof Promise)) {
138209
- deliver(source);
138637
+ if (earlier === void 0 && pending === null && reference2 === null && !(prepared instanceof Promise) && !(source instanceof Promise)) {
138638
+ deliver(prepared, source);
138210
138639
  } else {
138211
- const completed = Promise.all([earlier, pending, reference2, source]).then(([, , replyTo, names]) => {
138640
+ const completed = Promise.all([earlier, pending, reference2, source, prepared]).then(([, , replyTo, names, ready]) => {
138212
138641
  if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
138213
- if (open) deliver(names, replyTo ?? void 0);
138642
+ if (open) deliver(ready, names, replyTo ?? void 0);
138214
138643
  }).catch((error) => handlers.onError(describeError(error))).finally(() => {
138215
138644
  if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
138216
138645
  });
@@ -138249,17 +138678,15 @@ var makeLarkAdapter = (deps) => {
138249
138678
  }
138250
138679
  },
138251
138680
  checkConnection: async (bot) => {
138252
- const { appId, appSecret } = credentials(bot);
138253
- if (appId === "" || appSecret === "") {
138681
+ const { appId, appSecret, error: credentialError } = larkCredentials(bot);
138682
+ if (credentialError !== void 0) {
138254
138683
  return {
138255
138684
  stage: "credentials",
138256
138685
  ok: false,
138257
- detail: "missing app id or secret in saved config"
138686
+ detail: credentialError,
138687
+ hint: "larkCredentials"
138258
138688
  };
138259
138689
  }
138260
- if (!isLarkAppId(appId)) {
138261
- return { stage: "credentials", ok: false, detail: "malformed app id in saved config" };
138262
- }
138263
138690
  try {
138264
138691
  const { expiresInSeconds } = await fetchTenantToken({ http: deps.http, appId, appSecret });
138265
138692
  return {
@@ -138268,7 +138695,13 @@ var makeLarkAdapter = (deps) => {
138268
138695
  detail: `token ok; expiresIn=${String(expiresInSeconds)}s`
138269
138696
  };
138270
138697
  } catch (error) {
138271
- return { stage: "tenant_access_token", ok: false, detail: describeError(error) };
138698
+ const hint = larkConnectionHint(error);
138699
+ return {
138700
+ stage: "tenant_access_token",
138701
+ ok: false,
138702
+ detail: describeError(error),
138703
+ ...hint === void 0 ? {} : { hint }
138704
+ };
138272
138705
  }
138273
138706
  },
138274
138707
  sendImage: async (binding, image2) => {
@@ -138606,6 +139039,7 @@ var en_default = {
138606
139039
  projectArchived: 'Project "{{title}}" is archived. Restore it, or send /repo to pick another project, then message again.',
138607
139040
  projectMissing: "The project this chat was working on is no longer on this machine (it may have been deleted), so a thread cannot be started. Send /repo to pick another one.",
138608
139041
  threadGone: "The earlier session was deleted or archived, so this chat now starts a new one.",
139042
+ turnFailedToStart: "This message couldn't be dispatched. Please send it again later. Reference {{ref}}.",
138609
139043
  turnFailed: "This turn didn't finish: {{detail}}\nReference {{ref}}. Send the message again; if it keeps failing, send /new to start a new conversation.",
138610
139044
  turnFailedNoDetail: "This turn didn't finish. Reference {{ref}}. Send the message again; if it keeps failing, send /new to start a new conversation.",
138611
139045
  turnFailedBrief: "This turn didn't finish: {{detail}}\nSend the message again; if it keeps failing, send /new to start a new conversation.",
@@ -138622,9 +139056,11 @@ var en_default = {
138622
139056
  attachmentsNoTeam: "Files cannot be sent to a team yet, so they were left out. Send /thread to go back to a normal session.",
138623
139057
  unsupportedType: "I can't process {{type}} messages yet.",
138624
139058
  unsupportedFolder: "Folders can't be downloaded. Zip it and send the archive instead.",
139059
+ unsupportedVoice: "Voice messages aren't supported yet. Please send text.",
138625
139060
  sendingFile: "Sending {{name}}:",
138626
139061
  notAllowedYet: `You are not on the allow list yet. Your user id is {{id}}. Add it under Settings \u2192 Plugins \u2192 Chat, in this bot's "Allowed people" box.`,
138627
- received: "Got it, working on it."
139062
+ received: "Got it, working on it.",
139063
+ welcome: "Connected. Send text or images to get started, or send /help to see the available commands."
138628
139064
  },
138629
139065
  approval: {
138630
139066
  allow: "Allow",
@@ -138928,6 +139364,7 @@ var en_default = {
138928
139364
  }
138929
139365
  },
138930
139366
  repo: {
139367
+ autoSelected: "Using project {{title}}.",
138931
139368
  needProject: "This chat has no project yet, so I won't start any work. Pick one and I will.",
138932
139369
  currentLine: 'This chat works on project "{{title}}".',
138933
139370
  noProjects: "There are no projects on this machine yet. Add one in Ycodium first.",
@@ -138958,21 +139395,41 @@ var en_default = {
138958
139395
  failed: "Connection failed",
138959
139396
  notSaved: "Save this row first (credentials are read from saved config only).",
138960
139397
  hint: {
139398
+ connecting: "Still connecting. There is usually a result within 20 seconds; check again in a moment.",
138961
139399
  badToken: "This does not look like a bot token. Copy it again from BotFather: digits, a colon, then the key.",
138962
- unauthorized: "Telegram does not accept this token. It may have been revoked; get the current one from BotFather.",
139400
+ unauthorized: "Telegram does not accept this token. It may have been revoked; get the current one from BotFather. Save the corrected token here to reconnect automatically; if nothing needs changing, use Reconnect below.",
138963
139401
  notBot: "This token does not belong to a bot. Use the token BotFather gave you.",
138964
139402
  webhookActive: "This bot has a webhook set by another service, so it cannot receive messages here. Remove the webhook there, then check again.",
139403
+ larkCredentials: "Lark does not accept this App ID or App Secret. Copy both from Credentials & Basic Info for your self-built app in the Lark developer console. Save the corrected App ID and App Secret here to reconnect automatically; if nothing needs changing, use Reconnect below.",
139404
+ larkScope: "This app is missing a required permission. In the Lark developer console, open Permission Management, add the permissions listed in the details, then publish a version.",
139405
+ larkBotOff: "This app's bot capability is not enabled. In the Lark developer console, open App Capabilities \u2192 Bot, enable the bot, then publish a version.",
139406
+ larkAppUnavailable: "This app is unavailable in your organization. In the Lark developer console, check that the app is enabled and published; ask your administrator to confirm it is installed and enabled for your organization.",
139407
+ wecomBotCredentials: "WeCom does not accept this Bot ID or Secret. Open this smart bot's configuration in the WeCom admin console, select API mode \u2192 Long connection, and copy its Bot ID and Secret. These are not the callback Token or EncodingAESKey. Save the corrected Bot ID and Secret here to reconnect automatically; if nothing needs changing, use Reconnect below.",
139408
+ wecomLongConnOff: "This smart bot's long connection mode is not enabled. Open its configuration in the WeCom admin console, select API mode \u2192 Long connection and save; the bot reconnects on its own within a minute, or use Reconnect below.",
139409
+ wecomTakenOver: "Another machine or program is using the same Bot ID. Stop using this smart bot there, then use Reconnect below.",
138965
139410
  wecomTrustedIp: "Add this machine's outbound public IP to the app's trusted IP list in the WeCom admin console.",
138966
139411
  wecomInvalidUser: "Check that the recipient, department or tag exists and is in this app's visible range.",
138967
- wecomInvalidAgent: "Check the AgentId against this self-built application in the WeCom admin console."
139412
+ wecomInvalidAgent: "Check the AgentId against this self-built application in the WeCom admin console.",
139413
+ wecomCallbackNotSeen: "Credentials are valid, but no verified callback has arrived since this startup. In the app's Receive messages settings in the WeCom admin console, paste the callback URL shown on this bot's settings card and save; WeCom will send a verification request. This machine must be reachable by WeCom over the public internet.",
139414
+ wecomCallbackSignature: "A callback arrived, but its signature or decryption failed; no verified callback has arrived since this startup. Match the Callback Token and EncodingAESKey to this app's Receive messages settings in the WeCom admin console, then save there to verify again."
138968
139415
  }
138969
139416
  },
139417
+ reconnect: {
139418
+ label: "Reconnect",
139419
+ description: "Disconnect and reconnect this bot using its saved settings.",
139420
+ notAvailable: "This platform can't be connected yet, so reconnecting won't help.",
139421
+ running: "Starting to reconnect {{rowId}}\u2026",
139422
+ done: "Reconnection started. Check the bot's connection status for the result.",
139423
+ notSaved: "Save this row first before reconnecting. Only saved settings are used.",
139424
+ notConfigured: "Complete and save this bot's settings before reconnecting.",
139425
+ duplicate: "Another bot row uses these credentials. Resolve the duplicate before reconnecting.",
139426
+ inactive: "The chat bridge is turned off. Enable it before reconnecting."
139427
+ },
138970
139428
  copyDiagnostics: {
138971
139429
  label: "Copy diagnostics",
138972
139430
  description: "Run a connection probe and produce copy-paste diagnostics (no secrets).",
138973
139431
  running: "Building diagnostics for {{rowId}}\u2026",
138974
- done: "Diagnostics ready \u2014 see details below",
138975
- failed: "Diagnostics failed",
139432
+ done: "Diagnostic report ready \u2014 see details below",
138976
139433
  notSaved: "Save this row first before copying diagnostics."
138977
139434
  },
138978
139435
  diagnostic: {
@@ -138980,16 +139437,23 @@ var en_default = {
138980
139437
  },
138981
139438
  larkRepair: {
138982
139439
  label: "Repair scopes",
138983
- description: "Check whether the saved credentials can mint a token; scopes, event subscriptions and publishing are not checked automatically.",
139440
+ description: "List missing required scopes and a link to request them on the Feishu open platform; an app administrator then publishes the app version manually.",
138984
139441
  step: {
138985
- scopes: "Scopes \u2014 not checked",
138986
- events: "Event subscriptions \u2014 not checked",
138987
- publish: "Publishing \u2014 not checked",
138988
- verify: "Verify the app can still mint a tenant token"
138989
- },
138990
- done: "Only token issuance was verified. Scopes, event subscriptions and publishing were not checked automatically; review them on the Feishu open platform.",
138991
- hint: "Reference: {{ref}}\n{{detail}}\nReview scopes, event subscriptions and publishing on the Feishu open platform; none were checked automatically.",
138992
- failed: "Could not mint a token with the saved credentials. Scopes, event subscriptions and publishing were not checked automatically; review them on the Feishu open platform.",
139442
+ token: "Get a tenant token with the saved credentials",
139443
+ scopes: "Check the app's granted tenant scopes"
139444
+ },
139445
+ missing: "Some required scopes are missing.",
139446
+ allGranted: "All required scopes are enabled.",
139447
+ unverified: "Unable to check scopes automatically.",
139448
+ missingScopes: "Missing scopes:\n{{scopes}}",
139449
+ requiredScopes: "Required scopes \u2014 check each one manually:\n{{scopes}}\n\nApps without application:application:self_manage cannot report their granted scopes, so this list could not be checked automatically.",
139450
+ openApplyLink: "Request scopes on the Feishu open platform",
139451
+ openCheckLink: "Check and request scopes on the Feishu open platform",
139452
+ applyLink: "Open platform URL:\n{{url}}",
139453
+ publishHint: "As an app administrator, select the scopes at that link. Then publish a version via App release \u2192 Version management & release \u2192 Create version, and return here to click \u201CCheck connection\u201D.",
139454
+ botHint: "To enable the bot, go to App \u2192 Add app capability \u2192 Bot. Bot capability is not checked here.",
139455
+ eventHint: "To receive messages, set both subscriptions in Events & Callbacks to Long connection, then add these (not checked here):\nEvents: {{events}}\nCallbacks: {{callbacks}}",
139456
+ failed: "Could not check the app's required scopes. Review the error details and try again.",
138993
139457
  notLark: "This row is not a saved Lark bot."
138994
139458
  }
138995
139459
  },
@@ -139036,11 +139500,13 @@ var en_default = {
139036
139500
  connection: {
139037
139501
  failed: "The last connection attempt failed.",
139038
139502
  details: "Details: {{error}}",
139503
+ reconnect: "You can try again with Reconnect below.",
139039
139504
  reason: {
139040
139505
  "not-available": "This platform can't be connected yet.",
139041
139506
  "invalid-app-id": "The App ID isn't in the right format.",
139042
139507
  disconnected: "The connection to the platform is down.",
139043
139508
  "subscription-failed": "The platform didn't confirm the subscription.",
139509
+ "taken-over": "This connection was taken over elsewhere, so automatic reconnection has stopped here.",
139044
139510
  "token-unavailable": "Couldn't get an access token.",
139045
139511
  "credentials-rejected": "The platform rejected the credentials."
139046
139512
  },
@@ -139064,7 +139530,7 @@ var en_default = {
139064
139530
  done: "Done.",
139065
139531
  close: "Close",
139066
139532
  failedToRun: "Couldn't run this on the machine.",
139067
- saveFirst: "Save or undo your changes first. These check the saved settings.",
139533
+ saveFirst: "Save or undo your changes first. These actions use the saved settings.",
139068
139534
  qrTitle: "QR code to scan with the Lark app"
139069
139535
  },
139070
139536
  step: {
@@ -139090,6 +139556,24 @@ var en_default = {
139090
139556
  callbackFollowsKeys: "This URL changes when Token or EncodingAESKey changes. Paste it into WeCom again after changing them."
139091
139557
  }
139092
139558
  },
139559
+ mergeForward: {
139560
+ title: "[Forwarded chat history, messages: {{count}}]",
139561
+ nested: "[Nested forward, messages: {{count}}]",
139562
+ someone: "Someone",
139563
+ bot: "A bot",
139564
+ unknownTime: "[unknown time]",
139565
+ resource: {
139566
+ image: "Image",
139567
+ file: "File",
139568
+ audio: "Audio",
139569
+ video: "Video",
139570
+ folder: "Folder"
139571
+ },
139572
+ unavailable: "[{{type}}: could not be retrieved]",
139573
+ unavailableNamed: "[{{type}} {{name}}: could not be retrieved]",
139574
+ omitted: "\u2026 not shown: {{count}} more",
139575
+ truncated: "\u2026 [message truncated]"
139576
+ },
139093
139577
  team: {
139094
139578
  noRoster: "This project has no team roster yet. Save a roster in the Teams tab and choose a lead first.",
139095
139579
  unavailable: "Could not read this project's team roster. Check that the Teams plugin is enabled and try again.",
@@ -139308,6 +139792,7 @@ var zh_CN_default = {
139308
139792
  projectArchived: "\u9879\u76EE\u300C{{title}}\u300D\u5DF2\u5F52\u6863\u3002\u6062\u590D\u5B83\uFF0C\u6216\u53D1 /repo \u6362\u4E2A\u9879\u76EE\uFF0C\u518D\u53D1\u6D88\u606F\u3002",
139309
139793
  projectMissing: "\u8FD9\u4E2A\u804A\u5929\u539F\u6765\u505A\u7684\u9879\u76EE\u5DF2\u7ECF\u4E0D\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u4E86\uFF08\u53EF\u80FD\u88AB\u5220\u4E86\uFF09\uFF0C\u5F00\u4E0D\u4E86\u4F1A\u8BDD\u3002\u53D1 /repo \u91CD\u65B0\u9009\u4E00\u4E2A\u3002",
139310
139794
  threadGone: "\u4E4B\u524D\u90A3\u6761\u4F1A\u8BDD\u5DF2\u88AB\u5220\u9664\u6216\u5F52\u6863\uFF0C\u8FD9\u4E2A\u804A\u5929\u6539\u4E3A\u5F00\u4E00\u6761\u65B0\u4F1A\u8BDD\u3002",
139795
+ turnFailedToStart: "\u8FD9\u6761\u6D88\u606F\u6CA1\u80FD\u6D3E\u51FA\u53BB\uFF0C\u8BF7\u7A0D\u540E\u91CD\u53D1\u3002\u53C2\u8003\u53F7 {{ref}}\u3002",
139311
139796
  turnFailed: "\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\uFF1A{{detail}}\n\u53C2\u8003\u53F7 {{ref}}\u3002\u53EF\u4EE5\u76F4\u63A5\u518D\u53D1\u4E00\u6B21\uFF1B\u4E00\u76F4\u4E0D\u884C\u5C31\u53D1 /new \u5F00\u4E2A\u65B0\u4F1A\u8BDD\u3002",
139312
139797
  turnFailedNoDetail: "\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\u3002\u53C2\u8003\u53F7 {{ref}}\u3002\u53EF\u4EE5\u76F4\u63A5\u518D\u53D1\u4E00\u6B21\uFF1B\u4E00\u76F4\u4E0D\u884C\u5C31\u53D1 /new \u5F00\u4E2A\u65B0\u4F1A\u8BDD\u3002",
139313
139798
  turnFailedBrief: "\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5B8C\u6210\uFF1A{{detail}}\n\u53EF\u4EE5\u76F4\u63A5\u518D\u53D1\u4E00\u6B21\uFF1B\u4E00\u76F4\u4E0D\u884C\u5C31\u53D1 /new \u5F00\u4E2A\u65B0\u4F1A\u8BDD\u3002",
@@ -139324,9 +139809,11 @@ var zh_CN_default = {
139324
139809
  attachmentsNoTeam: "\u6587\u4EF6\u8FD8\u53D1\u4E0D\u5230\u56E2\u961F\uFF0C\u8FD9\u6B21\u6CA1\u5E26\u4E0A\u3002\u53D1 /thread \u56DE\u5230\u666E\u901A\u4F1A\u8BDD\u3002",
139325
139810
  unsupportedType: "\u6211\u8FD8\u4E0D\u80FD\u5904\u7406 {{type}} \u7C7B\u578B\u7684\u6D88\u606F\u3002",
139326
139811
  unsupportedFolder: "\u6587\u4EF6\u5939\u6CA1\u6CD5\u4E0B\u8F7D\uFF0C\u8BF7\u6253\u5305\u6210\u538B\u7F29\u6587\u4EF6\u518D\u53D1\u3002",
139812
+ unsupportedVoice: "\u6682\u65F6\u4E0D\u652F\u6301\u8BED\u97F3\uFF0C\u8BF7\u53D1\u6587\u5B57\u3002",
139327
139813
  sendingFile: "\u6B63\u5728\u53D1\u9001 {{name}}\uFF1A",
139328
139814
  notAllowedYet: "\u4F60\u8FD8\u4E0D\u5728\u5141\u8BB8\u540D\u5355\u91CC\uFF0C\u4F60\u7684\u7528\u6237 id \u662F {{id}}\uFF0C\u8BF7\u628A\u5B83\u586B\u8FDB \u8BBE\u7F6E \u2192 \u63D2\u4EF6 \u2192 \u804A\u5929 \u2192 \u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u300C\u5141\u8BB8\u7684\u4EBA\u300D\u3002",
139329
- received: "\u6536\u5230\uFF0C\u5728\u5904\u7406\u3002"
139815
+ received: "\u6536\u5230\uFF0C\u5728\u5904\u7406\u3002",
139816
+ welcome: "\u5DF2\u8FDE\u4E0A\u3002\u76F4\u63A5\u53D1\u6587\u5B57\u6216\u56FE\u7247\u5C31\u80FD\u5F00\u59CB\uFF0C\u53D1 /help \u770B\u80FD\u7528\u7684\u547D\u4EE4\u3002"
139330
139817
  },
139331
139818
  approval: {
139332
139819
  allow: "\u5141\u8BB8",
@@ -139630,6 +140117,7 @@ var zh_CN_default = {
139630
140117
  }
139631
140118
  },
139632
140119
  repo: {
140120
+ autoSelected: "\u4F7F\u7528\u9879\u76EE\u300C{{title}}\u300D\u3002",
139633
140121
  needProject: "\u8FD9\u4E2A\u804A\u5929\u8FD8\u6CA1\u9009\u9879\u76EE\uFF0C\u9009\u5B9A\u4E4B\u524D\u6211\u4E0D\u5F00\u5DE5\u3002\u9009\u4E00\u4E2A\u5C31\u5F00\u59CB\u3002",
139634
140122
  currentLine: "\u8FD9\u4E2A\u804A\u5929\u73B0\u5728\u5728\u9879\u76EE\u300C{{title}}\u300D\u4E0A\u3002",
139635
140123
  noProjects: "\u8FD9\u53F0\u673A\u5668\u4E0A\u8FD8\u6CA1\u6709\u9879\u76EE\uFF0C\u5148\u5230 Ycodium \u91CC\u6DFB\u52A0\u4E00\u4E2A\u3002",
@@ -139660,21 +140148,41 @@ var zh_CN_default = {
139660
140148
  failed: "\u8FDE\u63A5\u5931\u8D25",
139661
140149
  notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u68C0\u67E5\u8FDE\u63A5\uFF08\u51ED\u636E\u53EA\u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u8BFB\u53D6\uFF09\u3002",
139662
140150
  hint: {
140151
+ connecting: "\u8FD8\u5728\u8FDE\u63A5\uFF0C\u4E00\u822C 20 \u79D2\u5185\u4F1A\u6709\u7ED3\u679C\uFF0C\u7A0D\u540E\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
139663
140152
  badToken: "\u8FD9\u4E0D\u50CF\u673A\u5668\u4EBA\u7684 token\u3002\u5230 BotFather \u91CD\u65B0\u590D\u5236\u4E00\u4EFD\uFF1A\u6570\u5B57\u3001\u5192\u53F7\uFF0C\u540E\u9762\u662F\u5BC6\u94A5\u3002",
139664
- unauthorized: "Telegram \u4E0D\u8BA4\u8FD9\u4E2A token\uFF0C\u53EF\u80FD\u5DF2\u7ECF\u4F5C\u5E9F\u3002\u5230 BotFather \u53D6\u73B0\u5728\u7684\u90A3\u4E00\u4EFD\u3002",
140153
+ unauthorized: "Telegram \u4E0D\u8BA4\u8FD9\u4E2A token\uFF0C\u53EF\u80FD\u5DF2\u7ECF\u4F5C\u5E9F\u3002\u5230 BotFather \u53D6\u73B0\u5728\u7684\u90A3\u4E00\u4EFD\u3002\u6539\u597D\u540E\u5728\u8FD9\u91CC\u4FDD\u5B58\uFF0C\u673A\u5668\u4EBA\u4F1A\u81EA\u52A8\u91CD\u65B0\u8FDE\u63A5\uFF1B\u6CA1\u6709\u8981\u6539\u7684\u4E5F\u53EF\u4EE5\u70B9\u4E0B\u65B9\u7684\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u3002",
139665
140154
  notBot: "\u8FD9\u4E2A token \u4E0D\u662F\u673A\u5668\u4EBA\u7684\u3002\u8BF7\u7528 BotFather \u7ED9\u7684\u90A3\u4E00\u4EFD\u3002",
139666
140155
  webhookActive: "\u8FD9\u4E2A\u673A\u5668\u4EBA\u88AB\u522B\u7684\u670D\u52A1\u8BBE\u4E86 webhook\uFF0C\u5728\u8FD9\u91CC\u6536\u4E0D\u5230\u6D88\u606F\u3002\u5148\u5230\u90A3\u8FB9\u64A4\u6389 webhook\uFF0C\u518D\u68C0\u67E5\u4E00\u6B21\u3002",
140156
+ larkCredentials: "\u98DE\u4E66\u4E0D\u8BA4\u8FD9\u4E2A App ID \u6216 App Secret\u3002\u5230\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u8FD9\u4E2A\u4F01\u4E1A\u81EA\u5EFA\u5E94\u7528\u7684\u300C\u51ED\u8BC1\u4E0E\u57FA\u7840\u4FE1\u606F\u300D\u91CD\u65B0\u590D\u5236\u4E24\u9879\u3002\u6539\u597D\u540E\u5728\u8FD9\u91CC\u4FDD\u5B58\uFF0C\u673A\u5668\u4EBA\u4F1A\u81EA\u52A8\u91CD\u65B0\u8FDE\u63A5\uFF1B\u6CA1\u6709\u8981\u6539\u7684\u4E5F\u53EF\u4EE5\u70B9\u4E0B\u65B9\u7684\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u3002",
140157
+ larkScope: "\u8FD9\u4E2A\u5E94\u7528\u7F3A\u5C11\u6240\u9700\u6743\u9650\u3002\u5230\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u7684\u300C\u6743\u9650\u7BA1\u7406\u300D\uFF0C\u6DFB\u52A0\u8BE6\u60C5\u4E2D\u5217\u51FA\u7684\u6743\u9650\uFF0C\u518D\u53D1\u5E03\u4E00\u4E2A\u7248\u672C\u3002",
140158
+ larkBotOff: "\u8FD9\u4E2A\u5E94\u7528\u6CA1\u6709\u542F\u7528\u673A\u5668\u4EBA\u80FD\u529B\u3002\u5230\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u7684\u300C\u5E94\u7528\u80FD\u529B \u2192 \u673A\u5668\u4EBA\u300D\u542F\u7528\u673A\u5668\u4EBA\uFF0C\u518D\u53D1\u5E03\u4E00\u4E2A\u7248\u672C\u3002",
140159
+ larkAppUnavailable: "\u8FD9\u4E2A\u5E94\u7528\u5728\u5F53\u524D\u4F01\u4E1A\u4E0D\u53EF\u7528\u3002\u5230\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u786E\u8BA4\u5E94\u7528\u5DF2\u542F\u7528\u5E76\u53D1\u5E03\uFF0C\u518D\u8BF7\u7BA1\u7406\u5458\u786E\u8BA4\u5F53\u524D\u4F01\u4E1A\u5DF2\u5B89\u88C5\u5E76\u542F\u7528\u8FD9\u4E2A\u5E94\u7528\u3002",
140160
+ wecomBotCredentials: "\u4F01\u4E1A\u5FAE\u4FE1\u4E0D\u8BA4\u8FD9\u4E2A Bot ID \u6216 Secret\u3002\u5230\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u8FD9\u4E2A\u667A\u80FD\u673A\u5668\u4EBA\u7684\u914D\u7F6E\u9875\uFF0C\u9009\u62E9\u300CAPI \u6A21\u5F0F \u2192 \u957F\u8FDE\u63A5\u300D\uFF0C\u91CD\u65B0\u590D\u5236 Bot ID \u548C Secret\uFF1B\u8FD9\u91CC\u7528\u7684\u4E0D\u662F\u56DE\u8C03\u6A21\u5F0F\u7684 Token \u6216 EncodingAESKey\u3002\u6539\u597D\u540E\u5728\u8FD9\u91CC\u4FDD\u5B58\uFF0C\u667A\u80FD\u673A\u5668\u4EBA\u4F1A\u81EA\u52A8\u91CD\u65B0\u8FDE\u63A5\uFF1B\u6CA1\u6709\u8981\u6539\u7684\u4E5F\u53EF\u4EE5\u70B9\u4E0B\u65B9\u7684\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u3002",
140161
+ wecomLongConnOff: "\u8FD9\u4E2A\u667A\u80FD\u673A\u5668\u4EBA\u6CA1\u6709\u5F00\u542F\u957F\u8FDE\u63A5\u6A21\u5F0F\u3002\u5230\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u8FD9\u4E2A\u667A\u80FD\u673A\u5668\u4EBA\u7684\u914D\u7F6E\u9875\u9009\u62E9\u300CAPI \u6A21\u5F0F \u2192 \u957F\u8FDE\u63A5\u300D\u5E76\u4FDD\u5B58\uFF0C\u8FD9\u91CC\u4F1A\u5728\u4E00\u5206\u949F\u5185\u81EA\u52A8\u91CD\u65B0\u8FDE\u4E0A\uFF1B\u4E5F\u53EF\u4EE5\u76F4\u63A5\u70B9\u4E0B\u65B9\u7684\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u3002",
140162
+ wecomTakenOver: "\u53E6\u4E00\u53F0\u673A\u5668\u6216\u7A0B\u5E8F\u5728\u7528\u540C\u4E00\u4E2A Bot ID\u3002\u8BA9\u5B83\u505C\u7528\u8FD9\u4E2A\u667A\u80FD\u673A\u5668\u4EBA\u540E\uFF0C\u70B9\u4E0B\u65B9\u7684\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u3002",
139667
140163
  wecomTrustedIp: "\u628A\u8FD9\u53F0\u673A\u5668\u7684\u51FA\u7AD9\u516C\u7F51 IP \u52A0\u8FDB\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u8FD9\u4E2A\u5E94\u7528\u7684\u4F01\u4E1A\u53EF\u4FE1 IP\u3002",
139668
140164
  wecomInvalidUser: "\u786E\u8BA4\u6536\u4EF6\u4EBA\u3001\u90E8\u95E8\u6216\u6807\u7B7E\u5B58\u5728\uFF0C\u5E76\u5728\u8FD9\u4E2A\u81EA\u5EFA\u5E94\u7528\u7684\u53EF\u89C1\u8303\u56F4\u5185\u3002",
139669
- wecomInvalidAgent: "\u6838\u5BF9\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u8FD9\u4E2A\u81EA\u5EFA\u5E94\u7528\u7684 AgentId\u3002"
140165
+ wecomInvalidAgent: "\u6838\u5BF9\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u8FD9\u4E2A\u81EA\u5EFA\u5E94\u7528\u7684 AgentId\u3002",
140166
+ wecomCallbackNotSeen: "\u51ED\u636E\u6709\u6548\uFF0C\u4F46\u81EA\u672C\u6B21\u542F\u52A8\u4EE5\u6765\u8FD8\u6CA1\u6536\u5230\u8FC7\u901A\u8FC7\u6821\u9A8C\u7684\u56DE\u8C03\u3002\u5230\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u8FD9\u4E2A\u5E94\u7528\u7684\u300C\u63A5\u6536\u6D88\u606F\u300D\uFF0C\u586B\u5165\u673A\u5668\u4EBA\u8BBE\u7F6E\u5361\u4E0A\u663E\u793A\u7684\u56DE\u8C03\u5730\u5740\u5E76\u4FDD\u5B58\uFF1B\u4F01\u5FAE\u4F1A\u53D1\u6765\u4E00\u6B21\u6821\u9A8C\u8BF7\u6C42\u3002\u8FD9\u53F0\u673A\u5668\u5FC5\u987B\u80FD\u88AB\u4F01\u5FAE\u4ECE\u516C\u7F51\u8BBF\u95EE\u5230\u3002",
140167
+ wecomCallbackSignature: "\u6536\u5230\u8FC7\u56DE\u8C03\uFF0C\u4F46\u7B7E\u540D\u6821\u9A8C\u6216\u89E3\u5BC6\u5931\u8D25\uFF1B\u81EA\u672C\u6B21\u542F\u52A8\u4EE5\u6765\u8FD8\u6CA1\u6536\u5230\u8FC7\u901A\u8FC7\u6821\u9A8C\u7684\u56DE\u8C03\u3002\u8BF7\u628A\u56DE\u8C03 Token \u548C EncodingAESKey \u4E0E\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u8FD9\u4E2A\u5E94\u7528\u7684\u300C\u63A5\u6536\u6D88\u606F\u300D\u8BBE\u7F6E\u4FDD\u6301\u4E00\u81F4\uFF0C\u518D\u5230\u540E\u53F0\u4FDD\u5B58\u4E00\u6B21\u4EE5\u91CD\u65B0\u6821\u9A8C\u3002"
139670
140168
  }
139671
140169
  },
140170
+ reconnect: {
140171
+ label: "\u91CD\u65B0\u8FDE\u63A5",
140172
+ description: "\u6309\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u65AD\u5F00\u5E76\u91CD\u65B0\u8FDE\u63A5\u8FD9\u4E2A\u673A\u5668\u4EBA\u3002",
140173
+ notAvailable: "\u8FD9\u4E2A\u5E73\u53F0\u8FD8\u4E0D\u80FD\u8FDE\u63A5\uFF0C\u91CD\u65B0\u8FDE\u63A5\u4E5F\u8FDE\u4E0D\u4E0A\u3002",
140174
+ running: "\u6B63\u5728\u91CD\u65B0\u8FDE\u63A5 {{rowId}} \u2026",
140175
+ done: "\u5DF2\u5F00\u59CB\u91CD\u65B0\u8FDE\u63A5\uFF0C\u7ED3\u679C\u8BF7\u770B\u673A\u5668\u4EBA\u7684\u8FDE\u63A5\u72B6\u6001\u3002",
140176
+ notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u91CD\u65B0\u8FDE\u63A5\uFF0C\u52A8\u4F5C\u53EA\u4F7F\u7528\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u3002",
140177
+ notConfigured: "\u8BF7\u5148\u8865\u5168\u5E76\u4FDD\u5B58\u8FD9\u4E2A\u673A\u5668\u4EBA\u7684\u8BBE\u7F6E\uFF0C\u518D\u91CD\u65B0\u8FDE\u63A5\u3002",
140178
+ duplicate: "\u53E6\u4E00\u4E2A\u673A\u5668\u4EBA\u884C\u6B63\u5728\u4F7F\u7528\u8FD9\u4EFD\u51ED\u636E\uFF0C\u8BF7\u5148\u5904\u7406\u91CD\u590D\u914D\u7F6E\u518D\u91CD\u65B0\u8FDE\u63A5\u3002",
140179
+ inactive: "\u804A\u5929\u6865\u5DF2\u505C\u7528\uFF0C\u8BF7\u5148\u542F\u7528\u518D\u91CD\u65B0\u8FDE\u63A5\u3002"
140180
+ },
139672
140181
  copyDiagnostics: {
139673
140182
  label: "\u590D\u5236\u8BCA\u65AD",
139674
140183
  description: "\u8DD1\u4E00\u6B21\u8FDE\u63A5\u63A2\u6D4B\uFF0C\u751F\u6210\u53EF\u590D\u5236\u7684\u8BCA\u65AD\u6587\u672C\uFF08\u4E0D\u542B\u5BC6\u94A5\uFF09\u3002",
139675
140184
  running: "\u6B63\u5728\u751F\u6210\u8BCA\u65AD {{rowId}} \u2026",
139676
- done: "\u8BCA\u65AD\u5DF2\u751F\u6210\uFF0C\u89C1\u4E0B\u65B9\u8BE6\u60C5",
139677
- failed: "\u8BCA\u65AD\u5931\u8D25",
140185
+ done: "\u8BCA\u65AD\u62A5\u544A\u5DF2\u751F\u6210\uFF0C\u89C1\u4E0B\u65B9\u8BE6\u60C5",
139678
140186
  notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u590D\u5236\u8BCA\u65AD\u3002"
139679
140187
  },
139680
140188
  diagnostic: {
@@ -139682,16 +140190,23 @@ var zh_CN_default = {
139682
140190
  },
139683
140191
  larkRepair: {
139684
140192
  label: "\u8865\u5168\u6743\u9650",
139685
- description: "\u68C0\u67E5\u5DF2\u4FDD\u5B58\u7684\u51ED\u636E\u80FD\u5426\u6362\u53D6 token\uFF1B\u6743\u9650\u3001\u4E8B\u4EF6\u8BA2\u9605\u548C\u53D1\u5E03\u4E0D\u4F1A\u81EA\u52A8\u68C0\u67E5\u3002",
140193
+ description: "\u5217\u51FA\u7F3A\u5C11\u7684\u5FC5\u9700\u6743\u9650\u548C\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u7533\u8BF7\u94FE\u63A5\uFF0C\u518D\u7531\u7BA1\u7406\u5458\u624B\u52A8\u53D1\u5E03\u5E94\u7528\u7248\u672C\u3002",
139686
140194
  step: {
139687
- scopes: "\u6743\u9650\uFF1A\u672A\u68C0\u67E5",
139688
- events: "\u4E8B\u4EF6\u8BA2\u9605\uFF1A\u672A\u68C0\u67E5",
139689
- publish: "\u53D1\u5E03\uFF1A\u672A\u68C0\u67E5",
139690
- verify: "\u9A8C\u8BC1\u5E94\u7528\u4ECD\u53EF\u6362\u53D6 tenant token"
139691
- },
139692
- done: "\u4EC5\u6838\u5B9E\u4E86\u51ED\u636E\u4ECD\u80FD\u6362\u53D6 token\uFF1B\u6743\u9650\u3001\u4E8B\u4EF6\u8BA2\u9605\u548C\u53D1\u5E03\u6CA1\u6709\u81EA\u52A8\u68C0\u67E5\uFF0C\u8BF7\u5230\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u6838\u5BF9\u3002",
139693
- hint: "\u53C2\u8003\u53F7\uFF1A{{ref}}\n{{detail}}\n\u8BF7\u5230\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u6838\u5BF9\u6743\u9650\u3001\u4E8B\u4EF6\u8BA2\u9605\u548C\u53D1\u5E03\uFF1B\u8FD9\u4E09\u9879\u5747\u672A\u81EA\u52A8\u68C0\u67E5\u3002",
139694
- failed: "\u5DF2\u4FDD\u5B58\u7684\u51ED\u636E\u672A\u80FD\u6362\u53D6 token\uFF1B\u6743\u9650\u3001\u4E8B\u4EF6\u8BA2\u9605\u548C\u53D1\u5E03\u6CA1\u6709\u81EA\u52A8\u68C0\u67E5\uFF0C\u8BF7\u5230\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u6838\u5BF9\u3002",
140195
+ token: "\u7528\u5DF2\u4FDD\u5B58\u7684\u51ED\u636E\u6362\u53D6 tenant token",
140196
+ scopes: "\u6838\u5BF9\u5E94\u7528\u5DF2\u5F00\u901A\u7684\u79DF\u6237\u6743\u9650"
140197
+ },
140198
+ missing: "\u6709\u5FC5\u9700\u6743\u9650\u5C1A\u672A\u5F00\u901A\u3002",
140199
+ allGranted: "\u5FC5\u9700\u6743\u9650\u90FD\u5DF2\u5F00\u901A\u3002",
140200
+ unverified: "\u65E0\u6CD5\u81EA\u52A8\u6838\u5BF9\u6743\u9650\u3002",
140201
+ missingScopes: "\u7F3A\u5C11\u7684\u6743\u9650\uFF1A\n{{scopes}}",
140202
+ requiredScopes: "\u5FC5\u9700\u6743\u9650\u6E05\u5355\uFF0C\u8BF7\u9010\u9879\u624B\u52A8\u6838\u5BF9\uFF1A\n{{scopes}}\n\n\u5E94\u7528\u7F3A\u5C11 application:application:self_manage \u65F6\u65E0\u6CD5\u67E5\u8BE2\u81EA\u8EAB\u5DF2\u5F00\u901A\u7684\u6743\u9650\uFF0C\u6240\u4EE5\u8FD9\u4EFD\u6E05\u5355\u6CA1\u80FD\u81EA\u52A8\u6838\u5BF9\u3002",
140203
+ openApplyLink: "\u6253\u5F00\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u7533\u8BF7\u8FD9\u4E9B\u6743\u9650",
140204
+ openCheckLink: "\u6253\u5F00\u98DE\u4E66\u5F00\u653E\u5E73\u53F0\u6838\u5BF9\u5E76\u7533\u8BF7\u8FD9\u4E9B\u6743\u9650",
140205
+ applyLink: "\u5F00\u653E\u5E73\u53F0\u5730\u5740\uFF1A\n{{url}}",
140206
+ publishHint: "\u8BF7\u5E94\u7528\u7BA1\u7406\u5458\u6253\u5F00\u4E0A\u8FF0\u94FE\u63A5\u52FE\u9009\u6743\u9650\uFF0C\u518D\u5230\u300C\u5E94\u7528\u53D1\u5E03 \u2192 \u7248\u672C\u7BA1\u7406\u4E0E\u53D1\u5E03 \u2192 \u521B\u5EFA\u7248\u672C\u300D\u53D1\u5E03\u65B0\u7248\u672C\uFF0C\u56DE\u6765\u70B9\u300C\u68C0\u67E5\u8FDE\u63A5\u300D\u786E\u8BA4\u3002",
140207
+ botHint: "\u542F\u7528\u673A\u5668\u4EBA\u8BF7\u5230\u300C\u5E94\u7528 \u2192 \u6DFB\u52A0\u5E94\u7528\u80FD\u529B \u2192 \u673A\u5668\u4EBA\u300D\uFF1B\u8FD9\u91CC\u4E0D\u68C0\u67E5\u673A\u5668\u4EBA\u80FD\u529B\u3002",
140208
+ eventHint: "\u6536\u6D88\u606F\u9700\u8981\u5728\u300C\u4E8B\u4EF6\u4E0E\u56DE\u8C03\u300D\u91CC\u628A\u4E8B\u4EF6\u8BA2\u9605\u548C\u56DE\u8C03\u8BA2\u9605\u90FD\u8BBE\u4E3A\u300C\u4F7F\u7528\u957F\u8FDE\u63A5\u300D\uFF0C\u518D\u6DFB\u52A0\u4E0B\u9762\u8FD9\u4E9B\uFF08\u8FD9\u91CC\u4E0D\u68C0\u67E5\uFF09\uFF1A\n\u4E8B\u4EF6\uFF1A{{events}}\n\u56DE\u8C03\uFF1A{{callbacks}}",
140209
+ failed: "\u672A\u80FD\u6838\u5BF9\u5E94\u7528\u7684\u5FC5\u9700\u6743\u9650\uFF0C\u8BF7\u67E5\u770B\u9519\u8BEF\u8BE6\u60C5\u540E\u91CD\u8BD5\u3002",
139695
140210
  notLark: "\u8FD9\u4E00\u884C\u4E0D\u662F\u5DF2\u4FDD\u5B58\u7684\u98DE\u4E66\u673A\u5668\u4EBA\u3002"
139696
140211
  }
139697
140212
  },
@@ -139738,11 +140253,13 @@ var zh_CN_default = {
139738
140253
  connection: {
139739
140254
  failed: "\u4E0A\u6B21\u8FDE\u63A5\u6CA1\u6709\u6210\u529F\u3002",
139740
140255
  details: "\u8BE6\u60C5\uFF1A{{error}}",
140256
+ reconnect: "\u53EF\u4EE5\u70B9\u4E0B\u65B9\u7684\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u518D\u8BD5\u3002",
139741
140257
  reason: {
139742
140258
  "not-available": "\u8FD9\u4E2A\u5E73\u53F0\u8FD8\u4E0D\u80FD\u8FDE\u63A5\u3002",
139743
140259
  "invalid-app-id": "App ID \u683C\u5F0F\u4E0D\u6B63\u786E\u3002",
139744
140260
  disconnected: "\u548C\u5E73\u53F0\u7684\u8FDE\u63A5\u65AD\u4E86\u3002",
139745
140261
  "subscription-failed": "\u5E73\u53F0\u6CA1\u6709\u786E\u8BA4\u8BA2\u9605\u3002",
140262
+ "taken-over": "\u8FD9\u91CC\u7684\u8FDE\u63A5\u5DF2\u88AB\u522B\u5904\u53D6\u4EE3\uFF0C\u5DF2\u505C\u6B62\u81EA\u52A8\u91CD\u8FDE\u3002",
139746
140263
  "token-unavailable": "\u6CA1\u80FD\u6362\u5230\u8BBF\u95EE\u4EE4\u724C\u3002",
139747
140264
  "credentials-rejected": "\u5E73\u53F0\u62D2\u7EDD\u4E86\u51ED\u636E\u3002"
139748
140265
  },
@@ -139766,7 +140283,7 @@ var zh_CN_default = {
139766
140283
  done: "\u5B8C\u6210\u3002",
139767
140284
  close: "\u5173\u95ED",
139768
140285
  failedToRun: "\u6CA1\u80FD\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u8FD0\u884C\u3002",
139769
- saveFirst: "\u5148\u4FDD\u5B58\u6216\u64A4\u9500\u6539\u52A8\u3002\u8FD9\u51E0\u9879\u68C0\u67E5\u7684\u662F\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u3002",
140286
+ saveFirst: "\u5148\u4FDD\u5B58\u6216\u64A4\u9500\u6539\u52A8\u3002\u8FD9\u4E9B\u52A8\u4F5C\u4F7F\u7528\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u3002",
139770
140287
  qrTitle: "\u7528\u98DE\u4E66 App \u626B\u7684\u4E8C\u7EF4\u7801"
139771
140288
  },
139772
140289
  step: {
@@ -139792,6 +140309,24 @@ var zh_CN_default = {
139792
140309
  callbackFollowsKeys: "\u6539\u4E86 Token \u6216 EncodingAESKey\uFF0C\u8FD9\u4E2A\u5730\u5740\u4F1A\u53D8\uFF0C\u8981\u91CD\u65B0\u8D34\u8FDB\u4F01\u4E1A\u5FAE\u4FE1\u3002"
139793
140310
  }
139794
140311
  },
140312
+ mergeForward: {
140313
+ title: "[\u5408\u5E76\u8F6C\u53D1\u7684\u804A\u5929\u8BB0\u5F55\uFF0C\u5171 {{count}} \u6761]",
140314
+ nested: "[\u5D4C\u5957\u8F6C\u53D1\uFF0C\u5171 {{count}} \u6761]",
140315
+ someone: "\u67D0\u4EBA",
140316
+ bot: "\u673A\u5668\u4EBA",
140317
+ unknownTime: "[\u65F6\u95F4\u672A\u77E5]",
140318
+ resource: {
140319
+ image: "\u56FE\u7247",
140320
+ file: "\u6587\u4EF6",
140321
+ audio: "\u8BED\u97F3",
140322
+ video: "\u89C6\u9891",
140323
+ folder: "\u6587\u4EF6\u5939"
140324
+ },
140325
+ unavailable: "[{{type}}\uFF1A\u6CA1\u80FD\u53D6\u5230]",
140326
+ unavailableNamed: "[{{type}} {{name}}\uFF1A\u6CA1\u80FD\u53D6\u5230]",
140327
+ omitted: "\u2026\u2026\u8FD8\u6709 {{count}} \u6761\u6CA1\u5217\u51FA",
140328
+ truncated: "\u2026\u2026[\u6B63\u6587\u5DF2\u622A\u65AD]"
140329
+ },
139795
140330
  team: {
139796
140331
  noRoster: "\u8FD9\u4E2A\u9879\u76EE\u8FD8\u6CA1\u6709\u56E2\u961F\u540D\u5355\uFF0C\u5148\u5728 Teams \u6807\u7B7E\u91CC\u4FDD\u5B58\u540D\u5355\u5E76\u9009\u4E00\u540D\u8D1F\u8D23\u4EBA\u3002",
139797
140332
  unavailable: "\u6682\u65F6\u8BFB\u4E0D\u5230\u8FD9\u4E2A\u9879\u76EE\u7684\u56E2\u961F\u540D\u5355\uFF0C\u8BF7\u786E\u8BA4 Teams \u63D2\u4EF6\u5DF2\u542F\u7528\u540E\u91CD\u8BD5\u3002",
@@ -140069,7 +140604,10 @@ var makeNotifications = (core, deps) => {
140069
140604
  const said = threadsWithSomethingToReport.delete(threadId);
140070
140605
  await ready;
140071
140606
  const targets = targetsForProject(threadId, await projectOfThread(threadId));
140072
- const { debt, handled, execution } = await conclusions.forTurn(threadId);
140607
+ const owes = Object.values(conclusionDebtsOf(core.state)).some(
140608
+ (debt2) => debt2.threadId === threadId
140609
+ );
140610
+ const { debt, handled, execution } = said || owes || targets.length > 0 ? await conclusions.forTurn(threadId) : { debt: void 0, handled: false, execution: null };
140073
140611
  if (debt !== void 0) {
140074
140612
  const binding = bindingForChatKey(debt.chatKey);
140075
140613
  if (binding !== void 0 && !streamsTo(binding, threadId) && execution?.session?.activeTurnId == null && execution?.latestTurn?.state !== "running" && execution?.latestTurn?.state !== "error" && payload.session?.status !== "error") {
@@ -140254,7 +140792,7 @@ var PANEL_PAGE_SIZE = 8;
140254
140792
  var PANEL_CARD_LIMIT = 200;
140255
140793
  var LABEL_MAX_CHARS = 60;
140256
140794
  var PICK_KINDS = /* @__PURE__ */ new Set(["sessions", "models", "projects"]);
140257
- var clip2 = (label) => {
140795
+ var clip3 = (label) => {
140258
140796
  const chars = Array.from(label);
140259
140797
  return chars.length <= LABEL_MAX_CHARS ? label : `${chars.slice(0, LABEL_MAX_CHARS - 1).join("")}\u2026`;
140260
140798
  };
@@ -140279,7 +140817,7 @@ var makePanelController = (deps) => {
140279
140817
  }
140280
140818
  };
140281
140819
  const button = (label, op, arg, danger) => ({
140282
- label: clip2(label),
140820
+ label: clip3(label),
140283
140821
  action: { op, arg },
140284
140822
  ...danger === true ? { danger } : {}
140285
140823
  });
@@ -140698,6 +141236,20 @@ var makeFailureNotices = (core) => {
140698
141236
  const heldForCard = /* @__PURE__ */ new Map();
140699
141237
  const reasonOf2 = (detail) => detail?.trim() ?? "";
140700
141238
  return {
141239
+ /** 派发的参考号沿用同一次请求;原始错误只进日志。 */
141240
+ forDispatchFailure: (threadId, requestId, error) => {
141241
+ const reference2 = stableDiagnosticRef(
141242
+ "msg",
141243
+ (identityDigest(requestId, "turn") ?? requestId).replaceAll("-", "")
141244
+ );
141245
+ core.host.log.warn("chat could not start a turn", {
141246
+ threadId,
141247
+ requestId,
141248
+ reference: reference2,
141249
+ error: describeError(error)
141250
+ });
141251
+ return core.t("reply.turnFailedToStart", { ref: reference2 });
141252
+ },
140701
141253
  /** 一次失败通知的那句话(带新参考号,同号进日志):实时失败事件与重启补发各取一次。 */
140702
141254
  forFailure: (threadId, detail) => {
140703
141255
  const reference2 = diagnosticRef("msg");
@@ -141473,7 +142025,7 @@ var makeSessions = (core) => {
141473
142025
 
141474
142026
  // src/teamGoals.ts
141475
142027
  import * as NodeCrypto4 from "node:crypto";
141476
- var isRecord2 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
142028
+ var isRecord3 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
141477
142029
  var teamGoalRequestId = (binding, messageId) => {
141478
142030
  const identity = JSON.stringify([
141479
142031
  "im",
@@ -141515,7 +142067,7 @@ var makeTeamGoals = (core) => {
141515
142067
  requestId
141516
142068
  }
141517
142069
  });
141518
- if (isRecord2(result) && result._tag === "TeamGoalRejection") {
142070
+ if (isRecord3(result) && result._tag === "TeamGoalRejection") {
141519
142071
  await deliver(
141520
142072
  binding,
141521
142073
  t(
@@ -141524,7 +142076,7 @@ var makeTeamGoals = (core) => {
141524
142076
  );
141525
142077
  return null;
141526
142078
  }
141527
- if (!isRecord2(result) || typeof result.leadThreadId !== "string" || result.leadThreadId === "" || result.leadName !== null && typeof result.leadName !== "string") {
142079
+ if (!isRecord3(result) || typeof result.leadThreadId !== "string" || result.leadThreadId === "" || result.leadName !== null && typeof result.leadName !== "string") {
141528
142080
  throw new Error("team startGoal returned an invalid result");
141529
142081
  }
141530
142082
  topicRoot?.confirmConsumed();
@@ -141547,6 +142099,38 @@ var makeTeamGoals = (core) => {
141547
142099
  return { hasRosterFor, startTeamGoal };
141548
142100
  };
141549
142101
 
142102
+ // src/telegramAdapter.ts
142103
+ import * as NodeCrypto5 from "node:crypto";
142104
+
142105
+ // src/telegramApiResponse.ts
142106
+ var TelegramApiError = class extends Error {
142107
+ code;
142108
+ /** 限流(429)时平台给的等待秒数;别的错误没有。 */
142109
+ retryAfterSeconds;
142110
+ constructor(method, code2, description, retryAfterSeconds) {
142111
+ super(`telegram ${method} failed (${String(code2 ?? "?")}): ${description}`);
142112
+ this.name = "TelegramApiError";
142113
+ this.code = code2;
142114
+ this.retryAfterSeconds = retryAfterSeconds;
142115
+ }
142116
+ };
142117
+ var isTelegramCredentialsRejected = (error) => error instanceof TelegramApiError && (error.code === 401 || error.code === 404);
142118
+ var readTelegramResponse = async (method, response) => {
142119
+ const envelope = await response.json().catch((error) => {
142120
+ if (response.ok) throw error;
142121
+ return { ok: false, description: response.statusText || void 0 };
142122
+ });
142123
+ if (!response.ok || envelope?.ok !== true) {
142124
+ throw new TelegramApiError(
142125
+ method,
142126
+ response.status === 401 ? 401 : envelope?.error_code ?? (response.ok ? void 0 : response.status),
142127
+ envelope?.description ?? "no reason",
142128
+ envelope?.parameters?.retry_after
142129
+ );
142130
+ }
142131
+ return envelope.result;
142132
+ };
142133
+
141550
142134
  // src/telegramApi.ts
141551
142135
  var API_BASE = "https://api.telegram.org";
141552
142136
  var POLL_TIMEOUT_SECONDS = 45;
@@ -141571,18 +142155,11 @@ var target = (chatId) => {
141571
142155
  const { chat, thread } = splitChatId(chatId);
141572
142156
  return { chat_id: chat, ...thread === null ? {} : { message_thread_id: thread } };
141573
142157
  };
141574
- var chatOnly = (chatId) => ({ chat_id: splitChatId(chatId).chat });
141575
- var TelegramApiError = class extends Error {
141576
- code;
141577
- /** 限流(429)时平台给的等待秒数;别的错误没有。 */
141578
- retryAfterSeconds;
141579
- constructor(method, code2, description, retryAfterSeconds) {
141580
- super(`telegram ${method} failed (${String(code2 ?? "?")}): ${description}`);
141581
- this.name = "TelegramApiError";
141582
- this.code = code2;
141583
- this.retryAfterSeconds = retryAfterSeconds;
141584
- }
142158
+ var replyParameters = (messageId) => {
142159
+ const id = Number(messageId);
142160
+ return messageId === void 0 || !Number.isSafeInteger(id) ? {} : { reply_parameters: { message_id: id, allow_sending_without_reply: true } };
141585
142161
  };
142162
+ var chatOnly = (chatId) => ({ chat_id: splitChatId(chatId).chat });
141586
142163
  var telegramSendVerdict = (error) => error instanceof TelegramApiError && (error.code === 429 || error.code === 503 && error.retryAfterSeconds !== void 0) ? {
141587
142164
  retry: true,
141588
142165
  afterMs: error.retryAfterSeconds === void 0 ? void 0 : error.retryAfterSeconds * 1e3
@@ -141617,7 +142194,6 @@ var resourceOf2 = (message) => {
141617
142194
  };
141618
142195
  }
141619
142196
  const media = [
141620
- [message.voice, "voice.ogg", "audio/ogg"],
141621
142197
  [message.audio, "audio", "application/octet-stream"],
141622
142198
  [message.video, "video.mp4", "video/mp4"],
141623
142199
  [message.video_note, "video-note.mp4", "video/mp4"],
@@ -141637,7 +142213,15 @@ var resourceOf2 = (message) => {
141637
142213
  return null;
141638
142214
  };
141639
142215
  var unsupportedTypeOf = (message) => {
141640
- for (const type of ["sticker", "location", "venue", "contact", "poll", "dice"]) {
142216
+ for (const type of [
142217
+ "voice",
142218
+ "sticker",
142219
+ "location",
142220
+ "venue",
142221
+ "contact",
142222
+ "poll",
142223
+ "dice"
142224
+ ]) {
141641
142225
  if (message[type] !== void 0) return type;
141642
142226
  }
141643
142227
  return null;
@@ -141691,24 +142275,17 @@ var makeTelegramApi = (input) => {
141691
142275
  },
141692
142276
  timeoutMs
141693
142277
  );
141694
- const envelope = await response.json();
141695
- if (envelope.ok !== true) {
141696
- throw new TelegramApiError(
141697
- method,
141698
- envelope.error_code,
141699
- envelope.description ?? "no reason",
141700
- envelope.parameters?.retry_after
141701
- );
141702
- }
141703
- return envelope.result;
142278
+ return readTelegramResponse(method, response);
141704
142279
  };
141705
- const upload = async (method, field, chatId, file) => {
142280
+ const upload = async (method, field, chatId, file, replyMessageId) => {
141706
142281
  const form = new FormData();
141707
142282
  const where = target(chatId);
141708
142283
  form.set("chat_id", where.chat_id);
141709
142284
  if (where.message_thread_id !== void 0) {
141710
142285
  form.set("message_thread_id", String(where.message_thread_id));
141711
142286
  }
142287
+ const reply = replyParameters(replyMessageId).reply_parameters;
142288
+ if (reply !== void 0) form.set("reply_parameters", JSON.stringify(reply));
141712
142289
  form.set(field, new Blob([new Uint8Array(file.bytes)], { type: file.mimeType }), file.name);
141713
142290
  const response = await send(
141714
142291
  method,
@@ -141716,24 +142293,17 @@ var makeTelegramApi = (input) => {
141716
142293
  { method: "POST", body: form },
141717
142294
  UPLOAD_TIMEOUT_MS
141718
142295
  );
141719
- const envelope = await response.json();
141720
- if (envelope.ok !== true) {
141721
- throw new TelegramApiError(
141722
- method,
141723
- envelope.error_code,
141724
- envelope.description ?? "no reason",
141725
- envelope.parameters?.retry_after
141726
- );
141727
- }
142296
+ await readTelegramResponse(method, response);
141728
142297
  };
141729
142298
  const keyboard = (rows) => ({
141730
142299
  inline_keyboard: rows.map(
141731
142300
  (row) => row.map((button) => ({ text: button.text, callback_data: button.data }))
141732
142301
  )
141733
142302
  });
141734
- const sendSingle = async (method, chatId, body, rows) => {
142303
+ const sendSingle = async (method, chatId, body, rows, replyMessageId) => {
141735
142304
  const sent = await call(method, {
141736
142305
  ...target(chatId),
142306
+ ...replyParameters(replyMessageId),
141737
142307
  ...body,
141738
142308
  ...rows === void 0 ? {} : { reply_markup: keyboard(rows) }
141739
142309
  });
@@ -141793,10 +142363,11 @@ var makeTelegramApi = (input) => {
141793
142363
  },
141794
142364
  POLL_CALL_TIMEOUT_MS
141795
142365
  ),
141796
- sendMessage: async (chatId, text4, retry) => {
141797
- for (const chunk of splitForTelegram(text4)) {
142366
+ sendMessage: async (chatId, text4, retry, replyMessageId) => {
142367
+ for (const [index, chunk] of splitForTelegram(text4).entries()) {
142368
+ const reply = index === 0 ? replyMessageId : void 0;
141798
142369
  await sendWithRetry(
141799
- () => call("sendMessage", { ...target(chatId), text: chunk }),
142370
+ () => sendSingle("sendMessage", chatId, { text: chunk }, void 0, reply),
141800
142371
  telegramSendVerdict,
141801
142372
  retry
141802
142373
  );
@@ -141806,12 +142377,12 @@ var makeTelegramApi = (input) => {
141806
142377
  * 发一条消息,回它的 id。调用方保证 `text` 不超过单条上限(边写边更新的回复要自己
141807
142378
  * 在上限处翻篇,这里不替它切)。带 `rows` 就挂一块内联键盘。
141808
142379
  */
141809
- sendOne: (chatId, text4, rows) => sendSingle("sendMessage", chatId, { text: text4 }, rows),
142380
+ sendOne: (chatId, text4, rows, replyMessageId) => sendSingle("sendMessage", chatId, { text: text4 }, rows, replyMessageId),
141810
142381
  /**
141811
142382
  * 发一条富文本,回它的 id。`markdown` 已是平台要的样子。带 `rows` 就挂一块内联键盘。
141812
142383
  * 调用方保证不超富文本单条上限(这里不替它切)。
141813
142384
  */
141814
- sendRichOne: (chatId, markdown, rows) => sendSingle("sendRichMessage", chatId, { rich_message: { markdown } }, rows),
142385
+ sendRichOne: (chatId, markdown, rows, replyMessageId) => sendSingle("sendRichMessage", chatId, { rich_message: { markdown } }, rows, replyMessageId),
141815
142386
  /**
141816
142387
  * 改一条已发出的消息。内容没变(`message is not modified`)当成功。`rows` 不给就是
141817
142388
  * 去掉按钮:改了文字又要保留按钮的调用方得自己再传一次。
@@ -141823,20 +142394,21 @@ var makeTelegramApi = (input) => {
141823
142394
  */
141824
142395
  editRich: (chatId, messageId, markdown, rows = []) => editSingle(chatId, messageId, { rich_message: { markdown } }, rows),
141825
142396
  /** 带按钮的审批消息,一行一个按钮;回消息 id,之后才改得了它(平台没给就是 null)。 */
141826
- sendButtons: (chatId, text4, buttons) => sendSingle(
142397
+ sendButtons: (chatId, text4, buttons, replyMessageId) => sendSingle(
141827
142398
  "sendMessage",
141828
142399
  chatId,
141829
142400
  { text: text4 },
141830
- buttons.map((button) => [{ text: button.label, data: button.token }])
142401
+ buttons.map((button) => [{ text: button.label, data: button.token }]),
142402
+ replyMessageId
141831
142403
  ),
141832
142404
  /** 答完点击,不然客户端上那个按钮一直转圈。带 `text` 时点击者会看到一句提示。 */
141833
142405
  answerCallback: (callbackQueryId, text4) => call("answerCallbackQuery", {
141834
142406
  callback_query_id: callbackQueryId,
141835
142407
  ...text4 === void 0 ? {} : { text: text4 }
141836
142408
  }),
141837
- sendPhoto: (chatId, photo) => upload("sendPhoto", "photo", chatId, photo),
142409
+ sendPhoto: (chatId, photo, replyMessageId) => upload("sendPhoto", "photo", chatId, photo, replyMessageId),
141838
142410
  /** 以文件形式发(不压缩、不限尺寸比例):什么类型都行,音频与视频也走这条。 */
141839
- sendDocument: (chatId, file) => upload("sendDocument", "document", chatId, file),
142411
+ sendDocument: (chatId, file, replyMessageId) => upload("sendDocument", "document", chatId, file, replyMessageId),
141840
142412
  /** 取一个文件的字节。要两步:先换路径,再按路径下载。 */
141841
142413
  fetchFile: async (fileId, maxBytes) => {
141842
142414
  const file = await call("getFile", {
@@ -142016,7 +142588,7 @@ var toTelegramRichMarkdown = (value2) => {
142016
142588
 
142017
142589
  // src/plainTurnText.ts
142018
142590
  var RUNNING_LABEL_MAX_CHARS = 200;
142019
- var clip3 = (text4, max) => {
142591
+ var clip4 = (text4, max) => {
142020
142592
  const chars = Array.from(text4);
142021
142593
  return chars.length <= max ? text4 : `${chars.slice(0, max - 1).join("")}\u2026`;
142022
142594
  };
@@ -142025,7 +142597,7 @@ var turnTextParts = (view, t) => {
142025
142597
  if (view.omitted > 0) names.unshift(t("card.step.omitted", { count: view.omitted }));
142026
142598
  const headLines = [view.title === "" ? t("card.untitled") : view.title];
142027
142599
  if (names.length > 0) headLines.push(names.join(" \u2192 "));
142028
- if (view.running !== null) headLines.push(`\u{1F527} ${clip3(view.running, RUNNING_LABEL_MAX_CHARS)}`);
142600
+ if (view.running !== null) headLines.push(`\u{1F527} ${clip4(view.running, RUNNING_LABEL_MAX_CHARS)}`);
142029
142601
  const body = view.body.trim();
142030
142602
  const footLines = [];
142031
142603
  if (view.status === "working") {
@@ -142048,7 +142620,7 @@ var makeTurnWriter = (deps) => {
142048
142620
  const put = async (text4) => {
142049
142621
  const bounded = text4.length <= deps.limit ? text4 : text4.slice(0, deps.limit);
142050
142622
  if (live === null) {
142051
- const id = await deps.messages.send(bounded);
142623
+ const id = await deps.messages.send(bounded, first === null);
142052
142624
  if (id === null) throw new Error("telegram sent a message without an id");
142053
142625
  live = id;
142054
142626
  first ??= id;
@@ -142099,6 +142671,7 @@ var TELEGRAM_CARD_THROTTLE_MS = 1200;
142099
142671
  var FLOOD_WAIT_MAX_SECONDS = 30;
142100
142672
  var REMEMBERED_CARD_LIMIT = 200;
142101
142673
  var tokenOf = (binding) => (binding.values["telegramToken"] ?? "").trim();
142674
+ var tokenFingerprint = (bot) => NodeCrypto5.createHash("sha256").update(tokenOf(bot)).digest("hex");
142102
142675
  var REACTION_WORKING = "\u{1F440}";
142103
142676
  var REACTION_DONE = "\u{1F44D}";
142104
142677
  var REACTION_FAILED = "\u{1F44E}";
@@ -142188,26 +142761,27 @@ var makeTelegramAdapter = (deps) => {
142188
142761
  limit: TELEGRAM_TEXT_MAX_CHARS,
142189
142762
  ...resume === void 0 ? {} : { resume },
142190
142763
  messages: {
142191
- send: (text4) => withRichFallback(
142192
- toTelegramRichMarkdown(text4),
142193
- async (markdown) => (await apiFor(slot.binding)).sendRichOne(chatId, markdown),
142194
- async () => (await apiFor(slot.binding)).sendOne(chatId, text4)
142195
- ),
142764
+ send: (text4, first) => {
142765
+ const reply = first ? slot.binding.reply?.messageId : void 0;
142766
+ return withRichFallback(
142767
+ toTelegramRichMarkdown(text4),
142768
+ async (markdown) => (await apiFor(slot.binding)).sendRichOne(chatId, markdown, void 0, reply),
142769
+ async () => (await apiFor(slot.binding)).sendOne(chatId, text4, void 0, reply)
142770
+ );
142771
+ },
142196
142772
  edit: (messageId, text4) => withRichFallback(
142197
142773
  toTelegramRichMarkdown(text4),
142198
- async (markdown) => {
142199
- await (await apiFor(slot.binding)).editRich(chatId, messageId, markdown);
142200
- },
142201
- async () => {
142202
- await (await apiFor(slot.binding)).editMessage(chatId, messageId, text4);
142203
- }
142774
+ async (markdown) => (await apiFor(slot.binding)).editRich(chatId, messageId, markdown),
142775
+ async () => (await apiFor(slot.binding)).editMessage(chatId, messageId, text4)
142204
142776
  )
142205
142777
  }
142206
142778
  });
142207
142779
  return {
142208
142780
  platform: "telegram",
142781
+ botIdentity: tokenFingerprint,
142209
142782
  icon: "telegram",
142210
142783
  label: "Telegram",
142784
+ quotesReplies: true,
142211
142785
  columns: COLUMNS2,
142212
142786
  validate: telegramConfigIssues,
142213
142787
  isConfigured: (binding) => telegramConfigIssues(binding).length === 0,
@@ -142216,7 +142790,12 @@ var makeTelegramAdapter = (deps) => {
142216
142790
  };
142217
142791
  const stopped = new Promise((resolve) => stop = resolve);
142218
142792
  let running = true;
142219
- let offset = 0;
142793
+ const tokenHash = tokenFingerprint(binding);
142794
+ let offset = deps.readCursor(binding.rowId, tokenHash);
142795
+ const advanceOffset = (update) => {
142796
+ offset = nextOffset([update], offset ?? 0);
142797
+ deps.saveCursor(binding.rowId, tokenHash, offset);
142798
+ };
142220
142799
  let acknowledged = 0;
142221
142800
  let backoff = BACKOFF_START_MS;
142222
142801
  let botUsername;
@@ -142229,7 +142808,7 @@ var makeTelegramAdapter = (deps) => {
142229
142808
  });
142230
142809
  const publishAck = () => {
142231
142810
  const api = liveApi;
142232
- if (api === void 0 || offset <= acknowledged) return;
142811
+ if (api === void 0 || offset === void 0 || offset <= acknowledged) return;
142233
142812
  const next = offset;
142234
142813
  acknowledged = next;
142235
142814
  void api.getUpdates(next, 0).catch(() => void 0);
@@ -142258,6 +142837,12 @@ var makeTelegramAdapter = (deps) => {
142258
142837
  });
142259
142838
  });
142260
142839
  }
142840
+ if (offset === void 0) {
142841
+ const backlog = await api.getUpdates(-1, 0);
142842
+ if (!running) return;
142843
+ offset = nextOffset(backlog, 0);
142844
+ deps.saveCursor(binding.rowId, tokenHash, offset);
142845
+ }
142261
142846
  acknowledged = offset;
142262
142847
  const updates = await api.getUpdates(offset);
142263
142848
  if (!running) return;
@@ -142270,13 +142855,13 @@ var makeTelegramAdapter = (deps) => {
142270
142855
  if (callback?.data !== void 0) {
142271
142856
  const operatorId = callback.from === void 0 ? null : String(callback.from.id);
142272
142857
  const notice = callback.data.startsWith(PANEL_DATA_PREFIX) ? panelClick(binding.rowId, callback, operatorId, handlers) : handlers.onButton(callback.data, operatorId, callback.id);
142273
- offset = nextOffset([update], offset);
142858
+ advanceOffset(update);
142274
142859
  await api.answerCallback(callback.id, notice ?? void 0).catch(() => void 0);
142275
142860
  continue;
142276
142861
  }
142277
142862
  const message = update.message;
142278
142863
  if (message === void 0) {
142279
- offset = nextOffset([update], offset);
142864
+ advanceOffset(update);
142280
142865
  continue;
142281
142866
  }
142282
142867
  const resource = resourceOf2(message);
@@ -142285,13 +142870,13 @@ var makeTelegramAdapter = (deps) => {
142285
142870
  botUsername
142286
142871
  );
142287
142872
  if (text4 === null) {
142288
- offset = nextOffset([update], offset);
142873
+ advanceOffset(update);
142289
142874
  continue;
142290
142875
  }
142291
142876
  const gated = !passesGroupGate2({
142292
142877
  message,
142293
142878
  text: text4,
142294
- hasResource: resource !== null,
142879
+ hasResource: resource !== null || message.voice !== void 0,
142295
142880
  botUsername: botUsername ?? null,
142296
142881
  groupMention: binding.groupMention
142297
142882
  });
@@ -142299,16 +142884,16 @@ var makeTelegramAdapter = (deps) => {
142299
142884
  const solo = binding.groupMention === "always" && await isSoloGroup(String(message.chat.id));
142300
142885
  if (!running) break;
142301
142886
  if (!solo) {
142302
- offset = nextOffset([update], offset);
142887
+ advanceOffset(update);
142303
142888
  continue;
142304
142889
  }
142305
142890
  }
142306
142891
  if (!running) break;
142307
- offset = nextOffset([update], offset);
142892
+ advanceOffset(update);
142308
142893
  const senderId = message.from === void 0 ? null : String(message.from.id);
142309
- const senderName = telegramDisplayName(message.from);
142894
+ const senderName2 = telegramDisplayName(message.from);
142310
142895
  const chatKind = message.chat.type === "private" ? "private" : "group";
142311
- if (text4 === "" && resource === null) {
142896
+ if (message.voice !== void 0 || text4 === "" && resource === null) {
142312
142897
  const unsupported = unsupportedTypeOf(message);
142313
142898
  if (unsupported === null) continue;
142314
142899
  handlers.onMessage({
@@ -142330,7 +142915,7 @@ var makeTelegramAdapter = (deps) => {
142330
142915
  ...parentChatId === void 0 ? {} : { parentChatId },
142331
142916
  senderId,
142332
142917
  text: text4,
142333
- ...senderName ? { senderName } : {},
142918
+ ...senderName2 ? { senderName: senderName2 } : {},
142334
142919
  ...message.chat.title ? { chatTitle: message.chat.title } : {},
142335
142920
  ...replyTo === null ? {} : { replyTo },
142336
142921
  chatKind,
@@ -142347,6 +142932,17 @@ var makeTelegramAdapter = (deps) => {
142347
142932
  }
142348
142933
  } catch (error) {
142349
142934
  if (!running) return;
142935
+ if (isTelegramCredentialsRejected(error)) {
142936
+ running = false;
142937
+ stop();
142938
+ handlers.onConnectionState?.({
142939
+ state: "failed",
142940
+ reason: "credentials-rejected",
142941
+ hint: "unauthorized",
142942
+ error
142943
+ });
142944
+ return;
142945
+ }
142350
142946
  if (checkingConnection) handlers.onConnectionState?.({ state: "failed", error });
142351
142947
  handlers.onError(error instanceof Error ? error.message : String(error));
142352
142948
  await Promise.race([(deps.sleep ?? defaultSleep)(backoff), stopped]);
@@ -142358,16 +142954,16 @@ var makeTelegramAdapter = (deps) => {
142358
142954
  deps.log.info("telegram polling", { bot: binding.displayName });
142359
142955
  return {
142360
142956
  close: () => {
142957
+ if (running) publishAck();
142361
142958
  running = false;
142362
142959
  stop();
142363
- publishAck();
142364
142960
  apis.delete(binding.rowId);
142365
142961
  }
142366
142962
  };
142367
142963
  },
142368
142964
  sendText: async (binding, text4, options) => {
142369
142965
  const api = await apiFor(binding);
142370
- await api.sendMessage(binding.chatId, text4, options?.retry);
142966
+ await api.sendMessage(binding.chatId, text4, options?.retry, binding.reply?.messageId);
142371
142967
  },
142372
142968
  checkConnection: async (bot) => {
142373
142969
  const token = tokenOf(bot);
@@ -142391,12 +142987,11 @@ var makeTelegramAdapter = (deps) => {
142391
142987
  }
142392
142988
  username = me.username !== void 0 && me.username !== "" ? `@${me.username}` : "(no username)";
142393
142989
  } catch (error) {
142394
- const rejected = error instanceof TelegramApiError && (error.code === 401 || error.code === 404);
142395
142990
  return {
142396
142991
  stage: "getMe",
142397
142992
  ok: false,
142398
142993
  detail: describeError(error),
142399
- ...rejected ? { hint: "unauthorized" } : {}
142994
+ ...isTelegramCredentialsRejected(error) ? { hint: "unauthorized" } : {}
142400
142995
  };
142401
142996
  }
142402
142997
  try {
@@ -142436,25 +143031,30 @@ var makeTelegramAdapter = (deps) => {
142436
143031
  sendImage: async (binding, image2) => {
142437
143032
  const api = await apiFor(binding);
142438
143033
  try {
142439
- await api.sendPhoto(binding.chatId, image2);
143034
+ await api.sendPhoto(binding.chatId, image2, binding.reply?.messageId);
142440
143035
  } catch (error) {
142441
143036
  if (!(error instanceof TelegramApiError && error.code === 400)) throw error;
142442
143037
  deps.log.info("telegram could not send the picture as a photo, sending it as a file", {
142443
143038
  name: image2.name,
142444
143039
  reason: error.message
142445
143040
  });
142446
- await api.sendDocument(binding.chatId, image2);
143041
+ await api.sendDocument(binding.chatId, image2, binding.reply?.messageId);
142447
143042
  }
142448
143043
  },
142449
143044
  sendFile: async (binding, file) => {
142450
143045
  const api = await apiFor(binding);
142451
- await api.sendDocument(binding.chatId, file);
143046
+ await api.sendDocument(binding.chatId, file, binding.reply?.messageId);
142452
143047
  },
142453
143048
  // 单张照片 10 MB、单个文件 50 MB。这两个数来自 Bot API 文档的记忆,本仓库里没有核对过。
142454
143049
  outboundLimits: { imageBytes: 10 * 1024 * 1024, fileBytes: 50 * 1024 * 1024 },
142455
143050
  askApproval: async (binding, request) => {
142456
143051
  const api = await apiFor(binding);
142457
- return api.sendButtons(binding.chatId, request.body, request.options);
143052
+ return api.sendButtons(
143053
+ binding.chatId,
143054
+ request.body,
143055
+ request.options,
143056
+ binding.reply?.messageId
143057
+ );
142458
143058
  },
142459
143059
  settleApproval: async (binding, cardId, settlement) => {
142460
143060
  const api = await apiFor(binding);
@@ -142506,7 +143106,7 @@ ${line}`
142506
143106
  const api = await apiFor(binding);
142507
143107
  const rendered = renderPanel(card, 1);
142508
143108
  const cardId = await withFloodWait(
142509
- () => api.sendOne(binding.chatId, rendered.text, rendered.rows)
143109
+ () => api.sendOne(binding.chatId, rendered.text, rendered.rows, binding.reply?.messageId)
142510
143110
  );
142511
143111
  if (cardId !== null) {
142512
143112
  panelKeyboards.set(`${binding.rowId}:${binding.chatId}:${cardId}`, {
@@ -142531,7 +143131,9 @@ ${line}`
142531
143131
  open: async (binding, card) => {
142532
143132
  const api = await apiFor(binding);
142533
143133
  const rendered = renderChoiceCard(card);
142534
- return withFloodWait(() => api.sendOne(binding.chatId, rendered.text, rendered.rows));
143134
+ return withFloodWait(
143135
+ () => api.sendOne(binding.chatId, rendered.text, rendered.rows, binding.reply?.messageId)
143136
+ );
142535
143137
  },
142536
143138
  update: async (binding, cardId, card) => {
142537
143139
  const api = await apiFor(binding);
@@ -142551,12 +143153,96 @@ ${line}`
142551
143153
 
142552
143154
  // src/turns.ts
142553
143155
  import { projectOrNull as projectOrNull2, threadOrNull as threadOrNull8 } from "@ycodium-ai/plugin-api";
143156
+
143157
+ // src/dispatchRetry.ts
143158
+ var RETRY_DELAYS_MS = [1e3, 3e3];
143159
+ var retryable2 = (error) => {
143160
+ if (typeof error !== "object" || error === null || !("_tag" in error)) return true;
143161
+ const reason = "reason" in error ? error.reason : void 0;
143162
+ switch (error._tag) {
143163
+ case "PluginCapabilityError":
143164
+ return reason === void 0 || reason === "host-unavailable";
143165
+ case "DataCommandError":
143166
+ return reason === "unavailable";
143167
+ case "EnvironmentAuthorizationError":
143168
+ return false;
143169
+ case "OrchestrationDispatchCommandError":
143170
+ return reason == null && (!("rejection" in error) || error.rejection == null);
143171
+ default:
143172
+ return true;
143173
+ }
143174
+ };
143175
+ var dispatchTurnWithRetry = async (start, input, options = {}) => {
143176
+ const sleep3 = options.sleep ?? abortableSleep;
143177
+ for (let attempt = 0; ; attempt += 1) {
143178
+ if (options.signal?.aborted) throw new Error("aborted");
143179
+ try {
143180
+ return await start(input);
143181
+ } catch (error) {
143182
+ const delay = RETRY_DELAYS_MS[attempt];
143183
+ if (delay === void 0 || !retryable2(error)) throw error;
143184
+ await sleep3(delay, options.signal);
143185
+ }
143186
+ }
143187
+ };
143188
+
143189
+ // src/dispatchReceipt.ts
143190
+ var dispatchTurnWithReceipt = async (host, input, options) => {
143191
+ try {
143192
+ await dispatchTurnWithRetry((next) => host.threads.turns.start(next), input, options);
143193
+ } catch (error) {
143194
+ if (options.signal.aborted) throw error;
143195
+ let receipt;
143196
+ try {
143197
+ receipt = await host.commands.getReceipt({
143198
+ commandId: input.requestId,
143199
+ commandType: "thread.turn.start",
143200
+ threadId: input.threadId
143201
+ });
143202
+ } catch (receiptError) {
143203
+ host.log.warn("chat could not read a turn command receipt", {
143204
+ requestId: input.requestId,
143205
+ threadId: input.threadId,
143206
+ error: describeError(receiptError)
143207
+ });
143208
+ throw error;
143209
+ }
143210
+ if (options.signal.aborted) throw error;
143211
+ if (receipt.status === "accepted") return;
143212
+ if (receipt.status === "rejected")
143213
+ throw new Error(receipt.error ?? "turn command rejected", { cause: error });
143214
+ throw error;
143215
+ }
143216
+ };
143217
+
143218
+ // src/messageIdentity.ts
143219
+ import * as NodeCrypto6 from "node:crypto";
143220
+ var messageIdentity = (operation, bot, message, adapter) => {
143221
+ const tuple = [
143222
+ IDENTITY_VERSION,
143223
+ operation,
143224
+ adapter.platform,
143225
+ adapter.botIdentity(bot),
143226
+ message.originalChatId ?? message.chatId,
143227
+ message.messageId
143228
+ ];
143229
+ return `${identityPrefix(operation)}${NodeCrypto6.createHash("sha256").update(JSON.stringify(tuple)).digest("hex")}`;
143230
+ };
143231
+
143232
+ // src/turns.ts
142554
143233
  var TITLE_MAX_CHARS = 72;
142555
143234
  var titleFrom = (text4) => text4.replace(/\s+/g, " ").slice(0, TITLE_MAX_CHARS) || "Chat thread";
142556
143235
  var makeTurns = (core, deps) => {
142557
143236
  const { host, t, commit, bindChat, deliver } = core;
142558
143237
  const { queueFlow, rememberThreadProject } = deps;
142559
143238
  const { pendingByChat, pendingFor, collectResources } = deps.attachments;
143239
+ const dispatchAbort = new AbortController();
143240
+ host.onDispose(() => dispatchAbort.abort());
143241
+ const identity = (operation, binding, message) => {
143242
+ const adapter = core.adapterFor(binding.platform);
143243
+ if (adapter === void 0) throw new Error(`missing IM adapter: ${binding.platform}`);
143244
+ return messageIdentity(operation, binding, message, adapter);
143245
+ };
142560
143246
  const threadStillUsable = async (binding, threadId) => {
142561
143247
  try {
142562
143248
  const thread = await threadOrNull8(host.threads, threadId, { archived: "include" });
@@ -142570,7 +143256,7 @@ var makeTurns = (core, deps) => {
142570
143256
  return waiting !== void 0 && waiting.threadId === threadId ? waiting : void 0;
142571
143257
  };
142572
143258
  const interactionModeOf = (chatKey) => chatOf(core.state, chatKey).plan ? "plan" : "default";
142573
- const ensureThread = async (binding, titleSeed) => {
143259
+ const ensureThread = async (binding, message) => {
142574
143260
  const chatKey = chatKeyOf(binding);
142575
143261
  let threadId = chatOf(core.state, chatKey).threadId ?? void 0;
142576
143262
  if (threadId !== void 0 && !await threadStillUsable(binding, threadId)) {
@@ -142600,12 +143286,12 @@ var makeTurns = (core, deps) => {
142600
143286
  await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
142601
143287
  return null;
142602
143288
  }
142603
- const createdThreadId = crypto.randomUUID();
143289
+ const createdThreadId = identity("thread", binding, message);
142604
143290
  await host.threads.create({
142605
- requestId: crypto.randomUUID(),
143291
+ requestId: identity("create", binding, message),
142606
143292
  threadId: createdThreadId,
142607
143293
  projectId: binding.projectId,
142608
- title: titleFrom(titleSeed),
143294
+ title: titleFrom(message.text || message.resources[0]?.name || "Chat"),
142609
143295
  modelSelection,
142610
143296
  runtimeMode: binding.runtimeMode,
142611
143297
  interactionMode: interactionModeOf(chatKey),
@@ -142624,7 +143310,7 @@ var makeTurns = (core, deps) => {
142624
143310
  const startTurn = async (binding, message) => {
142625
143311
  const chatKey = chatKeyOf(binding);
142626
143312
  const text4 = message.text;
142627
- const threadId = await ensureThread(binding, text4 || message.resources[0]?.name || "Chat");
143313
+ const threadId = await ensureThread(binding, message);
142628
143314
  if (threadId === null) return null;
142629
143315
  const topicRoot = await message.topicRootForSession?.(threadId);
142630
143316
  const carried = waitingFor(chatKey, threadId);
@@ -142638,7 +143324,7 @@ var makeTurns = (core, deps) => {
142638
143324
  );
142639
143325
  const attachments = [...carried?.attachments ?? [], ...own.attachments];
142640
143326
  const notes = [...carried?.notes ?? [], ...own.notes];
142641
- const userMessageId = crypto.randomUUID();
143327
+ const userMessageId = identity("msg", binding, message);
142642
143328
  deps.anchors.note(threadId, userMessageId, binding);
142643
143329
  let intentAction;
142644
143330
  if (binding.queueMode === "steer") {
@@ -142650,33 +143336,39 @@ var makeTurns = (core, deps) => {
142650
143336
  kind: "steer",
142651
143337
  source: "automatic",
142652
143338
  turnId: activeTurnId,
142653
- commandId: crypto.randomUUID()
143339
+ commandId: identity("steer", binding, message)
142654
143340
  };
142655
143341
  }
142656
143342
  } catch {
142657
143343
  }
142658
143344
  }
142659
143345
  const registered = deps.conclusions.starting(threadId);
143346
+ const requestId = identity("turn", binding, message);
143347
+ let failureNotice;
142660
143348
  try {
142661
- await host.threads.turns.start({
142662
- requestId: crypto.randomUUID(),
142663
- threadId,
142664
- message: {
142665
- messageId: userMessageId,
142666
- role: "user",
142667
- text: [
142668
- textWithInboundContext(message, binding.rowId, t, topicRoot?.reference),
142669
- ...notes
142670
- ].join("\n"),
142671
- attachments: attachments.map(
142672
- (attachment) => attachment.type === "image" ? { ...attachment, type: "image" } : { ...attachment, type: "file" }
142673
- )
143349
+ await dispatchTurnWithReceipt(
143350
+ host,
143351
+ {
143352
+ requestId,
143353
+ threadId,
143354
+ message: {
143355
+ messageId: userMessageId,
143356
+ role: "user",
143357
+ text: [
143358
+ textWithInboundContext(message, binding.rowId, t, topicRoot?.reference),
143359
+ ...notes
143360
+ ].join("\n"),
143361
+ attachments: attachments.map(
143362
+ (attachment) => attachment.type === "image" ? { ...attachment, type: "image" } : { ...attachment, type: "file" }
143363
+ )
143364
+ },
143365
+ titleSeed: titleFrom(text4 || attachments[0]?.name || "Chat"),
143366
+ runtimeMode: binding.runtimeMode,
143367
+ interactionMode: interactionModeOf(chatKey),
143368
+ ...intentAction !== void 0 ? { intentAction } : {}
142674
143369
  },
142675
- titleSeed: titleFrom(text4 || attachments[0]?.name || "Chat"),
142676
- runtimeMode: binding.runtimeMode,
142677
- interactionMode: interactionModeOf(chatKey),
142678
- ...intentAction !== void 0 ? { intentAction } : {}
142679
- });
143370
+ { sleep: deps.dispatchSleep, signal: dispatchAbort.signal }
143371
+ );
142680
143372
  topicRoot?.confirmConsumed();
142681
143373
  if (intentAction?.kind !== "steer") {
142682
143374
  deps.conclusions.owe({
@@ -142688,10 +143380,15 @@ var makeTurns = (core, deps) => {
142688
143380
  }
142689
143381
  } catch (error) {
142690
143382
  if (carried !== void 0) pendingByChat.set(chatKey, carried);
142691
- throw error;
143383
+ if (dispatchAbort.signal.aborted) return null;
143384
+ failureNotice = deps.failures.forDispatchFailure(threadId, requestId, error);
142692
143385
  } finally {
142693
143386
  registered();
142694
143387
  }
143388
+ if (failureNotice !== void 0) {
143389
+ await deliver(binding, failureNotice);
143390
+ return null;
143391
+ }
142695
143392
  deps.noteInboundUserMessage?.(userMessageId);
142696
143393
  host.log.info("chat started a turn from a message", {
142697
143394
  bot: binding.displayName,
@@ -142708,7 +143405,7 @@ var makeTurns = (core, deps) => {
142708
143405
  return threadId;
142709
143406
  };
142710
143407
  const receiveAttachmentsOnly = async (binding, message) => {
142711
- const threadId = await ensureThread(binding, message.resources[0]?.name || "Chat");
143408
+ const threadId = await ensureThread(binding, message);
142712
143409
  if (threadId === null) return;
142713
143410
  const waiting = pendingFor(chatKeyOf(binding), threadId);
142714
143411
  const collected = await collectResources(
@@ -142790,6 +143487,84 @@ var makeWebSync = (core) => {
142790
143487
  return { noteInboundUserMessage };
142791
143488
  };
142792
143489
 
143490
+ // src/lark/setup/repairScopes.ts
143491
+ var SUBSCRIPTIONS = { events: APP_EVENTS.join(", "), callbacks: CARD_CALLBACKS.join(", ") };
143492
+ var repairLarkScopes = async (http, bot, emit, ref, redactor) => {
143493
+ const { appId, appSecret, error: credentialError } = larkCredentials(bot);
143494
+ const steps = (token2, scopes) => emit({
143495
+ kind: "steps",
143496
+ steps: [
143497
+ { id: "token", labelKey: "rowAction.larkRepair.step.token", state: token2 },
143498
+ ...scopes === void 0 ? [] : [{ id: "scopes", labelKey: "rowAction.larkRepair.step.scopes", state: scopes }]
143499
+ ]
143500
+ });
143501
+ const fail = (error, credentials2 = false) => {
143502
+ const hint = credentials2 ? "larkCredentials" : larkConnectionHint(error);
143503
+ emit({
143504
+ kind: "failed",
143505
+ reasonKey: hint === void 0 ? "rowAction.larkRepair.failed" : connectionHintKey(hint),
143506
+ params: { ref, ...hint === void 0 ? {} : { hint } },
143507
+ detail: redactor.redact(describeError(error))
143508
+ });
143509
+ };
143510
+ steps("running", "pending");
143511
+ if (credentialError !== void 0) {
143512
+ steps("failed");
143513
+ fail(new Error(credentialError), true);
143514
+ return;
143515
+ }
143516
+ let token;
143517
+ try {
143518
+ ({ token } = await fetchTenantToken({ http, appId, appSecret }));
143519
+ redactor.register(token);
143520
+ } catch (error) {
143521
+ steps("failed");
143522
+ fail(error);
143523
+ return;
143524
+ }
143525
+ steps("done", "running");
143526
+ let granted;
143527
+ try {
143528
+ granted = await fetchAppTenantScopes({ http, token });
143529
+ } catch (error) {
143530
+ if (error instanceof LarkApiError && (error.status === 400 || error.status === 403 || isLarkPermissionCode(error.code))) {
143531
+ granted = void 0;
143532
+ } else {
143533
+ steps("done", "failed");
143534
+ fail(error);
143535
+ return;
143536
+ }
143537
+ }
143538
+ const missing = granted === void 0 ? SCOPES : SCOPES.filter((scope) => !granted.includes(scope));
143539
+ steps("done", granted === void 0 ? "failed" : "done");
143540
+ const needsAction = missing.length > 0;
143541
+ const url = `${CONSOLE_ORIGIN}${CONSOLE_APP_PATH}/${appId}/auth?q=${missing.join(",")}&op_from=openapi&token_type=tenant`;
143542
+ emit({
143543
+ kind: "done",
143544
+ fills: {},
143545
+ ...granted === void 0 || needsAction ? { tone: "caution" } : {},
143546
+ ...needsAction ? {
143547
+ link: {
143548
+ url,
143549
+ labelKey: granted === void 0 ? "rowAction.larkRepair.openCheckLink" : "rowAction.larkRepair.openApplyLink"
143550
+ }
143551
+ } : {},
143552
+ messageKey: granted === void 0 ? "rowAction.larkRepair.unverified" : needsAction ? "rowAction.larkRepair.missing" : "rowAction.larkRepair.allGranted",
143553
+ warnings: [
143554
+ ...needsAction ? [
143555
+ {
143556
+ key: granted === void 0 ? "rowAction.larkRepair.requiredScopes" : "rowAction.larkRepair.missingScopes",
143557
+ params: { scopes: missing.join("\n") }
143558
+ },
143559
+ { key: "rowAction.larkRepair.applyLink", params: { url } },
143560
+ { key: "rowAction.larkRepair.publishHint" }
143561
+ ] : [],
143562
+ { key: "rowAction.larkRepair.botHint" },
143563
+ { key: "rowAction.larkRepair.eventHint", params: SUBSCRIPTIONS }
143564
+ ]
143565
+ });
143566
+ };
143567
+
142793
143568
  // src/settingsRowActions.ts
142794
143569
  var ROW_PAYLOAD_SCHEMA = {
142795
143570
  type: "object",
@@ -142857,19 +143632,21 @@ var emitReport = (emit, report, action) => {
142857
143632
  });
142858
143633
  return;
142859
143634
  }
142860
- const suggested = report.hint !== void 0 && action.keyPrefix === "rowAction.checkConnection";
143635
+ const connectionReasonKey = report.hint === void 0 ? "rowAction.checkConnection.failed" : connectionHintKey(report.hint);
143636
+ const copying = action.keyPrefix === "rowAction.copyDiagnostics";
142861
143637
  emit({
142862
143638
  kind: "failed",
142863
- reasonKey: suggested && report.hint !== void 0 ? connectionHintKey(report.hint) : `${action.keyPrefix}.failed`,
143639
+ reasonKey: copying ? "rowAction.copyDiagnostics.done" : connectionReasonKey,
142864
143640
  // 参考号由界面写在细节第一行(「参考号:…」),结论句里不带。
142865
143641
  params: {
142866
143642
  ref: report.ref,
142867
- ...suggested && report.hint !== void 0 ? { hint: report.hint } : {}
143643
+ ...report.hint === void 0 ? {} : { hint: report.hint }
142868
143644
  },
143645
+ ...copying ? { noteKey: connectionReasonKey } : {},
142869
143646
  detail
142870
143647
  });
142871
143648
  };
142872
- var registerSettingsRowActions = (ctx, adapters) => {
143649
+ var registerSettingsRowActions = (ctx, adapters, connections) => {
142873
143650
  const resolveBot = (rowId) => botOf(ctx.config, adapters, rowId);
142874
143651
  const registerProbeAction = (action) => {
142875
143652
  ctx.stream({
@@ -142897,6 +143674,26 @@ var registerSettingsRowActions = (ctx, adapters) => {
142897
143674
  keyPrefix: "rowAction.checkConnection",
142898
143675
  copyable: false
142899
143676
  });
143677
+ ctx.stream({
143678
+ name: BOT_ACTION_COMMANDS.reconnect,
143679
+ description: "Restart only this IM bot's connection using its saved settings.",
143680
+ scope: "operate",
143681
+ payloadSchema: ROW_PAYLOAD_SCHEMA,
143682
+ itemSchema: ROW_ACTION_ITEM_SCHEMA,
143683
+ buffer: { latest: 8 },
143684
+ handle: async (payload, emit) => {
143685
+ const rowId = typeof payload["rowId"] === "string" ? payload["rowId"] : "";
143686
+ emit({ kind: "status", statusKey: "rowAction.reconnect.running", params: { rowId } });
143687
+ if (resolveBot(rowId) === void 0) {
143688
+ emit({ kind: "failed", reasonKey: "rowAction.reconnect.notSaved" });
143689
+ return;
143690
+ }
143691
+ const result = connections.reconnect(rowId);
143692
+ emit(
143693
+ result.started ? { kind: "done", fills: {}, messageKey: "rowAction.reconnect.done" } : { kind: "failed", reasonKey: `rowAction.reconnect.${result.reason}` }
143694
+ );
143695
+ }
143696
+ });
142900
143697
  registerProbeAction({
142901
143698
  name: BOT_ACTION_COMMANDS.copyDiagnostics,
142902
143699
  summary: "Probe an IM bot and return a copy-paste diagnostic report.",
@@ -142905,7 +143702,7 @@ var registerSettingsRowActions = (ctx, adapters) => {
142905
143702
  });
142906
143703
  ctx.stream({
142907
143704
  name: BOT_ACTION_COMMANDS.larkRepair,
142908
- description: "Guide Lark scope repair and verify the saved app can still mint a token.",
143705
+ description: "Check required Lark scopes through the public API and list manual repair steps.",
142909
143706
  scope: "operate",
142910
143707
  payloadSchema: ROW_PAYLOAD_SCHEMA,
142911
143708
  itemSchema: ROW_ACTION_ITEM_SCHEMA,
@@ -142914,15 +143711,6 @@ var registerSettingsRowActions = (ctx, adapters) => {
142914
143711
  const rowId = typeof payload["rowId"] === "string" ? payload["rowId"] : "";
142915
143712
  const send = (item) => emit(item);
142916
143713
  const ref = diagnosticRef("setup");
142917
- send({
142918
- kind: "steps",
142919
- steps: [
142920
- { id: "scopes", labelKey: "rowAction.larkRepair.step.scopes", state: "skipped" },
142921
- { id: "events", labelKey: "rowAction.larkRepair.step.events", state: "skipped" },
142922
- { id: "publish", labelKey: "rowAction.larkRepair.step.publish", state: "skipped" },
142923
- { id: "verify", labelKey: "rowAction.larkRepair.step.verify", state: "running" }
142924
- ]
142925
- });
142926
143714
  const bot = resolveBot(rowId);
142927
143715
  if (bot === void 0 || bot.platform !== "lark") {
142928
143716
  send({
@@ -142932,46 +143720,20 @@ var registerSettingsRowActions = (ctx, adapters) => {
142932
143720
  });
142933
143721
  return;
142934
143722
  }
142935
- const report = await probe(adapters, bot);
142936
- send({
142937
- kind: "steps",
142938
- steps: [
142939
- { id: "scopes", labelKey: "rowAction.larkRepair.step.scopes", state: "skipped" },
142940
- { id: "events", labelKey: "rowAction.larkRepair.step.events", state: "skipped" },
142941
- { id: "publish", labelKey: "rowAction.larkRepair.step.publish", state: "skipped" },
142942
- {
142943
- id: "verify",
142944
- labelKey: "rowAction.larkRepair.step.verify",
142945
- state: report.ok ? "done" : "failed"
142946
- }
142947
- ]
142948
- });
142949
- if (report.ok) {
142950
- send({
142951
- kind: "done",
142952
- fills: {},
142953
- messageKey: "rowAction.larkRepair.done",
142954
- warnings: [
142955
- {
142956
- key: "rowAction.larkRepair.hint",
142957
- params: { ref, detail: formatDiagnosticCopy(report) }
142958
- }
142959
- ]
142960
- });
142961
- return;
142962
- }
142963
- send({
142964
- kind: "failed",
142965
- reasonKey: "rowAction.larkRepair.failed",
142966
- params: { ref },
142967
- detail: formatDiagnosticCopy(report)
142968
- });
143723
+ const lark = adapters.find((adapter) => adapter.platform === "lark");
143724
+ await repairLarkScopes(
143725
+ ctx.http,
143726
+ bot,
143727
+ send,
143728
+ ref,
143729
+ botRedactor(lark?.columns ?? [], bot.values)
143730
+ );
142969
143731
  }
142970
143732
  });
142971
143733
  };
142972
143734
 
142973
143735
  // src/settingsView.ts
142974
- var isRecord3 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
143736
+ var isRecord4 = (value2) => typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
142975
143737
  var hasStoredSecret = (row, key) => {
142976
143738
  const value2 = row[key];
142977
143739
  return typeof value2 === "string" && value2 !== "";
@@ -142989,10 +143751,10 @@ var targetTable = () => ({
142989
143751
  });
142990
143752
  var targetViews = (config2) => {
142991
143753
  const table2 = config2[DELIVERY_TARGETS_FIELD];
142992
- if (!isRecord3(table2)) return [];
143754
+ if (!isRecord4(table2)) return [];
142993
143755
  const columns = deliveryTargetColumns();
142994
143756
  return Object.entries(table2).flatMap(([rowId, raw]) => {
142995
- if (!isRecord3(raw)) return [];
143757
+ if (!isRecord4(raw)) return [];
142996
143758
  const issues = targetIssues(raw);
142997
143759
  const values = Object.fromEntries(
142998
143760
  columns.map((column) => [column.key, readString(raw, column.key)])
@@ -143004,9 +143766,9 @@ var buildSettingsView = (config2, adapters) => {
143004
143766
  const columns = buildBotColumns(adapters);
143005
143767
  const table2 = config2[BOTS_FIELD];
143006
143768
  const bots = [];
143007
- if (isRecord3(table2)) {
143769
+ if (isRecord4(table2)) {
143008
143770
  for (const [rowId, raw] of Object.entries(table2)) {
143009
- if (!isRecord3(raw)) continue;
143771
+ if (!isRecord4(raw)) continue;
143010
143772
  const platform = platformOf(raw);
143011
143773
  const values = {};
143012
143774
  const secrets = {};
@@ -143069,10 +143831,10 @@ var applyRowEdit = (config2, { field, columns }, edit) => {
143069
143831
  for (const key of clearSecrets) {
143070
143832
  if (!secretColumns.has(key)) return columnError("not-a-secret", key);
143071
143833
  }
143072
- const table2 = isRecord3(config2[field]) ? config2[field] : {};
143834
+ const table2 = isRecord4(config2[field]) ? config2[field] : {};
143073
143835
  const requested = edit.rowId;
143074
143836
  const current = requested === void 0 ? {} : Object.hasOwn(table2, requested) ? table2[requested] : void 0;
143075
- if (!isRecord3(current)) return { ok: false, code: "unknown-row" };
143837
+ if (!isRecord4(current)) return { ok: false, code: "unknown-row" };
143076
143838
  const rowId = requested ?? nextRowId(table2);
143077
143839
  const nextRow = { ...current, ...edit.values };
143078
143840
  const clearing = clearSecrets.filter((key) => !Object.hasOwn(secrets, key));
@@ -143090,7 +143852,7 @@ var applyBotEdit = (config2, adapters, edit) => applyRowEdit(config2, { field: B
143090
143852
  var applyTargetEdit = (config2, edit) => applyRowEdit(config2, targetTable(), edit);
143091
143853
  var removeRow = (config2, field, rowId) => {
143092
143854
  const table2 = config2[field];
143093
- if (!isRecord3(table2) || !Object.hasOwn(table2, rowId)) return null;
143855
+ if (!isRecord4(table2) || !Object.hasOwn(table2, rowId)) return null;
143094
143856
  const nextTable = { ...table2 };
143095
143857
  delete nextTable[rowId];
143096
143858
  return { ...config2, [field]: nextTable };
@@ -143294,6 +144056,7 @@ var wechatConfigIssues = (bot) => tokenOf2(bot) === "" ? [{ column: "wechatBotTo
143294
144056
  var makeWechatAdapter = (deps) => {
143295
144057
  return {
143296
144058
  platform: "wechat",
144059
+ botIdentity: () => "wechat-stub",
143297
144060
  icon: "wechat",
143298
144061
  // 平台名与设置表的平台选项同一个键,免得默认机器人名与选项写成两种叫法。
143299
144062
  label: deps.translate("platform.wechat"),
@@ -143329,7 +144092,7 @@ var makeWechatAdapter = (deps) => {
143329
144092
  };
143330
144093
 
143331
144094
  // src/wecomUpload.ts
143332
- import * as NodeCrypto5 from "node:crypto";
144095
+ import * as NodeCrypto7 from "node:crypto";
143333
144096
  var WECOM_CHUNK_BYTES = 512 * 1024;
143334
144097
  var WECOM_MAX_CHUNKS = 100;
143335
144098
  var WECOM_MIN_UPLOAD_BYTES = 5;
@@ -143362,7 +144125,7 @@ var uploadWecomMedia = async (input) => {
143362
144125
  filename: clampName(input.filename),
143363
144126
  total_size: bytes.byteLength,
143364
144127
  total_chunks: totalChunks,
143365
- md5: NodeCrypto5.createHash("md5").update(bytes).digest("hex")
144128
+ md5: NodeCrypto7.createHash("md5").update(bytes).digest("hex")
143366
144129
  });
143367
144130
  const uploadId = init.upload_id;
143368
144131
  if (typeof uploadId !== "string" || uploadId === "") {
@@ -143541,6 +144304,26 @@ var parseWecomMessage = (frame, botId) => {
143541
144304
  };
143542
144305
  var splitForWecom = (text4, limit = WECOM_TEXT_MAX_CHARS) => splitMessage(text4, limit);
143543
144306
  var decoder = new TextDecoder();
144307
+ var WecomAckError = class extends Error {
144308
+ code;
144309
+ constructor(code2, msg) {
144310
+ super(`WeCom ack failed: ${String(code2)} ${msg}`);
144311
+ this.name = "WecomAckError";
144312
+ this.code = code2;
144313
+ }
144314
+ };
144315
+ var subscriptionHint = (error) => {
144316
+ if (!(error instanceof WecomAckError)) return void 0;
144317
+ switch (error.code) {
144318
+ case 853e3:
144319
+ case 40058:
144320
+ return "wecomBotCredentials";
144321
+ case 846601:
144322
+ return "wecomLongConnOff";
144323
+ default:
144324
+ return void 0;
144325
+ }
144326
+ };
143544
144327
  var runWecomClient = (input) => {
143545
144328
  let sequence = 0;
143546
144329
  const nextRequestId = (kind) => `${kind}_${++sequence}`;
@@ -143571,32 +144354,47 @@ var runWecomClient = (input) => {
143571
144354
  if (typeof frame !== "object" || frame === null) return;
143572
144355
  const requestId = frame.headers?.req_id;
143573
144356
  if (typeof frame.cmd !== "string") {
143574
- if (typeof requestId !== "string") return;
144357
+ const error = typeof frame.errcode === "number" && frame.errcode !== 0 ? new WecomAckError(frame.errcode, frame.errmsg ?? "") : null;
144358
+ if (typeof requestId !== "string") {
144359
+ if (error !== null) settleAck(subscribeId, error, frame);
144360
+ return;
144361
+ }
143575
144362
  if (requestId.startsWith("ping")) {
143576
144363
  missedPong = 0;
143577
144364
  return;
143578
144365
  }
143579
- const error = typeof frame.errcode === "number" && frame.errcode !== 0 ? new Error(`WeCom ack failed: ${String(frame.errcode)} ${frame.errmsg ?? ""}`) : null;
143580
144366
  const stream = openStreams.get(requestId);
143581
144367
  if (stream !== void 0 && error !== null) stream.failed = true;
143582
144368
  settleAck(requestId, error, frame);
143583
144369
  return;
143584
144370
  }
144371
+ if (frame.cmd === "aibot_event_callback" && frame.body?.event?.eventtype === "disconnected_event") {
144372
+ finish("taken-over");
144373
+ return;
144374
+ }
143585
144375
  if (frame.cmd !== "aibot_msg_callback") return;
143586
144376
  const message = parseWecomMessage(frame, input.botId);
143587
144377
  if (message !== null) input.handlers.onMessage(message);
143588
144378
  };
143589
- const finish = (error = new Error("WeCom connection closed"), reason = "disconnected") => {
144379
+ const finish = (reason = "disconnected", error = reason === "taken-over" ? void 0 : new Error("WeCom connection closed")) => {
143590
144380
  if (finished) return;
143591
144381
  finished = true;
143592
- if (!closed) input.handlers.onConnectionState?.({ state: "failed", error, reason });
144382
+ const hint = reason === "taken-over" ? "wecomTakenOver" : reason === "subscription-failed" ? subscriptionHint(error) : void 0;
144383
+ const failureReason = hint === "wecomBotCredentials" ? "credentials-rejected" : reason;
144384
+ if (!closed)
144385
+ input.handlers.onConnectionState?.({
144386
+ state: "failed",
144387
+ ...error === void 0 ? {} : { error },
144388
+ reason: failureReason,
144389
+ ...hint === void 0 ? {} : { hint }
144390
+ });
143593
144391
  clearInterval(heartbeat);
143594
144392
  openStreams.clear();
143595
144393
  void input.socket.close().catch(() => void 0);
143596
144394
  for (const requestId of pendingAcks.keys()) {
143597
144395
  settleAck(requestId, new Error("WeCom connection closed"));
143598
144396
  }
143599
- ended.resolve();
144397
+ ended.resolve(failureReason === "subscription-failed" ? "disconnected" : failureReason);
143600
144398
  };
143601
144399
  const readLoop = async () => {
143602
144400
  try {
@@ -143607,7 +144405,7 @@ var runWecomClient = (input) => {
143607
144405
  if (!closed) {
143608
144406
  input.handlers.onError(error instanceof Error ? error.message : String(error));
143609
144407
  }
143610
- finish(error);
144408
+ finish("disconnected", error);
143611
144409
  }
143612
144410
  finish();
143613
144411
  };
@@ -143615,7 +144413,7 @@ var runWecomClient = (input) => {
143615
144413
  if (finished) return;
143616
144414
  if (missedPong >= MAX_MISSED_PONGS) {
143617
144415
  input.handlers.onError("socket hang up: missed pong");
143618
- finish(new Error("socket hang up: missed pong"));
144416
+ finish("disconnected", new Error("socket hang up: missed pong"));
143619
144417
  return;
143620
144418
  }
143621
144419
  missedPong += 1;
@@ -143642,7 +144440,7 @@ var runWecomClient = (input) => {
143642
144440
  },
143643
144441
  (error) => {
143644
144442
  if (!closed) input.handlers.onError(error instanceof Error ? error.message : String(error));
143645
- finish(error, "subscription-failed");
144443
+ finish("subscription-failed", error);
143646
144444
  return false;
143647
144445
  }
143648
144446
  );
@@ -143777,7 +144575,7 @@ var runWecomClient = (input) => {
143777
144575
  };
143778
144576
 
143779
144577
  // src/wecomMedia.ts
143780
- import * as NodeCrypto6 from "node:crypto";
144578
+ import * as NodeCrypto8 from "node:crypto";
143781
144579
 
143782
144580
  // src/wecomPadding.ts
143783
144581
  var PADDING_BLOCK = 32;
@@ -143808,7 +144606,7 @@ var decryptWecomMedia = (cipher, aesKey) => {
143808
144606
  if (cipher.byteLength === 0 || cipher.byteLength % AES_BLOCK !== 0) {
143809
144607
  throw new WecomDecryptError("the ciphertext is not a whole number of blocks");
143810
144608
  }
143811
- const decipher = NodeCrypto6.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
144609
+ const decipher = NodeCrypto8.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
143812
144610
  decipher.setAutoPadding(false);
143813
144611
  const plain = Buffer.concat([decipher.update(cipher), decipher.final()]);
143814
144612
  const unpadded = stripPadding(plain);
@@ -143872,13 +144670,13 @@ var COLUMNS4 = [
143872
144670
  { key: "wecomBotSecret", kind: "password", labelKey: "column.wecomBotSecret.label" }
143873
144671
  ];
143874
144672
  var defaultSleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
143875
- var credentials2 = (binding) => ({
144673
+ var credentials = (binding) => ({
143876
144674
  botId: binding.values["wecomBotId"] ?? "",
143877
144675
  botSecret: binding.values["wecomBotSecret"] ?? "",
143878
144676
  endpoint: normalizeWecomEndpoint(binding.values["wecomEndpoint"] || DEFAULT_WECOM_ENDPOINT)
143879
144677
  });
143880
144678
  var wecomConfigIssues = (binding) => {
143881
- const { botId, botSecret } = credentials2(binding);
144679
+ const { botId, botSecret } = credentials(binding);
143882
144680
  const issues = [];
143883
144681
  if (botId === "") issues.push({ column: "wecomBotId", code: "required" });
143884
144682
  if (botSecret === "") issues.push({ column: "wecomBotSecret", code: "required" });
@@ -143886,6 +144684,7 @@ var wecomConfigIssues = (binding) => {
143886
144684
  };
143887
144685
  var makeWecomAdapter = (deps) => {
143888
144686
  const clients = /* @__PURE__ */ new Map();
144687
+ const managedConnections = /* @__PURE__ */ new Map();
143889
144688
  const t = deps.translate;
143890
144689
  const liveStreams = /* @__PURE__ */ new Map();
143891
144690
  const rememberStream = (key, entry) => {
@@ -143924,6 +144723,7 @@ ${kept}`;
143924
144723
  };
143925
144724
  return {
143926
144725
  platform: "wecom",
144726
+ botIdentity: (bot) => credentials(bot).botId,
143927
144727
  icon: "wecom",
143928
144728
  label: "WeCom",
143929
144729
  columns: COLUMNS4,
@@ -143931,15 +144731,22 @@ ${kept}`;
143931
144731
  validate: wecomConfigIssues,
143932
144732
  isConfigured: (binding) => wecomConfigIssues(binding).length === 0,
143933
144733
  connect: (binding, handlers) => {
143934
- const { botId, botSecret, endpoint } = credentials2(binding);
144734
+ const { botId, botSecret, endpoint } = credentials(binding);
144735
+ const managed = { endpoint };
144736
+ managedConnections.set(binding.rowId, managed);
143935
144737
  let stopped = false;
143936
144738
  let current = null;
143937
144739
  const stopSignal = Promise.withResolvers();
144740
+ const onConnectionState = (state) => {
144741
+ if (stopped) return;
144742
+ managed.state = state;
144743
+ handlers.onConnectionState?.(state);
144744
+ };
143938
144745
  const runOnce = async () => {
143939
144746
  const socket = await deps.connectSocket(endpoint);
143940
144747
  if (stopped) {
143941
144748
  await socket.close();
143942
- return false;
144749
+ return { healthy: false, stopRetrying: false };
143943
144750
  }
143944
144751
  const client = runWecomClient({
143945
144752
  socket: {
@@ -143951,9 +144758,7 @@ ${kept}`;
143951
144758
  botSecret,
143952
144759
  handlers: {
143953
144760
  onError: handlers.onError,
143954
- onConnectionState: (state) => {
143955
- if (!stopped) handlers.onConnectionState?.(state);
143956
- },
144761
+ onConnectionState,
143957
144762
  onMessage: (message) => {
143958
144763
  if (message.chatType !== "single") return;
143959
144764
  client.rememberRequest(message.chatId, message.requestId);
@@ -143983,20 +144788,25 @@ ${kept}`;
143983
144788
  if (ok) deps.log.info("wecom connected", { bot: binding.displayName });
143984
144789
  return ok;
143985
144790
  });
143986
- await client.ended;
144791
+ const reason = await client.ended;
143987
144792
  if (clients.get(binding.rowId) === client) clients.delete(binding.rowId);
143988
144793
  current = null;
143989
- return healthy;
144794
+ return {
144795
+ healthy: await healthy,
144796
+ stopRetrying: reason === "taken-over" || reason === "credentials-rejected"
144797
+ };
143990
144798
  };
143991
144799
  const supervise = async () => {
143992
144800
  let backoff = BACKOFF_START_MS2;
143993
144801
  for (; ; ) {
143994
144802
  if (stopped) return;
143995
144803
  try {
143996
- if (await runOnce()) backoff = BACKOFF_START_MS2;
144804
+ const result = await runOnce();
144805
+ if (result.stopRetrying) return;
144806
+ if (result.healthy) backoff = BACKOFF_START_MS2;
143997
144807
  } catch (error) {
143998
144808
  if (stopped) return;
143999
- handlers.onConnectionState?.({ state: "failed", error, reason: "disconnected" });
144809
+ onConnectionState({ state: "failed", error, reason: "disconnected" });
144000
144810
  handlers.onError(error instanceof Error ? error.message : String(error));
144001
144811
  }
144002
144812
  if (stopped) return;
@@ -144011,6 +144821,9 @@ ${kept}`;
144011
144821
  stopped = true;
144012
144822
  stopSignal.resolve();
144013
144823
  current?.close();
144824
+ if (managedConnections.get(binding.rowId) === managed) {
144825
+ managedConnections.delete(binding.rowId);
144826
+ }
144014
144827
  }
144015
144828
  };
144016
144829
  },
@@ -144023,12 +144836,33 @@ ${kept}`;
144023
144836
  await client.sendText(binding.chatId, text4);
144024
144837
  },
144025
144838
  checkConnection: async (bot) => {
144026
- const { botId, botSecret, endpoint } = credentials2(bot);
144839
+ const managed = managedConnections.get(bot.rowId);
144840
+ if (managed !== void 0) {
144841
+ const state = managed.state;
144842
+ const detail = `managed connection, ${managed.endpoint}`;
144843
+ if (state?.state === "connected") {
144844
+ return {
144845
+ stage: "aibot_subscribe",
144846
+ ok: true,
144847
+ detail: `subscription confirmed (${detail})`
144848
+ };
144849
+ }
144850
+ return {
144851
+ stage: "aibot_subscribe",
144852
+ ok: false,
144853
+ detail: state === void 0 ? `connecting (${detail})` : `${state.error === void 0 ? state.reason ?? "connection failed" : describeError(state.error)} (${detail})`,
144854
+ // 还没回报状态只是开连到订阅回执之间:宿主开连 10 秒、订阅回执 10 秒(wecomApi 的 SUBSCRIBE_TIMEOUT_MS)
144855
+ // 都会超时转 failed,所以不会一直停在「还在连接」。
144856
+ ...state === void 0 ? { hint: "connecting" } : state.state === "failed" && state.hint !== void 0 ? { hint: state.hint } : {}
144857
+ };
144858
+ }
144859
+ const { botId, botSecret, endpoint } = credentials(bot);
144027
144860
  if (botId === "" || botSecret === "") {
144028
144861
  return {
144029
144862
  stage: "credentials",
144030
144863
  ok: false,
144031
- detail: "missing bot id or secret in saved config"
144864
+ detail: "missing bot id or secret in saved config",
144865
+ hint: "wecomBotCredentials"
144032
144866
  };
144033
144867
  }
144034
144868
  let stage = "websocket";
@@ -144036,6 +144870,7 @@ ${kept}`;
144036
144870
  const socket = await deps.connectSocket(endpoint);
144037
144871
  stage = "aibot_subscribe";
144038
144872
  let detail = "WeCom connection closed before subscribe ack";
144873
+ let hint;
144039
144874
  let closing;
144040
144875
  const client = runWecomClient({
144041
144876
  socket: {
@@ -144047,6 +144882,9 @@ ${kept}`;
144047
144882
  botSecret,
144048
144883
  handlers: {
144049
144884
  onMessage: () => void 0,
144885
+ onConnectionState: (state) => {
144886
+ if (state.state === "failed") hint = state.hint;
144887
+ },
144050
144888
  onError: (error) => {
144051
144889
  detail = error;
144052
144890
  }
@@ -144054,7 +144892,12 @@ ${kept}`;
144054
144892
  });
144055
144893
  try {
144056
144894
  const ok = await client.ready;
144057
- return { stage, ok, detail: ok ? `subscription confirmed (${endpoint})` : detail };
144895
+ return {
144896
+ stage,
144897
+ ok,
144898
+ detail: ok ? `subscription confirmed (${endpoint})` : detail,
144899
+ ...!ok && hint !== void 0 ? { hint } : {}
144900
+ };
144058
144901
  } finally {
144059
144902
  client.close();
144060
144903
  await closing;
@@ -144406,7 +145249,7 @@ import * as NodeHttp2 from "node:http";
144406
145249
  import * as NodeNet from "node:net";
144407
145250
 
144408
145251
  // src/wecomAppCrypto.ts
144409
- import * as NodeCrypto7 from "node:crypto";
145252
+ import * as NodeCrypto9 from "node:crypto";
144410
145253
  var AES_KEY_BYTES = 32;
144411
145254
  var RANDOM_BYTES = 16;
144412
145255
  var WecomAppCryptoError = class extends Error {
@@ -144429,13 +145272,13 @@ var timingSafeEqualString2 = (a, b) => {
144429
145272
  const left = Buffer.from(a);
144430
145273
  const right = Buffer.from(b);
144431
145274
  if (left.length === 0 || left.length !== right.length) return false;
144432
- return NodeCrypto7.timingSafeEqual(left, right);
145275
+ return NodeCrypto9.timingSafeEqual(left, right);
144433
145276
  };
144434
- var computeSignature = (token, timestamp, nonce, encrypt) => NodeCrypto7.createHash("sha1").update([token, timestamp, nonce, encrypt].sort().join("")).digest("hex");
145277
+ var computeSignature = (token, timestamp, nonce, encrypt) => NodeCrypto9.createHash("sha1").update([token, timestamp, nonce, encrypt].sort().join("")).digest("hex");
144435
145278
  var verifySignature = (token, timestamp, nonce, encrypt, signature) => timingSafeEqualString2(computeSignature(token, timestamp, nonce, encrypt), signature);
144436
145279
  var decrypt = (encodingAESKey, encryptText, expectReceiveId) => {
144437
145280
  const { key, iv } = parseEncodingAESKey(encodingAESKey);
144438
- const decipher = NodeCrypto7.createDecipheriv("aes-256-cbc", key, iv);
145281
+ const decipher = NodeCrypto9.createDecipheriv("aes-256-cbc", key, iv);
144439
145282
  decipher.setAutoPadding(false);
144440
145283
  let deciphered;
144441
145284
  try {
@@ -144494,10 +145337,10 @@ var createWecomAppCrypto = (input) => {
144494
145337
  decrypt: (encryptText) => decrypt(encodingAESKey, encryptText, corpId).message
144495
145338
  };
144496
145339
  };
144497
- var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto7.createHash("sha256").update(`${token.trim()}:${encodingAESKey.trim()}`).digest("hex").slice(0, 24);
145340
+ var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto9.createHash("sha256").update(`${token.trim()}:${encodingAESKey.trim()}`).digest("hex").slice(0, 24);
144498
145341
 
144499
145342
  // src/wecomAppParse.ts
144500
- var isRecord4 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
145343
+ var isRecord5 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
144501
145344
  var isXmlFormat = (raw) => {
144502
145345
  const trimmed = raw.trim();
144503
145346
  return trimmed.startsWith("<") && trimmed.endsWith(">");
@@ -144531,7 +145374,6 @@ var parseWecomAppPlainMessage = (raw) => {
144531
145374
  text: xml["Content"] !== void 0 ? { content: xml["Content"] } : void 0,
144532
145375
  image: xml["PicUrl"] ? { url: xml["PicUrl"] } : void 0,
144533
145376
  mediaId: xml["MediaId"] ?? xml["mediaid"],
144534
- recognition: xml["Recognition"],
144535
145377
  event: (xml["Event"] ?? xml["event"] ?? "").toLowerCase() || void 0,
144536
145378
  eventKey: xml["EventKey"],
144537
145379
  chatid: xml["ChatId"]
@@ -144543,7 +145385,7 @@ var parseWecomAppPlainMessage = (raw) => {
144543
145385
  } catch {
144544
145386
  return null;
144545
145387
  }
144546
- if (!isRecord4(data)) return null;
145388
+ if (!isRecord5(data)) return null;
144547
145389
  const fromRaw = data["from"];
144548
145390
  const textRaw = data["text"];
144549
145391
  const imageRaw = data["image"];
@@ -144552,12 +145394,11 @@ var parseWecomAppPlainMessage = (raw) => {
144552
145394
  msgtype: String(data["msgtype"] ?? data["MsgType"] ?? "").toLowerCase(),
144553
145395
  createTime: data["createTime"] !== void 0 ? String(data["createTime"]) : data["CreateTime"] !== void 0 ? String(data["CreateTime"]) : void 0,
144554
145396
  agentId: data["agentid"] !== void 0 ? String(data["agentid"]) : data["AgentID"] !== void 0 ? String(data["AgentID"]) : void 0,
144555
- from: isRecord4(fromRaw) ? { userid: typeof fromRaw["userid"] === "string" ? fromRaw["userid"] : void 0 } : typeof data["FromUserName"] === "string" ? { userid: data["FromUserName"] } : void 0,
145397
+ from: isRecord5(fromRaw) ? { userid: typeof fromRaw["userid"] === "string" ? fromRaw["userid"] : void 0 } : typeof data["FromUserName"] === "string" ? { userid: data["FromUserName"] } : void 0,
144556
145398
  to: typeof data["to"] === "string" ? data["to"] : void 0,
144557
- text: isRecord4(textRaw) ? { content: typeof textRaw["content"] === "string" ? textRaw["content"] : void 0 } : void 0,
144558
- image: isRecord4(imageRaw) ? { url: typeof imageRaw["url"] === "string" ? imageRaw["url"] : void 0 } : void 0,
145399
+ text: isRecord5(textRaw) ? { content: typeof textRaw["content"] === "string" ? textRaw["content"] : void 0 } : void 0,
145400
+ image: isRecord5(imageRaw) ? { url: typeof imageRaw["url"] === "string" ? imageRaw["url"] : void 0 } : void 0,
144559
145401
  mediaId: typeof data["mediaid"] === "string" ? data["mediaid"] : void 0,
144560
- recognition: typeof data["recognition"] === "string" ? data["recognition"] : void 0,
144561
145402
  event: String(data["event"] ?? data["eventtype"] ?? "").toLowerCase() || void 0,
144562
145403
  eventKey: typeof data["eventkey"] === "string" ? data["eventkey"] : void 0,
144563
145404
  chatid: typeof data["chatid"] === "string" ? data["chatid"] : void 0
@@ -144621,13 +145462,16 @@ var startWecomAppCallbackServer = async (options) => {
144621
145462
  }
144622
145463
  try {
144623
145464
  if (!crypto2.verifySignature(signature, timestamp, nonce, echostr)) {
145465
+ route.onVerification?.(false);
144624
145466
  writeText(res, 401, "unauthorized");
144625
145467
  return;
144626
145468
  }
144627
145469
  const plaintext = crypto2.decrypt(echostr);
145470
+ route.onVerification?.(true);
144628
145471
  route.log?.info("wecom-app URL verification succeeded", { bot: route.botRowId });
144629
145472
  writeText(res, 200, plaintext);
144630
145473
  } catch (error) {
145474
+ route.onVerification?.(false);
144631
145475
  route.log?.warn("wecom-app URL verification failed", {
144632
145476
  bot: route.botRowId,
144633
145477
  error: error instanceof Error ? error.message : String(error)
@@ -144689,11 +145533,13 @@ var startWecomAppCallbackServer = async (options) => {
144689
145533
  let plaintext;
144690
145534
  try {
144691
145535
  if (!crypto2.verifySignature(signature, timestamp, nonce, encrypt)) {
145536
+ route.onVerification?.(false);
144692
145537
  writeText(res, 401, "unauthorized");
144693
145538
  return;
144694
145539
  }
144695
145540
  plaintext = crypto2.decrypt(encrypt);
144696
145541
  } catch (error) {
145542
+ route.onVerification?.(false);
144697
145543
  route.log?.warn("wecom-app callback signature or decrypt failed", {
144698
145544
  bot: route.botRowId,
144699
145545
  error: error instanceof Error ? error.message : String(error)
@@ -144701,6 +145547,7 @@ var startWecomAppCallbackServer = async (options) => {
144701
145547
  writeText(res, 401, "unauthorized");
144702
145548
  return;
144703
145549
  }
145550
+ route.onVerification?.(true);
144704
145551
  const message = parseWecomAppPlainMessage(plaintext);
144705
145552
  if (message === null) {
144706
145553
  writeText(res, 400, "invalid message");
@@ -144879,11 +145726,11 @@ var validateWecomAppBot = (bot) => {
144879
145726
  };
144880
145727
  var readWecomAppConnectConfig = (bot) => {
144881
145728
  const issues = validateWecomAppBot(bot);
144882
- const credentials3 = wecomAppCredentials(bot);
144883
- const listenPort = parseListenPort(credentials3.listenPortRaw);
144884
- const listenHost = parseListenHost(credentials3.listenHostRaw);
145729
+ const credentials2 = wecomAppCredentials(bot);
145730
+ const listenPort = parseListenPort(credentials2.listenPortRaw);
145731
+ const listenHost = parseListenHost(credentials2.listenHostRaw);
144885
145732
  if (issues.length > 0 || listenPort === null || listenHost === null) return { ok: false, issues };
144886
- return { ok: true, credentials: credentials3, listenHost, listenPort };
145733
+ return { ok: true, credentials: credentials2, listenHost, listenPort };
144887
145734
  };
144888
145735
 
144889
145736
  // src/wecomAppAdapter.ts
@@ -144943,6 +145790,7 @@ var WECOM_APP_MIN_BYTES = 5;
144943
145790
  var WECOM_APP_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png"]);
144944
145791
  var CREDENTIAL_ERROR_CODES = /* @__PURE__ */ new Set(["40001", "40013", "40014", "40056", "42001", "60020"]);
144945
145792
  var joinCallbackUrl = (baseOrOrigin, path) => `${baseOrOrigin}${path}`;
145793
+ var callbackHealthKey = (rowId, c) => JSON.stringify([rowId, c.corpId, c.token, c.encodingAESKey]);
144946
145794
  var wecomAppPublicFields = (bot) => {
144947
145795
  const c = wecomAppCredentials(bot);
144948
145796
  if (c.token === "" || c.encodingAESKey === "") {
@@ -144979,6 +145827,7 @@ var makeWecomAppAdapter = (deps) => {
144979
145827
  fetch: deps.fetch
144980
145828
  });
144981
145829
  const callbackHub = makeWecomAppCallbackHub();
145830
+ const callbackHealth = /* @__PURE__ */ new Map();
144982
145831
  const connectionFor = (binding) => {
144983
145832
  const api = apis.get(binding.rowId);
144984
145833
  if (api === void 0) {
@@ -145008,7 +145857,6 @@ var makeWecomAppAdapter = (deps) => {
145008
145857
  });
145009
145858
  };
145010
145859
  const toInbound = (message, handlers, botName) => {
145011
- if (message.msgtype === "event") return;
145012
145860
  if (message.chatid !== void 0 && message.chatid !== "") {
145013
145861
  deps.log.info("wecom-app ignored a group message; only direct messages are supported", {
145014
145862
  bot: botName
@@ -145017,6 +145865,12 @@ var makeWecomAppAdapter = (deps) => {
145017
145865
  }
145018
145866
  const chatId = message.from?.userid ?? "";
145019
145867
  if (chatId === "") return;
145868
+ if (message.msgtype === "event") {
145869
+ if (message.event === "subscribe" || message.event === "enter_agent") {
145870
+ handlers.onGreeting?.(chatId, chatId);
145871
+ }
145872
+ return;
145873
+ }
145020
145874
  const messageId = message.msgid ?? `${chatId}:${message.createTime ?? Date.now()}`;
145021
145875
  const base2 = { messageId, chatId, senderId: chatId };
145022
145876
  if (message.msgtype === "text") {
@@ -145042,6 +145896,10 @@ var makeWecomAppAdapter = (deps) => {
145042
145896
  };
145043
145897
  return {
145044
145898
  platform: "wecom-app",
145899
+ botIdentity: (bot) => {
145900
+ const { corpId, agentId } = wecomAppCredentials(bot);
145901
+ return JSON.stringify([corpId, agentId]);
145902
+ },
145045
145903
  icon: "wecom",
145046
145904
  label: deps.translate("platform.wecom-app"),
145047
145905
  columns: COLUMNS5,
@@ -145067,8 +145925,7 @@ var makeWecomAppAdapter = (deps) => {
145067
145925
  };
145068
145926
  },
145069
145927
  /**
145070
- * 自建应用是回调式渠道:入站由企微推到我们的 listener,没有可探的长连接;能探的是出站
145071
- * 凭据——和真实发送同一条路,向 gettoken 换一次 access_token。
145928
+ * 出站凭据走真实发送的 gettoken;入站只能报告本次运行已观察到的回调。
145072
145929
  */
145073
145930
  checkConnection: async (bot) => {
145074
145931
  const outbound = validateWecomAppBot(bot).filter(
@@ -145089,11 +145946,21 @@ var makeWecomAppAdapter = (deps) => {
145089
145946
  };
145090
145947
  }
145091
145948
  try {
145092
- await newApi(wecomAppCredentials(bot)).getAccessToken();
145949
+ const credentials2 = wecomAppCredentials(bot);
145950
+ await newApi(credentials2).getAccessToken();
145951
+ const health = callbackHealth.get(callbackHealthKey(bot.rowId, credentials2));
145952
+ if (health?.lastReceivedAt === void 0) {
145953
+ return {
145954
+ stage: "callback",
145955
+ ok: false,
145956
+ hint: health?.lastRejectedAt === void 0 ? "wecomCallbackNotSeen" : "wecomCallbackSignature",
145957
+ detail: health?.lastRejectedAt === void 0 ? "access_token ok; no verified callback since this startup" : `access_token ok; last callback signature or decryption failure since this startup: ${health.lastRejectedAt}`
145958
+ };
145959
+ }
145093
145960
  return {
145094
- stage: "gettoken",
145961
+ stage: "callback",
145095
145962
  ok: true,
145096
- detail: "access_token ok (callback reachability is not probed)"
145963
+ detail: `access_token ok; last verified callback since this startup: ${health.lastReceivedAt}`
145097
145964
  };
145098
145965
  } catch (error) {
145099
145966
  return {
@@ -145131,7 +145998,14 @@ var makeWecomAppAdapter = (deps) => {
145131
145998
  encodingAESKey: c.encodingAESKey,
145132
145999
  corpId: c.corpId,
145133
146000
  log: deps.log,
145134
- onInbound: (message) => toInbound(message, handlers, bot.displayName)
146001
+ onInbound: (message) => toInbound(message, handlers, bot.displayName),
146002
+ onVerification: (verified) => {
146003
+ const key = callbackHealthKey(bot.rowId, c);
146004
+ callbackHealth.set(key, {
146005
+ ...callbackHealth.get(key),
146006
+ ...verified ? { lastReceivedAt: (/* @__PURE__ */ new Date()).toISOString() } : { lastRejectedAt: (/* @__PURE__ */ new Date()).toISOString() }
146007
+ });
146008
+ }
145135
146009
  });
145136
146010
  } catch (error) {
145137
146011
  apis.delete(bot.rowId);
@@ -145221,10 +146095,18 @@ var makeWecomAppAdapter = (deps) => {
145221
146095
  };
145222
146096
 
145223
146097
  // src/index.ts
145224
- var imPlugin = definePlugin({
146098
+ var definition = {
145225
146099
  id: "im",
145226
- apiVersion: 18,
145227
- activate: async (ctx) => {
146100
+ apiVersion: 20,
146101
+ // 第二个参数只给插件自己的测试夹具:换掉文件 IO 与派发重试的等待。宿主只传 ctx。
146102
+ activate: async (ctx, testing = {}) => {
146103
+ const { dispatchSleep } = testing;
146104
+ const inboundState = makeInboundStore({
146105
+ dataDir: ctx.paths.dataDir,
146106
+ log: ctx.log,
146107
+ io: testing.inboundIo
146108
+ });
146109
+ ctx.onDispose(inboundState.flush);
145228
146110
  const { t, setLanguage } = makeLiveTranslate(
145229
146111
  parseReplyLanguage(ctx.config[REPLY_LANGUAGE_FIELD])
145230
146112
  );
@@ -145246,6 +146128,8 @@ var imPlugin = definePlugin({
145246
146128
  // Telegram 在不少网络里要绕代理,而绕不绕、绕哪个是按行配的 —— 这是插件面上
145247
146129
  // `ctx.proxies` 的第一个真实消费者。
145248
146130
  fetchVia: (proxy) => ctx.proxies.fetch(proxy),
146131
+ readCursor: inboundState.readCursor,
146132
+ saveCursor: inboundState.saveCursor,
145249
146133
  translate: t,
145250
146134
  log: {
145251
146135
  info: (message, fields) => ctx.log.info(message, fields),
@@ -145283,13 +146167,13 @@ var imPlugin = definePlugin({
145283
146167
  ctx.settingsCard(buildSettingsCard(adapters));
145284
146168
  const updateSettings = makeSettingsUpdater(ctx.settings);
145285
146169
  registerLarkSetup(ctx, { updateSettings });
145286
- registerSettingsRowActions(ctx, adapters);
145287
146170
  const core = makeImCore({
145288
146171
  host: {
145289
146172
  log: ctx.log,
145290
146173
  projections: ctx.projections,
145291
146174
  projects: ctx.projects,
145292
146175
  threads: ctx.threads,
146176
+ commands: ctx.commands,
145293
146177
  approvals: ctx.approvals,
145294
146178
  plugins: ctx.plugins,
145295
146179
  attachments: ctx.attachments,
@@ -145302,7 +146186,8 @@ var imPlugin = definePlugin({
145302
146186
  config: () => ctx.config,
145303
146187
  derivedState: ctx.derivedState,
145304
146188
  t,
145305
- adapters
146189
+ adapters,
146190
+ inboundReady: inboundState.ready
145306
146191
  });
145307
146192
  const acknowledgements = makeAcknowledgements(core);
145308
146193
  const failures = makeFailureNotices(core);
@@ -145338,7 +146223,9 @@ var imPlugin = definePlugin({
145338
146223
  queueFlow: interactions.queueFlow,
145339
146224
  rememberThreadProject: notifications.rememberThreadProject,
145340
146225
  noteInboundUserMessage: webSync.noteInboundUserMessage,
145341
- anchors: replyAnchors
146226
+ anchors: replyAnchors,
146227
+ failures,
146228
+ dispatchSleep
145342
146229
  });
145343
146230
  const sessions = makeSessions(core);
145344
146231
  const modelPlan = makeModelPlan(core, turns);
@@ -145353,6 +146240,7 @@ var imPlugin = definePlugin({
145353
146240
  });
145354
146241
  const chatSetup = makeChatSetup(core, {
145355
146242
  rows: projectRows,
146243
+ resumeMessage: (bot, message) => inbound.resumeMessage(bot, message),
145356
146244
  openPicker: (chatKey, rows, reply) => panelWiring.panel.openProjects(chatKey, rows, reply)
145357
146245
  });
145358
146246
  const commands = makeCommands(core, {
@@ -145365,10 +146253,10 @@ var imPlugin = definePlugin({
145365
146253
  openPanel: (chatKey, reply) => panelWiring.panel.open(chatKey, reply)
145366
146254
  });
145367
146255
  const inbound = makeInbound(core, {
146256
+ inboundState,
145368
146257
  enqueueForChat: chatQueue.enqueueForChat,
145369
146258
  commands,
145370
146259
  chatSetup,
145371
- rows: projectRows,
145372
146260
  interactions,
145373
146261
  approvals,
145374
146262
  panelWiring,
@@ -145394,6 +146282,7 @@ var imPlugin = definePlugin({
145394
146282
  ]
145395
146283
  });
145396
146284
  const connections = startConnections(core, inbound.inboundHandlersFor);
146285
+ registerSettingsRowActions(ctx, adapters, connections);
145397
146286
  registerSettingsCommands(ctx, adapters, updateSettings, connections);
145398
146287
  const { bots, connecting } = connections.reconcile();
145399
146288
  const deliveryHttp = makeDeliveryHttp({
@@ -145412,8 +146301,11 @@ var imPlugin = definePlugin({
145412
146301
  }
145413
146302
  ctx.log.info("chat plugin active", { bots, connecting });
145414
146303
  }
145415
- });
146304
+ };
146305
+ var activateImPlugin = definition.activate;
146306
+ var imPlugin = definePlugin(definition);
145416
146307
  var index_default = imPlugin;
145417
146308
  export {
146309
+ activateImPlugin,
145418
146310
  index_default as default
145419
146311
  };