lark-remote 0.5.10 → 0.5.13

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.
Files changed (126) hide show
  1. package/CHANGELOG.md +51 -2
  2. package/README.en.md +1 -14
  3. package/README.md +1 -14
  4. package/dist/binder.js +7 -1
  5. package/dist/bridge/approval-coordinator.d.ts +1 -0
  6. package/dist/bridge/approval-coordinator.js +12 -24
  7. package/dist/bridge/index.d.ts +7 -0
  8. package/dist/bridge/index.js +89 -230
  9. package/dist/bridge/run-approval.d.ts +15 -0
  10. package/dist/bridge/run-approval.js +41 -0
  11. package/dist/bridge/run-usage.d.ts +46 -0
  12. package/dist/bridge/run-usage.js +96 -0
  13. package/dist/bridge/session-writeback.d.ts +18 -0
  14. package/dist/bridge/session-writeback.js +36 -0
  15. package/dist/card/approval-render.d.ts +14 -0
  16. package/dist/card/approval-render.js +155 -29
  17. package/dist/card/bash-card-session.js +10 -5
  18. package/dist/card/bash-renderer.js +11 -23
  19. package/dist/card/card-budget.js +24 -4
  20. package/dist/card/card-session.d.ts +2 -0
  21. package/dist/card/card-session.js +20 -22
  22. package/dist/card/card-shared.d.ts +4 -4
  23. package/dist/card/card-shared.js +3 -9
  24. package/dist/card/collapsible.js +3 -2
  25. package/dist/card/final-answer-overflow.d.ts +7 -0
  26. package/dist/card/final-answer-overflow.js +46 -0
  27. package/dist/card/run-card-session.js +2 -7
  28. package/dist/card/run-renderer.d.ts +14 -0
  29. package/dist/card/run-renderer.js +150 -10
  30. package/dist/card/run-state.d.ts +12 -1
  31. package/dist/card/run-state.js +73 -34
  32. package/dist/common/text-width.js +5 -0
  33. package/dist/common/tool-summary.js +4 -2
  34. package/dist/config/codex-config.js +5 -3
  35. package/dist/config/index.d.ts +4 -2
  36. package/dist/config/index.js +19 -5
  37. package/dist/inbound/turn-assembler.js +6 -0
  38. package/dist/index.js +15 -391
  39. package/dist/instance-lock.d.ts +9 -1
  40. package/dist/instance-lock.js +42 -7
  41. package/dist/message-handlers.d.ts +26 -0
  42. package/dist/message-handlers.js +275 -0
  43. package/dist/platform/identity.js +4 -3
  44. package/dist/platform/path.js +3 -1
  45. package/dist/platform/terminator-win32.d.ts +13 -1
  46. package/dist/platform/terminator-win32.js +4 -14
  47. package/dist/router/card-action-payload.d.ts +1 -1
  48. package/dist/router/card-action-payload.js +1 -1
  49. package/dist/router/commands/ls.d.ts +9 -0
  50. package/dist/router/commands/ls.js +243 -0
  51. package/dist/router/commands/order.d.ts +12 -0
  52. package/dist/router/commands/order.js +178 -0
  53. package/dist/router/commands/resume.d.ts +47 -0
  54. package/dist/router/commands/resume.js +246 -0
  55. package/dist/router/commands/ws.d.ts +15 -0
  56. package/dist/router/commands/ws.js +189 -0
  57. package/dist/router/config/claude.js +3 -3
  58. package/dist/router/config/codex.js +2 -6
  59. package/dist/router/config/common/turn-idle.d.ts +2 -0
  60. package/dist/router/config/common/turn-idle.js +9 -0
  61. package/dist/router/config/kimi.js +2 -6
  62. package/dist/router/config/opencode.js +2 -6
  63. package/dist/router/index.d.ts +14 -22
  64. package/dist/router/index.js +93 -1133
  65. package/dist/router/paging.d.ts +13 -0
  66. package/dist/router/paging.js +20 -0
  67. package/dist/router/path-validation.d.ts +27 -0
  68. package/dist/router/path-validation.js +45 -0
  69. package/dist/runner/claude/runner.d.ts +3 -4
  70. package/dist/runner/claude/runner.js +4 -5
  71. package/dist/runner/claude/session.js +1 -1
  72. package/dist/runner/codex/app-server/runner.js +12 -0
  73. package/dist/runner/codex/app-server/translator.d.ts +2 -0
  74. package/dist/runner/codex/app-server/translator.js +65 -9
  75. package/dist/runner/common/acp/base-translator.d.ts +15 -8
  76. package/dist/runner/common/acp/base-translator.js +38 -7
  77. package/dist/runner/common/jsonl-stream.js +12 -1
  78. package/dist/runner/common/jsonrpc/connection-manager.js +11 -1
  79. package/dist/runner/common/jsonrpc/transport.d.ts +2 -1
  80. package/dist/runner/common/jsonrpc/transport.js +7 -1
  81. package/dist/runner/common/spawning-runner.js +10 -4
  82. package/dist/runner/common/turn-started.d.ts +2 -0
  83. package/dist/runner/common/turn-started.js +10 -0
  84. package/dist/runner/dsh/approval.js +16 -3
  85. package/dist/runner/dsh/auth.js +2 -3
  86. package/dist/runner/dsh/client.d.ts +32 -7
  87. package/dist/runner/dsh/client.js +115 -149
  88. package/dist/runner/dsh/daemon.d.ts +0 -2
  89. package/dist/runner/dsh/daemon.js +1 -6
  90. package/dist/runner/dsh/errors.d.ts +5 -0
  91. package/dist/runner/dsh/errors.js +11 -0
  92. package/dist/runner/dsh/exit-cancel.d.ts +0 -2
  93. package/dist/runner/dsh/exit-cancel.js +63 -24
  94. package/dist/runner/dsh/home.d.ts +2 -0
  95. package/dist/runner/dsh/home.js +8 -0
  96. package/dist/runner/dsh/inflight.d.ts +0 -2
  97. package/dist/runner/dsh/inflight.js +1 -1
  98. package/dist/runner/dsh/mux-stream.d.ts +26 -0
  99. package/dist/runner/dsh/mux-stream.js +157 -0
  100. package/dist/runner/dsh/runner.d.ts +7 -0
  101. package/dist/runner/dsh/runner.js +42 -5
  102. package/dist/runner/dsh/translator.d.ts +0 -10
  103. package/dist/runner/dsh/translator.js +1 -1
  104. package/dist/runner/index.d.ts +1 -1
  105. package/dist/runner/kimi/acp/runner.js +42 -5
  106. package/dist/runner/kimi/acp/translator.d.ts +8 -0
  107. package/dist/runner/kimi/acp/translator.js +11 -12
  108. package/dist/runner/opencode/acp/translator.js +1 -12
  109. package/dist/runner/pi/rpc/runner.js +33 -5
  110. package/dist/runner/pi/rpc/translator.d.ts +2 -8
  111. package/dist/runner/pi/rpc/translator.js +2 -7
  112. package/dist/runner/registry.d.ts +0 -19
  113. package/dist/runner/registry.js +0 -32
  114. package/dist/runner/resolve-agent-choices.js +5 -1
  115. package/dist/runner/sync-agent-choices.js +4 -1
  116. package/dist/runner/types.d.ts +17 -1
  117. package/dist/session/claude/session-index.js +13 -4
  118. package/dist/session/codex/rollout-reader.js +51 -26
  119. package/dist/session/common/usage-accumulator.d.ts +10 -1
  120. package/dist/session/common/usage-accumulator.js +10 -2
  121. package/dist/session/dsh/sessions.js +29 -9
  122. package/dist/session/kimi/sessions.js +11 -7
  123. package/dist/session/opencode/sessions.js +15 -7
  124. package/dist/session/pi/sessions.js +3 -2
  125. package/dist/session/session-store.js +4 -3
  126. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,55 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.13] - 2026-10-26
