@ycodium-ai/plugin-im 0.2.3208 → 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
@@ -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: createHash7 } = __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 = createHash7("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: createHash7 } = __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 = createHash7("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",
@@ -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
  };
@@ -127474,6 +127475,14 @@ var pickProject = (arg, listed, rows) => {
127474
127475
  return prefixed.length === 1 ? prefixed[0]?.projectId ?? null : null;
127475
127476
  };
127476
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
+
127477
127486
  // src/sessionList.ts
127478
127487
  var SESSION_LIST_MAX = 20;
127479
127488
  var isSessionRunning = (status) => status === "running";
@@ -127488,7 +127497,10 @@ var ageLabel = (updatedAt, now, t) => {
127488
127497
  if (hours < 24) return t("sessions.age.hours", { count: hours });
127489
127498
  return t("sessions.age.days", { count: Math.floor(hours / 24) });
127490
127499
  };
127491
- 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
+ );
127492
127504
  var sessionLine = (row, position, now, t) => {
127493
127505
  const title = row.title.replace(/\s+/g, " ").trim() || t("sessions.untitled");
127494
127506
  const age = ageLabel(row.updatedAt, now, t);
@@ -128130,30 +128142,36 @@ var startConnections = (core, inboundHandlersFor) => {
128130
128142
  }
128131
128143
  };
128132
128144
  };
128133
- const reconcile = () => {
128145
+ const plan = () => {
128134
128146
  const bots = disposed ? [] : core.bots();
128135
128147
  const wanted = /* @__PURE__ */ new Map();
128136
- const credentials3 = /* @__PURE__ */ new Map();
128148
+ const credentials2 = /* @__PURE__ */ new Map();
128137
128149
  const duplicates = /* @__PURE__ */ new Map();
128138
128150
  for (const bot of bots) {
128139
128151
  const adapter = adapterFor(bot.platform);
128140
128152
  if (adapter === void 0) continue;
128141
128153
  const fingerprint = `${bot.platform}:${adapter.columns.map((column) => bot.values[column.key] ?? "").join(" ")}`;
128142
- const duplicateOf = credentials3.get(fingerprint);
128154
+ const duplicateOf = credentials2.get(fingerprint);
128143
128155
  if (duplicateOf !== void 0) {
128144
128156
  duplicates.set(bot.rowId, duplicateOf);
128145
- if (statuses.get(bot.rowId)?.duplicateOf !== duplicateOf) {
128146
- record3(bot.rowId, { state: "duplicate", duplicateOf });
128147
- }
128148
- host.log.warn("chat did not connect a bot that repeats another bot's credentials", {
128149
- bot: bot.displayName,
128150
- platform: bot.platform
128151
- });
128152
128157
  continue;
128153
128158
  }
128154
- credentials3.set(fingerprint, bot.rowId);
128159
+ credentials2.set(fingerprint, bot.rowId);
128155
128160
  wanted.set(bot.rowId, { bot, key: connectionKey(adapter, bot) });
128156
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
+ }
128157
128175
  for (const [rowId, connection] of live) {
128158
128176
  if (wanted.get(rowId)?.key === connection.key) continue;
128159
128177
  connection.close();
@@ -128174,6 +128192,23 @@ var startConnections = (core, inboundHandlersFor) => {
128174
128192
  });
128175
128193
  return {
128176
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
+ },
128177
128212
  subscribe: (listener) => {
128178
128213
  if (!disposed) listeners.add(listener);
128179
128214
  listener(Object.fromEntries(statuses));
@@ -128982,6 +129017,25 @@ var makeInboundAttachments = (core) => {
128982
129017
 
128983
129018
  // src/inbound.ts
128984
129019
  import { projectOrNull } from "@ycodium-ai/plugin-api";
129020
+
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
+ });
129035
+ };
129036
+ };
129037
+
129038
+ // src/inbound.ts
128985
129039
  var ARCHIVED_REPLY_COOLDOWN_MS = 10 * 6e4;
128986
129040
  var UNSUPPORTED_REPLY_COOLDOWN_MS = 6e4;
128987
129041
  var NOT_ALLOWED_REPLY_COOLDOWN_MS = 10 * 6e4;
@@ -128989,6 +129043,8 @@ var NOT_ALLOWED_REPLY_KEY_LIMIT = 1e3;
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
129049
  const { prepareMessage } = deps.chatSetup;
128994
129050
  const { questionFlow, handleButtonClick } = deps.interactions;
@@ -128997,11 +129053,11 @@ var makeInbound = (core, deps) => {
128997
129053
  const { startTurn, receiveAttachmentsOnly, hasWaitingAttachments } = deps.turns;
128998
129054
  const { startTeamGoal } = deps.teams;
128999
129055
  const { markWorking } = deps.acknowledgements;
129000
- const archivedReplyCooldown = makeCooldown(ARCHIVED_REPLY_COOLDOWN_MS, () => Date.now());
129001
- 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);
129002
129058
  const notAllowedReplyCooldown = makeCooldown(
129003
129059
  NOT_ALLOWED_REPLY_COOLDOWN_MS,
129004
- () => Date.now(),
129060
+ now,
129005
129061
  NOT_ALLOWED_REPLY_KEY_LIMIT
129006
129062
  );
129007
129063
  const { inboundState } = deps;
@@ -129037,6 +129093,7 @@ var makeInbound = (core, deps) => {
129037
129093
  commit("chat-aliased", { rowId: bot.rowId, alias, chatId });
129038
129094
  };
129039
129095
  const settleChat = (bot, message) => {
129096
+ message = { ...message, originalChatId: message.originalChatId ?? message.chatId };
129040
129097
  if (message.chatAlias === void 0) return message;
129041
129098
  const known = chatAliasOf(core.state, bot.rowId, message.chatAlias);
129042
129099
  if (known === void 0) noteChatAlias(bot, message.chatAlias, message.chatId);
@@ -129118,6 +129175,7 @@ var makeInbound = (core, deps) => {
129118
129175
  },
129119
129176
  noteChatAlias: (alias, chatId) => noteChatAlias(bot, alias, chatId),
129120
129177
  knowsChatAlias: (alias) => chatAliasOf(core.state, bot.rowId, alias) !== void 0,
129178
+ onGreeting: (chatId, senderId) => greet(bot.rowId, chatId, senderId),
129121
129179
  onMessage: (received) => {
129122
129180
  const current = currentBot(bot.rowId);
129123
129181
  if (current === void 0) return;
@@ -129137,7 +129195,7 @@ var makeInbound = (core, deps) => {
129137
129195
  if (!unsupportedReplyCooldown.tryAcquire(chatKeyOf(arrived))) return;
129138
129196
  await deliver(
129139
129197
  arrived,
129140
- 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 })
129141
129199
  );
129142
129200
  });
129143
129201
  return;
