dsh-multi-router 0.5.8 → 0.5.9

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.
@@ -11,8 +11,9 @@ import {
11
11
  discoverLocalAccounts,
12
12
  importAccount,
13
13
  localAccountKey,
14
+ normalizeLocalAccountKey,
14
15
  readPortableCredential
15
- } from "./chunk-OFFVP6YN.js";
16
+ } from "./chunk-S6ZQ45SS.js";
16
17
  import {
17
18
  hostIsLoopback
18
19
  } from "./chunk-DNQPZMPF.js";
@@ -39,12 +40,12 @@ import {
39
40
  import { createHash, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
40
41
 
41
42
  // package.json
42
- var version = "0.5.8";
43
+ var version = "0.5.9";
43
44
 
44
45
  // src/manager.mjs
45
- import { readFile as readFile3, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
46
- import { watch } from "node:fs";
47
- import { dirname as dirname3, join as join4, relative as relative2, resolve } from "node:path";
46
+ import { readFile as readFile4, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
47
+ import { watch as watch2 } from "node:fs";
48
+ import { dirname as dirname3, join as join5, relative as relative2, resolve } from "node:path";
48
49
  import { withFileLock as withFileLock3 } from "@deepseek-ai/dsh-atomic-write";
49
50
 
50
51
  // upstream/qoder/src/status-paths.ts
@@ -239,14 +240,140 @@ async function prepareMigration(manager, raw, portable, mode) {
239
240
  }
240
241
 
241
242
  // src/plugin-updates.mjs
242
- import { readFile, stat, rm as rm2 } from "node:fs/promises";
243
- import { join as join3 } from "node:path";
243
+ import { readFile as readFile2, stat, rm as rm2 } from "node:fs/promises";
244
+ import { join as join4 } from "node:path";
244
245
  import { randomUUID } from "node:crypto";
245
246
  import { createRequire } from "node:module";
246
247
  import { pathToFileURL } from "node:url";
247
248
  import { parse as parseYaml } from "yaml";
248
249
  import { withFileLock, writeFileAtomic as writeFileAtomic2 } from "@deepseek-ai/dsh-atomic-write";
249
250
 
251
+ // src/plugin-reload.mjs
252
+ import { readFile } from "node:fs/promises";
253
+ import { watch } from "node:fs";
254
+ import { join as join3 } from "node:path";
255
+ import { parse } from "yaml";
256
+ function outsideActivation(hmr, operation) {
257
+ const scope = hmr?.executing;
258
+ return typeof scope?.exit === "function" ? scope.exit(operation) : operation();
259
+ }
260
+ var PluginReload = class {
261
+ constructor({ service, profile, canReload, hmr = () => void 0, busy = () => false, now = Date.now, runningVersion }) {
262
+ Object.assign(this, { service, profile, canReload, hmr, busy, now, runningVersion });
263
+ this.phase = "current";
264
+ this.installedVersion = runningVersion;
265
+ this.retryAt = 0;
266
+ }
267
+ state() {
268
+ return { runningVersion: this.runningVersion, installedVersion: this.installedVersion, phase: this.phase, canReload: !!this.canReload(), message: this.message };
269
+ }
270
+ start() {
271
+ const dir = this.profile()?.dir;
272
+ this.watchers = [];
273
+ if (dir) for (const target of [dir, join3(dir, "node_modules")]) try {
274
+ const watcher = watch(target, () => this.schedule());
275
+ watcher.unref();
276
+ this.watchers.push(watcher);
277
+ } catch {
278
+ }
279
+ this.schedule();
280
+ }
281
+ schedule(delay = 500) {
282
+ if (this.closed) return;
283
+ clearTimeout(this.timer);
284
+ this.timer = setTimeout(() => {
285
+ void this.check().catch(() => {
286
+ });
287
+ }, delay);
288
+ this.timer.unref();
289
+ }
290
+ stop() {
291
+ this.closed = true;
292
+ clearTimeout(this.timer);
293
+ for (const watcher of this.watchers ?? []) watcher.close();
294
+ }
295
+ check() {
296
+ if (this.closed) return Promise.resolve();
297
+ if (this.flight) return this.flight;
298
+ this.flight = this.inspect().finally(() => {
299
+ this.flight = void 0;
300
+ });
301
+ return this.flight;
302
+ }
303
+ async inspect() {
304
+ const dir = this.profile()?.dir;
305
+ if (!dir) return;
306
+ let manifest, installed;
307
+ try {
308
+ manifest = JSON.parse(await readFile(join3(dir, "package.json"), "utf8"));
309
+ installed = JSON.parse(await readFile(join3(dir, "node_modules/dsh-multi-router/package.json"), "utf8"));
310
+ } catch {
311
+ return;
312
+ }
313
+ if (this.closed || !manifest.dsh?.profile?.bundles?.includes("dsh-multi-router")) return;
314
+ if (installed.name !== "dsh-multi-router" || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(installed.version)) return;
315
+ this.installedVersion = installed.version;
316
+ if (installed.version === this.runningVersion) {
317
+ this.phase = "current";
318
+ this.message = void 0;
319
+ return;
320
+ }
321
+ const declared = manifest.dependencies?.["dsh-multi-router"];
322
+ const exact = typeof declared === "string" && /^(?:npm:dsh-multi-router@)?(\d+\.\d+\.\d+(?:-[\w.-]+)?)$/.exec(declared)?.[1];
323
+ let installing = false;
324
+ const pid = Number(await readFile(join3(dir, "package.json.lock"), "utf8").catch(() => ""));
325
+ if (Number.isInteger(pid) && pid > 0) try {
326
+ process.kill(pid, 0);
327
+ installing = true;
328
+ } catch (e) {
329
+ installing = e.code !== "ESRCH";
330
+ }
331
+ if (installing || exact && exact !== installed.version) {
332
+ this.phase = "waiting";
333
+ this.message = "\u5B89\u88C5\u4E8B\u52A1\u5C1A\u672A\u5B8C\u6210\uFF0C\u6216\u5B89\u88C5\u5931\u8D25\u5DF2\u56DE\u6EDA\uFF1B\u7B49\u5F85\u914D\u7F6E\u4E0E\u6587\u4EF6\u7248\u672C\u4E00\u81F4\u540E\u518D\u52A0\u8F7D\u3002";
334
+ if (installing) this.schedule(1e3);
335
+ return;
336
+ }
337
+ if (!this.canReload() || !this.service()?.setBundleEnabled) {
338
+ this.phase = "restart-required";
339
+ this.message = "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u8FD0\u884C\u4E2D\u91CD\u8F7D\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
340
+ return;
341
+ }
342
+ if (this.busy()) {
343
+ this.phase = "waiting";
344
+ this.message = "\u65B0\u7248\u672C\u5DF2\u5B89\u88C5\uFF0C\u7B49\u5F85\u5F53\u524D\u8BF7\u6C42\u548C\u8D26\u53F7\u64CD\u4F5C\u7ED3\u675F\u540E\u81EA\u52A8\u751F\u6548\u3002";
345
+ this.schedule(1e3);
346
+ return;
347
+ }
348
+ const root = join3(dir, "node_modules/dsh-multi-router");
349
+ try {
350
+ const patch = parse(await readFile(join3(root, "cordis.patch.yml"), "utf8"));
351
+ const row = Array.isArray(patch) && patch.flatMap((p) => p?.insert ?? []).find((e) => e?.id === `llm-multi-router-v2-${installed.version}`);
352
+ if (row?.name !== `./lib/host-v${installed.version}.js` || row.disabled) throw Error("legacy entry");
353
+ await readFile(join3(root, `lib/host-v${installed.version}.js`));
354
+ } catch {
355
+ this.phase = "restart-required";
356
+ this.message = "\u5B89\u88C5\u7248\u672C\u7F3A\u5C11\u7248\u672C\u5316\u5165\u53E3\uFF0C\u91CD\u65B0\u5B89\u88C5\u6700\u65B0\u7248\u540E\u518D\u8BD5\u3002";
357
+ return;
358
+ }
359
+ if (this.now() < this.retryAt) return;
360
+ this.phase = "reloading";
361
+ this.message = "\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u4E0E\u914D\u7F6E\u9762\u677F\u2026";
362
+ this.retryAt = this.now() + 6e4;
363
+ try {
364
+ const result = await outsideActivation(this.hmr(), () => this.service().setBundleEnabled("dsh-multi-router", true));
365
+ if (this.closed) return;
366
+ this.phase = "restart-required";
367
+ this.message = result.application === "applied" ? "DSH \u5C1A\u672A\u5207\u6362\u5230\u65B0\u7248\u672C\uFF0C\u8BF7\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u6216\u67E5\u770B\u7EC4\u4EF6\u9519\u8BEF\u3002" : "DSH \u672A\u80FD\u91CD\u8F7D\u63D2\u4EF6\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
368
+ } catch {
369
+ if (!this.closed) {
370
+ this.phase = "failed";
371
+ this.message = "\u63D2\u4EF6\u91CD\u8F7D\u672A\u5B8C\u6210\uFF0C\u53EF\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5DF2\u4FDD\u5B58\u7684\u914D\u7F6E\u4FDD\u6301\u4E0D\u53D8\u3002";
372
+ }
373
+ }
374
+ }
375
+ };
376
+
250
377
  // upstream/workbuddy/src/update.ts
251
378
  var WORKBUDDY_REPOSITORY_URL = "https://github.com/corrinehu/dsh-workbuddy-connect";
252
379
  var WORKBUDDY_UPDATE_PACKAGE_NAME = "dsh-workbuddy-connect";
@@ -322,7 +449,7 @@ function githubCommit(value) {
322
449
  }
323
450
  async function jsonFile(file) {
324
451
  try {
325
- return JSON.parse(await readFile(file, "utf8"));
452
+ return JSON.parse(await readFile2(file, "utf8"));
326
453
  } catch {
327
454
  return void 0;
328
455
  }
@@ -330,11 +457,11 @@ async function jsonFile(file) {
330
457
  async function locks(dir) {
331
458
  let pnpm;
332
459
  try {
333
- pnpm = parseYaml(await readFile(join3(dir, "pnpm-lock.yaml"), "utf8"), { maxAliasCount: 20 });
460
+ pnpm = parseYaml(await readFile2(join4(dir, "pnpm-lock.yaml"), "utf8"), { maxAliasCount: 20 });
334
461
  } catch {
335
462
  }
336
- const npm = await jsonFile(join3(dir, "package-lock.json"));
337
- const times = await Promise.all(["pnpm-lock.yaml", "package-lock.json"].map((name) => stat(join3(dir, name)).then((s) => s.mtimeMs).catch(() => 0)));
463
+ const npm = await jsonFile(join4(dir, "package-lock.json"));
464
+ const times = await Promise.all(["pnpm-lock.yaml", "package-lock.json"].map((name) => stat(join4(dir, name)).then((s) => s.mtimeMs).catch(() => 0)));
338
465
  return { pnpm, npm, preferNpm: times[1] > times[0] };
339
466
  }
340
467
  async function publicJson(url, fetchImpl, signal) {
@@ -355,11 +482,46 @@ async function publicJson(url, fetchImpl, signal) {
355
482
  return /^[a-f0-9]{40}$/i.test(text) ? { sha: text } : JSON.parse(text);
356
483
  }
357
484
  var short = (sha) => sha?.slice(0, 8);
358
- async function registryVersion(service, profile, spec, signal) {
485
+ var installFailed = (r) => !!(r.error || ["failed", "cancelled"].includes(r.application) || r.packageResult && r.packageResult.exitCode !== 0);
486
+ function installFailure(result) {
487
+ const text = [result.error?.diagnostic, result.packageResult?.output, result.packageResult?.kind].filter((v) => typeof v === "string").join("\n");
488
+ let reason = "unknown", message = "\u5B89\u88C5\u672A\u5B8C\u6210\uFF1B\u8BF7\u5728 DSH \u539F\u751F\u63D2\u4EF6\u9875\u67E5\u770B\u5B89\u88C5\u65E5\u5FD7\uFF0C\u6216\u590D\u5236\u4E0B\u65B9\u8BCA\u65AD\u4FE1\u606F\u3002";
489
+ if (result.application === "cancelled") {
490
+ reason = "cancelled";
491
+ message = "\u5B89\u88C5\u5DF2\u53D6\u6D88\uFF0C\u53EF\u91CD\u65B0\u68C0\u67E5\u540E\u91CD\u8BD5\u3002";
492
+ } else if (/ERR_PNPM_(?:MINIMUM_RELEASE_AGE_VIOLATION|NO_MATURE_MATCHING_VERSION)|minimumReleaseAge/i.test(text)) {
493
+ reason = "release-age";
494
+ message = "\u65B0\u7248\u672C\u5C1A\u5728\u5B89\u88C5\u5668\u7684\u65B0\u5305\u7B49\u5F85\u671F\u5185\u3002\u8F93\u5165\u5305\u540D\u53EF\u80FD\u9009\u4E2D\u65E7\u7248\uFF0C\u6307\u5B9A\u65B0\u7248\u672C\u53EF\u80FD\u88AB\u62D2\u7EDD\uFF1B\u8BF7\u7B49\u5F85\u7B56\u7565\u89C4\u5B9A\u7684\u65F6\u95F4\u540E\u91CD\u8BD5\uFF0C\u6216\u7531\u7528\u6237\u660E\u786E\u4E3A\u8BE5\u7248\u672C\u8BBE\u7F6E\u4F8B\u5916\u3002\u6362\u955C\u50CF\u6216\u91CD\u542F\u4E0D\u4F1A\u89E3\u9664\u6B64\u9650\u5236\u3002";
495
+ } else if (result.pendingBuilds?.length) {
496
+ reason = "build";
497
+ message = "\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\u3002";
498
+ } else if (result.error?.code === "incompatible-version") {
499
+ reason = "compatibility";
500
+ message = "\u63D2\u4EF6\u4E0E\u5F53\u524D DSH \u4E0D\u517C\u5BB9\uFF0C\u8BF7\u5148\u5347\u7EA7 DSH \u6216\u9009\u62E9\u517C\u5BB9\u7248\u672C\u3002";
501
+ } else if (/\b(?:EPERM|EBUSY|EACCES)\b|resource busy|file.*(?:locked|in use)/i.test(text)) {
502
+ reason = "files";
503
+ message = "\u63D2\u4EF6\u6587\u4EF6\u88AB\u5360\u7528\u6216\u76EE\u5F55\u6743\u9650\u4E0D\u8DB3\uFF0C\u5B89\u88C5\u672A\u5B8C\u6210\u3002\u8BF7\u5148\u5728 DSH \u505C\u7528\u8BE5\u63D2\u4EF6\u540E\u91CD\u8BD5\uFF0C\u5E76\u68C0\u67E5\u5B89\u88C5\u76EE\u5F55\u6743\u9650\uFF1B\u4ECD\u88AB\u5360\u7528\u65F6\u9700\u9000\u51FA\u6258\u76D8\u5E94\u7528\u3002";
504
+ } else if (/HMR transactions cannot be nested|activation|did not activate|entry.*activate/i.test(text)) {
505
+ reason = "activation";
506
+ message = "\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25\u3002\u8BF7\u68C0\u67E5 DSH \u63D2\u4EF6\u9875\u7684\u7EC4\u4EF6\u9519\u8BEF\uFF0C\u518D\u5173\u95ED\u5E76\u91CD\u65B0\u542F\u7528\uFF1B\u5B89\u88C5\u4E0E\u52A0\u8F7D\u662F\u4E24\u4E2A\u72EC\u7ACB\u6B65\u9AA4\u3002";
507
+ } else if (result.packageResult?.timedOut || /\b(?:ETIMEDOUT|ECONNRESET|ENOTFOUND|EAI_AGAIN|ECONNREFUSED)\b|network|fetch|download/i.test(text)) {
508
+ reason = "network";
509
+ message = "\u63D2\u4EF6\u4E0B\u8F7D\u6216\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF0C\u5B89\u88C5\u672A\u5B8C\u6210\u3002npm \u6765\u6E90\u53EF\u5207\u6362\u5230\u300Cnpm \u5B98\u65B9\u300D\u540E\u91CD\u8BD5\uFF1BGitHub \u6765\u6E90\u8BF7\u68C0\u67E5 GitHub \u7F51\u7EDC\u8FDE\u63A5\u3002";
510
+ }
511
+ const code = /\b(?:ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION|ERR_PNPM_NO_MATURE_MATCHING_VERSION|EPERM|EBUSY|EACCES|ETIMEDOUT|ECONNRESET|ENOTFOUND|EAI_AGAIN|ECONNREFUSED)\b/.exec(text)?.[0] ?? (["operation-error", "incompatible-version"].includes(result.error?.code) ? result.error.code : "unknown");
512
+ return { status: "failed", application: result.application, message, diagnostic: {
513
+ reason,
514
+ code,
515
+ platform: process.platform,
516
+ stage: reason === "activation" ? "activation" : "install",
517
+ ...Number.isInteger(result.packageResult?.exitCode) ? { exitCode: result.packageResult.exitCode } : {}
518
+ } };
519
+ }
520
+ async function registryVersion(service, profile, spec, signal, requestedRegistry) {
359
521
  const require2 = createRequire(profile.installAnchor);
360
522
  const { viewProfilePackage } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/operations")).href);
361
523
  const { registryPlan } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
362
- for (const registry of registryPlan(void 0, await service.registries())) {
524
+ for (const registry of registryPlan(requestedRegistry, await service.registries())) {
363
525
  const answer = await viewProfilePackage(profile.dir, spec, { ...profile.packageManager, registry, timeoutMs: 12e3, signal });
364
526
  if (signal.aborted) throw Error("\u68C0\u67E5\u5DF2\u53D6\u6D88");
365
527
  if (answer.exitCode !== 0) continue;
@@ -374,11 +536,11 @@ async function refreshGitPlugin(service, profile, row, options2, signal) {
374
536
  const { registryPlan, attributeFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
375
537
  const { classifyInstallFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager")).href);
376
538
  const plan = registryPlan(options2.registry, await service.registries());
377
- return withFileLock(join3(profile.dir, "package.json"), async () => {
539
+ return withFileLock(join4(profile.dir, "package.json"), async () => {
378
540
  const files = await Promise.all(["package.json", "pnpm-lock.yaml"].map(async (name) => {
379
- const path = join3(profile.dir, name);
541
+ const path = join4(profile.dir, name);
380
542
  try {
381
- return { path, content: await readFile(path, "utf8") };
543
+ return { path, content: await readFile2(path, "utf8") };
382
544
  } catch (e) {
383
545
  if (e.code !== "ENOENT") throw e;
384
546
  return { path };
@@ -417,7 +579,7 @@ async function refreshGitPlugin(service, profile, row, options2, signal) {
417
579
  }, { waitMs: 12e4 });
418
580
  }
419
581
  var PluginUpdates = class {
420
- constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin, onInstalled, stateFile, now = Date.now } = {}) {
582
+ constructor({ service = () => void 0, profile = () => void 0, hmr = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin, onInstalled, stateFile, now = Date.now } = {}) {
421
583
  this.stateFile = stateFile;
422
584
  this.now = now;
423
585
  this.dismissed = {};
@@ -425,6 +587,7 @@ var PluginUpdates = class {
425
587
  this.saveQueue = Promise.resolve();
426
588
  this.service = service;
427
589
  this.profile = profile;
590
+ this.hmr = hmr;
428
591
  this.fetchImpl = fetchImpl;
429
592
  this.rows = [];
430
593
  this.abort = new AbortController();
@@ -485,7 +648,7 @@ var PluginUpdates = class {
485
648
  this.rows = [];
486
649
  return;
487
650
  }
488
- const [manifest, bundles, lock] = await Promise.all([jsonFile(join3(profile.dir, "package.json")), service.listBundles(), locks(profile.dir)]);
651
+ const [manifest, bundles, lock] = await Promise.all([jsonFile(join4(profile.dir, "package.json")), service.listBundles(), locks(profile.dir)]);
489
652
  if (!manifest) throw Error("\u5F53\u524D DSH \u914D\u7F6E\u7684\u63D2\u4EF6\u6E05\u5355\u65E0\u6CD5\u8BFB\u53D6");
490
653
  this.rows = bundles.filter((b) => (b.installed || b.version) && manifest.dependencies?.[b.name] && b.removable !== false).map((b) => {
491
654
  const spec = manifest.dependencies?.[b.name];
@@ -578,7 +741,7 @@ var PluginUpdates = class {
578
741
  });
579
742
  }
580
743
  async installRow(row, registry) {
581
- const original = await jsonFile(join3(this.profile().dir, "package.json"));
744
+ const original = await jsonFile(join4(this.profile().dir, "package.json"));
582
745
  if (original?.dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
583
746
  const current = (await this.service().listBundles()).find((b) => b.name === row.name);
584
747
  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");
@@ -591,17 +754,8 @@ var PluginUpdates = class {
591
754
  } finally {
592
755
  this.gitAbort = void 0;
593
756
  }
594
- } else result = await this.service().installBundle(row.target, options2);
595
- if (result.packageResult && result.packageResult.exitCode !== 0 || ["failed", "cancelled"].includes(result.application) || result.error) {
596
- return {
597
- name: row.name,
598
- status: "failed",
599
- application: result.application,
600
- 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`,
601
- pendingBuilds: result.pendingBuilds ?? [],
602
- logPath: result.packageResult?.logPath
603
- };
604
- }
757
+ } else result = await outsideActivation(this.hmr(), () => this.service().installBundle(row.target, options2));
758
+ if (installFailed(result)) return { name: row.name, ...installFailure(result), expectedVersion: row.latestVersion };
605
759
  const installed = (await this.service().listBundles()).find((b) => b.name === row.name);
606
760
  if (row.source.kind === "npm" && installed?.version !== row.latestVersion) return {
607
761
  name: row.name,
@@ -654,7 +808,32 @@ var PluginUpdates = class {
654
808
  this.start("install", async (job) => {
655
809
  job.total = 1;
656
810
  job.current = spec;
657
- const result = await this.service().installBundle(spec, { requestId: job.id, ...registry ? { registry } : {} });
811
+ let expected;
812
+ if (npm?.kind === "npm" && github.kind !== "github") {
813
+ const found = await this.lookup(this.service(), this.profile(), `${npm.package}@${named[2] || "latest"}`, this.abort.signal, registry);
814
+ if (!parseWorkBuddyVersion(found.version)) throw Error("npm \u672A\u8FD4\u56DE\u6709\u6548\u7248\u672C\uFF0C\u8BF7\u68C0\u67E5\u5B89\u88C5\u6E90\u540E\u91CD\u8BD5");
815
+ expected = found.version;
816
+ }
817
+ const result = await outsideActivation(this.hmr(), () => this.service().installBundle(spec, { requestId: job.id, ...registry ? { registry } : {} }));
818
+ if (installFailed(result)) {
819
+ job.results.push({ name: result.bundle ?? spec, ...installFailure(result), expectedVersion: expected });
820
+ delete job.current;
821
+ await this.list();
822
+ return;
823
+ }
824
+ const installed = (await this.service().listBundles()).find((b) => b.name === (result.bundle ?? named?.[1]));
825
+ if (expected && installed?.version !== expected) {
826
+ job.results.push({
827
+ name: result.bundle ?? named[1],
828
+ status: "failed",
829
+ expectedVersion: expected,
830
+ installedVersion: installed?.version,
831
+ message: `\u5B9E\u9645\u5B89\u88C5\u7248\u672C ${installed?.version ?? "\u672A\u77E5"} \u4E0E\u68C0\u67E5\u7248\u672C ${expected} \u4E0D\u540C\uFF1B\u8BF7\u5207\u6362\u300Cnpm \u5B98\u65B9\u300D\u540E\u91CD\u65B0\u68C0\u67E5\u5E76\u5B89\u88C5\u6307\u5B9A\u7248\u672C\u3002`
832
+ });
833
+ delete job.current;
834
+ await this.list();
835
+ return;
836
+ }
658
837
  const reloadPending = !result.error && result.packageResult?.exitCode === 0 && result.application === "restart-required" && this.onInstalled?.(result.bundle) === true;
659
838
  job.results.push({
660
839
  name: result.bundle ?? spec,
@@ -689,7 +868,7 @@ var PluginUpdates = class {
689
868
 
690
869
  // src/workbuddy-login.mjs
691
870
  import { randomBytes as randomBytes2 } from "node:crypto";
692
- import { readFile as readFile2 } from "node:fs/promises";
871
+ import { readFile as readFile3 } from "node:fs/promises";
693
872
  import { withFileLock as withFileLock2 } from "@deepseek-ai/dsh-atomic-write";
694
873
  var WorkBuddyLogin = class {
695
874
  constructor(file, { fetchImpl = (...args) => fetch(...args), now = Date.now } = {}) {
@@ -703,7 +882,7 @@ var WorkBuddyLogin = class {
703
882
  await withFileLock2(this.file, async () => {
704
883
  let state = {};
705
884
  try {
706
- state = JSON.parse(await readFile2(this.file, "utf8"));
885
+ state = JSON.parse(await readFile3(this.file, "utf8"));
707
886
  } catch (e) {
708
887
  if (e.code !== "ENOENT") throw Error("\u767B\u5F55\u51B7\u5374\u8BB0\u5F55\u4E0D\u53EF\u8BFB\uFF0C\u8BF7\u68C0\u67E5\u672C\u673A\u6587\u4EF6");
709
888
  }
@@ -715,7 +894,7 @@ var WorkBuddyLogin = class {
715
894
  async pause() {
716
895
  this.pending = void 0;
717
896
  await withFileLock2(this.file, async () => {
718
- const state = JSON.parse(await readFile2(this.file, "utf8"));
897
+ const state = JSON.parse(await readFile3(this.file, "utf8"));
719
898
  state.pausedUntil = this.now() + 15 * 6e4;
720
899
  await writePrivateJson(this.file, state);
721
900
  });
@@ -865,31 +1044,31 @@ var Manager = class {
865
1044
  this.file = file;
866
1045
  this.router = router;
867
1046
  this.onChange = onChange;
868
- this.catalogFile = join4(dirname3(file), "catalogs.json");
1047
+ this.catalogFile = join5(dirname3(file), "catalogs.json");
869
1048
  this.catalogs = /* @__PURE__ */ new Map();
870
1049
  this.catalogErrors = /* @__PURE__ */ new Map();
871
1050
  this.refreshes = /* @__PURE__ */ new Map();
872
1051
  this.closed = false;
873
1052
  this.monitorFlights = /* @__PURE__ */ new Map();
874
1053
  this.loginInfo = /* @__PURE__ */ new Map();
875
- this.telemetry = new Telemetry(join4(dirname3(file), "usage.json"));
876
- this.plugins = new PluginUpdates({ ...pluginAccess, stateFile: join4(dirname3(file), "plugin-updates.json"), onInstalled: (name) => {
1054
+ this.telemetry = new Telemetry(join5(dirname3(file), "usage.json"));
1055
+ this.plugins = new PluginUpdates({ ...pluginAccess, stateFile: join5(dirname3(file), "plugin-updates.json"), onInstalled: (name) => {
877
1056
  if (name !== "dsh-multi-router" || !this.runtime?.canReload()) return false;
878
1057
  this.runtime.schedule();
879
1058
  return true;
880
1059
  } });
881
- this.checkins = new Checkins(join4(dirname3(file), "checkins.json"), this);
882
- this.workbuddyLogin = new WorkBuddyLogin(join4(dirname3(file), "workbuddy-login.json"), { fetchImpl: pluginAccess.fetchImpl });
883
- this.setupFile = join4(dirname3(file), "initial-setup.json");
1060
+ this.checkins = new Checkins(join5(dirname3(file), "checkins.json"), this);
1061
+ this.workbuddyLogin = new WorkBuddyLogin(join5(dirname3(file), "workbuddy-login.json"), { fetchImpl: pluginAccess.fetchImpl });
1062
+ this.setupFile = join5(dirname3(file), "initial-setup.json");
884
1063
  router.telemetry = this.telemetry;
885
- router.monitor = (a) => this.monitorAccount(a);
1064
+ router.monitor = (a) => this.monitorAccount(a, { maxAgeMs: 6e4 });
886
1065
  router.prepareFree = () => this.checkFreeModels(true);
887
1066
  router.freeModels = (account) => this.freeModels(account);
888
1067
  }
889
1068
  async load() {
890
- this.raw = JSON.parse(await readFile3(this.file, "utf8"));
1069
+ this.raw = JSON.parse(await readFile4(this.file, "utf8"));
891
1070
  try {
892
- this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
1071
+ this.setup = JSON.parse(await readFile4(this.setupFile, "utf8"));
893
1072
  } catch {
894
1073
  this.setup = void 0;
895
1074
  }
@@ -897,7 +1076,7 @@ var Manager = class {
897
1076
  await this.checkins.load();
898
1077
  await this.plugins.load();
899
1078
  try {
900
- const saved = JSON.parse(await readFile3(this.catalogFile, "utf8"));
1079
+ const saved = JSON.parse(await readFile4(this.catalogFile, "utf8"));
901
1080
  for (const account of this.config.accounts) {
902
1081
  const item = saved[account.id];
903
1082
  if (item?.credentialFile === account.credentialFile && Array.isArray(item.models)) {
@@ -939,12 +1118,12 @@ var Manager = class {
939
1118
  if (this.closed || !discovered.accounts.length) return;
940
1119
  await withFileLock3(this.file, async () => {
941
1120
  if (this.closed) return;
942
- let raw = JSON.parse(await readFile3(this.file, "utf8"));
1121
+ let raw = JSON.parse(await readFile4(this.file, "utf8"));
943
1122
  let changed = false;
944
1123
  const saved = /* @__PURE__ */ new Map();
945
1124
  for (const item of discovered.accounts) {
946
1125
  const key = localAccountKey(item.provider, item.document);
947
- if (!force && raw.ignoredLocalAccounts?.includes(key)) continue;
1126
+ if (!force && raw.ignoredLocalAccounts?.some((k) => normalizeLocalAccountKey(k) === key)) continue;
948
1127
  const config = validateConfig(raw, this.file);
949
1128
  let existing, current;
950
1129
  for (const a of config.accounts.filter((a2) => a2.provider === item.provider)) {
@@ -976,11 +1155,11 @@ var Manager = class {
976
1155
  recordSaved();
977
1156
  continue;
978
1157
  }
979
- const base = join4(dirname3(this.file), "accounts");
1158
+ const base = join5(dirname3(this.file), "accounts");
980
1159
  await mkdir2(base, { recursive: true, mode: 448 });
981
- const staging = await mkdtemp2(join4(base, `${id}-`));
1160
+ const staging = await mkdtemp2(join5(base, `${id}-`));
982
1161
  try {
983
- const credentialFile = join4(staging, "credential.json"), identityFile = join4(staging, "identity.json");
1162
+ const credentialFile = join5(staging, "credential.json"), identityFile = join5(staging, "identity.json");
984
1163
  await importAccount({ ...item, credentialFile, identityFile });
985
1164
  const paths = {
986
1165
  credentialFile: relative2(dirname3(this.file), credentialFile),
@@ -994,7 +1173,7 @@ var Manager = class {
994
1173
  name: item.document.credential.nickname || item.document.credential.accountName || item.document.credential.uid || item.document.credential.userId,
995
1174
  ...paths
996
1175
  });
997
- next.ignoredLocalAccounts = (raw.ignoredLocalAccounts ?? []).filter((k) => k !== key);
1176
+ next.ignoredLocalAccounts = (raw.ignoredLocalAccounts ?? []).filter((k) => normalizeLocalAccountKey(k) !== key);
998
1177
  validateConfig(next, this.file);
999
1178
  await writePrivateJson(this.file, next);
1000
1179
  raw = next;
@@ -1025,7 +1204,7 @@ var Manager = class {
1025
1204
  if (this.setup?.completedAt) return;
1026
1205
  await withFileLock3(this.setupFile, async () => {
1027
1206
  try {
1028
- this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
1207
+ this.setup = JSON.parse(await readFile4(this.setupFile, "utf8"));
1029
1208
  } catch {
1030
1209
  }
1031
1210
  if (this.setup?.completedAt) return;
@@ -1033,8 +1212,12 @@ var Manager = class {
1033
1212
  await writePrivateJson(this.setupFile, this.setup);
1034
1213
  });
1035
1214
  }
1036
- async monitorAccount(account) {
1215
+ async monitorAccount(account, { force = false, maxAgeMs = 3e5 } = {}) {
1037
1216
  if (this.monitorFlights.has(account.id)) return this.monitorFlights.get(account.id);
1217
+ const cached = this.telemetry.row("accounts", account.id).balance;
1218
+ const last = cached?.error ? cached.attemptedAt : cached?.fetchedAt;
1219
+ const ttl = cached?.error ? 3e5 : maxAgeMs;
1220
+ if (!force && Number.isFinite(last) && Date.now() >= last && Date.now() - last < ttl) return;
1038
1221
  const promise = (async () => {
1039
1222
  try {
1040
1223
  const client = await this.router.client(account);
@@ -1053,8 +1236,8 @@ var Manager = class {
1053
1236
  this.monitorFlights.set(account.id, promise);
1054
1237
  return promise;
1055
1238
  }
1056
- async monitorAll(provider) {
1057
- await Promise.all(this.config.accounts.filter((a) => !provider || a.provider === provider).map((a) => this.monitorAccount(a)));
1239
+ async monitorAll(provider, options2) {
1240
+ await Promise.all(this.config.accounts.filter((a) => (!provider || a.provider === provider) && (options2?.force || a.enabled !== false)).map((a) => this.monitorAccount(a, options2)));
1058
1241
  }
1059
1242
  freeAccounts() {
1060
1243
  const targets = Object.values(this.config.routes).filter((r) => r.freeOnly).flatMap((r) => r.targets);
@@ -1277,7 +1460,7 @@ var Manager = class {
1277
1460
  }
1278
1461
  }
1279
1462
  if (input.action === "workbuddy.login") {
1280
- if (input.revision !== revisionOf(JSON.parse(await readFile3(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u63D0\u4EA4\u767B\u5F55");
1463
+ if (input.revision !== revisionOf(JSON.parse(await readFile4(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u63D0\u4EA4\u767B\u5F55");
1281
1464
  let login;
1282
1465
  try {
1283
1466
  login = await this.workbuddyLogin.complete(input.challenge, input.code, input.verify ?? true);
@@ -1357,7 +1540,7 @@ var Manager = class {
1357
1540
  return this.state();
1358
1541
  }
1359
1542
  if (input.action === "account.export") {
1360
- if (input.revision !== revisionOf(JSON.parse(await readFile3(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
1543
+ if (input.revision !== revisionOf(JSON.parse(await readFile4(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
1361
1544
  const account = this.config.accounts.find((a) => a.id === input.id);
1362
1545
  if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1363
1546
  let credential;
@@ -1379,7 +1562,7 @@ var Manager = class {
1379
1562
  } };
1380
1563
  }
1381
1564
  if (input.action === "config.export") {
1382
- if (input.revision !== revisionOf(JSON.parse(await readFile3(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
1565
+ if (input.revision !== revisionOf(JSON.parse(await readFile4(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
1383
1566
  try {
1384
1567
  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) } };
1385
1568
  } catch (e) {
@@ -1400,10 +1583,10 @@ var Manager = class {
1400
1583
  const account = this.config.accounts.find((a) => a.id === input.id);
1401
1584
  if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
1402
1585
  this.router.clients.delete(account.id);
1403
- await this.monitorAccount(account);
1586
+ await this.monitorAccount(account, { force: true });
1404
1587
  } else {
1405
1588
  await this.syncLocalAccounts(input.provider);
1406
- await this.monitorAll(input.provider);
1589
+ await this.monitorAll(input.provider, { force: true });
1407
1590
  }
1408
1591
  return this.state();
1409
1592
  }
@@ -1427,7 +1610,7 @@ var Manager = class {
1427
1610
  return this.state();
1428
1611
  }
1429
1612
  return withFileLock3(this.file, async () => {
1430
- let raw = JSON.parse(await readFile3(this.file, "utf8"));
1613
+ let raw = JSON.parse(await readFile4(this.file, "utf8"));
1431
1614
  if (input.revision !== revisionOf(raw)) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u4FDD\u5B58");
1432
1615
  raw.routes ??= structuredClone(this.config.routes);
1433
1616
  let staging;
@@ -1450,10 +1633,10 @@ var Manager = class {
1450
1633
  }
1451
1634
  if (!/^[a-zA-Z0-9_-]{1,64}$/.test(input.id ?? "") || !PROVIDERS.includes(input.provider)) bad("\u8D26\u53F7 ID \u6216\u4F9B\u5E94\u5546\u65E0\u6548");
1452
1635
  if (account && !replacing) bad("\u8D26\u53F7 ID \u5DF2\u5B58\u5728\uFF0C\u8BF7\u4F7F\u7528\u4E0D\u540C ID");
1453
- const base = join4(dirname3(this.file), "accounts");
1636
+ const base = join5(dirname3(this.file), "accounts");
1454
1637
  await mkdir2(base, { recursive: true, mode: 448 });
1455
- staging = await mkdtemp2(join4(base, `${input.id}-`));
1456
- const credentialFile = join4(staging, "credential.json"), identityFile = join4(staging, "identity.json");
1638
+ staging = await mkdtemp2(join5(base, `${input.id}-`));
1639
+ const credentialFile = join5(staging, "credential.json"), identityFile = join5(staging, "identity.json");
1457
1640
  const added = {
1458
1641
  id: input.id,
1459
1642
  name: input.name || account?.name || input.id,
@@ -1649,12 +1832,12 @@ var Manager = class {
1649
1832
  }
1650
1833
  watch(onError) {
1651
1834
  let timer;
1652
- const watcher = watch(dirname3(this.file), (_, filename) => {
1835
+ const watcher = watch2(dirname3(this.file), (_, filename) => {
1653
1836
  if (filename && String(filename) !== this.file.split(/[\\/]/).pop()) return;
1654
1837
  clearTimeout(timer);
1655
1838
  timer = setTimeout(() => {
1656
1839
  void withFileLock3(this.file, async () => {
1657
- const raw = JSON.parse(await readFile3(this.file, "utf8"));
1840
+ const raw = JSON.parse(await readFile4(this.file, "utf8"));
1658
1841
  if (this.closed || revisionOf(raw) === this.revision) return;
1659
1842
  const config = validateConfig(raw, this.file);
1660
1843
  this.raw = raw;
@@ -1719,10 +1902,11 @@ function managementHandler(manager) {
1719
1902
 
1720
1903
  export {
1721
1904
  version,
1905
+ PluginReload,
1722
1906
  MANAGEMENT_PATH,
1723
1907
  ManagementError,
1724
1908
  modelInfo,
1725
1909
  Manager,
1726
1910
  managementHandler
1727
1911
  };
1728
- //# sourceMappingURL=chunk-ILRW3ELD.js.map
1912
+ //# sourceMappingURL=chunk-I4EW5TI7.js.map