dsh-multi-router 0.5.6 → 0.5.7

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,6 +2,13 @@
2
2
 
3
3
  本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。版本号从 `0.1` 起以功能迭代为主,接口在 `1.0` 前可能变化。
4
4
 
5
+ ## 0.5.7
6
+
7
+ - 修复更新后仍使用旧模块:每次发布生成独立的宿主入口和组件 ID,使用 DSH 官方配置重载流程同时替换后台与前端模块,避免复用旧代码缓存。
8
+ - 检测插件页、内置更新器与外部 CLI 的安装变化,空闲后自动重载本插件;等待流式调用、签到、登录与安装任务结束,正在输出的请求正常完成。关闭了插件的用户不会被自动重新启用;失败重载有退避和明确提示。
9
+ - 启动不再等待本机账号扫描、插件清单查询或网络请求。先注册管理接口与模型入口,账号与用量后台更新;初次连接自动重试,显示扫描状态和实际运行版本,新增「重新加载插件」恢复入口。
10
+ - 从 v0.5.6 及更早版本首次升级,关闭再启用插件一次即可迁移,无需退出 DSH;后续更新自动加载。未提供热加载接口的 DSH 保留明确的重启回退说明。
11
+
5
12
  ## 0.5.6
6
13
 
7
14
  - 修复 WorkBuddy 中国版 `workbuddy.cn` 签发的有效令牌被本地误判失效,同时兼容原 `codebuddy.cn` 签发地址;保留 RS256、账号身份与中国版精确签发地址校验。
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  一个插件内置 WorkBuddy、Trae、Qoder 的认证、模型发现和聊天传输,提供三家的多账号池及集中路由。运行不依赖原来三个 DSH 插件;三家代码随安装包一起打包。基于 MIT 上游,固定版本见 `upstream-lock.json`,许可见 `THIRD_PARTY_NOTICES.md`。
8
8
 
9
- 版本:0.5.5。适配 DSH Desktop / Web 0.2.0-rc.2,Node.js ^22.19.0 或 >=24。
9
+ 版本:0.5.7。适配 DSH Desktop / Web 0.2.0-rc.2,Node.js ^22.19.0 或 >=24。
10
10
 
11
11
  ## 在 DSH 里管理
12
12
 
@@ -24,13 +24,15 @@
24
24
 
25
25
  保存即时生效;CLI / 手工修改 router.json 也会自动加载。无效配置保留最后一份有效运行配置,写入冲突返回 409,避免覆盖其他页面刚保存的修改。
26
26
 
27
+ 面板与模型入口先启用,本机登录扫描、平台用量、免费模型发现和插件检查在后台进行。扫描时显示进度提示,已有账号和缓存路由可以继续使用;安装 / 更新中的短暂断连会自动重新连接。
28
+
27
29
  原始模型 ID 用于上游协议。面板新建路由只填写名称,内部 ID 自动生成;编辑名称保留 ID,不影响已有会话引用。已有路由和预配置的内部 ID 保持兼容,面板不再提供 ID 配置。
28
30
 
29
31
  隐藏只影响模型选择器,已有会话及路由仍可使用隐藏模型;停用账号会将其从新请求的候选池中排除。移出账号池保留独立凭证文件,且禁止移除仍被路由明确绑定的账号。
30
32
 
31
33
  ### 界面预览
32
34
 
33
- 以下为 **v0.5.5–v0.5.6 实际面板 + 合成演示数据**。账号名称已替换,不包含真实手机号、邮箱、身份 ID、设备标识、登录凭证或会话内容;余额、用量和性能均为演示数值。
35
+ 以下为 **v0.5.5–v0.5.7 实际面板 + 合成演示数据**。账号名称已替换,不包含真实手机号、邮箱、身份 ID、设备标识、登录凭证或会话内容;余额、用量和性能均为演示数值。
34
36
 
35
37
  | 总览与用量 | Token 明细与调用性能 |
36
38
  | --- | --- |