9
+
10
+ ### 修复
11
+
12
+ - **长回合的最终回答不再被进度内容挤掉**:工具输出、思考、通知此前与最终回答共用同一份卡片预算,回合越长越容易把回答截断甚至整段挤没。现在最终回答独立保存,不受进度文本窗口和工具块上限淘汰;终态渲染先排状态、审批与操作控件,再放完整回答,剩余空间才从新到旧回填进度内容。协议带 `final_answer` 标记的(Codex app-server)直接采用,没有标记的 agent 在收尾时把最后一条非空 assistant 文本提升为最终回答,Claude、opencode、Kimi、Pi、DSH 共用同一套保护
13
+ - **回答放不下时随后补发完整文本**:单张卡片连「最终回答 + 必要控件」都装不下时,原卡提示「最终回答较长,完整内容已随后发送」,完整回答按 UTF-8 字节边界拆成多条纯文本消息补发(优先落在换行处,单条上限 20000 字节),拼接后与原文逐字一致,不再静默截断;补发失败会在原卡给出「完整回答补发失败」的可见回执
14
+
15
+ ### 维护
16
+
17
+ - **入口与路由/桥接层拆分为独立模块**:消息入站装配、命令实现(`ls` / `order` / `resume` / `ws`)、分页与路径校验、审批转发、用量聚合、会话回写、DSH 多路复用流各自成模块,`src/index.ts`、`src/bridge/index.ts`、`src/router/index.ts` 只保留编排职责;同时为拆分中暴露的状态竞争(中断与自然结束并发写同一张卡的终态、审批重投递、bash 卡片收尾)补上回归用例。无行为变化
18
+
19
+ ## [0.5.12] - 2026-10-24
20
+
21
+ ### 修复
22
+
23
+ - **Bash 工具结果不再偶发丢失**:kimi 的终端子进程退出后,runner 会等 stdout 尾部 flush(最多 200ms)再推 Bash 输出;但 `terminal/wait_for_exit` 此前在 flush 之前就返回,服务端拿到响应后立即 release 并结束回合,回合结果可能先于延迟推送的 tool_result 到达,卡片上的 Bash 面板变成空输出。现在 wait_for_exit 在 tool_result 入队后才返回,保证输出先于回合结束送达
24
+
25
+ ### 维护
26
+
27
+ - **测试同步原语收紧**:pid 文件改为等完整内容再读(避免读到空串后把 0 当 PID 轮询进程组);kimi 压缩完成用例改由 mock server 在收到 prompt 后延迟落记录,消除并行负载下的假红。无行为变化
28
+
29
+ ## [0.5.11] - 2026-10-24
30
+
31
+ ### 修复
32
+
33
+ - **审批卡在超长或超量输入下不再整卡消失**:审批理由、题面、选项、文件变更等 agent 产出的文本此前没有上限,一条超长理由(实测 3 万字符)或 8 题 × 5 选项就能把卡片顶出字节/元素预算,被飞书整卡拒收——审批彻底不可见,回合只能挂到超时。现在自由文本按字节截断、题数/选项/变更条目按渲染上限折叠,最底层兜底卡改用紧凑审批区:审批按钮在任何降级档都保留
34
+ - **停止 bash 任务后卡片稳定显示「已手动终止」**:停止路径与自然收尾会并发写同一张卡的终态,后写者胜出;现在首个终态胜出,不再被随后的「执行失败」覆盖
35
+ - **内容审核截断后的自动续跑接回本轮会话**:续跑此前用入队时的会话快照,新会话第一条消息被截断时会落到一个全新的空会话,本轮上下文全部丢失;现在按卡片状态 → 运行中会话 → 起点快照三级取本轮实际使用的会话
36
+ - **DSH 停止后残留的排队 prompt 不再被下一个回合隐形执行**:服务端 cancel 不会清空 inbox,被停的 prompt 可能仍留在里面等下一个无人订阅的 turn 认领(耗 token、改工作区、卡片不可见)。现在 cancel 确认关闭后补一次撤回探测,确认不在或撤回成功才清在途标记;退出路径的 cancel 走同一收尾
37
+ - **DSH 断线重连后的历史缺口会回填**:重连快照按消息截取,断连期间积压超过 200 条消息时快照首条会越过上次游标,中间的运行事件此前对本次订阅永久缺失。现在用 `session/page` 的游标向前翻页补齐(最多 20×200 条);尾部是用户消息时事件探测返回 `unknown`,不再误报「已停止」
38
+ - **pi 会话切换后旧连接不再误杀新回合**:`/new`、`/resume` 切换会替换连接,旧连接迟到的关闭/事件此前会被推进新回合的共享队列,导致新消息未发出即丢失或新回合空转「成功」。现在连接回调带轮次代际锚点,非当前轮的迟到回调直接丢弃
39
+ - **上下文占用统计包含末轮输出**:统一口径改为「输入 + 输出 + 缓存读 + 缓存写」,与文档和压缩水位一致;长收尾回合不再系统性低估数千到数万 token
40
+ - **会话列表不再把 subagent 子会话当成父会话**:codex 按 id 读取与列表/active 同口径过滤子线程;kimi 的工作目录判定改用同路径比较,大小写或尾分隔符差异不再让会话从列表消失
41
+ - **配置 YAML 语法错误不再静默退出**:与配置校验失败同路径在终端输出原因并退出,而不是零输出地无声退出
42
+ - **工作区兜底与路径竞态收紧**:没有当前目录时取「最近使用」的工作区而不是最早保存的别名;`/cd`、`ls.switch`、`/ws use` 的目录检查补 TOCTOU 防护,路径在检查与使用之间被删时给出明确回复而不是静默失败
43
+ - **只有 chatId 的旧绑定记录可以重新认领**:这类记录此前会让所有消息被静默拒绝且永远无法重新绑定,现在视为未绑定,任意首条私聊可重新认领
44
+ - **卡片动作不再被在途媒体下载卡住**:会话变更类卡片动作执行前的入站冲刷改为 2 秒有界等待,大文件下载在途时点「新会话」不再让整个聊天停摆到下载超时;clone 引导期间到达的附件直接忽略,不再落盘或与引导话术冲突
45
+ - **`job.kill` 等纯远程操作立即执行**:终态卡上的「停止后台任务」不再排进串行队列等当前 run 结束
46
+ - **显式清空的模型配置重启后不再被旧选择填回**:`/config` 里清空模型/服务商等于回到默认,残留的旧选择不会再把它填回去
47
+ - **其他可靠性修复**:实例锁改为先写完整临时文件再原子链接,消除并发启动时的空文件窗口;pi 大上下文压缩不再受 60 秒请求超时影响;kimi 非终态的空工具结果不再把运行中的工具提前标成完成;DSH 会话读取遇到单条畸形事件只跳过该条,不再整张恢复卡变空;JSONL 流按字节边界解码,中文/emoji 被 chunk 切开时不再出现乱码
48
+
49
+ ### 安全
50
+
51
+ - **退出时取消 DSH 回合的凭据临时文件不再可预测**:此前把短时 cookie 写到 `<tmpdir>/lark-remote-exit-cancel-<pid>.hdr`,同机其他用户可预建软链把凭据引到自己可读的文件。现在写入仅本人可进入的随机 0700 临时目录、文件 0600,正常结束与异常路径都会清理
52
+
53
+ ### 变更
54
+
55
+ - **架构文档同步**:last-session 允许 cwd 缺失、两处终端直写豁免、标题显示宽度常量、DSH follow 快照与事件落盘顺序,中英两侧一致
56
+
8
57
  ## [0.5.10] - 2026-10-24
