tianshu-mcp 0.3.0 → 0.3.2

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 CHANGED
@@ -16,6 +16,99 @@ Chinese version: [CHANGELOG.md](CHANGELOG.md)
16
16
  - TraeWork executable discovery and native-dialog driving on macOS (currently fail-closed).
17
17
  - Optional project-level skill seeding (by default nothing is written into target repos).
18
18
  - macOS hardware verification for the Codex GUI adapter (built-in status remains `research`).
19
+ - Best-effort stop of a GUI-side pending session (via a temporary CDP connection) when cancelling
20
+ a task in the `needs_user` state.
21
+
22
+ ---
23
+
24
+ ## [0.3.2] — 2026-09-12
25
+
26
+ Fixes for issues #5 / #6: **the MCP task model was disconnected from the state of the turn inside
27
+ the Codex GUI**. The former is the completion-detection deadlock that kept reporting `running`
28
+ while Codex waited for user confirmation; the latter is `cancel_task` only aborting the MCP-side
29
+ wait loop, never stopping the in-GUI run, with a misleading description. Both share the same root
30
+ cause and are resolved together.
31
+
32
+ ### Fixed
33
+
34
+ - **Waiting-for-user detection (issue #5)**:
35
+ - `judgeCodexPoll` gains a stall fallback: while the stop button stays visible and the
36
+ conversation hash is unchanged for `gui.stallTimeoutMs` (default 5 minutes), the task
37
+ transitions to `needs_user` (`needsUserKind=user_confirmation`) instead of deadlocking in
38
+ `running` until the overall timeout; the stall timer resets as soon as content changes again.
39
+ - New configurable UI detection `gui.selectors.userGate` (e.g. the embedded-checkout page or
40
+ approval cards): when configured and matched, transitions to `needs_user` immediately.
41
+ Unset by default (disabled) — no unverified selectors are built in.
42
+ - Tightened the `stopButton` selector: removed the `aria-label*="取消"` over-match (the Cancel
43
+ button on waiting-for-user screens used to be mistaken for a running signal).
44
+ - **Recovery path (issue #5 fallout)**: `continue_task` now supports codex —
45
+ - `user_confirmation`: after the user completes the action in the Codex window, resume by
46
+ re-attaching as an observer of the in-GUI run (no message is sent); if the turn already
47
+ finished before resuming, the task is still judged `succeeded` correctly;
48
+ - `login_required`: after login, re-checks the environment and re-dispatches the task brief
49
+ (fresh session + project binding + full initial prompt);
50
+ - zcode recovery behaviour is unchanged; other agents are rejected explicitly.
51
+ - **Cancel actually stops the GUI (issue #6)**:
52
+ - `cancel_task` no longer succeeds on request alone for GUI agents: it first best-effort clicks
53
+ the in-app stop button over CDP, then waits (bounded by `gui.cancelWaitMs`, default 15s) for
54
+ the GUI to become idle before settling `cancelled`; if the stop could not be confirmed the
55
+ terminal message states "GUI 内运行未确认停止…" (the in-GUI run may still be going);
56
+ - process-tree termination for CLI agents is unchanged;
57
+ - cancelling from `needs_user` now notes that a pending session may remain in the GUI
58
+ (no CDP connection exists at that point — documented limitation).
59
+ - **Re-dispatch anti-overlap guard (issue #6 chain risk)**: when dispatching, if the managed
60
+ instance still has an unstopped run, MCP first tries to stop it; if it cannot, the dispatch
61
+ fails hard with `instance_busy`, preventing old and new turns from overlapping inside the same
62
+ app (observed in the wild when re-dispatching right after a cancel).
63
+ - The startup log no longer hardcodes the tool count as `8`; it reports the actual registry size
64
+ (`TOOL_DEFS.length`, currently 9).
65
+
66
+ ### Changed
67
+
68
+ - Tool descriptions match actual semantics: `cancel_task` distinguishes CLI (kill process tree)
69
+ from GUI (best-effort stop click + bounded wait); `continue_task` no longer claims to be
70
+ ZCode-only.
71
+ - `GuiProfile` gains two configurable options, `stallTimeoutMs` (default 300000) and
72
+ `cancelWaitMs` (default 15000), both overridable via agent-profiles.json.
73
+ - Skill docs (SKILL.md §4/§5, usage-examples.md §7) document the codex `user_confirmation` /
74
+ `login_required` recovery flows, GUI cancel semantics and the new options.
75
+
76
+ ---
77
+
78
+ ## [0.3.1] — 2026-09-12
79
+
80
+ Post-v0.3.0 housekeeping for docs and release automation: **no source-level behavior changes**.
81
+ The focus is a full rewrite of the self-installed skill docs plus GitHub/Gitee release-body
82
+ composition and link fixes.
83
+
84
+ ### Changed
85
+
86
+ - **Skill docs (`skills/tianshu-mcp/`) fully rewritten to match the actual v0.3.0 tool surface**:
87
+ - Corrected the `codex` description from "headless CLI" to the ChatGPT desktop GUI adapter
88
+ (MSIX + COM activation + CDP); documents the required `model`, optional `reasoningLevel` /
89
+ `planDoc` / `designSystem`, and that `mode` is not supported;
90
+ - Documented the `run_task` `context` parameter and the send-time validation of path references
91
+ inside task/context; corrected the `autoFixRounds` default precedence
92
+ (call argument > codex 5 / zcode 2 > server default 0);
93
+ - Added usage for `list_tasks`, `query_task(tailLines)`, `get_task_report(round)` and
94
+ `verify_task` (`extraChecks` / `checksMode` / `baselineRef`) plus the four-level acceptance
95
+ command precedence;
96
+ - Documented the four `needs_user` kinds and meta fields such as `needsUserKind` /
97
+ `pendingQuestion` / `errorType` / `reportRound` / `verificationSource`;
98
+ - Added `continue_task` to the approval list; replaced emoji status markers with plain text
99
+ (PASS / warning) in the usage examples.
100
+ - **Release automation fixes (exposed by the v0.3.0 tag)**:
101
+ - The release body is now composed bilingually from `docs/release-v<version>.md` and `.en.md`,
102
+ with in-document relative links rewritten to tag-absolute links; a missing doc fails the
103
+ workflow loudly instead of producing a shell-only body;
104
+ - `Full Changelog` resolves the previous tag via `git describe` into a `compare/<prev>...<tag>`
105
+ link instead of degrading to a commits link;
106
+ - The body's `CI` link resolves the CI run for the same SHA instead of pointing at the Release run;
107
+ - Gitee releases are automated in `release.yml`: `scripts/gitee-release.mjs` idempotently
108
+ creates/updates the mirrored release (requires the `GITEE_TOKEN` secret; skipped loudly when unset).
109
+ - `.gitignore` now ignores npm pack artifacts and local temporary verification directories.
110
+ - Added the missing `[0.1.10]` / `[0.2.0]` / `[0.3.0]` / `[0.3.1]` compare links at the bottom of
111
+ this file and its Chinese counterpart.
19
112
 
20
113
  ---
21
114
 
@@ -401,7 +494,12 @@ project → pick model and reasoning level → send instructions → run detecti
401
494
 
402
495
  ---
403
496
 
404
- [Unreleased]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.9...HEAD
497
+ [Unreleased]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.3.2...HEAD
498
+ [0.3.2]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.3.1...v0.3.2
499
+ [0.3.1]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.3.0...v0.3.1
500
+ [0.3.0]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.2.0...v0.3.0
501
+ [0.2.0]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.10...v0.2.0
502
+ [0.1.10]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.9...v0.1.10
405
503
  [0.1.9]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.8...v0.1.9
406
504
  [0.1.8]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.7...v0.1.8
407
505
  [0.1.7]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.6...v0.1.7
package/CHANGELOG.md CHANGED
@@ -15,6 +15,81 @@
15
15
  - TraeWork 在 macOS 下的可执行探测与原生对话框驱动(当前 macOS 分支 fail-closed)。
16
16
  - 可选的项目级技能播种(默认不写入目标项目仓库)。
17
17
  - Codex GUI 的 macOS 真机验证(当前内置状态为 `research`)。
18
+ - needs_user 状态下取消任务时经临时 CDP 连接尽力停止 GUI 内等待中的会话。
19
+
20
+ ---
21
+
22
+ ## [0.3.2] — 2026-09-12
23
+
24
+ 修复 issue #5 / #6:**MCP 任务模型与 Codex GUI 内 turn 的状态脱节**。
25
+ 前者是"等用户确认时恒报 running"的完成判定死锁,后者是 `cancel_task` 只停 MCP 侧等待、
26
+ 不停 GUI 内运行且描述失实。同根问题一并收敛。
27
+
28
+ ### 修复
29
+
30
+ - **等待用户检测(issue #5)**:
31
+ - `judgeCodexPoll` 新增 stall 兜底:停止按钮持续可见且对话哈希 `gui.stallTimeoutMs`
32
+ (默认 5 分钟)不变 → 判定等待用户,任务转 `needs_user`(`needsUserKind=user_confirmation`),
33
+ 不再死锁在 `running` 直到总超时;对话内容恢复变化会重置 stall 计时。
34
+ - 新增可配置界面检测 `gui.selectors.userGate`(如结账页 `embedded-checkout`、确认卡):
35
+ 命中即快速转 `needs_user`;默认未配置 = 禁用,不内置未真机验证的选择器。
36
+ - 收紧 `stopButton` 选择器:移除 `aria-label*="取消"` 过匹配(等待用户界面的"取消"
37
+ 按钮曾被误判为运行信号)。
38
+ - **恢复通道(issue #5 牵连)**:`continue_task` 扩展支持 codex——
39
+ - `user_confirmation`:用户在 Codex 窗口处理完后恢复,MCP 仅重新接入观察 GUI 内运行
40
+ (不发送消息);恢复前 turn 已完成也能正确判 `succeeded`;
41
+ - `login_required`:复检环境后重新派发任务书(新会话 + 项目绑定 + 完整初始指令);
42
+ - zcode 原有恢复行为不变;其他 agent 明确拒绝。
43
+ - **取消真停 GUI(issue #6)**:
44
+ - `cancel_task` 对 GUI agent 不再"请求即成功":先经 CDP 尽力点击界面停止按钮,
45
+ 并在 `gui.cancelWaitMs`(默认 15 秒)内有界等待 GUI 真正空闲后落 `cancelled`;
46
+ 未确认停止时终态文案明示"GUI 内运行未确认停止,Codex 窗口中的任务可能仍在继续";
47
+ - CLI agent 的进程树终止语义不变;
48
+ - `needs_user` 状态取消的文案提示"GUI 内可能仍有等待中的会话"(此时 MCP 侧无 CDP 连接,
49
+ 列为已知边界)。
50
+ - **重派防交叠护栏(issue #6 连锁)**:派发前发现受管实例上仍有未停止的运行时,
51
+ 先尽力停止;仍不空闲则以 `instance_busy` 硬失败拒绝派发,杜绝新旧 turn 在同一
52
+ 应用内交叠(取消后立刻重派曾实测踩中)。
53
+ - 启动日志的工具数由硬编码 `8` 改为按注册表实际数量(`TOOL_DEFS.length`,当前为 9)输出。
54
+
55
+ ### 变更
56
+
57
+ - 工具描述对齐实际语义:`cancel_task` 区分 CLI(kill 进程树)与 GUI(尽力点击停止 +
58
+ 有界等待);`continue_task` 去掉"只用于 ZCode"限定。
59
+ - `GuiProfile` 新增 `stallTimeoutMs`(默认 300000)与 `cancelWaitMs`(默认 15000)配置项,
60
+ 均可由 agent-profiles.json 覆盖。
61
+ - 技能文档(SKILL.md §4/§5、usage-examples.md §7)补齐 codex `user_confirmation` /
62
+ `login_required` 恢复方式、GUI 取消语义与相关配置示例。
63
+
64
+ ---
65
+
66
+ ## [0.3.1] — 2026-09-12
67
+
68
+ v0.3.0 发布后的文档与发布自动化收口:**无源码行为变更**,重点是技能自检安装文档全面重写、
69
+ GitHub/Gitee 发行版正文合成与链接修复。
70
+
71
+ ### 变更
72
+
73
+ - **技能文档(`skills/tianshu-mcp/`)全面重写并对齐 v0.3.0 实际工具面**:
74
+ - 修正 `codex` 描述:由「无头 CLI」更正为 ChatGPT 桌面端 GUI adapter(MSIX + COM 激活 + CDP),
75
+ 标注 `model` 必填、`reasoningLevel` / `planDoc` / `designSystem` 用法、不支持 `mode`;
76
+ - 补齐 `run_task` 的 `context` 参数语义与 task/context 路径引用发送前校验说明;
77
+ 修正 `autoFixRounds` 默认值优先级(调用参数 > codex 5 / zcode 2 > server 默认 0);
78
+ - 补齐 `list_tasks`、`query_task(tailLines)`、`get_task_report(round)` 与
79
+ `verify_task`(`extraChecks` / `checksMode` / `baselineRef`)的用法与验收命令四级优先级;
80
+ - 补充 `needs_user` 四种等待类型与 meta 块 `needsUserKind` / `pendingQuestion` / `errorType` /
81
+ `reportRound` / `verificationSource` 等字段解读;
82
+ - 审批清单补上 `continue_task`;使用示例中的 emoji 状态标记改为文字(PASS / 告警)。
83
+ - **发布自动化修复(v0.3.0 tag 实测暴露)**:
84
+ - Release 正文改为由 `docs/release-v<版本>.md` 与 `.en.md` 双语合成,文档内相对链接改写为
85
+ 该 tag 的绝对链接,缺文档时工作流明确报错(不再产出空壳正文);
86
+ - `Full Changelog` 经 `git describe` 解析上一 tag,生成 `compare/<prev>...<tag>` 比较链接,
87
+ 不再退化为 commits 链接;
88
+ - 正文 `CI` 链接解析同 SHA 的 CI 运行,避免误指 Release 自身运行;
89
+ - Gitee 发行版纳入 `release.yml` 自动化:`scripts/gitee-release.mjs` 幂等创建/更新
90
+ (需仓库 Secret `GITEE_TOKEN`,未配置时明确提示并跳过)。
91
+ - `.gitignore` 忽略 npm pack 产物与本地临时校验目录。
92
+ - 补齐本文件与英文版底部缺失的 `[0.1.10]` / `[0.2.0]` / `[0.3.0]` / `[0.3.1]` 比较链接。
18
93
 
19
94
  ---
20
95
 
@@ -356,7 +431,12 @@ Codex 桌面端改为 **GUI 驱动**:新增 `codex-gui` adapter,通过 MSIX
356
431
 
357
432
  ---
358
433
 
359
- [未发布]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.9...HEAD
434
+ [未发布]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.3.2...HEAD
435
+ [0.3.2]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.3.1...v0.3.2
436
+ [0.3.1]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.3.0...v0.3.1
437
+ [0.3.0]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.2.0...v0.3.0
438
+ [0.2.0]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.10...v0.2.0
439
+ [0.1.10]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.9...v0.1.10
360
440
  [0.1.9]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.8...v0.1.9
361
441
  [0.1.8]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.7...v0.1.8
362
442
  [0.1.7]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.1.6...v0.1.7
package/README.en.md CHANGED
@@ -182,6 +182,7 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
182
182
  | [docs/zcode-windows-smoke.en.md](docs/zcode-windows-smoke.en.md) | ZCode Windows hardware record for development, same-session repair, and question continuation |
183
183
  | [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 |
184
184
  | [docs/codex-windows-smoke.en.md](docs/codex-windows-smoke.en.md) | Codex Windows hardware record (incl. verify-fail → auto plan → repair-pass loop) |
185
+ | [docs/release-v0.3.1.en.md](docs/release-v0.3.1.en.md) | v0.3.1 release notes (skill docs rewrite + release automation fixes) |
185
186
  | [docs/release-v0.3.0.en.md](docs/release-v0.3.0.en.md) | v0.3.0 release notes (Codex desktop GUI adapter, incl. BREAKING) |
186
187
  | [docs/release-v0.2.0.en.md](docs/release-v0.2.0.en.md) | v0.2.0 release notes (unified ZCode GUI loop) |
187
188
  | [docs/acceptance-config.en.md](docs/acceptance-config.en.md) | Project-level `.tianshu-mcp/acceptance.json` acceptance config spec |
@@ -254,6 +255,9 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
254
255
  - Hardware-verified: full loop for a registered project, full loop for an unregistered project after automatic registration, and the verify-fail → generated plan → repair-pass loop
255
256
  - Real business acceptance: drove Codex to build a "Fruit Ninja" mini-game, passed acceptance, verified playable in a headless browser; [acceptance record](docs/codex-windows-smoke.en.md)
256
257
  - macOS unverified; built-in status `research`
258
+ - **M13 — Skill docs alignment + release automation fixes + v0.3.1** (2026-09-12, see [release-v0.3.1.en.md](docs/release-v0.3.1.en.md))
259
+ - Self-installed skill docs (SKILL.md / usage-examples.md) rewritten item by item against the v0.3.0 tool surface (codex GUI params, needs_user handling, verify/list usage)
260
+ - Bilingual release bodies, Full Changelog & CI link fixes, and automated Gitee releases
257
261
 
258
262
  ## Agent support status
259
263
 
@@ -276,7 +280,7 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
276
280
 
277
281
  | Document | Content |
278
282
  |---|---|
279
- | [CHANGELOG.en.md](CHANGELOG.en.md) | Version history (v0.1.0 → v0.3.0) |
283
+ | [CHANGELOG.en.md](CHANGELOG.en.md) | Version history (v0.1.0 → v0.3.1) |
280
284
  | [CONTRIBUTING.en.md](CONTRIBUTING.en.md) | Dev setup, conventions, commit/release flow, adding an agent |
281
285
  | [SECURITY.en.md](SECURITY.en.md) | Security model (zero credentials / command whitelist / process & desktop-automation boundaries) and private reporting |
282
286
  | [CODE_OF_CONDUCT.en.md](CODE_OF_CONDUCT.en.md) | Contributor Code of Conduct |
package/README.md CHANGED
@@ -180,6 +180,7 @@ ZCode 提问或需要用户处理登录、旧实例、系统权限时进入 `nee
180
180
  | [docs/zcode-windows-smoke.md](docs/zcode-windows-smoke.md) | ZCode Windows 真机开发、同会话返修与提问续跑验收记录 |
181
181
  | [docs/codex-gui-cdp.md](docs/codex-gui-cdp.md) | Codex 桌面端 GUI 驱动:MSIX COM 激活、CDP 接管、选择器、运行检测、验收返修 |
182
182
  | [docs/codex-windows-smoke.md](docs/codex-windows-smoke.md) | Codex Windows 真机验收记录(含验收失败→自动生成计划→返修通过闭环) |
183
+ | [docs/release-v0.3.1.md](docs/release-v0.3.1.md) | v0.3.1 发布说明(技能文档重写 + 发布自动化修复) |
183
184
  | [docs/release-v0.3.0.md](docs/release-v0.3.0.md) | v0.3.0 发布说明(Codex 桌面端 GUI 适配,含 BREAKING) |
184
185
  | [docs/release-v0.2.0.md](docs/release-v0.2.0.md) | v0.2.0 发布说明(ZCode GUI 统一闭环) |
185
186
  | [docs/acceptance-config.md](docs/acceptance-config.md) | 项目级 `.tianshu-mcp/acceptance.json` 验收配置规范 |
@@ -254,6 +255,9 @@ ZCode 提问或需要用户处理登录、旧实例、系统权限时进入 `nee
254
255
  - 真机通过:已登记项目全链路、未登记项目自动登记全链路、验收失败→自动生成计划→返修通过闭环
255
256
  - 真实业务验收:驱动 Codex 开发「切水果小游戏」并通过验收,无头浏览器实测可玩;[验收记录](docs/codex-windows-smoke.md)
256
257
  - macOS 未验证,内置状态 `research`
258
+ - **M13 — 技能文档对齐 + 发布自动化修复 + v0.3.1**(2026-09-12,见 [release-v0.3.1.md](docs/release-v0.3.1.md))
259
+ - 技能自检安装文档(SKILL.md / usage-examples.md)对照 v0.3.0 工具面逐项重写(codex GUI 参数、needs_user 处理、verify/list 用法)
260
+ - Release 正文双语合成、Full Changelog 与 CI 链接修复、Gitee 发行版纳入自动化
257
261
 
258
262
  ## Agent 适配现状
259
263
 
@@ -277,7 +281,7 @@ ZCode 提问或需要用户处理登录、旧实例、系统权限时进入 `nee
277
281
  | 文档 | 内容 |
278
282
  |---|---|
279
283
  | [HANDOFF.md](HANDOFF.md) | 项目交接文档:当前状态快照、架构导览、硬性红线、已知限制、接手建议 |
280
- | [CHANGELOG.md](CHANGELOG.md) | 版本变更日志(v0.1.0 → v0.3.0) |
284
+ | [CHANGELOG.md](CHANGELOG.md) | 版本变更日志(v0.1.0 → v0.3.1) |
281
285
  | [CONTRIBUTING.md](CONTRIBUTING.md) | 开发环境、工程规范、提交与发布流程、如何新增 agent |
282
286
  | [SECURITY.md](SECURITY.md) | 安全模型(凭证零管理/命令白名单/进程与桌面自动化边界)与私密报告渠道 |
283
287
  | [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) | 贡献者行为准则 |
@@ -47,6 +47,11 @@ export interface TaskContext {
47
47
  kind: "continue" | "rework";
48
48
  message?: string;
49
49
  sendMessage: boolean;
50
+ /**
51
+ * 重新接入观察(不发送任何消息):codex user_confirmation 恢复专用。
52
+ * 用户在 GUI 处理完等待项后 turn 自行继续,MCP 只需重连 CDP 观察到终态。
53
+ */
54
+ reobserve?: boolean;
50
55
  sessionId?: string;
51
56
  sessionTitle?: string;
52
57
  boundProjectPath?: string;
@@ -75,8 +80,17 @@ export interface AgentRunResult {
75
80
  endReason?: string;
76
81
  /** GUI 实例是否因任务未真正完成而被保留 */
77
82
  keptInstance?: boolean;
78
- needsUserKind?: "agent_question" | "close_existing_instance" | "login_required" | "system_permission";
83
+ needsUserKind?: "agent_question" | "close_existing_instance" | "login_required" | "system_permission" | "user_confirmation";
79
84
  pendingQuestion?: string;
85
+ /**
86
+ * 取消路径的 GUI 侧停止结果(GUI agent 专用):
87
+ * clicked=是否点击了界面停止按钮;idle=等待窗口内 GUI 是否真正空闲。
88
+ * idle=false 时编排方必须在终态文案中明示「GUI 内运行未停止」。
89
+ */
90
+ guiStop?: {
91
+ clicked: boolean;
92
+ idle: boolean;
93
+ };
80
94
  session?: {
81
95
  id?: string;
82
96
  title?: string;
@@ -37,6 +37,8 @@ export const BUILTIN_PROFILES = {
37
37
  pollIntervalMs: 3_000,
38
38
  stableRounds: 4,
39
39
  idleTimeoutMs: 10 * 60_000,
40
+ stallTimeoutMs: 5 * 60_000,
41
+ cancelWaitMs: 15_000,
40
42
  cdpSendTimeoutMs: 15_000,
41
43
  progressIntervalMs: 30_000,
42
44
  modelSwitch: true,
@@ -94,6 +96,8 @@ export const BUILTIN_PROFILES = {
94
96
  pollIntervalMs: 3_000,
95
97
  stableRounds: 4,
96
98
  idleTimeoutMs: 10 * 60_000,
99
+ stallTimeoutMs: 5 * 60_000,
100
+ cancelWaitMs: 15_000,
97
101
  cdpSendTimeoutMs: 15_000,
98
102
  progressIntervalMs: 30_000,
99
103
  modelSwitch: true,
@@ -150,6 +154,8 @@ export const BUILTIN_PROFILES = {
150
154
  pollIntervalMs: 3_000,
151
155
  stableRounds: 12,
152
156
  idleTimeoutMs: 10 * 60_000,
157
+ stallTimeoutMs: 5 * 60_000,
158
+ cancelWaitMs: 15_000,
153
159
  cdpSendTimeoutMs: 15_000,
154
160
  progressIntervalMs: 30_000,
155
161
  modelSwitch: true,
@@ -26,6 +26,8 @@ export interface CodexPoll {
26
26
  conversationText: string;
27
27
  /** 登录/引导页可见 */
28
28
  loginVisible: boolean;
29
+ /** 「等待用户」界面检测命中(gui.selectors.userGate 配置后启用) */
30
+ userGateVisible: boolean;
29
31
  }
30
32
  export declare class CodexCdpClient {
31
33
  private readonly selectors;
@@ -258,11 +258,16 @@ export class CodexCdpClient {
258
258
  const els=__codexResolve(${specArgs("modelMenuItem", this.selectors)}).filter(vis);
259
259
  const available=els.map((e)=>(e.innerText||'').replace(/\\s+/g,' ').trim()).filter(Boolean);
260
260
  const hits=els.filter((e)=>norm(e.innerText)===target);
261
- if(hits.length===1){const e=hits[0];const r=e.getBoundingClientRect();return {count:1,available,point:{x:Math.round(r.left+r.width/2),y:Math.round(r.top+r.height/2)}}}
261
+ if(hits.length===1){
262
+ // 真机实测(26.903.9818.0):模型 radio 对 trusted 鼠标点击只收起菜单、不切换选中态,
263
+ // DOM .click() 才会真正生效(aria-checked 翻转)且菜单保持打开(后续调滑块依赖它)。
264
+ // 与「项目选择触发器需要 trusted 才能展开」相反——菜单项选择用 DOM click。
265
+ hits[0].click();
266
+ return {count:1,available,clicked:true};
267
+ }
262
268
  return {count:hits.length,available};`));
263
- if (!found.point)
269
+ if (!found.clicked)
264
270
  return { clicked: false, count: found.count, available: found.available };
265
- await this.clickAt(found.point.x, found.point.y);
266
271
  return { clicked: true, count: found.count, available: found.available };
267
272
  }
268
273
  /** 读取当前模型/思考等级触发器文本(限定输入框作用域,排除菜单栏与模式切换器) */
@@ -336,6 +341,7 @@ export class CodexCdpClient {
336
341
  sendButton: specArgs("sendButton", this.selectors),
337
342
  chatInput: specArgs("chatInput", this.selectors),
338
343
  loginIndicator: specArgs("loginIndicator", this.selectors),
344
+ userGate: specArgs("userGate", this.selectors),
339
345
  messageArea: specArgs("messageArea", this.selectors),
340
346
  })};${this.visibleFilter}
341
347
  const resolve=(k)=>__codexResolve(JSON.parse(sels[k]));
@@ -346,7 +352,8 @@ export class CodexCdpClient {
346
352
  sendVisible:visAny('sendButton'),
347
353
  composerText:(textOf('chatInput')||'').trim(),
348
354
  conversationText:(textOf('messageArea')||'').trim().slice(0,20000),
349
- loginVisible:visAny('loginIndicator')
355
+ loginVisible:visAny('loginIndicator'),
356
+ userGateVisible:visAny('userGate')
350
357
  };`));
351
358
  }
352
359
  }
@@ -9,6 +9,8 @@ export interface CodexPoll {
9
9
  loginVisible: boolean;
10
10
  /** 发送按钮当前是否可见(输入非空时) */
11
11
  sendVisible?: boolean;
12
+ /** 「等待用户」界面检测命中(gui.selectors.userGate 配置后启用) */
13
+ userGateVisible?: boolean;
12
14
  }
13
15
  export interface CodexPollState {
14
16
  hash: string;
@@ -16,12 +18,16 @@ export interface CodexPollState {
16
18
  idleSince: number;
17
19
  /** 本轮是否曾观测到权威运行信号 */
18
20
  sawRunning: boolean;
21
+ /** 停止按钮可见且对话哈希不变的起始时刻(0 = 未在计时) */
22
+ stallSince: number;
19
23
  }
20
24
  export type CodexVerdict = {
21
- kind: "running" | "pending" | "finished" | "needs_login" | "idle_timeout";
25
+ kind: "running" | "pending" | "finished" | "needs_login" | "needs_user" | "idle_timeout";
22
26
  state: CodexPollState;
23
27
  evidence: string;
24
28
  };
29
+ /** 与 GuiProfileSchema.stallTimeoutMs 默认一致;仅供直调兜底,生产路径由 codexGuiOf 传入 */
30
+ export declare const DEFAULT_STALL_TIMEOUT_MS = 300000;
25
31
  export declare function hashText(text: string): string;
26
32
  export declare const initialCodexState: () => CodexPollState;
27
- export declare function judgeCodexPoll(poll: CodexPoll, previous: CodexPollState, stableRounds: number, idleTimeoutMs: number, now?: number): CodexVerdict;
33
+ export declare function judgeCodexPoll(poll: CodexPoll, previous: CodexPollState, stableRounds: number, idleTimeoutMs: number, now?: number, stallTimeoutMs?: number): CodexVerdict;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Codex 任务运行判定(开发计划决策 7/8)。
2
+ * Codex 任务运行判定(开发计划决策 7/8;issue #5 修复后含 stall 判定)。
3
3
  *
4
4
  * 设计(吸取 TraeWork 误判完成的教训):
5
5
  * 1. 停止按钮为**权威运行信号**:出现即 running,绝不在此时判完成。
@@ -7,8 +7,17 @@
7
7
  * 把「其实还在生成、只是 DOM 恰好静止」误判为完成。
8
8
  * 3. 若始终未观测到运行信号(选择器未命中)→ 不判完成,转入空闲计时,最终 idle_timeout:
9
9
  * 结束本轮但**不终止实例**(失败开放,不比现状更糟)。
10
+ * 4. 等待用户判定(issue #5):「方案确认卡/订阅结账页」等场景下 turn 是暂停而非结束,
11
+ * 停止按钮恒可见 → 上面的规则会死锁。两条出路:
12
+ * - userGateVisible:profile.gui.selectors.userGate 配置的界面检测(快速路径);
13
+ * - stall 兜底:停止按钮持续可见且对话哈希 stallTimeoutMs 不变 → needs_user。
14
+ * 它无法区分「等用户确认」与「长时间静默的合法长命令」,误判后果是转
15
+ * needs_user(非终态、可 continue 恢复),远好于死等总超时;长命令任务
16
+ * 应调大 gui.stallTimeoutMs。
10
17
  */
11
18
  import { createHash } from "node:crypto";
19
+ /** 与 GuiProfileSchema.stallTimeoutMs 默认一致;仅供直调兜底,生产路径由 codexGuiOf 传入 */
20
+ export const DEFAULT_STALL_TIMEOUT_MS = 300_000;
12
21
  export function hashText(text) {
13
22
  return createHash("sha256").update(text).digest("hex").slice(0, 12);
14
23
  }
@@ -17,29 +26,55 @@ export const initialCodexState = () => ({
17
26
  stable: 0,
18
27
  idleSince: 0,
19
28
  sawRunning: false,
29
+ stallSince: 0,
20
30
  });
21
- export function judgeCodexPoll(poll, previous, stableRounds, idleTimeoutMs, now = Date.now()) {
31
+ export function judgeCodexPoll(poll, previous, stableRounds, idleTimeoutMs, now = Date.now(), stallTimeoutMs = DEFAULT_STALL_TIMEOUT_MS) {
22
32
  const hash = hashText(poll.conversationText);
23
33
  if (poll.loginVisible)
24
34
  return {
25
35
  kind: "needs_login",
26
- state: { hash, stable: 0, idleSince: 0, sawRunning: previous.sawRunning },
36
+ state: { hash, stable: 0, idleSince: 0, sawRunning: previous.sawRunning, stallSince: 0 },
27
37
  evidence: "login_indicator",
28
38
  };
29
- if (poll.stopVisible)
39
+ // 等待用户快速路径:界面检测命中(如结账页/确认卡,选择器可配置)
40
+ if (poll.userGateVisible)
41
+ return {
42
+ kind: "needs_user",
43
+ state: { hash, stable: 0, idleSince: 0, sawRunning: previous.sawRunning, stallSince: 0 },
44
+ evidence: "user_gate",
45
+ };
46
+ if (poll.stopVisible) {
47
+ // stall 兜底(issue #5):停止按钮恒可见时对话哈希长时间不变 → 判定等待用户。
48
+ const hashUnchanged = hash === previous.hash;
49
+ const stallSince = hashUnchanged ? previous.stallSince || now : now;
50
+ if (hashUnchanged && previous.stallSince && now - previous.stallSince >= stallTimeoutMs)
51
+ return {
52
+ kind: "needs_user",
53
+ state: { hash, stable: 0, idleSince: 0, sawRunning: true, stallSince },
54
+ evidence: "stop_button+stall",
55
+ };
30
56
  return {
31
57
  kind: "running",
32
- state: { hash, stable: 0, idleSince: 0, sawRunning: true },
58
+ state: { hash, stable: 0, idleSince: 0, sawRunning: true, stallSince },
33
59
  evidence: "stop_button",
34
60
  };
61
+ }
35
62
  const sawRunning = previous.sawRunning;
36
63
  const stable = hash === previous.hash && poll.conversationText.trim() ? previous.stable + 1 : 0;
37
64
  const idleSince = stable >= stableRounds ? previous.idleSince || now : 0;
38
65
  // 权威完成:曾运行 → 停止钮消失 → 文本稳定
39
66
  if (sawRunning && stable >= stableRounds && poll.conversationText.trim())
40
- return { kind: "finished", state: { hash, stable, idleSince, sawRunning }, evidence: "stop_button_gone+text_stable" };
67
+ return {
68
+ kind: "finished",
69
+ state: { hash, stable, idleSince, sawRunning, stallSince: 0 },
70
+ evidence: "stop_button_gone+text_stable",
71
+ };
41
72
  // 失败开放:始终无运行信号 → 空闲超时(结束本轮、保留实例)
42
73
  if (idleSince && now - idleSince >= idleTimeoutMs)
43
- return { kind: "idle_timeout", state: { hash, stable, idleSince, sawRunning }, evidence: "no_running_signal+idle" };
44
- return { kind: "pending", state: { hash, stable, idleSince, sawRunning }, evidence: "none" };
74
+ return {
75
+ kind: "idle_timeout",
76
+ state: { hash, stable, idleSince, sawRunning, stallSince: 0 },
77
+ evidence: "no_running_signal+idle",
78
+ };
79
+ return { kind: "pending", state: { hash, stable, idleSince, sawRunning, stallSince: 0 }, evidence: "none" };
45
80
  }