Ver Fonte

refactor(agent-presets): remove the global-tool-mask package nothing composes

`dsh-global-tool-mask` wrapped `tools.restrict()` as a composition row so a
preset's user patch layer could hide a host tool. No shipped preset, the
plugin manager, and the settings UI compose such a row, so the package had a
README, a test, and no consumer. It goes, with its `apps/cli` dependency,
its `tsconfig.host.json` reference, the README example that named it, and the
generated catalogs. `tools.restrict()` stays: subagent tool filters call it.

Hiding a host tool from one preset is a per-preset setting for the
scope-resolved settings of the next layer, not a hand-written row; the Agent
Note records the removal under alternatives considered.
Yichen Jiang há 2 semanas atrás
pai
commit
42764971ca

+ 2 - 2
.agents/notes/implemented/architecture/2026-09-04-preset-user-patch-layer-and-patch-file-writer.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-04-preset-user-patch-layer-and-patch-file-writer.md
-2026-09-04-preset-user-patch-layer-and-patch-file-writer.md: 6610e28584b57a91443b1bde2e7d81b345ef853d
-2026-09-04-preset-user-patch-layer-and-patch-file-writer.zh.md: 62a92eb366263407b8122bda29a2caaad65fd706
+2026-09-04-preset-user-patch-layer-and-patch-file-writer.md: 6f31e45ddf8aae56af56d7b30bccc36a23b42073
+2026-09-04-preset-user-patch-layer-and-patch-file-writer.zh.md: 38e8ac614743984636fcf869acb33ed3cd0d1549

+ 3 - 3
.agents/notes/implemented/architecture/2026-09-04-preset-user-patch-layer-and-patch-file-writer.md

