larkway 0.3.43 → 0.3.45

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/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  You @ the bot in a Feishu thread. It runs on your machine — reading your real codebase, executing commands, opening MRs — and posts the result back. You define what the agent knows and what it can do. Larkway just carries the messages.
10
10
 
11
- **Current release: v0.3.43**
11
+ **Current release: v0.3.45**
12
12
 
13
13
  ---
14
14
 
package/README.zh.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  你在飞书话题里 @ bot,它在你的机器上运行——读真实代码库、执行命令、开 MR——把结果贴回飞书。你定义 agent 知道什么、能做什么。Larkway 只负责传递消息。
10
10
 
11
- **当前版本:v0.3.43**
11
+ **当前版本:v0.3.45**
12
12
 
13
13
  ---
14
14
 
package/dist/cli/index.js CHANGED
@@ -112530,6 +112530,11 @@ var init_messageLookupClient = __esm({
112530
112530
  const cached = this.#cache.get(messageId);
112531
112531
  if (cached) return cached;
112532
112532
  }
112533
+ const first = await this.#getOnce(messageId);
112534
+ if (first) return first;
112535
+ return this.#getOnce(messageId);
112536
+ }
112537
+ async #getOnce(messageId) {
112533
112538
  try {
112534
112539
  const channel = this.#resolveChannel();
112535
112540
  if (!channel) return void 0;
package/dist/main.js CHANGED
@@ -116684,6 +116684,11 @@ var ChannelMessageLookupClient = class {
116684
116684
  const cached = this.#cache.get(messageId);
116685
116685
  if (cached) return cached;
116686
116686
  }
116687
+ const first = await this.#getOnce(messageId);
116688
+ if (first) return first;
116689
+ return this.#getOnce(messageId);
116690
+ }
116691
+ async #getOnce(messageId) {
116687
116692
  try {
116688
116693
  const channel = this.#resolveChannel();
116689
116694
  if (!channel) return void 0;
@@ -119225,12 +119230,18 @@ function renderTaskRootBlock(taskRoot) {
119225
119230
  `task_root_claimed: ${taskRoot.claimed ? "yes" : "no"}`
119226
119231
  ];
119227
119232
  if (taskRoot.claimed) {
119228
- lines.push(
119229
- "\u672C\u8BDD\u9898\u5DF2\u8BA4\u9886\u8FD9\u4E2A\u4EFB\u52A1\u3002\u7EF4\u62A4\u9762\u53EA\u6709\u4EFB\u52A1\u8BC4\u8BBA:\u4EC5\u5728\u4EA4\u4ED8/\u5931\u8D25/\u7B49\u62CD\u677F\u4E09\u7C7B\u91CC\u7A0B\u7891\u8282\u70B9\u53D1\u4EFB\u52A1\u8BC4\u8BBA(lark-cli task comments create);\u771F\u6B63\u4EA4\u4ED8\u7684\u90A3\u4E00\u8F6E\u5728 state.json \u7684 task_handle \u91CC\u58F0\u660E done: true,\u5E76\u53D1\u4E00\u6761\u300C\u5DF2\u4EA4\u4ED8,\u770B\u8FC7\u8BF7\u70B9\u5B8C\u6210\u300D\u8BC4\u8BBA\u3002\u4E0D\u8981\u4FEE\u6539\u4EFB\u52A1\u63CF\u8FF0\u3001\u4E0D\u8981\u52FE\u5B8C\u6210/reopen\u2014\u2014\u5B8C\u6210\u6C38\u8FDC\u7531\u4EBA\u5728\u4EFB\u52A1\u4E2D\u5FC3\u70B9\u3002"
119230
- );
119233
+ if (taskRoot.justClaimed) {
119234
+ lines.push(
119235
+ `bridge \u5DF2\u81EA\u52A8\u8BA4\u9886\u8FD9\u4E2A\u4EFB\u52A1(\u673A\u68B0\u7ED1\u5B9A,\u4E0D\u9700\u8981\u4F60\u518D\u5199 state.json \u8BA4\u9886),\u4F46**\u8BA4\u9886\u8BC4\u8BBA\u8FD8\u6B20\u7740**\u3002\u672C\u8F6E\u8BF7:\u2460 \u5148\u8BFB\u4E00\u904D\u8BE5\u4EFB\u52A1\u7684\u73B0\u6709\u8BC4\u8BBA(lark-cli task comments list)\u2014\u2014\u5DF2\u6709**\u5176\u4ED6 agent** \u7684\u8BA4\u9886\u58F0\u660E\u8BF4\u660E\u5BF9\u65B9\u5DF2\u63A5\u624B,\u4E0D\u8981\u518D\u53D1\u8BA4\u9886\u8BC4\u8BBA(\u91CD\u590D\u8BC4\u8BBA=\u9A9A\u6270),\u914D\u5408\u5E72\u6D3B\u5373\u53EF;\u2461 \u8BC4\u8BBA\u533A\u6CA1\u6709\u522B\u4EBA\u8BA4\u9886 \u2192 \u53D1\u4E00\u6761**\u8BA4\u9886\u8BC4\u8BBA**(lark-cli task comments create):\u4E00\u53E5\u8BA4\u9886\u58F0\u660E${taskRoot.topicLink ? " + \u4E0A\u9762\u7684 topic_link(\u7528\u6237\u70B9\u5B83\u76F4\u8FBE\u672C\u8BDD\u9898\u2014\u2014\u4EFB\u52A1\u2192\u8BDD\u9898\u552F\u4E00\u56DE\u8DF3,\u522B\u7701\u7565)" : ""};\u2462 \u6B63\u5E38\u5E72\u6D3B(\u4EFB\u52A1\u6807\u9898/\u63CF\u8FF0\u5C31\u662F\u9700\u6C42)\u3002\u4E4B\u540E\u7EF4\u62A4\u9762\u53EA\u6709\u4EFB\u52A1\u8BC4\u8BBA(\u4EA4\u4ED8/\u5931\u8D25/\u7B49\u62CD\u677F\u624D\u53D1,\u8FC7\u7A0B\u788E\u788E\u5FF5\u4E0D\u53D1);\u771F\u6B63\u4EA4\u4ED8\u7684\u90A3\u4E00\u8F6E\u5728 state.json \u7684 task_handle \u91CC\u58F0\u660E done: true \u5E76\u53D1\u300C\u5DF2\u4EA4\u4ED8,\u770B\u8FC7\u8BF7\u70B9\u5B8C\u6210\u300D\u8BC4\u8BBA\u3002\u4E0D\u8981\u4FEE\u6539\u4EFB\u52A1\u63CF\u8FF0\u3001\u4E0D\u8981\u52FE\u5B8C\u6210/reopen\u2014\u2014\u5B8C\u6210\u6C38\u8FDC\u7531\u4EBA\u5728\u4EFB\u52A1\u4E2D\u5FC3\u70B9\u3002`
119236
+ );
119237
+ } else {
119238
+ lines.push(
119239
+ "\u672C\u8BDD\u9898\u5DF2\u8BA4\u9886\u8FD9\u4E2A\u4EFB\u52A1\u3002\u7EF4\u62A4\u9762\u53EA\u6709\u4EFB\u52A1\u8BC4\u8BBA:\u4EC5\u5728\u4EA4\u4ED8/\u5931\u8D25/\u7B49\u62CD\u677F\u4E09\u7C7B\u91CC\u7A0B\u7891\u8282\u70B9\u53D1\u4EFB\u52A1\u8BC4\u8BBA(lark-cli task comments create);\u771F\u6B63\u4EA4\u4ED8\u7684\u90A3\u4E00\u8F6E\u5728 state.json \u7684 task_handle \u91CC\u58F0\u660E done: true,\u5E76\u53D1\u4E00\u6761\u300C\u5DF2\u4EA4\u4ED8,\u770B\u8FC7\u8BF7\u70B9\u5B8C\u6210\u300D\u8BC4\u8BBA\u3002\u4E0D\u8981\u4FEE\u6539\u4EFB\u52A1\u63CF\u8FF0\u3001\u4E0D\u8981\u52FE\u5B8C\u6210/reopen\u2014\u2014\u5B8C\u6210\u6C38\u8FDC\u7531\u4EBA\u5728\u4EFB\u52A1\u4E2D\u5FC3\u70B9\u3002"
119240
+ );
119241
+ }
119231
119242
  } else {
119232
119243
  lines.push(
119233
- `\u672C\u8F6E\u8BF7\u987A\u5E26\u9759\u9ED8\u8BA4\u9886,\u5206\u4E24\u6B65:\u2460 \u5148\u8BFB\u4E00\u904D\u8BE5\u4EFB\u52A1\u7684\u73B0\u6709\u8BC4\u8BBA(lark-cli task comments list)\u2014\u2014\u5982\u679C\u5DF2\u6709**\u5176\u4ED6 agent** \u7684\u8BA4\u9886\u58F0\u660E,\u8BF4\u660E\u540C\u8BDD\u9898\u7684\u53E6\u4E00\u4E2A bot \u5DF2\u63A5\u624B,\u672C\u8F6E\u4E0D\u8BA4\u9886\u3001\u4E0D\u58F0\u5F20\u3001\u6B63\u5E38\u56DE\u7B54\u5373\u53EF(\u53CC @ \u7ADE\u6001\u62A4\u680F:\u91CD\u590D\u8BA4\u9886 = \u53CC\u4EFD\u7EF4\u62A4 + \u53CC\u4EFD\u5DE1\u68C0\u9A9A\u6270);\u2461 \u6CA1\u6709\u522B\u4EBA\u8BA4\u9886 \u2192 \u628A\u4E0A\u9762\u7684 task_guid \u5199\u5165 state.json \u7684 task_handle.guid,\u5E76\u53D1\u4E00\u6761\u8BA4\u9886\u8BC4\u8BBA(lark-cli task comments create),\u5185\u5BB9 = \u4E00\u53E5\u8BA4\u9886\u58F0\u660E${taskRoot.topicLink ? " + \u4E0A\u9762\u7684 topic_link(\u7528\u6237\u70B9\u5B83\u76F4\u8FBE\u672C\u8BDD\u9898)" : ""}\u3002\u4E4B\u540E\u7EF4\u62A4\u9762\u53EA\u6709\u4EFB\u52A1\u8BC4\u8BBA(\u4EA4\u4ED8/\u5931\u8D25/\u7B49\u62CD\u677F\u624D\u53D1,\u8FC7\u7A0B\u788E\u788E\u5FF5\u4E0D\u53D1);\u4E0D\u8981\u4FEE\u6539\u4EFB\u52A1\u63CF\u8FF0\u3001\u4E0D\u8981\u52FE\u5B8C\u6210\u2014\u2014\u5B8C\u6210\u6C38\u8FDC\u7531\u4EBA\u70B9\u3002`
119244
+ `bridge \u672A\u80FD\u81EA\u52A8\u8BA4\u9886\u8FD9\u4E2A\u4EFB\u52A1\u2014\u2014\u6700\u5E38\u89C1\u7684\u539F\u56E0\u662F\u5B83\u5DF2\u88AB**\u53E6\u4E00\u4E2A\u8BDD\u9898\u6216\u53E6\u4E00\u4E2A agent** \u8BA4\u9886(\u673A\u68B0\u5B88\u536B\u62D2\u7EDD\u91CD\u590D\u8BA4\u9886),\u4E5F\u53EF\u80FD\u662F\u8BA4\u9886\u901A\u9053\u672A\u63A5\u5165\u3002\u672C\u8F6E\u8BF7:\u2460 \u5148\u8BFB\u4E00\u904D\u8BE5\u4EFB\u52A1\u7684\u73B0\u6709\u8BC4\u8BBA(lark-cli task comments list)\u2014\u2014\u5DF2\u6709\u5176\u4ED6 agent \u7684\u8BA4\u9886\u58F0\u660E \u2192 \u5BF9\u65B9\u5DF2\u63A5\u624B,\u4F60\u53EA\u662F\u88AB\u53EB\u6765\u534F\u4F5C:\u5E72\u597D\u88AB @ \u6765\u505A\u7684\u4E8B,\u4E0D\u8981\u8BA4\u9886\u3001\u4E0D\u8981\u91CD\u590D\u53D1\u8BA4\u9886\u8BC4\u8BBA;\u2461 \u8BC4\u8BBA\u533A\u6CA1\u6709\u4EFB\u4F55\u8BA4\u9886\u3001\u4E14\u4F60\u5224\u65AD\u8BE5\u7531\u4F60\u63A5\u624B \u2192 \u628A\u4E0A\u9762\u7684 task_guid \u5199\u5165 state.json \u7684 task_handle.guid,\u5E76\u53D1\u4E00\u6761\u8BA4\u9886\u8BC4\u8BBA(lark-cli task comments create),\u5185\u5BB9 = \u4E00\u53E5\u8BA4\u9886\u58F0\u660E${taskRoot.topicLink ? " + \u4E0A\u9762\u7684 topic_link(\u7528\u6237\u70B9\u5B83\u76F4\u8FBE\u672C\u8BDD\u9898)" : ""}\u3002\u4E4B\u540E\u7EF4\u62A4\u9762\u53EA\u6709\u4EFB\u52A1\u8BC4\u8BBA(\u4EA4\u4ED8/\u5931\u8D25/\u7B49\u62CD\u677F\u624D\u53D1,\u8FC7\u7A0B\u788E\u788E\u5FF5\u4E0D\u53D1);\u4E0D\u8981\u4FEE\u6539\u4EFB\u52A1\u63CF\u8FF0\u3001\u4E0D\u8981\u52FE\u5B8C\u6210\u2014\u2014\u5B8C\u6210\u6C38\u8FDC\u7531\u4EBA\u70B9\u3002`
119234
119245
  );
119235
119246
  }
119236
119247
  lines.push("</task-root>");
@@ -122447,6 +122458,15 @@ var BridgeHandler = class {
122447
122458
  * broken handoff and fire a spurious nudge. Read via getThreadReceivedAt().
122448
122459
  */
122449
122460
  threadReceivedAt = /* @__PURE__ */ new Map();
122461
+ /**
122462
+ * v4.2 (docs/task-handle.md §13 revision 5): per-thread OUTCOME of the most
122463
+ * recent finished turn ("completed" | "failed"), recorded at finalize.
122464
+ * StallDetector reads a PEER handler's map via getThreadLastOutcome to tell
122465
+ * "the peer finished successfully" apart from "the peer crashed" — a failed
122466
+ * finish must NOT resolve a handoff. In-memory like threadReceivedAt (empty
122467
+ * after restart → detector falls back to its documented restart posture).
122468
+ */
122469
+ threadLastOutcome = /* @__PURE__ */ new Map();
122450
122470
  constructor(deps) {
122451
122471
  this.deps = deps;
122452
122472
  }
@@ -122459,6 +122479,10 @@ var BridgeHandler = class {
122459
122479
  * — callers must apply their own startup quiet-period before treating that
122460
122480
  * as evidence of a truly broken handoff.
122461
122481
  */
122482
+ /** v4.2 — see threadLastOutcome's field doc. */
122483
+ getThreadLastOutcome(threadId) {
122484
+ return this.threadLastOutcome.get(threadId);
122485
+ }
122462
122486
  getThreadReceivedAt(threadId) {
122463
122487
  return this.threadReceivedAt.get(threadId);
122464
122488
  }
@@ -122625,7 +122649,33 @@ var BridgeHandler = class {
122625
122649
  const eventStartedAt = Date.now();
122626
122650
  let taskRootInfo;
122627
122651
  let taskCardAnchorId;
122652
+ let taskRootJustClaimed = false;
122628
122653
  let deferredTaskRootProbe;
122654
+ const autoClaimTaskRoot = async (guid) => {
122655
+ if (!this.deps.taskHandleClaim || !botId) return;
122656
+ const alreadyThis = this.deps.taskHandleClaimGuidLookup?.(threadId) === guid;
122657
+ if (alreadyThis) return;
122658
+ if (this.deps.taskGuidClaimedByOtherBot?.(guid)) return;
122659
+ try {
122660
+ await this.deps.taskHandleClaim({
122661
+ botId,
122662
+ threadId,
122663
+ chatId: parsed.chatId,
122664
+ taskGuid: guid,
122665
+ mode: "comment",
122666
+ // Round-2 fix: the mechanical auto-claim must never REPLACE an
122667
+ // existing different-guid claim on this thread (same charter as the
122668
+ // v3 auto-bind guard) — only the agent's own re-declaration may.
122669
+ onlyIfThreadUnclaimed: true,
122670
+ // Round-2 fix: persist "claim comment still owed" so a first-turn
122671
+ // crash after the claim doesn't lose the task→topic backlink.
122672
+ claimCommentPending: true
122673
+ });
122674
+ taskRootJustClaimed = this.deps.taskHandleClaimGuidLookup ? this.deps.taskHandleClaimGuidLookup(threadId) === guid : true;
122675
+ } catch (err) {
122676
+ console.warn("[bridge.handler] task-root auto-claim failed (continuing):", err);
122677
+ }
122678
+ };
122629
122679
  {
122630
122680
  const realTopic = realTopicThreadId(parsed.raw.thread_id);
122631
122681
  const rootCandidate = (typeof parsed.raw.root_id === "string" && parsed.raw.root_id ? parsed.raw.root_id : void 0) ?? (typeof parsed.raw.parent_id === "string" && parsed.raw.parent_id ? parsed.raw.parent_id : void 0);
@@ -122646,6 +122696,7 @@ var BridgeHandler = class {
122646
122696
  };
122647
122697
  taskCardAnchorId = rootCandidate;
122648
122698
  threadId = rootCandidate;
122699
+ await autoClaimTaskRoot(todo.taskGuid);
122649
122700
  }
122650
122701
  }
122651
122702
  }
@@ -123185,6 +123236,7 @@ var BridgeHandler = class {
123185
123236
  summary: todo.summaryText,
123186
123237
  topicLink: rawThreadId ? buildTopicDeepLink(parsed.chatId, rawThreadId) : probeThreadId ? buildTopicDeepLink(parsed.chatId, probeThreadId) : void 0
123187
123238
  };
123239
+ await autoClaimTaskRoot(todo.taskGuid);
123188
123240
  }
123189
123241
  }
