memory-lancedb-pro 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/src/scopes.ts ADDED
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Multi-Scope Access Control System
3
+ * Manages memory isolation and access permissions
4
+ */
5
+
6
+ // ============================================================================
7
+ // Types & Configuration
8
+ // ============================================================================
9
+
10
+ export interface ScopeDefinition {
11
+ description: string;
12
+ metadata?: Record<string, unknown>;
13
+ }
14
+
15
+ export interface ScopeConfig {
16
+ default: string;
17
+ definitions: Record<string, ScopeDefinition>;
18
+ agentAccess: Record<string, string[]>;
19
+ }
20
+
21
+ export interface ScopeManager {
22
+ getAccessibleScopes(agentId?: string): string[];
23
+ getDefaultScope(agentId?: string): string;
24
+ isAccessible(scope: string, agentId?: string): boolean;
25
+ validateScope(scope: string): boolean;
26
+ getAllScopes(): string[];
27
+ getScopeDefinition(scope: string): ScopeDefinition | undefined;
28
+ }
29
+
30
+ // ============================================================================
31
+ // Default Configuration
32
+ // ============================================================================
33
+
34
+ export const DEFAULT_SCOPE_CONFIG: ScopeConfig = {
35
+ default: "global",
36
+ definitions: {
37
+ global: {
38
+ description: "Shared knowledge across all agents",
39
+ },
40
+ },
41
+ agentAccess: {},
42
+ };
43
+
44
+ // ============================================================================
45
+ // Built-in Scope Patterns
46
+ // ============================================================================
47
+
48
+ const SCOPE_PATTERNS = {
49
+ GLOBAL: "global",
50
+ AGENT: (agentId: string) => `agent:${agentId}`,
51
+ CUSTOM: (name: string) => `custom:${name}`,
52
+ PROJECT: (projectId: string) => `project:${projectId}`,
53
+ USER: (userId: string) => `user:${userId}`,
54
+ };
55
+
56
+ // ============================================================================
57
+ // Scope Manager Implementation
58
+ // ============================================================================
59
+
60
+ export class MemoryScopeManager implements ScopeManager {
61
+ private config: ScopeConfig;
62
+
63
+ constructor(config: Partial<ScopeConfig> = {}) {
64
+ this.config = {
65
+ default: config.default || DEFAULT_SCOPE_CONFIG.default,
66
+ definitions: {
67
+ ...DEFAULT_SCOPE_CONFIG.definitions,
68
+ ...config.definitions,
69
+ },
70
+ agentAccess: {
71
+ ...DEFAULT_SCOPE_CONFIG.agentAccess,
72
+ ...config.agentAccess,
73
+ },
74
+ };
75
+
76
+ // Ensure global scope always exists
77
+ if (!this.config.definitions.global) {
78
+ this.config.definitions.global = {
79
+ description: "Shared knowledge across all agents",
80
+ };
81
+ }
82
+
83
+ this.validateConfiguration();
84
+ }
85
+
86
+ private validateConfiguration(): void {
87
+ // Validate default scope exists in definitions
88
+ if (!this.config.definitions[this.config.default]) {
89
+ throw new Error(`Default scope '${this.config.default}' not found in definitions`);
90
+ }
91
+
92
+ // Validate agent access scopes exist in definitions
93
+ for (const [agentId, scopes] of Object.entries(this.config.agentAccess)) {
94
+ for (const scope of scopes) {
95
+ if (!this.config.definitions[scope] && !this.isBuiltInScope(scope)) {
96
+ console.warn(`Agent '${agentId}' has access to undefined scope '${scope}'`);
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ private isBuiltInScope(scope: string): boolean {
103
+ return (
104
+ scope === "global" ||
105
+ scope.startsWith("agent:") ||
106
+ scope.startsWith("custom:") ||
107
+ scope.startsWith("project:") ||
108
+ scope.startsWith("user:")
109
+ );
110
+ }
111
+
112
+ getAccessibleScopes(agentId?: string): string[] {
113
+ if (!agentId) {
114
+ // No agent specified, return all scopes
115
+ return this.getAllScopes();
116
+ }
117
+
118
+ // Check explicit agent access configuration
119
+ const explicitAccess = this.config.agentAccess[agentId];
120
+ if (explicitAccess) {
121
+ return explicitAccess;
122
+ }
123
+
124
+ // Default access: global + agent-specific scope
125
+ const defaultScopes = ["global"];
126
+ const agentScope = SCOPE_PATTERNS.AGENT(agentId);
127
+
128
+ // Only include agent scope if it already exists — don't mutate config as a side effect
129
+ if (this.config.definitions[agentScope] || this.isBuiltInScope(agentScope)) {
130
+ defaultScopes.push(agentScope);
131
+ }
132
+
133
+ return defaultScopes;
134
+ }
135
+
136
+ getDefaultScope(agentId?: string): string {
137
+ if (!agentId) {
138
+ return this.config.default;
139
+ }
140
+
141
+ // For agents, default to their private scope if they have access to it
142
+ const agentScope = SCOPE_PATTERNS.AGENT(agentId);
143
+ const accessibleScopes = this.getAccessibleScopes(agentId);
144
+
145
+ if (accessibleScopes.includes(agentScope)) {
146
+ return agentScope;
147
+ }
148
+
149
+ return this.config.default;
150
+ }
151
+
152
+ isAccessible(scope: string, agentId?: string): boolean {
153
+ if (!agentId) {
154
+ // No agent specified, allow access to all valid scopes
155
+ return this.validateScope(scope);
156
+ }
157
+
158
+ const accessibleScopes = this.getAccessibleScopes(agentId);
159
+ return accessibleScopes.includes(scope);
160
+ }
161
+
162
+ validateScope(scope: string): boolean {
163
+ if (!scope || typeof scope !== "string" || scope.trim().length === 0) {
164
+ return false;
165
+ }
166
+
167
+ const trimmedScope = scope.trim();
168
+
169
+ // Check if scope is defined or is a built-in pattern
170
+ return (
171
+ this.config.definitions[trimmedScope] !== undefined ||
172
+ this.isBuiltInScope(trimmedScope)
173
+ );
174
+ }
175
+
176
+ getAllScopes(): string[] {
177
+ return Object.keys(this.config.definitions);
178
+ }
179
+
180
+ getScopeDefinition(scope: string): ScopeDefinition | undefined {
181
+ return this.config.definitions[scope];
182
+ }
183
+
184
+ // Management methods
185
+
186
+ addScopeDefinition(scope: string, definition: ScopeDefinition): void {
187
+ if (!this.validateScopeFormat(scope)) {
188
+ throw new Error(`Invalid scope format: ${scope}`);
189
+ }
190
+
191
+ this.config.definitions[scope] = definition;
192
+ }
193
+
194
+ removeScopeDefinition(scope: string): boolean {
195
+ if (scope === "global") {
196
+ throw new Error("Cannot remove global scope");
197
+ }
198
+
199
+ if (!this.config.definitions[scope]) {
200
+ return false;
201
+ }
202
+
203
+ delete this.config.definitions[scope];
204
+
205
+ // Clean up agent access references
206
+ for (const [agentId, scopes] of Object.entries(this.config.agentAccess)) {
207
+ const filtered = scopes.filter(s => s !== scope);
208
+ if (filtered.length !== scopes.length) {
209
+ this.config.agentAccess[agentId] = filtered;
210
+ }
211
+ }
212
+
213
+ return true;
214
+ }
215
+
216
+ setAgentAccess(agentId: string, scopes: string[]): void {
217
+ if (!agentId || typeof agentId !== "string") {
218
+ throw new Error("Invalid agent ID");
219
+ }
220
+
221
+ // Validate all scopes
222
+ for (const scope of scopes) {
223
+ if (!this.validateScope(scope)) {
224
+ throw new Error(`Invalid scope: ${scope}`);
225
+ }
226
+ }
227
+
228
+ this.config.agentAccess[agentId] = [...scopes];
229
+ }
230
+
231
+ removeAgentAccess(agentId: string): boolean {
232
+ if (!this.config.agentAccess[agentId]) {
233
+ return false;
234
+ }
235
+
236
+ delete this.config.agentAccess[agentId];
237
+ return true;
238
+ }
239
+
240
+ private validateScopeFormat(scope: string): boolean {
241
+ if (!scope || typeof scope !== "string") {
242
+ return false;
243
+ }
244
+
245
+ const trimmed = scope.trim();
246
+
247
+ // Basic format validation
248
+ if (trimmed.length === 0 || trimmed.length > 100) {
249
+ return false;
250
+ }
251
+
252
+ // Allow alphanumeric, hyphens, underscores, colons, and dots
253
+ const validFormat = /^[a-zA-Z0-9._:-]+$/.test(trimmed);
254
+ return validFormat;
255
+ }
256
+
257
+ // Export/Import configuration
258
+
259
+ exportConfig(): ScopeConfig {
260
+ return JSON.parse(JSON.stringify(this.config));
261
+ }
262
+
263
+ importConfig(config: Partial<ScopeConfig>): void {
264
+ this.config = {
265
+ default: config.default || this.config.default,
266
+ definitions: {
267
+ ...this.config.definitions,
268
+ ...config.definitions,
269
+ },
270
+ agentAccess: {
271
+ ...this.config.agentAccess,
272
+ ...config.agentAccess,
273
+ },
274
+ };
275
+
276
+ this.validateConfiguration();
277
+ }
278
+
279
+ // Statistics
280
+
281
+ getStats(): {
282
+ totalScopes: number;
283
+ agentsWithCustomAccess: number;
284
+ scopesByType: Record<string, number>;
285
+ } {
286
+ const scopes = this.getAllScopes();
287
+ const scopesByType: Record<string, number> = {
288
+ global: 0,
289
+ agent: 0,
290
+ custom: 0,
291
+ project: 0,
292
+ user: 0,
293
+ other: 0,
294
+ };
295
+
296
+ for (const scope of scopes) {
297
+ if (scope === "global") {
298
+ scopesByType.global++;
299
+ } else if (scope.startsWith("agent:")) {
300
+ scopesByType.agent++;
301
+ } else if (scope.startsWith("custom:")) {
302
+ scopesByType.custom++;
303
+ } else if (scope.startsWith("project:")) {
304
+ scopesByType.project++;
305
+ } else if (scope.startsWith("user:")) {
306
+ scopesByType.user++;
307
+ } else {
308
+ scopesByType.other++;
309
+ }
310
+ }
311
+
312
+ return {
313
+ totalScopes: scopes.length,
314
+ agentsWithCustomAccess: Object.keys(this.config.agentAccess).length,
315
+ scopesByType,
316
+ };
317
+ }
318
+ }
319
+
320
+ // ============================================================================
321
+ // Factory Functions
322
+ // ============================================================================
323
+
324
+ export function createScopeManager(config?: Partial<ScopeConfig>): MemoryScopeManager {
325
+ return new MemoryScopeManager(config);
326
+ }
327
+
328
+ export function createAgentScope(agentId: string): string {
329
+ return SCOPE_PATTERNS.AGENT(agentId);
330
+ }
331
+
332
+ export function createCustomScope(name: string): string {
333
+ return SCOPE_PATTERNS.CUSTOM(name);
334
+ }
335
+
336
+ export function createProjectScope(projectId: string): string {
337
+ return SCOPE_PATTERNS.PROJECT(projectId);
338
+ }
339
+
340
+ export function createUserScope(userId: string): string {
341
+ return SCOPE_PATTERNS.USER(userId);
342
+ }
343
+
344
+ // ============================================================================
345
+ // Utility Functions
346
+ // ============================================================================
347
+
348
+ export function parseScopeId(scope: string): { type: string; id: string } | null {
349
+ if (scope === "global") {
350
+ return { type: "global", id: "" };
351
+ }
352
+
353
+ const colonIndex = scope.indexOf(":");
354
+ if (colonIndex === -1) {
355
+ return null;
356
+ }
357
+
358
+ return {
359
+ type: scope.substring(0, colonIndex),
360
+ id: scope.substring(colonIndex + 1),
361
+ };
362
+ }
363
+
364
+ export function isScopeAccessible(scope: string, allowedScopes: string[]): boolean {
365
+ return allowedScopes.includes(scope);
366
+ }
367
+
368
+ export function filterScopesForAgent(scopes: string[], agentId?: string, scopeManager?: ScopeManager): string[] {
369
+ if (!scopeManager || !agentId) {
370
+ return scopes;
371
+ }
372
+
373
+ return scopes.filter(scope => scopeManager.isAccessible(scope, agentId));
374
+ }