Bundle
dsh-hooks-claude-code-per-workspace
Per-workspace Claude Code command hooks for DeepSeek Harness: each session discovers .claude/settings.json from its own cwd
- Source
- chendefine
- License
- MIT
- Updated
- Updated 2 days ago
Readme
# dsh-hooks-claude-code-per-workspace
[](https://www.npmjs.com/package/dsh-hooks-claude-code-per-workspace)
[](./LICENSE)
**English** | [中文](#中文文档)
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) plugin that runs **each workspace's own Claude Code command hooks**.
The official `@deepseek-ai/dsh-hooks-claude-code` bridge reads **one** process-level `configPath` once at load (`TODO(per-session-hook-config)` upstream). This plugin closes that gap for the multi-workspace web surface: at **every hook interception point** it resolves the current session's own cwd, loads that workspace's `.claude/settings.json` (plus `.claude/settings.local.json`, concatenated in order), cached by `mtime:size` — **edit the file and the next hook point picks it up, no reload or restart**. When a single `dsh web` instance has several workspaces open, each workspace's project-level hooks stay independent.
Event → extension-point mapping, stdin payloads, and decision semantics are a faithful port of the official bridge; execution, decoding, and merging come from `@deepseek-ai/dsh-hook-protocol`.
## Install
```sh
# from the registry (prebuilt, no build permission needed)
dsh plugin --profile web add dsh-hooks-claude-code-per-workspace
# or equivalently
dshpm install dsh-hooks-claude-code-per-workspace --profile web
# from GitHub (sources; needs the pnpm allowBuilds allowance — see the dsh publish docs)
dsh plugin --profile web add github:chendefine/dsh-hooks-claude-code-per-workspace
```
You can also install it from the Web GUI's plugin manager. **Restart the profile afterwards** (bundle membership changes load at boot).
See the [中文文档](#中文文档) below for configuration, the workspace hook format, the trust model, and a comparison with the official bridge.
---
# 中文文档
每个工作区(session)运行自己的 Claude Code command hooks 的 DeepSeek Harness 插件。
官方 `@deepseek-ai/dsh-hooks-claude-code` bridge 在进程加载时读取**一个** `configPath`(进程级、读一次,`TODO(per-session-hook-config)`)。本插件补上这一层:在**每个 hook 拦截点**,取当前 session 自己的 cwd,加载该目录下的 `.claude/settings.json`(以及 `.claude/settings.local.json`,两组按序合并),按 `mtime:size` 缓存 —— **编辑文件后下一个 hook 点即生效,无需重载或重启**。
单个 `dsh web` 实例打开多个工作区时,每个工作区各自的项目级 hooks 互不干扰。事件 → 扩展点映射、stdin payload、决策语义均为官方 bridge 的忠实移植;执行/解码/合并来自 `@deepseek-ai/dsh-hook-protocol`。
## 安装
```sh
# 从 npm 安装(预构建产物,无需构建许可)
dsh plugin --profile web add dsh-hooks-claude-code-per-workspace
# 或等价的 dshpm 用法
dshpm install dsh-hooks-claude-code-per-workspace --profile web
# 从 GitHub 安装(源码;需在 profile 的 pnpm-workspace.yaml 里 allowBuilds,见 dsh 发布文档)
dsh plugin --profile web add github:chendefine/dsh-hooks-claude-code-per-workspace
```
也可以在 Web GUI 的插件管理器中直接安装。安装后**重启该 profile**(bundle 成员变化需要重启加载)。
## 配置(全部可选)
profile 的 `cordis.patch.yml` 中按 id patch 该行(插件不导出 schema,`config` 原样传入,缺省键在插件内兜底):
```yaml
- id: hooks-claude-code-per-workspace
config:
settingsPaths: ['.claude/settings.json', '.claude/settings.local.json']
defaultTimeoutMs: 600000 # hook 未写 timeout 时的默认超时(ms)
stderrSummaryMaxChars: 500 # hook/result 事件里 stderr 摘要的长度上限
allowDirs: [] # 目录前缀白名单;空 = 所有工作区都允许
```
- `settingsPaths`:相对每个工作区 cwd 解析(绝对路径原样使用);多个文件的 hook 组按序拼接 —— 与 Claude Code 项目级两份文件的行为对齐。也可指向 `.claude/hooks.json`(裸事件映射格式同样接受)。
- `allowDirs`:非空时,只有 cwd 等于或位于这些目录之下的 session 才加载 hooks;其余静默跳过。
## 工作区里怎么写 hooks
与 Claude Code 完全同格式(`.claude/settings.json` 的 `hooks` 键,其余键被忽略):
```json
{
"hooks": {
"PreToolUse": [
{ "matcher": "bash", "hooks": [{ "type": "command", "command": "./scripts/guard.sh" }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "notify-done.sh", "timeout": 10 }] }
]
}
}
```
- hook 命令在**该工作区目录**里执行;`CLAUDE_PROJECT_DIR` 指向它;命令串里的 `${CLAUDE_PROJECT_DIR}` 会被替换。
- 支持 7 个事件:`SessionStart`、`UserPromptSubmit`、`PreToolUse`、`PostToolUse`、`Stop`、`SubagentStart`、`SubagentStop`。其余 CC 事件的配置被忽略;仅 `type: "command"` 执行(`http`/`mcp_tool`/`prompt`/`agent` 跳过并告警)。
- 输出契约与 CC 一致:exit 2 = 阻断(stderr 为原因);exit 0 + stdout JSON 的 `hookSpecificOutput.permissionDecision`(`deny`/`ask`)与 `additionalContext` 生效。
- 每次 hook 运行写入 `hook/invoked` / `hook/result` 会话事件(dialect 标记 `claude-code`),可在会话日志中审计。
### 事件 → 扩展点映射
| CC hook | Harness 扩展点 | 语义 |
|---|---|---|
| `SessionStart` | `agent/session-start` | `additionalContext` → 注入新会话(不可阻断) |
| `UserPromptSubmit` | `agent/pre-step` | `deny` → 拒绝本轮;`additionalContext` → 追加到下游 enter 决策 |
| `PreToolUse` | `tools/pre-execute` | `deny` → 拒绝工具;`ask` → 转人工确认 |
| `PostToolUse` | `tools/post-execute` | `deny` → block 并回传原因;`additionalContext` → 附加到下游决策 |
| `Stop` | `agent/turn-stopping` | 阻断 → `steer()` 原因,强制再走一步 |
| `SubagentStart` | `subagent/start` | `additionalContext` → 注入子代理 |
| `SubagentStop` | `subagent/end` | 仅观察 |
## 容错
- 工作区没有 settings 文件 → 零 hook(正常,静默)。
- JSON 损坏 / matcher 非法 regex → 该文件告警一次并被忽略,**绝不拖垮会话**。
- hook 执行失败(无法 spawn 等)→ 非阻断错误,轮次继续。
## 与官方 bridge 的差异
| | 官方 `dsh-hooks-claude-code` | 本插件 |
|---|---|---|
| 配置来源 | 单个 `configPath`,加载时读一次,进程级 | 每个工作区 cwd 下的 settings 文件,逐点解析 |
| 生效范围 | 整个进程所有 session | 仅该工作区的 session |
| 配置热更新 | 无 | 改文件即生效(mtime+size 缓存) |
| `configPath`/`pluginRoot`/`projectDir` 配置 | 有 | 无(`pluginRoot` 不替换;`projectDir` 恒为工作区 cwd) |
| 限制(事件子集、字段缺口、Stop 无限续步、`updatedInput` 不生效) | — | 完全相同,见官方 bridge README |
## 信任模型(必读)
打开一个工作区 = 允许该工作区 `settings.json` 里声明的任意 shell 命令在 hook 点执行。默认放行所有工作区,每个工作区首次激活会打一条 info 日志(含 hook 数量)。如需收紧,用 `allowDirs` 白名单。
## 开发
```sh
node test/smoke.mjs # 35 项断言的离线冒烟测试(伪造 ctx/shell/session)
```
- `vendor/` 是 `@deepseek-ai/dsh-hook-protocol`(MIT)逐文件移植:发布版 npm 包不在 harness 安装闭包内,无法作为依赖装进 profile,故内联保持自包含。唯一外部运行时依赖是 `@deepseek-ai/dsh-llm`(peerDependency,由 `$DSH_HOME/profiles/node_modules` 平铺兜底解析);本地跑测试可 `ln -s <dsh 源码>/packages/llm/llm node_modules/@deepseek-ai/dsh-llm`。
- 真实环境 E2E(已在开发中验证):一次性 `DSH_HOME` + headless profile,工作区放 exit-2 的 `UserPromptSubmit` hook → 会话日志出现 `hook/invoked`/`hook/result`(decision=block, exitCode=2),turn 以 `blocked` 结束;无配置目录的对照会话零 hook 触发。
## 卸载
```sh
dshpm remove dsh-hooks-claude-code-per-workspace --profile web
# 或
dsh plugin --profile web remove dsh-hooks-claude-code-per-workspace
```
## 许可
MIT — 见 [LICENSE](./LICENSE)。`vendor/` 内联自 `@deepseek-ai/dsh-hook-protocol`(MIT,DeepSeek Harness),文件头保留上游署名。
Install
dsh plugin --profile web add github:chendefine/dsh-hooks-claude-code-per-workspace#9b686bb86c31a279c651fc5dba046e760553837c
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-hooks-claude-code-per-workspace from the hub