larkway 0.3.22 → 0.3.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  You @ the bot in a Feishu thread. It runs on your machine — reading your real codebase, executing commands, opening MRs — and posts the result back. You define what the agent knows and what it can do. Larkway just carries the messages.
10
10
 
11
- **Current release: v0.3.22**
11
+ **Current release: v0.3.24**
12
12
 
13
13
  ---
14
14
 
package/README.zh.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  你在飞书话题里 @ bot,它在你的机器上运行——读真实代码库、执行命令、开 MR——把结果贴回飞书。你定义 agent 知道什么、能做什么。Larkway 只负责传递消息。
10
10
 
11
- **当前版本:v0.3.22**
11
+ **当前版本:v0.3.24**
12
12
 
13
13
  ---
14
14
 
package/dist/cli/index.js CHANGED
@@ -111185,6 +111185,7 @@ var init_channelCardClient = __esm({
111185
111185
  // src/lark/channelCardKitClient.ts
111186
111186
  import { createHash } from "node:crypto";
111187
111187
  function isRetryable2(err2) {
111188
+ if (err2 instanceof CardKitReplyConversionError) return true;
111188
111189
  if (!(err2 instanceof Error)) return false;
111189
111190
  const msg = err2.message.toLowerCase();
111190
111191
  if (msg.includes("200810")) return true;
@@ -111227,10 +111228,21 @@ async function withCardKitRetry(label, fn, opts) {
111227
111228
  }
111228
111229
  throw lastErr;
111229
111230
  }
111230
- var ChannelCardKitClient;
111231
+ var CardKitReplyConversionError, ChannelCardKitClient;
111231
111232
  var init_channelCardKitClient = __esm({
111232
111233
  "src/lark/channelCardKitClient.ts"() {
111233
111234
  "use strict";
111235
+ CardKitReplyConversionError = class extends Error {
111236
+ messageId;
111237
+ constructor(messageId, cause) {
111238
+ super(
111239
+ `[channel.cardkit] card.idConvert returned no card_id (messageId=${messageId})`
111240
+ );
111241
+ this.name = "CardKitReplyConversionError";
111242
+ this.messageId = messageId;
111243
+ this.cause = cause;
111244
+ }
111245
+ };
111234
111246
  ChannelCardKitClient = class {
111235
111247
  resolveChannel;
111236
111248
  cardThreads;
@@ -111283,17 +111295,19 @@ var init_channelCardKitClient = __esm({
111283
111295
  }
111284
111296
  const converted = await withCardKitRetry(
111285
111297
  "idConvert",
111286
- () => this.channel().rawClient.cardkit.v1.card.idConvert({
111287
- data: { message_id: messageId }
111288
- }),
111298
+ async () => {
111299
+ const converted2 = await this.channel().rawClient.cardkit.v1.card.idConvert({
111300
+ data: { message_id: messageId }
111301
+ });
111302
+ if (!converted2.data?.card_id) {
111303
+ throw new CardKitReplyConversionError(messageId);
111304
+ }
111305
+ return converted2;
111306
+ },
111289
111307
  { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111290
111308
  );
111291
111309
  const cardId = converted.data?.card_id;
111292
- if (!cardId) {
111293
- throw new Error(
111294
- `[channel.cardkit] card.idConvert returned no card_id (messageId=${messageId})`
111295
- );
111296
- }
111310
+ if (!cardId) throw new CardKitReplyConversionError(messageId);
111297
111311
  this.cardThreads.set(messageId, opts.threadId ?? replyToMessageId);
111298
111312
  return { cardId, messageId };
111299
111313
  }
package/dist/main.js CHANGED
@@ -115422,7 +115422,22 @@ var ChannelCardClient = class {
115422
115422
 
115423
115423
  // src/lark/channelCardKitClient.ts
115424
115424
  import { createHash } from "node:crypto";
115425
+ var CardKitReplyConversionError = class extends Error {
115426
+ messageId;
115427
+ constructor(messageId, cause) {
115428
+ super(
115429
+ `[channel.cardkit] card.idConvert returned no card_id (messageId=${messageId})`
115430
+ );
115431
+ this.name = "CardKitReplyConversionError";
115432
+ this.messageId = messageId;
115433
+ this.cause = cause;
115434
+ }
115435
+ };
115436
+ function cardKitReplyConversionMessageId(err) {
115437
+ return err instanceof CardKitReplyConversionError ? err.messageId : void 0;
115438
+ }
115425
115439
  function isRetryable2(err) {
115440
+ if (err instanceof CardKitReplyConversionError) return true;
115426
115441
  if (!(err instanceof Error)) return false;
115427
115442
  const msg = err.message.toLowerCase();
115428
115443
  if (msg.includes("200810")) return true;
@@ -115520,17 +115535,19 @@ var ChannelCardKitClient = class {
115520
115535
  }
115521
115536
  const converted = await withCardKitRetry(
115522
115537
  "idConvert",
115523
- () => this.channel().rawClient.cardkit.v1.card.idConvert({
115524
- data: { message_id: messageId }
115525
- }),
115538
+ async () => {
115539
+ const converted2 = await this.channel().rawClient.cardkit.v1.card.idConvert({
115540
+ data: { message_id: messageId }
115541
+ });
115542
+ if (!converted2.data?.card_id) {
115543
+ throw new CardKitReplyConversionError(messageId);
115544
+ }
115545
+ return converted2;
115546
+ },
115526
115547
  { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
115527
115548
  );
115528
115549
  const cardId = converted.data?.card_id;
115529
- if (!cardId) {
115530
- throw new Error(
115531
- `[channel.cardkit] card.idConvert returned no card_id (messageId=${messageId})`
115532
- );
115533
- }
115550
+ if (!cardId) throw new CardKitReplyConversionError(messageId);
115534
115551
  this.cardThreads.set(messageId, opts.threadId ?? replyToMessageId);
115535
115552
  return { cardId, messageId };
115536
115553
  }
@@ -116732,20 +116749,6 @@ var ChannelClient = class {
116732
116749
  };
116733
116750
 
116734
116751
  // src/lark/card.ts
116735
- function summarizeInput(input) {
116736
- if (input === null || input === void 0) return "";
116737
- let s;
116738
- if (typeof input === "string") {
116739
- s = input;
116740
- } else if (typeof input === "object") {
116741
- const obj = input;
116742
- const snippet2 = obj["command"] ?? obj["path"] ?? obj["file_path"] ?? obj["description"] ?? null;
116743
- s = snippet2 != null ? String(snippet2) : JSON.stringify(input);
116744
- } else {
116745
- s = String(input);
116746
- }
116747
- return s.length > 60 ? s.slice(0, 57) + "\u2026" : s;
116748
- }
116749
116752
  var MAX_CARD_ELEMENTS = 50;
116750
116753
  var CHOICE_MARKERS = ["A", "B", "C", "D", "E"];
116751
116754
  function choiceMarker(i) {
@@ -116896,7 +116899,7 @@ ${recent.join("\n")}` : recent.join("\n");
116896
116899
  } else if (opts.status === "thinking") {
116897
116900
  pushElement(elements, {
116898
116901
  tag: "markdown",
116899
- content: "\u{1F914} \u601D\u8003\u4E2D\u2026"
116902
+ content: "\u52AA\u529B\u56DE\u7B54\u4E2D..."
116900
116903
  });
116901
116904
  }
116902
116905
  if (opts.status === "failure" && opts.failureReason) {
@@ -116923,7 +116926,7 @@ ${recent.join("\n")}` : recent.join("\n");
116923
116926
  pushElement(elements, buildChoiceRow(opts.choices));
116924
116927
  }
116925
116928
  const headerColor = opts.status === "thinking" || opts.status === "streaming" ? "blue" : opts.status === "success" ? "green" : "red";
116926
- const statusText = opts.status === "thinking" ? "\u23F3 \u5904\u7406\u4E2D" : opts.status === "streaming" ? "\u{1F527} \u5904\u7406\u4E2D" : opts.status === "success" ? "\u2705 \u5B8C\u6210" : "\u274C \u51FA\u9519\u4E86";
116929
+ const statusText = opts.status === "thinking" ? "\u5904\u7406\u4E2D" : opts.status === "streaming" ? "\u56DE\u7B54\u4E2D" : opts.status === "success" ? "\u2705 \u5B8C\u6210" : "\u274C \u51FA\u9519\u4E86";
116927
116930
  const headerTitle = opts.titleOverride ?? statusText;
116928
116931
  const card = {
116929
116932
  schema: "2.0",
@@ -116948,7 +116951,6 @@ var CardHandleImpl = class {
116948
116951
  showToolSummary;
116949
116952
  state = {
116950
116953
  textBuffer: "",
116951
- currentRawMsgId: null,
116952
116954
  toolStatusLines: [],
116953
116955
  lastPatchAt: 0,
116954
116956
  pendingPatch: null
@@ -116960,7 +116962,7 @@ var CardHandleImpl = class {
116960
116962
  * before its own PATCH so the finalize is guaranteed to land LAST. Tracking
116961
116963
  * only "the latest" is insufficient: an older retry can still be in flight
116962
116964
  * after a newer live patch overwrote the pointer, then land after finalize and
116963
- * flip the Feishu card back to 🔧 处理中.
116965
+ * flip the Feishu card back to the in-progress render.
116964
116966
  */
116965
116967
  livePatchesInFlight = /* @__PURE__ */ new Set();
116966
116968
  constructor(opts) {
@@ -116972,8 +116974,9 @@ var CardHandleImpl = class {
116972
116974
  // ── Public: handle ────────────────────────────────────────────────────────
116973
116975
  handle(event) {
116974
116976
  if (this.finalized) return;
116975
- this.accumulate(event);
116976
- this.scheduleThrottledPatch();
116977
+ if (this.accumulate(event)) {
116978
+ this.scheduleThrottledPatch();
116979
+ }
116977
116980
  }
116978
116981
  // ── Public: finalize ─────────────────────────────────────────────────────
116979
116982
  async finalize(opts) {
@@ -117014,37 +117017,14 @@ var CardHandleImpl = class {
117014
117017
  }
117015
117018
  // ── Private: accumulate ───────────────────────────────────────────────────
117016
117019
  accumulate(event) {
117017
- if (event.type === "text_delta") {
117018
- const rawMsgId = extractRawMessageId(event.raw);
117019
- if (rawMsgId !== null && rawMsgId !== this.state.currentRawMsgId) {
117020
- if (this.state.textBuffer.length > 0) {
117021
- this.state.textBuffer += "\n\n";
117022
- }
117023
- this.state.currentRawMsgId = rawMsgId;
117024
- this.state.textBuffer += event.text;
117025
- } else {
117026
- if (this.state.currentRawMsgId === null) {
117027
- this.state.currentRawMsgId = rawMsgId;
117028
- }
117029
- const prevTurnEnd = this.findPrevTurnEnd();
117030
- this.state.textBuffer = this.state.textBuffer.slice(0, prevTurnEnd) + event.text;
117031
- }
117032
- } else if (event.type === "tool_use" && this.showToolSummary) {
117033
- const summary = summarizeInput(event.toolInput);
117034
- const line = summary ? `\u{1F527} ${event.toolName} ${summary}` : `\u{1F527} ${event.toolName}`;
117035
- this.state.toolStatusLines.push(line);
117020
+ if (event.type === "answer_delta") {
117021
+ this.state.textBuffer += event.text;
117022
+ return true;
117023
+ } else if (event.type === "answer_snapshot") {
117024
+ this.state.textBuffer = event.text;
117025
+ return true;
117036
117026
  }
117037
- }
117038
- /**
117039
- * Returns the character offset in textBuffer where the CURRENT turn's text
117040
- * starts. If there is only one turn, returns 0.
117041
- */
117042
- findPrevTurnEnd() {
117043
- if (!this.state.textBuffer) return 0;
117044
- const sep = "\n\n";
117045
- const lastSep = this.state.textBuffer.lastIndexOf(sep);
117046
- if (lastSep === -1) return 0;
117047
- return lastSep + sep.length;
117027
+ return false;
117048
117028
  }
117049
117029
  // ── Private: throttle ────────────────────────────────────────────────────
117050
117030
  scheduleThrottledPatch() {
@@ -117117,14 +117097,6 @@ var CardHandleImpl = class {
117117
117097
  await this.livePatchWithRetry(cardJsonStr);
117118
117098
  }
117119
117099
  };
117120
- function extractRawMessageId(raw) {
117121
- if (typeof raw !== "object" || raw === null) return null;
117122
- const rec = raw;
117123
- const msg = rec["message"];
117124
- if (typeof msg !== "object" || msg === null) return null;
117125
- const id = msg["id"];
117126
- return typeof id === "string" ? id : null;
117127
- }
117128
117100
  async function createCardWithRetry(outbound, replyToMessageId, cardJson, opts, maxAttempts = 3) {
117129
117101
  let lastErr;
117130
117102
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
@@ -117672,6 +117644,108 @@ function deriveTriggerFacts(parsed, isNewThread, larkCliProfile) {
117672
117644
  };
117673
117645
  }
117674
117646
 
117647
+ // src/agent/answerChannel.ts
117648
+ var ANSWER_BEGIN_MARKER = "LARKWAY_ANSWER_BEGIN";
117649
+ var ANSWER_END_MARKER = "LARKWAY_ANSWER_END";
117650
+ var STREAM_HOLD_CHARS = ANSWER_END_MARKER.length + 2;
117651
+ function stripLeadingNewline(text) {
117652
+ return text.replace(/^\r?\n/, "");
117653
+ }
117654
+ function stripTrailingNewline(text) {
117655
+ return text.replace(/\r?\n$/, "");
117656
+ }
117657
+ function markerLineIndex(text, marker) {
117658
+ const re = new RegExp(`(^|\\r?\\n)${marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(\\r?\\n|$)`);
117659
+ const match = re.exec(text);
117660
+ if (!match || match.index == null) return null;
117661
+ const lineStart = match.index + (match[1]?.length ?? 0);
117662
+ const lineEnd = lineStart + marker.length;
117663
+ const after = lineEnd + (match[2]?.length ?? 0);
117664
+ return { start: lineStart, end: after };
117665
+ }
117666
+ function hasUsefulText(text) {
117667
+ return text.length > 0;
117668
+ }
117669
+ function splitAnswerChannelText(text, raw) {
117670
+ const begin = markerLineIndex(text, ANSWER_BEGIN_MARKER);
117671
+ if (!begin) return [{ type: "internal_text", text, raw }];
117672
+ const before = stripTrailingNewline(text.slice(0, begin.start));
117673
+ const afterBegin = text.slice(begin.end);
117674
+ const end = markerLineIndex(afterBegin, ANSWER_END_MARKER);
117675
+ const answer = stripLeadingNewline(end ? afterBegin.slice(0, end.start) : afterBegin);
117676
+ const trailing = end ? stripLeadingNewline(afterBegin.slice(end.end)) : "";
117677
+ const events = [];
117678
+ if (before.trim()) events.push({ type: "internal_text", text: before, raw });
117679
+ events.push({ type: "answer_snapshot", text: stripTrailingNewline(answer), raw });
117680
+ if (trailing.trim()) events.push({ type: "internal_text", text: trailing, raw });
117681
+ return events;
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
+ };
117748
+
117675
117749
  // src/claude/prompt.ts
117676
117750
  var MEMORY_CATEGORY_FILE_NAMES = [
117677
117751
  "preferences.md",
@@ -117698,19 +117772,23 @@ function renderStateContract(stateFilePath) {
117698
117772
  return [
117699
117773
  "<state-contract>",
117700
117774
  "\u4F60\u548C\u8FD0\u8425\u4E4B\u95F4\u7684\u754C\u9762\u662F\u98DE\u4E66\u8BDD\u9898\u91CC\u7684 response surface,\u8FD9\u662F\u4E00\u4E2A thin-channel \u5916\u58F3:",
117701
- "- \u9ED8\u8BA4\u4E3B\u56DE\u590D\u9762\u662F post/RichText:bridge \u8D77\u624B\u53D1\u4E00\u6761\u8F7B\u91CF\u201C\u6B63\u5728\u5904\u7406\u2026\u201Dpost,\u6267\u884C\u4E2D\u7528\u5206\u5757\u7EA7\u539F\u5730\u7F16\u8F91\u6A21\u62DF\u6D41\u5F0F\u8F93\u51FA,\u7ED3\u675F\u65F6\u7F16\u8F91\u6210\u5E72\u51C0\u7EC8\u7A3F\u3002",
117702
- "- \u5361\u7247\u662F\u4F8B\u5916\u8865\u5145\u9762,\u53EA\u5728\u9700\u8981\u79BB\u6563\u9009\u62E9/\u6309\u94AE(`choices`)\u6216 post \u8868\u8FBE\u4E0D\u4E86\u7684\u7ED3\u6784\u5316\u5185\u5BB9(`content_blocks`/`image_blocks`,\u4F8B\u5982\u56FE\u7247\u7EC4\u3001\u9A8C\u6536\u6E05\u5355\u3001dev \u9884\u89C8)\u65F6\u624D\u8865\u4E00\u5F20\u3002",
117703
- "- bridge \u8D1F\u8D23\u521B\u5EFA/\u7F16\u8F91 post\u3001\u5FC5\u8981\u65F6\u521B\u5EFA/\u66F4\u65B0\u98DE\u4E66\u5361\u7247\u3001\u8282\u6D41\u3001\u6E32\u67D3\u6B63\u6587\u3001\u628A choices \u8F6C\u6210\u6309\u94AE\u5E76\u628A\u70B9\u51FB\u503C\u56DE\u4F20\u7ED9\u4F60\u3002",
117775
+ "- \u9ED8\u8BA4\u4E3B\u56DE\u590D\u9762\u662F\u4E00\u5F20 CardKit \u6D41\u5F0F\u5361\u7247:bridge \u8D77\u624B\u53EA\u663E\u793A\u4E00\u884C\u201C\u52AA\u529B\u56DE\u7B54\u4E2D...\u201D,\u7B54\u6848\u901A\u9053\u4E00\u4EA7\u51FA\u5C31\u9010 token \u6D41\u5165\u540C\u4E00\u5F20\u5361,\u5B8C\u6210\u540E\u6536\u655B\u6210\u5E72\u51C0\u603B\u7ED3\u5361\u3002",
117776
+ "- \u8FD0\u884C\u4E2D\u7EDD\u4E0D\u5C55\u793A\u601D\u8003\u3001\u5DE5\u5177\u8BE6\u60C5\u3001\u8FDB\u5EA6 list\u3001\u672C\u5730\u8DEF\u5F84\u6216\u539F\u59CB runner text_delta;\u8FD9\u4E9B\u90FD\u5C5E\u4E8E\u5185\u90E8\u901A\u9053\u3002",
117704
117777
  "- \u4F60\u8D1F\u8D23\u628A\u6700\u7EC8\u7ED9\u8FD0\u8425\u770B\u7684\u6B63\u6587\u3001\u72B6\u6001\u3001\u4E0B\u4E00\u6B65\u95EE\u9898\u3001\u662F\u5426\u9700\u8981 choices/\u7ED3\u6784\u5316\u5361\u7247\u5199\u8FDB state.json\u3002",
117705
117778
  `\u4F60\u4E0D\u76F4\u63A5\u53D1/\u7F16\u8F91 bridge \u7BA1\u7406\u7684 post \u6216\u5361\u7247;\u4F60\u53EA\u5199 ${stateTarget},bridge \u8BFB\u5B83\u6765\u505A\u5B89\u5168\u6E32\u67D3\u3002`,
117779
+ "",
117780
+ "\u7B54\u6848\u6D41\u901A\u9053:",
117781
+ `- \u53EA\u6709\u771F\u6B63\u8981\u7ED9\u7528\u6237\u770B\u7684\u7B54\u6848\u6B63\u6587,\u624D\u5305\u5728\u72EC\u7ACB\u884C marker \u4E4B\u95F4\u8F93\u51FA\u5230 stdout: \`${ANSWER_BEGIN_MARKER}\` \u5230 \`${ANSWER_END_MARKER}\`\u3002`,
117782
+ "- marker \u5916\u7684\u53D9\u8FF0\u3001\u8BA1\u5212\u3001\u5DE5\u5177\u8BF4\u660E\u3001\u5185\u90E8\u5206\u6790\u90FD\u4F1A\u88AB bridge \u5F53\u4F5C internal_text,\u4E0D\u4F1A\u8FDB\u5165\u5361\u7247\u3002",
117783
+ "- \u5F53\u6700\u7EC8\u7B54\u6848\u8FD8\u6CA1\u51C6\u5907\u597D\u65F6\u53EF\u4EE5\u4E0D\u8F93\u51FA\u7B54\u6848 marker;\u5361\u7247\u53EA\u4FDD\u6301\u201C\u52AA\u529B\u56DE\u7B54\u4E2D...\u201D\u3002\u4E00\u65E6\u5F00\u59CB\u8F93\u51FA\u7B54\u6848 marker \u5185\u6B63\u6587,bridge \u4F1A\u7ACB\u5373\u6D41\u5F0F\u5C55\u793A\u3002",
117784
+ "- \u6BCF\u8F6E\u7ED3\u675F\u524D\u4ECD\u5FC5\u987B\u5199 state.json;\u6700\u7EC8\u6570\u636E\u4EE5 state.json \u7684 last_message/content_blocks/choices \u7B49\u4E3A\u51C6,\u7B54\u6848\u6D41\u53EA\u662F\u8FD0\u884C\u4E2D\u53EF\u89C1\u7684\u4F4E\u5EF6\u8FDF\u6B63\u6587\u901A\u9053\u3002",
117706
117785
  "**\u5B8C\u6210\u672C\u6B21\u54CD\u5E94\u524D\u5FC5\u987B**\u6839\u636E\u5F53\u524D\u5B9E\u9645\u72B6\u6001\u66F4\u65B0\u8FD9\u4E2A\u6587\u4EF6(\u539F\u5B50\u5199:\u5199 .tmp \u518D mv)\u3002",
117707
117786
  "",
117708
117787
  "\u4F60\u80FD\u5199\u7684\u5B57\u6BB5:",
117709
117788
  "- status: in_progress / ready / failed(bridge \u552F\u4E00\u5F3A\u4F9D\u8D56\u7684\u5B57\u6BB5)",
117710
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",
117711
117790
  "- error: \u5931\u8D25\u539F\u56E0(\u642D\u914D status=failed)",
117712
- "- card_title: \u5361\u7247\u6807\u9898\u8986\u76D6(\u53EF\u9009)",
117713
- "- 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",
117714
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",
117715
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',
117716
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',
@@ -117724,8 +117802,8 @@ function renderStateContract(stateFilePath) {
117724
117802
  "\u6302\u7740\u4E0D\u9000\u51FA = \u8BDD\u9898\u6C38\u8FDC\u5361\u5728\u300C\u6B63\u5728\u5904\u7406\u2026\u300D)\u3002",
117725
117803
  "",
117726
117804
  "\u5361\u7247\u5C55\u793A\u539F\u5219:",
117727
- "- \u8FD0\u884C\u4E2D\u4E3B\u9762\u662F post,bridge \u901A\u8FC7\u6574\u6761\u5BCC\u6587\u672C\u66FF\u6362\u505A\u5206\u5757\u5237\u65B0;\u5B83\u4E0D\u662F\u9010\u5B57 token streaming,\u4E0D\u8981\u4F9D\u8D56\u5DE5\u5177\u6D41/\u65E5\u5FD7\u8868\u8FBE\u4E1A\u52A1\u9636\u6BB5\u3002",
117728
- "- \u6700\u7EC8 post \u4EE5\u4F60\u7684 `last_message` \u4E3A\u4E3B;\u82E5\u5199\u4E86 `choices`\u3001`content_blocks` \u6216 `image_blocks`,bridge \u4F1A\u5728 post \u4E4B\u540E\u8865\u5361\u7247\u627F\u8F7D\u8FD9\u4E9B\u80FD\u529B\u3002",
117805
+ "- \u8FD0\u884C\u4E2D\u4E3B\u9762\u662F CardKit \u771F\u6D41\u5F0F\u5361\u7247;\u4E0D\u8981\u4F9D\u8D56\u5DE5\u5177\u6D41/\u65E5\u5FD7\u8868\u8FBE\u4E1A\u52A1\u9636\u6BB5,\u5B83\u4EEC\u4E0D\u4F1A\u663E\u793A\u3002",
117806
+ "- \u6700\u7EC8\u5361\u7247\u4EE5\u4F60\u7684 `last_message` \u4E3A\u4E3B;\u82E5\u5199\u4E86 `choices`\u3001`content_blocks` \u6216 `image_blocks`,bridge \u4F1A\u5728\u540C\u4E00\u5F20\u6700\u7EC8\u5361\u7247\u627F\u8F7D\u8FD9\u4E9B\u80FD\u529B\u3002",
117729
117807
  "- \u4E0D\u8981\u4F9D\u8D56 bridge \u4ECE\u8F93\u51FA\u91CC\u89E3\u6790\u4E1A\u52A1\u9636\u6BB5\u3001MR\u3001\u9884\u89C8\u5730\u5740\u6216\u4E0B\u4E00\u6B65\u52A8\u4F5C;\u9700\u8981\u5C55\u793A\u7684\u5185\u5BB9\u4F60\u81EA\u5DF1\u5199\u8FDB last_message\u3002",
117730
117808
  "- \u4E0D\u8981\u6C42\u56FA\u5B9A\u683C\u5F0F\u3002\u6839\u636E\u4EFB\u52A1\u9009\u62E9\u6700\u6E05\u695A\u7684\u8868\u8FBE:\u77ED\u7ED3\u8BBA\u3001\u5206\u70B9\u3001\u8868\u683C\u3001\u94FE\u63A5\u3001\u4E0B\u4E00\u6B65\u95EE\u9898\u90FD\u53EF\u4EE5\u3002",
117731
117809
  "- \u5982\u679C\u672C\u8F6E\u6D89\u53CA repo / \u4EE3\u7801 / \u6587\u6863\u4FEE\u6539,last_message \u5E94\u5305\u542B\u8DB3\u591F\u8BA9\u8FD0\u8425\u9A8C\u6536\u7684\u8BC1\u636E,\u4F8B\u5982\u4F60\u5B9E\u9645\u4F7F\u7528\u7684 workspace/repo\u3001\u5173\u952E diff \u6216\u94FE\u63A5\u3001\u8FD0\u884C\u8FC7\u7684\u6D4B\u8BD5/\u68C0\u67E5\u547D\u4EE4\u548C\u7ED3\u679C\u3002\u5177\u4F53\u8BC1\u636E\u7531\u4EFB\u52A1\u51B3\u5B9A;dogfood E2E \u7684\u4E25\u683C\u6E05\u5355\u53EA\u5728 dogfood guide \u4E2D\u8981\u6C42\u3002",
@@ -119047,8 +119125,9 @@ var CardKitFileSchema = external_exports.object({
119047
119125
  idempotencyKey: external_exports.string().min(1),
119048
119126
  sequence: external_exports.number().int().nonnegative().default(0),
119049
119127
  elements: external_exports.object({
119050
- status: external_exports.object({ elementId: external_exports.string().min(1) }),
119051
- thinking: external_exports.object({ elementId: external_exports.string().min(1) }),
119128
+ status: external_exports.object({ elementId: external_exports.string().min(1) }).optional(),
119129
+ thinking: external_exports.object({ elementId: external_exports.string().min(1) }).optional(),
119130
+ footer: external_exports.object({ elementId: external_exports.string().min(1) }).optional(),
119052
119131
  final: external_exports.object({ elementId: external_exports.string().min(1) })
119053
119132
  }).optional(),
119054
119133
  lastVisibleFallbackMessageId: external_exports.string().min(1).nullable().default(null),
@@ -119111,13 +119190,11 @@ async function deleteCardKitFile(worktreePath) {
119111
119190
  }
119112
119191
 
119113
119192
  // src/lark/cardkitSurface.ts
119114
- var CARDKIT_STATUS_ELEMENT_ID = "status_md";
119115
- var CARDKIT_THINKING_ELEMENT_ID = "thinking_md";
119116
119193
  var CARDKIT_FINAL_ELEMENT_ID = "final_md";
119194
+ var CARDKIT_FOOTER_ELEMENT_ID = "footer_md";
119117
119195
  var CARDKIT_CHOICES_ELEMENT_ID = "choices_slot";
119118
119196
  var MAX_CARD_BYTES = 30 * 1024;
119119
119197
  var FINAL_BUDGET_CHARS = 22e3;
119120
- var THINKING_BUDGET_CHARS = 6e3;
119121
119198
  var CHOICE_MARKERS2 = ["A", "B", "C", "D", "E"];
119122
119199
  function plainText2(content) {
119123
119200
  return { tag: "plain_text", content };
@@ -119127,6 +119204,9 @@ function markdown(content, elementId) {
119127
119204
  if (elementId) element["element_id"] = elementId;
119128
119205
  return element;
119129
119206
  }
119207
+ function buildCardKitAnswerElement(content = "") {
119208
+ return markdown(content, CARDKIT_FINAL_ELEMENT_ID);
119209
+ }
119130
119210
  function safeAtMention(target) {
119131
119211
  const id = target.user_id.trim();
119132
119212
  if (!/^[A-Za-z0-9_:-]+$/.test(id)) return "";
@@ -119164,8 +119244,7 @@ function truncateCardToBudget(card) {
119164
119244
  return cloned;
119165
119245
  }
119166
119246
  function buildCardKitInitialCard(opts = {}) {
119167
- const statusText = opts.statusText ?? "\u6B63\u5728\u5904\u7406\u2026";
119168
- const thinkingText = opts.thinkingText ?? "\u51C6\u5907\u542F\u52A8\u672C\u5730 agent\u3002";
119247
+ const footerText = opts.footerText ?? opts.statusText ?? "\u52AA\u529B\u56DE\u7B54\u4E2D...";
119169
119248
  return {
119170
119249
  schema: "2.0",
119171
119250
  config: {
@@ -119178,45 +119257,11 @@ function buildCardKitInitialCard(opts = {}) {
119178
119257
  print_strategy: "fast"
119179
119258
  }
119180
119259
  },
119181
- header: {
119182
- title: plainText2(opts.title ?? "\u5904\u7406\u4E2D"),
119183
- template: "blue"
119184
- },
119185
119260
  body: {
119186
- elements: [
119187
- markdown(statusText, CARDKIT_STATUS_ELEMENT_ID),
119188
- markdown(thinkingText, CARDKIT_THINKING_ELEMENT_ID),
119189
- { tag: "hr" },
119190
- markdown(" ", CARDKIT_FINAL_ELEMENT_ID)
119191
- ]
119261
+ elements: [markdown(footerText, CARDKIT_FOOTER_ELEMENT_ID)]
119192
119262
  }
119193
119263
  };
119194
119264
  }
119195
- function summarizeCardKitToolInput(input) {
119196
- if (input === null || input === void 0) return "";
119197
- let raw;
119198
- if (typeof input === "string") {
119199
- raw = input;
119200
- } else if (typeof input === "object") {
119201
- const obj = input;
119202
- const snippet2 = obj["command"] ?? obj["path"] ?? obj["file_path"] ?? obj["description"] ?? obj["cmd"] ?? null;
119203
- raw = snippet2 != null ? String(snippet2) : JSON.stringify(input);
119204
- } else {
119205
- raw = String(input);
119206
- }
119207
- return raw.length > 80 ? `${raw.slice(0, 77)}...` : raw;
119208
- }
119209
- function buildCardKitProgressMarkdown(snapshot) {
119210
- const status = snapshot.statusLines.slice(-3).map((line) => `- ${line.trim()}`);
119211
- const tools = snapshot.toolLines.slice(-5).map((line) => `- ${line.trim()}`);
119212
- const sections = [];
119213
- if (status.length) sections.push(`**\u8FDB\u5EA6**
119214
- ${status.join("\n")}`);
119215
- if (tools.length) sections.push(`**\u5DE5\u5177\u8C03\u7528**
119216
- ${tools.join("\n")}`);
119217
- const body = sections.join("\n\n") || "\u6B63\u5728\u5904\u7406\u2026";
119218
- return truncateChars(body, THINKING_BUDGET_CHARS, "\n\n_\u8FDB\u5EA6\u8F83\u957F,\u5DF2\u622A\u65AD\u3002_");
119219
- }
119220
119265
  function choiceMarker2(index) {
119221
119266
  return CHOICE_MARKERS2[index] ?? String(index + 1);
119222
119267
  }
@@ -119260,7 +119305,7 @@ function finalMarkdown(opts) {
119260
119305
  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_");
119261
119306
  }
119262
119307
  function buildCardKitFinalCard(opts) {
119263
- const elements = [markdown(finalMarkdown(opts), CARDKIT_FINAL_ELEMENT_ID)];
119308
+ const elements = [buildCardKitAnswerElement(finalMarkdown(opts))];
119264
119309
  const images = [];
119265
119310
  if (opts.contentBlocks?.length) {
119266
119311
  for (const block of opts.contentBlocks) {
@@ -119286,10 +119331,6 @@ function buildCardKitFinalCard(opts) {
119286
119331
  streaming_mode: false,
119287
119332
  summary: { content: truncateChars(opts.finalText.replace(/\s+/g, " ").trim(), 50, "...") }
119288
119333
  },
119289
- header: {
119290
- title: plainText2(opts.title ?? "\u5B8C\u6210"),
119291
- template: "green"
119292
- },
119293
119334
  body: { elements }
119294
119335
  });
119295
119336
  }
@@ -119298,8 +119339,8 @@ function buildCardKitFinalMarkdown(opts) {
119298
119339
  }
119299
119340
 
119300
119341
  // src/bridge/cardkitProgress.ts
119301
- var DEFAULT_PATCH_INTERVAL_MS = 1e3;
119302
- var DEFAULT_MAX_PROGRESS_UPDATES = 20;
119342
+ var DEFAULT_PATCH_INTERVAL_MS = 250;
119343
+ var DEFAULT_MAX_PROGRESS_UPDATES = 240;
119303
119344
  function idempotencyKey(facts) {
119304
119345
  return deriveCardKitUuid(
119305
119346
  ["reply", facts.botId, facts.threadId, facts.triggerMessageId].join("\0")
@@ -119308,6 +119349,11 @@ function idempotencyKey(facts) {
119308
119349
  function sequenceUuid(cardId, role, sequence) {
119309
119350
  return deriveCardKitUuid([cardId, role, String(sequence)].join("\0"));
119310
119351
  }
119352
+ function isMissingCardKitElementError(err) {
119353
+ if (!(err instanceof Error)) return false;
119354
+ const message = err.message.toLowerCase();
119355
+ return message.includes("element") && (message.includes("not found") || message.includes("not exist") || message.includes("\u4E0D\u5B58\u5728"));
119356
+ }
119311
119357
  var LiveCardKitProgressHandle = class {
119312
119358
  cardId;
119313
119359
  messageId;
@@ -119316,12 +119362,12 @@ var LiveCardKitProgressHandle = class {
119316
119362
  patchIntervalMs;
119317
119363
  maxProgressUpdates;
119318
119364
  onSequenceCommitted;
119319
- statusLines = [];
119320
- toolLines = [];
119365
+ answerBuffer = "";
119321
119366
  pendingPatch = null;
119322
119367
  inFlight = Promise.resolve();
119323
119368
  closed = false;
119324
119369
  progressUpdates = 0;
119370
+ answerElementCreated = false;
119325
119371
  sequence = 0;
119326
119372
  constructor(opts) {
119327
119373
  this.cardKitClient = opts.cardKitClient;
@@ -119331,18 +119377,19 @@ var LiveCardKitProgressHandle = class {
119331
119377
  this.patchIntervalMs = opts.patchIntervalMs;
119332
119378
  this.maxProgressUpdates = opts.maxProgressUpdates;
119333
119379
  this.onSequenceCommitted = opts.onSequenceCommitted;
119334
- this.statusLines.push(opts.initialStatusText);
119380
+ }
119381
+ get answerText() {
119382
+ return this.answerBuffer;
119335
119383
  }
119336
119384
  handle(event) {
119337
119385
  if (this.closed) return;
119338
- if (event.type === "system_init") {
119339
- this.statusLines.push("\u672C\u5730 agent \u5DF2\u542F\u52A8\u3002");
119386
+ if (event.type === "answer_delta") {
119387
+ this.answerBuffer += event.text;
119340
119388
  this.schedulePatch();
119341
119389
  return;
119342
119390
  }
119343
- if (event.type === "tool_use") {
119344
- const summary = summarizeCardKitToolInput(event.toolInput);
119345
- this.toolLines.push(`\u{1F527} ${event.toolName}${summary ? ` ${summary}` : ""}`);
119391
+ if (event.type === "answer_snapshot") {
119392
+ this.answerBuffer = event.text;
119346
119393
  this.schedulePatch();
119347
119394
  }
119348
119395
  }
@@ -119358,17 +119405,21 @@ var LiveCardKitProgressHandle = class {
119358
119405
  await this.drain();
119359
119406
  this.closed = true;
119360
119407
  const finalMarkdown2 = buildCardKitFinalMarkdown(opts);
119361
- await this.next(
119362
- (sequence) => this.cardKitClient.streamElementContent(
119363
- this.cardId,
119364
- CARDKIT_FINAL_ELEMENT_ID,
119365
- finalMarkdown2,
119366
- {
119367
- sequence,
119368
- uuid: sequenceUuid(this.cardId, "final-content", sequence)
119369
- }
119370
- )
119371
- );
119408
+ if (finalMarkdown2 !== this.answerBuffer) {
119409
+ await this.withAnswerElement(finalMarkdown2);
119410
+ await this.next(
119411
+ (sequence) => this.cardKitClient.streamElementContent(
119412
+ this.cardId,
119413
+ CARDKIT_FINAL_ELEMENT_ID,
119414
+ finalMarkdown2,
119415
+ {
119416
+ sequence,
119417
+ uuid: sequenceUuid(this.cardId, "final-content", sequence)
119418
+ }
119419
+ )
119420
+ );
119421
+ this.answerBuffer = finalMarkdown2;
119422
+ }
119372
119423
  await this.next(
119373
119424
  (sequence) => this.cardKitClient.updateCardEntity(this.cardId, buildCardKitFinalCard(opts), {
119374
119425
  sequence,
@@ -119408,23 +119459,38 @@ var LiveCardKitProgressHandle = class {
119408
119459
  }
119409
119460
  async patchProgress() {
119410
119461
  if (this.closed || this.progressUpdates >= this.maxProgressUpdates) return;
119411
- const content = buildCardKitProgressMarkdown({
119412
- statusLines: this.statusLines,
119413
- toolLines: this.toolLines
119414
- });
119462
+ if (!this.answerBuffer) return;
119415
119463
  this.progressUpdates += 1;
119416
119464
  this.inFlight = this.inFlight.then(
119417
- () => this.next(
119418
- (sequence) => this.cardKitClient.streamElementContent(this.cardId, "thinking_md", content, {
119419
- sequence,
119420
- uuid: sequenceUuid(this.cardId, "thinking", sequence)
119421
- })
119465
+ () => this.withAnswerElement(this.answerBuffer).then(
119466
+ () => this.next(
119467
+ (sequence) => this.cardKitClient.streamElementContent(this.cardId, CARDKIT_FINAL_ELEMENT_ID, this.answerBuffer, {
119468
+ sequence,
119469
+ uuid: sequenceUuid(this.cardId, "answer", sequence)
119470
+ })
119471
+ )
119422
119472
  )
119423
119473
  ).catch((err) => {
119424
119474
  console.warn("[cardkit_progress] progress update failed (continuing):", err);
119425
119475
  });
119426
119476
  await this.inFlight;
119427
119477
  }
119478
+ async withAnswerElement(initialContent) {
119479
+ if (this.answerElementCreated) return;
119480
+ await this.next(
119481
+ (sequence) => this.cardKitClient.createElements(
119482
+ this.cardId,
119483
+ [buildCardKitAnswerElement(initialContent)],
119484
+ {
119485
+ sequence,
119486
+ uuid: sequenceUuid(this.cardId, "answer-element", sequence),
119487
+ type: "insert_before",
119488
+ targetElementId: CARDKIT_FOOTER_ELEMENT_ID
119489
+ }
119490
+ )
119491
+ );
119492
+ this.answerElementCreated = true;
119493
+ }
119428
119494
  async next(fn) {
119429
119495
  this.sequence += 1;
119430
119496
  await fn(this.sequence);
@@ -119433,8 +119499,8 @@ var LiveCardKitProgressHandle = class {
119433
119499
  };
119434
119500
  async function createCardKitProgressHandle(opts) {
119435
119501
  const key = idempotencyKey(opts.facts);
119436
- const initialStatusText = opts.initialStatusText ?? "\u6B63\u5728\u5904\u7406\u2026";
119437
- const initialCard = buildCardKitInitialCard({ statusText: initialStatusText });
119502
+ const initialStatusText = opts.initialStatusText ?? "\u52AA\u529B\u56DE\u7B54\u4E2D...";
119503
+ const initialCard = buildCardKitInitialCard({ footerText: initialStatusText });
119438
119504
  const created = opts.cardKitClient.createCardReply ? await opts.cardKitClient.createCardReply(
119439
119505
  opts.replyToMessageId,
119440
119506
  initialCard,
@@ -119463,7 +119529,6 @@ async function createCardKitProgressHandle(opts) {
119463
119529
  idempotencyKey: key,
119464
119530
  patchIntervalMs: opts.patchIntervalMs ?? DEFAULT_PATCH_INTERVAL_MS,
119465
119531
  maxProgressUpdates: opts.maxProgressUpdates ?? DEFAULT_MAX_PROGRESS_UPDATES,
119466
- initialStatusText,
119467
119532
  onSequenceCommitted: opts.onSequenceCommitted
119468
119533
  });
119469
119534
  }
@@ -119475,7 +119540,7 @@ async function finalizeExistingCardKitCard(opts) {
119475
119540
  await opts.onSequenceCommitted?.(sequence);
119476
119541
  };
119477
119542
  const finalMarkdown2 = buildCardKitFinalMarkdown(opts.final);
119478
- await next(
119543
+ const streamFinalContent = () => next(
119479
119544
  "reconcile-final-content",
119480
119545
  (seq2, uuid) => opts.cardKitClient.streamElementContent(
119481
119546
  opts.cardId,
@@ -119484,6 +119549,25 @@ async function finalizeExistingCardKitCard(opts) {
119484
119549
  { sequence: seq2, uuid }
119485
119550
  )
119486
119551
  );
119552
+ try {
119553
+ await streamFinalContent();
119554
+ } catch (err) {
119555
+ if (!isMissingCardKitElementError(err)) throw err;
119556
+ await next(
119557
+ "reconcile-final-element",
119558
+ (seq2, uuid) => opts.cardKitClient.createElements(
119559
+ opts.cardId,
119560
+ [buildCardKitAnswerElement(finalMarkdown2)],
119561
+ {
119562
+ sequence: seq2,
119563
+ uuid,
119564
+ type: "insert_before",
119565
+ targetElementId: CARDKIT_FOOTER_ELEMENT_ID
119566
+ }
119567
+ )
119568
+ );
119569
+ await streamFinalContent();
119570
+ }
119487
119571
  await next(
119488
119572
  "reconcile-final-card",
119489
119573
  (seq2, uuid) => opts.cardKitClient.updateCardEntity(
@@ -120363,7 +120447,7 @@ async function dispatchResponseSurface(input) {
120363
120447
  }
120364
120448
 
120365
120449
  // src/bridge/postProgress.ts
120366
- var DEFAULT_PLACEHOLDER_TEXT = "\u6B63\u5728\u5904\u7406\u2026";
120450
+ var DEFAULT_PLACEHOLDER_TEXT = "\u52AA\u529B\u56DE\u7B54\u4E2D...";
120367
120451
  var DEFAULT_PATCH_INTERVAL_MS2 = 1500;
120368
120452
  var DEFAULT_MAX_PROGRESS_EDITS = 16;
120369
120453
  function normalizeText2(text) {
@@ -120401,8 +120485,13 @@ var LivePostProgressHandle = class {
120401
120485
  }
120402
120486
  handle(event) {
120403
120487
  if (this.closed) return;
120404
- if (event.type !== "text_delta") return;
120405
- this.textBuffer += event.text;
120488
+ if (event.type === "answer_delta") {
120489
+ this.textBuffer += event.text;
120490
+ } else if (event.type === "answer_snapshot") {
120491
+ this.textBuffer = event.text;
120492
+ } else {
120493
+ return;
120494
+ }
120406
120495
  this.schedulePatch();
120407
120496
  }
120408
120497
  async finalize(opts) {
@@ -121088,7 +121177,7 @@ var BridgeHandler = class {
121088
121177
  threadId,
121089
121178
  triggerMessageId: messageId
121090
121179
  },
121091
- initialStatusText: "\u6B63\u5728\u5904\u7406\u2026",
121180
+ initialStatusText: "\u52AA\u529B\u56DE\u7B54\u4E2D...",
121092
121181
  onSequenceCommitted: async (sequence) => {
121093
121182
  await updateCardKitRecord({ status: "streaming", sequence });
121094
121183
  }
@@ -121107,8 +121196,7 @@ var BridgeHandler = class {
121107
121196
  idempotencyKey: cardKitProgress.idempotencyKey,
121108
121197
  sequence: cardKitProgress.sequence,
121109
121198
  elements: {
121110
- status: { elementId: "status_md" },
121111
- thinking: { elementId: "thinking_md" },
121199
+ footer: { elementId: "footer_md" },
121112
121200
  final: { elementId: "final_md" }
121113
121201
  },
121114
121202
  lastVisibleFallbackMessageId: null,
@@ -121125,8 +121213,20 @@ var BridgeHandler = class {
121125
121213
  reason: "response surface \u4F7F\u7528 CardKit \u4F5C\u4E3A\u672C\u8F6E\u4E3B\u56DE\u590D\u9762\u3002"
121126
121214
  });
121127
121215
  } catch (err) {
121128
- cardKitStartFailed = true;
121129
- console.warn("[bridge.handler] create CardKit progress surface failed; using card fallback:", err);
121216
+ const existingMessageId = cardKitReplyConversionMessageId(err);
121217
+ if (existingMessageId) {
121218
+ card = this.deps.cardRenderer.handleFor(existingMessageId);
121219
+ await this.deps.client.removeProcessingReaction?.(messageId);
121220
+ await recordEvent({
121221
+ status: "running",
121222
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
121223
+ appendPath: "\u5DF2\u6536\u7F16 CardKit \u5360\u4F4D\u5361",
121224
+ reason: "CardKit idConvert \u5931\u8D25\u4F46\u5360\u4F4D\u5361\u5DF2\u53D1\u51FA\uFF0Cbridge \u590D\u7528\u540C\u4E00\u5F20\u5361\u505A\u53EF\u89C1\u515C\u5E95\u3002"
121225
+ });
121226
+ } else {
121227
+ cardKitStartFailed = true;
121228
+ console.warn("[bridge.handler] create CardKit progress surface failed; using card fallback:", err);
121229
+ }
121130
121230
  }
121131
121231
  }
121132
121232
  if (!card && !cardKitAvailable && postOutboundAvailable && this.deps.postClient) {
@@ -121153,7 +121253,7 @@ var BridgeHandler = class {
121153
121253
  threadId,
121154
121254
  triggerMessageId: messageId
121155
121255
  },
121156
- initialText: "\u6B63\u5728\u5904\u7406\u2026"
121256
+ initialText: "\u52AA\u529B\u56DE\u7B54\u4E2D..."
121157
121257
  });
121158
121258
  await this.deps.client.removeProcessingReaction?.(messageId);
121159
121259
  await recordEvent({
@@ -121274,7 +121374,7 @@ var BridgeHandler = class {
121274
121374
  gitlabToken: this.deps.gitlabToken
121275
121375
  });
121276
121376
  let sessionId;
121277
- let lastText = "";
121377
+ let trustedAnswerText = "";
121278
121378
  try {
121279
121379
  for await (const ev of handle.events) {
121280
121380
  if (cardKitProgress) cardKitProgress.handle(ev);
@@ -121283,8 +121383,10 @@ var BridgeHandler = class {
121283
121383
  if (ev.type === "system_init") {
121284
121384
  sessionId = ev.sessionId;
121285
121385
  }
121286
- if (ev.type === "text_delta") {
121287
- lastText = ev.text;
121386
+ if (ev.type === "answer_delta") {
121387
+ trustedAnswerText += ev.text;
121388
+ } else if (ev.type === "answer_snapshot") {
121389
+ trustedAnswerText = ev.text;
121288
121390
  }
121289
121391
  }
121290
121392
  const result = await handle.done;
@@ -121330,7 +121432,8 @@ var BridgeHandler = class {
121330
121432
  success = false;
121331
121433
  failureReason = `claude exited ${result.exitCode} \u4E14 bot \u672A\u66F4\u65B0 state.json status \u2014 \u53EF\u80FD\u5D29\u6E83`;
121332
121434
  }
121333
- const cardBody = reportedState?.last_message ?? (lastText.trim() ? lastText : "\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");
121435
+ const fallbackAnswer = trustedAnswerText.trim() || cardKitProgress?.answerText.trim() || "";
121436
+ 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");
121334
121437
  const noReportThisTurn = reportedState === null;
121335
121438
  const neutralTitle = noReportThisTurn && success && !failureReason ? "\u{1F4AC} \u5DF2\u56DE\u590D" : void 0;
121336
121439
  const baseCardPayload = {
@@ -121546,12 +121649,32 @@ var BridgeHandler = class {
121546
121649
  reason: String(err)
121547
121650
  });
121548
121651
  settle(false);
121652
+ const wtPath = this.deps.conventions.runtime === "agent_workspace" && this.deps.conventions.workspaceSessionsDir ? path10.join(this.deps.conventions.workspaceSessionsDir, threadId) : path10.join(this.deps.conventions.worktreesDir, threadId);
121653
+ const hardFailureText = `\u6267\u884C\u5931\u8D25: ${String(err)}`;
121654
+ const createHardFailurePostFallback = async (failureReason) => {
121655
+ const fallback = await createOnlyPostFallback({
121656
+ postClient: this.deps.postClient,
121657
+ replyToMessageId: messageId,
121658
+ replyInThread,
121659
+ botId: this.deps.botConfig?.id ?? "v1-default",
121660
+ threadId,
121661
+ triggerMessageId: messageId,
121662
+ finalText: hardFailureText,
121663
+ failureReason,
121664
+ title: "Larkway failure fallback",
121665
+ logPrefix: "[bridge.handler]"
121666
+ });
121667
+ if (fallback) {
121668
+ await deleteCardFile(wtPath);
121669
+ await deleteCardKitFile(wtPath);
121670
+ }
121671
+ return fallback;
121672
+ };
121549
121673
  if (!card && cardKitProgress) {
121550
121674
  try {
121551
121675
  await cardKitProgress.finalize({
121552
- finalText: `\u6267\u884C\u5931\u8D25: ${String(err)}`
121676
+ finalText: hardFailureText
121553
121677
  });
121554
- const wtPath = this.deps.conventions.runtime === "agent_workspace" && this.deps.conventions.workspaceSessionsDir ? path10.join(this.deps.conventions.workspaceSessionsDir, threadId) : path10.join(this.deps.conventions.worktreesDir, threadId);
121555
121678
  await deleteCardKitFile(wtPath);
121556
121679
  } catch (cardKitFinalizeErr) {
121557
121680
  console.error(
@@ -121562,6 +121685,9 @@ var BridgeHandler = class {
121562
121685
  card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121563
121686
  } catch (cardStartErr) {
121564
121687
  console.error("[bridge.handler] failure card start also failed:", cardStartErr);
121688
+ await createHardFailurePostFallback(
121689
+ `CardKit failure finalize failed: ${String(cardKitFinalizeErr)}; legacy visible card fallback also failed: ${String(cardStartErr)}`
121690
+ );
121565
121691
  }
121566
121692
  }
121567
121693
  }
@@ -121579,6 +121705,9 @@ var BridgeHandler = class {
121579
121705
  card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
121580
121706
  } catch (cardStartErr) {
121581
121707
  console.error("[bridge.handler] failure card start also failed:", cardStartErr);
121708
+ await createHardFailurePostFallback(
121709
+ `progress post failure update failed: ${String(postFinalizeErr)}; legacy visible card fallback also failed: ${String(cardStartErr)}`
121710
+ );
121582
121711
  }
121583
121712
  }
121584
121713
  }
@@ -121592,8 +121721,10 @@ var BridgeHandler = class {
121592
121721
  });
121593
121722
  } catch (finalizeErr) {
121594
121723
  console.error("[bridge.handler] finalize(failure) also failed:", finalizeErr);
121724
+ await createHardFailurePostFallback(
121725
+ `legacy visible failure card finalize failed: ${String(finalizeErr)}`
121726
+ );
121595
121727
  }
121596
- const wtPath = this.deps.conventions.runtime === "agent_workspace" && this.deps.conventions.workspaceSessionsDir ? path10.join(this.deps.conventions.workspaceSessionsDir, threadId) : path10.join(this.deps.conventions.worktreesDir, threadId);
121597
121728
  await deleteCardFile(wtPath);
121598
121729
  }
121599
121730
  }
@@ -125416,7 +125547,7 @@ function buildCommand(opts) {
125416
125547
  args.push("-p", opts.prompt);
125417
125548
  return [bin, args];
125418
125549
  }
125419
- function* parseLinesMulti(line) {
125550
+ function* parseLinesMulti(line, answerExtractor = new AnswerChannelExtractor()) {
125420
125551
  const trimmed = line.trim();
125421
125552
  if (trimmed === "") return;
125422
125553
  let obj;
@@ -125450,7 +125581,7 @@ function* parseLinesMulti(line) {
125450
125581
  if (typeof item !== "object" || item === null) continue;
125451
125582
  const block = item;
125452
125583
  if (block["type"] === "text" && typeof block["text"] === "string") {
125453
- yield { type: "text_delta", text: block["text"], raw: obj };
125584
+ yield* answerExtractor.ingestSnapshot(block["text"], obj);
125454
125585
  emitted = true;
125455
125586
  } else if (block["type"] === "tool_use") {
125456
125587
  yield {
@@ -125468,6 +125599,22 @@ function* parseLinesMulti(line) {
125468
125599
  yield { type: "raw", raw: obj };
125469
125600
  return;
125470
125601
  }
125602
+ if (eventType === "stream_event") {
125603
+ const event = record["event"];
125604
+ if (typeof event === "object" && event !== null) {
125605
+ const streamEvent = event;
125606
+ const delta = streamEvent["delta"];
125607
+ if (typeof delta === "object" && delta !== null) {
125608
+ const deltaRecord = delta;
125609
+ if (streamEvent["type"] === "content_block_delta" && deltaRecord["type"] === "text_delta" && typeof deltaRecord["text"] === "string") {
125610
+ yield* answerExtractor.ingestDelta(deltaRecord["text"], obj);
125611
+ return;
125612
+ }
125613
+ }
125614
+ }
125615
+ yield { type: "raw", raw: obj };
125616
+ return;
125617
+ }
125471
125618
  if (eventType === "user") {
125472
125619
  const message = record["message"];
125473
125620
  if (typeof message === "object" && message !== null && Array.isArray(message["content"])) {
@@ -125641,9 +125788,10 @@ stderr: ${stderr}` : "")
125641
125788
  // unblock the events loop so handler.ts can reach card.finalize().
125642
125789
  signal: rlAbortController.signal
125643
125790
  });
125791
+ const answerExtractor = new AnswerChannelExtractor();
125644
125792
  try {
125645
125793
  for await (const line of rl) {
125646
- for (const event of parseLinesMulti(line)) {
125794
+ for (const event of parseLinesMulti(line, answerExtractor)) {
125647
125795
  if (event.type === "system_init") {
125648
125796
  discoveredSessionId = event.sessionId;
125649
125797
  }
@@ -125795,7 +125943,7 @@ function* parseCodexLine(line) {
125795
125943
  return;
125796
125944
  }
125797
125945
  if (itemRecord["type"] === "agent_message" && typeof itemRecord["text"] === "string") {
125798
- yield { type: "text_delta", text: itemRecord["text"], raw: obj };
125946
+ yield* splitAnswerChannelText(itemRecord["text"], obj);
125799
125947
  return;
125800
125948
  }
125801
125949
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",