dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DeepSeek Harness Web UI plugin: browser-style tabs for open Sessions - switch, close, reorder by drag, and tear a tab off into a separate window.
Browser-style tabs for the Sessions a DeepSeek Harness window has open — switch, close, reorder by drag, and tear a tab off into a window of its own, as a DeepSeek Harness client plugin.
Every Session the window has touched becomes a tab in a strip above the whole app. Click a tab to
switch to it, close it with × / middle click / the context menu, drag it left or right to reorder,
or drag it out of the strip to hand it to a new window. Closing a tab never deletes a Session —
exactly like closing a browser tab never deletes a page.
Nothing upstream is patched. The plugin registers into the official slot, locale and Session faces of the web client, so it survives DSH upgrades and uninstalls by removing one row.
× on the tab, the middle mouse button, or the context menu (Close tab, Close other tabs, Close all tabs). The Session itself stays in the sidebar and reopens with one click.Open in a new window does the same without dragging.Enter / Space on a tab opens it, Delete / Backspace closes it; the strip is
a tablist and the tabs are tabs.+ at the left of the strip starts a Session through the client's own
navigation service, so the reuse-or-create policy stays where it belongs.The package declares dsh.client (browser half) and dsh.bundle.patch (loader row), so it installs
like any other DSH plugin:
# from a checkout, or from a published copy
dsh plugin --profile <profile> add <checkout>
# after that, reload the page once (a NEW row is picked up when the page's module table is built)
Manual mounting into a profile, which is what this repository was developed against:
# 1. live link so edits are picked up by client HMR without reinstalling
New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\<profile>\node_modules\dsh-session-tabs" `
-Target (Resolve-Path .).Path
# 2. dependency of the profile: $DSH_HOME/profiles/<profile>/package.json
# "dsh-session-tabs": "file:<path to this checkout>"
# 3. loader row: $DSH_HOME/profiles/<profile>/cordis.patch.yml
# - insert:
# - id: session-tabs
# name: dsh-session-tabs
The row belongs in the profile's patch layer rather than in dsh.profile.bundles, because the patch
layer reloads live: an edit to lib/client.js is picked up by client HMR without a restart, and a
page reload picks up everything else. A patch file must not be empty — use [] to disable the layer.
Rollback = drop the insert row, then reload the page; the dependency and the junction can stay, since without the row the package is simply not mounted.
| File | Role |
|---|---|
index.js | Host half: an empty apply(), there only so the loader can mount the package and the client-modules scanner can find the dsh.client declaration |
lib/client.js | The whole feature: the strip, the per-window tab store, the gestures, the caption-row handling, the tear-off hand-off |
cordis.patch.yml | The loader row a bundle install applies |
Five decisions worth keeping when editing:
shell.overlay seat. That seat is a click-through
absolute layer above every column, so the strip can sit above the whole app. The frame itself is
shifted down by the strip's height through a CSS rule keyed on the data-dsh-session-tabs
attribute and the --dsh-session-tabs-h variable this plugin sets — the strip then occupies
exactly the reserved band, drag handles included.selection, openSession, startSession, clearMain). A tab is just a Session id this window
keeps around.sessionStorage holds the window's own id (and a
flag for a window born by tearing a tab off) and localStorage holds the tab list filed behind
that id, under dsh.session-tabs.v1:<window>. That is what lets several windows show different
Sessions at once and lets each of them restore its own tabs after a reload. A window handed a
Session over ?dshTab= always mints a fresh identity, because Chromium clones sessionStorage
into script-opened windows and inheriting the source window's list would duplicate every tab.window.dshShell.openSessionWindow({ href, x, y, width, height }), which the plugin calls with
the drop point; anywhere else it falls back to window.open with a popup geometry. Either way the
new window is handed the Session as ?dshTab=<sessionId> on this page's own address, reads it on
boot through the official openSession, and strips the parameter with history.replaceState — so
the mechanism does not depend on who created the window. The launch token is not forwarded; the
signed cookie is enough.pointer-events: none (its own controls opt back in) — otherwise
it swallowed the click on the sidebar-collapse button, which then looked broken — and the free
space after the tabs is a separate sibling drag region, because the strip must stay no-drag for
tab dragging and wheel scrolling to work.# 1. syntax
node --check index.js && node --check lib/client.js
# 2. headless logic (93 checks): registration, the per-window store, restore, switching,
# closing, middle click, reorder, tear-off into a new window, a refused shell,
# the ?dshTab hand-off, housekeeping, the new-session square, and the caption-row geometry
node tools/smoke.mjs
# 3. a running GUI is serving THIS revision (needs a live host and its launcher log)
node tools/verify-live.mjs
# 4. the pre-publish gate: no personal data anywhere, no forbidden APIs in the payload
node tools/audit-public.mjs
tools/smoke.mjs runs the browser half against a mock React (hooks with persistent cells), a mock
DOM (elements, rects, ref, attribute queries) and a mock client context (slots, locale, the Session
Controller, the navigation service) — no browser, no host, no model call.
tools/verify-live.mjs reads the last dsh web: <url> line a launcher wrote, authenticates with the
launch token, and then proves two things: that dsh-session-tabs is a row of the boot graph the page
receives, and that the served bundle carries this revision's markers. It is read-only.
The plugin payload (index.js, lib/client.js, cordis.patch.yml, package.json) is deliberately
boring, and tools/audit-public.mjs fails the build if that changes:
fetch, XMLHttpRequest, WebSocket, EventSource, sendBeacon, no
dynamic import. The plugin talks only to the client's own services.localStorage / sessionStorage calls, all of
them the per-window state described above: a window id, a detached-window flag, the tab list behind
that id, and one knob for the caption inset. The keys hold Session ids, the order they were opened
in, and the label the app already shows on screen — not message content. No cookies, no
indexedDB. The audit prints every declared hit with its reason and fails on the first one beyond
the declared count.eval, no new Function, no document.write, no
dangerouslySetInnerHTML, no innerHTML assignment. All UI goes through React elements, and the
stylesheet is a static string inserted once as a <style> tag.tools/ (read-only: the boot graph and the served bundle), and only at the URL you pass.Personal data: the repository contains no user paths, no e-mail addresses, no tokens and no logs.
tools/audit-public.mjs scans the payload and the whole tree for those, so a re-run is a one-liner
instead of a promise.
uiWorkspace
service (selection, openSession, startSession, clearMain), and dsh.client.inject lists
@deepseek-ai/dsh-client-ui-workspace so that module is loaded before this one.uiWorkspace the strip still draws correctly but shows no
tabs, because there is no selection to reconcile against. That is the one seam this plugin needs;
if a future client moves it, the strip fails visibly rather than half-working.titleBarStyle: 'hidden' with titleBarOverlay, as this account's own shell does). Without it the
plugin falls back to a plain band inside the page, and nothing is lost.engines), browser-side: whatever the DSH web client runs on.window.open and gives the page no IPC for
creating a window. So the tab stays where it is and a hint explains why instead of failing
silently. Tear-off works in a shell that exposes the openSessionWindow bridge and in an ordinary
browser.There is no diagnostics object in this plugin; the strip reports through the client's own logger with
a dsh-session-tabs: prefix — mount failures, a shell that refused the new window, a window.open
that failed, a Session that is gone, a click that could not leave a global panel. Those lines appear
in the page console (F12) and in the host's log.
Two things worth knowing when the strip misbehaves:
window.__DSH_TABS_LEAD__ = <px> set before the page boots, with
localStorage['dsh.session-tabs.lead'] = <px> (then reload), or with the --dsh-session-tabs-lead
custom property on <html>; 0 disables the reserve and leaves the measured chrome only.document.querySelector('[data-slot-error]')
naming shell.overlay means the entry was retired and only a page reload brings it back.MIT — see LICENSE.
Плагин для веб-интерфейса DeepSeek Harness: открытые сессии становятся вкладками в полосе над всем
окном — как в браузере. Клик переключает, × / средняя кнопка / контекстное меню закрывают
(сессия при этом не удаляется), перетаскивание меняет порядок, а вытаскивание вкладки за пределы
полосы отцепляет её в отдельное окно. Список вкладок окна переживает перезагрузку страницы.
Состояние вкладок — на окно: sessionStorage хранит идентификатор окна, localStorage — список
вкладок этого окна, поэтому несколько окон показывают разные сессии одновременно.
Отцепление идёт через мост оболочки window.dshShell.openSessionWindow({href,x,y,width,height}), а
новое окно получает сессию параметром ?dshTab=<sessionId>; в обычном браузере используется
window.open. В окне официального приложения DeepSeek Harness отцепить вкладку невозможно: хост
запрещает все window.open и не даёт странице IPC для создания окна — вкладка остаётся на месте и
показывает подсказку с причиной.
Когда оболочка отдаёт странице полосу заголовка (caption row), вкладки встают вплотную к верху окна, нативные кнопки Windows остаются нативными, а их цвет берётся из темы. Полоса сдвинута правее кнопки панели и нативного меню и прозрачна для кликов, кроме своих элементов.
Проверки: npm run check, npm run smoke (93 проверки), npm run audit; npm run verify требует
живого GUI. Лицензия MIT.