dsh-multi-router 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/host.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  createShim
3
- } from "./chunk-CT6ZYIAF.js";
3
+ } from "./chunk-P43AWJ4Q.js";
4
4
  import {
5
5
  MANAGEMENT_PATH,
6
6
  Manager,
7
- managementHandler
8
- } from "./chunk-XKDXPB5T.js";
7
+ managementHandler,
8
+ version
9
+ } from "./chunk-JDZCWBA4.js";
9
10
  import {
10
11
  createAccountClient
11
12
  } from "./chunk-U2MU2M5C.js";
@@ -31,7 +32,7 @@ import {
31
32
  import z from "@deepseek-ai/schemastery";
32
33
  import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
33
34
  import { resolveRetryPolicy as resolveRetryPolicy2, resolveImageAttachmentAccess } from "@deepseek-ai/dsh-llm";
34
- import { join, resolve } from "node:path";
35
+ import { join as join2, resolve } from "node:path";
35
36
 
36
37
  // upstream/workbuddy/src/adapter.ts
37
38
  import { createProvider } from "@earendil-works/pi-ai";
@@ -245,24 +246,128 @@ var WorkBuddyPiAiAdapter = class extends PiAiAdapter {
245
246
  }
246
247
  };
247
248
 
249
+ // src/plugin-reload.mjs
250
+ import { readFile } from "node:fs/promises";
251
+ import { watch } from "node:fs";
252
+ import { join } from "node:path";
253
+ var PluginReload = class {
254
+ constructor({ service, profile, canReload, busy = () => false, now = Date.now, runningVersion }) {
255
+ Object.assign(this, { service, profile, canReload, busy, now, runningVersion });
256
+ this.phase = "current";
257
+ this.installedVersion = runningVersion;
258
+ this.retryAt = 0;
259
+ }
260
+ state() {
261
+ return { runningVersion: this.runningVersion, installedVersion: this.installedVersion, phase: this.phase, canReload: !!this.canReload(), message: this.message };
262
+ }
263
+ start() {
264
+ const dir = this.profile()?.dir;
265
+ if (dir) try {
266
+ this.watcher = watch(dir, () => this.schedule());
267
+ this.watcher.unref();
268
+ } catch {
269
+ }
270
+ this.schedule();
271
+ }
272
+ schedule(delay = 500) {
273
+ if (this.closed) return;
274
+ clearTimeout(this.timer);
275
+ this.timer = setTimeout(() => {
276
+ void this.check().catch(() => {
277
+ });
278
+ }, delay);
279
+ this.timer.unref();
280
+ }
281
+ stop() {
282
+ this.closed = true;
283
+ clearTimeout(this.timer);
284
+ this.watcher?.close();
285
+ }
286
+ check() {
287
+ if (this.closed) return Promise.resolve();
288
+ if (this.flight) return this.flight;
289
+ this.flight = this.inspect().finally(() => {
290
+ this.flight = void 0;
291
+ });
292
+ return this.flight;
293
+ }
294
+ async inspect() {
295
+ const dir = this.profile()?.dir;
296
+ if (!dir) return;
297
+ let manifest, installed;
298
+ try {
299
+ manifest = JSON.parse(await readFile(join(dir, "package.json"), "utf8"));
300
+ installed = JSON.parse(await readFile(join(dir, "node_modules/dsh-multi-router/package.json"), "utf8"));
301
+ } catch {
302
+ return;
303
+ }
304
+ if (this.closed || !manifest.dsh?.profile?.bundles?.includes("dsh-multi-router")) return;
305
+ if (installed.name !== "dsh-multi-router" || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(installed.version)) return;
306
+ this.installedVersion = installed.version;
307
+ if (installed.version === this.runningVersion) {
308
+ this.phase = "current";
309
+ this.message = void 0;
310
+ return;
311
+ }
312
+ if (!this.canReload() || !this.service()?.setBundleEnabled) {
313
+ this.phase = "restart-required";
314
+ this.message = "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u8FD0\u884C\u4E2D\u91CD\u8F7D\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
315
+ return;
316
+ }
317
+ if (this.busy()) {
318
+ this.phase = "waiting";
319
+ 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";
320
+ this.schedule(1e3);
321
+ return;
322
+ }
323
+ const root = join(dir, "node_modules/dsh-multi-router");
324
+ try {
325
+ const patch = await readFile(join(root, "cordis.patch.yml"), "utf8");
326
+ if (!patch.includes(`id: llm-multi-router-v2-${installed.version}
327
+ `) || !patch.includes(`name: ./lib/host-v${installed.version}.js`)) throw Error("legacy entry");
328
+ await readFile(join(root, `lib/host-v${installed.version}.js`));
329
+ } catch {
330
+ this.phase = "restart-required";
331
+ 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";
332
+ return;
333
+ }
334
+ if (this.now() < this.retryAt) return;
335
+ this.phase = "reloading";
336
+ this.message = "\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u4E0E\u914D\u7F6E\u9762\u677F\u2026";
337
+ this.retryAt = this.now() + 6e4;
338
+ try {
339
+ const result = await this.service().setBundleEnabled("dsh-multi-router", true);
340
+ if (this.closed) return;
341
+ this.phase = "restart-required";
342
+ 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";
343
+ } catch {
344
+ if (!this.closed) {
345
+ this.phase = "failed";
346
+ 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";
347
+ }
348
+ }
349
+ }
350
+ };
351
+
248
352
  // src/index.mjs
