# 配置开关说明（配合场景理解）

本插件在插件页上的四个控件分别做什么。配套的定义示例在本目录：
[`explorer.toml`](explorer.toml)（轻量探索、可继续）、[`reviewer.toml`](reviewer.toml)（只读审查、拒绝写入）。

## 先分清一条界线：安装时 vs 调用时

| 阶段 | 何时发生 | 决定什么 | 受哪个开关影响 |
|---|---|---|---|
| **安装工具** | Agent（新任务/新会话）创建时；定义文件变化时（监听开着）；**任一开关被保存时** | 模型**看到的**工具名、`agent_type` 取值列表、工具描述 | 工具名、信任项目级定义、监听定义目录、失败上报 |
| **调用工具** | 模型真的发起一次委派 | 委派能否成功、按哪个定义执行 | 只有"信任项目级定义"影响（它决定读哪些目录）；其余开关不影响：定义内容每次都重读 |

一句话：**定义的内容能不能用，永远以"调用那一刻"的文件为准**；另外三个开关只决定模型"事先看到什么"，而**保存设置会立刻触发一次安装**，所以它们的改动同样当场可见，不必等新会话。

> 保存设置为什么会重装：这四个开关**只改 volatile 字段**，Loader 因此不重挂本插件行，而是原地更新取值并发出 `loader/volatile-update`。插件收到通知后对每个运行中 Agent 重跑"发现 + 安装"。只重新对齐 watcher 是不够的——`trustProjectAgents` 会改变列表本身，`toolName` 会改变注册名，`reportFailuresToModel` 会改变描述，三者都只有重装才能作用于**已经装着工具**的会话。

## 一、信任项目级定义（`trustProjectAgents`）

- **作用**：是否加载 `<项目根>/.dsh/agents/*.toml`（随仓库分发的定义）。默认关。
- **生效点**：**立即**——保存后插件重装每个运行中 Agent 的工具，`agent_type` 列表当场更新；若项目目录此前没被监听，也会随即开始监听。无需重启、无需新会话。
- **例子**：你在仓库里放了 `.dsh/agents/reviewer.toml` 并提交。关着时，模型只看得到 `$DSH_HOME/agents` 里的定义，项目里那份不生效（Host 日志会记一行 "ignoring project definitions"）。打开后，工作目录在该仓库下的任务就能用 `agent_type = "reviewer"`，且**同名时项目定义覆盖用户级定义**。
- **为什么默认关**：这些文件跟着 `git clone` 一起来，等同于"仓库决定你能调用什么"。
- **注意**：项目根 = 会话工作目录向上最近的含 `.git` 目录。目录不可读（权限问题）会作为一条失败记录上报，**不会**被当成"目录不存在"而静默跳过。

## 二、工具名（`toolName`，默认 `subagent_custom`）

- **作用**：模型调用的那把**工具**叫什么。
- **关键区分**：模型一次委派里有两个名字，别混淆——

  ```json
  { "agent_type": "reviewer", "description": "审查 demo.js", "prompt": "..." }
  ```

  - **工具名**（本开关）= 上面这个调用走的工具，默认 `subagent_custom`；
  - **`agent_type`** = 委派给哪个定义（`reviewer` / `explorer`），由 TOML 的 `name` 决定，**不受本开关影响**。

  改成本开关为 `delegate_to_reviewer` 后，模型调用的就变成 `delegate_to_reviewer(agent_type = "reviewer")`，TOML 一个字都不用改。
- **例子 A（避冲突）**：另装了一个也注册 `subagent_custom` 的插件，Harness 会拒绝第二个注册：
  `tool "subagent_custom" is already registered in this scope`。改成 `project_subagent` 即可共存。
