DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Plugin Pi Ui — DeepSeek Harness 插件(DSH Plugin)
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
P

dsh-plugin-pi-ui

Plugin Pi Ui

受 pi-web 启发的 DeepSeek Harness Web GUI 侧边栏界面:顶部显示临时会话,工作目录选择器下方列出单个目录中的会话,草稿编辑器在发送前不会创建任何内容,以及一个接管右侧栏 Files 标签页的紧凑文件浏览器。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:ice-ai-lab/dsh-plugin-pi-ui#84c4a0a8b7afed8631cab55fa8bd48da225ed7d0
README兼容性版本

兼容性与来源证明

Plugin Pi Ui 以 dsh-plugin-pi-ui 发布,当前版本为 0.1.1。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
github
Registry 更新时间
2026/9/11

版本

0.1.1stable
2026/9/11

相关插件

正在加载相关插件…

最新版
0.1.1
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
web
许可证
MIT
发布源
github
GitHub
★ 0
周下载
0
最近提交
2026/9/11
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 ui-customization 分类下经过校验的插件。

Web App@deepseek-ai/dsh-web-appdsh 浏览器界面捆绑包:位于 dsh-base 之上的 Web 补丁层,加上运行时粘合插件(提供前端 dist、Web 界面提示符、bash 运行时变量和 URL 行)Experimental Agent Team Web Profile@deepseek-ai/dsh-experimental-agent-team-web-profile用于 Agent Teams Remote 和 UI 插件的实验性 Web 配置层Remote Web Ui@linxin666/dsh-remote-web-ui通过扫码配对访问 dsh Web GUI,共享一个官方界面:设置按钮旁的二维码可将手机和 PC 配对到同一个 Web GUI(手机采用竖屏触控适配层,PC 使用完整桌面界面),通过一次性令牌和 rClient Ui Task Board@linxin666/dsh-client-ui-task-board面向 DSH Web GUI 的主机权威任务面板,支持实际会话执行、主机 cron 调度以及可选的跨平台空闲睡眠保护;以挂载方式提供,无需修改 DSH 源代码。

README


description: "pi-web-inspired sidebar chrome for the DeepSeek Harness Web GUI: temporary sessions at the top, a one-directory session list under a working-directory picker, a draft composer that creates nothing until you send, and a compact file explorer that takes over the right column's Files tab." kind: "package-reference"

dsh-plugin-pi-ui

English | 中文

Summary

This package reshapes the Web GUI's left sidebar into blocks that read as one system, riding the harness --dsw-* design tokens so it follows every shipped theme:

  1. Temporary sessions at the top. The newest disposable scratch sessions, five at a time with a 查看更多 footer that loads ten more per press. The + button starts a draft; the 🗑 cleans up scratch directories no session is using.
  2. The working directory below it, anchored at the column's middle. A compact dropdown picks the directory being worked in — and the shipped session list under it always shows that one directory's sessions.
  3. A compact file explorer in the right column, where its Files tab otherwise shows the shipped workspace-files view.

Cutting across all three: nothing exists until you send. Every "new session" gesture opens a draft composer and stops there. No scratch directory is minted and no session row appears until the first message is actually sent — then the directory and the session are created together, or an existing provisional blank session in the named directory is reused.

Table of Contents

  • Install
  • Use this plugin
  • Understand the implementation
  • Further exploration
  • Model experience
  • Known limitations and deferred work
  • Dev note

Install

The plugin is a plain out-of-tree package: a node half, a browser half, and a loader patch. Add it to a profile and restart dsh web.

# From a profile with this package available:
dsh plugin --profile web add dsh-plugin-pi-ui

The package declares dsh.bundle.patch, so the command above appends it to dsh.profile.bundles and its own cordis.patch.yml applies on the next boot. The equivalent hand-written row goes in the profile's own patch layer:

# ~/.dsh/profiles/web/cordis.patch.yml
- insert:
    - id: pi-ui
      name: 'dsh-plugin-pi-ui'

Use exactly one of the two routes: the same row id inserted twice fails boot. For a local checkout, a link dependency keeps edits live across restarts:

