DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

0xshawn /

0xshawn/dsh-dev-plugins

Verified

Serve the DeepSeek Harness (dsh) Web UI through a non-localhost authority: trusted hosts, loopback page parity, pinned launch token, no patching.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@b72f96bb

dsh-dev-plugins

license node

A growing collection of plugins for the DeepSeek Harness (dsh), installed as one bundle. The current release serves the dsh Web UI through a non-localhost authority — a reverse proxy, SSH tunnel, or LAN address — without patching the installed dsh, and adds browser notifications, a workspace token-usage page, and a composer where Enter inserts a line break.

中文文档

What this fixes

Stock dsh web is built for a browser on the same machine. Six things are missing or break when the browser reaches it through another authority:

Problem Stock dsh This plugin
/api Host/Origin fence accepts loopback, the listener's bind literal, and --trusted-host only; every RPC answers 403 for a proxy authority appends DSH_DEV_PLUGINS_TRUSTED_HOSTS authorities to the same list (--trusted-host keeps working)
Page view (connection.isLoopback) derived from location.hostname; a proxied page is treated as non-loopback, so on dsh 0.2.0 settings stay in process-local memory (settings are unavailable in this browser on the Models page) and on every version the host-document action is hidden injects __DSH_TRANSPORT__ = { ownsHost: true }, the host-owned transport hook dsh already uses for its desktop shell, so authenticated pages report the loopback view
Launch token random per process; the only way to find the login URL after a restart is the service log pins DSH_WEB_TOKEN (or plugin config) across restarts
First login opening the bare deployment URL answers a plain 401, so the token must come from the printed ?token= URL serves an HTML token form on the bare URL (loginPage, default on) and feeds the submitted token back into the stock exchange
Background attention a finished session or a pending approval/question stays invisible while the tab is in the background raises a Web Notification for completion and for pending input (notifications, default on)
Token usage history the chat stats strip only shows the open Session; there is no workspace or cross-session view adds a sidebar Token usage page that aggregates provider-billed tokens by workspace and day from the Host's durable session logs (statsPage, default on)

Authentication is unchanged: the browser still has to exchange the launch token for the signed, authority-bound session cookie, and the Host/Origin fence still runs before every /api request. This plugin removes deployment friction; it does not bypass access control.

Install

# from GitHub (public repository, no build step)
dsh plugin --profile web add git+https://github.com/0xshawn/dsh-dev-plugins.git

# from a local checkout
dsh plugin --profile web add /path/to/dsh-dev-plugins

Restart dsh web afterwards — dsh loads bundles at boot. Verify the row is active:

dsh --profile web --dump-config | grep -A6 'dsh-dev-plugins'

Remove with dsh plugin --profile web remove dsh-dev-plugins; the stock behaviour returns on the next restart.

Renamed in 0.5.0. The package was dsh-remote-access before; the profile row and the environment variable are now dsh-dev-plugins and DSH_DEV_PLUGINS_TRUSTED_HOSTS. Remove the old row before adding this one, and rename the variable in the service environment.

Migrating from a source patch

If a deployment currently patches the installed dsh bundles (fixed launch token plus a forced loopback classification), the switch is:

  1. Stop applying the patch in the installer; npm install -g @deepseek-ai/dsh@<version> no longer needs a follow-up patch step.
  2. Install this bundle into the web profile (above) and restart dsh web.
  3. Keep the deployment's DSH_WEB_TOKEN; it is read by the same name. Keep --trusted-host or move the authorities to DSH_DEV_PLUGINS_TRUSTED_HOSTS.
  4. Confirm the startup line still prints the pinned token: dsh web: ...?token=<same token>.

Configure

Environment

Variable Value Effect
DSH_DEV_PLUGINS_TRUSTED_HOSTS comma-separated bare authorities, e.g. dsh.example.com,dsh.example.com:8443 appended to --trusted-host; a port-less entry matches any port
DSH_WEB_TOKEN non-empty launch token pins the login token; empty/unset keeps the random per-process token

Both work in systemd Environment=/EnvironmentFile=, launchd EnvironmentVariables, Docker env, or a shell profile. Keep --trusted-host if you prefer flags; the expression appends the environment authorities to the invocation list. Note that a patch layer replaces the whole connection.config: if another layer (or a later profile patch) overrides connection, restate trustedHosts there — otherwise the environment authorities are dropped — and restate any other key you still need, such as cookieMaxAgeDays or recovery.

