# HANDOFF.md — 项目交接说明 > **本轮(0.8.2 → 0.9.4)交付**:7 个版本累积。**v0.9.0 是破坏性变更(工具面 13 → 8)**,升级调用方前 > 必读下方迁移映射;其余六版为工具面瘦身与三个 GUI adapter 的真机冒烟修复。逐版细节以 `CHANGELOG.md` 为准。 > > **① v0.9.0(BREAKING)· 工具面按域合并 13 → 8**(`src/mcp/tools.ts` 实测 8 个): > - `cancel_task` / `continue_task` / `rework_task` → **`manage_task(taskId, action, …)`**(`action` ∈ `cancel` / `continue` / `rework`) > - `list_tasks` / `get_task_report` / `get_profiles` → **`query_info(type, …)`**(`type` ∈ `tasks` / `report` / `profiles`) > - `wait_any(taskIds)` → **`wait_task(taskIds)`**(批量模式;单任务 `wait_task(taskId)` 调用方式不变) > - **刻意不合并** `prepare_visual_baseline` / `approve_visual_baseline`:收益仅约 500 字符,而 > `candidateId` / `expectedDigest` / `approvalNote` 三个必填参数构成视觉基准的**防篡改摘要核对闸门**。 > - 分支约束**下沉到 handler**(合并后 schema 是 plain `z.object`——`z.discriminatedUnion` 经 SDK 序列化后 > 线上退化为空 `properties`):`action=continue` 要求 `message` 非空,分支专属字段按白名单 fail-closed。 > - 同步面:`skills/tianshu-mcp/`(SKILL + usage-examples)、`src/agents/**` 47 处运行时文案、 > GUI 工具面镜像(`check-schema-parity.mjs` 真源 ↔ 镜像 ↔ Rust 三方零漂移)。 > **旧技能副本不会随 npm 升级自动替换**,需按 `docs/agent-profiles.md` 的 `--approve-skill-update` 放行一次。 > > **② v0.8.4 · 工具面瘦身(无破坏性)**:`run_task` / `verify_task` 的 `acceptanceOverride` 线上声明骨架化, > `tools/list` **35581 → 11717 字符(−67.1%)**;工具名、参数集、校验语义全不变。因 `visual` 变为不透明对象, > 新增 handler 入口 `validateAcceptanceOverride()` 补下沉校验(否则内部非法字段会穿透 SDK 层)。 > > **③ v0.8.3 · TraeWork 缺陷修复(issue #38)**:下拉底部点击改**副作用驱动三级阶梯**——`cdp.click()` 的 > `true` 只表示元素可见,不代表原生弹窗已唤起;总探测预算不再膨胀(真机 28183ms → **19213ms ≤ 20000ms**); > 逻辑性 setup 失败不再误报 `errorType=spawn`(新增 `errorType: "setup_failed"`,由适配器自归类)。 > > **④ v0.9.1–0.9.4 · 三个 adapter 的真机冒烟修复**(每版都触到假成功或假失败): > - **0.9.1(TraeWork)**:排队提醒被误报「任务完成」(该气泡带「由 AI 生成」footer,正是完成标志); > 取消路径三处缺陷(停止按钮是图标元素、无 `click()` 致回退不可达;`return abortResult()` 缺 `await` > 致连接先断;`lastRunSignal` 因 note 措辞全仓唯一不同而恒 undefined)。新增 `scripts/smoke-traework.mjs`。 > - **0.9.2(ZCode 3.14.4)**:模型标识三套语义互不推导(面板可见标签 / 分组显示名 / `供应商/模型` 参数段), > 且模型名可含 `/` 与 `:`(实测 `inclusionai/ling-3.0-flash-sante:free`)——4 个缺陷让含冒号或三段式的模型 > **完全无法派单**;另修「顶部新建任务点击假成功」(元素在视口外、鼠标事件被静默丢弃,而 `click()` 仍返回 true)。 > - **0.9.3(Codex 26.1002)**:5 个缺陷——发送确认假成功(`messageArea` 把 composer 一起包住)、 > 连接就绪判据顺序颠倒、回复稳定判定失真(游离克隆体无布局 → `innerText` 读到 4 字符空壳)、 > CDP 断开误报进程退出、CDP 错误文案硬编码程序名(`TraeworkCdpClient` 被 6 个适配器复用)。新增 `scripts/smoke-codex.mjs`。 > - **0.9.4(Codex)**:对话文本采集丢弃纯文本节点 → `【tianshu:…】` 标记中间段丢失 → 发送确认判据 > `seenMessage` **恒 false**(而消息早已送达)。改遍历 `childNodes` + `textContent`。 > - **0.9.5(Kimi Code)**:浮层页处于 `hidden` 态时**不参与命中测试**——`elementFromPoint(菜单行中心)` > 返回舞台容器 `browser-overlay-stage` 而非那一行,合成点击被容器接收、行上零事件;但 > `clickOverlayExact` 仍返回 `clicked: true`(坐标算得出来),于是「切换模型」对话框永不出现 → > 误报 `model_unavailable`。根因是 `clickAt()` 只对**主窗口**做前置检查,浮层页从未置前; > 新增 `focusOverlayWindow()` / `overlayPageHidden()`(与 `focusMainWindow()` 同构)。 > 真机 **0/5 → 4/4**。**判据教训**:`clicked: true` 只代表坐标算得出来,**不代表事件送达**—— > 真实判据只能是后置条件回读。 > - **0.9.6(MiniMax Code)**:三处缺陷。① `projectPointExpression` 的页面内归一先「盘符大写」 > 再 `toLocaleLowerCase()`,前者恒被抵消(死代码),与 Node 侧 `normalizeProjectPath`(先小写 > 再恢复盘符)**永不相等** → 点选既有项目分组恒返回 `null` → `setup_failed`。**触发条件是项目 > 已在侧栏**,故首轮成功后才在第二轮暴露。② `pickOption` 点击后只等固定 `sleep(350)` 就回读, > 实测 **1/8 成功**(`+350ms` 仍旧值、`+1550ms` 已新值)——改为**有界轮询至收敛**后 **6/6**。 > ③ 验收层:非 git 仓库下 `requireChanges` 零变更门禁**静默跳过**(仅 [INFO])却照判 PASS; > 判定不变,改为双写 `warnings` 使其进 `[WARN]` 与报告摘要。 > 另:`hoverModel` 失败现场新增诊断快照(原本只留 `elapsed=9630ms`,无法区分四种失败原因); > 新增 `scripts/smoke-minimax.mjs`(此前 MiniMax 只有只读 probe)。 > - **0.9.7(Open Design)**:**接通此前零调用方的产物导出链路** + 三处适配器缺陷。 > ① **导出从未接线**:`export.ts` 逻辑与单测齐备,但 `run.ts` 从未调用、schema 无 `exportKind` > ——「判据齐全、接线缺失」。真机取证推翻了原实现的核心假设:0.24.1 是**浏览器式下载** > (blob → Electron `will-download`),**不弹**保存对话框(点完只出现无子控件的 `blob:` 空壳窗口, > 下载停在 `~/Downloads/.tmp`);正解是 CDP `Page.setDownloadBehavior` 指下载目录 → 点菜单项 > → 文件直接落盘。`saveViaNativeDialog` 降级为**可选兜底**(短预算试一次,失败不 return)。 > ② **首页入口漂移**:首页 `od://app/` 与会话页 `.../files/` 是**互斥形态**(`home-hero` / > `working-dir-trigger` 只在首页,会话页只有 `workspace-home-chrome`);`HOME_ENTRY` 缺后者 → > 会话页点空气 → 守卫在会话页跑 → `title` 恒 0 → `selector_drift`。并固化 `title` 的**仅首页命中**语义。 > ③ **失败态未接线**:`liveness.errorText → failed` 判据早已齐备(含单测),但 `cdp` 快照类型 / > `pollExpression` / `run.ts` poll 组装**三层从未接线** → UI 已显示失败,适配器仍报 `running` > 空等 10 分钟+。④ **工作目录回读假失败**:`working-dir-trigger` 的 `title` **语义随状态变化** > (空态是 tooltip、绑定态才是完整路径),而 `innerText` 只给末段;原判据只读 `innerText` → > 只能靠 `app-config.json` 的 `recentLinkedDirs` 旁证,而该文件**异步落盘晚 8.2 秒**(实测 > 对话框 08:30:30.6 完成、config 08:30:38.8 才写)→ 绑定成功却判 `readback` 假失败。 > 改为「`title` 像路径才采信」(盘符/正斜杠/UNC)。 > 真机端到端验证:zip → `Website-Clone.zip`(魔数 `504b0304`、`testzip()` 无损坏、3 条目); > html → `minecraft-promo.html`(178065 字节)。两种格式各跑两轮全通过。 > > **⑤ 测试基线**:全量 **1692 passed / 2 failed / 12 skipped**(1706 项,137 文件)。两处失败为 > **既存环境失败(非本仓库缺陷)**——`spawn-regression`(本机缺 `tianshu-runtime.exe`)与 `codex-flow`, > v0.9.1 已用改动前 HEAD 的 git worktree 对照确证。`tsc --noEmit` / `eslint --max-warnings 0` 全绿。 > > **⑥ 独立交付面 `mcp-gui`**:已推进到 **`gui-v0.1.1-beta.6`**(工具面同步 MCP 0.9.0 的 13 → 8), > 独立 tag 线 `gui-v*`,**不随 MCP 主包发布**。 > > **历史快照(0.8.1)**:修复 **issue #31**「ZCode / Kimi Code / Open Design 完成判定缺少 > 『曾观测到运行信号』门」(**同时补上 issue 未列出的第五个违反点 MiniMax Code**)。 > 缺陷本质:四个 driver 的 `finished` 判据只看 `stable >= stableRounds`,不要求本轮见过运行信号; > 停止按钮 / loading 选择器漂移时界面「看起来静止」,进行中的任务会在 > `stableRounds × pollInterval`(默认约 12s)后被误判成功并进入验收/返修链。 > - **RED 复现**(`.rivet/scratch/issue31-red.ts`,已转正为单测后清理):加门前四 driver 第 5 轮判 > `finished`,Codex 对照组全程 `pending`。 > - **代码**:四个 `liveness.ts` 的 `PollState` 加 `sawRunning`(运行分支置真、其余透传、`finished` 加门); > 三个 `run.ts` 在 `reobserve` 轮种子 `sawRunning: true`(照 `codex/run.ts` 既有做法,规避「恢复后 turn > 已完成 → 判不了 finished → 误落 idle_timeout」);`fix-loop.ts` 抽出 > `shouldParkAsNeedsAttention()` 并把白名单扩到五个 GUI driver。 > - **为什么必须改 fix-loop(本 issue 的实质增量)**:原白名单只含 `zcode`/`codex`,其余三个 driver 的 > `idle_timeout` 因 `autoVerify` 默认为 `true` 而绕过两个 `!autoVerify` 出口,**仍会进项目验收链**—— > 只加门的话,用户可见行为只是「仍进验收,但迟了 10 分钟」。 > - **两处偏离 issue 建议**(已在 CHANGELOG 与 issue 答复中说明):① Open Design 的产物指纹 > (`artifactSignature`)**不**计入运行信号——它在 `finished` 终态后仍会写文件(`fetchArtifactForSummary`); > ② MiniMax 的 `stopVisible` 依赖的 testid 真机未复验,加门后若采不到会**每个任务**都走 `idle_timeout` > → `needs_attention`(有意的 fail-closed:可 `continue_task` 恢复,而误判成功不可逆)。 > - **测试**:新增 `test/unit/liveness-running-gate.test.ts`(12 例,跨 driver 契约)+ > `test/unit/fix-loop-abort-parking.test.ts`(5 例);全量 unit **93 文件 / 1261 用例通过**; > `tsc --noEmit` / ESLint `--max-warnings 0` 通过。 > - **边界**:本版为纯函数与编排层修复,**未做真机验证**;四个 driver 的选择器采集层未改动。 > > **HANDOFF 快照的历史断层**:本轮已把顶部快照补到 `0.9.4`;下方 `0.7.8`–`0.8.0` 的中间快照仍未回填 > (含 issue #35/#30 恢复语义修复与 MiniMax Code 接入),那些版本的逐条记录以 `CHANGELOG.md` 为准。 > > **历史快照(0.7.7)** > **交接快照:2026-10-01 · 已发布版本 `0.7.7`(tag `v0.7.7` + npm `tianshu-mcp@0.7.7`)。** > **本轮(0.7.6 → 0.7.7)交付**:新增**阻塞等待原语** `wait_task` / `wait_any`(**issue #28**,功能请求 P6)—— > 工具面 **11 → 13**(`read` 族 +2,纯只读、免审批)。诉求:`run_task` 秒回 `taskId` 后**调用方没有任何方式等到任务结束**, > 而目标调用方(天枢 agent 会话)**回合驱动**——只在收到用户消息的回合内运行、回合之间不运行,**无法自行轮询**, > 于是每次任务完成都必须人工再发一条消息触发查询。 > - **停点**(单一判定点 `isWaitSettled(status) = isTerminal(status) || status === "needs_user"`):任务**停止推进**的时刻即应唤醒调用方; > `needs_user` 虽非终态但已停等人工(可被 `continue_task` 恢复、之后可能再次进入),不等它会空等到超时。 > - `wait_task(taskId, timeoutMs?)` 阻塞等到停点或超时;`wait_any(taskIds, timeoutMs?)` 等一组(1..20)中**数组顺序首个**停者。 > 超时 `timeoutMs` 缺省 **50000ms**(低于生态常见 60s 客户端超时)、上限 **600000ms**,超上限**钳制并如实披露**;超时返回体引导循环调用(每轮 ≈50s)。 > - **无损**:等待**纯只读**,被截断 / 连接中断 / 超时都**不影响任务本体**;`extra.signal` 让循环在请求取消时立即退出(SDK `_onclose` 会 abort 全部 in-flight handler)。 > - **物理前提已实测**:SDK 请求**互不阻塞**(探针:3s `slow` 发出后 +200ms 的 `fast` 仅 215ms 返回)——等待期间的 `cancel_task` / `query_task` 照常处理。 > - **代码**:新增 `src/tasks/wait.ts`(`waitForStops` 纯逻辑、依赖注入 `getMeta`)、`src/tasks/task.ts`(`isWaitSettled`)、 > `TaskManager.waitForStops`;`src/config/schema.ts`(常量 + 两个 ParamsSchema + `clampWaitTimeout`); > `src/mcp/tools.ts`(+2)、`handlers.ts`(`waitTaskHandler`/`waitAnyHandler` + `HandlerExtra`)、 > `formatter.ts`(`waitSettled`/`waitedMs`)、`server.ts`(透传 `extra` + instructions)。 > - **测试**:新增 `test/unit/wait-task.test.ts`(8 例)+ `test/integration/wait-task.test.ts`(6 例);`protocol.test.ts` 同步 13 工具真值表。 > **全量 1447 passed / 12 skipped**(1459 项,122 文件)。 > - **文档**:新增 [等待原语](docs/wait-task.md) 双语;README / ARCHITECTURE / tianshu-integration / core-principles / SKILL / usage-examples 同步 11→13 与工具表。 > - **发布链**:见下方「发布流程」——GitHub 主仓 + Gitee 镜像 Release、npm `tianshu-mcp@0.7.7`。 > > **日志台 GUI(独立交付面,同会话并行交付)**:`mcp-gui` 推进到 **`0.1.1-beta.3`**(计划 `.trae/documents/mcp-gui-insights-0.1.1-plan.md` 的**批次三,也是最后一批**)—— > **A5 工作区新增「基线」分区**(动工前 `baseline.json` 摘要 + 与最新报告改动对照)、**A6 事件流新增「阶段」视图**(状态跃迁甘特, > 按需读一次全量,末段标「进行中」不编造时长)、**A9 洞察页新增「磁盘占用」**(总量 / logs 占比 / TOP 20 / **只提示不删除**的相对判据)、 > **A8b 深链 `tianshu://task/`**(单实例 + 协议注册 + 冷/热启动;Rust 侧处理,不给 webview 多余权限)。 > A1–A9 至此**全部交付**。详见下方「独立交付面 · 日志台 GUI **「洞察」批次三**」。 > **历史快照(批次二)**:`mcp-gui` 推进到 **`0.1.1-beta.2`**(A7 结构化筛选 / A4 多任务对比 / A8a 命令面板)。 > **历史快照(批次一)**:`mcp-gui` 推进到 **`0.1.1-beta.1`**—— > 侧栏新增「洞察」整页:**A1 效能看板**(按 Agent / 项目的任务数、成功率、平均轮次、一次通过率、平均验收耗时、报告缺失)+ **A2 失败归因** > (`errorType` / 失败检查项 / 阻塞问题 / 代码信号 四类 TOP)+ **A3 时间趋势**(按天 / 按周,任务量柱 + 成功率与返修率折线); > 后端新增**只读**聚合命令 `get_insights`(`insights.rs` + `timestamps.rs`)。 > 另修掉真机缺陷**「更新窗口正文只剩一行标题」**——更新清单 `notes` 改取与发行页**同源**的正文(取数 fail-closed), > 并回填 `update/gui/latest.json` / `latest-gitee.json`;**版本号不升**,详见下方「更新窗口正文(更新清单 `notes`)修复」。 > > **历史快照(0.7.6)** > **交接快照:2026-09-30 · 已发布版本 `0.7.6`(tag `v0.7.6` + npm `tianshu-mcp@0.7.6`)。** > **本轮(0.7.5 → 0.7.6)交付**:修掉 **issue #27** 的三条 ZCode 缺陷 —— ① 项目采集渠道分裂导致的绑定死锁 > (3.14.3 上 `workspace-item-*` **并未从 DOM 消失**,只是被滚出视口:真机实测 **42 个节点中 40 个不可见**; > 旧实现不做可见性过滤,幽灵项使 `if(!out.length)` 短路恒为假,唯一可信的菜单渠道永不执行,任务卡死在 > `project_mismatch`);② 运行期 CDP 断连无恢复入口(现在**首次断连重连观察一次、绝不重发**, > 重连失败或再次断连落 `needs_user(setup_recovery)`,并附「进程是否仍在」「窗口是否仍有运行信号」两侧事实); > ③ `reasoningLevel` 未实现(现在在**模型确认之后**读界面实际档位集合校验,越权在**发送前**报错, > 未指定则不触碰界面)与**两级模型菜单**(模型项在 provider 分组二级子菜单里,**必须 hover 才渲染**, > 分组 testid 已漂移为 `chat-model-select-group-registry-provider:`)。此外回落导入路径(点击始终不落地时)也已接线。 > **真机测试另暴露权限菜单契约漂移**(3.14.3 权限项 role 是 `menuitemradio`/`menuitemcheckbox` 而非 `option`, > 可见名在项内**直接文本节点**里、后面跟说明文本),一并修复。 > **✅ 真机端到端已跑通**(ZCode `3.14.3.7762` / Windows 10):`npm run smoke:zcode` → `succeeded` / > `reply_stable`,权限落在「完全访问」,耗时约 40 秒,**产物 `done.txt` 内容为 `issue27-ok`**。 > 取证入口:`npm run probe:zcode -- dom-contracts|models|permission`。 > 详见 `CHANGELOG.md` 的 `[0.7.6]` 与 `docs/release-v0.7.6.md`。 > > **历史快照(0.7.4)** > **交接快照:2026-09-28 · 已发布版本 `0.7.4`(tag `v0.7.4` + npm `tianshu-mcp@0.7.4`)。** > **本轮(0.7.3 → 0.7.4)交付**:修掉 **issue #24** —— ZCode `3.14.x` 删除了 `data-project-path` 与 > `data-testid^="workspace-item-"` 两处 DOM 契约,而 `pathOf()` 只认这两个来源,于是 > `workspaceBinding().projectPath` 恒空、`cdp.projects()` 恒 `[]`,**所有带 `projectPath` 的派单**都恒败于 > `project_mismatch`(失败在发送任务书之前,`autoFixRounds` 形同虚设)。绑定判据改为**分层** > (`boundProjectVerdict()`):路径可得时严格判等(3.11.x 语义不变),无路径渠道时按**显示名 + 全局同名消歧** > (同名即 `project_ambiguous`,绝不猜测);显示名**不写进 `projectPath`**,故无项目模式的 > `workspaceIsDefault` 判据不受影响。另修掉「菜单是否恰好还开着」这条隐式时序前提,并给 > `project_mismatch` 补上「触发器文本 / 菜单勾选态 / 路径回读」诊断。新增 > `npm run probe:zcode -- dom-contracts` 作真机取证入口。全量 **1383 passed / 12 skipped**(1395 项,115 文件)。 > ⚠️ **本机无 ZCode 3.14.x,未真机复验**:根因在 `linkedom` 夹具上已 RED→GREEN 决定性复现 > (先确认 3 处红灯,再修到全绿),issue 的真机实测数据未被独立复现 —— 有 3.14.x 环境的维护者请跑 > `npm run probe:zcode -- dom-contracts` 复核。详见 `CHANGELOG.md` 的 `[0.7.4]` 与 `docs/release-v0.7.4.md`。 > 发布链上有两个需要知道的坑(都是 `release.yml` 的前置门禁,不满足时 npm 可能已发但 Release 建不出来): > ① 该 commit 必须有**成功的 CI**(`Require successful CI for this commit`); > ② 正文必须来自 `docs/release-v.md` 与 `.en.md`。 > `v0.7.1`(CI 红)与 `v0.7.2`(缺正文文档)的 GitHub 发行版因此**改用手动 API 补建**,两者的 tag 未动。 > **本轮(0.7.0 → 0.7.1)交付**:内置 agent `opendesign`(Open Design 桌面端)**从「开发中」推进到完整可派发**—— > 选择器按产品产物取证落地、12 步执行链全部接线、并接入验收 → 自动返修 → 再验收闭环。 > **同仓另有一条独立交付面**:日志台 GUI(`mcp-gui/`,**正式版 `0.1.0`**,独立 tag `gui-v*`,独立演进;本轮**转正 + 新增更新日志面板**——桌面端独立的更新窗口(启动静默检查命中即弹、`忽略此版本` 只压自动提示、显式展示本次使用的更新源与探测结果、正文为该版本的双语发行说明),发布链同步支持正式版(tag 过滤器 `gui-v*`、按 tag 形态判定 pre-release、发行版正文取自 `docs/release-gui-v<版本>.md` + `.en.md`);前几轮已按真机反馈补「已成功」指标、数据目录上移、设置面板改居中弹窗、「刷新」上移到数据目录行,并修好「手动下载」随源跳转)。 > **issue #25 已交付**:新增**独立交付面** `mcp-gui/`(「Tianshu-mcp 日志台」,Tauri 2.x + Vue 3)——本地只读查看四类日志与任务产物;MCP 主包 GUI 侧零改动。签名密钥等 4 项 Secrets **已由维护者配置完成**(2026-09-27)。 > **issue #25 已按 DoD 全部达成回复并关闭**(2026-09-27):DoD 2(F1~F12)与 DoD 9(U4~U8)由维护者在 Windows 10 真机逐项验收通过; > 验收中发现并修掉「更新后旧版本不消失」(GUI `0.1.0-beta.4`),已发布并完成真机端到端复现——详见下方「升级路径修复」小节与 `docs/issue-25-gui-real-machine-record.md`。 > **issue #18~#22 五项增强已全部交付**(v0.6.3~v0.6.7,每版各自完整发布),**五个 issue 均已回复并关闭**(2026-09-24)。 > **#18~#22 的真机记录已全部补齐**(2026-09-25):见 [issue #19/#20/#21/#22 真机记录](docs/issue-19-22-real-machine-record.md) 与 [issue #18/#19/#21 真机记录](docs/issue-18-21-real-machine-record.md);各 issue 另附真机证据补充评论。 > **✅ 此前那条会阻断全部 Codex 派发的适配器缺陷已修复**(`26.917.9434` / `26.930.4958.0` 模型触发器回读混入整条思考等级条 → `model_mismatch`),随 **v0.8.2** 交付(issue #34),详见下方「真机取证补记」与记录文件 §5。 > **✅ Open Design 真机链路已跑通(2026-09-28)**:先前「主线程停在启动期、CDP 接不上」的阻塞已定位并修复 —— > 根因是**固定 `--remote-debugging-port` 被不承载窗口的 launcher 进程抢占并驻留**,真窗口进程绑定失败后 > `/json/list` 恒为 `[]`(端口连得上却无 page target)。改用 `=0`(各拿随机端口)+ 按 `DevToolsActivePort` > 定位真窗口后,**真机 4 秒接管**。整条链路已验:绑定工作目录 → 选模型/设计系统/设计方向 → 输入发送 → > 轮询完成 → **产物取回**(`onboarding-guide.html` 落进任务目录,视觉验收识别入口)。 > 剩余:zip 导出方式(产品主进程接管下载,`state=canceled`)待后续优化;`docs/opendesign-cdp.md` §4.4 的证据表仍可补。 > 下面两段 2026-09-27 的旧结论保留作对照(当时的判断已被上一条取代)。 > **2026-09-27 复测(联网环境)**:再次执行 `node scripts/probe-opendesign.mjs all --launch --save` —— 安装探测、数据目录推导与 `app-config.json` 读取全部正常(`0.24.1` / `release-stable-win`), > 但启动后 **90000ms 内 CDP 始终未就绪**(stderr 已宣告 `DevTools listening on ws://127.0.0.1:9889/...`,主线程仍卡在启动期请求),与上一条结论一致 —— > 阻塞在**应用自身启动路径**,与本机是否有外网无关。实测现场见 `docs/opendesign-evidence/opendesign-probe-2026-09-27T13-36-09-530Z.md`; > 真机 DOM 采集仍需在能正常启动 Open Design 的终端执行。 > 本文写给**接手本仓库的人**:先说清「这是什么、现在到哪一步」,再给出「怎么跑、怎么改、哪里会踩坑」。 > 工作区规则见 `AGENTS.md`(gitignore,仅本地);安装与用法见 `README.md`,本文不重复,只做导览与状态记录。 --- ### 内置 agent · ZCode 项目绑定 / CDP 恢复 / 思考档位(`0.7.6`,**已发布** 2026-09-30) - **范围**:`src/agents/zcode/**`(`cdp.ts` / `run.ts` / `model.ts` / `selectors.ts`)、`src/mcp/handlers.ts` 的 zcode 参数级校验;测试 `test/unit/zcode-dom.test.ts`、`test/unit/zcode-thought-level.test.ts`(新)、 `test/integration/zcode-flow.test.ts`。**未改** `traework/cdp/client.ts`(重连是适配器层职责)、 **未改** `pathOf()` 的既有语义。 - **根因(真机取证,不是静态推理)**:`scripts/probe-zcode.mjs` 与三个一次性探针在 ZCode `3.14.3.7762` 上实测到 —— `[data-testid^="workspace-item-"]` **42 个节点中 40 个不可见**(`rect.top` 从 720 起, 视口高 640)、`data-project-path` 为 0;模型菜单的 provider 分组 testid 是 `chat-model-select-group-registry-provider:new-provider`,模型项**只在 hover 分组后**才渲染 (`chat-model-select-item-custom:new-provider:step-5-preview`);权限项 role 为 `menuitemradio`/`menuitemcheckbox`、可见名是项内直接文本节点;思考档位是二值 `chat-thought-level-select-item-{disabled,enabled}`,选项仅在菜单展开时挂载。 - **改法**:① `projects()` 加 `ZCODE_DOM.visible` 过滤、去掉 `if(!out.length)` 短路、两渠道合并且 同名菜单项覆盖侧边栏项;② `clickProject` 返回 `{clicked, reason}`,`ensureProjectBound` 在 「从未成功点中」时放开回落导入;③ 新增 `guardRuntimeCdp()` 统一护栏(发送阶段 + 主循环), 首次断连走 `reconnectOnce()`(`launchTimeoutMs` 夹到 20s)继续观察,否则抛 `ZcodeRuntimeDisconnect` 由外层归位 `needs_user(setup_recovery)`;④ `parseZcodeModel(model, level)` + `thoughtTierSetOf/assertZcodeLevelSupported` 的 fail-closed 档位校验,`cdp.thoughtLevelSnapshot()` 按需展开档位菜单读取后关闭;⑤ `clickExact(key, value, "hover")` 支持只 hover 不发键, 模型段用 hover 展开 provider 分组,两轮候选皆空时重开菜单再试一轮;⑥ 权限候选补无 role 限制的兜底、 `clickExact` 标签解析优先取直接文本节点。 - **验证**:`npm run typecheck` / `npm run lint` 全绿;zcode 相关 unit + integration 全绿(新增 issue #27 的采集、reasoningLevel、两级菜单、权限契约用例);**反向验证**过回落导入用例 (去掉 `unclickable` 判定即红)。真机:`probe:zcode -- dom-contracts|models|permission` 取证, `smoke:zcode` 端到端 `succeeded`(产物 `done.txt` = `issue27-ok`)。 - **遗留**:真机上窗口被遮挡时合成点击仍会被吞(Chromium 节流),派发前请把 ZCode 窗口置前台; `max`/`低` 等档位在当前模型(step-5-preview)不存在,会按设计报 `reasoning_level_invalid`。 ### 内置 agent · Open Design 适配器接线完成(`0.7.1`,**已发布** 2026-09-28) - **范围**:`src/agents/opendesign/**`(新增 `transport.ts` / `menu.ts` / `send.ts` / `recovery.ts`,重写 `run.ts`、`selectors.ts`、`cdp.ts`), 以及 `src/loop/fix-loop.ts`、`src/tasks/task-manager.ts`、`src/mcp/context.ts` 的 opendesign 接线。**未改 `tianshu-mcp-web/`**。 - **选择器取证(不是截图目测)**:Open Design 的 Web 前端产物(`resources/open-design-web-standalone/apps/web/.next/static/chunks/*.js`) **系统性使用 `data-testid`**(600+),`primary` 全部取自这些真实钩子(`chat-send` / `working-dir-trigger` / `composer-design-system-trigger` / `design-system-search` / `home-hero-template-trigger` / `home-hero-input` / `home-hero-submit` …); 停止按钮无 testid,用产品自身的 `class="composer-send stop"` + `aria-label=`;菜单项统一 `role="option"`。 - **真机新发现 → 传输层必须双路径**(`transport.ts`):Open Design 的浏览器进程 `/json/version` 正常,但 `/json` 与 `/json/list` **连接成功后长时间无响应**(Target 枚举走 UI 线程,产品启动期主线程被自身计费/遥测请求占住)。 故先走 HTTP `/json`(≤3s 上限),失败即回退浏览器级 WebSocket:`Target.getTargets` + `Target.attachToTarget(flatten)` 拿 `sessionId`(页面级命令带 `sessionId`,`Target.*`/`Browser.*` 不带)。就绪探测与传输**共用同一份目标枚举实现**。 - **完整 12 步**:接管/自启 → 连接主窗口(要求输入框就绪)→ 版本门禁 → 选择器守卫与布局盘点 → 绑定工作目录(含原生对话框 + **回读**) → 模型(精确匹配 + 回读,写回 `actualModel`)→ 设计系统(搜索过滤 + 精确点选 + 回读)→ 设计方向(只支持原型/文档/网站复刻) → 输入任务书(可信输入 + 回读含标记)→ 发送(**只点一次、绝不重发**、三证据有界确认)→ 三信号轮询(停止按钮 / 对话文本哈希 / 产物文件指纹)→ 终态。 - **验收-返修闭环**:`fix-loop.ts` 在 opendesign 验收失败时生成**项目根** `.opendesign/plans/opendesign-fix-r.md` (Open Design 只能读它工作目录白名单内的文件),并同会话发送返修指令;`continue_task` / `rework_task` 已支持 opendesign。 - **细粒度事件流(对齐 issue #18 词表)**:除 `task_dispatched` / `file_modification_started`,**全部「卡在等人」的出口**都上报 `awaiting_user_authorization`(既有实例无法接管 / 停在登录引导页 / 工作目录绑定失败 / 停止按钮久亮且对话与产物全静止转人工确认 / 初始化阶段环境不可自愈), 清理残留原生对话框与经原生「选择文件夹」绑定目录时上报 `confirmation_dialog_detected` —— `query_task` 的 `recentEvents` 因此能看见「卡死等人」, 不必再靠盲等或超时判断。 - **本轮修掉的两个真实缺陷**:① `inputText()` 曾把 `{found,value,length}` 当 `string` 返回 → 发送前回读抛错、被误报 `setup_failed`; ② 工作目录绑定失败曾落成「非硬失败的 `setup_failed`」→ 编排器按普通失败处理、用户无法 `continue_task`,现一律转 `needs_user`。 - **测试**:新增 **63** 用例(集成 `opendesign-flow` 14 → **18** + `opendesign-rework-loop` 5(仅 Windows:派发本身有平台门禁);单测 `opendesign-{menu,send,transport,recovery}` 8/8/7/14;`opendesign-discovery` 扩到 33,含端口避让), 桩扩展在 `test/fake-cdp.ts`(Open Design 页面桩,语义键由注册表自身反查,选择器漂移时桩会一起失败)。全部**不依赖本机安装 Open Design**、不联网。 - **真机冒烟(2026-09-27)暴露并修掉 6 个只在真实窗口上才显形的缺陷**(单测全绿时它们一个都不显形): ① **CDP 接不上** —— 固定 `--remote-debugging-port` 被**不承载窗口的 launcher 进程**占住并驻留, 真窗口进程绑不上,于是 `/json/version` 正常而 `/json/list` 恒为 `[]`;改用 `=0`(两进程各拿随机端口) 并按 **`DevToolsActivePort`** 定位真窗口,真机 **4 秒**接管成功。 ② **就绪判据在 `instance.ts` 有一份不同步的副本**(缺 `^od://`),而真机页面正是 `title=OpenDesign`(**无空格**)+ `url=od://app/` —— 两个判据都不命中 → 改成**单一真源**并容忍有无空格。 ③ **主窗口排序会被同标题辅助页抢走**(`od://app/desktop-pet` 标题也是 `OpenDesign`)→ 辅助页压到最低档。 ④ **选择器候选按并集累加**:primary 与 fallback 各命中一个**不同**元素就报「多命中」 (真机 `modelTrigger` = chip(button) + 外层 div)→ 改为**命中即停**(primary 优先)。 ⑤ **工作目录回读不认真实 UI**:绑定成功后触发区只显示**末段目录名**(`test`)→ 增加 「末段相等 **且** 产品旁证 `recentLinkedDirs` 命中目标」这一档;**缺旁证仍拒绝**(末段太宽,会误判)。 ⑥ **预算照搬 ZCode 默认值**:`setupRecovery` 120s,而真机**单是「启动 + 连接主窗口」就吃掉 77s**, 绑目录的 `15s+20s` cap 也会被咬断 → 分别放大到 **300s** 与 **30s+60s**。 - **真机冒烟尚未跑通到「派发」**(2026-09-27 收尾状态):链路停在**步 3「绑定工作目录」**。 适配器的行为是正确的 fail-closed —— 原生对话框流程走完(编辑框内容正确、确定按钮可用、对话框正常关闭), 但应用没接受:`recentLinkedDirs` 未更新、触发区仍显示「工作目录」,于是转 `needs_user` (可 `continue_task` 续跑),**没有把「对话框关了就当成绑定成功」**。 已加的尽力路径:补 Enter 导航、发官方 `BFFM_SETSELECTIONW` 设选中项、按 `id=1152` 唯一化对话框歧义、 歧义时把每个候选的 hwnd/标题写进错误、绑定流程补阶段日志。**Enter 与 BFFM 在本机 0.24.1 上均未让应用接受** ——已在代码注释里如实标注,不当成已解决。可试的下一步:① 面板里的「最近目录」 (本轮实测点开后**没有**子列表,只有 `选择目录` / `最近使用的目录` 两项,故当前不可用); ② 改用 `IFileDialog` 的 COM 接口而非 Win32 消息;③ 在能稳定复现的机器上抓对话框的选中项控件。 - **版本边界**:MCP 主包 `0.7.0 → 0.7.1`(`package.json` + `src/version.generated.ts` 同提交),**已打 tag `v0.7.1`、已发布 npm**; `mcp-gui` 独立版本线不受影响(**不迭代该版本**,符合 `AGENTS.md`)。 --- ### 独立交付面 · 日志台 GUI **「洞察」批次一(效能看板 / 失败归因 / 时间趋势)**(`mcp-gui/`,`0.1.1-beta.1`) - **计划文档**:`.trae/documents/mcp-gui-insights-0.1.1-plan.md`(A1–A9 分三批落地,**本批 = A1 + A2 + A3 + 聚合取数通道**)。 - **范围**: - **新增**:`mcp-gui/src-tauri/src/insights.rs`(只读聚合,含 4 项 Rust 单测)、 `mcp-gui/src-tauri/src/timestamps.rs`(最小 UTC 解析 `parse_iso_ms`,含 5 项单测)、 `mcp-gui/src/core/insights.ts`(比率 / TopN / 趋势补齐 / 周历聚合 / mock 用的 TS 聚合镜像)、 `mcp-gui/src/components/InsightsPage.vue`、`mcp-gui/test/insights.test.ts`(22 项)、 `docs/release-gui-v0.1.1-beta.1.md` + `.en.md`; - **修改**:`src-tauri/src/{models,lib}.rs`(`InsightsRequest` / `InsightsSummary` / `InsightsGroup` / `InsightsDay` / `InsightsReason` / `InsightsResult` + 注册 `get_insights`)、`src/api/{types,gui-api,tauri,mock}.ts`、 `src/App.vue`(**三态 → 四态** + 侧栏「洞察」插在「任务列表」之后)、`src/components/AppIcon.vue`(`insights` 等新图标)、 `src/stores/app.ts`(`insights` 状态 + `loadInsights()`)、`src/i18n/{zh-CN,en-US}.ts`、`src/styles.css`(§21 洞察页样式)。 - **口径(改这里之前先读,Rust 与 TS 必须同步)**:日期按 **UTC** 分桶(`createdAt` 前 10 字符);**按周以周一为周始**(跨月 / 跨年归并); 「一次通过」= `succeeded` 且 `roundsUsed <= 1`;返修率 = `roundsUsed > 1` 占比;**只统计每个任务的最新一轮报告** (故「返修后才成功」的任务,其更早一轮的失败**不进归因**——有意为之);平均验收耗时只算起止时间**都可解析**的轮次; 分母为 0 显示 `—`;**全程只读、不提供删除 / 清理**。 - **职责边界(计划 D4)**:Rust 只做**扫描 + 解析 + 计数/求和**;比率 / TopN / 趋势补齐 / 周历聚合全在前端纯函数里(可单测)。 - **验证**: - 本机门禁:`typecheck` / `lint` 全绿;`vitest` **125 passed**(本批 +22);`check:schema` 报「GUI 版本号一致(0.1.1-beta.1)」; - `cargo fmt --check` 通过;**本机仍无法跑 `cargo clippy` / `cargo test`**(依赖构建脚本需要 MSVC 链接器,与计划事实 21 一致) → **Rust 门禁交 `gui.yml`**(计划 D10); - 渲染实测(headless Edge + mock):侧栏出现「洞察」,三分区可切换,A1 两张表有数据、A3 按天/按周切换与柱线均渲染, 归因分区在 fixtures 下为空态(**印证口径**:fixtures 里各任务最新一轮报告全绿,失败项在更早一轮)。 - **版本边界**:GUI 独立线 `0.1.0 → 0.1.1-beta.1`(`package.json` / `package-lock.json` 两处 / `tauri.conf.json` / `Cargo.toml` 四处同步); **MCP 主包不受影响**(`AGENTS.md`:`mcp-gui` 不迭代主包版本)。 - **后续批次**:`0.1.1-beta.2` = A4 多任务对比 + A7 结构化筛选增强 + A8a 命令面板; `0.1.1-beta.3` = A5 基线漂移 + A6 状态跃迁甘特 + A9 磁盘占用 + A8b 深链(**唯一有架构风险项**,需 Tauri 插件 + 协议注册)。 --- ### 独立交付面 · 日志台 GUI **「洞察」批次三(基线 / 阶段甘特 / 磁盘占用 / 深链)**(`mcp-gui/`,`0.1.1-beta.3`) - **计划文档**:`.trae/documents/mcp-gui-insights-0.1.1-plan.md` §6(A5 + A6 + A9 + A8b)。**A1–A9 九项至此全部交付**。 - **A5 基线漂移**: - 新增 `src-tauri/src/baseline.rs`(`read_baseline`,容错解析 `tasks/<任务>/baseline.json`,含 4 项单测)+ `models.rs` 的 `BaselineRequest` / `BaselineInfo` + `lib.rs` 注册(任务 ID 为空直接报错,不静默给空基线); - 新增 `src/components/BaselinePanel.vue`(工作区第 5 个分区)+ `stores/app.ts` 的 `TabKey += "baseline"` / `loadBaseline()`; **与最新报告对照**(报告按需读取、按任务缓存);缺失 / 损坏一律提示「没有保存的动工前基线」; - fixtures 新增 `tasks/tsk_20260926114012_a1b2c3/baseline.json`(结构对齐 `src/verify/git-baseline.ts`),供预览与探针取证。 - **A6 状态跃迁甘特**: - `models.rs` 的 `ReadEventsRequest` 新增 `full`(`#[serde(default)]`);`event_stream.rs` 在 `full = true` 时用 `tail::read_whole` 读全量、**解析逻辑与窗口模式共用同一段代码**,失败退回尾部窗口;**`full = false` 行为逐字节不变**; - 新增纯函数 `src/core/timeline.ts`(`buildStages` / `totalMs` / `stageShare`)+ `src/components/TimelineGantt.vue`; `EventTimeline.vue` 新增 **列表 / 阶段** 分段切换(首次切到阶段才读全量,同一任务只读一次;刷新会丢弃缓存重读); - `stores/app.ts` 新增 `eventsFull` / `eventsFullTaskId` / `loadEventsFull()` / `invalidateEventsFull()`。 - **A9 磁盘占用**: - 新增 `src-tauri/src/diskscan.rs`(`scan_disk_usage`,只 `stat`、只统计 `logs/` 与 `tasks/` 下所有子目录、TOP 20,含 4 项单测); - `core/insights.ts` 新增 `medianOf` / `cleanupHints` 与两个**具名相对阈值常量**(`CLEANUP_HEAVIEST_RATIO = 0.5` / `CLEANUP_MEDIAN_MULTIPLE = 2`);新增 `src/components/InsightDisk.vue`(洞察页第 5 个子分区); - **只提示不删除**:页面与命令都没有清理 / 删除入口(探针专门断言「页面上没有任何删除 / 清理按钮」)。 - **A8b 深链**: - `Cargo.toml` 新增 `tauri-plugin-single-instance`(features = ["deep-link"],**必须最先注册**)与 `tauri-plugin-deep-link`; `tauri.conf.json` 声明 `plugins.deep-link.desktop.schemes = ["tianshu"]`; - `lib.rs`:`queue_deeplinks()`(冷启动 `get_current` + 热启动 `on_open_url` **都只入队并发 `gui://deeplink` 信号**, **队列是唯一事实来源**,避免冷启动丢链接)+ `take_pending_deeplinks` 命令 + `AppState.pending_deeplinks`; - 新增 `src/core/deeplink.ts`(`parseDeepLink` / `firstDeepLinkTarget`,ID 白名单 `[A-Za-z0-9_-]`,含 `..`/`%2e` 直接拒绝); `App.vue` 挂载时与收到信号时走同一次「取队列 → 解析 → 路由」,无法识别的链接**如实提示**; - **权限口径(有意偏离计划的一处,已记录)**:计划 §6.4 提到给 `capabilities/default.json` 加 `"deep-link:default"`, 但深链**全部在 Rust 侧处理**、前端不调用该插件的 JS API,因此**不加**这项能力——少给 webview 一个插件能力, 与 §16.8 的最小权限口径一致(见 `ARCHITECTURE` §16.10)。 - **验证(本机)**:`check:schema`(版本一致 `0.1.1-beta.3` + 三方词表无漂移)/ `typecheck` / `lint` / `vitest`(**169 passed**,15 文件,较批次二 +17)/ `vite build` 全绿;`cargo fmt --all --check` 通过 (**本机仍不跑 `cargo clippy` / `cargo test`**,Rust 门禁交 `gui.yml`,与 issue #25 决策 D2 一致)。 - **模拟真机**:`puppeteer-core` + 本机无头 Edge 打开 mock 预览,DOM 断言 **14/14 通过**(深链空队列不报错;基线分区 显示摘要并与报告对照、无基线任务如实提示;阶段视图 12 段含末段「进行中」与阶段合计、切回列表仍有事件行; 磁盘分区四项读数 + TOP 表 + 提示且**无任何删除按钮**;无 `pageerror`)。探针为临时文件,未入库。 - **版本边界**:`mcp-gui` 四处版本同步 `0.1.1-beta.2 → 0.1.1-beta.3`(`package.json` / `package-lock.json` 两处 / `src-tauri/tauri.conf.json` / `src-tauri/Cargo.toml`);**MCP 主包不受影响**。 - **发布文档**:新增 `docs/release-gui-v0.1.1-beta.3.md` + `.en.md`(发布 job 的正文来源,**缺文档直接失败**,计划 D11)。 - **后续(需维护者的动作)**: 1. **真机验收**(Windows 10 安装 Release 安装包):逐项核对 A5 基线 / A6 阶段 / A9 磁盘 / A8b 深链 (深链需**冷启动与热启动两条路径**都试:命令行 `start tianshu://task/`;若冷启动不通则按 §6.4 的 **降级预案**记录平台差异,不动其它批次内容); 2. 批次三真机通过后,可按计划 §6.5 由维护者决定**转正 `0.1.1`**(新增发布文档、打 tag `gui-v0.1.1`)。 --- ### 独立交付面 · 日志台 GUI **「洞察」批次二(结构化筛选 / 多任务对比 / 命令面板)**(`mcp-gui/`,`0.1.1-beta.2`) - **计划文档**:`.trae/documents/mcp-gui-insights-0.1.1-plan.md` §5(A4 + A7 + A8a;**批次三** = A5 + A6 + A9 + A8b 仍待交付)。 - **A7 结构化筛选增强**: - Rust:`src-tauri/src/models.rs` 的 `TaskFilter` 新增 `error_type` / `dry_run` / `reworked` / `has_visual`(全部 `#[serde(default)]`, 旧调用方不传不失败);`scanner.rs` 的 `matches_filter` 实现四项,并新增单测 `matches_filter_supports_new_dimensions`。 - 前端:`src/api/types.ts` 同步四项;`src/core/filter.ts` 的 `emptyFilter` / `filterTasks` / `facetValues`(新增 `errorTypes` 分面)同口径实现; `OverviewPage.vue` 筛选浮层新增 4 个控件(错误类型用分面下拉,另三项为「全部 / 是 / 否」三态)。 - **口径**:`reworked` = `roundsUsed > 1`(**1 轮不算返修**);`hasVisual` = `artifacts.reportHtml` 非空;`errorType` 精确匹配(空串 = 不限)。 Rust 与 TS **双份同口径**(与「双份 schema」同一约束,见 `ARCHITECTURE` §16.9)。 - **A4 多任务对比**:新增 `src/components/InsightCompare.vue`(洞察页第 4 个子分区)+ `core/insights.ts` 的 `compareTasks` / `bestOf` / `nextCompareSelection` / `COMPARE_MAX`;`stores/app.ts` 的 `InsightsState` 新增 `compareIds` / `compareReports` / `compareLoading` 与 `toggleCompareTask` / `clearCompareTasks`。 勾选上限 4(满额时**明确提示、不顶替**);报告**按需**读最新一轮 `report-<轮次>.json` 并按 `taskId` 缓存; 切换数据目录清空勾选与缓存;缺失值一律 `—`(**不编造时长与结论**)。 - **A8a 命令面板与快捷键**:新增 `src/core/hotkeys.ts`(`matchHotkey`,面板打开时只认关闭键)+ `src/core/palette.ts` (`buildStaticCommands` / `buildTaskCommands` / `rankCommands` / `fuzzyScore` / `stepIndex`)+ `src/components/CommandPalette.vue`; `App.vue` 挂载面板并在 window 上统一监听分发(`Ctrl/Cmd + K` 开关、`Ctrl/Cmd + R` 刷新并 `preventDefault`、 `Esc` 关闭),侧栏新增常驻入口。**动作实现只在 `App.vue`**(面板只派发命令 `id`);任务命令**按输入实时检索**(见下条「发布后修复」)。 - **验证(本机)**:`check:schema`(版本一致 `0.1.1-beta.2` + 三方词表无漂移)/ `typecheck` / `lint` / `vitest`(**152 passed**,13 文件,较批次一 +26)/ `vite build` 全绿;`cargo fmt --all --check` 通过 (**本机仍不跑 `cargo clippy` / `cargo test`**,Rust 门禁交 `gui.yml`,与 issue #25 决策 D2 一致)。 - **模拟真机**:`puppeteer-core` + 本机无头 Edge 打开 mock 预览(`vite preview`),DOM 断言 **13/13 通过** (新筛选控件齐全、返修=是 实际生效 4→1、`Ctrl+K` 开面板并模糊命中后跳转到洞察页、对比矩阵列头与 11 行指标齐全、 选满 4 个不越界、无 `pageerror`);命令面板改实时检索后另补 **6/6**(空输入只列导航 / 目录、按 ID 片段与任务书关键字 实时命中、回车直接打开该任务、无匹配给空态、无 `pageerror`)。探针脚本均为临时文件,未入库。 - **版本边界**:`mcp-gui` 四处版本同步 `0.1.1-beta.1 → 0.1.1-beta.2`(`package.json` / `package-lock.json` 两处 / `src-tauri/tauri.conf.json` / `src-tauri/Cargo.toml`),随本版发布 tag `gui-v0.1.1-beta.2`; `update/gui/latest*.json` 由发布 job 自动生成并提交(`[skip ci]`)。**MCP 主包不受影响**(`AGENTS.md`:`mcp-gui` 不迭代主包版本)。 - **发布文档**:新增 `docs/release-gui-v0.1.1-beta.2.md` + `.en.md`(发布 job 的正文来源,**缺文档直接失败**,计划 D11)。 - **发布(2026-10-02)✅**:tag `gui-v0.1.1-beta.2` → `78f9722`。`GUI` workflow 的 **5 个 job 全 success** (`Schema parity` + 三平台 `Build` + `Publish release (GitHub + Gitee)`);三平台 job 内含 `cargo fmt --check` / `clippy -D warnings` / `cargo test`,**本机跑不了的两道 Rust 门禁由此通过**(含新增的 `matches_filter_supports_new_dimensions`)。 GitHub 侧 pre-release 已创建(**8 个产物**、正文 7544 字符 = 完整双语说明);Gitee 侧 pre-release 亦已创建 (两端各 3 个更新载体下载地址实测 **HTTP 200**,共 6 个);`gui-v*` **未触发** `release.yml`(该 tag 下只有 GUI 一个 workflow 运行)。 发布后两仓各多一个机器人提交(GitHub `b9eb516` / Gitee `7eecc83`),已合并为 `e468c31` 并回推两端收敛。 - **⚠️ 发布后修复(未发布,将随下一个预发布版交付)**:命令面板的任务命令原为**预生成前 50 条**,任务数超过 50 时 **排在后面的任务搜不到**且只提示「没有匹配的命令」(不报错、不提示截断,比没有该功能更误导)。现已去掉上限, 改为 `buildTaskCommands` **按输入实时检索全部任务**(空输入不列任务),`rankCommands` 的 `PALETTE_RESULT_LIMIT = 20` **只截展示条数、不限制检索范围**;`buildCommands` 拆为 `buildStaticCommands` + `buildTaskCommands`(面板接收二者并按输入合成)。`0.1.1-beta.2` 已发布产物仍是旧行为(版本号未动,**不重打该 tag**)。 - **后续**:批次三(`0.1.1-beta.3`)= A5 基线漂移 + A6 状态跃迁甘特 + A9 磁盘占用 + A8b 深链(**唯一有架构风险项**,需 Tauri 插件 + 协议注册); 真机验收(Windows 10 安装 Release 安装包,逐项核对 A4/A7/A8a)待维护者执行。 --- ### 独立交付面 · 日志台 GUI **更新窗口正文(更新清单 `notes`)修复**(`mcp-gui/`,随 `0.1.1-beta.1` 回填,**不升版本**) - **真机现象**:`0.1.1-beta.1` 的「更新日志」窗口正文只有一行 `Tianshu-mcp 日志台 0.1.1-beta.1`(28 字符), 已合成的完整双语发行说明没有出现(更新源 GitHub)。 - **根因**:更新窗口正文的唯一来源是**更新清单的 `notes`**(`updater.rs` 的 `update.body`),而发布链合成清单时 **只传了兜底标题** `--notes "Tianshu-mcp 日志台 $VER"`;`scripts/gui-release-body.mjs` 合成的完整正文虽已用于 发行页(GitHub `--notes-file` / Gitee `--body-file`),却**没有传给清单生成脚本**。Gitee 清单复用同一份 `latest.json`(`gitee-gui-release.mjs --github-manifest`),故**两个源同时退化成单行标题**。 - **修法**: - `.github/workflows/gui.yml` 的「Compose updater manifest (GitHub)」新增 `--notes-file "${{ steps.meta.outputs.body }}"`——清单 `notes` 与发行页正文**同源同一份**, 并打印取数路径与字节数便于排障; - `mcp-gui/scripts/build-updater-manifest.mjs` 新增 `resolveNotes()`:`--notes-file` **优先**; **读取失败直接非 0 退出**(fail-closed,杜绝静默退化成一行标题);文件存在但内容为空时回退 `--notes` 并打 warning。 - **回归测试**:新增 `test/unit/gui-updater-manifest.test.ts`(5 例:正文优先 / 无 `--notes-file` 回退标题 / 两者皆无为空串 / 缺文件 fail-closed / 空文件回退并告警),本机 **5 passed**。 - **已发布版本回填**:`update/gui/latest.json` 与 `latest-gitee.json` 的 `notes` 补为完整双语说明 —— 已发布 tag 的 workflow 无法重跑改写清单,只能回填已落库的清单;`version` / `pub_date` / `platforms` **均未改动**。 - **版本边界**:**不升版本、不重打 `gui-v0.1.1-beta.1`**(`package.json` / `package-lock.json` 两处 / `tauri.conf.json` / `Cargo.toml` 四处仍为 `0.1.1-beta.1`);MCP 主包**零改动**。 - **待验证(需维护者的动作)**:~~下次 GUI 发版时确认清单 `notes` 为完整正文~~ **✅ 已闭环(2026-10-02)**: `gui-v0.1.1-beta.2` 发布后两端清单 `notes` 均为 **7543 字符的完整双语正文**(首行 `# 日志台 0.1.1-beta.2 — …`,含英文段落), 即桌面端更新窗口会显示这一版发行说明,而不再是一行标题。 本机仍不执行任何 Rust 构建 / 检查(issue #25 约束),Rust 侧门禁交 `GUI` workflow。 --- ### 独立交付面 · 日志台 GUI **正式版 `0.1.0`(转正 + 更新日志面板)**(`mcp-gui/`,`0.1.0`) - **范围**(用户决策:0.1.0 = 转正 + 更新日志面板,功能与界面在 beta.9 基础上只加面板): - **新增**:`mcp-gui/src/core/version.ts`(版本比较与忽略判定纯函数)、`mcp-gui/test/version.test.ts`(16 项)、 `mcp-gui/src/components/UpdateDialog.vue`(大尺寸居中弹窗)、`scripts/gui-release-body.mjs`(合成双语发行版正文)、 `test/unit/gui-release-body.test.ts`(7 项)、`docs/release-gui-v0.1.0.md` + `.en.md`、`docs/gui-0.1.0-release-record.md`; - **修改**:`mcp-gui/src/stores/app.ts`(`dialogOpen` + `checkUpdateOnStartup()` + `ignoreUpdateVersion()`)、 `mcp-gui/src-tauri/src/models.rs`(`Preferences.ignored_update_version`,**带 `#[serde(default)]`**)、 `mcp-gui/src/api/{types,mock}.ts`、`mcp-gui/src/stores/preferences.ts`、`mcp-gui/src/App.vue`、 `mcp-gui/src/components/SettingsDrawer.vue`、`mcp-gui/src/styles.css`、`mcp-gui/src/i18n/{zh-CN,en-US}.ts`、 `.github/workflows/gui.yml`、`scripts/gitee-gui-release.mjs`、四处版本号、`docs/gui-log-viewer.md` + `.en.md`、 `CHANGELOG.md` + `.en.md`、`README.md` + `.en.md`、`HANDOFF.md`; - **未改**:MCP 主包 `src/**`、`.github/workflows/ci.yml`、`.github/workflows/release.yml`、 `scripts/release-body.mjs`(复用其导出的 `absolutizeDocLinks`,**零触碰**);也未改 `tianshu-mcp-web/`。 - **验证**(本机可验的部分): - `mcp-gui` 五项门禁 `typecheck` / `lint` / `test` / `check:schema` / `build` **全 exit 0**, `vitest` **98 passed**(9 文件,含新增 16 项),`check:schema` 输出「GUI 版本号一致(0.1.0)」; - 新增 `test/unit/gui-release-body.test.ts` **7 passed**;CLI 在缺文档时**非 0 退出**(fail-closed 已实测); - **无头 Edge 探针 15/15 通过**:启动自动弹窗、弹窗尺寸 1070×750、三个动作齐备、release 正文渲染为 DOM、 忽略后持久化且重载不弹、手动检查仍弹、全程无 `pageerror`; - `gui.yml` YAML 语法经 `js-yaml` 解析通过;版本号四处均为 `0.1.0`;全库「测试版」字样**零残留**。 - **排障记录(重要)**:vitest **无法 import 带 shebang 的 `.mjs`**——vite 的 SSR transform 会把 `#!/usr/bin/env node` 当作非法 token 抛 `SyntaxError: Invalid or unexpected token`(`node` 直接运行不受影响)。 因此 `scripts/gui-release-body.mjs` **刻意不带 shebang**,并在文件头注明原因;调用一律 `node scripts/gui-release-body.mjs …`。 - **版本边界**:`mcp-gui` 四处版本同步 `0.1.0-beta.9 → 0.1.0`;发布走 tag `gui-v0.1.0`(**正式版**, 发行页不带 Pre-release 标记),两端发行版与 `update/gui/latest*.json` 由发布 job 生成/提交, **发布后两仓各会多出一个机器人提交**,按既有做法合并回同一提交再推两仓。**MCP 主包版本不受影响**(仍 `0.7.4`)。 - **发布结果(2026-09-29)**:tag `gui-v0.1.0` 已推双仓;GUI run 36500075023 的 5 个 job **全部 success** (含 `Publish release`);**双端发行版均为正式版**(`prerelease=false`),GitHub 8 资产 / Gitee 10 附件 (8 产物 + Gitee 自动生成的 2 个源码归档);两端清单 `version=0.1.0`,6 个下载地址实测可达 (GitHub HTTP 206 / Gitee HTTP 200),签名齐备;两仓清单提交已合并回同一提交 `dc9bba3`。 完整证据链(含核验断言明细)见 `docs/gui-0.1.0-release-record.md`。 - **遗留(如实披露)**: 1. **Windows 真机安装与观感目测未执行**(维护者选择直接打 tag,跳过原计划的人工闸门),因此 「0.1.0 界面显示 / 启动不误弹 / 弹窗尺寸与文案」仅有本机无头 Edge(mock)+ 单测证据,**无桌面真机证据**; 2. **从 `0.1.0-beta.9` 升级到 `0.1.0` 的真机升级链路未验证**; 3. macOS 仅保证 CI 构建通过,**未做真机功能验收**(亦未做 Apple 签名/公证)。 ### 独立交付面 · 日志台 GUI **「刷新」上移到数据目录行**(`mcp-gui/`,`0.1.0-beta.9`) - **范围**:`mcp-gui/src/App.vue`(移除侧栏底部的刷新行,`.rail-foot` 改为**仅 mock 运行时**渲染)、`src/components/DataHomeBar.vue`(head 行新增「刷新」按钮)、`src/styles.css`(删掉仅供旧底部行使用的 `.rail-row`),以及四处版本号。**未改** `src/core/**`、`src/api/**`、`src/stores/**`、`src/i18n/**`(复用既有 `common.refresh` / `dataHome.*` 键,**零新增文案键**)。 - **背景**:`0.1.0-beta.8` 把数据目录移进导航区后,侧栏底部只剩一个孤立的「刷新」按钮(外面还包着一条分隔线),维护者据此截图提出把它上移。 - **改法**:把「刷新」放进**「数据目录」标题那一行**,与既有的「添加目录 / 移除」图标按钮同排(顺序 `刷新 / 添加目录 / 移除`)——这一行本就是侧栏的小图标按钮组,且**刷新是全局动作**(两态内容区都可用),放这里不会像放进内容区工具行那样在「全屏工作区」态丢失入口。侧栏底部因此**不再常驻任何东西**:`.rail-foot` 仅在 mock 运行时渲染那条「本地预览」提示条,真实构建下整条底栏不再出现(避免留下空的带边框区域)。 - **验证(本机)**:`check:schema`(版本一致 `0.1.0-beta.9`)/ `vue-tsc --noEmit` / `eslint . --max-warnings 0` / `vitest`(**82 passed**,8 文件)/ `vite build` 全绿。 - **模拟真机**:`puppeteer-core` + 本机无头 Edge(mock 运行时)DOM 断言 **7/7 通过** —— 数据目录行的图标按钮为 `["刷新","添加目录","移除"]` 且三者 `top` 完全对齐(`[203,203,203]`)、侧栏底部已无刷新按钮(按钮数 0)、mock 提示条仍在、点击刷新无报错且界面正常、全程无 `pageerror`。 - **待办**:桌面端观感需在 `0.1.0-beta.9` 的 Windows 产物上复核。 - **版本边界**:`mcp-gui` 四处版本同步 `0.1.0-beta.8 → 0.1.0-beta.9`,随本版发布 tag `gui-v0.1.0-beta.9`;`update/gui/latest*.json` 由发布 job 自动生成并提交(`[skip ci]`),**发布后两仓各会多出一个机器人提交**,按既有做法合并回同一提交再推两仓。**MCP 主包版本不受影响**。 --- ### 独立交付面 · 日志台 GUI **指标仪补「已成功」+ 数据目录上移 + 设置面板改居中弹窗**(`mcp-gui/`,`0.1.0-beta.8`) - **范围**:`mcp-gui/src/App.vue`、`src/components/{OverviewPage,MetricsStrip}.vue`、`src/styles.css`,以及四处版本号(`package.json` / `package-lock.json` 两处 / `src-tauri/tauri.conf.json` / `src-tauri/Cargo.toml`)。**未改** `src/core/**`、`src/api/**`、`src/stores/**`、`src/i18n/**`(**零新增文案键**)与 `src-tauri/**` 的代码(仅版本号),也未改 MCP 主包 `src/**` 与 `tianshu-mcp-web/`。 - **背景**:维护者在真机上验收 `0.1.0-beta.7` 时反馈 —— **「手动下载」点击已可直接跳转**(beta.7 的核心修复已真机确认,`docs/issue-25-gui-real-machine-record.md` §4.1 遗留项闭环),同时按截图提出三处界面调整。 - **三处调整**: 1. **指标仪补「已成功」**(`OverviewPage.vue` 新增 `succeededCount` + 第 4 格,`.metrics` 由 4 列改 **5 列**):顺序为 **任务总数 / 进行中 / 已结束 / 已成功 / 已失败**,读数用语义色 `tone-ok`;标签复用既有 `status.succeeded`,**不新增 i18n 键**(与相邻「已失败」复用 `status.failed` 同口径)。 2. **「数据目录」上移**:由 `.rail-foot` 移入 `.rail-nav`,**紧接「设置」之后**;侧栏序列变为 **品牌 → 全局搜索 → 任务列表 / 运行日志 / 设置 → 数据目录 → 任务分区 → 刷新**,侧栏底部只剩「刷新」。为保持分组可读,新增一条 `.rail-nav > .rail-block` 分组线(与 `.rail-label` 同款 `border-top`,**不写硬编码色值**)。 3. **设置面板改居中弹窗**:`.poverlay` 由 `justify-content: flex-end` 改为 `align-items/justify-content: center`;`.panel` 由「右侧全高滑出」改为居中卡片(`max-height: 86vh`、四边描边、圆角 `var(--r-2)`、`overflow: hidden`),入场动画改用既有 `@keyframes rise` 并删除只服务于旧形态的 `slide-in`;`.panel-body` 补 `min-height: 0` 以保证在 `max-height` 下正常内滚。点遮罩关闭的行为不变。 - **验证(本机)**:`check:schema`(版本一致 `0.1.0-beta.8`)/ `vue-tsc --noEmit` / `eslint . --max-warnings 0` / `vitest`(**82 passed**,8 文件)/ `vite build` 全绿。 - **模拟真机**:`puppeteer-core` + 本机无头 Edge(mock 运行时)DOM 断言 **12/12 通过** —— 指标仪 5 格与顺序、`已成功` 读数与 `tone-ok` 语义色、数据目录位于 `.rail-nav` 内且紧接「设置」、底部只剩「刷新」、**面板中心 720.0 = 窗口中心 720.0(1440×900)**、四周均不贴窗口边缘、圆角 4px、遮罩居中、`height=589.5 ≤ 视口`、全程无 `pageerror`。 - **待办**:桌面端观感(居中弹窗在真实窗口尺寸下的观感、侧栏上移后的分组线)需在 `0.1.0-beta.8` 的 Windows 产物上复核。 - **版本边界**:`mcp-gui` 四处版本同步 `0.1.0-beta.7 → 0.1.0-beta.8`,随本版发布 tag `gui-v0.1.0-beta.8`;`update/gui/latest*.json` 由发布 job 自动生成并提交(`[skip ci]`),**发布后两仓各会多出一个机器人提交**,按既有做法合并回同一提交再推两仓。**MCP 主包版本不受影响**。 --- ### 独立交付面 · 日志台 GUI **手动下载修复 + 设置入口迁入侧栏导航**(`mcp-gui/`,`0.1.0-beta.7`) - **范围**:`mcp-gui/src/App.vue`、`src/styles.css`、`src/components/SettingsDrawer.vue`、`src/api/{gui-api,tauri,mock}.ts`、`src/stores/app.ts`、`src-tauri/src/{lib,models,updater}.rs`、`src-tauri/{Cargo.toml,capabilities/default.json}`、`package.json` / `package-lock.json`、`test/mock.test.ts`。**未改** `src/core/**`、`src/i18n/**`(零新增文案键)与 MCP 主包 `src/**`、`tianshu-mcp-web/`。 - **三项修改**: 1. **删掉侧栏品牌区装饰绿块**(`App.vue` 的 `` + `styles.css` 的 `.mark` 规则整条移除,全局已无引用)。 2. **修「手动下载」两个缺陷**:① 入口原是 ``,Tauri 2 的 webview 会拦截新建窗口请求且此前**未接入任何外部打开能力**,故点了没有任何反应 —— 现接入官方 `tauri-plugin-opener`(JS 绑定经 `src/api` 出口,`GuiApi.openExternal`),并把入口改为 `