dsh-multi-router 0.5.6 → 0.5.8

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-ILRW3ELD.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,132 @@ 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
+ function outsideActivation(hmr, operation) {
254
+ const scope = hmr?.executing;
255
+ return typeof scope?.exit === "function" ? scope.exit(operation) : operation();
256
+ }
257
+ var PluginReload = class {
258
+ constructor({ service, profile, canReload, hmr = () => void 0, busy = () => false, now = Date.now, runningVersion }) {
259
+ Object.assign(this, { service, profile, canReload, hmr, busy, now, runningVersion });
260
+ this.phase = "current";
261
+ this.installedVersion = runningVersion;
262
+ this.retryAt = 0;
263
+ }
264
+ state() {
265
+ return { runningVersion: this.runningVersion, installedVersion: this.installedVersion, phase: this.phase, canReload: !!this.canReload(), message: this.message };
266
+ }
267
+ start() {
268
+ const dir = this.profile()?.dir;
269
+ if (dir) try {
270
+ this.watcher = watch(dir, () => this.schedule());
271
+ this.watcher.unref();
272
+ } catch {
273
+ }
274
+ this.schedule();
275
+ }
276
+ schedule(delay = 500) {
277
+ if (this.closed) return;
278
+ clearTimeout(this.timer);
279
+ this.timer = setTimeout(() => {
280
+ void this.check().catch(() => {
281
+ });
282
+ }, delay);
283
+ this.timer.unref();
284
+ }
285
+ stop() {
286
+ this.closed = true;
287
+ clearTimeout(this.timer);
288
+ this.watcher?.close();
289
+ }
290
+ check() {
291
+ if (this.closed) return Promise.resolve();
292
+ if (this.flight) return this.flight;
293
+ this.flight = this.inspect().finally(() => {
294
+ this.flight = void 0;
295
+ });
296
+ return this.flight;
297
+ }
298
+ async inspect() {
299
+ const dir = this.profile()?.dir;
300
+ if (!dir) return;
301
+ let manifest, installed;
302
+ try {
303
+ manifest = JSON.parse(await readFile(join(dir, "package.json"), "utf8"));
304
+ installed = JSON.parse(await readFile(join(dir, "node_modules/dsh-multi-router/package.json"), "utf8"));
305
+ } catch {
306
+ return;
307
+ }
308
+ if (this.closed || !manifest.dsh?.profile?.bundles?.includes("dsh-multi-router")) return;
309
+ if (installed.name !== "dsh-multi-router" || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(installed.version)) return;
310
+ this.installedVersion = installed.version;
311
+ if (installed.version === this.runningVersion) {
312
+ this.phase = "current";
313
+ this.message = void 0;
314
+ return;
315
+ }
316
+ if (!this.canReload() || !this.service()?.setBundleEnabled) {
317
+ this.phase = "restart-required";
318
+ this.message = "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u8FD0\u884C\u4E2D\u91CD\u8F7D\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
319
+ return;
320
+ }
321
+ if (this.busy()) {
322
+ this.phase = "waiting";
323
+ 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";
324
+ this.schedule(1e3);
325
+ return;
326
+ }
327
+ const root = join(dir, "node_modules/dsh-multi-router");
328
+ try {
329
+ const patch = await readFile(join(root, "cordis.patch.yml"), "utf8");
330
+ if (!patch.includes(`id: llm-multi-router-v2-${installed.version}
331
+ `) || !patch.includes(`name: ./lib/host-v${installed.version}.js`)) throw Error("legacy entry");
332
+ await readFile(join(root, `lib/host-v${installed.version}.js`));
333
+ } catch {
334
+ this.phase = "restart-required";
335
+ 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";
336
+ return;
337
+ }
338
+ if (this.now() < this.retryAt) return;
339
+ this.phase = "reloading";
340
+ this.message = "\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u4E0E\u914D\u7F6E\u9762\u677F\u2026";
341
+ this.retryAt = this.now() + 6e4;
342
+ try {
343
+ const result = await outsideActivation(this.hmr(), () => this.service().setBundleEnabled("dsh-multi-router", true));
344
+ if (this.closed) return;
345
+ this.phase = "restart-required";
346
+ 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";
347
+ } catch {
348
+ if (!this.closed) {
349
+ this.phase = "failed";
350
+ 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";
351
+ }
352
+ }
353
+ }
354
+ };
355
+
248
356
  // src/index.mjs
