dsh-recall-plugin 2.3.12 → 2.3.13

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/CHANGELOG.md CHANGED
@@ -2,8 +2,18 @@
2
2
 
3
3
  本文件格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循语义化版本。
4
4
 
5
+ ## [2.3.13] - 2026-09-11
6
+
7
+ ### 修复
8
+
9
+ - **DSH Desktop 上插件树加载失败(fiber 永久 pending)**:桌面端 composition(`desktop.cordis.patch.yml`)把 `webserver` row 置 `disabled: true`(Electron 用 file:// + IPC 替代 HTTP),插件 host 侧顶层 `inject` 含 `webServer`,fiber 因此停在 `pending (waiting for service: webServer)` → 「1 entry did not activate」整树加载失败。修复:① 顶层 inject 收敛为 `['shell', 'sessions', 'agents']`;② 删除 `ctx.webServer` 前缀路由注册,改走官方「载体无关」路由注册表 `ctx.connection.fetch.register`——12 个端点各注册一条 `POST /api/recall/<name>` 的 exact 路由(`requestBody: 'buffered'`、插件自留 1MB 请求体上限、`{ ok:false, code, message }` 错误包装与 200 语义不变);③ `connection` 经 `ctx.inject(['connection'], cb)` 可选注入(服务缺席不 pending,仅 Client API 降级不可用),并用 `cb.effect` 包裹 register 返回的**异步 disposer**——注册本体挂在 connection 插件 fiber 的 effect 上(`owner = this.ctx`),不包会在 HMR 重载时撞「exact Fetch route ... is already registered」。web 端由 client-connection 把 `/api` 挂到 webServer 之下、桌面端由 dsh-desktop-host 以 `createSharedFetchHandler('/api')` 直接分发,客户端 URL 与方法两端完全一致,**客户端 bundle 零改动**。契约类型按官方 `.d.ts` 换为 `ConnectionFetchRoute` / `HostConnectionFetch`(HttpRequest/HttpResponse/WebRoute/WebServer 删除);`verify:host` 门禁改 connection 桩并新增路由形状(methods/requestBody/fetch)、exact 分发 404、异步 disposer 清零断言;`compat-audit` 新增 I32、`dsh-contract.md` 与 AGENTS.md 同步。插件功能与 `lib/client.js` 零变化。
10
+
5
11
  ## [2.3.12] - 2026-09-11
6
12
 
13
+ ### 变更
14
+
15
+ - **dsh 0.1.5-rc.2 兼容性声明与台账同步**:全局实装 `@deepseek-ai/dsh@0.1.5-rc.2`(tag `dsh-v0.1.5-rc.2`,commit `fb2c4b9`,npm dist-tag `next`)后跑三层门禁——`test:probe` 31/31、`verify:host` 装配断言通过、`npm test` 307/307;rc.1→rc.2 的 4 commits / 300 文件按插件消费面包与类型源过滤后唯一命中 ui-chat 的 `TurnTailNodeView.module.css`(+3 行纯 CSS 间距),`sessions.d.ts`/`slots.d.ts`/`slot-contract.d.ts` 等类型源零改动;reference/ 镜像按 rc.2 tag 重拉 13 源(12 份内容相同、11 号仅 CRLF 噪声)。`dsh.compatibility.dshReleases` 补 `0.1.5-rc.2`,README 双语兼容声明、镜像索引与契约文档版本字段同步。peer 范围沿用按 minor 线开窗(`>=0.1.5-alpha.1 <0.1.6`),npm semver 的 prerelease 门槛天然放行同 tuple 的 rc.2,无需改 peer 串。评估实证沉淀于 `docs/upgrade-assessments/dsh-0.1.5-rc.2.md`,本插件源码零功能变更。
16
+
7
17
  ### 修复
8
18
 
9
19
  - **DSH Desktop(0.1.5-rc.2)安装插件被 `validateDesktopPluginGraph` 拒绝**:桌面端 profile 固定 `autoInstallPeers: false`,安装时逐个解析 `package.json` 的 peerDependencies 并要求实体包存在于 profile 的 node_modules。插件声明的 `@deepseek-ai/dsh-client-web-react` 是 dsh 0.1.0 时代的历史包(npm 最高 0.1.0-rc.7),官方 0.1.1+ 已不再发布、0.1.5-rc.2 的宿主共享包中不存在,故报 `requires missing @deepseek-ai/dsh-client-web-react`。该声明早已无运行时意义:`lib/client.js` 唯一的模块请求是 `require("react")`,React 由宿主平台基线模块表提供。修复三处:① 删除该历史 peer;② 保留 `react` peer 但补 `peerDependenciesMeta.react.optional`——桌面端 profile 顶层无 `react`,不标 optional 会在下一轮校验继续报 missing;③ 删除过时的 `dsh.client.inject`(新机制下是包名依赖边,指向已不存在的包会被宿主静默跳过;`dsh.client.platform: "web"` 保留,宿主据此识别 client 半侧的运行平台)。其余 8 个 peer 及其范围不变。`dsh.compatibility.dshReleases` 补 `0.1.5-rc.2`,README 双语安装兼容声明同步。插件功能与运行时零变化,`lib/` 产物结构与 2.3.11 相同。
package/lib/index.js CHANGED
@@ -9,9 +9,8 @@ import { createRoutesManage } from "./routes-manage.js";
9
9
  import * as dshSettings from "@deepseek-ai/dsh-settings";
10
10
  import * as E from "./errors.js";
11
11
  const name = "dsh-recall-plugin";
12
- const inject = ["shell", "sessions", "webServer", "agents"];
12
+ const inject = ["shell", "sessions", "agents"];
13
13
  function apply(ctx, config) {
14
- const webServer = ctx.webServer;
15
14
  const cfg = createConfig(config);
16
15
  const rt = createRuntime(ctx, cfg);
17
16
  const snaps = createSnapshots(ctx, rt, cfg);
@@ -56,21 +55,11 @@ function apply(ctx, config) {
56
55
  const excludeCache = { at: 0, payload: null };
57
56
  const usageCache = { at: 0, payload: null };
58
57
  const sessionInfo = createSessionInfo(ctx);
59
- async function readJsonBody(req) {
60
- const chunks = [];
61
- let size = 0;
62
- for await (const chunk of req) {
63
- size += chunk.length;
64
- if (size > MAX_BODY_BYTES) throw new Error(E.RECALL_BODY_TOO_LARGE);
65
- chunks.push(chunk);
66
- }
67
- const text = Buffer.concat(chunks).toString("utf8");
68
- if (!text.trim()) return {};
69
- return JSON.parse(text);
70
- }
71
- function sendJson(res, status, body) {
72
- res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
73
- res.end(JSON.stringify(body));
58
+ function jsonResponse(body, status = 200) {
59
+ return new Response(JSON.stringify(body), {
60
+ status,
61
+ headers: { "content-type": "application/json; charset=utf-8" }
62
+ });
74
63
  }
75
64
  function errBody(error) {
76
65
  const e = error;
@@ -251,8 +240,6 @@ function apply(ctx, config) {
251
240
  enqueue,
252
241
  agentBusy,
253
242
  runLimited,
254
- readJsonBody,
255
- sendJson,
256
243
  errBody,
257
244
  listExcludeFiles,
258
245
  dumpStores,
@@ -278,25 +265,31 @@ function apply(ctx, config) {
278
265
  ...createRoutesCore(deps),
279
266
  ...createRoutesManage(deps)
280
267
  };
281
- ctx.effect(() => webServer.register({
282
- kind: "prefix",
283
- path: "/api/recall",
284
- handler: async (req, res) => {
285
- const path = (req.url || "").split("?")[0];
286
- const name2 = path.replace(/^\/api\/recall\/?/, "").split("/")[0];
287
- const endpoint = endpoints[name2];
288
- if (!endpoint) {
289
- sendJson(res, 404, { ok: false, code: E.RECALL_UNKNOWN_ENDPOINT, message: "unknown endpoint: " + name2 });
290
- return;
291
- }
292
- try {
293
- const args = await readJsonBody(req);
294
- sendJson(res, 200, await endpoint(args));
295
- } catch (error) {
296
- sendJson(res, 200, errBody(error));
297
- }
268
+ const handleRecall = async (request) => {
269
+ const name2 = new URL(request.url).pathname.replace(/^\/api\/recall\/?/, "").split("/")[0];
270
+ const endpoint = endpoints[name2];
271
+ if (!endpoint) {
272
+ return jsonResponse({ ok: false, code: E.RECALL_UNKNOWN_ENDPOINT, message: "unknown endpoint: " + name2 });
273
+ }
274
+ try {
275
+ const text = await request.text();
276
+ if (Buffer.byteLength(text, "utf8") > MAX_BODY_BYTES) throw new Error(E.RECALL_BODY_TOO_LARGE);
277
+ const args = text.trim() ? JSON.parse(text) : {};
278
+ return jsonResponse(await endpoint(args));
279
+ } catch (error) {
280
+ return jsonResponse(errBody(error));
298
281
  }
299
- }));
282
+ };
283
+ ctx.inject(["connection"], (connectionCtx) => {
284
+ for (const name2 of Object.keys(endpoints)) {
285
+ connectionCtx.effect(() => connectionCtx.connection.fetch.register({
286
+ path: "/api/recall/" + name2,
287
+ methods: ["POST"],
288
+ requestBody: "buffered",
289
+ fetch: handleRecall
290
+ }));
291
+ }
292
+ });
300
293
  if (!supported) return;
301
294
  ctx.on("session/event", (session, event) => {
302
295
  if (!event || event.type !== "user/message") return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-recall-plugin",
3
- "version": "2.3.12",
3
+ "version": "2.3.13",
4
4
  "description": "DSH 消息撤回插件:在用户消息气泡旁加「撤回」按钮,把项目文件(独立影子 git 仓库快照)与对话历史(官方 fork)一并回退到该消息发送之前。",
5
5
  "type": "module",
6
6
  "repository": {