tianshu-mcp 0.7.2 → 0.7.4
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.
- package/CHANGELOG.en.md +90 -0
- package/CHANGELOG.md +69 -0
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/agents/zcode/cdp.js +29 -1
- package/dist/agents/zcode/dom.js +12 -2
- package/dist/agents/zcode/project.js +53 -2
- package/dist/agents/zcode/run.js +63 -31
- package/dist/agents/zcode/selectors.js +7 -1
- package/dist/version.generated.js +1 -1
- package/package.json +1 -1
- package/scripts/probe-zcode.mjs +72 -0
- package/skills/tianshu-mcp/SKILL.md +8 -0
- package/skills/tianshu-mcp/usage-examples.md +11 -2
package/CHANGELOG.en.md
CHANGED
|
@@ -8,6 +8,96 @@ Chinese version: [CHANGELOG.md](CHANGELOG.md)
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## [0.7.4] - 2026-09-28
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Project-scoped dispatch on ZCode 3.14.x always failed with `project_mismatch` (issue #24).**
|
|
16
|
+
ZCode 3.14.x removed the two DOM contracts tianshu-mcp used for **binding read-back**
|
|
17
|
+
(`data-project-path` and `data-testid^="workspace-item-"`), while `pathOf()` in
|
|
18
|
+
`src/agents/zcode/dom.ts:27` recognises nothing else — so `workspaceBinding().projectPath` was always
|
|
19
|
+
empty and `cdp.projects()` always returned `[]`, leaving the binding criterion unsatisfiable forever.
|
|
20
|
+
The failure happens **before the task brief is ever sent**, which made `autoFixRounds` inert
|
|
21
|
+
(`roundsUsed` stayed 0). The fix touches four places, all stemming from that one root cause:
|
|
22
|
+
- `selectors.ts:107` gains `projectMenuItem` (`[role="menuitemcheckbox"]`), and `projectItem` /
|
|
23
|
+
`projectPath` are now explicitly labelled as 3.11.x contracts;
|
|
24
|
+
- `workspaceBindingExpression()` in `dom.ts:38` now also returns `projectName` (the bound display name)
|
|
25
|
+
and `menuChecked` (display names of menu items with `aria-checked="true"`), treating multiple checked
|
|
26
|
+
items as ambiguity; it **never fabricates a path** — on 3.14.x `projectPath` stays an empty string, and
|
|
27
|
+
the decision layer uses that to distinguish evidence strength;
|
|
28
|
+
- `projects()` in `cdp.ts:279` falls back to harvesting the expanded menu **only when the old contracts
|
|
29
|
+
yield nothing**, and it includes the checked state and excludes "work outside a project", so the
|
|
30
|
+
3.11.x result is byte-for-byte unchanged;
|
|
31
|
+
- `matchZcodeProject()` in `project.ts:61` gains a **display-name exact-match** branch and reports
|
|
32
|
+
`matchedBy`; a new `boundProjectVerdict()` implements the layered criterion — **strict path equality
|
|
33
|
+
whenever a path is available (unchanged semantics), display name when there is no path channel** —
|
|
34
|
+
and requires the project list to be free of same-name ambiguity.
|
|
35
|
+
- **Removed a fragile timing dependency**: `ensureProjectBound()` used to accept an externally supplied
|
|
36
|
+
item, which made "the menu happens to still be open" an implicit precondition. It is now an idempotent
|
|
37
|
+
self-check: read the verdict first, pass immediately when already bound, and only expand the menu and
|
|
38
|
+
click when it is not.
|
|
39
|
+
- **Better failure diagnostics**: the `project_mismatch` message now carries the trigger text, the menu
|
|
40
|
+
checked state and the path read-back, so users are no longer left with a bare "read-back mismatch".
|
|
41
|
+
- **On-device evidence entry point**: `scripts/probe-zcode.mjs` gains a `dom-contracts` subcommand that
|
|
42
|
+
reports hit counts for both legacy contracts, the trigger's full attributes, the menu item list and the
|
|
43
|
+
post-fix read-back — for re-checking this contract assumption against a real ZCode build.
|
|
44
|
+
|
|
45
|
+
### Known limitation
|
|
46
|
+
|
|
47
|
+
- The ZCode 3.14.x DOM exposes a project's **display name** but not its absolute path, so the criterion is
|
|
48
|
+
"display name + global same-name disambiguation": more than one same-name item yields
|
|
49
|
+
`project_ambiguous` (fail-closed). Disambiguation requires the menu to be expanded (that is `projects()`'
|
|
50
|
+
data source); with the menu closed only the `binding` display-name evidence is used, and a missing list
|
|
51
|
+
never negates a binding. The 3.11.x path-equality chain keeps the highest priority and is unchanged.
|
|
52
|
+
|
|
53
|
+
### Tests
|
|
54
|
+
|
|
55
|
+
- **14** new cases: 4 in `test/unit/zcode-dom.test.ts` (read-back contract under the missing 3.14.x
|
|
56
|
+
contracts, multi-check ambiguity, and no regression for 3.11.x), 6 in `test/unit/zcode-core.test.ts`
|
|
57
|
+
(display-name matching / same-name fail-closed / layered criterion / path priority), and 4 in
|
|
58
|
+
`test/integration/zcode-flow.test.ts` (already-bound dispatch without a stray click, switch-then-dispatch,
|
|
59
|
+
same-name ambiguity fail-closed, and a **counter-example** where missing display-name evidence still
|
|
60
|
+
fails closed with diagnostics). Full suite: **1383 passed / 12 skipped** (1395 tests, 115 files plus 3
|
|
61
|
+
real-browser files skipped by design).
|
|
62
|
+
|
|
63
|
+
> **On-device verification boundary (disclosed honestly)**: this machine has **no ZCode 3.14.x**, so the fix
|
|
64
|
+
> was not re-verified on hardware. The root cause (missing DOM contracts) is **deterministically reproducible
|
|
65
|
+
> locally** against a `linkedom` fixture: the new cases were first written to show three failing assertions
|
|
66
|
+
> (`projectName`/`menuChecked` absent, `projects()` empty, multi-check not treated as ambiguous) and then
|
|
67
|
+
> driven to green. The on-device measurements reported in issue #24 (`data-project-path` 0 hits,
|
|
68
|
+
> `workspace-item-` 0 hits, `menuitemcheckbox` intact) were **not independently reproduced**; the fix is
|
|
69
|
+
> designed against those contracts. Maintainers with a 3.14.x environment should run
|
|
70
|
+
> `npm run probe:zcode -- dom-contracts` to confirm.
|
|
71
|
+
|
|
72
|
+
## [0.7.3] - 2026-09-28
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
|
|
76
|
+
- **Skill docs had drifted from the implementation** (`skills/tianshu-mcp/`):
|
|
77
|
+
- `usage-examples.md` carried a **duplicate `### 2.9`** (codex-cli and the shared-conventions section
|
|
78
|
+
collided) → the latter is now `### 2.10`;
|
|
79
|
+
- `SKILL.md` §9 did not state that its error-code table is a subset — `endReason` is typed as an optional
|
|
80
|
+
`string` (`src/agents/adapter.ts:93`) with **no enum constraint**, so an adapter can add a value without a
|
|
81
|
+
type change. The doc now says so, and points readers at the adapter's `run.ts` for unknown values;
|
|
82
|
+
- added three high-frequency `endReason` values that were missing from the table: `input_mismatch`
|
|
83
|
+
(a leftover draft makes the task land inside stale text), `send_unknown` (delivery unconfirmable —
|
|
84
|
+
**never resends**), and `reply_stable` (normal completion, not an error).
|
|
85
|
+
|
|
86
|
+
### Docs
|
|
87
|
+
|
|
88
|
+
- **Documented Open Design artifact retrieval** (a `v0.7.1` capability, previously absent from the skill):
|
|
89
|
+
designs live in the product's artifact store `<dataRoot>/projects/<projectId>/<entry>`, **not** in the
|
|
90
|
+
task directory; once terminal the adapter copies it into `projectPath`.
|
|
91
|
+
- **Documented the daemon-readiness precondition**: the product completes an auth handshake with its daemon
|
|
92
|
+
before opening the folder picker (measured ~30s after launch); when unready the product **opens no dialog
|
|
93
|
+
at all**.
|
|
94
|
+
- **Marked the `zip` export as a known limitation** (the product's main process takes over downloads,
|
|
95
|
+
overriding CDP's handling; the `html` path is fully working).
|
|
96
|
+
|
|
97
|
+
> This release contains **no runtime code changes** (only `skills/` docs and the version bump), so upgrading
|
|
98
|
+
> from `0.7.2` changes no behaviour; it syncs docs with implementation and aligns tag / Release / npm on a
|
|
99
|
+
> single commit.
|
|
100
|
+
|
|
11
101
|
## [0.7.2] - 2026-09-28
|
|
12
102
|
|
|
13
103
|
### Fixed
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,75 @@
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.7.4] - 2026-09-28
|
|
11
|
+
|
|
12
|
+
### 修复
|
|
13
|
+
|
|
14
|
+
- **ZCode 3.14.x 有项目派单恒败于 `project_mismatch`(issue #24)**。ZCode 3.14.x 删除了 tianshu-mcp
|
|
15
|
+
用于**绑定回读**的两处 DOM 契约(`data-project-path` 与 `data-testid^="workspace-item-"`),而
|
|
16
|
+
`src/agents/zcode/dom.ts:27` 的 `pathOf()` 只认这两个来源 —— 于是 `workspaceBinding().projectPath`
|
|
17
|
+
恒为空、`cdp.projects()` 恒为 `[]`,绑定判据永不成立。失败发生在**发送任务书之前**,
|
|
18
|
+
`autoFixRounds` 形同虚设(`roundsUsed` 恒为 0)。修复落在四处(同一根因):
|
|
19
|
+
- `selectors.ts:107` 新增 `projectMenuItem`(`[role="menuitemcheckbox"]`),并把 `projectItem` /
|
|
20
|
+
`projectPath` 显式标注为 3.11.x 契约;
|
|
21
|
+
- `dom.ts:38` 的 `workspaceBindingExpression()` 增补 `projectName`(当前绑定显示名)与 `menuChecked`
|
|
22
|
+
(菜单中 `aria-checked="true"` 的显示名),多勾选判歧义;**不伪造路径** —— 3.14.x 下 `projectPath`
|
|
23
|
+
仍为空串,判定层据此区分证据等级;
|
|
24
|
+
- `cdp.ts:279` 的 `projects()` 改为「旧契约完全落空时才从展开菜单采集」(含勾选态、排除
|
|
25
|
+
「不在项目中工作」),保证 3.11.x 的采集结果逐字不变;
|
|
26
|
+
- `project.ts:61` 的 `matchZcodeProject()` 增加**显示名精确匹配**分支并返回 `matchedBy`,
|
|
27
|
+
新增 `boundProjectVerdict()` 实现分层判据:**路径可得时严格判等(原语义不变),无路径渠道时按显示名**,
|
|
28
|
+
且要求项目列表无同名歧义。
|
|
29
|
+
- **修掉一条被掩盖的脆弱时序**:`ensureProjectBound()` 原先接受外部传入的 item,使「菜单是否恰好还开着」
|
|
30
|
+
成为隐式前提。现改为幂等自检 —— 每轮先读判定,已绑定即通过、未绑定才展开菜单并点击。
|
|
31
|
+
- **绑定失败诊断补齐**:`project_mismatch` 的错误信息现携带「触发器文本 / 菜单勾选态 / 路径回读」,
|
|
32
|
+
用户不必再面对一句「回读不一致」而无法自救。
|
|
33
|
+
- **真机取证入口**:`scripts/probe-zcode.mjs` 新增 `dom-contracts` 子命令,输出两处旧契约的命中计数、
|
|
34
|
+
触发器全属性、菜单项清单与修复后的回读结果(用于在真实 ZCode 上复核本次契约假设)。
|
|
35
|
+
|
|
36
|
+
### 已知限制
|
|
37
|
+
|
|
38
|
+
- ZCode 3.14.x 的 DOM 只暴露项目**显示名**、不暴露绝对路径,因此判定采用「显示名 + 全局同名消歧」:
|
|
39
|
+
同名项 > 1 直接判 `project_ambiguous`(fail-closed)。**能区分的前提是菜单已展开**(`projects()` 的数据源);
|
|
40
|
+
菜单收起时只凭 `binding` 的显示名证据,不因列表缺失而否定绑定。3.11.x 的路径判等链路优先级最高,行为不变。
|
|
41
|
+
|
|
42
|
+
### 测试
|
|
43
|
+
|
|
44
|
+
- 新增 **14** 用例:`test/unit/zcode-dom.test.ts` 4 条(3.14.x 契约缺席下的回读契约 + 多勾选歧义 +
|
|
45
|
+
3.11.x 无退化)、`test/unit/zcode-core.test.ts` 6 条(显示名匹配 / 同名 fail-closed / 分层判据 / 路径优先)、
|
|
46
|
+
`test/integration/zcode-flow.test.ts` 4 条(已绑定直发不误点、未绑定切换后派发、同名歧义 fail-closed、
|
|
47
|
+
**缺显示名证据的反例**:仍 fail-closed 且错误信息带诊断)。全量 **1383 passed / 12 skipped**
|
|
48
|
+
(1395 项,115 文件 + 3 个真实浏览器文件按设计 skip)。
|
|
49
|
+
|
|
50
|
+
> **真机验证边界(如实披露)**:本机**无 ZCode 3.14.x**,未在真机复验。根因(DOM 契约缺席)在
|
|
51
|
+
> `linkedom` 夹具上可**本地决定性复现**:先写用例确认 3 处红灯(`projectName`/`menuChecked` 缺失、
|
|
52
|
+
> `projects()` 为空、多勾选不判歧义),再修到全绿。issue #24 报告的真机实测数据(`data-project-path`
|
|
53
|
+
> 0 命中、`workspace-item-` 0 命中、`menuitemcheckbox` 完好)**未被独立复现**,修复按该契约设计;
|
|
54
|
+
> 有 3.14.x 环境的维护者请跑 `npm run probe:zcode -- dom-contracts` 复核一次。
|
|
55
|
+
|
|
56
|
+
## [0.7.3] - 2026-09-28
|
|
57
|
+
|
|
58
|
+
### 修复
|
|
59
|
+
|
|
60
|
+
- **skill 文档与实现脱节**(`skills/tianshu-mcp/`):
|
|
61
|
+
- `usage-examples.md` 存在**两个 `### 2.9`**(codex-cli 与通用约定撞号)→ 后者改为 `### 2.10`;
|
|
62
|
+
- `SKILL.md` §9 的错误码表未声明「这是子集而非闭集」—— `endReason` 在类型层面只是可选
|
|
63
|
+
`string`(`src/agents/adapter.ts:93`),**没有枚举约束**,适配器新增分支无需改类型即可引入新取值。
|
|
64
|
+
现已显式声明,并提示读者遇到表外取值时去读对应适配器的 `run.ts`;
|
|
65
|
+
- 补入三个真机高频但表中缺失的 `endReason`:`input_mismatch`(输入框残留导致任务被插进残文)、
|
|
66
|
+
`send_unknown`(无法确认落地,**绝不重发**)、`reply_stable`(正常完成,非错误)。
|
|
67
|
+
|
|
68
|
+
### 文档
|
|
69
|
+
|
|
70
|
+
- **补写 Open Design 产物取回**(`v0.7.1` 起的能力,此前未进 skill 正文):设计稿存在产品的产物存储
|
|
71
|
+
`<dataRoot>/projects/<projectId>/<entry>` 而**不在任务目录**,终态后由适配器复制到 `projectPath`。
|
|
72
|
+
- **补写 daemon 就绪前置**:产品打开文件夹选择器前需与 daemon 完成鉴权握手(实测启动后约 30s 才驻留),
|
|
73
|
+
未就绪时产品**根本不弹对话框**。
|
|
74
|
+
- **标注 `zip` 导出为已知限制**(产品主进程接管下载,CDP 接管被覆盖;`html` 路径完全可用)。
|
|
75
|
+
|
|
76
|
+
> 本版**不含运行时代码变更**(仅 `skills/` 文档 + 版本号),从 `0.7.2` 升级无功能差异;
|
|
77
|
+
> 发布目的是让文档与实现同步、并让 tag / Release / npm 收敛到同一 commit。
|
|
78
|
+
|
|
10
79
|
## [0.7.2] - 2026-09-28
|
|
11
80
|
|
|
12
81
|
### 修复
|
package/README.en.md
CHANGED
|
@@ -77,7 +77,7 @@ git clone https://github.com/lanlan0811/tianshu-mcp.git
|
|
|
77
77
|
cd tianshu-mcp
|
|
78
78
|
npm ci
|
|
79
79
|
npm run build # sync-version + tsc → dist/
|
|
80
|
-
npm test #
|
|
80
|
+
npm test # 1383 passed / 12 skipped (1395 tests, 115 files: unit/integration/protocol plus 3 real-browser files skipped by design)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
### Install the npm package
|
|
@@ -494,7 +494,7 @@ Qoder CN uses `agentId="qoder"` and requires an existing `projectPath` plus a re
|
|
|
494
494
|
| agentId | driver / adapter | status | Notes |
|
|
495
495
|
|---|---|---|---|
|
|
496
496
|
| `codex` | `gui` / `codex-gui` | **ready** (`research` on macOS) | Desktop GUI over CDP (Windows: MSIX COM activation; macOS: spawn .app binary + CDP); supports `model`/`reasoningLevel`/`planDoc`/`designSystem`; wait-user, cancel and dispatch-guard semantics machine-verified (v0.3.2); Windows machine-verified; macOS basic closed loop machine-verified (v0.4.0) — stays `research` until the cancel/rework matrix is covered |
|
|
497
|
-
| `zcode` | `gui` / `zcode-gui` | **research** | CDP GUI adapter with the Windows hardware loop passed; adapted to ZCode 3.11.2 model menu and project binding (v0.3.3), with hardened project/model read-back and initialization recovery (v0.3.4); supports project-less dispatch and `allowCreateProject` (v0.5.2, issue #12), and v0.5.3 fixed instance survival across server exit, new-task page switching and send-failure attribution; macOS basic closed loop machine-verified (2026-09-13, v0.4.0) — stays `research` until the cancel/rework/new-project matrix is covered |
|
|
497
|
+
| `zcode` | `gui` / `zcode-gui` | **research** | CDP GUI adapter with the Windows hardware loop passed; adapted to ZCode 3.11.2 model menu and project binding (v0.3.3), with hardened project/model read-back and initialization recovery (v0.3.4); supports project-less dispatch and `allowCreateProject` (v0.5.2, issue #12), and v0.5.3 fixed instance survival across server exit, new-task page switching and send-failure attribution; **v0.7.4 adapts to ZCode 3.14.x** (the product removed the `data-project-path` and `workspace-item-*` contracts, so the binding criterion is now "path first, display name plus global same-name disambiguation when no path channel exists", failing closed on ambiguity); macOS basic closed loop machine-verified (2026-09-13, v0.4.0) — stays `research` until the cancel/rework/new-project matrix is covered |
|
|
498
498
|
| `traework` | `gui` / `traework-gui` | **ready** | CDP-driven TRAE SOLO CN desktop UI; all three panel modes machine-verified |
|
|
499
499
|
| `kimicode` | `gui` / `kimicode-gui` | **ready** (`research` on macOS) | Kimi Code desktop (Electron, measured 1.0.2); **two renderer processes** (main window + `Kimi Browser Overlay` carrying the model/tier/mode menus); workspaces bind by full path and unregistered ones are imported through the native "add workspace" dialog; supports `model`/`reasoningLevel` and rejects `mode`; Windows machine-verified for the success path, unregistered-workspace import + auto-acceptance, and the failure → rework → re-acceptance same-session loop; cancellation/question answering are covered by hermetic integration tests only, and macOS stays `research` and fail-closed |
|
|
500
500
|
| `qoder` | `gui` / `qoder-gui` | Windows real GUI loop passed; macOS **research** | Qoder CN only; full-path workspaces, unregistered directories imported via New Workspace + the native folder picker, default/custom models, global reasoning readback through Model Management, same-session answers and rework; Windows machine-verified for default-model development in an existing workspace, custom-model development in a newly imported workspace, and the controlled failure → plan → same-session repair → re-acceptance loop; macOS is `research` and fail-closed (dispatch disabled) |
|
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ git clone https://github.com/lanlan0811/tianshu-mcp.git
|
|
|
77
77
|
cd tianshu-mcp
|
|
78
78
|
npm ci
|
|
79
79
|
npm run build # sync-version + tsc → dist/
|
|
80
|
-
npm test #
|
|
80
|
+
npm test # 1383 passed / 12 skipped(1395 项,115 个测试文件:单元/集成/协议 + 3 个真实浏览器文件按设计 skip)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
### 安装 npm 包
|
|
@@ -495,7 +495,7 @@ Qoder CN 使用 `agentId="qoder"`,必须提供已有 `projectPath` 和可读 `
|
|
|
495
495
|
| agentId | driver / adapter | status | 说明 |
|
|
496
496
|
|---|---|---|---|
|
|
497
497
|
| `codex` | `gui` / `codex-gui` | **ready**(macOS 为 `research`) | Codex 桌面端 GUI(Windows:MSIX COM 激活 + CDP;macOS:spawn .app + CDP);支持 `model`/`reasoningLevel`/`planDoc`/`designSystem`;等待用户确认、取消与重派护栏均已真机验证(v0.3.2);Windows 真机已验证;macOS 基本闭环已真机验证(v0.4.0),取消/返修矩阵补齐前保持 `research` |
|
|
498
|
-
| `zcode` | `gui` / `zcode-gui` | **research** | CDP GUI adapter 已实现且 Windows 真机闭环通过;已适配 ZCode 3.11.2 模型菜单与项目绑定(v0.3.3),并加固项目/模型回读与初始化恢复(v0.3.4);支持无项目派发与 `allowCreateProject`(v0.5.2,issue #12),v0.5.3 修复实例跨 server
|
|
498
|
+
| `zcode` | `gui` / `zcode-gui` | **research** | CDP GUI adapter 已实现且 Windows 真机闭环通过;已适配 ZCode 3.11.2 模型菜单与项目绑定(v0.3.3),并加固项目/模型回读与初始化恢复(v0.3.4);支持无项目派发与 `allowCreateProject`(v0.5.2,issue #12),v0.5.3 修复实例跨 server 驻留、新建任务切页与发送失败归因;**v0.7.4 适配 ZCode 3.14.x**(`data-project-path` 与 `workspace-item-*` 两处旧契约已被产品删除 → 绑定判据改为「路径优先、无路径渠道时按显示名 + 全局同名消歧」,同名即 fail-closed);macOS 基本闭环已真机验证(2026-09-13,v0.4.0),取消/返修/新建项目矩阵补齐前保持 `research` |
|
|
499
499
|
| `traework` | `gui` / `traework-gui` | **ready** | CDP 驱动 TRAE SOLO CN 桌面 UI;三种面板模式真机验证通过 |
|
|
500
500
|
| `kimicode` | `gui` / `kimicode-gui` | **ready**(macOS 为 `research`) | Kimi Code 桌面端(Electron,实测 1.0.2);**双渲染进程**(主窗口 + `Kimi Browser Overlay` 浮层承载模型/档位/模式菜单);工作区以完整路径绑定,未登记时经原生「添加工作区」对话框导入;支持 `model`/`reasoningLevel`,不支持 `mode`;Windows 真机已验证成功路径、未登记工作区导入 + 自动验收、失败 → 返修 → 再验收同会话闭环;取消/提问续答仅由 hermetic 集成测试覆盖,macOS 为 `research` 且 fail-closed |
|
|
501
501
|
| `qoder` | `gui` / `qoder-gui` | Windows 真机闭环通过;macOS **research** | 仅 Qoder CN;完整路径工作区、未登记目录经新建工作区 + 原生目录选择器导入、默认/自定义模型、模型管理全局思考等级保存回读、原会话续答与返修;Windows 真机验证:已有工作区默认模型、新登记工作区自定义模型、受控失败 → 落计划 → 原会话返修 → 再验收均通过;macOS 为 `research` 且 fail-closed(禁止派发) |
|
package/dist/agents/zcode/cdp.js
CHANGED
|
@@ -178,7 +178,35 @@ export class ZcodeCdpClient {
|
|
|
178
178
|
return normalizeZcodeModelSelection(raw);
|
|
179
179
|
}
|
|
180
180
|
async projects() {
|
|
181
|
-
return this.evaluate(`(function(){
|
|
181
|
+
return this.evaluate(`(function(){
|
|
182
|
+
const norm=s=>(s||'').normalize('NFKC').trim();
|
|
183
|
+
const out=[],seen=new Set();
|
|
184
|
+
const push=(name,path,id,checked)=>{
|
|
185
|
+
const n=(name||'').trim();
|
|
186
|
+
if(!n)return;
|
|
187
|
+
const key=norm(n).toLocaleLowerCase();
|
|
188
|
+
if(seen.has(key))return;
|
|
189
|
+
seen.add(key);
|
|
190
|
+
out.push({name:n,path:path||undefined,id:id||undefined,checked});
|
|
191
|
+
};
|
|
192
|
+
for(const s of ${candidateExpr("projectItem", this.selectors)})for(const e of document.querySelectorAll(s)){
|
|
193
|
+
const testid=e.getAttribute('data-testid')||'';
|
|
194
|
+
const testPath=testid.startsWith('workspace-item-')?testid.slice('workspace-item-'.length):undefined;
|
|
195
|
+
const name=(e.getAttribute('data-project-name')||e.querySelector('[class*=name]')?.textContent||e.textContent||'').trim();
|
|
196
|
+
const p=e.getAttribute('data-project-path')||e.querySelector('[data-project-path]')?.getAttribute('data-project-path')||testPath||e.getAttribute('title')||undefined;
|
|
197
|
+
const id=e.getAttribute('data-project-id')||e.getAttribute('data-id')||testid||undefined;
|
|
198
|
+
push(name,p,id,undefined);
|
|
199
|
+
}
|
|
200
|
+
// issue #24:ZCode 3.14.x 已删除 workspace-item-*,项目列表只能从展开的下拉菜单采集。
|
|
201
|
+
// 仅在旧契约完全落空时启用,保证 3.11.x 的采集结果逐字不变。
|
|
202
|
+
if(!out.length)for(const s of ${candidateExpr("projectMenuItem", this.selectors)})for(const e of document.querySelectorAll(s)){
|
|
203
|
+
const name=(e.getAttribute('aria-label')||e.textContent||'').trim();
|
|
204
|
+
// 「不在项目中工作」是工作区切换项,不是项目——按本地化标签排除。
|
|
205
|
+
if(/不在项目中|outside a project|work outside/i.test(name))continue;
|
|
206
|
+
push(name,undefined,e.getAttribute('data-value')||undefined,e.getAttribute('aria-checked')==='true');
|
|
207
|
+
}
|
|
208
|
+
return out
|
|
209
|
+
})()`);
|
|
182
210
|
}
|
|
183
211
|
async clickProject(id, projectPath) {
|
|
184
212
|
const displayName = projectPath ? projectDisplayName(projectPath) : "";
|
package/dist/agents/zcode/dom.js
CHANGED
|
@@ -34,15 +34,25 @@ const trigger = triggerMatch.node;`;
|
|
|
34
34
|
}
|
|
35
35
|
export function workspaceBindingExpression(overrides) {
|
|
36
36
|
return `(function(){${projectTriggerDom(overrides)}
|
|
37
|
-
if (!trigger) return {triggerText:'',projectPath:'',ambiguous:triggerMatch.count>1};
|
|
37
|
+
if (!trigger) return {triggerText:'',projectPath:'',projectName:'',menuChecked:[],ambiguous:triggerMatch.count>1};
|
|
38
38
|
const triggerText = labelOf(trigger);
|
|
39
39
|
// A path anywhere in the sidebar is not evidence of the composer's binding.
|
|
40
40
|
const explicit = pathOf(trigger) || pathOf(trigger.querySelector('[data-project-path]'));
|
|
41
41
|
const rows = [...document.querySelectorAll('[data-testid^="workspace-item-"]')];
|
|
42
42
|
const matches = rows.filter(e => norm(labelOf(e)) === norm(triggerText));
|
|
43
43
|
const paths = [...new Set(matches.map(pathOf).filter(Boolean))];
|
|
44
|
+
// issue #24:ZCode 3.14.x 删除了 data-project-path 与 workspace-item-*,路径渠道归零。
|
|
45
|
+
// 此时当前绑定只剩「显示名」这一证据:展开菜单里 aria-checked=true 的项最权威
|
|
46
|
+
// (它是 ZCode 自己渲染的绑定状态),菜单未开时退回触发器文本。
|
|
47
|
+
// 绝不把显示名写进 projectPath——路径字段保持为空,判定层据此区分证据等级。
|
|
48
|
+
const checked = [...document.querySelectorAll('[role="menuitemcheckbox"][aria-checked="true"]')]
|
|
49
|
+
.filter(visible).map(labelOf).filter(Boolean);
|
|
50
|
+
const menuChecked = [...new Set(checked)];
|
|
51
|
+
const projectName = menuChecked.length===1 ? menuChecked[0]
|
|
52
|
+
: (menuChecked.length===0 ? triggerText : '');
|
|
44
53
|
return {triggerText, projectPath:explicit || (paths.length===1?paths[0]:''),
|
|
45
|
-
|
|
54
|
+
projectName, menuChecked,
|
|
55
|
+
ambiguous:(!explicit && paths.length>1) || menuChecked.length>1};
|
|
46
56
|
})()`;
|
|
47
57
|
}
|
|
48
58
|
export function modelSelectionExpression(overrides) {
|
|
@@ -43,16 +43,67 @@ export function matchZcodeProject(items, target, platform = process.platform) {
|
|
|
43
43
|
const exact = items.filter((item) => item.path &&
|
|
44
44
|
normalizeProjectPath(item.path, platform) === normalizeProjectPath(target, platform));
|
|
45
45
|
if (exact.length === 1)
|
|
46
|
-
return { item: exact[0], ambiguous: false };
|
|
46
|
+
return { item: exact[0], ambiguous: false, matchedBy: "path" };
|
|
47
47
|
if (exact.length > 1)
|
|
48
48
|
return { ambiguous: true };
|
|
49
49
|
const api = platform === "win32" ? path.win32 : path.posix;
|
|
50
50
|
const base = api.basename(target);
|
|
51
51
|
const names = items.filter((item) => exactName(item.name, base, platform));
|
|
52
|
-
if (names.length >
|
|
52
|
+
if (names.length > 1)
|
|
53
53
|
return { ambiguous: true };
|
|
54
|
+
if (names.length === 1) {
|
|
55
|
+
const only = names[0];
|
|
56
|
+
// 同名项带路径且与目标不符 → 列表里那个不是目标,保持 fail-closed(原语义)。
|
|
57
|
+
if (only.path &&
|
|
58
|
+
normalizeProjectPath(only.path, platform) !== normalizeProjectPath(target, platform))
|
|
59
|
+
return { ambiguous: true };
|
|
60
|
+
// 无路径渠道(ZCode 3.14.x):显示名是唯一可用证据,交给上层按显示名判定绑定。
|
|
61
|
+
return { item: only, ambiguous: false, matchedBy: "name" };
|
|
62
|
+
}
|
|
54
63
|
return { ambiguous: false };
|
|
55
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* 判定「当前 ZCode 工作区是否绑定到 target」(issue #24)。
|
|
67
|
+
*
|
|
68
|
+
* 判据分层,证据等级从强到弱:
|
|
69
|
+
* 1. 路径回读(`binding.projectPath`)——老版本(与未来若恢复路径渠道)的严格判等,语义不变。
|
|
70
|
+
* 2. 显示名(`binding.projectName`)——3.14.x 的唯一渠道;仅在名称一致时成立,
|
|
71
|
+
* 并要求项目列表未出现同名歧义。
|
|
72
|
+
* 绝不把显示名当路径:路径不一致时一定判不绑定,宁可多走一轮点击也不误投。
|
|
73
|
+
*/
|
|
74
|
+
export function boundProjectVerdict(binding, match, target, platform = process.platform) {
|
|
75
|
+
if (binding.ambiguous)
|
|
76
|
+
return { bound: false, ambiguous: true, evidence: "工作区绑定回读本身已歧义" };
|
|
77
|
+
if (binding.projectPath) {
|
|
78
|
+
const ok = normalizeProjectPath(binding.projectPath, platform) === normalizeProjectPath(target, platform);
|
|
79
|
+
return { bound: ok, ambiguous: false, evidence: `路径回读=${binding.projectPath}` };
|
|
80
|
+
}
|
|
81
|
+
const expect = projectDisplayName(target, platform);
|
|
82
|
+
const boundName = (binding.projectName || "").trim();
|
|
83
|
+
if (!boundName)
|
|
84
|
+
return { bound: false, ambiguous: false, evidence: "未能回读当前绑定的显示名" };
|
|
85
|
+
if (!sameDisplayName(boundName, expect))
|
|
86
|
+
return {
|
|
87
|
+
bound: false,
|
|
88
|
+
ambiguous: false,
|
|
89
|
+
evidence: `当前绑定显示名=${boundName},目标=${expect}`,
|
|
90
|
+
};
|
|
91
|
+
// 名称一致:只有列表确实可得且表明同名歧义时才拒绝(列表不可得时不能据此否定)。
|
|
92
|
+
if (match.ambiguous)
|
|
93
|
+
return { bound: false, ambiguous: true, evidence: "项目列表存在同名项,无法消歧" };
|
|
94
|
+
if (match.item?.path &&
|
|
95
|
+
normalizeProjectPath(match.item.path, platform) !== normalizeProjectPath(target, platform))
|
|
96
|
+
return {
|
|
97
|
+
bound: false,
|
|
98
|
+
ambiguous: false,
|
|
99
|
+
evidence: `同名项目路径不符:${match.item.path}`,
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
bound: true,
|
|
103
|
+
ambiguous: false,
|
|
104
|
+
evidence: `显示名一致=${boundName}${match.matchedBy === "name" ? "(无路径渠道,按 3.14.x 契约以显示名判定)" : ""}`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
56
107
|
function exactName(a, b, platform) {
|
|
57
108
|
return platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
58
109
|
}
|
package/dist/agents/zcode/run.js
CHANGED
|
@@ -5,7 +5,7 @@ import { ZCODE_SETUP_DEFAULTS } from "../../config/schema.js";
|
|
|
5
5
|
import { mkdirp } from "../../util/fs.js";
|
|
6
6
|
import { parseZcodeModel, exactUiName, ZcodeModelReadbackError } from "./model.js";
|
|
7
7
|
import { validateTaskReferences } from "./references.js";
|
|
8
|
-
import {
|
|
8
|
+
import { boundProjectVerdict, matchZcodeProject, isUnboundTriggerText, } from "./project.js";
|
|
9
9
|
import { judgeZcodePoll } from "./liveness.js";
|
|
10
10
|
import { ZcodeCdpClient, CdpDisconnectedError, CdpUnavailableError, } from "./cdp.js";
|
|
11
11
|
import { ensureZcodeInstance, listZcodeProcessesAsync, } from "./instance.js";
|
|
@@ -80,9 +80,10 @@ function prompt(ctx, refs, initialDispatch) {
|
|
|
80
80
|
async function waitBound(cdp, target, deps) {
|
|
81
81
|
for (let i = 0; i < 30; i++) {
|
|
82
82
|
const binding = await cdp.workspaceBinding();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
// 与项目分支共用同一套分层判据(issue #24):路径可得时严格判等,3.14.x 无路径渠道时按显示名。
|
|
84
|
+
// 点击菜单项后菜单会收起、列表为空,所以这里不传列表——不能因列表缺失而否定已确认的绑定。
|
|
85
|
+
const verdict = boundProjectVerdict(binding, { ambiguous: false }, target);
|
|
86
|
+
if (verdict.bound)
|
|
86
87
|
return true;
|
|
87
88
|
await deps.sleep(300);
|
|
88
89
|
}
|
|
@@ -404,37 +405,68 @@ export async function runZcodeTask(args) {
|
|
|
404
405
|
await deps.sleep(Math.min(300, Math.max(0, deadline - Date.now())));
|
|
405
406
|
}
|
|
406
407
|
};
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
408
|
+
/**
|
|
409
|
+
* 绑定失败时的证据摘要:触发器文本、菜单勾选态与路径回读。
|
|
410
|
+
* issue #24 要求 fail-closed 时把这几项一并写进错误信息,用户在 ZCode 里能据此判断
|
|
411
|
+
* 到底绑到了哪个项目(而不是只看到一句「回读不一致」无从自救)。
|
|
412
|
+
*/
|
|
413
|
+
const describeBinding = async () => {
|
|
414
|
+
try {
|
|
415
|
+
const b = await cdp.workspaceBinding();
|
|
416
|
+
return `触发器文本=${b.triggerText || "空"};菜单勾选=${b.menuChecked?.length ? b.menuChecked.join("、") : "无"};路径回读=${b.projectPath || "无"}`;
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
return "绑定状态回读失败";
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* 读一次「当前绑定是否为目标项目」的分层判定(issue #24):
|
|
424
|
+
* 路径可得时严格判等;无路径渠道(ZCode 3.14.x)时按显示名,并在列表可得时做同名消歧。
|
|
425
|
+
*/
|
|
426
|
+
const readBoundVerdict = async () => {
|
|
427
|
+
const binding = await cdp.workspaceBinding();
|
|
428
|
+
const match = matchZcodeProject(await cdp.projects(), ctx.projectPath);
|
|
429
|
+
return { verdict: boundProjectVerdict(binding, match, ctx.projectPath), match };
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* 幂等确认绑定:每轮先读判定,已绑定即通过;未绑定则(必要时先展开菜单)点击目标项目再复检。
|
|
433
|
+
* 不再接受外部传入的 item——那会把「菜单是否恰好还开着」变成隐式前提(issue #24 暴露的脆弱时序)。
|
|
434
|
+
*/
|
|
435
|
+
const ensureProjectBound = async () => {
|
|
415
436
|
for (let round = 0; round <= gui.setupRecoveryMaxRetries; round++) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
437
|
+
// eslint-disable-next-line no-await-in-loop
|
|
438
|
+
const { verdict, match } = await readBoundVerdict();
|
|
439
|
+
if (verdict.bound)
|
|
440
|
+
return { bound: true, ambiguous: false };
|
|
441
|
+
if (verdict.ambiguous)
|
|
442
|
+
return { bound: false, ambiguous: true };
|
|
443
|
+
if (match.item) {
|
|
422
444
|
// eslint-disable-next-line no-await-in-loop
|
|
423
|
-
await
|
|
445
|
+
const clicked = await cdp.clickProject(match.item.id, match.item.path ?? ctx.projectPath);
|
|
424
446
|
// eslint-disable-next-line no-await-in-loop
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
item = matched.item;
|
|
447
|
+
if (clicked && (await waitBound(cdp, ctx.projectPath, deps)))
|
|
448
|
+
return { bound: true, ambiguous: false };
|
|
449
|
+
continue;
|
|
429
450
|
}
|
|
430
|
-
|
|
451
|
+
// 目标不在当前列表(菜单多未展开):显式展开菜单后再采集一次。
|
|
452
|
+
// eslint-disable-next-line no-await-in-loop
|
|
453
|
+
await cdp.dismissMenus();
|
|
454
|
+
// eslint-disable-next-line no-await-in-loop
|
|
455
|
+
if (!(await cdp.clickProjectTriggerAndConfirm(projectTriggerDeadline())).opened)
|
|
456
|
+
continue;
|
|
457
|
+
// eslint-disable-next-line no-await-in-loop
|
|
458
|
+
await deps.sleep(300);
|
|
459
|
+
// eslint-disable-next-line no-await-in-loop
|
|
460
|
+
const expanded = matchZcodeProject(await cdp.projects(), ctx.projectPath);
|
|
461
|
+
if (expanded.ambiguous)
|
|
462
|
+
return { bound: false, ambiguous: true };
|
|
463
|
+
if (!expanded.item)
|
|
431
464
|
continue;
|
|
432
465
|
// eslint-disable-next-line no-await-in-loop
|
|
433
|
-
const clicked = await cdp.clickProject(item.id, item.path);
|
|
466
|
+
const clicked = await cdp.clickProject(expanded.item.id, expanded.item.path ?? ctx.projectPath);
|
|
434
467
|
// eslint-disable-next-line no-await-in-loop
|
|
435
468
|
if (clicked && (await waitBound(cdp, ctx.projectPath, deps)))
|
|
436
469
|
return { bound: true, ambiguous: false };
|
|
437
|
-
item = undefined;
|
|
438
470
|
}
|
|
439
471
|
return { bound: false, ambiguous: false };
|
|
440
472
|
};
|
|
@@ -555,13 +587,13 @@ export async function runZcodeTask(args) {
|
|
|
555
587
|
pendingQuestion: "项目路径存在歧义,请在 ZCode 中确认目标项目后调用 continue_task。",
|
|
556
588
|
});
|
|
557
589
|
if (matched.item) {
|
|
558
|
-
const binding = await ensureProjectBound(
|
|
590
|
+
const binding = await ensureProjectBound();
|
|
559
591
|
if (!binding.bound)
|
|
560
592
|
return result({
|
|
561
593
|
hardFailure: true,
|
|
562
594
|
error: binding.ambiguous
|
|
563
|
-
?
|
|
564
|
-
:
|
|
595
|
+
? `项目绑定重试时目标项目无法唯一匹配:${ctx.projectPath};${await describeBinding()}`
|
|
596
|
+
: `ZCode 项目绑定有限重试均未生效:${ctx.projectPath};${await describeBinding()}`,
|
|
565
597
|
endReason: "project_mismatch",
|
|
566
598
|
needsUserKind: "setup_recovery",
|
|
567
599
|
pendingQuestion: "项目绑定尚未确认,请在 ZCode 中确认目标项目后调用 continue_task。",
|
|
@@ -693,8 +725,8 @@ export async function runZcodeTask(args) {
|
|
|
693
725
|
return result({
|
|
694
726
|
hardFailure: true,
|
|
695
727
|
error: finalBinding.ambiguous
|
|
696
|
-
?
|
|
697
|
-
:
|
|
728
|
+
? `ZCode 项目绑定回读不一致,且重试时目标项目无法唯一匹配:${ctx.projectPath};${await describeBinding()}`
|
|
729
|
+
: `ZCode 项目绑定回读与 projectPath 不一致,有限幂等重试均失败:${ctx.projectPath};${await describeBinding()}`,
|
|
698
730
|
endReason: "project_mismatch",
|
|
699
731
|
needsUserKind: "setup_recovery",
|
|
700
732
|
pendingQuestion: "项目绑定尚未确认,请在 ZCode 中确认目标项目后调用 continue_task。",
|
|
@@ -75,7 +75,13 @@ export const ZCODE_SELECTORS = {
|
|
|
75
75
|
primary: '[data-testid^="workspace-item-"]',
|
|
76
76
|
fallbacks: ['[class*="project-item"]', '[class*="workspace-item"]'],
|
|
77
77
|
verifiedVersion: "3.11.x",
|
|
78
|
-
note: "
|
|
78
|
+
note: "3.11.x 的项目列表项。ZCode 3.14.x 已删除该契约(app.asar 扫描 0 命中),改用 projectMenuItem。",
|
|
79
|
+
},
|
|
80
|
+
projectMenuItem: {
|
|
81
|
+
primary: '[role="menuitemcheckbox"]',
|
|
82
|
+
fallbacks: [],
|
|
83
|
+
verifiedVersion: "3.14.x",
|
|
84
|
+
note: "工作区下拉中的项目项:textContent 即项目显示名,aria-checked 表示是否为当前绑定。3.14.x 下 workspace-item-* 与 data-project-path 均已不存在,这是唯一可用的绑定证据来源;仅在项目菜单展开时有效。",
|
|
79
85
|
},
|
|
80
86
|
projectPath: {
|
|
81
87
|
primary: "[data-project-path]",
|
package/package.json
CHANGED
package/scripts/probe-zcode.mjs
CHANGED
|
@@ -169,6 +169,77 @@ async function uiState() {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* issue #24 真机取证:ZCode 3.14.x 是否真的删除了项目绑定的两处 DOM 契约。
|
|
174
|
+
*
|
|
175
|
+
* 输出各契约的命中计数、触发器全属性、展开菜单后的项目项清单,以及修复后的适配器回读结果。
|
|
176
|
+
* **会展开一次工作区菜单**(只读采集,不选中任何项)。
|
|
177
|
+
*/
|
|
178
|
+
async function domContracts() {
|
|
179
|
+
const endpoints = await cdpTargets();
|
|
180
|
+
const endpoint = endpoints.find((item) =>
|
|
181
|
+
item.targets?.some(
|
|
182
|
+
(target) =>
|
|
183
|
+
target.type === "page" && /z[ -]?code/i.test(`${target.title ?? ""} ${target.url ?? ""}`),
|
|
184
|
+
),
|
|
185
|
+
);
|
|
186
|
+
if (!endpoint) return { ok: false, message: "没有通过产品标识核验的 ZCode CDP 页面" };
|
|
187
|
+
try {
|
|
188
|
+
const { ZcodeCdpClient } = await import("../dist/agents/zcode/cdp.js");
|
|
189
|
+
const client = new ZcodeCdpClient(endpoint.port, 10_000, {});
|
|
190
|
+
await client.connect();
|
|
191
|
+
try {
|
|
192
|
+
const counts = await client.evaluate(
|
|
193
|
+
`(function(){return {
|
|
194
|
+
dataProjectPath: document.querySelectorAll('[data-project-path]').length,
|
|
195
|
+
workspaceItems: document.querySelectorAll('[data-testid^="workspace-item-"]').length,
|
|
196
|
+
dataProjectName: document.querySelectorAll('[data-project-name]').length,
|
|
197
|
+
triggers: document.querySelectorAll('[data-testid="composer-workspace-trigger"]').length,
|
|
198
|
+
menuItemCheckbox: document.querySelectorAll('[role="menuitemcheckbox"]').length
|
|
199
|
+
}})()`,
|
|
200
|
+
);
|
|
201
|
+
const trigger = await client.evaluate(
|
|
202
|
+
`(function(){const e=document.querySelector('[data-testid="composer-workspace-trigger"]');if(!e)return null;const attrs={};for(const a of e.attributes)attrs[a.name]=a.value;return {text:(e.textContent||'').trim().slice(0,200),attrs}})()`,
|
|
203
|
+
);
|
|
204
|
+
// 项目身份只在菜单展开时可见:先收起残留菜单,再展开一次做只读采集。
|
|
205
|
+
await client.dismissMenus();
|
|
206
|
+
const opened = await client.clickProjectTriggerAndConfirm(Date.now() + 5_000);
|
|
207
|
+
const menuItems = opened.opened
|
|
208
|
+
? await client.evaluate(
|
|
209
|
+
`(function(){return [...document.querySelectorAll('[role="menuitemcheckbox"]')].map(function(e){return {text:(e.textContent||'').trim().slice(0,200),checked:e.getAttribute('aria-checked'),testid:e.getAttribute('data-testid')||undefined};})})()`,
|
|
210
|
+
)
|
|
211
|
+
: [];
|
|
212
|
+
const binding = await client.workspaceBinding();
|
|
213
|
+
const projects = await client.projects();
|
|
214
|
+
await client.dismissMenus();
|
|
215
|
+
return {
|
|
216
|
+
ok: true,
|
|
217
|
+
port: endpoint.port,
|
|
218
|
+
counts,
|
|
219
|
+
trigger,
|
|
220
|
+
menuOpened: opened.opened,
|
|
221
|
+
menuItems,
|
|
222
|
+
binding,
|
|
223
|
+
projects,
|
|
224
|
+
verdict: {
|
|
225
|
+
// 两处旧契约都缺席 ⇒ 路径渠道不可用,适配器必须靠显示名完成绑定。
|
|
226
|
+
pathChannelAvailable: counts.dataProjectPath > 0 || counts.workspaceItems > 0,
|
|
227
|
+
nameChannelAvailable: binding.projectPath === "" && !!binding.projectName,
|
|
228
|
+
ambiguous: binding.ambiguous === true,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
} finally {
|
|
232
|
+
client.disconnect();
|
|
233
|
+
}
|
|
234
|
+
} catch (e) {
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
port: endpoint.port,
|
|
238
|
+
message: `绑定契约诊断失败:${e.message}。请先执行 npm run build。`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
172
243
|
async function menuControls(kind) {
|
|
173
244
|
const endpoints = await cdpTargets();
|
|
174
245
|
const endpoint = endpoints.find((item) =>
|
|
@@ -273,6 +344,7 @@ else if (command === "process") json(processInfo());
|
|
|
273
344
|
else if (command === "cdp") json(await cdpTargets());
|
|
274
345
|
else if (command === "models") json(await modelState());
|
|
275
346
|
else if (command === "permission") json(await permissionState());
|
|
347
|
+
else if (command === "dom-contracts") json(await domContracts());
|
|
276
348
|
else if (command === "selectors")
|
|
277
349
|
json({
|
|
278
350
|
source: "src/agents/zcode/selectors.ts",
|
|
@@ -264,6 +264,11 @@ meta 的 `needsUserKind` 给出等待类型,`pendingQuestion` 给出问题原
|
|
|
264
264
|
|
|
265
265
|
`agentEndReason` 与终态映射(先记住这张表,再看错误码表):
|
|
266
266
|
|
|
267
|
+
> **下面的错误码表是「常见子集」而非闭集**:`endReason` 在类型层面只是一个可选 `string`
|
|
268
|
+
> (`src/agents/adapter.ts:93`),没有枚举约束,某个适配器新增分支时无需改类型就能引入新取值。
|
|
269
|
+
> 因此看到表里没有的取值时,**按名字猜语义不如去读该适配器的 `run.ts`**,或看 `query_task` 的
|
|
270
|
+
> 日志尾部与 `progressSummary`。
|
|
271
|
+
|
|
267
272
|
| `agentEndReason` | 任务终态 |
|
|
268
273
|
|---|---|
|
|
269
274
|
| `task_timeout` | `needs_attention` + `errorType=timeout`(GUI:codex/zcode);qoder 落 `failed(timeout)` |
|
|
@@ -273,6 +278,9 @@ meta 的 `needsUserKind` 给出等待类型,`pendingQuestion` 给出问题原
|
|
|
273
278
|
| `agentEndReason` | 含义 | 处置 |
|
|
274
279
|
|---|---|---|
|
|
275
280
|
| `setup_failed` | 找不到安装 / 实例未就绪 / 点不到「新对话」 | 让用户确认已安装且能手动打开;重试一次 |
|
|
281
|
+
| `input_mismatch` | 任务输入框回读与本次标记不符(真机常见成因:输入框残留模板/草稿,任务被插进残文里) | 适配器已先清空再输入;若仍失败,让用户手动清空输入框后重派 |
|
|
282
|
+
| `send_unknown` | 点了发送但**无法确认消息落地** | 适配器**绝不重发**(避免重复派活);请用户在界面确认是否已发出,再决定重派或 `continue_task` |
|
|
283
|
+
| `reply_stable` | **正常完成**:对话与产物均已静止 | 不是错误;去读验收报告 |
|
|
276
284
|
| `project_ambiguous` | 项目同名或路径重复,无法消歧 | 已转 `needs_user(setup_recovery)`:请用户确认目标项目后 `continue_task` |
|
|
277
285
|
| `project_mismatch` | 项目绑定或回读不一致,幂等重试仍失败 | 同上:请用户在 GUI 里确认/手工绑定 |
|
|
278
286
|
| `project_create_failed` | 在 GUI 内新建项目失败 | 让用户手动把项目加进 agent,或换 `projectPath` |
|
|
@@ -193,7 +193,16 @@ run_task(projectPath=D:/repo/design, agentId=opendesign,
|
|
|
193
193
|
- `designSystem` 传**设计系统名**(如 `Claude`、`Neutral Modern`),不是目录路径——与 codex 的 `designSystem` 语义不同(那边是目录)。
|
|
194
194
|
- `mode` 不支持(那是 traework 的面板模式);`model` 必填,按名字**精确匹配**菜单项,未命中会报错并**回显当前可见候选**,不会退化成模糊匹配。
|
|
195
195
|
- 目录绑定按「展开工作目录 → 选择目录 → 原生『选择文件夹』填绝对路径 → **回读显示值校验**」;已是目标目录则跳过。
|
|
196
|
-
|
|
196
|
+
(绑目录前适配器会先等 **daemon sidecar 就绪** —— 产品打开文件夹选择器前要与 daemon 完成鉴权握手,
|
|
197
|
+
实测启动后约 30s 才驻留;未就绪时产品只会显示自己的提示、**根本不弹对话框**。)
|
|
198
|
+
- **产物会自动取回任务目录**(`v0.7.1` 起):Open Design 的设计稿存在它自己的产物存储
|
|
199
|
+
`<dataRoot>/projects/<projectId>/<entry>`,**不在任务目录里**。终态后适配器把它复制到
|
|
200
|
+
`projectPath`,视觉验收随后可推导静态入口(`onboarding-guide.html` → 路由 `/onboarding-guide.html`)。
|
|
201
|
+
取回是**增值步骤**:失败只写进 `progressSummary`,**不改变任务终态**。
|
|
202
|
+
- **`zip` 导出方式尚未打通**(已知限制):产品的下载由主进程接管,CDP 的下载接管被覆盖;
|
|
203
|
+
`html` 路径完全可用,zip 待后续优化。
|
|
204
|
+
- 视觉验收要求项目里有可截图的页面来源;适配器只**推导建议**(静态入口优先,白名单未命中时
|
|
205
|
+
若项目根下 html **恰好唯一**也认它),**不会自动修改** `.tianshu-mcp/acceptance.json`。
|
|
197
206
|
- 修复/优化计划落项目根 `.opendesign/plans/`(Open Design 只能读它工作目录白名单内的文件)。
|
|
198
207
|
- 可正常派活(Windows 真机取证;macOS 为 `research` 且禁止派发)。仍 **fail-closed**:选择器漂移时硬失败 `selector_drift` 并列出缺失键,模型未命中报 `model_unavailable` 并回显可见候选,设计方向非法在**入口**即拒绝。
|
|
199
208
|
详见 [docs/opendesign-cdp.md](../../docs/opendesign-cdp.md) 与 `.dsh/plans/opendesign-gui-adapter-plan.md`。
|
|
@@ -212,7 +221,7 @@ run_task(projectPath=/path/to/项目, agentId=codex-cli,
|
|
|
212
221
|
- 写入被 `workspace-write` 沙箱限制在项目目录内;POSIX 下取消/超时对进程组 `SIGTERM`→`SIGKILL`。
|
|
213
222
|
- **无头路径没有 GUI 交互**:不存在 `user_confirmation` 这类等待,`continue_task` 不适用;失败直接看 `agentEndReason` 与日志。
|
|
214
223
|
|
|
215
|
-
### 2.
|
|
224
|
+
### 2.10 通用约定
|
|
216
225
|
|
|
217
226
|
- `run_task` 是**异步契约**:立即返回 `taskId` + 队列位置,不要当同步调用等结果。
|
|
218
227
|
- 轮询间隔 5–10 秒(`query_task` 缺省返回 agent 日志末 40 行);同项目串行 + 全局并发默认 2,重复派单只会排队。
|