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 through ctx.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-step
waterfall. pre-step hands over exactly the messages the step claimed — never
replayed history — so a batch can only ride one round. When the step carries a
source.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 hidden
system/message node. 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/message record, 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-step marks it consumed (the
browser then clears the strip) and that turn's turn/end retires 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
consumed explicitly, 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