deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:nonmean/dsh-lan-access
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
A DeepSeek Harness web plugin that adds a LAN access toggle to the DSH
Settings shell (Settings → General). It replaces the manual cordis.patch.yml
webserver override:
Tested with DeepSeek Harness
v0.2.1-alpha.1— this plugin is verified runnable against that harness version.
0.0.0.0, so other machines on the same network
can open it at http://<LAN-IP>:3080/?token=…. dsh web prints the full
URL (with the per-process ?token= launch token) for the LAN address when
it starts — a fresh LAN browser needs that token in the URL to authenticate.
The /api trust fence is updated live, so the browser on a LAN machine works
fully (chat, tools, workspace).127.0.0.1 again (loopback only — the safe default).The DSH web GUI opened from another machine on the same network
(http://192.168.0.101:3080):

The LAN access toggle in Settings → General, showing the address other devices can open:

| Half | File | Role |
|---|---|---|
| Host | src/index.ts |
Owns a plugin-local persisted flag ($DSH_HOME/lan-access.json), the fenced /lan-access JSON route (GET state / POST set), the bind controller, and the lanAccess bind-host service. The webserver row's composed host expression reads that service, so every webserver (re)start — boot, toggle, or a post-boot user-patch re-apply — converges to the persisted setting. A toggle restarts the webserver fiber only when the bind differs, using fiber.update(config, noSave): the no-save path keeps the composed tree out of cordis.yml, which would otherwise trigger an HMR subtree reload. |
| Client | src/client/ |
Registers the General-settings row (settings.general.item, order 15) with a native checkbox switch, the LAN URLs (primary first, all live NIC addresses shown, copy button), zh/en copy, and restart-tolerant polling. |
The route fence accepts loopback or the deployment's trusted authorities, read live from the connection row's resolved config — the same boundary the /api gateway uses. Cross-site requests are refused.
The built artifacts (lib/) are committed, so installation needs no build
step and no modification of the DeepSeek Harness checkout:
# From GitHub
dsh plugin --profile web add git+https://github.com/nonmean/dsh-lan-access.git
# ...or clone and install the local checkout (link: keeps your rebuilds live)
git clone https://github.com/nonmean/dsh-lan-access.git
dsh plugin --profile web add link:/path/to/dsh-lan-access
# Restart the GUI
dsh web
The install appends dsh-lan-access to dsh.profile.bundles; its
dsh.bundle.patch inserts the host row and overrides the webserver row's
host with the lanAccess service expression. The client half is picked up
by the client-modules scanner automatically. No harness change is required
for the core feature — the toggle, the LAN bind, and the live /api trust
fence all ship inside the plugin.
Local development — rebuild with
pnpm build(ornpm run build) after changingsrc/, then reinstall/restart. The repo'snode_modulesmirrors the DSH profile's package farm (TypeScript/tsdown come from the harness checkout).
Migrating from a manual patch — remove any
webserverhost: 0.0.0.0override from the profile'scordis.patch.yml(and the bundle patch layers) so the plugin is the single owner of the bind host.
Open the GUI, go to Settings (sidebar footer) → General.
Flip 局域网访问 / LAN access.
http://192.168.x.x:3080) —
with a copy button.?token=… when opened
from another machine. dsh web prints the full URL (with the token)
for both the loopback and the LAN address on startup — copy the LAN one,
e.g. http://192.168.0.101:3080/?token=ICKD2317KYP…. The token is a
per-process launch token that exchanges for a session cookie; a fresh LAN
browser cannot authenticate without it.crypto.randomUUID polyfill on plain-HTTP
LAN origins (that Web API only exists in secure contexts, and the DSH
API client mints every RPC id with it — without the polyfill a remote
browser fails with "crypto.randomUUID is not a function").The choice is persisted by the plugin in $DSH_HOME/lan-access.json
(~/.dsh/lan-access.json by default):
{
"enabled": true
}
Version 0.3.0 targets DeepSeek Harness v0.2.1-alpha.1 (peers
^0.2.1-alpha.1). The harness now gates each bundle by its declared DSH peer
range, so this release is skipped on 0.1.x harnesses — install the plugin
version that matches the harness you run (0.2.0 was the v0.1.7-rc.2 line).
On v0.2.1+, upgrade in place:
A full upgrade needs both halves (host + client) and a dsh web restart. The
host half now owns its persistence, so the harness settings namespace is no
longer used:
$DSH_HOME/lan-access.json. The old
lan-access: section in ~/.dsh/settings.yaml.imported is not read; flip
the switch once after upgrading.lib/) so the host and client halves match; the
client bundle is cached until the next dsh web.Everything the plugin serves works from a LAN browser with zero modification of the DSH checkout:
/api gateway trusts the served LAN authority, so the
configuration plane (settings.*, credentials.*) reaches the host
directly — the Host/Origin fence admits the LAN host and the ordinary
browser-session auth authenticates it. The Models page provider directory,
the Plugins configuration cards, and the Language/Appearance rows therefore
work remotely with no extra hop.connection.isLoopback to "loopback OR served LAN authority" at runtime.
The client entry injects connection and is marked
dsh.client.immediately, so its bundle is prefetched and its apply
runs right after the connection row provides the handle — before any
settings surface (which waits on remote) reads
remote.$host.isLoopback. That inject-ordered widening is what keeps
the scope in host mode on a LAN page: without it, a surface that binds early
sees the unpatched isLoopback and its persistence stays memory-mode — the
plugin configuration cards render nothing.crypto.randomUUID does not exist on plain-HTTP LAN origins. The
bundle installs a getRandomValues-based polyfill (same CSPRNG).v0.2.1-alpha.1 added a plugin admission gate: before a profile imports a
bundle, DSH checks that bundle's peerDependencies on @deepseek-ai/dsh and
@deepseek-ai/dsh-* against the single runtime version and skips the bundle
when any declared range does not match (prereleases participate in range
matching). A plugin that declares only the previous harness line is therefore
silently dropped at startup with a skipping profile bundle line. This
release targets the current runtime line — every DSH peer is
^0.2.1-alpha.1 — so the bundle is admitted again. The host and client
service contracts it consumes (webServer, settings, credentials,
loader, connection, slots, locale, and the
settings.general.item slot) are unchanged in this version, so this port is
a peer-range/version bump, not a rewrite. Rebuild lib/ after upgrading.
The harness replaced its standalone, file-backed settings provider with
profile-backed Config forms in v0.1.7-alpha.1:
@deepseek-ai/dsh-settings dropped settings.register(ns, schema) /
SettingsScope (and the dsh-settings-file package) in favour of
SettingsForms, which projects each plugin entry's volatile Config. A
settings write runs inside an HMR transaction (configEditor.edit →
hmr.runExclusive) that reconciles the whole profile. Because this plugin
must restart the web server to change the bind, routing the toggle through
that plane would restart the server inside the transaction and taint its
async context. The plugin therefore persists its own flag
($DSH_HOME/lan-access.json) and restarts the web server fiber directly
(fiber.update(config, true)), staying off the harness settings plane.Earlier, the harness evolved the settings/connection APIs between
0.1.0-rc.5 and 0.1.5-alpha.1; the plugin was updated accordingly:
@deepseek-ai/dsh-settings dropped the settingsNamespace(ns) helper —
settings.register / .update / .replace / .mutate now take the raw
namespace string (validated at runtime and by a compile-time guard).ConnectionHandle no longer carries an api member — remote
methods go through connection.rpc.call('/api', '<ns>/<method>', …) — so
the plugin no longer patches connection.api.settings.* /
connection.api.credentials.*./api gateway stopped pinning the configuration plane to loopback: it
now trusts the served LAN authority, so the settings/credentials RPCs reach
the host directly. The plugin therefore widens connection.isLoopback early
(via inject: ['connection'] plus a synchronous patch) to keep the client
settings persistence in host mode on a LAN page; the fenced /lan-access/rpc
proxy is no longer required for the remote Settings surfaces.Remaining loopback-only (hardcoded in the harness, not patchable from a
plugin): host.pickDirectory / host.openPath (native dialogs and host
file opens) and llm.discoverModels (the Models page "discover" button).
The workspace's own add/browse flow does not need them, and chat file
opens route into the sidebar editor.
The host exposes GET /lan-access/diag (fenced like the other routes) with
the latest browser boot reports: slot-registration counts, whether the
connection patch is active, and the plugin-item slot ledger. During the first
minute after boot the browser also posts a
2-second poll of the Plugins cards' own injected snapshots (available
flags), the slot ledger view, and the declared spec — the exact data that
separates "cards gone", "cards abdicated", and "cards present but rendering
null" when a Settings page misbehaves on a remote machine.
dsh-better-sidebar's trust fence matched the connection row by the wrong
name and read the raw !!js config, so its panels (explorer / editor /
terminal / git) only ever accepted loopback. The repo ships the fix as a
profile-level pnpm patch (no harness change):
./scripts/install-patches.sh web
This copies patches/dsh-better-sidebar.patch into the profile's
patches/ directory, registers it under patchedDependencies in
pnpm-workspace.yaml, and runs pnpm install.
--host 0.0.0.0 for the same reason: binding all interfaces exposes the
agent's tools to the network. Only enable it on a trusted network.pnpm build # tsdown: lib/index.js (host) + lib/client.js (browser bundle)
pnpm typecheck # tsc --noEmit
The client bundle is a __ModuleLoader__.load closure-factory artifact (same
format as the DSH monorepo's tsdown client preset); only the frozen
platform-module table words stay external. After changing client code, rebuild
and restart dsh web (the client-modules package metadata cache expires only
on restart).
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。