249
357
  var name = "dsh-multi-router";
250
358
  var inject = ["llm"];
251
- var defaultConfigFile = () => join(resolveDshHome(), "multi-router", "router.json");
359
+ var defaultConfigFile = () => join2(resolveDshHome(), "multi-router", "router.json");
252
360
  var Config = z.object({ configFile: z.string().description("Absolute path to multi-account router.json") });
253
361
  async function apply(ctx, options = {}) {
254
- let disposed = false, manager, stopWatch, monitorTimer;
362
+ let disposed = false, manager, stopWatch, monitorTimer, startup;
255
363
  const releases = [], shims = [], wrappers = [];
256
364
  ctx.effect(() => () => {
257
365
  disposed = true;
258
366
  stopWatch?.();
259
367
  clearInterval(monitorTimer);
368
+ manager?.runtime?.stop();
260
369
  if (manager) manager.closed = true;
261
370
  manager?.plugins.abort.abort();
262
371
  manager?.checkins.abort.abort();
263
372
  manager?.workbuddyLogin.abort.abort();
264
373
  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(() => {
374
+ 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
375
  }), manager?.telemetry.queue.catch(() => {
267
376
  })]);
268
377
  });
@@ -280,6 +389,19 @@ async function apply(ctx, options = {}) {
280
389
  ctx.emit("llm/adapters-updated");
281
390
  }, { service: () => ctx.get("pluginManager"), profile: () => ctx.get("profileContext") });
282
391
  await manager.load();
392
+ if (disposed) return;
393
+ manager.startup = { phase: "discovering" };
394
+ manager.runtime = new PluginReload({
395
+ runningVersion: version,
396
+ service: () => ctx.get("pluginManager"),
397
+ profile: () => ctx.get("profileContext"),
398
+ canReload: () => !!ctx.get("hmr"),
399
+ hmr: () => ctx.get("hmr"),
400
+ 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()))
401
+ });
402
+ ctx.inject?.(["webServer"], (webCtx) => {
403
+ webCtx.effect(() => webCtx.webServer.register({ kind: "exact", path: MANAGEMENT_PATH, handler: managementHandler(manager) }));
404
+ });
283
405
  const metrics = new SessionMetrics(manager.telemetry);
284
406
  ctx.on("session/event", (session, event) => {
285
407
  try {
@@ -289,20 +411,6 @@ async function apply(ctx, options = {}) {
289
411
  }
290
412
  });
291
413
  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
414
  for (const provider of ["multi-router", ...PROVIDERS]) {
307
415
  if (disposed) return;
308
416
  const isRouter = provider === "multi-router";
@@ -361,13 +469,35 @@ async function apply(ctx, options = {}) {
361
469
  releases.push(ctx.llm.registerAdapter([provider], wrapper.adapter));
362
470
  }
363
471
  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
- });
472
+ startup = (async () => {
473
+ await manager.syncLocalAccounts().catch(() => ctx.logger.warn("multi-router: local login discovery failed; manual import remains available"));
474
+ if (disposed) return;
475
+ await manager.finishInitialSetup().catch(() => ctx.logger.warn("multi-router: first-install guidance could not be saved"));
476
+ if (disposed) return;
477
+ manager.startup = { phase: "ready" };
478
+ manager.checkins.tick();
479
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
480
+ void manager.monitorAll().catch(() => ctx.logger.warn("multi-router: usage persistence failed"));
481
+ })();
482
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
483
+ manager.runtime.start();
484
+ ctx.on("plugin-manager/changed", () => manager.runtime.schedule());
485
+ monitorTimer = setInterval(() => {
486
+ void manager.syncLocalAccounts().then(() => {
487
+ if (!disposed) return manager.monitorAll();
488
+ }).catch(() => ctx.logger.warn("multi-router: local login discovery or usage persistence failed"));
489
+ manager.checkins.tick();
490
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
491
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
492
+ void manager.runtime.check().catch(() => {
493
+ });
494
+ }, 6e4);
495
+ monitorTimer.unref();
367
496
  ctx.emit("llm/adapters-updated");
