dsh-composer-input-history
Shell-style input history for the DeepSeek Harness Web composer — the behaviour you know from opencode and other terminal agents.
Press ↑ in the composer to pull back a message you already sent in this Session. Press ↓ to walk forward again; past the newest message your original draft comes back.
What it does
| Gesture | Behaviour |
|---|---|
| ↑ with an empty draft | recalls the newest sent message |
| ↑ again | walks one message older each time |
| ↑ on the first line of a single-line draft | recalls; the draft you were writing is remembered |
| ↑ inside a multi-line draft, below the first line | left to the editor, so the caret still moves |
| ↓ while browsing | walks one message newer |
| ↓ past the newest message | restores the draft you were writing before browsing |
| typing anything while browsing | leaves browse mode; the recalled text becomes your draft |
History is what you sent in this Session, oldest to newest, read from the Chat node store the view itself renders (snapshot.nodes, ordered by anchor sequence): ordinary user messages, steering messages, and the message that woke a turn. Skipped: hidden nodes (compacted or interrupted), attachment-only messages, and everything the Harness authors on your behalf — goal rounds, skill catalogs, runtime snapshots, job notices, model notices, approval answers, and compaction checkpoints.
Why it is a keyboard observer
The Harness composer deliberately leaves ↑/↓ to its shell-owned Lexical editor, so no slot or command seam owns them. This plugin therefore:
- registers an invisible Session-scoped occupant in
conversation.composer.dock; - reads the Session's Chat nodes through the standard
useChathook (snapshot.nodes) and the draft through the standarduseInputhook; - writes the recalled text through the standard
inputActions.setDraftAPI, so the edit stays part of the composer's own draft history; - watches the key on two paths: a document-level
keydownlistener and the shortcuts service'sobserveFixedInputseam. The document listener runs after the composer's own keymap, so a slash menu or an IME that already handled the key still wins; the fixed-input seam covers compositions where a native Desktop adapter owns the DOM feed. Exactly one path acts, because the first one to act cancels the event.
A key is declined when the slash/reference menu already consumed it, when an IME is composing, when a modifier is held, when the event is outside the composer editor, or when the composer is submitting/adjudicating. The plugin imports no Harness Client package and styles nothing.
Install
From this repository (available now):
dsh plugin --profile web add https://github.com/liumorrisclaw/dsh-composer-input-history
Once the package is on npm, the short form is:
dsh plugin --profile web add dsh-composer-input-history
Or add it in the Web UI's Plugins page, or list the package directory with the plugin manager. The bundle ships off until the profile selects it.
For a local checkout, install by path:
dsh plugin --profile web add /absolute/path/to/dsh-composer-input-history
Relationship to other plugins
The npm name dsh-input-history is already taken by a different community plugin that also recalls prompts with ↑/↓. The two differ in mechanism and behaviour:
| dsh-composer-input-history (this plugin) | dsh-input-history | |
|---|---|---|
| History source | the Session transcript the Chat view already loads | a separate local store of submitted prompts |
| Non-empty draft | recalls and remembers the draft, restoring it below the newest message | recalls only from an empty composer |
| Attachments | not part of the recalled text | persists and re-attaches them |
| Menu/IME | yields the key to an open slash/reference menu and to IME composition | — |
Pick this one if you want recall driven by the conversation you can see, including from a draft you are already typing.
Compatibility
Developed and verified against @deepseek-ai/dsh 0.2.0-rc.2. The plugin uses only documented seams: ctx.slots, the Session standard props (useChat, useInput, inputActions), and ctx.shortcuts.observeFixedInput. It declares dsh.client.inject on dsh-client-ui-conversation, dsh-client-ui-chat, and dsh-client-shortcuts so activation order is deterministic.
Verified
- 27 unit tests over the pure history reader and browse state machine (
npm test), run on every push by.github/workflows/ci.ymlon Node 20 and 22. - The composed profile contains the bundle row (
dsh --profile <name> --dump-config). - The browser boot graph announces the bundle with its inject list and
immediately: true, and the web host serves the bundle body byte-for-byte.
Not yet verified by automated browser control: the in-page interaction itself. Pressing ↑ in a running composer is the acceptance check.
Limitations
- History is per Session. Cross-session recall (recent Sessions' inputs) is not implemented yet.
- History reflects the loaded transcript window; messages hidden by compaction are not recalled.
- The recalled text is plain text: reference chips and attachments from the original message are not restored.
- One resident composer per Web page is assumed, matching the shell's own design.
License
MIT
No comments yet. Be the first to write one.