dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH 前置/基座适配插件:把 Claude Code / Codex / WorkBuddy / OpenCode / GitHub Copilot 的 hooks 与 rules,通过显式声明适配到 DSH seam。5 家 vendor、85 个事件,每条适配逐项列出损失,fail-closed。
让 Claude Code / Codex / WorkBuddy / OpenCode / Copilot 的 hooks 与 rules,在 DSH 里真正跑起来。
一个 DSH 前置适配插件:显式声明 → 能力分级 → 路由到真实 seam。
不猜、不假装修好了、不把别家运行时塞进 Host。
🔌 我要用它 — 别的插件如何调用本适配器(消费者指南)
English · 简体中文
DSH(DeepSeek Harness)是新的 agent 运行时。而你已经在 Claude Code、Codex、WorkBuddy、OpenCode、GitHub Copilot 里积累了成套的 hooks 与 rules —— 那些"写文件前先拦一下""工具调用后补一段上下文""会话结束时跑个脚本"的东西。
把它们搬到 DSH 最省事的做法,是照着名字硬接一遍。那会静默地坏掉:
Claude 的
updatedInput被解析了但不生效; Codex 的permissionDecision: "ask"没有审批路径,退化成放行; Copilot 把 deny 写在顶层,而 DSH 的 hook 解析器只读hookSpecificOutput—— 直接复用的话,每一个 deny 都会被当成"没有决定"而放行; OpenCode 的插件是 TypeScript 回调,"名字"能对上,"回调体"根本过不来。
这个插件存在的意义,是让每一次适配的"损失"都变成可读的输出,而不是一个看起来能跑的假象。
它只做三件事:
| 声明 | 预置作者显式写出 vendor 与 event。从不嗅探文件名、从不猜归属。 |
| 分级 | 每个资产返回冻结的四级结果,损失逐条列出。 |
| 路由 | 有官方桥就复用官方桥,有可拦截 seam 就真翻译,只能观察就明说只能观察,做不到就拒绝。 |
@deepseek-ai/dsh-hooks-claude-code / -codex,本插件只负责"把声明变成桥的配置"和"没有桥的事件怎么办"。http / mcp_tool / prompt / agent handler —— 一律 UNSUPPORTED。别家插件运行时不嵌进 DSH Host。每个注册都返回一个冻结的能力结果。没有 supported: true 这种把风险抹平的字段。
| 分级 | 含义 | 本版本情况 |
|---|---|---|
DIRECT | DSH 存在 1:1 seam,语义原样保留。 | 零。见下。 |
ADAPTED | DSH 有 seam,但存在语义损失;degradations[] 逐条列明。 | 全部 112 个事件 |
CARRY | 无通用映射,原文逐字节携带、会话开始注入;永不变成可执行策略。 | 全部 5 家 rules |
UNSUPPORTED | 无忠实映射;拒绝并给出 reason。 | 程序性边界(见下) |
DIRECT 都没有两个官方桥本身就是"尽力而为"的方言适配。宣称逐字节忠实,就是虚假能力。
所以本插件任何一行都不标 DIRECT —— 全部 112 行都是 ADAPTED,每一行都必须写出自己的损失。这不是悲观,这是诚实:test/matrix-readme.test.js 会断言 DIRECT.length === 0,并断言每一行 ADAPTED 都至少带一条 degradations。
全覆盖不等于等价。 112 个事件全部可执行(0 拒绝),但这是"每行都写明损失"的全覆盖,不是"全都一模一样"。其中 10 个事件在自己的
degradations里明确写着Approximate(ClaudeMessageDisplay、StopFailure;CodexInterrupt;WorkBuddyElicitation、ElicitationResult;OpenCodeinstallation.updated、server.connected、session.deleted、session.updated、tui.toast.show),其余 102 个为真实/精确映射。test/doc-counts.test.js会把这组数字与代码逐项比对,文档不能比代码多宣称一分能力。
| 不变量 | 由谁保证 |
|---|---|
ADAPTED 必须至少列出一条 degradations,否则构造即抛错 | capabilityResult() + test/capability.test.js |
UNSUPPORTED 必须带 reason,否则构造即抛错 | capabilityResult() + test/capability.test.js |
不认识的 vendor / event 抛错(UNKNOWN_VENDOR / UNKNOWN_EVENT),不静默 fallback | test/service.test.js |
UNSUPPORTED 的资产永不挂载:不写配置、不挂桥、不进 status() 的可执行列表 | test/service.test.js + verify:live |
那么在全部可执行的今天,UNSUPPORTED 还剩哪些真实触发路径?
ctx.shell —— 组合里没有 shell executor,命令 hook 跑不了;rules 仍然可用(只读文件、只注入)。dsh-hooks-* 从 profile 解析不到时拒绝,不退回自研实现。http / mcp_tool / prompt / agent 抛 INVALID_HANDLER。五家互相独立。每家是一个纯数据模块(src/vendors/<vendor>.js):无 I/O、不 import DSH,所以整张矩阵脱离 Harness 也能单测。
| Vendor | 别名 | 官方桥 | 事件 | 官方桥 | 原生翻译 | 只观察 | 自建桥 | 近似 | rules |
|---|---|---|---|---|---|---|---|---|---|
claude | claude-code, cc | dsh-hooks-claude-code | 33 | 7 | 24 | 2 | 0 | 2 | CARRY |
codex | openai-codex | dsh-hooks-codex | 12 | 5 | 4 | 3 | 0 | 1 | CARRY |
workbuddy | codebuddy | dsh-hooks-claude-code | 24 | 5 | 19 | 0 | 0 | 2 | CARRY |
opencode | — | 无 | 29 | 0 | 29 | 0 | 0 | 5 | CARRY |
copilot | github-copilot | 无 | 14 | 0 | 4 | 0 | 10 | 0 | CARRY |
| 合计 | 112 | 17 | 80 | 5 | 10 | 10 |
"近似"列统计
degradations[]中自述Approximate的事件数。它与其他列并列展示、而不是藏进正文:它不为零,就不许写成零。
三个容易被误读的点:
workbuddy:WorkBuddy 桌面端没有公开 hook API。本插件按 CodeBuddy 引擎(腾讯文档为 Claude-Code 兼容)处理,并把"桌面端无 API""CODEBUDDY_PROJECT_DIR 不导出""modifiedInput 不被读取"写进每一条 degradations,由测试断言。opencode:29 个回调名全部映射到 DSH seam,但 TS 回调"体"不携带 —— 必须改写为命令。这是 out of scope,不是"忘做了"。copilot:DSH 没有官方 Copilot 桥,所以其 14 个事件里 10 个走本插件自建桥(builtin-bridge),4 个走原生翻译。deny 真拦工具、block 真续跑、子 agent 真注入、approval 真决定 —— 详见 Copilot 的特殊路径。flowchart TB
P["预置插件<br/>preset-class plugin"] -->|"显式声明 vendor + event"| S["ctx.compatAdapter<br/>registerHook / registerRules"]
S --> G{"能力分级"}
G -->|"official-bridge · 17"| B["官方桥<br/>dsh-hooks-claude-code / -codex"]
G -->|"native-translate · 80"| T["原生翻译层<br/>src/translate.js"]
G -->|"native-seam · 5"| O["只观察路由<br/>src/observe.js"]
G -->|"builtin-bridge · 10"| BB["自建桥<br/>src/bridges/copilot.js"]
G -->|"CARRY · rules"| R["原文逐字节携带"]
G -->|"UNSUPPORTED"| X["拒绝 + reason<br/>永不挂载"]
B --> SEAM["DSH seam<br/>tools/pre-execute · approval/request<br/>agent/turn-stopping · session/event …"]
T --> SEAM
O --> SEAM
BB --> SEAM
R --> CTX["agent/created<br/>注入模型可见上下文"]
SEAM --> RUN["真实拦截<br/>deny 阻断 · block 续跑 · ask 走审批"]
四种路由的差别,就是**"决定能不能到达 DSH"**:
| 路由 | 决定是否生效 | 用在哪 |
|---|---|---|
official-bridge | ✅ 由官方桥映射 | 有官方桥的 vendor(claude / codex / workbuddy) |
native-translate | ✅ 本插件直接产出 DSH 裁决类型 | 无桥但 seam 可拦截 |
native-seam | ❌ 只观察,阻断尝试被记录并忽略 | 无取消能力的生命周期时刻 |
builtin-bridge | ✅ 本插件自建的桥(仅官方无桥时使用) | copilot |
标准 DSH Cordis bundle(package.json 的 dsh.bundle.patch + cordis.patch.yml)。
# 方式 A:plugin_manager 工具(受管理路径,推荐)
# action: install_bundle, target: <本仓库绝对路径>
# 方式 B:CLI
dsh plugin --profile <profile> add <本仓库绝对路径>
装好后插件只做一件事:提供 ctx.compatAdapter 服务。它自己不做任何适配 —— 适配由预置插件声明触发。
cordis.patch.yml 里的 name 必须是 bare specifierbundle patch 的 baseUrl 是 profile 根目录,不是 patch 文件所在目录。所以 row 的 name 写成相对路径或 node_modules/<pkg>/ 前缀都会找不到包。
# ✅ 正确
- insert:
- id: compat-adapter
name: 'dsh-compat-adapter'
test/index.test.js 会断言这一点(并先剥掉注释,避免把注释里的反例误判成 patch 内容)。
在预置的 plugins 列表里插入本插件(装载顺序由 inject 保证),然后在你自己的 apply() 里调用服务:
- insert:
- id: preset-my-preset
name: '@deepseek-ai/dsh-agent-preset'
config:
id: my-preset
name: My Preset
plugins:
- id: compat-adapter
name: 'dsh-compat-adapter'
- id: my-vendor-assets
name: './my-vendor-assets/index.js'
// my-vendor-assets/index.js
export const name = 'my-vendor-assets'
export const inject = ['compatAdapter']
// apply 可以是 async:Cordis 会等待它,且注册必须在插件 setup 阶段完成。
export async function apply(ctx) {
// ── hooks ──────────────────────────────────────────────────────────────
const r = await ctx.compatAdapter.registerHook({
vendor: 'claude', // 显式声明,不靠检测
event: 'PreToolUse',
matcher: 'Write', // 厂商 matcher 语义,原样交给官方桥
handler: 'node ./hooks/no-write.mjs', // 命令字符串 = command hook
})
// r.capability === 'ADAPTED'
// r.seams === ['tools/pre-execute']
// r.degradations === [... 逐条损失 ...]
// ── rules ──────────────────────────────────────────────────────────────
const rules = await ctx.compatAdapter.registerRules({
vendor: 'codex',
source: 'rules/*.rules', // 绝对路径 / 相对 baseDir / file:// / glob
baseDir: '/abs/path/to/package', // 相对路径的基准;必须绝对
})
// rules.capability === 'CARRY',会话开始时作为上下文注入
}
registerHook(declaration)| 字段 | 必填 | 说明 |
|---|---|---|
vendor | ✅ | claude / codex / workbuddy / opencode / copilot,或文档化别名 |
event | ✅ | 厂商事件名(如 PreToolUse);未知事件抛 UNKNOWN_EVENT |
matcher | — | 厂商 matcher 字符串;非字符串抛 INVALID_MATCHER |
handler | — | 命令字符串,或 { type: 'command', command, timeoutSec? }。http / mcp_tool / prompt / agent 抛 INVALID_HANDLER;JS 函数返回 UNSUPPORTED |
id | — | 稳定 id,缺省 <vendor>:<event>:<n> |
返回 Promise<CapabilityResult>(冻结)。可移植性不足不抛错 —— 只有"声明本身无法处理"才抛错。
registerRules(declaration)| 字段 | 必填 | 说明 |
|---|---|---|
vendor | ✅ | 同上 |
source | ✅ | 字符串或字符串数组:绝对路径、baseDir 相对路径、file:// URL、glob |
baseDir | — | 相对源的基准目录,必须绝对路径(否则抛 INVALID_DECLARATION) |
id | — | 稳定 id,缺省 <vendor>:rules:<n> |
返回 Promise<CapabilityResult>,capability === 'CARRY',seams === ['agent/created']。
规则语义:注册时读盘并逐字节携带(附 sha256 与 bytes),在 agent/created(fresh / resume / clear / compact 全覆盖)时通过 agent.inject(createUserMessage(...)) 作为模型可见上下文入队。注入消息的 source.kind 是 compat-adapter-rules,与 DSH 自带的 agent-instructions 区分,日志里可追溯。
规则永远不是安全边界。 全部
CARRY。尤其 Codex 的 Starlark 规则是命令策略 DSL,DSH 没有对应规则引擎 —— 本插件只把它作为"原工具策略的说明文本"注入,不翻译、不强制执行。任何"规则会阻止某事"的预期都是错的;要强制,请写原生 DSH 插件挂到tools/pre-execute。
| 方法 | 作用 |
|---|---|
matrix() | 静态能力矩阵(本版本"永远能做什么")。 |
status() | 运行时审计("此刻实际在跑什么"):每个 hook 的分级 / seam / 是否挂了桥,规则文件的路径 / 字节 / sha256,以及当前组合里 shell / sessionProjections 是否存在。 |
dispose() | 卸载所有桥与监听;之后注册抛 ADAPTER_DISPOSED(不会半死不活地继续)。 |
cordis.patch.yml 的 row config)| 字段 | 默认 | 说明 |
|---|---|---|
logRegistrations | true | 每次分级注册输出一行结构化日志 |
failClosed | true | 缺所需 seam 时拒绝注册,而不是挂一个空转的 no-op |
targetDshVersion | 0.2.0-rc.2 | 目标 DSH 版本;不一致时按 failClosed 报错或告警 |
maxRuleBytes | 524288 | 单文件携带上限;超限报错而不是静默截断 |
参考工具文档里"exit code 2 = 阻断"的行为,在本 DSH 版本的两个官方桥里不可靠(可能被记录为 decision: 'pass' 而放行)。因此本插件统一契约:
stdout 输出
hookSpecificOutput.permissionDecision(deny/ask)+ 退出码0。
#!/usr/bin/env bash
# 例:拒绝写入 —— deny + exit 0
printf '%s' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"no writes in this preset"}}'
exit 0
本插件生成的任何示例、测试与文档都用这一种;从不生成依赖 exit 2 的 handler。src/vendors/shared.js 的 permissionDecisionOutput() 是唯一生成入口,测试直接断言生成物含 permissionDecision、且不含遗留的顶层 decision 字段。
ask 映射成 DSH 的 {kind:'ask'},deny 映射成 {kind:'deny'}(经官方桥,不经过本插件)。exit 2 仍会被桥解析(用户手写的老 hook 不会被吞掉),但这不是本插件承诺的契约路径。
GitHub Copilot 是唯一没有官方 DSH 桥的 vendor(Claude / Codex / WorkBuddy 都能复用官方桥),所以它的适配路径不同:本插件真的实现翻译层,不是旁观。
DSH 的 @deepseek-ai/dsh-hook-protocol 只认 Claude/Codex 方言:
// DSH 解析器只读这一层
{ "hookSpecificOutput": { "permissionDecision": "deny", "permissionDecisionReason": "…" } }
而 Copilot 把决定写在顶层:
{ "permissionDecision": "deny", "permissionDecisionReason": "…" }
若直接复用,每一个 Copilot deny 都会被静默当成"没有决定"而放行。所以 src/translate.js 里有一层 parseCopilotStdout() / normalizeCopilotOutput():先按 Copilot 原生格式解析,再归一化成内部形状。
这条路径有专门的测试(test/smoke-copilot.test.js,21 个用例)并纳入 npm run verify:live 的真实启动验证。
| Copilot 事件 | DSH seam | 能否阻断 |
|---|---|---|
preToolUse | tools/pre-execute | ✅ deny / ask 真的生效 |
postToolUse | tools/post-execute | 转发 additionalContext(modifiedResult 不采纳) |
subagentStart | subagent/start | ✅ 可把上下文注入子 agent(经 ctx.agents.get(runId)) |
subagentStop | subagent/end | 只观察(拿到 stopReason / lastAssistantMessage) |
errorOccurred | agent/error | 只观察(DSH 错误路径无裁决通道) |
sessionStart | agent/created | 注入 additionalContext 为消息 |
sessionEnd | agent/disposed | 只观察 |
userPromptSubmitted | agent/pre-step | 注入上下文(不改写 prompt) |
agentStop | agent/turn-stopping | ✅ decision:"block" 真的强制续跑(含 8 次 runaway guard) |
notification | agent/status | 只观察(DSH 只发布状态翻转,无 notification 类型) |
permissionRequest | approval/request | ✅ allow → allowed-once,deny → rejected |
postToolUseFailure | tools/post-execute | 仅当结果被标记为 error 时触发 |
^(?:PATTERN)$ 且必须全匹配 —— bas 不会匹配 bash;无效正则会让该 hook 整体跳过(不是 match-all)。本插件按此实现。allow 不预批准。 Copilot 的 allow 是"不弹权限提示",不是"强制放行",所以翻译成 next() 而非 allow 短路。本节由代码生成:
npm run matrix:write从src/matrix.js重新生成,test/matrix-readme.test.js在漂移时报错。不要手改。
读矩阵需要注意的三件事:
ADAPTED 的损失是逐条列出的,不是修辞。例如 Claude PreToolUse 的 updatedInput 只被解析、不被采纳;Codex PreToolUse 的 ask 没有审批路径;Claude SubagentStart 的 agent_type 恒为 general-purpose(针对具体 agent type 的 matcher 永不命中)。native-seam 的事件是"只观察":PreCompact 映射到 session/event 的 compaction/start,SessionEnd 映射到 agent/disposed。DSH 的压缩是服务调用、不是可取消 waterfall,所以不能否决压缩;尝试阻断只会被记入审计日志并忽略。degradations 并由测试断言。| Vendor | Event | Capability | Route | DSH seam(s) | Losses / reason |
|---|---|---|---|---|---|
| claude | SessionStart | ADAPTED | official bridge (claude) | agent/created | Runs detached through the bridge, so context can miss the model first request.; Plain-stdout context, initialUserMessage, sessionTitle, watchPaths and CLAUDE_ENV_FILE are not consumed.; Payload omits model, agent_type and session_title. |
| claude | UserPromptSubmit | ADAPTED | official bridge (claude) | agent/pre-step | Deny is honored; plain-stdout context, sessionTitle and suppressOriginalPrompt are not.; Unless the hook sets its own timeout the bridge uses its 600s default, not the reference 30s. |
| claude | PreToolUse | ADAPTED | official bridge (claude) | tools/pre-execute | deny and ask are honored; allow does not pre-approve and defer is unsupported.; updatedInput is parsed and warned about but not honored.; additionalContext from a PreToolUse hook is ignored. |
| claude | PostToolUse | ADAPTED | official bridge (claude) | tools/post-execute | Blocking feedback and additionalContext work; updatedToolOutput and updatedMCPToolOutput do not.; tool_response is flattened to text. |
| claude | Stop | ADAPTED | official bridge (claude) | agent/turn-stopping | A blocking Stop forces another step, but stop_hook_active is always false and last_assistant_message is omitted.; There is no consecutive-block cap; an unconditional blocking hook keeps forcing turns. |
| claude | SubagentStart | ADAPTED | official bridge (claude) | subagent/start | agent_type is the constant general-purpose; a matcher naming a specific agent type never fires.; Injected context can only reach an in-process child. |
| claude | SubagentStop | ADAPTED | official bridge (claude) | subagent/end | Observe-only: cannot block the child or feed it context.; agent_transcript_path, last_assistant_message, background_tasks and session_crons are omitted. |
| claude | PostCompact | ADAPTED | native translation (compaction/end) | session/event | Observe-only: the compaction already completed, so a hook can react but never change the result.; The summary is taken from the preceding durable compaction/summary event; the hook sees the same text, not a live object.; trigger is reported as "auto" because DSH does not distinguish a manual /compact from automatic pressure at this seam. |
| claude | PreCompact | ADAPTED | native seam, observe-only (compaction/start) | session/event | Cannot veto compaction: DSH compaction is a service call, not a cancellable waterfall, so the hook is observe-only.; Fires on the durable compaction/start event after compaction was requested, not before the summary is computed.; A blocking decision (deny / exit 2) is audited but does NOT prevent compaction. |
| claude | SessionEnd | ADAPTED | native seam, observe-only (agent/disposed) | agent/disposed | agent/disposed fires when an Agent leaves the live registry, which is broader than the reference end-of-session moment (dispose also happens on reload/teardown).; Payload carries no reason and no transcript path; anything the hook needs must be read from the session working directory. |
| claude | Setup | ADAPTED | native translation (session/created) | session/created | Observe-only: DSH announces a created session but exposes no pre-activation configuration channel here.; Fires when a Session object is created, which is after the harness is already running — not before hooks are enabled as the reference tool does. |
| claude | InstructionsLoaded | ADAPTED | native translation (agent/instructions-loaded) | session/created | Fires once per created session with the full discovered list, not once per file as the reference event does.; The list is what DSH discovery would find (CLAUDE.md, AGENTS.md, configured candidates); a file added later is not re-reported.; Observe-only: DSH loads instructions through dsh-agent-instructions, which exposes no veto or rewrite here. |
| claude | UserPromptExpansion | ADAPTED | native translation (agent/pre-step) | agent/pre-step | Observe-only: DSH pre-step receives the already-assembled batch, so the hook cannot rewrite the expansion.; Fires per step, not once per expanded prompt. |
| claude | MessageDisplay | ADAPTED | native translation (session/event) | session/event | Observe-only and approximate: DSH has no display seam, so this reports the durable message event that would be displayed.; The hook cannot alter, delay, or suppress presentation. |
| claude | PermissionRequest | ADAPTED | native translation (approval/request) | approval/request | Decisions map onto DSH ApprovalOutcome: allow→allowed-once, deny→rejected. DSH has no broader grant, so a hook cannot express "always allow".; The approval waterfall only runs when DSH actually asks for approval; a tool the preset already permits never reaches this hook.; The reference payload’s permission-mode detail is absent; DSH reports the tool and its reason only. |
| claude | PermissionDenied | ADAPTED | native translation (session/event) | session/event | Observe-only: the decision already committed, so the hook cannot change it.; Driven by the durable approval/decided audit event with outcome rejected; other outcomes do not fire it. |
| claude | PostToolUseFailure | ADAPTED | native translation (tools/post-execute) | tools/post-execute | Mounted by this adapter rather than the shipped bridge (the bridge does not map this event).; Only fires when the result is marked as an error; DSH uses one waterfall for successes and failures. |
| claude | PostToolBatch | ADAPTED | native translation (step/end) | session/event | REAL batch boundary: DSH defines one step as one model call plus the tool executions it requested, so this fires once per completed step with the batch's tool names and count.; The payload carries the batch identity and tool names, not the individual tool results; a hook needing result bodies should also use PostToolUse. |
| claude | Notification | ADAPTED | native translation (agent/status) | agent/status | Observe-only and coarse: DSH publishes agent status flips, not typed notifications.; matcher cannot narrow because DSH carries no notification type. |
| claude | TaskCreated | ADAPTED | native translation (todo/write) | session/event | Observe-only: DSH publishes a whole todo-list snapshot, so creation is derived by diffing snapshots.; Only todo-style tasks are visible; jobs and team tasks register through services with no event. |
| claude | TaskCompleted | ADAPTED | native translation (todo/write) | session/event | Observe-only: completion is derived from a todo snapshot where an item's status became completed.; A task removed from the list without being completed does not fire it. |
| claude | StopFailure | ADAPTED | native translation (agent/error) | agent/error | Observe-only: DSH exposes no verdict channel on its error path, so this hook can report but never retry or alter the failure.; Fires for any turn/step agent error, not only the "API error ended the turn" moment the reference tool reports.; Reference payload fields (error type, retryability) are approximated from the thrown error and are not normalized to Claude’s vocabulary. |
| claude | TeammateIdle | ADAPTED | native translation (agent/status) | agent/status | Observe-only: DSH status flips are a consequence of driver activity, so a hook cannot make an agent idle.; Fires for any agent entering idle, not only teammates in a team. |
| claude | ConfigChange | ADAPTED | native translation (settings/document-updated) | settings/document-updated | Observe-only: the new settings revision has already committed, so a hook cannot block or rewrite it.; DSH reports settings namespaces, not the configuration FILES the reference tool watches; filePath is null and a matcher on a path never narrows. |
| claude | CwdChanged | ADAPTED | native translation (session/created) | session/created | Observational approximation: DSH pins the session working directory at creation and never changes it, so this fires once at session creation with the initial cwd instead of on a change. |
| claude | DirectoryAdded | ADAPTED | native translation (session/created) | session/created | Observational approximation: DSH grants one working directory per session and has no directory-addition event, so this fires once at session creation. |
| claude | FileChanged | ADAPTED | native translation (fs/watch) | fs.watch | This adapter ARMS a real filesystem watcher (ctx.fs.watch) on the session working directory, so external edits fire it — not only DSH-made changes.; One watcher covers the session cwd with depth 0, matching the reference tool's project-directory watch; a change deep inside a subtree may not be reported by every backend.; Bursts are coalesced with a 250ms debounce, so several rapid edits report as one change.; A composition without ctx.fs falls back to the turn-summary approximation, and the reason is logged. |
| claude | WorktreeCreate | ADAPTED | native translation (git/worktrees) | shell | REAL detection: this adapter polls git worktree list --porcelain and fires when a worktree actually appears, so the hook receives the worktree path, HEAD and branch.; DSH has no worktree lifecycle event, so polling is used; a create/remove pair inside one poll interval is observed as the net change.; Requires ctx.shell and a real git repository; a non-repository session yields no worktrees and the hook never fires. |
| claude | WorktreeRemove | ADAPTED | native translation (git/worktrees) | shell | REAL detection: fires when a worktree disappears from git worktree list --porcelain, and the payload names the removed path with its last known HEAD and branch.; Polling-based, so the exact removal moment is bounded by the poll interval. |
| claude | PreModelSwitch | ADAPTED | native translation (request/header) | session/event | REAL detection: fires when the durable request/header shows a changed provider/model, carrying both the previous and current values.; Timing differs from the reference tool: DSH logs the header when the next request carries the new config, so the hook observes th |