@elizaos/plugin-secrets-manager 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,4171 @@
1
+ // src/plugin.ts
2
+ import { logger as logger13 } from "@elizaos/core";
3
+
4
+ // src/services/secrets.ts
5
+ import {
6
+ Service,
7
+ logger as logger5
8
+ } from "@elizaos/core";
9
+
10
+ // src/types.ts
11
+ var SECRET_KEY_MAX_LENGTH = 256;
12
+ var SECRET_VALUE_MAX_LENGTH = 65536;
13
+ var SECRET_DESCRIPTION_MAX_LENGTH = 1024;
14
+ var SECRET_KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/;
15
+ var MAX_ACCESS_LOG_ENTRIES = 1e3;
16
+ var SecretsError = class extends Error {
17
+ constructor(message, code, details) {
18
+ super(message);
19
+ this.code = code;
20
+ this.details = details;
21
+ this.name = "SecretsError";
22
+ }
23
+ };
24
+ var PermissionDeniedError = class extends SecretsError {
25
+ constructor(key, action, context) {
26
+ super(
27
+ `Permission denied: cannot ${action} secret '${key}' at level '${context.level}'`,
28
+ "PERMISSION_DENIED",
29
+ { key, action, context }
30
+ );
31
+ this.name = "PermissionDeniedError";
32
+ }
33
+ };
34
+ var SecretNotFoundError = class extends SecretsError {
35
+ constructor(key, context) {
36
+ super(
37
+ `Secret '${key}' not found at level '${context.level}'`,
38
+ "SECRET_NOT_FOUND",
39
+ { key, context }
40
+ );
41
+ this.name = "SecretNotFoundError";
42
+ }
43
+ };
44
+ var ValidationError = class extends SecretsError {
45
+ constructor(key, message, details) {
46
+ super(
47
+ `Validation failed for secret '${key}': ${message}`,
48
+ "VALIDATION_FAILED",
49
+ { key, ...details }
50
+ );
51
+ this.name = "ValidationError";
52
+ }
53
+ };
54
+ var EncryptionError = class extends SecretsError {
55
+ constructor(message, details) {
56
+ super(message, "ENCRYPTION_ERROR", details);
57
+ this.name = "EncryptionError";
58
+ }
59
+ };
60
+ var StorageError = class extends SecretsError {
61
+ constructor(message, details) {
62
+ super(message, "STORAGE_ERROR", details);
63
+ this.name = "StorageError";
64
+ }
65
+ };
66
+
67
+ // src/crypto/encryption.ts
68
+ import {
69
+ createCipheriv,
70
+ createDecipheriv,
71
+ randomBytes,
72
+ createHash,
73
+ pbkdf2Sync,
74
+ scryptSync
75
+ } from "crypto";
76
+ var ALGORITHM_GCM = "aes-256-gcm";
77
+ var ALGORITHM_CBC = "aes-256-cbc";
78
+ var IV_LENGTH = 16;
79
+ var KEY_LENGTH = 32;
80
+ var DEFAULT_SALT_LENGTH = 32;
81
+ var DEFAULT_PBKDF2_ITERATIONS = 1e5;
82
+ function generateSalt(length = DEFAULT_SALT_LENGTH) {
83
+ return randomBytes(length).toString("base64");
84
+ }
85
+ function generateKey() {
86
+ return randomBytes(KEY_LENGTH);
87
+ }
88
+ function deriveKeyPbkdf2(password, salt, iterations = DEFAULT_PBKDF2_ITERATIONS) {
89
+ const saltBuffer = typeof salt === "string" ? Buffer.from(salt, "base64") : salt;
90
+ return pbkdf2Sync(password, saltBuffer, iterations, KEY_LENGTH, "sha256");
91
+ }
92
+ function deriveKeyFromAgentId(agentId, salt = "default-salt") {
93
+ return createHash("sha256").update(agentId + salt).digest();
94
+ }
95
+ function createKeyDerivationParams(salt, iterations = DEFAULT_PBKDF2_ITERATIONS) {
96
+ return {
97
+ salt: salt ?? generateSalt(),
98
+ iterations,
99
+ algorithm: "pbkdf2-sha256",
100
+ keyLength: KEY_LENGTH
101
+ };
102
+ }
103
+ function encryptGcm(plaintext, key, keyId = "default") {
104
+ if (key.length !== KEY_LENGTH) {
105
+ throw new EncryptionError(
106
+ `Invalid key length: expected ${KEY_LENGTH}, got ${key.length}`
107
+ );
108
+ }
109
+ const iv = randomBytes(IV_LENGTH);
110
+ const cipher = createCipheriv(ALGORITHM_GCM, key, iv);
111
+ let encrypted = cipher.update(plaintext, "utf8", "base64");
112
+ encrypted += cipher.final("base64");
113
+ const authTag = cipher.getAuthTag();
114
+ return {
115
+ value: encrypted,
116
+ iv: iv.toString("base64"),
117
+ authTag: authTag.toString("base64"),
118
+ algorithm: "aes-256-gcm",
119
+ keyId
120
+ };
121
+ }
122
+ function encrypt(plaintext, key, keyId = "default") {
123
+ return encryptGcm(plaintext, key, keyId);
124
+ }
125
+ function decryptGcm(encrypted, key) {
126
+ if (key.length !== KEY_LENGTH) {
127
+ throw new EncryptionError(
128
+ `Invalid key length: expected ${KEY_LENGTH}, got ${key.length}`
129
+ );
130
+ }
131
+ if (encrypted.algorithm !== "aes-256-gcm") {
132
+ throw new EncryptionError(
133
+ `Algorithm mismatch: expected aes-256-gcm, got ${encrypted.algorithm}`
134
+ );
135
+ }
136
+ if (!encrypted.authTag) {
137
+ throw new EncryptionError("Missing authentication tag for GCM decryption");
138
+ }
139
+ const iv = Buffer.from(encrypted.iv, "base64");
140
+ const authTag = Buffer.from(encrypted.authTag, "base64");
141
+ const decipher = createDecipheriv(ALGORITHM_GCM, key, iv);
142
+ decipher.setAuthTag(authTag);
143
+ let decrypted = decipher.update(encrypted.value, "base64", "utf8");
144
+ decrypted += decipher.final("utf8");
145
+ return decrypted;
146
+ }
147
+ function decryptCbc(encrypted, key) {
148
+ if (key.length !== KEY_LENGTH) {
149
+ throw new EncryptionError(
150
+ `Invalid key length: expected ${KEY_LENGTH}, got ${key.length}`
151
+ );
152
+ }
153
+ if (encrypted.algorithm !== "aes-256-cbc") {
154
+ throw new EncryptionError(
155
+ `Algorithm mismatch: expected aes-256-cbc, got ${encrypted.algorithm}`
156
+ );
157
+ }
158
+ const iv = Buffer.from(encrypted.iv, "base64");
159
+ const decipher = createDecipheriv(ALGORITHM_CBC, key, iv);
160
+ let decrypted = decipher.update(encrypted.value, "base64", "utf8");
161
+ decrypted += decipher.final("utf8");
162
+ return decrypted;
163
+ }
164
+ function decrypt(encrypted, key) {
165
+ if (typeof encrypted === "string") {
166
+ return encrypted;
167
+ }
168
+ switch (encrypted.algorithm) {
169
+ case "aes-256-gcm":
170
+ return decryptGcm(encrypted, key);
171
+ case "aes-256-cbc":
172
+ return decryptCbc(encrypted, key);
173
+ default:
174
+ throw new EncryptionError(
175
+ `Unsupported algorithm: ${encrypted.algorithm}`
176
+ );
177
+ }
178
+ }
179
+ function isEncryptedSecret(value) {
180
+ if (!value || typeof value !== "object") {
181
+ return false;
182
+ }
183
+ const obj = value;
184
+ return typeof obj.value === "string" && typeof obj.iv === "string" && typeof obj.algorithm === "string" && (obj.algorithm === "aes-256-gcm" || obj.algorithm === "aes-256-cbc");
185
+ }
186
+ function generateSecureToken(length = 32) {
187
+ return randomBytes(length).toString("hex");
188
+ }
189
+ var KeyManager = class {
190
+ keys = /* @__PURE__ */ new Map();
191
+ currentKeyId = "default";
192
+ derivationParams = null;
193
+ constructor(options) {
194
+ if (options?.primaryKey) {
195
+ const keyId = options.primaryKeyId ?? "default";
196
+ this.keys.set(keyId, options.primaryKey);
197
+ this.currentKeyId = keyId;
198
+ }
199
+ if (options?.derivationParams) {
200
+ this.derivationParams = options.derivationParams;
201
+ }
202
+ }
203
+ /**
204
+ * Initialize with a password-derived key
205
+ */
206
+ initializeFromPassword(password, salt) {
207
+ this.derivationParams = createKeyDerivationParams(salt);
208
+ const key = deriveKeyPbkdf2(
209
+ password,
210
+ this.derivationParams.salt,
211
+ this.derivationParams.iterations
212
+ );
213
+ this.keys.set("default", key);
214
+ this.currentKeyId = "default";
215
+ }
216
+ /**
217
+ * Initialize with an agent ID (OpenClaw compatible)
218
+ */
219
+ initializeFromAgentId(agentId, salt) {
220
+ const key = deriveKeyFromAgentId(agentId, salt);
221
+ this.keys.set("default", key);
222
+ this.currentKeyId = "default";
223
+ }
224
+ /**
225
+ * Add a key for decryption (supports key rotation)
226
+ */
227
+ addKey(keyId, key) {
228
+ this.keys.set(keyId, key);
229
+ }
230
+ /**
231
+ * Set the current key for encryption
232
+ */
233
+ setCurrentKey(keyId) {
234
+ if (!this.keys.has(keyId)) {
235
+ throw new EncryptionError(`Key not found: ${keyId}`);
236
+ }
237
+ this.currentKeyId = keyId;
238
+ }
239
+ /**
240
+ * Get the current key ID
241
+ */
242
+ getCurrentKeyId() {
243
+ return this.currentKeyId;
244
+ }
245
+ /**
246
+ * Get a key by ID
247
+ */
248
+ getKey(keyId) {
249
+ return this.keys.get(keyId);
250
+ }
251
+ /**
252
+ * Get the current encryption key
253
+ */
254
+ getCurrentKey() {
255
+ const key = this.keys.get(this.currentKeyId);
256
+ if (!key) {
257
+ throw new EncryptionError("No encryption key configured");
258
+ }
259
+ return key;
260
+ }
261
+ /**
262
+ * Get derivation parameters (for storage)
263
+ */
264
+ getDerivationParams() {
265
+ return this.derivationParams;
266
+ }
267
+ /**
268
+ * Encrypt a value with the current key
269
+ */
270
+ encrypt(plaintext) {
271
+ return encryptGcm(plaintext, this.getCurrentKey(), this.currentKeyId);
272
+ }
273
+ /**
274
+ * Decrypt a value (automatically selects the correct key)
275
+ */
276
+ decrypt(encrypted) {
277
+ if (typeof encrypted === "string") {
278
+ return encrypted;
279
+ }
280
+ const key = this.keys.get(encrypted.keyId);
281
+ if (!key) {
282
+ throw new EncryptionError(
283
+ `Key not found for decryption: ${encrypted.keyId}`
284
+ );
285
+ }
286
+ return decrypt(encrypted, key);
287
+ }
288
+ /**
289
+ * Re-encrypt a value with the current key (for key rotation)
290
+ */
291
+ reencrypt(encrypted) {
292
+ const plaintext = this.decrypt(encrypted);
293
+ return this.encrypt(plaintext);
294
+ }
295
+ /**
296
+ * Clear all keys from memory
297
+ */
298
+ clear() {
299
+ for (const key of this.keys.values()) {
300
+ key.fill(0);
301
+ }
302
+ this.keys.clear();
303
+ }
304
+ };
305
+
306
+ // src/storage/interface.ts
307
+ var BaseSecretStorage = class {
308
+ /**
309
+ * Create a default secret configuration
310
+ */
311
+ createDefaultConfig(key, context, partial) {
312
+ return {
313
+ type: partial?.type ?? "secret",
314
+ required: partial?.required ?? false,
315
+ description: partial?.description ?? `Secret: ${key}`,
316
+ canGenerate: partial?.canGenerate ?? false,
317
+ validationMethod: partial?.validationMethod,
318
+ status: partial?.status ?? "valid",
319
+ lastError: partial?.lastError,
320
+ attempts: partial?.attempts ?? 0,
321
+ createdAt: partial?.createdAt ?? Date.now(),
322
+ validatedAt: partial?.validatedAt ?? Date.now(),
323
+ plugin: partial?.plugin ?? context.level,
324
+ level: context.level,
325
+ ownerId: context.userId,
326
+ worldId: context.worldId,
327
+ encrypted: partial?.encrypted ?? true,
328
+ permissions: partial?.permissions ?? [],
329
+ sharedWith: partial?.sharedWith ?? [],
330
+ expiresAt: partial?.expiresAt
331
+ };
332
+ }
333
+ };
334
+ var CompositeSecretStorage = class {
335
+ storageType = "memory";
336
+ globalStorage;
337
+ worldStorage;
338
+ userStorage;
339
+ constructor(options) {
340
+ this.globalStorage = options.globalStorage;
341
+ this.worldStorage = options.worldStorage;
342
+ this.userStorage = options.userStorage;
343
+ }
344
+ async initialize() {
345
+ await Promise.all([
346
+ this.globalStorage.initialize(),
347
+ this.worldStorage.initialize(),
348
+ this.userStorage.initialize()
349
+ ]);
350
+ }
351
+ getStorageForContext(context) {
352
+ switch (context.level) {
353
+ case "global":
354
+ return this.globalStorage;
355
+ case "world":
356
+ return this.worldStorage;
357
+ case "user":
358
+ return this.userStorage;
359
+ default:
360
+ return this.globalStorage;
361
+ }
362
+ }
363
+ async exists(key, context) {
364
+ return this.getStorageForContext(context).exists(key, context);
365
+ }
366
+ async get(key, context) {
367
+ return this.getStorageForContext(context).get(key, context);
368
+ }
369
+ async set(key, value, context, config) {
370
+ return this.getStorageForContext(context).set(key, value, context, config);
371
+ }
372
+ async delete(key, context) {
373
+ return this.getStorageForContext(context).delete(key, context);
374
+ }
375
+ async list(context) {
376
+ return this.getStorageForContext(context).list(context);
377
+ }
378
+ async getConfig(key, context) {
379
+ return this.getStorageForContext(context).getConfig(key, context);
380
+ }
381
+ async updateConfig(key, context, config) {
382
+ return this.getStorageForContext(context).updateConfig(
383
+ key,
384
+ context,
385
+ config
386
+ );
387
+ }
388
+ };
389
+
390
+ // src/storage/memory-store.ts
391
+ var MemorySecretStorage = class extends BaseSecretStorage {
392
+ storageType = "memory";
393
+ store = /* @__PURE__ */ new Map();
394
+ async initialize() {
395
+ }
396
+ async exists(key, context) {
397
+ const storageKey = this.generateStorageKey(key, context);
398
+ return this.store.has(storageKey);
399
+ }
400
+ async get(key, context) {
401
+ const storageKey = this.generateStorageKey(key, context);
402
+ const entry = this.store.get(storageKey);
403
+ if (!entry) {
404
+ return null;
405
+ }
406
+ if (entry.config.expiresAt && entry.config.expiresAt < Date.now()) {
407
+ this.store.delete(storageKey);
408
+ return null;
409
+ }
410
+ return entry.value;
411
+ }
412
+ async set(key, value, context, config) {
413
+ const storageKey = this.generateStorageKey(key, context);
414
+ const existingEntry = this.store.get(storageKey);
415
+ const fullConfig = this.createDefaultConfig(key, context, {
416
+ ...existingEntry?.config,
417
+ ...config
418
+ });
419
+ this.store.set(storageKey, {
420
+ value,
421
+ config: fullConfig
422
+ });
423
+ return true;
424
+ }
425
+ async delete(key, context) {
426
+ const storageKey = this.generateStorageKey(key, context);
427
+ return this.store.delete(storageKey);
428
+ }
429
+ async list(context) {
430
+ const prefix = this.getContextPrefix(context);
431
+ const metadata = {};
432
+ for (const [storageKey, entry] of this.store) {
433
+ if (storageKey.startsWith(prefix)) {
434
+ const originalKey = this.extractOriginalKey(storageKey, context);
435
+ if (originalKey) {
436
+ if (entry.config.expiresAt && entry.config.expiresAt < Date.now()) {
437
+ this.store.delete(storageKey);
438
+ continue;
439
+ }
440
+ metadata[originalKey] = { ...entry.config };
441
+ }
442
+ }
443
+ }
444
+ return metadata;
445
+ }
446
+ async getConfig(key, context) {
447
+ const storageKey = this.generateStorageKey(key, context);
448
+ const entry = this.store.get(storageKey);
449
+ if (!entry) {
450
+ return null;
451
+ }
452
+ return { ...entry.config };
453
+ }
454
+ async updateConfig(key, context, config) {
455
+ const storageKey = this.generateStorageKey(key, context);
456
+ const entry = this.store.get(storageKey);
457
+ if (!entry) {
458
+ return false;
459
+ }
460
+ entry.config = {
461
+ ...entry.config,
462
+ ...config
463
+ };
464
+ this.store.set(storageKey, entry);
465
+ return true;
466
+ }
467
+ /**
468
+ * Generate a storage key from the secret key and context
469
+ */
470
+ generateStorageKey(key, context) {
471
+ switch (context.level) {
472
+ case "global":
473
+ return `global:${context.agentId}:${key}`;
474
+ case "world":
475
+ return `world:${context.worldId}:${key}`;
476
+ case "user":
477
+ return `user:${context.userId}:${key}`;
478
+ default:
479
+ return `unknown:${key}`;
480
+ }
481
+ }
482
+ /**
483
+ * Get the storage key prefix for a context level
484
+ */
485
+ getContextPrefix(context) {
486
+ switch (context.level) {
487
+ case "global":
488
+ return `global:${context.agentId}:`;
489
+ case "world":
490
+ return `world:${context.worldId}:`;
491
+ case "user":
492
+ return `user:${context.userId}:`;
493
+ default:
494
+ return "";
495
+ }
496
+ }
497
+ /**
498
+ * Extract the original key from a storage key
499
+ */
500
+ extractOriginalKey(storageKey, context) {
501
+ const prefix = this.getContextPrefix(context);
502
+ if (!storageKey.startsWith(prefix)) {
503
+ return null;
504
+ }
505
+ return storageKey.slice(prefix.length);
506
+ }
507
+ /**
508
+ * Clear all stored secrets (for testing)
509
+ */
510
+ clear() {
511
+ this.store.clear();
512
+ }
513
+ /**
514
+ * Get the number of stored secrets (for testing)
515
+ */
516
+ size() {
517
+ return this.store.size;
518
+ }
519
+ };
520
+
521
+ // src/storage/character-store.ts
522
+ import { logger } from "@elizaos/core";
523
+ var SECRETS_KEY = "secrets";
524
+ var METADATA_KEY = "__secrets_metadata";
525
+ var CharacterSettingsStorage = class extends BaseSecretStorage {
526
+ storageType = "character";
527
+ runtime;
528
+ keyManager;
529
+ initialized = false;
530
+ constructor(runtime, keyManager) {
531
+ super();
532
+ this.runtime = runtime;
533
+ this.keyManager = keyManager;
534
+ }
535
+ async initialize() {
536
+ if (this.initialized) {
537
+ return;
538
+ }
539
+ logger.debug("[CharacterSettingsStorage] Initializing");
540
+ this.ensureSettingsStructure();
541
+ this.initialized = true;
542
+ logger.debug("[CharacterSettingsStorage] Initialized");
543
+ }
544
+ /**
545
+ * Ensure the character.settings.secrets structure exists
546
+ */
547
+ ensureSettingsStructure() {
548
+ if (!this.runtime.character.settings) {
549
+ this.runtime.character.settings = {};
550
+ }
551
+ if (!this.runtime.character.settings[SECRETS_KEY]) {
552
+ this.runtime.character.settings[SECRETS_KEY] = {};
553
+ }
554
+ }
555
+ async exists(key, _context) {
556
+ this.ensureSettingsStructure();
557
+ const secrets = this.getSecretsObject();
558
+ return key in secrets;
559
+ }
560
+ async get(key, context) {
561
+ this.ensureSettingsStructure();
562
+ const secrets = this.getSecretsObject();
563
+ const stored = secrets[key];
564
+ if (stored === void 0 || stored === null) {
565
+ return null;
566
+ }
567
+ if (typeof stored === "string") {
568
+ return stored;
569
+ }
570
+ if (typeof stored === "object") {
571
+ const storedSecret = stored;
572
+ if (storedSecret.config?.expiresAt && storedSecret.config.expiresAt < Date.now()) {
573
+ await this.delete(key, context);
574
+ return null;
575
+ }
576
+ if (isEncryptedSecret(storedSecret.value)) {
577
+ return this.keyManager.decrypt(storedSecret.value);
578
+ }
579
+ if (typeof storedSecret.value === "string") {
580
+ return storedSecret.value;
581
+ }
582
+ }
583
+ return null;
584
+ }
585
+ async set(key, value, context, config) {
586
+ this.ensureSettingsStructure();
587
+ const secrets = this.getSecretsObject();
588
+ const existingStored = secrets[key];
589
+ const existingConfig = typeof existingStored === "object" ? existingStored.config : void 0;
590
+ const fullConfig = this.createDefaultConfig(key, context, {
591
+ ...existingConfig,
592
+ ...config
593
+ });
594
+ const shouldEncrypt = fullConfig.encrypted !== false;
595
+ const storedValue = shouldEncrypt ? this.keyManager.encrypt(value) : value;
596
+ const storedSecret = {
597
+ value: storedValue,
598
+ config: fullConfig
599
+ };
600
+ secrets[key] = storedSecret;
601
+ logger.debug(`[CharacterSettingsStorage] Set secret: ${key}`);
602
+ return true;
603
+ }
604
+ async delete(key, _context) {
605
+ this.ensureSettingsStructure();
606
+ const secrets = this.getSecretsObject();
607
+ if (!(key in secrets)) {
608
+ return false;
609
+ }
610
+ delete secrets[key];
611
+ logger.debug(`[CharacterSettingsStorage] Deleted secret: ${key}`);
612
+ return true;
613
+ }
614
+ async list(_context) {
615
+ const secrets = this.getSecretsObject();
616
+ const metadata = {};
617
+ for (const [key, stored] of Object.entries(secrets)) {
618
+ if (key === METADATA_KEY) {
619
+ continue;
620
+ }
621
+ if (typeof stored === "object" && stored !== null) {
622
+ const storedSecret = stored;
623
+ if (storedSecret.config?.expiresAt && storedSecret.config.expiresAt < Date.now()) {
624
+ continue;
625
+ }
626
+ if (storedSecret.config) {
627
+ metadata[key] = { ...storedSecret.config };
628
+ }
629
+ } else {
630
+ metadata[key] = this.createDefaultConfig(key, {
631
+ level: "global",
632
+ agentId: this.runtime.agentId
633
+ });
634
+ }
635
+ }
636
+ return metadata;
637
+ }
638
+ async getConfig(key, context) {
639
+ const secrets = this.getSecretsObject();
640
+ const stored = secrets[key];
641
+ if (!stored) {
642
+ return null;
643
+ }
644
+ if (typeof stored === "object" && "config" in stored) {
645
+ return { ...stored.config };
646
+ }
647
+ return this.createDefaultConfig(key, context);
648
+ }
649
+ async updateConfig(key, _context, config) {
650
+ this.ensureSettingsStructure();
651
+ const secrets = this.getSecretsObject();
652
+ const stored = secrets[key];
653
+ if (!stored) {
654
+ return false;
655
+ }
656
+ if (typeof stored === "object" && "config" in stored) {
657
+ const storedSecret = stored;
658
+ storedSecret.config = {
659
+ ...storedSecret.config,
660
+ ...config
661
+ };
662
+ secrets[key] = storedSecret;
663
+ } else {
664
+ const defaultConfig = this.createDefaultConfig(key, {
665
+ level: "global",
666
+ agentId: this.runtime.agentId
667
+ });
668
+ const storedSecret = {
669
+ value: stored,
670
+ config: { ...defaultConfig, ...config }
671
+ };
672
+ secrets[key] = storedSecret;
673
+ }
674
+ return true;
675
+ }
676
+ /**
677
+ * Get the secrets object from character settings
678
+ *
679
+ * Accesses character.settings.secrets directly instead of using getSetting()
680
+ * because getSetting() only returns primitives, not objects.
681
+ */
682
+ getSecretsObject() {
683
+ this.ensureSettingsStructure();
684
+ const settings = this.runtime.character.settings;
685
+ const secrets = settings[SECRETS_KEY];
686
+ if (!secrets || typeof secrets !== "object") {
687
+ return {};
688
+ }
689
+ return secrets;
690
+ }
691
+ /**
692
+ * Migrate legacy environment variable format to new format
693
+ */
694
+ async migrateFromEnvVars(envVarPrefix = "ENV_") {
695
+ let migrated = 0;
696
+ const settings = this.runtime.character?.settings ?? {};
697
+ for (const [key, value] of Object.entries(settings)) {
698
+ if (key.startsWith(envVarPrefix) && typeof value === "string") {
699
+ const secretKey = key.slice(envVarPrefix.length);
700
+ const exists = await this.exists(secretKey, {
701
+ level: "global",
702
+ agentId: this.runtime.agentId
703
+ });
704
+ if (!exists) {
705
+ await this.set(
706
+ secretKey,
707
+ value,
708
+ { level: "global", agentId: this.runtime.agentId },
709
+ {
710
+ description: `Migrated from ${key}`,
711
+ encrypted: true
712
+ }
713
+ );
714
+ migrated++;
715
+ }
716
+ }
717
+ }
718
+ logger.info(
719
+ `[CharacterSettingsStorage] Migrated ${migrated} env vars to secrets`
720
+ );
721
+ return migrated;
722
+ }
723
+ /**
724
+ * Get a secret as an environment variable (for backward compatibility)
725
+ */
726
+ async getAsEnvVar(key) {
727
+ return this.get(key, { level: "global", agentId: this.runtime.agentId });
728
+ }
729
+ /**
730
+ * Sync a secret to process.env (for plugins that read env vars directly)
731
+ */
732
+ async syncToEnv(key, envVarName) {
733
+ const value = await this.getAsEnvVar(key);
734
+ if (value === null) {
735
+ return false;
736
+ }
737
+ const envKey = envVarName ?? key;
738
+ process.env[envKey] = value;
739
+ return true;
740
+ }
741
+ /**
742
+ * Sync all secrets to process.env
743
+ */
744
+ async syncAllToEnv() {
745
+ const metadata = await this.list({
746
+ level: "global",
747
+ agentId: this.runtime.agentId
748
+ });
749
+ let synced = 0;
750
+ for (const key of Object.keys(metadata)) {
751
+ if (await this.syncToEnv(key)) {
752
+ synced++;
753
+ }
754
+ }
755
+ return synced;
756
+ }
757
+ };
758
+
759
+ // src/storage/world-store.ts
760
+ import { logger as logger2, Role } from "@elizaos/core";
761
+ var WorldMetadataStorage = class extends BaseSecretStorage {
762
+ storageType = "world";
763
+ runtime;
764
+ keyManager;
765
+ worldCache = /* @__PURE__ */ new Map();
766
+ constructor(runtime, keyManager) {
767
+ super();
768
+ this.runtime = runtime;
769
+ this.keyManager = keyManager;
770
+ }
771
+ async initialize() {
772
+ logger2.debug("[WorldMetadataStorage] Initialized");
773
+ }
774
+ async exists(key, context) {
775
+ if (!context.worldId) {
776
+ return false;
777
+ }
778
+ const secrets = await this.getWorldSecrets(context.worldId);
779
+ return key in secrets;
780
+ }
781
+ async get(key, context) {
782
+ if (!context.worldId) {
783
+ logger2.warn("[WorldMetadataStorage] Cannot get secret without worldId");
784
+ return null;
785
+ }
786
+ const secrets = await this.getWorldSecrets(context.worldId);
787
+ const stored = secrets[key];
788
+ if (stored === void 0 || stored === null) {
789
+ return null;
790
+ }
791
+ if (typeof stored === "string") {
792
+ return stored;
793
+ }
794
+ if (typeof stored === "object") {
795
+ const storedSecret = stored;
796
+ if (storedSecret.config?.expiresAt && storedSecret.config.expiresAt < Date.now()) {
797
+ await this.delete(key, context);
798
+ return null;
799
+ }
800
+ if (isEncryptedSecret(storedSecret.value)) {
801
+ return this.keyManager.decrypt(storedSecret.value);
802
+ }
803
+ if (typeof storedSecret.value === "string") {
804
+ return storedSecret.value;
805
+ }
806
+ }
807
+ return null;
808
+ }
809
+ async set(key, value, context, config) {
810
+ if (!context.worldId) {
811
+ throw new StorageError("Cannot set world secret without worldId");
812
+ }
813
+ if (context.requesterId) {
814
+ const hasPermission = await this.checkWritePermission(
815
+ context.worldId,
816
+ context.requesterId
817
+ );
818
+ if (!hasPermission) {
819
+ throw new PermissionDeniedError(key, "write", context);
820
+ }
821
+ }
822
+ const world = await this.getWorld(context.worldId);
823
+ if (!world) {
824
+ throw new StorageError(`World not found: ${context.worldId}`);
825
+ }
826
+ if (!world.metadata) {
827
+ world.metadata = {};
828
+ }
829
+ const worldMeta = world.metadata;
830
+ if (!worldMeta.secrets) {
831
+ worldMeta.secrets = {};
832
+ }
833
+ const secrets = worldMeta.secrets;
834
+ const existingStored = secrets[key];
835
+ const existingConfig = typeof existingStored === "object" ? existingStored.config : void 0;
836
+ const fullConfig = this.createDefaultConfig(key, context, {
837
+ ...existingConfig,
838
+ ...config,
839
+ worldId: context.worldId
840
+ });
841
+ const shouldEncrypt = fullConfig.encrypted !== false;
842
+ const storedValue = shouldEncrypt ? this.keyManager.encrypt(value) : value;
843
+ const storedSecret = {
844
+ value: storedValue,
845
+ config: fullConfig
846
+ };
847
+ secrets[key] = storedSecret;
848
+ worldMeta.secrets = secrets;
849
+ await this.runtime.updateWorld(world);
850
+ this.worldCache.set(context.worldId, world);
851
+ logger2.debug(
852
+ `[WorldMetadataStorage] Set secret: ${key} for world: ${context.worldId}`
853
+ );
854
+ return true;
855
+ }
856
+ async delete(key, context) {
857
+ if (!context.worldId) {
858
+ return false;
859
+ }
860
+ if (context.requesterId) {
861
+ const hasPermission = await this.checkWritePermission(
862
+ context.worldId,
863
+ context.requesterId
864
+ );
865
+ if (!hasPermission) {
866
+ throw new PermissionDeniedError(key, "delete", context);
867
+ }
868
+ }
869
+ const world = await this.getWorld(context.worldId);
870
+ if (!world) {
871
+ return false;
872
+ }
873
+ const metadata = world.metadata;
874
+ if (!metadata?.secrets) {
875
+ return false;
876
+ }
877
+ const secrets = metadata.secrets;
878
+ if (!(key in secrets)) {
879
+ return false;
880
+ }
881
+ delete secrets[key];
882
+ metadata.secrets = secrets;
883
+ await this.runtime.updateWorld(world);
884
+ this.worldCache.set(context.worldId, world);
885
+ logger2.debug(
886
+ `[WorldMetadataStorage] Deleted secret: ${key} from world: ${context.worldId}`
887
+ );
888
+ return true;
889
+ }
890
+ async list(context) {
891
+ if (!context.worldId) {
892
+ return {};
893
+ }
894
+ const secrets = await this.getWorldSecrets(context.worldId);
895
+ const metadata = {};
896
+ for (const [key, stored] of Object.entries(secrets)) {
897
+ if (typeof stored === "object" && stored !== null) {
898
+ const storedSecret = stored;
899
+ if (storedSecret.config?.expiresAt && storedSecret.config.expiresAt < Date.now()) {
900
+ continue;
901
+ }
902
+ if (storedSecret.config) {
903
+ metadata[key] = { ...storedSecret.config };
904
+ }
905
+ } else {
906
+ metadata[key] = this.createDefaultConfig(key, context);
907
+ }
908
+ }
909
+ return metadata;
910
+ }
911
+ async getConfig(key, context) {
912
+ if (!context.worldId) {
913
+ return null;
914
+ }
915
+ const secrets = await this.getWorldSecrets(context.worldId);
916
+ const stored = secrets[key];
917
+ if (!stored) {
918
+ return null;
919
+ }
920
+ if (typeof stored === "object" && "config" in stored) {
921
+ return { ...stored.config };
922
+ }
923
+ return this.createDefaultConfig(key, context);
924
+ }
925
+ async updateConfig(key, context, config) {
926
+ if (!context.worldId) {
927
+ return false;
928
+ }
929
+ if (context.requesterId) {
930
+ const hasPermission = await this.checkWritePermission(
931
+ context.worldId,
932
+ context.requesterId
933
+ );
934
+ if (!hasPermission) {
935
+ throw new PermissionDeniedError(key, "write", context);
936
+ }
937
+ }
938
+ const world = await this.getWorld(context.worldId);
939
+ if (!world) {
940
+ return false;
941
+ }
942
+ const metadata = world.metadata;
943
+ if (!metadata?.secrets) {
944
+ return false;
945
+ }
946
+ const secrets = metadata.secrets;
947
+ const stored = secrets[key];
948
+ if (!stored) {
949
+ return false;
950
+ }
951
+ if (typeof stored === "object" && "config" in stored) {
952
+ const storedSecret = stored;
953
+ storedSecret.config = {
954
+ ...storedSecret.config,
955
+ ...config
956
+ };
957
+ secrets[key] = storedSecret;
958
+ } else {
959
+ const defaultConfig = this.createDefaultConfig(key, context);
960
+ const storedSecret = {
961
+ value: stored,
962
+ config: { ...defaultConfig, ...config }
963
+ };
964
+ secrets[key] = storedSecret;
965
+ }
966
+ metadata.secrets = secrets;
967
+ await this.runtime.updateWorld(world);
968
+ this.worldCache.set(context.worldId, world);
969
+ return true;
970
+ }
971
+ /**
972
+ * Get a world from cache or database
973
+ */
974
+ async getWorld(worldId) {
975
+ const cached = this.worldCache.get(worldId);
976
+ if (cached) {
977
+ return cached;
978
+ }
979
+ const world = await this.runtime.getWorld(worldId);
980
+ if (world) {
981
+ this.worldCache.set(worldId, world);
982
+ }
983
+ return world;
984
+ }
985
+ /**
986
+ * Get secrets object from world metadata
987
+ */
988
+ async getWorldSecrets(worldId) {
989
+ const world = await this.getWorld(worldId);
990
+ const metadata = world?.metadata;
991
+ if (!metadata?.secrets) {
992
+ return {};
993
+ }
994
+ return metadata.secrets;
995
+ }
996
+ /**
997
+ * Check if a user has write permission in a world
998
+ */
999
+ async checkWritePermission(worldId, userId) {
1000
+ const world = await this.getWorld(worldId);
1001
+ if (!world) {
1002
+ return false;
1003
+ }
1004
+ if (userId === this.runtime.agentId) {
1005
+ return true;
1006
+ }
1007
+ const roles = world.metadata?.roles;
1008
+ if (!roles) {
1009
+ return false;
1010
+ }
1011
+ const userRole = roles[userId];
1012
+ return userRole === Role.OWNER || userRole === Role.ADMIN;
1013
+ }
1014
+ /**
1015
+ * Clear the world cache
1016
+ */
1017
+ clearCache() {
1018
+ this.worldCache.clear();
1019
+ }
1020
+ /**
1021
+ * Invalidate a specific world in the cache
1022
+ */
1023
+ invalidateWorld(worldId) {
1024
+ this.worldCache.delete(worldId);
1025
+ }
1026
+ };
1027
+
1028
+ // src/storage/component-store.ts
1029
+ import { logger as logger3, createUniqueUuid } from "@elizaos/core";
1030
+ var COMPONENT_TYPE = "secret";
1031
+ var ComponentSecretStorage = class extends BaseSecretStorage {
1032
+ storageType = "component";
1033
+ runtime;
1034
+ keyManager;
1035
+ constructor(runtime, keyManager) {
1036
+ super();
1037
+ this.runtime = runtime;
1038
+ this.keyManager = keyManager;
1039
+ }
1040
+ async initialize() {
1041
+ logger3.debug("[ComponentSecretStorage] Initialized");
1042
+ }
1043
+ async exists(key, context) {
1044
+ if (!context.userId) {
1045
+ return false;
1046
+ }
1047
+ const component = await this.findSecretComponent(context.userId, key);
1048
+ return component !== null;
1049
+ }
1050
+ async get(key, context) {
1051
+ if (!context.userId) {
1052
+ logger3.warn("[ComponentSecretStorage] Cannot get secret without userId");
1053
+ return null;
1054
+ }
1055
+ if (context.requesterId && context.requesterId !== context.userId) {
1056
+ throw new PermissionDeniedError(key, "read", context);
1057
+ }
1058
+ const component = await this.findSecretComponent(context.userId, key);
1059
+ if (!component) {
1060
+ return null;
1061
+ }
1062
+ const data = component.data;
1063
+ if (!data) {
1064
+ return null;
1065
+ }
1066
+ if (data.config?.expiresAt && data.config.expiresAt < Date.now()) {
1067
+ await this.delete(key, context);
1068
+ return null;
1069
+ }
1070
+ if (isEncryptedSecret(data.value)) {
1071
+ return this.keyManager.decrypt(data.value);
1072
+ }
1073
+ if (typeof data.value === "string") {
1074
+ return data.value;
1075
+ }
1076
+ return null;
1077
+ }
1078
+ async set(key, value, context, config) {
1079
+ if (!context.userId) {
1080
+ throw new StorageError("Cannot set user secret without userId");
1081
+ }
1082
+ if (context.requesterId && context.requesterId !== context.userId) {
1083
+ throw new PermissionDeniedError(key, "write", context);
1084
+ }
1085
+ const existingComponent = await this.findSecretComponent(
1086
+ context.userId,
1087
+ key
1088
+ );
1089
+ const existingData = existingComponent?.data;
1090
+ const existingConfig = existingData?.config;
1091
+ const fullConfig = this.createDefaultConfig(key, context, {
1092
+ ...existingConfig,
1093
+ ...config,
1094
+ ownerId: context.userId
1095
+ });
1096
+ const shouldEncrypt = fullConfig.encrypted !== false;
1097
+ const storedValue = shouldEncrypt ? this.keyManager.encrypt(value) : value;
1098
+ const componentData = {
1099
+ key,
1100
+ value: storedValue,
1101
+ config: fullConfig,
1102
+ updatedAt: Date.now()
1103
+ };
1104
+ if (existingComponent) {
1105
+ await this.runtime.updateComponent({
1106
+ ...existingComponent,
1107
+ data: componentData
1108
+ });
1109
+ logger3.debug(
1110
+ `[ComponentSecretStorage] Updated secret: ${key} for user: ${context.userId}`
1111
+ );
1112
+ } else {
1113
+ const newComponent = {
1114
+ id: createUniqueUuid(this.runtime, `${context.userId}-secret-${key}`),
1115
+ createdAt: Date.now(),
1116
+ entityId: context.userId,
1117
+ agentId: this.runtime.agentId,
1118
+ roomId: this.runtime.agentId,
1119
+ worldId: this.runtime.agentId,
1120
+ sourceEntityId: context.userId,
1121
+ type: COMPONENT_TYPE,
1122
+ data: componentData
1123
+ };
1124
+ await this.runtime.createComponent(newComponent);
1125
+ logger3.debug(
1126
+ `[ComponentSecretStorage] Created secret: ${key} for user: ${context.userId}`
1127
+ );
1128
+ }
1129
+ return true;
1130
+ }
1131
+ async delete(key, context) {
1132
+ if (!context.userId) {
1133
+ return false;
1134
+ }
1135
+ if (context.requesterId && context.requesterId !== context.userId) {
1136
+ throw new PermissionDeniedError(key, "delete", context);
1137
+ }
1138
+ const component = await this.findSecretComponent(context.userId, key);
1139
+ if (!component) {
1140
+ return false;
1141
+ }
1142
+ await this.runtime.deleteComponent(component.id);
1143
+ logger3.debug(
1144
+ `[ComponentSecretStorage] Deleted secret: ${key} for user: ${context.userId}`
1145
+ );
1146
+ return true;
1147
+ }
1148
+ async list(context) {
1149
+ if (!context.userId) {
1150
+ return {};
1151
+ }
1152
+ if (context.requesterId && context.requesterId !== context.userId) {
1153
+ throw new PermissionDeniedError("*", "read", context);
1154
+ }
1155
+ const components = await this.runtime.getComponents(context.userId);
1156
+ const metadata = {};
1157
+ for (const component of components) {
1158
+ if (component.type !== COMPONENT_TYPE) {
1159
+ continue;
1160
+ }
1161
+ const data = component.data;
1162
+ if (!data?.key || !data?.config) {
1163
+ continue;
1164
+ }
1165
+ if (data.config.expiresAt && data.config.expiresAt < Date.now()) {
1166
+ continue;
1167
+ }
1168
+ metadata[data.key] = { ...data.config };
1169
+ }
1170
+ return metadata;
1171
+ }
1172
+ async getConfig(key, context) {
1173
+ if (!context.userId) {
1174
+ return null;
1175
+ }
1176
+ const component = await this.findSecretComponent(context.userId, key);
1177
+ if (!component) {
1178
+ return null;
1179
+ }
1180
+ const data = component.data;
1181
+ return data?.config ? { ...data.config } : null;
1182
+ }
1183
+ async updateConfig(key, context, config) {
1184
+ if (!context.userId) {
1185
+ return false;
1186
+ }
1187
+ if (context.requesterId && context.requesterId !== context.userId) {
1188
+ throw new PermissionDeniedError(key, "write", context);
1189
+ }
1190
+ const component = await this.findSecretComponent(context.userId, key);
1191
+ if (!component) {
1192
+ return false;
1193
+ }
1194
+ const data = component.data;
1195
+ if (!data) {
1196
+ return false;
1197
+ }
1198
+ data.config = {
1199
+ ...data.config,
1200
+ ...config
1201
+ };
1202
+ data.updatedAt = Date.now();
1203
+ await this.runtime.updateComponent({
1204
+ ...component,
1205
+ data
1206
+ });
1207
+ return true;
1208
+ }
1209
+ /**
1210
+ * Find a secret component for a user by key
1211
+ */
1212
+ async findSecretComponent(userId, key) {
1213
+ const components = await this.runtime.getComponents(userId);
1214
+ for (const component of components) {
1215
+ if (component.type !== COMPONENT_TYPE) {
1216
+ continue;
1217
+ }
1218
+ const data = component.data;
1219
+ if (data?.key === key) {
1220
+ return component;
1221
+ }
1222
+ }
1223
+ return null;
1224
+ }
1225
+ /**
1226
+ * Get all secret keys for a user
1227
+ */
1228
+ async listKeys(userId) {
1229
+ const components = await this.runtime.getComponents(userId);
1230
+ const keys = [];
1231
+ for (const component of components) {
1232
+ if (component.type !== COMPONENT_TYPE) {
1233
+ continue;
1234
+ }
1235
+ const data = component.data;
1236
+ if (data?.key) {
1237
+ keys.push(data.key);
1238
+ }
1239
+ }
1240
+ return keys;
1241
+ }
1242
+ /**
1243
+ * Delete all secrets for a user
1244
+ */
1245
+ async deleteAllForUser(userId) {
1246
+ const components = await this.runtime.getComponents(userId);
1247
+ let deleted = 0;
1248
+ for (const component of components) {
1249
+ if (component.type !== COMPONENT_TYPE) {
1250
+ continue;
1251
+ }
1252
+ await this.runtime.deleteComponent(component.id);
1253
+ deleted++;
1254
+ }
1255
+ logger3.info(
1256
+ `[ComponentSecretStorage] Deleted ${deleted} secrets for user: ${userId}`
1257
+ );
1258
+ return deleted;
1259
+ }
1260
+ /**
1261
+ * Count secrets for a user
1262
+ */
1263
+ async countForUser(userId) {
1264
+ const components = await this.runtime.getComponents(userId);
1265
+ let count = 0;
1266
+ for (const component of components) {
1267
+ if (component.type === COMPONENT_TYPE) {
1268
+ count++;
1269
+ }
1270
+ }
1271
+ return count;
1272
+ }
1273
+ };
1274
+
1275
+ // src/validation.ts
1276
+ import { logger as logger4 } from "@elizaos/core";
1277
+ var ValidationStrategies = {
1278
+ /**
1279
+ * No validation - always passes
1280
+ */
1281
+ none: async (_key, _value) => ({
1282
+ isValid: true,
1283
+ validatedAt: Date.now()
1284
+ }),
1285
+ /**
1286
+ * OpenAI API key validation
1287
+ * Format: sk-... or sk-proj-...
1288
+ */
1289
+ "api_key:openai": async (key, value) => {
1290
+ const validatedAt = Date.now();
1291
+ if (!value.startsWith("sk-")) {
1292
+ return {
1293
+ isValid: false,
1294
+ error: 'OpenAI API key must start with "sk-"',
1295
+ validatedAt
1296
+ };
1297
+ }
1298
+ if (value.length < 20) {
1299
+ return {
1300
+ isValid: false,
1301
+ error: "OpenAI API key is too short",
1302
+ validatedAt
1303
+ };
1304
+ }
1305
+ const shouldVerify = process.env.VALIDATE_API_KEYS === "true";
1306
+ if (shouldVerify) {
1307
+ const verified = await verifyOpenAIKey(value);
1308
+ if (!verified.isValid) {
1309
+ return { ...verified, validatedAt };
1310
+ }
1311
+ }
1312
+ return { isValid: true, validatedAt };
1313
+ },
1314
+ /**
1315
+ * Anthropic API key validation
1316
+ * Format: sk-ant-...
1317
+ */
1318
+ "api_key:anthropic": async (key, value) => {
1319
+ const validatedAt = Date.now();
1320
+ if (!value.startsWith("sk-ant-")) {
1321
+ return {
1322
+ isValid: false,
1323
+ error: 'Anthropic API key must start with "sk-ant-"',
1324
+ validatedAt
1325
+ };
1326
+ }
1327
+ if (value.length < 30) {
1328
+ return {
1329
+ isValid: false,
1330
+ error: "Anthropic API key is too short",
1331
+ validatedAt
1332
+ };
1333
+ }
1334
+ const shouldVerify = process.env.VALIDATE_API_KEYS === "true";
1335
+ if (shouldVerify) {
1336
+ const verified = await verifyAnthropicKey(value);
1337
+ if (!verified.isValid) {
1338
+ return { ...verified, validatedAt };
1339
+ }
1340
+ }
1341
+ return { isValid: true, validatedAt };
1342
+ },
1343
+ /**
1344
+ * Groq API key validation
1345
+ * Format: gsk_...
1346
+ */
1347
+ "api_key:groq": async (key, value) => {
1348
+ const validatedAt = Date.now();
1349
+ if (!value.startsWith("gsk_")) {
1350
+ return {
1351
+ isValid: false,
1352
+ error: 'Groq API key must start with "gsk_"',
1353
+ validatedAt
1354
+ };
1355
+ }
1356
+ if (value.length < 20) {
1357
+ return {
1358
+ isValid: false,
1359
+ error: "Groq API key is too short",
1360
+ validatedAt
1361
+ };
1362
+ }
1363
+ return { isValid: true, validatedAt };
1364
+ },
1365
+ /**
1366
+ * Google API key validation
1367
+ * Format: AIza...
1368
+ */
1369
+ "api_key:google": async (key, value) => {
1370
+ const validatedAt = Date.now();
1371
+ if (!value.startsWith("AIza")) {
1372
+ return {
1373
+ isValid: false,
1374
+ error: 'Google API key must start with "AIza"',
1375
+ validatedAt
1376
+ };
1377
+ }
1378
+ if (value.length < 30) {
1379
+ return {
1380
+ isValid: false,
1381
+ error: "Google API key is too short",
1382
+ validatedAt
1383
+ };
1384
+ }
1385
+ return { isValid: true, validatedAt };
1386
+ },
1387
+ /**
1388
+ * Mistral API key validation
1389
+ */
1390
+ "api_key:mistral": async (key, value) => {
1391
+ const validatedAt = Date.now();
1392
+ if (value.length < 20) {
1393
+ return {
1394
+ isValid: false,
1395
+ error: "Mistral API key is too short",
1396
+ validatedAt
1397
+ };
1398
+ }
1399
+ return { isValid: true, validatedAt };
1400
+ },
1401
+ /**
1402
+ * Cohere API key validation
1403
+ */
1404
+ "api_key:cohere": async (key, value) => {
1405
+ const validatedAt = Date.now();
1406
+ if (value.length < 20) {
1407
+ return {
1408
+ isValid: false,
1409
+ error: "Cohere API key is too short",
1410
+ validatedAt
1411
+ };
1412
+ }
1413
+ return { isValid: true, validatedAt };
1414
+ },
1415
+ /**
1416
+ * URL format validation
1417
+ */
1418
+ "url:valid": async (key, value) => {
1419
+ const validatedAt = Date.now();
1420
+ try {
1421
+ new URL(value);
1422
+ return { isValid: true, validatedAt };
1423
+ } catch {
1424
+ return {
1425
+ isValid: false,
1426
+ error: "Invalid URL format",
1427
+ validatedAt
1428
+ };
1429
+ }
1430
+ },
1431
+ /**
1432
+ * URL reachability validation
1433
+ */
1434
+ "url:reachable": async (key, value) => {
1435
+ const validatedAt = Date.now();
1436
+ try {
1437
+ new URL(value);
1438
+ } catch {
1439
+ return {
1440
+ isValid: false,
1441
+ error: "Invalid URL format",
1442
+ validatedAt
1443
+ };
1444
+ }
1445
+ try {
1446
+ const response = await fetch(value, {
1447
+ method: "HEAD",
1448
+ signal: AbortSignal.timeout(5e3)
1449
+ });
1450
+ if (!response.ok) {
1451
+ return {
1452
+ isValid: false,
1453
+ error: `URL returned status ${response.status}`,
1454
+ validatedAt
1455
+ };
1456
+ }
1457
+ return { isValid: true, validatedAt };
1458
+ } catch (error) {
1459
+ return {
1460
+ isValid: false,
1461
+ error: `URL is not reachable: ${error instanceof Error ? error.message : "Unknown error"}`,
1462
+ validatedAt
1463
+ };
1464
+ }
1465
+ },
1466
+ /**
1467
+ * Custom validation placeholder
1468
+ */
1469
+ custom: async (key, value) => {
1470
+ return {
1471
+ isValid: true,
1472
+ details: "Custom validation not implemented",
1473
+ validatedAt: Date.now()
1474
+ };
1475
+ }
1476
+ };
1477
+ var customValidators = /* @__PURE__ */ new Map();
1478
+ function registerValidator(name, validator) {
1479
+ customValidators.set(name, validator);
1480
+ logger4.debug(`[Validation] Registered custom validator: ${name}`);
1481
+ }
1482
+ function unregisterValidator(name) {
1483
+ return customValidators.delete(name);
1484
+ }
1485
+ function getValidator(strategy) {
1486
+ if (strategy in ValidationStrategies) {
1487
+ return ValidationStrategies[strategy];
1488
+ }
1489
+ return customValidators.get(strategy);
1490
+ }
1491
+ async function validateSecret(key, value, strategy) {
1492
+ const strategyName = strategy ?? "none";
1493
+ const validator = getValidator(strategyName);
1494
+ if (!validator) {
1495
+ logger4.warn(`[Validation] Unknown validation strategy: ${strategyName}`);
1496
+ return {
1497
+ isValid: true,
1498
+ details: `Unknown validation strategy: ${strategyName}`,
1499
+ validatedAt: Date.now()
1500
+ };
1501
+ }
1502
+ try {
1503
+ return await validator(key, value);
1504
+ } catch (error) {
1505
+ const errorMessage = error instanceof Error ? error.message : "Validation error";
1506
+ logger4.error(`[Validation] Error validating ${key}: ${errorMessage}`);
1507
+ return {
1508
+ isValid: false,
1509
+ error: errorMessage,
1510
+ validatedAt: Date.now()
1511
+ };
1512
+ }
1513
+ }
1514
+ function inferValidationStrategy(key) {
1515
+ const upperKey = key.toUpperCase();
1516
+ if (upperKey.includes("OPENAI") && upperKey.includes("KEY")) {
1517
+ return "api_key:openai";
1518
+ }
1519
+ if (upperKey.includes("ANTHROPIC") && upperKey.includes("KEY")) {
1520
+ return "api_key:anthropic";
1521
+ }
1522
+ if (upperKey.includes("GROQ") && upperKey.includes("KEY")) {
1523
+ return "api_key:groq";
1524
+ }
1525
+ if (upperKey.includes("GOOGLE") && upperKey.includes("KEY")) {
1526
+ return "api_key:google";
1527
+ }
1528
+ if (upperKey.includes("MISTRAL") && upperKey.includes("KEY")) {
1529
+ return "api_key:mistral";
1530
+ }
1531
+ if (upperKey.includes("COHERE") && upperKey.includes("KEY")) {
1532
+ return "api_key:cohere";
1533
+ }
1534
+ if (upperKey.includes("URL") || upperKey.includes("ENDPOINT")) {
1535
+ return "url:valid";
1536
+ }
1537
+ return "none";
1538
+ }
1539
+ async function verifyOpenAIKey(apiKey) {
1540
+ try {
1541
+ const response = await fetch("https://api.openai.com/v1/models", {
1542
+ method: "GET",
1543
+ headers: {
1544
+ Authorization: `Bearer ${apiKey}`
1545
+ },
1546
+ signal: AbortSignal.timeout(1e4)
1547
+ });
1548
+ if (response.status === 401) {
1549
+ return { isValid: false, error: "Invalid API key" };
1550
+ }
1551
+ if (response.status === 429) {
1552
+ return { isValid: true };
1553
+ }
1554
+ if (!response.ok) {
1555
+ return {
1556
+ isValid: false,
1557
+ error: `API returned status ${response.status}`
1558
+ };
1559
+ }
1560
+ return { isValid: true };
1561
+ } catch (error) {
1562
+ return {
1563
+ isValid: false,
1564
+ error: `Failed to verify: ${error instanceof Error ? error.message : "Unknown error"}`
1565
+ };
1566
+ }
1567
+ }
1568
+ async function verifyAnthropicKey(apiKey) {
1569
+ try {
1570
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
1571
+ method: "POST",
1572
+ headers: {
1573
+ "x-api-key": apiKey,
1574
+ "anthropic-version": "2023-06-01",
1575
+ "content-type": "application/json"
1576
+ },
1577
+ body: JSON.stringify({
1578
+ model: "claude-3-haiku-20240307",
1579
+ max_tokens: 1,
1580
+ messages: [{ role: "user", content: "Hi" }]
1581
+ }),
1582
+ signal: AbortSignal.timeout(1e4)
1583
+ });
1584
+ if (response.status === 401) {
1585
+ return { isValid: false, error: "Invalid API key" };
1586
+ }
1587
+ if (response.status === 429) {
1588
+ return { isValid: true };
1589
+ }
1590
+ if (response.status === 400) {
1591
+ const body = await response.json().catch(() => ({}));
1592
+ if (body.error?.type === "invalid_request_error") {
1593
+ return { isValid: true };
1594
+ }
1595
+ }
1596
+ if (!response.ok && response.status !== 400) {
1597
+ return {
1598
+ isValid: false,
1599
+ error: `API returned status ${response.status}`
1600
+ };
1601
+ }
1602
+ return { isValid: true };
1603
+ } catch (error) {
1604
+ return {
1605
+ isValid: false,
1606
+ error: `Failed to verify: ${error instanceof Error ? error.message : "Unknown error"}`
1607
+ };
1608
+ }
1609
+ }
1610
+
1611
+ // src/services/secrets.ts
1612
+ var SECRETS_SERVICE_TYPE = "SECRETS";
1613
+ var DEFAULT_CONFIG = {
1614
+ enableEncryption: true,
1615
+ encryptionSalt: void 0,
1616
+ enableAccessLogging: true,
1617
+ maxAccessLogEntries: MAX_ACCESS_LOG_ENTRIES
1618
+ };
1619
+ var SecretsService = class _SecretsService extends Service {
1620
+ static serviceType = SECRETS_SERVICE_TYPE;
1621
+ capabilityDescription = "Manage secrets at global, world, and user levels with encryption and access control";
1622
+ secretsConfig;
1623
+ keyManager;
1624
+ storage;
1625
+ globalStorage;
1626
+ worldStorage;
1627
+ userStorage;
1628
+ accessLogs = [];
1629
+ changeCallbacks = /* @__PURE__ */ new Map();
1630
+ globalChangeCallbacks = [];
1631
+ constructor(runtime, config) {
1632
+ super(runtime);
1633
+ this.secretsConfig = { ...DEFAULT_CONFIG, ...config };
1634
+ this.keyManager = new KeyManager();
1635
+ if (runtime) {
1636
+ this.keyManager.initializeFromAgentId(
1637
+ runtime.agentId,
1638
+ this.secretsConfig.encryptionSalt ?? runtime.getSetting("ENCRYPTION_SALT")
1639
+ );
1640
+ this.globalStorage = new CharacterSettingsStorage(
1641
+ runtime,
1642
+ this.keyManager
1643
+ );
1644
+ this.worldStorage = new WorldMetadataStorage(runtime, this.keyManager);
1645
+ this.userStorage = new ComponentSecretStorage(runtime, this.keyManager);
1646
+ this.storage = new CompositeSecretStorage({
1647
+ globalStorage: this.globalStorage,
1648
+ worldStorage: this.worldStorage,
1649
+ userStorage: this.userStorage
1650
+ });
1651
+ }
1652
+ }
1653
+ /**
1654
+ * Start the service
1655
+ */
1656
+ static async start(runtime, config) {
1657
+ const service = new _SecretsService(runtime, config);
1658
+ await service.initialize();
1659
+ return service;
1660
+ }
1661
+ /**
1662
+ * Initialize the service
1663
+ */
1664
+ async initialize() {
1665
+ logger5.info("[SecretsService] Initializing");
1666
+ await this.storage.initialize();
1667
+ const migrated = await this.globalStorage.migrateFromEnvVars();
1668
+ if (migrated > 0) {
1669
+ logger5.info(`[SecretsService] Migrated ${migrated} legacy env vars`);
1670
+ }
1671
+ const synced = await this.globalStorage.syncAllToEnv();
1672
+ logger5.debug(`[SecretsService] Synced ${synced} secrets to process.env`);
1673
+ logger5.info("[SecretsService] Initialized");
1674
+ }
1675
+ /**
1676
+ * Stop the service
1677
+ */
1678
+ async stop() {
1679
+ logger5.info("[SecretsService] Stopping");
1680
+ this.keyManager.clear();
1681
+ this.accessLogs = [];
1682
+ this.changeCallbacks.clear();
1683
+ this.globalChangeCallbacks = [];
1684
+ logger5.info("[SecretsService] Stopped");
1685
+ }
1686
+ // ============================================================================
1687
+ // Core Secret Operations
1688
+ // ============================================================================
1689
+ /**
1690
+ * Get a secret value
1691
+ */
1692
+ async get(key, context) {
1693
+ this.logAccess(key, "read", context, true);
1694
+ const value = await this.storage.get(key, context);
1695
+ if (value === null) {
1696
+ this.logAccess(key, "read", context, false, "Secret not found");
1697
+ }
1698
+ return value;
1699
+ }
1700
+ /**
1701
+ * Set a secret value
1702
+ */
1703
+ async set(key, value, context, config) {
1704
+ this.logAccess(key, "write", context, true);
1705
+ if (config?.validationMethod && config.validationMethod !== "none") {
1706
+ const validation = await this.validate(
1707
+ key,
1708
+ value,
1709
+ config.validationMethod
1710
+ );
1711
+ if (!validation.isValid) {
1712
+ this.logAccess(
1713
+ key,
1714
+ "write",
1715
+ context,
1716
+ false,
1717
+ `Validation failed: ${validation.error}`
1718
+ );
1719
+ throw new SecretsError(
1720
+ `Validation failed for ${key}: ${validation.error}`,
1721
+ "VALIDATION_FAILED",
1722
+ { key, error: validation.error }
1723
+ );
1724
+ }
1725
+ }
1726
+ const previousValue = await this.storage.get(key, context);
1727
+ const success = await this.storage.set(key, value, context, config);
1728
+ if (success) {
1729
+ if (context.level === "global") {
1730
+ await this.globalStorage.syncToEnv(key);
1731
+ }
1732
+ await this.emitChangeEvent({
1733
+ type: previousValue === null ? "created" : "updated",
1734
+ key,
1735
+ value,
1736
+ previousValue: previousValue ?? void 0,
1737
+ context,
1738
+ timestamp: Date.now()
1739
+ });
1740
+ } else {
1741
+ this.logAccess(key, "write", context, false, "Storage operation failed");
1742
+ }
1743
+ return success;
1744
+ }
1745
+ /**
1746
+ * Delete a secret
1747
+ */
1748
+ async delete(key, context) {
1749
+ this.logAccess(key, "delete", context, true);
1750
+ const previousValue = await this.storage.get(key, context);
1751
+ const success = await this.storage.delete(key, context);
1752
+ if (success) {
1753
+ if (context.level === "global") {
1754
+ delete process.env[key];
1755
+ }
1756
+ await this.emitChangeEvent({
1757
+ type: "deleted",
1758
+ key,
1759
+ value: null,
1760
+ previousValue: previousValue ?? void 0,
1761
+ context,
1762
+ timestamp: Date.now()
1763
+ });
1764
+ } else {
1765
+ this.logAccess(key, "delete", context, false, "Secret not found");
1766
+ }
1767
+ return success;
1768
+ }
1769
+ /**
1770
+ * Check if a secret exists
1771
+ */
1772
+ async exists(key, context) {
1773
+ return this.storage.exists(key, context);
1774
+ }
1775
+ /**
1776
+ * List secrets (metadata only, no values)
1777
+ */
1778
+ async list(context) {
1779
+ return this.storage.list(context);
1780
+ }
1781
+ /**
1782
+ * Get secret configuration
1783
+ */
1784
+ async getConfig(key, context) {
1785
+ return this.storage.getConfig(key, context);
1786
+ }
1787
+ /**
1788
+ * Update secret configuration
1789
+ */
1790
+ async updateConfig(key, context, config) {
1791
+ return this.storage.updateConfig(key, context, config);
1792
+ }
1793
+ // ============================================================================
1794
+ // Convenience Methods
1795
+ // ============================================================================
1796
+ /**
1797
+ * Get a global secret (agent-level)
1798
+ */
1799
+ async getGlobal(key) {
1800
+ return this.get(key, { level: "global", agentId: this.runtime.agentId });
1801
+ }
1802
+ /**
1803
+ * Set a global secret (agent-level)
1804
+ */
1805
+ async setGlobal(key, value, config) {
1806
+ return this.set(
1807
+ key,
1808
+ value,
1809
+ { level: "global", agentId: this.runtime.agentId },
1810
+ config
1811
+ );
1812
+ }
1813
+ /**
1814
+ * Get a world secret
1815
+ */
1816
+ async getWorld(key, worldId) {
1817
+ return this.get(key, {
1818
+ level: "world",
1819
+ worldId,
1820
+ agentId: this.runtime.agentId
1821
+ });
1822
+ }
1823
+ /**
1824
+ * Set a world secret
1825
+ */
1826
+ async setWorld(key, value, worldId, config) {
1827
+ return this.set(
1828
+ key,
1829
+ value,
1830
+ { level: "world", worldId, agentId: this.runtime.agentId },
1831
+ config
1832
+ );
1833
+ }
1834
+ /**
1835
+ * Get a user secret
1836
+ */
1837
+ async getUser(key, userId) {
1838
+ return this.get(key, {
1839
+ level: "user",
1840
+ userId,
1841
+ agentId: this.runtime.agentId,
1842
+ requesterId: userId
1843
+ });
1844
+ }
1845
+ /**
1846
+ * Set a user secret
1847
+ */
1848
+ async setUser(key, value, userId, config) {
1849
+ return this.set(
1850
+ key,
1851
+ value,
1852
+ {
1853
+ level: "user",
1854
+ userId,
1855
+ agentId: this.runtime.agentId,
1856
+ requesterId: userId
1857
+ },
1858
+ config
1859
+ );
1860
+ }
1861
+ // ============================================================================
1862
+ // Validation
1863
+ // ============================================================================
1864
+ /**
1865
+ * Validate a secret value
1866
+ */
1867
+ async validate(key, value, strategy) {
1868
+ return validateSecret(key, value, strategy);
1869
+ }
1870
+ /**
1871
+ * Get available validation strategies
1872
+ */
1873
+ getValidationStrategies() {
1874
+ return Object.keys(ValidationStrategies);
1875
+ }
1876
+ // ============================================================================
1877
+ // Plugin Requirements
1878
+ // ============================================================================
1879
+ /**
1880
+ * Check which secrets are missing for a plugin
1881
+ */
1882
+ async checkPluginRequirements(pluginId, requirements) {
1883
+ const missingRequired = [];
1884
+ const missingOptional = [];
1885
+ const invalid = [];
1886
+ for (const [key, requirement] of Object.entries(requirements)) {
1887
+ const value = await this.getGlobal(key);
1888
+ if (value === null) {
1889
+ if (requirement.required) {
1890
+ missingRequired.push(key);
1891
+ } else {
1892
+ missingOptional.push(key);
1893
+ }
1894
+ continue;
1895
+ }
1896
+ if (requirement.validationMethod && requirement.validationMethod !== "none") {
1897
+ const validation = await this.validate(
1898
+ key,
1899
+ value,
1900
+ requirement.validationMethod
1901
+ );
1902
+ if (!validation.isValid) {
1903
+ invalid.push(key);
1904
+ }
1905
+ }
1906
+ }
1907
+ return {
1908
+ ready: missingRequired.length === 0 && invalid.length === 0,
1909
+ missingRequired,
1910
+ missingOptional,
1911
+ invalid
1912
+ };
1913
+ }
1914
+ /**
1915
+ * Get missing secrets for a set of keys
1916
+ */
1917
+ async getMissingSecrets(keys, level = "global") {
1918
+ const missing = [];
1919
+ for (const key of keys) {
1920
+ let exists;
1921
+ switch (level) {
1922
+ case "global":
1923
+ exists = await this.exists(key, {
1924
+ level: "global",
1925
+ agentId: this.runtime.agentId
1926
+ });
1927
+ break;
1928
+ case "world":
1929
+ case "user":
1930
+ exists = false;
1931
+ break;
1932
+ default:
1933
+ exists = false;
1934
+ }
1935
+ if (!exists) {
1936
+ missing.push(key);
1937
+ }
1938
+ }
1939
+ return missing;
1940
+ }
1941
+ // ============================================================================
1942
+ // Change Notifications
1943
+ // ============================================================================
1944
+ /**
1945
+ * Register a callback for changes to a specific secret
1946
+ */
1947
+ onSecretChanged(key, callback) {
1948
+ const callbacks = this.changeCallbacks.get(key) ?? [];
1949
+ callbacks.push(callback);
1950
+ this.changeCallbacks.set(key, callbacks);
1951
+ return () => {
1952
+ const cbs = this.changeCallbacks.get(key);
1953
+ if (cbs) {
1954
+ const index = cbs.indexOf(callback);
1955
+ if (index !== -1) {
1956
+ cbs.splice(index, 1);
1957
+ }
1958
+ }
1959
+ };
1960
+ }
1961
+ /**
1962
+ * Register a callback for all secret changes
1963
+ */
1964
+ onAnySecretChanged(callback) {
1965
+ this.globalChangeCallbacks.push(callback);
1966
+ return () => {
1967
+ const index = this.globalChangeCallbacks.indexOf(callback);
1968
+ if (index !== -1) {
1969
+ this.globalChangeCallbacks.splice(index, 1);
1970
+ }
1971
+ };
1972
+ }
1973
+ /**
1974
+ * Emit a change event to registered callbacks
1975
+ */
1976
+ async emitChangeEvent(event) {
1977
+ const keyCallbacks = this.changeCallbacks.get(event.key) ?? [];
1978
+ for (const callback of keyCallbacks) {
1979
+ await callback(event.key, event.value, event.context);
1980
+ }
1981
+ for (const callback of this.globalChangeCallbacks) {
1982
+ await callback(event.key, event.value, event.context);
1983
+ }
1984
+ logger5.debug(
1985
+ `[SecretsService] Emitted ${event.type} event for ${event.key}`
1986
+ );
1987
+ }
1988
+ // ============================================================================
1989
+ // Access Logging
1990
+ // ============================================================================
1991
+ /**
1992
+ * Log a secret access attempt
1993
+ */
1994
+ logAccess(key, action, context, success, error) {
1995
+ if (!this.secretsConfig.enableAccessLogging) {
1996
+ return;
1997
+ }
1998
+ const log = {
1999
+ secretKey: key,
2000
+ accessedBy: context.requesterId ?? context.userId ?? context.agentId,
2001
+ action,
2002
+ timestamp: Date.now(),
2003
+ context,
2004
+ success,
2005
+ error
2006
+ };
2007
+ this.accessLogs.push(log);
2008
+ if (this.accessLogs.length > this.secretsConfig.maxAccessLogEntries) {
2009
+ this.accessLogs = this.accessLogs.slice(
2010
+ -this.secretsConfig.maxAccessLogEntries
2011
+ );
2012
+ }
2013
+ if (!success && error) {
2014
+ logger5.debug(
2015
+ `[SecretsService] Access denied: ${action} ${key} - ${error}`
2016
+ );
2017
+ }
2018
+ }
2019
+ /**
2020
+ * Get access logs
2021
+ */
2022
+ getAccessLogs(filter) {
2023
+ let logs = [...this.accessLogs];
2024
+ if (filter?.key) {
2025
+ logs = logs.filter((l) => l.secretKey === filter.key);
2026
+ }
2027
+ if (filter?.action) {
2028
+ logs = logs.filter((l) => l.action === filter.action);
2029
+ }
2030
+ if (filter?.since) {
2031
+ logs = logs.filter((l) => l.timestamp >= filter.since);
2032
+ }
2033
+ if (filter?.context) {
2034
+ logs = logs.filter((l) => {
2035
+ if (filter.context.level && l.context.level !== filter.context.level)
2036
+ return false;
2037
+ if (filter.context.worldId && l.context.worldId !== filter.context.worldId)
2038
+ return false;
2039
+ if (filter.context.userId && l.context.userId !== filter.context.userId)
2040
+ return false;
2041
+ return true;
2042
+ });
2043
+ }
2044
+ return logs;
2045
+ }
2046
+ /**
2047
+ * Clear access logs
2048
+ */
2049
+ clearAccessLogs() {
2050
+ this.accessLogs = [];
2051
+ }
2052
+ // ============================================================================
2053
+ // Storage Access
2054
+ // ============================================================================
2055
+ /**
2056
+ * Get the global storage backend
2057
+ */
2058
+ getGlobalStorage() {
2059
+ return this.globalStorage;
2060
+ }
2061
+ /**
2062
+ * Get the world storage backend
2063
+ */
2064
+ getWorldStorage() {
2065
+ return this.worldStorage;
2066
+ }
2067
+ /**
2068
+ * Get the user storage backend
2069
+ */
2070
+ getUserStorage() {
2071
+ return this.userStorage;
2072
+ }
2073
+ /**
2074
+ * Get the key manager (for advanced use cases)
2075
+ */
2076
+ getKeyManager() {
2077
+ return this.keyManager;
2078
+ }
2079
+ };
2080
+
2081
+ // src/services/plugin-activator.ts
2082
+ import {
2083
+ Service as Service2,
2084
+ logger as logger6
2085
+ } from "@elizaos/core";
2086
+ var PLUGIN_ACTIVATOR_SERVICE_TYPE = "PLUGIN_ACTIVATOR";
2087
+ var DEFAULT_CONFIG2 = {
2088
+ enableAutoActivation: true,
2089
+ pollingIntervalMs: 5e3,
2090
+ maxWaitMs: 0
2091
+ // 0 = wait forever
2092
+ };
2093
+ var PluginActivatorService = class _PluginActivatorService extends Service2 {
2094
+ static serviceType = PLUGIN_ACTIVATOR_SERVICE_TYPE;
2095
+ capabilityDescription = "Activate plugins dynamically when their required secrets become available";
2096
+ activatorConfig;
2097
+ secretsService = null;
2098
+ pendingPlugins = /* @__PURE__ */ new Map();
2099
+ activatedPlugins = /* @__PURE__ */ new Set();
2100
+ pluginSecretMapping = /* @__PURE__ */ new Map();
2101
+ pollingInterval = null;
2102
+ unsubscribeSecretChanges = null;
2103
+ constructor(runtime, config) {
2104
+ super(runtime);
2105
+ this.activatorConfig = { ...DEFAULT_CONFIG2, ...config };
2106
+ }
2107
+ /**
2108
+ * Start the service
2109
+ */
2110
+ static async start(runtime, config) {
2111
+ const service = new _PluginActivatorService(runtime, config);
2112
+ await service.initialize();
2113
+ return service;
2114
+ }
2115
+ /**
2116
+ * Initialize the service
2117
+ */
2118
+ async initialize() {
2119
+ logger6.info("[PluginActivator] Initializing");
2120
+ this.secretsService = this.runtime.getService(SECRETS_SERVICE_TYPE);
2121
+ if (!this.secretsService) {
2122
+ logger6.warn(
2123
+ "[PluginActivator] SecretsService not available, activation will be limited"
2124
+ );
2125
+ } else {
2126
+ this.unsubscribeSecretChanges = this.secretsService.onAnySecretChanged(
2127
+ async (key, value, context) => {
2128
+ await this.onSecretChanged(key, value, context);
2129
+ }
2130
+ );
2131
+ }
2132
+ if (this.activatorConfig.enableAutoActivation && this.activatorConfig.pollingIntervalMs > 0) {
2133
+ this.startPolling();
2134
+ }
2135
+ logger6.info("[PluginActivator] Initialized");
2136
+ }
2137
+ /**
2138
+ * Stop the service
2139
+ */
2140
+ async stop() {
2141
+ logger6.info("[PluginActivator] Stopping");
2142
+ if (this.pollingInterval) {
2143
+ clearInterval(this.pollingInterval);
2144
+ this.pollingInterval = null;
2145
+ }
2146
+ if (this.unsubscribeSecretChanges) {
2147
+ this.unsubscribeSecretChanges();
2148
+ this.unsubscribeSecretChanges = null;
2149
+ }
2150
+ this.pendingPlugins.clear();
2151
+ this.activatedPlugins.clear();
2152
+ this.pluginSecretMapping.clear();
2153
+ logger6.info("[PluginActivator] Stopped");
2154
+ }
2155
+ // ============================================================================
2156
+ // Plugin Registration
2157
+ // ============================================================================
2158
+ /**
2159
+ * Register a plugin for activation when secrets are ready
2160
+ */
2161
+ async registerPlugin(plugin, activationCallback) {
2162
+ const pluginId = plugin.name;
2163
+ if (this.activatedPlugins.has(pluginId)) {
2164
+ logger6.debug(`[PluginActivator] Plugin ${pluginId} already activated`);
2165
+ return true;
2166
+ }
2167
+ if (!plugin.requiredSecrets || Object.keys(plugin.requiredSecrets).length === 0) {
2168
+ logger6.info(
2169
+ `[PluginActivator] Plugin ${pluginId} has no secret requirements, activating`
2170
+ );
2171
+ return this.activatePlugin(pluginId, plugin, activationCallback);
2172
+ }
2173
+ const status = await this.checkPluginRequirements(plugin);
2174
+ if (status.ready) {
2175
+ logger6.info(
2176
+ `[PluginActivator] Plugin ${pluginId} has all required secrets, activating`
2177
+ );
2178
+ return this.activatePlugin(pluginId, plugin, activationCallback);
2179
+ }
2180
+ logger6.info(
2181
+ `[PluginActivator] Plugin ${pluginId} queued, waiting for: ${status.missingRequired.join(", ")}`
2182
+ );
2183
+ const requiredSecrets = Object.entries(plugin.requiredSecrets).filter(([_, req]) => req.required).map(([key]) => key);
2184
+ this.pendingPlugins.set(pluginId, {
2185
+ pluginId,
2186
+ requiredSecrets,
2187
+ callback: async () => {
2188
+ await this.activatePlugin(pluginId, plugin, activationCallback);
2189
+ },
2190
+ registeredAt: Date.now()
2191
+ });
2192
+ for (const secretKey of requiredSecrets) {
2193
+ const plugins = this.pluginSecretMapping.get(secretKey) ?? /* @__PURE__ */ new Set();
2194
+ plugins.add(pluginId);
2195
+ this.pluginSecretMapping.set(secretKey, plugins);
2196
+ }
2197
+ return false;
2198
+ }
2199
+ /**
2200
+ * Unregister a pending plugin
2201
+ */
2202
+ unregisterPlugin(pluginId) {
2203
+ const pending = this.pendingPlugins.get(pluginId);
2204
+ if (!pending) {
2205
+ return false;
2206
+ }
2207
+ for (const secretKey of pending.requiredSecrets) {
2208
+ const plugins = this.pluginSecretMapping.get(secretKey);
2209
+ if (plugins) {
2210
+ plugins.delete(pluginId);
2211
+ if (plugins.size === 0) {
2212
+ this.pluginSecretMapping.delete(secretKey);
2213
+ }
2214
+ }
2215
+ }
2216
+ this.pendingPlugins.delete(pluginId);
2217
+ logger6.info(`[PluginActivator] Unregistered plugin ${pluginId}`);
2218
+ return true;
2219
+ }
2220
+ // ============================================================================
2221
+ // Plugin Activation
2222
+ // ============================================================================
2223
+ /**
2224
+ * Activate a plugin
2225
+ */
2226
+ async activatePlugin(pluginId, plugin, callback) {
2227
+ try {
2228
+ if (callback) {
2229
+ await callback();
2230
+ }
2231
+ if (plugin.onSecretsReady) {
2232
+ await plugin.onSecretsReady(this.runtime);
2233
+ }
2234
+ this.activatedPlugins.add(pluginId);
2235
+ this.pendingPlugins.delete(pluginId);
2236
+ logger6.info(`[PluginActivator] Activated plugin ${pluginId}`);
2237
+ return true;
2238
+ } catch (error) {
2239
+ const errorMessage = error instanceof Error ? error.message : String(error);
2240
+ logger6.error(
2241
+ `[PluginActivator] Failed to activate plugin ${pluginId}: ${errorMessage}`
2242
+ );
2243
+ return false;
2244
+ }
2245
+ }
2246
+ /**
2247
+ * Check requirements for a plugin
2248
+ */
2249
+ async checkPluginRequirements(plugin) {
2250
+ if (!plugin.requiredSecrets) {
2251
+ return {
2252
+ pluginId: plugin.name,
2253
+ ready: true,
2254
+ missingRequired: [],
2255
+ missingOptional: [],
2256
+ invalid: [],
2257
+ message: "No secrets required"
2258
+ };
2259
+ }
2260
+ if (!this.secretsService) {
2261
+ const required = Object.entries(plugin.requiredSecrets).filter(([_, req]) => req.required).map(([key]) => key);
2262
+ return {
2263
+ pluginId: plugin.name,
2264
+ ready: required.length === 0,
2265
+ missingRequired: required,
2266
+ missingOptional: [],
2267
+ invalid: [],
2268
+ message: "SecretsService not available"
2269
+ };
2270
+ }
2271
+ const result = await this.secretsService.checkPluginRequirements(
2272
+ plugin.name,
2273
+ plugin.requiredSecrets
2274
+ );
2275
+ return {
2276
+ pluginId: plugin.name,
2277
+ ...result,
2278
+ message: result.ready ? "All secrets available" : `Missing: ${result.missingRequired.join(", ")}`
2279
+ };
2280
+ }
2281
+ /**
2282
+ * Get status of all registered plugins
2283
+ */
2284
+ getPluginStatuses() {
2285
+ const statuses = /* @__PURE__ */ new Map();
2286
+ for (const [pluginId, pending] of this.pendingPlugins) {
2287
+ statuses.set(pluginId, {
2288
+ pending: true,
2289
+ activated: false,
2290
+ missingSecrets: pending.requiredSecrets
2291
+ });
2292
+ }
2293
+ for (const pluginId of this.activatedPlugins) {
2294
+ statuses.set(pluginId, {
2295
+ pending: false,
2296
+ activated: true,
2297
+ missingSecrets: []
2298
+ });
2299
+ }
2300
+ return statuses;
2301
+ }
2302
+ // ============================================================================
2303
+ // Secret Change Handling
2304
+ // ============================================================================
2305
+ /**
2306
+ * Handle secret change event
2307
+ */
2308
+ async onSecretChanged(key, value, context) {
2309
+ if (context.level !== "global") {
2310
+ return;
2311
+ }
2312
+ const affectedPlugins = this.pluginSecretMapping.get(key);
2313
+ if (!affectedPlugins || affectedPlugins.size === 0) {
2314
+ return;
2315
+ }
2316
+ logger6.debug(
2317
+ `[PluginActivator] Secret ${key} changed, checking ${affectedPlugins.size} plugins`
2318
+ );
2319
+ for (const pluginId of affectedPlugins) {
2320
+ const pending = this.pendingPlugins.get(pluginId);
2321
+ if (!pending) {
2322
+ continue;
2323
+ }
2324
+ const missing = await this.getMissingSecrets(pending.requiredSecrets);
2325
+ if (missing.length === 0) {
2326
+ logger6.info(
2327
+ `[PluginActivator] All secrets available for ${pluginId}, activating`
2328
+ );
2329
+ await pending.callback();
2330
+ }
2331
+ }
2332
+ }
2333
+ /**
2334
+ * Get missing secrets from a list
2335
+ */
2336
+ async getMissingSecrets(keys) {
2337
+ if (!this.secretsService) {
2338
+ return keys;
2339
+ }
2340
+ return this.secretsService.getMissingSecrets(keys, "global");
2341
+ }
2342
+ // ============================================================================
2343
+ // Polling
2344
+ // ============================================================================
2345
+ /**
2346
+ * Start polling for pending plugins
2347
+ */
2348
+ startPolling() {
2349
+ if (this.pollingInterval) {
2350
+ return;
2351
+ }
2352
+ this.pollingInterval = setInterval(async () => {
2353
+ await this.checkPendingPlugins();
2354
+ }, this.activatorConfig.pollingIntervalMs);
2355
+ logger6.debug(
2356
+ `[PluginActivator] Started polling every ${this.activatorConfig.pollingIntervalMs}ms`
2357
+ );
2358
+ }
2359
+ /**
2360
+ * Stop polling
2361
+ */
2362
+ stopPolling() {
2363
+ if (this.pollingInterval) {
2364
+ clearInterval(this.pollingInterval);
2365
+ this.pollingInterval = null;
2366
+ }
2367
+ }
2368
+ /**
2369
+ * Check all pending plugins
2370
+ */
2371
+ async checkPendingPlugins() {
2372
+ if (this.pendingPlugins.size === 0) {
2373
+ return;
2374
+ }
2375
+ const now = Date.now();
2376
+ for (const [pluginId, pending] of this.pendingPlugins) {
2377
+ if (this.activatorConfig.maxWaitMs > 0) {
2378
+ const elapsed = now - pending.registeredAt;
2379
+ if (elapsed > this.activatorConfig.maxWaitMs) {
2380
+ logger6.warn(
2381
+ `[PluginActivator] Plugin ${pluginId} timed out waiting for secrets`
2382
+ );
2383
+ this.unregisterPlugin(pluginId);
2384
+ continue;
2385
+ }
2386
+ }
2387
+ const missing = await this.getMissingSecrets(pending.requiredSecrets);
2388
+ if (missing.length === 0) {
2389
+ logger6.info(`[PluginActivator] Secrets now available for ${pluginId}`);
2390
+ await pending.callback();
2391
+ }
2392
+ }
2393
+ }
2394
+ // ============================================================================
2395
+ // Utility Methods
2396
+ // ============================================================================
2397
+ /**
2398
+ * Get list of pending plugins
2399
+ */
2400
+ getPendingPlugins() {
2401
+ return Array.from(this.pendingPlugins.keys());
2402
+ }
2403
+ /**
2404
+ * Get list of activated plugins
2405
+ */
2406
+ getActivatedPlugins() {
2407
+ return Array.from(this.activatedPlugins);
2408
+ }
2409
+ /**
2410
+ * Check if a plugin is pending
2411
+ */
2412
+ isPending(pluginId) {
2413
+ return this.pendingPlugins.has(pluginId);
2414
+ }
2415
+ /**
2416
+ * Check if a plugin is activated
2417
+ */
2418
+ isActivated(pluginId) {
2419
+ return this.activatedPlugins.has(pluginId);
2420
+ }
2421
+ /**
2422
+ * Get secrets required by pending plugins
2423
+ */
2424
+ getRequiredSecrets() {
2425
+ const secrets = /* @__PURE__ */ new Set();
2426
+ for (const pending of this.pendingPlugins.values()) {
2427
+ for (const key of pending.requiredSecrets) {
2428
+ secrets.add(key);
2429
+ }
2430
+ }
2431
+ return secrets;
2432
+ }
2433
+ /**
2434
+ * Get plugins waiting for a specific secret
2435
+ */
2436
+ getPluginsWaitingFor(secretKey) {
2437
+ const plugins = this.pluginSecretMapping.get(secretKey);
2438
+ return plugins ? Array.from(plugins) : [];
2439
+ }
2440
+ };
2441
+
2442
+ // src/actions/set-secret.ts
2443
+ import {
2444
+ ModelType,
2445
+ logger as logger7,
2446
+ composePromptFromState
2447
+ } from "@elizaos/core";
2448
+ var extractSecretsTemplate = `You are extracting secret/configuration values from the user's message.
2449
+
2450
+ The user wants to set one or more secrets. Extract:
2451
+ 1. The secret key (should be UPPERCASE_WITH_UNDERSCORES format)
2452
+ 2. The secret value
2453
+ 3. Optional description
2454
+ 4. Secret type (api_key, secret, credential, url, or config)
2455
+
2456
+ Common patterns:
2457
+ - "Set my OpenAI key to sk-..." -> key: OPENAI_API_KEY, value: sk-...
2458
+ - "My Anthropic API key is sk-ant-..." -> key: ANTHROPIC_API_KEY, value: sk-ant-...
2459
+ - "Use this Discord token: ..." -> key: DISCORD_BOT_TOKEN, value: ...
2460
+ - "Set DATABASE_URL to postgres://..." -> key: DATABASE_URL, value: postgres://...
2461
+
2462
+ {{recentMessages}}
2463
+
2464
+ Extract the secrets from the user's message. If the key name isn't explicitly specified, infer an appropriate UPPERCASE_WITH_UNDERSCORES name based on the context.`;
2465
+ var setSecretAction = {
2466
+ name: "SET_SECRET",
2467
+ similes: [
2468
+ "STORE_SECRET",
2469
+ "SAVE_SECRET",
2470
+ "SET_API_KEY",
2471
+ "CONFIGURE_SECRET",
2472
+ "SET_ENV_VAR",
2473
+ "STORE_API_KEY",
2474
+ "SET_TOKEN",
2475
+ "SAVE_KEY"
2476
+ ],
2477
+ description: "Set a secret value (API key, token, password, etc.) for the agent to use",
2478
+ validate: async (runtime, message, state) => {
2479
+ const text = message.content.text?.toLowerCase() ?? "";
2480
+ const setPatterns = [
2481
+ /\bset\b.*\b(key|token|secret|password|credential|api)/i,
2482
+ /\bmy\b.*\b(key|token|secret|api)\b.*\bis\b/i,
2483
+ /\buse\b.*\b(key|token|this)\b/i,
2484
+ /\bstore\b.*\b(key|token|secret)/i,
2485
+ /\bconfigure\b.*\b(key|token|secret)/i,
2486
+ /\bsave\b.*\b(key|token|secret)/i,
2487
+ /sk-[a-zA-Z0-9]+/i,
2488
+ // OpenAI key pattern
2489
+ /sk-ant-[a-zA-Z0-9]+/i,
2490
+ // Anthropic key pattern
2491
+ /gsk_[a-zA-Z0-9]+/i
2492
+ // Groq key pattern
2493
+ ];
2494
+ const hasIntent = setPatterns.some((pattern) => pattern.test(text));
2495
+ if (!hasIntent) {
2496
+ return false;
2497
+ }
2498
+ const secretsService = runtime.getService(SECRETS_SERVICE_TYPE);
2499
+ return secretsService !== null;
2500
+ },
2501
+ handler: async (runtime, message, state, _options, callback) => {
2502
+ logger7.info("[SetSecret] Processing secret set request");
2503
+ const secretsService = runtime.getService(SECRETS_SERVICE_TYPE);
2504
+ if (!secretsService) {
2505
+ if (callback) {
2506
+ await callback({
2507
+ text: "Secret management is not available. Please ensure the secrets plugin is properly configured.",
2508
+ action: "SET_SECRET"
2509
+ });
2510
+ }
2511
+ return { success: false, text: "Secrets service not available" };
2512
+ }
2513
+ const currentState = state ?? await runtime.composeState(message);
2514
+ let extracted;
2515
+ try {
2516
+ const prompt = composePromptFromState({
2517
+ state: currentState,
2518
+ template: extractSecretsTemplate
2519
+ });
2520
+ const result = await runtime.useModel(ModelType.OBJECT_SMALL, {
2521
+ prompt
2522
+ });
2523
+ const secretsArray = Array.isArray(result.secrets) ? result.secrets : [];
2524
+ extracted = {
2525
+ secrets: secretsArray.filter(
2526
+ (s) => s !== null && typeof s === "object"
2527
+ ).map((s) => ({
2528
+ key: String(s.key || ""),
2529
+ value: String(s.value || ""),
2530
+ description: s.description ? String(s.description) : void 0,
2531
+ type: s.type
2532
+ })).filter((s) => s.key && s.value),
2533
+ level: result.level
2534
+ };
2535
+ } catch (error) {
2536
+ const errorMessage = error instanceof Error ? error.message : String(error);
2537
+ logger7.error(`[SetSecret] Failed to extract secrets: ${errorMessage}`);
2538
+ if (callback) {
2539
+ await callback({
2540
+ text: 'I had trouble understanding the secret you wanted to set. Could you please provide it in a clearer format? For example: "Set my OPENAI_API_KEY to sk-..."',
2541
+ action: "SET_SECRET"
2542
+ });
2543
+ }
2544
+ return { success: false, text: "Failed to extract secrets from message" };
2545
+ }
2546
+ if (!extracted.secrets || extracted.secrets.length === 0) {
2547
+ if (callback) {
2548
+ await callback({
2549
+ text: `I couldn't find any secrets to set in your message. Please provide a key and value, like: "Set my OPENAI_API_KEY to sk-..."`,
2550
+ action: "SET_SECRET"
2551
+ });
2552
+ }
2553
+ return { success: false, text: "No secrets found in message" };
2554
+ }
2555
+ const level = extracted.level ?? "global";
2556
+ const context = {
2557
+ level,
2558
+ agentId: runtime.agentId,
2559
+ worldId: level === "world" ? message.roomId : void 0,
2560
+ userId: level === "user" ? message.entityId : void 0,
2561
+ requesterId: message.entityId
2562
+ };
2563
+ const results = [];
2564
+ for (const secret of extracted.secrets) {
2565
+ const key = secret.key.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
2566
+ const validationMethod = inferValidationStrategy(key);
2567
+ try {
2568
+ const success = await secretsService.set(key, secret.value, context, {
2569
+ type: secret.type ?? "secret",
2570
+ description: secret.description ?? `Secret set via conversation`,
2571
+ validationMethod,
2572
+ encrypted: true
2573
+ });
2574
+ results.push({ key, success });
2575
+ if (success) {
2576
+ logger7.info(`[SetSecret] Successfully set secret: ${key}`);
2577
+ }
2578
+ } catch (error) {
2579
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2580
+ results.push({ key, success: false, error: errorMessage });
2581
+ logger7.error(
2582
+ `[SetSecret] Failed to set secret ${key}: ${errorMessage}`
2583
+ );
2584
+ }
2585
+ }
2586
+ const successful = results.filter((r) => r.success);
2587
+ const failed = results.filter((r) => !r.success);
2588
+ let responseText;
2589
+ if (successful.length > 0 && failed.length === 0) {
2590
+ const keys = successful.map((r) => r.key).join(", ");
2591
+ responseText = successful.length === 1 ? `I've securely stored your ${keys}. It's now available for use.` : `I've securely stored ${successful.length} secrets: ${keys}. They're now available for use.`;
2592
+ } else if (successful.length === 0 && failed.length > 0) {
2593
+ const errors = failed.map((r) => `${r.key}: ${r.error}`).join("; ");
2594
+ responseText = `I wasn't able to store the secret(s). ${errors}`;
2595
+ } else {
2596
+ const successKeys = successful.map((r) => r.key).join(", ");
2597
+ const failedKeys = failed.map((r) => r.key).join(", ");
2598
+ responseText = `I stored ${successful.length} secret(s) (${successKeys}), but ${failed.length} failed (${failedKeys}).`;
2599
+ }
2600
+ if (callback) {
2601
+ await callback({
2602
+ text: responseText,
2603
+ action: "SET_SECRET"
2604
+ });
2605
+ }
2606
+ return { success: successful.length > 0, text: responseText };
2607
+ },
2608
+ examples: [
2609
+ [
2610
+ {
2611
+ name: "{{user1}}",
2612
+ content: { text: "Set my OpenAI API key to sk-abc123xyz789" }
2613
+ },
2614
+ {
2615
+ name: "{{agent}}",
2616
+ content: {
2617
+ text: "I've securely stored your OPENAI_API_KEY. It's now available for use.",
2618
+ action: "SET_SECRET"
2619
+ }
2620
+ }
2621
+ ],
2622
+ [
2623
+ {
2624
+ name: "{{user1}}",
2625
+ content: { text: "My Anthropic key is sk-ant-secret123" }
2626
+ },
2627
+ {
2628
+ name: "{{agent}}",
2629
+ content: {
2630
+ text: "I've securely stored your ANTHROPIC_API_KEY. It's now available for use.",
2631
+ action: "SET_SECRET"
2632
+ }
2633
+ }
2634
+ ],
2635
+ [
2636
+ {
2637
+ name: "{{user1}}",
2638
+ content: { text: "Use this Discord bot token: MTIz.abc.xyz" }
2639
+ },
2640
+ {
2641
+ name: "{{agent}}",
2642
+ content: {
2643
+ text: "I've securely stored your DISCORD_BOT_TOKEN. It's now available for use.",
2644
+ action: "SET_SECRET"
2645
+ }
2646
+ }
2647
+ ]
2648
+ ]
2649
+ };
2650
+
2651
+ // src/actions/manage-secret.ts
2652
+ import {
2653
+ ModelType as ModelType2,
2654
+ logger as logger8,
2655
+ composePromptFromState as composePromptFromState2
2656
+ } from "@elizaos/core";
2657
+ var extractOperationTemplate = `You are helping manage secrets for an AI agent.
2658
+
2659
+ Determine what operation the user wants to perform:
2660
+ - get: Retrieve a secret value
2661
+ - set: Store a new secret
2662
+ - delete: Remove a secret
2663
+ - list: Show all available secrets (without values)
2664
+ - check: Check if a secret exists
2665
+
2666
+ Common patterns:
2667
+ - "What is my OpenAI key?" -> operation: get, key: OPENAI_API_KEY
2668
+ - "Do I have a Discord token set?" -> operation: check, key: DISCORD_BOT_TOKEN
2669
+ - "Show me my secrets" -> operation: list
2670
+ - "Delete my old API key" -> operation: delete
2671
+ - "Remove TWITTER_API_KEY" -> operation: delete, key: TWITTER_API_KEY
2672
+ - "Set my key to sk-..." -> operation: set, key: <infer>, value: sk-...
2673
+
2674
+ {{recentMessages}}
2675
+
2676
+ Extract the operation, key (if applicable), value (if applicable), and level from the user's message.`;
2677
+ var manageSecretAction = {
2678
+ name: "MANAGE_SECRET",
2679
+ similes: [
2680
+ "SECRET_MANAGEMENT",
2681
+ "HANDLE_SECRET",
2682
+ "SECRET_OPERATION",
2683
+ "GET_SECRET",
2684
+ "DELETE_SECRET",
2685
+ "LIST_SECRETS",
2686
+ "CHECK_SECRET"
2687
+ ],
2688
+ description: "Manage secrets - get, set, delete, or list secrets at various levels",
2689
+ validate: async (runtime, message, state) => {
2690
+ const text = message.content.text?.toLowerCase() ?? "";
2691
+ const patterns = [
2692
+ /\b(get|show|what|retrieve)\b.*\b(secret|key|token|credential)/i,
2693
+ /\b(delete|remove|clear)\b.*\b(secret|key|token|credential)/i,
2694
+ /\b(list|show)\b.*\b(secrets|keys|tokens|credentials)/i,
2695
+ /\bdo i have\b.*\b(secret|key|token)/i,
2696
+ /\b(check|is)\b.*\b(secret|key|token)\b.*\b(set|configured)/i,
2697
+ /\bmy secrets\b/i,
2698
+ /\bwhat secrets\b/i
2699
+ ];
2700
+ const hasIntent = patterns.some((pattern) => pattern.test(text));
2701
+ if (!hasIntent) {
2702
+ return false;
2703
+ }
2704
+ const secretsService = runtime.getService(SECRETS_SERVICE_TYPE);
2705
+ return secretsService !== null;
2706
+ },
2707
+ handler: async (runtime, message, state, _options, callback) => {
2708
+ logger8.info("[ManageSecret] Processing secret management request");
2709
+ const secretsService = runtime.getService(SECRETS_SERVICE_TYPE);
2710
+ if (!secretsService) {
2711
+ if (callback) {
2712
+ await callback({
2713
+ text: "Secret management is not available.",
2714
+ action: "MANAGE_SECRET"
2715
+ });
2716
+ }
2717
+ return { success: false, text: "Secrets service not available" };
2718
+ }
2719
+ const currentState = state ?? await runtime.composeState(message);
2720
+ let operation;
2721
+ try {
2722
+ const prompt = composePromptFromState2({
2723
+ state: currentState,
2724
+ template: extractOperationTemplate
2725
+ });
2726
+ const result = await runtime.useModel(ModelType2.OBJECT_SMALL, {
2727
+ prompt
2728
+ });
2729
+ operation = {
2730
+ operation: result.operation || "list",
2731
+ key: result.key ? String(result.key) : void 0,
2732
+ value: result.value ? String(result.value) : void 0,
2733
+ level: result.level,
2734
+ description: result.description ? String(result.description) : void 0,
2735
+ type: result.type
2736
+ };
2737
+ } catch (error) {
2738
+ const errorMessage = error instanceof Error ? error.message : String(error);
2739
+ logger8.error(
2740
+ `[ManageSecret] Failed to extract operation: ${errorMessage}`
2741
+ );
2742
+ if (callback) {
2743
+ await callback({
2744
+ text: "I had trouble understanding what you want to do with secrets. Could you be more specific?",
2745
+ action: "MANAGE_SECRET"
2746
+ });
2747
+ }
2748
+ return {
2749
+ success: false,
2750
+ text: "Failed to extract operation from message"
2751
+ };
2752
+ }
2753
+ const level = operation.level ?? "global";
2754
+ const context = {
2755
+ level,
2756
+ agentId: runtime.agentId,
2757
+ worldId: level === "world" ? message.roomId : void 0,
2758
+ userId: level === "user" ? message.entityId : void 0,
2759
+ requesterId: message.entityId
2760
+ };
2761
+ let responseText;
2762
+ switch (operation.operation) {
2763
+ case "get": {
2764
+ if (!operation.key) {
2765
+ responseText = "Please specify which secret you want to retrieve.";
2766
+ break;
2767
+ }
2768
+ const key = operation.key.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
2769
+ const value = await secretsService.get(key, context);
2770
+ if (value) {
2771
+ const maskedValue = maskSecretValue(value);
2772
+ responseText = `Your ${key} is set to: ${maskedValue}`;
2773
+ } else {
2774
+ responseText = `I don't have a ${key} stored. Would you like to set one?`;
2775
+ }
2776
+ break;
2777
+ }
2778
+ case "set": {
2779
+ if (!operation.key || !operation.value) {
2780
+ responseText = "Please provide both a key and value to set a secret.";
2781
+ break;
2782
+ }
2783
+ const key = operation.key.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
2784
+ try {
2785
+ const success = await secretsService.set(
2786
+ key,
2787
+ operation.value,
2788
+ context,
2789
+ {
2790
+ type: operation.type ?? "secret",
2791
+ description: operation.description ?? "Set via conversation",
2792
+ encrypted: true
2793
+ }
2794
+ );
2795
+ if (success) {
2796
+ responseText = `I've securely stored your ${key}.`;
2797
+ } else {
2798
+ responseText = `Failed to store ${key}. Please try again.`;
2799
+ }
2800
+ } catch (error) {
2801
+ responseText = `Error storing ${key}: ${error instanceof Error ? error.message : "Unknown error"}`;
2802
+ }
2803
+ break;
2804
+ }
2805
+ case "delete": {
2806
+ if (!operation.key) {
2807
+ responseText = "Please specify which secret you want to delete.";
2808
+ break;
2809
+ }
2810
+ const key = operation.key.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
2811
+ const deleted = await secretsService.delete(key, context);
2812
+ if (deleted) {
2813
+ responseText = `I've deleted your ${key}.`;
2814
+ } else {
2815
+ responseText = `I couldn't find a ${key} to delete.`;
2816
+ }
2817
+ break;
2818
+ }
2819
+ case "list": {
2820
+ const metadata = await secretsService.list(context);
2821
+ const keys = Object.keys(metadata);
2822
+ if (keys.length === 0) {
2823
+ responseText = `You don't have any ${level} secrets stored yet.`;
2824
+ } else {
2825
+ const secretList = keys.map((key) => {
2826
+ const config = metadata[key];
2827
+ const status = config.status === "valid" ? "\u2713" : "\u26A0";
2828
+ return `\u2022 ${key} ${status}`;
2829
+ }).join("\n");
2830
+ responseText = `Here are your ${level} secrets:
2831
+ ${secretList}`;
2832
+ }
2833
+ break;
2834
+ }
2835
+ case "check": {
2836
+ if (!operation.key) {
2837
+ responseText = "Please specify which secret you want to check.";
2838
+ break;
2839
+ }
2840
+ const key = operation.key.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
2841
+ const exists = await secretsService.exists(key, context);
2842
+ if (exists) {
2843
+ const config = await secretsService.getConfig(key, context);
2844
+ const status = config?.status === "valid" ? "valid" : config?.status ?? "unknown";
2845
+ responseText = `Yes, ${key} is set and its status is: ${status}.`;
2846
+ } else {
2847
+ responseText = `No, ${key} is not set. Would you like to configure it?`;
2848
+ }
2849
+ break;
2850
+ }
2851
+ default:
2852
+ responseText = "I'm not sure what operation you want to perform. You can get, set, delete, list, or check secrets.";
2853
+ }
2854
+ if (callback) {
2855
+ await callback({
2856
+ text: responseText,
2857
+ action: "MANAGE_SECRET"
2858
+ });
2859
+ }
2860
+ return { success: true, text: responseText };
2861
+ },
2862
+ examples: [
2863
+ [
2864
+ {
2865
+ name: "{{user1}}",
2866
+ content: { text: "What secrets do I have?" }
2867
+ },
2868
+ {
2869
+ name: "{{agent}}",
2870
+ content: {
2871
+ text: "Here are your global secrets:\n\u2022 OPENAI_API_KEY \u2713\n\u2022 ANTHROPIC_API_KEY \u2713",
2872
+ action: "MANAGE_SECRET"
2873
+ }
2874
+ }
2875
+ ],
2876
+ [
2877
+ {
2878
+ name: "{{user1}}",
2879
+ content: { text: "Do I have a Discord token set?" }
2880
+ },
2881
+ {
2882
+ name: "{{agent}}",
2883
+ content: {
2884
+ text: "No, DISCORD_BOT_TOKEN is not set. Would you like to configure it?",
2885
+ action: "MANAGE_SECRET"
2886
+ }
2887
+ }
2888
+ ],
2889
+ [
2890
+ {
2891
+ name: "{{user1}}",
2892
+ content: { text: "Delete my old Twitter API key" }
2893
+ },
2894
+ {
2895
+ name: "{{agent}}",
2896
+ content: {
2897
+ text: "I've deleted your TWITTER_API_KEY.",
2898
+ action: "MANAGE_SECRET"
2899
+ }
2900
+ }
2901
+ ]
2902
+ ]
2903
+ };
2904
+ function maskSecretValue(value) {
2905
+ if (value.length <= 8) {
2906
+ return "****";
2907
+ }
2908
+ const visibleStart = value.slice(0, 4);
2909
+ const visibleEnd = value.slice(-4);
2910
+ const maskedLength = Math.min(value.length - 8, 20);
2911
+ const mask = "*".repeat(maskedLength);
2912
+ return `${visibleStart}${mask}${visibleEnd}`;
2913
+ }
2914
+
2915
+ // src/providers/secrets-status.ts
2916
+ import {
2917
+ logger as logger9
2918
+ } from "@elizaos/core";
2919
+ var secretsStatusProvider = {
2920
+ name: "SECRETS_STATUS",
2921
+ description: "Provides information about configured secrets and their status",
2922
+ get: async (runtime, message, state) => {
2923
+ const secretsService = runtime.getService(SECRETS_SERVICE_TYPE);
2924
+ if (!secretsService) {
2925
+ return { text: "" };
2926
+ }
2927
+ try {
2928
+ const globalSecrets = await secretsService.list({
2929
+ level: "global",
2930
+ agentId: runtime.agentId
2931
+ });
2932
+ const secretKeys = Object.keys(globalSecrets);
2933
+ if (secretKeys.length === 0) {
2934
+ return {
2935
+ text: `[Secrets Status]
2936
+ No secrets are currently configured. The agent may need API keys or other credentials to access certain services.`
2937
+ };
2938
+ }
2939
+ const valid = [];
2940
+ const missing = [];
2941
+ const invalid = [];
2942
+ for (const [key, config] of Object.entries(globalSecrets)) {
2943
+ switch (config.status) {
2944
+ case "valid":
2945
+ valid.push(key);
2946
+ break;
2947
+ case "missing":
2948
+ missing.push(key);
2949
+ break;
2950
+ case "invalid":
2951
+ case "expired":
2952
+ case "revoked":
2953
+ invalid.push(key);
2954
+ break;
2955
+ default:
2956
+ valid.push(key);
2957
+ }
2958
+ }
2959
+ const lines = ["[Secrets Status]"];
2960
+ if (valid.length > 0) {
2961
+ lines.push(`Configured secrets: ${valid.join(", ")}`);
2962
+ }
2963
+ if (invalid.length > 0) {
2964
+ lines.push(`Invalid/expired secrets: ${invalid.join(", ")}`);
2965
+ }
2966
+ if (missing.length > 0) {
2967
+ lines.push(`Missing required secrets: ${missing.join(", ")}`);
2968
+ }
2969
+ const activatorService = runtime.getService(
2970
+ PLUGIN_ACTIVATOR_SERVICE_TYPE
2971
+ );
2972
+ if (activatorService) {
2973
+ const pendingPlugins = activatorService.getPendingPlugins();
2974
+ if (pendingPlugins.length > 0) {
2975
+ lines.push(
2976
+ `Plugins waiting for secrets: ${pendingPlugins.join(", ")}`
2977
+ );
2978
+ const requiredSecrets = activatorService.getRequiredSecrets();
2979
+ if (requiredSecrets.size > 0) {
2980
+ lines.push(
2981
+ `Secrets needed for pending plugins: ${Array.from(requiredSecrets).join(", ")}`
2982
+ );
2983
+ }
2984
+ }
2985
+ }
2986
+ return { text: lines.join("\n") };
2987
+ } catch (error) {
2988
+ const errorMsg = error instanceof Error ? error.message : String(error);
2989
+ logger9.error(
2990
+ `[SecretsStatusProvider] Error getting secrets status: ${errorMsg}`
2991
+ );
2992
+ return { text: "" };
2993
+ }
2994
+ }
2995
+ };
2996
+ var secretsInfoProvider = {
2997
+ name: "SECRETS_INFO",
2998
+ description: "Provides detailed secret information based on conversation context",
2999
+ get: async (runtime, message, state) => {
3000
+ const secretsService = runtime.getService(SECRETS_SERVICE_TYPE);
3001
+ if (!secretsService) {
3002
+ return { text: "" };
3003
+ }
3004
+ const text = message.content.text?.toLowerCase() ?? "";
3005
+ const isAboutSecrets = /\b(secret|key|token|credential|api|password|configure)\b/i.test(text);
3006
+ if (!isAboutSecrets) {
3007
+ return { text: "" };
3008
+ }
3009
+ try {
3010
+ const globalSecrets = await secretsService.list({
3011
+ level: "global",
3012
+ agentId: runtime.agentId
3013
+ });
3014
+ const secretCount = Object.keys(globalSecrets).length;
3015
+ if (secretCount === 0) {
3016
+ return {
3017
+ text: `[Secrets Info]
3018
+ No secrets configured. User can set secrets by saying things like "Set my OPENAI_API_KEY to sk-..."`
3019
+ };
3020
+ }
3021
+ const lines = ["[Secrets Info]"];
3022
+ lines.push(`Total configured secrets: ${secretCount}`);
3023
+ const byType = {};
3024
+ for (const [key, config] of Object.entries(globalSecrets)) {
3025
+ const type = config.type ?? "secret";
3026
+ if (!byType[type]) {
3027
+ byType[type] = [];
3028
+ }
3029
+ byType[type].push(key);
3030
+ }
3031
+ for (const [type, keys] of Object.entries(byType)) {
3032
+ lines.push(`${type}: ${keys.join(", ")}`);
3033
+ }
3034
+ const commonSecrets = [
3035
+ "OPENAI_API_KEY",
3036
+ "ANTHROPIC_API_KEY",
3037
+ "DISCORD_BOT_TOKEN",
3038
+ "TELEGRAM_BOT_TOKEN",
3039
+ "TWITTER_API_KEY"
3040
+ ];
3041
+ const missingCommon = commonSecrets.filter((key) => !globalSecrets[key]);
3042
+ if (missingCommon.length > 0 && missingCommon.length < commonSecrets.length) {
3043
+ lines.push(`Common secrets not set: ${missingCommon.join(", ")}`);
3044
+ }
3045
+ return { text: lines.join("\n") };
3046
+ } catch (error) {
3047
+ const errorMsg = error instanceof Error ? error.message : String(error);
3048
+ logger9.error(`[SecretsInfoProvider] Error: ${errorMsg}`);
3049
+ return { text: "" };
3050
+ }
3051
+ }
3052
+ };
3053
+
3054
+ // src/onboarding/config.ts
3055
+ var DEFAULT_ONBOARDING_MESSAGES = {
3056
+ welcome: [
3057
+ "Hi! I need to collect some information to get set up. Is now a good time?",
3058
+ "Hey there! I need to configure a few things. Do you have a moment?",
3059
+ "Hello! Could we take a few minutes to get everything set up?"
3060
+ ],
3061
+ askSetting: "I need your {{settingName}}. {{usageDescription}}",
3062
+ settingUpdated: "Got it! I've saved your {{settingName}}.",
3063
+ allComplete: "Great! All required settings have been configured. You're all set!",
3064
+ error: "I had trouble understanding that. Could you try again?"
3065
+ };
3066
+ var COMMON_API_KEY_SETTINGS = {
3067
+ OPENAI_API_KEY: {
3068
+ name: "OpenAI API Key",
3069
+ description: "API key for OpenAI services (GPT models)",
3070
+ usageDescription: 'Your OpenAI API key starts with "sk-"',
3071
+ secret: true,
3072
+ public: false,
3073
+ required: false,
3074
+ dependsOn: [],
3075
+ validationMethod: "openai",
3076
+ type: "api_key",
3077
+ envVar: "OPENAI_API_KEY"
3078
+ },
3079
+ ANTHROPIC_API_KEY: {
3080
+ name: "Anthropic API Key",
3081
+ description: "API key for Anthropic services (Claude models)",
3082
+ usageDescription: 'Your Anthropic API key starts with "sk-ant-"',
3083
+ secret: true,
3084
+ public: false,
3085
+ required: false,
3086
+ dependsOn: [],
3087
+ validationMethod: "anthropic",
3088
+ type: "api_key",
3089
+ envVar: "ANTHROPIC_API_KEY"
3090
+ },
3091
+ GROQ_API_KEY: {
3092
+ name: "Groq API Key",
3093
+ description: "API key for Groq inference services",
3094
+ usageDescription: 'Your Groq API key starts with "gsk_"',
3095
+ secret: true,
3096
+ public: false,
3097
+ required: false,
3098
+ dependsOn: [],
3099
+ validationMethod: "groq",
3100
+ type: "api_key",
3101
+ envVar: "GROQ_API_KEY"
3102
+ },
3103
+ GOOGLE_API_KEY: {
3104
+ name: "Google API Key",
3105
+ description: "API key for Google AI services (Gemini)",
3106
+ usageDescription: "Your Google API key for Gemini models",
3107
+ secret: true,
3108
+ public: false,
3109
+ required: false,
3110
+ dependsOn: [],
3111
+ validationMethod: "google",
3112
+ type: "api_key",
3113
+ envVar: "GOOGLE_API_KEY"
3114
+ },
3115
+ DISCORD_TOKEN: {
3116
+ name: "Discord Bot Token",
3117
+ description: "Bot token for Discord integration",
3118
+ usageDescription: "Your Discord bot token from the developer portal",
3119
+ secret: true,
3120
+ public: false,
3121
+ required: false,
3122
+ dependsOn: [],
3123
+ validationMethod: "discord",
3124
+ type: "token",
3125
+ envVar: "DISCORD_TOKEN"
3126
+ },
3127
+ TELEGRAM_BOT_TOKEN: {
3128
+ name: "Telegram Bot Token",
3129
+ description: "Bot token for Telegram integration",
3130
+ usageDescription: "Your Telegram bot token from @BotFather",
3131
+ secret: true,
3132
+ public: false,
3133
+ required: false,
3134
+ dependsOn: [],
3135
+ validationMethod: "telegram",
3136
+ type: "token",
3137
+ envVar: "TELEGRAM_BOT_TOKEN"
3138
+ },
3139
+ TWITTER_USERNAME: {
3140
+ name: "Twitter Username",
3141
+ description: "Twitter/X username for posting",
3142
+ usageDescription: "The Twitter username (without @)",
3143
+ secret: false,
3144
+ public: true,
3145
+ required: false,
3146
+ dependsOn: [],
3147
+ type: "credential"
3148
+ },
3149
+ TWITTER_PASSWORD: {
3150
+ name: "Twitter Password",
3151
+ description: "Twitter/X account password",
3152
+ usageDescription: "The password for your Twitter account",
3153
+ secret: true,
3154
+ public: false,
3155
+ required: false,
3156
+ dependsOn: ["TWITTER_USERNAME"],
3157
+ type: "credential"
3158
+ },
3159
+ TWITTER_EMAIL: {
3160
+ name: "Twitter Email",
3161
+ description: "Email associated with Twitter account",
3162
+ usageDescription: "The email address linked to your Twitter account",
3163
+ secret: false,
3164
+ public: false,
3165
+ required: false,
3166
+ dependsOn: ["TWITTER_USERNAME"],
3167
+ type: "credential",
3168
+ validation: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
3169
+ },
3170
+ TWITTER_2FA_SECRET: {
3171
+ name: "Twitter 2FA Secret",
3172
+ description: "2FA secret for Twitter account",
3173
+ usageDescription: "The 2FA/TOTP secret (if 2FA is enabled)",
3174
+ secret: true,
3175
+ public: false,
3176
+ required: false,
3177
+ dependsOn: ["TWITTER_USERNAME", "TWITTER_PASSWORD"],
3178
+ type: "credential"
3179
+ }
3180
+ };
3181
+ function createOnboardingConfig(requiredKeys, optionalKeys = [], customSettings = {}) {
3182
+ const settings = {};
3183
+ for (const key of requiredKeys) {
3184
+ const common = COMMON_API_KEY_SETTINGS[key] || {};
3185
+ const custom = customSettings[key] || {};
3186
+ settings[key] = {
3187
+ name: custom.name || common.name || key,
3188
+ description: custom.description || common.description || `Configure ${key}`,
3189
+ usageDescription: custom.usageDescription || common.usageDescription,
3190
+ secret: custom.secret ?? common.secret ?? true,
3191
+ public: custom.public ?? common.public ?? false,
3192
+ required: true,
3193
+ dependsOn: custom.dependsOn || common.dependsOn || [],
3194
+ validationMethod: custom.validationMethod || common.validationMethod,
3195
+ type: custom.type || common.type || "api_key",
3196
+ envVar: custom.envVar || common.envVar || key,
3197
+ value: null,
3198
+ ...custom
3199
+ };
3200
+ }
3201
+ for (const key of optionalKeys) {
3202
+ const common = COMMON_API_KEY_SETTINGS[key] || {};
3203
+ const custom = customSettings[key] || {};
3204
+ settings[key] = {
3205
+ name: custom.name || common.name || key,
3206
+ description: custom.description || common.description || `Configure ${key}`,
3207
+ usageDescription: custom.usageDescription || common.usageDescription,
3208
+ secret: custom.secret ?? common.secret ?? true,
3209
+ public: custom.public ?? common.public ?? false,
3210
+ required: false,
3211
+ dependsOn: custom.dependsOn || common.dependsOn || [],
3212
+ validationMethod: custom.validationMethod || common.validationMethod,
3213
+ type: custom.type || common.type || "api_key",
3214
+ envVar: custom.envVar || common.envVar || key,
3215
+ value: null,
3216
+ ...custom
3217
+ };
3218
+ }
3219
+ return { settings };
3220
+ }
3221
+ function getUnconfiguredRequired(config) {
3222
+ return Object.entries(config.settings).filter(
3223
+ ([_, setting]) => setting.required && setting.value === null
3224
+ );
3225
+ }
3226
+ function getUnconfiguredOptional(config) {
3227
+ return Object.entries(config.settings).filter(
3228
+ ([_, setting]) => !setting.required && setting.value === null
3229
+ );
3230
+ }
3231
+ function isOnboardingComplete(config) {
3232
+ return getUnconfiguredRequired(config).length === 0;
3233
+ }
3234
+ function getNextSetting(config) {
3235
+ const unconfigured = getUnconfiguredRequired(config);
3236
+ for (const [key, setting] of unconfigured) {
3237
+ const dependenciesMet = setting.dependsOn.every((dep) => {
3238
+ const depSetting = config.settings[dep];
3239
+ return depSetting && depSetting.value !== null;
3240
+ });
3241
+ const isVisible = !setting.visibleIf || setting.visibleIf(config.settings);
3242
+ if (dependenciesMet && isVisible) {
3243
+ return [key, setting];
3244
+ }
3245
+ }
3246
+ const optionalUnconfigured = getUnconfiguredOptional(config);
3247
+ for (const [key, setting] of optionalUnconfigured) {
3248
+ const dependenciesMet = setting.dependsOn.every((dep) => {
3249
+ const depSetting = config.settings[dep];
3250
+ return depSetting && depSetting.value !== null;
3251
+ });
3252
+ const isVisible = !setting.visibleIf || setting.visibleIf(config.settings);
3253
+ if (dependenciesMet && isVisible) {
3254
+ return [key, setting];
3255
+ }
3256
+ }
3257
+ return null;
3258
+ }
3259
+ function generateSettingPrompt(key, setting, agentName) {
3260
+ const required = setting.required ? "(Required)" : "(Optional)";
3261
+ const usage = setting.usageDescription || setting.description;
3262
+ return `${agentName} needs to collect the ${setting.name} ${required}.
3263
+ Description: ${usage}
3264
+ Ask the user for their ${setting.name} in a natural, conversational way.`;
3265
+ }
3266
+
3267
+ // src/onboarding/service.ts
3268
+ import { Service as Service3, logger as logger10 } from "@elizaos/core";
3269
+ var ONBOARDING_SERVICE_TYPE = "SECRETS_ONBOARDING";
3270
+ var OnboardingService = class _OnboardingService extends Service3 {
3271
+ static serviceType = ONBOARDING_SERVICE_TYPE;
3272
+ capabilityDescription = "Manage secrets onboarding across chat platforms";
3273
+ secretsService = null;
3274
+ sessions = /* @__PURE__ */ new Map();
3275
+ constructor(runtime) {
3276
+ super(runtime);
3277
+ }
3278
+ /**
3279
+ * Start the service
3280
+ */
3281
+ static async start(runtime) {
3282
+ const service = new _OnboardingService(runtime);
3283
+ await service.initialize();
3284
+ return service;
3285
+ }
3286
+ /**
3287
+ * Initialize the service
3288
+ */
3289
+ async initialize() {
3290
+ logger10.info("[OnboardingService] Starting");
3291
+ this.secretsService = this.runtime.getService("SECRETS");
3292
+ this.registerEvents();
3293
+ logger10.info("[OnboardingService] Started");
3294
+ }
3295
+ async stop() {
3296
+ logger10.info("[OnboardingService] Stopping");
3297
+ this.sessions.clear();
3298
+ logger10.info("[OnboardingService] Stopped");
3299
+ }
3300
+ /**
3301
+ * Register platform-specific event handlers.
3302
+ */
3303
+ registerEvents() {
3304
+ this.runtime.registerEvent("DISCORD_WORLD_JOINED", async (params) => {
3305
+ const server = params.server;
3306
+ if (server) {
3307
+ logger10.info(`[OnboardingService] Discord world joined: ${server.id}`);
3308
+ }
3309
+ });
3310
+ this.runtime.registerEvent("DISCORD_SERVER_CONNECTED", async (params) => {
3311
+ const server = params.server;
3312
+ if (server) {
3313
+ logger10.info(
3314
+ `[OnboardingService] Discord server connected: ${server.id}`
3315
+ );
3316
+ }
3317
+ });
3318
+ this.runtime.registerEvent("TELEGRAM_WORLD_JOINED", async (params) => {
3319
+ const typedParams = params;
3320
+ if (typedParams.world && typedParams.chat && typedParams.entities && typedParams.botUsername) {
3321
+ logger10.info(
3322
+ `[OnboardingService] Telegram world joined: ${typedParams.world.id}`
3323
+ );
3324
+ await this.startTelegramOnboarding(
3325
+ typedParams.world,
3326
+ typedParams.chat,
3327
+ typedParams.entities,
3328
+ typedParams.botUsername
3329
+ );
3330
+ }
3331
+ });
3332
+ }
3333
+ /**
3334
+ * Initialize onboarding for a world with the given config.
3335
+ */
3336
+ async initializeOnboarding(world, config) {
3337
+ logger10.info(
3338
+ `[OnboardingService] Initializing onboarding for world: ${world.id}`
3339
+ );
3340
+ if (!world.metadata) {
3341
+ world.metadata = {};
3342
+ }
3343
+ const settingsState = {};
3344
+ for (const [key, setting] of Object.entries(config.settings)) {
3345
+ settingsState[key] = {
3346
+ ...setting,
3347
+ value: null
3348
+ };
3349
+ }
3350
+ const metadata = world.metadata;
3351
+ metadata["settings"] = settingsState;
3352
+ metadata.onboardingConfig = config;
3353
+ await this.runtime.updateWorld(world);
3354
+ logger10.info(
3355
+ `[OnboardingService] Onboarding initialized for world: ${world.id}`
3356
+ );
3357
+ }
3358
+ /**
3359
+ * Start onboarding via DM (Discord).
3360
+ */
3361
+ async startDiscordOnboardingDM(serverId, ownerId, worldId, config) {
3362
+ const messages = config.messages?.welcome || DEFAULT_ONBOARDING_MESSAGES.welcome;
3363
+ const _randomMessage = messages[Math.floor(Math.random() * messages.length)];
3364
+ logger10.info(
3365
+ `[OnboardingService] Discord DM onboarding started - server: ${serverId}, owner: ${ownerId}, world: ${worldId}`
3366
+ );
3367
+ }
3368
+ /**
3369
+ * Start onboarding via deep link (Telegram).
3370
+ */
3371
+ async startTelegramOnboarding(world, chat, entities, botUsername) {
3372
+ let ownerId = null;
3373
+ let ownerUsername = null;
3374
+ for (const entity of entities) {
3375
+ if (entity.metadata?.telegram?.adminTitle === "Owner") {
3376
+ ownerId = entity.metadata.telegram.id;
3377
+ ownerUsername = entity.metadata.telegram.username;
3378
+ break;
3379
+ }
3380
+ }
3381
+ if (!ownerId) {
3382
+ logger10.warn("[OnboardingService] No owner found for Telegram group");
3383
+ return;
3384
+ }
3385
+ const telegramService = this.runtime.getService("telegram");
3386
+ if (telegramService?.messageManager) {
3387
+ const deepLinkMessage = [
3388
+ `Hello @${ownerUsername}! Could we take a few minutes to get everything set up?`,
3389
+ `Please click this link to start chatting with me: https://t.me/${botUsername}?start=onboarding`
3390
+ ].join(" ");
3391
+ await telegramService.messageManager.sendMessage(chat.id, {
3392
+ text: deepLinkMessage
3393
+ });
3394
+ logger10.info(
3395
+ `[OnboardingService] Sent Telegram deep link - chatId: ${chat.id}, ownerId: ${ownerId}`
3396
+ );
3397
+ }
3398
+ }
3399
+ /**
3400
+ * Start a new onboarding session.
3401
+ */
3402
+ async startSession(worldId, userId, roomId, config, platform = "other", mode = "conversational") {
3403
+ const session = {
3404
+ worldId,
3405
+ userId,
3406
+ roomId,
3407
+ config,
3408
+ currentSettingKey: null,
3409
+ startedAt: Date.now(),
3410
+ lastActivityAt: Date.now(),
3411
+ platform,
3412
+ mode
3413
+ };
3414
+ this.sessions.set(roomId, session);
3415
+ logger10.info(
3416
+ `[OnboardingService] Session started - roomId: ${roomId}, worldId: ${worldId}, userId: ${userId}`
3417
+ );
3418
+ return session;
3419
+ }
3420
+ /**
3421
+ * Get an active session by room ID.
3422
+ */
3423
+ getSession(roomId) {
3424
+ return this.sessions.get(roomId) || null;
3425
+ }
3426
+ /**
3427
+ * Process a user message during onboarding.
3428
+ */
3429
+ async processMessage(roomId, message) {
3430
+ const session = this.sessions.get(roomId);
3431
+ if (!session) {
3432
+ return { shouldRespond: false };
3433
+ }
3434
+ session.lastActivityAt = Date.now();
3435
+ const unconfigured = getUnconfiguredRequired(session.config);
3436
+ if (unconfigured.length === 0) {
3437
+ return {
3438
+ shouldRespond: true,
3439
+ response: session.config.messages?.allComplete || DEFAULT_ONBOARDING_MESSAGES.allComplete,
3440
+ complete: true
3441
+ };
3442
+ }
3443
+ const text = message.content?.text || "";
3444
+ const currentSetting = session.currentSettingKey ? session.config.settings[session.currentSettingKey] : null;
3445
+ if (currentSetting && text.trim()) {
3446
+ const value = text.trim();
3447
+ if (currentSetting.validation && !currentSetting.validation(value)) {
3448
+ return {
3449
+ shouldRespond: true,
3450
+ response: `That doesn't look like a valid ${currentSetting.name}. ${currentSetting.usageDescription || "Please try again."}`
3451
+ };
3452
+ }
3453
+ if (this.secretsService) {
3454
+ const context = {
3455
+ level: "world",
3456
+ agentId: this.runtime.agentId,
3457
+ worldId: session.worldId,
3458
+ userId: session.userId
3459
+ };
3460
+ await this.secretsService.set(
3461
+ session.currentSettingKey,
3462
+ value,
3463
+ context,
3464
+ {
3465
+ description: currentSetting.description,
3466
+ type: currentSetting.type,
3467
+ encrypted: currentSetting.secret
3468
+ }
3469
+ );
3470
+ }
3471
+ session.config.settings[session.currentSettingKey].value = value;
3472
+ if (isOnboardingComplete(session.config)) {
3473
+ this.sessions.delete(roomId);
3474
+ return {
3475
+ shouldRespond: true,
3476
+ response: session.config.messages?.allComplete || DEFAULT_ONBOARDING_MESSAGES.allComplete,
3477
+ updatedKey: session.currentSettingKey,
3478
+ complete: true
3479
+ };
3480
+ }
3481
+ const next2 = getNextSetting(session.config);
3482
+ if (next2) {
3483
+ const [nextKey, nextSetting] = next2;
3484
+ session.currentSettingKey = nextKey;
3485
+ const askMessage = (session.config.messages?.askSetting || DEFAULT_ONBOARDING_MESSAGES.askSetting).replace("{{settingName}}", nextSetting.name).replace(
3486
+ "{{usageDescription}}",
3487
+ nextSetting.usageDescription || nextSetting.description
3488
+ );
3489
+ return {
3490
+ shouldRespond: true,
3491
+ response: `${session.config.messages?.settingUpdated || DEFAULT_ONBOARDING_MESSAGES.settingUpdated.replace("{{settingName}}", currentSetting.name)}
3492
+
3493
+ ${askMessage}`,
3494
+ updatedKey: session.currentSettingKey
3495
+ };
3496
+ }
3497
+ }
3498
+ const next = getNextSetting(session.config);
3499
+ if (next) {
3500
+ const [nextKey, nextSetting] = next;
3501
+ session.currentSettingKey = nextKey;
3502
+ const askMessage = (session.config.messages?.askSetting || DEFAULT_ONBOARDING_MESSAGES.askSetting).replace("{{settingName}}", nextSetting.name).replace(
3503
+ "{{usageDescription}}",
3504
+ nextSetting.usageDescription || nextSetting.description
3505
+ );
3506
+ return {
3507
+ shouldRespond: true,
3508
+ response: askMessage
3509
+ };
3510
+ }
3511
+ return { shouldRespond: false };
3512
+ }
3513
+ /**
3514
+ * End an onboarding session.
3515
+ */
3516
+ endSession(roomId) {
3517
+ this.sessions.delete(roomId);
3518
+ logger10.info(`[OnboardingService] Session ended - roomId: ${roomId}`);
3519
+ }
3520
+ /**
3521
+ * Get the onboarding status for a world.
3522
+ */
3523
+ async getOnboardingStatus(worldId) {
3524
+ const world = await this.runtime.getWorld(worldId);
3525
+ if (!world?.metadata?.settings) {
3526
+ return {
3527
+ initialized: false,
3528
+ complete: false,
3529
+ configuredCount: 0,
3530
+ requiredCount: 0,
3531
+ missingRequired: []
3532
+ };
3533
+ }
3534
+ const settings = world.metadata.settings;
3535
+ const entries = Object.entries(settings);
3536
+ const required = entries.filter(([_, s]) => s.required);
3537
+ const configured = required.filter(([_, s]) => s.value !== null);
3538
+ const missing = required.filter(([_, s]) => s.value === null).map(([k, _]) => k);
3539
+ return {
3540
+ initialized: true,
3541
+ complete: missing.length === 0,
3542
+ configuredCount: configured.length,
3543
+ requiredCount: required.length,
3544
+ missingRequired: missing
3545
+ };
3546
+ }
3547
+ /**
3548
+ * Generate the SETTINGS provider context for LLM.
3549
+ */
3550
+ generateSettingsContext(config, isOnboarding, agentName) {
3551
+ const entries = Object.entries(config.settings);
3552
+ const unconfigured = getUnconfiguredRequired(config);
3553
+ const settingsList = entries.map(([key, setting]) => {
3554
+ const status = setting.value !== null ? "Configured" : "Not set";
3555
+ const required = setting.required ? "(Required)" : "(Optional)";
3556
+ const value = setting.secret && setting.value ? "****************" : setting.value || "Not set";
3557
+ return `${key}: ${value} ${required}
3558
+ (${setting.name}) ${setting.usageDescription || setting.description}`;
3559
+ }).join("\n\n");
3560
+ const validKeys = `Valid setting keys: ${entries.map(([k, _]) => k).join(", ")}`;
3561
+ if (isOnboarding && unconfigured.length > 0) {
3562
+ return `# PRIORITY TASK: Onboarding
3563
+
3564
+ ${agentName} needs to help the user configure ${unconfigured.length} required settings:
3565
+
3566
+ ${settingsList}
3567
+
3568
+ ${validKeys}
3569
+
3570
+ Instructions for ${agentName}:
3571
+ - Only update settings if the user is clearly responding to a setting you are currently asking about.
3572
+ - If the user's reply clearly maps to a setting and a valid value, you **must** call the UPDATE_SETTINGS action.
3573
+ - Never hallucinate settings or respond with values not listed above.
3574
+ - Prioritize configuring required settings before optional ones.`;
3575
+ }
3576
+ return `## Current Configuration
3577
+ ${unconfigured.length > 0 ? `IMPORTANT: ${unconfigured.length} required settings still need configuration.
3578
+
3579
+ ` : "All required settings are configured.\n\n"}${settingsList}`;
3580
+ }
3581
+ };
3582
+
3583
+ // src/onboarding/provider.ts
3584
+ import { ChannelType as ChannelType2, logger as logger11 } from "@elizaos/core";
3585
+ function formatSettingValue(setting, isOnboarding) {
3586
+ if (setting.value === null || setting.value === void 0) {
3587
+ return "Not set";
3588
+ }
3589
+ if (setting.secret && !isOnboarding) {
3590
+ return "****************";
3591
+ }
3592
+ return String(setting.value);
3593
+ }
3594
+ function generateStatusMessage(settings, isOnboarding, agentName, senderName) {
3595
+ const entries = Object.entries(settings);
3596
+ const formattedSettings = entries.map(([key, setting]) => {
3597
+ if (setting.visibleIf && !setting.visibleIf(settings)) {
3598
+ return null;
3599
+ }
3600
+ return {
3601
+ key,
3602
+ name: setting.name,
3603
+ value: formatSettingValue(setting, isOnboarding),
3604
+ description: setting.description,
3605
+ usageDescription: setting.usageDescription || setting.description,
3606
+ required: setting.required,
3607
+ configured: setting.value !== null
3608
+ };
3609
+ }).filter(Boolean);
3610
+ const requiredUnconfigured = formattedSettings.filter(
3611
+ (s) => s?.required && !s.configured
3612
+ ).length;
3613
+ if (isOnboarding) {
3614
+ const settingsList = formattedSettings.map((s) => {
3615
+ if (!s) return "";
3616
+ const label = s.required ? "(Required)" : "(Optional)";
3617
+ return `${s.key}: ${s.value} ${label}
3618
+ (${s.name}) ${s.usageDescription}`;
3619
+ }).filter(Boolean).join("\n\n");
3620
+ const validKeys = `Valid setting keys: ${entries.map(([k]) => k).join(", ")}`;
3621
+ const instructions = `Instructions for ${agentName}:
3622
+ - Only update settings if the user is clearly responding to a setting you are currently asking about.
3623
+ - If the user's reply clearly maps to a setting and a valid value, you **must** call the UPDATE_SETTINGS action with the correct key and value.
3624
+ - Never hallucinate settings or respond with values not listed above.
3625
+ - Do not call UPDATE_SETTINGS just because onboarding started. Only update when the user provides a specific value.
3626
+ - Answer setting-related questions using only the name, description, and value from the list.`;
3627
+ if (requiredUnconfigured > 0) {
3628
+ const name = senderName || "user";
3629
+ return `# PRIORITY TASK: Onboarding with ${name}
3630
+
3631
+ ${agentName} needs to help the user configure ${requiredUnconfigured} required settings:
3632
+
3633
+ ${settingsList}
3634
+
3635
+ ${validKeys}
3636
+
3637
+ ${instructions}
3638
+
3639
+ - Prioritize configuring required settings before optional ones.`;
3640
+ }
3641
+ return `All required settings have been configured. Here's the current configuration:
3642
+
3643
+ ${settingsList}
3644
+
3645
+ ${validKeys}
3646
+
3647
+ ${instructions}`;
3648
+ }
3649
+ return `## Current Configuration
3650
+
3651
+ ${requiredUnconfigured > 0 ? `IMPORTANT!: ${requiredUnconfigured} required settings still need configuration. ${agentName} should get onboarded with the OWNER as soon as possible.
3652
+
3653
+ ` : "All required settings are configured.\n\n"}${formattedSettings.map((s) => {
3654
+ if (!s) return "";
3655
+ return `### ${s.name}
3656
+ **Value:** ${s.value}
3657
+ **Description:** ${s.description}`;
3658
+ }).filter(Boolean).join("\n\n")}`;
3659
+ }
3660
+ var onboardingSettingsProvider = {
3661
+ name: "ONBOARDING_SETTINGS",
3662
+ description: "Current onboarding settings status for secrets collection",
3663
+ get: async (runtime, message, state) => {
3664
+ const room = await runtime.getRoom(message.roomId);
3665
+ if (!room) {
3666
+ logger11.debug("[OnboardingSettingsProvider] No room found");
3667
+ return {
3668
+ data: { settings: [] },
3669
+ values: { settings: "Error: Room not found" },
3670
+ text: "Error: Room not found"
3671
+ };
3672
+ }
3673
+ if (!room.worldId) {
3674
+ logger11.debug("[OnboardingSettingsProvider] No world ID for room");
3675
+ return {
3676
+ data: { settings: [] },
3677
+ values: { settings: "Room has no associated world." },
3678
+ text: "Room has no associated world."
3679
+ };
3680
+ }
3681
+ const isOnboarding = room.type === ChannelType2.DM;
3682
+ const world = await runtime.getWorld(room.worldId);
3683
+ if (!world) {
3684
+ logger11.debug("[OnboardingSettingsProvider] No world found");
3685
+ return {
3686
+ data: { settings: [] },
3687
+ values: { settings: "Error: World not found" },
3688
+ text: "Error: World not found"
3689
+ };
3690
+ }
3691
+ const worldSettings = world.metadata?.settings;
3692
+ if (!worldSettings) {
3693
+ if (isOnboarding) {
3694
+ return {
3695
+ data: { settings: [] },
3696
+ values: {
3697
+ settings: "No settings configured for this world. Use initializeOnboarding to set up."
3698
+ },
3699
+ text: "No settings configured for this world."
3700
+ };
3701
+ }
3702
+ return {
3703
+ data: { settings: [] },
3704
+ values: { settings: "" },
3705
+ text: ""
3706
+ };
3707
+ }
3708
+ const agentName = runtime.character.name ?? "Agent";
3709
+ const senderName = state?.senderName;
3710
+ const output = generateStatusMessage(
3711
+ worldSettings,
3712
+ isOnboarding,
3713
+ agentName,
3714
+ senderName
3715
+ );
3716
+ return {
3717
+ data: { settings: worldSettings },
3718
+ values: { settings: output },
3719
+ text: output
3720
+ };
3721
+ }
3722
+ };
3723
+ var missingSecretsProvider = {
3724
+ name: "MISSING_SECRETS",
3725
+ description: "Lists secrets that still need to be configured",
3726
+ get: async (runtime, message, _state) => {
3727
+ const room = await runtime.getRoom(message.roomId);
3728
+ if (!room?.worldId) {
3729
+ return {
3730
+ data: { missing: [] },
3731
+ values: { missingSecrets: "" },
3732
+ text: ""
3733
+ };
3734
+ }
3735
+ const world = await runtime.getWorld(room.worldId);
3736
+ if (!world?.metadata?.settings) {
3737
+ return {
3738
+ data: { missing: [] },
3739
+ values: { missingSecrets: "" },
3740
+ text: ""
3741
+ };
3742
+ }
3743
+ const settings = world.metadata.settings;
3744
+ const entries = Object.entries(settings);
3745
+ const missingRequired = entries.filter(([_, s]) => s.required && s.value === null).map(([key, setting]) => ({
3746
+ key,
3747
+ name: setting.name,
3748
+ description: setting.usageDescription || setting.description
3749
+ }));
3750
+ const missingOptional = entries.filter(([_, s]) => !s.required && s.value === null).map(([key, setting]) => ({
3751
+ key,
3752
+ name: setting.name,
3753
+ description: setting.usageDescription || setting.description
3754
+ }));
3755
+ if (missingRequired.length === 0 && missingOptional.length === 0) {
3756
+ return {
3757
+ data: { missing: [] },
3758
+ values: { missingSecrets: "All secrets are configured." },
3759
+ text: "All secrets are configured."
3760
+ };
3761
+ }
3762
+ let output = "";
3763
+ if (missingRequired.length > 0) {
3764
+ output += `Missing required secrets:
3765
+ ${missingRequired.map((s) => `- ${s.key}: ${s.description}`).join("\n")}
3766
+
3767
+ `;
3768
+ }
3769
+ if (missingOptional.length > 0) {
3770
+ output += `Missing optional secrets:
3771
+ ${missingOptional.map((s) => `- ${s.key}: ${s.description}`).join("\n")}`;
3772
+ }
3773
+ return {
3774
+ data: {
3775
+ missing: [...missingRequired, ...missingOptional],
3776
+ missingRequired,
3777
+ missingOptional
3778
+ },
3779
+ values: { missingSecrets: output.trim() },
3780
+ text: output.trim()
3781
+ };
3782
+ }
3783
+ };
3784
+
3785
+ // src/onboarding/action.ts
3786
+ import { ChannelType as ChannelType3, ModelType as ModelType3, logger as logger12 } from "@elizaos/core";
3787
+ async function extractSettingValues(runtime, message, state, settings) {
3788
+ const unconfigured = Object.entries(settings).filter(
3789
+ ([_, s]) => s.value === null
3790
+ );
3791
+ if (unconfigured.length === 0) {
3792
+ return [];
3793
+ }
3794
+ const settingsContext = unconfigured.map(([key, setting]) => {
3795
+ const requiredStr = setting.required ? "Required." : "Optional.";
3796
+ return `${key}: ${setting.description} ${requiredStr}`;
3797
+ }).join("\n");
3798
+ const prompt = `I need to extract settings values from the user's message.
3799
+
3800
+ Available settings:
3801
+ ${settingsContext}
3802
+
3803
+ User message: ${state.text || message.content?.text || ""}
3804
+
3805
+ For each setting mentioned in the user's message, extract the value.
3806
+
3807
+ Only return settings that are clearly mentioned in the user's message.
3808
+ If a setting is mentioned but no clear value is provided, do not include it.`;
3809
+ const result = await runtime.useModel(ModelType3.OBJECT_LARGE, {
3810
+ prompt,
3811
+ output: "array",
3812
+ schema: {
3813
+ type: "array",
3814
+ items: {
3815
+ type: "object",
3816
+ properties: {
3817
+ key: { type: "string" },
3818
+ value: { type: "string" }
3819
+ },
3820
+ required: ["key", "value"]
3821
+ }
3822
+ }
3823
+ });
3824
+ if (!result) {
3825
+ return [];
3826
+ }
3827
+ const validUpdates = [];
3828
+ const extractFromResult = (obj) => {
3829
+ if (Array.isArray(obj)) {
3830
+ for (const item of obj) {
3831
+ extractFromResult(item);
3832
+ }
3833
+ } else if (typeof obj === "object" && obj !== null) {
3834
+ for (const [key, value] of Object.entries(obj)) {
3835
+ if (settings[key] && typeof value !== "object") {
3836
+ validUpdates.push({ key, value });
3837
+ } else {
3838
+ extractFromResult(value);
3839
+ }
3840
+ }
3841
+ }
3842
+ };
3843
+ extractFromResult(result);
3844
+ return validUpdates;
3845
+ }
3846
+ async function processSettingUpdates(runtime, world, settings, updates, secretsService) {
3847
+ if (!updates.length) {
3848
+ return { updatedAny: false, messages: [] };
3849
+ }
3850
+ const messages = [];
3851
+ let updatedAny = false;
3852
+ const updatedSettings = { ...settings };
3853
+ for (const update of updates) {
3854
+ const setting = updatedSettings[update.key];
3855
+ if (!setting) continue;
3856
+ if (setting.dependsOn?.length) {
3857
+ const dependenciesMet = setting.dependsOn.every((dep) => {
3858
+ const depSetting = updatedSettings[dep];
3859
+ return depSetting && depSetting.value !== null;
3860
+ });
3861
+ if (!dependenciesMet) {
3862
+ messages.push(`Cannot update ${setting.name} - dependencies not met`);
3863
+ continue;
3864
+ }
3865
+ }
3866
+ const valueStr = String(update.value);
3867
+ if (setting.validation && !setting.validation(valueStr)) {
3868
+ messages.push(`Invalid value for ${setting.name}`);
3869
+ continue;
3870
+ }
3871
+ if (setting.validationMethod) {
3872
+ const validation = await validateSecret(
3873
+ update.key,
3874
+ valueStr,
3875
+ setting.validationMethod
3876
+ );
3877
+ if (!validation.isValid) {
3878
+ messages.push(
3879
+ `Validation failed for ${setting.name}: ${validation.error}`
3880
+ );
3881
+ continue;
3882
+ }
3883
+ }
3884
+ updatedSettings[update.key] = {
3885
+ ...setting,
3886
+ value: valueStr
3887
+ };
3888
+ if (secretsService) {
3889
+ const context = {
3890
+ level: "world",
3891
+ agentId: runtime.agentId,
3892
+ worldId: world.id
3893
+ };
3894
+ await secretsService.set(update.key, valueStr, context, {
3895
+ description: setting.description,
3896
+ type: setting.type,
3897
+ encrypted: setting.secret
3898
+ });
3899
+ }
3900
+ messages.push(`Updated ${setting.name} successfully`);
3901
+ updatedAny = true;
3902
+ if (setting.onSetAction) {
3903
+ const actionMessage = setting.onSetAction(update.value);
3904
+ if (actionMessage) {
3905
+ messages.push(actionMessage);
3906
+ }
3907
+ }
3908
+ }
3909
+ if (updatedAny) {
3910
+ if (!world.metadata) {
3911
+ world.metadata = {};
3912
+ }
3913
+ world.metadata["settings"] = updatedSettings;
3914
+ await runtime.updateWorld(world);
3915
+ }
3916
+ return { updatedAny, messages };
3917
+ }
3918
+ function getNextRequiredSetting(settings) {
3919
+ const entries = Object.entries(settings);
3920
+ for (const [key, setting] of entries) {
3921
+ if (!setting.required || setting.value !== null) continue;
3922
+ const dependenciesMet = (setting.dependsOn || []).every((dep) => {
3923
+ const depSetting = settings[dep];
3924
+ return depSetting && depSetting.value !== null;
3925
+ });
3926
+ if (dependenciesMet) {
3927
+ return [key, setting];
3928
+ }
3929
+ }
3930
+ return null;
3931
+ }
3932
+ function countUnconfiguredRequired(settings) {
3933
+ return Object.values(settings).filter((s) => s.required && s.value === null).length;
3934
+ }
3935
+ var updateSettingsAction = {
3936
+ name: "UPDATE_SETTINGS",
3937
+ similes: ["UPDATE_SETTING", "SAVE_SETTING", "SET_CONFIGURATION", "CONFIGURE"],
3938
+ description: "Saves a configuration setting during the onboarding process. Use when onboarding with a world owner or admin.",
3939
+ validate: async (runtime, message, _state) => {
3940
+ if (message.content.channelType !== ChannelType3.DM) {
3941
+ return false;
3942
+ }
3943
+ const room = await runtime.getRoom(message.roomId);
3944
+ if (!room?.worldId) return false;
3945
+ const world = await runtime.getWorld(room.worldId);
3946
+ if (!world?.metadata?.settings) return false;
3947
+ const settings = world.metadata.settings;
3948
+ const hasUnconfigured = Object.values(settings).some(
3949
+ (s) => s.value === null
3950
+ );
3951
+ return hasUnconfigured;
3952
+ },
3953
+ handler: async (runtime, message, state, _options, callback) => {
3954
+ if (!state || !callback) {
3955
+ return {
3956
+ text: "State and callback required",
3957
+ values: { success: false },
3958
+ data: { actionName: "UPDATE_SETTINGS" },
3959
+ success: false
3960
+ };
3961
+ }
3962
+ const room = await runtime.getRoom(message.roomId);
3963
+ if (!room?.worldId) {
3964
+ await callback({ text: "Unable to find room configuration." });
3965
+ return {
3966
+ text: "Room not found",
3967
+ values: { success: false },
3968
+ data: { actionName: "UPDATE_SETTINGS" },
3969
+ success: false
3970
+ };
3971
+ }
3972
+ const world = await runtime.getWorld(room.worldId);
3973
+ if (!world?.metadata?.settings) {
3974
+ await callback({ text: "No settings configured for this world." });
3975
+ return {
3976
+ text: "No settings found",
3977
+ values: { success: false },
3978
+ data: { actionName: "UPDATE_SETTINGS" },
3979
+ success: false
3980
+ };
3981
+ }
3982
+ const settings = world.metadata.settings;
3983
+ const secretsService = runtime.getService(
3984
+ "SECRETS"
3985
+ );
3986
+ logger12.info("[UpdateSettings] Extracting settings from message");
3987
+ const extractedSettings = await extractSettingValues(
3988
+ runtime,
3989
+ message,
3990
+ state,
3991
+ settings
3992
+ );
3993
+ logger12.info(
3994
+ `[UpdateSettings] Extracted ${extractedSettings.length} settings`
3995
+ );
3996
+ const results = await processSettingUpdates(
3997
+ runtime,
3998
+ world,
3999
+ settings,
4000
+ extractedSettings,
4001
+ secretsService
4002
+ );
4003
+ const updatedWorld = await runtime.getWorld(room.worldId);
4004
+ const updatedSettings = updatedWorld?.metadata?.settings;
4005
+ if (results.updatedAny) {
4006
+ const remaining = countUnconfiguredRequired(updatedSettings || settings);
4007
+ if (remaining === 0) {
4008
+ await callback({
4009
+ text: `${results.messages.join("\n")}
4010
+
4011
+ All required settings have been configured! You're all set.`,
4012
+ actions: ["ONBOARDING_COMPLETE"]
4013
+ });
4014
+ return {
4015
+ text: "Onboarding complete",
4016
+ values: { success: true, onboardingComplete: true },
4017
+ data: {
4018
+ actionName: "UPDATE_SETTINGS",
4019
+ action: "ONBOARDING_COMPLETE"
4020
+ },
4021
+ success: true
4022
+ };
4023
+ }
4024
+ const next2 = getNextRequiredSetting(updatedSettings || settings);
4025
+ const nextPrompt = next2 ? `
4026
+
4027
+ Next, I need your ${next2[1].name}. ${next2[1].usageDescription || next2[1].description}` : "";
4028
+ await callback({
4029
+ text: `${results.messages.join("\n")}${nextPrompt}`,
4030
+ actions: ["SETTING_UPDATED"]
4031
+ });
4032
+ return {
4033
+ text: "Settings updated",
4034
+ values: { success: true, remainingRequired: remaining },
4035
+ data: {
4036
+ actionName: "UPDATE_SETTINGS",
4037
+ action: "SETTING_UPDATED",
4038
+ updated: extractedSettings.map((s) => s.key)
4039
+ },
4040
+ success: true
4041
+ };
4042
+ }
4043
+ const next = getNextRequiredSetting(settings);
4044
+ const prompt = next ? `I couldn't understand that. I need your ${next[1].name}. ${next[1].usageDescription || next[1].description}` : "I couldn't extract any settings from your message. Could you try again?";
4045
+ await callback({
4046
+ text: prompt,
4047
+ actions: ["SETTING_UPDATE_FAILED"]
4048
+ });
4049
+ return {
4050
+ text: "No settings updated",
4051
+ values: { success: false },
4052
+ data: { actionName: "UPDATE_SETTINGS", action: "SETTING_UPDATE_FAILED" },
4053
+ success: false
4054
+ };
4055
+ },
4056
+ examples: [
4057
+ [
4058
+ {
4059
+ name: "{{name1}}",
4060
+ content: {
4061
+ text: "My OpenAI key is sk-abc123def456",
4062
+ source: "discord"
4063
+ }
4064
+ },
4065
+ {
4066
+ name: "{{name2}}",
4067
+ content: {
4068
+ text: "Got it! I've saved your OpenAI API Key. Next, I need your Anthropic API Key.",
4069
+ actions: ["SETTING_UPDATED"],
4070
+ source: "discord"
4071
+ }
4072
+ }
4073
+ ],
4074
+ [
4075
+ {
4076
+ name: "{{name1}}",
4077
+ content: {
4078
+ text: "Here's my Twitter login: @myhandle with password secret123",
4079
+ source: "discord"
4080
+ }
4081
+ },
4082
+ {
4083
+ name: "{{name2}}",
4084
+ content: {
4085
+ text: "Perfect! I've updated your Twitter Username and Twitter Password. We're all set!",
4086
+ actions: ["ONBOARDING_COMPLETE"],
4087
+ source: "discord"
4088
+ }
4089
+ }
4090
+ ]
4091
+ ]
4092
+ };
4093
+
4094
+ // src/plugin.ts
4095
+ var secretsManagerPlugin = {
4096
+ name: "@elizaos/plugin-secrets-manager",
4097
+ description: "Multi-level secret management with encryption, dynamic plugin activation, and conversational onboarding",
4098
+ // Services
4099
+ services: [SecretsService, PluginActivatorService, OnboardingService],
4100
+ // Actions for natural language secret management and onboarding
4101
+ actions: [setSecretAction, manageSecretAction, updateSettingsAction],
4102
+ // Providers for context injection
4103
+ providers: [
4104
+ secretsStatusProvider,
4105
+ secretsInfoProvider,
4106
+ onboardingSettingsProvider,
4107
+ missingSecretsProvider
4108
+ ],
4109
+ // Plugin initialization
4110
+ init: async (config, runtime) => {
4111
+ logger13.info("[SecretsManagerPlugin] Initializing");
4112
+ logger13.info("[SecretsManagerPlugin] Initialized");
4113
+ }
4114
+ };
4115
+ export {
4116
+ BaseSecretStorage,
4117
+ COMMON_API_KEY_SETTINGS,
4118
+ CharacterSettingsStorage,
4119
+ ComponentSecretStorage,
4120
+ CompositeSecretStorage,
4121
+ DEFAULT_ONBOARDING_MESSAGES,
4122
+ EncryptionError,
4123
+ KeyManager,
4124
+ MAX_ACCESS_LOG_ENTRIES,
4125
+ MemorySecretStorage,
4126
+ ONBOARDING_SERVICE_TYPE,
4127
+ OnboardingService,
4128
+ PLUGIN_ACTIVATOR_SERVICE_TYPE,
4129
+ PermissionDeniedError,
4130
+ PluginActivatorService,
4131
+ SECRETS_SERVICE_TYPE,
4132
+ SECRET_DESCRIPTION_MAX_LENGTH,
4133
+ SECRET_KEY_MAX_LENGTH,
4134
+ SECRET_KEY_PATTERN,
4135
+ SECRET_VALUE_MAX_LENGTH,
4136
+ SecretNotFoundError,
4137
+ SecretsError,
4138
+ SecretsService,
4139
+ StorageError,
4140
+ ValidationError,
4141
+ ValidationStrategies,
4142
+ WorldMetadataStorage,
4143
+ createOnboardingConfig,
4144
+ decrypt,
4145
+ secretsManagerPlugin as default,
4146
+ deriveKeyFromAgentId,
4147
+ deriveKeyPbkdf2,
4148
+ encrypt,
4149
+ generateKey,
4150
+ generateSalt,
4151
+ generateSecureToken,
4152
+ generateSettingPrompt,
4153
+ getNextSetting,
4154
+ getUnconfiguredOptional,
4155
+ getUnconfiguredRequired,
4156
+ inferValidationStrategy,
4157
+ isEncryptedSecret,
4158
+ isOnboardingComplete,
4159
+ manageSecretAction,
4160
+ missingSecretsProvider,
4161
+ onboardingSettingsProvider,
4162
+ registerValidator,
4163
+ secretsInfoProvider,
4164
+ secretsManagerPlugin,
4165
+ secretsStatusProvider,
4166
+ setSecretAction,
4167
+ unregisterValidator,
4168
+ updateSettingsAction,
4169
+ validateSecret
4170
+ };
4171
+ //# sourceMappingURL=index.js.map