dsh-multi-router 0.5.5 → 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,14 +1,15 @@
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-PQETKUGT.js";
7
+ managementHandler,
8
+ version
9
+ } from "./chunk-JDZCWBA4.js";
9
10
  import {
10
11
  createAccountClient
11
- } from "./chunk-EGLF4CWD.js";
12
+ } from "./chunk-U2MU2M5C.js";
12
13
  import "./chunk-OFFVP6YN.js";
13
14
  import "./chunk-FTM7RHQV.js";
14
15
  import "./chunk-S5KMFAQM.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,23 +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();
368
+ manager?.workbuddyLogin.abort.abort();
263
369
  for (const release of releases) release();
264
- 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(() => {
265
371
  }), manager?.telemetry.queue.catch(() => {
266
372
  })]);
267
373
  });
@@ -279,6 +385,18 @@ async function apply(ctx, options = {}) {
279
385
  ctx.emit("llm/adapters-updated");
280
386
  }, { service: () => ctx.get("pluginManager"), profile: () => ctx.get("profileContext") });
281
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
+ });
282
400
  const metrics = new SessionMetrics(manager.telemetry);
283
401
  ctx.on("session/event", (session, event) => {
284
402
  try {
@@ -288,20 +406,6 @@ async function apply(ctx, options = {}) {
288
406
  }
289
407
  });
290
408
  ctx.on("session/disposed", (session) => metrics.dispose(session));
291
- await manager.syncLocalAccounts().catch(() => ctx.logger.warn("multi-router: local login discovery failed; manual import remains available"));
292
- await manager.finishInitialSetup().catch(() => ctx.logger.warn("multi-router: first-install guidance could not be saved"));
293
- manager.checkins.tick();
294
- void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
295
- if (disposed) return;
296
- void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
297
- void manager.monitorAll().catch(() => ctx.logger.warn("multi-router: usage persistence failed"));
298
- monitorTimer = setInterval(() => {
299
- void manager.syncLocalAccounts().then(() => manager.monitorAll()).catch(() => ctx.logger.warn("multi-router: local login discovery or usage persistence failed"));
300
- manager.checkins.tick();
301
- void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
302
- void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
303
- }, 6e4);
304
- monitorTimer.unref();
305
409
  for (const provider of ["multi-router", ...PROVIDERS]) {
306
410
  if (disposed) return;
307
411
  const isRouter = provider === "multi-router";
@@ -360,13 +464,35 @@ async function apply(ctx, options = {}) {
360
464
  releases.push(ctx.llm.registerAdapter([provider], wrapper.adapter));
361
465
  }
362
466
  stopWatch = manager.watch(() => ctx.logger.warn("multi-router: invalid config change; keeping last valid configuration"));
363
- ctx.inject?.(["webServer"], (webCtx) => {
364
- webCtx.effect(() => webCtx.webServer.register({ kind: "exact", path: MANAGEMENT_PATH, handler: managementHandler(manager) }));
365
- });
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();
366
491
  ctx.emit("llm/adapters-updated");
367
492
  } catch (error) {
368
493
  clearInterval(monitorTimer);
369
494
  stopWatch?.();
495
+ manager?.runtime?.stop();
370
496
  for (const release of releases) release();
371
497
  await Promise.all(shims.map((shim) => shim.close()));
372
498
  ctx.logger.error("multi-router: initialization failed", error.code ?? error.message);
package/lib/index.js CHANGED
@@ -1,14 +1,15 @@
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-PQETKUGT.js";
7
+ managementHandler,
8
+ version
9
+ } from "./chunk-JDZCWBA4.js";
9
10
  import {
10
11
  createAccountClient
11
- } from "./chunk-EGLF4CWD.js";
12
+ } from "./chunk-U2MU2M5C.js";
12
13
  import "./chunk-OFFVP6YN.js";
13
14
  import "./chunk-FTM7RHQV.js";
14
15
  import "./chunk-S5KMFAQM.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,23 +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();
368
+ manager?.workbuddyLogin.abort.abort();
263
369
  for (const release of releases) release();
264
- 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(() => {
265
371
  }), manager?.telemetry.queue.catch(() => {
266
372
  })]);
267
373
  });
@@ -279,6 +385,18 @@ async function apply(ctx, options = {}) {
279
385
  ctx.emit("llm/adapters-updated");
280
386
  }, { service: () => ctx.get("pluginManager"), profile: () => ctx.get("profileContext") });
281
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
+ });
282
400
  const metrics = new SessionMetrics(manager.telemetry);
283
401
  ctx.on("session/event", (session, event) => {
284
402
  try {
@@ -288,20 +406,6 @@ async function apply(ctx, options = {}) {
288
406
  }
289
407
  });
290
408
  ctx.on("session/disposed", (session) => metrics.dispose(session));
291
- await manager.syncLocalAccounts().catch(() => ctx.logger.warn("multi-router: local login discovery failed; manual import remains available"));
292
- await manager.finishInitialSetup().catch(() => ctx.logger.warn("multi-router: first-install guidance could not be saved"));
293
- manager.checkins.tick();
294
- void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
295
- if (disposed) return;
296
- void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
297
- void manager.monitorAll().catch(() => ctx.logger.warn("multi-router: usage persistence failed"));
298
- monitorTimer = setInterval(() => {
299
- void manager.syncLocalAccounts().then(() => manager.monitorAll()).catch(() => ctx.logger.warn("multi-router: local login discovery or usage persistence failed"));
300
- manager.checkins.tick();
301
- void manager.plugins.tick().catch(() => ctx.logger.warn("multi-router: automatic update check unavailable"));
302
- void manager.checkFreeModels().catch(() => ctx.logger.warn("multi-router: free catalog persistence failed"));
303
- }, 6e4);
304
- monitorTimer.unref();
305
409
  for (const provider of ["multi-router", ...PROVIDERS]) {
306
410
  if (disposed) return;
307
411
  const isRouter = provider === "multi-router";
@@ -360,13 +464,35 @@ async function apply(ctx, options = {}) {
360
464
  releases.push(ctx.llm.registerAdapter([provider], wrapper.adapter));
361
465
  }
362
466
  stopWatch = manager.watch(() => ctx.logger.warn("multi-router: invalid config change; keeping last valid configuration"));
363
- ctx.inject?.(["webServer"], (webCtx) => {
364
- webCtx.effect(() => webCtx.webServer.register({ kind: "exact", path: MANAGEMENT_PATH, handler: managementHandler(manager) }));
365
- });
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();
366
491
  ctx.emit("llm/adapters-updated");
367
492
  } catch (error) {
368
493
  clearInterval(monitorTimer);
369
494
  stopWatch?.();
495
+ manager?.runtime?.stop();
370
496
  for (const release of releases) release();
371
497
  await Promise.all(shims.map((shim) => shim.close()));
372
498
  ctx.logger.error("multi-router: initialization failed", error.code ?? error.message);