larkway 0.3.29 → 0.3.30

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.29**
11
+ **Current release: v0.3.30**
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.29**
11
+ **当前版本:v0.3.30**
12
12
 
13
13
  ---
14
14
 
package/dist/cli/index.js CHANGED
@@ -124829,6 +124829,9 @@ import path from "node:path";
124829
124829
 
124830
124830
  // src/responseSurface.ts
124831
124831
  var NonEmptyString = external_exports.string().trim().min(1);
124832
+ var CardKitMentionUserIdSchema = NonEmptyString.max(128).regex(/^[A-Za-z0-9_:-]+$/, {
124833
+ message: "mention user_id must contain only letters, numbers, underscore, colon, or hyphen"
124834
+ });
124832
124835
  function defaultResponseSurfacePrototypeConfig() {
124833
124836
  return {
124834
124837
  enabled: true,
@@ -124838,6 +124841,7 @@ function defaultResponseSurfacePrototypeConfig() {
124838
124841
  post_outbound_enabled: true,
124839
124842
  cardkit_streaming_enabled: true,
124840
124843
  allow_agent_mentions: true,
124844
+ denied_mention_open_ids: [],
124841
124845
  allowed_mention_open_ids: []
124842
124846
  };
124843
124847
  }
@@ -124850,6 +124854,7 @@ var responseSurfacePrototypeConfigDefaults = () => ({
124850
124854
  post_outbound_enabled: true,
124851
124855
  cardkit_streaming_enabled: true,
124852
124856
  allow_agent_mentions: true,
124857
+ denied_mention_open_ids: [],
124853
124858
  allowed_mention_open_ids: []
124854
124859
  });
124855
124860
  var ResponseSurfaceModeSchema = external_exports.enum(["card", "post", "hybrid"]);
@@ -124862,7 +124867,7 @@ var ResponseSurfaceCapabilitySchema = external_exports.enum([
124862
124867
  "audit"
124863
124868
  ]);
124864
124869
  var MentionTargetSchema = external_exports.object({
124865
- user_id: NonEmptyString.max(128),
124870
+ user_id: CardKitMentionUserIdSchema,
124866
124871
  label: external_exports.string().trim().min(1).max(80).optional()
124867
124872
  });
124868
124873
  var ResponseSurfacePostSchema = external_exports.object({
@@ -124873,7 +124878,7 @@ var ResponseSurfaceCardSchema = external_exports.object({
124873
124878
  capabilities: external_exports.array(ResponseSurfaceCapabilitySchema).max(8).default([])
124874
124879
  });
124875
124880
  var StrictResponseSurfaceStateSchema = external_exports.object({
124876
- mode: ResponseSurfaceModeSchema,
124881
+ mode: ResponseSurfaceModeSchema.optional().default("card"),
124877
124882
  primary: ResponseSurfacePrimarySchema.optional(),
124878
124883
  post: ResponseSurfacePostSchema.optional(),
124879
124884
  card: ResponseSurfaceCardSchema.optional()
@@ -124888,9 +124893,18 @@ var StrictResponseSurfaceStateSchema = external_exports.object({
124888
124893
  });
124889
124894
  var ResponseSurfaceStateSchema = external_exports.preprocess((value) => {
124890
124895
  if (value === void 0) return void 0;
124891
- const result = StrictResponseSurfaceStateSchema.safeParse(value);
124892
- return result.success ? result.data : void 0;
124896
+ return parseResponseSurfaceState(value).state;
124893
124897
  }, StrictResponseSurfaceStateSchema.optional());
124898
+ function parseResponseSurfaceState(value) {
124899
+ const result = StrictResponseSurfaceStateSchema.safeParse(value);
124900
+ if (result.success) return { state: result.data, diagnostics: [] };
124901
+ return {
124902
+ diagnostics: result.error.issues.map((issue) => {
124903
+ const path25 = issue.path.length > 0 ? issue.path.join(".") : "<root>";
124904
+ return `${path25}: ${issue.message}`;
124905
+ })
124906
+ };
124907
+ }
124894
124908
  var ResponseSurfacePrototypeConfigSchema = external_exports.object({
124895
124909
  /**
124896
124910
  * Master gate. Default true makes response surfaces available unless the
@@ -124931,10 +124945,16 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
124931
124945
  */
124932
124946
  allow_agent_mentions: external_exports.boolean().default(true),
124933
124947
  /**
124934
- * Optional target allowlist for real post @ mentions. Empty means the Agent
124935
- * may choose mention targets; non-empty narrows mentions to this exact set.
124948
+ * Optional deny-list for Agent-authored @ mentions. Empty means the Agent
124949
+ * may choose mention targets, except broadcast aliases such as @all.
124936
124950
  * Keep real IDs in private bot config, never in public docs/tests.
124937
124951
  */
124952
+ denied_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([]),
124953
+ /**
124954
+ * Deprecated compatibility field. It is still parsed so older private
124955
+ * configs do not fail, but mention policy is deny-list/default-allow.
124956
+ * Use denied_mention_open_ids for new restrictions.
124957
+ */
124938
124958
  allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([])
124939
124959
  }).default(responseSurfacePrototypeConfigDefaults);
124940
124960
 
package/dist/main.js CHANGED
@@ -118687,7 +118687,7 @@ function renderStateContract(stateFilePath) {
118687
118687
  "- 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",
118688
118688
  "- 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",
118689
118689
  '- 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',
118690
- "- response_surface: \u53EF\u9009\u8986\u76D6\u5B57\u6BB5,\u4E3B\u8981\u7528\u4E8E `{post:{mentions:[{user_id,label?}]}}` late peer @\u3002\u9ED8\u8BA4\u53EF\u4E0D\u5199;bridge \u6309 CardKit \u6D41\u5F0F\u5361\u7247\u5904\u7406,\u6700\u7EC8\u6536\u6210\u5E72\u51C0\u603B\u7ED3\u5361\u3002\u65E7 `mode`/`primary` \u4EC5\u517C\u5BB9\u89E3\u6790,\u4E0D\u518D\u9009\u62E9 post-only/hybrid \u4E3B\u54CD\u5E94\u9762\u3002\u9700\u8981\u771F\u5B9E @ \u65F6\u628A\u76EE\u6807\u5199\u5165 `post.mentions`;\u4E0D\u8981\u5199 raw Feishu post/card JSON\u3002",
118690
+ "- response_surface: \u53EF\u9009\u8986\u76D6\u5B57\u6BB5,\u4E3B\u8981\u7528\u4E8E `{post:{mentions:[{user_id,label?}]}}` late peer @\u3002\u9ED8\u8BA4\u53EF\u4E0D\u5199;bridge \u6309 CardKit \u6D41\u5F0F\u5361\u7247\u5904\u7406,\u6700\u7EC8\u6536\u6210\u5E72\u51C0\u603B\u7ED3\u5361\u3002\u65E7 `mode`/`primary` \u4EC5\u517C\u5BB9\u89E3\u6790,\u4E0D\u518D\u9009\u62E9 post-only/hybrid \u4E3B\u54CD\u5E94\u9762\u3002\u8FD9\u91CC\u7684 late @ \u53EA\u662F\u6700\u7EC8\u5361\u7247\u91CC\u7684\u89C6\u89C9\u63D0\u793A;\u9700\u8981 peer bot \u6D88\u8D39\u6B63\u6587\u7684 handoff \u5FC5\u987B\u7531 Agent/\u56E2\u961F\u5DE5\u4F5C\u6D41\u53D1\u9001\u771F\u5B9E Feishu post + at \u6807\u7B7E\u3002\u4E0D\u8981\u5199 raw Feishu post/card JSON\u3002",
118691
118691
  "- scheduled reply / daily social ops review card \u7B49\u9700\u8981\u201C\u5E73\u53F0\u6B63\u6587 + \u5339\u914D\u56FE\u7247\u201D\u540C\u6BB5\u76F8\u90BB\u5C55\u793A\u7684\u573A\u666F,\u5E94\u5148\u53D6\u5F97\u5404\u56FE\u7247 `img_key`,\u518D\u5199 `content_blocks` \u4E3A `\u5E73\u53F0 markdown -> \u5BF9\u5E94 image -> \u4E0B\u4E2A\u5E73\u53F0 markdown -> \u5BF9\u5E94 image`;\u4E0D\u8981\u7528\u5355\u72EC\u8BDD\u9898\u56FE\u7247\u6D88\u606F\u6216\u5C3E\u90E8 `image_blocks` \u4EE3\u66FF\u9A8C\u6536\u9762\u3002",
118692
118692
  "- dev_url / mr_url / \u5176\u4F59\u4E1A\u52A1\u5B57\u6BB5:\u81EA\u7531\u5199\u5165,bridge \u4E0D\u611F\u77E5\u5176\u4E1A\u52A1\u542B\u4E49;\u8981\u8BA9\u8FD0\u8425\u770B\u5230,\u8BF7\u5199\u8FDB last_message",
118693
118693
  "- updated_at: ISO 8601 timestamp",
@@ -118738,6 +118738,9 @@ function renderPeersBlock(peers) {
118738
118738
  "- \u4E0D\u8981\u628A\u540C\u4E00\u4EFB\u52A1\u540C\u65F6\u8F6C\u53D1\u7ED9\u591A\u4E2A peer",
118739
118739
  '- @ peer \u5FC5\u987B\u7528 **post \u6D88\u606F** + at \u6807\u7B7E `{"tag":"at","user_id":"ou_xxx"}`(\u7528\u4E0A\u9762\u7684 open_id),',
118740
118740
  " **\u4E25\u7981\u7528\u7EAF text \u7684 @xxx**(\u7EAF\u6587\u672C @ \u4E0D\u4F1A\u771F\u6B63\u89E6\u8FBE\u5BF9\u65B9 bot)",
118741
+ "- \u53D1\u8D77 peer handoff \u540E,\u5728\u4F60\u7684\u534F\u8C03\u5C42/\u5DE5\u4F5C\u533A\u53F0\u8D26\u8BB0\u5F55 task_id\u3001assignee\u3001\u6765\u6E90\u3001\u671F\u671B\u4EA7\u51FA\u3001deadline \u548C\u5347\u7EA7\u4EBA;\u6CA1\u6709\u4E13\u7528 skill \u65F6\u81F3\u5C11\u5199\u5165\u672C session summary\u3002",
118742
+ "- \u6536\u5230 peer handoff \u540E,\u5148\u7528\u771F\u5B9E post \u8F7B\u91CF ack(\u6536\u5230/\u5F00\u59CB)\u518D\u505A\u957F\u4EFB\u52A1;\u5B8C\u6210\u3001\u5931\u8D25\u6216\u963B\u585E\u65F6\u5FC5\u987B\u7528\u771F\u5B9E post \u56DE\u62A5\u7EC8\u6001,\u4E0D\u8981\u8BA9\u94FE\u8DEF\u9759\u9ED8\u505C\u5728\u4F60\u8FD9\u91CC\u3002",
118743
+ "- \u9ED8\u8BA4 deadline \u53EF\u6309\u56E2\u961F\u5DE5\u4F5C\u6D41\u8BBE\u7F6E(\u5E38\u89C1\u9ED8\u8BA4 15 \u5206\u949F);\u8D85\u65F6\u68C0\u6D4B\u3001\u91CD\u8BD5/\u91CD\u6D3E/\u5347\u7EA7\u5C5E\u4E8E\u534F\u8C03\u5C42 skill/workspace \u903B\u8F91,\u4E0D\u8981\u671F\u5F85 bridge \u66FF\u4F60\u7F16\u6392\u3002",
118741
118744
  "</peer-bots>"
118742
118745
  ];
118743
118746
  }
@@ -119494,6 +119497,9 @@ import path6 from "node:path";
119494
119497
 
119495
119498
  // src/responseSurface.ts
119496
119499
  var NonEmptyString = external_exports.string().trim().min(1);
119500
+ var CardKitMentionUserIdSchema = NonEmptyString.max(128).regex(/^[A-Za-z0-9_:-]+$/, {
119501
+ message: "mention user_id must contain only letters, numbers, underscore, colon, or hyphen"
119502
+ });
119497
119503
  function defaultResponseSurfacePrototypeConfig() {
119498
119504
  return {
119499
119505
  enabled: true,
@@ -119503,6 +119509,7 @@ function defaultResponseSurfacePrototypeConfig() {
119503
119509
  post_outbound_enabled: true,
119504
119510
  cardkit_streaming_enabled: true,
119505
119511
  allow_agent_mentions: true,
119512
+ denied_mention_open_ids: [],
119506
119513
  allowed_mention_open_ids: []
119507
119514
  };
119508
119515
  }
@@ -119515,6 +119522,7 @@ var responseSurfacePrototypeConfigDefaults = () => ({
119515
119522
  post_outbound_enabled: true,
119516
119523
  cardkit_streaming_enabled: true,
119517
119524
  allow_agent_mentions: true,
119525
+ denied_mention_open_ids: [],
119518
119526
  allowed_mention_open_ids: []
119519
119527
  });
119520
119528
  var ResponseSurfaceModeSchema = external_exports.enum(["card", "post", "hybrid"]);
@@ -119527,7 +119535,7 @@ var ResponseSurfaceCapabilitySchema = external_exports.enum([
119527
119535
  "audit"
119528
119536
  ]);
119529
119537
  var MentionTargetSchema = external_exports.object({
119530
- user_id: NonEmptyString.max(128),
119538
+ user_id: CardKitMentionUserIdSchema,
119531
119539
  label: external_exports.string().trim().min(1).max(80).optional()
119532
119540
  });
119533
119541
  var ResponseSurfacePostSchema = external_exports.object({
@@ -119538,7 +119546,7 @@ var ResponseSurfaceCardSchema = external_exports.object({
119538
119546
  capabilities: external_exports.array(ResponseSurfaceCapabilitySchema).max(8).default([])
119539
119547
  });
119540
119548
  var StrictResponseSurfaceStateSchema = external_exports.object({
119541
- mode: ResponseSurfaceModeSchema,
119549
+ mode: ResponseSurfaceModeSchema.optional().default("card"),
119542
119550
  primary: ResponseSurfacePrimarySchema.optional(),
119543
119551
  post: ResponseSurfacePostSchema.optional(),
119544
119552
  card: ResponseSurfaceCardSchema.optional()
@@ -119553,9 +119561,18 @@ var StrictResponseSurfaceStateSchema = external_exports.object({
119553
119561
  });
119554
119562
  var ResponseSurfaceStateSchema = external_exports.preprocess((value) => {
119555
119563
  if (value === void 0) return void 0;
119556
- const result = StrictResponseSurfaceStateSchema.safeParse(value);
119557
- return result.success ? result.data : void 0;
119564
+ return parseResponseSurfaceState(value).state;
119558
119565
  }, StrictResponseSurfaceStateSchema.optional());
119566
+ function parseResponseSurfaceState(value) {
119567
+ const result = StrictResponseSurfaceStateSchema.safeParse(value);
119568
+ if (result.success) return { state: result.data, diagnostics: [] };
119569
+ return {
119570
+ diagnostics: result.error.issues.map((issue) => {
119571
+ const path16 = issue.path.length > 0 ? issue.path.join(".") : "<root>";
119572
+ return `${path16}: ${issue.message}`;
119573
+ })
119574
+ };
119575
+ }
119559
119576
  var ResponseSurfacePrototypeConfigSchema = external_exports.object({
119560
119577
  /**
119561
119578
  * Master gate. Default true makes response surfaces available unless the
@@ -119596,10 +119613,16 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
119596
119613
  */
119597
119614
  allow_agent_mentions: external_exports.boolean().default(true),
119598
119615
  /**
119599
- * Optional target allowlist for real post @ mentions. Empty means the Agent
119600
- * may choose mention targets; non-empty narrows mentions to this exact set.
119616
+ * Optional deny-list for Agent-authored @ mentions. Empty means the Agent
119617
+ * may choose mention targets, except broadcast aliases such as @all.
119601
119618
  * Keep real IDs in private bot config, never in public docs/tests.
119602
119619
  */
119620
+ denied_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([]),
119621
+ /**
119622
+ * Deprecated compatibility field. It is still parsed so older private
119623
+ * configs do not fail, but mention policy is deny-list/default-allow.
119624
+ * Use denied_mention_open_ids for new restrictions.
119625
+ */
119603
119626
  allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([])
119604
119627
  }).default(responseSurfacePrototypeConfigDefaults);
119605
119628
  function isResponseSurfacePrototypeAllowlisted(config, facts) {
@@ -119611,12 +119634,30 @@ function isResponseSurfacePrototypeAllowlisted(config, facts) {
119611
119634
  const threadAllowed = config.allowed_threads.length > 0 && config.allowed_threads.includes(facts.threadId);
119612
119635
  return chatAllowed || threadAllowed;
119613
119636
  }
119614
- function isResponseSurfaceMentionAllowed(config, userId) {
119615
- if (!config?.allow_agent_mentions) return false;
119637
+ function evaluateResponseSurfaceMentionPolicy(config, userId) {
119638
+ if (!config?.allow_agent_mentions) {
119639
+ return {
119640
+ allowed: false,
119641
+ rule: "agent_mentions_disabled",
119642
+ reason: "Agent-authored mentions are disabled by allow_agent_mentions=false."
119643
+ };
119644
+ }
119616
119645
  const normalized = userId.trim().toLowerCase();
119617
- if (normalized === "all" || normalized === "@all") return false;
119618
- if (config.allowed_mention_open_ids.length === 0) return true;
119619
- return config.allowed_mention_open_ids.includes(userId);
119646
+ if (normalized === "all" || normalized === "@all") {
119647
+ return {
119648
+ allowed: false,
119649
+ rule: "broadcast_blocked",
119650
+ reason: "Broadcast mentions such as @all are blocked."
119651
+ };
119652
+ }
119653
+ if (config.denied_mention_open_ids.includes(userId)) {
119654
+ return {
119655
+ allowed: false,
119656
+ rule: "denied_target",
119657
+ reason: "Mention target is denied by denied_mention_open_ids."
119658
+ };
119659
+ }
119660
+ return { allowed: true, rule: "allowed" };
119620
119661
  }
119621
119662
  function shouldProvideResponseSurfacePostClient(config) {
119622
119663
  return !!(config?.enabled && !config.kill_switch && config.post_outbound_enabled);
@@ -119801,14 +119842,19 @@ async function ensureStateFile(worktreePath) {
119801
119842
  await fs3.writeFile(file, JSON.stringify(initial, null, 2), "utf8");
119802
119843
  }
119803
119844
  async function readStateFile(worktreePath) {
119845
+ return (await readStateFileDetailed(worktreePath)).state;
119846
+ }
119847
+ async function readStateFileDetailed(worktreePath) {
119804
119848
  const file = stateFilePathOf(worktreePath);
119805
119849
  let raw;
119806
119850
  try {
119807
119851
  raw = await fs3.readFile(file, "utf8");
119808
119852
  } catch (err) {
119809
- if (err.code === "ENOENT") return null;
119853
+ if (err.code === "ENOENT") {
119854
+ return { state: null, diagnostics: [] };
119855
+ }
119810
119856
  console.warn(`[stateFile] read ${file} failed:`, err);
119811
- return null;
119857
+ return { state: null, diagnostics: [] };
119812
119858
  }
119813
119859
  let parsed;
119814
119860
  try {
@@ -119826,11 +119872,11 @@ async function readStateFile(worktreePath) {
119826
119872
  `[stateFile] ${file} not valid JSON (repair also failed):`,
119827
119873
  err2
119828
119874
  );
119829
- return null;
119875
+ return { state: null, diagnostics: [] };
119830
119876
  }
119831
119877
  } else {
119832
119878
  console.warn(`[stateFile] ${file} not valid JSON:`, err);
119833
- return null;
119879
+ return { state: null, diagnostics: [] };
119834
119880
  }
119835
119881
  }
119836
119882
  const result = StateFileSchema.safeParse(parsed);
@@ -119839,9 +119885,22 @@ async function readStateFile(worktreePath) {
119839
119885
  `[stateFile] ${file} failed schema validation:`,
119840
119886
  result.error.issues
119841
119887
  );
119842
- return null;
119888
+ return { state: null, diagnostics: [] };
119843
119889
  }
119844
- return result.data;
119890
+ const diagnostics = diagnoseStateFile(parsed, result.data);
119891
+ for (const diagnostic of diagnostics) {
119892
+ console.warn(`[stateFile] ${file}: ${diagnostic}`);
119893
+ }
119894
+ return { state: result.data, diagnostics };
119895
+ }
119896
+ function diagnoseStateFile(parsed, state) {
119897
+ if (parsed === null || typeof parsed !== "object") return [];
119898
+ if (!Object.prototype.hasOwnProperty.call(parsed, "response_surface")) return [];
119899
+ const rawSurface = parsed.response_surface;
119900
+ if (rawSurface === void 0 || state.response_surface !== void 0) return [];
119901
+ const parsedSurface = parseResponseSurfaceState(rawSurface);
119902
+ const details = parsedSurface.diagnostics.length > 0 ? parsedSurface.diagnostics.join("; ") : "unknown parse failure";
119903
+ return [`response_surface ignored: ${details}`];
119845
119904
  }
119846
119905
  function tryRepairBareQuotesInLastMessage(raw) {
119847
119906
  const startRe = /"last_message"\s*:\s*"/;
@@ -120664,6 +120723,13 @@ function derivePostIdempotencyKey(input) {
120664
120723
 
120665
120724
  // src/bridge/handler.ts
120666
120725
  var DEFAULT_CARDKIT_RESPONSE_SURFACE_TIMEOUT_MS = 20 * 60 * 1e3;
120726
+ function summarizeMentionPolicyRules(rules) {
120727
+ const counts = /* @__PURE__ */ new Map();
120728
+ for (const rule of rules) {
120729
+ counts.set(rule, (counts.get(rule) ?? 0) + 1);
120730
+ }
120731
+ return Array.from(counts.entries()).map(([rule, count]) => `${rule}=${count}`).join(", ");
120732
+ }
120667
120733
  function execGit(cwd, args) {
120668
120734
  return new Promise((resolve2, reject) => {
120669
120735
  const child = child_process.spawn("git", args, {
@@ -121426,8 +121492,23 @@ var BridgeHandler = class {
121426
121492
  }
121427
121493
  const result = await handle.done;
121428
121494
  const cardKitTurnTimedOut = cardKitProgress != null && result.exitCode !== 0 && Date.now() - runnerStartedAt >= timeoutMs;
121429
- const rawReportedState = await readStateFile(worktreePath);
121495
+ const reportedStateRead = await readStateFileDetailed(worktreePath);
121496
+ const rawReportedState = reportedStateRead.state;
121430
121497
  const reportedState = rawReportedState?.updated_at != null && rawReportedState.updated_at !== preRunUpdatedAt ? rawReportedState : null;
121498
+ if (reportedStateRead.diagnostics.length > 0 && reportedState !== null) {
121499
+ await recordEvent({
121500
+ status: "running",
121501
+ appendPath: "state \u8BCA\u65AD",
121502
+ reason: reportedStateRead.diagnostics.join("; ")
121503
+ });
121504
+ }
121505
+ if (reportedState === null) {
121506
+ await recordEvent({
121507
+ status: "running",
121508
+ appendPath: "\u672A\u66F4\u65B0 state.json",
121509
+ reason: rawReportedState === null ? "\u672C\u8F6E\u7ED3\u675F\u65F6\u672A\u8BFB\u53D6\u5230 state.json\u3002" : "\u672C\u8F6E state.json \u6CA1\u6709 fresh updated_at\uFF0C\u5DF2\u5FFD\u7565\u65E7\u72B6\u6001\u3002"
121510
+ });
121511
+ }
121431
121512
  const now = Date.now();
121432
121513
  if (sessionId !== void 0 && currentExisting === void 0) {
121433
121514
  await this.deps.sessionStore.put({
@@ -121466,11 +121547,21 @@ var BridgeHandler = class {
121466
121547
  } else if (cardKitTimeoutFailure) {
121467
121548
  success = false;
121468
121549
  failureReason = `agent turn timed out after ${timeoutMs}ms; CardKit running card was finalized as interrupted`;
121550
+ await recordEvent({
121551
+ status: "running",
121552
+ appendPath: "Agent \u8D85\u65F6",
121553
+ reason: failureReason
121554
+ });
121469
121555
  } else if (result.exitCode === 0) {
121470
121556
  success = true;
121471
121557
  } else {
121472
121558
  success = false;
121473
121559
  failureReason = `claude exited ${result.exitCode} \u4E14 bot \u672A\u66F4\u65B0 state.json status \u2014 \u53EF\u80FD\u5D29\u6E83`;
121560
+ await recordEvent({
121561
+ status: "running",
121562
+ appendPath: "Agent \u5F02\u5E38\u9000\u51FA",
121563
+ reason: failureReason
121564
+ });
121474
121565
  }
121475
121566
  const fallbackAnswer = trustedAnswerText.trim() || cardKitProgress?.answerText.trim() || "";
121476
121567
  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");
@@ -121491,9 +121582,31 @@ var BridgeHandler = class {
121491
121582
  contentBlocks: reportedState?.content_blocks
121492
121583
  };
121493
121584
  if (cardKitProgress) {
121494
- const mentions = (reportedState?.response_surface?.post?.mentions ?? []).filter(
121495
- (mention) => isResponseSurfaceMentionAllowed(prototypeConfig, mention.user_id)
121496
- );
121585
+ const declaredMentions = reportedState?.response_surface?.post?.mentions ?? [];
121586
+ const responseSurfacePostDeclared = reportedState?.response_surface?.post !== void 0;
121587
+ const mentionPolicyResults = declaredMentions.map((mention) => ({
121588
+ mention,
121589
+ policy: evaluateResponseSurfaceMentionPolicy(prototypeConfig, mention.user_id)
121590
+ }));
121591
+ const mentions = mentionPolicyResults.filter(({ policy }) => policy.allowed).map(({ mention }) => mention);
121592
+ const blockedMentionRules = mentionPolicyResults.filter(({ policy }) => !policy.allowed).map(({ policy }) => policy.rule);
121593
+ if (responseSurfacePostDeclared && declaredMentions.length === 0) {
121594
+ const reason = "response_surface.post was declared with an empty mentions array.";
121595
+ console.warn("[bridge.handler] response_surface post has no mentions");
121596
+ await recordEvent({
121597
+ status: "running",
121598
+ appendPath: "mention \u8BCA\u65AD",
121599
+ reason
121600
+ });
121601
+ } else if (declaredMentions.length > mentions.length) {
121602
+ const reason = `response_surface mentions filtered by policy: ${mentions.length}/${declaredMentions.length} allowed; blocked rules: ${summarizeMentionPolicyRules(blockedMentionRules)}.`;
121603
+ console.warn("[bridge.handler] response_surface mention policy filtered targets");
121604
+ await recordEvent({
121605
+ status: "running",
121606
+ appendPath: "mention \u8BCA\u65AD",
121607
+ reason
121608
+ });
121609
+ }
121497
121610
  try {
121498
121611
  await cardKitProgress.finalize({
121499
121612
  title: baseCardPayload.titleOverride,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.29",
3
+ "version": "0.3.30",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",