123190
123242
  }
@@ -123232,7 +123284,8 @@ var BridgeHandler = class {
123232
123284
  // Exact-guid comparison (see taskHandleClaimGuidLookup's doc);
123233
123285
  // falls back to the boolean lookup only when the precise one
123234
123286
  // isn't wired (older embedding code).
123235
- claimed: this.deps.taskHandleClaimGuidLookup ? this.deps.taskHandleClaimGuidLookup(threadId) === taskRootInfo.guid : this.deps.taskHandleClaimedLookup?.(threadId) ?? false
123287
+ claimed: this.deps.taskHandleClaimGuidLookup ? this.deps.taskHandleClaimGuidLookup(threadId) === taskRootInfo.guid : this.deps.taskHandleClaimedLookup?.(threadId) ?? false,
123288
+ justClaimed: taskRootJustClaimed || this.deps.taskHandleClaimCommentPending?.(threadId) || void 0
123236
123289
  } : void 0,
123237
123290
  mtimeFacts
123238
123291
  });
@@ -123417,6 +123470,7 @@ var BridgeHandler = class {
123417
123470
  reason: failureReason
123418
123471
  });
123419
123472
  }
123473
+ this.threadLastOutcome.set(threadId, success ? "completed" : "failed");
123420
123474
  const stuckResetAfter = resolveStuckSessionResetAfter();
