larkway 0.3.19 → 0.3.21

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.19**
11
+ **Current release: v0.3.21**
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.19**
11
+ **当前版本:v0.3.21**
12
12
 
13
13
  ---
14
14
 
package/dist/cli/index.js CHANGED
@@ -111269,6 +111269,20 @@ var init_channelPostClient = __esm({
111269
111269
  }
111270
111270
  return { messageId };
111271
111271
  }
111272
+ async updatePost(messageId, content) {
111273
+ const res = await withPostRetry(
111274
+ "updatePost",
111275
+ () => this.channel().rawClient.im.v1.message.update({
111276
+ path: { message_id: messageId },
111277
+ data: {
111278
+ content,
111279
+ msg_type: "post"
111280
+ }
111281
+ }),
111282
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
111283
+ );
111284
+ return { messageId: res.data?.message_id ?? messageId };
111285
+ }
111272
111286
  };
111273
111287
  }
111274
111288
  });
@@ -123677,12 +123691,13 @@ import path from "node:path";
123677
123691
  var NonEmptyString = external_exports.string().trim().min(1);
123678
123692
  function defaultResponseSurfacePrototypeConfig() {
123679
123693
  return {
123680
- enabled: false,
123694
+ enabled: true,
123681
123695
  allowed_chats: [],
123682
123696
  allowed_threads: [],
123683
- lazy_card_creation: false,
123697
+ lazy_card_creation: true,
123684
123698
  kill_switch: false,
123685
- post_outbound_enabled: false,
123699
+ post_outbound_enabled: true,
123700
+ allow_agent_mentions: true,
123686
123701
  allowed_mention_open_ids: [],
123687
123702
  max_posts_per_turn: 1,
123688
123703
  max_posts_per_window: 4,
@@ -123693,12 +123708,13 @@ function defaultResponseSurfacePrototypeConfig() {
123693
123708
  }
123694
123709
  var DEFAULT_RESPONSE_SURFACE_PROTOTYPE = defaultResponseSurfacePrototypeConfig();
123695
123710
  var responseSurfacePrototypeConfigDefaults = () => ({
123696
- enabled: false,
123711
+ enabled: true,
123697
123712
  allowed_chats: [],
123698
123713
  allowed_threads: [],
123699
- lazy_card_creation: false,
123714
+ lazy_card_creation: true,
123700
123715
  kill_switch: false,
123701
- post_outbound_enabled: false,
123716
+ post_outbound_enabled: true,
123717
+ allow_agent_mentions: true,
123702
123718
  allowed_mention_open_ids: [],
123703
123719
  max_posts_per_turn: 1,
123704
123720
  max_posts_per_window: 4,
@@ -123747,40 +123763,46 @@ var ResponseSurfaceStateSchema = external_exports.preprocess((value) => {
123747
123763
  }, StrictResponseSurfaceStateSchema.optional());
123748
123764
  var ResponseSurfacePrototypeConfigSchema = external_exports.object({
123749
123765
  /**
123750
- * Master gate. Default false keeps the legacy card-only response path.
123766
+ * Master gate. Default true makes response surfaces available unless the
123767
+ * runtime kill switch disables them.
123751
123768
  */
123752
- enabled: external_exports.boolean().default(false),
123769
+ enabled: external_exports.boolean().default(true),
123753
123770
  /**
123754
- * Optional chat allowlist for dark-launch experiments. Empty means no chat
123755
- * is allowlisted by this gate.
123771
+ * Optional chat allowlist for staged rollout. Empty means all chats are
123772
+ * allowed by this gate.
123756
123773
  */
123757
123774
  allowed_chats: external_exports.array(external_exports.string().min(1)).default([]),
123758
123775
  /**
123759
- * Optional Larkway session/thread allowlist for narrow dogfood topics.
123760
- * Empty means no thread is allowlisted by this gate.
123776
+ * Optional Larkway session/thread allowlist for staged rollout. Empty means
123777
+ * all threads are allowed by this gate.
123761
123778
  */
123762
123779
  allowed_threads: external_exports.array(external_exports.string().min(1)).default([]),
123763
123780
  /**
123764
- * Dark-launch hook for PR2. This does not enable post-only output by itself;
123765
- * the bridge still requires a visible card fallback until post outbound
123766
- * exists and is explicitly enabled in a later PR.
123781
+ * Post-first gate. Default true means the bridge starts a lightweight post
123782
+ * as the live main surface when post outbound is available, and creates a
123783
+ * card only for fallback or card-only capabilities.
123767
123784
  */
123768
- lazy_card_creation: external_exports.boolean().default(false),
123785
+ lazy_card_creation: external_exports.boolean().default(true),
123769
123786
  /**
123770
123787
  * Runtime kill switch for emergency rollback. When true, every response
123771
123788
  * surface post path is treated as disabled even if enabled/allowlists are
123772
- * otherwise configured. Default false preserves current default-off behavior.
123789
+ * otherwise configured.
123773
123790
  */
123774
123791
  kill_switch: external_exports.boolean().default(false),
123775
123792
  /**
123776
- * PR3 dark-launch gate for real post outbound. This must stay false by
123777
- * default. PR4's dispatcher also requires this gate before any post path.
123793
+ * Gate for real post outbound. Defaults on, but the runtime still requires
123794
+ * an injected post client and all safety gates before any post path.
123795
+ */
123796
+ post_outbound_enabled: external_exports.boolean().default(true),
123797
+ /**
123798
+ * Allows Agent-authored post mentions. This powers handoff to peer bots.
123799
+ * Keep this false only when the operator wants to suppress every real @.
123778
123800
  */
123779
- post_outbound_enabled: external_exports.boolean().default(false),
123801
+ allow_agent_mentions: external_exports.boolean().default(true),
123780
123802
  /**
123781
- * Explicit target allowlist for future real post @ mentions. Empty means no
123782
- * mention target is authorized. Keep real IDs in private bot config, never
123783
- * in public docs/tests.
123803
+ * Optional target allowlist for real post @ mentions. Empty means the Agent
123804
+ * may choose mention targets; non-empty narrows mentions to this exact set.
123805
+ * Keep real IDs in private bot config, never in public docs/tests.
123784
123806
  */
123785
123807
  allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([]),
123786
123808
  /**
package/dist/main.js CHANGED
@@ -115497,6 +115497,20 @@ var ChannelPostClient = class {
115497
115497
  }
115498
115498
  return { messageId };
115499
115499
  }
115500
+ async updatePost(messageId, content) {
115501
+ const res = await withPostRetry(
115502
+ "updatePost",
115503
+ () => this.channel().rawClient.im.v1.message.update({
115504
+ path: { message_id: messageId },
115505
+ data: {
115506
+ content,
115507
+ msg_type: "post"
115508
+ }
115509
+ }),
115510
+ { maxAttempts: this.maxAttempts, baseDelayMs: this.baseDelayMs }
115511
+ );
115512
+ return { messageId: res.data?.message_id ?? messageId };
115513
+ }
115500
115514
  };
115501
115515
 
115502
115516
  // src/lark/channelClient.ts
@@ -117434,10 +117448,12 @@ function renderStateContract(stateFilePath) {
117434
117448
  const stateTarget = stateFilePath ? `\u6307\u5B9A\u8DEF\u5F84 \`${stateFilePath}\`` : "\u5DE5\u4F5C\u76EE\u5F55\u91CC\u7684 `.larkway/state.json`";
117435
117449
  return [
117436
117450
  "<state-contract>",
117437
- "\u4F60\u548C\u8FD0\u8425\u4E4B\u95F4\u7684\u754C\u9762\u662F\u98DE\u4E66\u8BDD\u9898\u91CC\u7684\u5361\u7247,\u4F46\u8FD9\u5F20\u5361\u7247\u662F\u4E00\u4E2A thin-channel \u5916\u58F3:",
117438
- "- bridge \u53EA\u8D1F\u8D23\u521B\u5EFA/\u66F4\u65B0\u98DE\u4E66\u5361\u7247\u3001\u8282\u6D41 PATCH\u3001\u663E\u793A\u901A\u7528\u5934\u90E8\u72B6\u6001\u3001\u6E32\u67D3\u6B63\u6587 markdown\u3001\u663E\u793A\u5FC5\u8981\u7684\u5DE5\u5177\u6458\u8981\u3001\u628A choices \u8F6C\u6210\u6309\u94AE\u5E76\u628A\u70B9\u51FB\u503C\u56DE\u4F20\u7ED9\u4F60\u3002",
117439
- "- \u4F60\u8D1F\u8D23\u51B3\u5B9A\u5361\u7247\u91CC\u8981\u544A\u8BC9\u8FD0\u8425\u4EC0\u4E48\u3001\u5F53\u524D\u662F\u4EC0\u4E48\u4E1A\u52A1\u72B6\u6001\u3001\u9700\u8981\u7528\u6237\u56DE\u590D\u4EC0\u4E48\u3001\u8981\u4E0D\u8981\u7ED9\u79BB\u6563\u6309\u94AE\u3002",
117440
- `\u4F60\u4E0D\u76F4\u63A5\u753B\u98DE\u4E66\u5361\u7247,\u4E5F\u4E0D\u76F4\u63A5 PATCH \u5361\u7247;\u4F60\u53EA\u5199 ${stateTarget},bridge \u8BFB\u5B83\u6765\u505A\u5B89\u5168\u6E32\u67D3\u3002`,
117451
+ "\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:",
117452
+ "- \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",
117453
+ "- \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",
117454
+ "- 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",
117455
+ "- \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",
117456
+ `\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`,
117441
117457
  "**\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",
117442
117458
  "",
117443
117459
  "\u4F60\u80FD\u5199\u7684\u5B57\u6BB5:",
@@ -117448,18 +117464,19 @@ function renderStateContract(stateFilePath) {
117448
117464
  "- card_color: \u5361\u7247\u914D\u8272(\u53EF\u9009,success/failure/neutral,\u4E5F\u53EF\u76F4\u63A5\u5199 green/red/grey)",
117449
117465
  "- 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",
117450
117466
  '- 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',
117451
- '- response_surface: \u53EF\u9009\u539F\u578B\u5B57\u6BB5,\u5F62\u5982 `{mode:"card"|"post"|"hybrid", primary?:"card"|"post", post?:{mentions:[{user_id,label?}]}, card?:{compact?:boolean, capabilities?:[...]}}`\u3002\u5F53\u524D\u4EC5\u5728 bot \u914D\u7F6E\u663E\u5F0F\u542F\u7528\u4E14\u547D\u4E2D\u767D\u540D\u5355\u65F6\u624D\u53EF\u80FD\u88AB bridge \u8BFB\u53D6;PR3 \u771F\u5B9E post outbound/ledger \u672A\u5B9E\u73B0\u524D,bridge \u5FC5\u987B\u4FDD\u7559\u73B0\u6709\u53EF\u89C1 card \u515C\u5E95\u3002\u4E0D\u8981\u5199 raw Feishu post/card JSON\u3002',
117467
+ '- 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 post-first \u5904\u7406\u3002\u9700\u8981\u771F\u5B9E @ \u65F6\u628A\u76EE\u6807\u5199\u5165 `post.mentions`;\u4E0D\u8981\u5199 raw Feishu post/card JSON\u3002',
117452
117468
  "- 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",
117453
117469
  "- 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",
117454
117470
  "- updated_at: ISO 8601 timestamp",
117455
117471
  "",
117456
- "**\u7EDD\u4E0D\u81EA\u5DF1 `lark-cli api PATCH .../im/v1/messages/...` \u6539\u5361\u7247** \u2014\u2014 \u90A3\u662F bridge \u7684\u6D3B;",
117457
- "\u81EA\u5DF1 PATCH \u4F1A\u548C bridge \u51B2\u7A81,\u8FD8\u4F1A\u8BA9\u672C\u8F6E turn \u4E0D\u7ED3\u675F\u3002\u4F60\u53EA\u5199 state.json,\u5361\u7247\u5916\u58F3\u548C\u7F51\u7EDC\u66F4\u65B0\u4EA4\u7ED9 bridge\u3002",
117458
- "\u5199\u5B8C state.json \u5C31**\u5E72\u51C0\u7ED3\u675F\u672C\u8F6E**(\u4F60\u7684\u8FDB\u7A0B\u9000\u51FA \u2192 bridge \u624D finalize \u5361\u7247;",
117459
- "\u6302\u7740\u4E0D\u9000\u51FA = \u5361\u7247\u6C38\u8FDC\u5361\u5728\u300C\u5904\u7406\u4E2D\u300D)\u3002",
117472
+ "**\u7EDD\u4E0D\u81EA\u5DF1 `lark-cli api PATCH/PUT .../im/v1/messages/...` \u6539 bridge \u7BA1\u7406\u7684 post/card** \u2014\u2014 \u90A3\u662F bridge \u7684\u6D3B;",
117473
+ "\u81EA\u5DF1\u53D1/\u6539\u4E3B\u56DE\u590D\u9762\u4F1A\u548C bridge \u7684 post \u7F16\u8F91\u3001\u5361\u7247 finalize\u3001\u6309\u94AE\u56DE\u4F20\u3001\u5D29\u6E83\u6062\u590D\u51B2\u7A81\u3002\u4F60\u53EA\u5199 state.json,\u7F51\u7EDC\u66F4\u65B0\u4EA4\u7ED9 bridge\u3002",
117474
+ "\u5199\u5B8C state.json \u5C31**\u5E72\u51C0\u7ED3\u675F\u672C\u8F6E**(\u4F60\u7684\u8FDB\u7A0B\u9000\u51FA \u2192 bridge \u624D\u628A post/card \u6536\u655B\u6210\u7EC8\u6001;",
117475
+ "\u6302\u7740\u4E0D\u9000\u51FA = \u8BDD\u9898\u6C38\u8FDC\u5361\u5728\u300C\u6B63\u5728\u5904\u7406\u2026\u300D)\u3002",
117460
117476
  "",
117461
117477
  "\u5361\u7247\u5C55\u793A\u539F\u5219:",
117462
- "- bridge \u53EF\u80FD\u5728\u8FD0\u884C\u4E2D\u5C55\u793A\u5DE5\u5177\u6458\u8981,\u8FD9\u662F\u901A\u7528\u8FDB\u5EA6\u4FE1\u53F7;\u6700\u7EC8\u6210\u529F\u5361\u7247\u4EE5\u4F60\u7684 `content_blocks`(\u82E5\u975E\u7A7A)\u6216 `last_message` \u4E3A\u4E3B\u3002",
117478
+ "- \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",
117479
+ "- \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",
117463
117480
  "- \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",
117464
117481
  "- \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",
117465
117482
  "- \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",
@@ -118256,12 +118273,13 @@ import path6 from "node:path";
118256
118273
  var NonEmptyString = external_exports.string().trim().min(1);
118257
118274
  function defaultResponseSurfacePrototypeConfig() {
118258
118275
  return {
118259
- enabled: false,
118276
+ enabled: true,
118260
118277
  allowed_chats: [],
118261
118278
  allowed_threads: [],
118262
- lazy_card_creation: false,
118279
+ lazy_card_creation: true,
118263
118280
  kill_switch: false,
118264
- post_outbound_enabled: false,
118281
+ post_outbound_enabled: true,
118282
+ allow_agent_mentions: true,
118265
118283
  allowed_mention_open_ids: [],
118266
118284
  max_posts_per_turn: 1,
118267
118285
  max_posts_per_window: 4,
@@ -118272,12 +118290,13 @@ function defaultResponseSurfacePrototypeConfig() {
118272
118290
  }
118273
118291
  var DEFAULT_RESPONSE_SURFACE_PROTOTYPE = defaultResponseSurfacePrototypeConfig();
118274
118292
  var responseSurfacePrototypeConfigDefaults = () => ({
118275
- enabled: false,
118293
+ enabled: true,
118276
118294
  allowed_chats: [],
118277
118295
  allowed_threads: [],
118278
- lazy_card_creation: false,
118296
+ lazy_card_creation: true,
118279
118297
  kill_switch: false,
118280
- post_outbound_enabled: false,
118298
+ post_outbound_enabled: true,
118299
+ allow_agent_mentions: true,
118281
118300
  allowed_mention_open_ids: [],
118282
118301
  max_posts_per_turn: 1,
118283
118302
  max_posts_per_window: 4,
@@ -118326,40 +118345,46 @@ var ResponseSurfaceStateSchema = external_exports.preprocess((value) => {
118326
118345
  }, StrictResponseSurfaceStateSchema.optional());
118327
118346
  var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118328
118347
  /**
118329
- * Master gate. Default false keeps the legacy card-only response path.
118348
+ * Master gate. Default true makes response surfaces available unless the
118349
+ * runtime kill switch disables them.
118330
118350
  */
118331
- enabled: external_exports.boolean().default(false),
118351
+ enabled: external_exports.boolean().default(true),
118332
118352
  /**
118333
- * Optional chat allowlist for dark-launch experiments. Empty means no chat
118334
- * is allowlisted by this gate.
118353
+ * Optional chat allowlist for staged rollout. Empty means all chats are
118354
+ * allowed by this gate.
118335
118355
  */
118336
118356
  allowed_chats: external_exports.array(external_exports.string().min(1)).default([]),
118337
118357
  /**
118338
- * Optional Larkway session/thread allowlist for narrow dogfood topics.
118339
- * Empty means no thread is allowlisted by this gate.
118358
+ * Optional Larkway session/thread allowlist for staged rollout. Empty means
118359
+ * all threads are allowed by this gate.
118340
118360
  */
118341
118361
  allowed_threads: external_exports.array(external_exports.string().min(1)).default([]),
118342
118362
  /**
118343
- * Dark-launch hook for PR2. This does not enable post-only output by itself;
118344
- * the bridge still requires a visible card fallback until post outbound
118345
- * exists and is explicitly enabled in a later PR.
118363
+ * Post-first gate. Default true means the bridge starts a lightweight post
118364
+ * as the live main surface when post outbound is available, and creates a
118365
+ * card only for fallback or card-only capabilities.
118346
118366
  */
118347
- lazy_card_creation: external_exports.boolean().default(false),
118367
+ lazy_card_creation: external_exports.boolean().default(true),
118348
118368
  /**
118349
118369
  * Runtime kill switch for emergency rollback. When true, every response
118350
118370
  * surface post path is treated as disabled even if enabled/allowlists are
118351
- * otherwise configured. Default false preserves current default-off behavior.
118371
+ * otherwise configured.
118352
118372
  */
118353
118373
  kill_switch: external_exports.boolean().default(false),
118354
118374
  /**
118355
- * PR3 dark-launch gate for real post outbound. This must stay false by
118356
- * default. PR4's dispatcher also requires this gate before any post path.
118375
+ * Gate for real post outbound. Defaults on, but the runtime still requires
118376
+ * an injected post client and all safety gates before any post path.
118357
118377
  */
118358
- post_outbound_enabled: external_exports.boolean().default(false),
118378
+ post_outbound_enabled: external_exports.boolean().default(true),
118359
118379
  /**
118360
- * Explicit target allowlist for future real post @ mentions. Empty means no
118361
- * mention target is authorized. Keep real IDs in private bot config, never
118362
- * in public docs/tests.
118380
+ * Allows Agent-authored post mentions. This powers handoff to peer bots.
118381
+ * Keep this false only when the operator wants to suppress every real @.
118382
+ */
118383
+ allow_agent_mentions: external_exports.boolean().default(true),
118384
+ /**
118385
+ * Optional target allowlist for real post @ mentions. Empty means the Agent
118386
+ * may choose mention targets; non-empty narrows mentions to this exact set.
118387
+ * Keep real IDs in private bot config, never in public docs/tests.
118363
118388
  */
118364
118389
  allowed_mention_open_ids: external_exports.array(external_exports.string().min(1)).default([]),
118365
118390
  /**
@@ -118391,12 +118416,21 @@ var ResponseSurfacePrototypeConfigSchema = external_exports.object({
118391
118416
  function isResponseSurfacePrototypeAllowlisted(config, facts) {
118392
118417
  if (!config?.enabled) return false;
118393
118418
  if (config.kill_switch) return false;
118419
+ const hasScopedAllowlist = config.allowed_chats.length > 0 || config.allowed_threads.length > 0;
118420
+ if (!hasScopedAllowlist) return true;
118394
118421
  const chatAllowed = config.allowed_chats.length > 0 && config.allowed_chats.includes(facts.chatId);
118395
118422
  const threadAllowed = config.allowed_threads.length > 0 && config.allowed_threads.includes(facts.threadId);
118396
118423
  return chatAllowed || threadAllowed;
118397
118424
  }
118425
+ function isResponseSurfaceMentionAllowed(config, userId) {
118426
+ if (!config?.allow_agent_mentions) return false;
118427
+ const normalized = userId.trim().toLowerCase();
118428
+ if (normalized === "all" || normalized === "@all") return false;
118429
+ if (config.allowed_mention_open_ids.length === 0) return true;
118430
+ return config.allowed_mention_open_ids.includes(userId);
118431
+ }
118398
118432
  function shouldProvideResponseSurfacePostClient(config) {
118399
- return !!(config?.enabled && !config.kill_switch && config.post_outbound_enabled && config.max_posts_per_turn >= 1 && config.max_posts_per_window >= 1 && (config.allowed_chats.length > 0 || config.allowed_threads.length > 0));
118433
+ return !!(config?.enabled && !config.kill_switch && config.post_outbound_enabled && config.max_posts_per_turn >= 1 && config.max_posts_per_window >= 1);
118400
118434
  }
118401
118435
  function isResponseSurfacePostOutboundAvailable(config, facts, opts) {
118402
118436
  return !!(opts.postClientAvailable && shouldProvideResponseSurfacePostClient(config) && isResponseSurfacePrototypeAllowlisted(config, facts));
@@ -119202,6 +119236,9 @@ function fullCard(input, reason) {
119202
119236
  function hasCardOnlyPayload(state) {
119203
119237
  return !!(state?.choices?.length || state?.image_blocks?.length || state?.content_blocks?.length);
119204
119238
  }
119239
+ function hasCardOnlyPayloadIn(input) {
119240
+ return !!(hasCardOnlyPayload(input.state) || input.baseCard.choices?.length || input.baseCard.imageBlocks?.length || input.baseCard.contentBlocks?.length);
119241
+ }
119205
119242
  function compactAuditCard(input, post) {
119206
119243
  const status = input.state?.status ?? (input.baseCard.success ? "ready" : "failed");
119207
119244
  const title = input.baseCard.titleOverride ?? "Post \u5DF2\u53D1\u9001";
@@ -119228,7 +119265,7 @@ function policyBlockedCard(input) {
119228
119265
  return {
119229
119266
  ...input.baseCard,
119230
119267
  success: false,
119231
- failureReason: "response_surface post mention target is not in allowed_mention_open_ids; visible card fallback used"
119268
+ failureReason: "response_surface post mention target is blocked by policy; visible card fallback used"
119232
119269
  };
119233
119270
  }
119234
119271
  function postText(input) {
@@ -119271,10 +119308,18 @@ async function existingLedgerEntry(input, idempotencyKey) {
119271
119308
  return ledger?.posts.find((post) => post.idempotencyKey === idempotencyKey) ?? null;
119272
119309
  }
119273
119310
  function sentResult(input, surface, post, reason) {
119311
+ if (hasCardOnlyPayloadIn(input)) {
119312
+ return {
119313
+ card: input.baseCard,
119314
+ reason: "post-sent-card-capability-required",
119315
+ visible: true,
119316
+ post
119317
+ };
119318
+ }
119274
119319
  if (surface.mode === "hybrid" || input.cardStarted) {
119275
119320
  return {
119276
119321
  card: compactAuditCard(input, post),
119277
- reason: reason === "post-ledger-already-sent" ? "post-ledger-already-sent" : surface.mode === "hybrid" ? "hybrid-post-sent-compact-card" : "post-sent",
119322
+ reason: reason === "post-ledger-already-sent" ? "post-ledger-already-sent" : surface.mode === "hybrid" ? "hybrid-post-sent-compact-card" : reason,
119278
119323
  visible: true,
119279
119324
  post
119280
119325
  };
@@ -119317,10 +119362,11 @@ function emitSurfaceObservation(input) {
119317
119362
  );
119318
119363
  }
119319
119364
  async function dispatchResponseSurfaceInner(input) {
119320
- const surface = input.state?.response_surface;
119321
- if (!surface || surface.mode === "card" || surface.primary === "card") {
119365
+ const declaredSurface = input.state?.response_surface;
119366
+ if (declaredSurface?.mode === "card" || declaredSurface?.primary === "card") {
119322
119367
  return fullCard(input, "legacy-card-mode");
119323
119368
  }
119369
+ const surface = declaredSurface ?? { mode: "post", primary: "post" };
119324
119370
  const cfg = input.prototypeConfig;
119325
119371
  if (!cfg?.enabled) return fullCard(input, "prototype-disabled");
119326
119372
  if (cfg.kill_switch) return fullCard(input, "kill-switch-active");
@@ -119339,15 +119385,12 @@ async function dispatchResponseSurfaceInner(input) {
119339
119385
  if (!input.visibleFallbackAvailable) {
119340
119386
  return fullCard(input, "visible-fallback-unavailable");
119341
119387
  }
119342
- if (hasCardOnlyPayload(input.state)) {
119343
- return fullCard(input, "card-capability-required");
119344
- }
119345
119388
  if (!input.postLedgerAvailable || !input.worktreePath) {
119346
119389
  return fullCard(input, "post-ledger-unavailable");
119347
119390
  }
119348
119391
  const mentions = surface.post?.mentions ?? [];
119349
119392
  const blockedMention = mentions.find(
119350
- (mention) => !cfg.allowed_mention_open_ids.includes(mention.user_id)
119393
+ (mention) => !isResponseSurfaceMentionAllowed(cfg, mention.user_id)
119351
119394
  );
119352
119395
  const text = postText(input);
119353
119396
  const policyDigest = digestPostContent(text);
@@ -119363,7 +119406,17 @@ async function dispatchResponseSurfaceInner(input) {
119363
119406
  });
119364
119407
  const now = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
119365
119408
  if (blockedMention) {
119366
- const policyError = `mention target is not allowed: ${blockedMention.user_id}`;
119409
+ const policyError = `mention target is not allowed by response surface policy: ${blockedMention.user_id}`;
119410
+ if (input.livePost) {
119411
+ try {
119412
+ await input.postClient.updatePost(input.livePost.messageId, buildPostContent({ text }));
119413
+ } catch (err) {
119414
+ console.warn(
119415
+ "[surface_dispatch] live post policy-blocked cleanup update failed:",
119416
+ err
119417
+ );
119418
+ }
119419
+ }
119367
119420
  await writeLedger(
119368
119421
  input,
119369
119422
  newLedgerEntry({
@@ -119398,7 +119451,7 @@ async function dispatchResponseSurfaceInner(input) {
119398
119451
  }))
119399
119452
  });
119400
119453
  const contentDigest = digestPostContent(content);
119401
- const idempotencyKey = derivePostIdempotencyKey({
119454
+ const idempotencyKey = input.livePost?.idempotencyKey ?? derivePostIdempotencyKey({
119402
119455
  botId: input.facts.botId,
119403
119456
  threadId: input.facts.threadId,
119404
119457
  triggerMessageId: input.facts.triggerMessageId,
@@ -119451,7 +119504,7 @@ async function dispatchResponseSurfaceInner(input) {
119451
119504
  }
119452
119505
  };
119453
119506
  }
119454
- const budget = input.postBudget?.reserve();
119507
+ const budget = input.livePost ? void 0 : input.postBudget?.reserve();
119455
119508
  if (budget && !budget.allowed) {
119456
119509
  return {
119457
119510
  ...fullCard(input, "post-rate-limit-exhausted"),
@@ -119485,7 +119538,7 @@ async function dispatchResponseSurfaceInner(input) {
119485
119538
  })
119486
119539
  );
119487
119540
  try {
119488
- const sent = await input.postClient.createPostReply(input.facts.replyToMessageId, content, {
119541
+ const sent = input.livePost ? await input.postClient.updatePost(input.livePost.messageId, content) : await input.postClient.createPostReply(input.facts.replyToMessageId, content, {
119489
119542
  replyInThread: input.facts.replyInThread,
119490
119543
  idempotencyKey
119491
119544
  });
@@ -119511,7 +119564,10 @@ async function dispatchResponseSurfaceInner(input) {
119511
119564
  })
119512
119565
  );
119513
119566
  const post = { idempotencyKey, messageId: sent.messageId, role };
119514
- return { ...sentResult(input, surface, post, "post-sent"), budget };
119567
+ return {
119568
+ ...sentResult(input, surface, post, input.livePost ? "post-updated" : "post-sent"),
119569
+ budget
119570
+ };
119515
119571
  } catch (err) {
119516
119572
  const failedAt = input.now?.() ?? (/* @__PURE__ */ new Date()).toISOString();
119517
119573
  const error = err instanceof Error ? err.message : String(err);
@@ -119564,6 +119620,122 @@ async function dispatchResponseSurface(input) {
119564
119620
  return result;
119565
119621
  }
119566
119622
 
119623
+ // src/bridge/postProgress.ts
119624
+ var DEFAULT_PLACEHOLDER_TEXT = "\u6B63\u5728\u5904\u7406\u2026";
119625
+ var DEFAULT_PATCH_INTERVAL_MS = 1500;
119626
+ var DEFAULT_MAX_PROGRESS_EDITS = 16;
119627
+ function normalizeText2(text) {
119628
+ return text.replace(/\r\n/g, "\n").trim();
119629
+ }
119630
+ function progressText(raw, fallback) {
119631
+ const text = normalizeText2(raw);
119632
+ if (!text) return fallback;
119633
+ return text.length > 3500 ? `${text.slice(0, 3500)}
119634
+
119635
+ _\u5904\u7406\u4E2D\u5185\u5BB9\u8F83\u957F\uFF0C\u7EC8\u7A3F\u4F1A\u6536\u655B\u3002_` : text;
119636
+ }
119637
+ var LivePostProgressHandle = class {
119638
+ messageId;
119639
+ idempotencyKey;
119640
+ role = "primary";
119641
+ postClient;
119642
+ patchIntervalMs;
119643
+ maxProgressEdits;
119644
+ initialText;
119645
+ textBuffer = "";
119646
+ lastPatchedText = "";
119647
+ progressEdits = 0;
119648
+ pendingPatch = null;
119649
+ inFlight = Promise.resolve();
119650
+ closed = false;
119651
+ constructor(opts) {
119652
+ this.postClient = opts.postClient;
119653
+ this.messageId = opts.messageId;
119654
+ this.idempotencyKey = opts.idempotencyKey;
119655
+ this.initialText = opts.initialText;
119656
+ this.lastPatchedText = opts.initialText;
119657
+ this.patchIntervalMs = opts.patchIntervalMs;
119658
+ this.maxProgressEdits = opts.maxProgressEdits;
119659
+ }
119660
+ handle(event) {
119661
+ if (this.closed) return;
119662
+ if (event.type !== "text_delta") return;
119663
+ this.textBuffer += event.text;
119664
+ this.schedulePatch();
119665
+ }
119666
+ async finalize(opts) {
119667
+ await this.drain();
119668
+ const finalText = normalizeText2(opts.text) || this.initialText;
119669
+ await this.update(finalText, opts.mentions);
119670
+ }
119671
+ async drain() {
119672
+ this.closed = true;
119673
+ if (this.pendingPatch) {
119674
+ clearTimeout(this.pendingPatch);
119675
+ this.pendingPatch = null;
119676
+ }
119677
+ await this.inFlight;
119678
+ }
119679
+ close() {
119680
+ this.closed = true;
119681
+ if (this.pendingPatch) {
119682
+ clearTimeout(this.pendingPatch);
119683
+ this.pendingPatch = null;
119684
+ }
119685
+ }
119686
+ schedulePatch() {
119687
+ if (this.pendingPatch || this.progressEdits >= this.maxProgressEdits) return;
119688
+ this.pendingPatch = setTimeout(() => {
119689
+ this.pendingPatch = null;
119690
+ void this.patchProgress();
119691
+ }, this.patchIntervalMs);
119692
+ this.pendingPatch.unref?.();
119693
+ }
119694
+ async patchProgress() {
119695
+ if (this.closed || this.progressEdits >= this.maxProgressEdits) return;
119696
+ const nextText = progressText(this.textBuffer, this.initialText);
119697
+ if (nextText === this.lastPatchedText) return;
119698
+ this.progressEdits += 1;
119699
+ this.inFlight = this.inFlight.then(() => this.update(nextText)).catch((err) => {
119700
+ console.warn("[post_progress] progress update failed (continuing):", err);
119701
+ });
119702
+ await this.inFlight;
119703
+ }
119704
+ async update(text, mentions = []) {
119705
+ const content = buildPostContent({ text, mentions });
119706
+ await this.postClient.updatePost(this.messageId, content);
119707
+ this.lastPatchedText = text;
119708
+ }
119709
+ };
119710
+ async function createPostProgressHandle(opts) {
119711
+ const role = "primary";
119712
+ const initialText = normalizeText2(opts.initialText ?? DEFAULT_PLACEHOLDER_TEXT);
119713
+ const idempotencyKey = derivePostIdempotencyKey({
119714
+ botId: opts.facts.botId,
119715
+ threadId: opts.facts.threadId,
119716
+ triggerMessageId: opts.facts.triggerMessageId,
119717
+ role,
119718
+ logicalIndex: 0,
119719
+ contentDigest: digestPostContent("live-progress-placeholder")
119720
+ });
119721
+ const sent = await opts.postClient.createPostReply(
119722
+ opts.replyToMessageId,
119723
+ buildPostContent({ text: initialText }),
119724
+ {
119725
+ replyInThread: opts.replyInThread,
119726
+ idempotencyKey
119727
+ }
119728
+ );
119729
+ return new LivePostProgressHandle({
119730
+ postClient: opts.postClient,
119731
+ messageId: sent.messageId,
119732
+ idempotencyKey,
119733
+ initialText,
119734
+ patchIntervalMs: opts.patchIntervalMs ?? DEFAULT_PATCH_INTERVAL_MS,
119735
+ maxProgressEdits: opts.maxProgressEdits ?? DEFAULT_MAX_PROGRESS_EDITS
119736
+ });
119737
+ }
119738
+
119567
119739
  // src/bridge/postBudget.ts
119568
119740
  function scopeKey(scope) {
119569
119741
  return `${scope.botId}\0${scope.chatId}\0${scope.threadId}`;
@@ -119945,6 +120117,8 @@ var BridgeHandler = class {
119945
120117
  visibleFallbackAvailable: true
119946
120118
  });
119947
120119
  let card;
120120
+ let progressPost;
120121
+ let progressPostStartFailed = false;
119948
120122
  if (surfaceController.shouldStartCardImmediately()) {
119949
120123
  try {
119950
120124
  card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
@@ -120108,6 +120282,55 @@ var BridgeHandler = class {
120108
120282
  } catch (err) {
120109
120283
  console.warn("[bridge.handler] ensureStateFile failed (continuing):", err);
120110
120284
  }
120285
+ if (!card && postOutboundAvailable && this.deps.postClient) {
120286
+ const budget = prototypeConfig ? this.responseSurfacePostBudget.reserve({
120287
+ scope: {
120288
+ botId: this.deps.botConfig?.id ?? "v1-default",
120289
+ chatId: parsed.chatId,
120290
+ threadId
120291
+ },
120292
+ maxPosts: prototypeConfig.max_posts_per_window,
120293
+ windowMs: prototypeConfig.post_window_ms
120294
+ }) : void 0;
120295
+ if (budget?.allowed === false) {
120296
+ console.warn("[bridge.handler] post progress budget exhausted; using card fallback");
120297
+ progressPostStartFailed = true;
120298
+ } else {
120299
+ try {
120300
+ progressPost = await createPostProgressHandle({
120301
+ postClient: this.deps.postClient,
120302
+ replyToMessageId: messageId,
120303
+ replyInThread,
120304
+ facts: {
120305
+ botId: this.deps.botConfig?.id ?? "v1-default",
120306
+ threadId,
120307
+ triggerMessageId: messageId
120308
+ },
120309
+ initialText: "\u6B63\u5728\u5904\u7406\u2026"
120310
+ });
120311
+ await this.deps.client.removeProcessingReaction?.(messageId);
120312
+ await recordEvent({
120313
+ status: "running",
120314
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
120315
+ appendPath: "\u5DF2\u521B\u5EFA post",
120316
+ reason: "response surface \u4F7F\u7528 post \u4F5C\u4E3A\u672C\u8F6E\u4E3B\u56DE\u590D\u9762\u3002"
120317
+ });
120318
+ } catch (err) {
120319
+ progressPostStartFailed = true;
120320
+ console.warn("[bridge.handler] create progress post failed; using card fallback:", err);
120321
+ }
120322
+ }
120323
+ }
120324
+ if (!card && progressPostStartFailed) {
120325
+ card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
120326
+ await this.deps.client.removeProcessingReaction?.(messageId);
120327
+ await recordEvent({
120328
+ status: "running",
120329
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
120330
+ appendPath: "post \u5931\u8D25\uFF0C\u5DF2\u521B\u5EFA\u5361\u7247",
120331
+ reason: "post \u4E3B\u9762\u521B\u5EFA\u5931\u8D25\uFF0Cbridge \u4F7F\u7528\u53EF\u89C1\u5361\u7247\u515C\u5E95\u3002"
120332
+ });
120333
+ }
120111
120334
  if (card) {
120112
120335
  try {
120113
120336
  await writeCardFile(worktreePath, {
@@ -120182,6 +120405,7 @@ var BridgeHandler = class {
120182
120405
  try {
120183
120406
  for await (const ev of handle.events) {
120184
120407
  if (card) card.handle(ev);
120408
+ else progressPost?.handle(ev);
120185
120409
  if (ev.type === "system_init") {
120186
120410
  sessionId = ev.sessionId;
120187
120411
  }
@@ -120249,6 +120473,7 @@ var BridgeHandler = class {
120249
120473
  imageBlocks: reportedState?.image_blocks,
120250
120474
  contentBlocks: reportedState?.content_blocks
120251
120475
  };
120476
+ await progressPost?.drain();
120252
120477
  const surfaceDispatch = await dispatchResponseSurface({
120253
120478
  state: reportedState,
120254
120479
  prototypeConfig,
@@ -120263,10 +120488,15 @@ var BridgeHandler = class {
120263
120488
  worktreePath,
120264
120489
  baseCard: baseCardPayload,
120265
120490
  cardStarted: !!card,
120266
- postOutboundAvailable,
120491
+ postOutboundAvailable: postOutboundAvailable && !progressPostStartFailed,
120267
120492
  postLedgerAvailable: true,
120268
120493
  visibleFallbackAvailable: true,
120269
120494
  postClient: this.deps.postClient,
120495
+ livePost: progressPost ? {
120496
+ messageId: progressPost.messageId,
120497
+ idempotencyKey: progressPost.idempotencyKey,
120498
+ role: progressPost.role
120499
+ } : void 0,
120270
120500
  postBudget: prototypeConfig ? {
120271
120501
  reserve: () => this.responseSurfacePostBudget.reserve({
120272
120502
  scope: {
@@ -120370,6 +120600,23 @@ var BridgeHandler = class {
120370
120600
  reason: String(err)
120371
120601
  });
120372
120602
  settle(false);
120603
+ if (!card && progressPost) {
120604
+ try {
120605
+ await progressPost.finalize({
120606
+ text: `\u6267\u884C\u5931\u8D25: ${String(err)}`
120607
+ });
120608
+ } catch (postFinalizeErr) {
120609
+ console.error(
120610
+ "[bridge.handler] progress post failure update failed; creating card fallback:",
120611
+ postFinalizeErr
120612
+ );
120613
+ try {
120614
+ card = await this.deps.cardRenderer.start(messageId, { replyInThread, threadId });
120615
+ } catch (cardStartErr) {
120616
+ console.error("[bridge.handler] failure card start also failed:", cardStartErr);
120617
+ }
120618
+ }
120619
+ }
120373
120620
  if (card) {
120374
120621
  try {
120375
120622
  await card.finalize({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",