@@ -16,8 +16,6 @@ A shipped agent preset was read-only in every sense: to switch one of its rows o
 
 **`dsh-patch-file` owns the format.** `parsePatchList` moved out of `dsh-app-boot` and is the one parser (js-yaml with the include's `!!js` dialect, relative names anchored to the file); `PatchDocument` edits a file at the key level through the `yaml` package's comment-preserving document, which keeps an unresolved `!!js` tag on its scalar and prints it back verbatim; `mutatePatchFile` takes the `dsh-atomic-write` lock, reads, edits, replaces atomically when the document is dirty, and parses the written text back. A removed row's comment block moves to its neighbour or the document's trailing comment instead of vanishing.
 
-**`dsh-global-tool-mask` is `tools.restrict()` as a row.** A preset's layer can add rows but could not subtract a host tool; this scope-only row masks named global tools for the sessions of one preset and rejects an unscoped mount, an empty mask, and a name the host does not register.
-
 ## Alternatives considered
 
 **A settings namespace per preset for enablement.** Rejected earlier in the design: enablement is composition, not preference, and the profile's own user layer is already a patch file; a preset's should be the same file in the same format.
@@ -26,10 +24,12 @@ A shipped agent preset was read-only in every sense: to switch one of its rows o
 
 **A second parser in `dsh-agent-presets` to avoid a dependency on `dsh-app-boot`.** Rejected: two parsers of one format drift; the format got its own package instead, which both depend on.
 
+**A `dsh-global-tool-mask` row: `tools.restrict()` as a composition row, so a preset's layer could hide a host tool.** Built, then removed before merge: no shipped preset, the plugin manager, or the settings UI composes one, so it was a package with a README and no consumer. A layer adds rows and switches the composition's rows off; hiding a host tool from one preset is a per-preset setting for the scope-resolved settings of the next layer, not a hand-written row.
+
 ## Consequences
 
 A person hides a tool from `standard` with three lines under `.agent-presets/standard/` and keeps the shipped composition; the plugin manager writes the same file for them. A layer applies to the sessions created after it changes, never to running ones. The layer addresses rows by the composition's own ids, so an id the composition leaves anonymous cannot be targeted.
 
 ## Testing
 
-`packages/preset/agent-presets/tests/overlay.spec.ts` pins discovery (attach, own layer, orphan slot, unparsable, malformed, unresolvable insert, composition verdict first), the mount applying the layer, generations across edits with the retired generation reused, the inventory's `source` and `disabledBy` from the file and from a mount, the layer path for shipped and authored presets, copy carrying the layer, and removal. `packages/util/patch-file/tests/patch-file.spec.ts` pins the parser, the document edits with comments and `!!js` intact, and the locked atomic mutation with readback. `packages/preset/global-tool-mask/tests/global-tool-mask.spec.ts` pins the row's scoping and refusals.
+`packages/preset/agent-presets/tests/overlay.spec.ts` pins discovery (attach, own layer, orphan slot, unparsable, malformed, unresolvable insert, composition verdict first), the mount applying the layer, generations across edits with the retired generation reused, the inventory's `source` and `disabledBy` from the file and from a mount, the layer path for shipped and authored presets, copy carrying the layer, and removal. `packages/util/patch-file/tests/patch-file.spec.ts` pins the parser, the document edits with comments and `!!js` intact, and the locked atomic mutation with readback.

+ 3 - 3
.agents/notes/implemented/architecture/2026-09-04-preset-user-patch-layer-and-patch-file-writer.zh.md

@@ -16,8 +16,6 @@ Status: implemented
 
 **`dsh-patch-file` 拥有这种格式。** `parsePatchList` 从 `dsh-app-boot` 搬出,成为唯一的解析器(js-yaml 配 include 的 `!!js` 方言,相对名字锚定到文件);`PatchDocument` 通过 `yaml` 包保留注释的 document 在键级编辑文件,它把未解析的 `!!js` 标签留在标量上并原样打印;`mutatePatchFile` 拿 `dsh-atomic-write` 的锁,读取、编辑、在 document 变脏时原子替换,并把写出的文本回读解析。被移除行上方的注释块移到邻居或文档尾注释,而不是消失。
 
-**`dsh-global-tool-mask` 是写成行的 `tools.restrict()`。** preset 的层能加行却减不掉宿主工具;这一仅限作用域的行对某个 preset 的会话遮蔽点名的全局工具,并拒绝无作用域挂载、空掩码与宿主未注册的名字。
-
 ## 考虑过的替代方案
 
 **为每个 preset 用一个 settings 命名空间做启停。** 在设计阶段已否决:启停是组合而非偏好,profile 自己的用户层本就是补丁文件;preset 的应当是同样格式的同一种文件。
@@ -26,10 +24,12 @@ Status: implemented
 
 **在 `dsh-agent-presets` 里再写一个解析器以避免依赖 `dsh-app-boot`。** 否决:同一格式的两个解析器会漂移;改为让格式拥有自己的包,两者都依赖它。
 
+**一个 `dsh-global-tool-mask` 行:把 `tools.restrict()` 写成组合行,让 preset 的层能遮蔽宿主工具。** 做出来后在合并前移除:随附 preset、插件管理器与设置 UI 都不会组合它,它是一个只有 README 没有消费者的包。层负责加行和关掉组装自己的行;对某个 preset 隐藏宿主工具属于下一层按 scope 解析的设置里的一条 preset 级设置,不是手写的行。
+
 ## 后果
 
 一个人用 `.agent-presets/standard/` 下的三行就能对 `standard` 隐藏一个工具并保留随附组合;插件管理器替他们写同一个文件。层作用于其变化之后创建的会话,从不作用于运行中的会话。层按组合自己的 id 寻址行,因此组合留作匿名的行无法被定位。
 
 ## 测试
 
-`packages/preset/agent-presets/tests/overlay.spec.ts` 钉住 discovery(附着、自有层、孤儿槽位、不可解析、畸形、不可解析的插入、组合判定优先)、施加层的挂载、编辑之间的代际及退役代际的复用、清单从文件与从挂载得到的 `source` 与 `disabledBy`、随附与自作 preset 的层路径、复制携带层,以及移除。`packages/util/patch-file/tests/patch-file.spec.ts` 钉住解析器、保留注释与 `!!js` 的文档编辑,以及带回读的加锁原子变更。`packages/preset/global-tool-mask/tests/global-tool-mask.spec.ts` 钉住该行的作用域与拒绝。
+`packages/preset/agent-presets/tests/overlay.spec.ts` 钉住 discovery(附着、自有层、孤儿槽位、不可解析、畸形、不可解析的插入、组合判定优先)、施加层的挂载、编辑之间的代际及退役代际的复用、清单从文件与从挂载得到的 `source` 与 `disabledBy`、随附与自作 preset 的层路径、复制携带层,以及移除。`packages/util/patch-file/tests/patch-file.spec.ts` 钉住解析器、保留注释与 `!!js` 的文档编辑,以及带回读的加锁原子变更。

+ 0 - 1
apps/cli/package.json

@@ -57,7 +57,6 @@
     "@deepseek-ai/dsh-launch-environment": "workspace:^",
     "@deepseek-ai/dsh-mcp-client": "workspace:^",
     "@deepseek-ai/dsh-persona": "workspace:^",
-    "@deepseek-ai/dsh-global-tool-mask": "workspace:^",
     "@deepseek-ai/dsh-plan-mode": "workspace:^",
     "@deepseek-ai/dsh-pwsh-local": "workspace:^",
     "@deepseek-ai/dsh-pwsh-sandbox": "workspace:^",

+ 2 - 2
docs/config-catalog.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/config-catalog.md
-config-catalog.md: ce14d7813079f2a48de652364e7091abb457087b
-config-catalog.zh.md: 1e68cc99a70566664cf3e3e3aaa1fcda848821cf
+config-catalog.md: 17db4a1d98b9063a96f6483bd36188633b21cd1c
+config-catalog.zh.md: 141edb9e0c4fda8280c8349db46e51ad73e7158e

+ 0 - 18
docs/config-catalog.md

@@ -766,24 +766,6 @@ Depends on: [`LocalConfig`](#deepseek-aidsh-fs-local)
 
 Source: [`packages/fs/fs-sandbox/src/index.ts:45`](../packages/fs/fs-sandbox/src/index.ts)
 
-<a id="deepseek-aidsh-global-tool-mask"></a>
-
-## `@deepseek-ai/dsh-global-tool-mask`
-
-Requires: `tools`
-
-```ts config-catalog
-/** Plugin config: the global-tool mask, in `tools.restrict()`'s own vocabulary. */
-export interface Config {
-  /** Global tool names that stay visible; everything else is hidden. Empty means no allow list. */
-  allow: string[]
-  /** Global tool names hidden from this scope. Empty means no deny list. */
-  deny: string[]
-}
-```
-
-Source: [`packages/preset/global-tool-mask/src/index.ts:28`](../packages/preset/global-tool-mask/src/index.ts)
-
 <a id="deepseek-aidsh-goal"></a>
 
 ## `@deepseek-ai/dsh-goal`

+ 0 - 18
docs/config-catalog.zh.md

@@ -768,24 +768,6 @@ export type Config = LocalConfig
 
 来源:[`packages/fs/fs-sandbox/src/index.ts:45`](../packages/fs/fs-sandbox/src/index.ts)
 
-<a id="deepseek-aidsh-global-tool-mask"></a>
-
-## `@deepseek-ai/dsh-global-tool-mask`
-
-需要:`tools`
-
-```ts config-catalog
-/** Plugin config: the global-tool mask, in `tools.restrict()`'s own vocabulary. */
-export interface Config {
-  /** Global tool names that stay visible; everything else is hidden. Empty means no allow list. */
-  allow: string[]
-  /** Global tool names hidden from this scope. Empty means no deny list. */
-  deny: string[]
-}
-```
-
-来源:[`packages/preset/global-tool-mask/src/index.ts:28`](../packages/preset/global-tool-mask/src/index.ts)
-
 <a id="deepseek-aidsh-goal"></a>
 
 ## `@deepseek-ai/dsh-goal`

+ 2 - 2
docs/module-graph.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/module-graph.md
-module-graph.md: ba03a0dfae2a045d1526760196415e602738a535
-module-graph.zh.md: d75a6a11f4901a16f1f2fdff984eef18d55de3a8
+module-graph.md: 8b8ad75fbb8c01348dc0d355ac278077d08b7107
+module-graph.zh.md: 0bc5f1340f4c4c99b98ad3ca543ee8e5f2e2306f

+ 0 - 3
docs/module-graph.md

@@ -267,7 +267,6 @@ flowchart TD
   end
   subgraph group_preset["packages/preset"]
     pkg_agent_presets["agent-presets"]
-    pkg_global_tool_mask["global-tool-mask"]
     pkg_persona["persona"]
   end
   subgraph group_runtime_diagnostics["packages/runtime-diagnostics"]
@@ -825,7 +824,6 @@ flowchart TD
   pkg_agent_presets --> pkg_system_prompt
   pkg_agent_presets --> pkg_tools
   pkg_agent_presets --> pkg_typert_protocol
-  pkg_global_tool_mask --> pkg_tools
   pkg_schedule --> pkg_agent
   pkg_schedule --> pkg_brand
   pkg_schedule --> pkg_invariants
@@ -1377,7 +1375,6 @@ flowchart TD
 | [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`agent-presets`](../packages/preset/agent-presets) | `preset` | [`agent`](../packages/core/agent), [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`patch-file`](../packages/util/patch-file), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
-| [`global-tool-mask`](../packages/preset/global-tool-mask) | `preset` | [`tools`](../packages/core/tools) |
 | [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
 | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |

+ 0 - 3
docs/module-graph.zh.md

@@ -269,7 +269,6 @@ flowchart TD
   end
   subgraph group_preset["packages/preset"]
     pkg_agent_presets["agent-presets"]
-    pkg_global_tool_mask["global-tool-mask"]
     pkg_persona["persona"]
   end
   subgraph group_runtime_diagnostics["packages/runtime-diagnostics"]
@@ -827,7 +826,6 @@ flowchart TD
   pkg_agent_presets --> pkg_system_prompt
   pkg_agent_presets --> pkg_tools
   pkg_agent_presets --> pkg_typert_protocol
-  pkg_global_tool_mask --> pkg_tools
   pkg_schedule --> pkg_agent
   pkg_schedule --> pkg_brand
   pkg_schedule --> pkg_invariants
@@ -1379,7 +1377,6 @@ flowchart TD
 | [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`agent-presets`](../packages/preset/agent-presets) | `preset` | [`agent`](../packages/core/agent), [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`patch-file`](../packages/util/patch-file), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
-| [`global-tool-mask`](../packages/preset/global-tool-mask) | `preset` | [`tools`](../packages/core/tools) |
 | [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
 | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |

+ 2 - 2
packages/preset/agent-presets/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/preset/agent-presets/README.md
-README.md: ca7874fcdc0c5fcacac3dc06a081154c7429eb25
-README.zh.md: 94c21d577b12f04f14339c2001cfa509be00ef1c
+README.md: b2c7fb2c48758313689800abdbbbaaf37e7b5caf
+README.zh.md: 34366b0d8da66a85db0f394a7d05407611175941

+ 3 - 3
packages/preset/agent-presets/README.md

@@ -82,10 +82,10 @@ Every preset accepts a user patch layer: a `cordis.patch.yml` in the Loader's pa
 - id: tool-web
   disabled: true
 - insert:
-    - id: hide-fetch
-      name: '@deepseek-ai/dsh-global-tool-mask'
+    - id: sql
+      name: '@acme/dsh-sql-tool'
       config:
-        deny: [web_fetch]
+        dsn: sqlite://local.db
 ```
 
 The layer is judged with the composition: a layer that does not parse, inserts a malformed row, or inserts a row naming a module that cannot resolve makes the preset broken with that reason, and a layer whose id no root supplies is listed as a broken slot. Sessions created after the layer changes compose the new content; running sessions keep theirs. The composition inventory reports each row's `source` (`preset` or `user`) and, for a row that is off, whether the composition or the layer switched it off. A copy carries the layer in beside the new composition, and `removeOverlay` deletes it. The [plugin manager](../../host/plugin-manager/README.md) writes this file through `overlayPathFor`.

+ 3 - 3
packages/preset/agent-presets/README.zh.md

@@ -82,10 +82,10 @@ agent-presets:
 - id: tool-web
   disabled: true
 - insert:
-    - id: hide-fetch
-      name: '@deepseek-ai/dsh-global-tool-mask'
+    - id: sql
+      name: '@acme/dsh-sql-tool'
       config:
-        deny: [web_fetch]
+        dsn: sqlite://local.db
 ```
 
 这一层与组装一起判定:无法解析的层、插入了畸形行、或插入了引用无法解析模块的行,都会让 preset 带着该原因变为 broken;没有任何根目录提供其 id 的层会被列为损坏的槽位。在层变化之后创建的会话组合新内容;运行中的会话保持各自的。组合清单报告每一行的 `source`(`preset` 或 `user`),以及被关掉的行是组装还是这一层关掉的。复制会把这一层带到新组装旁边,`removeOverlay` 删除它。[插件管理器](../../host/plugin-manager/README.zh.md) 经 `overlayPathFor` 写入这个文件。

+ 0 - 6
packages/preset/global-tool-mask/README.i18n.yaml

@@ -1,6 +0,0 @@
-# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
-# side as of the last confirmed-consistent state. Both languages carry equal authority;
-# after editing either side, bring the other along and re-record with:
-#   pnpm run verify-translation-pairing --write packages/preset/global-tool-mask/README.md
-README.md: 8c0b9dba5ac932f9d531323fcfde016b0029a7d2
-README.zh.md: 9e65f12f8f2636bc33b24ba4a2a751474019f3be

+ 0 - 98
packages/preset/global-tool-mask/README.md

@@ -1,98 +0,0 @@
----
-description: "Composition-authored global-tool mask for one agent preset: a row that hides named host tools from the sessions the preset composes."
-kind: "package-reference"
----
-
-# @deepseek-ai/dsh-global-tool-mask
-
-English | [中文](README.zh.md)
-
-## Summary
-
-`dsh-global-tool-mask` is `tools.restrict()` as a composition row. The host's global tools — the ones a host row registers, such as `web_fetch` — reach every session through the global layer, and nothing in a preset file could hide one until now: a preset can add rows, not subtract tools other rows registered. Mounted inside an agent preset, this row masks the named global tools for the sessions that preset composes and nothing else; mounted globally it rejects, because a context-global restriction would mask every agent. Its intended home is a preset's user patch layer (`$DSH_HOME/.agent-presets/<id>/cordis.patch.yml`), where a person hides a host tool from one preset without editing the composition the deployment ships.
-
-## Table of Contents
-
-- [Use this package](#use-this-package)
-- [Understand the implementation](#understand-the-implementation)
-- [Further Exploration](#further-exploration)
-- [Model Experience](#model-experience)
-- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
-- [Dev Note](#dev-note)
-
------
-
-<a id="use-this-package"></a>
-## Use this package
-
-Insert one row into a preset's composition or its user patch layer. `deny` hides the named tools; `allow` keeps only the named tools; both together intersect:
-
-```yaml
-- insert:
-    - id: hide-web
-      name: '@deepseek-ai/dsh-global-tool-mask'
-      config:
-        deny: [web_fetch, web_search]
-```
-
-The mask follows the row's fiber: it applies when the preset's standing composition mounts and lifts when that composition is torn down. The names are checked at mount, so a mask that names no tool, or names one the host does not register, fails the preset loud with the registry's own message rather than silently masking nothing. Scoped registrations — tools the preset's own rows register — are never affected; the reserved PTC transport name cannot be masked either.
-
------
-
-<a id="understand-the-implementation"></a>
-## Understand the implementation
-
-<details>
-<summary>Implementation internals — click to expand</summary>
-
-The row is one `ctx.effect()` around `ctx.tools.restrict(config)`. Every rule — scoped context required, non-empty filter, known names, the reserved transport name — belongs to the registry and is enforced there; the row adds a `Config` schema so a hand-written composition is validated at load.
-
-### Source map
-
-| File | Role |
-|---|---|
-| [`src/index.ts`](src/index.ts) | The `global-tool-mask` function plugin: `name`, `inject`, `Config`, `apply` |
-| — | No runtime invariant companion is published; the registry owns the restriction's lifetime and reports the mask through its own views. |
-
-</details>
-
------
-
-<a id="further-exploration"></a>
-## Further Exploration
-
-Read these when the question is what the mask acts on or where the row lives.
-
-- [Tool registry](../../core/tools/README.md) — `tools.restrict()`, the layered views, and why a restriction requires a scope.
-- [Agent presets](../agent-presets/README.md) — the compositions and user patch layers this row is inserted into.
-
------
-
-<a id="model-experience"></a>
-## Model Experience
-
-Indirectly, through the tool registry: the row removes tool schemas from the request the registry assembles for the scoped agent and registers no prompt, schema, or result of its own.
-
-#### KV Cache effect
-
-None of its own; the registry assembles the tool list, and a mask changes which schemas that list carries for the sessions of one preset from their first request onward.
-
-## Known Limitations and Deferred Work
-
-<a id="known-limitations-and-deferred-work"></a>
-
-
-These limits define what the row will not do. They are current package constraints, not a task backlog.
-
-- **Global tools only** — the mask cannot hide a tool a row of the same preset registers; disable that row instead.
-- **Mount-time names** — a tool registered after the preset mounted is not retroactively judged: a name the host adds later cannot have been in the mask, and a name the host stops registering leaves the mask as it was.
-
-<a id="dev-note"></a>
-### Dev Note
-
-<details>
-<summary>Working context for maintainers — click to expand</summary>
-
-None.
-
-</details>

+ 0 - 98
packages/preset/global-tool-mask/README.zh.md

@@ -1,98 +0,0 @@
----
-description: "面向单个 agent preset 的、由组合书写的全局工具掩码:一行把点名的宿主工具对该 preset 组合出的会话隐藏起来。"
-kind: "package-reference"
----
-
-# @deepseek-ai/dsh-global-tool-mask
-
-[English](README.md) | 中文
-
-## 概述
-
-`dsh-global-tool-mask` 是写成组合行的 `tools.restrict()`。宿主的全局工具——由宿主行注册的那些,如 `web_fetch`——经全局层到达每一个会话,而在此之前 preset 文件里没有任何东西能藏起其中一个:preset 能加行,却减不掉别的行注册的工具。挂在 agent preset 内部时,本行只对该 preset 组合出的会话遮蔽点名的全局工具,别的一概不动;挂在全局则拒绝,因为一个上下文全局的限制会遮住每一个 agent。它预期的落点是 preset 的用户补丁层(`$DSH_HOME/.agent-presets/<id>/cordis.patch.yml`):一个人不必编辑部署随附的组合,就能把一个宿主工具对某个 preset 藏起来。
-
-## 目录
-
-- [使用本包](#use-this-package)
-- [理解实现](#understand-the-implementation)
-- [进一步探索](#further-exploration)
-- [模型体验](#model-experience)
-- [已知限制与延期工作](#known-limitations-and-deferred-work)
-- [开发备注](#dev-note)
-
------
-
-<a id="use-this-package"></a>
-## 使用本包
-
-往 preset 的组合或其用户补丁层插入一行。`deny` 隐藏点名的工具;`allow` 只保留点名的工具;两者同时给出时取交集:
-
-```yaml
-- insert:
-    - id: hide-web
-      name: '@deepseek-ai/dsh-global-tool-mask'
-      config:
-        deny: [web_fetch, web_search]
-```
-
-掩码跟随本行的 fiber:preset 的常驻组合挂载时生效,该组合被拆除时解除。名字在挂载时检查,因此一个没点名任何工具、或点名了宿主未注册工具的掩码会带着注册表自己的消息让 preset 大声失败,而不是悄悄什么都不遮。作用域内的注册——preset 自己的行注册的工具——永远不受影响;保留的 PTC 传输名也不能被遮蔽。
-
------
-
-<a id="understand-the-implementation"></a>
-## 理解实现
-
-<details>
-<summary>实现内幕——点击展开</summary>
-
-本行就是包着 `ctx.tools.restrict(config)` 的一个 `ctx.effect()`。每条规则——必须是作用域上下文、过滤器非空、名字已知、保留的传输名——都属于注册表并在那里执行;本行只增加一个 `Config` schema,让手写的组合在加载时得到校验。
-
-### 源码地图
-
-| 文件 | 职责 |
-|---|---|
-| [`src/index.ts`](src/index.ts) | `global-tool-mask` 函数插件:`name`、`inject`、`Config`、`apply` |
-| — | 不发布运行时不变量伴随件;注册表拥有限制的生命周期,并通过自己的视图报告掩码。 |
-
-</details>
-
------
-
-<a id="further-exploration"></a>
-## 进一步探索
-
-当问题在于掩码作用于什么、或这一行放在哪里时,读这些。
-
-- [工具注册表](../../core/tools/README.zh.md)——`tools.restrict()`、分层视图,以及限制为何需要作用域。
-- [Agent presets](../agent-presets/README.zh.md)——本行被插入其中的组合与用户补丁层。
-
------
-
-<a id="model-experience"></a>
-## 模型体验
-
-间接地,经由工具注册表:本行从注册表为作用域内 agent 组装的请求中移除工具 schema,自身不注册任何提示词、schema 或结果。
-
-#### KV Cache 影响
-
-自身无;工具列表由注册表组装,掩码改变的是该列表为某个 preset 的会话从首次请求起携带哪些 schema。
-
-## 已知限制与延期工作
-
-<a id="known-limitations-and-deferred-work"></a>
-
-
-这些限制界定本行不会做什么。它们是当前包的约束,不是任务清单。
-
-- **仅限全局工具**——掩码藏不住同一 preset 中某行注册的工具;请改为停用那一行。
-- **名字在挂载时判定**——preset 挂载后才注册的工具不会被追溯判定:宿主之后新增的名字不可能出现在掩码里,宿主不再注册的名字也让掩码保持原样。
-
-<a id="dev-note"></a>
-### 开发备注
-
-<details>
-<summary>维护者工作上下文——点击展开</summary>
-
-无。
-
-</details>

+ 0 - 42
packages/preset/global-tool-mask/package.json

@@ -1,42 +0,0 @@
-{
-  "name": "@deepseek-ai/dsh-global-tool-mask",
-  "description": "Composition-authored global-tool mask for one agent preset: a row that hides named host tools from the sessions the preset composes",
-  "version": "0.1.3-alpha.1",
-  "publishConfig": {
-    "access": "public"
-  },
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
-    "directory": "packages/preset/global-tool-mask"
-  },
-  "type": "module",
-  "main": "lib/index.js",
-  "types": "lib/types/index.d.ts",
-  "exports": {
-    ".": {
-      "types": "./lib/types/index.d.ts",
-      "default": "./lib/index.js"
-    },
-    "./src/*": "./src/*",
-    "./package.json": "./package.json"
-  },
-  "files": [
-    "lib/index.js",
-    "lib/types/**/*.d.ts"
-  ],
-  "license": "MIT",
-  "peerDependencies": {
-    "@deepseek-ai/dsh-tools": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
-  },
-  "dependencies": {
-    "@deepseek-ai/schemastery": "workspace:^"
-  },
-  "devDependencies": {
-    "@deepseek-ai/dsh-scope": "workspace:^",
-    "@deepseek-ai/dsh-system-prompt": "workspace:^",
-    "@deepseek-ai/dsh-tools": "workspace:^",
-    "@deepseek-ai/cordis": "workspace:^"
-  }
-}

+ 0 - 54
packages/preset/global-tool-mask/src/index.ts

@@ -1,54 +0,0 @@
-/**
- * A global-tool mask as a composable row.
- *
- * `dsh-tools` lets an agent scope restrict the host's global tools through
- * `tools.restrict()`, and nothing in a composition file could call it: a
- * preset can add rows, but the tools a host row registers reach every
- * session through the global layer. This row is that call as a row —
- * **scope-only**, like `dsh-persona`: mounted inside an agent preset it hides
- * the named global tools from the sessions the preset composes; mounted
- * globally it rejects, because a context-global restriction would mask every
- * agent. A preset's user patch layer is where the row is meant to land, so a
- * person can hide a host tool from one preset without editing the file the
- * deployment ships.
- * @module @deepseek-ai/dsh-global-tool-mask
- */
-
-import type { Context } from '@deepseek-ai/cordis'
-import z from '@deepseek-ai/schemastery'
-import type {} from '@deepseek-ai/dsh-tools'
-
-/** Cordis plugin name. */
-export const name = 'global-tool-mask'
-
-/** The registry this row masks. */
-export const inject = ['tools']
-
-/** Plugin config: the global-tool mask, in `tools.restrict()`'s own vocabulary. */
-export interface Config {
-  /** Global tool names that stay visible; everything else is hidden. Empty means no allow list. */
-  allow: string[]
-  /** Global tool names hidden from this scope. Empty means no deny list. */
-  deny: string[]
-}
-
-/** Runtime schema for the mask row. */
-export const Config: z<Config> = z.object({
-  allow: z.array(z.string()),
-  deny: z.array(z.string()),
-})
-
-/**
- * Restrict the global tools visible to the mounting context's scope.
- * @param ctx - an agent scope context; an unscoped context rejects, as does
- * a mask that names no tool or names one the host does not register.
- * @param config - the mask; `allow` and `deny` intersect when both are given.
- * An empty list is the same as an absent one: the schema materializes a
- * missing list as `[]`, and an empty `allow` would otherwise hide every tool.
- */
-export function apply(ctx: Context, config: Config): void {
-  ctx.effect(() => ctx.tools.restrict({
-    ...config.allow.length === 0 ? {} : { allow: config.allow },
-    ...config.deny.length === 0 ? {} : { deny: config.deny },
-  }), 'global-tool-mask.restrict()')
-}

+ 0 - 71
packages/preset/global-tool-mask/tests/global-tool-mask.spec.ts

@@ -1,71 +0,0 @@
-import { Context } from '@deepseek-ai/cordis'
-import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
-import ToolRuntime from '@deepseek-ai/dsh-tools'
-import { createScope, type ScopeKey } from '@deepseek-ai/dsh-scope'
-import { describe, expect, it } from 'vitest'
-import * as ToolRestrict from '@deepseek-ai/dsh-global-tool-mask'
-
-async function harness(): Promise<Context> {
-  const ctx = new Context()
-  await ctx.plugin(SystemPrompt, { persona: '' })
-  await ctx.plugin(ToolRuntime)
-  for (const name of ['alpha', 'beta', 'gamma']) {
-    ctx.tools.register({
-      name,
-      description: `fixture tool ${name}`,
-      parameters: { type: 'object', properties: {}, additionalProperties: false },
-      output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }] },
-      execute: () => Promise.resolve(name),
-    })
-  }
-  return ctx
-}
-
-const visible = (ctx: Context, scope?: ScopeKey): string[] =>
-  ctx.tools.schemas(scope).map(schema => schema.name).sort()
-
-describe('the global-tool-mask row', () => {
-  it('rejects an unscoped mount, which would mask every agent', async () => {
-    const ctx = await harness()
-
-    await expect(ctx.plugin(ToolRestrict, { allow: [], deny: ['alpha'] })).rejects.toThrow(/requires a scoped context/)
-  })
-
-  it('hides denied global tools from one scope only', async () => {
-    const ctx = await harness()
-    const key: ScopeKey = { agent: 'a1' }
-
-    await createScope(ctx, key).ctx.plugin(ToolRestrict, { allow: [], deny: ['alpha'] })
-
-    expect(visible(ctx, key)).toEqual(['beta', 'gamma'])
-    expect(visible(ctx)).toEqual(['alpha', 'beta', 'gamma'])
-  })
-
-  it('keeps only allowed tools, intersected with a deny list', async () => {
-    const ctx = await harness()
-    const key: ScopeKey = { agent: 'a2' }
-
-    await createScope(ctx, key).ctx.plugin(ToolRestrict, { allow: ['alpha', 'beta'], deny: ['beta'] })
-
-    expect(visible(ctx, key)).toEqual(['alpha'])
-  })
-
-  it('fails loud on an empty mask and on a tool the host does not register', async () => {
-    const ctx = await harness()
-
-    await expect(createScope(ctx, { agent: 'a3' }).ctx.plugin(ToolRestrict, { allow: [], deny: [] })).rejects.toThrow(/is a no-op/)
-    await expect(createScope(ctx, { agent: 'a4' }).ctx.plugin(ToolRestrict, { allow: [], deny: ['nope'] }))
-      .rejects.toThrow(/unknown global tool "nope"/)
-  })
-
-  it('lifts the mask when its fiber unloads', async () => {
-    const ctx = await harness()
-    const key: ScopeKey = { agent: 'a5' }
-    const fiber = await createScope(ctx, key).ctx.plugin(ToolRestrict, { allow: [], deny: ['alpha'] })
-    expect(visible(ctx, key)).toEqual(['beta', 'gamma'])
-
-    await fiber.dispose()
-
-    expect(visible(ctx, key)).toEqual(['alpha', 'beta', 'gamma'])
-  })
-})

+ 0 - 22
packages/preset/global-tool-mask/tsconfig.json

@@ -1,22 +0,0 @@
-{
-  "extends": "../../../tsconfig.base.json",
-  "compilerOptions": {
-    "rootDir": "src",
-    "outDir": "lib/types"
-  },
-  "include": ["src"],
-  "references": [
-    {
-      "path": "../../../vendor/cosmokit"
-    },
-    {
-      "path": "../../../vendor/cordis"
-    },
-    {
-      "path": "../../../vendor/schemastery"
-    },
-    {
-      "path": "../../core/tools"
-    }
-  ]
-}

+ 0 - 22
pnpm-lock.yaml

@@ -187,9 +187,6 @@ importers:
       '@deepseek-ai/dsh-fs-local':
         specifier: workspace:^
         version: link:../../packages/fs/fs-local
-      '@deepseek-ai/dsh-global-tool-mask':
-        specifier: workspace:^
-        version: link:../../packages/preset/global-tool-mask
       '@deepseek-ai/dsh-goal':
         specifier: workspace:^
         version: link:../../packages/goal/goal
@@ -6838,25 +6835,6 @@ importers:
         specifier: workspace:^
         version: link:../../typert/protocol
 
-  packages/preset/global-tool-mask:
-    dependencies:
-      '@deepseek-ai/schemastery':
-        specifier: link:../../../vendor/schemastery
-        version: link:../../../vendor/schemastery
-    devDependencies:
-      '@deepseek-ai/cordis':
-        specifier: workspace:^
-        version: link:../../../vendor/cordis
-      '@deepseek-ai/dsh-scope':
-        specifier: workspace:^
-        version: link:../../core/scope
-      '@deepseek-ai/dsh-system-prompt':
-        specifier: workspace:^
-        version: link:../../core/system-prompt
-      '@deepseek-ai/dsh-tools':
-        specifier: workspace:^
-        version: link:../../core/tools
-
   packages/preset/persona:
     dependencies:
       '@deepseek-ai/schemastery':

+ 0 - 1
scripts/verify-package-readme-model-experience.ts

@@ -55,7 +55,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
   'packages/code-runtime/code-runtime-worker-thread': { kind: 'indirect', reason: 'The worker backend delegates model rendering to PTC mode in dsh-tools.' },
   'packages/experimental/code-runtime-python': { kind: 'indirect', reason: 'Explicit source-checkout compositions delegate model rendering to PTC mode in dsh-tools.' },
   'packages/client/ui-agent-preset': { kind: 'indirect', reason: 'Browser-side settings row; the preset it selects owns every model-facing effect.' },
-  'packages/preset/global-tool-mask': { kind: 'indirect', reason: 'The row only calls tools.restrict(); the registry owns the tool list the model sees.' },
   'packages/util/crypto': { kind: 'indirect', reason: 'Pure identifier minting; the ids consumers mint with it never enter prompts as semantic content.' },
   'packages/util/deque': { kind: 'none', reason: 'In-process collection primitive; registers nothing model-facing.' },
   'packages/util/patch-file': { kind: 'none', reason: 'Composition patch-file parser and writer; the rows those files name own every model-facing registration.' },

+ 0 - 1
tsconfig.base.json

@@ -281,7 +281,6 @@
       "@deepseek-ai/dsh-fs-local": ["./packages/fs/fs-local/src"],
       "@deepseek-ai/dsh-fs-observation-policy": ["./packages/fs/fs-observation-policy/src"],
       "@deepseek-ai/dsh-fs-sandbox": ["./packages/fs/fs-sandbox/src"],
-      "@deepseek-ai/dsh-global-tool-mask": ["./packages/preset/global-tool-mask/src"],
       "@deepseek-ai/dsh-goal": ["./packages/goal/goal/src"],
       "@deepseek-ai/dsh-goal/invariant": ["./packages/goal/goal/src/invariant.ts"],
       "@deepseek-ai/dsh-goal-round-driver": ["./packages/goal/goal-round-driver/src"],

+ 0 - 1
tsconfig.host.json

@@ -322,7 +322,6 @@
     { "path": "./packages/plan/plan-mode" },
     { "path": "./packages/preset/agent-presets" },
     { "path": "./packages/preset/persona" },
-    { "path": "./packages/preset/global-tool-mask" },
     { "path": "./packages/guard/repeat-tool-reminder" },
     { "path": "./packages/extensions/cordis-host-runner" },
     { "path": "./packages/extensions/tool-cordis" },