Repository navigation
Releases: Bcy2020/dsh-cc-ecosystem
Release list
v0.5.0 — dsh-cc-memory
v0.5.0 — 新增 dsh-cc-memory
新包 dsh-cc-memory@0.1.0:把 Claude Code 的 auto-memory 注入 DSH。
MEMORY.md索引与 pinned 正文进入系统提示,每次请求重读;索引 200 行 / 25,000 字符整行截断 + WARNING。/cc-memory模态面板,两个开关:启用 Memory(关掉后 DSH 完全看不到)与允许写入(只读 / 可写);第一个关闭时第二个置灰。- 工具:
cc_memory_list/cc_memory_read/cc_memory_write/cc_memory_edit;可写走「写文件 → 改索引」两步法。 - 开关持久化到
$DSH_HOME/cc-memory-state.json,改动立即生效,无需重启。 - 全家桶
dsh-cc-ecosystem依赖并挂载它。
协同发版:dsh-cc-loader / cc-skills / cc-agents / cc-hooks / cc-mcp / dsh-cc-ecosystem 0.5.0,cc-permissions 0.6.0,dsh-cc-memory 0.1.0。
参考了 Claude Code 的实现(CLI 2.1.275)。 Memory 说明段(Types / What NOT / How to save / When to access / Before recommending / persistence)逐字对齐;仅替换工具名、只读句与 DSH 的
{{…}}插值约束。
安装(务必带精确版本):
dsh plugin --profile <name> add dsh-cc-ecosystem@0.5.0v0.4.0 — 按 Claude Code 的逐组件作用域重做发现层
⚠️ 破坏性变更 —— 升级前请读「破坏性变更」一节。
这一版参考 Claude Code 的实现重做了发现层,有多处会改变已有配置的含义。
协同发版:六包0.4.0,cc-permissions0.5.0。
根因:/mcp 面板空白,副标题还显示一个假的 C:\
我们从一开始就在实现一个 Claude Code 里不存在的概念:「项目根」(五个包共用)。它的假设是「所有 .claude 资产都从同一个根往上累积」,而实际上 ——
| 组件 | 从会话 cwd 往上读到哪里 |
|---|---|
| 技能 / 命令 / 子代理 | 到 git 根为止(家目录不含) |
.claude/rules/** |
整条祖先链到文件系统根(不含根本身),根在前 |
.mcp.json(project) |
整条祖先链,近的覆盖远的 |
settings.json / hooks |
只读 <cwd>/.claude/,不向上找 |
| MCP 的 user / local | 不在项目里 —— 在 ~/.claude.json |
最后一行就是面板空白的直接原因:~/.claude.json 我们从来没读过。在发现这个问题的机器上,它顶层有 6 个 server,旧版一个都没注册。
主要改动
- 逐组件发现(新增
scopes.js,删掉findProjectRoot/findClaudeProjectRoot)。技能类走「有界祖先」、规则走「全链祖先」,两种走查的差异是照抄的——那是 Claude Code 自己的行为。 - MCP 现在读它读的三个作用域:
~/.claude.json的 user / local,加上各层祖先的.mcp.json与插件内联。查找优先级照抄enterprise > managed > local > project > user。 - 面板显示来源:每条 server 点进去会显示 读自哪个作用域、哪个文件。不再显示项目根(没有那个概念了)。
- rank 数字(150/160/170)废弃 —— Claude Code 没有数值优先级,顺序就是语义。
- MCP 连接池化 + 并行拨号:按 (server, 配置) 复用连接,配置没变就不重拨(照抄 Claude Code 的
reconcileMcpServers);闲置idleTimeoutMs后自动关闭。修的是两件体感问题 —— 面板一行一行冒出来、以及换个工作区再开/mcp又要重来一遍。
破坏性变更
projectRootMarkers配置项从五个包里删除。 配了它的 profile 需要删掉这一项,否则配置校验会报未知字段。- 项目 / 本地的
settings.json、settings.local.json与 hooks 只从<cwd>/.claude/读,不再向上找。 这是 Claude Code 的行为,但它意味着从子目录启动的会话拿不到仓库根的 settings 与 hooks。 dsh-cc-mcp的状态文件改为<该会话的启动工作区>/.dsh/cc-mcp-state.json。旧位置里的启停决定不会自动搬过来。- 配置项
globalSkillRank/globalSkillSource/skillSource/projectAgentRank/globalAgentRank/pluginAgentRank/pluginSkillRank删除。 - 权限规则里前导
/的路径锚点(Read(/secrets/**)这类)对项目 / 本地规则改为<cwd>。
一处「有意偏离」Claude Code
Claude Code 对 project 作用域的 MCP server 有批准闸,并且会交互式弹窗问你批不批。DSH 没有这个弹窗,照抄的结果是 .mcp.json 里声明的 server 永远不可用(面板列着、调用全被拒、无处说「可以」)。所以这里反过来:默认放行,只有 settings 里显式 disabledMcpjsonServers 才拦;enabledMcpjsonServers / enableAllProjectMcpServers 仍读取但不再决定任何事。
这是有意为之,不是 bug。
升级
dsh plugin --profile <name> add dsh-cc-ecosystem@0.4.0
务必带上版本号,并在装完后重启 DSH。升级就是在 GUI 里按新版本号再装一次 —— 不带版本号再点一次安装不会升级。
⚠️ pnpm 11 的minimumReleaseAge(默认 24 小时)会让发布不满一天的新版本装不上,而且退出码仍是 0。对存量用户是 T+24h 生效。
完整清单见 CHANGELOG。
v0.3.4 — 规则子目录、.claude 作为项目根标记、hook 的 shell 边界
补记的历史版本。 当前版本见 v0.4.0。
⚠️ 本版的「把.claude当作项目根标记」已被 v0.4.0 取代 —— 那一版整体改成 Claude Code 的逐组件发现,「项目根」这个概念连同projectRootMarkers配置项一起删除了。本版其余改动(规则子目录、Windows hook 三处修复)不受影响。
协同发版:dsh-cc-loader / cc-skills / cc-agents / cc-hooks / cc-mcp / dsh-cc-ecosystem 0.3.4,cc-permissions 0.4.4。
规则子目录现在会被读取(#11)
discoverRules 只看顶层 .md,而规则包常按目录组织(rules/common/、rules/typescript/)。这类项目拿到的是零条规则,注入函数等于没被调用,而且一声不响:没有报错、没有日志、加载照常成功。现在整棵树都走,跳过隐藏目录与 node_modules。
一处偏离报告方案的取法:name 用相对规则目录的路径,而不是裸文件名 —— 报告人自己举的例子就是 common/style.md 与 typescript/style.md 同名,裸文件名会让两条规则在排序键上相撞、并在注入文本里渲染成两个一模一样的 ## style.md 标题。顶层文件的相对路径就是它的文件名,所以扁平规则目录的输出与之前逐字节相同。
.claude 成为默认项目根标记(#11)
projectRootMarkers 默认只有 ['.git'],于是「有 .claude/、没有 git 仓库」的项目根本解析不出项目根。现在 cc-skills / cc-agents / cc-permissions / cc-hooks 的默认值都是 ['.git', '.claude']。
findProjectRoot 现在排除家目录
报告里没提,但不补上就是拿标记换来的新缺陷:一旦 .claude 成为标记,不在任何项目里的会话会一路走上去命中 ~/.claude —— 那是 Claude Code 的全局配置目录 —— 于是把全局技能、规则、设置再当项目级加载一遍。它现在接受 { homeDir } 并跳过该目录,五个包都把家目录传了进去。
Windows 上 command hook 的三处失效(#10)
- hook 的 shell 请求不带调用会话的沙箱策略 → 执行器拿自己的部署根去解沙箱,Windows 的 ACL 运行器直接拒绝这个 hook。现在按会话解析后随请求传递;新增
sandboxMode配置可覆盖。 ctx.shell在 Windows 上是 PowerShell,而 hook 命令是照 bash 写的 —— 首 token 是带引号的路径时("C:/Program Files/nodejs/node.exe" x.mjs),bash 读作调用,pwsh 读作字符串表达式并语法报错。ShellExecutor不暴露自己是哪一种,所以探一次并缓存;探针只在命令首字符是引号时才跑,其余命令零开销。新增shellDialect配置(auto/posix/pwsh)。CLAUDE_PLUGIN_ROOT只被替换进命令文本,没有导出为环境变量 —— 真正去读这个变量的插件 hook 拿到空值。现在与CLAUDE_PROJECT_DIR一起导出。
测试
全仓库 356 例。此前 test/project-root.test.mjs、packages/cc-loader/test/frontmatter-lenient.test.mjs、test/hooks-executors.test.mjs 没有被 npm test 登记,一直不在 CI 里跑 —— 已补上。
完整清单见 CHANGELOG。
v0.3.3 — 非严格 YAML frontmatter 不再被丢弃
协同发版:dsh-cc-loader / cc-skills / cc-agents / cc-hooks / cc-mcp / dsh-cc-ecosystem 0.3.3,
cc-permissions 0.4.3。代码改动只在 dsh-cc-loader,其余六包随家族同步升版。
- Claude Code 读得了、DSH 却整个丢弃的 frontmatter 现在能正常加载(#9) ——
parseFrontmatter一遇yaml.parse抛错就return undefined,调用方只报一句skipped: no frontmatter,于是文件在 DSH 里等于不存在。最常见的触发写法是未加引号的description里出现了第二个:—— 社区 agent 文件在<example>块里写Context: …/user: '…'就会这样。实测同样会丢整个文件的还有:值换行且续行有缩进、用 Tab 缩进、同一个 key 写两遍、值以*开头。
现在严格解析失败时退回逐行key: value读取,并把降级报进warnings(附上 YAML 的原始报错行),不再静默。技能、斜杠命令、子代理、插件内组件共用这一个解析器,四类一起受益。
两处实现取舍值得记住:值一律保持字符串,否则description: 2024会被转成数字、被stringField拒绝,等于拿「丢文件」换了「丢字段」;而 Claude Code 文档里本就是逗号分隔的五个字段(tools/disallowedTools/skills/allowed-tools/disallowed-tools)会按逗号切开,否则tools: Read, Grep, Glob会变成一个叫"Read, Grep, Glob"的假工具名 —— 文件救回来了,agent 却没有工具。 - 顺带修掉同一段告警管道里的两条既有缺陷 —— 都是在接这条新告警时撞出来的,不修的话新提示要么看不见、要么看两遍:
loadClaude把mergeAgentCatalog的返回值又 push 回它自己刚填充过的那个数组 → 每条 agent 告警打印两遍;collectClaudeDir忽略了调用方传进来的告警收集器、自己新建一个又没人合并 → skill 与 command 的告警被整个丢弃(包括既有的skipped: name not kebab-case之类)。
- 仍未覆盖:整块 frontmatter 里连一行
key: value都读不出来时,文件依旧被丢弃,提示依旧是no frontmatter。对这类文件该措辞是准的,但对「有 frontmatter、只是 YAML 不严格」的文件曾经是误导。
测试:packages/cc-loader/test/frontmatter-lenient.test.mjs(11 例,含经 discoverAgents 与 loadClaude 的端到端;覆盖报告里的原始形状、其余几种严格 YAML 失败、逗号列表拆分、字符串不被数字化,以及「真的读不出来」仍返回 undefined)。做了变异验证 —— 把兜底退回旧行为有 6 例转红、去掉逗号拆分有 2 例转红。全仓库 296 例。
v0.3.2 — 修复 0.2.0 上四处静默失效
协同发版:dsh-cc-loader / cc-skills / cc-agents / cc-hooks / cc-mcp / dsh-cc-ecosystem 0.3.2,
cc-permissions 0.4.2。
这四处缺陷在 0.2.0 上都是静默失败 —— 不抛错、日志里一个字都没有、插件照常加载。全部在真机 0.2.0 profile 上复现并验证过,不只是单元测试。
- 含方括号的权限规则让每一轮都失败(#8) —— glob 转换把任何
[...]原样当正则字符类,于是文件名叫[11-04-54]的规则会生成1-0这种反向区间,new RegExp抛Range out of order in character class;而规则在每个工具调用上都要编译,所以一条坏规则拖垮整个权限闸,连无关命令一起拦。
字符类本来就只是 path 方言的特性(文件头注释一直这么写):现在只在 path 模式里解析[...],命令与域名模式里[是普通字符;即便在 path 模式,不是合法正则的类(如[11-04-54])也降级成字面量而不是抛错。 agent/session-start在 0.2.0 被移除(#7) —— 0.2.0 把边界挪到agent/created并补上了source。两代的名字不是别名:0.1.5 的agent/created只有{agent},没有source,直接改名会把source悄悄丢掉(而 SessionStart hook 的载荷需要它)。
新增packages/cc-loader/src/session-start-compat.js统一拥有这个边界:双监听、只对带source的载荷动作,两代都恰好触发一次。它同时给出 0.2.0 所需的两个保证 —— 永不返回处理器的 promise、永不让抛错逃逸,因为 0.2.0 的agent/created是串行事件,监听器拒绝会否决会话发布。- 注入消息用了已退役的 V3 source(发版后真机验证才暴露)—— 0.2.0 的会话格式 v4 准入门校验每一条持久化消息的
source:kind必须是非空字符串且不等于'plugin',否则抛format v4 message requires a producer-owned source kind,整轮失败。
cc-hooks(3 处)与 cc-agents(1 处)一直在用 V3 的{kind:'plugin', plugin:…},于是每条注入路径都是地雷 —— SessionStart 上下文、hook 注入的上下文、prompt/agent hook 结果、以及 cc-agents 的目录提醒。
新增packages/cc-loader/src/message-source.js集中产出合规 source,并加了一条扫描全部包源码的漂移守卫。 - cc-hooks 在事件边界裸解引用
agent.session(发版后真机验证才暴露)—— 0.2.0 先 announce agent、后挂 session,所以agent.session此时可能还是undefined。SessionStart 监听器的第一行就抛,而兼容边界按设计吞掉它(监听器不能否决会话发布),于是处理器死在那一行、runPoint根本没机会跑。
表现为插件正常加载、其它 hook 点全正常,只有 SessionStart 静默全死 —— 真机会话里 SessionStart 的hook/invoked一条都没有。
发布流程:release.yml 末尾新增 npmmirror 同步步骤 —— 发布后逐个等待版本在官方 registry 可见,再显式触发镜像同步。v0.3.1 时镜像漏了 dsh-cc-loader(其余六个都依赖它),安装卡了四小时。
未包含:#2(Windows 上 Bash(...) 规则不覆盖 pwsh)、#9(非严格 YAML frontmatter 的 agent 被丢弃)、#10(Windows 上 command hook 不运行)、#11(rules 不递归 / .claude 不作项目根标记),均仍未修复。
v0.3.1 — 双宿主支持 + 全家桶 meta 包
协同发版:dsh-cc-loader / cc-skills / cc-agents / cc-hooks / cc-mcp 0.3.1,
cc-permissions 0.4.1,新增 dsh-cc-ecosystem 0.3.1。DSH 0.2.0 换掉了插件安装路径
并新增一道兼容闸,本次让全部 7 个包同时通过新旧两代宿主。
- 新增
dsh-cc-ecosystem(全家桶):一条命令装齐 loader + 五个适配器 ——
dsh plugin --profile <name> add dsh-cc-ecosystem(Web GUI 粘贴包名同理)。
为什么必须单独一个包:DSH 把dsh.profile.bundles与 profile 的直接依赖对账,
传递依赖不进bundles、其 patch 永不加载 —— 装全家桶时五个适配器都是它的传递依赖,
所以只有它自己的 patch 能挂载它们。因此它的cordis.patch.yml是五个适配器各自 patch 的
副本;新增test/umbrella.test.mjs(5)逐行比对两份配置,任何 config 漂移即失败
(已做变异验证:改动一个值即可让该断言失败)。本包不声明dsh-*peer —— 适配器的兼容性
由宿主顺着 patch 里插入的行逐个读出并校验,再加一道闸只会产生第二份可能互相打架的判定。 - 通过 0.2.0 的兼容闸:0.2.0 的安装路径新增
evaluatePluginCompatibility—— 对每个
@deepseek-ai/dsh/dsh-*开头的 peer,用semver.satisfies(runtime, range, { includePrerelease: true })对照运行时版本,不满足即在安装前拒绝。dsh-llm与
dsh-subprocess的 peer 范围由^0.1.5-rc.2放宽为^0.1.5-rc.2 || ^0.2.0-rc.2
(cc-skills / cc-agents / cc-hooks / cc-mcp)。这四个包此前在 0.2.0 上会报
Plugin … is incompatible with dsh 0.2.0-rc.2: peerDependencies {…}。
cc-permissions 与 cc-loader 不含dsh-*peer,不受这道闸影响。 dsh-cc-loader现在可作插件安装:0.2.0 的插件管理器拒绝任何未声明dsh.bundle的包
(… declares no dsh.bundle)。loader 是纯库、不贡献任何插件行,因此声明一个内容为[]
的空 patch:它通过宿主的每一道校验(dsh.bundle须是对象、patch须是非空字符串、解析后
的文件须在包目录内且存在、顶层须是 YAML 数组),并激活一个不插入任何行的空层。
dsh.bundle.patch允许是字符串或字符串数组,空数组合法。- 修复 cc-hooks 在 0.2.0 上 command hook 崩溃:宿主 shell 执行器接口由
run(spec)
变为(await execute(spec)).result()。两版dsh-hook-protocol的runHook除这一行外
逐字节相同,所以任何依赖版本范围都只能满足其中一代(钉 0.2.x 坏 0.1.x,钉 0.1.x 坏
0.2.x)。cc-hooks 改为自己持有这次调用,按execute/run特性探测
(src/shell-compat.js,与 loader 的session-compat.js同一模式);请求形状、超时、
stdin 封装与错误语义与原实现逐字保持一致,两代宿主行为相同。新增
test/hooks-shell-compat.test.mjs(8 用例)分别覆盖两代形状、execute直接返回结果、
两者皆缺与执行器抛错时的降级。
v0.3.0 — dsh-cc-mcp /mcp 管理面板(宿主 MCP 行 / 按工作区禁用 / 重连)
dsh-cc-mcp 的 /mcp 管理面板是本次发布的功能主体:Web GUI 内列出项目/插件 MCP 与宿主 dsh-mcp-client 行,给出状态、工具列表、Connect 重连、按工作区禁用启用与会话自检提示。
协同版本:dsh-cc-loader / dsh-cc-skills / dsh-cc-agents / dsh-cc-hooks / dsh-cc-mcp 0.3.0,dsh-cc-permissions 0.4.0。其余 5 包无代码改动,仅随家族升版并把 dsh-cc-loader 依赖范围同步为 ^0.3.0。最低 DSH 版本仍为 0.1.5-rc.2。
v0.3.0 — dsh-cc-mcp 管理面板(/mcp)
协同发版:dsh-cc-loader / cc-skills / cc-agents / cc-hooks / cc-mcp 0.3.0,
cc-permissions 0.4.0(其余 5 包无代码改动,仅随家族升版并同步 dsh-cc-loader ^0.3.0 依赖范围)。
最低 dsh 版本仍为 0.1.5-rc.2,宿主契约未变。
/mcp面板(Web GUI):dsh-cc-mcp现在为每个 MCP 服务器维护一行状态
(ready/error/disabled/skipped/checking)与工具清单。斜杠命令/mcp
打开面板:列表页给出服务器 / 类型 / 状态,失败的行有 Connect 按钮(点击重连,
仍失败则再弹一次自消失提示),已连接的行点 ✓ 可重新自检;点任意一行进入详情页,
显示Connected · N tools、配置来源、该服务器的工具列表,以及
Disable/Enable 按钮 —— 禁用后该服务器的工具从模型上下文消失,新会话同样继承;
再次点击即可恢复。- 宿主 MCP 行纳入面板:profile 里那些
@deepseek-ai/dsh-mcp-client行(如 github /
biorxiv / fetch / chrome-devtools-edge)也会列出,类型为host,状态与工具清单取自
宿主当前真实暴露的工具。对它们的操作按工作区生效、且不改写 profile 配置:- Disable = 用
tools.restrict({ deny })只在本工作区的各会话里隐藏该行的
mcp__<server>__*工具(宿主行本身不动,其他工作区不受影响),Enable 时调用
restrict返回的撤销器恢复; - Connect = 该行暴露不出工具时(它自己连接失败/仍在启动),由本插件用该行自己的
配置连上去,在该会话的 scope 内注册工具把能力补回来(状态标adopted),Disable
即撤销这份注册。 - 新配置
manageHostRows(默认true)可关掉这部分。
- Disable = 用
- 状态改为按工作区落盘:禁用/隐藏决定写在
<项目根>/.dsh/cc-mcp-state.json
(无项目根时回落到$DSH_HOME/cc-mcp-state.json,也可用statePath固定路径)。
因此「在这个工作区禁用 github」不会影响别的项目;.mcp.json与 Claude Code 设置依旧只读。 - 会话开始的自检:进入会话时后台逐个连接各 MCP(与对话并行),失败的服务器以
自动消失的提示框报出(每个失败只报一次,会话切换/重连不会重复弹)。 - 传输:面板走插件自己在宿主
webServer上注册的同源路由/cc-mcp/*(与官方插件市场
dshmarket 同一约定):POST 带x-cc-mcp头、校验Origin === Host、只收 JSON、4 KiB 上限。
不用ctx.connection.rpc—— 实测该服务对 profile 顶层插件不可见
(ctx.inject(['connection'])永不触发),那条路根本挂不上路由。 - 迟到接管(late wiring):
agent/created只覆盖插件激活后新建的会话,而宿主重启会在
用户层插件挂载前恢复上次会话,这类会话改为按需接管:面板/命令用宿主ctx.agents
注册表解析活 agent,/mcp命令直接接管调用者;接管时等待首轮自检完成,首次打开面板
看到的是已定型的行而不是转圈。 - 热挂载的两个坑已在代码里规避(见
src/index.js注释与 DSHCCECO-INSTALL-SKILL.md):- 非
insert的补丁条目里name只是断言,与目标行不同会整条跳过 —— 挂本地
checkout 必须「- id: <原行>+disabled: true」再- insert:一个新行; - DSH 热挂载只重新 import 入口 URL,相对导入的
manage.js/register.js会被
Node 的 ESM 缓存留住 → 新旧混用。现在相对导入继承入口的?v=N,三个模块永远同批加载。
- 非
/mcp宿主命令:/mcp <任意参数>与 CLI/headless 场景输出文本版状态报告
(/mcp裸调用在 GUI 里打开面板);POST /cc-mcp/diag返回路由注册状态、已接管会话
与当前可见的mcp__*工具,用于支持与测试。- 新增浏览器半边
client/index.js:手写 classic script(懒 CJS 包装,零require、
零构建步骤),通过package.json的dsh.client+exports["./client"]被宿主扫描并
按需加载。这是在 DSH 里为仓库外插件提供 Web UI 的受支持路径。 - 新配置:
enableManager(默认true,注册/mcp命令与面板路由)、
manageHostRows(默认true)、statePath(默认空 = 按工作区)。 - 修复:
watchProject: false时卸载 agent 会在detachWatcher上抛
Cannot read properties of null(旧代码只在undefined上做了保护)。 - 已知限制:插件热重载后,上一实例在会话 scope 内注册过的工具(例如项目
.mcp.json
里已连上的服务器)可能残留到该会话被回收为止——面板会把这类行标成skipped并列出可见
工具;宿主重启或开新会话即干净。这也是skipped的判据(该命名空间已被别的层注册)。 - 测试:
packages/cc-mcp/test/mcp-manager.test.mjs(29 例:投影/持久化/真实 stdio MCP
端到端、失败上报、Connect 重试、禁用启用、宿主行的列出/按工作区隐藏/按需接管、
宿主命令、卸载回收、迟到接管、路由安全边界)、
packages/cc-mcp/test/client-bundle.test.mjs(9 例打包与传输契约)、
packages/cc-mcp/test/client-panel.dom.test.mjs(6 例真实 DOM 面板交互);根npm test
已纳入(共 249 例)。
v0.2.1 — 版本策略声明(仅升级到 0.1.5 时需要,无代码改动)
⚠️ 只有把 DSH 升级到 0.1.5-rc.2 时才需要使用这一代版本
这是升级路径版本,不是功能版本。 升级前请先确认你要升的是哪一边:
| 你的 DSH | 该用哪一代 |
|---|---|
0.1.5-rc.2 及以上 |
✅ 用本代(v0.2.x / cc-permissions v0.3.x) |
0.1.0-rc.7 ~ 0.1.1-rc.2 |
⛔ 不要升级,留在 v0.1.x(cc-permissions v0.2.x) |
两代互斥:本代绑定 DSH 0.1.5 的宿主契约(snapshotEvents() / eventAt()、ToolCallId、^0.1.5-rc.2 的 peer 范围),
在旧宿主上无法工作。旧宿主不满足 peer 范围,包管理器会报 unmet peer dependency,而不是等到 import 时才崩。
本补丁做了什么
v0.2.0 发布时,npm 包页面渲染的 README 里没有上面这条声明(npm 的版本内容不可覆盖),
于是 latest 指向了一个"装到旧宿主上会坏"的版本,却没有任何提示。
v0.2.1 把声明写入根 README、6 个包 README 与 CHANGELOG,使其在 npm 包页面上可见。
代码与依赖范围与 v0.2.0 完全一致,没有任何行为变化。已装 v0.2.0 的用户无需升级。
dsh-cc-loader 依赖范围仍为 ^0.2.0 —— 0.2.1 是文档补丁、互相兼容,没有理由强制升级 loader。
版本
| 包 | 版本 |
|---|---|
dsh-cc-loader |
0.2.1 |
dsh-cc-skills |
0.2.1 |
dsh-cc-agents |
0.2.1 |
dsh-cc-hooks |
0.2.1 |
dsh-cc-mcp |
0.2.1 |
dsh-cc-permissions |
0.3.1 |
安装
dsh plugin --profile <name> add dsh-cc-loader dsh-cc-skills dsh-cc-permissions dsh-cc-agents dsh-cc-hooks dsh-cc-mcpdsh-cc-loader 会随依赖自动装上;单独安装时需先装它。
v0.2.0 — DSH 0.1.5-rc.2 兼容(仅升级到 0.1.5 时需要)
⚠️ 升级前提:宿主 DSH 必须为 0.1.5-rc.2 或更高
本版本不兼容更早的 DSH。 旧宿主(0.1.0-rc.7 ~ 0.1.1-rc.2)请留在 v0.1.x
(cc-permissions v0.2.x)—— 本版本绑定 0.1.5 的宿主契约,装到旧宿主上无法工作。
两代版本号互斥。
修复的四处宿主契约破坏
DSH 从 0.1.1-rc.2 升到 0.1.5-rc.2 后,cc-* 插件启动失败、运行时报错。
四处破坏都是逐个符号与 0.1.5-rc.2 源码比对得出的,不是凭记忆改的:
1. Session.events 已被移除(自 0.1.2-alpha.4)
在 0.1.5 上该属性是 undefined,于是:
[...session.events]→TypeError: agent.session.events is not iterablesession.events[seq]→TypeError: Cannot read properties of undefined (reading 'NN')
三个包都中招(cc-skills 的 rules 去重、cc-hooks 的 lastTurn / lastAssistantMessage、
cc-permissions 的 approval 自动应答)。
修复:在 dsh-cc-loader 新增集中兼容边界,所有日志读取统一走它 ——
sessionEvents()(优先 snapshotEvents())、sessionEventAt()(优先 eventAt(seq))、
sessionLastEvent();仅在 0.1.2-alpha.4 之前的旧宿主才回退到 session.events。
2. @deepseek-ai/dsh-llm 的 CallId 改名为 ToolCallId
0.1.5 已不导出 CallId。ESM 具名导入不存在的符号是模块求值期 SyntaxError,
所以 dsh-cc-hooks 根本 import 不进来 —— 这是最致命的一处。已改为 ToolCallId(品牌类型构造器,语义等价)。
3. SessionPersistence 公开契约没有 locate
0.1.5 的抽象服务只声明 create / open / flush / stat / list;
JSONL 后端那个 locate 是 TypeScript private(运行时存在,但不在契约内)。
旧写法 ctx.get('sessionPersistence')?.locate(h)?.path 的 ?. 只保护服务、不保护方法;
只要服务被注册(正常情况)就会去读并调用该方法,因此任何没有 locate 的 persistence provider
都会同步抛 TypeError: …locate is not a function —— 而该表达式在每个 hook payload 构造器里
急切求值,SessionStart / PreToolUse / Stop / SessionEnd 会全部失效。
修复:抽出 transcriptPath(ctx, session),typeof 判定 + try/catch,不可用时退化为 ''
(与官方桥 hooks-claude-code 的硬编码一致),可用时仍保留该字段。
4. @deepseek-ai/dsh-host-apiproxy 包已移除
排查确认本仓库从未引用,无需改动。
另外修正了依赖声明(这是"能跑但跑在旧 SDK 上"的根因)
DSH profile 使用 nodeLinker: hoisted + autoInstallPeers: false,其设计意图是让缺失的 peer
回落到宿主安装(profiles/node_modules 扁平 fallback 层),全进程共享宿主的单份 SDK 实例。
此前把 dsh-llm / dsh-subprocess / schemastery 声明为正式 dependencies,导致 profile 里
被装进一份旧 SDK 0.1.0-rc.8,插件实际跑在旧 SDK 上;且进程内存在两份 dsh-llm
(品牌类型 / 服务身份判断可能静默失效 —— 当时能跑纯属解析巧合,pnpm 一 prune 就会崩)。
- 宿主提供的 SDK → peerDependencies(
^0.1.5-rc.2/^3.18.1),不带自己副本 @deepseek-ai/dsh-hook-protocol→ 保留为正式 dependencies。该包零运行时依赖
(只有import type,无跨模块instanceof),私有副本完全惰性;而缺失它会让宿主启动即崩
(ESM 具名导入失败没有优雅降级),自带副本是更安全的一侧
实测:在干净 profile 中安装后,@deepseek-ai/dsh-llm 与 schemastery 均解析到宿主的
0.1.5-rc.2,profile 内 @deepseek-ai/ 只剩 dsh-hook-protocol 一份。
验证
- 单元测试 210 用例全绿(新增 34)。两种 Session 形状(0.1.5 的
snapshotEvents()/eventAt()
与旧的events数组)均有回归覆盖,并做过变异验证:把兼容层退回"只读session.events"后,
5 个 0.1.5 形状用例全部失败、旧形状用例仍通过 - 端到端:干净 profile 从 npm 装齐 6 包 → 启动零错误 → 真实对话 + 工具调用
(tool/call→tool/result,isError:false),日志零错误签名 - cc-skills rules 只注入一次(session 日志中
source.kind==='cc-skills'的user/message计数 = 1) - 真实 hook payload 捕获(证明修复在线上生效):
{
"session_id": "session-…",
"transcript_path": "C:\\Users\\…\\session.v3.jsonl.zstd",
"cwd": "D:\\deepseek-harness",
"hook_event_name": "Stop",
"stop_hook_active": false,
"last_assistant_message": "DONE"
}last_assistant_message 必须走 Session.events 的替代读取路径才能得到 —— 这正是原来崩溃的地方。
版本与发布顺序
| 包 | 版本 |
|---|---|
dsh-cc-loader |
0.2.0 |
dsh-cc-skills |
0.2.0 |
dsh-cc-agents |
0.2.0 |
dsh-cc-hooks |
0.2.0 |
dsh-cc-mcp |
0.2.0 |
dsh-cc-permissions |
0.3.0 |
dsh-cc-loader 必须先发布,其余包依赖 ^0.2.0。
v0.1.2
dsh-cc-ecosystem v0.1.2
Coordinated 6-package npm release.
Highlights
dsh-cc-hooks 0.1.1 -> 0.1.2 — prompt-type Stop hooks upgraded to carry the calling agent's full session context, aligning with CC official prompt-hook semantics:
runPromptHooknow sendssession.deriveMessages()as the message prefix (same projection the agent sends → provider prompt-cache prefix intact), with the hook prompt appended as the final user message. Stop-timing hooks can now see the turn's last assistant message (e.g. "edited README, did not sync hot.md") and correctly return{"ok": false}to block.substituteArgumentsappends the hook input JSON when the template has no$ARGUMENTSplaceholder (CC: "if$ARGUMENTSis not present, input JSON is appended to the prompt").stopPayload/ SubagentStop payload carrylast_assistant_message(CC Stop input field).- The hook LLM prompt gains a JSON-only output contract.
Packages published (dependency order)
| package | version |
|---|---|
| dsh-cc-loader | 0.1.2 |
| dsh-cc-skills | 0.1.2 |
| dsh-cc-permissions | 0.2.2 |
| dsh-cc-agents | 0.1.2 |
| dsh-cc-hooks | 0.1.2 |
| dsh-cc-mcp | 0.1.2 |
Tests
- hooks-executors 13/13 (history-preserved + JSON-contract case)
- hooks-stop-payload 2/2
- full suite 170/170, syntax 24/24