@mebular/dsh-plugin 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Windsander
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,335 @@
1
- # Temporary Holding Version
1
+ # @mebular/dsh-plugin
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 把 Mebular 的记忆、任务图与共享 Console 接入
4
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)。
5
+
6
+ **DSH 是 Mebular 的第二个壳,不是第二套 Console。** Console 只有一份,由 Mebular daemon 在
7
+ `/console` 提供;桌面 App、浏览器和 DSH 插件打开的是同一个页面。插件本身零构建、零运行时
8
+ 依赖,也不 import 任何 `@mebular/*` 包。
9
+
10
+ 插件做四件事:
11
+
12
+ - 用官方 `@deepseek-ai/dsh-mcp-client` 连接 Mebular 的 `/mcp`,把记忆工具注册给 DSH;
13
+ 连接、重连、工具发现与注册都归官方 peer,插件只负责按同一套 daemon 发现结果挂载;
14
+ - 在 DSH 侧边栏加入 **Mebular** 面板,用 iframe 加载共享 Console 的 `?embed=dsh`;
15
+ - 提供只读的 `GET /mebular/embed-info`,供面板判断 daemon 状态与可嵌入性;
16
+ - 默认 `attach` 只探测已有 daemon;可选 `managed` 在 daemon 不存在时启动并托管一个
17
+ `mebular serve` 子进程。
18
+
19
+ ## 前提
20
+
21
+ - Node.js `>=22.19`
22
+ - DSH `>=0.2.1-rc.2 <0.3.0`
23
+ - 已运行的 Mebular daemon(`attach`),或已安装 Mebular CLI(`managed` 可代为启动)
24
+
25
+ 先启动 Mebular App(或单独运行 daemon),确认默认端点已就绪:
26
+
27
+ ```bash
28
+ curl -sS http://127.0.0.1:7331/healthz
29
+ ```
30
+
31
+ 默认端口是 `7331`,MCP 路径是 `/mcp`,Console 路径是 `/console`。
32
+
33
+ ## 安装
34
+
35
+ v0.1.0 起从 npm 安装到 DSH 的 Web profile:
36
+
37
+ ```bash
38
+ dsh plugin --profile web add @mebular/dsh-plugin@0.1.0
39
+ ```
40
+
41
+ 插件开发时仍可从仓库工作树安装:
42
+
43
+ ```bash
44
+ dsh plugin --profile web add ./packages/dsh-plugin
45
+ ```
46
+
47
+ 安装后 `$DSH_HOME/profiles/web/package.json` 的 `dsh.profile.bundles` 会包含
48
+ `@mebular/dsh-plugin`。重启 DSH Web 后,侧边栏出现 Mebular 图标;点击后进入全局面板。
49
+
50
+ > `@mebular/dsh-plugin` 是**独立版本线**:v0.1.0 与 Mebular v0.1.0 同一 release train,
51
+ > 但版本号只由 `packages/dsh-plugin/package.json` 决定,不进入五包 lockstep。官方插件列表
52
+ > 尚未提交、尚未收录;不要按“已进官方列表”来使用本文档。
53
+
54
+ 插件零构建:安装不会触发 `prepare` / `allowBuilds`。`@deepseek-ai/dsh-mcp-client` 声明为
55
+ peer(兼容范围 `>=0.2.1-rc.2 <0.2.2-0 || >=0.2.2-rc.1 <0.3.0-0`),由 DSH host 在运行时
56
+ 提供,不在插件内打包。
57
+
58
+ ## 配置与 daemon 发现
59
+
60
+ `cordis.patch.yml` 只插入一行 `mebular`(本插件 host,`lifecycle: attach`,
61
+ `panel: true`)。它不写死 `daemonUrl`,也不插入静态的 MCP client row。MCP 桥由 host 在
62
+ `apply` 时动态 import 官方 `@deepseek-ai/dsh-mcp-client`,并用**同一个**解析后的 daemon
63
+ origin 挂载。因此面板、`/mebular/embed-info` 与 MCP 工具始终指向同一个端口。
64
+
65
+ host 按以下顺序确定 daemon origin:
66
+
67
+ 1. 显式 `daemonUrl`(含凭据的 URL 会被拒绝);
68
+ 2. `MEBULAR_HTTP_PORT`(有效端口 1–65535);
69
+ 3. `<home>/runtime.json`(daemon 在 HTTP listen 成功后原子写入;`pid` 已不存在则视为
70
+ stale 并跳过);
71
+ 4. `http://127.0.0.1:7331`。
72
+
73
+ `<home>` 与 Mebular CLI 一致:`MEBULAR_HOME` 覆盖,缺省 `~/.mebular`。host 只读取
74
+ runtime 文件里的 `version`、`pid`、`http.host`、`http.port`、`consolePath`、`mcpPath`、
75
+ `embedProtocol`,不读取 `config.json`、身份、token 或 store。
76
+
77
+ ### 配置字段
78
+
79
+ | 字段 | 默认 | 说明 |
80
+ |---|---|---|
81
+ | `daemonUrl` | `http://127.0.0.1:7331` | Mebular daemon origin;只接受 `http:` / `https:`,尾部斜杠会被去掉 |
82
+ | `lifecycle` | `attach` | `attach` 只探测已有 daemon,绝不启动/停止;`managed` 在 daemon 不存在时启动一个由插件托管的 `mebular serve` 子进程 |
83
+ | `panel` | `true` | `false` 时不注册 DSH sidebar/main 面板;host 仍挂载官方 MCP client,工具照常可用 |
84
+ | `cliPath` | 未设置 | 仅 `managed` 使用;可执行的 Mebular CLI 路径。也接受 `MEBULAR_CLI`,最后回退到 `PATH` 上的 `mebular` |
85
+
86
+ 非法 `lifecycle` / `panel` / `cliPath` 会回退到安全默认值并写一条 `warn` 日志,不会让
87
+ DSH 启动失败。
88
+
89
+ 不提供 Mebular 系统设置(身份、网络、邀请、域、审计等)。这些设置只属于 Mebular Console。
90
+
91
+ ### 生命周期策略
92
+
93
+ | 模式 | 已有健康 daemon | daemon 不存在 | 插件 dispose |
94
+ |---|---|---|---|
95
+ | `attach`(默认) | 复用,只探测 | 不启动,状态 `down`,面板显示启动引导 | 不停止任何进程 |
96
+ | `managed` | 复用,不启动(`managed: false`) | 解析 CLI 后启动 `mebular serve --port <port>`,有界等待 `/healthz`;失败时清理自己的 child | 只对自己启动的 child 发 `SIGTERM`,2s 后仍未退出则 `SIGKILL` |
97
+
98
+ `managed` 是子进程兜底,不是服务安装:
99
+
100
+ - 不 import `@mebular/service`,不调用系统服务管理器;
101
+ - 不覆盖 `MEBULAR_HOME`,让 Mebular CLI 自己解析 home;
102
+ - 插件本身不写 Mebular 配置、不读密钥/token/store;daemon 自己负责 `runtime.json` 与它的 home;
103
+ - 启动前先探测 `/healthz`;已有健康 daemon(包括服务托管或桌面 App 启动的实例)只复用,
104
+ 绝不抢占或停止;
105
+ - 等待 `/healthz` 有界(默认 20s),期间状态为 `starting`;超时、spawn error 或 child 提前
106
+ 退出时,只停止自己启动的 child,状态 `down`、`reason: managed_start_failed`,不会自动
107
+ 无限重试;
108
+ - 停止顺序固定为 `SIGTERM` → 2s grace → `SIGKILL`;外部 daemon(桌面 App、服务托管、
109
+ 手动 `mebular serve`)永远不会被 host 停止;
110
+ - Windows 上 CLI 为 `.cmd` / `.bat` 时使用 `shell: true` 启动(args 固定,不接受用户拼接
111
+ 命令);其他平台直接 exec;
112
+ - CLI 无法解析时降级为 `attach`,状态 `down`、`reason: managed_cli_missing`,不会启动任何
113
+ 进程;
114
+ - managed child 的 stdout/stderr 先脱敏再写 DSH 日志:`token` / `secret` / `password` /
115
+ `api_key` 等值替换为 `<redacted>`,`Authorization: Bearer …` 保留协议名,CLI 路径与
116
+ `<home>` 替换为 `<cli>` / `<home>`;单行上限 500 字符。
117
+
118
+ ### 只关闭面板,保留 MCP
119
+
120
+ ```yaml
121
+ - id: mebular
122
+ name: '@mebular/dsh-plugin'
123
+ config:
124
+ panel: false
125
+ ```
126
+
127
+ `panel: false` 时 sidebar 入口与 `main` 面板都不注册;`/mebular/embed-info` 仍可访问,官方
128
+ MCP client 仍在 `apply` 时挂载。host 通过 DSH `webServer.tapIndex` 注入
129
+ `window.__MEBULAR_DSH__={"panel":false}`,client 在 `apply` 时读取。
130
+
131
+ ### 端口与环境变量
132
+
133
+ | 变量 | 作用 |
134
+ |---|---|
135
+ | `MEBULAR_HTTP_PORT` | 覆盖 daemon 端口(默认 `7331`);host、面板、MCP 桥一起跟随 |
136
+ | `MEBULAR_HOME` | Mebular home,用于定位 `runtime.json`;缺省 `~/.mebular` |
137
+ | `MEBULAR_CLI` | `managed` 模式下 Mebular CLI 路径的候选(`cliPath` 优先) |
138
+ | `DSH_HOME` | DSH 自己的 profile 根;与 Mebular home 无关 |
139
+
140
+ 端口覆盖示例:
141
+
142
+ ```sh
143
+ MEBULAR_HTTP_PORT=7332 dsh --profile web
144
+ ```
145
+
146
+ `managed` 启动的 `--port` 取自最终解析出的 `daemonUrl` 端口。MCP client 不自己读端口、
147
+ 不自己重连:连接、重连与工具注册全部归官方 peer;daemon 未启动时它按
148
+ `failOnStartupError: false` 等待并重试。
149
+
150
+ ## 验证
151
+
152
+ 安装并重启后,先看 profile 组合结果:
153
+
154
+ ```bash
155
+ dsh --profile web --dump-config | grep -A6 mebular
156
+ ```
157
+
158
+ 应该只看到一条 `mebular` host row(`name: '@mebular/dsh-plugin'`),没有静态的
159
+ `@deepseek-ai/dsh-mcp-client` row,也没有写死的 URL。
160
+
161
+ 工具列表里应出现 `mebular` 服务下的工具,命名形如:
162
+
163
+ ```text
164
+ mcp__mebular__*
165
+ ```
166
+
167
+ 例如让 DSH 调用 `mcp__mebular__*` 中的记忆检索/写入工具;能返回 Mebular 的数据即接通。
168
+ DSH 与 Mebular App 同时保持运行,Mebular App 继续独立运行、继续负责存储与控制台,不会因为
169
+ 安装插件而退出或消失。
170
+
171
+ P7.3 在 `@deepseek-ai/dsh@0.2.1-rc.2`、临时 home 与非默认端口上实测:侧边栏出现 Mebular,
172
+ iframe 为 `/console/?embed=dsh`,Console 内 `dataset.embed='dsh'`、星图与设置正常;
173
+ 临时 probe 插件从 `ctx.tools.schemas()` 读到 29 个 `mcp__mebular__*` 工具(总计 33 个,
174
+ 其余为 DSH 自带工具)。工具数量会随 daemon 版本变化,不承诺固定值。
175
+
176
+ 面板状态可单独检查(只接受 loopback 请求):
177
+
178
+ ```bash
179
+ curl -sS http://127.0.0.1:7331/app/embed/info
180
+ curl -sS http://127.0.0.1:<dsh-port>/mebular/embed-info
181
+ ```
182
+
183
+ `/mebular/embed-info` 返回非敏感状态 JSON(`status`、`mode`、`managed`、`panel`、
184
+ `version`、`embedProtocol`、`consoleUrl`、`embedUrl`、`mcpUrl`、`loopback`、`reason`)。
185
+ 只有 daemon、`/app/embed/info` 的 `loopback`、`embedProtocol`、`capabilities.embedConsole`
186
+ 全部满足时才返回 `embedUrl`;否则面板显示占位页,不加载 iframe。
187
+
188
+ ## 状态契约
189
+
190
+ ```json
191
+ {
192
+ "ok": true,
193
+ "status": "up",
194
+ "mode": "attach",
195
+ "managed": false,
196
+ "panel": true,
197
+ "version": "0.1.0",
198
+ "embedProtocol": 1,
199
+ "consoleUrl": "http://127.0.0.1:7331/console",
200
+ "embedUrl": "http://127.0.0.1:7331/console/?embed=dsh",
201
+ "mcpUrl": "http://127.0.0.1:7331/mcp",
202
+ "loopback": true,
203
+ "reason": null
204
+ }
205
+ ```
206
+
207
+ - `status`:`up`(daemon 健康)、`down`(不可达/未健康)、`starting`(managed 正在启动并
208
+ 等待 healthz)。`starting` 时 `embedUrl` 为 `null`,面板不加载 iframe。
209
+ - `mode`:当前生效的生命周期。CLI 缺失时从 `managed` 降级为 `attach`。
210
+ - `managed`:仅当当前 daemon 由本插件启动且 child 仍在运行时为 `true`;复用外部 daemon 时
211
+ 始终为 `false`。
212
+ - `panel`:当前 `panel` 配置的非敏感快照。`panel: false` 时 client 不注册槽位,但该只读
213
+ 路由仍可用于诊断。
214
+ - `reason`:稳定诊断码,成功为 `null`。当前包括 `daemon_unreachable`、`daemon_unhealthy`、
215
+ `embed_info_unavailable`、`embed_not_ok`、`not_loopback`、
216
+ `embed_protocol_unsupported`、`embed_unsupported`、`embed_url_invalid`、
217
+ `managed_cli_missing`、`managed_start_failed`。
218
+ - 不返回 token、密钥、home 路径、CLI 路径或进程环境。
219
+
220
+ 状态路由只接受 loopback 请求;其他来源返回 `404`,响应带 `Cache-Control: no-store`。
221
+ 1s 状态缓存由生命周期持有,`starting` 期间不缓存失败。
222
+
223
+ ## 架构边界
224
+
225
+ ```text
226
+ DSH host Mebular daemon
227
+ ├─ cordis.patch.yml ├─ /healthz
228
+ │ └─ mebular (index.js) ├─ /app/embed/info
229
+ │ ├─ GET /mebular/embed-info
230
+ │ └─ mounts official mcp-client ──┼─ /mcp
231
+ └─ client.js
232
+ └─ iframe ─────────────────┼─ /console/?embed=dsh
233
+ ```
234
+
235
+ - 本包**不 import** `@mebular/core`、`@mebular/console`、`@mebular/desktop`,也不复制
236
+ Console 的任何 UI 或静态资源。
237
+ - Console 只有一套实现,由 Mebular daemon 提供;DSH 只负责 iframe 容器。
238
+ - 插件不打开 `store.jsonl`、不读密钥/token、不写 `~/.mebular`。
239
+ - 插件只动态 import 官方 `@deepseek-ai/dsh-mcp-client` peer;MCP 连接、重连、工具发现与
240
+ 注册仍归该官方包,host 只负责用发现的 origin 挂载它,不实现 MCP 客户端。
241
+ - 面板与 MCP 桥共享同一次 daemon 解析;不会出现“面板连 7332、工具连 7331”。
242
+
243
+ ## 安全
244
+
245
+ - `GET /mebular/embed-info` 只接受 loopback 请求(检查 `req.socket.remoteAddress`),其他
246
+ 来源返回 `404`。
247
+ - 路由只返回上述非敏感 JSON;响应带 `Cache-Control: no-store`。
248
+ - 只有 daemon URL、`/app/embed/info` 的 `loopback`、`embedProtocol`、
249
+ `capabilities.embedConsole` 全部满足时才返回可嵌入状态;否则 `embedUrl` 为 `null`,面板
250
+ 显示占位页,不加载 iframe。
251
+ - iframe 使用 `sandbox="allow-scripts allow-same-origin allow-forms"`,不授予 top
252
+ navigation;iframe 超时或加载失败时同样回退到占位页,不保留空白框。
253
+ - Console 的 `?embed=dsh` 只在 daemon 绑定 loopback 时移除 `X-Frame-Options`,并把
254
+ `frame-ancestors` 限制为 loopback HTTP 祖先;普通 `/console` 仍为 `DENY` / `'none'`。
255
+ - `<home>/runtime.json` 只含 version/pid/HTTP 地址/Console、MCP 路径/embed 协议,权限
256
+ `0600`,原子写入;停止时按 pid 清理,stale 文件不会阻塞 host 回退。host 不读取 Mebular
257
+ 配置、身份、token 或 store。
258
+ - panel 全局只包含 `{"panel":boolean}`,JSON 中的 `<` 转义为 `\u003c` 后才进入内联
259
+ `<script>`;host 不向页面注入 daemonUrl 或凭据。
260
+ - 插件不接受长期 token 进 URL。Mebular 的 OAuth/凭据协商由 MCP 客户端负责,不经过面板
261
+ URL。
262
+ - `managed` 子进程只继承环境、不写 Mebular home;stdout/stderr 脱敏后才进 DSH 日志。
263
+
264
+ ## 已知限制
265
+
266
+ - **默认不托管**:默认 `attach` 不启动、不停止 Mebular。daemon 未运行时面板显示引导与
267
+ “在浏览器打开 Console”,官方 MCP client 保持等待/重连。只有显式 `lifecycle: managed`
268
+ 才会启动子进程,且只停止自己启动的 child。
269
+ - **仅 loopback 可嵌入**:DSH 或 Mebular 任一侧不在 loopback(例如公网 HTTPS DSH)时,
270
+ 面板只显示占位页。这是有意设计;当前 `frame-ancestors` 也只放行 HTTP loopback。
271
+ - **MCP peer 由 DSH 提供**:`@deepseek-ai/dsh-mcp-client` 是 peerDependency,不在插件内
272
+ 打包。正常 DSH 安装下 host 能在 `apply` 时解析并挂载;在 Jest、普通 Node 或缺少该 peer
273
+ 的宿主里,`import` 会失败并记录 `mcp client mount failed (ERR_MODULE_NOT_FOUND)`,面板
274
+ 与 `/mebular/embed-info` 不受影响,但 MCP 工具不可用。
275
+ - **runtime home**:host 用 `MEBULAR_HOME`(缺省 `~/.mebular`)定位 runtime.json;如果 DSH
276
+ 与 daemon 运行在不同 home,自动发现不会命中,请显式设置 `daemonUrl` 或
277
+ `MEBULAR_HTTP_PORT`。
278
+ - **DSH preview API**:`panel` 开关依赖 DSH `0.2.1-rc.2` 的
279
+ `webServer.tapIndex(transform)`;若未来 DSH 移除该 API,client 会因全局缺失而回退为
280
+ `panel:true`。`dsh.client` / slot API 仍在 developer preview,升级 DSH 后需要重新验证
281
+ `sidebar.panellist` 与 `main` keyed slot。
282
+ - **MCP 工具数量随 daemon 版本变化**:插件不承诺固定数量。
283
+ - **Desktop origin 未验证**:`@deepseek-ai/dsh-desktop-host` 不在 npm 上,也未出现在公开
284
+ DSH 仓库可检索的 tree 中。当前 CSP 只放行 HTTP loopback;若 DSH Desktop 使用 HTTPS 或
285
+ 自定义 scheme,需要扩展 `frame-ancestors` 并重跑 E2E。
286
+ - **卸载残留**:`dsh plugin remove` 后 pnpm 可能保留 `node_modules/@mebular/dsh-plugin`
287
+ 符号链接;它不在 profile bundles 中,不会被加载。
288
+ - **截图状态**:`assets/dsh-panel.jpg` 来自隔离的临时 daemon。Console 顶部显示“守护
289
+ 未运行 / 未启用 P2P / 记忆 0”是临时实例真值,DSH 面板 header 同时显示“已连接”;
290
+ 截图不是 mock。
291
+
292
+ ## 卸载
293
+
294
+ ```bash
295
+ dsh plugin --profile web remove @mebular/dsh-plugin
296
+ ```
297
+
298
+ 确认:
299
+
300
+ ```bash
301
+ dsh --profile web --dump-config | grep -i mebular # 应无输出
302
+ ```
303
+
304
+ 卸载只移除 DSH 插件;Mebular daemon 与 Console 不受影响,`/healthz`、`/console/` 仍可用。
305
+ 不要删除 Mebular home 或 store。
306
+
307
+ ## 开发与测试
308
+
309
+ ```sh
310
+ # host 纯函数 + HTTP handler + 生命周期 + 客户端契约
311
+ npm test -- --runInBand tests/dsh
312
+
313
+ # 相关回归(Console embed / MCP / runtime / shutdown)
314
+ npm test -- --runInBand tests/dsh tests/console tests/mcp
315
+
316
+ # 插件打包契约(无网络、不写文件)
317
+ npm run verify:dsh-plugin
318
+ ```
319
+
320
+ `index.js` 是纯 ESM host plugin;`client.js` 是手写
321
+ `window.__ModuleLoader__.load({ id: '@mebular/dsh-plugin', factory })` 包装,仅
322
+ `require('react')`。两者都没有构建步骤。
323
+
324
+ ## 发布状态
325
+
326
+ - 版本:`0.1.0`
327
+ - npm:`@mebular/dsh-plugin@0.1.0`(随 Mebular v0.1.0 release train,独立版本线)
328
+ - 官方插件列表:**未提交、未收录**
329
+ - 正式安装:`dsh plugin --profile web add @mebular/dsh-plugin@0.1.0`
330
+ - 开发安装:`dsh plugin --profile web add ./packages/dsh-plugin`
331
+ - 版本线:不进入 `core` / `service` / `skill` / `mcp` / `fleet` 五包 lockstep
332
+
333
+ ## License
334
+
335
+ MIT
Binary file
Binary file
Binary file