@elizaos/plugin-registry 2.0.3-beta.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.
@@ -0,0 +1,1391 @@
1
+ import {
2
+ applyAdvancedCapabilitiesConfig,
3
+ applyPluginRuntimeMutation,
4
+ CORE_PLUGINS,
5
+ getPluginWidgets,
6
+ isAdvancedCapabilityPluginId,
7
+ loadElizaConfig,
8
+ OPTIONAL_CORE_PLUGINS,
9
+ resolveAdvancedCapabilitiesEnabled,
10
+ resolveDefaultAgentWorkspaceDir,
11
+ saveElizaConfig,
12
+ validatePluginConfig
13
+ } from "@elizaos/agent";
14
+ import { logger } from "@elizaos/core";
15
+ import {
16
+ asRecord,
17
+ isElizaSettingsDebugEnabled,
18
+ PostPluginCoreToggleRequestSchema,
19
+ PostPluginInstallRequestSchema,
20
+ PostPluginUninstallRequestSchema,
21
+ PostPluginUpdateRequestSchema,
22
+ PutPluginRequestSchema,
23
+ PutSecretsRequestSchema,
24
+ sanitizeForSettingsDebug,
25
+ settingsDebugCloudSummary
26
+ } from "@elizaos/shared";
27
+ function optionalPluginListId(npmName) {
28
+ if (npmName.startsWith("@elizaos/app-")) {
29
+ return npmName.slice("@elizaos/".length);
30
+ }
31
+ return npmName.replace("@elizaos/plugin-", "");
32
+ }
33
+ const ADVANCED_CAPABILITY_SERVICE_BY_PLUGIN_ID = {
34
+ experience: "EXPERIENCE",
35
+ personality: "CHARACTER_MANAGEMENT"
36
+ };
37
+ function getPluginHealthProbe(plugin) {
38
+ if (!plugin || typeof plugin !== "object") {
39
+ return null;
40
+ }
41
+ const record = plugin;
42
+ for (const key of ["health", "healthCheck", "testConnection", "test"]) {
43
+ const candidate = record[key];
44
+ if (typeof candidate === "function") {
45
+ return candidate;
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ function normalizeRegistryLookupKey(value) {
51
+ const trimmed = value?.trim();
52
+ return trimmed ? trimmed.toLowerCase() : null;
53
+ }
54
+ function registryLookupCandidates(plugin) {
55
+ const candidates = /* @__PURE__ */ new Set();
56
+ const add = (value) => {
57
+ const normalized = normalizeRegistryLookupKey(value);
58
+ if (normalized) candidates.add(normalized);
59
+ };
60
+ add(plugin.npmName);
61
+ add(plugin.name);
62
+ add(plugin.id);
63
+ add(`@elizaos/plugin-${plugin.id}`);
64
+ add(`@elizaos/app-${plugin.id}`);
65
+ return Array.from(candidates);
66
+ }
67
+ function applyRegistryMetadata(plugin, registryMetadataByName) {
68
+ const registryInfo = registryLookupCandidates(plugin).map((candidate) => registryMetadataByName.get(candidate)).find((candidate) => Boolean(candidate));
69
+ if (!registryInfo) {
70
+ if (plugin.npmName?.startsWith("@elizaos/")) {
71
+ plugin.origin ??= "builtin";
72
+ plugin.registrySource ??= "builtin";
73
+ plugin.support ??= "first-party";
74
+ plugin.builtIn ??= true;
75
+ plugin.firstParty ??= true;
76
+ plugin.thirdParty ??= false;
77
+ }
78
+ return;
79
+ }
80
+ plugin.directory = registryInfo.directory ?? plugin.directory ?? null;
81
+ plugin.registryKind = registryInfo.registryKind ?? registryInfo.kind ?? plugin.registryKind;
82
+ plugin.origin = registryInfo.origin ?? plugin.origin;
83
+ plugin.registrySource = registryInfo.source ?? plugin.registrySource;
84
+ plugin.support = registryInfo.support ?? plugin.support;
85
+ plugin.builtIn = registryInfo.builtIn ?? plugin.builtIn;
86
+ plugin.firstParty = registryInfo.firstParty ?? plugin.firstParty;
87
+ plugin.thirdParty = registryInfo.thirdParty ?? plugin.thirdParty;
88
+ plugin.status = registryInfo.status ?? plugin.status;
89
+ plugin.homepage = plugin.homepage ?? registryInfo.homepage ?? void 0;
90
+ plugin.repository = plugin.repository ?? `https://github.com/${registryInfo.gitRepo}`;
91
+ plugin.latestVersion = plugin.latestVersion ?? registryInfo.npm.v2Version ?? registryInfo.npm.v1Version ?? registryInfo.npm.v0Version ?? null;
92
+ }
93
+ const pluginsListInFlight = /* @__PURE__ */ new WeakMap();
94
+ function readCompatEnabledFromConfig(config, pluginId) {
95
+ const container = asRecord(config.connectors)?.[pluginId] ?? asRecord(config.streaming)?.[pluginId];
96
+ const value = asRecord(container)?.enabled;
97
+ return typeof value === "boolean" ? value : null;
98
+ }
99
+ function buildCoreToggleDiagnostics(config, npmName) {
100
+ const pluginId = optionalPluginListId(npmName);
101
+ const isOptional = OPTIONAL_CORE_PLUGINS.includes(
102
+ npmName
103
+ );
104
+ if (!isOptional) {
105
+ return null;
106
+ }
107
+ const allowList = new Set(config.plugins?.allow ?? []);
108
+ const enabledAllowList = allowList.has(npmName) || allowList.has(pluginId);
109
+ const entryEnabledRaw = config.plugins?.entries?.[pluginId]?.enabled;
110
+ const enabledEntries = typeof entryEnabledRaw === "boolean" ? entryEnabledRaw : null;
111
+ const enabledCompat = readCompatEnabledFromConfig(config, pluginId);
112
+ const driftFlags = [];
113
+ if (enabledEntries !== null && enabledEntries !== enabledAllowList) {
114
+ driftFlags.push("entries_vs_allowlist");
115
+ }
116
+ if (enabledEntries !== null && enabledCompat !== null && enabledEntries !== enabledCompat) {
117
+ driftFlags.push("entries_vs_compat");
118
+ }
119
+ return {
120
+ pluginId,
121
+ npmName,
122
+ enabled_allowlist: enabledAllowList,
123
+ enabled_entries: enabledEntries,
124
+ enabled_compat: enabledCompat,
125
+ drift_flags: driftFlags
126
+ };
127
+ }
128
+ async function handlePluginRoutes(ctx) {
129
+ const {
130
+ req,
131
+ res,
132
+ method,
133
+ pathname,
134
+ state,
135
+ json,
136
+ error,
137
+ readJsonBody,
138
+ scheduleRuntimeRestart,
139
+ restartRuntime,
140
+ BLOCKED_ENV_KEYS,
141
+ discoverInstalledPlugins,
142
+ maskValue,
143
+ aggregateSecrets,
144
+ readProviderCache,
145
+ paramKeyToCategory,
146
+ buildPluginEvmDiagnosticEntry,
147
+ EVM_PLUGIN_PACKAGE,
148
+ applyWhatsAppQrOverride,
149
+ applySignalQrOverride,
150
+ resolvePluginConfigMutationRejections,
151
+ requirePluginManager,
152
+ requireCoreManager
153
+ } = ctx;
154
+ const buildPluginsListSnapshot = async () => {
155
+ let freshConfig;
156
+ try {
157
+ freshConfig = loadElizaConfig();
158
+ } catch {
159
+ freshConfig = state.config;
160
+ }
161
+ const bundledIds = new Set(state.plugins.map((p) => p.id));
162
+ const installedEntries = discoverInstalledPlugins(freshConfig, bundledIds);
163
+ const allPlugins = [...state.plugins, ...installedEntries];
164
+ let installedMetadataByName = /* @__PURE__ */ new Map();
165
+ let registryMetadataByName = /* @__PURE__ */ new Map();
166
+ try {
167
+ const pluginManager = requirePluginManager(state.runtime);
168
+ const installed = await pluginManager.listInstalledPlugins();
169
+ installedMetadataByName = new Map(
170
+ installed.map((plugin) => [
171
+ plugin.name,
172
+ {
173
+ version: plugin.version,
174
+ releaseStream: plugin.releaseStream,
175
+ requestedVersion: plugin.requestedVersion,
176
+ latestVersion: plugin.latestVersion,
177
+ betaVersion: plugin.betaVersion
178
+ }
179
+ ])
180
+ );
181
+ const registry = await pluginManager.refreshRegistry();
182
+ registryMetadataByName = /* @__PURE__ */ new Map();
183
+ for (const [key, info] of registry) {
184
+ for (const candidate of [key, info.name, info.npm.package]) {
185
+ const normalized = normalizeRegistryLookupKey(candidate);
186
+ if (normalized) registryMetadataByName.set(normalized, info);
187
+ }
188
+ }
189
+ } catch {
190
+ }
191
+ const evmDiagnostic = buildPluginEvmDiagnosticEntry({
192
+ config: state.config,
193
+ runtime: state.runtime
194
+ });
195
+ const existingEvmPlugin = allPlugins.find(
196
+ (plugin) => plugin.id === "evm" || plugin.id === "wallet" || plugin.npmName === EVM_PLUGIN_PACKAGE
197
+ );
198
+ if (existingEvmPlugin) {
199
+ existingEvmPlugin.autoEnabled = evmDiagnostic.autoEnabled;
200
+ existingEvmPlugin.managementMode = "core-optional";
201
+ existingEvmPlugin.capabilityStatus = evmDiagnostic.capabilityStatus;
202
+ existingEvmPlugin.capabilityReason = evmDiagnostic.capabilityReason;
203
+ existingEvmPlugin.prerequisites = evmDiagnostic.prerequisites;
204
+ existingEvmPlugin.setupGuideUrl = existingEvmPlugin.setupGuideUrl ?? evmDiagnostic.setupGuideUrl;
205
+ existingEvmPlugin.tags = Array.from(
206
+ /* @__PURE__ */ new Set([...existingEvmPlugin.tags, ...evmDiagnostic.tags])
207
+ );
208
+ } else {
209
+ allPlugins.push(evmDiagnostic);
210
+ }
211
+ const configEntries = freshConfig.plugins?.entries;
212
+ const advancedCapabilitiesEnabled = resolveAdvancedCapabilitiesEnabled(freshConfig);
213
+ const loadedNames = state.runtime ? state.runtime.plugins.map((p) => p.name) : [];
214
+ for (const plugin of allPlugins) {
215
+ applyRegistryMetadata(plugin, registryMetadataByName);
216
+ const installedMetadata = (plugin.npmName ? installedMetadataByName.get(plugin.npmName) : null) ?? installedMetadataByName.get(plugin.name);
217
+ if (installedMetadata) {
218
+ plugin.version = installedMetadata.version ?? plugin.version;
219
+ plugin.releaseStream = installedMetadata.releaseStream ?? plugin.releaseStream;
220
+ plugin.requestedVersion = installedMetadata.requestedVersion ?? plugin.requestedVersion;
221
+ plugin.latestVersion = installedMetadata.latestVersion ?? plugin.latestVersion ?? null;
222
+ plugin.betaVersion = installedMetadata.betaVersion ?? plugin.betaVersion ?? null;
223
+ }
224
+ if (isAdvancedCapabilityPluginId(plugin.id)) {
225
+ const serviceType = ADVANCED_CAPABILITY_SERVICE_BY_PLUGIN_ID[plugin.id];
226
+ plugin.enabled = advancedCapabilitiesEnabled;
227
+ plugin.isActive = advancedCapabilitiesEnabled ? serviceType ? Boolean(state.runtime?.getService(serviceType)) : Boolean(state.runtime) : false;
228
+ plugin.autoEnabled = advancedCapabilitiesEnabled;
229
+ plugin.loadError = void 0;
230
+ continue;
231
+ }
232
+ const suffix = `plugin-${plugin.id}`;
233
+ const packageName = `@elizaos/plugin-${plugin.id}`;
234
+ const npmPkgName = plugin.npmName;
235
+ const isLoaded = loadedNames.length > 0 && loadedNames.some((name) => {
236
+ return name === plugin.id || name === suffix || name === packageName || npmPkgName != null && name === npmPkgName || name.endsWith(`/${suffix}`);
237
+ });
238
+ plugin.isActive = isLoaded;
239
+ const configEntry = configEntries?.[plugin.id];
240
+ if (configEntry && typeof configEntry.enabled === "boolean") {
241
+ plugin.enabled = configEntry.enabled;
242
+ } else {
243
+ plugin.enabled = isLoaded;
244
+ }
245
+ plugin.loadError = void 0;
246
+ if (plugin.enabled && !isLoaded && state.runtime) {
247
+ const installs = freshConfig.plugins?.installs;
248
+ const packageName2 = `@elizaos/plugin-${plugin.id}`;
249
+ const hasInstallRecord = installs?.[packageName2] || installs?.[plugin.id];
250
+ if (hasInstallRecord) {
251
+ plugin.loadError = "Plugin installed but failed to load \u2014 the package may be missing compiled files.";
252
+ }
253
+ }
254
+ if (plugin.id === "evm" || plugin.id === "wallet" || plugin.npmName === EVM_PLUGIN_PACKAGE) {
255
+ plugin.enabled = evmDiagnostic.enabled;
256
+ plugin.isActive = evmDiagnostic.isActive;
257
+ plugin.autoEnabled = evmDiagnostic.autoEnabled;
258
+ plugin.managementMode = "core-optional";
259
+ plugin.capabilityStatus = evmDiagnostic.capabilityStatus;
260
+ plugin.capabilityReason = evmDiagnostic.capabilityReason;
261
+ plugin.prerequisites = evmDiagnostic.prerequisites;
262
+ }
263
+ }
264
+ for (const plugin of allPlugins) {
265
+ for (const param of plugin.parameters) {
266
+ const envValue = process.env[param.key];
267
+ param.isSet = Boolean(envValue?.trim());
268
+ param.currentValue = param.isSet ? param.sensitive ? maskValue(envValue ?? "") : envValue ?? "" : null;
269
+ }
270
+ const paramInfos = plugin.parameters.map((p) => ({
271
+ key: p.key,
272
+ required: p.required,
273
+ sensitive: p.sensitive,
274
+ type: p.type,
275
+ description: p.description,
276
+ default: p.default
277
+ }));
278
+ const validation = validatePluginConfig(
279
+ plugin.id,
280
+ plugin.category,
281
+ plugin.envKey,
282
+ plugin.configKeys,
283
+ void 0,
284
+ paramInfos
285
+ );
286
+ plugin.validationErrors = validation.errors;
287
+ plugin.validationWarnings = validation.warnings;
288
+ }
289
+ applyWhatsAppQrOverride(allPlugins, resolveDefaultAgentWorkspaceDir());
290
+ applySignalQrOverride(allPlugins, resolveDefaultAgentWorkspaceDir());
291
+ for (const plugin of allPlugins) {
292
+ const providerModels = readProviderCache(plugin.id)?.models ?? [];
293
+ for (const param of plugin.parameters) {
294
+ if (!param.key.toUpperCase().includes("MODEL")) continue;
295
+ const expectedCat = paramKeyToCategory(param.key);
296
+ const filtered = providerModels.filter(
297
+ (m) => m.category === expectedCat
298
+ );
299
+ if (!plugin.configUiHints) plugin.configUiHints = {};
300
+ plugin.configUiHints[param.key] = {
301
+ ...plugin.configUiHints[param.key],
302
+ type: "select",
303
+ options: filtered.map((m) => ({
304
+ value: m.id,
305
+ label: m.name !== m.id ? `${m.name} (${m.id})` : m.id
306
+ }))
307
+ };
308
+ }
309
+ }
310
+ const runtimePlugins = state.runtime?.plugins ?? [];
311
+ const normalizeId = (value) => {
312
+ let v = value.trim();
313
+ if (v.startsWith("@")) {
314
+ const slash = v.indexOf("/");
315
+ if (slash > 0) v = v.slice(slash + 1);
316
+ }
317
+ if (v.startsWith("plugin-")) v = v.slice("plugin-".length);
318
+ if (v.startsWith("app-")) v = v.slice("app-".length);
319
+ return v;
320
+ };
321
+ for (const plugin of allPlugins) {
322
+ const widgets = getPluginWidgets(plugin.id, runtimePlugins);
323
+ if (widgets.length > 0) {
324
+ plugin.widgets = widgets;
325
+ }
326
+ const normalizedPluginId = normalizeId(plugin.id);
327
+ const matchedRuntime = runtimePlugins.find(
328
+ (p) => normalizeId(p.name) === normalizedPluginId
329
+ );
330
+ if (matchedRuntime?.app) {
331
+ const a = matchedRuntime.app;
332
+ plugin.app = {
333
+ displayName: a.displayName,
334
+ category: a.category,
335
+ icon: a.icon,
336
+ developerOnly: a.developerOnly,
337
+ visibleInAppStore: a.visibleInAppStore,
338
+ navTabs: a.navTabs?.map((t) => ({
339
+ id: t.id,
340
+ label: t.label,
341
+ icon: t.icon,
342
+ path: t.path,
343
+ order: t.order,
344
+ developerOnly: t.developerOnly,
345
+ group: t.group,
346
+ componentExport: t.componentExport
347
+ }))
348
+ };
349
+ }
350
+ }
351
+ return allPlugins;
352
+ };
353
+ const resolvePluginsSnapshot = async (config) => {
354
+ const { resolvePlugins } = await import("@elizaos/agent");
355
+ return resolvePlugins(config, { quiet: true });
356
+ };
357
+ const resolvePluginsSnapshotSafe = async (config, reason) => {
358
+ try {
359
+ return await resolvePluginsSnapshot(config);
360
+ } catch (err) {
361
+ logger.warn(
362
+ `[plugin-routes] Failed to resolve plugin snapshot for ${reason}: ${err instanceof Error ? err.message : String(err)}`
363
+ );
364
+ return void 0;
365
+ }
366
+ };
367
+ const npmNamePattern = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
368
+ const validateRegistryPluginPackageName = (pluginName) => {
369
+ const trimmedName = pluginName.trim();
370
+ if (!trimmedName) {
371
+ return "Request body must include 'name' (plugin package name)";
372
+ }
373
+ if (!npmNamePattern.test(trimmedName)) {
374
+ return "Invalid plugin name format";
375
+ }
376
+ return null;
377
+ };
378
+ if (method === "GET" && pathname === "/api/plugins") {
379
+ let inFlight = pluginsListInFlight.get(state);
380
+ if (!inFlight) {
381
+ inFlight = buildPluginsListSnapshot();
382
+ pluginsListInFlight.set(state, inFlight);
383
+ }
384
+ const allPlugins = await inFlight.finally(() => {
385
+ pluginsListInFlight.delete(state);
386
+ });
387
+ json(res, { plugins: allPlugins });
388
+ return true;
389
+ }
390
+ if (method === "PUT" && pathname.startsWith("/api/plugins/")) {
391
+ const pluginId = pathname.slice("/api/plugins/".length);
392
+ const rawPlugin = await readJsonBody(req, res);
393
+ if (rawPlugin === null) return true;
394
+ const parsedPlugin = PutPluginRequestSchema.safeParse(rawPlugin);
395
+ if (!parsedPlugin.success) {
396
+ error(
397
+ res,
398
+ parsedPlugin.error.issues[0]?.message ?? "Invalid request body",
399
+ 400
400
+ );
401
+ return true;
402
+ }
403
+ const body = parsedPlugin.data;
404
+ if (isElizaSettingsDebugEnabled()) {
405
+ logger.debug(
406
+ `[eliza][settings][api] PUT /api/plugins/${pluginId} body=${JSON.stringify(
407
+ sanitizeForSettingsDebug({
408
+ enabled: body.enabled,
409
+ configKeys: body.config ? Object.keys(body.config).sort() : [],
410
+ config: body.config ?? {}
411
+ })
412
+ )}`
413
+ );
414
+ }
415
+ let plugin = state.plugins.find((p) => p.id === pluginId);
416
+ if (!plugin) {
417
+ let freshCfg;
418
+ try {
419
+ freshCfg = loadElizaConfig();
420
+ } catch {
421
+ freshCfg = state.config;
422
+ }
423
+ const bundledIds = new Set(state.plugins.map((p) => p.id));
424
+ const installed = discoverInstalledPlugins(freshCfg, bundledIds);
425
+ const found = installed.find((p) => p.id === pluginId);
426
+ if (found) {
427
+ state.plugins.push(found);
428
+ plugin = found;
429
+ }
430
+ }
431
+ if (!plugin) {
432
+ error(res, `Plugin "${pluginId}" not found`, 404);
433
+ return true;
434
+ }
435
+ const previousConfig = structuredClone(state.config);
436
+ const previousResolvedPlugins = state.runtime ? await resolvePluginsSnapshotSafe(previousConfig, "plugin update") : void 0;
437
+ if (body.enabled !== void 0) {
438
+ plugin.enabled = body.enabled;
439
+ }
440
+ if (body.config) {
441
+ const configRejections = resolvePluginConfigMutationRejections(
442
+ plugin.parameters,
443
+ body.config
444
+ );
445
+ if (configRejections.length > 0) {
446
+ json(
447
+ res,
448
+ { ok: false, plugin, validationErrors: configRejections },
449
+ 422
450
+ );
451
+ return true;
452
+ }
453
+ const configObj = body.config;
454
+ const submittedParamInfos = plugin.parameters.filter((p) => p.key in configObj).map((p) => ({
455
+ key: p.key,
456
+ required: p.required,
457
+ sensitive: p.sensitive,
458
+ type: p.type,
459
+ description: p.description,
460
+ default: p.default
461
+ }));
462
+ const configValidation = validatePluginConfig(
463
+ pluginId,
464
+ plugin.category,
465
+ plugin.envKey,
466
+ plugin.configKeys,
467
+ body.config,
468
+ submittedParamInfos
469
+ );
470
+ if (!configValidation.valid) {
471
+ json(
472
+ res,
473
+ { ok: false, plugin, validationErrors: configValidation.errors },
474
+ 422
475
+ );
476
+ return true;
477
+ }
478
+ const allowedParamKeys = new Set(plugin.parameters.map((p) => p.key));
479
+ const allowedParamsByKey = new Map(
480
+ plugin.parameters.map((p) => [p.key, p])
481
+ );
482
+ if (!state.config.env) {
483
+ state.config.env = {};
484
+ }
485
+ if (!state.config.plugins) {
486
+ state.config.plugins = {};
487
+ }
488
+ if (!state.config.plugins.entries) {
489
+ state.config.plugins.entries = {};
490
+ }
491
+ const entries = state.config.plugins.entries;
492
+ const pluginEntry = entries[pluginId] ?? {};
493
+ const nextPluginConfig = asRecord(pluginEntry.config) ? { ...pluginEntry.config } : {};
494
+ let touchedPluginConfig = false;
495
+ for (const [key, value] of Object.entries(body.config)) {
496
+ if (allowedParamKeys.has(key) && !BLOCKED_ENV_KEYS.has(key.toUpperCase()) && typeof value === "string") {
497
+ touchedPluginConfig = true;
498
+ if (value.trim()) {
499
+ process.env[key] = value;
500
+ state.config.env[key] = value;
501
+ nextPluginConfig[key] = value;
502
+ } else if (!allowedParamsByKey.get(key)?.required) {
503
+ delete process.env[key];
504
+ delete state.config.env[key];
505
+ delete nextPluginConfig[key];
506
+ }
507
+ }
508
+ }
509
+ if (touchedPluginConfig) {
510
+ pluginEntry.config = nextPluginConfig;
511
+ entries[pluginId] = pluginEntry;
512
+ }
513
+ plugin.configured = true;
514
+ if (body.enabled === void 0) {
515
+ try {
516
+ saveElizaConfig(state.config);
517
+ } catch (err) {
518
+ logger.warn(
519
+ `[eliza-api] Failed to save config: ${err instanceof Error ? err.message : err}`
520
+ );
521
+ }
522
+ }
523
+ }
524
+ const refreshParamInfos = plugin.parameters.map((p) => ({
525
+ key: p.key,
526
+ required: p.required,
527
+ sensitive: p.sensitive,
528
+ type: p.type,
529
+ description: p.description,
530
+ default: p.default
531
+ }));
532
+ const updated = validatePluginConfig(
533
+ pluginId,
534
+ plugin.category,
535
+ plugin.envKey,
536
+ plugin.configKeys,
537
+ void 0,
538
+ refreshParamInfos
539
+ );
540
+ plugin.validationErrors = updated.errors;
541
+ plugin.validationWarnings = updated.warnings;
542
+ if (body.enabled !== void 0) {
543
+ if (isAdvancedCapabilityPluginId(pluginId)) {
544
+ applyAdvancedCapabilitiesConfig(state.config, body.enabled);
545
+ for (const candidate of state.plugins) {
546
+ if (isAdvancedCapabilityPluginId(candidate.id)) {
547
+ candidate.enabled = body.enabled;
548
+ }
549
+ }
550
+ logger.info(
551
+ `[eliza-api] ${body.enabled ? "Enabled" : "Disabled"} advanced capabilities via plugin alias: ${pluginId}`
552
+ );
553
+ } else {
554
+ const packageName = `@elizaos/plugin-${pluginId}`;
555
+ if (!state.config.plugins) {
556
+ state.config.plugins = {};
557
+ }
558
+ if (!state.config.plugins.entries) {
559
+ state.config.plugins.entries = {};
560
+ }
561
+ const entries = state.config.plugins.entries;
562
+ entries[pluginId] = { enabled: body.enabled };
563
+ state.config.plugins.allow = state.config.plugins.allow ?? [];
564
+ const allow = state.config.plugins.allow;
565
+ if (body.enabled) {
566
+ if (!allow.includes(pluginId) && !allow.includes(packageName)) {
567
+ allow.push(pluginId);
568
+ }
569
+ } else {
570
+ state.config.plugins.allow = allow.filter(
571
+ (p) => p !== pluginId && p !== packageName
572
+ );
573
+ }
574
+ logger.info(
575
+ `[eliza-api] ${body.enabled ? "Enabled" : "Disabled"} plugin: ${packageName}`
576
+ );
577
+ }
578
+ const CAPABILITY_FEATURE_IDS = /* @__PURE__ */ new Set([
579
+ "vision",
580
+ "browser",
581
+ "computeruse",
582
+ "coding-agent"
583
+ ]);
584
+ if (CAPABILITY_FEATURE_IDS.has(pluginId)) {
585
+ if (!state.config.features) {
586
+ state.config.features = {};
587
+ }
588
+ state.config.features[pluginId] = body.enabled;
589
+ }
590
+ try {
591
+ saveElizaConfig(state.config);
592
+ } catch (err) {
593
+ logger.warn(
594
+ `[eliza-api] Failed to save config: ${err instanceof Error ? err.message : err}`
595
+ );
596
+ }
597
+ }
598
+ const runtimeApply = await applyPluginRuntimeMutation({
599
+ runtime: state.runtime,
600
+ previousConfig,
601
+ nextConfig: state.config,
602
+ previousResolvedPlugins,
603
+ changedPluginId: pluginId,
604
+ changedPluginPackage: plugin.npmName,
605
+ config: body.config,
606
+ expectRuntimeGraphChange: body.enabled !== void 0,
607
+ reason: body.enabled !== void 0 ? `Plugin toggle: ${pluginId}` : `Plugin config updated: ${pluginId}`,
608
+ restartRuntime
609
+ });
610
+ if (runtimeApply.requiresRestart) {
611
+ scheduleRuntimeRestart(runtimeApply.reason);
612
+ }
613
+ if (isElizaSettingsDebugEnabled()) {
614
+ const cloud = state.config.cloud;
615
+ logger.debug(
616
+ `[eliza][settings][api] PUT /api/plugins/${pluginId} \u2192 done configured=${plugin.configured} enabled=${plugin.enabled} cloud=${JSON.stringify(settingsDebugCloudSummary(cloud))}`
617
+ );
618
+ }
619
+ json(res, {
620
+ ok: true,
621
+ plugin,
622
+ applied: runtimeApply.mode,
623
+ requiresRestart: runtimeApply.requiresRestart,
624
+ restartedRuntime: runtimeApply.restartedRuntime,
625
+ loadedPackages: runtimeApply.loadedPackages,
626
+ unloadedPackages: runtimeApply.unloadedPackages,
627
+ reloadedPackages: runtimeApply.reloadedPackages
628
+ });
629
+ return true;
630
+ }
631
+ if (method === "GET" && pathname === "/api/secrets") {
632
+ const bundledIds = new Set(state.plugins.map((p) => p.id));
633
+ const installedEntries = discoverInstalledPlugins(state.config, bundledIds);
634
+ const allPlugins = [...state.plugins, ...installedEntries];
635
+ if (state.runtime) {
636
+ const loadedNames = state.runtime.plugins.map((p) => p.name);
637
+ for (const plugin of allPlugins) {
638
+ const suffix = `plugin-${plugin.id}`;
639
+ const packageName = `@elizaos/plugin-${plugin.id}`;
640
+ plugin.enabled = loadedNames.some(
641
+ (name) => name === plugin.id || name === suffix || name === packageName || name.endsWith(`/${suffix}`)
642
+ );
643
+ }
644
+ }
645
+ const secrets = aggregateSecrets(allPlugins);
646
+ json(res, { secrets });
647
+ return true;
648
+ }
649
+ if (method === "PUT" && pathname === "/api/secrets") {
650
+ const rawSecrets = await readJsonBody(req, res);
651
+ if (rawSecrets === null) return true;
652
+ const parsedSecrets = PutSecretsRequestSchema.safeParse(rawSecrets);
653
+ if (!parsedSecrets.success) {
654
+ error(
655
+ res,
656
+ parsedSecrets.error.issues[0]?.message ?? "Invalid request body",
657
+ 400
658
+ );
659
+ return true;
660
+ }
661
+ const body = parsedSecrets.data;
662
+ const bundledIds = new Set(state.plugins.map((p) => p.id));
663
+ const installedEntries = discoverInstalledPlugins(state.config, bundledIds);
664
+ const allPlugins = [...state.plugins, ...installedEntries];
665
+ const allowedKeys = /* @__PURE__ */ new Set();
666
+ for (const plugin of allPlugins) {
667
+ for (const param of plugin.parameters) {
668
+ if (param.sensitive) allowedKeys.add(param.key);
669
+ }
670
+ }
671
+ const updatedKeys = [];
672
+ for (const [key, value] of Object.entries(body.secrets)) {
673
+ if (typeof value !== "string" || !value.trim()) continue;
674
+ if (!allowedKeys.has(key)) continue;
675
+ if (BLOCKED_ENV_KEYS.has(key.toUpperCase())) continue;
676
+ process.env[key] = value;
677
+ updatedKeys.push(key);
678
+ }
679
+ for (const plugin of allPlugins) {
680
+ const pluginKeys = new Set(plugin.parameters.map((p) => p.key));
681
+ if (updatedKeys.some((k) => pluginKeys.has(k))) {
682
+ plugin.configured = true;
683
+ }
684
+ }
685
+ json(res, { ok: true, updated: updatedKeys });
686
+ return true;
687
+ }
688
+ const pluginTestMatch = method === "POST" && pathname.match(/^\/api\/plugins\/([^/]+)\/test$/);
689
+ if (pluginTestMatch) {
690
+ const pluginId = decodeURIComponent(pluginTestMatch[1]);
691
+ const startMs = Date.now();
692
+ try {
693
+ const allPlugins = state.runtime?.plugins ?? [];
694
+ const normalizePluginId = (value) => {
695
+ const scopedPackage = value.match(/^@[^/]+\/plugin-(.+)$/);
696
+ if (scopedPackage) {
697
+ return scopedPackage[1] ?? value;
698
+ }
699
+ return value.replace(/^@[^/]+\//, "").replace(/^plugin-/, "");
700
+ };
701
+ const normalizedPluginId = normalizePluginId(pluginId);
702
+ const plugin = allPlugins.find((p) => {
703
+ const runtimeName = p.name ?? "";
704
+ const runtimeId = normalizePluginId(runtimeName);
705
+ return p.id === pluginId || p.name === pluginId || runtimeId === pluginId || runtimeId === normalizedPluginId;
706
+ });
707
+ if (!plugin) {
708
+ json(
709
+ res,
710
+ {
711
+ success: false,
712
+ pluginId,
713
+ error: "Plugin not found or not loaded",
714
+ durationMs: Date.now() - startMs
715
+ },
716
+ 404
717
+ );
718
+ return true;
719
+ }
720
+ const testFn = getPluginHealthProbe(plugin);
721
+ if (typeof testFn === "function") {
722
+ const result = await testFn();
723
+ json(res, {
724
+ success: result.ok !== false,
725
+ pluginId,
726
+ message: result.message ?? (result.ok !== false ? "Connection successful" : "Connection failed"),
727
+ durationMs: Date.now() - startMs
728
+ });
729
+ return true;
730
+ }
731
+ json(res, {
732
+ success: true,
733
+ pluginId,
734
+ message: "Plugin is loaded and active (no custom test available)",
735
+ durationMs: Date.now() - startMs
736
+ });
737
+ } catch (err) {
738
+ json(
739
+ res,
740
+ {
741
+ success: false,
742
+ pluginId,
743
+ error: err instanceof Error ? err.message : String(err),
744
+ durationMs: Date.now() - startMs
745
+ },
746
+ 500
747
+ );
748
+ }
749
+ return true;
750
+ }
751
+ if (method === "POST" && pathname === "/api/plugins/install") {
752
+ const rawInstall = await readJsonBody(req, res);
753
+ if (rawInstall === null) return true;
754
+ const parsedInstall = PostPluginInstallRequestSchema.safeParse(rawInstall);
755
+ if (!parsedInstall.success) {
756
+ error(
757
+ res,
758
+ parsedInstall.error.issues[0]?.message ?? "Invalid request body",
759
+ 400
760
+ );
761
+ return true;
762
+ }
763
+ const body = parsedInstall.data;
764
+ const pluginName = body.name;
765
+ const installValidationError = validateRegistryPluginPackageName(pluginName);
766
+ if (installValidationError) {
767
+ error(res, installValidationError, 400);
768
+ return true;
769
+ }
770
+ try {
771
+ const previousConfig = structuredClone(state.config);
772
+ const previousResolvedPlugins = state.runtime ? await resolvePluginsSnapshotSafe(previousConfig, "plugin install") : void 0;
773
+ const pluginManager = requirePluginManager(state.runtime);
774
+ const result = await pluginManager.installPlugin(
775
+ pluginName,
776
+ (progress) => {
777
+ logger.info(`[install] ${progress.phase}: ${progress.message}`);
778
+ state.broadcastWs?.({
779
+ type: "install-progress",
780
+ pluginName: progress.pluginName,
781
+ phase: progress.phase,
782
+ message: progress.message
783
+ });
784
+ },
785
+ {
786
+ releaseStream: body.stream,
787
+ version: body.version
788
+ }
789
+ );
790
+ if (!result.success) {
791
+ json(res, { ok: false, error: result.error }, 422);
792
+ return true;
793
+ }
794
+ const installedId = result.pluginName.replace(/^@[^/]+\/plugin-/, "").replace(/^@[^/]+\//, "").replace(/^plugin-/, "");
795
+ if (!state.config.plugins) {
796
+ state.config.plugins = {};
797
+ }
798
+ if (!state.config.plugins.entries) {
799
+ state.config.plugins.entries = {};
800
+ }
801
+ const pluginEntries = state.config.plugins.entries;
802
+ pluginEntries[installedId] = { enabled: true };
803
+ if (result.installPath) {
804
+ if (!state.config.plugins.installs || typeof state.config.plugins.installs !== "object") {
805
+ state.config.plugins.installs = {};
806
+ }
807
+ const installs = state.config.plugins.installs;
808
+ installs[result.pluginName] = {
809
+ source: "npm",
810
+ requestedVersion: result.requestedVersion,
811
+ releaseStream: result.releaseStream,
812
+ installPath: result.installPath,
813
+ version: result.version,
814
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
815
+ };
816
+ }
817
+ try {
818
+ saveElizaConfig(state.config);
819
+ } catch (err) {
820
+ logger.warn(
821
+ `[eliza-api] Failed to save config after install: ${err instanceof Error ? err.message : err}`
822
+ );
823
+ }
824
+ const runtimeApply = await applyPluginRuntimeMutation({
825
+ runtime: state.runtime,
826
+ previousConfig,
827
+ nextConfig: state.config,
828
+ previousResolvedPlugins,
829
+ changedPluginId: installedId,
830
+ changedPluginPackage: result.pluginName,
831
+ expectRuntimeGraphChange: true,
832
+ reason: `Plugin ${result.pluginName} installed`,
833
+ restartRuntime
834
+ });
835
+ if (runtimeApply.requiresRestart && body.autoRestart !== false) {
836
+ scheduleRuntimeRestart(runtimeApply.reason);
837
+ }
838
+ json(res, {
839
+ ok: true,
840
+ pluginName: result.pluginName,
841
+ plugin: {
842
+ name: result.pluginName,
843
+ version: result.version,
844
+ installPath: result.installPath
845
+ },
846
+ applied: runtimeApply.mode,
847
+ requiresRestart: runtimeApply.requiresRestart,
848
+ restartedRuntime: runtimeApply.restartedRuntime,
849
+ loadedPackages: runtimeApply.loadedPackages,
850
+ unloadedPackages: runtimeApply.unloadedPackages,
851
+ reloadedPackages: runtimeApply.reloadedPackages,
852
+ releaseStream: result.releaseStream,
853
+ requestedVersion: result.requestedVersion,
854
+ latestVersion: result.latestVersion,
855
+ betaVersion: result.betaVersion,
856
+ message: runtimeApply.requiresRestart ? `${result.pluginName} installed. Restart required to activate.` : `${result.pluginName} installed.`
857
+ });
858
+ } catch (err) {
859
+ error(
860
+ res,
861
+ `Install failed: ${err instanceof Error ? err.message : String(err)}`,
862
+ 500
863
+ );
864
+ }
865
+ return true;
866
+ }
867
+ if (method === "POST" && pathname === "/api/plugins/update") {
868
+ const rawUpdate = await readJsonBody(req, res);
869
+ if (rawUpdate === null) return true;
870
+ const parsedUpdate = PostPluginUpdateRequestSchema.safeParse(rawUpdate);
871
+ if (!parsedUpdate.success) {
872
+ error(
873
+ res,
874
+ parsedUpdate.error.issues[0]?.message ?? "Invalid request body",
875
+ 400
876
+ );
877
+ return true;
878
+ }
879
+ const body = parsedUpdate.data;
880
+ const pluginName = body.name;
881
+ const updateValidationError = validateRegistryPluginPackageName(pluginName);
882
+ if (updateValidationError) {
883
+ error(res, updateValidationError, 400);
884
+ return true;
885
+ }
886
+ try {
887
+ const previousConfig = structuredClone(state.config);
888
+ const previousResolvedPlugins = state.runtime ? await resolvePluginsSnapshotSafe(previousConfig, "plugin update") : void 0;
889
+ const pluginManager = requirePluginManager(state.runtime);
890
+ const updatePlugin = typeof pluginManager.updatePlugin === "function" ? pluginManager.updatePlugin.bind(pluginManager) : pluginManager.installPlugin.bind(pluginManager);
891
+ const result = await updatePlugin(
892
+ pluginName,
893
+ (progress) => {
894
+ logger.info(`[update] ${progress.phase}: ${progress.message}`);
895
+ state.broadcastWs?.({
896
+ type: "install-progress",
897
+ pluginName: progress.pluginName,
898
+ phase: progress.phase,
899
+ message: progress.message
900
+ });
901
+ },
902
+ {
903
+ releaseStream: body.stream,
904
+ version: body.version
905
+ }
906
+ );
907
+ if (!result.success) {
908
+ json(res, { ok: false, error: result.error }, 422);
909
+ return true;
910
+ }
911
+ if (!state.config.plugins) {
912
+ state.config.plugins = {};
913
+ }
914
+ if (!state.config.plugins.entries) {
915
+ state.config.plugins.entries = {};
916
+ }
917
+ const updatedId = result.pluginName.replace(/^@[^/]+\/plugin-/, "").replace(/^@[^/]+\//, "").replace(/^plugin-/, "");
918
+ state.config.plugins.entries[updatedId] = { enabled: true };
919
+ state.config.plugins.installs = state.config.plugins.installs ?? {};
920
+ state.config.plugins.installs[result.pluginName] = {
921
+ source: "npm",
922
+ requestedVersion: result.requestedVersion,
923
+ releaseStream: result.releaseStream,
924
+ installPath: result.installPath,
925
+ version: result.version,
926
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
927
+ };
928
+ try {
929
+ saveElizaConfig(state.config);
930
+ } catch (err) {
931
+ logger.warn(
932
+ `[eliza-api] Failed to save config after update: ${err instanceof Error ? err.message : err}`
933
+ );
934
+ }
935
+ const runtimeApply = await applyPluginRuntimeMutation({
936
+ runtime: state.runtime,
937
+ previousConfig,
938
+ nextConfig: state.config,
939
+ previousResolvedPlugins,
940
+ changedPluginId: updatedId,
941
+ changedPluginPackage: result.pluginName,
942
+ forceReloadPackages: [result.pluginName],
943
+ expectRuntimeGraphChange: true,
944
+ reason: `Plugin ${result.pluginName} updated`,
945
+ restartRuntime
946
+ });
947
+ if (runtimeApply.requiresRestart && body.autoRestart !== false) {
948
+ scheduleRuntimeRestart(runtimeApply.reason);
949
+ }
950
+ json(res, {
951
+ ok: true,
952
+ pluginName: result.pluginName,
953
+ plugin: {
954
+ name: result.pluginName,
955
+ version: result.version,
956
+ installPath: result.installPath
957
+ },
958
+ applied: runtimeApply.mode,
959
+ requiresRestart: runtimeApply.requiresRestart,
960
+ restartedRuntime: runtimeApply.restartedRuntime,
961
+ loadedPackages: runtimeApply.loadedPackages,
962
+ unloadedPackages: runtimeApply.unloadedPackages,
963
+ reloadedPackages: runtimeApply.reloadedPackages,
964
+ releaseStream: result.releaseStream,
965
+ requestedVersion: result.requestedVersion,
966
+ latestVersion: result.latestVersion,
967
+ betaVersion: result.betaVersion,
968
+ message: runtimeApply.requiresRestart ? `${result.pluginName} updated. Restart required to activate.` : `${result.pluginName} updated.`
969
+ });
970
+ } catch (err) {
971
+ error(
972
+ res,
973
+ `Update failed: ${err instanceof Error ? err.message : String(err)}`,
974
+ 500
975
+ );
976
+ }
977
+ return true;
978
+ }
979
+ if (method === "POST" && pathname === "/api/plugins/uninstall") {
980
+ const rawUninstall = await readJsonBody(req, res);
981
+ if (rawUninstall === null) return true;
982
+ const parsedUninstall = PostPluginUninstallRequestSchema.safeParse(rawUninstall);
983
+ if (!parsedUninstall.success) {
984
+ error(
985
+ res,
986
+ parsedUninstall.error.issues[0]?.message ?? "Invalid request body",
987
+ 400
988
+ );
989
+ return true;
990
+ }
991
+ const body = parsedUninstall.data;
992
+ const pluginName = body.name;
993
+ const uninstallValidationError = validateRegistryPluginPackageName(pluginName);
994
+ if (uninstallValidationError) {
995
+ error(res, uninstallValidationError, 400);
996
+ return true;
997
+ }
998
+ try {
999
+ const previousConfig = structuredClone(state.config);
1000
+ const previousResolvedPlugins = state.runtime ? await resolvePluginsSnapshotSafe(previousConfig, "plugin uninstall") : void 0;
1001
+ const pluginManager = requirePluginManager(state.runtime);
1002
+ const result = await pluginManager.uninstallPlugin(pluginName);
1003
+ if (!result.success) {
1004
+ json(res, { ok: false, error: result.error }, 422);
1005
+ return true;
1006
+ }
1007
+ const removedId = result.pluginName.replace(/^@[^/]+\/plugin-/, "").replace(/^@[^/]+\//, "").replace(/^plugin-/, "");
1008
+ const installs = state.config.plugins?.installs;
1009
+ if (installs && typeof installs === "object") {
1010
+ delete installs[result.pluginName];
1011
+ }
1012
+ const entries = state.config.plugins?.entries;
1013
+ if (entries && typeof entries === "object") {
1014
+ delete entries[removedId];
1015
+ }
1016
+ try {
1017
+ saveElizaConfig(state.config);
1018
+ } catch (err) {
1019
+ logger.warn(
1020
+ `[eliza-api] Failed to save config after uninstall: ${err instanceof Error ? err.message : err}`
1021
+ );
1022
+ }
1023
+ const runtimeApply = await applyPluginRuntimeMutation({
1024
+ runtime: state.runtime,
1025
+ previousConfig,
1026
+ nextConfig: state.config,
1027
+ previousResolvedPlugins,
1028
+ changedPluginId: removedId,
1029
+ changedPluginPackage: result.pluginName,
1030
+ expectRuntimeGraphChange: true,
1031
+ reason: `Plugin ${pluginName} uninstalled`,
1032
+ restartRuntime
1033
+ });
1034
+ if (runtimeApply.requiresRestart && body.autoRestart !== false) {
1035
+ scheduleRuntimeRestart(runtimeApply.reason);
1036
+ }
1037
+ json(res, {
1038
+ ok: true,
1039
+ pluginName: result.pluginName,
1040
+ applied: runtimeApply.mode,
1041
+ requiresRestart: runtimeApply.requiresRestart,
1042
+ restartedRuntime: runtimeApply.restartedRuntime,
1043
+ loadedPackages: runtimeApply.loadedPackages,
1044
+ unloadedPackages: runtimeApply.unloadedPackages,
1045
+ reloadedPackages: runtimeApply.reloadedPackages,
1046
+ message: runtimeApply.requiresRestart ? `${pluginName} uninstalled. Restart required.` : `${pluginName} uninstalled.`
1047
+ });
1048
+ } catch (err) {
1049
+ error(
1050
+ res,
1051
+ `Uninstall failed: ${err instanceof Error ? err.message : String(err)}`,
1052
+ 500
1053
+ );
1054
+ }
1055
+ return true;
1056
+ }
1057
+ if (method === "POST" && pathname.match(/^\/api\/plugins\/[^/]+\/eject$/)) {
1058
+ const pluginName = decodeURIComponent(
1059
+ pathname.slice("/api/plugins/".length, pathname.length - "/eject".length)
1060
+ );
1061
+ try {
1062
+ const previousConfig = structuredClone(state.config);
1063
+ const previousResolvedPlugins = state.runtime ? await resolvePluginsSnapshotSafe(previousConfig, "plugin eject") : void 0;
1064
+ const pluginManager = requirePluginManager(state.runtime);
1065
+ if (typeof pluginManager.ejectPlugin !== "function") {
1066
+ throw new Error("Plugin manager does not support ejecting plugins");
1067
+ }
1068
+ const result = await pluginManager.ejectPlugin(pluginName);
1069
+ if (!result.success) {
1070
+ json(res, { ok: false, error: result.error }, 422);
1071
+ return true;
1072
+ }
1073
+ const runtimeApply = await applyPluginRuntimeMutation({
1074
+ runtime: state.runtime,
1075
+ previousConfig,
1076
+ nextConfig: state.config,
1077
+ previousResolvedPlugins,
1078
+ changedPluginId: pluginName,
1079
+ changedPluginPackage: result.pluginName,
1080
+ forceReloadPackages: [result.pluginName],
1081
+ expectRuntimeGraphChange: true,
1082
+ reason: `Plugin ${pluginName} ejected`,
1083
+ restartRuntime
1084
+ });
1085
+ if (runtimeApply.requiresRestart) {
1086
+ scheduleRuntimeRestart(runtimeApply.reason);
1087
+ }
1088
+ json(res, {
1089
+ ok: true,
1090
+ pluginName: result.pluginName,
1091
+ applied: runtimeApply.mode,
1092
+ requiresRestart: runtimeApply.requiresRestart,
1093
+ restartedRuntime: runtimeApply.restartedRuntime,
1094
+ loadedPackages: runtimeApply.loadedPackages,
1095
+ unloadedPackages: runtimeApply.unloadedPackages,
1096
+ reloadedPackages: runtimeApply.reloadedPackages,
1097
+ message: `${pluginName} ejected to local source.`
1098
+ });
1099
+ } catch (err) {
1100
+ error(
1101
+ res,
1102
+ `Eject failed: ${err instanceof Error ? err.message : String(err)}`,
1103
+ 500
1104
+ );
1105
+ }
1106
+ return true;
1107
+ }
1108
+ if (method === "POST" && pathname.match(/^\/api\/plugins\/[^/]+\/sync$/)) {
1109
+ const pluginName = decodeURIComponent(
1110
+ pathname.slice("/api/plugins/".length, pathname.length - "/sync".length)
1111
+ );
1112
+ try {
1113
+ const previousConfig = structuredClone(state.config);
1114
+ const previousResolvedPlugins = state.runtime ? await resolvePluginsSnapshotSafe(previousConfig, "plugin sync") : void 0;
1115
+ const pluginManager = requirePluginManager(state.runtime);
1116
+ if (typeof pluginManager.syncPlugin !== "function") {
1117
+ throw new Error("Plugin manager does not support syncing plugins");
1118
+ }
1119
+ const result = await pluginManager.syncPlugin(pluginName);
1120
+ if (!result.success) {
1121
+ json(res, { ok: false, error: result.error }, 422);
1122
+ return true;
1123
+ }
1124
+ const runtimeApply = await applyPluginRuntimeMutation({
1125
+ runtime: state.runtime,
1126
+ previousConfig,
1127
+ nextConfig: state.config,
1128
+ previousResolvedPlugins,
1129
+ changedPluginId: pluginName,
1130
+ changedPluginPackage: result.pluginName,
1131
+ forceReloadPackages: result.requiresRestart ? [result.pluginName] : [],
1132
+ expectRuntimeGraphChange: true,
1133
+ reason: `Plugin ${pluginName} synced`,
1134
+ restartRuntime
1135
+ });
1136
+ if (runtimeApply.requiresRestart) {
1137
+ scheduleRuntimeRestart(runtimeApply.reason);
1138
+ }
1139
+ json(res, {
1140
+ ok: true,
1141
+ pluginName: result.pluginName,
1142
+ applied: runtimeApply.mode,
1143
+ requiresRestart: runtimeApply.requiresRestart,
1144
+ restartedRuntime: runtimeApply.restartedRuntime,
1145
+ loadedPackages: runtimeApply.loadedPackages,
1146
+ unloadedPackages: runtimeApply.unloadedPackages,
1147
+ reloadedPackages: runtimeApply.reloadedPackages,
1148
+ message: `${pluginName} synced with upstream.`
1149
+ });
1150
+ } catch (err) {
1151
+ error(
1152
+ res,
1153
+ `Sync failed: ${err instanceof Error ? err.message : String(err)}`,
1154
+ 500
1155
+ );
1156
+ }
1157
+ return true;
1158
+ }
1159
+ if (method === "POST" && pathname.match(/^\/api\/plugins\/[^/]+\/reinject$/)) {
1160
+ const pluginName = decodeURIComponent(
1161
+ pathname.slice(
1162
+ "/api/plugins/".length,
1163
+ pathname.length - "/reinject".length
1164
+ )
1165
+ );
1166
+ try {
1167
+ const previousConfig = structuredClone(state.config);
1168
+ const previousResolvedPlugins = state.runtime ? await resolvePluginsSnapshotSafe(previousConfig, "plugin reinject") : void 0;
1169
+ const pluginManager = requirePluginManager(state.runtime);
1170
+ if (typeof pluginManager.reinjectPlugin !== "function") {
1171
+ throw new Error("Plugin manager does not support reinjecting plugins");
1172
+ }
1173
+ const result = await pluginManager.reinjectPlugin(pluginName);
1174
+ if (!result.success) {
1175
+ json(res, { ok: false, error: result.error }, 422);
1176
+ return true;
1177
+ }
1178
+ const runtimeApply = await applyPluginRuntimeMutation({
1179
+ runtime: state.runtime,
1180
+ previousConfig,
1181
+ nextConfig: state.config,
1182
+ previousResolvedPlugins,
1183
+ changedPluginId: pluginName,
1184
+ changedPluginPackage: result.pluginName,
1185
+ forceReloadPackages: [result.pluginName],
1186
+ expectRuntimeGraphChange: true,
1187
+ reason: `Plugin ${pluginName} reinjected`,
1188
+ restartRuntime
1189
+ });
1190
+ if (runtimeApply.requiresRestart) {
1191
+ scheduleRuntimeRestart(runtimeApply.reason);
1192
+ }
1193
+ json(res, {
1194
+ ok: true,
1195
+ pluginName: result.pluginName,
1196
+ applied: runtimeApply.mode,
1197
+ requiresRestart: runtimeApply.requiresRestart,
1198
+ restartedRuntime: runtimeApply.restartedRuntime,
1199
+ loadedPackages: runtimeApply.loadedPackages,
1200
+ unloadedPackages: runtimeApply.unloadedPackages,
1201
+ reloadedPackages: runtimeApply.reloadedPackages,
1202
+ message: `${pluginName} restored to registry version.`
1203
+ });
1204
+ } catch (err) {
1205
+ error(
1206
+ res,
1207
+ `Reinject failed: ${err instanceof Error ? err.message : String(err)}`,
1208
+ 500
1209
+ );
1210
+ }
1211
+ return true;
1212
+ }
1213
+ if (method === "GET" && pathname === "/api/plugins/installed") {
1214
+ try {
1215
+ const pluginManager = requirePluginManager(state.runtime);
1216
+ const installed = await pluginManager.listInstalledPlugins();
1217
+ json(res, { count: installed.length, plugins: installed });
1218
+ } catch (err) {
1219
+ error(
1220
+ res,
1221
+ `Failed to list installed plugins: ${err instanceof Error ? err.message : String(err)}`,
1222
+ 500
1223
+ );
1224
+ }
1225
+ return true;
1226
+ }
1227
+ if (method === "GET" && pathname === "/api/plugins/ejected") {
1228
+ try {
1229
+ const pluginManager = requirePluginManager(state.runtime);
1230
+ if (typeof pluginManager.listEjectedPlugins !== "function") {
1231
+ throw new Error(
1232
+ "Plugin manager does not support listing ejected plugins"
1233
+ );
1234
+ }
1235
+ const plugins = await pluginManager.listEjectedPlugins();
1236
+ json(res, { count: plugins.length, plugins });
1237
+ } catch (err) {
1238
+ error(
1239
+ res,
1240
+ `Failed to list ejected plugins: ${err instanceof Error ? err.message : String(err)}`,
1241
+ 500
1242
+ );
1243
+ }
1244
+ return true;
1245
+ }
1246
+ if (method === "GET" && pathname === "/api/core/status") {
1247
+ try {
1248
+ const coreManager = requireCoreManager(state.runtime);
1249
+ const coreStatus = await coreManager.getCoreStatus();
1250
+ json(res, coreStatus);
1251
+ } catch (err) {
1252
+ error(
1253
+ res,
1254
+ `Failed to get core status: ${err instanceof Error ? err.message : String(err)}`,
1255
+ 500
1256
+ );
1257
+ }
1258
+ return true;
1259
+ }
1260
+ if (method === "GET" && pathname === "/api/plugins/core") {
1261
+ const loadedNames = state.runtime ? new Set(state.runtime.plugins.map((p) => p.name)) : /* @__PURE__ */ new Set();
1262
+ const isLoaded = (npmName) => {
1263
+ if (loadedNames.has(npmName)) return true;
1264
+ const withoutScope = npmName.replace("@elizaos/", "");
1265
+ if (loadedNames.has(withoutScope)) return true;
1266
+ const shortId = withoutScope.replace("plugin-", "");
1267
+ if (loadedNames.has(shortId)) return true;
1268
+ for (const n of loadedNames) {
1269
+ if (n.includes(shortId) || shortId.includes(n)) return true;
1270
+ }
1271
+ return false;
1272
+ };
1273
+ const allowList = new Set(state.config.plugins?.allow ?? []);
1274
+ const makeEntry = (npm, isCore) => {
1275
+ const id = optionalPluginListId(npm);
1276
+ return {
1277
+ npmName: npm,
1278
+ id,
1279
+ name: id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
1280
+ isCore,
1281
+ loaded: isLoaded(npm),
1282
+ enabled: isCore || allowList.has(npm) || allowList.has(id)
1283
+ };
1284
+ };
1285
+ const coreList = CORE_PLUGINS.map((npm) => makeEntry(npm, true));
1286
+ const optionalList = OPTIONAL_CORE_PLUGINS.map(
1287
+ (npm) => makeEntry(npm, false)
1288
+ );
1289
+ json(res, { core: coreList, optional: optionalList });
1290
+ return true;
1291
+ }
1292
+ if (method === "POST" && pathname === "/api/plugins/core/toggle") {
1293
+ const rawToggle = await readJsonBody(req, res);
1294
+ if (rawToggle === null) return true;
1295
+ const parsedToggle = PostPluginCoreToggleRequestSchema.safeParse(rawToggle);
1296
+ if (!parsedToggle.success) {
1297
+ error(
1298
+ res,
1299
+ parsedToggle.error.issues[0]?.message ?? "Invalid request body",
1300
+ 400
1301
+ );
1302
+ return true;
1303
+ }
1304
+ const body = parsedToggle.data;
1305
+ const isCorePlugin = CORE_PLUGINS.includes(
1306
+ body.npmName
1307
+ );
1308
+ if (isCorePlugin) {
1309
+ error(res, "Core plugins cannot be disabled");
1310
+ return true;
1311
+ }
1312
+ const isOptional = OPTIONAL_CORE_PLUGINS.includes(
1313
+ body.npmName
1314
+ );
1315
+ if (!isOptional) {
1316
+ error(res, "Unknown optional plugin");
1317
+ return true;
1318
+ }
1319
+ const previousConfig = structuredClone(state.config);
1320
+ const previousResolvedPlugins = state.runtime ? await resolvePluginsSnapshotSafe(previousConfig, "core plugin toggle") : void 0;
1321
+ state.config.plugins = state.config.plugins ?? {};
1322
+ state.config.plugins.allow = state.config.plugins.allow ?? [];
1323
+ const allow = state.config.plugins.allow;
1324
+ const shortId = optionalPluginListId(body.npmName);
1325
+ if (body.enabled) {
1326
+ if (!allow.includes(body.npmName) && !allow.includes(shortId)) {
1327
+ allow.push(body.npmName);
1328
+ }
1329
+ } else {
1330
+ state.config.plugins.allow = allow.filter(
1331
+ (p) => p !== body.npmName && p !== shortId
1332
+ );
1333
+ }
1334
+ const pluginsRoot = state.config.plugins;
1335
+ const prevEntries = pluginsRoot.entries ?? {};
1336
+ pluginsRoot.entries = {
1337
+ ...prevEntries,
1338
+ [shortId]: {
1339
+ ...prevEntries[shortId],
1340
+ enabled: body.enabled
1341
+ }
1342
+ };
1343
+ try {
1344
+ saveElizaConfig(state.config);
1345
+ } catch (err) {
1346
+ logger.warn(
1347
+ `[api] Config save failed: ${err instanceof Error ? err.message : err}`
1348
+ );
1349
+ }
1350
+ const runtimeApply = await applyPluginRuntimeMutation({
1351
+ runtime: state.runtime,
1352
+ previousConfig,
1353
+ nextConfig: state.config,
1354
+ previousResolvedPlugins,
1355
+ changedPluginId: shortId,
1356
+ changedPluginPackage: body.npmName,
1357
+ expectRuntimeGraphChange: true,
1358
+ reason: `Plugin ${shortId} ${body.enabled ? "enabled" : "disabled"}`,
1359
+ restartRuntime
1360
+ });
1361
+ if (runtimeApply.requiresRestart) {
1362
+ scheduleRuntimeRestart(runtimeApply.reason);
1363
+ }
1364
+ json(res, {
1365
+ ok: true,
1366
+ applied: runtimeApply.mode,
1367
+ requiresRestart: runtimeApply.requiresRestart,
1368
+ restartedRuntime: runtimeApply.restartedRuntime,
1369
+ loadedPackages: runtimeApply.loadedPackages,
1370
+ unloadedPackages: runtimeApply.unloadedPackages,
1371
+ reloadedPackages: runtimeApply.reloadedPackages,
1372
+ diagnostics: (() => {
1373
+ const diagnostic = buildCoreToggleDiagnostics(
1374
+ state.config,
1375
+ body.npmName
1376
+ );
1377
+ return diagnostic && diagnostic.drift_flags.length > 0 ? {
1378
+ withDrift: true,
1379
+ plugin: diagnostic
1380
+ } : void 0;
1381
+ })(),
1382
+ message: runtimeApply.requiresRestart ? `${shortId} ${body.enabled ? "enabled" : "disabled"}. Restart required.` : `${shortId} ${body.enabled ? "enabled" : "disabled"}.`
1383
+ });
1384
+ return true;
1385
+ }
1386
+ return false;
1387
+ }
1388
+ export {
1389
+ handlePluginRoutes
1390
+ };
1391
+ //# sourceMappingURL=plugin-routes.js.map