dsh-plugin-tool-management 0.2.0 → 0.5.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_EN.md CHANGED
@@ -1,231 +1,290 @@
1
- # dsh-plugin-tool-management
2
-
3
- [![npm version](https://img.shields.io/npm/v/dsh-plugin-tool-management?logo=npm&color=cb3837)](https://www.npmjs.com/package/dsh-plugin-tool-management)
4
- [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
5
- [![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js)](package.json)
6
- [![GitHub](https://img.shields.io/badge/GitHub-ouli--1242%2Fdsh--plugin--tool--management-181717?logo=github)](https://github.com/ouli-1242/dsh-plugin-tool-management)
7
-
8
- [简体中文](README.md) · **English**
9
-
10
- **An MCP server, skills & memory manager for DeepSeek Harness.** One settings panel keeps five things under control:
11
-
12
- - **MCP**: which servers are configured, what tools each one exposes, and which tools the model may call — add, edit, remove, toggle, restart; every change takes effect immediately;
13
- - **Skills**: every skill on the machine (DSH / Agents / Codex / Claude / project-level / any directory you add) at a glance — toggle individually or per source, create, import, recycle;
14
- - **AGENTS.md**: keep multiple global instruction baselines as presets, apply one with a click to write `~/.dsh/AGENTS.md` — new sessions pick it up, current sessions stay unchanged;
15
- - **History**: archived sessions in one place — grouped by project, batch restore / delete, import & export transcripts, retention-based auto-cleanup;
16
- - **Scene Memory**: under `~/.dsh/scene-memory/<scene>/`, one folder = one scene and one `.md` = one memory — **create a scene**, drop `.md` files in (Chinese file names are fine), toggle the scene; the full body of every memory in an enabled scene is **injected into the system prompt automatically**, so you never repeat yourself.
17
-
18
- No hand-editing of `cordis.patch.yml`, and skill source files are never touched. Configuration survives restarts and upgrades.
19
-
20
- ---
21
-
22
- <!-- Image slot 1: MCP management page screenshot → docs/images/mcp.png -->
23
-
24
- ![MCP management](docs/images/mcp.png)
25
-
26
- <!-- Image slot 2: Skills management page screenshot → docs/images/skills.png -->
27
-
28
- ![Skills management](docs/images/skills.png)
29
-
30
- <!-- Image slot 3: AGENTS.md presets page screenshot → docs/images/agents-md.png -->
31
-
32
- ![AGENTS.md presets](docs/images/agents-md.png)
33
-
34
- <!-- Image slot 4: History archived sessions page screenshot → docs/images/history.png -->
35
-
36
- ![History archived sessions](docs/images/history.png)
37
-
38
- <!-- Image slot 5: Scene memory page screenshot → docs/images/场景记忆.png -->
39
-
40
- ![Scene memory](docs/images/场景记忆.png)
41
-
42
- ## Highlights
43
-
44
- | Capability | Description |
45
- |---|---|
46
- | Per-tool switches | **Toggle individual tools** inside one MCP server: hidden from the model and blocked at call time, restorable at any moment; whole-server batch enable/disable also supported |
47
- | Restart semantics | Restart only reconnects — it **never flips the enabled state** (restarting a disabled server does not silently enable it) |
48
- | Secret safety | Secret-looking values in `env` / `headers` are **masked by default**, URL query strings are always redacted; revealing plaintext is token-gated just like writes |
49
- | Write protection | Every patch rewrite keeps a timestamped `.bak` backup (last 5); duplicate loader ids are rejected before write; failed cross-level migration rolls back |
50
- | Backup / restore | JSON import supports `conflict: 'overwrite'` to replace entries with the same id, not just skip them |
51
- | Skill sources | Hooks up `~/.agents` / `~/.codex` / `~/.claude` (three directories official DSH does not load) plus **any custom skill directory** you add (read-only, overlapping paths rejected) |
52
- | Skill operations | Create skills, import ZIP / folders, plugin recycle bin (restore / permanent delete with OS-trash fallback), open the source file in the system editor |
53
- | Live refresh | Skill directories are watched from a background thread — edits made in an editor show up automatically |
54
- | AGENTS.md presets | Multiple global instruction baselines as presets — create / import / edit / apply / delete; "Apply" writes `~/.dsh/AGENTS.md` (new sessions pick it up, current sessions stay unchanged) |
55
- | Archived session management | History page groups archived sessions by project: search, select-all, batch restore / permanent delete, retention-based auto-cleanup (changing the retention resets the countdown from the change time) |
56
- | Transcript import / export | Seamlessly take over conversations from Claude Code / Cursor (JSONL), Codex (Markdown), or any text; export picks the session scope, defaults to the desktop, in Markdown / JSONL |
57
- | Slash commands | `/mcp`, `/skills`, `/agents-md`, `/scene-memory` right from the chat box |
58
- | Scene memory auto-injected | A memory is `~/.dsh/scene-memory/<scene>/<name>.md`; every `.md` inside an enabled scene is **injected into the system prompt automatically** (per-agent `systemPrompt` section) with no tool call, and toggling takes effect on the next request |
59
- | Scene enable switch | A scene is a top-level `scene-memory/` folder (Unicode names fine); the multi-select switch persists globally in `rules-index.json`'s `active`; **all scenes enabled by default**, `_shared/` always on |
60
- | Prefix-cache friendly | Section text depends only on enabled scenes + file contents, so it is byte-stable; switching scenes or editing a memory changes it exactly once, every other request keeps hitting the cache (this does not violate the "no injection layer" rule — that one only bans per-turn dynamic content) |
61
- | Rule checkup | One click scans for shadowed rules, over-long descriptions, filename ≠ name, bad frontmatter, empty bodies, and memories whose scene is disabled |
62
- | Model tools | **10**: `skill_mcp_manager_*` for MCP, `skill_manager_*` for skills, `rule_manager_*` for memories (creation asks for confirmation unless disabled in settings) |
63
- | Model tools | **10 tools**: `skill_mcp_manager_*` for MCP servers, `skill_manager_*` for skills, `rule_manager_*` for rules (writes ask for user confirmation; can be disabled in settings) |
64
- | UI | Its own `dsm-*` design system, consistent across all six pages |
65
-
66
- ## Getting started
67
-
68
- Prerequisites: DSH installed (`dsh web` runs), Node.js ≥ 18.
69
-
70
- ```sh
71
- # Install (package + auto-mount)
72
- dsh plugin --profile web add dsh-plugin-tool-management@latest
73
-
74
- # Update: run the same command again
75
- # Uninstall:
76
- dsh plugin --profile web remove dsh-plugin-tool-management
77
- ```
78
-
79
- Hard-refresh the browser (Cmd/Ctrl+Shift-R) after installing — the **MCP**, **Skills**, **AGENTS.md**, **History** and **Scene Memory** pages appear in Settings (client changes are hot-loaded by DSH, no restart needed).
80
-
81
- You can also tell any DSH session:
82
-
83
- ```text
84
- Install the dsh-plugin-tool-management plugin:
85
- dsh plugin --profile web add dsh-plugin-tool-management@latest
86
- Then remind me to hard-refresh the browser.
87
- ```
88
-
89
- ## Feature guide
90
-
91
- ### Managing MCP servers
92
-
93
- - **Add a server**: fill in `serverName` (unique, 1–32 chars `[A-Za-z0-9_-]`), the transport and its fields (`streamable-http` → URL / headers; `stdio` → command / args / env), and choose project or global level. The write lands as a loader row in `cordis.patch.yml` and applies via HMR.
94
- - **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.
95
- - **Turn off just one tool**: the "Details" dialog lists every tool with its parameter summary — disable the ones the model keeps misusing; the schema disappears from the model's view and calls are denied, ready to re-enable anytime.
96
- - **Inspect secrets safely**: secret-looking values render as `••••••` by default; click "Reveal" only when you need them.
97
- - **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.
98
-
99
- ### Managing skills
100
-
101
- - **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.
102
- - **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.
103
- - **Custom directories**: click "Add directory", enter an absolute path, and that directory becomes a read-only skill source — ideal for skill collections living in repos or synced folders; overlapping paths are rejected to keep the shadow policy sound.
104
- - **Create / import / recycle**: create from a form; drag in a ZIP, a `.md` file or a skill folder; deleted skills go to the plugin recycle bin first, and permanent delete still tries the OS trash as a last safety net.
105
-
106
- ### Managing AGENTS.md presets
107
-
108
- - **Preset library**: create, import and edit multiple global instruction baselines (e.g. different teams' coding standards or role behaviors).
109
- - **Apply = write**: "Apply" writes the selected preset to `~/.dsh/AGENTS.md` — **new sessions pick it up, current sessions stay unchanged**; "Re-apply" syncs the latest content after editing; switch to another preset before deleting.
110
-
111
- ### Managing archived sessions
112
-
113
- - **Grouped by project**: archived sessions are grouped by workspace automatically; search by title / session ID / project path; sessions whose workspace folder no longer exists are flagged with ⚠.
114
- - **Batch operations**: "Select all" then batch-restore or permanently delete; restored sessions return to the workspace list, and deletion cascades to their subagent sessions.
115
- - **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.
116
- - **Import conversations**: take over sessions from other tools — Claude Code / Cursor JSONL, Codex Markdown, and arbitrary text — and keep chatting right after import.
117
- - **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.
118
-
119
- ### Managing scene memory (the Scene Memory page)
120
-
121
- > This page merges the former "Rules" and "Scenes" pages: **a scene (top-level folder) is the grouping dimension, a memory (`.md`) is the content.**
122
- > The folder was also renamed from `~/.dsh/rules/` to **`~/.dsh/scene-memory/`** — move your existing files over after upgrading (see "Upgrade note" below).
123
-
124
- - **A scene is a top-level folder under `scene-memory/`; the folder name *is* the scene name**: `~/.dsh/scene-memory/办公/流程.md` is one memory in the "办公" scene. Folder names accept any Unicode (≤64 chars, no `/ \ < > : " | ? *`, must not start with a dot); `_shared/` is the reserved shared scene.
125
- - **New scene**: "New scene" creates the folder for you (or just `mkdir` under `scene-memory/` — same result). Empty scenes are listed and get a "Delete scene" button; a scene that still holds memories cannot be deleted, so nothing is lost in one click.
126
- - **Every `.md` is one memory**: a sentence or a paragraph, no frontmatter needed, and the whole body is injected. Drop a file into the scene folder and it takes effect, or use "New memory" on the card to write it on the page — **file names can be Chinese** (e.g. `站会流程.md`).
127
- - **Toggle a scene**: the switch on the right of each scene card enables/disables it (same component and layout as the Skills page). Every `.md` inside an enabled scene is **injected into the system prompt automatically**; the model needs no tool call and you never have to explain again. Toggling takes effect on the **very next request**, with no new session and no plugin reload.
128
- - **All scenes are enabled by default**: with no configuration at all, every scene is live ("drop it in and it works"); narrow the set in the UI once you have many scenes. `_shared/` is always on (its card has no checkbox).
129
- - **One memory = one Markdown file**: `<scene>/<name>.md` (flat) or `<scene>/<name>/SKILL.md` (bundle, with attachments). When creating, fill in the scene (pick an existing one or **type a new scene name** — its folder is created for you), the name (= file name), description and body — frontmatter is entirely optional and derived automatically when missing.
130
- - **Bundle attachments**: with the bundle form you can **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 you can remove them one by one while editing. The flat form is a single file, so it has nowhere to put attachments.
131
- - **Toggle & recycle**: enable/disable each memory (the switch on the right of every row — a disabled memory stays on disk and is simply left out of the prompt), edit, and move to trash; the "Trash" button in the page header can **restore** or **permanently delete** removed memories, with a confirmation step before the permanent delete. `enabled` and friends live in the sidecar index and are never written back to your files.
132
- - **Injection budget is visible**: a budget bar (used / max bytes) sits under the summary and turns red with an "Over budget" label. 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 instead of losing it silently.
133
- - **`~/.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.
134
-
135
- #### Caching and refresh (§5.2)
136
-
137
- | Situation | Is the prefix stable? | Result |
138
- |---|---|---|
139
- | Scene set unchanged, memory files unchanged | byte-for-byte stable | ✅ prompt prefix cache hits |
140
- | Enabling/disabling a scene (explicit action) | changes once | ⚠️ that session re-warms once — acceptable |
141
- | Editing a memory (page or editor) | changes once | ⚠️ same, and it takes effect on the **next request** |
142
- | Timestamps / counts / relative time in the section | changes every request | ❌ forbidden (and absent from the implementation) |
143
-
144
- The implementation uses a **two-phase scan with a fingerprint cache**: each assembly only walks
145
- directories with `stat` to build a fingerprint (no body reads) and reuses the previous rendering
146
- when it is unchanged; only a changed fingerprint (scene toggle, file edit, enable/disable) triggers
147
- reading bodies and re-rendering. **`fs.watch` is deliberately not used** — recursive watching is
148
- unreliable on Windows, and a silently dead watcher would return stale content forever; the
149
- fingerprint probe costs sub-milliseconds and buys "always fresh, never silently stale".
150
-
151
- #### Upgrade note: the folder was renamed
152
-
153
- Since v0.3 the default folder is `~/.dsh/scene-memory/`; the plugin **neither reads nor migrates** the
154
- old `~/.dsh/rules/` automatically. Just move your content over (instant on the same volume):
155
-
156
- ```sh
157
- # Windows PowerShell
158
- Move-Item ~/.dsh/rules ~/.dsh/scene-memory
159
- # macOS / Linux
160
- mv ~/.dsh/rules ~/.dsh/scene-memory
161
- ```
162
-
163
- If the new folder already exists, move the **scene subfolders** one by one instead; `_shared/` is an
164
- ordinary scene folder and moves along with the rest.
165
-
166
- ### Let the model and scripts help
167
-
168
- | Entry point | What it does |
169
- |---|---|
170
- | `/mcp`, `/skills`, `/agents-md`, `/scene-memory` | Check the current state from the chat box |
171
- | `skill_mcp_manager_list / set_enabled / restart / add` | Let the model query and operate MCP servers |
172
- | `skill_manager_list / set_enabled / create` | Let the model query and operate skills (creating asks for your consent) |
173
- | `rule_manager_list / read / write` | Let the model query and write rules (writes ask for your consent; can be disabled in settings) |
174
- | `POST /dsh-plugin-tool-management/api` | HTTP API for scripts (`{op, args}` protocol) |
175
-
176
- ## Configuration & security
177
-
178
- Optional fields on the plugin loader row (`dsh plugin add` inserts it automatically):
179
-
180
- | Field | Description |
181
- |---|---|
182
- | `token` | Optional access token. When set, **every write operation and "Reveal"** requires the `x-dsh-token` header. 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. |
183
- | `maxBodyBytes` | Request body cap, default 88 MiB (skill ZIP uploads need it). |
184
-
185
- Why a token: the cross-site protection (POST-only + custom header + same-origin check) assumes DSH listens on localhost only. If you forward the port to a LAN or the public internet, the token is the last line of defense against strangers injecting MCP commands (equivalent to remote code execution) and reading plaintext secrets — not needed for local single-user setups.
186
-
187
- ## Where data lives
188
-
189
- | Content | Location |
190
- |---|---|
191
- | MCP server definitions | `profiles/<profile>/cordis.patch.yml` (project) or `~/.dsh/cordis.patch.yml` (global), auto-`.bak` before every rewrite |
192
- | Server notes / page settings / disabled tools / export | Sidecar JSON files under the DSH home (`dsh-plugin-tool-management-*.json`) |
193
- | Skill toggle policy / custom directories | `~/.dsh/tool-management/state.json` |
194
- | Skill recycle bin / import staging | `~/.dsh/tool-management/trash`, `uploads` |
195
- | AGENTS.md presets / applied file | Plugin dir `data/agents-md-presets/`; "Apply" writes `~/.dsh/AGENTS.md` |
196
- | Archived session ledger / retention | Plugin dir `data/history-archived-at.json`, `data/history-retention.json` |
197
- | Rule files (source of truth) | `~/.dsh/scene-memory/<scene>/<name>.md` (flat) or `<scene>/<name>/SKILL.md` (bundle); scene folder names may be non-ASCII |
198
- | Rule index / enabled scenes | `~/.dsh/tool-management/rules-index.json` (`enabled` / order / tags + `active` enabled-scene set; `active: null` = all scenes enabled) |
199
- | Rule recycle bin | `~/.dsh/tool-management/rules-trash/<trashId>/` (deleted memories land here and can be restored) |
200
- | Runtime log | `~/.dsh/dsh-plugin-tool-management.log` (rolling) |
201
-
202
- ## FAQ
203
-
204
- | Symptom | Fix |
205
- |---|---|
206
- | Pages missing in Settings after install | Hard refresh; if that fails, restart DSH once. |
207
- | Duplicate MCP tabs / duplicated tools | Stale loader row double-mounting the plugin — remove the old entry from `cordis.patch.yml` and restart. |
208
- | Broken config, DSH won't boot | Restore the newest `cordis.patch.yml.bak-<timestamp>` next to it. |
209
- | Page data not refreshing | Wait for the automatic polling (default 5s) or click "Refresh". |
210
- | Latest version not found on a mirror | Add `--registry=https://registry.npmjs.org` and retry later. |
211
-
212
- ## Development
213
-
214
- ```bash
215
- npm install
216
- npm run build # build (tsc + sync client bundle)
217
- npm run build:client # sync src/client.js → lib/client.js only
218
- npm run lint # syntax self-check (node --check on both artifacts)
219
- ```
220
-
221
- > This project keeps no test suite. Changes are verified by **actually exercising the real
222
- > behaviour** (see the acceptance items in the change requests under `docs/`) instead of asserting
223
- > what the code currently does — the latter just copies the implementation and passes by construction.
224
-
225
- Layout: host half `src/index.ts` (object-form Cordis plugin, `lib/index.js` is the shipped artifact); skill core `src/skills/core.js` (pure Node); AGENTS.md presets `src/agents-md/service.ts`; archived session management `lib/history/` (`workspace.js` / `projcache.js` / `tombstone.js`); transcript import parsing `src/imports/parsers.js`; scene-memory store `src/rules/` (`service.ts` discovery/CRUD/index/checkup/two-phase section render, `provider.ts` per-agent `systemPrompt` section registration; the module path and `rules-*` op names stay as internal protocol, while the user-visible page and folder became “Scene Memory” / `scene-memory/`); browser half `src/client.js` (ModuleLoader CJS bundle, `dsm-*` design system, talks to the host through the same-origin API). The only runtime dependency is `fflate` (ZIP extraction).
226
-
227
- Publish: `npm version patch && npm publish` (`prepublishOnly` builds automatically).
228
-
229
- ## License
230
-
231
- MIT
1
+ # dsh-plugin-tool-management
2
+
3
+ [![npm version](https://img.shields.io/npm/v/dsh-plugin-tool-management?logo=npm&color=cb3837)](https://www.npmjs.com/package/dsh-plugin-tool-management)
4
+ [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
5
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js)](package.json)
6
+ [![GitHub](https://img.shields.io/badge/GitHub-ouli--1242%2Fdsh--plugin--tool--management-181717?logo=github)](https://github.com/ouli-1242/dsh-plugin-tool-management)
7
+ [![DSH Market](https://raw.githubusercontent.com/2BingLing/dsh-market/master/assets/readme/badge-listed-en.svg)](https://dsh.market/)
8
+
9
+ [简体中文](README.md) · **English** · [Changelog](docs/Changelog.md)
10
+
11
+ **An MCP server, skills & memory manager for DeepSeek Harness.** One settings panel keeps five things under control:
12
+
13
+ - **MCP**: which servers are configured, what tools each one exposes, and which tools the model may call — add, edit, remove, toggle, restart; every change takes effect immediately;
14
+ - **Skills**: every skill on the machine (DSH / Agents / Codex / Claude / project-level / any directory you add) at a glance — toggle individually or per source, create, import, recycle;
15
+ - **AGENTS.md**: keep multiple global instruction baselines as presets, apply one with a click to write `~/.dsh/AGENTS.md` — new sessions pick it up, current sessions stay unchanged;
16
+ - **History**: archived sessions in one place — grouped by project, batch restore / delete, import & export transcripts, retention-based auto-cleanup;
17
+ - **Scene Memory**: under `~/.dsh/tool-management/memories/<scene>/`, one folder = one scene and one `.md` = one memory — **create scenes** (with a description), drop `.md` files in (non-ASCII names are fine), toggle scenes; the bodies of memories in an enabled scene are **injected into the system prompt in full**, so you never re-explain them. The reserved scene `global` ("Global" in the UI) is injected into every conversation.
18
+
19
+ No hand-editing of `cordis.patch.yml`, and skill source files are never touched. Configuration survives restarts and upgrades.
20
+
21
+ ---
22
+
23
+ <!-- Image slot 1: MCP management page screenshot → docs/images/MCP.png -->
24
+
25
+ ![MCP management](docs/images/MCP.png)
26
+
27
+ <!-- Image slot 2: Skills management page screenshot → docs/images/技能.png -->
28
+
29
+ ![Skills management](docs/images/技能.png)
30
+
31
+ <!-- Image slot 3: AGENTS.md presets page screenshot → docs/images/提示词.png -->
32
+
33
+ ![AGENTS.md presets](docs/images/提示词.png)
34
+
35
+ <!-- Image slot 4: History archived sessions page screenshot → docs/images/会话.png -->
36
+
37
+ ![History archived sessions](docs/images/会话.png)
38
+
39
+ <!-- Image slot 5: Scene memory page screenshot → docs/images/场景.png -->
40
+
41
+ ![Scene memory](docs/images/场景.png)
42
+ <!-- Image slot 6: Memory page screenshot → docs/images/记忆.png -->
43
+ ![Memory](docs/images/记忆.png)
44
+
45
+ <!-- Image slot 7: Subagents page screenshot → docs/images/子智能体.png -->
46
+ ![Subagents](docs/images/子智能体.png)
47
+
48
+ ## Highlights
49
+
50
+ | Capability | Description |
51
+ |---|---|
52
+ | Per-tool switches | **Toggle individual tools** inside one MCP server: hidden from the model and blocked at call time, restorable at any moment; whole-server batch enable/disable also supported |
53
+ | Restart semantics | Restart only reconnects — it **never flips the enabled state** (restarting a disabled server does not silently enable it) |
54
+ | Secret safety | Secret-looking values in `env` / `headers` are **masked by default**, URL query strings are always redacted; revealing plaintext is token-gated just like writes |
55
+ | Write protection | Every patch rewrite keeps a timestamped `.bak` backup (last 5); duplicate loader ids are rejected before write; failed cross-level migration rolls back |
56
+ | Backup / restore | JSON import supports `conflict: 'overwrite'` to replace entries with the same id, not just skip them |
57
+ | Skill sources | Hooks up `~/.agents` / `~/.codex` / `~/.claude` (three directories official DSH does not load) plus **any custom skill directory** you add (read-only, overlapping paths rejected) |
58
+ | Skill operations | Create skills, import ZIP / folders, plugin recycle bin (restore / permanent delete with OS-trash fallback), open the source file in the system editor. **Deletion is project-level only**: DSH skills and imported skills (`~/.dsh/skills/`, `~/.dsh/tool-management/skills/`) cannot be deleted — disable them instead |
59
+ | Skill source names | `DSH skills` = the official `~/.dsh/skills/`; **`Imported skills`** = where this plugin puts what you create/import, `~/.dsh/tool-management/skills/` (higher priority, so a same-named copy shadows the official one) |
60
+ | Live refresh | Skill directories are watched from a background thread — edits made in an editor show up automatically |
61
+ | AGENTS.md presets | Multiple global instruction baselines as presets — create / import / edit / apply / delete; "Apply" writes `~/.dsh/AGENTS.md` (new sessions pick it up, current sessions stay unchanged) |
62
+ | Archived session management | History page groups archived sessions by project: search, select-all, batch restore / permanent delete, retention-based auto-cleanup (changing the retention resets the countdown from the change time) |
63
+ | Transcript import / export | Seamlessly take over conversations from Claude Code / Cursor (JSONL), Codex (Markdown), or any text; export picks the session scope, defaults to the desktop, in Markdown / JSONL |
64
+ | Scene memory auto-injected | A memory is `~/.dsh/tool-management/memories/<scene>/<name>.md`; every `.md` inside an enabled scene has its body **injected into the system prompt automatically** (per-agent `systemPrompt` section), with no tool call from the model and effect on the **very next request**; the reserved scene **`global`** ("Global" in the UI) is injected into every conversation. |
65
+ | Memory import | "Import memory" on the Memory page: `.md` / `.zip` (multi-select, drag-and-drop); inside a zip a directory name is the scene, and a bare `.md` lands in the scene picked in the dialog (leave it empty = the reserved scene "Global"); `<scene>/<name>/SKILL.md` inside a zip is imported as a **bundle** (sibling files become attachments); same names are skipped and listed, **including scenes created just for this import** |
66
+ | Scene enable switch | A scene is an **explicit record** (with a description and order); the multi-select switch persists globally in `rules-index.json`'s `active`; **all scenes enabled by default**, `global` and `_shared/` always on |
67
+ | Scene profile (four free-form sections) | Each scene can select its own **MCP tool set / skill set / subagent bindings / memories** in any combination (the lists show only what exists right now; checked = enabled, unchecked = disabled; MCP has two levels: not checking a server disables it entirely, checking a server but none of its tools stops that whole server). **The memory section only affects injection** (an unchecked memory stays out of the prompt while the file is left exactly as it is). A scene with a tool or skill section also gets "Set as active mode": applying the profile persists a snapshot first, and exiting restores it **verbatim**; a scene with only memories or only subagents shows no mode button; the change takes effect on the next request |
68
+ | Lightweight subagents | `~/.dsh/tool-management/agents/<persona>.md` — one file per persona (optional frontmatter: `description` / `provider` + `model` / `tools` allowlist / `toolsDeny` denylist; the body is the persona prompt and is derived automatically when missing); the page header's "Import" takes `.md` / `.zip` (same names skipped and listed); the model calls them through `subagent_list` / `subagent_run` — the child runs with the persona, returns only its result, and is discarded (it never enters History); it **inherits the memories of the currently enabled scenes automatically**; a scene profile can bind "which personas are available in this scene" (calls outside the binding are refused); running asks for confirmation by default, which can be turned off in settings |
69
+ | Prefix-cache friendly | Section text depends only on enabled scenes + file contents, so it is byte-stable; switching scenes or editing a memory changes it exactly once, every other request keeps hitting the cache (this does not violate the "no injection layer" rule — that one only bans per-turn dynamic content) |
70
+ | Model tools | **14**: `skill_mcp_manager_*` for MCP (4), `skill_manager_*` for skills (3), `agentsmd_list` / `agentsmd_apply` for the AGENTS.md preset library (2 — the model may only list and switch, never create or delete, so it cannot wipe your presets), `rule_manager_*` for scene memories (3; creating asks for your consent, can be turned off in settings), `subagent_list` / `subagent_run` for persona subagents (2; running asks for your consent by default, turn off with `requireConfirmForModelSubagentRun`). **All three confirm gates respect the session approval policy**: under `approval=never` (full access) no card can appear, so the plugin treats it as "the user has pre-approved" and passes through, logging `confirm-bypass` — matching the official subagent tools' behaviour under full access |
71
+ | UI | Its own `dsm-*` design system, **seven columns** (Scenes / MCP / Skills / Subagents / Prompts / Memory / Sessions) with a uniform page header and shared section cards; every checkbox-style surface (the four profile sections, the persona tool allow/deny lists) uses one layout, and long lists all have a filter box; a persona's model and tool limits live in an "Advanced options" fold-out (auto-expanded once configured); notices come in two levels (success = toast, warning/error = in-page banner); the profile dialog has a fixed height so adding or removing sections never makes it jump |
72
+
73
+ ## Getting started
74
+
75
+ Prerequisites: DSH installed (`dsh web` runs), Node.js ≥ 18.
76
+
77
+ ```sh
78
+ # Install (package + auto-mount)
79
+ dsh plugin --profile web add dsh-plugin-tool-management@latest
80
+
81
+ # Update: run the same command again
82
+ # Uninstall:
83
+ dsh plugin --profile web remove dsh-plugin-tool-management
84
+ ```
85
+
86
+ Hard-refresh the browser (Cmd/Ctrl+Shift-R) after installing — a **Tools** panel appears in Settings with seven tabs (Scenes / MCP / Skills / Subagents / Prompts / Memory / Sessions), which means the install worked (client changes are hot-loaded by DSH, no restart needed).
87
+
88
+ You can also tell any DSH session:
89
+
90
+ ```text
91
+ Install the dsh-plugin-tool-management plugin:
92
+ dsh plugin --profile web add dsh-plugin-tool-management@latest
93
+ Then remind me to hard-refresh the browser.
94
+ ```
95
+
96
+ ## Feature guide
97
+
98
+ ### Managing MCP servers
99
+
100
+ - **Add a server**: fill in `serverName` (unique, 1–32 chars `[A-Za-z0-9_-]`), the transport and its fields (`streamable-http` → URL / headers; `stdio` → command / args / env), and choose project or global level. The write lands as a loader row in `cordis.patch.yml` and applies via HMR.
101
+ - **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.
102
+ - **Turn off just one tool**: the "Details" dialog lists every tool with its parameter summary — disable the ones the model keeps misusing; the schema disappears from the model's view and calls are denied, ready to re-enable anytime.
103
+ - **Inspect secrets safely**: secret-looking values render as `••••••` by default; click "Reveal" only when you need them.
104
+ - **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.
105
+
106
+ ### Managing skills
107
+
108
+ - **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.
109
+ - **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.
110
+ - **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 too (provider candidates). Not a single byte is touched on disk, and it can be restored at any time. The reserved `dsh` (official DSH skills) and `hub` (the plugin's own import target) sources, and project-level sources, cannot be removed and show no button.
111
+ - **Custom directories**: click "Add directory", enter an absolute path, and that directory becomes a read-only skill source — ideal for skill collections living in repos or synced folders; overlapping paths are rejected to keep the shadow policy sound.
112
+ - **Create / import / recycle**: create from a form; drag in a ZIP, a `.md` file or a skill folder; deleted skills go to the plugin recycle bin first, and permanent delete still tries the OS trash as a last safety net.
113
+
114
+ ### Managing AGENTS.md presets
115
+
116
+ - **Preset library**: create, import and edit multiple global instruction baselines (e.g. different teams' coding standards or role behaviors).
117
+ - **Apply = write**: "Apply" writes the selected preset to `~/.dsh/AGENTS.md` — **new sessions pick it up, current sessions stay unchanged**; "Re-apply" syncs the latest content after editing; switch to another preset before deleting.
118
+
119
+ ### Managing archived sessions
120
+
121
+ - **Grouped by project**: archived sessions are grouped by workspace automatically; search by title / session ID / project path; sessions whose workspace folder no longer exists are flagged with ⚠.
122
+ - **Batch operations**: "Select all" then batch-restore or permanently delete; restored sessions return to the workspace list, and deletion cascades to their subagent sessions.
123
+ - **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.
124
+ - **Import conversations**: take over sessions from other tools — Claude Code / Cursor JSONL, Codex Markdown, and arbitrary text — and keep chatting right after import.
125
+ - **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.
126
+
127
+ ### Managing scene memory (the Scene Memory page)
128
+
129
+ > This page merges the former "Rules" and "Scenes" pages: **a scene is the grouping dimension, a memory (`.md`) is the content.**
130
+ > The data folder also moved to **`~/.dsh/tool-management/memories/`** — existing files are moved in automatically (see "Upgrade note" below).
131
+
132
+ - **A scene is an explicit record** (name + description, stored in the `scenes` slice of `rules-index.json`); `memories/<scene>/` holds its memories: `~/.dsh/tool-management/memories/办公/流程.md` is one memory in the "办公" scene. 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.
133
+ - **New scene**: "New scene" asks for a name and a one-line description (or just `mkdir` under `memories/` — a record is filled in on the next read). **An empty scene is perfectly valid**, so you can create scenes first and add memories later; the card also has "Edit" for the description.
134
+ - **Every `.md` is one memory**: a sentence or a paragraph, no frontmatter needed, and the whole body is injected. Drop a file into the scene folder and it takes effect, or use "New memory" on the card to write it on the page — **file names can be Chinese** (e.g. `站会流程.md`). A memory whose scene does not exist is **rejected outright** (`scene not found`) instead of silently creating one.
135
+ - **Toggle a scene**: the switch on the right of each scene card enables/disables it (same component and layout as the Skills page). Every `.md` inside an enabled scene is **injected into the system prompt automatically**; the model needs no tool call and you never have to explain again. Toggling takes effect on the **very next request**, with no new session and no plugin reload.
136
+ - **All scenes are enabled by default**: with no configuration at all, every scene is live ("drop it in and it works"); narrow the set in the UI once you have many scenes. `global` ("Global") and `_shared/` are always on (their cards have no switch).
137
+ - **One memory = one Markdown file**: `<scene>/<name>.md` (flat) or `<scene>/<name>/SKILL.md` (bundle, with attachments). When creating, fill in the scene (pick an existing one or **type a new scene name** — its folder is created for you), the name (= file name), description and body — frontmatter is entirely optional and derived automatically when missing.
138
+ - **Bundle attachments**: with the bundle form you can **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 you can remove them one by one while editing. The flat form is a single file, so it has nowhere to put attachments.
139
+ - **Toggle & recycle**: enable/disable each memory (the switch on the right of every row — a disabled memory stays on disk and is simply left out of the prompt), edit, and move to trash; the "Trash" button in the page header can **restore** or **permanently delete** removed memories, with a confirmation step before the permanent delete. `enabled` and friends live in the sidecar index and are never written back to your files.
140
+ - **Injection budget is visible**: a budget bar (used / max bytes) sits under the summary and turns red with an "Over budget" label. 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 instead of losing it silently.
141
+ - **`~/.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.
142
+ - **Scene profile (four free-form sections)**: the "Profile" button opens an editor where **MCP tools**, **skills**, **subagent bindings** and **memories** are added/removed independently. For memories the editor lists each scene as a card (description + how many of its memories are checked) and "Pick memories" drills into that scene; check semantics are the same as the other sections (**unchecked = not injected for that scene; files and content are never touched**). Sections with a defined-but-empty selection disable that whole domain. Every section body has a filter box, and the dialog keeps a fixed height so adding/removing sections never makes it jump. A scene with MCP/skill sections also gets a "Set as active mode" button: entering takes a runtime snapshot, persists it first, applies the selections and narrows memory injection to that scene; exiting restores the snapshot **verbatim**. Failures roll back and are reported honestly (an incomplete rollback is written into the error text rather than claimed as "rolled back").
143
+ - **Subagents (personas)**: `~/.dsh/tool-management/agents/<persona>.md`, one file per persona — frontmatter is optional (`description` for when to call it, **one sentence is enough**; `provider` + `model` for the model route (**a pair**: switching providers requires both, e.g. `provider: sensenova` + `model: sensenova-6.8-flash-lite`; a bare `model` resolves against the main session's provider); `tools` allowlist; `toolsDeny` denylist), and the body is the persona prompt. On the page all of this sits in an **Advanced options** fold-out (auto-expanded when the persona already uses a model or tool restriction): the model is a **dropdown** (the `provider · model` pairs from the host LLM catalogue, with a "Custom" entry to type one it does not list), and the tool allow/deny lists are **pickers** whose candidates are the **union of tool names across all agent presets**, tagged "available in this session" vs "available in other presets" — 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).
144
+
145
+ #### Caching and refresh (§5.2)
146
+
147
+ | Situation | Is the prefix stable? | Result |
148
+ |---|---|---|
149
+ | Scene set unchanged, memory files unchanged | byte-for-byte stable | ✅ prompt prefix cache hits |
150
+ | Enabling/disabling a scene (explicit action) | changes once | ⚠️ that session re-warms once — acceptable |
151
+ | Editing a memory (page or editor) | changes once | ⚠️ same, and it takes effect on the **next request** |
152
+ | Timestamps / counts / relative time in the section | changes every request | ❌ forbidden (and absent from the implementation) |
153
+
154
+ The implementation uses a **two-phase scan with a fingerprint cache**: each assembly only walks
155
+ directories with `stat` to build a fingerprint (no body reads) and reuses the previous rendering
156
+ when it is unchanged; only a changed fingerprint (scene toggle, file edit, enable/disable) triggers
157
+ reading bodies and re-rendering. **`fs.watch` is deliberately not used** — recursive watching is
158
+ unreliable on Windows, and a silently dead watcher would return stale content forever; the
159
+ fingerprint probe costs sub-milliseconds and buys "always fresh, never silently stale".
160
+
161
+ #### Upgrade note: the data folder moved (v0.4)
162
+
163
+ Since v0.4 **all plugin data lives under one directory**, `~/.dsh/tool-management/`
164
+ (easier to inspect and back up):
165
+
166
+ ```
167
+ ~/.dsh/tool-management/
168
+ ├─ memories/<scene>/<name>.md | <scene>/<name>/SKILL.md memory bodies (source of truth)
169
+ ├─ agents/<persona>.md subagent personas
170
+ ├─ agents-md/<preset id>/AGENTS.md AGENTS.md preset library
171
+ ├─ skills/ skills created/imported by the plugin
172
+ ├─ trash/ skill trash; rules-trash/ = memory trash
173
+ ├─ rules-index.json enable/order/scene records/profiles/mode
174
+ └─ state.json skill enable policy and custom roots
175
+ ```
176
+
177
+ **Old locations are moved in automatically on first start** (move only, never delete, never
178
+ overwrite an existing target, once per process, failures do not block startup):
179
+
180
+ | Old location | New location |
181
+ |---|---|
182
+ | `~/.dsh/scene-memory/<scene>/…` | `~/.dsh/tool-management/memories/<scene>/…` |
183
+ | `~/.dsh/scene-memory/<root>.md` (the old global memory) | `~/.dsh/tool-management/memories/global/<root>.md` |
184
+ | `~/.dsh/rules/…` (pre-v0.3) | as the two rows above |
185
+ | `~/.dsh/subagents/<persona>.md` | `~/.dsh/tool-management/agents/<persona>.md` |
186
+ | plugin dir `data/agents-md-presets/` | `~/.dsh/tool-management/agents-md/` |
187
+
188
+ The move uses `rename` (instant on one volume) and leaves the source folder as an empty shell you can
189
+ delete once you are satisfied. `~/.dsh/skills/` (the official DSH skill directory) is **not** moved: it
190
+ stays listed as a switchable source, while skills **created or imported by the plugin** now land in
191
+ `tool-management/skills/` (the hub copy wins when both define the same name).
192
+
193
+ ### Let the model and scripts help
194
+
195
+ | Entry point | What it does |
196
+ |---|---|
197
+ | `skill_mcp_manager_list / set_enabled / restart / add` | Let the model query and operate MCP servers |
198
+ | `skill_manager_list / set_enabled / create` | Let the model query and operate skills (creating asks for your consent) |
199
+ | `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 |
200
+ | `rule_manager_list / read / write` | Let the model query and write scene memories (writes ask for your consent; can be disabled in settings) |
201
+ | `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) |
202
+ | `POST /dsh-plugin-tool-management/api` | HTTP API for scripts (`{op, args}` protocol) |
203
+
204
+ > v0.4 **no longer registers slash commands** (there used to be `/mcp`, `/skills`, `/agents-md`,
205
+ > `/scene-memory`): they could only print a text snapshot, could not operate anything, and drifted from
206
+ > the panel state. Every one of them has an equivalent entry in the settings panel.
207
+
208
+ ## Configuration & security
209
+
210
+ Optional fields on the plugin loader row (`dsh plugin add` inserts it automatically):
211
+
212
+ | Field | Description |
213
+ |---|---|
214
+ | `token` | Optional access token. When set, **every write operation and "Reveal"** requires the `x-dsh-token` header. 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. |
215
+ | `maxBodyBytes` | Request body cap, default 88 MiB (skill ZIP uploads need it). |
216
+
217
+ Why a token: the cross-site protection (POST-only + custom header + same-origin check) assumes DSH listens on localhost only. If you forward the port to a LAN or the public internet, the token is the last line of defense against strangers injecting MCP commands (equivalent to remote code execution) and reading plaintext secrets — not needed for local single-user setups.
218
+
219
+ ## Where data lives
220
+
221
+ | Content | Location |
222
+ |---|---|
223
+ | MCP server definitions | `profiles/<profile>/cordis.patch.yml` (project) or `~/.dsh/cordis.patch.yml` (global), auto-`.bak` before every rewrite |
224
+ | Server notes / page settings / disabled tools / export | Sidecar JSON files under the DSH home (`dsh-plugin-tool-management-*.json`) |
225
+ | Skill toggle policy / custom directories | `~/.dsh/tool-management/state.json` |
226
+ | Skill recycle bin / import staging | `~/.dsh/tool-management/trash`, `uploads` |
227
+ | Skills created/imported by the plugin | `~/.dsh/tool-management/skills/<skill>/` (the official `~/.dsh/skills/` stays listed as a source, read-only) |
228
+ | AGENTS.md presets / applied file | `~/.dsh/tool-management/agents-md/<preset id>/AGENTS.md`; "Apply" writes `~/.dsh/AGENTS.md` |
229
+ | Archived session ledger / retention | Plugin dir `data/history-archived-at.json`, `data/history-retention.json` |
230
+ | 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 checkup reports `noScene`) |
231
+ | 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) |
232
+ | Persona files (source of truth) | `~/.dsh/tool-management/agents/<persona>.md` (frontmatter optional, body = persona prompt) |
233
+ | Page settings / confirm switches | `~/.dsh/dsh-plugin-tool-management-settings.json` (`requireConfirmForModelSubagentRun` etc.) |
234
+ | Memory recycle bin | `~/.dsh/tool-management/rules-trash/<trashId>/` (deleted memories land here and can be restored) |
235
+ | Runtime log | `~/.dsh/dsh-plugin-tool-management.log` (rolling) |
236
+
237
+ ## FAQ
238
+
239
+ | Symptom | Fix |
240
+ |---|---|
241
+ | Pages missing in Settings after install | Hard refresh; if that fails, restart DSH once. |
242
+ | Duplicate MCP tabs / duplicated tools | Stale loader row double-mounting the plugin — remove the old entry from `cordis.patch.yml` and restart. |
243
+ | Broken config, DSH won't boot | Restore the newest `cordis.patch.yml.bak-<timestamp>` next to it. |
244
+ | Page data not refreshing | Wait for the automatic polling (default 5s) or click "Refresh". |
245
+ | Latest version not found on a mirror | Add `--registry=https://registry.npmjs.org` and retry later. |
246
+ | 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. |
247
+ | `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. |
248
+ | 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; `subagent_fork` likewise ran card-free). This plugin's governance covers `subagent_run` only — tightening the official pair would take a host-side convention or a later version that brings them into the plugin's pre-execute gate. |
249
+
250
+ ## Development
251
+
252
+ ```bash
253
+ npm install
254
+ npm run build # build (tsc + sync client bundle)
255
+ npm run build:client # sync src/client.js → lib/client.js only
256
+ npm run lint # syntax self-check (node --check on both artifacts)
257
+ npm test # build + all semantic-contract tests (node --test test/*.test.mjs)
258
+ ```
259
+
260
+ > Changes are verified by **actually exercising the real behaviour** (evidence and known issues live in
261
+ > [Changelog](docs/Changelog.md)) instead of asserting what the code currently does — the latter
262
+ > just copies the implementation and passes by construction. The exception is ten groups of
263
+ > **semantic-contract** tests (`npm test`, run against the built `lib/`, 76 cases):
264
+ > `archive.test.mjs` (engine state machine), `import.test.mjs` (ZIP expansion, landing plans, limit
265
+ > reporting), `approval-policy.test.mjs` (never-policy detection, driving a real cordis context and
266
+ > a real `ApprovalService`), `subagent-scene.test.mjs` (scene binding must reject *before* a
267
+ > subagent runs), `subagent-persona.test.mjs` (persona frontmatter round-trip: `provider`,
268
+ > `model` and `toolsDeny` survive a UI save; creating a persona with no directory present),
269
+ > `hub-layout.test.mjs` (unified data directory: legacy layouts move without overwriting, the
270
+ > reserved `global` scene always exists and cannot be deleted, a memory must belong to an existing
271
+ > scene, and the profile memory section only affects projection), `client-exports.test.mjs`
272
+ > (client export contract: evaluating the factory alone — without running `apply` — must already
273
+ > expose `dict`/`pages`; exports written inside the `apply` method body are rejected), and
274
+ > `client-render.test.mjs` (assembly and rendering: a fake ctx drives the whole `apply`, asserts
275
+ > `settings.section` is registered, then renders the entire component tree without throwing). They
276
+ > assert contracts, not
277
+ > implementation copies; real-behaviour
278
+ > acceptance still happens
279
+ > in the browser/host and these tests do not replace it.
280
+ > `npm run check:i18n` additionally checks the zh/en dictionaries for key-set and placeholder
281
+ > drift, and `node scripts/i18n-debt.mjs` reports how much hard-coded Chinese is left (113 lines
282
+ > today: 38 on the prompts page, 75 on the sessions page).
283
+
284
+ Layout: host half `src/index.ts` (object-form Cordis plugin, `lib/index.js` is the shipped artifact); 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` / `projcache.js` / `tombstone.js`); transcript import parsing `src/imports/parsers.js`; scene-memory store `src/rules/` (`service.ts` discovery/CRUD/index/checkup/two-phase section render, `provider.ts` per-agent `systemPrompt` section registration; the module path and `rules-*` op names stay as internal protocol, while the user-visible page and folder became “Scene Memory” / `memories/`); browser half `src/client.js` (ModuleLoader CJS bundle, `dsm-*` design system, talks to the host through the same-origin API). The only runtime dependency is `fflate` (ZIP extraction).
285
+
286
+ Publish: `npm version patch && npm publish` (`prepublishOnly` builds automatically).
287
+
288
+ ## License
289
+
290
+ MIT