dsh-tool-deny 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +111 -2
- package/README.zh.md +112 -0
- package/cordis.patch.yml +3 -0
- package/index.js +81 -0
- package/package.json +48 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hhuangpanda
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,112 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-tool-deny
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Choose which tools each agent preset gets in [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh).
|
|
6
|
+
|
|
7
|
+
A plugin loaded at the root of a profile registers its tools globally, so every preset inherits them. A preset can add plugins, but it has no setting to remove a global tool. dsh-tool-deny fills that gap with a few lines of YAML.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
| Option | Where | Value | What it does |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| `hideByPreset` | Root row | Map of preset id to tool names | Removes tools from the model's tool list for the presets you name |
|
|
14
|
+
| `hide` | Inside a preset | Tool names | Same, for the preset that mounts the plugin |
|
|
15
|
+
| `deny` | Inside a preset | Tool names | Keeps tools listed, but refuses every call |
|
|
16
|
+
| `hideSections` | Either | Section names | Blanks global system prompt sections for those agents |
|
|
17
|
+
|
|
18
|
+
Use `hideByPreset` in a root row, and `hide` and `deny` in a preset row. If a row sets `hideByPreset`, `hide` and `deny` are ignored and a warning is logged.
|
|
19
|
+
|
|
20
|
+
Hidden tools cost no tokens, because their definitions are never sent to the model. Other presets are unaffected. Subagents, automation runs and resumed sessions are covered too.
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
Tested on dsh 0.2.1-rc.4.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
dsh plugin --profile web add dsh-tool-deny
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The plugin does nothing until you configure it.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
Say you installed a plugin that adds `image_search` and `translate`, and you want to keep them away from your `minimal` and `reviewer` presets. Add this to your profile's `cordis.patch.yml`:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- insert:
|
|
40
|
+
- id: tool-deny-root
|
|
41
|
+
name: dsh-tool-deny
|
|
42
|
+
config:
|
|
43
|
+
hideByPreset:
|
|
44
|
+
minimal:
|
|
45
|
+
- image_search
|
|
46
|
+
- translate
|
|
47
|
+
reviewer:
|
|
48
|
+
- translate
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Restart dsh. New `minimal` sessions no longer see either tool, `reviewer` sessions no longer see `translate`, and every other preset keeps both.
|
|
52
|
+
|
|
53
|
+
If several presets share a list, a YAML anchor avoids repeating it:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
hideByPreset:
|
|
57
|
+
minimal: &extra-tools
|
|
58
|
+
- image_search
|
|
59
|
+
- translate
|
|
60
|
+
reviewer: *extra-tools
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Use inside a preset
|
|
64
|
+
|
|
65
|
+
Use this when a preset should carry its own rules, or when you need `deny`. Add the row to the preset's `plugins` list:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
- id: tool-deny
|
|
69
|
+
name: dsh-tool-deny
|
|
70
|
+
config:
|
|
71
|
+
hide:
|
|
72
|
+
- image_search
|
|
73
|
+
deny:
|
|
74
|
+
- delete_records
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`image_search` disappears from the list. `delete_records` stays listed, and calling it returns `"delete_records" is not available to this agent.`
|
|
78
|
+
|
|
79
|
+
## Hide or refuse?
|
|
80
|
+
|
|
81
|
+
Prefer hiding: the model never sees the tool, and you save its tokens. Use `deny` for tools that hiding can't reach, which are the ones a plugin registers on each agent directly (through `agent.ctx`) instead of globally. If you try to hide one, it stays visible.
|
|
82
|
+
|
|
83
|
+
## Hide prompt sections
|
|
84
|
+
|
|
85
|
+
Some plugins add a global section to the system prompt. Blank it by name for the same agents:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
config:
|
|
89
|
+
hideByPreset:
|
|
90
|
+
minimal:
|
|
91
|
+
- image_search
|
|
92
|
+
hideSections:
|
|
93
|
+
- my-plugin:notes
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
At the root, `hideSections` applies to every preset named in `hideByPreset`. The section name is defined by the plugin that registers it.
|
|
97
|
+
|
|
98
|
+
## Limitations
|
|
99
|
+
|
|
100
|
+
- **Failures are quiet.** If you remove the row or the plugin stops loading, the tools come back with no error. Don't rely on it as your only safeguard for a sensitive agent.
|
|
101
|
+
- **Lists are by name.** A new tool added by a plugin update stays visible until you add it.
|
|
102
|
+
- **Unknown names are skipped.** A tool name that doesn't exist logs a warning and is otherwise ignored, so check your spelling.
|
|
103
|
+
- **It hides tools, not plugins.** The plugin keeps running. Anything it does beyond its tools, such as acting as the provider behind `web_search`, is unchanged.
|
|
104
|
+
- **Preset switches can drop tools.** Switching a new session to another preset and back can leave out `subagent`, which is dsh behavior. Starting a fresh session restores it.
|
|
105
|
+
|
|
106
|
+
## How it works
|
|
107
|
+
|
|
108
|
+
In root mode, the plugin listens for `agent/created` and `agent-preset/selected`, looks up the agent's preset, and calls `ctx.tools.restrict({ deny })` on that agent. dsh then filters those tools whenever it builds the agent's tool list. The tools stay registered, so other agents are unaffected. `deny` uses `ctx.tools.guard`, which rejects the call at execution time.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
[MIT](LICENSE)
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# dsh-tool-deny
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)中的每个智能体预设(preset)选择可用的工具。
|
|
6
|
+
|
|
7
|
+
在 profile 根部加载的插件会全局注册其工具,因此所有预设都会继承这些工具。预设可以添加插件,却没有移除全局工具的设置。dsh-tool-deny 用几行 YAML 补上了这个空缺。
|
|
8
|
+
|
|
9
|
+
## 功能
|
|
10
|
+
|
|
11
|
+
| 选项 | 位置 | 值 | 作用 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| `hideByPreset` | 根行 | 预设 id 到工具名的映射 | 从指定预设的模型工具列表中移除工具 |
|
|
14
|
+
| `hide` | 预设内 | 工具名 | 同上,作用于挂载该插件的预设 |
|
|
15
|
+
| `deny` | 预设内 | 工具名 | 工具仍在列表中,但拒绝每一次调用 |
|
|
16
|
+
| `hideSections` | 两处皆可 | 段落名 | 为这些智能体清空全局系统提示词中的指定段落 |
|
|
17
|
+
|
|
18
|
+
在根行使用 `hideByPreset`,在预设行使用 `hide` 和 `deny`。如果某一行设置了 `hideByPreset`,则 `hide` 和 `deny` 会被忽略,并记录一条警告。
|
|
19
|
+
|
|
20
|
+
被隐藏的工具不消耗 token,因为其定义不会发送给模型。其他预设不受影响。子智能体、自动化运行和恢复的会话同样适用。
|
|
21
|
+
|
|
22
|
+
## 环境要求
|
|
23
|
+
|
|
24
|
+
已在 dsh 0.2.1-rc.4 上测试。
|
|
25
|
+
|
|
26
|
+
## 安装
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
dsh plugin --profile web add dsh-tool-deny
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
配置之前,插件不会产生任何效果。
|
|
33
|
+
|
|
34
|
+
## 快速开始
|
|
35
|
+
|
|
36
|
+
假设你安装了一个添加 `image_search` 和 `translate` 的插件,并希望 `minimal` 和 `reviewer` 预设用不到它们。将以下内容添加到 profile 的 `cordis.patch.yml`:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- insert:
|
|
40
|
+
- id: tool-deny-root
|
|
41
|
+
name: dsh-tool-deny
|
|
42
|
+
config:
|
|
43
|
+
hideByPreset:
|
|
44
|
+
minimal:
|
|
45
|
+
- image_search
|
|
46
|
+
- translate
|
|
47
|
+
reviewer:
|
|
48
|
+
- translate
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
重启 dsh。新的 `minimal` 会话将看不到这两个工具,`reviewer` 会话将看不到 `translate`,其他预设仍保留两者。
|
|
52
|
+
|
|
53
|
+
如果多个预设共用同一份列表,可以用 YAML 锚点避免重复:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
hideByPreset:
|
|
57
|
+
minimal: &extra-tools
|
|
58
|
+
- image_search
|
|
59
|
+
- translate
|
|
60
|
+
reviewer: *extra-tools
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 在预设内使用
|
|
64
|
+
|
|
65
|
+
当预设需要自带规则,或需要使用 `deny` 时,请这样做。将该行添加到预设的 `plugins` 列表:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
- id: tool-deny
|
|
69
|
+
name: dsh-tool-deny
|
|
70
|
+
config:
|
|
71
|
+
hide:
|
|
72
|
+
- image_search
|
|
73
|
+
deny:
|
|
74
|
+
- delete_records
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`image_search` 会从列表中消失。`delete_records` 仍在列表中,调用它会返回 `"delete_records" is not available to this agent.`。
|
|
78
|
+
|
|
79
|
+
## 隐藏还是拒绝?
|
|
80
|
+
|
|
81
|
+
优先使用隐藏:模型看不到该工具,还能节省它占用的 token。对于隐藏无法覆盖的工具,请使用 `deny`。这类工具由插件直接注册在每个智能体上(通过 `agent.ctx`),而不是全局注册。若尝试隐藏它们,它们仍然可见。
|
|
82
|
+
|
|
83
|
+
## 隐藏提示词段落
|
|
84
|
+
|
|
85
|
+
有些插件会向系统提示词中添加全局段落。可以按名称为相同的智能体清空它:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
config:
|
|
89
|
+
hideByPreset:
|
|
90
|
+
minimal:
|
|
91
|
+
- image_search
|
|
92
|
+
hideSections:
|
|
93
|
+
- my-plugin:notes
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
在根部,`hideSections` 适用于 `hideByPreset` 中列出的每个预设。段落名由注册它的插件定义。
|
|
97
|
+
|
|
98
|
+
## 局限性
|
|
99
|
+
|
|
100
|
+
- **失效时不会报错。** 如果移除了该行,或插件无法加载,工具会悄悄恢复。不要把它作为敏感智能体的唯一保护措施。
|
|
101
|
+
- **按名称列出。** 插件更新新增的工具在你添加之前仍然可见。
|
|
102
|
+
- **未知名称会被跳过。** 不存在的工具名只会记录一条警告,其余情况下被忽略,请仔细检查拼写。
|
|
103
|
+
- **隐藏的是工具,而不是插件。** 插件仍在运行。它在工具之外的行为(例如作为 `web_search` 背后的提供方)不会改变。
|
|
104
|
+
- **切换预设可能丢失工具。** 将新会话切换到另一个预设再切回来,可能会丢失 `subagent`,这是 dsh 本身的行为。开启新会话即可恢复。
|
|
105
|
+
|
|
106
|
+
## 工作原理
|
|
107
|
+
|
|
108
|
+
在根模式下,插件监听 `agent/created` 和 `agent-preset/selected`,查找该智能体的预设,并对该智能体调用 `ctx.tools.restrict({ deny })`。之后 dsh 在构建该智能体的工具列表时会过滤这些工具。工具仍保持注册状态,因此其他智能体不受影响。`deny` 使用 `ctx.tools.guard`,在执行时拒绝调用。
|
|
109
|
+
|
|
110
|
+
## 许可证
|
|
111
|
+
|
|
112
|
+
[MIT](LICENSE)
|
package/cordis.patch.yml
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Limits which tools an agent gets. See README.md for the config options.
|
|
3
|
+
*/
|
|
4
|
+
export const name = "dsh-tool-deny"
|
|
5
|
+
export const inject = ["tools", "systemPrompt"]
|
|
6
|
+
|
|
7
|
+
function refuseCalls(ctx, toolNames) {
|
|
8
|
+
if (toolNames.length === 0) return
|
|
9
|
+
const refused = new Set(toolNames)
|
|
10
|
+
ctx.tools.guard((execution) =>
|
|
11
|
+
refused.has(execution?.name)
|
|
12
|
+
? `"${execution.name}" is not available to this agent.`
|
|
13
|
+
: undefined,
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// dsh names this filter "deny", but it hides the tool. It is unrelated to our `deny` option.
|
|
18
|
+
function removeFromToolList(ctx, toolName) {
|
|
19
|
+
return ctx.tools.restrict({ deny: [toolName] })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function hide(ctx, logger, toolNames, sectionNames) {
|
|
23
|
+
const undo = []
|
|
24
|
+
for (const toolName of toolNames) {
|
|
25
|
+
// One call per name: restrict() throws on an unknown tool, which would fail the agent.
|
|
26
|
+
try {
|
|
27
|
+
undo.push(removeFromToolList(ctx, toolName))
|
|
28
|
+
} catch (error) {
|
|
29
|
+
logger?.warn?.(`dsh-tool-deny: cannot hide "${toolName}": ${error}`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const sectionName of sectionNames) {
|
|
33
|
+
undo.push(ctx.systemPrompt.section({ name: sectionName, order: 500, text: "" }))
|
|
34
|
+
}
|
|
35
|
+
return undo
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hideByPreset(ctx, toolNamesByPreset, sectionNames) {
|
|
39
|
+
ctx.inject(["agents", "agentPresets"], (sctx) => {
|
|
40
|
+
const undoByAgent = new Map()
|
|
41
|
+
|
|
42
|
+
const unhide = (agent) => {
|
|
43
|
+
for (const undo of undoByAgent.get(agent) ?? []) undo()
|
|
44
|
+
undoByAgent.delete(agent)
|
|
45
|
+
}
|
|
46
|
+
const refresh = (agent) => {
|
|
47
|
+
unhide(agent)
|
|
48
|
+
const preset = sctx.agentPresets.composedPreset(agent.ctx)
|
|
49
|
+
const toolNames = toolNamesByPreset[preset]
|
|
50
|
+
if (toolNames) undoByAgent.set(agent, hide(agent.ctx, sctx.logger, toolNames, sectionNames))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const agent of sctx.agents.roots()) refresh(agent)
|
|
54
|
+
sctx.on("agent/created", ({ agent }) => {
|
|
55
|
+
refresh(agent)
|
|
56
|
+
return undefined
|
|
57
|
+
})
|
|
58
|
+
// A session can change preset before its first turn, after agent/created fired.
|
|
59
|
+
sctx.on("agent-preset/selected", (sessionId) => {
|
|
60
|
+
const agent = sctx.agents.get(sessionId)
|
|
61
|
+
if (agent) refresh(agent)
|
|
62
|
+
})
|
|
63
|
+
sctx.on("agent/disposed", ({ agent }) => unhide(agent))
|
|
64
|
+
sctx.effect(() => () => {
|
|
65
|
+
for (const agent of [...undoByAgent.keys()]) unhide(agent)
|
|
66
|
+
}, "dsh-tool-deny: hide by preset")
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function apply(ctx, config = {}) {
|
|
71
|
+
const sectionNames = config.hideSections ?? []
|
|
72
|
+
if (config.hideByPreset) {
|
|
73
|
+
if (config.hide || config.deny) {
|
|
74
|
+
ctx.logger?.warn?.("dsh-tool-deny: hide and deny only work inside a preset; ignoring them")
|
|
75
|
+
}
|
|
76
|
+
hideByPreset(ctx, config.hideByPreset, sectionNames)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
refuseCalls(ctx, config.deny ?? [])
|
|
80
|
+
hide(ctx, ctx.logger, config.hide ?? [], sectionNames)
|
|
81
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tool-deny",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Hide or refuse named tools per agent preset in DeepSeek Harness. Can also hide system prompts added by tools. Useful when plugins force tools on all agent presets.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./index.js",
|
|
13
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22",
|
|
18
|
+
"dsh": ">=0.2.1-rc.1 <0.3.0-0"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"index.js",
|
|
22
|
+
"cordis.patch.yml",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"deepseek-harness",
|
|
28
|
+
"dsh",
|
|
29
|
+
"dsh-plugin",
|
|
30
|
+
"tools",
|
|
31
|
+
"agent-preset"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test \"test/*.test.mjs\""
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/hhuangpanda/dsh-tool-deny.git"
|
|
39
|
+
},
|
|
40
|
+
"bugs": "https://github.com/hhuangpanda/dsh-tool-deny/issues",
|
|
41
|
+
"homepage": "https://github.com/hhuangpanda/dsh-tool-deny#readme",
|
|
42
|
+
"dsh": {
|
|
43
|
+
"bundle": {
|
|
44
|
+
"patch": "./cordis.patch.yml"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"dsh": ">=0.2.1-rc.1 <0.3.0-0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|