249
353
  var name = "dsh-multi-router";
250
354
  var inject = ["llm"];
251
- var defaultConfigFile = () => join(resolveDshHome(), "multi-router", "router.json");
355
+ var defaultConfigFile = () => join2(resolveDshHome(), "multi-router", "router.json");
252
356
  var Config = z.object({ configFile: z.string().description("Absolute path to multi-account router.json") });
253
357
  async function apply(ctx, options = {}) {
254
- let disposed = false, manager, stopWatch, monitorTimer;
358
+ let disposed = false, manager, stopWatch, monitorTimer, startup;
255
359
  const releases = [], shims = [], wrappers = [];
256
360
  ctx.effect(() => () => {
257
361
  disposed = true;
258
362
  stopWatch?.();
259
363
  clearInterval(monitorTimer);
364
+ manager?.runtime?.stop();
260
365
  if (manager) manager.closed = true;
261
366
  manager?.plugins.abort.abort();
262
367
  manager?.checkins.abort.abort();
263
368
  manager?.workbuddyLogin.abort.abort();
264
369
  for (const release of releases) release();
265
- return Promise.all([...shims.map((shim) => shim.close()), ...manager?.refreshes.values() ?? [], ...manager?.monitorFlights.values() ?? [], manager?.localScan, manager?.checkins.flight, manager?.plugins.flight, manager?.plugins.saveQueue.catch(() => {
370
+ return Promise.all([...shims.map((shim) => shim.close()), ...manager?.refreshes.values() ?? [], ...manager?.monitorFlights.values() ?? [], startup, manager?.localScan, manager?.checkins.flight, manager?.plugins.flight, manager?.plugins.saveQueue.catch(() => {
266
371
  }), manager?.telemetry.queue.catch(() => {
267
372
  })]);
268
373
  });
@@ -280,6 +385,18 @@ async function apply(ctx, options = {}) {
280
385
  ctx.emit("llm/adapters-updated");
281
386
  }, { service: () => ctx.get("pluginManager"), profile: () => ctx.get("profileContext") });
282
387
  await manager.load();
388
+ if (disposed) return;
389
+ manager.startup = { phase: "discovering" };
390
+ manager.runtime = new PluginReload({
391
+ runningVersion: version,
392
+ service: () => ctx.get("pluginManager"),
393
+ profile: () => ctx.get("profileContext"),
394
+ canReload: () => !!ctx.get("hmr"),
395
+ busy: () => !!(manager.localScan || manager.requests || manager.plugins.flight || manager.checkins.flight || manager.workbuddyLogin.flight || manager.workbuddyLogin.pending || manager.refreshes.size || manager.monitorFlights.size || shims.some((s) => s.active()))
396
+ });
397
+ ctx.inject?.(["webServer"], (webCtx) => {
398
+ webCtx.effect(() => webCtx.webServer.register({ kind: "exact", path: MANAGEMENT_PATH, handler: managementHandler(manager) }));
399
+ });
283
400
  const metrics = new SessionMetrics(manager.telemetry);
284
401
  ctx.on("session/event", (session, event) => {
285
402
  try {
@@ -289,20 +406,6 @@ async function apply(ctx, options = {}) {
289
406
  }
290
407
  });
291
408
  ctx.on("session/disposed", (session) => metrics.dispose(session));
292
- await manager.syncLocalAccounts().catch(() => ctx.logger.warn("multi-router: local login discovery failed; manual import remains available"));
293
- await manager.finishInitialSetup().catch(() => ctx.logger.warn("multi-router: first-install guidance could not be saved"));
294
- manager.checkins.tick();
295
- void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
296
- if (disposed) return;
297
- void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
298
- void manager.monitorAll().catch(() => ctx.logger.warn("multi-router: usage persistence failed"));
299
- monitorTimer = setInterval(() => {
300
- void manager.syncLocalAccounts().then(() => manager.monitorAll()).catch(() => ctx.logger.warn("multi-router: local login discovery or usage persistence failed"));
301
- manager.checkins.tick();
302
- void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
303
- void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
304
- }, 6e4);
305
- monitorTimer.unref();
306
409
  for (const provider of ["multi-router", ...PROVIDERS]) {
307
410
  if (disposed) return;
308
411
  const isRouter = provider === "multi-router";
@@ -361,13 +464,35 @@ async function apply(ctx, options = {}) {
361
464
  releases.push(ctx.llm.registerAdapter([provider], wrapper.adapter));
362
465
  }
363
466
  stopWatch = manager.watch(() => ctx.logger.warn("multi-router: invalid config change; keeping last valid configuration"));
364
- ctx.inject?.(["webServer"], (webCtx) => {
365
- webCtx.effect(() => webCtx.webServer.register({ kind: "exact", path: MANAGEMENT_PATH, handler: managementHandler(manager) }));
366
- });
467
+ startup = (async () => {
468
+ await manager.syncLocalAccounts().catch(() => ctx.logger.warn("multi-router: local login discovery failed; manual import remains available"));
469
+ if (disposed) return;
470
+ await manager.finishInitialSetup().catch(() => ctx.logger.warn("multi-router: first-install guidance could not be saved"));
471
+ if (disposed) return;
472
+ manager.startup = { phase: "ready" };
473
+ manager.checkins.tick();
474
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
475
+ void manager.monitorAll().catch(() => ctx.logger.warn("multi-router: usage persistence failed"));
476
+ })();
477
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
478
+ manager.runtime.start();
479
+ ctx.on("plugin-manager/changed", () => manager.runtime.schedule());
480
+ monitorTimer = setInterval(() => {
481
+ void manager.syncLocalAccounts().then(() => {
482
+ if (!disposed) return manager.monitorAll();
483
+ }).catch(() => ctx.logger.warn("multi-router: local login discovery or usage persistence failed"));
484
+ manager.checkins.tick();
485
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
486
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
487
+ void manager.runtime.check().catch(() => {
488
+ });
489
+ }, 6e4);
490
+ monitorTimer.unref();
367
491
  ctx.emit("llm/adapters-updated");
368
492
  } catch (error) {
369
493
  clearInterval(monitorTimer);
370
494
  stopWatch?.();
495
+ manager?.runtime?.stop();
371
496
  for (const release of releases) release();
372
497
  await Promise.all(shims.map((shim) => shim.close()));
373
498
  ctx.logger.error("multi-router: initialization failed", error.code ?? error.message);
package/lib/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  createShim
3
- } from "./chunk-CT6ZYIAF.js";
3
+ } from "./chunk-P43AWJ4Q.js";
4
4
  import {
5
5
  MANAGEMENT_PATH,
6
6
  Manager,
7
- managementHandler
8
- } from "./chunk-XKDXPB5T.js";
7
+ managementHandler,
8
+ version
9
+ } from "./chunk-JDZCWBA4.js";
9
10
  import {
10
11
  createAccountClient
11
12
  } from "./chunk-U2MU2M5C.js";
@@ -31,7 +32,7 @@ import {
31
32
  import z from "@deepseek-ai/schemastery";
32
33
  import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
33
34
  import { resolveRetryPolicy as resolveRetryPolicy2, resolveImageAttachmentAccess } from "@deepseek-ai/dsh-llm";
34
- import { join, resolve } from "node:path";
35
+ import { join as join2, resolve } from "node:path";
35
36
 
36
37
  // upstream/workbuddy/src/adapter.ts
37
38
  import { createProvider } from "@earendil-works/pi-ai";
@@ -245,24 +246,128 @@ var WorkBuddyPiAiAdapter = class extends PiAiAdapter {
245
246
  }
246
247
  };
247
248
 
249
+ // src/plugin-reload.mjs
250
+ import { readFile } from "node:fs/promises";
251
+ import { watch } from "node:fs";
252
+ import { join } from "node:path";
253
+ var PluginReload = class {
254
+ constructor({ service, profile, canReload, busy = () => false, now = Date.now, runningVersion }) {
255
+ Object.assign(this, { service, profile, canReload, busy, now, runningVersion });
256
+ this.phase = "current";
257
+ this.installedVersion = runningVersion;
258
+ this.retryAt = 0;
259
+ }
260
+ state() {
261
+ return { runningVersion: this.runningVersion, installedVersion: this.installedVersion, phase: this.phase, canReload: !!this.canReload(), message: this.message };
262
+ }
263
+ start() {
264
+ const dir = this.profile()?.dir;
265
+ if (dir) try {
266
+ this.watcher = watch(dir, () => this.schedule());
267
+ this.watcher.unref();
268
+ } catch {
269
+ }
270
+ this.schedule();
271
+ }
272
+ schedule(delay = 500) {
273
+ if (this.closed) return;
274
+ clearTimeout(this.timer);
275
+ this.timer = setTimeout(() => {
276
+ void this.check().catch(() => {
277
+ });
278
+ }, delay);
279
+ this.timer.unref();
280
+ }
281
+ stop() {
282
+ this.closed = true;
283
+ clearTimeout(this.timer);
284
+ this.watcher?.close();
285
+ }
286
+ check() {
287
+ if (this.closed) return Promise.resolve();
288
+ if (this.flight) return this.flight;
289
+ this.flight = this.inspect().finally(() => {
290
+ this.flight = void 0;
291
+ });
292
+ return this.flight;
293
+ }
294
+ async inspect() {
295
+ const dir = this.profile()?.dir;
296
+ if (!dir) return;
297
+ let manifest, installed;
298
+ try {
299
+ manifest = JSON.parse(await readFile(join(dir, "package.json"), "utf8"));
300
+ installed = JSON.parse(await readFile(join(dir, "node_modules/dsh-multi-router/package.json"), "utf8"));
301
+ } catch {
302
+ return;
303
+ }
304
+ if (this.closed || !manifest.dsh?.profile?.bundles?.includes("dsh-multi-router")) return;
305
+ if (installed.name !== "dsh-multi-router" || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(installed.version)) return;
306
+ this.installedVersion = installed.version;
307
+ if (installed.version === this.runningVersion) {
308
+ this.phase = "current";
309
+ this.message = void 0;
310
+ return;
311
+ }
312
+ if (!this.canReload() || !this.service()?.setBundleEnabled) {
313
+ this.phase = "restart-required";
314
+ this.message = "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u8FD0\u884C\u4E2D\u91CD\u8F7D\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
315
+ return;
316
+ }
317
+ if (this.busy()) {
318
+ this.phase = "waiting";
319
+ 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";
320
+ this.schedule(1e3);
321
+ return;
322
+ }
323
+ const root = join(dir, "node_modules/dsh-multi-router");
324
+ try {
325
+ const patch = await readFile(join(root, "cordis.patch.yml"), "utf8");
326
+ if (!patch.includes(`id: llm-multi-router-v2-${installed.version}
327
+ `) || !patch.includes(`name: ./lib/host-v${installed.version}.js`)) throw Error("legacy entry");
328
+ await readFile(join(root, `lib/host-v${installed.version}.js`));
329
+ } catch {
330
+ this.phase = "restart-required";
331
+ 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";
332
+ return;
333
+ }
334
+ if (this.now() < this.retryAt) return;
335
+ this.phase = "reloading";
336
+ this.message = "\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u4E0E\u914D\u7F6E\u9762\u677F\u2026";
337
+ this.retryAt = this.now() + 6e4;
338
+ try {
339
+ const result = await this.service().setBundleEnabled("dsh-multi-router", true);
340
+ if (this.closed) return;
341
+ this.phase = "restart-required";
342
+ 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";
343
+ } catch {
344
+ if (!this.closed) {
345
+ this.phase = "failed";
346
+ 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";
347
+ }
348
+ }
349
+ }
350
+ };
351
+
248
352
  // src/index.mjs
249
353
  var name = "dsh-multi-router";
250
354
  var inject = ["llm"];
251
- var defaultConfigFile = () => join(resolveDshHome(), "multi-router", "router.json");
355
+ var defaultConfigFile = () => join2(resolveDshHome(), "multi-router", "router.json");
252
356
  var Config = z.object({ configFile: z.string().description("Absolute path to multi-account router.json") });
253
357
  async function apply(ctx, options = {}) {
254
- let disposed = false, manager, stopWatch, monitorTimer;
358
+ let disposed = false, manager, stopWatch, monitorTimer, startup;
255
359
  const releases = [], shims = [], wrappers = [];
256
360
  ctx.effect(() => () => {
257
361
  disposed = true;
258
362
  stopWatch?.();
259
363
  clearInterval(monitorTimer);
364
+ manager?.runtime?.stop();
260
365
  if (manager) manager.closed = true;
261
366
  manager?.plugins.abort.abort();
262
367
  manager?.checkins.abort.abort();
263
368
  manager?.workbuddyLogin.abort.abort();
264
369
  for (const release of releases) release();
265
- return Promise.all([...shims.map((shim) => shim.close()), ...manager?.refreshes.values() ?? [], ...manager?.monitorFlights.values() ?? [], manager?.localScan, manager?.checkins.flight, manager?.plugins.flight, manager?.plugins.saveQueue.catch(() => {
370
+ return Promise.all([...shims.map((shim) => shim.close()), ...manager?.refreshes.values() ?? [], ...manager?.monitorFlights.values() ?? [], startup, manager?.localScan, manager?.checkins.flight, manager?.plugins.flight, manager?.plugins.saveQueue.catch(() => {
266
371
  }), manager?.telemetry.queue.catch(() => {
267
372
  })]);
268
373
  });
@@ -280,6 +385,18 @@ async function apply(ctx, options = {}) {
280
385
  ctx.emit("llm/adapters-updated");
281
386
  }, { service: () => ctx.get("pluginManager"), profile: () => ctx.get("profileContext") });
282
387
  await manager.load();
388
+ if (disposed) return;
389
+ manager.startup = { phase: "discovering" };
390
+ manager.runtime = new PluginReload({
391
+ runningVersion: version,
392
+ service: () => ctx.get("pluginManager"),
393
+ profile: () => ctx.get("profileContext"),
394
+ canReload: () => !!ctx.get("hmr"),
395
+ busy: () => !!(manager.localScan || manager.requests || manager.plugins.flight || manager.checkins.flight || manager.workbuddyLogin.flight || manager.workbuddyLogin.pending || manager.refreshes.size || manager.monitorFlights.size || shims.some((s) => s.active()))
396
+ });
397
+ ctx.inject?.(["webServer"], (webCtx) => {
398
+ webCtx.effect(() => webCtx.webServer.register({ kind: "exact", path: MANAGEMENT_PATH, handler: managementHandler(manager) }));
399
+ });
283
400
  const metrics = new SessionMetrics(manager.telemetry);
284
401
  ctx.on("session/event", (session, event) => {
285
402
  try {
@@ -289,20 +406,6 @@ async function apply(ctx, options = {}) {
289
406
  }
290
407
  });
291
408
  ctx.on("session/disposed", (session) => metrics.dispose(session));
292
- await manager.syncLocalAccounts().catch(() => ctx.logger.warn("multi-router: local login discovery failed; manual import remains available"));
293
- await manager.finishInitialSetup().catch(() => ctx.logger.warn("multi-router: first-install guidance could not be saved"));
294
- manager.checkins.tick();
295
- void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
296
- if (disposed) return;
297
- void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
298
- void manager.monitorAll().catch(() => ctx.logger.warn("multi-router: usage persistence failed"));
299
- monitorTimer = setInterval(() => {
300
- void manager.syncLocalAccounts().then(() => manager.monitorAll()).catch(() => ctx.logger.warn("multi-router: local login discovery or usage persistence failed"));
301
- manager.checkins.tick();
302
- void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
303
- void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
304
- }, 6e4);
305
- monitorTimer.unref();
306
409
  for (const provider of ["multi-router", ...PROVIDERS]) {
307
410
  if (disposed) return;
308
411
  const isRouter = provider === "multi-router";
@@ -361,13 +464,35 @@ async function apply(ctx, options = {}) {
361
464
  releases.push(ctx.llm.registerAdapter([provider], wrapper.adapter));
362
465
  }
363
466
  stopWatch = manager.watch(() => ctx.logger.warn("multi-router: invalid config change; keeping last valid configuration"));
364
- ctx.inject?.(["webServer"], (webCtx) => {
365
- webCtx.effect(() => webCtx.webServer.register({ kind: "exact", path: MANAGEMENT_PATH, handler: managementHandler(manager) }));
366
- });
467
+ startup = (async () => {
468
+ await manager.syncLocalAccounts().catch(() => ctx.logger.warn("multi-router: local login discovery failed; manual import remains available"));
469
+ if (disposed) return;
470
+ await manager.finishInitialSetup().catch(() => ctx.logger.warn("multi-router: first-install guidance could not be saved"));
471
+ if (disposed) return;
472
+ manager.startup = { phase: "ready" };
473
+ manager.checkins.tick();
474
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
475
+ void manager.monitorAll().catch(() => ctx.logger.warn("multi-router: usage persistence failed"));
476
+ })();
477
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
478
+ manager.runtime.start();
479
+ ctx.on("plugin-manager/changed", () => manager.runtime.schedule());
480
+ monitorTimer = setInterval(() => {
481
+ void manager.syncLocalAccounts().then(() => {
482
+ if (!disposed) return manager.monitorAll();
483
+ }).catch(() => ctx.logger.warn("multi-router: local login discovery or usage persistence failed"));
484
+ manager.checkins.tick();
485
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
486
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
487
+ void manager.runtime.check().catch(() => {
488
+ });
489
+ }, 6e4);
490
+ monitorTimer.unref();
367
491
  ctx.emit("llm/adapters-updated");
368
492
  } catch (error) {
369
493
  clearInterval(monitorTimer);
370
494
  stopWatch?.();
495
+ manager?.runtime?.stop();
371
496
  for (const release of releases) release();
372
497
  await Promise.all(shims.map((shim) => shim.close()));
373
498
  ctx.logger.error("multi-router: initialization failed", error.code ?? error.message);