9
58
 
10
59
  ### 修复
@@ -524,8 +573,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
524
573
  ### 修复
525
574
 
526
575
  - `displayTitle`/`summary` 跳过 task-notification 注入
527
- - 修复 `scanClaudeSessions` 对迁移到其他目录的会话的遗漏(S2)
528
- - 修复 probe 生命周期、工厂回退、卡片回调等评审发现的问题(F1-F8)
576
+ - 修复 `scanClaudeSessions` 对迁移到其他目录的会话的遗漏
577
+ - 修复 probe 生命周期、工厂回退、卡片回调等问题
529
578
 
530
579
  ### 变更
531
580
 
package/README.en.md CHANGED
@@ -14,19 +14,6 @@ Designed for a single-user, peer-to-peer private chat. The agent's system prompt
14
14
 
15
15
  > **New here? Start with [docs/en/getting-started.md](docs/en/getting-started.md).**
16
16
 
17
- ## ⚠️ Security Warning (read first)
18
-
19
- This tool turns Feishu messages directly into local agent executions. **Out of the box, agents run without approval prompts**: Claude defaults to `bypassPermissions` (no approval card), but that permission mode is **configurable** — set it to anything other than `bypassPermissions` (`default` / `acceptEdits` / `auto` / `dontAsk` / `plan`) to enable interactive approval, where high-risk actions wait for your confirmation on the Feishu card. Codex uses the **app-server approval mode** (default `approvalPolicy=on-request` — command execution requires your confirmation on the Feishu card, sandbox defaults to `workspace-write`); Kimi defaults to `manual` (per-action approval; note that since kimi 0.40, `yolo` means Ask-When-Needed rather than allow-everything — use `auto` for fully unattended, no-approval execution); other agents run with their CLI defaults. **In any mode**, agents can read and write the local directories you specify. You MUST:
20
-
21
- - **Use it only in your own private (p2p) chat.** Never add the bot to any group chat; never let anyone else talk to it.
22
- - Restrict the app's visibility to **yourself only** in the Feishu Open Platform.
23
- - Don't run it on a machine holding data you can't afford to lose; prefer a dedicated user / machine / container.
24
- - Keep credentials such as `appSecret` in your local `config.yaml` only — **never commit them anywhere**.
25
-
26
- In approval mode (Claude, Codex, Kimi), an approval card arrives before a command runs — approve or reject it right in Feishu:
27
-
28
- ![Command approval card example](docs/images/approval-card.png)
29
-
30
17
  ## Prerequisites
