Pin conversations (置顶) to the top of each workspace group in the DeepSeek Harness (DSH) web workspace list, and keep every group ordered by recency.
English | 中文
A DSH web plugin + a tiny patch to the official workspace browser. DSH's workspace/session list exposes no plugin extension point for menu buttons or ordering, so this package ships:
-
A real, packaged plugin (
dsh.client→lib/client.js) that:- renders a 「管理工作区」 (Manage workspace) button in the workspace header (left of search/view/add);
- opens a manage panel (in the
shell.overlayslot) listing all sessions with pin/unpin, AI rename, and per-session auto-rename opt-out; - exposes a window-scoped store
window.__dspSessionPin(pin toggle,auto-rename toggle, per-session opt-out, persist + broadcast); - its server half (
lib/index.js) auto-reapplies the tiny patch on everydsh webstart, and exposesPOST /dsh-session-pin/retitleso the panel can ask the host to regenerate a session's title from its content (DSHsessionTitle.refresh→ configured LLM).
-
A small patch to
@deepseek-ai/dsh-client-ui-workspace/lib/client.js, split into four independently verifiable feature groups:manage-button— the 「管理工作区」 gear button callingwindow.__dspSessionPin.open();pin-order/pin-order-flat— list order becomes pinned-first, then newest-first (both pinned and unpinned follow recency, so a freshly-created session appears right below the pinned ones), in the grouped tree view and the flat view;pinned-prop-plumbing— the pinned-id bus wired fromWorkspaceBrowserinto both lists.
A group whose anchors drifted with a DSH upgrade fails alone: the other features keep working, the host log names the broken group, and
node lib/patch-official.mjs --auditprints which anchor is missing.
- The manage panel has a global switch (default off): when on, sessions with no new message for over 1 day are auto re-titled from their content (runs DSH's LLM title provider via
sessionTitle.refresh). - Each session row has an 「AI」 button to re-title it immediately (regardless of age).
- Each session row has an opt-out toggle: turn it on to stop that one session from being auto re-titled (even if it passes 1 day).
- "Over 1 day" is measured from the session's last message time (
updatedAt).
The auto/manual re-title calls the host bridge (
/dsh-session-pin/retitle) which runs DSH's built-in content-based title generation — the name is model-generated from the conversation, matching DSH's own automatic titles. Requires a registered LLM title provider (the shippeddsh-session-title-*-llmplugins) and a model to be configured.
⚠️ This is a plugin + patch, not a pure plugin. The patch is version-shaped, so it is tested against a fixture set of the upstream bundles (seefixtures/) and it disables only what it cannot apply — never the whole feature set, and never half-written code.
把左侧「工作区」会话列表里的对话置顶,并让每个分组都按最近更新排序;还能按内容给会话重新命名(AI)。
- 工作区标题右侧出现一个 ⚙ 管理工作区 按钮;点开面板即可对任意会话置顶/取消置顶、AI 重命名、单独停用某会话的自动重命名。
- 排序:置顶会话在顶部,**置顶与非置顶都按最近更新(最新在前)**排序;置顶项自身也随时序更新,新建的会话会出现在置顶区下方。
- AI 重命名:面板顶部有一个全局开关(默认关)。开启后,超过 1 天没有新消息的会话会自动按内容重新命名(复用 DSH 的 LLM 标题生成
sessionTitle.refresh);每个会话行有 「AI」按钮随时手动重命名,以及一个停用开关(打开后该会话即使超 1 天也不会被自动重命名)。 - “超过 1 天”按最后一条消息时间(
updatedAt)算。 - 置顶/开关/停用状态存
localStorage,刷新/重启后保留。 - 插件服务端每次启动会幂等重放官方补丁,并逐特性打印结果;另提供
POST /dsh-session-pin/retitle桥接,让面板能把“按内容重新命名”的请求交给主机(由你配置的模型生成新标题)。 - 补丁按特性分组(齿轮按钮 / 置顶排序 / 置顶状态接线),官方升版导致某一组锚点失效时只关掉那一组,其余照常工作,并会在宿主日志里点名是哪一组。
- 补丁对
0.1.0-rc.7/0.1.0-rc.8/0.1.5-rc.1三份官方 bundle 都有离线验证:node tests/patch-fixtures.test.mjs(50 项,不联网、不需要装 DSH)。 - 齿轮没出现、排序也没变?先跑
node lib/patch-official.mjs --audit,它会明确指出哪一组、哪一步的锚点对不上;确认浏览器真正收到的那份文件可以用 README 上方 F12 那行命令。
This plugin is high-permission by design, and DSH STORE classifies it as a user-reviewed (per-install confirm) plugin rather than auto-approved:
- Patches the official DSH bundle at boot:
lib/patch-official.mjsreads/writes@deepseek-ai/dsh-client-ui-workspace/lib/client.js(nodefs), backs it up (*.bak-session-pin), and runsnode --checkon the result (nodechild_process). It is seed-guarded (aborts loudly on version mismatch — never silently corrupts) and idempotent (skips when already patched). This is the only way to add an ordering/menu hook to a workspace list that exposes no plugin extension point. - Binds a local HTTP route
POST /dsh-session-pin/retitleon the DSH web server (loopback). It resolves a session id and calls DSH'ssessionTitle.refresh(session)— the configured LLM title provider — to re-title a conversation from its content. No credentials, no external host, no reads outside the DSH install. - Browser state is per-browser
localStorageonly (pins, auto-rename toggle, opt-outs); nothing is sent anywhere. The manage panel lives inshell.overlay. - Failure bounds: the patch writes nothing on version mismatch; the retitle route returns 400/404/500 without side effects on bad input; the server half never breaks DSH boot.
中文:该插件包含对官方 bundle 的补丁(写文件 + 运行
node --check),且提供本地retitle路由调用 LLM 重命名——因此属于高权限 / 用户逐次确认类别。补丁带种子校验、版本不符会安全中止;浏览器状态仅存于本机localStorage;不访问凭据、不外联。
Acceptance evidence (install / config-synthesis / uninstall on a disposable profile) is recorded in docs/acceptance.md.
lib/
client.js # browser half (window.__dspSessionPin + manage panel)
index.js # server half (mount + boot-time patch report + retitle bridge)
patch-spec.mjs # WHAT gets patched: grouped, multi-version, seed-guarded spec
patch-official.mjs # patch engine (candidate walk, per-group apply, --audit CLI)
cordis.patch.yml # bundle patch (inserts dsh-session-pin into the roster)
fixtures/ # pristine upstream bundles, used by the offline fixture test
tests/ # node tests/patch-fixtures.test.mjs (no network, no DSH install)
package.json # dsh.client / dsh.bundle declarations, MIT
# From this GitHub repo (or a tarball of it)
dsh plugin --profile web add https://github.com/Lstalu/dsh-session-pin
# or from a local checkout / tarball
dsh plugin --profile web add F:/path/to/dsh-session-pin
dsh plugin --profile web add ./dsh-session-pin-0.3.0.tgzThen restart the web app (the browser client bundle is served at boot; the patch is also applied at boot by the server half).
The server half auto-applies the patch on every start (and logs each feature's result). To run it manually:
node node_modules/dsh-session-pin/lib/patch-official.mjs # auto-locates the official bundle
node node_modules/dsh-session-pin/lib/patch-official.mjs <path> # or point it explicitly
node node_modules/dsh-session-pin/lib/patch-official.mjs --audit # report only, writes nothingIt prints per-group status: applied, already, partial (some groups could not be applied)
or failed, and exits non-zero only on failed. Any written output must pass node --check or
it is rolled back from the <path>.bak-session-pin backup.
The gear button is missing / the list order looks stock? Run the audit and read the report — it names the exact group and anchor step that no longer matches this DSH build:
node node_modules/dsh-session-pin/lib/patch-official.mjs --auditTo check the copy the browser is actually being served (that is the file that matters):
// F12 console
fetch('/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js').then(r => r.text())
.then(t => console.log('pinFirstSessions:', t.includes('pinFirstSessions'), 'menu.manage:', t.includes('menu.manage')))| DSH release | workspace bundle | state |
|---|---|---|
0.1.0-rc.7 |
@deepseek-ai/[email protected] |
✅ all 4 groups |
0.1.0-rc.8 |
@deepseek-ai/[email protected] |
✅ all 4 groups |
0.1.5-rc.1 |
@deepseek-ai/[email protected] |
✅ all 4 groups |
- Every claim above is exercised offline against the exact upstream bundles in
fixtures/:node tests/patch-fixtures.test.mjs(50 checks — apply, assert,node --check, idempotence, per-group isolation, candidate resolution). - If your DSH release is not in that table, run
--auditand open an issue with the version and the reported missing step — the report is designed to say exactly what to rebase. - Pinning is stored per-session-id in browser localStorage; it is per-account/browser, not synced to the host.
Three release-time gotchas that bit this repo before — follow these when tagging a new version:
-
Run the fixture test before pushing anything.
node tests/patch-fixtures.test.mjs(ornpm test) is the only thing that catches "the patch silently stopped fitting the official bundle"; the author's own machine can stay on a blessed DSH release forever, so local manual testing will never surface it. -
Rebasing onto a new DSH release. The patcher is version-shaped; treat a new release as a fixture-and-seeds task, not a code task:
# 1. capture the new upstream bundle as a fixture (see fixtures/README.md) # 2. run the test — it will name the exact group/step that no longer matches node tests/patch-fixtures.test.mjs # 3. add a seed variant to the failing step in lib/patch-spec.mjs, then re-run # 4. update the compatibility table above + dsh.compatibility.dshReleases in package.json
Keep the old variants: they are what makes the plugin work on machines that are behind.
-
Push hygiene. Never rewrite
package.jsonwith PowerShellSet-Content -Encoding utf8. On Windows PowerShell that writes a UTF-8 BOM (which makes DSH'sJSON.parsefail at boot and crashes the server) and also mojibakes any Chinese text (e.g.置顶→缃嫥《). Use your editor, or write no-BOM UTF-8 explicitly:[System.IO.File]::WriteAllText($path, $json, (New-Object System.Text.UTF8Encoding $false))
After any write, verify:
node -e "JSON.parse(require('fs').readFileSync('package.json','utf8'))"and check the content has no BOM (first 3 bytes are notEF BB BF). -
Pushing over a China-network proxy /
ghfastmirror. Aghfast.top-styleurl.*.insteadOfrewrite is read-only and cannot push; directhttps://github.comHTTPS git is often connection-reset. Push through the local VPN's HTTP proxy, temporarily disabling the rewrite, then restore it:git config --global --unset url.https://ghfast.top/https://github.com/.insteadof git -C . -c http.proxy=http://127.0.0.1:2081 push git config --global url."https://ghfast.top/https://github.com/".insteadOf "https://github.com/"
(The proxy host/port (
127.0.0.1:2081) is whatever your VPN client exposes; check it withTest-NetConnection 127.0.0.1 -Port 2081.)
Also note: this plugin legitimately patches the official DSH bundle (node:fs write + node:child_process run node --check), so DSH STORE classifies it high-permission / user-reviewed (per-install confirm), not auto-approved source-verified.
MIT