larkway 0.3.24 → 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 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.25**
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.25**
12
12
 
13
13
  ---
14
14
 
package/dist/main.js CHANGED
@@ -119111,6 +119111,20 @@ async function deleteCardFile(worktreePath) {
119111
119111
  // src/bridge/cardkitFile.ts
119112
119112
  import fs5 from "node:fs/promises";
119113
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
+ });
119114
119128
  var CardKitFileSchema = external_exports.object({
119115
119129
  surface: external_exports.literal("cardkit_stream"),
119116
119130
  status: external_exports.enum(["planned", "message_sent", "streaming", "finalized", "fallback_visible", "failed"]).default("planned"),
@@ -119124,6 +119138,20 @@ var CardKitFileSchema = external_exports.object({
119124
119138
  replyInThread: external_exports.boolean().default(true),
119125
119139
  idempotencyKey: external_exports.string().min(1),
119126
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),
119127
119155
  elements: external_exports.object({
119128
119156
  status: external_exports.object({ elementId: external_exports.string().min(1) }).optional(),
119129
119157
  thinking: external_exports.object({ elementId: external_exports.string().min(1) }).optional(),
@@ -119354,6 +119382,29 @@ function isMissingCardKitElementError(err) {
119354
119382
  const message = err.message.toLowerCase();
119355
119383
  return message.includes("element") && (message.includes("not found") || message.includes("not exist") || message.includes("\u4E0D\u5B58\u5728"));
119356
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
+ }
119357
119408
  var LiveCardKitProgressHandle = class {
119358
119409
  cardId;
119359
119410
  messageId;
@@ -119362,12 +119413,14 @@ var LiveCardKitProgressHandle = class {
119362
119413
  patchIntervalMs;
119363
119414
  maxProgressUpdates;
119364
119415
  onSequenceCommitted;
119416
+ onLiveMetricsChanged;
119365
119417
  answerBuffer = "";
119366
119418
  pendingPatch = null;
119367
119419
  inFlight = Promise.resolve();
119368
119420
  closed = false;
119369
- progressUpdates = 0;
119370
119421
  answerElementCreated = false;
119422
+ immediatePatchStarted = false;
119423
+ metrics = initialLiveMetrics();
119371
119424
  sequence = 0;
119372
119425
  constructor(opts) {
119373
119426
  this.cardKitClient = opts.cardKitClient;
@@ -119377,20 +119430,31 @@ var LiveCardKitProgressHandle = class {
119377
119430
  this.patchIntervalMs = opts.patchIntervalMs;
119378
119431
  this.maxProgressUpdates = opts.maxProgressUpdates;
119379
119432
  this.onSequenceCommitted = opts.onSequenceCommitted;
119433
+ this.onLiveMetricsChanged = opts.onLiveMetricsChanged;
119380
119434
  }
119381
119435
  get answerText() {
119382
119436
  return this.answerBuffer;
119383
119437
  }
119438
+ get liveMetrics() {
119439
+ return { ...this.metrics };
119440
+ }
119384
119441
  handle(event) {
119385
119442
  if (this.closed) return;
119443
+ if (event.type === "tool_use") {
119444
+ this.recordToolUse();
119445
+ this.patchStatus(toolStatusText(this.metrics.toolUseCount));
119446
+ return;
119447
+ }
119386
119448
  if (event.type === "answer_delta") {
119387
119449
  this.answerBuffer += event.text;
119388
- this.schedulePatch();
119450
+ this.recordAnswerEvent("answer_delta");
119451
+ this.schedulePatch({ immediate: !this.immediatePatchStarted });
119389
119452
  return;
119390
119453
  }
119391
119454
  if (event.type === "answer_snapshot") {
119392
119455
  this.answerBuffer = event.text;
119393
- this.schedulePatch();
119456
+ this.recordAnswerEvent("answer_snapshot");
119457
+ this.schedulePatch({ immediate: !this.immediatePatchStarted });
119394
119458
  }
119395
119459
  }
119396
119460
  async drain() {
@@ -119449,8 +119513,71 @@ var LiveCardKitProgressHandle = class {
119449
119513
  this.pendingPatch = null;
119450
119514
  }
119451
119515
  }
119452
- schedulePatch() {
119453
- if (this.pendingPatch || this.progressUpdates >= this.maxProgressUpdates) return;
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
+ }
119454
119581
  this.pendingPatch = setTimeout(() => {
119455
119582
  this.pendingPatch = null;
119456
119583
  void this.patchProgress();
@@ -119458,9 +119585,8 @@ var LiveCardKitProgressHandle = class {
119458
119585
  this.pendingPatch.unref?.();
119459
119586
  }
119460
119587
  async patchProgress() {
119461
- if (this.closed || this.progressUpdates >= this.maxProgressUpdates) return;
119588
+ if (this.closed || this.metrics.progressUpdateCount >= this.maxProgressUpdates) return;
119462
119589
  if (!this.answerBuffer) return;
119463
- this.progressUpdates += 1;
119464
119590
  this.inFlight = this.inFlight.then(
119465
119591
  () => this.withAnswerElement(this.answerBuffer).then(
119466
119592
  () => this.next(
@@ -119470,7 +119596,21 @@ var LiveCardKitProgressHandle = class {
119470
119596
  })
119471
119597
  )
119472
119598
  )
119473
- ).catch((err) => {
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();
119474
119614
  console.warn("[cardkit_progress] progress update failed (continuing):", err);
119475
119615
  });
119476
119616
  await this.inFlight;
@@ -119496,6 +119636,9 @@ var LiveCardKitProgressHandle = class {
119496
119636
  await fn(this.sequence);
119497
119637
  await this.onSequenceCommitted?.(this.sequence);
119498
119638
  }
119639
+ emitLiveMetrics() {
119640
+ this.onLiveMetricsChanged?.({ ...this.metrics, sequence: this.sequence });
119641
+ }
119499
119642
  };
119500
119643
  async function createCardKitProgressHandle(opts) {
119501
119644
  const key = idempotencyKey(opts.facts);
@@ -119529,7 +119672,8 @@ async function createCardKitProgressHandle(opts) {
119529
119672
  idempotencyKey: key,
119530
119673
  patchIntervalMs: opts.patchIntervalMs ?? DEFAULT_PATCH_INTERVAL_MS,
119531
119674
  maxProgressUpdates: opts.maxProgressUpdates ?? DEFAULT_MAX_PROGRESS_UPDATES,
119532
- onSequenceCommitted: opts.onSequenceCommitted
119675
+ onSequenceCommitted: opts.onSequenceCommitted,
119676
+ onLiveMetricsChanged: opts.onLiveMetricsChanged
119533
119677
  });
119534
119678
  }
119535
119679
  async function finalizeExistingCardKitCard(opts) {
@@ -120603,6 +120747,7 @@ var ResponseSurfacePostBudget = class {
120603
120747
  };
120604
120748
 
120605
120749
  // src/bridge/handler.ts
120750
+ var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
120606
120751
  function execGit(cwd, args) {
120607
120752
  return new Promise((resolve2, reject) => {
120608
120753
  const child = child_process.spawn("git", args, {
@@ -121157,6 +121302,7 @@ var BridgeHandler = class {
121157
121302
  } catch (err) {
121158
121303
  console.warn("[bridge.handler] ensureStateFile failed (continuing):", err);
121159
121304
  }
121305
+ let cardKitRecordWrite = Promise.resolve();
121160
121306
  const updateCardKitRecord = async (patch) => {
121161
121307
  if (!cardKitRecord) return;
121162
121308
  cardKitRecord = {
@@ -121164,7 +121310,16 @@ var BridgeHandler = class {
121164
121310
  ...patch,
121165
121311
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
121166
121312
  };
121167
- await writeCardKitFile(worktreePath, cardKitRecord);
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
+ });
121168
121323
  };
121169
121324
  if (!card && cardKitAvailable && this.deps.cardKitClient) {
121170
121325
  try {
@@ -121180,7 +121335,8 @@ var BridgeHandler = class {
121180
121335
  initialStatusText: "\u52AA\u529B\u56DE\u7B54\u4E2D...",
121181
121336
  onSequenceCommitted: async (sequence) => {
121182
121337
  await updateCardKitRecord({ status: "streaming", sequence });
121183
- }
121338
+ },
121339
+ onLiveMetricsChanged: updateCardKitLiveMetrics
121184
121340
  });
121185
121341
  cardKitRecord = {
121186
121342
  surface: "cardkit_stream",
@@ -121195,6 +121351,7 @@ var BridgeHandler = class {
121195
121351
  replyInThread,
121196
121352
  idempotencyKey: cardKitProgress.idempotencyKey,
121197
121353
  sequence: cardKitProgress.sequence,
121354
+ live: cardKitProgress.liveMetrics,
121198
121355
  elements: {
121199
121356
  footer: { elementId: "footer_md" },
121200
121357
  final: { elementId: "final_md" }
@@ -121362,7 +121519,12 @@ var BridgeHandler = class {
121362
121519
  });
121363
121520
  const backend = this.deps.botConfig?.backend ?? "claude";
121364
121521
  const permissionMode = this.deps.permissionMode ?? "bypassPermissions";
121365
- const timeoutMs = this.deps.subprocessTimeoutMs ?? 60 * 60 * 1e3;
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();
121366
121528
  const handle = createRunner(backend).run({
121367
121529
  prompt,
121368
121530
  resumeSessionId: currentExisting?.sessionId,
@@ -121390,6 +121552,7 @@ var BridgeHandler = class {
121390
121552
  }
121391
121553
  }
121392
121554
  const result = await handle.done;
121555
+ const cardKitTurnTimedOut = cardKitProgress != null && result.exitCode !== 0 && Date.now() - runnerStartedAt >= timeoutMs;
121393
121556
  const rawReportedState = await readStateFile(worktreePath);
121394
121557
  const reportedState = rawReportedState?.updated_at != null && rawReportedState.updated_at !== preRunUpdatedAt ? rawReportedState : null;
121395
121558
  const now = Date.now();
@@ -121419,6 +121582,7 @@ var BridgeHandler = class {
121419
121582
  }
121420
121583
  const reportedStatus = reportedState?.status;
121421
121584
  const reportedError = reportedState?.error;
121585
+ const cardKitTimeoutFailure = cardKitTurnTimedOut && reportedStatus !== "ready" && reportedStatus !== "failed";
121422
121586
  let success;
121423
121587
  let failureReason;
121424
121588
  if (reportedStatus === "failed") {
@@ -121426,6 +121590,9 @@ var BridgeHandler = class {
121426
121590
  failureReason = reportedError ?? "bot \u62A5\u544A failed (\u65E0 error \u5B57\u6BB5)";
121427
121591
  } else if (reportedStatus === "ready") {
121428
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`;
121429
121596
  } else if (result.exitCode === 0) {
121430
121597
  success = true;
121431
121598
  } else {
@@ -121433,14 +121600,14 @@ var BridgeHandler = class {
121433
121600
  failureReason = `claude exited ${result.exitCode} \u4E14 bot \u672A\u66F4\u65B0 state.json status \u2014 \u53EF\u80FD\u5D29\u6E83`;
121434
121601
  }
121435
121602
  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");
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");
121437
121604
  const noReportThisTurn = reportedState === null;
121438
121605
  const neutralTitle = noReportThisTurn && success && !failureReason ? "\u{1F4AC} \u5DF2\u56DE\u590D" : void 0;
121439
121606
  const baseCardPayload = {
121440
121607
  finalText: cardBody,
121441
121608
  success,
121442
121609
  failureReason,
121443
- titleOverride: reportedState?.card_title ?? neutralTitle,
121610
+ titleOverride: reportedState?.card_title ?? (cardKitTimeoutFailure ? "\u5DF2\u4E2D\u65AD" : neutralTitle),
121444
121611
  colorOverride: reportedState?.card_color ?? (neutralTitle ? "neutral" : void 0),
121445
121612
  // V2 dynamic-choice buttons — agent-declared, rendered verbatim.
121446
121613
  // reportedState is null when state.json wasn't freshly written
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.24",
3
+ "version": "0.3.25",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",