Introduction

In the pluginized workflow of DSH (DeepSeek Harness), many automation requirements are straightforward: sending notifications after a round of dialogue ends, logging when a specific tool is invoked, or displaying a desktop notification when an approval request arises. These actions typically require listening to events, parsing context, and then executing commands or notifications. dsh-hooks places this kind of logic in profile configuration: you can declare events, matching conditions, and the run or notify to execute in cordis.patch.yml without needing to write extra plugin code.

What is it

dsh-hooks is a DeepSeek Harness plugin maintained by PeterBon and licensed under the MIT license. Its positioning is config-driven lifecycle hooks: declaring event -> command/notify hooks via configuration.

It consists of two parts:

  1. Hook Engine: Listens to DSH events based on configuration and executes commands or built-in notifications.
  2. Web GUI Settings Page: After installation, the dsh web settings panel adds a Hooks section, providing a history timeline, manual testing, notify testing, hook editor, and Feishu connect.

Core Capabilities

Below are the main capabilities of dsh-hooks:

  1. Declare event -> command or event -> notification hooks in the profile’s cordis.patch.yml.
  2. No need to write plugin code; configuration can trigger commands or notifications.
  3. A single package provides both the hook engine and the Web GUI settings page.
  4. The Web GUI settings panel adds a Hooks section, including history timeline, manual tester, notify tests, hook editor, and Feishu connect.
  5. Built-in notification channels:
    • desktop: Platform bubble/notification.
    • webhook: Send JSON to an HTTP endpoint; slack: true can be configured to generate a summary line.
  6. Supports the following events:
    • turn/start
    • turn/end
    • tree/settled
    • step/end
    • tool/call
    • tool/result
    • user/message
    • approval/asked
    • approval/decided
    • session/title
    • session/created
    • session/disposed
    • agent/created
    • agent/disposed
    • agent/error
    • agent/status
    • hook/failed
  7. Context is passed via DSH_HOOK_* environment variables; if input: stdin is configured, the full context JSON will be written to the command’s stdin.

Installation and Enablement

dsh-hooks requires Node.js:

node >=22

You can install the plugin via npm:

dsh plugin --profile web add dsh-hooks

You can also add it directly from the GitHub repository:

dsh plugin --profile web add github:PeterBon/dsh-hooks

After installation, you need to restart dsh web. Once the restart is complete, the settings panel will add a Hooks section for viewing and configuring hooks.

Basic Configuration

Configuration is written in the profile’s cordis.patch.yml. Below are several typical entries:

- id: dsh-hooks
  name: dsh-hooks
  config:
    hooks:
      - on: 'turn/end'
        when: 'completed'
        run: 'node examples/notify-feishu.mjs'
        timeoutMs: 10000

      - on: 'approval/asked'
        notify:
          channel: 'desktop'

      - on: 'turn/end'
        when: 'completed'
        notify:
          channel: 'webhook'
          url: 'https://hooks.slack.com/services/…'
          slack: true

      - on: 'tool/call'
        match:
          tool: '^(rm|git|ssh)'
        run: 'node examples/notify-webhook.mjs --slack'

The configuration above accomplishes four things:

  1. When turn/end ends with completed, execute node examples/notify-feishu.mjs.
  2. When an approval/asked event occurs, send a desktop notification.
  3. When turn/end completes, send a notification via webhook with a Slack-style summary line.
  4. When a model request invokes a tool matching rm, git, or ssh, execute the webhook notification script.

Configuration Fields

dsh-hooks’ hook fields cover event triggering, condition filtering, execution mode, notification, retry, concurrency, and debounce. Common fields are as follows:

  1. on: The triggering event name, such as turn/end, tool/call, approval/asked.
  2. when: Filters the end reason of turn/end, such as completed.
  3. match: Conditionally filters on context fields, such as regex matching on tool names.
  4. run: The command to execute. You must choose either run or notify.
  5. notify: Built-in notification, supports channel: desktop and channel: webhook.
  6. input: Controls the context passing method. When using stdin, the full context JSON is written to the command’s stdin.
  7. timeoutMs: Timeout for a single command, in milliseconds.
  8. retries: Number of retries for non-zero exit codes.
  9. retryDelayMs: Retry interval, in milliseconds.
  10. enabled: When set to false, the configuration is retained but not triggered.
  11. cwd: Command working directory, can be session or an absolute path; only used for run.
  12. maxConcurrent: Maximum number of concurrent processes for the same hook.
  13. debounceMs: Debounce window for high-frequency events, in milliseconds.

