dsh-plugin-annotation
Annotate passages in a DeepSeek Harness conversation: select text, add a comment, and the annotation rides your next turn as hidden context. Annotations persist per session, so switching conversations does not lose them.
What it does
- Select text anywhere in the conversation.
- A Comment pill appears beside the selection; click it.
- Type an optional comment and press Enter to stage it, or Ctrl+Enter to send only that annotation immediately.
Staged annotations stay out of the composer draft. They collect in a small count pill above the composer:
[ 💬 2 annotations (×) ]
Hover the pill to open the stack. The panel lists each annotation with its quoted passage, your comment, and edit/remove actions:
2 annotations [Send all]
1 SELECTED TEXT
"remove"
USER COMMENT
are you sure?
2 SELECTED TEXT
"the official 0.4.10 installer"
USER COMMENT
no comment
- Each annotation carries a numbered pin over the passage it came from; the passage stays highlighted.
- Hovering a row or its pin paints that passage with a stronger highlight.
- The panel is resizable vertically and matches the composer width.
- The × button appears when you hover the count pill and clears the whole stack.
- A blank comment is allowed: the selection alone is staged, so it can stand as context or agreement for the prompt you are about to write.
- Ctrl+Enter sends only the annotation you are drafting, even when other annotations are already staged.
- Annotations are stored per session and reload when you return to a conversation. Pins are re-anchored by finding the quoted passage again.
- Comments support bullet and numbered lists:
-and1.continue on Enter,Tabindents, and Enter on an empty item ends the list.
Annotations never enter your text field. They are held by the plugin's host half and attached to the next turn as a hidden context message, so the model sees the quoted passage and your comment while the conversation shows only your own message:
(shown) please also check the checksum
(hidden) The user annotated passages of the conversation. …
1. Quoted passage:
remove
User comment: are you sure?
Press the composer's normal Send button (or Enter) and the staged stack goes with it. With annotations pending and an empty composer, the send button stays enabled because the plugin keeps an invisible token in the draft; the host strips it before the model step.
Keyboard
| Where | Key | Effect |
|---|---|---|
| Comment card | Enter |
Stage in the stack (continues a list item when the line is one) |
| Comment card | Ctrl/Cmd+Enter |
Send only this annotation now |
| Comment card | Shift+Enter |
New line |
| Comment card | Tab / Shift+Tab |
Indent / outdent a list item |
| Comment card | Esc |
Cancel |
| Count pill | Hover | Open the stack |
| Count pill | × (on hover) |
Clear the stack |
| Stack panel | Send all |
Send the stack plus the draft |
Install
From a plugin market
Add this index source in Plugins → Discover → Add index source:
https://samuelraj-game.github.io/dsh-plugin-annotation/index.json
(Discover needs the experimental Custom plugin markets bundle switched on in Plugins → More.)
From npm
dsh plugin install dsh-plugin-annotation
Or add the bundle in the app: Plugins → Installed → Add bundle, then pick dsh-plugin-annotation.
As a profile bundle
This package is both a bundle and a plugin. The bundle patch (cordis.patch.yml) inserts its own row:
- insert:
- id: ui-annotation
name: dsh-plugin-annotation
Adding dsh-plugin-annotation to the profile's dsh.profile.bundles (and installing the package) is enough — no hand-editing of cordis.patch.yml.
How it works
- The browser half registers one entry in the
conversation.input.overlayslot, which the shell mounts once per Session, so it is available in every conversation. - Selections are tracked with
mouseup/keyup/selectionchange; selections inside editable surfaces (the composer, inputs) are ignored. - The host half owns the pending annotations: a per-session store behind six loopback routes under
/dsh-plugin-annotation, each mutation requiring thex-dsh-plugin-annotationheader. - On the next
agent/pre-step, the host appends a separate hiddenuser/messagewhosesource.kindisannotation, then drains the store. The direct user message is left untouched and the raw block never renders, because Chat hides ordinary context nodes. - Quote-only sends stay possible through an invisible draft token (
U+2063): the client writes it so the native send button enables, and the host strips it before the model step, substituting(annotation)if the message becomes empty. - Annotated ranges are painted with the CSS Custom Highlight API (
::highlight(dsh-annotation)), and the hovered annotation gets a second, stronger highlight; numbered pins re-measure on scroll, resize, and a light interval. - Because only the quote text survives a session switch, restored annotations are re-anchored by locating that text in the rendered transcript; a passage that is gone simply gets no pin.
- Styles use the shell's design tokens (
--dsw-alias-*), so light and dark themes both look native.
Compatibility and permissions
| DSH | >= 0.2.1-rc.5 (declared in engines.dsh) |
| Profile | a base-backed web or Desktop profile that mounts @deepseek-ai/dsh-client-ui-conversation and @deepseek-ai/dsh-host-webserver |
| Node.js | DSH's own — the host half uses only node: built-ins (Buffer), no dependencies |
| OS | any platform the DSH client runs on (Windows, macOS, Linux) |
Permissions: no DSH permission category is used. No filesystem access, no outbound network, no command execution, no credential access, no MCP or host APIs.
- Host reads/writes: one in-memory
Mapkeyed by session id. Nothing is written to disk. - Host routes: six routes under the
/dsh-plugin-annotationprefix on DSH's own loopback web server. Mutations require thex-dsh-plugin-annotationrequest header and are rejected otherwise. - Client reads: the browser's text selection and the composer draft state already supplied to UI plugins.
- Client writes: the composer draft through the public
inputActionsface, one<style>element, and its own fixed-position overlay elements. - Dependencies: none. No
dependencies, no runtimedevDependencies, nopreinstall/install/postinstall/preparescripts, no native binaries, no dynamic code loading. - Build: the shipped
lib/client.jsis the artifact; consuming the package needs no build step.
Main risks
- The injected context message is persisted in conversation history, so annotations become part of the session log once sent.
- Pending annotations are volatile: they live in host memory and are lost on a Host restart.
- The plugin observes text selection document-wide; selections inside editable surfaces and its own UI are ignored.
- The invisible draft token (
U+2063) is written into the composer when annotations are pending and the composer is empty. The host strips it before the model step; if a send fails mid-flight the token can remain in the draft, where it is invisible and harmless but present.
Known limits
- Pins follow the live DOM range. If the transcript re-renders and detaches the range (for example a streaming message that replaces its nodes), the pin disappears while the annotation stays in the stack.
- Pending annotations are kept in Host memory: a Host restart clears them. They survive page reloads and session switches.
- A restored annotation is re-anchored by matching its quote text in the rendered transcript. If the passage was edited, compacted away, or is not currently rendered, the annotation returns without a pin.
Releasing
node scripts/release.mjs patch # 0.4.0 -> 0.4.1
node scripts/release.mjs minor # 0.4.0 -> 0.5.0
node scripts/release.mjs 0.4.2 # explicit version
One command bumps package.json, commits the release, pins that commit in docs/index.json, commits the index, and pushes. Add --dry-run to see the plan without touching anything. It requires a clean working tree on main, and it does not publish to npm — the market installs straight from the pinned commit.
Development
The repository is the published package; lib/client.js is hand-authored in the bundle format the client module system expects:
window.__ModuleLoader__.load({ id: "dsh-plugin-annotation", factory: (require) => { /* ... */ } });
After changing the client half, copy lib/client.js into <profile>/node_modules/dsh-plugin-annotation/lib/ and reload the app. Host-half changes need a profile restart, because its routes and the agent/pre-step listener are registered at Host startup.
npm run check # node --check both halves, then run the host tests
docs/ holds the plugin-market index (index.json + icons/), published with GitHub Pages (Settings → Pages → Deploy from a branch → main / /docs).
License
MIT
No comments yet. Be the first to write one.