@frontmcp/plugin-remember 1.8.7 → 1.9.1-rc.1
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/esm/index.mjs +66 -53
- package/esm/package.json +4 -4
- package/index.js +63 -53
- package/package.json +4 -4
- package/providers/remember-accessor.provider.d.ts +8 -3
- package/providers/remember-accessor.provider.d.ts.map +1 -1
- package/providers/remember-vercel-kv.provider.d.ts +5 -1
- package/providers/remember-vercel-kv.provider.d.ts.map +1 -1
package/esm/index.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
1
4
|
var __defProp = Object.defineProperty;
|
|
2
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
6
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -405,6 +408,9 @@ var STORAGE_LAYOUT_VERSION = "v2";
|
|
|
405
408
|
function encodeKeyPart(value) {
|
|
406
409
|
return encodeURIComponent(value);
|
|
407
410
|
}
|
|
411
|
+
function remainingSeconds(expiresAt, now) {
|
|
412
|
+
return Math.max(1, Math.ceil((expiresAt - now) / 1e3));
|
|
413
|
+
}
|
|
408
414
|
var RememberAccessor = class {
|
|
409
415
|
store;
|
|
410
416
|
ctx;
|
|
@@ -454,16 +460,8 @@ var RememberAccessor = class {
|
|
|
454
460
|
*/
|
|
455
461
|
async get(key, options = {}) {
|
|
456
462
|
const scope = options.scope ?? "session";
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
if (!raw) return options.defaultValue;
|
|
460
|
-
const entry = this.encryptionEnabled ? await deserializeAndDecrypt(raw, this.getKeySource(scope)) : this.parseEntry(raw);
|
|
461
|
-
if (!entry) return options.defaultValue;
|
|
462
|
-
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
463
|
-
await this.forget(key, { scope });
|
|
464
|
-
return options.defaultValue;
|
|
465
|
-
}
|
|
466
|
-
return entry.value;
|
|
463
|
+
const entry = await this.readEntry(this.buildStorageKey(key, scope), scope);
|
|
464
|
+
return entry ? entry.value : options.defaultValue;
|
|
467
465
|
}
|
|
468
466
|
/**
|
|
469
467
|
* Get the full entry including metadata.
|
|
@@ -474,16 +472,7 @@ var RememberAccessor = class {
|
|
|
474
472
|
*/
|
|
475
473
|
async getEntry(key, options = {}) {
|
|
476
474
|
const scope = options.scope ?? "session";
|
|
477
|
-
|
|
478
|
-
const raw = await this.store.getValue(storageKey);
|
|
479
|
-
if (!raw) return void 0;
|
|
480
|
-
const entry = this.encryptionEnabled ? await deserializeAndDecrypt(raw, this.getKeySource(scope)) : this.parseEntry(raw);
|
|
481
|
-
if (!entry) return void 0;
|
|
482
|
-
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
483
|
-
await this.forget(key, { scope });
|
|
484
|
-
return void 0;
|
|
485
|
-
}
|
|
486
|
-
return entry;
|
|
475
|
+
return this.readEntry(this.buildStorageKey(key, scope), scope);
|
|
487
476
|
}
|
|
488
477
|
/**
|
|
489
478
|
* Forget (delete) a value from memory.
|
|
@@ -497,19 +486,18 @@ var RememberAccessor = class {
|
|
|
497
486
|
await this.store.delete(storageKey);
|
|
498
487
|
}
|
|
499
488
|
/**
|
|
500
|
-
* Check if a key is remembered (
|
|
489
|
+
* Check if a key is remembered: true exactly when `get()` would return its value.
|
|
501
490
|
*
|
|
502
491
|
* @param key - The key to check
|
|
503
492
|
* @param options - Options (scope)
|
|
504
|
-
* @returns true if the key exists
|
|
493
|
+
* @returns true if the key exists and has not expired
|
|
505
494
|
*/
|
|
506
495
|
async knows(key, options = {}) {
|
|
507
496
|
const scope = options.scope ?? "session";
|
|
508
|
-
|
|
509
|
-
return this.store.exists(storageKey);
|
|
497
|
+
return await this.readEntry(this.buildStorageKey(key, scope), scope) !== void 0;
|
|
510
498
|
}
|
|
511
499
|
/**
|
|
512
|
-
* List all remembered keys for a scope.
|
|
500
|
+
* List all remembered keys for a scope: the keys `get()` would return a value for.
|
|
513
501
|
*
|
|
514
502
|
* @param options - Options (scope, pattern)
|
|
515
503
|
* @returns Array of keys (without the scope prefix)
|
|
@@ -517,13 +505,16 @@ var RememberAccessor = class {
|
|
|
517
505
|
async list(options = {}) {
|
|
518
506
|
const scope = options.scope ?? "session";
|
|
519
507
|
const scopePrefix = this.buildScopePrefix(scope);
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
return
|
|
508
|
+
const storageKeys = await this.store.keys(scopePrefix + (options.pattern ?? "*"));
|
|
509
|
+
const entries = await Promise.all(storageKeys.map((storageKey) => this.readEntry(storageKey, scope)));
|
|
510
|
+
return storageKeys.filter((_storageKey, index) => entries[index] !== void 0).map((storageKey) => storageKey.slice(scopePrefix.length));
|
|
523
511
|
}
|
|
524
512
|
/**
|
|
525
513
|
* Update an existing entry's value while preserving metadata.
|
|
526
514
|
*
|
|
515
|
+
* With a `ttl` the entry expires that many seconds from now; without one it keeps its current
|
|
516
|
+
* expiry, in the store as well, so the store does not keep it past that (rounded up to a second).
|
|
517
|
+
*
|
|
527
518
|
* @param key - The key to update
|
|
528
519
|
* @param value - The new value
|
|
529
520
|
* @param options - Options (scope, ttl)
|
|
@@ -532,15 +523,18 @@ var RememberAccessor = class {
|
|
|
532
523
|
const scope = options.scope ?? "session";
|
|
533
524
|
const existing = await this.getEntry(key, { scope });
|
|
534
525
|
if (!existing) return false;
|
|
526
|
+
const now = Date.now();
|
|
527
|
+
const expiresAt = options.ttl ? now + options.ttl * 1e3 : existing.expiresAt;
|
|
535
528
|
const entry = {
|
|
536
529
|
...existing,
|
|
537
530
|
value,
|
|
538
|
-
updatedAt:
|
|
539
|
-
expiresAt
|
|
531
|
+
updatedAt: now,
|
|
532
|
+
expiresAt
|
|
540
533
|
};
|
|
541
534
|
const storageKey = this.buildStorageKey(key, scope);
|
|
542
535
|
const serialized = this.encryptionEnabled ? await encryptAndSerialize(entry, this.getKeySource(scope)) : JSON.stringify(entry);
|
|
543
|
-
|
|
536
|
+
const storageTtl = options.ttl ?? (expiresAt !== void 0 ? remainingSeconds(expiresAt, now) : void 0);
|
|
537
|
+
await this.store.setValue(storageKey, serialized, storageTtl);
|
|
544
538
|
return true;
|
|
545
539
|
}
|
|
546
540
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -658,6 +652,19 @@ var RememberAccessor = class {
|
|
|
658
652
|
toolName: scope === "tool" ? this.toolName : void 0
|
|
659
653
|
});
|
|
660
654
|
}
|
|
655
|
+
/** The entry stored under `storageKey`, if readable and unexpired; an expired one is deleted. */
|
|
656
|
+
async readEntry(storageKey, scope) {
|
|
657
|
+
const stored = await this.store.getValue(storageKey);
|
|
658
|
+
if (!stored) return void 0;
|
|
659
|
+
const raw = typeof stored === "string" ? stored : JSON.stringify(stored);
|
|
660
|
+
const entry = this.encryptionEnabled ? await deserializeAndDecrypt(raw, this.getKeySource(scope)) : this.parseEntry(raw);
|
|
661
|
+
if (!entry) return void 0;
|
|
662
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
663
|
+
await this.store.delete(storageKey);
|
|
664
|
+
return void 0;
|
|
665
|
+
}
|
|
666
|
+
return entry;
|
|
667
|
+
}
|
|
661
668
|
/**
|
|
662
669
|
* Parse a JSON entry (when encryption is disabled).
|
|
663
670
|
*/
|
|
@@ -907,7 +914,7 @@ var RememberRedisProvider = class {
|
|
|
907
914
|
}
|
|
908
915
|
}
|
|
909
916
|
const fullKey = this.keyPrefix + key;
|
|
910
|
-
const strValue =
|
|
917
|
+
const strValue = JSON.stringify(value);
|
|
911
918
|
if (ttlSeconds !== void 0 && ttlSeconds > 0) {
|
|
912
919
|
await this.client.set(fullKey, strValue, "EX", ttlSeconds);
|
|
913
920
|
} else {
|
|
@@ -922,7 +929,7 @@ var RememberRedisProvider = class {
|
|
|
922
929
|
*/
|
|
923
930
|
async setIfAbsent(key, value, ttlSeconds) {
|
|
924
931
|
const fullKey = this.keyPrefix + key;
|
|
925
|
-
const strValue =
|
|
932
|
+
const strValue = JSON.stringify(value);
|
|
926
933
|
const result = ttlSeconds !== void 0 && ttlSeconds > 0 ? await this.client.set(fullKey, strValue, "EX", ttlSeconds, "NX") : await this.client.set(fullKey, strValue, "NX");
|
|
927
934
|
return result === "OK";
|
|
928
935
|
}
|
|
@@ -999,8 +1006,11 @@ RememberRedisProvider = __decorateClass([
|
|
|
999
1006
|
|
|
1000
1007
|
// plugins/plugin-remember/src/providers/remember-vercel-kv.provider.ts
|
|
1001
1008
|
import { Provider as Provider4, ProviderScope as ProviderScope4 } from "@frontmcp/sdk";
|
|
1009
|
+
import { getEnv } from "@frontmcp/utils";
|
|
1002
1010
|
var RememberVercelKvProvider = class {
|
|
1003
|
-
|
|
1011
|
+
createClient;
|
|
1012
|
+
connection;
|
|
1013
|
+
client;
|
|
1004
1014
|
keyPrefix;
|
|
1005
1015
|
defaultTTL;
|
|
1006
1016
|
constructor(options = {}) {
|
|
@@ -1012,17 +1022,23 @@ var RememberVercelKvProvider = class {
|
|
|
1012
1022
|
`RememberVercelKvProvider: Both 'url' and 'token' must be provided together, or neither. Received: url=${hasUrl ? "provided" : "missing"}, token=${hasToken ? "provided" : "missing"}`
|
|
1013
1023
|
);
|
|
1014
1024
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
url: options.url,
|
|
1018
|
-
token: options.token
|
|
1019
|
-
});
|
|
1020
|
-
} else {
|
|
1021
|
-
this.kv = vercelKv.kv;
|
|
1022
|
-
}
|
|
1025
|
+
this.createClient = vercelKv.createClient;
|
|
1026
|
+
this.connection = { url: options.url, token: options.token };
|
|
1023
1027
|
this.keyPrefix = options.keyPrefix ?? "remember:";
|
|
1024
1028
|
this.defaultTTL = options.defaultTTL;
|
|
1025
1029
|
}
|
|
1030
|
+
/** Built on first use, like the module's `kv` singleton, which would JSON-parse what it reads. */
|
|
1031
|
+
get kv() {
|
|
1032
|
+
if (!this.client) {
|
|
1033
|
+
const url = this.connection.url ?? getEnv("KV_REST_API_URL");
|
|
1034
|
+
const token = this.connection.token ?? getEnv("KV_REST_API_TOKEN");
|
|
1035
|
+
if (!url || !token) {
|
|
1036
|
+
throw new Error("RememberVercelKvProvider: pass url and token, or set KV_REST_API_URL and KV_REST_API_TOKEN.");
|
|
1037
|
+
}
|
|
1038
|
+
this.client = this.createClient({ url, token, automaticDeserialization: false });
|
|
1039
|
+
}
|
|
1040
|
+
return this.client;
|
|
1041
|
+
}
|
|
1026
1042
|
prefixKey(key) {
|
|
1027
1043
|
return `${this.keyPrefix}${key}`;
|
|
1028
1044
|
}
|
|
@@ -1031,7 +1047,7 @@ var RememberVercelKvProvider = class {
|
|
|
1031
1047
|
*/
|
|
1032
1048
|
async setValue(key, value, ttlSeconds) {
|
|
1033
1049
|
const fullKey = this.prefixKey(key);
|
|
1034
|
-
const strValue =
|
|
1050
|
+
const strValue = JSON.stringify(value);
|
|
1035
1051
|
const ttl = ttlSeconds ?? this.defaultTTL;
|
|
1036
1052
|
if (ttl && ttl > 0) {
|
|
1037
1053
|
await this.kv.set(fullKey, strValue, { ex: ttl });
|
|
@@ -1047,7 +1063,7 @@ var RememberVercelKvProvider = class {
|
|
|
1047
1063
|
*/
|
|
1048
1064
|
async setIfAbsent(key, value, ttlSeconds) {
|
|
1049
1065
|
const fullKey = this.prefixKey(key);
|
|
1050
|
-
const strValue =
|
|
1066
|
+
const strValue = JSON.stringify(value);
|
|
1051
1067
|
const ttl = ttlSeconds ?? this.defaultTTL;
|
|
1052
1068
|
const result = ttl && ttl > 0 ? await this.kv.set(fullKey, strValue, { nx: true, ex: ttl }) : await this.kv.set(fullKey, strValue, { nx: true });
|
|
1053
1069
|
return result === "OK";
|
|
@@ -1058,15 +1074,12 @@ var RememberVercelKvProvider = class {
|
|
|
1058
1074
|
async getValue(key, defaultValue) {
|
|
1059
1075
|
const fullKey = this.prefixKey(key);
|
|
1060
1076
|
const raw = await this.kv.get(fullKey);
|
|
1061
|
-
if (raw === null
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
return raw;
|
|
1067
|
-
}
|
|
1077
|
+
if (raw === null) return defaultValue;
|
|
1078
|
+
try {
|
|
1079
|
+
return JSON.parse(raw);
|
|
1080
|
+
} catch {
|
|
1081
|
+
return raw;
|
|
1068
1082
|
}
|
|
1069
|
-
return raw;
|
|
1070
1083
|
}
|
|
1071
1084
|
/**
|
|
1072
1085
|
* Delete a key.
|
|
@@ -1100,7 +1113,7 @@ var RememberVercelKvProvider = class {
|
|
|
1100
1113
|
for (const key of keys) {
|
|
1101
1114
|
result.push(key.slice(this.keyPrefix.length));
|
|
1102
1115
|
}
|
|
1103
|
-
} while (cursor !== 0);
|
|
1116
|
+
} while (String(cursor) !== "0");
|
|
1104
1117
|
} catch {
|
|
1105
1118
|
try {
|
|
1106
1119
|
const keys = await this.kv.keys(searchPattern);
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-remember",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1-rc.1",
|
|
4
4
|
"description": "Remember plugin for FrontMCP - encrypted session memory with approval system for secure tool authorization",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.
|
|
53
|
-
"@frontmcp/utils": "1.
|
|
52
|
+
"@frontmcp/sdk": "1.9.1-rc.1",
|
|
53
|
+
"@frontmcp/utils": "1.9.1-rc.1",
|
|
54
54
|
"ioredis": "^5.8.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@frontmcp/lazy-zod": "1.
|
|
57
|
+
"@frontmcp/lazy-zod": "1.9.1-rc.1",
|
|
58
58
|
"@vercel/kv": "^2.0.0 || ^3.0.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
package/index.js
CHANGED
|
@@ -427,6 +427,9 @@ var STORAGE_LAYOUT_VERSION = "v2";
|
|
|
427
427
|
function encodeKeyPart(value) {
|
|
428
428
|
return encodeURIComponent(value);
|
|
429
429
|
}
|
|
430
|
+
function remainingSeconds(expiresAt, now) {
|
|
431
|
+
return Math.max(1, Math.ceil((expiresAt - now) / 1e3));
|
|
432
|
+
}
|
|
430
433
|
var RememberAccessor = class {
|
|
431
434
|
store;
|
|
432
435
|
ctx;
|
|
@@ -476,16 +479,8 @@ var RememberAccessor = class {
|
|
|
476
479
|
*/
|
|
477
480
|
async get(key, options = {}) {
|
|
478
481
|
const scope = options.scope ?? "session";
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
if (!raw) return options.defaultValue;
|
|
482
|
-
const entry = this.encryptionEnabled ? await deserializeAndDecrypt(raw, this.getKeySource(scope)) : this.parseEntry(raw);
|
|
483
|
-
if (!entry) return options.defaultValue;
|
|
484
|
-
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
485
|
-
await this.forget(key, { scope });
|
|
486
|
-
return options.defaultValue;
|
|
487
|
-
}
|
|
488
|
-
return entry.value;
|
|
482
|
+
const entry = await this.readEntry(this.buildStorageKey(key, scope), scope);
|
|
483
|
+
return entry ? entry.value : options.defaultValue;
|
|
489
484
|
}
|
|
490
485
|
/**
|
|
491
486
|
* Get the full entry including metadata.
|
|
@@ -496,16 +491,7 @@ var RememberAccessor = class {
|
|
|
496
491
|
*/
|
|
497
492
|
async getEntry(key, options = {}) {
|
|
498
493
|
const scope = options.scope ?? "session";
|
|
499
|
-
|
|
500
|
-
const raw = await this.store.getValue(storageKey);
|
|
501
|
-
if (!raw) return void 0;
|
|
502
|
-
const entry = this.encryptionEnabled ? await deserializeAndDecrypt(raw, this.getKeySource(scope)) : this.parseEntry(raw);
|
|
503
|
-
if (!entry) return void 0;
|
|
504
|
-
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
505
|
-
await this.forget(key, { scope });
|
|
506
|
-
return void 0;
|
|
507
|
-
}
|
|
508
|
-
return entry;
|
|
494
|
+
return this.readEntry(this.buildStorageKey(key, scope), scope);
|
|
509
495
|
}
|
|
510
496
|
/**
|
|
511
497
|
* Forget (delete) a value from memory.
|
|
@@ -519,19 +505,18 @@ var RememberAccessor = class {
|
|
|
519
505
|
await this.store.delete(storageKey);
|
|
520
506
|
}
|
|
521
507
|
/**
|
|
522
|
-
* Check if a key is remembered (
|
|
508
|
+
* Check if a key is remembered: true exactly when `get()` would return its value.
|
|
523
509
|
*
|
|
524
510
|
* @param key - The key to check
|
|
525
511
|
* @param options - Options (scope)
|
|
526
|
-
* @returns true if the key exists
|
|
512
|
+
* @returns true if the key exists and has not expired
|
|
527
513
|
*/
|
|
528
514
|
async knows(key, options = {}) {
|
|
529
515
|
const scope = options.scope ?? "session";
|
|
530
|
-
|
|
531
|
-
return this.store.exists(storageKey);
|
|
516
|
+
return await this.readEntry(this.buildStorageKey(key, scope), scope) !== void 0;
|
|
532
517
|
}
|
|
533
518
|
/**
|
|
534
|
-
* List all remembered keys for a scope.
|
|
519
|
+
* List all remembered keys for a scope: the keys `get()` would return a value for.
|
|
535
520
|
*
|
|
536
521
|
* @param options - Options (scope, pattern)
|
|
537
522
|
* @returns Array of keys (without the scope prefix)
|
|
@@ -539,13 +524,16 @@ var RememberAccessor = class {
|
|
|
539
524
|
async list(options = {}) {
|
|
540
525
|
const scope = options.scope ?? "session";
|
|
541
526
|
const scopePrefix = this.buildScopePrefix(scope);
|
|
542
|
-
const
|
|
543
|
-
const
|
|
544
|
-
return
|
|
527
|
+
const storageKeys = await this.store.keys(scopePrefix + (options.pattern ?? "*"));
|
|
528
|
+
const entries = await Promise.all(storageKeys.map((storageKey) => this.readEntry(storageKey, scope)));
|
|
529
|
+
return storageKeys.filter((_storageKey, index) => entries[index] !== void 0).map((storageKey) => storageKey.slice(scopePrefix.length));
|
|
545
530
|
}
|
|
546
531
|
/**
|
|
547
532
|
* Update an existing entry's value while preserving metadata.
|
|
548
533
|
*
|
|
534
|
+
* With a `ttl` the entry expires that many seconds from now; without one it keeps its current
|
|
535
|
+
* expiry, in the store as well, so the store does not keep it past that (rounded up to a second).
|
|
536
|
+
*
|
|
549
537
|
* @param key - The key to update
|
|
550
538
|
* @param value - The new value
|
|
551
539
|
* @param options - Options (scope, ttl)
|
|
@@ -554,15 +542,18 @@ var RememberAccessor = class {
|
|
|
554
542
|
const scope = options.scope ?? "session";
|
|
555
543
|
const existing = await this.getEntry(key, { scope });
|
|
556
544
|
if (!existing) return false;
|
|
545
|
+
const now = Date.now();
|
|
546
|
+
const expiresAt = options.ttl ? now + options.ttl * 1e3 : existing.expiresAt;
|
|
557
547
|
const entry = {
|
|
558
548
|
...existing,
|
|
559
549
|
value,
|
|
560
|
-
updatedAt:
|
|
561
|
-
expiresAt
|
|
550
|
+
updatedAt: now,
|
|
551
|
+
expiresAt
|
|
562
552
|
};
|
|
563
553
|
const storageKey = this.buildStorageKey(key, scope);
|
|
564
554
|
const serialized = this.encryptionEnabled ? await encryptAndSerialize(entry, this.getKeySource(scope)) : JSON.stringify(entry);
|
|
565
|
-
|
|
555
|
+
const storageTtl = options.ttl ?? (expiresAt !== void 0 ? remainingSeconds(expiresAt, now) : void 0);
|
|
556
|
+
await this.store.setValue(storageKey, serialized, storageTtl);
|
|
566
557
|
return true;
|
|
567
558
|
}
|
|
568
559
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -680,6 +671,19 @@ var RememberAccessor = class {
|
|
|
680
671
|
toolName: scope === "tool" ? this.toolName : void 0
|
|
681
672
|
});
|
|
682
673
|
}
|
|
674
|
+
/** The entry stored under `storageKey`, if readable and unexpired; an expired one is deleted. */
|
|
675
|
+
async readEntry(storageKey, scope) {
|
|
676
|
+
const stored = await this.store.getValue(storageKey);
|
|
677
|
+
if (!stored) return void 0;
|
|
678
|
+
const raw = typeof stored === "string" ? stored : JSON.stringify(stored);
|
|
679
|
+
const entry = this.encryptionEnabled ? await deserializeAndDecrypt(raw, this.getKeySource(scope)) : this.parseEntry(raw);
|
|
680
|
+
if (!entry) return void 0;
|
|
681
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
682
|
+
await this.store.delete(storageKey);
|
|
683
|
+
return void 0;
|
|
684
|
+
}
|
|
685
|
+
return entry;
|
|
686
|
+
}
|
|
683
687
|
/**
|
|
684
688
|
* Parse a JSON entry (when encryption is disabled).
|
|
685
689
|
*/
|
|
@@ -929,7 +933,7 @@ var RememberRedisProvider = class {
|
|
|
929
933
|
}
|
|
930
934
|
}
|
|
931
935
|
const fullKey = this.keyPrefix + key;
|
|
932
|
-
const strValue =
|
|
936
|
+
const strValue = JSON.stringify(value);
|
|
933
937
|
if (ttlSeconds !== void 0 && ttlSeconds > 0) {
|
|
934
938
|
await this.client.set(fullKey, strValue, "EX", ttlSeconds);
|
|
935
939
|
} else {
|
|
@@ -944,7 +948,7 @@ var RememberRedisProvider = class {
|
|
|
944
948
|
*/
|
|
945
949
|
async setIfAbsent(key, value, ttlSeconds) {
|
|
946
950
|
const fullKey = this.keyPrefix + key;
|
|
947
|
-
const strValue =
|
|
951
|
+
const strValue = JSON.stringify(value);
|
|
948
952
|
const result = ttlSeconds !== void 0 && ttlSeconds > 0 ? await this.client.set(fullKey, strValue, "EX", ttlSeconds, "NX") : await this.client.set(fullKey, strValue, "NX");
|
|
949
953
|
return result === "OK";
|
|
950
954
|
}
|
|
@@ -1021,8 +1025,11 @@ RememberRedisProvider = __decorateClass([
|
|
|
1021
1025
|
|
|
1022
1026
|
// plugins/plugin-remember/src/providers/remember-vercel-kv.provider.ts
|
|
1023
1027
|
var import_sdk5 = require("@frontmcp/sdk");
|
|
1028
|
+
var import_utils3 = require("@frontmcp/utils");
|
|
1024
1029
|
var RememberVercelKvProvider = class {
|
|
1025
|
-
|
|
1030
|
+
createClient;
|
|
1031
|
+
connection;
|
|
1032
|
+
client;
|
|
1026
1033
|
keyPrefix;
|
|
1027
1034
|
defaultTTL;
|
|
1028
1035
|
constructor(options = {}) {
|
|
@@ -1034,17 +1041,23 @@ var RememberVercelKvProvider = class {
|
|
|
1034
1041
|
`RememberVercelKvProvider: Both 'url' and 'token' must be provided together, or neither. Received: url=${hasUrl ? "provided" : "missing"}, token=${hasToken ? "provided" : "missing"}`
|
|
1035
1042
|
);
|
|
1036
1043
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
url: options.url,
|
|
1040
|
-
token: options.token
|
|
1041
|
-
});
|
|
1042
|
-
} else {
|
|
1043
|
-
this.kv = vercelKv.kv;
|
|
1044
|
-
}
|
|
1044
|
+
this.createClient = vercelKv.createClient;
|
|
1045
|
+
this.connection = { url: options.url, token: options.token };
|
|
1045
1046
|
this.keyPrefix = options.keyPrefix ?? "remember:";
|
|
1046
1047
|
this.defaultTTL = options.defaultTTL;
|
|
1047
1048
|
}
|
|
1049
|
+
/** Built on first use, like the module's `kv` singleton, which would JSON-parse what it reads. */
|
|
1050
|
+
get kv() {
|
|
1051
|
+
if (!this.client) {
|
|
1052
|
+
const url = this.connection.url ?? (0, import_utils3.getEnv)("KV_REST_API_URL");
|
|
1053
|
+
const token = this.connection.token ?? (0, import_utils3.getEnv)("KV_REST_API_TOKEN");
|
|
1054
|
+
if (!url || !token) {
|
|
1055
|
+
throw new Error("RememberVercelKvProvider: pass url and token, or set KV_REST_API_URL and KV_REST_API_TOKEN.");
|
|
1056
|
+
}
|
|
1057
|
+
this.client = this.createClient({ url, token, automaticDeserialization: false });
|
|
1058
|
+
}
|
|
1059
|
+
return this.client;
|
|
1060
|
+
}
|
|
1048
1061
|
prefixKey(key) {
|
|
1049
1062
|
return `${this.keyPrefix}${key}`;
|
|
1050
1063
|
}
|
|
@@ -1053,7 +1066,7 @@ var RememberVercelKvProvider = class {
|
|
|
1053
1066
|
*/
|
|
1054
1067
|
async setValue(key, value, ttlSeconds) {
|
|
1055
1068
|
const fullKey = this.prefixKey(key);
|
|
1056
|
-
const strValue =
|
|
1069
|
+
const strValue = JSON.stringify(value);
|
|
1057
1070
|
const ttl = ttlSeconds ?? this.defaultTTL;
|
|
1058
1071
|
if (ttl && ttl > 0) {
|
|
1059
1072
|
await this.kv.set(fullKey, strValue, { ex: ttl });
|
|
@@ -1069,7 +1082,7 @@ var RememberVercelKvProvider = class {
|
|
|
1069
1082
|
*/
|
|
1070
1083
|
async setIfAbsent(key, value, ttlSeconds) {
|
|
1071
1084
|
const fullKey = this.prefixKey(key);
|
|
1072
|
-
const strValue =
|
|
1085
|
+
const strValue = JSON.stringify(value);
|
|
1073
1086
|
const ttl = ttlSeconds ?? this.defaultTTL;
|
|
1074
1087
|
const result = ttl && ttl > 0 ? await this.kv.set(fullKey, strValue, { nx: true, ex: ttl }) : await this.kv.set(fullKey, strValue, { nx: true });
|
|
1075
1088
|
return result === "OK";
|
|
@@ -1080,15 +1093,12 @@ var RememberVercelKvProvider = class {
|
|
|
1080
1093
|
async getValue(key, defaultValue) {
|
|
1081
1094
|
const fullKey = this.prefixKey(key);
|
|
1082
1095
|
const raw = await this.kv.get(fullKey);
|
|
1083
|
-
if (raw === null
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
return raw;
|
|
1089
|
-
}
|
|
1096
|
+
if (raw === null) return defaultValue;
|
|
1097
|
+
try {
|
|
1098
|
+
return JSON.parse(raw);
|
|
1099
|
+
} catch {
|
|
1100
|
+
return raw;
|
|
1090
1101
|
}
|
|
1091
|
-
return raw;
|
|
1092
1102
|
}
|
|
1093
1103
|
/**
|
|
1094
1104
|
* Delete a key.
|
|
@@ -1122,7 +1132,7 @@ var RememberVercelKvProvider = class {
|
|
|
1122
1132
|
for (const key of keys) {
|
|
1123
1133
|
result.push(key.slice(this.keyPrefix.length));
|
|
1124
1134
|
}
|
|
1125
|
-
} while (cursor !== 0);
|
|
1135
|
+
} while (String(cursor) !== "0");
|
|
1126
1136
|
} catch {
|
|
1127
1137
|
try {
|
|
1128
1138
|
const keys = await this.kv.keys(searchPattern);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-remember",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1-rc.1",
|
|
4
4
|
"description": "Remember plugin for FrontMCP - encrypted session memory with approval system for secure tool authorization",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.
|
|
53
|
-
"@frontmcp/utils": "1.
|
|
52
|
+
"@frontmcp/sdk": "1.9.1-rc.1",
|
|
53
|
+
"@frontmcp/utils": "1.9.1-rc.1",
|
|
54
54
|
"ioredis": "^5.8.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@frontmcp/lazy-zod": "1.
|
|
57
|
+
"@frontmcp/lazy-zod": "1.9.1-rc.1",
|
|
58
58
|
"@vercel/kv": "^2.0.0 || ^3.0.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
|
@@ -66,15 +66,15 @@ export declare class RememberAccessor {
|
|
|
66
66
|
*/
|
|
67
67
|
forget(key: string, options?: RememberForgetOptions): Promise<void>;
|
|
68
68
|
/**
|
|
69
|
-
* Check if a key is remembered (
|
|
69
|
+
* Check if a key is remembered: true exactly when `get()` would return its value.
|
|
70
70
|
*
|
|
71
71
|
* @param key - The key to check
|
|
72
72
|
* @param options - Options (scope)
|
|
73
|
-
* @returns true if the key exists
|
|
73
|
+
* @returns true if the key exists and has not expired
|
|
74
74
|
*/
|
|
75
75
|
knows(key: string, options?: RememberKnowsOptions): Promise<boolean>;
|
|
76
76
|
/**
|
|
77
|
-
* List all remembered keys for a scope.
|
|
77
|
+
* List all remembered keys for a scope: the keys `get()` would return a value for.
|
|
78
78
|
*
|
|
79
79
|
* @param options - Options (scope, pattern)
|
|
80
80
|
* @returns Array of keys (without the scope prefix)
|
|
@@ -83,6 +83,9 @@ export declare class RememberAccessor {
|
|
|
83
83
|
/**
|
|
84
84
|
* Update an existing entry's value while preserving metadata.
|
|
85
85
|
*
|
|
86
|
+
* With a `ttl` the entry expires that many seconds from now; without one it keeps its current
|
|
87
|
+
* expiry, in the store as well, so the store does not keep it past that (rounded up to a second).
|
|
88
|
+
*
|
|
86
89
|
* @param key - The key to update
|
|
87
90
|
* @param value - The new value
|
|
88
91
|
* @param options - Options (scope, ttl)
|
|
@@ -149,6 +152,8 @@ export declare class RememberAccessor {
|
|
|
149
152
|
* Get the encryption key source for a scope.
|
|
150
153
|
*/
|
|
151
154
|
private getKeySource;
|
|
155
|
+
/** The entry stored under `storageKey`, if readable and unexpired; an expired one is deleted. */
|
|
156
|
+
private readEntry;
|
|
152
157
|
/**
|
|
153
158
|
* Parse a JSON entry (when encryption is disabled).
|
|
154
159
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remember-accessor.provider.d.ts","sourceRoot":"","sources":["../../src/providers/remember-accessor.provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AAKvB,OAAO,KAAK,EACV,aAAa,EACb,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"remember-accessor.provider.d.ts","sourceRoot":"","sources":["../../src/providers/remember-accessor.provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,10 +17,14 @@ export interface RememberVercelKvProviderOptions {
|
|
|
17
17
|
* Provides serverless-compatible, edge-ready storage.
|
|
18
18
|
*/
|
|
19
19
|
export default class RememberVercelKvProvider implements RememberStoreInterface {
|
|
20
|
-
private
|
|
20
|
+
private readonly createClient;
|
|
21
|
+
private readonly connection;
|
|
22
|
+
private client?;
|
|
21
23
|
private readonly keyPrefix;
|
|
22
24
|
private readonly defaultTTL?;
|
|
23
25
|
constructor(options?: RememberVercelKvProviderOptions);
|
|
26
|
+
/** Built on first use, like the module's `kv` singleton, which would JSON-parse what it reads. */
|
|
27
|
+
private get kv();
|
|
24
28
|
private prefixKey;
|
|
25
29
|
/**
|
|
26
30
|
* Store a value with optional TTL.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remember-vercel-kv.provider.d.ts","sourceRoot":"","sources":["../../src/providers/remember-vercel-kv.provider.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"remember-vercel-kv.provider.d.ts","sourceRoot":"","sources":["../../src/providers/remember-vercel-kv.provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAmBzE;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC9C,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AAMH,MAAM,CAAC,OAAO,OAAO,wBAAyB,YAAW,sBAAsB;IAC7E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmC;IAC9D,OAAO,CAAC,MAAM,CAAC,CAAiB;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;gBAEzB,OAAO,GAAE,+BAAoC;IAoBzD,kGAAkG;IAClG,OAAO,KAAK,EAAE,GAUb;IAED,OAAO,CAAC,SAAS;IAIjB;;OAEG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/E;;;;;OAKG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAarF;;OAEG;IACG,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAalF;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3C;;;OAGG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAmC/C;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|