Typical Usage

1. Execute Command

When a certain event occurs, execute a local command:

- on: 'turn/end'
  when: 'completed'
  run: 'node examples/notify-feishu.mjs'

This configuration starts node examples/notify-feishu.mjs when turn/end occurs with the end reason completed.

2. Use Desktop Notification

When an approval request appears, send a platform bubble/notification directly:

- on: 'approval/asked'
  notify:
    channel: 'desktop'

This configuration does not require an extra script; it is handled directly by dsh-hooks’ built-in notification logic.

3. Use Webhook Notification

Post event information to an HTTP endpoint:

- on: 'turn/end'
  when: 'completed'
  notify:
    channel: 'webhook'
    url: 'https://hooks.slack.com/services/…'
    slack: true

If the target is Slack, you can configure slack: true to send a summary line.

4. Filter by Tool Name

Only listen to tool/call events that match specific tool names:

- on: 'tool/call'
  match:
    tool: '^(rm|git|ssh)'
  run: 'node examples/notify-webhook.mjs --slack'

Here, match.tool is a regular expression used to filter the tool names invoked by the model request.

5. Pass Full Context via stdin

By default, context is passed via DSH_HOOK_* environment variables. If your script needs the full JSON, you can use:

- on: 'turn/end'
  input: 'stdin'
  run: 'node my-hook.mjs'

After configuration, the full context JSON will be written to the stdin of node my-hook.mjs.

6. Control Retries

Perform limited background retries for non-zero exit codes:

- on: 'turn/end'
  when: 'completed'
  run: 'node examples/notify-feishu.mjs'
  retries: 2
  retryDelayMs: 1000

Retries here apply only to non-zero exit codes. Spawn failures and timeouts are not retried.

7. Control Concurrency and Debounce

For high-frequency events, you can limit concurrency and apply debounce:

- on: 'step/end'
  run: 'node examples/log-step.mjs'
  debounceMs: 500
  maxConcurrent: 2

debounceMs: 500 merges high-frequency triggers within a 500-millisecond window; maxConcurrent: 2 limits the number of concurrent processes for the same hook. Triggers exceeding the limit are discarded and recorded as skipped.

8. Temporarily Disable a Hook

If you don’t want to delete the configuration, you can keep the declaration and disable it:

- on: 'turn/end'
  enabled: false
  cwd: 'session'
  run: 'node examples/log-turn.mjs'

enabled: false means this hook will not trigger, but the configuration is retained. cwd: session means the command runs in the session’s working directory.

Behavior Description

dsh-hooks’ execution model leans towards “triggering external actions without blocking the main process”:

  1. Command execution is fire-and-forget.
  2. Command failures only generate a console.warn; by default, they do not block DSH’s agent loop.
  3. run and notify must be one or the other.
  4. retries is only used for background retries on non-zero exit codes; spawn failures and timeouts are not retried.
  5. maxConcurrent limits the number of concurrent processes for the same hook; triggers exceeding the limit are discarded and recorded as skipped.
  6. cwd is only for run; it can be set to session or an absolute path.

Dependencies and License

dsh-hooks’ package information contains the following peer dependencies:

@deepseek-ai/cordis
@deepseek-ai/dsh-session
@deepseek-ai/schemastery
react

Runtime requirements:

node >=22

The license is MIT.

Use Cases

dsh-hooks is suitable for the following scenarios:

  1. You want to connect DSH lifecycle events to local scripts or notification systems.
  2. You need to execute lightweight automation when events such as turn/end, tool/call, or approval/asked occur.
  3. You want to send desktop notifications, Slack-style webhook notifications, or invoke custom scripts.
  4. You do not want to maintain extra plugin code for event listening and wish to manage everything directly in the profile configuration.

Note: The run command in the plugin executes with the permissions of the current DSH process. It is recommended to check the source code, example scripts, and license before installation, and confirm that the commands to be executed will not perform dangerous operations.

Conclusion

The value of dsh-hooks lies in converting DSH lifecycle events into low-code automation entry points: after configuring cordis.patch.yml, you can execute commands or send notifications on events such as turn, step, tool, approval, session, and agent, and manage and test them via the Hooks settings page in dsh web.

Related Links:

  1. GitHub Repository: https://github.com/PeterBon/dsh-hooks
  2. Plugin Directory Page: The current documentation does not provide a separate URL; after installation, you can view the Hooks section in the settings panel of dsh web, or search for dsh-hooks in the current DSH plugin directory entry.