跳过主要内容
全部文章
开发

DeepSeek-Harness 中的 Hooks 与斜杠命令(附复用 Claude Code / Codex Hooks)

DeepSeek-Harness 中斜杠命令与 hooks 的工作原理、hook 可以监听的扩展点,以及可以直接复用现有 Claude Code、Codex hooks.json 的官方桥接插件。

DeepSeek-Harness 有两套从外部看都像"命令"的扩展机制:人类命令(斜杠开头,由 ctx.commands 处理,模型永远看不到)和 hooks(监听 agent/session-starttools/pre-execute 等生命周期扩展点的插件)。两者都没有独立的 manifest 格式——和 dsh 里的一切一样,它们都只是针对 Context 的普通插件注册。

两个不同的东西,同一套机制

"命令"和"hooks"很容易被混为一谈,因为在别的 harness 里它们有时是同一个概念。但在 dsh 的术语表里,它们被明确区分开:

  • 人类命令是斜杠开头的指令(/compact/goal),由 ctx.commands 直接解释执行。它永远不会变成一条模型消息——它是与"面向模型的工具"和"shell 命令"并列的第三个类别,专门留给人类操作者在界面里输入的东西,而不是智能体自己决定要调用的东西。
  • hook 是一个订阅了某个生命周期扩展点、在该点触发时运行代码的插件——会话开始、模型执行一步之前、工具执行之前,或一个通用会话事件。hook 没有自己的界面,纯粹是拦截逻辑。

结构上两者的实现方式相同:一个插件的 apply(ctx) 函数针对 ctx 做注册。dsh 里没有独立的 commands.jsonhooks.json 原生 schema——这是"一切皆插件"设计理念的直接后果,详见我们的架构总览

人类命令:ctx.commands

插件通过往 ctx.commands 添加一条记录来注册一个斜杠命令。基础 bundle 自带两个内置命令:

  • /compact——触发压缩子系统(compaction/command-compact)来收缩对话历史。
  • /goal——dsh-goal 插件面向人类的入口,负责追踪挂在某个会话上的持久完成目标(active/paused/blocked/complete 四态,带"goal round"轮次上限)。

因为命令是被解释执行而非发送给模型的,所以适合放那些应该确定性执行、绝不能被 LLM 重新解读或忽略的东西——这是管理性动作,不是智能体的决策。

Hooks:你可以监听的扩展点

hook 的工作方式是订阅一个具名扩展点。截至 2026 年 8 月,与 hook 式拦截相关、有文档记录的扩展点是:

扩展点触发时机
agent/session-start一个新会话开始时
agent/pre-step智能体执行一个模型步骤之前(一次模型请求以及它触发的任何工具调用)
tools/pre-execute一次工具调用真正执行之前——权限/沙箱插件也是在这里返回 allow/deny/ask 决策的
session/event一个通用会话事件触发时——UI 插件渲染对话节点用的也是同一个点

一个 hook 插件本质上就是在 apply(ctx) 里对上述某个点注册一个监听器的代码,仅此而已。不需要另外写一个 hook 定义文件——监听器注册本身就是这个 hook,并且和所有其他插件注册一样,插件卸载时会被自动清理。

import type { Context } from '@deepseek-ai/cordis'

export const name = 'session-logger-hook'

export function apply(ctx: Context) {
  ctx.on('agent/session-start', (session) => {
    console.log(`session started: ${session.id}`)
  })
}

(这个例子遵循的插件形态和我们的自定义工具教程里一样——hook 和工具都只是 ctx 上的注册,只是挂在不同的接缝上。)

复用 Claude Code 或 Codex 的 hooks.json

如果你已经为 Claude Code 或 Codex 配好了一份 hooks.json,不需要手工把它重写成 dsh 的扩展点监听器。官方专门为此提供了两个桥接包:

桥接对象
dsh-hooks-claude-codeClaude Code 的 hooks.json shell-hook 协议
dsh-hooks-codexCodex 对应的 hook 配置

