@huiliyi37/dsh-hooks-claude
v0.3.0
Published
Bridge plugin: run a Claude Code hooks.json / settings hook config on the Tianshu Harness interception seams
Downloads
604
Readme
@huiliyi37/dsh-hooks-claude
English | 中文
一个 Cordis 插件,在 harness 的规范拦截点上运行用户现有 Claude Code hook 配置(hooks.json 或 settings 文件的 hooks key)中受支持的 command hook 子集。它是 hooks 子系统的 CC 方言部分,负责桥接中 CC 格式的逐事件 stdin payload、CC 的 env 和 ${CLAUDE_PLUGIN_ROOT}/${CLAUDE_PROJECT_DIR} 替换,以及将 hook 的中性结果映射为 harness 的类型化 Decision。方言无关原语(matcher、退出码/stdout codec、ctx.bash 执行、最严格合并、hook/* 事件)来自 @huiliyi37/dsh-hook-protocol。
原生 Cordis 插件可以完成此桥接的所有工作,功能更强,且具有类型化返回,没有序列化边界。该桥接只是已映射 CC command hook 子集的兼容路径;所有定制行为都应当使用相同扩展点上的原生插件(见 拦截扩展点 Agent Note)。
配置
import type { Config } from '@huiliyi37/dsh-hooks-claude'
const config: Config = {
configPath: '/path/to/hooks.json', // required: a hooks.json or a settings file with a `hooks` key
pluginRoot: '/path/to/plugin', // optional: replaces ${CLAUDE_PLUGIN_ROOT} in command strings
projectDir: '/path/to/project', // optional: replaces ${CLAUDE_PROJECT_DIR} AND sets the hook env var; defaults to the session cwd when omitted
defaultTimeoutMs: 600_000, // optional: per-hook timeout when a hook sets none (CC default)
stderrSummaryMaxChars: 500, // optional: char cap on the hook/result event's persisted stderr summary
}在 cordis.yml 中:
- dsh-hooks-claude:
configPath: ./.claude/hooks.json
pluginRoot: ./.claude/plugins/my-plugin
projectDir: .配置只在加载时解析一次。configPath 是进程级配置:相对路径在加载时根据进程启动 cwd 解析,因此一份配置应用于整个进程。尚未进行每会话(session/new.cwd)配置发现(TODO(per-session-hook-config))。读取/解析失败会被隔离处理,其中包括实际消费 matcher 的事件所带的无效 matcher 正则(会报告其 pattern 与事件):桥接记录警告且不注册任何内容,而不是使启动崩溃(路径拼写错误不应使 agent(智能体)停止)。只运行 shell 形式 type: 'command' hook;http/mcp_tool/prompt/agent hook 会被解析并跳过,同时记录警告。没有每 hook timeout 的 hook 会使用协议参考默认值 DEFAULT_HOOK_TIMEOUT_MS(来自 dsh-hook-protocol,10 分钟,即 CC 默认值)。
hook 本身会在 agent 的会话工作区中运行:对 agent scope 点,桥接会将会话 cwd(session/new.cwd)作为 hook 进程工作目录,因此 hook 的 pwd/相对路径/marker 作用于用户项目树,而非服务器启动目录。
Hook 点 → 类型化 Decision
| CC hook | Harness 点 | 映射 |
|---|---|---|
| SessionStart | agent/session-start(emit) | additionalContext → agent.inject() 到新会话(无法阻塞) |
| UserPromptSubmit | agent/pre-step(waterfall,瀑布式事件) | deny → PreStepDecision.reject;仅 additionalContext → 通过 next() 委托,再向下游 enter 决策追加一条单独标记来源的消息(后续外层 listener 仍可 reject/改写) |
| PreToolUse | tools/pre-execute(waterfall) | deny → PreToolDecision.deny;ask → PreToolDecision.ask |
| PostToolUse | tools/post-execute(waterfall) | deny → 带反馈的 block;仅 additionalContext → 通过 next() 委托,再将一个单独标记源的上下文前置到下游决策;Code Mode 将子调用上下文延迟到外层 run_code 结果 |
| Stop | agent/turn-stopping(serial) | 阻塞 Stop hook 通过 steer() 送入其原因,强制再执行一步 |
| SubagentStart | subagent/start(emit) | additionalContext → agent.inject() 到仍在运行的同进程 child;远程 child 没有本地注入目标 |
| SubagentStop | subagent/end(emit) | 只观测 |
三个 emit 点都以分离方式运行:没有扩展点会等待 SessionStart/SubagentStart/SubagentStop hook。每条运行链都会被跟踪;对桥接执行 dispose(资源释放)时,会中止仍在运行的 hook 进程,并在 dispose 完成前排空 continuation(createDetachedRuns,位于 dsh-hook-protocol)。
matcher subject 是工具名称(PreToolUse/PostToolUse)、会话源(SessionStart),或常量 agent_type,其值为 general-purpose(SubagentStart/SubagentStop)。harness subagent seam 不携带每 kind label,因此桥接报告 Claude Code 自身 Task 工具默认值;默认/*/空 agent_type matcher 会触发,特定 kind matcher 不会触发。UserPromptSubmit/Stop 忽略 matcher。一个点上文件配置的多个 hook 会按配置顺序串行运行,并按最严格方式折叠(deny > ask > allow,见 dsh-hook-protocol)。串行使每个 hook 的 hook/invoked/hook/result 对在日志中相邻,决策折叠与顺序无关(见 Agent Note 的「run serially, not concurrently」说明)。
每个 agent scope stdin payload 都携带 session_id 与字符串形式的 transcript_path。可用时,桥接通过 ctx.sessionPersistence.locate(session.header) 解析后者,否则发送 ''。查找不会创建或 flush 产物,因此第一个轮次结束检查点之前路径可能不存在,也可能省略当前开启轮次。
上下文源
注入上下文携带显式 { kind: 'plugin', plugin: 'hooks-claude' } 来源,因此持久消息绝不会被误认为用户提示词。
模型体验
Hook 提供的上下文
模型看到的内容
SessionStart、已接受提示词、工具后和实时同进程 subagent-start hook 可以添加带源归因的上下文消息;阻塞 Stop hook 将原因添加为下一步 steering(中途引导)。远程 child 注入没有本地目标。
Token 影响
hook 不返回上下文时没有成本。Hook 文本取决于数据,会被记录,并在后续会话请求中重发,直到压缩(compaction)。
KV Cache 影响
仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
已阻塞提示词或工具结果
模型看到的内容
提供方提供的原因逐字传递。缺失原因时,已阻塞提示词精确使用 blocked by UserPromptSubmit hook,已拒绝工具变为 Error: blocked by PreToolUse hook,已阻塞工具后反馈精确为 blocked by PostToolUse hook,阻塞 stop 则精确添加 steering continue: blocked by Stop hook。systemMessage 随持久 hook/result 事件落账,由客户端透出(TUI 渲染一条暗色 [hook] … scrollback 行);loop 派发的调用上 updatedInput 已生效。两者对模型均不可见。
Token 影响
阻塞提示词会移除该提示词的请求 token;拒绝或反馈会添加保留的回退或提供方文本;强制 continuation 需要另一个完整请求。
KV Cache 影响
已阻塞提示词不发送请求,不会导致失效。拒绝、反馈与强制 continuation 上下文会追加在可复用前缀之后,不改写前缀。
已知限制与暂缓事项
- 不支持的 hook 事件(Claude Code 当前 30 项中的 23 项):
Setup、InstructionsLoaded、UserPromptExpansion、MessageDisplay、PermissionRequest、PostToolUseFailure、PostToolBatch、PermissionDenied、Notification、TaskCreated、TaskCompleted、StopFailure、TeammateIdle、ConfigChange、CwdChanged、FileChanged、WorktreeCreate、WorktreeRemove、PreCompact、PostCompact、SessionEnd、Elicitation和ElicitationResult。这些事件的配置会在配置组解析前被忽略,因此不支持的事件既不会使配置失效,也不会注册 hook。比较基线是 Claude Code 官方 hook 事件参考。 SessionStart只支持部分功能: 会消费 JSONadditionalContext,但不支持纯 stdout 上下文、initialUserMessage、sessionTitle、watchPaths、reloadSkills与CLAUDE_ENV_FILE。hook 脱离运行,因此上下文可能错过第一个请求(TODO(session-start-gating)),payload 会省略model、agent_type和session_title等当前可选字段。UserPromptSubmit只支持部分功能: 支持阻塞与 JSONadditionalContext,但不支持纯 stdout 上下文、sessionTitle和suppressOriginalPrompt。除非被覆盖,否则桥接还会使用自身 600 秒默认值,而非 Claude Code 的事件特定 30 秒 command 超时。PreToolUse只支持部分功能:deny与ask决策可用;allow不会预审批,不支持defer,additionalContext会被忽略,updatedInput对 loop 派发的调用生效(历史/审计/展示/执行同带改写值;见 pre-tool-input-rewrite Agent Note),绕过 loop 相位的调用警告并保留原入参。PostToolUse只支持部分功能: 支持阻塞反馈与 JSONadditionalContext,但不支持updatedToolOutput和updatedMCPToolOutput,tool_response会展平为文本。SubagentStart与SubagentStop只支持部分功能: 两者均报告常量agent_type,其值为general-purpose,并在 Claude Code 报告父会话的位置使用 child 会话 id。Start 上下文是尽力而为,且只能到达仍在运行的同进程 child;stop 只观测,无法阻塞 subagent 或向其提供上下文。Start 省略transcript_path;stop 还省略agent_transcript_path、last_assistant_message、background_tasks和session_crons,并始终报告stop_hook_active: false。Stop只支持部分功能: 阻塞会强制另一个模型轮次,但stop_hook_active始终为false,会省略last_assistant_message、background_tasks和session_crons,且未实现连续阻塞上限(TODO(stop-loop-guard))。因此,无条件阻塞 hook 会在每个步骤中强制 continuation,除非它自我限制。- 通用 payload 与输出字段只支持部分功能: 已映射事件会省略 Claude Code 原本会提供的
prompt_id、transcript_path、permission_mode和effort。systemMessage记录于hook/result事件并由客户端透出(对模型绝不可见);{"continue": false}会被记录但不会停止运行;不会应用suppressOutput、stopReason和terminalSequence(TODO(hook-continue-false))。 - Handler 与配置只支持部分功能: 只运行 shell 形式 command handler。会跳过
http、mcp_tool、prompt和agenthandler;不遵循args、async、asyncRewake、shell、if、once和statusMessage等 command handler 选项。匹配 handler 串行运行且不去重,而 Claude Code 会并行运行并对相同 handler 去重。一个进程级configPath会在加载时解析一次;尚未实现 Claude Code 的分层项目、用户、插件与策略发现和实时重新加载(TODO(per-session-hook-config))。
