tianshu-mcp 0.4.1 → 0.5.0

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 (54) hide show
  1. package/CHANGELOG.en.md +47 -0
  2. package/CHANGELOG.md +47 -0
  3. package/README.en.md +22 -7
  4. package/README.md +22 -7
  5. package/dist/agents/codex/fixplan.js +8 -1
  6. package/dist/agents/zcode/recovery.js +14 -2
  7. package/dist/config/schema.js +3 -1
  8. package/dist/index.js +5 -0
  9. package/dist/loop/fix-loop.js +54 -7
  10. package/dist/loop/repair-plan.js +2 -0
  11. package/dist/mcp/handlers.js +7 -5
  12. package/dist/mcp/tools.js +15 -0
  13. package/dist/server.js +5 -1
  14. package/dist/tasks/task-manager.js +4 -1
  15. package/dist/tasks/task-store.js +5 -0
  16. package/dist/verify/acceptance.js +124 -14
  17. package/dist/verify/report.js +16 -2
  18. package/dist/version.generated.js +1 -1
  19. package/dist/visual/baselines.js +209 -0
  20. package/dist/visual/budget.js +52 -0
  21. package/dist/visual/capture.js +368 -0
  22. package/dist/visual/cli.js +96 -0
  23. package/dist/visual/compare.js +142 -0
  24. package/dist/visual/config.js +22 -0
  25. package/dist/visual/defaults.js +25 -0
  26. package/dist/visual/engine.js +115 -0
  27. package/dist/visual/errors.js +15 -0
  28. package/dist/visual/images.js +144 -0
  29. package/dist/visual/lock.js +29 -0
  30. package/dist/visual/manage.js +107 -0
  31. package/dist/visual/paths.js +31 -0
  32. package/dist/visual/report.js +48 -0
  33. package/dist/visual/runtime.js +116 -0
  34. package/dist/visual/schema.js +225 -0
  35. package/dist/visual/services.js +155 -0
  36. package/dist/visual/snapshot.js +57 -0
  37. package/dist/visual/types.js +1 -0
  38. package/docs/visual-acceptance.en.md +123 -0
  39. package/docs/visual-acceptance.md +128 -0
  40. package/docs/visual-validation-evidence/macos-15-arm64-node20.environment.json +11 -0
  41. package/docs/visual-validation-evidence/macos-15-arm64-node22.environment.json +11 -0
  42. package/docs/visual-validation-evidence/macos-15-arm64-node24.environment.json +11 -0
  43. package/docs/visual-validation-evidence/macos-15-intel-node20.environment.json +11 -0
  44. package/docs/visual-validation-evidence/macos-15-intel-node22.environment.json +11 -0
  45. package/docs/visual-validation-evidence/macos-15-intel-node24.environment.json +11 -0
  46. package/docs/visual-validation-evidence/macos-ci-summary.txt +32 -0
  47. package/docs/visual-validation-evidence/windows-10-matrix.json +75 -0
  48. package/docs/visual-validation-evidence/windows-10-tests.log +22 -0
  49. package/docs/visual-validation.en.md +83 -0
  50. package/docs/visual-validation.md +83 -0
  51. package/package.json +17 -4
  52. package/scripts/evidence-visual-windows.mjs +321 -0
  53. package/skills/tianshu-mcp/SKILL.md +9 -1
  54. package/skills/tianshu-mcp/usage-examples.md +27 -0
package/CHANGELOG.en.md CHANGED
@@ -10,6 +10,16 @@ Chinese version: [CHANGELOG.md](CHANGELOG.md)
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ### Tests
14
+
15
+ - Two additional real-browser-gated visual cases (added after v0.5.0): screenshots succeed when the project path contains CJK characters and spaces; a main-document 302 redirect to a non-allowlisted origin is blocked by policy and passes once explicitly allowed. Full suite: **486 passed / 10 skipped**.
16
+ - New `npm run evidence:visual:windows` (`scripts/evidence-visual-windows.mjs`): collects the full Windows 10 local functional matrix (existing/static/command sources, port conflict that blocks without terminating another service, bounded readiness failure that cleans up the child process, local Edge isolated instance and version mismatch, missing-browser blocker), 9/9 passed.
17
+ - Collected macOS 13+ platform evidence: on macOS 15 hardware runners, Intel x64 and Apple Silicon arm64 (Node 20/22/24) each passed 10 files with 51 cases; raw records are committed under `docs/visual-validation-evidence/`.
18
+
19
+ ### Docs
20
+
21
+ - `docs/visual-validation{,.en}.md` rewritten with full platform evidence tables (system, Node, browser version, command, result), plus the new `docs/visual-validation-evidence/` raw machine-readable records.
22
+
13
23
  ### Planned
14
24
 
15
25
  - More external AI-Agent adapters (a new agent = one profile + an optional adapter file).
@@ -21,6 +31,43 @@ Chinese version: [CHANGELOG.md](CHANGELOG.md)
21
31
 
22
32
  ---
23
33
 
