dsh-any-agent 0.3.9 → 0.5.0
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.md +153 -1
- package/README.md +75 -634
- package/README.zh.md +64 -649
- package/bin/dsh-any-agent.mjs +59 -0
- package/demo/mcp-client.mjs +130 -0
- package/demo/run.mjs +326 -0
- package/demo/transcript.json +47 -0
- package/docs/brand/README.md +58 -0
- package/docs/brand/header.jpg +0 -0
- package/docs/en/architecture.md +43 -0
- package/docs/en/compatibility.md +51 -0
- package/docs/en/demo.md +90 -0
- package/docs/en/install.md +257 -0
- package/docs/en/limits.md +27 -0
- package/docs/en/locking.md +24 -0
- package/docs/en/reference.md +40 -0
- package/docs/en/release.md +33 -0
- package/docs/en/security.md +59 -0
- package/docs/en/team.md +74 -0
- package/docs/en/tests.md +96 -0
- package/docs/en/usage.md +71 -0
- package/docs/zh/architecture.md +43 -0
- package/docs/zh/compatibility.md +49 -0
- package/docs/zh/demo.md +90 -0
- package/docs/zh/install.md +274 -0
- package/docs/zh/limits.md +30 -0
- package/docs/zh/locking.md +24 -0
- package/docs/zh/reference.md +40 -0
- package/docs/zh/release.md +38 -0
- package/docs/zh/security.md +59 -0
- package/docs/zh/team.md +71 -0
- package/docs/zh/tests.md +95 -0
- package/docs/zh/usage.md +69 -0
- package/package.json +5 -1
- package/scripts/check-release.mjs +73 -0
- package/src/acp.mjs +31 -3
- package/src/audit.mjs +32 -0
- package/src/bootstrap.mjs +26 -1
- package/src/config.mjs +178 -9
- package/src/host.mjs +148 -0
- package/src/hub.mjs +494 -73
- package/src/procstat.mjs +72 -0
- package/src/tools.mjs +561 -27
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
> 返回 [README](../../README.zh.md)
|
|
2
|
+
|
|
3
|
+
# 📦 前置要求
|
|
4
|
+
|
|
5
|
+
- **Node.js ≥ 22.15** —— 服务用 `node:zlib` 的 `zstdDecompressSync` 读 DSH 会话头,这个 API 在 22.15 之前不存在
|
|
6
|
+
- **已安装 DSH**(`@deepseek-ai/dsh`),且 `~/.dsh` 下已有可用的 provider 与凭据(GUI 能正常聊天即可)
|
|
7
|
+
|
|
8
|
+
> 本服务**不自带** provider 配置,也**从不**接收或保存 API 密钥。
|
|
9
|
+
|
|
10
|
+
## 🚀 安装配置
|
|
11
|
+
|
|
12
|
+
### 1. 让 profile 自己就绪(通常无需手工操作)
|
|
13
|
+
|
|
14
|
+
> 📌 **为什么还叫 `dsh-mcp`?** 项目已改名 **dsh-any-agent**,但 DSH profile 名、`DSH_MCP_*` 环境变量、`# dsh-mcp:managed` 标记行都**故意保留旧名**。它们是已落盘的稳定标识符:改了会让现存 profile、会话和你的客户端配置一起失联。这是刻意的向后兼容,不是漏改。
|
|
15
|
+
|
|
16
|
+
首次拉起 DSH 时,[`src/bootstrap.mjs`](../../src/bootstrap.mjs) 会自动:
|
|
17
|
+
|
|
18
|
+
1. 在 `$DSH_HOME/profiles/dsh-mcp/` 建一个基于出厂 `acp` 模板的 profile;
|
|
19
|
+
2. 从本机**已有**的 profile(`$DSH_HOME/cordis.patch.yml` → `desktop` → `web` → `headless` → `acp` 取第一个有配置的)复制模型路由;
|
|
20
|
+
3. 写 `acp`、`agent-default-model` 与三档 `permission` presets(**不写 `defaultPreset`**,否则会覆盖权限档),让 ACP 会话用与 GUI 相同的模型,并让 read-only 真正生效。
|
|
21
|
+
|
|
22
|
+
复制的内容只有 `apiKeyEnv` / `baseURL` / `api` / `models` / `reasoningEfforts`;`apiKey`(适配器从不读取的死字段)与 `headers`(可能夹凭据)一律剥掉。凭据本身由 DSH 的凭据服务逐次请求解析,路径与 GUI 完全一致。
|
|
23
|
+
|
|
24
|
+
想要的来源不是自动挑的那个,可设 `DSH_MCP_ROUTE_SOURCE=desktop`(或 `__home__`);彻底关掉自动同步则设 `DSH_MCP_BOOTSTRAP=0`。
|
|
25
|
+
|
|
26
|
+
<details>
|
|
27
|
+
<summary>手工维护 profile 与 ACP 配置细节(可选)</summary>
|
|
28
|
+
|
|
29
|
+
### 1b. 手工维护(可选)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
dsh --profile dsh-mcp --from-default-profile acp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
然后编辑 `~/.dsh/profiles/dsh-mcp/cordis.patch.yml`,**并删掉文件开头的 `# dsh-mcp:managed` 行** —— 有那行时本服务每次启动都会按来源重新生成,手改会被覆盖。可以从 [`profile-example/cordis.patch.yml`](../../profile-example/cordis.patch.yml) 起步。
|
|
36
|
+
|
|
37
|
+
> ⚠️ **这里有两个坑**(都是踩出来的):
|
|
38
|
+
>
|
|
39
|
+
> 1. **ACP 的模型不走 `agent-default-model`**,而是读 `dsh-acp` 插件自己的 `config.provider` / `config.model` —— 而 `acp` bundle 把它硬编码成了 `deepseek-official`。不覆盖 `acp` 这一行,会话会静默跑在错误的 provider 上,报 `no API key for provider route "deepseek-official"`。
|
|
40
|
+
> 2. **ACP 的 `reasoning_effort` 默认是空串**(= "Provider default"),不是最大档。本服务在每次 `session/new` 以及每次 `session/resume` 之后都会显式设成 `max` —— 因为新进程不会记住上一次的选择。
|
|
41
|
+
|
|
42
|
+
</details>
|
|
43
|
+
|
|
44
|
+
### 2. 注册 MCP 服务
|
|
45
|
+
|
|
46
|
+
执行[快速开始](../../README.zh.md#快速开始)中的三条命令,再重新连接客户端。需要手工配置时,可展开下方的客户端示例。
|
|
47
|
+
|
|
48
|
+
`setup` 会找出你机器上的 MCP 客户端 —— **Claude Code、Claude Desktop、Codex、Cursor、Windsurf、Zed、Gemini CLI、opencode、VS Code** —— 逐个写进去,没装的跳过。它的安全性是结构性保证的:
|
|
49
|
+
|
|
50
|
+
- **默认 dry-run**,必须加 `--yes` 才写;预览模式下**不启动任何子进程**,只读取它将要触及的那些文件
|
|
51
|
+
- 每个文件写前备份成 `<file>.bak-dsh-<时间戳>`,内容是**写入前的原始字节**
|
|
52
|
+
- 写入前后各解析一次,坏配置绝不落盘;坏 JSON 与只读文件一律拒绝而不是硬写,目录不存在则安全创建
|
|
53
|
+
- **文本级写入**:只替换自己那个条目的字节,缩进(2 空格 / 4 空格 / Tab)、键顺序、CRLF 行尾、UTF-8 BOM、有无末尾换行都原样保留
|
|
54
|
+
- **幂等**,重复运行只改变有差异的部分;第二次 `--yes` 不写盘、不新增 `.bak`
|
|
55
|
+
|
|
56
|
+
**实测影响范围(2026-11-02,本机 plan 模式):** 9 个客户端中检测到 5 个(Claude Code、Codex、Cursor、Zed、VS Code),其余 4 个跳过;`--json` 报告 `mode=plan, changed=5, skipped=4, failed=0`。所有已存在客户端配置的 SHA-256、大小与 mtime **全部未变**。真正执行 `--yes` 也是增量写入:只写 `dsh-any-agent` 这一个条目,其余字节原样保留,写前先落一份 `.bak-dsh-<时间戳>`;`--remove` 也只删这一个条目。
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
dsh-any-agent-setup --yes --only cursor,zed # 只装这几个
|
|
60
|
+
dsh-any-agent-setup --yes --permission read-only
|
|
61
|
+
dsh-any-agent-setup --remove --yes # 全部移除,只摘自己那条
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
<details>
|
|
65
|
+
<summary>高级开关与各客户端的配置路径</summary>
|
|
66
|
+
|
|
67
|
+
**新增的三个开关**
|
|
68
|
+
|
|
69
|
+
| 开关 | 作用 |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `--json` | **stdout 只有一个可解析的 JSON 对象,人类可读日志全部走 stderr**。结构:`{ ok, mode, clients: [{ id, label, path, status, summary, error? }], changed, skipped, failed }`,其中 `mode` 取 `plan` / `apply` / `remove`。这是**契约**,不是便利功能:脚本可以把 stdout 直接喂给 JSON 解析器 |
|
|
72
|
+
| `--home <dir>` | 用指定的 home 目录代替真实 `HOME` / `USERPROFILE` / `APPDATA`,注册—注销可以整套演练而不碰真实配置 |
|
|
73
|
+
| `--force` | 只做两件事:(a) 目录或文件不存在时创建它们(默认也会创建,这个开关让它在自动化里、以及检测不到客户端时也照做);(b) 目标文件里已有同名条目、但形状不是本程序期望的样子时,允许覆盖它。它**绝不删别人的条目**,也**绝不跳过校验** |
|
|
74
|
+
|
|
75
|
+
**每个客户端到底会碰哪个文件**
|
|
76
|
+
|
|
77
|
+
写入前一律先落 `.bak-dsh-<时间戳>` 备份。表中路径均为通用示例,「自己那条」指名为 `dsh-any-agent` 的那一个条目 / 那一段。
|
|
78
|
+
|
|
79
|
+
| 客户端(`--only` 取值) | 配置文件 | 容器键 | 安装 / 更新 | 卸载(`--remove`) | 撤销 |
|
|
80
|
+
|---|---|---|---|---|---|
|
|
81
|
+
| Claude Code(`claude-code`) | `~/.claude.json` | `mcpServers` | 只增 / 改自己那条,且带 `type: "stdio"` | 删掉自己那条 | 把备份复制回去 |
|
|
82
|
+
| Claude Desktop(`claude-desktop`) | Windows `%APPDATA%\Claude\claude_desktop_config.json`;macOS `~/Library/Application Support/Claude/claude_desktop_config.json`;Linux `~/.config/Claude/claude_desktop_config.json` | `mcpServers` | 只增 / 改自己那条 | 删掉自己那条 | 把备份复制回去 |
|
|
83
|
+
| Codex(`codex`) | `~/.codex/config.toml` | `[mcp_servers.dsh-any-agent]` | 按行追加自己那两段 TOML | 只删这两段 | 把备份复制回去 |
|
|
84
|
+
| Cursor(`cursor`) | `~/.cursor/mcp.json` | `mcpServers` | 只增 / 改自己那条 | 删掉自己那条 | 把备份复制回去 |
|
|
85
|
+
| Windsurf(`windsurf`) | `~/.codeium/windsurf/mcp_config.json` | `mcpServers` | 只增 / 改自己那条 | 删掉自己那条 | 把备份复制回去 |
|
|
86
|
+
| Zed(`zed`) | Windows `%APPDATA%\Zed\settings.json`;其他平台 `~/.config/zed/settings.json` | `context_servers` | 只增 / 改自己那条 | 删掉自己那条 | 把备份复制回去 |
|
|
87
|
+
| Gemini CLI(`gemini`) | `~/.gemini/settings.json` | `mcpServers` | 只增 / 改自己那条 | 删掉自己那条 | 把备份复制回去 |
|
|
88
|
+
| opencode(`opencode`) | `~/.config/opencode/opencode.json` | `mcp` | 只增 / 改自己那条,且按 opencode 的形状写:`{ type: "local", command: [exe, ...args], enabled: true }` —— 一个数组,不是分开的 `command`/`args` | 删掉自己那条 | 把备份复制回去 |
|
|
89
|
+
| VS Code(`vscode`) | Windows `%APPDATA%\Code\User\mcp.json`;其他平台是 VS Code 用户目录下的同名 `mcp.json` | `servers` —— **不是** `mcpServers` | 只增 / 改自己那条 | 删掉自己那条 | 把备份复制回去 |
|
|
90
|
+
|
|
91
|
+
具体路径以 `--json` 输出里每个客户端的 `path` 字段为准 —— 那是本次运行在你这个系统上真正要写的文件,比任何文档都准。
|
|
92
|
+
|
|
93
|
+
有三种形状值得单独点出来,写错了是**静默失效**。**VS Code** 的 MCP 配置放在 `servers` 下,不是 `mcpServers`;按常见写法写进去,编辑器会直接忽略。**opencode** 用的是**一个** `command` 数组(`{ type: "local", command: [exe, ...args], enabled: true }`),不是分开的 `command`/`args`。**Claude Code** 则是本次发布**完全不用 JSON 库**的原因:`~/.claude.json` 的 `projects` 映射里确实存在**只差大小写**的重复键(`.../Rust` 和 `.../rust` 并存),朴素的 `JSON.parse` 在那里会失败,而任何整份重写都会毁掉用户的项目列表。所以改用按字符区间扫描、**从不构造值对象**的扫描器;早先那版「调 `claude mcp add`」也出于同样理由被放弃 —— 那条命令会用它自己的格式重写整个文件,且不产生我们可以回滚的备份。
|
|
94
|
+
|
|
95
|
+
</details>
|
|
96
|
+
|
|
97
|
+
<details>
|
|
98
|
+
<summary>注册器检查与注册、撤销的往返验证</summary>
|
|
99
|
+
|
|
100
|
+
**setup 会动什么、不会动什么**
|
|
101
|
+
|
|
102
|
+
它只做新增,而且只动自己那一条。在真正装着别的 MCP 服务器的机器上验证过(Cursor 4 个、Codex 2 个段、Claude Code 5 个、Zed 33 个配置键):所有既有条目**逐字节不变**,键顺序不变,唯一的差异是每个客户端多了一个条目。
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Cursor 4 -> 5 个条目 既有条目逐字节相同
|
|
106
|
+
Codex 2 -> 3 个段 node_repl 与 rea 内容不变
|
|
107
|
+
Claude Code 5 -> 6 个服务 原有四个都还在列表里
|
|
108
|
+
Zed 33 -> 33 个键 只有 context_servers 多了个子项
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
和 0.1.x 的区别:过去 JSON 配置是**整份重新序列化**的(值保留、排版丢失),所以「白空格不保证原样」。现在写入是文本级的 —— 除了自己那一条,文件其余部分逐字节相同:4 空格缩进、2 空格缩进、Tab、单行 JSON、没有末尾换行、CRLF 行尾、带 UTF-8 BOM,一律原样。
|
|
112
|
+
|
|
113
|
+
Codex 的 `config.toml` 同理,是按行处理的:其它 `[表]` 及其注释、顶层键(`model`、`approval_policy`、`sandbox_mode` 等)、CRLF 行尾都原样保留 —— `--remove` 只删 `[mcp_servers.dsh-any-agent]` 那两段,其余一律不动。
|
|
114
|
+
|
|
115
|
+
这些断言的详细覆盖见 [`test/`](../../test/)(`test/config-safety.mjs` 共 407 项,逐条断言上面每一条)。
|
|
116
|
+
|
|
117
|
+
**在真实 home 上做过只读验证。** 用 `setup --json`(纯计划模式)跑一遍开发机上的真实配置:9 个客户端全部报出,而现存的每份配置都**原封未动** —— size、mtime、SHA-256 全同,没有新增任何 `.bak-dsh-*`,也没有新建任何文件:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
~/.claude.json 182693 B 未变
|
|
121
|
+
~/.cursor/mcp.json 1396 B 未变
|
|
122
|
+
~/.codex/config.toml 6647 B 未变
|
|
123
|
+
~/.gemini/settings.json 465 B 未变
|
|
124
|
+
~/.config/opencode/opencode.json 441 B 未变
|
|
125
|
+
%APPDATA%/Zed/settings.json 5647 B 未变
|
|
126
|
+
%APPDATA%/Claude/claude_desktop_config.json 247 B 未变
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
同一条命令加 `--home <临时目录>` 时,报出的**每一个** path 都落进了那个目录(`APPDATA` 也跟着走)—— 注册 / 注销整套演练之所以安全,靠的就是这条。
|
|
130
|
+
|
|
131
|
+
**在真实 home 上做过完整的注册 → 撤销往返(不只是预览)。** 备份全部 7 份配置后,实际执行 `--yes` 再执行 `--remove --yes`,然后逐字节核对:
|
|
132
|
+
|
|
133
|
+
| 客户端 | 注册前 | 注册后 | 撤销后 | 其它 MCP 服务器 |
|
|
134
|
+
|---|---|---|---|---|
|
|
135
|
+
| ~/.claude.json | 182693 B | 182693 B(原本已是最新) | 逐字节回到基线 | 未动;67 个顶层键、60 个 projects 条目、两个只差大小写的重复键都还在 |
|
|
136
|
+
| ~/.cursor/mcp.json | 1396 B | 1389 B | 1009 B | stitch / obsidian-mcp-server / codegraph / rea 逐字段相同 |
|
|
137
|
+
| ~/.codex/config.toml | 6647 B | 6647 B(原本已是最新) | 6239 B | 其它 `[表]`、注释、顶层键全部保留 |
|
|
138
|
+
| %APPDATA%/Zed/settings.json | 5647 B | 5647 B(原本已是最新) | 5240 B | 32 个顶层设置键全部保留 |
|
|
139
|
+
| ~/.gemini/settings.json | 465 B | 845 B | 465 B | 逐字节还原 |
|
|
140
|
+
| ~/.config/opencode/opencode.json | 441 B | 869 B | 441 B | `$schema` 与 pencil 条目保留 |
|
|
141
|
+
| %APPDATA%/Claude/claude_desktop_config.json | 247 B | 627 B | 247 B | 逐字节还原 |
|
|
142
|
+
|
|
143
|
+
撤销的验收方式不是「看起来对」,而是**从备份原文里用同一套编辑引擎摘掉 `dsh-any-agent` 这一条,结果必须与磁盘上的当前文件逐字节相同** —— 7 个文件全部通过。这条证明撤销是注册的严格逆运算,不会顺手带走别的东西。
|
|
144
|
+
|
|
145
|
+
撤销后所有 JSON 仍可解析、`codex mcp list` 仍能正常读取配置,客户端不需要人工修复。
|
|
146
|
+
|
|
147
|
+
</details>
|
|
148
|
+
|
|
149
|
+
**`cwd` 可以省略:默认用客户端当前打开的项目**
|
|
150
|
+
|
|
151
|
+
客户端本来就知道用户在哪个目录干活(MCP roots)。现在 `dsh_start` 不传 `cwd` 时,会向客户端发一次 `roots/list`,把返回的 `file://` URI 转成路径直接用。于是「客户端里看到哪个项目,DSH 就在哪个项目干活」成了默认行为,也顺带堵掉了「手拼路径拼到家目录」这条最常见的翻车路径。客户端不声明 roots、或者超时没回,就退回一条能照着改的报错,而不是猜一个目录。
|
|
152
|
+
|
|
153
|
+
**cwd 不要选家目录本身**
|
|
154
|
+
|
|
155
|
+
DSH 自己的 ACL 沙箱要求临时目录在工作区**之外**。Windows 上 `%TEMP%` 是 `C:/Users/<你>/AppData/Local/Temp`,就在家目录里面,所以 `cwd` 取家目录时,第一条 shell 命令会失败并报 `Windows ACL: temp root must be outside the workspace`。现在 `dsh_start` 会直接拒绝这个组合并给出两条出路:换一个具体项目目录,或改用 `permission=read-only` / `danger-full-access`。
|
|
156
|
+
|
|
157
|
+
**备份与回滚**
|
|
158
|
+
|
|
159
|
+
注册和删除**都会**在动文件之前先存一份:`<配置文件>.bak-dsh-<时间戳>`,内容就是操作前的原始字节。两个方向都存,因为要退回的状态不同——注册前那份用来「装错了退回原样」,删除前那份用来「删错了装回去」。
|
|
160
|
+
|
|
161
|
+
每次实际写入后,`--json` 的每个客户端条目里都带 `backup` 字段,直接告诉你备份在哪;人类可读的输出也会在每条下面打印 `backup: <路径>`。
|
|
162
|
+
|
|
163
|
+
备份散落在各客户端目录里,手动翻文件容易挑错,所以附带一个还原命令:
|
|
164
|
+
|
|
165
|
+
| 要做的事 | 命令 |
|
|
166
|
+
|---|---|
|
|
167
|
+
| 看有哪些备份(新的在前) | `dsh-any-agent-restore --list` |
|
|
168
|
+
| 退回每个文件最新的那份 | `dsh-any-agent-restore --latest` |
|
|
169
|
+
| 退回某一批次 | `dsh-any-agent-restore --stamp 2026-10-30T01-35` |
|
|
170
|
+
| 退回指定的一份 | `dsh-any-agent-restore --file <备份路径>` |
|
|
171
|
+
| 先看会做什么 | 任意命令加 `--dry-run` |
|
|
172
|
+
|
|
173
|
+
还原本身也是破坏性操作,所以覆盖之前会把**当前**状态另存为 `.bak-dsh-pre-restore-<时间戳>`——退错了还能再退回来。
|
|
174
|
+
|
|
175
|
+
**安装 / 更新 / 删除**
|
|
176
|
+
|
|
177
|
+
| 要做的事 | 命令 |
|
|
178
|
+
|---|---|
|
|
179
|
+
| 预览会改什么(不写) | `dsh-any-agent-setup` |
|
|
180
|
+
| 注册到所有检测到的客户端 | `dsh-any-agent-setup --yes` |
|
|
181
|
+
| 只注册某几个 | `dsh-any-agent-setup --yes --only cursor,zed` |
|
|
182
|
+
| 就地改权限档 | `dsh-any-agent-setup --yes --permission read-only` |
|
|
183
|
+
| 升级包之后更新配置 | `dsh-any-agent-setup --yes`(幂等,只重写有差异的条目) |
|
|
184
|
+
| 从所有客户端移除 | `dsh-any-agent-setup --remove --yes` |
|
|
185
|
+
| 只从某几个移除 | `dsh-any-agent-setup --remove --yes --only codex` |
|
|
186
|
+
| 撤销某次误写 | 把 `.bak-dsh-<时间戳>` 复制回原文件 |
|
|
187
|
+
|
|
188
|
+
<details>
|
|
189
|
+
<summary>移除操作的实测记录</summary>
|
|
190
|
+
|
|
191
|
+
**`--remove` 到底动了什么 —— 实测**
|
|
192
|
+
|
|
193
|
+
注销只删掉每个客户端里的**那一个键**,其余一概不碰。在真机上测得的前后对比:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
Cursor { keep, dsh-any-agent } -> { keep }
|
|
197
|
+
手工写的那条原样保留
|
|
198
|
+
Claude Code 6 个服务 -> 5 个服务
|
|
199
|
+
原有五个都还在列表里
|
|
200
|
+
Zed 33 个顶层键 -> 33 个键
|
|
201
|
+
只有 context_servers 少了子项,现在是空对象
|
|
202
|
+
Codex 我们的两个 TOML 段 -> 已移除
|
|
203
|
+
model、approval_policy、sandbox_mode、node_repl、rea 全部完好
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
这些条目之外的东西它一概不读、不改、不删。你的凭据、其他 MCP 服务器、无关设置都不受影响。
|
|
207
|
+
|
|
208
|
+
注意:**`--remove` 不会卸载包本身** —— 那是 `npm uninstall -g dsh-any-agent`,两件事互相独立:你可以保留包但不注册任何客户端,也可以只注销而不卸载。
|
|
209
|
+
|
|
210
|
+
事后可自行核验:`claude mcp list`、`codex mcp list`,以及看 `~/.cursor/mcp.json` 和 Zed 设置里的 `context_servers` 键。
|
|
211
|
+
|
|
212
|
+
</details>
|
|
213
|
+
|
|
214
|
+
**记录的是绝对路径**
|
|
215
|
+
|
|
216
|
+
`setup` 写进去的是它运行时所处安装目录的**绝对路径**。**如果你移动或删除了那个目录,所有客户端都会报 `CONNECTION_CLOSED`**,直到你在新位置重新跑一遍 `setup`,或者用 `--remove` 把失效条目清掉。
|
|
217
|
+
|
|
218
|
+
最容易踩到的情况:装到临时目录里试用,然后清理掉 —— 配置还指着那个已经不存在的路径。
|
|
219
|
+
|
|
220
|
+
<details>
|
|
221
|
+
<summary>手工配置客户端:Claude Code、Codex 与通用 MCP</summary>
|
|
222
|
+
|
|
223
|
+
**从本地目录安装**
|
|
224
|
+
|
|
225
|
+
解压到任意位置,把 `command` 指向 `node`、`args` 指向 `<解压目录>/bin/dsh-any-agent.mjs`(见下)。
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
**Claude Code**
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# 全局安装
|
|
233
|
+
claude mcp add dsh-any-agent -- dsh-any-agent
|
|
234
|
+
|
|
235
|
+
# 本地目录
|
|
236
|
+
claude mcp add dsh-any-agent -- node /绝对路径/server/bin/dsh-any-agent.mjs
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Codex**(`~/.codex/config.toml`)
|
|
240
|
+
|
|
241
|
+
```toml
|
|
242
|
+
[mcp_servers.dsh-any-agent]
|
|
243
|
+
command = "dsh-any-agent" # 或 "node" + bin/dsh-any-agent.mjs 的路径
|
|
244
|
+
args = []
|
|
245
|
+
startup_timeout_sec = 20.0
|
|
246
|
+
tool_timeout_sec = 120.0
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**通用 MCP 客户端** —— 任何支持 stdio 的 MCP 客户端:
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"mcpServers": {
|
|
254
|
+
"dsh-any-agent": {
|
|
255
|
+
"command": "dsh-any-agent",
|
|
256
|
+
"args": []
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
本地目录安装时改用 `"command": "node"` 和 `"args": ["/绝对路径/server/bin/dsh-any-agent.mjs"]`。
|
|
263
|
+
|
|
264
|
+
> Cursor、Windsurf、Cline、Continue 等大部分客户端都吃同一套 `mcpServers` 结构——只要能拉起 stdio MCP 服务,就能用。Zed 是例外:它用 `context_servers`(见上方示例)。
|
|
265
|
+
|
|
266
|
+
</details>
|
|
267
|
+
|
|
268
|
+
### 3. 自检
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
dsh-any-agent --version # 打印版本,不启动任何东西
|
|
272
|
+
dsh-any-agent --help # 参数、关键环境变量、文档链接
|
|
273
|
+
node test/smoke.mjs # 不调用 LLM,验证整条管道
|
|
274
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
> 返回 [README](../../README.zh.md)
|
|
2
|
+
|
|
3
|
+
# ⚠️ 已知限制
|
|
4
|
+
|
|
5
|
+
1. **逐字流式进不了模型上下文** —— MCP 的固有限制,不是 DSH 的。调用方拿到的是分步结果;人能通过 stderr 日志跟进。
|
|
6
|
+
2. **真·中途改向做不到** —— ACP 明确拒绝并发 prompt(`a prompt is already in flight for this session`)。`dsh_interject` 是实用等价物:取消 + 立刻开新回合,历史保留。
|
|
7
|
+
3. **图片提示不支持** —— ACP 自报 `promptCapabilities: {image: false}`。
|
|
8
|
+
4. **MCP 服务重启会连坐** —— 本服务被重启/杀掉时,它拉起的 DSH 子进程会随 stdio 管道关闭一起退出:**进行中的回合会中断、哨兵不会落地**(此时要靠核对工作区状态判断,别干等哨兵)。好消息是**写锁会自动释放**,重启后总能拿回来。
|
|
9
|
+
另注:本服务不会主动发 MCP 通知,"跑完通知"靠哨兵文件 + 调用方的后台等待任务实现。
|
|
10
|
+
5. **首次成功回合之前就死掉的会话可能尚未落盘** —— 此时 resume 会报清晰错误。发出第一条消息后就安全了。
|
|
11
|
+
6. **不能改名** —— DSH 的标题子系统没有对外改名接口(`SessionTitleService.rename` 需要进程内的活会话)。标题一律由首条消息自动生成。
|
|
12
|
+
7. **`session/list` 只返回 `{sessionId, cwd}`,且排除已打开的会话** —— 对话名由本服务从 DSH 投影缓存补全。
|
|
13
|
+
8. **内存(实测 300–480MB/活跃进程)** —— 0.5.0 起同 `(cwd, permission)` 的会话**默认共用一个 ACP 进程**,所以这份开销是**每组一份**,不再按会话翻倍;再加上 0.4.0 起的默认 **空闲 10 分钟回收 + `MAX_LIVE=4`**,最坏约 1.2–1.9GB。`keep_alive=true` 的会话不因空闲被回收(代价是常驻)。还可以:① `DSH_MCP_MAX_RSS_MB` 按实际 RSS 预算回收;② `DSH_MCP_CHILD_MAX_OLD_SPACE_MB` 给子进程设堆上限。一批活干完想立刻归零:**`dsh_release(all=true)`** ✓(默认只释放空闲会话,**正在跑的回合会跳过、继续跑**;要连它们一起停掉传 `force=true`。之后照样 resume)。
|
|
14
|
+
9. **共享进程会连坐** —— 默认开启的共享宿主让同 `(cwd, permission)` 的会话共用一个 PID:会话之间互不干扰(各自有 Agent 与上下文),但**进程一旦崩溃/被杀,同组所有会话的进行中回合会一起断**(会统一写 error 哨兵并唤醒等待者,下次派活各自 resume,会话本体无损)。所以:① `dsh_takeover` 杀这类进程前要求 `force=true`,并列出"车上还有谁";② 需要严格隔离(分析不可信样本、跑互不信任的任务)就设 `DSH_MCP_SHARED_PROCESS=0`;③ 不同工作区/不同权限档本来就各起一个进程 —— 那是 `dsh-base` 的进程级沙箱与审批配置决定的,不是可选项。
|
|
15
|
+
|
|
16
|
+
## ❓ 为什么 profile 还叫 `dsh-mcp`?
|
|
17
|
+
|
|
18
|
+
因为改名会打断一堆已经落盘的东西。
|
|
19
|
+
|
|
20
|
+
本服务首次启动时会在 `~/.dsh/profiles/dsh-mcp/` 建一个 DSH profile。0.4.0 起团队模式默认改用 `dsh-team`(同样由本服务自动创建);`dsh-mcp` 仍用于团队关闭或既有部署。两者共用同一套 `DSH_MCP_*` 变量与 `# dsh-mcp:managed` 标记。
|
|
21
|
+
|
|
22
|
+
这个目录名被另外三样持久化标识引用着:
|
|
23
|
+
|
|
24
|
+
| 持久标识 | 位置 | 改名的后果 |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| profile 目录 | `~/.dsh/profiles/dsh-mcp/` | 已有会话解析不到 provider(`no adapter registered for provider "…"`) |
|
|
27
|
+
| `DSH_MCP_*` 环境变量 | 你的客户端配置 | 所有部署都得同时改 |
|
|
28
|
+
| `# dsh-mcp:managed` 标记行 | 生成的 `cordis.patch.yml` 首行 | bootstrap 不再认它是自己写的,从此不再同步 |
|
|
29
|
+
|
|
30
|
+
所以**产品名**是 `dsh-any-agent`,**持久标识**故意保留旧名。这是刻意的兼容决定,不是漏改。如果你是全新部署、想要别的 profile 名,直接设 `DSH_MCP_PROFILE` —— 服务会照你给的名字用。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
> 返回 [README](../../README.zh.md)
|
|
2
|
+
|
|
3
|
+
# 🔒 写锁:一个会话同一时刻只有一个写者
|
|
4
|
+
|
|
5
|
+
DSH 的会话有**跨进程写锁**:**持有者活着就永不过期,也没有 API 能从活的持有者手里夺走** —— 所以**谁先拿到就决定一切**。本服务把它做成"**你看得见,我们写得动**":
|
|
6
|
+
|
|
7
|
+
| 情形 | 结论 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **本服务持有锁**(最近用过,或 `keep_alive=true`) | ✅ 你在 GUI 里点开它是**只读**的:**内容照看**,但**抢不走锁**。0.4.0 起默认空闲 **10 分钟**后才回收;`keep_alive=true` 的会话永不因空闲回收 |
|
|
10
|
+
| **锁空着时你在 GUI 点开它** | ❌ **GUI 会永久持有它**(切走、等待、归档都不释放),本服务**再也接不回来**,只能重启 `dsh web` |
|
|
11
|
+
| **推荐做法** | 看进度用 **`dsh_read`**(游标增量读)或等**哨兵文件**;非要用 GUI 就先 `dsh_release`(会中断在跑的回合,适合任务间隙) |
|
|
12
|
+
|
|
13
|
+
**锁在谁手上**看 `dsh_status.lock_holder`,`dsh_takeover` 按四类分别处置:
|
|
14
|
+
|
|
15
|
+
| `lock_holder` | 含义 | 处置 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `self` | 本进程持有 | 无需抢占 |
|
|
18
|
+
| `stale-mcp` | 持有者**活着但已失联**(心跳 90 秒未续,典型是那个 MCP 卡死了) | ✅ `dsh_takeover` **自动杀掉并接管** |
|
|
19
|
+
| `live-mcp` | 另一个**活着**的 dsh-any-agent 实例在用 | ⚠️ 默认拒绝,`force=true` 才夺 |
|
|
20
|
+
| `none` | 无登记 → 多半是**你自己的 DSH GUI** | ❌ **绝不杀**(GUI 会话跑在 `dsh web` **单进程**里,杀它 = 整个界面连同其它 GUI 会话一起断),只报告 |
|
|
21
|
+
|
|
22
|
+
> **两条实测补充**:① **崩溃不会留下孤儿锁** —— `SIGKILL` 掉 MCP,它拉起的子进程随 stdio 管道关闭一起退出,锁自动释放 ✓;所以 `stale-mcp` 真正对应的是"**活着但卡死**"。② **多实例**:注册表是"整份文档、最后写入者获胜"(后保存的会抹掉它没见过的会话),但**会话存储才是权威** —— 只要会话在磁盘上,**仅凭 id 就能取回并派活**;若你给每个实例配了不同的 `DSH_MCP_STATE`,**务必把 `DSH_MCP_LOCKS_DIR` 指到同一处**,否则彼此看不见对方的持锁登记。
|
|
23
|
+
>
|
|
24
|
+
> 完整实测过程(A/B 探测方法、GUI"只读打开"的证据、为什么连归档都不释放、损坏与卡死怎么区分)见 [使用注意事项 §3.5–3.12](../../USAGE-NOTES.md)。
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
> 返回 [README](../../README.zh.md)
|
|
2
|
+
|
|
3
|
+
# ⚙️ 环境变量
|
|
4
|
+
|
|
5
|
+
| 变量 | 默认 | 说明 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `DSH_BIN` | 自动探测 | `@deepseek-ai/dsh/lib/bin.js` 路径 |
|
|
8
|
+
| `DSH_MCP_PROFILE` | `dsh-mcp`(团队开启时 `dsh-team`) | 驱动用的 DSH profile;显式设置优先 |
|
|
9
|
+
| `DSH_MCP_TEAM` | `0`(关闭) | 团队模式,按需开启。`dsh-any-agent team on` 写设置文件;这里设 `1`/`0` 可覆盖它。开启后使用 `dsh-team` profile,并在 `dsh_send` 前注入"需要时先建团队"的提示;单次关闭用 `dsh_send(team=false)` |
|
|
10
|
+
| `DSH_MCP_SETTINGS` | `$DSH_HOME/.dsh-any-agent/settings.json` | 团队开关的设置文件(`dsh-any-agent team on/off` 写它) |
|
|
11
|
+
| `DSH_HOME` | `~/.dsh` | DSH 主目录。**默认就用本机用户自己的**,凭据与会话历史因此与 GUI 共用。指向别处即隔离环境(那就得自己准备那个目录的凭据与 profile) |
|
|
12
|
+
| `DSH_MCP_BOOTSTRAP` | 开 | `0` 关闭首启 profile 自动创建/同步(关掉后只驱动已存在的 profile,不写任何文件) |
|
|
13
|
+
| `DSH_MCP_ROUTE_SOURCE` | 自动 | 复制模型路由的来源 profile 名;`__home__` 表示只认 `$DSH_HOME/cordis.patch.yml` |
|
|
14
|
+
| `DSH_MCP_STATE` | `$DSH_HOME/.dsh-any-agent/conversations.json` | 会话注册表,跨重启存活。旧版 `<仓库>/.state` 会在首次启动时自动迁移一次 |
|
|
15
|
+
| `DSH_MCP_RUNS_DIR` | `<状态文件同目录>/runs` | 异步完成哨兵的根目录(`<此目录>/<会话id>/<run_id>.json`) |
|
|
16
|
+
| `DSH_MCP_LOCKS_DIR` | `<状态文件同目录>/locks` | 写锁"持有者登记"的目录。⚠️ **多个实例必须指向同一个值**,否则彼此看不见对方的持锁登记、会把对方误判成"无法识别的持有者(GUI)"而拒绝抢占 |
|
|
17
|
+
| `DSH_MCP_APPROVAL` | `auto-allow`(非只读档) | 默认审批策略。设成 `ask` 才能让越界请求挂起给 `dsh_approval_decide`,而不是自动放行 |
|
|
18
|
+
| `DSH_MCP_APPROVAL_TIMEOUT_MS` | `300000` | 待决 `ask` 审批的等待上限;超时按拒绝处理 |
|
|
19
|
+
| `DSH_MCP_FORGOTTEN_DIR` | `$DSH_HOME/.dsh-any-agent-forgotten` | `dsh_release(forget=true)` 写的墓碑目录 |
|
|
20
|
+
| `DSH_MCP_LOCK_STALE_MS` | `90000`(3× `REAP_INTERVAL_MS`,下限 30 秒) | 持锁登记多久没续心跳就算"持有者失联"(此时可被 `dsh_takeover` 自动抢占);改回收间隔会改变推导出的默认值 |
|
|
21
|
+
| `DSH_MCP_IDLE_TTL_MS` | `600000`(10 分钟) | 会话空闲多久回收其 DSH 进程。**默认 10 分钟**:回收后内存释放,下次派活自动 resume(对话不丢),代价是写锁会空出来、GUI 可能拿走。设 `0` = 永不回收(一直握锁,代价是每会话常驻 300–480MB);需要长期握锁的个别会话用 `dsh_start(keep_alive=true)` |
|
|
22
|
+
| `DSH_MCP_MAX_LIVE` | `4`(最坏约 1.2–1.9GB) | ★ **最多同时保活几个会话进程**(`0` = 不限)。超标时回收**最久没用过**的(**在跑的回合绝不回收**)。`keep_alive=true` 的会话不受本上限约束。用完想立刻放掉空闲会话:`dsh_release(all=true)`(busy 回合默认跳过,`force=true` 才中断) |
|
|
23
|
+
| `DSH_MCP_MAX_RSS_MB` | `0`(关闭) | 全局子进程内存预算(MB)。实际 RSS 超过预算时按"最久未用"回收空闲会话,`keep_alive` 会话最后才动。按实际内存而不是会话数,适合上下文大小差异大的场景 |
|
|
24
|
+
| `DSH_MCP_CHILD_MAX_OLD_SPACE_MB` | `0`(不设) | 给每个 DSH 子进程追加 `--max-old-space-size`,兜底失控的上下文。设太小会让长会话 OOM |
|
|
25
|
+
| `DSH_MCP_SHARED_PROCESS` | `1`(开启) | **默认开启**:同 `(cwd, permission)` 的会话共享一个 ACP 进程。显著省内存与冷启动(实测第二个会话 `session/new` 约 300ms,独立进程约 4s),`dsh_doctor` 的 RSS 也按 PID 去重。代价:进程崩溃会连坐同组会话(统一写 error 哨兵,之后各自 resume),所以 `dsh_takeover` 杀这类进程时要求 `force=true`。设 `0` 回到"一会话一进程" |
|
|
26
|
+
| `DSH_MCP_AUDIT_FILE` | 空(关闭) | 元数据审计 JSONL 路径。只记事件、会话/回合 ID、决定与计数;**不记** prompt、正文、思考、工具输入。10MB 滚动到 `.1` |
|
|
27
|
+
| `DSH_MCP_LIST_PROBE_TTL_MS` | `10000` | `dsh_list` 磁盘探测结果的缓存时长(探测要 spawn 一个 DSH 进程,实测约 1 秒) |
|
|
28
|
+
| `DSH_MCP_SENTINEL_TTL_MS` | `604800000`(7 天) | 启动时清理超过此时长的哨兵;`0` 关闭清理 |
|
|
29
|
+
| `DSH_MCP_SENTINEL_INCLUDE_REASONING` | 未设置 | `1` 会把 `result.thinking` 一并写进哨兵(**打破"思考不落盘"的保证**);answer/error/title/工具输入仍会脱敏 |
|
|
30
|
+
| `DSH_MCP_PERMISSION` | `workspace-write` | `dsh_start` 未指定时的权限档。设为 `danger-full-access` 还需要 `DSH_MCP_ALLOW_FULL_ACCESS=1` |
|
|
31
|
+
| `DSH_MCP_ALLOW_FULL_ACCESS` | `0` | 是否允许 `permission=danger-full-access`。默认拒绝:该档把整机写权限交给调用方 |
|
|
32
|
+
| `DSH_MCP_REASONING_EFFORT` | `max` | 默认思考深度 |
|
|
33
|
+
| `DSH_MCP_PROMPT_TIMEOUT_MS` | `0`(不设超时) | **仅 `wait=true` 时**的等待上限;到期只转后台,不取消回合 |
|
|
34
|
+
| `DSH_MCP_REGISTER_WORKSPACE` | `project` | `project` / `all` / `0` |
|
|
35
|
+
| `DSH_MCP_GIT_STATUS` | 开 | `0` 关闭每回合的 `git status` 摘要。分析不可信仓库时建议关掉(git 可能触发仓库配置的 fsmonitor/filter,且发生在 DSH 沙箱之外) |
|
|
36
|
+
| `DSH_MCP_BACKUP_KEEP` | `20` | 每个客户端配置最多保留多少份 `.bak-dsh-*`(尽力轮转) |
|
|
37
|
+
| `DSH_MCP_REQUEST_TIMEOUT_MS` | `60000` | ACP 通道单请求超时(initialize、session/new 等) |
|
|
38
|
+
| `DSH_MCP_REAP_INTERVAL_MS` | `30000` | 空闲回收器多久检查一次。低于安全下限的值会回落到默认值 |
|
|
39
|
+
| `DSH_MCP_LOG` | `silent` | `silent` / `info` / `debug` |
|
|
40
|
+
| `DSH_MCP_LOG_STDERR` | 未设置 | `1` 才转发 DSH 原始 stderr |
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
> 返回 [README](../../README.zh.md)
|
|
2
|
+
|
|
3
|
+
# 📦 发布流程
|
|
4
|
+
|
|
5
|
+
发版清单。顺序有讲究 —— 其中两步是因为跳过它们发过一次坏版本才补上的。
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. 升版本号。SERVER_VERSION 读的是 package.json,别处不用改
|
|
9
|
+
# (以前这里有两份数字,漂移过)
|
|
10
|
+
npm version patch --no-git-tag-version
|
|
11
|
+
# 然后手工改 CHANGELOG.md:把 [Unreleased] 变成新版本一节
|
|
12
|
+
|
|
13
|
+
# 2. 本地跑一遍离线套件(和 CI 相同,不花 token、不需要 DSH)
|
|
14
|
+
node test/run.mjs
|
|
15
|
+
node test/package-check.mjs
|
|
16
|
+
npm run check:release # 版本号 / CHANGELOG / 文档链接 / 打包清单一致性
|
|
17
|
+
|
|
18
|
+
# 3. 确认 tarball 本身:打得出来、装得上、起得来
|
|
19
|
+
npm pack --dry-run
|
|
20
|
+
# 再在临时目录里:npm install <tgz> && node_modules/.bin/dsh-any-agent --version
|
|
21
|
+
|
|
22
|
+
# 4. 打 tag 并推送(CI 绿了再发)
|
|
23
|
+
git add -A && git commit -m "chore: release x.y.z"
|
|
24
|
+
git tag vx.y.z && git push origin main --tags
|
|
25
|
+
# 推送 tag 后,.github/workflows/release.yml 会自动创建 GitHub Release
|
|
26
|
+
# (使用 GitHub 生成的 release notes);没有 gh CLI 也不影响。
|
|
27
|
+
|
|
28
|
+
# 5. 发布
|
|
29
|
+
npm publish
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**发布后看到 404,不要当成失败。** npm 对新版本是异步入库(返回 `PUT 202` 而不是 `200`),几秒后去查 registry 仍然是 404,看起来像没发成功。先确认本地 npm 日志里有这一行:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
grep 'http fetch PUT 202' "$LOCALAPPDATA/npm-cache/_logs/<最新的>.log"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
然后等一两分钟再查 `npm view dsh-any-agent dist-tags`。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
> 返回 [README](../../README.zh.md)
|
|
2
|
+
|
|
3
|
+
# 🔐 权限
|
|
4
|
+
|
|
5
|
+
| 档位 | 审批默认 | 用途 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `danger-full-access`(服务端裸默认) | `auto-allow` | 零摩擦;`setup` 默认写入 `workspace-write` |
|
|
8
|
+
| `workspace-write` | `auto-allow` | DSH 沙箱限制可写范围;越界请求默认放行,传 `on_approval=ask` 才挂起审批 |
|
|
9
|
+
| `read-only` | `auto-deny` | 分析不信任样本(恶意软件、来路不明 dump) |
|
|
10
|
+
|
|
11
|
+
`on_approval` 可选 `auto-allow` / `auto-deny` / `ask`(`ask` 会把审批挂起,交给 `dsh_approval_decide` 裁决)。
|
|
12
|
+
|
|
13
|
+
**`read-only` 是强制边界,不只是默认值**:它固定使用 `auto-deny`;给只读会话传 `auto-allow` / `ask` 会在 `dsh_start` 被直接拒绝,审批层也会拒绝来自只读会话的任何权限请求。建会话/resume 后还会回读会话自己记录的权限档;只读档如果在 DSH 投影缓存刷盘窗口内仍读不到记录,会 **fail-closed 拒绝继续**,而不是只依赖审批层兜底。否则 profile 的 `defaultPreset` 加宽松审批策略可能放行写操作(对抗审计实测过)。
|
|
14
|
+
|
|
15
|
+
## 🪟 会话在 DSH GUI 里的可见性
|
|
16
|
+
|
|
17
|
+
两件事,别混淆:
|
|
18
|
+
|
|
19
|
+
| | 取决于 | 现状 |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| **工作区是否生效** | 会话的 `cwd` | ✅ 永远正确。进程工作目录、文件落点、沙箱可写根都跟着它 |
|
|
22
|
+
| **GUI 里是否分组** | `~/.dsh/storages/workspace.json` 的 `sessionIds` | ⚠️ 本服务会登记,但**正在运行的 DSH 服务把注册表缓存在内存里**,外部改文件要重启 DSH 才生效 |
|
|
23
|
+
|
|
24
|
+
**"生效"有实测**([`test/workspace-effect.mjs`](../../test/workspace-effect.mjs)):桌面两个文件夹各开一个会话,派活时**不给任何路径**,结果两个会话各自把文件写进了自己的工作区、自报的当前目录正确、没有泄漏到 MCP 服务工作目录 / 家目录 / 临时目录 / 另一个工作区(24/24 全过)。
|
|
25
|
+
|
|
26
|
+
维护命令:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node bin/dsh-any-agent-workspaces.mjs --list # 查看注册表
|
|
30
|
+
node bin/dsh-any-agent-workspaces.mjs --backfill # 扫描磁盘上的会话,全部补登记
|
|
31
|
+
node bin/dsh-any-agent-workspaces.mjs --prune-temp # 清掉临时目录条目
|
|
32
|
+
node bin/dsh-any-agent-workspaces.mjs --prune-empty # 清掉"无会话且路径已不存在"的工作区
|
|
33
|
+
node bin/dsh-any-agent-workspaces.mjs --purge-test-sessions # 删测试会话(规则写得很死)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> ⚠️ 重启 DSH 之前**别在 GUI 里操作工作区**,否则服务会用内存状态把文件覆盖回去。
|
|
37
|
+
|
|
38
|
+
## 🕶️ 日志与隐私
|
|
39
|
+
|
|
40
|
+
默认**完全静默**,一个字节都不输出。
|
|
41
|
+
|
|
42
|
+
1. MCP 的 **stdout 是协议通道**,混进任何东西都会破坏报文;
|
|
43
|
+
2. 本服务被 Claude/Codex 拉起,它的 stderr 会进**对方的日志**;
|
|
44
|
+
3. DSH 子进程的 stderr 可能夹带模型输出(**含思考内容**),所以默认既不转发也不落盘。
|
|
45
|
+
|
|
46
|
+
| 级别 | 行为 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `silent`(默认) | 什么都不输出 |
|
|
49
|
+
| `info` | 只输出本服务自己的生命周期事件,**绝不含 DSH 的任何输出** |
|
|
50
|
+
| `debug` | 更细的本服务事件;仍不打印 DSH 输出 |
|
|
51
|
+
| `DSH_MCP_LOG_STDERR=1` | 额外转发 DSH 原始 stderr(**可能含思考内容**) |
|
|
52
|
+
|
|
53
|
+
**本服务不把思考内容写进自己的落盘物**,只有一个**刻意留下**的例外:
|
|
54
|
+
|
|
55
|
+
- 不写日志(默认静默);状态文件与 `<状态目录>/meta/` 下的会话元数据侧车都只有元数据(id、cwd、权限、计数、标题提示),冒烟测试里有一条断言专门守注册表。
|
|
56
|
+
- **异步完成哨兵需要把正文写进文件**(否则调用方无法离线收活),载荷还包含用量与(已按审批规则脱敏的)工具输入,但 `result.thinking` 会被**剥掉**,并始终给出布尔 `thinking_omitted`。测试里专门验证了:即使请求 `reasoning=full`,哨兵里也不出现任何思考文本——而同一 run 在内存(`dsh_get`)里仍能看到。设 `DSH_MCP_SENTINEL_INCLUDE_REASONING=1` 可刻意打破这条。
|
|
57
|
+
- 哨兵默认 7 天后由启动清理删除(`DSH_MCP_SENTINEL_TTL_MS`,`0` 表示永久保留)。
|
|
58
|
+
|
|
59
|
+
> ⚠️ **DSH 自己会在 `~/.dsh/sessions` 写会话日志,而且其中包含完整 reasoning 文本**(即使 `reasoning=hide` 也照存)。那是 resume 的依据,不在本服务控制范围内;“思考不落盘”的绝对表述只适用于**本服务自己的**日志/哨兵/注册表。
|
package/docs/zh/team.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
> 返回 [README](../../README.zh.md)
|
|
2
|
+
|
|
3
|
+
# 🧑🤝🧑 团队模式(按需开启)
|
|
4
|
+
|
|
5
|
+
**0.5.0 起默认关闭。** 需要时用一条命令开启,不用手改客户端配置。
|
|
6
|
+
|
|
7
|
+
开启后:profile 切到 `dsh-team`(含实验性 Agent Teams,关闭旧的 `subagent` 工具),
|
|
8
|
+
`dsh_send` 会在 prompt 前注入一句"需要并行/多角色时先建团队";简单任务不会被强制组队。
|
|
9
|
+
|
|
10
|
+
## 命令
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
dsh-any-agent team on # 开启(写 settings.json)
|
|
14
|
+
dsh-any-agent team off # 关闭
|
|
15
|
+
dsh-any-agent team status # 查看当前生效值、来源、profile
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`team on/off` 写入 `<DSH_HOME>/.dsh-any-agent/settings.json` 的 `team` 字段,
|
|
19
|
+
**不改任何客户端配置**。改完需要**重连 MCP 服务**(或重启客户端)才会生效。
|
|
20
|
+
|
|
21
|
+
## 开启步骤
|
|
22
|
+
|
|
23
|
+
1. 运行:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
dsh-any-agent team on
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. 重连 MCP 服务:Claude Code 用 `/mcp` → `dsh-any-agent` → reconnect;
|
|
30
|
+
其它客户端重启或在 MCP 面板里 reconnect。
|
|
31
|
+
3. 验证:调用 `dsh_start`,返回里应该出现:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
团队模式: 开(profile=dsh-team;派发时会提示"需要时先建团队",简单任务不会组队)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
或调用 `dsh_doctor`,`team` 检查应为 `on`。
|
|
38
|
+
|
|
39
|
+
## 关闭
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
dsh-any-agent team off
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
重连后 profile 回到 `dsh-mcp`,没有 team 工具,也不注入提示。
|
|
46
|
+
|
|
47
|
+
**单次关闭**(只这一次不提示组队,profile 仍是 `dsh-team`):
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
dsh_send(..., team=false)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 优先级与排查
|
|
54
|
+
|
|
55
|
+
生效值的优先级:
|
|
56
|
+
|
|
57
|
+
1. 环境变量 `DSH_MCP_TEAM`(`1` / `0`)—— **最高**;
|
|
58
|
+
2. `settings.json` 的 `team` 字段;
|
|
59
|
+
3. 默认关闭。
|
|
60
|
+
|
|
61
|
+
如果客户端配置里还留着旧版本写下的 `DSH_MCP_TEAM`,它会覆盖 `team on/off`。
|
|
62
|
+
`dsh-any-agent team status` 会显示来源(`env` / `file` / `default`);
|
|
63
|
+
`team on/off` 检测到环境变量时也会提示。
|
|
64
|
+
|
|
65
|
+
## 注意
|
|
66
|
+
|
|
67
|
+
- 团队模式要求 DSH `>= 0.1.7-0`;版本不够时即使 `team on` 也不会启用,
|
|
68
|
+
`dsh_doctor` 会把它标成 `warn`。
|
|
69
|
+
- team profile 会关闭旧的 `subagent` 工具,换成 9 个团队工具;成员共享一个工作区。
|
|
70
|
+
- 团队是**实验性**能力,DSH 不提供稳定性承诺。
|
|
71
|
+
- 不需要删 `~/.dsh/profiles/dsh-team`;关闭后它只是不被使用。
|