larkway 0.3.24 → 0.3.26

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.24**
11
+ **Current release: v0.3.26**
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.24**
11
+ **当前版本:v0.3.26**
12
12
 
13
13
  ---
14
14
 
package/dist/main.js CHANGED
@@ -117684,12 +117684,14 @@ var AnswerChannelExtractor = class {
117684
117684
  mode = "waiting";
117685
117685
  buffer = "";
117686
117686
  visibleText = "";
117687
+ lastSnapshotText = "";
117687
117688
  ingestDelta(text, raw) {
117688
117689
  if (!text || this.mode === "closed") return [];
117689
117690
  this.buffer += text;
117690
117691
  return this.drain(raw);
117691
117692
  }
117692
117693
  ingestSnapshot(text, raw) {
117694
+ this.lastSnapshotText = text;
117693
117695
  const events = splitAnswerChannelText(text, raw);
117694
117696
  const out = [];
117695
117697
  for (const event of events) {
@@ -117697,13 +117699,30 @@ var AnswerChannelExtractor = class {
117697
117699
  out.push(event);
117698
117700
  continue;
117699
117701
  }
117700
- if (event.text === this.visibleText) continue;
117702
+ if (event.text === this.visibleText) {
117703
+ if (text.includes(ANSWER_END_MARKER)) this.mode = "closed";
117704
+ continue;
117705
+ }
117701
117706
  this.visibleText = event.text;
117702
117707
  out.push(event);
117703
117708
  if (text.includes(ANSWER_END_MARKER)) this.mode = "closed";
117704
117709
  }
117705
117710
  return out;
117706
117711
  }
117712
+ ingestGrowingSnapshot(text, raw) {
117713
+ if (!text || this.mode === "closed") return [];
117714
+ if (this.lastSnapshotText && text.startsWith(this.lastSnapshotText)) {
117715
+ const delta = text.slice(this.lastSnapshotText.length);
117716
+ this.lastSnapshotText = text;
117717
+ return delta ? this.ingestDelta(delta, raw) : [];
117718
+ }
117719
+ if (this.lastSnapshotText === "") {
117720
+ this.lastSnapshotText = text;
117721
+ return this.ingestDelta(text, raw);
117722
+ }
117723
+ this.lastSnapshotText = text;
117724
+ return this.ingestSnapshot(text, raw);
117725
+ }
117707
117726
  drain(raw) {
117708
117727
  const events = [];
117709
117728
  if (this.mode === "waiting") {
@@ -119111,6 +119130,20 @@ async function deleteCardFile(worktreePath) {
119111
119130
  // src/bridge/cardkitFile.ts
119112
119131
  import fs5 from "node:fs/promises";
119113
119132
  import path8 from "node:path";
119133
+ var defaultCardKitLiveMetrics = () => ({
119134
+ answerDeltaCount: 0,
119135
+ answerSnapshotCount: 0,
119136
+ firstAnswerAt: null,
119137
+ lastAnswerAt: null,
119138
+ visibleAnswerLength: 0,
119139
+ toolUseCount: 0,
119140
+ lastToolUseAt: null,
119141
+ statusPatchCount: 0,
119142
+ lastStatusPatchAt: null,
119143
+ progressUpdateCount: 0,
119144
+ lastProgressPatchAt: null,
119145
+ lastPatchError: null
119146
+ });
119114
119147
  var CardKitFileSchema = external_exports.object({
119115
119148
  surface: external_exports.literal("cardkit_stream"),
119116
119149
  status: external_exports.enum(["planned", "message_sent", "streaming", "finalized", "fallback_visible", "failed"]).default("planned"),
@@ -119124,6 +119157,20 @@ var CardKitFileSchema = external_exports.object({
119124
119157
  replyInThread: external_exports.boolean().default(true),
119125
119158
  idempotencyKey: external_exports.string().min(1),
119126
119159
  sequence: external_exports.number().int().nonnegative().default(0),
119160
+ live: external_exports.object({
119161
+ answerDeltaCount: external_exports.number().int().nonnegative().default(0),
119162
+ answerSnapshotCount: external_exports.number().int().nonnegative().default(0),
119163
+ firstAnswerAt: external_exports.string().min(1).nullable().default(null),
119164
+ lastAnswerAt: external_exports.string().min(1).nullable().default(null),
119165
+ visibleAnswerLength: external_exports.number().int().nonnegative().default(0),
119166
+ toolUseCount: external_exports.number().int().nonnegative().default(0),
119167
+ lastToolUseAt: external_exports.string().min(1).nullable().default(null),
119168
+ statusPatchCount: external_exports.number().int().nonnegative().default(0),
119169
+ lastStatusPatchAt: external_exports.string().min(1).nullable().default(null),
119170
+ progressUpdateCount: external_exports.number().int().nonnegative().default(0),
119171
+ lastProgressPatchAt: external_exports.string().min(1).nullable().default(null),
119172
+ lastPatchError: external_exports.string().min(1).nullable().default(null)
119173
+ }).default(defaultCardKitLiveMetrics),
119127
119174
  elements: external_exports.object({
119128
119175
  status: external_exports.object({ elementId: external_exports.string().min(1) }).optional(),
119129
119176
  thinking: external_exports.object({ elementId: external_exports.string().min(1) }).optional(),
@@ -119354,6 +119401,29 @@ function isMissingCardKitElementError(err) {
119354
119401
  const message = err.message.toLowerCase();
119355
119402
  return message.includes("element") && (message.includes("not found") || message.includes("not exist") || message.includes("\u4E0D\u5B58\u5728"));
119356
119403
  }
119404
+ function initialLiveMetrics() {
119405
+ return {
119406
+ answerDeltaCount: 0,
119407
+ answerSnapshotCount: 0,
119408
+ firstAnswerAt: null,
119409
+ lastAnswerAt: null,
119410
+ visibleAnswerLength: 0,
119411
+ toolUseCount: 0,
119412
+ lastToolUseAt: null,
119413
+ statusPatchCount: 0,
119414
+ lastStatusPatchAt: null,
119415
+ progressUpdateCount: 0,
119416
+ lastProgressPatchAt: null,
119417
+ lastPatchError: null
119418
+ };
119419
+ }
119420
+ function summarizeError(err) {
119421
+ const message = err instanceof Error ? err.message : String(err);
119422
+ return message.replace(/\s+/g, " ").trim().slice(0, 240) || "unknown error";
119423
+ }
119424
+ function toolStatusText(toolUseCount) {
119425
+ return toolUseCount > 0 ? `\u52AA\u529B\u56DE\u7B54\u4E2D... \xB7 \u5DF2\u7528 ${toolUseCount} \u4E2A\u5DE5\u5177` : "\u52AA\u529B\u56DE\u7B54\u4E2D...";
119426
+ }
119357
119427
  var LiveCardKitProgressHandle = class {
119358
119428
  cardId;
119359
119429
  messageId;
@@ -119362,12 +119432,14 @@ var LiveCardKitProgressHandle = class {
119362
119432
  patchIntervalMs;
119363
119433
  maxProgressUpdates;
119364
119434
  onSequenceCommitted;
119435
+ onLiveMetricsChanged;
119365
119436
  answerBuffer = "";
119366
119437
  pendingPatch = null;
119367
119438
  inFlight = Promise.resolve();
119368
119439
  closed = false;
119369
- progressUpdates = 0;
119370
119440
  answerElementCreated = false;
119441
+ immediatePatchStarted = false;
119442
+ metrics = initialLiveMetrics();
119371
119443
  sequence = 0;
119372
119444
  constructor(opts) {
119373
119445
  this.cardKitClient = opts.cardKitClient;
@@ -119377,20 +119449,31 @@ var LiveCardKitProgressHandle = class {
119377
119449
  this.patchIntervalMs = opts.patchIntervalMs;
119378
119450
  this.maxProgressUpdates = opts.maxProgressUpdates;
119379
119451
  this.onSequenceCommitted = opts.onSequenceCommitted;
119452
+ this.onLiveMetricsChanged = opts.onLiveMetricsChanged;
119380
119453
  }
119381
119454
  get answerText() {
119382
119455
  return this.answerBuffer;
119383
119456
  }
119457
+ get liveMetrics() {
119458
+ return { ...this.metrics };
119459
+ }
119384
119460
  handle(event) {
119385
119461
  if (this.closed) return;
119462
+ if (event.type === "tool_use") {
119463
+ this.recordToolUse();
119464
+ this.patchStatus(toolStatusText(this.metrics.toolUseCount));
119465
+ return;
119466
+ }
119386
119467
  if (event.type === "answer_delta") {
119387
119468
  this.answerBuffer += event.text;
119388
- this.schedulePatch();
119469
+ this.recordAnswerEvent("answer_delta");
119470
+ this.schedulePatch({ immediate: !this.immediatePatchStarted });
119389
119471
  return;
119390
119472
  }
119391
119473
  if (event.type === "answer_snapshot") {
119392
119474
  this.answerBuffer = event.text;
119393
- this.schedulePatch();
119475
+ this.recordAnswerEvent("answer_snapshot");
119476
+ this.schedulePatch({ immediate: !this.immediatePatchStarted });
119394
119477
  }
119395
119478
  }
119396
119479
  async drain() {
@@ -119449,8 +119532,71 @@ var LiveCardKitProgressHandle = class {
119449
119532
  this.pendingPatch = null;
119450
119533
  }
119451
119534
  }
119452
- schedulePatch() {
119453
- if (this.pendingPatch || this.progressUpdates >= this.maxProgressUpdates) return;
119535
+ recordAnswerEvent(type2) {
119536
+ const now = (/* @__PURE__ */ new Date()).toISOString();
119537
+ if (type2 === "answer_delta") {
119538
+ this.metrics.answerDeltaCount += 1;
119539
+ } else {
119540
+ this.metrics.answerSnapshotCount += 1;
119541
+ }
119542
+ this.metrics.firstAnswerAt ??= now;
119543
+ this.metrics.lastAnswerAt = now;
119544
+ this.metrics.visibleAnswerLength = this.answerBuffer.length;
119545
+ this.emitLiveMetrics();
119546
+ console.info(
119547
+ "[cardkit_progress] answer event",
119548
+ `type=${type2}`,
119549
+ `delta_count=${this.metrics.answerDeltaCount}`,
119550
+ `snapshot_count=${this.metrics.answerSnapshotCount}`,
119551
+ `visible_length=${this.metrics.visibleAnswerLength}`,
119552
+ `sequence=${this.sequence}`
119553
+ );
119554
+ }
119555
+ recordToolUse() {
119556
+ this.metrics.toolUseCount += 1;
119557
+ this.metrics.lastToolUseAt = (/* @__PURE__ */ new Date()).toISOString();
119558
+ this.emitLiveMetrics();
119559
+ console.info(
119560
+ "[cardkit_progress] tool event",
119561
+ `tool_use_count=${this.metrics.toolUseCount}`,
119562
+ `sequence=${this.sequence}`
119563
+ );
119564
+ }
119565
+ patchStatus(content) {
119566
+ this.inFlight = this.inFlight.then(
119567
+ () => this.next(
119568
+ (sequence) => this.cardKitClient.updateElement(
119569
+ this.cardId,
119570
+ CARDKIT_FOOTER_ELEMENT_ID,
119571
+ {
119572
+ tag: "markdown",
119573
+ content,
119574
+ element_id: CARDKIT_FOOTER_ELEMENT_ID
119575
+ },
119576
+ {
119577
+ sequence,
119578
+ uuid: sequenceUuid(this.cardId, "status", sequence)
119579
+ }
119580
+ )
119581
+ )
119582
+ ).then(() => {
119583
+ this.metrics.statusPatchCount += 1;
119584
+ this.metrics.lastStatusPatchAt = (/* @__PURE__ */ new Date()).toISOString();
119585
+ this.metrics.lastPatchError = null;
119586
+ this.emitLiveMetrics();
119587
+ }).catch((err) => {
119588
+ this.metrics.lastPatchError = summarizeError(err);
119589
+ this.emitLiveMetrics();
119590
+ console.warn("[cardkit_progress] status update failed (continuing):", err);
119591
+ });
119592
+ }
119593
+ schedulePatch(opts = {}) {
119594
+ if (this.pendingPatch || this.metrics.progressUpdateCount >= this.maxProgressUpdates) return;
119595
+ if (opts.immediate) {
119596
+ this.immediatePatchStarted = true;
119597
+ void this.patchProgress();
119598
+ return;
119599
+ }
119454
119600
  this.pendingPatch = setTimeout(() => {
119455
119601
  this.pendingPatch = null;
119456
119602
  void this.patchProgress();
@@ -119458,9 +119604,8 @@ var LiveCardKitProgressHandle = class {
119458
119604
  this.pendingPatch.unref?.();
119459
119605
  }
119460
119606
  async patchProgress() {
119461
- if (this.closed || this.progressUpdates >= this.maxProgressUpdates) return;
119607
+ if (this.closed || this.metrics.progressUpdateCount >= this.maxProgressUpdates) return;
119462
119608
  if (!this.answerBuffer) return;
119463
- this.progressUpdates += 1;
119464
119609
  this.inFlight = this.inFlight.then(
119465
119610
  () => this.withAnswerElement(this.answerBuffer).then(
119466
119611
  () => this.next(
@@ -119470,7 +119615,21 @@ var LiveCardKitProgressHandle = class {
119470
119615
  })
119471
119616
  )
119472
119617
  )
119473
- ).catch((err) => {
119618
+ ).then(() => {
119619
+ this.metrics.progressUpdateCount += 1;
119620
+ this.metrics.visibleAnswerLength = this.answerBuffer.length;
119621
+ this.metrics.lastProgressPatchAt = (/* @__PURE__ */ new Date()).toISOString();
119622
+ this.metrics.lastPatchError = null;
119623
+ this.emitLiveMetrics();
119624
+ console.info(
119625
+ "[cardkit_progress] progress committed",
119626
+ `progress_update_count=${this.metrics.progressUpdateCount}`,
119627
+ `visible_length=${this.metrics.visibleAnswerLength}`,
119628
+ `sequence=${this.sequence}`
119629
+ );
119630
+ }).catch((err) => {
119631
+ this.metrics.lastPatchError = summarizeError(err);
119632
+ this.emitLiveMetrics();
119474
119633
  console.warn("[cardkit_progress] progress update failed (continuing):", err);
119475
119634
  });
119476
119635
  await this.inFlight;
@@ -119496,6 +119655,9 @@ var LiveCardKitProgressHandle = class {
119496
119655
  await fn(this.sequence);
119497
119656
  await this.onSequenceCommitted?.(this.sequence);
119498
119657
  }
119658
+ emitLiveMetrics() {
119659
+ this.onLiveMetricsChanged?.({ ...this.metrics, sequence: this.sequence });
119660
+ }
119499
119661
  };
119500
119662
  async function createCardKitProgressHandle(opts) {
119501
119663
  const key = idempotencyKey(opts.facts);
@@ -119529,7 +119691,8 @@ async function createCardKitProgressHandle(opts) {
119529
119691
  idempotencyKey: key,
119530
119692
  patchIntervalMs: opts.patchIntervalMs ?? DEFAULT_PATCH_INTERVAL_MS,
119531
119693
  maxProgressUpdates: opts.maxProgressUpdates ?? DEFAULT_MAX_PROGRESS_UPDATES,
119532
- onSequenceCommitted: opts.onSequenceCommitted
119694
+ onSequenceCommitted: opts.onSequenceCommitted,
119695
+ onLiveMetricsChanged: opts.onLiveMetricsChanged
119533
119696
  });
119534
119697
  }
119535
119698
  async function finalizeExistingCardKitCard(opts) {
@@ -120603,6 +120766,7 @@ var ResponseSurfacePostBudget = class {
120603
120766
  };
120604
120767
 
120605
120768
  // src/bridge/handler.ts
120769
+ var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
120606
120770
  function execGit(cwd, args) {
120607
120771
  return new Promise((resolve2, reject) => {
120608
120772
  const child = child_process.spawn("git", args, {
@@ -121157,6 +121321,7 @@ var BridgeHandler = class {
121157
121321
  } catch (err) {
121158
121322
  console.warn("[bridge.handler] ensureStateFile failed (continuing):", err);
121159
121323
  }
121324
+ let cardKitRecordWrite = Promise.resolve();
121160
121325
  const updateCardKitRecord = async (patch) => {
121161
121326
  if (!cardKitRecord) return;
121162
121327
  cardKitRecord = {
@@ -121164,7 +121329,16 @@ var BridgeHandler = class {
121164
121329
  ...patch,
121165
121330
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
121166
121331
  };
121167
- await writeCardKitFile(worktreePath, cardKitRecord);
121332
+ const record = cardKitRecord;
121333
+ cardKitRecordWrite = cardKitRecordWrite.catch(() => {
121334
+ }).then(() => writeCardKitFile(worktreePath, record));
121335
+ await cardKitRecordWrite;
121336
+ };
121337
+ const updateCardKitLiveMetrics = (metrics) => {
121338
+ const { sequence, ...live } = metrics;
121339
+ void updateCardKitRecord({ sequence, live }).catch((err) => {
121340
+ console.warn("[bridge.handler] write CardKit live metrics failed:", err);
121341
+ });
121168
121342
  };
121169
121343
  if (!card && cardKitAvailable && this.deps.cardKitClient) {
121170
121344
  try {
@@ -121180,7 +121354,8 @@ var BridgeHandler = class {
121180
121354
  initialStatusText: "\u52AA\u529B\u56DE\u7B54\u4E2D...",
121181
121355
  onSequenceCommitted: async (sequence) => {
121182
121356
  await updateCardKitRecord({ status: "streaming", sequence });
121183
- }
121357
+ },
121358
+ onLiveMetricsChanged: updateCardKitLiveMetrics
121184
121359
  });
121185
121360
  cardKitRecord = {
121186
121361
  surface: "cardkit_stream",
@@ -121195,6 +121370,7 @@ var BridgeHandler = class {
121195
121370
  replyInThread,
121196
121371
  idempotencyKey: cardKitProgress.idempotencyKey,
121197
121372
  sequence: cardKitProgress.sequence,
121373
+ live: cardKitProgress.liveMetrics,
121198
121374
  elements: {
121199
121375
  footer: { elementId: "footer_md" },
121200
121376
  final: { elementId: "final_md" }
@@ -121362,7 +121538,12 @@ var BridgeHandler = class {
121362
121538
  });
121363
121539
  const backend = this.deps.botConfig?.backend ?? "claude";
121364
121540
  const permissionMode = this.deps.permissionMode ?? "bypassPermissions";
121365
- const timeoutMs = this.deps.subprocessTimeoutMs ?? 60 * 60 * 1e3;
121541
+ const baseTimeoutMs = this.deps.subprocessTimeoutMs ?? 60 * 60 * 1e3;
121542
+ const timeoutMs = cardKitProgress ? Math.min(
121543
+ baseTimeoutMs,
121544
+ this.deps.responseSurfaceTimeoutMs ?? DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS
121545
+ ) : baseTimeoutMs;
121546
+ const runnerStartedAt = Date.now();
121366
121547
  const handle = createRunner(backend).run({
121367
121548
  prompt,
121368
121549
  resumeSessionId: currentExisting?.sessionId,
@@ -121390,6 +121571,7 @@ var BridgeHandler = class {
121390
121571
  }
121391
121572
  }
121392
121573
  const result = await handle.done;
121574
+ const cardKitTurnTimedOut = cardKitProgress != null && result.exitCode !== 0 && Date.now() - runnerStartedAt >= timeoutMs;
121393
121575
  const rawReportedState = await readStateFile(worktreePath);
121394
121576
  const reportedState = rawReportedState?.updated_at != null && rawReportedState.updated_at !== preRunUpdatedAt ? rawReportedState : null;
121395
121577
  const now = Date.now();
@@ -121419,6 +121601,7 @@ var BridgeHandler = class {
121419
121601
  }
121420
121602
  const reportedStatus = reportedState?.status;
121421
121603
  const reportedError = reportedState?.error;
121604
+ const cardKitTimeoutFailure = cardKitTurnTimedOut && reportedStatus !== "ready" && reportedStatus !== "failed";
121422
121605
  let success;
121423
121606
  let failureReason;
121424
121607
  if (reportedStatus === "failed") {
@@ -121426,6 +121609,9 @@ var BridgeHandler = class {
121426
121609
  failureReason = reportedError ?? "bot \u62A5\u544A failed (\u65E0 error \u5B57\u6BB5)";
121427
121610
  } else if (reportedStatus === "ready") {
121428
121611
  success = true;
121612
+ } else if (cardKitTimeoutFailure) {
121613
+ success = false;
121614
+ failureReason = `agent turn timed out after ${timeoutMs}ms; CardKit running card was finalized as interrupted`;
121429
121615
  } else if (result.exitCode === 0) {
121430
121616
  success = true;
121431
121617
  } else {
@@ -121433,14 +121619,14 @@ var BridgeHandler = class {
121433
121619
  failureReason = `claude exited ${result.exitCode} \u4E14 bot \u672A\u66F4\u65B0 state.json status \u2014 \u53EF\u80FD\u5D29\u6E83`;
121434
121620
  }
121435
121621
  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");
121622
+ 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");
121437
121623
  const noReportThisTurn = reportedState === null;
121438
121624
  const neutralTitle = noReportThisTurn && success && !failureReason ? "\u{1F4AC} \u5DF2\u56DE\u590D" : void 0;
121439
121625
  const baseCardPayload = {
121440
121626
  finalText: cardBody,
121441
121627
  success,
121442
121628
  failureReason,
121443
- titleOverride: reportedState?.card_title ?? neutralTitle,
121629
+ titleOverride: reportedState?.card_title ?? (cardKitTimeoutFailure ? "\u5DF2\u4E2D\u65AD" : neutralTitle),
121444
121630
  colorOverride: reportedState?.card_color ?? (neutralTitle ? "neutral" : void 0),
121445
121631
  // V2 dynamic-choice buttons — agent-declared, rendered verbatim.
121446
121632
  // reportedState is null when state.json wasn't freshly written
@@ -125581,7 +125767,7 @@ function* parseLinesMulti(line, answerExtractor = new AnswerChannelExtractor())
125581
125767
  if (typeof item !== "object" || item === null) continue;
125582
125768
  const block = item;
125583
125769
  if (block["type"] === "text" && typeof block["text"] === "string") {
125584
- yield* answerExtractor.ingestSnapshot(block["text"], obj);
125770
+ yield* answerExtractor.ingestGrowingSnapshot(block["text"], obj);
125585
125771
  emitted = true;
125586
125772
  } else if (block["type"] === "tool_use") {
125587
125773
  yield {
@@ -125893,64 +126079,80 @@ function buildCodexCommand(opts, codexBinPath = "codex") {
125893
126079
  const freshFlags = opts.cwd != null ? ["-C", opts.cwd, ...commonFlags] : commonFlags;
125894
126080
  return [codexBinPath, ["exec", ...freshFlags]];
125895
126081
  }
125896
- function* parseCodexLine(line) {
125897
- const trimmed = line.trim();
125898
- if (trimmed === "") return;
125899
- let obj;
125900
- try {
125901
- obj = JSON.parse(trimmed);
125902
- } catch {
125903
- yield { type: "raw", raw: trimmed };
125904
- return;
125905
- }
125906
- if (typeof obj !== "object" || obj === null) {
125907
- yield { type: "raw", raw: obj };
125908
- return;
125909
- }
125910
- const record = obj;
125911
- const topType = record["type"];
125912
- if (topType === "thread.started" && typeof record["thread_id"] === "string") {
125913
- yield { type: "system_init", sessionId: record["thread_id"], raw: obj };
125914
- return;
125915
- }
125916
- if (topType === "turn.completed") {
125917
- yield { type: "result", stopReason: "end_turn", raw: obj };
125918
- return;
125919
- }
125920
- if (topType === "item.started") {
125921
- const item = record["item"];
125922
- if (typeof item === "object" && item !== null) {
125923
- const itemRecord = item;
125924
- if (itemRecord["type"] === "command_execution" && typeof itemRecord["command"] === "string") {
125925
- yield {
125926
- type: "tool_use",
125927
- toolName: "shell",
125928
- toolInput: { command: itemRecord["command"] },
125929
- raw: obj
125930
- };
126082
+ var CodexLineParser = class {
126083
+ answerExtractor = new AnswerChannelExtractor();
126084
+ *parseLine(line) {
126085
+ const trimmed = line.trim();
126086
+ if (trimmed === "") return;
126087
+ let obj;
126088
+ try {
126089
+ obj = JSON.parse(trimmed);
126090
+ } catch {
126091
+ yield { type: "raw", raw: trimmed };
126092
+ return;
126093
+ }
126094
+ if (typeof obj !== "object" || obj === null) {
126095
+ yield { type: "raw", raw: obj };
126096
+ return;
126097
+ }
126098
+ const record = obj;
126099
+ const topType = record["type"];
126100
+ if (topType === "thread.started" && typeof record["thread_id"] === "string") {
126101
+ yield { type: "system_init", sessionId: record["thread_id"], raw: obj };
126102
+ return;
126103
+ }
126104
+ if (topType === "turn.completed") {
126105
+ yield { type: "result", stopReason: "end_turn", raw: obj };
126106
+ return;
126107
+ }
126108
+ const agentMessageText = agentMessageTextFrom(record);
126109
+ if (agentMessageText !== void 0) {
126110
+ if (topType === "item.completed") {
126111
+ yield* this.answerExtractor.ingestSnapshot(agentMessageText, obj);
125931
126112
  return;
125932
126113
  }
126114
+ yield* this.answerExtractor.ingestGrowingSnapshot(agentMessageText, obj);
126115
+ return;
125933
126116
  }
125934
- yield { type: "raw", raw: obj };
125935
- return;
125936
- }
125937
- if (topType === "item.completed") {
125938
- const item = record["item"];
125939
- if (typeof item === "object" && item !== null) {
125940
- const itemRecord = item;
125941
- if (itemRecord["type"] === "command_execution") {
125942
- yield { type: "tool_result", raw: obj };
125943
- return;
126117
+ if (topType === "item.started") {
126118
+ const item = record["item"];
126119
+ if (typeof item === "object" && item !== null) {
126120
+ const itemRecord = item;
126121
+ if (itemRecord["type"] === "command_execution" && typeof itemRecord["command"] === "string") {
126122
+ yield {
126123
+ type: "tool_use",
126124
+ toolName: "shell",
126125
+ toolInput: { command: itemRecord["command"] },
126126
+ raw: obj
126127
+ };
126128
+ return;
126129
+ }
125944
126130
  }
125945
- if (itemRecord["type"] === "agent_message" && typeof itemRecord["text"] === "string") {
125946
- yield* splitAnswerChannelText(itemRecord["text"], obj);
125947
- return;
126131
+ yield { type: "raw", raw: obj };
126132
+ return;
126133
+ }
126134
+ if (topType === "item.completed") {
126135
+ const item = record["item"];
126136
+ if (typeof item === "object" && item !== null) {
126137
+ const itemRecord = item;
126138
+ if (itemRecord["type"] === "command_execution") {
126139
+ yield { type: "tool_result", raw: obj };
126140
+ return;
126141
+ }
125948
126142
  }
126143
+ yield { type: "raw", raw: obj };
126144
+ return;
125949
126145
  }
125950
126146
  yield { type: "raw", raw: obj };
125951
- return;
125952
126147
  }
125953
- yield { type: "raw", raw: obj };
126148
+ };
126149
+ function agentMessageTextFrom(record) {
126150
+ const item = record["item"];
126151
+ if (typeof item !== "object" || item === null) return void 0;
126152
+ const itemRecord = item;
126153
+ if (itemRecord["type"] !== "agent_message") return void 0;
126154
+ const text = itemRecord["text"];
126155
+ return typeof text === "string" ? text : void 0;
125954
126156
  }
125955
126157
  function runCodex(opts, codexBinPath = "codex") {
125956
126158
  const timeoutMs = opts.timeoutMs ?? 15 * 60 * 1e3;
@@ -126075,9 +126277,10 @@ stderr: ${stderr}` : "")
126075
126277
  crlfDelay: Infinity,
126076
126278
  signal: rlAbortController.signal
126077
126279
  });
126280
+ const parser = new CodexLineParser();
126078
126281
  try {
126079
126282
  for await (const line of rl) {
126080
- for (const event of parseCodexLine(line)) {
126283
+ for (const event of parser.parseLine(line)) {
126081
126284
  if (event.type === "system_init") {
126082
126285
  discoveredSessionId = event.sessionId;
126083
126286
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.24",
3
+ "version": "0.3.26",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",