34
+ ## [0.5.0] — 2026-09-14
35
+
36
+ Adds an **optional visual acceptance module** that wires screenshot comparison and static image specification checks into the "develop → verify → repair → re-verify" loop. Projects that do not enable visuals behave compatibly, and legacy reports and task snapshots remain readable. See the [v0.5.0 release notes](docs/release-v0.5.0.en.md) for the full description.
37
+
38
+ ### Added
39
+
40
+ - **Page sources**: mutually exclusive `existing` / `command` / `static`; identical service definitions share one managed instance per round; the static host rejects traversal and out-of-project symlinks; an occupied port blocks instead of reusing or terminating another service.
41
+ - **Screenshots and interaction**: `viewport` / `fullPage` / `element` modes with declarative `click` / `input` / `hover` / `scroll` / `wait` steps; a fixed readiness flow (isolated context, login state, fonts and images, disabled animations, masks, sampling); bounded full-page scrolling to trigger lazy loading.
42
+ - **Stabilization and masks**: up to 3 samples taking two adjacent identical captures; continuously changing pages, exceeded pixel budgets and unstable captures block; an unmatchable mask selector or a fully masked image never passes.
43
+ - **Pixel comparison**: unified PNG; size mismatches fail without scaling; masked regions are excluded from numerator and denominator; pixelmatch antialiasing is excluded by default; connected-component analysis outputs coordinates, areas and an annotated image, keeping at most 100 regions while recording the remaining count and overall bounds.
44
+ - **Static image specifications**: explicit file lists; encoded-format/extension consistency, existence with complete decoding, EXIF-orientation-normalized dimensions, aspect ratio, byte size, optional DPI and real-transparent-pixel detection; unsupported formats are reported explicitly.
45
+ - **Two-phase baselines**: `prepare` produces a candidate (candidate ID, digest, target paths, preview) and `approve` verifies candidate/original-baseline/configuration digests before atomically writing the official baseline and manifest; a missing baseline can only produce a candidate and never a pass; automatic repair never calls the approval entry point.
46
+ - **Rule freezing**: visual configuration and baseline digests are saved before the agent starts and checked around each round; changes require a new snapshot through the dedicated `rules review` / `rules approve` flow.
47
+ - **MCP tools**: new `prepare_visual_baseline` and `approve_visual_baseline`, both side-effecting `write` operations requiring host approval.
48
+ - **CLI**: new `tianshu-mcp visual` subcommand (`init`, `browser install`, `doctor`, `baseline prepare/approve`, `rules review/approve`, `artifacts clean`), dispatched before the stdio connection.
49
+ - **Reports and artifacts**: `VerifyReport` gains an optional `visual` field and `files.html`; the offline HTML supports status filtering, side-by-side images, opacity overlays and region location with only local artifacts, escaped text and no CDN; each round's artifacts live at `<home>/tasks/<taskId>/visual/<round>/` with rounds allocated by a unified task-level lock.
50
+ - **Blockers and recovery**: distinguishes repairable defects, environment blockers and user cancellation; visual blockers enter `needs_attention` with a re-verification-pending marker; `rework_task` re-verifies first for visually blocked tasks and only real defects consume the repair budget; both the generic and Codex-specific repair plans include visual evidence and state that baselines, thresholds and switches must not be modified to bypass failures.
51
+ - **Configuration robustness**: an invalid acceptance configuration blocks explicitly instead of falling back silently; only an explicit `checks: []` disables command checks; `extraChecks` and `checksMode=replace` never override the visual gate; `visual` strictly validates unknown fields, duplicate IDs, empty rules and conflicting options.
52
+ - **Dependencies and runtime**: pinned `puppeteer-core@24.43.1`, `@puppeteer/browsers@2.13.2`, `sharp@0.34.5`, `pixelmatch@7.2.0`; the image library is an optional dynamic dependency whose absence does not prevent startup; browsers are installed explicitly on demand with no download during npm install or MCP startup; the visual module requires Node.js >=20.3 while non-visual features retain >=20.
53
+ - **Docs and gates**: new bilingual visual acceptance guide and validation progress; CI adds a real-browser matrix (ubuntu/windows/macos-intel/macos × Node 20/22/24) plus production-package consumer acceptance; release requires a successful CI for the target commit and blocks when mirror credentials are missing.
54
+
55
+ ### Fixed
56
+
57
+ - The acceptance engine no longer silently falls back to default checks when a project configuration is invalid; it blocks explicitly with a reason.
58
+ - Manual and automatic verification share the task-level lock for report round allocation, preventing concurrency or recovery from overwriting historical evidence.
59
+ - Manual verification now lands a visually blocked task in `needs_attention` instead of misreporting `failed`.
60
+ - `rework_task` allows a visually blocked task that lacks original session location information to re-verify first instead of being rejected outright.
61
+ - The ZCode recovery budget now determines the controlling reason before aborting dependent operations, so the reason is not overwritten by downstream abort listeners.
62
+ - `TaskOrchestrator` distinguishes "cancelled" from "blocked" when a visual integrity problem appears during startup, so cancellation is no longer misrecorded as `needs_attention`.
63
+
64
+ ### Tests
65
+
66
+ - Full suite: **486 passed / 8 skipped** (Windows 10 x64, Node 24.18.0), adding visual configuration, image specification, report, baseline, budget, snapshot, service, real-browser capture, flow and repair cases.
67
+ - The 8 real-browser-gated cases pass separately with `TIANSHU_VISUAL_BROWSER_TEST=1`; the production tarball visual smoke test passes in an isolated consumer.
68
+
69
+ ---
70
+
24
71
  ## [0.4.1] — 2026-09-13
25
72
 
26
73
  Documentation release: the orchestration skill docs are aligned with the actual v0.4.0 tool
package/CHANGELOG.md CHANGED
@@ -9,6 +9,16 @@
9
9
 
10
10
  ## [未发布]
11
11
 
12
+ ### 测试
13
+
14
+ - 视觉验收补两条真实浏览器门禁用例(v0.5.0 之后补充):项目路径含中文与空格时截图正常;主文档 302 跳转到未放行来源时按策略拦截,显式放行后通过。全量测试 **486 passed / 10 skipped**。
15
+ - 新增 `npm run evidence:visual:windows`(`scripts/evidence-visual-windows.mjs`):在 Windows 10 本机采集完整功能矩阵证据(`existing`/静态/命令三种来源、端口冲突阻塞且不结束他人服务、就绪失败有界阻塞并清理子进程、本机 Edge 独立实例与版本不匹配、缺浏览器阻塞),9/9 通过。
16
+ - 采集 macOS 13+ 平台证据:macOS 15 真机 runner 上 Intel x64 与 Apple Silicon arm64(Node 20/22/24)各跑通 10 文件 51 用例;原始记录随 `docs/visual-validation-evidence/` 入库。
17
+
18
+ ### 文档
19
+
20
+ - `docs/visual-validation{,.en}.md` 重写为完整平台证据表(系统、Node、浏览器版本、命令、结果),并新增 `docs/visual-validation-evidence/` 原始机器可读记录。
21
+
12
22
  ### 计划中
13
23
 
14
24
  - 更多外部 AI-Agent 适配(新 agent = 一个 profile +(如需)一个 adapter 文件)。
@@ -19,6 +29,43 @@
19
29
 
20
30
  ---
21
31
 
