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