@lov3kaizen/agentsea-memory 0.5.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/LICENSE +21 -0
- package/README.md +450 -0
- package/dist/chunk-GACX3FPR.js +1402 -0
- package/dist/chunk-M44NB53O.js +1226 -0
- package/dist/chunk-MQDWBPZU.js +972 -0
- package/dist/chunk-TPC7MYWK.js +1495 -0
- package/dist/chunk-XD2CQGSD.js +1540 -0
- package/dist/chunk-YI7RPDEV.js +1215 -0
- package/dist/core.types-lkxKv-bW.d.cts +242 -0
- package/dist/core.types-lkxKv-bW.d.ts +242 -0
- package/dist/debug/index.cjs +1248 -0
- package/dist/debug/index.d.cts +3 -0
- package/dist/debug/index.d.ts +3 -0
- package/dist/debug/index.js +20 -0
- package/dist/index-7SsAJ4et.d.ts +525 -0
- package/dist/index-BGxYqpFb.d.cts +601 -0
- package/dist/index-BX62efZu.d.ts +565 -0
- package/dist/index-Bbc3COw0.d.cts +748 -0
- package/dist/index-Bczz1Eyk.d.ts +637 -0
- package/dist/index-C7pEiT8L.d.cts +637 -0
- package/dist/index-CHetLTb0.d.ts +389 -0
- package/dist/index-CloeiFyx.d.ts +748 -0
- package/dist/index-DNOhq-3y.d.cts +525 -0
- package/dist/index-Da-M8FOV.d.cts +389 -0
- package/dist/index-Dy8UjRFz.d.cts +565 -0
- package/dist/index-aVcITW0B.d.ts +601 -0
- package/dist/index.cjs +8554 -0
- package/dist/index.d.cts +293 -0
- package/dist/index.d.ts +293 -0
- package/dist/index.js +742 -0
- package/dist/processing/index.cjs +1575 -0
- package/dist/processing/index.d.cts +2 -0
- package/dist/processing/index.d.ts +2 -0
- package/dist/processing/index.js +24 -0
- package/dist/retrieval/index.cjs +1262 -0
- package/dist/retrieval/index.d.cts +2 -0
- package/dist/retrieval/index.d.ts +2 -0
- package/dist/retrieval/index.js +26 -0
- package/dist/sharing/index.cjs +1003 -0
- package/dist/sharing/index.d.cts +3 -0
- package/dist/sharing/index.d.ts +3 -0
- package/dist/sharing/index.js +16 -0
- package/dist/stores/index.cjs +1445 -0
- package/dist/stores/index.d.cts +2 -0
- package/dist/stores/index.d.ts +2 -0
- package/dist/stores/index.js +20 -0
- package/dist/structures/index.cjs +1530 -0
- package/dist/structures/index.d.cts +3 -0
- package/dist/structures/index.d.ts +3 -0
- package/dist/structures/index.js +24 -0
- package/package.json +141 -0
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
// src/sharing/SharedMemory.ts
|
|
2
|
+
import { EventEmitter } from "eventemitter3";
|
|
3
|
+
var SharedMemory = class extends EventEmitter {
|
|
4
|
+
store;
|
|
5
|
+
config;
|
|
6
|
+
sharedState = /* @__PURE__ */ new Map();
|
|
7
|
+
agentStates = /* @__PURE__ */ new Map();
|
|
8
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
9
|
+
// key -> agent IDs
|
|
10
|
+
lockManager = /* @__PURE__ */ new Map();
|
|
11
|
+
constructor(store, config = {}) {
|
|
12
|
+
super();
|
|
13
|
+
this.store = store;
|
|
14
|
+
this.config = {
|
|
15
|
+
conflictResolution: config.conflictResolution ?? "last-write-wins",
|
|
16
|
+
syncInterval: config.syncInterval ?? 5e3,
|
|
17
|
+
enableLocking: config.enableLocking ?? true,
|
|
18
|
+
lockTimeout: config.lockTimeout ?? 3e4,
|
|
19
|
+
maxSharedEntries: config.maxSharedEntries ?? 1e3
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Set a shared value
|
|
24
|
+
*/
|
|
25
|
+
async set(agentId, key, value) {
|
|
26
|
+
if (this.config.enableLocking && this.isLocked(key) && !this.hasLock(key, agentId)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const existing = this.sharedState.get(key);
|
|
30
|
+
if (existing && existing.writtenBy !== agentId) {
|
|
31
|
+
const resolved = this.resolveConflict(key, existing, { agentId, value });
|
|
32
|
+
if (!resolved) {
|
|
33
|
+
this.emit("conflict", key, [existing.writtenBy, agentId]);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const sharedValue = {
|
|
38
|
+
value,
|
|
39
|
+
writtenBy: agentId,
|
|
40
|
+
writtenAt: Date.now(),
|
|
41
|
+
version: (existing?.version ?? 0) + 1,
|
|
42
|
+
readers: existing?.readers ?? /* @__PURE__ */ new Set()
|
|
43
|
+
};
|
|
44
|
+
this.sharedState.set(key, sharedValue);
|
|
45
|
+
await this.persistSharedValue(key, sharedValue);
|
|
46
|
+
this.emit("write", agentId, key, value);
|
|
47
|
+
this.notifySubscribers(key, agentId, value);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get a shared value
|
|
52
|
+
*/
|
|
53
|
+
async get(agentId, key) {
|
|
54
|
+
const shared = this.sharedState.get(key);
|
|
55
|
+
if (!shared) {
|
|
56
|
+
const loaded = await this.loadSharedValue(key);
|
|
57
|
+
if (!loaded) return void 0;
|
|
58
|
+
this.sharedState.set(key, loaded);
|
|
59
|
+
return loaded.value;
|
|
60
|
+
}
|
|
61
|
+
shared.readers.add(agentId);
|
|
62
|
+
this.emit("read", agentId, key);
|
|
63
|
+
return shared.value;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Delete a shared value
|
|
67
|
+
*/
|
|
68
|
+
async delete(agentId, key) {
|
|
69
|
+
if (this.config.enableLocking && this.isLocked(key) && !this.hasLock(key, agentId)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const existed = this.sharedState.delete(key);
|
|
73
|
+
if (existed) {
|
|
74
|
+
await this.store.delete(`shared:${key}`);
|
|
75
|
+
this.emit("delete", agentId, key);
|
|
76
|
+
}
|
|
77
|
+
return existed;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Set agent-specific state (not shared)
|
|
81
|
+
*/
|
|
82
|
+
setAgentState(agentId, key, value) {
|
|
83
|
+
if (!this.agentStates.has(agentId)) {
|
|
84
|
+
this.agentStates.set(agentId, /* @__PURE__ */ new Map());
|
|
85
|
+
}
|
|
86
|
+
this.agentStates.get(agentId).set(key, value);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get agent-specific state
|
|
90
|
+
*/
|
|
91
|
+
getAgentState(agentId, key) {
|
|
92
|
+
return this.agentStates.get(agentId)?.get(key);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get all agent state
|
|
96
|
+
*/
|
|
97
|
+
getAllAgentState(agentId) {
|
|
98
|
+
return new Map(this.agentStates.get(agentId) ?? []);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Subscribe to changes on a key
|
|
102
|
+
*/
|
|
103
|
+
subscribe(agentId, key) {
|
|
104
|
+
if (!this.subscriptions.has(key)) {
|
|
105
|
+
this.subscriptions.set(key, /* @__PURE__ */ new Set());
|
|
106
|
+
}
|
|
107
|
+
this.subscriptions.get(key).add(agentId);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Unsubscribe from changes
|
|
111
|
+
*/
|
|
112
|
+
unsubscribe(agentId, key) {
|
|
113
|
+
this.subscriptions.get(key)?.delete(agentId);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Acquire a lock on a key
|
|
117
|
+
*/
|
|
118
|
+
acquireLock(agentId, key) {
|
|
119
|
+
if (!this.config.enableLocking) return true;
|
|
120
|
+
const lock = this.lockManager.get(key);
|
|
121
|
+
if (lock && lock.expiresAt > Date.now() && lock.agentId !== agentId) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
this.lockManager.set(key, {
|
|
125
|
+
agentId,
|
|
126
|
+
expiresAt: Date.now() + this.config.lockTimeout
|
|
127
|
+
});
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Release a lock
|
|
132
|
+
*/
|
|
133
|
+
releaseLock(agentId, key) {
|
|
134
|
+
const lock = this.lockManager.get(key);
|
|
135
|
+
if (lock?.agentId === agentId) {
|
|
136
|
+
this.lockManager.delete(key);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check if key is locked
|
|
143
|
+
*/
|
|
144
|
+
isLocked(key) {
|
|
145
|
+
const lock = this.lockManager.get(key);
|
|
146
|
+
return lock !== void 0 && lock.expiresAt > Date.now();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Check if agent has lock
|
|
150
|
+
*/
|
|
151
|
+
hasLock(key, agentId) {
|
|
152
|
+
const lock = this.lockManager.get(key);
|
|
153
|
+
return lock?.agentId === agentId && lock.expiresAt > Date.now();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Share memories from one agent to shared space
|
|
157
|
+
*/
|
|
158
|
+
async shareMemories(agentId, entries) {
|
|
159
|
+
let added = 0;
|
|
160
|
+
let updated = 0;
|
|
161
|
+
let conflicts = 0;
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
const key = `memory:${entry.id}`;
|
|
164
|
+
const existing = this.sharedState.get(key);
|
|
165
|
+
if (existing) {
|
|
166
|
+
if (existing.writtenBy !== agentId) {
|
|
167
|
+
if (this.config.conflictResolution === "last-write-wins") {
|
|
168
|
+
await this.set(agentId, key, entry);
|
|
169
|
+
updated++;
|
|
170
|
+
} else {
|
|
171
|
+
conflicts++;
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
await this.set(agentId, key, entry);
|
|
175
|
+
updated++;
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
await this.set(agentId, key, entry);
|
|
179
|
+
added++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
this.emit("sync", agentId, entries);
|
|
183
|
+
return {
|
|
184
|
+
added,
|
|
185
|
+
updated,
|
|
186
|
+
conflicts,
|
|
187
|
+
timestamp: Date.now()
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get shared memories
|
|
192
|
+
*/
|
|
193
|
+
getSharedMemories(_agentId) {
|
|
194
|
+
const memories = [];
|
|
195
|
+
for (const [key, shared] of this.sharedState) {
|
|
196
|
+
if (key.startsWith("memory:")) {
|
|
197
|
+
const entry = shared.value;
|
|
198
|
+
memories.push(entry);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return memories;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Sync with store
|
|
205
|
+
*/
|
|
206
|
+
async sync() {
|
|
207
|
+
for (const [key, value] of this.sharedState) {
|
|
208
|
+
await this.persistSharedValue(key, value);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get all shared keys
|
|
213
|
+
*/
|
|
214
|
+
getSharedKeys() {
|
|
215
|
+
return Array.from(this.sharedState.keys());
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get metadata for a shared value
|
|
219
|
+
*/
|
|
220
|
+
getMetadata(key) {
|
|
221
|
+
const shared = this.sharedState.get(key);
|
|
222
|
+
if (!shared) return void 0;
|
|
223
|
+
return {
|
|
224
|
+
writtenBy: shared.writtenBy,
|
|
225
|
+
writtenAt: shared.writtenAt,
|
|
226
|
+
version: shared.version,
|
|
227
|
+
readers: shared.readers
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Resolve conflict between values
|
|
232
|
+
*/
|
|
233
|
+
resolveConflict(key, existing, incoming) {
|
|
234
|
+
switch (this.config.conflictResolution) {
|
|
235
|
+
case "last-write-wins":
|
|
236
|
+
return true;
|
|
237
|
+
case "first-write-wins":
|
|
238
|
+
return false;
|
|
239
|
+
case "merge":
|
|
240
|
+
if (typeof existing.value === "object" && typeof incoming.value === "object") {
|
|
241
|
+
this.sharedState.set(key, {
|
|
242
|
+
...existing,
|
|
243
|
+
value: {
|
|
244
|
+
...existing.value,
|
|
245
|
+
...incoming.value
|
|
246
|
+
},
|
|
247
|
+
writtenBy: incoming.agentId,
|
|
248
|
+
writtenAt: Date.now(),
|
|
249
|
+
version: existing.version + 1
|
|
250
|
+
});
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
// Fall back to last-write-wins for non-objects
|
|
255
|
+
default:
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Notify subscribers of changes
|
|
261
|
+
*/
|
|
262
|
+
notifySubscribers(key, writerId, value) {
|
|
263
|
+
const subscribers = this.subscriptions.get(key);
|
|
264
|
+
if (!subscribers) return;
|
|
265
|
+
for (const agentId of subscribers) {
|
|
266
|
+
if (agentId !== writerId) {
|
|
267
|
+
this.emit("write", writerId, key, value);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Persist shared value to store
|
|
273
|
+
*/
|
|
274
|
+
async persistSharedValue(key, value) {
|
|
275
|
+
await this.store.add({
|
|
276
|
+
id: `shared:${key}`,
|
|
277
|
+
content: JSON.stringify(value),
|
|
278
|
+
type: "context",
|
|
279
|
+
importance: 0.5,
|
|
280
|
+
metadata: {
|
|
281
|
+
source: "agent",
|
|
282
|
+
confidence: 1,
|
|
283
|
+
sharedKey: key,
|
|
284
|
+
writtenBy: value.writtenBy,
|
|
285
|
+
version: value.version
|
|
286
|
+
},
|
|
287
|
+
timestamp: value.writtenAt,
|
|
288
|
+
accessCount: 0,
|
|
289
|
+
createdAt: value.writtenAt,
|
|
290
|
+
updatedAt: value.writtenAt
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Load shared value from store
|
|
295
|
+
*/
|
|
296
|
+
async loadSharedValue(key) {
|
|
297
|
+
const entry = await this.store.get(`shared:${key}`);
|
|
298
|
+
if (!entry) return null;
|
|
299
|
+
try {
|
|
300
|
+
const data = JSON.parse(entry.content);
|
|
301
|
+
return {
|
|
302
|
+
...data,
|
|
303
|
+
readers: new Set(data.readers ?? [])
|
|
304
|
+
};
|
|
305
|
+
} catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get statistics
|
|
311
|
+
*/
|
|
312
|
+
getStats() {
|
|
313
|
+
let totalSubscriptions = 0;
|
|
314
|
+
for (const subs of this.subscriptions.values()) {
|
|
315
|
+
totalSubscriptions += subs.size;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
sharedEntries: this.sharedState.size,
|
|
319
|
+
agentCount: this.agentStates.size,
|
|
320
|
+
activeLocks: Array.from(this.lockManager.values()).filter(
|
|
321
|
+
(l) => l.expiresAt > Date.now()
|
|
322
|
+
).length,
|
|
323
|
+
totalSubscriptions
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
function createSharedMemory(store, config) {
|
|
328
|
+
return new SharedMemory(store, config);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/sharing/Namespaces.ts
|
|
332
|
+
import { EventEmitter as EventEmitter2 } from "eventemitter3";
|
|
333
|
+
var NamespaceManager = class extends EventEmitter2 {
|
|
334
|
+
store;
|
|
335
|
+
namespaces = /* @__PURE__ */ new Map();
|
|
336
|
+
defaultNamespace = "default";
|
|
337
|
+
constructor(store, _config) {
|
|
338
|
+
super();
|
|
339
|
+
this.store = store;
|
|
340
|
+
this.createNamespace("default", {
|
|
341
|
+
description: "Default namespace",
|
|
342
|
+
settings: { accessLevel: "public" }
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Create a new namespace
|
|
347
|
+
*/
|
|
348
|
+
createNamespace(name, options = {}) {
|
|
349
|
+
if (this.namespaces.has(name)) {
|
|
350
|
+
throw new Error(`Namespace "${name}" already exists`);
|
|
351
|
+
}
|
|
352
|
+
const metadata = {
|
|
353
|
+
name,
|
|
354
|
+
description: options.description,
|
|
355
|
+
owner: options.owner,
|
|
356
|
+
createdAt: Date.now(),
|
|
357
|
+
updatedAt: Date.now(),
|
|
358
|
+
entryCount: 0,
|
|
359
|
+
tags: options.tags,
|
|
360
|
+
settings: {
|
|
361
|
+
accessLevel: "private",
|
|
362
|
+
...options.settings
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
this.namespaces.set(name, metadata);
|
|
366
|
+
this.emit("created", metadata);
|
|
367
|
+
return metadata;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Delete a namespace
|
|
371
|
+
*/
|
|
372
|
+
async deleteNamespace(name, deleteEntries = false) {
|
|
373
|
+
if (name === "default") {
|
|
374
|
+
throw new Error("Cannot delete default namespace");
|
|
375
|
+
}
|
|
376
|
+
if (!this.namespaces.has(name)) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
if (deleteEntries) {
|
|
380
|
+
await this.store.clear({ namespace: name });
|
|
381
|
+
}
|
|
382
|
+
this.namespaces.delete(name);
|
|
383
|
+
this.emit("deleted", name);
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get namespace metadata
|
|
388
|
+
*/
|
|
389
|
+
getNamespace(name) {
|
|
390
|
+
return this.namespaces.get(name);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* List all namespaces
|
|
394
|
+
*/
|
|
395
|
+
listNamespaces() {
|
|
396
|
+
return Array.from(this.namespaces.values());
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Update namespace settings
|
|
400
|
+
*/
|
|
401
|
+
updateNamespace(name, updates) {
|
|
402
|
+
const existing = this.namespaces.get(name);
|
|
403
|
+
if (!existing) return null;
|
|
404
|
+
const updated = {
|
|
405
|
+
...existing,
|
|
406
|
+
...updates,
|
|
407
|
+
settings: {
|
|
408
|
+
...existing.settings,
|
|
409
|
+
...updates.settings
|
|
410
|
+
},
|
|
411
|
+
updatedAt: Date.now()
|
|
412
|
+
};
|
|
413
|
+
this.namespaces.set(name, updated);
|
|
414
|
+
this.emit("updated", updated);
|
|
415
|
+
return updated;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Check if agent can access namespace
|
|
419
|
+
*/
|
|
420
|
+
canAccess(name, agentId, action = "read") {
|
|
421
|
+
const namespace = this.namespaces.get(name);
|
|
422
|
+
if (!namespace) return false;
|
|
423
|
+
const { settings } = namespace;
|
|
424
|
+
if (settings.accessLevel === "public") {
|
|
425
|
+
return action === "read" || !settings.readOnly;
|
|
426
|
+
}
|
|
427
|
+
if (settings.accessLevel === "private") {
|
|
428
|
+
return namespace.owner === agentId;
|
|
429
|
+
}
|
|
430
|
+
if (settings.accessLevel === "restricted") {
|
|
431
|
+
const isAllowed = settings.allowedAgents?.includes(agentId) ?? false;
|
|
432
|
+
if (!isAllowed) {
|
|
433
|
+
this.emit("accessDenied", name, agentId, action);
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
return action === "read" || !settings.readOnly;
|
|
437
|
+
}
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Add entry to namespace
|
|
442
|
+
*/
|
|
443
|
+
async addEntry(namespace, entry, agentId) {
|
|
444
|
+
if (!this.canAccess(namespace, agentId, "write")) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
const ns = this.namespaces.get(namespace);
|
|
448
|
+
if (!ns) return null;
|
|
449
|
+
if (ns.settings.maxEntries && ns.entryCount >= ns.settings.maxEntries) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
const fullEntry = {
|
|
453
|
+
...entry,
|
|
454
|
+
metadata: {
|
|
455
|
+
source: entry.metadata?.source ?? "explicit",
|
|
456
|
+
confidence: entry.metadata?.confidence ?? 1,
|
|
457
|
+
...entry.metadata,
|
|
458
|
+
namespace
|
|
459
|
+
},
|
|
460
|
+
expiresAt: ns.settings.ttl ? Date.now() + ns.settings.ttl : entry.expiresAt
|
|
461
|
+
};
|
|
462
|
+
await this.store.add(fullEntry);
|
|
463
|
+
ns.entryCount++;
|
|
464
|
+
ns.updatedAt = Date.now();
|
|
465
|
+
this.emit("entryAdded", namespace, fullEntry);
|
|
466
|
+
return fullEntry.id;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Query entries in namespace
|
|
470
|
+
*/
|
|
471
|
+
async queryEntries(namespace, agentId, options) {
|
|
472
|
+
if (!this.canAccess(namespace, agentId, "read")) {
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
const { entries } = await this.store.query({
|
|
476
|
+
namespace,
|
|
477
|
+
query: options?.query,
|
|
478
|
+
limit: options?.limit,
|
|
479
|
+
types: options?.types
|
|
480
|
+
});
|
|
481
|
+
return entries;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Delete entry from namespace
|
|
485
|
+
*/
|
|
486
|
+
async deleteEntry(namespace, entryId, agentId) {
|
|
487
|
+
if (!this.canAccess(namespace, agentId, "write")) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
const result = await this.store.delete(entryId);
|
|
491
|
+
if (result) {
|
|
492
|
+
const ns = this.namespaces.get(namespace);
|
|
493
|
+
if (ns) {
|
|
494
|
+
ns.entryCount = Math.max(0, ns.entryCount - 1);
|
|
495
|
+
ns.updatedAt = Date.now();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Move entries between namespaces
|
|
502
|
+
*/
|
|
503
|
+
async moveEntries(fromNamespace, toNamespace, entryIds, agentId) {
|
|
504
|
+
if (!this.canAccess(fromNamespace, agentId, "write")) return 0;
|
|
505
|
+
if (!this.canAccess(toNamespace, agentId, "write")) return 0;
|
|
506
|
+
let moved = 0;
|
|
507
|
+
for (const id of entryIds) {
|
|
508
|
+
const entry = await this.store.get(id);
|
|
509
|
+
if (entry && entry.metadata.namespace === fromNamespace) {
|
|
510
|
+
await this.store.update(id, {
|
|
511
|
+
metadata: { ...entry.metadata, namespace: toNamespace }
|
|
512
|
+
});
|
|
513
|
+
moved++;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const fromNs = this.namespaces.get(fromNamespace);
|
|
517
|
+
const toNs = this.namespaces.get(toNamespace);
|
|
518
|
+
if (fromNs) fromNs.entryCount -= moved;
|
|
519
|
+
if (toNs) toNs.entryCount += moved;
|
|
520
|
+
return moved;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Copy entries between namespaces
|
|
524
|
+
*/
|
|
525
|
+
async copyEntries(fromNamespace, toNamespace, entryIds, agentId) {
|
|
526
|
+
if (!this.canAccess(fromNamespace, agentId, "read")) return 0;
|
|
527
|
+
if (!this.canAccess(toNamespace, agentId, "write")) return 0;
|
|
528
|
+
let copied = 0;
|
|
529
|
+
for (const id of entryIds) {
|
|
530
|
+
const entry = await this.store.get(id);
|
|
531
|
+
if (entry && entry.metadata.namespace === fromNamespace) {
|
|
532
|
+
const newEntry = {
|
|
533
|
+
...entry,
|
|
534
|
+
id: `${entry.id}-copy-${Date.now()}`,
|
|
535
|
+
metadata: { ...entry.metadata, namespace: toNamespace },
|
|
536
|
+
createdAt: Date.now(),
|
|
537
|
+
updatedAt: Date.now()
|
|
538
|
+
};
|
|
539
|
+
await this.store.add(newEntry);
|
|
540
|
+
copied++;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const toNs = this.namespaces.get(toNamespace);
|
|
544
|
+
if (toNs) toNs.entryCount += copied;
|
|
545
|
+
return copied;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Grant access to namespace
|
|
549
|
+
*/
|
|
550
|
+
grantAccess(namespace, agentId) {
|
|
551
|
+
const ns = this.namespaces.get(namespace);
|
|
552
|
+
if (!ns) return false;
|
|
553
|
+
if (!ns.settings.allowedAgents) {
|
|
554
|
+
ns.settings.allowedAgents = [];
|
|
555
|
+
}
|
|
556
|
+
if (!ns.settings.allowedAgents.includes(agentId)) {
|
|
557
|
+
ns.settings.allowedAgents.push(agentId);
|
|
558
|
+
}
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Revoke access from namespace
|
|
563
|
+
*/
|
|
564
|
+
revokeAccess(namespace, agentId) {
|
|
565
|
+
const ns = this.namespaces.get(namespace);
|
|
566
|
+
if (!ns || !ns.settings.allowedAgents) return false;
|
|
567
|
+
const index = ns.settings.allowedAgents.indexOf(agentId);
|
|
568
|
+
if (index !== -1) {
|
|
569
|
+
ns.settings.allowedAgents.splice(index, 1);
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Set default namespace
|
|
576
|
+
*/
|
|
577
|
+
setDefaultNamespace(name) {
|
|
578
|
+
if (!this.namespaces.has(name)) return false;
|
|
579
|
+
this.defaultNamespace = name;
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Get default namespace
|
|
584
|
+
*/
|
|
585
|
+
getDefaultNamespace() {
|
|
586
|
+
return this.defaultNamespace;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Get namespace statistics
|
|
590
|
+
*/
|
|
591
|
+
async getNamespaceStats(name) {
|
|
592
|
+
if (!this.namespaces.has(name)) return null;
|
|
593
|
+
const { entries, total } = await this.store.query({
|
|
594
|
+
namespace: name,
|
|
595
|
+
limit: 1e4
|
|
596
|
+
});
|
|
597
|
+
const typeDistribution = {};
|
|
598
|
+
let oldestEntry = null;
|
|
599
|
+
let newestEntry = null;
|
|
600
|
+
let totalSize = 0;
|
|
601
|
+
for (const entry of entries) {
|
|
602
|
+
typeDistribution[entry.type] = (typeDistribution[entry.type] ?? 0) + 1;
|
|
603
|
+
totalSize += entry.content.length;
|
|
604
|
+
if (oldestEntry === null || entry.timestamp < oldestEntry) {
|
|
605
|
+
oldestEntry = entry.timestamp;
|
|
606
|
+
}
|
|
607
|
+
if (newestEntry === null || entry.timestamp > newestEntry) {
|
|
608
|
+
newestEntry = entry.timestamp;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
entryCount: total,
|
|
613
|
+
totalSize,
|
|
614
|
+
oldestEntry,
|
|
615
|
+
newestEntry,
|
|
616
|
+
typeDistribution
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
function createNamespaceManager(store, config) {
|
|
621
|
+
return new NamespaceManager(store, config);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/sharing/AccessControl.ts
|
|
625
|
+
import { EventEmitter as EventEmitter3 } from "eventemitter3";
|
|
626
|
+
var AccessControl = class extends EventEmitter3 {
|
|
627
|
+
config;
|
|
628
|
+
rules = /* @__PURE__ */ new Map();
|
|
629
|
+
accessLog = [];
|
|
630
|
+
maxLogSize = 1e3;
|
|
631
|
+
constructor(config = {}) {
|
|
632
|
+
super();
|
|
633
|
+
this.config = {
|
|
634
|
+
roles: config.roles ?? {},
|
|
635
|
+
defaultRole: config.defaultRole ?? "user",
|
|
636
|
+
adminUsers: config.adminUsers ?? [],
|
|
637
|
+
defaultPermission: config.defaultPermission ?? "read",
|
|
638
|
+
enableAuditLog: config.enableAuditLog ?? true,
|
|
639
|
+
strictMode: config.strictMode ?? false,
|
|
640
|
+
maxRulesPerAgent: config.maxRulesPerAgent ?? 100
|
|
641
|
+
};
|
|
642
|
+
if (!this.config.strictMode) {
|
|
643
|
+
this.addRule({
|
|
644
|
+
id: "default-read",
|
|
645
|
+
agentId: "*",
|
|
646
|
+
resource: "*",
|
|
647
|
+
permission: this.config.defaultPermission,
|
|
648
|
+
createdBy: "system",
|
|
649
|
+
createdAt: Date.now()
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Add a permission rule
|
|
655
|
+
*/
|
|
656
|
+
addRule(rule) {
|
|
657
|
+
if (rule.agentId !== "*") {
|
|
658
|
+
const agentRules = Array.from(this.rules.values()).filter(
|
|
659
|
+
(r) => r.agentId === rule.agentId
|
|
660
|
+
);
|
|
661
|
+
if (agentRules.length >= this.config.maxRulesPerAgent) {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
this.rules.set(rule.id, rule);
|
|
666
|
+
this.emit("ruleAdded", rule);
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Remove a permission rule
|
|
671
|
+
*/
|
|
672
|
+
removeRule(ruleId) {
|
|
673
|
+
const existed = this.rules.delete(ruleId);
|
|
674
|
+
if (existed) {
|
|
675
|
+
this.emit("ruleRemoved", ruleId);
|
|
676
|
+
}
|
|
677
|
+
return existed;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Get rule by ID
|
|
681
|
+
*/
|
|
682
|
+
getRule(ruleId) {
|
|
683
|
+
return this.rules.get(ruleId);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Get all rules for an agent
|
|
687
|
+
*/
|
|
688
|
+
getAgentRules(agentId) {
|
|
689
|
+
return Array.from(this.rules.values()).filter(
|
|
690
|
+
(r) => r.agentId === agentId || r.agentId === "*"
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Check if access is allowed
|
|
695
|
+
*/
|
|
696
|
+
checkAccess(request) {
|
|
697
|
+
const applicableRules = this.findApplicableRules(request);
|
|
698
|
+
if (applicableRules.length === 0) {
|
|
699
|
+
const result2 = {
|
|
700
|
+
allowed: !this.config.strictMode,
|
|
701
|
+
reason: this.config.strictMode ? "No applicable rules found" : "Default permission applied"
|
|
702
|
+
};
|
|
703
|
+
this.logAccess(request, result2);
|
|
704
|
+
return result2;
|
|
705
|
+
}
|
|
706
|
+
const sortedRules = this.sortRulesBySpecificity(applicableRules);
|
|
707
|
+
const bestRule = sortedRules[0];
|
|
708
|
+
if (bestRule.conditions && !this.checkConditions(request, bestRule.conditions)) {
|
|
709
|
+
const result2 = {
|
|
710
|
+
allowed: false,
|
|
711
|
+
reason: "Conditions not met",
|
|
712
|
+
matchedRule: bestRule
|
|
713
|
+
};
|
|
714
|
+
this.logAccess(request, result2);
|
|
715
|
+
return result2;
|
|
716
|
+
}
|
|
717
|
+
const requiredLevel = this.actionToPermissionLevel(request.action);
|
|
718
|
+
const hasPermission = this.hasPermissionLevel(
|
|
719
|
+
bestRule.permission,
|
|
720
|
+
requiredLevel
|
|
721
|
+
);
|
|
722
|
+
const result = {
|
|
723
|
+
allowed: hasPermission,
|
|
724
|
+
reason: hasPermission ? "Permission granted" : "Insufficient permission level",
|
|
725
|
+
matchedRule: bestRule
|
|
726
|
+
};
|
|
727
|
+
this.logAccess(request, result);
|
|
728
|
+
if (hasPermission) {
|
|
729
|
+
this.emit("accessGranted", request);
|
|
730
|
+
} else {
|
|
731
|
+
this.emit("accessDenied", request, result.reason);
|
|
732
|
+
}
|
|
733
|
+
return result;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Grant permission to agent
|
|
737
|
+
*/
|
|
738
|
+
grantPermission(granterId, agentId, resource, permission, options) {
|
|
739
|
+
const rule = {
|
|
740
|
+
id: `rule-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
741
|
+
agentId,
|
|
742
|
+
resource,
|
|
743
|
+
permission,
|
|
744
|
+
conditions: options?.conditions,
|
|
745
|
+
expiresAt: options?.expiresAt,
|
|
746
|
+
createdBy: granterId,
|
|
747
|
+
createdAt: Date.now()
|
|
748
|
+
};
|
|
749
|
+
this.addRule(rule);
|
|
750
|
+
return rule;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Revoke all permissions for agent on resource
|
|
754
|
+
*/
|
|
755
|
+
revokePermission(agentId, resource) {
|
|
756
|
+
const toRemove = [];
|
|
757
|
+
for (const [id, rule] of this.rules) {
|
|
758
|
+
if (rule.agentId === agentId && rule.resource === resource) {
|
|
759
|
+
toRemove.push(id);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
for (const id of toRemove) {
|
|
763
|
+
this.removeRule(id);
|
|
764
|
+
}
|
|
765
|
+
return toRemove.length;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Check if agent has specific permission
|
|
769
|
+
*/
|
|
770
|
+
hasPermission(agentId, resource, action) {
|
|
771
|
+
return this.checkAccess({ agentId, resource, action }).allowed;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get access log
|
|
775
|
+
*/
|
|
776
|
+
getAccessLog(options) {
|
|
777
|
+
let log = [...this.accessLog];
|
|
778
|
+
if (options?.agentId) {
|
|
779
|
+
log = log.filter((e) => e.agentId === options.agentId);
|
|
780
|
+
}
|
|
781
|
+
if (options?.resource) {
|
|
782
|
+
log = log.filter((e) => e.resource === options.resource);
|
|
783
|
+
}
|
|
784
|
+
if (options?.startTime) {
|
|
785
|
+
log = log.filter((e) => e.timestamp >= options.startTime);
|
|
786
|
+
}
|
|
787
|
+
if (options?.endTime) {
|
|
788
|
+
log = log.filter((e) => e.timestamp <= options.endTime);
|
|
789
|
+
}
|
|
790
|
+
log.sort((a, b) => b.timestamp - a.timestamp);
|
|
791
|
+
return options?.limit ? log.slice(0, options.limit) : log;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Clear access log
|
|
795
|
+
*/
|
|
796
|
+
clearAccessLog() {
|
|
797
|
+
this.accessLog = [];
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Find applicable rules for request
|
|
801
|
+
*/
|
|
802
|
+
findApplicableRules(request) {
|
|
803
|
+
const now = Date.now();
|
|
804
|
+
return Array.from(this.rules.values()).filter((rule) => {
|
|
805
|
+
if (rule.expiresAt && rule.expiresAt < now) {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
if (rule.agentId !== "*" && rule.agentId !== request.agentId) {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
if (rule.resource !== "*" && rule.resource !== request.resource) {
|
|
812
|
+
if (!request.resource.startsWith(rule.resource + ":")) {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return true;
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Sort rules by specificity (most specific first)
|
|
821
|
+
*/
|
|
822
|
+
sortRulesBySpecificity(rules) {
|
|
823
|
+
return rules.sort((a, b) => {
|
|
824
|
+
const agentSpecA = a.agentId !== "*" ? 1 : 0;
|
|
825
|
+
const agentSpecB = b.agentId !== "*" ? 1 : 0;
|
|
826
|
+
if (agentSpecA !== agentSpecB) return agentSpecB - agentSpecA;
|
|
827
|
+
const resourceSpecA = a.resource !== "*" ? 1 : 0;
|
|
828
|
+
const resourceSpecB = b.resource !== "*" ? 1 : 0;
|
|
829
|
+
if (resourceSpecA !== resourceSpecB) return resourceSpecB - resourceSpecA;
|
|
830
|
+
const levelA = this.permissionToLevel(a.permission);
|
|
831
|
+
const levelB = this.permissionToLevel(b.permission);
|
|
832
|
+
return levelB - levelA;
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Check conditions
|
|
837
|
+
*/
|
|
838
|
+
checkConditions(request, conditions) {
|
|
839
|
+
const now = Date.now();
|
|
840
|
+
for (const condition of conditions) {
|
|
841
|
+
switch (condition.type) {
|
|
842
|
+
case "time-range": {
|
|
843
|
+
const { start, end } = condition.value;
|
|
844
|
+
if (now < start || now > end) return false;
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
case "entry-type": {
|
|
848
|
+
if (!request.entry) break;
|
|
849
|
+
const allowedTypes = condition.value;
|
|
850
|
+
if (!allowedTypes.includes(request.entry.type)) return false;
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
853
|
+
case "importance": {
|
|
854
|
+
if (!request.entry) break;
|
|
855
|
+
const { min, max } = condition.value;
|
|
856
|
+
if (min !== void 0 && request.entry.importance < min) return false;
|
|
857
|
+
if (max !== void 0 && request.entry.importance > max) return false;
|
|
858
|
+
break;
|
|
859
|
+
}
|
|
860
|
+
case "custom": {
|
|
861
|
+
const fn = condition.value;
|
|
862
|
+
if (!fn(request)) return false;
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Convert action to required permission level
|
|
871
|
+
*/
|
|
872
|
+
actionToPermissionLevel(action) {
|
|
873
|
+
switch (action) {
|
|
874
|
+
case "read":
|
|
875
|
+
return 1;
|
|
876
|
+
case "write":
|
|
877
|
+
return 2;
|
|
878
|
+
case "delete":
|
|
879
|
+
return 2;
|
|
880
|
+
case "admin":
|
|
881
|
+
return 3;
|
|
882
|
+
default:
|
|
883
|
+
return 1;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Convert permission to level number
|
|
888
|
+
*/
|
|
889
|
+
permissionToLevel(permission) {
|
|
890
|
+
switch (permission) {
|
|
891
|
+
case "none":
|
|
892
|
+
return 0;
|
|
893
|
+
case "read":
|
|
894
|
+
return 1;
|
|
895
|
+
case "write":
|
|
896
|
+
return 2;
|
|
897
|
+
case "admin":
|
|
898
|
+
return 3;
|
|
899
|
+
default:
|
|
900
|
+
return 0;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Check if permission level is sufficient
|
|
905
|
+
*/
|
|
906
|
+
hasPermissionLevel(permission, required) {
|
|
907
|
+
return this.permissionToLevel(permission) >= required;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Log access attempt
|
|
911
|
+
*/
|
|
912
|
+
logAccess(request, result) {
|
|
913
|
+
if (!this.config.enableAuditLog) return;
|
|
914
|
+
this.accessLog.push({
|
|
915
|
+
timestamp: Date.now(),
|
|
916
|
+
agentId: request.agentId,
|
|
917
|
+
resource: request.resource,
|
|
918
|
+
action: request.action,
|
|
919
|
+
allowed: result.allowed,
|
|
920
|
+
reason: result.reason
|
|
921
|
+
});
|
|
922
|
+
if (this.accessLog.length > this.maxLogSize) {
|
|
923
|
+
this.accessLog = this.accessLog.slice(-this.maxLogSize);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Get statistics
|
|
928
|
+
*/
|
|
929
|
+
getStats() {
|
|
930
|
+
const agents = new Set(
|
|
931
|
+
Array.from(this.rules.values()).filter((r) => r.agentId !== "*").map((r) => r.agentId)
|
|
932
|
+
);
|
|
933
|
+
const granted = this.accessLog.filter((e) => e.allowed).length;
|
|
934
|
+
const denied = this.accessLog.filter((e) => !e.allowed).length;
|
|
935
|
+
return {
|
|
936
|
+
totalRules: this.rules.size,
|
|
937
|
+
agentCount: agents.size,
|
|
938
|
+
accessGranted: granted,
|
|
939
|
+
accessDenied: denied
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Export rules
|
|
944
|
+
*/
|
|
945
|
+
exportRules() {
|
|
946
|
+
return Array.from(this.rules.values());
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Import rules
|
|
950
|
+
*/
|
|
951
|
+
importRules(rules) {
|
|
952
|
+
let imported = 0;
|
|
953
|
+
for (const rule of rules) {
|
|
954
|
+
if (this.addRule(rule)) {
|
|
955
|
+
imported++;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return imported;
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
function createAccessControl(config) {
|
|
962
|
+
return new AccessControl(config);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
export {
|
|
966
|
+
SharedMemory,
|
|
967
|
+
createSharedMemory,
|
|
968
|
+
NamespaceManager,
|
|
969
|
+
createNamespaceManager,
|
|
970
|
+
AccessControl,
|
|
971
|
+
createAccessControl
|
|
972
|
+
};
|