// ~/.dsh/profiles/web/package.json
{
  "dependencies": {
    "dsh-plugin-pi-ui": "link:/absolute/path/to/dsh-plugin-pi-ui"
  }
}

A restart of dsh web is required. The browser half is composed into window.__DSH_BOOT__ at boot, so a newly added client plugin cannot appear in an already-running server even though its node half loads live. After restarting, reload the page.

To remove it again, drop the row from dsh.profile.bundles (or the profile patch), remove the dependency, and restart. Scratch directories stay on disk; remove <DSH_HOME>/scratch/ when you no longer want them.

Use this plugin

The column at a glance

[ 临时会话                         1  🗑  + ]
                     ⋮  (the column's middle)
[ 工作区  [ 📁 …/my-project ▾ ]        + ]
[ that one directory's sessions …        ]

The shipped full-width New Session button, the search magnifier, and the add-workspace button stand down while the column is wide; the plugin supplies the controls in the table below. In the collapsed rail the plugin draws nothing and every shipped control returns.

ControlGesture
+ in the 临时会话 headerOpens the draft composer for a temporary session. The click creates nothing; sending mints the scratch directory and the session together. If the session cannot be created, the fresh directory is deleted again, so no orphan folder is left behind.
🗑 in the 临时会话 headerDeletes scratch directories that no session is rooted in (shown only when one exists). It confirms first and never touches a directory in use.
the dropdown between 工作区 and its +The working-directory picker: your directories, Open folder…, and Remove working directory…. Picking a directory with sessions opens its provisional blank session, else its newest; picking one with no sessions yet hands you the draft composer instead of minting a session.
+ at the 工作区 row's right edgeThe draft composer for a new session in the directory shown. The click creates nothing; the first message creates the session, reusing that directory's provisional blank session when one is already there.
Remove working directory…Lists every registered directory with its session count and removes the one you pick. Removal touches the registry alone: the directory, its files, and its session logs all stay put — those sessions simply stop being grouped under it.

A temporary session row re-opens that scratch session. Temporary sessions are deliberately absent from the dropdown — that control stays a directory picker — and they are never registered as workspaces, so they do not accumulate in the durable workspace registry.

Nothing exists until you send

Every start gesture ends in the same place: a draft composer instead of a new session. That is what keeps the sidebar free of "新会话" rows for sessions nobody ever wrote in, and it is why a temporary session's directory only appears under <DSH_HOME>/scratch/ at the moment you send.

The draft composer is the shipped blank-state card with two live twins laid over it: an editable input box over the card's own input area, and a send control over the card's (inert) send circle. Everything else about that card is the shipped card, drawn by the shipped component: the dashed border, the attach control, the permission select, the model seat, and the send button's own look. The plugin adds no hint line and no controls of its own. Type, then press Enter or the send circle.

The session list always shows one working directory

The region below 工作区 is a working directory's session list, and it shows exactly one directory's sessions at a time — the directory the picker shows: the current session's own workspace when it has one, else the one you last chose. A temporary session carries no working directory, so it neither changes nor appears in that list. There is no toggle and no "show everything" state: the picker is the selector.

The picker belongs to you, not to the selected session. The directory you chose last is remembered in the browser, so moving in and out of temporary sessions never blanks it out. Searching (the section header's magnifier) still searches every session, whatever the list is showing.

Settings

Settings → General gains two switches:

  • File explorer — the right column's explorer, in place of the shipped file view. Turning it off restores the shipped view immediately, with no reload.
  • Working directory switcher — the plugin's sidebar chrome as a whole.

Both are in-memory: they reset on reload. That is deliberate — a persisted preference would want the settings service rather than a client-local store.

Understand the implementation

Why the chrome is an overlay

The shipped sidebar declares no seat above its browsing region, and the only seats inside its brand row (sidebar.brand.mark / sidebar.brand.name) are single cells a shipped package already occupies — registering there would shadow the product brand, and both sit inside a <button> that starts a session. So this plugin registers into shell.overlay, the frame-wide additive list seat, and positions its surfaces over the sidebar column.

A seat is given no geometry, so the plugin measures. It resolves the column from its semantic CSS-module class suffixes (the build keeps them: the runtime class is <hash>_regionArea) and writes one custom property onto it:

[class*="regionArea"] {
  padding-top: var(--dshpi-reserve-top, 0px);
}

The shipped region reserves that band, so the temporary list sits above the list's own content instead of covering it. Three further marks are deliberate and narrow:

  • data-dshpi-wide on the column — only while the column is wide — hides the shipped full-width New Session button, the header actions, and the search button, and reserves the brand's right padding so the row's own text stays clear of the controls that replace it. The collapsed rail keeps everything.
  • data-dshpi-focus plus a one-rule stylesheet shows exactly one session group, described below.
  • data-dshpi-header-controls keeps the CSS hook meaningful while the header row has no plugin-drawn control.

Every other shipped element is left alone.

The one-directory session list

The browsing region is a single seat that ui-workspace already occupies and its group rows carry no workspace identity, so the plugin cannot filter the list's data. What it can rely on is the list's own order: groupByWorkspace emits one group per workspace in Host order and appends the ungrouped bucket last, so a group's position is its workspace's position. Marking the column and revealing exactly that one position with an nth-of-type rule is declarative and immune to a React re-render undoing it.

The focused group's folder header is hidden while it is expanded, so the region reads as a plain session list rather than a folder with one child. That hiding is conditional on the group being expanded because a folded group keeps its header as the only control that can unfold it — and since the fold state is persisted and there is no toggle to escape with, leaving that control in place is what stops a collapsed group from stranding a directory with no way back. That same reasoning is why the dropdown carries Remove working directory…: with every other group hidden and the focused header hidden while expanded, the shipped rename/delete gestures are out of reach.

The draft composer

In the blank state the shipped card is inert: ui-conversation only activates it for a session that has a workspace (its own chip title), and the shipped model seat is not even rendered without a session. The plugin therefore twins the two controls a draft needs and leaves the row alone:

  • the input box is measured from [data-composer-card] / [data-composer-input] and covered by a transparent-bordered textarea filled with the card's own --dsw-specific-input-major (so the shipped placeholder underneath cannot show through), using the card's own font size, line height and caret color;
  • the send control is measured from the card's primary button and covered by a live twin of the same size, radius and fill.

Measurement is one rAF-coalesced pass fed by a 400 ms poll, a window resize listener, and a body-subtree MutationObserver, so the twins follow the hero's entrance animation, a sidebar collapse, and a window resize.

Start gestures and the pending target

A start gesture sets an in-memory pending target and clears the current selection, which is what puts the draft composer on stage:

TargetCreated on send
scratch (the temporary +, the default)POST /dsh-pi-ui/scratch mints a directory under the scratch root, then the session is created with that cwd. A refused session creation deletes the fresh directory again.
workspace (the workspace +, a directory with no sessions)The session is created with that workspaceId — nothing is written to disk. An existing provisional blank session there is adopted instead of duplicated.
cwd (a session-less directory the plugin can see)The session is created with that cwd.

The target lives only in memory and is retired as soon as any session opens, so a reload before the first send simply drops the draft.

The explorer as a right-column tab type

ctx.sidebarRightTabs.register(...) claims the shipped file view's kind — the files kind — at extension priority, the band a type from outside the product may use to take a builtin kind over, and the keyed sidebar.right.pane.tab seat supplies the body under this type's own id. A guide entry keeps the column's "new tab" page listing it, and because that entry replaces the builtin's, the column's default page is still the same kind.

The tree is rooted at the current session's working directory — for a temporary session, its scratch directory — and lists one level at a time through this package's node half. Clicking a file opens it through the harness resource address, so it lands in the right column exactly as the shipped tree's rows do. Turning the Settings switch off unregisters the type and the shipped view resumes, with no reload.

The node half

Three plain exact HTTP routes, because an out-of-tree plugin has no codegen step for a generated Remote namespace:

RouteMethodBounds
/dsh-pi-ui/treeGETRead-only, direct children only, resolved through the harness fs backend, refused when the resolved target escapes the session working directory, capped at 800 entries. Never reads file contents.
/dsh-pi-ui/scratchGET / POSTPOST takes no caller input: the directory name is generated server-side, under the scratch root only.
/dsh-pi-ui/scratch/deletePOSTAccepts names only. Each must be a single path segment, is re-checked for containment after resolution, and the scratch root itself is never a deletion target. Caps 200 names and 64 KiB per request.

Like the rest of the plugin surface in this ecosystem, these routes are not behind the browser-session cookie. Treat them as local-only and do not expose the server beyond loopback.

What is persisted

Only one client-local key: dsh-pi-ui.workspace, the working directory the picker last showed. Everything else — the pending target, the two Settings switches, the measured geometry — is in memory. Temporary sessions need no bookkeeping at all: they are recognised by matching a session's working directory against the canonical scratch root.

Further exploration

  • @deepseek-ai/dsh-client-ui-slots and the renderer's seat semantics — what a single, list, keyed, or chain seat promises a registration.
  • @deepseek-ai/dsh-client-ui-sidebar-right — the tab registry this plugin's explorer registers into, including the priority bands.
  • @deepseek-ai/dsh-client-ui-workspace — the browser whose session groups this plugin scopes one at a time.
  • @deepseek-ai/dsh-host-directory-picker-browse plus its client half — compose them to replace the OS folder chooser with pi-web's in-app browser. The plugin's Open folder… tries the in-app primitives first and falls back to the OS chooser, so no plugin change is needed.
  • dsh --profile web --dump-config — the composed profile tree, the fastest way to see which bundles and patch layers are in play.

Model experience

The plugin registers no tools and no commands, so a model sees only what the harness already gives it. What this package changes about that:

  • A temporary session's working directory is <DSH_HOME>/scratch/<YYYYMMDD-HHmmss>-<4 hex>/, created at send time. The ordinary sandbox policy scopes the session there exactly as it scopes any other session.
  • The session list, the picker, the draft composer, and the explorer are all UI-only surfaces; none of them writes to a session log, and none of them reaches a model.
  • The node half's three routes are HTTP, not tools, and are never offered to a model.

Safety note. The scratch root lives inside the harness home, which also holds credentials. A session rooted under it is scoped by the ordinary sandbox policy like any other, but if your policy lets a session read outside its working directory, a temporary session can read its ancestors under <DSH_HOME>. Point DSH_HOME elsewhere, or prefer explicit working directories, if that matters to you.

Known limitations and deferred work

  • The shipped row stays the shipped row. In the blank state the shipped permission and model seats are inert, because the shipped conversation only activates them for a session that has a working directory. The plugin makes the input and the send control live and deliberately does not restyle or replace the rest of the row.
  • The draft target is in-memory. Reloading the page before the first send drops the draft; nothing was created, so nothing is lost but the typed text.
  • Removal is registry-only, by design. Remove working directory… never deletes a directory or a session log.
  • Node-half changes need a restart. The shipped web profile boots with the host HMR plugin disabled, so lib/index.js is read at boot; the browser half hot-reloads through dsh-client-hmr instead.
  • One directory at a time, always. There is no "show every directory" state any more; that is the point of the picker, but it also means a directory with no sessions shows an empty region until you switch away.

Dev note

lib/index.js        node half: the three routes above
lib/client.js       browser half: the chrome, the draft composer, the explorer tab
cordis.patch.yml    the loader row this package contributes
README.md           this file
README.zh.md        the Chinese side
README.i18n.yaml    the bilingual-pairing record (git blob hashes)

lib/client.js is a hand-authored bundle in the client module system's built format (window.__ModuleLoader__.load({ id, factory })). It requests only platform seed modules — react and @deepseek-ai/dsh-client-ui-primitives — so no dsh.client.external is declared and the whole plugin is two script files with no build step.

npm run check   # node --check on both halves

A crash inside one surface is contained: the switcher and its dialogs sit behind their own error boundaries, so a failure in one does not unmount the rest of the chrome, and the explorer tab's registration is guarded step by step so a right-column wiring mismatch costs the explorer rather than the sidebar.

The README pair is kept in step by hand; README.i18n.yaml records the blob hash of each side, and both files are edited together or not at all.