368
497
  } catch (error) {
369
498
  clearInterval(monitorTimer);
370
499
  stopWatch?.();
500
+ manager?.runtime?.stop();
371
501
  for (const release of releases) release();
372
502
  await Promise.all(shims.map((shim) => shim.close()));
373
503
  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-ILRW3ELD.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,132 @@ 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
+ function outsideActivation(hmr, operation) {
254
+ const scope = hmr?.executing;
255
+ return typeof scope?.exit === "function" ? scope.exit(operation) : operation();
256
+ }
257
+ var PluginReload = class {
258
+ constructor({ service, profile, canReload, hmr = () => void 0, busy = () => false, now = Date.now, runningVersion }) {
259
+ Object.assign(this, { service, profile, canReload, hmr, busy, now, runningVersion });
260
+ this.phase = "current";
261
+ this.installedVersion = runningVersion;
262
+ this.retryAt = 0;
263
+ }
264
+ state() {
265
+ return { runningVersion: this.runningVersion, installedVersion: this.installedVersion, phase: this.phase, canReload: !!this.canReload(), message: this.message };
266
+ }
267
+ start() {
268
+ const dir = this.profile()?.dir;
269
+ if (dir) try {
270
+ this.watcher = watch(dir, () => this.schedule());
271
+ this.watcher.unref();
272
+ } catch {
273
+ }
274
+ this.schedule();
275
+ }
276
+ schedule(delay = 500) {
277
+ if (this.closed) return;
278
+ clearTimeout(this.timer);
279
+ this.timer = setTimeout(() => {
280
+ void this.check().catch(() => {
281
+ });
282
+ }, delay);
283
+ this.timer.unref();
284
+ }
285
+ stop() {
286
+ this.closed = true;
287
+ clearTimeout(this.timer);
288
+ this.watcher?.close();
289
+ }
290
+ check() {
291
+ if (this.closed) return Promise.resolve();
292
+ if (this.flight) return this.flight;
293
+ this.flight = this.inspect().finally(() => {
294
+ this.flight = void 0;
295
+ });
296
+ return this.flight;
297
+ }
298
+ async inspect() {
299
+ const dir = this.profile()?.dir;
300
+ if (!dir) return;
301
+ let manifest, installed;
302
+ try {
303
+ manifest = JSON.parse(await readFile(join(dir, "package.json"), "utf8"));
304
+ installed = JSON.parse(await readFile(join(dir, "node_modules/dsh-multi-router/package.json"), "utf8"));
305
+ } catch {
306
+ return;
307
+ }
308
+ if (this.closed || !manifest.dsh?.profile?.bundles?.includes("dsh-multi-router")) return;
309
+ if (installed.name !== "dsh-multi-router" || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(installed.version)) return;
310
+ this.installedVersion = installed.version;
311
+ if (installed.version === this.runningVersion) {
312
+ this.phase = "current";
313
+ this.message = void 0;
314
+ return;
315
+ }
316
+ if (!this.canReload() || !this.service()?.setBundleEnabled) {
317
+ this.phase = "restart-required";
318
+ this.message = "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u8FD0\u884C\u4E2D\u91CD\u8F7D\uFF0C\u8BF7\u5347\u7EA7 DSH \u6216\u91CD\u542F\u5E94\u7528\u3002";
319
+ return;
320
+ }
321
+ if (this.busy()) {
322
+ this.phase = "waiting";
323
+ 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";
324
+ this.schedule(1e3);
325
+ return;
326
+ }
327
+ const root = join(dir, "node_modules/dsh-multi-router");
328
+ try {
329
+ const patch = await readFile(join(root, "cordis.patch.yml"), "utf8");
330
+ if (!patch.includes(`id: llm-multi-router-v2-${installed.version}
331
+ `) || !patch.includes(`name: ./lib/host-v${installed.version}.js`)) throw Error("legacy entry");
332
+ await readFile(join(root, `lib/host-v${installed.version}.js`));
333
+ } catch {
334
+ this.phase = "restart-required";
335
+ 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";
336
+ return;
337
+ }
338
+ if (this.now() < this.retryAt) return;
339
+ this.phase = "reloading";
340
+ this.message = "\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u4E0E\u914D\u7F6E\u9762\u677F\u2026";
341
+ this.retryAt = this.now() + 6e4;
342
+ try {
343
+ const result = await outsideActivation(this.hmr(), () => this.service().setBundleEnabled("dsh-multi-router", true));
344
+ if (this.closed) return;
345
+ this.phase = "restart-required";
346
+ 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";
347
+ } catch {
348
+ if (!this.closed) {
349
+ this.phase = "failed";
350
+ 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";
351
+ }
352
+ }
353
+ }
354
+ };
355
+
248
356
  // src/index.mjs
249
357
  var name = "dsh-multi-router";
250
358
  var inject = ["llm"];
251
- var defaultConfigFile = () => join(resolveDshHome(), "multi-router", "router.json");
359
+ var defaultConfigFile = () => join2(resolveDshHome(), "multi-router", "router.json");
252
360
  var Config = z.object({ configFile: z.string().description("Absolute path to multi-account router.json") });
253
361
  async function apply(ctx, options = {}) {
254
- let disposed = false, manager, stopWatch, monitorTimer;
362
+ let disposed = false, manager, stopWatch, monitorTimer, startup;
255
363
  const releases = [], shims = [], wrappers = [];
256
364
  ctx.effect(() => () => {
257
365
  disposed = true;
258
366
  stopWatch?.();
259
367
  clearInterval(monitorTimer);
368
+ manager?.runtime?.stop();
260
369
  if (manager) manager.closed = true;
261
370
  manager?.plugins.abort.abort();
262
371
  manager?.checkins.abort.abort();
263
372
  manager?.workbuddyLogin.abort.abort();
264
373
  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(() => {
374
+ 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
375
  }), manager?.telemetry.queue.catch(() => {
267
376
  })]);
268
377
  });
