dsh-plugin-doc-present
Doc Present for the DeepSeek Harness (dsh): let the agent show plans and
designs instead of dumping long text into the chat.
When the agent has a proposal to present — an architecture, a flow, a state
machine, a message sequence, a numeric curve, a UI walkthrough — it publishes a
self-contained interactive HTML page (inline CSS/JS, FX animation
framework included) and you view it in an in-GUI panel or any browser,
including colleagues on the LAN.
方案展示插件:agent 把方案做成自包含 HTML 动态演示页(状态机/时序/曲线/UI
四类动图配方),你在 GUI 内嵌面板或浏览器里看,替代对话框长文。
Surfaces
| Surface | What you get |
|---|
| In-GUI panel | 📄 方案预览 button in the session header opens a drawer with the doc list and a live iframe preview (same-origin route /doc-present/... on the dsh web server — no CORS, no extra port needed for local viewing). |
| Tool card | Each doc_present tool call renders a card in the conversation with 在面板中打开 / 浏览器打开 / 复制内网链接 buttons. |
| LAN server | A standalone preview server on 0.0.0.0 (default port 8899, auto-scans upward if occupied) so colleagues open http://<your-LAN-IP>:<port>/<slug>/ directly. Started lazily on first publish; survives nothing but a dsh restart (docs persist on disk; it re-ensures itself on next use). |
Repository layout
package.json plugin manifest (dsh.client declaration, exports["./client"])
lib/index.js host half: doc_present tool, /doc-present command, prompt section, /doc-present web route
lib/server.js document server core (pure Node, no dsh imports — standalone-testable)
lib/client.js client half: header button + overlay drawer + doc_present tool card
assets/template.html self-contained doc template with the FX animation framework
references/ authoring guides the agent reads before writing pages
writing-guide.md skeleton catalog (review / explainer / tutorial / comparison /
diagnosis), badge vocabulary, cross-link convention, self-check table
html-authoring.md FX framework usage + 4 copy-paste demo recipes + publish checklist
scripts/smoke.mjs standalone smoke test for lib/server.js
The doc template, the FX framework, and both guides are ported from the
design-preview agent skill; the preview server is a Node port of its
doc_server.py with the same lifecycle rules (unpublish → 410, delete →
trash, etc.).
What a new page looks like
doc_present(action="new") copies assets/template.html and fills two
placeholders — __DOC_TITLE__ (HTML-escaped) and __DOC_DATE__ (local
YYYY-MM-DD). It also supports an optional __DOC_AUTHOR__ slot: it is
blanked, and the now-empty 作者: span plus its trailing separator are dropped
so the meta line never renders a dangling 作者: ·. No __X__ placeholder
ever reaches a rendered page.
Two deliberate properties of the template, both learned from how pages were
actually written:
- The prose skeleton is a scaffold, not a spec. The shipped body is one
skeleton (design review) plus a minimal
A → B → C demo, every block marked
「待改 / 示例」. The real section-shape guidance lives in
references/writing-guide.md: the guides are what agents actually read
(18/18 publishing sessions), while the template file itself was read once in
18 sessions.
- The first line carries a
TEMPLATE-NOT-EDITED marker so an unedited page
is recognizable; removing it (which the header comment tells the writer to
do) is the signal that the page has real content.
The CSS and the FX framework are the intentionally stable part: they are the
design system copied verbatim into every published page.
Installation
This package is a profile bundle: package.json declares
dsh.bundle.patch, and cordis.patch.yml inserts the host row. The row
declares inject: ["tools", "systemPrompt", "commands", "webServer"], so it
needs the web profile (the webServer service only exists there). One
command installs it — it links the package, registers the bundle and enables
the row — and the change applies immediately through HMR:
plugin_manager action=install_bundle target=E:\dsh\dsh-plugin-doc-present
Do not write the profile's package.json or cordis.patch.yml by hand, and do
not copy or Junction the package into $DSH_HOME\profiles\node_modules.
Why the old copy/Junction method broke on Desktop 0.2.0: a package placed
or linked in $DSH_HOME\profiles\node_modules resolves its @deepseek-ai/*
peers against that same shared directory, which the Desktop module resolver
treats as an obsolete fallback and rejects. install_bundle links the
package under the profile instead, so peers resolve from the app's own
install.
Every session gains the doc_present tool, the /doc-present command, the
prompt section, the header button, the panel, and the tool card.
Verify
- the row
include:doc-present reports enabled: true, fiberPhase: "active"
(plugin_manager action=list_plugins), then
- refresh the browser once: the
📄 方案预览 button appears in the session
header (top-right actions row).
Published documents live in the doc root on disk, not in the plugin package, so
reinstalling the bundle leaves every existing document in place.
Usage
For the agent (model tool)
doc_present — one tool for the whole lifecycle:
| action | What it does |
|---|
new | Create <root>/<slug>/index.html from the template (needs slug, optional title), ensure the LAN server, return the file path and all URLs. The agent then writes the content with its normal write/edit tools. |
status | Server state, root, port, doc counts, URLs. |
ensure | Start (or reuse/adopt) the LAN server; prints share URLs. |
list | List published docs with update times (all: true includes withdrawn, query: "..." filters by slug/title). |
unpublish | Take a doc offline (visitors see a 410 page); files kept, restorable. |
republish | Re-publish a withdrawn doc. |
delete | Move a doc to the trash (hard: true deletes permanently). |
trash | List the trash (empty: true empties it). |
restore | Move a trash item back to the doc root (needs name from the trash list). |
Authoring rules the agent follows (enforced by the prompt section): a doc is
one self-contained HTML file — inline CSS/JS, no CDN / web fonts /
external images (LAN readers may not reach the public internet), demos
paused by default with a static caption each, semantic colors (blue = active,
green = done, yellow = attention, red = risk, purple = self-call). The two
guides in references/ teach the FX framework and the writing structure; the
prompt section points the agent at their absolute paths.
For the user
📄 方案预览 button (session header, top right) — toggle the preview
drawer: doc chips on top, live preview below; 浏览器打开 / 复制链接 /
↔ 全宽 / ⟳ / ✕ in the drawer header; Esc closes.
- Doc management in the drawer — a search box filters chips by title/slug;
the manage bar shows the selected doc's update time and offers
撤回 /
重新发布 and 删除 (soft delete into the trash, confirmed first); the
回收站 section restores items individually or empties the trash. These
actions call the loopback-only POST /doc-present/__api__/manage
endpoint, so LAN visitors keep read-only access.
- Tool cards — after the agent publishes, its
doc_present result renders a
card with 在面板中打开 (opens the drawer straight at that doc),
浏览器打开, and 复制内网链接.
/doc-present status|start|stop|list — the service as a command.
(/doc-present json is the machine-readable state the panel uses.)
How the two servers relate
- The same-origin route
/doc-present/... on the dsh web server (e.g.
http://127.0.0.1:3080/doc-present/) serves the doc root directly from
disk. It is always on while the plugin is mounted and powers the in-GUI
panel. Only people who can reach your dsh web server can see it.
- The standalone LAN server (default
0.0.0.0:8899) exists for sharing:
it is what colleagues open. It starts lazily on the first
new/ensure/start, stops on /doc-present stop, and is closed when
the plugin unloads (docs persist on disk). If the configured port is taken
by a foreign process it scans upward (up to +20) and persists the chosen
port back to <root>/.server/config.json; if it finds a healthy
doc-present instance already on the port, it adopts it instead of starting
a second one.
Configuration
- Doc root:
DOC_PRESENT_ROOT env var, default ~/.dsh/doc-present
(docs + .server/ metadata: config.json, state.json, trash/).
- Port:
DOC_PRESENT_PORT env var > <root>/.server/config.json > 8899.
- Template and guides resolve relative to the installed package, so keep
assets/ and references/ next to lib/.
Development
lib/server.js is pure Node and covered by a standalone smoke test (new →
serve → unpublish/410 → republish → delete/trash → port persistence):
node scripts/smoke.mjs
Client-half edits hot-reload only while a watcher rebuilds plugin bundles
(pnpm run dev:web from the dsh checkout); otherwise refresh the page after
copying the package into the profile. Host-half changes load when the row
reloads (edit the patch file) — note that webServer.register returns a raw
disposer, and while the plugin captures and disposes its route on unload, a
route leaked by an older build is detected and reused (a warning is logged);
restart the profile for a completely clean slate.
License
MIT