Two standalone packages, installable as one plugin for the DeepSeek Harness CLI:
| package | role | installed by users? |
|---|---|---|
@hy-sde-org/dsh-hashline |
pure line-anchored edit-patch engine (library) | no — transitive |
@hy-sde-org/dsh-tool-edit |
the plugin: edit tool (replace / patch / apply_patch / hashline) + embedded format-on-write & diagnostics |
yes |
The edit tool is a full parity port of oh-my-pi's coding-agent edit tool
onto the harness tool/filesystem contract (ctx.tools, ctx.fs,
ctx.systemPrompt). It reads through the filesystem before every edit —
its own authoritative read records fs/observed, so a deployment's
observation policy is satisfied in a single call the way omp's ToolSession
was (no separate model-facing read required, and the observed-version CAS
still rejects a concurrent mutation) — and it writes through the
fs/edit-intent waterfall. The hashline snapshot store is shared per agent
session through @hy-sde-org/dsh-hashline's getSessionSnapshotStore, so a
[path#tag] header that a read/search tool minted validates against the
exact content (and lines) the model saw: first-try tags, no re-read cycle.
It also bundles its own LSP client, so formatOnWrite and
diagnosticsOnEdit work on stock DeepSeek Harness deployments with zero
upstream changes (the language server runs via npx typescript-language-server and degrades to edit-only when unavailable).
Why this exists. For coding agents, much of the leverage is in the
harness, not the model — the "harness problem" Stencil So frames
well: the tool surface (how a model reads, edits, and is shown errors) often
bounds reliability more than the model does. This plugin is harness work: a
better edit tool with a built-in language server, shipped as an installable
plugin. The benchmark section records the A/B run that measures
it against the harness's built-in str_replace_editor, so the "it's better"
claim is evidenced, not asserted.
pnpm install --global @deepseek-ai/dshBoth packages are published on the npm registry under the hy-sde-org
organization (@hy-sde-org/dsh-hashline and @hy-sde-org/dsh-tool-edit,
version 0.1.2-rc.1). Install the plugin straight from npm — the registry
resolves the hashline library dependency and the DeepSeek Harness peer
packages automatically, no tarballs, no ordering:
# one command; @hy-sde-org/dsh-hashline comes in as a transitive dependency
dsh plugin --profile web add @hy-sde-org/dsh-tool-editOn official harness releases (rc.8 and later) the bundle's patch seams are
deliberately minimal (see Replacing the built-in editor):
dsh plugin add reconciles the profile, disables the shipped
str_replace_editor tool, and the rich editor is then mounted by adding the
provided agent preset row. Installing the
bundle alone never breaks boot and never claims the edit name on the host
plane.
You can also just depend on the packages from your own tooling as normal npm dependencies:
npm install @hy-sde-org/dsh-tool-edit # or pnpm add / yarn add
npm install @hy-sde-org/dsh-hashline # the engine, if you need it directlyRegistry notes.
latestis0.1.2-rc.1after the next publish. The0.1.2-rc.1bundle uses the minimal-patch posture that boots on official harness releases; the earlier0.1.xreleases ofdsh-tool-editwere published before the rc.7 bundle posture (they mounted a self-contained fs realm that requires theenableEditharness feature and fails boot on stock rc.7/rc.8) — do not install them on an official harness.
Before the registry publish (or when hacking on the repo itself), install
from the built tarballs — and pin the unpublished dependency so pnpm add
doesn't query the registry for @hy-sde-org/dsh-hashline:
git clone git@github.com:hy-sde/dsh-tool-edit.git
cd dsh-tool-edit
pnpm install
pnpm run build
HASHLINE_TGZ="$(cd packages/hashline && pnpm pack --silent --pack-destination /tmp)"
dsh plugin --profile web add "$HASHLINE_TGZ"
# Pin hashline locally so the tool-edit tarball's dependency resolves offline.
printf 'overrides:\n "@hy-sde-org/dsh-hashline": "file:%s/packages/hashline"\n' "$PWD" >> "$DSH_HOME/profiles/web/pnpm-workspace.yaml"
TOOLEDIT_TGZ="$(cd packages/tool-edit && pnpm pack --silent --pack-destination /tmp)"
dsh plugin --profile web add "$TOOLEDIT_TGZ"(The overrides entry is a pre-publication shim; delete it once you're on
the released registry package.)
Install order note: don't try to satisfy the dependency by installing the hashline tarball "first" — pnpm re-resolves the full graph on every
addand will 404 on an unpublished@hy-sde-org/dsh-hashlineunless the override above pins it.
dsh web --dump-config # the tool-str-replace-editor row is patched to disabled: truedsh plugin --profile web remove @hy-sde-org/dsh-tool-edit
dsh plugin --profile web remove @hy-sde-org/dsh-hashline
# remove the preset directory you copied from examples/agent-preset/ as wellThe official harness ships TWO editing surfaces host-wide: the tool-fs
read/write/edit suite and a dedicated Anthropic-style str_replace_editor
tool. On stock releases this plugin replaces them like this:
-
Installing the bundle disables
str_replace_editor— the plugin'scordis.patch.ymlpatches the shippedtool-str-replace-editorrow todisabled: true(re-enable later withdisabled: null). -
The rich
editreplaces the stockeditby agent scope shadowing — harness scoped-tools semantics let a tool registered in the agent's own scope layer shadow a global tool of the same name. Mounting the plugin'stool-editrow in a user agent preset makeseditresolve to the rich editor, whileread/writekeep resolving from the deployment's hosttool-fs. No host-plane bundle row can do this: the officialtool-fshas no way to disable its ownedit(theenableEdit: falsekey exists only in the hy-sde fork), so a secondedit-owner on the host plane fails boot with "prompt sectiontool:editis already registered".
The ready-to-copy preset ships in the package under
examples/agent-preset/ (agent.cordis.yml + preset.yml). Install:
# from the installed package or this repo:
mkdir -p ~/.dsh/.agent-presets/my-edit
cp packages/tool-edit/examples/agent-preset/agent.cordis.yml \
packages/tool-edit/examples/agent-preset/preset.yml \
~/.dsh/.agent-presets/my-edit/
# then select "Edit Mode (rich)" in the Web UI preset picker (or `dsh agent`)The example preset mounts the rich editor beside the LSP seam in an entry-local
realm (isolate: { lsp: true }) so format-on-write and diagnostics-on-edit
work with zero upstream changes, and explicitly omits tool-fs — keeping the
per-session tool-fs row would register the stock edit in the same scope and
fail the preset boot. Copy the other rows you normally use (bash, todo,
ask-user, skills, …) from your current preset next to these; only the
filesystem-editing rows are special. read/write always keep coming from the
deployment's host tool-fs.
On official releases the plugin's cordis.patch.yml is intentionally not
self-contained: a host-plane fs realm cannot own the edit name because the
official tool-fs always registers edit (no enableEdit switch) — every
variant collides at boot. What the patch does instead:
-
tool-str-replace-editor→disabled: true— retires the standalonestr_replace_editortool the harness ships host-wide (matches the hy-sde fork, which deleted the package outright). - No rows inserted — the rich editor is mounted at the agent plane per the
section above, where scoped shadowing makes it THE
edittool.
The hy-sde fork already integrates the rich editor directly in its shipped
presets (code-edit, minimal); there you keep mounting
@hy-sde-org/dsh-tool-edit in a preset beside tool-fs with
enableEdit: false (fork-only key), and the bundle patch above is a harmless
no-op (the str_replace_editor row does not exist there — the loader warns and
continues).
Configure the tool by patching the tool-edit row in your preset by id:
- id: tool-edit
config:
mode: hashline # 'auto' | 'hashline' | 'replace' | 'patch' | 'apply_patch'
formatOnWrite: true
diagnosticsOnEdit: true
lspCommand: npx --yes typescript-language-server --stdio
typescriptNative: {} # opt-in: tsc --lsp --stdio for TS7 workspacesThe single edit tool dispatches on argument shape (or is pinned to one mode
via config.mode; the default is hashline for input payloads, while
replace/patch argument shapes keep routing automatically):
| mode | argument shape | notes |
|---|---|---|
replace |
path + old_string + new_string (replace_all) |
fuzzy whitespace matching by default |
patch |
path + edits[]
|
JSON edit entries |
apply_patch |
input |
Codex/Aider-style patch envelope |
hashline |
input |
line-anchored patch language: tags the model saw (@file) + @@ blocks referencing line numbers and hashes |
- The embedded client spawns
typescript-language-serveron first edit that writes through LSP; the server process is shared for the plugin lifetime and torn down on unmount. -
TypeScript 7 (
typescriptNative, opt-in): a workspace whose TypeScript install has nolib/tsserver.js(TS7+) spawns the nativetsc --lsp --stdiofrom the workspace's own launcher (or thetscresolved on PATH) instead of the wrapper, which cannot drive TS7; classic and undetectable workspaces keeplspCommandunchanged (upstream oh-my-pi530664c8f5). - With
formatOnWrite, edited files are formatted viatextDocument/formattingbefore the write lands. - With
diagnosticsOnEdit,textDocument/publishDiagnosticsare attached to the edit result as anN error(s), M warning(s)summary withline:col [severity] [source] messagelines. - Any failure (no server, spawn error, timeout) degrades to edit-only silently. Never blocks an edit.
pnpm install
pnpm -r check # strict typecheck of both packages
pnpm -r test # hashline 246 tests + tool-edit 23 tests (incl. embedded LSP client)
pnpm -r build # tsc -> dist
bash scripts/release-public.sh --check # pre-publish validation
bash scripts/release-public.sh --publish # publish hashline then tool-editpackages/hashline/ @hy-sde-org/dsh-hashline — the engine (no runtime deps)
packages/tool-edit/ @hy-sde-org/dsh-tool-edit — the plugin (embedds the LSP client)
cordis.patch.yml the installable harness bundle
src/lsp/ embedded client: stdio framing, LSP client, provider,
writethrough (port of oh-my-pi's lsp writethrough)