@@ -130038,6 +130096,8 @@ var consolePath = {
130038
130096
  var APP_EVENTS = ["im.message.receive_v1"];
130039
130097
  var CARD_CALLBACKS = ["card.action.trigger"];
130040
130098
  var SCOPES = [
130099
+ // 让「补全权限」查询应用自身已开通的权限,并列出缺项。
130100
+ "application:application:self_manage",
130041
130101
  // 收发消息、改卡片、读消息。
130042
130102
  "im:message",
130043
130103
  // 群里 @ 机器人的消息(入站的主路径)。
@@ -130062,7 +130122,7 @@ var SCOPES = [
130062
130122
  // 「在弄了」表情回执(`larkApi.ts` 的 `addReaction`)。unconfirmed
130063
130123
  "im:message.reactions:write_only"
130064
130124
  ];
130065
- var isTenantScope = (name) => name.startsWith("im:");
130125
+ var isTenantScope = (name) => name.startsWith("im:") || name === "application:application:self_manage";
130066
130126
  var appMarker = (environmentId) => `[ycodium:${environmentId.slice(0, 8)}]`;
130067
130127
  var DEFAULT_APP_NAME = "Ycodium Agent";
130068
130128
  var DEFAULT_APP_DESCRIPTION = "Ycodium chat bot";
@@ -130113,6 +130173,16 @@ var makeFlowItemSchema = (shape) => ({
130113
130173
  },
130114
130174
  fills: shape.fills,
130115
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
+ },
130116
130186
  warnings: {
130117
130187
  type: "array",
130118
130188
  items: {
@@ -130126,6 +130196,7 @@ var makeFlowItemSchema = (shape) => ({
130126
130196
  }
130127
130197
  },
130128
130198
  reasonKey: { type: "string" },
130199
+ noteKey: { type: "string" },
130129
130200
  detail: { type: "string" }
130130
130201
  },
130131
130202
  oneOf: [
@@ -131769,8 +131840,8 @@ var resourceOf = (element, index) => {
131769
131840
  }
131770
131841
  return null;
131771
131842
  };
131772
- var placeholderOf = (element, index) => `<${mediaOf(element)} ${String(index)}>`;
131773
- var serializeElement = (element, index, botIds) => {
131843
+ var placeholderOf = (resource, index) => `<${resource.media} ${String(index)}>`;
131844
+ var serializeElement = (element, resourceText, botIds) => {
131774
131845
  switch (element.tag) {
131775
131846
  case "text":
131776
131847
  return styledText(element.text ?? "", element.style);
@@ -131793,12 +131864,12 @@ ${element.text ?? ""}
131793
131864
  case "img":
131794
131865
  case "media":
131795
131866
  case "file":
131796
- return index === null ? "" : placeholderOf(element, index);
131867
+ return resourceText ?? "";
131797
131868
  default:
131798
131869
  return "";
131799
131870
  }
131800
131871
  };
131801
- var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
131872
+ var serializePost = (content, botIds = /* @__PURE__ */ new Set(), renderResource = placeholderOf) => {
131802
131873
  let parsed;
131803
131874
  try {
131804
131875
  parsed = JSON.parse(content);
@@ -131807,31 +131878,31 @@ var serializePost = (content, botIds = /* @__PURE__ */ new Set()) => {
131807
131878
  }
131808
131879
  const elements = (parsed.content ?? []).flat();
131809
131880
  const resources = [];
131810
- const indexOf = /* @__PURE__ */ new Map();
131881
+ const resourceTextOf = /* @__PURE__ */ new Map();
131811
131882
  for (const element of elements) {
131812
131883
  const resource = resourceOf(element, resources.length + 1);
131813
131884
  if (resource === null) continue;
131814
131885
  resources.push(resource);
131815
- indexOf.set(element, resources.length);
131886
+ resourceTextOf.set(element, renderResource(resource, resources.length));
131816
131887
  }
131817
131888
  const md2 = parsed.md;
131818
131889
  if (typeof md2 === "string" && md2.trim() !== "") {
131819
- const placeholderByKey = /* @__PURE__ */ new Map();
131890
+ const resourceTextByKey = /* @__PURE__ */ new Map();
131820
131891
  for (const element of elements) {
131821
- const index = indexOf.get(element);
131892
+ const resourceText = resourceTextOf.get(element);
131822
131893
  const key = element.tag === "img" ? element.image_key : element.file_key;
131823
- if (index !== void 0 && nonEmpty4(key)) {
131824
- placeholderByKey.set(key, placeholderOf(element, index));
131894
+ if (resourceText !== void 0 && nonEmpty4(key)) {
131895
+ resourceTextByKey.set(key, resourceText);
131825
131896
  }
131826
131897
  }
131827
131898
  const text5 = md2.replace(
131828
131899
  /!\[(?:img|video|file)\]\(([^)]+)\)/g,
131829
- (whole, rawKey) => placeholderByKey.get(rawKey.trim()) ?? whole
131900
+ (whole, rawKey) => resourceTextByKey.get(rawKey.trim()) ?? whole
131830
131901
  );
131831
131902
  return { text: text5, resources };
131832
131903
  }
131833
131904
  const paragraphs = (parsed.content ?? []).map(
131834
- (row) => row.map((element) => serializeElement(element, indexOf.get(element) ?? null, botIds)).join("")
131905
+ (row) => row.map((element) => serializeElement(element, resourceTextOf.get(element), botIds)).join("")
131835
131906
  );
131836
131907
  const text4 = paragraphs.map((line) => line.trim()).filter((line) => line !== "").join("\n");
131837
131908
  if (text4 === "" && resources.length === 0) return void 0;
@@ -131972,6 +132043,7 @@ var LARK_TEXT_MAX_CHARS = 3500;
131972
132043
  var LarkApiError = class extends Error {
131973
132044
  operation;
131974
132045
  code;
132046
+ msg;
131975
132047
  status;
131976
132048
  retryAfterMs;
131977
132049
  constructor(operation, code2, message, http = {}) {
@@ -131979,11 +132051,13 @@ var LarkApiError = class extends Error {
131979
132051
  this.name = "LarkApiError";
131980
132052
  this.operation = operation;
131981
132053
  this.code = code2;
132054
+ this.msg = message;
131982
132055
  this.status = http.status;
131983
132056
  this.retryAfterMs = http.retryAfterMs;
131984
132057
  }
131985
132058
  };
131986
- 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);
131987
132061
  var LARK_RATE_LIMITED = 99991400;
131988
132062
  var isReplyRejected = (error) => error instanceof LarkApiError && error.status === void 0 && error.code !== LARK_RATE_LIMITED;
131989
132063
  var larkSendVerdict = (error) => {
@@ -131998,14 +132072,20 @@ var larkSendVerdict = (error) => {
131998
132072
  };
131999
132073
  var readEnvelope = async (operation, response) => {
132000
132074
  if (!response.ok) {
132001
- await response.body?.cancel().catch(() => void 0);
132002
- throw new LarkApiError(operation, void 0, `HTTP ${String(response.status)}`, {
132003
- status: response.status,
132004
- retryAfterMs: parseRetryAfterMs(
132005
- response.headers.get("retry-after") ?? response.headers.get("x-ogw-ratelimit-reset"),
132006
- Date.now()
132007
- ) ?? void 0
132008
- });
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
+ );
132009
132089
  }
132010
132090
  const body = await response.json();
132011
132091
  const envelope = body;
@@ -132026,6 +132106,19 @@ var fetchTenantToken = async (input) => {
132026
132106
  }
132027
132107
  return { token: body.tenant_access_token, expiresInSeconds: body.expire ?? 0 };
132028
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
+ };
132029
132122
  var postMessage = async (input) => {
132030
132123
  const headers = {
132031
132124
  authorization: `Bearer ${input.token}`,
@@ -132063,16 +132156,31 @@ var postMessage = async (input) => {
132063
132156
  });
132064
132157
  return sent?.message_id ?? null;
132065
132158
  };
132066
- var readMessage = async (input) => {
132159
+ var readMessageItems = async (input) => {
132067
132160
  const response = await input.http(
132068
- `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}`,
132161
+ `${LARK_BASE}/im/v1/messages/${encodeURIComponent(input.messageId)}${input.withSenderName ? "?with_sender_name=true" : ""}`,
132069
132162
  {
132070
132163
  headers: { authorization: `Bearer ${input.token}` },
132071
132164
  ...input.signal === void 0 ? {} : { signal: input.signal }
132072
132165
  }
132073
132166
  );
132074
- const body = await readEnvelope("message.get", response);
132075
- 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;
132076
132184
  };
132077
132185
  var fetchMessageThreadId = async (input) => (await readMessage(input))?.thread_id || void 0;
132078
132186
  var fetchMessageReference = async (input) => {
@@ -137646,6 +137754,50 @@ var buildChoiceCard = (card) => {
137646
137754
 
137647
137755
  // src/larkInbound.ts
137648
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
137649
137801
  var isOwnMention = (mention, appId) => mention.id?.app_id === appId || mention.mentioned_type === "bot";
137650
137802
  var mentionsApp = (event, appId) => (event.message?.mentions ?? []).some((mention) => isOwnMention(mention, appId));
137651
137803
  var mentionsSomeoneElse = (event, appId) => (event.message?.mentions ?? []).some(
@@ -137659,12 +137811,12 @@ var passesGroupGate = (event, appId, groupMention, messageType) => {
137659
137811
  };
137660
137812
  var RESOURCE_MESSAGE_TYPES = /* @__PURE__ */ new Set(["image", "file", "audio", "media"]);
137661
137813
  var UNSUPPORTED_MESSAGE_TYPES = /* @__PURE__ */ new Set([
137814
+ "audio",
137662
137815
  "sticker",
137663
137816
  "folder",
137664
137817
  "share_chat",
137665
137818
  "share_user",
137666
137819
  "system",
137667
- "merge_forward",
137668
137820
  "interactive"
137669
137821
  ]);
137670
137822
  var botIdsOf = (mentions, appId) => {
@@ -137702,10 +137854,6 @@ var readResource = (messageType, content) => {
137702
137854
  const name = key("file_name") ?? "file";
137703
137855
  return { fileKey, kind: "file", name, mimeType: mimeFromName(name) };
137704
137856
  }
137705
- if (messageType === "audio") {
137706
- const fileKey = key("file_key");
137707
- return fileKey === null ? null : { fileKey, kind: "file", name: "voice.opus", mimeType: "audio/ogg" };
137708
- }
137709
137857
  if (messageType === "media") {
137710
137858
  const fileKey = key("file_key");
137711
137859
  if (fileKey === null) return null;
@@ -137734,7 +137882,11 @@ var parseInboundMessage = (event, appId, groupMention = "always", topicOnMention
137734
137882
  const rootGiven = typeof message.root_id === "string" && message.root_id !== "";
137735
137883
  const rootId = rootGiven && message.root_id !== void 0 ? message.root_id : messageId;
137736
137884
  const topic = chatType === "p2p" || !inThread ? {} : threadId === void 0 ? { topic: { rootMessageId: messageId } } : { topic: { rootMessageId: rootId, threadId, rootGiven } };
137737
- 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)) {
137738
137890
  return {
137739
137891
  messageId,
137740
137892
  chatId,
@@ -137744,7 +137896,7 @@ var parseInboundMessage = (event, appId, groupMention = "always", topicOnMention
137744
137896
  resources: [],
137745
137897
  inThread,
137746
137898
  ...topic,
137747
- unsupportedType: messageType
137899
+ ...messageType === "merge_forward" ? { mergeForward: true, ...parentMessageId ? { parentMessageId } : {} } : { unsupportedType: messageType === "audio" ? "voice" : messageType }
137748
137900
  };
137749
137901
  }
137750
137902
  const mentions = message.mentions ?? [];
@@ -137836,7 +137988,6 @@ var cardActionOperator = (event) => {
137836
137988
  }
137837
137989
  return null;
137838
137990
  };
137839
- var isLarkAppId = (appId) => /^cli_[0-9a-fA-F]{16}$/u.test(appId);
137840
137991
  var startLarkInbound = (input) => {
137841
137992
  const dispatcher = new import_node_sdk.EventDispatcher({}).register({
137842
137993
  "im.message.receive_v1": (event) => {
@@ -137879,7 +138030,11 @@ var startLarkInbound = (input) => {
137879
138030
  }
137880
138031
  });
137881
138032
  if (!isLarkAppId(input.appId)) {
137882
- input.onConnectionState?.({ state: "failed", reason: "invalid-app-id" });
138033
+ input.onConnectionState?.({
138034
+ state: "failed",
138035
+ reason: "invalid-app-id",
138036
+ hint: "larkCredentials"
138037
+ });
137883
138038
  return { close: () => void 0 };
137884
138039
  }
137885
138040
  let closed = false;
@@ -137888,7 +138043,13 @@ var startLarkInbound = (input) => {
137888
138043
  };
137889
138044
  const failed = (error) => {
137890
138045
  if (!closed) {
137891
- 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
+ });
137892
138053
  }
137893
138054
  };
137894
138055
  const client = new import_node_sdk.WSClient({
@@ -138124,15 +138285,157 @@ var makeLarkMessageContext = (deps) => {
138124
138285
  if (message.chatType !== "group" || message.senderOpenId === null) return {};
138125
138286
  const name = contactDenied ? null : userName(message.senderOpenId);
138126
138287
  const chat = chatInfo(message.chatId);
138127
- const fields = (senderName, info) => ({
138128
- ...senderName ? { senderName } : {},
138288
+ const fields = (senderName2, info) => ({
138289
+ ...senderName2 ? { senderName: senderName2 } : {},
138129
138290
  ...info?.name ? { chatTitle: info.name } : {}
138130
138291
  });
138131
- 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);
138132
138293
  };
138133
138294
  return { chatInfo, forMessage, loadReference, topicRootForSession };
138134
138295
  };
138135
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
+
138136
138439
  // src/larkAdapter.ts
138137
138440
  var TOKEN_CACHE_MAX_AGE_MS = 30 * 6e4;
138138
138441
  var COLUMNS = [
@@ -138164,21 +138467,9 @@ var splitTopicChatId = (chatId) => {
138164
138467
  return at < 0 ? { chatId } : { chatId: chatId.slice(0, at), rootMessageId: chatId.slice(at + 1) };
138165
138468
  };
138166
138469
  var topicAliasOf = (chatId, threadId) => `${chatId}:${threadId}`;
138167
- var credentials = (binding) => ({
138168
- appId: binding.values["larkAppId"] ?? "",
138169
- appSecret: binding.values["larkAppSecret"] ?? ""
138170
- });
138171
- var larkConfigIssues = (binding) => {
138172
- const { appId, appSecret } = credentials(binding);
138173
- const issues = [];
138174
- if (appId === "") issues.push({ column: "larkAppId", code: "required" });
138175
- else if (!isLarkAppId(appId)) issues.push({ column: "larkAppId", code: "format" });
138176
- if (appSecret === "") issues.push({ column: "larkAppSecret", code: "required" });
138177
- return issues;
138178
- };
138179
138470
  var makeLarkAdapter = (deps) => {
138180
138471
  const tenantToken = async (binding) => {
138181
- const { appId, appSecret } = credentials(binding);
138472
+ const { appId, appSecret } = larkCredentials(binding);
138182
138473
  const key = `lark-tenant-token.${appId}`;
138183
138474
  const cached = await deps.cacheGet(key, TOKEN_CACHE_MAX_AGE_MS);
138184
138475
  if (typeof cached?.token === "string") return cached.token;
@@ -138251,6 +138542,7 @@ var makeLarkAdapter = (deps) => {
138251
138542
  };
138252
138543
  return {
138253
138544
  platform: "lark",
138545
+ botIdentity: (bot) => larkCredentials(bot).appId,
138254
138546
  icon: "lark",
138255
138547
  label: "Lark",
138256
138548
  columns: COLUMNS,
@@ -138259,10 +138551,10 @@ var makeLarkAdapter = (deps) => {
138259
138551
  const { chatId: groupId, rootMessageId } = splitTopicChatId(chatId);
138260
138552
  return rootMessageId === void 0 ? void 0 : groupId;
138261
138553
  },
138262
- validate: larkConfigIssues,
138263
- 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"),
138264
138556
  connect: (binding, handlers) => {
138265
- const { appId, appSecret } = credentials(binding);
138557
+ const { appId, appSecret } = larkCredentials(binding);
138266
138558
  const port = {
138267
138559
  noteChatAlias: handlers.noteChatAlias,
138268
138560
  knowsChatAlias: handlers.knowsChatAlias
@@ -138299,11 +138591,17 @@ var makeLarkAdapter = (deps) => {
138299
138591
  arg: click.arg
138300
138592
  }) ?? null,
138301
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;
138302
138600
  const { topic } = message;
138303
138601
  const source = context.forMessage(message);
138304
138602
  const parentMessageId = message.parentMessageId;
138305
138603
  const reference2 = parentMessageId === void 0 ? null : context.loadReference(parentMessageId);
138306
- const deliver = (names, replyTo) => handlers.onMessage({
138604
+ const deliver = (ready, names, replyTo) => handlers.onMessage({
138307
138605
  messageId: message.messageId,
138308
138606
  // 已经开出来的话题就是它自己那个聊天;将要开出来的(主窗口里 @ 机器人)先算群的,
138309
138607
  // 由核心决定要不要分出去。
@@ -138317,7 +138615,7 @@ var makeLarkAdapter = (deps) => {
138317
138615
  },
138318
138616
  senderId: message.senderOpenId,
138319
138617
  ...names,
138320
- text: message.text,
138618
+ text: ready.text,
138321
138619
  topicRootForSession: context.topicRootForSession(message),
138322
138620
  ...replyTo === void 0 ? {} : { replyTo },
138323
138621
  chatKind: message.chatType === "p2p" ? "private" : "group",
@@ -138328,7 +138626,7 @@ var makeLarkAdapter = (deps) => {
138328
138626
  mimeType: resource.mimeType,
138329
138627
  kind: resource.kind
138330
138628
  })),
138331
- ...message.unsupportedType === void 0 ? {} : { unsupportedType: message.unsupportedType }
138629
+ ...ready.unsupportedType === void 0 ? {} : { unsupportedType: ready.unsupportedType }
138332
138630
  });
138333
138631
  const pending = topic?.threadId === void 0 ? null : topicUnsettled(binding.rowId, message.chatId, {
138334
138632
  threadId: topic.threadId,
@@ -138336,12 +138634,12 @@ var makeLarkAdapter = (deps) => {
138336
138634
  });
138337
138635
  const key = JSON.stringify([message.chatId, topic?.threadId]);
138338
138636
  const earlier = pendingDeliveries.get(key);
138339
- if (earlier === void 0 && pending === null && reference2 === null && !(source instanceof Promise)) {
138340
- deliver(source);
138637
+ if (earlier === void 0 && pending === null && reference2 === null && !(prepared instanceof Promise) && !(source instanceof Promise)) {
138638
+ deliver(prepared, source);
138341
138639
  } else {
138342
- 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]) => {
138343
138641
  if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
138344
- if (open) deliver(names, replyTo ?? void 0);
138642
+ if (open) deliver(ready, names, replyTo ?? void 0);
138345
138643
  }).catch((error) => handlers.onError(describeError(error))).finally(() => {
138346
138644
  if (pendingDeliveries.get(key) === completed) pendingDeliveries.delete(key);
138347
138645
  });
@@ -138380,17 +138678,15 @@ var makeLarkAdapter = (deps) => {
138380
138678
  }
138381
138679
  },
138382
138680
  checkConnection: async (bot) => {
138383
- const { appId, appSecret } = credentials(bot);
138384
- if (appId === "" || appSecret === "") {
138681
+ const { appId, appSecret, error: credentialError } = larkCredentials(bot);
138682
+ if (credentialError !== void 0) {
138385
138683
  return {
138386
138684
  stage: "credentials",
138387
138685
  ok: false,
138388
- detail: "missing app id or secret in saved config"
138686
+ detail: credentialError,
138687
+ hint: "larkCredentials"
138389
138688
  };
138390
138689
  }
138391
- if (!isLarkAppId(appId)) {
138392
- return { stage: "credentials", ok: false, detail: "malformed app id in saved config" };
138393
- }
138394
138690
  try {
138395
138691
  const { expiresInSeconds } = await fetchTenantToken({ http: deps.http, appId, appSecret });
138396
138692
  return {
@@ -138399,7 +138695,13 @@ var makeLarkAdapter = (deps) => {
138399
138695
  detail: `token ok; expiresIn=${String(expiresInSeconds)}s`
138400
138696
  };
138401
138697
  } catch (error) {
138402
- 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
+ };
138403
138705
  }
138404
138706
  },
138405
138707
  sendImage: async (binding, image2) => {
@@ -138737,6 +139039,7 @@ var en_default = {
138737
139039
  projectArchived: 'Project "{{title}}" is archived. Restore it, or send /repo to pick another project, then message again.',
138738
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.",
138739
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}}.",
138740
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.",
138741
139044
  turnFailedNoDetail: "This turn didn't finish. Reference {{ref}}. Send the message again; if it keeps failing, send /new to start a new conversation.",
138742
139045
  turnFailedBrief: "This turn didn't finish: {{detail}}\nSend the message again; if it keeps failing, send /new to start a new conversation.",
@@ -138753,9 +139056,11 @@ var en_default = {
138753
139056
  attachmentsNoTeam: "Files cannot be sent to a team yet, so they were left out. Send /thread to go back to a normal session.",
138754
139057
  unsupportedType: "I can't process {{type}} messages yet.",
138755
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.",
138756
139060
  sendingFile: "Sending {{name}}:",
138757
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.`,
138758
- 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."
138759
139064
  },
138760
139065
  approval: {
138761
139066
  allow: "Allow",
@@ -139090,21 +139395,41 @@ var en_default = {
139090
139395
  failed: "Connection failed",
139091
139396
  notSaved: "Save this row first (credentials are read from saved config only).",
139092
139397
  hint: {
139398
+ connecting: "Still connecting. There is usually a result within 20 seconds; check again in a moment.",
139093
139399
  badToken: "This does not look like a bot token. Copy it again from BotFather: digits, a colon, then the key.",
139094
- 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.",
139095
139401
  notBot: "This token does not belong to a bot. Use the token BotFather gave you.",
139096
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.",
139097
139410
  wecomTrustedIp: "Add this machine's outbound public IP to the app's trusted IP list in the WeCom admin console.",
139098
139411
  wecomInvalidUser: "Check that the recipient, department or tag exists and is in this app's visible range.",
139099
- 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."
139100
139415
  }
139101
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
+ },
139102
139428
  copyDiagnostics: {
139103
139429
  label: "Copy diagnostics",
139104
139430
  description: "Run a connection probe and produce copy-paste diagnostics (no secrets).",
139105
139431
  running: "Building diagnostics for {{rowId}}\u2026",
139106
- done: "Diagnostics ready \u2014 see details below",
139107
- failed: "Diagnostics failed",
139432
+ done: "Diagnostic report ready \u2014 see details below",
139108
139433
  notSaved: "Save this row first before copying diagnostics."
139109
139434
  },
139110
139435
  diagnostic: {
@@ -139112,16 +139437,23 @@ var en_default = {
139112
139437
  },
139113
139438
  larkRepair: {
139114
139439
  label: "Repair scopes",
139115
- 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.",
139116
139441
  step: {
139117
- scopes: "Scopes \u2014 not checked",
139118
- events: "Event subscriptions \u2014 not checked",
139119
- publish: "Publishing \u2014 not checked",
139120
- verify: "Verify the app can still mint a tenant token"
139121
- },
139122
- done: "Only token issuance was verified. Scopes, event subscriptions and publishing were not checked automatically; review them on the Feishu open platform.",
139123
- hint: "Reference: {{ref}}\n{{detail}}\nReview scopes, event subscriptions and publishing on the Feishu open platform; none were checked automatically.",
139124
- 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.",
139125
139457
  notLark: "This row is not a saved Lark bot."
139126
139458
  }
139127
139459
  },
@@ -139168,11 +139500,13 @@ var en_default = {
139168
139500
  connection: {
139169
139501
  failed: "The last connection attempt failed.",
139170
139502
  details: "Details: {{error}}",
139503
+ reconnect: "You can try again with Reconnect below.",
139171
139504
  reason: {
139172
139505
  "not-available": "This platform can't be connected yet.",
139173
139506
  "invalid-app-id": "The App ID isn't in the right format.",
139174
139507
  disconnected: "The connection to the platform is down.",
139175
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.",
139176
139510
  "token-unavailable": "Couldn't get an access token.",
139177
139511
  "credentials-rejected": "The platform rejected the credentials."
139178
139512
  },
@@ -139196,7 +139530,7 @@ var en_default = {
139196
139530
  done: "Done.",
139197
139531
  close: "Close",
139198
139532
  failedToRun: "Couldn't run this on the machine.",
139199
- 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.",
139200
139534
  qrTitle: "QR code to scan with the Lark app"
139201
139535
  },
139202
139536
  step: {
@@ -139222,6 +139556,24 @@ var en_default = {
139222
139556
  callbackFollowsKeys: "This URL changes when Token or EncodingAESKey changes. Paste it into WeCom again after changing them."
139223
139557
  }
139224
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
+ },
139225
139577
  team: {
139226
139578
  noRoster: "This project has no team roster yet. Save a roster in the Teams tab and choose a lead first.",
139227
139579
  unavailable: "Could not read this project's team roster. Check that the Teams plugin is enabled and try again.",
@@ -139440,6 +139792,7 @@ var zh_CN_default = {
139440
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",
139441
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",
139442
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",
139443
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",
139444
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",
139445
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",
@@ -139456,9 +139809,11 @@ var zh_CN_default = {
139456
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",
139457
139810
  unsupportedType: "\u6211\u8FD8\u4E0D\u80FD\u5904\u7406 {{type}} \u7C7B\u578B\u7684\u6D88\u606F\u3002",
139458
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",
139459
139813
  sendingFile: "\u6B63\u5728\u53D1\u9001 {{name}}\uFF1A",
139460
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",
139461
- 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"
139462
139817
  },
139463
139818
  approval: {
139464
139819
  allow: "\u5141\u8BB8",
@@ -139793,21 +140148,41 @@ var zh_CN_default = {
139793
140148
  failed: "\u8FDE\u63A5\u5931\u8D25",
139794
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",
139795
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",
139796
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",
139797
- 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",
139798
140154
  notBot: "\u8FD9\u4E2A token \u4E0D\u662F\u673A\u5668\u4EBA\u7684\u3002\u8BF7\u7528 BotFather \u7ED9\u7684\u90A3\u4E00\u4EFD\u3002",
139799
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",
139800
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",
139801
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",
139802
- 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"
139803
140168
  }
139804
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
+ },
139805
140181
  copyDiagnostics: {
139806
140182
  label: "\u590D\u5236\u8BCA\u65AD",
139807
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",
139808
140184
  running: "\u6B63\u5728\u751F\u6210\u8BCA\u65AD {{rowId}} \u2026",
139809
- done: "\u8BCA\u65AD\u5DF2\u751F\u6210\uFF0C\u89C1\u4E0B\u65B9\u8BE6\u60C5",
139810
- failed: "\u8BCA\u65AD\u5931\u8D25",
140185
+ done: "\u8BCA\u65AD\u62A5\u544A\u5DF2\u751F\u6210\uFF0C\u89C1\u4E0B\u65B9\u8BE6\u60C5",
139811
140186
  notSaved: "\u8BF7\u5148\u4FDD\u5B58\u8FD9\u4E00\u884C\u518D\u590D\u5236\u8BCA\u65AD\u3002"
139812
140187
  },
139813
140188
  diagnostic: {
@@ -139815,16 +140190,23 @@ var zh_CN_default = {
139815
140190
  },
139816
140191
  larkRepair: {
139817
140192
  label: "\u8865\u5168\u6743\u9650",
139818
- 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",
139819
140194
  step: {
139820
- scopes: "\u6743\u9650\uFF1A\u672A\u68C0\u67E5",
139821
- events: "\u4E8B\u4EF6\u8BA2\u9605\uFF1A\u672A\u68C0\u67E5",
139822
- publish: "\u53D1\u5E03\uFF1A\u672A\u68C0\u67E5",
139823
- verify: "\u9A8C\u8BC1\u5E94\u7528\u4ECD\u53EF\u6362\u53D6 tenant token"
139824
- },
139825
- 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",
139826
- 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",
139827
- 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",
139828
140210
  notLark: "\u8FD9\u4E00\u884C\u4E0D\u662F\u5DF2\u4FDD\u5B58\u7684\u98DE\u4E66\u673A\u5668\u4EBA\u3002"
139829
140211
  }
139830
140212
  },
@@ -139871,11 +140253,13 @@ var zh_CN_default = {
139871
140253
  connection: {
139872
140254
  failed: "\u4E0A\u6B21\u8FDE\u63A5\u6CA1\u6709\u6210\u529F\u3002",
139873
140255
  details: "\u8BE6\u60C5\uFF1A{{error}}",
140256
+ reconnect: "\u53EF\u4EE5\u70B9\u4E0B\u65B9\u7684\u300C\u91CD\u65B0\u8FDE\u63A5\u300D\u518D\u8BD5\u3002",
139874
140257
  reason: {
139875
140258
  "not-available": "\u8FD9\u4E2A\u5E73\u53F0\u8FD8\u4E0D\u80FD\u8FDE\u63A5\u3002",
139876
140259
  "invalid-app-id": "App ID \u683C\u5F0F\u4E0D\u6B63\u786E\u3002",
139877
140260
  disconnected: "\u548C\u5E73\u53F0\u7684\u8FDE\u63A5\u65AD\u4E86\u3002",
139878
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",
139879
140263
  "token-unavailable": "\u6CA1\u80FD\u6362\u5230\u8BBF\u95EE\u4EE4\u724C\u3002",
139880
140264
  "credentials-rejected": "\u5E73\u53F0\u62D2\u7EDD\u4E86\u51ED\u636E\u3002"
139881
140265
  },
@@ -139899,7 +140283,7 @@ var zh_CN_default = {
139899
140283
  done: "\u5B8C\u6210\u3002",
139900
140284
  close: "\u5173\u95ED",
139901
140285
  failedToRun: "\u6CA1\u80FD\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u8FD0\u884C\u3002",
139902
- 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",
139903
140287
  qrTitle: "\u7528\u98DE\u4E66 App \u626B\u7684\u4E8C\u7EF4\u7801"
139904
140288
  },
139905
140289
  step: {
@@ -139925,6 +140309,24 @@ var zh_CN_default = {
139925
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"
139926
140310
  }
139927
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
+ },
139928
140330
  team: {
139929
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",
139930
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",
@@ -140390,7 +140792,7 @@ var PANEL_PAGE_SIZE = 8;
140390
140792
  var PANEL_CARD_LIMIT = 200;
140391
140793
  var LABEL_MAX_CHARS = 60;
140392
140794
  var PICK_KINDS = /* @__PURE__ */ new Set(["sessions", "models", "projects"]);
140393
- var clip2 = (label) => {
140795
+ var clip3 = (label) => {
140394
140796
  const chars = Array.from(label);
140395
140797
  return chars.length <= LABEL_MAX_CHARS ? label : `${chars.slice(0, LABEL_MAX_CHARS - 1).join("")}\u2026`;
140396
140798
  };
@@ -140415,7 +140817,7 @@ var makePanelController = (deps) => {
140415
140817
  }
140416
140818
  };
140417
140819
  const button = (label, op, arg, danger) => ({
140418
- label: clip2(label),
140820
+ label: clip3(label),
140419
140821
  action: { op, arg },
140420
140822
  ...danger === true ? { danger } : {}
140421
140823
  });
@@ -140834,6 +141236,20 @@ var makeFailureNotices = (core) => {
140834
141236
  const heldForCard = /* @__PURE__ */ new Map();
140835
141237
  const reasonOf2 = (detail) => detail?.trim() ?? "";
140836
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
+ },
140837
141253
  /** 一次失败通知的那句话(带新参考号,同号进日志):实时失败事件与重启补发各取一次。 */
140838
141254
  forFailure: (threadId, detail) => {
140839
141255
  const reference2 = diagnosticRef("msg");
@@ -141686,6 +142102,35 @@ var makeTeamGoals = (core) => {
141686
142102
  // src/telegramAdapter.ts
141687
142103
  import * as NodeCrypto5 from "node:crypto";
141688
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
+
141689
142134
  // src/telegramApi.ts
141690
142135
  var API_BASE = "https://api.telegram.org";
141691
142136
  var POLL_TIMEOUT_SECONDS = 45;
@@ -141715,17 +142160,6 @@ var replyParameters = (messageId) => {
141715
142160
  return messageId === void 0 || !Number.isSafeInteger(id) ? {} : { reply_parameters: { message_id: id, allow_sending_without_reply: true } };
141716
142161
  };
141717
142162
  var chatOnly = (chatId) => ({ chat_id: splitChatId(chatId).chat });
141718
- var TelegramApiError = class extends Error {
141719
- code;
141720
- /** 限流(429)时平台给的等待秒数;别的错误没有。 */
141721
- retryAfterSeconds;
141722
- constructor(method, code2, description, retryAfterSeconds) {
141723
- super(`telegram ${method} failed (${String(code2 ?? "?")}): ${description}`);
141724
- this.name = "TelegramApiError";
141725
- this.code = code2;
141726
- this.retryAfterSeconds = retryAfterSeconds;
141727
- }
141728
- };
141729
142163
  var telegramSendVerdict = (error) => error instanceof TelegramApiError && (error.code === 429 || error.code === 503 && error.retryAfterSeconds !== void 0) ? {
141730
142164
  retry: true,
141731
142165
  afterMs: error.retryAfterSeconds === void 0 ? void 0 : error.retryAfterSeconds * 1e3
@@ -141760,7 +142194,6 @@ var resourceOf2 = (message) => {
141760
142194
  };
141761
142195
  }
141762
142196
  const media = [
141763
- [message.voice, "voice.ogg", "audio/ogg"],
141764
142197
  [message.audio, "audio", "application/octet-stream"],
141765
142198
  [message.video, "video.mp4", "video/mp4"],
141766
142199
  [message.video_note, "video-note.mp4", "video/mp4"],
@@ -141780,7 +142213,15 @@ var resourceOf2 = (message) => {
141780
142213
  return null;
141781
142214
  };
141782
142215
  var unsupportedTypeOf = (message) => {
141783
- 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
+ ]) {
141784
142225
  if (message[type] !== void 0) return type;
141785
142226
  }
141786
142227
  return null;
@@ -141834,16 +142275,7 @@ var makeTelegramApi = (input) => {
141834
142275
  },
141835
142276
  timeoutMs
141836
142277
  );
141837
- const envelope = await response.json();
141838
- if (envelope.ok !== true) {
141839
- throw new TelegramApiError(
141840
- method,
141841
- envelope.error_code,
141842
- envelope.description ?? "no reason",
141843
- envelope.parameters?.retry_after
141844
- );
141845
- }
141846
- return envelope.result;
142278
+ return readTelegramResponse(method, response);
141847
142279
  };
141848
142280
  const upload = async (method, field, chatId, file, replyMessageId) => {
141849
142281
  const form = new FormData();
@@ -141861,15 +142293,7 @@ var makeTelegramApi = (input) => {
141861
142293
  { method: "POST", body: form },
141862
142294
  UPLOAD_TIMEOUT_MS
141863
142295
  );
141864
- const envelope = await response.json();
141865
- if (envelope.ok !== true) {
141866
- throw new TelegramApiError(
141867
- method,
141868
- envelope.error_code,
141869
- envelope.description ?? "no reason",
141870
- envelope.parameters?.retry_after
141871
- );
141872
- }
142296
+ await readTelegramResponse(method, response);
141873
142297
  };
141874
142298
  const keyboard = (rows) => ({
141875
142299
  inline_keyboard: rows.map(
@@ -142164,7 +142588,7 @@ var toTelegramRichMarkdown = (value2) => {
142164
142588
 
142165
142589
  // src/plainTurnText.ts
142166
142590
  var RUNNING_LABEL_MAX_CHARS = 200;
142167
- var clip3 = (text4, max) => {
142591
+ var clip4 = (text4, max) => {
142168
142592
  const chars = Array.from(text4);
142169
142593
  return chars.length <= max ? text4 : `${chars.slice(0, max - 1).join("")}\u2026`;
142170
142594
  };
@@ -142173,7 +142597,7 @@ var turnTextParts = (view, t) => {
142173
142597
  if (view.omitted > 0) names.unshift(t("card.step.omitted", { count: view.omitted }));
142174
142598
  const headLines = [view.title === "" ? t("card.untitled") : view.title];
142175
142599
  if (names.length > 0) headLines.push(names.join(" \u2192 "));
142176
- 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)}`);
142177
142601
  const body = view.body.trim();
142178
142602
  const footLines = [];
142179
142603
  if (view.status === "working") {
@@ -142247,6 +142671,7 @@ var TELEGRAM_CARD_THROTTLE_MS = 1200;
142247
142671
  var FLOOD_WAIT_MAX_SECONDS = 30;
142248
142672
  var REMEMBERED_CARD_LIMIT = 200;
142249
142673
  var tokenOf = (binding) => (binding.values["telegramToken"] ?? "").trim();
142674
+ var tokenFingerprint = (bot) => NodeCrypto5.createHash("sha256").update(tokenOf(bot)).digest("hex");
142250
142675
  var REACTION_WORKING = "\u{1F440}";
142251
142676
  var REACTION_DONE = "\u{1F44D}";
142252
142677
  var REACTION_FAILED = "\u{1F44E}";
@@ -142353,6 +142778,7 @@ var makeTelegramAdapter = (deps) => {
142353
142778
  });
142354
142779
  return {
142355
142780
  platform: "telegram",
142781
+ botIdentity: tokenFingerprint,
142356
142782
  icon: "telegram",
142357
142783
  label: "Telegram",
142358
142784
  quotesReplies: true,
@@ -142364,7 +142790,7 @@ var makeTelegramAdapter = (deps) => {
142364
142790
  };
142365
142791
  const stopped = new Promise((resolve) => stop = resolve);
142366
142792
  let running = true;
142367
- const tokenHash = NodeCrypto5.createHash("sha256").update(tokenOf(binding)).digest("hex");
142793
+ const tokenHash = tokenFingerprint(binding);
142368
142794
  let offset = deps.readCursor(binding.rowId, tokenHash);
142369
142795
  const advanceOffset = (update) => {
142370
142796
  offset = nextOffset([update], offset ?? 0);
@@ -142450,7 +142876,7 @@ var makeTelegramAdapter = (deps) => {
142450
142876
  const gated = !passesGroupGate2({
142451
142877
  message,
142452
142878
  text: text4,
142453
- hasResource: resource !== null,
142879
+ hasResource: resource !== null || message.voice !== void 0,
142454
142880
  botUsername: botUsername ?? null,
142455
142881
  groupMention: binding.groupMention
142456
142882
  });
@@ -142465,9 +142891,9 @@ var makeTelegramAdapter = (deps) => {
142465
142891
  if (!running) break;
142466
142892
  advanceOffset(update);
142467
142893
  const senderId = message.from === void 0 ? null : String(message.from.id);
142468
- const senderName = telegramDisplayName(message.from);
142894
+ const senderName2 = telegramDisplayName(message.from);
142469
142895
  const chatKind = message.chat.type === "private" ? "private" : "group";
142470
- if (text4 === "" && resource === null) {
142896
+ if (message.voice !== void 0 || text4 === "" && resource === null) {
142471
142897
  const unsupported = unsupportedTypeOf(message);
142472
142898
  if (unsupported === null) continue;
142473
142899
  handlers.onMessage({
@@ -142489,7 +142915,7 @@ var makeTelegramAdapter = (deps) => {
142489
142915
  ...parentChatId === void 0 ? {} : { parentChatId },
142490
142916
  senderId,
142491
142917
  text: text4,
142492
- ...senderName ? { senderName } : {},
142918
+ ...senderName2 ? { senderName: senderName2 } : {},
142493
142919
  ...message.chat.title ? { chatTitle: message.chat.title } : {},
142494
142920
  ...replyTo === null ? {} : { replyTo },
142495
142921
  chatKind,
@@ -142506,6 +142932,17 @@ var makeTelegramAdapter = (deps) => {
142506
142932
  }
142507
142933
  } catch (error) {
142508
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
+ }
142509
142946
  if (checkingConnection) handlers.onConnectionState?.({ state: "failed", error });
142510
142947
  handlers.onError(error instanceof Error ? error.message : String(error));
142511
142948
  await Promise.race([(deps.sleep ?? defaultSleep)(backoff), stopped]);
@@ -142517,9 +142954,9 @@ var makeTelegramAdapter = (deps) => {
142517
142954
  deps.log.info("telegram polling", { bot: binding.displayName });
142518
142955
  return {
142519
142956
  close: () => {
142957
+ if (running) publishAck();
142520
142958
  running = false;
142521
142959
  stop();
142522
- publishAck();
142523
142960
  apis.delete(binding.rowId);
142524
142961
  }
142525
142962
  };
@@ -142550,12 +142987,11 @@ var makeTelegramAdapter = (deps) => {
142550
142987
  }
142551
142988
  username = me.username !== void 0 && me.username !== "" ? `@${me.username}` : "(no username)";
142552
142989
  } catch (error) {
142553
- const rejected = error instanceof TelegramApiError && (error.code === 401 || error.code === 404);
142554
142990
  return {
142555
142991
  stage: "getMe",
142556
142992
  ok: false,
142557
142993
  detail: describeError(error),
142558
- ...rejected ? { hint: "unauthorized" } : {}
142994
+ ...isTelegramCredentialsRejected(error) ? { hint: "unauthorized" } : {}
142559
142995
  };
142560
142996
  }
142561
142997
  try {
@@ -142717,12 +143153,96 @@ ${line}`
142717
143153
 
142718
143154
  // src/turns.ts
142719
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
142720
143233
  var TITLE_MAX_CHARS = 72;
142721
143234
  var titleFrom = (text4) => text4.replace(/\s+/g, " ").slice(0, TITLE_MAX_CHARS) || "Chat thread";
142722
143235
  var makeTurns = (core, deps) => {
142723
143236
  const { host, t, commit, bindChat, deliver } = core;
142724
143237
  const { queueFlow, rememberThreadProject } = deps;
142725
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
+ };
142726
143246
  const threadStillUsable = async (binding, threadId) => {
142727
143247
  try {
142728
143248
  const thread = await threadOrNull8(host.threads, threadId, { archived: "include" });
@@ -142736,7 +143256,7 @@ var makeTurns = (core, deps) => {
142736
143256
  return waiting !== void 0 && waiting.threadId === threadId ? waiting : void 0;
142737
143257
  };
142738
143258
  const interactionModeOf = (chatKey) => chatOf(core.state, chatKey).plan ? "plan" : "default";
142739
- const ensureThread = async (binding, titleSeed) => {
143259
+ const ensureThread = async (binding, message) => {
142740
143260
  const chatKey = chatKeyOf(binding);
142741
143261
  let threadId = chatOf(core.state, chatKey).threadId ?? void 0;
142742
143262
  if (threadId !== void 0 && !await threadStillUsable(binding, threadId)) {
@@ -142766,12 +143286,12 @@ var makeTurns = (core, deps) => {
142766
143286
  await deliver(binding, t("reply.noDefaultModel", { title: project.title }));
142767
143287
  return null;
142768
143288
  }
142769
- const createdThreadId = crypto.randomUUID();
143289
+ const createdThreadId = identity("thread", binding, message);
142770
143290
  await host.threads.create({
142771
- requestId: crypto.randomUUID(),
143291
+ requestId: identity("create", binding, message),
142772
143292
  threadId: createdThreadId,
142773
143293
  projectId: binding.projectId,
142774
- title: titleFrom(titleSeed),
143294
+ title: titleFrom(message.text || message.resources[0]?.name || "Chat"),
142775
143295
  modelSelection,
142776
143296
  runtimeMode: binding.runtimeMode,
142777
143297
  interactionMode: interactionModeOf(chatKey),
@@ -142790,7 +143310,7 @@ var makeTurns = (core, deps) => {
142790
143310
  const startTurn = async (binding, message) => {
142791
143311
  const chatKey = chatKeyOf(binding);
142792
143312
  const text4 = message.text;
142793
- const threadId = await ensureThread(binding, text4 || message.resources[0]?.name || "Chat");
143313
+ const threadId = await ensureThread(binding, message);
142794
143314
  if (threadId === null) return null;
142795
143315
  const topicRoot = await message.topicRootForSession?.(threadId);
142796
143316
  const carried = waitingFor(chatKey, threadId);
@@ -142804,7 +143324,7 @@ var makeTurns = (core, deps) => {
142804
143324
  );
142805
143325
  const attachments = [...carried?.attachments ?? [], ...own.attachments];
142806
143326
  const notes = [...carried?.notes ?? [], ...own.notes];
142807
- const userMessageId = crypto.randomUUID();
143327
+ const userMessageId = identity("msg", binding, message);
142808
143328
  deps.anchors.note(threadId, userMessageId, binding);
142809
143329
  let intentAction;
142810
143330
  if (binding.queueMode === "steer") {
@@ -142816,33 +143336,39 @@ var makeTurns = (core, deps) => {
142816
143336
  kind: "steer",
142817
143337
  source: "automatic",
142818
143338
  turnId: activeTurnId,
142819
- commandId: crypto.randomUUID()
143339
+ commandId: identity("steer", binding, message)
142820
143340
  };
142821
143341
  }
142822
143342
  } catch {
142823
143343
  }
142824
143344
  }
142825
143345
  const registered = deps.conclusions.starting(threadId);
143346
+ const requestId = identity("turn", binding, message);
143347
+ let failureNotice;
142826
143348
  try {
142827
- await host.threads.turns.start({
142828
- requestId: crypto.randomUUID(),
142829
- threadId,
142830
- message: {
142831
- messageId: userMessageId,
142832
- role: "user",
142833
- text: [
142834
- textWithInboundContext(message, binding.rowId, t, topicRoot?.reference),
142835
- ...notes
142836
- ].join("\n"),
142837
- attachments: attachments.map(
142838
- (attachment) => attachment.type === "image" ? { ...attachment, type: "image" } : { ...attachment, type: "file" }
142839
- )
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 } : {}
142840
143369
  },
142841
- titleSeed: titleFrom(text4 || attachments[0]?.name || "Chat"),
142842
- runtimeMode: binding.runtimeMode,
142843
- interactionMode: interactionModeOf(chatKey),
142844
- ...intentAction !== void 0 ? { intentAction } : {}
142845
- });
143370
+ { sleep: deps.dispatchSleep, signal: dispatchAbort.signal }
143371
+ );
142846
143372
  topicRoot?.confirmConsumed();
142847
143373
  if (intentAction?.kind !== "steer") {
142848
143374
  deps.conclusions.owe({
@@ -142854,10 +143380,15 @@ var makeTurns = (core, deps) => {
142854
143380
  }
142855
143381
  } catch (error) {
142856
143382
  if (carried !== void 0) pendingByChat.set(chatKey, carried);
142857
- throw error;
143383
+ if (dispatchAbort.signal.aborted) return null;
143384
+ failureNotice = deps.failures.forDispatchFailure(threadId, requestId, error);
142858
143385
  } finally {
142859
143386
  registered();
142860
143387
  }
143388
+ if (failureNotice !== void 0) {
143389
+ await deliver(binding, failureNotice);
143390
+ return null;
143391
+ }
142861
143392
  deps.noteInboundUserMessage?.(userMessageId);
142862
143393
  host.log.info("chat started a turn from a message", {
142863
143394
  bot: binding.displayName,
@@ -142874,7 +143405,7 @@ var makeTurns = (core, deps) => {
142874
143405
  return threadId;
142875
143406
  };
142876
143407
  const receiveAttachmentsOnly = async (binding, message) => {
142877
- const threadId = await ensureThread(binding, message.resources[0]?.name || "Chat");
143408
+ const threadId = await ensureThread(binding, message);
142878
143409
  if (threadId === null) return;
142879
143410
  const waiting = pendingFor(chatKeyOf(binding), threadId);
142880
143411
  const collected = await collectResources(
@@ -142956,6 +143487,84 @@ var makeWebSync = (core) => {
142956
143487
  return { noteInboundUserMessage };
142957
143488
  };
142958
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
+
142959
143568
  // src/settingsRowActions.ts
142960
143569
  var ROW_PAYLOAD_SCHEMA = {
142961
143570
  type: "object",
@@ -143023,19 +143632,21 @@ var emitReport = (emit, report, action) => {
143023
143632
  });
143024
143633
  return;
143025
143634
  }
143026
- 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";
143027
143637
  emit({
143028
143638
  kind: "failed",
143029
- reasonKey: suggested && report.hint !== void 0 ? connectionHintKey(report.hint) : `${action.keyPrefix}.failed`,
143639
+ reasonKey: copying ? "rowAction.copyDiagnostics.done" : connectionReasonKey,
143030
143640
  // 参考号由界面写在细节第一行(「参考号:…」),结论句里不带。
143031
143641
  params: {
143032
143642
  ref: report.ref,
143033
- ...suggested && report.hint !== void 0 ? { hint: report.hint } : {}
143643
+ ...report.hint === void 0 ? {} : { hint: report.hint }
143034
143644
  },
143645
+ ...copying ? { noteKey: connectionReasonKey } : {},
143035
143646
  detail
143036
143647
  });
143037
143648
  };
143038
- var registerSettingsRowActions = (ctx, adapters) => {
143649
+ var registerSettingsRowActions = (ctx, adapters, connections) => {
143039
143650
  const resolveBot = (rowId) => botOf(ctx.config, adapters, rowId);
143040
143651
  const registerProbeAction = (action) => {
143041
143652
  ctx.stream({
@@ -143063,6 +143674,26 @@ var registerSettingsRowActions = (ctx, adapters) => {
143063
143674
  keyPrefix: "rowAction.checkConnection",
143064
143675
  copyable: false
143065
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
+ });
143066
143697
  registerProbeAction({
143067
143698
  name: BOT_ACTION_COMMANDS.copyDiagnostics,
143068
143699
  summary: "Probe an IM bot and return a copy-paste diagnostic report.",
@@ -143071,7 +143702,7 @@ var registerSettingsRowActions = (ctx, adapters) => {
143071
143702
  });
143072
143703
  ctx.stream({
143073
143704
  name: BOT_ACTION_COMMANDS.larkRepair,
143074
- 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.",
143075
143706
  scope: "operate",
143076
143707
  payloadSchema: ROW_PAYLOAD_SCHEMA,
143077
143708
  itemSchema: ROW_ACTION_ITEM_SCHEMA,
@@ -143080,15 +143711,6 @@ var registerSettingsRowActions = (ctx, adapters) => {
143080
143711
  const rowId = typeof payload["rowId"] === "string" ? payload["rowId"] : "";
143081
143712
  const send = (item) => emit(item);
143082
143713
  const ref = diagnosticRef("setup");
143083
- send({
143084
- kind: "steps",
143085
- steps: [
143086
- { id: "scopes", labelKey: "rowAction.larkRepair.step.scopes", state: "skipped" },
143087
- { id: "events", labelKey: "rowAction.larkRepair.step.events", state: "skipped" },
143088
- { id: "publish", labelKey: "rowAction.larkRepair.step.publish", state: "skipped" },
143089
- { id: "verify", labelKey: "rowAction.larkRepair.step.verify", state: "running" }
143090
- ]
143091
- });
143092
143714
  const bot = resolveBot(rowId);
143093
143715
  if (bot === void 0 || bot.platform !== "lark") {
143094
143716
  send({
@@ -143098,40 +143720,14 @@ var registerSettingsRowActions = (ctx, adapters) => {
143098
143720
  });
143099
143721
  return;
143100
143722
  }
143101
- const report = await probe(adapters, bot);
143102
- send({
143103
- kind: "steps",
143104
- steps: [
143105
- { id: "scopes", labelKey: "rowAction.larkRepair.step.scopes", state: "skipped" },
143106
- { id: "events", labelKey: "rowAction.larkRepair.step.events", state: "skipped" },
143107
- { id: "publish", labelKey: "rowAction.larkRepair.step.publish", state: "skipped" },
143108
- {
143109
- id: "verify",
143110
- labelKey: "rowAction.larkRepair.step.verify",
143111
- state: report.ok ? "done" : "failed"
143112
- }
143113
- ]
143114
- });
143115
- if (report.ok) {
143116
- send({
143117
- kind: "done",
143118
- fills: {},
143119
- messageKey: "rowAction.larkRepair.done",
143120
- warnings: [
143121
- {
143122
- key: "rowAction.larkRepair.hint",
143123
- params: { ref, detail: formatDiagnosticCopy(report) }
143124
- }
143125
- ]
143126
- });
143127
- return;
143128
- }
143129
- send({
143130
- kind: "failed",
143131
- reasonKey: "rowAction.larkRepair.failed",
143132
- params: { ref },
143133
- detail: formatDiagnosticCopy(report)
143134
- });
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
+ );
143135
143731
  }
143136
143732
  });
143137
143733
  };
@@ -143460,6 +144056,7 @@ var wechatConfigIssues = (bot) => tokenOf2(bot) === "" ? [{ column: "wechatBotTo
143460
144056
  var makeWechatAdapter = (deps) => {
143461
144057
  return {
143462
144058
  platform: "wechat",
144059
+ botIdentity: () => "wechat-stub",
143463
144060
  icon: "wechat",
143464
144061
  // 平台名与设置表的平台选项同一个键,免得默认机器人名与选项写成两种叫法。
143465
144062
  label: deps.translate("platform.wechat"),
@@ -143495,7 +144092,7 @@ var makeWechatAdapter = (deps) => {
143495
144092
  };
143496
144093
 
143497
144094
  // src/wecomUpload.ts
143498
- import * as NodeCrypto6 from "node:crypto";
144095
+ import * as NodeCrypto7 from "node:crypto";
143499
144096
  var WECOM_CHUNK_BYTES = 512 * 1024;
143500
144097
  var WECOM_MAX_CHUNKS = 100;
143501
144098
  var WECOM_MIN_UPLOAD_BYTES = 5;
@@ -143528,7 +144125,7 @@ var uploadWecomMedia = async (input) => {
143528
144125
  filename: clampName(input.filename),
143529
144126
  total_size: bytes.byteLength,
143530
144127
  total_chunks: totalChunks,
143531
- md5: NodeCrypto6.createHash("md5").update(bytes).digest("hex")
144128
+ md5: NodeCrypto7.createHash("md5").update(bytes).digest("hex")
143532
144129
  });
143533
144130
  const uploadId = init.upload_id;
143534
144131
  if (typeof uploadId !== "string" || uploadId === "") {
@@ -143707,6 +144304,26 @@ var parseWecomMessage = (frame, botId) => {
143707
144304
  };
143708
144305
  var splitForWecom = (text4, limit = WECOM_TEXT_MAX_CHARS) => splitMessage(text4, limit);
143709
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
+ };
143710
144327
  var runWecomClient = (input) => {
143711
144328
  let sequence = 0;
143712
144329
  const nextRequestId = (kind) => `${kind}_${++sequence}`;
@@ -143737,32 +144354,47 @@ var runWecomClient = (input) => {
143737
144354
  if (typeof frame !== "object" || frame === null) return;
143738
144355
  const requestId = frame.headers?.req_id;
143739
144356
  if (typeof frame.cmd !== "string") {
143740
- 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
+ }
143741
144362
  if (requestId.startsWith("ping")) {
143742
144363
  missedPong = 0;
143743
144364
  return;
143744
144365
  }
143745
- const error = typeof frame.errcode === "number" && frame.errcode !== 0 ? new Error(`WeCom ack failed: ${String(frame.errcode)} ${frame.errmsg ?? ""}`) : null;
143746
144366
  const stream = openStreams.get(requestId);
143747
144367
  if (stream !== void 0 && error !== null) stream.failed = true;
143748
144368
  settleAck(requestId, error, frame);
143749
144369
  return;
143750
144370
  }
144371
+ if (frame.cmd === "aibot_event_callback" && frame.body?.event?.eventtype === "disconnected_event") {
144372
+ finish("taken-over");
144373
+ return;
144374
+ }
143751
144375
  if (frame.cmd !== "aibot_msg_callback") return;
143752
144376
  const message = parseWecomMessage(frame, input.botId);
143753
144377
  if (message !== null) input.handlers.onMessage(message);
143754
144378
  };
143755
- 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")) => {
143756
144380
  if (finished) return;
143757
144381
  finished = true;
143758
- 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
+ });
143759
144391
  clearInterval(heartbeat);
143760
144392
  openStreams.clear();
143761
144393
  void input.socket.close().catch(() => void 0);
143762
144394
  for (const requestId of pendingAcks.keys()) {
143763
144395
  settleAck(requestId, new Error("WeCom connection closed"));
143764
144396
  }
143765
- ended.resolve();
144397
+ ended.resolve(failureReason === "subscription-failed" ? "disconnected" : failureReason);
143766
144398
  };
143767
144399
  const readLoop = async () => {
143768
144400
  try {
@@ -143773,7 +144405,7 @@ var runWecomClient = (input) => {
143773
144405
  if (!closed) {
143774
144406
  input.handlers.onError(error instanceof Error ? error.message : String(error));
143775
144407
  }
143776
- finish(error);
144408
+ finish("disconnected", error);
143777
144409
  }
143778
144410
  finish();
143779
144411
  };
@@ -143781,7 +144413,7 @@ var runWecomClient = (input) => {
143781
144413
  if (finished) return;
143782
144414
  if (missedPong >= MAX_MISSED_PONGS) {
143783
144415
  input.handlers.onError("socket hang up: missed pong");
143784
- finish(new Error("socket hang up: missed pong"));
144416
+ finish("disconnected", new Error("socket hang up: missed pong"));
143785
144417
  return;
143786
144418
  }
143787
144419
  missedPong += 1;
@@ -143808,7 +144440,7 @@ var runWecomClient = (input) => {
143808
144440
  },
143809
144441
  (error) => {
143810
144442
  if (!closed) input.handlers.onError(error instanceof Error ? error.message : String(error));
143811
- finish(error, "subscription-failed");
144443
+ finish("subscription-failed", error);
143812
144444
  return false;
143813
144445
  }
143814
144446
  );
@@ -143943,7 +144575,7 @@ var runWecomClient = (input) => {
143943
144575
  };
143944
144576
 
143945
144577
  // src/wecomMedia.ts
143946
- import * as NodeCrypto7 from "node:crypto";
144578
+ import * as NodeCrypto8 from "node:crypto";
143947
144579
 
143948
144580
  // src/wecomPadding.ts
143949
144581
  var PADDING_BLOCK = 32;
@@ -143974,7 +144606,7 @@ var decryptWecomMedia = (cipher, aesKey) => {
143974
144606
  if (cipher.byteLength === 0 || cipher.byteLength % AES_BLOCK !== 0) {
143975
144607
  throw new WecomDecryptError("the ciphertext is not a whole number of blocks");
143976
144608
  }
143977
- const decipher = NodeCrypto7.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
144609
+ const decipher = NodeCrypto8.createDecipheriv("aes-256-cbc", key, key.subarray(0, IV_BYTES));
143978
144610
  decipher.setAutoPadding(false);
143979
144611
  const plain = Buffer.concat([decipher.update(cipher), decipher.final()]);
143980
144612
  const unpadded = stripPadding(plain);
@@ -144038,13 +144670,13 @@ var COLUMNS4 = [
144038
144670
  { key: "wecomBotSecret", kind: "password", labelKey: "column.wecomBotSecret.label" }
144039
144671
  ];
144040
144672
  var defaultSleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
144041
- var credentials2 = (binding) => ({
144673
+ var credentials = (binding) => ({
144042
144674
  botId: binding.values["wecomBotId"] ?? "",
144043
144675
  botSecret: binding.values["wecomBotSecret"] ?? "",
144044
144676
  endpoint: normalizeWecomEndpoint(binding.values["wecomEndpoint"] || DEFAULT_WECOM_ENDPOINT)
144045
144677
  });
144046
144678
  var wecomConfigIssues = (binding) => {
144047
- const { botId, botSecret } = credentials2(binding);
144679
+ const { botId, botSecret } = credentials(binding);
144048
144680
  const issues = [];
144049
144681
  if (botId === "") issues.push({ column: "wecomBotId", code: "required" });
144050
144682
  if (botSecret === "") issues.push({ column: "wecomBotSecret", code: "required" });
@@ -144052,6 +144684,7 @@ var wecomConfigIssues = (binding) => {
144052
144684
  };
144053
144685
  var makeWecomAdapter = (deps) => {
144054
144686
  const clients = /* @__PURE__ */ new Map();
144687
+ const managedConnections = /* @__PURE__ */ new Map();
144055
144688
  const t = deps.translate;
144056
144689
  const liveStreams = /* @__PURE__ */ new Map();
144057
144690
  const rememberStream = (key, entry) => {
@@ -144090,6 +144723,7 @@ ${kept}`;
144090
144723
  };
144091
144724
  return {
144092
144725
  platform: "wecom",
144726
+ botIdentity: (bot) => credentials(bot).botId,
144093
144727
  icon: "wecom",
144094
144728
  label: "WeCom",
144095
144729
  columns: COLUMNS4,
@@ -144097,15 +144731,22 @@ ${kept}`;
144097
144731
  validate: wecomConfigIssues,
144098
144732
  isConfigured: (binding) => wecomConfigIssues(binding).length === 0,
144099
144733
  connect: (binding, handlers) => {
144100
- const { botId, botSecret, endpoint } = credentials2(binding);
144734
+ const { botId, botSecret, endpoint } = credentials(binding);
144735
+ const managed = { endpoint };
144736
+ managedConnections.set(binding.rowId, managed);
144101
144737
  let stopped = false;
144102
144738
  let current = null;
144103
144739
  const stopSignal = Promise.withResolvers();
144740
+ const onConnectionState = (state) => {
144741
+ if (stopped) return;
144742
+ managed.state = state;
144743
+ handlers.onConnectionState?.(state);
144744
+ };
144104
144745
  const runOnce = async () => {
144105
144746
  const socket = await deps.connectSocket(endpoint);
144106
144747
  if (stopped) {
144107
144748
  await socket.close();
144108
- return false;
144749
+ return { healthy: false, stopRetrying: false };
144109
144750
  }
144110
144751
  const client = runWecomClient({
144111
144752
  socket: {
@@ -144117,9 +144758,7 @@ ${kept}`;
144117
144758
  botSecret,
144118
144759
  handlers: {
144119
144760
  onError: handlers.onError,
144120
- onConnectionState: (state) => {
144121
- if (!stopped) handlers.onConnectionState?.(state);
144122
- },
144761
+ onConnectionState,
144123
144762
  onMessage: (message) => {
144124
144763
  if (message.chatType !== "single") return;
144125
144764
  client.rememberRequest(message.chatId, message.requestId);
@@ -144149,20 +144788,25 @@ ${kept}`;
144149
144788
  if (ok) deps.log.info("wecom connected", { bot: binding.displayName });
144150
144789
  return ok;
144151
144790
  });
144152
- await client.ended;
144791
+ const reason = await client.ended;
144153
144792
  if (clients.get(binding.rowId) === client) clients.delete(binding.rowId);
144154
144793
  current = null;
144155
- return healthy;
144794
+ return {
144795
+ healthy: await healthy,
144796
+ stopRetrying: reason === "taken-over" || reason === "credentials-rejected"
144797
+ };
144156
144798
  };
144157
144799
  const supervise = async () => {
144158
144800
  let backoff = BACKOFF_START_MS2;
144159
144801
  for (; ; ) {
144160
144802
  if (stopped) return;
144161
144803
  try {
144162
- 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;
144163
144807
  } catch (error) {
144164
144808
  if (stopped) return;
144165
- handlers.onConnectionState?.({ state: "failed", error, reason: "disconnected" });
144809
+ onConnectionState({ state: "failed", error, reason: "disconnected" });
144166
144810
  handlers.onError(error instanceof Error ? error.message : String(error));
144167
144811
  }
144168
144812
  if (stopped) return;
@@ -144177,6 +144821,9 @@ ${kept}`;
144177
144821
  stopped = true;
144178
144822
  stopSignal.resolve();
144179
144823
  current?.close();
144824
+ if (managedConnections.get(binding.rowId) === managed) {
144825
+ managedConnections.delete(binding.rowId);
144826
+ }
144180
144827
  }
144181
144828
  };
144182
144829
  },
@@ -144189,12 +144836,33 @@ ${kept}`;
144189
144836
  await client.sendText(binding.chatId, text4);
144190
144837
  },
144191
144838
  checkConnection: async (bot) => {
144192
- 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);
144193
144860
  if (botId === "" || botSecret === "") {
144194
144861
  return {
144195
144862
  stage: "credentials",
144196
144863
  ok: false,
144197
- detail: "missing bot id or secret in saved config"
144864
+ detail: "missing bot id or secret in saved config",
144865
+ hint: "wecomBotCredentials"
144198
144866
  };
144199
144867
  }
144200
144868
  let stage = "websocket";
@@ -144202,6 +144870,7 @@ ${kept}`;
144202
144870
  const socket = await deps.connectSocket(endpoint);
144203
144871
  stage = "aibot_subscribe";
144204
144872
  let detail = "WeCom connection closed before subscribe ack";
144873
+ let hint;
144205
144874
  let closing;
144206
144875
  const client = runWecomClient({
144207
144876
  socket: {
@@ -144213,6 +144882,9 @@ ${kept}`;
144213
144882
  botSecret,
144214
144883
  handlers: {
144215
144884
  onMessage: () => void 0,
144885
+ onConnectionState: (state) => {
144886
+ if (state.state === "failed") hint = state.hint;
144887
+ },
144216
144888
  onError: (error) => {
144217
144889
  detail = error;
144218
144890
  }
@@ -144220,7 +144892,12 @@ ${kept}`;
144220
144892
  });
144221
144893
  try {
144222
144894
  const ok = await client.ready;
144223
- 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
+ };
144224
144901
  } finally {
144225
144902
  client.close();
144226
144903
  await closing;
@@ -144572,7 +145249,7 @@ import * as NodeHttp2 from "node:http";
144572
145249
  import * as NodeNet from "node:net";
144573
145250
 
144574
145251
  // src/wecomAppCrypto.ts
144575
- import * as NodeCrypto8 from "node:crypto";
145252
+ import * as NodeCrypto9 from "node:crypto";
144576
145253
  var AES_KEY_BYTES = 32;
144577
145254
  var RANDOM_BYTES = 16;
144578
145255
  var WecomAppCryptoError = class extends Error {
@@ -144595,13 +145272,13 @@ var timingSafeEqualString2 = (a, b) => {
144595
145272
  const left = Buffer.from(a);
144596
145273
  const right = Buffer.from(b);
144597
145274
  if (left.length === 0 || left.length !== right.length) return false;
144598
- return NodeCrypto8.timingSafeEqual(left, right);
145275
+ return NodeCrypto9.timingSafeEqual(left, right);
144599
145276
  };
144600
- var computeSignature = (token, timestamp, nonce, encrypt) => NodeCrypto8.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");
144601
145278
  var verifySignature = (token, timestamp, nonce, encrypt, signature) => timingSafeEqualString2(computeSignature(token, timestamp, nonce, encrypt), signature);
144602
145279
  var decrypt = (encodingAESKey, encryptText, expectReceiveId) => {
144603
145280
  const { key, iv } = parseEncodingAESKey(encodingAESKey);
144604
- const decipher = NodeCrypto8.createDecipheriv("aes-256-cbc", key, iv);
145281
+ const decipher = NodeCrypto9.createDecipheriv("aes-256-cbc", key, iv);
144605
145282
  decipher.setAutoPadding(false);
144606
145283
  let deciphered;
144607
145284
  try {
@@ -144660,7 +145337,7 @@ var createWecomAppCrypto = (input) => {
144660
145337
  decrypt: (encryptText) => decrypt(encodingAESKey, encryptText, corpId).message
144661
145338
  };
144662
145339
  };
144663
- var deriveCallbackPathSecret = (token, encodingAESKey) => NodeCrypto8.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);
144664
145341
 
144665
145342
  // src/wecomAppParse.ts
144666
145343
  var isRecord5 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
@@ -144697,7 +145374,6 @@ var parseWecomAppPlainMessage = (raw) => {
144697
145374
  text: xml["Content"] !== void 0 ? { content: xml["Content"] } : void 0,
144698
145375
  image: xml["PicUrl"] ? { url: xml["PicUrl"] } : void 0,
144699
145376
  mediaId: xml["MediaId"] ?? xml["mediaid"],
144700
- recognition: xml["Recognition"],
144701
145377
  event: (xml["Event"] ?? xml["event"] ?? "").toLowerCase() || void 0,
144702
145378
  eventKey: xml["EventKey"],
144703
145379
  chatid: xml["ChatId"]
@@ -144723,7 +145399,6 @@ var parseWecomAppPlainMessage = (raw) => {
144723
145399
  text: isRecord5(textRaw) ? { content: typeof textRaw["content"] === "string" ? textRaw["content"] : void 0 } : void 0,
144724
145400
  image: isRecord5(imageRaw) ? { url: typeof imageRaw["url"] === "string" ? imageRaw["url"] : void 0 } : void 0,
144725
145401
  mediaId: typeof data["mediaid"] === "string" ? data["mediaid"] : void 0,
144726
- recognition: typeof data["recognition"] === "string" ? data["recognition"] : void 0,
144727
145402
  event: String(data["event"] ?? data["eventtype"] ?? "").toLowerCase() || void 0,
144728
145403
  eventKey: typeof data["eventkey"] === "string" ? data["eventkey"] : void 0,
144729
145404
  chatid: typeof data["chatid"] === "string" ? data["chatid"] : void 0
@@ -144787,13 +145462,16 @@ var startWecomAppCallbackServer = async (options) => {
144787
145462
  }
144788
145463
  try {
144789
145464
  if (!crypto2.verifySignature(signature, timestamp, nonce, echostr)) {
145465
+ route.onVerification?.(false);
144790
145466
  writeText(res, 401, "unauthorized");
144791
145467
  return;
144792
145468
  }
144793
145469
  const plaintext = crypto2.decrypt(echostr);
145470
+ route.onVerification?.(true);
144794
145471
  route.log?.info("wecom-app URL verification succeeded", { bot: route.botRowId });
144795
145472
  writeText(res, 200, plaintext);
144796
145473
  } catch (error) {
145474
+ route.onVerification?.(false);
144797
145475
  route.log?.warn("wecom-app URL verification failed", {
144798
145476
  bot: route.botRowId,
144799
145477
  error: error instanceof Error ? error.message : String(error)
@@ -144855,11 +145533,13 @@ var startWecomAppCallbackServer = async (options) => {
144855
145533
  let plaintext;
144856
145534
  try {
144857
145535
  if (!crypto2.verifySignature(signature, timestamp, nonce, encrypt)) {
145536
+ route.onVerification?.(false);
144858
145537
  writeText(res, 401, "unauthorized");
144859
145538
  return;
144860
145539
  }
144861
145540
  plaintext = crypto2.decrypt(encrypt);
144862
145541
  } catch (error) {
145542
+ route.onVerification?.(false);
144863
145543
  route.log?.warn("wecom-app callback signature or decrypt failed", {
144864
145544
  bot: route.botRowId,
144865
145545
  error: error instanceof Error ? error.message : String(error)
@@ -144867,6 +145547,7 @@ var startWecomAppCallbackServer = async (options) => {
144867
145547
  writeText(res, 401, "unauthorized");
144868
145548
  return;
144869
145549
  }
145550
+ route.onVerification?.(true);
144870
145551
  const message = parseWecomAppPlainMessage(plaintext);
144871
145552
  if (message === null) {
144872
145553
  writeText(res, 400, "invalid message");
@@ -145045,11 +145726,11 @@ var validateWecomAppBot = (bot) => {
145045
145726
  };
145046
145727
  var readWecomAppConnectConfig = (bot) => {
145047
145728
  const issues = validateWecomAppBot(bot);
145048
- const credentials3 = wecomAppCredentials(bot);
145049
- const listenPort = parseListenPort(credentials3.listenPortRaw);
145050
- const listenHost = parseListenHost(credentials3.listenHostRaw);
145729
+ const credentials2 = wecomAppCredentials(bot);
145730
+ const listenPort = parseListenPort(credentials2.listenPortRaw);
145731
+ const listenHost = parseListenHost(credentials2.listenHostRaw);
145051
145732
  if (issues.length > 0 || listenPort === null || listenHost === null) return { ok: false, issues };
145052
- return { ok: true, credentials: credentials3, listenHost, listenPort };
145733
+ return { ok: true, credentials: credentials2, listenHost, listenPort };
145053
145734
  };
145054
145735
 
145055
145736
  // src/wecomAppAdapter.ts
@@ -145109,6 +145790,7 @@ var WECOM_APP_MIN_BYTES = 5;
145109
145790
  var WECOM_APP_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png"]);
145110
145791
  var CREDENTIAL_ERROR_CODES = /* @__PURE__ */ new Set(["40001", "40013", "40014", "40056", "42001", "60020"]);
145111
145792
  var joinCallbackUrl = (baseOrOrigin, path) => `${baseOrOrigin}${path}`;
145793
+ var callbackHealthKey = (rowId, c) => JSON.stringify([rowId, c.corpId, c.token, c.encodingAESKey]);
145112
145794
  var wecomAppPublicFields = (bot) => {
145113
145795
  const c = wecomAppCredentials(bot);
145114
145796
  if (c.token === "" || c.encodingAESKey === "") {
@@ -145145,6 +145827,7 @@ var makeWecomAppAdapter = (deps) => {
145145
145827
  fetch: deps.fetch
145146
145828
  });
145147
145829
  const callbackHub = makeWecomAppCallbackHub();
145830
+ const callbackHealth = /* @__PURE__ */ new Map();
145148
145831
  const connectionFor = (binding) => {
145149
145832
  const api = apis.get(binding.rowId);
145150
145833
  if (api === void 0) {
@@ -145174,7 +145857,6 @@ var makeWecomAppAdapter = (deps) => {
145174
145857
  });
145175
145858
  };
145176
145859
  const toInbound = (message, handlers, botName) => {
145177
- if (message.msgtype === "event") return;
145178
145860
  if (message.chatid !== void 0 && message.chatid !== "") {
145179
145861
  deps.log.info("wecom-app ignored a group message; only direct messages are supported", {
145180
145862
  bot: botName
@@ -145183,6 +145865,12 @@ var makeWecomAppAdapter = (deps) => {
145183
145865
  }
145184
145866
  const chatId = message.from?.userid ?? "";
145185
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
+ }
145186
145874
  const messageId = message.msgid ?? `${chatId}:${message.createTime ?? Date.now()}`;
145187
145875
  const base2 = { messageId, chatId, senderId: chatId };
145188
145876
  if (message.msgtype === "text") {
@@ -145208,6 +145896,10 @@ var makeWecomAppAdapter = (deps) => {
145208
145896
  };
145209
145897
  return {
145210
145898
  platform: "wecom-app",
145899
+ botIdentity: (bot) => {
145900
+ const { corpId, agentId } = wecomAppCredentials(bot);
145901
+ return JSON.stringify([corpId, agentId]);
145902
+ },
145211
145903
  icon: "wecom",
145212
145904
  label: deps.translate("platform.wecom-app"),
145213
145905
  columns: COLUMNS5,
@@ -145233,8 +145925,7 @@ var makeWecomAppAdapter = (deps) => {
145233
145925
  };
145234
145926
  },
145235
145927
  /**
145236
- * 自建应用是回调式渠道:入站由企微推到我们的 listener,没有可探的长连接;能探的是出站
145237
- * 凭据——和真实发送同一条路,向 gettoken 换一次 access_token。
145928
+ * 出站凭据走真实发送的 gettoken;入站只能报告本次运行已观察到的回调。
145238
145929
  */
145239
145930
  checkConnection: async (bot) => {
145240
145931
  const outbound = validateWecomAppBot(bot).filter(
@@ -145255,11 +145946,21 @@ var makeWecomAppAdapter = (deps) => {
145255
145946
  };
145256
145947
  }
145257
145948
  try {
145258
- 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
+ }
145259
145960
  return {
145260
- stage: "gettoken",
145961
+ stage: "callback",
145261
145962
  ok: true,
145262
- detail: "access_token ok (callback reachability is not probed)"
145963
+ detail: `access_token ok; last verified callback since this startup: ${health.lastReceivedAt}`
145263
145964
  };
145264
145965
  } catch (error) {
145265
145966
  return {
@@ -145297,7 +145998,14 @@ var makeWecomAppAdapter = (deps) => {
145297
145998
  encodingAESKey: c.encodingAESKey,
145298
145999
  corpId: c.corpId,
145299
146000
  log: deps.log,
145300
- 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
+ }
145301
146009
  });
145302
146010
  } catch (error) {
145303
146011
  apis.delete(bot.rowId);
@@ -145389,12 +146097,14 @@ var makeWecomAppAdapter = (deps) => {
145389
146097
  // src/index.ts
145390
146098
  var definition = {
145391
146099
  id: "im",
145392
- apiVersion: 19,
145393
- activate: async (ctx, inboundIo) => {
146100
+ apiVersion: 20,
146101
+ // 第二个参数只给插件自己的测试夹具:换掉文件 IO 与派发重试的等待。宿主只传 ctx。
146102
+ activate: async (ctx, testing = {}) => {
146103
+ const { dispatchSleep } = testing;
145394
146104
  const inboundState = makeInboundStore({
145395
146105
  dataDir: ctx.paths.dataDir,
145396
146106
  log: ctx.log,
145397
- io: inboundIo
146107
+ io: testing.inboundIo
145398
146108
  });
145399
146109
  ctx.onDispose(inboundState.flush);
145400
146110
  const { t, setLanguage } = makeLiveTranslate(
@@ -145457,13 +146167,13 @@ var definition = {
145457
146167
  ctx.settingsCard(buildSettingsCard(adapters));
145458
146168
  const updateSettings = makeSettingsUpdater(ctx.settings);
145459
146169
  registerLarkSetup(ctx, { updateSettings });
145460
- registerSettingsRowActions(ctx, adapters);
145461
146170
  const core = makeImCore({
145462
146171
  host: {
145463
146172
  log: ctx.log,
145464
146173
  projections: ctx.projections,
145465
146174
  projects: ctx.projects,
145466
146175
  threads: ctx.threads,
146176
+ commands: ctx.commands,
145467
146177
  approvals: ctx.approvals,
145468
146178
  plugins: ctx.plugins,
145469
146179
  attachments: ctx.attachments,
@@ -145513,7 +146223,9 @@ var definition = {
145513
146223
  queueFlow: interactions.queueFlow,
145514
146224
  rememberThreadProject: notifications.rememberThreadProject,
145515
146225
  noteInboundUserMessage: webSync.noteInboundUserMessage,
145516
- anchors: replyAnchors
146226
+ anchors: replyAnchors,
146227
+ failures,
146228
+ dispatchSleep
145517
146229
  });
145518
146230
  const sessions = makeSessions(core);
145519
146231
  const modelPlan = makeModelPlan(core, turns);
@@ -145570,6 +146282,7 @@ var definition = {
145570
146282
  ]
145571
146283
  });
145572
146284
  const connections = startConnections(core, inbound.inboundHandlersFor);
146285
+ registerSettingsRowActions(ctx, adapters, connections);
145573
146286
  registerSettingsCommands(ctx, adapters, updateSettings, connections);
145574
146287
  const { bots, connecting } = connections.reconcile();
145575
146288
  const deliveryHttp = makeDeliveryHttp({