DSH Web browser annotation plugin: mark selected assistant reply text with labels, press Enter to send the message to the model, and replies respond to each numbered item sequentially.
$ dsh plugin --profile web add github:omdsh-dev/dsh-annotationRun the command above in your terminal to install this plugin via the dsh CLI. You can switch Profile in the top-right corner. New to dsh? Read the beginner tutorial
Adds annotation capability for assistant replies in DSH Web—select any text in assistant messages, write a note (or leave empty for marking only), press Enter, and the annotation list will be sent to the model together with your question in the input box. The model responds by annotation number, and each "Annotation N:" in the reply can be hovered to view the original text and annotation.
exports.inject = ['sessions','conversation']Suitable for users who need the model to perform refined questioning or correction on specific paragraphs in assistant replies: for example, having the model explain specific lines in long answers paragraph by paragraph, asking the model to modify certain paragraph phrasing, or giving different instructions for multiple sections. Regular one-question-one-answer doesn't need this—the main use case is "pointing at some text to chat with the model".
| Dependency | Min Version | Notes |
|---|---|---|
| DSH | Not declared | package.json doesn't declare DSH version, only declares cordis ^4.0.0-rc.7 |
| Node | >=20 | Only affects build of Node-side empty implementation (tsc compilation), browser-side runtime doesn't depend on Node |
| Platform | Cross-platform | All capabilities run in browser (client.js), platform-agnostic |
| Native modules | None | No native dependencies introduced, pure DOM + Cordis service subscription |
dsh plugin --profile web add github:omdsh-dev/dsh-annotation
This plugin requires no additional configuration. All behaviors (numbering style, response format, separator marker "Question:", history message self-repair, etc.) are built-in defaults—no Schema/options fields exposed in the source code.
Q: Do I need to restart DSH Web after installation?
A: Restarting is recommended. The installation command only registers the bundle, but client.js is served to the browser with no-cache per request; on macOS, the official recommendation is launchctl kickstart -k "gui/$(id -u)/com.dsh.web", other platforms restart according to your DSH Web service management method.
Q: How to confirm the plugin installed successfully?
A: Run two self-check commands: dsh --profile web --dump-config | rg "id: dsh-annotation" must output exactly 1 line (to avoid duplicate insert with profile/home's cordis.patch.yml); curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3080/plugins/@omdsh-dev/dsh-annotation/client.js should return 200.
Q: Can I add multiple annotations to the same text? Or can I edit the note after annotating?
A: The same selected text region is judged as the same annotation; duplicate operations enter edit mode to update the existing note; to add a new annotation, just select a different position. Saved annotations can be removed one by one via the delete button in the "Annotations ×N" tag next to the input box.
Q: If I switch to another conversation, will annotations carry over to the new conversation?
A: No. When subscribing to sessions.list detects a conversation switch, the plugin actively clears the collected annotation set to avoid cross-session contamination.
Q: Will it conflict with DSH's built-in citation feature?
A: No. The plugin doesn't depend on any send completion event chain, nor does it modify the composer textarea DOM; it only intercepts Enter at the capture stage, splices the annotation block using setDraft right before submission (not overwriting user draft), then lets the composer's own submission logic continue.
Q: Can it be used together with focus view plugins like dsh-focus-chat?
A: Yes. The plugin recognizes both DSH main view (assistant rows with data-time-hover-root) and focus-chat focus view (containers with data-focus-flow and class containing assistant); selection annotation, reply chips, and scroll repositioning all work in both views.
Beginner — one command to install, no configuration; just understand the interaction flow "select → toolbar annotate → Enter to send", no need to understand Cordis or DSH internals.
watchInputDraft cannot subscribe to input draft state when the plugin is just enabled and session list hasn't loaded yet (client.js:1388). The plugin's fallback strategy is MutationObserver + 1s polling (client.js:1747-1750), so the annotation bubble hide logic still works, but in extreme first-launch scenarios, the first send completion event might be missed and will self-repair in the next polling round.tag.__annotationItems on user messages (client.js:1500-1507).English · 简体中文
Selection-annotation plugin for DSH Web: select text → annotate → press Enter to send it along with your message; the model replies to each annotation by number.
Select any text in an assistant reply to annotate it (the annotation body may be left empty = just mark the passage). Annotations accumulate across messages and turns. An Annotations ×N chip appears next to the input box — hover to view all annotations, remove them one by one. Press Enter and the annotation block goes to the model together with whatever question is in the input box. The annotation block never shows up as text in your own message bubble — only the question plus the chip (content visible on hover; hidden before paint, zero flicker). The model replies with Annotation 1: … … Annotation N: …, one per annotation, and every Annotation label in the reply is a hoverable chip showing the annotated passage and your note.
Form: official bundle plugin (dsh.bundle + a dsh.client declaration in package.json, injected into the browser via client-modules; the Node half is an empty implementation). Zero core changes — no DSH files are touched; cordis.patch.yml only inserts its own id once, and the profile patch stays [].
| Feature | Description |
|---|---|
| Select-to-annotate | Select assistant text → toolbar "Annotate" → write your note (may be empty); dismiss by clicking elsewhere or pressing Esc |
| Numbered marker + highlight | A blue numbered marker + highlight anchored to the passage, viewport-anchored with collision avoidance, never lost when scrolled out of view |
| Cross-turn collection | Any number of annotations accumulate across messages/turns, numbered from 1 |
| "Annotations ×N" chip | Small chip beside the input box; hover shows every annotation, deletable individually |
| Enter sends with your message | Annotation block + the question in the input box are sent to the model together (the model receives the full content) |
| Hidden in your bubble | The annotation block is removed from your bubble's DOM the moment you send (before the browser paints), leaving only the question + the chip (hover to view); historical messages self-heal after a refresh |
| Numbered reply correspondence | A format instruction is injected into the message so the model replies Annotation 1: … … Annotation N: … one by one |
| Reply annotation chips | Annotation N: in the reply renders as hoverable chips showing the passage + your note |
Select assistant text ──▶ Toolbar "Annotate" ──▶ Write note / save empty ──▶ Blue numbered marker + highlight
▲ │
└────────────── any number, accumulate across turns ◀────┘
│
▼
"Annotations ×N" chip beside the input (hover to view / delete)
│
Press Enter
▼
Model receives: annotation block (number + passage + note) + your question
Your bubble: question only + "Annotations ×N" chip (zero flicker)
Model reply: Annotation 1: … Annotation 2: … (hoverable chips)
# Public GitHub install (works without an npm account)
dsh plugin --profile web add git+https://github.com/omdsh-dev/dsh-annotation.git
# local path install (development / debugging)
cd /path/to/dsh-annotation
dsh plugin --profile web add .
# restart the web service — see "Restarting the web service" below
| Do | Don't |
|---|---|
Only dsh plugin add / only write bundles | Never insert the same id again in the profile/home cordis.patch.yml |
Self-check:
dsh --profile web --dump-config | rg "id: dsh-annotation" # must be exactly 1 line
curl -s -o /dev/null -w '%{http_code}\n' "http://127.0.0.1:3080/plugins/@omdsh-dev/dsh-annotation/client.js" # 200
Pick the command for your platform:
# macOS (launchd)
launchctl kickstart -k "gui/$(id -u)/com.dsh.web"
# WSL / Linux with systemd user services
# The unit name may differ by install method; check with:
# systemctl --user list-units | rg dsh
systemctl --user restart dsh-web
Environments without a service manager (e.g. some containers) often need no restart at all: client.js is served per request with no caching, so a hard refresh (Cmd/Ctrl+Shift+R) picks up plugin changes. The self-check commands above are platform-neutral.
Pure browser-side: everything lives in client.js (a hand-written CJS bundle, no build step, served no-cache per request)
Message format (the literal protocol block sent to the model; follows the DSH locale preference — zh or en):
zh: 我批注了以下 N 处内容…\n\n1. 原文\n 批注:…\n\n请用「Annotation 1:…」…\n\n提问:
en: I annotated the following N passage(s)…\n\n1. quote\n Note: …\n\nPlease respond… "Annotation 1: …"…\n\nAsk:
The zh delimiter is 「提问:」(ask:) rather than 「问题:」(question:) — the heading line "回答我的问题:" also contains the latter, and the bubble-hiding surgery would misfire on it; the en delimiter is Ask:. Hiding and reverse-parsing accept both languages plus the legacy 「问题:」 marker.
Bubble hiding: user bubbles are plain-text rendered (a single MessageText node, not markdown); a MutationObserver in the microtask phase (before paint) splits at the last \n提问:, cuts the annotation block, and attaches the chip; a 1 s polling fallback plus historical-message repair after refresh
Reply chips: after streaming settles (data-streaming removed), each Annotation N: is replaced with a hoverable chip; item data is stored on the most recent user message carrying the annotation tag (tag.__annotationItems) and rebuilt after refresh; snapshot the text nodes collected by the TreeWalker before touching the DOM, then replace one by one — replacing a child mid-walk invalidates the walker pointer and only the first node gets processed
Locale-aware: UI copy and the protocol block follow DSH's locale service (zh/en, live switch); historical bubbles stay parseable across languages; missing locale service falls back to zh
IME-safe: the Enter interception carries isComposing / keyCode 229 guards; never hard-edits the composer textarea's DOM; setDraft only assembles the annotation block at the last moment before submit and never clobbers the user's draft
No reliance on send-completion event chains: bubble decoration uses MutationObserver + polling (watchInputDraft can be ineffective before the session is loaded at init; it is only a staging entry)
Focus-chat compatible: works inside the focus conversation view of dsh-focus-chat — assistant rows there are [data-focus-flow] containers with a *_assistant CSS-Modules class (plus data-streaming while running); selection, annotation, reply chips, and re-anchoring all work in the focus tab alongside the main chat view
| Version | Highlights |
|---|---|
| v1.4.x | Locale-aware: zh/en UI copy and annotation protocol block, live switch via DSH locale service |
| v1.3.x | Numbered reply correspondence: format-instruction injection + hoverable Annotation N: chips (TreeWalker snapshot fix) |
| v1.2.x | Hidden annotation block in bubble: MutationObserver microtask zero-flicker + polling fallback + historical-message repair |
| v1.x | Self-contained annotation flow (replaces the v0.9 chip design): capture-Enter assembles the block and sends it with the message |
| v0.9.x | Early chip design (insertReference + slash codec), superseded by v1.x |
MIT