dsh-plugin-tool-management 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -238
- package/README_EN.md +300 -304
- package/cordis.patch.yml +9 -55
- package/docs/Changelog.md +108 -481
- package/docs/images/1/345/234/272/346/231/257.png +0 -0
- package/docs/images/1/345/234/272/346/231/257_en.png +0 -0
- package/docs/images/2MCP.png +0 -0
- package/docs/images/2MCP_en.png +0 -0
- package/docs/images/3/346/212/200/350/203/275.png +0 -0
- package/docs/images/3/346/212/200/350/203/275_en.png +0 -0
- package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223.png +0 -0
- package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223_en.png +0 -0
- package/docs/images/5/346/217/220/347/244/272/350/257/215.png +0 -0
- package/docs/images/5/346/217/220/347/244/272/350/257/215_en.png +0 -0
- package/docs/images/6/350/256/260/345/277/206.png +0 -0
- package/docs/images/6/350/256/260/345/277/206_en.png +0 -0
- package/docs/images/7/344/274/232/350/257/235.png +0 -0
- package/docs/images/7/344/274/232/350/257/235_en.png +0 -0
- package/docs/images/8/345/205/274/345/256/271.png +0 -0
- package/docs/images/8/345/205/274/345/256/271_en.png +0 -0
- package/docs/update.md +54 -0
- package/lib/agents-md/preset-id.js +49 -0
- package/lib/agents-md/service.js +138 -56
- package/lib/client.js +4142 -3560
- package/lib/compat/probe.js +665 -0
- package/lib/history/bridge.js +293 -0
- package/lib/history/workspace.js +498 -52
- package/lib/http-fence.js +73 -0
- package/lib/index.js +511 -126
- package/lib/rules/provider.js +3 -3
- package/lib/rules/service.js +264 -30
- package/lib/scene-prompt-sync.js +112 -0
- package/lib/skills/core.js +112 -35
- package/lib/skills/service.js +6 -1
- package/package.json +5 -3
- package/screenshots.json +8 -7
- package/docs/images/MCP.png +0 -0
- package/docs/images//344/274/232/350/257/235.png +0 -0
- package/docs/images//345/234/272/346/231/257.png +0 -0
- package/docs/images//345/255/220/346/231/272/350/203/275/344/275/223.png +0 -0
- package/docs/images//346/212/200/350/203/275.png +0 -0
- package/docs/images//346/217/220/347/244/272/350/257/215.png +0 -0
- package/docs/images//350/256/260/345/277/206.png +0 -0
package/README_EN.md
CHANGED
|
@@ -1,304 +1,300 @@
|
|
|
1
|
-
# dsh-plugin-tool-management
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/dsh-plugin-tool-management)
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
[](package.json)
|
|
6
|
-
[](https://github.com/ouli-1242/dsh-plugin-tool-management)
|
|
7
|
-
[](https://dsh.market/)
|
|
8
|
-
|
|
9
|
-
[简体中文](README.md) · **English** · [Changelog](docs/Changelog.md)
|
|
10
|
-
|
|
11
|
-
**An MCP
|
|
12
|
-
|
|
13
|
-
- **MCP
|
|
14
|
-
- **Skills
|
|
15
|
-
- **AGENTS.md
|
|
16
|
-
- **History
|
|
17
|
-
- **Scene
|
|
18
|
-
|
|
19
|
-
No hand-editing of `cordis.patch.yml`,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
](https://www.npmjs.com/package/dsh-plugin-tool-management)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](package.json)
|
|
6
|
+
[](https://github.com/ouli-1242/dsh-plugin-tool-management)
|
|
7
|
+
[](https://dsh.market/)
|
|
8
|
+
|
|
9
|
+
[简体中文](README.md) · **English** · [Changelog](docs/Changelog.md) · [Release overview](docs/update.md)
|
|
10
|
+
|
|
11
|
+
**An MCP, skills & memory manager for DeepSeek Harness.** One **Tools** panel, eight tabs, five things under control:
|
|
12
|
+
|
|
13
|
+
- **MCP** — which servers are configured, what tools each exposes, and which ones the model may call: add, edit, remove, toggle, restart, effective immediately;
|
|
14
|
+
- **Skills** — every skill on the machine (DSH / Agents / Codex / Claude / project-level / any custom directory) at a glance, toggled individually or per source, created, imported, recycled;
|
|
15
|
+
- **AGENTS.md** — multiple global instruction baselines as presets, applied with one click into `~/.dsh/AGENTS.md`;
|
|
16
|
+
- **History** — archived sessions grouped by project, batch restore / delete, transcript import & export, retention-based cleanup;
|
|
17
|
+
- **Scene memory** — `memories/<scene>/<name>.md`, one folder per scene; the bodies of memories in an enabled scene are **injected into the system prompt in full**, so you never re-explain them.
|
|
18
|
+
|
|
19
|
+
No hand-editing of `cordis.patch.yml`, no skill source file ever touched, configuration survives restarts and upgrades.
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
dsh plugin --profile web add dsh-plugin-tool-management@latest
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Hard-refresh the browser afterwards (Cmd/Ctrl+Shift-R); a **Tools** panel in Settings means the install worked.
|
|
26
|
+
|
|
27
|
+
## Screenshots
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
## Highlights
|
|
46
|
+
|
|
47
|
+
| Capability | In one line |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Host compatibility | The **Host** tab is a read-only check-up: whether plugin and host load the same module instances, which route each action takes (host-native entry vs plugin adapter), and which capabilities are degraded — with reasons |
|
|
50
|
+
| Per-tool switches | **Individual tools** inside one MCP server can be disabled: invisible to the model and blocked at call time, restorable at any moment; whole-server batch toggling too |
|
|
51
|
+
| Restart semantics | Restart only reconnects — it **never flips the enabled state** (restarting a disabled server does not silently enable it) |
|
|
52
|
+
| Secret safety | Secret-looking values in `env` / `headers` are masked by default and URL query strings are redacted; "Reveal" accepts only same-origin requests or local tooling holding a valid token |
|
|
53
|
+
| Write protection | Every patch rewrite keeps a timestamped `.bak` (last 5); duplicate loader ids are rejected before the write; failed cross-level migration rolls back; applying an `AGENTS.md` preset keeps 5 generations too |
|
|
54
|
+
| Skill sources | Hooks up `~/.agents` / `~/.codex` / `~/.claude` and any custom directory (read-only, overlapping paths rejected); the plugin's own landing spot is **Imported skills** |
|
|
55
|
+
| Skill permissions | The two source groups are exact opposites: default sources **must be read** (cannot be removed or disabled) but **their skills can be deleted**; external and custom directories **can be disabled or removed** but **their skills are read-only** |
|
|
56
|
+
| Skill operations | Create, ZIP / folder import, recycle bin (restore / permanent delete), open the source in the system editor; directories are watched, so edits show up automatically |
|
|
57
|
+
| AGENTS.md presets | Multiple global baselines: create / import / edit / apply / delete; "Apply" writes `~/.dsh/AGENTS.md` (new sessions pick it up, current sessions stay unchanged) |
|
|
58
|
+
| Archived sessions | Grouped by project, search, select-all, batch restore / permanent delete, retention cleanup; if a workspace registration is deleted, the group is rebuilt from session directories and can be re-registered with one click |
|
|
59
|
+
| Transcript import / export | Take over conversations from Claude Code / Cursor (JSONL), Codex (Markdown) or any text; export as Markdown / JSONL, defaulting to the desktop |
|
|
60
|
+
| Scene memory auto-injected | The body of every `.md` in an enabled scene goes into the system prompt (per-agent `systemPrompt` section) with no tool call, effective on the **very next request**; the reserved `global` scene is always injected |
|
|
61
|
+
| Memory management | Import `.md` / `.zip` (directory name = scene, bundles carry attachments), toggle individually, recycle bin; the injection budget is visible and oversized memories are skipped with a list |
|
|
62
|
+
| Scene profile | Every scene freely combines **MCP tool set / skill set / subagent bindings / memories**; a scene with tools or skills also gets "Enter this mode", and exiting restores the snapshot **verbatim** |
|
|
63
|
+
| Lightweight subagents | One file per persona in `agents/`; `subagent_list` / `subagent_run` run and discard, never entering History, inheriting the memories of currently enabled scenes |
|
|
64
|
+
| Prefix-cache friendly | Section text depends only on enabled scenes + file contents, so it is byte-stable; switching a scene or editing a memory changes it exactly once |
|
|
65
|
+
| Model tools | **14**, five prefixes: `skill_mcp_manager_*` (4), `skill_manager_*` (3), `agentsmd_*` (2), `rule_manager_*` (3), `subagent_*` (2); all three confirm gates respect the session approval policy |
|
|
66
|
+
| UI | Its own `dsm-*` design system, **eight tabs** (Scenes / MCP / Skills / Subagents / Prompts / Memories / Sessions / Host), bilingual and following the host language |
|
|
67
|
+
|
|
68
|
+
## Install & update
|
|
69
|
+
|
|
70
|
+
Prerequisites: DSH installed (`dsh web` runs), Node.js ≥ 18.
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
# Install / update (same command; installs the package and mounts it)
|
|
74
|
+
dsh plugin --profile web add dsh-plugin-tool-management@latest
|
|
75
|
+
|
|
76
|
+
# Uninstall
|
|
77
|
+
dsh plugin --profile web remove dsh-plugin-tool-management
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Hard-refresh the browser (Cmd/Ctrl+Shift-R) afterwards — a **Tools** panel with eight tabs means it worked. Client changes are hot-loaded by DSH, no restart needed; **host-side changes need `dsh web` restarted** to take effect.
|
|
81
|
+
|
|
82
|
+
You can also tell any DSH session:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
Install the dsh-plugin-tool-management plugin:
|
|
86
|
+
dsh plugin --profile web add dsh-plugin-tool-management@latest
|
|
87
|
+
Then remind me to hard-refresh the browser.
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Feature guide
|
|
91
|
+
|
|
92
|
+
### Scenes and memories
|
|
93
|
+
|
|
94
|
+
> A scene is the grouping dimension, a memory (`.md`) is the content. A scene can carry a **profile**: MCP tool set / skill set / subagent bindings / memories, four freely combined sections.
|
|
95
|
+
> All data lives under `~/.dsh/tool-management/` (see [Where data lives](#where-data-lives)).
|
|
96
|
+
|
|
97
|
+
- **A scene is an explicit record**: `memories/<scene>/` holds its memories, and the scene itself carries a **description** and an order (in the `scenes` slice of `rules-index.json`). Scene names accept any Unicode (≤64 chars, no `/ \ < > : " | ? *`, must not start with a dot, **a single path segment**). `global` is the reserved always-on scene ("Global" in the UI) and `_shared/` is the legacy shared scene.
|
|
98
|
+
- **New scene**: "New scene" asks for a name and a one-line description; you can also just `mkdir` under `memories/` — a record is filled in on the next read. **An empty scene is valid**, so you can create scenes first and add memories later. **A new scene starts switched off.**
|
|
99
|
+
- **Every `.md` is one memory**: no frontmatter needed, the whole body is injected. Drop a file into the scene folder and it takes effect, or use "New memory" on the page — **file names can be non-ASCII** (e.g. `站会流程.md`). A memory whose scene does not exist is rejected outright (`scene not found`) instead of silently creating one.
|
|
100
|
+
- **Toggle a scene (single choice)**: besides the reserved `global` scene, **only one scene can be enabled at a time** — once one is on, the other switches are greyed out until you turn it off; turning everything off leaves just `global` and `_shared` injecting. Every `.md` inside the enabled scene is injected into the system prompt, effective on the **very next request** — no new session, no plugin reload.
|
|
101
|
+
- **A scene can bind one prompt preset**: pick a preset in the scene form (one preset per scene; it **defaults to the one currently in effect**). **Switching scenes rewrites `~/.dsh/AGENTS.md` directly** — the bound preset is written into the global baseline (multi-generation backup under `agents-md/__last-applied__/` first); **turning the scene off restores the baseline from before you entered it** (hand-written content comes back verbatim). Rebinding, or editing the body of the preset currently in effect, also syncs the file. The Prompts page marks that preset as **Active**, and **it cannot be deleted**. If the bound preset is deleted the card says "preset missing" instead of silently touching the file. "Entering a scene" (applying its profile) also enables it, so profile, memories and prompt take effect together.
|
|
102
|
+
- **Global memories**: memories of the reserved `global` scene are injected into **every** conversation (they ignore scene switches; the global group on the Memories page is tagged **"always injected"**), which suits universal preferences and conventions. Leaving the scene empty when creating a memory lands it there.
|
|
103
|
+
- **Import memories**: the page header's "Import memories" takes `.md` and `.zip` (multi-select, drag-and-drop). Inside a zip, a directory name is the scene (`工作/standup.md` → scene `工作`); **dropping a folder works the same way** (multi-level directories are kept as `A/B`); a bare `.md` lands in the scene picked in the dialog — **leaving it empty means the reserved "Global" scene**. `<scene>/<name>/SKILL.md` inside a zip is imported as a **bundle** (sibling files become attachments; an illegal name, empty content, a single file >8 MB or >16 MB in total is skipped and reported). Files are written verbatim, **same names are skipped and listed** (never overwritten), nothing is dropped silently — every discarded item is reported with a reason — and scenes referenced by an import are created automatically and listed in the result.
|
|
104
|
+
- **A memory is a Markdown file**: `<scene>/<name>.md` (flat) or `<scene>/<name>/SKILL.md` (bundle). Creating one asks for scene, name (= file name), description and body; frontmatter is entirely optional and derived automatically when missing.
|
|
105
|
+
- **Bundle attachments**: the bundle form lets you add attachments right in the dialog (multi-select, ≤8 MB each, ≤16 MB / 32 files per upload); they live in the memory folder and are **never injected into the prompt** (only the `SKILL.md` body is), and can be removed one by one while editing. The flat form is a single file, so it has nowhere to put attachments.
|
|
106
|
+
- **Toggle & recycle**: enable/disable each row (a disabled memory stays on disk, it is simply left out of the prompt), edit, move to trash; the header's "Trash" can **restore** or **permanently delete** (with a confirmation step). `enabled` and friends live in the sidecar index and are never written back to your files.
|
|
107
|
+
- **The injection budget is visible**: a budget bar (used / max bytes) sits under the header and turns red when over. Default cap 64 KiB; when one memory does not fit it is **skipped** while smaller ones behind it are still included, and the section tail carries a "not injected (over budget)" list — both the model and you can see what was left out.
|
|
108
|
+
- **`~/.dsh/AGENTS.md` is no longer written**: the old "always layer" is gone; the shared baseline now lives in `_shared/` and flows through the system-prompt section.
|
|
109
|
+
- **Scenes page layout**: scenes form a **card grid** (single column below 640px). Each card carries exactly four things: the name (plus the on-disk folder name, status tags and the switch), a **one-line description**, and its action buttons ("Enter this mode" appears only when a profile exists; on the right "Profile / Edit / Delete scene"). Descriptions are capped at **60 characters** (enforced in the field, clipped with an ellipsis on the card with the full text in the tooltip) because cards carry **no counters at all**: what is in effect is told only by the **Active mode** bar at the top of the page, which **appears only while a mode is actually active** (no bar = no mode).
|
|
110
|
+
- **Scene profile (four free-form sections)**: "Profile" opens an editor where **MCP tools** (two levels: check a server first; unchecked = the whole server off, checked with no tool picked = all its tools off), **skills** (only currently discovered entries are listed; checked = enabled), **subagent bindings** (check personas; nothing checked = no restriction) and **memories** are added/removed independently. The memory section lists **only the memories of the scene being edited** and is filterable; it **only affects injection** — global memories are always injected and another scene's choices would have no effect, so neither appears here. **A newly added section starts with nothing checked**: the memory section pre-checks only the **enabled** memories of that same scene, the other three give an empty set (an empty MCP / skill / memory section disables that domain, an empty subagent section means no restriction — the section footer spells this out), and "Select all" covers the whole scene. Every section body has a filter box, memory descriptions are clipped at 80 characters with the full text in the tooltip, and the dialog keeps a fixed height so adding or removing sections never makes it jump.
|
|
111
|
+
- **Scene modes**: a scene with a tool or skill section gets "Enter this mode" — **entering takes a snapshot of the current toggles, persists it first, applies the selection and narrows memory injection to that scene**; exiting restores the snapshot **verbatim** (whole-server disable keys written during the mode disappear with it). Manual changes made while a mode is running are never silently written back — "Save to scene" does that. Any failed step rolls back and is reported honestly (an incomplete rollback goes into the error text instead of claiming success).
|
|
112
|
+
|
|
113
|
+
### Subagents (personas)
|
|
114
|
+
|
|
115
|
+
- **One file per persona**: `~/.dsh/tool-management/agents/<persona>.md`; every frontmatter key is optional — `description` (when to call it, one sentence is enough), `provider` + `model` (the model route, **a pair**: switching providers requires both; a bare `model` resolves against the main session's provider), `tools` allowlist, `toolsDeny` denylist. The body is the persona prompt.
|
|
116
|
+
- **Advanced options**: model and tool limits live in an "Advanced options" fold-out (auto-expanded for personas already using them). The model is a **dropdown** of `provider · model` pairs from the host LLM catalogue, with a "Custom" entry to type one it does not list; the tool allow/deny lists are **pickers** whose candidates are the **union of tool names across all agent presets**, grouped by preset — a persona can be reused under any preset, and listing only this session's tools would make the child fail to start after a preset switch (the official `toolFilter` rejects unknown names outright).
|
|
117
|
+
- **Import**: the header's "Import" takes `.md` and `.zip` (a `.md` at any depth inside a zip is imported by file name; **same names are skipped and listed**).
|
|
118
|
+
- **Running**: the model lists them with `subagent_list` and calls `subagent_run{agent, task}`; the child runs **with the persona**, inherits the memories of currently enabled scenes, returns only its final output (≤16 KiB) to the main model, and is discarded without entering History. A scene profile can bind "which personas are available in this scene" (a call outside the binding reports "persona unavailable"); running asks for confirmation by default (it spends real tokens), which `requireConfirmForModelSubagentRun: false` turns off.
|
|
119
|
+
- **Governance boundary**: all of the above covers only the `subagent_run` channel — DSH's own `subagent` / `subagent_fork` are host capabilities with no confirm gate and no notion of these personas, so they honour neither in any mode (see [FAQ](#faq)). Personas never enter a recycle bin (deleted is deleted), and v1 has no model tool that writes persona files.
|
|
120
|
+
|
|
121
|
+
### MCP servers
|
|
122
|
+
|
|
123
|
+
- **Add a server**: "Add server" asks for `serverName` (1–32 chars `[A-Za-z0-9_-]`, globally unique), the transport and its fields (`streamable-http` → URL / headers; `stdio` → command / args / env), and project or global level. The write lands as a loader row in `cordis.patch.yml` and applies via HMR.
|
|
124
|
+
- **See the state**: every card shows live status, loader phase and registered tool count; a summary bar sits on top, and fatal issues such as duplicate loader ids are flagged right on the page.
|
|
125
|
+
- **Turn off just one tool**: the "Details" dialog lists every tool — disable the ones the model keeps misusing; the schema disappears from the model's view and calls are denied, ready to re-enable anytime.
|
|
126
|
+
- **Inspect secrets safely**: secret-looking values render as `••••••` by default; click "Reveal" only when you need them.
|
|
127
|
+
- **Move and back up**: editing can rename a server or migrate it between project/global level (with automatic rollback on failure); JSON export/import covers full backups and machine moves.
|
|
128
|
+
|
|
129
|
+
### Skills
|
|
130
|
+
|
|
131
|
+
- **See everything**: skills are grouped by source — project, runtime, built-in, plugin-shipped, the four user directories (`~/.dsh` / `~/.agents` / `~/.codex` / `~/.claude`; the last three are hooked up by this plugin) and any custom directories you added.
|
|
132
|
+
- **Default sources must be read**: `DSH skills` (`~/.dsh/skills/`) and `Imported skills` (`~/.dsh/tool-management/skills/`) may not even be *disabled* — disabling means "still listed but not callable", which contradicts "the path may not go unread"; the server rejects it and the UI renders no source switch for these rows (they show a "Manageable" tag instead). Only the **source layer** is locked: the skills **inside** those two sources can still be deleted — the two are independent.
|
|
133
|
+
- **Toggle**: individual skills, whole sources or whole projects — implemented as an override-provider shadow policy, so not a single byte of the source file changes; moving machines is just copying the state file.
|
|
134
|
+
- **Remove a source**: unlike disabling one — a disabled source is still scanned and listed (its skills simply cannot be called) — **removing means the directory is not scanned at all**: its skills disappear from the list, drop out of the same-name priority and become invisible to the model (provider candidates). Not a single byte is touched on disk, and it can be restored at any time. Default and project-level sources cannot be removed and show no button.
|
|
135
|
+
- **Same-name skills**: when a name appears in several sources, the highest-priority source wins automatically (the others show "shadowed" and have no switch). To use another source's copy, click "Enable this one" on that row — the preference is written to the state file only (`preferredSkills`), never to a source file; the winner row can "Clear preference" to go back to automatic. When the whole source is disabled, "Enable this one" is refused with a hint to enable the source first.
|
|
136
|
+
- **Custom directories**: "Add directory" takes an absolute path and turns it into a read-only skill source — ideal for skill collections living in repos or synced folders; overlapping paths are rejected so the shadow policy stays sound.
|
|
137
|
+
- **Create / import / recycle**: create from a form; drag in a ZIP, a `.md` file or a skill folder, or use "Select folder" inside the import dialog; deleted skills go to the recycle bin first (restorable), and permanent delete still tries the OS trash as a last safety net. Deletion is available for sources the plugin itself manages (DSH skills / Imported skills / project `.dsh/skills`); external agent and custom directories are read-only and their skills cannot be deleted.
|
|
138
|
+
|
|
139
|
+
### AGENTS.md presets
|
|
140
|
+
|
|
141
|
+
- **Preset library**: create, import and edit multiple global instruction baselines (e.g. different teams' coding standards or role behaviours).
|
|
142
|
+
- **Apply = write**: "Apply" writes the selected preset to `~/.dsh/AGENTS.md` — **new sessions pick it up, current sessions stay unchanged**; "Apply again" syncs the latest content after editing. Every apply stores what it overwrites as a timestamped backup, **keeping the last 5 generations** (clicking twice cannot lose your original content); switch to another preset before deleting.
|
|
143
|
+
|
|
144
|
+
### Archived sessions
|
|
145
|
+
|
|
146
|
+
- **Grouped by project**: archived sessions are grouped by workspace; search by title / session ID / project path; sessions whose workspace folder no longer exists are flagged with ⚠. When a **workspace registration is deleted** (DSH deletes neither the folder nor the sessions), the group is rebuilt from session directories and marked "Workspace removed" / "Unregistered directory"; while the folder still exists one click re-registers it (a registration only — no file or session is touched), and restoring a session also attaches it back to its workspace (previously it simply fell into "Ungrouped").
|
|
147
|
+
- **Batch operations**: "Select all" then batch-restore or permanently delete; restored sessions return to the workspace list, and deletion cascades to their subagent sessions.
|
|
148
|
+
- **Retention**: pick the cleanup period from the dropdown (0 = keep forever); the expiry baseline is the later of the archive time and the last retention change, so changing the retention resets the countdown.
|
|
149
|
+
- **Import conversations**: take over sessions from other tools — Claude Code / Cursor JSONL, Codex Markdown, and arbitrary text — and keep chatting right after import.
|
|
150
|
+
- **Export conversations**: pick a session scope (all / archived only / by workspace); each session becomes a Markdown or JSONL file; the export directory defaults to the desktop, and the adjacent "Select" button opens a directory tree to browse and fill in the absolute path.
|
|
151
|
+
|
|
152
|
+
#### Archive service compatibility
|
|
153
|
+
|
|
154
|
+
- The plugin **neither disables nor replaces** the official `workspace` / `session-projection-cache`, registers no second service of the same name, and no longer guesses compatibility from a package name — `cordis.patch.yml` only inserts the plugin's own row.
|
|
155
|
+
- Archiving goes through the plugin's own facade: **a native entry when one exists, a checked adapter otherwise, and a refusal when neither works**. The decision is based on "are plugin and host the same physical module + does this capability exist on the live host object", **not on comparing official source text** — an upstream refactor can no longer kill the feature wholesale; the worst case is that one action is disabled with a reason, listed on the **Host** tab and in the refusal message.
|
|
156
|
+
- The adapter touches workspace internals (including methods the official API declares private) and, when the host lacks its own delete barrier, reversibly wraps the projection cache's `put` / `write`. **So "no longer replaces services / no longer competes for registration" does not mean "zero intrusion"**, nor "decoupled".
|
|
157
|
+
- Official durable events supply archive timestamps. Existing archives without a timestamp receive a fresh retention window on first observation instead of expiring from their older creation date.
|
|
158
|
+
- Legacy independent caches are retained untouched; official summaries may need rebuilding on demand. Explicit legacy `/workspace` or `/projcache` entries in user patches require review; the plugin does not rewrite those patches.
|
|
159
|
+
- Archive/restore/batch delete and late-cache-write protection were exercised with official services and isolated JSON storage. Actual co-installation with `@michengai/dsh-archive-manager` has not been verified; compatibility with arbitrary versions is not guaranteed.
|
|
160
|
+
|
|
161
|
+
### Host compatibility
|
|
162
|
+
|
|
163
|
+
At runtime the plugin uses the host's **own** `@deepseek-ai/*` libraries (`dsh-tools` / `dsh-workspace` / `dsh-session-projection-cache` / `cordis` / `dsh-storage-domain` / `dsh-spill-local`). Those must be the **same physical modules the host is running**: with two copies, `instanceof`, `===` and symbol lookups do not cross the boundary, and every "adapt to the host implementation" decision degrades into guesswork.
|
|
164
|
+
|
|
165
|
+
- **Settings → Tools → Host** (the eighth tab): the same check-up in the UI — host version and required range, verified plugin version, usable capability count, **which route each host action takes** (host-native entry / plugin adapter / unavailable), degraded capabilities with reasons, whether plugin and host share one module instance, and the blocker list. Read-only, nothing is modified.
|
|
166
|
+
- **Command line**:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
node scripts/doctor.mjs # read-only: same module instances? host capability overview
|
|
170
|
+
node scripts/host-deps.mjs # report drift (dry run by default)
|
|
171
|
+
node scripts/host-deps.mjs --fix # junction drifted packages into the host installation (originals backed up to .host-deps-backup/)
|
|
172
|
+
node scripts/host-deps.mjs --restore # put the originals back
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Re-run `--fix` after a DSH upgrade that moves the installation directory. When the host is missing a package, the plugin refuses the affected action and says why instead of guessing.
|
|
176
|
+
|
|
177
|
+
> `doctor.mjs` carries a **static subset** of the capability list (it only checks whether members exist, performs no runtime behaviour probe and does not cover the delete route); for the full runtime verdict, trust the **Host tab**.
|
|
178
|
+
|
|
179
|
+
### Let the model and scripts help
|
|
180
|
+
|
|
181
|
+
| Entry point | What it does |
|
|
182
|
+
|---|---|
|
|
183
|
+
| `skill_mcp_manager_list / set_enabled / restart / add` | Let the model query and operate MCP servers |
|
|
184
|
+
| `skill_manager_list / set_enabled / create` | Let the model query and operate skills (creating asks for your consent) |
|
|
185
|
+
| `agentsmd_list / agentsmd_apply` | Let the model list the AGENTS.md preset library and switch the active preset (writes `~/.dsh/AGENTS.md`, effective for new sessions); **no create or delete**, so the model cannot wipe your presets |
|
|
186
|
+
| `rule_manager_list / read / write` | Let the model query and write scene memories (writes ask for your consent; can be disabled in settings) |
|
|
187
|
+
| `subagent_list / subagent_run` | Let the model list personas and run a one-shot persona subagent (result only, discarded afterwards; running asks for your consent by default, can be disabled in settings) |
|
|
188
|
+
| `POST /dsh-plugin-tool-management/api` | HTTP API for scripts (`{op, args}` protocol) |
|
|
189
|
+
|
|
190
|
+
> v0.4 **no longer registers slash commands** (there used to be `/mcp`, `/skills`, `/agents-md`,
|
|
191
|
+
> `/scene-memory`): they could only print a text snapshot, could not operate anything, and drifted from
|
|
192
|
+
> the panel state. Every one of them has an equivalent entry in the settings panel.
|
|
193
|
+
|
|
194
|
+
## Where data lives
|
|
195
|
+
|
|
196
|
+
| Content | Location |
|
|
197
|
+
|---|---|
|
|
198
|
+
| MCP server definitions | `profiles/<profile>/cordis.patch.yml` (project) or `~/.dsh/cordis.patch.yml` (global), auto-`.bak` before every rewrite |
|
|
199
|
+
| Server notes / page settings / disabled tools / export | Sidecar JSON files under the DSH home (`dsh-plugin-tool-management-*.json`) |
|
|
200
|
+
| Skill toggle policy / same-name preferences / custom directories | `~/.dsh/tool-management/state.json` (`sources` / `enabledSkills` / `disabledSkills` / `preferredSkills` / `customRoots`). **The default sources `dsh` / `hub` are not in the `sources` map**: they must be read and have no source switch; `sources.hub` / `removedSources` entries written by older versions are dropped on read |
|
|
201
|
+
| Skill recycle bin / import staging | `~/.dsh/tool-management/trash`, `uploads` |
|
|
202
|
+
| Skills created/imported by the plugin | `~/.dsh/tool-management/skills/<skill>/` (the official `~/.dsh/skills/` is listed as a source too and is equally manageable: its skills can be deleted, the source itself cannot be removed or disabled) |
|
|
203
|
+
| AGENTS.md presets / applied file | `~/.dsh/tool-management/agents-md/<preset id>/AGENTS.md`; "Apply" writes `~/.dsh/AGENTS.md`, overwritten content is backed up under `__last-applied__/` (5 generations) |
|
|
204
|
+
| Archive ledger / retention / workspace registration snapshot | `~/.dsh/tool-management/history-archived-at.json`, `history-retention.json`, `history-workspaces.json` (the old location was the plugin dir `data/`, moved in on startup — moved, never overwritten. It **must not** live in the plugin directory: under an npm install `dsh plugin update` replaces that directory wholesale, and losing the ledger makes the retention baseline fall back to session creation time, so archived sessions get cleaned up too early) |
|
|
205
|
+
| Memory files (source of truth) | `~/.dsh/tool-management/memories/<scene>/<name>.md` (flat) or `<scene>/<name>/SKILL.md` (bundle); scene names may be non-ASCII; the reserved scene **`global`** (shown as "Global") is injected into every conversation; a bare `.md` in the `memories/` root belongs to no scene and is **never injected** (the check-up reports `noScene`) |
|
|
206
|
+
| Memory index / scene records / enabled scenes | `~/.dsh/tool-management/rules-index.json` (`enabled` / order / tags + `scenes` records (label / description / order) + `active` enabled-scene set (`null` = all) + `archives` profile selections + `mode` snapshot) |
|
|
207
|
+
| Persona files (source of truth) | `~/.dsh/tool-management/agents/<persona>.md` (frontmatter optional, body = persona prompt) |
|
|
208
|
+
| Page settings / confirm switches | `~/.dsh/dsh-plugin-tool-management-settings.json` (`requireConfirmForModelSubagentRun` etc.) |
|
|
209
|
+
| Memory recycle bin | `~/.dsh/tool-management/rules-trash/<trashId>/` (deleted memories land here and can be restored) |
|
|
210
|
+
| Runtime log | `~/.dsh/dsh-plugin-tool-management.log` (rolling) |
|
|
211
|
+
|
|
212
|
+
**No user data is ever stored inside the plugin's installation directory** — under an npm install, `dsh plugin update` replaces that directory wholesale.
|
|
213
|
+
|
|
214
|
+
## Configuration & security
|
|
215
|
+
|
|
216
|
+
Optional fields on the plugin loader row (`dsh plugin add` inserts it automatically):
|
|
217
|
+
|
|
218
|
+
| Field | Description |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `token` | Optional access token. When set, **every write operation and "Reveal"** requires the `x-dsh-token` header. It also acts as the escape hatch from browser authentication: a correct token is accepted as authorization on its own, for curl/scripts and LAN deployments. The client reads it from localStorage (key `dsh-plugin-tool-management-token`; set it in the DevTools console and refresh), or via the `DSH_PLUGIN_TOOL_MANAGEMENT_TOKEN` environment variable. |
|
|
221
|
+
| `maxBodyBytes` | Request body cap, default 88 MiB (skill ZIP uploads need it). |
|
|
222
|
+
|
|
223
|
+
How authentication works: the plugin route is **not** behind a host-wide auth gate, so it calls the host's `connection.requestRejection(req)` itself — first the Host / Origin fence (Host must be a loopback or deployment-derived LAN IP literal, the one header DNS rebinding cannot forge), then browser-session cookie authentication. If that host service is absent, a **local check** takes over that only requires "loopback Host + not a cross-site fetch + Origin matching Host" and **does not include cookie authentication** (weaker than the host fence — configure a token in such an environment). Therefore:
|
|
224
|
+
|
|
225
|
+
- **In the browser GUI**: authenticated by cookie, no token needed.
|
|
226
|
+
- **curl / scripts**: send a correct `x-dsh-token`, or carry the browser cookie.
|
|
227
|
+
- Endpoints that return plaintext secrets ("Reveal", config export) additionally require an `Origin` header — a same-origin browser request always sends one, which filters out local scripts that omit it.
|
|
228
|
+
- If you forward the port to a LAN or the public internet, a token remains the key defense against strangers injecting MCP commands (equivalent to remote code execution — `command` / `args` are spawned verbatim) and reading plaintext secrets, so **configure one**.
|
|
229
|
+
- The `x-dsh-plugin` custom header is only a contact-prevention token, not a credential.
|
|
230
|
+
|
|
231
|
+
## FAQ
|
|
232
|
+
|
|
233
|
+
| Symptom | Fix |
|
|
234
|
+
|---|---|
|
|
235
|
+
| Pages missing in Settings after install | Hard refresh; if that fails, restart DSH once. |
|
|
236
|
+
| Duplicate MCP tabs / duplicated tools | Stale loader row double-mounting the plugin — remove the old entry from `cordis.patch.yml` and restart. |
|
|
237
|
+
| Broken config, DSH won't boot | Restore the newest `cordis.patch.yml.bak-<timestamp>` next to it. |
|
|
238
|
+
| Page data not refreshing | Wait for the automatic polling (default 5s) or click "Refresh". |
|
|
239
|
+
| Latest version not found on a mirror | Add `--registry=https://registry.npmjs.org` and retry later. |
|
|
240
|
+
| An action stopped working after a DSH upgrade | Open Settings → Tools → **Host** for the reason; run `node scripts/doctor.mjs` first, and `node scripts/host-deps.mjs --fix` if it reports two copies. |
|
|
241
|
+
| Archive / restore / delete reports an unavailable capability | Same as above. The plugin prefers refusing over mutating data through an unknown implementation, and the message names the missing capability plus the recovery step. |
|
|
242
|
+
| Do the confirmations still apply in full access (`approval=never`)? | **No, and no card appears.** The three confirm gates (`rule_manager_write` / `skill_manager_create` / `subagent_run`) treat a `never` session as "the user has pre-approved", so they pass straight through and write a `confirm-bypass` line to `~/.dsh/dsh-plugin-tool-management.log`. Switch the access mode back to "workspace write" to get asked again, or turn off a single gate with the matching `requireConfirmForModel*` setting. |
|
|
243
|
+
| `subagent_run` reports "spawn provider unavailable" | **Conditional**: the host ships a `spawn` provider (recent versions need no extra package and no mount). It only appears when the host really registers none *and* this plugin cannot mount `@deepseek-ai/dsh-subagent-spawn-in-process` either — the message carries the original reason, and it is mostly an older version or a specific profile. Mount that package in the host profile and restart DSH: this plugin deliberately keeps it out of `cordis.patch.yml` so a host without the package still boots. |
|
|
244
|
+
| The scene binds only persona A, so why did an unbound subagent still run? | **There are two subagent channels.** This plugin's `subagent_run` goes through its confirm gate and the scene persona binding; DSH's own `subagent` / `subagent_fork` are host capabilities with **no confirm gate and no notion of this plugin's personas**, so they honour neither in any mode (verified live: in one message the official `subagent` returned with no approval card while the following `subagent_run` did prompt). This plugin's governance covers `subagent_run` only. |
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
npm install
|
|
250
|
+
npm run build # build (tsc + sync client bundle)
|
|
251
|
+
npm run build:client # sync src/client.js → lib/client.js only
|
|
252
|
+
npm run lint # syntax self-check (node --check on both artifacts)
|
|
253
|
+
npm run check:i18n # dictionaries: key sets / duplicates / placeholders + every literal key referenced in code must exist
|
|
254
|
+
npm run check:host # host dependency check (read-only, see "Host compatibility")
|
|
255
|
+
npm run host-deps # point the plugin's shared deps at the host installation
|
|
256
|
+
npm run doctor # print module identity + host capability probe results
|
|
257
|
+
npm test # build + i18n check + semantic-contract tests (node --test test/*.test.mjs, 13 groups)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
> `lib/` is generated by `npm run build` and is **not** tracked in git — build before anything else after cloning.
|
|
261
|
+
> The host loads `lib/` at startup, so **changes need `dsh web` restarted** (patch hot-reload does not re-import the plugin module).
|
|
262
|
+
|
|
263
|
+
### How this project verifies things
|
|
264
|
+
|
|
265
|
+
Verification means **actually exercising the real behaviour**, not asserting what the code currently does — the latter just copies the implementation and passes by construction.
|
|
266
|
+
**Wording and layout are not asserted line by line** (maintainer decision, 2026-09-14: those assertions chase a moving target — every style change would demand an assertion change, and the real look is confirmed by a human on the page).
|
|
267
|
+
|
|
268
|
+
What remains is 13 groups of **semantic-contract** tests (`npm test`, run against the built `lib/`):
|
|
269
|
+
|
|
270
|
+
| Test | Contract it asserts |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `archive.test.mjs` | Scene engine state machine: section existence is independent of empty sets, selection → disable complement, deep-copied snapshots, failure rolls back and is reported honestly |
|
|
273
|
+
| `import.test.mjs` | Import expansion and landing plans: path traversal rejected, ZIP recognised by magic bytes not extension, over-limit and illegal entries each reported with a reason |
|
|
274
|
+
| `approval-policy.test.mjs` | The `approval=never` detection chain, driven by a real cordis context and a real `ApprovalService`; a missing service or a throw never means "allow" |
|
|
275
|
+
| `subagent-scene.test.mjs` | Scene persona bindings: a call outside the binding must be rejected **before** the subagent runs |
|
|
276
|
+
| `subagent-persona.test.mjs` | Persona frontmatter round-trip: `provider` / `model` / `toolsDeny` survive a UI save; creating a persona with no directory present |
|
|
277
|
+
| `hub-layout.test.mjs` | Unified data directory: legacy layouts move without overwriting, the reserved `global` scene always exists and cannot be deleted, a memory must belong to an existing scene, and the profile memory section only affects projection |
|
|
278
|
+
| `skills-state.test.mjs` | State-file read resilience: missing keys self-heal, type errors stay fail-closed, leftover policy bits for default sources are dropped |
|
|
279
|
+
| `skills-delete.test.mjs` | Default-source skills can be deleted and restored **byte-for-byte** from the recycle bin; read-only sources stay read-only |
|
|
280
|
+
| `skills-source-remove.test.mjs` | "Remove a source": it is no longer read, drops out of the same-name priority and is invisible to the model, while not a byte on disk changes and it can be restored; default sources can neither be removed nor disabled |
|
|
281
|
+
| `client-exports.test.mjs` | Client export contract: evaluating the factory alone — without running `apply` — must already expose `dict` / `pages`; exports written inside the `apply` method body are rejected |
|
|
282
|
+
| `client-render.test.mjs` | Assembly and rendering: a fake ctx drives the whole `apply`, `settings.section` is registered and the entire component tree renders without throwing (this is the one that caught a real blank screen), and again once data has arrived; the scenes page and profile dialog pin structure rather than wording; the Host tab colours only for real blockers or real degradation, and its summary line must contain no Chinese characters under the English dictionary |
|
|
283
|
+
| `compat-probe.test.mjs` | Host capability probing: host objects are built from the real official class prototypes, and a renamed member, a changed return shape or a wholly absent service must degrade into a **named** finding instead of a throw; the probe itself is read-only |
|
|
284
|
+
| `compat-fallback.test.mjs` | The capability gate really stops writes: with the serialized-write capability missing, archiving is refused and not a single host write method is called; the cache is wrapped only when the host lacks its own delete barrier, and restored on dispose |
|
|
285
|
+
|
|
286
|
+
Also `npm run check:i18n` (dictionary key sets, duplicates, placeholder alignment and literal-reference completeness — currently 722 keys per language, 454 referenced literals) and `node scripts/i18n-debt.mjs` (how much hard-coded Chinese is left — **214 lines today**: 15 on the prompts page, the rest spread across components and shared shells the script does not attribute to a page; the sessions page is at zero).
|
|
287
|
+
|
|
288
|
+
These tests assert contracts, not implementation copies; **real-behaviour acceptance still happens in the browser / host and these tests do not replace it**.
|
|
289
|
+
|
|
290
|
+
### Layout
|
|
291
|
+
|
|
292
|
+
Host half `src/index.ts` (object-form Cordis plugin, `lib/index.js` is the shipped artifact); host capability probe `src/compat/probe.ts` (the single place that decides what the plugin may do to the host, read-only); HTTP fence `src/http-fence.ts`; data-directory constants and migration `src/hub.ts`; skill core `src/skills/core.js` (pure Node); AGENTS.md presets `src/agents-md/service.ts`; archived session management `lib/history/` (`workspace.js` facade, `bridge.js` compatibility layer, `projcache.js`, `tombstone.js`); transcript import parsing `src/imports/parsers.js`; scene-memory store `src/rules/` (`service.ts` discovery, CRUD, index, check-up and two-phase section render; `provider.ts` registers the per-agent `systemPrompt` section — the module path and `rules-*` op names stay as internal protocol, while the user-visible page and folder are "Scene memory" / `memories/`); browser half `src/client.js` (ModuleLoader CJS bundle, `dsm-*` design system, talks to the host through the same-origin API); dev scripts `scripts/host-deps.mjs` (dependency alignment) and `scripts/doctor.mjs` (compatibility check).
|
|
293
|
+
|
|
294
|
+
The only runtime dependency is `fflate` (ZIP extraction); every `@deepseek-ai/*` package comes from the host (see "Host compatibility" above).
|
|
295
|
+
|
|
296
|
+
Publish: `npm publish` (`prepublishOnly` builds automatically; bump with `npm version <minor|patch>`).
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
MIT
|