dsh-plugin-tool-management 0.5.1 → 0.7.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +185 -238
  3. package/README_EN.md +185 -304
  4. package/cordis.patch.yml +9 -55
  5. package/docs/images/1/345/234/272/346/231/257.png +0 -0
  6. package/docs/images/1/345/234/272/346/231/257_en.png +0 -0
  7. package/docs/images/2MCP.png +0 -0
  8. package/docs/images/2MCP_en.png +0 -0
  9. package/docs/images/3/346/212/200/350/203/275.png +0 -0
  10. package/docs/images/3/346/212/200/350/203/275_en.png +0 -0
  11. package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223.png +0 -0
  12. package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223_en.png +0 -0
  13. package/docs/images/5/346/217/220/347/244/272/350/257/215.png +0 -0
  14. package/docs/images/5/346/217/220/347/244/272/350/257/215_en.png +0 -0
  15. package/docs/images/6/350/256/260/345/277/206.png +0 -0
  16. package/docs/images/6/350/256/260/345/277/206_en.png +0 -0
  17. package/docs/images/7/344/274/232/350/257/235.png +0 -0
  18. package/docs/images/7/344/274/232/350/257/235_en.png +0 -0
  19. package/docs/images/8/345/205/274/345/256/271.png +0 -0
  20. package/docs/images/8/345/205/274/345/256/271_en.png +0 -0
  21. package/docs/update.md +55 -0
  22. package/lib/agents-md/preset-id.js +49 -0
  23. package/lib/agents-md/service.js +180 -57
  24. package/lib/client.js +4710 -3560
  25. package/lib/compat/preset-reach.js +425 -0
  26. package/lib/compat/probe.js +665 -0
  27. package/lib/history/bridge.js +293 -0
  28. package/lib/history/workspace.js +498 -52
  29. package/lib/http-fence.js +94 -0
  30. package/lib/hub.js +160 -2
  31. package/lib/index.js +759 -157
  32. package/lib/mcp/override-blocks.js +195 -0
  33. package/lib/rules/provider.js +3 -3
  34. package/lib/rules/service.js +342 -31
  35. package/lib/scene-prompt-sync.js +112 -0
  36. package/lib/skills/core.js +123 -35
  37. package/lib/skills/service.js +9 -1
  38. package/lib/subagents/service.js +197 -23
  39. package/lib/subagents/tools.js +8 -2
  40. package/package.json +6 -3
  41. package/screenshots.json +10 -9
  42. package/docs/Changelog.md +0 -517
  43. package/docs/images/MCP.png +0 -0
  44. package/docs/images//344/274/232/350/257/235.png +0 -0
  45. package/docs/images//345/234/272/346/231/257.png +0 -0
  46. package/docs/images//345/255/220/346/231/272/350/203/275/344/275/223.png +0 -0
  47. package/docs/images//346/212/200/350/203/275.png +0 -0
  48. package/docs/images//346/217/220/347/244/272/350/257/215.png +0 -0
  49. package/docs/images//350/256/260/345/277/206.png +0 -0