- **例子 B（让模型更好选）**：同一次请求里通常还有官方 `subagent`、`subagent_fork`；语义明确的名字能减少选错。
- **例子 C（审计）**：会话日志里 `tool/call` 记录的 name 就是这个值，不同 profile 用不同名字便于一眼区分。
- **生效时机**：**立即**——保存后每个运行中 Agent 用新名字重新注册（旧注册先释放），下一次模型请求里就是新名字；**刷新页面不够也不需要**——工具表是 Host 侧按 Agent 安装的，浏览器刷新只影响界面。

## 三、监听定义目录（`watchDefinitions`，默认开）

- **作用**：定义文件变化后，是否立刻重装工具 → 刷新**模型看到的** `agent_type` 列表与工具描述。
- **生效点**：**立即**。关掉时会**关闭已经打开的目录监听**（不必等重启或新任务）；重新打开会立刻打开。
- **实现**：保存时 Host 会把改动写入 profile 补丁；若只改了本插件的开关类字段，Loader **不会重挂本插件行**，而是原地更新取值并通知本插件，插件收到通知后重装各 Agent 的工具，顺带把监听对齐。四项开关走的是同一条路径。
- **例子（开着）**：长会话进行到一半，你把 `reviewer.toml` 复制成 `security-audit.toml` 并改好内容。约 200ms 后清单刷新，下一次模型请求里 `agent_type` 已含 `security-audit`，你可以直接说"委派给 security-audit"。
- **例子（关掉）**：同样改动，模型手里的列表**还是旧的**，它很可能回答"没有这个子代理"。但**调用期是真读文件的**：你明确指名（或让它试一次）依然能成功，只是它事先不知道。
- **关掉的好处**：不再打开文件监听（少几个句柄）。定义目录在网络盘/超大目录上时更稳妥；只在两次会话之间改定义的人，关掉完全够用。
- **建议**：会话里边改边用 → 保持打开；改动只发生在部署阶段 → 可以关。
- **一个细节**：目录当时不存在（还没建 `$DSH_HOME/agents`）时不会留下一个"假监听"——下一次安装会重新尝试；日志会记一条 `cannot watch … yet`。

## 四、在工具描述里列出不可用定义（`reportFailuresToModel`，默认开）

- **作用**：把**失败的定义及原因**写进工具描述，让模型知道"哪些定义被跳过了、为什么"。失败本身永远不影响其他定义。
- **例子（开着）**：把 `reviewer.toml` 的 `max_depth = 1` 误改成 `0`（或把 `[tools] deny` 里写成不存在的工具名）。该定义被跳过，其余照常，工具描述里会多出：

  ```
  Unavailable definitions: reviewer (max_depth must be at least 1).
  ```

  于是你问"为什么用不了 reviewer？"，模型能直接讲出真实原因。
- **例子（关掉）**：描述里只有 `Configured subagents: explorer, gui-reviewer.` → 模型多半回答"没有 reviewer 这个子代理"，真正原因只留在 Host 日志（`logger.warn`，运维视角）。
- **不受本开关影响的部分**：真去调用一个失败定义时，错误信息照样带原因与文件名：

  ```
  subagent "reviewer" is unavailable: max_depth must be at least 1 (…\.dsh\agents\reviewer.toml)
  ```

  本开关只影响"事前告知"。
- **代价**：工具描述随每次模型请求发送；失败条目多、理由长时会持续占 token。
- **生效时机**：**立即**，与**工具名**相同——保存后重装各 Agent 的工具，描述当场重新生成。

## 组合场景

你在长会话中新增 `.dsh/agents/security-audit.toml`，但里面把 `[tools] deny` 写成了不存在的工具名：

| 配置 | 现象 |
|---|---|
| 监听开 + 失败上报开 | 约 200ms 后描述刷新；`security-audit` 因失败**不在** `agent_type` 列表里，但描述带着原因 → 你一问，模型说得出"deny 里的名字不存在" |
| 监听开 + 失败上报关 | 描述刷新了，但**只有**可用名单；模型不知道 security-audit 的存在与原因，你得自己看日志 |
| 监听关 | 模型手里的清单仍是会话开始时的样子，要等新任务或保存一次设置才刷新（调用期依然读最新文件） |
| 两个都关 | 完全静默：模型看到的与磁盘可能长期不一致 |