两者都会把外部工具的 shell-hook 协议翻译成对 dsh 自身扩展点(agent/session-startagent/pre-steptools/pre-executesession/event)的调用,所以一份为另一个 harness 写的现有 hook 脚本不需要重写就能继续运行。如果你正在评估从 Claude Code 迁移过来,这一点尤其重要——完整的迁移图景参见我们的DeepSeek-Harness vs Claude Code 对比

命令、hook 还是工具——该选哪个?

写第一个扩展时,一个常见的困惑是不知道该给这个功能选哪种机制。既然三者最终都落地为对 ctx 的普通注册,选择就取决于"由谁在什么时候调用它":

你想要的效果应该用
让人类在界面里显式触发一个确定性动作命令ctx.commands
让模型在一轮对话中自己决定要不要带参数调用它工具ctx.tools.register()
在会话生命周期发生某件事时自动响应,不需要被显式调用hook,监听某个扩展点
桥接一份已经写好的 Claude Code 或 Codex hook 脚本dsh-hooks-claude-codedsh-hooks-codex 桥接插件,而不是手写 hook

命令和工具的区别在涉及安全或状态敏感的场景下最重要:命令保证永远不会是模型自己选择调用的东西,而工具的定义恰恰就是让模型可以选择调用它。如果你要实现类似"清空会话的待办列表"或"强制触发一次压缩"这样的功能,把它做成命令而不是工具,可以直接消除"智能体在没有被要求的情况下自己决定这么做"这整一类故障模式。

为什么没有独立的 manifest 格式

如果你从一个 skills、commands、hooks、MCP 各自有独立目录结构或 manifest schema 的 harness 过来,这里没有对应格式可能会让人觉得像是个缺口。其实不是——这是 dsh 微内核设计的直接后果:包括命令和 hook 在内的每一个产品特性,都是通过针对 Context 对象做注册来实现的,上面没有再叠加一层"特性专属"的文件格式。代价是你需要理解插件/Context 模型才能理解任何一个具体特性(包括 hooks)——没有一份可以走捷径浏览的 manifest。完整推理和全部扩展点表格见架构指南

FAQ

人类命令和工具有什么区别?

人类命令(/compact/goal)由人输入、由 ctx.commands 解释——永远不会传给模型。工具是用 ctx.tools.register() 注册的,是模型在一轮对话中自己决定要调用的东西。这是刻意区分开的两个类别。

hook 能阻止一次工具调用吗?

可以,间接地——tools/pre-execute 正是权限和沙箱插件用来返回 allow/deny/ask 决策的扩展点,所以注册在这个点上的 hook 实际上可以否决或限制执行。

我需要为 dsh 重写 Claude Code 的 hooks 吗?

如果你使用官方的 dsh-hooks-claude-code 桥接插件,不需要——它会把你现有的 hooks.json shell-hook 协议翻译到 dsh 的扩展点上,不需要重写。

dsh 有原生的 hooks.json 文件吗?

没有。dsh 里的 hook 是订阅扩展点的插件代码,而不是一套独立的声明式 JSON schema。桥接插件之所以存在,正是因为 dsh 没有一套原生的对等格式可以手工翻译过去。

在哪里能找到 dsh 暴露的全部扩展点?

扩展点到实现机制的完整映射(工具、命令、技能、MCP、hook、模型适配器、UI、权限、后台任务、子代理委派)以一张表的形式记录在 cookbook 里;我们的架构指南复现了其中相关的行。

Next steps

如果你正在迁移已有配置,阅读从 Claude Code 迁移到 DeepSeek-Harness获取完整的 hooks/MCP/配置对照表。想理解为什么 hooks 和 dsh 里的一切共享同一套机制,参见DeepSeek-Harness 架构:一切皆插件以及术语表里"人类命令"等词条。想浏览基于这些扩展点构建的通知类插件,去通知与集成分类页。