Files
omp-edit/README.md
T

7.4 KiB

omp-edit

oh-my-pi-style editing for the DeepSeek Harness (DSH). It replaces DSH's built-in edit tool with oh-my-pi's hashline patch language. It also wraps read and grep so their output carries the content-hash tags the patches anchor on.

The model points at line numbers of a tagged snapshot instead of retyping the old text. That avoids whitespace-mismatch "string not found" loops and saves output tokens. A stale snapshot is detected before anything is written, and is rebased automatically when the anchored lines survived.

What the agent sees

read output carries a [PATH#TAG] header and LINE:TEXT rows (the 4 hex characters are a hash of the whole file):

<path>[src/app.py#1A2B]</path>
1:def greet(name):
2:    print("Hello, " + name)

grep groups matches under a tagged header per file. A grep hit counts as having read the file, so it can be edited straight away:

[src/app.py#1A2B]
2:    print("Hello, " + name)

edit takes one input string with one or more file sections:

[src/app.py#1A2B]
PUT 2.=2:
+    print(f"Hi, {name}")

It returns the fresh tag plus a compact numbered preview of the changed region, so the agent can chain edits without re-reading. The Web UI shows the usual diff card.

Hashline operations

Form Effect
PUT N.=M: + +rows Replace original lines N..M (N.=N for one line)
PUT N*: + +rows Replace the syntactic block opening at line N (function/class/object, Markdown heading section, Python/YAML indented block; leading decorators included)
PUT <N: / PUT >N: Insert before / after line N (<1 = file head, >$ = file tail)
PUT >N*: Insert after the block opening at N
CUT N.=M [@name] / CUT N* [@name] Delete and capture (anonymous register, or a named one)
PUT <N [@name] / PUT >N [@name] Paste a register at a gap
PUT N.=M @name / PUT N* @name Paste a named register over a range/block
REM Delete the file
MV DEST Move the file after the section's edits

The rules match oh-my-pi:

  • Body rows are +TEXT; a lone + is a blank line; literal -x is written +-x.
  • All numbers refer to the tagged snapshot, never shifted by earlier hunks.
  • Overlapping ranges are rejected.
  • Named registers persist for the session, and cross-file moves work (CUT 1* @fn in one section, PUT <1 @fn in another).
  • The whole call is validated before anything is written.
  • Tolerant parsing: an optional *** Begin/End Patch envelope and markdown fences are stripped, and PUT 3-5: / PUT 3: are accepted with a warning.

Stale tags

Every tagged read/grep/edit records a snapshot. When the file changed since the tag (another tool, a formatter, the user):

  • if every anchored line still exists unchanged, with a neighbour that moved by the same offset, the patch is remapped onto the current file and applied. A warning says so.
  • otherwise the edit is rejected without writing, with a message to re-read.

A tag that was never issued for that file (e.g. copied from another file) is rejected with a hint.

Edit modes

mode picks the wire format of the replacement edit tool:

Mode Input Notes
hashline (default) input Described above
replace file_path, old_string, new_string, replace_all, or edits: [{old_string,new_string,replace_all}] Fuzzy whitespace matching: exact → indentation/trailing-space-insensitive → whitespace-collapsed → smart quotes/dashes → ≥95% similarity. The file's indentation is carried over. Batched edits are atomic
sloppy input *** Edit File: / *** Find / *** Replace · *** Insert Before · *** Insert After, … captures, all suffix
apply_patch input Codex format: *** Add/Delete/Update File, *** Move to, @@ scoped hunks, *** End of File

Per-model selection, like oh-my-pi:

  1. modelVariants (substring of provider/model → mode), first match wins.
  2. mode.
  3. With hashline and strict: false, model families oh-my-pi considers unreliable at hashline fall back to fallbackMode (default replace). The list is fallbackModels, default kimi, mimo, minimax, deepseek, stepfun, codex-spark, glm-5.3-flash.

The tool schema follows the model the agent actually uses and switches mid-session when you change models.

How it replaces the built-ins

DSH registers edit/read/grep in each agent preset. Tools registered in an agent's own scope shadow those, so this plugin listens for agent/created and registers into every agent's scope:

  • edit is replaced outright. Nothing of DSH's edit is used.
  • read and grep delegate to DSH's implementations (sandbox, limits, ripgrep, spill files all unchanged) and only add tags to the output.

DSH's safety rules are kept:

  • Read first: you still need to read (or grep) a file before editing it. This is fs-observation-policy.
  • Guarded writes: writes go through ctx.fs with the version guard.
  • Sandbox: the session's sandbox policy applies to writes and REM.
  • Line endings: CRLF files stay CRLF.

To use DSH's original tools again, disable this plugin (Settings → Plugins → OMP Edit) or set replaceEdit/replaceRead/replaceGrep: false.

Installation

dsh plugin --profile web add git+https://git.rizzy.dev/DSH-Plugins/omp-edit.git

Or in the Web UI, go to Settings → Plugins → Install and paste git+https://git.rizzy.dev/DSH-Plugins/omp-edit.git. You can also ask the agent to install it with plugin_manager → install_bundle.

For development:

git clone https://git.rizzy.dev/DSH-Plugins/omp-edit.git ~/dsh-plugins/omp-edit
dsh plugin --profile web add link:$HOME/dsh-plugins/omp-edit

Then restart DSH (systemctl --user restart dsh or re-run dsh web). New sessions get the new edit tool. Sessions that already exist pick it up once they're re-created, e.g. after the restart.

No dependencies: everything is plain Node.js (≥ 20).

Configuration

Set options in the config of the row in ~/.dsh/profiles/web/cordis.patch.yml:

- id: omp-edit
  config:
    mode: hashline          # hashline | replace | sloppy | apply_patch
    strict: false           # true: never fall back from hashline
    fallbackMode: replace
    fallbackModels: [kimi, mimo, minimax, deepseek, stepfun, codex-spark, glm-5.3-flash]
    modelVariants:          # substring of "provider/model" -> mode
      gpt-5-codex: apply_patch
    replaceEdit: true
    replaceRead: true
    replaceGrep: true
    fuzzy: true             # replace mode fuzzy matching

Tests

npm test     # node --test test/*.test.mjs — 32 tests (engine + integration on the real DSH fs-local backend)

Files

File Role
index.js DSH plugin: per-agent tool registration, mode selection, fs policy, diff cards
lib/hashline.js Hashline parser and applier
lib/block.js N* block resolution (brackets, indentation, Markdown, decorators)
lib/store.js Session snapshot store, Myers line diff, stale-tag rebase
lib/replace.js Fuzzy replace mode
lib/sloppy.js sloppy mode
lib/apply_patch.js apply_patch mode
lib/diff.js Hunk diffs for the UI diff card
lib/prompts.js Model-facing tool descriptions (adapted from oh-my-pi's crates/pi-edit/prompts)

Pairs well with omp-lsp, which appends language-server diagnostics to every edit.