dsh-a11y-announcer
Accessibility plugin for DeepSeek Harness web UI: announces tool calls and finished assistant messages via aria-live for screen readers
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 21, 2026
- Updated
- Aug 25, 2026
Introduction
dsh-a11y-announcer
Accessibility plugin for the DeepSeek Harness web UI. Announces tool calls as
soon as they appear and finished assistant messages through an aria-live
region. NVDA/JAWS/VoiceOver read the live region automatically, without moving
the user's focus.
Behavior
- Tool calls are announced shortly after they appear ("Tool: <name + content>"), once their text is briefly stable — NOT when they finish (the start is the interesting moment). A short debounce (TOOL_SETTLE_MS=400ms) avoids reading the very first streaming fragment (e.g. a half token like "ulti" from "multi-...").
- The last finished assistant message is announced with its full text over the live region (not while streaming).
- Only the visible answer text is read; the agent's internal reasoning ("Think" block) is excluded so streamed reasoning fragments are not read aloud.
- Deduplication via
data-chat-anchor-key: each element is announced only once; re-renders do not trigger a new announcement. - Assistant settle is detected via the
data-streamingattribute (dsh removes it once streaming finishes), so only complete text is announced. - No replay: nodes already present when loading an old session are not walked through, only new events from now on.
Deep-diving announcement
- While the dsh "Deep diving..." status (agent working) is active, "Deep diving..." is announced every 5 seconds over the live region for as long as the state persists (first announcement after 5s, then every 5s).
- Detection by content: a
role="status"element whose text contains "Deep diving". IMPORTANT: dsh may additionally render an EMPTYrole="status"element; therefore match on text content, not on the first role=status. - Fallback: in addition to the MutationObserver, a 1s polling loop (for missed mutations).
How it works
- Client plugin, discovered via
dsh.clientmetadata (platform: web) and activated as its own bundle (dsh.bundle.patch= cordis.patch.yml with- insert) in the web profile. lib/index.js= plain host loader entry (apply/inject), NOwindow.__ModuleLoader__wrapper (server-side, otherwise "window is not defined" and the plugin is not activated).lib/client.js= browser bundle (ModuleLoader wrapper). Uses a MutationObserver on the[data-slot="conversation.session"]container and selects targets viadata-chat-flow-kind/data-chat-anchor-key.- Assistant settle detection via the
data-streamingattribute: dsh renders the assistant markdown root withdata-streamingwhile it streams and REMOVES it once the full text is settled. The plugin observes attribute changes (attributes+attributeFilter: ["data-streaming"]) and only announces when the node is no longer streaming — so it never reads partial output. - Tool calls are announced after a short debounce (TOOL_SETTLE_MS=400ms) once their text is briefly stable, without waiting for completion.
- Text extraction: clones the node and REMOVES the agent's internal reasoning
("Think" block,
data-variant="think") before readingtextContent. This keeps the hidden, streamed reasoning fragments from being read aloud — only the visible answer text is announced. It also strips known model-artifact lines (a bare "ulti" the LLM occasionally appends). For tool calls,textContentalso captures collapsed/hidden content (whichinnerTextwould omit). - Announcement: a hidden (but assistive-visible)
divwitharia-live="polite"+role="status";textContentis set when settled. Noelement.focus()(moves the user's focus unnecessarily and did not reliably read elements).
Installation
- Package: this repository (e.g. cloned to
/opt/dsh/plugins/dsh-a11y-announcer/). - Add as a file dependency in
/opt/dsh/package.jsonand/mnt/ssd/dsh/profiles/web/package.json. - Add as a bundle in
dsh.profile.bundles(profiles/web/package.json). - After changes to
lib/client.js: restartdsh web(no bundle build needed, the file is served directly).
Files
lib/client.js— browser bundle (MutationObserver + aria-live announcement)lib/index.js— host loader entry (empty)cordis.patch.yml—- insertof the loader entrypackage.json—dsh.client+dsh.bundle+exports["./client"]
Tests
Logic verified in jsdom (see development test cases):
- A tool call is announced shortly after appearing ("Tool: ") with its full text, without waiting for completion; streaming fragments (e.g. "ulti" from "multi-...") are not announced thanks to the short debounce.
- A finished assistant is only announced after text stability (900ms) with the full text, not while streaming.
- The live region has
aria-live="polite". - Deduplication via
data-chat-anchor-keyworks.