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