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