tianshu-mcp 0.5.7 → 0.5.9
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 +47 -0
- package/CHANGELOG.md +47 -0
- package/README.en.md +42 -12
- package/README.md +41 -11
- package/dist/agents/gui-instance.js +1 -1
- package/dist/config/schema.js +12 -0
- package/dist/loop/fix-loop.js +43 -13
- package/dist/mcp/formatter.js +3 -0
- package/dist/mcp/handlers.js +1 -1
- package/dist/mcp/tools.js +1 -1
- package/dist/server.js +6 -3
- package/dist/tasks/task-manager.js +120 -18
- package/dist/tasks/task.js +46 -0
- package/dist/version.generated.js +1 -1
- package/package.json +5 -1
- package/scripts/probe-traework.mjs +108 -0
- package/skills/tianshu-mcp/SKILL.md +4 -3
- package/skills/tianshu-mcp/usage-examples.md +43 -5
package/CHANGELOG.en.md
CHANGED
|
@@ -8,6 +8,52 @@ Chinese version: [CHANGELOG.md](CHANGELOG.md)
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## [0.5.9] - 2026-09-23
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **The GUI terminal state is no longer a false claim on server exit or restart archiving** ([issue #14](https://github.com/lanlan0811/tianshu-mcp/issues/14)). A GUI agent is an external desktop application and the server **owns no process** for it: after an abort the adapter can at best best-effort click the in-app stop control. The old code wrote "server exited, process terminated" for every active task, applying a statement that only holds for spawn children to GUI agents — the orchestrator was dead, the GUI might still be editing the user's project, and nobody was watching.
|
|
16
|
+
- `persistInterrupted()` now branches on the driver: spawn keeps the original wording; GUI writes the honest outcome derived from the adapter's `guiStop` — "confirmed stopped", "stop unconfirmed, the task in the … window may still be running, please open … and confirm there is no residual run", or "no stop result to confirm" (ZCode and TraeWork never click stop and never report `guiStop`).
|
|
17
|
+
- `shutdownInterrupt()` gives GUI tasks a **globally shared** `guiStopWaitMs` budget (new config, default 15000, overridable in `config.json`) so the best-effort stop and bounded wait can finish before the terminal state is written; spawn tasks keep their 2 s budget. An unconfirmed outcome is stated as such.
|
|
18
|
+
- `initialize()` appends "stop unconfirmed + please check manually" when archiving GUI leftovers and sets `guiResidualUnconfirmed`; an unreadable profile is conservatively treated as GUI.
|
|
19
|
+
- `abortTerminal()` (the competing writer in the shutdown race) now persists `guiStop` plus the structured fields on both branches, and derives the window name from `profile.displayName` (the old hard-coded mapping reported zcode/qoder as "Codex", itself a distortion). `runRes.guiStop` is persisted for every agent instead of only qoder, which previously lost the adapter's stop result in the shutdown race.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- New structured `TaskMeta` fields `interruptedCleanStop` (whether the stop is **confirmed**) and `guiResidualUnconfirmed` (pending manual confirmation after restart archiving). The `query_task` / `list_tasks` meta block gains `guiStopUnconfirmed`, giving the orchestrator one structured reason not to re-dispatch.
|
|
24
|
+
- A manual acknowledgement path with **no new tool**: calling `cancel_task` on a terminal GUI task clears `guiResidualUnconfirmed` and appends a `gui_residual_acknowledged` event, **without changing the terminal status or errorType**.
|
|
25
|
+
- New config `config.json` → `shutdown.guiStopWaitMs` (default 15000): the global upper bound for the GUI stop wait on shutdown, decoupled from `gui.cancelWaitMs` (the cancellation path).
|
|
26
|
+
|
|
27
|
+
### Tests
|
|
28
|
+
|
|
29
|
+
- New unit cases `test/unit/gui-stop-disclosure.test.ts` (three-state disclosure, window-name derivation with the "never strip to empty" fallback, and the guarantee that no input produces "the process has been terminated").
|
|
30
|
+
- New integration cases `test/integration/gui-shutdown-interrupt.test.ts` (the `idle=true` / `idle=false` / no-stop-result shutdown paths, restart archiving plus the `cancel_task` acknowledgement, and spawn tasks being unaffected by the GUI branching).
|
|
31
|
+
- `config-hotreload` gained default and override cases for `guiStopWaitMs`; the running-cancellation case in `codex-flow` now asserts the structured fields as well.
|
|
32
|
+
|
|
33
|
+
## [0.5.8] - 2026-09-23
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **The four primary documents were rewritten item by item against the code** (bilingual README / HANDOFF / bilingual ARCHITECTURE). The audit covered `src/mcp/`, `src/tasks/`, `src/loop/`, `src/agents/` (all five GUI adapters), `src/verify/`, `src/visual/`, `src/config/` and `src/util/`:
|
|
38
|
+
- **Acceptance stage order fixed**: the built-in `git-diff-check` runs **before** the configured checks, and the visual stage runs **after** the command checks (the old text had it reversed).
|
|
39
|
+
- **The real boundary of `visual.enabled=false`**: snapshot freezing and integrity checking run **unconditionally**, independent of `enabled`; `enabled` only decides whether screenshots/specs/content judgement actually run — which is exactly what detects edits to the acceptance config or baselines.
|
|
40
|
+
- **Tool result contract fixed**: `prepare_visual_baseline` / `approve_visual_baseline` success results also carry no meta block, and neither does any tool's error result (the old text claimed only `get_task_report` was an exception).
|
|
41
|
+
- **All five agent tables now include Qoder CN**: the agent list, driver list, `endReason` table, `needsUserKind` table, cancellation-capability table, registry special discovery branches, GUI instance lifecycle and test-layer descriptions were corrected from "four drivers" to "five", and Qoder CN's execution order and completion criterion were added to the architecture document.
|
|
42
|
+
- **`endReason` / `needsUserKind` verified value by value**: added Kimi Code's `system_permission` / `setup_recovery`; noted that Qoder CN is the only adapter able to emit all six kinds and that it **never emits `idle_timeout`** (static screen without this-turn evidence → `needs_user(setup_recovery)`); noted that Codex has no `close_existing_instance` path because `needsClose` never returns true; and that ZCode and TraeWork never click a stop button and never report `guiStop`.
|
|
43
|
+
- **Cancellation semantics** are now split per adapter capability; **checkpoints** now state that `qoder-session.json` is the only persisted checkpoint (with all four phases and their read/write points) while the rest keep in-memory state.
|
|
44
|
+
- Corrected the **Kimi Code tier domain** (README front-page example and milestone text changed from `Low`/`High`/`Max` to `低/low`, `高/high`, `max`, `on`, `off`), the **test baseline** (776 tests/73 files → 826 passed / 12 skipped across 81 files, broken down as unit 54 / integration 26 / protocol 1) and the **runtime dependency licence table** (Apache-2.0 and ISC entries added, "all MIT" corrected).
|
|
45
|
+
- The architecture document gained a callout for **declared-but-unused profile fields** (`gui.windowMode`, `gui.modelRequired`, ZCode's `gui.stallTimeoutMs` / `gui.cancelWaitMs`), and the known-gaps list now carries two real debts instead of a stale tool-count note.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **Distribution gap**: `scripts/probe-traework.mjs` was not in `package.json`'s `files`, yet the docs tell users to run that probe — npm consumers could not get it. It is now shipped, and the `probe:traework` / `probe:zcode` / `probe:codex` npm scripts were added (previously only `probe:kimicode` / `probe:qoder` existed although the corresponding scripts had long been published).
|
|
50
|
+
- **Source comments and strings**: the "9 tools" header comments in `src/mcp/handlers.ts` and `src/server.ts` now say 11; the MCP `instructions` string mentions `kimicode` / `qoder`; `src/agents/gui-instance.ts` lists all five users; `src/tasks/task.ts` fixes a Kimi Code comment that sat on the Qoder fields and notes that `qoderTurnId` has no writer. **No runtime behaviour change.**
|
|
51
|
+
|
|
52
|
+
### Tests
|
|
53
|
+
|
|
54
|
+
- Full regression: **826 passed / 12 skipped** (Windows 10 x64, Node 24.18.0); typecheck and lint pass; `npm pack` (231 files) verified to include all five probe scripts.
|
|
55
|
+
- Documentation link check: the four primary documents plus the skill docs contain **255 relative links, 0 broken**.
|
|
56
|
+
|
|
11
57
|
## [0.5.7] - 2026-09-22
|
|
12
58
|
|
|
13
59
|
### Changed
|
|
@@ -829,6 +875,7 @@ project → pick model and reasoning level → send instructions → run detecti
|
|
|
829
875
|
|
|
830
876
|
---
|
|
831
877
|
|
|
878
|
+
[0.5.8]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.5.7...v0.5.8
|
|
832
879
|
[0.5.7]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.5.6...v0.5.7
|
|
833
880
|
[0.5.6]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.5.5...v0.5.6
|
|
834
881
|
[0.5.5]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.5.4...v0.5.5
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,52 @@
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.5.9] - 2026-09-23
|
|
11
|
+
|
|
12
|
+
### 修复
|
|
13
|
+
|
|
14
|
+
- **server 退出 / 重启归档路径的 GUI 终态不再谎报已停止**([issue #14](https://github.com/lanlan0811/tianshu-mcp/issues/14)):GUI agent 是外部桌面应用,server 对其进程**没有所有权**,abort 后适配器至多"尽力点击界面停止",旧实现统一写「server 退出,进程已终止」是把只对 spawn 子进程成立的断言套到了 GUI 上——编排器已死、GUI 可能仍在改用户项目、且无人观察。
|
|
15
|
+
- `persistInterrupted()` 按 `driver` 分流:spawn 类维持原文案;GUI 类按适配器回报的 `guiStop` 如实落「已确认 … 内运行停止」/「未确认停止,窗口中的任务可能仍在继续,请人工打开 … 确认无残留运行」/「无停止结果可确认」(ZCode、TraeWork 不点停止、不回传 `guiStop`)。
|
|
16
|
+
- `shutdownInterrupt()` 为 GUI 任务提供**全局共享**的 `guiStopWaitMs` 预算(新配置,默认 15000,`config.json` 可覆盖),让"尽力停止 + 有界等待"跑完再落终态;spawn 类保持原 2s 预算。到期未确认时如实标注。
|
|
17
|
+
- `initialize()` 归档重启遗留任务时,GUI 类追加「未确认停止 + 请人工检查」并置 `guiResidualUnconfirmed`;profile 不可读时保守按 GUI 处理。
|
|
18
|
+
- `abortTerminal()`(shutdown 竞态中的另一写方)两个分支都落 `guiStop` 与结构化字段;窗口名改由 `profile.displayName` 派生(旧实现把 zcode/qoder 一律写成 "Codex",本身即失真);`runRes.guiStop` 改为对所有 agent 落盘(此前只写 qoder,其余 GUI agent 的停止结果在 shutdown 竞态里丢失)。
|
|
19
|
+
|
|
20
|
+
### 新增
|
|
21
|
+
|
|
22
|
+
- `TaskMeta` 新增结构化字段 `interruptedCleanStop`(是否**已确认**停止)与 `guiResidualUnconfirmed`(重启归档的待人工确认标记);`query_task` / `list_tasks` 的 meta 块新增 `guiStopUnconfirmed`,编排方据此禁止直接重派。
|
|
23
|
+
- 人工确认路径(**不新增工具**):对已终态的 GUI 任务调用 `cancel_task`,可清除 `guiResidualUnconfirmed` 并追加 `gui_residual_acknowledged` 事件,**不改终态与 errorType**。
|
|
24
|
+
- 新配置 `config.json` → `shutdown.guiStopWaitMs`(默认 15000):server 关闭时 GUI 停止等待的全局上限,与 `gui.cancelWaitMs`(取消路径)解耦。
|
|
25
|
+
|
|
26
|
+
### 测试
|
|
27
|
+
|
|
28
|
+
- 新增单元用例 `test/unit/gui-stop-disclosure.test.ts`(三态披露判定、窗口名派生与"剥空退回原名"、任何输入都不得出现「进程已终止」)。
|
|
29
|
+
- 新增集成用例 `test/integration/gui-shutdown-interrupt.test.ts`(shutdown 的 `idle=true`/`idle=false`/无停止结果三条路径、重启归档 + `cancel_task` 确认清除、spawn 类不受 GUI 分流影响)。
|
|
30
|
+
- `config-hotreload` 补 `guiStopWaitMs` 默认值与覆盖用例;`codex-flow` 的运行中取消用例同步为结构化断言。
|
|
31
|
+
|
|
32
|
+
## [0.5.8] - 2026-09-23
|
|
33
|
+
|
|
34
|
+
### 变更
|
|
35
|
+
|
|
36
|
+
- **四份主文档按代码逐项核对重写**(README 双语 / HANDOFF / ARCHITECTURE 双语),核对范围覆盖 `src/mcp/`、`src/tasks/`、`src/loop/`、`src/agents/`(五个 GUI 适配器全部)、`src/verify/`、`src/visual/`、`src/config/`、`src/util/`:
|
|
37
|
+
- **验收阶段顺序修正**:内置 `git-diff-check` 在配置的检查项**之前**执行,视觉检查在命令检查**之后**执行(旧文档写反)。
|
|
38
|
+
- **`visual.enabled=false` 的真实边界**:快照冻结与完整性核对**恒定执行**,与 `enabled` 无关;`enabled` 只决定是否真的跑截图/规格/内容判定(这正是检出「验收配置或基准被改」的机制)。
|
|
39
|
+
- **工具返回契约修正**:`prepare_visual_baseline` / `approve_visual_baseline` 的成功结果同样不带 meta 块,任何工具的错误结果也不带(旧文档称仅 `get_task_report` 例外)。
|
|
40
|
+
- **五份 agent 表补齐 Qoder CN**:agent 列表、driver 列表、`endReason` 表、`needsUserKind` 表、取消能力表、registry 特殊探测分支、GUI 实例生命周期、测试分层说明全部由「四个 driver」更正为「五个」,并把 Qoder CN 执行顺序与完成判定写入架构文档。
|
|
41
|
+
- **`endReason` / `needsUserKind` 逐值核对**:补 Kimi Code 的 `system_permission` / `setup_recovery`;注明 Qoder CN 是唯一能产出全部 6 种 kind 的适配器且**不产出 `idle_timeout`**(静止而无本轮完成证据 → `needs_user(setup_recovery)`);注明 Codex 因 `needsClose` 从不返回 true 而没有 `close_existing_instance` 路径;注明 ZCode 与 TraeWork 不点界面停止按钮也不回传 `guiStop`。
|
|
42
|
+
- **取消语义**改为按适配器能力分列;**检查点**明确 `qoder-session.json` 是唯一持久化检查点(含四个 phase 的读写点),其余为内存态判断。
|
|
43
|
+
- 修正 **Kimi Code 档位取值域**(README 首页示例与里程碑文字由 `Low`/`High`/`Max` 改为 `低/low`、`高/high`、`max`、`on`、`off`)、**测试基线**(776 项/73 文件 → 826 passed / 12 skipped、81 文件,并给出单元 54 / 集成 26 / 协议 1 的构成)、**运行时依赖许可表**(补 Apache-2.0 与 ISC 项,纠正「均为 MIT」)。
|
|
44
|
+
- 架构文档新增「声明了但当前无消费方」的 profile 字段提示框(`gui.windowMode`、`gui.modelRequired`、ZCode 的 `gui.stallTimeoutMs` / `gui.cancelWaitMs`),并把已知缺口更新为两条真实技术债。
|
|
45
|
+
|
|
46
|
+
### 修复
|
|
47
|
+
|
|
48
|
+
- **分发缺口**:`scripts/probe-traework.mjs` 此前不在 `package.json` 的 `files` 中,而文档要求用户运行该探针——npm 包内拿不到该脚本。现已纳入分发,并补齐 `probe:traework` / `probe:zcode` / `probe:codex` 三个 npm script(此前只有 `probe:kimicode` / `probe:qoder`,而对应脚本早已随包发布)。
|
|
49
|
+
- **源码注释与描述**:`src/mcp/handlers.ts`、`src/server.ts` 头注「9 个工具」更正为 11;MCP `instructions` 补 `kimicode` / `qoder`;`src/agents/gui-instance.ts` 头注补全五个使用者;`src/tasks/task.ts` 修正贴在 Qoder 字段上的 Kimi Code 注释并注明 `qoderTurnId` 无写入方。**均无运行时行为变更。**
|
|
50
|
+
|
|
51
|
+
### 测试
|
|
52
|
+
|
|
53
|
+
- 全量 **826 passed / 12 skipped**(Windows 10 x64,Node 24.18.0);类型检查与 lint 通过;`npm pack`(231 文件)核验包含五个探针脚本。
|
|
54
|
+
- 文档相对链接检查:四份主文档 + 技能文档共 **255 条相对链接、0 条失效**。
|
|
55
|
+
|
|
10
56
|
## [0.5.7] - 2026-09-22
|
|
11
57
|
|
|
12
58
|
### 变更
|
|
@@ -724,6 +770,7 @@ Codex 桌面端改为 **GUI 驱动**:新增 `codex-gui` adapter,通过 MSIX
|
|
|
724
770
|
|
|
725
771
|
---
|
|
726
772
|
|
|
773
|
+
[0.5.8]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.5.7...v0.5.8
|
|
727
774
|
[0.5.7]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.5.6...v0.5.7
|
|
728
775
|
[0.5.6]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.5.5...v0.5.6
|
|
729
776
|
[0.5.5]: https://github.com/lanlan0811/tianshu-mcp/compare/v0.5.4...v0.5.5
|
package/README.en.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
# tianshu-mcp
|
|
10
10
|
|
|
11
|
-
Visual acceptance (since v0.5.0, with optional AI content validation since v0.5.4): [English guide](docs/visual-acceptance.en.md) · [Validation record](docs/visual-validation.en.md) · [Latest release notes](<docs/release-v0.5.
|
|
11
|
+
Visual acceptance (since v0.5.0, with optional AI content validation since v0.5.4): [English guide](docs/visual-acceptance.en.md) · [Validation record](docs/visual-validation.en.md) · [Latest release notes](<docs/release-v0.5.9.en.md>) · [All versions](CHANGELOG.en.md).
|
|
12
12
|
|
|
13
13
|
**Tianshu × AI-Agent orchestration MCP server**
|
|
14
14
|
|
|
@@ -35,14 +35,14 @@ Registered by Tianshu as a standard MCP server, it dispatches external AI-Agents
|
|
|
35
35
|
|
|
36
36
|
## What this is
|
|
37
37
|
|
|
38
|
-
Tianshu plays the role of the overall commander; this MCP server is the **scheduler + execution surface + objective acceptance gate**; the external AI-Agent (Codex / TraeWork / ZCode / Kimi Code GUI) is the "worker" that does the development.
|
|
38
|
+
Tianshu plays the role of the overall commander; this MCP server is the **scheduler + execution surface + objective acceptance gate**; the external AI-Agent (Codex / TraeWork / ZCode / Kimi Code / Qoder CN GUI) is the "worker" that does the development.
|
|
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
42
|
- **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
43
|
- **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
44
|
- **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.
|
|
45
|
-
- **Execution surfaces**: `driver: "gui"` selects an explicit, isolated Codex/TraeWork/ZCode/Kimi Code CDP adapter; `driver: "spawn"` runs an external CLI child process.
|
|
45
|
+
- **Execution surfaces**: `driver: "gui"` selects an explicit, isolated Codex/TraeWork/ZCode/Kimi Code/Qoder CN CDP adapter; `driver: "spawn"` runs an external CLI child process.
|
|
46
46
|
- **Project-less dispatch (ZCode, issue #12)**: `run_task`'s `projectPath` may be omitted — ZCode runs the task in its `default` workspace without registering/importing a project, collecting a Git baseline, or running project acceptance (the result is marked structurally as `verificationNotApplicable: "no_project"` and `verify_task`/`get_task_report` return a not-applicable explanation). The companion `allowCreateProject: false` stops dispatch before any import side effect when the target directory is unregistered. See the [ZCode CDP adapter](docs/zcode-cdp.en.md).
|
|
47
47
|
- **Scheduling discipline**: per-project serial queue + global concurrency cap (default 2, configurable).
|
|
48
48
|
- **Optional AI content validation (v0.5.4, off by default)**: validates whether the **content** of an image or page screenshot matches an expectation you declare explicitly. Judgement is fully **delegated to a local command you supply** (the MCP reads, stores, and forwards no keys and ships no model client), it **warns only** by default and can be upgraded to failing per rule, and it debounces with majority sampling plus a task-level cache; split votes or confidence below the threshold yield `uncertain`, which never gates and never triggers rework. Configuration and the command contract are in [visual acceptance](docs/visual-acceptance.en.md).
|
|
@@ -70,7 +70,7 @@ git clone https://github.com/lanlan0811/tianshu-mcp.git
|
|
|
70
70
|
cd tianshu-mcp
|
|
71
71
|
npm ci
|
|
72
72
|
npm run build # sync-version + tsc → dist/
|
|
73
|
-
npm test #
|
|
73
|
+
npm test # 826 passed / 12 skipped (838 tests, 81 files: unit/integration/protocol plus 3 real-browser files skipped by design)
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
### Install the npm package
|
|
@@ -158,14 +158,27 @@ For Kimi Code, `model` is required and takes the UI model name directly, while `
|
|
|
158
158
|
|
|
159
159
|
```text
|
|
160
160
|
run_task(projectPath=D:/xxx/my-app, agentId=kimicode, task="Implement `./plan.md`",
|
|
161
|
-
model=K3, reasoningLevel=
|
|
161
|
+
model=K3, reasoningLevel=high, autoVerify=true, autoFixRounds=2)
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
> Kimi Code is a plain Electron install (measured 1.0.2): injecting `--remote-debugging-port` and driving it over CDP is enough — no MSIX COM activation.
|
|
165
165
|
> **The model menu, thinking tiers and execution-mode menu render in a separate `Kimi Browser Overlay` renderer window**, while the workspace menu and the "switch model" dialog stay in the main window.
|
|
166
166
|
> A task must bind a workspace folder (**project-less dispatch is not supported**); an unregistered workspace is imported through the native "add workspace" dialog.
|
|
167
|
-
> `reasoningLevel`: official models use
|
|
168
|
-
> and a tier the UI does not render fails loudly before sending. The `mode` parameter is not supported. See the [Kimi Code CDP adapter](docs/kimi-cdp.en.md).
|
|
167
|
+
> `reasoningLevel`: official models use `低/low`, `高/high` and `max`; **unofficial models (e.g. `stepfun/step-3.7-flash:free`) only have `on` / `off`**.
|
|
168
|
+
> The domain deliberately excludes `中`/`medium`; tiers are validated against the label set the UI actually renders, and a tier the UI does not render fails loudly before sending. The `mode` parameter is not supported. See the [Kimi Code CDP adapter](docs/kimi-cdp.en.md).
|
|
169
|
+
|
|
170
|
+
For Qoder CN, an existing `projectPath` and a readable `planDoc` are mandatory:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
run_task(projectPath=D:/xxx/my-app, agentId=qoder, planDoc=./plans/development.md,
|
|
174
|
+
modelSource=custom, model=<UI model name>, reasoningLevel=极高,
|
|
175
|
+
task="Implement the project per the plan", autoVerify=true, autoFixRounds=3)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
> **Qoder CN only** (the international edition or a similarly titled window is not a substitute). `modelSource=default|custom` disambiguates identical names across the default/custom groups;
|
|
179
|
+
> the thinking tier is saved in Model Management as a **global Qoder preference** (never restored afterwards) and read back by reopening the dialog, and an unsupported tier fails before sending;
|
|
180
|
+
> the permission mode is retained as-is. Both automatic and manual repair **write the plan first**, then send its filename, full path and complete text to the original conversation.
|
|
181
|
+
> macOS is `research` and dispatch is disabled. See the [Qoder CN adapter](docs/qoder-cdp.en.md).
|
|
169
182
|
|
|
170
183
|
## Tool surface (11 tools)
|
|
171
184
|
|
|
@@ -176,7 +189,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=kimicode, task="Implement `./plan.md
|
|
|
176
189
|
| `query_task` | read | Poll status / progress / log tail |
|
|
177
190
|
| `list_tasks` | read | Filtered history of tasks |
|
|
178
191
|
| `get_task_report` | read | Full text of a verification round's report (`report.md`) |
|
|
179
|
-
| `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 |
|
|
192
|
+
| `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 |
|
|
180
193
|
| `verify_task` | read | Run one verification pass on a task/project path (no source changes) |
|
|
181
194
|
| `rework_task` | write + approval | Manual rework (feed the failure report back to the same agent) |
|
|
182
195
|
| `get_profiles` | read | Inspect agent adapters and executable discovery results |
|
|
@@ -215,6 +228,8 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
|
|
|
215
228
|
| [docs/codex-windows-smoke.en.md](docs/codex-windows-smoke.en.md) | Codex Windows hardware record (incl. verify-fail → auto plan → repair-pass loop) |
|
|
216
229
|
| [docs/release-v0.3.4.en.md](<docs/release-v0.3.4.en.md>) | v0.3.4 release notes (ZCode project/model read-back, initialization recovery, session dispatch confirmation, issues #8/#9/#10) |
|
|
217
230
|
| [docs/qoder-cdp.en.md](docs/qoder-cdp.en.md) | Qoder CN GUI driver: installation discovery and instance reuse, full-path workspaces with native import, `modelSource` and global Model Management reasoning tiers, send/answer checkpoints, liveness judging and same-session repair, hardware evidence and uncovered items |
|
|
231
|
+
| [docs/release-v0.5.9.en.md](<docs/release-v0.5.9.en.md>) | v0.5.9 release notes (truthful GUI terminal state on server exit / restart archiving: wording branches on `guiStop`, the bounded `shutdown.guiStopWaitMs` wait, structured pending fields and the manual acknowledgement entry point; issue #14) |
|
|
232
|
+
| [docs/release-v0.5.8.en.md](<docs/release-v0.5.8.en.md>) | v0.5.8 release notes (the four primary documents rewritten against the code, plus the missing TraeWork probe and three probe scripts; no runtime change) |
|
|
218
233
|
| [docs/release-v0.5.7.en.md](<docs/release-v0.5.7.en.md>) | v0.5.7 release notes (orchestration skill docs rewritten against the code: parameter matrix, default precedence, tier correction and the qoder section; no runtime change) |
|
|
219
234
|
| [docs/release-v0.5.6.en.md](<docs/release-v0.5.6.en.md>) | v0.5.6 release notes (Qoder CN GUI adapter, hardware acceptance scope, macOS research boundary) |
|
|
220
235
|
| [docs/release-v0.5.5.en.md](<docs/release-v0.5.5.en.md>) | v0.5.5 release notes (Kimi Code GUI adapter: dual renderer processes, full-path workspace binding with native import, three-stage model selection and tier validation, run detection and recovery) |
|
|
@@ -265,7 +280,7 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
|
|
|
265
280
|
- **Engineering / CI** ✅
|
|
266
281
|
- GitHub Actions: `CI` (`build-test` ubuntu/windows/macos × Node 20/22/24 + `pack-check`, plus a `visual-browser` real-browser matrix ubuntu/windows/macos-15-intel/macos-15 × Node 20/22/24, all green with the v0.5.1 tag) and `Release` (tag-triggered) both green
|
|
267
282
|
- Skill self-install verified idempotent on this machine's real `~/.rivet/skills/tianshu-mcp`
|
|
268
|
-
- npm package name `tianshu-mcp` published continuously since v0.1.1 (currently `0.5.
|
|
283
|
+
- npm package name `tianshu-mcp` published continuously since v0.1.1 (currently `0.5.9`)
|
|
269
284
|
- **Real Tianshu host integration (DoD #6)** ✅ (2026-09-07)
|
|
270
285
|
- Configured the local mode in the real `D:\Tianshu` desktop host `mcp.servers` → sidecar reported `MCP: 2 servers connected, 10 tools` (including this server's 8 tools), spawned the child process and connected over stdio
|
|
271
286
|
- Exposed and fixed a skill-install source-path bug (fileURLToPath, commit 55cf2d0)
|
|
@@ -359,7 +374,7 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
|
|
|
359
374
|
- **M24 — Kimi Code GUI adapter (fourth GUI agent) + v0.5.5** (2026-09-20) — **764 tests**
|
|
360
375
|
- **Dual-renderer CDP driver**: the model / thinking-tier / execution-mode menus render in the separate `Kimi Browser Overlay` window while the workspace menu and the "switch model" dialog stay in the main window
|
|
361
376
|
- **Full-path workspace binding**: an unregistered directory is imported through the native "add workspace" dialog (Win32 coordinate clicks plus `WM_SETTEXT`/`WM_GETTEXT` read-back), and same-name/different-directory cases fail closed
|
|
362
|
-
- **Three-stage model selection and tier validation**: pill read-back → overlay shortcut menu → "more models…" dialog; tiers are validated against **the set the UI actually renders** (official
|
|
377
|
+
- **Three-stage model selection and tier validation**: pill read-back → overlay shortcut menu → "more models…" dialog; tiers are validated against **the set the UI actually renders** (official models `低/low`, `高/high`, `max`; unofficial models only `on`/`off`), and a tier the UI does not render fails before sending
|
|
363
378
|
- **Run detection and recovery**: `button.stop` / `send.is-starting` are the authoritative signals; the six `needs_user` kinds recover via `continue_task`, and an uncertain send is never repeated
|
|
364
379
|
- See the [Kimi Code guide](docs/kimi-cdp.en.md) and the [v0.5.5 release notes](<docs/release-v0.5.5.en.md>)
|
|
365
380
|
- **M25 — Qoder CN GUI adapter (fifth GUI agent) + v0.5.6** (2026-09-22) — **826 tests**
|
|
@@ -375,6 +390,17 @@ Use `server.log` when troubleshooting connections; do not treat stderr output it
|
|
|
375
390
|
- **Kimi Code tier domain corrected** (`低/low`, `高/high`, `max`, `on`, `off` — deliberately without `中`/`medium`) and the qoder section completed (`modelSource` disambiguation, tier saved then read back, checkpoints that prevent resends, repair writing a plan before sending its full text, macOS dispatch disabled)
|
|
376
391
|
- **New `needsUserKind` × agent × `continue_task` matrix** and an `agentEndReason` → terminal-state mapping, plus `project_not_registered`/`unsupported_platform`/`qoder_error` and the new meta fields
|
|
377
392
|
- See the [v0.5.7 release notes](<docs/release-v0.5.7.en.md>)
|
|
393
|
+
- **M27 — The four primary documents rewritten against the code + packaging fix + v0.5.8** (2026-09-23) — **826 tests** (no runtime change)
|
|
394
|
+
- **Item-by-item audit of the four primary documents** (bilingual README / HANDOFF / bilingual ARCHITECTURE): corrected the **acceptance stage order** (the built-in `git-diff-check` runs before the configured checks and the visual stage after them), the fact that **`visual.enabled=false` still freezes and compares snapshots**, and the **tool result contract** (the two baseline tools and every error result carry no meta block either)
|
|
395
|
+
- **All five agent tables now include Qoder CN** (driver list, `endReason`, `needsUserKind`, cancellation capability, registry discovery branches, instance lifecycle), noting that Qoder CN is the only adapter able to emit all six wait kinds and that it **never emits `idle_timeout`**
|
|
396
|
+
- Corrected the Kimi Code tier domain, the test baseline (826 passed / 12 skipped across 81 files) and the runtime dependency licence table (Apache-2.0 / ISC), and added a callout for **declared-but-unused profile fields**
|
|
397
|
+
- **Fixed a distribution gap**: `scripts/probe-traework.mjs` was not shipped although the docs tell users to run it → added to `files`, plus the missing `probe:traework` / `probe:zcode` / `probe:codex` scripts
|
|
398
|
+
- See the [v0.5.8 release notes](<docs/release-v0.5.8.en.md>)
|
|
399
|
+
- **M28 — Truthful GUI terminal state (server exit / restart archiving) + v0.5.9** (2026-09-23) — 9 new unit cases + 5 new integration cases (issue #14)
|
|
400
|
+
- **No more false claims**: a GUI agent is an external desktop application and the server owns no process for it, so `persistInterrupted()` and `initialize()` now branch honestly on `guiStop` (confirmed / unconfirmed / no stop result) and **never** write "the process has been terminated", which only holds for spawn children
|
|
401
|
+
- **Bounded wait**: the new `shutdown.guiStopWaitMs` (15 s default, one shared budget) lets the best-effort stop finish before the terminal state is written; both branches of `abortTerminal()` now persist `guiStop` plus the structured fields, and the window name is derived from `profile.displayName`
|
|
402
|
+
- **Manual acknowledgement entry point**: new `TaskMeta.interruptedCleanStop` / `guiResidualUnconfirmed` and the meta-block `guiStopUnconfirmed`; calling `cancel_task` on a terminal GUI task clears the pending marker (no new tool, terminal status unchanged)
|
|
403
|
+
- See the [v0.5.9 release notes](<docs/release-v0.5.9.en.md>)
|
|
378
404
|
|
|
379
405
|
## Agent support status
|
|
380
406
|
|
|
@@ -451,7 +477,7 @@ Behavior and limits:
|
|
|
451
477
|
|
|
452
478
|
| Document | Content |
|
|
453
479
|
|---|---|
|
|
454
|
-
| [CHANGELOG.en.md](<CHANGELOG.en.md>) | Version history (v0.1.0 → v0.5.
|
|
480
|
+
| [CHANGELOG.en.md](<CHANGELOG.en.md>) | Version history (v0.1.0 → v0.5.9) |
|
|
455
481
|
| [CONTRIBUTING.en.md](CONTRIBUTING.en.md) | Dev setup, conventions, commit/release flow, adding an agent |
|
|
456
482
|
| [SECURITY.en.md](SECURITY.en.md) | Security model (zero credentials / command whitelist / process & desktop-automation boundaries) and private reporting |
|
|
457
483
|
| [CODE_OF_CONDUCT.en.md](CODE_OF_CONDUCT.en.md) | Contributor Code of Conduct |
|
|
@@ -510,13 +536,17 @@ The software is provided **"AS IS"**, without warranties or conditions of any ki
|
|
|
510
536
|
|
|
511
537
|
### Third-party dependency licenses
|
|
512
538
|
|
|
513
|
-
Runtime dependencies are all
|
|
539
|
+
Runtime dependencies are licensed as follows (all permissive and Apache-2.0 compatible):
|
|
514
540
|
|
|
515
541
|
| Dependency | License | Purpose |
|
|
516
542
|
|---|---|---|
|
|
517
543
|
| [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/sdk) | MIT | MCP protocol implementation |
|
|
518
544
|
| [`zod`](https://github.com/colinhacks/zod) | MIT | External input validation |
|
|
519
545
|
| [`cross-spawn`](https://github.com/moxystudio/node-cross-spawn) | MIT | Cross-platform child processes |
|
|
546
|
+
| [`puppeteer-core`](https://github.com/puppeteer/puppeteer) | Apache-2.0 | Drives the headless browser for visual acceptance |
|
|
547
|
+
| [`@puppeteer/browsers`](https://github.com/puppeteer/puppeteer) | Apache-2.0 | Installs and version-locks the managed Chrome/Edge |
|
|
548
|
+
| [`pixelmatch`](https://github.com/mapbox/pixelmatch) | ISC | Page-screenshot pixel comparison |
|
|
549
|
+
| [`sharp`](https://github.com/lovell/sharp) (optional) | Apache-2.0 | Image decoding and spec validation; the visual module blocks explicitly when it is missing |
|
|
520
550
|
|
|
521
551
|
Development dependencies (TypeScript, ESLint, Prettier, Vitest, Vite, tsx, etc.) follow their own open-source licenses and are not distributed with the npm package.
|
|
522
552
|
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
# tianshu-mcp
|
|
10
10
|
|
|
11
|
-
视觉验收(v0.5.0 起,含 v0.5.4 可选 AI 内容校验):[中文指南](docs/visual-acceptance.md) · [验证记录](docs/visual-validation.md) · [最新发布说明](<docs/release-v0.5.
|
|
11
|
+
视觉验收(v0.5.0 起,含 v0.5.4 可选 AI 内容校验):[中文指南](docs/visual-acceptance.md) · [验证记录](docs/visual-validation.md) · [最新发布说明](<docs/release-v0.5.9.md>) · [全部版本](CHANGELOG.md)。
|
|
12
12
|
|
|
13
13
|
**天枢 × AI-Agent 编排 MCP server**
|
|
14
14
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
## 这是什么
|
|
37
37
|
|
|
38
|
-
天枢的角色是总指挥;本 MCP server 是**调度层 + 执行面 + 客观验收仪**;外部 AI-Agent(Codex / TraeWork / ZCode / Kimi Code GUI)是执行开发的「工人」。
|
|
38
|
+
天枢的角色是总指挥;本 MCP server 是**调度层 + 执行面 + 客观验收仪**;外部 AI-Agent(Codex / TraeWork / ZCode / Kimi Code / Qoder CN GUI)是执行开发的「工人」。
|
|
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`)。
|
|
@@ -70,7 +70,7 @@ git clone https://github.com/lanlan0811/tianshu-mcp.git
|
|
|
70
70
|
cd tianshu-mcp
|
|
71
71
|
npm ci
|
|
72
72
|
npm run build # sync-version + tsc → dist/
|
|
73
|
-
npm test #
|
|
73
|
+
npm test # 826 passed / 12 skipped(838 项,81 个测试文件:单元/集成/协议 + 3 个真实浏览器文件按设计 skip)
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
### 安装 npm 包
|
|
@@ -154,14 +154,27 @@ ZCode 提问、需要登录、旧实例无 CDP、系统权限不足,或自动
|
|
|
154
154
|
|
|
155
155
|
```text
|
|
156
156
|
run_task(projectPath=D:/xxx/my-app, agentId=kimicode, task=「按 `./plan.md` 完成开发」,
|
|
157
|
-
model=K3, reasoningLevel=
|
|
157
|
+
model=K3, reasoningLevel=high, autoVerify=true, autoFixRounds=2)
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
> Kimi Code 为普通 Electron 安装(实测 1.0.2),以 `--remote-debugging-port` 注入后经 CDP 驱动,无需 MSIX COM 激活。
|
|
161
161
|
> **模型菜单 / 思考档位 / 执行模式菜单渲染在独立的 `Kimi Browser Overlay` 浮层窗口**,工作区菜单与「切换模型」对话框仍在主窗口。
|
|
162
162
|
> 任务必须绑定工作区文件夹(**不支持无项目派发**);未登记的工作区会经原生「添加工作区」对话框导入。
|
|
163
|
-
> `reasoningLevel`:官方模型为
|
|
164
|
-
>
|
|
163
|
+
> `reasoningLevel`:官方模型为 `低/low`、`高/high`、`max`;**非官方模型(如 `stepfun/step-3.7-flash:free`)只有 `on` / `off`**,
|
|
164
|
+
> 取值域刻意不含 `中`/`medium`;档位以界面实际渲染的标签集合校验,传了界面不存在的档位会在发送前响亮报错。`mode` 参数不支持。详见 [Kimi Code CDP 适配器](docs/kimi-cdp.md)。
|
|
165
|
+
|
|
166
|
+
驱动 Qoder CN 时必须提供已有 `projectPath` 与可读 `planDoc`:
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
run_task(projectPath=D:/xxx/my-app, agentId=qoder, planDoc=./plans/development.md,
|
|
170
|
+
modelSource=custom, model=<界面模型名>, reasoningLevel=极高,
|
|
171
|
+
task=「按计划实现项目」, autoVerify=true, autoFixRounds=3)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
> **仅 Qoder CN**(国际版或同名窗口不算)。`modelSource=default|custom` 用于消除「默认/自定义」两组同名;
|
|
175
|
+
> 思考等级经「模型管理」保存为 **Qoder 全局偏好**(任务结束不还原)并重新打开回读,不支持的档位在发送前报错;
|
|
176
|
+
> 权限模式沿用当前设置。自动与手动返修都**先落修复计划**,再把文件名、完整路径与全文发回原会话。
|
|
177
|
+
> macOS 为 `research` 且禁止派发。详见 [Qoder CN 适配器](docs/qoder-cdp.md)。
|
|
165
178
|
|
|
166
179
|
## 工具面(11 个)
|
|
167
180
|
|
|
@@ -172,7 +185,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=kimicode, task=「按 `./plan.md`
|
|
|
172
185
|
| `query_task` | read | 轮询状态 / 进度 / 日志尾 |
|
|
173
186
|
| `list_tasks` | read | 历史任务过滤列表 |
|
|
174
187
|
| `get_task_report` | read | 某轮验收报告全文(`report.md`) |
|
|
175
|
-
| `cancel_task` | write + 审批 | 取消运行中任务:CLI agent kill 进程树;GUI agent 经 CDP 点击停止并在 `gui.cancelWaitMs`(默认 15s)内有界等待 GUI
|
|
188
|
+
| `cancel_task` | write + 审批 | 取消运行中任务:CLI agent kill 进程树;GUI agent 经 CDP 点击停止并在 `gui.cancelWaitMs`(默认 15s)内有界等待 GUI 空闲,未确认停止时终态明示。对已终态的 GUI 任务,本调用兼任人工确认入口——核实窗口无残留运行后调用可清除 `guiStopUnconfirmed` 待确认标记 |
|
|
176
189
|
| `verify_task` | read | 对任务/项目路径做一次验收(不改源码) |
|
|
177
190
|
| `rework_task` | write + 审批 | 手动返修(把失败报告喂回同一 agent) |
|
|
178
191
|
| `get_profiles` | read | 查看 agent 适配与可执行探测结果 |
|
|
@@ -212,6 +225,8 @@ run_task(projectPath=D:/xxx/my-app, agentId=kimicode, task=「按 `./plan.md`
|
|
|
212
225
|
| [docs/kimi-cdp.md](docs/kimi-cdp.md) | Kimi Code GUI 驱动:双渲染进程(主窗口 + `Kimi Browser Overlay`)、工作区完整路径绑定与原生对话框导入、模型三级选择与思考档位、执行模式、运行检测与排障 |
|
|
213
226
|
| [docs/codex-windows-smoke.md](docs/codex-windows-smoke.md) | Codex Windows 真机验收记录(含验收失败→自动生成计划→返修通过闭环) |
|
|
214
227
|
| [docs/qoder-cdp.md](docs/qoder-cdp.md) | Qoder CN GUI 驱动:安装发现与实例复用、完整路径工作区与原生导入、`modelSource` 与模型管理全局思考等级、发送/答题检查点、运行判定与原会话返修、真机证据与未覆盖项 |
|
|
228
|
+
| [docs/release-v0.5.9.md](<docs/release-v0.5.9.md>) | v0.5.9 发布说明(server 退出 / 重启归档的 GUI 终态如实化:按 `guiStop` 分流文案、`shutdown.guiStopWaitMs` 有界等待、结构化待确认字段与人工确认入口;issue #14) |
|
|
229
|
+
| [docs/release-v0.5.8.md](<docs/release-v0.5.8.md>) | v0.5.8 发布说明(四份主文档按代码逐项核对重写 + 补发 TraeWork 探针与三个 probe script;无运行时变更) |
|
|
215
230
|
| [docs/release-v0.5.7.md](<docs/release-v0.5.7.md>) | v0.5.7 发布说明(编排技能文档按代码实况重写:参数兼容矩阵、默认值优先级、档位修正与 qoder 章节;无运行时变更) |
|
|
216
231
|
| [docs/release-v0.5.6.md](<docs/release-v0.5.6.md>) | v0.5.6 发布说明(Qoder CN GUI 适配、真机验收范围与 macOS research 边界) |
|
|
217
232
|
| [docs/release-v0.5.5.md](<docs/release-v0.5.5.md>) | v0.5.5 发布说明(Kimi Code GUI 适配:双渲染进程、工作区完整路径绑定与原生导入、模型三级选择与档位校验、运行检测与恢复) |
|
|
@@ -266,7 +281,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=kimicode, task=「按 `./plan.md`
|
|
|
266
281
|
- **工程 / CI** ✅
|
|
267
282
|
- GitHub Actions:`CI`(`build-test` ubuntu/windows/macos × Node 20/22/24 + `pack-check`,另加 `visual-browser` 真实浏览器矩阵 ubuntu/windows/macos-15-intel/macos-15 × Node 20/22/24,随 v0.5.1 tag 全绿)与 `Release`(tag 触发)均绿
|
|
268
283
|
- 技能自检安装已在本机真实 `~/.rivet/skills/tianshu-mcp` 验证生效且幂等
|
|
269
|
-
- npm 包名 `tianshu-mcp` 自 v0.1.1 起持续发布(当前 `0.5.
|
|
284
|
+
- npm 包名 `tianshu-mcp` 自 v0.1.1 起持续发布(当前 `0.5.9`)
|
|
270
285
|
- **天枢宿主真实接入(DoD #6)** ✅(2026-09-07,[host-integration-record.md](docs/host-integration-record.md))
|
|
271
286
|
- 在真实 `D:\Tianshu` 桌面宿主 `mcp.servers` 配置本地模式 → sidecar `MCP: 2 servers connected, 10 tools`(含本 server 8 工具),spawn 子进程并 stdio 连通
|
|
272
287
|
- 实测暴露并修复技能安装源路径 bug(fileURLToPath,提交 55cf2d0)
|
|
@@ -360,7 +375,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=kimicode, task=「按 `./plan.md`
|
|
|
360
375
|
- **M24 — Kimi Code GUI 适配(第四个 GUI agent)+ v0.5.5**(2026-09-20)— **764 测试**
|
|
361
376
|
- **双渲染进程 CDP 驱动**:模型 / 思考档位 / 执行模式菜单渲染在独立的 `Kimi Browser Overlay` 浮层窗口,工作区菜单与「切换模型」对话框仍在主窗口
|
|
362
377
|
- **工作区完整路径绑定**:未登记目录经原生「添加工作区」对话框导入(Win32 坐标点击 + `WM_SETTEXT`/`WM_GETTEXT` 回读),同名不同目录一律 fail-closed
|
|
363
|
-
- **模型三级选择与档位校验**:pill 回读 → overlay 快捷菜单 →
|
|
378
|
+
- **模型三级选择与档位校验**:pill 回读 → overlay 快捷菜单 → 「更多模型…」对话框;档位按**界面实际渲染的集合**校验(官方模型 `低/low`、`高/high`、`max`,非官方模型仅 `on`/`off`),请求不到的档位在发送前报错
|
|
364
379
|
- **运行检测与恢复**:`button.stop` / `send.is-starting` 为权威信号;`needs_user` 六类由 `continue_task` 恢复,发布发送确认失败绝不重发
|
|
365
380
|
- 详见 [Kimi Code 文档](docs/kimi-cdp.md) 与 [v0.5.5 发布说明](<docs/release-v0.5.5.md>)
|
|
366
381
|
- **M25 — Qoder CN GUI 适配(第五个 GUI agent)+ v0.5.6**(2026-09-22)— **826 测试**
|
|
@@ -376,6 +391,17 @@ run_task(projectPath=D:/xxx/my-app, agentId=kimicode, task=「按 `./plan.md`
|
|
|
376
391
|
- **修正 Kimi Code 档位取值域**(`低/low`、`高/high`、`max`、`on`、`off`,刻意不含 `中`/`medium`)与 qoder 章节(`modelSource` 消歧、档位保存回读、检查点不重发、返修先落计划再回发全文、macOS 禁止派发)
|
|
377
392
|
- **新增 `needsUserKind` × agent × `continue_task` 行为矩阵**与 `agentEndReason` → 终态映射,补 `project_not_registered`/`unsupported_platform`/`qoder_error` 等错误码与 meta 新字段
|
|
378
393
|
- 详见 [v0.5.7 发布说明](<docs/release-v0.5.7.md>)
|
|
394
|
+
- **M27 — 四份主文档按代码实况重写 + 打包一致性修复 + v0.5.8**(2026-09-23)— **826 测试**(无运行时变更)
|
|
395
|
+
- **四份主文档按代码逐项核对**(README 双语 / HANDOFF / ARCHITECTURE 双语):修正**验收阶段顺序**(内置 `git-diff-check` 在配置检查项之前、视觉在命令检查之后)、**`visual.enabled=false` 仍会冻结核对快照**、**工具返回契约**(两个视觉基准工具与所有错误结果也不带 meta 块)
|
|
396
|
+
- **五份 agent 表补齐 Qoder CN**(driver 列表、`endReason`、`needsUserKind`、取消能力、registry 探测分支、实例生命周期),并注明 Qoder CN 是唯一能产出全部 6 种等待类型且**不产出 `idle_timeout`** 的适配器
|
|
397
|
+
- 修正 Kimi Code 档位取值域、测试基线(826 passed / 12 skipped、81 文件)、运行时依赖许可表(Apache-2.0 / ISC),并新增「声明了但无消费方」的 profile 字段提示
|
|
398
|
+
- **修复分发缺口**:`scripts/probe-traework.mjs` 未随包发布(文档却要求用户运行它)→ 纳入 `files`,并补齐 `probe:traework` / `probe:zcode` / `probe:codex` script
|
|
399
|
+
- 详见 [v0.5.8 发布说明](<docs/release-v0.5.8.md>)
|
|
400
|
+
- **M28 — GUI 终态如实化(server 退出 / 重启归档)+ v0.5.9**(2026-09-23)— 新增 9 单元 + 5 集成用例(issue #14)
|
|
401
|
+
- **不再谎报**:GUI agent 是外部桌面应用、server 对其进程无所有权,`persistInterrupted()` 与 `initialize()` 改为按 `guiStop` 如实分流(已确认停止 / 未确认停止 / 无停止结果),**绝不写只对 spawn 子进程成立的「进程已终止」**
|
|
402
|
+
- **有界等待**:新增 `shutdown.guiStopWaitMs`(默认 15s,全局共享预算)让「尽力停止 + 有界等待」跑完再落终态;`abortTerminal()` 两分支补齐 `guiStop` 与结构化字段,窗口名改由 `profile.displayName` 派生
|
|
403
|
+
- **人工确认入口**:新增 `TaskMeta.interruptedCleanStop` / `guiResidualUnconfirmed` 与 meta 块 `guiStopUnconfirmed`;对已终态 GUI 任务调用 `cancel_task` 可清除待确认标记(不新增工具,不改终态)
|
|
404
|
+
- 详见 [v0.5.9 发布说明](<docs/release-v0.5.9.md>)
|
|
379
405
|
|
|
380
406
|
## Agent 适配现状
|
|
381
407
|
|
|
@@ -453,7 +479,7 @@ run_task(projectPath=/path/to/项目, agentId=codex-cli, task="任务书", autoV
|
|
|
453
479
|
| 文档 | 内容 |
|
|
454
480
|
|---|---|
|
|
455
481
|
| [HANDOFF.md](HANDOFF.md) | 项目交接文档:当前状态快照、架构导览、硬性红线、已知限制、接手建议 |
|
|
456
|
-
| [CHANGELOG.md](<CHANGELOG.md>) | 版本变更日志(v0.1.0 → v0.5.
|
|
482
|
+
| [CHANGELOG.md](<CHANGELOG.md>) | 版本变更日志(v0.1.0 → v0.5.9) |
|
|
457
483
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | 开发环境、工程规范、提交与发布流程、如何新增 agent |
|
|
458
484
|
| [SECURITY.md](SECURITY.md) | 安全模型(凭证零管理/命令白名单/进程与桌面自动化边界)与私密报告渠道 |
|
|
459
485
|
| [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) | 贡献者行为准则 |
|
|
@@ -512,13 +538,17 @@ run_task(projectPath=/path/to/项目, agentId=codex-cli, task="任务书", autoV
|
|
|
512
538
|
|
|
513
539
|
### 第三方依赖许可
|
|
514
540
|
|
|
515
|
-
|
|
541
|
+
运行时依赖的许可如下(均为与 Apache-2.0 兼容的宽松许可):
|
|
516
542
|
|
|
517
543
|
| 依赖 | 许可 | 用途 |
|
|
518
544
|
|---|---|---|
|
|
519
545
|
| [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/sdk) | MIT | MCP 协议实现 |
|
|
520
546
|
| [`zod`](https://github.com/colinhacks/zod) | MIT | 外部输入校验 |
|
|
521
547
|
| [`cross-spawn`](https://github.com/moxystudio/node-cross-spawn) | MIT | 跨平台子进程 |
|
|
548
|
+
| [`puppeteer-core`](https://github.com/puppeteer/puppeteer) | Apache-2.0 | 视觉验收驱动无头浏览器 |
|
|
549
|
+
| [`@puppeteer/browsers`](https://github.com/puppeteer/puppeteer) | Apache-2.0 | 托管 Chrome/Edge 的安装与版本锁定 |
|
|
550
|
+
| [`pixelmatch`](https://github.com/mapbox/pixelmatch) | ISC | 页面截图像素比对 |
|
|
551
|
+
| [`sharp`](https://github.com/lovell/sharp)(optional) | Apache-2.0 | 图片解码与规格校验;缺失时视觉模块明确阻塞 |
|
|
522
552
|
|
|
523
553
|
开发依赖(TypeScript、ESLint、Prettier、Vitest、Vite、tsx 等)各自遵循其开源许可,且不随 npm 发布产物分发。
|
|
524
554
|
|
package/dist/config/schema.js
CHANGED
|
@@ -151,6 +151,18 @@ export const ServerConfigSchema = z.object({
|
|
|
151
151
|
autoInstall: z.boolean().default(true),
|
|
152
152
|
})
|
|
153
153
|
.default({}),
|
|
154
|
+
/**
|
|
155
|
+
* server 关闭路径的预算(issue #14)。
|
|
156
|
+
* `guiStopWaitMs`:`shutdownInterrupt()` 对 GUI agent 任务"尽力点击界面停止 + 有界等待空闲"
|
|
157
|
+
* 的**全局**等待上限(全部 GUI 任务共享一份预算,避免多任务串行拖长退出),到期仍无法确认时
|
|
158
|
+
* 终态如实写「未确认停止」。与 `gui.cancelWaitMs`(cancel_task 路径)解耦:cancel 由调用方
|
|
159
|
+
* 主动等待,shutdown 受进程退出时限约束,因此默认值单独可调。
|
|
160
|
+
*/
|
|
161
|
+
shutdown: z
|
|
162
|
+
.object({
|
|
163
|
+
guiStopWaitMs: z.number().int().positive().default(15_000),
|
|
164
|
+
})
|
|
165
|
+
.default({}),
|
|
154
166
|
});
|
|
155
167
|
/* ---------------- agent-profiles.json ---------------- */
|
|
156
168
|
export const ExecutableDiscoverySchema = z.object({
|
package/dist/loop/fix-loop.js
CHANGED
|
@@ -14,7 +14,7 @@ import { writeRepairPlan } from "./repair-plan.js";
|
|
|
14
14
|
import { writeCodexFixPlan } from "../agents/codex/fixplan.js";
|
|
15
15
|
import { buildFixPrompt } from "../agents/codex/input.js";
|
|
16
16
|
import { extractFailureEvidence } from "../agents/codex/verify.js";
|
|
17
|
-
import { isProjectWorkspace } from "../tasks/task.js";
|
|
17
|
+
import { isProjectWorkspace, guiAppNameOf, guiStopDisclosure, } from "../tasks/task.js";
|
|
18
18
|
import { freezeVisualSnapshot, checkVisualSnapshot } from "../visual/snapshot.js";
|
|
19
19
|
import { visualEvidence } from "../visual/report.js";
|
|
20
20
|
import { withVisualLock } from "../visual/lock.js";
|
|
@@ -136,8 +136,11 @@ export class TaskOrchestrator {
|
|
|
136
136
|
meta.actualModel = runRes.actualModel ?? meta.actualModel;
|
|
137
137
|
meta.actualReasoningLevel = runRes.actualReasoningLevel ?? meta.actualReasoningLevel;
|
|
138
138
|
meta.modelSource = runRes.actualModelSource ?? meta.modelSource;
|
|
139
|
-
meta.guiStop = runRes.guiStop;
|
|
140
139
|
}
|
|
140
|
+
// GUI 停止结果对所有 agent 落盘(issue #14):此前只写 qoder,导致其余 GUI agent
|
|
141
|
+
// 在 shutdown 竞态里丢失适配器已回报的停止结果,只能写"无停止结果可确认"。
|
|
142
|
+
if (runRes.guiStop)
|
|
143
|
+
meta.guiStop = runRes.guiStop;
|
|
141
144
|
if (runRes.session) {
|
|
142
145
|
// 会话锚点按 agent 分槽存放:zcodeSession* 与 kimicodeSession* 语义不同
|
|
143
146
|
// (旧快照里的 zcodeSession* 是 ZCode 会话,拿去 Kimi Code 里定位必然失败)。
|
|
@@ -326,8 +329,9 @@ export class TaskOrchestrator {
|
|
|
326
329
|
* - 用户取消(cancelRequestedAt 已置位 / abortSource=user / 排队中)→ cancelled
|
|
327
330
|
* - server 关闭/EOF/未显式取消的中止 → interrupted
|
|
328
331
|
* - 超时 → failed(timeout) + timeout_killed(委托 timeoutTerminal)
|
|
329
|
-
* guiStop(issue #6
|
|
330
|
-
* idle=false
|
|
332
|
+
* guiStop(issue #6 / #14):GUI 停止结果必须如实写进**终态文案与结构化字段**——
|
|
333
|
+
* `idle=false` 与"无停止结果"(ZCode/TraeWork 无停止能力、或未及尝试)都不得声称已停止。
|
|
334
|
+
* 窗口名按 profile 派生,不再按 agentId 硬编码(旧实现把 zcode/qoder 都写成 "Codex" 是失真的)。
|
|
331
335
|
*/
|
|
332
336
|
async abortTerminal(guiStop) {
|
|
333
337
|
if (this.done)
|
|
@@ -337,30 +341,56 @@ export class TaskOrchestrator {
|
|
|
337
341
|
}
|
|
338
342
|
this.done = true;
|
|
339
343
|
const meta = this.meta;
|
|
344
|
+
// 落结构化停止结果:manager 的 persistInterrupted 在 shutdown 竞态里依赖它决定文案,
|
|
345
|
+
// query_task 也可直接读 meta.guiStop 判断是否已确认停止。
|
|
346
|
+
if (guiStop)
|
|
347
|
+
meta.guiStop = guiStop;
|
|
348
|
+
// 只有 driver=gui 的 agent 才谈「GUI 内运行是否已确认停止」:spawn 子进程由 killTree 终止,
|
|
349
|
+
// 对其套用 GUI 文案本身就是新的失真。
|
|
350
|
+
const disclosure = (await this.isGuiDriver())
|
|
351
|
+
? guiStopDisclosure(meta.guiStop, await this.guiAppName())
|
|
352
|
+
: { clean: true, text: "" };
|
|
353
|
+
meta.interruptedCleanStop = disclosure.clean;
|
|
340
354
|
// 用户取消只认结构化意图。排队中用户取消由 TaskManager.cancel 直接落终态,不会进入本分支;
|
|
341
355
|
// orchestrator 尚在采集基线时 status 仍可能是 queued,server shutdown 不得因此误记为用户取消。
|
|
342
356
|
const isCancelled = Boolean(meta.cancelRequestedAt) || meta.abortSource === "user";
|
|
343
357
|
if (isCancelled) {
|
|
344
358
|
meta.errorType = "cancelled";
|
|
345
359
|
meta.abortSource = "user";
|
|
346
|
-
meta.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const guiName = meta.agentId === "kimicode" ? "Kimi Code" : "Codex";
|
|
350
|
-
meta.lastMessage += guiStop.idle
|
|
351
|
-
? ";GUI 内运行已停止。"
|
|
352
|
-
: `;GUI 内运行未确认停止,${guiName} 窗口中的任务可能仍在继续。`;
|
|
353
|
-
}
|
|
360
|
+
meta.guiResidualUnconfirmed = !disclosure.clean;
|
|
361
|
+
meta.lastMessage =
|
|
362
|
+
(meta.cancelReason ? `已取消:${meta.cancelReason}` : "已取消") + disclosure.text;
|
|
354
363
|
await this.deps.store.updateStatus(meta, "cancelled", meta.lastMessage);
|
|
355
364
|
}
|
|
356
365
|
else {
|
|
357
366
|
meta.errorType = "interrupted";
|
|
358
367
|
meta.abortSource = "shutdown";
|
|
359
|
-
meta.
|
|
368
|
+
meta.guiResidualUnconfirmed = !disclosure.clean;
|
|
369
|
+
meta.lastMessage = `任务已中断(server 退出 / 父进程 EOF / 未显式取消的中止)。${disclosure.text}`;
|
|
360
370
|
await this.deps.store.updateStatus(meta, "interrupted", meta.lastMessage);
|
|
361
371
|
}
|
|
362
372
|
return { status: meta.status, meta, reason: meta.lastMessage, summary: meta.lastMessage };
|
|
363
373
|
}
|
|
374
|
+
/** 该任务是否由 GUI driver 驱动;profile 不可读时保守按 GUI 处理(宁可多提示人工检查)。 */
|
|
375
|
+
async isGuiDriver() {
|
|
376
|
+
try {
|
|
377
|
+
const profile = await this.deps.dataHome.getProfile(this.meta.agentId);
|
|
378
|
+
return profile ? profile.driver === "gui" : true;
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/** 终态文案里的界面窗口称呼:profile 派生,读取失败回退 agentId(issue #14)。 */
|
|
385
|
+
async guiAppName() {
|
|
386
|
+
try {
|
|
387
|
+
const profile = await this.deps.dataHome.getProfile(this.meta.agentId);
|
|
388
|
+
return guiAppNameOf(profile?.displayName, this.meta.agentId);
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
return guiAppNameOf(undefined, this.meta.agentId);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
364
394
|
async finish(status, errorType, reason) {
|
|
365
395
|
if (this.done)
|
|
366
396
|
return { status, meta: this.meta, reason };
|
package/dist/mcp/formatter.js
CHANGED
|
@@ -47,6 +47,9 @@ export function metaFromTask(meta, extra) {
|
|
|
47
47
|
actualReasoningLevel: meta.actualReasoningLevel,
|
|
48
48
|
modelSource: meta.modelSource,
|
|
49
49
|
guiStop: meta.guiStop,
|
|
50
|
+
// 待确认的两个来源取并集:shutdown 路径的 interruptedCleanStop===false,以及重启归档的
|
|
51
|
+
// guiResidualUnconfirmed。未确认时为 true——编排方据此禁止直接重派。
|
|
52
|
+
guiStopUnconfirmed: meta.guiResidualUnconfirmed === true || meta.interruptedCleanStop === false ? true : undefined,
|
|
50
53
|
lastRunSignal: meta.lastRunSignal,
|
|
51
54
|
...extra,
|
|
52
55
|
};
|
package/dist/mcp/handlers.js
CHANGED
package/dist/mcp/tools.js
CHANGED
|
@@ -58,7 +58,7 @@ export const TOOL_DEFS = [
|
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
name: "cancel_task",
|
|
61
|
-
description: "取消运行中任务:CLI agent 终止进程树;GUI agent(codex 等)尽力点击界面停止按钮并等待 GUI
|
|
61
|
+
description: "取消运行中任务:CLI agent 终止进程树;GUI agent(codex 等)尽力点击界面停止按钮并等待 GUI 空闲(有界超时),未确认停止时结果中明示。排队中任务直接移除。对已处于终态的 GUI 任务,本调用兼任人工确认入口:人工核实窗口中已无残留运行后调用,可清除 meta 的 guiStopUnconfirmed 待确认标记(不改终态)。",
|
|
62
62
|
inputSchema: CancelTaskParamsSchema,
|
|
63
63
|
capability: "write",
|
|
64
64
|
requireApproval: true,
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* server.ts:组装 —— 加载配置、初始化数据目录/日志、TaskManager/AcceptanceEngine/
|
|
3
|
-
* Registry、注册
|
|
3
|
+
* Registry、注册 11 个工具到 McpServer、触发技能自检安装。被 index.ts 调用以 stdio 启动。
|
|
4
4
|
*/
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -23,11 +23,14 @@ export async function buildServer(opts = {}) {
|
|
|
23
23
|
await dataHome.init();
|
|
24
24
|
const cfg = await dataHome.loadConfig();
|
|
25
25
|
const maxRunning = opts.maxRunningOverride ?? cfg.concurrency?.maxRunning ?? 2;
|
|
26
|
+
// shutdown 预算(issue #14):GUI 任务在 server 退出时的停止等待上限,注入 manager 供
|
|
27
|
+
// shutdownInterrupt() 使用;与 profile 的 gui.cancelWaitMs(取消路径)解耦。
|
|
28
|
+
const guiStopWaitMs = cfg.shutdown?.guiStopWaitMs ?? 15_000;
|
|
26
29
|
const store = new TaskStore(home, logger);
|
|
27
30
|
const registry = new AgentAdapterRegistry(() => dataHome.loadProfiles(), logger);
|
|
28
31
|
const engine = new AcceptanceEngine(store, logger);
|
|
29
32
|
const manager = new TaskManager(store, dataHome, registry, engine, logger, makeBuildCtx({ store, dataHome }));
|
|
30
|
-
await manager.initialize(maxRunning);
|
|
33
|
+
await manager.initialize({ maxRunning, guiStopWaitMs });
|
|
31
34
|
// 技能自检安装(失败仅告警不阻断,§17.5);后台执行,不阻塞握手
|
|
32
35
|
if (!opts.skipSkillInstall && (cfg.skills?.autoInstall ?? true)) {
|
|
33
36
|
void skillSelfInstall(logger).then((r) => {
|
|
@@ -45,7 +48,7 @@ export async function buildServer(opts = {}) {
|
|
|
45
48
|
});
|
|
46
49
|
const server = new McpServer({ name: "tianshu-mcp", version: MCP_SERVER_VERSION }, {
|
|
47
50
|
capabilities: { tools: {} },
|
|
48
|
-
instructions: "tianshu-mcp:调度外部 AI-Agent(codex/zcode/traework)完成项目开发、验收、返修闭环。ZCode 提问或等待用户环境处理时进入 needs_user,可用 continue_task 恢复原会话。run_task 异步返回 taskId,再用 query_task 轮询。",
|
|
51
|
+
instructions: "tianshu-mcp:调度外部 AI-Agent(codex/zcode/traework/kimicode/qoder)完成项目开发、验收、返修闭环。ZCode 提问或等待用户环境处理时进入 needs_user,可用 continue_task 恢复原会话。run_task 异步返回 taskId,再用 query_task 轮询。",
|
|
49
52
|
});
|
|
50
53
|
for (const tool of TOOL_DEFS) {
|
|
51
54
|
const handler = handlers[tool.name];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ACTIVE_STATUSES, isTerminal, isProjectWorkspace, } from "./task.js";
|
|
1
|
+
import { ACTIVE_STATUSES, isTerminal, isProjectWorkspace, guiAppNameOf, guiStopDisclosure, } from "./task.js";
|
|
2
2
|
import { TaskOrchestrator } from "../loop/fix-loop.js";
|
|
3
3
|
import { genTaskId, nowIso } from "../util/id.js";
|
|
4
4
|
import { normPath } from "../util/path.js";
|
|
@@ -17,6 +17,11 @@ const DEFAULT_WORKSPACE_QUEUE_KEY = "__zcode_default_workspace__";
|
|
|
17
17
|
function queueKeyOf(meta) {
|
|
18
18
|
return isProjectWorkspace(meta) ? meta.projectPath : DEFAULT_WORKSPACE_QUEUE_KEY;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* spawn 类任务在 server 关闭时等待 `killTree` 收尾的预算(issue #14 保留原值)。
|
|
22
|
+
* GUI 类任务不复用该值:其停止等待预算见 `shutdown.guiStopWaitMs`(默认 15s)。
|
|
23
|
+
*/
|
|
24
|
+
const SPAWN_INTERRUPT_SETTLE_MS = 2000;
|
|
20
25
|
export class TaskManager {
|
|
21
26
|
store;
|
|
22
27
|
dataHome;
|
|
@@ -30,6 +35,12 @@ export class TaskManager {
|
|
|
30
35
|
runningCount = 0;
|
|
31
36
|
abortControllers = new Map();
|
|
32
37
|
maxRunning = 2;
|
|
38
|
+
/**
|
|
39
|
+
* server 关闭时 GUI agent 任务的停止等待预算(issue #14):
|
|
40
|
+
* 由 `initialize()` 注入(config.json 的 `shutdown.guiStopWaitMs`),与 profile 的
|
|
41
|
+
* `gui.cancelWaitMs` 解耦——shutdown 受进程退出时限约束,取消路径由调用方主动等待。
|
|
42
|
+
*/
|
|
43
|
+
guiStopWaitMs = 15_000;
|
|
33
44
|
constructor(store, dataHome, registry, engine, logger, buildCtx) {
|
|
34
45
|
this.store = store;
|
|
35
46
|
this.dataHome = dataHome;
|
|
@@ -38,20 +49,67 @@ export class TaskManager {
|
|
|
38
49
|
this.logger = logger;
|
|
39
50
|
this.buildCtx = buildCtx;
|
|
40
51
|
}
|
|
41
|
-
/**
|
|
42
|
-
|
|
52
|
+
/**
|
|
53
|
+
* 启动前注入并发上限与 shutdown 预算,并归档遗留活动任务(不续跑)。
|
|
54
|
+
* 遗留的 GUI 任务必须如实标注「GUI 内运行未确认停止,请人工检查」(issue #14):
|
|
55
|
+
* 此刻服务器对该 GUI 无任何连接,必然无法确认,绝不写「进程已终止」。
|
|
56
|
+
*/
|
|
57
|
+
async initialize(opts) {
|
|
58
|
+
const { maxRunning, guiStopWaitMs } = typeof opts === "number" ? { maxRunning: opts, guiStopWaitMs: undefined } : opts;
|
|
43
59
|
this.maxRunning = maxRunning;
|
|
60
|
+
if (guiStopWaitMs !== undefined)
|
|
61
|
+
this.guiStopWaitMs = guiStopWaitMs;
|
|
44
62
|
const legacy = await this.store.scanLegacyActive();
|
|
45
63
|
for (const meta of legacy) {
|
|
46
64
|
this.tasks.set(meta.taskId, meta);
|
|
47
65
|
meta.errorType = "interrupted";
|
|
48
66
|
meta.abortSource = "shutdown";
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
// 遗留快照里的 guiStop 是上一次 abort 的结果,不构成本次重启后的确认,先清掉。
|
|
68
|
+
delete meta.guiStop;
|
|
69
|
+
let message = "server 重启遗留(启动时归档,不续跑)。";
|
|
70
|
+
if (await this.isGuiDriver(meta.agentId)) {
|
|
71
|
+
const app = await this.guiAppName(meta.agentId);
|
|
72
|
+
const disclosure = guiStopDisclosure(undefined, app);
|
|
73
|
+
meta.interruptedCleanStop = false;
|
|
74
|
+
meta.guiResidualUnconfirmed = true;
|
|
75
|
+
// 启动归档没有"点击停止"这一步(适配器要求实例归属证明,缺失即 fail-closed),
|
|
76
|
+
// 因此文案用「无停止结果可确认」而不是「未确认停止」。
|
|
77
|
+
message = `server 重启遗留(启动时归档,不续跑)${disclosure.text}`;
|
|
78
|
+
}
|
|
79
|
+
meta.lastMessage = message;
|
|
80
|
+
// 逐个 await:与 shutdownInterrupt 同款的「先写状态再等快照」竞态必须避免,
|
|
81
|
+
// 否则紧随其后的 query_task 可能读到半截 meta。
|
|
82
|
+
await this.store
|
|
83
|
+
.updateStatus(meta, "interrupted", "server 重启遗留归档")
|
|
84
|
+
.catch((e) => this.logger.warn(`遗留任务 ${meta.taskId} 归档失败:${String(e)}`));
|
|
51
85
|
}
|
|
52
86
|
if (legacy.length)
|
|
53
87
|
this.logger.warn(`启动归档 ${legacy.length} 个遗留任务(interrupted)`);
|
|
54
88
|
}
|
|
89
|
+
/** agent 是否由 GUI 驱动(driver="gui")。profile 不可读时保守按 GUI 处理,绝不谎报已停止。 */
|
|
90
|
+
async isGuiDriver(agentId) {
|
|
91
|
+
try {
|
|
92
|
+
const profiles = await this.dataHome.loadProfiles();
|
|
93
|
+
const profile = profiles[agentId];
|
|
94
|
+
if (!profile)
|
|
95
|
+
return true;
|
|
96
|
+
return profile.driver === "gui";
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
this.logger.warn(`读取 agent profile 失败(${agentId}),按 GUI 保守处理:${String(e)}`);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** 终态文案里的界面窗口称呼(profile 派生,无法读取时回退 agentId)。 */
|
|
104
|
+
async guiAppName(agentId) {
|
|
105
|
+
try {
|
|
106
|
+
const profiles = await this.dataHome.loadProfiles();
|
|
107
|
+
return guiAppNameOf(profiles[agentId]?.displayName, agentId);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return guiAppNameOf(undefined, agentId);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
55
113
|
get activeCount() {
|
|
56
114
|
return this.runningCount;
|
|
57
115
|
}
|
|
@@ -244,6 +302,8 @@ export class TaskManager {
|
|
|
244
302
|
* 返回 settled 表示任务是否已在本调用内落终态(issue #6):活动中任务会 abort 编排
|
|
245
303
|
* 协程,GUI agent 侧 run 协程先尽力点击界面停止按钮并等待空闲(gui.cancelWaitMs,
|
|
246
304
|
* 默认 15s),本方法有界等待其落终态后再返回,取消结果不再"请求即成功"。
|
|
305
|
+
* 对**已是终态**的 GUI 任务,本方法兼任「人工确认消除残留待确认状态」的入口(issue #14),
|
|
306
|
+
* 此时返回 cleared=true(见方法末尾注释)。
|
|
247
307
|
*/
|
|
248
308
|
async cancel(taskId, reason) {
|
|
249
309
|
const meta = await this.getMeta(taskId);
|
|
@@ -305,39 +365,72 @@ export class TaskManager {
|
|
|
305
365
|
reason: "已请求取消,但任务尚未在本调用内落终态(GUI 侧停止可能未完成);请稍后 query_task 复核",
|
|
306
366
|
};
|
|
307
367
|
}
|
|
368
|
+
// 终态任务:不取消,但承担 issue #14 的「人工确认消除」职责——GUI 任务被 shutdown/重启
|
|
369
|
+
// 归档为 interrupted 时会留下「GUI 内运行未确认停止」的待确认状态,人与 AI 检查过 GUI
|
|
370
|
+
// 无残留运行后,用同一个 cancel_task 清除标记(不新增工具)。仅清标记,不改终态与 errorType。
|
|
371
|
+
if (meta.guiResidualUnconfirmed || meta.interruptedCleanStop === false) {
|
|
372
|
+
const app = await this.guiAppName(meta.agentId);
|
|
373
|
+
const note = reason
|
|
374
|
+
? `已确认人工核查(${app})无残留运行:${reason}`
|
|
375
|
+
: `已确认人工核查(${app})无残留运行`;
|
|
376
|
+
meta.guiResidualUnconfirmed = false;
|
|
377
|
+
meta.interruptedCleanStop = true;
|
|
378
|
+
meta.lastMessage = `${meta.lastMessage ?? ""}(${note})`;
|
|
379
|
+
await this.store.appendEvent(meta.taskId, "gui_residual_acknowledged", meta.status, note);
|
|
380
|
+
await this.store.writeSnapshot(meta);
|
|
381
|
+
this.tasks.set(meta.taskId, meta);
|
|
382
|
+
return {
|
|
383
|
+
found: true,
|
|
384
|
+
settled: true,
|
|
385
|
+
cleared: true,
|
|
386
|
+
reason: `任务已处于终态(${meta.status}):${note}`,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
308
389
|
return { found: true, reason: `任务已处于终态(${meta.status}),无需取消`, settled: true };
|
|
309
390
|
}
|
|
310
|
-
/**
|
|
391
|
+
/**
|
|
392
|
+
* server 退出:终止全部活动任务并标 interrupted(排队中任务也归档)。
|
|
393
|
+
* GUI agent 是外部桌面应用,server 对其进程没有所有权:abort 后适配器至多"尽力点击界面停止"。
|
|
394
|
+
* 因此这里给 GUI 类任务一份**全局共享**的 `guiStopWaitMs` 预算(issue #14),让适配器的停止逻辑
|
|
395
|
+
* 跑完并把结果经 orchestrator / meta 如实落盘;到期仍无法确认时由 `persistInterrupted` 写「未确认停止」。
|
|
396
|
+
* 绝不写「进程已终止」这类只对 spawn 子进程成立的断言。
|
|
397
|
+
*/
|
|
311
398
|
async shutdownInterrupt() {
|
|
312
399
|
for (const ac of this.abortControllers.values())
|
|
313
400
|
ac.abort();
|
|
314
|
-
//
|
|
401
|
+
// 全局 deadline:N 个 GUI 任务并行等待,退出的最坏耗时仍是 guiStopWaitMs(不是 N 倍)
|
|
402
|
+
const guiDeadline = Date.now() + this.guiStopWaitMs;
|
|
315
403
|
const killPromises = [];
|
|
316
404
|
for (const id of this.running) {
|
|
317
405
|
const meta = this.tasks.get(id);
|
|
318
406
|
if (meta && ACTIVE_STATUSES.includes(meta.status)) {
|
|
319
|
-
killPromises.push(this.persistInterrupted(meta));
|
|
407
|
+
killPromises.push(this.persistInterrupted(meta, guiDeadline));
|
|
320
408
|
}
|
|
321
409
|
}
|
|
322
410
|
await Promise.all(killPromises);
|
|
323
411
|
// 排队中任务归档
|
|
324
|
-
for (const
|
|
412
|
+
for (const meta of this.tasks.values()) {
|
|
325
413
|
if (meta.status === "queued") {
|
|
326
414
|
meta.status = "interrupted";
|
|
327
415
|
meta.errorType = "interrupted";
|
|
328
416
|
meta.abortSource = "shutdown";
|
|
329
417
|
meta.lastMessage = "server 退出,排队中任务已归档";
|
|
330
418
|
await this.store.updateStatus(meta, "interrupted", meta.lastMessage).catch(() => { });
|
|
331
|
-
void taskId;
|
|
332
419
|
}
|
|
333
420
|
}
|
|
334
421
|
this.running.clear();
|
|
335
422
|
this.runningCount = 0;
|
|
336
423
|
this.queue.clear();
|
|
337
424
|
}
|
|
338
|
-
/**
|
|
339
|
-
|
|
340
|
-
|
|
425
|
+
/**
|
|
426
|
+
* 有界等待子进程/停止动作收尾后落 interrupted(server 关闭路径,不得误记为 cancelled)。
|
|
427
|
+
* - spawn 类:沿用 2s 预算(给 `killTree` 收尾);
|
|
428
|
+
* - GUI 类:等到共享的 `guiDeadline`,把适配器的停止结果如实写进文案与字段(issue #14)。
|
|
429
|
+
* 若 orchestrator 已在本预算内落了终态,则直接返回,保留它更精确的文案。
|
|
430
|
+
*/
|
|
431
|
+
async persistInterrupted(meta, guiDeadline) {
|
|
432
|
+
const gui = await this.isGuiDriver(meta.agentId);
|
|
433
|
+
const deadline = gui ? guiDeadline : Date.now() + SPAWN_INTERRUPT_SETTLE_MS;
|
|
341
434
|
for (;;) {
|
|
342
435
|
if (!ACTIVE_STATUSES.includes(meta.status))
|
|
343
436
|
return; // orchestrator 已落终态
|
|
@@ -345,13 +438,22 @@ export class TaskManager {
|
|
|
345
438
|
break;
|
|
346
439
|
await new Promise((r) => setTimeout(r, 50));
|
|
347
440
|
}
|
|
348
|
-
if (ACTIVE_STATUSES.includes(meta.status))
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
441
|
+
if (!ACTIVE_STATUSES.includes(meta.status))
|
|
442
|
+
return;
|
|
443
|
+
meta.status = "interrupted";
|
|
444
|
+
meta.errorType = "interrupted";
|
|
445
|
+
meta.abortSource = "shutdown";
|
|
446
|
+
if (gui) {
|
|
447
|
+
const app = await this.guiAppName(meta.agentId);
|
|
448
|
+
const disclosure = guiStopDisclosure(meta.guiStop, app);
|
|
449
|
+
meta.interruptedCleanStop = disclosure.clean;
|
|
450
|
+
meta.guiResidualUnconfirmed = !disclosure.clean;
|
|
451
|
+
meta.lastMessage = `server 退出${disclosure.text}`;
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
352
454
|
meta.lastMessage = "server 退出,进程已终止";
|
|
353
|
-
await this.store.updateStatus(meta, "interrupted", meta.lastMessage).catch(() => { });
|
|
354
455
|
}
|
|
456
|
+
await this.store.updateStatus(meta, "interrupted", meta.lastMessage).catch(() => { });
|
|
355
457
|
}
|
|
356
458
|
enqueue(meta) {
|
|
357
459
|
const key = queueKeyOf(meta);
|
package/dist/tasks/task.js
CHANGED
|
@@ -58,3 +58,49 @@ export function isProjectWorkspace(meta) {
|
|
|
58
58
|
export function isDefaultWorkspace(meta) {
|
|
59
59
|
return workspaceModeOf(meta) === "default";
|
|
60
60
|
}
|
|
61
|
+
/* ---------------- GUI 停止结果的如实披露(issue #14) ---------------- */
|
|
62
|
+
/**
|
|
63
|
+
* 由 profile.displayName 派生「界面窗口称呼」,供终态文案使用。
|
|
64
|
+
* 剥掉括号内的形式说明与「桌面端 / 客户端 / Desktop」等通用后缀——写进文案的是窗口名,
|
|
65
|
+
* 不是 profile 文档里的自我介绍;无 displayName 时回退 agentId。
|
|
66
|
+
* 纯字符串处理,无平台/文件系统依赖。
|
|
67
|
+
* @param displayName profile.displayName(可能缺失)
|
|
68
|
+
* @param agentId 回退用的 agentId
|
|
69
|
+
*/
|
|
70
|
+
export function guiAppNameOf(displayName, agentId) {
|
|
71
|
+
const raw = (displayName ?? "").trim() || agentId;
|
|
72
|
+
// 只剥"说明性质"的括号段(含 GUI/桌面/客户端/Desktop 等描述词),以及这些通用后缀词本身。
|
|
73
|
+
// 刻意不做无限剥离:像「TraeWork(测试)」这类有实际意义的括号段必须保留;
|
|
74
|
+
// 凡是剥空的都不采纳,退回原名——文案可以长,不能失真。
|
|
75
|
+
const stripped = raw
|
|
76
|
+
.replace(/[((]([^(())]*)[))]/g, (whole, inner) => /gui|desktop|桌面|客户端|应用|窗口/i.test(inner) ? "" : whole)
|
|
77
|
+
.replace(/\b(desktop|gui)\b/gi, "")
|
|
78
|
+
.replace(/桌面端|桌面版/g, "")
|
|
79
|
+
.replace(/\s{2,}/g, " ")
|
|
80
|
+
.replace(/^[\s\-–—·]+|[\s\-–—·]+$/g, "")
|
|
81
|
+
.trim();
|
|
82
|
+
return stripped || raw.replace(/\s{2,}/g, " ").trim();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* GUI 停止结果的如实披露(红线 8 的单一实现)。
|
|
86
|
+
* 判定矩阵:`idle === true` → 已确认停止;`idle === false` → 点击过但未确认;
|
|
87
|
+
* `undefined` → 无停止结果(该 agent 无停止能力或未及尝试)——后两者都**不得**声称已停止。
|
|
88
|
+
* @param stop 适配器回报的 guiStop;缺失即无停止结果
|
|
89
|
+
* @param app 界面窗口称呼(见 guiAppNameOf)
|
|
90
|
+
* @returns undefined 表示无需披露(非 GUI agent 调用方自行判断)
|
|
91
|
+
*/
|
|
92
|
+
export function guiStopDisclosure(stop, app) {
|
|
93
|
+
if (stop?.idle === true) {
|
|
94
|
+
return { clean: true, text: `;已确认 ${app} 内运行停止。` };
|
|
95
|
+
}
|
|
96
|
+
if (!stop) {
|
|
97
|
+
return {
|
|
98
|
+
clean: false,
|
|
99
|
+
text: `;${app} 内运行无停止结果可确认,窗口中的任务可能仍在继续,请人工打开 ${app} 确认无残留运行。`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
clean: false,
|
|
104
|
+
text: `;${app} 内运行未确认停止,窗口中的任务可能仍在继续,请人工打开 ${app} 确认无残留运行。`,
|
|
105
|
+
};
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tianshu-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "天枢 × AI-Agent 编排 MCP server —— 驱动 Codex、TraeWork、ZCode、Kimi Code 与 Qoder CN 完成项目开发、验收、失败返修与再验收闭环。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"docs/visual-validation.md",
|
|
25
25
|
"docs/visual-validation.en.md",
|
|
26
26
|
"docs/visual-validation-evidence",
|
|
27
|
+
"scripts/probe-traework.mjs",
|
|
27
28
|
"scripts/probe-zcode.mjs",
|
|
28
29
|
"scripts/probe-codex.mjs",
|
|
29
30
|
"scripts/probe-kimicode.mjs",
|
|
@@ -62,6 +63,9 @@
|
|
|
62
63
|
"check:stdio": "node scripts/check-stdio.mjs --entry dist/index.js",
|
|
63
64
|
"check:stdio:src": "node scripts/check-stdio.mjs --entry src/index.ts --tsx",
|
|
64
65
|
"smoke:zcode": "node scripts/smoke-zcode.mjs",
|
|
66
|
+
"probe:traework": "node scripts/probe-traework.mjs",
|
|
67
|
+
"probe:zcode": "node scripts/probe-zcode.mjs",
|
|
68
|
+
"probe:codex": "node scripts/probe-codex.mjs",
|
|
65
69
|
"probe:kimicode": "node scripts/probe-kimicode.mjs",
|
|
66
70
|
"probe:qoder": "node scripts/probe-qoder.mjs",
|
|
67
71
|
"fixture:zcode": "node scripts/prepare-zcode-fixture.mjs",
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TraeWork CDP 真机探针(开发计划 §7:真机探针不入 CI,手动运行)。
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* node scripts/probe-traework.mjs # 连接并 dump 选择器状态
|
|
6
|
+
* node scripts/probe-traework.mjs selectors # 同上(显式)
|
|
7
|
+
* node scripts/probe-traework.mjs send "任务书" # 端到端:新建会话 → 发送 → 等回复
|
|
8
|
+
* node scripts/probe-traework.mjs project <路径> # 新建会话 → 绑定项目文件夹
|
|
9
|
+
*
|
|
10
|
+
* 前提:TraeWork 已以 --remote-debugging-port=<port> 启动且窗口可见。
|
|
11
|
+
* 端口取 TRAEWORK_CDP_PORT 环境变量,默认 9222。
|
|
12
|
+
*/
|
|
13
|
+
import { TraeworkCdpClient } from "../dist/agents/traework/cdp/client.js";
|
|
14
|
+
import { SELECTORS, resolveSelectors } from "../dist/agents/traework/cdp/selectors.js";
|
|
15
|
+
import { startNewSession, bindProject, listSessions, readProjectItems, readMode, ensureMode } from "../dist/agents/traework/ui/session.js";
|
|
16
|
+
import { typeAndSend } from "../dist/agents/traework/ui/composer.js";
|
|
17
|
+
import { judgePoll, makeMarker, parseAdded } from "../dist/agents/traework/ui/reply.js";
|
|
18
|
+
|
|
19
|
+
const PORT = Number(process.env.TRAEWORK_CDP_PORT || 9222);
|
|
20
|
+
const mode = process.argv[2] || "selectors";
|
|
21
|
+
|
|
22
|
+
const logger = {
|
|
23
|
+
info: (m) => console.log(`[probe] ${m}`),
|
|
24
|
+
warn: (m) => console.warn(`[probe][warn] ${m}`),
|
|
25
|
+
error: (m) => console.error(`[probe][error] ${m}`),
|
|
26
|
+
debug: (m) => console.log(`[probe][debug] ${m}`),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const cdp = new TraeworkCdpClient({ port: PORT });
|
|
30
|
+
await cdp.connect();
|
|
31
|
+
console.log(`[probe] CDP 已连接(端口 ${PORT})`);
|
|
32
|
+
|
|
33
|
+
if (mode === "selectors") {
|
|
34
|
+
console.log("[probe] 选择器实测状态:");
|
|
35
|
+
for (const key of Object.keys(SELECTORS)) {
|
|
36
|
+
const spec = SELECTORS[key];
|
|
37
|
+
const exists = await cdp.exists(key);
|
|
38
|
+
const text = exists ? (await cdp.text(key)).slice(0, 60) : "";
|
|
39
|
+
const mark = exists ? "OK " : "MISS";
|
|
40
|
+
console.log(` ${mark} ${key.padEnd(22)} verified=${spec.verified ? "Y" : "N"} ${exists ? JSON.stringify(text) : `(${resolveSelectors(key)[0]})`}`);
|
|
41
|
+
}
|
|
42
|
+
const liveness = await cdp.probeLiveness();
|
|
43
|
+
console.log("[probe] 运行状态探针:", JSON.stringify(liveness));
|
|
44
|
+
console.log("[probe] 任务列表:", (await listSessions(cdp)).slice(0, 10).join(" | ") || "(空)");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (mode === "project") {
|
|
48
|
+
const target = process.argv[3];
|
|
49
|
+
if (!target) {
|
|
50
|
+
console.error("[probe] 用法: node scripts/probe-traework.mjs project <项目绝对路径>");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
await startNewSession(cdp, { logger });
|
|
54
|
+
console.log("[probe] 已新建会话");
|
|
55
|
+
const items = await readProjectItems(cdp);
|
|
56
|
+
console.log(`[probe] 下拉项目 ${items.length} 项:`, items.map((i) => i.name).join("、") || "(空)");
|
|
57
|
+
const r = await bindProject(cdp, target, { logger });
|
|
58
|
+
console.log("[probe] 绑定结果:", JSON.stringify(r));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (mode === "mode") {
|
|
62
|
+
const target = process.argv[3];
|
|
63
|
+
console.log("[probe] 当前模式:", (await readMode(cdp)) || "(读不到)");
|
|
64
|
+
if (!target) {
|
|
65
|
+
console.log("[probe] 用法: node scripts/probe-traework.mjs mode <Work|Code|Design>");
|
|
66
|
+
} else if (!["Work", "Code", "Design"].includes(target)) {
|
|
67
|
+
console.error("[probe] 模式必须是 Work | Code | Design");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
} else {
|
|
70
|
+
const ok = await ensureMode(cdp, target, { logger });
|
|
71
|
+
console.log(`[probe] 切换到 ${target} 结果:`, ok, "| 当前:", (await readMode(cdp)) || "(读不到)");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (mode === "send") {
|
|
76
|
+
const text = process.argv[3];
|
|
77
|
+
if (!text) {
|
|
78
|
+
console.error('[probe] 用法: node scripts/probe-traework.mjs send "任务书"');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
await startNewSession(cdp, { logger });
|
|
82
|
+
const marker = makeMarker();
|
|
83
|
+
await typeAndSend(cdp, marker + text, { logger });
|
|
84
|
+
console.log("[probe] 已发送,开始轮询…");
|
|
85
|
+
const base = await cdp.text("messageContainer");
|
|
86
|
+
let state = { prev: "", stable: 0, idleSince: 0 };
|
|
87
|
+
const deadline = Date.now() + 180_000;
|
|
88
|
+
for (;;) {
|
|
89
|
+
if (Date.now() > deadline) {
|
|
90
|
+
console.log("[probe] 超时");
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
94
|
+
const current = await cdp.text("messageContainer");
|
|
95
|
+
const liveness = await cdp.probeLiveness();
|
|
96
|
+
const v = judgePoll(current, marker, base, state, 12, { liveness, idleTimeoutMs: 10 * 60_000 });
|
|
97
|
+
if (v.kind === "finished" || v.kind === "ask_user" || v.kind === "idle") {
|
|
98
|
+
const parsed = parseAdded(v.added);
|
|
99
|
+
console.log(`[probe] 结束(${v.kind}),正文 ${parsed.content.length} 字符:`);
|
|
100
|
+
console.log(parsed.content.slice(0, 2000));
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
state = v.state;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
cdp.disconnect();
|
|
108
|
+
console.log("[probe] 完成");
|
|
@@ -39,7 +39,7 @@ run_task(秒回 taskId,异步)
|
|
|
39
39
|
| `run_task` | write + 审批 | 派活给外部 agent;**异步**返回 `taskId` | 见 §3 |
|
|
40
40
|
| `query_task` | read | 轮询状态 + agent 日志尾(`tailLines` 缺省 40 行) | `taskId`、`tailLines?` |
|
|
41
41
|
| `list_tasks` | read | 查历史任务(每行:taskId / status / agent / project / 摘要) | `projectPath?`、`status?`、`limit?`(缺省 50,上限 200) |
|
|
42
|
-
| `get_task_report` | read | 读某轮验收报告 **Markdown
|
|
42
|
+
| `get_task_report` | read | 读某轮验收报告 **Markdown 全文** | `taskId`、`round?`(0-based,缺省最新) |
|
|
43
43
|
| `verify_task` | read | 对任务或任意项目**独立验收**(不改源码、无需审批) | `taskId` 或 `projectPath` 二选一、`extraChecks?`、`checksMode?`、`baselineRef?` |
|
|
44
44
|
| `rework_task` | write + 审批 | 手动返修:终态任务重新入队续跑(同 agent/项目、同一轮次记账) | `taskId`、`feedback?` |
|
|
45
45
|
| `continue_task` | write + 审批 | 恢复 `needs_user`(仅 codex/zcode/kimicode/qoder) | `taskId`、`message`(必填) |
|
|
@@ -179,7 +179,7 @@ meta 的 `needsUserKind` 给出等待类型,`pendingQuestion` 给出问题原
|
|
|
179
179
|
| `succeeded` | 验收通过(或未开验收且 agent 正常退出) | `get_task_report(taskId, round?)` 取 changedFiles / diffstat / checks,向用户汇报 |
|
|
180
180
|
| `failed` | 未开自动返修时的失败,或**硬失败**(`errorType=spawn`) | 读 `agentEndReason`(§9)区分:硬失败按表中处置;真失败给**针对性** feedback 调 `rework_task` |
|
|
181
181
|
| `needs_attention` | ①自动返修轮次用尽仍失败;②验收**阻塞**(配置/完整性错误、缺基准、页面不可达等);③GUI 空闲/超时/断线(`errorType=agent_failed`/`timeout`) | 先读报告定位,再决定 `rework_task` 或转人工 |
|
|
182
|
-
| `cancelled` / `interrupted` | 取消 / 超时或中断 | 看 `abortSource`(`user`/`shutdown`/`timeout`/`internal
|
|
182
|
+
| `cancelled` / `interrupted` | 取消 / 超时或中断 | 看 `abortSource`(`user`/`shutdown`/`timeout`/`internal`);GUI 任务另看 `guiStopUnconfirmed`(见下) |
|
|
183
183
|
|
|
184
184
|
关键区别:
|
|
185
185
|
|
|
@@ -189,6 +189,7 @@ meta 的 `needsUserKind` 给出等待类型,`pendingQuestion` 给出问题原
|
|
|
189
189
|
- **`running` 卡死**才用 `cancel_task`:CLI agent 终止进程树;GUI agent 尽力点击界面停止并等待空闲(有界超时),取消文案会如实标注 GUI 侧是否已停。
|
|
190
190
|
**标注“未确认停止”时不要重派同项目任务**——窗口内可能仍在跑,重派护栏也会以 `instance_busy` 拒绝。
|
|
191
191
|
- **`needs_user` 状态下取消**:run 协程已退出、CDP 已断开,MCP 无法再点 GUI 停止按钮,文案会提示人工检查。
|
|
192
|
+
- **`interrupted` + `guiStopUnconfirmed=true`(server 退出 / 宿主 EOF / 重启归档)**:`tianshu-mcp` 对 GUI 进程没有所有权,**“编排器已停”不等于“窗口里的任务已停”**。先让用户人工打开对应窗口确认没有还在跑的 turn,再调 `cancel_task(taskId, reason="已人工核对窗口无残留运行")` 清除待确认标记(终态仍是 `interrupted`),**之后**才可安全重派同项目任务。详见 `usage-examples.md` §9.5.1。
|
|
192
193
|
|
|
193
194
|
汇报纪律:**不要反复空转重试**。多次仍不过或不可修时,如实汇报 `errorType`/`agentEndReason`、失败 check 与输出尾部、变更清单,并给建议(人工看报告 / 换 agent / 缩小任务)。
|
|
194
195
|
|
|
@@ -207,7 +208,7 @@ meta 的 `needsUserKind` 给出等待类型,`pendingQuestion` 给出问题原
|
|
|
207
208
|
|
|
208
209
|
### 8.1 验收报告解读
|
|
209
210
|
|
|
210
|
-
`get_task_report(taskId, round?)` 返回报告 Markdown
|
|
211
|
+
`get_task_report(taskId, round?)` 返回报告 Markdown 全文。**注意:报告类与视觉基准类工具的成功结果不带 meta 块**——`get_task_report` 返回报告原文,`prepare_visual_baseline` / `approve_visual_baseline` 返回视觉操作的 JSON 原文;其余工具结果末尾都带 `---tianshu-mcp-meta---` JSON 块(字段全表见 usage-examples.md §4),任何工具的**错误**结果也不带 meta 块。
|
|
211
212
|
|
|
212
213
|
- `checks[]`:每项 PASS / FAIL / SKIP + 输出尾部。默认**并行 2 条**(`verifyConcurrency`,1–4);checks 之间有顺序依赖(后续读 build 产物、带 `--fix`、共享缓存目录)时**必须显式设 1**,否则偶发误报。
|
|
213
214
|
- `analysis`:变更清单、diffstat、可疑标记命中(TODO/FIXME、`console.log`/`debugger`、疑似密钥形态、超大单文件改动告警)。这是**确定性规则,不是 LLM 评审**,命中只提示人工,不等同于任务失败。
|
|
@@ -219,7 +219,11 @@ run_task(projectPath=/path/to/项目, agentId=codex-cli,
|
|
|
219
219
|
|
|
220
220
|
## 4. meta 块解读(字段全表)
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
除下列情况外,各工具结果文本末尾都是「人类可读文本 + 结构化 meta」:
|
|
223
|
+
|
|
224
|
+
- `get_task_report` 成功时直接返回 `report-<round>.md` 原文;
|
|
225
|
+
- `prepare_visual_baseline` / `approve_visual_baseline` 成功时直接返回视觉操作的 JSON 原文;
|
|
226
|
+
- **任何工具的错误结果**都只有 `Error: …` 文本,不带 meta 块。
|
|
223
227
|
|
|
224
228
|
```text
|
|
225
229
|
---tianshu-mcp-meta---
|
|
@@ -274,7 +278,8 @@ run_task(projectPath=/path/to/项目, agentId=codex-cli,
|
|
|
274
278
|
| `zcodeSessionId` / `qoderSessionId` / `boundProjectPath` | 会话锚点与项目绑定回执(kimicode 的锚点仅在服务端保留,不回显) |
|
|
275
279
|
| `modelProvider` / `permissionMode` | 实际生效的供应商标识与权限模式(zcode 等) |
|
|
276
280
|
| `actualModel` / `actualReasoningLevel` / `modelSource` | 实际生效模型、等级与模型来源(qoder) |
|
|
277
|
-
| `guiStop` |
|
|
281
|
+
| `guiStop` | 最近一次中断时 GUI 停止的点击与空闲确认结果(`clicked` / `idle`);`idle=true` 才是**已确认**停止 |
|
|
282
|
+
| `guiStopUnconfirmed` | 出现即为 `true`:GUI 任务的终态**未确认**停止(`guiStop.idle=false` 或重启归档无任何确认手段),重派前必须先人工确认并用 `cancel_task` 消除(§9.5.1) |
|
|
278
283
|
| `progressSummary` / `lastRunSignal` | 轮询期进度摘要 / 最近运行信号 |
|
|
279
284
|
| `finishedAt` | 终态落定时间 |
|
|
280
285
|
| `model` / `mode` | 本次派单传入的模型 / 面板模式 |
|
|
@@ -498,13 +503,38 @@ continue_task(taskId=tsk_..., message={"选择开发语言":"TypeScript","需要
|
|
|
498
503
|
```text
|
|
499
504
|
cancel_task(taskId, reason="用户要求停止")
|
|
500
505
|
→ 返回 meta.message 可能为:
|
|
501
|
-
"
|
|
502
|
-
"
|
|
506
|
+
"已取消:…;已确认 <窗口名> 内运行停止。" ← 已确认停止,可安全重派
|
|
507
|
+
"已取消:…;<窗口名> 内运行未确认停止,窗口中的任务可能仍在继续,请人工打开 <窗口名> 确认无残留运行。" ← 需人工检查
|
|
508
|
+
"已取消:…;<窗口名> 内运行无停止结果可确认,…(ZCode / TraeWork 无停止能力)" ← 需人工检查
|
|
503
509
|
"已取消(等待用户处理时):…;GUI 内可能仍有等待中的会话,请人工检查。"
|
|
504
510
|
"已取消 …(尚未落终态:GUI 侧停止可能未完成,请稍后 query_task 复核)"
|
|
505
511
|
```
|
|
506
512
|
|
|
507
|
-
GUI agent 取消语义:尽力点击界面停止按钮并等待 GUI 空闲(有界超时)。**未确认停止前不要重派同项目任务**——重派护栏会以 `instance_busy` 拒绝派发(防止新旧 turn 交叠),宁可等人工确认。也可看 meta 的 `guiStop`(`clicked` / `idle
|
|
513
|
+
GUI agent 取消语义:尽力点击界面停止按钮并等待 GUI 空闲(有界超时)。**未确认停止前不要重派同项目任务**——重派护栏会以 `instance_busy` 拒绝派发(防止新旧 turn 交叠),宁可等人工确认。也可看 meta 的 `guiStop`(`clicked` / `idle`)与 `guiStopUnconfirmed`:`guiStopUnconfirmed=true` 或 `guiStop.idle=false` 即未确认停止。
|
|
514
|
+
|
|
515
|
+
### 9.5.1 server 退出 / 重启后的 GUI 残留确认(issue #14)
|
|
516
|
+
|
|
517
|
+
```text
|
|
518
|
+
# server 退出(宿主退出 / stdio EOF)后重新接入:
|
|
519
|
+
query_task(taskId)
|
|
520
|
+
→ meta.status = "interrupted",meta.abortSource = "shutdown"
|
|
521
|
+
文案二选一:
|
|
522
|
+
"server 退出;已确认 <窗口名> 内运行停止。" ← 停止已确认,可安全重派
|
|
523
|
+
"server 退出;<窗口名> 内运行未确认停止,窗口中的任务可能仍在继续,请人工打开 <窗口名> 确认无残留运行。"
|
|
524
|
+
"server 退出;<窗口名> 内运行无停止结果可确认,…" ← 无停止能力(zcode / traework)
|
|
525
|
+
meta.interruptedCleanStop = false、meta.guiStopUnconfirmed = true
|
|
526
|
+
|
|
527
|
+
# 重启归档(上一进程被 kill,来不及落终态):
|
|
528
|
+
→ 文案为 "server 重启遗留(启动时归档,不续跑);… 请人工打开 <窗口名> 确认无残留运行。"
|
|
529
|
+
meta.guiResidualUnconfirmed = true
|
|
530
|
+
|
|
531
|
+
# 人工打开该窗口,确认没有还在跑的 turn 之后:
|
|
532
|
+
cancel_task(taskId, reason="已人工核对窗口无残留运行")
|
|
533
|
+
→ 清除待确认标记(追加 gui_residual_acknowledged 事件),status 仍是 interrupted
|
|
534
|
+
→ meta.guiStopUnconfirmed 变为不存在、meta.guiResidualUnconfirmed = false
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
**顺序纪律**:`guiStopUnconfirmed` 为真时**先人工确认、再重派**同项目任务——`tianshu-mcp` 对 GUI 进程没有所有权,`interrupted` 只说明**编排器**不再观察,不等于窗口里的任务已停。服务端在 shutdown 时会给 GUI 任务一份全局共享的 `shutdown.guiStopWaitMs`(默认 15 秒)预算去"尽力停止 + 有界等待",但到期仍无法确认时只会如实标注,**不会**替你断言已停止;重启归档更是**不会**自动去点停止(无会话锚点、对无归属证明的实例 fail-closed)。
|
|
508
538
|
|
|
509
539
|
### 9.6 agent-profiles.json 相关配置(可选)
|
|
510
540
|
|
|
@@ -528,6 +558,14 @@ GUI agent 取消语义:尽力点击界面停止按钮并等待 GUI 空闲(
|
|
|
528
558
|
- `defaultAutoFixRounds`:该 agent 的自动返修缺省轮数(codex 5 / zcode 2 / kimicode 2 / qoder 3)。
|
|
529
559
|
- 整键覆盖语义:数据目录 `agent-profiles.json` 里同名键会**覆盖**内置 profile 的对应字段;用户自定义 profile(如 `codex-cli`)会出现在 `get_profiles` 中。
|
|
530
560
|
|
|
561
|
+
### 9.7 server 级配置(config.json,可选)
|
|
562
|
+
|
|
563
|
+
```json
|
|
564
|
+
{ "shutdown": { "guiStopWaitMs": 15000 } }
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
- `shutdown.guiStopWaitMs`:server 退出时,GUI agent 任务「尽力点击界面停止 + 有界等待空闲」的**全局共享**上限(默认 15000 = 15 秒,全部 GUI 任务共用一份预算,退出耗时不随任务数增长)。spawn 类任务不受影响(固定 2 秒等 `killTree` 收尾)。到期仍未确认空闲时,终态如实写「未确认停止」并置 `guiStopUnconfirmed`;调大它可以让退出时更容易等到确认结果,代价是退出(以及宿主关闭)变慢。
|
|
568
|
+
|
|
531
569
|
---
|
|
532
570
|
|
|
533
571
|
## 10. 汇报模板
|