dsh-multi-router 0.4.0 → 0.4.1
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 +5 -0
- package/README.md +4 -4
- package/lib/{chunk-UQS7DNOQ.js → chunk-DZYRIZW2.js} +86 -20
- package/lib/chunk-DZYRIZW2.js.map +7 -0
- package/lib/client.js +1 -1
- package/lib/client.js.map +1 -1
- package/lib/host.js +1 -1
- package/lib/index.js +1 -1
- package/lib/manager.js +1 -1
- package/package.json +1 -1
- package/lib/chunk-UQS7DNOQ.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。版本号从 `0.1` 起以功能迭代为主,接口在 `1.0` 前可能变化。
|
|
4
4
|
|
|
5
|
+
## 0.4.1
|
|
6
|
+
|
|
7
|
+
- 修复 GitHub 来源再次安装时 pnpm 复用旧锁文件:改走官方包管理更新路径刷新原分支,保留安装来源与启用状态。
|
|
8
|
+
- 更新完成后核验实际 Git 提交,旧提交或检查结果不一致不会误报成功;更新失败恢复原 manifest / 锁文件,支持取消与 registry 回退。
|
|
9
|
+
|
|
5
10
|
## 0.4.0
|
|
6
11
|
|
|
7
12
|
- 首次启用自动获取并保存 WorkBuddy / Trae 本机登录,首页明确显示检测与保存结果,增加一键重试和下一步引导。
|
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.4.
|
|
9
|
+
版本:0.4.1。适配 DSH Desktop / Web 0.2.0-rc.2,Node.js ^22.19.0 或 >=24。
|
|
10
10
|
|
|
11
11
|
## 在 DSH 里管理
|
|
12
12
|
|
|
@@ -98,7 +98,7 @@ Qoder 的 `dfmodel` 在真实目录里显示为 DeepSeek-Flash,未声明具体
|
|
|
98
98
|
在本插件配置面板打开 **插件更新**,管理当前 DSH 配置中的全部已安装插件,包括其它插件和已停用插件。首次打开自动检查,后续超过 24 小时重新打开时再检查,也可随时点击「检查全部更新」。
|
|
99
99
|
|
|
100
100
|
- **npm 来源**:比较 SemVer,保留 `latest` / `next` 等频道;固定版本和版本范围在用户确认更新后升级到最新正式版本。查询沿用 DSH 的 registry、网络、认证与镜像回退设置。
|
|
101
|
-
- **GitHub 来源**:比较原分支 / 标签的 Git
|
|
101
|
+
- **GitHub 来源**:比较原分支 / 标签的 Git 提交,版本号未变化也能发现更新;安装时使用官方 `pnpm update` 路径刷新原分支,并核验实际锁文件提交,避免 `pnpm add` 复用旧锁文件。支持 `github:用户/仓库`、GitHub HTTPS / SSH 地址及锁文件中的提交;固定 Git 提交不自动解除。GitHub 查询需要能够访问仓库公开元数据,私有仓库或限流会显示原因,不会猜测最新提交。
|
|
102
102
|
- 可按名称、来源和可更新状态筛选;支持单个更新、勾选批量更新、取消以及失败后重试。更新前显示目标,并保留每个插件原有启用 / 停用状态。
|
|
103
103
|
- 可直接输入 npm 包名 / 指定版本,或 `github:用户/仓库` 安装新插件。实际安装由 DSH 官方插件管理服务执行兼容检查、镜像重试、配置锁及失败回滚;依赖构建脚本仍在 DSH 原安装页授权,不自动绕过。
|
|
104
104
|
- 结果显示实际安装是否成功及是否需要重启。npm 镜像尚未同步、网络超时、版本不兼容会明确提示。GitHub 下载不会由 npm 镜像代替。
|
|
@@ -124,7 +124,7 @@ dsh plugin --profile desktop add dsh-multi-router
|
|
|
124
124
|
dsh plugin --profile web add dsh-multi-router
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.4.
|
|
127
|
+
包地址:[dsh-multi-router · npm](https://www.npmjs.com/package/dsh-multi-router)。正式版本由 GitHub Release 自动测试、打包并发布到 npm `latest`,包中已包含编译产物,用户无需自行构建。需要固定版本时可安装 `dsh-multi-router@0.4.1`。
|
|
128
128
|
|
|
129
129
|
已使用 npm 来源的用户更新:
|
|
130
130
|
|
|
@@ -181,7 +181,7 @@ cd dsh-multi-router
|
|
|
181
181
|
npm ci
|
|
182
182
|
npm run check
|
|
183
183
|
npm run pack:release
|
|
184
|
-
dsh plugin --profile desktop add ./dsh-multi-router-0.4.
|
|
184
|
+
dsh plugin --profile desktop add ./dsh-multi-router-0.4.1.tgz
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
### 配置账号
|
|
@@ -34,13 +34,13 @@ import {
|
|
|
34
34
|
import { createHash, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
35
35
|
|
|
36
36
|
// package.json
|
|
37
|
-
var version = "0.4.
|
|
37
|
+
var version = "0.4.1";
|
|
38
38
|
|
|
39
39
|
// src/manager.mjs
|
|
40
|
-
import { readFile as readFile2, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as
|
|
40
|
+
import { readFile as readFile2, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
|
|
41
41
|
import { watch } from "node:fs";
|
|
42
42
|
import { dirname as dirname3, join as join4, relative as relative2, resolve } from "node:path";
|
|
43
|
-
import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
|
|
43
|
+
import { withFileLock as withFileLock2 } from "@deepseek-ai/dsh-atomic-write";
|
|
44
44
|
|
|
45
45
|
// upstream/qoder/src/status-paths.ts
|
|
46
46
|
var QODER_STATUS_PATH = "/plugins/dsh-qoder-connect/status";
|
|
@@ -234,12 +234,13 @@ async function prepareMigration(manager, raw, portable, mode) {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
// src/plugin-updates.mjs
|
|
237
|
-
import { readFile, stat } from "node:fs/promises";
|
|
237
|
+
import { readFile, stat, rm as rm2 } from "node:fs/promises";
|
|
238
238
|
import { join as join3 } from "node:path";
|
|
239
239
|
import { randomUUID } from "node:crypto";
|
|
240
240
|
import { createRequire } from "node:module";
|
|
241
241
|
import { pathToFileURL } from "node:url";
|
|
242
242
|
import { parse as parseYaml } from "yaml";
|
|
243
|
+
import { withFileLock, writeFileAtomic as writeFileAtomic2 } from "@deepseek-ai/dsh-atomic-write";
|
|
243
244
|
|
|
244
245
|
// upstream/workbuddy/src/update.ts
|
|
245
246
|
var WORKBUDDY_REPOSITORY_URL = "https://github.com/corrinehu/dsh-workbuddy-connect";
|
|
@@ -360,14 +361,63 @@ async function registryVersion(service, profile, spec, signal) {
|
|
|
360
361
|
}
|
|
361
362
|
throw Error("npm \u67E5\u8BE2\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5 DSH \u7F51\u7EDC\u3001registry \u8BBE\u7F6E\u6216\u5305\u7684\u8BBF\u95EE\u6743\u9650");
|
|
362
363
|
}
|
|
364
|
+
async function refreshGitPlugin(service, profile, row, options2, signal) {
|
|
365
|
+
const require2 = createRequire(profile.installAnchor);
|
|
366
|
+
const { runProfilePnpm } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/operations")).href);
|
|
367
|
+
const { registryPlan, attributeFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
|
|
368
|
+
const { classifyInstallFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager")).href);
|
|
369
|
+
const plan = registryPlan(options2.registry, await service.registries());
|
|
370
|
+
return withFileLock(join3(profile.dir, "package.json"), async () => {
|
|
371
|
+
const files = await Promise.all(["package.json", "pnpm-lock.yaml"].map(async (name) => {
|
|
372
|
+
const path = join3(profile.dir, name);
|
|
373
|
+
try {
|
|
374
|
+
return { path, content: await readFile(path, "utf8") };
|
|
375
|
+
} catch (e) {
|
|
376
|
+
if (e.code !== "ENOENT") throw e;
|
|
377
|
+
return { path };
|
|
378
|
+
}
|
|
379
|
+
}));
|
|
380
|
+
const restore = () => Promise.all(files.map((f) => f.content === void 0 ? rm2(f.path, { force: true }) : writeFileAtomic2(f.path, f.content, { mode: 384 })));
|
|
381
|
+
if (JSON.parse(files[0].content).dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
|
|
382
|
+
let result;
|
|
383
|
+
try {
|
|
384
|
+
for (const registry of plan) {
|
|
385
|
+
signal.throwIfAborted();
|
|
386
|
+
const run = await runProfilePnpm({ ...profile, profile: profile.name }, ["update", row.name, ...registry ? [`--registry=${registry}`] : []], {
|
|
387
|
+
execution: "service",
|
|
388
|
+
...profile.packageManager,
|
|
389
|
+
signal,
|
|
390
|
+
outputBytes: 16384,
|
|
391
|
+
idleTimeoutMs: 6e5,
|
|
392
|
+
lookupTimeoutMs: 2e4,
|
|
393
|
+
activateNewBundles: false
|
|
394
|
+
});
|
|
395
|
+
if (run.exitCode === 0 && !run.timedOut && !signal.aborted) return { packageResult: run, application: "restart-required" };
|
|
396
|
+
const kind = classifyInstallFailure({ log: run.output, timedOut: run.timedOut });
|
|
397
|
+
result = {
|
|
398
|
+
packageResult: { ...run, kind },
|
|
399
|
+
application: signal.aborted ? "cancelled" : "failed",
|
|
400
|
+
...run.incompatible?.length ? { error: { code: "incompatible-version" } } : {}
|
|
401
|
+
};
|
|
402
|
+
await restore();
|
|
403
|
+
if (signal.aborted || attributeFailure(kind, run.output, { kind: "git", host: "github.com" }) !== "registry") break;
|
|
404
|
+
}
|
|
405
|
+
return result;
|
|
406
|
+
} catch (e) {
|
|
407
|
+
await restore();
|
|
408
|
+
throw e;
|
|
409
|
+
}
|
|
410
|
+
}, { waitMs: 12e4 });
|
|
411
|
+
}
|
|
363
412
|
var PluginUpdates = class {
|
|
364
|
-
constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion } = {}) {
|
|
413
|
+
constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin } = {}) {
|
|
365
414
|
this.service = service;
|
|
366
415
|
this.profile = profile;
|
|
367
416
|
this.fetchImpl = fetchImpl;
|
|
368
417
|
this.rows = [];
|
|
369
418
|
this.abort = new AbortController();
|
|
370
419
|
this.lookup = lookup;
|
|
420
|
+
this.refreshGit = refreshGit;
|
|
371
421
|
}
|
|
372
422
|
state() {
|
|
373
423
|
return {
|
|
@@ -475,11 +525,16 @@ var PluginUpdates = class {
|
|
|
475
525
|
if (original?.dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
|
|
476
526
|
const current = (await this.service().listBundles()).find((b) => b.name === row.name);
|
|
477
527
|
if (!current || current.enabled !== row.checkedEnabled || current.version !== row.version) throw Error("\u63D2\u4EF6\u7248\u672C\u6216\u542F\u7528\u72B6\u6001\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
528
|
+
const options2 = { enabled: row.enabled, requestId: this.job.id, ...registry ? { registry } : {} };
|
|
529
|
+
let result;
|
|
530
|
+
if (row.source.kind === "github") {
|
|
531
|
+
this.gitAbort = new AbortController();
|
|
532
|
+
try {
|
|
533
|
+
result = await this.refreshGit(this.service(), this.profile(), row, options2, AbortSignal.any([this.abort.signal, this.gitAbort.signal]));
|
|
534
|
+
} finally {
|
|
535
|
+
this.gitAbort = void 0;
|
|
536
|
+
}
|
|
537
|
+
} else result = await this.service().installBundle(row.target, options2);
|
|
483
538
|
if (result.packageResult && result.packageResult.exitCode !== 0 || ["failed", "cancelled"].includes(result.application) || result.error) {
|
|
484
539
|
return {
|
|
485
540
|
name: row.name,
|
|
@@ -496,6 +551,16 @@ var PluginUpdates = class {
|
|
|
496
551
|
status: "failed",
|
|
497
552
|
message: `\u5B9E\u9645\u5B89\u88C5\u7248\u672C ${installed?.version ?? "\u672A\u77E5"} \u4E0E\u68C0\u67E5\u7248\u672C ${row.latestVersion} \u4E0D\u540C\uFF1B\u8BF7\u7A0D\u540E\u91CD\u65B0\u68C0\u67E5\uFF0C\u53EF\u80FD\u662F\u955C\u50CF\u5C1A\u672A\u540C\u6B65\u6216\u53D1\u884C\u65F6\u95F4\u9650\u5236`
|
|
498
553
|
};
|
|
554
|
+
if (row.source.kind === "github") {
|
|
555
|
+
const lock = await locks(this.profile().dir);
|
|
556
|
+
const pnpm = lock.pnpm?.importers?.["."]?.dependencies?.[row.name]?.version, npm = lock.npm?.packages?.[`node_modules/${row.name}`]?.resolved;
|
|
557
|
+
if (githubCommit(lock.preferNpm ? npm ?? pnpm : pnpm ?? npm) !== row.latestCommit) return {
|
|
558
|
+
name: row.name,
|
|
559
|
+
status: "failed",
|
|
560
|
+
restartRequired: true,
|
|
561
|
+
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"
|
|
562
|
+
};
|
|
563
|
+
}
|
|
499
564
|
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" };
|
|
500
565
|
}
|
|
501
566
|
update(names, registry) {
|
|
@@ -555,7 +620,8 @@ var PluginUpdates = class {
|
|
|
555
620
|
else if (input.action === "plugins.cancel") {
|
|
556
621
|
if (this.job?.state === "running") {
|
|
557
622
|
this.job.cancelRequested = true;
|
|
558
|
-
if (this.
|
|
623
|
+
if (this.gitAbort) this.gitAbort.abort();
|
|
624
|
+
else if (this.job.kind !== "check") await this.service().cancelInstall?.(this.job.id);
|
|
559
625
|
}
|
|
560
626
|
} else throw Error("\u672A\u77E5\u63D2\u4EF6\u64CD\u4F5C");
|
|
561
627
|
}
|
|
@@ -668,7 +734,7 @@ var Manager = class {
|
|
|
668
734
|
...discovered.status.map((s) => ({ ...s, checkedAt: Date.now() }))
|
|
669
735
|
];
|
|
670
736
|
if (this.closed || !discovered.accounts.length) return;
|
|
671
|
-
await
|
|
737
|
+
await withFileLock2(this.file, async () => {
|
|
672
738
|
if (this.closed) return;
|
|
673
739
|
let raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
674
740
|
let changed = false;
|
|
@@ -735,7 +801,7 @@ var Manager = class {
|
|
|
735
801
|
this.catalogs.delete(id);
|
|
736
802
|
this.catalogErrors.delete(id);
|
|
737
803
|
} catch (e) {
|
|
738
|
-
await
|
|
804
|
+
await rm3(staging, { recursive: true, force: true });
|
|
739
805
|
this.localDiscovery = this.localDiscovery.map((s) => s.provider === item.provider ? { ...s, state: "unreadable", code: "import_failed" } : s);
|
|
740
806
|
}
|
|
741
807
|
}
|
|
@@ -754,7 +820,7 @@ var Manager = class {
|
|
|
754
820
|
}
|
|
755
821
|
async finishInitialSetup() {
|
|
756
822
|
if (this.setup?.completedAt) return;
|
|
757
|
-
await
|
|
823
|
+
await withFileLock2(this.setupFile, async () => {
|
|
758
824
|
try {
|
|
759
825
|
this.setup = JSON.parse(await readFile2(this.setupFile, "utf8"));
|
|
760
826
|
} catch {
|
|
@@ -938,7 +1004,7 @@ var Manager = class {
|
|
|
938
1004
|
if (this.closed || !this.config.accounts.some((a) => a.id === account.id && a.credentialFile === account.credentialFile)) return;
|
|
939
1005
|
this.catalogs.set(account.id, { credentialFile: account.credentialFile, fetchedAt: Date.now(), pricingVersion: 1, models });
|
|
940
1006
|
this.catalogErrors.delete(account.id);
|
|
941
|
-
await
|
|
1007
|
+
await withFileLock2(this.catalogFile, () => writePrivateJson(this.catalogFile, Object.fromEntries(this.catalogs)));
|
|
942
1008
|
} catch (error) {
|
|
943
1009
|
if (this.closed || !this.config.accounts.some((a) => a.id === account.id)) return;
|
|
944
1010
|
this.catalogErrors.set(account.id, { status: Number(error.status ?? error.failure?.status) || 502, code: "catalog_fetch_failed", at: Date.now() });
|
|
@@ -999,7 +1065,7 @@ var Manager = class {
|
|
|
999
1065
|
return this.state();
|
|
1000
1066
|
}
|
|
1001
1067
|
if (input.action === "setup.dismiss") {
|
|
1002
|
-
await
|
|
1068
|
+
await withFileLock2(this.setupFile, async () => {
|
|
1003
1069
|
this.setup = { ...this.setup, completedAt: this.setup?.completedAt ?? Date.now(), dismissed: true };
|
|
1004
1070
|
await writePrivateJson(this.setupFile, this.setup);
|
|
1005
1071
|
});
|
|
@@ -1075,7 +1141,7 @@ var Manager = class {
|
|
|
1075
1141
|
await this.checkFreeModels(true);
|
|
1076
1142
|
return this.state();
|
|
1077
1143
|
}
|
|
1078
|
-
return
|
|
1144
|
+
return withFileLock2(this.file, async () => {
|
|
1079
1145
|
let raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
1080
1146
|
if (input.revision !== revisionOf(raw)) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u4FDD\u5B58");
|
|
1081
1147
|
raw.routes ??= structuredClone(this.config.routes);
|
|
@@ -1262,7 +1328,7 @@ var Manager = class {
|
|
|
1262
1328
|
this.publish();
|
|
1263
1329
|
await this.telemetry.persist();
|
|
1264
1330
|
} finally {
|
|
1265
|
-
if (staging) await
|
|
1331
|
+
if (staging) await rm3(staging, { recursive: true, force: true });
|
|
1266
1332
|
}
|
|
1267
1333
|
return this.state();
|
|
1268
1334
|
}, { waitMs: 3e4 }).then(async (state) => {
|
|
@@ -1290,7 +1356,7 @@ var Manager = class {
|
|
|
1290
1356
|
if (filename && String(filename) !== this.file.split(/[\\/]/).pop()) return;
|
|
1291
1357
|
clearTimeout(timer);
|
|
1292
1358
|
timer = setTimeout(() => {
|
|
1293
|
-
void
|
|
1359
|
+
void withFileLock2(this.file, async () => {
|
|
1294
1360
|
const raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
1295
1361
|
if (this.closed || revisionOf(raw) === this.revision) return;
|
|
1296
1362
|
const config = validateConfig(raw, this.file);
|
|
@@ -1358,4 +1424,4 @@ export {
|
|
|
1358
1424
|
Manager,
|
|
1359
1425
|
managementHandler
|
|
1360
1426
|
};
|
|
1361
|
-
//# sourceMappingURL=chunk-
|
|
1427
|
+
//# sourceMappingURL=chunk-DZYRIZW2.js.map
|