larkway 0.3.32 → 0.3.33

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.30**
11
+ **Current release: v0.3.33**
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.30**
11
+ **当前版本:v0.3.33**
12
12
 
13
13
  ---
14
14
 
package/dist/main.js CHANGED
@@ -119133,7 +119133,30 @@ var StateFileSchema = external_exports.object({
119133
119133
  * ignored and the legacy card path remains authoritative.
119134
119134
  */
119135
119135
  response_surface: ResponseSurfaceStateSchema.optional(),
119136
- updated_at: external_exports.string()
119136
+ /**
119137
+ * Freshness signal for the handler's stale-guard (it compares this against a
119138
+ * pre-run snapshot to decide whether the bot rewrote state THIS turn).
119139
+ *
119140
+ * **OPTIONAL by thin-channel principle.** This used to be the ONE hard-required
119141
+ * field, so a bot that wrote a perfectly good `status` + `last_message` but
119142
+ * omitted `updated_at` had its ENTIRE state discarded → the card stranded on
119143
+ * "本轮没有拿到 agent 的新回复" every single turn (2026-07-02 排障: one shared
119144
+ * topic reproduced this on every @). Same failure class as the old
119145
+ * `z.string().url()` mr_url and the strict `stage`/`card_color` enums.
119146
+ *
119147
+ * When absent, {@link readStateFileDetailed} server-stamps it from the file's
119148
+ * mtime — which advances only on a real rewrite, so the stale-guard stays
119149
+ * correct even for bots that never write the field.
119150
+ */
119151
+ updated_at: external_exports.string().optional()
119152
+ });
119153
+ var SalvageStateSchema = external_exports.object({
119154
+ status: external_exports.enum(["in_progress", "ready", "failed"]),
119155
+ last_message: external_exports.string().optional().catch(void 0),
119156
+ error: external_exports.string().optional().catch(void 0),
119157
+ card_title: external_exports.string().optional().catch(void 0),
119158
+ card_text: external_exports.string().optional().catch(void 0),
119159
+ updated_at: external_exports.string().optional().catch(void 0)
119137
119160
  });
119138
119161
  function stateDirOf(worktreePath) {
119139
119162
  return path6.join(worktreePath, ".larkway");
@@ -119171,6 +119194,11 @@ async function readStateFileDetailed(worktreePath) {
119171
119194
  console.warn(`[stateFile] read ${file} failed:`, err);
119172
119195
  return { state: null, diagnostics: [] };
119173
119196
  }
119197
+ let mtimeIso;
119198
+ try {
119199
+ mtimeIso = (await fs3.stat(file)).mtime.toISOString();
119200
+ } catch {
119201
+ }
119174
119202
  let parsed;
119175
119203
  try {
119176
119204
  parsed = JSON.parse(raw);
@@ -119195,18 +119223,39 @@ async function readStateFileDetailed(worktreePath) {
119195
119223
  }
119196
119224
  }
119197
119225
  const result = StateFileSchema.safeParse(parsed);
119198
- if (!result.success) {
119199
- console.warn(
119200
- `[stateFile] ${file} failed schema validation:`,
119201
- result.error.issues
119202
- );
119203
- return { state: null, diagnostics: [] };
119204
- }
119205
- const diagnostics = diagnoseStateFile(parsed, result.data);
119206
- for (const diagnostic of diagnostics) {
119226
+ if (result.success) {
119227
+ const state = withStampedUpdatedAt(result.data, mtimeIso);
119228
+ const diagnostics = diagnoseStateFile(parsed, result.data);
119229
+ for (const diagnostic of diagnostics) {
119230
+ console.warn(`[stateFile] ${file}: ${diagnostic}`);
119231
+ }
119232
+ return { state, diagnostics };
119233
+ }
119234
+ const salvaged = SalvageStateSchema.safeParse(parsed);
119235
+ if (salvaged.success) {
119236
+ const droppedFields = summarizeRejectedTopFields(result.error);
119237
+ const state = withStampedUpdatedAt(salvaged.data, mtimeIso);
119238
+ const diagnostic = `state.json \u90E8\u5206\u5B57\u6BB5\u65E0\u6548\u5DF2\u5FFD\u7565\uFF0C\u4FDD\u7559 status/last_message\uFF08\u53D7\u5F71\u54CD\u5B57\u6BB5\uFF1A${droppedFields}\uFF09`;
119207
119239
  console.warn(`[stateFile] ${file}: ${diagnostic}`);
119240
+ return { state, diagnostics: [diagnostic] };
119241
+ }
119242
+ console.warn(
119243
+ `[stateFile] ${file} failed schema validation (unsalvageable):`,
119244
+ result.error.issues
119245
+ );
119246
+ return { state: null, diagnostics: [] };
119247
+ }
119248
+ function withStampedUpdatedAt(state, mtimeIso) {
119249
+ if (state.updated_at != null && state.updated_at !== "") return state;
119250
+ return { ...state, updated_at: mtimeIso ?? (/* @__PURE__ */ new Date()).toISOString() };
119251
+ }
119252
+ function summarizeRejectedTopFields(error) {
119253
+ const fields = /* @__PURE__ */ new Set();
119254
+ for (const issue of error.issues) {
119255
+ const top = issue.path[0];
119256
+ fields.add(top === void 0 ? "(root)" : String(top));
119208
119257
  }
119209
- return { state: result.data, diagnostics };
119258
+ return [...fields].join(", ") || "(unknown)";
119210
119259
  }
119211
119260
  function diagnoseStateFile(parsed, state) {
119212
119261
  if (parsed === null || typeof parsed !== "object") return [];
@@ -120952,7 +121001,9 @@ var BridgeHandler = class {
120952
121001
  });
120953
121002
  }
