larkway 0.3.43 → 0.3.44
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 +1 -1
- package/README.zh.md +1 -1
- package/dist/cli/index.js +5 -0
- package/dist/main.js +96 -12
- package/package.json +1 -1
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.
|
|
11
|
+
**Current release: v0.3.44**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
package/README.zh.md
CHANGED
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
|
-
|
|
119229
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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.#
|
|
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.#
|
|
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);
|