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.
@@ -1,23 +1,20 @@
1
1
  import {
2
2
  jsonCredential,
3
3
  qoderPluginDataDir
4
- } from "./chunk-VESY2B5O.js";
4
+ } from "./chunk-UFS7ZJKY.js";
5
5
  import {
6
+ discoverLocalAccounts,
6
7
  importAccount,
8
+ localAccountKey,
7
9
  readPortableCredential
8
- } from "./chunk-IEN3ZQFS.js";
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-3VVQXFPV.js";
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-HZVBEBOD.js";
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
- import { readFile, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2, access } from "node:fs/promises";
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 join3, relative as relative2, resolve } from "node:path";
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 = join3(dirname3(file), "catalogs.json");
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(join3(dirname3(file), "usage.json"));
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 readFile(this.file, "utf8"));
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 readFile(this.catalogFile, "utf8"));
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 readFile(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
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 readFile(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
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 await this.monitorAll(input.provider);
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 readFile(this.file, "utf8"));
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 = join3(dirname3(this.file), "accounts");
1102
+ const base = join4(dirname3(this.file), "accounts");
633
1103
  await mkdir2(base, { recursive: true, mode: 448 });
634
- staging = await mkdtemp2(join3(base, `${input.id}-`));
635
- const credentialFile = join3(staging, "credential.json"), identityFile = join3(staging, "identity.json");
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
- let source = input.source;
662
- if (!source && input.provider.startsWith("qoder")) source = qoderOwnAuthPath(QODER_VARIANTS.find((v) => v.id === input.provider));
663
- if (!source && input.provider.startsWith("workbuddy")) source = defaultDesktopAuthPath(WORKBUDDY_VARIANTS.find((v) => v.id === input.provider));
664
- if (!source && input.provider.startsWith("trae")) {
665
- for (const candidate of traeStorageCandidates().filter((c) => c.source === "desktop" && c.edition === input.edition)) {
666
- try {
667
- await access(candidate.path);
668
- source = candidate.path;
669
- break;
670
- } catch {
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 readFile(this.file, "utf8"));
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-BIEDP6NW.js.map
1361
+ //# sourceMappingURL=chunk-UQS7DNOQ.js.map