31
18
 
32
19
  - OS: macOS / Linux / Windows 10+ (native Windows support is verified on real hardware, with behavior matching macOS/Linux)
@@ -70,7 +57,7 @@ defaultAgent: claude
70
57
  claude:
71
58
  model: claude-opus-4-8
72
59
  effort: medium # low | medium | high | xhigh | max
73
- permissionMode: bypassPermissions # Claude official --permission-mode: default | acceptEdits | auto | bypassPermissions | manual | dontAsk | plan (switchable via /config card; anything other than bypassPermissions enables approval)
60
+ permissionMode: auto # Claude official --permission-mode: default | acceptEdits | auto | bypassPermissions | manual | dontAsk | plan (switchable via /config card; auto is the default: low-risk actions run, high-risk actions ask)
74
61
  stopGraceMs: 5000
75
62
 
76
63
  logging:
package/README.md CHANGED
@@ -14,19 +14,6 @@
14
14
 
15
15
  > **第一次接触本项目?先看 [docs/zh/getting-started.md](docs/zh/getting-started.md)。**
16
16
 
17
- ## ⚠️ 安全警告(必读)
18
-
19
- 本工具会把飞书消息直接转为本地 agent 执行。**开箱默认是不弹审批的**:Claude 默认以 `bypassPermissions` 运行(无审批卡),但该权限模式**可配置**——把它改成 `bypassPermissions` 以外的值(`default` / `acceptEdits` / `auto` / `dontAsk` / `plan`)即启用交互式审批,高风险操作会在飞书卡片上等你确认;Codex 采用 **app-server 审批模式**(默认 `approvalPolicy=on-request`,执行命令需在飞书卡片上确认,沙箱默认 `workspace-write`);Kimi 默认 `manual`(逐条审批;注意 kimi 0.40+ 起 `yolo` 已改为「必要时询问」而非全部放行,要完全不弹审批需选 `auto`);其他 agent 以各自 CLI 的默认权限运行。**无论哪种模式**,agent 都能读写你指定的本地目录,请务必:
20
-
21
- - **仅限你自己的私聊(p2p)使用**。不要把机器人拉进任何群聊,不要让任何其他人能与它对话。
22
- - 在飞书开放平台把应用可见范围限制到**只有你自己**。
23
- - 运行本工具的机器上不要存放与损失承受力不符的数据;建议在独立用户/机器/容器里运行。
24
- - `appSecret` 等凭据只在本地 `config.yaml`,**不要提交到任何仓库**。
25
-
26
- 审批模式下(Claude、Codex、Kimi),执行命令前会收到审批卡片,可直接在飞书里允许或拒绝:
27
-
28
- ![命令审批卡片示例](docs/images/approval-card.png)
29
-
30
17
  ## 前置条件
