dsh-context-annotations
English · 中文
A DSH (DeepSeek Harness) Web GUI plugin: select any text in the conversation, write a note about it, and let the whole batch ride along with your next message — for that one round only.
This repository carries the dsh-plugin topic, which the DeepSeek Harness documentation requires of plugin repositories so they can be discovered; the link above is the inline, clickable form of that requirement.
What it does
- Select → annotate. Select text anywhere in the conversation (a reply, a tool card, your own message), and a floating 注释 button appears beside the selection. Click it, type the note, press Enter. Shift+Enter breaks the line, Escape cancels.
- Collect several. Every saved note becomes a numbered marker at the left edge of the passage it is about — hovering a marker shows that note — and a N 条注释 strip appears above the composer. Click the strip to open the pending batch, where each annotation shows its quoted passage and note with edit and delete actions. Clicking a marker opens that note's editor, with a delete button.
- Send together. Your message gains one short “💬 N 条注释” bubble; the annotation text never appears in the conversation. Hover that bubble to see exactly which passages the batch quoted and what each note says. The list reaches the model as a system-prompt section, so the request still carries every quoted passage and note. The batch is consumed once and its markers stay in the transcript, dimmed.
- Send alone. With an empty composer, the strip's send button starts a round from the annotations alone.
How it works
The browser half owns the editing UI; the Host half owns delivery.
- The browser posts the complete batch to
/api/dsh-context-annotations(a route registered throughctx.connection.fetch, behind the carrier's trust fence). It never writes into the draft. - The Host keeps that batch per Session and listens on the
agent/pre-stepwaterfall.pre-stephands over exactly the messages the step claimed — never replayed history — so a batch can only ride one round. When the step carries asource.kind === "user"message, the Host appends one extra message that holds only the marker (💬 N 条注释) and leaves the human message byte-for-byte unchanged. - The annotation text does not travel in a message:
ctx.systemPrompt.section()contributes it to the prompt, and the rendered prompt is logged as a hiddensystem/messagenode. The conversation therefore shows one short bubble while the request carries the full list; both are reconstructable from the log, and no plugin event type is invented. - The marker message is written before the step records its messages, so it is
part of an ordinary
user/messagerecord, and the prompt is likewise reconstructable from the log. By design, fork, resume and replay should reproduce the conversation the model saw; no plugin event type is invented. - An empty composer cannot submit on its own, so the strip's send button writes a zero-width draft; that round is one empty human message plus the marker.
- The batch outlives only the turn it rode:
pre-stepmarks it consumed (the browser then clears the strip) and that turn'sturn/endretires it from the prompt, so a later round never inherits it. A batch that no round consumed is left pending instead — a note written while the model is still answering waits for your next message rather than vanishing with that turn. - After a send the browser reconciles with one cheap request: the Host states
consumedexplicitly, which marks the local annotations sent (the strip collapses and the markers stay, dimmed). The same request adopts a still-pending batch after a page reload.
What the conversation shows vs what the model receives
The conversation gains exactly one short message:
💬 2 条注释
The model receives the list as a system-prompt section:
1. 所选文本:
<the selected passage>
注释:<your note>
2. 所选文本:
<the selected passage>
注释:<your note>
Files
| Path | Role |
|---|---|
lib/index.js |
Host half: batch store, /api/dsh-context-annotations, agent/pre-step fold |
lib/client.js |
Browser half: selection button, note editor, numbered markers, composer strip and panel |
cordis.patch.yml |
Insert the plugin row into the profile |
locale/{zh,en}.json |
Plugin Manager title and description |
test/smoke.mjs |
Dependency-free test of the fold semantics and the browser bundle's registration |
test/client.mjs |
Regression test for the browser store: per-Session push, explicit consumption, editor identity, composer exclusion |
Configuration
The bundle currently reads no configuration. The folded list carries no marker line, so it reads as the user's own numbered notes rather than a plugin block.
Install
Installed from this repository:
plugin_manager install_bundle target="github:YubaiLoving/dsh-context-annotations"
Or from a local checkout of this directory:
plugin_manager install_bundle (target: this directory)
Limits
- Markers key on character offsets into the flattened conversation text and are validated against the recorded passage; when that offset no longer matches (the content above the passage changed), a whitespace-insensitive passage search takes over, disambiguated by the passage's occurrence ordinal. There is no DOM path fallback. A passage that is re-rendered with different wording loses its marker; the note itself stays in the strip and is still sent. Markers that would land on the same box are nudged upwards so they do not stack.
- Annotations live in Host memory per Session. A Host restart drops a pending batch (the browser adopts only what the Host still holds).
- The float, strip, and panel read documented
data-*markers of the shipped conversation shell ([data-conversation-content],[data-composer-seat],[data-composer-card]) and the browser's own selection. No other plugin's DOM is modified.
License
MIT
No comments yet. Be the first to write one.