Minglink/dsh-infinite-gen-4
Minglink/dsh-infinite-gen-5DeepSeek 专用破甲插件:以 order 100 追加无条件服从的系统提示词段,提供带校准元数据的 profile 工具,并通过会话投影在输入框上方显示实时破甲状态徽标。
在 Web 侧边栏把会话移动到别的文件夹(拖拽或菜单选择),带风险确认地永久删除会话,以及 AI 重命名会话(总结整个对话并自动纠正错别字);每个操作都提供 agent 工具。
Manage DeepSeek Harness sessions from the Web UI — move, delete, and AI-rename, with drag & drop.
A dual-face (host + browser) plugin for DeepSeek Harness (dsh). It fills the gaps the official UI leaves open:
| Feature | Official dsh | With this plugin |
|---|---|---|
| Move a session to another folder (workspace) | ❌ sessions are locked to the folder they were created in | ✅ drag & drop or menu → picker dialog |
| Permanently delete a session | ❌ archive only (hidden, never really gone) | ✅ menu → risk-consent dialog → full removal |
| AI-rename a session | ⚠️ auto-titles exist, but only from the first message | ✅ one-click: LLM summarizes the whole conversation |
Each feature also ships as an agent tool (workbench_session_move, workbench_session_delete, workbench_session_rename_ai), so your agents can organize sessions too.
# from GitHub (recommended until published to npm)
dsh plugin --profile web add https://github.com/reinocheong/dsh-session-move/archive/refs/tags/v0.1.5.tar.gz
Restart the profile for the plugin to load:
# systemd-managed installs
sudo systemctl restart dsh
Note for manual installs (copying the package into
~/.dsh/profiles/node_modules/yourself): you must also register it in the profile'scordis.patch.yml:- insert: - id: session-move name: dsh-session-move
dsh plugin adddoes this automatically.
Drag & drop — grab any session row in the sidebar and drop it onto the title of another folder. The target folder highlights while you hover; release to move.
Menu — open a session's ... menu → Move to folder… → pick the target folder → confirm.
Moving a session relocates its working directory: the session header's cwd is rewritten, the persisted log is moved to the target folder's storage slug, and the workspace accounting is updated — history travels with it, and the session keeps working in the new directory afterwards.
Open a session's ... menu → Delete session → tick "I understand the consequences" → confirm.
Deletion is complete and permanent: running agents are stopped first, then the log directory, projection-cache row, and workspace accounting are removed. Nothing lingers in "Ungrouped".
Open a session's ... menu → AI Rename — the LLM reads a representative sample across the whole conversation (evenly sampled to fit the token budget), fixes any typos, and writes a concise title in the conversation's language.
By default it uses the session's own model route. To pin a different provider/model, configure the plugin row:
- id: session-move
name: dsh-session-move
config:
renameAi:
provider: deepseek-official
model: deepseek-v4-flash
targetWords: 6
targetCjkCharacters: 14
maxInputBytes: 8192
maxOutputTokens: 96
timeoutMs: 60000
| Tool | Description |
|---|---|
workbench_session_move | Move a session to another workspace (by session id + workspace id). |
workbench_session_delete | Permanently delete a session. |
workbench_session_rename_ai | AI-rename a session. |
These power the UI; they are also usable directly:
| Endpoint | Method | Body | Returns |
|---|---|---|---|
/__sessionmove/info | GET | — | all workspaces + the session's current workspace |
/__sessionmove/move | POST | { sessionId, workspaceId } | move result (old/new cwd, workspace ids, agentReleased) |
/__sessionmove/delete | POST | { sessionId } | what was removed |
/__sessionmove/rename-ai | POST | { sessionId } | the new title |
dsh folders ("workspaces") are not free-form containers. A session belongs to the workspace whose path equals the session's working directory (cwd), which is frozen in the header line of the session's zstd-compressed log. Moving a session therefore means:
cwd to the target folder's path (decompress the first zstd frame, patch the JSON, recompress, keep the trailing frames byte-for-byte),All storage mutation goes through dsh's own storageDomain, keeping memory and disk consistent; nothing is left half-accounted.
The session id localization layer now uses identical safety standards across all code paths:
displayTitle fallback (the v0.1.4 path for title-less sessions) guarded against ambiguity; the two higher-priority title-based loops (exact s.title match and fork-stripped s.title match) still silently returned the first hit. Two ordinary sessions in this very profile share the durable title "开始 Gyden OS WebUI 设计治理" — so the missing guard was not theoretical. A single uniqueBy(pick, wantText) helper now enforces the same rule everywhere: operate only on an unambiguous match; otherwise report "not found". For the (infrequent) case where two sessions hold the same title the plugin can no longer act, the pop-up shows "not found" with a blank session id — a deliberate fail-safe rather than a silent deletion of the wrong session.readSessionIdFromRow each emit a console.warn([dsh-session-move] …), making it visible when React's internal DOM key structure changes. The fallback to title matching is unchanged, but an operator can now tell from the DevTools console that the fallback was used — previously it was silent.Sessions without a durable title can now be operated on — previously they could not be deleted at all:
node.id); the DOM exposes no attribute for it. The plugin used to scrape the row's displayed text and match it against SessionSummary.title, which is optional and stays undefined until the host projects a durable title. Every title-less session — empty ones included — therefore failed to resolve: the dialog reported "not found" and the confirm button stayed disabled no matter what the user did. The id is now read from row.__reactFiber$… (walking up to memoizedProps.node.id, validated against the session-id shape) — the same value the official row writes into dataTransfer on dragstart.displayTitle (always present, unlike title) and accepts the match only when it is unique. Several title-less sessions in one folder all display the same cwd basename as their label, and guessing wrong would act on the wrong session — an irreversible outcome for deletion — so an ambiguous match is reported as "not found" rather than resolved.This release fixes two AI-rename defects that could leave the dialog spinning forever:
setBusy(true) while busy was still in its dependency array. React therefore ran the previous effect's cleanup on every state change, which aborted the fetch that had just been issued; the aborted request then set busy back to false, restarting the cycle. The symptom was a spinner that never stopped, never succeeded, and never reported an error. The effect now carries a startedRef guard keyed on the session id and depends on target only, so each rename issues exactly one request.timeoutMs was configured but never applied (host) — AI_RENAME_DEFAULTS declared timeoutMs: 60000, yet the stream was handed a bare new AbortController().signal that could never fire, so a stalled provider hung the HTTP request indefinitely. The LLM stream is now wrapped in deadline(request.signal, config.timeoutMs, ...) from @deepseek-ai/dsh-timeout — the same pattern the official dsh-session-title-llm provider uses — making the documented timeout real.The move keeps every place a session's location is recorded in sync — not just the header and the log directory:
identity.cwd in the projection cache (session_projcache) follows the move, so the web session list resolves the correct storage slug immediately (no ENOENT on cold sessions and no "Ungrouped" residue after a move).identity.cwd entries left behind by earlier half-applied moves or manual reorgs, so the list shows real session titles from the first render instead of the folder placeholder.cwd (this was the cause of moves silently landing in "Ungrouped" while the physical move succeeded).agent/disposed), not just cancels its current turn. This is the load-bearing part: dsh's agent factory waits for BOTH the session and the agent to leave their registries (waitForDrainingConfiguredIdentity listens for session/disposed and agent/disposed) before rebuilding a live session; cancelling the turn alone leaves the agent registered, so the wait never resolves and the moved session can never be re-opened or resumed (symptom: you can type and hear the notification sound, but the agent never replies). The client also seamlessly reloads the moved session's view after a successful move — the host flags the resident conversation instance as removed ("session unavailable" input) and that flag cannot be reset in place, so the plugin switches to another listed session first (while the moved session is still unlisted, sweepDeferred genuinely drops its stale scope), refreshes the list, then switches back — the session re-opens cold from its new location with history intact in a few hundred milliseconds and no page reload. Any step that fails falls back to an automatic full page reload, so the view can never stay stuck on "session unavailable".Each fix is fail-soft: if a repair can't run, the plugin logs a warning and leaves the state untouched rather than corrupting it.
The plugin is designed to be lightweight:
dsh-tools, dsh-llm, dsh-session-title, dsh-session-title-llm, dsh-timeout). Nothing new is installed./__sessionmove/info); the only potentially slow step is AI rename, which calls the LLM once on demand (a few seconds, timeout-configurable) and never runs otherwise.>=22.19.0 (same as dsh)0.1.0-rc.6 or newer@deepseek-ai/dsh-session-title / @deepseek-ai/dsh-session-title-llm packages (bundled with dsh)MIT