larkway 0.3.23 → 0.3.25
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/main.js +326 -34
- 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.25**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
package/README.zh.md
CHANGED
package/dist/main.js
CHANGED
|
@@ -117647,6 +117647,7 @@ function deriveTriggerFacts(parsed, isNewThread, larkCliProfile) {
|
|
|
117647
117647
|
// src/agent/answerChannel.ts
|
|
117648
117648
|
var ANSWER_BEGIN_MARKER = "LARKWAY_ANSWER_BEGIN";
|
|
117649
117649
|
var ANSWER_END_MARKER = "LARKWAY_ANSWER_END";
|
|
117650
|
+
var STREAM_HOLD_CHARS = ANSWER_END_MARKER.length + 2;
|
|
117650
117651
|
function stripLeadingNewline(text) {
|
|
117651
117652
|
return text.replace(/^\r?\n/, "");
|
|
117652
117653
|
}
|
|
@@ -117662,6 +117663,9 @@ function markerLineIndex(text, marker) {
|
|
|
117662
117663
|
const after = lineEnd + (match[2]?.length ?? 0);
|
|
117663
117664
|
return { start: lineStart, end: after };
|
|
117664
117665
|
}
|
|
117666
|
+
function hasUsefulText(text) {
|
|
117667
|
+
return text.length > 0;
|
|
117668
|
+
}
|
|
117665
117669
|
function splitAnswerChannelText(text, raw) {
|
|
117666
117670
|
const begin = markerLineIndex(text, ANSWER_BEGIN_MARKER);
|
|
117667
117671
|
if (!begin) return [{ type: "internal_text", text, raw }];
|
|
@@ -117676,6 +117680,71 @@ function splitAnswerChannelText(text, raw) {
|
|
|
117676
117680
|
if (trailing.trim()) events.push({ type: "internal_text", text: trailing, raw });
|
|
117677
117681
|
return events;
|
|
117678
117682
|
}
|
|
117683
|
+
var AnswerChannelExtractor = class {
|
|
117684
|
+
mode = "waiting";
|
|
117685
|
+
buffer = "";
|
|
117686
|
+
visibleText = "";
|
|
117687
|
+
ingestDelta(text, raw) {
|
|
117688
|
+
if (!text || this.mode === "closed") return [];
|
|
117689
|
+
this.buffer += text;
|
|
117690
|
+
return this.drain(raw);
|
|
117691
|
+
}
|
|
117692
|
+
ingestSnapshot(text, raw) {
|
|
117693
|
+
const events = splitAnswerChannelText(text, raw);
|
|
117694
|
+
const out = [];
|
|
117695
|
+
for (const event of events) {
|
|
117696
|
+
if (event.type !== "answer_snapshot") {
|
|
117697
|
+
out.push(event);
|
|
117698
|
+
continue;
|
|
117699
|
+
}
|
|
117700
|
+
if (event.text === this.visibleText) continue;
|
|
117701
|
+
this.visibleText = event.text;
|
|
117702
|
+
out.push(event);
|
|
117703
|
+
if (text.includes(ANSWER_END_MARKER)) this.mode = "closed";
|
|
117704
|
+
}
|
|
117705
|
+
return out;
|
|
117706
|
+
}
|
|
117707
|
+
drain(raw) {
|
|
117708
|
+
const events = [];
|
|
117709
|
+
if (this.mode === "waiting") {
|
|
117710
|
+
const begin = markerLineIndex(this.buffer, ANSWER_BEGIN_MARKER);
|
|
117711
|
+
if (!begin) {
|
|
117712
|
+
this.trimWaitingBuffer();
|
|
117713
|
+
return events;
|
|
117714
|
+
}
|
|
117715
|
+
const before = stripTrailingNewline(this.buffer.slice(0, begin.start));
|
|
117716
|
+
if (before.trim()) events.push({ type: "internal_text", text: before, raw });
|
|
117717
|
+
this.buffer = this.buffer.slice(begin.end);
|
|
117718
|
+
this.mode = "answer";
|
|
117719
|
+
}
|
|
117720
|
+
if (this.mode !== "answer") return events;
|
|
117721
|
+
const end = markerLineIndex(this.buffer, ANSWER_END_MARKER);
|
|
117722
|
+
if (end) {
|
|
117723
|
+
const answerTail = stripTrailingNewline(this.buffer.slice(0, end.start));
|
|
117724
|
+
if (hasUsefulText(answerTail)) events.push(this.answerDelta(answerTail, raw));
|
|
117725
|
+
const trailing = stripLeadingNewline(this.buffer.slice(end.end));
|
|
117726
|
+
if (trailing.trim()) events.push({ type: "internal_text", text: trailing, raw });
|
|
117727
|
+
this.buffer = "";
|
|
117728
|
+
this.mode = "closed";
|
|
117729
|
+
return events;
|
|
117730
|
+
}
|
|
117731
|
+
if (this.buffer.length <= STREAM_HOLD_CHARS) return events;
|
|
117732
|
+
const emitText = this.buffer.slice(0, this.buffer.length - STREAM_HOLD_CHARS);
|
|
117733
|
+
this.buffer = this.buffer.slice(this.buffer.length - STREAM_HOLD_CHARS);
|
|
117734
|
+
if (hasUsefulText(emitText)) events.push(this.answerDelta(emitText, raw));
|
|
117735
|
+
return events;
|
|
117736
|
+
}
|
|
117737
|
+
answerDelta(text, raw) {
|
|
117738
|
+
this.visibleText += text;
|
|
117739
|
+
return { type: "answer_delta", text, raw };
|
|
117740
|
+
}
|
|
117741
|
+
trimWaitingBuffer() {
|
|
117742
|
+
const max = ANSWER_BEGIN_MARKER.length + 2;
|
|
117743
|
+
if (this.buffer.length > max) {
|
|
117744
|
+
this.buffer = this.buffer.slice(this.buffer.length - max);
|
|
117745
|
+
}
|
|
117746
|
+
}
|
|
117747
|
+
};
|
|
117679
117748
|
|
|
117680
117749
|
// src/claude/prompt.ts
|
|
117681
117750
|
var MEMORY_CATEGORY_FILE_NAMES = [
|
|
@@ -117719,8 +117788,7 @@ function renderStateContract(stateFilePath) {
|
|
|
117719
117788
|
"- status: in_progress / ready / failed(bridge \u552F\u4E00\u5F3A\u4F9D\u8D56\u7684\u5B57\u6BB5)",
|
|
117720
117789
|
"- last_message: \u7ED9\u8FD0\u8425\u770B\u7684\u5361\u7247\u6B63\u6587\u3002\u4F60\u81EA\u884C\u7EC4\u7EC7\u5C55\u793A\u5185\u5BB9,\u4F8B\u5982\u8FDB\u5EA6\u3001\u7ED3\u8BBA\u3001\u8BC1\u636E\u3001MR \u94FE\u63A5\u3001dev \u9884\u89C8\u3001\u9700\u8981\u7528\u6237\u8865\u5145\u7684\u4FE1\u606F",
|
|
117721
117790
|
"- error: \u5931\u8D25\u539F\u56E0(\u642D\u914D status=failed)",
|
|
117722
|
-
"- card_title: \
|
|
117723
|
-
"- card_color: \u5361\u7247\u914D\u8272(\u53EF\u9009,success/failure/neutral,\u4E5F\u53EF\u76F4\u63A5\u5199 green/red/grey)",
|
|
117791
|
+
"- card_title/card_color: \u517C\u5BB9\u5B57\u6BB5; \u9ED8\u8BA4 CardKit \u4E0D\u6E32\u67D3\u9876\u90E8\u6807\u9898\u8272\u6761,legacy/fallback \u5361\u7247\u8DEF\u5F84\u53EF\u80FD\u4F7F\u7528",
|
|
117724
117792
|
"- image_blocks: \u53EF\u9009\u56FE\u7247\u9884\u89C8\u5757\u6570\u7EC4,\u6700\u591A 4 \u4E2A\u3002\u6BCF\u9879 `{img_key, alt?, title?, mode?, preview?}`; `img_key` \u5FC5\u987B\u662F\u5DF2\u4E0A\u4F20/\u53EF\u7528\u4E8E\u5361\u7247\u7684 Feishu \u56FE\u7247 key,`alt` \u7701\u7565\u65F6 bridge \u9ED8\u8BA4\u201C\u56FE\u7247\u9884\u89C8\u201D,`mode` \u53EA\u5141\u8BB8 `crop_center`/`fit_horizontal` \u5E76\u6620\u5C04\u5230 Card JSON 2.0 `scale_type`,`preview` \u9ED8\u8BA4 true\u3002bridge \u4E0D\u8D1F\u8D23\u4E0B\u8F7D/\u4E0A\u4F20/\u9009\u62E9\u56FE\u7247;\u8FD9\u4E9B\u7531\u4F60\u7528 lark-cli \u7B49\u5DE5\u5177\u5148\u5B8C\u6210\u3002",
|
|
117725
117793
|
'- content_blocks: \u53EF\u9009\u6709\u5E8F\u6B63\u6587\u5757\u6570\u7EC4,\u6700\u591A 12 \u4E2A block\u3001\u6700\u591A 4 \u4E2A image block\u3002\u53EA\u652F\u6301\u7A84 union:`{type:"markdown", content}` \u548C `{type:"image", img_key, alt?, title?, mode?, preview?}`;\u4E0D\u652F\u6301 raw card JSON\u3002\u7528\u4E8E\u6B63\u6587\u4E0E\u56FE\u7247\u4EA4\u9519\u6392\u7248,\u4F8B\u5982 markdown -> image -> markdown -> image\u3002\u82E5 `content_blocks` \u975E\u7A7A,bridge \u4EE5\u5B83\u4F5C\u4E3A\u4E3B\u6B63\u6587\u5E76\u5FFD\u7565 `last_message` + `image_blocks` \u7684\u6B63\u6587\u6E32\u67D3,\u907F\u514D\u91CD\u590D;\u82E5\u7701\u7565\u5219\u4FDD\u6301\u65E7 `last_message` + `image_blocks` \u884C\u4E3A\u3002',
|
|
117726
117794
|
'- response_surface: \u53EF\u9009\u8986\u76D6\u5B57\u6BB5,\u5F62\u5982 `{mode:"card"|"post"|"hybrid", primary?:"card"|"post", post?:{mentions:[{user_id,label?}]}, card?:{compact?:boolean, capabilities?:[...]}}`\u3002\u9ED8\u8BA4\u53EF\u4E0D\u5199;\u65E0\u663E\u5F0F card \u610F\u56FE\u65F6 bridge \u6309 CardKit \u6D41\u5F0F\u5361\u7247\u5904\u7406,\u6700\u7EC8\u6536\u6210\u5E72\u51C0\u603B\u7ED3\u5361\u3002\u9700\u8981\u771F\u5B9E @ \u65F6\u628A\u76EE\u6807\u5199\u5165 `post.mentions`;\u4E0D\u8981\u5199 raw Feishu post/card JSON\u3002',
|
|
@@ -119043,6 +119111,20 @@ async function deleteCardFile(worktreePath) {
|
|
|
119043
119111
|
// src/bridge/cardkitFile.ts
|
|
119044
119112
|
import fs5 from "node:fs/promises";
|
|
119045
119113
|
import path8 from "node:path";
|
|
119114
|
+
var defaultCardKitLiveMetrics = () => ({
|
|
119115
|
+
answerDeltaCount: 0,
|
|
119116
|
+
answerSnapshotCount: 0,
|
|
119117
|
+
firstAnswerAt: null,
|
|
119118
|
+
lastAnswerAt: null,
|
|
119119
|
+
visibleAnswerLength: 0,
|
|
119120
|
+
toolUseCount: 0,
|
|
119121
|
+
lastToolUseAt: null,
|
|
119122
|
+
statusPatchCount: 0,
|
|
119123
|
+
lastStatusPatchAt: null,
|
|
119124
|
+
progressUpdateCount: 0,
|
|
119125
|
+
lastProgressPatchAt: null,
|
|
119126
|
+
lastPatchError: null
|
|
119127
|
+
});
|
|
119046
119128
|
var CardKitFileSchema = external_exports.object({
|
|
119047
119129
|
surface: external_exports.literal("cardkit_stream"),
|
|
119048
119130
|
status: external_exports.enum(["planned", "message_sent", "streaming", "finalized", "fallback_visible", "failed"]).default("planned"),
|
|
@@ -119056,6 +119138,20 @@ var CardKitFileSchema = external_exports.object({
|
|
|
119056
119138
|
replyInThread: external_exports.boolean().default(true),
|
|
119057
119139
|
idempotencyKey: external_exports.string().min(1),
|
|
119058
119140
|
sequence: external_exports.number().int().nonnegative().default(0),
|
|
119141
|
+
live: external_exports.object({
|
|
119142
|
+
answerDeltaCount: external_exports.number().int().nonnegative().default(0),
|
|
119143
|
+
answerSnapshotCount: external_exports.number().int().nonnegative().default(0),
|
|
119144
|
+
firstAnswerAt: external_exports.string().min(1).nullable().default(null),
|
|
119145
|
+
lastAnswerAt: external_exports.string().min(1).nullable().default(null),
|
|
119146
|
+
visibleAnswerLength: external_exports.number().int().nonnegative().default(0),
|
|
119147
|
+
toolUseCount: external_exports.number().int().nonnegative().default(0),
|
|
119148
|
+
lastToolUseAt: external_exports.string().min(1).nullable().default(null),
|
|
119149
|
+
statusPatchCount: external_exports.number().int().nonnegative().default(0),
|
|
119150
|
+
lastStatusPatchAt: external_exports.string().min(1).nullable().default(null),
|
|
119151
|
+
progressUpdateCount: external_exports.number().int().nonnegative().default(0),
|
|
119152
|
+
lastProgressPatchAt: external_exports.string().min(1).nullable().default(null),
|
|
119153
|
+
lastPatchError: external_exports.string().min(1).nullable().default(null)
|
|
119154
|
+
}).default(defaultCardKitLiveMetrics),
|
|
119059
119155
|
elements: external_exports.object({
|
|
119060
119156
|
status: external_exports.object({ elementId: external_exports.string().min(1) }).optional(),
|
|
119061
119157
|
thinking: external_exports.object({ elementId: external_exports.string().min(1) }).optional(),
|
|
@@ -119136,6 +119232,9 @@ function markdown(content, elementId) {
|
|
|
119136
119232
|
if (elementId) element["element_id"] = elementId;
|
|
119137
119233
|
return element;
|
|
119138
119234
|
}
|
|
119235
|
+
function buildCardKitAnswerElement(content = "") {
|
|
119236
|
+
return markdown(content, CARDKIT_FINAL_ELEMENT_ID);
|
|
119237
|
+
}
|
|
119139
119238
|
function safeAtMention(target) {
|
|
119140
119239
|
const id = target.user_id.trim();
|
|
119141
119240
|
if (!/^[A-Za-z0-9_:-]+$/.test(id)) return "";
|
|
@@ -119187,10 +119286,7 @@ function buildCardKitInitialCard(opts = {}) {
|
|
|
119187
119286
|
}
|
|
119188
119287
|
},
|
|
119189
119288
|
body: {
|
|
119190
|
-
elements: [
|
|
119191
|
-
markdown(" ", CARDKIT_FINAL_ELEMENT_ID),
|
|
119192
|
-
markdown(footerText, CARDKIT_FOOTER_ELEMENT_ID)
|
|
119193
|
-
]
|
|
119289
|
+
elements: [markdown(footerText, CARDKIT_FOOTER_ELEMENT_ID)]
|
|
119194
119290
|
}
|
|
119195
119291
|
};
|
|
119196
119292
|
}
|
|
@@ -119237,7 +119333,7 @@ function finalMarkdown(opts) {
|
|
|
119237
119333
|
return truncateChars([mentionLine, body].filter(Boolean).join("\n\n") || "\u5B8C\u6210\u3002", FINAL_BUDGET_CHARS, "\n\n_\u5185\u5BB9\u8FC7\u957F,\u5DF2\u622A\u65AD\u3002_");
|
|
119238
119334
|
}
|
|
119239
119335
|
function buildCardKitFinalCard(opts) {
|
|
119240
|
-
const elements = [
|
|
119336
|
+
const elements = [buildCardKitAnswerElement(finalMarkdown(opts))];
|
|
119241
119337
|
const images = [];
|
|
119242
119338
|
if (opts.contentBlocks?.length) {
|
|
119243
119339
|
for (const block of opts.contentBlocks) {
|
|
@@ -119263,10 +119359,6 @@ function buildCardKitFinalCard(opts) {
|
|
|
119263
119359
|
streaming_mode: false,
|
|
119264
119360
|
summary: { content: truncateChars(opts.finalText.replace(/\s+/g, " ").trim(), 50, "...") }
|
|
119265
119361
|
},
|
|
119266
|
-
header: {
|
|
119267
|
-
title: plainText2(opts.title ?? "\u5B8C\u6210"),
|
|
119268
|
-
template: "green"
|
|
119269
|
-
},
|
|
119270
119362
|
body: { elements }
|
|
119271
119363
|
});
|
|
119272
119364
|
}
|
|
@@ -119285,6 +119377,34 @@ function idempotencyKey(facts) {
|
|
|
119285
119377
|
function sequenceUuid(cardId, role, sequence) {
|
|
119286
119378
|
return deriveCardKitUuid([cardId, role, String(sequence)].join("\0"));
|
|
119287
119379
|
}
|
|
119380
|
+
function isMissingCardKitElementError(err) {
|
|
119381
|
+
if (!(err instanceof Error)) return false;
|
|
119382
|
+
const message = err.message.toLowerCase();
|
|
119383
|
+
return message.includes("element") && (message.includes("not found") || message.includes("not exist") || message.includes("\u4E0D\u5B58\u5728"));
|
|
119384
|
+
}
|
|
119385
|
+
function initialLiveMetrics() {
|
|
119386
|
+
return {
|
|
119387
|
+
answerDeltaCount: 0,
|
|
119388
|
+
answerSnapshotCount: 0,
|
|
119389
|
+
firstAnswerAt: null,
|
|
119390
|
+
lastAnswerAt: null,
|
|
119391
|
+
visibleAnswerLength: 0,
|
|
119392
|
+
toolUseCount: 0,
|
|
119393
|
+
lastToolUseAt: null,
|
|
119394
|
+
statusPatchCount: 0,
|
|
119395
|
+
lastStatusPatchAt: null,
|
|
119396
|
+
progressUpdateCount: 0,
|
|
119397
|
+
lastProgressPatchAt: null,
|
|
119398
|
+
lastPatchError: null
|
|
119399
|
+
};
|
|
119400
|
+
}
|
|
119401
|
+
function summarizeError(err) {
|
|
119402
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
119403
|
+
return message.replace(/\s+/g, " ").trim().slice(0, 240) || "unknown error";
|
|
119404
|
+
}
|
|
119405
|
+
function toolStatusText(toolUseCount) {
|
|
119406
|
+
return toolUseCount > 0 ? `\u52AA\u529B\u56DE\u7B54\u4E2D... \xB7 \u5DF2\u7528 ${toolUseCount} \u4E2A\u5DE5\u5177` : "\u52AA\u529B\u56DE\u7B54\u4E2D...";
|
|
119407
|
+
}
|
|
119288
119408
|
var LiveCardKitProgressHandle = class {
|
|
119289
119409
|
cardId;
|
|
119290
119410
|
messageId;
|
|
@@ -119293,11 +119413,14 @@ var LiveCardKitProgressHandle = class {
|
|
|
119293
119413
|
patchIntervalMs;
|
|
119294
119414
|
maxProgressUpdates;
|
|
119295
119415
|
onSequenceCommitted;
|
|
119416
|
+
onLiveMetricsChanged;
|
|
119296
119417
|
answerBuffer = "";
|
|
119297
119418
|
pendingPatch = null;
|
|
119298
119419
|
inFlight = Promise.resolve();
|
|
119299
119420
|
closed = false;
|
|
119300
|
-
|
|
119421
|
+
answerElementCreated = false;
|
|
119422
|
+
immediatePatchStarted = false;
|
|
119423
|
+
metrics = initialLiveMetrics();
|
|
119301
119424
|
sequence = 0;
|
|
119302
119425
|
constructor(opts) {
|
|
119303
119426
|
this.cardKitClient = opts.cardKitClient;
|
|
@@ -119307,20 +119430,31 @@ var LiveCardKitProgressHandle = class {
|
|
|
119307
119430
|
this.patchIntervalMs = opts.patchIntervalMs;
|
|
119308
119431
|
this.maxProgressUpdates = opts.maxProgressUpdates;
|
|
119309
119432
|
this.onSequenceCommitted = opts.onSequenceCommitted;
|
|
119433
|
+
this.onLiveMetricsChanged = opts.onLiveMetricsChanged;
|
|
119310
119434
|
}
|
|
119311
119435
|
get answerText() {
|
|
119312
119436
|
return this.answerBuffer;
|
|
119313
119437
|
}
|
|
119438
|
+
get liveMetrics() {
|
|
119439
|
+
return { ...this.metrics };
|
|
119440
|
+
}
|
|
119314
119441
|
handle(event) {
|
|
119315
119442
|
if (this.closed) return;
|
|
119443
|
+
if (event.type === "tool_use") {
|
|
119444
|
+
this.recordToolUse();
|
|
119445
|
+
this.patchStatus(toolStatusText(this.metrics.toolUseCount));
|
|
119446
|
+
return;
|
|
119447
|
+
}
|
|
119316
119448
|
if (event.type === "answer_delta") {
|
|
119317
119449
|
this.answerBuffer += event.text;
|
|
119318
|
-
this.
|
|
119450
|
+
this.recordAnswerEvent("answer_delta");
|
|
119451
|
+
this.schedulePatch({ immediate: !this.immediatePatchStarted });
|
|
119319
119452
|
return;
|
|
119320
119453
|
}
|
|
119321
119454
|
if (event.type === "answer_snapshot") {
|
|
119322
119455
|
this.answerBuffer = event.text;
|
|
119323
|
-
this.
|
|
119456
|
+
this.recordAnswerEvent("answer_snapshot");
|
|
119457
|
+
this.schedulePatch({ immediate: !this.immediatePatchStarted });
|
|
119324
119458
|
}
|
|
119325
119459
|
}
|
|
119326
119460
|
async drain() {
|
|
@@ -119336,6 +119470,7 @@ var LiveCardKitProgressHandle = class {
|
|
|
119336
119470
|
this.closed = true;
|
|
119337
119471
|
const finalMarkdown2 = buildCardKitFinalMarkdown(opts);
|
|
119338
119472
|
if (finalMarkdown2 !== this.answerBuffer) {
|
|
119473
|
+
await this.withAnswerElement(finalMarkdown2);
|
|
119339
119474
|
await this.next(
|
|
119340
119475
|
(sequence) => this.cardKitClient.streamElementContent(
|
|
119341
119476
|
this.cardId,
|
|
@@ -119378,8 +119513,71 @@ var LiveCardKitProgressHandle = class {
|
|
|
119378
119513
|
this.pendingPatch = null;
|
|
119379
119514
|
}
|
|
119380
119515
|
}
|
|
119381
|
-
|
|
119382
|
-
|
|
119516
|
+
recordAnswerEvent(type2) {
|
|
119517
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
119518
|
+
if (type2 === "answer_delta") {
|
|
119519
|
+
this.metrics.answerDeltaCount += 1;
|
|
119520
|
+
} else {
|
|
119521
|
+
this.metrics.answerSnapshotCount += 1;
|
|
119522
|
+
}
|
|
119523
|
+
this.metrics.firstAnswerAt ??= now;
|
|
119524
|
+
this.metrics.lastAnswerAt = now;
|
|
119525
|
+
this.metrics.visibleAnswerLength = this.answerBuffer.length;
|
|
119526
|
+
this.emitLiveMetrics();
|
|
119527
|
+
console.info(
|
|
119528
|
+
"[cardkit_progress] answer event",
|
|
119529
|
+
`type=${type2}`,
|
|
119530
|
+
`delta_count=${this.metrics.answerDeltaCount}`,
|
|
119531
|
+
`snapshot_count=${this.metrics.answerSnapshotCount}`,
|
|
119532
|
+
`visible_length=${this.metrics.visibleAnswerLength}`,
|
|
119533
|
+
`sequence=${this.sequence}`
|
|
119534
|
+
);
|
|
119535
|
+
}
|
|
119536
|
+
recordToolUse() {
|
|
119537
|
+
this.metrics.toolUseCount += 1;
|
|
119538
|
+
this.metrics.lastToolUseAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
119539
|
+
this.emitLiveMetrics();
|
|
119540
|
+
console.info(
|
|
119541
|
+
"[cardkit_progress] tool event",
|
|
119542
|
+
`tool_use_count=${this.metrics.toolUseCount}`,
|
|
119543
|
+
`sequence=${this.sequence}`
|
|
119544
|
+
);
|
|
119545
|
+
}
|
|
119546
|
+
patchStatus(content) {
|
|
119547
|
+
this.inFlight = this.inFlight.then(
|
|
119548
|
+
() => this.next(
|
|
119549
|
+
(sequence) => this.cardKitClient.updateElement(
|
|
119550
|
+
this.cardId,
|
|
119551
|
+
CARDKIT_FOOTER_ELEMENT_ID,
|
|
119552
|
+
{
|
|
119553
|
+
tag: "markdown",
|
|
119554
|
+
content,
|
|
119555
|
+
element_id: CARDKIT_FOOTER_ELEMENT_ID
|
|
119556
|
+
},
|
|
119557
|
+
{
|
|
119558
|
+
sequence,
|
|
119559
|
+
uuid: sequenceUuid(this.cardId, "status", sequence)
|
|
119560
|
+
}
|
|
119561
|
+
)
|
|
119562
|
+
)
|
|
119563
|
+
).then(() => {
|
|
119564
|
+
this.metrics.statusPatchCount += 1;
|
|
119565
|
+
this.metrics.lastStatusPatchAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
119566
|
+
this.metrics.lastPatchError = null;
|
|
119567
|
+
this.emitLiveMetrics();
|
|
119568
|
+
}).catch((err) => {
|
|
119569
|
+
this.metrics.lastPatchError = summarizeError(err);
|
|
119570
|
+
this.emitLiveMetrics();
|
|
119571
|
+
console.warn("[cardkit_progress] status update failed (continuing):", err);
|
|
119572
|
+
});
|
|
119573
|
+
}
|
|
119574
|
+
schedulePatch(opts = {}) {
|
|
119575
|
+
if (this.pendingPatch || this.metrics.progressUpdateCount >= this.maxProgressUpdates) return;
|
|
119576
|
+
if (opts.immediate) {
|
|
119577
|
+
this.immediatePatchStarted = true;
|
|
119578
|
+
void this.patchProgress();
|
|
119579
|
+
return;
|
|
119580
|
+
}
|
|
119383
119581
|
this.pendingPatch = setTimeout(() => {
|
|
119384
119582
|
this.pendingPatch = null;
|
|
119385
119583
|
void this.patchProgress();
|
|
@@ -119387,26 +119585,60 @@ var LiveCardKitProgressHandle = class {
|
|
|
119387
119585
|
this.pendingPatch.unref?.();
|
|
119388
119586
|
}
|
|
119389
119587
|
async patchProgress() {
|
|
119390
|
-
if (this.closed || this.
|
|
119588
|
+
if (this.closed || this.metrics.progressUpdateCount >= this.maxProgressUpdates) return;
|
|
119391
119589
|
if (!this.answerBuffer) return;
|
|
119392
|
-
this.progressUpdates += 1;
|
|
119393
119590
|
this.inFlight = this.inFlight.then(
|
|
119394
|
-
() => this.
|
|
119395
|
-
(
|
|
119396
|
-
sequence,
|
|
119397
|
-
|
|
119398
|
-
|
|
119591
|
+
() => this.withAnswerElement(this.answerBuffer).then(
|
|
119592
|
+
() => this.next(
|
|
119593
|
+
(sequence) => this.cardKitClient.streamElementContent(this.cardId, CARDKIT_FINAL_ELEMENT_ID, this.answerBuffer, {
|
|
119594
|
+
sequence,
|
|
119595
|
+
uuid: sequenceUuid(this.cardId, "answer", sequence)
|
|
119596
|
+
})
|
|
119597
|
+
)
|
|
119399
119598
|
)
|
|
119400
|
-
).
|
|
119599
|
+
).then(() => {
|
|
119600
|
+
this.metrics.progressUpdateCount += 1;
|
|
119601
|
+
this.metrics.visibleAnswerLength = this.answerBuffer.length;
|
|
119602
|
+
this.metrics.lastProgressPatchAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
119603
|
+
this.metrics.lastPatchError = null;
|
|
119604
|
+
this.emitLiveMetrics();
|
|
119605
|
+
console.info(
|
|
119606
|
+
"[cardkit_progress] progress committed",
|
|
119607
|
+
`progress_update_count=${this.metrics.progressUpdateCount}`,
|
|
119608
|
+
`visible_length=${this.metrics.visibleAnswerLength}`,
|
|
119609
|
+
`sequence=${this.sequence}`
|
|
119610
|
+
);
|
|
119611
|
+
}).catch((err) => {
|
|
119612
|
+
this.metrics.lastPatchError = summarizeError(err);
|
|
119613
|
+
this.emitLiveMetrics();
|
|
119401
119614
|
console.warn("[cardkit_progress] progress update failed (continuing):", err);
|
|
119402
119615
|
});
|
|
119403
119616
|
await this.inFlight;
|
|
119404
119617
|
}
|
|
119618
|
+
async withAnswerElement(initialContent) {
|
|
119619
|
+
if (this.answerElementCreated) return;
|
|
119620
|
+
await this.next(
|
|
119621
|
+
(sequence) => this.cardKitClient.createElements(
|
|
119622
|
+
this.cardId,
|
|
119623
|
+
[buildCardKitAnswerElement(initialContent)],
|
|
119624
|
+
{
|
|
119625
|
+
sequence,
|
|
119626
|
+
uuid: sequenceUuid(this.cardId, "answer-element", sequence),
|
|
119627
|
+
type: "insert_before",
|
|
119628
|
+
targetElementId: CARDKIT_FOOTER_ELEMENT_ID
|
|
119629
|
+
}
|
|
119630
|
+
)
|
|
119631
|
+
);
|
|
119632
|
+
this.answerElementCreated = true;
|
|
119633
|
+
}
|
|
119405
119634
|
async next(fn) {
|
|
119406
119635
|
this.sequence += 1;
|
|
119407
119636
|
await fn(this.sequence);
|
|
119408
119637
|
await this.onSequenceCommitted?.(this.sequence);
|
|
119409
119638
|
}
|
|
119639
|
+
emitLiveMetrics() {
|
|
119640
|
+
this.onLiveMetricsChanged?.({ ...this.metrics, sequence: this.sequence });
|
|
119641
|
+
}
|
|
119410
119642
|
};
|
|
119411
119643
|
async function createCardKitProgressHandle(opts) {
|
|
119412
119644
|
const key = idempotencyKey(opts.facts);
|
|
@@ -119440,7 +119672,8 @@ async function createCardKitProgressHandle(opts) {
|
|
|
119440
119672
|
idempotencyKey: key,
|
|
119441
119673
|
patchIntervalMs: opts.patchIntervalMs ?? DEFAULT_PATCH_INTERVAL_MS,
|
|
119442
119674
|
maxProgressUpdates: opts.maxProgressUpdates ?? DEFAULT_MAX_PROGRESS_UPDATES,
|
|
119443
|
-
onSequenceCommitted: opts.onSequenceCommitted
|
|
119675
|
+
onSequenceCommitted: opts.onSequenceCommitted,
|
|
119676
|
+
onLiveMetricsChanged: opts.onLiveMetricsChanged
|
|
119444
119677
|
});
|
|
119445
119678
|
}
|
|
119446
119679
|
async function finalizeExistingCardKitCard(opts) {
|
|
@@ -119451,7 +119684,7 @@ async function finalizeExistingCardKitCard(opts) {
|
|
|
119451
119684
|
await opts.onSequenceCommitted?.(sequence);
|
|
119452
119685
|
};
|
|
119453
119686
|
const finalMarkdown2 = buildCardKitFinalMarkdown(opts.final);
|
|
119454
|
-
|
|
119687
|
+
const streamFinalContent = () => next(
|
|
119455
119688
|
"reconcile-final-content",
|
|
119456
119689
|
(seq2, uuid) => opts.cardKitClient.streamElementContent(
|
|
119457
119690
|
opts.cardId,
|
|
@@ -119460,6 +119693,25 @@ async function finalizeExistingCardKitCard(opts) {
|
|
|
119460
119693
|
{ sequence: seq2, uuid }
|
|
119461
119694
|
)
|
|
119462
119695
|
);
|
|
119696
|
+
try {
|
|
119697
|
+
await streamFinalContent();
|
|
119698
|
+
} catch (err) {
|
|
119699
|
+
if (!isMissingCardKitElementError(err)) throw err;
|
|
119700
|
+
await next(
|
|
119701
|
+
"reconcile-final-element",
|
|
119702
|
+
(seq2, uuid) => opts.cardKitClient.createElements(
|
|
119703
|
+
opts.cardId,
|
|
119704
|
+
[buildCardKitAnswerElement(finalMarkdown2)],
|
|
119705
|
+
{
|
|
119706
|
+
sequence: seq2,
|
|
119707
|
+
uuid,
|
|
119708
|
+
type: "insert_before",
|
|
119709
|
+
targetElementId: CARDKIT_FOOTER_ELEMENT_ID
|
|
119710
|
+
}
|
|
119711
|
+
)
|
|
119712
|
+
);
|
|
119713
|
+
await streamFinalContent();
|
|
119714
|
+
}
|
|
119463
119715
|
await next(
|
|
119464
119716
|
"reconcile-final-card",
|
|
119465
119717
|
(seq2, uuid) => opts.cardKitClient.updateCardEntity(
|
|
@@ -120495,6 +120747,7 @@ var ResponseSurfacePostBudget = class {
|
|
|
120495
120747
|
};
|
|
120496
120748
|
|
|
120497
120749
|
// src/bridge/handler.ts
|
|
120750
|
+
var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
120498
120751
|
function execGit(cwd, args) {
|
|
120499
120752
|
return new Promise((resolve2, reject) => {
|
|
120500
120753
|
const child = child_process.spawn("git", args, {
|
|
@@ -121049,6 +121302,7 @@ var BridgeHandler = class {
|
|
|
121049
121302
|
} catch (err) {
|
|
121050
121303
|
console.warn("[bridge.handler] ensureStateFile failed (continuing):", err);
|
|
121051
121304
|
}
|
|
121305
|
+
let cardKitRecordWrite = Promise.resolve();
|
|
121052
121306
|
const updateCardKitRecord = async (patch) => {
|
|
121053
121307
|
if (!cardKitRecord) return;
|
|
121054
121308
|
cardKitRecord = {
|
|
@@ -121056,7 +121310,16 @@ var BridgeHandler = class {
|
|
|
121056
121310
|
...patch,
|
|
121057
121311
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
121058
121312
|
};
|
|
121059
|
-
|
|
121313
|
+
const record = cardKitRecord;
|
|
121314
|
+
cardKitRecordWrite = cardKitRecordWrite.catch(() => {
|
|
121315
|
+
}).then(() => writeCardKitFile(worktreePath, record));
|
|
121316
|
+
await cardKitRecordWrite;
|
|
121317
|
+
};
|
|
121318
|
+
const updateCardKitLiveMetrics = (metrics) => {
|
|
121319
|
+
const { sequence, ...live } = metrics;
|
|
121320
|
+
void updateCardKitRecord({ sequence, live }).catch((err) => {
|
|
121321
|
+
console.warn("[bridge.handler] write CardKit live metrics failed:", err);
|
|
121322
|
+
});
|
|
121060
121323
|
};
|
|
121061
121324
|
if (!card && cardKitAvailable && this.deps.cardKitClient) {
|
|
121062
121325
|
try {
|
|
@@ -121072,7 +121335,8 @@ var BridgeHandler = class {
|
|
|
121072
121335
|
initialStatusText: "\u52AA\u529B\u56DE\u7B54\u4E2D...",
|
|
121073
121336
|
onSequenceCommitted: async (sequence) => {
|
|
121074
121337
|
await updateCardKitRecord({ status: "streaming", sequence });
|
|
121075
|
-
}
|
|
121338
|
+
},
|
|
121339
|
+
onLiveMetricsChanged: updateCardKitLiveMetrics
|
|
121076
121340
|
});
|
|
121077
121341
|
cardKitRecord = {
|
|
121078
121342
|
surface: "cardkit_stream",
|
|
@@ -121087,6 +121351,7 @@ var BridgeHandler = class {
|
|
|
121087
121351
|
replyInThread,
|
|
121088
121352
|
idempotencyKey: cardKitProgress.idempotencyKey,
|
|
121089
121353
|
sequence: cardKitProgress.sequence,
|
|
121354
|
+
live: cardKitProgress.liveMetrics,
|
|
121090
121355
|
elements: {
|
|
121091
121356
|
footer: { elementId: "footer_md" },
|
|
121092
121357
|
final: { elementId: "final_md" }
|
|
@@ -121254,7 +121519,12 @@ var BridgeHandler = class {
|
|
|
121254
121519
|
});
|
|
121255
121520
|
const backend = this.deps.botConfig?.backend ?? "claude";
|
|
121256
121521
|
const permissionMode = this.deps.permissionMode ?? "bypassPermissions";
|
|
121257
|
-
const
|
|
121522
|
+
const baseTimeoutMs = this.deps.subprocessTimeoutMs ?? 60 * 60 * 1e3;
|
|
121523
|
+
const timeoutMs = cardKitProgress ? Math.min(
|
|
121524
|
+
baseTimeoutMs,
|
|
121525
|
+
this.deps.responseSurfaceTimeoutMs ?? DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS
|
|
121526
|
+
) : baseTimeoutMs;
|
|
121527
|
+
const runnerStartedAt = Date.now();
|
|
121258
121528
|
const handle = createRunner(backend).run({
|
|
121259
121529
|
prompt,
|
|
121260
121530
|
resumeSessionId: currentExisting?.sessionId,
|
|
@@ -121282,6 +121552,7 @@ var BridgeHandler = class {
|
|
|
121282
121552
|
}
|
|
121283
121553
|
}
|
|
121284
121554
|
const result = await handle.done;
|
|
121555
|
+
const cardKitTurnTimedOut = cardKitProgress != null && result.exitCode !== 0 && Date.now() - runnerStartedAt >= timeoutMs;
|
|
121285
121556
|
const rawReportedState = await readStateFile(worktreePath);
|
|
121286
121557
|
const reportedState = rawReportedState?.updated_at != null && rawReportedState.updated_at !== preRunUpdatedAt ? rawReportedState : null;
|
|
121287
121558
|
const now = Date.now();
|
|
@@ -121311,6 +121582,7 @@ var BridgeHandler = class {
|
|
|
121311
121582
|
}
|
|
121312
121583
|
const reportedStatus = reportedState?.status;
|
|
121313
121584
|
const reportedError = reportedState?.error;
|
|
121585
|
+
const cardKitTimeoutFailure = cardKitTurnTimedOut && reportedStatus !== "ready" && reportedStatus !== "failed";
|
|
121314
121586
|
let success;
|
|
121315
121587
|
let failureReason;
|
|
121316
121588
|
if (reportedStatus === "failed") {
|
|
@@ -121318,6 +121590,9 @@ var BridgeHandler = class {
|
|
|
121318
121590
|
failureReason = reportedError ?? "bot \u62A5\u544A failed (\u65E0 error \u5B57\u6BB5)";
|
|
121319
121591
|
} else if (reportedStatus === "ready") {
|
|
121320
121592
|
success = true;
|
|
121593
|
+
} else if (cardKitTimeoutFailure) {
|
|
121594
|
+
success = false;
|
|
121595
|
+
failureReason = `agent turn timed out after ${timeoutMs}ms; CardKit running card was finalized as interrupted`;
|
|
121321
121596
|
} else if (result.exitCode === 0) {
|
|
121322
121597
|
success = true;
|
|
121323
121598
|
} else {
|
|
@@ -121325,14 +121600,14 @@ var BridgeHandler = class {
|
|
|
121325
121600
|
failureReason = `claude exited ${result.exitCode} \u4E14 bot \u672A\u66F4\u65B0 state.json status \u2014 \u53EF\u80FD\u5D29\u6E83`;
|
|
121326
121601
|
}
|
|
121327
121602
|
const fallbackAnswer = trustedAnswerText.trim() || cardKitProgress?.answerText.trim() || "";
|
|
121328
|
-
const cardBody = reportedState?.last_message ?? (fallbackAnswer ? fallbackAnswer : "\u26A0\uFE0F \u672C\u8F6E\u6CA1\u6709\u62FF\u5230 agent \u7684\u65B0\u56DE\u590D(\u53EF\u80FD\u88AB\u4E2D\u65AD\u6216\u672A\u66F4\u65B0\u72B6\u6001),\u518D @ \u6211\u4E00\u6B21\u91CD\u8BD5\u3002");
|
|
121603
|
+
const cardBody = cardKitTimeoutFailure ? "\u26A0\uFE0F \u672C\u8F6E\u5904\u7406\u8D85\u65F6\uFF0C\u5DF2\u4E2D\u65AD\u3002\u8BF7\u518D @ \u6211\u4E00\u6B21\u91CD\u8BD5\u3002" : reportedState?.last_message ?? (fallbackAnswer ? fallbackAnswer : "\u26A0\uFE0F \u672C\u8F6E\u6CA1\u6709\u62FF\u5230 agent \u7684\u65B0\u56DE\u590D(\u53EF\u80FD\u88AB\u4E2D\u65AD\u6216\u672A\u66F4\u65B0\u72B6\u6001),\u518D @ \u6211\u4E00\u6B21\u91CD\u8BD5\u3002");
|
|
121329
121604
|
const noReportThisTurn = reportedState === null;
|
|
121330
121605
|
const neutralTitle = noReportThisTurn && success && !failureReason ? "\u{1F4AC} \u5DF2\u56DE\u590D" : void 0;
|
|
121331
121606
|
const baseCardPayload = {
|
|
121332
121607
|
finalText: cardBody,
|
|
121333
121608
|
success,
|
|
121334
121609
|
failureReason,
|
|
121335
|
-
titleOverride: reportedState?.card_title ?? neutralTitle,
|
|
121610
|
+
titleOverride: reportedState?.card_title ?? (cardKitTimeoutFailure ? "\u5DF2\u4E2D\u65AD" : neutralTitle),
|
|
121336
121611
|
colorOverride: reportedState?.card_color ?? (neutralTitle ? "neutral" : void 0),
|
|
121337
121612
|
// V2 dynamic-choice buttons — agent-declared, rendered verbatim.
|
|
121338
121613
|
// reportedState is null when state.json wasn't freshly written
|
|
@@ -125439,7 +125714,7 @@ function buildCommand(opts) {
|
|
|
125439
125714
|
args.push("-p", opts.prompt);
|
|
125440
125715
|
return [bin, args];
|
|
125441
125716
|
}
|
|
125442
|
-
function* parseLinesMulti(line) {
|
|
125717
|
+
function* parseLinesMulti(line, answerExtractor = new AnswerChannelExtractor()) {
|
|
125443
125718
|
const trimmed = line.trim();
|
|
125444
125719
|
if (trimmed === "") return;
|
|
125445
125720
|
let obj;
|
|
@@ -125473,7 +125748,7 @@ function* parseLinesMulti(line) {
|
|
|
125473
125748
|
if (typeof item !== "object" || item === null) continue;
|
|
125474
125749
|
const block = item;
|
|
125475
125750
|
if (block["type"] === "text" && typeof block["text"] === "string") {
|
|
125476
|
-
yield*
|
|
125751
|
+
yield* answerExtractor.ingestSnapshot(block["text"], obj);
|
|
125477
125752
|
emitted = true;
|
|
125478
125753
|
} else if (block["type"] === "tool_use") {
|
|
125479
125754
|
yield {
|
|
@@ -125491,6 +125766,22 @@ function* parseLinesMulti(line) {
|
|
|
125491
125766
|
yield { type: "raw", raw: obj };
|
|
125492
125767
|
return;
|
|
125493
125768
|
}
|
|
125769
|
+
if (eventType === "stream_event") {
|
|
125770
|
+
const event = record["event"];
|
|
125771
|
+
if (typeof event === "object" && event !== null) {
|
|
125772
|
+
const streamEvent = event;
|
|
125773
|
+
const delta = streamEvent["delta"];
|
|
125774
|
+
if (typeof delta === "object" && delta !== null) {
|
|
125775
|
+
const deltaRecord = delta;
|
|
125776
|
+
if (streamEvent["type"] === "content_block_delta" && deltaRecord["type"] === "text_delta" && typeof deltaRecord["text"] === "string") {
|
|
125777
|
+
yield* answerExtractor.ingestDelta(deltaRecord["text"], obj);
|
|
125778
|
+
return;
|
|
125779
|
+
}
|
|
125780
|
+
}
|
|
125781
|
+
}
|
|
125782
|
+
yield { type: "raw", raw: obj };
|
|
125783
|
+
return;
|
|
125784
|
+
}
|
|
125494
125785
|
if (eventType === "user") {
|
|
125495
125786
|
const message = record["message"];
|
|
125496
125787
|
if (typeof message === "object" && message !== null && Array.isArray(message["content"])) {
|
|
@@ -125664,9 +125955,10 @@ stderr: ${stderr}` : "")
|
|
|
125664
125955
|
// unblock the events loop so handler.ts can reach card.finalize().
|
|
125665
125956
|
signal: rlAbortController.signal
|
|
125666
125957
|
});
|
|
125958
|
+
const answerExtractor = new AnswerChannelExtractor();
|
|
125667
125959
|
try {
|
|
125668
125960
|
for await (const line of rl) {
|
|
125669
|
-
for (const event of parseLinesMulti(line)) {
|
|
125961
|
+
for (const event of parseLinesMulti(line, answerExtractor)) {
|
|
125670
125962
|
if (event.type === "system_init") {
|
|
125671
125963
|
discoveredSessionId = event.sessionId;
|
|
125672
125964
|
}
|