mindcache 3.4.2 → 3.4.4
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/README.md +22 -0
- package/dist/{CloudAdapter-D2xxVv4E.d.mts → CloudAdapter-DOvDQswy.d.mts} +72 -23
- package/dist/{CloudAdapter-D2xxVv4E.d.ts → CloudAdapter-DOvDQswy.d.ts} +72 -23
- package/dist/cloud/index.d.mts +2 -2
- package/dist/cloud/index.d.ts +2 -2
- package/dist/cloud/index.js +1205 -794
- package/dist/cloud/index.js.map +1 -1
- package/dist/cloud/index.mjs +1205 -794
- package/dist/cloud/index.mjs.map +1 -1
- package/dist/index.d.mts +4 -31
- package/dist/index.d.ts +4 -31
- package/dist/index.js +1205 -796
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1205 -796
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +33 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.js +2654 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +2624 -0
- package/dist/server.mjs.map +1 -0
- package/docs/mindcache-api.md +148 -0
- package/package.json +8 -2
package/dist/server.js
ADDED
|
@@ -0,0 +1,2654 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
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);
|
|
35
|
+
|
|
36
|
+
var __defProp = Object.defineProperty;
|
|
37
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
38
|
+
var __esm = (fn, res) => function __init() {
|
|
39
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
40
|
+
};
|
|
41
|
+
var __export = (target, all) => {
|
|
42
|
+
for (var name in all)
|
|
43
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/local/IndexedDBAdapter.ts
|
|
47
|
+
var IndexedDBAdapter_exports = {};
|
|
48
|
+
__export(IndexedDBAdapter_exports, {
|
|
49
|
+
IndexedDBAdapter: () => exports.IndexedDBAdapter
|
|
50
|
+
});
|
|
51
|
+
exports.IndexedDBAdapter = void 0;
|
|
52
|
+
var init_IndexedDBAdapter = __esm({
|
|
53
|
+
"src/local/IndexedDBAdapter.ts"() {
|
|
54
|
+
exports.IndexedDBAdapter = class {
|
|
55
|
+
constructor(config = {}) {
|
|
56
|
+
this.config = config;
|
|
57
|
+
this.dbName = config.dbName || "mindcache_db";
|
|
58
|
+
this.storeName = config.storeName || "mindcache_store";
|
|
59
|
+
this.key = config.key || "mindcache_data";
|
|
60
|
+
}
|
|
61
|
+
mindcache = null;
|
|
62
|
+
unsubscribe = null;
|
|
63
|
+
saveTimeout = null;
|
|
64
|
+
db = null;
|
|
65
|
+
dbName;
|
|
66
|
+
storeName;
|
|
67
|
+
key;
|
|
68
|
+
async attach(mc) {
|
|
69
|
+
if (this.mindcache) {
|
|
70
|
+
this.detach();
|
|
71
|
+
}
|
|
72
|
+
this.mindcache = mc;
|
|
73
|
+
await this.initDB();
|
|
74
|
+
await this.load();
|
|
75
|
+
const listener = () => {
|
|
76
|
+
if (this.mindcache) {
|
|
77
|
+
this.scheduleSave();
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
mc.subscribeToAll(listener);
|
|
81
|
+
this.unsubscribe = () => mc.unsubscribeFromAll(listener);
|
|
82
|
+
console.log("\u{1F5C4}\uFE0F IndexedDBAdapter: Attached to MindCache instance");
|
|
83
|
+
}
|
|
84
|
+
detach() {
|
|
85
|
+
if (this.unsubscribe) {
|
|
86
|
+
this.unsubscribe();
|
|
87
|
+
this.unsubscribe = null;
|
|
88
|
+
}
|
|
89
|
+
if (this.saveTimeout) {
|
|
90
|
+
clearTimeout(this.saveTimeout);
|
|
91
|
+
this.saveTimeout = null;
|
|
92
|
+
}
|
|
93
|
+
this.mindcache = null;
|
|
94
|
+
if (this.db) {
|
|
95
|
+
this.db.close();
|
|
96
|
+
this.db = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
initDB() {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const request = indexedDB.open(this.dbName);
|
|
102
|
+
request.onerror = () => {
|
|
103
|
+
console.error("MindCache: IndexedDB error:", request.error);
|
|
104
|
+
reject(request.error);
|
|
105
|
+
};
|
|
106
|
+
request.onsuccess = () => {
|
|
107
|
+
const db = request.result;
|
|
108
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
109
|
+
const currentVersion = db.version;
|
|
110
|
+
db.close();
|
|
111
|
+
const upgradeRequest = indexedDB.open(this.dbName, currentVersion + 1);
|
|
112
|
+
upgradeRequest.onerror = () => {
|
|
113
|
+
console.error("MindCache: IndexedDB upgrade error:", upgradeRequest.error);
|
|
114
|
+
reject(upgradeRequest.error);
|
|
115
|
+
};
|
|
116
|
+
upgradeRequest.onupgradeneeded = () => {
|
|
117
|
+
const upgradeDb = upgradeRequest.result;
|
|
118
|
+
if (!upgradeDb.objectStoreNames.contains(this.storeName)) {
|
|
119
|
+
upgradeDb.createObjectStore(this.storeName);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
upgradeRequest.onsuccess = () => {
|
|
123
|
+
this.db = upgradeRequest.result;
|
|
124
|
+
resolve();
|
|
125
|
+
};
|
|
126
|
+
} else {
|
|
127
|
+
this.db = db;
|
|
128
|
+
resolve();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
request.onupgradeneeded = () => {
|
|
132
|
+
const db = request.result;
|
|
133
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
134
|
+
db.createObjectStore(this.storeName);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
load() {
|
|
140
|
+
if (!this.db || !this.mindcache) {
|
|
141
|
+
return Promise.resolve();
|
|
142
|
+
}
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
try {
|
|
145
|
+
const transaction = this.db.transaction([this.storeName], "readonly");
|
|
146
|
+
const store = transaction.objectStore(this.storeName);
|
|
147
|
+
const request = store.get(this.key);
|
|
148
|
+
request.onsuccess = () => {
|
|
149
|
+
if (request.result) {
|
|
150
|
+
this.mindcache.deserialize(request.result);
|
|
151
|
+
console.log("\u{1F5C4}\uFE0F IndexedDBAdapter: Loaded data from IndexedDB");
|
|
152
|
+
}
|
|
153
|
+
resolve();
|
|
154
|
+
};
|
|
155
|
+
request.onerror = () => {
|
|
156
|
+
console.error("MindCache: Failed to load from IndexedDB:", request.error);
|
|
157
|
+
resolve();
|
|
158
|
+
};
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error("MindCache: Error accessing IndexedDB for load:", error);
|
|
161
|
+
resolve();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
scheduleSave() {
|
|
166
|
+
if (this.saveTimeout) {
|
|
167
|
+
clearTimeout(this.saveTimeout);
|
|
168
|
+
}
|
|
169
|
+
this.saveTimeout = setTimeout(() => {
|
|
170
|
+
this.save();
|
|
171
|
+
this.saveTimeout = null;
|
|
172
|
+
}, this.config.debounceMs ?? 1e3);
|
|
173
|
+
}
|
|
174
|
+
save() {
|
|
175
|
+
if (!this.db || !this.mindcache) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const data = this.mindcache.serialize();
|
|
180
|
+
const transaction = this.db.transaction([this.storeName], "readwrite");
|
|
181
|
+
const store = transaction.objectStore(this.storeName);
|
|
182
|
+
const request = store.put(data, this.key);
|
|
183
|
+
request.onsuccess = () => {
|
|
184
|
+
console.log("\u{1F5C4}\uFE0F IndexedDBAdapter: Saved to IndexedDB");
|
|
185
|
+
};
|
|
186
|
+
request.onerror = () => {
|
|
187
|
+
console.error("MindCache: Failed to save to IndexedDB:", request.error);
|
|
188
|
+
};
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error("MindCache: Error accessing IndexedDB for save:", error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// src/cloud/CloudAdapter.ts
|
|
198
|
+
var CloudAdapter_exports = {};
|
|
199
|
+
__export(CloudAdapter_exports, {
|
|
200
|
+
CloudAdapter: () => exports.CloudAdapter
|
|
201
|
+
});
|
|
202
|
+
var RECONNECT_DELAY, MAX_RECONNECT_DELAY; exports.CloudAdapter = void 0;
|
|
203
|
+
var init_CloudAdapter = __esm({
|
|
204
|
+
"src/cloud/CloudAdapter.ts"() {
|
|
205
|
+
RECONNECT_DELAY = 1e3;
|
|
206
|
+
MAX_RECONNECT_DELAY = 3e4;
|
|
207
|
+
exports.CloudAdapter = class {
|
|
208
|
+
// Track if initial sync is complete
|
|
209
|
+
constructor(config) {
|
|
210
|
+
this.config = config;
|
|
211
|
+
if (!config.baseUrl) {
|
|
212
|
+
throw new Error("MindCache Cloud: baseUrl is required. Please provide the cloud API URL in your configuration.");
|
|
213
|
+
}
|
|
214
|
+
this.setupNetworkDetection();
|
|
215
|
+
}
|
|
216
|
+
ws = null;
|
|
217
|
+
mindcache = null;
|
|
218
|
+
unsubscribe = null;
|
|
219
|
+
reconnectAttempts = 0;
|
|
220
|
+
reconnectTimeout = null;
|
|
221
|
+
_state = "disconnected";
|
|
222
|
+
_isOnline = true;
|
|
223
|
+
// Browser network status
|
|
224
|
+
listeners = {};
|
|
225
|
+
token = null;
|
|
226
|
+
handleOnline = null;
|
|
227
|
+
handleOffline = null;
|
|
228
|
+
_synced = false;
|
|
229
|
+
/** Browser network status - instantly updated via navigator.onLine */
|
|
230
|
+
get isOnline() {
|
|
231
|
+
return this._isOnline;
|
|
232
|
+
}
|
|
233
|
+
setupNetworkDetection() {
|
|
234
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this._isOnline = navigator.onLine;
|
|
238
|
+
this.handleOnline = () => {
|
|
239
|
+
console.log("\u2601\uFE0F CloudAdapter: Network is back online");
|
|
240
|
+
this._isOnline = true;
|
|
241
|
+
this.emit("network_online");
|
|
242
|
+
if (this._state === "disconnected" || this._state === "error") {
|
|
243
|
+
this.connect();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
this.handleOffline = () => {
|
|
247
|
+
console.log("\u2601\uFE0F CloudAdapter: Network went offline");
|
|
248
|
+
this._isOnline = false;
|
|
249
|
+
this.emit("network_offline");
|
|
250
|
+
if (this._state === "connected" || this._state === "connecting") {
|
|
251
|
+
this._state = "disconnected";
|
|
252
|
+
this.emit("disconnected");
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
window.addEventListener("online", this.handleOnline);
|
|
256
|
+
window.addEventListener("offline", this.handleOffline);
|
|
257
|
+
}
|
|
258
|
+
cleanupNetworkDetection() {
|
|
259
|
+
if (typeof window === "undefined") {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (this.handleOnline) {
|
|
263
|
+
window.removeEventListener("online", this.handleOnline);
|
|
264
|
+
}
|
|
265
|
+
if (this.handleOffline) {
|
|
266
|
+
window.removeEventListener("offline", this.handleOffline);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
setToken(token) {
|
|
270
|
+
this.token = token;
|
|
271
|
+
}
|
|
272
|
+
setTokenProvider(provider) {
|
|
273
|
+
this.config.tokenProvider = provider;
|
|
274
|
+
}
|
|
275
|
+
get state() {
|
|
276
|
+
return this._state;
|
|
277
|
+
}
|
|
278
|
+
attach(mc) {
|
|
279
|
+
if (this.mindcache) {
|
|
280
|
+
this.detach();
|
|
281
|
+
}
|
|
282
|
+
this.mindcache = mc;
|
|
283
|
+
mc.doc.on("update", (update, origin) => {
|
|
284
|
+
if (origin !== this && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
285
|
+
const encoder = encoding__namespace.createEncoder();
|
|
286
|
+
syncProtocol__namespace.writeUpdate(encoder, update);
|
|
287
|
+
this.sendBinary(encoding__namespace.toUint8Array(encoder));
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
console.log("\u2601\uFE0F CloudAdapter: Attached to MindCache instance");
|
|
291
|
+
}
|
|
292
|
+
detach() {
|
|
293
|
+
if (this.unsubscribe) {
|
|
294
|
+
this.unsubscribe();
|
|
295
|
+
this.unsubscribe = null;
|
|
296
|
+
}
|
|
297
|
+
this.mindcache = null;
|
|
298
|
+
}
|
|
299
|
+
async fetchTokenWithApiKey() {
|
|
300
|
+
if (!this.config.apiKey) {
|
|
301
|
+
throw new Error("API key is required to fetch token");
|
|
302
|
+
}
|
|
303
|
+
const httpBaseUrl = this.config.baseUrl.replace("wss://", "https://").replace("ws://", "http://");
|
|
304
|
+
const isDelegate = this.config.apiKey.startsWith("del_") && this.config.apiKey.includes(":");
|
|
305
|
+
const authHeader = isDelegate ? `ApiKey ${this.config.apiKey}` : `Bearer ${this.config.apiKey}`;
|
|
306
|
+
const response = await fetch(`${httpBaseUrl}/api/ws-token`, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: {
|
|
309
|
+
"Content-Type": "application/json",
|
|
310
|
+
"Authorization": authHeader
|
|
311
|
+
},
|
|
312
|
+
body: JSON.stringify({
|
|
313
|
+
instanceId: this.config.instanceId,
|
|
314
|
+
permission: "write"
|
|
315
|
+
})
|
|
316
|
+
});
|
|
317
|
+
if (!response.ok) {
|
|
318
|
+
const error = await response.json().catch(() => ({ error: "Failed to get token" }));
|
|
319
|
+
throw new Error(error.error || `Failed to get WebSocket token: ${response.status}`);
|
|
320
|
+
}
|
|
321
|
+
const data = await response.json();
|
|
322
|
+
return data.token;
|
|
323
|
+
}
|
|
324
|
+
async connect() {
|
|
325
|
+
if (this._state === "connecting" || this._state === "connected") {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
this._state = "connecting";
|
|
329
|
+
try {
|
|
330
|
+
if (!this.token) {
|
|
331
|
+
if (this.config.tokenProvider) {
|
|
332
|
+
this.token = await this.config.tokenProvider();
|
|
333
|
+
} else if (this.config.apiKey) {
|
|
334
|
+
this.token = await this.fetchTokenWithApiKey();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
let url = `${this.config.baseUrl}/sync/${this.config.instanceId}`;
|
|
338
|
+
if (this.token) {
|
|
339
|
+
url += `?token=${encodeURIComponent(this.token)}`;
|
|
340
|
+
this.token = null;
|
|
341
|
+
} else {
|
|
342
|
+
throw new Error("MindCache Cloud: No authentication method available. Provide apiKey or tokenProvider.");
|
|
343
|
+
}
|
|
344
|
+
this.ws = new WebSocket(url);
|
|
345
|
+
this.ws.binaryType = "arraybuffer";
|
|
346
|
+
this.setupWebSocket();
|
|
347
|
+
} catch (error) {
|
|
348
|
+
this._state = "error";
|
|
349
|
+
this.emit("error", error);
|
|
350
|
+
this.scheduleReconnect();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
disconnect() {
|
|
354
|
+
if (this.reconnectTimeout) {
|
|
355
|
+
clearTimeout(this.reconnectTimeout);
|
|
356
|
+
this.reconnectTimeout = null;
|
|
357
|
+
}
|
|
358
|
+
if (this.ws) {
|
|
359
|
+
this.ws.close();
|
|
360
|
+
this.ws = null;
|
|
361
|
+
}
|
|
362
|
+
this.cleanupNetworkDetection();
|
|
363
|
+
this._state = "disconnected";
|
|
364
|
+
this.emit("disconnected");
|
|
365
|
+
}
|
|
366
|
+
on(event, listener) {
|
|
367
|
+
if (!this.listeners[event]) {
|
|
368
|
+
this.listeners[event] = [];
|
|
369
|
+
}
|
|
370
|
+
this.listeners[event].push(listener);
|
|
371
|
+
}
|
|
372
|
+
off(event, listener) {
|
|
373
|
+
if (this.listeners[event]) {
|
|
374
|
+
this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
emit(event, ...args) {
|
|
378
|
+
if (this.listeners[event]) {
|
|
379
|
+
this.listeners[event].forEach((listener) => listener(...args));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
setupWebSocket() {
|
|
383
|
+
if (!this.ws) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.ws.onopen = () => {
|
|
387
|
+
if (this.mindcache) {
|
|
388
|
+
const encoder = encoding__namespace.createEncoder();
|
|
389
|
+
syncProtocol__namespace.writeSyncStep1(encoder, this.mindcache.doc);
|
|
390
|
+
this.sendBinary(encoding__namespace.toUint8Array(encoder));
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
this.ws.onmessage = (event) => {
|
|
394
|
+
try {
|
|
395
|
+
if (typeof event.data === "string") {
|
|
396
|
+
const msg = JSON.parse(event.data);
|
|
397
|
+
if (msg.type === "auth_success") {
|
|
398
|
+
this._state = "connected";
|
|
399
|
+
this.reconnectAttempts = 0;
|
|
400
|
+
this.emit("connected");
|
|
401
|
+
} else if (msg.type === "auth_error" || msg.type === "error") {
|
|
402
|
+
this._state = "error";
|
|
403
|
+
this.emit("error", new Error(msg.error));
|
|
404
|
+
} else {
|
|
405
|
+
console.debug("MindCache Cloud: Received message type:", msg.type, msg);
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
const encoder = encoding__namespace.createEncoder();
|
|
409
|
+
const decoder = decoding__namespace.createDecoder(new Uint8Array(event.data));
|
|
410
|
+
if (this.mindcache) {
|
|
411
|
+
const messageType = syncProtocol__namespace.readSyncMessage(decoder, encoder, this.mindcache.doc, this);
|
|
412
|
+
if (encoding__namespace.length(encoder) > 0) {
|
|
413
|
+
this.sendBinary(encoding__namespace.toUint8Array(encoder));
|
|
414
|
+
}
|
|
415
|
+
if (!this._synced && (messageType === 1 || messageType === 2)) {
|
|
416
|
+
this._synced = true;
|
|
417
|
+
this.emit("synced");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
console.error("MindCache Cloud: Failed to handle message:", error);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
this.ws.onclose = () => {
|
|
426
|
+
this._state = "disconnected";
|
|
427
|
+
this.emit("disconnected");
|
|
428
|
+
this.scheduleReconnect();
|
|
429
|
+
};
|
|
430
|
+
this.ws.onerror = () => {
|
|
431
|
+
this._state = "error";
|
|
432
|
+
this.emit("error", new Error("WebSocket connection failed"));
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
sendBinary(data) {
|
|
436
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
437
|
+
this.ws.send(data);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
scheduleReconnect() {
|
|
441
|
+
if (this.reconnectTimeout) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const delay = Math.min(
|
|
445
|
+
RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts),
|
|
446
|
+
MAX_RECONNECT_DELAY
|
|
447
|
+
);
|
|
448
|
+
this.reconnectTimeout = setTimeout(async () => {
|
|
449
|
+
this.reconnectTimeout = null;
|
|
450
|
+
this.reconnectAttempts++;
|
|
451
|
+
this.connect();
|
|
452
|
+
}, delay);
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// src/core/types.ts
|
|
459
|
+
var DEFAULT_KEY_ATTRIBUTES = {
|
|
460
|
+
type: "text",
|
|
461
|
+
contentTags: [],
|
|
462
|
+
systemTags: [],
|
|
463
|
+
// Keys are private by default - explicitly add SystemPrompt/LLMRead/LLMWrite to enable LLM access
|
|
464
|
+
zIndex: 0
|
|
465
|
+
};
|
|
466
|
+
var SystemTagHelpers = {
|
|
467
|
+
/** Check if key is writable by LLM */
|
|
468
|
+
isLLMWritable: (attrs) => attrs.systemTags.includes("LLMWrite"),
|
|
469
|
+
/** Check if key is readable by LLM (in context or via tools) */
|
|
470
|
+
isLLMReadable: (attrs) => attrs.systemTags.includes("SystemPrompt") || attrs.systemTags.includes("LLMRead"),
|
|
471
|
+
/** Check if key is included in system prompt */
|
|
472
|
+
isInSystemPrompt: (attrs) => attrs.systemTags.includes("SystemPrompt"),
|
|
473
|
+
/** Check if key uses template injection */
|
|
474
|
+
hasTemplateInjection: (attrs) => attrs.systemTags.includes("ApplyTemplate")
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// src/core/MarkdownSerializer.ts
|
|
478
|
+
var MarkdownSerializer = class {
|
|
479
|
+
/**
|
|
480
|
+
* Export MindCache data to Markdown format.
|
|
481
|
+
*/
|
|
482
|
+
static toMarkdown(mc) {
|
|
483
|
+
const now = /* @__PURE__ */ new Date();
|
|
484
|
+
const lines = [];
|
|
485
|
+
const appendixEntries = [];
|
|
486
|
+
let appendixCounter = 0;
|
|
487
|
+
lines.push("# MindCache STM Export");
|
|
488
|
+
lines.push("");
|
|
489
|
+
lines.push(`Export Date: ${now.toISOString().split("T")[0]}`);
|
|
490
|
+
lines.push("");
|
|
491
|
+
lines.push("---");
|
|
492
|
+
lines.push("");
|
|
493
|
+
lines.push("## STM Entries");
|
|
494
|
+
lines.push("");
|
|
495
|
+
const sortedKeys = mc.getSortedKeys();
|
|
496
|
+
sortedKeys.forEach((key) => {
|
|
497
|
+
const attributes = mc.get_attributes(key);
|
|
498
|
+
const value = mc.get_value(key);
|
|
499
|
+
lines.push(`### ${key}`);
|
|
500
|
+
const entryType = attributes?.type || "text";
|
|
501
|
+
lines.push(`- **Type**: \`${entryType}\``);
|
|
502
|
+
lines.push(`- **System Tags**: \`${attributes?.systemTags?.join(", ") || "none"}\``);
|
|
503
|
+
lines.push(`- **Z-Index**: \`${attributes?.zIndex ?? 0}\``);
|
|
504
|
+
if (attributes?.contentTags && attributes.contentTags.length > 0) {
|
|
505
|
+
lines.push(`- **Tags**: \`${attributes.contentTags.join("`, `")}\``);
|
|
506
|
+
}
|
|
507
|
+
if (attributes?.contentType) {
|
|
508
|
+
lines.push(`- **Content Type**: \`${attributes.contentType}\``);
|
|
509
|
+
}
|
|
510
|
+
if (entryType === "image" || entryType === "file") {
|
|
511
|
+
const label = String.fromCharCode(65 + appendixCounter);
|
|
512
|
+
appendixCounter++;
|
|
513
|
+
lines.push(`- **Value**: [See Appendix ${label}]`);
|
|
514
|
+
appendixEntries.push({
|
|
515
|
+
key,
|
|
516
|
+
type: entryType,
|
|
517
|
+
contentType: attributes?.contentType || "application/octet-stream",
|
|
518
|
+
base64: value,
|
|
519
|
+
label
|
|
520
|
+
});
|
|
521
|
+
} else if (entryType === "json") {
|
|
522
|
+
lines.push("- **Value**:");
|
|
523
|
+
lines.push("```json");
|
|
524
|
+
try {
|
|
525
|
+
const jsonValue = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
526
|
+
lines.push(jsonValue);
|
|
527
|
+
} catch {
|
|
528
|
+
lines.push(String(value));
|
|
529
|
+
}
|
|
530
|
+
lines.push("```");
|
|
531
|
+
} else {
|
|
532
|
+
lines.push("- **Value**:");
|
|
533
|
+
lines.push("```");
|
|
534
|
+
lines.push(String(value));
|
|
535
|
+
lines.push("```");
|
|
536
|
+
}
|
|
537
|
+
lines.push("");
|
|
538
|
+
});
|
|
539
|
+
if (appendixEntries.length > 0) {
|
|
540
|
+
lines.push("---");
|
|
541
|
+
lines.push("");
|
|
542
|
+
lines.push("## Appendix: Binary Data");
|
|
543
|
+
lines.push("");
|
|
544
|
+
appendixEntries.forEach((entry) => {
|
|
545
|
+
lines.push(`### Appendix ${entry.label}: ${entry.key}`);
|
|
546
|
+
lines.push(`- **Type**: \`${entry.type}\``);
|
|
547
|
+
lines.push(`- **Content Type**: \`${entry.contentType}\``);
|
|
548
|
+
lines.push("- **Base64 Data**:");
|
|
549
|
+
lines.push("```");
|
|
550
|
+
lines.push(entry.base64);
|
|
551
|
+
lines.push("```");
|
|
552
|
+
lines.push("");
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
return lines.join("\n");
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Import Markdown into MindCache data.
|
|
559
|
+
* @param markdown The markdown string to import
|
|
560
|
+
* @param mc The MindCache instance to import into
|
|
561
|
+
* @param merge If false (default), clears existing data before importing
|
|
562
|
+
*/
|
|
563
|
+
static fromMarkdown(markdown, mc, merge = false) {
|
|
564
|
+
const lines = markdown.split("\n");
|
|
565
|
+
let currentKey = null;
|
|
566
|
+
let currentAttributes = {};
|
|
567
|
+
let currentValue = null;
|
|
568
|
+
let inCodeBlock = false;
|
|
569
|
+
let codeBlockContent = [];
|
|
570
|
+
if (!merge) {
|
|
571
|
+
mc.clear();
|
|
572
|
+
}
|
|
573
|
+
for (const line of lines) {
|
|
574
|
+
if (line.startsWith("### ") && !line.startsWith("### Appendix")) {
|
|
575
|
+
if (currentKey && currentValue !== null) {
|
|
576
|
+
mc.set_value(currentKey, currentValue.trim(), currentAttributes);
|
|
577
|
+
}
|
|
578
|
+
currentKey = line.substring(4).trim();
|
|
579
|
+
currentAttributes = {};
|
|
580
|
+
currentValue = null;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (line.startsWith("### Appendix ")) {
|
|
584
|
+
if (currentKey && currentValue !== null) {
|
|
585
|
+
mc.set_value(currentKey, currentValue.trim(), currentAttributes);
|
|
586
|
+
}
|
|
587
|
+
const match = line.match(/### Appendix ([A-Z]): (.+)/);
|
|
588
|
+
if (match) {
|
|
589
|
+
currentKey = match[2];
|
|
590
|
+
currentAttributes = {};
|
|
591
|
+
currentValue = null;
|
|
592
|
+
}
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (line.startsWith("- **Type**:")) {
|
|
596
|
+
const type = line.match(/`(.+)`/)?.[1];
|
|
597
|
+
if (type) {
|
|
598
|
+
currentAttributes.type = type;
|
|
599
|
+
}
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (line.startsWith("- **System Tags**:")) {
|
|
603
|
+
const tagsStr = line.match(/`([^`]+)`/)?.[1] || "";
|
|
604
|
+
if (tagsStr !== "none") {
|
|
605
|
+
currentAttributes.systemTags = tagsStr.split(", ").filter((t) => t);
|
|
606
|
+
}
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (line.startsWith("- **Z-Index**:")) {
|
|
610
|
+
const zIndex = parseInt(line.match(/`(\d+)`/)?.[1] || "0", 10);
|
|
611
|
+
currentAttributes.zIndex = zIndex;
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
if (line.startsWith("- **Tags**:")) {
|
|
615
|
+
const tags = line.match(/`([^`]+)`/g)?.map((t) => t.slice(1, -1)) || [];
|
|
616
|
+
currentAttributes.contentTags = tags;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (line.startsWith("- **Content Type**:")) {
|
|
620
|
+
currentAttributes.contentType = line.match(/`(.+)`/)?.[1];
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (line.startsWith("- **Base64 Data**:")) {
|
|
624
|
+
currentValue = "";
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (line.startsWith("- **Value**:")) {
|
|
628
|
+
const afterValue = line.substring(12).trim();
|
|
629
|
+
if (afterValue.includes("[See Appendix")) {
|
|
630
|
+
if (currentKey) {
|
|
631
|
+
mc.set_value(currentKey, "", currentAttributes);
|
|
632
|
+
}
|
|
633
|
+
currentValue = null;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (afterValue === "") {
|
|
637
|
+
currentValue = "";
|
|
638
|
+
} else if (afterValue === "```" || afterValue === "```json") {
|
|
639
|
+
inCodeBlock = true;
|
|
640
|
+
codeBlockContent = [];
|
|
641
|
+
currentValue = "";
|
|
642
|
+
} else if (afterValue.startsWith("```")) {
|
|
643
|
+
inCodeBlock = true;
|
|
644
|
+
codeBlockContent = [afterValue.substring(3)];
|
|
645
|
+
currentValue = "";
|
|
646
|
+
} else {
|
|
647
|
+
currentValue = afterValue;
|
|
648
|
+
}
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
const trimmedLine = line.trim();
|
|
652
|
+
if (trimmedLine === "```json" || trimmedLine === "```") {
|
|
653
|
+
if (inCodeBlock) {
|
|
654
|
+
inCodeBlock = false;
|
|
655
|
+
if (currentKey && codeBlockContent.length > 0) {
|
|
656
|
+
currentValue = codeBlockContent.join("\n");
|
|
657
|
+
}
|
|
658
|
+
codeBlockContent = [];
|
|
659
|
+
} else {
|
|
660
|
+
inCodeBlock = true;
|
|
661
|
+
codeBlockContent = [];
|
|
662
|
+
}
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (inCodeBlock) {
|
|
666
|
+
codeBlockContent.push(line);
|
|
667
|
+
} else if (currentKey && currentValue !== null) {
|
|
668
|
+
if (line.trim() === "---" || line.startsWith("## Appendix")) {
|
|
669
|
+
mc.set_value(currentKey, currentValue.trim(), currentAttributes);
|
|
670
|
+
currentKey = null;
|
|
671
|
+
currentValue = null;
|
|
672
|
+
currentAttributes = {};
|
|
673
|
+
} else {
|
|
674
|
+
currentValue += "\n" + line;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (currentKey && currentValue !== null) {
|
|
679
|
+
mc.set_value(currentKey, currentValue.trim(), currentAttributes);
|
|
680
|
+
}
|
|
681
|
+
const hasParsedKeys = lines.some((line) => line.startsWith("### ") && !line.startsWith("### Appendix"));
|
|
682
|
+
const isSTMExport = markdown.includes("# MindCache STM Export") || markdown.includes("## STM Entries");
|
|
683
|
+
if (!hasParsedKeys && !isSTMExport && markdown.trim().length > 0) {
|
|
684
|
+
mc.set_value("imported_content", markdown.trim(), {
|
|
685
|
+
type: "text",
|
|
686
|
+
systemTags: ["SystemPrompt", "LLMWrite"],
|
|
687
|
+
// Default assumptions
|
|
688
|
+
zIndex: 0
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Parse markdown and return STM data without applying to a MindCache instance.
|
|
694
|
+
* Useful for validation or preview.
|
|
695
|
+
*/
|
|
696
|
+
static parseMarkdown(markdown) {
|
|
697
|
+
const result = {};
|
|
698
|
+
const lines = markdown.split("\n");
|
|
699
|
+
let currentKey = null;
|
|
700
|
+
let currentAttributes = {};
|
|
701
|
+
let currentValue = null;
|
|
702
|
+
let inCodeBlock = false;
|
|
703
|
+
let codeBlockContent = [];
|
|
704
|
+
const saveEntry = () => {
|
|
705
|
+
if (currentKey && currentValue !== null) {
|
|
706
|
+
result[currentKey] = {
|
|
707
|
+
value: currentValue.trim(),
|
|
708
|
+
attributes: { ...DEFAULT_KEY_ATTRIBUTES, ...currentAttributes }
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
for (const line of lines) {
|
|
713
|
+
if (line.startsWith("### ") && !line.startsWith("### Appendix")) {
|
|
714
|
+
saveEntry();
|
|
715
|
+
currentKey = line.substring(4).trim();
|
|
716
|
+
currentAttributes = {};
|
|
717
|
+
currentValue = null;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (line.startsWith("### Appendix ")) {
|
|
721
|
+
saveEntry();
|
|
722
|
+
const match = line.match(/### Appendix ([A-Z]): (.+)/);
|
|
723
|
+
if (match) {
|
|
724
|
+
currentKey = match[2];
|
|
725
|
+
currentAttributes = {};
|
|
726
|
+
currentValue = null;
|
|
727
|
+
}
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (line.startsWith("- **Type**:")) {
|
|
731
|
+
const type = line.match(/`(.+)`/)?.[1];
|
|
732
|
+
if (type) {
|
|
733
|
+
currentAttributes.type = type;
|
|
734
|
+
}
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (line.startsWith("- **System Tags**:")) {
|
|
738
|
+
const tagsStr = line.match(/`([^`]+)`/)?.[1] || "";
|
|
739
|
+
if (tagsStr !== "none") {
|
|
740
|
+
currentAttributes.systemTags = tagsStr.split(", ").filter((t) => t);
|
|
741
|
+
}
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (line.startsWith("- **Z-Index**:")) {
|
|
745
|
+
const zIndex = parseInt(line.match(/`(\d+)`/)?.[1] || "0", 10);
|
|
746
|
+
currentAttributes.zIndex = zIndex;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (line.startsWith("- **Tags**:")) {
|
|
750
|
+
const tags = line.match(/`([^`]+)`/g)?.map((t) => t.slice(1, -1)) || [];
|
|
751
|
+
currentAttributes.contentTags = tags;
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (line.startsWith("- **Content Type**:")) {
|
|
755
|
+
currentAttributes.contentType = line.match(/`(.+)`/)?.[1];
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (line.startsWith("- **Base64 Data**:")) {
|
|
759
|
+
currentValue = "";
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
if (line.startsWith("- **Value**:")) {
|
|
763
|
+
const afterValue = line.substring(12).trim();
|
|
764
|
+
if (afterValue.includes("[See Appendix")) {
|
|
765
|
+
currentValue = "";
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (afterValue === "" || afterValue === "```" || afterValue === "```json") {
|
|
769
|
+
inCodeBlock = afterValue !== "";
|
|
770
|
+
codeBlockContent = [];
|
|
771
|
+
currentValue = "";
|
|
772
|
+
} else if (afterValue.startsWith("```")) {
|
|
773
|
+
inCodeBlock = true;
|
|
774
|
+
codeBlockContent = [afterValue.substring(3)];
|
|
775
|
+
currentValue = "";
|
|
776
|
+
} else {
|
|
777
|
+
currentValue = afterValue;
|
|
778
|
+
}
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
const trimmedLine = line.trim();
|
|
782
|
+
if (trimmedLine === "```json" || trimmedLine === "```") {
|
|
783
|
+
if (inCodeBlock) {
|
|
784
|
+
inCodeBlock = false;
|
|
785
|
+
if (currentKey && codeBlockContent.length > 0) {
|
|
786
|
+
currentValue = codeBlockContent.join("\n");
|
|
787
|
+
}
|
|
788
|
+
codeBlockContent = [];
|
|
789
|
+
} else {
|
|
790
|
+
inCodeBlock = true;
|
|
791
|
+
codeBlockContent = [];
|
|
792
|
+
}
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
if (inCodeBlock) {
|
|
796
|
+
codeBlockContent.push(line);
|
|
797
|
+
} else if (currentKey && currentValue !== null) {
|
|
798
|
+
if (line.trim() === "---" || line.startsWith("## Appendix")) {
|
|
799
|
+
saveEntry();
|
|
800
|
+
currentKey = null;
|
|
801
|
+
currentValue = null;
|
|
802
|
+
currentAttributes = {};
|
|
803
|
+
} else {
|
|
804
|
+
currentValue += "\n" + line;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
saveEntry();
|
|
809
|
+
const hasParsedKeys = lines.some((line) => line.startsWith("### ") && !line.startsWith("### Appendix"));
|
|
810
|
+
const isSTMExport = markdown.includes("# MindCache STM Export") || markdown.includes("## STM Entries");
|
|
811
|
+
if (!hasParsedKeys && !isSTMExport && markdown.trim().length > 0) {
|
|
812
|
+
result["imported_content"] = {
|
|
813
|
+
value: markdown.trim(),
|
|
814
|
+
attributes: {
|
|
815
|
+
...DEFAULT_KEY_ATTRIBUTES,
|
|
816
|
+
systemTags: ["SystemPrompt", "LLMWrite"]
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
return result;
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
// src/core/AIToolBuilder.ts
|
|
825
|
+
var AIToolBuilder = class _AIToolBuilder {
|
|
826
|
+
/**
|
|
827
|
+
* Sanitize key name for use in tool names
|
|
828
|
+
*/
|
|
829
|
+
static sanitizeKeyForTool(key) {
|
|
830
|
+
return key.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Find original key from sanitized tool name
|
|
834
|
+
*/
|
|
835
|
+
static findKeyFromSanitizedTool(mc, sanitizedKey) {
|
|
836
|
+
for (const key of mc.keys()) {
|
|
837
|
+
if (_AIToolBuilder.sanitizeKeyForTool(key) === sanitizedKey) {
|
|
838
|
+
return key;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return void 0;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Generate Vercel AI SDK compatible tools for writable keys.
|
|
845
|
+
* For document type keys, generates additional tools: append_, insert_, edit_
|
|
846
|
+
*
|
|
847
|
+
* Security: All tools use llm_set_key internally which:
|
|
848
|
+
* - Only modifies VALUES, never attributes/systemTags
|
|
849
|
+
* - Prevents LLMs from escalating privileges
|
|
850
|
+
*/
|
|
851
|
+
static createVercelAITools(mc) {
|
|
852
|
+
const tools = {};
|
|
853
|
+
for (const key of mc.keys()) {
|
|
854
|
+
if (key.startsWith("$")) {
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
if (!mc.keyMatchesContext(key)) {
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
const attributes = mc.get_attributes(key);
|
|
861
|
+
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
862
|
+
if (!isWritable) {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
|
|
866
|
+
const isDocument = attributes?.type === "document";
|
|
867
|
+
tools[`write_${sanitizedKey}`] = {
|
|
868
|
+
description: isDocument ? `Rewrite the entire "${key}" document` : `Write a value to the STM key: ${key}`,
|
|
869
|
+
inputSchema: {
|
|
870
|
+
type: "object",
|
|
871
|
+
properties: {
|
|
872
|
+
value: { type: "string", description: isDocument ? "New document content" : "The value to write" }
|
|
873
|
+
},
|
|
874
|
+
required: ["value"]
|
|
875
|
+
},
|
|
876
|
+
execute: async ({ value }) => {
|
|
877
|
+
const success = mc.llm_set_key(key, value);
|
|
878
|
+
if (success) {
|
|
879
|
+
return {
|
|
880
|
+
result: `Successfully wrote "${value}" to ${key}`,
|
|
881
|
+
key,
|
|
882
|
+
value
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
return {
|
|
886
|
+
result: `Failed to write to ${key} - permission denied or key not found`,
|
|
887
|
+
key,
|
|
888
|
+
error: true
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
if (isDocument) {
|
|
893
|
+
tools[`append_${sanitizedKey}`] = {
|
|
894
|
+
description: `Append text to the end of "${key}" document`,
|
|
895
|
+
inputSchema: {
|
|
896
|
+
type: "object",
|
|
897
|
+
properties: {
|
|
898
|
+
text: { type: "string", description: "Text to append" }
|
|
899
|
+
},
|
|
900
|
+
required: ["text"]
|
|
901
|
+
},
|
|
902
|
+
execute: async ({ text }) => {
|
|
903
|
+
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
904
|
+
return { result: `Permission denied for ${key}`, key, error: true };
|
|
905
|
+
}
|
|
906
|
+
const yText = mc.get_document(key);
|
|
907
|
+
if (yText) {
|
|
908
|
+
yText.insert(yText.length, text);
|
|
909
|
+
return {
|
|
910
|
+
result: `Successfully appended to ${key}`,
|
|
911
|
+
key,
|
|
912
|
+
appended: text
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
return { result: `Document ${key} not found`, key };
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
tools[`insert_${sanitizedKey}`] = {
|
|
919
|
+
description: `Insert text at a position in "${key}" document`,
|
|
920
|
+
inputSchema: {
|
|
921
|
+
type: "object",
|
|
922
|
+
properties: {
|
|
923
|
+
index: { type: "number", description: "Position to insert at (0 = start)" },
|
|
924
|
+
text: { type: "string", description: "Text to insert" }
|
|
925
|
+
},
|
|
926
|
+
required: ["index", "text"]
|
|
927
|
+
},
|
|
928
|
+
execute: async ({ index, text }) => {
|
|
929
|
+
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
930
|
+
return { result: `Permission denied for ${key}`, key, error: true };
|
|
931
|
+
}
|
|
932
|
+
mc.insert_text(key, index, text);
|
|
933
|
+
return {
|
|
934
|
+
result: `Successfully inserted text at position ${index} in ${key}`,
|
|
935
|
+
key,
|
|
936
|
+
index,
|
|
937
|
+
inserted: text
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
tools[`edit_${sanitizedKey}`] = {
|
|
942
|
+
description: `Find and replace text in "${key}" document`,
|
|
943
|
+
inputSchema: {
|
|
944
|
+
type: "object",
|
|
945
|
+
properties: {
|
|
946
|
+
find: { type: "string", description: "Text to find" },
|
|
947
|
+
replace: { type: "string", description: "Replacement text" }
|
|
948
|
+
},
|
|
949
|
+
required: ["find", "replace"]
|
|
950
|
+
},
|
|
951
|
+
execute: async ({ find, replace }) => {
|
|
952
|
+
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
953
|
+
return { result: `Permission denied for ${key}`, key, error: true };
|
|
954
|
+
}
|
|
955
|
+
const yText = mc.get_document(key);
|
|
956
|
+
if (yText) {
|
|
957
|
+
const text = yText.toString();
|
|
958
|
+
const idx = text.indexOf(find);
|
|
959
|
+
if (idx !== -1) {
|
|
960
|
+
yText.delete(idx, find.length);
|
|
961
|
+
yText.insert(idx, replace);
|
|
962
|
+
return {
|
|
963
|
+
result: `Successfully replaced "${find}" with "${replace}" in ${key}`,
|
|
964
|
+
key,
|
|
965
|
+
find,
|
|
966
|
+
replace,
|
|
967
|
+
index: idx
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
return { result: `Text "${find}" not found in ${key}`, key };
|
|
971
|
+
}
|
|
972
|
+
return { result: `Document ${key} not found`, key };
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return tools;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Generate a system prompt containing all visible STM keys and their values.
|
|
981
|
+
* Indicates which tools can be used to modify writable keys.
|
|
982
|
+
*/
|
|
983
|
+
static getSystemPrompt(mc) {
|
|
984
|
+
const lines = [];
|
|
985
|
+
for (const key of mc.keys()) {
|
|
986
|
+
if (key.startsWith("$")) {
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
if (!mc.keyMatchesContext(key)) {
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
const attributes = mc.get_attributes(key);
|
|
993
|
+
const isVisible = attributes?.systemTags?.includes("SystemPrompt") || attributes?.systemTags?.includes("LLMRead");
|
|
994
|
+
if (!isVisible) {
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const value = mc.get_value(key);
|
|
998
|
+
const displayValue = typeof value === "object" ? JSON.stringify(value) : value;
|
|
999
|
+
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
1000
|
+
const isDocument = attributes?.type === "document";
|
|
1001
|
+
const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
|
|
1002
|
+
if (isWritable) {
|
|
1003
|
+
if (isDocument) {
|
|
1004
|
+
lines.push(
|
|
1005
|
+
`${key}: ${displayValue}. Document tools: write_${sanitizedKey}, append_${sanitizedKey}, edit_${sanitizedKey}`
|
|
1006
|
+
);
|
|
1007
|
+
} else {
|
|
1008
|
+
const oldValueHint = displayValue ? ` This tool DOES NOT append \u2014 start your response with the old value (${displayValue})` : "";
|
|
1009
|
+
lines.push(
|
|
1010
|
+
`${key}: ${displayValue}. You can rewrite "${key}" by using the write_${sanitizedKey} tool.${oldValueHint}`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
} else {
|
|
1014
|
+
lines.push(`${key}: ${displayValue}`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return lines.join("\n");
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Execute a tool call by name with the given value.
|
|
1021
|
+
* Returns the result or null if tool not found.
|
|
1022
|
+
*/
|
|
1023
|
+
static executeToolCall(mc, toolName, value) {
|
|
1024
|
+
const match = toolName.match(/^(write|append|insert|edit)_(.+)$/);
|
|
1025
|
+
if (!match) {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
const [, action, sanitizedKey] = match;
|
|
1029
|
+
const key = _AIToolBuilder.findKeyFromSanitizedTool(mc, sanitizedKey);
|
|
1030
|
+
if (!key) {
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
const attributes = mc.get_attributes(key);
|
|
1034
|
+
if (!attributes) {
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
1038
|
+
if (!isWritable) {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
const isDocument = attributes?.type === "document";
|
|
1042
|
+
switch (action) {
|
|
1043
|
+
case "write":
|
|
1044
|
+
if (isDocument) {
|
|
1045
|
+
mc._replaceDocumentText(key, value);
|
|
1046
|
+
} else {
|
|
1047
|
+
mc.set_value(key, value);
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
result: `Successfully wrote "${value}" to ${key}`,
|
|
1051
|
+
key,
|
|
1052
|
+
value
|
|
1053
|
+
};
|
|
1054
|
+
case "append":
|
|
1055
|
+
if (isDocument) {
|
|
1056
|
+
const yText = mc.get_document(key);
|
|
1057
|
+
if (yText) {
|
|
1058
|
+
yText.insert(yText.length, value);
|
|
1059
|
+
return {
|
|
1060
|
+
result: `Successfully appended to ${key}`,
|
|
1061
|
+
key,
|
|
1062
|
+
value
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return null;
|
|
1067
|
+
case "insert":
|
|
1068
|
+
if (isDocument && typeof value === "object" && value.index !== void 0 && value.text) {
|
|
1069
|
+
mc.insert_text(key, value.index, value.text);
|
|
1070
|
+
return {
|
|
1071
|
+
result: `Successfully inserted at position ${value.index} in ${key}`,
|
|
1072
|
+
key,
|
|
1073
|
+
value: value.text
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
return null;
|
|
1077
|
+
case "edit":
|
|
1078
|
+
if (isDocument && typeof value === "object" && value.find && value.replace !== void 0) {
|
|
1079
|
+
const yText = mc.get_document(key);
|
|
1080
|
+
if (yText) {
|
|
1081
|
+
const text = yText.toString();
|
|
1082
|
+
const idx = text.indexOf(value.find);
|
|
1083
|
+
if (idx !== -1) {
|
|
1084
|
+
yText.delete(idx, value.find.length);
|
|
1085
|
+
yText.insert(idx, value.replace);
|
|
1086
|
+
return {
|
|
1087
|
+
result: `Successfully replaced "${value.find}" with "${value.replace}" in ${key}`,
|
|
1088
|
+
key,
|
|
1089
|
+
value: value.replace
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return null;
|
|
1095
|
+
default:
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
// src/core/TagManager.ts
|
|
1102
|
+
var TagManager = class _TagManager {
|
|
1103
|
+
// ============================================
|
|
1104
|
+
// Content Tag Methods
|
|
1105
|
+
// ============================================
|
|
1106
|
+
/**
|
|
1107
|
+
* Add a content tag to a key.
|
|
1108
|
+
* @returns true if the tag was added, false if key doesn't exist or tag already exists
|
|
1109
|
+
*/
|
|
1110
|
+
static addTag(mc, key, tag) {
|
|
1111
|
+
const entryMap = mc.rootMap.get(key);
|
|
1112
|
+
if (!entryMap) {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
const attributes = entryMap.get("attributes");
|
|
1116
|
+
const contentTags = attributes?.contentTags || [];
|
|
1117
|
+
if (contentTags.includes(tag)) {
|
|
1118
|
+
return false;
|
|
1119
|
+
}
|
|
1120
|
+
mc.doc.transact(() => {
|
|
1121
|
+
const newContentTags = [...contentTags, tag];
|
|
1122
|
+
entryMap.set("attributes", {
|
|
1123
|
+
...attributes,
|
|
1124
|
+
contentTags: newContentTags,
|
|
1125
|
+
tags: newContentTags
|
|
1126
|
+
// Sync legacy tags array
|
|
1127
|
+
});
|
|
1128
|
+
});
|
|
1129
|
+
mc.notifyGlobalListeners();
|
|
1130
|
+
return true;
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Remove a content tag from a key.
|
|
1134
|
+
* @returns true if the tag was removed
|
|
1135
|
+
*/
|
|
1136
|
+
static removeTag(mc, key, tag) {
|
|
1137
|
+
const entryMap = mc.rootMap.get(key);
|
|
1138
|
+
if (!entryMap) {
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
const attributes = entryMap.get("attributes");
|
|
1142
|
+
const contentTags = attributes?.contentTags || [];
|
|
1143
|
+
const tagIndex = contentTags.indexOf(tag);
|
|
1144
|
+
if (tagIndex === -1) {
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
mc.doc.transact(() => {
|
|
1148
|
+
const newContentTags = contentTags.filter((t) => t !== tag);
|
|
1149
|
+
entryMap.set("attributes", {
|
|
1150
|
+
...attributes,
|
|
1151
|
+
contentTags: newContentTags,
|
|
1152
|
+
tags: newContentTags
|
|
1153
|
+
// Sync legacy tags array
|
|
1154
|
+
});
|
|
1155
|
+
});
|
|
1156
|
+
mc.notifyGlobalListeners();
|
|
1157
|
+
return true;
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Get all content tags for a key.
|
|
1161
|
+
*/
|
|
1162
|
+
static getTags(mc, key) {
|
|
1163
|
+
const entryMap = mc.rootMap.get(key);
|
|
1164
|
+
if (!entryMap) {
|
|
1165
|
+
return [];
|
|
1166
|
+
}
|
|
1167
|
+
const attributes = entryMap.get("attributes");
|
|
1168
|
+
return attributes?.contentTags || [];
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Get all unique content tags across all keys.
|
|
1172
|
+
*/
|
|
1173
|
+
static getAllTags(mc) {
|
|
1174
|
+
const allTags = /* @__PURE__ */ new Set();
|
|
1175
|
+
for (const [, val] of mc.rootMap) {
|
|
1176
|
+
const entryMap = val;
|
|
1177
|
+
const attributes = entryMap.get("attributes");
|
|
1178
|
+
if (attributes?.contentTags) {
|
|
1179
|
+
attributes.contentTags.forEach((tag) => allTags.add(tag));
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return Array.from(allTags);
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Check if a key has a specific content tag.
|
|
1186
|
+
*/
|
|
1187
|
+
static hasTag(mc, key, tag) {
|
|
1188
|
+
const entryMap = mc.rootMap.get(key);
|
|
1189
|
+
if (!entryMap) {
|
|
1190
|
+
return false;
|
|
1191
|
+
}
|
|
1192
|
+
const attributes = entryMap.get("attributes");
|
|
1193
|
+
return attributes?.contentTags?.includes(tag) || false;
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Get all keys with a specific content tag as formatted string.
|
|
1197
|
+
*/
|
|
1198
|
+
static getTagged(mc, tag) {
|
|
1199
|
+
const entries = [];
|
|
1200
|
+
const keys = mc.getSortedKeys();
|
|
1201
|
+
keys.forEach((key) => {
|
|
1202
|
+
if (_TagManager.hasTag(mc, key, tag)) {
|
|
1203
|
+
entries.push([key, mc.get_value(key)]);
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
return entries.map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Get array of keys with a specific content tag.
|
|
1210
|
+
*/
|
|
1211
|
+
static getKeysByTag(mc, tag) {
|
|
1212
|
+
const keys = mc.getSortedKeys();
|
|
1213
|
+
return keys.filter((key) => _TagManager.hasTag(mc, key, tag));
|
|
1214
|
+
}
|
|
1215
|
+
// ============================================
|
|
1216
|
+
// System Tag Methods (requires system access)
|
|
1217
|
+
// ============================================
|
|
1218
|
+
/**
|
|
1219
|
+
* Add a system tag to a key (requires system access).
|
|
1220
|
+
*/
|
|
1221
|
+
static systemAddTag(mc, key, tag) {
|
|
1222
|
+
if (!mc.hasSystemAccess) {
|
|
1223
|
+
console.warn("MindCache: systemAddTag requires system access level");
|
|
1224
|
+
return false;
|
|
1225
|
+
}
|
|
1226
|
+
const entryMap = mc.rootMap.get(key);
|
|
1227
|
+
if (!entryMap) {
|
|
1228
|
+
return false;
|
|
1229
|
+
}
|
|
1230
|
+
const attributes = entryMap.get("attributes");
|
|
1231
|
+
const systemTags = attributes?.systemTags || [];
|
|
1232
|
+
if (systemTags.includes(tag)) {
|
|
1233
|
+
return false;
|
|
1234
|
+
}
|
|
1235
|
+
mc.doc.transact(() => {
|
|
1236
|
+
const newSystemTags = [...systemTags, tag];
|
|
1237
|
+
const normalizedTags = mc.normalizeSystemTags(newSystemTags);
|
|
1238
|
+
entryMap.set("attributes", {
|
|
1239
|
+
...attributes,
|
|
1240
|
+
systemTags: normalizedTags
|
|
1241
|
+
});
|
|
1242
|
+
});
|
|
1243
|
+
mc.notifyGlobalListeners();
|
|
1244
|
+
return true;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Remove a system tag from a key (requires system access).
|
|
1248
|
+
*/
|
|
1249
|
+
static systemRemoveTag(mc, key, tag) {
|
|
1250
|
+
if (!mc.hasSystemAccess) {
|
|
1251
|
+
console.warn("MindCache: systemRemoveTag requires system access level");
|
|
1252
|
+
return false;
|
|
1253
|
+
}
|
|
1254
|
+
const entryMap = mc.rootMap.get(key);
|
|
1255
|
+
if (!entryMap) {
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1258
|
+
const attributes = entryMap.get("attributes");
|
|
1259
|
+
const systemTags = attributes?.systemTags || [];
|
|
1260
|
+
const tagIndex = systemTags.indexOf(tag);
|
|
1261
|
+
if (tagIndex === -1) {
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1264
|
+
mc.doc.transact(() => {
|
|
1265
|
+
const newSystemTags = systemTags.filter((t) => t !== tag);
|
|
1266
|
+
entryMap.set("attributes", {
|
|
1267
|
+
...attributes,
|
|
1268
|
+
systemTags: newSystemTags
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
mc.notifyGlobalListeners();
|
|
1272
|
+
return true;
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Get all system tags for a key (requires system access).
|
|
1276
|
+
*/
|
|
1277
|
+
static systemGetTags(mc, key) {
|
|
1278
|
+
if (!mc.hasSystemAccess) {
|
|
1279
|
+
console.warn("MindCache: systemGetTags requires system access level");
|
|
1280
|
+
return [];
|
|
1281
|
+
}
|
|
1282
|
+
const entryMap = mc.rootMap.get(key);
|
|
1283
|
+
if (!entryMap) {
|
|
1284
|
+
return [];
|
|
1285
|
+
}
|
|
1286
|
+
const attributes = entryMap.get("attributes");
|
|
1287
|
+
return attributes?.systemTags || [];
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Check if a key has a specific system tag (requires system access).
|
|
1291
|
+
*/
|
|
1292
|
+
static systemHasTag(mc, key, tag) {
|
|
1293
|
+
if (!mc.hasSystemAccess) {
|
|
1294
|
+
console.warn("MindCache: systemHasTag requires system access level");
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
const entryMap = mc.rootMap.get(key);
|
|
1298
|
+
if (!entryMap) {
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
const attributes = entryMap.get("attributes");
|
|
1302
|
+
return attributes?.systemTags?.includes(tag) || false;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Set all system tags for a key at once (requires system access).
|
|
1306
|
+
*/
|
|
1307
|
+
static systemSetTags(mc, key, tags) {
|
|
1308
|
+
if (!mc.hasSystemAccess) {
|
|
1309
|
+
console.warn("MindCache: systemSetTags requires system access level");
|
|
1310
|
+
return false;
|
|
1311
|
+
}
|
|
1312
|
+
const entryMap = mc.rootMap.get(key);
|
|
1313
|
+
if (!entryMap) {
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
mc.doc.transact(() => {
|
|
1317
|
+
const attributes = entryMap.get("attributes");
|
|
1318
|
+
entryMap.set("attributes", {
|
|
1319
|
+
...attributes,
|
|
1320
|
+
systemTags: [...tags]
|
|
1321
|
+
});
|
|
1322
|
+
});
|
|
1323
|
+
mc.notifyGlobalListeners();
|
|
1324
|
+
return true;
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Get all keys with a specific system tag (requires system access).
|
|
1328
|
+
*/
|
|
1329
|
+
static systemGetKeysByTag(mc, tag) {
|
|
1330
|
+
if (!mc.hasSystemAccess) {
|
|
1331
|
+
console.warn("MindCache: systemGetKeysByTag requires system access level");
|
|
1332
|
+
return [];
|
|
1333
|
+
}
|
|
1334
|
+
const keys = mc.getSortedKeys();
|
|
1335
|
+
return keys.filter((key) => _TagManager.systemHasTag(mc, key, tag));
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
|
|
1339
|
+
// src/core/MindCache.ts
|
|
1340
|
+
var MindCache = class {
|
|
1341
|
+
// Public doc for adapter access
|
|
1342
|
+
doc;
|
|
1343
|
+
rootMap;
|
|
1344
|
+
// Key -> EntryMap({value, attributes})
|
|
1345
|
+
// Cache listeners
|
|
1346
|
+
listeners = {};
|
|
1347
|
+
globalListeners = [];
|
|
1348
|
+
// Metadata
|
|
1349
|
+
version = "3.3.2";
|
|
1350
|
+
// Internal flag to prevent sync loops when receiving remote updates
|
|
1351
|
+
// (Less critical with Yjs but kept for API compat)
|
|
1352
|
+
normalizeSystemTags(tags) {
|
|
1353
|
+
const normalized = [];
|
|
1354
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1355
|
+
for (const tag of tags) {
|
|
1356
|
+
if (["SystemPrompt", "LLMRead", "LLMWrite", "ApplyTemplate"].includes(tag)) {
|
|
1357
|
+
if (!seen.has(tag)) {
|
|
1358
|
+
seen.add(tag);
|
|
1359
|
+
normalized.push(tag);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return normalized;
|
|
1364
|
+
}
|
|
1365
|
+
// Cloud sync state
|
|
1366
|
+
_cloudAdapter = null;
|
|
1367
|
+
_connectionState = "disconnected";
|
|
1368
|
+
_isLoaded = true;
|
|
1369
|
+
// Default true for local mode
|
|
1370
|
+
_cloudConfig = null;
|
|
1371
|
+
// Access level for admin operations
|
|
1372
|
+
_accessLevel = "user";
|
|
1373
|
+
// Context filtering (client-local, not persisted)
|
|
1374
|
+
_contextRules = null;
|
|
1375
|
+
_initPromise = null;
|
|
1376
|
+
// Y-IndexedDB provider
|
|
1377
|
+
_idbProvider = null;
|
|
1378
|
+
// Undo Managers Cache
|
|
1379
|
+
_undoManagers = /* @__PURE__ */ new Map();
|
|
1380
|
+
// Global Undo Manager (watches entire rootMap)
|
|
1381
|
+
_globalUndoManager = null;
|
|
1382
|
+
// History tracking
|
|
1383
|
+
_history = [];
|
|
1384
|
+
_historyOptions = { maxEntries: 100, snapshotInterval: 10 };
|
|
1385
|
+
_historyEnabled = false;
|
|
1386
|
+
constructor(options) {
|
|
1387
|
+
this.doc = options?.doc || new Y__namespace.Doc();
|
|
1388
|
+
this.rootMap = this.doc.getMap("mindcache");
|
|
1389
|
+
this.rootMap.observeDeep((events) => {
|
|
1390
|
+
const keysAffected = /* @__PURE__ */ new Set();
|
|
1391
|
+
events.forEach((event) => {
|
|
1392
|
+
if (event.target === this.rootMap) {
|
|
1393
|
+
const mapEvent = event;
|
|
1394
|
+
mapEvent.keysChanged.forEach((key) => keysAffected.add(key));
|
|
1395
|
+
} else if (event.target.parent === this.rootMap) {
|
|
1396
|
+
for (const [key, val] of this.rootMap) {
|
|
1397
|
+
if (val === event.target) {
|
|
1398
|
+
keysAffected.add(key);
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
} else {
|
|
1403
|
+
let current = event.target;
|
|
1404
|
+
while (current && current.parent) {
|
|
1405
|
+
if (current.parent.parent === this.rootMap) {
|
|
1406
|
+
for (const [key, val] of this.rootMap) {
|
|
1407
|
+
if (val === current.parent) {
|
|
1408
|
+
keysAffected.add(key);
|
|
1409
|
+
break;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
break;
|
|
1413
|
+
}
|
|
1414
|
+
current = current.parent;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
keysAffected.forEach((key) => {
|
|
1419
|
+
const entryMap = this.rootMap.get(key);
|
|
1420
|
+
if (entryMap) {
|
|
1421
|
+
const value = entryMap.get("value");
|
|
1422
|
+
const attrs = entryMap.get("attributes");
|
|
1423
|
+
const resolvedValue = attrs?.type === "document" && value instanceof Y__namespace.Text ? value.toString() : value;
|
|
1424
|
+
if (this.listeners[key]) {
|
|
1425
|
+
this.listeners[key].forEach((l) => l(resolvedValue));
|
|
1426
|
+
}
|
|
1427
|
+
} else {
|
|
1428
|
+
if (this.listeners[key]) {
|
|
1429
|
+
this.listeners[key].forEach((l) => l(void 0));
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
this.notifyGlobalListeners();
|
|
1434
|
+
});
|
|
1435
|
+
this.initGlobalUndoManager();
|
|
1436
|
+
if (options?.accessLevel) {
|
|
1437
|
+
this._accessLevel = options.accessLevel;
|
|
1438
|
+
}
|
|
1439
|
+
if (options?.context) {
|
|
1440
|
+
this._contextRules = options.context;
|
|
1441
|
+
}
|
|
1442
|
+
const initPromises = [];
|
|
1443
|
+
if (options?.cloud) {
|
|
1444
|
+
this._cloudConfig = options.cloud;
|
|
1445
|
+
this._isLoaded = false;
|
|
1446
|
+
this._connectionState = "disconnected";
|
|
1447
|
+
initPromises.push(this._initCloud());
|
|
1448
|
+
if (typeof window !== "undefined") {
|
|
1449
|
+
const dbName = `mindcache_cloud_${options.cloud.instanceId}`;
|
|
1450
|
+
initPromises.push(this._initYIndexedDB(dbName));
|
|
1451
|
+
}
|
|
1452
|
+
this.enableHistory(options.history);
|
|
1453
|
+
}
|
|
1454
|
+
if (options?.indexedDB && !options?.cloud) {
|
|
1455
|
+
this._isLoaded = false;
|
|
1456
|
+
initPromises.push(this._initYIndexedDB(options.indexedDB.dbName || "mindcache_yjs_db"));
|
|
1457
|
+
this.enableHistory(options.history);
|
|
1458
|
+
}
|
|
1459
|
+
if (initPromises.length > 0) {
|
|
1460
|
+
this._initPromise = Promise.all(initPromises).then(() => {
|
|
1461
|
+
if (!this._cloudConfig) {
|
|
1462
|
+
this._isLoaded = true;
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
// Helper: Get or Create UndoManager for a key
|
|
1468
|
+
getUndoManager(key) {
|
|
1469
|
+
const entryMap = this.rootMap.get(key);
|
|
1470
|
+
if (!entryMap) {
|
|
1471
|
+
return void 0;
|
|
1472
|
+
}
|
|
1473
|
+
if (!this._undoManagers.has(key)) {
|
|
1474
|
+
const um = new Y__namespace.UndoManager(entryMap, {
|
|
1475
|
+
captureTimeout: 500
|
|
1476
|
+
});
|
|
1477
|
+
this._undoManagers.set(key, um);
|
|
1478
|
+
}
|
|
1479
|
+
return this._undoManagers.get(key);
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Undo changes for a specific key
|
|
1483
|
+
*/
|
|
1484
|
+
undo(key) {
|
|
1485
|
+
const um = this.getUndoManager(key);
|
|
1486
|
+
if (um) {
|
|
1487
|
+
um.undo();
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Redo changes for a specific key
|
|
1492
|
+
*/
|
|
1493
|
+
redo(key) {
|
|
1494
|
+
const um = this.getUndoManager(key);
|
|
1495
|
+
if (um) {
|
|
1496
|
+
um.redo();
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
getHistory(key) {
|
|
1500
|
+
const um = this.getUndoManager(key);
|
|
1501
|
+
if (!um) {
|
|
1502
|
+
return [];
|
|
1503
|
+
}
|
|
1504
|
+
return um.undoStack;
|
|
1505
|
+
}
|
|
1506
|
+
// Initialize global undo manager (watches entire rootMap)
|
|
1507
|
+
initGlobalUndoManager() {
|
|
1508
|
+
if (!this._globalUndoManager) {
|
|
1509
|
+
this._globalUndoManager = new Y__namespace.UndoManager(this.rootMap, {
|
|
1510
|
+
captureTimeout: 500
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Undo all recent local changes (across all keys)
|
|
1516
|
+
* Only undoes YOUR changes, not changes from other users in cloud mode
|
|
1517
|
+
*/
|
|
1518
|
+
undoAll() {
|
|
1519
|
+
this.initGlobalUndoManager();
|
|
1520
|
+
this._globalUndoManager?.undo();
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Redo previously undone local changes
|
|
1524
|
+
*/
|
|
1525
|
+
redoAll() {
|
|
1526
|
+
this.initGlobalUndoManager();
|
|
1527
|
+
this._globalUndoManager?.redo();
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Check if there are changes to undo globally
|
|
1531
|
+
*/
|
|
1532
|
+
canUndoAll() {
|
|
1533
|
+
this.initGlobalUndoManager();
|
|
1534
|
+
return (this._globalUndoManager?.undoStack.length ?? 0) > 0;
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Check if there are changes to redo globally
|
|
1538
|
+
*/
|
|
1539
|
+
canRedoAll() {
|
|
1540
|
+
this.initGlobalUndoManager();
|
|
1541
|
+
return (this._globalUndoManager?.redoStack.length ?? 0) > 0;
|
|
1542
|
+
}
|
|
1543
|
+
// Enable history tracking (called for IndexedDB and Cloud modes)
|
|
1544
|
+
enableHistory(options) {
|
|
1545
|
+
if (this._historyEnabled) {
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
this._historyEnabled = true;
|
|
1549
|
+
if (options) {
|
|
1550
|
+
this._historyOptions = { ...this._historyOptions, ...options };
|
|
1551
|
+
}
|
|
1552
|
+
this.rootMap.observeDeep((events) => {
|
|
1553
|
+
const keysAffected = /* @__PURE__ */ new Set();
|
|
1554
|
+
events.forEach((event) => {
|
|
1555
|
+
if (event.target === this.rootMap) {
|
|
1556
|
+
const mapEvent = event;
|
|
1557
|
+
mapEvent.keysChanged.forEach((key) => keysAffected.add(key));
|
|
1558
|
+
} else {
|
|
1559
|
+
let current = event.target;
|
|
1560
|
+
while (current && current.parent) {
|
|
1561
|
+
if (current.parent === this.rootMap) {
|
|
1562
|
+
for (const [key, val] of this.rootMap) {
|
|
1563
|
+
if (val === current) {
|
|
1564
|
+
keysAffected.add(key);
|
|
1565
|
+
break;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
break;
|
|
1569
|
+
}
|
|
1570
|
+
current = current.parent;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1574
|
+
if (keysAffected.size > 0) {
|
|
1575
|
+
const entry = {
|
|
1576
|
+
id: this.generateId(),
|
|
1577
|
+
timestamp: Date.now(),
|
|
1578
|
+
keysAffected: Array.from(keysAffected)
|
|
1579
|
+
};
|
|
1580
|
+
this._history.push(entry);
|
|
1581
|
+
const max = this._historyOptions.maxEntries || 100;
|
|
1582
|
+
if (this._history.length > max) {
|
|
1583
|
+
this._history = this._history.slice(-max);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
generateId() {
|
|
1589
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Get global history of all changes (available in IndexedDB and Cloud modes)
|
|
1593
|
+
*/
|
|
1594
|
+
getGlobalHistory() {
|
|
1595
|
+
return [...this._history];
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Check if history tracking is enabled
|
|
1599
|
+
*/
|
|
1600
|
+
get historyEnabled() {
|
|
1601
|
+
return this._historyEnabled;
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Restore to a specific version (time travel)
|
|
1605
|
+
* Note: Full implementation requires storing update binaries, which is not yet implemented.
|
|
1606
|
+
* @returns false - not yet fully implemented
|
|
1607
|
+
*/
|
|
1608
|
+
restoreToVersion(_versionId) {
|
|
1609
|
+
console.warn("restoreToVersion: Full implementation requires storing update binaries. Not yet implemented.");
|
|
1610
|
+
return false;
|
|
1611
|
+
}
|
|
1612
|
+
get accessLevel() {
|
|
1613
|
+
return this._accessLevel;
|
|
1614
|
+
}
|
|
1615
|
+
get hasSystemAccess() {
|
|
1616
|
+
return this._accessLevel === "admin";
|
|
1617
|
+
}
|
|
1618
|
+
// ============================================
|
|
1619
|
+
// Context Methods (client-local filtering)
|
|
1620
|
+
// ============================================
|
|
1621
|
+
/**
|
|
1622
|
+
* Check if context filtering is currently active.
|
|
1623
|
+
*/
|
|
1624
|
+
get hasContext() {
|
|
1625
|
+
return this._contextRules !== null;
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Get current context rules, or null if no context is set.
|
|
1629
|
+
*/
|
|
1630
|
+
get_context() {
|
|
1631
|
+
return this._contextRules;
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Set context filtering rules.
|
|
1635
|
+
* When context is set, only keys with ALL specified tags are visible.
|
|
1636
|
+
*
|
|
1637
|
+
* @param rules - Context rules, or array of tags (shorthand for { tags: [...] })
|
|
1638
|
+
*/
|
|
1639
|
+
set_context(rules) {
|
|
1640
|
+
if (Array.isArray(rules)) {
|
|
1641
|
+
this._contextRules = { tags: rules };
|
|
1642
|
+
} else {
|
|
1643
|
+
this._contextRules = rules;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Clear context filtering. All keys become visible again.
|
|
1648
|
+
*/
|
|
1649
|
+
reset_context() {
|
|
1650
|
+
this._contextRules = null;
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Check if a key matches the current context rules.
|
|
1654
|
+
* Returns true if no context is set.
|
|
1655
|
+
*/
|
|
1656
|
+
keyMatchesContext(key) {
|
|
1657
|
+
if (key.startsWith("$")) {
|
|
1658
|
+
return true;
|
|
1659
|
+
}
|
|
1660
|
+
if (!this._contextRules) {
|
|
1661
|
+
return true;
|
|
1662
|
+
}
|
|
1663
|
+
if (this._contextRules.tags.length === 0) {
|
|
1664
|
+
return true;
|
|
1665
|
+
}
|
|
1666
|
+
const entryMap = this.rootMap.get(key);
|
|
1667
|
+
if (!entryMap) {
|
|
1668
|
+
return false;
|
|
1669
|
+
}
|
|
1670
|
+
const attrs = entryMap.get("attributes");
|
|
1671
|
+
const contentTags = attrs?.contentTags || [];
|
|
1672
|
+
return this._contextRules.tags.every((tag) => contentTags.includes(tag));
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Create a new key with optional default tags from context.
|
|
1676
|
+
*
|
|
1677
|
+
* @throws Error if key already exists
|
|
1678
|
+
*/
|
|
1679
|
+
create_key(key, value, attributes) {
|
|
1680
|
+
if (this.rootMap.has(key)) {
|
|
1681
|
+
throw new Error(`Key already exists: ${key}. Use set_value to update.`);
|
|
1682
|
+
}
|
|
1683
|
+
let finalAttributes = { ...attributes };
|
|
1684
|
+
if (this._contextRules) {
|
|
1685
|
+
const contextContentTags = this._contextRules.defaultContentTags || this._contextRules.tags;
|
|
1686
|
+
const existingContentTags = finalAttributes.contentTags || [];
|
|
1687
|
+
finalAttributes.contentTags = [.../* @__PURE__ */ new Set([...existingContentTags, ...contextContentTags])];
|
|
1688
|
+
if (this._contextRules.defaultSystemTags) {
|
|
1689
|
+
const existingSystemTags = finalAttributes.systemTags || [];
|
|
1690
|
+
finalAttributes.systemTags = [.../* @__PURE__ */ new Set([...existingSystemTags, ...this._contextRules.defaultSystemTags])];
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
const entryMap = new Y__namespace.Map();
|
|
1694
|
+
this.rootMap.set(key, entryMap);
|
|
1695
|
+
this.getUndoManager(key);
|
|
1696
|
+
this.doc.transact(() => {
|
|
1697
|
+
const baseAttributes = {
|
|
1698
|
+
...DEFAULT_KEY_ATTRIBUTES,
|
|
1699
|
+
...finalAttributes
|
|
1700
|
+
};
|
|
1701
|
+
if (baseAttributes.systemTags) {
|
|
1702
|
+
baseAttributes.systemTags = this.normalizeSystemTags(baseAttributes.systemTags);
|
|
1703
|
+
}
|
|
1704
|
+
let valueToSet = value;
|
|
1705
|
+
if (baseAttributes.type === "document" && !(valueToSet instanceof Y__namespace.Text)) {
|
|
1706
|
+
valueToSet = new Y__namespace.Text(typeof value === "string" ? value : String(value ?? ""));
|
|
1707
|
+
}
|
|
1708
|
+
entryMap.set("value", valueToSet);
|
|
1709
|
+
entryMap.set("attributes", baseAttributes);
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
async _initCloud() {
|
|
1713
|
+
if (!this._cloudConfig) {
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
const CloudAdapter2 = await this._getCloudAdapterClass();
|
|
1717
|
+
if (!this._cloudConfig.baseUrl) ;
|
|
1718
|
+
const baseUrl = (this._cloudConfig.baseUrl || "https://api.mindcache.dev").replace("https://", "wss://").replace("http://", "ws://");
|
|
1719
|
+
const adapter = new CloudAdapter2({
|
|
1720
|
+
instanceId: this._cloudConfig.instanceId,
|
|
1721
|
+
projectId: this._cloudConfig.projectId || "default",
|
|
1722
|
+
baseUrl,
|
|
1723
|
+
apiKey: this._cloudConfig.apiKey
|
|
1724
|
+
});
|
|
1725
|
+
if (this._cloudConfig.tokenProvider) {
|
|
1726
|
+
adapter.setTokenProvider(this._cloudConfig.tokenProvider);
|
|
1727
|
+
} else if (this._cloudConfig.tokenEndpoint) {
|
|
1728
|
+
const tokenEndpoint = this._cloudConfig.tokenEndpoint;
|
|
1729
|
+
const instanceId = this._cloudConfig.instanceId;
|
|
1730
|
+
let resolvedBaseUrl;
|
|
1731
|
+
if (tokenEndpoint.startsWith("http://") || tokenEndpoint.startsWith("https://")) {
|
|
1732
|
+
resolvedBaseUrl = tokenEndpoint;
|
|
1733
|
+
} else if (typeof window !== "undefined" && window.location?.origin) {
|
|
1734
|
+
resolvedBaseUrl = `${window.location.origin}${tokenEndpoint.startsWith("/") ? "" : "/"}${tokenEndpoint}`;
|
|
1735
|
+
} else {
|
|
1736
|
+
resolvedBaseUrl = tokenEndpoint;
|
|
1737
|
+
}
|
|
1738
|
+
adapter.setTokenProvider(async () => {
|
|
1739
|
+
const url = resolvedBaseUrl.includes("?") ? `${resolvedBaseUrl}&instanceId=${instanceId}` : `${resolvedBaseUrl}?instanceId=${instanceId}`;
|
|
1740
|
+
const response = await fetch(url);
|
|
1741
|
+
if (!response.ok) {
|
|
1742
|
+
throw new Error("Failed to get token");
|
|
1743
|
+
}
|
|
1744
|
+
const data = await response.json();
|
|
1745
|
+
return data.token;
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
adapter.on("connected", () => {
|
|
1749
|
+
this._connectionState = "connected";
|
|
1750
|
+
this.notifyGlobalListeners();
|
|
1751
|
+
});
|
|
1752
|
+
adapter.on("disconnected", () => {
|
|
1753
|
+
this._connectionState = "disconnected";
|
|
1754
|
+
this.notifyGlobalListeners();
|
|
1755
|
+
});
|
|
1756
|
+
adapter.on("error", () => {
|
|
1757
|
+
this._connectionState = "error";
|
|
1758
|
+
this.notifyGlobalListeners();
|
|
1759
|
+
});
|
|
1760
|
+
adapter.on("synced", () => {
|
|
1761
|
+
this._isLoaded = true;
|
|
1762
|
+
this.notifyGlobalListeners();
|
|
1763
|
+
});
|
|
1764
|
+
adapter.attach(this);
|
|
1765
|
+
this._cloudAdapter = adapter;
|
|
1766
|
+
this._connectionState = "connecting";
|
|
1767
|
+
adapter.connect();
|
|
1768
|
+
}
|
|
1769
|
+
async _initYIndexedDB(dbName) {
|
|
1770
|
+
if (typeof window === "undefined") {
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
this._idbProvider = new yIndexeddb.IndexeddbPersistence(dbName, this.doc);
|
|
1774
|
+
return new Promise((resolve) => {
|
|
1775
|
+
if (!this._idbProvider) {
|
|
1776
|
+
return resolve();
|
|
1777
|
+
}
|
|
1778
|
+
this._idbProvider.on("synced", () => {
|
|
1779
|
+
this._isLoaded = true;
|
|
1780
|
+
resolve();
|
|
1781
|
+
});
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
// Legacy IndexedDB method stub
|
|
1785
|
+
async _initIndexedDB(_config) {
|
|
1786
|
+
}
|
|
1787
|
+
async _getIndexedDBAdapterClass() {
|
|
1788
|
+
const { IndexedDBAdapter: IndexedDBAdapter2 } = await Promise.resolve().then(() => (init_IndexedDBAdapter(), IndexedDBAdapter_exports));
|
|
1789
|
+
return IndexedDBAdapter2;
|
|
1790
|
+
}
|
|
1791
|
+
get connectionState() {
|
|
1792
|
+
return this._connectionState;
|
|
1793
|
+
}
|
|
1794
|
+
get isLoaded() {
|
|
1795
|
+
return this._isLoaded;
|
|
1796
|
+
}
|
|
1797
|
+
async _getCloudAdapterClass() {
|
|
1798
|
+
const { CloudAdapter: CloudAdapter2 } = await Promise.resolve().then(() => (init_CloudAdapter(), CloudAdapter_exports));
|
|
1799
|
+
return CloudAdapter2;
|
|
1800
|
+
}
|
|
1801
|
+
get isCloud() {
|
|
1802
|
+
return this._cloudConfig !== null;
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Browser network status. Returns true if online or in local-only mode.
|
|
1806
|
+
* In cloud mode, this updates instantly when network status changes.
|
|
1807
|
+
*/
|
|
1808
|
+
get isOnline() {
|
|
1809
|
+
if (!this._cloudAdapter) {
|
|
1810
|
+
if (typeof navigator !== "undefined") {
|
|
1811
|
+
return navigator.onLine;
|
|
1812
|
+
}
|
|
1813
|
+
return true;
|
|
1814
|
+
}
|
|
1815
|
+
return this._cloudAdapter.isOnline;
|
|
1816
|
+
}
|
|
1817
|
+
async waitForSync() {
|
|
1818
|
+
if (this._isLoaded) {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (this._initPromise) {
|
|
1822
|
+
await this._initPromise;
|
|
1823
|
+
}
|
|
1824
|
+
if (this._isLoaded) {
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
return new Promise((resolve) => {
|
|
1828
|
+
if (this._isLoaded) {
|
|
1829
|
+
return resolve();
|
|
1830
|
+
}
|
|
1831
|
+
const interval = setInterval(() => {
|
|
1832
|
+
if (this._isLoaded) {
|
|
1833
|
+
clearInterval(interval);
|
|
1834
|
+
resolve();
|
|
1835
|
+
}
|
|
1836
|
+
}, 100);
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
disconnect() {
|
|
1840
|
+
if (this._cloudAdapter) {
|
|
1841
|
+
this._cloudAdapter.disconnect();
|
|
1842
|
+
this._cloudAdapter.detach();
|
|
1843
|
+
this._cloudAdapter = null;
|
|
1844
|
+
this._connectionState = "disconnected";
|
|
1845
|
+
}
|
|
1846
|
+
if (this._idbProvider) {
|
|
1847
|
+
this._idbProvider.destroy();
|
|
1848
|
+
this._idbProvider = null;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
// Serialize state
|
|
1852
|
+
serialize() {
|
|
1853
|
+
const json = {};
|
|
1854
|
+
for (const [key, val] of this.rootMap) {
|
|
1855
|
+
const entryMap = val;
|
|
1856
|
+
const attrs = entryMap.get("attributes");
|
|
1857
|
+
let value = entryMap.get("value");
|
|
1858
|
+
if (attrs?.type === "document" && value instanceof Y__namespace.Text) {
|
|
1859
|
+
value = value.toString();
|
|
1860
|
+
}
|
|
1861
|
+
json[key] = {
|
|
1862
|
+
value,
|
|
1863
|
+
attributes: attrs
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
return json;
|
|
1867
|
+
}
|
|
1868
|
+
// Deserialize state (for IndexedDBAdapter compatibility)
|
|
1869
|
+
deserialize(data) {
|
|
1870
|
+
if (!data || typeof data !== "object") {
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
this.doc.transact(() => {
|
|
1874
|
+
for (const key of this.rootMap.keys()) {
|
|
1875
|
+
this.rootMap.delete(key);
|
|
1876
|
+
}
|
|
1877
|
+
for (const [key, entry] of Object.entries(data)) {
|
|
1878
|
+
if (key.startsWith("$")) {
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
const entryMap = new Y__namespace.Map();
|
|
1882
|
+
this.rootMap.set(key, entryMap);
|
|
1883
|
+
entryMap.set("value", entry.value);
|
|
1884
|
+
const attrs = entry.attributes || {};
|
|
1885
|
+
const contentTags = attrs.contentTags || attrs.tags || [];
|
|
1886
|
+
const normalizedAttrs = {
|
|
1887
|
+
type: attrs.type || "text",
|
|
1888
|
+
contentType: attrs.contentType,
|
|
1889
|
+
contentTags,
|
|
1890
|
+
systemTags: this.normalizeSystemTags(attrs.systemTags || []),
|
|
1891
|
+
zIndex: attrs.zIndex ?? 0
|
|
1892
|
+
};
|
|
1893
|
+
entryMap.set("attributes", normalizedAttrs);
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
encodeFileToBase64(file) {
|
|
1898
|
+
return new Promise((resolve, reject) => {
|
|
1899
|
+
if (typeof FileReader !== "undefined") {
|
|
1900
|
+
const reader = new FileReader();
|
|
1901
|
+
reader.onload = () => {
|
|
1902
|
+
const result = reader.result;
|
|
1903
|
+
const base64Data = result.split(",")[1];
|
|
1904
|
+
resolve(base64Data);
|
|
1905
|
+
};
|
|
1906
|
+
reader.onerror = reject;
|
|
1907
|
+
reader.readAsDataURL(file);
|
|
1908
|
+
} else {
|
|
1909
|
+
reject(new Error("FileReader not available in Node.js environment. Use set_base64() method instead."));
|
|
1910
|
+
}
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
createDataUrl(base64Data, contentType) {
|
|
1914
|
+
return `data:${contentType};base64,${base64Data}`;
|
|
1915
|
+
}
|
|
1916
|
+
validateContentType(type, contentType) {
|
|
1917
|
+
if (type === "text" || type === "json") {
|
|
1918
|
+
return true;
|
|
1919
|
+
}
|
|
1920
|
+
if (!contentType) {
|
|
1921
|
+
return false;
|
|
1922
|
+
}
|
|
1923
|
+
if (type === "image") {
|
|
1924
|
+
return contentType.startsWith("image/");
|
|
1925
|
+
}
|
|
1926
|
+
if (type === "file") {
|
|
1927
|
+
return true;
|
|
1928
|
+
}
|
|
1929
|
+
return false;
|
|
1930
|
+
}
|
|
1931
|
+
// InjectSTM replacement (private helper)
|
|
1932
|
+
// Handles special template variables: $date, $time, $version
|
|
1933
|
+
_injectSTMInternal(template, _processingStack) {
|
|
1934
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
1935
|
+
const trimmedKey = key.trim();
|
|
1936
|
+
if (trimmedKey === "$date") {
|
|
1937
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1938
|
+
}
|
|
1939
|
+
if (trimmedKey === "$time") {
|
|
1940
|
+
return (/* @__PURE__ */ new Date()).toTimeString().split(" ")[0];
|
|
1941
|
+
}
|
|
1942
|
+
if (trimmedKey === "$version") {
|
|
1943
|
+
return this.version;
|
|
1944
|
+
}
|
|
1945
|
+
const val = this.get_value(trimmedKey, _processingStack);
|
|
1946
|
+
return val !== void 0 ? String(val) : "";
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Replace {{key}} placeholders in a template string with values from MindCache.
|
|
1951
|
+
* @param template The template string with {{key}} placeholders
|
|
1952
|
+
* @returns The template with placeholders replaced by values
|
|
1953
|
+
*/
|
|
1954
|
+
injectSTM(template) {
|
|
1955
|
+
return this._injectSTMInternal(template, /* @__PURE__ */ new Set());
|
|
1956
|
+
}
|
|
1957
|
+
// Public API Methods
|
|
1958
|
+
getAll() {
|
|
1959
|
+
const result = {};
|
|
1960
|
+
for (const [key] of this.rootMap) {
|
|
1961
|
+
if (this.keyMatchesContext(key)) {
|
|
1962
|
+
result[key] = this.get_value(key);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return result;
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Get all entries with their full structure (value + attributes).
|
|
1969
|
+
* Use this for UI/admin interfaces that need to display key properties.
|
|
1970
|
+
* Unlike serialize(), this format is stable and won't change.
|
|
1971
|
+
*/
|
|
1972
|
+
getAllEntries() {
|
|
1973
|
+
const result = {};
|
|
1974
|
+
for (const [key] of this.rootMap) {
|
|
1975
|
+
if (this.keyMatchesContext(key)) {
|
|
1976
|
+
const value = this.get_value(key);
|
|
1977
|
+
const attributes = this.get_attributes(key);
|
|
1978
|
+
if (attributes) {
|
|
1979
|
+
result[key] = { value, attributes };
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
return result;
|
|
1984
|
+
}
|
|
1985
|
+
get_value(key, _processingStack) {
|
|
1986
|
+
const entryMap = this.rootMap.get(key);
|
|
1987
|
+
if (!entryMap) {
|
|
1988
|
+
return void 0;
|
|
1989
|
+
}
|
|
1990
|
+
if (!this.keyMatchesContext(key)) {
|
|
1991
|
+
return void 0;
|
|
1992
|
+
}
|
|
1993
|
+
const attributes = entryMap.get("attributes");
|
|
1994
|
+
const value = entryMap.get("value");
|
|
1995
|
+
if (attributes?.type === "document" && value instanceof Y__namespace.Text) {
|
|
1996
|
+
return value.toString();
|
|
1997
|
+
}
|
|
1998
|
+
if (_processingStack && _processingStack.has(key)) {
|
|
1999
|
+
return `{{${key}}}`;
|
|
2000
|
+
}
|
|
2001
|
+
if (attributes?.systemTags?.includes("ApplyTemplate")) {
|
|
2002
|
+
if (typeof value === "string") {
|
|
2003
|
+
const stack = _processingStack || /* @__PURE__ */ new Set();
|
|
2004
|
+
stack.add(key);
|
|
2005
|
+
return this._injectSTMInternal(value, stack);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
return value;
|
|
2009
|
+
}
|
|
2010
|
+
get_attributes(key) {
|
|
2011
|
+
const entryMap = this.rootMap.get(key);
|
|
2012
|
+
return entryMap ? entryMap.get("attributes") : void 0;
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Update only the attributes of a key without modifying the value.
|
|
2016
|
+
* Useful for updating tags, permissions etc. on document type keys.
|
|
2017
|
+
* @returns true if attributes were updated, false if key doesn't exist or is protected
|
|
2018
|
+
*/
|
|
2019
|
+
set_attributes(key, attributes) {
|
|
2020
|
+
const entryMap = this.rootMap.get(key);
|
|
2021
|
+
if (!entryMap) {
|
|
2022
|
+
return false;
|
|
2023
|
+
}
|
|
2024
|
+
if (this._contextRules && !this.keyMatchesContext(key)) {
|
|
2025
|
+
throw new Error(`Cannot modify key "${key}": does not match current context`);
|
|
2026
|
+
}
|
|
2027
|
+
this.doc.transact(() => {
|
|
2028
|
+
const existingAttrs = entryMap.get("attributes");
|
|
2029
|
+
const mergedAttrs = { ...existingAttrs, ...attributes };
|
|
2030
|
+
if (mergedAttrs.systemTags) {
|
|
2031
|
+
mergedAttrs.systemTags = this.normalizeSystemTags(mergedAttrs.systemTags);
|
|
2032
|
+
}
|
|
2033
|
+
entryMap.set("attributes", mergedAttrs);
|
|
2034
|
+
const currentValue = entryMap.get("value");
|
|
2035
|
+
if (mergedAttrs.type === "document" && !(currentValue instanceof Y__namespace.Text)) {
|
|
2036
|
+
const strValue = typeof currentValue === "string" ? currentValue : String(currentValue ?? "");
|
|
2037
|
+
entryMap.set("value", new Y__namespace.Text(strValue));
|
|
2038
|
+
this.getUndoManager(key);
|
|
2039
|
+
} else if (mergedAttrs.type !== "document" && currentValue instanceof Y__namespace.Text) {
|
|
2040
|
+
entryMap.set("value", currentValue.toString());
|
|
2041
|
+
}
|
|
2042
|
+
});
|
|
2043
|
+
return true;
|
|
2044
|
+
}
|
|
2045
|
+
set_value(key, value, attributes) {
|
|
2046
|
+
const existingEntry = this.rootMap.get(key);
|
|
2047
|
+
if (existingEntry) {
|
|
2048
|
+
if (this._contextRules && !this.keyMatchesContext(key)) {
|
|
2049
|
+
throw new Error(`Cannot modify key "${key}": does not match current context`);
|
|
2050
|
+
}
|
|
2051
|
+
const existingAttrs = existingEntry.get("attributes");
|
|
2052
|
+
if (existingAttrs?.type === "document") {
|
|
2053
|
+
if (!attributes?.type || attributes.type === "document") {
|
|
2054
|
+
if (typeof value === "string") {
|
|
2055
|
+
this._replaceDocumentText(key, value);
|
|
2056
|
+
}
|
|
2057
|
+
if (attributes) {
|
|
2058
|
+
this.set_attributes(key, attributes);
|
|
2059
|
+
}
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
if (!existingEntry && this._contextRules) {
|
|
2065
|
+
this.create_key(key, value, attributes);
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
if (!existingEntry && attributes?.type === "document") {
|
|
2069
|
+
this.set_document(key, typeof value === "string" ? value : "", attributes);
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
let entryMap = this.rootMap.get(key);
|
|
2073
|
+
const isNewEntry = !entryMap;
|
|
2074
|
+
if (isNewEntry) {
|
|
2075
|
+
entryMap = new Y__namespace.Map();
|
|
2076
|
+
this.rootMap.set(key, entryMap);
|
|
2077
|
+
}
|
|
2078
|
+
this.getUndoManager(key);
|
|
2079
|
+
this.doc.transact(() => {
|
|
2080
|
+
const oldAttributes = isNewEntry ? {
|
|
2081
|
+
...DEFAULT_KEY_ATTRIBUTES
|
|
2082
|
+
} : entryMap.get("attributes");
|
|
2083
|
+
const finalAttributes = attributes ? { ...oldAttributes, ...attributes } : oldAttributes;
|
|
2084
|
+
let normalizedAttributes = { ...finalAttributes };
|
|
2085
|
+
if (finalAttributes.systemTags) {
|
|
2086
|
+
normalizedAttributes.systemTags = this.normalizeSystemTags(finalAttributes.systemTags);
|
|
2087
|
+
}
|
|
2088
|
+
let valueToSet = value;
|
|
2089
|
+
if (normalizedAttributes.type === "document" && !(valueToSet instanceof Y__namespace.Text)) {
|
|
2090
|
+
valueToSet = new Y__namespace.Text(typeof value === "string" ? value : String(value ?? ""));
|
|
2091
|
+
} else if (normalizedAttributes.type !== "document" && valueToSet instanceof Y__namespace.Text) {
|
|
2092
|
+
valueToSet = valueToSet.toString();
|
|
2093
|
+
}
|
|
2094
|
+
entryMap.set("value", valueToSet);
|
|
2095
|
+
entryMap.set("attributes", normalizedAttributes);
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* LLM-safe method to write a value to a key.
|
|
2100
|
+
* This method:
|
|
2101
|
+
* - Only updates the value, never modifies attributes/systemTags
|
|
2102
|
+
* - Checks LLMWrite permission before writing
|
|
2103
|
+
* - Returns false if key doesn't exist or lacks LLMWrite permission
|
|
2104
|
+
*
|
|
2105
|
+
* Used by create_vercel_ai_tools() to prevent LLMs from escalating privileges.
|
|
2106
|
+
*/
|
|
2107
|
+
llm_set_key(key, value) {
|
|
2108
|
+
const entryMap = this.rootMap.get(key);
|
|
2109
|
+
if (!entryMap) {
|
|
2110
|
+
return false;
|
|
2111
|
+
}
|
|
2112
|
+
const attributes = entryMap.get("attributes");
|
|
2113
|
+
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
2114
|
+
return false;
|
|
2115
|
+
}
|
|
2116
|
+
if (attributes.type === "document") {
|
|
2117
|
+
if (typeof value === "string") {
|
|
2118
|
+
this._replaceDocumentText(key, value);
|
|
2119
|
+
}
|
|
2120
|
+
return true;
|
|
2121
|
+
}
|
|
2122
|
+
this.doc.transact(() => {
|
|
2123
|
+
entryMap.set("value", value);
|
|
2124
|
+
});
|
|
2125
|
+
return true;
|
|
2126
|
+
}
|
|
2127
|
+
delete_key(key) {
|
|
2128
|
+
this.rootMap.delete(key);
|
|
2129
|
+
}
|
|
2130
|
+
clear() {
|
|
2131
|
+
const keys = Array.from(this.rootMap.keys());
|
|
2132
|
+
this.doc.transact(() => {
|
|
2133
|
+
keys.forEach((k) => this.rootMap.delete(k));
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
// ============================================
|
|
2137
|
+
// Restored Methods (from v2.x)
|
|
2138
|
+
// ============================================
|
|
2139
|
+
/**
|
|
2140
|
+
* Check if a key exists in MindCache.
|
|
2141
|
+
*/
|
|
2142
|
+
has(key) {
|
|
2143
|
+
if (!this.rootMap.has(key)) {
|
|
2144
|
+
return false;
|
|
2145
|
+
}
|
|
2146
|
+
return this.keyMatchesContext(key);
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Delete a key from MindCache.
|
|
2150
|
+
* @returns true if the key existed and was deleted
|
|
2151
|
+
*/
|
|
2152
|
+
delete(key) {
|
|
2153
|
+
if (!this.rootMap.has(key)) {
|
|
2154
|
+
return false;
|
|
2155
|
+
}
|
|
2156
|
+
this.rootMap.delete(key);
|
|
2157
|
+
this.notifyGlobalListeners();
|
|
2158
|
+
if (this.listeners[key]) {
|
|
2159
|
+
this.listeners[key].forEach((listener) => listener(void 0));
|
|
2160
|
+
}
|
|
2161
|
+
return true;
|
|
2162
|
+
}
|
|
2163
|
+
/** @deprecated Use get_value instead */
|
|
2164
|
+
get(key) {
|
|
2165
|
+
return this.get_value(key);
|
|
2166
|
+
}
|
|
2167
|
+
/** @deprecated Use set_value instead */
|
|
2168
|
+
set(key, value) {
|
|
2169
|
+
this.set_value(key, value);
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Update multiple values at once from an object.
|
|
2173
|
+
* @deprecated Use set_value for individual keys
|
|
2174
|
+
*/
|
|
2175
|
+
update(data) {
|
|
2176
|
+
this.doc.transact(() => {
|
|
2177
|
+
for (const [key, value] of Object.entries(data)) {
|
|
2178
|
+
this.set_value(key, value);
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2181
|
+
this.notifyGlobalListeners();
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Get the number of keys in MindCache.
|
|
2185
|
+
*/
|
|
2186
|
+
size() {
|
|
2187
|
+
let count = 0;
|
|
2188
|
+
for (const [key] of this.rootMap) {
|
|
2189
|
+
if (this.keyMatchesContext(key)) {
|
|
2190
|
+
count++;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
return count;
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Get all keys in MindCache.
|
|
2197
|
+
*/
|
|
2198
|
+
keys() {
|
|
2199
|
+
const keys = [];
|
|
2200
|
+
for (const [key] of this.rootMap) {
|
|
2201
|
+
if (this.keyMatchesContext(key)) {
|
|
2202
|
+
keys.push(key);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
return keys;
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Get all values in MindCache.
|
|
2209
|
+
*/
|
|
2210
|
+
values() {
|
|
2211
|
+
const result = [];
|
|
2212
|
+
for (const [key] of this.rootMap) {
|
|
2213
|
+
if (this.keyMatchesContext(key)) {
|
|
2214
|
+
result.push(this.get_value(key));
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return result;
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Get all key-value entries.
|
|
2221
|
+
*/
|
|
2222
|
+
entries() {
|
|
2223
|
+
const result = [];
|
|
2224
|
+
for (const [key] of this.rootMap) {
|
|
2225
|
+
if (this.keyMatchesContext(key)) {
|
|
2226
|
+
result.push([key, this.get_value(key)]);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
return result;
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Unsubscribe from key changes.
|
|
2233
|
+
* @deprecated Use the cleanup function returned by subscribe() instead
|
|
2234
|
+
*/
|
|
2235
|
+
unsubscribe(key, listener) {
|
|
2236
|
+
if (this.listeners[key]) {
|
|
2237
|
+
this.listeners[key] = this.listeners[key].filter((l) => l !== listener);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Get the STM as a formatted string for LLM context.
|
|
2242
|
+
* @deprecated Use get_system_prompt() instead
|
|
2243
|
+
*/
|
|
2244
|
+
getSTM() {
|
|
2245
|
+
return this.get_system_prompt();
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Get the STM as an object with values directly (no attributes).
|
|
2249
|
+
* @deprecated Use getAll() for full STM format
|
|
2250
|
+
*/
|
|
2251
|
+
getSTMObject() {
|
|
2252
|
+
const result = {};
|
|
2253
|
+
for (const [key] of this.rootMap) {
|
|
2254
|
+
result[key] = this.get_value(key);
|
|
2255
|
+
}
|
|
2256
|
+
return result;
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Add a content tag to a key.
|
|
2260
|
+
* @returns true if the tag was added, false if key doesn't exist or tag already exists
|
|
2261
|
+
*/
|
|
2262
|
+
addTag(key, tag) {
|
|
2263
|
+
return TagManager.addTag(this, key, tag);
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Remove a content tag from a key.
|
|
2267
|
+
* @returns true if the tag was removed
|
|
2268
|
+
*/
|
|
2269
|
+
removeTag(key, tag) {
|
|
2270
|
+
return TagManager.removeTag(this, key, tag);
|
|
2271
|
+
}
|
|
2272
|
+
/**
|
|
2273
|
+
* Get all content tags for a key.
|
|
2274
|
+
*/
|
|
2275
|
+
getTags(key) {
|
|
2276
|
+
return TagManager.getTags(this, key);
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Get all unique content tags across all keys.
|
|
2280
|
+
*/
|
|
2281
|
+
getAllTags() {
|
|
2282
|
+
return TagManager.getAllTags(this);
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Check if a key has a specific content tag.
|
|
2286
|
+
*/
|
|
2287
|
+
hasTag(key, tag) {
|
|
2288
|
+
return TagManager.hasTag(this, key, tag);
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Get all keys with a specific content tag as formatted string.
|
|
2292
|
+
*/
|
|
2293
|
+
getTagged(tag) {
|
|
2294
|
+
return TagManager.getTagged(this, tag);
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Get array of keys with a specific content tag.
|
|
2298
|
+
*/
|
|
2299
|
+
getKeysByTag(tag) {
|
|
2300
|
+
return TagManager.getKeysByTag(this, tag);
|
|
2301
|
+
}
|
|
2302
|
+
// ============================================
|
|
2303
|
+
// System Tag Methods (requires system access level)
|
|
2304
|
+
// ============================================
|
|
2305
|
+
/**
|
|
2306
|
+
* Add a system tag to a key (requires system access).
|
|
2307
|
+
* System tags: 'SystemPrompt', 'LLMRead', 'LLMWrite', 'ApplyTemplate'
|
|
2308
|
+
*/
|
|
2309
|
+
systemAddTag(key, tag) {
|
|
2310
|
+
return TagManager.systemAddTag(this, key, tag);
|
|
2311
|
+
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Remove a system tag from a key (requires system access).
|
|
2314
|
+
*/
|
|
2315
|
+
systemRemoveTag(key, tag) {
|
|
2316
|
+
return TagManager.systemRemoveTag(this, key, tag);
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Get all system tags for a key (requires system access).
|
|
2320
|
+
*/
|
|
2321
|
+
systemGetTags(key) {
|
|
2322
|
+
return TagManager.systemGetTags(this, key);
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Check if a key has a specific system tag (requires system access).
|
|
2326
|
+
*/
|
|
2327
|
+
systemHasTag(key, tag) {
|
|
2328
|
+
return TagManager.systemHasTag(this, key, tag);
|
|
2329
|
+
}
|
|
2330
|
+
/**
|
|
2331
|
+
* Set all system tags for a key at once (requires system access).
|
|
2332
|
+
*/
|
|
2333
|
+
systemSetTags(key, tags) {
|
|
2334
|
+
return TagManager.systemSetTags(this, key, tags);
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Get all keys with a specific system tag (requires system access).
|
|
2338
|
+
*/
|
|
2339
|
+
systemGetKeysByTag(tag) {
|
|
2340
|
+
return TagManager.systemGetKeysByTag(this, tag);
|
|
2341
|
+
}
|
|
2342
|
+
/**
|
|
2343
|
+
* Helper to get sorted keys (by zIndex).
|
|
2344
|
+
* Respects context filtering when set.
|
|
2345
|
+
*/
|
|
2346
|
+
getSortedKeys() {
|
|
2347
|
+
const entries = [];
|
|
2348
|
+
for (const [key, val] of this.rootMap) {
|
|
2349
|
+
if (!this.keyMatchesContext(key)) {
|
|
2350
|
+
continue;
|
|
2351
|
+
}
|
|
2352
|
+
const entryMap = val;
|
|
2353
|
+
const attributes = entryMap.get("attributes");
|
|
2354
|
+
entries.push({ key, zIndex: attributes?.zIndex ?? 0 });
|
|
2355
|
+
}
|
|
2356
|
+
return entries.sort((a, b) => a.zIndex - b.zIndex).map((e) => e.key);
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
2359
|
+
* Serialize to JSON string.
|
|
2360
|
+
*/
|
|
2361
|
+
toJSON() {
|
|
2362
|
+
return JSON.stringify(this.serialize());
|
|
2363
|
+
}
|
|
2364
|
+
/**
|
|
2365
|
+
* Deserialize from JSON string.
|
|
2366
|
+
*/
|
|
2367
|
+
fromJSON(jsonString) {
|
|
2368
|
+
try {
|
|
2369
|
+
const data = JSON.parse(jsonString);
|
|
2370
|
+
this.deserialize(data);
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
console.error("MindCache: Failed to deserialize JSON:", error);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Export to Markdown format.
|
|
2377
|
+
*/
|
|
2378
|
+
toMarkdown() {
|
|
2379
|
+
return MarkdownSerializer.toMarkdown(this);
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Import from Markdown format.
|
|
2383
|
+
* @param markdown The markdown string to import
|
|
2384
|
+
* @param merge If false (default), clears existing data before importing. If true, merges with existing data.
|
|
2385
|
+
*/
|
|
2386
|
+
fromMarkdown(markdown, merge = false) {
|
|
2387
|
+
MarkdownSerializer.fromMarkdown(markdown, this, merge);
|
|
2388
|
+
}
|
|
2389
|
+
/**
|
|
2390
|
+
* Set base64 binary data.
|
|
2391
|
+
*/
|
|
2392
|
+
set_base64(key, base64Data, contentType, type = "file", attributes) {
|
|
2393
|
+
if (!this.validateContentType(type, contentType)) {
|
|
2394
|
+
throw new Error(`Invalid content type ${contentType} for type ${type}`);
|
|
2395
|
+
}
|
|
2396
|
+
const fileAttributes = {
|
|
2397
|
+
type,
|
|
2398
|
+
contentType,
|
|
2399
|
+
...attributes
|
|
2400
|
+
};
|
|
2401
|
+
this.set_value(key, base64Data, fileAttributes);
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Add an image from base64 data.
|
|
2405
|
+
*/
|
|
2406
|
+
add_image(key, base64Data, contentType = "image/jpeg", attributes) {
|
|
2407
|
+
if (!contentType.startsWith("image/")) {
|
|
2408
|
+
throw new Error(`Invalid image content type: ${contentType}. Must start with 'image/'`);
|
|
2409
|
+
}
|
|
2410
|
+
this.set_base64(key, base64Data, contentType, "image", attributes);
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Get the data URL for an image or file key.
|
|
2414
|
+
*/
|
|
2415
|
+
get_data_url(key) {
|
|
2416
|
+
const entryMap = this.rootMap.get(key);
|
|
2417
|
+
if (!entryMap) {
|
|
2418
|
+
return void 0;
|
|
2419
|
+
}
|
|
2420
|
+
const attributes = entryMap.get("attributes");
|
|
2421
|
+
if (attributes?.type !== "image" && attributes?.type !== "file") {
|
|
2422
|
+
return void 0;
|
|
2423
|
+
}
|
|
2424
|
+
if (!attributes?.contentType) {
|
|
2425
|
+
return void 0;
|
|
2426
|
+
}
|
|
2427
|
+
const value = entryMap.get("value");
|
|
2428
|
+
return this.createDataUrl(value, attributes.contentType);
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Get the base64 data for an image or file key.
|
|
2432
|
+
*/
|
|
2433
|
+
get_base64(key) {
|
|
2434
|
+
const entryMap = this.rootMap.get(key);
|
|
2435
|
+
if (!entryMap) {
|
|
2436
|
+
return void 0;
|
|
2437
|
+
}
|
|
2438
|
+
const attributes = entryMap.get("attributes");
|
|
2439
|
+
if (attributes?.type !== "image" && attributes?.type !== "file") {
|
|
2440
|
+
return void 0;
|
|
2441
|
+
}
|
|
2442
|
+
return entryMap.get("value");
|
|
2443
|
+
}
|
|
2444
|
+
// File methods
|
|
2445
|
+
async set_file(key, file, attributes) {
|
|
2446
|
+
const base64 = await this.encodeFileToBase64(file);
|
|
2447
|
+
const dataUrl = this.createDataUrl(base64, file.type);
|
|
2448
|
+
this.set_value(key, dataUrl, {
|
|
2449
|
+
...attributes,
|
|
2450
|
+
type: "file",
|
|
2451
|
+
contentType: file.type
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
set_image(key, file, attributes) {
|
|
2455
|
+
return this.set_file(key, file, {
|
|
2456
|
+
...attributes,
|
|
2457
|
+
type: "image"
|
|
2458
|
+
// Override to image
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
// Document methods for collaborative editing
|
|
2462
|
+
/**
|
|
2463
|
+
* Create or get a collaborative document key.
|
|
2464
|
+
* Uses Y.Text for character-level concurrent editing.
|
|
2465
|
+
*
|
|
2466
|
+
* Note: This exposes Yjs Y.Text directly for editor bindings (y-quill, y-codemirror, etc.)
|
|
2467
|
+
*/
|
|
2468
|
+
set_document(key, initialText, attributes) {
|
|
2469
|
+
let entryMap = this.rootMap.get(key);
|
|
2470
|
+
if (!entryMap) {
|
|
2471
|
+
entryMap = new Y__namespace.Map();
|
|
2472
|
+
this.rootMap.set(key, entryMap);
|
|
2473
|
+
const yText = new Y__namespace.Text(initialText || "");
|
|
2474
|
+
entryMap.set("value", yText);
|
|
2475
|
+
entryMap.set("attributes", {
|
|
2476
|
+
...DEFAULT_KEY_ATTRIBUTES,
|
|
2477
|
+
type: "document",
|
|
2478
|
+
contentTags: [],
|
|
2479
|
+
systemTags: ["SystemPrompt", "LLMWrite"],
|
|
2480
|
+
tags: [],
|
|
2481
|
+
zIndex: 0,
|
|
2482
|
+
...attributes
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
this.getUndoManager(key);
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Get the Y.Text object for a document key.
|
|
2489
|
+
* Use this to bind to editors (Quill, CodeMirror, Monaco, etc.)
|
|
2490
|
+
*
|
|
2491
|
+
* @returns Y.Text or undefined if key doesn't exist or isn't a document
|
|
2492
|
+
*/
|
|
2493
|
+
get_document(key) {
|
|
2494
|
+
const entryMap = this.rootMap.get(key);
|
|
2495
|
+
if (!entryMap) {
|
|
2496
|
+
return void 0;
|
|
2497
|
+
}
|
|
2498
|
+
const attrs = entryMap.get("attributes");
|
|
2499
|
+
if (attrs?.type !== "document") {
|
|
2500
|
+
return void 0;
|
|
2501
|
+
}
|
|
2502
|
+
const value = entryMap.get("value");
|
|
2503
|
+
if (value instanceof Y__namespace.Text) {
|
|
2504
|
+
return value;
|
|
2505
|
+
}
|
|
2506
|
+
return void 0;
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* Insert text at a position in a document key.
|
|
2510
|
+
*/
|
|
2511
|
+
insert_text(key, index, text) {
|
|
2512
|
+
const yText = this.get_document(key);
|
|
2513
|
+
if (yText) {
|
|
2514
|
+
yText.insert(index, text);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Delete text from a document key.
|
|
2519
|
+
*/
|
|
2520
|
+
delete_text(key, index, length2) {
|
|
2521
|
+
const yText = this.get_document(key);
|
|
2522
|
+
if (yText) {
|
|
2523
|
+
yText.delete(index, length2);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Replace all text in a document key.
|
|
2528
|
+
* Uses diff-based updates when changes are < diffThreshold (default 80%).
|
|
2529
|
+
* This preserves concurrent edits and provides better undo granularity.
|
|
2530
|
+
*/
|
|
2531
|
+
_replaceDocumentText(key, newText, diffThreshold = 0.8) {
|
|
2532
|
+
const yText = this.get_document(key);
|
|
2533
|
+
if (!yText) {
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
const oldText = yText.toString();
|
|
2537
|
+
if (oldText === newText) {
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
if (oldText.length === 0) {
|
|
2541
|
+
yText.insert(0, newText);
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
const diffs = diff__default.default(oldText, newText);
|
|
2545
|
+
let changedChars = 0;
|
|
2546
|
+
for (const [op, text] of diffs) {
|
|
2547
|
+
if (op !== 0) {
|
|
2548
|
+
changedChars += text.length;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
const changeRatio = changedChars / Math.max(oldText.length, newText.length);
|
|
2552
|
+
if (changeRatio > diffThreshold) {
|
|
2553
|
+
this.doc.transact(() => {
|
|
2554
|
+
yText.delete(0, yText.length);
|
|
2555
|
+
yText.insert(0, newText);
|
|
2556
|
+
});
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
this.doc.transact(() => {
|
|
2560
|
+
let cursor = 0;
|
|
2561
|
+
for (const [op, text] of diffs) {
|
|
2562
|
+
if (op === 0) {
|
|
2563
|
+
cursor += text.length;
|
|
2564
|
+
} else if (op === -1) {
|
|
2565
|
+
yText.delete(cursor, text.length);
|
|
2566
|
+
} else if (op === 1) {
|
|
2567
|
+
yText.insert(cursor, text);
|
|
2568
|
+
cursor += text.length;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
// ... (subscribe methods)
|
|
2574
|
+
subscribe(key, listener) {
|
|
2575
|
+
if (!this.listeners[key]) {
|
|
2576
|
+
this.listeners[key] = [];
|
|
2577
|
+
}
|
|
2578
|
+
this.listeners[key].push(listener);
|
|
2579
|
+
return () => {
|
|
2580
|
+
this.listeners[key] = this.listeners[key].filter((l) => l !== listener);
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
subscribeToAll(listener) {
|
|
2584
|
+
this.globalListeners.push(listener);
|
|
2585
|
+
return () => {
|
|
2586
|
+
this.globalListeners = this.globalListeners.filter((l) => l !== listener);
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
unsubscribeFromAll(listener) {
|
|
2590
|
+
this.globalListeners = this.globalListeners.filter((l) => l !== listener);
|
|
2591
|
+
}
|
|
2592
|
+
notifyGlobalListeners() {
|
|
2593
|
+
this.globalListeners.forEach((l) => l());
|
|
2594
|
+
}
|
|
2595
|
+
// Sanitize key name for use in tool names
|
|
2596
|
+
sanitizeKeyForTool(key) {
|
|
2597
|
+
return AIToolBuilder.sanitizeKeyForTool(key);
|
|
2598
|
+
}
|
|
2599
|
+
// Find original key from sanitized tool name
|
|
2600
|
+
findKeyFromSanitizedTool(sanitizedKey) {
|
|
2601
|
+
return AIToolBuilder.findKeyFromSanitizedTool(this, sanitizedKey);
|
|
2602
|
+
}
|
|
2603
|
+
/**
|
|
2604
|
+
* Generate Vercel AI SDK compatible tools for writable keys.
|
|
2605
|
+
* For document type keys, generates additional tools: append_, insert_, edit_
|
|
2606
|
+
*
|
|
2607
|
+
* Security: All tools use llm_set_key internally which:
|
|
2608
|
+
* - Only modifies VALUES, never attributes/systemTags
|
|
2609
|
+
* - Prevents LLMs from escalating privileges
|
|
2610
|
+
*/
|
|
2611
|
+
create_vercel_ai_tools() {
|
|
2612
|
+
return AIToolBuilder.createVercelAITools(this);
|
|
2613
|
+
}
|
|
2614
|
+
/**
|
|
2615
|
+
* @deprecated Use create_vercel_ai_tools() instead
|
|
2616
|
+
*/
|
|
2617
|
+
get_aisdk_tools() {
|
|
2618
|
+
return this.create_vercel_ai_tools();
|
|
2619
|
+
}
|
|
2620
|
+
/**
|
|
2621
|
+
* Generate a system prompt containing all visible STM keys and their values.
|
|
2622
|
+
* Indicates which tools can be used to modify writable keys.
|
|
2623
|
+
*/
|
|
2624
|
+
get_system_prompt() {
|
|
2625
|
+
return AIToolBuilder.getSystemPrompt(this);
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Execute a tool call by name with the given value.
|
|
2629
|
+
* Returns the result or null if tool not found.
|
|
2630
|
+
*/
|
|
2631
|
+
executeToolCall(toolName, value) {
|
|
2632
|
+
return AIToolBuilder.executeToolCall(this, toolName, value);
|
|
2633
|
+
}
|
|
2634
|
+
// Internal method stub for legacy compatibility
|
|
2635
|
+
_setFromRemote(_key, _value, _attributes) {
|
|
2636
|
+
}
|
|
2637
|
+
_deleteFromRemote(_key) {
|
|
2638
|
+
}
|
|
2639
|
+
_clearFromRemote() {
|
|
2640
|
+
}
|
|
2641
|
+
};
|
|
2642
|
+
|
|
2643
|
+
// src/cloud/index.ts
|
|
2644
|
+
init_CloudAdapter();
|
|
2645
|
+
init_CloudAdapter();
|
|
2646
|
+
|
|
2647
|
+
// src/local/index.ts
|
|
2648
|
+
init_IndexedDBAdapter();
|
|
2649
|
+
|
|
2650
|
+
exports.DEFAULT_KEY_ATTRIBUTES = DEFAULT_KEY_ATTRIBUTES;
|
|
2651
|
+
exports.MindCache = MindCache;
|
|
2652
|
+
exports.SystemTagHelpers = SystemTagHelpers;
|
|
2653
|
+
//# sourceMappingURL=server.js.map
|
|
2654
|
+
//# sourceMappingURL=server.js.map
|