Three-tier now/next/later input planning dock for the DeepSeek Harness web GUI: a red/yellow/green priority strip replaces the busy-Enter queue, plain Enter queues to later, with freeze/resume and in-place multi-line editing.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-input-traffic
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:drscrewdriver/dsh-input-traffic
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
dsh-input-traffic
While the agent is busy, "interrupt" and "queue" are no longer mutually exclusive: red interrupts and sends now, yellow inserts at the next turn, green queues until the end — all three coexist. Near DeepSeek peak pricing hours, one click freezes the session; resume later during off-peak pricing.
A cordis client plugin assembled via the dsh plugin command and a bundle patch — no dsh source changes, no PR required.
What it does
- Three tiers coexist: while the agent is busy, every input lands in a waiting area first, then you choose when it enters the conversation — no longer a single "interrupt" or a single "queue":
- 🔴 Red (now): interrupt the current turn and send immediately — the running generation stops and the message is processed and answered right away;
- 🟡 Yellow (next): insert at the next natural turn — the current action (tool call / ongoing generation) finishes first, no interruption;
- 🟢 Green (later): queue until the whole logic has finished — processed after all previously queued actions complete (the default).
- Yellow is reversible: pressing green on an already-steered (yellow) message revokes the insertion and pulls it back to the queue.
- Queued content stays editable: messages already in the queue can be edited in place — the multi-line editor auto-grows with the content so long messages stay fully visible (Enter saves / Shift+Enter newline / Esc cancels); they can also be pulled back into the composer for editing (back-filled draft, then resubmitted).
- Queue management: messages in the waiting area can be moved up / down to reorder, removed, or cleared with the queue-level "cancel and clear".
- Edits are never lost: if saving an edit fails (the agent already claimed the message), the edited content automatically moves back to the composer; an occupied draft is never overwritten.
- Peak-hour freeze: a "Freeze session" button on the composer's right — near DeepSeek peak pricing hours (09:00-12:00, 14:00-18:00) it pauses API consumption: the current turn finishes naturally, then the unsent queue is frozen; "Resume session" continues during off-peak hours.
- Official behavior takeover: while the plugin is mounted, the official "busy-Enter behavior" settings row is hidden (Enter stays queue-later).
UI preview
Layout sketch of the waiting area and the freeze button in a session page:
┌─ Composer ───────────────────────────────────── Send ── [❄ Freeze] ─┐
└─────────────────────────────────────────────────────────────────────┘
┌─ Waiting area (three-tier planning dock) ───────────────────────────┐
│ ┌ 2 queued messages 🗑 Cancel & clear ┐ │
│ │ 🟢 queued First message preview… ↑ ↓ Pull Edit Remove │ │
│ │ 🟢 queued Second message preview… ↑ ↓ Pull Edit Remove │ │
│ │ Editing: the multi-line editor auto-grows (up to ~8 rows) │ │
│ │ Enter saves · Shift+Enter newline · Esc cancels │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
The three tiers
| Tier | Color | Semantics | Underlying mechanism (existing dsh RPCs) |
|---|---|---|---|
| later (default) | Green | Queue: processed after all previously queued actions finish; green on an already-steered message revokes the insertion | Enter default queue → agent.followup() (next-turn); revoke = updateQueue(remove) + send(text) |
| next | Yellow | Insert at the next natural turn: after the current action finishes | updateQueue(id, { kind: 'steer' }) → agent.steer() (next-step boundary) |
| now | Red | Interrupt and send: stop the current turn, the message is processed immediately | cancel() → updateQueue(remove) (avoids the inbox duplicate-insertion rejection) → send(text) (re-submit, wakes the driver immediately) |
Why red is cancel + remove + resend: the harness inbox rejects inserting a message that is already pending; steering the original message after an interrupt would be rejected and strand the message (see FAQ).
Session freeze / resume (peak-hour pause)
The "Freeze session / Resume session" button on the composer's right (beside the send button) pauses API consumption near DeepSeek peak pricing hours:
- Freeze: the current turn is not interrupted — it finishes naturally, then consumption pauses. The queue is fully decoupled from freezing: freezing only stops the agent from consuming (executing / inserting / appending), while the waiting area stays visible and fully operable — reorder, edit, remove and set the red/yellow/green insertion tier, just like when not frozen;
- Resume: the (possibly edited) queue is re-submitted and each entry executes with its planned tier (red = interrupt and process immediately, yellow = interject, green = queue); the agent continues in FIFO order;
- Engine: freeze = detach every queued row via
updateQueue(remove)(copies with their tiers kept in the plugin store); the driver stops naturally once the current turn ends with no pending work; edits made while frozen (text / order / tier) write back to the store in real time; resume = re-submit viasend(text), waking the driver (red-tier entries are preceded bycancel()); - Note: queued messages containing non-text content (images) cannot be re-sent and are released by the freeze (they do not come back).
Queue management
Each waiting-area message (while not frozen) offers:
| Action | Description |
|---|---|
| Move up / down | Reorder the FIFO queue (the whole queue is rebuilt in the new order; disabled while any image message is queued) |
| Drag to reorder | Drag a row onto its target position (native HTML5 DnD, no extra dependency); same server-side rebuild as the arrow buttons |
| Edit in composer | Back-fill the message into the composer draft and remove it from the queue for editing |
| Edit / remove | Edit the queued content in a multi-line editor / cancel the message |
| Red / yellow / green planning | See "The three tiers" |
| Cancel and clear | Two-step confirm, then stop the current run and remove every queued message (first click shows "Confirm clear?") |
Concurrency protection for reordering: if a message was already claimed by the agent during the rebuild (queue-item-not-found), the reorder stops immediately and nothing is re-sent — the changed queue is never scrambled.
The waiting area's collapse state is remembered across sessions.
When editing a queued message (inline):
- Auto-grow: the editor grows with the content in real time; long messages expand fully, up to about 8 rows, then scroll internally;
- Shortcuts:
Entersaves,Shift+Enterinserts a newline,Esccancels (composition input is protected from accidental saves); - Failure fallback: if the save fails because the agent already claimed the message (e.g. "started sending"), the edited content automatically moves back to the composer with a notice — nothing is lost; the back-fill only happens when the composer is empty, so an existing draft is never overwritten.
Installation
# Option 1: install from npm (recommended)
# (the profile is a pnpm workspace root, so -w is required)
dsh plugin --profile web add dsh-input-traffic -w
# Option 2: assemble from git or a local path
# dsh plugin --profile web add /absolute/path/to/dsh-input-traffic -w
# (after git install, build in the profile's node_modules: npm install --legacy-peer-deps && npm run build)
# Confirm the composed tree contains the new row
dsh web --dump-config | grep -B1 -A2 'input-traffic'
# Restart dsh web — required! A running instance does not hot-load the bundle layer
dsh web
Local build and tests:
npm install --legacy-peer-deps # the @deepseek-ai client package chain is incomplete on npm; toolchain only
npm run build # tsc (lib/types) + tsdown (lib/index.js + lib/client.js)
node examples/verify-assembly.mjs # 12 assembly assertions
npm test # 36 vitest component tests
npm run lint # ESLint (src + tests, flat config)
npm run verify # one-shot gate: lint + test + build + verify-assembly
Development (TDD + Lint)
This project is maintained with TDD (test-driven development): write the failing test first, then implement to green.
npm run tdd # vitest watch: re-runs on change, red-to-green loop
Workflow:
- Add/update a case in
tests/(red: confirm the new behavior is not implemented yet); npm run tddand watch it fail;- Implement the minimal change in
src/(green); npm run verifyall green before committing (lint + 36 tests + build + 12 assembly assertions).
Lint:
npm run lint # ESLint flat config (eslint.config.mjs)
npm run lint:fix # auto-fix what can be fixed
- Scope:
src/andtests/(TypeScript + React); build outputlib/is ignored; - Rules:
@typescript-eslint/recommended+react-hooksbest practices; unused variables are errors (underscore prefix_exempts).
Usage
- While the agent is busy, type and send — the message enters the waiting area (green queue by default);
- Press a planning button on the message:
- 🟡 Yellow = interject — insert after the current action finishes;
- 🔴 Red = interrupt — stop the current action, the message is processed right away;
- 🟢 Green = keep queued (the default); on an already-steered message, green revokes it back to the queue;
- Reorder / re-edit: use move up/down, "Edit in composer", or the multi-line inline editor (Enter saves, Shift+Enter newline);
- Near peak hours: press "Freeze session" on the composer's right; the session pauses after the current turn; press "Resume session" during off-peak hours to continue.
FAQ
After an interrupt the message gets no reply / the conversation stalls
Fixed (historical issue). Root cause: the harness inbox rejects inserting a message that is already pending — steering the original message right after an interrupt was rejected with "message is already pending", stranding the message and stopping the driver. The current implementation is cancel → remove → resend (the text re-submitted as a fresh message), so the interrupted message is processed and answered immediately. If it still happens, rebuild the plugin and restart dsh web.
Where does the content go after a failed edit save?
It is not lost. When the save fails (the agent already claimed the message), the edited content automatically moves back to the composer with an "Edit failed; the content was moved back to the composer" notice; if the composer already has a draft, nothing is back-filled and only the failure is reported.
The "busy-Enter behavior" settings row is missing
Expected — the plugin hides it and pins Enter to green queue; a stale preference cannot leak behind the hidden row.
Queued messages disappeared after freezing
Expected — the freeze detaches the queue into the plugin store (removed from the waiting area); they return on resume. Refreshing the page loses the frozen queue; avoid refreshing while frozen.
Move up/down is disabled
Reordering is disabled while any queued message contains non-text content (images cannot be re-sent). Same for "Edit in composer".
Interrupt / interject buttons are disabled
Red and yellow are disabled while the agent is idle — an idle agent would process the message immediately anyway, so planning is not needed.
Uninstall
dsh plugin --profile web remove dsh-input-traffic
Restart dsh web afterwards to restore the official queue dock and the "busy-Enter behavior" settings row.
Compatibility and privacy
- Requires DeepSeek Harness with the web profile; verified on Windows/macOS/Linux dsh web.
- A browser-side (client) plugin only — every operation goes through existing dsh RPCs (
session.prompt/session.updateQueue/session.cancel); no official source changes. - The plugin reads no data beyond session state and uploads nothing; the frozen queue lives only in browser memory.
- The contract types are declared locally in
src/types/contracts.d.ts(the npm dsh client chain is incomplete) and mirror the harness sources at build-verification time.
Architecture
src/
├── index.ts # node half (loader entry, empty apply)
├── invariant.ts # takeover invariants
├── types/contracts.d.ts # local @deepseek-ai/* contract declarations
└── client/
├── index.ts # browser half apply: busyEnter pinned to queue + three slot registrations
├── steer-queue-dock.tsx # three-tier planning dock (shadows conversation.input.dock id queue)
├── freeze-button.tsx # freeze/resume button (conversation.input.right)
├── freeze-store.ts # shared freeze state (composer button ↔ dock banner)
├── hide-enter-row.tsx # settings-row hiding (shadows settings.general.item id composer-enter)
├── locales.ts # steer dictionaries (zh/en)
└── *.module.css
- Slot shadowing: list slots render the lowest priority per cell — the same id at priority -1 overrides the official entries (QueueDock, EnterBehaviorRow).
- Build chain: tsdown mirrors harness
packages/client/tsdown.client.tssemantics (__ModuleLoader__.loadbanner, lightningcss-inlined CSS Modules, platform externals table, bundle purity gate). - Consumer contract:
conversation.updateQueue / cancel / send / input.for(actx).notify / actions.setDraft(official ui-conversation service, verified against api-proxy.ts). - Auto-growing editor:
resizeEditor(a pure export of steer-queue-dock.tsx) resets the textarea height and grows it byscrollHeight; a CSSmax-heightcaps the growth and the editor scrolls internally.
Real-environment verification (Windows, 2026-08-17)
End-to-end browser verification on a live dsh web, zero application console errors:
| Item | Result |
|---|---|
| Assembly | Composed tree contains the input-traffic row; plugin tab shows mounted/enabled; /plugins/dsh-input-traffic/client.js 200 |
| Settings-row hiding | The "busy-Enter behavior" row is absent (zero DOM matches) |
| Red now | Interrupted message is processed immediately: the agent replies to it explicitly and continues; no stranded intermediate state |
| Yellow next + green revoke | After interjecting, green pulls the message back to the queue |
| Freeze / resume | Current turn finishes naturally without interruption, queue frozen with banner; resume drains everything in FIFO order |
| Queue editing (multi-line / fallback / drag-reorder / confirm) | Covered by component tests (36/36 green); real-environment re-check pending |
References
- dsh-plugin-creation-convention.md (workspace root) — the dsh plugin creation convention this plugin follows
- Semantics reference: dsh-traffic-light (desktop session-status traffic light)
- Harness anchors:
packages/client/AGENTS.md,packages/client/tsdown.client.ts,packages/client/web/src/platform.ts,packages/bundle/web-app/cordis.patch.yml,packages/client/ui-conversation/src/client/queue/QueueDock.tsx,packages/host/apiproxy/src/api-proxy.ts
License
MIT
Links
More in this category
zhu1090093659/dsh-web-ui#packages/dsh-web-ui-all★ 4430
Plugin and skin collection for the DSH Web UI: task board, Git graph, right-side panel, remote mobile UI, pet, live token stats, and a skin center.
omdsh-dev/DSH-better-sidebar★ 2101
Full sidebar workbench with file rendering and editing, terminal, Git, and subagents; third-party plugins can register new tabs.
ccch1mneyyy/dsh-TUI★ 1924
Claude Code-style full-screen terminal UI: pixel-whale header, live status line, and streaming thought expansion.
omdsh-dev/dsh-at-file★ 362
Codex-style `@file` mentions: search workspace files in the composer and attach their contents to prompts.
omdsh-dev/dsh-genui★ 214
Interactive UI components rendered inline in replies: layout, charts, forms, quizzes, mermaid, 3D scenes, and an action event loop back to the model.
huiliyi37/dsh-tianshu-tui★ 211
A terminal UI (TUI) for DeepSeek Harness.