31
18
 
32
19
  - 操作系统:macOS / Linux / Windows 10+(原生 Windows 支持已完成真机验证,行为与 macOS/Linux 对齐)
@@ -70,7 +57,7 @@ defaultAgent: claude
70
57
  claude:
71
58
  model: claude-opus-4-8
72
59
  effort: medium # low | medium | high | xhigh | max
73
- permissionMode: bypassPermissions # Claude 官方 --permission-mode:default | acceptEdits | auto | bypassPermissions | manual | dontAsk | plan(`/config` 卡片可切换;非 bypassPermissions 会启用审批)
60
+ permissionMode: auto # Claude 官方 --permission-mode:default | acceptEdits | auto | bypassPermissions | manual | dontAsk | plan(`/config` 卡片可切换;auto 为默认:低风险放行、高风险审批)
74
61
  stopGraceMs: 5000
75
62
 
76
63
  logging:
package/dist/binder.js CHANGED
@@ -44,7 +44,13 @@ export class OwnerBinder {
44
44
  */
45
45
  authorize(userId, chatId) {
46
46
  const bound = this.store.getContact();
47
- if (bound) {
47
+ // 只有 chatId 没有 userId 的记录(老版本写入/手改文件)视同未绑定:
48
+ // bound.userId === undefined 时任何真实 openId 都判不等,一切消息被
49
+ // 静默 rejected 且永远无法重新认领——用户只能靠文档提示删文件。
50
+ if (bound && bound.userId === undefined) {
51
+ getLogger().warn('[binder] startup contact has chatId but no userId; treating as unbound (re-claimable)');
52
+ }
53
+ else if (bound) {
48
54
  if (userId === bound.userId)
49
55
  return { kind: 'owner' };
50
56
  this.rejectedTotal++;
@@ -179,6 +179,7 @@ export declare class ApprovalCoordinator {
179
179
  /** 记录问题答案到 tracked.answers(懒初始化 Map)。 */
180
180
  private recordAnswer;
181
181
  private getPendingQuestion;
182
+ private getPendingQuestionAt;
182
183
  private allQuestionsAnswered;
183
184
  /** 推送审批卡视图更新(approval_view_updated 重渲染;失败仅记日志)。 */
184
185
  private pushApprovalViewUpdate;
@@ -178,12 +178,7 @@ export class ApprovalCoordinator {
178
178
  * submitAnswers() 显式提交。
179
179
  */
180
180
  async toggleAnswer(action, ctx) {
181
- const tracked = this.getPendingQuestion(ctx.requestId);
182
- const questions = tracked.view.questions;
183
- const q = questions[action.questionIndex];
184
- if (!q) {
185
- throw new Error(`Question ${action.questionIndex} not found in request ${ctx.requestId}`);
186
- }
181
+ const { tracked, question: q } = this.getPendingQuestionAt(ctx.requestId, action.questionIndex);
187
182
  const option = q.options.find((o) => o.label === action.option);
188
183
  if (!option) {
189
184
  throw new Error(`Option "${action.option}" not found in question ${action.questionIndex}`);
@@ -217,12 +212,7 @@ export class ApprovalCoordinator {
217
212
  * 提交该问题当前勾选的选项;全部问题答完时通过 responder 提交。
218
213
  */
219
214
  async submitAnswers(action, ctx) {
220
- const tracked = this.getPendingQuestion(ctx.requestId);
221
- const questions = tracked.view.questions;
222
- const q = questions[action.questionIndex];
223
- if (!q) {
224
- throw new Error(`Question ${action.questionIndex} not found in request ${ctx.requestId}`);
225
- }
215
+ const { tracked, question: q } = this.getPendingQuestionAt(ctx.requestId, action.questionIndex);
226
216
  if (!q.multiSelect) {
227
217
  throw new Error('该问题为单选,请直接点击选项');
228
218
  }
@@ -240,12 +230,7 @@ export class ApprovalCoordinator {
240
230
  * 单选问题的答案。与单选 toggle 同语义:全部问题答完自动提交。
241
231
  */
242
232
  async answerCustom(action, ctx) {
243
- const tracked = this.getPendingQuestion(ctx.requestId);
244
- const questions = tracked.view.questions;
245
- const q = questions[action.questionIndex];
246
- if (!q) {
247
- throw new Error(`Question ${action.questionIndex} not found in request ${ctx.requestId}`);
248
- }
233
+ const { tracked, question: q } = this.getPendingQuestionAt(ctx.requestId, action.questionIndex);
249
234
  if (q.multiSelect) {
250
235
  throw new Error('自定义答案仅支持单选');
251
236
  }
@@ -266,12 +251,7 @@ export class ApprovalCoordinator {
266
251
  * 后回流视图(卡片回显),同一请求可多次修改(新 nonce 来自新渲染)。
267
252
  */
268
253
  async answerNote(action, ctx) {
269
- const tracked = this.getPendingQuestion(ctx.requestId);
270
- const questions = tracked.view.questions;
271
- const q = questions[action.questionIndex];
272
- if (!q) {
273
- throw new Error(`Question ${action.questionIndex} not found in request ${ctx.requestId}`);
274
- }
254
+ const { tracked, question: q } = this.getPendingQuestionAt(ctx.requestId, action.questionIndex);
275
255
  const text = action.text.trim();
276
256
  if (!text) {
277
257
  throw new Error('请输入补充说明');
@@ -443,6 +423,14 @@ export class ApprovalCoordinator {
443
423
  }
444
424
  return tracked;
445
425
  }
426
+ getPendingQuestionAt(requestId, questionIndex) {
427
+ const tracked = this.getPendingQuestion(requestId);
428
+ const question = tracked.view.questions?.[questionIndex];
429
+ if (!question) {
430
+ throw new Error(`Question ${questionIndex} not found in request ${requestId}`);
431
+ }
432
+ return { tracked, question };
433
+ }
446
434
  allQuestionsAnswered(tracked) {
447
435
  const questions = tracked.view.questions ?? [];
448
436
  if (questions.length === 0)
@@ -369,6 +369,12 @@ export declare class Bridge {
369
369
  private interruptActiveRun;
370
370
  /** Send a result message to the user (§7). Returns true on success, false on failure. */
371
371
  sendResult(result: CommandResult, ctx: CommandContext): Promise<boolean>;
372
+ /**
373
+ * Deliver a final answer that cannot fit in the run card. Plain text avoids
374
+ * markdown parser limits and preserves every character; sequence labels make
375
+ * multi-message delivery unambiguous.
376
+ */
377
+ private sendFinalAnswerOverflow;
372
378
  /**
373
379
  * Update an existing card in place (原地更新) instead of sending a new one.
374
380
  *
@@ -567,6 +573,7 @@ export declare class Bridge {
567
573
  text: string;
568
574
  nonce: string;
569
575
  }): Promise<void>;
576
+ private dispatchQuestionAction;
570
577
  /**
571
578
  * 记下一次「可压缩 run」:终态卡上的 Compact 按钮点击时据此反查
572
579
  * sessionId + agentKind。runId 是按钮自带的唯一键,同 cwd 内唯一。