DSH_WEB_TOKEN="$(cat /etc/dsh/web-token)" \
DSH_DEV_PLUGINS_TRUSTED_HOSTS="dsh.example.com" \
dsh web --host 127.0.0.1 --port 3080 --no-open

Malformed authorities (a scheme, path, userinfo, or zero-padded port) abort boot with the upstream validation error instead of being silently ignored.

Plugin config

Override the dsh-dev-plugins row in the profile patch ($DSH_HOME/profiles/web/cordis.patch.yml) for per-profile values:

- id: dsh-dev-plugins
  config:
    # false keeps the stock per-authority page view.
    loopback: true
    # Fixed token; empty falls back to DSH_WEB_TOKEN, then the random token.
    # Prefer DSH_WEB_TOKEN: profile configuration is plain text.
    launchToken: ''
    # Serve a token entry form when the bare URL is opened without a token
    # or cookie. false restores the stock plain 401.
    loginPage: true
    # Enable the browser-notification client half.
    notifications: true
    # Enable the sidebar token statistics page and its read-only data route.
    statsPage: true
    # Make Enter insert a line break in the chat composer.
    composerNewline: true
    # Enable the browser-control MCP tools (applies after dsh web restarts).
    browserControl: true

Settings → Dev plugins exposes the same switches with a form, so the values can be changed without editing this file.

First login without the token URL

An unauthenticated browser that opens the bare deployment URL gets a small sign-in form instead of the plain 401. The form submits the token back to the relative index path (GET ./?token=...), so the stock token exchange, signed cookie, proxy mount handling, and redirect to the clean URL all still apply. A wrong token simply re-renders the form with an error; a valid token logs in.

The form is served only when the request authority already passes the deployment's Host/Origin fence (loopback, the bind literal, or a DSH_DEV_PLUGINS_TRUSTED_HOSTS/--trusted-host authority). An untrusted authority keeps the plain 401, so a DNS-rebinding or foreign host cannot collect the token. The token still travels in the query string exactly like the printed URL, and it remains the only credential; the form does not introduce a second password store or a bypass. Set loginPage: false to restore the stock 401 if a deployment fronting the port has its own login page.

Behind a reverse proxy

--public-url https://dsh.example.com/ advertises the address browsers use (printed URL, DSH_WEB_URL, runtime context); this plugin does not change that. The proxy must still:

  • preserve the browser-facing Host header (the fence compares it verbatim);
  • strip any mount prefix before forwarding to the listener;
  • forward WebSocket upgrades with Upgrade/Connection intact;
  • rewrite cookie Path to the mount, and add Secure on the HTTPS leg.

See upstream Publish the Web UI behind a reverse proxy for the full checklist. TLS termination, network isolation, and who may reach the port remain the operator's job.

Browser notifications

The bundle also ships a browser client half (plain hand-written bundle, no build step). It watches the Session status source and raises a Web Notification when:

  • a top-level session transitions from running to idle — Task finished: <title>;
  • the same transition carries a recent Host agent error — Task failed: <title>, with the first line of the error as the body;
  • a session starts waiting for approval, plan review, or an answer — Approval needed / Plan review needed / Input needed.

Failures are read from the forwarded api-session/error event and paired with the following idle transition, so a failed model call, aborted turn, or tool loop reports the failure instead of a generic completion. Any pending interaction registered through uiSession gets a notification; the current dsh build registers approval and user questions (including plan review), and an unknown future domain falls back to the generic Input needed copy.

A background Session's completion or failure notifies even while the page is focused. The Session currently open in the page notifies only while the page is hidden or the window is unfocused; pending-interaction notifications are not gated by the main view. A notification is held for three seconds after a background Session goes idle.

Tabs coordinate through local storage: a focused tab publishes the Session it shows, other tabs suppress notifications for that Session, and one tab claims each event so hidden tabs do not duplicate it. The client also fetches the served boot manifest every 15 seconds and reloads itself when the plugin bundle revision changes, so an open page cannot keep running stale notification code after a hot update (the first release with this check still needs one manual refresh). If a retry, queued continuation, or new pending input resumes the session in that window, the notification is cancelled; this keeps transient failures that immediately retry from producing an invalid notification.

Notifications appear only while the page is hidden or the window is unfocused, and only after the browser grants notification permission. The page asks once on the first click or keypress; grant it for the deployment origin. Clicking a notification brings the dsh window back to the foreground and opens the notified Session in this page. Subagent and removed sessions are skipped, and an unknown or not-yet-listed Session is never guessed into a notification. Set notifications: false in the plugin config to turn the notification half off (the composer Enter rewrite stays active).