120954
121003
  const fallbackAnswer = trustedAnswerText.trim() || cardKitProgress?.answerText.trim() || "";
120955
- const cardBody = cardKitTimeoutFailure ? "\u26A0\uFE0F \u672C\u8F6E\u88AB\u4E2D\u65AD\uFF08\u957F\u65F6\u95F4\u65E0\u6D3B\u6027\uFF0C\u5224\u5B9A\u5361\u6B7B\uFF09\uFF0C\u672A\u5B8C\u6210\u3002\u8BF7\u91CD\u8BD5\u3002" : reportedState?.last_message ?? (fallbackAnswer ? fallbackAnswer : "\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");
121004
+ const noOutputFallback = "\u26A0\uFE0F \u672C\u8F6E agent \u6CA1\u6709\u4EA7\u51FA\u6B63\u6587\uFF0C\u4E5F\u6CA1\u6709\u5199\u5165\u6709\u6548\u72B6\u6001(state.json)\u3002\n" + (result.exitCode !== 0 ? `agent \u8FDB\u7A0B\u5F02\u5E38\u9000\u51FA(exit code ${result.exitCode})\u3002
121005
+ ` : "") + "\u4E0B\u4E00\u6B65\uFF1A\u6362\u4E2A\u8BF4\u6CD5\u91CD\u8BD5\uFF0C\u6216\u65B0\u5F00\u4E00\u4E2A\u8BDD\u9898\u7EE7\u7EED\uFF1B\u82E5\u53CD\u590D\u5982\u6B64\uFF0C\u8BF7\u8BA9\u7EF4\u62A4\u8005\u67E5\u770B\u8BE5 session \u65E5\u5FD7\u3002";
121006
+ const cardBody = cardKitTimeoutFailure ? "\u26A0\uFE0F \u672C\u8F6E\u88AB\u4E2D\u65AD\uFF08\u957F\u65F6\u95F4\u65E0\u6D3B\u6027\uFF0C\u5224\u5B9A\u5361\u6B7B\uFF09\uFF0C\u672A\u5B8C\u6210\u3002\u8BF7\u91CD\u8BD5\u3002" : reportedState?.last_message ?? (fallbackAnswer ? fallbackAnswer : noOutputFallback);
120956
121007
  const noReportThisTurn = reportedState === null;
120957
121008
  const neutralTitle = noReportThisTurn && success && !failureReason ? "\u{1F4AC} \u5DF2\u56DE\u590D" : void 0;
120958
121009
  const baseCardPayload = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.32",
3
+ "version": "0.3.33",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",