package/README_EN.md CHANGED
@@ -1,304 +1,185 @@
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 at the top 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 — **the Scenes page only lists switchable preset scenes**, so the reserved `global` scene does not appear there (the legacy `_shared/` name shows as an "always on" card with no switch if it exists).
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
- - **Scenes page layout**: scenes are laid out as 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" only when a profile exists; on the right "Profile / Edit / Delete scene" — reserved scenes have neither the switch nor delete). Descriptions are capped at **60 characters** (enforced in the create/edit field, clipped with an ellipsis on the card with the full text in the tooltip) because cards carry **no counters at all**: what is currently 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). **The reserved `global` scene does not appear on this page**: it is an always-injected baseline rather than a switchable preset, and MCP servers, skills and personas each have their own page; global memories are managed on the Memories page. With no dedicated scene at all the page shows an empty state — you only need to create a scene when you want to switch a whole set of MCP servers / skills / personas at once.
143
- - **Scene profile (four free-form sections)**: the "Profile" button 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** (the picker only lists what is currently discovered; checked = enabled, unchecked = disabled), **subagent bindings** (check personas; nothing checked = no restriction) and **memories** are added/removed independently. For memories the editor lists **only the memories of the scene being edited** as a flat, filterable checkbox list (a scene's memory section can only gate its own memories — global memories are always injected and another scene's choices would have no effect, so neither appears here); check semantics are the same as the other sections (**unchecked = not injected for that scene; files and content are never touched**). **A newly added section starts with nothing checked**: the memory section pre-checks only the **enabled** memories of that same scene, while "Add" on the other three gives 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 every memory of that scene. Every section body has a filter box, and the dialog keeps a fixed height so adding/removing sections never makes it jump; memory descriptions are clipped at 80 characters with the full text in the tooltip. A scene with MCP/skill sections also gets an "Enter this 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").
144
- - **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).
145
-
146
- #### Caching and refresh (§5.2)
147
-
148
- | Situation | Is the prefix stable? | Result |
149
- |---|---|---|
150
- | Scene set unchanged, memory files unchanged | byte-for-byte stable | ✅ prompt prefix cache hits |
151
- | Enabling/disabling a scene (explicit action) | changes once | ⚠️ that session re-warms once — acceptable |
152
- | Editing a memory (page or editor) | changes once | ⚠️ same, and it takes effect on the **next request** |
153
- | Timestamps / counts / relative time in the section | changes every request | ❌ forbidden (and absent from the implementation) |
154
-
155
- The implementation uses a **two-phase scan with a fingerprint cache**: each assembly only walks
156
- directories with `stat` to build a fingerprint (no body reads) and reuses the previous rendering
157
- when it is unchanged; only a changed fingerprint (scene toggle, file edit, enable/disable) triggers
158
- reading bodies and re-rendering. **`fs.watch` is deliberately not used** — recursive watching is
159
- unreliable on Windows, and a silently dead watcher would return stale content forever; the
160
- fingerprint probe costs sub-milliseconds and buys "always fresh, never silently stale".
161
-
162
- #### Upgrade note: the data folder moved (v0.4)
163
-
164
- Since v0.4 **all plugin data lives under one directory**, `~/.dsh/tool-management/`
165
- (easier to inspect and back up):
166
-
167
- ```
168
- ~/.dsh/tool-management/
169
- ├─ memories/<scene>/<name>.md | <scene>/<name>/SKILL.md memory bodies (source of truth)
170
- ├─ agents/<persona>.md subagent personas
171
- ├─ agents-md/<preset id>/AGENTS.md AGENTS.md preset library
172
- ├─ skills/ skills created/imported by the plugin
173
- ├─ trash/ skill trash; rules-trash/ = memory trash
174
- ├─ rules-index.json enable/order/scene records/profiles/mode
175
- └─ state.json skill enable policy and custom roots
176
- ```
177
-
178
- **Old locations are moved in automatically on first start** (move only, never delete, never
179
- overwrite an existing target, once per process, failures do not block startup):
180
-
181
- | Old location | New location |
182
- |---|---|
183
- | `~/.dsh/scene-memory/<scene>/…` | `~/.dsh/tool-management/memories/<scene>/…` |
184
- | `~/.dsh/scene-memory/<root>.md` (the old global memory) | `~/.dsh/tool-management/memories/global/<root>.md` |
185
- | `~/.dsh/rules/…` (pre-v0.3) | as the two rows above |
186
- | `~/.dsh/subagents/<persona>.md` | `~/.dsh/tool-management/agents/<persona>.md` |
187
- | plugin dir `data/agents-md-presets/` | `~/.dsh/tool-management/agents-md/` |
188
-
189
- The move uses `rename` (instant on one volume) and leaves the source folder as an empty shell you can
190
- delete once you are satisfied. `~/.dsh/skills/` (the official DSH skill directory) is **not** moved: it
191
- stays listed as a switchable source, while skills **created or imported by the plugin** now land in
192
- `tool-management/skills/` (the hub copy wins when both define the same name).
193
-
194
- ### Let the model and scripts help
195
-
196
- | Entry point | What it does |
197
- |---|---|
198
- | `skill_mcp_manager_list / set_enabled / restart / add` | Let the model query and operate MCP servers |
199
- | `skill_manager_list / set_enabled / create` | Let the model query and operate skills (creating asks for your consent) |
200
- | `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 |
201
- | `rule_manager_list / read / write` | Let the model query and write scene memories (writes ask for your consent; can be disabled in settings) |
202
- | `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) |
203
- | `POST /dsh-plugin-tool-management/api` | HTTP API for scripts (`{op, args}` protocol) |
204
-
205
- > v0.4 **no longer registers slash commands** (there used to be `/mcp`, `/skills`, `/agents-md`,
206
- > `/scene-memory`): they could only print a text snapshot, could not operate anything, and drifted from
207
- > the panel state. Every one of them has an equivalent entry in the settings panel.
208
-
209
- ## Configuration & security
210
-
211
- Optional fields on the plugin loader row (`dsh plugin add` inserts it automatically):
212
-
213
- | Field | Description |
214
- |---|---|
215
- | `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. |
216
- | `maxBodyBytes` | Request body cap, default 88 MiB (skill ZIP uploads need it). |
217
-
218
- 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.
219
-
220
- ## Where data lives
221
-
222
- | Content | Location |
223
- |---|---|
224
- | MCP server definitions | `profiles/<profile>/cordis.patch.yml` (project) or `~/.dsh/cordis.patch.yml` (global), auto-`.bak` before every rewrite |
225
- | Server notes / page settings / disabled tools / export | Sidecar JSON files under the DSH home (`dsh-plugin-tool-management-*.json`) |
226
- | Skill toggle policy / custom directories | `~/.dsh/tool-management/state.json` |
227
- | Skill recycle bin / import staging | `~/.dsh/tool-management/trash`, `uploads` |
228
- | Skills created/imported by the plugin | `~/.dsh/tool-management/skills/<skill>/` (the official `~/.dsh/skills/` stays listed as a source, read-only) |
229
- | AGENTS.md presets / applied file | `~/.dsh/tool-management/agents-md/<preset id>/AGENTS.md`; "Apply" writes `~/.dsh/AGENTS.md` |
230
- | Archived session ledger / retention | Plugin dir `data/history-archived-at.json`, `data/history-retention.json` |
231
- | 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`) |
232
- | 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) |
233
- | Persona files (source of truth) | `~/.dsh/tool-management/agents/<persona>.md` (frontmatter optional, body = persona prompt) |
234
- | Page settings / confirm switches | `~/.dsh/dsh-plugin-tool-management-settings.json` (`requireConfirmForModelSubagentRun` etc.) |
235
- | Memory recycle bin | `~/.dsh/tool-management/rules-trash/<trashId>/` (deleted memories land here and can be restored) |
236
- | Runtime log | `~/.dsh/dsh-plugin-tool-management.log` (rolling) |
237
-
238
- ## FAQ
239
-
240
- | Symptom | Fix |
241
- |---|---|
242
- | Pages missing in Settings after install | Hard refresh; if that fails, restart DSH once. |
243
- | Duplicate MCP tabs / duplicated tools | Stale loader row double-mounting the plugin — remove the old entry from `cordis.patch.yml` and restart. |
244
- | Broken config, DSH won't boot | Restore the newest `cordis.patch.yml.bak-<timestamp>` next to it. |
245
- | Page data not refreshing | Wait for the automatic polling (default 5s) or click "Refresh". |
246
- | Latest version not found on a mirror | Add `--registry=https://registry.npmjs.org` and retry later. |
247
- | 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. |
248
- | `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. |
249
- | 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. |
250
-
251
- ## Development
252
-
253
- ```bash
254
- npm install
255
- npm run build # build (tsc + sync client bundle)
256
- npm run build:client # sync src/client.js → lib/client.js only
257
- npm run lint # syntax self-check (node --check on both artifacts)
258
- npm run check:i18n # zh/en dictionary key-set + placeholder alignment
259
- npm test # build + i18n check + all semantic-contract tests (node --test test/*.test.mjs, 11 groups / 85 cases)
260
- ```
261
-
262
- > Changes are verified by **actually exercising the real behaviour** (evidence and known issues live in
263
- > [Changelog](docs/Changelog.md)) instead of asserting what the code currently does — the latter
264
- > just copies the implementation and passes by construction. The exception is eleven groups of
265
- > **semantic-contract** tests (`npm test`, run against the built `lib/`, 85 cases):
266
- > `archive.test.mjs` (engine state machine), `import.test.mjs` (ZIP expansion, landing plans, limit
267
- > reporting), `approval-policy.test.mjs` (never-policy detection, driving a real cordis context and
268
- > a real `ApprovalService`), `subagent-scene.test.mjs` (scene binding must reject *before* a
269
- > subagent runs), `subagent-persona.test.mjs` (persona frontmatter round-trip: `provider`,
270
- > `model` and `toolsDeny` survive a UI save; creating a persona with no directory present),
271
- > `hub-layout.test.mjs` (unified data directory: legacy layouts move without overwriting, the
272
- > reserved `global` scene always exists and cannot be deleted, a memory must belong to an existing
273
- > scene, and the profile memory section only affects projection), `skills-delete.test.mjs` (which
274
- > skills may be deleted: user-level sources cannot be, read-only sources stay read-only),
275
- > `skills-state.test.mjs` (state-file read resilience: missing keys self-heal, type errors stay
276
- > fail-closed), `skills-source-remove.test.mjs` (the "remove a source" semantics: a removed source is
277
- > no longer read, drops out of the same-name priority and is invisible to the model, while not a byte
278
- > on disk changes and it can be restored), `client-exports.test.mjs`
279
- > (client export contract: evaluating the factory alone — without running `apply` — must already
280
- > expose `dict`/`pages`; exports written inside the `apply` method body are rejected), and
281
- > `client-render.test.mjs` (assembly and rendering: a fake ctx drives the whole `apply`, asserts
282
- > `settings.section` is registered, then renders the entire component tree without throwing; it also
283
- > pins the **Scenes page contract** — the reserved `global` scene does not appear there (an
284
- > only-global data set renders the empty state), switchable presets still render, and an over-long
285
- > description is always clipped to the cap — plus the **profile default-pick contract**: "Add" on
286
- > MCP/skills/
287
- > subagents yields an empty set, the memory section pre-checks only the enabled `global` memories,
288
- > and "Select all" really selects everything — plus the **mode-bar state contract**: the "Active
289
- > mode" bar exists only while a mode is active and is absent otherwise). They
290
- > assert contracts, not
291
- > implementation copies; real-behaviour
292
- > acceptance still happens
293
- > in the browser/host and these tests do not replace it.
294
- > `npm run check:i18n` additionally checks the zh/en dictionaries for key-set and placeholder
295
- > drift, and `node scripts/i18n-debt.mjs` reports how much hard-coded Chinese is left (113 lines
296
- > today: 38 on the prompts page, 75 on the sessions page).
297
-
298
- 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).
299
-
300
- Publish: `npm version patch && npm publish` (`prepublishOnly` builds automatically).
301
-
302
- ## License
303
-
304
- 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](CHANGELOG.md) · [Release overview](docs/update.md)
10
+
11
+ - An **MCP, skills, scenes, memories, subagents, prompts & archived sessions** manager for DeepSeek Harness.
12
+ - Eight tabs: **Scenes**, **MCP**, **Skills**, **Subagents**, **Prompts**, **Memories**, **Sessions**, **Host**.
13
+
14
+ ```sh
15
+ dsh plugin --profile web add dsh-plugin-tool-management@latest
16
+ ```
17
+
18
+ Hard-refresh the browser (Cmd/Ctrl+Shift-R) afterwards — a **Tools** panel in Settings means it worked. No hand-editing of `cordis.patch.yml`, no skill source files touched, configuration survives restarts and upgrades.
19
+
20
+ ---
21
+
22
+ ## Screenshots
23
+
24
+ | | |
25
+ |:---:|:---:|
26
+ | ![Scenes](docs/images/1场景_en.png) | ![MCP](docs/images/2MCP_en.png) |
27
+ | **Scenes** | **MCP** |
28
+ | ![Skills](docs/images/3技能_en.png) | ![Subagents](docs/images/4子智能体_en.png) |
29
+ | **Skills** | **Subagents** |
30
+ | ![Prompts](docs/images/5提示词_en.png) | ![Memories](docs/images/6记忆_en.png) |
31
+ | **Prompts** | **Memories** |
32
+ | ![Sessions](docs/images/7会话_en.png) | ![Host](docs/images/8兼容_en.png) |
33
+ | **Sessions** | **Host** |
34
+
35
+ ## Highlights
36
+
37
+ | Capability | Description |
38
+ |---|---|
39
+ | Scene memory | `.md` bodies in an enabled scene are **injected into the system prompt**, effective on the next request |
40
+ | Scene profile | Every scene freely combines **MCP tools / skills / subagents / memories**; "Enter mode" narrows injection in one click |
41
+ | Scene prompt | A scene can bind a prompt preset; switching scenes rewrites `~/.dsh/AGENTS.md` (auto-restores on exit) |
42
+ | Per-tool switches | **Individual tools** inside one MCP server can be disabled: invisible to the model, blocked at call time |
43
+ | Restart semantics | Restart only reconnects — it **never flips the enabled state** |
44
+ | Secret safety | Secrets masked by default; "Reveal" & export **require a token** — no `token` configured means no plaintext |
45
+ | Skill sources | Hooks up `~/.agents` / `~/.codex` / `~/.claude` & custom dirs; default sources must be read but skills can be deleted |
46
+ | Recycle bin | Personas / scenes / prompts / memories / skills all go to recycle bin on delete, restorable |
47
+ | AGENTS.md presets | Multiple global baselines, one-click apply, 5-generation backup |
48
+ | Archived sessions | Grouped by project, batch restore / delete, retention cleanup; rebuildable after workspace deletion |
49
+ | Transcript import/export | Take over Claude Code / Cursor / Codex / any text; export Markdown / JSONL |
50
+ | Subagents | One file per persona, run-and-discard, never enters History, inherits scene memories |
51
+ | Prefix-cache friendly | Section text depends only on enabled scenes + file contents, byte-stable |
52
+ | Compatibility check | The **Host** tab shows host capabilities, per-action routing, and degradations at a glance |
53
+ | Model tools | **14** (`skill_mcp_manager_*` / `skill_manager_*` / `agentsmd_*` / `rule_manager_*` / `subagent_*`) |
54
+ | UI | Custom design system, **eight tabs**, bilingual, follows host language |
55
+
56
+ ## Quick start
57
+
58
+ Prerequisites: DSH installed (`dsh web` runs), Node.js ≥ 18.
59
+
60
+ ```sh
61
+ dsh plugin --profile web add dsh-plugin-tool-management@latest # install / update
62
+ dsh plugin --profile web remove dsh-plugin-tool-management # uninstall
63
+ ```
64
+
65
+ Hard-refresh the browser — a **Tools** panel with eight tabs means it worked. Client changes hot-reload; host-side changes need `dsh web` restarted.
66
+
67
+ You can also ask the model:
68
+
69
+ ```text
70
+ Install the dsh-plugin-tool-management plugin:
71
+ dsh plugin --profile web add dsh-plugin-tool-management@latest
72
+ Then remind me to hard-refresh the browser.
73
+ ```
74
+
75
+ The model can manage everything above via 14 tools (see highlights); scripts use `POST /dsh-plugin-tool-management/api` (`{op, args}` protocol).
76
+
77
+ ---
78
+
79
+ ## Features
80
+
81
+ ### Scenes & memories
82
+
83
+ - **A scene = a group, a memory = a `.md` file**. `memories/<scene>/<name>.md`, the whole body is injected, file names can be non-ASCII.
84
+ - **Single-choice toggle**: only one scene at a time (others greyed out); turning all off = only `global` and `_shared` inject. New scenes start off.
85
+ - **Scene-bound prompt**: switching scenes rewrites `~/.dsh/AGENTS.md` (5-gen backup, auto-restore on exit).
86
+ - **Scene profile**: every scene combines MCP tools / skills / subagents / memories (any mix); "Enter mode" applies and narrows in one click, exit restores verbatim.
87
+ - **Import**: `.md` / `.zip` (dir name = scene, bundles carry attachments), same names skipped never overwritten, over-limit items reported.
88
+ - **Injection budget**: default 64 KiB, oversized memories skipped with a list. Deletes go to recycle bin.
89
+
90
+ ### Subagents
91
+
92
+ - **One file per persona**: `agents/<persona>.md`, frontmatter entirely optional.
93
+ - **Tool limits per Agent preset**: each preset gets its own allow/deny list (mutually exclusive), effective at runtime by the current preset — fixes the old "union of all presets" list that broke subagents after a preset switch.
94
+ - **Run and discard**: `subagent_run` runs with the persona, returns only the result, never enters History, inherits scene memories. Scenes can bind which personas are available.
95
+
96
+ ### MCP servers
97
+
98
+ - **CRUD + immediate effect**: writes to `cordis.patch.yml`, HMR picks it up.
99
+ - **Per-tool switches**: disable individual tools (invisible to the model, blocked at call), whole-server batch.
100
+ - **Secret masking**: defaults to `••••••`, "Reveal" needs a token.
101
+ - **Migrate & back up**: cross-project/global migration rolls back on failure; JSON export/import.
102
+
103
+ ### Skills
104
+
105
+ - **Sources at a glance**: project / DSH / Agents / Codex / Claude / custom dirs, grouped by source.
106
+ - **Opposite permissions**: default sources must be read but skills can be deleted; external dirs can be disabled/removed but skills are read-only.
107
+ - **Remove ≠ disable**: remove = directory not scanned at all (files untouched, restorable); disable = still listed but not callable.
108
+ - **Same-name picker / custom dirs / ZIP import / recycle bin**.
109
+
110
+ ### Prompt presets
111
+
112
+ - Multiple `~/.dsh/AGENTS.md` baselines, one-click apply (new sessions only, current unchanged), 5-gen backup.
113
+ - Create with body inline, edit can change id (= dir rename, scene bindings follow). Active preset can't be deleted; deletes go to recycle bin.
114
+
115
+ ### Archived sessions
116
+
117
+ - Grouped by project, search, batch restore / delete, retention auto-cleanup.
118
+ - Workspace registration deleted → group rebuilt from session dirs, one-click re-register.
119
+ - Import Claude Code / Cursor / Codex / any text; export Markdown / JSONL.
120
+
121
+ ### Host compatibility
122
+
123
+ The plugin uses the host's own `@deepseek-ai/*` libraries at runtime — they must be the same physical modules, or every "adapt to host" decision degrades into guesswork.
124
+
125
+ - **Host tab**: host version, usable capability count, per-action routing (native/adapter/unavailable), degradations & reasons. Read-only.
126
+ - **Command line**: `node scripts/doctor.mjs` (check), `node scripts/host-deps.mjs --fix` (align deps).
127
+ - Under `minimal` preset, scene memory / AGENTS.md / skill directory don't take effect (by design); the Host tab marks this per column.
128
+
129
+ ---
130
+
131
+ ## Where data lives
132
+
133
+ | Content | Location |
134
+ |---|---|
135
+ | MCP definitions | `cordis.patch.yml` (auto `.bak` before rewrite) |
136
+ | Skill policy / custom dirs | `~/.dsh/tool-management/state.json` |
137
+ | Skills / memories / personas / presets | `~/.dsh/tool-management/{skills,memories,agents,agents-md}/` |
138
+ | Recycle bin | `~/.dsh/tool-management/trash/` |
139
+ | Archive ledger / retention | `~/.dsh/tool-management/history-*.json` |
140
+ | Memory index / scenes / profiles | `~/.dsh/tool-management/rules-index.json` |
141
+ | Page settings | `~/.dsh/dsh-plugin-tool-management-settings.json` |
142
+ | Runtime log | `~/.dsh/dsh-plugin-tool-management.log` |
143
+
144
+ **No user data is stored inside the plugin's install directory** (`dsh plugin update` replaces it wholesale).
145
+
146
+ ## Configuration & security
147
+
148
+ | Field | Description |
149
+ |---|---|
150
+ | `token` | Access token. When set, **all writes + plaintext secrets** require `x-dsh-token`; **unset = plaintext endpoints closed**. Also the escape hatch for curl / LAN. |
151
+ | `maxBodyBytes` | Request body cap, default 88 MiB. |
152
+
153
+ - **Browser**: reads/writes via cookie, no token needed; but **plaintext secrets** (Reveal / export) need a token.
154
+ - **curl / scripts**: send `x-dsh-token`, or carry the browser cookie.
155
+ - **Port forwarded to public**: configure a token — prevents strangers injecting MCP commands (≈ remote code execution) and stealing secrets.
156
+
157
+ ## FAQ
158
+
159
+ | Symptom | Fix |
160
+ |---|---|
161
+ | Pages missing after install | Hard refresh; restart DSH if that fails. |
162
+ | Duplicate MCP tabs | Remove the stale loader row from `cordis.patch.yml`, restart. |
163
+ | Broken config, DSH won't boot | Restore the newest `.bak-<timestamp>`. |
164
+ | Action stopped after DSH upgrade | Settings → Tools → **Host** for the reason; `doctor.mjs` → `host-deps.mjs --fix`. |
165
+ | Still asked to confirm in `approval=never`? | No card appears — straight through with a log line; switch back to "workspace write" to get asked again. |
166
+ | `subagent_run` reports spawn unavailable | Host has no spawn provider; mount `@deepseek-ai/dsh-subagent-spawn-in-process` and restart. |
167
+ | Scene binds persona A, but official `subagent` ran something else | Two channels: this plugin only governs `subagent_run`; official `subagent` / `subagent_fork` have no gate and don't know about personas. |
168
+
169
+ ---
170
+
171
+ ## Development
172
+
173
+ ```bash
174
+ npm install
175
+ npm run build # tsc + sync client
176
+ npm test # build + i18n + semantic-contract tests
177
+ npm run check:i18n # dictionary self-check
178
+ npm run doctor # host compatibility check
179
+ ```
180
+
181
+ `lib/` is not tracked — run `npm run build` after cloning. Changes need `dsh web` restarted. Runtime dep is only `fflate`; `@deepseek-ai/*` all come from the host.
182
+
183
+ ## License
184
+
185
+ MIT