mindcache 2.4.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,9 @@
1
- import { z } from 'zod';
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';
2
7
 
3
8
  var __defProp = Object.defineProperty;
4
9
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -179,7 +184,6 @@ var init_CloudAdapter = __esm({
179
184
  }
180
185
  }
181
186
  ws = null;
182
- queue = [];
183
187
  mindcache = null;
184
188
  unsubscribe = null;
185
189
  reconnectAttempts = 0;
@@ -187,49 +191,29 @@ var init_CloudAdapter = __esm({
187
191
  _state = "disconnected";
188
192
  listeners = {};
189
193
  token = null;
190
- /**
191
- * Set auth token (short-lived, from /api/ws-token)
192
- * Call this before connect() or use setTokenProvider for auto-refresh
193
- */
194
194
  setToken(token) {
195
195
  this.token = token;
196
196
  }
197
- /**
198
- * Set a function that returns a fresh token
199
- * Used for automatic token refresh on reconnect
200
- */
201
197
  setTokenProvider(provider) {
202
198
  this.config.tokenProvider = provider;
203
199
  }
204
- /**
205
- * Get current connection state
206
- */
207
200
  get state() {
208
201
  return this._state;
209
202
  }
210
- /**
211
- * Attach to a MindCache instance and start syncing
212
- */
213
203
  attach(mc) {
214
204
  if (this.mindcache) {
215
205
  this.detach();
216
206
  }
217
207
  this.mindcache = mc;
218
- const listener = () => {
219
- if (mc.isRemoteUpdate()) {
220
- console.log("\u2601\uFE0F CloudAdapter: Skipping remote update");
221
- return;
208
+ mc.doc.on("update", (update, origin) => {
209
+ if (origin !== this && this.ws && this.ws.readyState === WebSocket.OPEN) {
210
+ const encoder = encoding.createEncoder();
211
+ syncProtocol.writeUpdate(encoder, update);
212
+ this.sendBinary(encoding.toUint8Array(encoder));
222
213
  }
223
- console.log("\u2601\uFE0F CloudAdapter: Local change detected, syncing...");
224
- this.syncLocalChanges();
225
- };
226
- mc.subscribeToAll(listener);
227
- this.unsubscribe = () => mc.unsubscribeFromAll(listener);
214
+ });
228
215
  console.log("\u2601\uFE0F CloudAdapter: Attached to MindCache instance");
229
216
  }
230
- /**
231
- * Detach from the MindCache instance
232
- */
233
217
  detach() {
234
218
  if (this.unsubscribe) {
235
219
  this.unsubscribe();
@@ -237,15 +221,6 @@ var init_CloudAdapter = __esm({
237
221
  }
238
222
  this.mindcache = null;
239
223
  }
240
- /**
241
- * Fetch a short-lived WebSocket token from the API using the API key.
242
- * This keeps the API key secure by only using it for a single HTTPS request,
243
- * then using the short-lived token for the WebSocket connection.
244
- *
245
- * Supports two key formats:
246
- * - API keys: mc_live_xxx or mc_test_xxx → Bearer token
247
- * - Delegate keys: del_xxx:sec_xxx → ApiKey format
248
- */
249
224
  async fetchTokenWithApiKey() {
250
225
  if (!this.config.apiKey) {
251
226
  throw new Error("API key is required to fetch token");
@@ -271,9 +246,6 @@ var init_CloudAdapter = __esm({
271
246
  const data = await response.json();
272
247
  return data.token;
273
248
  }
274
- /**
275
- * Connect to the cloud service
276
- */
277
249
  async connect() {
278
250
  if (this._state === "connecting" || this._state === "connected") {
279
251
  return;
@@ -295,6 +267,7 @@ var init_CloudAdapter = __esm({
295
267
  throw new Error("MindCache Cloud: No authentication method available. Provide apiKey or tokenProvider.");
296
268
  }
297
269
  this.ws = new WebSocket(url);
270
+ this.ws.binaryType = "arraybuffer";
298
271
  this.setupWebSocket();
299
272
  } catch (error) {
300
273
  this._state = "error";
@@ -302,9 +275,6 @@ var init_CloudAdapter = __esm({
302
275
  this.scheduleReconnect();
303
276
  }
304
277
  }
305
- /**
306
- * Disconnect from the cloud service
307
- */
308
278
  disconnect() {
309
279
  if (this.reconnectTimeout) {
310
280
  clearTimeout(this.reconnectTimeout);
@@ -317,28 +287,12 @@ var init_CloudAdapter = __esm({
317
287
  this._state = "disconnected";
318
288
  this.emit("disconnected");
319
289
  }
320
- /**
321
- * Push an operation to the cloud
322
- */
323
- push(op) {
324
- if (this.ws?.readyState === WebSocket.OPEN) {
325
- this.ws.send(JSON.stringify(op));
326
- } else {
327
- this.queue.push(op);
328
- }
329
- }
330
- /**
331
- * Add event listener
332
- */
333
290
  on(event, listener) {
334
291
  if (!this.listeners[event]) {
335
292
  this.listeners[event] = [];
336
293
  }
337
294
  this.listeners[event].push(listener);
338
295
  }
339
- /**
340
- * Remove event listener
341
- */
342
296
  off(event, listener) {
343
297
  if (this.listeners[event]) {
344
298
  this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
@@ -354,14 +308,38 @@ var init_CloudAdapter = __esm({
354
308
  return;
355
309
  }
356
310
  this.ws.onopen = () => {
311
+ if (this.mindcache) {
312
+ const encoder = encoding.createEncoder();
313
+ syncProtocol.writeSyncStep1(encoder, this.mindcache.doc);
314
+ this.sendBinary(encoding.toUint8Array(encoder));
315
+ }
357
316
  };
358
317
  this.ws.onmessage = (event) => {
359
318
  try {
360
- const msg = JSON.parse(event.data);
361
- this.handleMessage(msg);
319
+ if (typeof event.data === "string") {
320
+ const msg = JSON.parse(event.data);
321
+ if (msg.type === "auth_success") {
322
+ this._state = "connected";
323
+ this.reconnectAttempts = 0;
324
+ this.emit("connected");
325
+ } else if (msg.type === "auth_error" || msg.type === "error") {
326
+ this._state = "error";
327
+ this.emit("error", new Error(msg.error));
328
+ } else {
329
+ console.debug("MindCache Cloud: Received message type:", msg.type, msg);
330
+ }
331
+ } else {
332
+ const encoder = encoding.createEncoder();
333
+ const decoder = decoding.createDecoder(new Uint8Array(event.data));
334
+ if (this.mindcache) {
335
+ syncProtocol.readSyncMessage(decoder, encoder, this.mindcache.doc, this);
336
+ if (encoding.length(encoder) > 0) {
337
+ this.sendBinary(encoding.toUint8Array(encoder));
338
+ }
339
+ }
340
+ }
362
341
  } catch (error) {
363
- console.error("MindCache Cloud: Failed to parse message:", error);
364
- console.error("Raw message:", typeof event.data === "string" ? event.data.slice(0, 200) : event.data);
342
+ console.error("MindCache Cloud: Failed to handle message:", error);
365
343
  }
366
344
  };
367
345
  this.ws.onclose = () => {
@@ -371,73 +349,12 @@ var init_CloudAdapter = __esm({
371
349
  };
372
350
  this.ws.onerror = () => {
373
351
  this._state = "error";
374
- const url = `${this.config.baseUrl}/sync/${this.config.instanceId}`;
375
- console.error(`MindCache Cloud: WebSocket error connecting to ${url}`);
376
- console.error("Check that the instance ID and API key are correct, and that the server is reachable.");
377
- this.emit("error", new Error(`WebSocket connection failed to ${url}`));
352
+ this.emit("error", new Error("WebSocket connection failed"));
378
353
  };
379
354
  }
380
- handleMessage(msg) {
381
- switch (msg.type) {
382
- case "auth_success":
383
- this._state = "connected";
384
- this.reconnectAttempts = 0;
385
- this.emit("connected");
386
- this.flushQueue();
387
- break;
388
- case "auth_error":
389
- this._state = "error";
390
- this.emit("error", new Error(msg.error));
391
- this.disconnect();
392
- break;
393
- case "sync":
394
- if (this.mindcache && msg.data) {
395
- Object.entries(msg.data).forEach(([key, entry]) => {
396
- const { value, attributes } = entry;
397
- this.mindcache._setFromRemote(key, value, attributes);
398
- });
399
- this.emit("synced");
400
- }
401
- break;
402
- case "set":
403
- if (this.mindcache) {
404
- this.mindcache._setFromRemote(msg.key, msg.value, msg.attributes);
405
- }
406
- break;
407
- case "key_updated":
408
- if (this.mindcache) {
409
- this.mindcache._setFromRemote(msg.key, msg.value, msg.attributes);
410
- }
411
- break;
412
- case "delete":
413
- if (this.mindcache) {
414
- this.mindcache._deleteFromRemote(msg.key);
415
- }
416
- break;
417
- case "key_deleted":
418
- if (this.mindcache) {
419
- this.mindcache._deleteFromRemote(msg.key);
420
- }
421
- break;
422
- case "clear":
423
- if (this.mindcache) {
424
- this.mindcache._clearFromRemote();
425
- }
426
- break;
427
- case "cleared":
428
- if (this.mindcache) {
429
- this.mindcache._clearFromRemote();
430
- }
431
- break;
432
- case "error":
433
- this.emit("error", new Error(msg.error));
434
- break;
435
- }
436
- }
437
- flushQueue() {
438
- while (this.queue.length > 0 && this.ws?.readyState === WebSocket.OPEN) {
439
- const op = this.queue.shift();
440
- this.ws.send(JSON.stringify(op));
355
+ sendBinary(data) {
356
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
357
+ this.ws.send(data);
441
358
  }
442
359
  }
443
360
  scheduleReconnect() {
@@ -454,23 +371,6 @@ var init_CloudAdapter = __esm({
454
371
  this.connect();
455
372
  }, delay);
456
373
  }
457
- syncLocalChanges() {
458
- if (!this.mindcache) {
459
- return;
460
- }
461
- const entries = this.mindcache.serialize();
462
- console.log("\u2601\uFE0F CloudAdapter: Syncing local changes:", Object.keys(entries));
463
- Object.entries(entries).forEach(([key, entry]) => {
464
- console.log("\u2601\uFE0F CloudAdapter: Pushing key:", key, "=", entry.value);
465
- this.push({
466
- type: "set",
467
- key,
468
- value: entry.value,
469
- attributes: entry.attributes,
470
- timestamp: Date.now()
471
- });
472
- });
473
- }
474
374
  };
475
375
  }
476
376
  });
@@ -492,16 +392,18 @@ var DEFAULT_KEY_ATTRIBUTES = {
492
392
 
493
393
  // src/core/MindCache.ts
494
394
  var MindCache = class {
495
- stm = {};
395
+ // Public doc for adapter access
396
+ doc;
397
+ rootMap;
398
+ // Key -> EntryMap({value, attributes})
399
+ // Cache listeners
496
400
  listeners = {};
497
401
  globalListeners = [];
402
+ // Metadata
403
+ version = "3.1.0";
498
404
  // Internal flag to prevent sync loops when receiving remote updates
405
+ // (Less critical with Yjs but kept for API compat)
499
406
  _isRemoteUpdate = false;
500
- /**
501
- * Normalize system tags: migrate old tags to new ones
502
- * - 'prompt' → 'SystemPrompt'
503
- * - 'readonly' → remove 'LLMWrite' (or add if not readonly)
504
- */
505
407
  normalizeSystemTags(tags) {
506
408
  const normalized = [];
507
409
  let hasSystemPrompt = false;
@@ -533,29 +435,9 @@ var MindCache = class {
533
435
  normalized.push("readonly");
534
436
  } else if (hasLLMWrite) {
535
437
  normalized.push("LLMWrite");
536
- } else {
537
- normalized.push("LLMWrite");
538
438
  }
539
439
  return normalized;
540
440
  }
541
- /**
542
- * Check if key should be visible in system prompt
543
- */
544
- hasSystemPrompt(tags) {
545
- return tags.includes("SystemPrompt") || tags.includes("prompt");
546
- }
547
- /**
548
- * Check if key can be read by LLM (has LLMRead or SystemPrompt)
549
- */
550
- hasLLMRead(tags) {
551
- return tags.includes("LLMRead") || tags.includes("SystemPrompt") || tags.includes("prompt");
552
- }
553
- /**
554
- * Check if key can be written by LLM (has LLMWrite and not readonly)
555
- */
556
- hasLLMWrite(tags) {
557
- return tags.includes("LLMWrite") && !tags.includes("readonly");
558
- }
559
441
  // Cloud sync state
560
442
  _cloudAdapter = null;
561
443
  _connectionState = "disconnected";
@@ -565,25 +447,57 @@ var MindCache = class {
565
447
  // Access level for system operations
566
448
  _accessLevel = "user";
567
449
  _initPromise = null;
450
+ // Y-IndexedDB provider
451
+ _idbProvider = null;
452
+ // Undo Managers Cache
453
+ _undoManagers = /* @__PURE__ */ new Map();
454
+ // Global Undo Manager (watches entire rootMap)
455
+ _globalUndoManager = null;
456
+ // History tracking
457
+ _history = [];
458
+ _historyOptions = { maxEntries: 100, snapshotInterval: 10 };
459
+ _historyEnabled = false;
568
460
  constructor(options) {
461
+ this.doc = new Y.Doc();
462
+ this.rootMap = this.doc.getMap("mindcache");
463
+ this.rootMap.observe((event) => {
464
+ event.keysChanged.forEach((key) => {
465
+ const entryMap = this.rootMap.get(key);
466
+ if (entryMap) {
467
+ const value = entryMap.get("value");
468
+ if (this.listeners[key]) {
469
+ this.listeners[key].forEach((l) => l(value));
470
+ }
471
+ } else {
472
+ if (this.listeners[key]) {
473
+ this.listeners[key].forEach((l) => l(void 0));
474
+ }
475
+ }
476
+ });
477
+ });
478
+ this.rootMap.observeDeep((_events) => {
479
+ this.notifyGlobalListeners();
480
+ });
481
+ this.initGlobalUndoManager();
569
482
  if (options?.accessLevel) {
570
483
  this._accessLevel = options.accessLevel;
571
484
  }
572
- if (options?.cloud && options?.indexedDB) {
573
- throw new Error(
574
- "MindCache: Cannot use both cloud and indexedDB together. Choose one persistence method to avoid data conflicts. Use cloud for real-time sync, or indexedDB for local-only persistence."
575
- );
576
- }
577
485
  const initPromises = [];
578
486
  if (options?.cloud) {
579
487
  this._cloudConfig = options.cloud;
580
488
  this._isLoaded = false;
581
489
  this._connectionState = "disconnected";
582
490
  initPromises.push(this._initCloud());
491
+ if (typeof window !== "undefined") {
492
+ const dbName = `mindcache_cloud_${options.cloud.instanceId}`;
493
+ initPromises.push(this._initYIndexedDB(dbName));
494
+ }
495
+ this.enableHistory(options.history);
583
496
  }
584
- if (options?.indexedDB) {
497
+ if (options?.indexedDB && !options?.cloud) {
585
498
  this._isLoaded = false;
586
- initPromises.push(this._initIndexedDB(options.indexedDB));
499
+ initPromises.push(this._initYIndexedDB(options.indexedDB.dbName || "mindcache_yjs_db"));
500
+ this.enableHistory(options.history);
587
501
  }
588
502
  if (initPromises.length > 0) {
589
503
  this._initPromise = Promise.all(initPromises).then(() => {
@@ -593,15 +507,147 @@ var MindCache = class {
593
507
  });
594
508
  }
595
509
  }
510
+ // Helper: Get or Create UndoManager for a key
511
+ getUndoManager(key) {
512
+ const entryMap = this.rootMap.get(key);
513
+ if (!entryMap) {
514
+ return void 0;
515
+ }
516
+ if (!this._undoManagers.has(key)) {
517
+ const um = new Y.UndoManager(entryMap, {
518
+ captureTimeout: 500
519
+ });
520
+ this._undoManagers.set(key, um);
521
+ }
522
+ return this._undoManagers.get(key);
523
+ }
596
524
  /**
597
- * Get the current access level
525
+ * Undo changes for a specific key
598
526
  */
599
- get accessLevel() {
600
- return this._accessLevel;
527
+ undo(key) {
528
+ const um = this.getUndoManager(key);
529
+ if (um) {
530
+ um.undo();
531
+ }
532
+ }
533
+ /**
534
+ * Redo changes for a specific key
535
+ */
536
+ redo(key) {
537
+ const um = this.getUndoManager(key);
538
+ if (um) {
539
+ um.redo();
540
+ }
541
+ }
542
+ getHistory(key) {
543
+ const um = this.getUndoManager(key);
544
+ if (!um) {
545
+ return [];
546
+ }
547
+ return um.undoStack;
548
+ }
549
+ // Initialize global undo manager (watches entire rootMap)
550
+ initGlobalUndoManager() {
551
+ if (!this._globalUndoManager) {
552
+ this._globalUndoManager = new Y.UndoManager(this.rootMap, {
553
+ captureTimeout: 500
554
+ });
555
+ }
556
+ }
557
+ /**
558
+ * Undo all recent local changes (across all keys)
559
+ * Only undoes YOUR changes, not changes from other users in cloud mode
560
+ */
561
+ undoAll() {
562
+ this.initGlobalUndoManager();
563
+ this._globalUndoManager?.undo();
564
+ }
565
+ /**
566
+ * Redo previously undone local changes
567
+ */
568
+ redoAll() {
569
+ this.initGlobalUndoManager();
570
+ this._globalUndoManager?.redo();
571
+ }
572
+ /**
573
+ * Check if there are changes to undo globally
574
+ */
575
+ canUndoAll() {
576
+ this.initGlobalUndoManager();
577
+ return (this._globalUndoManager?.undoStack.length ?? 0) > 0;
578
+ }
579
+ /**
580
+ * Check if there are changes to redo globally
581
+ */
582
+ canRedoAll() {
583
+ this.initGlobalUndoManager();
584
+ return (this._globalUndoManager?.redoStack.length ?? 0) > 0;
585
+ }
586
+ // Enable history tracking (called for IndexedDB and Cloud modes)
587
+ enableHistory(options) {
588
+ if (this._historyEnabled) {
589
+ return;
590
+ }
591
+ this._historyEnabled = true;
592
+ if (options) {
593
+ this._historyOptions = { ...this._historyOptions, ...options };
594
+ }
595
+ this.rootMap.observeDeep((events) => {
596
+ const keysAffected = /* @__PURE__ */ new Set();
597
+ events.forEach((event) => {
598
+ if (event.target === this.rootMap) {
599
+ const mapEvent = event;
600
+ mapEvent.keysChanged.forEach((key) => keysAffected.add(key));
601
+ } else if (event.target.parent === this.rootMap) {
602
+ for (const [key, val] of this.rootMap) {
603
+ if (val === event.target) {
604
+ keysAffected.add(key);
605
+ break;
606
+ }
607
+ }
608
+ }
609
+ });
610
+ if (keysAffected.size > 0) {
611
+ const entry = {
612
+ id: this.generateId(),
613
+ timestamp: Date.now(),
614
+ keysAffected: Array.from(keysAffected)
615
+ };
616
+ this._history.push(entry);
617
+ const max = this._historyOptions.maxEntries || 100;
618
+ if (this._history.length > max) {
619
+ this._history = this._history.slice(-max);
620
+ }
621
+ }
622
+ });
623
+ }
624
+ generateId() {
625
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
626
+ }
627
+ /**
628
+ * Get global history of all changes (available in IndexedDB and Cloud modes)
629
+ */
630
+ getGlobalHistory() {
631
+ return [...this._history];
601
632
  }
602
633
  /**
603
- * Check if this instance has system-level access
634
+ * Check if history tracking is enabled
604
635
  */
636
+ get historyEnabled() {
637
+ return this._historyEnabled;
638
+ }
639
+ /**
640
+ * Restore to a specific version (time travel)
641
+ * Note: Full implementation requires storing update binaries, which is not yet implemented.
642
+ * @returns false - not yet fully implemented
643
+ */
644
+ restoreToVersion(_versionId) {
645
+ console.warn("restoreToVersion: Full implementation requires storing update binaries. Not yet implemented.");
646
+ return false;
647
+ }
648
+ get accessLevel() {
649
+ return this._accessLevel;
650
+ }
605
651
  get hasSystemAccess() {
606
652
  return this._accessLevel === "system";
607
653
  }
@@ -609,110 +655,94 @@ var MindCache = class {
609
655
  if (!this._cloudConfig) {
610
656
  return;
611
657
  }
612
- try {
613
- const CloudAdapter2 = await this._getCloudAdapterClass();
614
- if (!this._cloudConfig.baseUrl) {
615
- throw new Error("MindCache Cloud: baseUrl is required. Please provide the cloud API URL in your configuration.");
658
+ const CloudAdapter2 = await this._getCloudAdapterClass();
659
+ if (!this._cloudConfig.baseUrl) ;
660
+ const baseUrl = (this._cloudConfig.baseUrl || "https://api.mindcache.dev").replace("https://", "wss://").replace("http://", "ws://");
661
+ const adapter = new CloudAdapter2({
662
+ instanceId: this._cloudConfig.instanceId,
663
+ projectId: this._cloudConfig.projectId || "default",
664
+ baseUrl,
665
+ apiKey: this._cloudConfig.apiKey
666
+ });
667
+ if (this._cloudConfig.tokenProvider) {
668
+ adapter.setTokenProvider(this._cloudConfig.tokenProvider);
669
+ } else if (this._cloudConfig.tokenEndpoint) {
670
+ const tokenEndpoint = this._cloudConfig.tokenEndpoint;
671
+ const instanceId = this._cloudConfig.instanceId;
672
+ let resolvedBaseUrl;
673
+ if (tokenEndpoint.startsWith("http://") || tokenEndpoint.startsWith("https://")) {
674
+ resolvedBaseUrl = tokenEndpoint;
675
+ } else if (typeof window !== "undefined" && window.location?.origin) {
676
+ resolvedBaseUrl = `${window.location.origin}${tokenEndpoint.startsWith("/") ? "" : "/"}${tokenEndpoint}`;
677
+ } else {
678
+ resolvedBaseUrl = tokenEndpoint;
616
679
  }
617
- const baseUrl = this._cloudConfig.baseUrl.replace("https://", "wss://").replace("http://", "ws://");
618
- const adapter = new CloudAdapter2({
619
- instanceId: this._cloudConfig.instanceId,
620
- projectId: this._cloudConfig.projectId || "default",
621
- baseUrl,
622
- apiKey: this._cloudConfig.apiKey
623
- });
624
- if (this._cloudConfig.tokenEndpoint) {
625
- const tokenEndpoint = this._cloudConfig.tokenEndpoint;
626
- const instanceId = this._cloudConfig.instanceId;
627
- let resolvedBaseUrl;
628
- if (tokenEndpoint.startsWith("http://") || tokenEndpoint.startsWith("https://")) {
629
- resolvedBaseUrl = tokenEndpoint;
630
- } else if (typeof window !== "undefined" && window.location?.origin) {
631
- resolvedBaseUrl = `${window.location.origin}${tokenEndpoint.startsWith("/") ? "" : "/"}${tokenEndpoint}`;
632
- } else {
633
- console.warn("MindCache: Cannot resolve tokenEndpoint to absolute URL - window.location not available");
634
- resolvedBaseUrl = tokenEndpoint;
680
+ adapter.setTokenProvider(async () => {
681
+ const url = resolvedBaseUrl.includes("?") ? `${resolvedBaseUrl}&instanceId=${instanceId}` : `${resolvedBaseUrl}?instanceId=${instanceId}`;
682
+ const response = await fetch(url);
683
+ if (!response.ok) {
684
+ throw new Error("Failed to get token");
635
685
  }
636
- adapter.setTokenProvider(async () => {
637
- const url = resolvedBaseUrl.includes("?") ? `${resolvedBaseUrl}&instanceId=${instanceId}` : `${resolvedBaseUrl}?instanceId=${instanceId}`;
638
- const response = await fetch(url);
639
- if (!response.ok) {
640
- const error = await response.json().catch(() => ({ error: "Failed to get token" }));
641
- throw new Error(error.error || "Failed to get token");
642
- }
643
- const data = await response.json();
644
- return data.token;
645
- });
646
- }
647
- adapter.on("connected", () => {
648
- this._connectionState = "connected";
649
- this.notifyGlobalListeners();
650
- });
651
- adapter.on("disconnected", () => {
652
- this._connectionState = "disconnected";
653
- this.notifyGlobalListeners();
654
- });
655
- adapter.on("error", () => {
656
- this._connectionState = "error";
657
- this.notifyGlobalListeners();
658
- });
659
- adapter.on("synced", () => {
660
- this._isLoaded = true;
661
- this.notifyGlobalListeners();
686
+ const data = await response.json();
687
+ return data.token;
662
688
  });
663
- adapter.attach(this);
664
- this._cloudAdapter = adapter;
665
- this._connectionState = "connecting";
666
- adapter.connect();
667
- } catch (error) {
668
- console.error("MindCache: Failed to initialize cloud connection:", error);
689
+ }
690
+ adapter.on("connected", () => {
691
+ this._connectionState = "connected";
692
+ this.notifyGlobalListeners();
693
+ });
694
+ adapter.on("disconnected", () => {
695
+ this._connectionState = "disconnected";
696
+ this.notifyGlobalListeners();
697
+ });
698
+ adapter.on("error", () => {
669
699
  this._connectionState = "error";
700
+ this.notifyGlobalListeners();
701
+ });
702
+ adapter.on("synced", () => {
670
703
  this._isLoaded = true;
671
- }
704
+ this.notifyGlobalListeners();
705
+ });
706
+ adapter.attach(this);
707
+ this._cloudAdapter = adapter;
708
+ this._connectionState = "connecting";
709
+ adapter.connect();
672
710
  }
673
- async _initIndexedDB(config) {
674
- try {
675
- const IndexedDBAdapter2 = await this._getIndexedDBAdapterClass();
676
- const adapter = new IndexedDBAdapter2(config);
677
- await adapter.attach(this);
678
- } catch (error) {
679
- console.error("MindCache: Failed to initialize IndexedDB:", error);
711
+ async _initYIndexedDB(dbName) {
712
+ if (typeof window === "undefined") {
713
+ return;
680
714
  }
715
+ this._idbProvider = new IndexeddbPersistence(dbName, this.doc);
716
+ return new Promise((resolve) => {
717
+ if (!this._idbProvider) {
718
+ return resolve();
719
+ }
720
+ this._idbProvider.on("synced", () => {
721
+ this._isLoaded = true;
722
+ resolve();
723
+ });
724
+ });
725
+ }
726
+ // Legacy IndexedDB method stub
727
+ async _initIndexedDB(_config) {
681
728
  }
682
729
  async _getIndexedDBAdapterClass() {
683
730
  const { IndexedDBAdapter: IndexedDBAdapter2 } = await Promise.resolve().then(() => (init_IndexedDBAdapter(), IndexedDBAdapter_exports));
684
731
  return IndexedDBAdapter2;
685
732
  }
686
- /**
687
- * Get the current cloud connection state
688
- */
689
733
  get connectionState() {
690
734
  return this._connectionState;
691
735
  }
692
- /**
693
- * Check if data is loaded (true for local, true after sync for cloud)
694
- */
695
736
  get isLoaded() {
696
737
  return this._isLoaded;
697
738
  }
698
- /**
699
- * Protected method to load CloudAdapter class.
700
- * Can be overridden/mocked for testing.
701
- */
702
739
  async _getCloudAdapterClass() {
703
740
  const { CloudAdapter: CloudAdapter2 } = await Promise.resolve().then(() => (init_CloudAdapter(), CloudAdapter_exports));
704
741
  return CloudAdapter2;
705
742
  }
706
- /**
707
- * Check if this instance is connected to cloud
708
- */
709
743
  get isCloud() {
710
744
  return this._cloudConfig !== null;
711
745
  }
712
- /**
713
- * Wait for initial sync to complete (or resolve immediately if already synced/local).
714
- * Useful for scripts or linear execution flows.
715
- */
716
746
  async waitForSync() {
717
747
  if (this._isLoaded) {
718
748
  return;
@@ -724,20 +754,17 @@ var MindCache = class {
724
754
  return;
725
755
  }
726
756
  return new Promise((resolve) => {
727
- if (!this._cloudAdapter) {
728
- resolve();
729
- return;
757
+ if (this._isLoaded) {
758
+ return resolve();
730
759
  }
731
- const handler = () => {
732
- this._cloudAdapter?.off("synced", handler);
733
- resolve();
734
- };
735
- this._cloudAdapter.on("synced", handler);
760
+ const interval = setInterval(() => {
761
+ if (this._isLoaded) {
762
+ clearInterval(interval);
763
+ resolve();
764
+ }
765
+ }, 100);
736
766
  });
737
767
  }
738
- /**
739
- * Disconnect from cloud (if connected)
740
- */
741
768
  disconnect() {
742
769
  if (this._cloudAdapter) {
743
770
  this._cloudAdapter.disconnect();
@@ -745,8 +772,49 @@ var MindCache = class {
745
772
  this._cloudAdapter = null;
746
773
  this._connectionState = "disconnected";
747
774
  }
775
+ if (this._idbProvider) {
776
+ this._idbProvider.destroy();
777
+ this._idbProvider = null;
778
+ }
779
+ }
780
+ // Legacy bridge
781
+ isRemoteUpdate() {
782
+ return false;
783
+ }
784
+ // Serialize state
785
+ serialize() {
786
+ const json = {};
787
+ for (const [key, val] of this.rootMap) {
788
+ const entryMap = val;
789
+ const attrs = entryMap.get("attributes");
790
+ let value = entryMap.get("value");
791
+ if (attrs?.type === "document" && value instanceof Y.Text) {
792
+ value = value.toString();
793
+ }
794
+ json[key] = {
795
+ value,
796
+ attributes: attrs
797
+ };
798
+ }
799
+ return json;
800
+ }
801
+ // Deserialize state (for IndexedDBAdapter compatibility)
802
+ deserialize(data) {
803
+ this.doc.transact(() => {
804
+ for (const [key, entry] of Object.entries(data)) {
805
+ if (key.startsWith("$")) {
806
+ continue;
807
+ }
808
+ let entryMap = this.rootMap.get(key);
809
+ if (!entryMap) {
810
+ entryMap = new Y.Map();
811
+ this.rootMap.set(key, entryMap);
812
+ }
813
+ entryMap.set("value", entry.value);
814
+ entryMap.set("attributes", entry.attributes);
815
+ }
816
+ });
748
817
  }
749
- // Helper method to encode file to base64
750
818
  encodeFileToBase64(file) {
751
819
  return new Promise((resolve, reject) => {
752
820
  if (typeof FileReader !== "undefined") {
@@ -763,11 +831,9 @@ var MindCache = class {
763
831
  }
764
832
  });
765
833
  }
766
- // Helper method to create data URL from base64 and content type
767
834
  createDataUrl(base64Data, contentType) {
768
835
  return `data:${contentType};base64,${base64Data}`;
769
836
  }
770
- // Helper method to validate content type for different STM types
771
837
  validateContentType(type, contentType) {
772
838
  if (type === "text" || type === "json") {
773
839
  return true;
@@ -783,11 +849,17 @@ var MindCache = class {
783
849
  }
784
850
  return false;
785
851
  }
786
- /** @deprecated Use get_value instead */
787
- get(key) {
788
- return this.get_value(key);
852
+ // InjectSTM replacement
853
+ injectSTM(template, _processingStack) {
854
+ return template.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
855
+ const val = this.get_value(key.trim(), _processingStack);
856
+ return val !== void 0 ? String(val) : `{{${key}}}`;
857
+ });
858
+ }
859
+ // Public API Methods
860
+ getAll() {
861
+ return this.serialize();
789
862
  }
790
- // Get a value from the STM with template processing if enabled
791
863
  get_value(key, _processingStack) {
792
864
  if (key === "$date") {
793
865
  const today = /* @__PURE__ */ new Date();
@@ -797,32 +869,37 @@ var MindCache = class {
797
869
  const now = /* @__PURE__ */ new Date();
798
870
  return now.toTimeString().split(" ")[0];
799
871
  }
800
- const entry = this.stm[key];
801
- if (!entry) {
872
+ if (key === "$version") {
873
+ return this.version;
874
+ }
875
+ const entryMap = this.rootMap.get(key);
876
+ if (!entryMap) {
802
877
  return void 0;
803
878
  }
804
- if (entry.attributes.systemTags?.includes("ApplyTemplate") || entry.attributes.systemTags?.includes("template") || entry.attributes.template) {
805
- const processingStack = _processingStack || /* @__PURE__ */ new Set();
806
- if (processingStack.has(key)) {
807
- return entry.value;
879
+ const attributes = entryMap.get("attributes");
880
+ const value = entryMap.get("value");
881
+ if (attributes?.type === "document" && value instanceof Y.Text) {
882
+ return value.toString();
883
+ }
884
+ if (_processingStack && _processingStack.has(key)) {
885
+ return `{{${key}}}`;
886
+ }
887
+ if (attributes?.systemTags?.includes("ApplyTemplate") || attributes?.systemTags?.includes("template") || attributes?.template) {
888
+ if (typeof value === "string") {
889
+ const stack = _processingStack || /* @__PURE__ */ new Set();
890
+ stack.add(key);
891
+ return this.injectSTM(value, stack);
808
892
  }
809
- processingStack.add(key);
810
- const result = this.injectSTM(entry.value, processingStack);
811
- processingStack.delete(key);
812
- return result;
813
893
  }
814
- return entry.value;
894
+ return value;
815
895
  }
816
- // Get attributes for a key
817
896
  get_attributes(key) {
818
- if (key === "$date" || key === "$time") {
897
+ if (key === "$date" || key === "$time" || key === "$version") {
819
898
  return {
820
899
  type: "text",
821
900
  contentTags: [],
822
901
  systemTags: ["prompt", "readonly", "protected"],
823
902
  zIndex: 999999,
824
- // System keys appear last
825
- // Legacy attributes
826
903
  readonly: true,
827
904
  visible: true,
828
905
  hardcoded: true,
@@ -830,1270 +907,507 @@ var MindCache = class {
830
907
  tags: []
831
908
  };
832
909
  }
833
- const entry = this.stm[key];
834
- return entry ? entry.attributes : void 0;
910
+ const entryMap = this.rootMap.get(key);
911
+ return entryMap ? entryMap.get("attributes") : void 0;
835
912
  }
836
- // Set a value in the STM with default attributes
837
- set_value(key, value, attributes) {
838
- if (key === "$date" || key === "$time") {
913
+ /**
914
+ * Update only the attributes of a key without modifying the value.
915
+ * Useful for updating tags, permissions etc. on document type keys.
916
+ */
917
+ set_attributes(key, attributes) {
918
+ if (key === "$date" || key === "$time" || key === "$version") {
839
919
  return;
840
920
  }
841
- const existingEntry = this.stm[key];
842
- const wasHardcoded = existingEntry?.attributes.hardcoded || existingEntry?.attributes.systemTags?.includes("protected");
843
- const baseAttributes = existingEntry ? {
844
- ...existingEntry.attributes,
845
- contentTags: [...existingEntry.attributes.contentTags || []],
846
- systemTags: [...existingEntry.attributes.systemTags || []],
847
- tags: [...existingEntry.attributes.tags || []],
848
- zIndex: existingEntry.attributes.zIndex ?? 0
849
- } : {
850
- ...DEFAULT_KEY_ATTRIBUTES,
851
- contentTags: [],
852
- // Fresh array
853
- systemTags: ["SystemPrompt", "LLMWrite"],
854
- // Fresh array with default
855
- tags: [],
856
- // Fresh array
857
- zIndex: 0
858
- };
859
- const finalAttributes = attributes ? { ...baseAttributes, ...attributes } : baseAttributes;
860
- let systemTags = this.normalizeSystemTags(finalAttributes.systemTags || []);
861
- if (attributes) {
862
- if ("readonly" in attributes) {
863
- if (attributes.readonly) {
864
- systemTags = systemTags.filter((t) => t !== "LLMWrite");
865
- if (!systemTags.includes("readonly")) {
866
- systemTags.push("readonly");
867
- }
868
- } else if (!wasHardcoded) {
869
- if (!systemTags.includes("LLMWrite")) {
870
- systemTags.push("LLMWrite");
871
- }
872
- systemTags = systemTags.filter((t) => t !== "readonly");
873
- }
874
- }
875
- if ("visible" in attributes) {
876
- if (attributes.visible) {
877
- if (!systemTags.includes("SystemPrompt")) {
878
- systemTags.push("SystemPrompt");
879
- }
880
- systemTags = systemTags.filter((t) => t !== "prompt");
881
- } else {
882
- systemTags = systemTags.filter((t) => t !== "SystemPrompt" && t !== "prompt");
883
- }
884
- }
885
- if ("systemTags" in attributes && Array.isArray(attributes.systemTags)) {
886
- systemTags = this.normalizeSystemTags(attributes.systemTags);
887
- }
888
- if ("hardcoded" in attributes) {
889
- if (attributes.hardcoded && !systemTags.includes("protected")) {
890
- systemTags.push("protected");
891
- } else if (!attributes.hardcoded && !wasHardcoded) {
892
- systemTags = systemTags.filter((t) => t !== "protected");
893
- }
894
- if (wasHardcoded && !systemTags.includes("protected")) {
895
- systemTags.push("protected");
896
- }
897
- } else if (wasHardcoded) {
898
- if (!systemTags.includes("protected")) {
899
- systemTags.push("protected");
900
- }
921
+ const entryMap = this.rootMap.get(key);
922
+ if (!entryMap) {
923
+ return;
924
+ }
925
+ this.doc.transact(() => {
926
+ const existingAttrs = entryMap.get("attributes");
927
+ const mergedAttrs = { ...existingAttrs, ...attributes };
928
+ if (mergedAttrs.systemTags) {
929
+ mergedAttrs.systemTags = this.normalizeSystemTags(mergedAttrs.systemTags);
901
930
  }
902
- if ("template" in attributes) {
903
- if (attributes.template && !wasHardcoded && !systemTags.includes("ApplyTemplate") && !systemTags.includes("template")) {
904
- systemTags.push("ApplyTemplate");
905
- } else if (!attributes.template || wasHardcoded) {
906
- systemTags = systemTags.filter((t) => t !== "ApplyTemplate" && t !== "template");
931
+ entryMap.set("attributes", mergedAttrs);
932
+ });
933
+ }
934
+ set_value(key, value, attributes) {
935
+ if (key === "$date" || key === "$time" || key === "$version") {
936
+ return;
937
+ }
938
+ const existingEntry = this.rootMap.get(key);
939
+ if (existingEntry) {
940
+ const existingAttrs = existingEntry.get("attributes");
941
+ if (existingAttrs?.type === "document") {
942
+ if (typeof value === "string") {
943
+ this.replace_document_text(key, value);
907
944
  }
945
+ return;
908
946
  }
909
- } else if (wasHardcoded) {
910
- if (!systemTags.includes("protected")) {
911
- systemTags.push("protected");
912
- }
913
- systemTags = systemTags.filter((t) => t !== "LLMWrite");
914
- if (!systemTags.includes("readonly")) {
915
- systemTags.push("readonly");
916
- }
917
- systemTags = systemTags.filter((t) => t !== "template");
918
947
  }
919
- if (wasHardcoded && !systemTags.includes("protected")) {
920
- systemTags.push("protected");
948
+ let entryMap = this.rootMap.get(key);
949
+ const isNewEntry = !entryMap;
950
+ if (isNewEntry) {
951
+ entryMap = new Y.Map();
952
+ this.rootMap.set(key, entryMap);
921
953
  }
922
- if (systemTags.includes("protected")) {
923
- systemTags = systemTags.filter((t) => t !== "LLMWrite");
924
- if (!systemTags.includes("readonly")) {
925
- systemTags.push("readonly");
954
+ this.getUndoManager(key);
955
+ this.doc.transact(() => {
956
+ const oldAttributes = isNewEntry ? {
957
+ ...DEFAULT_KEY_ATTRIBUTES,
958
+ contentTags: [],
959
+ systemTags: ["SystemPrompt", "LLMWrite"],
960
+ tags: [],
961
+ zIndex: 0
962
+ } : entryMap.get("attributes");
963
+ const finalAttributes = attributes ? { ...oldAttributes, ...attributes } : oldAttributes;
964
+ let normalizedAttributes = { ...finalAttributes };
965
+ if (finalAttributes.systemTags) {
966
+ normalizedAttributes.systemTags = this.normalizeSystemTags(finalAttributes.systemTags);
967
+ }
968
+ if (finalAttributes.template) {
969
+ if (!normalizedAttributes.systemTags.includes("template")) {
970
+ normalizedAttributes.systemTags.push("template");
971
+ }
926
972
  }
927
- systemTags = systemTags.filter((t) => t !== "template");
928
- }
929
- finalAttributes.systemTags = systemTags;
930
- finalAttributes.readonly = systemTags.includes("readonly") || !systemTags.includes("LLMWrite");
931
- finalAttributes.visible = this.hasSystemPrompt(systemTags);
932
- finalAttributes.hardcoded = wasHardcoded || systemTags.includes("protected");
933
- finalAttributes.template = systemTags.includes("ApplyTemplate") || systemTags.includes("template");
934
- if (attributes && "tags" in attributes && attributes.tags) {
935
- finalAttributes.contentTags = [...attributes.tags];
936
- }
937
- finalAttributes.tags = [...finalAttributes.contentTags || []];
938
- this.stm[key] = {
939
- value,
940
- attributes: finalAttributes
941
- };
942
- if (this.listeners[key]) {
943
- this.listeners[key].forEach((listener) => listener(value));
944
- }
945
- this.notifyGlobalListeners();
973
+ entryMap.set("value", value);
974
+ entryMap.set("attributes", normalizedAttributes);
975
+ });
946
976
  }
947
- // Internal method for setting values from remote (cloud sync)
948
- // This doesn't trigger the global listener to prevent sync loops
949
- _setFromRemote(key, value, attributes) {
977
+ delete_key(key) {
950
978
  if (key === "$date" || key === "$time") {
951
979
  return;
952
980
  }
953
- this._isRemoteUpdate = true;
954
- let systemTags = attributes.systemTags || [];
955
- if (!attributes.systemTags || systemTags.length === 0) {
956
- systemTags = [];
957
- if (attributes.visible !== false) {
958
- systemTags.push("prompt");
959
- }
960
- if (attributes.readonly) {
961
- systemTags.push("readonly");
962
- } else {
963
- systemTags.push("LLMWrite");
964
- }
965
- if (attributes.hardcoded) {
966
- systemTags.push("protected");
967
- }
968
- if (attributes.template) {
969
- systemTags.push("ApplyTemplate");
970
- }
971
- }
972
- systemTags = this.normalizeSystemTags(systemTags);
973
- const contentTags = attributes.contentTags || attributes.tags || [];
974
- this.stm[key] = {
975
- value,
976
- attributes: {
977
- ...attributes,
978
- contentTags,
979
- systemTags,
980
- zIndex: attributes.zIndex ?? 0,
981
- tags: contentTags,
982
- // Sync legacy attributes FROM normalized systemTags
983
- readonly: systemTags.includes("readonly") || !systemTags.includes("LLMWrite"),
984
- visible: this.hasSystemPrompt(systemTags),
985
- hardcoded: systemTags.includes("protected"),
986
- template: systemTags.includes("ApplyTemplate") || systemTags.includes("template")
987
- }
988
- };
989
- if (this.listeners[key]) {
990
- this.listeners[key].forEach((listener) => listener(value));
991
- }
992
- this.notifyGlobalListeners();
993
- this._isRemoteUpdate = false;
981
+ this.rootMap.delete(key);
994
982
  }
995
- // Check if current update is from remote
996
- isRemoteUpdate() {
997
- return this._isRemoteUpdate;
983
+ clear() {
984
+ const keys = Array.from(this.rootMap.keys());
985
+ this.doc.transact(() => {
986
+ keys.forEach((k) => this.rootMap.delete(k));
987
+ });
998
988
  }
999
- // Internal method for deleting from remote (cloud sync)
1000
- _deleteFromRemote(key) {
1001
- if (key === "$date" || key === "$time") {
989
+ // File methods
990
+ async set_file(key, file, attributes) {
991
+ const base64 = await this.encodeFileToBase64(file);
992
+ const dataUrl = this.createDataUrl(base64, file.type);
993
+ this.set_value(key, dataUrl, {
994
+ ...attributes,
995
+ type: "file",
996
+ contentType: file.type
997
+ });
998
+ }
999
+ set_image(key, file, attributes) {
1000
+ return this.set_file(key, file, {
1001
+ ...attributes,
1002
+ type: "image"
1003
+ // Override to image
1004
+ });
1005
+ }
1006
+ // Document methods for collaborative editing
1007
+ /**
1008
+ * Create or get a collaborative document key.
1009
+ * Uses Y.Text for character-level concurrent editing.
1010
+ *
1011
+ * Note: This exposes Yjs Y.Text directly for editor bindings (y-quill, y-codemirror, etc.)
1012
+ */
1013
+ set_document(key, initialText, attributes) {
1014
+ if (key === "$date" || key === "$time" || key === "$version") {
1002
1015
  return;
1003
1016
  }
1004
- this._isRemoteUpdate = true;
1005
- if (key in this.stm) {
1006
- delete this.stm[key];
1007
- if (this.listeners[key]) {
1008
- this.listeners[key].forEach((listener) => listener(void 0));
1009
- }
1010
- this.notifyGlobalListeners();
1017
+ let entryMap = this.rootMap.get(key);
1018
+ if (!entryMap) {
1019
+ entryMap = new Y.Map();
1020
+ this.rootMap.set(key, entryMap);
1021
+ const yText = new Y.Text(initialText || "");
1022
+ entryMap.set("value", yText);
1023
+ entryMap.set("attributes", {
1024
+ ...DEFAULT_KEY_ATTRIBUTES,
1025
+ type: "document",
1026
+ contentTags: [],
1027
+ systemTags: ["SystemPrompt", "LLMWrite"],
1028
+ tags: [],
1029
+ zIndex: 0,
1030
+ ...attributes
1031
+ });
1011
1032
  }
1012
- this._isRemoteUpdate = false;
1013
- }
1014
- // Internal method for clearing from remote (cloud sync)
1015
- _clearFromRemote() {
1016
- this._isRemoteUpdate = true;
1017
- this.stm = {};
1018
- this.notifyGlobalListeners();
1019
- this._isRemoteUpdate = false;
1033
+ this.getUndoManager(key);
1020
1034
  }
1021
- // Set attributes for an existing key
1022
- set_attributes(key, attributes) {
1023
- if (key === "$date" || key === "$time") {
1024
- return false;
1035
+ /**
1036
+ * Get the Y.Text object for a document key.
1037
+ * Use this to bind to editors (Quill, CodeMirror, Monaco, etc.)
1038
+ *
1039
+ * @returns Y.Text or undefined if key doesn't exist or isn't a document
1040
+ */
1041
+ get_document(key) {
1042
+ const entryMap = this.rootMap.get(key);
1043
+ if (!entryMap) {
1044
+ return void 0;
1025
1045
  }
1026
- const entry = this.stm[key];
1027
- if (!entry) {
1028
- return false;
1046
+ const attrs = entryMap.get("attributes");
1047
+ if (attrs?.type !== "document") {
1048
+ return void 0;
1029
1049
  }
1030
- const wasHardcoded = entry.attributes.hardcoded || entry.attributes.systemTags?.includes("protected");
1031
- const { hardcoded: _hardcoded, systemTags: _systemTags, ...allowedAttributes } = attributes;
1032
- if (wasHardcoded) {
1033
- if ("readonly" in allowedAttributes) {
1034
- delete allowedAttributes.readonly;
1035
- }
1036
- if ("template" in allowedAttributes) {
1037
- delete allowedAttributes.template;
1038
- }
1050
+ const value = entryMap.get("value");
1051
+ if (value instanceof Y.Text) {
1052
+ return value;
1039
1053
  }
1040
- entry.attributes = { ...entry.attributes, ...allowedAttributes };
1041
- if ("readonly" in attributes || "visible" in attributes || "template" in attributes || "systemTags" in attributes) {
1042
- let newSystemTags = entry.attributes.systemTags || [];
1043
- if ("systemTags" in attributes && Array.isArray(attributes.systemTags)) {
1044
- newSystemTags = this.normalizeSystemTags(attributes.systemTags);
1045
- } else {
1046
- newSystemTags = [];
1047
- if (!entry.attributes.readonly) {
1048
- newSystemTags.push("LLMWrite");
1049
- } else {
1050
- newSystemTags.push("readonly");
1051
- }
1052
- if (entry.attributes.visible) {
1053
- newSystemTags.push("SystemPrompt");
1054
- }
1055
- if (entry.attributes.template) {
1056
- newSystemTags.push("ApplyTemplate");
1057
- }
1058
- if (wasHardcoded || entry.attributes.hardcoded) {
1059
- newSystemTags.push("protected");
1060
- }
1061
- newSystemTags = this.normalizeSystemTags(newSystemTags);
1062
- }
1063
- if (newSystemTags.includes("protected")) {
1064
- newSystemTags = newSystemTags.filter((t) => t !== "LLMWrite");
1065
- if (!newSystemTags.includes("readonly")) {
1066
- newSystemTags.push("readonly");
1067
- }
1068
- newSystemTags = newSystemTags.filter((t) => t !== "ApplyTemplate" && t !== "template");
1069
- entry.attributes.readonly = true;
1070
- entry.attributes.template = false;
1071
- }
1072
- entry.attributes.systemTags = newSystemTags;
1073
- entry.attributes.readonly = newSystemTags.includes("readonly") || !newSystemTags.includes("LLMWrite");
1074
- entry.attributes.visible = this.hasSystemPrompt(newSystemTags);
1075
- entry.attributes.template = newSystemTags.includes("ApplyTemplate") || newSystemTags.includes("template");
1076
- } else if (wasHardcoded) {
1077
- let systemTags = this.normalizeSystemTags(entry.attributes.systemTags || []);
1078
- if (!systemTags.includes("protected")) {
1079
- systemTags.push("protected");
1080
- }
1081
- systemTags = systemTags.filter((t) => t !== "LLMWrite");
1082
- if (!systemTags.includes("readonly")) {
1083
- systemTags.push("readonly");
1084
- }
1085
- systemTags = systemTags.filter((t) => t !== "ApplyTemplate" && t !== "template");
1086
- entry.attributes.systemTags = systemTags;
1087
- entry.attributes.readonly = true;
1088
- entry.attributes.visible = this.hasSystemPrompt(systemTags);
1089
- entry.attributes.template = false;
1090
- }
1091
- if (wasHardcoded) {
1092
- entry.attributes.hardcoded = true;
1093
- if (!entry.attributes.systemTags?.includes("protected")) {
1094
- entry.attributes.systemTags = [...entry.attributes.systemTags || [], "protected"];
1095
- }
1096
- entry.attributes.readonly = true;
1097
- entry.attributes.template = false;
1098
- }
1099
- if ("contentTags" in attributes) {
1100
- entry.attributes.tags = [...entry.attributes.contentTags || []];
1101
- }
1102
- this.notifyGlobalListeners();
1103
- return true;
1054
+ return void 0;
1104
1055
  }
1105
- set(key, value) {
1106
- this.set_value(key, value);
1107
- }
1108
- async set_file(key, file, attributes) {
1109
- const base64Data = await this.encodeFileToBase64(file);
1110
- const contentType = file.type;
1111
- const fileAttributes = {
1112
- type: contentType.startsWith("image/") ? "image" : "file",
1113
- contentType,
1114
- ...attributes
1115
- };
1116
- this.set_value(key, base64Data, fileAttributes);
1117
- }
1118
- set_base64(key, base64Data, contentType, type = "file", attributes) {
1119
- if (!this.validateContentType(type, contentType)) {
1120
- throw new Error(`Invalid content type ${contentType} for type ${type}`);
1121
- }
1122
- const fileAttributes = {
1123
- type,
1124
- contentType,
1125
- ...attributes
1126
- };
1127
- this.set_value(key, base64Data, fileAttributes);
1128
- }
1129
- add_image(key, base64Data, contentType = "image/jpeg", attributes) {
1130
- if (!contentType.startsWith("image/")) {
1131
- throw new Error(`Invalid image content type: ${contentType}. Must start with 'image/'`);
1132
- }
1133
- this.set_base64(key, base64Data, contentType, "image", attributes);
1134
- this.set_attributes(key, {
1135
- type: "image",
1136
- contentType
1137
- });
1056
+ /**
1057
+ * Get plain text content of a document key.
1058
+ * For collaborative editing, use get_document() and bind to an editor.
1059
+ */
1060
+ get_document_text(key) {
1061
+ const yText = this.get_document(key);
1062
+ return yText?.toString();
1138
1063
  }
1139
- get_data_url(key) {
1140
- const entry = this.stm[key];
1141
- if (!entry || entry.attributes.type !== "image" && entry.attributes.type !== "file") {
1142
- return void 0;
1143
- }
1144
- if (!entry.attributes.contentType) {
1145
- return void 0;
1064
+ /**
1065
+ * Insert text at a position in a document key.
1066
+ */
1067
+ insert_text(key, index, text) {
1068
+ const yText = this.get_document(key);
1069
+ if (yText) {
1070
+ yText.insert(index, text);
1146
1071
  }
1147
- return this.createDataUrl(entry.value, entry.attributes.contentType);
1148
1072
  }
1149
- get_base64(key) {
1150
- const entry = this.stm[key];
1151
- if (!entry || entry.attributes.type !== "image" && entry.attributes.type !== "file") {
1152
- return void 0;
1073
+ /**
1074
+ * Delete text from a document key.
1075
+ */
1076
+ delete_text(key, index, length2) {
1077
+ const yText = this.get_document(key);
1078
+ if (yText) {
1079
+ yText.delete(index, length2);
1153
1080
  }
1154
- return entry.value;
1155
1081
  }
1156
- has(key) {
1157
- if (key === "$date" || key === "$time") {
1158
- return true;
1082
+ /**
1083
+ * Replace all text in a document key.
1084
+ * Uses diff-based updates when changes are < diffThreshold (default 80%).
1085
+ * This preserves concurrent edits and provides better undo granularity.
1086
+ *
1087
+ * @param key - The document key
1088
+ * @param newText - The new text content
1089
+ * @param diffThreshold - Percentage (0-1) of change above which full replace is used (default: 0.8)
1090
+ */
1091
+ replace_document_text(key, newText, diffThreshold = 0.8) {
1092
+ const yText = this.get_document(key);
1093
+ if (!yText) {
1094
+ return;
1159
1095
  }
1160
- return key in this.stm;
1161
- }
1162
- delete(key) {
1163
- if (key === "$date" || key === "$time") {
1164
- return false;
1096
+ const oldText = yText.toString();
1097
+ if (oldText === newText) {
1098
+ return;
1165
1099
  }
1166
- if (!(key in this.stm)) {
1167
- return false;
1100
+ if (oldText.length === 0) {
1101
+ yText.insert(0, newText);
1102
+ return;
1168
1103
  }
1169
- const deleted = delete this.stm[key];
1170
- if (deleted) {
1171
- this.notifyGlobalListeners();
1172
- if (this.listeners[key]) {
1173
- this.listeners[key].forEach((listener) => listener(void 0));
1104
+ const diffs = diff(oldText, newText);
1105
+ let changedChars = 0;
1106
+ for (const [op, text] of diffs) {
1107
+ if (op !== 0) {
1108
+ changedChars += text.length;
1174
1109
  }
1175
1110
  }
1176
- return deleted;
1177
- }
1178
- clear() {
1179
- this.stm = {};
1180
- this.notifyGlobalListeners();
1181
- }
1182
- /**
1183
- * Get keys sorted by zIndex (ascending), then by key name
1184
- */
1185
- getSortedKeys() {
1186
- return Object.entries(this.stm).sort(([keyA, entryA], [keyB, entryB]) => {
1187
- const zIndexA = entryA.attributes.zIndex ?? 0;
1188
- const zIndexB = entryB.attributes.zIndex ?? 0;
1189
- if (zIndexA !== zIndexB) {
1190
- return zIndexA - zIndexB;
1191
- }
1192
- return keyA.localeCompare(keyB);
1193
- }).map(([key]) => key);
1194
- }
1195
- keys() {
1196
- return [...this.getSortedKeys(), "$date", "$time"];
1197
- }
1198
- values() {
1199
- const now = /* @__PURE__ */ new Date();
1200
- const sortedKeys = this.getSortedKeys();
1201
- const stmValues = sortedKeys.map((key) => this.stm[key].value);
1202
- return [
1203
- ...stmValues,
1204
- now.toISOString().split("T")[0],
1205
- now.toTimeString().split(" ")[0]
1206
- ];
1207
- }
1208
- entries() {
1209
- const now = /* @__PURE__ */ new Date();
1210
- const sortedKeys = this.getSortedKeys();
1211
- const stmEntries = sortedKeys.map(
1212
- (key) => [key, this.stm[key].value]
1213
- );
1214
- return [
1215
- ...stmEntries,
1216
- ["$date", now.toISOString().split("T")[0]],
1217
- ["$time", now.toTimeString().split(" ")[0]]
1218
- ];
1219
- }
1220
- size() {
1221
- return Object.keys(this.stm).length + 2;
1222
- }
1223
- getAll() {
1224
- const now = /* @__PURE__ */ new Date();
1225
- const result = {};
1226
- const sortedKeys = this.getSortedKeys();
1227
- sortedKeys.forEach((key) => {
1228
- result[key] = this.stm[key].value;
1229
- });
1230
- result["$date"] = now.toISOString().split("T")[0];
1231
- result["$time"] = now.toTimeString().split(" ")[0];
1232
- return result;
1233
- }
1234
- update(newValues) {
1235
- Object.entries(newValues).forEach(([key, value]) => {
1236
- if (key !== "$date" && key !== "$time") {
1237
- this.stm[key] = {
1238
- value,
1239
- attributes: { ...DEFAULT_KEY_ATTRIBUTES }
1240
- };
1241
- if (this.listeners[key]) {
1242
- this.listeners[key].forEach((listener) => listener(this.stm[key]?.value));
1111
+ const changeRatio = changedChars / Math.max(oldText.length, newText.length);
1112
+ if (changeRatio > diffThreshold) {
1113
+ this.doc.transact(() => {
1114
+ yText.delete(0, yText.length);
1115
+ yText.insert(0, newText);
1116
+ });
1117
+ return;
1118
+ }
1119
+ this.doc.transact(() => {
1120
+ let cursor = 0;
1121
+ for (const [op, text] of diffs) {
1122
+ if (op === 0) {
1123
+ cursor += text.length;
1124
+ } else if (op === -1) {
1125
+ yText.delete(cursor, text.length);
1126
+ } else if (op === 1) {
1127
+ yText.insert(cursor, text);
1128
+ cursor += text.length;
1243
1129
  }
1244
1130
  }
1245
1131
  });
1246
- this.notifyGlobalListeners();
1247
1132
  }
1133
+ // ... (subscribe methods)
1248
1134
  subscribe(key, listener) {
1249
1135
  if (!this.listeners[key]) {
1250
1136
  this.listeners[key] = [];
1251
1137
  }
1252
1138
  this.listeners[key].push(listener);
1253
- }
1254
- unsubscribe(key, listener) {
1255
- if (this.listeners[key]) {
1139
+ return () => {
1256
1140
  this.listeners[key] = this.listeners[key].filter((l) => l !== listener);
1257
- }
1141
+ };
1258
1142
  }
1259
1143
  subscribeToAll(listener) {
1260
1144
  this.globalListeners.push(listener);
1145
+ return () => {
1146
+ this.globalListeners = this.globalListeners.filter((l) => l !== listener);
1147
+ };
1261
1148
  }
1262
1149
  unsubscribeFromAll(listener) {
1263
1150
  this.globalListeners = this.globalListeners.filter((l) => l !== listener);
1264
1151
  }
1265
1152
  notifyGlobalListeners() {
1266
- this.globalListeners.forEach((listener) => listener());
1267
- }
1268
- injectSTM(template, _processingStack) {
1269
- if (template === null || template === void 0) {
1270
- return String(template);
1271
- }
1272
- const templateStr = String(template);
1273
- const keys = templateStr.match(/\{\{([$\w]+)\}\}/g);
1274
- if (!keys) {
1275
- return templateStr;
1276
- }
1277
- const cleanKeys = keys.map((key) => key.replace(/[{}]/g, ""));
1278
- const inputValues = cleanKeys.reduce((acc, key) => {
1279
- if (key === "$date" || key === "$time") {
1280
- return {
1281
- ...acc,
1282
- [key]: this.get_value(key, _processingStack)
1283
- };
1284
- }
1285
- const attributes = this.get_attributes(key);
1286
- if (_processingStack || attributes && attributes.visible) {
1287
- if (attributes && (attributes.type === "image" || attributes.type === "file")) {
1288
- return acc;
1289
- }
1290
- return {
1291
- ...acc,
1292
- [key]: this.get_value(key, _processingStack)
1293
- };
1294
- }
1295
- return acc;
1296
- }, {});
1297
- return templateStr.replace(/\{\{([$\w]+)\}\}/g, (match, key) => {
1298
- if (inputValues[key] !== void 0) {
1299
- return inputValues[key];
1300
- }
1301
- const attributes = this.get_attributes(key);
1302
- if (attributes && (attributes.type === "image" || attributes.type === "file")) {
1303
- return match;
1304
- }
1305
- return "";
1306
- });
1307
- }
1308
- getSTM() {
1309
- const now = /* @__PURE__ */ new Date();
1310
- const entries = [];
1311
- const sortedKeys = this.getSortedKeys();
1312
- sortedKeys.forEach((key) => {
1313
- const entry = this.stm[key];
1314
- if (entry.attributes.visible) {
1315
- entries.push([key, this.get_value(key)]);
1316
- }
1317
- });
1318
- entries.push(["$date", now.toISOString().split("T")[0]]);
1319
- entries.push(["$time", now.toTimeString().split(" ")[0]]);
1320
- return entries.map(([key, value]) => `${key}: ${value}`).join(", ");
1321
- }
1322
- getSTMObject() {
1323
- return this.getAll();
1324
- }
1325
- getSTMForAPI() {
1326
- const now = /* @__PURE__ */ new Date();
1327
- const apiData = [];
1328
- const sortedKeys = this.getSortedKeys();
1329
- sortedKeys.forEach((key) => {
1330
- const entry = this.stm[key];
1331
- if (this.hasLLMRead(entry.attributes.systemTags) || entry.attributes.visible) {
1332
- const hasTemplate = entry.attributes.systemTags?.includes("ApplyTemplate") || entry.attributes.systemTags?.includes("template") || entry.attributes.template;
1333
- const processedValue = hasTemplate ? this.get_value(key) : entry.value;
1334
- apiData.push({
1335
- key,
1336
- value: processedValue,
1337
- type: entry.attributes.type,
1338
- contentType: entry.attributes.contentType
1339
- });
1340
- }
1341
- });
1342
- apiData.push({
1343
- key: "$date",
1344
- value: now.toISOString().split("T")[0],
1345
- type: "text"
1346
- });
1347
- apiData.push({
1348
- key: "$time",
1349
- value: now.toTimeString().split(" ")[0],
1350
- type: "text"
1351
- });
1352
- return apiData;
1353
- }
1354
- getVisibleImages() {
1355
- const imageParts = [];
1356
- const sortedKeys = this.getSortedKeys();
1357
- sortedKeys.forEach((key) => {
1358
- const entry = this.stm[key];
1359
- if ((this.hasLLMRead(entry.attributes.systemTags) || entry.attributes.visible) && entry.attributes.type === "image" && entry.attributes.contentType) {
1360
- const dataUrl = this.createDataUrl(entry.value, entry.attributes.contentType);
1361
- imageParts.push({
1362
- type: "file",
1363
- mediaType: entry.attributes.contentType,
1364
- url: dataUrl,
1365
- filename: key
1366
- });
1367
- }
1368
- });
1369
- return imageParts;
1370
- }
1371
- toJSON() {
1372
- return JSON.stringify(this.serialize());
1373
- }
1374
- fromJSON(jsonString) {
1375
- try {
1376
- const data = JSON.parse(jsonString);
1377
- this.deserialize(data);
1378
- } catch (error) {
1379
- console.error("MindCache: Failed to deserialize JSON:", error);
1380
- }
1381
- }
1382
- serialize() {
1383
- const result = {};
1384
- const sortedKeys = this.getSortedKeys();
1385
- sortedKeys.forEach((key) => {
1386
- const entry = this.stm[key];
1387
- if (!entry.attributes.hardcoded) {
1388
- result[key] = {
1389
- value: entry.value,
1390
- attributes: { ...entry.attributes }
1391
- };
1392
- }
1393
- });
1394
- return result;
1153
+ this.globalListeners.forEach((l) => l());
1395
1154
  }
1396
- deserialize(data) {
1397
- if (typeof data === "object" && data !== null) {
1398
- this._isRemoteUpdate = true;
1399
- this.clear();
1400
- Object.entries(data).forEach(([key, entry]) => {
1401
- if (entry && typeof entry === "object" && "value" in entry && "attributes" in entry) {
1402
- const attrs = entry.attributes;
1403
- if (attrs.hardcoded === true || attrs.systemTags?.includes("protected")) {
1404
- return;
1405
- }
1406
- let systemTags = attrs.systemTags || [];
1407
- if (!attrs.systemTags || systemTags.length === 0) {
1408
- systemTags = [];
1409
- if (attrs.visible !== false) {
1410
- systemTags.push("prompt");
1411
- }
1412
- if (attrs.readonly) {
1413
- systemTags.push("readonly");
1414
- } else {
1415
- systemTags.push("LLMWrite");
1416
- }
1417
- if (attrs.hardcoded) {
1418
- systemTags.push("protected");
1419
- }
1420
- if (attrs.template) {
1421
- systemTags.push("ApplyTemplate");
1422
- }
1423
- }
1424
- systemTags = this.normalizeSystemTags(systemTags);
1425
- const contentTags = attrs.contentTags || attrs.tags || [];
1426
- this.stm[key] = {
1427
- value: entry.value,
1428
- attributes: {
1429
- ...attrs,
1430
- contentTags,
1431
- systemTags,
1432
- zIndex: attrs.zIndex ?? 0,
1433
- // Sync legacy attributes FROM normalized systemTags
1434
- tags: contentTags,
1435
- readonly: systemTags.includes("readonly") || !systemTags.includes("LLMWrite"),
1436
- visible: this.hasSystemPrompt(systemTags),
1437
- hardcoded: systemTags.includes("protected"),
1438
- template: systemTags.includes("ApplyTemplate") || systemTags.includes("template")
1439
- }
1440
- };
1441
- }
1442
- });
1443
- this.notifyGlobalListeners();
1444
- this._isRemoteUpdate = false;
1445
- }
1155
+ // Sanitize key name for use in tool names
1156
+ sanitizeKeyForTool(key) {
1157
+ return key.replace(/[^a-zA-Z0-9_-]/g, "_");
1446
1158
  }
1447
- get_system_prompt() {
1448
- const now = /* @__PURE__ */ new Date();
1449
- const promptLines = [];
1450
- const sortedKeys = this.getSortedKeys();
1451
- sortedKeys.forEach((key) => {
1452
- const entry = this.stm[key];
1453
- if (this.hasLLMRead(entry.attributes.systemTags) || entry.attributes.visible) {
1454
- if (entry.attributes.type === "image") {
1455
- promptLines.push(`image ${key} available`);
1456
- return;
1457
- }
1458
- if (entry.attributes.type === "file") {
1459
- const canWrite2 = this.hasLLMWrite(entry.attributes.systemTags) || !entry.attributes.readonly && !entry.attributes.systemTags.includes("readonly");
1460
- if (!canWrite2) {
1461
- promptLines.push(`${key}: [${entry.attributes.type.toUpperCase()}] - ${entry.attributes.contentType || "unknown format"}`);
1462
- } else {
1463
- const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, "_");
1464
- promptLines.push(`${key}: [${entry.attributes.type.toUpperCase()}] - ${entry.attributes.contentType || "unknown format"}. You can update this ${entry.attributes.type} using the write_${sanitizedKey} tool.`);
1465
- }
1466
- return;
1467
- }
1468
- const value = this.get_value(key);
1469
- const formattedValue = typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
1470
- const canWrite = this.hasLLMWrite(entry.attributes.systemTags) || !entry.attributes.readonly && !entry.attributes.systemTags.includes("readonly");
1471
- if (!canWrite) {
1472
- promptLines.push(`${key}: ${formattedValue}`);
1473
- } else {
1474
- const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, "_");
1475
- const toolInstruction = `You can rewrite "${key}" by using the write_${sanitizedKey} tool. This tool DOES NOT append \u2014 start your response with the old value (${formattedValue})`;
1476
- promptLines.push(`${key}: ${formattedValue}. ${toolInstruction}`);
1477
- }
1159
+ // Find original key from sanitized tool name
1160
+ findKeyFromSanitizedTool(sanitizedKey) {
1161
+ for (const [key] of this.rootMap) {
1162
+ if (this.sanitizeKeyForTool(key) === sanitizedKey) {
1163
+ return key;
1478
1164
  }
1479
- });
1480
- promptLines.push(`$date: ${now.toISOString().split("T")[0]}`);
1481
- promptLines.push(`$time: ${now.toTimeString().split(" ")[0]}`);
1482
- return promptLines.join("\n");
1483
- }
1484
- findKeyFromToolName(toolName) {
1485
- if (!toolName.startsWith("write_")) {
1486
- return void 0;
1487
1165
  }
1488
- const sanitizedKey = toolName.replace("write_", "");
1489
- const sortedKeys = this.getSortedKeys();
1490
- return sortedKeys.find(
1491
- (k) => k.replace(/[^a-zA-Z0-9_-]/g, "_") === sanitizedKey
1492
- );
1166
+ return void 0;
1493
1167
  }
1168
+ /**
1169
+ * Generate Vercel AI SDK compatible tools for writable keys.
1170
+ * For document type keys, generates additional tools: append_, insert_, edit_
1171
+ */
1494
1172
  get_aisdk_tools() {
1495
1173
  const tools = {};
1496
- const sortedKeys = this.getSortedKeys();
1497
- const writableKeys = sortedKeys.filter((key) => {
1498
- const entry = this.stm[key];
1499
- return this.hasLLMWrite(entry.attributes.systemTags) || !entry.attributes.readonly && !entry.attributes.systemTags.includes("readonly");
1500
- });
1501
- writableKeys.forEach((key) => {
1502
- const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, "_");
1503
- const toolName = `write_${sanitizedKey}`;
1504
- const entry = this.stm[key];
1505
- const keyType = entry?.attributes.type || "text";
1506
- let inputSchema;
1507
- let description = `Write a value to the STM key: ${key}`;
1508
- if (keyType === "image" || keyType === "file") {
1509
- description += " (expects base64 encoded data)";
1510
- inputSchema = z.object({
1511
- value: z.string().describe(`Base64 encoded data for ${key}`),
1512
- contentType: z.string().optional().describe(`MIME type for the ${keyType}`)
1513
- });
1514
- } else if (keyType === "json") {
1515
- description += " (expects JSON string)";
1516
- inputSchema = z.object({
1517
- value: z.string().describe(`JSON string value for ${key}`)
1518
- });
1519
- } else {
1520
- inputSchema = z.object({
1521
- value: z.string().describe(`The text value to write to ${key}`)
1522
- });
1174
+ for (const [key, val] of this.rootMap) {
1175
+ if (key.startsWith("$")) {
1176
+ continue;
1523
1177
  }
1524
- tools[toolName] = {
1525
- description,
1526
- inputSchema,
1527
- execute: async (input) => {
1528
- if (keyType === "image" || keyType === "file") {
1529
- if (input.contentType) {
1530
- this.set_base64(key, input.value, input.contentType, keyType);
1531
- } else {
1532
- const existingContentType = entry?.attributes.contentType;
1533
- if (existingContentType) {
1534
- this.set_base64(key, input.value, existingContentType, keyType);
1535
- } else {
1536
- throw new Error(`Content type required for ${keyType} data`);
1537
- }
1538
- }
1539
- } else {
1540
- this.set_value(key, input.value);
1541
- }
1542
- let resultMessage;
1543
- if (keyType === "image") {
1544
- resultMessage = `Successfully saved image to ${key}`;
1545
- } else if (keyType === "file") {
1546
- resultMessage = `Successfully saved file to ${key}`;
1547
- } else if (keyType === "json") {
1548
- resultMessage = `Successfully saved JSON data to ${key}`;
1178
+ const entryMap = val;
1179
+ const attributes = entryMap.get("attributes");
1180
+ const isWritable = !attributes?.readonly && (attributes?.systemTags?.includes("LLMWrite") || !attributes?.systemTags);
1181
+ if (!isWritable) {
1182
+ continue;
1183
+ }
1184
+ const sanitizedKey = this.sanitizeKeyForTool(key);
1185
+ const isDocument = attributes?.type === "document";
1186
+ tools[`write_${sanitizedKey}`] = {
1187
+ description: isDocument ? `Rewrite the entire "${key}" document` : `Write a value to the STM key: ${key}`,
1188
+ inputSchema: {
1189
+ type: "object",
1190
+ properties: {
1191
+ value: { type: "string", description: isDocument ? "New document content" : "The value to write" }
1192
+ },
1193
+ required: ["value"]
1194
+ },
1195
+ execute: async ({ value }) => {
1196
+ if (isDocument) {
1197
+ this.replace_document_text(key, value);
1549
1198
  } else {
1550
- resultMessage = `Successfully wrote "${input.value}" to ${key}`;
1199
+ this.set_value(key, value);
1551
1200
  }
1552
1201
  return {
1553
- result: resultMessage,
1202
+ result: `Successfully wrote "${value}" to ${key}`,
1554
1203
  key,
1555
- value: input.value,
1556
- type: keyType,
1557
- contentType: input.contentType,
1558
- sanitizedKey
1204
+ value
1559
1205
  };
1560
1206
  }
1561
1207
  };
1562
- });
1563
- if (writableKeys.length === 0) {
1564
- return {};
1208
+ if (isDocument) {
1209
+ tools[`append_${sanitizedKey}`] = {
1210
+ description: `Append text to the end of "${key}" document`,
1211
+ inputSchema: {
1212
+ type: "object",
1213
+ properties: {
1214
+ text: { type: "string", description: "Text to append" }
1215
+ },
1216
+ required: ["text"]
1217
+ },
1218
+ execute: async ({ text }) => {
1219
+ const yText = this.get_document(key);
1220
+ if (yText) {
1221
+ yText.insert(yText.length, text);
1222
+ return {
1223
+ result: `Successfully appended to ${key}`,
1224
+ key,
1225
+ appended: text
1226
+ };
1227
+ }
1228
+ return { result: `Document ${key} not found`, key };
1229
+ }
1230
+ };
1231
+ tools[`insert_${sanitizedKey}`] = {
1232
+ description: `Insert text at a position in "${key}" document`,
1233
+ inputSchema: {
1234
+ type: "object",
1235
+ properties: {
1236
+ index: { type: "number", description: "Position to insert at (0 = start)" },
1237
+ text: { type: "string", description: "Text to insert" }
1238
+ },
1239
+ required: ["index", "text"]
1240
+ },
1241
+ execute: async ({ index, text }) => {
1242
+ this.insert_text(key, index, text);
1243
+ return {
1244
+ result: `Successfully inserted text at position ${index} in ${key}`,
1245
+ key,
1246
+ index,
1247
+ inserted: text
1248
+ };
1249
+ }
1250
+ };
1251
+ tools[`edit_${sanitizedKey}`] = {
1252
+ description: `Find and replace text in "${key}" document`,
1253
+ inputSchema: {
1254
+ type: "object",
1255
+ properties: {
1256
+ find: { type: "string", description: "Text to find" },
1257
+ replace: { type: "string", description: "Replacement text" }
1258
+ },
1259
+ required: ["find", "replace"]
1260
+ },
1261
+ execute: async ({ find, replace }) => {
1262
+ const yText = this.get_document(key);
1263
+ if (yText) {
1264
+ const text = yText.toString();
1265
+ const idx = text.indexOf(find);
1266
+ if (idx !== -1) {
1267
+ yText.delete(idx, find.length);
1268
+ yText.insert(idx, replace);
1269
+ return {
1270
+ result: `Successfully replaced "${find}" with "${replace}" in ${key}`,
1271
+ key,
1272
+ find,
1273
+ replace,
1274
+ index: idx
1275
+ };
1276
+ }
1277
+ return { result: `Text "${find}" not found in ${key}`, key };
1278
+ }
1279
+ return { result: `Document ${key} not found`, key };
1280
+ }
1281
+ };
1282
+ }
1565
1283
  }
1566
1284
  return tools;
1567
1285
  }
1568
- executeToolCall(toolName, value) {
1569
- const originalKey = this.findKeyFromToolName(toolName);
1570
- if (!originalKey) {
1571
- return null;
1572
- }
1573
- const entry = this.stm[originalKey];
1574
- const canWrite = entry && (this.hasLLMWrite(entry.attributes.systemTags) || !entry.attributes.readonly && !entry.attributes.systemTags.includes("readonly"));
1575
- if (!canWrite) {
1576
- return null;
1577
- }
1578
- this.set_value(originalKey, value);
1579
- return {
1580
- result: `Successfully wrote "${value}" to ${originalKey}`,
1581
- key: originalKey,
1582
- value
1583
- };
1584
- }
1585
- // ============================================
1586
- // Content Tag Methods (available to all access levels)
1587
- // ============================================
1588
- /**
1589
- * Add a content tag to a key (user-level organization)
1590
- */
1591
- addTag(key, tag) {
1592
- if (key === "$date" || key === "$time") {
1593
- return false;
1594
- }
1595
- const entry = this.stm[key];
1596
- if (!entry) {
1597
- return false;
1598
- }
1599
- if (!entry.attributes.contentTags) {
1600
- entry.attributes.contentTags = [];
1601
- }
1602
- if (!entry.attributes.contentTags.includes(tag)) {
1603
- entry.attributes.contentTags.push(tag);
1604
- entry.attributes.tags = [...entry.attributes.contentTags];
1605
- this.notifyGlobalListeners();
1606
- return true;
1607
- }
1608
- return false;
1609
- }
1610
- /**
1611
- * Remove a content tag from a key
1612
- */
1613
- removeTag(key, tag) {
1614
- if (key === "$date" || key === "$time") {
1615
- return false;
1616
- }
1617
- const entry = this.stm[key];
1618
- if (!entry || !entry.attributes.contentTags) {
1619
- return false;
1620
- }
1621
- const tagIndex = entry.attributes.contentTags.indexOf(tag);
1622
- if (tagIndex > -1) {
1623
- entry.attributes.contentTags.splice(tagIndex, 1);
1624
- entry.attributes.tags = [...entry.attributes.contentTags];
1625
- this.notifyGlobalListeners();
1626
- return true;
1627
- }
1628
- return false;
1629
- }
1630
1286
  /**
1631
- * Get all content tags for a key
1287
+ * Generate a system prompt containing all visible STM keys and their values.
1288
+ * Indicates which tools can be used to modify writable keys.
1632
1289
  */
1633
- getTags(key) {
1634
- if (key === "$date" || key === "$time") {
1635
- return [];
1636
- }
1637
- const entry = this.stm[key];
1638
- return entry?.attributes.contentTags || [];
1639
- }
1640
- /**
1641
- * Get all unique content tags across all keys
1642
- */
1643
- getAllTags() {
1644
- const allTags = /* @__PURE__ */ new Set();
1645
- Object.values(this.stm).forEach((entry) => {
1646
- if (entry.attributes.contentTags) {
1647
- entry.attributes.contentTags.forEach((tag) => allTags.add(tag));
1290
+ get_system_prompt() {
1291
+ const lines = [];
1292
+ for (const [key, val] of this.rootMap) {
1293
+ if (key.startsWith("$")) {
1294
+ continue;
1648
1295
  }
1649
- });
1650
- return Array.from(allTags);
1651
- }
1652
- /**
1653
- * Check if a key has a specific content tag
1654
- */
1655
- hasTag(key, tag) {
1656
- if (key === "$date" || key === "$time") {
1657
- return false;
1658
- }
1659
- const entry = this.stm[key];
1660
- return entry?.attributes.contentTags?.includes(tag) || false;
1661
- }
1662
- /**
1663
- * Get all keys with a specific content tag as formatted string
1664
- */
1665
- getTagged(tag) {
1666
- const entries = [];
1667
- const sortedKeys = this.getSortedKeys();
1668
- sortedKeys.forEach((key) => {
1669
- const entry = this.stm[key];
1670
- if (entry.attributes.contentTags?.includes(tag)) {
1671
- entries.push([key, this.get_value(key)]);
1296
+ const entryMap = val;
1297
+ const attributes = entryMap.get("attributes");
1298
+ const isVisible = attributes?.visible !== false && (attributes?.systemTags?.includes("prompt") || attributes?.systemTags?.includes("SystemPrompt") || !attributes?.systemTags);
1299
+ if (!isVisible) {
1300
+ continue;
1672
1301
  }
1673
- });
1674
- return entries.map(([key, value]) => `${key}: ${value}`).join(", ");
1675
- }
1676
- /**
1677
- * Get all keys with a specific content tag
1678
- */
1679
- getKeysByTag(tag) {
1680
- const sortedKeys = this.getSortedKeys();
1681
- return sortedKeys.filter((key) => {
1682
- const entry = this.stm[key];
1683
- return entry.attributes.contentTags?.includes(tag);
1684
- });
1685
- }
1686
- // ============================================
1687
- // System Tag Methods (requires system access level)
1688
- // ============================================
1689
- /**
1690
- * Add a system tag to a key (requires system access)
1691
- * System tags: 'prompt', 'readonly', 'protected', 'template'
1692
- */
1693
- systemAddTag(key, tag) {
1694
- if (!this.hasSystemAccess) {
1695
- console.warn("MindCache: systemAddTag requires system access level");
1696
- return false;
1697
- }
1698
- if (key === "$date" || key === "$time") {
1699
- return false;
1700
- }
1701
- const entry = this.stm[key];
1702
- if (!entry) {
1703
- return false;
1704
- }
1705
- if (!entry.attributes.systemTags) {
1706
- entry.attributes.systemTags = [];
1707
- }
1708
- if (!entry.attributes.systemTags.includes(tag)) {
1709
- entry.attributes.systemTags.push(tag);
1710
- this.syncLegacyFromSystemTags(entry);
1711
- this.notifyGlobalListeners();
1712
- return true;
1713
- }
1714
- return false;
1715
- }
1716
- /**
1717
- * Remove a system tag from a key (requires system access)
1718
- */
1719
- systemRemoveTag(key, tag) {
1720
- if (!this.hasSystemAccess) {
1721
- console.warn("MindCache: systemRemoveTag requires system access level");
1722
- return false;
1723
- }
1724
- if (key === "$date" || key === "$time") {
1725
- return false;
1726
- }
1727
- const entry = this.stm[key];
1728
- if (!entry || !entry.attributes.systemTags) {
1729
- return false;
1730
- }
1731
- const isHardcoded = entry.attributes.hardcoded || entry.attributes.systemTags.includes("protected");
1732
- if (tag === "protected" && isHardcoded) {
1733
- return false;
1734
- }
1735
- const tagIndex = entry.attributes.systemTags.indexOf(tag);
1736
- if (tagIndex > -1) {
1737
- entry.attributes.systemTags.splice(tagIndex, 1);
1738
- this.syncLegacyFromSystemTags(entry);
1739
- if (isHardcoded) {
1740
- if (!entry.attributes.systemTags.includes("protected")) {
1741
- entry.attributes.systemTags.push("protected");
1302
+ const value = this.get_value(key);
1303
+ const displayValue = typeof value === "object" ? JSON.stringify(value) : value;
1304
+ const isWritable = !attributes?.readonly && (attributes?.systemTags?.includes("LLMWrite") || !attributes?.systemTags);
1305
+ const isDocument = attributes?.type === "document";
1306
+ const sanitizedKey = this.sanitizeKeyForTool(key);
1307
+ if (isWritable) {
1308
+ if (isDocument) {
1309
+ lines.push(`${key}: ${displayValue}. Document tools: write_${sanitizedKey}, append_${sanitizedKey}, edit_${sanitizedKey}`);
1310
+ } else {
1311
+ const oldValueHint = displayValue ? ` This tool DOES NOT append \u2014 start your response with the old value (${displayValue})` : "";
1312
+ lines.push(`${key}: ${displayValue}. You can rewrite "${key}" by using the write_${sanitizedKey} tool.${oldValueHint}`);
1742
1313
  }
1743
- entry.attributes.hardcoded = true;
1744
- entry.attributes.readonly = true;
1745
- entry.attributes.template = false;
1314
+ } else {
1315
+ lines.push(`${key}: ${displayValue}`);
1746
1316
  }
1747
- this.notifyGlobalListeners();
1748
- return true;
1749
- }
1750
- return false;
1751
- }
1752
- /**
1753
- * Get all system tags for a key (requires system access)
1754
- */
1755
- systemGetTags(key) {
1756
- if (!this.hasSystemAccess) {
1757
- console.warn("MindCache: systemGetTags requires system access level");
1758
- return [];
1759
- }
1760
- if (key === "$date" || key === "$time") {
1761
- return [];
1762
- }
1763
- const entry = this.stm[key];
1764
- return entry?.attributes.systemTags || [];
1765
- }
1766
- /**
1767
- * Check if a key has a specific system tag (requires system access)
1768
- */
1769
- systemHasTag(key, tag) {
1770
- if (!this.hasSystemAccess) {
1771
- console.warn("MindCache: systemHasTag requires system access level");
1772
- return false;
1773
- }
1774
- if (key === "$date" || key === "$time") {
1775
- return false;
1776
1317
  }
1777
- const entry = this.stm[key];
1778
- return entry?.attributes.systemTags?.includes(tag) || false;
1318
+ lines.push(`$date: ${this.get_value("$date")}`);
1319
+ lines.push(`$time: ${this.get_value("$time")}`);
1320
+ return lines.join(", ");
1779
1321
  }
1780
1322
  /**
1781
- * Set all system tags for a key at once (requires system access)
1323
+ * Execute a tool call by name with the given value.
1324
+ * Returns the result or null if tool not found.
1782
1325
  */
1783
- systemSetTags(key, tags) {
1784
- if (!this.hasSystemAccess) {
1785
- console.warn("MindCache: systemSetTags requires system access level");
1786
- return false;
1787
- }
1788
- if (key === "$date" || key === "$time") {
1789
- return false;
1326
+ executeToolCall(toolName, value) {
1327
+ const match = toolName.match(/^(write|append|insert|edit)_(.+)$/);
1328
+ if (!match) {
1329
+ return null;
1790
1330
  }
1791
- const entry = this.stm[key];
1792
- if (!entry) {
1793
- return false;
1331
+ const [, action, sanitizedKey] = match;
1332
+ const key = this.findKeyFromSanitizedTool(sanitizedKey);
1333
+ if (!key) {
1334
+ return null;
1794
1335
  }
1795
- entry.attributes.systemTags = [...tags];
1796
- this.syncLegacyFromSystemTags(entry);
1797
- this.notifyGlobalListeners();
1798
- return true;
1799
- }
1800
- /**
1801
- * Get all keys with a specific system tag (requires system access)
1802
- */
1803
- systemGetKeysByTag(tag) {
1804
- if (!this.hasSystemAccess) {
1805
- console.warn("MindCache: systemGetKeysByTag requires system access level");
1806
- return [];
1336
+ const entryMap = this.rootMap.get(key);
1337
+ if (!entryMap) {
1338
+ return null;
1807
1339
  }
1808
- const sortedKeys = this.getSortedKeys();
1809
- return sortedKeys.filter((key) => {
1810
- const entry = this.stm[key];
1811
- return entry.attributes.systemTags?.includes(tag);
1812
- });
1813
- }
1814
- /**
1815
- * Helper to sync legacy boolean attributes from system tags
1816
- */
1817
- syncLegacyFromSystemTags(entry) {
1818
- const tags = entry.attributes.systemTags || [];
1819
- entry.attributes.readonly = tags.includes("readonly");
1820
- entry.attributes.visible = tags.includes("prompt");
1821
- entry.attributes.hardcoded = tags.includes("protected");
1822
- entry.attributes.template = tags.includes("ApplyTemplate") || tags.includes("template");
1823
- }
1824
- toMarkdown() {
1825
- const now = /* @__PURE__ */ new Date();
1826
- const lines = [];
1827
- const appendixEntries = [];
1828
- let appendixCounter = 0;
1829
- lines.push("# MindCache STM Export");
1830
- lines.push("");
1831
- lines.push(`Export Date: ${now.toISOString().split("T")[0]}`);
1832
- lines.push("");
1833
- lines.push("---");
1834
- lines.push("");
1835
- lines.push("## STM Entries");
1836
- lines.push("");
1837
- const sortedKeys = this.getSortedKeys();
1838
- sortedKeys.forEach((key) => {
1839
- const entry = this.stm[key];
1840
- if (entry.attributes.hardcoded) {
1841
- return;
1842
- }
1843
- lines.push(`### ${key}`);
1844
- const entryType = entry.attributes.type && entry.attributes.type !== "undefined" ? entry.attributes.type : "text";
1845
- lines.push(`- **Type**: \`${entryType}\``);
1846
- lines.push(`- **Readonly**: \`${entry.attributes.readonly}\``);
1847
- lines.push(`- **Visible**: \`${entry.attributes.visible}\``);
1848
- lines.push(`- **Template**: \`${entry.attributes.template}\``);
1849
- lines.push(`- **Z-Index**: \`${entry.attributes.zIndex ?? 0}\``);
1850
- if (entry.attributes.tags && entry.attributes.tags.length > 0) {
1851
- lines.push(`- **Tags**: \`${entry.attributes.tags.join("`, `")}\``);
1852
- }
1853
- if (entry.attributes.contentType) {
1854
- lines.push(`- **Content Type**: \`${entry.attributes.contentType}\``);
1855
- }
1856
- if (entryType === "image" || entryType === "file") {
1857
- const label = String.fromCharCode(65 + appendixCounter);
1858
- appendixCounter++;
1859
- lines.push(`- **Value**: [See Appendix ${label}]`);
1860
- appendixEntries.push({
1861
- key,
1862
- type: entryType,
1863
- contentType: entry.attributes.contentType || "application/octet-stream",
1864
- base64: entry.value,
1865
- label
1866
- });
1867
- } else if (entryType === "json") {
1868
- lines.push("- **Value**:");
1869
- lines.push("```json");
1870
- try {
1871
- const jsonValue = typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value, null, 2);
1872
- lines.push(jsonValue);
1873
- } catch {
1874
- lines.push(String(entry.value));
1875
- }
1876
- lines.push("```");
1877
- } else {
1878
- const valueStr = String(entry.value);
1879
- lines.push("- **Value**:");
1880
- lines.push("```");
1881
- lines.push(valueStr);
1882
- lines.push("```");
1883
- }
1884
- lines.push("");
1885
- lines.push("---");
1886
- lines.push("");
1887
- });
1888
- if (appendixEntries.length > 0) {
1889
- lines.push("## Appendix: Binary Data");
1890
- lines.push("");
1891
- appendixEntries.forEach(({ key, contentType, base64, label }) => {
1892
- lines.push(`### Appendix ${label}: ${key}`);
1893
- lines.push(`**Type**: ${contentType}`);
1894
- lines.push("");
1895
- lines.push("```");
1896
- lines.push(base64);
1897
- lines.push("```");
1898
- lines.push("");
1899
- lines.push("---");
1900
- lines.push("");
1901
- });
1340
+ const attributes = entryMap.get("attributes");
1341
+ const isWritable = !attributes?.readonly && (attributes?.systemTags?.includes("LLMWrite") || !attributes?.systemTags);
1342
+ if (!isWritable) {
1343
+ return null;
1902
1344
  }
1903
- lines.push("*End of MindCache Export*");
1904
- return lines.join("\n");
1905
- }
1906
- fromMarkdown(markdown) {
1907
- const lines = markdown.split("\n");
1908
- let currentSection = "header";
1909
- let currentKey = null;
1910
- let currentEntry = null;
1911
- let inCodeBlock = false;
1912
- let codeBlockContent = [];
1913
- let codeBlockType = null;
1914
- const appendixData = {};
1915
- let currentAppendixKey = null;
1916
- const pendingEntries = {};
1917
- this.clear();
1918
- for (let i = 0; i < lines.length; i++) {
1919
- const line = lines[i];
1920
- const trimmed = line.trim();
1921
- if (trimmed === "## STM Entries") {
1922
- currentSection = "entries";
1923
- continue;
1924
- }
1925
- if (trimmed === "## Appendix: Binary Data") {
1926
- currentSection = "appendix";
1927
- continue;
1928
- }
1929
- if (trimmed === "```" || trimmed === "```json") {
1930
- if (!inCodeBlock) {
1931
- inCodeBlock = true;
1932
- codeBlockContent = [];
1933
- codeBlockType = currentSection === "appendix" ? "base64" : trimmed === "```json" ? "json" : "value";
1345
+ const isDocument = attributes?.type === "document";
1346
+ switch (action) {
1347
+ case "write":
1348
+ if (isDocument) {
1349
+ this.replace_document_text(key, value);
1934
1350
  } else {
1935
- inCodeBlock = false;
1936
- const content = codeBlockContent.join("\n");
1937
- if (currentSection === "appendix" && currentAppendixKey) {
1938
- appendixData[currentAppendixKey].base64 = content;
1939
- } else if (currentEntry && codeBlockType === "json") {
1940
- currentEntry.value = content;
1941
- } else if (currentEntry && codeBlockType === "value") {
1942
- currentEntry.value = content;
1943
- }
1944
- codeBlockContent = [];
1945
- codeBlockType = null;
1351
+ this.set_value(key, value);
1946
1352
  }
1947
- continue;
1948
- }
1949
- if (inCodeBlock) {
1950
- codeBlockContent.push(line);
1951
- continue;
1952
- }
1953
- if (currentSection === "entries") {
1954
- if (trimmed.startsWith("### ")) {
1955
- if (currentKey && currentEntry && currentEntry.attributes) {
1956
- pendingEntries[currentKey] = currentEntry;
1353
+ return {
1354
+ result: `Successfully wrote "${value}" to ${key}`,
1355
+ key,
1356
+ value
1357
+ };
1358
+ case "append":
1359
+ if (isDocument) {
1360
+ const yText = this.get_document(key);
1361
+ if (yText) {
1362
+ yText.insert(yText.length, value);
1363
+ return {
1364
+ result: `Successfully appended to ${key}`,
1365
+ key,
1366
+ value
1367
+ };
1957
1368
  }
1958
- currentKey = trimmed.substring(4);
1959
- currentEntry = {
1960
- value: void 0,
1961
- attributes: {
1962
- ...DEFAULT_KEY_ATTRIBUTES,
1963
- contentTags: [],
1964
- systemTags: ["prompt"],
1965
- tags: []
1966
- }
1369
+ }
1370
+ return null;
1371
+ case "insert":
1372
+ if (isDocument && typeof value === "object" && value.index !== void 0 && value.text) {
1373
+ this.insert_text(key, value.index, value.text);
1374
+ return {
1375
+ result: `Successfully inserted at position ${value.index} in ${key}`,
1376
+ key,
1377
+ value: value.text
1967
1378
  };
1968
- } else if (trimmed.startsWith("- **Type**: `")) {
1969
- const type = trimmed.match(/`([^`]+)`/)?.[1];
1970
- if (currentEntry && type && type !== "undefined") {
1971
- currentEntry.attributes.type = type;
1972
- }
1973
- } else if (trimmed.startsWith("- **Readonly**: `")) {
1974
- const value = trimmed.match(/`([^`]+)`/)?.[1] === "true";
1975
- if (currentEntry) {
1976
- currentEntry.attributes.readonly = value;
1977
- }
1978
- } else if (trimmed.startsWith("- **Visible**: `")) {
1979
- const value = trimmed.match(/`([^`]+)`/)?.[1] === "true";
1980
- if (currentEntry) {
1981
- currentEntry.attributes.visible = value;
1982
- }
1983
- } else if (trimmed.startsWith("- **Template**: `")) {
1984
- const value = trimmed.match(/`([^`]+)`/)?.[1] === "true";
1985
- if (currentEntry) {
1986
- currentEntry.attributes.template = value;
1987
- }
1988
- } else if (trimmed.startsWith("- **Z-Index**: `")) {
1989
- const zIndexStr = trimmed.match(/`([^`]+)`/)?.[1];
1990
- if (currentEntry && zIndexStr) {
1991
- const zIndex = parseInt(zIndexStr, 10);
1992
- if (!isNaN(zIndex)) {
1993
- currentEntry.attributes.zIndex = zIndex;
1994
- }
1995
- }
1996
- } else if (trimmed.startsWith("- **Tags**: `")) {
1997
- const tagsStr = trimmed.substring(13, trimmed.length - 1);
1998
- if (currentEntry) {
1999
- currentEntry.attributes.tags = tagsStr.split("`, `");
2000
- }
2001
- } else if (trimmed.startsWith("- **Content Type**: `")) {
2002
- const contentType = trimmed.match(/`([^`]+)`/)?.[1];
2003
- if (currentEntry && contentType) {
2004
- currentEntry.attributes.contentType = contentType;
2005
- }
2006
- } else if (trimmed.startsWith("- **Value**: `")) {
2007
- const value = trimmed.substring(14, trimmed.length - 1);
2008
- if (currentEntry) {
2009
- currentEntry.value = value;
2010
- }
2011
- } else if (trimmed.startsWith("- **Value**: [See Appendix ")) {
2012
- const labelMatch = trimmed.match(/Appendix ([A-Z])\]/);
2013
- if (currentEntry && labelMatch && currentKey) {
2014
- currentEntry.appendixLabel = labelMatch[1];
2015
- currentEntry.value = "";
2016
- }
2017
1379
  }
2018
- }
2019
- if (currentSection === "appendix") {
2020
- if (trimmed.startsWith("### Appendix ")) {
2021
- const match = trimmed.match(/### Appendix ([A-Z]): (.+)/);
2022
- if (match) {
2023
- const label = match[1];
2024
- const key = match[2];
2025
- currentAppendixKey = `${label}:${key}`;
2026
- appendixData[currentAppendixKey] = { contentType: "", base64: "" };
2027
- }
2028
- } else if (trimmed.startsWith("**Type**: ")) {
2029
- const contentType = trimmed.substring(10);
2030
- if (currentAppendixKey) {
2031
- appendixData[currentAppendixKey].contentType = contentType;
1380
+ return null;
1381
+ case "edit":
1382
+ if (isDocument && typeof value === "object" && value.find && value.replace !== void 0) {
1383
+ const yText = this.get_document(key);
1384
+ if (yText) {
1385
+ const text = yText.toString();
1386
+ const idx = text.indexOf(value.find);
1387
+ if (idx !== -1) {
1388
+ yText.delete(idx, value.find.length);
1389
+ yText.insert(idx, value.replace);
1390
+ return {
1391
+ result: `Successfully replaced "${value.find}" with "${value.replace}" in ${key}`,
1392
+ key,
1393
+ value: value.replace
1394
+ };
1395
+ }
2032
1396
  }
2033
1397
  }
2034
- }
1398
+ return null;
1399
+ default:
1400
+ return null;
2035
1401
  }
2036
- if (currentKey && currentEntry && currentEntry.attributes) {
2037
- pendingEntries[currentKey] = currentEntry;
2038
- }
2039
- Object.entries(pendingEntries).forEach(([key, entry]) => {
2040
- const appendixLabel = entry.appendixLabel;
2041
- if (appendixLabel) {
2042
- const appendixKey = `${appendixLabel}:${key}`;
2043
- const appendixInfo = appendixData[appendixKey];
2044
- if (appendixInfo && appendixInfo.base64) {
2045
- entry.value = appendixInfo.base64;
2046
- if (!entry.attributes.contentType && appendixInfo.contentType) {
2047
- entry.attributes.contentType = appendixInfo.contentType;
2048
- }
2049
- }
2050
- }
2051
- if (entry.value !== void 0 && entry.attributes) {
2052
- const attrs = entry.attributes;
2053
- if (attrs.tags && attrs.tags.length > 0 && (!attrs.contentTags || attrs.contentTags.length === 0)) {
2054
- attrs.contentTags = [...attrs.tags];
2055
- }
2056
- if (!attrs.systemTags || attrs.systemTags.length === 0) {
2057
- const systemTags = [];
2058
- if (attrs.visible !== false) {
2059
- systemTags.push("prompt");
2060
- }
2061
- if (attrs.readonly) {
2062
- systemTags.push("readonly");
2063
- } else {
2064
- systemTags.push("LLMWrite");
2065
- }
2066
- if (attrs.hardcoded) {
2067
- systemTags.push("protected");
2068
- }
2069
- if (attrs.template) {
2070
- systemTags.push("template");
2071
- }
2072
- attrs.systemTags = this.normalizeSystemTags(systemTags);
2073
- } else {
2074
- attrs.systemTags = this.normalizeSystemTags(attrs.systemTags);
2075
- }
2076
- if (!attrs.contentTags) {
2077
- attrs.contentTags = [];
2078
- }
2079
- if (!attrs.tags) {
2080
- attrs.tags = [...attrs.contentTags];
2081
- }
2082
- const normalizedTags = attrs.systemTags || [];
2083
- attrs.readonly = normalizedTags.includes("readonly") || !normalizedTags.includes("LLMWrite");
2084
- attrs.visible = this.hasSystemPrompt(normalizedTags);
2085
- attrs.hardcoded = normalizedTags.includes("protected");
2086
- attrs.template = normalizedTags.includes("ApplyTemplate") || normalizedTags.includes("template");
2087
- this.stm[key] = {
2088
- value: entry.value,
2089
- attributes: attrs
2090
- };
2091
- }
2092
- });
2093
- this.notifyGlobalListeners();
1402
+ }
1403
+ // Internal method stub for legacy compatibility
1404
+ _setFromRemote(_key, _value, _attributes) {
1405
+ }
1406
+ _deleteFromRemote(_key) {
1407
+ }
1408
+ _clearFromRemote() {
2094
1409
  }
2095
1410
  };
2096
- new MindCache();
2097
1411
 
2098
1412
  // src/cloud/index.ts
2099
1413
  init_CloudAdapter();