Patch-style file editing: one apply_patch tool accepting git/unified diff (default) and Codex apply_patch syntax (opt-in), applying every change all-or-nothing after a full read-only verification pass, with sandbox-aware delete and move and a dry-run mode.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-patch-edit-plus
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:drscrewdriver/dsh-patch-edit-plus
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
English | 简体中文 | 日本語 | 한국어 | Français | Deutsch | Italiano | Русский | Español
Patch-style file editing for DeepSeek Harness (DSH): one model-facing apply_patch tool that accepts git/unified diff (default) and Codex apply_patch syntax (opt-in), applies every change all-or-nothing, and stays completely out of DSH's way.
DSH version compatibility
| DSH version | Status | Notes |
|---|---|---|
0.2.0-rc.1+ |
✅ supported (this line, v0.3.0+) | Declarative settings: allowCodexPatch is marked .volatile() — the settings form renders automatically and loader/volatile-update re-registers the tool without a remount. No registration call. Delete/Move migrated to the ShellExecutor.execute() + result() foreground API. |
0.1.7-rc.1+ |
↗ maintenance line | Served by the compat/0.1.7 branch / dsh-0.1.7 dist-tag (v0.2.1 there). 0.2.0's shell executor renamed run → execute (introduced between 0.1.2 and 0.1.7), so one codebase cannot serve both lines type-safely. |
0.1.2-rc.1 ~ 0.1.5-rc.2 |
↗ maintenance line | Served by the pre-0.1.7 branch / dsh-0.1.5 dist-tag. 0.1.7 removed the imperative settings APIs the dual-API fallback relied on. |
Settings are declarative on this line: fields marked .volatile() in Config become the settings form; the plugin subscribes to loader/volatile-update instead of receiving registration hooks.
Why patch at all? (tool routing guide)
The tool description explicitly routes the model:
- Single small change in one already-read file → prefer the native
edittool (a patch costs more tokens than a literal edit). - Small change inside a LARGE file →
apply_patch(no need to echo the whole file). - Batched changes across MANY files →
apply_patch(one call, verified atomically). - New file with substantial content / delete / rename →
apply_patch.
Installation
dsh plugin --profile web add <path-to-dsh-patch-edit-plus>
dsh web --dump-config # verify the plugin row appears
# restart DSH
Usage
Unified diff (default, enabled)
--- a/src/app.ts
+++ b/src/app.ts
@@ -10,4 +10,4 @@ export function main() {
init()
- start(oldPort)
+ start(newPort)
await shutdown()
}
Codex apply_patch syntax (opt-in)
*** Begin Patch
*** Update File: src/app.ts
@@
init()
- start(oldPort)
+ start(newPort)
*** Add File: docs/notes.md
+# Notes
*** Delete File: tmp/junk.txt
*** Move to: src/app.ts (via `*** Move to:` after *** Update File:)
*** End Patch
Enable with allowCodexPatch: true. When a Codex patch arrives while disabled, the tool returns an actionable hint (how to enable or how to retry in unified diff) instead of a generic parse error — Codex syntax is a strong prior for GPT-family models and a silent failure traps them in retry loops.
Operations
| Operation | Unified diff | Codex | Execution path |
|---|---|---|---|
| Add | new file mode + /dev/null |
*** Add File: |
ctx.fs.writeText (official intent dance) |
| Update (multi-hunk) | @@ hunks |
@@ hunks |
ctx.fs.writeText |
| Delete | deleted file mode + /dev/null |
*** Delete File: |
ctx.shell (sandbox-aware, env-passed path) |
| Move / rename | rename from/to |
*** Move to: |
ctx.shell |
Guarantees
- All-or-nothing: the whole patch is verified against current file content first (context location, workspace containment, symlink policy, duplicate paths); any failure aborts with zero bytes written.
- Read-before-write gate respected: every write replicates the official
writetool's intent dance —fs/write-intentwaterfall → guardedwriteText→fs/observedemit. A barewriteTextwould silently bypass the gate because DSH providers never dispatchfs/*events themselves. - Precise failure diagnostics: on hunk mismatch the error carries file + hunk index, search origin line, expected-line preview (whitespace made visible), the file's actual excerpt, and a targeted hint.
- Newline fidelity: CRLF files are written back as CRLF; no-trailing-newline tails are preserved unless the patch changes the tail.
- Pure addition: the plugin registers exactly one tool, never calls
tools.restrict(), never overrides a native tool, registers no global services, and removes everything on unload. Name conflicts with otherapply_patchproviders (bainianlaoyao/dsh-codex-mode,shuind/dsh-codex-harness, …) are resolved by rename avoidance (apply_patch_1,…_2, …) so DSH never fails to start.
Configuration
| Option | Default | Description |
|---|---|---|
toolName |
apply_patch |
Model-facing tool name. |
conflictPolicy |
rename |
rename / skip / fail when the tool name is taken. |
renameSuffix |
_1 |
Suffix used by rename avoidance. |
allowUnifiedDiff |
true |
Accept git/unified diffs. |
allowCodexPatch |
false |
Accept Codex apply_patch syntax. Also editable in the DSH settings panel; changes take effect immediately, no restart needed. |
deleteBackend |
shell |
shell or none (Delete/Move return a structured error). |
shellDialect |
auto |
auto (pwsh on win32) / posix / pwsh. |
deleteCommand / moveCommand |
built-in | Custom command templates. Paths still arrive only via env (DSH_PATCH_TARGET / DSH_PATCH_SOURCE); never interpolate them into the command string. |
dryRunByDefault |
false |
Treat calls as dry runs unless dryRun: false. |
followSymlinks |
false |
Allow patch paths to traverse symlinks. |
maxFiles |
50 |
Max file sections per patch. |
maxPatchBytes |
524288 |
Max patch text size in bytes. |
maxDiffBytes |
16384 |
Max per-file diff kept in the result metadata (larger diffs are emptied and marked truncated). |
Limitations (disclosed)
- Delete/Move run through
ctx.shell. The sandbox strength is whatever the loaded shell executor enforces (bash-sandboxfences;bash-localdoes not) — the same risk posture as the native bash tool. Every request carries asandboxPolicyand reports sandbox facts so "policy denied" is distinguishable from "command failed". - Every write and every Delete/Move carries the session-scoped policy.
apply_patchresolvesctx.sandboxPolicy.resolve({ session })per call — the session's mode override plus its cwd as the workspace root — exactly like the nativewrite/edittools, and resolves plan paths against that same root. Without it the enforcing filesystem falls back to the deployment root, which makes in-workspace writes fail asworkspace-writedenials even in adanger-full-accesssession. Aworkspace-writedenial surfaces as aPatchErrorwhose message carries the backend text (the structured[sandbox: …]marker and the same-turn escalation fields are not implemented; use the nativewrite/edittools when you need to escalate). - Add does not create parent directories. This matches the native
writetool (ctx.fshas no mkdir); the error names the missing directory. - No fuzzy/offset matching: hunk location is exact →
trimEnd→trim, deliberately (fuzzy matching is unsafe for destructive ops; it is on the roadmap). - Binary patches are rejected with a clear error.
Development
npm install
npm run typecheck # against 0.2.0-rc.1 peers (devDependencies)
npm run typecheck:0.2.0 # against real 0.2.0-rc.1 peer packages installed from the registry (dual-baseline proof)
npm test # vitest, 92 tests
npm run lint
npm run build # lib/
npm run verify:source # static safety assertions (intent dance, no node:fs, …)
npm run smoke # load-level smoke against a stub host
Manual verification on a live DSH (dsh plugin --profile web add → restart → call the tool in a session) is intentionally left to the operator.
License
MIT
Links
More in this category
DayuanJiang/next-ai-draw-io#dsh-plugin★ 36548
Draws draw.io diagrams in DeepSeek Harness through the next-ai-drawio MCP server: the link in the reply opens a live preview in the right sidebar, you can edit the diagram by hand, and each AI change is a version you can restore. Node.js 18+; npx downloads the MCP server on first use.
Tencent/WeKnora#dsh-weknora★ 36201
Four read-only tools over a WeKnora knowledge base: list knowledge bases, hybrid passage search, reassemble one document's chunks in order, and WeKnora's own cited RAG or ReAct-agent answer with a resumable session id.
superdesigndev/treg★ 6962
Tool catalog for agents: search ~2,600 external endpoints (SEO and SERP, backlinks, social, people and company enrichment, ad libraries, scraping) by the task you want done, read each one's parameters and per-call price, then call it with the credential injected server-side. Ships the skill plus an MCP row that stays disabled until TREG_TOKEN is set.
T-Auto/dsh-ops★ 5011
Provides bash, PowerShell 7, and high-performance Rust tools for DSH on Windows to reduce token usage.
sixtysevenlf/dsh-blender-plugin★ 1536
Lets a DSH agent drive a local Blender over a direct TCP channel with 15 tools: viewport frames, custom-angle renders, Python execution, headless and job offload, transaction rollback, and a planning layer of 228 ops across 34 families.
TencentCloudBase/CloudBase-AI-Toolkit#dsh-plugin★ 1153
Tencent CloudBase backend for DeepSeek Harness — scaffold and deploy full-stack apps from chat, render query results as table cards with paging, sorting and CSV export, preview a deployment on its domain, and call the CloudBase MCP toolset (`mcp__cloudbase__*`) with device-code login.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.