dsh-plugin-local-infer 0.1.0 → 0.1.2
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/LICENSE +7 -0
- package/README.md +8 -1
- package/cordis.patch.yml +6 -6
- package/docs/screenshot-20261014.png +0 -0
- package/docs/zh/README.md +100 -0
- package/dsh/client.js +644 -143
- package/dsh/host.js +1 -1
- package/package.json +14 -11
- package/packages/core/lib/adapter.d.ts +75 -4
- package/packages/core/lib/debug.d.ts +1 -1
- package/packages/core/lib/debug.js +1 -1
- package/packages/core/lib/index.d.ts +3 -2
- package/packages/core/lib/index.js +2 -1
- package/packages/core/lib/launch-command.d.ts +13 -0
- package/packages/core/lib/launch-command.js +64 -0
- package/packages/core/lib/models.d.ts +2 -1
- package/packages/core/lib/models.js +10 -1
- package/packages/core/lib/status.d.ts +9 -1
- package/packages/core/lib/status.js +6 -1
- package/packages/core/package.json +1 -1
- package/packages/providers/lib/acquire.d.ts +125 -0
- package/packages/providers/lib/acquire.js +325 -0
- package/packages/providers/lib/adapters.d.ts +43 -6
- package/packages/providers/lib/adapters.js +386 -58
- package/packages/providers/lib/debug.d.ts +1 -1
- package/packages/providers/lib/debug.js +1 -1
- package/packages/providers/lib/deps.d.ts +6 -4
- package/packages/providers/lib/deps.js +17 -3
- package/packages/providers/lib/engines.d.ts +6 -5
- package/packages/providers/lib/engines.js +12 -4
- package/packages/providers/lib/guidelines.d.ts +2 -2
- package/packages/providers/lib/guidelines.js +17 -1
- package/packages/providers/lib/hyperqwen.d.ts +2 -2
- package/packages/providers/lib/hyperqwen.js +1 -1
- package/packages/providers/lib/index.d.ts +10 -4
- package/packages/providers/lib/index.js +9 -5
- package/packages/providers/lib/infernix.d.ts +170 -0
- package/packages/providers/lib/infernix.js +671 -0
- package/packages/providers/lib/llamacpp.d.ts +2 -2
- package/packages/providers/lib/llamacpp.js +1 -1
- package/packages/providers/lib/model-defaults.d.ts +7 -5
- package/packages/providers/lib/model-defaults.js +28 -4
- package/packages/providers/lib/ninfer.d.ts +9 -2
- package/packages/providers/lib/ninfer.js +10 -3
- package/packages/providers/lib/platform.d.ts +31 -0
- package/packages/providers/lib/platform.js +58 -0
- package/packages/providers/lib/presets.d.ts +2 -2
- package/packages/providers/lib/presets.js +1 -1
- package/packages/providers/lib/strata.d.ts +2 -2
- package/packages/providers/lib/strata.js +1 -1
- package/packages/providers/lib/vram.d.ts +2 -2
- package/packages/providers/lib/vram.js +1 -1
- package/packages/providers/package.json +3 -3
- package/packages/service/lib/index.d.ts +63 -20
- package/packages/service/lib/index.js +279 -43
- package/packages/service/lib/types.d.ts +44 -2
- package/packages/service/package.json +2 -2
- package/packages/tool/lib/index.d.ts +1 -1
- package/packages/tool/lib/index.js +1 -1
- package/packages/tool/package.json +3 -3
- package/packages/ui/lib/deps.d.ts +2 -2
- package/packages/ui/lib/deps.js +1 -1
- package/packages/ui/lib/form.d.ts +2 -2
- package/packages/ui/lib/form.js +1 -1
- package/packages/ui/lib/index.d.ts +1 -1
- package/packages/ui/lib/index.js +1 -1
- package/packages/ui/lib/meter.d.ts +2 -2
- package/packages/ui/lib/meter.js +1 -1
- package/packages/ui/lib/route.d.ts +84 -13
- package/packages/ui/lib/route.js +496 -56
- package/packages/ui/lib/warnings.d.ts +2 -2
- package/packages/ui/lib/warnings.js +1 -1
- package/packages/ui/package.json +5 -5
package/packages/ui/lib/route.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* ~/.dsh/local-infer/config.json, shared with the CLI, so the browser never
|
|
13
13
|
* sees a secret it did not store. The routes are held by an effect, so they
|
|
14
14
|
* go when the plugin does.
|
|
15
|
-
* @module
|
|
15
|
+
* @module dsh-plugin-local-infer/ui/route
|
|
16
16
|
*/
|
|
17
17
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
18
|
import { homedir } from 'node:os';
|
|
@@ -20,8 +20,8 @@ import { dirname, join } from 'node:path';
|
|
|
20
20
|
// Relative, not the root package subpath: a self-referencing bare specifier
|
|
21
21
|
// only resolves when the package sits inside a node_modules tree, so a
|
|
22
22
|
// local-path or pnpm-linked install of this repo would fail the import.
|
|
23
|
-
import { CHAT_TEMPLATE_FILE, buildModelEntry, checkDeps, DEFAULT_LOG_PATH_TEMPLATE, hfChatTemplateCommand, hfDownloadCommand, launchRecordFromPlan, modelArtifactPath, modelEntriesForEngine, providerIdFor, renderInstallString, resolveLogPath, } from '../../core/lib/index.js';
|
|
24
|
-
import { CHAT_TEMPLATE_OPTIONS, defaultModelFor, defaultModelsForEngine, ENGINES, } from '../../providers/lib/index.js';
|
|
23
|
+
import { CHAT_TEMPLATE_FILE, buildModelEntry, checkDeps, DEFAULT_LOG_PATH_TEMPLATE, formatLaunchCommand, hfChatTemplateCommand, hfDownloadCommand, launchRecordFromPlan, modelArtifactPath, modelEntriesForEngine, providerIdFor, renderInstallString, resolveLogPath, } from '../../core/lib/index.js';
|
|
24
|
+
import { CHAT_TEMPLATE_OPTIONS, buildEngineRepo, defaultModelFor, defaultModelsForEngine, ENGINES, enginePlatformDefaults, infernixGetDownloads, nodeLifecycleHost, syncEngineRepo, } from '../../providers/lib/index.js';
|
|
25
25
|
import { renderDeps } from "./deps.js";
|
|
26
26
|
import { renderForm } from "./form.js";
|
|
27
27
|
import { renderMeter } from "./meter.js";
|
|
@@ -241,7 +241,16 @@ function parsePlatformConfigs(raw) {
|
|
|
241
241
|
if (typeof fields.engineFork === 'string') {
|
|
242
242
|
stored.engineFork = fields.engineFork;
|
|
243
243
|
}
|
|
244
|
-
if (
|
|
244
|
+
if (typeof fields.buildCommands === 'string') {
|
|
245
|
+
stored.buildCommands = fields.buildCommands;
|
|
246
|
+
}
|
|
247
|
+
if (typeof fields.dockerTag === 'string') {
|
|
248
|
+
stored.dockerTag = fields.dockerTag;
|
|
249
|
+
}
|
|
250
|
+
if (stored.engineDirectory !== undefined ||
|
|
251
|
+
stored.engineFork !== undefined ||
|
|
252
|
+
stored.buildCommands !== undefined ||
|
|
253
|
+
stored.dockerTag !== undefined) {
|
|
245
254
|
platform[engineId] = stored;
|
|
246
255
|
}
|
|
247
256
|
}
|
|
@@ -265,11 +274,55 @@ function parseStoredPlatformConfig(raw) {
|
|
|
265
274
|
if (typeof fields.engineFork === 'string') {
|
|
266
275
|
stored.engineFork = fields.engineFork;
|
|
267
276
|
}
|
|
268
|
-
if (
|
|
277
|
+
if (typeof fields.buildCommands === 'string') {
|
|
278
|
+
stored.buildCommands = fields.buildCommands;
|
|
279
|
+
}
|
|
280
|
+
if (typeof fields.dockerTag === 'string') {
|
|
281
|
+
stored.dockerTag = fields.dockerTag;
|
|
282
|
+
}
|
|
283
|
+
if (stored.engineDirectory === undefined &&
|
|
284
|
+
stored.engineFork === undefined &&
|
|
285
|
+
stored.buildCommands === undefined &&
|
|
286
|
+
stored.dockerTag === undefined) {
|
|
269
287
|
return undefined;
|
|
270
288
|
}
|
|
271
289
|
return stored;
|
|
272
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* relinkStoredProviders prices every stored provider launch record against the
|
|
293
|
+
* live process table through the service's re-link rule and returns the
|
|
294
|
+
* provider list the card should read: an adopted engine is re-marked plugin-
|
|
295
|
+
* launched and tied to its model by the service, and a liveness watcher starts
|
|
296
|
+
* on its port so a later pkill settles the stored state through the death
|
|
297
|
+
* observer without another summary read; a dead engine's record is cleared on
|
|
298
|
+
* disk here (the previous-pid list with it), so a recycled pid cannot re-link
|
|
299
|
+
* to a config whose engine is down. The route calls it on mount and the
|
|
300
|
+
* summary calls it per read, making re-attach independent of the card opening.
|
|
301
|
+
*/
|
|
302
|
+
export async function relinkStoredProviders(localInfer, stored) {
|
|
303
|
+
const recorded = stored.providers.filter((provider) => provider.launch !== undefined);
|
|
304
|
+
if (recorded.length === 0)
|
|
305
|
+
return stored.providers;
|
|
306
|
+
const outcomes = await localInfer.reLinkProviders(recorded.map((provider) => ({ id: provider.id, record: provider.launch })));
|
|
307
|
+
if (!recorded.some((provider) => outcomes[provider.id] === 'dead')) {
|
|
308
|
+
return stored.providers;
|
|
309
|
+
}
|
|
310
|
+
const providers = stored.providers.map((provider) => {
|
|
311
|
+
if (provider.launch === undefined || outcomes[provider.id] !== 'dead') {
|
|
312
|
+
return provider;
|
|
313
|
+
}
|
|
314
|
+
// Rebuild the dead entry without the launch key, so the record (including
|
|
315
|
+
// the previous-pid list) is gone from the file, not just ignored.
|
|
316
|
+
return {
|
|
317
|
+
id: provider.id,
|
|
318
|
+
engine: provider.engine,
|
|
319
|
+
config: provider.config,
|
|
320
|
+
platform: provider.platform,
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
writeConfig(stored.engine, stored.config, stored.allowRunDeps, stored.platform, stored.showHelp, providers, stored.logPath, stored.lastLogs);
|
|
324
|
+
return providers;
|
|
325
|
+
}
|
|
273
326
|
/**
|
|
274
327
|
* buildSummary assembles the card's read model for one engine and config: the
|
|
275
328
|
* static engine and model lists, the detected hardware, the form model the
|
|
@@ -359,40 +412,26 @@ export async function buildSummary(localInfer, request) {
|
|
|
359
412
|
// to init) is re-adopted as plugin-launched, so its status dot reads as
|
|
360
413
|
// running for its model and the OS-reserve resolver keeps the default. A
|
|
361
414
|
// record whose engine is dead is cleared (the previous-pid list with it),
|
|
362
|
-
// so a recycled pid cannot re-link to a config whose engine is down.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (recorded.some((provider) => outcomes[provider.id] === 'dead')) {
|
|
367
|
-
const providers = stored.providers.map((provider) => {
|
|
368
|
-
if (provider.launch === undefined || outcomes[provider.id] !== 'dead') {
|
|
369
|
-
return provider;
|
|
370
|
-
}
|
|
371
|
-
return {
|
|
372
|
-
id: provider.id,
|
|
373
|
-
engine: provider.engine,
|
|
374
|
-
config: provider.config,
|
|
375
|
-
platform: provider.platform,
|
|
376
|
-
};
|
|
377
|
-
});
|
|
378
|
-
writeConfig(stored.engine, stored.config, stored.allowRunDeps, stored.platform, stored.showHelp, providers, stored.logPath, stored.lastLogs);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
415
|
+
// so a recycled pid cannot re-link to a config whose engine is down. The
|
|
416
|
+
// adopted engines also get a liveness watcher, so a later pkill settles the
|
|
417
|
+
// stored state without waiting for another summary read.
|
|
418
|
+
const linkedProviders = await relinkStoredProviders(localInfer, stored);
|
|
381
419
|
// The provider list the card renders as collapsed entries: each saved
|
|
382
420
|
// config's engine and model feed the detector, which resolves the running-
|
|
383
421
|
// state its status dot paints. A provider the detector cannot tie to a
|
|
384
422
|
// confirmed model reads as unsure, so the dot never overstates a live
|
|
385
|
-
// engine.
|
|
386
|
-
|
|
423
|
+
// engine. The config rides into the detector so the signature prices the
|
|
424
|
+
// provider's own build mode and port, not the adapter's default-port guess.
|
|
425
|
+
const providerRefs = linkedProviders.map((provider) => {
|
|
387
426
|
const model = typeof provider.config.model === 'string' ? provider.config.model : undefined;
|
|
388
427
|
// exactOptionalPropertyTypes: include the model key only when it is a
|
|
389
428
|
// string, so a config that names no model never writes `undefined`.
|
|
390
429
|
return model !== undefined
|
|
391
|
-
? { id: provider.id, engine: provider.engine, model }
|
|
392
|
-
: { id: provider.id, engine: provider.engine };
|
|
430
|
+
? { id: provider.id, engine: provider.engine, model, config: provider.config }
|
|
431
|
+
: { id: provider.id, engine: provider.engine, config: provider.config };
|
|
393
432
|
});
|
|
394
433
|
const providerStatuses = providerRefs.length > 0 ? await localInfer.providerStatuses(providerRefs) : {};
|
|
395
|
-
const providers =
|
|
434
|
+
const providers = linkedProviders.map((provider) => ({
|
|
396
435
|
id: provider.id,
|
|
397
436
|
engine: provider.engine,
|
|
398
437
|
model: typeof provider.config.model === 'string' ? provider.config.model : '',
|
|
@@ -431,7 +470,23 @@ export async function buildSummary(localInfer, request) {
|
|
|
431
470
|
// read as unregistered, which hides the Remove button (a no-op removal).
|
|
432
471
|
}
|
|
433
472
|
return {
|
|
434
|
-
engines: ENGINES.map((entry) =>
|
|
473
|
+
engines: ENGINES.map((entry) => {
|
|
474
|
+
// The platform defaults ride the engine entry so the card's Engine
|
|
475
|
+
// platform section seeds the build-command and docker-tag placeholders
|
|
476
|
+
// from the same data the deploy step consumes.
|
|
477
|
+
const defaults = enginePlatformDefaults(entry.id);
|
|
478
|
+
return {
|
|
479
|
+
id: entry.id,
|
|
480
|
+
label: entry.label,
|
|
481
|
+
supports: entry.supports,
|
|
482
|
+
...(defaults.defaultBuildCommand !== undefined
|
|
483
|
+
? { defaultBuildCommand: defaults.defaultBuildCommand }
|
|
484
|
+
: {}),
|
|
485
|
+
...(defaults.defaultDockerTag !== undefined
|
|
486
|
+
? { defaultDockerTag: defaults.defaultDockerTag }
|
|
487
|
+
: {}),
|
|
488
|
+
};
|
|
489
|
+
}),
|
|
435
490
|
models: localInfer.registeredVramModels().map((model) => ({ id: model.id, source: model.source })),
|
|
436
491
|
hardware,
|
|
437
492
|
engineId: engine.id,
|
|
@@ -502,7 +557,10 @@ function setPlatformConfig(localInfer, engineId, config) {
|
|
|
502
557
|
if (config === undefined) {
|
|
503
558
|
return;
|
|
504
559
|
}
|
|
505
|
-
if (config.engineDirectory === undefined &&
|
|
560
|
+
if (config.engineDirectory === undefined &&
|
|
561
|
+
config.engineFork === undefined &&
|
|
562
|
+
config.buildCommands === undefined &&
|
|
563
|
+
config.dockerTag === undefined) {
|
|
506
564
|
return;
|
|
507
565
|
}
|
|
508
566
|
const platform = { engineId, ...config };
|
|
@@ -670,16 +728,56 @@ function resolveAdapter(localInfer, engineId) {
|
|
|
670
728
|
}
|
|
671
729
|
return adapter;
|
|
672
730
|
}
|
|
731
|
+
/**
|
|
732
|
+
* launchCommandNote renders the plan's child-process launch line for the
|
|
733
|
+
* card's Logs section: the core formatter's command under a [local-infer]
|
|
734
|
+
* launch prefix, newline-terminated. A plan that names no command (a bare
|
|
735
|
+
* docker plan with no argv) has no note, and the run logs stay as they are.
|
|
736
|
+
*/
|
|
737
|
+
function launchCommandNote(plan) {
|
|
738
|
+
const command = formatLaunchCommand(plan);
|
|
739
|
+
if (command === undefined)
|
|
740
|
+
return undefined;
|
|
741
|
+
return `[local-infer] launch: ${command}\n`;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* applyComposeArgs seeds a compose plan's compose arguments from the stored
|
|
745
|
+
* platform config (the card's docker-tag field for a compose engine, e.g.
|
|
746
|
+
* `--profile single`), so the up line the deploy runs and the down line the
|
|
747
|
+
* stop runs both select the profile the user named. A compose plan with no
|
|
748
|
+
* stored docker-tag keeps the engine's default profile; a non-compose plan is
|
|
749
|
+
* untouched.
|
|
750
|
+
*/
|
|
751
|
+
function applyComposeArgs(plan, localInfer, engineId) {
|
|
752
|
+
if (plan.kind !== 'compose')
|
|
753
|
+
return;
|
|
754
|
+
const platform = localInfer.enginePlatformConfig(engineId);
|
|
755
|
+
if (platform?.dockerTag !== undefined) {
|
|
756
|
+
plan.composeArgs = platform.dockerTag;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* persistStoredLogs overwrites the stored last-run logs (the card's Logs
|
|
761
|
+
* section shows them after a reload) and leaves every other stored field as
|
|
762
|
+
* the launch reads it.
|
|
763
|
+
*/
|
|
764
|
+
function persistStoredLogs(logs) {
|
|
765
|
+
const stored = readConfig();
|
|
766
|
+
writeConfig(stored.engine, stored.config, stored.allowRunDeps, stored.platform, stored.showHelp, stored.providers, stored.logPath, logs);
|
|
767
|
+
}
|
|
673
768
|
/**
|
|
674
769
|
* launchEngine renders the plan the registered adapter produces for the config
|
|
675
770
|
* and the detected hardware, then runs the service's launch entry point
|
|
676
771
|
* (deploy, then the health poll). The detail the launch returns names the
|
|
677
|
-
* broken step when the engine does not come up. The launch
|
|
772
|
+
* broken step when the engine does not come up. The launch line (the engine
|
|
773
|
+
* binary and its arguments) leads the run logs the card's Logs section and
|
|
774
|
+
* the log file carry, so a broken setup diagnoses against the exact child
|
|
775
|
+
* process the launch started. The launch step persists the
|
|
678
776
|
* launch record (the engine pid, start time, launch arguments, and the DSH
|
|
679
777
|
* process pid that launched it) on the request's provider entry, so a DSH
|
|
680
778
|
* restart can re-link a surviving engine to its config.
|
|
681
779
|
*/
|
|
682
|
-
export async function launchEngine(localInfer, request) {
|
|
780
|
+
export async function launchEngine(localInfer, request, onProgress) {
|
|
683
781
|
const stored = readConfig();
|
|
684
782
|
const engineId = request.engine ?? stored.engine;
|
|
685
783
|
const config = request.config ?? stored.config;
|
|
@@ -692,7 +790,56 @@ export async function launchEngine(localInfer, request) {
|
|
|
692
790
|
if (request.platform !== undefined) {
|
|
693
791
|
setPlatformConfig(localInfer, engineId, request.platform);
|
|
694
792
|
}
|
|
695
|
-
const
|
|
793
|
+
const plan = adapter.renderLaunch(config, hardware);
|
|
794
|
+
applyComposeArgs(plan, localInfer, engineId);
|
|
795
|
+
const commandNote = launchCommandNote(plan);
|
|
796
|
+
if (commandNote !== undefined) {
|
|
797
|
+
// Persist before the deploy starts: a slow or hung launch keeps the
|
|
798
|
+
// command readable in the Logs section across a card reload, where the
|
|
799
|
+
// engine output has not landed yet.
|
|
800
|
+
persistStoredLogs(commandNote);
|
|
801
|
+
}
|
|
802
|
+
// The prereqs phase: the system-dependency install the local build needs. It
|
|
803
|
+
// runs only while the Play/Run trust gate is on, the same rule the Get step
|
|
804
|
+
// applies; a failed install aborts the launch before the deploy starts. The
|
|
805
|
+
// card's button reads "Prereqs..." for this phase, so the phase is reported
|
|
806
|
+
// only while an install actually runs (the gate on), not when the deps are
|
|
807
|
+
// already satisfied and the phase is skipped.
|
|
808
|
+
if (stored.allowRunDeps === true) {
|
|
809
|
+
onProgress?.('prereqs');
|
|
810
|
+
const buildMode = plan.kind === 'docker' ? 'docker' : 'local';
|
|
811
|
+
const install = await localInfer.runInstall(engineId, buildMode, hardware);
|
|
812
|
+
if (!install.ok) {
|
|
813
|
+
const result = {
|
|
814
|
+
plan,
|
|
815
|
+
healthy: false,
|
|
816
|
+
detail: 'a system dependency install failed',
|
|
817
|
+
};
|
|
818
|
+
persistLaunchLogs(result, engineId, config);
|
|
819
|
+
persistLaunchRecord(result, engineId, config, request.providerId);
|
|
820
|
+
return result;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
// The trust gate and the auto-rebuild flag ride into the deploy step, which
|
|
824
|
+
// reads them to decide whether a rebuild's missing-binary symlink into
|
|
825
|
+
// /usr/local/bin runs (on) or is written to the card's Logs section (off),
|
|
826
|
+
// and whether the git sync (fetch, rebase, rebuild) runs at all.
|
|
827
|
+
const result = await localInfer.launch(plan, {
|
|
828
|
+
...(onProgress !== undefined ? { onProgress } : {}),
|
|
829
|
+
allowRunDeps: stored.allowRunDeps,
|
|
830
|
+
...(request.autoRebuild !== undefined ? { autoRebuild: request.autoRebuild } : {}),
|
|
831
|
+
});
|
|
832
|
+
// Order the run logs: the build output (the rebuild decision, the build
|
|
833
|
+
// command, and its output) leads, the launch command follows it (after a
|
|
834
|
+
// successful build, or a direct launch with no build), and the engine's
|
|
835
|
+
// runtime output comes last. A deploy that failed before the engine printed
|
|
836
|
+
// anything still reports the command it tried to run.
|
|
837
|
+
const buildLogs = result.buildLogs ?? '';
|
|
838
|
+
const engineLogs = result.logs ?? '';
|
|
839
|
+
result.logs = buildLogs + (commandNote ?? '') + engineLogs;
|
|
840
|
+
if (result.buildLogs !== undefined) {
|
|
841
|
+
delete result.buildLogs;
|
|
842
|
+
}
|
|
696
843
|
persistLaunchLogs(result, engineId, config);
|
|
697
844
|
persistLaunchRecord(result, engineId, config, request.providerId);
|
|
698
845
|
return result;
|
|
@@ -756,6 +903,47 @@ function clearLaunchRecord(engineId, config, providerId) {
|
|
|
756
903
|
});
|
|
757
904
|
writeConfig(stored.engine, stored.config, stored.allowRunDeps, stored.platform, stored.showHelp, providers, stored.logPath, stored.lastLogs);
|
|
758
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* handleEngineDeath persists a provider entry's state when its engine process
|
|
908
|
+
* dies outside the plugin's stop path: a crash, a pkill of a plugin-launched
|
|
909
|
+
* child seen through the service's exit watch, or the poll-detected exit of a
|
|
910
|
+
* process adopted after a DSH restart. The entry whose launch record names the
|
|
911
|
+
* dead pid (or the dead engine-and-port, when the report carries no pid match)
|
|
912
|
+
* has its record cleared, so a restart never re-links a dead pid, and the exit
|
|
913
|
+
* line lands in the card's last-run logs beside the engine's own output. An
|
|
914
|
+
* event that matches no entry carrying a record (a stop the route already
|
|
915
|
+
* cleared, an engine the plugin never tracked) is a no-op, which keeps an
|
|
916
|
+
* expected stop quiet.
|
|
917
|
+
*/
|
|
918
|
+
export function handleEngineDeath(event) {
|
|
919
|
+
const stored = readConfig();
|
|
920
|
+
let matched = false;
|
|
921
|
+
const providers = stored.providers.map((entry) => {
|
|
922
|
+
const record = entry.launch;
|
|
923
|
+
if (record === undefined)
|
|
924
|
+
return entry;
|
|
925
|
+
const byPid = event.pid !== undefined && record.pid === event.pid;
|
|
926
|
+
const byEnginePort = event.port !== undefined && record.engineId === entry.engine && record.port === event.port;
|
|
927
|
+
if (!byPid && !byEnginePort)
|
|
928
|
+
return entry;
|
|
929
|
+
matched = true;
|
|
930
|
+
// Rebuild the entry without the launch key, so the record (including the
|
|
931
|
+
// previous-pid list) is gone from the file, not just ignored.
|
|
932
|
+
return { id: entry.id, engine: entry.engine, config: entry.config, platform: entry.platform };
|
|
933
|
+
});
|
|
934
|
+
if (!matched)
|
|
935
|
+
return;
|
|
936
|
+
const signalNote = event.signal !== null && event.signal !== undefined
|
|
937
|
+
? ` on signal ${event.signal}`
|
|
938
|
+
: ` with exit code ${event.code}`;
|
|
939
|
+
const note = `[local-infer] engine ${event.engineId}` +
|
|
940
|
+
`${event.port !== undefined ? ` (port ${event.port})` : ''}` +
|
|
941
|
+
` pid ${event.pid ?? 'unknown'} exited${signalNote}` +
|
|
942
|
+
`${event.detail !== undefined ? `: ${event.detail}` : ''}\n`;
|
|
943
|
+
writeConfig(stored.engine, stored.config, stored.allowRunDeps, stored.platform, stored.showHelp, providers, stored.logPath,
|
|
944
|
+
// Keep the last-run tail bounded, the same rule the launch logs cap uses.
|
|
945
|
+
(stored.lastLogs + note).slice(-65536));
|
|
946
|
+
}
|
|
759
947
|
/**
|
|
760
948
|
* persistLaunchLogs stores a launch's captured output as the last run and
|
|
761
949
|
* writes it to the configured log file. The log file is only written when the
|
|
@@ -794,9 +982,143 @@ export async function stopEngine(localInfer, request) {
|
|
|
794
982
|
const config = request.config ?? stored.config;
|
|
795
983
|
const adapter = resolveAdapter(localInfer, engineId);
|
|
796
984
|
const hardware = await localInfer.detectHardware();
|
|
797
|
-
|
|
985
|
+
const plan = adapter.renderLaunch(config, hardware);
|
|
986
|
+
applyComposeArgs(plan, localInfer, engineId);
|
|
987
|
+
await localInfer.stop(plan);
|
|
798
988
|
clearLaunchRecord(engineId, config, request.providerId);
|
|
799
989
|
}
|
|
990
|
+
/**
|
|
991
|
+
* resolveEngineDirectory picks the checkout the Get step syncs: the Engine
|
|
992
|
+
* Directory override when it names a path, else a per-engine folder inside
|
|
993
|
+
* the model directory (the default model directory, or the config's
|
|
994
|
+
* override), so a Get with no override still has a real place to live.
|
|
995
|
+
*/
|
|
996
|
+
function resolveEngineDirectory(localInfer, config, engineId, platform) {
|
|
997
|
+
const override = platform?.engineDirectory;
|
|
998
|
+
if (override !== undefined && override.trim() !== '') {
|
|
999
|
+
return override;
|
|
1000
|
+
}
|
|
1001
|
+
const rawModelDir = config.modelDirectory;
|
|
1002
|
+
const modelDir = typeof rawModelDir === 'string' && rawModelDir.trim() !== ''
|
|
1003
|
+
? rawModelDir
|
|
1004
|
+
: localInfer.defaultModelDirectory();
|
|
1005
|
+
return join(modelDir, engineId);
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* resolveModelDirectory picks the model directory a Get run downloads into:
|
|
1009
|
+
* the config's modelDirectory override when it names a path, else the
|
|
1010
|
+
* plugin's default model directory. The same rule the summary and deps routes
|
|
1011
|
+
* apply, so a Get and the card's Model Directory field agree on the target.
|
|
1012
|
+
*/
|
|
1013
|
+
function resolveModelDirectory(localInfer, config) {
|
|
1014
|
+
const rawModelDir = config.modelDirectory;
|
|
1015
|
+
return typeof rawModelDir === 'string' && rawModelDir.trim() !== ''
|
|
1016
|
+
? rawModelDir
|
|
1017
|
+
: localInfer.defaultModelDirectory();
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* getEngine is the Get step: it syncs the engine checkout (clone the git
|
|
1021
|
+
* repo, or the fork when the platform entry provides one; fetch and rebase
|
|
1022
|
+
* a clean checkout; abort with a Logs alert when the checkout has local
|
|
1023
|
+
* changes), runs the system dependency plan, and runs the engine's build
|
|
1024
|
+
* steps. Every command and its output streams through log as it lands, so
|
|
1025
|
+
* the card appends it to the Logs section live. The dependency install only
|
|
1026
|
+
* runs while the Play/Run trust gate is on; with the gate off the deps are
|
|
1027
|
+
* re-checked and a missing one aborts the step with the copy/paste note.
|
|
1028
|
+
*/
|
|
1029
|
+
export async function getEngine(localInfer, request, log, host = nodeLifecycleHost) {
|
|
1030
|
+
const stored = readConfig();
|
|
1031
|
+
const engineId = request.engine ?? stored.engine;
|
|
1032
|
+
const config = request.config ?? stored.config;
|
|
1033
|
+
// ENGINES is a known non-empty constant, so the fallback is always defined.
|
|
1034
|
+
const engine = ENGINES.find((entry) => entry.id === engineId) ?? ENGINES[0];
|
|
1035
|
+
// Register the request's platform override the same way launchEngine does,
|
|
1036
|
+
// so the next launch reads the Engine Directory / Engine Fork the Get used.
|
|
1037
|
+
if (request.platform !== undefined) {
|
|
1038
|
+
setPlatformConfig(localInfer, engineId, request.platform);
|
|
1039
|
+
}
|
|
1040
|
+
const platform = request.platform ?? stored.platform[engineId];
|
|
1041
|
+
const directory = resolveEngineDirectory(localInfer, config, engineId, platform);
|
|
1042
|
+
const fork = platform?.engineFork;
|
|
1043
|
+
log(`get ${engineId} in ${directory}\n`);
|
|
1044
|
+
const sync = await syncEngineRepo(host, { engineId, directory, ...(fork !== undefined && fork.trim() !== '' ? { fork } : {}) }, log);
|
|
1045
|
+
if (!sync.ok) {
|
|
1046
|
+
// A dirty checkout already alerted in the sync step; any other failure
|
|
1047
|
+
// names its step here.
|
|
1048
|
+
if (sync.aborted !== true && sync.detail !== undefined) {
|
|
1049
|
+
log(`!! ${sync.detail}\n`);
|
|
1050
|
+
}
|
|
1051
|
+
return { ok: false, ...(sync.detail !== undefined ? { detail: sync.detail } : {}) };
|
|
1052
|
+
}
|
|
1053
|
+
const hardware = await localInfer.detectHardware();
|
|
1054
|
+
const buildMode = resolveBuildMode(engine, config);
|
|
1055
|
+
log(`system dependencies (${buildMode})\n`);
|
|
1056
|
+
if (stored.allowRunDeps === true) {
|
|
1057
|
+
const result = await localInfer.runInstall(engineId, buildMode, hardware);
|
|
1058
|
+
for (const entry of result.results) {
|
|
1059
|
+
log(` ${entry.id}: ${entry.status}${entry.detail !== undefined ? ` (${entry.detail})` : ''}\n`);
|
|
1060
|
+
}
|
|
1061
|
+
if (!result.ok) {
|
|
1062
|
+
log('!! a system dependency install failed; copy the install string from the panel\n');
|
|
1063
|
+
return { ok: false, detail: 'a system dependency install failed' };
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
const deps = await localInfer.deps(engineId, buildMode, hardware);
|
|
1068
|
+
const checks = await checkDeps(deps, hardware);
|
|
1069
|
+
let missing = false;
|
|
1070
|
+
for (const check of checks) {
|
|
1071
|
+
log(` ${check.dep.id}: ${check.status}\n`);
|
|
1072
|
+
if (check.status !== 'satisfied')
|
|
1073
|
+
missing = true;
|
|
1074
|
+
}
|
|
1075
|
+
if (missing) {
|
|
1076
|
+
log('!! a system dependency is missing and one-click install is off; copy the install string from the panel\n');
|
|
1077
|
+
return { ok: false, detail: 'a system dependency is missing' };
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const build = await buildEngineRepo(host, engineId, directory, log);
|
|
1081
|
+
if (!build.ok) {
|
|
1082
|
+
if (build.detail !== undefined) {
|
|
1083
|
+
log(`!! ${build.detail}\n`);
|
|
1084
|
+
}
|
|
1085
|
+
return { ok: false, ...(build.detail !== undefined ? { detail: build.detail } : {}) };
|
|
1086
|
+
}
|
|
1087
|
+
// The model artifacts a Get leaves ready to launch: the Infernix DFlash2
|
|
1088
|
+
// drafter (when a preset is selected) and the PLE n-gram volume (when the
|
|
1089
|
+
// model key names one), pulled through the hf CLI into the model directory.
|
|
1090
|
+
// A download failure aborts the Get, so a launch never reads a missing
|
|
1091
|
+
// drafter or table.
|
|
1092
|
+
if (engineId === 'infernix') {
|
|
1093
|
+
const modelDir = resolveModelDirectory(localInfer, config);
|
|
1094
|
+
for (const command of infernixGetDownloads(config, modelDir)) {
|
|
1095
|
+
log(`$ ${command}\n`);
|
|
1096
|
+
const outcome = host.runStream !== undefined
|
|
1097
|
+
? await host.runStream(command, (chunk) => log(chunk))
|
|
1098
|
+
: await host.run(command);
|
|
1099
|
+
if (!outcome.ok) {
|
|
1100
|
+
log(`!! ${command} failed: ${outcome.detail ?? 'unknown'}\n`);
|
|
1101
|
+
return { ok: false, detail: 'a model artifact download failed' };
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
log('get complete\n');
|
|
1106
|
+
return { ok: true };
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* persistGetLogs appends one Get run's streamed output to the stored last-
|
|
1110
|
+
* run logs (the card's Logs section shows it after a reload) and writes no
|
|
1111
|
+
* log file: the launch step owns the per-run files. The tail rule matches
|
|
1112
|
+
* the launch logs' cap, so a long build cannot grow the config without
|
|
1113
|
+
* bound.
|
|
1114
|
+
*/
|
|
1115
|
+
function persistGetLogs(output) {
|
|
1116
|
+
if (output === '')
|
|
1117
|
+
return;
|
|
1118
|
+
const stored = readConfig();
|
|
1119
|
+
const joined = `${stored.lastLogs}${stored.lastLogs === '' ? '' : '\n'}${output}`;
|
|
1120
|
+
writeConfig(stored.engine, stored.config, stored.allowRunDeps, stored.platform, stored.showHelp, stored.providers, stored.logPath, joined.slice(-65536));
|
|
1121
|
+
}
|
|
800
1122
|
/**
|
|
801
1123
|
* resolveContextWindow picks the context window the registrar publishes: the
|
|
802
1124
|
* config's maxContext when it names one, else the native ceiling of the
|
|
@@ -894,13 +1216,16 @@ export async function unregisterFor(localInfer, request) {
|
|
|
894
1216
|
return { removed: true };
|
|
895
1217
|
}
|
|
896
1218
|
/**
|
|
897
|
-
* registerLocalInferRoute registers the summary, save, deps, install,
|
|
898
|
-
* stop, register, and unregister routes on the web server and holds
|
|
899
|
-
* an effect, so a bundle switch off removes them before the next
|
|
900
|
-
* re-registers. The scoped context carries the webServer; the
|
|
901
|
-
* service is passed in because the scope only injects what it
|
|
1219
|
+
* registerLocalInferRoute registers the summary, save, deps, install, get,
|
|
1220
|
+
* launch, stop, register, and unregister routes on the web server and holds
|
|
1221
|
+
* them by an effect, so a bundle switch off removes them before the next
|
|
1222
|
+
* switch on re-registers. The scoped context carries the webServer; the
|
|
1223
|
+
* localInfer service is passed in because the scope only injects what it
|
|
1224
|
+
* names. The optional getHost is the lifecycle host the Get route's git and
|
|
1225
|
+
* build steps run through; it defaults to the node host, and a test injects
|
|
1226
|
+
* a stub so the route never clones a real repo in CI.
|
|
902
1227
|
*/
|
|
903
|
-
export function registerLocalInferRoute(scope, localInfer) {
|
|
1228
|
+
export function registerLocalInferRoute(scope, localInfer, getHost) {
|
|
904
1229
|
const summary = async (url) => {
|
|
905
1230
|
const engineParam = url.searchParams.get('engine');
|
|
906
1231
|
const configParam = url.searchParams.get('config');
|
|
@@ -990,23 +1315,33 @@ export function registerLocalInferRoute(scope, localInfer) {
|
|
|
990
1315
|
// reads, so a provider never carries a field it cannot price. A save
|
|
991
1316
|
// that updates an entry in place carries the entry's launch record
|
|
992
1317
|
// over, so editing a running provider's config never drops the record
|
|
993
|
-
// the restart re-link prices.
|
|
1318
|
+
// the restart re-link prices. A settings-only save skips the upsert:
|
|
1319
|
+
// it persists the global fields the toggle edits and keeps the stored
|
|
1320
|
+
// list intact, so the help and allow-run toggles never mint a
|
|
1321
|
+
// duplicate entry.
|
|
994
1322
|
const stored = readConfig();
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1323
|
+
let providers;
|
|
1324
|
+
if (body.settingsOnly === true) {
|
|
1325
|
+
providers = stored.providers;
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
const provider = {
|
|
1329
|
+
id: providerId ?? newProviderId(),
|
|
1330
|
+
engine,
|
|
1331
|
+
config,
|
|
1332
|
+
platform: platform[engine] ?? {},
|
|
1333
|
+
};
|
|
1334
|
+
if (providerId !== undefined) {
|
|
1335
|
+
const existing = stored.providers.find((entry) => entry.id === providerId);
|
|
1336
|
+
if (existing?.launch !== undefined) {
|
|
1337
|
+
provider.launch = existing.launch;
|
|
1338
|
+
}
|
|
1005
1339
|
}
|
|
1340
|
+
providers =
|
|
1341
|
+
providerId !== undefined
|
|
1342
|
+
? stored.providers.map((entry) => (entry.id === providerId ? provider : entry))
|
|
1343
|
+
: [provider, ...stored.providers];
|
|
1006
1344
|
}
|
|
1007
|
-
const providers = providerId !== undefined
|
|
1008
|
-
? stored.providers.map((entry) => (entry.id === providerId ? provider : entry))
|
|
1009
|
-
: [provider, ...stored.providers];
|
|
1010
1345
|
writeConfig(engine, config, allowRunDeps, platform, showHelp, providers, logPath);
|
|
1011
1346
|
// Re-sync the service registry with the saved platform map, so the next
|
|
1012
1347
|
// launch reads the platform config the user just saved.
|
|
@@ -1265,6 +1600,68 @@ export function registerLocalInferRoute(scope, localInfer) {
|
|
|
1265
1600
|
}
|
|
1266
1601
|
},
|
|
1267
1602
|
};
|
|
1603
|
+
// The Get route: a POST whose answer streams as plain text, so the card
|
|
1604
|
+
// appends the git sync, the dependency run, and the build output to its
|
|
1605
|
+
// Logs section as it lands. The full run persists as the last-run logs.
|
|
1606
|
+
const get = {
|
|
1607
|
+
name: 'local-infer-get',
|
|
1608
|
+
kind: 'exact',
|
|
1609
|
+
path: '/local-infer/get',
|
|
1610
|
+
handler: async (req, res) => {
|
|
1611
|
+
const send = (status, body) => {
|
|
1612
|
+
res.writeHead(status, { 'content-type': 'application/json' });
|
|
1613
|
+
res.end(JSON.stringify(body));
|
|
1614
|
+
};
|
|
1615
|
+
if (req.method !== 'POST') {
|
|
1616
|
+
res.writeHead(405).end();
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
try {
|
|
1620
|
+
const chunks = [];
|
|
1621
|
+
let total = 0;
|
|
1622
|
+
for await (const chunk of req) {
|
|
1623
|
+
total += chunk.length;
|
|
1624
|
+
if (total > 64 * 1024) {
|
|
1625
|
+
send(413, { error: 'get payload too large' });
|
|
1626
|
+
req.destroy();
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
chunks.push(chunk);
|
|
1630
|
+
}
|
|
1631
|
+
const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
1632
|
+
const request = {};
|
|
1633
|
+
if (typeof body.engine === 'string') {
|
|
1634
|
+
request.engine = body.engine;
|
|
1635
|
+
}
|
|
1636
|
+
if (body.config && typeof body.config === 'object') {
|
|
1637
|
+
request.config = body.config;
|
|
1638
|
+
}
|
|
1639
|
+
const platform = parseStoredPlatformConfig(body.platform);
|
|
1640
|
+
if (platform !== undefined) {
|
|
1641
|
+
request.platform = platform;
|
|
1642
|
+
}
|
|
1643
|
+
res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
|
|
1644
|
+
let collected = '';
|
|
1645
|
+
const log = (text) => {
|
|
1646
|
+
collected += text;
|
|
1647
|
+
if (typeof res.write === 'function') {
|
|
1648
|
+
res.write(text);
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
try {
|
|
1652
|
+
await getEngine(localInfer, request, log, getHost);
|
|
1653
|
+
}
|
|
1654
|
+
catch (error) {
|
|
1655
|
+
log(`!! get failed: ${String(error?.message ?? error)}\n`);
|
|
1656
|
+
}
|
|
1657
|
+
persistGetLogs(collected);
|
|
1658
|
+
res.end();
|
|
1659
|
+
}
|
|
1660
|
+
catch (error) {
|
|
1661
|
+
send(400, { error: String(error?.message ?? error) });
|
|
1662
|
+
}
|
|
1663
|
+
},
|
|
1664
|
+
};
|
|
1268
1665
|
// The launch and stop routes share a POST body parser: the engine, config,
|
|
1269
1666
|
// and platform override the plan renders from, falling back to the stored
|
|
1270
1667
|
// config per key. The stop route ignores the platform key. `providerId`
|
|
@@ -1285,6 +1682,9 @@ export function registerLocalInferRoute(scope, localInfer) {
|
|
|
1285
1682
|
if (typeof body.providerId === 'string' && body.providerId.trim() !== '') {
|
|
1286
1683
|
request.providerId = body.providerId;
|
|
1287
1684
|
}
|
|
1685
|
+
if (typeof body.autoRebuild === 'boolean') {
|
|
1686
|
+
request.autoRebuild = body.autoRebuild;
|
|
1687
|
+
}
|
|
1288
1688
|
return request;
|
|
1289
1689
|
};
|
|
1290
1690
|
const launch = {
|
|
@@ -1313,7 +1713,34 @@ export function registerLocalInferRoute(scope, localInfer) {
|
|
|
1313
1713
|
chunks.push(chunk);
|
|
1314
1714
|
}
|
|
1315
1715
|
const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
1316
|
-
|
|
1716
|
+
// The launch streams its progress as NDJSON: a step event as each phase
|
|
1717
|
+
// (prereqs, download, build, launch) begins, then a final result event.
|
|
1718
|
+
// The card's Launch control reads the step that is running and times
|
|
1719
|
+
// out only the launch (health-poll) step; the rebuild steps run as long
|
|
1720
|
+
// as they need to.
|
|
1721
|
+
res.writeHead(200, { 'content-type': 'application/x-ndjson; charset=utf-8' });
|
|
1722
|
+
const writeLine = (line) => {
|
|
1723
|
+
if (typeof res.write === 'function') {
|
|
1724
|
+
res.write(JSON.stringify(line) + '\n');
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
const onProgress = (step) => {
|
|
1728
|
+
writeLine({ step });
|
|
1729
|
+
};
|
|
1730
|
+
try {
|
|
1731
|
+
const result = await launchEngine(localInfer, launchRequestFromBody(body), onProgress);
|
|
1732
|
+
writeLine({ result });
|
|
1733
|
+
}
|
|
1734
|
+
catch (error) {
|
|
1735
|
+
// A launch that breaks before a plan renders (an unknown engine, a
|
|
1736
|
+
// hardware probe failure) still settles the card's control to the red
|
|
1737
|
+
// Logs state through a result event, not a bare 500.
|
|
1738
|
+
const detail = error instanceof RouteError
|
|
1739
|
+
? error.message
|
|
1740
|
+
: String(error?.message ?? error);
|
|
1741
|
+
writeLine({ result: { healthy: false, detail } });
|
|
1742
|
+
}
|
|
1743
|
+
res.end();
|
|
1317
1744
|
}
|
|
1318
1745
|
catch (error) {
|
|
1319
1746
|
if (error instanceof RouteError) {
|
|
@@ -1457,6 +1884,16 @@ export function registerLocalInferRoute(scope, localInfer) {
|
|
|
1457
1884
|
// route registration, so a launch before the first save reads the
|
|
1458
1885
|
// platform config the user last saved.
|
|
1459
1886
|
syncPlatformConfigs(localInfer, readConfig());
|
|
1887
|
+
// The engine death observer: a crash, a pkill, or the poll-detected exit
|
|
1888
|
+
// of an adopted process clears the provider entry's launch record and
|
|
1889
|
+
// notes the exit in the last-run logs the moment it happens, so the card
|
|
1890
|
+
// and a later restart read the provider stopped rather than stale.
|
|
1891
|
+
localInfer.setEngineDeathObserver(handleEngineDeath);
|
|
1892
|
+
// The mount re-link: re-adopt a surviving engine from a stored launch
|
|
1893
|
+
// record and start its liveness watcher without waiting for the card to
|
|
1894
|
+
// open the summary route, so a DSH that restarts re-attaches its engines
|
|
1895
|
+
// and a pkill after the restart still settles the stored state.
|
|
1896
|
+
void relinkStoredProviders(localInfer, readConfig()).catch(() => undefined);
|
|
1460
1897
|
const disposeSummary = scope.webServer.register(route);
|
|
1461
1898
|
const disposeSave = scope.webServer.register(save);
|
|
1462
1899
|
const disposeDeleteProvider = scope.webServer.register(deleteProvider);
|
|
@@ -1464,11 +1901,13 @@ export function registerLocalInferRoute(scope, localInfer) {
|
|
|
1464
1901
|
const disposeInstall = scope.webServer.register(install);
|
|
1465
1902
|
const disposeDownload = scope.webServer.register(download);
|
|
1466
1903
|
const disposeChatTemplate = scope.webServer.register(chatTemplate);
|
|
1904
|
+
const disposeGet = scope.webServer.register(get);
|
|
1467
1905
|
const disposeLaunch = scope.webServer.register(launch);
|
|
1468
1906
|
const disposeStop = scope.webServer.register(stop);
|
|
1469
1907
|
const disposeRegister = scope.webServer.register(register);
|
|
1470
1908
|
const disposeUnregister = scope.webServer.register(unregister);
|
|
1471
1909
|
return () => {
|
|
1910
|
+
localInfer.setEngineDeathObserver(undefined);
|
|
1472
1911
|
disposeSummary();
|
|
1473
1912
|
disposeSave();
|
|
1474
1913
|
disposeDeleteProvider();
|
|
@@ -1476,6 +1915,7 @@ export function registerLocalInferRoute(scope, localInfer) {
|
|
|
1476
1915
|
disposeInstall();
|
|
1477
1916
|
disposeDownload();
|
|
1478
1917
|
disposeChatTemplate();
|
|
1918
|
+
disposeGet();
|
|
1479
1919
|
disposeLaunch();
|
|
1480
1920
|
disposeStop();
|
|
1481
1921
|
disposeRegister();
|