@@ -161,7 +163,7 @@ dsh plugin --profile desktop add dsh-multi-router
161
163
  dsh plugin --profile web add dsh-multi-router
162
164
  ```
163
165
 
164
- 包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.5.5`。
166
+ 包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.5.7`。
165
167
 
166
168
  已使用 npm 来源的用户更新:
167
169
 
@@ -175,7 +177,11 @@ dsh plugin --profile desktop update dsh-multi-router
175
177
  dsh plugin --profile desktop add dsh-multi-router
176
178
  ```
177
179
 
178
- 这是同名插件的来源切换;账号、路由和用量保存在包目录之外,会保留。更新完成后**完全退出并重新打开 DeepSeek Harness**;关闭窗口或点击「刷新面板」不会重载后台。插件面板检测到前后台版本不一致时会显示重启步骤。
180
+ 这是同名插件的来源切换;账号、路由和用量保存在包目录之外,会保留。
181
+
182
+ **从 v0.5.7 起,本插件支持运行中更新。** 更新完成后会等待当前模型调用、签到、账号操作与安装任务结束,调用 DSH 官方接口重新加载本插件,前端面板也同步切换。面板顶部显示实际运行版本;安装版本尚未生效时单独提示,不把磁盘版本冒充运行版本。通过插件页、插件内更新器或外部 CLI 更新均可检测;外部更新可能最多延迟一分钟。
183
+
184
+ **首次从 v0.5.6 或更早版本升级:** 旧后台没有自动重载逻辑,在 DSH 插件页关闭再重新启用一次 `dsh-multi-router` 即可加载新入口,无需退出应用。之后升级自动生效。若遇到连接或版本同步异常,面板提供「重新加载插件」与「重新连接」;只有不提供热加载接口的 DSH 才需要升级 DSH 或重启服务。其他插件是否能即时更新仍以 DSH 返回的结果为准。
179
185
 
180
186
  ### 方式二:GitHub 链接安装
181
187
 
