larkway 0.3.38 → 0.3.39

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.38**
11
+ **Current release: v0.3.39**
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.38**
11
+ **当前版本:v0.3.39**
12
12
 
13
13
  ---
14
14
 
package/dist/main.js CHANGED
@@ -118525,7 +118525,10 @@ var SessionStore = class _SessionStore {
118525
118525
  lastActiveTs: record.lastActiveTs,
118526
118526
  ...record.senderOpenId !== void 0 ? { senderOpenId: record.senderOpenId } : {},
118527
118527
  ...record.rootText !== void 0 ? { rootText: record.rootText } : {},
118528
- ...record.chatId !== void 0 ? { chatId: record.chatId } : {}
118528
+ ...record.chatId !== void 0 ? { chatId: record.chatId } : {},
118529
+ // BL-38: only persist when > 0 — a 0/undefined counter is a clean thread,
118530
+ // so passing consecutiveStuckCount: 0 naturally clears the field on reset.
118531
+ ...record.consecutiveStuckCount ? { consecutiveStuckCount: record.consecutiveStuckCount } : {}
118529
118532
  };
118530
118533
  this.#map.set(key, stored);
118531
118534
  await this.#flush();
@@ -121946,6 +121949,13 @@ function derivePostIdempotencyKey(input) {
121946
121949
  var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
121947
121950
  var DEFAULT_CARDKIT_IDLE_TIMEOUT_MS = 3 * 60 * 1e3;
121948
121951
  var COT_BUBBLE_CREATE_BUDGET_MS = 3e3;
121952
+ var DEFAULT_STUCK_SESSION_RESET_AFTER = 3;
121953
+ function resolveStuckSessionResetAfter() {
121954
+ const raw = process.env.LARKWAY_STUCK_SESSION_RESET_AFTER;
121955
+ if (raw === void 0) return DEFAULT_STUCK_SESSION_RESET_AFTER;
121956
+ const n = Number.parseInt(raw, 10);
121957
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_STUCK_SESSION_RESET_AFTER;
121958
+ }
121949
121959
  function summarizeMentionPolicyRules(rules) {
121950
121960
  const counts = /* @__PURE__ */ new Map();
121951
121961
  for (const rule of rules) {
@@ -123092,34 +123102,6 @@ var BridgeHandler = class {
123092
123102
  console.warn("[bridge.handler] taskHandleClaim hook failed (continuing):", err);
123093
123103
  }
123094
123104
  }
123095
- const now = Date.now();
123096
- if (sessionId !== void 0 && currentExisting === void 0) {
123097
- await this.deps.sessionStore.put({
123098
- threadId,
123099
- sessionId,
123100
- botId,
123101
- createdTs: now,
123102
- lastActiveTs: now,
123103
- senderOpenId,
123104
- ...isTopLevel ? { rootText: parsed.text.slice(0, 200), chatId: parsed.chatId } : {}
123105
- });
123106
- } else if (sessionId !== void 0 && currentExisting !== void 0) {
123107
- await this.deps.sessionStore.put({
123108
- threadId,
123109
- sessionId,
123110
- botId,
123111
- createdTs: currentExisting.createdTs,
123112
- lastActiveTs: now,
123113
- senderOpenId,
123114
- rootText: currentExisting.rootText,
123115
- chatId: currentExisting.chatId
123116
- });
123117
- } else if (currentExisting !== void 0 && sessionId === void 0) {
123118
- await this.deps.sessionStore.put({
123119
- ...currentExisting,
123120
- lastActiveTs: now
123121
- });
123122
- }
123123
123105
  const reportedStatus = reportedState?.status;
123124
123106
  const reportedError = reportedState?.error;
123125
123107
  const cardKitTimeoutFailure = interruptedByIdle && reportedStatus !== "ready" && reportedStatus !== "failed";
@@ -123149,10 +123131,53 @@ var BridgeHandler = class {
123149
123131
  reason: failureReason
123150
123132
  });
123151
123133
  }