123421
123475
  const prevStuckCount = currentExisting?.consecutiveStuckCount ?? 0;
123422
123476
  const nextStuckCount = cardKitTimeoutFailure ? prevStuckCount + 1 : success ? 0 : prevStuckCount;
@@ -123661,6 +123715,7 @@ var BridgeHandler = class {
123661
123715
  }
123662
123716
  } catch (err) {
123663
123717
  console.error("[bridge.handler] handleOne failed for thread", threadId, err);
123718
+ this.threadLastOutcome.set(threadId, "failed");
123664
123719
  cotTurnOutcome = "error";
123665
123720
  if (cotPublisher) {
123666
123721
  void cotPublisher.finalize("error", { message: String(err) }).catch(() => {
@@ -130482,7 +130537,7 @@ function isStallNudgeState(value) {
130482
130537
  function isTaskHandleRecord(value) {
130483
130538
  if (typeof value !== "object" || value === null) return false;
130484
130539
  const v = value;
130485
- return typeof v["threadId"] === "string" && typeof v["taskGuid"] === "string" && typeof v["chatId"] === "string" && typeof v["claimedTs"] === "number" && (v["lastSeenCommentId"] === void 0 || typeof v["lastSeenCommentId"] === "string") && (v["lastTurnOutcome"] === void 0 || v["lastTurnOutcome"] === "completed" || v["lastTurnOutcome"] === "failed") && (v["stallNudge"] === void 0 || isStallNudgeState(v["stallNudge"])) && (v["stallSuppressUntilActivityAfter"] === void 0 || typeof v["stallSuppressUntilActivityAfter"] === "number") && (v["lastTurnMentions"] === void 0 || Array.isArray(v["lastTurnMentions"]) && v["lastTurnMentions"].every((m) => typeof m === "string")) && (v["lastTurnMentionsAt"] === void 0 || typeof v["lastTurnMentionsAt"] === "number") && (v["mode"] === void 0 || v["mode"] === "comment") && (v["doneDeclared"] === void 0 || typeof v["doneDeclared"] === "boolean");
130540
+ return typeof v["threadId"] === "string" && typeof v["taskGuid"] === "string" && typeof v["chatId"] === "string" && typeof v["claimedTs"] === "number" && (v["lastSeenCommentId"] === void 0 || typeof v["lastSeenCommentId"] === "string") && (v["lastTurnOutcome"] === void 0 || v["lastTurnOutcome"] === "completed" || v["lastTurnOutcome"] === "failed") && (v["stallNudge"] === void 0 || isStallNudgeState(v["stallNudge"])) && (v["stallSuppressUntilActivityAfter"] === void 0 || typeof v["stallSuppressUntilActivityAfter"] === "number") && (v["lastTurnMentions"] === void 0 || Array.isArray(v["lastTurnMentions"]) && v["lastTurnMentions"].every((m) => typeof m === "string")) && (v["lastTurnMentionsAt"] === void 0 || typeof v["lastTurnMentionsAt"] === "number") && (v["mode"] === void 0 || v["mode"] === "comment") && (v["doneDeclared"] === void 0 || typeof v["doneDeclared"] === "boolean") && (v["claimCommentPending"] === void 0 || typeof v["claimCommentPending"] === "boolean");
130486
130541
  }
130487
130542
  var TaskHandleStore = class _TaskHandleStore {
130488
130543
  #filePath;
@@ -130698,7 +130753,8 @@ var TaskHandleStore = class _TaskHandleStore {
130698
130753
  taskGuid: input.taskGuid,
130699
130754
  chatId: input.chatId,
130700
130755
  claimedTs: Date.now(),
130701
- ...input.mode ? { mode: input.mode } : {}
130756
+ ...input.mode ? { mode: input.mode } : {},
130757
+ ...input.claimCommentPending ? { claimCommentPending: true } : {}
130702
130758
  };
130703
130759
  });
130704
130760
  return { claimed, reason };
@@ -131178,7 +131234,11 @@ async function applyTaskHandleWriteback(patch, deps) {
131178
131234
  // Agent declared delivery — its own "已交付" comment is the
131179
131235
  // user-facing artifact; bridge just stops patrolling the
131180
131236
  // "delivered, human hasn't ticked complete yet" window.
131181
- ...patch.agentDeclaredDone === true ? { doneDeclared: true } : {}
131237
+ ...patch.agentDeclaredDone === true ? { doneDeclared: true } : {},
131238
+ // v4.2 round-2: a successfully completed turn ran under a
131239
+ // <task-root> that instructed the claim comment — consider
131240
+ // the backlink posted; only crashed turns keep it owed.
131241
+ claimCommentPending: void 0
131182
131242
  } : current
131183
131243
  );
131184
131244
  break;
@@ -131827,10 +131887,11 @@ var CandidateAlertStore = class _CandidateAlertStore {
131827
131887
  // src/tasklist/stallDetector.ts
131828
131888
  var DEFAULT_INTERVAL_MS3 = 6e4;
131829
131889
  var DEFAULT_JITTER_MS3 = 1e4;
131830
- var DEFAULT_STALL_THRESHOLD_MS = 24 * 60 * 6e4;
131890
+ var DEFAULT_STALL_THRESHOLD_MS = 60 * 6e4;
131831
131891
  var DEFAULT_STALL_FAST_THRESHOLD_MS = 30 * 6e4;
131832
131892
  var DEFAULT_STALL_HANDOFF_THRESHOLD_MS = 5 * 6e4;
131833
- var DEFAULT_NUDGE_COOLDOWN_MS = 24 * 60 * 6e4;
131893
+ var DEFAULT_NUDGE_COOLDOWN_MS = 60 * 6e4;
131894
+ var DUE_NUDGE_COOLDOWN_FLOOR_MS = 24 * 60 * 6e4;
131834
131895
  var DEFAULT_ESCALATE_AFTER_NUDGES = 2;
131835
131896
  var DEFAULT_PENDING_CONFIRM_TIMEOUT_MS = 30 * 6e4;
131836
131897
  var DEFAULT_HANDOFF_STARTUP_QUIET_MS = 6 * 6e4;
@@ -132009,7 +132070,7 @@ var StallDetector = class {
132009
132070
  const { ms: threshold, reason } = this.#effectiveThreshold(record, threadId);
132010
132071
  if (!nudge || nudge.count === 0) {
132011
132072
  if (now - lastActiveTs < threshold) return;
132012
- if (reason === "due" && !justClearedPendingTimeout && nudge?.lastNudgeSentAt !== void 0 && now - nudge.lastNudgeSentAt < this.#nudgeCooldownMs) {
132073
+ if (reason === "due" && !justClearedPendingTimeout && nudge?.lastNudgeSentAt !== void 0 && now - nudge.lastNudgeSentAt < this.#cooldownFor(reason)) {
132013
132074
  return;
132014
132075
  }
132015
132076
  await this.#sendNudge(threadId);
@@ -132034,7 +132095,7 @@ var StallDetector = class {
132034
132095
  });
132035
132096
  return;
132036
132097
  }
132037
- if (now - nudge.lastNudgeSentAt < this.#nudgeCooldownMs) return;
132098
+ if (now - nudge.lastNudgeSentAt < this.#cooldownFor(reason)) return;
132038
132099
  if (nudge.count >= this.#escalateAfterNudges) {
132039
132100
  await this.#escalate(threadId);
132040
132101
  } else {
@@ -132073,6 +132134,10 @@ var StallDetector = class {
132073
132134
  * until gap-fill has had a chance to redeliver anything in flight across
132074
132135
  * a restart.
132075
132136
  */
132137
+ /** Cooldown for a given tier — the due tier keeps a 24h floor (see DUE_NUDGE_COOLDOWN_FLOOR_MS). An explicit operator nudgeCooldownMs LONGER than the floor still wins. */
132138
+ #cooldownFor(reason) {
132139
+ return reason === "due" ? Math.max(this.#nudgeCooldownMs, DUE_NUDGE_COOLDOWN_FLOOR_MS) : this.#nudgeCooldownMs;
132140
+ }
132076
132141
  #effectiveThreshold(record, threadId) {
132077
132142
  const base = record.lastTurnOutcome === "failed" ? { ms: this.#stallFastThresholdMs, reason: "failed" } : { ms: this.#stallThresholdMs, reason: "normal" };
132078
132143
  const dueMs = this.#deps.getTaskDueMs?.(record.taskGuid);
@@ -132086,6 +132151,14 @@ var StallDetector = class {
132086
132151
  const now = Date.now();
132087
132152
  let brokenFloorMs;
132088
132153
  for (const peerBotId of record.lastTurnMentions) {
132154
+ {
132155
+ const peerOutcome = this.#deps.getPeerLastTurnOutcome?.(peerBotId, threadId);
132156
+ const peerEndedAt = this.#deps.getPeerLastActiveTs?.(peerBotId, threadId);
132157
+ if (peerOutcome === "failed" && peerEndedAt !== void 0 && peerEndedAt > mentionAt) {
132158
+ brokenFloorMs = this.#stallHandoffThresholdMs;
132159
+ break;
132160
+ }
132161
+ }
132089
132162
  const receivedAt = this.#deps.getPeerReceivedAt(peerBotId, threadId);
132090
132163
  if (receivedAt === void 0 || receivedAt <= mentionAt) {
132091
132164
  const completedAt2 = this.#deps.getPeerLastActiveTs?.(peerBotId, threadId);
@@ -132369,6 +132442,7 @@ async function runV2Mode({
132369
132442
  const { basename } = path19;
132370
132443
  const instances = [];
132371
132444
  const handlersByBotId = /* @__PURE__ */ new Map();
132445
+ const allTaskHandleStores = [];
132372
132446
  const tasklistPollersByGuid = /* @__PURE__ */ new Map();
132373
132447
  const tasklistGuidGroups = /* @__PURE__ */ new Map();
132374
132448
  for (const bot of healthyBots) {
@@ -132462,6 +132536,8 @@ async function runV2Mode({
132462
132536
  let taskHandleClaim;
132463
132537
  let taskHandleClaimedLookup;
132464
132538
  let taskHandleClaimGuidLookup;
132539
+ let taskGuidClaimedByOtherBot;
132540
+ let taskHandleClaimCommentPending;
132465
132541
  let taskHandleCandidatesLookup;
132466
132542
  let taskCommentPoller;
132467
132543
  let stallDetector;
@@ -132491,6 +132567,7 @@ async function runV2Mode({
132491
132567
  effectiveTaskHandleTasklistGuid = guid;
132492
132568
  }
132493
132569
  const taskHandleStore = await TaskHandleStore.load(resolveTaskHandlesPath(bot.id));
132570
+ allTaskHandleStores.push({ botId: bot.id, store: taskHandleStore });
132494
132571
  taskHandleLifecycle = (patch) => applyTaskHandleWriteback(patch, { store: taskHandleStore, client: taskListClient });
132495
132572
  taskHandleClaim = async (patch) => {
132496
132573
  const result = await taskHandleStore.claim(patch);
@@ -132502,6 +132579,8 @@ async function runV2Mode({
132502
132579
  };
132503
132580
  taskHandleClaimedLookup = (threadId) => taskHandleStore.get(threadId) !== void 0;
132504
132581
  taskHandleClaimGuidLookup = (threadId) => taskHandleStore.get(threadId)?.taskGuid;
132582
+ taskGuidClaimedByOtherBot = (guid2) => allTaskHandleStores.some((e) => e.botId !== bot.id && e.store.list().some((r) => r.taskGuid === guid2));
132583
+ taskHandleClaimCommentPending = (threadId) => taskHandleStore.get(threadId)?.claimCommentPending === true;
132505
132584
  if (guid) {
132506
132585
  const group = tasklistGuidGroups.get(guid) ?? { client: taskListClient, clientOwnerBotId: bot.id, bots: [] };
132507
132586
  group.bots.push({ botId: bot.id, sessionStore, taskHandleStore });
@@ -132564,6 +132643,9 @@ async function runV2Mode({
132564
132643
  // window — "did their turn genuinely finish", not "did it start
132565
132644
  // or get queued". See stallDetector.ts's module doc revision 3.
132566
132645
  getPeerLastActiveTs: (peerBotId, threadId) => handlersByBotId.get(peerBotId)?.getThreadLastActiveTs(threadId),
132646
+ // v4.2 revision 5: the peer's most recent finished-turn OUTCOME —
132647
+ // a failed finish must not resolve a handoff (see stallDetector.ts).
132648
+ getPeerLastTurnOutcome: (peerBotId, threadId) => handlersByBotId.get(peerBotId)?.getThreadLastOutcome(threadId),
132567
132649
  // v3.3 due-date stall detection (docs/task-handle.md §14): free
132568
132650
  // read of TasklistPoller's own per-guid `due` observation — same
132569
132651
  // "populated after, read lazily" closure trick as
@@ -132726,6 +132808,8 @@ async function runV2Mode({
132726
132808
  taskHandleClaim,
132727
132809
  taskHandleClaimedLookup,
132728
132810
  taskHandleClaimGuidLookup,
132811
+ taskGuidClaimedByOtherBot,
132812
+ taskHandleClaimCommentPending,
132729
132813
  taskHandleCandidatesLookup
132730
132814
  });
132731
132815
  handlersByBotId.set(bot.id, handler);
@@ -821,9 +821,11 @@ function modelSuggestionsForBackend(backendId) {
821
821
  ];
822
822
  }
823
823
  return [
824
- ["fable", "fable"],
825
- ["opus", "opus"],
826
- ["sonnet", "sonnet"],
824
+ ["claude-sonnet-5", "claude-sonnet-5 · Sonnet 5,快/省"],
825
+ ["claude-opus-4-8", "claude-opus-4-8 · Opus 4.8"],
826
+ ["claude-fable-5", "claude-fable-5 · Fable 5,最强"],
827
+ ["sonnet", "sonnet · 别名=最新 Sonnet"],
828
+ ["opus", "opus · 别名=最新 Opus"],
827
829
  ];
828
830
  }
829
831
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.43",
3
+ "version": "0.3.45",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",