Limitations: this uses page Web Notifications, not Web Push. The dsh page must stay open in a tab (it may be backgrounded); a fully closed browser receives nothing. Permission requires a secure context, so use HTTPS or loopback, not plain HTTP on a LAN address. Notification text includes the session title, so lock-screen previews may expose it.

Token usage statistics

The sidebar gains a Token usage entry that opens a page with:

  • time presets (today, last 7 days, last 30 days, all time, custom range), with the day boundary taken from the browser's IANA time zone; the control uses the active light or dark theme colors;
  • a daily bar chart of provider-billed tokens;
  • summary cards for the total plus uncached input, output, cache read, and cache write;
  • a workspace table with session counts and the same four buckets.

The data comes from the Host's durable session logs, read through ctx.sessionQuery without activating any Agent. The plugin replays the same provider usage samples the stock tokenUsage projection folds — one settlement per Turn/step, with retried steps counting their replacement attempt — and skips fork-inherited event prefixes so a forked Session does not double-count its parent's usage. Subagent Sessions are included and attributed to the workspace of their parent chain. Sessions not registered in a workspace fall back to a per-directory row; Sessions with no directory at all share one "Other sessions" row.

The first request scans the complete history and may take a few seconds; later requests reuse a per-Session fold cache that the live session/event feed invalidates. The page's Refresh button forces a full rescan. Usage is attributed to the local day of the event that settled the billed sample, so a Turn crossing midnight stays on the day it settled. Set statsPage: false to remove both the sidebar page and its data route.

The page reads GET /dsh-dev-plugins/stats/data?from=<ms>&to=<ms>&tz=<IANA> (from/to optional, refresh=1 forces a rescan). The route is read-only and applies the same trusted-authority and launch-token cookie checks as /api; it returns only token totals, workspace paths, and day keys, never message content.

Settings page

Settings → Dev plugins is a form with one switch per bundle feature, all on by default: loopback page view, token sign-in page, browser notifications, token usage page, composer Enter rewrite, and browser control. Writes go through the Host configuration form, so they land in the profile patch as ordinary plugin config and survive restarts.

Host-side switches are volatile config: the running plugin reads them live, so the sign-in page and the statistics route follow a change without a restart, including after a Host plugin reload. Switches that change the browser half (notifications, the statistics entry, the composer rewrite) need a page reload; the page shows a reload banner after a write. Browser control is a boot-time MCP child process, so its switch needs a dsh web restart.

Composer: Enter inserts a line break

The dsh chat composer submits on Enter and inserts a line break on Shift+Enter. This bundle flips the plain Enter key: it now inserts a line break, and a message is sent with Cmd/Ctrl+Enter or the Send button. The rewrite targets the composer's Lexical editor only — a plain Enter in any other input, and Enter while a slash-command, mention, or file menu is open, keep their stock behaviour, and IME composition is left alone. Turn it off with the composerNewline switch on the settings page (or composerNewline: false in the plugin config). While a session is running, the keyboard send uses dsh's accelerated submit, so it does not follow the busy-Enter queue/steer preference the Send button still honours.

Browser control

The bundle also ships a Chrome extension plus a local bridge that exposes the browser as dsh tools, so the model can drive your local Chrome during a session with the same fidelity as DevTools: click, type, navigate, wait, screenshot, read the DOM, run page JavaScript, inspect network traffic, and manage cookies/storage. Tools appear as mcp__browser__* and the bridge is spawned by the bundle's mcp-browser row — no extra MCP configuration.

Control is fully automatic: the bridge attaches the tab it needs and may switch tabs, so anything open in that Chrome can be read or operated. Only install the extension when you accept that scope. dsh's own approval prompts still apply (each tool call may ask once; dsh has no allow-always rule), so pair this with a permission preset you are comfortable with.

Install. The bridge needs nothing beyond the bundle, and the extension connects with a built-in token — no pairing step. For the extension:

  1. Open chrome://extensions, enable Developer mode, choose Load unpacked, and select the extension/ directory of this package (for a profile install: ~/.dsh/profiles/web/node_modules/dsh-dev-plugins/extension). Chrome asks for debugger, tabs, storage, alarms, and downloads.
  2. Restart dsh web so the bundle's mcp-browser row spawns the bridge. The extension reconnects by itself; the popup shows Connected and the attached tabs. The popup's optional Token override field is only needed when the bridge sets DSH_BROWSER_BRIDGE_TOKEN. Coming from 0.7.0/0.7.1: the old random 64-hex token is cleared once; a custom hex-shaped override has to be re-entered once.

