tianshu-mcp 0.6.0 → 0.6.1
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 +27 -0
- package/CHANGELOG.md +27 -0
- package/README.en.md +8 -1
- package/README.md +8 -1
- package/dist/config/schema.js +5 -1
- package/dist/config/store.js +12 -2
- package/dist/mcp/handlers.js +12 -4
- package/dist/mcp/tools.js +6 -2
- package/dist/server.js +3 -0
- package/dist/util/path.js +45 -9
- package/dist/version.generated.js +1 -1
- package/package.json +1 -1
- package/skills/tianshu-mcp/SKILL.md +2 -1
- package/skills/tianshu-mcp/usage-examples.md +2 -2
package/CHANGELOG.en.md
CHANGED
|
@@ -8,6 +8,33 @@ Chinese version: [CHANGELOG.md](CHANGELOG.md)
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## [0.6.1] - 2026-09-23
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Dangerous-directory decision collapsed into a platform-injectable pure function** `isDangerousProjectDir(norm, platform)` (`src/util/path.ts`): exact root / drive root / system-directory subtree, so all three platform shapes are verifiable on any OS.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`verify_task` capability moves from `read` to `execute`** ([issue #17](https://github.com/lanlan0811/tianshu-mcp/issues/17) item 2): it runs the project's configured commands and may produce build artifacts, so it never was read-only. **Host note**: its MCP `readOnlyHint` changes from `true` to **`false`**; `requireApproval` stays `false` (still approval-free, the R11 conclusion is unchanged) — **policy layers should key off `_meta.requireApproval`, not `readOnlyHint`**. The policy examples in `docs/tianshu-integration` (both languages) are updated.
|
|
20
|
+
- **The `"network"` value, used by no tool at all, is removed from the `ToolDef.capability` union**, collapsed to three families (`read` / `write` / `execute`) documented on the type and in the `tools.ts` header.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **System directories are now denied as subtrees** (issue #17 item 4): `/etc`, `/usr`, `/bin`, `/sbin`, `/private/etc` plus `c:/windows`, `c:/program files`, `c:/program files (x86)` moved from exact-equality to **boundary-aware subtree denial**, closing leaks such as `c:/windows/system32` and `/etc/anything`. Boundary-awareness keeps `c:/windows.old`, `/etcetera` and `/usrlocal` from being falsely matched; `/var`, `/tmp`, `/opt`, the home directory and friends stay exact-match (on macOS `os.tmpdir()` *is* `/var/folders/...`, so a subtree rule would sever the test base and legitimate workspaces).
|
|
25
|
+
- **Project registration failures are no longer silent** (issue #17 item 3): `run_task` used to discard the `registerProject` return value (`void registered;`) and then re-read the same record via `projectByPath`; it now consumes the return value, drops the redundant read, and on failure logs `WARN` and returns a structured `isError` **without dispatching** (eliminating the "task created but project unregistered" half-state).
|
|
26
|
+
- **Count-vs-reality wrap-up** (issue #17 item 1): the leftover "9 tools" header comment in `test/protocol/protocol.test.ts` is corrected and a `TOOL_DEFS` count assertion added; the lagging "6 scenarios" references in the `ci.yml` comment, `HANDOFF` (two places) and `CONTRIBUTING` (both languages) are synced to **8** (they lagged behind the two skill scenarios added in issue #16).
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
- **The `cmd` string-form pitfalls made explicit** (issue #17 item 5, zero behaviour change): `docs/acceptance-config.md` / `.en.md` gain a measured table (no escaping, an unclosed quote does not error, an empty quote yields an empty argument), and the `schema.ts` / `store.ts` comments, `SKILL.md` and `usage-examples.md` carry an "always prefer the array form" note.
|
|
31
|
+
- Bilingual: README (`verify_task` capability row + M31 milestone), ARCHITECTURE (three-family tool table + `readOnlyHint` derivation rule + two new §15 gaps), SECURITY (§3 dangerous-directory matching semantics + §5 wording corrected), `docs/tianshu-integration`, `docs/acceptance-config`; single-language: HANDOFF, `SKILL.md`, `usage-examples.md`; new `docs/issue-17-small-fixes-record.md` (measured evidence).
|
|
32
|
+
|
|
33
|
+
### Tests
|
|
34
|
+
|
|
35
|
+
- Full suite **940 passed / 12 skipped** (Windows 10 x64, Node 24.18.0), a net **+42** over v0.6.0's 898: `project-dir-guard` gains `isDangerousProjectDir` table-driven cases for all three platform shapes (including the three key counter-examples `c:/windows.old`, `/etcetera`, `/private/var/folders/...`) and a win32 subtree integration case; `protocol` gains an 11-tool × capability × four-annotation **truth table** and a count assertion; `zcode-handler` gains registration-failure-does-not-dispatch and return-value-consumed cases (the latter asserts a `projectByPath` call count of 0); `core` gains `splitCmd` boundary-semantics locks.
|
|
36
|
+
- The strict stdio gate passes **8/8** on both the dist and src entry points; `pack:check` passes (232 files).
|
|
37
|
+
|
|
11
38
|
## [0.6.0] - 2026-09-23
|
|
12
39
|
|
|
13
40
|
### Added
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.6.1] - 2026-09-23
|
|
11
|
+
|
|
12
|
+
### 新增
|
|
13
|
+
|
|
14
|
+
- **危险目录判定收敛为可注入平台的纯函数** `isDangerousProjectDir(norm, platform)`(`src/util/path.ts`):精确根 / 盘符根 / 系统目录子树三选一判定,任意平台都能验证三平台形态。
|
|
15
|
+
|
|
16
|
+
### 变更
|
|
17
|
+
|
|
18
|
+
- **`verify_task` 的能力由 `read` 改为 `execute`**([issue #17](https://github.com/lanlan0811/tianshu-mcp/issues/17) 问题 2):它会执行项目配置命令、可产生构建产物,本就不是只读。**宿主需知**:其 MCP `readOnlyHint` 由 `true` 变为 **`false`**;`requireApproval` 维持 `false`(仍免审批,R11 结论不变)——**策略层请以 `_meta.requireApproval` 而非 `readOnlyHint` 判断是否需授权**,`docs/tianshu-integration` 双语的 policy 示例已同步。
|
|
19
|
+
- **`ToolDef.capability` 联合类型删除始终无人使用的 `"network"`**,收敛为三族语义(`read` / `write` / `execute`),并写进类型定义与 `tools.ts` 头注释。
|
|
20
|
+
|
|
21
|
+
### 修复
|
|
22
|
+
|
|
23
|
+
- **系统目录改为子树拒绝**(issue #17 问题 4):`/etc`、`/usr`、`/bin`、`/sbin`、`/private/etc` 与 `c:/windows`、`c:/program files`、`c:/program files (x86)` 由「仅精确相等」升级为**边界感知子树拒绝**,修补了 `c:/windows/system32`、`/etc/anything` 这类漏挡。边界感知使 `c:/windows.old`、`/etcetera`、`/usrlocal` 不被误伤;`/var`、`/tmp`、`/opt`、用户主目录等维持精确匹配(macOS 的 `os.tmpdir()` 就是 `/var/folders/...`,子树拒绝会切断测试基座与合法工作区)。
|
|
24
|
+
- **项目登记失败不再静默**(issue #17 问题 3):`run_task` 原先丢弃 `registerProject` 的返回值(`void registered;`)又用 `projectByPath` 二次读取同一条记录;现直接消费返回值、删除冗余读取,登记失败时记 `WARN` 并返回结构化 `isError` 且**不派单**(杜绝「任务已建、项目未登记」的半状态)。
|
|
25
|
+
- **计数与实际不符收尾**(issue #17 问题 1):改正 `test/protocol/protocol.test.ts` 头注释残留的「9 个工具」,并新增 `TOOL_DEFS` 数量硬断言;`ci.yml` 注释、`HANDOFF`(两处)与 `CONTRIBUTING` 双语滞后的「6 场景」同步为 **8**(issue #16 新增两技能场景后未同步)。
|
|
26
|
+
|
|
27
|
+
### 文档
|
|
28
|
+
|
|
29
|
+
- **`cmd` 字符串形态的坑显式化**(issue #17 问题 5,零行为变更):`docs/acceptance-config.md` / `.en.md` 新增实测表(无转义、引号不闭合不报错、空引号产出空参数),`schema.ts` / `store.ts` 注释、`SKILL.md`、`usage-examples.md` 补「推荐一律用数组形态」提示。
|
|
30
|
+
- 双语:README(`verify_task` 能力列 + M31 里程碑)、ARCHITECTURE(工具表三族语义 + `readOnlyHint` 推导规则 + §15 两条新缺口)、SECURITY(§3 危险目录匹配语义 + §5 措辞改准)、`docs/tianshu-integration`、`docs/acceptance-config`;单语:HANDOFF、`SKILL.md`、`usage-examples.md`;新增 `docs/issue-17-small-fixes-record.md`(实测证据)。
|
|
31
|
+
|
|
32
|
+
### 测试
|
|
33
|
+
|
|
34
|
+
- 全量 **940 passed / 12 skipped**(Windows 10 x64,Node 24.18.0),较 v0.6.0 的 898 净增 **42** 项:`project-dir-guard` 新增 `isDangerousProjectDir` 三平台表驱动用例(含 `c:/windows.old`、`/etcetera`、`/private/var/folders/...` 三个关键反例)与 win32 子树集成用例;`protocol` 新增 11 工具 × capability × 四注解**真值表**与数量硬断言;`zcode-handler` 新增登记失败不派单、返回值被消费(断言 `projectByPath` 调用计数为 0);`core` 新增 `splitCmd` 边界语义锁定。
|
|
35
|
+
- 严格 stdio 门禁 dist 与 src 两条入口各 **8/8**;`pack:check` 通过(232 文件)。
|
|
36
|
+
|
|
10
37
|
## [0.6.0] - 2026-09-23
|
|
11
38
|
|
|
12
39
|
### 新增
|
package/README.en.md
CHANGED
|
@@ -192,7 +192,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=qoder, planDoc=./plans/development.m
|
|
|
192
192
|
| `list_tasks` | read | Filtered history of tasks |
|
|
193
193
|
| `get_task_report` | read | Full text of a verification round's report (`report.md`) |
|
|
194
194
|
| `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 |
|
|
195
|
-
| `verify_task` |
|
|
195
|
+
| `verify_task` | execute (no source changes, no approval) | Run one verification pass on a task/project path. Capability is `execute`: it runs the project's configured commands and may produce build artifacts, so MCP `readOnlyHint` is `false` — but it **does not modify sources and still needs no approval**. Optional `idempotencyKey`: a retry with the same key never re-runs (a running pass answers "in progress", a finished one returns the existing report) |
|
|
196
196
|
| `rework_task` | write + approval | Manual rework (feed the failure report back to the same agent) |
|
|
197
197
|
| `get_profiles` | read | Inspect agent adapters and executable discovery results |
|
|
198
198
|
| `prepare_visual_baseline` | write + approval | Capture or import reference images into a reviewable candidate with a digest |
|
|
@@ -450,6 +450,13 @@ On stderr, `INFO` means skipped/installed; `WARN` means a stale copy was upgrade
|
|
|
450
450
|
- **Atomic install and log levels**: "tmp dir → backup → swap in", with rollback on failure so no half-copied tree remains; skip = INFO, overwrite/keep/unknown-source/failure = WARN; after an overwrite, historical `.bak-<timestamp>` directories are pruned to `backupKeep`
|
|
451
451
|
- **Gates**: the strict stdio check gains `skill-locally-modified` / `skill-approve-update` scenarios (6→8); Windows 10 real-machine verification R1–R7 recorded in the [issue-16 record](docs/issue-16-skill-install-hardening-record.md)
|
|
452
452
|
- See the [v0.6.0 release notes](<docs/release-v0.6.0.en.md>)
|
|
453
|
+
- **M31 — Small-fixes sweep (comment & count alignment / capability tri-family / registration failure stops dispatch / system-directory subtree denial / splitCmd docs) + v0.6.1** (2026-09-23) — 42 new cases (issue #17)
|
|
454
|
+
- **System directories now denied by subtree**: `/etc` `/usr` `/bin` `/sbin` `/private/etc` plus `c:/windows` and `c:/program files*` moved from exact-equality to **boundary-aware subtree denial** (`c:/windows.old` and `/etcetera` are not falsely matched). `/var`, `/tmp` and the home directory stay exact-match (`/var/folders/...` on macOS *is* `os.tmpdir()`, so a blanket subtree rule would sever the test base and many legitimate workspaces). The decision is a platform-injectable pure function `isDangerousProjectDir`, so all three platform shapes are verifiable on any OS
|
|
455
|
+
- **`capability` collapsed to three families**: the never-used `"network"` value is removed; `verify_task` moves from `read` to **`execute`** (it runs project commands and may produce build artifacts, so it never was read-only) — so its `readOnlyHint` flips from `true` to `false`, **but it still needs no approval** (`requireApproval` stays false; the R11 conclusion is unchanged). A new **truth-table test** covers all 11 tools × capability × four annotations
|
|
456
|
+
- **Project registration failure now stops dispatch**: `run_task` no longer discards the `registerProject` return value (previously `void registered;` was followed by a redundant `projectByPath` re-read); a registration failure logs WARN and returns a structured `isError`, eliminating the "task created but project unregistered" half-state
|
|
457
|
+
- **`cmd` string form documented**: the bilingual docs and the schema/source comments state "prefer the array form; the string form **supports no escaping**, an unclosed quote does not error, and a typo silently splits into multiple argv". Five boundary cases pin the existing tokenizer semantics (no behaviour change)
|
|
458
|
+
- **Counts and scenario numbers aligned**: the "9 tools" header comment in `protocol.test.ts` is corrected to 11 and a `TOOL_DEFS` count assertion is added; the "6 scenarios" references in `ci.yml` / `HANDOFF` / `CONTRIBUTING` (both languages) are synced to 8 (they lagged behind the two skill scenarios added in issue #16)
|
|
459
|
+
- See the [v0.6.1 release notes](<docs/release-v0.6.1.en.md>); verification record in the [issue-17 record](docs/issue-17-small-fixes-record.md)
|
|
453
460
|
|
|
454
461
|
## Agent support status
|
|
455
462
|
|
package/README.md
CHANGED
|
@@ -188,7 +188,7 @@ run_task(projectPath=D:/xxx/my-app, agentId=qoder, planDoc=./plans/development.m
|
|
|
188
188
|
| `list_tasks` | read | 历史任务过滤列表 |
|
|
189
189
|
| `get_task_report` | read | 某轮验收报告全文(`report.md`) |
|
|
190
190
|
| `cancel_task` | write + 审批 | 取消运行中任务:CLI agent kill 进程树;GUI agent 经 CDP 点击停止并在 `gui.cancelWaitMs`(默认 15s)内有界等待 GUI 空闲,未确认停止时终态明示。对已终态的 GUI 任务,本调用兼任人工确认入口——核实窗口无残留运行后调用可清除 `guiStopUnconfirmed` 待确认标记 |
|
|
191
|
-
| `verify_task` |
|
|
191
|
+
| `verify_task` | execute(不改源码,免审批) | 对任务/项目路径做一次验收。能力归 `execute`:会跑项目配置命令、可能产生构建产物,故 MCP `readOnlyHint` 为 `false`——但**不改源码、仍免审批**。可选 `idempotencyKey`:同键重试不重跑(执行中返回进行中提示,已完成返回既有报告) |
|
|
192
192
|
| `rework_task` | write + 审批 | 手动返修(把失败报告喂回同一 agent) |
|
|
193
193
|
| `get_profiles` | read | 查看 agent 适配与可执行探测结果 |
|
|
194
194
|
| `prepare_visual_baseline` | write + 审批 | 截图或导入参考图,生成待审阅候选和摘要 |
|
|
@@ -451,6 +451,13 @@ run_task(projectPath=D:/xxx/my-app, agentId=qoder, planDoc=./plans/development.m
|
|
|
451
451
|
- **安装原子化与日志分级**:改为「tmp 目录 → 备份 → 换入」,失败回滚不留半成品;跳过=INFO、覆盖/保留/来源不明/失败=WARN;覆盖后按 `backupKeep` 收敛历史 `.bak-<时间戳>`
|
|
452
452
|
- **门禁**:严格 stdio 检查新增 `skill-locally-modified` / `skill-approve-update` 两场景(6→8);Windows 10 真机复验 R1–R7 留档 [issue-16 记录](docs/issue-16-skill-install-hardening-record.md)
|
|
453
453
|
- 详见 [v0.6.0 发布说明](<docs/release-v0.6.0.md>)
|
|
454
|
+
- **M31 — 五项小项扫尾(注释与计数对齐 / capability 三族 / 登记失败不派单 / 系统目录子树拒绝 / splitCmd 文档)+ v0.6.1**(2026-09-23)— 新增 42 项用例(issue #17)
|
|
455
|
+
- **系统目录改为子树拒绝**:`/etc` `/usr` `/bin` `/sbin` `/private/etc` 与 `c:/windows`、`c:/program files*` 由「仅精确相等」升级为**边界感知子树拒绝**(`c:/windows.old`、`/etcetera` 不误伤);`/var`、`/tmp`、家目录维持精确匹配(macOS 的 `/var/folders/...` 正是 `os.tmpdir()`,一刀切会切断测试基座与大量合法工作区)。判定抽为可注入平台的纯函数 `isDangerousProjectDir`,任意平台都能验证三平台形态
|
|
456
|
+
- **`capability` 收敛为三族语义**:删除始终无人使用的 `"network"`;`verify_task` 由 `read` 改为 **`execute`**(会跑项目命令、可产生构建产物,本就不是只读)——连带 `readOnlyHint` 由 `true` 变 `false`,**但仍免审批**(`requireApproval` 维持 false,R11 结论不变);新增 11 工具 × capability × 四注解的**真值表测试**锁死全部映射
|
|
457
|
+
- **项目登记失败即终止派单**:`run_task` 不再丢弃 `registerProject` 的返回值(原先 `void registered;` 后还冗余地 `projectByPath` 二次读取);登记失败时记 WARN 并返回结构化 `isError`,杜绝「任务已建、项目未登记」的半状态
|
|
458
|
+
- **`cmd` 字符串形态文档化**:双语文档与 schema/代码注释明示「推荐数组形态;字符串形态**不支持转义**、引号不闭合不报错、写错会静默拆成多个 argv」;补 5 条边界用例锁定既有分词语义(零行为变更)
|
|
459
|
+
- **计数与场景数对齐**:`protocol.test.ts` 头注释「9 个工具」更正为 11 并新增 `TOOL_DEFS` 数量硬断言;`ci.yml` / `HANDOFF` / `CONTRIBUTING` 双语的「6 场景」同步为 8(issue #16 新增两技能场景后未同步)
|
|
460
|
+
- 详见 [v0.6.1 发布说明](<docs/release-v0.6.1.md>);验证记录见 [issue-17 记录](docs/issue-17-small-fixes-record.md)
|
|
454
461
|
|
|
455
462
|
## Agent 适配现状
|
|
456
463
|
|
package/dist/config/schema.js
CHANGED
|
@@ -118,7 +118,11 @@ export const CancelTaskParamsSchema = z.object({
|
|
|
118
118
|
taskId: z.string().min(1),
|
|
119
119
|
reason: z.string().optional(),
|
|
120
120
|
});
|
|
121
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* 验收命令:**推荐数组形态**(结构化 argv,无歧义)。
|
|
123
|
+
* 字符串形态为兼容保留:经极简分词(不经过 shell),**不支持转义**,引号不闭合
|
|
124
|
+
* 不报错,含空格参数必须手写整段引号,写错会静默拆成多个 argv——详见 docs/acceptance-config.md。
|
|
125
|
+
*/
|
|
122
126
|
const CheckCmd = z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]);
|
|
123
127
|
export const AcceptanceCheckSchema = z.object({
|
|
124
128
|
name: z.string().min(1),
|
package/dist/config/store.js
CHANGED
|
@@ -16,9 +16,19 @@ export function resolveDataHome() {
|
|
|
16
16
|
return path.resolve(env.trim());
|
|
17
17
|
return path.join(os.homedir(), ".tianshu-mcp");
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* 把 task.jsonl 等事件里的字符串命令转成规范化 argv。
|
|
21
|
+
*
|
|
22
|
+
* 极简分词,**只识别整段引号包裹**(`"..."` / `'...'`),不执行 shell:
|
|
23
|
+
* - **不支持转义**:`\"` 只是普通字符,无法在双引号内转义引号;
|
|
24
|
+
* - 引号不闭合**不报错**,引号退化为普通字符按空白切分;
|
|
25
|
+
* - 空引号产出空字符串参数。
|
|
26
|
+
*
|
|
27
|
+
* 因此含空格的路径/参数必须手写引号,写错会静默拆成多个 argv。验收配置的
|
|
28
|
+
* `cmd` **推荐一律用数组形态**(见 docs/acceptance-config.md),字符串形态仅为兼容保留。
|
|
29
|
+
*/
|
|
20
30
|
export function splitCmd(cmd) {
|
|
21
|
-
// 极简 argv 分词:支持引号包裹("..." 与 '...'),不执行 shell
|
|
31
|
+
// 极简 argv 分词:支持引号包裹("..." 与 '...'),不执行 shell、不做转义
|
|
22
32
|
const out = [];
|
|
23
33
|
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
24
34
|
let m;
|
package/dist/mcp/handlers.js
CHANGED
|
@@ -225,11 +225,19 @@ function runTaskHandler(ctx, defaults, idempotency) {
|
|
|
225
225
|
workspaceMode: "project",
|
|
226
226
|
projectPath: norm,
|
|
227
227
|
});
|
|
228
|
-
// 项目自动登记(首次出现即登记,R10
|
|
228
|
+
// 项目自动登记(首次出现即登记,R10)。登记失败即终止派单:否则会留下
|
|
229
|
+
// 「任务已建、项目未登记」的半状态,projectByPath / list_tasks 等按项目维度的
|
|
230
|
+
// 查询全部失真。registerProject 已返回记录,此处直接消费,不再二次读取。
|
|
229
231
|
const agentId = args.agentId ?? defaults.defaultAgentId;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
232
|
+
let record;
|
|
233
|
+
try {
|
|
234
|
+
({ record } = await dataHome.registerProject(norm, agentId));
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
238
|
+
ctx.logger.warn(`项目登记失败,未派单:${norm}(${msg})`);
|
|
239
|
+
return errorResult(`项目登记失败,未派单:${msg}`);
|
|
240
|
+
}
|
|
233
241
|
const finalAgentId = record?.defaultAgentId ?? agentId;
|
|
234
242
|
// 校验 agent 可解析(立即失败返回,不给天枢排队假象)
|
|
235
243
|
const resolved = await ctx.registry.resolve(finalAgentId, true);
|
package/dist/mcp/tools.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 工具注册表:11 个工具的 name/description/inputSchema/capability/approval 元数据。
|
|
3
3
|
* MCP 层用 inputSchema 声明;capability/requireApproval 供天枢 policy(§5/§11.3)。
|
|
4
|
-
* 能力标注遵守 R11
|
|
4
|
+
* 能力标注遵守 R11(三族语义):
|
|
5
|
+
* - read:读/查询,无副作用;`server.ts` 据此推导 MCP `readOnlyHint: true`。
|
|
6
|
+
* - write:派活/取消/返修/视觉基准写盘等有副作用操作,全部 requireApproval。
|
|
7
|
+
* - execute:会执行项目侧命令(可产生构建产物),但不改源码;当前仅 `verify_task`,
|
|
8
|
+
* 按 R11 仍免审批——`readOnlyHint` 会因此为 false,审批与否由 `_meta.requireApproval` 单独承载。
|
|
5
9
|
*/
|
|
6
10
|
import { z } from "zod";
|
|
7
11
|
import { PrepareBaselineSchema, ApproveBaselineSchema } from "../visual/baselines.js";
|
|
@@ -67,7 +71,7 @@ export const TOOL_DEFS = [
|
|
|
67
71
|
name: "verify_task",
|
|
68
72
|
description: "对已完成任务或项目路径执行一次验收(不改源码):自动命令检查 + 代码分析(相对 git 基线)。可用 extraChecks 临时加验。需任务/项目二选一。可选 idempotencyKey:同一 key 重试不重跑验收——执行中的同键请求返回进行中提示,已完成的直接返回既有报告与轮次,参数变更则报冲突。",
|
|
69
73
|
inputSchema: VerifyTaskParamsSchema,
|
|
70
|
-
capability: "
|
|
74
|
+
capability: "execute",
|
|
71
75
|
requireApproval: false,
|
|
72
76
|
},
|
|
73
77
|
{
|
package/dist/server.js
CHANGED
|
@@ -63,6 +63,9 @@ export async function buildServer(opts = {}) {
|
|
|
63
63
|
inputSchema: tool.inputSchema,
|
|
64
64
|
_meta: { requireApproval: tool.requireApproval, capability: tool.capability },
|
|
65
65
|
annotations: {
|
|
66
|
+
// readOnlyHint 只对 capability:"read" 为 true。capability:"execute"(目前仅 verify_task)
|
|
67
|
+
// 会跑项目侧命令、可产生构建产物,故 readOnlyHint 诚实为 false——但那不等于需要审批:
|
|
68
|
+
// 审批与否由 _meta.requireApproval 单独承载(verify_task 按 R11 仍免审批)。
|
|
66
69
|
readOnlyHint: tool.capability === "read",
|
|
67
70
|
destructiveHint: tool.name === "cancel_task" ||
|
|
68
71
|
tool.name === "rework_task" ||
|
package/dist/util/path.js
CHANGED
|
@@ -52,6 +52,8 @@ export function resolveProjectDir(p) {
|
|
|
52
52
|
/**
|
|
53
53
|
* 禁止作为 projectPath 的根级目录(normPath 形态,精确相等才命中):
|
|
54
54
|
* worker(CLI 沙箱/GUI 绑定)可写其整个子树——传错一次就是全盘写入事故。
|
|
55
|
+
* 其**子目录**是否也危险见 DANGEROUS_SUBTREES(本集合内的 /etc、/usr、/bin、
|
|
56
|
+
* /sbin 因同时列入子树集而冗余,保留是为了集合语义的完整与可读)。
|
|
55
57
|
*/
|
|
56
58
|
const DANGEROUS_ROOTS = new Set([
|
|
57
59
|
"/",
|
|
@@ -80,6 +82,26 @@ const DANGEROUS_ROOTS = new Set([
|
|
|
80
82
|
"c:/program files (x86)",
|
|
81
83
|
"d:/",
|
|
82
84
|
]);
|
|
85
|
+
/**
|
|
86
|
+
* 系统目录**子树**拒绝前缀(边界感知:命中要求后随 "/" 或字符串结束):
|
|
87
|
+
* 这些目录及其全部子目录都不可能是合法工作区,而 worker 对整个子树可写,
|
|
88
|
+
* 传错一次就是系统级事故。`/etc` 在 macOS 是符号链接,realpath 后落到
|
|
89
|
+
* `/private/etc`,故两形态都要覆盖。
|
|
90
|
+
*
|
|
91
|
+
* 注意 `/var`、`/tmp`、`/opt`、`/library`、`/system`、`/root`、家目录与
|
|
92
|
+
* `c:/users` **不在**此列——它们之下存在合法工作区(macOS 的 `/var/folders/…`
|
|
93
|
+
* 正是 `os.tmpdir()`,测试与大量临时工作区都建在那里),故维持精确相等。
|
|
94
|
+
*/
|
|
95
|
+
const DANGEROUS_SUBTREES = [
|
|
96
|
+
"/etc",
|
|
97
|
+
"/usr",
|
|
98
|
+
"/bin",
|
|
99
|
+
"/sbin",
|
|
100
|
+
"/private/etc",
|
|
101
|
+
"c:/windows",
|
|
102
|
+
"c:/program files",
|
|
103
|
+
"c:/program files (x86)",
|
|
104
|
+
];
|
|
83
105
|
/**
|
|
84
106
|
* DANGEROUS_ROOTS 查询 key:win32 下具名目录按大小写不敏感语义小写化
|
|
85
107
|
* (否则 `C:\Windows` 归一为 `c:/Windows`,与 Set 内小写字面量永不命中)。
|
|
@@ -88,24 +110,38 @@ const DANGEROUS_ROOTS = new Set([
|
|
|
88
110
|
export function dangerKey(norm, platform = process.platform) {
|
|
89
111
|
return platform === "win32" ? norm.toLowerCase() : norm;
|
|
90
112
|
}
|
|
91
|
-
/**
|
|
92
|
-
* 写入类入口(run_task / verify_task)的项目目录闸门:
|
|
93
|
-
* resolveProjectDir 之上再拒「主目录本身」与「系统/根级目录」。
|
|
94
|
-
* 注意只挡精确相等的根——/tmp/xxx、/Users/name/repo 等子目录不受影响。
|
|
95
|
-
*/
|
|
96
113
|
/** 盘符根(Windows 的 C:\ / D:\ 等):normPath 会剥掉尾斜杠得到 "d:",需单独判定 */
|
|
97
114
|
function isDriveRoot(norm) {
|
|
98
115
|
return /^[a-z]:$/.test(norm);
|
|
99
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* 危险目录判定:精确根 / 盘符根 / 系统目录子树三选一命中即危险。
|
|
119
|
+
* 纯函数且 platform 可注入——任何平台上都能验证三平台形态(含 macOS 的
|
|
120
|
+
* `/private/...` 与 win32 的大小写归一),无需等到对应平台的 CI 作业。
|
|
121
|
+
*/
|
|
122
|
+
export function isDangerousProjectDir(norm, platform = process.platform) {
|
|
123
|
+
const key = dangerKey(norm, platform);
|
|
124
|
+
if (DANGEROUS_ROOTS.has(key) || isDriveRoot(key))
|
|
125
|
+
return true;
|
|
126
|
+
return DANGEROUS_SUBTREES.some((sub) => {
|
|
127
|
+
const s = dangerKey(sub, platform);
|
|
128
|
+
return key === s || key.startsWith(`${s}/`);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 写入类入口(run_task / verify_task)的项目目录闸门:
|
|
133
|
+
* resolveProjectDir 之上再拒「主目录本身」与「系统/根级目录及其子树」。
|
|
134
|
+
* `/tmp/xxx`、`/Users/name/repo`、`/var/folders/...` 等合法工作区不受影响。
|
|
135
|
+
*/
|
|
100
136
|
export function assertSafeProjectDir(p) {
|
|
101
137
|
const r = resolveProjectDir(p);
|
|
102
138
|
if (dangerKey(r.norm) === dangerKey(normPath(os.homedir()))) {
|
|
103
139
|
throw new Error(`projectPath 不能是用户主目录本身(worker 将可写整个主目录): ${r.canonical}`);
|
|
104
140
|
}
|
|
105
|
-
//
|
|
106
|
-
// 与清单里的 "d:/"
|
|
107
|
-
if (
|
|
108
|
-
throw new Error(`projectPath
|
|
141
|
+
// 盘符根也不在 DANGEROUS_ROOTS 里:normPath 把 "D:\" 归一为 "d:"(尾斜杠被剥掉),
|
|
142
|
+
// 与清单里的 "d:/" 永不相等;isDangerousProjectDir 内部单独判定,覆盖所有盘符。
|
|
143
|
+
if (isDangerousProjectDir(r.norm)) {
|
|
144
|
+
throw new Error(`projectPath 指向系统/根级目录或其子树,worker 写权限将覆盖整个子树,已拒绝: ${r.canonical}`);
|
|
109
145
|
}
|
|
110
146
|
return r;
|
|
111
147
|
}
|
package/package.json
CHANGED
|
@@ -40,7 +40,7 @@ run_task(秒回 taskId,异步)
|
|
|
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
42
|
| `get_task_report` | read | 读某轮验收报告 **Markdown 全文** | `taskId`、`round?`(0-based,缺省最新) |
|
|
43
|
-
| `verify_task` |
|
|
43
|
+
| `verify_task` | execute(不改源码、无需审批) | 对任务或任意项目**独立验收**(会跑项目命令、可产生构建产物,故 `readOnlyHint=false`;不改源码、无需审批) | `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`(必填) |
|
|
46
46
|
| `cancel_task` | write + 审批 | 取消运行中任务(GUI agent 尽力点停止并回读) | `taskId`、`reason?` |
|
|
@@ -225,6 +225,7 @@ meta 的 `needsUserKind` 给出等待类型,`pendingQuestion` 给出问题原
|
|
|
225
225
|
- 传 `taskId`:用该任务动工前基线复跑,只更新其验收结论字段(`latestVerificationVerdict`),**不改写原任务终态**。
|
|
226
226
|
- 传 `projectPath`:独立健康检查,不设 `baselineRef` 时按当前基线;`baselineRef` 可填 git ref(如 `HEAD~1`),传 `task` 表示沿用任务基线。**独立路径验收不产生独立任务,遇配置/视觉阻塞时记录为 `needs_attention`。**
|
|
227
227
|
- `extraChecks` 临时加验(`name`/`cmd`/`timeoutMs`/`optional`);`checksMode` 缺省 `append`(项目/默认集 + 追加),`replace` 才只跑 extraChecks。
|
|
228
|
+
- **`cmd` 用数组形态**:`"cmd": ["npm", "run", "typecheck"]`。字符串形态仅为兼容保留——只识别整段引号包裹、**不支持转义**,引号不闭合**不报错**而是静默按空白拆成多个 argv,含空格路径务必手写引号或改用数组。
|
|
228
229
|
- 命令优先级:`extraChecks` > 项目 `.tianshu-mcp/acceptance.json` > projects.json 管理员补录 > 按技术栈推导的默认集。
|
|
229
230
|
- 任务没有保存的动工前基线时会报错——改用 `projectPath` 或传 `baselineRef`。
|
|
230
231
|
|
|
@@ -340,7 +340,7 @@ run_task(projectPath=/path/to/项目, agentId=codex-cli,
|
|
|
340
340
|
|
|
341
341
|
## 6. 验收:verify_task 示例
|
|
342
342
|
|
|
343
|
-
|
|
343
|
+
不改源码、无需审批(能力归 `execute`:会跑项目命令、可产生构建产物)。三种典型用法:
|
|
344
344
|
|
|
345
345
|
```text
|
|
346
346
|
# 1) 复跑某任务的验收(用该任务动工前基线;round 缺省对最新状态)
|
|
@@ -365,7 +365,7 @@ verify_task(projectPath=D:/repo/app, checksMode=replace,
|
|
|
365
365
|
- **幂等键用法**:`idempotencyKey` 由宿主按「本次逻辑意图」生成**一次**,之后所有重试复用同一条;参数一旦变化必须换 key。`run_task` 的键与 `verify_task` 的键互不影响(各自命名空间)。TTL 默认 24h(见 §9.7),过期后同键会重新真实执行。
|
|
366
366
|
|
|
367
367
|
- `checksMode=replace` 时**只用** `extraChecks`,不跑项目基础集。
|
|
368
|
-
- `extraChecks` 单条支持 `name` / `cmd`(argv 数组或字符串)/ `timeoutMs` / `optional`(`optional:true` 失败只记 warning
|
|
368
|
+
- `extraChecks` 单条支持 `name` / `cmd`(argv 数组或字符串)/ `timeoutMs` / `optional`(`optional:true` 失败只记 warning)。**`cmd` 请用数组**;字符串形态不支持转义,引号不闭合不报错而是静默拆成多个 argv。
|
|
369
369
|
- 命令优先级:`extraChecks` > 项目 `.tianshu-mcp/acceptance.json` > projects.json 管理员补录 > 按技术栈推导的默认集。
|
|
370
370
|
- 传 `taskId`:只更新该任务的验收结论字段,**不改写原任务终态**;任务没有保存的动工前基线时会报错,改用 `projectPath` 或传 `baselineRef`。
|
|
371
371
|
- 传 `projectPath`:`baselineRef` 只能是 **git ref**(如 `HEAD~1`);写任务 ID 会报错。
|