dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DeepSeek Harness 网页端分屏工作区 - 多个会话并排聊天,面板观感与原生对话完全一致:拖拽分屏、实时会话(排队消息、面板内 ask_user/审批卡片)、面板内视图标签(Chat/Trajectory/插件视图)、Alt+T 紧凑 TUI 输入模式与面板焦点快捷键。需要 DSH 0.1.5+。
Split-screen workspace for the DeepSeek Harness web GUI — chat with several agents side by side, in panes that look and behave exactly like the stock DeepSeek conversation.
Alt+↑/↓ / Alt+1‑9 from
dsh-session-hotkeys, the stock New Session button, agent-team / IM /
notification opens and any other uiWorkspace.openSession() caller move the
root session binding and the focused pane with it — exactly like a sidebar
click — instead of silently changing the covered stock view behind the
overlay. A restored layout is primed, so it is never yanked at boot.keydown/beforeinput
against a 6-repoints/10 s budget, and the guard then stood down forever after
a few ordinary sidebar switches — the visible pane composer became a plain
contenteditable (typing painted, Enter/Ctrl+A dead, / never opened the
command menu) while health() stayed green. Repairs are now deferred out of
the keystroke, throttled (one per second) rather than stopped, re-armed once
stable, and reported through health().editorRoots. Ctrl/Cmd+A and the
last-resort Enter path repair an orphaned host first./compact completion is announced. Native DSH only writes a muted
transcript-tail row; a new flow observer shows a short toast — "context
compacted" / "compaction did not complete" — in stock and split views.Known remaining gap: the compact/TUI composer (Alt+T, or panes shorter than
~280 px) is the plugin's own single-line textarea and still has no native
slash-suggestion menu; type / in the full composer (Alt+T) for suggestions.
| Keys | Action |
|---|---|
| Alt+T | Toggle the focused pane between the full composer and the compact TUI line |
| Alt+Shift+← / → / ↑ / ↓ | Move keyboard focus to the neighboring pane |
Everything else is mouse-driven and works exactly like the stock DeepSeek UI. On macOS the same chords run with ⌥ Option in place of Alt (Option+T, Option+Shift+Arrows).
ask_user_question (or a plan review / approval prompt opens), the pane's
composer is taken over by the native question card, exactly like the stock
view: options, multi-select, free-text answers, Skip and Cancel all work in
place. The pending interaction is read from the ROOT standard
useSessionStatus(...).pendingInteraction source — the same hook the stock
conversation uses — so every pane answers its own session's request.dsh-context Context dashboard) switch in place, with
the view's own detail slots (conversation.trajectory.images, inspect
panes) routed into that pane. The roster is read from the slot ledger, so a
view registered after the pane mounted still shows up. On a brand-new
session the stock header hides itself until the first message — the view
tabs come back with it.--dsh-content-font-size and the matching
line height, 14px/24px by default) instead of inheriting the pane's 16px, its
left inset matches the card's 14px text inset, and its caret uses the stock
token — so Alt+T swaps the input without changing the reader's typography.card − 48px wide (minus its own 8px inset, as
QueueDock.module.css defines it) and centered on the input card it merges
into, so the queue, the todo strip and the stats line all share the exact
width of the input box. The card geometry is measured from the live
composer editor (dsh 0.1.5 renders it as a Lexical contenteditable).MAX_PANES), depth capped at 6, invalid payloads discarded
with one console note instead of re-activating a broken workspace. A layout
that crashed the workspace before it became healthy is not auto-activated on
the next boot (see Safe mode below).dsh web — nothing changes until you
actually split: one conversation looks and behaves exactly like stock.The plugin is built so that a failing pane can never take the host down with it. Everything below is available without devtools-only tricks; all state is reported by one function that returns plain JSON.
Kill switch — disables the split workspace unconditionally and persists it (the saved layout is kept, not deleted):
window.__dshSplitView.setEnabled(false); // disable
window.__dshSplitView.setEnabled(true); // re-enable
// equivalent persistent switch:
localStorage.setItem("dsh.split-view.enabled.v1", "0");
Safe mode (crash-loop guard) — every deliberate activation is recorded before it can crash; after the workspace has been up and responsive for ~9 s it is marked healthy and the crash counter resets. If a previous boot never reached healthy, or the same guard tripped twice, the next boot does not auto-activate the saved layout:
Repeated render failures (3 boundary catches in ~20 s) also switch to safe mode automatically, and a workspace-level crash does so immediately. A failing pane body is isolated with its own Retry card — the other panes keep streaming. A changed plugin release resets the crash guard automatically.
Diagnostics API — window.__dshSplitView:
| Method | Returns |
|---|---|
health() | {pluginVersion, hostRelease, testedHost, testedReleases, enabled, safeMode, active, geometryBlocked, panes, maxPanes, blockedReason, failures, lastError, geometry, persistedTree, crashGuard} |
reset() | clears the saved tree + crash counters, stays in the stock view |
retry() | clears crash counters/safe mode and re-activates the kept tree |
help() | shortcut/help data (the same information as the "?" card) |
setEnabled(flag) | the kill switch |
focusChat(), closeFocused() | the companions bridge (dsh-session-hotkeys) |
All methods return plain serializable data and never throw. health().active
means "the split workspace is rendered and responsive"; health().panes is the
true persisted tree state even while safe mode/geometry keeps it off screen.
Tested host versions — the plugin reads
window.__DSH_BOOT__?.release?.version and compares it against a
TESTED_RELEASES list (currently 0.2.0-rc.2 and 0.2.1-rc.2 — the build is
verified on the user's live 0.2.1-rc.2 host). An untested release logs ONE
info note and shows up in health().testedHost — activation is never blocked
by the check; the crash loop guard is the protection.
Limits — at most 6 panes (MAX_PANES); persisted trees deeper than 6
levels or with duplicate/unknown session ids are repaired or discarded on load.
UI strings are localized (EN/ZH). The split-only contract is unchanged: with
fewer than two panes the plugin renders nothing over the stock UI — its only
chrome is the small fixed chip while safe mode/geometry keeps a saved split
inactive.
conversation.session.header.actions entry render in both
the full header (wide panes) and the slim header (narrow panes). The team
panel itself is portaled above the split overlay, so it is never clipped.Esc returns focus to the
conversation, Alt+Enter steers or queues while an agent runs, the model
picker is keyboard-reachable, and every shortcut is rebindable in a visual
panel. Works great inside split-screen panes.dsh plugin --profile web add dsh-split-screen
then restart dsh web.
sessions, slots,
conversation), plus a small per-session staging bridge (session.open()).@deepseek-ai/dsh-client-ui-layout (owner of the shell.overlay
slot the workspace mounts into), @deepseek-ai/dsh-client-locale,
@deepseek-ai/dsh-client-ui-primitives.dsv- class names) so a harness upgrade cannot silently break
the look.No dependencies and no build step: lib/client.js is the shipped browser
bundle, written for window.__ModuleLoader__.load({id, factory}), and is
syntax-checked directly. Node.js 18+ runs the suites (CI uses Node 24).
node --check lib/client.js # bundle syntax
npm test # both sandboxed suites (smoke + safety)
tests/smoke.mjs (16 assertions) evaluates the bundle under a stub DOM and
covers the module-loader contract, the double-apply guard, the global-chord /
drag-bridge / IME listener lifetimes, full teardown + re-apply, and the
split-only activation gate for empty, single-pane and two-pane persisted
trees.tests/safety.mjs (117 assertions) covers the 1.6.0–1.7.0 hardening:
0.2.0/0.2.1 frame-geometry fixtures (shell.bottom/handles/overlay excluded,
centre anchored, validated grid fallback, fail-closed measurement),
persistence validation (corruption, duplicate ids, pane cap, depth), the kill
switch, the crash-loop guard → safe mode → retry/reset, the
health()/help() / setEnabled() diagnostics contract, invisible-active
(B3) chord gating, the composer-chain inject-face parity behind in-pane
ask/approval cards (useQuestionCard / matched / store / dispatchers), and
the bounded session-retain retry driver with its one-warning diagnostics.
The 1.7.0 behaviors themselves (root-follow parity, editor-root ownership,
compaction toast) are verified live on an isolated real 0.2.1-rc.2 client —
see VERIFICATION-2026-10-20.md.Both suites are sandboxed (node:vm + stub DOM/localStorage): they never touch
a DSH profile and never start or contact the running app. CI runs the same
commands on every push/PR (.github/workflows/ci.yml).
/ menu and compaction feedback on
an isolated real 0.2.1-rc.2 client.@deepseek-ai/dsh@0.1.5-rc.2 and on @deepseek-ai/dsh@0.2.0-rc.2 under
dsh web and the 0.2.0 desktop composition (same client, desktop profile
bundles + patch layer). TESTED_RELEASES in the bundle lists the releases
the build is signed off against; health().testedHost reports the live
comparison.shell.bottom
row (which broke the conversation-column measurement, making split view a
silent no-op) and content moved to a new factory-slot render dispatcher
(renderFactorySlot) that every user bubble and the model label call. 1.6.0
fixes both: the geometry is now selected structurally (center column +
validated grid-track fallback, shell.bottom/handles/overlay excluded), and
panes receive a guarded renderFactorySlot outlet plus the
declared-children renderSlot/renderSlotChain seats the stock renderer
attaches. The live 0.2.1-rc.2 host is verified on the isolated real
0.2.1 desktop host by the release verifier (and reported as tested by
health()); a changed shell shape fails CLOSED (stock view stays in charge,
safe-mode chip appears) rather than covering the wrong area.health().editorRoots), and adds a compaction-completion toast.SessionReference
(sessions.retain(id, { source }) + uiSession.bindingSource(reference)),
which the plugin holds for each pane's lifetime;useSessions, useSessionStatus,
useSessionRetainInfo, useResource, useWorkspaces, usePanelInfo)
are harvested from the plugin's own root-scoped shell.overlay seat,
where the renderer materializes them — including anything a third-party
plugin contributed through slots.provideRoot;useDisclosure,
useTurnData) are bound per render from the hookContext the stock
ChatView passes through renderSlot.
A pane therefore needs nothing private from the renderer, and it is never
suppressed: if its session faces are not ready it renders its own built-in
transcript and composer for that pane instead.useSessionStatus source
the stock conversation uses (with the 0.1.x uiSession source as fallback),
so the plugin does not run on 0.1.1-rc.2 or earlier cores.MIT