dsh-chat-preset
给 dsh 的 ChatOnly 预设打包的一整套实现:预设行本身,加上它应有的请求形态——没有运行时上下文快照、没有工具 schema、系统提示词从文件热读。
目录
为什么需要它
dsh 的默认请求对「纯对话」这个场景是过重的。三件事会同时出现在每一条 ChatOnly 请求里:
- 运行时上下文快照——
dsh-working-directory把当前工作目录注入成一条runtime-context用户消息; - 全局工具 schema——桌面端 bundle 注册的
acp_*、sidebar_open、working_directory、search_context等,约 10 个工具对纯对话没有任何意义,却占着 prompt 预算; - 写死在预设里的系统提示词——改一次要重启,没法边聊边调。
前两项都不能在预设行里解决:
- 上下文那项被注册成
required: true。预设的includeRuntimeContext: false走的是suppressRuntimeContext(),它只移除 可选 contribution,refreshContext()仍会保留所有required === true的条目。 - 工具是全局收集的(
dsh-system-prompt/lib/index.js:364-365),预设作用域只能加、不能删,也没有suppressTools()。 - 连
agent/pre-step里改写 decision 也没用:dsh-agent-loop的step()会自己重算上下文并 append 到 surface,decision.messages只用来判断要不要接纳它。
所以这个包换了一条路:在 agent 作用域里用同名影子覆盖那个 contribution,再从装配瀑布里把工具列表清空。详细推导和源码行号见 index.js 头部注释。
它做什么
| 能力 | 做法 | 结果 |
|---|---|---|
| 拦截运行时上下文快照 | 在 agent 作用域用同名影子覆盖 working-directory:current(required: false + text: '') |
请求与会话日志里都不出现 Current runtime context / 工作目录那句 |
| 剥离工具 | 钩 system-prompt/assemble 瀑布,清空 assembly.tools |
请求里连 tools 键都没有 |
| 系统提示词热读 | 占 deployment:persona-prefix 槽位(complete: true),text 是函数,每次装配读文件 |
改文件 → 下一条消息生效,免重启 |
前两项只对 config.presetId(默认 chatonly)生效,其他预设不受影响。
两个 role
同一个模块挂两次,靠 config.role 分流:
| role | 挂载位置 | 做什么 |
|---|---|---|
policy(默认) |
全局 | 拦截上下文、剥离工具、注册 /chatprompt |
section |
预设的 plugins 里 |
占 persona 槽位,从文件读系统提示词 |
前置要求
- dsh(桌面端或 CLI),
dsh plugin子命令可用 - pnpm 在
PATH上——dsh plugin add内部就是转发pnpm add - Node.js ≥ 18(ESM;
test.mjs用到顶层await)
安装
dsh 把「把一个包装进 profile」做成了 pnpm 转发 + 事后对账,所以不用手改 bundles:
# 直接从 GitHub 装
dsh plugin --profile desktop add github:Xxx-Bin/dsh-chat-preset
# 或先克隆再 link(改代码即时生效,调试用这个)
git clone git@github.com:Xxx-Bin/dsh-chat-preset.git
dsh plugin --profile desktop add link:/absolute/path/to/dsh-chat-preset
dsh plugin <profile> add 会在 profile 目录里跑 pnpm add,然后对账 dsh.profile.bundles:凡是已安装的依赖且 package.json 声明了 dsh.bundle 的,自动追加进 layer stack。本包声明了 dsh.bundle.patch,所以加完依赖它自己就进 bundles 了。
重启 dsh,在预设选择里勾 ChatOnly。
手动安装(不依赖 dsh CLI)- 在
~/.dsh/profiles/<profile>/package.json的dependencies里加"dsh-chat-preset": "link:/absolute/path/to/dsh-chat-preset" - 把它加进同文件的
dsh.profile.bundles数组 - 在该 profile 目录跑
pnpm install - 重启 dsh
装完先确认它真的加载了:看 ~/.dsh-filter-rtc-debug.log,应该出现 {"evt":"applied",...} 和 {"evt":"prompt-section",...} 各一行。
配置
cordis.patch.yml 里的 chatonly-context-policy 行:
- id: chatonly-context-policy
name: 'dsh-chat-preset'
config:
role: 'policy'
presetId: 'chatonly' # 目标预设 id
contextName: 'working-directory:current' # 要覆盖的 contribution 名
stripTools: true # false = 保留工具
file: '~/.dsh/chatonly-system-prompt.md' # /chatprompt 的写入目标
系统提示词
- 文件:
~/.dsh/chatonly-system-prompt.md(空文件 = 不发任何系统消息) - 命令:
/chatprompt(预览)·/chatprompt show·/chatprompt clear·/chatprompt 正文… - 文件内容就是全部系统提示词,不会自动拼
You are an AI agent…之类的框架话
限制与冲突
1. 没有上下文压缩
本预设不挂压缩插件,没有自动压缩也不支持 /compact。对话一旦超出模型上下文窗口,直接失败,不会自动收缩。
2. 与 dsh-prompt-setting 的「我的 Prompt」互斥
complete: true 会吞掉同作用域的其他段。两者不能共存。dsh-prompt-setting 自己的实验文档也写了这一点(core/experiments.js E3):complete 段生效时,最终装配只剩它一个,监听器改写的顺序会被还原。
3. 一个作用域内同名 section 只能注册一次
不要在预设里再挂 dsh-persona,会撞名抛错。
4. 旧会话不追溯
修复前落库的快照仍在。那类会话下次对话会多一条一次性 Current runtime context: none.,新会话没有。
排查
~/.dsh-filter-rtc-debug.log,一行一个 JSON:
| 事件 | 含义 |
|---|---|
applied |
插件加载(stripTools 值一并记下) |
suppress |
影子已在 agent 作用域注册 |
strip-tools |
本次装配剥掉了 N 个工具(带名字) |
prompt-section |
section 注册成功,带文件路径与当前字数 |
miss / error |
能力缺失或异常(带 stage) |
会话侧要看解压后的日志(会话文件是 11 帧拼接的 zstd,普通解压只读得到 header)。
测试
node test.mjs # 11 项:作用域隔离、预设判定、幂等、降级
不依赖任何第三方包,只用到 node: 内置模块,clone 下来直接能跑。
No comments yet. Be the first to write one.