Keep interactive questions and answered cards at their original position in conversation history, including remote reception, recovery, and reopening. Preserve legacy history placement without a database migration. Move supplementary guidance into shared accessible contextual-help popovers across pages and settings. Add keyboard, modal, accessibility, and Electron regressions, plus bounded opt-in live canvas-analysis validation. Remove unused Agent, Runtime, and in-memory graph-search helpers, obsolete mocks and browser copy; reuse the existing package-size formatter. Keep production execution paths, protocol schemas, and released data migrations. Validation: targeted cleanup tests, typecheck, lint, shared Linux Host attach/read/re-attach, and added help/Electron checks passed. Final full-suite verification passed 4,858 tests with 67 skipped and zero failures (412 files passed, 9 skipped). Focused help checks passed 54 tests, including Electron style comparisons and Chromium accessibility descriptions. Details and validation boundaries are recorded in feature docs. Release note: 修复交互式问答在回答后排到后续输出末尾的问题,重新打开会话仍保留提问位置。页面和设置中的补充说明改为标题或字段旁的按需帮助,支持鼠标与键盘操作。
17 KiB
Runtime 交互边界
本文定义当前原生交互的适配范围,不增加工作模式或第二层 Execute 审批。
| Runtime | 权限确认 | 业务问答 |
|---|---|---|
| 本机 GoodBuddy 管理的 OpenCode | 内部默认配置允许权限;每个 Ask 请求仍使用 deny-all 与显式只读能力。Execute 自动回复属于当前请求的父、子孙会话权限事件,不处理其他并行请求 | 通过 question.asked 转交父、子孙会话问题,支持原生单选、多选、yes/no、自由文本及跳过;使用公开 ID 回传 question.reply/reject |
| 托管 SSH OpenCode | Agent 按已接受 Prompt 的 Ask/Execute 模式处理 ACP 权限,规则见远程主机技术设计 | 当前 Agent 插件转交原生父、子孙会话 question.asked;前台使用同一问答队列,回答和跳过经认证 Agent 控制通道回传,已知 Task 关联显示子任务归属 |
| Continue | Execute 启动传入 --auto,剩余原生权限请求按当前模式自动答复 |
对接 1.5.47 QuizService 的扁平 pendingQuestion,其中 question 是字符串。选项回答与自定义文本分别回传正确的 isCustomAnswer;跳过发送拒绝回答的文本 |
| DeepSeek Harness | 固定 Host 的 Execute 文件与命令工具不询问目录权限;若收到 ACP 权限请求,活跃 Execute 直接选一次性允许,缺少该选项时使用 Runtime 提供的允许选项,Ask 拒绝,不等待额外 authorizer | 固定 Host 当前未提供通用原生业务问答服务,不把普通模型正文误识别成待答协议。模型以普通回复提问时,用户通过下一条消息回答 |
托管 SSH OpenCode 的权限、原生问题事件与回复链路由
远程主机技术设计定义。该远端业务问答基线需要
Desktop 0.13.0、Agent 0.11.24 和新启动的托管 Runtime;请先升级 Desktop,再更新
Host 环境。2026-09-14 的已发布 Desktop 0.13.2 / Agent 0.11.25 已包含此前问答修复,
但不包含本文末尾尚未发布的进程复用改动。任意第三方 ACP 服务及旧 Agent 不因此获得通用问答接口。
运行与失败
- 结构化 yes/no 是普通选项,不从日志、自然语言或 Shell 输出猜测用户意图。
- 前台同一请求的待答问题按公开
questionId去重,按到达顺序保存在消息的pendingQuestions中。每次显示队首表单;有多个待答请求时显示数量。后续提问和重复 事件不替换当前表单或清空输入,回答或跳过成功后只移除对应 ID,再显示下一张表单。 提交失败保留队列、输入和重试入口,队列未清空时继续显示“等待你的回答”。 - 本机与托管 SSH OpenCode 在提问到达前已收到原生 Task 的
metadata.sessionId时,将精确匹配的 子会话关联为可选childTaskId。问答卡片显示该任务名称;对应 Task 卡片显示 “等待你的回答”,展开后可点击“前往问答表单”滚动并聚焦当前队首表单,仍按顺序回答。 没有已知关联的子孙会话问题照常排队,不猜测子任务归属,也不从工具名或进度文本构造表单。 - 回答或跳过成功后,会话保留只读的“问题与回答”,显示原问题及对应的选项答案、 自定义文本或“已跳过”。同一任务的多轮问答逐次保留,随本地会话保存,重新打开后仍可查看。 提交失败时保留输入与重试入口,不生成已回答记录;取消未回答的问题不记为主动跳过。
- 新消息在提问到达时,将只含
questionId的问题位置块插入当时的消息块序列; 待答和回答后的卡片使用同一位置,后续正文、工具输出不再排到问题前面。重复问题不追加 或移动位置块。位置块不保存待答表单;旧会话没有位置块时保留原有问答展示,不推测或 回填历史顺序。该变更复用既有消息元数据,不升级数据库 schema。 - 提交响应晚于任务结束时仍保存已提交的问答,但不覆盖完成或失败状态;晚于下一轮提问时 不清除新的待答卡片。旧版会话未保存的问题与答案无法从“已提交”状态文案恢复。
- 待答队列仅保存在当前桌面会话内存中,不写入持久会话快照。刷新仍在运行的消息时保留整个 本地队列;完成、失败、取消事件或持久化终态快照清除整个队列。迟到的提交结果可以保存 已回答记录,但不会恢复表单或覆盖终态。本机 Runtime 不恢复重启前未提交的问题;托管 SSH 重新附加同一存活 Agent Prompt 时,由 Agent 当前待答列表补发问题,已回答的历史事件不补发。
- 未发布的进程复用源码在 Desktop 重启后由 Main 并行附加同项目的存活任务,将待答
列表和 Stop 所需的请求 ID 投影到实时
activeRequest,不保存该字段或重新发送任务。 回答/跳过成功由 Main 写回原远程助理消息的既有问答历史,Renderer 不覆盖远程正文或终态。 Main 在前台与恢复请求收到实时问题时立即保存位置块;实时待答快照没有语义事件来源, 不伪造来源或追加 transcript。带来源的提问事件仍通过既有事务投影并按问题 ID 去重。 Runtime 设置、定制和能力更新让旧请求在原配置上结束,已有问题仍可回答;新请求使用 新配置。专家团队例外:既有专家调度器在设置替换时取消子任务,团队父请求也随之停止, 不使用替换后的模型汇总旧结果。详细实现见 恢复与终态投影。 - 后台任务收到结构化提问时,通过现有 Main 路径明确失败并提示改为前台对话,不永久等待。
- 请求结束或取消后清除对应待答映射;过期回答明确报错,不转交给后来的请求。
- 任意终端程序的密码、OAuth 登录、许可证确认或 stdin 提示不自动回复
yes。这类程序应 使用其非交互参数,或由用户在终端完成;不能把权限确认和业务决策混为一谈。Shell 工具保留其现有取消与超时,不新增日志解析或通用交互代理。
托管 Runtime 的并行与输出
子任务完整过程的显示与数据库差量存储相互独立,本机和托管 SSH 共用桌面存储层; 旧库升级与空间回收规则见执行记录存储与升级回收。
- 本机和 Agent 管理的 OpenCode 启动配置都显式设置
snapshot: false。GoodBuddy 不消费 原生自动 Git 快照、快照差异摘要或快照撤销;关闭这条内部路径,避免大行级差异的同步 计算阻塞同一原生进程中的其他会话。 - 文件工具、Shell、原生子代理、工作区 Git 状态和单文件差异保持可用;不新增全局会话 队列、请求重放或总执行截止时间。外部 OpenCode Server 的配置不由 GoodBuddy 改写, 已运行的旧原生进程也不会热更新配置。
- Continue 使用 HTTP 传递响应和状态;Electron UtilityProcess 启动后持续排空不用展示的 stdout,避免控制台输出填满管道后阻塞宿主。stderr 的有界错误处理、请求取消与进程 回收保持不变,不把诊断输出复制进聊天或持久日志。
- 远端最终启动配置由远程主机技术设计定义, 模型桥生成最终配置时同样保留上述快照策略。
工具操作摘要
工具记录沿用 UI 设计系统的折叠布局。Continue、DeepSeek Harness
和直连模型共用 toolOperationSummary:优先使用有效原生标题,否则从参数提取命令、
文件路径、搜索条件或 URL,合并空白后最多显示 240 字符。无法识别时保留原有工具名
回退规则;文件正文、整份参数 JSON 不进入标题,参数详情仍可展开查看。
Continue 在宿主截断输入前提取摘要,流式完成与失败事件使用合并后的输入和摘要。 DeepSeek Harness 按调用 ID 保留名称、输入和摘要,结束事件省略输入时仍能显示操作内容。 直连模型从原始参数生成一次摘要,在待执行、执行中和完成时复用;审批失败、拒绝和 执行失败、刷新重试仍保留已有提示。
本机 OpenCode 沿用原生操作标题。远端 ACP 独立使用原生标题作为记录名称,不经过上述 本地转换,本次没有修改 Agent、远程桥或协议。
消息底部请求状态
- 状态属于当前请求,不与顶部 Runtime 的连接中、就绪、不可用混为一谈。提交后先显示 “正在准备请求”;没有更具体的活动事件时显示“等待后续进展”,不声称模型正在思考。
- 本机 OpenCode 只把当前父会话的
session.status.retry映射为等待重试,显示原生attempt、计划重试时间和有界脱敏原因;后续busy才显示已开始重试。其他会话的 状态不串入当前消息,不猜测总重试次数,不把 SDK 事件流重连当作模型重试。 - 显示优先级为待回答、待确认、进行中的上下文压缩、明确的请求状态、待执行或执行中的 工具、未完成的子任务、等待后续进展。压缩沿用已有活动块,不重复增加底部提示。 正文、推理或工具进展会清除旧状态;子任务更新不覆盖父请求的重试提示。
- 完成、失败、取消后保留真实终态,迟到的状态事件不能把请求恢复成处理中。底部状态点 为静态,不凭请求尚未结束暗示仍有进展;会话列表闪点和发送、停止按钮保持原有行为。 持久化快照中的同一条消息仍为生成中时,也不能覆盖本地已收到的终态,即使快照的 会话更新时间更晚;否则连续发送或刷新会让已结束的请求重新显示停止按钮。
- Continue、DeepSeek Harness 和远端 ACP 保留其已接入的处理与工具事件,不伪造未接入 的原生重试状态。直连模型的重试等待与请求开始文案见 直连模型 UI 设计;本次不改变任何 Runtime 的重试上限、请求重放或取消策略。
实现与真实 Runtime 验证证据见进度记录。
Runtime 执行清单
本节为未实现的状态设计,对应 FR-CL1–FR-CL4、US-CL1–US-CL5。 原始格式和传递边界由技术设计维护。
清单属于一次请求及其助手消息。沿用项目、执行空间、Conversation、Runtime 选择和 原生 Session 到请求的既有映射;远端还需核对 binding/operation。不能仅以原生 Session 或 Conversation 为键更新顶部,因为同一会话会连续执行多轮请求。
| 输入或操作 | 清单与顶部行为 |
|---|---|
| 本轮尚无有效清单 | 隐藏顶部清单,不复制上一轮内容 |
| 收到本轮有效完整清单 | 按原生顺序整体替换,包括修改、删除、重排和重复文本;不按条目文本合并 |
| 收到有效空数组 | 记录明确清空并隐藏;空数组与缺少清单字段不同 |
| CN 调用进行中 | 候选只按该请求的调用 ID 暂存;成功后才提交完整替换 |
| CN 调用失败、取消或格式无效 | 丢弃该候选,保留上次有效清单,工具记录保留实际结果 |
| 请求完成、失败、取消或中断 | 冻结最新已提交清单,并显示请求真实结果;不推断条目完成,迟到输入不改写已结算清单 |
| 同一会话开始下一轮 | 顶部转向新请求,上一轮清单留在原消息;旧轮事件不得归入新轮 |
| 切换会话、项目、执行空间或 Runtime | 只读取目标归属的数据;原请求在原归属结算,不把原清单标为新 Runtime 的进度 |
| 查看历史或重连 | 从原消息及既有事件恢复;按原请求和远程来源顺序去重,不能用最新原生 Session 快照覆盖历史轮次 |
条目状态支持 pending、in_progress、completed、cancelled。OC 原生提供的 priority 保留;
CN 未勾选项映射为 pending,已勾选项为 completed,不推断 in_progress 或 priority。
OC 无稳定 item ID,视图索引仅服务本次完整列表渲染,不生成跨更新的条目身份。
清单内容不创建、调度或完成产品 Task,也不改变底部请求状态。清单更新与工具活动可以
同时保留,但顶部计数只来自已确认的结构化清单,不能反向解析普通状态文本、任意
Markdown 或名称含 list 的工具。子会话清单不覆盖父请求顶部。
以上产品状态规则已明确;远程 CN 接入、原生格式确认及历史字段选择仍属技术待办,见 进度记录。
Runtime 资源生命周期
本节对应 FR-RR1–FR-RR6、US-RR1–US-RR4, 定义进程复用的行为规则。当前源码已接入本机共享 owner 和 Agent 共享 ACP; 完整验收状态以进度记录为准,不表示已发布。 具体 owner、兼容键和协议实现见技术设计。
资源有三个独立维度:进程是否启动/退役,会话是否仍持有原生上下文,请求是否在途。 “任务完成”“当前没有选中该项目”“工作栏关闭”均不能直接推导进程可以销毁。
| 触发 | 请求与会话行为 | 进程行为 | 对应需求/场景 |
|---|---|---|---|
| 被动状态读取 | 不取得新执行会话,已有状态可读取 | 不冷启动,不延长无会话 owner 的空闲期限;不为探测替换服务中的进程 | RR2 / US-RR1 |
| 显式测试/原生清单 | 取得临时检查引用;清单带明确工作区 | 与执行共用启动 Promise 和 owner;结束只释放检查引用 | RR1、RR2 / US-RR1 |
| 首次或并行 Prompt | 取得对应 execution space + conversation 的绑定 | 同兼容组复用,非兼容组分别启动 | RR1、RR3 / US-RR1、RR2 |
| 普通请求完成 | 释放请求订阅、临时 token、问题和 callback,保留可续聊 Session | 不因为一个请求完成关闭整个进程 | RR3、RR4 / US-RR3 |
| 取消一个请求 | 取消其模型/工具调用并结算,其他会话继续 | 不以 kill 共享进程代替正常会话取消 | RR3、RR4 / US-RR2 |
| 关闭面板/切换项目 | 停止非必要 UI 推送;不取消后台任务或抹掉会话所有权 | 不触发隐式关闭 | RR4 / US-RR3 |
| 显式释放/删除会话 | 释放该 Session 与会话工具资源;删除历史仍只由原有明确删除操作决定 | 其他引用仍在则保留 | RR4 / US-RR3 |
| 无会话、无请求、无检查引用 | 不存在需要保留的原生上下文 | 进入短空闲等待,再关闭;新使用者到来取消等待 | RR4 / US-RR1、RR3 |
| 配置替换 | 已接受请求使用旧配置结算,新请求使用新配置 | 旧 owner 排空后退役;不积累只被历史 ID 持有的旧配置进程 | RR1、RR4 / US-RR4 |
| 进程故障/不可响应 | 只给该 owner 下受影响请求明确中断;未知结果不自动重放 | 有界终止失效 owner,不伪造逐会话故障隔离 | RR3、RR4 / US-RR2、RR4 |
| SSH detach | 保留 Agent 已接受 Prompt 及既有恢复归属 | 关闭传输不关闭 Agent 或活动 Runtime | RR5 / US-RR4 |
表中 RRn 是 PRD 稳定需求 ID FR-RRn 的简写。
不变量:
- 一条请求始终使用接受时的 execution space、工作模式、配置和归属;不能读取后来 切换的 UI 选择,也不以进程 cwd 表示所有会话的 cwd。
- 同一会话串行,跨会话不增加全局 Prompt 队列。权限、子代理、问题、用量和最终清理 必须沿同一 Session/请求归属,不因共享进程放宽 Ask 或限制 Execute。
- owner 的退出条件依据所有使用者,不依据某个 adapter 的空闲状态。 未启动对象的 dispose 不产生冷启动;一个等待者取消不取消其他等待者的初始化。
- 正常会话仍持有原生上下文时,不用任意 TTL 清掉它来声称“空闲内存已回收”。 无会话 owner 的短空闲等待为 60 秒; 不增加用户配置项。它是进程引用回收,不是聊天历史保留期限。
- 持久历史不因请求结束、进程 idle、配置变化或测试 cleanup 被删除。 原生进程确实重启后的续聊沿现有历史重建规则,不新增第二套持久会话快照。
已明确普通并行、取消、释放、配置替换及断线行为。各项实际通过的场景及尚未完成的 组合/性能验证以进度记录为准。