@@ -280,6 +389,19 @@ async function apply(ctx, options = {}) {
280
389
  ctx.emit("llm/adapters-updated");
281
390
  }, { service: () => ctx.get("pluginManager"), profile: () => ctx.get("profileContext") });
282
391
  await manager.load();
392
+ if (disposed) return;
393
+ manager.startup = { phase: "discovering" };
394
+ manager.runtime = new PluginReload({
395
+ runningVersion: version,
396
+ service: () => ctx.get("pluginManager"),
397
+ profile: () => ctx.get("profileContext"),
398
+ canReload: () => !!ctx.get("hmr"),
399
+ hmr: () => ctx.get("hmr"),
400
+ 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()))
401
+ });
402
+ ctx.inject?.(["webServer"], (webCtx) => {
403
+ webCtx.effect(() => webCtx.webServer.register({ kind: "exact", path: MANAGEMENT_PATH, handler: managementHandler(manager) }));
404
+ });
283
405
  const metrics = new SessionMetrics(manager.telemetry);
284
406
  ctx.on("session/event", (session, event) => {
285
407
  try {
@@ -289,20 +411,6 @@ async function apply(ctx, options = {}) {
289
411
  }
290
412
  });
291
413
  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
414
  for (const provider of ["multi-router", ...PROVIDERS]) {
307
415
  if (disposed) return;
308
416
  const isRouter = provider === "multi-router";
@@ -361,13 +469,35 @@ async function apply(ctx, options = {}) {
361
469
  releases.push(ctx.llm.registerAdapter([provider], wrapper.adapter));
362
470
  }
363
471
  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
- });
472
+ startup = (async () => {
473
+ await manager.syncLocalAccounts().catch(() => ctx.logger.warn("multi-router: local login discovery failed; manual import remains available"));
474
+ if (disposed) return;
475
+ await manager.finishInitialSetup().catch(() => ctx.logger.warn("multi-router: first-install guidance could not be saved"));
476
+ if (disposed) return;
477
+ manager.startup = { phase: "ready" };
478
+ manager.checkins.tick();
479
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
480
+ void manager.monitorAll().catch(() => ctx.logger.warn("multi-router: usage persistence failed"));
481
+ })();
482
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
483
+ manager.runtime.start();
484
+ ctx.on("plugin-manager/changed", () => manager.runtime.schedule());
485
+ monitorTimer = setInterval(() => {
486
+ void manager.syncLocalAccounts().then(() => {
487
+ if (!disposed) return manager.monitorAll();
488
+ }).catch(() => ctx.logger.warn("multi-router: local login discovery or usage persistence failed"));
489
+ manager.checkins.tick();
490
+ void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
491
+ void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
492
+ void manager.runtime.check().catch(() => {
493
+ });
494
+ }, 6e4);
495
+ monitorTimer.unref();
367
496
  ctx.emit("llm/adapters-updated");
368
497
  } catch (error) {
369
498
  clearInterval(monitorTimer);
370
499
  stopWatch?.();
500
+ manager?.runtime?.stop();
371
501
  for (const release of releases) release();
372
502
  await Promise.all(shims.map((shim) => shim.close()));
373
503
  ctx.logger.error("multi-router: initialization failed", error.code ?? error.message);