dsh-multi-router 0.3.7 → 0.4.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/CHANGELOG.md +79 -0
- package/README.md +69 -9
- package/lib/accounts.js +7 -3
- package/lib/{chunk-IEN3ZQFS.js → chunk-3VGKH7MF.js} +75 -6
- package/lib/chunk-3VGKH7MF.js.map +7 -0
- package/lib/{chunk-WNRHV24A.js → chunk-FTM7RHQV.js} +1 -6
- package/lib/{chunk-WNRHV24A.js.map → chunk-FTM7RHQV.js.map} +2 -2
- package/lib/{chunk-3VVQXFPV.js → chunk-GGRCX2NR.js} +2 -2
- package/lib/{chunk-VESY2B5O.js → chunk-UFS7ZJKY.js} +3 -3
- package/lib/{chunk-BIEDP6NW.js → chunk-UQS7DNOQ.js} +525 -49
- package/lib/chunk-UQS7DNOQ.js.map +7 -0
- package/lib/{chunk-HZVBEBOD.js → chunk-XMXFIV5G.js} +2 -1
- package/lib/{chunk-HZVBEBOD.js.map → chunk-XMXFIV5G.js.map} +2 -2
- package/lib/{chunk-PD2DTHG2.js → chunk-XY2UB22K.js} +3 -3
- package/lib/cli.js +5 -5
- package/lib/client.js +269 -18
- package/lib/client.js.map +3 -3
- package/lib/clients.js +4 -4
- package/lib/host.js +14 -10
- package/lib/index.js +14 -10
- package/lib/index.js.map +2 -2
- package/lib/manager.js +6 -6
- package/lib/router.js +2 -2
- package/lib/shim.js +3 -3
- package/package.json +27 -15
- package/lib/chunk-BIEDP6NW.js.map +0 -7
- package/lib/chunk-IEN3ZQFS.js.map +0 -7
- /package/lib/{chunk-3VVQXFPV.js.map → chunk-GGRCX2NR.js.map} +0 -0
- /package/lib/{chunk-VESY2B5O.js.map → chunk-UFS7ZJKY.js.map} +0 -0
- /package/lib/{chunk-PD2DTHG2.js.map → chunk-XY2UB22K.js.map} +0 -0
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
jsonCredential,
|
|
3
3
|
qoderPluginDataDir
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UFS7ZJKY.js";
|
|
5
5
|
import {
|
|
6
|
+
discoverLocalAccounts,
|
|
6
7
|
importAccount,
|
|
8
|
+
localAccountKey,
|
|
7
9
|
readPortableCredential
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import {
|
|
10
|
-
WORKBUDDY_VARIANTS,
|
|
11
|
-
defaultDesktopAuthPath,
|
|
12
|
-
traeStorageCandidates
|
|
13
|
-
} from "./chunk-WNRHV24A.js";
|
|
10
|
+
} from "./chunk-3VGKH7MF.js";
|
|
14
11
|
import {
|
|
15
12
|
hostIsLoopback
|
|
16
13
|
} from "./chunk-DNQPZMPF.js";
|
|
17
14
|
import {
|
|
18
15
|
Telemetry,
|
|
19
16
|
matchModel
|
|
20
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-GGRCX2NR.js";
|
|
21
18
|
import {
|
|
22
19
|
PROVIDERS,
|
|
23
20
|
PROVIDER_NAMES,
|
|
@@ -28,16 +25,21 @@ import {
|
|
|
28
25
|
routeReasoning,
|
|
29
26
|
validateConfig,
|
|
30
27
|
writePrivateJson
|
|
31
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-XMXFIV5G.js";
|
|
32
29
|
import {
|
|
33
30
|
modelWithCurrentPromotion
|
|
34
31
|
} from "./chunk-TEZ2FMEO.js";
|
|
35
32
|
|
|
36
33
|
// src/manager.mjs
|
|
37
34
|
import { createHash, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
38
|
-
|
|
35
|
+
|
|
36
|
+
// package.json
|
|
37
|
+
var version = "0.4.0";
|
|
38
|
+
|
|
39
|
+
// src/manager.mjs
|
|
40
|
+
import { readFile as readFile2, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
|
|
39
41
|
import { watch } from "node:fs";
|
|
40
|
-
import { dirname as dirname3, join as
|
|
42
|
+
import { dirname as dirname3, join as join4, relative as relative2, resolve } from "node:path";
|
|
41
43
|
import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
|
|
42
44
|
|
|
43
45
|
// upstream/qoder/src/status-paths.ts
|
|
@@ -231,6 +233,334 @@ async function prepareMigration(manager, raw, portable, mode) {
|
|
|
231
233
|
}
|
|
232
234
|
}
|
|
233
235
|
|
|
236
|
+
// src/plugin-updates.mjs
|
|
237
|
+
import { readFile, stat } from "node:fs/promises";
|
|
238
|
+
import { join as join3 } from "node:path";
|
|
239
|
+
import { randomUUID } from "node:crypto";
|
|
240
|
+
import { createRequire } from "node:module";
|
|
241
|
+
import { pathToFileURL } from "node:url";
|
|
242
|
+
import { parse as parseYaml } from "yaml";
|
|
243
|
+
|
|
244
|
+
// upstream/workbuddy/src/update.ts
|
|
245
|
+
var WORKBUDDY_REPOSITORY_URL = "https://github.com/corrinehu/dsh-workbuddy-connect";
|
|
246
|
+
var WORKBUDDY_UPDATE_PACKAGE_NAME = "dsh-workbuddy-connect";
|
|
247
|
+
var WORKBUDDY_UPDATE_NPM_METADATA_URL = `https://registry.npmjs.org/-/package/${WORKBUDDY_UPDATE_PACKAGE_NAME}/dist-tags`;
|
|
248
|
+
var WORKBUDDY_UPDATE_RELEASES_API_URL = `${WORKBUDDY_REPOSITORY_URL.replace("https://github.com", "https://api.github.com/repos")}/releases?per_page=100`;
|
|
249
|
+
var WORKBUDDY_RELEASE_PAGE_BASE = `${WORKBUDDY_REPOSITORY_URL}/releases/tag/`;
|
|
250
|
+
var WORKBUDDY_UPDATE_MAX_METADATA_BYTES = 64 * 1024;
|
|
251
|
+
var WORKBUDDY_UPDATE_MAX_RELEASES_BYTES = 1024 * 1024;
|
|
252
|
+
function parseWorkBuddyVersion(raw) {
|
|
253
|
+
if (typeof raw !== "string") return void 0;
|
|
254
|
+
const normalized = raw.startsWith("v") ? raw.slice(1) : raw;
|
|
255
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.exec(normalized);
|
|
256
|
+
if (match === null) return void 0;
|
|
257
|
+
const rawPrerelease = match[4] === void 0 ? [] : match[4].split(".");
|
|
258
|
+
if (rawPrerelease.some((identifier) => /^\d+$/u.test(identifier) && !/^(0|[1-9]\d*)$/u.test(identifier))) return void 0;
|
|
259
|
+
const prerelease = rawPrerelease.map((identifier) => /^(0|[1-9]\d*)$/u.test(identifier) ? Number(identifier) : identifier);
|
|
260
|
+
if (prerelease.some((identifier) => typeof identifier === "number" && !Number.isSafeInteger(identifier))) return void 0;
|
|
261
|
+
const parsed = {
|
|
262
|
+
major: Number(match[1]),
|
|
263
|
+
minor: Number(match[2]),
|
|
264
|
+
patch: Number(match[3]),
|
|
265
|
+
prerelease
|
|
266
|
+
};
|
|
267
|
+
return [parsed.major, parsed.minor, parsed.patch].every(Number.isSafeInteger) ? parsed : void 0;
|
|
268
|
+
}
|
|
269
|
+
function compareIdentifiers(left, right) {
|
|
270
|
+
if (typeof left === "number" && typeof right === "number") return left < right ? -1 : left > right ? 1 : 0;
|
|
271
|
+
if (typeof left === "number") return -1;
|
|
272
|
+
if (typeof right === "number") return 1;
|
|
273
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
274
|
+
}
|
|
275
|
+
function compareWorkBuddyVersions(left, right) {
|
|
276
|
+
const a = parseWorkBuddyVersion(left);
|
|
277
|
+
const b = parseWorkBuddyVersion(right);
|
|
278
|
+
if (a === void 0 || b === void 0) throw new TypeError("invalid WorkBuddy Connect version");
|
|
279
|
+
for (const [aPart, bPart] of [[a.major, b.major], [a.minor, b.minor], [a.patch, b.patch]]) {
|
|
280
|
+
if (aPart !== bPart) return aPart < bPart ? -1 : 1;
|
|
281
|
+
}
|
|
282
|
+
if (a.prerelease.length === 0 && b.prerelease.length !== 0) return 1;
|
|
283
|
+
if (a.prerelease.length !== 0 && b.prerelease.length === 0) return -1;
|
|
284
|
+
for (let index = 0; index < Math.max(a.prerelease.length, b.prerelease.length); index += 1) {
|
|
285
|
+
const aPart = a.prerelease[index];
|
|
286
|
+
const bPart = b.prerelease[index];
|
|
287
|
+
if (aPart === void 0) return -1;
|
|
288
|
+
if (bPart === void 0) return 1;
|
|
289
|
+
const comparison = compareIdentifiers(aPart, bPart);
|
|
290
|
+
if (comparison !== 0) return comparison;
|
|
291
|
+
}
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/plugin-updates.mjs
|
|
296
|
+
var packageName = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
|
|
297
|
+
var safeRef = /^[a-zA-Z0-9._/-]{1,200}$/;
|
|
298
|
+
function parsePluginSource(name, spec) {
|
|
299
|
+
if (!packageName.test(name) || typeof spec !== "string" || spec.length > 600 || spec.startsWith("-") || spec.trim() !== spec || /[\x00-\x1f]/.test(spec)) return { kind: "unsupported" };
|
|
300
|
+
const git = /^(?:github:|git\+https:\/\/github\.com\/|https:\/\/github\.com\/|git\+ssh:\/\/git@github\.com\/|git@github\.com:)?([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:#(.+))?$/.exec(spec);
|
|
301
|
+
if (git && !spec.startsWith("@") && !spec.startsWith("file:") && !spec.startsWith("link:")) {
|
|
302
|
+
const [, owner, repo, ref] = git;
|
|
303
|
+
if (owner === "." || owner === ".." || repo === "." || repo === ".." || ref && (!safeRef.test(ref) || ref.includes(".."))) return { kind: "unsupported" };
|
|
304
|
+
return { kind: "github", owner, repo, ref, source: `github:${owner}/${repo}${ref ? "#" + ref : ""}` };
|
|
305
|
+
}
|
|
306
|
+
if (/^(?:file:|link:|workspace:|https?:|git|\/|\.)/.test(spec)) return { kind: "unsupported" };
|
|
307
|
+
const alias = /^npm:((?:@[^/]+\/)?[^@]+)(?:@(.+))?$/.exec(spec);
|
|
308
|
+
const actual = alias?.[1] ?? name, range = alias?.[2] ?? spec;
|
|
309
|
+
if (!packageName.test(actual) || !/^[a-zA-Z0-9.*^~<>=|+ -]{1,160}$/.test(range)) return { kind: "unsupported" };
|
|
310
|
+
const channel = /^[a-zA-Z][\w.-]*$/.test(range);
|
|
311
|
+
return { kind: "npm", package: actual, alias: !!alias, channel, tag: channel ? range : "latest", source: spec };
|
|
312
|
+
}
|
|
313
|
+
function githubCommit(value) {
|
|
314
|
+
if (typeof value !== "string") return void 0;
|
|
315
|
+
return /(?:\/tar\.gz\/|\/tarball\/|#)([a-f0-9]{40})(?:\b|\()/i.exec(value)?.[1]?.toLowerCase();
|
|
316
|
+
}
|
|
317
|
+
async function jsonFile(file) {
|
|
318
|
+
try {
|
|
319
|
+
return JSON.parse(await readFile(file, "utf8"));
|
|
320
|
+
} catch {
|
|
321
|
+
return void 0;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function locks(dir) {
|
|
325
|
+
let pnpm;
|
|
326
|
+
try {
|
|
327
|
+
pnpm = parseYaml(await readFile(join3(dir, "pnpm-lock.yaml"), "utf8"), { maxAliasCount: 20 });
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
const npm = await jsonFile(join3(dir, "package-lock.json"));
|
|
331
|
+
const times = await Promise.all(["pnpm-lock.yaml", "package-lock.json"].map((name) => stat(join3(dir, name)).then((s) => s.mtimeMs).catch(() => 0)));
|
|
332
|
+
return { pnpm, npm, preferNpm: times[1] > times[0] };
|
|
333
|
+
}
|
|
334
|
+
async function publicJson(url, fetchImpl, signal) {
|
|
335
|
+
const res = await fetchImpl(url, {
|
|
336
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(12e3)]),
|
|
337
|
+
headers: { Accept: "application/json", "User-Agent": "dsh-multi-router-plugin-updates" },
|
|
338
|
+
redirect: "error"
|
|
339
|
+
});
|
|
340
|
+
if (!res.ok) throw Error(res.status === 403 || res.status === 429 ? "\u8FDC\u7AEF\u9650\u6D41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : res.status === 404 ? "\u4ED3\u5E93 / \u7248\u672C\u4E0D\u5B58\u5728\uFF0C\u6216\u9700\u8981\u79C1\u6709\u4ED3\u5E93\u6388\u6743" : `\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${res.status}\uFF09`);
|
|
341
|
+
let size = 0, chunks = [];
|
|
342
|
+
for await (const chunk of res.body) {
|
|
343
|
+
size += chunk.byteLength;
|
|
344
|
+
if (size > 256 * 1024) throw Error("\u66F4\u65B0\u5143\u6570\u636E\u8FC7\u5927");
|
|
345
|
+
chunks.push(Buffer.from(chunk));
|
|
346
|
+
}
|
|
347
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
348
|
+
}
|
|
349
|
+
var short = (sha) => sha?.slice(0, 8);
|
|
350
|
+
async function registryVersion(service, profile, spec, signal) {
|
|
351
|
+
const require2 = createRequire(profile.installAnchor);
|
|
352
|
+
const { viewProfilePackage } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/operations")).href);
|
|
353
|
+
const { registryPlan } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
|
|
354
|
+
for (const registry of registryPlan(void 0, await service.registries())) {
|
|
355
|
+
const answer = await viewProfilePackage(profile.dir, spec, { ...profile.packageManager, registry, timeoutMs: 12e3, signal });
|
|
356
|
+
if (signal.aborted) throw Error("\u68C0\u67E5\u5DF2\u53D6\u6D88");
|
|
357
|
+
if (answer.exitCode !== 0) continue;
|
|
358
|
+
const parsed = JSON.parse(answer.stdout), metadata = Array.isArray(parsed) ? parsed.at(-1) : parsed;
|
|
359
|
+
if (metadata?.version) return metadata;
|
|
360
|
+
}
|
|
361
|
+
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
|
+
var PluginUpdates = class {
|
|
364
|
+
constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion } = {}) {
|
|
365
|
+
this.service = service;
|
|
366
|
+
this.profile = profile;
|
|
367
|
+
this.fetchImpl = fetchImpl;
|
|
368
|
+
this.rows = [];
|
|
369
|
+
this.abort = new AbortController();
|
|
370
|
+
this.lookup = lookup;
|
|
371
|
+
}
|
|
372
|
+
state() {
|
|
373
|
+
return {
|
|
374
|
+
available: !!this.service()?.listBundles && !!this.profile()?.dir,
|
|
375
|
+
checkedAt: this.checkedAt,
|
|
376
|
+
rows: this.rows.map(({ spec, ...row }) => ({ ...row, source: row.source.kind === "unsupported" ? { kind: "unsupported" } : row.source })),
|
|
377
|
+
job: this.job,
|
|
378
|
+
error: this.error
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
async list() {
|
|
382
|
+
const service = this.service(), profile = this.profile();
|
|
383
|
+
if (!service?.listBundles || !profile?.dir) {
|
|
384
|
+
this.rows = [];
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const [manifest, bundles, lock] = await Promise.all([jsonFile(join3(profile.dir, "package.json")), service.listBundles(), locks(profile.dir)]);
|
|
388
|
+
if (!manifest) throw Error("\u5F53\u524D DSH \u914D\u7F6E\u7684\u63D2\u4EF6\u6E05\u5355\u65E0\u6CD5\u8BFB\u53D6");
|
|
389
|
+
this.rows = bundles.filter((b) => b.installed || b.version).map((b) => {
|
|
390
|
+
const spec = manifest.dependencies?.[b.name];
|
|
391
|
+
const source = !spec || b.removable === false && !b.readOnlyReason ? { kind: "builtin" } : parsePluginSource(b.name, spec);
|
|
392
|
+
const npm = lock.npm?.packages?.[`node_modules/${b.name}`]?.resolved;
|
|
393
|
+
const pnpm = lock.pnpm?.importers?.["."]?.dependencies?.[b.name]?.version;
|
|
394
|
+
const installed = lock.preferNpm ? npm ?? pnpm : pnpm ?? npm;
|
|
395
|
+
const previous = this.rows.find((r) => r.name === b.name && r.spec === spec && r.version === b.version && r.commit === githubCommit(installed));
|
|
396
|
+
return {
|
|
397
|
+
...previous,
|
|
398
|
+
name: b.name,
|
|
399
|
+
version: b.version,
|
|
400
|
+
enabled: b.enabled,
|
|
401
|
+
spec,
|
|
402
|
+
source,
|
|
403
|
+
commit: githubCommit(installed),
|
|
404
|
+
status: previous?.status ?? (source.kind === "builtin" ? "builtin" : source.kind === "unsupported" ? "unsupported" : "unchecked"),
|
|
405
|
+
...source.kind === "builtin" ? { message: "DSH \u81EA\u5E26 / \u53D7\u4FDD\u62A4\u7EC4\u4EF6\uFF0C\u8BF7\u901A\u8FC7\u66F4\u65B0 DeepSeek Harness \u5347\u7EA7" } : {},
|
|
406
|
+
...source.kind === "unsupported" ? { message: "\u672C\u5730\u6587\u4EF6 / \u94FE\u63A5\u6765\u6E90\uFF0C\u8BF7\u901A\u8FC7\u539F\u5B89\u88C5\u65B9\u5F0F\u66F4\u65B0" } : {}
|
|
407
|
+
};
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
async checkRow(row) {
|
|
411
|
+
try {
|
|
412
|
+
if (row.source.kind === "npm") {
|
|
413
|
+
const spec = `${row.source.package}@${row.source.tag}`;
|
|
414
|
+
const found = await this.lookup(this.service(), this.profile(), spec, this.abort.signal);
|
|
415
|
+
if (!found.version || found.problem && found.problem !== "already-installed") throw Error(found.problem === "not-found" ? "npm \u4E2D\u672A\u627E\u5230\u8BE5\u5305\u6216\u9891\u9053" : "npm \u67E5\u8BE2\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5 DSH \u7684\u7F51\u7EDC\u4E0E registry \u8BBE\u7F6E");
|
|
416
|
+
if (!parseWorkBuddyVersion(found.version) || row.version && !parseWorkBuddyVersion(row.version)) throw Error("\u65E0\u6CD5\u6BD4\u8F83\u7248\u672C\u53F7");
|
|
417
|
+
row.latestVersion = found.version;
|
|
418
|
+
row.target = `${row.name}@${row.source.alias ? `npm:${row.source.package}@` : ""}${row.source.channel ? row.source.tag : found.version}`;
|
|
419
|
+
row.status = !row.version || compareWorkBuddyVersions(found.version, row.version) > 0 ? "available" : "current";
|
|
420
|
+
row.message = !row.version ? "\u5B89\u88C5\u6587\u4EF6\u4E0D\u53EF\u8BFB\uFF0C\u53EF\u91CD\u65B0\u5B89\u88C5\u8BE5\u6765\u6E90\u4FEE\u590D" : row.status === "current" ? "\u5F53\u524D\u7248\u672C\u5DF2\u662F\u8BE5\u9891\u9053\u6700\u65B0\uFF0C\u6216\u9AD8\u4E8E\u8BE5\u9891\u9053" : void 0;
|
|
421
|
+
} else if (row.source.kind === "github") {
|
|
422
|
+
const { owner, repo, ref } = row.source;
|
|
423
|
+
const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(ref ?? "HEAD")}`;
|
|
424
|
+
const found = await publicJson(url, this.fetchImpl, this.abort.signal);
|
|
425
|
+
if (!/^[a-f0-9]{40}$/i.test(found.sha)) throw Error("GitHub \u8FD4\u56DE\u65E0\u6548\u63D0\u4EA4\u4FE1\u606F");
|
|
426
|
+
row.latestCommit = found.sha.toLowerCase();
|
|
427
|
+
row.target = `${row.name}@${row.spec}`;
|
|
428
|
+
row.status = row.commit === row.latestCommit ? "current" : "available";
|
|
429
|
+
row.message = row.commit ? row.status === "available" ? `\u63D0\u4EA4 ${short(row.commit)} \u2192 ${short(row.latestCommit)}` : "\u8BE5\u5206\u652F / \u6807\u7B7E\u7684\u63D0\u4EA4\u5DF2\u540C\u6B65" : "\u65E0\u6CD5\u8BFB\u53D6\u672C\u5730 Git \u63D0\u4EA4\uFF0C\u53EF\u91CD\u65B0\u5B89\u88C5\u5DF2\u68C0\u67E5\u7684\u6765\u6E90";
|
|
430
|
+
if (ref && /^[a-f0-9]{7,40}$/i.test(ref)) {
|
|
431
|
+
row.status = "pinned";
|
|
432
|
+
row.message = "\u5B89\u88C5\u6765\u6E90\u56FA\u5B9A\u5728 Git \u63D0\u4EA4\uFF1B\u4FDD\u7559\u6B64\u63D0\u4EA4\uFF0C\u4E0D\u81EA\u52A8\u5207\u6362\u5206\u652F";
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
row.checkedAt = Date.now();
|
|
436
|
+
row.checkedEnabled = row.enabled;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
row.status = "failed";
|
|
439
|
+
row.message = error.message;
|
|
440
|
+
delete row.target;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
start(kind, task) {
|
|
444
|
+
if (this.flight) throw Error("\u5DF2\u6709\u63D2\u4EF6\u64CD\u4F5C\u6B63\u5728\u6267\u884C\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210");
|
|
445
|
+
this.job = { id: randomUUID(), kind, state: "running", startedAt: Date.now(), results: [], total: 0 };
|
|
446
|
+
this.error = void 0;
|
|
447
|
+
this.flight = Promise.resolve().then(() => task(this.job)).then(() => {
|
|
448
|
+
this.job.state = this.job.cancelRequested ? "cancelled" : "done";
|
|
449
|
+
}, (e) => {
|
|
450
|
+
this.job.state = this.job.cancelRequested ? "cancelled" : "failed";
|
|
451
|
+
this.job.message = e.message;
|
|
452
|
+
}).finally(() => {
|
|
453
|
+
this.job.finishedAt = Date.now();
|
|
454
|
+
this.flight = void 0;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
check() {
|
|
458
|
+
this.start("check", async (job) => {
|
|
459
|
+
await this.list();
|
|
460
|
+
const rows = this.rows.filter((r) => ["npm", "github"].includes(r.source.kind));
|
|
461
|
+
job.total = rows.length;
|
|
462
|
+
let next = 0;
|
|
463
|
+
await Promise.all(Array.from({ length: Math.min(3, rows.length) }, async () => {
|
|
464
|
+
while (next < rows.length && !job.cancelRequested) {
|
|
465
|
+
const row = rows[next++];
|
|
466
|
+
await this.checkRow(row);
|
|
467
|
+
job.results.push({ name: row.name, status: row.status, message: row.message });
|
|
468
|
+
}
|
|
469
|
+
}));
|
|
470
|
+
this.checkedAt = Date.now();
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
async installRow(row, registry) {
|
|
474
|
+
const original = await jsonFile(join3(this.profile().dir, "package.json"));
|
|
475
|
+
if (original?.dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
|
|
476
|
+
const current = (await this.service().listBundles()).find((b) => b.name === row.name);
|
|
477
|
+
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 result = await this.service().installBundle(row.target, {
|
|
479
|
+
enabled: row.enabled,
|
|
480
|
+
requestId: this.job.id,
|
|
481
|
+
...registry ? { registry } : {}
|
|
482
|
+
});
|
|
483
|
+
if (result.packageResult && result.packageResult.exitCode !== 0 || ["failed", "cancelled"].includes(result.application) || result.error) {
|
|
484
|
+
return {
|
|
485
|
+
name: row.name,
|
|
486
|
+
status: "failed",
|
|
487
|
+
application: result.application,
|
|
488
|
+
message: result.pendingBuilds?.length ? "\u4F9D\u8D56\u6784\u5EFA\u811A\u672C\u9700\u8981\u6388\u6743\uFF0C\u8BF7\u5230 DSH \u63D2\u4EF6\u5B89\u88C5\u9875\u5141\u8BB8\u8FD9\u4E9B\u811A\u672C\u540E\u91CD\u8BD5" : result.error?.code === "incompatible-version" ? "\u63D2\u4EF6\u4E0E\u5F53\u524D DSH \u4E0D\u517C\u5BB9\uFF0C\u8BF7\u5148\u5347\u7EA7 DSH \u6216\u9009\u62E9\u517C\u5BB9\u7248\u672C" : `\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${result.error?.code ?? result.packageResult?.kind ?? "unknown"}\uFF09\uFF1B\u8BF7\u5230 DSH \u63D2\u4EF6\u9875\u67E5\u770B\u5B89\u88C5\u65E5\u5FD7`,
|
|
489
|
+
pendingBuilds: result.pendingBuilds ?? [],
|
|
490
|
+
logPath: result.packageResult?.logPath
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const installed = (await this.service().listBundles()).find((b) => b.name === row.name);
|
|
494
|
+
if (row.source.kind === "npm" && installed?.version !== row.latestVersion) return {
|
|
495
|
+
name: row.name,
|
|
496
|
+
status: "failed",
|
|
497
|
+
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
|
+
};
|
|
499
|
+
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
|
+
}
|
|
501
|
+
update(names, registry) {
|
|
502
|
+
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");
|
|
503
|
+
const selected = names.map((name) => this.rows.find((r) => r.name === name));
|
|
504
|
+
if (selected.some((r) => !r || !["available", "failed"].includes(r.status) || !r.target || Date.now() - r.checkedAt > 6e5)) throw Error("\u8BF7\u5148\u68C0\u67E5\u66F4\u65B0\uFF0C\u518D\u9009\u62E9\u53EF\u66F4\u65B0\u7684\u63D2\u4EF6\uFF08\u68C0\u67E5\u7ED3\u679C\u6709\u6548\u671F 10 \u5206\u949F\uFF09");
|
|
505
|
+
selected.sort((a, b) => Number(a.name === "dsh-multi-router") - Number(b.name === "dsh-multi-router"));
|
|
506
|
+
this.start("update", async (job) => {
|
|
507
|
+
job.total = selected.length;
|
|
508
|
+
for (const row of selected) {
|
|
509
|
+
if (job.cancelRequested) break;
|
|
510
|
+
job.current = row.name;
|
|
511
|
+
let result;
|
|
512
|
+
try {
|
|
513
|
+
result = await this.installRow(row, registry);
|
|
514
|
+
} catch (e) {
|
|
515
|
+
result = { name: row.name, status: "failed", message: e.message };
|
|
516
|
+
}
|
|
517
|
+
job.results.push(result);
|
|
518
|
+
row.status = result.status;
|
|
519
|
+
row.message = result.message;
|
|
520
|
+
}
|
|
521
|
+
delete job.current;
|
|
522
|
+
await this.list();
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
install(spec, registry) {
|
|
526
|
+
if (typeof spec !== "string" || spec.length > 600 || spec.startsWith("-") || /[\s\x00-\x1f]/.test(spec)) throw Error("\u8BF7\u8F93\u5165\u4E00\u4E2A npm \u5305\u540D\u6216 GitHub \u5B89\u88C5\u5730\u5740");
|
|
527
|
+
const named = /^((?:@[^/]+\/)?[^@]+)(?:@(.+))?$/.exec(spec);
|
|
528
|
+
const github = parsePluginSource("placeholder", spec);
|
|
529
|
+
const npm = named && parsePluginSource(named[1], named[2] || "latest");
|
|
530
|
+
if (github.kind !== "github" && npm?.kind !== "npm") throw Error("\u4EC5\u652F\u6301 npm \u5305\u540D / \u7248\u672C\u6216 github:\u7528\u6237/\u4ED3\u5E93");
|
|
531
|
+
this.start("install", async (job) => {
|
|
532
|
+
job.total = 1;
|
|
533
|
+
job.current = spec;
|
|
534
|
+
const result = await this.service().installBundle(spec, { requestId: job.id, ...registry ? { registry } : {} });
|
|
535
|
+
job.results.push({
|
|
536
|
+
name: result.bundle ?? spec,
|
|
537
|
+
status: result.error || ["failed", "cancelled"].includes(result.application) || result.packageResult && result.packageResult.exitCode !== 0 ? "failed" : "installed",
|
|
538
|
+
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",
|
|
539
|
+
application: result.application,
|
|
540
|
+
pendingBuilds: result.pendingBuilds ?? [],
|
|
541
|
+
restartRequired: result.application === "restart-required"
|
|
542
|
+
});
|
|
543
|
+
delete job.current;
|
|
544
|
+
await this.list();
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
async action(input) {
|
|
548
|
+
if (!this.state().available) throw Error("\u5F53\u524D DSH \u672A\u63D0\u4F9B\u63D2\u4EF6\u7BA1\u7406\u63A5\u53E3\uFF0C\u8BF7\u5347\u7EA7 DSH \u540E\u91CD\u8BD5");
|
|
549
|
+
if (input.registry && !["https://registry.npmjs.org/", "https://registry.npmmirror.com/"].includes(input.registry)) throw Error("\u65E0\u6548\u7684 npm \u6765\u6E90");
|
|
550
|
+
if (input.action === "plugins.list") {
|
|
551
|
+
if (!this.flight) await this.list();
|
|
552
|
+
} else if (input.action === "plugins.check") this.check();
|
|
553
|
+
else if (input.action === "plugins.update") this.update(input.names, input.registry);
|
|
554
|
+
else if (input.action === "plugins.install") this.install(input.spec, input.registry);
|
|
555
|
+
else if (input.action === "plugins.cancel") {
|
|
556
|
+
if (this.job?.state === "running") {
|
|
557
|
+
this.job.cancelRequested = true;
|
|
558
|
+
if (this.job.kind !== "check") await this.service().cancelInstall?.(this.job.id);
|
|
559
|
+
}
|
|
560
|
+
} else throw Error("\u672A\u77E5\u63D2\u4EF6\u64CD\u4F5C");
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
|
|
234
564
|
// src/manager.mjs
|
|
235
565
|
var MANAGEMENT_PATH = "/.well-known/dsh-multi-router";
|
|
236
566
|
var revisionOf = (raw) => createHash("sha256").update(JSON.stringify(raw)).digest("hex");
|
|
@@ -270,28 +600,35 @@ function modelInfo(row) {
|
|
|
270
600
|
}
|
|
271
601
|
var Manager = class {
|
|
272
602
|
constructor(file, router, onChange = () => {
|
|
273
|
-
}) {
|
|
603
|
+
}, pluginAccess = {}) {
|
|
274
604
|
this.file = file;
|
|
275
605
|
this.router = router;
|
|
276
606
|
this.onChange = onChange;
|
|
277
|
-
this.catalogFile =
|
|
607
|
+
this.catalogFile = join4(dirname3(file), "catalogs.json");
|
|
278
608
|
this.catalogs = /* @__PURE__ */ new Map();
|
|
279
609
|
this.catalogErrors = /* @__PURE__ */ new Map();
|
|
280
610
|
this.refreshes = /* @__PURE__ */ new Map();
|
|
281
611
|
this.closed = false;
|
|
282
612
|
this.monitorFlights = /* @__PURE__ */ new Map();
|
|
283
613
|
this.loginInfo = /* @__PURE__ */ new Map();
|
|
284
|
-
this.telemetry = new Telemetry(
|
|
614
|
+
this.telemetry = new Telemetry(join4(dirname3(file), "usage.json"));
|
|
615
|
+
this.plugins = new PluginUpdates(pluginAccess);
|
|
616
|
+
this.setupFile = join4(dirname3(file), "initial-setup.json");
|
|
285
617
|
router.telemetry = this.telemetry;
|
|
286
618
|
router.monitor = (a) => this.monitorAccount(a);
|
|
287
619
|
router.prepareFree = () => this.checkFreeModels(true);
|
|
288
620
|
router.freeModels = (account) => this.freeModels(account);
|
|
289
621
|
}
|
|
290
622
|
async load() {
|
|
291
|
-
this.raw = JSON.parse(await
|
|
623
|
+
this.raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
624
|
+
try {
|
|
625
|
+
this.setup = JSON.parse(await readFile2(this.setupFile, "utf8"));
|
|
626
|
+
} catch {
|
|
627
|
+
this.setup = void 0;
|
|
628
|
+
}
|
|
292
629
|
this.config = validateConfig(this.raw, this.file);
|
|
293
630
|
try {
|
|
294
|
-
const saved = JSON.parse(await
|
|
631
|
+
const saved = JSON.parse(await readFile2(this.catalogFile, "utf8"));
|
|
295
632
|
for (const account of this.config.accounts) {
|
|
296
633
|
const item = saved[account.id];
|
|
297
634
|
if (item?.credentialFile === account.credentialFile && Array.isArray(item.models)) {
|
|
@@ -322,6 +659,111 @@ var Manager = class {
|
|
|
322
659
|
this.loginInfo.set(account.id, { state: "missing" });
|
|
323
660
|
}
|
|
324
661
|
}
|
|
662
|
+
async syncLocalAccounts(provider, force = false) {
|
|
663
|
+
if (this.localScan) return this.localScan;
|
|
664
|
+
this.localScan = (async () => {
|
|
665
|
+
const discovered = await discoverLocalAccounts(provider);
|
|
666
|
+
this.localDiscovery = [
|
|
667
|
+
...(this.localDiscovery ?? []).filter((s) => !discovered.status.some((n) => n.provider === s.provider)),
|
|
668
|
+
...discovered.status.map((s) => ({ ...s, checkedAt: Date.now() }))
|
|
669
|
+
];
|
|
670
|
+
if (this.closed || !discovered.accounts.length) return;
|
|
671
|
+
await withFileLock(this.file, async () => {
|
|
672
|
+
if (this.closed) return;
|
|
673
|
+
let raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
674
|
+
let changed = false;
|
|
675
|
+
const saved = /* @__PURE__ */ new Map();
|
|
676
|
+
for (const item of discovered.accounts) {
|
|
677
|
+
const key = localAccountKey(item.provider, item.document);
|
|
678
|
+
if (!force && raw.ignoredLocalAccounts?.includes(key)) continue;
|
|
679
|
+
const config = validateConfig(raw, this.file);
|
|
680
|
+
let existing, current;
|
|
681
|
+
for (const a of config.accounts.filter((a2) => a2.provider === item.provider)) {
|
|
682
|
+
try {
|
|
683
|
+
const value = await jsonCredential(a.credentialFile);
|
|
684
|
+
if (localAccountKey(a.provider, value) === key) {
|
|
685
|
+
existing = a;
|
|
686
|
+
current = value;
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const id = existing?.id ?? `local_${createHash("sha256").update(key).digest("hex").slice(0, 20)}`;
|
|
693
|
+
const recordSaved = () => {
|
|
694
|
+
if (!saved.has(item.provider)) saved.set(item.provider, /* @__PURE__ */ new Set());
|
|
695
|
+
saved.get(item.provider).add(id);
|
|
696
|
+
};
|
|
697
|
+
let sameDevice = true;
|
|
698
|
+
if (existing && item.identityDocument) {
|
|
699
|
+
try {
|
|
700
|
+
const identity = await jsonCredential(existing.identityFile);
|
|
701
|
+
sameDevice = identity.machineId === item.identityDocument.machineId && identity.deviceId === item.identityDocument.deviceId;
|
|
702
|
+
} catch {
|
|
703
|
+
sameDevice = false;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (existing && sameDevice && current.credential.expiresAtMs >= item.document.credential.expiresAtMs) {
|
|
707
|
+
recordSaved();
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
const base = join4(dirname3(this.file), "accounts");
|
|
711
|
+
await mkdir2(base, { recursive: true, mode: 448 });
|
|
712
|
+
const staging = await mkdtemp2(join4(base, `${id}-`));
|
|
713
|
+
try {
|
|
714
|
+
const credentialFile = join4(staging, "credential.json"), identityFile = join4(staging, "identity.json");
|
|
715
|
+
await importAccount({ ...item, credentialFile, identityFile });
|
|
716
|
+
const paths = {
|
|
717
|
+
credentialFile: relative2(dirname3(this.file), credentialFile),
|
|
718
|
+
...item.provider.startsWith("trae") ? { identityFile: relative2(dirname3(this.file), identityFile) } : {}
|
|
719
|
+
};
|
|
720
|
+
const next = structuredClone(raw);
|
|
721
|
+
if (existing) Object.assign(next.accounts.find((a) => a.id === id), paths);
|
|
722
|
+
else next.accounts.push({
|
|
723
|
+
id,
|
|
724
|
+
provider: item.provider,
|
|
725
|
+
name: item.document.credential.nickname || item.document.credential.accountName || item.document.credential.uid || item.document.credential.userId,
|
|
726
|
+
...paths
|
|
727
|
+
});
|
|
728
|
+
next.ignoredLocalAccounts = (raw.ignoredLocalAccounts ?? []).filter((k) => k !== key);
|
|
729
|
+
validateConfig(next, this.file);
|
|
730
|
+
await writePrivateJson(this.file, next);
|
|
731
|
+
raw = next;
|
|
732
|
+
changed = true;
|
|
733
|
+
recordSaved();
|
|
734
|
+
this.router.clients.delete(id);
|
|
735
|
+
this.catalogs.delete(id);
|
|
736
|
+
this.catalogErrors.delete(id);
|
|
737
|
+
} catch (e) {
|
|
738
|
+
await rm2(staging, { recursive: true, force: true });
|
|
739
|
+
this.localDiscovery = this.localDiscovery.map((s) => s.provider === item.provider ? { ...s, state: "unreadable", code: "import_failed" } : s);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (changed) {
|
|
743
|
+
this.raw = raw;
|
|
744
|
+
this.config = validateConfig(raw, this.file);
|
|
745
|
+
await Promise.all(this.config.accounts.map((a) => this.readLogin(a)));
|
|
746
|
+
this.publish();
|
|
747
|
+
}
|
|
748
|
+
this.localDiscovery = this.localDiscovery.map((s) => discovered.status.some((d) => d.provider === s.provider) ? { ...s, savedCount: saved.get(s.provider)?.size ?? 0 } : s);
|
|
749
|
+
}, { waitMs: 3e4 });
|
|
750
|
+
})().finally(() => {
|
|
751
|
+
this.localScan = void 0;
|
|
752
|
+
});
|
|
753
|
+
return this.localScan;
|
|
754
|
+
}
|
|
755
|
+
async finishInitialSetup() {
|
|
756
|
+
if (this.setup?.completedAt) return;
|
|
757
|
+
await withFileLock(this.setupFile, async () => {
|
|
758
|
+
try {
|
|
759
|
+
this.setup = JSON.parse(await readFile2(this.setupFile, "utf8"));
|
|
760
|
+
} catch {
|
|
761
|
+
}
|
|
762
|
+
if (this.setup?.completedAt) return;
|
|
763
|
+
this.setup = { completedAt: Date.now(), dismissed: false };
|
|
764
|
+
await writePrivateJson(this.setupFile, this.setup);
|
|
765
|
+
});
|
|
766
|
+
}
|
|
325
767
|
async monitorAccount(account) {
|
|
326
768
|
if (this.monitorFlights.has(account.id)) return this.monitorFlights.get(account.id);
|
|
327
769
|
const promise = (async () => {
|
|
@@ -509,7 +951,11 @@ var Manager = class {
|
|
|
509
951
|
state() {
|
|
510
952
|
const status = this.router.status();
|
|
511
953
|
return {
|
|
954
|
+
pluginVersion: version,
|
|
512
955
|
revision: this.revision,
|
|
956
|
+
localDiscovery: this.localDiscovery ?? [],
|
|
957
|
+
setup: this.setup,
|
|
958
|
+
pluginUpdates: this.plugins.state(),
|
|
513
959
|
telemetry: this.telemetry.state(),
|
|
514
960
|
accounts: this.config.accounts.map((a) => ({
|
|
515
961
|
id: a.id,
|
|
@@ -544,8 +990,23 @@ var Manager = class {
|
|
|
544
990
|
}
|
|
545
991
|
async action(input) {
|
|
546
992
|
if (!input || typeof input !== "object" || Array.isArray(input)) bad("\u8BF7\u6C42\u683C\u5F0F\u65E0\u6548");
|
|
993
|
+
if (typeof input.action === "string" && input.action.startsWith("plugins.")) {
|
|
994
|
+
try {
|
|
995
|
+
await this.plugins.action(input);
|
|
996
|
+
} catch (e) {
|
|
997
|
+
bad(e.message);
|
|
998
|
+
}
|
|
999
|
+
return this.state();
|
|
1000
|
+
}
|
|
1001
|
+
if (input.action === "setup.dismiss") {
|
|
1002
|
+
await withFileLock(this.setupFile, async () => {
|
|
1003
|
+
this.setup = { ...this.setup, completedAt: this.setup?.completedAt ?? Date.now(), dismissed: true };
|
|
1004
|
+
await writePrivateJson(this.setupFile, this.setup);
|
|
1005
|
+
});
|
|
1006
|
+
return this.state();
|
|
1007
|
+
}
|
|
547
1008
|
if (input.action === "account.export") {
|
|
548
|
-
if (input.revision !== revisionOf(JSON.parse(await
|
|
1009
|
+
if (input.revision !== revisionOf(JSON.parse(await readFile2(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
|
|
549
1010
|
const account = this.config.accounts.find((a) => a.id === input.id);
|
|
550
1011
|
if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
|
|
551
1012
|
let credential;
|
|
@@ -567,7 +1028,7 @@ var Manager = class {
|
|
|
567
1028
|
} };
|
|
568
1029
|
}
|
|
569
1030
|
if (input.action === "config.export") {
|
|
570
|
-
if (input.revision !== revisionOf(JSON.parse(await
|
|
1031
|
+
if (input.revision !== revisionOf(JSON.parse(await readFile2(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
|
|
571
1032
|
try {
|
|
572
1033
|
return { ...this.state(), exported: { filename: `dsh-multi-router-${input.includeCredentials ? "encrypted" : "settings"}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`, document: await exportConfiguration(this, input) } };
|
|
573
1034
|
} catch (e) {
|
|
@@ -589,7 +1050,16 @@ var Manager = class {
|
|
|
589
1050
|
if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
|
|
590
1051
|
this.router.clients.delete(account.id);
|
|
591
1052
|
await this.monitorAccount(account);
|
|
592
|
-
} else
|
|
1053
|
+
} else {
|
|
1054
|
+
await this.syncLocalAccounts(input.provider);
|
|
1055
|
+
await this.monitorAll(input.provider);
|
|
1056
|
+
}
|
|
1057
|
+
return this.state();
|
|
1058
|
+
}
|
|
1059
|
+
if (input.action === "accounts.discover") {
|
|
1060
|
+
if (input.provider && !PROVIDERS.includes(input.provider)) bad("\u672A\u77E5\u4F9B\u5E94\u5546");
|
|
1061
|
+
await this.syncLocalAccounts(input.provider, true);
|
|
1062
|
+
await Promise.all([this.monitorAll(input.provider), ...this.config.accounts.filter((a) => !input.provider || a.provider === input.provider).map((a) => this.refreshAccount(a, true))]);
|
|
593
1063
|
return this.state();
|
|
594
1064
|
}
|
|
595
1065
|
if (input.action === "account.capture") {
|
|
@@ -606,7 +1076,7 @@ var Manager = class {
|
|
|
606
1076
|
return this.state();
|
|
607
1077
|
}
|
|
608
1078
|
return withFileLock(this.file, async () => {
|
|
609
|
-
let raw = JSON.parse(await
|
|
1079
|
+
let raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
610
1080
|
if (input.revision !== revisionOf(raw)) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u4FDD\u5B58");
|
|
611
1081
|
raw.routes ??= structuredClone(this.config.routes);
|
|
612
1082
|
let staging;
|
|
@@ -629,10 +1099,10 @@ var Manager = class {
|
|
|
629
1099
|
}
|
|
630
1100
|
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(input.id ?? "") || !PROVIDERS.includes(input.provider)) bad("\u8D26\u53F7 ID \u6216\u4F9B\u5E94\u5546\u65E0\u6548");
|
|
631
1101
|
if (account && !replacing) bad("\u8D26\u53F7 ID \u5DF2\u5B58\u5728\uFF0C\u8BF7\u4F7F\u7528\u4E0D\u540C ID");
|
|
632
|
-
const base =
|
|
1102
|
+
const base = join4(dirname3(this.file), "accounts");
|
|
633
1103
|
await mkdir2(base, { recursive: true, mode: 448 });
|
|
634
|
-
staging = await mkdtemp2(
|
|
635
|
-
const credentialFile =
|
|
1104
|
+
staging = await mkdtemp2(join4(base, `${input.id}-`));
|
|
1105
|
+
const credentialFile = join4(staging, "credential.json"), identityFile = join4(staging, "identity.json");
|
|
636
1106
|
const added = {
|
|
637
1107
|
id: input.id,
|
|
638
1108
|
name: input.name || account?.name || input.id,
|
|
@@ -658,31 +1128,31 @@ var Manager = class {
|
|
|
658
1128
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
659
1129
|
});
|
|
660
1130
|
} else {
|
|
661
|
-
|
|
662
|
-
if (!source && input.provider.startsWith("
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1131
|
+
const source = input.source;
|
|
1132
|
+
if (!source && (input.provider.startsWith("workbuddy") || input.provider.startsWith("trae"))) {
|
|
1133
|
+
const discovered = await discoverLocalAccounts(input.provider);
|
|
1134
|
+
const item = discovered.accounts.find((a) => !input.edition || input.edition === "auto" || a.document.credential.edition === input.edition);
|
|
1135
|
+
if (!item) bad(discovered.status.some((s) => s.state === "unreadable") ? "\u627E\u5230\u672C\u673A\u767B\u5F55\u6587\u4EF6\uFF0C\u4F46\u51ED\u8BC1\u6216\u8BBE\u5907\u8EAB\u4EFD\u65E0\u6CD5\u8BFB\u53D6\u3002\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u91CD\u65B0\u767B\u5F55\u540E\u91CD\u8BD5\uFF0C\u6216\u624B\u52A8\u5BFC\u5165\u51ED\u8BC1\u3002" : "\u672A\u627E\u5230\u6240\u9009\u533A\u57DF\u7684\u672C\u673A\u767B\u5F55\u3002\u8BF7\u5148\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u767B\u5F55\uFF0C\u518D\u70B9\u51FB\u91CD\u65B0\u68C0\u6D4B\u3002");
|
|
1136
|
+
try {
|
|
1137
|
+
await importAccount({ ...item, credentialFile, identityFile });
|
|
1138
|
+
} catch {
|
|
1139
|
+
bad("\u672C\u673A\u51ED\u8BC1\u5BFC\u5165\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u5BA2\u6237\u7AEF\u767B\u5F55\u72B6\u6001\u4E0E\u8BBE\u5907\u8EAB\u4EFD");
|
|
1140
|
+
}
|
|
1141
|
+
} else {
|
|
1142
|
+
const file = source || (input.provider.startsWith("qoder") ? qoderOwnAuthPath(QODER_VARIANTS.find((v) => v.id === input.provider)) : void 0);
|
|
1143
|
+
if (typeof file !== "string" || !file.trim()) bad("\u672A\u627E\u5230\u767B\u5F55\u6587\u4EF6\uFF0C\u8BF7\u586B\u5199\u6765\u6E90\u8DEF\u5F84\u6216 Qoder PAT");
|
|
1144
|
+
try {
|
|
1145
|
+
await importAccount({
|
|
1146
|
+
provider: input.provider,
|
|
1147
|
+
source: resolve(file),
|
|
1148
|
+
credentialFile,
|
|
1149
|
+
identityFile,
|
|
1150
|
+
edition: input.edition,
|
|
1151
|
+
identitySource: input.identity ? resolve(input.identity) : void 0
|
|
1152
|
+
});
|
|
1153
|
+
} catch {
|
|
1154
|
+
bad("\u8D26\u53F7\u5BFC\u5165\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u767B\u5F55\u6587\u4EF6\u3001\u533A\u57DF\u3001Trae edition \u548C\u8BBE\u5907\u8EAB\u4EFD");
|
|
672
1155
|
}
|
|
673
|
-
}
|
|
674
|
-
if (typeof source !== "string" || !source.trim()) bad("\u672A\u627E\u5230\u767B\u5F55\u6587\u4EF6\uFF0C\u8BF7\u586B\u5199\u6765\u6E90\u8DEF\u5F84\u6216 Qoder PAT");
|
|
675
|
-
try {
|
|
676
|
-
await importAccount({
|
|
677
|
-
provider: input.provider,
|
|
678
|
-
source: resolve(source),
|
|
679
|
-
credentialFile,
|
|
680
|
-
identityFile,
|
|
681
|
-
edition: input.edition,
|
|
682
|
-
identitySource: input.identity ? resolve(input.identity) : void 0
|
|
683
|
-
});
|
|
684
|
-
} catch {
|
|
685
|
-
bad("\u8D26\u53F7\u5BFC\u5165\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u767B\u5F55\u6587\u4EF6\u3001\u533A\u57DF\u3001Trae edition \u548C\u8BBE\u5907\u8EAB\u4EFD");
|
|
686
1156
|
}
|
|
687
1157
|
}
|
|
688
1158
|
const imported = await jsonCredential(credentialFile);
|
|
@@ -711,6 +1181,12 @@ var Manager = class {
|
|
|
711
1181
|
} else if (input.action === "account.remove") {
|
|
712
1182
|
if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
|
|
713
1183
|
if (Object.values(raw.routes).some((r) => r.targets.some((t) => t.account === input.id))) bad("\u8BF7\u5148\u79FB\u9664\u8DEF\u7531\u4E2D\u7ED1\u5B9A\u6B64\u8D26\u53F7\u7684\u76EE\u6807");
|
|
1184
|
+
if (account.provider.startsWith("workbuddy") || account.provider.startsWith("trae")) {
|
|
1185
|
+
try {
|
|
1186
|
+
raw.ignoredLocalAccounts = [.../* @__PURE__ */ new Set([...raw.ignoredLocalAccounts ?? [], localAccountKey(account.provider, await jsonCredential(this.config.accounts.find((a) => a.id === account.id).credentialFile))])];
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
714
1190
|
raw.accounts = raw.accounts.filter((a) => a.id !== input.id);
|
|
715
1191
|
} else if (input.action === "models.visibility") {
|
|
716
1192
|
if (input.provider !== void 0 && !PROVIDERS.includes(input.provider) || typeof input.visible !== "boolean") bad("\u4F9B\u5E94\u5546\u6216\u5C55\u793A\u5F00\u5173\u65E0\u6548");
|
|
@@ -815,7 +1291,7 @@ var Manager = class {
|
|
|
815
1291
|
clearTimeout(timer);
|
|
816
1292
|
timer = setTimeout(() => {
|
|
817
1293
|
void withFileLock(this.file, async () => {
|
|
818
|
-
const raw = JSON.parse(await
|
|
1294
|
+
const raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
819
1295
|
if (this.closed || revisionOf(raw) === this.revision) return;
|
|
820
1296
|
const config = validateConfig(raw, this.file);
|
|
821
1297
|
this.raw = raw;
|
|
@@ -882,4 +1358,4 @@ export {
|
|
|
882
1358
|
Manager,
|
|
883
1359
|
managementHandler
|
|
884
1360
|
};
|
|
885
|
-
//# sourceMappingURL=chunk-
|
|
1361
|
+
//# sourceMappingURL=chunk-UQS7DNOQ.js.map
|