Requires Chrome 120+ on the machine that runs dsh web; the debugger attachment shows Chrome's "being controlled by automated test software" banner while attached. The bridge binds 127.0.0.1 only.

Tools (all return JSON; screenshots also return an image block):

tool purpose
tabs list, open, select, close tabs
navigate URL, back, forward, reload; waits for load by default
observe URL, title, page text, and interactive elements with stable refs
screenshot viewport, full page, element, or ref
wait element, text, network idle, or timeout
click real mouse click by ref (or CSS selector)
type type text into a field, optionally clear first or submit
key key chords such as Control+A, Shift+Enter, Escape
scroll element or viewport
dialog accept/dismiss alert/confirm/prompt
evaluate run JavaScript in the page, return a JSON value
network start/stop capture, list requests, fetch one response body (2 MB cap)
upload set a file input from an allowed local directory
download fetch a URL into the local download directory, or list recent Chrome downloads
cookies get, set, clear cookies
storage get, set, clear localStorage/sessionStorage

Interactive tools accept ref first and fall back to a CSS selector; evaluate is the escape hatch for everything else. A ref stays valid until the next observe, a navigation, or a tab change, and a stale ref fails with a message telling the model to observe again.

Security and limits. Everything is local: WebSocket on 127.0.0.1, a shared token (fixed built-in by default, overridable), and an Origin: chrome-extension://<id> check (optionally pinned to an extension-id allowlist — recommended when another local extension must not be able to connect). Every tool call is appended to $DSH_HOME/browser-bridge/audit.jsonl without secrets or typed text. upload only reads real files under DSH_BROWSER_BRIDGE_UPLOAD_ROOTS (default ~/Downloads,~/Desktop), download op:"url" writes only under DSH_BROWSER_BRIDGE_DOWNLOAD_DIR (default ~/Downloads/dsh-browser) — page-triggered downloads keep Chrome's own download folder. Screenshots are returned as image blocks and never written, and network bodies stay in memory. Risky tools can be switched off individually with DSH_BROWSER_BRIDGE_DISABLE, and DSH_BROWSER_BRIDGE=0 exits the bridge so no browser tools are registered. The settings page's Browser control switch writes the same intent through the plugin config; the bridge reads it at startup, so restart dsh web after changing it.

variable default meaning
DSH_BROWSER_BRIDGE 1 0 disables the bridge and its tools (the settings switch has the same effect after a restart)
DSH_BROWSER_BRIDGE_PORT 8790 local WebSocket port
DSH_BROWSER_BRIDGE_TOKEN built-in fixed value token override for private deployments
DSH_BROWSER_BRIDGE_HOME $DSH_HOME/browser-bridge audit log
DSH_BROWSER_BRIDGE_EXTENSION_ID empty allowed extension ids
DSH_BROWSER_BRIDGE_UPLOAD_ROOTS ~/Downloads,~/Desktop upload roots
DSH_BROWSER_BRIDGE_DOWNLOAD_DIR ~/Downloads/dsh-browser bridge-side download destination
DSH_BROWSER_BRIDGE_DISABLE empty comma-separated disabled tools
DSH_BROWSER_BRIDGE_NETWORK_LIMIT 500 captured request ring buffer

On a headless server there is no Chrome to attach, so leave the extension uninstalled and the bridge idle (or set DSH_BROWSER_BRIDGE=0).

How it works

