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