mindcache 3.4.2 → 3.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2250 @@
1
+ import * as syncProtocol from 'y-protocols/sync';
2
+ import * as encoding from 'lib0/encoding';
3
+ import * as decoding from 'lib0/decoding';
4
+ import * as Y from 'yjs';
5
+ import { IndexeddbPersistence } from 'y-indexeddb';
6
+ import diff from 'fast-diff';
7
+
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __esm = (fn, res) => function __init() {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+
18
+ // src/local/IndexedDBAdapter.ts
19
+ var IndexedDBAdapter_exports = {};
20
+ __export(IndexedDBAdapter_exports, {
21
+ IndexedDBAdapter: () => IndexedDBAdapter
22
+ });
23
+ var IndexedDBAdapter;
24
+ var init_IndexedDBAdapter = __esm({
25
+ "src/local/IndexedDBAdapter.ts"() {
26
+ IndexedDBAdapter = class {
27
+ constructor(config = {}) {
28
+ this.config = config;
29
+ this.dbName = config.dbName || "mindcache_db";
30
+ this.storeName = config.storeName || "mindcache_store";
31
+ this.key = config.key || "mindcache_data";
32
+ }
33
+ mindcache = null;
34
+ unsubscribe = null;
35
+ saveTimeout = null;
36
+ db = null;
37
+ dbName;
38
+ storeName;
39
+ key;
40
+ async attach(mc) {
41
+ if (this.mindcache) {
42
+ this.detach();
43
+ }
44
+ this.mindcache = mc;
45
+ await this.initDB();
46
+ await this.load();
47
+ const listener = () => {
48
+ if (this.mindcache) {
49
+ this.scheduleSave();
50
+ }
51
+ };
52
+ mc.subscribeToAll(listener);
53
+ this.unsubscribe = () => mc.unsubscribeFromAll(listener);
54
+ console.log("\u{1F5C4}\uFE0F IndexedDBAdapter: Attached to MindCache instance");
55
+ }
56
+ detach() {
57
+ if (this.unsubscribe) {
58
+ this.unsubscribe();
59
+ this.unsubscribe = null;
60
+ }
61
+ if (this.saveTimeout) {
62
+ clearTimeout(this.saveTimeout);
63
+ this.saveTimeout = null;
64
+ }
65
+ this.mindcache = null;
66
+ if (this.db) {
67
+ this.db.close();
68
+ this.db = null;
69
+ }
70
+ }
71
+ initDB() {
72
+ return new Promise((resolve, reject) => {
73
+ const request = indexedDB.open(this.dbName);
74
+ request.onerror = () => {
75
+ console.error("MindCache: IndexedDB error:", request.error);
76
+ reject(request.error);
77
+ };
78
+ request.onsuccess = () => {
79
+ const db = request.result;
80
+ if (!db.objectStoreNames.contains(this.storeName)) {
81
+ const currentVersion = db.version;
82
+ db.close();
83
+ const upgradeRequest = indexedDB.open(this.dbName, currentVersion + 1);
84
+ upgradeRequest.onerror = () => {
85
+ console.error("MindCache: IndexedDB upgrade error:", upgradeRequest.error);
86
+ reject(upgradeRequest.error);
87
+ };
88
+ upgradeRequest.onupgradeneeded = () => {
89
+ const upgradeDb = upgradeRequest.result;
90
+ if (!upgradeDb.objectStoreNames.contains(this.storeName)) {
91
+ upgradeDb.createObjectStore(this.storeName);
92
+ }
93
+ };
94
+ upgradeRequest.onsuccess = () => {
95
+ this.db = upgradeRequest.result;
96
+ resolve();
97
+ };
98
+ } else {
99
+ this.db = db;
100
+ resolve();
101
+ }
102
+ };
103
+ request.onupgradeneeded = () => {
104
+ const db = request.result;
105
+ if (!db.objectStoreNames.contains(this.storeName)) {
106
+ db.createObjectStore(this.storeName);
107
+ }
108
+ };
109
+ });
110
+ }
111
+ load() {
112
+ if (!this.db || !this.mindcache) {
113
+ return Promise.resolve();
114
+ }
115
+ return new Promise((resolve) => {
116
+ try {
117
+ const transaction = this.db.transaction([this.storeName], "readonly");
118
+ const store = transaction.objectStore(this.storeName);
119
+ const request = store.get(this.key);
120
+ request.onsuccess = () => {
121
+ if (request.result) {
122
+ this.mindcache.deserialize(request.result);
123
+ console.log("\u{1F5C4}\uFE0F IndexedDBAdapter: Loaded data from IndexedDB");
124
+ }
125
+ resolve();
126
+ };
127
+ request.onerror = () => {
128
+ console.error("MindCache: Failed to load from IndexedDB:", request.error);
129
+ resolve();
130
+ };
131
+ } catch (error) {
132
+ console.error("MindCache: Error accessing IndexedDB for load:", error);
133
+ resolve();
134
+ }
135
+ });
136
+ }
137
+ scheduleSave() {
138
+ if (this.saveTimeout) {
139
+ clearTimeout(this.saveTimeout);
140
+ }
141
+ this.saveTimeout = setTimeout(() => {
142
+ this.save();
143
+ this.saveTimeout = null;
144
+ }, this.config.debounceMs ?? 1e3);
145
+ }
146
+ save() {
147
+ if (!this.db || !this.mindcache) {
148
+ return;
149
+ }
150
+ try {
151
+ const data = this.mindcache.serialize();
152
+ const transaction = this.db.transaction([this.storeName], "readwrite");
153
+ const store = transaction.objectStore(this.storeName);
154
+ const request = store.put(data, this.key);
155
+ request.onsuccess = () => {
156
+ console.log("\u{1F5C4}\uFE0F IndexedDBAdapter: Saved to IndexedDB");
157
+ };
158
+ request.onerror = () => {
159
+ console.error("MindCache: Failed to save to IndexedDB:", request.error);
160
+ };
161
+ } catch (error) {
162
+ console.error("MindCache: Error accessing IndexedDB for save:", error);
163
+ }
164
+ }
165
+ };
166
+ }
167
+ });
168
+
169
+ // src/cloud/CloudAdapter.ts
170
+ var CloudAdapter_exports = {};
171
+ __export(CloudAdapter_exports, {
172
+ CloudAdapter: () => CloudAdapter
173
+ });
174
+ var RECONNECT_DELAY, MAX_RECONNECT_DELAY, CloudAdapter;
175
+ var init_CloudAdapter = __esm({
176
+ "src/cloud/CloudAdapter.ts"() {
177
+ RECONNECT_DELAY = 1e3;
178
+ MAX_RECONNECT_DELAY = 3e4;
179
+ CloudAdapter = class {
180
+ // Track if initial sync is complete
181
+ constructor(config) {
182
+ this.config = config;
183
+ if (!config.baseUrl) {
184
+ throw new Error("MindCache Cloud: baseUrl is required. Please provide the cloud API URL in your configuration.");
185
+ }
186
+ this.setupNetworkDetection();
187
+ }
188
+ ws = null;
189
+ mindcache = null;
190
+ unsubscribe = null;
191
+ reconnectAttempts = 0;
192
+ reconnectTimeout = null;
193
+ _state = "disconnected";
194
+ _isOnline = true;
195
+ // Browser network status
196
+ listeners = {};
197
+ token = null;
198
+ handleOnline = null;
199
+ handleOffline = null;
200
+ _synced = false;
201
+ /** Browser network status - instantly updated via navigator.onLine */
202
+ get isOnline() {
203
+ return this._isOnline;
204
+ }
205
+ setupNetworkDetection() {
206
+ if (typeof window === "undefined" || typeof navigator === "undefined") {
207
+ return;
208
+ }
209
+ this._isOnline = navigator.onLine;
210
+ this.handleOnline = () => {
211
+ console.log("\u2601\uFE0F CloudAdapter: Network is back online");
212
+ this._isOnline = true;
213
+ this.emit("network_online");
214
+ if (this._state === "disconnected" || this._state === "error") {
215
+ this.connect();
216
+ }
217
+ };
218
+ this.handleOffline = () => {
219
+ console.log("\u2601\uFE0F CloudAdapter: Network went offline");
220
+ this._isOnline = false;
221
+ this.emit("network_offline");
222
+ if (this._state === "connected" || this._state === "connecting") {
223
+ this._state = "disconnected";
224
+ this.emit("disconnected");
225
+ }
226
+ };
227
+ window.addEventListener("online", this.handleOnline);
228
+ window.addEventListener("offline", this.handleOffline);
229
+ }
230
+ cleanupNetworkDetection() {
231
+ if (typeof window === "undefined") {
232
+ return;
233
+ }
234
+ if (this.handleOnline) {
235
+ window.removeEventListener("online", this.handleOnline);
236
+ }
237
+ if (this.handleOffline) {
238
+ window.removeEventListener("offline", this.handleOffline);
239
+ }
240
+ }
241
+ setToken(token) {
242
+ this.token = token;
243
+ }
244
+ setTokenProvider(provider) {
245
+ this.config.tokenProvider = provider;
246
+ }
247
+ get state() {
248
+ return this._state;
249
+ }
250
+ attach(mc) {
251
+ if (this.mindcache) {
252
+ this.detach();
253
+ }
254
+ this.mindcache = mc;
255
+ mc.doc.on("update", (update, origin) => {
256
+ if (origin !== this && this.ws && this.ws.readyState === WebSocket.OPEN) {
257
+ const encoder = encoding.createEncoder();
258
+ syncProtocol.writeUpdate(encoder, update);
259
+ this.sendBinary(encoding.toUint8Array(encoder));
260
+ }
261
+ });
262
+ console.log("\u2601\uFE0F CloudAdapter: Attached to MindCache instance");
263
+ }
264
+ detach() {
265
+ if (this.unsubscribe) {
266
+ this.unsubscribe();
267
+ this.unsubscribe = null;
268
+ }
269
+ this.mindcache = null;
270
+ }
271
+ async fetchTokenWithApiKey() {
272
+ if (!this.config.apiKey) {
273
+ throw new Error("API key is required to fetch token");
274
+ }
275
+ const httpBaseUrl = this.config.baseUrl.replace("wss://", "https://").replace("ws://", "http://");
276
+ const isDelegate = this.config.apiKey.startsWith("del_") && this.config.apiKey.includes(":");
277
+ const authHeader = isDelegate ? `ApiKey ${this.config.apiKey}` : `Bearer ${this.config.apiKey}`;
278
+ const response = await fetch(`${httpBaseUrl}/api/ws-token`, {
279
+ method: "POST",
280
+ headers: {
281
+ "Content-Type": "application/json",
282
+ "Authorization": authHeader
283
+ },
284
+ body: JSON.stringify({
285
+ instanceId: this.config.instanceId,
286
+ permission: "write"
287
+ })
288
+ });
289
+ if (!response.ok) {
290
+ const error = await response.json().catch(() => ({ error: "Failed to get token" }));
291
+ throw new Error(error.error || `Failed to get WebSocket token: ${response.status}`);
292
+ }
293
+ const data = await response.json();
294
+ return data.token;
295
+ }
296
+ async connect() {
297
+ if (this._state === "connecting" || this._state === "connected") {
298
+ return;
299
+ }
300
+ this._state = "connecting";
301
+ try {
302
+ if (!this.token) {
303
+ if (this.config.tokenProvider) {
304
+ this.token = await this.config.tokenProvider();
305
+ } else if (this.config.apiKey) {
306
+ this.token = await this.fetchTokenWithApiKey();
307
+ }
308
+ }
309
+ let url = `${this.config.baseUrl}/sync/${this.config.instanceId}`;
310
+ if (this.token) {
311
+ url += `?token=${encodeURIComponent(this.token)}`;
312
+ this.token = null;
313
+ } else {
314
+ throw new Error("MindCache Cloud: No authentication method available. Provide apiKey or tokenProvider.");
315
+ }
316
+ this.ws = new WebSocket(url);
317
+ this.ws.binaryType = "arraybuffer";
318
+ this.setupWebSocket();
319
+ } catch (error) {
320
+ this._state = "error";
321
+ this.emit("error", error);
322
+ this.scheduleReconnect();
323
+ }
324
+ }
325
+ disconnect() {
326
+ if (this.reconnectTimeout) {
327
+ clearTimeout(this.reconnectTimeout);
328
+ this.reconnectTimeout = null;
329
+ }
330
+ if (this.ws) {
331
+ this.ws.close();
332
+ this.ws = null;
333
+ }
334
+ this.cleanupNetworkDetection();
335
+ this._state = "disconnected";
336
+ this.emit("disconnected");
337
+ }
338
+ on(event, listener) {
339
+ if (!this.listeners[event]) {
340
+ this.listeners[event] = [];
341
+ }
342
+ this.listeners[event].push(listener);
343
+ }
344
+ off(event, listener) {
345
+ if (this.listeners[event]) {
346
+ this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
347
+ }
348
+ }
349
+ emit(event, ...args) {
350
+ if (this.listeners[event]) {
351
+ this.listeners[event].forEach((listener) => listener(...args));
352
+ }
353
+ }
354
+ setupWebSocket() {
355
+ if (!this.ws) {
356
+ return;
357
+ }
358
+ this.ws.onopen = () => {
359
+ if (this.mindcache) {
360
+ const encoder = encoding.createEncoder();
361
+ syncProtocol.writeSyncStep1(encoder, this.mindcache.doc);
362
+ this.sendBinary(encoding.toUint8Array(encoder));
363
+ }
364
+ };
365
+ this.ws.onmessage = (event) => {
366
+ try {
367
+ if (typeof event.data === "string") {
368
+ const msg = JSON.parse(event.data);
369
+ if (msg.type === "auth_success") {
370
+ this._state = "connected";
371
+ this.reconnectAttempts = 0;
372
+ this.emit("connected");
373
+ } else if (msg.type === "auth_error" || msg.type === "error") {
374
+ this._state = "error";
375
+ this.emit("error", new Error(msg.error));
376
+ } else {
377
+ console.debug("MindCache Cloud: Received message type:", msg.type, msg);
378
+ }
379
+ } else {
380
+ const encoder = encoding.createEncoder();
381
+ const decoder = decoding.createDecoder(new Uint8Array(event.data));
382
+ if (this.mindcache) {
383
+ const messageType = syncProtocol.readSyncMessage(decoder, encoder, this.mindcache.doc, this);
384
+ if (encoding.length(encoder) > 0) {
385
+ this.sendBinary(encoding.toUint8Array(encoder));
386
+ }
387
+ if (!this._synced && (messageType === 1 || messageType === 2)) {
388
+ this._synced = true;
389
+ this.emit("synced");
390
+ }
391
+ }
392
+ }
393
+ } catch (error) {
394
+ console.error("MindCache Cloud: Failed to handle message:", error);
395
+ }
396
+ };
397
+ this.ws.onclose = () => {
398
+ this._state = "disconnected";
399
+ this.emit("disconnected");
400
+ this.scheduleReconnect();
401
+ };
402
+ this.ws.onerror = () => {
403
+ this._state = "error";
404
+ this.emit("error", new Error("WebSocket connection failed"));
405
+ };
406
+ }
407
+ sendBinary(data) {
408
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
409
+ this.ws.send(data);
410
+ }
411
+ }
412
+ scheduleReconnect() {
413
+ if (this.reconnectTimeout) {
414
+ return;
415
+ }
416
+ const delay = Math.min(
417
+ RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts),
418
+ MAX_RECONNECT_DELAY
419
+ );
420
+ this.reconnectTimeout = setTimeout(async () => {
421
+ this.reconnectTimeout = null;
422
+ this.reconnectAttempts++;
423
+ this.connect();
424
+ }, delay);
425
+ }
426
+ };
427
+ }
428
+ });
429
+
430
+ // src/core/types.ts
431
+ var DEFAULT_KEY_ATTRIBUTES = {
432
+ type: "text",
433
+ contentTags: [],
434
+ systemTags: [],
435
+ // Keys are private by default - explicitly add SystemPrompt/LLMRead/LLMWrite to enable LLM access
436
+ zIndex: 0
437
+ };
438
+ var SystemTagHelpers = {
439
+ /** Check if key is writable by LLM */
440
+ isLLMWritable: (attrs) => attrs.systemTags.includes("LLMWrite"),
441
+ /** Check if key is readable by LLM (in context or via tools) */
442
+ isLLMReadable: (attrs) => attrs.systemTags.includes("SystemPrompt") || attrs.systemTags.includes("LLMRead"),
443
+ /** Check if key is included in system prompt */
444
+ isInSystemPrompt: (attrs) => attrs.systemTags.includes("SystemPrompt"),
445
+ /** Check if key is protected from deletion */
446
+ isProtected: (attrs) => attrs.systemTags.includes("protected"),
447
+ /** Check if key uses template injection */
448
+ hasTemplateInjection: (attrs) => attrs.systemTags.includes("ApplyTemplate")
449
+ };
450
+
451
+ // src/core/MindCache.ts
452
+ var MindCache = class {
453
+ // Public doc for adapter access
454
+ doc;
455
+ rootMap;
456
+ // Key -> EntryMap({value, attributes})
457
+ // Cache listeners
458
+ listeners = {};
459
+ globalListeners = [];
460
+ // Metadata
461
+ version = "3.3.2";
462
+ // Internal flag to prevent sync loops when receiving remote updates
463
+ // (Less critical with Yjs but kept for API compat)
464
+ normalizeSystemTags(tags) {
465
+ const normalized = [];
466
+ const seen = /* @__PURE__ */ new Set();
467
+ for (const tag of tags) {
468
+ if (["SystemPrompt", "LLMRead", "LLMWrite", "protected", "ApplyTemplate"].includes(tag)) {
469
+ if (!seen.has(tag)) {
470
+ seen.add(tag);
471
+ normalized.push(tag);
472
+ }
473
+ }
474
+ }
475
+ return normalized;
476
+ }
477
+ // Cloud sync state
478
+ _cloudAdapter = null;
479
+ _connectionState = "disconnected";
480
+ _isLoaded = true;
481
+ // Default true for local mode
482
+ _cloudConfig = null;
483
+ // Access level for system operations
484
+ _accessLevel = "user";
485
+ _initPromise = null;
486
+ // Y-IndexedDB provider
487
+ _idbProvider = null;
488
+ // Undo Managers Cache
489
+ _undoManagers = /* @__PURE__ */ new Map();
490
+ // Global Undo Manager (watches entire rootMap)
491
+ _globalUndoManager = null;
492
+ // History tracking
493
+ _history = [];
494
+ _historyOptions = { maxEntries: 100, snapshotInterval: 10 };
495
+ _historyEnabled = false;
496
+ constructor(options) {
497
+ this.doc = options?.doc || new Y.Doc();
498
+ this.rootMap = this.doc.getMap("mindcache");
499
+ this.rootMap.observeDeep((events) => {
500
+ const keysAffected = /* @__PURE__ */ new Set();
501
+ events.forEach((event) => {
502
+ if (event.target === this.rootMap) {
503
+ const mapEvent = event;
504
+ mapEvent.keysChanged.forEach((key) => keysAffected.add(key));
505
+ } else if (event.target.parent === this.rootMap) {
506
+ for (const [key, val] of this.rootMap) {
507
+ if (val === event.target) {
508
+ keysAffected.add(key);
509
+ break;
510
+ }
511
+ }
512
+ } else {
513
+ let current = event.target;
514
+ while (current && current.parent) {
515
+ if (current.parent.parent === this.rootMap) {
516
+ for (const [key, val] of this.rootMap) {
517
+ if (val === current.parent) {
518
+ keysAffected.add(key);
519
+ break;
520
+ }
521
+ }
522
+ break;
523
+ }
524
+ current = current.parent;
525
+ }
526
+ }
527
+ });
528
+ keysAffected.forEach((key) => {
529
+ const entryMap = this.rootMap.get(key);
530
+ if (entryMap) {
531
+ const value = entryMap.get("value");
532
+ const attrs = entryMap.get("attributes");
533
+ const resolvedValue = attrs?.type === "document" && value instanceof Y.Text ? value.toString() : value;
534
+ if (this.listeners[key]) {
535
+ this.listeners[key].forEach((l) => l(resolvedValue));
536
+ }
537
+ } else {
538
+ if (this.listeners[key]) {
539
+ this.listeners[key].forEach((l) => l(void 0));
540
+ }
541
+ }
542
+ });
543
+ this.notifyGlobalListeners();
544
+ });
545
+ this.initGlobalUndoManager();
546
+ if (options?.accessLevel) {
547
+ this._accessLevel = options.accessLevel;
548
+ }
549
+ const initPromises = [];
550
+ if (options?.cloud) {
551
+ this._cloudConfig = options.cloud;
552
+ this._isLoaded = false;
553
+ this._connectionState = "disconnected";
554
+ initPromises.push(this._initCloud());
555
+ if (typeof window !== "undefined") {
556
+ const dbName = `mindcache_cloud_${options.cloud.instanceId}`;
557
+ initPromises.push(this._initYIndexedDB(dbName));
558
+ }
559
+ this.enableHistory(options.history);
560
+ }
561
+ if (options?.indexedDB && !options?.cloud) {
562
+ this._isLoaded = false;
563
+ initPromises.push(this._initYIndexedDB(options.indexedDB.dbName || "mindcache_yjs_db"));
564
+ this.enableHistory(options.history);
565
+ }
566
+ if (initPromises.length > 0) {
567
+ this._initPromise = Promise.all(initPromises).then(() => {
568
+ if (!this._cloudConfig) {
569
+ this._isLoaded = true;
570
+ }
571
+ });
572
+ }
573
+ }
574
+ // Helper: Get or Create UndoManager for a key
575
+ getUndoManager(key) {
576
+ const entryMap = this.rootMap.get(key);
577
+ if (!entryMap) {
578
+ return void 0;
579
+ }
580
+ if (!this._undoManagers.has(key)) {
581
+ const um = new Y.UndoManager(entryMap, {
582
+ captureTimeout: 500
583
+ });
584
+ this._undoManagers.set(key, um);
585
+ }
586
+ return this._undoManagers.get(key);
587
+ }
588
+ /**
589
+ * Undo changes for a specific key
590
+ */
591
+ undo(key) {
592
+ const um = this.getUndoManager(key);
593
+ if (um) {
594
+ um.undo();
595
+ }
596
+ }
597
+ /**
598
+ * Redo changes for a specific key
599
+ */
600
+ redo(key) {
601
+ const um = this.getUndoManager(key);
602
+ if (um) {
603
+ um.redo();
604
+ }
605
+ }
606
+ getHistory(key) {
607
+ const um = this.getUndoManager(key);
608
+ if (!um) {
609
+ return [];
610
+ }
611
+ return um.undoStack;
612
+ }
613
+ // Initialize global undo manager (watches entire rootMap)
614
+ initGlobalUndoManager() {
615
+ if (!this._globalUndoManager) {
616
+ this._globalUndoManager = new Y.UndoManager(this.rootMap, {
617
+ captureTimeout: 500
618
+ });
619
+ }
620
+ }
621
+ /**
622
+ * Undo all recent local changes (across all keys)
623
+ * Only undoes YOUR changes, not changes from other users in cloud mode
624
+ */
625
+ undoAll() {
626
+ this.initGlobalUndoManager();
627
+ this._globalUndoManager?.undo();
628
+ }
629
+ /**
630
+ * Redo previously undone local changes
631
+ */
632
+ redoAll() {
633
+ this.initGlobalUndoManager();
634
+ this._globalUndoManager?.redo();
635
+ }
636
+ /**
637
+ * Check if there are changes to undo globally
638
+ */
639
+ canUndoAll() {
640
+ this.initGlobalUndoManager();
641
+ return (this._globalUndoManager?.undoStack.length ?? 0) > 0;
642
+ }
643
+ /**
644
+ * Check if there are changes to redo globally
645
+ */
646
+ canRedoAll() {
647
+ this.initGlobalUndoManager();
648
+ return (this._globalUndoManager?.redoStack.length ?? 0) > 0;
649
+ }
650
+ // Enable history tracking (called for IndexedDB and Cloud modes)
651
+ enableHistory(options) {
652
+ if (this._historyEnabled) {
653
+ return;
654
+ }
655
+ this._historyEnabled = true;
656
+ if (options) {
657
+ this._historyOptions = { ...this._historyOptions, ...options };
658
+ }
659
+ this.rootMap.observeDeep((events) => {
660
+ const keysAffected = /* @__PURE__ */ new Set();
661
+ events.forEach((event) => {
662
+ if (event.target === this.rootMap) {
663
+ const mapEvent = event;
664
+ mapEvent.keysChanged.forEach((key) => keysAffected.add(key));
665
+ } else {
666
+ let current = event.target;
667
+ while (current && current.parent) {
668
+ if (current.parent === this.rootMap) {
669
+ for (const [key, val] of this.rootMap) {
670
+ if (val === current) {
671
+ keysAffected.add(key);
672
+ break;
673
+ }
674
+ }
675
+ break;
676
+ }
677
+ current = current.parent;
678
+ }
679
+ }
680
+ });
681
+ if (keysAffected.size > 0) {
682
+ const entry = {
683
+ id: this.generateId(),
684
+ timestamp: Date.now(),
685
+ keysAffected: Array.from(keysAffected)
686
+ };
687
+ this._history.push(entry);
688
+ const max = this._historyOptions.maxEntries || 100;
689
+ if (this._history.length > max) {
690
+ this._history = this._history.slice(-max);
691
+ }
692
+ }
693
+ });
694
+ }
695
+ generateId() {
696
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
697
+ }
698
+ /**
699
+ * Get global history of all changes (available in IndexedDB and Cloud modes)
700
+ */
701
+ getGlobalHistory() {
702
+ return [...this._history];
703
+ }
704
+ /**
705
+ * Check if history tracking is enabled
706
+ */
707
+ get historyEnabled() {
708
+ return this._historyEnabled;
709
+ }
710
+ /**
711
+ * Restore to a specific version (time travel)
712
+ * Note: Full implementation requires storing update binaries, which is not yet implemented.
713
+ * @returns false - not yet fully implemented
714
+ */
715
+ restoreToVersion(_versionId) {
716
+ console.warn("restoreToVersion: Full implementation requires storing update binaries. Not yet implemented.");
717
+ return false;
718
+ }
719
+ get accessLevel() {
720
+ return this._accessLevel;
721
+ }
722
+ get hasSystemAccess() {
723
+ return this._accessLevel === "system";
724
+ }
725
+ async _initCloud() {
726
+ if (!this._cloudConfig) {
727
+ return;
728
+ }
729
+ const CloudAdapter2 = await this._getCloudAdapterClass();
730
+ if (!this._cloudConfig.baseUrl) ;
731
+ const baseUrl = (this._cloudConfig.baseUrl || "https://api.mindcache.dev").replace("https://", "wss://").replace("http://", "ws://");
732
+ const adapter = new CloudAdapter2({
733
+ instanceId: this._cloudConfig.instanceId,
734
+ projectId: this._cloudConfig.projectId || "default",
735
+ baseUrl,
736
+ apiKey: this._cloudConfig.apiKey
737
+ });
738
+ if (this._cloudConfig.tokenProvider) {
739
+ adapter.setTokenProvider(this._cloudConfig.tokenProvider);
740
+ } else if (this._cloudConfig.tokenEndpoint) {
741
+ const tokenEndpoint = this._cloudConfig.tokenEndpoint;
742
+ const instanceId = this._cloudConfig.instanceId;
743
+ let resolvedBaseUrl;
744
+ if (tokenEndpoint.startsWith("http://") || tokenEndpoint.startsWith("https://")) {
745
+ resolvedBaseUrl = tokenEndpoint;
746
+ } else if (typeof window !== "undefined" && window.location?.origin) {
747
+ resolvedBaseUrl = `${window.location.origin}${tokenEndpoint.startsWith("/") ? "" : "/"}${tokenEndpoint}`;
748
+ } else {
749
+ resolvedBaseUrl = tokenEndpoint;
750
+ }
751
+ adapter.setTokenProvider(async () => {
752
+ const url = resolvedBaseUrl.includes("?") ? `${resolvedBaseUrl}&instanceId=${instanceId}` : `${resolvedBaseUrl}?instanceId=${instanceId}`;
753
+ const response = await fetch(url);
754
+ if (!response.ok) {
755
+ throw new Error("Failed to get token");
756
+ }
757
+ const data = await response.json();
758
+ return data.token;
759
+ });
760
+ }
761
+ adapter.on("connected", () => {
762
+ this._connectionState = "connected";
763
+ this.notifyGlobalListeners();
764
+ });
765
+ adapter.on("disconnected", () => {
766
+ this._connectionState = "disconnected";
767
+ this.notifyGlobalListeners();
768
+ });
769
+ adapter.on("error", () => {
770
+ this._connectionState = "error";
771
+ this.notifyGlobalListeners();
772
+ });
773
+ adapter.on("synced", () => {
774
+ this._isLoaded = true;
775
+ this.notifyGlobalListeners();
776
+ });
777
+ adapter.attach(this);
778
+ this._cloudAdapter = adapter;
779
+ this._connectionState = "connecting";
780
+ adapter.connect();
781
+ }
782
+ async _initYIndexedDB(dbName) {
783
+ if (typeof window === "undefined") {
784
+ return;
785
+ }
786
+ this._idbProvider = new IndexeddbPersistence(dbName, this.doc);
787
+ return new Promise((resolve) => {
788
+ if (!this._idbProvider) {
789
+ return resolve();
790
+ }
791
+ this._idbProvider.on("synced", () => {
792
+ this._isLoaded = true;
793
+ resolve();
794
+ });
795
+ });
796
+ }
797
+ // Legacy IndexedDB method stub
798
+ async _initIndexedDB(_config) {
799
+ }
800
+ async _getIndexedDBAdapterClass() {
801
+ const { IndexedDBAdapter: IndexedDBAdapter2 } = await Promise.resolve().then(() => (init_IndexedDBAdapter(), IndexedDBAdapter_exports));
802
+ return IndexedDBAdapter2;
803
+ }
804
+ get connectionState() {
805
+ return this._connectionState;
806
+ }
807
+ get isLoaded() {
808
+ return this._isLoaded;
809
+ }
810
+ async _getCloudAdapterClass() {
811
+ const { CloudAdapter: CloudAdapter2 } = await Promise.resolve().then(() => (init_CloudAdapter(), CloudAdapter_exports));
812
+ return CloudAdapter2;
813
+ }
814
+ get isCloud() {
815
+ return this._cloudConfig !== null;
816
+ }
817
+ /**
818
+ * Browser network status. Returns true if online or in local-only mode.
819
+ * In cloud mode, this updates instantly when network status changes.
820
+ */
821
+ get isOnline() {
822
+ if (!this._cloudAdapter) {
823
+ if (typeof navigator !== "undefined") {
824
+ return navigator.onLine;
825
+ }
826
+ return true;
827
+ }
828
+ return this._cloudAdapter.isOnline;
829
+ }
830
+ async waitForSync() {
831
+ if (this._isLoaded) {
832
+ return;
833
+ }
834
+ if (this._initPromise) {
835
+ await this._initPromise;
836
+ }
837
+ if (this._isLoaded) {
838
+ return;
839
+ }
840
+ return new Promise((resolve) => {
841
+ if (this._isLoaded) {
842
+ return resolve();
843
+ }
844
+ const interval = setInterval(() => {
845
+ if (this._isLoaded) {
846
+ clearInterval(interval);
847
+ resolve();
848
+ }
849
+ }, 100);
850
+ });
851
+ }
852
+ disconnect() {
853
+ if (this._cloudAdapter) {
854
+ this._cloudAdapter.disconnect();
855
+ this._cloudAdapter.detach();
856
+ this._cloudAdapter = null;
857
+ this._connectionState = "disconnected";
858
+ }
859
+ if (this._idbProvider) {
860
+ this._idbProvider.destroy();
861
+ this._idbProvider = null;
862
+ }
863
+ }
864
+ // Serialize state
865
+ serialize() {
866
+ const json = {};
867
+ for (const [key, val] of this.rootMap) {
868
+ const entryMap = val;
869
+ const attrs = entryMap.get("attributes");
870
+ let value = entryMap.get("value");
871
+ if (attrs?.type === "document" && value instanceof Y.Text) {
872
+ value = value.toString();
873
+ }
874
+ json[key] = {
875
+ value,
876
+ attributes: attrs
877
+ };
878
+ }
879
+ return json;
880
+ }
881
+ // Deserialize state (for IndexedDBAdapter compatibility)
882
+ deserialize(data) {
883
+ if (!data || typeof data !== "object") {
884
+ return;
885
+ }
886
+ this.doc.transact(() => {
887
+ for (const key of this.rootMap.keys()) {
888
+ this.rootMap.delete(key);
889
+ }
890
+ for (const [key, entry] of Object.entries(data)) {
891
+ if (key.startsWith("$")) {
892
+ continue;
893
+ }
894
+ const entryMap = new Y.Map();
895
+ this.rootMap.set(key, entryMap);
896
+ entryMap.set("value", entry.value);
897
+ const attrs = entry.attributes || {};
898
+ const normalizedAttrs = {
899
+ type: attrs.type || "text",
900
+ contentType: attrs.contentType,
901
+ contentTags: attrs.contentTags || [],
902
+ systemTags: this.normalizeSystemTags(attrs.systemTags || []),
903
+ zIndex: attrs.zIndex ?? 0
904
+ };
905
+ entryMap.set("attributes", normalizedAttrs);
906
+ }
907
+ });
908
+ }
909
+ encodeFileToBase64(file) {
910
+ return new Promise((resolve, reject) => {
911
+ if (typeof FileReader !== "undefined") {
912
+ const reader = new FileReader();
913
+ reader.onload = () => {
914
+ const result = reader.result;
915
+ const base64Data = result.split(",")[1];
916
+ resolve(base64Data);
917
+ };
918
+ reader.onerror = reject;
919
+ reader.readAsDataURL(file);
920
+ } else {
921
+ reject(new Error("FileReader not available in Node.js environment. Use set_base64() method instead."));
922
+ }
923
+ });
924
+ }
925
+ createDataUrl(base64Data, contentType) {
926
+ return `data:${contentType};base64,${base64Data}`;
927
+ }
928
+ validateContentType(type, contentType) {
929
+ if (type === "text" || type === "json") {
930
+ return true;
931
+ }
932
+ if (!contentType) {
933
+ return false;
934
+ }
935
+ if (type === "image") {
936
+ return contentType.startsWith("image/");
937
+ }
938
+ if (type === "file") {
939
+ return true;
940
+ }
941
+ return false;
942
+ }
943
+ // InjectSTM replacement (private helper)
944
+ _injectSTMInternal(template, _processingStack) {
945
+ return template.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
946
+ const val = this.get_value(key.trim(), _processingStack);
947
+ return val !== void 0 ? String(val) : `{{${key}}}`;
948
+ });
949
+ }
950
+ /**
951
+ * Replace {{key}} placeholders in a template string with values from MindCache.
952
+ * @param template The template string with {{key}} placeholders
953
+ * @returns The template with placeholders replaced by values
954
+ */
955
+ injectSTM(template) {
956
+ return this._injectSTMInternal(template, /* @__PURE__ */ new Set());
957
+ }
958
+ // Public API Methods
959
+ getAll() {
960
+ const result = {};
961
+ for (const [key] of this.rootMap) {
962
+ result[key] = this.get_value(key);
963
+ }
964
+ result["$date"] = this.get_value("$date");
965
+ result["$time"] = this.get_value("$time");
966
+ return result;
967
+ }
968
+ /**
969
+ * Get all entries with their full structure (value + attributes).
970
+ * Use this for UI/admin interfaces that need to display key properties.
971
+ * Unlike serialize(), this format is stable and won't change.
972
+ */
973
+ getAllEntries() {
974
+ const result = {};
975
+ for (const [key] of this.rootMap) {
976
+ const value = this.get_value(key);
977
+ const attributes = this.get_attributes(key);
978
+ if (attributes) {
979
+ result[key] = { value, attributes };
980
+ }
981
+ }
982
+ return result;
983
+ }
984
+ get_value(key, _processingStack) {
985
+ if (key === "$date") {
986
+ const today = /* @__PURE__ */ new Date();
987
+ return today.toISOString().split("T")[0];
988
+ }
989
+ if (key === "$time") {
990
+ const now = /* @__PURE__ */ new Date();
991
+ return now.toTimeString().split(" ")[0];
992
+ }
993
+ if (key === "$version") {
994
+ return this.version;
995
+ }
996
+ const entryMap = this.rootMap.get(key);
997
+ if (!entryMap) {
998
+ return void 0;
999
+ }
1000
+ const attributes = entryMap.get("attributes");
1001
+ const value = entryMap.get("value");
1002
+ if (attributes?.type === "document" && value instanceof Y.Text) {
1003
+ return value.toString();
1004
+ }
1005
+ if (_processingStack && _processingStack.has(key)) {
1006
+ return `{{${key}}}`;
1007
+ }
1008
+ if (attributes?.systemTags?.includes("ApplyTemplate")) {
1009
+ if (typeof value === "string") {
1010
+ const stack = _processingStack || /* @__PURE__ */ new Set();
1011
+ stack.add(key);
1012
+ return this._injectSTMInternal(value, stack);
1013
+ }
1014
+ }
1015
+ return value;
1016
+ }
1017
+ get_attributes(key) {
1018
+ if (key === "$date" || key === "$time" || key === "$version") {
1019
+ return {
1020
+ type: "text",
1021
+ contentTags: [],
1022
+ systemTags: ["SystemPrompt", "protected"],
1023
+ zIndex: 999999
1024
+ };
1025
+ }
1026
+ const entryMap = this.rootMap.get(key);
1027
+ return entryMap ? entryMap.get("attributes") : void 0;
1028
+ }
1029
+ /**
1030
+ * Update only the attributes of a key without modifying the value.
1031
+ * Useful for updating tags, permissions etc. on document type keys.
1032
+ */
1033
+ set_attributes(key, attributes) {
1034
+ if (key === "$date" || key === "$time" || key === "$version") {
1035
+ return;
1036
+ }
1037
+ const entryMap = this.rootMap.get(key);
1038
+ if (!entryMap) {
1039
+ return;
1040
+ }
1041
+ this.doc.transact(() => {
1042
+ const existingAttrs = entryMap.get("attributes");
1043
+ const mergedAttrs = { ...existingAttrs, ...attributes };
1044
+ if (mergedAttrs.systemTags) {
1045
+ mergedAttrs.systemTags = this.normalizeSystemTags(mergedAttrs.systemTags);
1046
+ }
1047
+ entryMap.set("attributes", mergedAttrs);
1048
+ const currentValue = entryMap.get("value");
1049
+ if (mergedAttrs.type === "document" && !(currentValue instanceof Y.Text)) {
1050
+ const strValue = typeof currentValue === "string" ? currentValue : String(currentValue ?? "");
1051
+ entryMap.set("value", new Y.Text(strValue));
1052
+ this.getUndoManager(key);
1053
+ } else if (mergedAttrs.type !== "document" && currentValue instanceof Y.Text) {
1054
+ entryMap.set("value", currentValue.toString());
1055
+ }
1056
+ });
1057
+ }
1058
+ set_value(key, value, attributes) {
1059
+ if (key === "$date" || key === "$time" || key === "$version") {
1060
+ return;
1061
+ }
1062
+ const existingEntry = this.rootMap.get(key);
1063
+ if (existingEntry) {
1064
+ const existingAttrs = existingEntry.get("attributes");
1065
+ if (existingAttrs?.type === "document") {
1066
+ if (!attributes?.type || attributes.type === "document") {
1067
+ if (typeof value === "string") {
1068
+ this._replaceDocumentText(key, value);
1069
+ }
1070
+ if (attributes) {
1071
+ this.set_attributes(key, attributes);
1072
+ }
1073
+ return;
1074
+ }
1075
+ }
1076
+ }
1077
+ if (!existingEntry && attributes?.type === "document") {
1078
+ this.set_document(key, typeof value === "string" ? value : "", attributes);
1079
+ return;
1080
+ }
1081
+ let entryMap = this.rootMap.get(key);
1082
+ const isNewEntry = !entryMap;
1083
+ if (isNewEntry) {
1084
+ entryMap = new Y.Map();
1085
+ this.rootMap.set(key, entryMap);
1086
+ }
1087
+ this.getUndoManager(key);
1088
+ this.doc.transact(() => {
1089
+ const oldAttributes = isNewEntry ? {
1090
+ ...DEFAULT_KEY_ATTRIBUTES
1091
+ } : entryMap.get("attributes");
1092
+ const finalAttributes = attributes ? { ...oldAttributes, ...attributes } : oldAttributes;
1093
+ let normalizedAttributes = { ...finalAttributes };
1094
+ if (finalAttributes.systemTags) {
1095
+ normalizedAttributes.systemTags = this.normalizeSystemTags(finalAttributes.systemTags);
1096
+ }
1097
+ let valueToSet = value;
1098
+ if (normalizedAttributes.type === "document" && !(valueToSet instanceof Y.Text)) {
1099
+ valueToSet = new Y.Text(typeof value === "string" ? value : String(value ?? ""));
1100
+ } else if (normalizedAttributes.type !== "document" && valueToSet instanceof Y.Text) {
1101
+ valueToSet = valueToSet.toString();
1102
+ }
1103
+ entryMap.set("value", valueToSet);
1104
+ entryMap.set("attributes", normalizedAttributes);
1105
+ });
1106
+ }
1107
+ /**
1108
+ * LLM-safe method to write a value to a key.
1109
+ * This method:
1110
+ * - Only updates the value, never modifies attributes/systemTags
1111
+ * - Checks LLMWrite permission before writing
1112
+ * - Returns false if key doesn't exist or lacks LLMWrite permission
1113
+ *
1114
+ * Used by create_vercel_ai_tools() to prevent LLMs from escalating privileges.
1115
+ */
1116
+ llm_set_key(key, value) {
1117
+ if (key === "$date" || key === "$time" || key === "$version") {
1118
+ return false;
1119
+ }
1120
+ const entryMap = this.rootMap.get(key);
1121
+ if (!entryMap) {
1122
+ return false;
1123
+ }
1124
+ const attributes = entryMap.get("attributes");
1125
+ if (!attributes?.systemTags?.includes("LLMWrite")) {
1126
+ return false;
1127
+ }
1128
+ if (attributes.type === "document") {
1129
+ if (typeof value === "string") {
1130
+ this._replaceDocumentText(key, value);
1131
+ }
1132
+ return true;
1133
+ }
1134
+ this.doc.transact(() => {
1135
+ entryMap.set("value", value);
1136
+ });
1137
+ return true;
1138
+ }
1139
+ delete_key(key) {
1140
+ if (key === "$date" || key === "$time") {
1141
+ return;
1142
+ }
1143
+ this.rootMap.delete(key);
1144
+ }
1145
+ clear() {
1146
+ const keys = Array.from(this.rootMap.keys());
1147
+ this.doc.transact(() => {
1148
+ keys.forEach((k) => this.rootMap.delete(k));
1149
+ });
1150
+ }
1151
+ // ============================================
1152
+ // Restored Methods (from v2.x)
1153
+ // ============================================
1154
+ /**
1155
+ * Check if a key exists in MindCache.
1156
+ */
1157
+ has(key) {
1158
+ if (key === "$date" || key === "$time" || key === "$version") {
1159
+ return true;
1160
+ }
1161
+ return this.rootMap.has(key);
1162
+ }
1163
+ /**
1164
+ * Delete a key from MindCache.
1165
+ * @returns true if the key existed and was deleted
1166
+ */
1167
+ delete(key) {
1168
+ if (key === "$date" || key === "$time" || key === "$version") {
1169
+ return false;
1170
+ }
1171
+ if (!this.rootMap.has(key)) {
1172
+ return false;
1173
+ }
1174
+ this.rootMap.delete(key);
1175
+ this.notifyGlobalListeners();
1176
+ if (this.listeners[key]) {
1177
+ this.listeners[key].forEach((listener) => listener(void 0));
1178
+ }
1179
+ return true;
1180
+ }
1181
+ /** @deprecated Use get_value instead */
1182
+ get(key) {
1183
+ return this.get_value(key);
1184
+ }
1185
+ /** @deprecated Use set_value instead */
1186
+ set(key, value) {
1187
+ this.set_value(key, value);
1188
+ }
1189
+ /**
1190
+ * Update multiple values at once from an object.
1191
+ * @deprecated Use set_value for individual keys
1192
+ */
1193
+ update(data) {
1194
+ this.doc.transact(() => {
1195
+ for (const [key, value] of Object.entries(data)) {
1196
+ if (key !== "$date" && key !== "$time" && key !== "$version") {
1197
+ this.set_value(key, value);
1198
+ }
1199
+ }
1200
+ });
1201
+ this.notifyGlobalListeners();
1202
+ }
1203
+ /**
1204
+ * Get the number of keys in MindCache.
1205
+ */
1206
+ size() {
1207
+ return this.rootMap.size + 2;
1208
+ }
1209
+ /**
1210
+ * Get all keys in MindCache (including temporal keys).
1211
+ */
1212
+ keys() {
1213
+ const keys = Array.from(this.rootMap.keys());
1214
+ keys.push("$date", "$time");
1215
+ return keys;
1216
+ }
1217
+ /**
1218
+ * Get all values in MindCache (including temporal values).
1219
+ */
1220
+ values() {
1221
+ const result = [];
1222
+ for (const [key] of this.rootMap) {
1223
+ result.push(this.get_value(key));
1224
+ }
1225
+ result.push(this.get_value("$date"));
1226
+ result.push(this.get_value("$time"));
1227
+ return result;
1228
+ }
1229
+ /**
1230
+ * Get all key-value entries (including temporal entries).
1231
+ */
1232
+ entries() {
1233
+ const result = [];
1234
+ for (const [key] of this.rootMap) {
1235
+ result.push([key, this.get_value(key)]);
1236
+ }
1237
+ result.push(["$date", this.get_value("$date")]);
1238
+ result.push(["$time", this.get_value("$time")]);
1239
+ return result;
1240
+ }
1241
+ /**
1242
+ * Unsubscribe from key changes.
1243
+ * @deprecated Use the cleanup function returned by subscribe() instead
1244
+ */
1245
+ unsubscribe(key, listener) {
1246
+ if (this.listeners[key]) {
1247
+ this.listeners[key] = this.listeners[key].filter((l) => l !== listener);
1248
+ }
1249
+ }
1250
+ /**
1251
+ * Get the STM as a formatted string for LLM context.
1252
+ * @deprecated Use get_system_prompt() instead
1253
+ */
1254
+ getSTM() {
1255
+ return this.get_system_prompt();
1256
+ }
1257
+ /**
1258
+ * Get the STM as an object with values directly (no attributes).
1259
+ * Includes system keys ($date, $time).
1260
+ * @deprecated Use getAll() for full STM format
1261
+ */
1262
+ getSTMObject() {
1263
+ const result = {};
1264
+ for (const [key] of this.rootMap) {
1265
+ result[key] = this.get_value(key);
1266
+ }
1267
+ result["$date"] = this.get_value("$date");
1268
+ result["$time"] = this.get_value("$time");
1269
+ return result;
1270
+ }
1271
+ /**
1272
+ * Add a content tag to a key.
1273
+ * @returns true if the tag was added, false if key doesn't exist or tag already exists
1274
+ */
1275
+ addTag(key, tag) {
1276
+ if (key === "$date" || key === "$time" || key === "$version") {
1277
+ return false;
1278
+ }
1279
+ const entryMap = this.rootMap.get(key);
1280
+ if (!entryMap) {
1281
+ return false;
1282
+ }
1283
+ const attributes = entryMap.get("attributes");
1284
+ const contentTags = attributes?.contentTags || [];
1285
+ if (contentTags.includes(tag)) {
1286
+ return false;
1287
+ }
1288
+ this.doc.transact(() => {
1289
+ const newContentTags = [...contentTags, tag];
1290
+ entryMap.set("attributes", {
1291
+ ...attributes,
1292
+ contentTags: newContentTags,
1293
+ tags: newContentTags
1294
+ // Sync legacy tags array
1295
+ });
1296
+ });
1297
+ this.notifyGlobalListeners();
1298
+ return true;
1299
+ }
1300
+ /**
1301
+ * Remove a content tag from a key.
1302
+ * @returns true if the tag was removed
1303
+ */
1304
+ removeTag(key, tag) {
1305
+ if (key === "$date" || key === "$time" || key === "$version") {
1306
+ return false;
1307
+ }
1308
+ const entryMap = this.rootMap.get(key);
1309
+ if (!entryMap) {
1310
+ return false;
1311
+ }
1312
+ const attributes = entryMap.get("attributes");
1313
+ const contentTags = attributes?.contentTags || [];
1314
+ const tagIndex = contentTags.indexOf(tag);
1315
+ if (tagIndex === -1) {
1316
+ return false;
1317
+ }
1318
+ this.doc.transact(() => {
1319
+ const newContentTags = contentTags.filter((t) => t !== tag);
1320
+ entryMap.set("attributes", {
1321
+ ...attributes,
1322
+ contentTags: newContentTags,
1323
+ tags: newContentTags
1324
+ // Sync legacy tags array
1325
+ });
1326
+ });
1327
+ this.notifyGlobalListeners();
1328
+ return true;
1329
+ }
1330
+ /**
1331
+ * Get all content tags for a key.
1332
+ */
1333
+ getTags(key) {
1334
+ if (key === "$date" || key === "$time" || key === "$version") {
1335
+ return [];
1336
+ }
1337
+ const entryMap = this.rootMap.get(key);
1338
+ if (!entryMap) {
1339
+ return [];
1340
+ }
1341
+ const attributes = entryMap.get("attributes");
1342
+ return attributes?.contentTags || [];
1343
+ }
1344
+ /**
1345
+ * Get all unique content tags across all keys.
1346
+ */
1347
+ getAllTags() {
1348
+ const allTags = /* @__PURE__ */ new Set();
1349
+ for (const [, val] of this.rootMap) {
1350
+ const entryMap = val;
1351
+ const attributes = entryMap.get("attributes");
1352
+ if (attributes?.contentTags) {
1353
+ attributes.contentTags.forEach((tag) => allTags.add(tag));
1354
+ }
1355
+ }
1356
+ return Array.from(allTags);
1357
+ }
1358
+ /**
1359
+ * Check if a key has a specific content tag.
1360
+ */
1361
+ hasTag(key, tag) {
1362
+ if (key === "$date" || key === "$time" || key === "$version") {
1363
+ return false;
1364
+ }
1365
+ const entryMap = this.rootMap.get(key);
1366
+ if (!entryMap) {
1367
+ return false;
1368
+ }
1369
+ const attributes = entryMap.get("attributes");
1370
+ return attributes?.contentTags?.includes(tag) || false;
1371
+ }
1372
+ /**
1373
+ * Get all keys with a specific content tag as formatted string.
1374
+ */
1375
+ getTagged(tag) {
1376
+ const entries = [];
1377
+ const keys = this.getSortedKeys();
1378
+ keys.forEach((key) => {
1379
+ if (this.hasTag(key, tag)) {
1380
+ entries.push([key, this.get_value(key)]);
1381
+ }
1382
+ });
1383
+ return entries.map(([key, value]) => `${key}: ${value}`).join(", ");
1384
+ }
1385
+ /**
1386
+ * Get array of keys with a specific content tag.
1387
+ */
1388
+ getKeysByTag(tag) {
1389
+ const keys = this.getSortedKeys();
1390
+ return keys.filter((key) => this.hasTag(key, tag));
1391
+ }
1392
+ // ============================================
1393
+ // System Tag Methods (requires system access level)
1394
+ // ============================================
1395
+ /**
1396
+ * Add a system tag to a key (requires system access).
1397
+ * System tags: 'SystemPrompt', 'LLMRead', 'LLMWrite', 'readonly', 'protected', 'ApplyTemplate'
1398
+ */
1399
+ systemAddTag(key, tag) {
1400
+ if (!this.hasSystemAccess) {
1401
+ console.warn("MindCache: systemAddTag requires system access level");
1402
+ return false;
1403
+ }
1404
+ if (key === "$date" || key === "$time" || key === "$version") {
1405
+ return false;
1406
+ }
1407
+ const entryMap = this.rootMap.get(key);
1408
+ if (!entryMap) {
1409
+ return false;
1410
+ }
1411
+ const attributes = entryMap.get("attributes");
1412
+ const systemTags = attributes?.systemTags || [];
1413
+ if (systemTags.includes(tag)) {
1414
+ return false;
1415
+ }
1416
+ this.doc.transact(() => {
1417
+ const newSystemTags = [...systemTags, tag];
1418
+ const normalizedTags = this.normalizeSystemTags(newSystemTags);
1419
+ entryMap.set("attributes", {
1420
+ ...attributes,
1421
+ systemTags: normalizedTags
1422
+ });
1423
+ });
1424
+ this.notifyGlobalListeners();
1425
+ return true;
1426
+ }
1427
+ /**
1428
+ * Remove a system tag from a key (requires system access).
1429
+ */
1430
+ systemRemoveTag(key, tag) {
1431
+ if (!this.hasSystemAccess) {
1432
+ console.warn("MindCache: systemRemoveTag requires system access level");
1433
+ return false;
1434
+ }
1435
+ if (key === "$date" || key === "$time" || key === "$version") {
1436
+ return false;
1437
+ }
1438
+ const entryMap = this.rootMap.get(key);
1439
+ if (!entryMap) {
1440
+ return false;
1441
+ }
1442
+ const attributes = entryMap.get("attributes");
1443
+ const systemTags = attributes?.systemTags || [];
1444
+ const tagIndex = systemTags.indexOf(tag);
1445
+ if (tagIndex === -1) {
1446
+ return false;
1447
+ }
1448
+ this.doc.transact(() => {
1449
+ const newSystemTags = systemTags.filter((t) => t !== tag);
1450
+ entryMap.set("attributes", {
1451
+ ...attributes,
1452
+ systemTags: newSystemTags
1453
+ });
1454
+ });
1455
+ this.notifyGlobalListeners();
1456
+ return true;
1457
+ }
1458
+ /**
1459
+ * Get all system tags for a key (requires system access).
1460
+ */
1461
+ systemGetTags(key) {
1462
+ if (!this.hasSystemAccess) {
1463
+ console.warn("MindCache: systemGetTags requires system access level");
1464
+ return [];
1465
+ }
1466
+ if (key === "$date" || key === "$time" || key === "$version") {
1467
+ return [];
1468
+ }
1469
+ const entryMap = this.rootMap.get(key);
1470
+ if (!entryMap) {
1471
+ return [];
1472
+ }
1473
+ const attributes = entryMap.get("attributes");
1474
+ return attributes?.systemTags || [];
1475
+ }
1476
+ /**
1477
+ * Check if a key has a specific system tag (requires system access).
1478
+ */
1479
+ systemHasTag(key, tag) {
1480
+ if (!this.hasSystemAccess) {
1481
+ console.warn("MindCache: systemHasTag requires system access level");
1482
+ return false;
1483
+ }
1484
+ if (key === "$date" || key === "$time" || key === "$version") {
1485
+ return false;
1486
+ }
1487
+ const entryMap = this.rootMap.get(key);
1488
+ if (!entryMap) {
1489
+ return false;
1490
+ }
1491
+ const attributes = entryMap.get("attributes");
1492
+ return attributes?.systemTags?.includes(tag) || false;
1493
+ }
1494
+ /**
1495
+ * Set all system tags for a key at once (requires system access).
1496
+ */
1497
+ systemSetTags(key, tags) {
1498
+ if (!this.hasSystemAccess) {
1499
+ console.warn("MindCache: systemSetTags requires system access level");
1500
+ return false;
1501
+ }
1502
+ if (key === "$date" || key === "$time" || key === "$version") {
1503
+ return false;
1504
+ }
1505
+ const entryMap = this.rootMap.get(key);
1506
+ if (!entryMap) {
1507
+ return false;
1508
+ }
1509
+ this.doc.transact(() => {
1510
+ const attributes = entryMap.get("attributes");
1511
+ entryMap.set("attributes", {
1512
+ ...attributes,
1513
+ systemTags: [...tags]
1514
+ });
1515
+ });
1516
+ this.notifyGlobalListeners();
1517
+ return true;
1518
+ }
1519
+ /**
1520
+ * Get all keys with a specific system tag (requires system access).
1521
+ */
1522
+ systemGetKeysByTag(tag) {
1523
+ if (!this.hasSystemAccess) {
1524
+ console.warn("MindCache: systemGetKeysByTag requires system access level");
1525
+ return [];
1526
+ }
1527
+ const keys = this.getSortedKeys();
1528
+ return keys.filter((key) => this.systemHasTag(key, tag));
1529
+ }
1530
+ /**
1531
+ * Helper to get sorted keys (by zIndex).
1532
+ */
1533
+ getSortedKeys() {
1534
+ const entries = [];
1535
+ for (const [key, val] of this.rootMap) {
1536
+ const entryMap = val;
1537
+ const attributes = entryMap.get("attributes");
1538
+ entries.push({ key, zIndex: attributes?.zIndex ?? 0 });
1539
+ }
1540
+ return entries.sort((a, b) => a.zIndex - b.zIndex).map((e) => e.key);
1541
+ }
1542
+ /**
1543
+ * Serialize to JSON string.
1544
+ */
1545
+ toJSON() {
1546
+ return JSON.stringify(this.serialize());
1547
+ }
1548
+ /**
1549
+ * Deserialize from JSON string.
1550
+ */
1551
+ fromJSON(jsonString) {
1552
+ try {
1553
+ const data = JSON.parse(jsonString);
1554
+ this.deserialize(data);
1555
+ } catch (error) {
1556
+ console.error("MindCache: Failed to deserialize JSON:", error);
1557
+ }
1558
+ }
1559
+ /**
1560
+ * Export to Markdown format.
1561
+ */
1562
+ toMarkdown() {
1563
+ const now = /* @__PURE__ */ new Date();
1564
+ const lines = [];
1565
+ const appendixEntries = [];
1566
+ let appendixCounter = 0;
1567
+ lines.push("# MindCache STM Export");
1568
+ lines.push("");
1569
+ lines.push(`Export Date: ${now.toISOString().split("T")[0]}`);
1570
+ lines.push("");
1571
+ lines.push("---");
1572
+ lines.push("");
1573
+ lines.push("## STM Entries");
1574
+ lines.push("");
1575
+ const sortedKeys = this.getSortedKeys();
1576
+ sortedKeys.forEach((key) => {
1577
+ const entryMap = this.rootMap.get(key);
1578
+ if (!entryMap) {
1579
+ return;
1580
+ }
1581
+ const attributes = entryMap.get("attributes");
1582
+ const value = entryMap.get("value");
1583
+ if (attributes?.systemTags?.includes("protected")) {
1584
+ return;
1585
+ }
1586
+ lines.push(`### ${key}`);
1587
+ const entryType = attributes?.type || "text";
1588
+ lines.push(`- **Type**: \`${entryType}\``);
1589
+ lines.push(`- **System Tags**: \`${attributes?.systemTags?.join(", ") || "none"}\``);
1590
+ lines.push(`- **Z-Index**: \`${attributes?.zIndex ?? 0}\``);
1591
+ if (attributes?.contentTags && attributes.contentTags.length > 0) {
1592
+ lines.push(`- **Tags**: \`${attributes.contentTags.join("`, `")}\``);
1593
+ }
1594
+ if (attributes?.contentType) {
1595
+ lines.push(`- **Content Type**: \`${attributes.contentType}\``);
1596
+ }
1597
+ if (entryType === "image" || entryType === "file") {
1598
+ const label = String.fromCharCode(65 + appendixCounter);
1599
+ appendixCounter++;
1600
+ lines.push(`- **Value**: [See Appendix ${label}]`);
1601
+ appendixEntries.push({
1602
+ key,
1603
+ type: entryType,
1604
+ contentType: attributes?.contentType || "application/octet-stream",
1605
+ base64: value,
1606
+ label
1607
+ });
1608
+ } else if (entryType === "json") {
1609
+ lines.push("- **Value**:");
1610
+ lines.push("```json");
1611
+ try {
1612
+ const jsonValue = typeof value === "string" ? value : JSON.stringify(value, null, 2);
1613
+ lines.push(jsonValue);
1614
+ } catch {
1615
+ lines.push(String(value));
1616
+ }
1617
+ lines.push("```");
1618
+ } else {
1619
+ lines.push("- **Value**:");
1620
+ lines.push("```");
1621
+ lines.push(String(value));
1622
+ lines.push("```");
1623
+ }
1624
+ lines.push("");
1625
+ });
1626
+ if (appendixEntries.length > 0) {
1627
+ lines.push("---");
1628
+ lines.push("");
1629
+ lines.push("## Appendix: Binary Data");
1630
+ lines.push("");
1631
+ appendixEntries.forEach((entry) => {
1632
+ lines.push(`### Appendix ${entry.label}: ${entry.key}`);
1633
+ lines.push(`- **Type**: \`${entry.type}\``);
1634
+ lines.push(`- **Content Type**: \`${entry.contentType}\``);
1635
+ lines.push("- **Base64 Data**:");
1636
+ lines.push("```");
1637
+ lines.push(entry.base64);
1638
+ lines.push("```");
1639
+ lines.push("");
1640
+ });
1641
+ }
1642
+ return lines.join("\n");
1643
+ }
1644
+ /**
1645
+ * Import from Markdown format.
1646
+ */
1647
+ fromMarkdown(markdown) {
1648
+ const lines = markdown.split("\n");
1649
+ let currentKey = null;
1650
+ let currentAttributes = {};
1651
+ let currentValue = null;
1652
+ let inCodeBlock = false;
1653
+ let codeBlockContent = [];
1654
+ for (const line of lines) {
1655
+ if (line.startsWith("### ") && !line.startsWith("### Appendix")) {
1656
+ if (currentKey && currentValue !== null) {
1657
+ this.set_value(currentKey, currentValue.trim(), currentAttributes);
1658
+ }
1659
+ currentKey = line.substring(4).trim();
1660
+ currentAttributes = {};
1661
+ currentValue = null;
1662
+ continue;
1663
+ }
1664
+ if (line.startsWith("### Appendix ")) {
1665
+ const match = line.match(/### Appendix ([A-Z]): (.+)/);
1666
+ if (match) {
1667
+ currentKey = match[2];
1668
+ }
1669
+ continue;
1670
+ }
1671
+ if (line.startsWith("- **Type**:")) {
1672
+ const type = line.match(/`(.+)`/)?.[1];
1673
+ if (type) {
1674
+ currentAttributes.type = type;
1675
+ }
1676
+ continue;
1677
+ }
1678
+ if (line.startsWith("- **System Tags**:")) {
1679
+ const tagsStr = line.match(/`([^`]+)`/)?.[1] || "";
1680
+ if (tagsStr !== "none") {
1681
+ currentAttributes.systemTags = tagsStr.split(", ").filter((t) => t);
1682
+ }
1683
+ continue;
1684
+ }
1685
+ if (line.startsWith("- **Z-Index**:")) {
1686
+ const zIndex = parseInt(line.match(/`(\d+)`/)?.[1] || "0", 10);
1687
+ currentAttributes.zIndex = zIndex;
1688
+ continue;
1689
+ }
1690
+ if (line.startsWith("- **Tags**:")) {
1691
+ const tags = line.match(/`([^`]+)`/g)?.map((t) => t.slice(1, -1)) || [];
1692
+ currentAttributes.contentTags = tags;
1693
+ continue;
1694
+ }
1695
+ if (line.startsWith("- **Content Type**:")) {
1696
+ currentAttributes.contentType = line.match(/`(.+)`/)?.[1];
1697
+ continue;
1698
+ }
1699
+ if (line.startsWith("- **Value**:") && !line.includes("[See Appendix")) {
1700
+ const afterValue = line.substring(12).trim();
1701
+ if (afterValue === "") {
1702
+ currentValue = "";
1703
+ } else if (afterValue === "```" || afterValue === "```json") {
1704
+ inCodeBlock = true;
1705
+ codeBlockContent = [];
1706
+ currentValue = "";
1707
+ } else if (afterValue.startsWith("```")) {
1708
+ inCodeBlock = true;
1709
+ codeBlockContent = [afterValue.substring(3)];
1710
+ currentValue = "";
1711
+ } else {
1712
+ currentValue = afterValue;
1713
+ }
1714
+ continue;
1715
+ }
1716
+ const trimmedLine = line.trim();
1717
+ if (trimmedLine === "```json" || trimmedLine === "```") {
1718
+ if (inCodeBlock) {
1719
+ inCodeBlock = false;
1720
+ if (currentKey && codeBlockContent.length > 0) {
1721
+ currentValue = codeBlockContent.join("\n");
1722
+ }
1723
+ codeBlockContent = [];
1724
+ } else {
1725
+ inCodeBlock = true;
1726
+ codeBlockContent = [];
1727
+ }
1728
+ continue;
1729
+ }
1730
+ if (inCodeBlock) {
1731
+ codeBlockContent.push(line);
1732
+ } else if (currentKey && currentValue !== null) {
1733
+ currentValue += "\n" + line;
1734
+ }
1735
+ }
1736
+ if (currentKey && currentValue !== null) {
1737
+ this.set_value(currentKey, currentValue.trim(), currentAttributes);
1738
+ }
1739
+ const hasParsedKeys = lines.some((line) => line.startsWith("### ") && !line.startsWith("### Appendix"));
1740
+ if (!hasParsedKeys && markdown.trim().length > 0) {
1741
+ this.set_value("imported_content", markdown.trim(), {
1742
+ type: "text",
1743
+ systemTags: ["SystemPrompt", "LLMWrite"],
1744
+ // Default assumptions
1745
+ zIndex: 0
1746
+ });
1747
+ }
1748
+ }
1749
+ /**
1750
+ * Set base64 binary data.
1751
+ */
1752
+ set_base64(key, base64Data, contentType, type = "file", attributes) {
1753
+ if (!this.validateContentType(type, contentType)) {
1754
+ throw new Error(`Invalid content type ${contentType} for type ${type}`);
1755
+ }
1756
+ const fileAttributes = {
1757
+ type,
1758
+ contentType,
1759
+ ...attributes
1760
+ };
1761
+ this.set_value(key, base64Data, fileAttributes);
1762
+ }
1763
+ /**
1764
+ * Add an image from base64 data.
1765
+ */
1766
+ add_image(key, base64Data, contentType = "image/jpeg", attributes) {
1767
+ if (!contentType.startsWith("image/")) {
1768
+ throw new Error(`Invalid image content type: ${contentType}. Must start with 'image/'`);
1769
+ }
1770
+ this.set_base64(key, base64Data, contentType, "image", attributes);
1771
+ }
1772
+ /**
1773
+ * Get the data URL for an image or file key.
1774
+ */
1775
+ get_data_url(key) {
1776
+ const entryMap = this.rootMap.get(key);
1777
+ if (!entryMap) {
1778
+ return void 0;
1779
+ }
1780
+ const attributes = entryMap.get("attributes");
1781
+ if (attributes?.type !== "image" && attributes?.type !== "file") {
1782
+ return void 0;
1783
+ }
1784
+ if (!attributes?.contentType) {
1785
+ return void 0;
1786
+ }
1787
+ const value = entryMap.get("value");
1788
+ return this.createDataUrl(value, attributes.contentType);
1789
+ }
1790
+ /**
1791
+ * Get the base64 data for an image or file key.
1792
+ */
1793
+ get_base64(key) {
1794
+ const entryMap = this.rootMap.get(key);
1795
+ if (!entryMap) {
1796
+ return void 0;
1797
+ }
1798
+ const attributes = entryMap.get("attributes");
1799
+ if (attributes?.type !== "image" && attributes?.type !== "file") {
1800
+ return void 0;
1801
+ }
1802
+ return entryMap.get("value");
1803
+ }
1804
+ // File methods
1805
+ async set_file(key, file, attributes) {
1806
+ const base64 = await this.encodeFileToBase64(file);
1807
+ const dataUrl = this.createDataUrl(base64, file.type);
1808
+ this.set_value(key, dataUrl, {
1809
+ ...attributes,
1810
+ type: "file",
1811
+ contentType: file.type
1812
+ });
1813
+ }
1814
+ set_image(key, file, attributes) {
1815
+ return this.set_file(key, file, {
1816
+ ...attributes,
1817
+ type: "image"
1818
+ // Override to image
1819
+ });
1820
+ }
1821
+ // Document methods for collaborative editing
1822
+ /**
1823
+ * Create or get a collaborative document key.
1824
+ * Uses Y.Text for character-level concurrent editing.
1825
+ *
1826
+ * Note: This exposes Yjs Y.Text directly for editor bindings (y-quill, y-codemirror, etc.)
1827
+ */
1828
+ set_document(key, initialText, attributes) {
1829
+ if (key === "$date" || key === "$time" || key === "$version") {
1830
+ return;
1831
+ }
1832
+ let entryMap = this.rootMap.get(key);
1833
+ if (!entryMap) {
1834
+ entryMap = new Y.Map();
1835
+ this.rootMap.set(key, entryMap);
1836
+ const yText = new Y.Text(initialText || "");
1837
+ entryMap.set("value", yText);
1838
+ entryMap.set("attributes", {
1839
+ ...DEFAULT_KEY_ATTRIBUTES,
1840
+ type: "document",
1841
+ contentTags: [],
1842
+ systemTags: ["SystemPrompt", "LLMWrite"],
1843
+ tags: [],
1844
+ zIndex: 0,
1845
+ ...attributes
1846
+ });
1847
+ }
1848
+ this.getUndoManager(key);
1849
+ }
1850
+ /**
1851
+ * Get the Y.Text object for a document key.
1852
+ * Use this to bind to editors (Quill, CodeMirror, Monaco, etc.)
1853
+ *
1854
+ * @returns Y.Text or undefined if key doesn't exist or isn't a document
1855
+ */
1856
+ get_document(key) {
1857
+ const entryMap = this.rootMap.get(key);
1858
+ if (!entryMap) {
1859
+ return void 0;
1860
+ }
1861
+ const attrs = entryMap.get("attributes");
1862
+ if (attrs?.type !== "document") {
1863
+ return void 0;
1864
+ }
1865
+ const value = entryMap.get("value");
1866
+ if (value instanceof Y.Text) {
1867
+ return value;
1868
+ }
1869
+ return void 0;
1870
+ }
1871
+ /**
1872
+ * Insert text at a position in a document key.
1873
+ */
1874
+ insert_text(key, index, text) {
1875
+ const yText = this.get_document(key);
1876
+ if (yText) {
1877
+ yText.insert(index, text);
1878
+ }
1879
+ }
1880
+ /**
1881
+ * Delete text from a document key.
1882
+ */
1883
+ delete_text(key, index, length2) {
1884
+ const yText = this.get_document(key);
1885
+ if (yText) {
1886
+ yText.delete(index, length2);
1887
+ }
1888
+ }
1889
+ /**
1890
+ * Replace all text in a document key (private - use set_value for public API).
1891
+ * Uses diff-based updates when changes are < diffThreshold (default 80%).
1892
+ * This preserves concurrent edits and provides better undo granularity.
1893
+ */
1894
+ _replaceDocumentText(key, newText, diffThreshold = 0.8) {
1895
+ const yText = this.get_document(key);
1896
+ if (!yText) {
1897
+ return;
1898
+ }
1899
+ const oldText = yText.toString();
1900
+ if (oldText === newText) {
1901
+ return;
1902
+ }
1903
+ if (oldText.length === 0) {
1904
+ yText.insert(0, newText);
1905
+ return;
1906
+ }
1907
+ const diffs = diff(oldText, newText);
1908
+ let changedChars = 0;
1909
+ for (const [op, text] of diffs) {
1910
+ if (op !== 0) {
1911
+ changedChars += text.length;
1912
+ }
1913
+ }
1914
+ const changeRatio = changedChars / Math.max(oldText.length, newText.length);
1915
+ if (changeRatio > diffThreshold) {
1916
+ this.doc.transact(() => {
1917
+ yText.delete(0, yText.length);
1918
+ yText.insert(0, newText);
1919
+ });
1920
+ return;
1921
+ }
1922
+ this.doc.transact(() => {
1923
+ let cursor = 0;
1924
+ for (const [op, text] of diffs) {
1925
+ if (op === 0) {
1926
+ cursor += text.length;
1927
+ } else if (op === -1) {
1928
+ yText.delete(cursor, text.length);
1929
+ } else if (op === 1) {
1930
+ yText.insert(cursor, text);
1931
+ cursor += text.length;
1932
+ }
1933
+ }
1934
+ });
1935
+ }
1936
+ // ... (subscribe methods)
1937
+ subscribe(key, listener) {
1938
+ if (!this.listeners[key]) {
1939
+ this.listeners[key] = [];
1940
+ }
1941
+ this.listeners[key].push(listener);
1942
+ return () => {
1943
+ this.listeners[key] = this.listeners[key].filter((l) => l !== listener);
1944
+ };
1945
+ }
1946
+ subscribeToAll(listener) {
1947
+ this.globalListeners.push(listener);
1948
+ return () => {
1949
+ this.globalListeners = this.globalListeners.filter((l) => l !== listener);
1950
+ };
1951
+ }
1952
+ unsubscribeFromAll(listener) {
1953
+ this.globalListeners = this.globalListeners.filter((l) => l !== listener);
1954
+ }
1955
+ notifyGlobalListeners() {
1956
+ this.globalListeners.forEach((l) => l());
1957
+ }
1958
+ // Sanitize key name for use in tool names
1959
+ sanitizeKeyForTool(key) {
1960
+ return key.replace(/[^a-zA-Z0-9_-]/g, "_");
1961
+ }
1962
+ // Find original key from sanitized tool name
1963
+ findKeyFromSanitizedTool(sanitizedKey) {
1964
+ for (const [key] of this.rootMap) {
1965
+ if (this.sanitizeKeyForTool(key) === sanitizedKey) {
1966
+ return key;
1967
+ }
1968
+ }
1969
+ return void 0;
1970
+ }
1971
+ /**
1972
+ * Generate Vercel AI SDK compatible tools for writable keys.
1973
+ * For document type keys, generates additional tools: append_, insert_, edit_
1974
+ *
1975
+ * Security: All tools use llm_set_key internally which:
1976
+ * - Only modifies VALUES, never attributes/systemTags
1977
+ * - Prevents LLMs from escalating privileges
1978
+ */
1979
+ create_vercel_ai_tools() {
1980
+ const tools = {};
1981
+ for (const [key, val] of this.rootMap) {
1982
+ if (key.startsWith("$")) {
1983
+ continue;
1984
+ }
1985
+ const entryMap = val;
1986
+ const attributes = entryMap.get("attributes");
1987
+ const isWritable = attributes?.systemTags?.includes("LLMWrite");
1988
+ if (!isWritable) {
1989
+ continue;
1990
+ }
1991
+ const sanitizedKey = this.sanitizeKeyForTool(key);
1992
+ const isDocument = attributes?.type === "document";
1993
+ tools[`write_${sanitizedKey}`] = {
1994
+ description: isDocument ? `Rewrite the entire "${key}" document` : `Write a value to the STM key: ${key}`,
1995
+ inputSchema: {
1996
+ type: "object",
1997
+ properties: {
1998
+ value: { type: "string", description: isDocument ? "New document content" : "The value to write" }
1999
+ },
2000
+ required: ["value"]
2001
+ },
2002
+ execute: async ({ value }) => {
2003
+ const success = this.llm_set_key(key, value);
2004
+ if (success) {
2005
+ return {
2006
+ result: `Successfully wrote "${value}" to ${key}`,
2007
+ key,
2008
+ value
2009
+ };
2010
+ }
2011
+ return {
2012
+ result: `Failed to write to ${key} - permission denied or key not found`,
2013
+ key,
2014
+ error: true
2015
+ };
2016
+ }
2017
+ };
2018
+ if (isDocument) {
2019
+ tools[`append_${sanitizedKey}`] = {
2020
+ description: `Append text to the end of "${key}" document`,
2021
+ inputSchema: {
2022
+ type: "object",
2023
+ properties: {
2024
+ text: { type: "string", description: "Text to append" }
2025
+ },
2026
+ required: ["text"]
2027
+ },
2028
+ execute: async ({ text }) => {
2029
+ if (!attributes?.systemTags?.includes("LLMWrite")) {
2030
+ return { result: `Permission denied for ${key}`, key, error: true };
2031
+ }
2032
+ const yText = this.get_document(key);
2033
+ if (yText) {
2034
+ yText.insert(yText.length, text);
2035
+ return {
2036
+ result: `Successfully appended to ${key}`,
2037
+ key,
2038
+ appended: text
2039
+ };
2040
+ }
2041
+ return { result: `Document ${key} not found`, key };
2042
+ }
2043
+ };
2044
+ tools[`insert_${sanitizedKey}`] = {
2045
+ description: `Insert text at a position in "${key}" document`,
2046
+ inputSchema: {
2047
+ type: "object",
2048
+ properties: {
2049
+ index: { type: "number", description: "Position to insert at (0 = start)" },
2050
+ text: { type: "string", description: "Text to insert" }
2051
+ },
2052
+ required: ["index", "text"]
2053
+ },
2054
+ execute: async ({ index, text }) => {
2055
+ if (!attributes?.systemTags?.includes("LLMWrite")) {
2056
+ return { result: `Permission denied for ${key}`, key, error: true };
2057
+ }
2058
+ this.insert_text(key, index, text);
2059
+ return {
2060
+ result: `Successfully inserted text at position ${index} in ${key}`,
2061
+ key,
2062
+ index,
2063
+ inserted: text
2064
+ };
2065
+ }
2066
+ };
2067
+ tools[`edit_${sanitizedKey}`] = {
2068
+ description: `Find and replace text in "${key}" document`,
2069
+ inputSchema: {
2070
+ type: "object",
2071
+ properties: {
2072
+ find: { type: "string", description: "Text to find" },
2073
+ replace: { type: "string", description: "Replacement text" }
2074
+ },
2075
+ required: ["find", "replace"]
2076
+ },
2077
+ execute: async ({ find, replace }) => {
2078
+ if (!attributes?.systemTags?.includes("LLMWrite")) {
2079
+ return { result: `Permission denied for ${key}`, key, error: true };
2080
+ }
2081
+ const yText = this.get_document(key);
2082
+ if (yText) {
2083
+ const text = yText.toString();
2084
+ const idx = text.indexOf(find);
2085
+ if (idx !== -1) {
2086
+ yText.delete(idx, find.length);
2087
+ yText.insert(idx, replace);
2088
+ return {
2089
+ result: `Successfully replaced "${find}" with "${replace}" in ${key}`,
2090
+ key,
2091
+ find,
2092
+ replace,
2093
+ index: idx
2094
+ };
2095
+ }
2096
+ return { result: `Text "${find}" not found in ${key}`, key };
2097
+ }
2098
+ return { result: `Document ${key} not found`, key };
2099
+ }
2100
+ };
2101
+ }
2102
+ }
2103
+ return tools;
2104
+ }
2105
+ /**
2106
+ * @deprecated Use create_vercel_ai_tools() instead
2107
+ */
2108
+ get_aisdk_tools() {
2109
+ return this.create_vercel_ai_tools();
2110
+ }
2111
+ /**
2112
+ * Generate a system prompt containing all visible STM keys and their values.
2113
+ * Indicates which tools can be used to modify writable keys.
2114
+ */
2115
+ get_system_prompt() {
2116
+ const lines = [];
2117
+ for (const [key, val] of this.rootMap) {
2118
+ if (key.startsWith("$")) {
2119
+ continue;
2120
+ }
2121
+ const entryMap = val;
2122
+ const attributes = entryMap.get("attributes");
2123
+ const isVisible = attributes?.systemTags?.includes("SystemPrompt") || attributes?.systemTags?.includes("LLMRead");
2124
+ if (!isVisible) {
2125
+ continue;
2126
+ }
2127
+ const value = this.get_value(key);
2128
+ const displayValue = typeof value === "object" ? JSON.stringify(value) : value;
2129
+ const isWritable = attributes?.systemTags?.includes("LLMWrite");
2130
+ const isDocument = attributes?.type === "document";
2131
+ const sanitizedKey = this.sanitizeKeyForTool(key);
2132
+ if (isWritable) {
2133
+ if (isDocument) {
2134
+ lines.push(
2135
+ `${key}: ${displayValue}. Document tools: write_${sanitizedKey}, append_${sanitizedKey}, edit_${sanitizedKey}`
2136
+ );
2137
+ } else {
2138
+ const oldValueHint = displayValue ? ` This tool DOES NOT append \u2014 start your response with the old value (${displayValue})` : "";
2139
+ lines.push(
2140
+ `${key}: ${displayValue}. You can rewrite "${key}" by using the write_${sanitizedKey} tool.${oldValueHint}`
2141
+ );
2142
+ }
2143
+ } else {
2144
+ lines.push(`${key}: ${displayValue}`);
2145
+ }
2146
+ }
2147
+ lines.push(`$date: ${this.get_value("$date")}`);
2148
+ lines.push(`$time: ${this.get_value("$time")}`);
2149
+ return lines.join(", ");
2150
+ }
2151
+ /**
2152
+ * Execute a tool call by name with the given value.
2153
+ * Returns the result or null if tool not found.
2154
+ */
2155
+ executeToolCall(toolName, value) {
2156
+ const match = toolName.match(/^(write|append|insert|edit)_(.+)$/);
2157
+ if (!match) {
2158
+ return null;
2159
+ }
2160
+ const [, action, sanitizedKey] = match;
2161
+ const key = this.findKeyFromSanitizedTool(sanitizedKey);
2162
+ if (!key) {
2163
+ return null;
2164
+ }
2165
+ const entryMap = this.rootMap.get(key);
2166
+ if (!entryMap) {
2167
+ return null;
2168
+ }
2169
+ const attributes = entryMap.get("attributes");
2170
+ const isWritable = attributes?.systemTags?.includes("LLMWrite");
2171
+ if (!isWritable) {
2172
+ return null;
2173
+ }
2174
+ const isDocument = attributes?.type === "document";
2175
+ switch (action) {
2176
+ case "write":
2177
+ if (isDocument) {
2178
+ this._replaceDocumentText(key, value);
2179
+ } else {
2180
+ this.set_value(key, value);
2181
+ }
2182
+ return {
2183
+ result: `Successfully wrote "${value}" to ${key}`,
2184
+ key,
2185
+ value
2186
+ };
2187
+ case "append":
2188
+ if (isDocument) {
2189
+ const yText = this.get_document(key);
2190
+ if (yText) {
2191
+ yText.insert(yText.length, value);
2192
+ return {
2193
+ result: `Successfully appended to ${key}`,
2194
+ key,
2195
+ value
2196
+ };
2197
+ }
2198
+ }
2199
+ return null;
2200
+ case "insert":
2201
+ if (isDocument && typeof value === "object" && value.index !== void 0 && value.text) {
2202
+ this.insert_text(key, value.index, value.text);
2203
+ return {
2204
+ result: `Successfully inserted at position ${value.index} in ${key}`,
2205
+ key,
2206
+ value: value.text
2207
+ };
2208
+ }
2209
+ return null;
2210
+ case "edit":
2211
+ if (isDocument && typeof value === "object" && value.find && value.replace !== void 0) {
2212
+ const yText = this.get_document(key);
2213
+ if (yText) {
2214
+ const text = yText.toString();
2215
+ const idx = text.indexOf(value.find);
2216
+ if (idx !== -1) {
2217
+ yText.delete(idx, value.find.length);
2218
+ yText.insert(idx, value.replace);
2219
+ return {
2220
+ result: `Successfully replaced "${value.find}" with "${value.replace}" in ${key}`,
2221
+ key,
2222
+ value: value.replace
2223
+ };
2224
+ }
2225
+ }
2226
+ }
2227
+ return null;
2228
+ default:
2229
+ return null;
2230
+ }
2231
+ }
2232
+ // Internal method stub for legacy compatibility
2233
+ _setFromRemote(_key, _value, _attributes) {
2234
+ }
2235
+ _deleteFromRemote(_key) {
2236
+ }
2237
+ _clearFromRemote() {
2238
+ }
2239
+ };
2240
+
2241
+ // src/cloud/index.ts
2242
+ init_CloudAdapter();
2243
+ init_CloudAdapter();
2244
+
2245
+ // src/local/index.ts
2246
+ init_IndexedDBAdapter();
2247
+
2248
+ export { CloudAdapter, DEFAULT_KEY_ATTRIBUTES, IndexedDBAdapter, MindCache, SystemTagHelpers };
2249
+ //# sourceMappingURL=server.mjs.map
2250
+ //# sourceMappingURL=server.mjs.map