The package is a dsh bundle: cordis.patch.yml is a patch layer over @deepseek-ai/dsh-base + @deepseek-ai/dsh-web-app, and several seams are used.

  1. Trusted hosts — patch layer. The connection row's trustedHosts gets the environment list concatenated onto the invocation list. The expression reads ctx.get('webRuntime') (dsh 0.2.0, which already carries the bind-derived LAN literals and CLI extras) or ctx.get('webStartup') (dsh 0.2.1); 0.2.0 provides both services, so runtime must win to keep a wildcard bind's LAN authorities. One patch covers both layouts. The Host/Origin fence and the allowsRemoteAuthorities classification are then upstream's own.

  2. Loopback view — index injection. The host plugin listens to webserver/index-inject and adds one inline classic script to the served index:

    if (globalThis.__DSH_TRANSPORT__ === void 0) globalThis.__DSH_TRANSPORT__ = { ownsHost: true };
    

    ClientTransportHooks.ownsHost is upstream's flag for "this page owns the Host", documented as reserved for a shell that assembles its own transport; upstream also states that served pages never carry the global. This bundle sets it deliberately to obtain owner parity: dsh-client-connection reports the loopback view without changing the page's HTTP + WebSocket carrier. The guard leaves any real shell-provided transport untouched, and the script runs before the shell entry and every client plugin.

  3. Launch token — service field. The plugin waits for ctx.connection and sets connection.browserAuth.launchToken before the loader settles, so both the printed authenticated URL and token validation use the pinned value. If a future dsh version hides that field, the plugin fails loudly instead of silently printing a random URL.

  4. Login form — index authorization. The plugin wraps connection.authorizeIndex, the seam dsh-host-frontend-static calls before serving the index. Unauthenticated GET index requests from a trusted authority get the token form (401 with HTML); requests that are authenticated, carry a valid token, come from an untrusted authority, or use another method delegate to the stock implementation. The wrapper is restored on plugin disposal. If a future dsh version drops that method, the form is silently disabled and the stock 401 remains.

  5. Notifications — client plugin. package.json declares dsh.client with platform: web and exports ./client; dsh-client-modules serves the browser bundle to the page. The client subscribes to ctx.uiSession.sessionStatus through the sessions and uiSession services and raises local Web Notifications; ctx.remote.$on('api-session/error') supplies failure details for the next idle transition. The Host only injects the __DSH_DEV_PLUGINS_NOTIFY__ feature flag from config.notifications.

  6. Token statistics — Host route plus client panel. Each Session log is folded into provider usage settlements (assistant/message/assistant/attempt usage samples, replacement and retry aware) and cached per Session id. A session/event listener marks the Session dirty, so the next request rereads only what changed. The Host registers the exact route /dsh-dev-plugins/stats/data on ctx.webServer and guards it with connection.isTrusted and connection.browserAuth.isAuthenticated. The browser half registers a main panel and a sidebar.panellist entry; the Host injects __DSH_DEV_PLUGINS_STATS__ from config.statsPage.

  7. Browser control — MCP bridge plus extension. The bundle patch inserts a @deepseek-ai/dsh-mcp-client row (mcp-browser, serverName: browser) whose stdio command is the running Node interpreter and whose argument is bridge/index.js, resolved from $DSH_HOME/profiles/$DSH_PROFILE/node_modules/dsh-dev-plugins/. The bridge serves MCP tools and a localhost WebSocket relay; the MV3 extension connects to the relay, owns chrome.debugger, and forwards CDP commands and events. Tool calls and the page content they touch never leave the machine.

Security

  • The launch token is the credential. Anyone holding the printed ?token= URL can obtain a session cookie and full owner access. Share it deliberately. A pinned token is a long-lived credential — never commit it. Changing DSH_WEB_TOKEN and restarting invalidates the old URL but does not revoke cookies already issued (they live for cookieMaxAgeDays, 30 days by default); clear the site's cookies, or remove the browser-session credential record and restart, to revoke them.
  • Trusted hosts are not authentication. They only admit the authority to the Host/Origin fence; the signed session cookie still gates every RPC. Never expose the listener port to an untrusted network without TLS and a proxy you control.
  • Loopback parity is a privilege decision. On dsh 0.2.0 it makes settings host-backed for authenticated remote browsers; on every version it exposes local-only UI affordances such as Open configuration file, which opens the settings document in the host's editor. Remote browsers already hold owner credentials, but operators who want the stock least-privilege view can set loopback: false.
  • The loopback view goes beyond a documented client contract. Upstream documents ownsHost as reserved for a shell that assembles its own transport and says served pages never carry the global. This bundle sets it anyway; every current dsh consumer reads it only for isLoopback, but a future or third-party client plugin could treat it as proof of host ownership. loopback: false removes that exposure.
  • Plain HTTP leaks the token. Terminate TLS at the proxy or with --tls-cert/--tls-key; the upstream guide covers cookie Secure behaviour.
  • The login form does not rate-limit. It only reveals an input for the same high-entropy launch token the printed URL carries; a proxy-level rate limit is still the right control for exposed endpoints. The form never renders for an authority the Host/Origin fence rejects.
  • The statistics route exposes usage metadata. It is authenticated with the same launch-token cookie and trusted-authority fence as /api, and it returns only per-workspace and per-day token totals plus workspace paths — never prompts, responses, or tool arguments. Anyone with the owner credential already has the full UI; statsPage: false removes the route.
  • Do not add a CSP that blocks inline scripts. The loopback row is an inline classic script; dsh's own boot globals already require inline script permission, but a proxy-injected CSP without 'unsafe-inline' (or a nonce covering it) silently blocks the row while the page still loads.
  1. Composer Enter — client capture listener. The client half installs one capture-phase keydown listener on the page document. A plain Enter inside the composer's Lexical editor ([data-composer-card] containing [data-lexical-editor="true"]) is cancelled and replayed as the Shift+Enter chord the stock composer already maps to a line break; Cmd/Ctrl+Enter still submits. The rewrite is skipped while a slash-command, mention, or file menu ([data-trigger-menu]) is open, and during IME composition.