32
+ ## [0.5.0] — 2026-09-14
33
+
34
+ 新增**可选视觉验收模块**:把页面截图对比与静态图片规格检查接入「开发 → 验收 → 返修 → 再验收」闭环。未启用视觉的项目行为兼容;旧报告与旧任务快照仍可读取。完整说明见 [v0.5.0 发布说明](docs/release-v0.5.0.md)。
35
+
36
+ ### 新增
37
+
38
+ - **页面来源**:互斥的 `existing` / `command` / `static` 三种来源;服务按定义在同轮复用;静态托管拒绝目录穿越与项目外符号链接;端口占用即阻塞,不擅自复用或结束其他服务。
39
+ - **截图与交互**:`viewport` / `fullPage` / `element` 模式与声明式 `click` / `input` / `hover` / `scroll` / `wait` 步骤;固定就绪流程(隔离上下文、登录态、字体与图片、关闭动画、屏蔽、采样);支持整页有界滚动触发懒加载。
40
+ - **稳定化与屏蔽**:最多 3 次采样取相邻一致;持续变化、超像素预算或无法稳定即阻塞;屏蔽选择器定位失败或全图屏蔽都不通过。
41
+ - **像素对比**:统一 PNG;尺寸不一致直接失败不缩放;屏蔽区域不计入分子分母;pixelmatch 抗锯齿默认排除;连通区域分析输出坐标、面积、标注图,最多保留 100 个区域并记录其余数量与整体包围框。
42
+ - **静态图片规格**:显式文件列表;编码格式与扩展名一致性、存在非空且完整解码、EXIF 方向归一后的宽高、宽高比、字节数、可选 DPI 与真实透明像素判定;不支持格式明确报告。
43
+ - **基准两阶段**:`prepare` 生成候选(候选 ID、摘要、目标路径、预览),`approve` 核对候选/原基准/配置摘要后原子写入正式基准与 manifest;缺基准只能生成候选、不能判视觉通过;自动返修不调用批准入口。
44
+ - **规则冻结**:任务动工前保存视觉配置与基准摘要,每轮前后核对;变化需经独立 `rules review` / `rules approve` 重建快照。
45
+ - **MCP 工具**:新增 `prepare_visual_baseline`、`approve_visual_baseline`(均为有副作用、需宿主审批的 `write` 操作)。
46
+ - **CLI**:新增 `tianshu-mcp visual` 子命令(`init`、`browser install`、`doctor`、`baseline prepare/approve`、`rules review/approve`、`artifacts clean`),在 stdio 连接前分流。
47
+ - **报告与产物**:`VerifyReport` 新增可选 `visual` 字段与 `files.html`;离线 HTML 支持状态过滤、图片并排、透明叠加与区域定位,纯本地产物、文本转义、无 CDN;每轮产物位于 `<home>/tasks/<taskId>/visual/<round>/`,轮次由统一任务级锁分配。
48
+ - **阻塞与恢复**:区分可返修缺陷、环境阻塞与用户取消;视觉阻塞进 `needs_attention` 并保留待重新验收标记;`rework_task` 对视觉阻塞先重新验收、仅真实缺陷才消耗返修预算;通用与 Codex 专用返修计划均含视觉证据并明示不得修改基准/阈值/开关绕过。
49
+ - **配置健壮性**:无效 acceptance 配置显式阻塞而非静默回退;显式 `checks: []` 才关闭命令检查;`extraChecks` 与 `checksMode=replace` 不覆盖视觉门禁;`visual` 严格校验未知字段、重复 ID、空规则与冲突选项。
50
+ - **依赖与运行时**:固定 `puppeteer-core@24.43.1`、`@puppeteer/browsers@2.13.2`、`sharp@0.34.5`、`pixelmatch@7.2.0`;图像库为可选动态依赖,缺失不阻止启动;浏览器按需显式安装,npm 安装与 MCP 启动均不下载浏览器;视觉模块要求 Node.js >=20.3,非视觉功能保留 >=20。
51
+ - **文档与门禁**:新增双语视觉验收指南与验证进度;CI 新增真实浏览器矩阵(ubuntu/windows/macos-intel/macos × Node 20/22/24)与生产包消费者验收;release 要求目标提交存在成功 CI 且缺少镜像凭据时阻塞。
52
+
53
+ ### 修复
54
+
55
+ - 验收引擎解析到无效项目配置时不再静默回退默认检查,改为显式阻塞并给出原因。
56
+ - 手动验收与自动验收统一使用任务级锁分配报告轮次,避免并发或恢复覆盖历史证据。
57
+ - 手动验收遇到视觉阻塞时任务落 `needs_attention`,不再误记 `failed`。
58
+ - `rework_task` 允许对视觉阻塞但缺少原会话定位的任务先重新验收,不再直接拒绝。
59
+ - ZCode 恢复预算在取消/截止时间到达时先确定原因再中止依赖操作,避免原因被下游 abort 监听覆盖。
60
+ - `TaskOrchestrator` 启动阶段遇到视觉完整性问题时区分「被取消」与「阻塞」,取消不再误记为 `needs_attention`。
61
+
62
+ ### 测试
63
+
64
+ - 全量 **486 passed / 8 skipped**(Windows 10 x64,Node 24.18.0):新增视觉配置、图片规格、报告、基准、缓冲、快照、服务、真实浏览器捕获、流程与返修用例。
65
+ - 8 项真实浏览器门禁用例以 `TIANSHU_VISUAL_BROWSER_TEST=1` 单独跑通;生产 tarball 独立消费者视觉冒烟通过。
66
+
67
+ ---
68
+
22
69
  ## [0.4.1] — 2026-09-13
23
70
 
24
71
  文档版本:把编排技能文档对齐 v0.4.0 实际工具面,并补齐开源仓库的贡献者名录。本版本无代码行为变更。
package/README.en.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  # tianshu-mcp
10
10
 
11
+ Visual acceptance (v0.5.0): [English guide](docs/visual-acceptance.en.md) · [Validation record](docs/visual-validation.en.md) · [Release notes](<docs/release-v0.5.0.en.md>).
12
+
11
13
  **Tianshu × AI-Agent orchestration MCP server**
12
14
 
13
15
  Registered by Tianshu as a standard MCP server, it dispatches external AI-Agents (Codex desktop, TraeWork/TRAE SOLO CN and ZCode, all driven through their desktop UIs over CDP) to drive the closed loop of **project development → acceptance → failure rework → re-acceptance** (horizontally extensible).
@@ -64,7 +66,7 @@ git clone https://github.com/lanlan0811/tianshu-mcp.git
64
66
  cd tianshu-mcp
65
67
  npm ci
66
68
  npm run build # sync-version + tsc → dist/
67
- npm test # 443 tests across 46 files, including Codex/ZCode unit/fake-CDP/restart/recovery/repair coverage
69
+ npm test # 496 tests across 56 files, including Codex/ZCode/TraeWork unit/fake-CDP/restart/recovery/repair loops and visual acceptance
68
70
  ```
69
71
 
70
72
  ### Install the npm package
@@ -90,7 +92,7 @@ In Tianshu go to **Settings → MCP Servers → Add** and fill in the fields bel
90
92
  > - The server ID becomes the tool prefix: with `tianshu-mcp` the tools are `mcp__tianshu-mcp__run_task` and 8 others.
91
93
  > - Arguments are space-separated, **no quotes**; for local dev replace `<absolute-repo-path>` with a real path (e.g. `D:/TraeProject/tianshu-mcp/dist/index.js`).
92
94
  > - The dialog has no env-var field; to customize the data directory, use the `config.json` method below and set `TIANSHU_MCP_HOME`.
93
- > - Once the server connects, open a new session and the 9 tools appear.
95
+ > - Once the server connects, open a new session and the 11 tools appear.
94
96
 
95
97
  ### Or edit config.json (supports env vars)
96
98
 
@@ -110,7 +112,7 @@ Register as a Tianshu MCP server (local dev mode):
110
112
  }
111
113
  ```
112
114
 
113
- After opening a new session, the 9 tools such as `mcp__tianshu-mcp__run_task` appear. Rehearse with the stub agent first (no real login state), then switch to the `codex` profile for real tasks:
115
+ After opening a new session, the 11 tools such as `mcp__tianshu-mcp__run_task` appear. Rehearse with the stub agent first (no real login state), then switch to the `codex` profile for real tasks:
114
116
 
115
117
  ```text
116
118
  run_task(projectPath=D:/xxx/my-app, task=「…task brief…」, agentId=codex,
@@ -148,7 +150,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=zcode, task="Implement `./plan.md`",
148
150
 
149
151
  Questions, login, an existing non-CDP instance, or system permission pause as `needs_user`; call `continue_task(taskId, message)` to resume the recorded session. Model selection is adapted to ZCode 3.11.2: flat models are selected directly first, with provider/family group expansion as a fallback — both new and legacy layouts are supported. See [docs/zcode-cdp.en.md](docs/zcode-cdp.en.md).
150
152
 
151
- ## Tool surface (9 tools)
153
+ ## Tool surface (11 tools)
152
154
 
153
155
  | Tool | Capability / approval | Purpose |
154
156
  |---|---|---|
@@ -161,6 +163,8 @@ Questions, login, an existing non-CDP instance, or system permission pause as `n
161
163
  | `verify_task` | read | Run one verification pass on a task/project path (no source changes) |
