From 05ae61d04c5738ed30c4af4e329c7d6189e924a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=B3=BD=E6=A5=B7?= Date: Mon, 2 Nov 2026 20:32:31 +0800 Subject: [PATCH 1/3] docs(agents): narrow orchestration to multi-stage tasks Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- AGENTS.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c22a17b1..4c34e592 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,8 +17,10 @@ ## 执行 -- 推进本仓库的开发、界面整改、打包安装、分支提交与合并发版时,统一以项目入口 skill +- 涉及跨层新功能、VSIX 打包安装、PR 合并与正式发版等多阶段任务时,使用项目入口 skill [droid-dev-pipeline](.agents/skills/droid-dev-pipeline/SKILL.md) 编排阶段并按需进入对应子 skill。 +- 单组件 Bug、局部交互异常、小样式与文案调整直接走短链路:定位目标源码 → 修改 → + 运行受影响单测与 `typecheck` / `lint:budgets`,不预加载编排 skill,也不做无关分支与环境盘点。 - 要求明确且操作在授权范围内时直接执行,必要的读取、实现和验证不逐项确认。 只有关键选择缺失,或需执行尚未授权的破坏性操作、安装、发布、付费调用或对外 发送时才询问;普通文件修改和本地验证不视为需要额外审批的外部操作。 @@ -28,6 +30,9 @@ ## 实现 +- 用户已指明具体界面或交互问题时,直接阅读对应组件与状态源码定位;除非源码无法解释 + 或用户明确要求排查运行日志,否则禁止翻查 `.factory/sessions`、编辑器运行日志或 + `node_modules` 依赖产物。 - 沿受影响的生产调用链检查必要代码和直接消费者,不做无证据的全仓审计;交付一个 可独立运行和验收的完整结果。 - 保持 Runtime、Extension Host、共享 Bridge 和 Webview 的职责边界。 @@ -66,8 +71,9 @@ 多行编辑器内联补全组合共享 Popover/Button,保留输入框光标、IME 与补全键盘 路由;不得强换成会抢走输入焦点的独立 Select。 旧版仅开发/回归源码和设计原型不作为生产入口继续扩展。 -- 新增或修改界面时检查同类控件和生产调用链,交付前核对遗漏, - 不得仅凭引入依赖或少量组件就宣称全部迁移完成。 +- 修改 `packages/chat-ui` 共享组件或执行组件迁移任务时,检查受影响的同类消费者,交付前核对遗漏, + 不得仅凭引入依赖或少量组件就宣称全部迁移完成;修改单个业务页面或卡片的局部逻辑时, + 只处理当前组件及其直接状态,不横向扫描其他业务面板。 ## 分支与合并 @@ -151,8 +157,9 @@ - 任务分支允许构建本地试用 VSIX,交付时注明分支、提交号和“未正式发布”;不为 安装验收提前合并到 `main`。最终试用包须对应已提交的本任务代码,不含无关草稿。 聊天、模型管理、智能补全与 Next Edit 始终共用同一产品安装包。 -- 修改生产 Webview 或扩展代码时,默认依次构建、生成并校验 VSIX,不自动安装或 - 更新编辑器扩展。只有用户对本次安装明确授权后才安装,不沿用历史安装授权。 +- 日常代码与界面修复默认只运行受影响测试、`pnpm run typecheck` 和 `pnpm run lint:budgets`; + 仅在用户要求打包或安装试用包、或准备合并发布时,才执行 VSIX 构建与校验。 + 只有用户对本次安装明确授权后才安装,不沿用历史安装授权。 获准后默认安装到 Microsoft VS Code 的当前用户扩展目录,并告知用户 Reload Window。 安装前核对 CLI 所属产品,避免 PATH 中由 Cursor 提供的 `code` 别名;只有用户明确 要求时才处理 Cursor,不默认使用隔离的测试配置目录。 From a90b9b6be9e9c88675a618c9f6bfd5d4f7d6fda5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=B3=BD=E6=A5=B7?= Date: Mon, 2 Nov 2026 20:33:19 +0800 Subject: [PATCH 2/3] fix(chat): honor AskUser multi-select hints and exclusive custom answers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - infer multi-select from topic and question notes, keeping mixed hints like 可单选或多选 - clear the custom answer when picking a preset option and uncheck presets when typing - allow clicking a selected radio option again to clear it Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../events/runtimeInteractions.test.ts | 51 +++++++++++++++++++ src/runtime/events/runtimeInteractions.ts | 17 +------ src/shared/protocol/interactionProtocol.ts | 23 +++++++++ src/webview-v2/chat/InteractionPanel.test.tsx | 44 +++++++++++++++- src/webview-v2/chat/InteractionPanel.tsx | 31 +++++++---- .../chat/interactions/questionAnswers.ts | 7 ++- 6 files changed, 145 insertions(+), 28 deletions(-) diff --git a/src/runtime/events/runtimeInteractions.test.ts b/src/runtime/events/runtimeInteractions.test.ts index e44555ff..bc480f82 100644 --- a/src/runtime/events/runtimeInteractions.test.ts +++ b/src/runtime/events/runtimeInteractions.test.ts @@ -673,6 +673,57 @@ describe('runtime AskUser interactions', () => { }); }); + it('infers multiSelect from bracketed notes, mixed single/multi wording, and topic hints', async () => { + const askUser = vi.fn(async () => ({ + answers: [ + { index: 0, answer: 'A, B' }, + { index: 1, answer: 'C' }, + { index: 2, answer: 'D' }, + { index: 3, answer: 'E' }, + ], + })); + const callbacks = createRuntimeInteractionCallbacks(createHandler({ askUser })); + await callbacks.askUserHandler({ + toolCallId: 'implicit-multi', + questions: [ + { + index: 0, + topic: '场景', + question: '主要给谁用?(多选,决定输入来源)', + options: ['A', 'B'], + }, + { + index: 1, + topic: '范围(多选)', + question: '需要包含哪些模块?', + options: ['C', 'D'], + }, + { + index: 2, + topic: '模式', + question: '可单选或多选适用项', + options: ['D', 'E'], + }, + { + index: 3, + topic: '限制', + question: '仅限单选,不可多选', + options: ['E', 'F'], + }, + ], + }); + + expect(askUser).toHaveBeenCalledWith({ + toolCallId: 'implicit-multi', + questions: [ + expect.objectContaining({ index: 0, multiSelect: true }), + expect.objectContaining({ index: 1, multiSelect: true }), + expect.objectContaining({ index: 2, multiSelect: true }), + expect.objectContaining({ index: 3, multiSelect: false }), + ], + }); + }); + it.each([ { name: 'incomplete answers', diff --git a/src/runtime/events/runtimeInteractions.ts b/src/runtime/events/runtimeInteractions.ts index 4f9a2c8e..87edec68 100644 --- a/src/runtime/events/runtimeInteractions.ts +++ b/src/runtime/events/runtimeInteractions.ts @@ -27,6 +27,7 @@ import { MAX_PERMISSION_TOOL_NAME_LENGTH, MAX_SPEC_PLAN_LENGTH, PERMISSION_CONFIRMATION_KINDS, + isImplicitMultiSelectQuestion, type PermissionConfirmationKind, } from '../../shared/protocol/interactionProtocol'; @@ -532,27 +533,13 @@ function projectAskUserRequest( options: [...question.options], multiSelect: Boolean(question.multiSelect) || - isImplicitMultiSelectQuestion(question.question), + isImplicitMultiSelectQuestion(question.question, question.topic), }); } return { toolCallId: params.toolCallId, questions }; } -function isImplicitMultiSelectQuestion(question: string): boolean { - if (/(?:不可|不能|不支持|不允许|禁止)多选|单选/u.test(question)) { - return false; - } - return ( - /(?:可|支持|允许|可以)多选|多选题|多项选择|[(([【]\s*(?:多选|multi(?:-?select)?)\s*[))】\]]/iu.test( - question, - ) || - /\b(?:select|choose|check)\s+all\s+that\s+apply\b|\bmulti(?:ple)?[-\s]select\b/iu.test( - question, - ) - ); -} - /** * Marks display text that was cut at a projection cap. Details, titles, * and risk notes are display-only (approving never sends them back), so diff --git a/src/shared/protocol/interactionProtocol.ts b/src/shared/protocol/interactionProtocol.ts index a02d9d14..d5c95fa2 100644 --- a/src/shared/protocol/interactionProtocol.ts +++ b/src/shared/protocol/interactionProtocol.ts @@ -115,3 +115,26 @@ export interface PlanDocumentStateMessage { readonly status: PlanDocumentStatus; readonly content?: string; } + +export function isImplicitMultiSelectQuestion(question: string, topic = ''): boolean { + const text = `${topic}\n${question}`; + if (/(?:不可|不能|不支持|不允许|禁止|不是|非)多选|(?:仅|只)(?:限|能|可)?单选/u.test(text)) { + return false; + } + const hasMultiSelectHint = + /(?:可|支持|允许|可以)(?:单选[、/或与及和]?)?多选|单选[、/或与及和]多选|多选题|多项选择|可(?:选|勾选)多(?:个|项)|(?:一个|1\s*个)或多个|多个选项/u.test( + text, + ) || + /[(([【][^))】\]]*(?:多选|multi(?:-?select)?|\bmulti\b)[^))】\]]*[))】\]]/iu.test( + text, + ) || + /^(?:多选|multi(?:-?select)?)$/iu.test(topic.trim()) || + /\b(?:select|choose|check)\s+(?:all\s+that\s+apply|one\s+or\s+more|multiple)\b|\bmulti(?:ple)?[-\s]select\b/iu.test( + text, + ); + if (hasMultiSelectHint) { + return true; + } + return false; +} + diff --git a/src/webview-v2/chat/InteractionPanel.test.tsx b/src/webview-v2/chat/InteractionPanel.test.tsx index 38801494..f78300d4 100644 --- a/src/webview-v2/chat/InteractionPanel.test.tsx +++ b/src/webview-v2/chat/InteractionPanel.test.tsx @@ -53,12 +53,52 @@ describe('V2 request responses', () => { }; const actions = { onAnswer: vi.fn(), onPermission: vi.fn(), onOpenPlan: vi.fn() }; render(); - await user.click(screen.getByRole('radio', { name: 'All files' })); - await user.type(screen.getByRole('textbox', { name: 'Own answer for Scope' }), 'Only the webview'); + const allFilesRadio = screen.getByRole('radio', { name: 'All files' }); + const currentFileRadio = screen.getByRole('radio', { name: 'Current file' }); + const customInput = screen.getByRole('textbox', { name: 'Own answer for Scope' }); + await user.click(allFilesRadio); + expect(allFilesRadio.getAttribute('aria-checked')).toBe('true'); + await user.click(allFilesRadio); + expect(allFilesRadio.getAttribute('aria-checked')).toBe('false'); + await user.click(allFilesRadio); + expect(allFilesRadio.getAttribute('aria-checked')).toBe('true'); + await user.type(customInput, 'Only the webview'); + expect(allFilesRadio.getAttribute('aria-checked')).toBe('false'); + await user.click(currentFileRadio); + expect(currentFileRadio.getAttribute('aria-checked')).toBe('true'); + expect(customInput.value).toBe(''); + await user.type(customInput, 'Only the webview'); + expect(currentFileRadio.getAttribute('aria-checked')).toBe('false'); await user.dblClick(screen.getByRole('button', { name: 'Submit answers' })); expect(actions.onAnswer).toHaveBeenCalledExactlyOnceWith(request, false, [{ index: 2, answer: 'Only the webview' }]); }); + it('submits all selected options and custom input for multi-select questions', async () => { + const user = userEvent.setup(); + const request: PendingInteraction = { + sessionId: 'session', turnId: 'turn', + request: { + kind: 'ask-user', requestId: 'multi-question', toolCallId: 'tool', + questions: [{ + index: 0, + topic: '使用场景', + question: '这个 skill 主要给谁用?(多选,决定输入来源)', + options: ['开发提测前自测', '测试同学排测试计划', 'CI 自动生成'], + multiSelect: false, + }], + }, + }; + const actions = { onAnswer: vi.fn(), onPermission: vi.fn(), onOpenPlan: vi.fn() }; + render(); + await user.click(screen.getByText('开发提测前自测')); + await user.click(screen.getByRole('checkbox', { name: '测试同学排测试计划' })); + await user.type(screen.getByRole('textbox', { name: 'Own answer for 使用场景' }), '发版复盘'); + await user.click(screen.getByRole('button', { name: 'Submit answers' })); + expect(actions.onAnswer).toHaveBeenCalledExactlyOnceWith(request, false, [ + { index: 0, answer: '开发提测前自测, 测试同学排测试计划, 发版复盘' }, + ]); + }); + it('never approves an edited plan until its host-owned draft is ready', async () => { const user = userEvent.setup(); const request: PendingInteraction = { diff --git a/src/webview-v2/chat/InteractionPanel.tsx b/src/webview-v2/chat/InteractionPanel.tsx index a971b2d7..6f0ffe13 100644 --- a/src/webview-v2/chat/InteractionPanel.tsx +++ b/src/webview-v2/chat/InteractionPanel.tsx @@ -142,8 +142,15 @@ function Questionnaire({ interaction, questions, actions }: {
{question.topic || t("Question {p0}", { p0: question.index + 1 })} · {question.options.length === 0 ? t("open response") : question.multiSelect ? t("multiple choice") : t("single choice")}{question.question} setSelected((previous) => ({ ...previous, [question.index]: values }))} /> - setCustom((previous) => ({ ...previous, [question.index]: event.target.value }))} /> + onChange={(update) => { + setSelected((previous) => ({ ...previous, [question.index]: typeof update === 'function' ? update(previous[question.index] ?? []) : update })); + if (!question.multiSelect) setCustom((previous) => (previous[question.index] ? { ...previous, [question.index]: '' } : previous)); + }} /> + { + const value = event.target.value; + setCustom((previous) => ({ ...previous, [question.index]: value })); + if (!question.multiSelect && value) setSelected((previous) => (previous[question.index]?.length ? { ...previous, [question.index]: [] } : previous)); + }} />
))}
@@ -157,15 +164,19 @@ function Questionnaire({ interaction, questions, actions }: { } function QuestionOptions({ question, selected, disabled, onChange }: { - question: AskUserQuestion; selected: readonly number[]; disabled: boolean; onChange(values: readonly number[]): void; + question: AskUserQuestion; selected: readonly number[]; disabled: boolean; + onChange(values: readonly number[] | ((previous: readonly number[]) => readonly number[])): void; }) { - const options = question.options.map((option, index) => ); + const options = question.options.map((option, index) => { + const id = `question-${question.index}-option-${index}`; + return ; + }); return question.multiSelect ?
{options}
: onChange([Number(value)])}>{options}; diff --git a/src/webview-v2/chat/interactions/questionAnswers.ts b/src/webview-v2/chat/interactions/questionAnswers.ts index 8e7200e1..16248f86 100644 --- a/src/webview-v2/chat/interactions/questionAnswers.ts +++ b/src/webview-v2/chat/interactions/questionAnswers.ts @@ -1,3 +1,4 @@ +import { isImplicitMultiSelectQuestion } from '../../../shared/protocol/interactionProtocol'; import type { AskUserQuestion } from '../../../shared/protocol/interactions'; export interface QuestionAnswer { @@ -28,7 +29,11 @@ export function resolveAnswer( export function presentAskUserQuestion(question: AskUserQuestion): AskUserQuestionPresentation { const decoded = question.question.replaceAll('\\n', '\n'); if (!/\[topic\]/iu.test(decoded) || !/\[option\]/iu.test(decoded)) { - return { question: question.question, options: question.options, multiSelect: question.multiSelect }; + return { + question: question.question, + options: question.options, + multiSelect: question.multiSelect || isImplicitMultiSelectQuestion(question.question, question.topic), + }; } return { question: decoded From 0483c6108fc805a866bc4068522f84e352c7d96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=B3=BD=E6=A5=B7?= Date: Tue, 3 Nov 2026 19:03:29 +0800 Subject: [PATCH 3/3] chore(release): prepare 0.8.20 release Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- CHANGELOG.md | 12 ++++++++++++ docs/STATUS.md | 11 ++++++----- package.json | 2 +- src/shared/protocol/interactionProtocol.ts | 1 - 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ea6e6b5..5d9af6fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ 按版本记录功能、体验改进与修复。安装方式与使用范围见 [README](README.md)。 +## 0.8.20 + +### Highlights + +- 深色聊天背景支持繁星、三体和关闭三种模式,保留旧布尔设置兼容;浅色模式与隐藏态不运行动画。 +- AskUser 单选题支持预设选项与自定义填空互斥切换,再次点击已选项可取消;多选题可同时提交多个选项和补充填空。 + +### Fixed + +- 修复题目或主题中的「多选」提示带括号说明、或写成「可单选或多选」时被误判为单选的问题,Runtime 与 Webview 共用识别逻辑。 +- 修复连续勾选时旧状态覆盖选项,以及表单内复选框冒泡事件引发重复标签激活的问题。 + ## 0.8.19 ### Highlights diff --git a/docs/STATUS.md b/docs/STATUS.md index e1f3535d..7bb3d802 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,15 +1,13 @@ # 当前状态 -核对日期:2026-11-02。本文区分当前源码、实际验证与安装状态,不再追加逐次施工日志。 +核对日期:2026-11-03。本文区分当前源码、实际验证与安装状态,不再追加逐次施工日志。 功能边界见 [CAPABILITIES](CAPABILITIES.md),待办见 [PLAN](PLAN.md),交互约定见 [DESIGN](DESIGN.md)。 旧构建哈希、调查和性能数字从 Git 历史查阅,不能充当当前版本验收。 ## 版本与交付 -- 当前发布版本为 `0.8.19`,在 `0.8.18` 基础上合并了混合增量流式 Markdown 渲染与 Shiki `GrammarState` 按行增量高亮、Kilo 风格 AI 回复排版与流式渐显防跳闪、新提问置顶与动态留白跑道(含流式上滑「回到底部」按钮恢复)、BTW 按序过程穿插与气泡重构、会话恢复与子代理详情页提速、子任务三档模型/推理强度独立直选、补全设置即时保存、深色模式呼吸星空背景及引用胶囊精简。 -- `0.8.18` 已通过 [PR #18](https://github.com/fireman-droid/droid-for-vscode/pull/18) 合入 `main`(提交 `e9dfb6f0`),并同步发布至 GitHub Release 与 Open VSX。 -- `0.8.17` 已通过 [PR #17](https://github.com/fireman-droid/droid-for-vscode/pull/17) 合入 `main`(提交 `1639c2f2`),并同步发布至 GitHub Release 与 Open VSX。 -- `0.8.16` 已通过 [PR #14](https://github.com/fireman-droid/droid-for-vscode/pull/14) 合入 `main`(提交 `c6e8c8fa`),[GitHub v0.8.16](https://github.com/fireman-droid/droid-for-vscode/releases/tag/v0.8.16) 与 [Open VSX 0.8.16](https://open-vsx.org/extension/droidvisx/droidvisx/0.8.16) 均已正式公开(同一产物 `droid-0.8.16.vsix`,14,313,474 字节,SHA256 `8fa29f8a97a5014e945919d28309ad6a921f01fc6f7a40b2186c258249b99814`,172 项内容与入口校验通过)。 +- 当前源码版本为 `0.8.20`,在 `0.8.19` 基础上加入繁星/三体/关闭三种深色聊天背景,并修复 AskUser 多选识别、勾选状态与单选填空互斥切换。 +- 发布前基线为已公开的 `0.8.19`;`0.8.20` 的实际发布状态以 [GitHub Release](https://github.com/fireman-droid/droid-for-vscode/releases/tag/v0.8.20) 与 [Open VSX](https://open-vsx.org/extension/droidvisx/droidvisx/0.8.20) 为准。正式包从合入 `main` 的版本标签构建,两渠道复用同一已校验 VSIX;本轮未授权本地安装。 - 恢复文件等 composer 回执、附件/引用提示及临时诊断继续保留在消息列表底部状态区域;「Droid 正在工作/回答」、停止中(`Stopping…`)、压缩与等待子代理等运行状态统一移入底部 Composer 输入框的「继续提问」占位位置,未输入草稿时旋转显示、输入时隐藏、结束后恢复占位文字。 - Microsoft Marketplace 管理页在 2026-10-18 仍返回 429/Azure DevOps VSID 并发限流,此前还存在 `The extension 'droidvisx' already exists in the Marketplace`、Owner 列表为空的问题;未改名或覆盖既有产物,微软自动发布保持关闭,未配置 `VSCE_PAT`。 - 官方 Droid CLI 当前为 `0.242.0`;文档站为 https://fireman-droid.github.io/droid-for-vscode/ 。 @@ -18,6 +16,7 @@ | 范围 | 当前实现与边界 | | --- | --- | +| AskUser 问答卡片 | Runtime 与 Webview 共用多选提示识别,支持题目/主题中的括号说明和单选/多选混合表述;单选预设选项与自定义填空互斥,再次点击已选项可取消,多选保留全部已选项并合并补充填空 | | 聊天 | 新提问直接切换置顶,底部动态留白跑道承载首屏静止生长的回复,超出一屏后自动向下跟随,短回复结束时保持留白不回弹;最新活动独立成行,更早活动归入“执行过程”;正文按时间顺序穿插,历史数量紧随标题。单条工具与 thinking 独立折叠,Edit 默认显示精简 Diff,可在原卡片展开全部,没有 thinking 中间分组;长 thinking 原文分块合并更新并限高滚动。正文与折叠箭头左边缘对齐,失败操作收起时仍可查看。队列预览最多三行;常规设置提供等待/引导,默认等待;daemon 引导不停止当前任务,消费确认后移入聊天,投递不明时暂停保留内容。其余流式、附件、权限/问答、引用和 BTW 保留 | | 管理页 | 常规、模型、补全、会话、任务;共享设置行、搜索筛选、分页和滚动条,Select 弹层不少于触发器宽度并随单行选项内容扩展,列表刷新保留已有内容 | | Factory 账户与额度 | 设置页侧栏底部显示 CLI 登录的邮箱与姓名,折叠后只留首字母头像,读取走独立非阻塞通道不锁页面忙碌态;`/limits`(Usage Limits)提供 `Standard`/`Droid Core` 分段切换与 `5-hour`、`Weekly`、`Monthly` 三条独立进度条及重置时间。身份取自本地 access token 载荷,额度读取 `GET /api/billing/limits`(EU 账户与 `FACTORY_API_BASE_URL` 走各自主机),结果按窗口缓存 20 秒;token 只留在 Extension Host,未登录或读取失败给出对应提示 | @@ -41,6 +40,8 @@ ## 最近验证 +- `0.8.20` AskUser 修复:2 个定向测试文件共 28 项、`typecheck` 与 `lint:budgets` 已通过;真实页面点击验收留给用户,未将单元测试视为编辑器验收。发布构建与渠道结果以该版本 Actions 记录为准。 + - `codex/ui-style-fixes`(新提问直接置顶、动态留白跑道防抖、吸顶提问卡回滚与中文标点加粗修复、BTW 工具折叠与准备占位重构、智能补全自动保存与平滑折叠、`/limits` 悬停态修正、Skill 补全光标归位、Worker 多步运行态防闪烁、深色模式星空背景与遥测噪音治理):`TranscriptView.tsx` 与 `useTranscriptScroll.ts` 在新用户提问进入消息列表时直接将其切换置顶(`PINNED_QUESTION_GAP = 12`),通过列表容器 `min-height`(仅在值变化时写入,排除 `data-pin-runway` 观察)与动态留白跑道使首屏回复静止向下生长、过程卡片展开/收起零抖动、短回复结束保持留白不回弹;`TranscriptView.tsx` 将吸顶选区锁定范围收窄至 `pin.current`(仅置顶卡片自身存在文字选区时保留旧候选,新提问越界或跑道重贴合时直接切换)、在 `resolvePinOffset` 中结合留白跑道阈值与真实 DOM 行顶对齐最新提问 `start`,并配合 `useTranscriptScroll.ts` 仅在列表高度收缩或滚到底部时重置 `userScrolling`,修复向上查看历史再滚回底部时错锁上一条提问卡遮挡最新回复、需点击屏幕才恢复的问题;`transcriptStatus.ts`、`Transcript.tsx` 与 `ReadOnlyTranscript.tsx` 抽出 `replyOperationsLive`,只在停止仍在进行(`stopping`)时隐藏改动摘要,中断(`interrupted`)回合结束后恢复显示「N 个文件已修改/Review」卡片,修复回复中途暂停后已修改文件不出现改动摘要的问题;`turnFlow.ts` 在停止期间(`stopping`)继续接收已执行工具的 `tool-result`,其余事件照旧丢弃,避免暂停前已完成的 Edit 回执被丢弃而漏计改动;`parseMarkdown.ts` 在 `remarkGfm`/`remarkMath` 之后对非代码/非公式文本节点中被 CommonMark 标点侧翼规则漏解析的成对 `**…**`(如中文引号 `“…”`、方头括号 `【…】` 紧邻加粗)补齐 `strong` AST 节点;`webviewHtml.ts` 与 `projectFrontendEvent.ts` 过滤 Chromium 良性 `ResizeObserver loop` 提示,消除 `frontend_error` 误报;`routedDaemon.ts`、`createDaemonDroidSession.ts` 与 `extension.ts` 新增按会话直连归属 Daemon 的 `listOpenedForSession` 探针,避免回合看门狗与会话查看器每 5s/2.5s 触发跨池全量发现,且 `ga4Config.ts` 仅上报异常/重连类 `daemon_health` 事件;`statusMessage.ts` 消除中断轮次重复 `Stopped` 提示;`SideToolRecords.tsx`、`SideReply.tsx`、`SideChatSheet.tsx` 与 `btw.css` 将 BTW 工具折叠标题固定为「已探索」分类统计摘要并与思考折叠左箭头对齐,展开列表改为单行省略且移除已完成勾选列,用户提问改为右对齐自适应中性灰聊天气泡(`14px 14px 3px 14px` 圆角、`--soft-surface` 底色与 `--chat-edge` 细描边,移除 `You` 前缀),并将 `Preparing`、`Waiting for reply…`、`Thinking…`、`Using tools…`、`Receiving reply…`、`Stopping…` 等运行状态统一收进底部 Composer 输入占位位置;`btwThinking.ts`、`btwProtocol.ts` 与 `btwCardState.ts` 将 BTW 回答改为按到达顺序的 `parts`(思考段/工具/正文偏移,思考最多 64 段),`SideReply.tsx` 复用主聊天 `ActivityGroupView`/`ActivityItem`/`ThinkingView`,把相邻思考与工具合并为「已探索」折叠组并让正文穿插在原位置,修复多轮思考被拼成一段、工具与正文先后顺序丢失的问题;`CompletionSettings.tsx` 与 `ManagementBinding.ts` 改为开关/下拉即时保存、输入框失焦/回车保存、API Key 独立按钮设置,移除底部 Save/Saved 栏,并为 Next Edit 与 Advanced 接入 `AnimatedCollapsibleContent` 平滑高度与透明度过渡;`composer-panels.css` 移除 `/limits` 与 Context 面板关闭/刷新按钮的边框与灰色悬停底色,并保持激活分段页签悬停对比度;`inline-input.tsx`、`useCodeReferenceDraft.ts` 与 `ComposerSuggestions.tsx` 在通过回车/Tab/点击选择 `/skill`、`/command` 或 `@mention` 替换草稿后,将光标精准定位到插入前缀末尾(如 `Use the "…" skill: ` 之后),修复光标停留在原 `/query` 字符长度处的错位;`transcriptStatus.ts`、`Transcript.tsx` 与 `ReadOnlyTranscript.tsx` 按分组内全部条目匹配当前运行 `turnId` 并保持尾部在途回复的 `replyPending`,消除子代理/Worker 在多轮工具间隙暂停时提前闪现 `just now` 与复制按钮;`AgentNavigation.tsx` 与 `composer-changes.css` 在 Agent 列表行存在 `Stop`/`Stopping…` 操作时省略重复的 `Running` 状态文案并将操作按钮贴齐最右侧;`TranscriptView.tsx` 的置顶跑道改按「读者离开的旧 `pinTop`」判定释放,不再依赖会随每条流式文本变化的 `nextHeight`/`layoutShifted`,释放只停止吸附、不写 `scrollTop`,并对 `domTurnStart` 取整及为 `< pinTop` 预留 1px 亚像素量化容差,`useTranscriptScroll.ts` 记录滚轮脱离底部的手势并让其在随后那次 `scroll` 事件中优先于布局启发式、直接按脱离跟随且距当前 `bottom` 超过 `FOLLOW_REJOIN_PX` 判定 `away`(不再扣除 `runway` 导致置顶流式回复向上滑动时不显示「回到底部」向下箭头),`ActivityGroupView.tsx` 将运行态 `.v2-running-latest` 纳入反向同步的 `AnimatedCollapsibleContent` 200ms 过渡且 `processPresentation.tsx` 在展开/收起时同步调用 `stopFollowing` 清理滚动动画,修复发送提问后向上滚被拉回底部以及流式过程组展开/收起的高度跳变与亚像素抖动;`StarfieldBackdrop.tsx`、`ChatLayout.tsx` 与管理页常规设置新增深色模式专属透明层呼吸繁星背景(`droidvisx.chat.starfield` 开关控制,以右下角为天极、主聊天长为半径在左上 1/4 象限内顺时针缓慢旋转,离屏精灵缓存 + 空闲帧率节流 + 隐藏自动休眠,不改变原有 `--surface` 底色);`projectSessionHistory.ts` 将工具结果预算裁剪移至全量消息投影尾部执行一次(消除大历史文件逐个 `tool_result` 的 $O(N^2)$ 扫描),`FactorySessionCatalog.ts` 与 `worktreeSessions.ts` 收敛目录侧车读取与并发 Worktree 目录查询,`persistentIdeRelay.ts` 在等待 IDE 握手期间加密轮询至 50ms 且 `windowDaemonPool.ts` 按每次状态切换重置 `ide.native.relay-state` 计时;`SessionViewerPanelController.ts`、`ReadOnlyTranscript.tsx`、`ReadOnlyTranscriptView.tsx`、`ActivityGroup.tsx` 与 `planAnchor.ts` 为子代理只读详情页接入 80ms 实时刷新合并与未变更快照去重、稳定虚拟行 `renderMessage` 引用、60 条消息/过程项窗口分页与增量 Plan 锚点缓存;`DefaultsSettings.tsx`、`DefaultsFields.tsx`、`ManagementDefaultsService.ts` 与 `model-picker.tsx` 将默认设置中 Spec 与子任务自主等级/三档模型的默认继承项统一为「跟随主聊天」(`Follow main chat`,子任务重置映射为 `subagentInheritTiers`),并将子任务三档拆分为独立的 `Model` 与 `Reasoning effort` 两行直选(继承状态下推理强度锁定显示对应档位的 `Inherit (Low / Medium / High)`,`selection.tsx` 与 `DefaultsFields.tsx` 过滤表单内受控 `Select` 切换选项时 `SelectBubbleInput` 冒泡的空值事件);`markdownStream.ts`、`parseMarkdown.ts`、`streamingTree.ts`、`markdownWorkerProtocol.ts`、`syntaxWorker.ts`、`syntaxQueue.ts` 与 `CodeSyntax.tsx` 完成 Markdown 混合增量流式渲染与代码高亮改造(未闭合代码围栏 $O(1)$ 直通投影、顶层稳定块 LRU 缓存与 `[id]: url` 引用定义整篇回退、尾部 `live` 块未闭合行内语法补齐、Worker 树比对引用相等零序列化快路径、Shiki `GrammarState` 按行增量染色与同 `streamKey` 队列合并、移除 150ms 轮询并改为 `CodeLine` 按行 memo 化保选区渲染),并参照 Kilo 打磨 `chat-region.css`、`theme.css`、`webview-host.css`、`Markdown.tsx` 与 `streamingText.tsx` 的 AI 回复排版与流式渐显(修正宿主 `body.vscode-dark code` 预格式化绿字渗透,统一 `21px` 行高、段落/列表间距、弱化列表序号与引用块、行内代码微描边及文件引用虚线下划线,且 `FadingText` 保持在途渐显跨度直至 `onAnimationEnd` 消除高频 token 跳闪)。`typecheck`、`lint:budgets` 与定向测试全部通过。 - `codex/telemetry-and-daemon-fixes`:GA4 媒体资源 `properties/560369275` 已注册全部 21 个 EVENT 级自定义维度与 17 个自定义指标;`daemonLifecycle.ts` 的 Windows `Win32_Process` 查询限定 `-Property ProcessId,CommandLine`;`windowDaemonPool.ts` 复用在途 `verifyRecords` 批次避免并发重复拉起 `powershell.exe`,并在发现阶段与 `retireEmpty` 中自动回收旧窗口退出后遗留的无会话孤儿 `window-daemon`;`webviewStateDelivery.ts` 在侧栏隐藏期间仅标记待重放快照,不再每 30 秒刷写 `host.view.state-delivery` 警告;`webviewHtml.ts` 忽略内联无 `src`/`href` 的 `