Webhook 事件(Webhook Events) 是一种把经过验证的外部事件转化为 DSH 会话的能力:一次已认证的提供商投递(例如带签名的 GitHub webhook)到达一条可信的程序化规则,规则可以创建一个位于 Web Workspace 内的普通根会话(root Session)来处理该事件。分发是进程内且 fire-and-forget 的——没有投递数据库、队列、重试、去重或 Agent 完成状态。整个家族完全由用户自行组合:没有任何已发布 bundle 接入它。
| 包 | 角色 | ctx key |
|---|---|---|
packages/webhook/webhook | 规则注册表、回调生命周期与基于 Workspace 的会话创建 | ctx.webhookRuntime |
packages/webhook/webhook-github | 带签名的 GitHub HTTP 适配器(精确 WebServer 路由、有界请求体、HMAC 验证) | 消费 ctx.webhookRuntime + ctx.webServer |
拆分遵循 dsh 众多能力通用的接缝模式:提供商适配器负责认证与归一化投递,规则拥有任意条件与外部调用,运行时拥有回调生命周期与会话创建。这与 packages/hooks/*(Claude Code / Codex shell-hook 桥)是不同的机制——后者在 agent 运行期间触发既有的 hook 配置,而不是从外部事件启动新会话。
运行时:WebhookRuntime
ctx.webhookRuntime(见 packages/webhook/webhook/src/index.ts)是 Host 侧的可信程序化规则注册表,只有两个操作:
register<K extends string>(rule: WebhookRule<K>): () => Promise<void>
dispatch<K extends string>(delivery: VerifiedWebhookDelivery<K>): voidWebhookRule<K> 携带一个有 brand 的唯一 id、一个提供商 kind,以及 run(delivery, signal):
interface WebhookRule<K extends string> {
readonly id: WebhookRuleId
readonly kind: K
run(delivery: VerifiedWebhookDelivery<K>, signal: AbortSignal):
Promise<WebhookSessionRequest | null> | WebhookSessionRequest | null
}VerifiedWebhookDelivery<K> 携带提供商 kind、配置的 source、提供商的 deliveryId、归一化后的无损 JSON event,以及非负安全整数 receivedAt。运行时在把完整值分发给多于一条规则之前先验证、detach 并冻结它。deliveryId 仅是 provenance——运行时既不存储也不去重它,因此重复投递会再次运行规则。
dispatch() 为匹配的规则拍摄快照,独立调度每条规则,并在任何回调 settle 之前返回;抛错与拒绝按规则隔离。注册是一个 effect,其可等待的 disposer 先隐藏规则,然后中止并排空活动回调——回调必须观察传入的 signal,因为忽略取消的同进程代码无法被强制安全终止。
会话请求
非空的 WebhookSessionRequest 需要绝对 workspacePath、title、prompt、agentPreset 与 permissionPreset。可选的 model 指定显式的提供商/模型路由外加输出 token 上限:
interface WebhookSessionRequest {
readonly workspacePath: string // 绝对路径;在 Web Workspace 内创建根会话
readonly title: string
readonly prompt: string // 模型恰好看到的文本——没有私有框架
readonly agentPreset: string
readonly permissionPreset: string
readonly model?: { provider: string; model: string; maxTokens?: number }
}显式路由使用其适配器的 reasoning 默认值;省略则快照当前完整的部署选择(包括 reasoning effort),直到首个请求记录其持久 header。运行时在变更前验证 presets,解析或创建规范化 Workspace,以该 Workspace 路径作为 SessionHeader.cwd 创建 Agent,在发布前挂载 agent preset,并在应用权限、标题与提示词之前持久附加会话。成功的 Agent.followup() 是 webhook 操作的提交点——消息使用 source.kind: "webhook",并携带 provider、source、delivery 与 rule provenance。运行时不等 idle、不检查回复、也不发布完成状态;此后一切都归普通 Agent 与会话行为所有。
GitHub 适配器
@deepseek-ai/dsh-webhook-github 在注入的 WebServer 上注册恰好一条精确路由(kind: 'exact',见 packages/webhook/webhook-github/src/index.ts)。每个请求它都会解析凭据引用、在 maxBodyBytes 上限内读取原始请求体,并在任何 JSON 解析之前验证 X-Hub-Signature-256 HMAC——尚未进入解析器的请求体不会被信任:
| 检查 | 次序 | 失败 |
|---|---|---|
仅 POST | 读取请求体之前 | 405 + Allow: POST |
Content-Type: application/json(至多一个可选 UTF-8 charset 参数) | 读取请求体之前 | 415 |
Content-Length / 流式字节数 ≤ maxBodyBytes | 读取请求体期间 | 413 |
X-Hub-Signature-256、x-github-delivery、x-github-event 头存在 | 读取请求体之后 | 400 |
| 针对已解析密钥的 HMAC 验证 | JSON 解析之前 | 401 |
| 无损 JSON 对象载荷 | 验证之后 | 400 |
分发给 ctx.webhookRuntime | 最后 | 不可用时 503 |
成功后适配器在内存分发完成后立即应答 202——绝不等待规则或会话 settle。归一化事件是带签名的无损 JSON 对象({ name: eventName, payload }),以 VerifiedWebhookDelivery<'github'> 投递;这是提供商无关的投递,规则会验证它们消费的事件特定字段。当 webhook 密钥凭据不可用时也返回 503。
GitHub review 指南(docs/user/guide/github-review.md)把该路由挂载在隔离的第二个 WebServer 上,因此暴露 webhook 入口绝不会暴露浏览器 API。随附的示例覆盖位于 apps/cli/config/examples/github-review/cordis.yml:
# Opt-in GitHub webhook overlay over the shipped Web composition. The second
# WebServer lives in an isolated realm so exposing it never exposes the UI API.
- insert:
- id: webhook-runtime
name: '@deepseek-ai/dsh-webhook'
- id: github-ready-review-rule
name: './github-ready-review-rule.mjs'
config:
source: primary-github
repository: deepseek-harness/deepseek-harness
workspacePath: !!js process.env.DSH_GITHUB_REVIEW_WORKSPACE ?? process.cwd()
agentPreset: standard
permissionPreset: read-only
- id: github-webhook-ingress
name: cordis:group
group: true
isolate:
webServer: true
config:
- id: github-webhook-server
name: '@deepseek-ai/dsh-host-webserver'
config:
host: '127.0.0.1'
port: !!js Number(process.env.DSH_GITHUB_WEBHOOK_PORT ?? 3081)
- id: github-webhook-adapter
name: '@deepseek-ai/dsh-webhook-github'
config:
source: primary-github
path: /github
secretEnv: DSH_GITHUB_WEBHOOK_SECRET
maxBodyBytes: 1048576规则模块(github-ready-review-rule.mjs)承载组合的逻辑:检查投递、决定是否行动,并返回 WebhookSessionRequest 或 null。
Fire-and-forget 不提供什么
这些注意事项是这个契约诚实的边界——接受并不等于 Agent 成功:
- 仅进程内 fire-and-forget——崩溃会丢失尚未接纳提示词的规则调用;没有队列、重放或重试。
- 没有内置去重——重复的提供商投递可能创建重复的会话;需要幂等的规则自己负责。
- 没有完成结果——HTTP 接受与规则 settle 都不报告 Agent 的成功、idle 或输出。
- 可信回调必须配合取消——运行时拆除会中止并等待它们,但无法终止任意的同进程代码。
- Workspace 创建可能比一次失败的会话尝试活得更久——空的 Workspace 会被保留,因为另一个并发调用方可能已经在使用它。
由于这些都没有接入任何已发布 bundle,部署必须显式组合运行时、适配器与规则模块——包括从源码检出组合。
包
| 包 |
|---|
@deepseek-ai/dsh-webhook |
@deepseek-ai/dsh-webhook-github |
延伸阅读
packages/webhook/README.md——webhook 家族包地图packages/webhook/webhook/README.md——规则接口与会话请求契约packages/webhook/webhook-github/README.md——GitHub 适配器表面docs/subsystems/webhook.md——共享类型与时机保证docs/user/guide/github-review.md——完整的组合式 GitHub review 示例apps/cli/config/examples/github-review/cordis.yml——随附的 opt-in 覆盖层