dsh-feishu-bot 0.19.8 → 0.19.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4693,7 +4693,7 @@ async function fetchNpmLatestVersion(packageName, registryUrl = defaultRegistryU
4693
4693
  }
4694
4694
  return void 0;
4695
4695
  }
4696
- async function fetchNpmLatestVersionOnce(packageName, registryUrl = defaultRegistryUrl(), timeoutMs = 5e3, fetcher = fetch) {
4696
+ async function fetchNpmLatestVersionOnce(packageName, registryUrl = defaultRegistryUrl(), timeoutMs = NPM_LATEST_TIMEOUT_MS, fetcher = fetch) {
4697
4697
  try {
4698
4698
  const response = await fetcher(
4699
4699
  `${registryUrl}/${encodeURIComponent(packageName)}/latest`,
@@ -10879,6 +10879,7 @@ async function reply6(ctx, zh, en) {
10879
10879
  // src/upgrade/update-check.ts
10880
10880
  init_own_package();
10881
10881
  var UPDATE_CHECK_CACHE_MS = 60 * 6e4;
10882
+ var UPDATE_CHECK_FAILURE_CACHE_MS = 15e3;
10882
10883
  var cache;
10883
10884
  function upgradeCheckEnabled() {
10884
10885
  return (process.env.DSH_LARK_UPGRADE_CHECK ?? "1") !== "0";
@@ -10894,16 +10895,22 @@ function isNewer(latest, current) {
10894
10895
  async function latestVersion(options = {}) {
10895
10896
  if (!upgradeCheckEnabled()) return void 0;
10896
10897
  const now = Date.now();
10897
- if (cache !== void 0 && now - cache.at < (options.cacheMs ?? UPDATE_CHECK_CACHE_MS)) {
10898
- return cache.latest;
10898
+ if (cache !== void 0) {
10899
+ const ttl = options.cacheMs !== void 0 ? options.cacheMs : cache.failed ? UPDATE_CHECK_FAILURE_CACHE_MS : UPDATE_CHECK_CACHE_MS;
10900
+ if (now - cache.at < ttl) return cache.latest;
10899
10901
  }
10900
10902
  const probe = options.probe ?? fetchNpmLatestVersionOnce;
10901
10903
  const latest = await probe(options.packageName ?? ownPackageInfo().name);
10902
- cache = { at: now, latest, notified: cache?.notified ?? /* @__PURE__ */ new Set() };
10904
+ cache = {
10905
+ at: now,
10906
+ latest,
10907
+ failed: latest === void 0,
10908
+ notified: cache?.notified ?? /* @__PURE__ */ new Set()
10909
+ };
10903
10910
  return latest;
10904
10911
  }
10905
10912
  function markNotified(latest) {
10906
- if (cache === void 0) cache = { at: 0, latest, notified: /* @__PURE__ */ new Set() };
10913
+ if (cache === void 0) cache = { at: 0, latest, failed: false, notified: /* @__PURE__ */ new Set() };
10907
10914
  if (cache.notified.has(latest)) return false;
10908
10915
  cache.notified.add(latest);
10909
10916
  return true;
@@ -12138,6 +12145,34 @@ async function tryHandleCommand(text, ctx) {
12138
12145
  import { createHash } from "crypto";
12139
12146
  var CARD_STREAM_THROTTLE_MS = 100;
12140
12147
  var CARD_PATCH_ATTEMPTS = 2;
12148
+ var MAX_CARD_WITHDRAW_RECOVERIES = 3;
12149
+ function isMessageWithdrawn(error) {
12150
+ const seen = /* @__PURE__ */ new Set();
12151
+ const stack = [error];
12152
+ while (stack.length > 0) {
12153
+ const node = stack.pop();
12154
+ if (node === null || node === void 0) continue;
12155
+ if (typeof node === "string") {
12156
+ if (node.toLowerCase().includes("withdrawn")) return true;
12157
+ continue;
12158
+ }
12159
+ if (typeof node !== "object" || seen.has(node)) continue;
12160
+ seen.add(node);
12161
+ if (Array.isArray(node)) {
12162
+ for (const item of node) stack.push(item);
12163
+ continue;
12164
+ }
12165
+ const record = node;
12166
+ if (record["code"] === 230011) return true;
12167
+ if (typeof record["message"] === "string" && record["message"].toLowerCase().includes("withdrawn")) return true;
12168
+ if (typeof record["msg"] === "string" && record["msg"].toLowerCase().includes("withdrawn")) return true;
12169
+ for (const key of ["response", "data", "error", "errors"]) {
12170
+ const value = record[key];
12171
+ if (value !== void 0 && value !== null) stack.push(value);
12172
+ }
12173
+ }
12174
+ return false;
12175
+ }
12141
12176
  function toLarkSendOptions(options) {
12142
12177
  const sendOptions = {};
12143
12178
  if (options?.replyTo) sendOptions.replyTo = options.replyTo;
@@ -12172,6 +12207,7 @@ var ResilientCardStreamController = class {
12172
12207
  timer;
12173
12208
  inFlight;
12174
12209
  reanchorInFlight;
12210
+ recoveries = 0;
12175
12211
  async update(card) {
12176
12212
  if (this.closed || this.failed) return;
12177
12213
  this.latest = card;
@@ -12219,6 +12255,7 @@ var ResilientCardStreamController = class {
12219
12255
  for (let attempt = 1; attempt <= CARD_PATCH_ATTEMPTS; attempt += 1) {
12220
12256
  try {
12221
12257
  await this.channel.updateCard(this.messageId, snapshot);
12258
+ this.recoveries = 0;
12222
12259
  return;
12223
12260
  } catch (error) {
12224
12261
  lastError = error;
@@ -12227,6 +12264,19 @@ var ResilientCardStreamController = class {
12227
12264
  attempt,
12228
12265
  error: error instanceof Error ? error.message : String(error)
12229
12266
  });
12267
+ if (isMessageWithdrawn(error) && this.recoveries < MAX_CARD_WITHDRAW_RECOVERIES) {
12268
+ try {
12269
+ await this.healFromWithdrawal();
12270
+ } catch (healError) {
12271
+ log.warn("lark-card-stream", "heal-failed", {
12272
+ messageId: this.messageId,
12273
+ error: healError instanceof Error ? healError.message : String(healError)
12274
+ });
12275
+ break;
12276
+ }
12277
+ attempt -= 1;
12278
+ continue;
12279
+ }
12230
12280
  }
12231
12281
  }
12232
12282
  this.failed = true;
@@ -12246,6 +12296,26 @@ var ResilientCardStreamController = class {
12246
12296
  });
12247
12297
  }
12248
12298
  }
12299
+ /**
12300
+ * Re-create the process card at the conversation tail after the target message
12301
+ * was withdrawn (Feishu `230011` / "The message was withdrawn"). The message
12302
+ * is already gone, so there is nothing to recall: we send a fresh card carrying
12303
+ * the latest snapshot and rebind the controller to its new message id. Unlike
12304
+ * `reanchor()` this must NOT `await this.inFlight`, because we are already
12305
+ * running inside the in-flight flush; the recovery budget
12306
+ * (`MAX_CARD_WITHDRAW_RECOVERIES`) is enforced by the caller so a hostile loop
12307
+ * cannot spawn cards forever.
12308
+ */
12309
+ async healFromWithdrawal() {
12310
+ const card = this.latest;
12311
+ if (card === void 0) throw new Error("Feishu card re-creation has no snapshot to send");
12312
+ this.dirty = false;
12313
+ const sent = await this.channel.send(this.chatId, { card }, {});
12314
+ if (!sent.messageId) throw new Error("Feishu card re-creation returned no message_id");
12315
+ this.messageId = sent.messageId;
12316
+ this.failed = false;
12317
+ this.recoveries += 1;
12318
+ }
12249
12319
  /**
12250
12320
  * Recall the current card and re-create it as the newest top-level message in
12251
12321
  * the chat, rebinding the controller to the fresh message id. Because Feishu