@vibe-lark/larkpal 0.1.19 → 0.1.20

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.
Files changed (2) hide show
  1. package/dist/main.mjs +175 -38
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -6973,6 +6973,13 @@ const THROTTLE_CONSTANTS = {
6973
6973
  * mutex-guarded flushing, and reflush-on-conflict. Contains no
6974
6974
  * business logic — the actual flush work is provided via a callback.
6975
6975
  */
6976
+ /** flush 超时保护:单次 flush API 调用最大允许时间(超过后释放锁,允许后续 flush 继续) */
6977
+ const FLUSH_TIMEOUT_MS = 15e3;
6978
+ var FlushTimeoutError = class extends Error {
6979
+ constructor() {
6980
+ super("flush timeout");
6981
+ }
6982
+ };
6976
6983
  var FlushController = class {
6977
6984
  flushInProgress = false;
6978
6985
  flushResolvers = [];
@@ -7004,6 +7011,9 @@ var FlushController = class {
7004
7011
  *
7005
7012
  * If a flush is already in progress, marks needsReflush so a
7006
7013
  * follow-up flush fires immediately after the current one completes.
7014
+ *
7015
+ * 包含超时保护:如果单次 flush 超过 FLUSH_TIMEOUT_MS,强制释放锁并触发 reflush,
7016
+ * 防止飞书 API 偶发慢响应导致卡片长时间无法更新。
7007
7017
  */
7008
7018
  async flush() {
7009
7019
  if (!this.cardMessageReady() || this.flushInProgress || this.isCompleted) {
@@ -7014,8 +7024,19 @@ var FlushController = class {
7014
7024
  this.needsReflush = false;
7015
7025
  this.lastUpdateTime = Date.now();
7016
7026
  try {
7017
- await this.doFlush();
7027
+ let timeoutTimer;
7028
+ const timeoutPromise = new Promise((_, reject) => {
7029
+ timeoutTimer = setTimeout(() => reject(new FlushTimeoutError()), FLUSH_TIMEOUT_MS);
7030
+ });
7031
+ try {
7032
+ await Promise.race([this.doFlush(), timeoutPromise]);
7033
+ } finally {
7034
+ clearTimeout(timeoutTimer);
7035
+ }
7018
7036
  this.lastUpdateTime = Date.now();
7037
+ } catch (err) {
7038
+ if (!(err instanceof FlushTimeoutError)) throw err;
7039
+ this.needsReflush = true;
7019
7040
  } finally {
7020
7041
  this.flushInProgress = false;
7021
7042
  const resolvers = this.flushResolvers;
@@ -7409,6 +7430,21 @@ var UnavailableGuard = class {
7409
7430
  }
7410
7431
  };
7411
7432
  //#endregion
7433
+ //#region src/card/active-card-store.ts
7434
+ const store = /* @__PURE__ */ new Map();
7435
+ /** 注册活跃的 card controller(每条消息调度时) */
7436
+ function registerActiveCard(sessionId, controller) {
7437
+ store.set(sessionId, controller);
7438
+ }
7439
+ /** 注销(controller 进入终态后调用) */
7440
+ function unregisterActiveCard(sessionId) {
7441
+ store.delete(sessionId);
7442
+ }
7443
+ /** 获取当前活跃的 card controller */
7444
+ function getActiveCard(sessionId) {
7445
+ return store.get(sessionId);
7446
+ }
7447
+ //#endregion
7412
7448
  //#region src/card/streaming-card-controller.ts
7413
7449
  /**
7414
7450
  * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
@@ -7712,6 +7748,7 @@ var StreamingCardController = class StreamingCardController {
7712
7748
  this.flush.complete();
7713
7749
  this.disposeShutdownHook?.();
7714
7750
  this.disposeShutdownHook = null;
7751
+ unregisterActiveCard(this.deps.sessionKey);
7715
7752
  if (this.phase === "terminated" || this.phase === "creation_failed") clearToolUseTraceRun(this.deps.sessionKey);
7716
7753
  }
7717
7754
  markToolUseActivity() {
@@ -7889,21 +7926,6 @@ var StreamingCardController = class StreamingCardController {
7889
7926
  const idleEffectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
7890
7927
  try {
7891
7928
  if (this.cardKit.cardMessageId) {
7892
- if (idleEffectiveCardId) {
7893
- const seqBeforeClose = this.cardKit.cardKitSequence;
7894
- this.cardKit.cardKitSequence += 1;
7895
- log$13.info("onIdle: closing streaming mode", {
7896
- seqBefore: seqBeforeClose,
7897
- seqAfter: this.cardKit.cardKitSequence
7898
- });
7899
- await setCardStreamingMode({
7900
- cfg: this.deps.cfg,
7901
- cardId: idleEffectiveCardId,
7902
- streamingMode: false,
7903
- sequence: this.cardKit.cardKitSequence,
7904
- accountId: this.deps.accountId
7905
- });
7906
- }
7907
7929
  const isNoReplyLeak = !this.text.completedText && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim());
7908
7930
  const displayText = this.text.completedText || (isNoReplyLeak ? "" : this.text.accumulatedText) || "Done.";
7909
7931
  if (!this.text.completedText && !this.text.accumulatedText) log$13.warn("reply completed without visible text, using empty-reply fallback");
@@ -7941,33 +7963,65 @@ var StreamingCardController = class StreamingCardController {
7941
7963
  isAborted: this._userAborted,
7942
7964
  stepTitles: idleToolUseDisplay?.steps?.map((s) => s.title)
7943
7965
  });
7944
- if (idleEffectiveCardId) {
7945
- const seqBeforeUpdate = this.cardKit.cardKitSequence;
7946
- this.cardKit.cardKitSequence += 1;
7947
- log$13.info("onIdle: updating final card", {
7948
- seqBefore: seqBeforeUpdate,
7949
- seqAfter: this.cardKit.cardKitSequence
7950
- });
7951
- await updateCardKitCard({
7966
+ const MAX_FINAL_UPDATE_RETRIES = 3;
7967
+ const FINAL_UPDATE_RETRY_DELAY_MS = 2e3;
7968
+ for (let attempt = 1; attempt <= MAX_FINAL_UPDATE_RETRIES; attempt++) try {
7969
+ if (idleEffectiveCardId) {
7970
+ const seqBeforeClose = this.cardKit.cardKitSequence;
7971
+ this.cardKit.cardKitSequence += 1;
7972
+ log$13.info("onIdle: closing streaming mode", {
7973
+ attempt,
7974
+ seqBefore: seqBeforeClose,
7975
+ seqAfter: this.cardKit.cardKitSequence
7976
+ });
7977
+ await setCardStreamingMode({
7978
+ cfg: this.deps.cfg,
7979
+ cardId: idleEffectiveCardId,
7980
+ streamingMode: false,
7981
+ sequence: this.cardKit.cardKitSequence,
7982
+ accountId: this.deps.accountId
7983
+ });
7984
+ const seqBeforeUpdate = this.cardKit.cardKitSequence;
7985
+ this.cardKit.cardKitSequence += 1;
7986
+ log$13.info("onIdle: updating final card", {
7987
+ attempt,
7988
+ seqBefore: seqBeforeUpdate,
7989
+ seqAfter: this.cardKit.cardKitSequence
7990
+ });
7991
+ await updateCardKitCard({
7992
+ cfg: this.deps.cfg,
7993
+ cardId: idleEffectiveCardId,
7994
+ card: toCardKit2(completeCard),
7995
+ sequence: this.cardKit.cardKitSequence,
7996
+ accountId: this.deps.accountId
7997
+ });
7998
+ } else await updateCardFeishu({
7952
7999
  cfg: this.deps.cfg,
7953
- cardId: idleEffectiveCardId,
7954
- card: toCardKit2(completeCard),
7955
- sequence: this.cardKit.cardKitSequence,
8000
+ messageId: this.cardKit.cardMessageId,
8001
+ card: completeCard,
7956
8002
  accountId: this.deps.accountId
7957
8003
  });
7958
- } else await updateCardFeishu({
7959
- cfg: this.deps.cfg,
7960
- messageId: this.cardKit.cardMessageId,
7961
- card: completeCard,
7962
- accountId: this.deps.accountId
7963
- });
7964
- log$13.info("reply completed, card finalized", {
7965
- elapsedMs: this.elapsed(),
7966
- isCardKit: !!idleEffectiveCardId
7967
- });
8004
+ log$13.info("reply completed, card finalized", {
8005
+ elapsedMs: this.elapsed(),
8006
+ isCardKit: !!idleEffectiveCardId,
8007
+ attempt
8008
+ });
8009
+ break;
8010
+ } catch (retryErr) {
8011
+ log$13.warn("final card update attempt failed", {
8012
+ attempt,
8013
+ maxRetries: MAX_FINAL_UPDATE_RETRIES,
8014
+ error: String(retryErr)
8015
+ });
8016
+ if (attempt < MAX_FINAL_UPDATE_RETRIES) await new Promise((resolve) => setTimeout(resolve, FINAL_UPDATE_RETRY_DELAY_MS * attempt));
8017
+ else log$13.error("final card update exhausted all retries, card may remain in streaming state", {
8018
+ cardId: idleEffectiveCardId,
8019
+ messageId: this.cardKit.cardMessageId
8020
+ });
8021
+ }
7968
8022
  }
7969
8023
  } catch (err) {
7970
- log$13.warn("final card update failed", { error: String(err) });
8024
+ log$13.error("final card update failed (outer)", { error: String(err) });
7971
8025
  } finally {
7972
8026
  clearToolUseTraceRun(this.deps.sessionKey);
7973
8027
  }
@@ -8041,6 +8095,78 @@ var StreamingCardController = class StreamingCardController {
8041
8095
  clearToolUseTraceRun(this.deps.sessionKey);
8042
8096
  }
8043
8097
  }
8098
+ /**
8099
+ * 强制关闭 streaming 状态(兜底方法)。
8100
+ *
8101
+ * 当进程已退出且卡片可能因 API 调用失败仍停留在 streaming 状态时,
8102
+ * 由 session-control-handler 调用,强制调用 closeStreaming + updateCard。
8103
+ * 即使已经是 terminal phase 也会尝试执行 API 调用。
8104
+ */
8105
+ async forceCloseStreaming() {
8106
+ const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
8107
+ if (!effectiveCardId && !this.cardKit.cardMessageId) {
8108
+ log$13.warn("forceCloseStreaming: no card to close");
8109
+ return;
8110
+ }
8111
+ log$13.info("forceCloseStreaming: 强制终态化卡片", {
8112
+ cardId: effectiveCardId,
8113
+ messageId: this.cardKit.cardMessageId,
8114
+ phase: this.phase
8115
+ });
8116
+ this._userAborted = true;
8117
+ try {
8118
+ const displayText = this.text.accumulatedText || this.text.completedText || "Done.";
8119
+ const idleToolUseDisplay = this.computeToolUseDisplay();
8120
+ const terminalContent = prepareTerminalCardContent({
8121
+ text: displayText,
8122
+ reasoningText: this.reasoning.accumulatedReasoningText || void 0
8123
+ }, this.imageResolver);
8124
+ const completeCard = buildCardContent("complete", {
8125
+ text: terminalContent.text,
8126
+ reasoningText: terminalContent.reasoningText,
8127
+ reasoningElapsedMs: this.reasoning.reasoningElapsedMs || void 0,
8128
+ toolUseSteps: idleToolUseDisplay?.steps,
8129
+ toolUseTitleSuffix: this.computeToolUseTitleSuffix(idleToolUseDisplay),
8130
+ toolUseElapsedMs: this.visibleToolUseElapsedMs,
8131
+ showToolUse: this.deps.toolUseDisplay.showToolUse,
8132
+ elapsedMs: this.elapsed(),
8133
+ isAborted: true,
8134
+ footer: this.deps.resolvedFooter,
8135
+ sessionId: this.deps.sessionKey,
8136
+ showUndoButton: false
8137
+ });
8138
+ if (effectiveCardId) {
8139
+ this.cardKit.cardKitSequence += 1;
8140
+ await setCardStreamingMode({
8141
+ cfg: this.deps.cfg,
8142
+ cardId: effectiveCardId,
8143
+ streamingMode: false,
8144
+ sequence: this.cardKit.cardKitSequence,
8145
+ accountId: this.deps.accountId
8146
+ });
8147
+ this.cardKit.cardKitSequence += 1;
8148
+ await updateCardKitCard({
8149
+ cfg: this.deps.cfg,
8150
+ cardId: effectiveCardId,
8151
+ card: toCardKit2(completeCard),
8152
+ sequence: this.cardKit.cardKitSequence,
8153
+ accountId: this.deps.accountId
8154
+ });
8155
+ } else if (this.cardKit.cardMessageId) await updateCardFeishu({
8156
+ cfg: this.deps.cfg,
8157
+ messageId: this.cardKit.cardMessageId,
8158
+ card: completeCard,
8159
+ accountId: this.deps.accountId
8160
+ });
8161
+ log$13.info("forceCloseStreaming: 卡片已强制终态化");
8162
+ if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
8163
+ } catch (err) {
8164
+ log$13.error("forceCloseStreaming failed", { error: String(err) });
8165
+ } finally {
8166
+ unregisterActiveCard(this.deps.sessionKey);
8167
+ clearToolUseTraceRun(this.deps.sessionKey);
8168
+ }
8169
+ }
8044
8170
  async ensureCardCreated() {
8045
8171
  if (this.guard.shouldSkip("ensureCardCreated.precheck")) return;
8046
8172
  if (this.cardKit.cardMessageId || this.phase === "creation_failed" || this.isTerminalPhase) return;
@@ -8805,6 +8931,7 @@ async function dispatchToCC(params) {
8805
8931
  placeholderMessageId: params.placeholderMessageId
8806
8932
  };
8807
8933
  const cardController = new StreamingCardController(cardDeps);
8934
+ registerActiveCard(route.sessionId, cardController);
8808
8935
  log$11.info("StreamingCardController 已创建", {
8809
8936
  sessionId: route.sessionId,
8810
8937
  chatId,
@@ -8987,6 +9114,7 @@ async function dispatchTeammateEval(params) {
8987
9114
  model: false
8988
9115
  }
8989
9116
  });
9117
+ registerActiveCard(teammateSessionKey, cardController);
8990
9118
  bridge = new CCStreamBridge(cardController, {
8991
9119
  autoCompleteOnTurnEnd: true,
8992
9120
  sessionKey: teammateSessionKey
@@ -11884,6 +12012,15 @@ async function handleAbortSession(params) {
11884
12012
  sessionId,
11885
12013
  status: proc?.status
11886
12014
  });
12015
+ const activeCard = getActiveCard(sessionId);
12016
+ if (activeCard) {
12017
+ log.info("abort_session: 进程已退出但卡片仍在活跃状态,强制终态化", { sessionId });
12018
+ activeCard.forceCloseStreaming();
12019
+ return { toast: {
12020
+ type: "success",
12021
+ content: "任务已结束,正在更新卡片"
12022
+ } };
12023
+ }
11887
12024
  return { toast: {
11888
12025
  type: "info",
11889
12026
  content: "当前没有正在执行的任务"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",