dsh-feishu-bot 0.19.8 → 0.19.9

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
@@ -12138,6 +12138,34 @@ async function tryHandleCommand(text, ctx) {
12138
12138
  import { createHash } from "crypto";
12139
12139
  var CARD_STREAM_THROTTLE_MS = 100;
12140
12140
  var CARD_PATCH_ATTEMPTS = 2;
12141
+ var MAX_CARD_WITHDRAW_RECOVERIES = 3;
12142
+ function isMessageWithdrawn(error) {
12143
+ const seen = /* @__PURE__ */ new Set();
12144
+ const stack = [error];
12145
+ while (stack.length > 0) {
12146
+ const node = stack.pop();
12147
+ if (node === null || node === void 0) continue;
12148
+ if (typeof node === "string") {
12149
+ if (node.toLowerCase().includes("withdrawn")) return true;
12150
+ continue;
12151
+ }
12152
+ if (typeof node !== "object" || seen.has(node)) continue;
12153
+ seen.add(node);
12154
+ if (Array.isArray(node)) {
12155
+ for (const item of node) stack.push(item);
12156
+ continue;
12157
+ }
12158
+ const record = node;
12159
+ if (record["code"] === 230011) return true;
12160
+ if (typeof record["message"] === "string" && record["message"].toLowerCase().includes("withdrawn")) return true;
12161
+ if (typeof record["msg"] === "string" && record["msg"].toLowerCase().includes("withdrawn")) return true;
12162
+ for (const key of ["response", "data", "error", "errors"]) {
12163
+ const value = record[key];
12164
+ if (value !== void 0 && value !== null) stack.push(value);
12165
+ }
12166
+ }
12167
+ return false;
12168
+ }
12141
12169
  function toLarkSendOptions(options) {
12142
12170
  const sendOptions = {};
12143
12171
  if (options?.replyTo) sendOptions.replyTo = options.replyTo;
@@ -12172,6 +12200,7 @@ var ResilientCardStreamController = class {
12172
12200
  timer;
12173
12201
  inFlight;
12174
12202
  reanchorInFlight;
12203
+ recoveries = 0;
12175
12204
  async update(card) {
12176
12205
  if (this.closed || this.failed) return;
12177
12206
  this.latest = card;
@@ -12219,6 +12248,7 @@ var ResilientCardStreamController = class {
12219
12248
  for (let attempt = 1; attempt <= CARD_PATCH_ATTEMPTS; attempt += 1) {
12220
12249
  try {
12221
12250
  await this.channel.updateCard(this.messageId, snapshot);
12251
+ this.recoveries = 0;
12222
12252
  return;
12223
12253
  } catch (error) {
12224
12254
  lastError = error;
@@ -12227,6 +12257,19 @@ var ResilientCardStreamController = class {
12227
12257
  attempt,
12228
12258
  error: error instanceof Error ? error.message : String(error)
12229
12259
  });
12260
+ if (isMessageWithdrawn(error) && this.recoveries < MAX_CARD_WITHDRAW_RECOVERIES) {
12261
+ try {
12262
+ await this.healFromWithdrawal();
12263
+ } catch (healError) {
12264
+ log.warn("lark-card-stream", "heal-failed", {
12265
+ messageId: this.messageId,
12266
+ error: healError instanceof Error ? healError.message : String(healError)
12267
+ });
12268
+ break;
12269
+ }
12270
+ attempt -= 1;
12271
+ continue;
12272
+ }
12230
12273
  }
12231
12274
  }
12232
12275
  this.failed = true;
@@ -12246,6 +12289,26 @@ var ResilientCardStreamController = class {
12246
12289
  });
12247
12290
  }
12248
12291
  }
12292
+ /**
12293
+ * Re-create the process card at the conversation tail after the target message
12294
+ * was withdrawn (Feishu `230011` / "The message was withdrawn"). The message
12295
+ * is already gone, so there is nothing to recall: we send a fresh card carrying
12296
+ * the latest snapshot and rebind the controller to its new message id. Unlike
12297
+ * `reanchor()` this must NOT `await this.inFlight`, because we are already
12298
+ * running inside the in-flight flush; the recovery budget
12299
+ * (`MAX_CARD_WITHDRAW_RECOVERIES`) is enforced by the caller so a hostile loop
12300
+ * cannot spawn cards forever.
12301
+ */
12302
+ async healFromWithdrawal() {
12303
+ const card = this.latest;
12304
+ if (card === void 0) throw new Error("Feishu card re-creation has no snapshot to send");
12305
+ this.dirty = false;
12306
+ const sent = await this.channel.send(this.chatId, { card }, {});
12307
+ if (!sent.messageId) throw new Error("Feishu card re-creation returned no message_id");
12308
+ this.messageId = sent.messageId;
12309
+ this.failed = false;
12310
+ this.recoveries += 1;
12311
+ }
12249
12312
  /**
12250
12313
  * Recall the current card and re-create it as the newest top-level message in
12251
12314
  * the chat, rebinding the controller to the fresh message id. Because Feishu