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