## 该动哪一项？速查

| 你的情况 | 建议动作 |
|---|---|
| 想让某个仓库里的定义生效 | 打开**信任项目级定义** |
| 与别的插件重名报 "already registered" | 改**工具名** |
| 同时装着官方 `subagent`，模型选错工具 | 把**工具名**改成语义明确的（如 `delegate_to_reviewer`） |
| 会话里边加/改定义边用 | 保持**监听定义目录**打开 |
| 定义目录在网络盘、或只在部署时固定 | 关掉**监听定义目录** |
| 失败定义很多、理由很长，想省 token | 关掉**在工具描述里列出不可用定义**（原因仍写入 Host 日志） |
| 希望模型能解释"某个子代理为什么不可用" | 保持**失败上报**打开 |

## 相关文档

- 安装与开启步骤：[`README.md`](../README.md)
- 技术文档（机制、校验规则、能力位、限制）：[`technical.md`](technical.md)
- 定义文件字段参考：[`README.md` · TOML 字段参考](../README.md#toml-字段参考)
- `persona` 怎么写、`continuable` 的结论怎么回传：[`README.md` · 写 persona 时的要求](../README.md#写-persona-时的要求决定父会话能拿到什么)
- 逐键注释的完整示例：[`explorer.toml`](explorer.toml)、[`reviewer.toml`](reviewer.toml)

## 附：让 AI 代写定义时的对照表

内置 Skill 会按这张表提问与校验；你自己写或让别人写时，也按它自查：

| 症状 | 原因 | 怎么改 |
|---|---|---|
| 子代理"跑完了但父会话什么也没拿到" | `continuable` 只回传**收尾消息的文本**，而 `persona` 没要求把结论写进收尾 | 在 `persona` 里明确「收尾消息给结论 + 证据 + 未解决问题」 |
| 中途发现没传回父会话 | 中途的工具输出与思考**不在**结算通知里 | 需要提前通知时，要求它 `send_message(agent_id = 父代理 id)`（该 id 由宿主注入子代理任务提示）。发送方会同步拿到**投递确认**（失败可见、可重发），注意确认不等于答复 |
| 父会话同时收到两份重叠内容 | `persona` 只说"中途先发一条"，没说"只发增量"，子代理把整份结论中途也发了一遍 | 在 `persona` 里写明：中途只发**会改变父代理下一步决策的那一条增量**，完整结论只放收尾消息。两种形态的区分见 [README · 继续一个 continuable 子代理](../README.md#继续一个-continuable-子代理依赖官方控制工具) |
| 定义在列表里消失 | 该文件解析失败（未知键、`max_depth = 0`、`[tools]` 写了不存在的名字等） | 看工具描述里的 `Unavailable definitions:` 或 Host 日志；其余定义不受影响 |
| 定义在项目里不生效 | 项目级定义需要打开**信任项目级定义** | 打开该开关即可，保存后当期会话立即生效（无需重启） |
| 想让子代理报结构化结果却报错 | `output_schema` 只能配 `one-shot` | 改成 `one-shot`，或去掉 `output_schema` |
| 配了 `output_schema` 却像"没结果" | 这类子代理被要求用工具交值而不以文本收尾，返回值本身就是那段 JSON | 正常现象；若要散文结论，另在 `persona` 里要求它追加一段总结文本 |
| 用 `acp`/`codex`/`claude-code` 时莫名报"不支持深度上限" | 旧版本会给这些 provider 也下发 Host 的隐式深度上限，而它们没有 `depthLimit` | 升级到 0.3.0+：不写 `max_depth` 时不再下发隐式上限 |
| 委派报"不支持的能力" | provider 能力位不匹配（如 `acp` 不支持 `persona`/`model`） | 换 `spawn`/`fork`，或去掉该字段 |
