tianshu-mcp 0.6.1 → 0.6.3
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 +43 -0
- package/CHANGELOG.md +43 -0
- package/README.en.md +8 -1
- package/README.md +8 -1
- package/dist/agents/agent-events.js +50 -0
- package/dist/agents/builtin.js +10 -9
- package/dist/agents/codex/cdp.js +18 -2
- package/dist/agents/codex/run.js +58 -10
- package/dist/agents/codex/selectors.js +28 -16
- package/dist/agents/gui-diagnostics.js +81 -0
- package/dist/agents/qoder/cdp.js +41 -5
- package/dist/agents/qoder/selectors.js +217 -28
- package/dist/agents/qoder/workspace.js +13 -2
- package/dist/agents/registry.js +20 -0
- package/dist/agents/traework/cdp/client.js +13 -0
- package/dist/agents/traework/cdp/selectors.js +24 -24
- package/dist/agents/traework/discovery.js +194 -0
- package/dist/agents/traework/launcher.js +57 -1
- package/dist/agents/traework/run.js +41 -4
- package/dist/config/schema.js +8 -0
- package/dist/loop/fix-loop.js +12 -0
- package/dist/mcp/handlers.js +10 -1
- package/dist/mcp/tools.js +5 -1
- package/dist/tasks/task-manager.js +5 -6
- package/dist/tasks/task-store.js +28 -1
- package/dist/util/fs.js +45 -0
- package/dist/version.generated.js +1 -1
- package/docs/event-stream.en.md +96 -0
- package/docs/event-stream.md +85 -0
- package/docs/qoder-cdp.en.md +4 -0
- package/docs/qoder-cdp.md +4 -0
- package/package.json +3 -1
- package/scripts/probe-codex.mjs +52 -2
- package/scripts/probe-traework.mjs +1 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -8,6 +8,49 @@ Chinese version: [CHANGELOG.md](CHANGELOG.md)
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## [0.6.3] - 2026-09-24
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Fine-grained event stream** ([issue #18](https://github.com/lanlan0811/tianshu-mcp/issues/18)): adapters can proactively report semantic events at key nodes, and `query_task` returns the most recent N of them, so long tasks can be told apart as "working normally" versus "stuck on a dialog waiting for a human". Five event kinds: `task_dispatched` / `confirmation_dialog_detected` / `awaiting_user_authorization` / `file_modification_started` / `rework_triggered`. See the [event stream doc](docs/event-stream.en.md).
|
|
16
|
+
- **New optional `query_task` input `eventLimit`** (integer 1..50, **default 10**): events appear both in the meta block's `recentEvents` array and in a "recent events" section of the text area.
|
|
17
|
+
- **Two built-in GUI adapters actually report events**: codex and traework (four emission points each); `rework_triggered` is emitted engine-side (automatic rework `mode:"auto"`, manual `rework_task` `mode:"manual"`).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Manual `rework_task` now emits a typed `rework_triggered` event instead of an anonymous `note`** (visible in `task.jsonl`). The semantics and purpose of the existing `note` event are unchanged; `progressSummary` / `lastRunSignal` are still carried by `note`.
|
|
22
|
+
- **The `TaskEventName` union gained five members**, sourced from `AGENT_EVENT_NAMES` so the vocabulary cannot drift between two places.
|
|
23
|
+
|
|
24
|
+
### Compatibility
|
|
25
|
+
|
|
26
|
+
- **No tool contract, data model or MCP annotation changes.** `recentEvents` is a new optional field: adapters that do not implement event reporting (including all CLI adapters) return an empty array with no event section in the text area, and **every other field is exactly as in v0.6.2**.
|
|
27
|
+
- Events are written into the existing `task.jsonl` (**no parallel event file is created**); the read side only reads a 64 KiB tail window, so memory use is decoupled from total file size.
|
|
28
|
+
|
|
29
|
+
### Notes (disclosed honestly)
|
|
30
|
+
|
|
31
|
+
- **`file_modification_started` is a heuristic.** The codex / traework adapters do not observe the filesystem directly; they can only infer that execution started from the UI's "running" signal (stop button). Its detail always reads "stop button appeared, execution started (files may be modified)" and **does not claim files were actually changed**. For hard evidence of file changes, read `changedFiles` / `diffstat` from the acceptance report.
|
|
32
|
+
- **Event reporting is an optional capability.** The hook lives on `AgentRunOptions.onEvent`, not the agent profile (`agent-profiles.json` is plain JSON and cannot hold a function); adapters that don't implement it need not change a single byte. Adapters report through `makeEmitter` — a no-op when no hook is provided, swallowing reporting exceptions so that **a failed report never affects the task itself**.
|
|
33
|
+
- **Events are not delivery-guaranteed.** This is an observability capability, not a delivery guarantee; `query_task` reflects only "the last event that was persisted".
|
|
34
|
+
|
|
35
|
+
## [0.6.2] - 2026-09-23
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **Codex project-picker trigger copy drifts across versions** ([issue #23](https://github.com/lanlan0811/tianshu-mcp/issues/23), case 1): the trigger's `aria-label` is "选择项目:<name>" on some versions and "切换项目:<name>" on others (26.915, measured locally). `projectPickerTrigger` now covers both copies via primary/fallback/pattern (including the English `Select/Switch project`), and `boundProjectName` reads both back. **A full 20-key hardware audit was run** (`node scripts/probe-codex.mjs --launch audit`): on local 26.915 there is no drift beyond this trigger, and 9 verified keys were bumped to `verifiedVersion: 26.915.x`.
|
|
40
|
+
- **Qoder workspace binding failed on 0.3.4** (issue #23, case 2): a fresh hardware probe **corrected the issue's conclusion** — the 0.3.4 workspace menu **does render**; the real cause is that the page has **two** `[data-workspace-picker-trigger]` elements, and the old single-match `click()` failed as ambiguous. The workspace trigger's primary selector is now the unique `button[aria-label^="切换或清空当前工作区"]` (with `[data-workspace-picker-trigger]` demoted to a fallback); the "menu is open" check is relaxed to "search box **or** overlay". The production `bindWorkspace` now passes on real Qoder 0.3.4.
|
|
41
|
+
- **TraeWork install discovery failed** (issue #23, case 3): added `src/agents/traework/discovery.ts` (fixed-drive enumeration + registry `InstallLocation` + relative paths) with a dedicated `traework-gui` branch in `registry.ts`. Fixed the built-in profile — removed the wrong `{APPDATA}/TRAE SOLO CN` (measured to be the **user-data dir**: Cache/Crashpad/nested tool exes, not the install location) in favor of `{LOCALAPPDATA}/Programs/TRAE SOLO CN` etc., and added `preferredDrives`/`relativePaths`. **On Windows the executable name is narrowed to `TRAE SOLO CN.exe`** (the old list's `Trae CN` mismatched the unrelated TraeCode CN product). `waitReady` timeouts now emit on-site diagnostics (child exit code, port-listener enumeration, detection of existing instances without the debug port) — **diagnosis only: no launch-strategy change, no termination of existing instances**.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **Unified selector diagnostics across the three GUI agents** (`src/agents/gui-diagnostics.ts`): on selector-resolution failure the nearest visible page candidates (aria-labels / short texts) are appended to the error and log, so drift can be located in one step without opening CDP by hand. Wired into codex / qoder / traework.
|
|
46
|
+
- **Codex full-key audit mode** `scripts/probe-codex.mjs --launch audit`: prints the primary hit count and matched labels for all 20 selector keys as a "script + evidence table" gate.
|
|
47
|
+
- **Qoder layered selector structure**: `src/agents/qoder/selectors.ts` upgraded from flat strings to `primary/fallbacks/texts/ariaLabels/ariaPatterns/verifiedVersion` (27 keys); `QoderCdpClient.selector()` keeps its string semantics and adds `candidates()/existsKey()/clickKey()`.
|
|
48
|
+
- **TraeWork selector version field unified**: `verified: boolean` → `verifiedVersion: string` (matching codex/kimicode).
|
|
49
|
+
|
|
50
|
+
### Docs
|
|
51
|
+
|
|
52
|
+
- New [issue #23 verification record](docs/issue-23-selector-drift-record.md) (20-key audit table, Qoder 0.3.4 re-probe, TraeWork discovery hardware results); release notes [v0.6.2](docs/release-v0.6.2.en.md).
|
|
53
|
+
|
|
11
54
|
## [0.6.1] - 2026-09-23
|
|
12
55
|
|
|
13
56
|
### Added
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,49 @@
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.6.3] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### 新增
|
|
13
|
+
|
|
14
|
+
- **细粒度事件流**([issue #18](https://github.com/lanlan0811/tianshu-mcp/issues/18)):适配器可在关键节点主动上报语义事件,`query_task` 回传最近 N 条,长任务下可区分「正常执行」与「卡在弹窗等人」。词表 5 类:`task_dispatched` / `confirmation_dialog_detected` / `awaiting_user_authorization` / `file_modification_started` / `rework_triggered`。详见 [事件流文档](docs/event-stream.md)。
|
|
15
|
+
- **`query_task` 新增可选入参 `eventLimit`**(整数 1..50,**缺省 10**):事件同时出现在 meta 块的 `recentEvents` 数组与文本区的「最近事件」段落。
|
|
16
|
+
- **两个内置 GUI 适配器落地上报**:codex 与 traework(各 4 个发射点);`rework_triggered` 由引擎侧统一上报(自动返修 `mode:"auto"`、手动 `rework_task` `mode:"manual"`)。
|
|
17
|
+
|
|
18
|
+
### 变更
|
|
19
|
+
|
|
20
|
+
- **手动 `rework_task` 的事件由匿名 `note` 改为类型化 `rework_triggered`**(`task.jsonl` 可见)。既有 `note` 事件的语义与用途不变,`progressSummary` / `lastRunSignal` 仍由 `note` 承载。
|
|
21
|
+
- **`TaskEventName` 联合类型新增 5 个成员**,与 `AGENT_EVENT_NAMES` 同源(避免两处词表漂移)。
|
|
22
|
+
|
|
23
|
+
### 兼容性
|
|
24
|
+
|
|
25
|
+
- **无工具契约、数据模型或 MCP 注解变更**。`recentEvents` 是新增可选字段:未实现事件上报的适配器(含全部 CLI 适配器)返回空数组、文本区不出现事件段落,**其余字段与 v0.6.2 完全一致**。
|
|
26
|
+
- 事件写入既有的 `task.jsonl`(**不新建并行事件文件**);读取侧只读尾部 64 KiB 窗口,内存占用与文件总大小解耦。
|
|
27
|
+
|
|
28
|
+
### 说明(如实披露)
|
|
29
|
+
|
|
30
|
+
- **`file_modification_started` 是启发式推断**:codex / traework 适配器并不直接观测文件系统,只能从界面「运行中」信号(停止按钮)推断执行已开始。其 detail 一律写「停止按钮出现,开始执行(可能开始改动文件)」,**不声称文件确已改动**;确切的文件改动证据请看验收报告的 `changedFiles` / `diffstat`。
|
|
31
|
+
- **事件上报是可选能力**:钩子挂在 `AgentRunOptions.onEvent` 而非 agent profile(`agent-profiles.json` 是纯 JSON,装不下函数);未实现的适配器一个字节都不用改。适配器侧统一经 `makeEmitter` 上报 —— 未提供钩子时空操作,且吞掉上报异常,**上报失败绝不影响任务本体**。
|
|
32
|
+
- **事件不保证送达**:属观测能力而非交付保证;`query_task` 只反映「最后一次落盘的事件」。
|
|
33
|
+
|
|
34
|
+
## [0.6.2] - 2026-09-23
|
|
35
|
+
|
|
36
|
+
### 修复
|
|
37
|
+
|
|
38
|
+
- **Codex 项目选择触发器文案跨版本漂移**([issue #23](https://github.com/lanlan0811/tianshu-mcp/issues/23) 例 1):触发器 `aria-label` 在部分版本为「选择项目:<名>」、部分(本机实测 26.915)仍为「切换项目:<名>」。`projectPickerTrigger` 主/回退/模板并列覆盖两文案(含英文 `Select/Switch project`),`boundProjectName` 的项目名回读同步兼容。**并完成全量 20 键真机审计**(`node scripts/probe-codex.mjs --launch audit`):本机 26.915 除该触发器外无其他漂移,9 个已实测键的 `verifiedVersion` 提升为 `26.915.x`。
|
|
39
|
+
- **Qoder 工作区绑定在 0.3.4 失败**(issue #23 例 2):真机重探**更正了 issue 的结论**——0.3.4 工作区菜单**并非不渲染**,真因是页面存在**两个** `[data-workspace-picker-trigger]`,旧 `click()` 要求唯一命中而判歧义失败。工作区触发器主选择器改用唯一的 `button[aria-label^="切换或清空当前工作区"]`(`[data-workspace-picker-trigger]` 降为回退);「菜单已打开」判定放宽为「搜索框 **或** 浮层」。生产 `bindWorkspace` 已在真实 Qoder 0.3.4 上跑通。
|
|
40
|
+
- **TraeWork 安装发现失败**(issue #23 例 3):新增 `src/agents/traework/discovery.ts`(固定盘枚举 + 注册表 `InstallLocation` + 相对路径),`registry.ts` 增 `traework-gui` 专用分支。修正内置 profile——删除错误的 `{APPDATA}/TRAE SOLO CN`(实测该目录是**用户数据目录**:Cache/Crashpad/嵌套工具 exe,非安装位置),改为 `{LOCALAPPDATA}/Programs/TRAE SOLO CN` 等,并补 `preferredDrives`/`relativePaths`。**Windows 文件名收窄为只认 `TRAE SOLO CN.exe`**(旧清单含 `Trae CN`,会误匹配另一产品 TraeCode CN)。`waitReady` 超时新增现场诊断(子进程退出码、端口监听者枚举、既有未带调试端口实例检测),**只诊断、不改启动策略、不终止既有实例**。
|
|
41
|
+
|
|
42
|
+
### 新增
|
|
43
|
+
|
|
44
|
+
- **三 GUI agent 统一的选择器诊断**(`src/agents/gui-diagnostics.ts`):选择器解析失败时把页面可见候选标签(最接近的 aria-label / 短文本)写进错误与日志,使用者一步定位漂移,无需人工开 CDP。已接入 codex / qoder / traework。
|
|
45
|
+
- **Codex 全键审计模式** `scripts/probe-codex.mjs --launch audit`:对全部 20 个语义键输出 primary 命中数与命中标签,作为「脚本 + 证据表」门禁。
|
|
46
|
+
- **Qoder 选择器分层结构**:`src/agents/qoder/selectors.ts` 由扁平字符串升级为 `primary/fallbacks/texts/ariaLabels/ariaPatterns/verifiedVersion`(27 键);`QoderCdpClient.selector()` 保留字符串语义,另增 `candidates()/existsKey()/clickKey()`。
|
|
47
|
+
- **TraeWork 选择器版本字段统一**:`verified: boolean` → `verifiedVersion: string`(与 codex/kimicode 一致)。
|
|
48
|
+
|
|
49
|
+
### 文档
|
|
50
|
+
|
|
51
|
+
- 新增 [issue #23 验证记录](docs/issue-23-selector-drift-record.md)(含 20 键审计表、Qoder 0.3.4 重探、TraeWork discovery 真机结果);发布说明 [v0.6.2](docs/release-v0.6.2.md)。
|
|
52
|
+
|
|
10
53
|
## [0.6.1] - 2026-09-23
|
|
11
54
|
|
|
12
55
|
### 新增
|
package/README.en.md
CHANGED
|
@@ -39,6 +39,7 @@ Tianshu plays the role of the overall commander; this MCP server is the **schedu
|
|
|
39
39
|
|
|
40
40
|
- **11 MCP tools**: `run_task / continue_task / query_task / list_tasks / get_task_report / cancel_task / verify_task / rework_task / get_profiles`, plus `prepare_visual_baseline / approve_visual_baseline` for visual acceptance
|
|
41
41
|
- **Async contract**: `run_task` returns a `taskId` immediately; long-running work is polled via `query_task` (never blocks `tools/call`).
|
|
42
|
+
- **Long-task observability (issue #18)**: adapters report fine-grained events at key nodes (`task_dispatched` / `confirmation_dialog_detected` / `awaiting_user_authorization` / `file_modification_started` / `rework_triggered`), and `query_task` returns the most recent N via `eventLimit` (default 10) — **so you can tell "the agent is working" apart from "stuck on a dialog waiting for a human"**. Event reporting is an optional capability: adapters that don't implement it behave unchanged. codex and traework report in this version. See [event stream](docs/event-stream.en.md).
|
|
42
43
|
- **Objective acceptance**: automated command checks (typecheck/lint/test/build — skipped when absent, plus tech-stack derivation) + programmatic code analysis (changed-file list / diffstat / suspicious signals such as TODO, debugger, secret-like patterns), all relative to a **git baseline**; never auto-commits or stashes. The acceptance engine is **fail-closed**: a test check fails when its output reports zero executed tests even if the exit code is 0; git projects must produce changes relative to the pre-work baseline by default (pure analysis tasks can opt out with `"requireChanges": false` in `.tianshu-mcp/acceptance.json`).
|
|
43
44
|
- **Acceptance parallelism**: command checks run **bounded-parallel** by default (`verifyConcurrency`, default 2, range 1–4). When checks depend on an order (a later check reading build output, `--fix`, shared cache dirs), set it to `1` for fully serial behaviour; a project can override it in `.tianshu-mcp/acceptance.json`, and the server level lives in `config.json`. Report and log formats are unchanged (results are returned in declaration order).
|
|
44
45
|
- **Rework loop**: automatic rework (`autoFixRounds`) + manual `rework_task`; on verification failure a repair-plan file is generated and fed back to the agent; when rounds run out → `needs_attention` awaiting Tianshu's verdict.
|
|
@@ -188,7 +189,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=qoder, planDoc=./plans/development.m
|
|
|
188
189
|
|---|---|---|
|
|
189
190
|
| `run_task` | write + approval | Dispatch work (optional auto-verify / auto-rework); returns `taskId` asynchronously. Optional `idempotencyKey`: a retry with the same key returns the original `taskId` instead of creating a task |
|
|
190
191
|
| `continue_task` | write + approval | Resume the session behind `needs_user` (ZCode resumes the recorded session; Codex re-observes for `user_confirmation` / re-dispatches for `login_required`; Kimi Code resumes the recorded session and distinguishes question answering / re-observation / full re-dispatch) |
|
|
191
|
-
| `query_task` | read | Poll status / progress / log tail |
|
|
192
|
+
| `query_task` | read | Poll status / progress / log tail / recent fine-grained events. The optional `eventLimit` (1..50, default 10) controls how many `recentEvents` the meta block carries, so long tasks can be told apart as "working normally" versus "stuck on a dialog waiting for a human" |
|
|
192
193
|
| `list_tasks` | read | Filtered history of tasks |
|
|
193
194
|
| `get_task_report` | read | Full text of a verification round's report (`report.md`) |
|
|
194
195
|
| `cancel_task` | write + approval | Cancel a running task: CLI agents kill the process tree; GUI agents click the in-app stop control over CDP and bounded-wait (`gui.cancelWaitMs`, default 15s) for the GUI to go idle, stating so explicitly in the final message when the stop is unconfirmed. For a terminal GUI task this call doubles as the manual acknowledgement entry point — after verifying the window holds no residual run, it clears the `guiStopUnconfirmed` marker |
|
|
@@ -457,6 +458,12 @@ On stderr, `INFO` means skipped/installed; `WARN` means a stale copy was upgrade
|
|
|
457
458
|
- **`cmd` string form documented**: the bilingual docs and the schema/source comments state "prefer the array form; the string form **supports no escaping**, an unclosed quote does not error, and a typo silently splits into multiple argv". Five boundary cases pin the existing tokenizer semantics (no behaviour change)
|
|
458
459
|
- **Counts and scenario numbers aligned**: the "9 tools" header comment in `protocol.test.ts` is corrected to 11 and a `TOOL_DEFS` count assertion is added; the "6 scenarios" references in `ci.yml` / `HANDOFF` / `CONTRIBUTING` (both languages) are synced to 8 (they lagged behind the two skill scenarios added in issue #16)
|
|
459
460
|
- See the [v0.6.1 release notes](<docs/release-v0.6.1.en.md>); verification record in the [issue-17 record](docs/issue-17-small-fixes-record.md)
|
|
461
|
+
- **M32 — GUI selector version-drift fixes (codex / qoder / traework) + v0.6.2** (2026-09-23) — 26 new cases (issue #23)
|
|
462
|
+
- **Codex**: the project-picker trigger copy drifts across versions (26.915 locally measured "切换项目", 26.917 "选择项目"); primary+fallback now cover both, and `boundProjectName` reads both back. A new `probe-codex.mjs audit` prints a hit table for all 20 keys — **on local 26.915 there is no drift other than this trigger**
|
|
463
|
+
- **Qoder**: selectors upgraded from flat strings to a layered structure matching Codex (27 keys with `verifiedVersion`). **The hardware re-probe corrected the issue's conclusion**: on 0.3.4 the workspace menu **does render** — the real cause was **two** `[data-workspace-picker-trigger]` elements breaking the single-match click. Switching to the unique `button[aria-label^="切换或清空当前工作区"]` makes the production `bindWorkspace` pass on real 0.3.4
|
|
464
|
+
- **TraeWork**: added `discovery.ts` (fixed-drive enumeration + registry + relative paths) and fixed the built-in dir (`{APPDATA}/TRAE SOLO CN` is actually the **user-data dir**, not the install location). On Windows the executable name is narrowed to `TRAE SOLO CN.exe` (the old list's `Trae CN` mismatched the unrelated TraeCode CN product); a port-not-ready path now prints diagnostics (exit code / port listeners / existing instances) — **diagnosis only, no launch-strategy change**
|
|
465
|
+
- **Unified diagnostics**: new `src/agents/gui-diagnostics.ts` appends the nearest visible page candidates to selector-resolution failures across all three GUI agents
|
|
466
|
+
- See the [v0.6.2 release notes](<docs/release-v0.6.2.en.md>); verification record in the [issue-23 record](docs/issue-23-selector-drift-record.md)
|
|
460
467
|
|
|
461
468
|
## Agent support status
|
|
462
469
|
|
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
- **11 个 MCP 工具**:`run_task / continue_task / query_task / list_tasks / get_task_report / cancel_task / verify_task / rework_task / get_profiles`,外加视觉验收的 `prepare_visual_baseline / approve_visual_baseline`
|
|
41
41
|
- **异步契约**:`run_task` 秒回 `taskId`,长任务用 `query_task` 轮询(长任务不卡 `tools/call`)。
|
|
42
|
+
- **长任务可观测(issue #18)**:适配器在关键节点上报细粒度事件(`task_dispatched` / `confirmation_dialog_detected` / `awaiting_user_authorization` / `file_modification_started` / `rework_triggered`),`query_task` 经 `eventLimit`(默认 10)回传最近 N 条——**能区分「agent 正在干活」与「卡在弹窗等人工介入」**。事件上报是可选能力:未实现的适配器行为不变。本版 codex 与 traework 已落地上报。详见 [事件流](docs/event-stream.md)。
|
|
42
43
|
- **客观验收**:自动命令检查(typecheck/lint/test/build,缺则跳过 + 技术栈推导)+ 程序化代码分析(变更清单/diffstat/TODO·debugger·密钥形态等可疑标记),全部相对 **git 基线**,不自动 commit/stash。验收引擎 **fail-closed**:测试命令退出码为 0 但输出显示零用例时判失败;git 项目默认要求相对动工前基线产生变更(纯分析任务可在 `.tianshu-mcp/acceptance.json` 设 `"requireChanges": false` 显式关闭)。
|
|
43
44
|
- **验收并行度**:命令检查默认**有界并行**(`verifyConcurrency`,默认 2、范围 1–4)。检查项之间有顺序依赖时(后续检查读取 build 产物、带 `--fix`、共享缓存目录)请设 `1` 完全退化为串行;项目级 `.tianshu-mcp/acceptance.json` 可覆盖,server 级在 `config.json`。报告与日志格式不变(结果按声明顺序返回)。
|
|
44
45
|
- **失败返修闭环**:自动返修(`autoFixRounds`)+ 手动 `rework_task`;验收失败时自动生成修复计划文件并回填给 agent;轮次用尽 → `needs_attention` 等天枢裁决。
|
|
@@ -184,7 +185,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=qoder, planDoc=./plans/development.m
|
|
|
184
185
|
|---|---|---|
|
|
185
186
|
| `run_task` | write + 审批 | 派活(可带自动验收/自动返修),异步返回 `taskId`;可选 `idempotencyKey`:同键重试恒返回原 `taskId`,不新建任务 |
|
|
186
187
|
| `continue_task` | write + 审批 | 恢复 `needs_user` 的原会话(ZCode 恢复原会话;Codex 按 `user_confirmation` 重新观察 / `login_required` 重派;Kimi Code 恢复原会话并区分提问续答 / 重新观察 / 补发任务书) |
|
|
187
|
-
| `query_task` | read | 轮询状态 / 进度 / 日志尾 |
|
|
188
|
+
| `query_task` | read | 轮询状态 / 进度 / 日志尾 / 最近细粒度事件。可选 `eventLimit`(1..50,默认 10)控制 meta 的 `recentEvents` 条数,长任务下可区分「正常执行」与「卡在弹窗等人」 |
|
|
188
189
|
| `list_tasks` | read | 历史任务过滤列表 |
|
|
189
190
|
| `get_task_report` | read | 某轮验收报告全文(`report.md`) |
|
|
190
191
|
| `cancel_task` | write + 审批 | 取消运行中任务:CLI agent kill 进程树;GUI agent 经 CDP 点击停止并在 `gui.cancelWaitMs`(默认 15s)内有界等待 GUI 空闲,未确认停止时终态明示。对已终态的 GUI 任务,本调用兼任人工确认入口——核实窗口无残留运行后调用可清除 `guiStopUnconfirmed` 待确认标记 |
|
|
@@ -458,6 +459,12 @@ run_task(projectPath=D:/xxx/my-app, agentId=qoder, planDoc=./plans/development.m
|
|
|
458
459
|
- **`cmd` 字符串形态文档化**:双语文档与 schema/代码注释明示「推荐数组形态;字符串形态**不支持转义**、引号不闭合不报错、写错会静默拆成多个 argv」;补 5 条边界用例锁定既有分词语义(零行为变更)
|
|
459
460
|
- **计数与场景数对齐**:`protocol.test.ts` 头注释「9 个工具」更正为 11 并新增 `TOOL_DEFS` 数量硬断言;`ci.yml` / `HANDOFF` / `CONTRIBUTING` 双语的「6 场景」同步为 8(issue #16 新增两技能场景后未同步)
|
|
460
461
|
- 详见 [v0.6.1 发布说明](<docs/release-v0.6.1.md>);验证记录见 [issue-17 记录](docs/issue-17-small-fixes-record.md)
|
|
462
|
+
- **M32 — GUI 选择器版本漂移修复(codex / qoder / traework 三例)+ v0.6.2**(2026-09-23)— 新增 26 项用例(issue #23)
|
|
463
|
+
- **Codex**:项目选择触发器文案跨版本漂移(26.915 本机实测为「切换项目」,26.917 为「选择项目」),主/回退并列覆盖两文案;`boundProjectName` 回读同步兼容;新增 `probe-codex.mjs audit` 对全部 20 键输出命中表,**本机 26.915 除该触发器外无其他漂移**
|
|
464
|
+
- **Qoder**:选择器由扁平字符串升级为与 Codex 同构的分层结构(27 键,含 `verifiedVersion`);**真机重探更正 issue 结论**——0.3.4 工作区菜单**并非不渲染**,真因是页面有**两个** `[data-workspace-picker-trigger]` 致唯一点击判歧义失败;改用唯一的 `button[aria-label^="切换或清空当前工作区"]` 后,生产 `bindWorkspace` 已在真实 0.3.4 跑通
|
|
465
|
+
- **TraeWork**:新增 `discovery.ts`(固定盘枚举 + 注册表 + 相对路径),修正内置目录(`{APPDATA}/TRAE SOLO CN` 实为**用户数据目录**,非安装位置);Windows 文件名收窄为只认 `TRAE SOLO CN.exe`(旧清单含 `Trae CN` 会误匹配另一产品 TraeCode CN);端口未就绪时输出诊断(退出码 / 端口监听者 / 既有实例),**只诊断不改启动策略**
|
|
466
|
+
- **统一诊断**:新增 `src/agents/gui-diagnostics.ts`,三 GUI agent 在选择器解析失败时把「页面可见候选」写进错误,使用者一步定位漂移
|
|
467
|
+
- 详见 [v0.6.2 发布说明](<docs/release-v0.6.2.md>);验证记录见 [issue-23 记录](docs/issue-23-selector-drift-record.md)
|
|
461
468
|
|
|
462
469
|
## Agent 适配现状
|
|
463
470
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent 细粒度事件词表(issue #18)。
|
|
3
|
+
*
|
|
4
|
+
* 事件上报是**可选能力**:适配器实现 `AgentRunOptions.onEvent` 即在关键节点主动上报,
|
|
5
|
+
* 未实现的适配器一个字节都不用改(调用侧一律走 `opts.onEvent?.(...)` 可选链)。
|
|
6
|
+
*
|
|
7
|
+
* 本模块刻意**零依赖**:`adapter.ts`、`tasks/task.ts`、`tasks/task-store.ts` 都要引用它,
|
|
8
|
+
* 若它反向引用这些模块会形成循环。因此这里只有类型与常量,不 import 任何东西。
|
|
9
|
+
*
|
|
10
|
+
* 与既有 `note` 事件的关系:`note` 仍是进度/审计通道(承载 progressSummary、
|
|
11
|
+
* lastRunSignal 等自由文本),本词表只表达**语义化节点**,两者同写 task.jsonl、共用时序。
|
|
12
|
+
*/
|
|
13
|
+
export const AGENT_EVENT_NAMES = [
|
|
14
|
+
/** 指令已确认送达 agent(进入其执行队列) */
|
|
15
|
+
"task_dispatched",
|
|
16
|
+
/** 检测到需要人工处理的确认类对话框(含原生文件夹选择框、残留弹窗清理) */
|
|
17
|
+
"confirmation_dialog_detected",
|
|
18
|
+
/** 等待用户授权/登录/确认,任务已卡在人工介入上 */
|
|
19
|
+
"awaiting_user_authorization",
|
|
20
|
+
/**
|
|
21
|
+
* agent 开始执行(GUI 侧观测到「运行中」信号首次出现)。
|
|
22
|
+
* 注意:这是**启发式**推断——适配器并不直接观测文件系统,
|
|
23
|
+
* 因此 detail 文案一律如实写「可能开始改动文件」,不声称已改动。
|
|
24
|
+
*/
|
|
25
|
+
"file_modification_started",
|
|
26
|
+
/** 验收失败后进入返修(自动或 manual) */
|
|
27
|
+
"rework_triggered",
|
|
28
|
+
];
|
|
29
|
+
/** 判断某个事件名是否属于本词表(供读取侧过滤使用) */
|
|
30
|
+
export function isAgentEventName(name) {
|
|
31
|
+
return AGENT_EVENT_NAMES.includes(name);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 把可选的 `onEvent` 钩子包成「永不抛错」的上报函数。
|
|
35
|
+
*
|
|
36
|
+
* 这是 issue #18「事件上报为可选能力,不影响原有逻辑」的落点:上报失败(例如落盘 IO 出错)
|
|
37
|
+
* 绝不能中断正在进行的 GUI 任务,因此这里吞掉异常。未提供钩子时是空操作。
|
|
38
|
+
*/
|
|
39
|
+
export function makeEmitter(onEvent) {
|
|
40
|
+
return async (kind, detail, data) => {
|
|
41
|
+
if (!onEvent)
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
await onEvent({ kind, detail, data });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// 有意吞掉:事件上报属观测能力,不得影响任务本体
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
package/dist/agents/builtin.js
CHANGED
|
@@ -135,20 +135,21 @@ export const BUILTIN_PROFILES = {
|
|
|
135
135
|
killTree: "taskkill",
|
|
136
136
|
authNote: "复用 TraeWork 桌面端登录态;窗口需保持可见(发送依赖模拟输入)",
|
|
137
137
|
executableDiscovery: {
|
|
138
|
+
// issue #23 C3:原先写成 {APPDATA}/TRAE SOLO CN(Roaming),实际安装在
|
|
139
|
+
// {LOCALAPPDATA}/Programs(Local)。补 preferredDrives + relativePaths + 注册表探测
|
|
140
|
+
// (由 traework/discovery.ts 消费),Windows 只认 TRAE SOLO CN.exe。
|
|
138
141
|
dirs: [
|
|
139
|
-
"
|
|
140
|
-
"{
|
|
141
|
-
"{PROGRAMFILES
|
|
142
|
-
"{
|
|
143
|
-
"{LOCALAPPDATA}/TRAE WORK CN",
|
|
144
|
-
"{APPDATA}/TRAE SOLO CN",
|
|
142
|
+
"{LOCALAPPDATA}/Programs/TRAE SOLO CN",
|
|
143
|
+
"{LOCALAPPDATA}/TRAE SOLO CN",
|
|
144
|
+
"{PROGRAMFILES}/TRAE SOLO CN",
|
|
145
|
+
"{PROGRAMFILES(X86)}/TRAE SOLO CN",
|
|
145
146
|
"/Applications/TraeWork.app/Contents/MacOS",
|
|
146
147
|
"/Applications/Trae CN.app/Contents/MacOS",
|
|
147
148
|
],
|
|
148
|
-
fileNames: ["TRAE SOLO CN
|
|
149
|
+
fileNames: process.platform === "darwin" ? ["TRAE SOLO CN", "TraeWork", "Trae CN"] : ["TRAE SOLO CN.exe"],
|
|
149
150
|
fallbackCommand: undefined,
|
|
150
|
-
preferredDrives: [],
|
|
151
|
-
relativePaths: [],
|
|
151
|
+
preferredDrives: ["D:"],
|
|
152
|
+
relativePaths: ["TRAE Work CN/TRAE SOLO CN.exe"],
|
|
152
153
|
installRelativeExe: [],
|
|
153
154
|
scanRoots: [],
|
|
154
155
|
},
|
package/dist/agents/codex/cdp.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { TraeworkCdpClient, CdpDisconnectedError, CdpUnavailableError } from "../traework/cdp/client.js";
|
|
8
8
|
import { resolveFnSource, specArgs } from "./selectors.js";
|
|
9
|
+
import { visibleLabelsExpr, normalizeLabels } from "../gui-diagnostics.js";
|
|
9
10
|
export { CdpDisconnectedError, CdpUnavailableError };
|
|
10
11
|
export class CodexCdpClient {
|
|
11
12
|
selectors;
|
|
@@ -81,6 +82,20 @@ export class CodexCdpClient {
|
|
|
81
82
|
async exists(key) {
|
|
82
83
|
return this.evaluate(this.withResolve(`${this.visibleFilter}const els=__codexResolve(${specArgs(key, this.selectors)});return els.some(vis);`));
|
|
83
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* 收集页面可见候选标签(issue #23 诊断机制):选择器解析失败时,
|
|
87
|
+
* 把「页面实际有什么」写进错误信息,使用者一步定位漂移(无需人工开 CDP)。
|
|
88
|
+
* 永不抛错——诊断失败返回空数组,不影响主流程。
|
|
89
|
+
*/
|
|
90
|
+
async visibleLabels(scopeCss) {
|
|
91
|
+
try {
|
|
92
|
+
const raw = await this.evaluate(visibleLabelsExpr({ scope: scopeCss }));
|
|
93
|
+
return normalizeLabels(raw);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
84
99
|
/** 第一个可见元素文本 */
|
|
85
100
|
async text(key) {
|
|
86
101
|
return ((await this.evaluate(this.withResolve(`${this.visibleFilter}const els=__codexResolve(${specArgs(key, this.selectors)});for(const e of els){if(vis(e))return ((e.value!==undefined?e.value:'')||e.textContent||'').trim()}return '';`))) || "");
|
|
@@ -293,7 +308,8 @@ export class CodexCdpClient {
|
|
|
293
308
|
}
|
|
294
309
|
/**
|
|
295
310
|
* 读取当前会话已绑定的项目名。
|
|
296
|
-
* 权威信号:输入框内的项目 chip 带 `aria-label="
|
|
311
|
+
* 权威信号:输入框内的项目 chip 带 `aria-label="选择项目:<名>"`(26.917 起;
|
|
312
|
+
* 26.903 及更早为「切换项目:<名>」;未绑定时为「不在项目中工作」)。
|
|
297
313
|
* 真机教训:早期实现把所有非 chip 按钮文本拼起来,会返回整页文案并意外“通过”匹配。
|
|
298
314
|
*/
|
|
299
315
|
async boundProjectName() {
|
|
@@ -303,7 +319,7 @@ export class CodexCdpClient {
|
|
|
303
319
|
for(const e of document.querySelectorAll('[aria-label]')){
|
|
304
320
|
if(!vis(e))continue;
|
|
305
321
|
const aria=norm(e.getAttribute('aria-label'));
|
|
306
|
-
const m=/^(
|
|
322
|
+
const m=/^(?:选择项目|切换项目|Select project|Switch project)[::]\\s*(.+)$/i.exec(aria);
|
|
307
323
|
if(m&&m[1])return m[1].trim();
|
|
308
324
|
}
|
|
309
325
|
return '';
|
package/dist/agents/codex/run.js
CHANGED
|
@@ -14,6 +14,7 @@ import { ZCODE_SETUP_DEFAULTS } from "../../config/schema.js";
|
|
|
14
14
|
import fs from "node:fs";
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
|
+
import { makeEmitter } from "../agent-events.js";
|
|
17
18
|
import { mkdirp } from "../../util/fs.js";
|
|
18
19
|
import { parseCodexModel, exactUiName, parseTriggerValue } from "./model.js";
|
|
19
20
|
import { matchCodexProject, projectBasename } from "./project.js";
|
|
@@ -26,6 +27,7 @@ import { focusCodexApp } from "./launcher.js";
|
|
|
26
27
|
import { ensureProjectRegistered } from "./registry.js";
|
|
27
28
|
import { buildInitialPrompt } from "./input.js";
|
|
28
29
|
import { validateTaskReferences } from "../zcode/references.js";
|
|
30
|
+
import { withDiagnostics } from "../gui-diagnostics.js";
|
|
29
31
|
const DEFAULT_DEPS = {
|
|
30
32
|
discover: discoverCodex,
|
|
31
33
|
ensureInstance: ensureCodexInstance,
|
|
@@ -142,6 +144,8 @@ export async function runCodexTask(args) {
|
|
|
142
144
|
const deps = { ...DEFAULT_DEPS, ...args.deps };
|
|
143
145
|
const gui = codexGuiOf(resolved);
|
|
144
146
|
const { logger, close } = fileLogger(logFile, opts.logger);
|
|
147
|
+
// 细粒度事件上报(issue #18):未提供钩子时为空操作,失败不影响任务本体。
|
|
148
|
+
const emit = makeEmitter(opts.onEvent);
|
|
145
149
|
let cdp;
|
|
146
150
|
const result = (extra) => ({
|
|
147
151
|
ok: false,
|
|
@@ -192,13 +196,17 @@ export async function runCodexTask(args) {
|
|
|
192
196
|
cdp?.disconnect();
|
|
193
197
|
cdp = next;
|
|
194
198
|
};
|
|
195
|
-
if (await cdp.exists("loginIndicator"))
|
|
199
|
+
if (await cdp.exists("loginIndicator")) {
|
|
200
|
+
await emit("awaiting_user_authorization", "Codex 登录指示可见,等待用户完成登录或引导", {
|
|
201
|
+
needsUserKind: "login_required",
|
|
202
|
+
});
|
|
196
203
|
return result({
|
|
197
204
|
endReason: "needs_user",
|
|
198
205
|
needsUserKind: "login_required",
|
|
199
206
|
pendingQuestion: "请在 Codex 窗口中完成登录或引导,然后调用 continue_task 确认。",
|
|
200
207
|
session: { boundProjectPath: ctx.projectPath, model: spec.model, permissionMode: gui.defaultPermissionMode },
|
|
201
208
|
});
|
|
209
|
+
}
|
|
202
210
|
await cdp.dismissMenus();
|
|
203
211
|
const resumeKind = ctx.resume?.kind;
|
|
204
212
|
// 重观察恢复(user_confirmation):用户在 GUI 处理完等待项后 turn 自行继续,
|
|
@@ -249,7 +257,7 @@ export async function runCodexTask(args) {
|
|
|
249
257
|
logger.info(`[codex] 已选择既有项目:${matched.item.name}`);
|
|
250
258
|
}
|
|
251
259
|
else {
|
|
252
|
-
const created = await createProject(cdp, ctx.projectPath, aumid, gui, deps, logger);
|
|
260
|
+
const created = await createProject(cdp, ctx.projectPath, aumid, gui, deps, logger, emit);
|
|
253
261
|
if (!created.ok)
|
|
254
262
|
return result({
|
|
255
263
|
hardFailure: true,
|
|
@@ -352,6 +360,12 @@ export async function runCodexTask(args) {
|
|
|
352
360
|
endReason: "send_unknown",
|
|
353
361
|
});
|
|
354
362
|
logger.info(`[codex] 指令已确认发送(对话区=${seenMessage},输入清空=${seenCleared},运行信号=${seenRunning})`);
|
|
363
|
+
await emit("task_dispatched", `第 ${ctx.round} 轮指令已确认送达 Codex`, {
|
|
364
|
+
round: ctx.round,
|
|
365
|
+
seenMessage,
|
|
366
|
+
seenCleared,
|
|
367
|
+
seenRunning,
|
|
368
|
+
});
|
|
355
369
|
}
|
|
356
370
|
// ---- 步骤 6:运行检测 ----
|
|
357
371
|
const deadline = started + ctx.taskTimeoutMs;
|
|
@@ -363,6 +377,8 @@ export async function runCodexTask(args) {
|
|
|
363
377
|
}
|
|
364
378
|
let cdpFailures = 0;
|
|
365
379
|
let lastProgress = 0;
|
|
380
|
+
// file_modification_started 每次进程运行只发一次(首次观测到运行信号时)
|
|
381
|
+
let emittedRunning = false;
|
|
366
382
|
for (;;) {
|
|
367
383
|
// 取消(issue #6):不止退出 MCP 等待循环,还要尽力点击 GUI 停止按钮并等待空闲,
|
|
368
384
|
// 结果经 guiStop 上报,由编排方在终态文案中如实反映。
|
|
@@ -401,28 +417,44 @@ export async function runCodexTask(args) {
|
|
|
401
417
|
}
|
|
402
418
|
continue;
|
|
403
419
|
}
|
|
420
|
+
// 判定前先记下上一轮的运行信号:判定的输出含本轮状态,二者比较才能识别「首次开始运行」
|
|
421
|
+
const wasRunning = state.sawRunning;
|
|
404
422
|
const verdict = judgeCodexPoll(poll, state, gui.stableRounds, gui.idleTimeoutMs, Date.now(), gui.stallTimeoutMs);
|
|
405
423
|
state = verdict.state;
|
|
424
|
+
if (!wasRunning && state.sawRunning && !emittedRunning) {
|
|
425
|
+
emittedRunning = true;
|
|
426
|
+
// 文案如实保留:Codex 适配器并不直接观测文件系统,无法声称文件确已改动。
|
|
427
|
+
await emit("file_modification_started", "停止按钮出现,Codex 开始执行(可能开始改动文件)", {
|
|
428
|
+
round: ctx.round,
|
|
429
|
+
evidence: verdict.evidence,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
406
432
|
if (Date.now() - lastProgress >= gui.progressIntervalMs) {
|
|
407
433
|
const note = `Codex 进度:${verdict.kind};运行证据=${verdict.evidence};对话哈希=${state.hash};稳定轮=${state.stable}`;
|
|
408
434
|
await Promise.resolve(opts.onProgress?.(note)).catch(() => { });
|
|
409
435
|
logger.info(note);
|
|
410
436
|
lastProgress = Date.now();
|
|
411
437
|
}
|
|
412
|
-
if (verdict.kind === "needs_login")
|
|
438
|
+
if (verdict.kind === "needs_login") {
|
|
439
|
+
await emit("awaiting_user_authorization", "Codex 需要登录,等待用户完成登录", {
|
|
440
|
+
needsUserKind: "login_required",
|
|
441
|
+
});
|
|
413
442
|
return result({
|
|
414
443
|
endReason: "needs_user",
|
|
415
444
|
needsUserKind: "login_required",
|
|
416
445
|
pendingQuestion: "Codex 需要登录,请在窗口中完成登录后调用 continue_task 确认。",
|
|
417
446
|
session: { boundProjectPath: ctx.projectPath, model: spec.model, permissionMode: gui.defaultPermissionMode },
|
|
418
447
|
});
|
|
419
|
-
|
|
448
|
+
}
|
|
449
|
+
if (verdict.kind === "needs_user") {
|
|
450
|
+
await emit("awaiting_user_authorization", "停止按钮持续可见且对话长时间未变化,疑似等待用户确认(方案确认/订阅确认等)", { needsUserKind: "user_confirmation" });
|
|
420
451
|
return result({
|
|
421
452
|
endReason: "needs_user",
|
|
422
453
|
needsUserKind: "user_confirmation",
|
|
423
454
|
pendingQuestion: "Codex 停止按钮持续可见且对话内容长时间未变化,疑似在等待用户确认(方案确认/订阅确认等)。请在 Codex 窗口完成处理后调用 continue_task(taskId, message=已处理说明) 恢复;恢复后仅重新接入观察,不会发送消息。",
|
|
424
455
|
session: { boundProjectPath: ctx.projectPath, model: spec.model, permissionMode: gui.defaultPermissionMode },
|
|
425
456
|
});
|
|
457
|
+
}
|
|
426
458
|
if (verdict.kind === "idle_timeout")
|
|
427
459
|
return result({ endReason: "idle_timeout", error: "Codex 空闲超时;已停止 MCP 等待并保留现场" });
|
|
428
460
|
if (verdict.kind === "finished")
|
|
@@ -494,17 +526,26 @@ async function stopGuiTurn(cdp, gui, deps, logger) {
|
|
|
494
526
|
* 打开项目选择层 → 新建项目 → 点源文件夹中心空白区 → 原生对话框键盘填路径 →
|
|
495
527
|
* 确认源文件夹 → 点创建项目。任一步无法唯一定位即 fail-closed。
|
|
496
528
|
*/
|
|
497
|
-
async function createProject(cdp, projectPath, aumid, gui, deps, logger) {
|
|
529
|
+
async function createProject(cdp, projectPath, aumid, gui, deps, logger, emit) {
|
|
498
530
|
// 新建会话后输入框会重渲染,触发器可能短暂缺席 —— 先等它出现再点。
|
|
499
|
-
if (!(await waitFor(cdp, "projectPickerTrigger", deps, 12_000)))
|
|
531
|
+
if (!(await waitFor(cdp, "projectPickerTrigger", deps, 12_000))) {
|
|
532
|
+
// issue #23 诊断机制:解析失败时把页面可见候选一并给出,使用者一步定位文案漂移。
|
|
533
|
+
const labels = await cdp.visibleLabels();
|
|
500
534
|
return {
|
|
501
535
|
ok: false,
|
|
502
|
-
error: "新建会话后未出现项目选择触发器(可用 gui.selectors.projectPickerTrigger 热修复)",
|
|
536
|
+
error: withDiagnostics("新建会话后未出现项目选择触发器(可用 gui.selectors.projectPickerTrigger 热修复)", labels),
|
|
503
537
|
};
|
|
538
|
+
}
|
|
504
539
|
if (!(await cdp.click("projectPickerTrigger")))
|
|
505
|
-
return {
|
|
540
|
+
return {
|
|
541
|
+
ok: false,
|
|
542
|
+
error: withDiagnostics("项目选择触发器点击失败", await cdp.visibleLabels()),
|
|
543
|
+
};
|
|
506
544
|
if (!(await waitFor(cdp, "newProjectMenuItem", deps, 8_000)))
|
|
507
|
-
return {
|
|
545
|
+
return {
|
|
546
|
+
ok: false,
|
|
547
|
+
error: withDiagnostics("项目选择弹层未出现(找不到「新建项目」项)", await cdp.visibleLabels()),
|
|
548
|
+
};
|
|
508
549
|
const menu = await cdp.clickExact("newProjectMenuItem", "新建项目");
|
|
509
550
|
if (!menu.clicked)
|
|
510
551
|
return {
|
|
@@ -519,8 +560,12 @@ async function createProject(cdp, projectPath, aumid, gui, deps, logger) {
|
|
|
519
560
|
.map((p) => p.pid);
|
|
520
561
|
// 先清理残留原生对话框(上一轮失败可能留下,遮挡界面且会让本轮误判「无新对话框」)
|
|
521
562
|
const closed = await deps.closeDialogs(pids);
|
|
522
|
-
if (closed)
|
|
563
|
+
if (closed) {
|
|
523
564
|
logger.warn(`[codex] 已清理 ${closed} 个残留原生对话框`);
|
|
565
|
+
await emit("confirmation_dialog_detected", `清理残留原生对话框 ${closed} 个`, {
|
|
566
|
+
nativeDialogsClosed: closed,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
524
569
|
// 原生文件夹选择器只在应用窗口处于前台时弹出;无人值守下先用 COM 激活把窗口带到前台
|
|
525
570
|
// (SetForegroundWindow 会被前台锁拒绝,应用模型激活不会)。
|
|
526
571
|
const focused = aumid ? await deps.focusApp(aumid) : false;
|
|
@@ -531,6 +576,9 @@ async function createProject(cdp, projectPath, aumid, gui, deps, logger) {
|
|
|
531
576
|
const baseline = await deps.listDialogs(pids);
|
|
532
577
|
if (!(await cdp.clickTrusted("sourceFolderArea")))
|
|
533
578
|
return { ok: false, error: "无法触发「源文件夹」点击(元素不可见或落点被遮挡)" };
|
|
579
|
+
await emit("confirmation_dialog_detected", "已唤起原生「选择文件夹」对话框", {
|
|
580
|
+
dialog: "source_folder",
|
|
581
|
+
});
|
|
534
582
|
await deps.sleep(1500);
|
|
535
583
|
const selected = await deps.selectFolder(projectPath, pids, baseline);
|
|
536
584
|
if (!selected.ok)
|
|
@@ -13,14 +13,14 @@ export const CODEX_SELECTORS = {
|
|
|
13
13
|
primary: 'div.ProseMirror[contenteditable="true"]',
|
|
14
14
|
fallbacks: ['[contenteditable="true"][role="textbox"]', 'textarea[placeholder]'],
|
|
15
15
|
ariaLabels: ["随心输入", "Type a message", "Message", "输入消息"],
|
|
16
|
-
verifiedVersion: "26.
|
|
16
|
+
verifiedVersion: "26.915.x",
|
|
17
17
|
note: "ProseMirror 富文本输入框;必须走 CDP 输入,不能设 value",
|
|
18
18
|
},
|
|
19
19
|
sendButton: {
|
|
20
20
|
primary: 'button[aria-label="发送"]',
|
|
21
21
|
fallbacks: ['button[aria-label*="Send" i]', 'button[type="submit"]'],
|
|
22
22
|
ariaLabels: ["发送", "Send"],
|
|
23
|
-
verifiedVersion: "26.
|
|
23
|
+
verifiedVersion: "26.915.x",
|
|
24
24
|
note: "条件渲染:输入框为空时不存在,出现即代表可发送",
|
|
25
25
|
},
|
|
26
26
|
stopButton: {
|
|
@@ -49,40 +49,52 @@ export const CODEX_SELECTORS = {
|
|
|
49
49
|
primary: 'button.sidebar-item',
|
|
50
50
|
fallbacks: ['nav button', 'aside button'],
|
|
51
51
|
texts: ["新对话", "New chat"],
|
|
52
|
-
verifiedVersion: "26.
|
|
52
|
+
verifiedVersion: "26.915.x",
|
|
53
53
|
note: "侧边栏新建会话",
|
|
54
54
|
},
|
|
55
55
|
projectSection: {
|
|
56
56
|
primary: 'button[class*="section-toggle"]',
|
|
57
57
|
fallbacks: ['nav button', 'aside button'],
|
|
58
58
|
texts: ["项目", "Projects"],
|
|
59
|
-
verifiedVersion: "26.
|
|
59
|
+
verifiedVersion: "26.915.x",
|
|
60
60
|
note: "侧边栏项目分组开关",
|
|
61
61
|
},
|
|
62
62
|
addProject: {
|
|
63
63
|
primary: 'button[aria-label="添加新项目"]',
|
|
64
64
|
fallbacks: ["button[aria-label*='添加' i]", "button[aria-label*='Add project' i]"],
|
|
65
65
|
ariaLabels: ["添加新项目", "Add new project", "Add project"],
|
|
66
|
-
verifiedVersion: "26.
|
|
66
|
+
verifiedVersion: "26.915.x",
|
|
67
67
|
note: "打开新建项目入口",
|
|
68
68
|
},
|
|
69
69
|
projectPickerTrigger: {
|
|
70
|
-
//
|
|
71
|
-
//
|
|
70
|
+
// issue #23:触发器 aria-label 存在版本间文案漂移。
|
|
71
|
+
// - 26.915(本机实测):仍为「切换项目:<名>」
|
|
72
|
+
// - 26.917(issue #23 报告):已漂移为「选择项目:<名>」
|
|
73
|
+
// 两者均为 aria-haspopup="dialog",故主/回退并列覆盖,兼容 26.903 及更早的「切换项目」。
|
|
74
|
+
// 注意两点(沿用旧结论,不得回归):
|
|
72
75
|
// 1) 它**不在** ComposerLayout 作用域内;
|
|
73
76
|
// 2) 侧边栏有 aria-label="添加新项目"、输入框另有独立按钮 aria-label="不在项目中工作"
|
|
74
77
|
// (那是「离开项目」动作,不是触发器)——都不能混入本键,否则会误点。
|
|
75
|
-
primary: 'button[aria-haspopup="dialog"][aria-label^="
|
|
76
|
-
fallbacks: [
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
primary: 'button[aria-haspopup="dialog"][aria-label^="选择项目"]',
|
|
79
|
+
fallbacks: [
|
|
80
|
+
'button[aria-haspopup="dialog"][aria-label^="切换项目"]',
|
|
81
|
+
"button[aria-haspopup='dialog'][aria-label^='Select project']",
|
|
82
|
+
"button[aria-haspopup='dialog'][aria-label^='Switch project']",
|
|
83
|
+
],
|
|
84
|
+
ariaPatterns: [
|
|
85
|
+
"^选择项目[::]",
|
|
86
|
+
"^切换项目[::]",
|
|
87
|
+
"^Select project[::]",
|
|
88
|
+
"^Switch project[::]",
|
|
89
|
+
],
|
|
90
|
+
verifiedVersion: "26.915–26.917",
|
|
91
|
+
note: "输入框内的项目选择触发器;26.915 实测文案为「切换项目」,issue #23 报告 26.917 为「选择项目」,两者并列命中(含 26.903 及更早)",
|
|
80
92
|
},
|
|
81
93
|
projectItem: {
|
|
82
94
|
primary: 'button[aria-label$="的项目操作"]',
|
|
83
95
|
fallbacks: ["button[aria-label*='project actions' i]"],
|
|
84
96
|
ariaPatterns: ["的项目操作$", "project actions$"],
|
|
85
|
-
verifiedVersion: "26.
|
|
97
|
+
verifiedVersion: "26.915.x",
|
|
86
98
|
note: "项目项(按 aria-label 前缀提取项目名)",
|
|
87
99
|
},
|
|
88
100
|
newProjectMenuItem: {
|
|
@@ -144,7 +156,7 @@ export const CODEX_SELECTORS = {
|
|
|
144
156
|
// 作用域:输入框容器(CSS Module 基名 ComposerLayout 稳定,哈希后缀会变)。
|
|
145
157
|
// 兼容性优先,同时列出 ProseMirror 元素本身。
|
|
146
158
|
scope: '[class*="ComposerLayout"],div.ProseMirror[contenteditable="true"]',
|
|
147
|
-
verifiedVersion: "26.
|
|
159
|
+
verifiedVersion: "26.915.x",
|
|
148
160
|
note: "模型+思考等级菜单触发器;限定在输入框作用域内,排除顶部菜单栏与模式切换器",
|
|
149
161
|
},
|
|
150
162
|
reasoningSlider: {
|
|
@@ -178,7 +190,7 @@ export const CODEX_SELECTORS = {
|
|
|
178
190
|
primary: 'button[aria-label="更改权限"]',
|
|
179
191
|
fallbacks: ["button[aria-label*='权限' i]", "button[aria-label*='permission' i]"],
|
|
180
192
|
ariaLabels: ["更改权限", "Change permission", "Permissions"],
|
|
181
|
-
verifiedVersion: "26.
|
|
193
|
+
verifiedVersion: "26.915.x",
|
|
182
194
|
note: "权限模式触发器(文本如「完全访问」)",
|
|
183
195
|
},
|
|
184
196
|
permissionOption: {
|
|
@@ -205,7 +217,7 @@ export const CODEX_SELECTORS = {
|
|
|
205
217
|
'[class*="MessageList" i]',
|
|
206
218
|
'[class*="message-list" i]',
|
|
207
219
|
],
|
|
208
|
-
verifiedVersion: "26.
|
|
220
|
+
verifiedVersion: "26.915.x",
|
|
209
221
|
note: "对话正文区域;用于文本稳定兜底判定(勿用裸 main/#root,会混入导航壳)",
|
|
210
222
|
},
|
|
211
223
|
};
|