deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
PROJECT README
A hooks configuration compatibility layer for DeepSeek Harness (dsh): it reads existing hooks config files from mainstream agent harnesses (such as the hooks declarations in .claude/settings.json, .codex/hooks.json, and the hooks section of opencode.json), maps their lifecycle events to dsh extension points, and executes four kinds of handlers — shell / webhook / oracle / proxy — so the same hooks config can be reused as-is across different harnesses.
validate subcommand)apply), stdio JSON-lines protocol (any host), one-shot CLIhooks-adapter/
├── package.json # dsh bundle manifest (dsh.bundle + exports)
├── cordis.patch.yml # composite package layer: inserts this plugin into the plugin tree
├── dsh/plugin.js # dsh entry: Cordis plugin (name + apply(ctx, config))
├── lib/ # runtime core (usable independently of dsh)
│ ├── index.js # CLI entry + programmatic API exports
│ ├── events.js # canonical event catalog + four-dialect mapping table + matcher semantics
│ ├── discover.js # config file discovery (global/project/local)
│ ├── parse.js # four-dialect parsers (all go through diagnostics, never throw)
│ ├── config.js # runtime assembly: merging, disableAllHooks, defaults
│ ├── contract.js # stdin JSON contract construction + response decoding + decision folding
│ ├── execute.js # four-kind handler executor + timeout + process tree cleanup
│ ├── dispatch.js # dispatch pipeline: matcher matching, ordered execution, blockable constraints
│ └── serve.js # stdio JSON-lines protocol server
├── docs/ # config formats, event mapping, contract, integration notes, CLI guide
├── examples/ # four-dialect example configs + local mock LLM
└── test/ # node:test tests (111 items)
Declare hooks in .claude/settings.json (no matter which harness you wrote them for) and they keep working in dsh:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "guard.sh", "timeout": 10 }
]
}
],
"Stop": [
{ "hooks": [ { "type": "command", "command": "notify-send done" } ] }
]
}
}
Config files must be strict JSON (no comments); see examples/ for complete four-dialect examples.
PreToolUse → interception point before tool execution: handler exit code 2 / JSON decision: "block" will block the tool call (or turn it into ask for human confirmation)PostToolUse / PostToolUseFailure → after tool execution (mutually exclusive triggers): reject the write-back as result feedback, append contextUserPromptSubmit / SessionStart / Stop / SubagentStart / SubagentStop / SessionEnd → inject context, reject prompts, force the model to continueNotification / PreCompact → triggered manually or via the stdio protocolThe four handler kinds (the type field in config follows each harness's conventions; normalized internally):
| Config type | Internal kind | Behavior | Default timeout |
|---|---|---|---|
command |
shell |
spawn a shell process, feed the JSON contract on stdin | 600s |
http |
webhook |
POST JSON to a URL, the response body is the decision | 600s |
prompt |
oracle |
call an LLM endpoint to evaluate, {ok:false} rejects |
30s |
agent / subagent |
proxy |
delegate to a subagent runner (configurable command) | 60s |
# From a directory containing this plugin checkout
dsh plugin --profile demo add ./hooks-adapter
dsh --profile demo
After loading, the plugin automatically discovers hooks configs in project and user directories (see below). You can also override the config line in the profile's cordis.patch.yml:
- replace:
- id: hooks-adapter
config:
configPath: /abs/path/to/hooks.json # pin a single file (skip discovery)
discover: false
llm: { baseUrl: "https://api.example.com/v1", model: "eval-small" }
proxy: { command: "dsh run --quiet" }
Integration details: docs/INTEGRATION.md.
Install directly from the GitHub repository with the dsh plugin command:
dsh plugin --profile demo add github:JohnXu22786/hooks-adapter
The package is a dsh bundle (dsh.bundle.patch → cordis.patch.yml); once added, it inserts itself into the plugin tree and automatically discovers hooks configs on the next dsh run. Remove it with:
dsh plugin --profile demo remove hooks-adapter
echo '{"op":"ping"}' | node lib/index.js listen --config hooks.json
echo '{"op":"dispatch","event":"PreToolUse","payload":{"tool_name":"Bash","tool_input":{}}}' | node lib/index.js listen
Protocol details: docs/CONTRACT.md.
node lib/index.js validate # check all discoverable configs, exit code 0/1
node lib/index.js run --event PreToolUse --payload payload.json
node lib/index.js dump # print the merged effective config
node lib/index.js list # list discovered config files
Auto-discovered and merged in order (later files append groups for same-named events; disableAllHooks follows the most specific file):
| Order | File | Dialect |
|---|---|---|
| 1 | ~/.claude/settings.json |
claude |
| 2 | ~/.codex/hooks.json |
codex |
| 3 | ~/.config/opencode/opencode.json |
opencode |
| 4 | ~/.config/hooks-adapter/hooks.json |
native |
| 5 | <project>/.claude/settings.json |
claude |
| 6 | <project>/.codex/hooks.json |
codex |
| 7 | <project>/opencode.json |
opencode |
| 8 | <project>/.dsh-hooks.json |
native |
| 9 | <project>/.claude/settings.local.json |
claude |
HOOKS_ADAPTER_CONFIG (same as --config) and HOOKS_ADAPTER_HOME (same as --home)Every harness's event names map to a set of canonical events (session:start, tool:before, ...), which then bind to dsh extension points:
| Canonical event | claude dialect | codex dialect | opencode dialect | dsh extension point |
|---|---|---|---|---|
session:start |
SessionStart |
SessionStart |
session.created |
agent/session-start |
session:end |
SessionEnd |
SessionEnd |
session.deleted |
session/disposed |
prompt:submit |
UserPromptSubmit |
UserPromptSubmit |
chat.message |
agent/pre-step |
tool:before |
PreToolUse |
PreToolUse |
tool.execute.before |
tools/pre-execute |
tool:after |
PostToolUse / PostToolUseFailure |
PostToolUse |
tool.execute.after |
tools/post-execute |
turn:stop |
Stop |
Stop |
session.idle |
agent/turn-stopping |
subagent:start |
SubagentStart |
SubagentStart |
tool.execute.before.subagent |
subagent/start |
subagent:end |
SubagentStop |
SubagentStop |
tool.execute.after.subagent |
subagent/end |
notice |
Notification |
Notification |
notification |
manual / stdio |
compact:before |
PreCompact |
— | experimental.session.compacting |
manual / stdio |
Full semantics (blockability, matcher rules, payload fields): docs/EVENTS.md.
session_id, transcript_path, cwd, hook_event_name, permission_mode + event fields (tool_name/tool_input/tool_use_id/tool_response/prompt/source ...)0 = allow (when stdout is JSON, the decision is parsed from it); 2 = block (stderr is the reason); any other non-zero = non-blocking errordecision, continue/stopReason, systemMessage, hookSpecificOutput.permissionDecision (allow/deny/ask), additionalContext, updatedInput; oracle answers {ok: true|false, reason}deny > ask > allow; any continue:false stops; context accumulates in hook ordernode --test
(The default test-discovery mode runs all 111 tests; helper scripts live in test-support/ and are not mistaken for tests.)
Released under the MIT License.
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。