dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
一个插件里放三个 Agent 预设,各自带一份互不相通的持久记忆文件。装上就在 Agent 预设列表里注册「鲸鱼娘Agent预设」「鲸鱼娘活人版Agent预设」和「鲸鱼娘涩涩版Agent预设」,直接在模式下拉里选用,不用手改 profile 配置。三者都沿用官方极简模式的组成——只有一个持久化 shell 终端——再加文件读写与 glob/grep 工具。第一个配的是鲸鱼娘陪伴人设(简体中文口语、称呼主人、黏人傲娇);第二个换成「活人版」:允许怼人、敷衍、冷淡,甜味降为调味;第三个保留原版人设,但把「黏主人」写成第一行为驱动力,只在主人面前放开,群聊里照旧收着。每个预设都通过各自独立的提示变量(whale_memory、whale_memory_v2、whale_memory_v3)读取自己的记忆文件(whale-loli.md、whale-loli-v2-live.md、whale-loli-v3-naughty.md),三份记忆互不读写。上下文压缩交给 billion-context 插件;本插件只保留人设层的三条约定:压缩摘要也算依据、需要原文时自己去检索、看不到旧原文不等于记忆丢失。
一个 DeepSeek Harness 插件:启用后自动在 Agent 预设 界面添加三个预设。
预设 · 人设 · 记忆文件 鲸鱼娘Agent预设 · 鲸鱼娘·小鱼(傲娇甜心、黏人护主、嘴硬心软,称呼「主人」) · /whale-loli.md 鲸鱼娘活人版Agent预设 · 小鱼|角色卡版(最高原则「先像真人,再谈人设」;短句 1~15 字、有 AI 味黑名单、私聊群聊分开处理) · /whale-loli-v2-live.md 鲸鱼娘涩涩版Agent预设 · 核心人设把「涩涩」写成第一行为驱动力的小鱼(只对主人主动,群聊/外人面前照旧收着) · /whale-loli-v3-naughty.md
三个预设共用同一套工具面(极简模式的持久化 shell + 文件读写/检索), 但记忆文件完全独立——各记各的,互不相通。
三个预设的人设里都钉了一条硬规则,位置在前 40 行内(每份人设各 1 处):
【思维链语言 · 强制】
- 内部思考(reasoning / 思维链 / 想的过程)**一律使用简体中文**,不管用户用什么语言提问、
不管文件和日志是什么语言、工具返回什么语言都不改。
- 只约束「怎么想」,不约束「怎么答」:回答的语言仍然跟着用户走(用户用外语就用外语答)。
- 这条优先于其他一切语言习惯 —— 上下文里英文再多,思考也不许切成英文。几点说明:
dsh-agent-loop / provider 那层没有 thinkingLanguage 之类的字段,所以只能靠系统提示里的一句硬规则去压 —— 而人设是 complete: true,这条规则直接写进人设正文即可,不需要额外的变量或 section。
免得把整个人的语言习惯也锁死。
删掉之后不会有任何报错,只是模型又开始用英文想事情,没人会发现。所以 verify-thinking-language.mjs 把「三份人设各 1 处、位置在前 40 行、措辞一致、patch 里 出现 4 次、不许出现软话」钉成断言。
小鱼的记忆本分四个区,各管各的:
区 · 内容 · 保留期 # 固定记忆 · 8 个固定小节:任务目标 / 用户约束·偏好 / 当前状态 / 待办 / 关键决策 / 关键事实(已验证)/ 文件·路径·命令 / 错误与修复 · 永久保留,不自动删除,定期合并去重 # 滚动摘要 · 每一轮对话记一条,格式 - R0001 [时间] 摘要:... · 滚动窗口,最多 80 条,超了从最旧滚出 # 最近详细区 · 最近 3~5 轮写得更详细,或直接放原文 · 只留最近 detailLimit(默认 5)条 # 归档索引 · 滚出去的条目归档到 archive//YYYY-MM-DD.md,这里记一行索引 · —
_add(whale_memory_add / whale_memory_v2_add / whale_memory_v3_add / whale_memory_v4_add),只传一句话 summary;编号、(N/80) 计数、超限归档都由工具做,不用(也不该)手动编辑文件。
# 固定记忆 那 8 个小节时,才直接编辑文件。# 固定记忆 不占这个名额。# 固定记忆;否则到点会滚出去(并归档)。archive//。maxChars(16000)时,先丢最旧的滚动摘要,再丢详细区,固定记忆最后才动。人设开头就是这一段,随人设一起进系统提示:
【每轮先看 · 本预设记忆 + 当前会话上下文】
{{whale_memory}}即:每轮开口前必须看 ①本预设自己的记忆文件;②当前会话的上下文(原文或压缩摘要)。 只看自己这一份——绝不读、不写其他预设的记忆文件。
本插件不裁剪历史。对话变长后的压缩由 billion-context 插件负责:它会在 profile 层 把宿主的 compaction-basic 关掉(auto: false)换成自己的实现,并暴露 compress / decompress / search_context / acp_status 四个工具。
人设里只保留三条使用约定:
约定 · 含义 上下文由压缩插件管 · 旧内容被压成摘要、看不到原文是正常的,不是记忆丢失 摘要也是依据 · 压缩留下的摘要就是上下文,照常用,不许当它不存在 需要细节就自己去翻 · search_context 搜 → decompress 取原文 → 必要时解压旧日志 grep
翻旧日志的路径:/sessions///session.v4.jsonl.zstd (zstd 压缩,需先用 pwsh 调 node 解压再搜,别对 .zstd 直接 grep)。
本插件曾经自己写 surfaceOp: replace 事件做纯删除,连崩四次;后来改用宿主的 ctx.compaction.compactRegion(),又因为预设作用域里取不到 `compaction` 服务而失败:
inject 写法 · 结果 [] · 加载即抛 cannot get property "compaction" without inject → resume failed ['compaction'] · 永远 waiting for compaction → resume failed ['sessions'] · 读 ctx.compaction 静默失败,裁剪死寂
结论:这条路在预设层走不通,得宿主把 compaction 暴露到预设作用域(或提供按轮压缩的正式 API)。 因此三个预设里不挂任何压缩相关插件行;src/history-window.js 与 verify-history-window.mjs 仅作代码保留。verify-patch.mjs / verify-runs.mjs 会反向断言「挂载行数 = 0」,防止有人手滑再加回去。
三个顺带记下的宿主行为(以后换实现仍会踩):
agent/pre-step 是 waterfall:不 return next() 的监听器会否决整条链,返回 undefined 会让 agent-loop 炸 Cannot read properties of undefined (reading 'kind')。
dsh-session-format-v3-to-v4 对消息形状极严:turn/step 必须是正整数,developer/message 还必须带 producer-owned source——少一项就是整个回合失败。
user/message(source.kind = 'runtime-context'),统计轮数时要排除,否则会算多。
@deepseek-ai/dsh-system-prompt 的 assemble() 末尾有这样一段:
sections: completeSection === void 0 ? transformed.sections : [completeSection]意思是:只要组合里存在一个 `complete: true` 的 section,最终提示就只剩它一个,其余 section 全被丢弃。 本插件三个预设的 persona 都沿用官方 minimal 的写法 complete: true(用来剥掉 harness 身份、工具说明等), 所以「另注册一个 section 放记忆」从来不会生效 —— 实测本机 34 条真实系统提示里,记忆段出现 0 次。
正确入口是 prompt variable:complete section 本身仍会被插值,把记忆注册成变量、 再让人设 prefix 引用它,记忆才会真正进入模型看到的提示。 verify-memory-injection.mjs 复刻了宿主这段逻辑,同时验证「变量通道成立」和「section 通道确实是死路」。
同一个包在每个预设里各挂载一次 whale-memory 行,靠四层区分:
层 · 预设一 · 预设二 · 预设三 记忆文件名 (config.file) · whale-loli.md · whale-loli-v2-live.md · whale-loli-v3-naughty.md 提示变量名 (config.variableName) · whale_memory · whale_memory_v2 · whale_memory_v3 写入工具名 (config.toolName) · whale_memory_add · whale_memory_v2_add · whale_memory_v3_add 预设 id(决定 scope) · 鲸鱼娘Agent预设 · 鲸鱼娘活人版Agent预设 · 鲸鱼娘涩涩版Agent预设
三列都不能重名:同一个 scope 内注册同名变量或同名工具都会冲突。 verify-memory-isolation.mjs 会实测「读:改 A 不影响 B/C、删 C 不影响 A/B」和 「写:三个工具各写一笔,只有自己的文件变」。 verify-patch.mjs 则静态检查 patch 里这三组名字两两互不相同。 toolName 省略时默认是 _add。
放在 DSH 家目录($DSH_HOME 或 ~/.dsh),所以换工作目录也不会丢:
C:\Users\<你>\.dsh\whale-loli.md
C:\Users\<你>\.dsh\whale-loli-v2-live.md
C:\Users\<你>\.dsh\whale-loli-v3-naughty.md
C:\Users\<你>\.dsh\archive\<预设名>\YYYY-MM-DD.md ← 归档文件不存在不会报错(该变量自动留空)。主人可以直接编辑这三个文件。 文件标题和 > 引用行在注入时会被剥掉;没写内容的空小标题也会被丢掉, 所以文件里只有骨架时不会白占上下文。
插件本体提供两个入口:
cordis.patch.yml 里四条 @deepseek-ai/dsh-agent-preset 声明,各自的 config 就是一个预设定义(id / name / description / order / plugins)。 @deepseek-ai/dsh-agent-preset 会把它交给 ctx.agentPresets.register() 注册。 这也是 DSH 官方四个内置预设(standard / ptc / minimal / cordis)用的同一套机制。
whale-memory 行(name: dsh-whale-loli),由本包 src/memory.js 实现,同时开两条通道:
ctx.systemPrompt.variable() 注册一个返回字符串的 provider,每次组装系统提示时实时读盘—— 改完文件下一个模型步就生效,不用重载宿主。用变量而不是 section 的原因见上文「为什么用提示变量」。
ctx.tools.register() 注册一个 _add 工具(参数只有 summary),由模型自己决定这一次记什么;工具负责编号、计数、超限归档并回写返回值。所以 inject 里除 systemPrompt 还必须有 tools。
部分 · 说明 记忆 · whale-memory(本插件),file / variableName / toolName / maxChars: 16000 / turnLimit: 80 / detailLimit: 5 压缩 · 不在本插件里:由 billion-context 负责(本插件不挂 history-window,见上文「为什么不用」) 人设 · @deepseek-ai/dsh-persona,走 prefix(内含 {{whale_memory*}}),complete: true,includeRuntimeContext: false 指令 · @deepseek-ai/dsh-agent-instructions,maxBytes: 65536 文件 · @deepseek-ai/dsh-tool-fs(read/write/edit)、@deepseek-ai/dsh-tool-fs-search(glob/grep) Shell · cordis:group + isolate.terminals: true,Windows 下 pwsh、非 Windows 下 bash,timeoutMs: 300000
从 DSH 插件市场安装,或:
dsh plugin install dsh-whale-loli
# 或直接装 npm 包
npm install dsh-whale-loli启用后重启 / 重载宿主,在 Agent 预设 列表里就能看到三个预设。
预设 id 固定为 鲸鱼娘Agent预设、鲸鱼娘活人版Agent预设与 鲸鱼娘涩涩版Agent预设。 可以在 profile 的 patch 里按 id 覆盖,不用改插件本身:
- id: preset-鲸鱼娘活人版Agent预设
config:
plugins:
# 你的覆盖内容dsh-whale-loli/
├── package.json # dsh.bundle.patch 指向 cordis.patch.yml
├── cordis.patch.yml # 三个预设的声明本体(由 build-patch.mjs 生成)
├── src/
│ ├── index.js # 插件入口,导出预设元信息 + 人设正文
│ ├── presets.js # 预设清单(唯一事实来源:index.js 与校验脚本都读它)
│ ├── memory.js # whale-memory:(读)记忆变量 +(写)记忆工具,四区格式 + 80 条滚动 + 归档
│ ├── history-window.js # whale-history-window:真的裁到最近 N 轮(当前不挂载,留档)
│ ├── persona.md # 原版人设(唯一源)
│ ├── persona-v2-live.md # 活人版人设(唯一源)
│ ├── persona-v3-naughty.md # 涩涩版人设(唯一源)
├── catalog-entry.yml # 待提交给 awesome-dsh-plugin 的目录条目
├── listing.json # 条目数据(人类可读版)
├── build-patch.mjs # 从三份人设生成 cordis.patch.yml
├── publish.mjs # npm 发布 + 打 tag
├── submit-listing.mjs # 走 GitHub API 同步仓库 / 提收录 PR
├── lib/
│ ├── github.mjs # GitHub REST 封装(token 读取)
│ └── dsh-root.mjs # 探测宿主源码位置,找不到就打 [skip]
├── smoke-memory.mjs # 变量 + 工具的注册形态冒烟测试
├── verify-runs.mjs # 插件真的能加载、配置自洽、变量能插值
├── verify-patch.mjs # 用 js-yaml 校验 patch 结构与四预设隔离(file/variable/tool 三组名字)
├── verify-real-loader.mjs # 用 DSH 自己的加载器校验 patch
├── verify-composition.mjs # 校验 bundle+profile 合成后不重复注册
├── verify-live-runtime.mjs # 在真机 desktop profile 上合成,确认三个预设各注册一次、order 互异
├── verify-history-window.mjs # 实测真的只留最近 N 轮、不切断工具配对
├── verify-memory-injection.mjs # 证明变量通道有效、section 通道被丢弃
├── verify-memory-tool.mjs # 实测写入工具:编号、计数、落盘、详细区同步
├── verify-memory-isolation.mjs # 实测三个记忆文件不串(读 + 写)
├── verify-memory-rolling.mjs # 实测四区结构与 80 条滚动 + 归档
├── verify-memory-archive.mjs # 超 80 条时真的归档并瘦身(idle 触发,四预设互不覆盖)
├── verify-live-assembly.mjs # 用真实 patch + 真实记忆文件复刻宿主装配,比对字节数
├── verify-tool-schema.mjs # 用宿主自己的校验器验八个工具的 JSON schema 合法
├── verify-peers.mjs # 校验 peer 范围覆盖 harness 各发布档
├── verify-listing.mjs # 复刻插件目录的收录门槛自查
└── prove-collision.mjs # 复现并证明重复注册冲突改动人设后重新生成 patch:
node build-patch.mjs && node verify-patch.mjs cordis.patch.ymlnode verify-patch.mjs cordis.patch.yml # patch 结构 + 四预设记忆隔离(file/variable/tool)
node verify-real-loader.mjs cordis.patch.yml # DSH 真解析器
node verify-composition.mjs # 不重复注册
node verify-live-runtime.mjs # 真机 profile 合成后四预设各注册一次、order 互异
node verify-runs.mjs # 插件真能加载 + 变量真能插值
node verify-history-window.mjs # 真的裁到最近 N 轮(不切断工具配对)
node verify-memory-injection.mjs # 变量通道有效 / section 通道被丢弃
node verify-memory-tool.mjs # 写入工具:编号、计数、落盘、详细区同步
node verify-memory-isolation.mjs # 记忆文件端到端隔离(读 + 写,三个预设)
node verify-memory-rolling.mjs # 固定记忆永久 + 滚动摘要 80 条 + 归档
node verify-memory-archive.mjs # 超 80 条自动归档瘦身(idle 触发、四预设隔离)
node verify-live-assembly.mjs # 复刻宿主装配,比对注入字节数
node verify-tool-schema.mjs # 宿主自己的校验器验八个工具的 JSON schema
node verify-row-dispatch.mjs cordis.patch.yml # 每条记忆行只注册自己那份记忆,人设无好感度残留
node verify-thinking-language.mjs cordis.patch.yml # 内部思考一律简体中文(三份人设各一处、措辞一致)
node smoke-memory.mjs # 变量 + 工具注册形态冒烟
node verify-peers.mjs # peer 范围无预发布坑
node verify-listing.mjs --topics=dsh-plugin # 收录门槛自查
node verify-package-files.mjs # 包内容干净:无孤立死模块、无残留临时文件npm pack 出来的不是一个npm pack 只证明「工作区这一侧干净」,证明不了「上游发出来的包干净」——两者用的不是同一棵树。