misakanet 2.39.0 → 2.41.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/.codex-plugin/plugin.json +1 -1
- package/README.ja.md +8 -0
- package/README.md +116 -10
- package/README.zh-CN.md +9 -2
- package/icon.svg +13 -0
- package/index.js +109 -12
- package/lib/client.js +2085 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +37 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "misakanet",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.41.0",
|
|
4
4
|
"description": "Failure-memory network for AI coding agents: search evidence-rated failure lessons, submit new failures, and turn crashes into reusable knowledge.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ikalus1988",
|
package/README.ja.md
CHANGED
|
@@ -111,6 +111,14 @@ jobs:
|
|
|
111
111
|
> **MisakaNetは1つのこと専用に構築されています:** エージェントが既知の障害を繰り返すのを防ぐこと。
|
|
112
112
|
> これは汎用メモリレイヤーでもランタイムでもベクトルデータベースでもありません。
|
|
113
113
|
|
|
114
|
+
### 答えてくれること、答えてくれないこと
|
|
115
|
+
|
|
116
|
+

|
|
117
|
+
|
|
118
|
+
答えてくれるのは**収録済みの障害**であり、一般知識ではありません。見つからない場合は `no_match` と、
|
|
119
|
+
そのまま呼び出せる `intake` の案内が返ります —— 未ヒットはギャップが記録される経路そのものなので、
|
|
120
|
+
これもまた答えです。
|
|
121
|
+
|
|
114
122
|
### v2.16.0の新機能
|
|
115
123
|
|
|
116
124
|
| 機能 | 説明 |
|
package/README.md
CHANGED
|
@@ -63,6 +63,70 @@ mcp-name: io.github.Ikalus1988/misakanet
|
|
|
63
63
|
|
|
64
64
|
---
|
|
65
65
|
|
|
66
|
+
## Install (30 seconds)
|
|
67
|
+
|
|
68
|
+
| Your host | Command |
|
|
69
|
+
|---|---|
|
|
70
|
+
| **DeepSeek Harness** | `dsh plugin --profile web add misakanet` — or type `misakanet` in the host's **Add plugin** dialog |
|
|
71
|
+
| **Claude Code** | `/plugin marketplace add Ikalus1988/MisakaNet` then `/plugin install misakanet@misakanet` |
|
|
72
|
+
| **Codex, Cursor, Gemini CLI, Copilot CLI, OpenCode, …** | `npx @misaka-net/misakanet-setup` — writes the MCP row into each host's own config |
|
|
73
|
+
| **Any other MCP client** | point it at `https://misakanet.org/mcp` — the endpoint is public and reads are anonymous |
|
|
74
|
+
| **Your own code** | `pip install misakanet-core` (library) · `pip install misakanet` (stdio server) |
|
|
75
|
+
|
|
76
|
+
<p align="center">
|
|
77
|
+
<img src="docs/assets/dsh-plugin-add.png" width="760" alt="DeepSeek Harness plugin page: (1) the plugin icon in the sidebar, (2) the 添加插件 (Add plugin) button, (3) the Add-plugin dialog with the package name misakanet typed in"/>
|
|
78
|
+
</p>
|
|
79
|
+
|
|
80
|
+
<p align="center"><em>DeepSeek Harness: sidebar <b>插件</b> (1) → <b>添加插件</b> (2) → type <code>misakanet</code> (3) → <b>安装</b>.<br/>The dialog takes the same package name the CLI command above uses (its own hint: a package name, a GitHub URL, or a local path).</em></p>
|
|
81
|
+
|
|
82
|
+
Updates: `dsh plugin --profile web update misakanet@latest`.
|
|
83
|
+
Update the installer: `npx @misaka-net/misakanet-setup@latest` (its own command, its own flags).
|
|
84
|
+
|
|
85
|
+
No account, no token, no Python needed for the plugin path: the npm bundle mounts the hosted endpoint.
|
|
86
|
+
Declared hosts and what was measured: [compatibility](docs/compatibility.md). Every channel, the
|
|
87
|
+
prerequisites, and the two-package trap that costs people an install: [How to use it](#how-to-use-it).
|
|
88
|
+
|
|
89
|
+
## What the DeepSeek Harness plugin adds
|
|
90
|
+
|
|
91
|
+
Version 2.40.0 ships the browser half, and 2.41.0 adds the rest of it: the surfaces below did not all land in
|
|
92
|
+
the same release, so this section lists them by the version that carries them. It is not a dialog: it puts
|
|
93
|
+
MisakaNet where the session already is.
|
|
94
|
+
|
|
95
|
+
**In 2.40.0 — published.** These six seats are what `npm view misakanet version` gives you today.
|
|
96
|
+
|
|
97
|
+
| Where | What you get |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| **Left column** | A permanent `MisakaNet` entry directly under `Plugins`. It is a shortcut, not the panel: clicking it opens a full page in the main column. Root scope — it does not come and go with a session. |
|
|
100
|
+
| **Conversation tab** | A `MisakaNet` tab beside Chat and Trajectory: what this session asked, what came back, and what you filed, rebuilt from the conversation's own rows. |
|
|
101
|
+
| **Right column** | The same panel as a pane, so it can sit next to the file tree, a terminal, or a document. |
|
|
102
|
+
| **Tool call rows** | Every `misakanet_search` and `misakanet_submit_intake` call gets its own row on the tool card: the query as it was sent, whether a lesson came back, and which lesson is on top, with the raw result one disclosure away. The row reports and never posts — the vote belongs on the answer's action row below. |
|
|
103
|
+
| **Assistant action row** | 👍 / 👎 on the answer that used a lesson. Those two are the only things the page ever sends — counters live in the browser, not on a server. |
|
|
104
|
+
| **Voice** | An off-by-default switch that explains both mechanisms: the cue the server names on the next search, and the local hook a page cannot read. |
|
|
105
|
+
|
|
106
|
+
<p align="center">
|
|
107
|
+
<img src="docs/assets/dsh-client-left-column.png" width="820" alt="DeepSeek Harness: a permanent MisakaNet entry in the left column under Plugins, and the full page it opens"/>
|
|
108
|
+
</p>
|
|
109
|
+
|
|
110
|
+
<p align="center">
|
|
111
|
+
<img src="docs/assets/dsh-client-right-panel.png" width="820" alt="The MisakaNet pane in the right column of a DeepSeek Harness session"/>
|
|
112
|
+
</p>
|
|
113
|
+
|
|
114
|
+
**With 2.41.0 — [release PR #2591](https://github.com/Ikalus1988/MisakaNet/pull/2591).** These six surfaces
|
|
115
|
+
are in `main` and ship in 2.41.0; a 2.40.0 install does not have them yet.
|
|
116
|
+
|
|
117
|
+
| Where | What you get |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| **`/misakanet` in the composer** | Type `/misakanet pip install timeout` and press Enter: the lessons come back in a card inside the composer, with no agent in the loop. The query is the only thing it sends. |
|
|
120
|
+
| **Frame-wide toast** | After a `/misakanet` search comes back with lessons, a card appears over every column with the top hit; dismiss it or click through to the lesson. |
|
|
121
|
+
| **Sidebar foot** | One action beside Settings: copy this session's MisakaNet activity as a summary for an issue or a PR body. |
|
|
122
|
+
| **中文 / English** | Every MisakaNet surface follows the host language: the panel, the `/misakanet` card, the settings row, and the plugin page's own title and description. |
|
|
123
|
+
| **Settings → General** | A MisakaNet preference row: play voice cues in this browser, and how much the surfaces show (compact / full). Both stay in the browser. |
|
|
124
|
+
| **Plugin page** | The MCP row's effective configuration — endpoint, transport, timeout — shown read-only, next to where it is edited (the profile's `cordis.patch.yml`). |
|
|
125
|
+
|
|
126
|
+
Which seats the half occupies and why they are `root` or `session` scope, with the host's own contract text
|
|
127
|
+
quoted: [compatibility](docs/compatibility.md). Running a host of your own and want a check that cannot
|
|
128
|
+
touch your profile: `python3 scripts/install_smoke.py dsh-client --serve`.
|
|
129
|
+
|
|
66
130
|
## What is MisakaNet?
|
|
67
131
|
|
|
68
132
|
**Git-backed failure memory for AI coding agents.** An error shows up → the agent searches the lessons →
|
|
@@ -90,6 +154,13 @@ how much a lesson has been proven.
|
|
|
90
154
|
| ❌ A cloud service requiring signup | ✅ `git clone` → search locally |
|
|
91
155
|
| ❌ A skill marketplace | ✅ Debugging knowledge from real sessions |
|
|
92
156
|
|
|
157
|
+
### What it can and cannot answer
|
|
158
|
+
|
|
159
|
+

|
|
160
|
+
|
|
161
|
+
It answers for **the failures it has indexed**, not general knowledge. A query that finds nothing returns
|
|
162
|
+
`no_match` plus a ready-to-call intake — a miss is how a gap gets recorded, so a miss is an answer too.
|
|
163
|
+
|
|
93
164
|
### Lesson vs Skill
|
|
94
165
|
|
|
95
166
|
A **skill** teaches an agent *how to do something*. A **lesson** records *what went wrong before, and how
|
|
@@ -98,21 +169,53 @@ a general memory layer, not a vector database. → [FAQ](FAQ.md)
|
|
|
98
169
|
|
|
99
170
|
## Benchmark: how much of a lesson does a model reproduce when handed one?
|
|
100
171
|
|
|
101
|
-
Weekly benchmark (Cloudflare Workers AI
|
|
102
|
-
|
|
103
|
-
`with_lesson` arm is *that same lesson*, and the score is `lesson_hit_rate`: **the share of the injected lesson's commands reproduced in the answer**.
|
|
172
|
+
Weekly benchmark (Cloudflare Workers AI). **Read the metric before the numbers** — the scenario in this benchmark is each lesson's own title, the "matching lesson" injected into the `with_lesson` arm is *that same lesson*, and the
|
|
173
|
+
score is `lesson_hit_rate`: **the share of the injected lesson's commands reproduced in the answer**.
|
|
104
174
|
No retrieval is called and correctness is not checked, so this is the **recitation** half of RAG, not
|
|
105
|
-
evidence that search works
|
|
175
|
+
evidence that search works.
|
|
106
176
|
|
|
107
|
-
|
|
177
|
+
Latest aggregated data: [`docs/benchmarks/latest.json`](docs/benchmarks/latest.json) (2026-09-22, two
|
|
178
|
+
independent runs of ≈500 scenarios each):
|
|
179
|
+
|
|
180
|
+
| Condition | Run 1 hit rate | Run 2 hit rate | Avg | n (per run) | Actionable |
|
|
181
|
+
|---|---|---|---|---|---|
|
|
182
|
+
| plain (no lesson) | 0.239 | 0.233 | **23.3%** | ≈510 | 82–83% |
|
|
183
|
+
| with_lesson (pasted) | 0.464 | 0.461 | **46.1%** | ≈512 | 76–77% |
|
|
184
|
+
|
|
185
|
+
**Reproducibility.** Two runs with identical config produce hit rates within 0.3% of each other
|
|
186
|
+
(0.464 vs 0.461 for `with_lesson`; 0.239 vs 0.233 for `plain`), confirming the metric is stable.
|
|
187
|
+
|
|
188
|
+
**Aggregation.** Each run evaluates every lesson in the corpus as a scenario. The `with_lesson` arm pastes the matching lesson
|
|
189
|
+
into the prompt; `plain` uses no lesson. `actionable` is a boolean per scenario indicating whether the
|
|
190
|
+
model produced a usable answer. Actionable rates are stable across runs (76–77% with lesson, 82–83% plain).
|
|
191
|
+
|
|
192
|
+
**Trend** (weekly snapshots):
|
|
193
|
+
|
|
194
|
+
| Date | with_lesson hit rate | plain hit rate | n |
|
|
108
195
|
|---|---|---|---|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
196
|
+
| 2026-08-30 | 46.4% | 23.9% | 358 |
|
|
197
|
+
| 2026-08-31 | 49.1% | 25.1% | 398 |
|
|
198
|
+
| 2026-09-06 | 48.3% | 24.1% | 455 |
|
|
199
|
+
| 2026-09-14 | 46.6% | 23.4% | 494 |
|
|
200
|
+
| 2026-09-21 | 46.1% | 23.3% | 512 |
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
with_lesson hit rate (weekly)
|
|
204
|
+
49.1% │ ▄
|
|
205
|
+
48.3% │ █ ▄
|
|
206
|
+
46.6% │ █ █ ▄
|
|
207
|
+
46.4% │ ▄ █ █ █ ▄
|
|
208
|
+
46.1% │ █ █ █ █ █
|
|
209
|
+
└──────────────────
|
|
210
|
+
08 08 09 09 09
|
|
211
|
+
30 31 06 14 21
|
|
212
|
+
```
|
|
111
213
|
|
|
112
214
|
A model repeats more of a document it was handed, and the weaker the model the bigger the relative
|
|
113
215
|
difference. That is *necessary* for the product to help and it is not sufficient — the claim "search finds the
|
|
114
216
|
right lesson for a failure you described" is measured nowhere yet. Details:
|
|
115
|
-
[
|
|
217
|
+
[`docs/benchmarks/latest.json`](docs/benchmarks/latest.json) · per-run files in
|
|
218
|
+
[`docs/benchmarks/`](docs/benchmarks/) · metric definition: `METRIC_DEFINITION` in
|
|
116
219
|
[`scripts/benchmark_workers_ai.py`](scripts/benchmark_workers_ai.py)
|
|
117
220
|
|
|
118
221
|
→ [Full changelog](CHANGELOG.md) · [Release notes](https://github.com/Ikalus1988/MisakaNet/releases)
|
|
@@ -124,6 +227,7 @@ and where this design loses:
|
|
|
124
227
|
|---|---|---|
|
|
125
228
|
| Hit rate | share of the **injected** lesson's commands reproduced in the answer — a recitation check; the scenario is that lesson's own title and no retrieval happens | it is the ceiling on usefulness, not the measure of it: a corpus can be recitable and still unfindable |
|
|
126
229
|
| Gain (with − without) | how much more of that lesson appears when it is pasted in | separates "the model can use a lesson" from "the model guessed the same words" — it says nothing about finding the lesson |
|
|
230
|
+
| Actionable | whether the model produced a usable answer at all (boolean per scenario) | a high hit rate on an answer that is not actionable is noise; this tracks whether the model engages with the problem |
|
|
127
231
|
| Cost / latency | tokens and wall-clock per answer | the whole premise is cheaper than re-debugging, so it has to stay cheap |
|
|
128
232
|
|
|
129
233
|
**Where it loses on purpose:** BM25 matches words, not meaning. A failure described in vocabulary the
|
|
@@ -160,11 +264,12 @@ Supported agents — and what "supported" means per group (evidence levels in
|
|
|
160
264
|
| MCP by hand | Cursor · Gemini CLI · Windsurf · OpenCode · Copilot · DeepSeek Harness | the endpoint is standard MCP over HTTP; add the URL in that client's own config. Cursor also has a rules-file mode |
|
|
161
265
|
| Anything else that speaks MCP over HTTP | — | the endpoint is public, reads are anonymous and unmetered |
|
|
162
266
|
|
|
163
|
-
Pick one channel — they are independent, and none of them needs an account:
|
|
267
|
+
Pick one channel — they are independent, and none of them needs an account (the Claude Code row needs a Claude Code version with plugin support):
|
|
164
268
|
|
|
165
269
|
| I want… | Command | What it touches |
|
|
166
270
|
|---|---|---|
|
|
167
271
|
| my assistant to search the lessons | `npx @misaka-net/misakanet-setup` | writes the MCP endpoint into each assistant's own config; optionally a rules block and a hook |
|
|
272
|
+
| my **Claude Code** assistant to search the lessons, as a plugin | `/plugin marketplace add Ikalus1988/MisakaNet` then `/plugin install misakanet@misakanet` | adds the hosted MCP tools to Claude Code from this repository — no installer, no local process |
|
|
168
273
|
| to call the endpoint myself | the `curl` below | nothing to install |
|
|
169
274
|
| the library in my own code | `pip install misakanet-core` | nothing |
|
|
170
275
|
|
|
@@ -174,6 +279,7 @@ Pick one channel — they are independent, and none of them needs an account:
|
|
|
174
279
|
|---|---|---|
|
|
175
280
|
| `@misaka-net/misakanet-setup` (npm) | the **installer** — has `bin`, no plugin entry | teaching your assistant to search |
|
|
176
281
|
| `misakanet` (npm) | the **DSH / Codex plugin** (`index.js`, `SKILL.md`) | `dsh plugin --profile web add misakanet` |
|
|
282
|
+
| this repository (git) | also a **Claude Code plugin marketplace** (`.claude-plugin/`) | `/plugin marketplace add Ikalus1988/MisakaNet` — the Claude channel is repo-based on purpose: a marketplace resolves the plugin from the repository, so the npm bundle stays the DSH/Codex artifact |
|
|
177
283
|
| `misakanet` (PyPI) | ships the stdio **MCP server** | `python3 -m misakanet.server` |
|
|
178
284
|
| `misakanet-core` (PyPI) | the **library** (stdlib-only BM25 — Python ≥ 3.10 required, no third-party packages) | `from misakanet.search import search_lessons` |
|
|
179
285
|
|
|
@@ -243,7 +349,7 @@ jobs:
|
|
|
243
349
|
|---|---|
|
|
244
350
|
| 🔴 Debugging a real failure | [Search existing lessons](https://ikalus1988.github.io/MisakaNet/search/) before retrying |
|
|
245
351
|
| 🤖 Building an AI agent / tool | Use lessons as [failure-memory](docs/mcp-quickstart.md) for your workflow |
|
|
246
|
-
| 🧪 Using
|
|
352
|
+
| 🧪 Using DeepSeek Harness | `dsh plugin --profile web add misakanet`, then [what it registers](docs/integration/deepseek-harness.md) — skill + `mcp__misakanet__*` tools, no local Python |
|
|
247
353
|
| 🔧 Contributing a fix | Read [CONTRIBUTING.md](CONTRIBUTING.md) for code style + PR checklist, check [related lessons](https://ikalus1988.github.io/MisakaNet/search/), then open a small PR |
|
|
248
354
|
| 📝 Sharing a failure case | Submit a [5-line failure note](https://github.com/Ikalus1988/MisakaNet/issues/new?template=lesson-feedback.yml) — no polished PR required |
|
|
249
355
|
| 📊 Evaluating agent learning | Run the [benchmarks](scripts/retrieval_noisebench.py) and compare reuse behavior |
|
package/README.zh-CN.md
CHANGED
|
@@ -102,9 +102,9 @@ python3 search_knowledge.py "GitHub token 401"
|
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
104
|
# npm 安装(推荐)
|
|
105
|
-
dsh plugin add misakanet
|
|
105
|
+
dsh plugin --profile web add misakanet
|
|
106
106
|
# 或直接从 git 安装(同一 bundle)
|
|
107
|
-
# dsh plugin add git+https://github.com/Ikalus1988/MisakaNet.git
|
|
107
|
+
# dsh plugin --profile web add git+https://github.com/Ikalus1988/MisakaNet.git
|
|
108
108
|
|
|
109
109
|
# 让 failure-memory SKILL 可被发现(DSH 扫描 ~/.dsh/skills 与项目 .dsh/skills)
|
|
110
110
|
mkdir -p ~/.dsh/skills
|
|
@@ -164,6 +164,13 @@ MisakaNet 对不同用户的用途不同:
|
|
|
164
164
|
|
|
165
165
|
> 👉 第一次来?[看 MisakaNet Journey →](https://misakanet.org/journey/)
|
|
166
166
|
|
|
167
|
+
### 它能回答什么,不能回答什么
|
|
168
|
+
|
|
169
|
+

|
|
170
|
+
|
|
171
|
+
它只回答**已经收录过的失败**,不是通用知识。查不到时会返回 `no_match` 和一条可直接调用的 `intake`
|
|
172
|
+
指引 —— 未命中正是一条缺口被记下来的方式,所以它同样是一个答案。
|
|
173
|
+
|
|
167
174
|
### Lesson 和 Skill 有什么区别?
|
|
168
175
|
|
|
169
176
|
MisakaNet 的 lesson **不是** skill。
|
package/icon.svg
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!-- MisakaNet plugin icon — the mark the DSH plugin manager renders (package.json "icon").
|
|
3
|
+
Same mark as docs/assets/misaka-avatar-fallback.svg (dark plate, cyan ring + "M", amber node);
|
|
4
|
+
tests/test_dsh_plugin_surface.py pins the shared path so the two copies cannot drift apart.
|
|
5
|
+
The host's own rules (dsh-app-boot iconOf): relative path, SVG/PNG/JPEG/WebP, <=256 KiB, inside
|
|
6
|
+
the package — an icon that breaks any of them makes plugin loading throw, which is why the test
|
|
7
|
+
next to this file asserts them instead of trusting the eye. -->
|
|
8
|
+
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="MisakaNet">
|
|
9
|
+
<rect width="64" height="64" rx="12" fill="#070b15"/>
|
|
10
|
+
<rect x="2" y="2" width="60" height="60" rx="10" stroke="#00e5ff" stroke-opacity="0.55" stroke-width="2"/>
|
|
11
|
+
<path d="M16 44V20h7l9 14 9-14h7v24h-7V31.5L34.5 44h-5L23 31.5V44h-7Z" fill="#67e8f9"/>
|
|
12
|
+
<circle cx="50" cy="14" r="4" fill="#f59e0b"/>
|
|
13
|
+
</svg>
|
package/index.js
CHANGED
|
@@ -74,22 +74,119 @@ export const DEFAULT_MCP_CONFIG = Object.freeze({
|
|
|
74
74
|
*/
|
|
75
75
|
export async function apply(ctx, config = {}) {
|
|
76
76
|
const options = { ...DEFAULT_MCP_CONFIG, ...config };
|
|
77
|
-
if (typeof ctx?.plugin !== 'function') {
|
|
78
|
-
throw new Error('misakanet: cordis context has no plugin() to mount the MCP client');
|
|
79
|
-
}
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
// NOTHING BELOW MAY THROW OUT OF THIS FUNCTION.
|
|
79
|
+
//
|
|
80
|
+
// A throw here is a failed activation, and a failed activation inside a profile
|
|
81
|
+
// bundle can take the whole host down: `dsh: startup failed: N required plugins
|
|
82
|
+
// did not activate` — the entire `dsh web` refuses to start, not just this
|
|
83
|
+
// server. That is a catastrophic outcome for a feature whose worst case should
|
|
84
|
+
// be "the tools are missing": the skill (SKILL.md) and every other plugin in
|
|
85
|
+
// the profile are unrelated to whether MisakaNet's MCP client mounted.
|
|
86
|
+
//
|
|
87
|
+
// So the whole body is guarded and reports through the host logger instead.
|
|
88
|
+
// `failOnStartupError` still works as documented — it asks the *MCP client* to
|
|
89
|
+
// treat a failed initial connection or tool sync as fatal (that is a config
|
|
90
|
+
// field of the client, `dsh-mcp-client/lib/index.js`), and when it is set the
|
|
91
|
+
// client's own fiber fails where the host can attribute it. What it must never
|
|
92
|
+
// do is make *our* wrapper the reason a profile will not boot.
|
|
82
93
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
if (typeof ctx?.plugin !== 'function') {
|
|
95
|
+
// A host that cannot mount child plugins: the skill still works, so say so and stop. This used to
|
|
96
|
+
// throw unconditionally — before the try — which is exactly the failure mode above. The explicit
|
|
97
|
+
// opt-in still gets its error, because that is what an opt-in is for.
|
|
98
|
+
const message = 'misakanet: cordis context has no plugin(); MCP tools not mounted';
|
|
99
|
+
if (options.failOnStartupError) throw new Error(message);
|
|
100
|
+
ctx?.logger?.warn?.(message);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let client;
|
|
105
|
+
try {
|
|
106
|
+
// Dynamic import: the client is ESM and only exists inside a DSH install.
|
|
107
|
+
client = await import('@deepseek-ai/dsh-mcp-client');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (options.failOnStartupError) throw error;
|
|
110
|
+
// Expected on npm skill-only installs: the skill (SKILL.md) is the payload,
|
|
111
|
+
// and a missing client must not take the whole profile down with it.
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// The Loader normalizes ESM/CJS/default export shapes before applying a plugin.
|
|
116
|
+
const component = ctx?.loader?.unwrapExports ? ctx.loader.unwrapExports(client) : client;
|
|
117
|
+
ctx.plugin(component, options);
|
|
85
118
|
} catch (error) {
|
|
86
119
|
if (options.failOnStartupError) throw error;
|
|
87
|
-
|
|
88
|
-
// and a missing client must not take the whole profile down with it.
|
|
89
|
-
return;
|
|
120
|
+
ctx?.logger?.warn?.(`misakanet: MCP client not mounted: ${error?.message ?? error}`);
|
|
90
121
|
}
|
|
122
|
+
}
|
|
91
123
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
124
|
+
/**
|
|
125
|
+
* The row's `Config`: what the Loader validates the bundle patch's `config:` against, and what the plugin
|
|
126
|
+
* page turns into a form — the host's own words for it are "the cordis `Config` validator and the
|
|
127
|
+
* Config-form generation's served schema" (`dsh-context`'s `index.d.ts` says the same thing about its own).
|
|
128
|
+
*
|
|
129
|
+
* Built with a **top-level `await` inside a `try`**, not a static import. `@deepseek-ai/schemastery` is a
|
|
130
|
+
* host dependency, but this package is also installed by skill-only consumers that never load this module,
|
|
131
|
+
* and a module-level import failure would take the whole row down rather than only its form. Without it the
|
|
132
|
+
* row keeps working from the patch and only the GUI form is lost — the same "absence is not failure" rule
|
|
133
|
+
* `apply()` follows for the MCP client.
|
|
134
|
+
*/
|
|
135
|
+
/**
|
|
136
|
+
* Import schemastery from wherever this install can actually reach it.
|
|
137
|
+
*
|
|
138
|
+
* A profile that **installed** this package resolves the bare specifier on its own: Node walks up from
|
|
139
|
+
* `…/profiles/web/node_modules/misakanet/` and finds the profile's copy. A profile that **linked** a working
|
|
140
|
+
* tree does not — Node resolves the link to its real path, so the walk starts in the repository and never
|
|
141
|
+
* sees the profile. That is the shape this repository's own contributor profiles (and the owner's live one)
|
|
142
|
+
* use, so the second attempt resolves from the `dsh` entry point that started the host, where schemastery is
|
|
143
|
+
* a dependency of DSH itself.
|
|
144
|
+
*/
|
|
145
|
+
async function loadSchemastery() {
|
|
146
|
+
const attempts = ['@deepseek-ai/schemastery'];
|
|
147
|
+
try {
|
|
148
|
+
const { createRequire } = await import('node:module');
|
|
149
|
+
const { realpathSync } = await import('node:fs');
|
|
150
|
+
const { pathToFileURL } = await import('node:url');
|
|
151
|
+
const hostEntry = process.argv[1];
|
|
152
|
+
// Both spellings matter: the bin is usually a **symlink**, and the package's own node_modules sits
|
|
153
|
+
// beside its real path — resolving from the link's directory finds the global root instead.
|
|
154
|
+
const from = [];
|
|
155
|
+
if (hostEntry) from.push(hostEntry);
|
|
156
|
+
try { from.push(realpathSync(hostEntry)); } catch (error) { /* no real path to add */ }
|
|
157
|
+
for (const entry of from) {
|
|
158
|
+
try {
|
|
159
|
+
const resolved = createRequire(entry).resolve('@deepseek-ai/schemastery');
|
|
160
|
+
// `require.resolve` hands back a filesystem path, and a Windows one is not a valid ESM specifier
|
|
161
|
+
// (`C:` parses as a scheme). The bare specifier above is left alone; only paths are wrapped.
|
|
162
|
+
const url = pathToFileURL(resolved).href;
|
|
163
|
+
if (!attempts.includes(url)) attempts.push(url);
|
|
164
|
+
} catch (error) { /* this entry cannot see it */ }
|
|
165
|
+
}
|
|
166
|
+
} catch (error) { /* keep the bare specifier as the only attempt */ }
|
|
167
|
+
for (const specifier of attempts) {
|
|
168
|
+
try {
|
|
169
|
+
const loaded = await import(specifier);
|
|
170
|
+
return loaded.default ?? loaded;
|
|
171
|
+
} catch (error) { /* try the next location */ }
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
95
174
|
}
|
|
175
|
+
|
|
176
|
+
let Config;
|
|
177
|
+
try {
|
|
178
|
+
const z = await loadSchemastery();
|
|
179
|
+
if (z === undefined) throw new Error('schemastery is not resolvable from this install');
|
|
180
|
+
Config = z.object({
|
|
181
|
+
transport: z.string().default(DEFAULT_MCP_CONFIG.transport),
|
|
182
|
+
serverName: z.string().default(DEFAULT_MCP_CONFIG.serverName),
|
|
183
|
+
url: z.string().default(DEFAULT_MCP_CONFIG.url),
|
|
184
|
+
headers: z.dict(z.string()).default({ ...DEFAULT_MCP_CONFIG.headers }),
|
|
185
|
+
toolCallTimeoutMs: z.number().default(DEFAULT_MCP_CONFIG.toolCallTimeoutMs),
|
|
186
|
+
failOnStartupError: z.boolean().default(DEFAULT_MCP_CONFIG.failOnStartupError),
|
|
187
|
+
});
|
|
188
|
+
} catch (error) {
|
|
189
|
+
Config = undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export { Config };
|