- Accueil
- Plugins
- Améliorations UI
- dsh-composer-history
dsh-composer-history
perrylink/dsh-composer-history
Historique de saisie façon terminal pour le composeur web de DeepSeek Harness : flèches avec priorité aux bords et restauration exacte du brouillon/curseur, historique persistant en local dans le navigateur, recherche inversée Ctrl+R, rappel limité à l'espace de travail, et touches entièrement configurables — ainsi qu'une prise en compte du contexte glissant.
Installer
dsh plugin --profile web add github:perrylink/dsh-composer-historyREADME
⌨️ dsh-composer-history
Terminal-style input history for the DeepSeek Harness Web GUI composer.
Press ↑ like it's in a terminal — and keep your half-typed draft safe.
Compatibility
| Surface | Status |
|---|---|
| Harness | DeepSeek Harness 0.1.0-rc.6 |
| Node | ^22.19.0 || >=24.0.0 |
| Platforms | Web GUI only (client plugin; browser-local storage; no network, no native code) |
| Model | Any (no model requests — pure UI behavior) |
What you get
dsh-composer-history puts a terminal's input history into the DeepSeek Harness Web GUI composer:
- Edge-first arrow recall — bare ↑/↓ move the caret first; history recall triggers only when the caret sits on the first/last line. The first recall stashes
{draft, caret}, and reaching the newest entry again (or pressingEsc) restores both exactly — never cleared. - Persisted history — every sent message is appended to a bounded browser-local store, so recall survives page reloads and reaches across sessions.
- Reverse search —
Ctrl+R(configurable) opens a query overlay over merged history, snippets, and templates. - Smart input layer —
/save//loadsnippets, prompt templates with{{workspace}}/{{session}}/{{draft}}variables, and browser-local reuse insights. - Sliding-context aware — compaction summaries join recall and search as
[compacted] …entries, and a transient notice announces each compaction with a one-click/compactfill.
Pure UI behavior: no session events, no agent-loop changes, no model requests. Recalled text only enters the ordinary composer draft; it reaches the model only if you press Enter.
Quick start
# 1. install the bundle into your profile
dsh plugin --profile web add "github:PerryLink/dsh-composer-history#main"
# or from npm (published releases)
dsh plugin --profile web add dsh-composer-history
# 2. restart and verify the row
dsh --profile web --dump-config | grep -A3 'id: composer-history'
Install & uninstall
The npm package ships the built bundles; a source checkout must be built first (pnpm run build) — the client-package check refuses to boot against an unbuilt bundle.
- git channel (latest
main):dsh plugin --profile web add "github:PerryLink/dsh-composer-history#main". - npm channel (published releases):
dsh plugin --profile web add dsh-composer-history. - tarball channel:
pnpm packin this repo, thendsh plugin --profile web add ./dsh-composer-history-<version>.tgz. - uninstall:
dsh plugin --profile web remove dsh-composer-history(or remove the row from the profile patch).
Configuration
All tunables are Schemastery Config fields (changeable from cordis.yml and the settings document). An id-targeted override replaces the whole row — restate every key you need. Invalid enum values fail the whole dsh boot loudly.
| Key | Default | Meaning |
|---|---|---|
recallWithDraft | 'save' | Recall mode (save / gate): save stashes a non-empty draft before recall; gate recalls only an empty draft (Claude/Codex-style gating) |
restoreOnEscape | true | Restore the stashed draft when Esc ends browsing |
edgeMode | 'logical' | Edge detection mode (logical / visual): by \n lines or by measured wrapped lines |
enableCtrlAlias | true | Let Ctrl+↑/↓ behave like the bare arrows |
restoreCaret | true | Also restore the stashed caret on bottom-out / Esc |
upKey | 'ArrowUp' | KeyboardEvent.key that recalls upward; '' disables |
downKey | 'ArrowDown' | KeyboardEvent.key that walks newer / restores; '' disables |
escapeKey | 'Escape' | KeyboardEvent.key that escapes browsing; '' disables |
maxHistory | 500 | Maximum recalled entries (newest kept); 0 = unlimited |
includeKinds | ['user'] | Conversation node kinds admitted into the history (add 'steering' to include steer messages) |
historyScope | 'session' | History scope (session / workspace): workspace prepends other listed sessions' user messages before the current session's |
persistHistory | true | Append sent messages to the browser-local store |
maxPersisted | 200 | Maximum persisted entries; 0 = unlimited |
enableSearch | true | Enable the Ctrl+R reverse-search overlay |
searchKeys | ['Ctrl+R'] | Chord specs opening the search (modifiers Ctrl/Alt/Meta/Shift + a key name); a malformed spec fails the browser fiber loudly |
searchCaseSensitive | false | Whether search matching distinguishes letter case |
includeCompactionSummaries | true | Admit [compacted] … checkpoint summaries into recall and search |
showCompactionNotice | true | Show a transient notice when a compaction checkpoint lands |
compactCommandText | '/compact' | Slash command the notice's "Compact now" action fills into the composer; '' hides the action |
enableSnippets | true | Enable the snippet library (/save, /load, search-panel picking) |
maxSnippets | 200 | Maximum stored snippets; 0 = unlimited |
enableTemplates | true | Enable the prompt-template library (variables fill at insertion) |
enableInsights | true | Enable the reuse-insight hint (local usage statistics) |
insightMinUses | 2 | Minimum uses before the reuse hint shows |
enableCompactionHighlight | true | Badge [compacted] … summaries distinctly in the search panel |
Tools & surfaces
| Surface | Kind | Notes |
|---|---|---|
ArrowUp | keybinding | Edge-first ↑/↓ recall — stashes {draft, caret}, restores both exactly on bottom-out or Esc |
Ctrl+R | keybinding | Reverse-search overlay over merged history, snippets, and templates |
/save | command | Save the current draft as a named, tagged snippet |
/load | command | Insert a saved snippet at the caret |
templates | UI | Prompt-template export/import as a JSON document (explicit click only) |
composer-history | settings namespace | Carries the resolved config into the browser half |
Keybindings
| Key | State | Behavior |
|---|---|---|
| ↑ | IDLE, caret on first line | stash {draft, caret}, fill newest entry, caret to end (no history → pass) |
| ↑ | BROWSING, caret on first line | older entry; hold at the oldest (intercept, no mutation) |
| ↑ | caret not on first line | fully released (browser moves the caret) |
| ↓ | IDLE | always released (plain caret movement) |
| ↓ | BROWSING, caret on last line | newer entry; at newest → restore savedDraft + savedCaret → IDLE |
| ↓ | caret not on last line | fully released |
| Esc | BROWSING (restoreOnEscape: true) | restore savedDraft + savedCaret → IDLE, intercepted |
| Esc | otherwise | released (menu/popup Escape semantics untouched) |
| Ctrl+↑/↓ | enableCtrlAlias: true | same as bare arrows |
searchKeys chord | composer focused, plain phase, no menu/selection/IME | open reverse search; browsing ends, the shown text becomes the draft |
| Shift/Alt/Meta+arrows, IME, selection | any | always released |
upKey/downKey/escapeKey/searchKeys rename the keys above; the modifier policy (and the search chord's exact-modifier match) is unchanged. Inside the search overlay: ↑/↓ move the match selection (the selected row scrolls into view), Enter fills, Esc cancels, a click picks, a press outside cancels; matched substrings are highlighted in every row.
Reverse search
- Open: the
searchKeyschord while the composer is focused and the input isplain(aCtrl+Rhere also stops the browser's page reload — the key is consumed only inside the composer). - Filter: substring match over the merged history (current session + persisted + workspace entries); case sensitivity per
searchCaseSensitive; matched substrings are highlighted in each row. - Pick: Enter fills the draft and moves the caret to the end — the same single
setDraftwrite path as ordinary recall. Recalled text reaches the model only if you press Enter afterwards. - Cancel: Esc or a press outside the panel; the draft is untouched.
Smart input layer
On top of the terminal-style history, three browser-local libraries turn the composer into a reusable input surface. Everything below lives in localStorage (keys dsh.composer-history.snippets.v1, .templates.v1, .insights.v1), never touches the network, and every switch is a Config field.
Snippets (cross-session command library)
/save ship-check --tag=release,ops
check the build, run the smoke suite, tag the release ← the rest of the draft is the snippet
/save ship-check → "snippet saved: ship-check"
/load ship-check → the snippet fills the composer
Ctrl+R → search panel lists snippets (green badge = name) alongside history
/save <name>consumes the Enter, stores the draft (minus the command line) under a kebab-case name with optional tags, and clears the composer. Nothing to save → an error notice, the command never sends./load <name>inserts the snippet at the caret (whole-draft replace, caret to end) and counts the use.- Scope: snippets saved with a workspace cwd are workspace-scoped; snippets saved without one are global.
maxSnippetsbounds the library; same-name saves replace. - The plugin never sends: every fill lands in the ordinary draft and your Enter stays yours.
Prompt templates with variables
Templates are stored prompt texts with {{variable}} placeholders. The search panel lists them with a purple badge; picking one fills the variables from the live session and inserts the result. Built-in variables: {{workspace}} (the session's cwd), {{session}} (the session id), {{draft}} (the current draft). A template referencing an unknown variable fails loudly with the missing list — a half-filled prompt is worse than an error.
The template library exports to and imports from a JSON document (composer-templates-v1) through the panel's Export templates / Import templates buttons — an explicit user action; the plugin never writes files on its own.
Reuse insights
Every newly committed user message (and every snippet load) lands one browser-local usage record keyed by exact text. While you type, a small hint under the composer reports used M× in N sessions · 在 N 个会话里用过 M 次 once the draft matches a prompt used in at least insightMinUses (default 2) sessions. Toggle with enableInsights; the statistics contain only the deduped texts and counters.
Compaction summary highlight
Ctrl+R marks [compacted] … summaries with an amber badge (history stays unbadged), snippets green, templates purple — the panel's provenance is visible at a glance. Toggle with enableCompactionHighlight.
Sliding context
The harness core gives every dsh session a sliding context window, the same workflow Claude Code and Codex ship: when a conversation approaches the model's context limit (or the provider reports an overflow), the harness auto-compacts — older turns are summarized behind a compaction checkpoint marker that stays visible in the transcript, the model keeps only the summary plus the recent tail, and the session continues. /compact triggers the same compaction on demand, and the marker renders as an expandable "Context compacted" row.
dsh-composer-history plugs the composer into that workflow so the window slide never costs you your typing history:
- Recall survives compaction — shadowed turns stay in the session snapshot, so ↑ still walks every message you sent before and after a checkpoint.
- Summaries join the history — each checkpoint's summary text enters ↑ recall and
Ctrl+Rsearch as a[compacted] …entry (toggle:includeCompactionSummaries), so context the model no longer sees verbatim stays one keystroke away. - Compaction notice — when a checkpoint lands while the page is open, a transient snackbar announces it (the Claude Code "Auto-compacting conversation…" moment) with the summary snippet and a one-click Fill
/compactaction (showCompactionNotice,compactCommandText); the fill lands in the ordinary draft, and only your Enter sends it. - Search counts — the
Ctrl+Rpanel now shows a liveN entries/N matchesstatus line, and long entries are clamped to two lines.
Compaction itself (thresholds, summary model,
/compact) is owned by the harness core's compaction plugins — this plugin only observes the checkpoint markers the client snapshot already exposes, so it works without any agent-loop or model-request changes.
Permissions & data
- Permissions: the plugin declares
browser:local-storagein its workshop manifest — nothing else. No network, no subprocesses, no session events. - Data: four browser-local
localStoragekeys —dsh.composer-history.v1(sent-message history),dsh.composer-history.snippets.v1(snippet texts + tags + use counters),dsh.composer-history.templates.v1(template texts), anddsh.composer-history.insights.v1(deduped prompt texts + per-session use counters). All bounded, same-origin only, never uploaded; corrupt payloads reset silently. - Model-visible ⟺ you press Enter: recalled text, snippet loads, template fills, and the
/compactfill all land in the ordinary composer draft. Nothing reaches the model until you press Enter.
Security boundaries
- UI-only, never enforcement. The plugin edits the composer draft only; the sandbox, approval, and session systems remain the enforcement authorities, and no command or tool is ever claimed or bypassed.
- No content leaves the browser. History, snippets, templates, and insights live in
localStorage; nothing is uploaded, and no model request or network call is made. - Fail loud. Invalid enum values fail the whole dsh boot; a malformed search chord fails the browser fiber — misconfiguration never silently degrades.
- Bounded everything.
maxHistory,maxPersisted, andmaxSnippetscap retained entries; corrupt or foreign payloads reset silently. - Zero side effects on pass-through. The plugin intercepts only in the
plaininput phase and yields to the slash menu, command popups, IME composition, text selections, and modifier combos.
Known limitations
- Logical vs visual lines. Default
logicalkeys off\n(a long auto-wrapped message counts as one line);visualmeasures real wraps via a hidden mirror (O(lines·log n) binary search per edge check, memoized per draft/width). Mirror measurement needs a real layout engine — the pure span math is unit-tested instead. - Persisted history is per-browser. The store lives in one origin's
localStorage; it never syncs between browsers or machines. Corrupt payloads reset silently. - Undo stack includes recall transactions. Every fill/restore is one
setDrafttransaction in the input machine's undo log; Ctrl+Z steps back through recalls. The precision fix needs the upstream edit-range exposure. - Recalling a
/xxxentry then Enter follows the normal command claim/adjudication path (expected, and Enter is never intercepted). - Menus/popups and non-
plainphases always win; a committed send and session switches both reset to IDLE. - Reference chips (U+FFFC placeholders) ride along with recalled/restored draft text.
historyScope: 'workspace'reads the live assemblies of other listed sessions; sessions whose assembly has not materialized contribute nothing yet.- The search overlay is plain DOM (no React dependency); it renders all matches up to the
maxHistorybound. - Compaction awareness is observational. Checkpoints landed before install (or before a session switch) never trigger a notice; a checkpoint whose summary event fell outside the loaded window contributes no
[compacted] …entry (summary: null). - The notice's "Compact now" action only fills the configured command text into the draft — sending remains the user's Enter.
- Snippets, templates, and insights are browser-local. Names are kebab-case (1..64 chars); tags cap at 8 × 32 chars. Template variables resolve from the live session;
{{draft}}is the draft at pick time.
Development
pnpm install # node ^22.19 || >=24
pnpm run build # tsc build + tsdown bundle (lib/)
pnpm run typecheck # tsc --noEmit (src + tests)
pnpm test # vitest run
pnpm run test:watch # vitest watch
pnpm run test:coverage # vitest run --coverage
pnpm run check:readmes # README consistency gate
pnpm run verify:pack # pack-surface check
Topics
deepseek-harness · dsh · dsh-plugin · web-gui · input-history · keyboard-shortcuts · compaction · sliding-context · typescript
Contributors
- @PerryLink — creator and maintainer: edge-first arrow recall, persisted history, reverse search, sliding-context awareness, snippet library, prompt templates, reuse insights, and the
dsh.bundle/dshWorkshopmanifests.
PerryLink DSH Plugin Family
This project is one of the 15 DeepSeek Harness plugins maintained by PerryLink. If this one helps you, the others likely will too:
| Plugin | One-liner |
|---|---|
| dsh-mcp-panel | Read-only MCP runtime panel: /mcp command + Settings tab with status, tools and errors |
| dsh-doublecheck | Engineering-discipline guard: requirements grill, test gates, adversary review |
| dsh-background-agents | Durable background child agents with a Web UI sidebar, messaging and interrupt |
| dsh-lsp-actions | LSP diagnostics, formatting, completion, code actions and rename over language servers |
| dsh-output-styles | Claude Code outputStyles-equivalent runtime style switching |
| dsh-checkpoint-rewind | Claude Code /rewind-equivalent: snapshots, session forks, one-shot restore |
| dsh-permission-rules | Claude Code-style declarative allow/deny/ask permission rules with audit |
| dsh-auto-review | Second-model auto-review on the approval chain, fail-closed by default |
| dsh-memento | Approval-gated cross-session memory: ctx.memory seam + SQLite + memory tool |
| dsh-skill-pack-security | Security-audit skill pack: secret scan, dependency and supply-chain review |
| dsh-session-pin | Pin sessions in the Web sidebar with durable ordering |
| dsh-composer-history | Terminal-style input history for the web composer: arrows, Ctrl+R search |
| dsh-github | GitHub PR/issues integration for DSH, every write gated by approval |
| dsh-plugin-guide | Plugin-development knowledge base as an on-demand agent skill |
| dsh-claude-move | Migrate Claude Code sessions, memory, skills and CLAUDE.md into DSH |
License
Apache License 2.0 © 2026 dsh-composer-history contributors
Plugins associés
dsh-web-ui (dsh-web-ui-all)
zhu1090093659/dsh-web-ui
DSH-better-sidebar
omdsh-dev/dsh-better-sidebar
dsh-TUI
ccch1mneyyy/dsh-tui
dsh-at-file
omdsh-dev/dsh-at-file