dsh-customisations
One profile plugin that carries small, independent DeepSeek Harness fixes. Instead of a package per workaround, every fix is a module with a stable id, so the bundle stays one install, one patch row, and one health line while the fix list grows.
Today the bundle contains one fix: currency-guard, which stops DSH's single-dollar inline math from swallowing prose.
Install
dsh plugin --profile desktop add github:drm-nz/dsh-customisations
Then restart the app (host plugins load at boot). From a local checkout:
dsh plugin --profile desktop add link:/absolute/path/to/dsh-customisations
Verify the mount in the shared health log:
tail -1 ~/.dsh/health.log
# ... dsh-customisations ok fixes=currency-guard
Fixes
currency-guard
DSH's chat markdown enables micromark inline math with singleDollarTextMath
and no currency guard. A reply that writes money with $ therefore pairs
the first two dollars in a paragraph into one KaTeX span:
NVDA fell to **$187.43**; 53 shares remain at a $229.18 average.
...renders as one math run: spaces collapse (228.21andneverreplaced), prose
turns italic, the closing ** prints as ∗ ∗, and a & inside the span (as in
P&L) makes KaTeX fail and print the whole span source in #cc0000 red.
GenUI's own inline renderer is not involved: its single-$ rule rejects a
closing $ followed by a digit, so the same text renders correctly there.
The general rule
A single $ pair is inline math, so any two unescaped $ outside code in one
Markdown text block become one math span — the first and second pair, the third
and fourth, and so on. Currency is just the most common way to produce them.
Inside that span:
- whitespace collapses and letters turn italic;
**/==stop being Markdown and print literally (∗ ∗);- a bare
&is a TeX alignment character, so KaTeX fails and prints the whole span source in#cc0000red (P&Lis enough); - the span can cross sentences and blank lines inside the block;
- an odd trailing
$has no partner and stays literal.
So the fix has to decide, per dollar pair, whether it is math or prose.
The fix
It wraps the documented llm/stream waterfall (the same seam dsh-llm-retry,
dsh-llm-replay, and dsh-session-checkpoint-policy use) and runs two passes:
- Streaming deltas — escape a currency-shaped
$the moment it arrives, so the live view is correct while the reply streams. - Terminal
block-end— the adapter's full block text is whatBlockAssemblertreats as authoritative, so it gets the general pass: pair every unescaped$outside fenced and inline code, and escape both ends of a pair whose body reads as prose (longer than 80 characters, contains**/==, a blank line, a bare&, or an ordinary word of three or more letters outside a command argument). Real math is left alone.
Markdown renders \$ as a literal $, so the visible reply is unchanged.
- Currency is always escaped, paired or not:
$1,234,$5.,($5),$97,600,,$5k,$1.2M,$0.5%. - Prose pairs are escaped:
$quick brown fox$,−$5,833 … −$4,119. - Real math is preserved:
$x^2$,$2\pi r$,$E = mc^2$,$5$. - Fenced code blocks and inline code are untouched, including shell
$1. - A trailing
$/amount or backtick run is held across deltas, so a token split between chunks is still decided correctly. - Plain-text consumers such as session titles (
purpose: "session-title") are skipped, where a stored\$would show a backslash. - Fail-open: a transform error passes the original chunk through.
Limits: only new assistant text is guarded (history keeps its $), and
user-authored text never passes through llm/stream. A non-currency pair can
flash as math in the live view until the block settles, because only the
terminal pass has the whole block to pair. When DSH's tokenizer grows a currency
guard, remove this fix.
Configuration
- id: customisations
name: dsh-customisations
config:
enabled: true # whole bundle
disabled: [] # fix ids to skip, e.g. [currency-guard]
fixes: # per-fix options, keyed by fix id
currency-guard: {}
Adding a fix
- Add
lib/fixes/<id>.jsexportingidandapply(ctx, config); install the seam it needs there (waterfall listener, slot, instruction, ...). - List it in
lib/fixes/index.js. - Extend
injectinlib/index.jsif the fix consumes another service. - Add a test that the fix installs and that it is skipped by
disabled.
Keep each fix fail-open and idempotent: the bundle is installed once per profile, and one broken fix must not take the others down.
Development
node --test test/
The suite covers the reported reply end-to-end: the unguarded text parses into
three inlineMath nodes (one containing P&L), the guarded text parses into
none, and the only change to the text is a backslash before each currency
dollar.
License
MIT
No comments yet. Be the first to write one.