162
164
  | `rework_task` | write + approval | Manual rework (feed the failure report back to the same agent) |
163
165
  | `get_profiles` | read | Inspect agent adapters and executable discovery results |
166
+ | `prepare_visual_baseline` | write + approval | Capture or import reference images into a reviewable candidate with a digest |
167
+ | `approve_visual_baseline` | write + approval | Validate the reviewed digest and write the baseline and approval record |
164
168
 
165
169
  > Every result is "human-readable text + a `---tianshu-mcp-meta---` JSON block" so the host can extract it with a regex.
166
170
 
@@ -191,6 +195,10 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
191
195
  | [docs/codex-gui-cdp.en.md](docs/codex-gui-cdp.en.md) | Codex desktop GUI driver: MSIX COM activation, CDP attach, selectors, run detection, verify/repair |
192
196
  | [docs/codex-windows-smoke.en.md](docs/codex-windows-smoke.en.md) | Codex Windows hardware record (incl. verify-fail → auto plan → repair-pass loop) |
193
197
  | [docs/release-v0.3.4.en.md](<docs/release-v0.3.4.en.md>) | v0.3.4 release notes (ZCode project/model read-back, initialization recovery, session dispatch confirmation, issues #8/#9/#10) |
198
+ | [docs/release-v0.5.0.en.md](<docs/release-v0.5.0.en.md>) | v0.5.0 release notes (optional visual acceptance: screenshots, image specs, baseline approval, offline report) |
199
+ | [docs/visual-acceptance.en.md](<docs/visual-acceptance.en.md>) | Visual acceptance primer and full configuration: three page sources, baseline candidates/approval, rule freezing, thresholds and troubleshooting |
200
+ | [docs/visual-validation.en.md](<docs/visual-validation.en.md>) | Visual acceptance validation progress: full Windows 10 matrix and macOS Intel/Apple Silicon platform evidence (system/Node/browser/command/result) |
201
+ | [docs/visual-validation-evidence/](<docs/visual-validation-evidence/>) | Raw machine-readable records for the above (environment JSON, matrix results, test output and macOS CI summaries) |
194
202
  | [docs/release-v0.4.1.en.md](<docs/release-v0.4.1.en.md>) | v0.4.1 release notes (skill docs aligned with the v0.4.0 tool surface + contributor credits) |
195
203
  | [docs/zcode-issue-8-10-validation.en.md](<docs/zcode-issue-8-10-validation.en.md>) | ZCode #8/#9/#10 Windows hardware record (cold import, imported-project reuse, same-task recovery) |
196
204
  | [docs/release-v0.3.3.en.md](<docs/release-v0.3.3.en.md>) | v0.3.3 release notes (ZCode 3.11.2 adaptation + fail-closed acceptance engine) |
@@ -228,9 +236,9 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
228
236
  - Zcode headless entry (Z1) verified: ZCode desktop ships no headless CLI → unsupported
229
237
  - **R1–R8 / S1–S6 — two acceptance hardening rounds** ✅ (cancel / timeout / baseline attribution / parameter semantics / hot reload / CI hardening) — **72 tests**
230
238
  - **Engineering / CI** ✅
231
- - GitHub Actions: `CI` (ubuntu/windows/macos × Node 20/22/24 + pack-check, **10/10 green**, re-verified with the v0.4.1 tag) and `Release` (tag-triggered) both green
239
+ - GitHub Actions: `CI` (`build-test` ubuntu/windows/macos × Node 20/22/24 + `pack-check`, plus a `visual-browser` real-browser matrix ubuntu/windows/macos-15-intel/macos-15 × Node 20/22/24, all green with the v0.5.0 tag) and `Release` (tag-triggered) both green
232
240
  - Skill self-install verified idempotent on this machine's real `~/.rivet/skills/tianshu-mcp`
233
- - npm package name `tianshu-mcp` published continuously since v0.1.1 (currently `0.4.1`)
241
+ - npm package name `tianshu-mcp` published continuously since v0.1.1 (currently `0.5.0`)
234
242
  - **Real Tianshu host integration (DoD #6)** ✅ (2026-09-07)
235
243
  - Configured the local mode in the real `D:\Tianshu` desktop host `mcp.servers` → sidecar reported `MCP: 2 servers connected, 10 tools` (including this server's 8 tools), spawned the child process and connected over stdio
236
244
  - Exposed and fixed a skill-install source-path bug (fileURLToPath, commit 55cf2d0)
@@ -292,6 +300,13 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
292
300
  - `skills/tianshu-mcp/` now covers every v0.3.3 → v0.4.0 tool-surface change: the projectPath safety gate, the hard-failure error-code reference, the `setup_recovery` wait kind, the codex-cli headless path, `ready`/`research` status semantics, default-parallel-2 acceptance and the `requireChanges` gate; usage-examples adds the error-code table, the full meta field table, a project-level acceptance-config template and a `codex-cli` example
293
301
  - Bilingual README gains a contributor credits section (avatar + name, in order of first participation)
294
302
  - **No code behaviour changes**; no migration needed
303
+ - **M19 — optional visual acceptance module + v0.5.0** (2026-09-14) — **486 tests**
304
+ - **Page screenshot comparison**: three mutually exclusive page sources (existing service / command startup / temporary static host), three capture modes, declarative interaction steps, stabilization sampling with explicit masks, pixelmatch antialiasing exclusion and connected-region annotation; size mismatches fail directly
305
+ - **Static image specifications**: encoded-format/extension consistency, complete decoding, EXIF-orientation-normalized dimensions, aspect ratio/byte size/DPI/real transparent pixels; unsupported formats reported explicitly
306
+ - **Two-phase baselines and freezing**: candidate preparation → user approval; a missing baseline never passes; automatic repair may not approve; configuration and baseline digests are frozen before the agent starts and checked every round
307
+ - **MCP/CLI**: new `prepare_visual_baseline` / `approve_visual_baseline` and the `tianshu-mcp visual` subcommand family, dispatched before the stdio connection
308
+ - **Reports and recovery**: `VerifyReport` gains an optional `visual` section and offline HTML (status filter, opacity overlay, region location); visual blockers enter `needs_attention` and `rework_task` re-verifies first so only real defects consume repair budget
309
+ - **Gates**: CI adds a four-system, three-Node real-browser matrix plus isolated production-package consumer acceptance; release requires a successful CI for the target commit and blocks when Gitee credentials are missing rather than claiming success
295
310
 
296
311
  ## Agent support status
297
312
 
@@ -364,7 +379,7 @@ Behavior and limits:
364
379
 
365
380
  | Document | Content |
366
381
  |---|---|
367
- | [CHANGELOG.en.md](<CHANGELOG.en.md>) | Version history (v0.1.0 → v0.4.1) |
382
+ | [CHANGELOG.en.md](<CHANGELOG.en.md>) | Version history (v0.1.0 → v0.5.0) |
368
383
  | [CONTRIBUTING.en.md](CONTRIBUTING.en.md) | Dev setup, conventions, commit/release flow, adding an agent |
369
384
  | [SECURITY.en.md](SECURITY.en.md) | Security model (zero credentials / command whitelist / process & desktop-automation boundaries) and private reporting |
370
385
  | [CODE_OF_CONDUCT.en.md](CODE_OF_CONDUCT.en.md) | Contributor Code of Conduct |
package/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  # tianshu-mcp
10
10
 
11
+ 视觉验收(v0.5.0):[中文指南](docs/visual-acceptance.md) · [验证记录](docs/visual-validation.md) · [发布说明](<docs/release-v0.5.0.md>)。
12
+
11
13
  **天枢 × AI-Agent 编排 MCP server**
12
14
 
13
15
  由天枢(Tianshu)当作标准 MCP server 接入,调度外部 AI-Agent(Codex 桌面端、TraeWork/TRAE SOLO CN、ZCode 均经 CDP 驱动桌面 UI)完成 **项目开发 → 验收 → 失败返修 → 再验收** 的闭环(架构可横向扩展)。
@@ -64,7 +66,7 @@ git clone https://github.com/lanlan0811/tianshu-mcp.git
64
66
  cd tianshu-mcp
65
67
  npm ci
66
68
  npm run build # sync-version + tsc → dist/
67
- npm test # 443 项测试:46 个文件,含 Codex/ZCode 单元/假 CDP/重启/恢复/返修闭环
69
+ npm test # 496 项测试:56 个文件,含 Codex/ZCode/TraeWork 单元/假 CDP/重启/恢复/返修闭环与视觉验收
68
70
  ```
69
71
 
70
72
  ### 安装 npm 包
@@ -89,7 +91,7 @@ npm install -g tianshu-mcp
89
91
  > - 服务器 ID 即工具前缀:填 `tianshu-mcp` 后工具名为 `mcp__tianshu-mcp__run_task` 等 9 个。
90
92
  > - 参数按空格分隔填写,**不要加引号**;本地开发模式请把 `<仓库绝对路径>` 换成真实绝对路径(如 `D:/Trae项目/tianshu-mcp/dist/index.js`)。
91
93
  > - 界面未提供环境变量输入框;如需自定义数据目录,改用下面的 `config.json` 方式设置 `TIANSHU_MCP_HOME`。
92
- > - 添加后连接成功即完成;新开会话即可看到 9 个工具。
94
+ > - 添加后连接成功即完成;新开会话即可看到 11 个工具。
93
95
 
94
96
  ### 或改 config.json(可配环境变量)
95
97
 
@@ -109,7 +111,7 @@ npm install -g tianshu-mcp
109
111
  }
110
112
  ```
111
113
 
112
- 新开会话后,工具面出现 `mcp__tianshu-mcp__run_task` 等 9 个工具。用 stub 预演(不碰真实登录态)→ 切 codex 跑真实任务:
114
+ 新开会话后,工具面出现 `mcp__tianshu-mcp__run_task` 等 11 个工具。用 stub 预演(不碰真实登录态)→ 切 codex 跑真实任务:
113
115
 
114
116
  ```text
115
117
  run_task(projectPath=D:/xxx/my-app, task=「…任务书…」, agentId=codex,
@@ -144,7 +146,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=zcode, task=「按 `./plan.md` 完
144
146
 
145
147
  ZCode 提问、需要登录、旧实例无 CDP、系统权限不足,或自动恢复未完成(`needs_user/setup_recovery`)时进入 `needs_user`;处理后调用 `continue_task(taskId, message)` 恢复——确认文本不发给模型,无锚点的环境恢复会补发完整原任务、上下文与已验证引用,且不消耗返修轮数。模型选择已适配 ZCode 3.11.2:直选平铺模型优先,展开 provider/family 分组兜底,新旧布局均兼容。完整约束见 docs/zcode-cdp.md。
146
148
 
147
- ## 工具面(9 个)
149
+ ## 工具面(11 个)
148
150
 
149
151
  | 工具 | 能力 / 审批 | 作用 |
150
152
  |---|---|---|
@@ -157,6 +159,8 @@ ZCode 提问、需要登录、旧实例无 CDP、系统权限不足,或自动
157
159
  | `verify_task` | read | 对任务/项目路径做一次验收(不改源码) |
158
160
  | `rework_task` | write + 审批 | 手动返修(把失败报告喂回同一 agent) |
159
161
  | `get_profiles` | read | 查看 agent 适配与可执行探测结果 |
162
+ | `prepare_visual_baseline` | write + 审批 | 截图或导入参考图,生成待审阅候选和摘要 |
163
+ | `approve_visual_baseline` | write + 审批 | 用户审阅后校验摘要并写入基准与审批记录 |
160
164
 
161
165
  > 返回统一为「人类可读文本 + `---tianshu-mcp-meta---` JSON 块」,便于宿主正则抽取。
162
166
 
@@ -186,6 +190,10 @@ ZCode 提问、需要登录、旧实例无 CDP、系统权限不足,或自动
186
190
  | [docs/zcode-windows-smoke.md](docs/zcode-windows-smoke.md) | ZCode Windows 真机开发、同会话返修与提问续跑验收记录 |
187
191
  | [docs/codex-gui-cdp.md](docs/codex-gui-cdp.md) | Codex 桌面端 GUI 驱动:MSIX COM 激活、CDP 接管、选择器、运行检测、验收返修 |
188
192
  | [docs/codex-windows-smoke.md](docs/codex-windows-smoke.md) | Codex Windows 真机验收记录(含验收失败→自动生成计划→返修通过闭环) |
193
+ | [docs/release-v0.5.0.md](<docs/release-v0.5.0.md>) | v0.5.0 发布说明(可选视觉验收模块:截图对比、图片规格、基准批准、离线报告) |
194
+ | [docs/visual-acceptance.md](<docs/visual-acceptance.md>) | 视觉验收入门与完整配置:三种页面来源、基准候选/批准、规则冻结、阈值与排查 |
195
+ | [docs/visual-validation.md](<docs/visual-validation.md>) | 视觉验收验证进度:Windows 10 完整功能矩阵与 macOS Intel/Apple Silicon 平台证据(系统/Node/浏览器/命令/结果) |
196
+ | [docs/visual-validation-evidence/](<docs/visual-validation-evidence/>) | 上述验证的原始机器可读记录(环境 JSON、矩阵结果、测试输出与 macOS CI 摘要) |
189
197
  | [docs/release-v0.4.1.md](<docs/release-v0.4.1.md>) | v0.4.1 发布说明(技能文档对齐 v0.4.0 工具面 + 贡献者名录) |
190
198
  | [docs/release-v0.3.4.md](<docs/release-v0.3.4.md>) | v0.3.4 发布说明(ZCode 项目/模型回读、初始化恢复与会话发送确认,issue #8/#9/#10) |
191
199
  | [docs/zcode-issue-8-10-validation.md](<docs/zcode-issue-8-10-validation.md>) | ZCode #8/#9/#10 Windows 真机验收记录(冷导入、已导入复用、同任务恢复) |
@@ -227,9 +235,9 @@ ZCode 提问、需要登录、旧实例无 CDP、系统权限不足,或自动
227
235
  - Zcode 无头接口(Z1)实测定论:ZCode 桌面无随包 headless CLI → unsupported
228
236
  - **R1–R8 / S1–S6 — 两轮验收整改** ✅(取消/超时/基线归因/参数语义/热加载/CI 加固)— **72 测试**
229
237
  - **工程 / CI** ✅
230
- - GitHub Actions:`CI`(ubuntu/windows/macos × Node 20/22/24 + pack-check,**10/10 全绿**,随 v0.4.1 tag 再次校验)与 `Release`(tag 触发)均绿
238
+ - GitHub Actions:`CI`(`build-test` ubuntu/windows/macos × Node 20/22/24 + `pack-check`,另加 `visual-browser` 真实浏览器矩阵 ubuntu/windows/macos-15-intel/macos-15 × Node 20/22/24,随 v0.5.0 tag 全绿)与 `Release`(tag 触发)均绿
231
239
  - 技能自检安装已在本机真实 `~/.rivet/skills/tianshu-mcp` 验证生效且幂等
232
- - npm 包名 `tianshu-mcp` 自 v0.1.1 起持续发布(当前 `0.4.1`)
240
+ - npm 包名 `tianshu-mcp` 自 v0.1.1 起持续发布(当前 `0.5.0`)
233
241
  - **天枢宿主真实接入(DoD #6)** ✅(2026-09-07,[host-integration-record.md](docs/host-integration-record.md))
234
242
  - 在真实 `D:\Tianshu` 桌面宿主 `mcp.servers` 配置本地模式 → sidecar `MCP: 2 servers connected, 10 tools`(含本 server 8 工具),spawn 子进程并 stdio 连通
235
243
  - 实测暴露并修复技能安装源路径 bug(fileURLToPath,提交 55cf2d0)
@@ -291,6 +299,13 @@ ZCode 提问、需要登录、旧实例无 CDP、系统权限不足,或自动
291
299
  - `skills/tianshu-mcp/` 逐项补齐 v0.3.3 → v0.4.0 的工具面变化:projectPath 安全闸门、硬失败错误码速查表、`setup_recovery` 等待类型、codex-cli 无头路径、`ready`/`research` 状态语义、验收默认并行 2 与 `requireChanges` 门禁;usage-examples 新增错误码表、meta 字段全表、项目级验收配置模板与 `codex-cli` 示例
292
300
  - 双语 README 新增贡献者名录(头像 + 名字,按首次参与顺序)
293
301
  - 本版本**无代码行为变更**,升级无需迁移
302
+ - **M19 — 可选视觉验收模块 + v0.5.0**(2026-09-14)— **486 测试**
303
+ - **页面截图对比**:三种互斥页面来源(已有服务/命令启动/临时静态托管)、三种截图模式、声明式交互步骤、稳定化采样与显式屏蔽、pixelmatch 抗锯齿排除与连通区域标注;尺寸不一致直接失败
304
+ - **静态图片规格**:编码格式/扩展名一致性、完整解码、EXIF 方向归一宽高、宽高比/字节数/DPI/真实透明像素;不支持格式明确报告
305
+ - **基准两阶段与冻结**:候选准备 → 用户批准写入;缺基准不得判通过;自动返修禁止批准;任务动工前冻结配置与基准摘要并每轮核对
306
+ - **MCP/CLI**:新增 `prepare_visual_baseline` / `approve_visual_baseline` 与 `tianshu-mcp visual` 子命令族;CLI 在 stdio 连接前分流
307
+ - **报告与恢复**:`VerifyReport` 新增可选 `visual` 与离线 HTML(状态过滤、透明叠加、区域定位);视觉阻塞进 `needs_attention`,`rework_task` 先重新验收、仅真实缺陷才消耗返修预算
308
+ - **门禁**:CI 新增真实浏览器四系统三 Node 矩阵与生产包独立消费者验收;release 要求目标提交存在成功 CI,缺少 Gitee 凭据时阻塞不冒充成功
294
309
 
295
310
  ## Agent 适配现状
296
311
 
@@ -364,7 +379,7 @@ run_task(projectPath=/path/to/项目, agentId=codex-cli, task="任务书", autoV
364
379
  | 文档 | 内容 |
365
380
  |---|---|
366
381
  | [HANDOFF.md](HANDOFF.md) | 项目交接文档:当前状态快照、架构导览、硬性红线、已知限制、接手建议 |
367
- | [CHANGELOG.md](<CHANGELOG.md>) | 版本变更日志(v0.1.0 → v0.4.1) |
382
+ | [CHANGELOG.md](<CHANGELOG.md>) | 版本变更日志(v0.1.0 → v0.5.0) |
368
383
  | [CONTRIBUTING.md](CONTRIBUTING.md) | 开发环境、工程规范、提交与发布流程、如何新增 agent |
369
384
  | [SECURITY.md](SECURITY.md) | 安全模型(凭证零管理/命令白名单/进程与桌面自动化边界)与私密报告渠道 |
370
385
  | [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) | 贡献者行为准则 |
@@ -9,6 +9,7 @@
9
9
  * 而 Codex 只能读项目工作区内的文件。
10
10
  */
11
11
  import path from "node:path";
12
+ import { visualEvidence } from "../../visual/report.js";
12
13
  import { mkdirp, writeTextAtomic } from "../../util/fs.js";
13
14
  import { fixPlanRelPath, fixPlanAbsPath } from "./input.js";
14
15
  /** 渲染修复计划正文(含失败证据、通过项、代码分析、修复要求) */
@@ -20,6 +21,7 @@ export function renderCodexFixPlan(input) {
20
21
  const a = report.analysis;
21
22
  const roundNo = input.round + 1;
22
23
  const lines = [
24
+ visualEvidence(report),
23
25
  `# Codex 修复计划(第 ${roundNo} 轮返修)`,
24
26
  "",
25
27
  `- 任务 ID:\`${input.taskId}\``,
@@ -59,7 +61,12 @@ export function renderCodexFixPlan(input) {
59
61
  lines.push("## 4. 代码分析结果", "");
60
62
  const changed = [...a.changedFiles, ...a.untrackedFiles];
61
63
  lines.push(`- 变更文件(${changed.length} 个):`);
62
- lines.push(changed.length ? changed.slice(0, 50).map((f) => ` - \`${f}\``).join("\n") : " (无变更)");
64
+ lines.push(changed.length
65
+ ? changed
66
+ .slice(0, 50)
67
+ .map((f) => ` - \`${f}\``)
68
+ .join("\n")
69
+ : " (无变更)");
63
70
  lines.push(`- diffstat:+${a.diffstat.totalAdd} -${a.diffstat.totalDel}`);
64
71
  const sig = a.signals;
65
72
  lines.push(`- 可疑标记:TODO/FIXME ${sig.todo} 处、console.log/debugger ${sig.consoleDebug} 处、注释代码块 ${sig.commentedBlock} 处、疑似密钥 ${sig.secretLike} 处`);
@@ -65,13 +65,18 @@ export class ZcodeBudget {
65
65
  }
66
66
  async run(operation, cap = Infinity) {
67
67
  const timeoutMs = this.remaining(cap);
68
+ const deadline = Math.min(Date.now() + timeoutMs, this.taskDeadline, this.bound ? Infinity : this.setupDeadline);
68
69
  const controller = new AbortController();
69
70
  let reject;
70
71
  const aborted = new Promise((_, fail) => {
71
72
  reject = fail;
72
73
  });
74
+ let timer;
73
75
  const stop = () => {
74
- controller.abort();
76
+ if (!this.opts.signal?.aborted && Date.now() < deadline) {
77
+ timer = setTimeout(stop, Math.max(1, deadline - Date.now()));
78
+ return;
79
+ }
75
80
  const reason = this.opts.signal?.aborted
76
81
  ? "aborted"
77
82
  : Date.now() >= this.taskDeadline
@@ -80,14 +85,21 @@ export class ZcodeBudget {
80
85
  ? "setup_recovery"
81
86
  : "operation_timeout";
82
87
  reject(new ZcodeBudgetError(reason, this.stage));
88
+ // Preserve the controlling deadline/cancellation reason before dependent CDP
89
+ // operations synchronously reject their own promises in abort listeners.
90
+ controller.abort();
83
91
  };
84
- const timer = setTimeout(stop, timeoutMs);
92
+ timer = setTimeout(stop, timeoutMs);
85
93
  this.opts.signal?.addEventListener("abort", stop, { once: true });
86
94
  try {
87
95
  const result = await Promise.race([operation(controller.signal, timeoutMs), aborted]);
88
96
  this.check();
89
97
  return result;
90
98
  }
99
+ catch (error) {
100
+ this.check();
101
+ throw error;
102
+ }
91
103
  finally {
92
104
  clearTimeout(timer);
93
105
  this.opts.signal?.removeEventListener("abort", stop);
@@ -3,6 +3,7 @@
3
3
  * 所有外部输入都经这里解析,失败给默认值或明确报错(开发计划 §13)。
4
4
  */
5
5
  import { z } from "zod";
6
+ import { VisualConfigSchema } from "../visual/schema.js";
6
7
  /* ---------------- 工具入参 ---------------- */
7
8
  const AbsPath = z.string().min(1, "projectPath 不能为空");
8
9
  /** TraeWork 面板模式(Work=自带 agent 循环;Code=纯编码;Design=设计) */
@@ -286,7 +287,8 @@ export const ProjectRecordSchema = z.object({
286
287
  export const ProjectsFileSchema = z.record(z.string().min(1), ProjectRecordSchema);
287
288
  /* ---------------- 项目内 .tianshu-mcp/acceptance.json ---------------- */
288
289
  export const AcceptanceConfigSchema = z.object({
289
- checks: z.array(AcceptanceCheckSchema).default([]),
290
+ checks: z.array(AcceptanceCheckSchema).optional(),
291
+ visual: VisualConfigSchema.optional(),
290
292
  requireChanges: z.boolean().default(true),
291
293
  /** 命令检查并行度:1=串行(与历史行为一致);缺省继承 server config.json 的 verifyConcurrency(默认 2)。越界值 clamp 到 1..4(不再株连整份 acceptance.json 失效) */
292
294
  verifyConcurrency: z
package/dist/index.js CHANGED
@@ -11,6 +11,11 @@ import path from "node:path";
11
11
  import { resolveDataHome } from "./config/store.js";
12
12
  import { Logger } from "./util/log.js";
13
13
  async function main() {
14
+ if (process.argv[2] === "visual") {
15
+ const { runVisualCli } = await import("./visual/cli.js");
16
+ await runVisualCli(process.argv.slice(3));
17
+ return;
18
+ }
14
19
  const home = resolveDataHome();
15
20
  const logger = await Logger.create(path.join(home, "logs"));
16
21
  const skipSkillInstall = process.argv.includes("--no-skill-install") || process.env.TIANSHU_MCP_NO_SKILL_INSTALL === "1";
@@ -14,6 +14,12 @@ import { writeRepairPlan } from "./repair-plan.js";
14
14
  import { writeCodexFixPlan } from "../agents/codex/fixplan.js";
15
15
  import { buildFixPrompt } from "../agents/codex/input.js";
16
16
  import { extractFailureEvidence } from "../agents/codex/verify.js";
17
+ import { freezeVisualSnapshot, checkVisualSnapshot } from "../visual/snapshot.js";
18
+ import { visualEvidence } from "../visual/report.js";
19
+ import { withVisualLock } from "../visual/lock.js";
20
+ import path from "node:path";
21
+ import fsp from "node:fs/promises";
22
+ import { VisualError } from "../visual/errors.js";
17
23
  export class TaskOrchestrator {
18
24
  deps;
19
25
  meta;
@@ -32,11 +38,6 @@ export class TaskOrchestrator {
32
38
  const logger = this.deps.logger;
33
39
  const store = this.deps.store;
34
40
  try {
35
- // ---- 解析 agent(spawn/认证等基础设施错误不再重试)----
36
- const resolved = await this.deps.registry.resolve(meta.agentId, true);
37
- if (!resolved.ok) {
38
- return this.finish("failed", "agent_unresolved", `agent '${meta.agentId}' 不可用:${resolved.message}`);
39
- }
40
41
  // ---- 采集 git 基线(动工前,R12)----
41
42
  await store.appendEvent(meta.taskId, "note", meta.status, "采集 git 基线…");
42
43
  await mkdirp(store.dir(meta.taskId));
@@ -44,9 +45,38 @@ export class TaskOrchestrator {
44
45
  const baseline = savedBaseline ?? (await captureBaseline(meta.projectPath));
45
46
  if (!savedBaseline)
46
47
  await writeJsonAtomic(store.baselinePath(meta.taskId), baseline);
48
+ try {
49
+ const frozen = await freezeVisualSnapshot(meta.projectPath, store.dir(meta.taskId));
50
+ await checkVisualSnapshot(meta.projectPath, frozen);
51
+ }
52
+ catch (e) {
53
+ if (this.aborted())
54
+ return this.abortTerminal();
55
+ meta.pendingVisualVerification = true;
56
+ return this.finish("needs_attention", "verify_failed", String(e));
57
+ }
58
+ let resumedFeedback;
59
+ if (meta.pendingVisualVerification) {
60
+ await store.updateStatus(meta, "running", "恢复视觉阻塞:先重新验收");
61
+ await store.updateStatus(meta, "verify_start", "恢复视觉阻塞:先重新验收");
62
+ const retry = await this.runVerifyOnce(meta, meta.roundsUsed, baseline);
63
+ if (this.aborted())
64
+ return this.abortTerminal();
65
+ if (retry.report.blockingIssues?.length)
66
+ return this.finish("needs_attention", "verify_failed", retry.report.message);
67
+ delete meta.pendingVisualVerification;
68
+ if (retry.passed)
69
+ return this.finish("succeeded", null, retry.summary);
70
+ if (meta.roundsUsed > meta.autoFixRounds)
71
+ return this.finish("failed", "verify_failed", retry.summary);
72
+ resumedFeedback = `${retry.summary}\n${visualEvidence(retry.report)}`;
73
+ }
74
+ const resolved = await this.deps.registry.resolve(meta.agentId, true);
75
+ if (!resolved.ok)
76
+ return this.finish("failed", "agent_unresolved", `agent '${meta.agentId}' 不可用:${resolved.message}`);
47
77
  // ---- 返修循环 ----
48
78
  let round = meta.roundsUsed;
49
- let feedback = this.initialFeedback;
79
+ let feedback = [this.initialFeedback, resumedFeedback].filter(Boolean).join("\n") || undefined;
50
80
  const maxRounds = meta.autoFixRounds;
51
81
  for (;;) {
52
82
  if (this.aborted())
@@ -59,7 +89,10 @@ export class TaskOrchestrator {
59
89
  delete meta.continueReobserve;
60
90
  await store.writeSnapshot(meta);
61
91
  }
62
- const runRes = await this.runAgentOnce(ctx, resolved);
92
+ const runRes = await withVisualLock(path.dirname(path.dirname(store.dir(meta.taskId))), await fsp.realpath(meta.projectPath), async () => {
93
+ await checkVisualSnapshot(meta.projectPath, await freezeVisualSnapshot(meta.projectPath, store.dir(meta.taskId)));
94
+ return this.runAgentOnce(ctx, resolved);
95
+ });
63
96
  meta.logFile = runRes.logFile;
64
97
  meta.agentEndReason = runRes.endReason;
65
98
  meta.lastRunSignal = runRes.endReason ?? meta.lastRunSignal;
@@ -118,6 +151,10 @@ export class TaskOrchestrator {
118
151
  meta.diffstat = verdict.diffstat;
119
152
  if (this.aborted())
120
153
  return this.abortTerminal(); // 验收期间被取消:进入终态,不进入返修
154
+ if (verdict.report.blockingIssues?.length) {
155
+ meta.pendingVisualVerification = true;
156
+ return this.finish("needs_attention", "verify_failed", verdict.report.message);
157
+ }
121
158
  if (verdict.passed) {
122
159
  return this.finish("succeeded", null, verdict.summary);
123
160
  }
@@ -189,6 +226,12 @@ export class TaskOrchestrator {
189
226
  }
190
227
  }
191
228
  catch (e) {
229
+ if (this.aborted())
230
+ return this.abortTerminal();
231
+ if (e instanceof VisualError) {
232
+ meta.pendingVisualVerification = true;
233
+ return this.finish("needs_attention", "verify_failed", e.message);
234
+ }
192
235
  const msg = e instanceof Error ? e.message : String(e);
193
236
  logger.error(`任务 ${meta.taskId} 编排异常: ${msg}`);
194
237
  return this.finish("failed", "internal", `内部错误:${msg}`);
@@ -312,6 +355,10 @@ export class TaskOrchestrator {
312
355
  logger: this.deps.logger,
313
356
  };
314
357
  const { report, passed } = await this.deps.engine.runVerify(req);
358
+ meta.reportRound = report.round;
359
+ meta.verificationSource = "auto";
360
+ meta.reportMd = report.files.md;
361
+ meta.reportJson = report.files.json;
315
362
  const summary = summarizeReport(report);
316
363
  return {
317
364
  passed,
@@ -7,6 +7,7 @@
7
7
  * 仅写入 MCP 任务数据目录,避免临时计划污染项目工作区。
8
8
  */
9
9
  import path from "node:path";
10
+ import { visualEvidence } from "../visual/report.js";
10
11
  import { mkdirp, writeTextAtomic } from "../util/fs.js";
11
12
  /** 生成修复计划 markdown 正文 */
12
13
  export function renderRepairPlan(input) {
@@ -16,6 +17,7 @@ export function renderRepairPlan(input) {
16
17
  const passed = report.checks.filter((c) => c.passed);
17
18
  const a = report.analysis;
18
19
  const lines = [
20
+ visualEvidence(report),
19
21
  `# 修复计划(第 ${input.round + 1} 轮返修)`,
20
22
  "",
21
23
  `- 任务 ID:\`${input.taskId}\``,
@@ -3,6 +3,7 @@
3
3
  * run_task / rework / verify 依赖 AppContext 提供的 manager/engine/services。
4
4
  */
5
5
  import fsp from "node:fs/promises";
6
+ import { prepareBaseline, approveBaseline, PrepareBaselineSchema, ApproveBaselineSchema, } from "../visual/baselines.js";
6
7
  import { assertSafeProjectDir, normPath, resolveProjectDir } from "../util/path.js";
7
8
  import { execFileAsync } from "../verify/exec.js";
8
9
  import { toAcceptanceDef } from "../config/store.js";
@@ -28,6 +29,8 @@ async function nextReportRound(store, taskId) {
28
29
  }
29
30
  export function makeHandlers(ctx, defaults) {
30
31
  return {
32
+ prepare_visual_baseline: async (args) => textResult(JSON.stringify(await ctx.engine.runVisualOperation((signal) => prepareBaseline(ctx.dataHome.dir, PrepareBaselineSchema.parse(args), signal)), null, 2)),
33
+ approve_visual_baseline: async (args) => textResult(JSON.stringify(await approveBaseline(ctx.dataHome.dir, ApproveBaselineSchema.parse(args)), null, 2)),
31
34
  run_task: runTaskHandler(ctx, defaults),
32
35
  query_task: queryTaskHandler(ctx),
33
36
  list_tasks: listTasksHandler(ctx),
@@ -259,9 +262,7 @@ function cancelTaskHandler(ctx) {
259
262
  const meta = await manager.getMeta(args.taskId);
260
263
  if (meta) {
261
264
  // settled=false:GUI 侧停止尚未确认(issue #6 语义),明示编排方稍后复核
262
- const note = res.settled === false
263
- ? "(尚未落终态:GUI 侧停止可能未完成,请稍后 query_task 复核)"
264
- : "";
265
+ const note = res.settled === false ? "(尚未落终态:GUI 侧停止可能未完成,请稍后 query_task 复核)" : "";
265
266
  return formatToolResult((res.reason ?? `已取消 ${args.taskId}。`) + note, metaFromTask(meta));
266
267
  }
267
268
  return errorResult(res.reason ?? `任务不存在: ${args.taskId}`);
@@ -338,7 +339,7 @@ function verifyTaskHandler(ctx) {
338
339
  };
339
340
  });
340
341
  // round 分配:手动验收写入任务目录时不能覆盖已有 report-0.*,分配下一可用轮次
341
- const round = await nextReportRound(store, taskId);
342
+ let round = await nextReportRound(store, taskId);
342
343
  const verifyTaskId = taskId ?? `vfy_${Date.now()}`;
343
344
  const req = {
344
345
  taskId: verifyTaskId,
@@ -355,6 +356,7 @@ function verifyTaskHandler(ctx) {
355
356
  logger,
356
357
  };
357
358
  const { report, passed } = await engine.runVerify(req);
359
+ round = report.round;
358
360
  const head = passed
359
361
  ? `[PASS] 手动验收通过(reportRound ${round}):${report.checks.filter((c) => c.passed).length}/${report.checks.length} 项检查通过。`
360
362
  : `[FAIL] 手动验收失败(reportRound ${round}):${report.checks.filter((c) => !c.passed && !c.skipped).length} 项检查未通过。`;
@@ -382,7 +384,7 @@ function verifyTaskHandler(ctx) {
382
384
  // 独立 projectPath 验收:创建并持久化独立 vfy 记录
383
385
  resultMeta = {
384
386
  taskId: verifyTaskId,
385
- status: passed ? "succeeded" : "failed",
387
+ status: passed ? "succeeded" : report.blockingIssues?.length ? "needs_attention" : "failed",
386
388
  projectPath,
387
389
  displayPath,
388
390
  agentId: "manual-verify",