Compatibility

  • Tested against dsh 0.2.2-rc.1 (both halves on all three deployed endpoints; the login and settings flows were exercised on the workstation origin and its :9443 proxy), 0.2.1-rc.2, 0.2.1-rc.3, and 0.2.0-rc.2 (Web profile).
  • Declared range: engines.dsh >=0.2.0-0 <0.3.0-0. dsh 0.1.x is not supported or tested, and dsh 0.3.x stays outside the range until the plugin is verified against it. dsh 0.2.2 evaluates the range with prereleases participating, so 0.2.2-rc.1 passes the compatibility gate without a version exemption (install and boot share that evaluator).
  • On dsh 0.2.0 a profile-level wildcard bind (webserver.host: 0.0.0.0) derives LAN authorities; the patch preserves them, so the printed LAN URL works without extra trusted-host entries.
  • The plugin row is Web-profile only; in a profile without a connection service the row stays inactive, so installing it there achieves nothing.
  • npm install -g @deepseek-ai/dsh@<version> replaces the dsh install but not the profile: the plugin keeps working after a dsh upgrade. Restart dsh web to load a new dsh version.
  • The token seam reads connection.browserAuth.launchToken. If dsh changes that shape, the plugin throws at boot when a token is configured; unset the token or update the plugin.
  • Browser control needs Chrome 120+ with the extension loaded unpacked. The debugger permission shows Chrome's automation banner while a tab is attached. On a headless server there is no Chrome, so the bridge stays idle and the tools fail with bridge_disconnected.
  • The patch resolves bridge/index.js from $DSH_HOME and $DSH_PROFILE; a host that launches dsh web without those environment variables falls back to ~/.dsh and the web profile. A different profile name therefore needs the variable set (the stock CLI exports both).
  • The composer Enter rewrite depends on dsh-internal DOM markers ([data-composer-card], [data-lexical-editor="true"], [data-trigger-menu]). If a future dsh release renames them, the listener finds no composer and silently no-ops, restoring stock Enter-sends; it never breaks the page.
  • The login form wraps connection.authorizeIndex and reads browserAuth.isAuthenticated. If a future dsh version removes either, the form is skipped and the bare URL answers the stock plain 401; the rest of the plugin keeps working.
  • Browser notifications need the client module system plus ctx.uiSession.sessionStatus and ctx.sessions; if a future dsh version drops either, the client half stays inert while the Host half keeps working. The page must stay open for notifications; there is no service worker or push subscription. the client session list projects the raw parentSessionId field onto rows as parentId; both shapes suppress a row that carries a parent link, so subagent rows stop notifying.
  • Token statistics need ctx.sessionQuery (listSessions and readSession), ctx.workspaceRegistry (optional; an absent registry falls back to per-directory grouping), and ctx.webServer.register. When the session query service is missing, the data route answers 503 and the panel shows its load error.
  • The pin runs once at startup. A host-plugin reload (HMR) can rebuild the Connection service from dsh's cached per-process token and will not reprint the startup URL; restart dsh web after changing the token or the dsh version.

Development

npm install
npm test        # node:test unit tests for config, token resolution, index injection, usage folding, and route auth

End-to-end check on an isolated home (no effect on your real profile):

export DSH_HOME=$(mktemp -d)
dsh plugin --profile web add "$PWD"
DSH_WEB_TOKEN=dev-token DSH_DEV_PLUGINS_TRUSTED_HOSTS=dsh.test \
  dsh --profile web --host 127.0.0.1 --port 3199 --no-open
# then open http://<non-localhost>:3199/?token=dev-token through your proxy

License

MIT © 2026 shawn

—/ 5

No ratings yet

Verified DSH bundle

Commit b72f96bb549d

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout