larkway 0.3.38 → 0.3.40
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 +85 -2
- package/dist/main.js +330 -62
- 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.40**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
package/README.zh.md
CHANGED
package/dist/cli/index.js
CHANGED
|
@@ -112500,6 +112500,72 @@ var init_channelCotClient = __esm({
|
|
|
112500
112500
|
}
|
|
112501
112501
|
});
|
|
112502
112502
|
|
|
112503
|
+
// src/lark/messageLookupClient.ts
|
|
112504
|
+
async function withTimeout2(p, ms, label) {
|
|
112505
|
+
let timer;
|
|
112506
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
112507
|
+
timer = setTimeout(() => reject(new Error(`[channel.msglookup] ${label} timed out after ${ms}ms`)), ms);
|
|
112508
|
+
timer.unref?.();
|
|
112509
|
+
});
|
|
112510
|
+
try {
|
|
112511
|
+
return await Promise.race([p, timeout]);
|
|
112512
|
+
} finally {
|
|
112513
|
+
if (timer) clearTimeout(timer);
|
|
112514
|
+
}
|
|
112515
|
+
}
|
|
112516
|
+
var LOOKUP_TIMEOUT_MS, CACHE_MAX, ChannelMessageLookupClient;
|
|
112517
|
+
var init_messageLookupClient = __esm({
|
|
112518
|
+
"src/lark/messageLookupClient.ts"() {
|
|
112519
|
+
"use strict";
|
|
112520
|
+
LOOKUP_TIMEOUT_MS = 5e3;
|
|
112521
|
+
CACHE_MAX = 200;
|
|
112522
|
+
ChannelMessageLookupClient = class {
|
|
112523
|
+
#resolveChannel;
|
|
112524
|
+
#cache = /* @__PURE__ */ new Map();
|
|
112525
|
+
constructor(opts) {
|
|
112526
|
+
this.#resolveChannel = opts.resolveChannel;
|
|
112527
|
+
}
|
|
112528
|
+
async get(messageId, opts) {
|
|
112529
|
+
if (!opts?.refresh) {
|
|
112530
|
+
const cached = this.#cache.get(messageId);
|
|
112531
|
+
if (cached) return cached;
|
|
112532
|
+
}
|
|
112533
|
+
try {
|
|
112534
|
+
const channel = this.#resolveChannel();
|
|
112535
|
+
if (!channel) return void 0;
|
|
112536
|
+
const req = {
|
|
112537
|
+
url: `/open-apis/im/v1/messages/${encodeURIComponent(messageId)}`,
|
|
112538
|
+
method: "GET"
|
|
112539
|
+
};
|
|
112540
|
+
const res = await withTimeout2(
|
|
112541
|
+
channel.rawClient.request(req),
|
|
112542
|
+
LOOKUP_TIMEOUT_MS,
|
|
112543
|
+
`GET message ${messageId}`
|
|
112544
|
+
);
|
|
112545
|
+
if (res.code !== void 0 && res.code !== 0) return void 0;
|
|
112546
|
+
const items = res.data?.["items"];
|
|
112547
|
+
if (!Array.isArray(items) || items.length === 0) return void 0;
|
|
112548
|
+
const item = items[0];
|
|
112549
|
+
const body = item["body"];
|
|
112550
|
+
const info = {
|
|
112551
|
+
msgType: typeof item["msg_type"] === "string" ? item["msg_type"] : void 0,
|
|
112552
|
+
content: body && typeof body === "object" && typeof body["content"] === "string" ? body["content"] : void 0,
|
|
112553
|
+
threadId: typeof item["thread_id"] === "string" && item["thread_id"] ? item["thread_id"] : void 0
|
|
112554
|
+
};
|
|
112555
|
+
if (this.#cache.size >= CACHE_MAX) {
|
|
112556
|
+
const oldest = this.#cache.keys().next().value;
|
|
112557
|
+
if (oldest !== void 0) this.#cache.delete(oldest);
|
|
112558
|
+
}
|
|
112559
|
+
this.#cache.set(messageId, info);
|
|
112560
|
+
return info;
|
|
112561
|
+
} catch {
|
|
112562
|
+
return void 0;
|
|
112563
|
+
}
|
|
112564
|
+
}
|
|
112565
|
+
};
|
|
112566
|
+
}
|
|
112567
|
+
});
|
|
112568
|
+
|
|
112503
112569
|
// src/lark/channelClient.ts
|
|
112504
112570
|
var channelClient_exports = {};
|
|
112505
112571
|
__export(channelClient_exports, {
|
|
@@ -112660,6 +112726,7 @@ var init_channelClient = __esm({
|
|
|
112660
112726
|
init_channelCardKitClient();
|
|
112661
112727
|
init_channelPostClient();
|
|
112662
112728
|
init_channelCotClient();
|
|
112729
|
+
init_messageLookupClient();
|
|
112663
112730
|
execFile4 = promisify4(execFileCallback);
|
|
112664
112731
|
LEARNED_CHATS_LIMIT = 100;
|
|
112665
112732
|
SEEN_MESSAGES_LIMIT = 1e3;
|
|
@@ -112820,6 +112887,8 @@ var init_channelClient = __esm({
|
|
|
112820
112887
|
postClient = null;
|
|
112821
112888
|
/** Lazily built COT (思维链) client; only requested when a bot enables cot != off. */
|
|
112822
112889
|
cotClient = null;
|
|
112890
|
+
/** Lazily built single-message lookup client (v4 任务派单 root-type probe). */
|
|
112891
|
+
messageLookupClient = null;
|
|
112823
112892
|
constructor(opts) {
|
|
112824
112893
|
if (!opts.appId || !opts.appSecret) {
|
|
112825
112894
|
throw new Error(
|
|
@@ -113103,6 +113172,20 @@ var init_channelClient = __esm({
|
|
|
113103
113172
|
}
|
|
113104
113173
|
return this.cotClient;
|
|
113105
113174
|
}
|
|
113175
|
+
/**
|
|
113176
|
+
* Return a MessageLookupClient bound to this client's channel handle
|
|
113177
|
+
* (docs/task-handle.md §15.4 — the v4 任务派单 root-type probe). Same lazy
|
|
113178
|
+
* channel resolution and generic rawClient.request() auth path as the COT
|
|
113179
|
+
* client above; strictly best-effort per the client's own contract.
|
|
113180
|
+
*/
|
|
113181
|
+
outboundMessageLookupClient() {
|
|
113182
|
+
if (!this.messageLookupClient) {
|
|
113183
|
+
this.messageLookupClient = new ChannelMessageLookupClient({
|
|
113184
|
+
resolveChannel: () => this.channel
|
|
113185
|
+
});
|
|
113186
|
+
}
|
|
113187
|
+
return this.messageLookupClient;
|
|
113188
|
+
}
|
|
113106
113189
|
/**
|
|
113107
113190
|
* Return an OutboundPostClient bound to this client's channel handle.
|
|
113108
113191
|
*
|
|
@@ -113793,7 +113876,7 @@ function isTaskRequestTimeoutError(err2) {
|
|
|
113793
113876
|
if (err2 instanceof TaskApiError && err2.cause instanceof TaskRequestTimeoutError) return true;
|
|
113794
113877
|
return false;
|
|
113795
113878
|
}
|
|
113796
|
-
function
|
|
113879
|
+
function withTimeout3(promise, ms, label) {
|
|
113797
113880
|
let timer;
|
|
113798
113881
|
const timeout = new Promise((_, reject) => {
|
|
113799
113882
|
timer = setTimeout(() => reject(new TaskRequestTimeoutError(`${label} timed out after ${ms}ms`)), ms);
|
|
@@ -113837,7 +113920,7 @@ var init_client = __esm({
|
|
|
113837
113920
|
}
|
|
113838
113921
|
/** Single choke point for every outbound call — applies the timeout race uniformly. */
|
|
113839
113922
|
#request(config, label) {
|
|
113840
|
-
return
|
|
113923
|
+
return withTimeout3(this.#requester.request(config), this.#timeoutMs, label);
|
|
113841
113924
|
}
|
|
113842
113925
|
async getTask(taskGuid) {
|
|
113843
113926
|
let resp;
|
package/dist/main.js
CHANGED
|
@@ -116650,6 +116650,71 @@ function stringField(data, key) {
|
|
|
116650
116650
|
return typeof value === "string" ? value : void 0;
|
|
116651
116651
|
}
|
|
116652
116652
|
|
|
116653
|
+
// src/lark/messageLookupClient.ts
|
|
116654
|
+
function buildTopicDeepLink(chatId, threadId) {
|
|
116655
|
+
const c = encodeURIComponent(chatId);
|
|
116656
|
+
const t = encodeURIComponent(threadId);
|
|
116657
|
+
return `https://applink.feishu.cn/client/thread/open?open_chat_id=${c}&open_thread_id=${t}&openchatid=${c}&openthreadid=${t}&thread_position=-1`;
|
|
116658
|
+
}
|
|
116659
|
+
var LOOKUP_TIMEOUT_MS = 5e3;
|
|
116660
|
+
var CACHE_MAX = 200;
|
|
116661
|
+
async function withTimeout2(p, ms, label) {
|
|
116662
|
+
let timer;
|
|
116663
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
116664
|
+
timer = setTimeout(() => reject(new Error(`[channel.msglookup] ${label} timed out after ${ms}ms`)), ms);
|
|
116665
|
+
timer.unref?.();
|
|
116666
|
+
});
|
|
116667
|
+
try {
|
|
116668
|
+
return await Promise.race([p, timeout]);
|
|
116669
|
+
} finally {
|
|
116670
|
+
if (timer) clearTimeout(timer);
|
|
116671
|
+
}
|
|
116672
|
+
}
|
|
116673
|
+
var ChannelMessageLookupClient = class {
|
|
116674
|
+
#resolveChannel;
|
|
116675
|
+
#cache = /* @__PURE__ */ new Map();
|
|
116676
|
+
constructor(opts) {
|
|
116677
|
+
this.#resolveChannel = opts.resolveChannel;
|
|
116678
|
+
}
|
|
116679
|
+
async get(messageId, opts) {
|
|
116680
|
+
if (!opts?.refresh) {
|
|
116681
|
+
const cached = this.#cache.get(messageId);
|
|
116682
|
+
if (cached) return cached;
|
|
116683
|
+
}
|
|
116684
|
+
try {
|
|
116685
|
+
const channel = this.#resolveChannel();
|
|
116686
|
+
if (!channel) return void 0;
|
|
116687
|
+
const req = {
|
|
116688
|
+
url: `/open-apis/im/v1/messages/${encodeURIComponent(messageId)}`,
|
|
116689
|
+
method: "GET"
|
|
116690
|
+
};
|
|
116691
|
+
const res = await withTimeout2(
|
|
116692
|
+
channel.rawClient.request(req),
|
|
116693
|
+
LOOKUP_TIMEOUT_MS,
|
|
116694
|
+
`GET message ${messageId}`
|
|
116695
|
+
);
|
|
116696
|
+
if (res.code !== void 0 && res.code !== 0) return void 0;
|
|
116697
|
+
const items = res.data?.["items"];
|
|
116698
|
+
if (!Array.isArray(items) || items.length === 0) return void 0;
|
|
116699
|
+
const item = items[0];
|
|
116700
|
+
const body = item["body"];
|
|
116701
|
+
const info = {
|
|
116702
|
+
msgType: typeof item["msg_type"] === "string" ? item["msg_type"] : void 0,
|
|
116703
|
+
content: body && typeof body === "object" && typeof body["content"] === "string" ? body["content"] : void 0,
|
|
116704
|
+
threadId: typeof item["thread_id"] === "string" && item["thread_id"] ? item["thread_id"] : void 0
|
|
116705
|
+
};
|
|
116706
|
+
if (this.#cache.size >= CACHE_MAX) {
|
|
116707
|
+
const oldest = this.#cache.keys().next().value;
|
|
116708
|
+
if (oldest !== void 0) this.#cache.delete(oldest);
|
|
116709
|
+
}
|
|
116710
|
+
this.#cache.set(messageId, info);
|
|
116711
|
+
return info;
|
|
116712
|
+
} catch {
|
|
116713
|
+
return void 0;
|
|
116714
|
+
}
|
|
116715
|
+
}
|
|
116716
|
+
};
|
|
116717
|
+
|
|
116653
116718
|
// src/lark/channelClient.ts
|
|
116654
116719
|
var execFile = promisify(execFileCallback);
|
|
116655
116720
|
var LEARNED_CHATS_LIMIT = 100;
|
|
@@ -116947,6 +117012,8 @@ var ChannelClient = class {
|
|
|
116947
117012
|
postClient = null;
|
|
116948
117013
|
/** Lazily built COT (思维链) client; only requested when a bot enables cot != off. */
|
|
116949
117014
|
cotClient = null;
|
|
117015
|
+
/** Lazily built single-message lookup client (v4 任务派单 root-type probe). */
|
|
117016
|
+
messageLookupClient = null;
|
|
116950
117017
|
constructor(opts) {
|
|
116951
117018
|
if (!opts.appId || !opts.appSecret) {
|
|
116952
117019
|
throw new Error(
|
|
@@ -117230,6 +117297,20 @@ var ChannelClient = class {
|
|
|
117230
117297
|
}
|
|
117231
117298
|
return this.cotClient;
|
|
117232
117299
|
}
|
|
117300
|
+
/**
|
|
117301
|
+
* Return a MessageLookupClient bound to this client's channel handle
|
|
117302
|
+
* (docs/task-handle.md §15.4 — the v4 任务派单 root-type probe). Same lazy
|
|
117303
|
+
* channel resolution and generic rawClient.request() auth path as the COT
|
|
117304
|
+
* client above; strictly best-effort per the client's own contract.
|
|
117305
|
+
*/
|
|
117306
|
+
outboundMessageLookupClient() {
|
|
117307
|
+
if (!this.messageLookupClient) {
|
|
117308
|
+
this.messageLookupClient = new ChannelMessageLookupClient({
|
|
117309
|
+
resolveChannel: () => this.channel
|
|
117310
|
+
});
|
|
117311
|
+
}
|
|
117312
|
+
return this.messageLookupClient;
|
|
117313
|
+
}
|
|
117233
117314
|
/**
|
|
117234
117315
|
* Return an OutboundPostClient bound to this client's channel handle.
|
|
117235
117316
|
*
|
|
@@ -118525,7 +118606,10 @@ var SessionStore = class _SessionStore {
|
|
|
118525
118606
|
lastActiveTs: record.lastActiveTs,
|
|
118526
118607
|
...record.senderOpenId !== void 0 ? { senderOpenId: record.senderOpenId } : {},
|
|
118527
118608
|
...record.rootText !== void 0 ? { rootText: record.rootText } : {},
|
|
118528
|
-
...record.chatId !== void 0 ? { chatId: record.chatId } : {}
|
|
118609
|
+
...record.chatId !== void 0 ? { chatId: record.chatId } : {},
|
|
118610
|
+
// BL-38: only persist when > 0 — a 0/undefined counter is a clean thread,
|
|
118611
|
+
// so passing consecutiveStuckCount: 0 naturally clears the field on reset.
|
|
118612
|
+
...record.consecutiveStuckCount ? { consecutiveStuckCount: record.consecutiveStuckCount } : {}
|
|
118529
118613
|
};
|
|
118530
118614
|
this.#map.set(key, stored);
|
|
118531
118615
|
await this.#flush();
|
|
@@ -118653,6 +118737,17 @@ function extractPostText(value) {
|
|
|
118653
118737
|
}
|
|
118654
118738
|
return "";
|
|
118655
118739
|
}
|
|
118740
|
+
function parseTodoShareContent(rawContent) {
|
|
118741
|
+
try {
|
|
118742
|
+
const parsed = JSON.parse(rawContent);
|
|
118743
|
+
const taskGuid = parsed["task_id"];
|
|
118744
|
+
if (typeof taskGuid !== "string" || taskGuid.length === 0) return null;
|
|
118745
|
+
const summaryText = extractPostText(parsed["summary"]).trim();
|
|
118746
|
+
return { taskGuid, summaryText };
|
|
118747
|
+
} catch {
|
|
118748
|
+
return null;
|
|
118749
|
+
}
|
|
118750
|
+
}
|
|
118656
118751
|
function extractText(event) {
|
|
118657
118752
|
let parsed;
|
|
118658
118753
|
try {
|
|
@@ -118664,6 +118759,10 @@ function extractText(event) {
|
|
|
118664
118759
|
return stripAtPlaceholders(event.content);
|
|
118665
118760
|
}
|
|
118666
118761
|
let raw = "";
|
|
118762
|
+
const todo = typeof parsed["task_id"] === "string" ? parseTodoShareContent(event.content) : null;
|
|
118763
|
+
if (todo) {
|
|
118764
|
+
return `[\u98DE\u4E66\u4EFB\u52A1] ${todo.summaryText || "(\u65E0\u6807\u9898)"} (task_guid=${todo.taskGuid})`;
|
|
118765
|
+
}
|
|
118667
118766
|
if (typeof parsed["text"] === "string") {
|
|
118668
118767
|
raw = parsed["text"];
|
|
118669
118768
|
} else if (parsed["content"] !== void 0) {
|
|
@@ -119111,6 +119210,27 @@ function renderTaskHandleBlock(tasklistGuid, claimed, candidates) {
|
|
|
119111
119210
|
"</task-handle>"
|
|
119112
119211
|
];
|
|
119113
119212
|
}
|
|
119213
|
+
function renderTaskRootBlock(taskRoot) {
|
|
119214
|
+
const lines = [
|
|
119215
|
+
"<task-root>",
|
|
119216
|
+
"\u672C\u8BDD\u9898\u7684\u6839\u6D88\u606F\u662F\u4E00\u6761\u98DE\u4E66\u4EFB\u52A1\u5206\u4EAB(\u4EFB\u52A1\u6D3E\u5355\u4E3B\u8DEF\u5F84,docs/task-handle.md \xA715)\u3002",
|
|
119217
|
+
`task_guid: ${taskRoot.guid}`,
|
|
119218
|
+
`task_summary: ${taskRoot.summary || "(\u65E0\u6807\u9898)"}`,
|
|
119219
|
+
...taskRoot.topicLink ? [`topic_link: ${taskRoot.topicLink}`] : [],
|
|
119220
|
+
`task_root_claimed: ${taskRoot.claimed ? "yes" : "no"}`
|
|
119221
|
+
];
|
|
119222
|
+
if (taskRoot.claimed) {
|
|
119223
|
+
lines.push(
|
|
119224
|
+
"\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"
|
|
119225
|
+
);
|
|
119226
|
+
} else {
|
|
119227
|
+
lines.push(
|
|
119228
|
+
`\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`
|
|
119229
|
+
);
|
|
119230
|
+
}
|
|
119231
|
+
lines.push("</task-root>");
|
|
119232
|
+
return lines;
|
|
119233
|
+
}
|
|
119114
119234
|
function renderRuntimeWarningsBlock(warnings) {
|
|
119115
119235
|
if (warnings.length === 0) return [];
|
|
119116
119236
|
const hasMissingLarkCli = warnings.some((warning) => warning.command === "lark-cli");
|
|
@@ -119264,6 +119384,7 @@ async function renderPrompt(input) {
|
|
|
119264
119384
|
taskHandleTasklistGuid,
|
|
119265
119385
|
taskHandleClaimed = false,
|
|
119266
119386
|
taskHandleCandidates = [],
|
|
119387
|
+
taskRoot,
|
|
119267
119388
|
mtimeFacts = []
|
|
119268
119389
|
} = input;
|
|
119269
119390
|
const backend = input.backend ?? "claude";
|
|
@@ -119278,7 +119399,8 @@ async function renderPrompt(input) {
|
|
|
119278
119399
|
const stateContract = renderStateContract(conventions.stateFilePath);
|
|
119279
119400
|
const peersBlock = peers && peers.length > 0 ? renderPeersBlock(peers) : [];
|
|
119280
119401
|
const turnTakingBlock = turn_taking_limit && turn_taking_limit > 0 ? renderTurnTakingHint(turn_taking_limit) : [];
|
|
119281
|
-
const taskHandleBlock = taskHandleTasklistGuid ? renderTaskHandleBlock(taskHandleTasklistGuid, taskHandleClaimed, taskHandleCandidates) : [];
|
|
119402
|
+
const taskHandleBlock = taskHandleTasklistGuid && !taskRoot ? renderTaskHandleBlock(taskHandleTasklistGuid, taskHandleClaimed, taskHandleCandidates) : [];
|
|
119403
|
+
const taskRootBlock = taskRoot ? renderTaskRootBlock(taskRoot) : [];
|
|
119282
119404
|
const runtimeWarningsBlock = renderRuntimeWarningsBlock(runtimeWarnings);
|
|
119283
119405
|
const extraRepos = extraRepoPaths ?? conventions.extraRepoPaths ?? [];
|
|
119284
119406
|
const workspaceBlock = isAgentWorkspace ? await renderAgentWorkspaceBlock(conventions, extraRepos, isNewThread, mtimeFacts) : hasRepo ? renderWorkspaceBlock(
|
|
@@ -119395,6 +119517,7 @@ async function renderPrompt(input) {
|
|
|
119395
119517
|
...peersBlock.length > 0 ? ["", ...peersBlock] : [],
|
|
119396
119518
|
...turnTakingBlock.length > 0 ? ["", ...turnTakingBlock] : [],
|
|
119397
119519
|
...taskHandleBlock.length > 0 ? ["", ...taskHandleBlock] : [],
|
|
119520
|
+
...taskRootBlock.length > 0 ? ["", ...taskRootBlock] : [],
|
|
119398
119521
|
"",
|
|
119399
119522
|
"<user-message>",
|
|
119400
119523
|
`${parsed.senderOpenId}: ${parsed.text}`,
|
|
@@ -119451,6 +119574,7 @@ async function renderPrompt(input) {
|
|
|
119451
119574
|
...peersBlock.length > 0 ? ["", ...peersBlock] : [],
|
|
119452
119575
|
...turnTakingBlock.length > 0 ? ["", ...turnTakingBlock] : [],
|
|
119453
119576
|
...taskHandleBlock.length > 0 ? ["", ...taskHandleBlock] : [],
|
|
119577
|
+
...taskRootBlock.length > 0 ? ["", ...taskRootBlock] : [],
|
|
119454
119578
|
"",
|
|
119455
119579
|
"<user-message>",
|
|
119456
119580
|
`${parsed.senderOpenId}: ${parsed.text}`,
|
|
@@ -121946,6 +122070,13 @@ function derivePostIdempotencyKey(input) {
|
|
|
121946
122070
|
var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
121947
122071
|
var DEFAULT_CARDKIT_IDLE_TIMEOUT_MS = 3 * 60 * 1e3;
|
|
121948
122072
|
var COT_BUBBLE_CREATE_BUDGET_MS = 3e3;
|
|
122073
|
+
var DEFAULT_STUCK_SESSION_RESET_AFTER = 3;
|
|
122074
|
+
function resolveStuckSessionResetAfter() {
|
|
122075
|
+
const raw = process.env.LARKWAY_STUCK_SESSION_RESET_AFTER;
|
|
122076
|
+
if (raw === void 0) return DEFAULT_STUCK_SESSION_RESET_AFTER;
|
|
122077
|
+
const n = Number.parseInt(raw, 10);
|
|
122078
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_STUCK_SESSION_RESET_AFTER;
|
|
122079
|
+
}
|
|
121949
122080
|
function summarizeMentionPolicyRules(rules) {
|
|
121950
122081
|
const counts = /* @__PURE__ */ new Map();
|
|
121951
122082
|
for (const rule of rules) {
|
|
@@ -122471,7 +122602,34 @@ var BridgeHandler = class {
|
|
|
122471
122602
|
const existing = this.deps.sessionStore.get(threadId, botId);
|
|
122472
122603
|
const isNewThread = existing === void 0;
|
|
122473
122604
|
const isTopLevel = !(typeof parsed.raw.root_id === "string" && parsed.raw.root_id);
|
|
122474
|
-
|
|
122605
|
+
let replyInThread = isTopLevel;
|
|
122606
|
+
let replyAnchorId = messageId;
|
|
122607
|
+
let taskRootInfo;
|
|
122608
|
+
let deferredTaskRootProbe;
|
|
122609
|
+
{
|
|
122610
|
+
const rootId = typeof parsed.raw.root_id === "string" && parsed.raw.root_id ? parsed.raw.root_id : void 0;
|
|
122611
|
+
const rawThreadId = typeof parsed.raw.thread_id === "string" && parsed.raw.thread_id ? parsed.raw.thread_id : void 0;
|
|
122612
|
+
if (rootId && this.deps.messageLookup) {
|
|
122613
|
+
const probe = this.deps.messageLookup.get(rootId).catch(() => void 0);
|
|
122614
|
+
if (rawThreadId) {
|
|
122615
|
+
deferredTaskRootProbe = probe;
|
|
122616
|
+
} else {
|
|
122617
|
+
const info = await probe;
|
|
122618
|
+
if (info?.msgType === "todo" && info.content) {
|
|
122619
|
+
const todo = parseTodoShareContent(info.content);
|
|
122620
|
+
if (todo) {
|
|
122621
|
+
taskRootInfo = {
|
|
122622
|
+
guid: todo.taskGuid,
|
|
122623
|
+
summary: todo.summaryText,
|
|
122624
|
+
topicLink: info.threadId ? buildTopicDeepLink(parsed.chatId, info.threadId) : void 0
|
|
122625
|
+
};
|
|
122626
|
+
replyInThread = true;
|
|
122627
|
+
replyAnchorId = rootId;
|
|
122628
|
+
}
|
|
122629
|
+
}
|
|
122630
|
+
}
|
|
122631
|
+
}
|
|
122632
|
+
}
|
|
122475
122633
|
const prototypeConfig = this.deps.botConfig?.response_surface_prototype;
|
|
122476
122634
|
const cardKitAvailable = isResponseSurfaceCardKitAvailable(
|
|
122477
122635
|
prototypeConfig,
|
|
@@ -122487,7 +122645,7 @@ var BridgeHandler = class {
|
|
|
122487
122645
|
let startFailurePostFallbackSent = false;
|
|
122488
122646
|
if (!cardKitAvailable) {
|
|
122489
122647
|
try {
|
|
122490
|
-
card = await this.deps.cardRenderer.start(
|
|
122648
|
+
card = await this.deps.cardRenderer.start(replyAnchorId, { replyInThread, threadId });
|
|
122491
122649
|
await recordEvent({
|
|
122492
122650
|
status: "running",
|
|
122493
122651
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -122586,7 +122744,7 @@ var BridgeHandler = class {
|
|
|
122586
122744
|
try {
|
|
122587
122745
|
cardKitProgress = await createCardKitProgressHandle({
|
|
122588
122746
|
cardKitClient: this.deps.cardKitClient,
|
|
122589
|
-
replyToMessageId:
|
|
122747
|
+
replyToMessageId: replyAnchorId,
|
|
122590
122748
|
replyInThread,
|
|
122591
122749
|
facts: {
|
|
122592
122750
|
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
@@ -122617,7 +122775,7 @@ var BridgeHandler = class {
|
|
|
122617
122775
|
status: "message_sent",
|
|
122618
122776
|
cardId: cardKitProgress.cardId,
|
|
122619
122777
|
messageId: cardKitProgress.messageId,
|
|
122620
|
-
replyToMessageId:
|
|
122778
|
+
replyToMessageId: replyAnchorId,
|
|
122621
122779
|
chatId: parsed.chatId,
|
|
122622
122780
|
threadId,
|
|
122623
122781
|
botId: this.deps.botConfig?.id ?? "",
|
|
@@ -122662,7 +122820,7 @@ var BridgeHandler = class {
|
|
|
122662
122820
|
}
|
|
122663
122821
|
if (!card && cardKitStartFailed) {
|
|
122664
122822
|
try {
|
|
122665
|
-
card = await this.deps.cardRenderer.start(
|
|
122823
|
+
card = await this.deps.cardRenderer.start(replyAnchorId, { replyInThread, threadId });
|
|
122666
122824
|
await this.deps.client.removeProcessingReaction?.(messageId);
|
|
122667
122825
|
await recordEvent({
|
|
122668
122826
|
status: "running",
|
|
@@ -122677,7 +122835,7 @@ var BridgeHandler = class {
|
|
|
122677
122835
|
);
|
|
122678
122836
|
const postFallback = await createOnlyPostFallback({
|
|
122679
122837
|
postClient: this.deps.postClient,
|
|
122680
|
-
replyToMessageId:
|
|
122838
|
+
replyToMessageId: replyAnchorId,
|
|
122681
122839
|
replyInThread,
|
|
122682
122840
|
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
122683
122841
|
threadId,
|
|
@@ -122914,6 +123072,26 @@ var BridgeHandler = class {
|
|
|
122914
123072
|
console.warn("[bridge.handler] live-roster resolve failed (using static peers):", err);
|
|
122915
123073
|
}
|
|
122916
123074
|
}
|
|
123075
|
+
if (deferredTaskRootProbe) {
|
|
123076
|
+
const info = await deferredTaskRootProbe;
|
|
123077
|
+
if (info?.msgType === "todo" && info.content) {
|
|
123078
|
+
const todo = parseTodoShareContent(info.content);
|
|
123079
|
+
if (todo) {
|
|
123080
|
+
const rawThreadId = typeof parsed.raw.thread_id === "string" && parsed.raw.thread_id ? parsed.raw.thread_id : void 0;
|
|
123081
|
+
taskRootInfo = {
|
|
123082
|
+
guid: todo.taskGuid,
|
|
123083
|
+
summary: todo.summaryText,
|
|
123084
|
+
topicLink: rawThreadId ? buildTopicDeepLink(parsed.chatId, rawThreadId) : info.threadId ? buildTopicDeepLink(parsed.chatId, info.threadId) : void 0
|
|
123085
|
+
};
|
|
123086
|
+
}
|
|
123087
|
+
}
|
|
123088
|
+
}
|
|
123089
|
+
if (taskRootInfo && !taskRootInfo.topicLink && replyAnchorId !== messageId && this.deps.messageLookup) {
|
|
123090
|
+
const refreshed = await this.deps.messageLookup.get(replyAnchorId, { refresh: true }).catch(() => void 0);
|
|
123091
|
+
if (refreshed?.threadId) {
|
|
123092
|
+
taskRootInfo.topicLink = buildTopicDeepLink(parsed.chatId, refreshed.threadId);
|
|
123093
|
+
}
|
|
123094
|
+
}
|
|
122917
123095
|
const prompt = await renderPrompt({
|
|
122918
123096
|
parsed,
|
|
122919
123097
|
isNewThread: currentIsNewThread,
|
|
@@ -122945,6 +123123,13 @@ var BridgeHandler = class {
|
|
|
122945
123123
|
taskHandleTasklistGuid: this.deps.botConfig?.taskHandle?.tasklistGuid,
|
|
122946
123124
|
taskHandleClaimed: this.deps.taskHandleClaimedLookup?.(threadId) ?? false,
|
|
122947
123125
|
taskHandleCandidates: this.deps.taskHandleCandidatesLookup?.() ?? [],
|
|
123126
|
+
taskRoot: taskRootInfo ? {
|
|
123127
|
+
...taskRootInfo,
|
|
123128
|
+
// Exact-guid comparison (see taskHandleClaimGuidLookup's doc);
|
|
123129
|
+
// falls back to the boolean lookup only when the precise one
|
|
123130
|
+
// isn't wired (older embedding code).
|
|
123131
|
+
claimed: this.deps.taskHandleClaimGuidLookup ? this.deps.taskHandleClaimGuidLookup(threadId) === taskRootInfo.guid : this.deps.taskHandleClaimedLookup?.(threadId) ?? false
|
|
123132
|
+
} : void 0,
|
|
122948
123133
|
mtimeFacts
|
|
122949
123134
|
});
|
|
122950
123135
|
const backend = this.deps.botConfig?.backend ?? "claude";
|
|
@@ -123086,40 +123271,19 @@ var BridgeHandler = class {
|
|
|
123086
123271
|
botId,
|
|
123087
123272
|
threadId,
|
|
123088
123273
|
chatId: parsed.chatId,
|
|
123089
|
-
taskGuid: claimedTaskGuid
|
|
123274
|
+
taskGuid: claimedTaskGuid,
|
|
123275
|
+
// v4 任务派单 (docs/task-handle.md §15.3): a claim on the very
|
|
123276
|
+
// task this thread's ROOT message shares is comment-mode —
|
|
123277
|
+
// maintenance goes through task comments only (share-to-chat
|
|
123278
|
+
// grants read+comment; no tasklist/editor rights needed, and
|
|
123279
|
+
// completion is ALWAYS ticked by the human). Mechanical
|
|
123280
|
+
// equality check, not a judgment call.
|
|
123281
|
+
mode: taskRootInfo?.guid === claimedTaskGuid ? "comment" : void 0
|
|
123090
123282
|
});
|
|
123091
123283
|
} catch (err) {
|
|
123092
123284
|
console.warn("[bridge.handler] taskHandleClaim hook failed (continuing):", err);
|
|
123093
123285
|
}
|
|
123094
123286
|
}
|
|
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
123287
|
const reportedStatus = reportedState?.status;
|
|
123124
123288
|
const reportedError = reportedState?.error;
|
|
123125
123289
|
const cardKitTimeoutFailure = interruptedByIdle && reportedStatus !== "ready" && reportedStatus !== "failed";
|
|
@@ -123149,10 +123313,53 @@ var BridgeHandler = class {
|
|
|
123149
123313
|
reason: failureReason
|
|
123150
123314
|
});
|
|
123151
123315
|
}
|
|
123316
|
+
const stuckResetAfter = resolveStuckSessionResetAfter();
|
|
123317
|
+
const prevStuckCount = currentExisting?.consecutiveStuckCount ?? 0;
|
|
123318
|
+
const nextStuckCount = cardKitTimeoutFailure ? prevStuckCount + 1 : success ? 0 : prevStuckCount;
|
|
123319
|
+
const stuckResetTriggered = cardKitTimeoutFailure && nextStuckCount >= stuckResetAfter;
|
|
123320
|
+
const now = Date.now();
|
|
123321
|
+
if (stuckResetTriggered) {
|
|
123322
|
+
await this.deps.sessionStore.delete(threadId, botId);
|
|
123323
|
+
console.info(
|
|
123324
|
+
`[bridge.handler] BL-38 stuck-session reset: thread=${threadId} bot=${botId} consecutiveStuckCount=${nextStuckCount} (>= ${stuckResetAfter}) \u2014 dropped session record; next @ starts fresh`
|
|
123325
|
+
);
|
|
123326
|
+
} else if (sessionId !== void 0 && currentExisting === void 0) {
|
|
123327
|
+
await this.deps.sessionStore.put({
|
|
123328
|
+
threadId,
|
|
123329
|
+
sessionId,
|
|
123330
|
+
botId,
|
|
123331
|
+
createdTs: now,
|
|
123332
|
+
lastActiveTs: now,
|
|
123333
|
+
senderOpenId,
|
|
123334
|
+
// BL-38: a brand-new thread that idle-killed on its very first turn
|
|
123335
|
+
// starts the counter at 1 (0 when clean; only persisted when > 0).
|
|
123336
|
+
consecutiveStuckCount: nextStuckCount,
|
|
123337
|
+
...isTopLevel ? { rootText: parsed.text.slice(0, 200), chatId: parsed.chatId } : {}
|
|
123338
|
+
});
|
|
123339
|
+
} else if (sessionId !== void 0 && currentExisting !== void 0) {
|
|
123340
|
+
await this.deps.sessionStore.put({
|
|
123341
|
+
threadId,
|
|
123342
|
+
sessionId,
|
|
123343
|
+
botId,
|
|
123344
|
+
createdTs: currentExisting.createdTs,
|
|
123345
|
+
lastActiveTs: now,
|
|
123346
|
+
senderOpenId,
|
|
123347
|
+
rootText: currentExisting.rootText,
|
|
123348
|
+
chatId: currentExisting.chatId,
|
|
123349
|
+
// BL-38: +1 on an idle-stuck turn, 0 (cleared) on any clean turn.
|
|
123350
|
+
consecutiveStuckCount: nextStuckCount
|
|
123351
|
+
});
|
|
123352
|
+
} else if (currentExisting !== void 0 && sessionId === void 0) {
|
|
123353
|
+
await this.deps.sessionStore.put({
|
|
123354
|
+
...currentExisting,
|
|
123355
|
+
lastActiveTs: now,
|
|
123356
|
+
consecutiveStuckCount: nextStuckCount
|
|
123357
|
+
});
|
|
123358
|
+
}
|
|
123152
123359
|
const fallbackAnswer = trustedAnswerText.trim() || cardKitProgress?.answerText.trim() || "";
|
|
123153
123360
|
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
123361
|
` : "") + "\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);
|
|
123362
|
+
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
123363
|
const noReportThisTurn = reportedState === null;
|
|
123157
123364
|
const neutralTitle = noReportThisTurn && success && !failureReason ? "\u{1F4AC} \u5DF2\u56DE\u590D" : void 0;
|
|
123158
123365
|
const baseCardPayload = {
|
|
@@ -123216,7 +123423,7 @@ var BridgeHandler = class {
|
|
|
123216
123423
|
console.warn("[bridge.handler] CardKit finalize failed; using card fallback:", err);
|
|
123217
123424
|
cardKitProgress.close();
|
|
123218
123425
|
try {
|
|
123219
|
-
card = await this.deps.cardRenderer.start(
|
|
123426
|
+
card = await this.deps.cardRenderer.start(replyAnchorId, { replyInThread, threadId });
|
|
123220
123427
|
await writeCardFile(worktreePath, {
|
|
123221
123428
|
messageId: card.messageId,
|
|
123222
123429
|
chatId: parsed.chatId,
|
|
@@ -123241,7 +123448,7 @@ var BridgeHandler = class {
|
|
|
123241
123448
|
} catch (legacyErr) {
|
|
123242
123449
|
const postFallback = await createOnlyPostFallback({
|
|
123243
123450
|
postClient: this.deps.postClient,
|
|
123244
|
-
replyToMessageId:
|
|
123451
|
+
replyToMessageId: replyAnchorId,
|
|
123245
123452
|
replyInThread,
|
|
123246
123453
|
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
123247
123454
|
threadId,
|
|
@@ -123265,7 +123472,7 @@ var BridgeHandler = class {
|
|
|
123265
123472
|
} else {
|
|
123266
123473
|
if (!card) {
|
|
123267
123474
|
try {
|
|
123268
|
-
card = await this.deps.cardRenderer.start(
|
|
123475
|
+
card = await this.deps.cardRenderer.start(replyAnchorId, { replyInThread, threadId });
|
|
123269
123476
|
try {
|
|
123270
123477
|
await writeCardFile(worktreePath, {
|
|
123271
123478
|
messageId: card.messageId,
|
|
@@ -123289,7 +123496,7 @@ var BridgeHandler = class {
|
|
|
123289
123496
|
].filter((part) => !!part).join("; ");
|
|
123290
123497
|
const postFallback = await createOnlyPostFallback({
|
|
123291
123498
|
postClient: this.deps.postClient,
|
|
123292
|
-
replyToMessageId:
|
|
123499
|
+
replyToMessageId: replyAnchorId,
|
|
123293
123500
|
replyInThread,
|
|
123294
123501
|
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
123295
123502
|
threadId,
|
|
@@ -123370,7 +123577,7 @@ var BridgeHandler = class {
|
|
|
123370
123577
|
const createHardFailurePostFallback = async (failureReason) => {
|
|
123371
123578
|
const fallback = await createOnlyPostFallback({
|
|
123372
123579
|
postClient: this.deps.postClient,
|
|
123373
|
-
replyToMessageId:
|
|
123580
|
+
replyToMessageId: replyAnchorId,
|
|
123374
123581
|
replyInThread,
|
|
123375
123582
|
botId: this.deps.botConfig?.id ?? "v1-default",
|
|
123376
123583
|
threadId,
|
|
@@ -123399,7 +123606,7 @@ var BridgeHandler = class {
|
|
|
123399
123606
|
cardKitFinalizeErr
|
|
123400
123607
|
);
|
|
123401
123608
|
try {
|
|
123402
|
-
card = await this.deps.cardRenderer.start(
|
|
123609
|
+
card = await this.deps.cardRenderer.start(replyAnchorId, { replyInThread, threadId });
|
|
123403
123610
|
} catch (cardStartErr) {
|
|
123404
123611
|
console.error("[bridge.handler] failure card start also failed:", cardStartErr);
|
|
123405
123612
|
await createHardFailurePostFallback(
|
|
@@ -129989,7 +130196,7 @@ function isStallNudgeState(value) {
|
|
|
129989
130196
|
function isTaskHandleRecord(value) {
|
|
129990
130197
|
if (typeof value !== "object" || value === null) return false;
|
|
129991
130198
|
const v = value;
|
|
129992
|
-
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");
|
|
130199
|
+
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");
|
|
129993
130200
|
}
|
|
129994
130201
|
var TaskHandleStore = class _TaskHandleStore {
|
|
129995
130202
|
#filePath;
|
|
@@ -130184,6 +130391,9 @@ var TaskHandleStore = class _TaskHandleStore {
|
|
|
130184
130391
|
await this.update(input.threadId, (existing) => {
|
|
130185
130392
|
if (existing && existing.taskGuid === input.taskGuid) {
|
|
130186
130393
|
claimed = true;
|
|
130394
|
+
if (input.mode === "comment" && existing.mode === void 0) {
|
|
130395
|
+
return { ...existing, mode: "comment" };
|
|
130396
|
+
}
|
|
130187
130397
|
return existing;
|
|
130188
130398
|
}
|
|
130189
130399
|
if (input.onlyIfThreadUnclaimed && existing !== void 0) {
|
|
@@ -130197,7 +130407,13 @@ var TaskHandleStore = class _TaskHandleStore {
|
|
|
130197
130407
|
}
|
|
130198
130408
|
}
|
|
130199
130409
|
claimed = true;
|
|
130200
|
-
return {
|
|
130410
|
+
return {
|
|
130411
|
+
threadId: input.threadId,
|
|
130412
|
+
taskGuid: input.taskGuid,
|
|
130413
|
+
chatId: input.chatId,
|
|
130414
|
+
claimedTs: Date.now(),
|
|
130415
|
+
...input.mode ? { mode: input.mode } : {}
|
|
130416
|
+
};
|
|
130201
130417
|
});
|
|
130202
130418
|
return { claimed, reason };
|
|
130203
130419
|
}
|
|
@@ -130293,7 +130509,7 @@ function isTaskRequestTimeoutError(err) {
|
|
|
130293
130509
|
if (err instanceof TaskApiError && err.cause instanceof TaskRequestTimeoutError) return true;
|
|
130294
130510
|
return false;
|
|
130295
130511
|
}
|
|
130296
|
-
function
|
|
130512
|
+
function withTimeout3(promise, ms, label) {
|
|
130297
130513
|
let timer;
|
|
130298
130514
|
const timeout = new Promise((_, reject) => {
|
|
130299
130515
|
timer = setTimeout(() => reject(new TaskRequestTimeoutError(`${label} timed out after ${ms}ms`)), ms);
|
|
@@ -130310,7 +130526,7 @@ var TaskListClient = class {
|
|
|
130310
130526
|
}
|
|
130311
130527
|
/** Single choke point for every outbound call — applies the timeout race uniformly. */
|
|
130312
130528
|
#request(config, label) {
|
|
130313
|
-
return
|
|
130529
|
+
return withTimeout3(this.#requester.request(config), this.#timeoutMs, label);
|
|
130314
130530
|
}
|
|
130315
130531
|
async getTask(taskGuid) {
|
|
130316
130532
|
let resp;
|
|
@@ -130652,6 +130868,46 @@ async function applyTaskHandleWriteback(patch, deps) {
|
|
|
130652
130868
|
return;
|
|
130653
130869
|
}
|
|
130654
130870
|
const isCompleted = !!task.completedAt && task.completedAt !== "0";
|
|
130871
|
+
if (record.mode === "comment") {
|
|
130872
|
+
switch (patch.status) {
|
|
130873
|
+
case "received": {
|
|
130874
|
+
if (record.doneDeclared) {
|
|
130875
|
+
await deps.store.update(
|
|
130876
|
+
patch.threadId,
|
|
130877
|
+
(current) => current ? { ...current, doneDeclared: void 0 } : current
|
|
130878
|
+
);
|
|
130879
|
+
}
|
|
130880
|
+
break;
|
|
130881
|
+
}
|
|
130882
|
+
case "completed": {
|
|
130883
|
+
const mentionedPeerBotIds = patch.mentionedPeerBotIds && patch.mentionedPeerBotIds.length > 0 ? patch.mentionedPeerBotIds : void 0;
|
|
130884
|
+
const mentionAnchorMs = patch.turnReceivedAt ?? Date.now();
|
|
130885
|
+
await deps.store.update(
|
|
130886
|
+
patch.threadId,
|
|
130887
|
+
(current) => current ? {
|
|
130888
|
+
...current,
|
|
130889
|
+
lastTurnOutcome: "completed",
|
|
130890
|
+
lastTurnMentions: mentionedPeerBotIds,
|
|
130891
|
+
lastTurnMentionsAt: mentionedPeerBotIds ? mentionAnchorMs : void 0,
|
|
130892
|
+
// Agent declared delivery — its own "已交付" comment is the
|
|
130893
|
+
// user-facing artifact; bridge just stops patrolling the
|
|
130894
|
+
// "delivered, human hasn't ticked complete yet" window.
|
|
130895
|
+
...patch.agentDeclaredDone === true ? { doneDeclared: true } : {}
|
|
130896
|
+
} : current
|
|
130897
|
+
);
|
|
130898
|
+
break;
|
|
130899
|
+
}
|
|
130900
|
+
case "failed": {
|
|
130901
|
+
await deps.store.update(
|
|
130902
|
+
patch.threadId,
|
|
130903
|
+
(current) => current ? { ...current, lastTurnOutcome: "failed", lastTurnMentions: void 0, lastTurnMentionsAt: void 0 } : current
|
|
130904
|
+
);
|
|
130905
|
+
await deps.client.addComment(record.taskGuid, renderFailureComment(patch.failureReason));
|
|
130906
|
+
break;
|
|
130907
|
+
}
|
|
130908
|
+
}
|
|
130909
|
+
return;
|
|
130910
|
+
}
|
|
130655
130911
|
switch (patch.status) {
|
|
130656
130912
|
case "received": {
|
|
130657
130913
|
if (isCompleted) {
|
|
@@ -131413,6 +131669,7 @@ var StallDetector = class {
|
|
|
131413
131669
|
async #pollOne(threadId) {
|
|
131414
131670
|
let record = this.#deps.store.get(threadId);
|
|
131415
131671
|
if (!record) return;
|
|
131672
|
+
if (record.doneDeclared) return;
|
|
131416
131673
|
const lastActiveTs = this.#deps.getLastActiveTs(threadId) ?? record.claimedTs;
|
|
131417
131674
|
const now = Date.now();
|
|
131418
131675
|
if (record.stallSuppressUntilActivityAfter !== void 0 && lastActiveTs <= record.stallSuppressUntilActivityAfter) {
|
|
@@ -131659,6 +131916,7 @@ var StallDetector = class {
|
|
|
131659
131916
|
}
|
|
131660
131917
|
} : r
|
|
131661
131918
|
);
|
|
131919
|
+
if (record.mode === "comment") return;
|
|
131662
131920
|
const logSummary = `\u505C\u6EDE\u5524\u9192 #${prospectiveCount}(${this.#nudgeReasonLabel(reason, dueMs)})`;
|
|
131663
131921
|
const nextDescription = mergeDescriptionSnapshot(task.description, {
|
|
131664
131922
|
status: "in_progress",
|
|
@@ -131917,12 +132175,18 @@ async function runV2Mode({
|
|
|
131917
132175
|
let taskHandleLifecycle;
|
|
131918
132176
|
let taskHandleClaim;
|
|
131919
132177
|
let taskHandleClaimedLookup;
|
|
132178
|
+
let taskHandleClaimGuidLookup;
|
|
131920
132179
|
let taskHandleCandidatesLookup;
|
|
131921
132180
|
let taskCommentPoller;
|
|
131922
132181
|
let stallDetector;
|
|
131923
132182
|
{
|
|
131924
132183
|
const guid = bot.taskHandle?.tasklistGuid ?? await readTeamTasklistGuid(resolveTaskTeamRegistryPath());
|
|
131925
|
-
if (guid) {
|
|
132184
|
+
if (!guid) {
|
|
132185
|
+
console.log(
|
|
132186
|
+
`[larkway] bot "${bot.id}": \u4EFB\u52A1\u6D3E\u5355\u4E3B\u8DEF\u5F84\u5DF2\u5C31\u7EEA(\u5EFA\u4EFB\u52A1\u2192\u53D1\u9001\u5230\u7FA4\u2192@ \u672C bot,\u96F6\u914D\u7F6E)\u3002\u8BDD\u9898\u8F6C\u4EFB\u52A1\u8F85\u8DEF\u5F84\u7684\u5171\u4EAB\u6E05\u5355\u672A\u914D\u7F6E(taskHandle.tasklistGuid \u7F3A\u5931,\u5171\u4EAB\u6CE8\u518C\u6587\u4EF6\u91CC\u4E5F\u6CA1\u6709);\u60F3\u542F\u7528:\u8FD0\u884C larkway tasklist-init(\u89C1 docs/task-handle.md \xA77/\xA715)\u3002`
|
|
132187
|
+
);
|
|
132188
|
+
}
|
|
132189
|
+
{
|
|
131926
132190
|
const taskSdkClient = new import_node_sdk2.Client({
|
|
131927
132191
|
appId: bot.app_id,
|
|
131928
132192
|
appSecret,
|
|
@@ -131932,12 +132196,14 @@ async function runV2Mode({
|
|
|
131932
132196
|
request: (config2) => taskSdkClient.request(config2)
|
|
131933
132197
|
};
|
|
131934
132198
|
const taskListClient = new TaskListClient(taskRequester);
|
|
131935
|
-
|
|
131936
|
-
|
|
131937
|
-
|
|
131938
|
-
|
|
131939
|
-
|
|
131940
|
-
|
|
132199
|
+
if (guid) {
|
|
132200
|
+
await taskListClient.addTasklistMembers(guid, [{ id: bot.app_id, type: "app", role: "editor" }]).catch((err) => {
|
|
132201
|
+
console.warn(
|
|
132202
|
+
`[larkway] bot "${bot.id}": self-join tasklist ${guid} as editor failed (continuing, best-effort): ${compactErrorText(err)}`
|
|
132203
|
+
);
|
|
132204
|
+
});
|
|
132205
|
+
effectiveTaskHandleTasklistGuid = guid;
|
|
132206
|
+
}
|
|
131941
132207
|
const taskHandleStore = await TaskHandleStore.load(resolveTaskHandlesPath(bot.id));
|
|
131942
132208
|
taskHandleLifecycle = (patch) => applyTaskHandleWriteback(patch, { store: taskHandleStore, client: taskListClient });
|
|
131943
132209
|
taskHandleClaim = async (patch) => {
|
|
@@ -131949,12 +132215,13 @@ async function runV2Mode({
|
|
|
131949
132215
|
}
|
|
131950
132216
|
};
|
|
131951
132217
|
taskHandleClaimedLookup = (threadId) => taskHandleStore.get(threadId) !== void 0;
|
|
131952
|
-
|
|
132218
|
+
taskHandleClaimGuidLookup = (threadId) => taskHandleStore.get(threadId)?.taskGuid;
|
|
132219
|
+
if (guid) {
|
|
131953
132220
|
const group = tasklistGuidGroups.get(guid) ?? { client: taskListClient, clientOwnerBotId: bot.id, bots: [] };
|
|
131954
132221
|
group.bots.push({ botId: bot.id, sessionStore, taskHandleStore });
|
|
131955
132222
|
tasklistGuidGroups.set(guid, group);
|
|
132223
|
+
taskHandleCandidatesLookup = () => tasklistPollersByGuid.get(guid)?.getCandidates() ?? [];
|
|
131956
132224
|
}
|
|
131957
|
-
taskHandleCandidatesLookup = () => tasklistPollersByGuid.get(guid)?.getCandidates() ?? [];
|
|
131958
132225
|
taskCommentPoller = new CommentPoller({
|
|
131959
132226
|
store: taskHandleStore,
|
|
131960
132227
|
client: taskListClient,
|
|
@@ -132017,7 +132284,7 @@ async function runV2Mode({
|
|
|
132017
132284
|
// taskHandleCandidatesLookup above (the poller for `guid` is
|
|
132018
132285
|
// constructed AFTER this whole per-bot loop finishes, but this
|
|
132019
132286
|
// closure is only ever INVOKED later, at poll time).
|
|
132020
|
-
getTaskDueMs: (taskGuid) => tasklistPollersByGuid.get(guid)?.getDueTimestamp(taskGuid),
|
|
132287
|
+
getTaskDueMs: (taskGuid) => guid ? tasklistPollersByGuid.get(guid)?.getDueTimestamp(taskGuid) : void 0,
|
|
132021
132288
|
// Round-2 adversarial review fix: THIS bot's own receipt signal
|
|
132022
132289
|
// (not a peer's) — disambiguates a pending-nudge confirmation
|
|
132023
132290
|
// timeout from a nudge turn merely queued behind the semaphore.
|
|
@@ -132066,10 +132333,6 @@ async function runV2Mode({
|
|
|
132066
132333
|
);
|
|
132067
132334
|
stallDetector.start();
|
|
132068
132335
|
}
|
|
132069
|
-
} else {
|
|
132070
|
-
console.log(
|
|
132071
|
-
`[larkway] bot "${bot.id}": \u8BDD\u9898\u2194\u4EFB\u52A1\u53E5\u67C4\u672A\u914D\u7F6E(taskHandle.tasklistGuid \u7F3A\u5931,\u5171\u4EAB\u6CE8\u518C\u6587\u4EF6\u91CC\u4E5F\u6CA1\u6709)\u3002\u60F3\u542F\u7528:\u8FD0\u884C larkway tasklist-init(\u6216\u5BF9\u4F60\u7684 agent \u8BF4"\u5E2E\u6211\u914D\u7F6E Agent Team")(\u89C1 docs/task-handle.md \xA77)\u3002`
|
|
132072
|
-
);
|
|
132073
132336
|
}
|
|
132074
132337
|
}
|
|
132075
132338
|
let codexPool;
|
|
@@ -132140,6 +132403,10 @@ async function runV2Mode({
|
|
|
132140
132403
|
cardKitClient,
|
|
132141
132404
|
cotClient,
|
|
132142
132405
|
postClient,
|
|
132406
|
+
// v4 任务派单 root-type probe (docs/task-handle.md §15.4) — lazy channel
|
|
132407
|
+
// resolution, best-effort by the client's own contract, no config gate
|
|
132408
|
+
// (the probe only ever fires on quote-reply turns).
|
|
132409
|
+
messageLookup: client.outboundMessageLookupClient(),
|
|
132143
132410
|
gitlabToken: effectiveGitlabToken,
|
|
132144
132411
|
agentMemory: bot.agent_memory,
|
|
132145
132412
|
larkCliProfile,
|
|
@@ -132158,6 +132425,7 @@ async function runV2Mode({
|
|
|
132158
132425
|
taskHandleLifecycle,
|
|
132159
132426
|
taskHandleClaim,
|
|
132160
132427
|
taskHandleClaimedLookup,
|
|
132428
|
+
taskHandleClaimGuidLookup,
|
|
132161
132429
|
taskHandleCandidatesLookup
|
|
132162
132430
|
});
|
|
132163
132431
|
handlersByBotId.set(bot.id, handler);
|