123134
+ const stuckResetAfter = resolveStuckSessionResetAfter();
123135
+ const prevStuckCount = currentExisting?.consecutiveStuckCount ?? 0;
123136
+ const nextStuckCount = cardKitTimeoutFailure ? prevStuckCount + 1 : success ? 0 : prevStuckCount;
123137
+ const stuckResetTriggered = cardKitTimeoutFailure && nextStuckCount >= stuckResetAfter;
123138
+ const now = Date.now();
123139
+ if (stuckResetTriggered) {
123140
+ await this.deps.sessionStore.delete(threadId, botId);
123141
+ console.info(
123142
+ `[bridge.handler] BL-38 stuck-session reset: thread=${threadId} bot=${botId} consecutiveStuckCount=${nextStuckCount} (>= ${stuckResetAfter}) \u2014 dropped session record; next @ starts fresh`
123143
+ );
123144
+ } else if (sessionId !== void 0 && currentExisting === void 0) {
123145
+ await this.deps.sessionStore.put({
123146
+ threadId,
123147
+ sessionId,
123148
+ botId,
123149
+ createdTs: now,
123150
+ lastActiveTs: now,
123151
+ senderOpenId,
123152
+ // BL-38: a brand-new thread that idle-killed on its very first turn
123153
+ // starts the counter at 1 (0 when clean; only persisted when > 0).
123154
+ consecutiveStuckCount: nextStuckCount,
123155
+ ...isTopLevel ? { rootText: parsed.text.slice(0, 200), chatId: parsed.chatId } : {}
123156
+ });
123157
+ } else if (sessionId !== void 0 && currentExisting !== void 0) {
123158
+ await this.deps.sessionStore.put({
123159
+ threadId,
123160
+ sessionId,
123161
+ botId,
123162
+ createdTs: currentExisting.createdTs,
123163
+ lastActiveTs: now,
123164
+ senderOpenId,
123165
+ rootText: currentExisting.rootText,
123166
+ chatId: currentExisting.chatId,
123167
+ // BL-38: +1 on an idle-stuck turn, 0 (cleared) on any clean turn.
123168
+ consecutiveStuckCount: nextStuckCount
123169
+ });
123170
+ } else if (currentExisting !== void 0 && sessionId === void 0) {
123171
+ await this.deps.sessionStore.put({
123172
+ ...currentExisting,
123173
+ lastActiveTs: now,
123174
+ consecutiveStuckCount: nextStuckCount
123175
+ });
123176
+ }
123152
123177
  const fallbackAnswer = trustedAnswerText.trim() || cardKitProgress?.answerText.trim() || "";
123153
123178
  const noOutputFallback = "\u26A0\uFE0F \u672C\u8F6E agent \u6CA1\u6709\u4EA7\u51FA\u6B63\u6587\uFF0C\u4E5F\u6CA1\u6709\u5199\u5165\u6709\u6548\u72B6\u6001(state.json)\u3002\n" + (result.exitCode !== 0 ? `agent \u8FDB\u7A0B\u5F02\u5E38\u9000\u51FA(exit code ${result.exitCode})\u3002
123154
123179
  ` : "") + "\u4E0B\u4E00\u6B65\uFF1A\u6362\u4E2A\u8BF4\u6CD5\u91CD\u8BD5\uFF0C\u6216\u65B0\u5F00\u4E00\u4E2A\u8BDD\u9898\u7EE7\u7EED\uFF1B\u82E5\u53CD\u590D\u5982\u6B64\uFF0C\u8BF7\u8BA9\u7EF4\u62A4\u8005\u67E5\u770B\u8BE5 session \u65E5\u5FD7\u3002";
123155
- const cardBody = cardKitTimeoutFailure ? "\u26A0\uFE0F \u672C\u8F6E\u88AB\u4E2D\u65AD\uFF08\u957F\u65F6\u95F4\u65E0\u6D3B\u6027\uFF0C\u5224\u5B9A\u5361\u6B7B\uFF09\uFF0C\u672A\u5B8C\u6210\u3002\u8BF7\u91CD\u8BD5\u3002" : reportedState?.last_message ?? (fallbackAnswer ? fallbackAnswer : noOutputFallback);
123180
+ const cardBody = stuckResetTriggered ? "\u26A0\uFE0F \u672C\u8F6E\u88AB\u4E2D\u65AD\uFF08\u957F\u65F6\u95F4\u65E0\u6D3B\u6027\uFF0C\u5224\u5B9A\u5361\u6B7B\uFF09\u3002\u8FDE\u7EED\u591A\u6B21\u5361\u6B7B\uFF0C\u5DF2\u91CD\u7F6E\u672C\u8BDD\u9898\u4E0A\u4E0B\u6587 \u2014\u2014 \u4E0B\u6B21 @ \u6211\u5C06\u5168\u65B0\u5F00\u59CB\uFF0C\u8BF7\u628A\u9700\u6C42\u91CD\u65B0\u8BF4\u4E00\u904D\u3002" : cardKitTimeoutFailure ? "\u26A0\uFE0F \u672C\u8F6E\u88AB\u4E2D\u65AD\uFF08\u957F\u65F6\u95F4\u65E0\u6D3B\u6027\uFF0C\u5224\u5B9A\u5361\u6B7B\uFF09\uFF0C\u672A\u5B8C\u6210\u3002\u8BF7\u91CD\u8BD5\u3002" : reportedState?.last_message ?? (fallbackAnswer ? fallbackAnswer : noOutputFallback);
123156
123181
  const noReportThisTurn = reportedState === null;
123157
123182
  const neutralTitle = noReportThisTurn && success && !failureReason ? "\u{1F4AC} \u5DF2\u56DE\u590D" : void 0;
123158
123183
  const baseCardPayload = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.38",
3
+ "version": "0.3.39",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",