package/cordis.patch.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  - insert:
2
- - id: llm-multi-router-v2
3
- name: dsh-multi-router
2
+ - id: llm-multi-router-v2-0.5.7
3
+ name: ./lib/host-v0.5.7.js
@@ -39,7 +39,7 @@ import {
39
39
  import { createHash, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
40
40
 
41
41
  // package.json
42
- var version = "0.5.6";
42
+ var version = "0.5.7";
43
43
 
44
44
  // src/manager.mjs
45
45
  import { readFile as readFile3, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
@@ -417,7 +417,7 @@ async function refreshGitPlugin(service, profile, row, options2, signal) {
417
417
  }, { waitMs: 12e4 });
418
418
  }
419
419
  var PluginUpdates = class {
420
- constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin, stateFile, now = Date.now } = {}) {
420
+ constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin, onInstalled, stateFile, now = Date.now } = {}) {
421
421
  this.stateFile = stateFile;
422
422
  this.now = now;
423
423
  this.dismissed = {};
@@ -430,6 +430,7 @@ var PluginUpdates = class {
430
430
  this.abort = new AbortController();
431
431
  this.lookup = lookup;
432
432
  this.refreshGit = refreshGit;
433
+ this.onInstalled = onInstalled;
433
434
  }
434
435
  async load() {
435
436
  const saved = this.stateFile && await jsonFile(this.stateFile);
@@ -461,6 +462,7 @@ var PluginUpdates = class {
461
462
  async tick() {
462
463
  if (this.flight || !this.state().available) return;
463
464
  await this.list();
465
+ if (this.abort.signal.aborted) return;
464
466
  if (this.flight || this.now() >= this.lastAttempt && this.now() - this.lastAttempt < 864e5) return;
465
467
  this.lastAttempt = this.now();
466
468
  await this.persist();
@@ -616,7 +618,8 @@ var PluginUpdates = class {
616
618
  message: "\u5B9E\u9645 Git \u63D0\u4EA4\u4E0E\u68C0\u67E5\u7ED3\u679C\u4E0D\u4E00\u81F4\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u540E\u91CD\u8BD5\uFF1B\u4E0D\u628A\u4ECD\u505C\u7559\u5728\u65E7\u9501\u6587\u4EF6\u7684\u5B89\u88C5\u62A5\u544A\u4E3A\u6210\u529F"
617
619
  };
618
620
  }
619
- return { name: row.name, status: "installed", application: result.application, message: result.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF0C\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u65B0\u6253\u5F00 DSH \u540E\u751F\u6548" : "\u5B89\u88C5\u5B8C\u6210", restartRequired: result.application === "restart-required" };
621
+ const reloadPending = result.application === "restart-required" && this.onInstalled?.(row.name) === true;
622
+ return { name: row.name, status: "installed", application: reloadPending ? "reload-pending" : result.application, message: reloadPending ? "\u5DF2\u5B89\u88C5\uFF0C\u7B49\u5F85\u5F53\u524D\u64CD\u4F5C\u7ED3\u675F\u540E\u81EA\u52A8\u52A0\u8F7D\uFF1B\u8FD0\u884C\u7248\u672C\u89C1\u9762\u677F\u9876\u90E8" : result.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF0C\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u65B0\u6253\u5F00 DSH \u540E\u751F\u6548" : "\u5B89\u88C5\u5B8C\u6210", restartRequired: !reloadPending && result.application === "restart-required" };
620
623
  }
621
624
  update(names, registry) {
622
625
  if (!Array.isArray(names) || !names.length || names.length > 200 || names.some((n) => typeof n !== "string") || new Set(names).size !== names.length) throw Error("\u8BF7\u9009\u62E9\u8981\u66F4\u65B0\u7684\u63D2\u4EF6");
@@ -652,13 +655,14 @@ var PluginUpdates = class {
652
655
  job.total = 1;
653
656
  job.current = spec;
654
657
  const result = await this.service().installBundle(spec, { requestId: job.id, ...registry ? { registry } : {} });
658
+ const reloadPending = !result.error && result.packageResult?.exitCode === 0 && result.application === "restart-required" && this.onInstalled?.(result.bundle) === true;
655
659
  job.results.push({
656
660
  name: result.bundle ?? spec,
657
661
  status: result.error || ["failed", "cancelled"].includes(result.application) || result.packageResult && result.packageResult.exitCode !== 0 ? "failed" : "installed",
658
- message: result.error ? `\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${result.error.code}\uFF09\uFF0C\u8BF7\u67E5\u770B DSH \u63D2\u4EF6\u5B89\u88C5\u65E5\u5FD7` : result.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F DSH" : "\u5B89\u88C5\u5B8C\u6210",
659
- application: result.application,
662
+ message: result.error ? `\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${result.error.code}\uFF09\uFF0C\u8BF7\u67E5\u770B DSH \u63D2\u4EF6\u5B89\u88C5\u65E5\u5FD7` : reloadPending ? "\u5DF2\u5B89\u88C5\uFF0C\u7B49\u5F85\u5F53\u524D\u64CD\u4F5C\u7ED3\u675F\u540E\u81EA\u52A8\u52A0\u8F7D" : result.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F DSH" : "\u5B89\u88C5\u5B8C\u6210",
663
+ application: reloadPending ? "reload-pending" : result.application,
660
664
  pendingBuilds: result.pendingBuilds ?? [],
661
- restartRequired: result.application === "restart-required"
665
+ restartRequired: !reloadPending && result.application === "restart-required"
662
666
  });
663
667
  delete job.current;
664
668
  await this.list();
@@ -869,7 +873,11 @@ var Manager = class {
869
873
  this.monitorFlights = /* @__PURE__ */ new Map();
870
874
  this.loginInfo = /* @__PURE__ */ new Map();
871
875
  this.telemetry = new Telemetry(join4(dirname3(file), "usage.json"));
872
- this.plugins = new PluginUpdates({ ...pluginAccess, stateFile: join4(dirname3(file), "plugin-updates.json") });
876
+ this.plugins = new PluginUpdates({ ...pluginAccess, stateFile: join4(dirname3(file), "plugin-updates.json"), onInstalled: (name) => {
877
+ if (name !== "dsh-multi-router" || !this.runtime?.canReload()) return false;
878
+ this.runtime.schedule();
879
+ return true;
880
+ } });
873
881
  this.checkins = new Checkins(join4(dirname3(file), "checkins.json"), this);
874
882
  this.workbuddyLogin = new WorkBuddyLogin(join4(dirname3(file), "workbuddy-login.json"), { fetchImpl: pluginAccess.fetchImpl });
875
883
  this.setupFile = join4(dirname3(file), "initial-setup.json");
@@ -888,9 +896,6 @@ var Manager = class {
888
896
  this.config = validateConfig(this.raw, this.file);
889
897
  await this.checkins.load();
890
898
  await this.plugins.load();
891
- if (this.plugins.state().available) await this.plugins.list().catch(() => {
892
- this.plugins.rows = [];
893
- });
894
899
  try {
895
900
  const saved = JSON.parse(await readFile3(this.catalogFile, "utf8"));
896
901
  for (const account of this.config.accounts) {
@@ -1216,6 +1221,8 @@ var Manager = class {
1216
1221
  const status = this.router.status();
1217
1222
  return {
1218
1223
  pluginVersion: version,
1224
+ startup: this.startup,
1225
+ runtime: this.runtime?.state(),
1219
1226
  revision: this.revision,
1220
1227
  localDiscovery: this.localDiscovery ?? [],
1221
1228
  setup: this.setup,
@@ -1255,6 +1262,12 @@ var Manager = class {
1255
1262
  }
1256
1263
  async action(input) {
1257
1264
  if (!input || typeof input !== "object" || Array.isArray(input)) bad("\u8BF7\u6C42\u683C\u5F0F\u65E0\u6548");
1265
+ if (input.action === "plugin.reload") {
1266
+ if (!this.runtime) bad("\u5F53\u524D\u540E\u53F0\u4E0D\u652F\u6301\u91CD\u65B0\u52A0\u8F7D");
1267
+ this.runtime.retryAt = 0;
1268
+ this.runtime.schedule(0);
1269
+ return this.state();
1270
+ }
1258
1271
  if (input.action === "workbuddy.sms") {
1259
1272
  try {
1260
1273
  const workbuddyLogin = await this.workbuddyLogin.send(input.phone);
@@ -1665,6 +1678,7 @@ function managementHandler(manager) {
1665
1678
  res.end(JSON.stringify(value));
1666
1679
  };
1667
1680
  return async (req, res) => {
1681
+ manager.requests = (manager.requests ?? 0) + 1;
1668
1682
  try {
1669
1683
  const remote = req.socket?.remoteAddress;
1670
1684
  if (!hostIsLoopback(req.headers.host) || remote && !["127.0.0.1", "::1", "::ffff:127.0.0.1"].includes(remote)) throw new ManagementError(403, "\u4EC5\u5141\u8BB8\u672C\u673A\u8BF7\u6C42");
@@ -1697,15 +1711,18 @@ function managementHandler(manager) {
1697
1711
  error instanceof ManagementError ? error.status : 500,
1698
1712
  { error: error instanceof ManagementError ? error.message : "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u914D\u7F6E\u548C\u6765\u6E90\u6587\u4EF6" }
1699
1713
  );
1714
+ } finally {
1715
+ manager.requests--;
1700
1716
  }
1701
1717
  };
1702
1718
  }
1703
1719
 
1704
1720
  export {
1721
+ version,
1705
1722
  MANAGEMENT_PATH,
1706
1723
  ManagementError,
1707
1724
  modelInfo,
1708
1725
  Manager,
1709
1726
  managementHandler
1710
1727
  };
1711
- //# sourceMappingURL=chunk-XKDXPB5T.js.map
1728
+ //# sourceMappingURL=chunk-JDZCWBA4.js.map