dsh-chat-loli 1.0.0 → 1.1.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/LICENSE +0 -0
- package/README.md +39 -3
- package/cordis.patch.yml +20 -0
- package/icon.webp +0 -0
- package/lib/guard.js +128 -0
- package/package.json +11 -4
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -13,15 +13,51 @@ DSH 自定义 Agent 预设「**鲸鱼娘捆绑模式**」(preset id `dsh-chat-
|
|
|
13
13
|
|
|
14
14
|
人设正文来自 [dsh-whale-loli](https://www.npmjs.com/package/dsh-whale-loli)(MIT)的 `src/persona-v3-naughty.md`,原文照搬;只把 `{{whale_memory_v3}}` 占位符替换为「(本预设未挂载记忆文件)」,因为记忆插件被去掉了。
|
|
15
15
|
|
|
16
|
+
## 宿主级工具收窄(`lib/guard.js`)
|
|
17
|
+
|
|
18
|
+
上面那张表只覆盖**预设管得着的那一半**。DSH 的 Web 界面在 `dsh-web-app` 这一层只把 23 个「agent 平面」的工具行交给预设;**不在这份名单里的行留在宿主平面**,注册进进程级全局工具层,任何预设的会话都继承。
|
|
19
|
+
|
|
20
|
+
所以只声明 `persona + tool-web` 的预设,实际会话里仍然看得见:
|
|
21
|
+
|
|
22
|
+
| 来源 | 工具 |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `dsh-experimental-terminal-bundle` | `terminal_open` / `terminal_send` / `terminal_read` / `terminal_close` / `terminal_signal` / `terminal_list` |
|
|
25
|
+
| `dsh-cua-drive` | `cua_*` |
|
|
26
|
+
| `dsh-browser-use` | `browser_*` |
|
|
27
|
+
| `dsh-config-manager` | `config_*` |
|
|
28
|
+
| `dsh-experimental-session-search` | `session_search` 等 |
|
|
29
|
+
| `dsh-lan-gateway` | `lan_gateway` |
|
|
30
|
+
| `dsh-base` 宿主行 | `working_directory` / `*_mcp_resource*` / `load_workspace_dependencies` |
|
|
31
|
+
|
|
32
|
+
其中 `terminal_*` 等价于一个完整 shell —— 对「防 prompt 注入」这个卖点是致命的。
|
|
33
|
+
|
|
34
|
+
本包因此附带一个宿主级插件 `lib/guard.js`:在 `agent/created` 时判定 agent 绑定的预设,命中 `dsh-chat-loli` 就调 `agent.ctx.tools.restrict({ deny })` 把当前全局工具集合整体遮掉。依据是 `dsh-agent` 的契约 —— *`Agent.ctx` 是该 agent 的 scoped context,通过它做的注册(含 restrictions)只作用于该 agent*。遮罩只作用于全局层,预设自己 scoped 注册的 `web_search` / `web_fetch` 不受影响。
|
|
35
|
+
|
|
36
|
+
工具名是运行时现算的(`ctx.tools.schemas()` 省略 scope = 全局视图),因为 `restrict` 对未知名字会直接抛错。
|
|
37
|
+
|
|
38
|
+
配置(`cordis.patch.yml` 内):
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- id: chat-loli-guard
|
|
42
|
+
name: 'dsh-chat-loli/guard'
|
|
43
|
+
config:
|
|
44
|
+
preset: dsh-chat-loli # 要收窄的预设 id
|
|
45
|
+
keep: [] # 额外保留的全局工具名
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
边界:
|
|
49
|
+
|
|
50
|
+
- 只对**新建 / resume** 的 agent 生效;插件装上之前就在跑的会话需要新开。
|
|
51
|
+
- 判定拿不到预设 id 时**放行**(宁可不动作,也不误伤别的会话),所以它是兜底,不替代把宿主 bundle 关掉这种硬隔离。
|
|
52
|
+
- 晚注册的全局工具(运行中才连上的 MCP server)不会被追认。
|
|
53
|
+
|
|
16
54
|
## 适用场景
|
|
17
55
|
|
|
18
56
|
给 QQ / 微信这类**群聊机器人**用的聊天预设:即使被 prompt 注入改了人设,它也碰不到文件和 shell,只能说话和上网搜索——不会出现「把工作区打包发到群里」这种事故。
|
|
19
57
|
|
|
20
|
-
注意:宿主级工具(例如 `qqbot_send_file`、`lan_gateway`)不受预设控制,仍会对所有 agent 可见。
|
|
21
|
-
|
|
22
58
|
## 安装
|
|
23
59
|
|
|
24
|
-
在 DSH 里把本包作为 bundle 安装(profile
|
|
60
|
+
在 DSH 里把本包作为 bundle 安装(profile 级),或在插件市场里点安装。
|
|
25
61
|
|
|
26
62
|
安装后在 Agent 预设下拉里出现「鲸鱼娘捆绑模式」,开新会话或 `/new` 后生效;已有会话保持原预设(started 锁定)。
|
|
27
63
|
|
package/cordis.patch.yml
CHANGED
|
@@ -12,6 +12,26 @@
|
|
|
12
12
|
#
|
|
13
13
|
# 预设 id: dsh-chat-loli,显示名: 鲸鱼娘捆绑模式,order: 8。
|
|
14
14
|
# 安装后在 Agent 预设下拉里出现,开新会话即用。
|
|
15
|
+
#
|
|
16
|
+
# ── 工具收窄守卫(lib/guard.js)──────────────────────────────────────────────
|
|
17
|
+
#
|
|
18
|
+
# 预设只控制「agent 平面」的 23 个工具行;宿主进程级的全局工具
|
|
19
|
+
# (terminal_* / browser_* / cua_* / lan_gateway / *_mcp_resource 等)不在预设
|
|
20
|
+
# 管辖范围,任何会话都继承。对一个对外承诺「没有 shell、没有文件读写」的预设
|
|
21
|
+
# 来说,那条 terminal_* 就是完整 shell。
|
|
22
|
+
#
|
|
23
|
+
# 这个宿主级行补上剩下的那一半:agent 创建时判定它绑的预设,命中就把当前
|
|
24
|
+
# 全局工具集合整体 deny 掉(agent.ctx.tools.restrict,只对那一个 agent 生效)。
|
|
25
|
+
# 遮罩只作用于全局层,本预设自己 scoped 注册的 web_search / web_fetch 不受影响。
|
|
26
|
+
|
|
27
|
+
- insert:
|
|
28
|
+
- id: chat-loli-guard
|
|
29
|
+
name: 'dsh-chat-loli/guard'
|
|
30
|
+
config:
|
|
31
|
+
# 要收窄的预设 id。本包只服务一个预设,保持默认即可。
|
|
32
|
+
preset: dsh-chat-loli
|
|
33
|
+
# 想额外保留的全局工具名(宿主级)。留空 = 全部隐藏。
|
|
34
|
+
keep: []
|
|
15
35
|
|
|
16
36
|
- insert:
|
|
17
37
|
- id: preset-dsh-chat-loli
|
package/icon.webp
CHANGED
|
File without changes
|
package/lib/guard.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-chat-loli 的工具收窄守卫。
|
|
3
|
+
*
|
|
4
|
+
* 为什么这个文件在预设包里:预设只控制「agent 平面」的工具行(bash / fs /
|
|
5
|
+
* skill / todo / subagent …),宿主进程级的全局工具(terminal_* / browser_* /
|
|
6
|
+
* cua_* / lan_gateway / MCP 资源 …)不在预设的管辖范围,任何会话都继承。
|
|
7
|
+
* 这个预设对外承诺「没有 shell、没有文件读写」,那个承诺有一半得靠这里兑现。
|
|
8
|
+
*
|
|
9
|
+
* 做法:在 `agent/created` 时判定 agent 绑定的预设;命中目标预设就给该 agent
|
|
10
|
+
* 加一层工具遮罩。依据是 dsh-agent 的文档契约:
|
|
11
|
+
*
|
|
12
|
+
* Agent.ctx 是这个 agent 的 scoped context —— 通过它做的注册(tools、prompt
|
|
13
|
+
* sections、variables、event listeners、restrictions)只作用于该 agent,
|
|
14
|
+
* 并在销毁时自动撤销。
|
|
15
|
+
*
|
|
16
|
+
* 遮罩只作用于「全局层」,预设自己 scoped 注册的工具不受影响,所以本预设的
|
|
17
|
+
* web_search / web_fetch 仍然可见。
|
|
18
|
+
*
|
|
19
|
+
* 名字清单在运行时用 ctx.tools.schemas()(省略 scope = 全局视图)现算,
|
|
20
|
+
* 而不是硬编码:`restrict` 对未知名字会抛错,这样进程里装了什么就遮什么。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Cordis 插件名(loader 诊断用)。 */
|
|
24
|
+
export const name = 'chat-loli-guard'
|
|
25
|
+
|
|
26
|
+
/** 需要的宿主服务。 */
|
|
27
|
+
export const inject = ['tools', 'agentPresets', 'agents']
|
|
28
|
+
|
|
29
|
+
const DEFAULTS = {
|
|
30
|
+
preset: 'dsh-chat-loli',
|
|
31
|
+
keep: [],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 读取一个活着的 agent 用的是哪个预设。
|
|
36
|
+
*
|
|
37
|
+
* 主路径是 `composedPreset`;退回 `inspectCompositions` 是因为它按 agent 的
|
|
38
|
+
* 绑定查精确 revision,语义更窄但也更可靠。两条都拿不到就当没绑定,
|
|
39
|
+
* 直接放行(宁可不动,也不要误伤别的会话)。
|
|
40
|
+
*
|
|
41
|
+
* @param ctx - 插件所在的宿主 context。
|
|
42
|
+
* @param agent - 目标 agent。
|
|
43
|
+
* @returns 预设 id,或 undefined。
|
|
44
|
+
*/
|
|
45
|
+
function presetOf(ctx, agent) {
|
|
46
|
+
try {
|
|
47
|
+
const id = ctx.agentPresets.composedPreset(agent.ctx)
|
|
48
|
+
if (id) return id
|
|
49
|
+
} catch {
|
|
50
|
+
/* 落到下面的兜底 */
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return ctx.agentPresets.inspectCompositions(agent.ctx)[0]?.id
|
|
54
|
+
} catch {
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 给一个 agent 装上工具遮罩。
|
|
61
|
+
*
|
|
62
|
+
* @param ctx - 插件所在的宿主 context。
|
|
63
|
+
* @param agent - 刚创建(或刚切换预设)的 agent。
|
|
64
|
+
* @param presetId - 要收窄的预设 id。
|
|
65
|
+
* @param keep - 额外保留的全局工具名。
|
|
66
|
+
* @param applied - 已经处理过的 agent 集合,避免重复叠加。
|
|
67
|
+
* @returns 是否真的装了遮罩。
|
|
68
|
+
*/
|
|
69
|
+
function tighten(ctx, agent, presetId, keep, applied) {
|
|
70
|
+
if (!agent || typeof agent !== 'object' || applied.has(agent)) return false
|
|
71
|
+
if (presetOf(ctx, agent) !== presetId) return false
|
|
72
|
+
|
|
73
|
+
let deny
|
|
74
|
+
try {
|
|
75
|
+
// 省略 scope 参数 = 全局视图,所以这里拿到的是「宿主级」工具,不含
|
|
76
|
+
// 预设 scoped 注册的名字(那些名字传给 restrict 会直接报错)。
|
|
77
|
+
deny = ctx.tools
|
|
78
|
+
.schemas()
|
|
79
|
+
.map((schema) => schema.name)
|
|
80
|
+
.filter((toolName) => !keep.has(toolName))
|
|
81
|
+
} catch (error) {
|
|
82
|
+
ctx.logger?.warn?.('chat-loli-guard: 读取全局工具失败:%s', String(error?.message ?? error))
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (deny.length === 0) return false
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
agent.ctx.tools.restrict({ deny })
|
|
90
|
+
} catch (error) {
|
|
91
|
+
ctx.logger?.warn?.('chat-loli-guard: 收窄失败(preset=%s):%s', presetId, String(error?.message ?? error))
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
applied.add(agent)
|
|
96
|
+
ctx.logger?.info?.('chat-loli-guard: 已对预设 %s 隐藏 %d 个宿主级工具', presetId, deny.length)
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 注册监听。
|
|
102
|
+
*
|
|
103
|
+
* 监听器在 `agent/created` 这一轮里执行,AgentLoop 会等到监听器全部结束才
|
|
104
|
+
* 放行排队输入,所以遮罩在第一次请求之前就已就位。任何异常都被吞掉:
|
|
105
|
+
* `agent/created` 里抛出会让创建失败,不该为了收窄工具把会话弄挂。
|
|
106
|
+
*
|
|
107
|
+
* @param ctx - 宿主 context。
|
|
108
|
+
* @param config - `{ preset?, keep? }`。
|
|
109
|
+
*/
|
|
110
|
+
export function apply(ctx, config = {}) {
|
|
111
|
+
const presetId = typeof config.preset === 'string' && config.preset ? config.preset : DEFAULTS.preset
|
|
112
|
+
const keep = new Set(Array.isArray(config.keep) ? config.keep : DEFAULTS.keep)
|
|
113
|
+
const applied = new WeakSet()
|
|
114
|
+
|
|
115
|
+
// 新建或 resume 出来的 agent —— 这是主要入口。
|
|
116
|
+
ctx.on('agent/created', ({ agent }) => {
|
|
117
|
+
tighten(ctx, agent, presetId, keep, applied)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// 会话从别的预设切进目标预设。切换不会重新触发 agent/created,所以补这条。
|
|
121
|
+
ctx.on('agent-preset/selected', (sessionId, agentPreset) => {
|
|
122
|
+
if (agentPreset !== presetId) return
|
|
123
|
+
const agent = ctx.agents.get(sessionId)
|
|
124
|
+
if (agent) tighten(ctx, agent, presetId, keep, applied)
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default { name, inject, apply }
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-chat-loli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "DSH agent preset 「鲸鱼娘捆绑模式」:鲸鱼娘人设 + 仅 web_search / web_fetch
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "DSH agent preset 「鲸鱼娘捆绑模式」:鲸鱼娘人设 + 仅 web_search / web_fetch,并用 ctx.tools.restrict 收窄该预设继承的宿主级全局工具(shell / 文件读写 / glob/grep / 记忆写入 一个都不挂,宿主层的 terminal_* / cua_* / browser_* 等也一并隐藏)。人设来自 dsh-whale-loli,MIT。",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"main": "lib/guard.js",
|
|
6
7
|
"license": "MIT",
|
|
7
8
|
"keywords": [
|
|
8
9
|
"deepseek-harness",
|
|
@@ -11,12 +12,18 @@
|
|
|
11
12
|
"cordis",
|
|
12
13
|
"agent-preset",
|
|
13
14
|
"chat",
|
|
14
|
-
"persona"
|
|
15
|
+
"persona",
|
|
16
|
+
"tool-restriction"
|
|
15
17
|
],
|
|
16
18
|
"exports": {
|
|
17
|
-
"
|
|
19
|
+
".": "./lib/guard.js",
|
|
20
|
+
"./guard": "./lib/guard.js",
|
|
21
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
22
|
+
"./icon": "./icon.webp",
|
|
23
|
+
"./package.json": "./package.json"
|
|
18
24
|
},
|
|
19
25
|
"files": [
|
|
26
|
+
"lib",
|
|
20
27
|
"cordis.patch.yml",
|
|
21
28
|
"icon.webp",
|
|
22
29
|
"README.md",
|