@elizaos/plugin-registry 2.0.11-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1185 @@
1
+ import fs from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import {
6
+ applyPluginRuntimeMutation,
7
+ CONNECTOR_ENV_MAP,
8
+ discoverPluginsFromManifest,
9
+ findPrimaryEnvKey,
10
+ isAdvancedCapabilityPluginId,
11
+ isVaultRef,
12
+ loadElizaConfig,
13
+ parseVaultRef,
14
+ readBundledPluginPackageMetadata,
15
+ resolveAdvancedCapabilitiesEnabled,
16
+ saveElizaConfig
17
+ } from "@elizaos/agent";
18
+ import {
19
+ ensureCompatSensitiveRouteAuthorized,
20
+ ensureRouteAuthorized
21
+ } from "@elizaos/app-core/api/auth";
22
+ import {
23
+ readCompatJsonBody,
24
+ scheduleCompatRuntimeRestart
25
+ } from "@elizaos/app-core/api/compat-route-shared";
26
+ import {
27
+ sendJsonError as sendJsonErrorResponse,
28
+ sendJson as sendJsonResponse
29
+ } from "@elizaos/app-core/api/response";
30
+ import {
31
+ loadRegistry
32
+ } from "@elizaos/app-core/registry";
33
+ import {
34
+ _resetSharedVaultForTesting,
35
+ mirrorPluginSensitiveToVault,
36
+ sharedVault
37
+ } from "@elizaos/app-core/services/vault-mirror";
38
+ import { logger } from "@elizaos/core";
39
+ import {
40
+ asRecord,
41
+ CONNECTOR_PLUGINS,
42
+ STREAMING_PLUGINS
43
+ } from "@elizaos/shared";
44
+ import { VaultMissError } from "@elizaos/vault";
45
+ const require2 = createRequire(import.meta.url);
46
+ const FIELD_TYPE_TO_LEGACY = {
47
+ string: "string",
48
+ secret: "string",
49
+ url: "string",
50
+ "file-path": "string",
51
+ textarea: "string",
52
+ json: "string",
53
+ select: "string",
54
+ multiselect: "string",
55
+ boolean: "boolean",
56
+ number: "number"
57
+ };
58
+ function pluginSubtypeToCategory(entry) {
59
+ if (entry.kind !== "plugin") return entry.kind;
60
+ if (entry.subtype === "ai-provider") return "ai-provider";
61
+ if (entry.subtype === "database") return "database";
62
+ return "feature";
63
+ }
64
+ function connectorSubtypeToCategory(entry) {
65
+ if (entry.kind !== "connector") return "connector";
66
+ if (entry.subtype === "streaming") return "streaming";
67
+ return "connector";
68
+ }
69
+ function categoryForRegistryEntry(entry) {
70
+ if (entry.kind === "plugin") return pluginSubtypeToCategory(entry);
71
+ if (entry.kind === "connector") return connectorSubtypeToCategory(entry);
72
+ return entry.kind;
73
+ }
74
+ function envKeyForRegistryEntry(entry) {
75
+ if (entry.kind === "connector" && entry.auth) {
76
+ const [first] = entry.auth.credentialKeys;
77
+ if (first) return first;
78
+ }
79
+ for (const [key, field] of Object.entries(entry.config)) {
80
+ if (field.required && (field.type === "secret" || field.sensitive)) {
81
+ return key;
82
+ }
83
+ }
84
+ return void 0;
85
+ }
86
+ function fieldToManifestParameter(field) {
87
+ const param = {
88
+ type: FIELD_TYPE_TO_LEGACY[field.type],
89
+ description: field.help ?? field.label ?? "",
90
+ required: field.required,
91
+ sensitive: field.sensitive ?? field.type === "secret"
92
+ };
93
+ if (field.default !== void 0 && field.default !== null) {
94
+ param.default = String(field.default);
95
+ }
96
+ if (field.options) {
97
+ param.options = field.options.map((option) => option.value);
98
+ }
99
+ return param;
100
+ }
101
+ function registryEntryToManifest(entry) {
102
+ const pluginParameters = {};
103
+ for (const [key, field] of Object.entries(entry.config)) {
104
+ pluginParameters[key] = fieldToManifestParameter(field);
105
+ }
106
+ return {
107
+ id: entry.id,
108
+ dirName: entry.npmName?.replace(/^@[^/]+\//, ""),
109
+ name: entry.name,
110
+ npmName: entry.npmName,
111
+ description: entry.description,
112
+ tags: entry.tags,
113
+ category: categoryForRegistryEntry(entry),
114
+ envKey: envKeyForRegistryEntry(entry),
115
+ configKeys: Object.keys(entry.config),
116
+ version: entry.version,
117
+ pluginParameters,
118
+ icon: entry.render.icon ?? null,
119
+ homepage: entry.resources.homepage,
120
+ repository: entry.resources.repository,
121
+ setupGuideUrl: entry.resources.setupGuideUrl
122
+ };
123
+ }
124
+ const CAPABILITY_FEATURE_IDS = /* @__PURE__ */ new Set([
125
+ "vision",
126
+ "browser",
127
+ "computeruse",
128
+ "coding-agent"
129
+ ]);
130
+ const ADVANCED_CAPABILITY_SERVICE_BY_PLUGIN_ID = {
131
+ experience: "EXPERIENCE",
132
+ personality: "CHARACTER_MANAGEMENT"
133
+ };
134
+ const SENSITIVE_KEY_PREFIXES = ["SOLANA_", "ETHEREUM_", "EVM_", "WALLET_"];
135
+ const REVEALABLE_KEY_PREFIXES = [
136
+ "OPENAI_",
137
+ "ANTHROPIC_",
138
+ "GOOGLE_",
139
+ "GROQ_",
140
+ "MISTRAL_",
141
+ "PERPLEXITY_",
142
+ "COHERE_",
143
+ "TOGETHER_",
144
+ "FIREWORKS_",
145
+ "REPLICATE_",
146
+ "HUGGINGFACE_",
147
+ "ELEVENLABS_",
148
+ "DISCORD_",
149
+ "TELEGRAM_",
150
+ "TWITTER_",
151
+ "SLACK_",
152
+ "GITHUB_",
153
+ "REDIS_",
154
+ "POSTGRES_",
155
+ "DATABASE_",
156
+ "SUPABASE_",
157
+ "PINECONE_",
158
+ "QDRANT_",
159
+ "WEAVIATE_",
160
+ "CHROMADB_",
161
+ "AWS_",
162
+ "AZURE_",
163
+ "CLOUDFLARE_",
164
+ "ELIZA_",
165
+ "PLUGIN_",
166
+ "XAI_",
167
+ "DEEPSEEK_",
168
+ "OLLAMA_",
169
+ "FAL_",
170
+ "LETZAI_",
171
+ "GAIANET_",
172
+ "LIVEPEER_",
173
+ ...SENSITIVE_KEY_PREFIXES
174
+ ];
175
+ const DRIFT_LOG_THROTTLE_MS = 5 * 60 * 1e3;
176
+ let _lastDriftWarningAt = 0;
177
+ let _lastDriftWarningFingerprint = "";
178
+ function maskValue(value) {
179
+ if (value.length <= 8) return "****";
180
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
181
+ }
182
+ function normalizePluginCategory(value) {
183
+ switch (value) {
184
+ case "ai-provider":
185
+ case "connector":
186
+ case "streaming":
187
+ case "database":
188
+ case "app":
189
+ return value;
190
+ default:
191
+ return "feature";
192
+ }
193
+ }
194
+ function normalizePluginId(rawName) {
195
+ const scopedPackage = rawName.match(/^@[^/]+\/(?:plugin|app)-(.+)$/);
196
+ if (scopedPackage) {
197
+ return scopedPackage[1] ?? rawName;
198
+ }
199
+ return rawName.replace(/^@[^/]+\//, "").replace(/^(plugin|app)-/, "");
200
+ }
201
+ function decodePluginPathSegment(rawSegment) {
202
+ try {
203
+ return decodeURIComponent(rawSegment);
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+ function resolveCompatConfigKey(pluginId, npmName, pluginMap) {
209
+ const candidates = /* @__PURE__ */ new Set([pluginId, normalizePluginId(pluginId)]);
210
+ if (typeof npmName === "string" && npmName.length > 0) {
211
+ candidates.add(npmName);
212
+ candidates.add(normalizePluginId(npmName));
213
+ }
214
+ for (const [configKey, packageName] of Object.entries(pluginMap)) {
215
+ if (candidates.has(configKey) || candidates.has(packageName) || candidates.has(normalizePluginId(packageName))) {
216
+ return configKey;
217
+ }
218
+ }
219
+ return null;
220
+ }
221
+ function readCompatSectionEnabled(section, configKey) {
222
+ if (!configKey) {
223
+ return void 0;
224
+ }
225
+ const sectionRecord = asRecord(section);
226
+ if (!sectionRecord) {
227
+ return void 0;
228
+ }
229
+ const targetRecord = asRecord(sectionRecord[configKey]);
230
+ if (!targetRecord || typeof targetRecord.enabled !== "boolean") {
231
+ return void 0;
232
+ }
233
+ return targetRecord.enabled;
234
+ }
235
+ function writeCompatSectionEnabled(parent, sectionKey, configKey, enabled) {
236
+ if (!configKey) {
237
+ return;
238
+ }
239
+ const section = asRecord(parent[sectionKey]) ?? {};
240
+ const entry = asRecord(section[configKey]) ?? {};
241
+ entry.enabled = enabled;
242
+ section[configKey] = entry;
243
+ parent[sectionKey] = section;
244
+ }
245
+ function syncCompatConnectorConfigValues(config, pluginId, npmName, values) {
246
+ const connectorKey = resolveCompatConfigKey(
247
+ pluginId,
248
+ npmName,
249
+ CONNECTOR_PLUGINS
250
+ );
251
+ if (!connectorKey) {
252
+ return;
253
+ }
254
+ const envMap = CONNECTOR_ENV_MAP[connectorKey];
255
+ if (!envMap) {
256
+ return;
257
+ }
258
+ const typedEnvMap = envMap;
259
+ const connectors = asRecord(config.connectors) ?? {};
260
+ const connectorEntry = asRecord(connectors[connectorKey]) ?? {};
261
+ const envToField = /* @__PURE__ */ new Map();
262
+ for (const [field, envKey] of Object.entries(typedEnvMap)) {
263
+ if (!envToField.has(envKey)) {
264
+ envToField.set(envKey, field);
265
+ }
266
+ }
267
+ let touched = false;
268
+ for (const [envKey, field] of envToField.entries()) {
269
+ if (!(envKey in values)) {
270
+ continue;
271
+ }
272
+ touched = true;
273
+ const value = values[envKey];
274
+ if (value.trim()) {
275
+ connectorEntry[field] = value;
276
+ } else {
277
+ delete connectorEntry[field];
278
+ }
279
+ }
280
+ if (connectorKey === "discord" && "DISCORD_API_TOKEN" in values) {
281
+ touched = true;
282
+ const tokenValue = values.DISCORD_API_TOKEN.trim();
283
+ if (tokenValue) {
284
+ connectorEntry.token = tokenValue;
285
+ } else {
286
+ delete connectorEntry.token;
287
+ }
288
+ delete connectorEntry.botToken;
289
+ }
290
+ if (!touched) {
291
+ return;
292
+ }
293
+ connectors[connectorKey] = connectorEntry;
294
+ config.connectors = connectors;
295
+ }
296
+ function resolvePersistedPluginEnabled(pluginId, category, npmName, configEntries, config) {
297
+ const pluginEnabled = typeof configEntries[pluginId]?.enabled === "boolean" ? Boolean(configEntries[pluginId]?.enabled) : void 0;
298
+ if (category === "connector") {
299
+ const connectorEnabled = readCompatSectionEnabled(
300
+ config.connectors,
301
+ resolveCompatConfigKey(pluginId, npmName, CONNECTOR_PLUGINS)
302
+ );
303
+ return connectorEnabled ?? pluginEnabled;
304
+ }
305
+ if (category === "streaming") {
306
+ const streamingEnabled = readCompatSectionEnabled(
307
+ config.streaming,
308
+ resolveCompatConfigKey(pluginId, npmName, STREAMING_PLUGINS)
309
+ );
310
+ return streamingEnabled ?? pluginEnabled;
311
+ }
312
+ return pluginEnabled;
313
+ }
314
+ function resolveCompatPluginEnabledForList(active, persistedEnabled, advancedCapabilityEnabled) {
315
+ return advancedCapabilityEnabled ?? persistedEnabled ?? active;
316
+ }
317
+ function shortPluginIdFromNpmName(npmName) {
318
+ if (!npmName || typeof npmName !== "string") {
319
+ return null;
320
+ }
321
+ if (npmName.startsWith("@elizaos/app-")) {
322
+ return npmName.slice("@elizaos/".length);
323
+ }
324
+ if (npmName.startsWith("@elizaos/plugin-")) {
325
+ return npmName.slice("@elizaos/plugin-".length);
326
+ }
327
+ return normalizePluginId(npmName);
328
+ }
329
+ function analyzePluginStateDrift(pluginList, configRecord, configEntries, allowList) {
330
+ const diagnostics = pluginList.map((plugin) => {
331
+ const pluginId = String(plugin.id);
332
+ const category = normalizePluginCategory(plugin.category);
333
+ const npmName = typeof plugin.npmName === "string" && plugin.npmName.length > 0 ? plugin.npmName : null;
334
+ const shortId = shortPluginIdFromNpmName(npmName) ?? pluginId;
335
+ const uiEnabled = Boolean(plugin.enabled);
336
+ const compatEnabled = category === "connector" ? readCompatSectionEnabled(
337
+ configRecord.connectors,
338
+ resolveCompatConfigKey(
339
+ pluginId,
340
+ npmName ?? void 0,
341
+ CONNECTOR_PLUGINS
342
+ )
343
+ ) : category === "streaming" ? readCompatSectionEnabled(
344
+ configRecord.streaming,
345
+ resolveCompatConfigKey(
346
+ pluginId,
347
+ npmName ?? void 0,
348
+ STREAMING_PLUGINS
349
+ )
350
+ ) : void 0;
351
+ const entryEnabled = typeof configEntries[pluginId]?.enabled === "boolean" ? Boolean(configEntries[pluginId]?.enabled) : void 0;
352
+ const enabledAllowList = allowList === null || npmName == null ? null : allowList.has(npmName) || allowList.has(shortId);
353
+ const isActive = Boolean(plugin.isActive);
354
+ const driftFlags = [];
355
+ if (compatEnabled !== void 0 && entryEnabled !== void 0 && compatEnabled !== entryEnabled) {
356
+ driftFlags.push("entries_vs_compat");
357
+ }
358
+ if (enabledAllowList !== null && entryEnabled !== void 0) {
359
+ if (enabledAllowList !== entryEnabled) {
360
+ driftFlags.push("entries_vs_allowlist");
361
+ }
362
+ }
363
+ if (uiEnabled && !isActive) {
364
+ driftFlags.push("inactive_but_enabled");
365
+ }
366
+ if (!uiEnabled && isActive) {
367
+ driftFlags.push("active_but_disabled");
368
+ }
369
+ return {
370
+ pluginId,
371
+ npmName,
372
+ category,
373
+ enabled_ui: uiEnabled,
374
+ enabled_allowlist: enabledAllowList,
375
+ is_active: isActive,
376
+ drift_flags: driftFlags
377
+ };
378
+ });
379
+ const withDrift = diagnostics.filter(
380
+ (plugin) => plugin.drift_flags.length > 0
381
+ );
382
+ const byFlag = {
383
+ entries_vs_compat: 0,
384
+ entries_vs_allowlist: 0,
385
+ inactive_but_enabled: 0,
386
+ active_but_disabled: 0
387
+ };
388
+ for (const plugin of withDrift) {
389
+ for (const flag of plugin.drift_flags) {
390
+ byFlag[flag] += 1;
391
+ }
392
+ }
393
+ return {
394
+ summary: {
395
+ total: diagnostics.length,
396
+ withDrift: withDrift.length,
397
+ byFlag
398
+ },
399
+ plugins: diagnostics
400
+ };
401
+ }
402
+ function buildPluginDriftDiagnostics(runtime) {
403
+ const pluginList = buildPluginListResponse(runtime).plugins;
404
+ const config = loadElizaConfig();
405
+ const configRecord = config;
406
+ const configEntries = config.plugins?.entries ?? {};
407
+ const allowList = Array.isArray(config.plugins?.allow) ? new Set(config.plugins.allow) : null;
408
+ return analyzePluginStateDrift(
409
+ pluginList,
410
+ configRecord,
411
+ configEntries,
412
+ allowList
413
+ );
414
+ }
415
+ function maybeLogPluginStateDrift(report) {
416
+ if (report.summary.withDrift === 0) {
417
+ return;
418
+ }
419
+ const drifted = report.plugins.filter((plugin) => plugin.drift_flags.length > 0).map((plugin) => `${plugin.pluginId}:${plugin.drift_flags.join("+")}`).sort();
420
+ const fingerprint = drifted.join("|");
421
+ const now = Date.now();
422
+ if (fingerprint === _lastDriftWarningFingerprint && now - _lastDriftWarningAt < DRIFT_LOG_THROTTLE_MS) {
423
+ return;
424
+ }
425
+ _lastDriftWarningAt = now;
426
+ _lastDriftWarningFingerprint = fingerprint;
427
+ logger.warn(
428
+ {
429
+ src: "api:plugins",
430
+ driftCount: report.summary.withDrift,
431
+ byFlag: report.summary.byFlag,
432
+ plugins: drifted
433
+ },
434
+ "Plugin enable-state drift detected between /api/plugins and /api/plugins/core models"
435
+ );
436
+ }
437
+ let _enabledStateReconciled = false;
438
+ function reconcilePluginEnabledStates() {
439
+ if (_enabledStateReconciled) return;
440
+ _enabledStateReconciled = true;
441
+ const config = loadElizaConfig();
442
+ const configRecord = config;
443
+ const entries = config.plugins?.entries ?? {};
444
+ let dirty = false;
445
+ for (const [pluginId, entry] of Object.entries(entries)) {
446
+ if (typeof entry.enabled !== "boolean") continue;
447
+ const connectorKey = resolveCompatConfigKey(
448
+ pluginId,
449
+ void 0,
450
+ CONNECTOR_PLUGINS
451
+ );
452
+ if (connectorKey) {
453
+ const sectionEnabled = readCompatSectionEnabled(
454
+ configRecord.connectors,
455
+ connectorKey
456
+ );
457
+ if (sectionEnabled !== void 0 && sectionEnabled !== entry.enabled) {
458
+ writeCompatSectionEnabled(
459
+ configRecord,
460
+ "connectors",
461
+ connectorKey,
462
+ entry.enabled
463
+ );
464
+ dirty = true;
465
+ }
466
+ }
467
+ const streamingKey = resolveCompatConfigKey(
468
+ pluginId,
469
+ void 0,
470
+ STREAMING_PLUGINS
471
+ );
472
+ if (streamingKey) {
473
+ const sectionEnabled = readCompatSectionEnabled(
474
+ configRecord.streaming,
475
+ streamingKey
476
+ );
477
+ if (sectionEnabled !== void 0 && sectionEnabled !== entry.enabled) {
478
+ writeCompatSectionEnabled(
479
+ configRecord,
480
+ "streaming",
481
+ streamingKey,
482
+ entry.enabled
483
+ );
484
+ dirty = true;
485
+ }
486
+ }
487
+ }
488
+ if (dirty) {
489
+ saveElizaConfig(config);
490
+ logger.info("[plugins] Reconciled drifted plugin enabled states in config");
491
+ }
492
+ }
493
+ function compatMutationRequiresRestart(plugin, body) {
494
+ if (typeof body.enabled === "boolean") {
495
+ return true;
496
+ }
497
+ if (body.config !== void 0 && (plugin.category === "connector" || plugin.category === "streaming")) {
498
+ return true;
499
+ }
500
+ return false;
501
+ }
502
+ function createCompatRuntimeApplyFallback(reason, requiresRestart) {
503
+ return {
504
+ mode: requiresRestart ? "restart_required" : "none",
505
+ requiresRestart,
506
+ restartedRuntime: false,
507
+ loadedPackages: [],
508
+ unloadedPackages: [],
509
+ reloadedPackages: [],
510
+ appliedConfigPackage: null,
511
+ reason
512
+ };
513
+ }
514
+ async function applyCompatRuntimeMutation(options) {
515
+ const { state, pluginId, plugin, body, previousConfig, nextConfig } = options;
516
+ const reason = typeof body.enabled === "boolean" ? `Plugin toggle: ${pluginId}` : `Plugin config updated: ${pluginId}`;
517
+ const requiresRestartFallback = compatMutationRequiresRestart(plugin, body);
518
+ if (!state.current) {
519
+ return createCompatRuntimeApplyFallback(reason, requiresRestartFallback);
520
+ }
521
+ try {
522
+ return await applyPluginRuntimeMutation({
523
+ runtime: state.current,
524
+ previousConfig,
525
+ nextConfig,
526
+ changedPluginId: pluginId,
527
+ changedPluginPackage: plugin.npmName,
528
+ config: body.config && typeof body.config === "object" && !Array.isArray(body.config) ? body.config : void 0,
529
+ expectRuntimeGraphChange: typeof body.enabled === "boolean",
530
+ reason
531
+ });
532
+ } catch (error) {
533
+ logger.warn(
534
+ `[api/plugins] Live runtime apply failed for "${pluginId}": ${error instanceof Error ? error.message : String(error)}`
535
+ );
536
+ return createCompatRuntimeApplyFallback(reason, true);
537
+ }
538
+ }
539
+ function titleCasePluginId(id) {
540
+ return id.split("-").filter((segment) => segment.length > 0).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
541
+ }
542
+ function inferSensitiveConfigKey(key) {
543
+ return /(?:_API_KEY|_SECRET|_TOKEN|_PASSWORD|_PRIVATE_KEY|_SIGNING_|ENCRYPTION_)/i.test(
544
+ key
545
+ );
546
+ }
547
+ function buildPluginParamDefs(parameters, savedValues) {
548
+ if (!parameters) {
549
+ return [];
550
+ }
551
+ const allKeys = Object.keys(parameters);
552
+ const GENERIC_FALLBACK_SUFFIXES = [
553
+ "SMALL_MODEL",
554
+ "LARGE_MODEL",
555
+ "IMAGE_MODEL",
556
+ "EMBEDDING_MODEL"
557
+ ];
558
+ const filteredEntries = Object.entries(parameters).filter(([key]) => {
559
+ if (!GENERIC_FALLBACK_SUFFIXES.includes(key)) return true;
560
+ return !allKeys.some((other) => other !== key && other.endsWith(`_${key}`));
561
+ });
562
+ return filteredEntries.map(([key, definition]) => {
563
+ const envValue = process.env[key]?.trim() || void 0;
564
+ const savedValue = savedValues?.[key];
565
+ const effectiveValue = envValue ?? (savedValue ? savedValue.trim() || void 0 : void 0);
566
+ const isSet = Boolean(effectiveValue);
567
+ const sensitive = typeof definition.sensitive === "boolean" ? definition.sensitive : inferSensitiveConfigKey(key);
568
+ const currentValue = !isSet || !effectiveValue ? null : sensitive ? maskValue(effectiveValue) : effectiveValue;
569
+ return {
570
+ key,
571
+ type: definition.type ?? "string",
572
+ description: definition.description ?? "",
573
+ required: definition.required === true || definition.optional === false && definition.required !== false,
574
+ sensitive,
575
+ default: definition.default === void 0 ? void 0 : String(definition.default),
576
+ options: Array.isArray(definition.options) ? definition.options : void 0,
577
+ currentValue,
578
+ isSet
579
+ };
580
+ });
581
+ }
582
+ function findNearestFile(startDir, fileName, maxDepth = 12) {
583
+ let dir = path.resolve(startDir);
584
+ for (let depth = 0; depth <= maxDepth; depth += 1) {
585
+ const candidate = path.join(dir, fileName);
586
+ if (fs.existsSync(candidate)) {
587
+ return candidate;
588
+ }
589
+ const parent = path.dirname(dir);
590
+ if (parent === dir) {
591
+ break;
592
+ }
593
+ dir = parent;
594
+ }
595
+ return null;
596
+ }
597
+ function resolvePluginManifestPath() {
598
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
599
+ const candidates = [
600
+ process.cwd(),
601
+ moduleDir,
602
+ path.dirname(process.execPath),
603
+ path.join(path.dirname(process.execPath), "..", "Resources", "app")
604
+ ];
605
+ for (const candidate of candidates) {
606
+ const manifestPath = findNearestFile(candidate, "plugins.json");
607
+ if (manifestPath) {
608
+ return manifestPath;
609
+ }
610
+ }
611
+ return null;
612
+ }
613
+ function resolveInstalledPackageVersion(packageName) {
614
+ if (!packageName) {
615
+ return null;
616
+ }
617
+ try {
618
+ const packageJsonPath = require2.resolve(`${packageName}/package.json`);
619
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
620
+ return typeof pkg.version === "string" ? pkg.version : null;
621
+ } catch {
622
+ return null;
623
+ }
624
+ }
625
+ function resolveLoadedPluginNames(runtime) {
626
+ const loadedNames = /* @__PURE__ */ new Set();
627
+ for (const plugin of runtime?.plugins ?? []) {
628
+ const name = plugin.name;
629
+ if (typeof name === "string" && name.length > 0) {
630
+ loadedNames.add(name);
631
+ }
632
+ }
633
+ return loadedNames;
634
+ }
635
+ function isPluginLoaded(pluginId, npmName, loadedNames) {
636
+ const expectedNames = /* @__PURE__ */ new Set([
637
+ pluginId,
638
+ `plugin-${pluginId}`,
639
+ `app-${pluginId}`,
640
+ npmName ?? ""
641
+ ]);
642
+ for (const loadedName of loadedNames) {
643
+ if (expectedNames.has(loadedName)) {
644
+ return true;
645
+ }
646
+ if (loadedName.endsWith(`/plugin-${pluginId}`) || loadedName.endsWith(`/app-${pluginId}`)) {
647
+ return true;
648
+ }
649
+ }
650
+ return false;
651
+ }
652
+ function resolveAdvancedCapabilityCompatStatus(pluginId, config, runtime) {
653
+ if (!isAdvancedCapabilityPluginId(pluginId)) {
654
+ return null;
655
+ }
656
+ const enabled = resolveAdvancedCapabilitiesEnabled(config);
657
+ if (!enabled) {
658
+ return { enabled: false, isActive: false };
659
+ }
660
+ const serviceType = ADVANCED_CAPABILITY_SERVICE_BY_PLUGIN_ID[pluginId];
661
+ return {
662
+ enabled: true,
663
+ isActive: serviceType ? Boolean(runtime?.getService(serviceType)) : Boolean(runtime)
664
+ };
665
+ }
666
+ function buildPluginListResponse(runtime) {
667
+ reconcilePluginEnabledStates();
668
+ const config = loadElizaConfig();
669
+ const configRecord = config;
670
+ const loadedNames = resolveLoadedPluginNames(runtime);
671
+ const registry = loadRegistry();
672
+ const manifestRoot = resolvePluginManifestPath() ? path.dirname(resolvePluginManifestPath() ?? "") : process.cwd();
673
+ const manifest = {
674
+ plugins: registry.all.map(registryEntryToManifest)
675
+ };
676
+ const configEntries = config.plugins?.entries ?? {};
677
+ const installEntries = config.plugins?.installs ?? {};
678
+ const plugins = /* @__PURE__ */ new Map();
679
+ for (const entry of manifest.plugins ?? []) {
680
+ const pluginId = normalizePluginId(entry.id);
681
+ const category = normalizePluginCategory(entry.category);
682
+ const bundledMeta = entry.dirName && manifestRoot ? readBundledPluginPackageMetadata(
683
+ manifestRoot,
684
+ entry.dirName,
685
+ entry.npmName
686
+ ) : void 0;
687
+ const configKeys = Array.isArray(entry.configKeys) && entry.configKeys.length > 0 ? entry.configKeys : bundledMeta?.configKeys ?? [];
688
+ const envKey = entry.envKey ?? findPrimaryEnvKey(configKeys);
689
+ const parameters = buildPluginParamDefs(
690
+ entry.pluginParameters ?? bundledMeta?.pluginParameters
691
+ );
692
+ const advancedCapabilityStatus = resolveAdvancedCapabilityCompatStatus(
693
+ pluginId,
694
+ config,
695
+ runtime
696
+ );
697
+ const active = advancedCapabilityStatus?.isActive ?? isPluginLoaded(pluginId, entry.npmName, loadedNames);
698
+ const persistedEnabled = resolvePersistedPluginEnabled(
699
+ pluginId,
700
+ category,
701
+ entry.npmName,
702
+ configEntries,
703
+ configRecord
704
+ );
705
+ const enabled = resolveCompatPluginEnabledForList(
706
+ active,
707
+ persistedEnabled,
708
+ advancedCapabilityStatus?.enabled
709
+ );
710
+ const validationErrors = parameters.filter((parameter) => parameter.required && !parameter.isSet).map((parameter) => ({
711
+ field: parameter.key,
712
+ message: "Required value is not configured."
713
+ }));
714
+ const registryEntry = registry.byId.get(pluginId);
715
+ plugins.set(pluginId, {
716
+ id: pluginId,
717
+ name: entry.name ?? titleCasePluginId(pluginId),
718
+ description: entry.description ?? bundledMeta?.description ?? "",
719
+ tags: entry.tags ?? [],
720
+ enabled,
721
+ configured: validationErrors.length === 0,
722
+ envKey,
723
+ category,
724
+ source: "bundled",
725
+ configKeys,
726
+ parameters,
727
+ validationErrors,
728
+ validationWarnings: [],
729
+ npmName: entry.npmName,
730
+ version: resolveInstalledPackageVersion(entry.npmName) ?? entry.version ?? void 0,
731
+ pluginDeps: entry.pluginDeps,
732
+ isActive: active,
733
+ configUiHints: entry.configUiHints ?? bundledMeta?.configUiHints,
734
+ icon: entry.logoUrl ?? bundledMeta?.icon ?? null,
735
+ homepage: entry.homepage ?? bundledMeta?.homepage,
736
+ repository: entry.repository ?? bundledMeta?.repository,
737
+ setupGuideUrl: entry.setupGuideUrl,
738
+ iconName: registryEntry?.render.icon,
739
+ group: registryEntry?.render.group,
740
+ groupOrder: registryEntry?.render.groupOrder,
741
+ visible: registryEntry?.render.visible ?? true
742
+ });
743
+ }
744
+ for (const entry of discoverPluginsFromManifest()) {
745
+ const pluginId = normalizePluginId(entry.id);
746
+ const category = normalizePluginCategory(entry.category);
747
+ if (category === "app" || plugins.has(pluginId)) {
748
+ continue;
749
+ }
750
+ const active = isPluginLoaded(pluginId, entry.npmName, loadedNames);
751
+ const persistedEnabled = resolvePersistedPluginEnabled(
752
+ pluginId,
753
+ category,
754
+ entry.npmName,
755
+ configEntries,
756
+ configRecord
757
+ );
758
+ plugins.set(pluginId, {
759
+ id: pluginId,
760
+ name: entry.name,
761
+ description: entry.description,
762
+ tags: entry.tags,
763
+ enabled: resolveCompatPluginEnabledForList(active, persistedEnabled),
764
+ configured: entry.configured,
765
+ envKey: entry.envKey,
766
+ category,
767
+ source: entry.source,
768
+ configKeys: entry.configKeys,
769
+ parameters: entry.parameters,
770
+ validationErrors: entry.validationErrors,
771
+ validationWarnings: entry.validationWarnings,
772
+ npmName: entry.npmName,
773
+ version: resolveInstalledPackageVersion(entry.npmName) ?? entry.version ?? void 0,
774
+ pluginDeps: entry.pluginDeps,
775
+ isActive: active,
776
+ configUiHints: entry.configUiHints,
777
+ icon: entry.icon ?? null,
778
+ homepage: entry.homepage,
779
+ repository: entry.repository,
780
+ setupGuideUrl: entry.setupGuideUrl,
781
+ visible: true
782
+ });
783
+ }
784
+ for (const plugin of runtime?.plugins ?? []) {
785
+ const pluginName = typeof plugin.name === "string" ? plugin.name : "";
786
+ if (!pluginName) {
787
+ continue;
788
+ }
789
+ const pluginId = normalizePluginId(pluginName);
790
+ const existing = plugins.get(pluginId);
791
+ if (existing) {
792
+ existing.isActive = true;
793
+ if (existing.enabled !== true && configEntries[pluginId]?.enabled == null) {
794
+ existing.enabled = true;
795
+ }
796
+ if (!existing.version) {
797
+ existing.version = resolveInstalledPackageVersion(pluginName) ?? void 0;
798
+ }
799
+ continue;
800
+ }
801
+ plugins.set(pluginId, {
802
+ id: pluginId,
803
+ name: titleCasePluginId(pluginId),
804
+ description: plugin.description ?? "Loaded runtime plugin discovered without manifest metadata.",
805
+ tags: [],
806
+ enabled: typeof configEntries[pluginId]?.enabled === "boolean" ? Boolean(configEntries[pluginId]?.enabled) : true,
807
+ configured: true,
808
+ envKey: null,
809
+ category: "feature",
810
+ source: "bundled",
811
+ parameters: [],
812
+ validationErrors: [],
813
+ validationWarnings: [],
814
+ npmName: pluginName,
815
+ version: resolveInstalledPackageVersion(pluginName) ?? void 0,
816
+ isActive: true,
817
+ icon: null
818
+ });
819
+ }
820
+ for (const [pluginName, installRecord] of Object.entries(installEntries)) {
821
+ const pluginId = normalizePluginId(pluginName);
822
+ if (plugins.has(pluginId)) {
823
+ continue;
824
+ }
825
+ plugins.set(pluginId, {
826
+ id: pluginId,
827
+ name: titleCasePluginId(pluginId),
828
+ description: "Installed store plugin.",
829
+ tags: [],
830
+ enabled: typeof configEntries[pluginId]?.enabled === "boolean" ? Boolean(configEntries[pluginId]?.enabled) : false,
831
+ configured: true,
832
+ envKey: null,
833
+ category: "feature",
834
+ source: "store",
835
+ parameters: [],
836
+ validationErrors: [],
837
+ validationWarnings: [],
838
+ npmName: pluginName,
839
+ version: typeof installRecord.version === "string" ? installRecord.version : resolveInstalledPackageVersion(pluginName) ?? void 0,
840
+ isActive: isPluginLoaded(pluginId, pluginName, loadedNames),
841
+ icon: null
842
+ });
843
+ }
844
+ const pluginList = Array.from(plugins.values()).sort(
845
+ (left, right) => String(left.name ?? "").localeCompare(String(right.name ?? ""))
846
+ );
847
+ return { plugins: pluginList };
848
+ }
849
+ function validateCompatPluginConfig(plugin, config) {
850
+ const paramMap = new Map(
851
+ plugin.parameters.map((parameter) => [parameter.key, parameter])
852
+ );
853
+ const errors = [];
854
+ const values = {};
855
+ for (const [key, rawValue] of Object.entries(config)) {
856
+ const parameter = paramMap.get(key);
857
+ if (!parameter) {
858
+ errors.push({
859
+ field: key,
860
+ message: `${key} is not a declared config key for this plugin`
861
+ });
862
+ continue;
863
+ }
864
+ if (typeof rawValue !== "string") {
865
+ errors.push({
866
+ field: key,
867
+ message: "Plugin config values must be strings."
868
+ });
869
+ continue;
870
+ }
871
+ const trimmed = rawValue.trim();
872
+ if (parameter.required && trimmed.length === 0) {
873
+ errors.push({
874
+ field: key,
875
+ message: "Required value is not configured."
876
+ });
877
+ continue;
878
+ }
879
+ values[key] = rawValue;
880
+ }
881
+ return { errors, values };
882
+ }
883
+ function persistCompatPluginMutation(pluginId, body, plugin) {
884
+ const config = loadElizaConfig();
885
+ const configRecord = config;
886
+ config.plugins ??= {};
887
+ config.plugins.entries ??= {};
888
+ config.plugins.entries[pluginId] ??= {};
889
+ const pluginEntry = config.plugins.entries[pluginId];
890
+ if (typeof body.enabled === "boolean") {
891
+ pluginEntry.enabled = body.enabled;
892
+ if (CAPABILITY_FEATURE_IDS.has(pluginId)) {
893
+ config.features ??= {};
894
+ config.features[pluginId] = body.enabled;
895
+ }
896
+ if (plugin.category === "connector") {
897
+ writeCompatSectionEnabled(
898
+ configRecord,
899
+ "connectors",
900
+ resolveCompatConfigKey(pluginId, plugin.npmName, CONNECTOR_PLUGINS),
901
+ body.enabled
902
+ );
903
+ }
904
+ if (plugin.category === "streaming") {
905
+ writeCompatSectionEnabled(
906
+ configRecord,
907
+ "streaming",
908
+ resolveCompatConfigKey(pluginId, plugin.npmName, STREAMING_PLUGINS),
909
+ body.enabled
910
+ );
911
+ }
912
+ }
913
+ if (body.config !== void 0) {
914
+ if (!body.config || typeof body.config !== "object" || Array.isArray(body.config)) {
915
+ return {
916
+ status: 400,
917
+ payload: { ok: false, error: "Plugin config must be a JSON object." }
918
+ };
919
+ }
920
+ const configObject = body.config;
921
+ const { errors, values } = validateCompatPluginConfig(plugin, configObject);
922
+ if (errors.length > 0) {
923
+ return {
924
+ status: 422,
925
+ payload: { ok: false, plugin, validationErrors: errors }
926
+ };
927
+ }
928
+ const nextConfig = pluginEntry.config && typeof pluginEntry.config === "object" && !Array.isArray(pluginEntry.config) ? { ...pluginEntry.config } : {};
929
+ config.env ??= {};
930
+ for (const [key, value] of Object.entries(values)) {
931
+ if (value.trim()) {
932
+ config.env[key] = value;
933
+ nextConfig[key] = value;
934
+ } else {
935
+ delete config.env[key];
936
+ delete nextConfig[key];
937
+ }
938
+ }
939
+ pluginEntry.config = nextConfig;
940
+ if (plugin.category === "connector") {
941
+ syncCompatConnectorConfigValues(
942
+ configRecord,
943
+ pluginId,
944
+ plugin.npmName,
945
+ values
946
+ );
947
+ }
948
+ saveElizaConfig(config);
949
+ for (const [key, value] of Object.entries(values)) {
950
+ try {
951
+ if (value.trim()) {
952
+ process.env[key] = value;
953
+ } else {
954
+ delete process.env[key];
955
+ }
956
+ } catch {
957
+ }
958
+ }
959
+ } else {
960
+ saveElizaConfig(config);
961
+ }
962
+ const refreshed = buildPluginListResponse(null).plugins.find(
963
+ (candidate) => candidate.id === pluginId
964
+ );
965
+ return {
966
+ status: 200,
967
+ payload: {
968
+ ok: true,
969
+ plugin: refreshed ?? plugin
970
+ }
971
+ };
972
+ }
973
+ async function handlePluginsCompatRoutes(req, res, state) {
974
+ const method = (req.method ?? "GET").toUpperCase();
975
+ const url = new URL(req.url ?? "/", "http://localhost");
976
+ if (!url.pathname.startsWith("/api/plugins")) {
977
+ return false;
978
+ }
979
+ if (method === "GET" && url.pathname === "/api/plugins") {
980
+ if (!await ensureRouteAuthorized(req, res, state)) {
981
+ return true;
982
+ }
983
+ const pluginResponse = buildPluginListResponse(state.current);
984
+ logger.debug(
985
+ `[api/plugins] source=registry total=${pluginResponse.plugins.length} runtime=${state.current ? "active" : "null"}`
986
+ );
987
+ maybeLogPluginStateDrift(buildPluginDriftDiagnostics(state.current));
988
+ sendJsonResponse(res, 200, pluginResponse);
989
+ return true;
990
+ }
991
+ if (method === "GET" && url.pathname === "/api/plugins/diagnostics") {
992
+ if (!await ensureRouteAuthorized(req, res, state)) {
993
+ return true;
994
+ }
995
+ const diagnostics = buildPluginDriftDiagnostics(state.current);
996
+ maybeLogPluginStateDrift(diagnostics);
997
+ sendJsonResponse(res, 200, diagnostics);
998
+ return true;
999
+ }
1000
+ if (method === "PUT" && url.pathname.startsWith("/api/plugins/")) {
1001
+ if (!await ensureRouteAuthorized(req, res, state)) {
1002
+ return true;
1003
+ }
1004
+ const body = await readCompatJsonBody(req, res);
1005
+ if (body == null) {
1006
+ return true;
1007
+ }
1008
+ const decodedPluginId = decodePluginPathSegment(
1009
+ url.pathname.slice("/api/plugins/".length)
1010
+ );
1011
+ if (decodedPluginId === null) {
1012
+ sendJsonErrorResponse(res, 400, "Invalid plugin path");
1013
+ return true;
1014
+ }
1015
+ const pluginId = normalizePluginId(decodedPluginId);
1016
+ const plugin = buildPluginListResponse(state.current).plugins.find(
1017
+ (candidate) => candidate.id === pluginId
1018
+ );
1019
+ if (!plugin) {
1020
+ sendJsonErrorResponse(res, 404, `Plugin "${pluginId}" not found`);
1021
+ return true;
1022
+ }
1023
+ const previousConfig = structuredClone(loadElizaConfig());
1024
+ const result = persistCompatPluginMutation(pluginId, body, plugin);
1025
+ if (result.status === 200) {
1026
+ const nextConfig = loadElizaConfig();
1027
+ const runtimeApply = await applyCompatRuntimeMutation({
1028
+ state,
1029
+ pluginId,
1030
+ plugin,
1031
+ body,
1032
+ previousConfig,
1033
+ nextConfig
1034
+ });
1035
+ if (runtimeApply.requiresRestart) {
1036
+ scheduleCompatRuntimeRestart(state, runtimeApply.reason);
1037
+ }
1038
+ const refreshed = buildPluginListResponse(state.current).plugins.find(
1039
+ (candidate) => candidate.id === pluginId
1040
+ );
1041
+ result.payload.plugin = refreshed ?? result.payload.plugin ?? plugin;
1042
+ result.payload.applied = runtimeApply.mode;
1043
+ result.payload.requiresRestart = runtimeApply.requiresRestart;
1044
+ result.payload.restartedRuntime = runtimeApply.restartedRuntime;
1045
+ result.payload.loadedPackages = runtimeApply.loadedPackages;
1046
+ result.payload.unloadedPackages = runtimeApply.unloadedPackages;
1047
+ result.payload.reloadedPackages = runtimeApply.reloadedPackages;
1048
+ const mirrorResult = await mirrorPluginSensitiveToVault(plugin, body);
1049
+ if (mirrorResult.failures.length > 0) {
1050
+ result.payload.vaultMirrorFailures = mirrorResult.failures;
1051
+ }
1052
+ const diagnostics = buildPluginDriftDiagnostics(state.current);
1053
+ if (diagnostics.summary.withDrift > 0) {
1054
+ result.payload.diagnostics = diagnostics;
1055
+ }
1056
+ }
1057
+ sendJsonResponse(res, result.status, result.payload);
1058
+ return true;
1059
+ }
1060
+ const testMatch = method === "POST" && url.pathname.match(/^\/api\/plugins\/([^/]+)\/test$/);
1061
+ if (testMatch) {
1062
+ if (!await ensureRouteAuthorized(req, res, state)) return true;
1063
+ const decodedTestPluginId = decodePluginPathSegment(testMatch[1]);
1064
+ if (decodedTestPluginId === null) {
1065
+ sendJsonErrorResponse(res, 400, "Invalid plugin path");
1066
+ return true;
1067
+ }
1068
+ const testPluginId = normalizePluginId(decodedTestPluginId);
1069
+ const startMs = Date.now();
1070
+ if (testPluginId === "telegram") {
1071
+ const token = process.env.TELEGRAM_BOT_TOKEN;
1072
+ if (!token) {
1073
+ sendJsonResponse(res, 422, {
1074
+ success: false,
1075
+ pluginId: testPluginId,
1076
+ error: "No bot token configured",
1077
+ durationMs: Date.now() - startMs
1078
+ });
1079
+ return true;
1080
+ }
1081
+ try {
1082
+ const apiRoot = process.env.TELEGRAM_API_ROOT || "https://api.telegram.org";
1083
+ const tgResp = await fetch(`${apiRoot}/bot${token}/getMe`);
1084
+ const tgData = await tgResp.json();
1085
+ sendJsonResponse(res, tgData.ok ? 200 : 422, {
1086
+ success: tgData.ok,
1087
+ pluginId: testPluginId,
1088
+ message: tgData.ok ? `Connected as @${tgData.result?.username}` : `Telegram API error: ${tgData.description}`,
1089
+ durationMs: Date.now() - startMs
1090
+ });
1091
+ } catch (err) {
1092
+ sendJsonResponse(res, 422, {
1093
+ success: false,
1094
+ pluginId: testPluginId,
1095
+ error: err instanceof Error ? err.message : String(err),
1096
+ durationMs: Date.now() - startMs
1097
+ });
1098
+ }
1099
+ return true;
1100
+ }
1101
+ sendJsonResponse(res, 200, {
1102
+ success: true,
1103
+ pluginId: testPluginId,
1104
+ message: "Plugin is loaded (no custom test available)",
1105
+ durationMs: Date.now() - startMs
1106
+ });
1107
+ return true;
1108
+ }
1109
+ const revealMatch = method === "POST" && url.pathname.match(/^\/api\/plugins\/([^/]+)\/reveal$/);
1110
+ if (revealMatch) {
1111
+ if (!await ensureRouteAuthorized(req, res, state)) return true;
1112
+ const revealBody = await readCompatJsonBody(req, res);
1113
+ if (revealBody == null) return true;
1114
+ const key = revealBody.key?.trim();
1115
+ if (!key) {
1116
+ sendJsonErrorResponse(res, 400, "Missing key parameter");
1117
+ return true;
1118
+ }
1119
+ const upperKey = key.toUpperCase();
1120
+ if (!REVEALABLE_KEY_PREFIXES.some((prefix) => upperKey.startsWith(prefix))) {
1121
+ sendJsonErrorResponse(
1122
+ res,
1123
+ 403,
1124
+ "Key is not in the allowlist of revealable plugin config keys"
1125
+ );
1126
+ return true;
1127
+ }
1128
+ if (SENSITIVE_KEY_PREFIXES.some((prefix) => upperKey.startsWith(prefix))) {
1129
+ if (!ensureCompatSensitiveRouteAuthorized(req, res)) return true;
1130
+ }
1131
+ try {
1132
+ const decodedRevealPluginId = decodePluginPathSegment(revealMatch[1]);
1133
+ if (decodedRevealPluginId === null) {
1134
+ sendJsonErrorResponse(res, 400, "Invalid plugin path");
1135
+ return true;
1136
+ }
1137
+ const vaultValue = await sharedVault().reveal(
1138
+ key,
1139
+ `plugins:${decodedRevealPluginId}:reveal`
1140
+ );
1141
+ sendJsonResponse(res, 200, { ok: true, value: vaultValue });
1142
+ return true;
1143
+ } catch (err) {
1144
+ if (!(err instanceof VaultMissError)) {
1145
+ logger.warn(
1146
+ `[api/plugins] Vault reveal failed for ${key}: ${err instanceof Error ? err.message : String(err)}`
1147
+ );
1148
+ sendJsonErrorResponse(res, 500, "Vault reveal failed");
1149
+ return true;
1150
+ }
1151
+ }
1152
+ const config = loadElizaConfig();
1153
+ const fallbackValue = process.env[key] ?? config.env?.[key] ?? null;
1154
+ if (typeof fallbackValue === "string" && isVaultRef(fallbackValue)) {
1155
+ const innerKey = parseVaultRef(fallbackValue);
1156
+ if (innerKey) {
1157
+ try {
1158
+ const inner = await sharedVault().get(innerKey);
1159
+ if (inner) {
1160
+ sendJsonResponse(res, 200, { ok: true, value: inner });
1161
+ return true;
1162
+ }
1163
+ } catch {
1164
+ }
1165
+ }
1166
+ sendJsonResponse(res, 200, { ok: true, value: null });
1167
+ return true;
1168
+ }
1169
+ sendJsonResponse(res, 200, { ok: true, value: fallbackValue });
1170
+ return true;
1171
+ }
1172
+ return false;
1173
+ }
1174
+ export {
1175
+ _resetSharedVaultForTesting,
1176
+ analyzePluginStateDrift,
1177
+ buildPluginListResponse,
1178
+ handlePluginsCompatRoutes,
1179
+ mirrorPluginSensitiveToVault,
1180
+ persistCompatPluginMutation,
1181
+ resolveAdvancedCapabilityCompatStatus,
1182
+ resolveCompatPluginEnabledForList,
1183
+ resolvePluginManifestPath
1184
+ };
1185
+ //# sourceMappingURL=app-plugins-routes.js.map